ccbot-cli 1.0.0 → 1.2.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 +279 -395
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,52 +1,59 @@
1
1
  #!/usr/bin/env node
2
+ #!/usr/bin/env node
3
+
4
+ // src/index.ts
5
+ import { createRequire } from "module";
2
6
 
3
7
  // src/utils/logger.ts
4
8
  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) {
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) {
14
29
  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"));
30
+ console.log(pc.cyan(" \u{1F916} ccbot") + pc.dim(` v${version2}`) + pc.cyan(" \u2014 Claude Code \u73AF\u5883\u4E00\u952E\u914D\u7F6E"));
21
31
  console.log();
22
32
  }
23
- function summary(items) {
33
+ function done(items) {
34
+ const okCount = items.filter((i) => i.ok).length;
35
+ const failCount = items.filter((i) => !i.ok).length;
24
36
  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"));
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
+ }
28
42
  console.log();
29
43
  for (const item of items) {
30
- if (item.ok) {
31
- log.success(item.label);
32
- } else {
33
- log.error(item.label);
34
- }
44
+ if (item.ok) ok(item.label);
45
+ else fail(item.label);
35
46
  }
36
47
  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");
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"));
40
51
  console.log();
41
52
  }
42
53
 
43
54
  // 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";
55
+ import { confirm as confirm2 } from "@inquirer/prompts";
56
+ import pc3 from "picocolors";
50
57
 
51
58
  // src/utils/exec.ts
52
59
  import { execa } from "execa";
@@ -79,7 +86,6 @@ var SENSITIVE_PATTERNS = [
79
86
  /^ANTHROPIC_API_KEY$/i,
80
87
  /^CLAUDE_API_KEY$/i,
81
88
  /^OPENAI_API_KEY$/i,
82
- /^OPENAI_ORG_ID$/i,
83
89
  /^GOOGLE_API_KEY$/i,
84
90
  /^AZURE_OPENAI_API_KEY$/i,
85
91
  /^HUGGINGFACE_TOKEN$/i,
@@ -92,148 +98,97 @@ var SENSITIVE_PATTERNS = [
92
98
  /_TOKEN$/i,
93
99
  /_API_KEY$/i
94
100
  ];
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...");
101
+ async function detect(total) {
102
+ step(1, total, "\u73AF\u5883\u68C0\u6D4B");
115
103
  const nodeVersion = process.version;
116
104
  const npmResult = await run("npm", ["--version"]);
117
105
  const npmVersion = npmResult.stdout.trim();
118
- const osMap = {
119
- win32: "Windows",
120
- darwin: "macOS",
121
- linux: "Linux"
122
- };
106
+ const osMap = { win32: "Windows", darwin: "macOS", linux: "Linux" };
123
107
  const os = osMap[process.platform] ?? process.platform;
124
108
  const claudeInstalled = await commandExists("claude");
125
109
  let claudeVersion = null;
126
110
  if (claudeInstalled) {
127
- const result = await run("claude", ["--version"]);
128
- claudeVersion = result.stdout.trim();
111
+ const r = await run("claude", ["--version"]);
112
+ claudeVersion = r.stdout.trim();
129
113
  }
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");
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
+ }
139
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");
140
124
  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
- }
125
+ warn(`\u53D1\u73B0 ${sensitiveEnvVars.length} \u4E2A\u654F\u611F\u73AF\u5883\u53D8\u91CF`);
145
126
  }
146
- return {
147
- nodeVersion,
148
- npmVersion,
149
- os,
150
- platform: process.platform,
151
- claudeInstalled,
152
- claudeVersion,
153
- sensitiveEnvVars
154
- };
127
+ return { nodeVersion, npmVersion, os, platform: process.platform, claudeInstalled, claudeVersion, sensitiveEnvVars };
155
128
  }
156
129
 
157
130
  // 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 = [
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 = [
171
136
  {
172
137
  value: "installCli",
173
- label: "Claude Code CLI \u5B89\u88C5",
174
- hint: env.claudeInstalled ? "\u5DF2\u5B89\u88C5\uFF0C\u5C06\u8DF3\u8FC7" : "\u672A\u5B89\u88C5"
138
+ name: `Claude Code CLI ${env.claudeInstalled ? "(\u5DF2\u5B89\u88C5, \u8DF3\u8FC7)" : "(\u672A\u5B89\u88C5)"}`,
139
+ checked: true
175
140
  },
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" }
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 }
179
144
  ];
