install.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. #!/usr/bin/env node
  2. const fs = require('fs');
  3. const path = require('path');
  4. const { spawnSync } = require('child_process');
  5. const ROOT = __dirname;
  6. const MIN_NODE_MAJOR = 18;
  7. const PACKAGE_NAMES = new Set([
  8. 'claude-code-voc-intelligence',
  9. '@gangvy/claude-code-voc-intelligence'
  10. ]);
  11. function parseArgs(argv) {
  12. const opts = {
  13. check: false,
  14. smoke: false,
  15. skipInstall: false,
  16. help: false,
  17. noNext: false
  18. };
  19. for (const token of argv) {
  20. if (token === '--check') opts.check = true;
  21. else if (token === '--smoke') opts.smoke = true;
  22. else if (token === '--skip-install') opts.skipInstall = true;
  23. else if (token === '--no-next') opts.noNext = true;
  24. else if (token === '--help' || token === '-h') opts.help = true;
  25. }
  26. return opts;
  27. }
  28. function usage() {
  29. return [
  30. 'Claude Code VOC Intelligence 安装器',
  31. '',
  32. '用法:',
  33. ' node install.js',
  34. ' node install.js --smoke',
  35. ' node install.js --check',
  36. '',
  37. '参数:',
  38. ' --check 只检查文件和环境,不安装依赖',
  39. ' --smoke 安装后跑 sample 和 MCP smoke',
  40. ' --skip-install 跳过 npm install',
  41. ' --no-next 不打印下一步提示,供外层 CLI 调用',
  42. ' --help, -h 显示帮助'
  43. ].join('\n');
  44. }
  45. function run(command, args, options = {}) {
  46. const useCmd = process.platform === 'win32' && command === 'npm';
  47. const executable = useCmd ? 'cmd.exe' : command;
  48. const finalArgs = useCmd ? ['/d', '/s', '/c', 'npm', ...args] : args;
  49. const child = spawnSync(executable, finalArgs, {
  50. cwd: options.cwd || ROOT,
  51. encoding: 'utf8',
  52. stdio: options.stdio || 'inherit',
  53. maxBuffer: 1024 * 1024 * 100
  54. });
  55. if (child.status !== 0) {
  56. const detail = child.error ? `: ${child.error.message}` : '';
  57. throw new Error(`${command} ${args.join(' ')} failed with exit ${child.status}${detail}`);
  58. }
  59. return child;
  60. }
  61. function readJson(relativePath) {
  62. return JSON.parse(fs.readFileSync(path.join(ROOT, relativePath), 'utf8'));
  63. }
  64. function assertFile(relativePath) {
  65. const absolute = path.join(ROOT, relativePath);
  66. if (!fs.existsSync(absolute)) {
  67. throw new Error(`缺少必要文件:${relativePath}`);
  68. }
  69. }
  70. function checkNodeVersion() {
  71. const major = Number(process.versions.node.split('.')[0]);
  72. if (!Number.isFinite(major) || major < MIN_NODE_MAJOR) {
  73. throw new Error(`Node.js 版本过低:当前 ${process.version},需要 Node.js ${MIN_NODE_MAJOR}+`);
  74. }
  75. }
  76. function checkFiles() {
  77. [
  78. 'package.json',
  79. '.claude-plugin/plugin.json',
  80. '.mcp.json',
  81. 'skill-package-manifest.json',
  82. 'skills/xiaohongshu-trend-intelligence/SKILL.md',
  83. 'mcp/src/server.js'
  84. ].forEach(assertFile);
  85. const packageJson = readJson('package.json');
  86. const pluginJson = readJson('.claude-plugin/plugin.json');
  87. const mcpJson = readJson('.mcp.json');
  88. if (!PACKAGE_NAMES.has(packageJson.name)) {
  89. throw new Error('package.json name 不正确');
  90. }
  91. if (pluginJson.name !== 'voc-intelligence') {
  92. throw new Error('.claude-plugin/plugin.json name 不正确');
  93. }
  94. if (!mcpJson.mcpServers || !mcpJson.mcpServers['voc-intelligence']) {
  95. throw new Error('.mcp.json 未配置 voc-intelligence MCP server');
  96. }
  97. }
  98. function checkClaudeCommand() {
  99. const command = process.platform === 'win32' ? 'where' : 'which';
  100. const args = ['claude'];
  101. const child = spawnSync(command, args, {
  102. cwd: ROOT,
  103. encoding: 'utf8',
  104. stdio: 'pipe'
  105. });
  106. return child.status === 0;
  107. }
  108. function printNextSteps({ claudeAvailable }) {
  109. const loadCommand = `claude --plugin-dir "${ROOT}"`;
  110. console.log('');
  111. console.log('安装检查完成。');
  112. console.log('');
  113. console.log('下一步:');
  114. console.log(` 1. 启动 Claude Code:${loadCommand}`);
  115. console.log(' 2. 在 Claude Code 里说:');
  116. console.log(' 帮我做一份家装全屋定制行业的小红书趋势情报。');
  117. console.log(' 重点看女性客户下季度可能关注的设计元素、风格和消费顾虑。');
  118. console.log(' 先在聊天里给我第一轮样本观察和待确认问题,不要只给文件路径。');
  119. console.log('');
  120. if (!claudeAvailable) {
  121. console.log('提示:当前终端没有检测到 claude 命令。如果 Claude Code 已安装但不在 PATH 中,请在 Claude Code 所在终端里运行上面的加载命令。');
  122. console.log('');
  123. }
  124. console.log('真实采集需要配置小红书/TikHub token;未配置时仍可用 sample 模式做演示。');
  125. }
  126. function main() {
  127. const opts = parseArgs(process.argv.slice(2));
  128. if (opts.help) {
  129. console.log(usage());
  130. return;
  131. }
  132. console.log('');
  133. console.log('Claude Code VOC Intelligence 安装检查');
  134. console.log('====================================');
  135. console.log('');
  136. checkNodeVersion();
  137. checkFiles();
  138. console.log(`Node.js: ${process.version}`);
  139. console.log('必要文件:OK');
  140. if (!opts.check && !opts.skipInstall) {
  141. console.log('');
  142. console.log('正在安装依赖...');
  143. run('npm', ['install', '--omit=dev', '--ignore-scripts']);
  144. }
  145. if (opts.smoke) {
  146. console.log('');
  147. console.log('正在运行 sample / 偏好记忆 / MCP smoke...');
  148. run(process.execPath, ['scripts/smoke-package.js']);
  149. }
  150. if (!opts.noNext) {
  151. printNextSteps({ claudeAvailable: checkClaudeCommand() });
  152. }
  153. }
  154. try {
  155. main();
  156. } catch (error) {
  157. console.error('');
  158. console.error(`安装失败:${error.message}`);
  159. process.exit(1);
  160. }