code-abyss 1.6.0 → 1.6.1

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 (3) hide show
  1. package/README.md +11 -18
  2. package/bin/install.js +181 -169
  3. package/package.json +4 -1
package/README.md CHANGED
@@ -20,18 +20,16 @@
20
20
  npx code-abyss
21
21
  ```
22
22
 
23
- 交互式菜单:
23
+ 交互式菜单(方向键选择,回车确认):
24
24
 
25
25
  ```
26
- ☠️ Code Abyss v1.6.0
26
+ ☠️ Code Abyss v1.6.1
27
27
 
28
- 请选择操作:
29
- 1) 安装到 Claude Code (~/.claude/)
30
- 2) 安装到 Codex CLI (~/.codex/)
31
- 3) 卸载 Claude Code
32
- 4) 卸载 Codex CLI
33
-
34
- 选择 [1/2/3/4]:
28
+ ? 请选择操作 (Use arrow keys)
29
+ 安装到 Claude Code (~/.claude/)
30
+ 安装到 Codex CLI (~/.codex/)
31
+ 卸载 Claude Code
32
+ 卸载 Codex CLI
35
33
  ```
36
34
 
37
35
  也可以直接指定:
@@ -61,17 +59,12 @@ npx code-abyss --uninstall codex # 卸载 Codex CLI
61
59
 
62
60
  未检测到认证时会提示配置,可交互输入或跳过。
63
61
 
64
- 然后进入可选配置:
62
+ 然后进入可选配置(空格选择,回车确认):
65
63
 
66
64
  ```
