ccbot-cli 2.0.1 → 2.1.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 (83) hide show
  1. package/LICENSE +21 -0
  2. package/bin/adapters/claude.js +150 -0
  3. package/bin/adapters/codex.js +439 -0
  4. package/bin/install.js +509 -349
  5. package/bin/lib/ccline.js +82 -0
  6. package/bin/lib/utils.js +87 -34
  7. package/bin/uninstall.js +48 -0
  8. package/config/AGENTS.md +630 -0
  9. package/config/CLAUDE.md +229 -20
  10. package/config/ccline/config.toml +161 -0
  11. package/config/codex-config.example.toml +22 -0
  12. package/config/settings.example.json +32 -0
  13. package/output-styles/abyss-cultivator.md +399 -0
  14. package/package.json +14 -5
  15. package/skills/SKILL.md +159 -0
  16. package/skills/domains/ai/SKILL.md +34 -0
  17. package/skills/domains/ai/agent-dev.md +242 -0
  18. package/skills/domains/ai/llm-security.md +288 -0
  19. package/skills/domains/ai/prompt-and-eval.md +279 -0
  20. package/skills/domains/ai/rag-system.md +542 -0
  21. package/skills/domains/architecture/SKILL.md +42 -0
  22. package/skills/domains/architecture/api-design.md +225 -0
  23. package/skills/domains/architecture/caching.md +299 -0
  24. package/skills/domains/architecture/cloud-native.md +285 -0
  25. package/skills/domains/architecture/message-queue.md +329 -0
  26. package/skills/domains/architecture/security-arch.md +297 -0
  27. package/skills/domains/data-engineering/SKILL.md +207 -0
  28. package/skills/domains/development/SKILL.md +46 -0
  29. package/skills/domains/development/cpp.md +246 -0
  30. package/skills/domains/development/go.md +323 -0
  31. package/skills/domains/development/java.md +277 -0
  32. package/skills/domains/development/python.md +288 -0
  33. package/skills/domains/development/rust.md +313 -0
  34. package/skills/domains/development/shell.md +313 -0
  35. package/skills/domains/development/typescript.md +277 -0
  36. package/skills/domains/devops/SKILL.md +39 -0
  37. package/skills/domains/devops/cost-optimization.md +272 -0
  38. package/skills/domains/devops/database.md +217 -0
  39. package/skills/domains/devops/devsecops.md +198 -0
  40. package/skills/domains/devops/git-workflow.md +181 -0
  41. package/skills/domains/devops/observability.md +280 -0
  42. package/skills/domains/devops/performance.md +336 -0
  43. package/skills/domains/devops/testing.md +283 -0
  44. package/skills/domains/frontend-design/SKILL.md +38 -0
  45. package/skills/domains/frontend-design/claymorphism/SKILL.md +119 -0
  46. package/skills/domains/frontend-design/claymorphism/references/tokens.css +52 -0
  47. package/skills/domains/frontend-design/component-patterns.md +202 -0
  48. package/skills/domains/frontend-design/engineering.md +287 -0
  49. package/skills/domains/frontend-design/glassmorphism/SKILL.md +140 -0
  50. package/skills/domains/frontend-design/glassmorphism/references/tokens.css +32 -0
  51. package/skills/domains/frontend-design/liquid-glass/SKILL.md +137 -0
  52. package/skills/domains/frontend-design/liquid-glass/references/tokens.css +81 -0
  53. package/skills/domains/frontend-design/neubrutalism/SKILL.md +143 -0
  54. package/skills/domains/frontend-design/neubrutalism/references/tokens.css +44 -0
  55. package/skills/domains/frontend-design/state-management.md +680 -0
  56. package/skills/domains/frontend-design/ui-aesthetics.md +110 -0
  57. package/skills/domains/frontend-design/ux-principles.md +156 -0
  58. package/skills/domains/infrastructure/SKILL.md +200 -0
  59. package/skills/domains/mobile/SKILL.md +224 -0
  60. package/skills/domains/orchestration/SKILL.md +29 -0
  61. package/skills/domains/orchestration/multi-agent.md +263 -0
  62. package/skills/domains/security/SKILL.md +54 -0
  63. package/skills/domains/security/blue-team.md +436 -0
  64. package/skills/domains/security/code-audit.md +265 -0
  65. package/skills/domains/security/pentest.md +226 -0
  66. package/skills/domains/security/red-team.md +375 -0
  67. package/skills/domains/security/threat-intel.md +372 -0
  68. package/skills/domains/security/vuln-research.md +369 -0
  69. package/skills/orchestration/multi-agent/SKILL.md +493 -0
  70. package/skills/run_skill.js +129 -0
  71. package/skills/tools/gen-docs/SKILL.md +116 -0
  72. package/skills/tools/gen-docs/scripts/doc_generator.js +435 -0
  73. package/skills/tools/lib/shared.js +98 -0
  74. package/skills/tools/verify-change/SKILL.md +140 -0
  75. package/skills/tools/verify-change/scripts/change_analyzer.js +289 -0
  76. package/skills/tools/verify-module/SKILL.md +127 -0
  77. package/skills/tools/verify-module/scripts/module_scanner.js +171 -0
  78. package/skills/tools/verify-quality/SKILL.md +160 -0
  79. package/skills/tools/verify-quality/scripts/quality_checker.js +337 -0
  80. package/skills/tools/verify-security/SKILL.md +143 -0
  81. package/skills/tools/verify-security/scripts/security_scanner.js +283 -0
  82. package/bin/lib/registry.js +0 -61
  83. package/config/.claudeignore +0 -11