180
145
  if (env.sensitiveEnvVars.length > 0) {
181
- options.push({
146
+ choices.push({
182
147
  value: "cleanEnv",
183
- label: "\u73AF\u5883\u53D8\u91CF\u6E05\u7406",
184
- hint: `\u68C0\u6D4B\u5230 ${env.sensitiveEnvVars.length} \u4E2A\u654F\u611F\u53D8\u91CF`
148
+ name: `\u73AF\u5883\u53D8\u91CF\u6E05\u7406 (${env.sensitiveEnvVars.length} \u4E2A\u654F\u611F\u53D8\u91CF)`,
149
+ checked: true
185
150
  });
186
151
  }
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),
152
+ const selected = await checkbox({
153
+ message: "\u5B89\u88C5\u7EC4\u4EF6",
154
+ choices,
192
155
  required: true
193
156
  });
194
- if (p2.isCancel(selected)) {
195
- p2.cancel("\u5DF2\u53D6\u6D88");
196
- process.exit(0);
197
- }
198
- const values = selected;
199
157
  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")
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")
205
163
  };
206
164
  }
207
165
 
208
166
  // src/steps/install-cli.ts
209
- import * as p3 from "@clack/prompts";
210
167
  async function installCli(alreadyInstalled) {
211
168
  if (alreadyInstalled) {
212
- p3.log.info("Claude Code CLI \u5DF2\u5B89\u88C5\uFF0C\u8DF3\u8FC7");
169
+ info("Claude CLI \u5DF2\u5B89\u88C5, \u8DF3\u8FC7");
213
170
  return { success: true, version: null, skipped: true };
214
171
  }
215
- const s = p3.spinner();
216
- s.start("\u6B63\u5728\u5B89\u88C5 Claude Code CLI...");
172
+ info("\u6B63\u5728\u5B89\u88C5 Claude Code CLI...");
217
173
  const result = await npmInstallGlobal("@anthropic-ai/claude-code");
218
174
  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");
175
+ fail("Claude CLI \u5B89\u88C5\u5931\u8D25");
176
+ warn("\u624B\u52A8: npm install -g @anthropic-ai/claude-code");
221
177
  return { success: false, version: null, skipped: false };
222
178
  }
223
179
  const exists = await commandExists("claude");
224
180
  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");
181
+ warn("\u5B89\u88C5\u5B8C\u6210\u4F46 claude \u547D\u4EE4\u4E0D\u53EF\u7528, \u53EF\u80FD\u9700\u91CD\u542F\u7EC8\u7AEF");
227
182
  return { success: false, version: null, skipped: false };
228
183
  }
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 };
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 };
233
188
  }
234
189
 
235
190
  // src/steps/scaffold.ts
236
- import * as p4 from "@clack/prompts";
191
+ import { select } from "@inquirer/prompts";
237
192
  import { join as join2 } from "path";
238
193
 
239
194
  // src/utils/fs.ts
@@ -281,79 +236,98 @@ function deepMerge(target, source) {
281
236
  }
282
237
 
283
238
  // src/steps/scaffold.ts
284
- var CLAUDE_MD_TEMPLATE = `# Project Instructions
239
+ var TEMPLATES = {
240
+ minimal: `# Project Instructions
285
241
 
286
- <!-- \u5728\u6B64\u7F16\u5199\u9879\u76EE\u7EA7 Claude Code \u6307\u4EE4 -->
242
+ - Follow existing code style
243
+ - Read before write
244
+ - Keep changes minimal
245
+ `,
246
+ standard: `# Project Instructions
287
247
 
288
- ## \u9879\u76EE\u6982\u8FF0
248
+ ## Overview
249
+ <!-- Project purpose and tech stack -->
289
250
 
290
- <!-- \u63CF\u8FF0\u9879\u76EE\u7684\u76EE\u7684\u548C\u6280\u672F\u6808 -->
251
+ ## Coding Standards
252
+ - Follow existing patterns
253
+ - Read context before modifying
254
+ - Keep changes focused
291
255
 
