aihezu 1.8.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/ccinstall.js CHANGED
@@ -1,483 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const readline = require('readline');
4
- const path = require('path');
5
- const os = require('os');
6
- const fs = require('fs');
7
- const { modifyHostsFile } = require('../lib/hosts');
8
- const { cleanCache } = require('../lib/cache');
3
+ const installCommand = require('../commands/install');
4
+ const claudeService = require('../services/claude');
9
5
 
10
- const homeDir = os.homedir();
11
- const claudeSettingsPath = path.join(homeDir, '.claude', 'settings.json');
12
- const claudeDir = path.join(homeDir, '.claude');
13
- const codexDir = path.join(homeDir, '.codex');
14
- const codexConfigPath = path.join(codexDir, 'config.toml');
15
- const codexAuthPath = path.join(codexDir, 'auth.json');
16
- const DEFAULT_CLAUDE_API_BASE = 'https://cc.aihezu.dev/api';
17
- const DEFAULT_CODEX_BASE_URL = 'https://cc.aihezu.dev/openai';
18
- const PROVIDERS = {
19
- CLAUDE: 'claude',
20
- CODEX: 'codex'
21
- };
22
- const cliArgs = process.argv.slice(2);
6
+ console.warn("⚠️ DEPRECATION WARNING: 'ccinstall' is deprecated.");
7
+ console.warn(
8
+ " Please use 'aihezu install claude' in the future.\n"
9
+ );
23
10
 
