aihezu 1.7.1 → 1.8.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.
@@ -0,0 +1,152 @@
1
+ # aihezu install 使用说明
2
+
3
+ ## 功能概述
4
+
5
+ `npx aihezu install` 命令现在是完全交互式的,支持配置 Claude 和 Codex 服务。
6
+
7
+ ## 使用方式
8
+
9
+ ### 1. 基本用法(无参数)
10
+
11
+ ```bash
12
+ npx aihezu install
13
+ ```
14
+
15
+ 交互流程:
16
+ 1. 选择服务类型(Claude / Codex)
17
+ 2. 输入 API 地址(显示系统默认值,可直接回车使用)
18
+ 3. 选择模型(仅 Codex)
19
+ 4. 输入 API Key
20
+ 5. 查看配置摘要并确认
21
+ 6. 自动备份现有配置
22
+ 7. 自动清理缓存
23
+ 8. 写入新配置
24
+
25
+ **示例输出:**
26
+ ```
27
+ 🔧 Claude / Codex API 配置工具
28
+ 🌐 Powered by https://aihezu.dev
29
+
30
+ 请选择服务类型 [1] Claude (默认) / [2] Codex: 1
31
+
32
+ ✅ 已选择服务: Claude
33
+
34
+ 💡 Claude 默认 API 地址: https://cc.aihezu.dev/api
35
+ 请输入 API 地址(直接回车使用默认地址):
36
+ ✅ API 地址: https://cc.aihezu.dev/api
37
+ ...
38
+ ```
39
+
40
+ ### 2. 带参数用法(推荐)
41
+
42
+ #### Claude 企业域名
43
+
44
+ ```bash
45
+ npx aihezu install --api hk.aihezu.dev
46
+ ```
47
+
48
+ **交互提示:**
49
+ ```
50
+ ✅ 已选择服务: Claude
51
+
52
+ 💡 检测到命令行参数,默认 API 地址: https://hk.aihezu.dev/api
53
+ 请输入 API 地址(直接回车使用默认地址):
54
+ ```
55
+
56
+ 此时用户可以:
57
+ - **直接回车**:使用命令行参数 `https://hk.aihezu.dev/api`
58
+ - **输入其他值**:覆盖命令行参数
59
+
60
+ #### Codex 企业域名
61
+
62
+ ```bash
63
+ npx aihezu install --provider codex --api hk.aihezu.dev
64
+ ```
65
+
66
+ **交互提示:**
67
+ ```
68
+ ✅ 已选择服务: Codex
69
+
70
+ 💡 检测到命令行参数,默认 API 地址: https://hk.aihezu.dev/openai
71
+ 请输入 API 地址(直接回车使用默认地址):
72
+
73
+ 请选择模型:
74
+ [1] gpt-5-codex (默认)
75
+ [2] claude-sonnet-4.5
76
+ [3] 自定义模型名称
77
+ 请选择 [1/2/3]:
78
+ ```
79
+
80
+ ## 参数优先级
81
+
82
+ 对于 API 地址配置:
83
+
84
+ 1. **用户交互输入** > 命令行参数 > 系统默认值
85
+ 2. 如果带了 `--api` 参数,会将参数值作为默认值显示给用户
86
+ 3. 用户可以直接回车使用这个默认值,也可以输入新值覆盖
87
+
88
+ ## 配置摘要示例
89
+
90
+ ```
91
+ === 配置摘要 ===
92
+ 服务类型: Claude
93
+ API 地址: https://hk.aihezu.dev/api
94
+ API Key: sk-ant-123...xyz
95
+
96
+ ⚠️ 即将执行以下操作:
97
+ 1. 备份现有配置文件
98
+ 2. 清理缓存数据
99
+ 3. 写入新的配置
100
+ 4. 修改 hosts 文件
101
+
102
+ 是否继续?(y/n):
103
+ ```
104
+
105
+ ## 自动备份
106
+
107
+ 无论是否首次使用,每次运行都会:
108
+ - 自动备份现有配置文件(如果存在)
109
+ - 备份文件命名格式:`原文件名.backup-时间戳`
110
+ - 示例:`settings.json.backup-2024-12-10T08-30-45-123Z`
111
+
112
+ ## 模型选择(Codex)
113
+
114
+ Codex 用户可以选择:
115
+ 1. `gpt-5-codex`(默认)
116
+ 2. `claude-sonnet-4.5`
117
+ 3. 自定义模型名称
118
+
119
+ ## 完整示例
120
+
121
+ ### 场景 1:企业用户首次配置 Claude
122
+
123
+ ```bash
124
+ npx aihezu install --api company.aihezu.dev
125
+ ```
126
+
127
+ 只需要:
128
+ 1. 直接回车确认企业域名
129
+ 2. 输入 API Key
130
+ 3. 确认配置摘要
131
+ 4. 等待完成
132
+
133
+ ### 场景 2:切换到 Codex 服务
134
+
135
+ ```bash
136
+ npx aihezu install --provider codex
137
+ ```
138
+
139
+ 1. 系统识别 Codex 服务
140
+ 2. 显示默认地址 `https://cc.aihezu.dev/openai`
141
+ 3. 选择模型
142
+ 4. 输入 API Key
143
+ 5. 自动备份 Claude 配置
144
+ 6. 完成切换
145
+
146
+ ### 场景 3:完全自定义配置
147
+
148
+ ```bash
149
+ npx aihezu install
150
+ ```
151
+
152
+ 交互式输入所有选项,完全自定义。
package/bin/ccinstall.js CHANGED
@@ -147,11 +147,6 @@ function writeClaudeSettings(apiKey, apiBaseUrl) {
147
147
  settings = JSON.parse(content);
148
148
  } catch (e) {
149
149
  console.log('⚠️ 现有配置文件格式错误,将创建新的配置');
150
- // 备份错误的配置文件
151
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
152
- const backupPath = `${claudeSettingsPath}.backup-${timestamp}`;
153
- fs.writeFileSync(backupPath, content);
154
- console.log(`📦 已备份原配置文件到: ${path.basename(backupPath)}`);
155
150
  }