292
- ## \u7F16\u7801\u89C4\u8303
256
+ ## Key Files
257
+ <!-- Important file paths -->
293
258
 
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
259
+ ## Notes
260
+ <!-- Special considerations -->
261
+ `,
262
+ detailed: `# Project Instructions
297
263
 
298
- ## \u91CD\u8981\u6587\u4EF6
264
+ ## Overview
265
+ <!-- Project purpose, goals, tech stack -->
299
266
 
300
- <!-- \u5217\u51FA\u5173\u952E\u6587\u4EF6\u8DEF\u5F84 -->
267
+ ## Architecture
268
+ <!-- High-level design decisions -->
301
269
 
302
- ## \u6CE8\u610F\u4E8B\u9879
270
+ ## Coding Standards
271
+ - Follow existing patterns
272
+ - Read context before modifying
273
+ - Write clear commit messages
274
+ - Add comments for complex logic
303
275
 
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: {}
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
+ `
318
288
  };
319
- var CLAUDEIGNORE_TEMPLATE = `# \u5FFD\u7565\u6587\u4EF6
320
- node_modules/
289
+ var CLAUDEIGNORE = `node_modules/
321
290
  dist/
291
+ build/
322
292
  .env
323
293
  .env.*
324
294
  *.log
325
295
  .DS_Store
326
296
  coverage/
297
+ .git/
327
298
  `;
328
- async function scaffold(targetDir) {
329
- const s = p4.spinner();
330
- s.start("\u6B63\u5728\u751F\u6210\u914D\u7F6E\u6587\u4EF6...");
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) {
331
313
  const files = [];
332
- const claudeMdPath = join2(targetDir, "CLAUDE.md");
333
- const r1 = writeFileSafe(claudeMdPath, CLAUDE_MD_TEMPLATE);
314
+ const r1 = writeFileSafe(join2(targetDir, "CLAUDE.md"), TEMPLATES[opts.style]);
334
315
  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");
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");
339
319
  files.push({ path: ".claude/settings.json", written: r2.written });
340
- const ignorePath = join2(targetDir, ".claudeignore");
341
- const r3 = writeFileSafe(ignorePath, CLAUDEIGNORE_TEMPLATE);
320
+ const r3 = writeFileSafe(join2(targetDir, ".claudeignore"), CLAUDEIGNORE);
342
321
  files.push({ path: ".claudeignore", written: r3.written });
343
- s.stop("\u914D\u7F6E\u6587\u4EF6\u751F\u6210\u5B8C\u6210");
344
322
  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
- }
323
+ if (f.written) ok(f.path);
324
+ else info(`${f.path} (\u5DF2\u5B58\u5728, \u8DF3\u8FC7)`);
350
325
  }
351
326
  return { files };
352
327
  }
353
328
 
354
329
  // src/steps/install-mcp.ts
355
- import * as p5 from "@clack/prompts";
356
- import pc4 from "picocolors";
330
+ import { checkbox as checkbox2, confirm, input } from "@inquirer/prompts";
357
331
  import { join as join3 } from "path";
358
332
  import { homedir } from "os";
359
333
 
@@ -418,99 +392,73 @@ var MCP_SERVERS = [
418
392
  ];
419
393
 
420
394
  // 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) => ({
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) => ({
432
401
  value: s.name,
433
- label: s.name,
434
- hint: s.description
435
- })),
436
- initialValues: MCP_SERVERS.map((s) => s.name),
437
- required: false
402
+ name: `${s.name} \u2014 ${s.description}`,
403
+ checked: true
404
+ }))
438
405
  });
439
- if (p5.isCancel(selected)) {
440
- return [];
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
+ }
441
418
  }
442
- const names = selected;
443
- return MCP_SERVERS.filter((s) => names.includes(s.name));
419
+ return servers;
444
420
  }
445
421
  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...");
422
+ if (servers.length === 0) return { installed: [], failed: [] };
451
423
  const installed = [];
452
424
  const failed = [];
