@vocmarket/voc-skill 0.3.30 → 0.3.31
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.
|
@@ -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.31",
|
|
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
|
+
};
|
|
@@ -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.31",
|
|
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
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-voc-intelligence",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.31",
|
|
4
4
|
"capabilities": {
|
|
5
5
|
"summary": "三类情报源同源接入:①社媒内容 ②国内电商+创作者情报 ③海外选品。统一 search→doc→call、鉴权计费一致。",
|
|
6
6
|
"channels": [
|
|
@@ -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
|
+
}
|