@pikecode/api-key-manager 1.1.2 → 1.2.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/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  - 🔄 **快速切换** — 一键切换不同的 API 供应商
12
12
  - ⚡ **快速启动** — `-q` 跳过参数选择,秒级启动
13
13
  - 🧠 **智能记忆** — 自动记住上次使用的启动参数
14
- - 🏷️ **别名系统**为供应商设置简短别名
14
+ - 🗑️ **批量删除**支持一次选择多个供应商批量删除
15
15
  - 🔌 **MCP 管理** — 管理 Claude Code 的 MCP 服务器配置
16
16
  - 🧹 **配置清理** — 清理 Claude Code 配置文件中的冗余数据
17
17
  - 📋 **克隆配置** — 快速克隆现有供应商配置
@@ -53,7 +53,7 @@ akm current
53
53
  |------|------|
54
54
  | `akm` / `akm switch` | 交互式选择和切换供应商 |
55
55
  | `akm add` | 添加新的供应商配置 |
56
- | `akm remove [provider]` | 删除供应商配置 |
56
+ | `akm remove [provider]` | 删除供应商配置(支持批量删除) |
57
57
  | `akm list` | 列出所有供应商 |
58
58
  | `akm current` | 显示当前激活的配置 |
59
59
  | `akm edit [provider]` | 编辑供应商配置 |
@@ -128,9 +128,6 @@ akm
128
128
  # 直接切换到指定供应商
129
129
  akm my-provider
130
130
 
131
- # 通过别名切换
132
- akm prod
133
-
134
131
  # 仅显示 Claude Code 供应商
135
132
  akm switch --claude
136
133
 
@@ -185,6 +182,39 @@ akm clone my-provider
185
182
 
186
183
  克隆时可以修改名称、认证模式、Token、基础 URL 等。
187
184
 
185
+ ### 删除供应商
186
+
187
+ ```bash
188
+ # 交互式删除(支持批量选择)
189
+ akm remove
190
+
191
+ # 直接删除指定供应商
192
+ akm remove my-provider
193
+ ```
194
+
195
+ **批量删除流程:**
196
+ 1. 使用空格键选择多个要删除的供应商
197
+ 2. 按 Enter 确认选择
198
+ 3. 预览将要删除的供应商列表
199
+ 4. 最终确认后批量删除
200
+
201
+ ### 编辑供应商
202
+
203
+ ```bash
204
+ # 交互式选择要编辑的供应商
205
+ akm edit
206
+
207
+ # 直接编辑指定供应商
208
+ akm edit my-provider
209
+ ```
210
+
211
+ **可编辑内容:**
212
+ - 供应商名称
213
+ - 认证模式
214
+ - API 基础 URL
215
+ - Token
216
+ - 启动参数
217
+
188
218
  ### MCP 服务器管理
189
219
 
190
220
  管理 `~/.claude.json` 中的 MCP 服务器配置:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikecode/api-key-manager",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "description": "A CLI tool for managing and switching multiple API provider configurations",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Codex configuration importer.
