@pikecode/api-key-manager 1.0.21 → 1.0.23

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/akm.js CHANGED
@@ -34,15 +34,35 @@ program
34
34
  program
35
35
  .command('add')
36
36
  .description('添加新的API密钥配置')
37
- .action(async () => {
37
+ .option('--codex', '直接添加 Codex CLI 供应商')
38
+ .option('--claude', '直接添加 Claude Code 供应商')
39
+ .action(async (options) => {
38
40
  try {
39
- await registry.executeCommand('add');
41
+ const ideName = options.codex ? 'codex' : (options.claude ? 'claude' : null);
42
+ await registry.executeCommand('add', { ideName });
40
43
  } catch (error) {
41
44
  console.error(chalk.red('❌ 添加失败:'), error.message);
42
45
  process.exit(1);
43
46
  }
44
47
  });
45
48
 
49
+ // Switch command
50
+ program
51
+ .command('switch')
52
+ .description('切换到指定供应商')
53
+ .argument('[provider]', '直接切换到指定供应商')
54
+ .option('--codex', '仅显示 Codex CLI 供应商')
55
+ .option('--claude', '仅显示 Claude Code 供应商')
56
+ .action(async (provider, options) => {
57
+ try {
58
+ const filter = options.codex ? 'codex' : (options.claude ? 'claude' : null);
59
+ await registry.executeCommand('switch', provider, { filter });
60
+ } catch (error) {
61
+ console.error(chalk.red('❌ 切换失败:'), error.message);
62
+ process.exit(1);
63
+ }
64
+ });
65
+
46
66
  // Remove command
47
67
  program
48
68
  .command('remove')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikecode/api-key-manager",
3
- "version": "1.0.21",
3
+ "version": "1.0.23",
4
4
  "description": "A CLI tool for managing and switching multiple API provider configurations",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -29,6 +29,11 @@
29
29
  "anthropic",
30
30
  "anthropic-api-key",
31
31
  "anthropic-auth-token",
32
+ "openai",
33
+ "openai-api-key",
34
+ "codex",
35
+ "codex-cli",
36
+ "claude-code",
32
37
  "environment",
33
38
  "config",
34
39
  "provider",
@@ -7,9 +7,10 @@ const { UIHelper } = require('../utils/ui-helper');
7
7
  const { BaseCommand } = require('./BaseCommand');
8
8
 
9
9
  class ProviderAdder extends BaseCommand {
10
- constructor() {
10
+ constructor(options = {}) {
11
11
  super();
12
12
  this.configManager = new ConfigManager();
13
+ this.presetIdeName = options.ideName || null;
13
14
  }
14
15
 
15
16
  async interactive() {
@@ -174,6 +175,28 @@ class ProviderAdder extends BaseCommand {
174
175
 
175
176
  try {
176
177
  const answers = await this.prompt([
178
+ {
179
+ type: 'list',
180
+ name: 'ideName',
181
+ message: '选择要管理的 IDE:',
182
+ choices: [
183
+ { name: 'Claude Code (Anthropic)', value: 'claude' },
184
+ { name: 'Codex CLI (OpenAI)', value: 'codex' }
185
+ ],
186
+ default: this.presetIdeName || 'claude',
187
+ when: () => !this.presetIdeName
188
+ },
189
+ {
190
+ type: 'list',
191
+ name: 'importFromExisting',
192
+ message: '是否从现有 Codex 配置导入?',
193
+ choices: [
194
+ { name: '从 ~/.codex 导入现有配置', value: 'import' },
195
+ { name: '手动输入配置', value: 'manual' }
196
+ ],
197
+ default: 'import',
198
+ when: (answers) => (answers.ideName || this.presetIdeName) === 'codex'
199
+ },
177
200
  {
178
201
  type: 'input',
179
202
  name: 'name',
@@ -194,16 +217,6 @@ class ProviderAdder extends BaseCommand {
194
217
  return true;
195
218
  }
196
219
  },
197
- {
198
- type: 'list',
199
- name: 'ideName',
200
- message: '选择要管理的 IDE:',
201
- choices: [
202
- { name: 'Claude Code (Anthropic)', value: 'claude' },
203
- { name: 'Codex CLI (OpenAI)', value: 'codex' }
204
- ],
205
- default: 'claude'
206
- },
207
220
  {
208
221
  type: 'list',
209
222
  name: 'authMode',
@@ -214,7 +227,7 @@ class ProviderAdder extends BaseCommand {
214
227
  { name: '🌐 OAuth令牌模式 (CLAUDE_CODE_OAUTH_TOKEN) - 适用于官方Claude Code', value: 'oauth_token' }
215
228
  ],
216
229
  default: 'api_key',
217
- when: (answers) => answers.ideName !== 'codex'
230
+ when: (answers) => (answers.ideName || this.presetIdeName) !== 'codex'
218
231
  },
219
232
  {
220
233
  type: 'list',
@@ -225,7 +238,7 @@ class ProviderAdder extends BaseCommand {
225
238
  { name: '🔐 ANTHROPIC_AUTH_TOKEN - 认证令牌', value: 'auth_token' }
226
239
  ],
227
240
  default: 'api_key',
228
- when: (answers) => answers.ideName !== 'codex' && answers.authMode === 'api_key'
241
+ when: (answers) => (answers.ideName || this.presetIdeName) !== 'codex' && answers.authMode === 'api_key'
229
242
  },
230
243
  {
231
244
  type: 'input',
@@ -249,7 +262,7 @@ class ProviderAdder extends BaseCommand {
249
262
  if (error) return error;
250
263
  return true;
251
264
  },
252
- when: (answers) => answers.ideName !== 'codex' && (answers.authMode === 'api_key' || answers.authMode === 'auth_token')
265
+ when: (answers) => (answers.ideName || this.presetIdeName) !== 'codex' && (answers.authMode === 'api_key' || answers.authMode === 'auth_token')
253
266
  },
254
267
  {
255
268
  type: 'input',
@@ -271,9 +284,8 @@ class ProviderAdder extends BaseCommand {
271
284
  const error = validator.validateToken(input);
272
285
  if (error) return error;
273
286
  return true;
274
- }
275
- ,
276
- when: (answers) => answers.ideName !== 'codex'
287
+ },
288
+ when: (answers) => (answers.ideName || this.presetIdeName) !== 'codex'
277
289
  },
278
290
  {
279
291
  type: 'input',
@@ -286,7 +298,7 @@ class ProviderAdder extends BaseCommand {
286
298
  if (error) return error;
287
299
  return true;
288
300
  },
289
- when: (answers) => answers.ideName === 'codex'
301
+ when: (answers) => (answers.ideName || this.presetIdeName) === 'codex' && answers.importFromExisting === 'manual'
290
302
  },
291
303
  {
292
304
  type: 'input',
@@ -298,7 +310,7 @@ class ProviderAdder extends BaseCommand {
298
310
  if (error) return error;
299
311
  return true;
300
312
  },
301
- when: (answers) => answers.ideName === 'codex'
313
+ when: (answers) => (answers.ideName || this.presetIdeName) === 'codex' && answers.importFromExisting === 'manual'
302
314
  },
303
315
  {
304
316
  type: 'confirm',
@@ -311,24 +323,68 @@ class ProviderAdder extends BaseCommand {
311
323
  name: 'configureLaunchArgs',
312
324
  message: '是否配置启动参数?',
313
325
  default: false,
314
- when: (answers) => answers.ideName !== 'codex'
326
+ when: (answers) => (answers.ideName || this.presetIdeName) !== 'codex'
327
+ },
328
+ {
329
+ type: 'confirm',
330
+ name: 'configureCodexLaunchArgs',
331
+ message: '是否配置 Codex 启动参数?',
332
+ default: false,
333
+ when: (answers) => (answers.ideName || this.presetIdeName) === 'codex'
315
334
  },
316
335
  {
317
336
  type: 'confirm',
318
337
  name: 'configureModels',
319
338
  message: '是否配置模型参数?',
320
339
  default: false,
321
- when: (answers) => answers.ideName !== 'codex'
340
+ when: (answers) => (answers.ideName || this.presetIdeName) !== 'codex'
322
341
  }
323
342
  ]);
324
343
 
325
344
  // 移除 ESC 键监听
326
345
  this.removeESCListener(escListener);
327
-
346
+
347
+ // 如果是预设的 ideName,设置到 answers 中
348
+ if (!answers.ideName && this.presetIdeName) {
349
+ answers.ideName = this.presetIdeName;
350
+ }
351
+
328
352
  if (answers.ideName === 'codex') {
329
353
  answers.authMode = 'openai_api_key';
330
354
  answers.tokenType = null;
331
355
  answers.codexFiles = null;
356
+
357
+ // 从现有配置导入
358
+ if (answers.importFromExisting === 'import') {
359
+ const importedConfig = await this.importCodexConfig();
360
+ if (importedConfig) {
361
+ answers.authToken = importedConfig.apiKey;
362
+ answers.baseUrl = importedConfig.baseUrl;
363
+ } else {
364
+ Logger.warning('未能导入现有配置,请手动输入');
365
+ const manualAnswers = await this.prompt([
366
+ {
367
+ type: 'input',
368
+ name: 'baseUrl',
369
+ message: '请输入 OpenAI API 基础URL (如使用官方API可留空):',
370
+ default: ''
371
+ },
372
+ {
373
+ type: 'input',
374
+ name: 'authToken',
375
+ message: '请输入 OpenAI API Key (OPENAI_API_KEY):',
376
+ validate: (input) => input ? true : 'API Key 不能为空'
377
+ }
378
+ ]);
379
+ answers.authToken = manualAnswers.authToken;
380
+ answers.baseUrl = manualAnswers.baseUrl;
381
+ }
382
+ }
383
+
384
+ // Codex 启动参数配置
385
+ if (answers.configureCodexLaunchArgs) {
386
+ answers.launchArgs = await this.promptCodexLaunchArgsSelection();
387
+ }
332
388
  }
333
389
 
334
390
  await this.saveProvider(answers);
@@ -512,6 +568,110 @@ class ProviderAdder extends BaseCommand {
512
568
  }
513
569
  }
514
570
 
571
+ async importCodexConfig() {
572
+ try {
573
+ const { readCodexFiles } = require('../utils/codex-files');
574
+ const codexFiles = await readCodexFiles();
575
+
576
+ if (!codexFiles.authJson) {
577
+ return null;
578
+ }
579
+
580
+ // 解析 auth.json 获取 API Key
581
+ const authData = JSON.parse(codexFiles.authJson);
582
+ const apiKey = authData.api_key || authData.openai_api_key || authData.OPENAI_API_KEY;
583
+
584
+ if (!apiKey) {
585
+ return null;
586
+ }
587
+
588
+ // 尝试从 config.toml 获取 base URL
589
+ let baseUrl = null;
590
+ if (codexFiles.configToml) {
591
+ const baseUrlMatch = codexFiles.configToml.match(/api_base\s*=\s*["']([^"']+)["']/);
592
+ if (baseUrlMatch) {
593
+ baseUrl = baseUrlMatch[1];
594
+ }
595
+ }
596
+
597
+ Logger.success(`成功从 ${codexFiles.codexHome} 导入配置`);
598
+ return { apiKey, baseUrl };
599
+ } catch (error) {
600
+ Logger.warning(`导入配置失败: ${error.message}`);
601
+ return null;
602
+ }
603
+ }
604
+
605
+ async promptCodexLaunchArgsSelection() {
606
+ console.log(UIHelper.createTitle('配置 Codex 启动参数', UIHelper.icons.settings));
607
+ console.log();
608
+ console.log(UIHelper.createTooltip('选择要使用的 Codex 启动参数'));
609
+ console.log();
610
+ console.log(UIHelper.createHintLine([
611
+ ['空格', '切换选中'],
612
+ ['A', '全选'],
613
+ ['I', '反选'],
614
+ ['Enter', '确认选择'],
615
+ ['ESC', '跳过配置']
616
+ ]));
617
+ console.log();
618
+
619
+ const escListener = this.createESCListener(() => {
620
+ Logger.info('跳过 Codex 启动参数配置');
621
+ }, '跳过配置');
622
+
623
+ try {
624
+ const codexArgs = [
625
+ {
626
+ name: '--full-auto',
627
+ label: '全自动模式',
628
+ description: '自动批准所有操作',
629
+ checked: false
630
+ },
631
+ {
632
+ name: '--dangerously-bypass-approvals-and-sandbox',
633
+ label: '跳过审批和沙盒',
634
+ description: '危险:跳过所有安全检查',
635
+ checked: false
636
+ },
637
+ {
638
+ name: '--model',
639
+ label: '指定模型',
640
+ description: '使用特定模型 (需手动指定)',
641
+ checked: false
642
+ },
643
+ {
644
+ name: '--quiet',
645
+ label: '静默模式',
646
+ description: '减少输出信息',
647
+ checked: false
648
+ }
649
+ ];
650
+
651
+ const { launchArgs } = await this.prompt([
652
+ {
653
+ type: 'checkbox',
654
+ name: 'launchArgs',
655
+ message: '请选择 Codex 启动参数:',
656
+ choices: codexArgs.map(arg => ({
657
+ name: `${arg.label} (${arg.name}) - ${arg.description}`,
658
+ value: arg.name,
659
+ checked: arg.checked
660
+ }))
661
+ }
662
+ ]);
663
+
664
+ this.removeESCListener(escListener);
665
+ return launchArgs;
666
+ } catch (error) {
667
+ this.removeESCListener(escListener);
668
+ if (this.isEscCancelled(error)) {
669
+ return [];
670
+ }
671
+ throw error;
672
+ }
673
+ }
674
+
515
675
  printProviderSummary(answers, launchArgs, modelConfig) {
516
676
  const finalDisplayName = answers.displayName || answers.name;
517
677
  Logger.success(`供应商 '${finalDisplayName}' 添加成功!`);
@@ -528,6 +688,9 @@ class ProviderAdder extends BaseCommand {
528
688
  if (answers.authToken) {
529
689
  console.log(chalk.gray(` OPENAI_API_KEY: ${answers.authToken}`));
530
690
  }
691
+ if (answers.launchArgs && answers.launchArgs.length > 0) {
692
+ console.log(chalk.gray(` 启动参数: ${answers.launchArgs.join(' ')}`));
693
+ }
531
694
  console.log(chalk.green('\n🎉 供应商添加完成!正在返回主界面...'));
532
695
  return;
533
696
  }
@@ -571,8 +734,8 @@ class ProviderAdder extends BaseCommand {
571
734
  }
572
735
  }
573
736
 
574
- async function addCommand() {
575
- const adder = new ProviderAdder();
737
+ async function addCommand(options = {}) {
738
+ const adder = new ProviderAdder(options);
576
739
  try {
577
740
  await adder.interactive();
578
741
  } catch (error) {
@@ -47,7 +47,12 @@ class ProviderLister {
47
47
  const availabilityText = this._formatAvailability(availability);
48
48
  const nameColor = isCurrent ? chalk.green : chalk.white;
49
49
 
50
- console.log(`${status} ${availabilityIcon} ${nameColor(provider.name)} (${provider.displayName}) - ${availabilityText}`);
50
+ // IDE 类型标签
51
+ const ideTag = provider.ideName === 'codex'
52
+ ? chalk.cyan('[Codex]')
53
+ : chalk.magenta('[Claude]');
54
+
55
+ console.log(`${status} ${availabilityIcon} ${ideTag} ${nameColor(provider.name)} (${provider.displayName}) - ${availabilityText}`);
51
56
 
52
57
  if (provider.ideName === 'codex') {
53
58
  console.log(chalk.gray(' IDE: Codex CLI'));
@@ -20,6 +20,7 @@ class EnvSwitcher extends BaseCommand {
20
20
  this.latestStatusMap = {};
21
21
  this.currentPromptContext = null;
22
22
  this.activeStatusRefresh = null;
23
+ this.filter = null;
23
24
  }
24
25
 
25
26
  async validateProvider(providerName) {
@@ -280,14 +281,26 @@ class EnvSwitcher extends BaseCommand {
280
281
  async showProviderSelection() {
281
282
  try {
282
283
  // 并行加载配置和准备界面
283
- const providers = await this.configManager.ensureLoaded().then(() => this.configManager.listProviders());
284
+ let providers = await this.configManager.ensureLoaded().then(() => this.configManager.listProviders());
285
+
286
+ // 应用过滤器
287
+ if (this.filter === 'codex') {
288
+ providers = providers.filter(p => p.ideName === 'codex');
289
+ } else if (this.filter === 'claude') {
290
+ providers = providers.filter(p => p.ideName !== 'codex');
291
+ }
284
292
 
285
293
  const initialStatusMap = this._buildInitialStatusMap(providers);
286
294
  // 显示欢迎界面(立即渲染)
287
295
  this.showWelcomeScreen(providers, initialStatusMap, null);
288
296
 
289
297
  if (providers.length === 0) {
290
- Logger.warning('暂无配置的供应商');
298
+ if (this.filter) {
299
+ const filterName = this.filter === 'codex' ? 'Codex CLI' : 'Claude Code';
300
+ Logger.warning(`暂无 ${filterName} 供应商配置`);
301
+ } else {
302
+ Logger.warning('暂无配置的供应商');
303
+ }
291
304
  Logger.info('请先运行 "akm add" 添加供应商配置');
292
305
  return;
293
306
  }
@@ -314,6 +327,10 @@ class EnvSwitcher extends BaseCommand {
314
327
  const currentProvider = providers.find(p => p.current);
315
328
  const defaultChoice = currentProvider ? currentProvider.name : providers[0]?.name;
316
329
 
330
+ // 构建提示信息
331
+ const filterSuffix = this.filter === 'codex' ? ' (Codex CLI)' : (this.filter === 'claude' ? ' (Claude Code)' : '');
332
+ const promptMessage = `请选择要切换的供应商${filterSuffix} (总计 ${providers.length} 个):`;
333
+
317
334
  // 设置 ESC 键监听
318
335
  const escListener = this.createESCListener(() => {
319
336
  Logger.info('退出程序');
@@ -325,7 +342,7 @@ class EnvSwitcher extends BaseCommand {
325
342
  {
326
343
  type: 'list',
327
344
  name: 'provider',
328
- message: `请选择要切换的供应商 (总计 ${providers.length} 个):`,
345
+ message: promptMessage,
329
346
  choices,
330
347
  default: defaultChoice,
331
348
  pageSize: 12
@@ -863,7 +880,11 @@ class EnvSwitcher extends BaseCommand {
863
880
  const icon = this._iconForState(availability?.state);
864
881
  const statusText = this._formatAvailability(availability);
865
882
  const statusLabel = chalk.gray('-') + ' ' + statusText;
866
- const label = `${icon} ${UIHelper.formatProvider(provider)}${isLastUsed ? UIHelper.colors.muted(' --- 上次使用') : ''} ${statusLabel}`;
883
+ // IDE 类型标签
884
+ const ideTag = provider.ideName === 'codex'
885
+ ? chalk.cyan('[Codex]')
886
+ : chalk.magenta('[Claude]');
887
+ const label = `${icon} ${ideTag} ${UIHelper.formatProvider(provider)}${isLastUsed ? UIHelper.colors.muted(' --- 上次使用') : ''} ${statusLabel}`;
867
888
 
868
889
  return {
869
890
  name: label,
@@ -1465,9 +1486,10 @@ class EnvSwitcher extends BaseCommand {
1465
1486
  }
1466
1487
  }
1467
1488
 
1468
- async function switchCommand(providerName) {
1489
+ async function switchCommand(providerName, options = {}) {
1469
1490
  const switcher = new EnvSwitcher();
1470
-
1491
+ switcher.filter = options.filter || null;
1492
+
1471
1493
  try {
1472
1494
  if (providerName) {
1473
1495
  await switcher.showLaunchArgsSelection(providerName);