ccbot-cli 1.2.1 → 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 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 };
@@ -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 };
@@ -0,0 +1,11 @@
1
+ node_modules/
2
+ dist/
3
+ .env
4
+ .env.*
5
+ *.log
6
+ coverage/
7
+ .DS_Store
8
+ Thumbs.db
9
+ *.secret
10
+ *.pem
11
+ *.key
@@ -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": "1.2.1",
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
- "dependencies": {
31
- "@inquirer/prompts": "^8.3.0",
32
- "execa": "^9.5.2",
33
- "picocolors": "^1.1.1"
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
- "devDependencies": {
36
- "@types/node": "^22.10.0",
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,630 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/index.ts
4
- import { createRequire } from "module";
5
-
6
- // src/utils/logger.ts
7
- import pc from "picocolors";
8
- function step(n, total, title) {
9
- console.log(pc.cyan(`
10
- [${n}/${total}] `) + pc.bold(title));
11
- }
12
- function hint(msg) {
13
- console.log(pc.dim(` ${msg}`));
14
- }
15
- function ok(msg) {
16
- console.log(pc.green(" \u2713 ") + msg);
17
- }
18
- function fail(msg) {
19
- console.log(pc.red(" \u2717 ") + msg);
20
- }
21
- function warn(msg) {
22
- console.log(pc.yellow(" \u26A0 ") + msg);
23
- }
24
- function info(msg) {
25
- console.log(pc.cyan(" \u2139 ") + msg);
26
- }
27
- function banner(version2) {
28
- console.log();
29
- console.log(pc.cyan(" \u{1F916} ccbot") + pc.dim(` v${version2}`) + pc.cyan(" \u2014 Claude Code \u73AF\u5883\u4E00\u952E\u914D\u7F6E"));
30
- console.log();
31
- }
32
- function done(items) {
33
- const okCount = items.filter((i) => i.ok).length;
34
- const failCount = items.filter((i) => !i.ok).length;
35
- console.log();
36
- if (failCount === 0) {
37
- console.log(pc.green(pc.bold(" \u2713 \u5168\u90E8\u5B8C\u6210!")));
38
- } else {
39
- console.log(pc.yellow(` \u26A0 \u5B8C\u6210 ${okCount} \u9879, ${failCount} \u9879\u9700\u6CE8\u610F`));
40
- }
41
- console.log();
42
- for (const item of items) {
43
- if (item.ok) ok(item.label);
44
- else fail(item.label);
45
- }
46
- console.log();
47
- console.log(pc.dim(" \u4E0B\u4E00\u6B65:"));
48
- console.log(pc.dim(" 1. ") + pc.green("claude") + pc.dim(" \u542F\u52A8 Claude Code"));
49
- console.log(pc.dim(" 2. \u7F16\u8F91 ") + pc.green("CLAUDE.md") + pc.dim(" \u81EA\u5B9A\u4E49\u6307\u4EE4"));
50
- console.log();
51
- }
52
-
53
- // src/cli.ts
54
- import { confirm as confirm2 } from "@inquirer/prompts";
55
- import pc3 from "picocolors";
56
-
57
- // src/utils/exec.ts
58
- import { execa } from "execa";
59
- async function run(command, args = [], options) {
60
- try {
61
- const result = await execa(command, args, {
62
- ...options,
63
- reject: false
64
- });
65
- return {
66
- stdout: result.stdout?.toString() ?? "",
67
- stderr: result.stderr?.toString() ?? "",
68
- exitCode: result.exitCode ?? 0
69
- };
70
- } catch {
71
- return { stdout: "", stderr: "Command not found or failed to execute", exitCode: 127 };
72
- }
73
- }
74
- async function commandExists(command) {
75
- const cmd = process.platform === "win32" ? "where" : "which";
76
- const result = await run(cmd, [command]);
77
- return result.exitCode === 0;
78
- }
79
- async function npmInstallGlobal(pkg) {
80
- return run("npm", ["install", "-g", pkg]);
81
- }
82
-
83
- // src/steps/detect.ts
84
- var SENSITIVE_PATTERNS = [
85
- /^ANTHROPIC_API_KEY$/i,
86
- /^CLAUDE_API_KEY$/i,
87
- /^OPENAI_API_KEY$/i,
88
- /^GOOGLE_API_KEY$/i,
89
- /^AZURE_OPENAI_API_KEY$/i,
90
- /^HUGGINGFACE_TOKEN$/i,
91
- /^HF_TOKEN$/i,
92
- /^COHERE_API_KEY$/i,
93
- /^MISTRAL_API_KEY$/i,
94
- /^DEEPSEEK_API_KEY$/i,
95
- /^GROQ_API_KEY$/i,
96
- /_SECRET$/i,
97
- /_TOKEN$/i,
98
- /_API_KEY$/i
99
- ];
100
- async function detect(total) {
101
- step(1, total, "\u73AF\u5883\u68C0\u6D4B");
102
- const nodeVersion = process.version;
103
- const npmResult = await run("npm", ["--version"]);
104
- const npmVersion = npmResult.stdout.trim();
105
- const osMap = { win32: "Windows", darwin: "macOS", linux: "Linux" };
106
- const os = osMap[process.platform] ?? process.platform;
107
- const claudeInstalled = await commandExists("claude");
108
- let claudeVersion = null;
109
- if (claudeInstalled) {
110
- const r = await run("claude", ["--version"]);
111
- claudeVersion = r.stdout.trim();
112
- }
113
- const sensitiveEnvVars = [];
114
- for (const [key, value] of Object.entries(process.env)) {
115
- if (!value) continue;
116
- if (SENSITIVE_PATTERNS.some((p) => p.test(key))) {
117
- sensitiveEnvVars.push({ key, value });
118
- }
119
- }
120
- ok(`Node ${nodeVersion} \xB7 npm v${npmVersion} \xB7 ${os}`);
121
- if (claudeInstalled) ok(`Claude CLI ${claudeVersion}`);
122
- else warn("Claude CLI \u672A\u5B89\u88C5");
123
- if (sensitiveEnvVars.length > 0) {
124
- warn(`\u53D1\u73B0 ${sensitiveEnvVars.length} \u4E2A\u654F\u611F\u73AF\u5883\u53D8\u91CF`);
125
- }
126
- return { nodeVersion, npmVersion, os, platform: process.platform, claudeInstalled, claudeVersion, sensitiveEnvVars };
127
- }
128
-
129
- // src/steps/select.ts
130
- import { checkbox } from "@inquirer/prompts";
131
- async function selectComponents(env, total) {
132
- step(2, total, "\u9009\u62E9\u7EC4\u4EF6");
133
- hint("\u7A7A\u683C=\u5207\u6362 a=\u5168\u9009 \u56DE\u8F66=\u786E\u8BA4");
134
- const choices = [
135
- {
136
- value: "installCli",
137
- name: `Claude Code CLI ${env.claudeInstalled ? "(\u5DF2\u5B89\u88C5, \u8DF3\u8FC7)" : "(\u672A\u5B89\u88C5)"}`,
138
- checked: true
139
- },
140
- { value: "scaffold", name: "\u9879\u76EE\u914D\u7F6E\u6587\u4EF6 (CLAUDE.md + .claude/)", checked: true },
141
- { value: "installMcp", name: "MCP Servers (AI\u5DE5\u5177\u670D\u52A1)", checked: true },
142
- { value: "installSkills", name: "Skills / Plugins (\u5DE5\u4F5C\u6D41\u589E\u5F3A)", checked: true }
143
- ];
144
- if (env.sensitiveEnvVars.length > 0) {
145
- choices.push({
146
- value: "cleanEnv",
147
- name: `\u73AF\u5883\u53D8\u91CF\u6E05\u7406 (${env.sensitiveEnvVars.length} \u4E2A\u654F\u611F\u53D8\u91CF)`,
148
- checked: true
149
- });
150
- }
151
- const selected = await checkbox({
152
- message: "\u5B89\u88C5\u7EC4\u4EF6",
153
- choices,
154
- required: true
155
- });
156
- return {
157
- installCli: selected.includes("installCli"),
158
- scaffold: selected.includes("scaffold"),
159
- installMcp: selected.includes("installMcp"),
160
- installSkills: selected.includes("installSkills"),
161
- cleanEnv: selected.includes("cleanEnv")
162
- };
163
- }
164
-
165
- // src/steps/install-cli.ts
166
- async function installCli(alreadyInstalled) {
167
- if (alreadyInstalled) {
168
- info("Claude CLI \u5DF2\u5B89\u88C5, \u8DF3\u8FC7");
169
- return { success: true, version: null, skipped: true };
170
- }
171
- info("\u6B63\u5728\u5B89\u88C5 Claude Code CLI...");
172
- const result = await npmInstallGlobal("@anthropic-ai/claude-code");
173
- if (result.exitCode !== 0) {
174
- fail("Claude CLI \u5B89\u88C5\u5931\u8D25");
175
- warn("\u624B\u52A8: npm install -g @anthropic-ai/claude-code");
176
- return { success: false, version: null, skipped: false };
177
- }
178
- const exists = await commandExists("claude");
179
- if (!exists) {
180
- warn("\u5B89\u88C5\u5B8C\u6210\u4F46 claude \u547D\u4EE4\u4E0D\u53EF\u7528, \u53EF\u80FD\u9700\u91CD\u542F\u7EC8\u7AEF");
181
- return { success: false, version: null, skipped: false };
182
- }
183
- const v = await run("claude", ["--version"]);
184
- const version2 = v.stdout.trim();
185
- ok(`Claude CLI ${version2}`);
186
- return { success: true, version: version2, skipped: false };
187
- }
188
-
189
- // src/steps/scaffold.ts
190
- import { select } from "@inquirer/prompts";
191
- import { join as join2 } from "path";
192
-
193
- // src/utils/fs.ts
194
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
195
- import { dirname } from "path";
196
- function ensureDir(dir) {
197
- if (!existsSync(dir)) {
198
- mkdirSync(dir, { recursive: true });
199
- }
200
- }
201
- function writeFileSafe(filePath, content, overwrite = false) {
202
- ensureDir(dirname(filePath));
203
- if (existsSync(filePath) && !overwrite) {
204
- return { written: false, skipped: true };
205
- }
206
- writeFileSync(filePath, content, "utf-8");
207
- return { written: true, skipped: false };
208
- }
209
- function readJsonFile(filePath) {
210
- try {
211
- const raw = readFileSync(filePath, "utf-8");
212
- return JSON.parse(raw);
213
- } catch {
214
- return null;
215
- }
216
- }
217
- function mergeJsonFile(filePath, patch) {
218
- const existing = readJsonFile(filePath) ?? {};
219
- const merged = deepMerge(existing, patch);
220
- writeFileSync(filePath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
221
- }
222
- function deepMerge(target, source) {
223
- const result = { ...target };
224
- for (const key of Object.keys(source)) {
225
- if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key]) && target[key] && typeof target[key] === "object" && !Array.isArray(target[key])) {
226
- result[key] = deepMerge(
227
- target[key],
228
- source[key]
229
- );
230
- } else {
231
- result[key] = source[key];
232
- }
233
- }
234
- return result;
235
- }
236
-
237
- // src/steps/scaffold.ts
238
- var TEMPLATES = {
239
- minimal: `# Project Instructions
240
-
241
- - Follow existing code style
242
- - Read before write
243
- - Keep changes minimal
244
- `,
245
- standard: `# Project Instructions
246
-
247
- ## Overview
248
- <!-- Project purpose and tech stack -->
249
-
250
- ## Coding Standards
251
- - Follow existing patterns
252
- - Read context before modifying
253
- - Keep changes focused
254
-
255
- ## Key Files
256
- <!-- Important file paths -->
257
-
258
- ## Notes
259
- <!-- Special considerations -->
260
- `,
261
- detailed: `# Project Instructions
262
-
263
- ## Overview
264
- <!-- Project purpose, goals, tech stack -->
265
-
266
- ## Architecture
267
- <!-- High-level design decisions -->
268
-
269
- ## Coding Standards
270
- - Follow existing patterns
271
- - Read context before modifying
272
- - Write clear commit messages
273
- - Add comments for complex logic
274
-
275
- ## Key Files
276
- <!-- Important paths and purposes -->
277
-
278
- ## Dependencies
279
- <!-- Key deps and roles -->
280
-
281
- ## Testing
282
- <!-- How to run tests -->
283
-
284
- ## Notes
285
- <!-- Gotchas and context -->
286
- `
287
- };
288
- var CLAUDEIGNORE = `node_modules/
289
- dist/
290
- build/
291
- .env
292
- .env.*
293
- *.log
294
- .DS_Store
295
- coverage/
296
- .git/
297
- `;
298
- async function scaffoldConfig(total) {
299
- step(3, total, "CLAUDE.md \u6A21\u677F");
300
- const style = await select({
301
- message: "\u9009\u62E9\u6A21\u677F\u98CE\u683C",
302
- choices: [
303
- { value: "minimal", name: "\u6781\u7B80 \u2014 \u51E0\u884C\u6838\u5FC3\u89C4\u5219" },
304
- { value: "standard", name: "\u6807\u51C6 \u2014 \u5E38\u7528\u5206\u533A (\u63A8\u8350)" },
305
- { value: "detailed", name: "\u8BE6\u7EC6 \u2014 \u542B\u67B6\u6784/\u6D4B\u8BD5/\u4F9D\u8D56" }
306
- ],
307
- default: "standard"
308
- });
309
- return { style };
310
- }
311
- async function scaffold(targetDir, opts) {
312
- const files = [];
313
- const r1 = writeFileSafe(join2(targetDir, "CLAUDE.md"), TEMPLATES[opts.style]);
314
- files.push({ path: "CLAUDE.md", written: r1.written });
315
- ensureDir(join2(targetDir, ".claude"));
316
- const settings = { permissions: { allow: ["Read", "Glob", "Grep", "WebFetch", "WebSearch"], deny: [] }, mcpServers: {} };
317
- const r2 = writeFileSafe(join2(targetDir, ".claude", "settings.json"), JSON.stringify(settings, null, 2) + "\n");
318
- files.push({ path: ".claude/settings.json", written: r2.written });
319
- const r3 = writeFileSafe(join2(targetDir, ".claudeignore"), CLAUDEIGNORE);
320
- files.push({ path: ".claudeignore", written: r3.written });
321
- for (const f of files) {
322
- if (f.written) ok(f.path);
323
- else info(`${f.path} (\u5DF2\u5B58\u5728, \u8DF3\u8FC7)`);
324
- }
325
- return { files };
326
- }
327
-
328
- // src/steps/install-mcp.ts
329
- import { checkbox as checkbox2, confirm, input } from "@inquirer/prompts";
330
- import { join as join3 } from "path";
331
- import { homedir } from "os";
332
-
333
- // src/registry/mcp-servers.ts
334
- var MCP_SERVERS = [
335
- {
336
- name: "playwright",
337
- package: "@anthropic-ai/mcp-playwright",
338
- description: "\u6D4F\u89C8\u5668\u81EA\u52A8\u5316 (\u622A\u56FE\u3001\u70B9\u51FB\u3001\u8868\u5355\u586B\u5199)",
339
- scope: "user",
340
- command: "npx",
341
- args: ["-y", "@anthropic-ai/mcp-playwright"]
342
- },
343
- {
344
- name: "filesystem",
345
- package: "@modelcontextprotocol/server-filesystem",
346
- description: "\u6587\u4EF6\u7CFB\u7EDF\u8BFB\u5199\u8BBF\u95EE",
347
- scope: "project",
348
- command: "npx",
349
- args: ["-y", "@modelcontextprotocol/server-filesystem", "."]
350
- },
351
- {
352
- name: "context7",
353
- package: "@anthropic-ai/mcp-context7",
354
- description: "\u7F16\u7A0B\u6587\u6863\u5B9E\u65F6\u67E5\u8BE2",
355
- scope: "user",
356
- command: "npx",
357
- args: ["-y", "@upstash/context7-mcp"]
358
- },
359
- {
360
- name: "deepwiki",
361
- package: "@anthropic-ai/mcp-deepwiki",
362
- description: "GitHub \u4ED3\u5E93 Wiki \u77E5\u8BC6\u5E93",
363
- scope: "user",
364
- command: "npx",
365
- args: ["-y", "@anthropic-ai/mcp-deepwiki"]
366
- },
367
- {
368
- name: "open-websearch",
369
- package: "open-websearch-mcp",
370
- description: "\u591A\u5F15\u64CE\u7F51\u9875\u641C\u7D22 (DuckDuckGo/Bing/Brave)",
371
- scope: "user",
372
- command: "npx",
373
- args: ["-y", "open-websearch-mcp"]
374
- },
375
- {
376
- name: "memory",
377
- package: "@modelcontextprotocol/server-memory",
378
- description: "\u6301\u4E45\u5316\u8BB0\u5FC6\u5B58\u50A8",
379
- scope: "user",
380
- command: "npx",
381
- args: ["-y", "@modelcontextprotocol/server-memory"]
382
- },
383
- {
384
- name: "sequential-thinking",
385
- package: "@modelcontextprotocol/server-sequential-thinking",
386
- description: "\u94FE\u5F0F\u601D\u8003\u63A8\u7406",
387
- scope: "user",
388
- command: "npx",
389
- args: ["-y", "@modelcontextprotocol/server-sequential-thinking"]
390
- }
391
- ];
392
-
393
- // src/steps/install-mcp.ts
394
- async function selectMcpServers(total) {
395
- step(4, total, "MCP Servers");
396
- hint("\u7A7A\u683C=\u5207\u6362 a=\u5168\u9009 \u56DE\u8F66=\u786E\u8BA4");
397
- const selected = await checkbox2({
398
- message: "\u9009\u62E9 MCP Servers",
399
- choices: MCP_SERVERS.map((s) => ({
400
- value: s.name,
401
- name: `${s.name} \u2014 ${s.description}`,
402
- checked: true
403
- }))
404
- });
405
- const servers = MCP_SERVERS.filter((s) => selected.includes(s.name));
406
- const addCustom = await confirm({ message: "\u6DFB\u52A0\u81EA\u5B9A\u4E49 MCP (npm\u5305)?", default: false });
407
- if (addCustom) {
408
- const raw = await input({
409
- message: "npm \u5305\u540D (\u9017\u53F7\u5206\u9694)"
410
- });
411
- if (raw.trim()) {
412
- for (const pkg of raw.split(",").map((s) => s.trim()).filter(Boolean)) {
413
- const name = pkg.split("/").pop()?.replace(/^mcp-/, "") ?? pkg;
414
- servers.push({ name, package: pkg, description: `\u81EA\u5B9A\u4E49: ${pkg}`, scope: "user", command: "npx", args: ["-y", pkg] });
415
- }
416
- }
417
- }
418
- return servers;
419
- }
420
- async function installMcp(servers) {
421
- if (servers.length === 0) return { installed: [], failed: [] };
422
- const installed = [];
423
- const failed = [];
424
- const userServers = servers.filter((s) => s.scope === "user");
425
- const projectServers = servers.filter((s) => s.scope === "project");
426
- if (userServers.length > 0) {
427
- const dir = join3(homedir(), ".claude");
428
- ensureDir(dir);
429
- const cfg = {};
430
- for (const s of userServers) {
431
- cfg[s.name] = { command: s.command, args: s.args, ...s.env ? { env: s.env } : {} };
432
- }
433
- try {
434
- mergeJsonFile(join3(dir, "settings.json"), { mcpServers: cfg });
435
- userServers.forEach((s) => installed.push(s.name));
436
- } catch {
437
- userServers.forEach((s) => failed.push(s.name));
438
- }
439
- }
440
- if (projectServers.length > 0) {
441
- const dir = join3(process.cwd(), ".claude");
442
- ensureDir(dir);
443
- const cfg = {};
444
- for (const s of projectServers) {
445
- cfg[s.name] = { command: s.command, args: s.args, ...s.env ? { env: s.env } : {} };
446
- }
447
- try {
448
- mergeJsonFile(join3(dir, "settings.json"), { mcpServers: cfg });
449
- projectServers.forEach((s) => installed.push(s.name));
450
- } catch {
451
- projectServers.forEach((s) => failed.push(s.name));
452
- }
453
- }
454
- for (const n of installed) ok(`${n} MCP`);
455
- for (const n of failed) fail(`${n} MCP`);
456
- return { installed, failed };
457
- }
458
-
459
- // src/steps/install-skills.ts
460
- import { checkbox as checkbox3 } from "@inquirer/prompts";
461
-
462
- // src/registry/skills.ts
463
- var SKILLS = [
464
- {
465
- name: "superpowers",
466
- description: "\u589E\u5F3A\u5DE5\u4F5C\u6D41 (\u5934\u8111\u98CE\u66B4\u3001TDD\u3001\u4EE3\u7801\u5BA1\u67E5\u3001\u8BA1\u5212)",
467
- source: "claude-plugins-official/superpowers",
468
- installCmd: ["claude", "plugins", "install", "superpowers"]
469
- },
470
- {
471
- name: "spec-workflow",
472
- description: "\u89C4\u8303\u5316\u5F00\u53D1\u6D41\u7A0B (\u9700\u6C42\u2192\u8BBE\u8BA1\u2192\u4EFB\u52A1\u2192\u5B9E\u73B0)",
473
- source: "spec-workflow",
474
- installCmd: ["claude", "plugins", "install", "spec-workflow"]
475
- },
476
- {
477
- name: "skill-creator",
478
- description: "Skill \u521B\u5EFA\u4E0E\u7BA1\u7406\u5DE5\u5177",
479
- source: "skill-creator",
480
- installCmd: ["claude", "plugins", "install", "skill-creator"]
481
- }
482
- ];
483
-
484
- // src/steps/install-skills.ts
485
- async function selectSkills(total) {
486
- step(5, total, "Skills / Plugins");
487
- hint("\u7A7A\u683C=\u5207\u6362 a=\u5168\u9009 \u56DE\u8F66=\u786E\u8BA4");
488
- const selected = await checkbox3({
489
- message: "\u9009\u62E9 Skills",
490
- choices: SKILLS.map((s) => ({
491
- value: s.name,
492
- name: `${s.name} \u2014 ${s.description}`,
493
- checked: true
494
- }))
495
- });
496
- return SKILLS.filter((s) => selected.includes(s.name));
497
- }
498
- async function installSkills(skills) {
499
- if (skills.length === 0) return { installed: [], failed: [] };
500
- const installed = [];
501
- const failed = [];
502
- for (const skill of skills) {
503
- const [cmd, ...args] = skill.installCmd;
504
- const result = await run(cmd, args);
505
- if (result.exitCode === 0) {
506
- installed.push(skill.name);
507
- ok(skill.name);
508
- } else {
509
- failed.push(skill.name);
510
- fail(skill.name);
511
- warn(` \u624B\u52A8: ${skill.installCmd.join(" ")}`);
512
- }
513
- }
514
- return { installed, failed };
515
- }
516
-
517
- // src/steps/clean-env.ts
518
- import { checkbox as checkbox4 } from "@inquirer/prompts";
519
- import pc2 from "picocolors";
520
- function mask(val) {
521
- if (val.length <= 8) return "****";
522
- return val.slice(0, 4) + "..." + val.slice(-4);
523
- }
524
- async function cleanEnv(vars, total) {
525
- if (vars.length === 0) return { removed: [], skipped: [] };
526
- step(6, total, "\u73AF\u5883\u53D8\u91CF\u6E05\u7406");
527
- hint("\u7A7A\u683C=\u5207\u6362 \u56DE\u8F66=\u786E\u8BA4 (\u4E0D\u9009\u5219\u5168\u90E8\u4FDD\u7559)");
528
- const selected = await checkbox4({
529
- message: "\u9009\u62E9\u8981\u79FB\u9664\u7684\u53D8\u91CF",
530
- choices: vars.map((v) => ({
531
- value: v.key,
532
- name: `${v.key} = ${pc2.dim(mask(v.value))}`,
533
- checked: false
534
- }))
535
- });
536
- if (selected.length === 0) return { removed: [], skipped: vars.map((v) => v.key) };
537
- const removed = [];
538
- const skipped = [];
539
- for (const key of selected) {
540
- if (process.platform === "win32") {
541
- const r = await run("powershell", ["-Command", `[Environment]::SetEnvironmentVariable('${key}', $null, 'User')`]);
542
- if (r.exitCode === 0) {
543
- removed.push(key);
544
- ok(`${key} \u5DF2\u79FB\u9664`);
545
- } else {
546
- skipped.push(key);
547
- warn(`${key} \u79FB\u9664\u5931\u8D25`);
548
- }
549
- } else {
550
- delete process.env[key];
551
- skipped.push(key);
552
- warn(`${key} \u9700\u624B\u52A8\u4ECE ~/.bashrc \u6216 ~/.zshrc \u79FB\u9664`);
553
- }
554
- }
555
- return { removed, skipped };
556
- }
557
-
558
- // src/cli.ts
559
- var TOTAL = 7;
560
- async function runCli() {
561
- const results = [];
562
- const env = await detect(TOTAL);
563
- const components = await selectComponents(env, TOTAL);
564
- let scaffoldOpts = null;
565
- if (components.scaffold) scaffoldOpts = await scaffoldConfig(TOTAL);
566
- let mcpServers = [];
567
- if (components.installMcp) mcpServers = await selectMcpServers(TOTAL);
568
- let skills = [];
569
- if (components.installSkills) skills = await selectSkills(TOTAL);
570
- step(6, TOTAL, "\u786E\u8BA4");
571
- const actions = [];
572
- if (components.installCli && !env.claudeInstalled) actions.push("\u5B89\u88C5 Claude CLI");
573
- if (scaffoldOpts) actions.push(`\u751F\u6210\u914D\u7F6E (${scaffoldOpts.style})`);
574
- if (mcpServers.length > 0) actions.push(`${mcpServers.length} \u4E2A MCP`);
575
- if (skills.length > 0) actions.push(`${skills.length} \u4E2A Skills`);
576
- if (components.cleanEnv) actions.push(`\u6E05\u7406 ${env.sensitiveEnvVars.length} \u4E2A\u53D8\u91CF`);
577
- if (actions.length === 0) {
578
- info("\u65E0\u64CD\u4F5C");
579
- return;
580
- }
581
- console.log(pc3.dim(` \u2192 ${actions.join(" \xB7 ")}`));
582
- const go = await confirm2({ message: "\u5F00\u59CB\u6267\u884C?", default: true });
583
- if (!go) {
584
- info("\u5DF2\u53D6\u6D88");
585
- process.exit(0);
586
- }
587
- step(7, TOTAL, "\u6267\u884C\u5B89\u88C5");
588
- if (components.installCli) {
589
- const r = await installCli(env.claudeInstalled);
590
- results.push({ label: r.skipped ? "Claude CLI (\u5DF2\u5B89\u88C5)" : "Claude CLI", ok: r.success });
591
- }
592
- if (scaffoldOpts) {
593
- await scaffold(process.cwd(), scaffoldOpts);
594
- results.push({ label: "\u914D\u7F6E\u6587\u4EF6", ok: true });
595
- }
596
- if (mcpServers.length > 0) {
597
- const r = await installMcp(mcpServers);
598
- r.installed.forEach((n) => results.push({ label: `${n} MCP`, ok: true }));
599
- r.failed.forEach((n) => results.push({ label: `${n} MCP`, ok: false }));
600
- }
601
- if (skills.length > 0) {
602
- const r = await installSkills(skills);
603
- r.installed.forEach((n) => results.push({ label: `${n} Skill`, ok: true }));
604
- r.failed.forEach((n) => results.push({ label: `${n} Skill`, ok: false }));
605
- }
606
- if (components.cleanEnv) {
607
- const r = await cleanEnv(env.sensitiveEnvVars, TOTAL);
608
- r.removed.forEach((k) => results.push({ label: `${k} \u5DF2\u79FB\u9664`, ok: true }));
609
- r.skipped.forEach((k) => results.push({ label: `${k} \u9700\u624B\u52A8`, ok: false }));
610
- }
611
- done(results);
612
- }
613
-
614
- // src/index.ts
615
- var require2 = createRequire(import.meta.url);
616
- var { version } = require2("../package.json");
617
- async function main() {
618
- banner(version);
619
- try {
620
- await runCli();
621
- } catch (err) {
622
- if (err instanceof Error && err.message.includes("User force closed")) {
623
- console.log("\n \u5DF2\u9000\u51FA");
624
- process.exit(0);
625
- }
626
- console.error(err);
627
- process.exit(1);
628
- }
629
- }
630
- main();