156
151
  }
157
152
 
@@ -172,32 +167,87 @@ function writeClaudeSettings(apiKey, apiBaseUrl) {
172
167
  console.log(' ANTHROPIC_BASE_URL:', apiBaseUrl);
173
168
  }
174
169
 
175
- function writeCodexConfig(apiKey, codexBaseUrl) {
170
+ function writeCodexConfig(apiKey, codexBaseUrl, modelName = 'gpt-5-codex') {
176
171
  if (!fs.existsSync(codexDir)) {
177
172
  console.log('📁 创建 ~/.codex 目录...');
178
173
  fs.mkdirSync(codexDir, { recursive: true });
179
174
  }
180
175
 
181
- const configBackup = backupFile(codexConfigPath);
182
- if (configBackup) {
183
- console.log(`📦 已备份原配置文件到: ${path.basename(configBackup)}`);
184
- }
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
+ }
185
198
 
186
- const configContent = [
187
- 'model_provider = "aihezu"',
188
- 'model = "gpt-5-codex"',
189
- 'model_reasoning_effort = "high"',
190
- 'disable_response_storage = true',
191
- 'preferred_auth_method = "apikey"',
192
- '',
193
- '[model_providers.aihezu]',
194
- 'name = "aihezu"',
195
- `base_url = "${codexBaseUrl}"`,
196
- 'wire_api = "responses"',
197
- 'requires_openai_auth = true',
198
- 'env_key = "AIHEZU_OAI_KEY"',
199
- ''
200
- ].join('\n');
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
+ }
201
251
 
202
252
  fs.writeFileSync(codexConfigPath, configContent, 'utf8');
203
253
 
@@ -207,13 +257,12 @@ function writeCodexConfig(apiKey, codexBaseUrl) {
207
257
  const content = fs.readFileSync(codexAuthPath, 'utf8');
208
258
  authData = JSON.parse(content);
209
259
  } catch (e) {
210
- const backupPath = backupFile(codexAuthPath);
211
- console.log('⚠️ 现有 auth.json 解析失败,已备份旧文件:', backupPath ? path.basename(backupPath) : '未备份');
260
+ console.log('⚠️ 现有 auth.json 解析失败,将创建新文件');
212
261
  authData = {};
213
262
  }
214
263
  }
215
264
 
216
- authData.OPENAI_API_KEY = null;
265
+ // 只设置 AIHEZU_OAI_KEY,不修改其他环境变量
217
266
  authData.AIHEZU_OAI_KEY = apiKey;
218
267
 
219
268
  fs.writeFileSync(codexAuthPath, JSON.stringify(authData, null, 2), 'utf8');
@@ -224,10 +273,13 @@ function writeCodexConfig(apiKey, codexBaseUrl) {
224
273
  console.log(' -', codexConfigPath);
225
274
  console.log(' -', codexAuthPath);
226
275
  console.log('\n配置内容:');
227
- console.log(' AIHEZU_OAI_KEY:', apiKey);
228
- console.log(' base_url:', codexBaseUrl);
229
- console.log('\n💡 已在 auth.json 中写入密钥,并在 config.toml 中指定 env_key = "AIHEZU_OAI_KEY"');
230
- console.log(' 如需在 shell 中直接使用,可运行: export AIHEZU_OAI_KEY="<你的密钥>"');
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 + '"');
231
283
  }
232
284
 
233
285
  async function main() {
@@ -240,6 +292,7 @@ async function main() {
240
292
  });
241
293
 
