ccbot-cli 1.2.0 → 2.0.0
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/bin/install.js +349 -0
- package/bin/lib/registry.js +61 -0
- package/bin/lib/utils.js +34 -0
- package/config/.claudeignore +11 -0
- package/config/CLAUDE.md +20 -0
- package/package.json +21 -27
- package/dist/index.js +0 -631
package/bin/install.js
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const { execSync } = require('child_process');
|
|
8
|
+
|
|
9
|
+
const pkg = require(path.join(__dirname, '..', 'package.json'));
|
|
10
|
+
const VERSION = pkg.version;
|
|
11
|
+
const HOME = os.homedir();
|
|
12
|
+
const CLAUDE_DIR = path.join(HOME, '.claude');
|
|
13
|
+
|
|
14
|
+
const { rmSafe, ensureDir, readJSON, writeJSON, deepMerge } = require(path.join(__dirname, 'lib', 'utils.js'));
|
|
15
|
+
const { MCP_SERVERS, SKILLS } = require(path.join(__dirname, 'lib', 'registry.js'));
|
|
16
|
+
|
|
17
|
+
// ── ANSI ──
|
|
18
|
+
const c = {
|
|
19
|
+
b: s => `\x1b[1m${s}\x1b[0m`,
|
|
20
|
+
d: s => `\x1b[2m${s}\x1b[0m`,
|
|
21
|
+
red: s => `\x1b[31m${s}\x1b[0m`,
|
|
22
|
+
grn: s => `\x1b[32m${s}\x1b[0m`,
|
|
23
|
+
ylw: s => `\x1b[33m${s}\x1b[0m`,
|
|
24
|
+
blu: s => `\x1b[34m${s}\x1b[0m`,
|
|
25
|
+
mag: s => `\x1b[35m${s}\x1b[0m`,
|
|
26
|
+
cyn: s => `\x1b[36m${s}\x1b[0m`,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function banner() {
|
|
30
|
+
console.log(c.cyn(`
|
|
31
|
+
██████╗ ██████╗ ██████╗ ██████╗ ████████╗
|
|
32
|
+
██╔════╝██╔════╝██╔══██╗██╔═══██╗╚══██╔══╝
|
|
33
|
+
██║ ██║ ██████╔╝██║ ██║ ██║
|
|
34
|
+
██║ ██║ ██╔══██╗██║ ██║ ██║
|
|
35
|
+
╚██████╗╚██████╗██████╔╝╚██████╔╝ ██║
|
|
36
|
+
╚═════╝ ╚═════╝╚═════╝ ╚═════╝ ╚═╝`));
|
|
37
|
+
console.log(c.d(` Claude Code 环境一键配置 v${VERSION}\n`));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function step(n, total, msg) { console.log(`\n ${c.cyn(`[${n}/${total}]`)} ${c.b(msg)}`); }
|
|
41
|
+
function ok(msg) { console.log(` ${c.grn('✔')} ${msg}`); }
|
|
42
|
+
function warn(msg) { console.log(` ${c.ylw('⚠')} ${msg}`); }
|
|
43
|
+
function info(msg) { console.log(` ${c.blu('ℹ')} ${msg}`); }
|
|
44
|
+
function fail(msg) { console.log(` ${c.red('✘')} ${msg}`); }
|
|
45
|
+
|
|
46
|
+
function divider(title) {
|
|
47
|
+
const line = '─'.repeat(44);
|
|
48
|
+
const pad = ' '.repeat(Math.max(0, 43 - title.length));
|
|
49
|
+
console.log(`\n${c.d('┌' + line + '┐')}`);
|
|
50
|
+
console.log(`${c.d('│')} ${c.b(title)}${pad}${c.d('│')}`);
|
|
51
|
+
console.log(`${c.d('└' + line + '┘')}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── CLI 参数 ──
|
|
55
|
+
const args = process.argv.slice(2);
|
|
56
|
+
let autoYes = false;
|
|
57
|
+
let uninstallMode = false;
|
|
58
|
+
|
|
59
|
+
for (let i = 0; i < args.length; i++) {
|
|
60
|
+
if (args[i] === '--yes' || args[i] === '-y') autoYes = true;
|
|
61
|
+
else if (args[i] === '--uninstall') uninstallMode = true;
|
|
62
|
+
else if (args[i] === '--help' || args[i] === '-h') {
|
|
63
|
+
banner();
|
|
64
|
+
console.log(`${c.b('用法:')} npx ccbot-cli [选项]
|
|
65
|
+
|
|
66
|
+
${c.b('选项:')}
|
|
67
|
+
--yes, -y 全自动模式 (安装全部预设)
|
|
68
|
+
--uninstall 卸载已安装的配置
|
|
69
|
+
--help, -h 显示帮助
|
|
70
|
+
|
|
71
|
+
${c.b('示例:')}
|
|
72
|
+
npx ccbot-cli ${c.d('# 交互菜单')}
|
|
73
|
+
npx ccbot-cli -y ${c.d('# 一键全装')}
|
|
74
|
+
npx ccbot-cli --uninstall ${c.d('# 卸载')}
|
|
75
|
+
`);
|
|
76
|
+
process.exit(0);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ── 检测 Claude Code CLI ──
|
|
81
|
+
function detectClaudeCLI() {
|
|
82
|
+
try {
|
|
83
|
+
execSync('claude --version', { stdio: 'pipe' });
|
|
84
|
+
return true;
|
|
85
|
+
} catch { return false; }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── 安装 Claude Code CLI ──
|
|
89
|
+
function installClaudeCLI() {
|
|
90
|
+
info('正在安装 Claude Code CLI...');
|
|
91
|
+
try {
|
|
92
|
+
execSync('npm install -g @anthropic-ai/claude-code', { stdio: 'inherit' });
|
|
93
|
+
ok('Claude Code CLI 安装成功');
|
|
94
|
+
return true;
|
|
95
|
+
} catch (e) {
|
|
96
|
+
fail(`安装失败: ${e.message}`);
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ── 生成项目脚手架 ──
|
|
102
|
+
function scaffold(projectDir) {
|
|
103
|
+
const PKG_ROOT = path.join(__dirname, '..');
|
|
104
|
+
const files = [
|
|
105
|
+
{ src: 'config/CLAUDE.md', dest: 'CLAUDE.md' },
|
|
106
|
+
{ src: 'config/.claudeignore', dest: '.claudeignore' },
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
let count = 0;
|
|
110
|
+
files.forEach(({ src, dest }) => {
|
|
111
|
+
const srcPath = path.join(PKG_ROOT, src);
|
|
112
|
+
const destPath = path.join(projectDir, dest);
|
|
113
|
+
if (!fs.existsSync(srcPath)) { warn(`模板缺失: ${src}`); return; }
|
|
114
|
+
if (fs.existsSync(destPath)) { info(`已存在,跳过: ${dest}`); return; }
|
|
115
|
+
fs.copyFileSync(srcPath, destPath);
|
|
116
|
+
ok(dest);
|
|
117
|
+
count++;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// .claude/settings.json (项目级)
|
|
121
|
+
const localSettingsDir = path.join(projectDir, '.claude');
|
|
122
|
+
const localSettingsPath = path.join(localSettingsDir, 'settings.json');
|
|
123
|
+
if (!fs.existsSync(localSettingsPath)) {
|
|
124
|
+
ensureDir(localSettingsDir);
|
|
125
|
+
writeJSON(localSettingsPath, {});
|
|
126
|
+
ok('.claude/settings.json');
|
|
127
|
+
count++;
|
|
128
|
+
} else {
|
|
129
|
+
info('已存在,跳过: .claude/settings.json');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return count;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ── 安装 MCP Servers ──
|
|
136
|
+
function installMCPServers(selected) {
|
|
137
|
+
const settingsPath = path.join(CLAUDE_DIR, 'settings.json');
|
|
138
|
+
let settings = readJSON(settingsPath) || {};
|
|
139
|
+
if (!settings.mcpServers) settings.mcpServers = {};
|
|
140
|
+
|
|
141
|
+
let count = 0;
|
|
142
|
+
selected.forEach(srv => {
|
|
143
|
+
const [command, srvArgs] = srv.args(HOME);
|
|
144
|
+
settings.mcpServers[srv.name] = { command, args: srvArgs };
|
|
145
|
+
ok(`${srv.name} ${c.d(`(${srv.desc})`)}`);
|
|
146
|
+
count++;
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
ensureDir(CLAUDE_DIR);
|
|
150
|
+
writeJSON(settingsPath, settings);
|
|
151
|
+
info(`已写入 ${c.cyn(settingsPath)}`);
|
|
152
|
+
return count;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ── 安装 Skills ──
|
|
156
|
+
function installSkills(selected) {
|
|
157
|
+
let count = 0;
|
|
158
|
+
selected.forEach(skill => {
|
|
159
|
+
info(`安装 ${skill.name}...`);
|
|
160
|
+
try {
|
|
161
|
+
execSync(skill.cmd, { stdio: 'inherit' });
|
|
162
|
+
ok(`${skill.name} ${c.d(`(${skill.desc})`)}`);
|
|
163
|
+
count++;
|
|
164
|
+
} catch (e) {
|
|
165
|
+
fail(`${skill.name} 安装失败: ${e.message}`);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
return count;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ── 环境变量清理 ──
|
|
172
|
+
function cleanEnvKeys() {
|
|
173
|
+
const patterns = [
|
|
174
|
+
/^ANTHROPIC_API_KEY$/i,
|
|
175
|
+
/^CLAUDE_API_KEY$/i,
|
|
176
|
+
/^OPENAI_API_KEY$/i,
|
|
177
|
+
];
|
|
178
|
+
const found = [];
|
|
179
|
+
for (const [key] of Object.entries(process.env)) {
|
|
180
|
+
if (patterns.some(p => p.test(key))) found.push(key);
|
|
181
|
+
}
|
|
182
|
+
if (found.length === 0) {
|
|
183
|
+
ok('未检测到敏感环境变量');
|
|
184
|
+
return 0;
|
|
185
|
+
}
|
|
186
|
+
found.forEach(key => {
|
|
187
|
+
warn(`检测到: ${c.ylw(key)} — 建议从系统环境变量中移除`);
|
|
188
|
+
});
|
|
189
|
+
info('Claude Code 使用 OAuth 认证,无需 API Key 环境变量');
|
|
190
|
+
return found.length;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ── 卸载 ──
|
|
194
|
+
function runUninstall() {
|
|
195
|
+
divider('卸载 ccbot-cli 配置');
|
|
196
|
+
|
|
197
|
+
const settingsPath = path.join(CLAUDE_DIR, 'settings.json');
|
|
198
|
+
const settings = readJSON(settingsPath);
|
|
199
|
+
if (!settings) { fail('未找到 settings.json'); return; }
|
|
200
|
+
|
|
201
|
+
if (settings.mcpServers) {
|
|
202
|
+
const names = Object.keys(settings.mcpServers);
|
|
203
|
+
const known = MCP_SERVERS.map(s => s.name);
|
|
204
|
+
let removed = 0;
|
|
205
|
+
names.forEach(name => {
|
|
206
|
+
if (known.includes(name)) {
|
|
207
|
+
delete settings.mcpServers[name];
|
|
208
|
+
ok(`移除 MCP: ${name}`);
|
|
209
|
+
removed++;
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
if (removed === 0) info('无 ccbot 安装的 MCP Server');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
writeJSON(settingsPath, settings);
|
|
216
|
+
ok('settings.json 已更新');
|
|
217
|
+
console.log('');
|
|
218
|
+
ok(c.b('卸载完成\n'));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ── 主流程 ──
|
|
222
|
+
|
|
223
|
+
async function main() {
|
|
224
|
+
if (uninstallMode) { runUninstall(); return; }
|
|
225
|
+
|
|
226
|
+
banner();
|
|
227
|
+
|
|
228
|
+
if (autoYes) {
|
|
229
|
+
await runFullInstall();
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const { select } = await import('@inquirer/prompts');
|
|
234
|
+
const action = await select({
|
|
235
|
+
message: '请选择操作',
|
|
236
|
+
choices: [
|
|
237
|
+
{ name: `一键全装 ${c.d('(CLI + MCP + Skills + 脚手架)')}`, value: 'full' },
|
|
238
|
+
{ name: `仅安装 MCP Servers`, value: 'mcp' },
|
|
239
|
+
{ name: `仅安装 Skills / Plugins`, value: 'skills' },
|
|
240
|
+
{ name: `生成项目脚手架 ${c.d('(CLAUDE.md 等)')}`, value: 'scaffold' },
|
|
241
|
+
{ name: `环境变量检查`, value: 'env' },
|
|
242
|
+
{ name: `${c.red('卸载')} ccbot 配置`, value: 'uninstall' },
|
|
243
|
+
],
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
switch (action) {
|
|
247
|
+
case 'full': await runFullInstall(); break;
|
|
248
|
+
case 'mcp': await runMCPInstall(); break;
|
|
249
|
+
case 'skills': await runSkillsInstall(); break;
|
|
250
|
+
case 'scaffold': await runScaffold(); break;
|
|
251
|
+
case 'env': runEnvCheck(); break;
|
|
252
|
+
case 'uninstall': runUninstall(); break;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ── 一键全装 ──
|
|
257
|
+
async function runFullInstall() {
|
|
258
|
+
const totalSteps = 5;
|
|
259
|
+
|
|
260
|
+
// 1. CLI
|
|
261
|
+
step(1, totalSteps, 'Claude Code CLI');
|
|
262
|
+
if (detectClaudeCLI()) {
|
|
263
|
+
ok('已安装');
|
|
264
|
+
} else {
|
|
265
|
+
installClaudeCLI();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// 2. MCP
|
|
269
|
+
step(2, totalSteps, `安装 MCP Servers (${MCP_SERVERS.length} 个)`);
|
|
270
|
+
installMCPServers(MCP_SERVERS);
|
|
271
|
+
|
|
272
|
+
// 3. Skills
|
|
273
|
+
step(3, totalSteps, `安装 Skills (${SKILLS.length} 个)`);
|
|
274
|
+
installSkills(SKILLS);
|
|
275
|
+
|
|
276
|
+
// 4. 脚手架
|
|
277
|
+
step(4, totalSteps, '生成项目脚手架');
|
|
278
|
+
scaffold(process.cwd());
|
|
279
|
+
|
|
280
|
+
// 5. 环境检查
|
|
281
|
+
step(5, totalSteps, '环境变量检查');
|
|
282
|
+
cleanEnvKeys();
|
|
283
|
+
|
|
284
|
+
finish();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ── 单独安装 MCP ──
|
|
288
|
+
async function runMCPInstall() {
|
|
289
|
+
const { checkbox } = await import('@inquirer/prompts');
|
|
290
|
+
const selected = await checkbox({
|
|
291
|
+
message: '选择要安装的 MCP Servers',
|
|
292
|
+
choices: MCP_SERVERS.map(s => ({
|
|
293
|
+
name: `${s.name} ${c.d(`— ${s.desc}`)}`,
|
|
294
|
+
value: s,
|
|
295
|
+
checked: true,
|
|
296
|
+
})),
|
|
297
|
+
});
|
|
298
|
+
if (selected.length === 0) { info('未选择任何 MCP Server'); return; }
|
|
299
|
+
|
|
300
|
+
step(1, 1, `安装 MCP Servers (${selected.length} 个)`);
|
|
301
|
+
installMCPServers(selected);
|
|
302
|
+
finish();
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ── 单独安装 Skills ──
|
|
306
|
+
async function runSkillsInstall() {
|
|
307
|
+
const { checkbox } = await import('@inquirer/prompts');
|
|
308
|
+
const selected = await checkbox({
|
|
309
|
+
message: '选择要安装的 Skills',
|
|
310
|
+
choices: SKILLS.map(s => ({
|
|
311
|
+
name: `${s.name} ${c.d(`— ${s.desc}`)}`,
|
|
312
|
+
value: s,
|
|
313
|
+
checked: true,
|
|
314
|
+
})),
|
|
315
|
+
});
|
|
316
|
+
if (selected.length === 0) { info('未选择任何 Skill'); return; }
|
|
317
|
+
|
|
318
|
+
step(1, 1, `安装 Skills (${selected.length} 个)`);
|
|
319
|
+
installSkills(selected);
|
|
320
|
+
finish();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ── 单独脚手架 ──
|
|
324
|
+
async function runScaffold() {
|
|
325
|
+
step(1, 1, `生成项目脚手架 → ${c.cyn(process.cwd())}`);
|
|
326
|
+
scaffold(process.cwd());
|
|
327
|
+
finish();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// ── 单独环境检查 ──
|
|
331
|
+
function runEnvCheck() {
|
|
332
|
+
step(1, 1, '环境变量检查');
|
|
333
|
+
cleanEnvKeys();
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ── 完成 ──
|
|
337
|
+
function finish() {
|
|
338
|
+
divider('安装完成');
|
|
339
|
+
console.log('');
|
|
340
|
+
console.log(` ${c.b('配置目录:')} ${c.cyn(CLAUDE_DIR)}`);
|
|
341
|
+
console.log(` ${c.b('版本:')} v${VERSION}`);
|
|
342
|
+
console.log(` ${c.b('卸载:')} ${c.d('npx ccbot-cli --uninstall')}`);
|
|
343
|
+
console.log('');
|
|
344
|
+
console.log(c.cyn(' ✔ 配置完成,开始使用 Claude Code 吧!\n'));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (require.main === module) {
|
|
348
|
+
main().catch(err => { fail(err.message); process.exit(1); });
|
|
349
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MCP Servers 预设注册表
|
|
5
|
+
* 每项包含: name, pkg (npm包名), desc, args (启动参数生成函数)
|
|
6
|
+
*/
|
|
7
|
+
const MCP_SERVERS = [
|
|
8
|
+
{
|
|
9
|
+
name: 'playwright',
|
|
10
|
+
desc: '浏览器自动化 (Playwright)',
|
|
11
|
+
args: () => ['npx', ['-y', '@playwright/mcp']],
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: 'filesystem',
|
|
15
|
+
desc: '文件系统访问',
|
|
16
|
+
args: (home) => ['npx', ['-y', '@modelcontextprotocol/server-filesystem', home]],
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: 'context7',
|
|
20
|
+
desc: '文档查询 (Context7)',
|
|
21
|
+
args: () => ['npx', ['-y', '@upstash/context7-mcp@latest']],
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'deepwiki',
|
|
25
|
+
desc: '深度Wiki知识库',
|
|
26
|
+
args: () => ['npx', ['-y', 'mcp-deepwiki']],
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'memory',
|
|
30
|
+
desc: '持久化记忆',
|
|
31
|
+
args: () => ['npx', ['-y', '@modelcontextprotocol/server-memory']],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'sequential-thinking',
|
|
35
|
+
desc: '顺序推理',
|
|
36
|
+
args: () => ['npx', ['-y', '@modelcontextprotocol/server-sequential-thinking']],
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Skills / Plugins 预设注册表
|
|
42
|
+
*/
|
|
43
|
+
const SKILLS = [
|
|
44
|
+
{
|
|
45
|
+
name: 'superpowers',
|
|
46
|
+
desc: '超能力插件集 (brainstorming, TDD, debugging...)',
|
|
47
|
+
cmd: 'claude plugins install claude-plugins-official/superpowers',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'spec-workflow',
|
|
51
|
+
desc: 'Spec 工作流 MCP',
|
|
52
|
+
cmd: 'claude plugins install nicobailon/spec-workflow-mcp-server',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'skill-creator',
|
|
56
|
+
desc: 'Skill 创建器',
|
|
57
|
+
cmd: 'claude plugins install anthropic/skill-creator',
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
module.exports = { MCP_SERVERS, SKILLS };
|
package/bin/lib/utils.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
function rmSafe(p) {
|
|
6
|
+
if (fs.existsSync(p)) fs.rmSync(p, { recursive: true, force: true });
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function ensureDir(p) {
|
|
10
|
+
if (!fs.existsSync(p)) fs.mkdirSync(p, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function readJSON(p) {
|
|
14
|
+
try { return JSON.parse(fs.readFileSync(p, 'utf8')); }
|
|
15
|
+
catch { return null; }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function writeJSON(p, obj) {
|
|
19
|
+
fs.writeFileSync(p, JSON.stringify(obj, null, 2) + '\n');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function deepMerge(target, source) {
|
|
23
|
+
for (const key of Object.keys(source)) {
|
|
24
|
+
if (typeof source[key] === 'object' && source[key] !== null && !Array.isArray(source[key])) {
|
|
25
|
+
if (!target[key] || typeof target[key] !== 'object') target[key] = {};
|
|
26
|
+
deepMerge(target[key], source[key]);
|
|
27
|
+
} else {
|
|
28
|
+
target[key] = source[key];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return target;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = { rmSafe, ensureDir, readJSON, writeJSON, deepMerge };
|
package/config/CLAUDE.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Project Guidelines
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
This project uses Claude Code as the AI development assistant.
|
|
5
|
+
|
|
6
|
+
## Conventions
|
|
7
|
+
- Use clear, descriptive variable names
|
|
8
|
+
- Write comments for complex logic
|
|
9
|
+
- Follow the existing code style
|
|
10
|
+
- Keep functions small and focused
|
|
11
|
+
|
|
12
|
+
## Structure
|
|
13
|
+
- Source code in `src/`
|
|
14
|
+
- Tests alongside source files or in `__tests__/`
|
|
15
|
+
- Configuration in project root
|
|
16
|
+
|
|
17
|
+
## Commands
|
|
18
|
+
- `npm install` — Install dependencies
|
|
19
|
+
- `npm test` — Run tests
|
|
20
|
+
- `npm run build` — Build project
|
package/package.json
CHANGED
|
@@ -1,40 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ccbot-cli",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Claude Code 环境一键配置工具",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
"ccbot-cli": "dist/index.js"
|
|
8
|
-
},
|
|
9
|
-
"files": [
|
|
10
|
-
"dist"
|
|
11
|
-
],
|
|
12
|
-
"scripts": {
|
|
13
|
-
"build": "tsup",
|
|
14
|
-
"dev": "tsup --watch",
|
|
15
|
-
"start": "node dist/index.js",
|
|
16
|
-
"prepublishOnly": "npm run build"
|
|
17
|
-
},
|
|
18
|
-
"engines": {
|
|
19
|
-
"node": ">=18"
|
|
20
|
-
},
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Claude Code 环境一键配置工具 — MCP Servers / Skills / 项目脚手架",
|
|
21
5
|
"keywords": [
|
|
22
6
|
"claude",
|
|
23
7
|
"claude-code",
|
|
24
8
|
"cli",
|
|
25
9
|
"setup",
|
|
26
10
|
"mcp",
|
|
27
|
-
"ai"
|
|
11
|
+
"ai",
|
|
12
|
+
"configuration"
|
|
28
13
|
],
|
|
14
|
+
"author": "",
|
|
29
15
|
"license": "MIT",
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
16
|
+
"bin": {
|
|
17
|
+
"ccbot-cli": "bin/install.js"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"bin/",
|
|
21
|
+
"config/",
|
|
22
|
+
"LICENSE",
|
|
23
|
+
"README.md"
|
|
24
|
+
],
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18.0.0"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"test": "echo \"no tests yet\""
|
|
34
30
|
},
|
|
35
|
-
"
|
|
36
|
-
"@
|
|
37
|
-
"tsup": "^8.3.5",
|
|
38
|
-
"typescript": "^5.7.2"
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@inquirer/prompts": "^7.10.1"
|
|
39
33
|
}
|
|
40
34
|
}
|
package/dist/index.js
DELETED
|
@@ -1,631 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
#!/usr/bin/env node
|
|
3
|
-
|
|
4
|
-
// src/index.ts
|
|
5
|
-
import { createRequire } from "module";
|
|
6
|
-
|
|
7
|
-
// src/utils/logger.ts
|
|
8
|
-
import pc from "picocolors";
|
|
9
|
-
function step(n, total, title) {
|
|
10
|
-
console.log(pc.cyan(`
|
|
11
|
-
[${n}/${total}] `) + pc.bold(title));
|
|
12
|
-
}
|
|
13
|
-
function hint(msg) {
|
|
14
|
-
console.log(pc.dim(` ${msg}`));
|
|
15
|
-
}
|
|
16
|
-
function ok(msg) {
|
|
17
|
-
console.log(pc.green(" \u2713 ") + msg);
|
|
18
|
-
}
|
|
19
|
-
function fail(msg) {
|
|
20
|
-
console.log(pc.red(" \u2717 ") + msg);
|
|
21
|
-
}
|
|
22
|
-
function warn(msg) {
|
|
23
|
-
console.log(pc.yellow(" \u26A0 ") + msg);
|
|
24
|
-
}
|
|
25
|
-
function info(msg) {
|
|
26
|
-
console.log(pc.cyan(" \u2139 ") + msg);
|
|
27
|
-
}
|
|
28
|
-
function banner(version2) {
|
|
29
|
-
console.log();
|
|
30
|
-
console.log(pc.cyan(" \u{1F916} ccbot") + pc.dim(` v${version2}`) + pc.cyan(" \u2014 Claude Code \u73AF\u5883\u4E00\u952E\u914D\u7F6E"));
|
|
31
|
-
console.log();
|
|
32
|
-
}
|
|
33
|
-
function done(items) {
|
|
34
|
-
const okCount = items.filter((i) => i.ok).length;
|
|
35
|
-
const failCount = items.filter((i) => !i.ok).length;
|
|
36
|
-
console.log();
|
|
37
|
-
if (failCount === 0) {
|
|
38
|
-
console.log(pc.green(pc.bold(" \u2713 \u5168\u90E8\u5B8C\u6210!")));
|
|
39
|
-
} else {
|
|
40
|
-
console.log(pc.yellow(` \u26A0 \u5B8C\u6210 ${okCount} \u9879, ${failCount} \u9879\u9700\u6CE8\u610F`));
|
|
41
|
-
}
|
|
42
|
-
console.log();
|
|
43
|
-
for (const item of items) {
|
|
44
|
-
if (item.ok) ok(item.label);
|
|
45
|
-
else fail(item.label);
|
|
46
|
-
}
|
|
47
|
-
console.log();
|
|
48
|
-
console.log(pc.dim(" \u4E0B\u4E00\u6B65:"));
|
|
49
|
-
console.log(pc.dim(" 1. ") + pc.green("claude") + pc.dim(" \u542F\u52A8 Claude Code"));
|
|
50
|
-
console.log(pc.dim(" 2. \u7F16\u8F91 ") + pc.green("CLAUDE.md") + pc.dim(" \u81EA\u5B9A\u4E49\u6307\u4EE4"));
|
|
51
|
-
console.log();
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// src/cli.ts
|
|
55
|
-
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
56
|
-
import pc3 from "picocolors";
|
|
57
|
-
|
|
58
|
-
// src/utils/exec.ts
|
|
59
|
-
import { execa } from "execa";
|
|
60
|
-
async function run(command, args = [], options) {
|
|
61
|
-
try {
|
|
62
|
-
const result = await execa(command, args, {
|
|
63
|
-
...options,
|
|
64
|
-
reject: false
|
|
65
|
-
});
|
|
66
|
-
return {
|
|
67
|
-
stdout: result.stdout?.toString() ?? "",
|
|
68
|
-
stderr: result.stderr?.toString() ?? "",
|
|
69
|
-
exitCode: result.exitCode ?? 0
|
|
70
|
-
};
|
|
71
|
-
} catch {
|
|
72
|
-
return { stdout: "", stderr: "Command not found or failed to execute", exitCode: 127 };
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
async function commandExists(command) {
|
|
76
|
-
const cmd = process.platform === "win32" ? "where" : "which";
|
|
77
|
-
const result = await run(cmd, [command]);
|
|
78
|
-
return result.exitCode === 0;
|
|
79
|
-
}
|
|
80
|
-
async function npmInstallGlobal(pkg) {
|
|
81
|
-
return run("npm", ["install", "-g", pkg]);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// src/steps/detect.ts
|
|
85
|
-
var SENSITIVE_PATTERNS = [
|
|
86
|
-
/^ANTHROPIC_API_KEY$/i,
|
|
87
|
-
/^CLAUDE_API_KEY$/i,
|
|
88
|
-
/^OPENAI_API_KEY$/i,
|
|
89
|
-
/^GOOGLE_API_KEY$/i,
|
|
90
|
-
/^AZURE_OPENAI_API_KEY$/i,
|
|
91
|
-
/^HUGGINGFACE_TOKEN$/i,
|
|
92
|
-
/^HF_TOKEN$/i,
|
|
93
|
-
/^COHERE_API_KEY$/i,
|
|
94
|
-
/^MISTRAL_API_KEY$/i,
|
|
95
|
-
/^DEEPSEEK_API_KEY$/i,
|
|
96
|
-
/^GROQ_API_KEY$/i,
|
|
97
|
-
/_SECRET$/i,
|
|
98
|
-
/_TOKEN$/i,
|
|
99
|
-
/_API_KEY$/i
|
|
100
|
-
];
|
|
101
|
-
async function detect(total) {
|
|
102
|
-
step(1, total, "\u73AF\u5883\u68C0\u6D4B");
|
|
103
|
-
const nodeVersion = process.version;
|
|
104
|
-
const npmResult = await run("npm", ["--version"]);
|
|
105
|
-
const npmVersion = npmResult.stdout.trim();
|
|
106
|
-
const osMap = { win32: "Windows", darwin: "macOS", linux: "Linux" };
|
|
107
|
-
const os = osMap[process.platform] ?? process.platform;
|
|
108
|
-
const claudeInstalled = await commandExists("claude");
|
|
109
|
-
let claudeVersion = null;
|
|
110
|
-
if (claudeInstalled) {
|
|
111
|
-
const r = await run("claude", ["--version"]);
|
|
112
|
-
claudeVersion = r.stdout.trim();
|
|
113
|
-
}
|
|
114
|
-
const sensitiveEnvVars = [];
|
|
115
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
116
|
-
if (!value) continue;
|
|
117
|
-
if (SENSITIVE_PATTERNS.some((p) => p.test(key))) {
|
|
118
|
-
sensitiveEnvVars.push({ key, value });
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
ok(`Node ${nodeVersion} \xB7 npm v${npmVersion} \xB7 ${os}`);
|
|
122
|
-
if (claudeInstalled) ok(`Claude CLI ${claudeVersion}`);
|
|
123
|
-
else warn("Claude CLI \u672A\u5B89\u88C5");
|
|
124
|
-
if (sensitiveEnvVars.length > 0) {
|
|
125
|
-
warn(`\u53D1\u73B0 ${sensitiveEnvVars.length} \u4E2A\u654F\u611F\u73AF\u5883\u53D8\u91CF`);
|
|
126
|
-
}
|
|
127
|
-
return { nodeVersion, npmVersion, os, platform: process.platform, claudeInstalled, claudeVersion, sensitiveEnvVars };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// src/steps/select.ts
|
|
131
|
-
import { checkbox } from "@inquirer/prompts";
|
|
132
|
-
async function selectComponents(env, total) {
|
|
133
|
-
step(2, total, "\u9009\u62E9\u7EC4\u4EF6");
|
|
134
|
-
hint("\u7A7A\u683C=\u5207\u6362 a=\u5168\u9009 \u56DE\u8F66=\u786E\u8BA4");
|
|
135
|
-
const choices = [
|
|
136
|
-
{
|
|
137
|
-
value: "installCli",
|
|
138
|
-
name: `Claude Code CLI ${env.claudeInstalled ? "(\u5DF2\u5B89\u88C5, \u8DF3\u8FC7)" : "(\u672A\u5B89\u88C5)"}`,
|
|
139
|
-
checked: true
|
|
140
|
-
},
|
|
141
|
-
{ value: "scaffold", name: "\u9879\u76EE\u914D\u7F6E\u6587\u4EF6 (CLAUDE.md + .claude/)", checked: true },
|
|
142
|
-
{ value: "installMcp", name: "MCP Servers (AI\u5DE5\u5177\u670D\u52A1)", checked: true },
|
|
143
|
-
{ value: "installSkills", name: "Skills / Plugins (\u5DE5\u4F5C\u6D41\u589E\u5F3A)", checked: true }
|
|
144
|
-
];
|
|
145
|
-
if (env.sensitiveEnvVars.length > 0) {
|
|
146
|
-
choices.push({
|
|
147
|
-
value: "cleanEnv",
|
|
148
|
-
name: `\u73AF\u5883\u53D8\u91CF\u6E05\u7406 (${env.sensitiveEnvVars.length} \u4E2A\u654F\u611F\u53D8\u91CF)`,
|
|
149
|
-
checked: true
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
const selected = await checkbox({
|
|
153
|
-
message: "\u5B89\u88C5\u7EC4\u4EF6",
|
|
154
|
-
choices,
|
|
155
|
-
required: true
|
|
156
|
-
});
|
|
157
|
-
return {
|
|
158
|
-
installCli: selected.includes("installCli"),
|
|
159
|
-
scaffold: selected.includes("scaffold"),
|
|
160
|
-
installMcp: selected.includes("installMcp"),
|
|
161
|
-
installSkills: selected.includes("installSkills"),
|
|
162
|
-
cleanEnv: selected.includes("cleanEnv")
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// src/steps/install-cli.ts
|
|
167
|
-
async function installCli(alreadyInstalled) {
|
|
168
|
-
if (alreadyInstalled) {
|
|
169
|
-
info("Claude CLI \u5DF2\u5B89\u88C5, \u8DF3\u8FC7");
|
|
170
|
-
return { success: true, version: null, skipped: true };
|
|
171
|
-
}
|
|
172
|
-
info("\u6B63\u5728\u5B89\u88C5 Claude Code CLI...");
|
|
173
|
-
const result = await npmInstallGlobal("@anthropic-ai/claude-code");
|
|
174
|
-
if (result.exitCode !== 0) {
|
|
175
|
-
fail("Claude CLI \u5B89\u88C5\u5931\u8D25");
|
|
176
|
-
warn("\u624B\u52A8: npm install -g @anthropic-ai/claude-code");
|
|
177
|
-
return { success: false, version: null, skipped: false };
|
|
178
|
-
}
|
|
179
|
-
const exists = await commandExists("claude");
|
|
180
|
-
if (!exists) {
|
|
181
|
-
warn("\u5B89\u88C5\u5B8C\u6210\u4F46 claude \u547D\u4EE4\u4E0D\u53EF\u7528, \u53EF\u80FD\u9700\u91CD\u542F\u7EC8\u7AEF");
|
|
182
|
-
return { success: false, version: null, skipped: false };
|
|
183
|
-
}
|
|
184
|
-
const v = await run("claude", ["--version"]);
|
|
185
|
-
const version2 = v.stdout.trim();
|
|
186
|
-
ok(`Claude CLI ${version2}`);
|
|
187
|
-
return { success: true, version: version2, skipped: false };
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// src/steps/scaffold.ts
|
|
191
|
-
import { select } from "@inquirer/prompts";
|
|
192
|
-
import { join as join2 } from "path";
|
|
193
|
-
|
|
194
|
-
// src/utils/fs.ts
|
|
195
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
196
|
-
import { dirname } from "path";
|
|
197
|
-
function ensureDir(dir) {
|
|
198
|
-
if (!existsSync(dir)) {
|
|
199
|
-
mkdirSync(dir, { recursive: true });
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
function writeFileSafe(filePath, content, overwrite = false) {
|
|
203
|
-
ensureDir(dirname(filePath));
|
|
204
|
-
if (existsSync(filePath) && !overwrite) {
|
|
205
|
-
return { written: false, skipped: true };
|
|
206
|
-
}
|
|
207
|
-
writeFileSync(filePath, content, "utf-8");
|
|
208
|
-
return { written: true, skipped: false };
|
|
209
|
-
}
|
|
210
|
-
function readJsonFile(filePath) {
|
|
211
|
-
try {
|
|
212
|
-
const raw = readFileSync(filePath, "utf-8");
|
|
213
|
-
return JSON.parse(raw);
|
|
214
|
-
} catch {
|
|
215
|
-
return null;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
function mergeJsonFile(filePath, patch) {
|
|
219
|
-
const existing = readJsonFile(filePath) ?? {};
|
|
220
|
-
const merged = deepMerge(existing, patch);
|
|
221
|
-
writeFileSync(filePath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
222
|
-
}
|
|
223
|
-
function deepMerge(target, source) {
|
|
224
|
-
const result = { ...target };
|
|
225
|
-
for (const key of Object.keys(source)) {
|
|
226
|
-
if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key]) && target[key] && typeof target[key] === "object" && !Array.isArray(target[key])) {
|
|
227
|
-
result[key] = deepMerge(
|
|
228
|
-
target[key],
|
|
229
|
-
source[key]
|
|
230
|
-
);
|
|
231
|
-
} else {
|
|
232
|
-
result[key] = source[key];
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
return result;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// src/steps/scaffold.ts
|
|
239
|
-
var TEMPLATES = {
|
|
240
|
-
minimal: `# Project Instructions
|
|
241
|
-
|
|
242
|
-
- Follow existing code style
|
|
243
|
-
- Read before write
|
|
244
|
-
- Keep changes minimal
|
|
245
|
-
`,
|
|
246
|
-
standard: `# Project Instructions
|
|
247
|
-
|
|
248
|
-
## Overview
|
|
249
|
-
<!-- Project purpose and tech stack -->
|
|
250
|
-
|
|
251
|
-
## Coding Standards
|
|
252
|
-
- Follow existing patterns
|
|
253
|
-
- Read context before modifying
|
|
254
|
-
- Keep changes focused
|
|
255
|
-
|
|
256
|
-
## Key Files
|
|
257
|
-
<!-- Important file paths -->
|
|
258
|
-
|
|
259
|
-
## Notes
|
|
260
|
-
<!-- Special considerations -->
|
|
261
|
-
`,
|
|
262
|
-
detailed: `# Project Instructions
|
|
263
|
-
|
|
264
|
-
## Overview
|
|
265
|
-
<!-- Project purpose, goals, tech stack -->
|
|
266
|
-
|
|
267
|
-
## Architecture
|
|
268
|
-
<!-- High-level design decisions -->
|
|
269
|
-
|
|
270
|
-
## Coding Standards
|
|
271
|
-
- Follow existing patterns
|
|
272
|
-
- Read context before modifying
|
|
273
|
-
- Write clear commit messages
|
|
274
|
-
- Add comments for complex logic
|
|
275
|
-
|
|
276
|
-
## Key Files
|
|
277
|
-
<!-- Important paths and purposes -->
|
|
278
|
-
|
|
279
|
-
## Dependencies
|
|
280
|
-
<!-- Key deps and roles -->
|
|
281
|
-
|
|
282
|
-
## Testing
|
|
283
|
-
<!-- How to run tests -->
|
|
284
|
-
|
|
285
|
-
## Notes
|
|
286
|
-
<!-- Gotchas and context -->
|
|
287
|
-
`
|
|
288
|
-
};
|
|
289
|
-
var CLAUDEIGNORE = `node_modules/
|
|
290
|
-
dist/
|
|
291
|
-
build/
|
|
292
|
-
.env
|
|
293
|
-
.env.*
|
|
294
|
-
*.log
|
|
295
|
-
.DS_Store
|
|
296
|
-
coverage/
|
|
297
|
-
.git/
|
|
298
|
-
`;
|
|
299
|
-
async function scaffoldConfig(total) {
|
|
300
|
-
step(3, total, "CLAUDE.md \u6A21\u677F");
|
|
301
|
-
const style = await select({
|
|
302
|
-
message: "\u9009\u62E9\u6A21\u677F\u98CE\u683C",
|
|
303
|
-
choices: [
|
|
304
|
-
{ value: "minimal", name: "\u6781\u7B80 \u2014 \u51E0\u884C\u6838\u5FC3\u89C4\u5219" },
|
|
305
|
-
{ value: "standard", name: "\u6807\u51C6 \u2014 \u5E38\u7528\u5206\u533A (\u63A8\u8350)" },
|
|
306
|
-
{ value: "detailed", name: "\u8BE6\u7EC6 \u2014 \u542B\u67B6\u6784/\u6D4B\u8BD5/\u4F9D\u8D56" }
|
|
307
|
-
],
|
|
308
|
-
default: "standard"
|
|
309
|
-
});
|
|
310
|
-
return { style };
|
|
311
|
-
}
|
|
312
|
-
async function scaffold(targetDir, opts) {
|
|
313
|
-
const files = [];
|
|
314
|
-
const r1 = writeFileSafe(join2(targetDir, "CLAUDE.md"), TEMPLATES[opts.style]);
|
|
315
|
-
files.push({ path: "CLAUDE.md", written: r1.written });
|
|
316
|
-
ensureDir(join2(targetDir, ".claude"));
|
|
317
|
-
const settings = { permissions: { allow: ["Read", "Glob", "Grep", "WebFetch", "WebSearch"], deny: [] }, mcpServers: {} };
|
|
318
|
-
const r2 = writeFileSafe(join2(targetDir, ".claude", "settings.json"), JSON.stringify(settings, null, 2) + "\n");
|
|
319
|
-
files.push({ path: ".claude/settings.json", written: r2.written });
|
|
320
|
-
const r3 = writeFileSafe(join2(targetDir, ".claudeignore"), CLAUDEIGNORE);
|
|
321
|
-
files.push({ path: ".claudeignore", written: r3.written });
|
|
322
|
-
for (const f of files) {
|
|
323
|
-
if (f.written) ok(f.path);
|
|
324
|
-
else info(`${f.path} (\u5DF2\u5B58\u5728, \u8DF3\u8FC7)`);
|
|
325
|
-
}
|
|
326
|
-
return { files };
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// src/steps/install-mcp.ts
|
|
330
|
-
import { checkbox as checkbox2, confirm, input } from "@inquirer/prompts";
|
|
331
|
-
import { join as join3 } from "path";
|
|
332
|
-
import { homedir } from "os";
|
|
333
|
-
|
|
334
|
-
// src/registry/mcp-servers.ts
|
|
335
|
-
var MCP_SERVERS = [
|
|
336
|
-
{
|
|
337
|
-
name: "playwright",
|
|
338
|
-
package: "@anthropic-ai/mcp-playwright",
|
|
339
|
-
description: "\u6D4F\u89C8\u5668\u81EA\u52A8\u5316 (\u622A\u56FE\u3001\u70B9\u51FB\u3001\u8868\u5355\u586B\u5199)",
|
|
340
|
-
scope: "user",
|
|
341
|
-
command: "npx",
|
|
342
|
-
args: ["-y", "@anthropic-ai/mcp-playwright"]
|
|
343
|
-
},
|
|
344
|
-
{
|
|
345
|
-
name: "filesystem",
|
|
346
|
-
package: "@modelcontextprotocol/server-filesystem",
|
|
347
|
-
description: "\u6587\u4EF6\u7CFB\u7EDF\u8BFB\u5199\u8BBF\u95EE",
|
|
348
|
-
scope: "project",
|
|
349
|
-
command: "npx",
|
|
350
|
-
args: ["-y", "@modelcontextprotocol/server-filesystem", "."]
|
|
351
|
-
},
|
|
352
|
-
{
|
|
353
|
-
name: "context7",
|
|
354
|
-
package: "@anthropic-ai/mcp-context7",
|
|
355
|
-
description: "\u7F16\u7A0B\u6587\u6863\u5B9E\u65F6\u67E5\u8BE2",
|
|
356
|
-
scope: "user",
|
|
357
|
-
command: "npx",
|
|
358
|
-
args: ["-y", "@upstash/context7-mcp"]
|
|
359
|
-
},
|
|
360
|
-
{
|
|
361
|
-
name: "deepwiki",
|
|
362
|
-
package: "@anthropic-ai/mcp-deepwiki",
|
|
363
|
-
description: "GitHub \u4ED3\u5E93 Wiki \u77E5\u8BC6\u5E93",
|
|
364
|
-
scope: "user",
|
|
365
|
-
command: "npx",
|
|
366
|
-
args: ["-y", "@anthropic-ai/mcp-deepwiki"]
|
|
367
|
-
},
|
|
368
|
-
{
|
|
369
|
-
name: "open-websearch",
|
|
370
|
-
package: "open-websearch-mcp",
|
|
371
|
-
description: "\u591A\u5F15\u64CE\u7F51\u9875\u641C\u7D22 (DuckDuckGo/Bing/Brave)",
|
|
372
|
-
scope: "user",
|
|
373
|
-
command: "npx",
|
|
374
|
-
args: ["-y", "open-websearch-mcp"]
|
|
375
|
-
},
|
|
376
|
-
{
|
|
377
|
-
name: "memory",
|
|
378
|
-
package: "@modelcontextprotocol/server-memory",
|
|
379
|
-
description: "\u6301\u4E45\u5316\u8BB0\u5FC6\u5B58\u50A8",
|
|
380
|
-
scope: "user",
|
|
381
|
-
command: "npx",
|
|
382
|
-
args: ["-y", "@modelcontextprotocol/server-memory"]
|
|
383
|
-
},
|
|
384
|
-
{
|
|
385
|
-
name: "sequential-thinking",
|
|
386
|
-
package: "@modelcontextprotocol/server-sequential-thinking",
|
|
387
|
-
description: "\u94FE\u5F0F\u601D\u8003\u63A8\u7406",
|
|
388
|
-
scope: "user",
|
|
389
|
-
command: "npx",
|
|
390
|
-
args: ["-y", "@modelcontextprotocol/server-sequential-thinking"]
|
|
391
|
-
}
|
|
392
|
-
];
|
|
393
|
-
|
|
394
|
-
// src/steps/install-mcp.ts
|
|
395
|
-
async function selectMcpServers(total) {
|
|
396
|
-
step(4, total, "MCP Servers");
|
|
397
|
-
hint("\u7A7A\u683C=\u5207\u6362 a=\u5168\u9009 \u56DE\u8F66=\u786E\u8BA4");
|
|
398
|
-
const selected = await checkbox2({
|
|
399
|
-
message: "\u9009\u62E9 MCP Servers",
|
|
400
|
-
choices: MCP_SERVERS.map((s) => ({
|
|
401
|
-
value: s.name,
|
|
402
|
-
name: `${s.name} \u2014 ${s.description}`,
|
|
403
|
-
checked: true
|
|
404
|
-
}))
|
|
405
|
-
});
|
|
406
|
-
const servers = MCP_SERVERS.filter((s) => selected.includes(s.name));
|
|
407
|
-
const addCustom = await confirm({ message: "\u6DFB\u52A0\u81EA\u5B9A\u4E49 MCP (npm\u5305)?", default: false });
|
|
408
|
-
if (addCustom) {
|
|
409
|
-
const raw = await input({
|
|
410
|
-
message: "npm \u5305\u540D (\u9017\u53F7\u5206\u9694)"
|
|
411
|
-
});
|
|
412
|
-
if (raw.trim()) {
|
|
413
|
-
for (const pkg of raw.split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
414
|
-
const name = pkg.split("/").pop()?.replace(/^mcp-/, "") ?? pkg;
|
|
415
|
-
servers.push({ name, package: pkg, description: `\u81EA\u5B9A\u4E49: ${pkg}`, scope: "user", command: "npx", args: ["-y", pkg] });
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
return servers;
|
|
420
|
-
}
|
|
421
|
-
async function installMcp(servers) {
|
|
422
|
-
if (servers.length === 0) return { installed: [], failed: [] };
|
|
423
|
-
const installed = [];
|
|
424
|
-
const failed = [];
|
|
425
|
-
const userServers = servers.filter((s) => s.scope === "user");
|
|
426
|
-
const projectServers = servers.filter((s) => s.scope === "project");
|
|
427
|
-
if (userServers.length > 0) {
|
|
428
|
-
const dir = join3(homedir(), ".claude");
|
|
429
|
-
ensureDir(dir);
|
|
430
|
-
const cfg = {};
|
|
431
|
-
for (const s of userServers) {
|
|
432
|
-
cfg[s.name] = { command: s.command, args: s.args, ...s.env ? { env: s.env } : {} };
|
|
433
|
-
}
|
|
434
|
-
try {
|
|
435
|
-
mergeJsonFile(join3(dir, "settings.json"), { mcpServers: cfg });
|
|
436
|
-
userServers.forEach((s) => installed.push(s.name));
|
|
437
|
-
} catch {
|
|
438
|
-
userServers.forEach((s) => failed.push(s.name));
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
if (projectServers.length > 0) {
|
|
442
|
-
const dir = join3(process.cwd(), ".claude");
|
|
443
|
-
ensureDir(dir);
|
|
444
|
-
const cfg = {};
|
|
445
|
-
for (const s of projectServers) {
|
|
446
|
-
cfg[s.name] = { command: s.command, args: s.args, ...s.env ? { env: s.env } : {} };
|
|
447
|
-
}
|
|
448
|
-
try {
|
|
449
|
-
mergeJsonFile(join3(dir, "settings.json"), { mcpServers: cfg });
|
|
450
|
-
projectServers.forEach((s) => installed.push(s.name));
|
|
451
|
-
} catch {
|
|
452
|
-
projectServers.forEach((s) => failed.push(s.name));
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
for (const n of installed) ok(`${n} MCP`);
|
|
456
|
-
for (const n of failed) fail(`${n} MCP`);
|
|
457
|
-
return { installed, failed };
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
// src/steps/install-skills.ts
|
|
461
|
-
import { checkbox as checkbox3 } from "@inquirer/prompts";
|
|
462
|
-
|
|
463
|
-
// src/registry/skills.ts
|
|
464
|
-
var SKILLS = [
|
|
465
|
-
{
|
|
466
|
-
name: "superpowers",
|
|
467
|
-
description: "\u589E\u5F3A\u5DE5\u4F5C\u6D41 (\u5934\u8111\u98CE\u66B4\u3001TDD\u3001\u4EE3\u7801\u5BA1\u67E5\u3001\u8BA1\u5212)",
|
|
468
|
-
source: "claude-plugins-official/superpowers",
|
|
469
|
-
installCmd: ["claude", "plugins", "install", "superpowers"]
|
|
470
|
-
},
|
|
471
|
-
{
|
|
472
|
-
name: "spec-workflow",
|
|
473
|
-
description: "\u89C4\u8303\u5316\u5F00\u53D1\u6D41\u7A0B (\u9700\u6C42\u2192\u8BBE\u8BA1\u2192\u4EFB\u52A1\u2192\u5B9E\u73B0)",
|
|
474
|
-
source: "spec-workflow",
|
|
475
|
-
installCmd: ["claude", "plugins", "install", "spec-workflow"]
|
|
476
|
-
},
|
|
477
|
-
{
|
|
478
|
-
name: "skill-creator",
|
|
479
|
-
description: "Skill \u521B\u5EFA\u4E0E\u7BA1\u7406\u5DE5\u5177",
|
|
480
|
-
source: "skill-creator",
|
|
481
|
-
installCmd: ["claude", "plugins", "install", "skill-creator"]
|
|
482
|
-
}
|
|
483
|
-
];
|
|
484
|
-
|
|
485
|
-
// src/steps/install-skills.ts
|
|
486
|
-
async function selectSkills(total) {
|
|
487
|
-
step(5, total, "Skills / Plugins");
|
|
488
|
-
hint("\u7A7A\u683C=\u5207\u6362 a=\u5168\u9009 \u56DE\u8F66=\u786E\u8BA4");
|
|
489
|
-
const selected = await checkbox3({
|
|
490
|
-
message: "\u9009\u62E9 Skills",
|
|
491
|
-
choices: SKILLS.map((s) => ({
|
|
492
|
-
value: s.name,
|
|
493
|
-
name: `${s.name} \u2014 ${s.description}`,
|
|
494
|
-
checked: true
|
|
495
|
-
}))
|
|
496
|
-
});
|
|
497
|
-
return SKILLS.filter((s) => selected.includes(s.name));
|
|
498
|
-
}
|
|
499
|
-
async function installSkills(skills) {
|
|
500
|
-
if (skills.length === 0) return { installed: [], failed: [] };
|
|
501
|
-
const installed = [];
|
|
502
|
-
const failed = [];
|
|
503
|
-
for (const skill of skills) {
|
|
504
|
-
const [cmd, ...args] = skill.installCmd;
|
|
505
|
-
const result = await run(cmd, args);
|
|
506
|
-
if (result.exitCode === 0) {
|
|
507
|
-
installed.push(skill.name);
|
|
508
|
-
ok(skill.name);
|
|
509
|
-
} else {
|
|
510
|
-
failed.push(skill.name);
|
|
511
|
-
fail(skill.name);
|
|
512
|
-
warn(` \u624B\u52A8: ${skill.installCmd.join(" ")}`);
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
return { installed, failed };
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
// src/steps/clean-env.ts
|
|
519
|
-
import { checkbox as checkbox4 } from "@inquirer/prompts";
|
|
520
|
-
import pc2 from "picocolors";
|
|
521
|
-
function mask(val) {
|
|
522
|
-
if (val.length <= 8) return "****";
|
|
523
|
-
return val.slice(0, 4) + "..." + val.slice(-4);
|
|
524
|
-
}
|
|
525
|
-
async function cleanEnv(vars, total) {
|
|
526
|
-
if (vars.length === 0) return { removed: [], skipped: [] };
|
|
527
|
-
step(6, total, "\u73AF\u5883\u53D8\u91CF\u6E05\u7406");
|
|
528
|
-
hint("\u7A7A\u683C=\u5207\u6362 \u56DE\u8F66=\u786E\u8BA4 (\u4E0D\u9009\u5219\u5168\u90E8\u4FDD\u7559)");
|
|
529
|
-
const selected = await checkbox4({
|
|
530
|
-
message: "\u9009\u62E9\u8981\u79FB\u9664\u7684\u53D8\u91CF",
|
|
531
|
-
choices: vars.map((v) => ({
|
|
532
|
-
value: v.key,
|
|
533
|
-
name: `${v.key} = ${pc2.dim(mask(v.value))}`,
|
|
534
|
-
checked: false
|
|
535
|
-
}))
|
|
536
|
-
});
|
|
537
|
-
if (selected.length === 0) return { removed: [], skipped: vars.map((v) => v.key) };
|
|
538
|
-
const removed = [];
|
|
539
|
-
const skipped = [];
|
|
540
|
-
for (const key of selected) {
|
|
541
|
-
if (process.platform === "win32") {
|
|
542
|
-
const r = await run("powershell", ["-Command", `[Environment]::SetEnvironmentVariable('${key}', $null, 'User')`]);
|
|
543
|
-
if (r.exitCode === 0) {
|
|
544
|
-
removed.push(key);
|
|
545
|
-
ok(`${key} \u5DF2\u79FB\u9664`);
|
|
546
|
-
} else {
|
|
547
|
-
skipped.push(key);
|
|
548
|
-
warn(`${key} \u79FB\u9664\u5931\u8D25`);
|
|
549
|
-
}
|
|
550
|
-
} else {
|
|
551
|
-
delete process.env[key];
|
|
552
|
-
skipped.push(key);
|
|
553
|
-
warn(`${key} \u9700\u624B\u52A8\u4ECE ~/.bashrc \u6216 ~/.zshrc \u79FB\u9664`);
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
return { removed, skipped };
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
// src/cli.ts
|
|
560
|
-
var TOTAL = 7;
|
|
561
|
-
async function runCli() {
|
|
562
|
-
const results = [];
|
|
563
|
-
const env = await detect(TOTAL);
|
|
564
|
-
const components = await selectComponents(env, TOTAL);
|
|
565
|
-
let scaffoldOpts = null;
|
|
566
|
-
if (components.scaffold) scaffoldOpts = await scaffoldConfig(TOTAL);
|
|
567
|
-
let mcpServers = [];
|
|
568
|
-
if (components.installMcp) mcpServers = await selectMcpServers(TOTAL);
|
|
569
|
-
let skills = [];
|
|
570
|
-
if (components.installSkills) skills = await selectSkills(TOTAL);
|
|
571
|
-
step(6, TOTAL, "\u786E\u8BA4");
|
|
572
|
-
const actions = [];
|
|
573
|
-
if (components.installCli && !env.claudeInstalled) actions.push("\u5B89\u88C5 Claude CLI");
|
|
574
|
-
if (scaffoldOpts) actions.push(`\u751F\u6210\u914D\u7F6E (${scaffoldOpts.style})`);
|
|
575
|
-
if (mcpServers.length > 0) actions.push(`${mcpServers.length} \u4E2A MCP`);
|
|
576
|
-
if (skills.length > 0) actions.push(`${skills.length} \u4E2A Skills`);
|
|
577
|
-
if (components.cleanEnv) actions.push(`\u6E05\u7406 ${env.sensitiveEnvVars.length} \u4E2A\u53D8\u91CF`);
|
|
578
|
-
if (actions.length === 0) {
|
|
579
|
-
info("\u65E0\u64CD\u4F5C");
|
|
580
|
-
return;
|
|
581
|
-
}
|
|
582
|
-
console.log(pc3.dim(` \u2192 ${actions.join(" \xB7 ")}`));
|
|
583
|
-
const go = await confirm2({ message: "\u5F00\u59CB\u6267\u884C?", default: true });
|
|
584
|
-
if (!go) {
|
|
585
|
-
info("\u5DF2\u53D6\u6D88");
|
|
586
|
-
process.exit(0);
|
|
587
|
-
}
|
|
588
|
-
step(7, TOTAL, "\u6267\u884C\u5B89\u88C5");
|
|
589
|
-
if (components.installCli) {
|
|
590
|
-
const r = await installCli(env.claudeInstalled);
|
|
591
|
-
results.push({ label: r.skipped ? "Claude CLI (\u5DF2\u5B89\u88C5)" : "Claude CLI", ok: r.success });
|
|
592
|
-
}
|
|
593
|
-
if (scaffoldOpts) {
|
|
594
|
-
await scaffold(process.cwd(), scaffoldOpts);
|
|
595
|
-
results.push({ label: "\u914D\u7F6E\u6587\u4EF6", ok: true });
|
|
596
|
-
}
|
|
597
|
-
if (mcpServers.length > 0) {
|
|
598
|
-
const r = await installMcp(mcpServers);
|
|
599
|
-
r.installed.forEach((n) => results.push({ label: `${n} MCP`, ok: true }));
|
|
600
|
-
r.failed.forEach((n) => results.push({ label: `${n} MCP`, ok: false }));
|
|
601
|
-
}
|
|
602
|
-
if (skills.length > 0) {
|
|
603
|
-
const r = await installSkills(skills);
|
|
604
|
-
r.installed.forEach((n) => results.push({ label: `${n} Skill`, ok: true }));
|
|
605
|
-
r.failed.forEach((n) => results.push({ label: `${n} Skill`, ok: false }));
|
|
606
|
-
}
|
|
607
|
-
if (components.cleanEnv) {
|
|
608
|
-
const r = await cleanEnv(env.sensitiveEnvVars, TOTAL);
|
|
609
|
-
r.removed.forEach((k) => results.push({ label: `${k} \u5DF2\u79FB\u9664`, ok: true }));
|
|
610
|
-
r.skipped.forEach((k) => results.push({ label: `${k} \u9700\u624B\u52A8`, ok: false }));
|
|
611
|
-
}
|
|
612
|
-
done(results);
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
// src/index.ts
|
|
616
|
-
var require2 = createRequire(import.meta.url);
|
|
617
|
-
var { version } = require2("../package.json");
|
|
618
|
-
async function main() {
|
|
619
|
-
banner(version);
|
|
620
|
-
try {
|
|
621
|
-
await runCli();
|
|
622
|
-
} catch (err) {
|
|
623
|
-
if (err instanceof Error && err.message.includes("User force closed")) {
|
|
624
|
-
console.log("\n \u5DF2\u9000\u51FA");
|
|
625
|
-
process.exit(0);
|
|
626
|
-
}
|
|
627
|
-
console.error(err);
|
|
628
|
-
process.exit(1);
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
main();
|