453
- const userServers = servers.filter((sv) => sv.scope === "user");
454
- const projectServers = servers.filter((sv) => sv.scope === "project");
425
+ const userServers = servers.filter((s) => s.scope === "user");
426
+ const projectServers = servers.filter((s) => s.scope === "project");
455
427
  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
- };
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 } : {} };
466
433
  }
467
434
  try {
468
- mergeJsonFile(userSettingsPath, { mcpServers: mcpConfig });
469
- for (const sv of userServers) {
470
- installed.push(sv.name);
471
- }
435
+ mergeJsonFile(join3(dir, "settings.json"), { mcpServers: cfg });
436
+ userServers.forEach((s) => installed.push(s.name));
472
437
  } catch {
473
- for (const sv of userServers) {
474
- failed.push(sv.name);
475
- }
438
+ userServers.forEach((s) => failed.push(s.name));
476
439
  }
477
440
  }
478
441
  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
- };
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 } : {} };
489
447
  }
490
448
  try {
491
- mergeJsonFile(projectSettingsPath, { mcpServers: mcpConfig });
492
- for (const sv of projectServers) {
493
- installed.push(sv.name);
494
- }
449
+ mergeJsonFile(join3(dir, "settings.json"), { mcpServers: cfg });
450
+ projectServers.forEach((s) => installed.push(s.name));
495
451
  } catch {
496
- for (const sv of projectServers) {
497
- failed.push(sv.name);
498
- }
452
+ projectServers.forEach((s) => failed.push(s.name));
499
453
  }
500
454
  }
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
- }
455
+ for (const n of installed) ok(`${n} MCP`);
456
+ for (const n of failed) fail(`${n} MCP`);
508
457
  return { installed, failed };
509
458
  }
510
459
 
511
460
  // src/steps/install-skills.ts
512
- import * as p6 from "@clack/prompts";
513
- import pc5 from "picocolors";
461
+ import { checkbox as checkbox3 } from "@inquirer/prompts";
514
462
 
515
463
  // src/registry/skills.ts
516
464
  var SKILLS = [
@@ -535,213 +483,149 @@ var SKILLS = [
535
483
  ];
536
484
 
537
485
  // 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) => ({
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) => ({
549
492
  value: s.name,
550
- label: s.name,
551
- hint: s.description
552
- })),
553
- initialValues: SKILLS.map((s) => s.name),
554
- required: false
493
+ name: `${s.name} \u2014 ${s.description}`,
494
+ checked: true
495
+ }))
555
496
  });
556
- if (p6.isCancel(selected)) {
557
- return [];
558
- }
559
- const names = selected;
560
- return SKILLS.filter((s) => names.includes(s.name));
497
+ return SKILLS.filter((s) => selected.includes(s.name));
561
498
  }
562
499
  async function installSkills(skills) {
563
- if (skills.length === 0) {
564
- return { installed: [], failed: [] };
565
- }
566
- const s = p6.spinner();
500
+ if (skills.length === 0) return { installed: [], failed: [] };
567
501
  const installed = [];
568
502
  const failed = [];
569
503
  for (const skill of skills) {
570
- s.start(`\u6B63\u5728\u5B89\u88C5 ${skill.name}...`);
571
504
  const [cmd, ...args] = skill.installCmd;
572
505
  const result = await run(cmd, args);
573
506
  if (result.exitCode === 0) {
574
507
  installed.push(skill.name);
575
- s.stop(`${skill.name} \u5B89\u88C5\u5B8C\u6210`);
508
+ ok(skill.name);
576
509
  } else {
577
510
  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"}`);
511
+ fail(skill.name);
512
+ warn(` \u624B\u52A8: ${skill.installCmd.join(" ")}`);
580
513
  }
581
514
  }
582
515
  return { installed, failed };
583
516
  }
584
517
 
585
518
  // 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) => ({
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) => ({
602
532
  value: v.key,
603
- label: v.key,
604
- hint: maskValue2(v.value)
605
- })),
606
- initialValues: [],
607
- required: false
533
+ name: `${v.key} = ${pc2.dim(mask(v.value))}`,
534
+ checked: false
535
+ }))
608
536
  });
