sillyspec 3.20.2 → 3.20.4
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.
- package/.claude/skills/sillyspec-archive/SKILL.md +21 -21
- package/.claude/skills/sillyspec-auto/SKILL.md +83 -83
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +44 -44
- package/.claude/skills/sillyspec-commit/SKILL.md +106 -106
- package/.claude/skills/sillyspec-continue/SKILL.md +45 -45
- package/.claude/skills/sillyspec-doctor/SKILL.md +31 -31
- package/.claude/skills/sillyspec-execute/SKILL.md +30 -30
- package/.claude/skills/sillyspec-explore/SKILL.md +109 -109
- package/.claude/skills/sillyspec-knowledge/SKILL.md +269 -269
- package/.claude/skills/sillyspec-plan/SKILL.md +21 -21
- package/.claude/skills/sillyspec-propose/SKILL.md +21 -21
- package/.claude/skills/sillyspec-quick/SKILL.md +21 -21
- package/.claude/skills/sillyspec-resume/SKILL.md +68 -68
- package/.claude/skills/sillyspec-scan/SKILL.md +21 -21
- package/.claude/skills/sillyspec-state/SKILL.md +54 -54
- package/.claude/skills/sillyspec-status/SKILL.md +21 -21
- package/.claude/skills/sillyspec-verify/SKILL.md +21 -21
- package/.claude/skills/sillyspec-workspace/SKILL.md +157 -157
- package/.husky/pre-push +13 -13
- package/CLAUDE.md +18 -18
- package/README.md +198 -188
- package/SKILL.md +90 -91
- package/bin/sillyspec.js +2 -2
- package/docs/brainstorm-plan-contract.md +64 -64
- package/docs/plan-execute-contract.md +123 -123
- package/docs/platform-scan-protocol.md +298 -298
- package/docs/revision-mode.md +115 -115
- package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +99 -99
- package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +218 -218
- package/docs/sillyspec/file-lifecycle/stage-artifacts.md +167 -167
- package/docs/sillyspec/file-lifecycle/storage-and-state.md +148 -148
- package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +211 -193
- package/docs/sillyspec/file-lifecycle.md +125 -125
- package/docs/workflow-contract-regression.md +106 -106
- package/docs/worktree-isolation.md +252 -252
- package/package.json +40 -40
- package/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -7446
- package/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -1
- package/packages/dashboard/dist/index.html +16 -16
- package/packages/dashboard/index.html +15 -15
- package/packages/dashboard/package-lock.json +2384 -2384
- package/packages/dashboard/package.json +25 -25
- package/packages/dashboard/server/executor.js +86 -86
- package/packages/dashboard/server/index.js +588 -588
- package/packages/dashboard/server/parser.js +526 -526
- package/packages/dashboard/server/watcher.js +344 -344
- package/packages/dashboard/src/App.vue +558 -558
- package/packages/dashboard/src/components/ActionBar.vue +93 -93
- package/packages/dashboard/src/components/CommandPalette.vue +96 -96
- package/packages/dashboard/src/components/DetailPanel.vue +137 -137
- package/packages/dashboard/src/components/LogStream.vue +65 -65
- package/packages/dashboard/src/components/PipelineStage.vue +95 -95
- package/packages/dashboard/src/components/PipelineView.vue +156 -156
- package/packages/dashboard/src/components/ProjectList.vue +210 -210
- package/packages/dashboard/src/components/StageBadge.vue +67 -67
- package/packages/dashboard/src/components/StepCard.vue +94 -94
- package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -48
- package/packages/dashboard/src/components/detail/GitDetail.vue +61 -61
- package/packages/dashboard/src/components/detail/TechDetail.vue +43 -43
- package/packages/dashboard/src/composables/useDashboard.js +170 -170
- package/packages/dashboard/src/composables/useKeyboard.js +119 -119
- package/packages/dashboard/src/composables/useWebSocket.js +129 -129
- package/packages/dashboard/src/main.js +8 -8
- package/packages/dashboard/src/style.css +132 -132
- package/packages/dashboard/vite.config.js +18 -18
- package/src/brainstorm-postcheck.js +158 -158
- package/src/change-list.js +52 -52
- package/src/change-risk-profile.js +352 -352
- package/src/classify-change.js +73 -73
- package/src/constants.js +70 -70
- package/src/contract-matrix.js +278 -278
- package/src/db.js +201 -201
- package/src/endpoint-extractor.js +315 -315
- package/src/hooks/claude-pre-tool-use.cjs +125 -125
- package/src/hooks/worktree-guard.js +653 -653
- package/src/index.js +922 -900
- package/src/init.js +431 -431
- package/src/knowledge-match.js +130 -130
- package/src/migrate.js +117 -117
- package/src/modules.js +482 -482
- package/src/progress.js +1734 -1734
- package/src/run.js +3465 -3358
- package/src/scan-postcheck.js +387 -383
- package/src/setup.js +398 -398
- package/src/stage-contract.js +700 -700
- package/src/stages/archive.js +160 -160
- package/src/stages/brainstorm-auto.js +229 -229
- package/src/stages/brainstorm.js +645 -645
- package/src/stages/doctor.js +365 -365
- package/src/stages/execute.js +625 -625
- package/src/stages/explore.js +34 -34
- package/src/stages/index.js +29 -29
- package/src/stages/knowledge.js +498 -498
- package/src/stages/plan-postcheck.js +511 -513
- package/src/stages/plan.js +582 -582
- package/src/stages/propose.js +174 -174
- package/src/stages/quick.js +82 -82
- package/src/stages/scan.js +558 -558
- package/src/stages/status.js +65 -65
- package/src/stages/verify.js +322 -322
- package/src/sync.js +497 -497
- package/src/task-review.js +346 -346
- package/src/workflow.js +785 -785
- package/src/worktree-apply.js +549 -549
- package/src/worktree-deps.js +185 -0
- package/src/worktree.js +982 -932
- package/templates/workflows/archive-impact.yaml +79 -79
- package/templates/workflows/scan-docs.yaml +132 -132
- package/test/brainstorm-plan-contract.test.mjs +273 -273
- package/test/check-syntax.mjs +26 -26
- package/test/contract-artifacts.test.mjs +323 -323
- package/test/decision-supersede.test.mjs +277 -277
- package/test/knowledge-match.test.mjs +231 -231
- package/test/plan-execute-contract.test.mjs +330 -330
- package/test/plan-optimization.test.mjs +572 -572
- package/test/platform-artifacts.test.mjs +166 -166
- package/test/platform-failure-samples.test.mjs +199 -199
- package/test/platform-recovery-chain.test.mjs +167 -167
- package/test/platform-recovery.test.mjs +136 -136
- package/test/platform-scan-p0.test.mjs +168 -168
- package/test/revision-v1.test.mjs +1145 -1145
- package/test/run-scan-project-parse.test.mjs +200 -200
- package/test/run-tests.mjs +48 -48
- package/test/scan-knowledge.test.mjs +175 -175
- package/test/scan-paths.test.mjs +68 -68
- package/test/scan-postcheck.test.mjs +197 -197
- package/test/spec-dir.test.mjs +206 -206
- package/test/stage-contract.test.mjs +299 -299
- package/test/stage-definitions.test.mjs +39 -39
- package/test/wait-gates.test.mjs +496 -496
- package/test/worktree-deps-provision.test.mjs +148 -0
- package/test/worktree-guard.test.mjs +71 -71
- package/test/worktree-native-overlay.test.mjs +188 -188
package/src/setup.js
CHANGED
|
@@ -1,398 +1,398 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
2
|
-
import { join, dirname } from 'path';
|
|
3
|
-
import { execSync } 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
|
-
args: ['-y', '@upstash/context7-mcp@latest'],
|
|
17
|
-
url: 'https://github.com/upstash/context7-mcp',
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
id: 'chrome-devtools',
|
|
21
|
-
name: 'Chrome DevTools MCP',
|
|
22
|
-
description: '浏览器自动化,支持 E2E 测试(需 Chrome 已运行)',
|
|
23
|
-
command: 'npx',
|
|
24
|
-
args: ['chrome-devtools-mcp@latest'],
|
|
25
|
-
url: 'https://github.com/ChromeDevTools/chrome-devtools-mcp',
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
id: 'agent-browser',
|
|
29
|
-
name: 'Agent Browser (Vercel)',
|
|
30
|
-
description: 'Rust 原生浏览器 CLI,token 消耗极低,50+ 命令覆盖导航/表单/截图/网络',
|
|
31
|
-
command: 'npx',
|
|
32
|
-
args: ['@anthropic-ai/agent-browser@latest'],
|
|
33
|
-
url: 'https://github.com/vercel-labs/agent-browser',
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
id: 'pinchtab',
|
|
37
|
-
name: 'PinchTab',
|
|
38
|
-
description: '12MB Go 二进制,零依赖,accessibility tree 极省 token,有 MCP 支持',
|
|
39
|
-
command: 'npx',
|
|
40
|
-
args: ['pinchtab-mcp@latest'],
|
|
41
|
-
url: 'https://github.com/pinchtab/pinchtab',
|
|
42
|
-
},
|
|
43
|
-
];
|
|
44
|
-
|
|
45
|
-
// ── 数据库 MCP 定义(需要连接信息)──
|
|
46
|
-
|
|
47
|
-
const DB_MCP_TOOLS = [
|
|
48
|
-
{
|
|
49
|
-
id: 'postgres',
|
|
50
|
-
name: 'PostgreSQL',
|
|
51
|
-
description: '只读访问 PostgreSQL 数据库',
|
|
52
|
-
command: 'npx',
|
|
53
|
-
args: ['-y', '@modelcontextprotocol/server-postgres'],
|
|
54
|
-
envTemplate: { POSTGRES_CONNECTION_STRING: 'postgresql://user:password@localhost:5432/dbname' },
|
|
55
|
-
url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/postgres',
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
id: 'sqlite',
|
|
59
|
-
name: 'SQLite',
|
|
60
|
-
description: '访问 SQLite 数据库文件',
|
|
61
|
-
command: 'npx',
|
|
62
|
-
args: ['-y', '@modelcontextprotocol/server-sqlite'],
|
|
63
|
-
envTemplate: { SQLITE_DB_PATH: './data.db' },
|
|
64
|
-
url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/sqlite',
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
id: 'mysql',
|
|
68
|
-
name: 'MySQL / MariaDB',
|
|
69
|
-
description: '访问 MySQL / MariaDB 数据库',
|
|
70
|
-
command: 'npx',
|
|
71
|
-
args: ['-y', '@nicobailon/mysql-mcp-server'],
|
|
72
|
-
envTemplate: { MYSQL_HOST: 'localhost', MYSQL_PORT: '3306', MYSQL_USER: 'root', MYSQL_PASSWORD: '', MYSQL_DATABASE: '' },
|
|
73
|
-
url: 'https://github.com/modelcontextprotocol/servers',
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
id: 'redis',
|
|
77
|
-
name: 'Redis',
|
|
78
|
-
description: '访问 Redis 数据库,查看缓存和键值数据',
|
|
79
|
-
command: 'npx',
|
|
80
|
-
args: ['-y', '@modelcontextprotocol/server-redis'],
|
|
81
|
-
envTemplate: { REDIS_URL: 'redis://localhost:6379' },
|
|
82
|
-
url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/redis',
|
|
83
|
-
},
|
|
84
|
-
];
|
|
85
|
-
|
|
86
|
-
// ── 全局工具定义 ──
|
|
87
|
-
|
|
88
|
-
const GLOBAL_TOOLS = [
|
|
89
|
-
{
|
|
90
|
-
id: 'playwright',
|
|
91
|
-
name: 'Playwright',
|
|
92
|
-
description: 'E2E 测试框架,支持浏览器自动化测试',
|
|
93
|
-
checkCommand: 'npx playwright --version',
|
|
94
|
-
installCommand: 'npm install -g @playwright/test && npx playwright install chromium',
|
|
95
|
-
url: 'https://playwright.dev',
|
|
96
|
-
},
|
|
97
|
-
];
|
|
98
|
-
|
|
99
|
-
// ── MCP 配置文件路径 ──
|
|
100
|
-
|
|
101
|
-
const MCP_CONFIG_PATHS = [
|
|
102
|
-
{ tool: 'Claude Code', path: '.claude/mcp.json', key: 'claude' },
|
|
103
|
-
{ tool: 'Cursor', path: '.cursor/mcp.json', key: 'cursor' },
|
|
104
|
-
];
|
|
105
|
-
|
|
106
|
-
// ── 工具函数 ──
|
|
107
|
-
|
|
108
|
-
function readMcpConfig(dir, configPath) {
|
|
109
|
-
const fullPath = join(dir, configPath);
|
|
110
|
-
if (!existsSync(fullPath)) return null;
|
|
111
|
-
try {
|
|
112
|
-
return JSON.parse(readFileSync(fullPath, 'utf8'));
|
|
113
|
-
} catch {
|
|
114
|
-
return null;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function writeMcpConfig(dir, configPath, config) {
|
|
119
|
-
const fullPath = join(dir, configPath);
|
|
120
|
-
mkdirSync(dirname(fullPath), { recursive: true });
|
|
121
|
-
writeFileSync(fullPath, JSON.stringify(config, null, 2) + '\n');
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function installMcp(config, mcpTool) {
|
|
125
|
-
if (!config.mcpServers) config.mcpServers = {};
|
|
126
|
-
const entry = {
|
|
127
|
-
command: mcpTool.command,
|
|
128
|
-
args: mcpTool.args,
|
|
129
|
-
};
|
|
130
|
-
if (mcpTool.env) entry.env = mcpTool.env;
|
|
131
|
-
config.mcpServers[mcpTool.id] = entry;
|
|
132
|
-
return config;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function hasMcpInstalled(config, mcpId) {
|
|
136
|
-
return config?.mcpServers?.[mcpId] != null;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// ── 命令实现 ──
|
|
140
|
-
|
|
141
|
-
export async function cmdSetup(dir, options = {}) {
|
|
142
|
-
const { json } = options;
|
|
143
|
-
|
|
144
|
-
// 检查哪些 AI 工具有配置文件
|
|
145
|
-
const availableTools = MCP_CONFIG_PATHS.filter(({ path }) => {
|
|
146
|
-
return existsSync(join(dir, path));
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
// --list 模式:只查看状态(不依赖 AI 工具配置)
|
|
150
|
-
if (options.list) {
|
|
151
|
-
const results = {};
|
|
152
|
-
|
|
153
|
-
// MCP 状态
|
|
154
|
-
for (const { tool, path } of availableTools) {
|
|
155
|
-
const config = readMcpConfig(dir, path);
|
|
156
|
-
const mcpStatus = {};
|
|
157
|
-
for (const mcp of MCP_TOOLS) {
|
|
158
|
-
mcpStatus[mcp.id] = hasMcpInstalled(config, mcp.id);
|
|
159
|
-
}
|
|
160
|
-
for (const db of DB_MCP_TOOLS) {
|
|
161
|
-
mcpStatus[db.id] = hasMcpInstalled(config, db.id);
|
|
162
|
-
}
|
|
163
|
-
results[tool] = { configPath: path, mcp: mcpStatus };
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// 全局工具状态
|
|
167
|
-
results['全局工具'] = {};
|
|
168
|
-
for (const g of GLOBAL_TOOLS) {
|
|
169
|
-
try {
|
|
170
|
-
execSync(g.checkCommand, { stdio: 'pipe', encoding: 'utf8' });
|
|
171
|
-
results['全局工具'][g.id] = true;
|
|
172
|
-
} catch {
|
|
173
|
-
results['全局工具'][g.id] = false;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (json) {
|
|
178
|
-
console.log(JSON.stringify(results, null, 2));
|
|
179
|
-
} else {
|
|
180
|
-
console.log('');
|
|
181
|
-
for (const [tool, data] of Object.entries(results)) {
|
|
182
|
-
if (data.mcp) {
|
|
183
|
-
console.log(chalk.bold(`📋 ${tool} (${data.configPath})`));
|
|
184
|
-
for (const mcp of MCP_TOOLS) {
|
|
185
|
-
const status = data.mcp[mcp.id] ? chalk.green('✅') : chalk.gray('⬜');
|
|
186
|
-
console.log(` ${status} ${mcp.name} — ${mcp.description}`);
|
|
187
|
-
}
|
|
188
|
-
for (const db of DB_MCP_TOOLS) {
|
|
189
|
-
const status = data.mcp[db.id] ? chalk.green('✅') : chalk.gray('⬜');
|
|
190
|
-
console.log(` ${status} ${db.name} — ${db.description}`);
|
|
191
|
-
}
|
|
192
|
-
} else {
|
|
193
|
-
console.log(chalk.bold(`🛠️ ${tool}`));
|
|
194
|
-
for (const g of GLOBAL_TOOLS) {
|
|
195
|
-
const status = data[g.id] ? chalk.green('✅') : chalk.gray('⬜');
|
|
196
|
-
console.log(` ${status} ${g.name} — ${g.description}`);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
console.log('');
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// 非交互模式(或没有 AI 工具但有全局工具要装)
|
|
206
|
-
if (!process.stdin.isTTY) {
|
|
207
|
-
if (json) {
|
|
208
|
-
console.log(JSON.stringify({ message: '交互模式需要 TTY,请运行 sillyspec setup' }));
|
|
209
|
-
} else {
|
|
210
|
-
console.log(chalk.yellow('⚠️ 请在交互式终端运行 sillyspec setup'));
|
|
211
|
-
}
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// ── 交互式安装 ──
|
|
216
|
-
|
|
217
|
-
console.log('');
|
|
218
|
-
console.log(chalk.cyan('🔧 SillySpec Setup — 增强工具安装'));
|
|
219
|
-
console.log('');
|
|
220
|
-
|
|
221
|
-
if (availableTools.length === 0) {
|
|
222
|
-
console.log(chalk.dim(' 未检测到 AI 工具配置文件,MCP 工具将跳过。'));
|
|
223
|
-
console.log(chalk.dim(' 请先运行 sillyspec init 初始化项目。'));
|
|
224
|
-
console.log('');
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
console.log(' 选择要安装的工具(已安装的会跳过):');
|
|
228
|
-
console.log('');
|
|
229
|
-
|
|
230
|
-
// ── MCP 工具选择 ──
|
|
231
|
-
|
|
232
|
-
const installedSet = new Set();
|
|
233
|
-
for (const { path } of availableTools) {
|
|
234
|
-
const config = readMcpConfig(dir, path);
|
|
235
|
-
for (const mcp of MCP_TOOLS) {
|
|
236
|
-
if (hasMcpInstalled(config, mcp.id)) installedSet.add(mcp.id);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const mcpChoices = MCP_TOOLS.filter(m => !installedSet.has(m.id)).map(m => ({
|
|
241
|
-
name: `${m.name} — ${m.description}`,
|
|
242
|
-
value: `mcp:${m.id}`,
|
|
243
|
-
checked: false,
|
|
244
|
-
}));
|
|
245
|
-
|
|
246
|
-
// ── 全局工具选择 ──
|
|
247
|
-
|
|
248
|
-
const globalInstalled = new Set();
|
|
249
|
-
for (const tool of GLOBAL_TOOLS) {
|
|
250
|
-
try {
|
|
251
|
-
execSync(tool.checkCommand, { stdio: 'pipe', encoding: 'utf8' });
|
|
252
|
-
globalInstalled.add(tool.id);
|
|
253
|
-
} catch {}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const globalChoices = GLOBAL_TOOLS.filter(t => !globalInstalled.has(t.id)).map(t => ({
|
|
257
|
-
name: `${t.name} — ${t.description}(全局安装)`,
|
|
258
|
-
value: `global:${t.id}`,
|
|
259
|
-
checked: false,
|
|
260
|
-
}));
|
|
261
|
-
|
|
262
|
-
// ── 数据库 MCP 选择 ──
|
|
263
|
-
|
|
264
|
-
const dbInstalled = new Set();
|
|
265
|
-
for (const { path } of availableTools) {
|
|
266
|
-
const config = readMcpConfig(dir, path);
|
|
267
|
-
for (const db of DB_MCP_TOOLS) {
|
|
268
|
-
if (hasMcpInstalled(config, db.id)) dbInstalled.add(db.id);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const dbChoices = DB_MCP_TOOLS.filter(d => !dbInstalled.has(d.id)).map(d => ({
|
|
273
|
-
name: `${d.name} — ${d.description}(需要连接信息)`,
|
|
274
|
-
value: `db:${d.id}`,
|
|
275
|
-
checked: false,
|
|
276
|
-
}));
|
|
277
|
-
|
|
278
|
-
const allChoices = [
|
|
279
|
-
...mcpChoices.length > 0 ? [{ name: chalk.bold('── MCP 工具(AI 能力增强)──'), value: '_mcp_header', disabled: true }] : [],
|
|
280
|
-
...mcpChoices,
|
|
281
|
-
...dbChoices.length > 0 ? [{ name: chalk.bold('── 数据库 MCP ──'), value: '_db_header', disabled: true }] : [],
|
|
282
|
-
...dbChoices,
|
|
283
|
-
...globalChoices.length > 0 ? [{ name: chalk.bold('── 全局工具 ──'), value: '_global_header', disabled: true }] : [],
|
|
284
|
-
...globalChoices,
|
|
285
|
-
];
|
|
286
|
-
|
|
287
|
-
if (allChoices.length === 0) {
|
|
288
|
-
console.log(chalk.green(' ✅ 所有推荐工具已安装!'));
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const selected = await checkbox({
|
|
293
|
-
message: '选择要安装的工具',
|
|
294
|
-
choices: allChoices,
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
if (selected.length === 0) {
|
|
298
|
-
console.log(chalk.dim(' 未选择任何工具,退出。'));
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const selectedMcp = MCP_TOOLS.filter(m => selected.includes(`mcp:${m.id}`));
|
|
303
|
-
const selectedDb = DB_MCP_TOOLS.filter(d => selected.includes(`db:${d.id}`));
|
|
304
|
-
const selectedGlobal = GLOBAL_TOOLS.filter(t => selected.includes(`global:${t.id}`));
|
|
305
|
-
|
|
306
|
-
// ── 收集数据库连接信息 ──
|
|
307
|
-
|
|
308
|
-
if (selectedDb.length > 0) {
|
|
309
|
-
console.log('');
|
|
310
|
-
console.log(chalk.yellow('📡 数据库连接配置'));
|
|
311
|
-
console.log('');
|
|
312
|
-
|
|
313
|
-
for (const db of selectedDb) {
|
|
314
|
-
console.log(chalk.cyan(` ${db.name}:`));
|
|
315
|
-
const dbWithEnv = { ...db, env: {} };
|
|
316
|
-
for (const [key, placeholder] of Object.entries(db.envTemplate)) {
|
|
317
|
-
const value = await input({
|
|
318
|
-
message: ` ${key}`,
|
|
319
|
-
default: placeholder,
|
|
320
|
-
});
|
|
321
|
-
dbWithEnv.env[key] = value;
|
|
322
|
-
}
|
|
323
|
-
// 用带 env 的版本替换
|
|
324
|
-
const idx = selectedDb.indexOf(db);
|
|
325
|
-
selectedDb[idx] = dbWithEnv;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// ── 安装 MCP 工具 ──
|
|
330
|
-
|
|
331
|
-
const allMcp = [...selectedMcp, ...selectedDb];
|
|
332
|
-
|
|
333
|
-
if (allMcp.length > 0) {
|
|
334
|
-
const toolChoices = availableTools.map(t => ({
|
|
335
|
-
name: t.tool,
|
|
336
|
-
value: t.key,
|
|
337
|
-
checked: true,
|
|
338
|
-
}));
|
|
339
|
-
|
|
340
|
-
const targetTools = await checkbox({
|
|
341
|
-
message: 'MCP 工具安装到哪些 AI 工具?',
|
|
342
|
-
choices: toolChoices,
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
const targets = availableTools.filter(t => targetTools.includes(t.key));
|
|
346
|
-
|
|
347
|
-
console.log('');
|
|
348
|
-
for (const { tool, path } of targets) {
|
|
349
|
-
const spinner = ora(`安装 MCP 到 ${tool}...`).start();
|
|
350
|
-
let config = readMcpConfig(dir, path) || { mcpServers: {} };
|
|
351
|
-
for (const mcp of allMcp) {
|
|
352
|
-
config = installMcp(config, mcp);
|
|
353
|
-
}
|
|
354
|
-
writeMcpConfig(dir, path, config);
|
|
355
|
-
spinner.succeed(`${tool} MCP 完成 (${allMcp.length} 个工具)`);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// ── 安装全局工具 ──
|
|
360
|
-
|
|
361
|
-
if (selectedGlobal.length > 0) {
|
|
362
|
-
console.log('');
|
|
363
|
-
for (const tool of selectedGlobal) {
|
|
364
|
-
const spinner = ora(`安装 ${tool.name}...`).start();
|
|
365
|
-
try {
|
|
366
|
-
execSync(tool.installCommand, { stdio: 'pipe', encoding: 'utf8', timeout: 120000 });
|
|
367
|
-
spinner.succeed(`${tool.name} 安装完成`);
|
|
368
|
-
} catch (err) {
|
|
369
|
-
spinner.fail(`${tool.name} 安装失败: ${err.message}`);
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// ── 总结 ──
|
|
375
|
-
|
|
376
|
-
console.log('');
|
|
377
|
-
console.log(chalk.green(' ═══════════════════════════════════════'));
|
|
378
|
-
console.log(chalk.green(' ✅ 工具安装完成!'));
|
|
379
|
-
console.log(chalk.green(' ═══════════════════════════════════════'));
|
|
380
|
-
console.log('');
|
|
381
|
-
for (const mcp of selectedMcp) {
|
|
382
|
-
console.log(` 🔌 ${chalk.cyan(mcp.name)} — ${mcp.description}`);
|
|
383
|
-
console.log(chalk.dim(` ${mcp.url}`));
|
|
384
|
-
}
|
|
385
|
-
for (const db of selectedDb) {
|
|
386
|
-
console.log(` 🗄️ ${chalk.cyan(db.name)} — ${db.description}`);
|
|
387
|
-
console.log(chalk.dim(` ${db.url}`));
|
|
388
|
-
}
|
|
389
|
-
for (const g of selectedGlobal) {
|
|
390
|
-
console.log(` 🛠️ ${chalk.cyan(g.name)} — ${g.description}`);
|
|
391
|
-
console.log(chalk.dim(` ${g.url}`));
|
|
392
|
-
}
|
|
393
|
-
console.log('');
|
|
394
|
-
if (selectedMcp.length > 0) {
|
|
395
|
-
console.log(chalk.dim(' 重启你的 AI 工具以使 MCP 配置生效。'));
|
|
396
|
-
}
|
|
397
|
-
console.log('');
|
|
398
|
-
}
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { execSync } 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
|
+
args: ['-y', '@upstash/context7-mcp@latest'],
|
|
17
|
+
url: 'https://github.com/upstash/context7-mcp',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: 'chrome-devtools',
|
|
21
|
+
name: 'Chrome DevTools MCP',
|
|
22
|
+
description: '浏览器自动化,支持 E2E 测试(需 Chrome 已运行)',
|
|
23
|
+
command: 'npx',
|
|
24
|
+
args: ['chrome-devtools-mcp@latest'],
|
|
25
|
+
url: 'https://github.com/ChromeDevTools/chrome-devtools-mcp',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 'agent-browser',
|
|
29
|
+
name: 'Agent Browser (Vercel)',
|
|
30
|
+
description: 'Rust 原生浏览器 CLI,token 消耗极低,50+ 命令覆盖导航/表单/截图/网络',
|
|
31
|
+
command: 'npx',
|
|
32
|
+
args: ['@anthropic-ai/agent-browser@latest'],
|
|
33
|
+
url: 'https://github.com/vercel-labs/agent-browser',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'pinchtab',
|
|
37
|
+
name: 'PinchTab',
|
|
38
|
+
description: '12MB Go 二进制,零依赖,accessibility tree 极省 token,有 MCP 支持',
|
|
39
|
+
command: 'npx',
|
|
40
|
+
args: ['pinchtab-mcp@latest'],
|
|
41
|
+
url: 'https://github.com/pinchtab/pinchtab',
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
// ── 数据库 MCP 定义(需要连接信息)──
|
|
46
|
+
|
|
47
|
+
const DB_MCP_TOOLS = [
|
|
48
|
+
{
|
|
49
|
+
id: 'postgres',
|
|
50
|
+
name: 'PostgreSQL',
|
|
51
|
+
description: '只读访问 PostgreSQL 数据库',
|
|
52
|
+
command: 'npx',
|
|
53
|
+
args: ['-y', '@modelcontextprotocol/server-postgres'],
|
|
54
|
+
envTemplate: { POSTGRES_CONNECTION_STRING: 'postgresql://user:password@localhost:5432/dbname' },
|
|
55
|
+
url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/postgres',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'sqlite',
|
|
59
|
+
name: 'SQLite',
|
|
60
|
+
description: '访问 SQLite 数据库文件',
|
|
61
|
+
command: 'npx',
|
|
62
|
+
args: ['-y', '@modelcontextprotocol/server-sqlite'],
|
|
63
|
+
envTemplate: { SQLITE_DB_PATH: './data.db' },
|
|
64
|
+
url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/sqlite',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'mysql',
|
|
68
|
+
name: 'MySQL / MariaDB',
|
|
69
|
+
description: '访问 MySQL / MariaDB 数据库',
|
|
70
|
+
command: 'npx',
|
|
71
|
+
args: ['-y', '@nicobailon/mysql-mcp-server'],
|
|
72
|
+
envTemplate: { MYSQL_HOST: 'localhost', MYSQL_PORT: '3306', MYSQL_USER: 'root', MYSQL_PASSWORD: '', MYSQL_DATABASE: '' },
|
|
73
|
+
url: 'https://github.com/modelcontextprotocol/servers',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: 'redis',
|
|
77
|
+
name: 'Redis',
|
|
78
|
+
description: '访问 Redis 数据库,查看缓存和键值数据',
|
|
79
|
+
command: 'npx',
|
|
80
|
+
args: ['-y', '@modelcontextprotocol/server-redis'],
|
|
81
|
+
envTemplate: { REDIS_URL: 'redis://localhost:6379' },
|
|
82
|
+
url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/redis',
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
// ── 全局工具定义 ──
|
|
87
|
+
|
|
88
|
+
const GLOBAL_TOOLS = [
|
|
89
|
+
{
|
|
90
|
+
id: 'playwright',
|
|
91
|
+
name: 'Playwright',
|
|
92
|
+
description: 'E2E 测试框架,支持浏览器自动化测试',
|
|
93
|
+
checkCommand: 'npx playwright --version',
|
|
94
|
+
installCommand: 'npm install -g @playwright/test && npx playwright install chromium',
|
|
95
|
+
url: 'https://playwright.dev',
|
|
96
|
+
},
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
// ── MCP 配置文件路径 ──
|
|
100
|
+
|
|
101
|
+
const MCP_CONFIG_PATHS = [
|
|
102
|
+
{ tool: 'Claude Code', path: '.claude/mcp.json', key: 'claude' },
|
|
103
|
+
{ tool: 'Cursor', path: '.cursor/mcp.json', key: 'cursor' },
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
// ── 工具函数 ──
|
|
107
|
+
|
|
108
|
+
function readMcpConfig(dir, configPath) {
|
|
109
|
+
const fullPath = join(dir, configPath);
|
|
110
|
+
if (!existsSync(fullPath)) return null;
|
|
111
|
+
try {
|
|
112
|
+
return JSON.parse(readFileSync(fullPath, 'utf8'));
|
|
113
|
+
} catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function writeMcpConfig(dir, configPath, config) {
|
|
119
|
+
const fullPath = join(dir, configPath);
|
|
120
|
+
mkdirSync(dirname(fullPath), { recursive: true });
|
|
121
|
+
writeFileSync(fullPath, JSON.stringify(config, null, 2) + '\n');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function installMcp(config, mcpTool) {
|
|
125
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
126
|
+
const entry = {
|
|
127
|
+
command: mcpTool.command,
|
|
128
|
+
args: mcpTool.args,
|
|
129
|
+
};
|
|
130
|
+
if (mcpTool.env) entry.env = mcpTool.env;
|
|
131
|
+
config.mcpServers[mcpTool.id] = entry;
|
|
132
|
+
return config;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function hasMcpInstalled(config, mcpId) {
|
|
136
|
+
return config?.mcpServers?.[mcpId] != null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ── 命令实现 ──
|
|
140
|
+
|
|
141
|
+
export async function cmdSetup(dir, options = {}) {
|
|
142
|
+
const { json } = options;
|
|
143
|
+
|
|
144
|
+
// 检查哪些 AI 工具有配置文件
|
|
145
|
+
const availableTools = MCP_CONFIG_PATHS.filter(({ path }) => {
|
|
146
|
+
return existsSync(join(dir, path));
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// --list 模式:只查看状态(不依赖 AI 工具配置)
|
|
150
|
+
if (options.list) {
|
|
151
|
+
const results = {};
|
|
152
|
+
|
|
153
|
+
// MCP 状态
|
|
154
|
+
for (const { tool, path } of availableTools) {
|
|
155
|
+
const config = readMcpConfig(dir, path);
|
|
156
|
+
const mcpStatus = {};
|
|
157
|
+
for (const mcp of MCP_TOOLS) {
|
|
158
|
+
mcpStatus[mcp.id] = hasMcpInstalled(config, mcp.id);
|
|
159
|
+
}
|
|
160
|
+
for (const db of DB_MCP_TOOLS) {
|
|
161
|
+
mcpStatus[db.id] = hasMcpInstalled(config, db.id);
|
|
162
|
+
}
|
|
163
|
+
results[tool] = { configPath: path, mcp: mcpStatus };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 全局工具状态
|
|
167
|
+
results['全局工具'] = {};
|
|
168
|
+
for (const g of GLOBAL_TOOLS) {
|
|
169
|
+
try {
|
|
170
|
+
execSync(g.checkCommand, { stdio: 'pipe', encoding: 'utf8' });
|
|
171
|
+
results['全局工具'][g.id] = true;
|
|
172
|
+
} catch {
|
|
173
|
+
results['全局工具'][g.id] = false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (json) {
|
|
178
|
+
console.log(JSON.stringify(results, null, 2));
|
|
179
|
+
} else {
|
|
180
|
+
console.log('');
|
|
181
|
+
for (const [tool, data] of Object.entries(results)) {
|
|
182
|
+
if (data.mcp) {
|
|
183
|
+
console.log(chalk.bold(`📋 ${tool} (${data.configPath})`));
|
|
184
|
+
for (const mcp of MCP_TOOLS) {
|
|
185
|
+
const status = data.mcp[mcp.id] ? chalk.green('✅') : chalk.gray('⬜');
|
|
186
|
+
console.log(` ${status} ${mcp.name} — ${mcp.description}`);
|
|
187
|
+
}
|
|
188
|
+
for (const db of DB_MCP_TOOLS) {
|
|
189
|
+
const status = data.mcp[db.id] ? chalk.green('✅') : chalk.gray('⬜');
|
|
190
|
+
console.log(` ${status} ${db.name} — ${db.description}`);
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
console.log(chalk.bold(`🛠️ ${tool}`));
|
|
194
|
+
for (const g of GLOBAL_TOOLS) {
|
|
195
|
+
const status = data[g.id] ? chalk.green('✅') : chalk.gray('⬜');
|
|
196
|
+
console.log(` ${status} ${g.name} — ${g.description}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
console.log('');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// 非交互模式(或没有 AI 工具但有全局工具要装)
|
|
206
|
+
if (!process.stdin.isTTY) {
|
|
207
|
+
if (json) {
|
|
208
|
+
console.log(JSON.stringify({ message: '交互模式需要 TTY,请运行 sillyspec setup' }));
|
|
209
|
+
} else {
|
|
210
|
+
console.log(chalk.yellow('⚠️ 请在交互式终端运行 sillyspec setup'));
|
|
211
|
+
}
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ── 交互式安装 ──
|
|
216
|
+
|
|
217
|
+
console.log('');
|
|
218
|
+
console.log(chalk.cyan('🔧 SillySpec Setup — 增强工具安装'));
|
|
219
|
+
console.log('');
|
|
220
|
+
|
|
221
|
+
if (availableTools.length === 0) {
|
|
222
|
+
console.log(chalk.dim(' 未检测到 AI 工具配置文件,MCP 工具将跳过。'));
|
|
223
|
+
console.log(chalk.dim(' 请先运行 sillyspec init 初始化项目。'));
|
|
224
|
+
console.log('');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
console.log(' 选择要安装的工具(已安装的会跳过):');
|
|
228
|
+
console.log('');
|
|
229
|
+
|
|
230
|
+
// ── MCP 工具选择 ──
|
|
231
|
+
|
|
232
|
+
const installedSet = new Set();
|
|
233
|
+
for (const { path } of availableTools) {
|
|
234
|
+
const config = readMcpConfig(dir, path);
|
|
235
|
+
for (const mcp of MCP_TOOLS) {
|
|
236
|
+
if (hasMcpInstalled(config, mcp.id)) installedSet.add(mcp.id);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const mcpChoices = MCP_TOOLS.filter(m => !installedSet.has(m.id)).map(m => ({
|
|
241
|
+
name: `${m.name} — ${m.description}`,
|
|
242
|
+
value: `mcp:${m.id}`,
|
|
243
|
+
checked: false,
|
|
244
|
+
}));
|
|
245
|
+
|
|
246
|
+
// ── 全局工具选择 ──
|
|
247
|
+
|
|
248
|
+
const globalInstalled = new Set();
|
|
249
|
+
for (const tool of GLOBAL_TOOLS) {
|
|
250
|
+
try {
|
|
251
|
+
execSync(tool.checkCommand, { stdio: 'pipe', encoding: 'utf8' });
|
|
252
|
+
globalInstalled.add(tool.id);
|
|
253
|
+
} catch {}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const globalChoices = GLOBAL_TOOLS.filter(t => !globalInstalled.has(t.id)).map(t => ({
|
|
257
|
+
name: `${t.name} — ${t.description}(全局安装)`,
|
|
258
|
+
value: `global:${t.id}`,
|
|
259
|
+
checked: false,
|
|
260
|
+
}));
|
|
261
|
+
|
|
262
|
+
// ── 数据库 MCP 选择 ──
|
|
263
|
+
|
|
264
|
+
const dbInstalled = new Set();
|
|
265
|
+
for (const { path } of availableTools) {
|
|
266
|
+
const config = readMcpConfig(dir, path);
|
|
267
|
+
for (const db of DB_MCP_TOOLS) {
|
|
268
|
+
if (hasMcpInstalled(config, db.id)) dbInstalled.add(db.id);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const dbChoices = DB_MCP_TOOLS.filter(d => !dbInstalled.has(d.id)).map(d => ({
|
|
273
|
+
name: `${d.name} — ${d.description}(需要连接信息)`,
|
|
274
|
+
value: `db:${d.id}`,
|
|
275
|
+
checked: false,
|
|
276
|
+
}));
|
|
277
|
+
|
|
278
|
+
const allChoices = [
|
|
279
|
+
...mcpChoices.length > 0 ? [{ name: chalk.bold('── MCP 工具(AI 能力增强)──'), value: '_mcp_header', disabled: true }] : [],
|
|
280
|
+
...mcpChoices,
|
|
281
|
+
...dbChoices.length > 0 ? [{ name: chalk.bold('── 数据库 MCP ──'), value: '_db_header', disabled: true }] : [],
|
|
282
|
+
...dbChoices,
|
|
283
|
+
...globalChoices.length > 0 ? [{ name: chalk.bold('── 全局工具 ──'), value: '_global_header', disabled: true }] : [],
|
|
284
|
+
...globalChoices,
|
|
285
|
+
];
|
|
286
|
+
|
|
287
|
+
if (allChoices.length === 0) {
|
|
288
|
+
console.log(chalk.green(' ✅ 所有推荐工具已安装!'));
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const selected = await checkbox({
|
|
293
|
+
message: '选择要安装的工具',
|
|
294
|
+
choices: allChoices,
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
if (selected.length === 0) {
|
|
298
|
+
console.log(chalk.dim(' 未选择任何工具,退出。'));
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const selectedMcp = MCP_TOOLS.filter(m => selected.includes(`mcp:${m.id}`));
|
|
303
|
+
const selectedDb = DB_MCP_TOOLS.filter(d => selected.includes(`db:${d.id}`));
|
|
304
|
+
const selectedGlobal = GLOBAL_TOOLS.filter(t => selected.includes(`global:${t.id}`));
|
|
305
|
+
|
|
306
|
+
// ── 收集数据库连接信息 ──
|
|
307
|
+
|
|
308
|
+
if (selectedDb.length > 0) {
|
|
309
|
+
console.log('');
|
|
310
|
+
console.log(chalk.yellow('📡 数据库连接配置'));
|
|
311
|
+
console.log('');
|
|
312
|
+
|
|
313
|
+
for (const db of selectedDb) {
|
|
314
|
+
console.log(chalk.cyan(` ${db.name}:`));
|
|
315
|
+
const dbWithEnv = { ...db, env: {} };
|
|
316
|
+
for (const [key, placeholder] of Object.entries(db.envTemplate)) {
|
|
317
|
+
const value = await input({
|
|
318
|
+
message: ` ${key}`,
|
|
319
|
+
default: placeholder,
|
|
320
|
+
});
|
|
321
|
+
dbWithEnv.env[key] = value;
|
|
322
|
+
}
|
|
323
|
+
// 用带 env 的版本替换
|
|
324
|
+
const idx = selectedDb.indexOf(db);
|
|
325
|
+
selectedDb[idx] = dbWithEnv;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// ── 安装 MCP 工具 ──
|
|
330
|
+
|
|
331
|
+
const allMcp = [...selectedMcp, ...selectedDb];
|
|
332
|
+
|
|
333
|
+
if (allMcp.length > 0) {
|
|
334
|
+
const toolChoices = availableTools.map(t => ({
|
|
335
|
+
name: t.tool,
|
|
336
|
+
value: t.key,
|
|
337
|
+
checked: true,
|
|
338
|
+
}));
|
|
339
|
+
|
|
340
|
+
const targetTools = await checkbox({
|
|
341
|
+
message: 'MCP 工具安装到哪些 AI 工具?',
|
|
342
|
+
choices: toolChoices,
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
const targets = availableTools.filter(t => targetTools.includes(t.key));
|
|
346
|
+
|
|
347
|
+
console.log('');
|
|
348
|
+
for (const { tool, path } of targets) {
|
|
349
|
+
const spinner = ora(`安装 MCP 到 ${tool}...`).start();
|
|
350
|
+
let config = readMcpConfig(dir, path) || { mcpServers: {} };
|
|
351
|
+
for (const mcp of allMcp) {
|
|
352
|
+
config = installMcp(config, mcp);
|
|
353
|
+
}
|
|
354
|
+
writeMcpConfig(dir, path, config);
|
|
355
|
+
spinner.succeed(`${tool} MCP 完成 (${allMcp.length} 个工具)`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// ── 安装全局工具 ──
|
|
360
|
+
|
|
361
|
+
if (selectedGlobal.length > 0) {
|
|
362
|
+
console.log('');
|
|
363
|
+
for (const tool of selectedGlobal) {
|
|
364
|
+
const spinner = ora(`安装 ${tool.name}...`).start();
|
|
365
|
+
try {
|
|
366
|
+
execSync(tool.installCommand, { stdio: 'pipe', encoding: 'utf8', timeout: 120000 });
|
|
367
|
+
spinner.succeed(`${tool.name} 安装完成`);
|
|
368
|
+
} catch (err) {
|
|
369
|
+
spinner.fail(`${tool.name} 安装失败: ${err.message}`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ── 总结 ──
|
|
375
|
+
|
|
376
|
+
console.log('');
|
|
377
|
+
console.log(chalk.green(' ═══════════════════════════════════════'));
|
|
378
|
+
console.log(chalk.green(' ✅ 工具安装完成!'));
|
|
379
|
+
console.log(chalk.green(' ═══════════════════════════════════════'));
|
|
380
|
+
console.log('');
|
|
381
|
+
for (const mcp of selectedMcp) {
|
|
382
|
+
console.log(` 🔌 ${chalk.cyan(mcp.name)} — ${mcp.description}`);
|
|
383
|
+
console.log(chalk.dim(` ${mcp.url}`));
|
|
384
|
+
}
|
|
385
|
+
for (const db of selectedDb) {
|
|
386
|
+
console.log(` 🗄️ ${chalk.cyan(db.name)} — ${db.description}`);
|
|
387
|
+
console.log(chalk.dim(` ${db.url}`));
|
|
388
|
+
}
|
|
389
|
+
for (const g of selectedGlobal) {
|
|
390
|
+
console.log(` 🛠️ ${chalk.cyan(g.name)} — ${g.description}`);
|
|
391
|
+
console.log(chalk.dim(` ${g.url}`));
|
|
392
|
+
}
|
|
393
|
+
console.log('');
|
|
394
|
+
if (selectedMcp.length > 0) {
|
|
395
|
+
console.log(chalk.dim(' 重启你的 AI 工具以使 MCP 配置生效。'));
|
|
396
|
+
}
|
|
397
|
+
console.log('');
|
|
398
|
+
}
|