sillyspec 3.26.9 → 3.26.11

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.
Files changed (98) hide show
  1. package/.claude/skills/sillyspec-archive/SKILL.md +85 -85
  2. package/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -114
  3. package/.claude/skills/sillyspec-commit/SKILL.md +109 -109
  4. package/.claude/skills/sillyspec-continue/SKILL.md +45 -45
  5. package/.claude/skills/sillyspec-doctor/SKILL.md +76 -76
  6. package/.claude/skills/sillyspec-execute/SKILL.md +200 -200
  7. package/.claude/skills/sillyspec-explore/SKILL.md +95 -95
  8. package/.claude/skills/sillyspec-knowledge/SKILL.md +269 -269
  9. package/.claude/skills/sillyspec-plan/SKILL.md +148 -148
  10. package/.claude/skills/sillyspec-quick/SKILL.md +111 -111
  11. package/.claude/skills/sillyspec-resume/SKILL.md +68 -68
  12. package/.claude/skills/sillyspec-scan/SKILL.md +79 -79
  13. package/.claude/skills/sillyspec-state/SKILL.md +64 -64
  14. package/.claude/skills/sillyspec-status/SKILL.md +54 -54
  15. package/.claude/skills/sillyspec-verify/SKILL.md +83 -81
  16. package/.claude/skills/sillyspec-workspace/SKILL.md +169 -169
  17. package/.gitattributes +2 -0
  18. package/.husky/pre-push +20 -20
  19. package/.npmrc-publish +2 -0
  20. package/README.md +201 -201
  21. package/SKILL.md +90 -90
  22. package/package.json +39 -39
  23. package/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -7446
  24. package/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -1
  25. package/packages/dashboard/dist/index.html +16 -16
  26. package/packages/dashboard/package.json +25 -25
  27. package/packages/dashboard/server/executor.js +86 -86
  28. package/packages/dashboard/server/index.js +601 -601
  29. package/packages/dashboard/server/parser.js +526 -526
  30. package/src/change-risk-profile.js +254 -254
  31. package/src/classify-change.js +109 -109
  32. package/src/config-schema.js +310 -310
  33. package/src/contract-matrix.js +379 -379
  34. package/src/db.js +350 -350
  35. package/src/dispatch/backends/local-agent.js +155 -155
  36. package/src/dispatch/backends/sillyhub-mcp.js +245 -245
  37. package/src/dispatch/probe.js +210 -210
  38. package/src/dispatch/strategy.js +121 -121
  39. package/src/docs-check.js +131 -4
  40. package/src/doctor-diagnostics.js +711 -711
  41. package/src/endpoint-extractor.js +401 -401
  42. package/src/fs-atomic.js +69 -69
  43. package/src/git-helper.js +79 -79
  44. package/src/hooks/claude-pre-tool-use.cjs +125 -125
  45. package/src/hooks/worktree-guard.js +848 -848
  46. package/src/index.js +90 -8
  47. package/src/init.js +623 -623
  48. package/src/knowledge-match.js +130 -130
  49. package/src/local-detect.js +120 -120
  50. package/src/machine-interface.js +543 -543
  51. package/src/migrate.js +127 -127
  52. package/src/progress/change-registry.js +297 -260
  53. package/src/progress/consistency-doctor.js +355 -355
  54. package/src/progress/shared.js +36 -36
  55. package/src/progress/stage-machine.js +29 -1
  56. package/src/progress/step-store.js +261 -261
  57. package/src/progress.js +1131 -1127
  58. package/src/quicklog.js +693 -675
  59. package/src/review-tier.js +77 -77
  60. package/src/run/command.js +1385 -1364
  61. package/src/run/complete-handlers.js +198 -9
  62. package/src/run/complete.js +225 -24
  63. package/src/run/concurrent-detect.js +22 -0
  64. package/src/run/gates.js +755 -755
  65. package/src/run/prompt.js +761 -721
  66. package/src/run/quick-audit.js +155 -146
  67. package/src/run/shared.js +1268 -1197
  68. package/src/run/stage.js +487 -479
  69. package/src/scan-postcheck.js +371 -371
  70. package/src/setup.js +416 -416
  71. package/src/sillyhub-mcp/client.js +529 -529
  72. package/src/spec-sync.js +323 -0
  73. package/src/stage-contract.js +897 -897
  74. package/src/stage-review.js +549 -549
  75. package/src/stages/archive.js +155 -154
  76. package/src/stages/brainstorm-auto.js +236 -236
  77. package/src/stages/brainstorm.js +596 -597
  78. package/src/stages/cmd-existence.js +145 -145
  79. package/src/stages/doctor.js +371 -371
  80. package/src/stages/execute.js +1003 -991
  81. package/src/stages/explore.js +34 -34
  82. package/src/stages/index.js +29 -29
  83. package/src/stages/knowledge.js +498 -498
  84. package/src/stages/plan-postcheck.js +1302 -1281
  85. package/src/stages/plan.js +625 -604
  86. package/src/stages/quick.js +151 -143
  87. package/src/stages/scan.js +606 -605
  88. package/src/stages/status.js +64 -64
  89. package/src/stages/verify.js +264 -261
  90. package/src/sync.js +1223 -1125
  91. package/src/task-review.js +1019 -1019
  92. package/src/verify-postcheck.js +1186 -1186
  93. package/src/workflow.js +600 -600
  94. package/src/worktree-apply.js +1213 -1136
  95. package/src/worktree-deps.js +353 -353
  96. package/src/worktree.js +1481 -1437
  97. package/templates/workflows/archive-impact.yaml +79 -79
  98. package/templates/workflows/scan-docs.yaml +132 -132
