| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- #!/usr/bin/env node
- /**
- * 批量更新所有 api-config.json 的 tokenConfig 为 v2(多用户动态解析版)
- *
- * 变更:
- * - 移除硬编码的 qrCodeUrl(含固定 authid)
- * - 新增 paymentUrlResolution:AI Agent 动态解析用户 → 拼充值 URL
- * - onMissing/onBalanceInsufficient 的 action 改为 resolveUserThenShowPayment
- *
- * 用法:
- * node scripts/tools/update-token-config-v2.js # 更新项目源码
- * node scripts/tools/update-token-config-v2.js --deployed # 更新已部署的 skills
- * node scripts/tools/update-token-config-v2.js --both # 两者都更新
- * node scripts/tools/update-token-config-v2.js --dry-run # 预览
- */
- const fs = require('fs');
- const path = require('path');
- const os = require('os');
- // ============================================
- // 新版 tokenConfig(多用户动态解析)
- // ============================================
- const NEW_TOKEN_CONFIG = {
- type: "bearer",
- configFile: "~/.openclaw/voc-credentials.json",
- tokenField: "vocToken",
- currentToken: "r:858b3ee92314d5447d1fc3cdc10462d7",
- resolutionOrder: ["configFile", "currentToken"],
- apigId: "Vo3ROWEvDy",
- paymentUrlResolution: {
- description: "动态构建充值URL:通过当前token查出用户objectId,拼接到充值页面",
- steps: [
- "1. 从 voc-credentials.json 或 currentToken 获取当前 session token",
- "2. 调用 GET https://server.fmode.cn/parse/users/me (Header: X-Parse-Application-Id: ncloudmaster, X-Parse-Session-Token: {token})获取用户 objectId",
- "3. 拼接充值URL: https://pwa.fmode.cn/apig-pay.html?user={objectId}&apigid=Vo3ROWEvDy&fun_id=HOkkX72PMF",
- "4. 将该URL展示给用户,引导扫码支付"
- ],
- userResolveEndpoint: "https://server.fmode.cn/parse/users/me",
- userResolveHeaders: {
- "X-Parse-Application-Id": "ncloudmaster",
- "X-Parse-Session-Token": "{vocToken}"
- },
- paymentBaseUrl: "https://pwa.fmode.cn/apig-pay.html",
- paymentParams: {
- user: "{resolvedUserId}",
- apigid: "Vo3ROWEvDy",
- fun_id: "HOkkX72PMF"
- },
- balanceCheckEndpoint: "https://server.fmode.cn/api/apig/getApig"
- },
- onMissing: {
- action: "resolveUserThenShowPayment",
- title: "扫码开通 VOC-AI 数据服务",
- message: "此 Skill 需要有效的 API Token。请按以下步骤操作:\n1. 如果你已有 Token,请告诉我,我会保存到 ~/.openclaw/voc-credentials.json\n2. 如果没有 Token,我会引导你扫码充值开通"
- },
- onBalanceInsufficient: {
- action: "resolveUserThenShowPayment",
- title: "VOC-AI Token 余额不足,请扫码充值",
- message: "当前 Token 余额不足,我将为你生成专属充值链接,扫码支付后即可继续使用。"
- }
- };
- const NEW_ERROR_HANDLING = {
- balanceInsufficient: {
- conditions: [
- { responseField: "code", operator: "in", value: [-2, -3, -10, 402, 429] },
- { responseField: "msg", operator: "contains", value: ["余额不足", "insufficient", "balance", "quota"] },
- { responseField: "message", operator: "contains", value: ["余额不足", "insufficient", "balance"] }
- ],
- matchMode: "any",
- trigger: "tokenConfig.onBalanceInsufficient"
- },
- unauthorized: {
- conditions: [
- { responseField: "code", operator: "in", value: [401, 403] },
- { responseField: "msg", operator: "contains", value: ["unauthorized", "token", "invalid", "auth"] }
- ],
- matchMode: "any",
- trigger: "tokenConfig.onMissing"
- }
- };
- // ============================================
- // 参数解析
- // ============================================
- const args = process.argv.slice(2);
- const dryRun = args.includes('--dry-run');
- const updateDeployed = args.includes('--deployed') || args.includes('--both');
- const updateSource = !args.includes('--deployed') || args.includes('--both');
- const PROJECT_ROOT = path.resolve(__dirname, '..', '..');
- const DEPLOYED_DIR = path.join(os.homedir(), '.openclaw', 'skills');
- const CATEGORIES = [
- 'voc', 'social-media', 'competitor-analysis', 'review-analysis',
- 'synthesis', 'social-voc', 'douyin', 'video-creation', 'jimeng',
- 'payment', 'test', 'workshop'
- ];
- // ============================================
- // 核心逻辑
- // ============================================
- function findAllApiConfigs(rootDir, isFlat) {
- const results = [];
- if (isFlat) {
- // Deployed: flat structure ~/.openclaw/skills/{skill}/api-config.json
- if (!fs.existsSync(rootDir)) return results;
- fs.readdirSync(rootDir).forEach(skill => {
- const cfgPath = path.join(rootDir, skill, 'api-config.json');
- if (fs.existsSync(cfgPath)) {
- results.push({ path: cfgPath, skill });
- }
- });
- } else {
- // Source: categorized structure {category}/{skill}/api-config.json
- CATEGORIES.forEach(cat => {
- const catDir = path.join(rootDir, cat);
- if (!fs.existsSync(catDir)) return;
- fs.readdirSync(catDir).forEach(skill => {
- const cfgPath = path.join(catDir, skill, 'api-config.json');
- if (fs.existsSync(cfgPath)) {
- results.push({ path: cfgPath, skill, category: cat });
- }
- });
- });
- }
- return results;
- }
- function updateConfigFile(filePath, skillName) {
- let content;
- try {
- const raw = fs.readFileSync(filePath, 'utf-8');
- // Remove BOM if present
- content = raw.charCodeAt(0) === 0xFEFF ? raw.slice(1) : raw;
- } catch (e) {
- console.log(` [ERROR] Cannot read: ${skillName} - ${e.message}`);
- return false;
- }
- let config;
- try {
- config = JSON.parse(content);
- } catch (e) {
- console.log(` [ERROR] Invalid JSON: ${skillName}`);
- return false;
- }
- if (!config.tokenConfig) {
- return false; // No tokenConfig, skip
- }
- // Replace tokenConfig and errorHandling
- config.tokenConfig = { ...NEW_TOKEN_CONFIG };
- config.errorHandling = { ...NEW_ERROR_HANDLING };
- if (!dryRun) {
- fs.writeFileSync(filePath, JSON.stringify(config, null, 2), 'utf-8');
- }
- return true;
- }
- // ============================================
- // 主流程
- // ============================================
- console.log('');
- console.log(' ====================================================');
- console.log(' tokenConfig v2 Batch Updater (多用户动态解析)');
- console.log(' ====================================================');
- if (dryRun) console.log(' [DRY RUN] Preview only');
- console.log('');
- let totalUpdated = 0;
- let totalSkipped = 0;
- if (updateSource) {
- console.log(' [Source] Updating project files...');
- const sourceConfigs = findAllApiConfigs(PROJECT_ROOT, false);
- sourceConfigs.forEach(({ path: p, skill, category }) => {
- if (updateConfigFile(p, skill)) {
- console.log(` [OK] ${category}/${skill}`);
- totalUpdated++;
- } else {
- totalSkipped++;
- }
- });
- console.log('');
- }
- if (updateDeployed) {
- console.log(' [Deployed] Updating ~/.openclaw/skills/...');
- const deployedConfigs = findAllApiConfigs(DEPLOYED_DIR, true);
- deployedConfigs.forEach(({ path: p, skill }) => {
- if (updateConfigFile(p, skill)) {
- console.log(` [OK] ${skill}`);
- totalUpdated++;
- } else {
- totalSkipped++;
- }
- });
- console.log('');
- }
- console.log(' ====================================================');
- console.log(` Updated: ${totalUpdated}, Skipped: ${totalSkipped}`);
- console.log(' ====================================================');
- if (dryRun) console.log(' Remove --dry-run to apply.');
- console.log('');
|