#!/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 安装器', '', '用法:', ' node install.js', ' node install.js --smoke', ' node install.js --check', '', '参数:', ' --check 只检查文件和环境,不安装依赖', ' --smoke 安装后跑 sample 和 MCP smoke', ' --skip-install 跳过 npm install', ' --no-next 不打印下一步提示,供外层 CLI 调用', ' --help, -h 显示帮助' ].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 assertFile(relativePath) { const absolute = path.join(ROOT, relativePath); if (!fs.existsSync(absolute)) { throw new Error(`缺少必要文件:${relativePath}`); } } function checkNodeVersion() { const major = Number(process.versions.node.split('.')[0]); if (!Number.isFinite(major) || major < MIN_NODE_MAJOR) { throw new Error(`Node.js 版本过低:当前 ${process.version},需要 Node.js ${MIN_NODE_MAJOR}+`); } } function checkFiles() { [ 'package.json', '.claude-plugin/plugin.json', '.mcp.json', 'skill-package-manifest.json', 'skills/xiaohongshu-trend-intelligence/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 不正确'); } if (pluginJson.name !== 'voc-intelligence') { throw new Error('.claude-plugin/plugin.json name 不正确'); } if (!mcpJson.mcpServers || !mcpJson.mcpServers['voc-intelligence']) { throw new Error('.mcp.json 未配置 voc-intelligence MCP server'); } } function checkClaudeCommand() { const command = process.platform === 'win32' ? 'where' : 'which'; const args = ['claude']; const child = spawnSync(command, args, { cwd: ROOT, encoding: 'utf8', stdio: 'pipe' }); return child.status === 0; } function printNextSteps({ claudeAvailable }) { const loadCommand = `claude --plugin-dir "${ROOT}"`; console.log(''); console.log('安装检查完成。'); console.log(''); console.log('下一步:'); console.log(` 1. 启动 Claude Code:${loadCommand}`); console.log(' 2. 在 Claude Code 里说:'); console.log(' 帮我做一份家装全屋定制行业的小红书趋势情报。'); console.log(' 重点看女性客户下季度可能关注的设计元素、风格和消费顾虑。'); console.log(' 先在聊天里给我第一轮样本观察和待确认问题,不要只给文件路径。'); console.log(''); if (!claudeAvailable) { console.log('提示:当前终端没有检测到 claude 命令。如果 Claude Code 已安装但不在 PATH 中,请在 Claude Code 所在终端里运行上面的加载命令。'); console.log(''); } console.log('真实采集需要配置小红书/TikHub token;未配置时仍可用 sample 模式做演示。'); } function main() { const opts = parseArgs(process.argv.slice(2)); if (opts.help) { console.log(usage()); return; } console.log(''); console.log('Claude Code VOC Intelligence 安装检查'); console.log('===================================='); console.log(''); checkNodeVersion(); checkFiles(); console.log(`Node.js: ${process.version}`); console.log('必要文件:OK'); if (!opts.check && !opts.skipInstall) { console.log(''); console.log('正在安装依赖...'); run('npm', ['install', '--omit=dev', '--ignore-scripts']); } if (opts.smoke) { console.log(''); console.log('正在运行 sample / 偏好记忆 / MCP smoke...'); run(process.execPath, ['scripts/smoke-package.js']); } if (!opts.noNext) { printNextSteps({ claudeAvailable: checkClaudeCommand() }); } } try { main(); } catch (error) { console.error(''); console.error(`安装失败:${error.message}`); process.exit(1); }