@pikecode/api-key-manager 1.0.21 → 1.0.24

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.24",
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) {
@@ -35,11 +36,9 @@ class EnvSwitcher extends BaseCommand {
35
36
  try {
36
37
  this.clearScreen();
37
38
  const provider = await this.validateProvider(providerName);
38
- if (provider.ideName === 'codex') {
39
- // Codex CLI 使用环境变量注入方式启动,直接跳过参数选择
40
- return await this.launchProvider(provider, provider.launchArgs || []);
41
- }
42
- const availableArgs = this.getAvailableLaunchArgs();
39
+ const isCodex = provider.ideName === 'codex';
40
+ const availableArgs = isCodex ? this.getCodexLaunchArgs() : this.getAvailableLaunchArgs();
41
+ const ideDisplayName = isCodex ? 'Codex CLI' : 'Claude Code';
43
42
 
44
43
  console.log(UIHelper.createTitle('启动配置', UIHelper.icons.launch));
45
44
  console.log();
@@ -49,7 +48,7 @@ class EnvSwitcher extends BaseCommand {
49
48
  ['空格', '切换选中'],
50
49
  ['A', '全选'],
51
50
  ['I', '反选'],
52
- ['Enter', '启动 Claude Code'],
51
+ ['Enter', `启动 ${ideDisplayName}`],
53
52
  ['ESC', '返回供应商选择']
54
53
  ]));
55
54
  console.log();
@@ -275,19 +274,60 @@ class EnvSwitcher extends BaseCommand {
275
274
  ];
276
275
  }
277
276
 
277
+ getCodexLaunchArgs() {
278
+ return [
279
+ {
280
+ name: '--continue',
281
+ label: '继续上次对话',
282
+ description: '恢复上次的对话记录',
283
+ checked: false
284
+ },
285
+ {
286
+ name: '--full-auto',
287
+ label: '全自动模式',
288
+ description: '自动批准所有操作',
289
+ checked: false
290
+ },
291
+ {
292
+ name: '--dangerously-bypass-approvals-and-sandbox',
293
+ label: '跳过审批和沙盒',
294
+ description: '危险:跳过所有安全检查',
295
+ checked: false
296
+ },
297
+ {
298
+ name: '--quiet',
299
+ label: '静默模式',
300
+ description: '减少输出信息',
301
+ checked: false
302
+ }
303
+ ];
304
+ }
305
+
278
306
  // getArgDescription 方法已被移除,直接使用 arg.description
279
307
 
280
308
  async showProviderSelection() {
281
309
  try {
282
310
  // 并行加载配置和准备界面
283
- const providers = await this.configManager.ensureLoaded().then(() => this.configManager.listProviders());
311
+ let providers = await this.configManager.ensureLoaded().then(() => this.configManager.listProviders());
312
+
313
+ // 应用过滤器
314
+ if (this.filter === 'codex') {
315
+ providers = providers.filter(p => p.ideName === 'codex');
316
+ } else if (this.filter === 'claude') {
317
+ providers = providers.filter(p => p.ideName !== 'codex');
318
+ }
284
319
 
285
320
  const initialStatusMap = this._buildInitialStatusMap(providers);
286
321
  // 显示欢迎界面(立即渲染)
287
322
  this.showWelcomeScreen(providers, initialStatusMap, null);
288
323
 
289
324
  if (providers.length === 0) {
290
- Logger.warning('暂无配置的供应商');
325
+ if (this.filter) {
326
+ const filterName = this.filter === 'codex' ? 'Codex CLI' : 'Claude Code';
327
+ Logger.warning(`暂无 ${filterName} 供应商配置`);
328
+ } else {
329
+ Logger.warning('暂无配置的供应商');
330
+ }
291
331
  Logger.info('请先运行 "akm add" 添加供应商配置');
292
332
  return;
293
333
  }
@@ -314,6 +354,10 @@ class EnvSwitcher extends BaseCommand {
314
354
  const currentProvider = providers.find(p => p.current);
315
355
  const defaultChoice = currentProvider ? currentProvider.name : providers[0]?.name;
316
356
 
357
+ // 构建提示信息
358
+ const filterSuffix = this.filter === 'codex' ? ' (Codex CLI)' : (this.filter === 'claude' ? ' (Claude Code)' : '');
359
+ const promptMessage = `请选择要切换的供应商${filterSuffix} (总计 ${providers.length} 个):`;
360
+
317
361
  // 设置 ESC 键监听
318
362
  const escListener = this.createESCListener(() => {
319
363
  Logger.info('退出程序');
@@ -325,7 +369,7 @@ class EnvSwitcher extends BaseCommand {
325
369
  {
326
370
  type: 'list',
327
371
  name: 'provider',
328
- message: `请选择要切换的供应商 (总计 ${providers.length} 个):`,
372
+ message: promptMessage,
329
373
  choices,
330
374
  default: defaultChoice,
331
375
  pageSize: 12
@@ -863,7 +907,11 @@ class EnvSwitcher extends BaseCommand {
863
907
  const icon = this._iconForState(availability?.state);
864
908
  const statusText = this._formatAvailability(availability);
865
909
  const statusLabel = chalk.gray('-') + ' ' + statusText;
866
- const label = `${icon} ${UIHelper.formatProvider(provider)}${isLastUsed ? UIHelper.colors.muted(' --- 上次使用') : ''} ${statusLabel}`;
910
+ // IDE 类型标签
911
+ const ideTag = provider.ideName === 'codex'
912
+ ? chalk.cyan('[Codex]')
913
+ : chalk.magenta('[Claude]');
914
+ const label = `${icon} ${ideTag} ${UIHelper.formatProvider(provider)}${isLastUsed ? UIHelper.colors.muted(' --- 上次使用') : ''} ${statusLabel}`;
867
915
 
868
916
  return {
869
917
  name: label,
@@ -1465,9 +1513,10 @@ class EnvSwitcher extends BaseCommand {
1465
1513
  }
1466
1514
  }
1467
1515
 
1468
- async function switchCommand(providerName) {
1516
+ async function switchCommand(providerName, options = {}) {
1469
1517
  const switcher = new EnvSwitcher();
1470
-
1518
+ switcher.filter = options.filter || null;
1519
+
1471
1520
  try {
1472
1521
  if (providerName) {
1473
1522
  await switcher.showLaunchArgsSelection(providerName);