3
+ * Reads existing Codex config from ~/.codex and extracts API key and base URL.
4
+ */
5
+
6
+ const { Logger } = require('../../utils/logger');
7
+ const { readCodexFiles, extractBaseUrlFromConfigToml } = require('../../utils/codex-files');
8
+
9
+ /**
10
+ * Import Codex configuration from ~/.codex directory.
11
+ * @returns {Promise<{apiKey: string, baseUrl: string|null}|null>}
12
+ */
13
+ async function importCodexConfig() {
14
+ try {
15
+ const codexFiles = await readCodexFiles();
16
+
17
+ if (!codexFiles.authJson) {
18
+ return null;
19
+ }
20
+
21
+ // 解析 auth.json 获取 API Key
22
+ const authData = JSON.parse(codexFiles.authJson);
23
+ const apiKey = authData.api_key || authData.openai_api_key || authData.OPENAI_API_KEY;
24
+
25
+ if (!apiKey) {
26
+ return null;
27
+ }
28
+
29
+ // 从 config.toml 中读取当前激活 provider 的 base_url
30
+ let baseUrl = null;
31
+ if (codexFiles.configToml) {
32
+ baseUrl = extractBaseUrlFromConfigToml(codexFiles.configToml);
33
+ if (!baseUrl) {
34
+ // 兼容旧格式(akm 之前错误写入的顶层字段)
35
+ const legacyMatch = codexFiles.configToml.match(/^api_base_url\s*=\s*["']([^"']+)["']/m);
36
+ if (legacyMatch) baseUrl = legacyMatch[1];
37
+ }
38
+ }
39
+
40
+ Logger.success(`成功从 ${codexFiles.codexHome} 导入配置`);
41
+ return { apiKey, baseUrl };
42
+ } catch (error) {
43
+ Logger.warning(`导入配置失败: ${error.message}`);
44
+ return null;
45
+ }
46
+ }
47
+
48
+ module.exports = { importCodexConfig };
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Helper functions for ProviderAdder prompts.
3
+ * Each function returns a promise that resolves to the collected answers.
4
+ * The implementation mirrors the original large prompt array but is split
5
+ * into focused pieces to keep each function under 50 lines.
6
+ */
7
+
8
+ const inquirer = require('inquirer');
9
+ const chalk = require('chalk');
10
+ const { validator } = require('../../utils/validator');
11
+ const { UIHelper } = require('../../utils/ui-helper');
12
+ const { UI_MESSAGES } = require('../../constants/ui');
13
+ const { Logger } = require('../../utils/logger');
14
+ const { registry } = require('../../CommandRegistry');
15
+ const { getCodexLaunchArgs } = require('../../utils/launch-args');
16
+
17
+ /**
18
+ * Prompt the initial provider information (name, IDE, auth mode, URLs, tokens, etc.).
19
+ * @param {BaseCommand} adder - the ProviderAdder instance (used for ESC handling)
20
+ * @returns {Promise<Object>} answers object
21
+ */
22
+ async function promptProviderInfo(adder) {
23
+ return await adder.promptWithESC([
24
+ {
25
+ type: 'list',
26
+ name: 'ideName',
27
+ message: UI_MESSAGES.SELECT_IDE,
28
+ choices: [
29
+ { name: UI_MESSAGES.IDE_CLAUDE_CODE, value: 'claude' },
30
+ { name: UI_MESSAGES.IDE_CODEX, value: 'codex' }
31
+ ],
32
+ default: adder.presetIdeName || 'claude',
33
+ when: () => !adder.presetIdeName
34
+ },
35
+ {
36
+ type: 'list',
37
+ name: 'importFromExisting',
38
+ message: UI_MESSAGES.IMPORT_FROM_CODEX,
39
+ choices: [
40
+ { name: UI_MESSAGES.IMPORT_FROM_CODEX_EXISTING, value: 'import' },
41
+ { name: UI_MESSAGES.IMPORT_FROM_CODEX_MANUAL, value: 'manual' }
42
+ ],
43
+ default: 'import',
44
+ when: (answers) => (answers.ideName || adder.presetIdeName) === 'codex'
45
+ },
46
+ {
47
+ type: 'input',
48
+ name: 'name',
49
+ message: UI_MESSAGES.INPUT_PROVIDER_NAME,
50
+ validate: (input) => {
51
+ const err = validator.validateName(input);
52
+ return err || true;
53
+ }
54
+ },
55
+ {
56
+ type: 'list',
57
+ name: 'authMode',
58
+ message: UI_MESSAGES.SELECT_AUTH_MODE,
59
+ choices: [
60
+ { name: UI_MESSAGES.AUTH_MODE_API_KEY, value: 'api_key' },
61
+ { name: UI_MESSAGES.AUTH_MODE_AUTH_TOKEN, value: 'auth_token' }
62
+ ],
63
+ default: 'api_key',
64
+ when: (answers) => (answers.ideName || adder.presetIdeName) !== 'codex'
65
+ },
66
+ {
67
+ type: 'input',
68
+ name: 'baseUrl',
69
+ message: UI_MESSAGES.INPUT_BASE_URL,
70
+ validate: (input) => {
71
+ if (!input) return 'API 基础URL不能为空';
72
+ const err = validator.validateUrl(input);
73
+ return err || true;
74
+ },
75
+ when: (answers) => (answers.ideName || adder.presetIdeName) !== 'codex'
76
+ },
77
+ {
78
+ type: 'input',
79
+ name: 'authToken',
80
+ message: (answers) => {
81
+ const envVar = answers.authMode === 'auth_token' ? 'ANTHROPIC_AUTH_TOKEN' : 'ANTHROPIC_API_KEY';
82
+ return `${UI_MESSAGES.INPUT_TOKEN} (${envVar}):`;
83
+ },
84
+ validate: (input) => {
85
+ const err = validator.validateToken(input);
86
+ return err || true;
87
+ },
88
+ when: (answers) => (answers.ideName || adder.presetIdeName) !== 'codex'
89
+ },
90
+ // Codex‑specific prompts (manual entry)
91
+ {
92
+ type: 'input',
93
+ name: 'baseUrl',
94
+ message: UI_MESSAGES.INPUT_OPENAI_BASE_URL,
95
+ default: '',
96
+ validate: (input) => {
97
+ if (!input) return true;
98
+ const err = validator.validateUrl(input);
99
+ return err || true;
100
+ },
101
+ when: (answers) => (answers.ideName || adder.presetIdeName) === 'codex' && answers.importFromExisting === 'manual'
102
+ },
103
+ {
104
+ type: 'input',
105
+ name: 'authToken',
106
+ message: UI_MESSAGES.INPUT_OPENAI_API_KEY,
107
+ validate: (input) => {
108
+ if (!input) return 'API Key 不能为空';
109
+ const err = validator.validateToken(input);
110
+ return err || true;
111
+ },
112
+ when: (answers) => (answers.ideName || adder.presetIdeName) === 'codex' && answers.importFromExisting === 'manual'
113
+ },
114
+ {
115
+ type: 'confirm',
116
+ name: 'setAsDefault',
117
+ message: UI_MESSAGES.SET_AS_DEFAULT,
118
+ default: true
119
+ },
120
+ {
121
+ type: 'confirm',
122
+ name: 'configureLaunchArgs',
123
+ message: UI_MESSAGES.CONFIGURE_LAUNCH_ARGS,
124
+ default: false,
125
+ when: (answers) => (answers.ideName || adder.presetIdeName) !== 'codex'
126
+ },
127
+ {
128
+ type: 'confirm',
129
+ name: 'configureCodexLaunchArgs',
130
+ message: UI_MESSAGES.CONFIGURE_CODEX_LAUNCH_ARGS,
131
+ default: false,
132
+ when: (answers) => (answers.ideName || adder.presetIdeName) === 'codex'
133
+ },
134
+ {
135
+ type: 'confirm',
136
+ name: 'configureModels',
137
+ message: UI_MESSAGES.CONFIGURE_MODELS,
138
+ default: false,
139
+ when: (answers) => (answers.ideName || adder.presetIdeName) !== 'codex'
140
+ }
141
+ ], UI_MESSAGES.ESC_CANCEL_ADD, () => {
142
+ Logger.info(UI_MESSAGES.ADD_PROVIDER_CANCELLED);
143
+ registry.executeCommand('switch');
144
+ });
145
+ }
146
+
147
+ /**
148
+ * Prompt for launch arguments (non‑Codex).
149
+ */
150
+ async function promptLaunchArgs(adder) {
151
+ console.log(UIHelper.createTitle(UI_MESSAGES.CONFIG_LAUNCH_ARGS_TITLE, UIHelper.icons.settings));
152
+ console.log();
153
+ console.log(UIHelper.createTooltip(UI_MESSAGES.CONFIG_LAUNCH_ARGS_TOOLTIP));
154
+ console.log();
155
+ console.log(UIHelper.createStepIndicator(2, 2, UI_MESSAGES.ADD_PROVIDER_STEP_2_LAUNCH_ARGS));
156
+ console.log(UIHelper.createHintLine([
157
+ [UI_MESSAGES.HINT_SPACE, UI_MESSAGES.HINT_SPACE_DESC],
158
+ [UI_MESSAGES.HINT_A, UI_MESSAGES.HINT_A_DESC],
159
+ [UI_MESSAGES.HINT_I, UI_MESSAGES.HINT_I_DESC],
160
+ [UI_MESSAGES.HINT_ENTER, UI_MESSAGES.HINT_ENTER_CONFIRM],
161
+ [UI_MESSAGES.HINT_ESC, UI_MESSAGES.HINT_ESC_SKIP]
162
+ ]));
163
+ console.log();
164
+
165
+ const result = await adder.promptWithESCAndDefault([
166
+ {
167
+ type: 'checkbox',
168
+ name: 'launchArgs',
169
+ message: UI_MESSAGES.SELECT_LAUNCH_ARGS,
170
+ choices: validator.getAvailableLaunchArgs().map(arg => ({
171
+ name: `${arg.name} - ${arg.description}`,
172
+ value: arg.name,
173
+ checked: false
174
+ }))
175
+ }
176
+ ], UI_MESSAGES.ESC_SKIP_CONFIG, () => {
177
+ Logger.info(UI_MESSAGES.CONFIG_LAUNCH_ARGS_SKIP);
178
+ }, { launchArgs: [] });
179
+
180
+ return result.launchArgs;
181
+ }
182
+
183
+ /**
184
+ * Prompt for model configuration (non‑Codex).
185
+ */
186
+ async function promptModelConfig(adder) {
187
+ console.log(UIHelper.createTitle(UI_MESSAGES.CONFIG_MODELS_TITLE, UIHelper.icons.settings));
188
+ console.log();
189
+ console.log(UIHelper.createTooltip(UI_MESSAGES.CONFIG_MODELS_TOOLTIP));
190
+ console.log();
191
+ console.log(UIHelper.createStepIndicator(2, 2, UI_MESSAGES.ADD_PROVIDER_STEP_2_MODELS));
192
+ console.log(UIHelper.createHintLine([
193
+ [UI_MESSAGES.HINT_ENTER, UI_MESSAGES.HINT_ENTER_DESC],
194
+ [UI_MESSAGES.HINT_ESC, UI_MESSAGES.HINT_ESC_SKIP]
195
+ ]));
196
+ console.log();
197
+
198
+ const responses = await adder.promptWithESCAndDefault([
199
+ {
200
+ type: 'input',
201
+ name: 'primaryModel',
202
+ message: UI_MESSAGES.INPUT_PRIMARY_MODEL,
203
+ default: '',
204
+ validate: (input) => validator.validateModel(input) || true
205
+ },
206
+ {
207
+ type: 'input',
208
+ name: 'smallFastModel',
209
+ message: UI_MESSAGES.INPUT_SMALL_FAST_MODEL,
210
+ default: '',
211
+ validate: (input) => validator.validateModel(input) || true
212
+ }
213
+ ], UI_MESSAGES.ESC_SKIP_CONFIG, () => {
214
+ Logger.info(UI_MESSAGES.CONFIG_MODELS_SKIP);
215
+ }, { primaryModel: null, smallFastModel: null });
216
+
217
+ return { primaryModel: responses.primaryModel, smallFastModel: responses.smallFastModel };
218
+ }
219
+
220
+ /**
221
+ * Prompt for Codex‑specific launch arguments.
222
+ */
223
+ async function promptCodexLaunchArgs(adder) {
224
+ // Reuse the same UI as the generic launch args but fetch Codex args.
225
+ console.log(UIHelper.createTitle(UI_MESSAGES.CONFIG_CODEX_LAUNCH_ARGS_TITLE, UIHelper.icons.settings));
226
+ console.log();
227
+ console.log(UIHelper.createTooltip(UI_MESSAGES.CONFIG_CODEX_LAUNCH_ARGS_TOOLTIP));
228
+ console.log();
229
+ console.log(UIHelper.createStepIndicator(2, 2, UI_MESSAGES.ADD_PROVIDER_STEP_2_LAUNCH_ARGS));
230
+ console.log(UIHelper.createHintLine([
231
+ [UI_MESSAGES.HINT_SPACE, UI_MESSAGES.HINT_SPACE_DESC],
232
+ [UI_MESSAGES.HINT_A, UI_MESSAGES.HINT_A_DESC],
233
+ [UI_MESSAGES.HINT_I, UI_MESSAGES.HINT_I_DESC],
234
+ [UI_MESSAGES.HINT_ENTER, UI_MESSAGES.HINT_ENTER_CONFIRM],
235
+ [UI_MESSAGES.HINT_ESC, UI_MESSAGES.HINT_ESC_SKIP]
236
+ ]));
237
+ console.log();
238
+
239
+ const result = await adder.promptWithESCAndDefault([
240
+ {
241
+ type: 'checkbox',
242
+ name: 'launchArgs',
243
+ message: UI_MESSAGES.SELECT_LAUNCH_ARGS,
244
+ choices: getCodexLaunchArgs().map(arg => ({
245
+ name: `${arg.name} - ${arg.description}`,
246
+ value: arg.name,
247
+ checked: false
248
+ }))
249
+ }
250
+ ], UI_MESSAGES.ESC_SKIP_CONFIG, () => {
251
+ Logger.info(UI_MESSAGES.CONFIG_CODEX_LAUNCH_ARGS_SKIP);
252
+ }, { launchArgs: [] });
253
+
254
+ return result.launchArgs;
255
+ }
256
+
257
+ module.exports = {
258
+ promptProviderInfo,
259
+ promptLaunchArgs,
260
+ promptModelConfig,
261
+ promptCodexLaunchArgs
262
+ };
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Helper for saving a new provider and printing a summary.
3
+ * This mirrors the original `saveProvider` implementation but is split
4
+ * into a dedicated module to keep each file under 50 lines.
5
+ */
6
+
7
+ const { Logger } = require('../../utils/logger');
8
+ const { UIHelper } = require('../../utils/ui-helper');
9
+ const { promptCodexLaunchArgs, promptLaunchArgs, promptModelConfig } = require('./prompts');
10
+ const { printProviderSummary } = require('./summaryPrinter');
11
+ const { registry } = require('../../CommandRegistry');
12
+
13
+ /**
14
+ * Save the provider configuration.
15
+ * @param {BaseCommand} adder - ProviderAdder instance (provides configManager, prompts, etc.)
16
+ * @param {Object} answers - Collected answers from the prompts.
17
+ */
18
+ async function saveProvider(adder, answers) {
19
+ try {
20
+ await adder.configManager.load();
21
+
22
+ // 如果已经存在同名供应商,询问是否覆盖
23
+ if (adder.configManager.getProvider(answers.name)) {
24
+ const shouldOverwrite = await adder.confirmOverwrite(answers.name);
25
+ if (!shouldOverwrite) {
26
+ Logger.warning('操作已取消');
27
+ return;
28
+ }
29
+ }
30
+
31
+ // 处理启动参数
32
+ let launchArgs = [];
33
+ if (answers.ideName === 'codex') {
34
+ launchArgs = answers.configureCodexLaunchArgs
35
+ ? await promptCodexLaunchArgs(adder)
36
+ : [];
37
+ } else {
38
+ launchArgs = answers.configureLaunchArgs
39
+ ? await promptLaunchArgs(adder)
40
+ : [];
41
+ }
42
+
43
+ // 处理模型配置(仅非 Codex)
44
+ let modelConfig = { primaryModel: null, smallFastModel: null };
45
+ if (answers.configureModels) {
46
+ modelConfig = await promptModelConfig(adder);
47
+ }
48
+
49
+ // 保存到配置管理器(保持不可变写法在 configManager 中实现)
50
+ await adder.configManager.addProvider(answers.name, {
51
+ displayName: answers.displayName || answers.name,
52
+ ideName: answers.ideName || 'claude',
53
+ baseUrl: answers.baseUrl,
54
+ authToken: answers.authToken,
55
+ authMode: answers.authMode,
56
+ codexFiles: answers.codexFiles || null,
57
+ launchArgs,
58
+ primaryModel: modelConfig.primaryModel,
59
+ smallFastModel: modelConfig.smallFastModel,
60
+ setAsDefault: answers.setAsDefault
61
+ });
62
+
63
+ // 打印摘要并返回主界面
64
+ await printProviderSummary(adder, answers, launchArgs, modelConfig);
65
+ await adder.pauseBeforeReturn();
66
+
67
+ return await registry.executeCommand('switch');
68
+ } catch (error) {
69
+ if (adder.isEscCancelled(error)) return;
70
+ Logger.error(`添加供应商失败: ${error.message}`);
71
+ throw error;
72
+ }
73
+ }
74
+
75
+ module.exports = { saveProvider };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Helper for printing provider summary after creation.
3
+ * Mirrors the original `printProviderSummary` logic but isolated.
4
+ */
5
+ const chalk = require('chalk');
6
+ const { Logger } = require('../../utils/logger');
7
+ const { UIHelper } = require('../../utils/ui-helper');
8
+ const { UI_MESSAGES } = require('../../constants/ui');
9
+
10
+ async function printProviderSummary(adder, answers, launchArgs, modelConfig) {
11
+ console.log(UIHelper.createTitle('供应商已创建', UIHelper.icons.success));
12
+ console.log();
13
+ console.log(chalk.gray(` ${UI_MESSAGES.PROVIDER_NAME}: ${answers.name}`));
14
+ console.log(chalk.gray(` ${UI_MESSAGES.PROVIDER_IDE}: ${answers.ideName || 'claude'}`));
15
+ if (answers.baseUrl) console.log(chalk.gray(` ${UI_MESSAGES.PROVIDER_BASE_URL}: ${answers.baseUrl}`));
16
+ if (answers.authToken) console.log(chalk.gray(` ${UI_MESSAGES.PROVIDER_TOKEN}: ${answers.authToken}`));
17
+ if (launchArgs && launchArgs.length) {
18
+ console.log(chalk.gray(` ${UI_MESSAGES.PROVIDER_LAUNCH_ARGS}:`));
19
+ launchArgs.forEach(arg => console.log(chalk.gray(` - ${arg}`)));
20
+ }
21
+ if (modelConfig && (modelConfig.primaryModel || modelConfig.smallFastModel)) {
22
+ console.log(chalk.gray(` ${UI_MESSAGES.PROVIDER_MODEL_CONFIG}:`));
23
+ if (modelConfig.primaryModel) console.log(chalk.gray(` ${UI_MESSAGES.PROVIDER_PRIMARY_MODEL}: ${modelConfig.primaryModel}`));
24
+ if (modelConfig.smallFastModel) console.log(chalk.gray(` ${UI_MESSAGES.PROVIDER_SMALL_FAST_MODEL}: ${modelConfig.smallFastModel}`));
25
+ }
26
+ Logger.success(UI_MESSAGES.ADD_PROVIDER_SUCCESS);
27
+ }
28
+
29
+ module.exports = { printProviderSummary };