package/src/setup.js CHANGED
@@ -1,416 +1,416 @@
1
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
2
- import { join, dirname } from 'path';
3
- import { execSync, execFileSync } from 'child_process';
4
- import chalk from 'chalk';
5
- import ora from 'ora';
6
- import { checkbox, input } from '@inquirer/prompts';
7
-
8
- // ── MCP 工具定义 ──
9
-
10
- const MCP_TOOLS = [
11
- {
12
- id: 'context7',
13
- name: 'Context7',
14
- description: '查询最新库文档和 API 参考',
15
- command: 'npx',
16
- // 版本锁定(sec-c):MCP server 常驻且有系统访问能力,@latest = trust-on-future-publish,
17
- // 上游被攻破会直接进用户 AI 工具执行环境。升级走 setup 重跑。
18
- args: ['-y', '@upstash/context7-mcp@4.0.2'],
19
- url: 'https://github.com/upstash/context7-mcp',
20
- },
21
- {
22
- id: 'chrome-devtools',
23
- name: 'Chrome DevTools MCP',
24
- description: '浏览器自动化,支持 E2E 测试(需 Chrome 已运行)',
25
- command: 'npx',
26
- args: ['chrome-devtools-mcp@1.7.0'],
27
- url: 'https://github.com/ChromeDevTools/chrome-devtools-mcp',
28
- },
29
- {
30
- id: 'agent-browser',
31
- name: 'Agent Browser (Vercel)',
32
- description: 'Rust 原生浏览器 CLI,token 消耗极低,50+ 命令覆盖导航/表单/截图/网络',
33
- command: 'npx',
34
- args: ['agent-browser@0.34.0'],
35
- url: 'https://github.com/vercel-labs/agent-browser',
36
- },
37
- {
38
- id: 'pinchtab',
39
- name: 'PinchTab',
40
- description: '12MB Go 二进制,零依赖,accessibility tree 极省 token,有 MCP 支持',
41
- command: 'npx',
42
- args: ['pinchtab-mcp@1.4.1'],
43
- url: 'https://github.com/pinchtab/pinchtab',
44
- },
45
- ];
46
-
47
- // ── 数据库 MCP 定义(需要连接信息)──
48
-
49
- const DB_MCP_TOOLS = [
50
- {
51
- id: 'postgres',
52
- name: 'PostgreSQL',
53
- description: '只读访问 PostgreSQL 数据库',
54
- command: 'npx',
55
- args: ['-y', '@modelcontextprotocol/server-postgres@0.6.2'],
56
- envTemplate: { POSTGRES_CONNECTION_STRING: 'postgresql://user:password@localhost:5432/dbname' },
57
- url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/postgres',
58
- },
59
- {
60
- id: 'sqlite',
61
- name: 'SQLite',
62
- description: '访问 SQLite 数据库文件',
63
- command: 'npx',
64
- args: ['-y', 'mcp-server-sqlite@0.0.2'],
65
- envTemplate: { SQLITE_DB_PATH: './data.db' },
66
- url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/sqlite',
67
- },
68
- {
69
- id: 'mysql',
70
- name: 'MySQL / MariaDB',
71
- description: '访问 MySQL / MariaDB 数据库',
72
- command: 'npx',
73
- // 原 @nicobailon/mysql-mcp-server 已 404(包下架),换存在的 mysql-mcp-server(dpflucas,read-only)
74
- args: ['-y', 'mysql-mcp-server@0.1.3'],
75
- envTemplate: { MYSQL_HOST: 'localhost', MYSQL_PORT: '3306', MYSQL_USER: 'root', MYSQL_PASSWORD: '', MYSQL_DATABASE: '' },
76
- url: 'https://github.com/modelcontextprotocol/servers',
77
- },
78
- {
79
- id: 'redis',
80
- name: 'Redis',
81
- description: '访问 Redis 数据库,查看缓存和键值数据',
82
- command: 'npx',
83
- args: ['-y', '@modelcontextprotocol/server-redis@2025.4.25'],
84
- envTemplate: { REDIS_URL: 'redis://localhost:6379' },
85
- url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/redis',
86
- },
87
- ];
88
-
89
- // ── 全局工具定义 ──
90
-
91
- const GLOBAL_TOOLS = [
92
- {
93
- id: 'playwright',
94
- name: 'Playwright',
95
- description: 'E2E 测试框架,支持浏览器自动化测试',
96
- checkCommand: 'npx playwright --version',
97
- installCommand: 'npm install -g @playwright/test && npx playwright install chromium',
98
- url: 'https://playwright.dev',
99
- },
100
- ];
101
-
102
- // ── MCP 配置文件路径 ──
103
-
104
- const MCP_CONFIG_PATHS = [
105
- { tool: 'Claude Code', path: '.claude/mcp.json', key: 'claude' },
106
- { tool: 'Cursor', path: '.cursor/mcp.json', key: 'cursor' },
107
- ];
108
-
109
- // ── 工具函数 ──
110
-
111
- function readMcpConfig(dir, configPath) {
112
- const fullPath = join(dir, configPath);
113
- if (!existsSync(fullPath)) return null;
114
- try {
115
- return JSON.parse(readFileSync(fullPath, 'utf8'));
116
- } catch {
117
- return null;
118
- }
119
- }
120
-
121
- function writeMcpConfig(dir, configPath, config) {
122
- const fullPath = join(dir, configPath);
123
- mkdirSync(dirname(fullPath), { recursive: true });
124
- writeFileSync(fullPath, JSON.stringify(config, null, 2) + '\n');
125
- }
126
-
127
- function installMcp(config, mcpTool) {
128
- if (!config.mcpServers) config.mcpServers = {};
129
- const entry = {
130
- command: mcpTool.command,
131
- args: mcpTool.args,
132
- };
133
- if (mcpTool.env) entry.env = mcpTool.env;
134
- config.mcpServers[mcpTool.id] = entry;
135
- return config;
136
- }
137
-
138
- function hasMcpInstalled(config, mcpId) {
139
- return config?.mcpServers?.[mcpId] != null;
140
- }
141
-
142
- // ── 命令实现 ──
143
-
144
- export async function cmdSetup(dir, options = {}) {
145
- const { json } = options;
146
-
147
- // 检查哪些 AI 工具有配置文件
148
- const availableTools = MCP_CONFIG_PATHS.filter(({ path }) => {
149
- return existsSync(join(dir, path));
150
- });
151
-
152
- // --list 模式:只查看状态(不依赖 AI 工具配置)
153
- if (options.list) {
154
- const results = {};
155
-
156
- // MCP 状态
157
- for (const { tool, path } of availableTools) {
158
- const config = readMcpConfig(dir, path);
159
- const mcpStatus = {};
160
- for (const mcp of MCP_TOOLS) {
161
- mcpStatus[mcp.id] = hasMcpInstalled(config, mcp.id);
162
- }
163
- for (const db of DB_MCP_TOOLS) {
164
- mcpStatus[db.id] = hasMcpInstalled(config, db.id);
165
- }
166
- results[tool] = { configPath: path, mcp: mcpStatus };
167
- }
168
-
169
- // 全局工具状态
170
- results['全局工具'] = {};
171
- for (const g of GLOBAL_TOOLS) {
172
- try {
173
- execSync(g.checkCommand, { stdio: 'pipe', encoding: 'utf8' });
174
- results['全局工具'][g.id] = true;
175
- } catch {
176
- results['全局工具'][g.id] = false;
177
- }
178
- }
179
-
180
- if (json) {
181
- console.log(JSON.stringify(results, null, 2));
182
- } else {
183
- console.log('');
184
- for (const [tool, data] of Object.entries(results)) {
185
- if (data.mcp) {
186
- console.log(chalk.bold(`📋 ${tool} (${data.configPath})`));
187
- for (const mcp of MCP_TOOLS) {
188
- const status = data.mcp[mcp.id] ? chalk.green('✅') : chalk.gray('⬜');
189
- console.log(` ${status} ${mcp.name} — ${mcp.description}`);
190
- }
191
- for (const db of DB_MCP_TOOLS) {
192
- const status = data.mcp[db.id] ? chalk.green('✅') : chalk.gray('⬜');
193
- console.log(` ${status} ${db.name} — ${db.description}`);
194
- }
195
- } else {
196
- console.log(chalk.bold(`🛠️ ${tool}`));
197
- for (const g of GLOBAL_TOOLS) {
198
- const status = data[g.id] ? chalk.green('✅') : chalk.gray('⬜');
199
- console.log(` ${status} ${g.name} — ${g.description}`);
200
- }
201
- }
202
- console.log('');
203
- }
204
- }
205
- return;
206
- }
207
-
208
- // 非交互模式(或没有 AI 工具但有全局工具要装)
209
- if (!process.stdin.isTTY) {
210
- if (json) {
211
- console.log(JSON.stringify({ message: '交互模式需要 TTY,请运行 sillyspec setup' }));
212
- } else {
213
- console.log(chalk.yellow('⚠️ 请在交互式终端运行 sillyspec setup'));
214
- }
215
- return;
216
- }
217
-
218
- // ── 交互式安装 ──
219
-
220
- console.log('');
221
- console.log(chalk.cyan('🔧 SillySpec Setup — 增强工具安装'));
222
- console.log('');
223
-
224
- if (availableTools.length === 0) {
225
- console.log(chalk.dim(' 未检测到 AI 工具配置文件,MCP 工具将跳过。'));
226
- console.log(chalk.dim(' 请先运行 sillyspec init 初始化项目。'));
227
- console.log('');
228
- }
229
-
230
- console.log(' 选择要安装的工具(已安装的会跳过):');
231
- console.log('');
232
-
233
- // ── MCP 工具选择 ──
234
-
235
- const installedSet = new Set();
236
- for (const { path } of availableTools) {
237
- const config = readMcpConfig(dir, path);
238
- for (const mcp of MCP_TOOLS) {
239
- if (hasMcpInstalled(config, mcp.id)) installedSet.add(mcp.id);
240
- }
241
- }
242
-
243
- const mcpChoices = MCP_TOOLS.filter(m => !installedSet.has(m.id)).map(m => ({
244
- name: `${m.name} — ${m.description}`,
245
- value: `mcp:${m.id}`,
246
- checked: false,
247
- }));
248
-
249
- // ── 全局工具选择 ──
250
-
251
- const globalInstalled = new Set();
252
- for (const tool of GLOBAL_TOOLS) {
253
- try {
254
- execSync(tool.checkCommand, { stdio: 'pipe', encoding: 'utf8' });
255
- globalInstalled.add(tool.id);
256
- } catch {}
257
- }
258
-
259
- const globalChoices = GLOBAL_TOOLS.filter(t => !globalInstalled.has(t.id)).map(t => ({
260
- name: `${t.name} — ${t.description}(全局安装)`,
261
- value: `global:${t.id}`,
262
- checked: false,
263
- }));
264
-
265
- // ── 数据库 MCP 选择 ──
266
-
267
- const dbInstalled = new Set();
268
- for (const { path } of availableTools) {
269
- const config = readMcpConfig(dir, path);
270
- for (const db of DB_MCP_TOOLS) {
271
- if (hasMcpInstalled(config, db.id)) dbInstalled.add(db.id);
272
- }
273
- }
274
-
275
- const dbChoices = DB_MCP_TOOLS.filter(d => !dbInstalled.has(d.id)).map(d => ({
276
- name: `${d.name} — ${d.description}(需要连接信息)`,
277
- value: `db:${d.id}`,
278
- checked: false,
279
- }));
280
-
281
- const allChoices = [
282
- ...mcpChoices.length > 0 ? [{ name: chalk.bold('── MCP 工具(AI 能力增强)──'), value: '_mcp_header', disabled: true }] : [],
283
- ...mcpChoices,
284
- ...dbChoices.length > 0 ? [{ name: chalk.bold('── 数据库 MCP ──'), value: '_db_header', disabled: true }] : [],
285
- ...dbChoices,
286
- ...globalChoices.length > 0 ? [{ name: chalk.bold('── 全局工具 ──'), value: '_global_header', disabled: true }] : [],
287
- ...globalChoices,
288
- ];
289
-
290
- if (allChoices.length === 0) {
291
- console.log(chalk.green(' ✅ 所有推荐工具已安装!'));
292
- return;
293
- }
294
-
295
- const selected = await checkbox({
296
- message: '选择要安装的工具',
297
- choices: allChoices,
298
- });
299
-
300
- if (selected.length === 0) {
301
- console.log(chalk.dim(' 未选择任何工具,退出。'));
302
- return;
303
- }
304
-
305
- const selectedMcp = MCP_TOOLS.filter(m => selected.includes(`mcp:${m.id}`));
306
- const selectedDb = DB_MCP_TOOLS.filter(d => selected.includes(`db:${d.id}`));
307
- const selectedGlobal = GLOBAL_TOOLS.filter(t => selected.includes(`global:${t.id}`));
308
-
309
- // ── 收集数据库连接信息 ──
310
-
311
- if (selectedDb.length > 0) {
312
- console.log('');
313
- console.log(chalk.yellow('📡 数据库连接配置'));
314
- console.log('');
315
-
316
- for (const db of selectedDb) {
317
- console.log(chalk.cyan(` ${db.name}:`));
318
- const dbWithEnv = { ...db, env: {} };
319
- for (const [key, placeholder] of Object.entries(db.envTemplate)) {
320
- const value = await input({
321
- message: ` ${key}`,
322
- default: placeholder,
323
- });
324
- dbWithEnv.env[key] = value;
325
- }
326
- // 用带 env 的版本替换
327
- const idx = selectedDb.indexOf(db);
328
- selectedDb[idx] = dbWithEnv;
329
- }
330
- }
331
-
332
- // ── 安装 MCP 工具 ──
333
-
334
- const allMcp = [...selectedMcp, ...selectedDb];
335
-
336
- if (allMcp.length > 0) {
337
- const toolChoices = availableTools.map(t => ({
338
- name: t.tool,
339
- value: t.key,
340
- checked: true,
341
- }));
342
-
343
- const targetTools = await checkbox({
344
- message: 'MCP 工具安装到哪些 AI 工具?',
345
- choices: toolChoices,
346
- });
347
-
348
- const targets = availableTools.filter(t => targetTools.includes(t.key));
349
-
350
- console.log('');
351
- for (const { tool, path } of targets) {
352
- const spinner = ora(`安装 MCP 到 ${tool}...`).start();
353
- let config = readMcpConfig(dir, path) || { mcpServers: {} };
354
- for (const mcp of allMcp) {
355
- config = installMcp(config, mcp);
356
- }
357
- writeMcpConfig(dir, path, config);
358
- spinner.succeed(`${tool} MCP 完成 (${allMcp.length} 个工具)`);
359
- // 凭据提交风险提示(sec-c):.claude/mcp.json / .cursor/mcp.json 是项目共享文件、通常随 git
360
- // 提交,而 DB MCP 的 env 含明文连接串/密码。检测 git 跟踪状态,已跟踪/未忽略时显式警告。
361
- if (selectedDb.length > 0) {
362
- try {
363
- execFileSync('git', ['-C', dir, 'ls-files', '--error-unmatch', path], { stdio: 'pipe' });
364
- console.warn(chalk.yellow(` ⚠️ ${path} 已被 git 跟踪——其中数据库连接串/密码会随提交进入仓库,建议加入 .gitignore 或改用环境变量`));
365
- } catch {
366
- // 未被跟踪:再查是否被 ignore;都不满足(未忽略未跟踪)时提示防止将来误提交
367
- try {
368
- execFileSync('git', ['-C', dir, 'check-ignore', '-q', path], { stdio: 'pipe' });
369
- } catch {
370
- console.warn(chalk.yellow(` ⚠️ ${path} 含数据库连接串/密码且未被 .gitignore 忽略——提交前请先加入 .gitignore 或改用环境变量`));
371
- }
372
- }
373
- }
374
- }
375
- }
376
-
377
- // ── 安装全局工具 ──
378
-
379
- if (selectedGlobal.length > 0) {
380
- console.log('');
381
- for (const tool of selectedGlobal) {
382
- const spinner = ora(`安装 ${tool.name}...`).start();
383
- try {
384
- execSync(tool.installCommand, { stdio: 'pipe', encoding: 'utf8', timeout: 120000 });
385
- spinner.succeed(`${tool.name} 安装完成`);
386
- } catch (err) {
387
- spinner.fail(`${tool.name} 安装失败: ${err.message}`);
388
- }
389
- }
390
- }
391
-
392
- // ── 总结 ──
393
-
394
- console.log('');
395
- console.log(chalk.green(' ═══════════════════════════════════════'));
396
- console.log(chalk.green(' ✅ 工具安装完成!'));
397
- console.log(chalk.green(' ═══════════════════════════════════════'));
398
- console.log('');
399
- for (const mcp of selectedMcp) {
400
- console.log(` 🔌 ${chalk.cyan(mcp.name)} — ${mcp.description}`);
401
- console.log(chalk.dim(` ${mcp.url}`));
402
- }
403
- for (const db of selectedDb) {
404
- console.log(` 🗄️ ${chalk.cyan(db.name)} — ${db.description}`);
405
- console.log(chalk.dim(` ${db.url}`));
406
- }
407
- for (const g of selectedGlobal) {
408
- console.log(` 🛠️ ${chalk.cyan(g.name)} — ${g.description}`);
409
- console.log(chalk.dim(` ${g.url}`));
410
- }
411
- console.log('');
412
- if (selectedMcp.length > 0) {
413
- console.log(chalk.dim(' 重启你的 AI 工具以使 MCP 配置生效。'));
414
- }
415
- console.log('');
416
- }
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import { execSync, execFileSync } from 'child_process';
4
+ import chalk from 'chalk';
5
+ import ora from 'ora';
6
+ import { checkbox, input } from '@inquirer/prompts';
7
+
8
+ // ── MCP 工具定义 ──
9
+
10
+ const MCP_TOOLS = [
11
+ {
12
+ id: 'context7',
13
+ name: 'Context7',
14
+ description: '查询最新库文档和 API 参考',
15
+ command: 'npx',
16
+ // 版本锁定(sec-c):MCP server 常驻且有系统访问能力,@latest = trust-on-future-publish,
17
+ // 上游被攻破会直接进用户 AI 工具执行环境。升级走 setup 重跑。
18
+ args: ['-y', '@upstash/context7-mcp@4.0.2'],
19
+ url: 'https://github.com/upstash/context7-mcp',
20
+ },
21
+ {
22
+ id: 'chrome-devtools',
23
+ name: 'Chrome DevTools MCP',
24
+ description: '浏览器自动化,支持 E2E 测试(需 Chrome 已运行)',
25
+ command: 'npx',
26
+ args: ['chrome-devtools-mcp@1.7.0'],
27
+ url: 'https://github.com/ChromeDevTools/chrome-devtools-mcp',
28
+ },
29
+ {
30
+ id: 'agent-browser',
31
+ name: 'Agent Browser (Vercel)',
32
+ description: 'Rust 原生浏览器 CLI,token 消耗极低,50+ 命令覆盖导航/表单/截图/网络',
33
+ command: 'npx',
34
+ args: ['agent-browser@0.34.0'],
35
+ url: 'https://github.com/vercel-labs/agent-browser',
36
+ },
37
+ {
38
+ id: 'pinchtab',
39
+ name: 'PinchTab',
40
+ description: '12MB Go 二进制,零依赖,accessibility tree 极省 token,有 MCP 支持',
41
+ command: 'npx',
42
+ args: ['pinchtab-mcp@1.4.1'],
43
+ url: 'https://github.com/pinchtab/pinchtab',
44
+ },
45
+ ];
46
+
47
+ // ── 数据库 MCP 定义(需要连接信息)──
48
+
49
+ const DB_MCP_TOOLS = [
50
+ {
51
+ id: 'postgres',
52
+ name: 'PostgreSQL',
53
+ description: '只读访问 PostgreSQL 数据库',
54
+ command: 'npx',
55
+ args: ['-y', '@modelcontextprotocol/server-postgres@0.6.2'],
56
+ envTemplate: { POSTGRES_CONNECTION_STRING: 'postgresql://user:password@localhost:5432/dbname' },
57
+ url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/postgres',
58
+ },
59
+ {
60
+ id: 'sqlite',
61
+ name: 'SQLite',
62
+ description: '访问 SQLite 数据库文件',
63
+ command: 'npx',
64
+ args: ['-y', 'mcp-server-sqlite@0.0.2'],
65
+ envTemplate: { SQLITE_DB_PATH: './data.db' },
66
+ url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/sqlite',
67
+ },
68
+ {
69
+ id: 'mysql',
70
+ name: 'MySQL / MariaDB',
71
+ description: '访问 MySQL / MariaDB 数据库',
72
+ command: 'npx',
73
+ // 原 @nicobailon/mysql-mcp-server 已 404(包下架),换存在的 mysql-mcp-server(dpflucas,read-only)
74
+ args: ['-y', 'mysql-mcp-server@0.1.3'],
75
+ envTemplate: { MYSQL_HOST: 'localhost', MYSQL_PORT: '3306', MYSQL_USER: 'root', MYSQL_PASSWORD: '', MYSQL_DATABASE: '' },
76
+ url: 'https://github.com/modelcontextprotocol/servers',
77
+ },
78
+ {
79
+ id: 'redis',
80
+ name: 'Redis',
81
+ description: '访问 Redis 数据库,查看缓存和键值数据',
82
+ command: 'npx',
83
+ args: ['-y', '@modelcontextprotocol/server-redis@2025.4.25'],
84
+ envTemplate: { REDIS_URL: 'redis://localhost:6379' },
85
+ url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/redis',
86
+ },
87
+ ];
88
+
89
+ // ── 全局工具定义 ──
90
+
91
+ const GLOBAL_TOOLS = [
92
+ {
93
+ id: 'playwright',
94
+ name: 'Playwright',
95
+ description: 'E2E 测试框架,支持浏览器自动化测试',
96
+ checkCommand: 'npx playwright --version',
97
+ installCommand: 'npm install -g @playwright/test && npx playwright install chromium',
98
+ url: 'https://playwright.dev',
99
+ },
100
+ ];
101
+
102
+ // ── MCP 配置文件路径 ──
103
+
104
+ const MCP_CONFIG_PATHS = [
105
+ { tool: 'Claude Code', path: '.claude/mcp.json', key: 'claude' },
106
+ { tool: 'Cursor', path: '.cursor/mcp.json', key: 'cursor' },
107
+ ];
108
+
109
+ // ── 工具函数 ──
110
+
111
+ function readMcpConfig(dir, configPath) {
112
+ const fullPath = join(dir, configPath);
113
+ if (!existsSync(fullPath)) return null;
114
+ try {
115
+ return JSON.parse(readFileSync(fullPath, 'utf8'));
116
+ } catch {
117
+ return null;
118
+ }
119
+ }
120
+
121
+ function writeMcpConfig(dir, configPath, config) {
122
+ const fullPath = join(dir, configPath);
123
+ mkdirSync(dirname(fullPath), { recursive: true });
124
+ writeFileSync(fullPath, JSON.stringify(config, null, 2) + '\n');
125
+ }
126
+
127
+ function installMcp(config, mcpTool) {
128
+ if (!config.mcpServers) config.mcpServers = {};
129
+ const entry = {
130
+ command: mcpTool.command,
131
+ args: mcpTool.args,
132
+ };
133
+ if (mcpTool.env) entry.env = mcpTool.env;
134
+ config.mcpServers[mcpTool.id] = entry;
135
+ return config;
136
+ }
137
+
138
+ function hasMcpInstalled(config, mcpId) {
139
+ return config?.mcpServers?.[mcpId] != null;
140
+ }
141
+
142
+ // ── 命令实现 ──
143
+
144
+ export async function cmdSetup(dir, options = {}) {
145
+ const { json } = options;
146
+
147
+ // 检查哪些 AI 工具有配置文件
148
+ const availableTools = MCP_CONFIG_PATHS.filter(({ path }) => {
149
+ return existsSync(join(dir, path));
150
+ });
151
+
152
+ // --list 模式:只查看状态(不依赖 AI 工具配置)
153
+ if (options.list) {
154
+ const results = {};
155
+
156
+ // MCP 状态
157
+ for (const { tool, path } of availableTools) {
158
+ const config = readMcpConfig(dir, path);
159
+ const mcpStatus = {};
160
+ for (const mcp of MCP_TOOLS) {
161
+ mcpStatus[mcp.id] = hasMcpInstalled(config, mcp.id);
162
+ }
163
+ for (const db of DB_MCP_TOOLS) {
164
+ mcpStatus[db.id] = hasMcpInstalled(config, db.id);
165
+ }
166
+ results[tool] = { configPath: path, mcp: mcpStatus };
167
+ }
168
+
169
+ // 全局工具状态
170
+ results['全局工具'] = {};
171
+ for (const g of GLOBAL_TOOLS) {
172
+ try {
173
+ execSync(g.checkCommand, { stdio: 'pipe', encoding: 'utf8' });
174
+ results['全局工具'][g.id] = true;
175
+ } catch {
176
+ results['全局工具'][g.id] = false;
177
+ }
178
+ }
179
+
180
+ if (json) {
181
+ console.log(JSON.stringify(results, null, 2));
182
+ } else {
183
+ console.log('');
184
+ for (const [tool, data] of Object.entries(results)) {
185
+ if (data.mcp) {
186
+ console.log(chalk.bold(`📋 ${tool} (${data.configPath})`));
187
+ for (const mcp of MCP_TOOLS) {
188
+ const status = data.mcp[mcp.id] ? chalk.green('✅') : chalk.gray('⬜');
189
+ console.log(` ${status} ${mcp.name} — ${mcp.description}`);
190
+ }
191
+ for (const db of DB_MCP_TOOLS) {
192
+ const status = data.mcp[db.id] ? chalk.green('✅') : chalk.gray('⬜');
193
+ console.log(` ${status} ${db.name} — ${db.description}`);
194
+ }
195
+ } else {
196
+ console.log(chalk.bold(`🛠️ ${tool}`));
197
+ for (const g of GLOBAL_TOOLS) {
198
+ const status = data[g.id] ? chalk.green('✅') : chalk.gray('⬜');
199
+ console.log(` ${status} ${g.name} — ${g.description}`);
200
+ }
201
+ }
202
+ console.log('');
203
+ }
204
+ }
205
+ return;
206
+ }
207
+
208
+ // 非交互模式(或没有 AI 工具但有全局工具要装)
209
+ if (!process.stdin.isTTY) {
210
+ if (json) {
211
+ console.log(JSON.stringify({ message: '交互模式需要 TTY,请运行 sillyspec setup' }));
212
+ } else {
213
+ console.log(chalk.yellow('⚠️ 请在交互式终端运行 sillyspec setup'));
214
+ }
215
+ return;
216
+ }
217
+
218
+ // ── 交互式安装 ──
219
+
220
+ console.log('');
221
+ console.log(chalk.cyan('🔧 SillySpec Setup — 增强工具安装'));
222
+ console.log('');
223
+
224
+ if (availableTools.length === 0) {
225
+ console.log(chalk.dim(' 未检测到 AI 工具配置文件,MCP 工具将跳过。'));
226
+ console.log(chalk.dim(' 请先运行 sillyspec init 初始化项目。'));
227
+ console.log('');
228
+ }
229
+
230
+ console.log(' 选择要安装的工具(已安装的会跳过):');
231
+ console.log('');
232
+
233
+ // ── MCP 工具选择 ──
234
+
235
+ const installedSet = new Set();
236
+ for (const { path } of availableTools) {
237
+ const config = readMcpConfig(dir, path);
238
+ for (const mcp of MCP_TOOLS) {
239
+ if (hasMcpInstalled(config, mcp.id)) installedSet.add(mcp.id);
240
+ }
241
+ }
242
+
243
+ const mcpChoices = MCP_TOOLS.filter(m => !installedSet.has(m.id)).map(m => ({
244
+ name: `${m.name} — ${m.description}`,
245
+ value: `mcp:${m.id}`,
246
+ checked: false,
247
+ }));
248
+
249
+ // ── 全局工具选择 ──
250
+
251
+ const globalInstalled = new Set();
252
+ for (const tool of GLOBAL_TOOLS) {
253
+ try {
254
+ execSync(tool.checkCommand, { stdio: 'pipe', encoding: 'utf8' });
255
+ globalInstalled.add(tool.id);
256
+ } catch {}
257
+ }
258
+
259
+ const globalChoices = GLOBAL_TOOLS.filter(t => !globalInstalled.has(t.id)).map(t => ({
260
+ name: `${t.name} — ${t.description}(全局安装)`,
261
+ value: `global:${t.id}`,
262
+ checked: false,
263
+ }));
264
+
265
+ // ── 数据库 MCP 选择 ──
266
+
267
+ const dbInstalled = new Set();
268
+ for (const { path } of availableTools) {
269
+ const config = readMcpConfig(dir, path);
270
+ for (const db of DB_MCP_TOOLS) {
271
+ if (hasMcpInstalled(config, db.id)) dbInstalled.add(db.id);
272
+ }
273
+ }
274
+
275
+ const dbChoices = DB_MCP_TOOLS.filter(d => !dbInstalled.has(d.id)).map(d => ({
276
+ name: `${d.name} — ${d.description}(需要连接信息)`,
277
+ value: `db:${d.id}`,
278
+ checked: false,
279
+ }));
280
+
281
+ const allChoices = [
282
+ ...mcpChoices.length > 0 ? [{ name: chalk.bold('── MCP 工具(AI 能力增强)──'), value: '_mcp_header', disabled: true }] : [],
283
+ ...mcpChoices,
284
+ ...dbChoices.length > 0 ? [{ name: chalk.bold('── 数据库 MCP ──'), value: '_db_header', disabled: true }] : [],
285
+ ...dbChoices,
286
+ ...globalChoices.length > 0 ? [{ name: chalk.bold('── 全局工具 ──'), value: '_global_header', disabled: true }] : [],
287
+ ...globalChoices,
288
+ ];
289
+
290
+ if (allChoices.length === 0) {
291
+ console.log(chalk.green(' ✅ 所有推荐工具已安装!'));
292
+ return;
293
+ }
294
+
295
+ const selected = await checkbox({
296
+ message: '选择要安装的工具',
297
+ choices: allChoices,
298
+ });
299
+
300
+ if (selected.length === 0) {
301
+ console.log(chalk.dim(' 未选择任何工具,退出。'));
302
+ return;
303
+ }
304
+
305
+ const selectedMcp = MCP_TOOLS.filter(m => selected.includes(`mcp:${m.id}`));
306
+ const selectedDb = DB_MCP_TOOLS.filter(d => selected.includes(`db:${d.id}`));
307
+ const selectedGlobal = GLOBAL_TOOLS.filter(t => selected.includes(`global:${t.id}`));
308
+
309
+ // ── 收集数据库连接信息 ──
310
+
311
+ if (selectedDb.length > 0) {
312
+ console.log('');
313
+ console.log(chalk.yellow('📡 数据库连接配置'));
314
+ console.log('');
315
+
316
+ for (const db of selectedDb) {
317
+ console.log(chalk.cyan(` ${db.name}:`));
318
+ const dbWithEnv = { ...db, env: {} };
319
+ for (const [key, placeholder] of Object.entries(db.envTemplate)) {
320
+ const value = await input({
321
+ message: ` ${key}`,
322
+ default: placeholder,
323
+ });
324
+ dbWithEnv.env[key] = value;
325
+ }
326
+ // 用带 env 的版本替换
327
+ const idx = selectedDb.indexOf(db);
328
+ selectedDb[idx] = dbWithEnv;
329
+ }
330
+ }
331
+
332
+ // ── 安装 MCP 工具 ──
333
+
334
+ const allMcp = [...selectedMcp, ...selectedDb];
335
+
336
+ if (allMcp.length > 0) {
337
+ const toolChoices = availableTools.map(t => ({
338
+ name: t.tool,
339
+ value: t.key,
340
+ checked: true,
341
+ }));
342
+
343
+ const targetTools = await checkbox({
344
+ message: 'MCP 工具安装到哪些 AI 工具?',
345
+ choices: toolChoices,
346
+ });
347
+
348
+ const targets = availableTools.filter(t => targetTools.includes(t.key));
349
+
350
+ console.log('');
351
+ for (const { tool, path } of targets) {
352
+ const spinner = ora(`安装 MCP 到 ${tool}...`).start();
353
+ let config = readMcpConfig(dir, path) || { mcpServers: {} };
354
+ for (const mcp of allMcp) {
355
+ config = installMcp(config, mcp);
356
+ }
357
+ writeMcpConfig(dir, path, config);
358
+ spinner.succeed(`${tool} MCP 完成 (${allMcp.length} 个工具)`);
359
+ // 凭据提交风险提示(sec-c):.claude/mcp.json / .cursor/mcp.json 是项目共享文件、通常随 git
360
+ // 提交,而 DB MCP 的 env 含明文连接串/密码。检测 git 跟踪状态,已跟踪/未忽略时显式警告。
361
+ if (selectedDb.length > 0) {
362
+ try {
363
+ execFileSync('git', ['-C', dir, 'ls-files', '--error-unmatch', path], { stdio: 'pipe' });
364
+ console.warn(chalk.yellow(` ⚠️ ${path} 已被 git 跟踪——其中数据库连接串/密码会随提交进入仓库,建议加入 .gitignore 或改用环境变量`));
365
+ } catch {
366
+ // 未被跟踪:再查是否被 ignore;都不满足(未忽略未跟踪)时提示防止将来误提交
367
+ try {
368
+ execFileSync('git', ['-C', dir, 'check-ignore', '-q', path], { stdio: 'pipe' });
369
+ } catch {
370
+ console.warn(chalk.yellow(` ⚠️ ${path} 含数据库连接串/密码且未被 .gitignore 忽略——提交前请先加入 .gitignore 或改用环境变量`));
371
+ }
372
+ }
373
+ }
374
+ }
375
+ }
376
+
377
+ // ── 安装全局工具 ──
378
+
379
+ if (selectedGlobal.length > 0) {
380
+ console.log('');
381
+ for (const tool of selectedGlobal) {
382
+ const spinner = ora(`安装 ${tool.name}...`).start();
383
+ try {
384
+ execSync(tool.installCommand, { stdio: 'pipe', encoding: 'utf8', timeout: 120000 });
385
+ spinner.succeed(`${tool.name} 安装完成`);
386
+ } catch (err) {
387
+ spinner.fail(`${tool.name} 安装失败: ${err.message}`);
388
+ }
389
+ }
390
+ }
391
+
392
+ // ── 总结 ──
393
+
394
+ console.log('');
395
+ console.log(chalk.green(' ═══════════════════════════════════════'));
396
+ console.log(chalk.green(' ✅ 工具安装完成!'));
397
+ console.log(chalk.green(' ═══════════════════════════════════════'));
398
+ console.log('');
399
+ for (const mcp of selectedMcp) {
400
+ console.log(` 🔌 ${chalk.cyan(mcp.name)} — ${mcp.description}`);
401
+ console.log(chalk.dim(` ${mcp.url}`));
402
+ }
403
+ for (const db of selectedDb) {
404
+ console.log(` 🗄️ ${chalk.cyan(db.name)} — ${db.description}`);
405
+ console.log(chalk.dim(` ${db.url}`));
406
+ }
407
+ for (const g of selectedGlobal) {
408
+ console.log(` 🛠️ ${chalk.cyan(g.name)} — ${g.description}`);
409
+ console.log(chalk.dim(` ${g.url}`));
410
+ }
411
+ console.log('');
412
+ if (selectedMcp.length > 0) {
413
+ console.log(chalk.dim(' 重启你的 AI 工具以使 MCP 配置生效。'));
414
+ }
415
+ console.log('');
416
+ }