#!/usr/bin/env node /** * 烟测:验证 skill-executor 的模板替换 + Authorization 注入是否正确 * 读取某个 skill 的 api-config.json,按实际逻辑构造 headers/url, * 发一次真实请求,观察响应头部是否被后端识别。 * * 用法: node scripts/tools/test-skill-auth.js * 例: node scripts/tools/test-skill-auth.js social-media/instagram-search */ const fs = require('fs'); const os = require('os'); const path = require('path'); const skillDir = process.argv[2]; if (!skillDir) { console.log('用法: node scripts/tools/test-skill-auth.js '); process.exit(1); } // 复用 executor 的模板函数 function resolveTemplate(template, vars = {}) { if (typeof template !== 'string') return template; return template .replace(/\$\{(\w+)\}/g, (_, key) => vars[key] ?? '') .replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? '') .replace(/\{(\w+)\}/g, (_, key) => vars[key] ?? ''); } function resolveTemplateDeep(obj, vars = {}) { if (obj == null) return obj; if (typeof obj === 'string') return resolveTemplate(obj, vars); if (Array.isArray(obj)) return obj.map(v => resolveTemplateDeep(v, vars)); if (typeof obj === 'object') { const out = {}; for (const [k, v] of Object.entries(obj)) out[k] = resolveTemplateDeep(v, vars); return out; } return obj; } const configPath = path.resolve(__dirname, '..', '..', skillDir, 'api-config.json'); const cfg = JSON.parse(fs.readFileSync(configPath, 'utf-8')); console.log(`Skill: ${cfg.displayName || cfg.name}`); // 读取 token const credPath = path.join(os.homedir(), '.openclaw', 'voc-credentials.json'); const cred = fs.existsSync(credPath) ? JSON.parse(fs.readFileSync(credPath, 'utf-8')) : {}; const vocToken = cred.vocToken || ''; console.log(`Token: ${vocToken ? vocToken.slice(0, 6) + '...' + vocToken.slice(-4) : '(无)'}`); if (!cfg.endpoint) { console.error('此 skill 不是直接 endpoint 型(可能是 orchestration pipeline),跳过'); process.exit(0); } const vars = { vocToken, TIKHUB_TOKEN: vocToken, token: vocToken, sessionToken: vocToken, query: 'nike', select: 'users', keyword: 'nike', count: 1, cursor: 0, username: 'nike', user_id: '1234567890' }; const headers = resolveTemplateDeep(cfg.endpoint.headers || {}, vars); const query = resolveTemplateDeep(cfg.endpoint.queryParams || cfg.requestTransform?.queryParams || {}, vars); const url = resolveTemplate(cfg.endpoint.url, vars); // 兜底 Authorization if (vocToken && !headers.Authorization && !headers.authorization) { headers.Authorization = `Bearer ${vocToken}`; } // 拼 URL let finalUrl = url; const qsEntries = Object.entries(query).filter(([, v]) => v !== undefined && v !== ''); if (qsEntries.length > 0) { const qs = new URLSearchParams(); qsEntries.forEach(([k, v]) => qs.append(k, String(v))); finalUrl += (finalUrl.includes('?') ? '&' : '?') + qs.toString(); } console.log(''); console.log('─── 构造后的请求 ───'); console.log(`${cfg.endpoint.method || 'GET'} ${finalUrl}`); console.log('Headers:'); for (const [k, v] of Object.entries(headers)) { if (k.toLowerCase() === 'authorization' && vocToken) { console.log(` ${k}: Bearer ${vocToken.slice(0, 6)}...${vocToken.slice(-4)}`); } else { console.log(` ${k}: ${v}`); } } // 发一次真实请求 console.log(''); console.log('─── 实际调用 ───'); (async () => { const t0 = Date.now(); try { const resp = await fetch(finalUrl, { method: cfg.endpoint.method || 'GET', headers, body: ['POST','PUT','PATCH'].includes((cfg.endpoint.method||'GET').toUpperCase()) ? JSON.stringify(cfg.endpoint.body || {}) : undefined }); const ms = Date.now() - t0; const text = await resp.text(); let data; try { data = JSON.parse(text); } catch { data = text.slice(0, 200); } console.log(`HTTP ${resp.status} (${ms}ms)`); console.log('Response:', typeof data === 'string' ? data : JSON.stringify(data).slice(0, 300)); console.log(''); console.log('─── 诊断 ───'); const msg = (data?.mess || data?.message || data?.error || ''); if (resp.ok) { console.log('✅ 完整链路通!token 有效 + 余额充足'); } else if (msg.includes('当前用户不存在') || msg.includes('无使用权限') || data?.code === 209) { console.log('✅ 模板注入正确!后端收到了 Bearer token 并尝试解析用户'); console.log(' (报错原因是 token 过期,不是注入失败)'); } else if (msg.includes('请输入') || msg.includes('sessionToken') || msg.includes('API_KEY')) { console.log('❌ 模板注入失败!后端没有收到 Bearer token'); } else { console.log('⚠️ 其他错误:', msg); } } catch (e) { console.error('请求异常:', e.message); } })();