242
294
  try {
295
+ // 1. 选择服务类型
243
296
  const cliProviderInput = parseProviderArg(cliArgs);
244
297
  let provider = resolveProvider(cliProviderInput);
245
298
 
@@ -248,76 +301,161 @@ async function main() {
248
301
  provider = resolveProvider(providerAnswer);
249
302
  }
250
303
 
251
- const apiBaseInput = provider === PROVIDERS.CLAUDE ? parseApiBaseInput(cliArgs) : '';
252
- const codexBaseInput = provider === PROVIDERS.CODEX ? parseApiBaseInput(cliArgs) : '';
304
+ console.log(`\n✅ 已选择服务: ${provider === PROVIDERS.CLAUDE ? 'Claude' : 'Codex'}\n`);
253
305
 
306
+ // 2. 交互式询问 API 地址
254
307
  let apiBaseUrl = DEFAULT_CLAUDE_API_BASE;
255
308
  let codexBaseUrl = DEFAULT_CODEX_BASE_URL;
309
+
256
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
+
257
332
  try {
258
- apiBaseUrl = normalizeClaudeApiBaseUrl(apiBaseInput);
333
+ apiBaseUrl = normalizeClaudeApiBaseUrl(apiBaseInput || defaultApiBase);
334
+ console.log(`✅ API 地址: ${apiBaseUrl}\n`);
259
335
  } catch (error) {
260
336
  console.error(`❌ ${error.message}`);
261
337
  process.exit(1);
262
338
  }
263
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
+
264
361
  try {
265
- codexBaseUrl = normalizeCodexBaseUrl(codexBaseInput);
362
+ codexBaseUrl = normalizeCodexBaseUrl(codexBaseInput || defaultCodexBase);
363
+ console.log(`✅ API 地址: ${codexBaseUrl}\n`);
266
364
  } catch (error) {
267
365
  console.error(`❌ ${error.message}`);
268
366
  process.exit(1);
269
367
  }
270
368
  }
271
369
 
272
- const usingCustomDomain = provider === PROVIDERS.CLAUDE && !!apiBaseInput;
273
- const usingCustomCodexDomain = provider === PROVIDERS.CODEX && !!codexBaseInput;
274
-
275
- if (provider === PROVIDERS.CLAUDE) {
276
- if (usingCustomDomain) {
277
- console.log(`🏢 已启用企业独立域名: ${apiBaseUrl}\n`);
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';
278
384
  } else {
279
- console.log(`ℹ️ 未指定域名,将使用默认地址: ${DEFAULT_CLAUDE_API_BASE}`);
280
- console.log(' 企业用户可使用 --api 或 --api-url 指定独立域名,例如:');
281
- console.log(' sudo npx aihezu install --api your-org.aihezu.dev\n');
282
- }
283
- } else {
284
- if (usingCustomCodexDomain) {
285
- console.log(`🏢 Codex 已使用企业域名: ${codexBaseUrl}\n`);
286
- } else {
287
- console.log(`🤖 已选择 Codex,默认网关: ${DEFAULT_CODEX_BASE_URL}`);
288
- console.log(' 企业用户可用 --api 或 --api-url 指定独立域名,例如:');
289
- console.log(' npx aihezu install --provider codex --api your-org.aihezu.dev\n');
290
- }
291
- }
292
-
293
- const needCleanAnswer = await askQuestion(
294
- rl,
295
- '您是从其他服务切换过来的吗?(y/n,如果是首次使用请输入 n): '
296
- );
297
- const needClean = ['y', 'yes'].includes(needCleanAnswer.toLowerCase());
298
-
299
- if (needClean) {
300
- try {
301
- console.log('\n=== 清理旧缓存 ===');
302
- const count = cleanCache({ showHeader: false });
303
- console.log(`\n✅ 缓存清理完成!(共处理 ${count} 项)`);
304
- console.log('💡 配置文件已保留,即将配置新的 API Key\n');
305
- } catch (error) {
306
- console.error('⚠️ 清理缓存时出错:', error.message);
307
- console.log('继续配置流程...\n');
385
+ selectedModel = 'gpt-5-codex';
308
386
  }
387
+ console.log(`✅ 已选择模型: ${selectedModel}\n`);
309
388
  }
310
389
 
390
+ // 4. 询问 API Key
311
391
  const apiKey = await askQuestion(rl, '请输入您的 API Key: ');
312
392
  if (!apiKey) {
313
393
  console.error('❌ API Key 不能为空');
314
394
  process.exit(1);
315
395
  }
316
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. 写入配置
317
455
  if (provider === PROVIDERS.CLAUDE) {
318
456
  writeClaudeSettings(apiKey.trim(), apiBaseUrl);
319
457
  } else {
320
- writeCodexConfig(apiKey.trim(), codexBaseUrl);
458
+ writeCodexConfig(apiKey.trim(), codexBaseUrl, selectedModel);
321
459
  }
322
460
 
323
461
  if (provider === PROVIDERS.CLAUDE) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aihezu",
3
- "version": "1.7.1",
3
+ "version": "1.8.0",
4
4
  "description": "Claude Code CLI 清理工具 - 快速备份和清理 Claude Code 的本地配置和缓存,同时修改 hosts 文件实现本地代理",
5
5
  "main": "bin/ccclear.js",
6
6
  "bin": {