ccbot-cli 1.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.
Files changed (2) hide show
  1. package/dist/index.js +747 -0
  2. package/package.json +40 -0
package/dist/index.js ADDED
@@ -0,0 +1,747 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/utils/logger.ts
4
+ import pc from "picocolors";
5
+ var log = {
6
+ info: (msg) => console.log(pc.cyan(" \u2139 ") + msg),
7
+ success: (msg) => console.log(pc.green(" \u2713 ") + msg),
8
+ warn: (msg) => console.log(pc.yellow(" \u26A0 ") + msg),
9
+ error: (msg) => console.log(pc.red(" \u2717 ") + msg),
10
+ step: (msg) => console.log(pc.blue(" \u2192 ") + msg),
11
+ dim: (msg) => console.log(pc.dim(" " + msg))
12
+ };
13
+ function banner(version) {
14
+ console.log();
15
+ console.log(pc.cyan(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
16
+ console.log(pc.cyan(" \u2502 \u2502"));
17
+ console.log(pc.cyan(" \u2502") + " \u{1F916} ccbot - Claude Code \u73AF\u5883\u914D\u7F6E " + pc.cyan("\u2502"));
18
+ console.log(pc.cyan(" \u2502") + pc.dim(` \u4E00\u952E\u914D\u7F6E\u5DE5\u5177 v${version}`) + " " + pc.cyan("\u2502"));
19
+ console.log(pc.cyan(" \u2502 \u2502"));
20
+ console.log(pc.cyan(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
21
+ console.log();
22
+ }
23
+ function summary(items) {
24
+ console.log();
25
+ console.log(pc.cyan(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
26
+ console.log(pc.cyan(" \u2502") + pc.bold(" \u5B89\u88C5\u5B8C\u6210\uFF01") + " " + pc.cyan("\u2502"));
27
+ console.log(pc.cyan(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
28
+ console.log();
29
+ for (const item of items) {
30
+ if (item.ok) {
31
+ log.success(item.label);
32
+ } else {
33
+ log.error(item.label);
34
+ }
35
+ }
36
+ console.log();
37
+ log.info("\u4E0B\u4E00\u6B65\uFF1A");
38
+ log.dim("1. \u8FD0\u884C claude \u542F\u52A8 Claude Code");
39
+ log.dim("2. \u7F16\u8F91 CLAUDE.md \u81EA\u5B9A\u4E49\u9879\u76EE\u6307\u4EE4");
40
+ console.log();
41
+ }
42
+
43
+ // src/cli.ts
44
+ import * as p8 from "@clack/prompts";
45
+ import pc7 from "picocolors";
46
+
47
+ // src/steps/detect.ts
48
+ import * as p from "@clack/prompts";
49
+ import pc2 from "picocolors";
50
+
51
+ // src/utils/exec.ts
52
+ import { execa } from "execa";
53
+ async function run(command, args = [], options) {
54
+ try {
55
+ const result = await execa(command, args, {
56
+ ...options,
57
+ reject: false
58
+ });
59
+ return {
60
+ stdout: result.stdout?.toString() ?? "",
61
+ stderr: result.stderr?.toString() ?? "",
62
+ exitCode: result.exitCode ?? 0
63
+ };
64
+ } catch {
65
+ return { stdout: "", stderr: "Command not found or failed to execute", exitCode: 127 };
66
+ }
67
+ }
68
+ async function commandExists(command) {
69
+ const cmd = process.platform === "win32" ? "where" : "which";
70
+ const result = await run(cmd, [command]);
71
+ return result.exitCode === 0;
72
+ }
73
+ async function npmInstallGlobal(pkg) {
74
+ return run("npm", ["install", "-g", pkg]);
75
+ }
76
+
77
+ // src/steps/detect.ts
78
+ var SENSITIVE_PATTERNS = [
79
+ /^ANTHROPIC_API_KEY$/i,
80
+ /^CLAUDE_API_KEY$/i,
81
+ /^OPENAI_API_KEY$/i,
82
+ /^OPENAI_ORG_ID$/i,
83
+ /^GOOGLE_API_KEY$/i,
84
+ /^AZURE_OPENAI_API_KEY$/i,
85
+ /^HUGGINGFACE_TOKEN$/i,
86
+ /^HF_TOKEN$/i,
87
+ /^COHERE_API_KEY$/i,
88
+ /^MISTRAL_API_KEY$/i,
89
+ /^DEEPSEEK_API_KEY$/i,
90
+ /^GROQ_API_KEY$/i,
91
+ /_SECRET$/i,
92
+ /_TOKEN$/i,
93
+ /_API_KEY$/i
94
+ ];
95
+ function maskValue(val) {
96
+ if (val.length <= 8) return "****";
97
+ return val.slice(0, 4) + "..." + val.slice(-4);
98
+ }
99
+ function scanSensitiveEnvVars() {
100
+ const found = [];
101
+ for (const [key, value] of Object.entries(process.env)) {
102
+ if (!value) continue;
103
+ for (const pattern of SENSITIVE_PATTERNS) {
104
+ if (pattern.test(key)) {
105
+ found.push({ key, value });
106
+ break;
107
+ }
108
+ }
109
+ }
110
+ return found;
111
+ }
112
+ async function detect() {
113
+ const s = p.spinner();
114
+ s.start("\u6B63\u5728\u68C0\u6D4B\u73AF\u5883...");
115
+ const nodeVersion = process.version;
116
+ const npmResult = await run("npm", ["--version"]);
117
+ const npmVersion = npmResult.stdout.trim();
118
+ const osMap = {
119
+ win32: "Windows",
120
+ darwin: "macOS",
121
+ linux: "Linux"
122
+ };
123
+ const os = osMap[process.platform] ?? process.platform;
124
+ const claudeInstalled = await commandExists("claude");
125
+ let claudeVersion = null;
126
+ if (claudeInstalled) {
127
+ const result = await run("claude", ["--version"]);
128
+ claudeVersion = result.stdout.trim();
129
+ }
130
+ const sensitiveEnvVars = scanSensitiveEnvVars();
131
+ s.stop("\u73AF\u5883\u68C0\u6D4B\u5B8C\u6210");
132
+ p.log.success(`Node.js ${nodeVersion}`);
133
+ p.log.success(`npm v${npmVersion}`);
134
+ p.log.success(os);
135
+ if (claudeInstalled) {
136
+ p.log.success(`Claude Code CLI ${claudeVersion}`);
137
+ } else {
138
+ p.log.warning("Claude Code CLI \u672A\u5B89\u88C5");
139
+ }
140
+ if (sensitiveEnvVars.length > 0) {
141
+ p.log.warning("\u68C0\u6D4B\u5230\u654F\u611F\u73AF\u5883\u53D8\u91CF:");
142
+ for (const { key, value } of sensitiveEnvVars) {
143
+ console.log(pc2.yellow(` ${key}=${maskValue(value)}`));
144
+ }
145
+ }
146
+ return {
147
+ nodeVersion,
148
+ npmVersion,
149
+ os,
150
+ platform: process.platform,
151
+ claudeInstalled,
152
+ claudeVersion,
153
+ sensitiveEnvVars
154
+ };
155
+ }
156
+
157
+ // src/steps/select.ts
158
+ import * as p2 from "@clack/prompts";
159
+ import pc3 from "picocolors";
160
+ function printMultiselectHint() {
161
+ console.log();
162
+ console.log(pc3.dim(" \u64CD\u4F5C\u6307\u5357:"));
163
+ console.log(pc3.dim(" \u2191/\u2193 \u4E0A\u4E0B\u79FB\u52A8\u5149\u6807"));
164
+ console.log(pc3.dim(" \u7A7A\u683C \u9009\u4E2D/\u53D6\u6D88\u9009\u4E2D\u5F53\u524D\u9879"));
165
+ console.log(pc3.dim(" a \u5168\u9009/\u5168\u4E0D\u9009"));
166
+ console.log(pc3.dim(" \u56DE\u8F66 \u786E\u8BA4\u9009\u62E9"));
167
+ console.log();
168
+ }
169
+ async function selectComponents(env) {
170
+ const options = [
171
+ {
172
+ value: "installCli",
173
+ label: "Claude Code CLI \u5B89\u88C5",
174
+ hint: env.claudeInstalled ? "\u5DF2\u5B89\u88C5\uFF0C\u5C06\u8DF3\u8FC7" : "\u672A\u5B89\u88C5"
175
+ },
176
+ { value: "scaffold", label: "\u9879\u76EE\u914D\u7F6E\u811A\u624B\u67B6", hint: "CLAUDE.md + .claude/ \u914D\u7F6E" },
177
+ { value: "installMcp", label: "MCP Servers", hint: "\u4ECE\u9884\u8BBE\u5217\u8868\u9009\u62E9\u5B89\u88C5" },
178
+ { value: "installSkills", label: "Skills / Plugins", hint: "\u589E\u5F3A Claude Code \u80FD\u529B" }
179
+ ];
180
+ if (env.sensitiveEnvVars.length > 0) {
181
+ options.push({
182
+ value: "cleanEnv",
183
+ label: "\u73AF\u5883\u53D8\u91CF\u6E05\u7406",
184
+ hint: `\u68C0\u6D4B\u5230 ${env.sensitiveEnvVars.length} \u4E2A\u654F\u611F\u53D8\u91CF`
185
+ });
186
+ }
187
+ printMultiselectHint();
188
+ const selected = await p2.multiselect({
189
+ message: "\u9009\u62E9\u8981\u5B89\u88C5\u7684\u7EC4\u4EF6 (\u7A7A\u683C\u5207\u6362, \u56DE\u8F66\u786E\u8BA4)",
190
+ options,
191
+ initialValues: options.map((o) => o.value),
192
+ required: true
193
+ });
194
+ if (p2.isCancel(selected)) {
195
+ p2.cancel("\u5DF2\u53D6\u6D88");
196
+ process.exit(0);
197
+ }
198
+ const values = selected;
199
+ return {
200
+ installCli: values.includes("installCli"),
201
+ scaffold: values.includes("scaffold"),
202
+ installMcp: values.includes("installMcp"),
203
+ installSkills: values.includes("installSkills"),
204
+ cleanEnv: values.includes("cleanEnv")
205
+ };
206
+ }
207
+
208
+ // src/steps/install-cli.ts
209
+ import * as p3 from "@clack/prompts";
210
+ async function installCli(alreadyInstalled) {
211
+ if (alreadyInstalled) {
212
+ p3.log.info("Claude Code CLI \u5DF2\u5B89\u88C5\uFF0C\u8DF3\u8FC7");
213
+ return { success: true, version: null, skipped: true };
214
+ }
215
+ const s = p3.spinner();
216
+ s.start("\u6B63\u5728\u5B89\u88C5 Claude Code CLI...");
217
+ const result = await npmInstallGlobal("@anthropic-ai/claude-code");
218
+ if (result.exitCode !== 0) {
219
+ s.stop("Claude Code CLI \u5B89\u88C5\u5931\u8D25");
220
+ p3.log.error(result.stderr || "\u5B89\u88C5\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u8FD0\u884C: npm install -g @anthropic-ai/claude-code");
221
+ return { success: false, version: null, skipped: false };
222
+ }
223
+ const exists = await commandExists("claude");
224
+ if (!exists) {
225
+ s.stop("Claude Code CLI \u5B89\u88C5\u5F02\u5E38");
226
+ p3.log.warning("\u5B89\u88C5\u5B8C\u6210\u4F46 claude \u547D\u4EE4\u4E0D\u53EF\u7528\uFF0C\u53EF\u80FD\u9700\u8981\u91CD\u542F\u7EC8\u7AEF");
227
+ return { success: false, version: null, skipped: false };
228
+ }
229
+ const versionResult = await run("claude", ["--version"]);
230
+ const version = versionResult.stdout.trim();
231
+ s.stop(`Claude Code CLI \u5B89\u88C5\u5B8C\u6210 (${version})`);
232
+ return { success: true, version, skipped: false };
233
+ }
234
+
235
+ // src/steps/scaffold.ts
236
+ import * as p4 from "@clack/prompts";
237
+ import { join as join2 } from "path";
238
+
239
+ // src/utils/fs.ts
240
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
241
+ import { dirname } from "path";
242
+ function ensureDir(dir) {
243
+ if (!existsSync(dir)) {
244
+ mkdirSync(dir, { recursive: true });
245
+ }
246
+ }
247
+ function writeFileSafe(filePath, content, overwrite = false) {
248
+ ensureDir(dirname(filePath));
249
+ if (existsSync(filePath) && !overwrite) {
250
+ return { written: false, skipped: true };
251
+ }
252
+ writeFileSync(filePath, content, "utf-8");
253
+ return { written: true, skipped: false };
254
+ }
255
+ function readJsonFile(filePath) {
256
+ try {
257
+ const raw = readFileSync(filePath, "utf-8");
258
+ return JSON.parse(raw);
259
+ } catch {
260
+ return null;
261
+ }
262
+ }
263
+ function mergeJsonFile(filePath, patch) {
264
+ const existing = readJsonFile(filePath) ?? {};
265
+ const merged = deepMerge(existing, patch);
266
+ writeFileSync(filePath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
267
+ }
268
+ function deepMerge(target, source) {
269
+ const result = { ...target };
270
+ for (const key of Object.keys(source)) {
271
+ if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key]) && target[key] && typeof target[key] === "object" && !Array.isArray(target[key])) {
272
+ result[key] = deepMerge(
273
+ target[key],
274
+ source[key]
275
+ );
276
+ } else {
277
+ result[key] = source[key];
278
+ }
279
+ }
280
+ return result;
281
+ }
282
+
283
+ // src/steps/scaffold.ts
284
+ var CLAUDE_MD_TEMPLATE = `# Project Instructions
285
+
286
+ <!-- \u5728\u6B64\u7F16\u5199\u9879\u76EE\u7EA7 Claude Code \u6307\u4EE4 -->
287
+
288
+ ## \u9879\u76EE\u6982\u8FF0
289
+
290
+ <!-- \u63CF\u8FF0\u9879\u76EE\u7684\u76EE\u7684\u548C\u6280\u672F\u6808 -->
291
+
292
+ ## \u7F16\u7801\u89C4\u8303
293
+
294
+ - \u4F7F\u7528 TypeScript
295
+ - \u9075\u5FAA\u9879\u76EE\u73B0\u6709\u4EE3\u7801\u98CE\u683C
296
+ - \u5148\u8BFB\u540E\u5199\uFF0C\u7406\u89E3\u4E0A\u4E0B\u6587\u518D\u4FEE\u6539
297
+
298
+ ## \u91CD\u8981\u6587\u4EF6
299
+
300
+ <!-- \u5217\u51FA\u5173\u952E\u6587\u4EF6\u8DEF\u5F84 -->
301
+
302
+ ## \u6CE8\u610F\u4E8B\u9879
303
+
304
+ <!-- \u5217\u51FA\u9700\u8981\u7279\u522B\u6CE8\u610F\u7684\u4E8B\u9879 -->
305
+ `;
306
+ var SETTINGS_TEMPLATE = {
307
+ permissions: {
308
+ allow: [
309
+ "Read",
310
+ "Glob",
311
+ "Grep",
312
+ "WebFetch",
313
+ "WebSearch"
314
+ ],
315
+ deny: []
316
+ },
317
+ mcpServers: {}
318
+ };
319
+ var CLAUDEIGNORE_TEMPLATE = `# \u5FFD\u7565\u6587\u4EF6
320
+ node_modules/
321
+ dist/
322
+ .env
323
+ .env.*
324
+ *.log
325
+ .DS_Store
326
+ coverage/
327
+ `;
328
+ async function scaffold(targetDir) {
329
+ const s = p4.spinner();
330
+ s.start("\u6B63\u5728\u751F\u6210\u914D\u7F6E\u6587\u4EF6...");
331
+ const files = [];
332
+ const claudeMdPath = join2(targetDir, "CLAUDE.md");
333
+ const r1 = writeFileSafe(claudeMdPath, CLAUDE_MD_TEMPLATE);
334
+ files.push({ path: "CLAUDE.md", written: r1.written });
335
+ const claudeDir = join2(targetDir, ".claude");
336
+ ensureDir(claudeDir);
337
+ const settingsPath = join2(claudeDir, "settings.json");
338
+ const r2 = writeFileSafe(settingsPath, JSON.stringify(SETTINGS_TEMPLATE, null, 2) + "\n");
339
+ files.push({ path: ".claude/settings.json", written: r2.written });
340
+ const ignorePath = join2(targetDir, ".claudeignore");
341
+ const r3 = writeFileSafe(ignorePath, CLAUDEIGNORE_TEMPLATE);
342
+ files.push({ path: ".claudeignore", written: r3.written });
343
+ s.stop("\u914D\u7F6E\u6587\u4EF6\u751F\u6210\u5B8C\u6210");
344
+ for (const f of files) {
345
+ if (f.written) {
346
+ p4.log.success(`${f.path} \u5DF2\u751F\u6210`);
347
+ } else {
348
+ p4.log.info(`${f.path} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7`);
349
+ }
350
+ }
351
+ return { files };
352
+ }
353
+
354
+ // src/steps/install-mcp.ts
355
+ import * as p5 from "@clack/prompts";
356
+ import pc4 from "picocolors";
357
+ import { join as join3 } from "path";
358
+ import { homedir } from "os";
359
+
360
+ // src/registry/mcp-servers.ts
361
+ var MCP_SERVERS = [
362
+ {
363
+ name: "playwright",
364
+ package: "@anthropic-ai/mcp-playwright",
365
+ description: "\u6D4F\u89C8\u5668\u81EA\u52A8\u5316 (\u622A\u56FE\u3001\u70B9\u51FB\u3001\u8868\u5355\u586B\u5199)",
366
+ scope: "user",
367
+ command: "npx",
368
+ args: ["-y", "@anthropic-ai/mcp-playwright"]
369
+ },
370
+ {
371
+ name: "filesystem",
372
+ package: "@modelcontextprotocol/server-filesystem",
373
+ description: "\u6587\u4EF6\u7CFB\u7EDF\u8BFB\u5199\u8BBF\u95EE",
374
+ scope: "project",
375
+ command: "npx",
376
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "."]
377
+ },
378
+ {
379
+ name: "context7",
380
+ package: "@anthropic-ai/mcp-context7",
381
+ description: "\u7F16\u7A0B\u6587\u6863\u5B9E\u65F6\u67E5\u8BE2",
382
+ scope: "user",
383
+ command: "npx",
384
+ args: ["-y", "@upstash/context7-mcp"]
385
+ },
386
+ {
387
+ name: "deepwiki",
388
+ package: "@anthropic-ai/mcp-deepwiki",
389
+ description: "GitHub \u4ED3\u5E93 Wiki \u77E5\u8BC6\u5E93",
390
+ scope: "user",
391
+ command: "npx",
392
+ args: ["-y", "@anthropic-ai/mcp-deepwiki"]
393
+ },
394
+ {
395
+ name: "open-websearch",
396
+ package: "open-websearch-mcp",
397
+ description: "\u591A\u5F15\u64CE\u7F51\u9875\u641C\u7D22 (DuckDuckGo/Bing/Brave)",
398
+ scope: "user",
399
+ command: "npx",
400
+ args: ["-y", "open-websearch-mcp"]
401
+ },
402
+ {
403
+ name: "memory",
404
+ package: "@modelcontextprotocol/server-memory",
405
+ description: "\u6301\u4E45\u5316\u8BB0\u5FC6\u5B58\u50A8",
406
+ scope: "user",
407
+ command: "npx",
408
+ args: ["-y", "@modelcontextprotocol/server-memory"]
409
+ },
410
+ {
411
+ name: "sequential-thinking",
412
+ package: "@modelcontextprotocol/server-sequential-thinking",
413
+ description: "\u94FE\u5F0F\u601D\u8003\u63A8\u7406",
414
+ scope: "user",
415
+ command: "npx",
416
+ args: ["-y", "@modelcontextprotocol/server-sequential-thinking"]
417
+ }
418
+ ];
419
+
420
+ // src/steps/install-mcp.ts
421
+ async function selectMcpServers() {
422
+ console.log();
423
+ console.log(pc4.dim(" \u64CD\u4F5C\u6307\u5357:"));
424
+ console.log(pc4.dim(" \u2191/\u2193 \u4E0A\u4E0B\u79FB\u52A8\u5149\u6807"));
425
+ console.log(pc4.dim(" \u7A7A\u683C \u9009\u4E2D/\u53D6\u6D88\u9009\u4E2D\u5F53\u524D\u9879"));
426
+ console.log(pc4.dim(" a \u5168\u9009/\u5168\u4E0D\u9009"));
427
+ console.log(pc4.dim(" \u56DE\u8F66 \u786E\u8BA4\u9009\u62E9"));
428
+ console.log();
429
+ const selected = await p5.multiselect({
430
+ message: "\u9009\u62E9\u8981\u5B89\u88C5\u7684 MCP Servers (\u7A7A\u683C\u5207\u6362, \u56DE\u8F66\u786E\u8BA4)",
431
+ options: MCP_SERVERS.map((s) => ({
432
+ value: s.name,
433
+ label: s.name,
434
+ hint: s.description
435
+ })),
436
+ initialValues: MCP_SERVERS.map((s) => s.name),
437
+ required: false
438
+ });
439
+ if (p5.isCancel(selected)) {
440
+ return [];
441
+ }
442
+ const names = selected;
443
+ return MCP_SERVERS.filter((s) => names.includes(s.name));
444
+ }
445
+ async function installMcp(servers) {
446
+ if (servers.length === 0) {
447
+ return { installed: [], failed: [] };
448
+ }
449
+ const s = p5.spinner();
450
+ s.start("\u6B63\u5728\u914D\u7F6E MCP Servers...");
451
+ const installed = [];
452
+ const failed = [];
453
+ const userServers = servers.filter((sv) => sv.scope === "user");
454
+ const projectServers = servers.filter((sv) => sv.scope === "project");
455
+ if (userServers.length > 0) {
456
+ const userClaudeDir = join3(homedir(), ".claude");
457
+ ensureDir(userClaudeDir);
458
+ const userSettingsPath = join3(userClaudeDir, "settings.json");
459
+ const mcpConfig = {};
460
+ for (const sv of userServers) {
461
+ mcpConfig[sv.name] = {
462
+ command: sv.command,
463
+ args: sv.args,
464
+ ...sv.env ? { env: sv.env } : {}
465
+ };
466
+ }
467
+ try {
468
+ mergeJsonFile(userSettingsPath, { mcpServers: mcpConfig });
469
+ for (const sv of userServers) {
470
+ installed.push(sv.name);
471
+ }
472
+ } catch {
473
+ for (const sv of userServers) {
474
+ failed.push(sv.name);
475
+ }
476
+ }
477
+ }
478
+ if (projectServers.length > 0) {
479
+ const projectClaudeDir = join3(process.cwd(), ".claude");
480
+ ensureDir(projectClaudeDir);
481
+ const projectSettingsPath = join3(projectClaudeDir, "settings.json");
482
+ const mcpConfig = {};
483
+ for (const sv of projectServers) {
484
+ mcpConfig[sv.name] = {
485
+ command: sv.command,
486
+ args: sv.args,
487
+ ...sv.env ? { env: sv.env } : {}
488
+ };
489
+ }
490
+ try {
491
+ mergeJsonFile(projectSettingsPath, { mcpServers: mcpConfig });
492
+ for (const sv of projectServers) {
493
+ installed.push(sv.name);
494
+ }
495
+ } catch {
496
+ for (const sv of projectServers) {
497
+ failed.push(sv.name);
498
+ }
499
+ }
500
+ }
501
+ s.stop("MCP Servers \u914D\u7F6E\u5B8C\u6210");
502
+ for (const name of installed) {
503
+ p5.log.success(`${name} MCP \u5DF2\u914D\u7F6E`);
504
+ }
505
+ for (const name of failed) {
506
+ p5.log.error(`${name} MCP \u914D\u7F6E\u5931\u8D25`);
507
+ }
508
+ return { installed, failed };
509
+ }
510
+
511
+ // src/steps/install-skills.ts
512
+ import * as p6 from "@clack/prompts";
513
+ import pc5 from "picocolors";
514
+
515
+ // src/registry/skills.ts
516
+ var SKILLS = [
517
+ {
518
+ name: "superpowers",
519
+ description: "\u589E\u5F3A\u5DE5\u4F5C\u6D41 (\u5934\u8111\u98CE\u66B4\u3001TDD\u3001\u4EE3\u7801\u5BA1\u67E5\u3001\u8BA1\u5212)",
520
+ source: "claude-plugins-official/superpowers",
521
+ installCmd: ["claude", "plugins", "install", "superpowers"]
522
+ },
523
+ {
524
+ name: "spec-workflow",
525
+ description: "\u89C4\u8303\u5316\u5F00\u53D1\u6D41\u7A0B (\u9700\u6C42\u2192\u8BBE\u8BA1\u2192\u4EFB\u52A1\u2192\u5B9E\u73B0)",
526
+ source: "spec-workflow",
527
+ installCmd: ["claude", "plugins", "install", "spec-workflow"]
528
+ },
529
+ {
530
+ name: "skill-creator",
531
+ description: "Skill \u521B\u5EFA\u4E0E\u7BA1\u7406\u5DE5\u5177",
532
+ source: "skill-creator",
533
+ installCmd: ["claude", "plugins", "install", "skill-creator"]
534
+ }
535
+ ];
536
+
537
+ // src/steps/install-skills.ts
538
+ async function selectSkills() {
539
+ console.log();
540
+ console.log(pc5.dim(" \u64CD\u4F5C\u6307\u5357:"));
541
+ console.log(pc5.dim(" \u2191/\u2193 \u4E0A\u4E0B\u79FB\u52A8\u5149\u6807"));
542
+ console.log(pc5.dim(" \u7A7A\u683C \u9009\u4E2D/\u53D6\u6D88\u9009\u4E2D\u5F53\u524D\u9879"));
543
+ console.log(pc5.dim(" a \u5168\u9009/\u5168\u4E0D\u9009"));
544
+ console.log(pc5.dim(" \u56DE\u8F66 \u786E\u8BA4\u9009\u62E9"));
545
+ console.log();
546
+ const selected = await p6.multiselect({
547
+ message: "\u9009\u62E9\u8981\u5B89\u88C5\u7684 Skills / Plugins (\u7A7A\u683C\u5207\u6362, \u56DE\u8F66\u786E\u8BA4)",
548
+ options: SKILLS.map((s) => ({
549
+ value: s.name,
550
+ label: s.name,
551
+ hint: s.description
552
+ })),
553
+ initialValues: SKILLS.map((s) => s.name),
554
+ required: false
555
+ });
556
+ if (p6.isCancel(selected)) {
557
+ return [];
558
+ }
559
+ const names = selected;
560
+ return SKILLS.filter((s) => names.includes(s.name));
561
+ }
562
+ async function installSkills(skills) {
563
+ if (skills.length === 0) {
564
+ return { installed: [], failed: [] };
565
+ }
566
+ const s = p6.spinner();
567
+ const installed = [];
568
+ const failed = [];
569
+ for (const skill of skills) {
570
+ s.start(`\u6B63\u5728\u5B89\u88C5 ${skill.name}...`);
571
+ const [cmd, ...args] = skill.installCmd;
572
+ const result = await run(cmd, args);
573
+ if (result.exitCode === 0) {
574
+ installed.push(skill.name);
575
+ s.stop(`${skill.name} \u5B89\u88C5\u5B8C\u6210`);
576
+ } else {
577
+ failed.push(skill.name);
578
+ s.stop(`${skill.name} \u5B89\u88C5\u5931\u8D25`);
579
+ p6.log.warning(` ${result.stderr || "\u672A\u77E5\u9519\u8BEF\uFF0C\u8BF7\u624B\u52A8\u5B89\u88C5"}`);
580
+ }
581
+ }
582
+ return { installed, failed };
583
+ }
584
+
585
+ // src/steps/clean-env.ts
586
+ import * as p7 from "@clack/prompts";
587
+ import pc6 from "picocolors";
588
+ async function cleanEnv(sensitiveVars) {
589
+ if (sensitiveVars.length === 0) {
590
+ return { removed: [], skipped: [] };
591
+ }
592
+ console.log();
593
+ console.log(pc6.dim(" \u64CD\u4F5C\u6307\u5357:"));
594
+ console.log(pc6.dim(" \u2191/\u2193 \u4E0A\u4E0B\u79FB\u52A8\u5149\u6807"));
595
+ console.log(pc6.dim(" \u7A7A\u683C \u9009\u4E2D/\u53D6\u6D88\u9009\u4E2D\u5F53\u524D\u9879"));
596
+ console.log(pc6.dim(" a \u5168\u9009/\u5168\u4E0D\u9009"));
597
+ console.log(pc6.dim(" \u56DE\u8F66 \u786E\u8BA4\u9009\u62E9"));
598
+ console.log();
599
+ const selected = await p7.multiselect({
600
+ message: "\u9009\u62E9\u8981\u79FB\u9664\u7684\u654F\u611F\u73AF\u5883\u53D8\u91CF (\u7A7A\u683C\u5207\u6362, \u56DE\u8F66\u786E\u8BA4)",
601
+ options: sensitiveVars.map((v) => ({
602
+ value: v.key,
603
+ label: v.key,
604
+ hint: maskValue2(v.value)
605
+ })),
606
+ initialValues: [],
607
+ required: false
608
+ });
609
+ if (p7.isCancel(selected) || selected.length === 0) {
610
+ return { removed: [], skipped: sensitiveVars.map((v) => v.key) };
611
+ }
612
+ const toRemove = selected;
613
+ const removed = [];
614
+ const skipped = [];
615
+ const s = p7.spinner();
616
+ s.start("\u6B63\u5728\u6E05\u7406\u73AF\u5883\u53D8\u91CF...");
617
+ for (const key of toRemove) {
618
+ const success = await removeEnvVar(key);
619
+ if (success) {
620
+ removed.push(key);
621
+ } else {
622
+ skipped.push(key);
623
+ }
624
+ }
625
+ s.stop("\u73AF\u5883\u53D8\u91CF\u6E05\u7406\u5B8C\u6210");
626
+ for (const key of removed) {
627
+ p7.log.success(`${key} \u5DF2\u79FB\u9664`);
628
+ }
629
+ for (const key of skipped) {
630
+ p7.log.warning(`${key} \u79FB\u9664\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u5904\u7406`);
631
+ }
632
+ if (skipped.length > 0 && process.platform !== "win32") {
633
+ p7.log.info("\u624B\u52A8\u79FB\u9664\u65B9\u6CD5:");
634
+ for (const key of skipped) {
635
+ console.log(pc6.dim(` \u4ECE ~/.bashrc \u6216 ~/.zshrc \u4E2D\u5220\u9664: export ${key}=...`));
636
+ }
637
+ }
638
+ return { removed, skipped };
639
+ }
640
+ async function removeEnvVar(key) {
641
+ if (process.platform === "win32") {
642
+ const result = await run("powershell", [
643
+ "-Command",
644
+ `[Environment]::SetEnvironmentVariable('${key}', $null, 'User')`
645
+ ]);
646
+ return result.exitCode === 0;
647
+ } else {
648
+ delete process.env[key];
649
+ return false;
650
+ }
651
+ }
652
+ function maskValue2(val) {
653
+ if (val.length <= 8) return "****";
654
+ return val.slice(0, 4) + "..." + val.slice(-4);
655
+ }
656
+
657
+ // src/cli.ts
658
+ async function runCli() {
659
+ const results = [];
660
+ const env = await detect();
661
+ const components = await selectComponents(env);
662
+ const mcpServers = components.installMcp ? await selectMcpServers() : [];
663
+ const skills = components.installSkills ? await selectSkills() : [];
664
+ const actions = [];
665
+ if (components.installCli && !env.claudeInstalled) actions.push("\u5B89\u88C5 Claude Code CLI");
666
+ if (components.scaffold) actions.push("\u751F\u6210 CLAUDE.md + .claude/ \u914D\u7F6E");
667
+ if (mcpServers.length > 0) actions.push(`\u5B89\u88C5 ${mcpServers.length} \u4E2A MCP Servers`);
668
+ if (skills.length > 0) actions.push(`\u5B89\u88C5 ${skills.length} \u4E2A Skills`);
669
+ if (components.cleanEnv) actions.push(`\u6E05\u7406 ${env.sensitiveEnvVars.length} \u4E2A\u654F\u611F\u73AF\u5883\u53D8\u91CF`);
670
+ if (actions.length === 0) {
671
+ p8.log.info("\u6CA1\u6709\u9009\u62E9\u4EFB\u4F55\u64CD\u4F5C\uFF0C\u9000\u51FA");
672
+ return;
673
+ }
674
+ p8.log.info("\u5373\u5C06\u6267\u884C\u4EE5\u4E0B\u64CD\u4F5C:");
675
+ for (let i = 0; i < actions.length; i++) {
676
+ console.log(pc7.dim(` ${i + 1}. ${actions[i]}`));
677
+ }
678
+ console.log();
679
+ console.log(pc7.dim(" Y/\u56DE\u8F66 \u786E\u8BA4 | N \u53D6\u6D88"));
680
+ console.log();
681
+ const confirmed = await p8.confirm({
682
+ message: "\u786E\u8BA4\u6267\u884C\uFF1F",
683
+ initialValue: true
684
+ });
685
+ if (p8.isCancel(confirmed) || !confirmed) {
686
+ p8.cancel("\u5DF2\u53D6\u6D88");
687
+ process.exit(0);
688
+ }
689
+ if (components.installCli) {
690
+ const r = await installCli(env.claudeInstalled);
691
+ results.push({
692
+ label: r.skipped ? "Claude Code CLI (\u5DF2\u5B89\u88C5)" : "Claude Code CLI \u5B89\u88C5",
693
+ ok: r.success
694
+ });
695
+ }
696
+ if (components.scaffold) {
697
+ const r = await scaffold(process.cwd());
698
+ results.push({ label: "\u914D\u7F6E\u6587\u4EF6\u751F\u6210", ok: true });
699
+ }
700
+ if (mcpServers.length > 0) {
701
+ const r = await installMcp(mcpServers);
702
+ for (const name of r.installed) {
703
+ results.push({ label: `${name} MCP`, ok: true });
704
+ }
705
+ for (const name of r.failed) {
706
+ results.push({ label: `${name} MCP`, ok: false });
707
+ }
708
+ }
709
+ if (skills.length > 0) {
710
+ const r = await installSkills(skills);
711
+ for (const name of r.installed) {
712
+ results.push({ label: `${name} Skill`, ok: true });
713
+ }
714
+ for (const name of r.failed) {
715
+ results.push({ label: `${name} Skill`, ok: false });
716
+ }
717
+ }
718
+ if (components.cleanEnv) {
719
+ const r = await cleanEnv(env.sensitiveEnvVars);
720
+ for (const key of r.removed) {
721
+ results.push({ label: `${key} \u5DF2\u79FB\u9664`, ok: true });
722
+ }
723
+ for (const key of r.skipped) {
724
+ results.push({ label: `${key} \u9700\u624B\u52A8\u79FB\u9664`, ok: false });
725
+ }
726
+ }
727
+ summary(results);
728
+ }
729
+
730
+ // src/index.ts
731
+ import * as p9 from "@clack/prompts";
732
+ var VERSION = "1.0.0";
733
+ async function main() {
734
+ banner(VERSION);
735
+ p9.intro("\u5F00\u59CB\u914D\u7F6E Claude Code \u73AF\u5883");
736
+ try {
737
+ await runCli();
738
+ } catch (err) {
739
+ if (err instanceof Error) {
740
+ p9.log.error(err.message);
741
+ }
742
+ p9.cancel("\u53D1\u751F\u9519\u8BEF\uFF0C\u5DF2\u9000\u51FA");
743
+ process.exit(1);
744
+ }
745
+ p9.outro("\u611F\u8C22\u4F7F\u7528 ccbot\uFF01");
746
+ }
747
+ main();
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "ccbot-cli",
3
+ "version": "1.0.0",
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
+ },
21
+ "keywords": [
22
+ "claude",
23
+ "claude-code",
24
+ "cli",
25
+ "setup",
26
+ "mcp",
27
+ "ai"
28
+ ],
29
+ "license": "MIT",
30
+ "dependencies": {
31
+ "@clack/prompts": "^0.9.1",
32
+ "execa": "^9.5.2",
33
+ "picocolors": "^1.1.1"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^22.10.0",
37
+ "tsup": "^8.3.5",
38
+ "typescript": "^5.7.2"
39
+ }
40
+ }