| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 |
- #!/usr/bin/env node
- const fs = require('fs');
- const path = require('path');
- const { spawnSync } = require('child_process');
- const ROOT = __dirname;
- const MIN_NODE_MAJOR = 18;
- const PACKAGE_NAMES = new Set([
- 'claude-code-voc-intelligence',
- '@gangvy/claude-code-voc-intelligence'
- ]);
- function parseArgs(argv) {
- const opts = {
- check: false,
- smoke: false,
- skipInstall: false,
- help: false,
- noNext: false
- };
- for (const token of argv) {
- if (token === '--check') opts.check = true;
- else if (token === '--smoke') opts.smoke = true;
- else if (token === '--skip-install') opts.skipInstall = true;
- else if (token === '--no-next') opts.noNext = true;
- else if (token === '--help' || token === '-h') opts.help = true;
- }
- return opts;
- }
- function usage() {
- return [
- 'Claude Code VOC Intelligence installer',
- '',
- 'Usage:',
- ' node install.js',
- ' node install.js --smoke',
- ' node install.js --check',
- '',
- 'Options:',
- ' --check Check files and environment only',
- ' --smoke Run sample/preference/MCP smoke checks',
- ' --skip-install Skip npm install',
- ' --no-next Do not print next-step instructions',
- ' --help, -h Show help'
- ].join('\n');
- }
- function run(command, args, options = {}) {
- const useCmd = process.platform === 'win32' && command === 'npm';
- const executable = useCmd ? 'cmd.exe' : command;
- const finalArgs = useCmd ? ['/d', '/s', '/c', 'npm', ...args] : args;
- const child = spawnSync(executable, finalArgs, {
- cwd: options.cwd || ROOT,
- encoding: 'utf8',
- stdio: options.stdio || 'inherit',
- maxBuffer: 1024 * 1024 * 100
- });
- if (child.status !== 0) {
- const detail = child.error ? `: ${child.error.message}` : '';
- throw new Error(`${command} ${args.join(' ')} failed with exit ${child.status}${detail}`);
- }
- return child;
- }
- function readJson(relativePath) {
- return JSON.parse(fs.readFileSync(path.join(ROOT, relativePath), 'utf8'));
- }
- function writeMcpConfig(rootDir = ROOT) {
- const mcpConfigPath = path.join(rootDir, '.mcp.json');
- const serverPath = path.join(rootDir, 'mcp', 'src', 'server.js');
- const mcpConfig = {
- mcpServers: {
- voc: {
- command: 'node',
- args: [serverPath],
- cwd: rootDir
- }
- }
- };
- fs.writeFileSync(mcpConfigPath, `${JSON.stringify(mcpConfig, null, 2)}\n`, 'utf8');
- }
- function assertFile(relativePath) {
- const absolute = path.join(ROOT, relativePath);
- if (!fs.existsSync(absolute)) {
- throw new Error(`Missing required file: ${relativePath}`);
- }
- }
- function checkNodeVersion() {
- const major = Number(process.versions.node.split('.')[0]);
- if (!Number.isFinite(major) || major < MIN_NODE_MAJOR) {
- throw new Error(`Node.js ${MIN_NODE_MAJOR}+ is required. Current version: ${process.version}`);
- }
- }
- function checkFiles() {
- [
- 'package.json',
- '.claude-plugin/plugin.json',
- '.mcp.json',
- 'skill-package-manifest.json',
- 'skills/xiaohongshu-trend-intelligence/SKILL.md',
- 'skills/douyin-trend-intelligence/SKILL.md',
- 'skills/voc-issue-pool/SKILL.md',
- 'skills/voc-problem-deep-dive/SKILL.md',
- 'skills/voc-content-plan/SKILL.md',
- 'skills/voc-speaking-script/SKILL.md',
- 'skills/voc-competitor-map/SKILL.md',
- 'skills/voc-business-workflow/SKILL.md',
- 'mcp/src/server.js'
- ].forEach(assertFile);
- const packageJson = readJson('package.json');
- const pluginJson = readJson('.claude-plugin/plugin.json');
- const mcpJson = readJson('.mcp.json');
- if (!PACKAGE_NAMES.has(packageJson.name)) {
- throw new Error('package.json name is invalid');
- }
- if (pluginJson.name !== 'voc-intelligence') {
- throw new Error('.claude-plugin/plugin.json name is invalid');
- }
- if (!mcpJson.mcpServers || !(mcpJson.mcpServers.voc || mcpJson.mcpServers['voc-intelligence'])) {
- throw new Error('.mcp.json does not configure the VOC MCP server');
- }
- }
- function checkClaudeCommand() {
- const command = process.platform === 'win32' ? 'where' : 'which';
- const child = spawnSync(command, ['claude'], {
- cwd: ROOT,
- encoding: 'utf8',
- stdio: 'pipe'
- });
- return child.status === 0;
- }
- function printNextSteps({ claudeAvailable }) {
- const loadCommand = `claude --plugin-dir "${ROOT}"`;
- console.log('');
- console.log('Install check complete.');
- console.log('');
- console.log('Next step:');
- console.log(` 1. Start Claude Code with: ${loadCommand}`);
- console.log(' 2. In Claude Code, say:');
- console.log(' 帮我看一下南昌餐饮最近顾客在关心什么,并告诉我先改哪里、下周发什么。');
- console.log(' 或者说:');
- console.log(' 帮我做一份家装全屋定制行业的小红书趋势情报。');
- console.log(' 先用演示样例跑通流程,不要真实采集。');
- console.log(' 在聊天里给我第一轮样本观察和待确认问题。');
- console.log(' 或者说:');
- console.log(' 帮我做一份家装全屋定制行业的抖音趋势情报。');
- console.log(' 先用演示样例跑通流程,不要真实采集。');
- console.log(' 在聊天里给我第一轮样本观察和待确认问题。');
- console.log('');
- if (!claudeAvailable) {
- console.log('Tip: the current terminal did not detect the claude command.');
- console.log('If Claude Code is installed elsewhere, run the command above in that terminal.');
- console.log('');
- }
- console.log('Live collection requires a Xiaohongshu or Douyin/VOC token. Sample mode works without a token.');
- }
- function main() {
- const opts = parseArgs(process.argv.slice(2));
- if (opts.help) {
- console.log(usage());
- return;
- }
- console.log('');
- console.log('Claude Code VOC Intelligence install check');
- console.log('==========================================');
- console.log('');
- checkNodeVersion();
- checkFiles();
- writeMcpConfig(ROOT);
- console.log(`Node.js: ${process.version}`);
- console.log('Required files: OK');
- if (!opts.check && !opts.skipInstall) {
- console.log('');
- console.log('Installing dependencies...');
- run('npm', ['install', '--omit=dev', '--ignore-scripts']);
- }
- if (opts.smoke) {
- console.log('');
- console.log('Running sample/preference/MCP smoke checks...');
- run(process.execPath, ['scripts/smoke-package.js']);
- }
- if (!opts.noNext) {
- printNextSteps({ claudeAvailable: checkClaudeCommand() });
- }
- }
- try {
- main();
- } catch (error) {
- console.error('');
- console.error(`Install failed: ${error.message}`);
- process.exit(1);
- }
|