sillyspec 3.26.10 → 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 (97) 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 -83
  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 -693
  59. package/src/review-tier.js +77 -77
  60. package/src/run/command.js +1385 -1364
  61. package/src/run/complete-handlers.js +1443 -1399
  62. package/src/run/complete.js +225 -24
  63. package/src/run/gates.js +755 -755
  64. package/src/run/prompt.js +761 -761
  65. package/src/run/quick-audit.js +155 -146
  66. package/src/run/shared.js +1268 -1215
  67. package/src/run/stage.js +487 -479
  68. package/src/scan-postcheck.js +371 -371
  69. package/src/setup.js +416 -416
  70. package/src/sillyhub-mcp/client.js +529 -529
  71. package/src/spec-sync.js +323 -319
  72. package/src/stage-contract.js +897 -897
  73. package/src/stage-review.js +549 -549
  74. package/src/stages/archive.js +155 -155
  75. package/src/stages/brainstorm-auto.js +236 -236
  76. package/src/stages/brainstorm.js +596 -597
  77. package/src/stages/cmd-existence.js +145 -145
  78. package/src/stages/doctor.js +371 -371
  79. package/src/stages/execute.js +1003 -1003
  80. package/src/stages/explore.js +34 -34
  81. package/src/stages/index.js +29 -29
  82. package/src/stages/knowledge.js +498 -498
  83. package/src/stages/plan-postcheck.js +1302 -1302
  84. package/src/stages/plan.js +625 -623
  85. package/src/stages/quick.js +151 -151
  86. package/src/stages/scan.js +607 -607
  87. package/src/stages/status.js +64 -64
  88. package/src/stages/verify.js +264 -264
  89. package/src/sync.js +1223 -1135
  90. package/src/task-review.js +1019 -1019
  91. package/src/verify-postcheck.js +1186 -1186
  92. package/src/workflow.js +600 -600
  93. package/src/worktree-apply.js +1213 -1136
  94. package/src/worktree-deps.js +353 -353
  95. package/src/worktree.js +1481 -1481
  96. package/templates/workflows/archive-impact.yaml +79 -79
  97. 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
+ }