@pikecode/api-key-manager 1.0.38 → 1.0.42
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 +921 -2
- package/bin/akm.js +120 -3
- package/package.json +12 -5
- package/src/CommandRegistry.js +25 -0
- package/src/commands/BaseCommand.js +67 -0
- package/src/commands/add.js +133 -153
- package/src/commands/backup.js +14 -1
- package/src/commands/batch.js +314 -0
- package/src/commands/benchmark.js +344 -0
- package/src/commands/current.js +30 -14
- package/src/commands/edit.js +66 -46
- package/src/commands/health.js +222 -0
- package/src/commands/list.js +30 -11
- package/src/commands/remove.js +22 -3
- package/src/commands/stats.js +282 -0
- package/src/commands/switch/launch-args-helper.js +84 -0
- package/src/commands/switch/status-helper.js +124 -0
- package/src/commands/switch.js +226 -164
- package/src/commands/validate.js +310 -0
- package/src/config.js +243 -2
- package/src/constants/index.js +246 -0
- package/src/index.js +10 -3
- package/src/utils/codex-files.js +66 -14
- package/src/utils/codex-launcher.js +3 -0
- package/src/utils/config-opener.js +15 -0
- package/src/utils/env-utils.js +1 -1
- package/src/utils/error-handler.js +26 -26
- package/src/utils/health-checker.js +350 -0
- package/src/utils/inquirer-setup.js +11 -0
- package/src/utils/provider-status-checker.js +29 -10
- package/src/utils/ui-helper.js +87 -85
- package/src/utils/update-checker.js +25 -7
- package/src/utils/validator.js +12 -12
package/bin/akm.js
CHANGED
|
@@ -21,9 +21,11 @@ program.hook('preAction', async () => {
|
|
|
21
21
|
// Default command - show provider selection
|
|
22
22
|
program
|
|
23
23
|
.argument('[provider]', '直接切换到指定供应商')
|
|
24
|
-
.
|
|
24
|
+
.option('-q, --quick', '快速启动(使用默认或上次的启动参数)')
|
|
25
|
+
.option('--no-args', '以空参数启动(不使用任何启动参数)')
|
|
26
|
+
.action(async (provider, options) => {
|
|
25
27
|
try {
|
|
26
|
-
await main(provider);
|
|
28
|
+
await main(provider, options);
|
|
27
29
|
} catch (error) {
|
|
28
30
|
console.error(chalk.red('❌ 执行失败:'), error.message);
|
|
29
31
|
process.exit(1);
|
|
@@ -53,10 +55,16 @@ program
|
|
|
53
55
|
.argument('[provider]', '直接切换到指定供应商')
|
|
54
56
|
.option('--codex', '仅显示 Codex CLI 供应商')
|
|
55
57
|
.option('--claude', '仅显示 Claude Code 供应商')
|
|
58
|
+
.option('-q, --quick', '快速启动(使用默认或上次的启动参数)')
|
|
59
|
+
.option('--no-args', '以空参数启动(不使用任何启动参数)')
|
|
56
60
|
.action(async (provider, options) => {
|
|
57
61
|
try {
|
|
58
62
|
const filter = options.codex ? 'codex' : (options.claude ? 'claude' : null);
|
|
59
|
-
await registry.executeCommand('switch', provider, {
|
|
63
|
+
await registry.executeCommand('switch', provider, {
|
|
64
|
+
filter,
|
|
65
|
+
quick: options.quick,
|
|
66
|
+
noArgs: options.noArgs
|
|
67
|
+
});
|
|
60
68
|
} catch (error) {
|
|
61
69
|
console.error(chalk.red('❌ 切换失败:'), error.message);
|
|
62
70
|
process.exit(1);
|
|
@@ -168,5 +176,114 @@ program
|
|
|
168
176
|
}
|
|
169
177
|
});
|
|
170
178
|
|
|
179
|
+
// Validate command
|
|
180
|
+
program
|
|
181
|
+
.command('validate')
|
|
182
|
+
.description('验证供应商配置的有效性')
|
|
183
|
+
.argument('[provider]', '要验证的供应商名称(不指定则验证全部)')
|
|
184
|
+
.option('--codex', '仅验证 Codex CLI 供应商')
|
|
185
|
+
.option('--claude', '仅验证 Claude Code 供应商')
|
|
186
|
+
.action(async (provider, options) => {
|
|
187
|
+
try {
|
|
188
|
+
const filter = options.codex ? 'codex' : (options.claude ? 'claude' : null);
|
|
189
|
+
await registry.executeCommand('validate', provider, { filter });
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.error(chalk.red('❌ 验证失败:'), error.message);
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Stats command
|
|
197
|
+
program
|
|
198
|
+
.command('stats')
|
|
199
|
+
.description('显示供应商使用统计')
|
|
200
|
+
.argument('[provider]', '要查看统计的供应商名称(不指定则显示全部)')
|
|
201
|
+
.option('--codex', '仅显示 Codex CLI 供应商')
|
|
202
|
+
.option('--claude', '仅显示 Claude Code 供应商')
|
|
203
|
+
.option('-r, --recommend', '显示智能推荐')
|
|
204
|
+
.option('-s, --sort <type>', '排序方式: usage(使用次数), time(最近使用), name(名称)', 'usage')
|
|
205
|
+
.option('-l, --limit <number>', '推荐列表数量限制', '5')
|
|
206
|
+
.action(async (provider, options) => {
|
|
207
|
+
try {
|
|
208
|
+
const filter = options.codex ? 'codex' : (options.claude ? 'claude' : null);
|
|
209
|
+
const limit = parseInt(options.limit, 10);
|
|
210
|
+
await registry.executeCommand('stats', provider, {
|
|
211
|
+
filter,
|
|
212
|
+
recommend: options.recommend,
|
|
213
|
+
sort: options.sort,
|
|
214
|
+
limit
|
|
215
|
+
});
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error(chalk.red('❌ 统计失败:'), error.message);
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Health command
|
|
223
|
+
program
|
|
224
|
+
.command('health')
|
|
225
|
+
.description('检查供应商配置健康状态')
|
|
226
|
+
.argument('[provider]', '要检查的供应商名称(不指定则检查全部)')
|
|
227
|
+
.option('--codex', '仅检查 Codex CLI 供应商')
|
|
228
|
+
.option('--claude', '仅检查 Claude Code 供应商')
|
|
229
|
+
.option('--no-connectivity', '跳过 API 连接性检查(加快速度)')
|
|
230
|
+
.action(async (provider, options) => {
|
|
231
|
+
try {
|
|
232
|
+
const filter = options.codex ? 'codex' : (options.claude ? 'claude' : null);
|
|
233
|
+
await registry.executeCommand('health', provider, {
|
|
234
|
+
filter,
|
|
235
|
+
connectivity: options.connectivity
|
|
236
|
+
});
|
|
237
|
+
} catch (error) {
|
|
238
|
+
console.error(chalk.red('❌ 健康检查失败:'), error.message);
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// Batch command
|
|
244
|
+
program
|
|
245
|
+
.command('batch <operation>')
|
|
246
|
+
.description('批量操作供应商 (operations: update, delete)')
|
|
247
|
+
.option('--codex', '仅操作 Codex CLI 供应商')
|
|
248
|
+
.option('--claude', '仅操作 Claude Code 供应商')
|
|
249
|
+
.option('--unused', '仅操作长期未使用的供应商 (90天以上)')
|
|
250
|
+
.action(async (operation, options) => {
|
|
251
|
+
try {
|
|
252
|
+
const filter = options.codex ? 'codex' : (options.claude ? 'claude' : null);
|
|
253
|
+
await registry.executeCommand('batch', operation, {
|
|
254
|
+
filter,
|
|
255
|
+
unused: options.unused
|
|
256
|
+
});
|
|
257
|
+
} catch (error) {
|
|
258
|
+
console.error(chalk.red('❌ 批量操作失败:'), error.message);
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// Benchmark command
|
|
264
|
+
program
|
|
265
|
+
.command('benchmark')
|
|
266
|
+
.description('供应商性能测试和对比')
|
|
267
|
+
.option('--codex', '仅测试 Codex CLI 供应商')
|
|
268
|
+
.option('--claude', '仅测试 Claude Code 供应商')
|
|
269
|
+
.option('-r, --rounds <number>', '每个供应商测试轮数', '3')
|
|
270
|
+
.option('-p, --parallel', '并行测试(更快但可能不准确)')
|
|
271
|
+
.option('--report [file]', '生成 Markdown 测试报告')
|
|
272
|
+
.action(async (options) => {
|
|
273
|
+
try {
|
|
274
|
+
const filter = options.codex ? 'codex' : (options.claude ? 'claude' : null);
|
|
275
|
+
const rounds = parseInt(options.rounds, 10);
|
|
276
|
+
await registry.executeCommand('benchmark', {
|
|
277
|
+
filter,
|
|
278
|
+
rounds,
|
|
279
|
+
parallel: options.parallel,
|
|
280
|
+
report: options.report
|
|
281
|
+
});
|
|
282
|
+
} catch (error) {
|
|
283
|
+
console.error(chalk.red('❌ 性能测试失败:'), error.message);
|
|
284
|
+
process.exit(1);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
171
288
|
// Parse arguments
|
|
172
289
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikecode/api-key-manager",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.42",
|
|
4
4
|
"description": "A CLI tool for managing and switching multiple API provider configurations",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
"start": "node bin/akm.js",
|
|
17
17
|
"dev": "nodemon bin/akm.js",
|
|
18
18
|
"test": "jest",
|
|
19
|
+
"lint": "eslint src/**/*.js tests/**/*.js",
|
|
20
|
+
"lint:fix": "eslint src/**/*.js tests/**/*.js --fix",
|
|
21
|
+
"format": "prettier --write \"src/**/*.js\" \"tests/**/*.js\"",
|
|
22
|
+
"format:check": "prettier --check \"src/**/*.js\" \"tests/**/*.js\"",
|
|
19
23
|
"build": "echo 'No build needed'",
|
|
20
24
|
"prepare": "echo 'Preparing package'",
|
|
21
25
|
"prepublishOnly": "npm test",
|
|
@@ -53,17 +57,20 @@
|
|
|
53
57
|
},
|
|
54
58
|
"homepage": "https://github.com/pikecode/api-key-manager#readme",
|
|
55
59
|
"dependencies": {
|
|
56
|
-
"@anthropic-ai/sdk": "
|
|
60
|
+
"@anthropic-ai/sdk": "0.71.2",
|
|
57
61
|
"chalk": "^4.1.2",
|
|
58
|
-
"commander": "
|
|
62
|
+
"commander": "14.0.2",
|
|
59
63
|
"cross-spawn": "^7.0.3",
|
|
60
64
|
"fs-extra": "^11.1.1",
|
|
61
65
|
"inquirer": "^8.2.6",
|
|
62
66
|
"supports-color": "^9.4.0"
|
|
63
67
|
},
|
|
64
68
|
"devDependencies": {
|
|
65
|
-
"
|
|
66
|
-
"
|
|
69
|
+
"eslint": "^9.39.2",
|
|
70
|
+
"eslint-config-prettier": "^10.1.8",
|
|
71
|
+
"jest": "30.2.0",
|
|
72
|
+
"nodemon": "^3.0.1",
|
|
73
|
+
"prettier": "^3.8.1"
|
|
67
74
|
},
|
|
68
75
|
"engines": {
|
|
69
76
|
"node": ">=14.0.0"
|
package/src/CommandRegistry.js
CHANGED
|
@@ -86,4 +86,29 @@ registry.registerLazy('backup', async () => {
|
|
|
86
86
|
return backupCommand;
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
+
registry.registerLazy('validate', async () => {
|
|
90
|
+
const { validateCommand } = require('./commands/validate');
|
|
91
|
+
return validateCommand;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
registry.registerLazy('stats', async () => {
|
|
95
|
+
const { statsCommand } = require('./commands/stats');
|
|
96
|
+
return statsCommand;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
registry.registerLazy('health', async () => {
|
|
100
|
+
const { healthCommand } = require('./commands/health');
|
|
101
|
+
return healthCommand;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
registry.registerLazy('batch', async () => {
|
|
105
|
+
const { batchCommand } = require('./commands/batch');
|
|
106
|
+
return batchCommand;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
registry.registerLazy('benchmark', async () => {
|
|
110
|
+
const { benchmarkCommand } = require('./commands/benchmark');
|
|
111
|
+
return benchmarkCommand;
|
|
112
|
+
});
|
|
113
|
+
|
|
89
114
|
module.exports = { CommandRegistry, registry };
|
|
@@ -57,17 +57,38 @@ if (InputPrompt && !InputPrompt.prototype.__allowEmptyPatched) {
|
|
|
57
57
|
|
|
58
58
|
const ESC_CANCELLED_ERROR_CODE = 'ESC_CANCELLED';
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* 命令基类
|
|
62
|
+
* 提供所有命令的通用功能,包括 ESC 键处理、提示交互等
|
|
63
|
+
*/
|
|
60
64
|
class BaseCommand {
|
|
65
|
+
/**
|
|
66
|
+
* 创建命令实例
|
|
67
|
+
* @param {Object} [options={}] - 配置选项
|
|
68
|
+
* @param {NodeJS.ReadStream} [options.input] - 输入流
|
|
69
|
+
*/
|
|
61
70
|
constructor(options = {}) {
|
|
62
71
|
const input = options.input || process.stdin;
|
|
72
|
+
/** @type {EscNavigationManager} ESC 键管理器 */
|
|
63
73
|
this.escManager = new EscNavigationManager(input);
|
|
74
|
+
/** @type {Object|null} 当前活动的提示 */
|
|
64
75
|
this.activePrompt = null;
|
|
65
76
|
}
|
|
66
77
|
|
|
78
|
+
/**
|
|
79
|
+
* 检查错误是否为 ESC 取消错误
|
|
80
|
+
* @param {Error} error - 错误对象
|
|
81
|
+
* @returns {boolean} 是否为 ESC 取消
|
|
82
|
+
*/
|
|
67
83
|
isEscCancelled(error) {
|
|
68
84
|
return Boolean(error && error.code === ESC_CANCELLED_ERROR_CODE);
|
|
69
85
|
}
|
|
70
86
|
|
|
87
|
+
/**
|
|
88
|
+
* 显示交互式提示
|
|
89
|
+
* @param {Array|Object} questions - Inquirer 问题配置
|
|
90
|
+
* @returns {Promise<Object>} 用户答案
|
|
91
|
+
*/
|
|
71
92
|
async prompt(questions) {
|
|
72
93
|
const promptPromise = inquirer.prompt(questions);
|
|
73
94
|
let settled = false;
|
|
@@ -204,6 +225,52 @@ class BaseCommand {
|
|
|
204
225
|
}
|
|
205
226
|
}
|
|
206
227
|
|
|
228
|
+
/**
|
|
229
|
+
* 带 ESC 监听的 prompt 执行
|
|
230
|
+
* 自动处理 ESC 监听器的创建、移除和错误处理
|
|
231
|
+
* @param {Array} questions - inquirer 问卷配置
|
|
232
|
+
* @param {string} escMessage - ESC 返回信息
|
|
233
|
+
* @param {Function} escCallback - ESC 按下时的回调函数
|
|
234
|
+
* @returns {Promise<Object>} - prompt 答案
|
|
235
|
+
*/
|
|
236
|
+
async promptWithESC(questions, escMessage, escCallback) {
|
|
237
|
+
const escListener = this.createESCListener(escCallback, escMessage);
|
|
238
|
+
try {
|
|
239
|
+
return await this.prompt(questions);
|
|
240
|
+
} catch (error) {
|
|
241
|
+
if (this.isEscCancelled(error)) {
|
|
242
|
+
this.removeESCListener(escListener);
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
throw error;
|
|
246
|
+
} finally {
|
|
247
|
+
this.removeESCListener(escListener);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* 带 ESC 监听的 prompt 执行,支持返回默认值
|
|
253
|
+
* @param {Array} questions - inquirer 问卷配置
|
|
254
|
+
* @param {string} escMessage - ESC 返回信息
|
|
255
|
+
* @param {Function} escCallback - ESC 按下时的回调函数
|
|
256
|
+
* @param {*} escReturnValue - ESC 按下时返回的值
|
|
257
|
+
* @returns {Promise<Object|*>} - prompt 答案或 escReturnValue
|
|
258
|
+
*/
|
|
259
|
+
async promptWithESCAndDefault(questions, escMessage, escCallback, escReturnValue) {
|
|
260
|
+
const escListener = this.createESCListener(escCallback, escMessage);
|
|
261
|
+
try {
|
|
262
|
+
return await this.prompt(questions);
|
|
263
|
+
} catch (error) {
|
|
264
|
+
if (this.isEscCancelled(error)) {
|
|
265
|
+
this.removeESCListener(escListener);
|
|
266
|
+
return escReturnValue;
|
|
267
|
+
}
|
|
268
|
+
throw error;
|
|
269
|
+
} finally {
|
|
270
|
+
this.removeESCListener(escListener);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
207
274
|
destroy() {
|
|
208
275
|
this.cleanupAllListeners();
|
|
209
276
|
}
|