@vocmarket/voc-skill 0.3.30 → 0.3.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/bin/claude-voc.js +233 -34
- package/mcp/catalog/params/taobao.json +36 -1
- package/mcp/catalog/voc-social-endpoints.json +152 -10
- package/mcp/src/core/api-catalog.js +6 -2
- package/mcp/src/core/credentials.js +4 -0
- package/package.json +3 -2
- package/scripts/acceptance.js +1 -0
- package/scripts/installer-acceptance.js +79 -0
- package/scripts/smoke-mcp.js +49 -0
- package/skill-package-manifest.json +4 -4
- package/skills/voc-api-catalog/SKILL.md +12 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "voc-intelligence",
|
|
3
3
|
"description": "VOC intelligence skills for Claude Code: end-to-end VOC business workflows, Xiaohongshu and Douyin trend reports, live social VOC collection, Fmode Doubao image analysis, token/recharge checks, evidence-card outputs, prioritized issue pools, single-problem deep dives, competitor maps, 7-day content plans, VOC-backed speaking script co-creation, boss/operator action plans, and multi-turn memory.",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.32",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "nkkj-BrainHack"
|
|
7
7
|
}
|
package/bin/claude-voc.js
CHANGED
|
@@ -4,12 +4,17 @@ const os = require('os');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { spawnSync } = require('child_process');
|
|
6
6
|
const { ensureActivated } = require('../mcp/src/core/activation');
|
|
7
|
+
const { readNewApiToken } = require('../mcp/src/core/credentials');
|
|
7
8
|
|
|
8
9
|
const SOURCE_ROOT = path.resolve(__dirname, '..');
|
|
9
10
|
const WORKSPACE_ROOT = process.cwd();
|
|
10
11
|
const DEFAULT_TARGET = path.join(os.homedir(), '.claude', 'plugins', 'voc-intelligence');
|
|
11
12
|
const WORKSPACE_TARGET = path.join(WORKSPACE_ROOT, '.claude', 'plugins', 'voc-intelligence');
|
|
12
13
|
const WORKSPACE_PLUGINS_ROOT = path.join(WORKSPACE_ROOT, '.claude', 'plugins');
|
|
14
|
+
const CODEX_PROJECT_TARGET = path.join(WORKSPACE_ROOT, '.codex', 'plugins', 'voc-intelligence');
|
|
15
|
+
const CODEX_GLOBAL_TARGET = path.join(os.homedir(), '.codex', 'plugins', 'voc-intelligence');
|
|
16
|
+
const PORTABLE_WORKSPACE_TARGET = path.join(WORKSPACE_ROOT, '.vocmarket', 'plugins', 'voc-intelligence');
|
|
17
|
+
const VOCMARKET_CREDENTIALS = path.join(os.homedir(), '.vocmarket', 'credentials.json');
|
|
13
18
|
const MIN_NODE_MAJOR = 18;
|
|
14
19
|
|
|
15
20
|
const EXCLUDED_DIRS = new Set([
|
|
@@ -35,7 +40,7 @@ function usage() {
|
|
|
35
40
|
'Usage:',
|
|
36
41
|
' claude-voc install',
|
|
37
42
|
' claude-voc workspace',
|
|
38
|
-
' claude-voc workspace --activate',
|
|
43
|
+
' claude-voc workspace --activate --channel <name>',
|
|
39
44
|
' claude-voc install --workspace --smoke',
|
|
40
45
|
' claude-voc check',
|
|
41
46
|
' claude-voc smoke',
|
|
@@ -54,7 +59,8 @@ function usage() {
|
|
|
54
59
|
' --skip-install Skip npm install after copying files',
|
|
55
60
|
' --smoke Run sample/preference/MCP smoke checks after install',
|
|
56
61
|
' --activate 安装后按 token 状态激活:无 token 弹付费/登录页,有 token 直接放行(外部 AI IDE 专用,不影响 --smoke)',
|
|
57
|
-
' --channel <name>
|
|
62
|
+
' --channel <name> 安装渠道(claude-code/codex/workbuddy/fmode-studio)',
|
|
63
|
+
' --scope <name> Codex 安装范围(project/global,默认 project)',
|
|
58
64
|
' --help, -h Show help'
|
|
59
65
|
].join('\n');
|
|
60
66
|
}
|
|
@@ -72,6 +78,9 @@ function parseArgs(argv) {
|
|
|
72
78
|
smoke: false,
|
|
73
79
|
activate: false,
|
|
74
80
|
channel: 'external',
|
|
81
|
+
scope: 'project',
|
|
82
|
+
targetExplicit: false,
|
|
83
|
+
workspaceRequested: first === 'workspace' || first === 'install-workspace',
|
|
75
84
|
force: false,
|
|
76
85
|
help: false
|
|
77
86
|
};
|
|
@@ -85,10 +94,13 @@ function parseArgs(argv) {
|
|
|
85
94
|
const token = argv[i];
|
|
86
95
|
if (token === '--target') {
|
|
87
96
|
opts.target = argv[++i];
|
|
97
|
+
opts.targetExplicit = true;
|
|
88
98
|
} else if (token.startsWith('--target=')) {
|
|
89
99
|
opts.target = token.slice('--target='.length);
|
|
100
|
+
opts.targetExplicit = true;
|
|
90
101
|
} else if (token === '--workspace') {
|
|
91
102
|
opts.target = WORKSPACE_TARGET;
|
|
103
|
+
opts.workspaceRequested = true;
|
|
92
104
|
} else if (token === '--force') {
|
|
93
105
|
opts.force = true;
|
|
94
106
|
} else if (token === '--skip-install') {
|
|
@@ -101,15 +113,48 @@ function parseArgs(argv) {
|
|
|
101
113
|
opts.channel = argv[++i];
|
|
102
114
|
} else if (token.startsWith('--channel=')) {
|
|
103
115
|
opts.channel = token.slice('--channel='.length);
|
|
116
|
+
} else if (token === '--scope') {
|
|
117
|
+
opts.scope = argv[++i];
|
|
118
|
+
} else if (token.startsWith('--scope=')) {
|
|
119
|
+
opts.scope = token.slice('--scope='.length);
|
|
104
120
|
} else if (token === '--help' || token === '-h') {
|
|
105
121
|
opts.help = true;
|
|
106
122
|
}
|
|
107
123
|
}
|
|
108
124
|
|
|
125
|
+
opts.channel = normalizeInstallChannel(opts.channel);
|
|
126
|
+
opts.scope = normalizeInstallScope(opts.channel, opts.scope);
|
|
127
|
+
if (opts.workspaceRequested && !opts.targetExplicit) {
|
|
128
|
+
opts.target = defaultTargetFor(opts.channel, opts.scope);
|
|
129
|
+
}
|
|
109
130
|
opts.target = path.resolve(expandHome(opts.target));
|
|
110
131
|
return opts;
|
|
111
132
|
}
|
|
112
133
|
|
|
134
|
+
function normalizeInstallChannel(value) {
|
|
135
|
+
const channel = String(value || 'claude-code').trim().toLowerCase();
|
|
136
|
+
if (channel === 'external') return 'claude-code';
|
|
137
|
+
if (!['claude-code', 'codex', 'workbuddy', 'fmode-studio'].includes(channel)) {
|
|
138
|
+
throw new Error(`Unsupported install channel: ${channel}`);
|
|
139
|
+
}
|
|
140
|
+
return channel;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function normalizeInstallScope(channel, value) {
|
|
144
|
+
if (channel !== 'codex') return 'project';
|
|
145
|
+
const scope = String(value || 'project').trim().toLowerCase();
|
|
146
|
+
if (!['project', 'global'].includes(scope)) {
|
|
147
|
+
throw new Error(`Unsupported Codex install scope: ${scope}`);
|
|
148
|
+
}
|
|
149
|
+
return scope;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function defaultTargetFor(channel, scope) {
|
|
153
|
+
if (channel === 'codex') return scope === 'global' ? CODEX_GLOBAL_TARGET : CODEX_PROJECT_TARGET;
|
|
154
|
+
if (channel === 'workbuddy') return PORTABLE_WORKSPACE_TARGET;
|
|
155
|
+
return WORKSPACE_TARGET;
|
|
156
|
+
}
|
|
157
|
+
|
|
113
158
|
function run(command, args, cwd) {
|
|
114
159
|
const useCmd = process.platform === 'win32' && command === 'npm';
|
|
115
160
|
const executable = useCmd ? 'cmd.exe' : command;
|
|
@@ -184,8 +229,13 @@ function isSafeWorkspaceTarget(targetDir) {
|
|
|
184
229
|
return isInside(WORKSPACE_PLUGINS_ROOT, targetDir);
|
|
185
230
|
}
|
|
186
231
|
|
|
232
|
+
function isSafeManagedTarget(targetDir) {
|
|
233
|
+
return [WORKSPACE_TARGET, CODEX_PROJECT_TARGET, CODEX_GLOBAL_TARGET, PORTABLE_WORKSPACE_TARGET]
|
|
234
|
+
.some(candidate => path.resolve(candidate) === path.resolve(targetDir));
|
|
235
|
+
}
|
|
236
|
+
|
|
187
237
|
function canOverwriteTarget(targetDir, opts) {
|
|
188
|
-
return opts.force || isSafeDefaultTarget(targetDir) || isSafeWorkspaceTarget(targetDir);
|
|
238
|
+
return opts.force || isSafeDefaultTarget(targetDir) || isSafeWorkspaceTarget(targetDir) || isSafeManagedTarget(targetDir);
|
|
189
239
|
}
|
|
190
240
|
|
|
191
241
|
function shouldCopyEntry(entry) {
|
|
@@ -262,9 +312,8 @@ function readJsonIfExists(filePath) {
|
|
|
262
312
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
263
313
|
}
|
|
264
314
|
|
|
265
|
-
function
|
|
266
|
-
const
|
|
267
|
-
const existing = readJsonIfExists(mcpConfigPath);
|
|
315
|
+
function writeJsonMcpConfig(configPath, targetDir) {
|
|
316
|
+
const existing = readJsonIfExists(configPath);
|
|
268
317
|
const mcpConfig = {
|
|
269
318
|
...existing,
|
|
270
319
|
mcpServers: {
|
|
@@ -276,7 +325,99 @@ function writeWorkspaceMcpConfig(targetDir) {
|
|
|
276
325
|
}
|
|
277
326
|
}
|
|
278
327
|
};
|
|
279
|
-
|
|
328
|
+
ensureDir(path.dirname(configPath));
|
|
329
|
+
fs.writeFileSync(configPath, `${JSON.stringify(mcpConfig, null, 2)}\n`, 'utf8');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function writeWorkspaceMcpConfig(targetDir) {
|
|
333
|
+
const mcpConfigPath = path.join(WORKSPACE_ROOT, '.mcp.json');
|
|
334
|
+
writeJsonMcpConfig(mcpConfigPath, targetDir);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function tomlString(value) {
|
|
338
|
+
return JSON.stringify(String(value));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function upsertCodexProjectMcp(targetDir, workspaceRoot = WORKSPACE_ROOT) {
|
|
342
|
+
const configPath = path.join(workspaceRoot, '.codex', 'config.toml');
|
|
343
|
+
const original = fs.existsSync(configPath) ? fs.readFileSync(configPath, 'utf8') : '';
|
|
344
|
+
const lines = original.replace(/\r\n/g, '\n').split('\n');
|
|
345
|
+
const kept = [];
|
|
346
|
+
let skipping = false;
|
|
347
|
+
for (const line of lines) {
|
|
348
|
+
const table = line.match(/^\s*\[([^\]]+)]\s*(?:#.*)?$/);
|
|
349
|
+
if (table) {
|
|
350
|
+
skipping = table[1] === 'mcp_servers.voc' || table[1].startsWith('mcp_servers.voc.');
|
|
351
|
+
}
|
|
352
|
+
if (!skipping) kept.push(line);
|
|
353
|
+
}
|
|
354
|
+
while (kept.length && !kept[kept.length - 1].trim()) kept.pop();
|
|
355
|
+
const serverPath = path.join(targetDir, 'mcp', 'src', 'server.js');
|
|
356
|
+
const block = [
|
|
357
|
+
'[mcp_servers.voc]',
|
|
358
|
+
'command = "node"',
|
|
359
|
+
`args = [${tomlString(serverPath)}]`,
|
|
360
|
+
`cwd = ${tomlString(targetDir)}`
|
|
361
|
+
];
|
|
362
|
+
ensureDir(path.dirname(configPath));
|
|
363
|
+
fs.writeFileSync(configPath, `${kept.concat(kept.length ? [''] : [], block).join('\n')}\n`, 'utf8');
|
|
364
|
+
return configPath;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function runCapture(command, args, cwd = WORKSPACE_ROOT) {
|
|
368
|
+
const useCmd = process.platform === 'win32';
|
|
369
|
+
const executable = useCmd ? 'cmd.exe' : command;
|
|
370
|
+
const finalArgs = useCmd ? ['/d', '/s', '/c', command, ...args] : args;
|
|
371
|
+
return spawnSync(executable, finalArgs, {
|
|
372
|
+
cwd,
|
|
373
|
+
encoding: 'utf8',
|
|
374
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
375
|
+
maxBuffer: 1024 * 1024 * 10
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function registerCodexGlobalMcp(targetDir) {
|
|
380
|
+
const getResult = runCapture('codex', ['mcp', 'get', 'voc']);
|
|
381
|
+
if (!getResult.error && getResult.status === 0) {
|
|
382
|
+
const removeResult = runCapture('codex', ['mcp', 'remove', 'voc']);
|
|
383
|
+
if (removeResult.error || removeResult.status !== 0) {
|
|
384
|
+
throw new Error('Unable to update the existing Codex VOC MCP registration');
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
const serverPath = path.join(targetDir, 'mcp', 'src', 'server.js');
|
|
388
|
+
const addResult = runCapture('codex', ['mcp', 'add', 'voc', '--', 'node', serverPath]);
|
|
389
|
+
if (addResult.error || addResult.status !== 0) {
|
|
390
|
+
const detail = String(addResult.stderr || addResult.error?.message || '').trim();
|
|
391
|
+
throw new Error(`Codex MCP registration failed${detail ? `: ${detail}` : ''}`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function copyPackagedSkills(targetDir, destinationRoot) {
|
|
396
|
+
const sourceRoot = path.join(targetDir, 'skills');
|
|
397
|
+
ensureDir(destinationRoot);
|
|
398
|
+
let copied = 0;
|
|
399
|
+
for (const entry of fs.readdirSync(sourceRoot, { withFileTypes: true })) {
|
|
400
|
+
if (!entry.isDirectory()) continue;
|
|
401
|
+
const destination = path.join(destinationRoot, entry.name);
|
|
402
|
+
removeDirResilient(destination);
|
|
403
|
+
copied += copyDirRecursive(path.join(sourceRoot, entry.name), destination);
|
|
404
|
+
}
|
|
405
|
+
return copied;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function persistNeutralCredential(credentialsPath = VOCMARKET_CREDENTIALS) {
|
|
409
|
+
const token = readNewApiToken();
|
|
410
|
+
if (!token) return false;
|
|
411
|
+
const existing = readJsonIfExists(credentialsPath);
|
|
412
|
+
ensureDir(path.dirname(credentialsPath));
|
|
413
|
+
fs.writeFileSync(credentialsPath, `${JSON.stringify({ ...existing, fmodeApiKey: token }, null, 2)}\n`, {
|
|
414
|
+
encoding: 'utf8',
|
|
415
|
+
mode: 0o600
|
|
416
|
+
});
|
|
417
|
+
try {
|
|
418
|
+
fs.chmodSync(credentialsPath, 0o600);
|
|
419
|
+
} catch {}
|
|
420
|
+
return true;
|
|
280
421
|
}
|
|
281
422
|
|
|
282
423
|
function asForwardSlash(filePath) {
|
|
@@ -617,11 +758,10 @@ function writeWorkspaceSkillEntries(targetDir) {
|
|
|
617
758
|
fs.copyFileSync(path.join(targetDir, 'skills', 'voc-api-catalog', 'SKILL.md'), path.join(apiCatalogSkillDir, 'SKILL.md'));
|
|
618
759
|
}
|
|
619
760
|
|
|
620
|
-
function
|
|
621
|
-
if (!isSafeWorkspaceTarget(targetDir)) return;
|
|
761
|
+
function writeClaudeWorkspaceActivation(targetDir) {
|
|
622
762
|
writeWorkspaceMcpConfig(targetDir);
|
|
623
763
|
writeWorkspaceSkillEntries(targetDir);
|
|
624
|
-
console.log('Wrote workspace MCP config: .\\.mcp.json');
|
|
764
|
+
console.log('Wrote Claude/Fmode workspace MCP config: .\\.mcp.json');
|
|
625
765
|
console.log('Wrote workspace skill entry: .\\.claude\\skills\\xiaohongshu-trend-intelligence\\SKILL.md');
|
|
626
766
|
console.log('Wrote workspace skill entry: .\\.claude\\skills\\douyin-trend-intelligence\\SKILL.md');
|
|
627
767
|
console.log('Wrote workspace skill entry: .\\.claude\\skills\\voc-issue-pool\\SKILL.md');
|
|
@@ -634,26 +774,71 @@ function writeWorkspaceActivation(targetDir) {
|
|
|
634
774
|
console.log('Wrote workspace skill entry: .\\.claude\\skills\\voc-api-catalog\\SKILL.md');
|
|
635
775
|
}
|
|
636
776
|
|
|
637
|
-
function
|
|
638
|
-
|
|
777
|
+
function writeWorkspaceActivation(targetDir, opts) {
|
|
778
|
+
if (!opts.workspaceRequested) return;
|
|
779
|
+
if (opts.channel === 'codex') {
|
|
780
|
+
const skillRoot = opts.scope === 'global'
|
|
781
|
+
? path.join(os.homedir(), '.codex', 'skills')
|
|
782
|
+
: path.join(WORKSPACE_ROOT, '.codex', 'skills');
|
|
783
|
+
copyPackagedSkills(targetDir, skillRoot);
|
|
784
|
+
if (opts.scope === 'global') {
|
|
785
|
+
registerCodexGlobalMcp(targetDir);
|
|
786
|
+
console.log('Registered VOC MCP with Codex user config.');
|
|
787
|
+
console.log(`Installed Codex user skills: ${skillRoot}`);
|
|
788
|
+
} else {
|
|
789
|
+
const configPath = upsertCodexProjectMcp(targetDir);
|
|
790
|
+
console.log(`Registered VOC MCP with Codex project config: ${configPath}`);
|
|
791
|
+
console.log(`Installed Codex project skills: ${skillRoot}`);
|
|
792
|
+
}
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
if (opts.channel === 'workbuddy') {
|
|
796
|
+
writeWorkspaceMcpConfig(targetDir);
|
|
797
|
+
const skillRoot = path.join(WORKSPACE_ROOT, '.workbuddy', 'skills');
|
|
798
|
+
copyPackagedSkills(targetDir, skillRoot);
|
|
799
|
+
console.log('Wrote portable WorkBuddy MCP registration: .\\.mcp.json');
|
|
800
|
+
console.log('Installed WorkBuddy workspace skills: .\\.workbuddy\\skills');
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
writeClaudeWorkspaceActivation(targetDir);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
async function verifyVocToolDiscovery(targetDir) {
|
|
807
|
+
const { Client } = require('@modelcontextprotocol/sdk/client/index.js');
|
|
808
|
+
const { StdioClientTransport } = require('@modelcontextprotocol/sdk/client/stdio.js');
|
|
809
|
+
const { version } = require('../package.json');
|
|
810
|
+
const client = new Client({ name: 'voc-installer-verification', version });
|
|
811
|
+
const transport = new StdioClientTransport({
|
|
812
|
+
command: process.execPath,
|
|
813
|
+
args: [path.join(targetDir, 'mcp', 'src', 'server.js')],
|
|
814
|
+
cwd: targetDir,
|
|
815
|
+
stderr: 'pipe'
|
|
816
|
+
});
|
|
817
|
+
await client.connect(transport);
|
|
818
|
+
try {
|
|
819
|
+
const result = await client.listTools();
|
|
820
|
+
const names = (result.tools || []).map(tool => tool.name);
|
|
821
|
+
const discovered = names.filter(name => name.startsWith('voc_api_'));
|
|
822
|
+
if (!discovered.length) {
|
|
823
|
+
throw new Error('MCP started, but no voc_api_* tool was discovered');
|
|
824
|
+
}
|
|
825
|
+
console.log(`Verified MCP tool discovery (no data request): ${discovered.join(', ')}`);
|
|
826
|
+
return discovered;
|
|
827
|
+
} finally {
|
|
828
|
+
await client.close();
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function printNextSteps(targetDir, opts) {
|
|
833
|
+
const workspaceMode = opts.workspaceRequested;
|
|
639
834
|
console.log('');
|
|
640
835
|
console.log('Install complete.');
|
|
641
836
|
console.log('');
|
|
642
837
|
if (workspaceMode) {
|
|
643
|
-
console.log(
|
|
838
|
+
console.log(`${opts.channel} ${opts.scope} mode is ready.`);
|
|
644
839
|
console.log('');
|
|
645
840
|
console.log('Generated project files:');
|
|
646
|
-
console.log(
|
|
647
|
-
console.log(' .\\.claude\\skills\\xiaohongshu-trend-intelligence\\SKILL.md');
|
|
648
|
-
console.log(' .\\.claude\\skills\\douyin-trend-intelligence\\SKILL.md');
|
|
649
|
-
console.log(' .\\.claude\\skills\\voc-issue-pool\\SKILL.md');
|
|
650
|
-
console.log(' .\\.claude\\skills\\voc-problem-deep-dive\\SKILL.md');
|
|
651
|
-
console.log(' .\\.claude\\skills\\voc-content-plan\\SKILL.md');
|
|
652
|
-
console.log(' .\\.claude\\skills\\voc-speaking-script\\SKILL.md');
|
|
653
|
-
console.log(' .\\.claude\\skills\\voc-competitor-map\\SKILL.md');
|
|
654
|
-
console.log(' .\\.claude\\skills\\voc-business-workflow\\SKILL.md');
|
|
655
|
-
console.log(' .\\.claude\\skills\\fmode-image-analysis\\SKILL.md');
|
|
656
|
-
console.log(' .\\.claude\\plugins\\voc-intelligence');
|
|
841
|
+
console.log(` ${targetDir}`);
|
|
657
842
|
console.log('');
|
|
658
843
|
console.log('Restart the VSCode Claude Code session if it was already open.');
|
|
659
844
|
} else {
|
|
@@ -677,7 +862,7 @@ function printNextSteps(targetDir) {
|
|
|
677
862
|
console.log(`Install target: ${targetDir}`);
|
|
678
863
|
}
|
|
679
864
|
|
|
680
|
-
function install(opts) {
|
|
865
|
+
async function install(opts) {
|
|
681
866
|
checkNodeVersion();
|
|
682
867
|
if (path.resolve(SOURCE_ROOT) !== path.resolve(opts.target)) {
|
|
683
868
|
if (fs.existsSync(opts.target)) {
|
|
@@ -693,7 +878,7 @@ function install(opts) {
|
|
|
693
878
|
|
|
694
879
|
assertInstalled(opts.target);
|
|
695
880
|
writeMcpConfig(opts.target);
|
|
696
|
-
writeWorkspaceActivation(opts.target);
|
|
881
|
+
writeWorkspaceActivation(opts.target, opts);
|
|
697
882
|
|
|
698
883
|
if (!opts.skipInstall) {
|
|
699
884
|
console.log('Installing runtime dependencies...');
|
|
@@ -707,7 +892,11 @@ function install(opts) {
|
|
|
707
892
|
run(process.execPath, ['install.js', '--check', '--no-next'], opts.target);
|
|
708
893
|
}
|
|
709
894
|
|
|
710
|
-
|
|
895
|
+
if (opts.activate) {
|
|
896
|
+
persistNeutralCredential();
|
|
897
|
+
await verifyVocToolDiscovery(opts.target);
|
|
898
|
+
}
|
|
899
|
+
printNextSteps(opts.target, opts);
|
|
711
900
|
}
|
|
712
901
|
|
|
713
902
|
function check(opts) {
|
|
@@ -742,7 +931,7 @@ async function main() {
|
|
|
742
931
|
return;
|
|
743
932
|
}
|
|
744
933
|
if (opts.command === 'install') {
|
|
745
|
-
install(opts);
|
|
934
|
+
await install(opts);
|
|
746
935
|
if (opts.activate) {
|
|
747
936
|
await ensureActivated({ channel: opts.channel });
|
|
748
937
|
}
|
|
@@ -754,10 +943,20 @@ async function main() {
|
|
|
754
943
|
throw new Error(`Unknown command: ${opts.command}\n\n${usage()}`);
|
|
755
944
|
}
|
|
756
945
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
946
|
+
if (require.main === module) {
|
|
947
|
+
main().catch((error) => {
|
|
948
|
+
console.error('');
|
|
949
|
+
console.error(`claude-voc failed: ${error.message}`);
|
|
950
|
+
process.exit(1);
|
|
951
|
+
});
|
|
952
|
+
}
|
|
763
953
|
|
|
954
|
+
module.exports = {
|
|
955
|
+
parseArgs,
|
|
956
|
+
normalizeInstallChannel,
|
|
957
|
+
normalizeInstallScope,
|
|
958
|
+
defaultTargetFor,
|
|
959
|
+
upsertCodexProjectMcp,
|
|
960
|
+
verifyVocToolDiscovery,
|
|
961
|
+
persistNeutralCredential
|
|
962
|
+
};
|
|
@@ -17,6 +17,15 @@
|
|
|
17
17
|
"desc": "淘宝/天猫上的唯一商品标识符(商品ID)。"
|
|
18
18
|
}
|
|
19
19
|
],
|
|
20
|
+
"taobao.get_item_detail_v3": [
|
|
21
|
+
{
|
|
22
|
+
"name": "itemId",
|
|
23
|
+
"in": "query",
|
|
24
|
+
"type": "string",
|
|
25
|
+
"required": true,
|
|
26
|
+
"desc": "淘宝/天猫上的唯一商品标识符(商品ID)。"
|
|
27
|
+
}
|
|
28
|
+
],
|
|
20
29
|
"taobao.get_item_detail_v4": [
|
|
21
30
|
{
|
|
22
31
|
"name": "itemId",
|
|
@@ -35,6 +44,15 @@
|
|
|
35
44
|
"desc": "淘宝/天猫上的唯一商品标识符(商品ID)。"
|
|
36
45
|
}
|
|
37
46
|
],
|
|
47
|
+
"taobao.get_item_detail_v6": [
|
|
48
|
+
{
|
|
49
|
+
"name": "itemId",
|
|
50
|
+
"in": "query",
|
|
51
|
+
"type": "string",
|
|
52
|
+
"required": true,
|
|
53
|
+
"desc": "淘宝/天猫上的唯一商品标识符(商品ID)。"
|
|
54
|
+
}
|
|
55
|
+
],
|
|
38
56
|
"taobao.get_item_detail_v9": [
|
|
39
57
|
{
|
|
40
58
|
"name": "itemId",
|
|
@@ -167,6 +185,23 @@
|
|
|
167
185
|
"default": 1
|
|
168
186
|
}
|
|
169
187
|
],
|
|
188
|
+
"taobao.get_shop_item_list_v4": [
|
|
189
|
+
{
|
|
190
|
+
"name": "sellerId",
|
|
191
|
+
"in": "query",
|
|
192
|
+
"type": "string",
|
|
193
|
+
"required": true,
|
|
194
|
+
"desc": "用于标识淘宝/天猫店铺的卖家用户ID。"
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"name": "page",
|
|
198
|
+
"in": "query",
|
|
199
|
+
"type": "integer",
|
|
200
|
+
"required": false,
|
|
201
|
+
"desc": "用于分页的页码。",
|
|
202
|
+
"default": 1
|
|
203
|
+
}
|
|
204
|
+
],
|
|
170
205
|
"taobao.get_item_sale_v1": [
|
|
171
206
|
{
|
|
172
207
|
"name": "itemId",
|
|
@@ -223,4 +258,4 @@
|
|
|
223
258
|
"default": 1
|
|
224
259
|
}
|
|
225
260
|
]
|
|
226
|
-
}
|
|
261
|
+
}
|
|
@@ -22542,7 +22542,11 @@
|
|
|
22542
22542
|
"platform": "taobao",
|
|
22543
22543
|
"channel": "ecommerce",
|
|
22544
22544
|
"title": "淘宝天猫·商品详情",
|
|
22545
|
-
"summary": "
|
|
22545
|
+
"summary": "淘宝天猫商品详情兼容版本,可能返回上游快照。查价格与完整详情优先使用 v6,查页面展示销量使用 v3。",
|
|
22546
|
+
"recommendedFor": [
|
|
22547
|
+
"legacy_detail_compatibility"
|
|
22548
|
+
],
|
|
22549
|
+
"freshness": "recordTime 是上游数据采集时间,不代表本次请求时间;调用方应根据 recordTime 计算滞后时长。",
|
|
22546
22550
|
"method": "GET",
|
|
22547
22551
|
"paramsIn": "query",
|
|
22548
22552
|
"proxyPath": "taobao/get-item-detail/v1",
|
|
@@ -22570,7 +22574,8 @@
|
|
|
22570
22574
|
"platform": "taobao",
|
|
22571
22575
|
"channel": "ecommerce",
|
|
22572
22576
|
"title": "淘宝天猫·商品详情",
|
|
22573
|
-
"summary": "
|
|
22577
|
+
"summary": "商品详情 V2 任务结果读取接口;直接调用不会发起商品数据采集。普通详情查询优先使用 v6。",
|
|
22578
|
+
"recommendationPriority": -100,
|
|
22574
22579
|
"method": "GET",
|
|
22575
22580
|
"paramsIn": "query",
|
|
22576
22581
|
"proxyPath": "taobao/get-item-detail/v2",
|
|
@@ -22593,12 +22598,52 @@
|
|
|
22593
22598
|
],
|
|
22594
22599
|
"billing": 1
|
|
22595
22600
|
},
|
|
22601
|
+
{
|
|
22602
|
+
"id": "taobao.get_item_detail_v3",
|
|
22603
|
+
"platform": "taobao",
|
|
22604
|
+
"channel": "ecommerce",
|
|
22605
|
+
"title": "淘宝天猫·页面展示销量(V3)",
|
|
22606
|
+
"summary": "通过商品 ID 获取淘宝或天猫商品页当前展示的销量,适合核对“已售/付款人数”;金额与 SKU 明细请使用 v6。",
|
|
22607
|
+
"method": "GET",
|
|
22608
|
+
"paramsIn": "query",
|
|
22609
|
+
"proxyPath": "taobao/get-item-detail/v3",
|
|
22610
|
+
"params": [],
|
|
22611
|
+
"requiredParams": [
|
|
22612
|
+
"itemId"
|
|
22613
|
+
],
|
|
22614
|
+
"recommendationPriority": 90,
|
|
22615
|
+
"recommendedFor": [
|
|
22616
|
+
"page_sales",
|
|
22617
|
+
"displayed_sales",
|
|
22618
|
+
"buyer_count"
|
|
22619
|
+
],
|
|
22620
|
+
"freshness": "recordTime 是上游数据采集时间;返回前必须与当前时间比较,不把请求时间当作采集时间。",
|
|
22621
|
+
"responseHint": "页面销量读取 data.sellCount;该版本不返回商品价格。",
|
|
22622
|
+
"tags": [
|
|
22623
|
+
"taobao",
|
|
22624
|
+
"淘宝天猫",
|
|
22625
|
+
"淘宝",
|
|
22626
|
+
"天猫",
|
|
22627
|
+
"tmall",
|
|
22628
|
+
"Taobao and Tmall",
|
|
22629
|
+
"ecommerce",
|
|
22630
|
+
"电商",
|
|
22631
|
+
"商品",
|
|
22632
|
+
"product",
|
|
22633
|
+
"页面销量",
|
|
22634
|
+
"已售",
|
|
22635
|
+
"付款人数",
|
|
22636
|
+
"sellCount",
|
|
22637
|
+
"page sales"
|
|
22638
|
+
],
|
|
22639
|
+
"billing": 1
|
|
22640
|
+
},
|
|
22596
22641
|
{
|
|
22597
22642
|
"id": "taobao.get_item_detail_v4",
|
|
22598
22643
|
"platform": "taobao",
|
|
22599
22644
|
"channel": "ecommerce",
|
|
22600
|
-
"title": "
|
|
22601
|
-
"summary": "
|
|
22645
|
+
"title": "淘宝天猫·券后价与商品详情(V4)",
|
|
22646
|
+
"summary": "获取淘宝或天猫商品的标价、SKU 优惠后价格与商品详情;需要券后价/优惠后价口径时优先使用。",
|
|
22602
22647
|
"method": "GET",
|
|
22603
22648
|
"paramsIn": "query",
|
|
22604
22649
|
"proxyPath": "taobao/get-item-detail/v4",
|
|
@@ -22606,6 +22651,13 @@
|
|
|
22606
22651
|
"requiredParams": [
|
|
22607
22652
|
"itemId"
|
|
22608
22653
|
],
|
|
22654
|
+
"recommendationPriority": 95,
|
|
22655
|
+
"recommendedFor": [
|
|
22656
|
+
"coupon_price",
|
|
22657
|
+
"discount_price"
|
|
22658
|
+
],
|
|
22659
|
+
"freshness": "recordTime 是上游数据采集时间;优惠后价仍可因账号、SKU、地区、优惠券和活动时间与结算页不同。",
|
|
22660
|
+
"responseHint": "价格单位通常为分:itemPrice 是标价,DiscountPrice 是优惠后价,SKU 读取 sku2info.finalPrice/finalSkuPrice。",
|
|
22609
22661
|
"tags": [
|
|
22610
22662
|
"taobao",
|
|
22611
22663
|
"淘宝天猫",
|
|
@@ -22617,7 +22669,13 @@
|
|
|
22617
22669
|
"ecommerce",
|
|
22618
22670
|
"电商",
|
|
22619
22671
|
"商品",
|
|
22620
|
-
"product"
|
|
22672
|
+
"product",
|
|
22673
|
+
"商品详情",
|
|
22674
|
+
"券后价",
|
|
22675
|
+
"优惠后价",
|
|
22676
|
+
"DiscountPrice",
|
|
22677
|
+
"coupon price",
|
|
22678
|
+
"discount price"
|
|
22621
22679
|
],
|
|
22622
22680
|
"billing": 1
|
|
22623
22681
|
},
|
|
@@ -22649,12 +22707,54 @@
|
|
|
22649
22707
|
],
|
|
22650
22708
|
"billing": 1
|
|
22651
22709
|
},
|
|
22710
|
+
{
|
|
22711
|
+
"id": "taobao.get_item_detail_v6",
|
|
22712
|
+
"platform": "taobao",
|
|
22713
|
+
"channel": "ecommerce",
|
|
22714
|
+
"title": "淘宝天猫·商品详情与价格(V6)",
|
|
22715
|
+
"summary": "通过商品 ID 获取淘宝或天猫商品详情、SKU 价格、促销价、图片和店铺信息;查价格与完整详情时优先使用。页面展示销量请另用 v3 核对。",
|
|
22716
|
+
"method": "GET",
|
|
22717
|
+
"paramsIn": "query",
|
|
22718
|
+
"proxyPath": "taobao/get-item-detail/v6",
|
|
22719
|
+
"params": [],
|
|
22720
|
+
"requiredParams": [
|
|
22721
|
+
"itemId"
|
|
22722
|
+
],
|
|
22723
|
+
"recommendationPriority": 100,
|
|
22724
|
+
"recommendedFor": [
|
|
22725
|
+
"product_detail",
|
|
22726
|
+
"price",
|
|
22727
|
+
"sku_price",
|
|
22728
|
+
"promotion_price"
|
|
22729
|
+
],
|
|
22730
|
+
"freshness": "recordTime 是上游数据采集时间;返回前必须与当前时间比较,不把请求时间当作采集时间。",
|
|
22731
|
+
"responseHint": "价格优先读取 itemPrice.price / promotionPrice 及 SKU 价格;页面展示销量以 v3 的 sellCount 为准。",
|
|
22732
|
+
"tags": [
|
|
22733
|
+
"taobao",
|
|
22734
|
+
"淘宝天猫",
|
|
22735
|
+
"淘宝",
|
|
22736
|
+
"天猫",
|
|
22737
|
+
"tmall",
|
|
22738
|
+
"Taobao and Tmall",
|
|
22739
|
+
"ecommerce",
|
|
22740
|
+
"电商",
|
|
22741
|
+
"商品",
|
|
22742
|
+
"product",
|
|
22743
|
+
"商品详情",
|
|
22744
|
+
"价格",
|
|
22745
|
+
"促销价",
|
|
22746
|
+
"SKU",
|
|
22747
|
+
"product detail",
|
|
22748
|
+
"price"
|
|
22749
|
+
],
|
|
22750
|
+
"billing": 1
|
|
22751
|
+
},
|
|
22652
22752
|
{
|
|
22653
22753
|
"id": "taobao.get_item_detail_v9",
|
|
22654
22754
|
"platform": "taobao",
|
|
22655
22755
|
"channel": "ecommerce",
|
|
22656
|
-
"title": "
|
|
22657
|
-
"summary": "
|
|
22756
|
+
"title": "淘宝天猫·商品详情兼容版(V9)",
|
|
22757
|
+
"summary": "淘宝天猫商品详情 V9 兼容数据源。num 是该版本的销售量级信号,totalCount 是评价总量,两者均不等于商品页当前展示付款人数。价格/关键属性优先 v6,页面销量优先 v3。",
|
|
22658
22758
|
"method": "GET",
|
|
22659
22759
|
"paramsIn": "query",
|
|
22660
22760
|
"proxyPath": "taobao/get-item-detail/v9",
|
|
@@ -22662,6 +22762,12 @@
|
|
|
22662
22762
|
"requiredParams": [
|
|
22663
22763
|
"itemId"
|
|
22664
22764
|
],
|
|
22765
|
+
"recommendationPriority": -50,
|
|
22766
|
+
"recommendedFor": [
|
|
22767
|
+
"legacy_v9_compatibility"
|
|
22768
|
+
],
|
|
22769
|
+
"freshness": "recordTime 只表示 V9 响应快照的采集时间;嵌套 attribute 可能来自更旧的属性源,不能仅根据 recordTime 判定每个属性都已刷新。",
|
|
22770
|
+
"responseHint": "num=V9 销售量级信号;totalCount=评价总量;attribute=V9 属性快照。不要把 num/totalCount 当作当前页面付款人数,也不要用 V9 attribute 覆盖 V6 的更新关键属性。",
|
|
22665
22771
|
"tags": [
|
|
22666
22772
|
"taobao",
|
|
22667
22773
|
"淘宝天猫",
|
|
@@ -22795,7 +22901,8 @@
|
|
|
22795
22901
|
"platform": "taobao",
|
|
22796
22902
|
"channel": "ecommerce",
|
|
22797
22903
|
"title": "淘宝天猫·店铺商品列表",
|
|
22798
|
-
"summary": "
|
|
22904
|
+
"summary": "已弃用的淘宝天猫店铺商品列表 V3;新调用使用 v4,只需 sellerId 并支持分页。",
|
|
22905
|
+
"deprecated": true,
|
|
22799
22906
|
"method": "GET",
|
|
22800
22907
|
"paramsIn": "query",
|
|
22801
22908
|
"proxyPath": "taobao/get-shop-item-list/v3",
|
|
@@ -22819,12 +22926,47 @@
|
|
|
22819
22926
|
],
|
|
22820
22927
|
"billing": 1
|
|
22821
22928
|
},
|
|
22929
|
+
{
|
|
22930
|
+
"id": "taobao.get_shop_item_list_v4",
|
|
22931
|
+
"platform": "taobao",
|
|
22932
|
+
"channel": "ecommerce",
|
|
22933
|
+
"title": "淘宝天猫·店铺商品列表(V4)",
|
|
22934
|
+
"summary": "通过 sellerId 分页获取淘宝或天猫店铺商品列表,支持全部淘宝和天猫店铺;新调用优先使用。",
|
|
22935
|
+
"method": "GET",
|
|
22936
|
+
"paramsIn": "query",
|
|
22937
|
+
"proxyPath": "taobao/get-shop-item-list/v4",
|
|
22938
|
+
"params": [],
|
|
22939
|
+
"requiredParams": [
|
|
22940
|
+
"sellerId"
|
|
22941
|
+
],
|
|
22942
|
+
"recommendationPriority": 100,
|
|
22943
|
+
"recommendedFor": [
|
|
22944
|
+
"shop_product_list",
|
|
22945
|
+
"seller_catalog"
|
|
22946
|
+
],
|
|
22947
|
+
"tags": [
|
|
22948
|
+
"taobao",
|
|
22949
|
+
"淘宝天猫",
|
|
22950
|
+
"淘宝",
|
|
22951
|
+
"天猫",
|
|
22952
|
+
"tmall",
|
|
22953
|
+
"Taobao and Tmall",
|
|
22954
|
+
"ecommerce",
|
|
22955
|
+
"电商",
|
|
22956
|
+
"店铺商品",
|
|
22957
|
+
"店铺商品列表",
|
|
22958
|
+
"sellerId",
|
|
22959
|
+
"shop product list"
|
|
22960
|
+
],
|
|
22961
|
+
"billing": 1
|
|
22962
|
+
},
|
|
22822
22963
|
{
|
|
22823
22964
|
"id": "taobao.get_item_sale_v1",
|
|
22824
22965
|
"platform": "taobao",
|
|
22825
22966
|
"channel": "ecommerce",
|
|
22826
22967
|
"title": "淘宝天猫·商品销量",
|
|
22827
|
-
"summary": "
|
|
22968
|
+
"summary": "已弃用的 30 天商品销量接口,上游可能返回空数据。核对商品页“已售/付款人数”使用 get_item_detail_v3。",
|
|
22969
|
+
"deprecated": true,
|
|
22828
22970
|
"method": "GET",
|
|
22829
22971
|
"paramsIn": "query",
|
|
22830
22972
|
"proxyPath": "taobao/get-item-sale/v1",
|
|
@@ -28379,4 +28521,4 @@
|
|
|
28379
28521
|
"billing": 1
|
|
28380
28522
|
}
|
|
28381
28523
|
]
|
|
28382
|
-
}
|
|
28524
|
+
}
|
|
@@ -78,7 +78,8 @@ function searchEndpoints({ query, platform, tag, limit = 20 } = {}) {
|
|
|
78
78
|
const tagKey = normalize(tag);
|
|
79
79
|
let results = listEndpoints().map(endpoint => ({
|
|
80
80
|
endpoint,
|
|
81
|
-
score: scoreEndpoint(endpoint, terms)
|
|
81
|
+
score: scoreEndpoint(endpoint, terms),
|
|
82
|
+
preference: endpoint.deprecated ? -1000 : Number(endpoint.recommendationPriority || 0)
|
|
82
83
|
}));
|
|
83
84
|
if (platformKey) {
|
|
84
85
|
results = results.filter(item => normalize(item.endpoint.platform) === platformKey);
|
|
@@ -87,7 +88,7 @@ function searchEndpoints({ query, platform, tag, limit = 20 } = {}) {
|
|
|
87
88
|
results = results.filter(item => (item.endpoint.tags || []).some(t => normalize(t) === tagKey));
|
|
88
89
|
}
|
|
89
90
|
results = results.filter(item => item.score > 0);
|
|
90
|
-
results.sort((a, b) => b.score - a.score);
|
|
91
|
+
results.sort((a, b) => (b.score - a.score) || (b.preference - a.preference));
|
|
91
92
|
return results.slice(0, Math.max(1, limit)).map(item => item.endpoint);
|
|
92
93
|
}
|
|
93
94
|
|
|
@@ -99,6 +100,9 @@ function summarizeEndpoint(endpoint) {
|
|
|
99
100
|
summary: endpoint.summary,
|
|
100
101
|
method: endpoint.method,
|
|
101
102
|
proxyPath: endpoint.proxyPath,
|
|
103
|
+
deprecated: Boolean(endpoint.deprecated),
|
|
104
|
+
recommendedFor: endpoint.recommendedFor || [],
|
|
105
|
+
freshness: endpoint.freshness || null,
|
|
102
106
|
requiredParams: (endpoint.params && endpoint.params.length)
|
|
103
107
|
? endpoint.params.filter(p => p.required).map(p => p.name)
|
|
104
108
|
: (endpoint.requiredParams || []),
|
|
@@ -149,6 +149,7 @@ function readXiaohongshuToken(input = {}) {
|
|
|
149
149
|
// → Claude Code env(进程注入 + ~/.claude/settings.json)的 ANTHROPIC_AUTH_TOKEN(sk-、且 base 指向 fmode)。
|
|
150
150
|
function readNewApiToken(input = {}) {
|
|
151
151
|
const workspaceEnv = readEnvLocalFiles();
|
|
152
|
+
const vocMarketCreds = readJsonMaybe(path.join(os.homedir(), '.vocmarket', 'credentials.json'));
|
|
152
153
|
const claudeCreds = readJsonMaybe(path.join(os.homedir(), '.claude', 'voc-credentials.json'));
|
|
153
154
|
const fmodeConfig = readFmodeConfig();
|
|
154
155
|
const claudeEnv = readClaudeSettingsEnv();
|
|
@@ -165,6 +166,9 @@ function readNewApiToken(input = {}) {
|
|
|
165
166
|
process.env.NEW_API_TOKEN,
|
|
166
167
|
process.env.FMODE_API_KEY,
|
|
167
168
|
process.env.FMODE_API_TOKEN,
|
|
169
|
+
vocMarketCreds.fmodeApiKey,
|
|
170
|
+
vocMarketCreds.fmodeApiToken,
|
|
171
|
+
vocMarketCreds.newApiToken,
|
|
168
172
|
claudeCreds.fmodeApiKey,
|
|
169
173
|
claudeCreds.fmodeApiToken,
|
|
170
174
|
fmodeConfig.newapiToken,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vocmarket/voc-skill",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.32",
|
|
4
4
|
"description": "Claude Code VOC intelligence skills for Xiaohongshu and Douyin trend reports, live social VOC collection, Fmode image analysis, single-issue deep dives, boss/operator action plans, and multi-turn memory.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"voc:business-workflow": "node mcp/src/tools/voc-business-workflow-run.js --brand 示例品牌A --industry 家居定制 --platform douyin --collection-mode sample --keywords \"家居定制怎么选,第一次买怎么对比,售后\" --assistant-message-only",
|
|
30
30
|
"mcp:start": "node mcp/src/server.js",
|
|
31
31
|
"mcp:smoke": "node scripts/smoke-mcp.js",
|
|
32
|
-
"acceptance": "node scripts/acceptance.js"
|
|
32
|
+
"acceptance": "node scripts/acceptance.js",
|
|
33
|
+
"acceptance:installer": "node scripts/installer-acceptance.js"
|
|
33
34
|
},
|
|
34
35
|
"files": [
|
|
35
36
|
".claude-plugin/",
|
package/scripts/acceptance.js
CHANGED
|
@@ -13,6 +13,7 @@ function main() {
|
|
|
13
13
|
run('npm', ['run', 'fmode:image:smoke'], ROOT);
|
|
14
14
|
run('npm', ['run', 'smoke:package'], ROOT);
|
|
15
15
|
run('npm', ['run', 'mcp:smoke'], ROOT);
|
|
16
|
+
run('npm', ['run', 'acceptance:installer'], ROOT);
|
|
16
17
|
run('npm', ['pack', '--dry-run'], ROOT);
|
|
17
18
|
|
|
18
19
|
const workspace = fs.mkdtempSync(path.join(os.tmpdir(), 'voc-workspace-install-'));
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const assert = require('assert');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const {
|
|
9
|
+
parseArgs,
|
|
10
|
+
normalizeInstallChannel,
|
|
11
|
+
normalizeInstallScope,
|
|
12
|
+
upsertCodexProjectMcp,
|
|
13
|
+
verifyVocToolDiscovery,
|
|
14
|
+
persistNeutralCredential
|
|
15
|
+
} = require('../bin/claude-voc');
|
|
16
|
+
|
|
17
|
+
async function main() {
|
|
18
|
+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'voc-installer-acceptance-'));
|
|
19
|
+
const targetDir = path.join(tempRoot, '.codex', 'plugins', 'voc-intelligence');
|
|
20
|
+
const configDir = path.join(tempRoot, '.codex');
|
|
21
|
+
const configPath = path.join(configDir, 'config.toml');
|
|
22
|
+
const credentialsPath = path.join(tempRoot, '.vocmarket', 'credentials.json');
|
|
23
|
+
const previousToken = process.env.FMODE_API_KEY;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
assert.strictEqual(normalizeInstallChannel('CODEX'), 'codex');
|
|
27
|
+
assert.strictEqual(normalizeInstallScope('codex', 'global'), 'global');
|
|
28
|
+
assert.strictEqual(normalizeInstallScope('workbuddy', 'global'), 'project');
|
|
29
|
+
|
|
30
|
+
const options = parseArgs(['workspace', '--activate', '--channel=codex', '--scope=global']);
|
|
31
|
+
assert.strictEqual(options.activate, true);
|
|
32
|
+
assert.strictEqual(options.smoke, false);
|
|
33
|
+
assert.strictEqual(options.channel, 'codex');
|
|
34
|
+
assert.strictEqual(options.scope, 'global');
|
|
35
|
+
|
|
36
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
37
|
+
fs.writeFileSync(configPath, [
|
|
38
|
+
'model = "gpt-test"',
|
|
39
|
+
'',
|
|
40
|
+
'[mcp_servers.existing]',
|
|
41
|
+
'command = "existing-command"',
|
|
42
|
+
'',
|
|
43
|
+
'[mcp_servers.voc]',
|
|
44
|
+
'command = "old-command"',
|
|
45
|
+
''
|
|
46
|
+
].join('\n'));
|
|
47
|
+
upsertCodexProjectMcp(targetDir, tempRoot);
|
|
48
|
+
const config = fs.readFileSync(configPath, 'utf8');
|
|
49
|
+
assert.match(config, /model = "gpt-test"/);
|
|
50
|
+
assert.match(config, /\[mcp_servers\.existing]/);
|
|
51
|
+
assert.strictEqual((config.match(/\[mcp_servers\.voc]/g) || []).length, 1);
|
|
52
|
+
assert.ok(config.includes('mcp\\\\src\\\\server.js') || config.includes('mcp/src/server.js'));
|
|
53
|
+
|
|
54
|
+
process.env.FMODE_API_KEY = 'sk-installer_acceptance-123456789';
|
|
55
|
+
assert.strictEqual(persistNeutralCredential(credentialsPath), true);
|
|
56
|
+
const credentials = JSON.parse(fs.readFileSync(credentialsPath, 'utf8'));
|
|
57
|
+
assert.strictEqual(credentials.fmodeApiKey, process.env.FMODE_API_KEY);
|
|
58
|
+
|
|
59
|
+
const tools = await verifyVocToolDiscovery(path.resolve(__dirname, '..'));
|
|
60
|
+
assert.ok(tools.includes('voc_api_search'));
|
|
61
|
+
assert.ok(tools.includes('voc_api_doc'));
|
|
62
|
+
assert.ok(tools.includes('voc_api_call'));
|
|
63
|
+
|
|
64
|
+
console.log(JSON.stringify({
|
|
65
|
+
status: 'ok',
|
|
66
|
+
checks: ['channels', 'no-smoke activation', 'Codex TOML merge', 'neutral credentials', 'MCP ListTools'],
|
|
67
|
+
discoveredTools: tools
|
|
68
|
+
}, null, 2));
|
|
69
|
+
} finally {
|
|
70
|
+
if (previousToken === undefined) delete process.env.FMODE_API_KEY;
|
|
71
|
+
else process.env.FMODE_API_KEY = previousToken;
|
|
72
|
+
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
main().catch(error => {
|
|
77
|
+
console.error(error.message);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
});
|
package/scripts/smoke-mcp.js
CHANGED
|
@@ -291,6 +291,55 @@ async function main() {
|
|
|
291
291
|
throw new Error(`Expected voc_api_search to list douyin endpoints, got: ${JSON.stringify(apiSearchStructured)}`);
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
+
const taobaoDetailSearchResult = await client.callTool({
|
|
295
|
+
name: 'voc_api_search',
|
|
296
|
+
arguments: { platform: 'taobao', query: '淘宝 商品详情' }
|
|
297
|
+
});
|
|
298
|
+
const taobaoDetailEndpoints = taobaoDetailSearchResult.structuredContent?.data?.endpoints || [];
|
|
299
|
+
const taobaoDetailIds = taobaoDetailEndpoints.map(endpoint => endpoint.id);
|
|
300
|
+
if (taobaoDetailIds[0] !== 'taobao.get_item_detail_v6') {
|
|
301
|
+
throw new Error(`Expected current Taobao endpoints with v6 preferred for product details, got: ${taobaoDetailIds.join(', ')}`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const taobaoCatalogResult = await client.callTool({
|
|
305
|
+
name: 'voc_api_search',
|
|
306
|
+
arguments: { platform: 'taobao' }
|
|
307
|
+
});
|
|
308
|
+
const taobaoCatalogIds = (taobaoCatalogResult.structuredContent?.data?.endpoints || []).map(endpoint => endpoint.id);
|
|
309
|
+
for (const id of ['taobao.get_item_detail_v3', 'taobao.get_item_detail_v6', 'taobao.get_shop_item_list_v4']) {
|
|
310
|
+
if (!taobaoCatalogIds.includes(id)) {
|
|
311
|
+
throw new Error(`Expected current Taobao catalog to include ${id}, got: ${taobaoCatalogIds.join(', ')}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const taobaoSalesSearchResult = await client.callTool({
|
|
316
|
+
name: 'voc_api_search',
|
|
317
|
+
arguments: { platform: 'taobao', query: '淘宝 页面销量' }
|
|
318
|
+
});
|
|
319
|
+
const taobaoSalesEndpoints = taobaoSalesSearchResult.structuredContent?.data?.endpoints || [];
|
|
320
|
+
if (taobaoSalesEndpoints[0]?.id !== 'taobao.get_item_detail_v3' || taobaoSalesEndpoints[0]?.deprecated) {
|
|
321
|
+
throw new Error(`Expected Taobao v3 to be preferred for page sales, got: ${JSON.stringify(taobaoSalesEndpoints)}`);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const taobaoCouponPriceSearchResult = await client.callTool({
|
|
325
|
+
name: 'voc_api_search',
|
|
326
|
+
arguments: { platform: 'taobao', query: '淘宝 券后价' }
|
|
327
|
+
});
|
|
328
|
+
const taobaoCouponPriceEndpoints = taobaoCouponPriceSearchResult.structuredContent?.data?.endpoints || [];
|
|
329
|
+
if (taobaoCouponPriceEndpoints[0]?.id !== 'taobao.get_item_detail_v4') {
|
|
330
|
+
throw new Error(`Expected Taobao v4 to be preferred for coupon prices, got: ${JSON.stringify(taobaoCouponPriceEndpoints)}`);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const taobaoV9DocResult = await client.callTool({
|
|
334
|
+
name: 'voc_api_doc',
|
|
335
|
+
arguments: { id: 'taobao.get_item_detail_v9' }
|
|
336
|
+
});
|
|
337
|
+
const taobaoV9Endpoint = taobaoV9DocResult.structuredContent?.data?.endpoint || {};
|
|
338
|
+
if (!String(taobaoV9Endpoint.responseHint || '').includes('totalCount=') ||
|
|
339
|
+
Number(taobaoV9Endpoint.recommendationPriority) >= 0) {
|
|
340
|
+
throw new Error(`Expected Taobao v9 field semantics and compatibility priority, got: ${JSON.stringify(taobaoV9Endpoint)}`);
|
|
341
|
+
}
|
|
342
|
+
|
|
294
343
|
const apiDocResult = await client.callTool({
|
|
295
344
|
name: 'voc_api_doc',
|
|
296
345
|
arguments: { id: 'douyin.search_general' }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-voc-intelligence",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.32",
|
|
4
4
|
"capabilities": {
|
|
5
5
|
"summary": "三类情报源同源接入:①社媒内容 ②国内电商+创作者情报 ③海外选品。统一 search→doc→call、鉴权计费一致。",
|
|
6
6
|
"channels": [
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"key": "ecommerce",
|
|
29
29
|
"label": "国内电商 + 创作者情报",
|
|
30
30
|
"platforms": 32,
|
|
31
|
-
"apis":
|
|
31
|
+
"apis": 288,
|
|
32
32
|
"examples": [
|
|
33
33
|
"京东",
|
|
34
34
|
"淘宝天猫",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
],
|
|
54
54
|
"totals": {
|
|
55
55
|
"platforms": 51,
|
|
56
|
-
"apis":
|
|
56
|
+
"apis": 1343
|
|
57
57
|
}
|
|
58
58
|
},
|
|
59
59
|
"description": "Claude Code native VOC intelligence package for cross-industry social VOC workflows. Includes Xiaohongshu and Douyin trend intelligence, live token checks, evidence-card reports, single-issue VOC deep dives, boss/operator action plans, and scoped multi-turn memory.",
|
|
@@ -123,4 +123,4 @@
|
|
|
123
123
|
"workspaceInstallCommand": "npx @vocmarket/voc-skill workspace --smoke",
|
|
124
124
|
"workspaceInstallTarget": ".claude/plugins/voc-intelligence",
|
|
125
125
|
"installHint": "Supports three delivery modes: unzip and run node install.js, install globally into ~/.claude/plugins/voc-intelligence, or install into the current workspace. Workspace mode writes .mcp.json plus .claude/skills/* entries for trend intelligence, issue pool, deep dive, competitor map, content plan, speaking script, VOC business workflow, and Fmode image analysis so VSCode Claude Code can discover the workflows without relying only on --plugin-dir."
|
|
126
|
-
}
|
|
126
|
+
}
|
|
@@ -66,7 +66,18 @@ VOC 后端是一个**通用转发网关**(`https://server.fmode.cn/api/voc-soc
|
|
|
66
66
|
- 能力与性价比的客观评估:抖音 `douyin.xingtu.cost_performance_analysis` / `conversion_analysis`;小红书 `xiaohongshu.pgy.cost_effectiveness_analysis`。
|
|
67
67
|
3. 把这些创作者侧事实**喂给下游 VOC 技能**(voc-competitor-map 做竞对达人布局、voc-content-plan / voc-speaking-script 做选题与脚本、voc-issue-pool 补充创作者 / 受众视角),形成洞察结论,而非「该投谁」的采买清单。
|
|
68
68
|
|
|
69
|
-
> 提示:`voc_api_search` 用「星图 创作者」「蒲公英 画像」「受众分布」「性价比」等词即可定位创作者接口,用「京东 商品」「淘宝 评论」「1688 搜索」「亚马逊 选品」「ABA 关键词」等词定位电商/选品接口;`voc_api_doc` 会在请求行标注通道(ecommerce / overseas)并列出参数。当前清单 ecommerce 通道已登记约
|
|
69
|
+
> 提示:`voc_api_search` 用「星图 创作者」「蒲公英 画像」「受众分布」「性价比」等词即可定位创作者接口,用「京东 商品」「淘宝 评论」「1688 搜索」「亚马逊 选品」「ABA 关键词」等词定位电商/选品接口;`voc_api_doc` 会在请求行标注通道(ecommerce / overseas)并列出参数。当前清单 ecommerce 通道已登记约 288 个接口(电商商品 + 创作者 + 内容平台)、overseas 通道 37 个(亚马逊选品),未登记的可直接用 `voc_api_call` 传 `rawPath` + 对应 `channel` 调用。
|
|
70
|
+
|
|
71
|
+
## 淘宝/天猫版本选择与时效
|
|
72
|
+
|
|
73
|
+
- 查商品详情、SKU 标价与促销价:优先 `taobao.get_item_detail_v6`;查券后价/优惠后价使用 `taobao.get_item_detail_v4`。价格必须标注是标价、促销价、活动价还是券后价,不要压成单一「真实价格」。
|
|
74
|
+
- 查商品页当前展示的「已售/付款人数」:使用 `taobao.get_item_detail_v3` 的 `data.sellCount`。V3 不返回价格,价格与页面销量需分别调用 V6/V3。
|
|
75
|
+
- `taobao.get_item_detail_v1` 仅用于兼容旧流程;`v2` 直接调用不会发起商品采集。`taobao.get_item_sale_v1` 和 `taobao.get_shop_item_list_v3` 已被上游标记弃用。
|
|
76
|
+
- V9 的 `num` 是该版本销售量级信号,`totalCount` 是评价总量,两者都不是商品页当前展示付款人数。V9 `attribute` 可能是独立旧属性快照;价格和“上市时间”等关键属性优先 V6,页面销量优先 V3。
|
|
77
|
+
- 返回里的 `recordTime` 是上游数据采集时间,不是本次 API 请求时间。每个版本有独立数据源/快照,必须用「当前时间 - recordTime」计算滞后并在结果中明示。
|
|
78
|
+
- 上游文档未给出固定刷新 SLA,不要将返回值表述为「请求时实时数据」,也不要将旧版 `orderPayUV` 与商品页当前展示口径直接等同。需要页面口径时以 V3 `sellCount` 为准。
|
|
79
|
+
- 实际结算价还会受 SKU、账号会员价、地区、店铺券、平台补贴和活动时间影响。API 返回的是对应口径快照,不等于任意账号在结算页的最终应付价。
|
|
80
|
+
- 店铺商品列表的新调用优先 `taobao.get_shop_item_list_v4`,入参为 `sellerId` + 可选 `page`。
|
|
70
81
|
|
|
71
82
|
## Token(计费已迁移 NewAPI)
|
|
72
83
|
|