24
- function askQuestion(rl, question) {
25
- return new Promise(resolve => rl.question(question, answer => resolve(answer.trim())));
26
- }
27
-
28
- function getArgValue(argv, keys) {
29
- for (let i = 0; i < argv.length; i++) {
30
- const arg = argv[i];
31
- const [key, inlineValue] = arg.split('=');
32
- if (keys.includes(key)) {
33
- return (inlineValue || argv[i + 1] || '').trim();
34
- }
35
- }
36
- return '';
37
- }
38
-
39
- function parseProviderArg(argv) {
40
- const value = getArgValue(argv, ['--provider', '--product', '--service', '--mode']);
41
- return value.toLowerCase();
42
- }
43
-
44
- function parseApiBaseInput(argv) {
45
- let apiBaseInput = '';
46
-
47
- for (let i = 0; i < argv.length; i++) {
48
- const arg = argv[i];
49
-
50
- if (['--api', '--api-url', '--api-base', '--domain'].includes(arg)) {
51
- apiBaseInput = argv[i + 1] || '';
52
- i++;
53
- continue;
54
- }
55
-
56
- if (!arg.startsWith('-') && !apiBaseInput) {
57
- apiBaseInput = arg;
58
- }
59
- }
60
-
61
- return apiBaseInput.trim();
62
- }
63
-
64
- function normalizeClaudeApiBaseUrl(input) {
65
- if (!input) return DEFAULT_CLAUDE_API_BASE;
66
-
67
- let normalized = input;
68
-
69
- if (!/^https?:\/\//i.test(normalized)) {
70
- normalized = `https://${normalized}`;
71
- }
72
-
73
- let url;
74
- try {
75
- url = new URL(normalized);
76
- } catch (error) {
77
- throw new Error('无效的域名或 URL,请输入类似 your-org.aihezu.dev 或 https://your-org.aihezu.dev');
78
- }
79
-
80
- let pathname = url.pathname.replace(/\/+$/, '');
81
- if (!pathname || pathname === '/') {
82
- pathname = '/api';
83
- }
84
-
85
- url.pathname = pathname;
86
- url.search = '';
87
- url.hash = '';
88
-
89
- return `${url.origin}${url.pathname}`;
90
- }
91
-
92
- function normalizeCodexBaseUrl(input) {
93
- if (!input) return DEFAULT_CODEX_BASE_URL;
94
-
95
- let normalized = input;
96
-
97
- if (!/^https?:\/\//i.test(normalized)) {
98
- normalized = `https://${normalized}`;
99
- }
100
-
101
- let url;
102
- try {
103
- url = new URL(normalized);
104
- } catch (error) {
105
- throw new Error('无效的域名或 URL,请输入类似 your-org.aihezu.dev 或 https://your-org.aihezu.dev');
106
- }
107
-
108
- let pathname = url.pathname.replace(/\/+$/, '');
109
- if (!pathname || pathname === '/') {
110
- pathname = '/openai';
111
- }
112
-
113
- url.pathname = pathname;
114
- url.search = '';
115
- url.hash = '';
116
-
117
- return `${url.origin}${url.pathname}`;
118
- }
119
-
120
- function resolveProvider(input) {
121
- const value = (input || '').toLowerCase();
122
- if (['2', 'codex', 'c'].includes(value)) return PROVIDERS.CODEX;
123
- return PROVIDERS.CLAUDE;
124
- }
125
-
126
- function backupFile(filePath) {
127
- if (!fs.existsSync(filePath)) return null;
128
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
129
- const backupPath = `${filePath}.backup-${timestamp}`;
130
- fs.copyFileSync(filePath, backupPath);
131
- return backupPath;
132
- }
133
-
134
- function writeClaudeSettings(apiKey, apiBaseUrl) {
135
- // 确保 .claude 目录存在
136
- if (!fs.existsSync(claudeDir)) {
137
- console.log('📁 创建 ~/.claude 目录...');
138
- fs.mkdirSync(claudeDir, { recursive: true });
139
- }
140
-
141
- // 读取或创建 settings.json
142
- let settings = {};
143
- if (fs.existsSync(claudeSettingsPath)) {
144
- console.log('📖 读取现有配置文件...');
145
- const content = fs.readFileSync(claudeSettingsPath, 'utf8');
146
- try {
147
- settings = JSON.parse(content);
148
- } catch (e) {
149
- console.log('⚠️ 现有配置文件格式错误,将创建新的配置');
150
- }
151
- }
152
-
153
- // 完全替换 env 对象,清除所有旧的环境变量配置
154
- settings.env = {
155
- ANTHROPIC_AUTH_TOKEN: apiKey,
156
- ANTHROPIC_BASE_URL: apiBaseUrl
157
- };
158
-
159
- // 写入配置文件
160
- fs.writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 2), 'utf8');
161
-
162
- console.log('\n=== API 配置 (Claude) ===\n');
163
- console.log('✅ 配置成功!');
164
- console.log('📝 已更新配置文件:', claudeSettingsPath);
165
- console.log('\n配置内容:');
166
- console.log(' ANTHROPIC_AUTH_TOKEN:', apiKey);
167
- console.log(' ANTHROPIC_BASE_URL:', apiBaseUrl);
168
- }
169
-
170
- function writeCodexConfig(apiKey, codexBaseUrl, modelName = 'gpt-5-codex') {
171
- if (!fs.existsSync(codexDir)) {
172
- console.log('📁 创建 ~/.codex 目录...');
173
- fs.mkdirSync(codexDir, { recursive: true });
174
- }
175
-
176
- let configContent = '';
177
- let existingConfig = '';
178
- let providerName = 'aihezu'; // 默认 provider 名称
179
-
180
- // 读取现有配置
181
- if (fs.existsSync(codexConfigPath)) {
182
- console.log('📖 读取现有 Codex 配置文件...');
183
- existingConfig = fs.readFileSync(codexConfigPath, 'utf8');
184
-
185
- // 提取现有的 provider 名称
186
- const providerMatch = existingConfig.match(/model_provider\s*=\s*"([^"]+)"/);
187
- if (providerMatch) {
188
- providerName = providerMatch[1];
189
- console.log(`ℹ️ 检测到现有 provider: ${providerName}`);
190
- }
191
-
192
- // 更新 model
193
- const modelPattern = /^model\s*=\s*"[^"]*"/m;
194
- if (modelPattern.test(existingConfig)) {
195
- existingConfig = existingConfig.replace(modelPattern, `model = "${modelName}"`);
196
- console.log('✏️ 已更新模型配置');
197
- }
198
-
199
- // 更新 base_url
200
- const baseUrlPattern = new RegExp(
201
- `(\\[model_providers\\.${providerName}\\][\\s\\S]*?base_url\\s*=\\s*)"[^"]*"`,
202
- 'm'
203
- );
204
-
205
- if (baseUrlPattern.test(existingConfig)) {
206
- // 如果找到了 base_url,就替换它
207
- configContent = existingConfig.replace(baseUrlPattern, `$1"${codexBaseUrl}"`);
208
- console.log('✏️ 已更新现有配置中的 base_url');
209
- } else {
210
- // 如果没有找到完整的 provider 配置,尝试添加或创建
211
- const providerSectionPattern = new RegExp(`\\[model_providers\\.${providerName}\\]`, 'm');
212
-
213
- if (providerSectionPattern.test(existingConfig)) {
214
- // provider section 存在但没有 base_url,添加 base_url
215
- configContent = existingConfig.replace(
216
- providerSectionPattern,
217
- `[model_providers.${providerName}]\nbase_url = "${codexBaseUrl}"`
218
- );
219
- console.log('✏️ 已在现有 provider 配置中添加 base_url');
220
- } else {
221
- // 完全没有这个 provider,保留现有配置并追加新的
222
- configContent = existingConfig.trim() + '\n\n' +
223
- `[model_providers.${providerName}]\n` +
224
- `name = "${providerName}"\n` +
225
- `base_url = "${codexBaseUrl}"\n` +
226
- `wire_api = "responses"\n` +
227
- `requires_openai_auth = true\n` +
228
- `env_key = "AIHEZU_OAI_KEY"\n`;
229
- console.log('✏️ 已追加新的 provider 配置');
230
- }
231
- }
232
- } else {
233
- // 文件不存在,创建新配置
234
- console.log('📝 创建新的 Codex 配置文件...');
235
- configContent = [
236
- 'model_provider = "aihezu"',
237
- `model = "${modelName}"`,
238
- 'model_reasoning_effort = "high"',
239
- 'disable_response_storage = true',
240
- 'preferred_auth_method = "apikey"',
241
- '',
242
- '[model_providers.aihezu]',
243
- 'name = "aihezu"',
244
- `base_url = "${codexBaseUrl}"`,
245
- 'wire_api = "responses"',
246
- 'requires_openai_auth = true',
247
- 'env_key = "AIHEZU_OAI_KEY"',
248
- ''
249
- ].join('\n');
250
- }
251
-
252
- fs.writeFileSync(codexConfigPath, configContent, 'utf8');
253
-
254
- let authData = {};
255
- if (fs.existsSync(codexAuthPath)) {
256
- try {
257
- const content = fs.readFileSync(codexAuthPath, 'utf8');
258
- authData = JSON.parse(content);
259
- } catch (e) {
260
- console.log('⚠️ 现有 auth.json 解析失败,将创建新文件');
261
- authData = {};
262
- }
263
- }
264
-
265
- // 只设置 AIHEZU_OAI_KEY,不修改其他环境变量
266
- authData.AIHEZU_OAI_KEY = apiKey;
267
-
268
- fs.writeFileSync(codexAuthPath, JSON.stringify(authData, null, 2), 'utf8');
269
-
270
- console.log('\n=== API 配置 (Codex) ===\n');
271
- console.log('✅ 配置成功!');
272
- console.log('📝 已更新配置文件:');
273
- console.log(' -', codexConfigPath);
274
- console.log(' -', codexAuthPath);
275
- console.log('\n配置内容:');
276
- console.log(' 环境变量名: AIHEZU_OAI_KEY');
277
- console.log(' API 密钥:', apiKey);
278
- console.log(' API 地址:', codexBaseUrl);
279
- console.log('\n💡 Codex 会自动从 auth.json 中读取 AIHEZU_OAI_KEY');
280
- console.log(' 如果遇到认证问题,请确保:');
281
- console.log(' 1. auth.json 文件存在且格式正确');
282
- console.log(' 2. 或者在终端中手动设置: export AIHEZU_OAI_KEY="' + apiKey + '"');
283
- }
284
-
285
- async function main() {
286
- console.log('🔧 Claude / Codex API 配置工具');
287
- console.log('🌐 Powered by https://aihezu.dev\n');
288
-
289
- const rl = readline.createInterface({
290
- input: process.stdin,
291
- output: process.stdout
292
- });
293
-
294
- try {
295
- // 1. 选择服务类型
296
- const cliProviderInput = parseProviderArg(cliArgs);
297
- let provider = resolveProvider(cliProviderInput);
298
-
299
- if (!cliProviderInput) {
300
- const providerAnswer = await askQuestion(rl, '请选择服务类型 [1] Claude (默认) / [2] Codex: ');
301
- provider = resolveProvider(providerAnswer);
302
- }
303
-
304
- console.log(`\n✅ 已选择服务: ${provider === PROVIDERS.CLAUDE ? 'Claude' : 'Codex'}\n`);
305
-
306
- // 2. 交互式询问 API 地址
307
- let apiBaseUrl = DEFAULT_CLAUDE_API_BASE;
308
- let codexBaseUrl = DEFAULT_CODEX_BASE_URL;
309
-
310
- if (provider === PROVIDERS.CLAUDE) {
311
- // 先检查命令行参数
312
- const cliApiInput = parseApiBaseInput(cliArgs);
313
- let defaultApiBase = DEFAULT_CLAUDE_API_BASE;
314
- let promptMessage = '';
315
-
316
- if (cliApiInput) {
317
- // 如果用户传了参数,将参数作为默认值
318
- try {
319
- defaultApiBase = normalizeClaudeApiBaseUrl(cliApiInput);
320
- promptMessage = `💡 检测到命令行参数,默认 API 地址: ${defaultApiBase}`;
321
- } catch (error) {
322
- console.error(`❌ 命令行参数错误: ${error.message}`);
323
- process.exit(1);
324
- }
325
- } else {
326
- promptMessage = `💡 Claude 默认 API 地址: ${defaultApiBase}`;
327
- }
328
-
329
- console.log(promptMessage);
330
- const apiBaseInput = await askQuestion(rl, '请输入 API 地址(直接回车使用默认地址): ');
331
-
332
- try {
333
- apiBaseUrl = normalizeClaudeApiBaseUrl(apiBaseInput || defaultApiBase);
334
- console.log(`✅ API 地址: ${apiBaseUrl}\n`);
335
- } catch (error) {
336
- console.error(`❌ ${error.message}`);
337
- process.exit(1);
338
- }
339
- } else {
340
- // 先检查命令行参数
341
- const cliCodexInput = parseApiBaseInput(cliArgs);
342
- let defaultCodexBase = DEFAULT_CODEX_BASE_URL;
343
- let promptMessage = '';
344
-
345
- if (cliCodexInput) {
346
- // 如果用户传了参数,将参数作为默认值
347
- try {
348
- defaultCodexBase = normalizeCodexBaseUrl(cliCodexInput);
349
- promptMessage = `💡 检测到命令行参数,默认 API 地址: ${defaultCodexBase}`;
350
- } catch (error) {
351
- console.error(`❌ 命令行参数错误: ${error.message}`);
352
- process.exit(1);
353
- }
354
- } else {
355
- promptMessage = `💡 Codex 默认 API 地址: ${defaultCodexBase}`;
356
- }
357
-
358
- console.log(promptMessage);
359
- const codexBaseInput = await askQuestion(rl, '请输入 API 地址(直接回车使用默认地址): ');
360
-
361
- try {
362
- codexBaseUrl = normalizeCodexBaseUrl(codexBaseInput || defaultCodexBase);
363
- console.log(`✅ API 地址: ${codexBaseUrl}\n`);
364
- } catch (error) {
365
- console.error(`❌ ${error.message}`);
366
- process.exit(1);
367
- }
368
- }
369
-
370
- // 3. 交互式询问模型(Codex 专用)
371
- let selectedModel = '';
372
- if (provider === PROVIDERS.CODEX) {
373
- console.log('请选择模型:');
374
- console.log(' [1] gpt-5-codex (默认)');
375
- console.log(' [2] claude-sonnet-4.5');
376
- console.log(' [3] 自定义模型名称');
377
- const modelChoice = await askQuestion(rl, '请选择 [1/2/3]: ');
378
-
379
- if (modelChoice === '2') {
380
- selectedModel = 'claude-sonnet-4.5';
381
- } else if (modelChoice === '3') {
382
- const customModel = await askQuestion(rl, '请输入自定义模型名称: ');
383
- selectedModel = customModel.trim() || 'gpt-5-codex';
384
- } else {
385
- selectedModel = 'gpt-5-codex';
386
- }
387
- console.log(`✅ 已选择模型: ${selectedModel}\n`);
388
- }
389
-
390
- // 4. 询问 API Key
391
- const apiKey = await askQuestion(rl, '请输入您的 API Key: ');
392
- if (!apiKey) {
393
- console.error('❌ API Key 不能为空');
394
- process.exit(1);
395
- }
396
-
397
- // 5. 显示配置摘要并确认
398
- console.log('\n=== 配置摘要 ===');
399
- console.log(`服务类型: ${provider === PROVIDERS.CLAUDE ? 'Claude' : 'Codex'}`);
400
- if (provider === PROVIDERS.CLAUDE) {
401
- console.log(`API 地址: ${apiBaseUrl}`);
402
- } else {
403
- console.log(`API 地址: ${codexBaseUrl}`);
404
- console.log(`模型: ${selectedModel}`);
405
- }
406
- console.log(`API Key: ${apiKey.substring(0, 10)}...${apiKey.substring(apiKey.length - 4)}`);
407
- console.log('\n⚠️ 即将执行以下操作:');
408
- console.log(' 1. 备份现有配置文件');
409
- console.log(' 2. 清理缓存数据');
410
- console.log(' 3. 写入新的配置');
411
- if (provider === PROVIDERS.CLAUDE) {
412
- console.log(' 4. 修改 hosts 文件');
413
- }
414
-
415
- const confirmAnswer = await askQuestion(rl, '\n是否继续?(y/n): ');
416
- if (!['y', 'yes'].includes(confirmAnswer.toLowerCase())) {
417
- console.log('❌ 已取消配置');
418
- process.exit(0);
419
- }
420
-
421
- // 6. 默认执行备份和清理缓存
422
- try {
423
- console.log('\n=== 备份与清理 ===');
424
-
425
- // 备份现有配置文件
426
- if (provider === PROVIDERS.CLAUDE && fs.existsSync(claudeSettingsPath)) {
427
- const backup = backupFile(claudeSettingsPath);
428
- if (backup) {
429
- console.log(`📦 已备份 Claude 配置: ${path.basename(backup)}`);
430
- }
431
- } else if (provider === PROVIDERS.CODEX) {
432
- if (fs.existsSync(codexConfigPath)) {
433
- const backup = backupFile(codexConfigPath);
434
- if (backup) {
435
- console.log(`📦 已备份 Codex config.toml: ${path.basename(backup)}`);
436
- }
437
- }
438
- if (fs.existsSync(codexAuthPath)) {
439
- const backup = backupFile(codexAuthPath);
440
- if (backup) {
441
- console.log(`📦 已备份 Codex auth.json: ${path.basename(backup)}`);
442
- }
443
- }
444
- }
445
-
446
- // 清理缓存
447
- const count = cleanCache({ showHeader: false });
448
- console.log(`✅ 缓存清理完成!(共处理 ${count} 项)\n`);
449
- } catch (error) {
450
- console.error('⚠️ 备份或清理时出错:', error.message);
451
- console.log('继续配置流程...\n');
452
- }
453
-
454
- // 7. 写入配置
455
- if (provider === PROVIDERS.CLAUDE) {
456
- writeClaudeSettings(apiKey.trim(), apiBaseUrl);
457
- } else {
458
- writeCodexConfig(apiKey.trim(), codexBaseUrl, selectedModel);
459
- }
460
-
461
- if (provider === PROVIDERS.CLAUDE) {
462
- console.log('\n=== 修改 hosts 文件 ===\n');
463
- modifyHostsFile();
464
- } else {
465
- console.log('\n=== Codex 不需要修改 hosts,已跳过 ===');
466
- }
467
-
468
- console.log('\n=== 全部完成 ===');
469
- if (provider === PROVIDERS.CLAUDE) {
470
- console.log('💡 您现在可以开始使用 Claude Code 了!');
471
- } else {
472
- console.log('💡 您现在可以开始使用 Codex 了!');
473
- }
474
- console.log('🌐 更多服务请访问: https://aihezu.dev');
475
- } catch (error) {
476
- console.error('❌ 配置失败:', error.message);
477
- process.exit(1);
478
- } finally {
479
- rl.close();
480
- }
481
- }
482
-
483
- main();
11
+ // Forward to new logic
12
+ installCommand(claudeService);
@@ -0,0 +1,34 @@
1
+ const { modifyHostsFile } = require('../lib/hosts');
2
+ const { cleanCache } = require('../lib/cache');
3
+
4
+ async function clearCommand(service) {
5
+ console.log('');
6
+ console.log('🧹 正在清理 ' + service.displayName + ' 环境...');
7
+ console.log('');
8
+
9
+ // 1. Clean Cache
10
+ if (service.cacheConfig) {
11
+ cleanCache({
12
+ targetDir: service.cacheConfig.dir,
13
+ itemsToClean: service.cacheConfig.items,
14
+ showHeader: true
15
+ });
16
+ } else {
17
+ console.log('ℹ️ 此服务未定义缓存配置。');
18
+ }
19
+
20
+ // 2. Refresh Hosts (Ensure they are still set correctly)
21
+ if (service.hostsConfig && service.hostsConfig.length > 0) {
22
+ console.log('');
23
+ console.log('=== 刷新网络配置 ===');
24
+ const success = modifyHostsFile(service.hostsConfig);
25
+ if (success) {
26
+ console.log('✅ hosts 文件条目已验证/更新。');
27
+ }
28
+ }
29
+
30
+ console.log('');
31
+ console.log('✅ 清理完成!');
32
+ }
33
+
34
+ module.exports = clearCommand;
@@ -0,0 +1,164 @@
1
+ const readline = require('readline');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+ const { modifyHostsFile } = require('../lib/hosts');
5
+ const { cleanCache } = require('../lib/cache');
6
+
7
+ function askQuestion(rl, question) {
8
+ return new Promise(resolve => rl.question(question, answer => resolve(answer.trim())));
9
+ }
10
+
11
+ // Helper to extract value from args like --api=xxx or --api xxx
12
+ function getArgValue(args, keys) {
13
+ for (let i = 0; i < args.length; i++) {
14
+ const arg = args[i];
15
+
16
+ // Check for --key=value style
17
+ for (const key of keys) {
18
+ if (arg.startsWith(key + '=')) {
19
+ return arg.split('=')[1].trim();
20
+ }
21
+ // Check for --key value style
22
+ if (arg === key && i + 1 < args.length) {
23
+ return args[i + 1].trim();
24
+ }
25
+ }
26
+ }
27
+ return null;
28
+ }
29
+
30
+ async function installCommand(service, args = []) {
31
+ console.log('');
32
+ console.log('🔧 ' + service.displayName + ' 配置工具');
33
+ console.log('🌐 Powered by https://aihezu.dev');
34
+ console.log('');
35
+
36
+ const rl = readline.createInterface({
37
+ input: process.stdin,
38
+ output: process.stdout
39
+ });
40
+
41
+ try {
42
+ // Parse CLI args for defaults
43
+ const cliApiUrl = getArgValue(args, ['--api', '--url', '--base-url', '--host']);
44
+ const cliApiKey = getArgValue(args, ['--key', '--token', '--api-key']);
45
+ const cliModel = getArgValue(args, ['--model']);
46
+
47
+ // 1. API URL
48
+ const defaultUrl = cliApiUrl || service.defaultApiUrl;
49
+
50
+ if (cliApiUrl) {
51
+ console.log('💡 检测到命令行参数,默认 API 地址: ' + defaultUrl);
52
+ } else {
53
+ console.log('💡 默认 API 地址: ' + defaultUrl);
54
+ }
55
+
56
+ const apiUrlInput = await askQuestion(rl, '请输入 API 地址 (直接回车使用默认地址): ');
57
+ let apiUrl = apiUrlInput || defaultUrl;
58
+
59
+ // Basic URL validation/normalization
60
+ if (!/^https?:\/\//i.test(apiUrl)) {
61
+ apiUrl = 'https://' + apiUrl;
62
+ }
63
+
64
+ // 2. API Key
65
+ let defaultApiKey = cliApiKey || '';
66
+ let apiKeyPrompt = '请输入您的 API Key: ';
67
+ if (defaultApiKey) {
68
+ console.log('💡 检测到命令行参数提供了 API Key');
69
+ apiKeyPrompt = '请输入您的 API Key (直接回车使用提供的 Key): ';
70
+ }
71
+
72
+ const apiKeyInput = await askQuestion(rl, apiKeyPrompt);
73
+ let apiKey = apiKeyInput || defaultApiKey;
74
+
75
+ if (!apiKey) {
76
+ console.error('❌ API Key 不能为空。');
77
+ process.exit(1);
78
+ }
79
+
80
+ // 3. Extra Options (Service specific)
81
+ const options = {};
82
+ if (service.name === 'codex') {
83
+ let defaultModel = cliModel || 'gpt-5-codex';
84
+
85
+ console.log('请选择模型:');
86
+ console.log(' [1] gpt-5-codex' + (defaultModel === 'gpt-5-codex' ? ' (默认)' : ''));
87
+ console.log(' [2] claude-sonnet-4.5' + (defaultModel === 'claude-sonnet-4.5' ? ' (默认)' : ''));
88
+ console.log(' [3] 自定义模型名称' + (!['gpt-5-codex', 'claude-sonnet-4.5'].includes(defaultModel) ? ' (默认: ' + defaultModel + ')' : ''));
89
+
90
+ const choice = await askQuestion(rl, '请选择 [1/2/3]: ');
91
+
92
+ if (choice === '2') {
93
+ options.modelName = 'claude-sonnet-4.5';
94
+ } else if (choice === '3') {
95
+ const customModel = await askQuestion(rl, '请输入自定义模型名称: ');
96
+ options.modelName = customModel || defaultModel;
97
+ } else if (choice === '1' || choice === '') {
98
+ // Default choice (Enter or '1')
99
+ if (choice === '') {
100
+ options.modelName = defaultModel;
101
+ } else {
102
+ options.modelName = 'gpt-5-codex';
103
+ }
104
+ }
105
+ }
106
+
107
+ // 4. Confirmation
108
+ console.log('');
109
+ console.log('=== 配置摘要 ===');
110
+ console.log('服务类型: ' + service.displayName);
111
+ console.log('API 地址: ' + apiUrl);
112
+ if (options.modelName) console.log('模型: ' + options.modelName);
113
+ console.log('API Key: ' + apiKey.substring(0, 5) + '...');
114
+
115
+ console.log('');
116
+ const confirm = await askQuestion(rl, '是否继续? (y/n): ');
117
+ if (confirm.toLowerCase() !== 'y' && confirm.toLowerCase() !== 'yes' && confirm !== '') {
118
+ console.log('已取消。');
119
+ process.exit(0);
120
+ }
121
+
122
+ // 5. Execution
123
+ console.log('');
124
+ console.log('=== 开始执行设置 ===');
125
+
126
+ // Clean Cache first
127
+ if (service.cacheConfig) {
128
+ cleanCache({
129
+ targetDir: service.cacheConfig.dir,
130
+ itemsToClean: service.cacheConfig.items,
131
+ showHeader: true
132
+ });
133
+ }
134
+
135
+ // Setup Config
136
+ const configFiles = await service.setupConfig(apiKey, apiUrl, options);
137
+ console.log('✅ 配置已写入:');
138
+ configFiles.forEach(f => console.log(' - ' + f));
139
+
140
+ // Modify Hosts
141
+ if (service.hostsConfig && service.hostsConfig.length > 0) {
142
+ console.log('');
143
+ console.log('=== 网络配置 ===');
144
+ const success = modifyHostsFile(service.hostsConfig);
145
+ if (!success) {
146
+ console.log('⚠️ hosts 文件修改失败。您可能需要以 sudo/管理员权限运行。');
147
+ }
148
+ } else {
149
+ console.log('');
150
+ console.log('=== 网络配置 ===');
151
+ console.log('���️ 此服务不需要修改 hosts 文件。');
152
+ }
153
+
154
+ console.log('');
155
+ console.log('✅ 设置完成!');
156
+
157
+ } catch (error) {
158
+ console.error('❌ 安装过程中发生错误:', error);
159
+ } finally {
160
+ rl.close();
161
+ }
162
+ }
163
+
164
+ module.exports = installCommand;