609
- if (p7.isCancel(selected) || selected.length === 0) {
610
- return { removed: [], skipped: sensitiveVars.map((v) => v.key) };
611
- }
612
- const toRemove = selected;
537
+ if (selected.length === 0) return { removed: [], skipped: vars.map((v) => v.key) };
613
538
  const removed = [];
614
539
  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);
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
+ }
621
550
  } else {
551
+ delete process.env[key];
622
552
  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}=...`));
553
+ warn(`${key} \u9700\u624B\u52A8\u4ECE ~/.bashrc \u6216 ~/.zshrc \u79FB\u9664`);
636
554
  }
637
555
  }
638
556
  return { removed, skipped };
639
557
  }
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
558
 
657
559
  // src/cli.ts
560
+ var TOTAL = 7;
658
561
  async function runCli() {
659
562
  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() : [];
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");
664
572
  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`);
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`);
670
578
  if (actions.length === 0) {
671
- p8.log.info("\u6CA1\u6709\u9009\u62E9\u4EFB\u4F55\u64CD\u4F5C\uFF0C\u9000\u51FA");
579
+ info("\u65E0\u64CD\u4F5C");
672
580
  return;
673
581
  }
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");
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");
687
586
  process.exit(0);
688
587
  }
588
+ step(7, TOTAL, "\u6267\u884C\u5B89\u88C5");
689
589
  if (components.installCli) {
690
590
  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
- });
591
+ results.push({ label: r.skipped ? "Claude CLI (\u5DF2\u5B89\u88C5)" : "Claude CLI", ok: r.success });
695
592
  }
696
- if (components.scaffold) {
697
- const r = await scaffold(process.cwd());
698
- results.push({ label: "\u914D\u7F6E\u6587\u4EF6\u751F\u6210", ok: true });
593
+ if (scaffoldOpts) {
594
+ await scaffold(process.cwd(), scaffoldOpts);
595
+ results.push({ label: "\u914D\u7F6E\u6587\u4EF6", ok: true });
699
596
  }
700
597
  if (mcpServers.length > 0) {
701
598
  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
- }
599
+ r.installed.forEach((n) => results.push({ label: `${n} MCP`, ok: true }));
600
+ r.failed.forEach((n) => results.push({ label: `${n} MCP`, ok: false }));
708
601
  }
709
602
  if (skills.length > 0) {
710
603
  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
- }
604
+ r.installed.forEach((n) => results.push({ label: `${n} Skill`, ok: true }));
605
+ r.failed.forEach((n) => results.push({ label: `${n} Skill`, ok: false }));
717
606
  }
718
607
  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
- }
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 }));
726
611
  }
727
- summary(results);
612
+ done(results);
728
613
  }
729
614
 
730
615
  // src/index.ts
731
- import * as p9 from "@clack/prompts";
732
- var VERSION = "1.0.0";
616
+ var require2 = createRequire(import.meta.url);
617
+ var { version } = require2("../package.json");
733
618
  async function main() {
734
- banner(VERSION);
735
- p9.intro("\u5F00\u59CB\u914D\u7F6E Claude Code \u73AF\u5883");
619
+ banner(version);
736
620
  try {
737
621
  await runCli();
738
622
  } catch (err) {
739
- if (err instanceof Error) {
740
- p9.log.error(err.message);
623
+ if (err instanceof Error && err.message.includes("User force closed")) {
624
+ console.log("\n \u5DF2\u9000\u51FA");
625
+ process.exit(0);
741
626
  }
742
- p9.cancel("\u53D1\u751F\u9519\u8BEF\uFF0C\u5DF2\u9000\u51FA");
627
+ console.error(err);
743
628
  process.exit(1);
744
629
  }
745
- p9.outro("\u611F\u8C22\u4F7F\u7528 ccbot\uFF01");
746
630
  }
747
631
  main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccbot-cli",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Claude Code 环境一键配置工具",
5
5
  "type": "module",
6
6
  "bin": {
@@ -28,7 +28,7 @@
28
28
  ],
29
29
  "license": "MIT",
30
30
  "dependencies": {
31
- "@clack/prompts": "^0.9.1",
31
+ "@inquirer/prompts": "^8.3.0",
32
32
  "execa": "^9.5.2",
33
33
  "picocolors": "^1.1.1"
34
34
  },