@pikecode/api-key-manager 1.0.39 → 1.0.43

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,314 @@
1
+ /**
2
+ * Batch Operations Command
3
+ * 批量操作功能
4
+ * @module commands/batch
5
+ */
6
+
7
+ const chalk = require('chalk');
8
+ const { configManager } = require('../config');
9
+ const { Logger } = require('../utils/logger');
10
+ const { UIHelper } = require('../utils/ui-helper');
11
+ const inquirer = require('inquirer');
12
+
13
+ /**
14
+ * 批量更新供应商配置
15
+ * @param {Object} options - 选项
16
+ */
17
+ async function batchUpdate(options = {}) {
18
+ try {
19
+ await configManager.ensureLoaded();
20
+ let providers = configManager.listProviders();
21
+
22
+ // 应用过滤器
23
+ if (options.filter === 'codex') {
24
+ providers = providers.filter(p => p.ideName === 'codex');
25
+ } else if (options.filter === 'claude') {
26
+ providers = providers.filter(p => p.ideName !== 'codex');
27
+ }
28
+
29
+ if (providers.length === 0) {
30
+ Logger.warning('暂无可更新的供应商');
31
+ return;
32
+ }
33
+
34
+ console.log(chalk.blue(`\n📦 批量更新供应商配置`));
35
+ console.log(chalk.gray('═'.repeat(60)));
36
+ console.log();
37
+
38
+ console.log(chalk.yellow(`将更新以下 ${providers.length} 个供应商:`));
39
+ providers.forEach(p => {
40
+ const ideTag = p.ideName === 'codex' ? chalk.cyan('[Codex]') : chalk.magenta('[Claude]');
41
+ console.log(` ${ideTag} ${p.name} (${p.displayName})`);
42
+ });
43
+ console.log();
44
+
45
+ // 选择要更新的字段
46
+ const { fields } = await inquirer.prompt([
47
+ {
48
+ type: 'checkbox',
49
+ name: 'fields',
50
+ message: '选择要更新的字段:',
51
+ choices: [
52
+ { name: 'Base URL (基础 URL)', value: 'baseUrl' },
53
+ { name: 'Token Expiry (Token 过期时间)', value: 'tokenExpiry' },
54
+ { name: 'Quota Limit (配额限制)', value: 'quotaLimit' },
55
+ { name: 'Launch Args (启动参数)', value: 'launchArgs' }
56
+ ]
57
+ }
58
+ ]);
59
+
60
+ if (fields.length === 0) {
61
+ Logger.info('未选择任何字段,操作已取消');
62
+ return;
63
+ }
64
+
65
+ // 收集更新值
66
+ const updates = {};
67
+ for (const field of fields) {
68
+ let answer;
69
+ switch (field) {
70
+ case 'baseUrl':
71
+ answer = await inquirer.prompt([
72
+ {
73
+ type: 'input',
74
+ name: 'value',
75
+ message: '输入新的 Base URL (留空表示清除):',
76
+ validate: (input) => {
77
+ if (input && !input.startsWith('http://') && !input.startsWith('https://')) {
78
+ return 'Base URL 必须以 http:// 或 https:// 开头';
79
+ }
80
+ return true;
81
+ }
82
+ }
83
+ ]);
84
+ updates.baseUrl = answer.value || null;
85
+ break;
86
+
87
+ case 'tokenExpiry':
88
+ answer = await inquirer.prompt([
89
+ {
90
+ type: 'input',
91
+ name: 'value',
92
+ message: '输入 Token 过期时间 (YYYY-MM-DD 格式, 留空表示清除):',
93
+ validate: (input) => {
94
+ if (input && !/^\d{4}-\d{2}-\d{2}$/.test(input)) {
95
+ return '请使用 YYYY-MM-DD 格式';
96
+ }
97
+ return true;
98
+ }
99
+ }
100
+ ]);
101
+ updates.tokenExpiry = answer.value ? new Date(answer.value).toISOString() : null;
102
+ break;
103
+
104
+ case 'quotaLimit':
105
+ answer = await inquirer.prompt([
106
+ {
107
+ type: 'number',
108
+ name: 'value',
109
+ message: '输入配额限制 (数字, 0 表示清除):',
110
+ default: 0
111
+ }
112
+ ]);
113
+ updates.quota = answer.value > 0 ? { limit: answer.value } : null;
114
+ break;
115
+
116
+ case 'launchArgs':
117
+ answer = await inquirer.prompt([
118
+ {
119
+ type: 'input',
120
+ name: 'value',
121
+ message: '输入启动参数 (空格分隔, 留空表示清除):',
122
+ }
123
+ ]);
124
+ updates.launchArgs = answer.value ? answer.value.split(/\s+/).filter(Boolean) : [];
125
+ break;
126
+ }
127
+ }
128
+
129
+ // 确认更新
130
+ console.log();
131
+ console.log(chalk.yellow('将应用以下更新:'));
132
+ Object.entries(updates).forEach(([key, value]) => {
133
+ console.log(` ${key}: ${JSON.stringify(value)}`);
134
+ });
135
+ console.log();
136
+
137
+ const { confirm } = await inquirer.prompt([
138
+ {
139
+ type: 'confirm',
140
+ name: 'confirm',
141
+ message: `确认更新 ${providers.length} 个供应商?`,
142
+ default: false
143
+ }
144
+ ]);
145
+
146
+ if (!confirm) {
147
+ Logger.info('操作已取消');
148
+ return;
149
+ }
150
+
151
+ // 执行更新
152
+ let successCount = 0;
153
+ let failureCount = 0;
154
+
155
+ for (const provider of providers) {
156
+ try {
157
+ const updatedConfig = { ...provider, ...updates };
158
+
159
+ // 特殊处理 quota
160
+ if (updates.quota !== undefined) {
161
+ if (updates.quota === null) {
162
+ delete updatedConfig.quota;
163
+ } else {
164
+ updatedConfig.quota = {
165
+ ...(provider.quota || {}),
166
+ limit: updates.quota.limit
167
+ };
168
+ }
169
+ }
170
+
171
+ await configManager.addProvider(provider.name, updatedConfig);
172
+ successCount++;
173
+ } catch (error) {
174
+ Logger.error(`更新 ${provider.name} 失败: ${error.message}`);
175
+ failureCount++;
176
+ }
177
+ }
178
+
179
+ console.log();
180
+ console.log(chalk.green(`✓ 成功更新 ${successCount} 个供应商`));
181
+ if (failureCount > 0) {
182
+ console.log(chalk.red(`✗ 失败 ${failureCount} 个`));
183
+ }
184
+
185
+ } catch (error) {
186
+ Logger.error(`批量更新失败: ${error.message}`);
187
+ throw error;
188
+ }
189
+ }
190
+
191
+ /**
192
+ * 批量删除供应商
193
+ * @param {Object} options - 选项
194
+ */
195
+ async function batchDelete(options = {}) {
196
+ try {
197
+ await configManager.ensureLoaded();
198
+ let providers = configManager.listProviders();
199
+
200
+ // 应用过滤器
201
+ if (options.filter === 'codex') {
202
+ providers = providers.filter(p => p.ideName === 'codex');
203
+ } else if (options.filter === 'claude') {
204
+ providers = providers.filter(p => p.ideName !== 'codex');
205
+ }
206
+
207
+ // 根据条件过滤
208
+ if (options.unused) {
209
+ const ninetyDaysAgo = Date.now() - (90 * 24 * 60 * 60 * 1000);
210
+ providers = providers.filter(p => {
211
+ if (!p.lastUsed) return true;
212
+ return new Date(p.lastUsed).getTime() < ninetyDaysAgo;
213
+ });
214
+ }
215
+
216
+ if (providers.length === 0) {
217
+ Logger.warning('暂无符合条件的供应商');
218
+ return;
219
+ }
220
+
221
+ console.log(chalk.blue(`\n🗑️ 批量删除供应商`));
222
+ console.log(chalk.gray('═'.repeat(60)));
223
+ console.log();
224
+
225
+ console.log(chalk.red(`将删除以下 ${providers.length} 个供应商:`));
226
+ providers.forEach(p => {
227
+ const ideTag = p.ideName === 'codex' ? chalk.cyan('[Codex]') : chalk.magenta('[Claude]');
228
+ const lastUsed = p.lastUsed
229
+ ? new Date(p.lastUsed).toLocaleDateString('zh-CN')
230
+ : '从未使用';
231
+ console.log(` ${ideTag} ${p.name} (${p.displayName}) - 最后使用: ${lastUsed}`);
232
+ });
233
+ console.log();
234
+
235
+ const { confirm } = await inquirer.prompt([
236
+ {
237
+ type: 'confirm',
238
+ name: 'confirm',
239
+ message: chalk.red(`确认删除这 ${providers.length} 个供应商? (此操作不可恢复)`),
240
+ default: false
241
+ }
242
+ ]);
243
+
244
+ if (!confirm) {
245
+ Logger.info('操作已取消');
246
+ return;
247
+ }
248
+
249
+ // 再次确认
250
+ const { confirmAgain } = await inquirer.prompt([
251
+ {
252
+ type: 'input',
253
+ name: 'confirmAgain',
254
+ message: `请输入 "DELETE" 以确认删除:`,
255
+ validate: (input) => {
256
+ if (input !== 'DELETE') {
257
+ return '请输入 "DELETE" 以确认';
258
+ }
259
+ return true;
260
+ }
261
+ }
262
+ ]);
263
+
264
+ // 执行删除
265
+ let successCount = 0;
266
+ let failureCount = 0;
267
+
268
+ for (const provider of providers) {
269
+ try {
270
+ await configManager.removeProvider(provider.name);
271
+ successCount++;
272
+ } catch (error) {
273
+ Logger.error(`删除 ${provider.name} 失败: ${error.message}`);
274
+ failureCount++;
275
+ }
276
+ }
277
+
278
+ console.log();
279
+ console.log(chalk.green(`✓ 成功删除 ${successCount} 个供应商`));
280
+ if (failureCount > 0) {
281
+ console.log(chalk.red(`✗ 失败 ${failureCount} 个`));
282
+ }
283
+
284
+ } catch (error) {
285
+ Logger.error(`批量删除失败: ${error.message}`);
286
+ throw error;
287
+ }
288
+ }
289
+
290
+ /**
291
+ * 批量操作命令
292
+ * @param {string} operation - 操作类型
293
+ * @param {Object} options - 选项
294
+ */
295
+ async function batchCommand(operation, options = {}) {
296
+ try {
297
+ switch (operation) {
298
+ case 'update':
299
+ await batchUpdate(options);
300
+ break;
301
+ case 'delete':
302
+ await batchDelete(options);
303
+ break;
304
+ default:
305
+ Logger.error(`未知的批量操作: ${operation}`);
306
+ Logger.info('支持的操作: update, delete');
307
+ }
308
+ } catch (error) {
309
+ Logger.error(`批量操作失败: ${error.message}`);
310
+ throw error;
311
+ }
312
+ }
313
+
314
+ module.exports = { batchCommand };
@@ -0,0 +1,344 @@
1
+ /**
2
+ * Benchmark Command
3
+ * 多供应商性能测试和对比
4
+ * @module commands/benchmark
5
+ */
6
+
7
+ const chalk = require('chalk');
8
+ const fs = require('fs-extra');
9
+ const path = require('path');
10
+ const os = require('os');
11
+ const { configManager } = require('../config');
12
+ const { Logger } = require('../utils/logger');
13
+ const { ProviderStatusChecker } = require('../utils/provider-status-checker');
14
+ const { UIHelper } = require('../utils/ui-helper');
15
+
16
+ /**
17
+ * 性能测试器
18
+ */
19
+ class BenchmarkRunner {
20
+ constructor() {
21
+ this.statusChecker = new ProviderStatusChecker();
22
+ }
23
+
24
+ /**
25
+ * 运行单个供应商的性能测试
26
+ * @param {Object} provider - 供应商配置
27
+ * @param {number} rounds - 测试轮数
28
+ * @returns {Promise<Object>} 测试结果
29
+ */
30
+ async runTest(provider, rounds = 3) {
31
+ const results = {
32
+ provider: provider.name,
33
+ displayName: provider.displayName,
34
+ ideName: provider.ideName,
35
+ rounds: [],
36
+ average: 0,
37
+ min: Infinity,
38
+ max: 0,
39
+ successRate: 0,
40
+ errors: []
41
+ };
42
+
43
+ for (let i = 0; i < rounds; i++) {
44
+ try {
45
+ const status = await this.statusChecker.check(provider, { skipCache: true });
46
+
47
+ const result = {
48
+ round: i + 1,
49
+ latency: status.latency,
50
+ state: status.state,
51
+ success: status.state === 'online'
52
+ };
53
+
54
+ results.rounds.push(result);
55
+
56
+ if (status.latency !== null) {
57
+ results.min = Math.min(results.min, status.latency);
58
+ results.max = Math.max(results.max, status.latency);
59
+ }
60
+
61
+ // 间隔一下避免频繁请求
62
+ if (i < rounds - 1) {
63
+ await new Promise(resolve => setTimeout(resolve, 1000));
64
+ }
65
+ } catch (error) {
66
+ results.rounds.push({
67
+ round: i + 1,
68
+ latency: null,
69
+ state: 'error',
70
+ success: false,
71
+ error: error.message
72
+ });
73
+ results.errors.push(error.message);
74
+ }
75
+ }
76
+
77
+ // 计算统计
78
+ const successfulRounds = results.rounds.filter(r => r.success);
79
+ results.successRate = (successfulRounds.length / rounds) * 100;
80
+
81
+ if (successfulRounds.length > 0) {
82
+ const totalLatency = successfulRounds.reduce((sum, r) => sum + r.latency, 0);
83
+ results.average = totalLatency / successfulRounds.length;
84
+ }
85
+
86
+ if (results.min === Infinity) {
87
+ results.min = 0;
88
+ }
89
+
90
+ return results;
91
+ }
92
+
93
+ /**
94
+ * 运行多个供应商的性能测试
95
+ * @param {Array} providers - 供应商列表
96
+ * @param {Object} options - 选项
97
+ * @returns {Promise<Array>} 测试结果
98
+ */
99
+ async runBenchmark(providers, options = {}) {
100
+ const { rounds = 3, parallel = false } = options;
101
+ const results = [];
102
+
103
+ if (parallel) {
104
+ // 并行测试
105
+ const testPromises = providers.map(provider => this.runTest(provider, rounds));
106
+ results.push(...await Promise.all(testPromises));
107
+ } else {
108
+ // 串行测试
109
+ for (const provider of providers) {
110
+ const result = await this.runTest(provider, rounds);
111
+ results.push(result);
112
+ }
113
+ }
114
+
115
+ // 按平均延迟排序
116
+ results.sort((a, b) => {
117
+ if (a.successRate === 0) return 1;
118
+ if (b.successRate === 0) return -1;
119
+ return a.average - b.average;
120
+ });
121
+
122
+ return results;
123
+ }
124
+
125
+ /**
126
+ * 生成性能测试报告
127
+ * @param {Array} results - 测试结果
128
+ * @returns {string} 报告内容
129
+ */
130
+ generateReport(results) {
131
+ const lines = [];
132
+
133
+ lines.push('# 供应商性能测试报告');
134
+ lines.push('');
135
+ lines.push(`测试时间: ${new Date().toLocaleString('zh-CN')}`);
136
+ lines.push(`测试供应商数: ${results.length}`);
137
+ lines.push(`测试轮数: ${results[0]?.rounds.length || 0}`);
138
+ lines.push('');
139
+ lines.push('---');
140
+ lines.push('');
141
+
142
+ // 排行榜
143
+ lines.push('## 性能排行榜');
144
+ lines.push('');
145
+ lines.push('| 排名 | 供应商 | 平均延迟 | 最小延迟 | 最大延迟 | 成功率 |');
146
+ lines.push('|------|--------|----------|----------|----------|--------|');
147
+
148
+ results.forEach((result, index) => {
149
+ const rank = index + 1;
150
+ const rankMedal = rank === 1 ? '🥇' : rank === 2 ? '🥈' : rank === 3 ? '🥉' : rank;
151
+ const avgLatency = result.average > 0 ? `${Math.round(result.average)}ms` : 'N/A';
152
+ const minLatency = result.min > 0 ? `${Math.round(result.min)}ms` : 'N/A';
153
+ const maxLatency = result.max > 0 ? `${Math.round(result.max)}ms` : 'N/A';
154
+ const successRate = `${Math.round(result.successRate)}%`;
155
+
156
+ lines.push(`| ${rankMedal} | ${result.displayName} (${result.provider}) | ${avgLatency} | ${minLatency} | ${maxLatency} | ${successRate} |`);
157
+ });
158
+
159
+ lines.push('');
160
+ lines.push('---');
161
+ lines.push('');
162
+
163
+ // 详细测试结果
164
+ lines.push('## 详细测试结果');
165
+ lines.push('');
166
+
167
+ results.forEach(result => {
168
+ lines.push(`### ${result.displayName} (${result.provider})`);
169
+ lines.push('');
170
+ lines.push(`- **IDE类型**: ${result.ideName === 'codex' ? 'Codex CLI' : 'Claude Code'}`);
171
+ lines.push(`- **平均延迟**: ${result.average > 0 ? Math.round(result.average) + 'ms' : 'N/A'}`);
172
+ lines.push(`- **延迟范围**: ${result.min > 0 ? Math.round(result.min) : 'N/A'}ms ~ ${result.max > 0 ? Math.round(result.max) : 'N/A'}ms`);
173
+ lines.push(`- **成功率**: ${Math.round(result.successRate)}%`);
174
+ lines.push('');
175
+
176
+ // 各轮测试结果
177
+ lines.push('**各轮测试结果:**');
178
+ lines.push('');
179
+ lines.push('| 轮次 | 状态 | 延迟 |');
180
+ lines.push('|------|------|------|');
181
+
182
+ result.rounds.forEach(round => {
183
+ const status = round.success ? '✓ 成功' : '✗ 失败';
184
+ const latency = round.latency !== null ? `${Math.round(round.latency)}ms` : 'N/A';
185
+ lines.push(`| ${round.round} | ${status} | ${latency} |`);
186
+ });
187
+
188
+ if (result.errors.length > 0) {
189
+ lines.push('');
190
+ lines.push('**错误信息:**');
191
+ result.errors.forEach(error => {
192
+ lines.push(`- ${error}`);
193
+ });
194
+ }
195
+
196
+ lines.push('');
197
+ });
198
+
199
+ lines.push('---');
200
+ lines.push('');
201
+ lines.push('_报告由 AKM (API Key Manager) 生成_');
202
+
203
+ return lines.join('\n');
204
+ }
205
+
206
+ /**
207
+ * 显示性能测试结果
208
+ * @param {Array} results - 测试结果
209
+ */
210
+ displayResults(results) {
211
+ console.log(chalk.blue('\n📊 性能测试排行榜'));
212
+ console.log(chalk.gray('═'.repeat(80)));
213
+ console.log();
214
+
215
+ const currentProvider = configManager.getCurrentProvider();
216
+
217
+ results.forEach((result, index) => {
218
+ const rank = index + 1;
219
+ const rankMedal = rank === 1 ? '🥇' : rank === 2 ? '🥈' : rank === 3 ? '🥉' : `${rank}.`;
220
+ const isCurrent = result.provider === currentProvider?.name;
221
+ const statusIcon = isCurrent ? '✅' : '🔹';
222
+ const ideTag = result.ideName === 'codex' ? chalk.cyan('[Codex]') : chalk.magenta('[Claude]');
223
+ const nameColor = isCurrent ? chalk.green : chalk.white;
224
+
225
+ console.log(`${rankMedal} ${statusIcon} ${ideTag} ${nameColor(result.provider)} (${result.displayName})`);
226
+
227
+ if (result.successRate > 0) {
228
+ const avgColor = result.average < 1000 ? chalk.green : result.average < 3000 ? chalk.yellow : chalk.red;
229
+ console.log(` 平均延迟: ${avgColor(Math.round(result.average) + 'ms')} | 范围: ${Math.round(result.min)}ms ~ ${Math.round(result.max)}ms | 成功率: ${chalk.cyan(Math.round(result.successRate) + '%')}`);
230
+ } else {
231
+ console.log(` ${chalk.red('所有测试失败')}`);
232
+ if (result.errors.length > 0) {
233
+ console.log(` 错误: ${chalk.red(result.errors[0])}`);
234
+ }
235
+ }
236
+
237
+ console.log();
238
+ });
239
+
240
+ // 统计总结
241
+ console.log(chalk.gray('═'.repeat(80)));
242
+ console.log(chalk.blue('统计总结:'));
243
+
244
+ const successful = results.filter(r => r.successRate > 0);
245
+ const fastest = results[0];
246
+ const slowest = results[results.length - 1];
247
+
248
+ console.log(` 可用供应商: ${chalk.cyan(successful.length)}/${results.length}`);
249
+ if (successful.length > 0) {
250
+ console.log(` 最快: ${chalk.green(fastest.displayName)} (${Math.round(fastest.average)}ms)`);
251
+ if (successful.length > 1 && slowest.successRate > 0) {
252
+ console.log(` 最慢: ${chalk.yellow(slowest.displayName)} (${Math.round(slowest.average)}ms)`);
253
+ }
254
+ }
255
+ }
256
+ }
257
+
258
+ /**
259
+ * 性能测试命令
260
+ * @param {Object} options - 选项
261
+ */
262
+ async function benchmarkCommand(options = {}) {
263
+ try {
264
+ await configManager.ensureLoaded();
265
+ let providers = configManager.listProviders();
266
+
267
+ // 应用过滤器
268
+ if (options.filter === 'codex') {
269
+ providers = providers.filter(p => p.ideName === 'codex');
270
+ } else if (options.filter === 'claude') {
271
+ providers = providers.filter(p => p.ideName !== 'codex');
272
+ }
273
+
274
+ if (providers.length === 0) {
275
+ Logger.warning('暂无可测试的供应商');
276
+ return;
277
+ }
278
+
279
+ const titleSuffix = options.filter === 'codex' ? ' (Codex CLI)' : (options.filter === 'claude' ? ' (Claude Code)' : '');
280
+ console.log(chalk.blue(`\n🏁 性能测试${titleSuffix}`));
281
+ console.log(chalk.gray('═'.repeat(80)));
282
+ console.log();
283
+
284
+ const rounds = options.rounds || 3;
285
+ const parallel = options.parallel || false;
286
+
287
+ console.log(chalk.yellow(`将测试 ${providers.length} 个供应商, 每个供应商 ${rounds} 轮`));
288
+ console.log(chalk.yellow(`测试模式: ${parallel ? '并行' : '串行'}`));
289
+ console.log();
290
+
291
+ const runner = new BenchmarkRunner();
292
+
293
+ let completedCount = 0;
294
+ const total = providers.length * rounds;
295
+
296
+ // 创建进度显示(仅在串行模式)
297
+ let progressInterval;
298
+ if (!parallel) {
299
+ progressInterval = setInterval(() => {
300
+ const percent = Math.round((completedCount / total) * 100);
301
+ process.stdout.write(`\r测试中... ${completedCount}/${total} (${percent}%)`);
302
+ }, 100);
303
+ }
304
+
305
+ try {
306
+ const results = await runner.runBenchmark(providers, {
307
+ rounds,
308
+ parallel
309
+ });
310
+
311
+ if (progressInterval) {
312
+ clearInterval(progressInterval);
313
+ process.stdout.write('\r' + ' '.repeat(50) + '\r');
314
+ }
315
+
316
+ // 显示结果
317
+ runner.displayResults(results);
318
+
319
+ // 保存报告
320
+ if (options.report) {
321
+ const reportContent = runner.generateReport(results);
322
+ const reportPath = options.report === true
323
+ ? path.join(os.homedir(), `.akm-benchmark-${Date.now()}.md`)
324
+ : options.report;
325
+
326
+ await fs.writeFile(reportPath, reportContent, 'utf8');
327
+ console.log();
328
+ console.log(chalk.green(`✓ 报告已保存到: ${reportPath}`));
329
+ }
330
+
331
+ } catch (error) {
332
+ if (progressInterval) {
333
+ clearInterval(progressInterval);
334
+ }
335
+ Logger.error(`性能测试失败: ${error.message}`);
336
+ }
337
+
338
+ } catch (error) {
339
+ Logger.error(`性能测试执行失败: ${error.message}`);
340
+ throw error;
341
+ }
342
+ }
343
+
344
+ module.exports = { benchmarkCommand, BenchmarkRunner };