package/bin/install.js CHANGED
@@ -1,349 +1,509 @@
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
- }
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+
7
+ const pkg = require(path.join(__dirname, '..', 'package.json'));
8
+ const VERSION = pkg.version;
9
+ const HOME = os.homedir();
10
+
11
+ // ── Node.js 版本检查 ──
12
+ const MIN_NODE = pkg.engines?.node?.match(/(\d+)/)?.[1] || '18';
13
+ if (parseInt(process.versions.node) < parseInt(MIN_NODE)) {
14
+ console.error(`\x1b[31m✘ 需要 Node.js >= ${MIN_NODE},当前: ${process.versions.node}\x1b[0m`);
15
+ process.exit(1);
16
+ }
17
+ const PKG_ROOT = fs.realpathSync(path.join(__dirname, '..'));
18
+ const { shouldSkip, copyRecursive, rmSafe, deepMergeNew, printMergeLog, parseFrontmatter } =
19
+ require(path.join(__dirname, 'lib', 'utils.js'));
20
+ const { detectCclineBin, installCcline: _installCcline } = require(path.join(__dirname, 'lib', 'ccline.js'));
21
+ const {
22
+ detectCodexAuth: detectCodexAuthImpl,
23
+ getCodexCoreFiles,
24
+ postCodex: postCodexFlow,
25
+ } = require(path.join(__dirname, 'adapters', 'codex.js'));
26
+ const {
27
+ SETTINGS_TEMPLATE,
28
+ getClaudeCoreFiles,
29
+ detectClaudeAuth: detectClaudeAuthImpl,
30
+ postClaude: postClaudeFlow,
31
+ } = require(path.join(__dirname, 'adapters', 'claude.js'));
32
+
33
+ // ── ANSI ──
34
+
35
+ const c = {
36
+ b: s => `\x1b[1m${s}\x1b[0m`,
37
+ d: s => `\x1b[2m${s}\x1b[0m`,
38
+ red: s => `\x1b[31m${s}\x1b[0m`,
39
+ grn: s => `\x1b[32m${s}\x1b[0m`,
40
+ ylw: s => `\x1b[33m${s}\x1b[0m`,
41
+ blu: s => `\x1b[34m${s}\x1b[0m`,
42
+ mag: s => `\x1b[35m${s}\x1b[0m`,
43
+ cyn: s => `\x1b[36m${s}\x1b[0m`,
44
+ };
45
+
46
+ function banner() {
47
+ console.log(c.cyn(`
48
+ ██████╗ ██████╗ ██████╗ ██████╗ ████████╗
49
+ ██╔════╝██╔════╝██╔══██╗██╔═══██╗╚══██╔══╝
50
+ ██║ ██║ ██████╔╝██║ ██║ ██║
51
+ ██║ ██║ ██╔══██╗██║ ██║ ██║
52
+ ╚██████╗╚██████╗██████╔╝╚██████╔╝ ██║
53
+ ╚═════╝ ╚═════╝╚═════╝ ╚═════╝ ╚═╝`));
54
+ console.log(c.d(` ☠ ccbot-cli · Claude Code 一键配置 v${VERSION}\n`));
55
+ }
56
+
57
+ function divider(title) {
58
+ const line = '─'.repeat(44);
59
+ const pad = ' '.repeat(Math.max(0, 43 - title.length));
60
+ console.log(`\n${c.d('' + line + '')}`);
61
+ console.log(`${c.d('│')} ${c.b(title)}${pad}${c.d('')}`);
62
+ console.log(`${c.d('' + line + '')}`);
63
+ }
64
+
65
+ function step(n, total, msg) { console.log(`\n ${c.cyn(`[${n}/${total}]`)} ${c.b(msg)}`); }
66
+ function ok(msg) { console.log(` ${c.grn('')} ${msg}`); }
67
+ function warn(msg) { console.log(` ${c.ylw('⚠')} ${msg}`); }
68
+ function info(msg) { console.log(` ${c.blu('ℹ')} ${msg}`); }
69
+ function fail(msg) { console.log(` ${c.red('✘')} ${msg}`); }
70
+
71
+ // ── 认证 ──
72
+
73
+ function detectClaudeAuth(settings) {
74
+ return detectClaudeAuthImpl({ settings, HOME, warn });
75
+ }
76
+
77
+ function detectCodexAuth() {
78
+ return detectCodexAuthImpl({ HOME, warn });
79
+ }
80
+
81
+ // ── 模板 ──
82
+
83
+ // ── CLI 参数 ──
84
+
85
+ const args = process.argv.slice(2);
86
+ let target = null;
87
+ let uninstallTarget = null;
88
+ let autoYes = false;
89
+
90
+ for (let i = 0; i < args.length; i++) {
91
+ if (args[i] === '--target' && args[i + 1]) { target = args[++i]; }
92
+ else if (args[i] === '--uninstall' && args[i + 1]) { uninstallTarget = args[++i]; }
93
+ else if (args[i] === '--yes' || args[i] === '-y') { autoYes = true; }
94
+ else if (args[i] === '--help' || args[i] === '-h') {
95
+ banner();
96
+ console.log(`${c.b('用法:')} npx ccbot-cli [选项]
97
+
98
+ ${c.b('选项:')}
99
+ --target ${c.cyn('<claude|codex>')} 安装目标
100
+ --uninstall ${c.cyn('<claude|codex>')} 卸载目标
101
+ --yes, -y 全自动模式
102
+ --help, -h 显示帮助
103
+
104
+ ${c.b('示例:')}
105
+ npx ccbot-cli ${c.d('# 交互菜单')}
106
+ npx ccbot-cli --target claude -y ${c.d('# 零配置一键安装')}
107
+ npx ccbot-cli --uninstall claude ${c.d('# 直接卸载')}
108
+ `);
109
+ process.exit(0);
110
+ }
111
+ }
112
+
113
+ // ── 卸载 ──
114
+
115
+ function runUninstall(tgt) {
116
+ if (!['claude', 'codex'].includes(tgt)) { fail('--uninstall 必须是 claude 或 codex'); process.exit(1); }
117
+ const targetDir = path.join(HOME, `.${tgt}`);
118
+ const backupDir = path.join(targetDir, '.sage-backup');
119
+ const manifestPath = path.join(backupDir, 'manifest.json');
120
+ if (!fs.existsSync(manifestPath)) { fail(`未找到安装记录: ${manifestPath}`); process.exit(1); }
121
+
122
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
123
+ if (manifest.manifest_version && manifest.manifest_version > 1) {
124
+ fail(`manifest 版本 ${manifest.manifest_version} 不兼容,请升级 ccbot-cli 后再卸载`);
125
+ process.exit(1);
126
+ }
127
+ divider(`卸载 ccbot-cli v${manifest.version}`);
128
+
129
+ (manifest.installed || []).forEach(f => {
130
+ const p = path.join(targetDir, f);
131
+ if (fs.existsSync(p)) { rmSafe(p); console.log(` ${c.red('✘')} ${f}`); }
132
+ });
133
+ (manifest.backups || []).forEach(f => {
134
+ const bp = path.join(backupDir, f);
135
+ const tp = path.join(targetDir, f);
136
+ if (fs.existsSync(bp)) { fs.renameSync(bp, tp); ok(`恢复: ${f}`); }
137
+ });
138
+
139
+ rmSafe(backupDir);
140
+ const us = path.join(targetDir, '.sage-uninstall.js');
141
+ if (fs.existsSync(us)) fs.unlinkSync(us);
142
+ console.log('');
143
+ ok(c.b('卸载完成\n'));
144
+ }
145
+
146
+ // ── 安装核心 ──
147
+
148
+ /**
149
+ * 递归扫描 skills 目录,找出所有 user-invocable: true 的 SKILL.md
150
+ * @param {string} skillsDir - skills 源目录绝对路径
151
+ * @returns {Array<{meta: Object, relPath: string, hasScripts: boolean}>}
152
+ */
153
+ function scanInvocableSkills(skillsDir) {
154
+ const results = [];
155
+ function scan(dir) {
156
+ const skillMd = path.join(dir, 'SKILL.md');
157
+ if (fs.existsSync(skillMd)) {
158
+ try {
159
+ const content = fs.readFileSync(skillMd, 'utf8');
160
+ const meta = parseFrontmatter(content);
161
+ if (meta && meta['user-invocable'] === 'true' && meta.name) {
162
+ const relPath = path.relative(skillsDir, dir);
163
+ const scriptsDir = path.join(dir, 'scripts');
164
+ const hasScripts = fs.existsSync(scriptsDir) &&
165
+ fs.readdirSync(scriptsDir).some(f => f.endsWith('.js'));
166
+ results.push({ meta, relPath, hasScripts });
167
+ }
168
+ } catch (e) { /* 解析失败跳过 */ }
169
+ }
170
+ try {
171
+ fs.readdirSync(dir).forEach(sub => {
172
+ const subPath = path.join(dir, sub);
173
+ if (fs.statSync(subPath).isDirectory() && !shouldSkip(sub) && sub !== 'scripts') {
174
+ scan(subPath);
175
+ }
176
+ });
177
+ } catch (e) { /* 读取失败跳过 */ }
178
+ }
179
+ scan(skillsDir);
180
+ return results;
181
+ }
182
+
183
+ const INVOCABLE_TARGETS = {
184
+ claude: {
185
+ dir: 'commands',
186
+ label: '斜杠命令',
187
+ skillRoot: '~/.claude/skills',
188
+ },
189
+ codex: {
190
+ dir: 'prompts',
191
+ label: 'custom prompts',
192
+ skillRoot: '~/.codex/skills',
193
+ },
194
+ };
195
+
196
+ function getInvocableTarget(targetName) {
197
+ const targetCfg = INVOCABLE_TARGETS[targetName];
198
+ if (!targetCfg) throw new Error(`不支持的 invocable target: ${targetName}`);
199
+ return targetCfg;
200
+ }
201
+
202
+ function getSkillPath(skillRoot, skillRelPath) {
203
+ return skillRelPath
204
+ ? `${skillRoot}/${skillRelPath}/SKILL.md`
205
+ : `${skillRoot}/SKILL.md`;
206
+ }
207
+
208
+ function buildCommandFrontmatter(meta) {
209
+ const desc = (meta.description || '').replace(/"/g, '\\"');
210
+ const argHint = meta['argument-hint'];
211
+ const tools = meta['allowed-tools'] || 'Read';
212
+ const lines = ['---', `name: ${meta.name}`, `description: "${desc}"`];
213
+
214
+ if (argHint) lines.push(`argument-hint: "${argHint}"`);
215
+ lines.push(`allowed-tools: ${tools}`);
216
+ lines.push('---', '');
217
+ return lines;
218
+ }
219
+
220
+ function buildClaudeBody(skillPath, meta, hasScripts) {
221
+ const lines = [];
222
+ if (hasScripts) {
223
+ lines.push('以下所有步骤一气呵成,不要在步骤间停顿等待用户输入:', '');
224
+ lines.push(`1. 读取规范:${skillPath}`);
225
+ lines.push(`2. 执行命令:\`node ~/.claude/skills/run_skill.js ${meta.name} $ARGUMENTS\``);
226
+ lines.push('3. 按规范分析输出,完成后续动作', '');
227
+ lines.push('全程不要停顿,不要询问是否继续。');
228
+ return lines;
229
+ }
230
+
231
+ lines.push('读取以下秘典,根据内容为用户提供专业指导:', '');
232
+ lines.push('```', skillPath, '```');
233
+ return lines;
234
+ }
235
+
236
+ function buildCodexPromptBody(skillPath, meta, hasScripts) {
237
+ const lines = [];
238
+ if (meta['argument-hint']) lines.push(`Arguments: ${meta['argument-hint']}`, '');
239
+ lines.push(`Read \`${skillPath}\` before acting.`, '');
240
+ if (hasScripts) {
241
+ lines.push(`Then run \`node ~/.codex/skills/run_skill.js ${meta.name} $ARGUMENTS\`.`);
242
+ lines.push('Do not stop between steps unless blocked by permissions or missing required inputs.');
243
+ lines.push('Use the skill guidance plus script output to complete the task end-to-end.');
244
+ return lines;
245
+ }
246
+
247
+ lines.push('Use that skill as the authoritative playbook for the task.');
248
+ lines.push('Respond with concrete actions instead of generic advice.');
249
+ return lines;
250
+ }
251
+
252
+ function generateInvocableContent(meta, skillRelPath, hasScripts, targetName) {
253
+ const targetCfg = getInvocableTarget(targetName);
254
+ const skillPath = getSkillPath(targetCfg.skillRoot, skillRelPath);
255
+ const lines = targetName === 'claude' ? buildCommandFrontmatter(meta) : [];
256
+ const body = targetName === 'claude'
257
+ ? buildClaudeBody(skillPath, meta, hasScripts)
258
+ : buildCodexPromptBody(skillPath, meta, hasScripts);
259
+ return [...lines, ...body, ''].join('\n');
260
+ }
261
+
262
+ function generateCommandContent(meta, skillRelPath, hasScripts) {
263
+ return generateInvocableContent(meta, skillRelPath, hasScripts, 'claude');
264
+ }
265
+
266
+ function generatePromptContent(meta, skillRelPath, hasScripts) {
267
+ return generateInvocableContent(meta, skillRelPath, hasScripts, 'codex');
268
+ }
269
+
270
+ function installGeneratedArtifacts(skillsSrcDir, targetDir, backupDir, manifest, targetName) {
271
+ const skills = scanInvocableSkills(skillsSrcDir);
272
+ if (skills.length === 0) return 0;
273
+
274
+ const targetCfg = getInvocableTarget(targetName);
275
+ const installDir = path.join(targetDir, targetCfg.dir);
276
+ fs.mkdirSync(installDir, { recursive: true });
277
+
278
+ skills.forEach(({ meta, relPath, hasScripts }) => {
279
+ const fileName = `${meta.name}.md`;
280
+ const destFile = path.join(installDir, fileName);
281
+ const relFile = path.posix.join(targetCfg.dir, fileName);
282
+
283
+ if (fs.existsSync(destFile)) {
284
+ const backupSubdir = path.join(backupDir, targetCfg.dir);
285
+ fs.mkdirSync(backupSubdir, { recursive: true });
286
+ fs.copyFileSync(destFile, path.join(backupSubdir, fileName));
287
+ manifest.backups.push(relFile);
288
+ info(`备份: ${c.d(relFile)}`);
289
+ }
290
+
291
+ const content = generateInvocableContent(meta, relPath, hasScripts, targetName);
292
+ fs.writeFileSync(destFile, content);
293
+ manifest.installed.push(relFile);
294
+ });
295
+
296
+ ok(`${targetCfg.dir}/ ${c.d(`(自动生成 ${skills.length} 个 ${targetCfg.label})`)}`);
297
+ return skills.length;
298
+ }
299
+
300
+ function installGeneratedCommands(skillsSrcDir, targetDir, backupDir, manifest) {
301
+ return installGeneratedArtifacts(skillsSrcDir, targetDir, backupDir, manifest, 'claude');
302
+ }
303
+
304
+ function installGeneratedPrompts(skillsSrcDir, targetDir, backupDir, manifest) {
305
+ return installGeneratedArtifacts(skillsSrcDir, targetDir, backupDir, manifest, 'codex');
306
+ }
307
+
308
+ function backupPathIfExists(targetDir, backupDir, relPath, manifest) {
309
+ const targetPath = path.join(targetDir, relPath);
310
+ if (!fs.existsSync(targetPath)) return false;
311
+
312
+ const backupPath = path.join(backupDir, relPath);
313
+ rmSafe(backupPath);
314
+ copyRecursive(targetPath, backupPath);
315
+ manifest.backups.push(relPath);
316
+ info(`备份: ${c.d(relPath)}`);
317
+ return true;
318
+ }
319
+
320
+ function pruneLegacyCodexSettings(targetDir, backupDir, manifest) {
321
+ const relPath = 'settings.json';
322
+ const settingsPath = path.join(targetDir, relPath);
323
+ if (!fs.existsSync(settingsPath)) return null;
324
+
325
+ backupPathIfExists(targetDir, backupDir, relPath, manifest);
326
+ rmSafe(settingsPath);
327
+ warn('移除 legacy settings.json(Codex 已改用 config.toml)');
328
+ return settingsPath;
329
+ }
330
+
331
+ function installCore(tgt) {
332
+ const targetDir = path.join(HOME, `.${tgt}`);
333
+ const backupDir = path.join(targetDir, '.sage-backup');
334
+ const manifestPath = path.join(backupDir, 'manifest.json');
335
+
336
+ step(1, 3, `安装核心文件 → ${c.cyn(targetDir)}`);
337
+ fs.mkdirSync(backupDir, { recursive: true });
338
+
339
+ const filesToInstall = tgt === 'codex'
340
+ ? getCodexCoreFiles()
341
+ : getClaudeCoreFiles();
342
+
343
+ const manifest = {
344
+ manifest_version: 1, version: VERSION, target: tgt,
345
+ timestamp: new Date().toISOString(), installed: [], backups: []
346
+ };
347
+
348
+ filesToInstall.forEach(({ src, dest }) => {
349
+ const srcPath = path.join(PKG_ROOT, src);
350
+ const destPath = path.join(targetDir, dest);
351
+ if (!fs.existsSync(srcPath)) {
352
+ if (src === 'skills') {
353
+ fail(`核心文件缺失: ${srcPath}\n 请尝试: npm cache clean --force && npx ccbot-cli`);
354
+ process.exit(1);
355
+ }
356
+ warn(`跳过: ${src}`); return;
357
+ }
358
+
359
+ if (fs.existsSync(destPath)) {
360
+ const bp = path.join(backupDir, dest);
361
+ rmSafe(bp); copyRecursive(destPath, bp); manifest.backups.push(dest);
362
+ info(`备份: ${c.d(dest)}`);
363
+ }
364
+ ok(dest);
365
+ rmSafe(destPath); copyRecursive(srcPath, destPath); manifest.installed.push(dest);
366
+ });
367
+
368
+ // 为目标 CLI 自动生成 user-invocable artifacts
369
+ if (tgt === 'claude') {
370
+ const skillsSrc = path.join(PKG_ROOT, 'skills');
371
+ installGeneratedCommands(skillsSrc, targetDir, backupDir, manifest);
372
+ } else if (tgt === 'codex') {
373
+ const skillsSrc = path.join(PKG_ROOT, 'skills');
374
+ installGeneratedPrompts(skillsSrc, targetDir, backupDir, manifest);
375
+ }
376
+
377
+ let settingsPath = null;
378
+ let settings = {};
379
+ if (tgt === 'claude') {
380
+ settingsPath = path.join(targetDir, 'settings.json');
381
+ if (fs.existsSync(settingsPath)) {
382
+ try {
383
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
384
+ } catch (e) {
385
+ warn('settings.json 解析失败,将使用空配置');
386
+ settings = {};
387
+ }
388
+ fs.copyFileSync(settingsPath, path.join(backupDir, 'settings.json'));
389
+ manifest.backups.push('settings.json');
390
+ }
391
+ settings.outputStyle = 'abyss-cultivator';
392
+ ok(`outputStyle = ${c.mag('abyss-cultivator')}`);
393
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
394
+ manifest.installed.push('settings.json');
395
+ } else {
396
+ pruneLegacyCodexSettings(targetDir, backupDir, manifest);
397
+ }
398
+
399
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
400
+
401
+ const uSrc = path.join(PKG_ROOT, 'bin', 'uninstall.js');
402
+ const uDest = path.join(targetDir, '.sage-uninstall.js');
403
+ if (fs.existsSync(uSrc)) { fs.copyFileSync(uSrc, uDest); fs.chmodSync(uDest, '755'); }
404
+
405
+ return { targetDir, settingsPath, settings, manifest, manifestPath };
406
+ }
407
+
408
+ // ── Claude 后续 ──
409
+
410
+ async function postClaude(ctx) {
411
+ await postClaudeFlow({
412
+ ctx,
413
+ autoYes,
414
+ HOME,
415
+ PKG_ROOT,
416
+ step,
417
+ ok,
418
+ warn,
419
+ info,
420
+ c,
421
+ deepMergeNew,
422
+ printMergeLog,
423
+ installCcline: _installCcline,
424
+ });
425
+ }
426
+
427
+ // ── Codex 后续 ──
428
+
429
+ async function postCodex() {
430
+ await postCodexFlow({
431
+ autoYes,
432
+ HOME,
433
+ PKG_ROOT,
434
+ step,
435
+ ok,
436
+ warn,
437
+ info,
438
+ c,
439
+ });
440
+ }
441
+
442
+ // ── 主流程 ──
443
+
444
+ async function main() {
445
+ if (uninstallTarget) { runUninstall(uninstallTarget); return; }
446
+
447
+ banner();
448
+
449
+ if (target) {
450
+ if (!['claude', 'codex'].includes(target)) { fail('--target 必须是 claude 或 codex'); process.exit(1); }
451
+ const ctx = installCore(target);
452
+ if (target === 'claude') await postClaude(ctx);
453
+ else await postCodex();
454
+ finish(ctx);
455
+ return;
456
+ }
457
+
458
+ const { select } = await import('@inquirer/prompts');
459
+ const action = await select({
460
+ message: '请选择操作',
461
+ choices: [
462
+ { name: `安装到 Claude Code ${c.d('(~/.claude/')}${c.d(')')}`, value: 'install-claude' },
463
+ { name: `安装到 Codex CLI ${c.d('(~/.codex/')}${c.d(')')}`, value: 'install-codex' },
464
+ { name: `${c.red('卸载')} Claude Code`, value: 'uninstall-claude' },
465
+ { name: `${c.red('卸载')} Codex CLI`, value: 'uninstall-codex' },
466
+ ],
467
+ });
468
+
469
+ switch (action) {
470
+ case 'install-claude': {
471
+ const ctx = installCore('claude');
472
+ await postClaude(ctx);
473
+ finish(ctx); break;
474
+ }
475
+ case 'install-codex': {
476
+ const ctx = installCore('codex');
477
+ await postCodex();
478
+ finish(ctx); break;
479
+ }
480
+ case 'uninstall-claude': runUninstall('claude'); break;
481
+ case 'uninstall-codex': runUninstall('codex'); break;
482
+ }
483
+ }
484
+
485
+ function finish(ctx) {
486
+ const tgt = ctx.manifest.target;
487
+ divider('安装完成');
488
+ console.log('');
489
+ console.log(` ${c.b('目标:')} ${c.cyn(ctx.targetDir)}`);
490
+ console.log(` ${c.b('版本:')} v${VERSION}`);
491
+ console.log(` ${c.b('文件:')} ${ctx.manifest.installed.length} 个安装, ${ctx.manifest.backups.length} 个备份`);
492
+ console.log(` ${c.b('卸载:')} ${c.d(`npx ccbot-cli --uninstall ${tgt}`)}`);
493
+ console.log('');
494
+ console.log(c.mag(` ⚚ 劫——破——了——!!!\n`));
495
+ }
496
+
497
+ if (require.main === module) {
498
+ main().catch(err => { fail(err.message); process.exit(1); });
499
+ }
500
+
501
+ module.exports = {
502
+ deepMergeNew, detectClaudeAuth, detectCodexAuth,
503
+ detectCclineBin, copyRecursive, shouldSkip, SETTINGS_TEMPLATE,
504
+ scanInvocableSkills,
505
+ generateCommandContent,
506
+ generatePromptContent,
507
+ installGeneratedCommands,
508
+ installGeneratedPrompts,
509
+ };