67
- 核心文件安装完成
68
-
69
- 可选配置:
70
- [1] 写入推荐 settings.json (精细合并,保留现有配置)
71
- [2] 安装 ccline 状态栏 (需要 Nerd Font 字体)
72
- [3] 全部跳过
73
-
74
- 选择 (多选用逗号分隔,如 1,2) [3]:
65
+ ? 选择要安装的配置 (Press <space> to select, <enter> to submit)
66
+ ◉ 精细合并推荐 settings.json (保留现有配置)
67
+ ◯ 安装 ccline 状态栏 (需要 Nerd Font)
75
68
  ```
76
69
 
77
70
  - **settings.json 精细合并**:逐项合并推荐配置,已有的 key 不覆盖,缺失的 key 补上
package/bin/install.js CHANGED
@@ -3,14 +3,54 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const os = require('os');
6
- const readline = require('readline');
7
6
 
8
- const VERSION = '1.6.0';
7
+ const VERSION = '1.6.1';
9
8
  const HOME = os.homedir();
10
9
  const SKIP = ['__pycache__', '.pyc', '.pyo', '.egg-info', '.DS_Store', 'Thumbs.db', '.git'];
11
10
  const PKG_ROOT = path.join(__dirname, '..');
12
11
 
13
- // ── 工具函数 ──
12
+ // ── ANSI ──
13
+
14
+ const c = {
15
+ b: s => `\x1b[1m${s}\x1b[0m`,
16
+ d: s => `\x1b[2m${s}\x1b[0m`,
17
+ red: s => `\x1b[31m${s}\x1b[0m`,
18
+ grn: s => `\x1b[32m${s}\x1b[0m`,
19
+ ylw: s => `\x1b[33m${s}\x1b[0m`,
20
+ blu: s => `\x1b[34m${s}\x1b[0m`,
21
+ mag: s => `\x1b[35m${s}\x1b[0m`,
22
+ cyn: s => `\x1b[36m${s}\x1b[0m`,
23
+ };
24
+
25
+ function banner() {
26
+ console.log(c.mag(`
27
+ ██████╗ ██████╗ ██████╗ ███████╗
28
+ ██╔════╝██╔═══██╗██╔══██╗██╔════╝
29
+ ██║ ██║ ██║██║ ██║█████╗
30
+ ██║ ██║ ██║██║ ██║██╔══╝
31
+ ╚██████╗╚██████╔╝██████╔╝███████╗
32
+ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝
33
+ █████╗ ██████╗ ██╗ ██╗███████╗███████╗
34
+ ██╔══██╗██╔══██╗╚██╗ ██╔╝██╔════╝██╔════╝
35
+ ███████║██████╔╝ ╚████╔╝ ███████╗███████╗
36
+ ██╔══██║██╔══██╗ ╚██╔╝ ╚════██║╚════██║
37
+ ██║ ██║██████╔╝ ██║ ███████║███████║
38
+ ╚═╝ ╚═╝╚═════╝ ╚═╝ ╚══════╝╚══════╝`));
39
+ console.log(c.d(` ☠️ 邪修红尘仙 · 宿命深渊 v${VERSION}\n`));
40
+ }
41
+
42
+ function divider(title) {
43
+ const line = '─'.repeat(44);
44
+ console.log(`\n${c.d('┌' + line + '┐')}\n${c.d('│')} ${c.b(title)}${' '.repeat(Math.max(0, 43 - title.length))}${c.d('│')}\n${c.d('└' + line + '┘')}`);
45
+ }
46
+
47
+ function step(n, total, msg) { console.log(`\n ${c.cyn(`[${n}/${total}]`)} ${c.b(msg)}`); }
48
+ function ok(msg) { console.log(` ${c.grn('✔')} ${msg}`); }
49
+ function warn(msg) { console.log(` ${c.ylw('⚠')} ${msg}`); }
50
+ function info(msg) { console.log(` ${c.blu('ℹ')} ${msg}`); }
51
+ function fail(msg) { console.log(` ${c.red('✘')} ${msg}`); }
52
+
53
+ // ── 工具 ──
14
54
 
15
55
  function shouldSkip(name) { return SKIP.some(p => name.includes(p)); }
16
56
 
@@ -32,62 +72,59 @@ function rmSafe(p) {
32
72
  if (fs.existsSync(p)) fs.rmSync(p, { recursive: true, force: true });
33
73
  }
34
74
 
35
- function ask(rl, q) {
36
- return new Promise(r => rl.question(q, r));
37
- }
38
-
39
75
  function deepMergeNew(target, source, prefix, log) {
40
76
  for (const key of Object.keys(source)) {
41
77
  const fullKey = prefix ? `${prefix}.${key}` : key;
42
78
  if (typeof source[key] === 'object' && source[key] !== null && !Array.isArray(source[key])) {
43
79
  if (!target[key] || typeof target[key] !== 'object') {
44
80
  target[key] = {};
45
- log.push(`⚙️ 合并: ${fullKey} (新建对象)`);
81
+ log.push({ k: fullKey, a: 'new', v: '{}' });
46
82
  }
47
83
  deepMergeNew(target[key], source[key], fullKey, log);
48
84
  } else if (Array.isArray(source[key]) && Array.isArray(target[key])) {
49
85
  const added = source[key].filter(v => !target[key].includes(v));
50
86
  if (added.length > 0) {
51
87
  target[key] = [...target[key], ...added];
52
- log.push(`⚙️ 合并: ${fullKey} (补充 ${added.length} )`);
88
+ log.push({ k: fullKey, a: 'add', v: `+${added.length}` });
53
89
  } else {
54
- log.push(`⚙️ 保留: ${fullKey} (已完整)`);
90
+ log.push({ k: fullKey, a: 'keep', v: '完整' });
55
91
  }
56
92
  } else if (key in target) {
57
- log.push(`⚙️ 保留: ${fullKey} (已存在: ${JSON.stringify(target[key])})`);
93
+ log.push({ k: fullKey, a: 'keep', v: JSON.stringify(target[key]) });
58
94
  } else {
59
95
  target[key] = source[key];
60
- log.push(`⚙️ 合并: ${fullKey} = ${JSON.stringify(source[key])}`);
96
+ log.push({ k: fullKey, a: 'set', v: JSON.stringify(source[key]) });
61
97
  }
62
98
  }
63
99
  return target;
64
100
  }
65
101
 
66
- // ── 认证检测 ──
102
+ function printMergeLog(log) {
103
+ log.forEach(({ k, a, v }) => {
104
+ if (a === 'keep') console.log(` ${c.d('·')} ${c.d(`${k} (保留: ${v})`)}`);
105
+ else console.log(` ${c.grn('+')} ${c.cyn(k)} = ${v}`);
106
+ });
107
+ }
108
+
109
+ // ── 认证 ──
67
110
 
68
111
  function detectClaudeAuth(settings) {
69
- // 1. settings.json 中有自定义 provider
70
112
  const env = settings.env || {};
71
113
  if (env.ANTHROPIC_BASE_URL && env.ANTHROPIC_AUTH_TOKEN) return { type: 'custom', detail: env.ANTHROPIC_BASE_URL };
72
- // 2. 环境变量中有官方 key
73
114
  if (process.env.ANTHROPIC_API_KEY) return { type: 'env', detail: 'ANTHROPIC_API_KEY' };
74
- // 3. 环境变量中有自定义 provider
75
115
  if (process.env.ANTHROPIC_BASE_URL && process.env.ANTHROPIC_AUTH_TOKEN) return { type: 'env-custom', detail: process.env.ANTHROPIC_BASE_URL };
76
- // 4. 已通过 claude login 登录
77
116
  const cred = path.join(HOME, '.claude', '.credentials.json');
78
117
  if (fs.existsSync(cred)) {
79
118
  try {
80
- const c = JSON.parse(fs.readFileSync(cred, 'utf8'));
81
- if (c.claudeAiOauth || c.apiKey) return { type: 'login', detail: 'claude login' };
119
+ const cc = JSON.parse(fs.readFileSync(cred, 'utf8'));
120
+ if (cc.claudeAiOauth || cc.apiKey) return { type: 'login', detail: 'claude login' };
82
121
  } catch (e) {}
83
122
  }
84
123
  return null;
85
124
  }
86
125
 
87
126
  function detectCodexAuth() {
88
- // 1. 环境变量
89
127
  if (process.env.OPENAI_API_KEY) return { type: 'env', detail: 'OPENAI_API_KEY' };
90
- // 2. auth.json
91
128
  const auth = path.join(HOME, '.codex', 'auth.json');
92
129
  if (fs.existsSync(auth)) {
93
130
  try {
@@ -95,11 +132,9 @@ function detectCodexAuth() {
95
132
  if (a.token || a.api_key) return { type: 'login', detail: 'codex login' };
96
133
  } catch (e) {}
97
134
  }
98
- // 3. config.toml 中有自定义 provider
99
135
  const cfg = path.join(HOME, '.codex', 'config.toml');
100
136
  if (fs.existsSync(cfg)) {
101
- const content = fs.readFileSync(cfg, 'utf8');
102
- if (content.includes('base_url')) return { type: 'custom', detail: 'config.toml' };
137
+ if (fs.readFileSync(cfg, 'utf8').includes('base_url')) return { type: 'custom', detail: 'config.toml' };
103
138
  }
104
139
  return null;
105
140
  }
@@ -145,22 +180,19 @@ for (let i = 0; i < args.length; i++) {
145
180
  else if (args[i] === '--uninstall' && args[i + 1]) { uninstallTarget = args[++i]; }
146
181
  else if (args[i] === '--yes' || args[i] === '-y') { autoYes = true; }
147
182
  else if (args[i] === '--help' || args[i] === '-h') {
148
- console.log(`
149
- ☠️ Code Abyss v${VERSION} - 邪修红尘仙·宿命深渊
183
+ banner();
184
+ console.log(`${c.b('用法:')} npx code-abyss [选项]
150
185
 
151
- 用法:
152
- npx code-abyss [选项]
153
-
154
- 选项:
155
- --target <claude|codex> 安装目标
156
- --uninstall <claude|codex> 卸载目标
157
- --yes, -y 全自动模式 (跳过所有可选提示)
186
+ ${c.b('选项:')}
187
+ --target ${c.cyn('<claude|codex>')} 安装目标
188
+ --uninstall ${c.cyn('<claude|codex>')} 卸载目标
189
+ --yes, -y 全自动模式
158
190
  --help, -h 显示帮助
159
191
 
160
- 示例:
161
- npx code-abyss # 交互菜单
162
- npx code-abyss --target claude -y # 零配置一键安装
163
- npx code-abyss --uninstall claude # 直接卸载
192
+ ${c.b('示例:')}
193
+ npx code-abyss ${c.d('# 交互菜单')}
194
+ npx code-abyss --target claude -y ${c.d('# 零配置一键安装')}
195
+ npx code-abyss --uninstall claude ${c.d('# 直接卸载')}
164
196
  `);
165
197
  process.exit(0);
166
198
  }
@@ -169,36 +201,30 @@ for (let i = 0; i < args.length; i++) {
169
201
  // ── 卸载 ──
170
202
 
171
203
  function runUninstall(tgt) {
172
- if (!['claude', 'codex'].includes(tgt)) {
173
- console.error('❌ --uninstall 必须是 claude 或 codex');
174
- process.exit(1);
175
- }
204
+ if (!['claude', 'codex'].includes(tgt)) { fail('--uninstall 必须是 claude 或 codex'); process.exit(1); }
176
205
  const targetDir = path.join(HOME, `.${tgt}`);
177
206
  const backupDir = path.join(targetDir, '.sage-backup');
178
207
  const manifestPath = path.join(backupDir, 'manifest.json');
179
-
180
- if (!fs.existsSync(manifestPath)) {
181
- console.error(`❌ 未找到安装记录: ${manifestPath}`);
182
- process.exit(1);
183
- }
208
+ if (!fs.existsSync(manifestPath)) { fail(`未找到安装记录: ${manifestPath}`); process.exit(1); }
184
209
 
185
210
  const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
186
- console.log(`\n🗑️ 卸载 Code Abyss v${manifest.version} (${tgt})...\n`);
211
+ divider(`卸载 Code Abyss v${manifest.version}`);
187
212
 
188
213
  (manifest.installed || []).forEach(f => {
189
214
  const p = path.join(targetDir, f);
190
- if (fs.existsSync(p)) { rmSafe(p); console.log(`🗑️ 删除: ${f}`); }
215
+ if (fs.existsSync(p)) { rmSafe(p); console.log(` ${c.red('✘')} ${f}`); }
191
216
  });
192
217
  (manifest.backups || []).forEach(f => {
193
218
  const bp = path.join(backupDir, f);
194
219
  const tp = path.join(targetDir, f);
195
- if (fs.existsSync(bp)) { fs.renameSync(bp, tp); console.log(`✅ 恢复: ${f}`); }
220
+ if (fs.existsSync(bp)) { fs.renameSync(bp, tp); ok(`恢复: ${f}`); }
196
221
  });
197
222
 
198
223
  rmSafe(backupDir);
199
224
  const us = path.join(targetDir, '.sage-uninstall.js');
200
225
  if (fs.existsSync(us)) fs.unlinkSync(us);
201
- console.log('\n✅ 卸载完成\n');
226
+ console.log('');
227
+ ok(c.b('卸载完成\n'));
202
228
  }
203
229
 
204
230
  // ── 安装核心 ──
@@ -208,7 +234,7 @@ function installCore(tgt) {
208
234
  const backupDir = path.join(targetDir, '.sage-backup');
209
235
  const manifestPath = path.join(backupDir, 'manifest.json');
210
236
 
211
- console.log(`\n☠️ 开始安装到 ${targetDir}\n`);
237
+ step(1, 3, `安装核心文件 → ${c.cyn(targetDir)}`);
212
238
  fs.mkdirSync(backupDir, { recursive: true });
213
239
 
214
240
  const filesToInstall = [
@@ -218,26 +244,21 @@ function installCore(tgt) {
218
244
  { src: 'skills', dest: 'skills' }
219
245
  ].filter(f => f.dest !== null);
220
246
 
221
- const manifest = {
222
- version: VERSION, target: tgt,
223
- timestamp: new Date().toISOString(),
224
- installed: [], backups: []
225
- };
247
+ const manifest = { version: VERSION, target: tgt, timestamp: new Date().toISOString(), installed: [], backups: [] };
226
248
 
227
249
  filesToInstall.forEach(({ src, dest }) => {
228
250
  const srcPath = path.join(PKG_ROOT, src);
229
251
  const destPath = path.join(targetDir, dest);
230
- if (!fs.existsSync(srcPath)) { console.warn(`⚠️ 跳过: ${src} (源文件不存在)`); return; }
252
+ if (!fs.existsSync(srcPath)) { warn(`跳过: ${src}`); return; }
231
253
  if (fs.existsSync(destPath)) {
232
254
  const bp = path.join(backupDir, dest);
233
- console.log(`📦 备份: ${dest}`);
234
255
  rmSafe(bp); copyRecursive(destPath, bp); manifest.backups.push(dest);
256
+ info(`备份: ${c.d(dest)}`);
235
257
  }
236
- console.log(`📝 安装: ${dest}`);
258
+ ok(dest);
237
259
  rmSafe(destPath); copyRecursive(srcPath, destPath); manifest.installed.push(dest);
238
260
  });
239
261
 
240
- // settings.json 最小写入
241
262
  const settingsPath = path.join(targetDir, 'settings.json');
242
263
  let settings = {};
243
264
  if (fs.existsSync(settingsPath)) {
@@ -247,84 +268,79 @@ function installCore(tgt) {
247
268
  }
248
269
  if (tgt === 'claude') {
249
270
  settings.outputStyle = 'abyss-cultivator';
250
- console.log(`⚙️ 配置: outputStyle = abyss-cultivator`);
271
+ ok(`outputStyle = ${c.mag('abyss-cultivator')}`);
251
272
  }
252
273
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
253
274
  manifest.installed.push('settings.json');
254
-
255
275
  fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
256
276
 
257
- // 备用卸载脚本
258
277
  const uSrc = path.join(PKG_ROOT, 'bin', 'uninstall.js');
259
278
  const uDest = path.join(targetDir, '.sage-uninstall.js');
260
279
  if (fs.existsSync(uSrc)) { fs.copyFileSync(uSrc, uDest); fs.chmodSync(uDest, '755'); }
261
280
 
262
- console.log(`\n✅ 核心文件安装完成\n`);
263
281
  return { targetDir, settingsPath, settings, manifest, manifestPath };
264
282
  }
265
283
 
266
- // ── Claude 后续配置 ──
284
+ // ── Claude 后续 ──
285
+
286
+ async function postClaude(ctx) {
287
+ const { select, checkbox, confirm, input } = require('@inquirer/prompts');
267
288
 
268
- async function postClaude(rl, ctx) {
269
- // 认证检测
289
+ step(2, 3, '认证检测');
270
290
  const auth = detectClaudeAuth(ctx.settings);
271
- console.log('── 认证检测 ──');
272
291
  if (auth) {
273
- console.log(`✅ 已检测到认证: [${auth.type}] ${auth.detail}`);
292
+ ok(`${c.b(auth.type)} ${auth.detail}`);
274
293
  } else {
275
- console.log('⚠️ 未检测到 API 认证');
276
- console.log(' 支持方式:');
277
- console.log(' a) claude login (官方账号)');
278
- console.log(' b) 环境变量 ANTHROPIC_API_KEY');
279
- console.log(' c) 自定义 provider (base_url + token)');
294
+ warn('未检测到 API 认证');
295
+ info(`支持: ${c.cyn('claude login')} | ${c.cyn('ANTHROPIC_API_KEY')} | ${c.cyn('自定义 provider')}`);
280
296
  if (!autoYes) {
281
- const ans = (await ask(rl, '\n配置自定义 provider? [y/N]: ')).trim().toLowerCase();
282
- if (ans === 'y') {
297
+ const doCfg = await confirm({ message: '配置自定义 provider?', default: false });
298
+ if (doCfg) {
283
299
  if (!ctx.settings.env) ctx.settings.env = {};
284
- const url = (await ask(rl, 'ANTHROPIC_BASE_URL: ')).trim();
285
- const token = (await ask(rl, 'ANTHROPIC_AUTH_TOKEN: ')).trim();
300
+ const url = await input({ message: 'ANTHROPIC_BASE_URL:' });
301
+ const token = await input({ message: 'ANTHROPIC_AUTH_TOKEN:' });
286
302
  if (url) ctx.settings.env.ANTHROPIC_BASE_URL = url;
287
303
  if (token) ctx.settings.env.ANTHROPIC_AUTH_TOKEN = token;
288
304
  fs.writeFileSync(ctx.settingsPath, JSON.stringify(ctx.settings, null, 2) + '\n');
289
- console.log('provider 已配置');
305
+ ok('provider 已配置');
290
306
  }
291
307
  }
292
308
  }
293
309
 
294
- // 可选配置(一次多选)
310
+ step(3, 3, '可选配置');
295
311
  if (autoYes) {
296
- // 全自动:合并 settings,跳过 ccline
297
- console.log('\n── 自动配置 (--yes) ──');
312
+ info('自动模式: 合并推荐配置');
298
313
  const log = [];
299
314
  deepMergeNew(ctx.settings, SETTINGS_TEMPLATE, '', log);
300
- log.forEach(l => console.log(l));
315
+ printMergeLog(log);
301
316
  fs.writeFileSync(ctx.settingsPath, JSON.stringify(ctx.settings, null, 2) + '\n');
302
- console.log('settings.json 合并完成');
317
+ ok('settings.json 合并完成');
303
318
  return;
304
319
  }
305
320
 
306
- console.log('\n── 可选配置 ──');
307
- console.log(' [1] 精细合并推荐 settings.json (保留现有配置)');
308
- console.log(' [2] 安装 ccline 状态栏 (需要 Nerd Font)');
309
- console.log(' [3] 全部跳过');
310
- const answer = (await ask(rl, '\n选择 (多选用逗号分隔,如 1,2) [3]: ')).trim() || '3';
311
- const choices = answer.split(',').map(s => s.trim());
321
+ const choices = await checkbox({
322
+ message: '选择要安装的配置 (空格选择, 回车确认)',
323
+ choices: [
324
+ { name: '精细合并推荐 settings.json (保留现有配置)', value: 'settings', checked: true },
325
+ { name: '安装 ccline 状态栏 (需要 Nerd Font)', value: 'ccline' },
326
+ ],
327
+ });
312
328
 
313
- if (choices.includes('1')) {
314
- console.log('\n📋 精细合并 settings.json...\n');
329
+ if (choices.includes('settings')) {
315
330
  const log = [];
316
331
  deepMergeNew(ctx.settings, SETTINGS_TEMPLATE, '', log);
317
- log.forEach(l => console.log(l));
332
+ printMergeLog(log);
318
333
  fs.writeFileSync(ctx.settingsPath, JSON.stringify(ctx.settings, null, 2) + '\n');
319
- console.log('\n✅ settings.json 合并完成');
334
+ ok('settings.json 合并完成');
320
335
  }
321
- if (choices.includes('2')) {
336
+ if (choices.includes('ccline')) {
322
337
  await installCcline(ctx);
323
338
  }
324
339
  }
325
340
 
326
341
  async function installCcline(ctx) {
327
- console.log('\n📋 安装 ccline 状态栏...\n');
342
+ console.log('');
343
+ info('安装 ccline 状态栏...');
328
344
  const { execSync } = require('child_process');
329
345
  const cclineBin = path.join(HOME, '.claude', 'ccline', 'ccline');
330
346
 
@@ -333,91 +349,84 @@ async function installCcline(ctx) {
333
349
  if (!installed && fs.existsSync(cclineBin)) installed = true;
334
350
 
335
351
  if (!installed) {
336
- console.log('📦 ccline 未检测到,正在安装...');
352
+ info('ccline 未检测到,正在安装...');
337
353
  try {
338
354
  execSync('npm install -g @cometix/ccline', { stdio: 'inherit' });
339
355
  installed = true;
340
- console.log('ccline 安装成功');
356
+ ok('ccline 安装成功');
341
357
  } catch (e) {
342
- console.warn('⚠️ npm install -g @cometix/ccline 失败,请手动安装');
343
- console.warn(' 或从 https://github.com/Haleclipse/CCometixLine/releases 下载');
358
+ warn('npm install -g @cometix/ccline 失败');
359
+ info(`手动: ${c.cyn('https://github.com/Haleclipse/CCometixLine/releases')}`);
344
360
  }
345
361
  } else {
346
- console.log('ccline 已安装');
362
+ ok('ccline 已安装');
347
363
  }
348
364
 
349
365
  const cclineConfig = path.join(HOME, '.claude', 'ccline', 'config.toml');
350
366
  if (installed && !fs.existsSync(cclineConfig)) {
351
- try { execSync('ccline --init', { stdio: 'inherit' }); console.log('⚙️ ccline 默认配置已生成'); }
352
- catch (e) { console.warn('⚠️ ccline --init 失败,可手动运行: ccline --init'); }
367
+ try { execSync('ccline --init', { stdio: 'inherit' }); ok('ccline 默认配置已生成'); }
368
+ catch (e) { warn('ccline --init 失败,可手动运行'); }
353
369
  } else if (fs.existsSync(cclineConfig)) {
354
- console.log('⚙️ 保留: ccline/config.toml (已存在)');
370
+ ok('ccline/config.toml (已存在)');
355
371
  }
356
372
 
357
373
  const log = [];
358
374
  deepMergeNew(ctx.settings, CCLINE_STATUS_LINE, '', log);
359
- log.forEach(l => console.log(l));
375
+ printMergeLog(log);
360
376
  fs.writeFileSync(ctx.settingsPath, JSON.stringify(ctx.settings, null, 2) + '\n');
361
377
 
362
- console.log(`
363
- ⚠️ ccline 需要 Nerd Font 字体才能正确显示图标
364
- 推荐: FiraCode Nerd Font / JetBrainsMono Nerd Font
365
- 下载: https://www.nerdfonts.com/
366
- 配置: ccline --config (交互式 TUI 编辑器)
367
- `);
368
- console.log('✅ ccline 配置完成');
378
+ console.log('');
379
+ warn(`需要 ${c.b('Nerd Font')} 字体`);
380
+ info(`推荐: FiraCode Nerd Font / JetBrainsMono Nerd Font`);
381
+ info(`下载: ${c.cyn('https://www.nerdfonts.com/')}`);
382
+ info(`配置: ${c.cyn('ccline --config')}`);
383
+ ok('ccline 配置完成');
369
384
  }
370
385
 
371
- // ── Codex 后续配置 ──
386
+ // ── Codex 后续 ──
372
387
 
373
- async function postCodex(rl) {
388
+ async function postCodex() {
389
+ const { select, confirm, input } = require('@inquirer/prompts');
374
390
  const cfgPath = path.join(HOME, '.codex', 'config.toml');
375
391
  const exists = fs.existsSync(cfgPath);
376
392
 
377
- // 认证检测
393
+ step(2, 3, '认证检测');
378
394
  const auth = detectCodexAuth();
379
- console.log('── 认证检测 ──');
380
395
  if (auth) {
381
- console.log(`✅ 已检测到认证: [${auth.type}] ${auth.detail}`);
396
+ ok(`${c.b(auth.type)} ${auth.detail}`);
382
397
  } else {
383
- console.log('⚠️ 未检测到 API 认证');
384
- console.log(' 支持方式:');
385
- console.log(' a) codex login (官方账号)');
386
- console.log(' b) 环境变量 OPENAI_API_KEY');
387
- console.log(' c) 自定义 provider (config.toml 中配置 base_url)');
398
+ warn('未检测到 API 认证');
399
+ info(`支持: ${c.cyn('codex login')} | ${c.cyn('OPENAI_API_KEY')} | ${c.cyn('自定义 provider')}`);
388
400
  }
389
401
 
402
+ step(3, 3, '可选配置');
390
403
  if (autoYes) {
391
- // 全自动:不存在则写入模板
392
404
  if (!exists) {
393
405
  const src = path.join(PKG_ROOT, 'config', 'codex-config.example.toml');
394
406
  if (fs.existsSync(src)) {
395
407
  fs.copyFileSync(src, cfgPath);
396
- console.log('\n⚙️ 写入: ~/.codex/config.toml (模板)');
397
- console.log('⚠️ 请编辑 base_url 和 model 为你的实际配置');
408
+ ok('写入: ~/.codex/config.toml (模板)');
409
+ warn('请编辑 base_url 和 model');
398
410
  }
399
411
  } else {
400
- console.log('config.toml 已存在');
412
+ ok('config.toml 已存在');
401
413
  }
402
414
  return;
403
415
  }
404
416
 
405
417
  if (!exists) {
406
- console.log('\n⚠️ 未检测到 ~/.codex/config.toml');
407
- console.log('\n [1] 写入推荐 config.toml (含自定义 provider 模板)');
408
- console.log(' [2] 跳过');
409
- const answer = (await ask(rl, '\n选择 [1/2] [2]: ')).trim() || '2';
410
- if (answer === '1') {
418
+ warn('未检测到 ~/.codex/config.toml');
419
+ const doWrite = await confirm({ message: '写入推荐 config.toml (含自定义 provider 模板)?', default: true });
420
+ if (doWrite) {
411
421
  const src = path.join(PKG_ROOT, 'config', 'codex-config.example.toml');
412
422
  if (fs.existsSync(src)) {
413
423
  fs.copyFileSync(src, cfgPath);
414
- console.log('\n⚙️ 写入: ~/.codex/config.toml');
415
- console.log('⚠️ 请编辑 base_url 和 model 为你的实际配置');
424
+ ok('写入: ~/.codex/config.toml');
425
+ warn('请编辑 base_url 和 model');
416
426
  }
417
- console.log('✅ Codex 配置完成\n');
418
427
  }
419
428
  } else {
420
- console.log('config.toml 已存在');
429
+ ok('config.toml 已存在');
421
430
  }
422
431
  }
423
432
 
@@ -426,51 +435,54 @@ async function postCodex(rl) {
426
435
  async function main() {
427
436
  if (uninstallTarget) { runUninstall(uninstallTarget); return; }
428
437
 
438
+ const { select } = require('@inquirer/prompts');
439
+ banner();
440
+
429
441
  if (target) {
430
- if (!['claude', 'codex'].includes(target)) {
431
- console.error('❌ --target 必须是 claude 或 codex');
432
- process.exit(1);
433
- }
434
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
442
+ if (!['claude', 'codex'].includes(target)) { fail('--target 必须是 claude 或 codex'); process.exit(1); }
435
443
  const ctx = installCore(target);
436
- if (target === 'claude') await postClaude(rl, ctx);
437
- else await postCodex(rl);
438
- rl.close();
439
- finish(target);
444
+ if (target === 'claude') await postClaude(ctx);
445
+ else await postCodex();
446
+ finish(ctx);
440
447
  return;
441
448
  }
442
449
 
443
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
444
- console.log(`☠️ Code Abyss v${VERSION}\n`);
445
- console.log('请选择操作:');
446
- console.log(' 1) 安装到 Claude Code (~/.claude/)');
447
- console.log(' 2) 安装到 Codex CLI (~/.codex/)');
448
- console.log(' 3) 卸载 Claude Code');
449
- console.log(' 4) 卸载 Codex CLI');
450
-
451
- const choice = await ask(rl, '\n选择 [1/2/3/4]: ');
452
- switch (choice.trim()) {
453
- case '1': {
450
+ const action = await select({
451
+ message: '请选择操作',
452
+ choices: [
453
+ { name: `安装到 Claude Code ${c.d('(~/.claude/')}${c.d(')')}`, value: 'install-claude' },
454
+ { name: `安装到 Codex CLI ${c.d('(~/.codex/')}${c.d(')')}`, value: 'install-codex' },
455
+ { name: `${c.red('卸载')} Claude Code`, value: 'uninstall-claude' },
456
+ { name: `${c.red('卸载')} Codex CLI`, value: 'uninstall-codex' },
457
+ ],
458
+ });
459
+
460
+ switch (action) {
461
+ case 'install-claude': {
454
462
  const ctx = installCore('claude');
455
- await postClaude(rl, ctx);
456
- rl.close(); finish('claude'); break;
463
+ await postClaude(ctx);
464
+ finish(ctx); break;
457
465
  }
458
- case '2': {
466
+ case 'install-codex': {
459
467
  const ctx = installCore('codex');
460
- await postCodex(rl);
461
- rl.close(); finish('codex'); break;
468
+ await postCodex();
469
+ finish(ctx); break;
462
470
  }
463
- case '3': rl.close(); runUninstall('claude'); break;
464
- case '4': rl.close(); runUninstall('codex'); break;
465
- default: rl.close(); console.error('❌ 无效选择'); process.exit(1);
471
+ case 'uninstall-claude': runUninstall('claude'); break;
472
+ case 'uninstall-codex': runUninstall('codex'); break;
466
473
  }
467
474
  }
468
475
 
469
- function finish(tgt) {
470
- const dir = path.join(HOME, `.${tgt}`);
471
- console.log(`\n⚚ 劫——破——了——!!!\n`);
472
- console.log(`✅ 安装完成: ${dir}`);
473
- console.log(`\n卸载命令: npx code-abyss --uninstall ${tgt}\n`);
476
+ function finish(ctx) {
477
+ const tgt = ctx.manifest.target;
478
+ divider('安装完成');
479
+ console.log('');
480
+ console.log(` ${c.b('目标:')} ${c.cyn(ctx.targetDir)}`);
481
+ console.log(` ${c.b('版本:')} v${VERSION}`);
482
+ console.log(` ${c.b('文件:')} ${ctx.manifest.installed.length} 个安装, ${ctx.manifest.backups.length} 个备份`);
483
+ console.log(` ${c.b('卸载:')} ${c.d(`npx code-abyss --uninstall ${tgt}`)}`);
484
+ console.log('');
485
+ console.log(c.mag(` ⚚ 劫——破——了——!!!\n`));
474
486
  }
475
487
 
476
- main().catch(err => { console.error('❌ 错误:', err.message); process.exit(1); });
488
+ main().catch(err => { fail(err.message); process.exit(1); });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-abyss",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "description": "邪修红尘仙·宿命深渊 - 一键为 Claude Code / Codex CLI 注入邪修人格与安全工程知识体系",
5
5
  "keywords": [
6
6
  "claude",
@@ -37,5 +37,8 @@
37
37
  },
38
38
  "scripts": {
39
39
  "test": "echo \"No tests yet\" && exit 0"
40
+ },
41
+ "dependencies": {
42
+ "@inquirer/prompts": "^8.2.0"
40
43
  }
41
44
  }