@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.
- package/README.md +396 -2
- package/bin/akm.js +121 -4
- package/package.json +12 -5
- package/src/CommandRegistry.js +25 -0
- package/src/commands/BaseCommand.js +67 -0
- package/src/commands/add.js +125 -152
- 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 +224 -163
- 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 +33 -22
- 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
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health Checker
|
|
3
|
+
* 配置健康检查和告警
|
|
4
|
+
* @module utils/health-checker
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const { ProviderStatusChecker } = require('./provider-status-checker');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 健康检查器
|
|
12
|
+
*/
|
|
13
|
+
class HealthChecker {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.statusChecker = new ProviderStatusChecker();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 检查 Token 有效期
|
|
20
|
+
* @param {Object} provider - 供应商配置
|
|
21
|
+
* @returns {Object} 检查结果
|
|
22
|
+
*/
|
|
23
|
+
checkTokenExpiry(provider) {
|
|
24
|
+
const result = {
|
|
25
|
+
status: 'ok',
|
|
26
|
+
level: 'info',
|
|
27
|
+
message: '',
|
|
28
|
+
daysUntilExpiry: null
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// 如果配置中有过期时间
|
|
32
|
+
if (provider.tokenExpiry) {
|
|
33
|
+
const expiryDate = new Date(provider.tokenExpiry);
|
|
34
|
+
const now = new Date();
|
|
35
|
+
const daysUntilExpiry = Math.floor((expiryDate - now) / (1000 * 60 * 60 * 24));
|
|
36
|
+
|
|
37
|
+
result.daysUntilExpiry = daysUntilExpiry;
|
|
38
|
+
|
|
39
|
+
if (daysUntilExpiry < 0) {
|
|
40
|
+
result.status = 'expired';
|
|
41
|
+
result.level = 'error';
|
|
42
|
+
result.message = `Token 已过期 ${Math.abs(daysUntilExpiry)} 天`;
|
|
43
|
+
} else if (daysUntilExpiry <= 3) {
|
|
44
|
+
result.status = 'critical';
|
|
45
|
+
result.level = 'error';
|
|
46
|
+
result.message = `Token 将在 ${daysUntilExpiry} 天后过期,请尽快更新`;
|
|
47
|
+
} else if (daysUntilExpiry <= 7) {
|
|
48
|
+
result.status = 'warning';
|
|
49
|
+
result.level = 'warning';
|
|
50
|
+
result.message = `Token 将在 ${daysUntilExpiry} 天后过期`;
|
|
51
|
+
} else if (daysUntilExpiry <= 30) {
|
|
52
|
+
result.status = 'notice';
|
|
53
|
+
result.level = 'info';
|
|
54
|
+
result.message = `Token 将在 ${daysUntilExpiry} 天后过期`;
|
|
55
|
+
} else {
|
|
56
|
+
result.message = `Token 有效期还有 ${daysUntilExpiry} 天`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 检查使用配额
|
|
65
|
+
* @param {Object} provider - 供应商配置
|
|
66
|
+
* @returns {Object} 检查结果
|
|
67
|
+
*/
|
|
68
|
+
checkQuota(provider) {
|
|
69
|
+
const result = {
|
|
70
|
+
status: 'ok',
|
|
71
|
+
level: 'info',
|
|
72
|
+
message: '',
|
|
73
|
+
usagePercent: null
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// 如果配置中有配额信息
|
|
77
|
+
if (provider.quota && provider.quota.limit) {
|
|
78
|
+
const used = provider.quota.used || 0;
|
|
79
|
+
const limit = provider.quota.limit;
|
|
80
|
+
const usagePercent = Math.round((used / limit) * 100);
|
|
81
|
+
|
|
82
|
+
result.usagePercent = usagePercent;
|
|
83
|
+
|
|
84
|
+
if (usagePercent >= 100) {
|
|
85
|
+
result.status = 'exceeded';
|
|
86
|
+
result.level = 'error';
|
|
87
|
+
result.message = `配额已用完 (${used}/${limit})`;
|
|
88
|
+
} else if (usagePercent >= 90) {
|
|
89
|
+
result.status = 'critical';
|
|
90
|
+
result.level = 'error';
|
|
91
|
+
result.message = `配额即将用完 ${usagePercent}% (${used}/${limit})`;
|
|
92
|
+
} else if (usagePercent >= 75) {
|
|
93
|
+
result.status = 'warning';
|
|
94
|
+
result.level = 'warning';
|
|
95
|
+
result.message = `配额使用较高 ${usagePercent}% (${used}/${limit})`;
|
|
96
|
+
} else if (usagePercent >= 50) {
|
|
97
|
+
result.status = 'notice';
|
|
98
|
+
result.level = 'info';
|
|
99
|
+
result.message = `配额使用 ${usagePercent}% (${used}/${limit})`;
|
|
100
|
+
} else {
|
|
101
|
+
result.message = `配额充足 ${usagePercent}% (${used}/${limit})`;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 检查最后使用时间
|
|
110
|
+
* @param {Object} provider - 供应商配置
|
|
111
|
+
* @returns {Object} 检查结果
|
|
112
|
+
*/
|
|
113
|
+
checkLastUsed(provider) {
|
|
114
|
+
const result = {
|
|
115
|
+
status: 'ok',
|
|
116
|
+
level: 'info',
|
|
117
|
+
message: '',
|
|
118
|
+
daysSinceLastUse: null
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
if (provider.lastUsed) {
|
|
122
|
+
const lastUsedDate = new Date(provider.lastUsed);
|
|
123
|
+
const now = new Date();
|
|
124
|
+
const daysSinceLastUse = Math.floor((now - lastUsedDate) / (1000 * 60 * 60 * 24));
|
|
125
|
+
|
|
126
|
+
result.daysSinceLastUse = daysSinceLastUse;
|
|
127
|
+
|
|
128
|
+
if (daysSinceLastUse > 90) {
|
|
129
|
+
result.status = 'stale';
|
|
130
|
+
result.level = 'warning';
|
|
131
|
+
result.message = `已 ${daysSinceLastUse} 天未使用,可能需要验证或清理`;
|
|
132
|
+
} else if (daysSinceLastUse > 30) {
|
|
133
|
+
result.status = 'inactive';
|
|
134
|
+
result.level = 'info';
|
|
135
|
+
result.message = `已 ${daysSinceLastUse} 天未使用`;
|
|
136
|
+
} else {
|
|
137
|
+
result.message = `最近使用于 ${daysSinceLastUse} 天前`;
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
result.status = 'never';
|
|
141
|
+
result.level = 'info';
|
|
142
|
+
result.message = '从未使用过';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 检查 API 连接性
|
|
150
|
+
* @param {Object} provider - 供应商配置
|
|
151
|
+
* @returns {Promise<Object>} 检查结果
|
|
152
|
+
*/
|
|
153
|
+
async checkConnectivity(provider) {
|
|
154
|
+
const result = {
|
|
155
|
+
status: 'ok',
|
|
156
|
+
level: 'info',
|
|
157
|
+
message: '',
|
|
158
|
+
latency: null
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const status = await this.statusChecker.check(provider, { skipCache: true });
|
|
163
|
+
|
|
164
|
+
result.latency = status.latency;
|
|
165
|
+
|
|
166
|
+
if (status.state === 'offline') {
|
|
167
|
+
result.status = 'offline';
|
|
168
|
+
result.level = 'error';
|
|
169
|
+
result.message = 'API 无法连接';
|
|
170
|
+
} else if (status.state === 'degraded') {
|
|
171
|
+
result.status = 'degraded';
|
|
172
|
+
result.level = 'warning';
|
|
173
|
+
result.message = 'API 响应缓慢';
|
|
174
|
+
} else if (status.state === 'online') {
|
|
175
|
+
if (status.latency > 5000) {
|
|
176
|
+
result.status = 'slow';
|
|
177
|
+
result.level = 'warning';
|
|
178
|
+
result.message = `API 响应较慢 (${Math.round(status.latency)}ms)`;
|
|
179
|
+
} else {
|
|
180
|
+
result.status = 'online';
|
|
181
|
+
result.message = `API 正常 (${Math.round(status.latency)}ms)`;
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
result.status = 'unknown';
|
|
185
|
+
result.level = 'warning';
|
|
186
|
+
result.message = 'API 状态未知';
|
|
187
|
+
}
|
|
188
|
+
} catch (error) {
|
|
189
|
+
result.status = 'error';
|
|
190
|
+
result.level = 'error';
|
|
191
|
+
result.message = `检查失败: ${error.message}`;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* 执行完整健康检查
|
|
199
|
+
* @param {Object} provider - 供应商配置
|
|
200
|
+
* @param {Object} options - 选项
|
|
201
|
+
* @returns {Promise<Object>} 检查结果
|
|
202
|
+
*/
|
|
203
|
+
async performHealthCheck(provider, options = {}) {
|
|
204
|
+
const results = {
|
|
205
|
+
provider: provider.name,
|
|
206
|
+
displayName: provider.displayName,
|
|
207
|
+
overallStatus: 'ok',
|
|
208
|
+
checks: {}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// Token 过期检查
|
|
212
|
+
results.checks.tokenExpiry = this.checkTokenExpiry(provider);
|
|
213
|
+
|
|
214
|
+
// 配额检查
|
|
215
|
+
results.checks.quota = this.checkQuota(provider);
|
|
216
|
+
|
|
217
|
+
// 最后使用时间检查
|
|
218
|
+
results.checks.lastUsed = this.checkLastUsed(provider);
|
|
219
|
+
|
|
220
|
+
// API 连接性检查(可选,因为比较慢)
|
|
221
|
+
if (options.checkConnectivity !== false) {
|
|
222
|
+
results.checks.connectivity = await this.checkConnectivity(provider);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// 确定整体状态
|
|
226
|
+
const levels = Object.values(results.checks).map(c => c.level);
|
|
227
|
+
if (levels.includes('error')) {
|
|
228
|
+
results.overallStatus = 'error';
|
|
229
|
+
} else if (levels.includes('warning')) {
|
|
230
|
+
results.overallStatus = 'warning';
|
|
231
|
+
} else if (levels.includes('info')) {
|
|
232
|
+
results.overallStatus = 'info';
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return results;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* 格式化健康检查报告
|
|
240
|
+
* @param {Object} results - 检查结果
|
|
241
|
+
* @returns {string} 格式化的报告
|
|
242
|
+
*/
|
|
243
|
+
formatHealthReport(results) {
|
|
244
|
+
const lines = [];
|
|
245
|
+
|
|
246
|
+
// 标题
|
|
247
|
+
const statusIcon = this._getStatusIcon(results.overallStatus);
|
|
248
|
+
const statusColor = this._getStatusColor(results.overallStatus);
|
|
249
|
+
lines.push(statusColor(`${statusIcon} ${results.displayName} (${results.provider})`));
|
|
250
|
+
lines.push('');
|
|
251
|
+
|
|
252
|
+
// 各项检查结果
|
|
253
|
+
Object.entries(results.checks).forEach(([checkName, result]) => {
|
|
254
|
+
if (result.message) {
|
|
255
|
+
const icon = this._getLevelIcon(result.level);
|
|
256
|
+
const color = this._getLevelColor(result.level);
|
|
257
|
+
const label = this._getCheckLabel(checkName);
|
|
258
|
+
lines.push(` ${icon} ${label}: ${color(result.message)}`);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
return lines.join('\n');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* 获取状态图标
|
|
267
|
+
* @private
|
|
268
|
+
*/
|
|
269
|
+
_getStatusIcon(status) {
|
|
270
|
+
switch (status) {
|
|
271
|
+
case 'ok':
|
|
272
|
+
case 'info':
|
|
273
|
+
return '✓';
|
|
274
|
+
case 'warning':
|
|
275
|
+
return '⚠';
|
|
276
|
+
case 'error':
|
|
277
|
+
return '✗';
|
|
278
|
+
default:
|
|
279
|
+
return '?';
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* 获取状态颜色
|
|
285
|
+
* @private
|
|
286
|
+
*/
|
|
287
|
+
_getStatusColor(status) {
|
|
288
|
+
switch (status) {
|
|
289
|
+
case 'ok':
|
|
290
|
+
case 'info':
|
|
291
|
+
return chalk.green;
|
|
292
|
+
case 'warning':
|
|
293
|
+
return chalk.yellow;
|
|
294
|
+
case 'error':
|
|
295
|
+
return chalk.red;
|
|
296
|
+
default:
|
|
297
|
+
return chalk.gray;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* 获取级别图标
|
|
303
|
+
* @private
|
|
304
|
+
*/
|
|
305
|
+
_getLevelIcon(level) {
|
|
306
|
+
switch (level) {
|
|
307
|
+
case 'info':
|
|
308
|
+
return 'ℹ';
|
|
309
|
+
case 'warning':
|
|
310
|
+
return '⚠';
|
|
311
|
+
case 'error':
|
|
312
|
+
return '✗';
|
|
313
|
+
default:
|
|
314
|
+
return '·';
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* 获取级别颜色
|
|
320
|
+
* @private
|
|
321
|
+
*/
|
|
322
|
+
_getLevelColor(level) {
|
|
323
|
+
switch (level) {
|
|
324
|
+
case 'info':
|
|
325
|
+
return chalk.blue;
|
|
326
|
+
case 'warning':
|
|
327
|
+
return chalk.yellow;
|
|
328
|
+
case 'error':
|
|
329
|
+
return chalk.red;
|
|
330
|
+
default:
|
|
331
|
+
return chalk.gray;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* 获取检查标签
|
|
337
|
+
* @private
|
|
338
|
+
*/
|
|
339
|
+
_getCheckLabel(checkName) {
|
|
340
|
+
const labels = {
|
|
341
|
+
tokenExpiry: 'Token 有效期',
|
|
342
|
+
quota: 'API 配额',
|
|
343
|
+
lastUsed: '最后使用',
|
|
344
|
+
connectivity: 'API 连接'
|
|
345
|
+
};
|
|
346
|
+
return labels[checkName] || checkName;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
module.exports = { HealthChecker };
|
|
@@ -1,8 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inquirer Setup Utility
|
|
3
|
+
* 自定义 Inquirer.js 提示样式和本地化
|
|
4
|
+
* @module utils/inquirer-setup
|
|
5
|
+
*/
|
|
6
|
+
|
|
1
7
|
const inquirer = require('inquirer');
|
|
2
8
|
const CheckboxPrompt = require('inquirer/lib/prompts/checkbox');
|
|
3
9
|
const chalk = require('chalk');
|
|
4
10
|
const figures = require('figures');
|
|
5
11
|
|
|
12
|
+
/**
|
|
13
|
+
* 本地化复选框提示类
|
|
14
|
+
* 扩展 Inquirer 的 CheckboxPrompt,添加中文提示和自定义样式
|
|
15
|
+
* @extends CheckboxPrompt
|
|
16
|
+
*/
|
|
6
17
|
class LocalizedCheckboxPrompt extends CheckboxPrompt {
|
|
7
18
|
render(error) {
|
|
8
19
|
let message = this.getQuestion();
|
|
@@ -1,18 +1,37 @@
|
|
|
1
1
|
const { Anthropic, APIConnectionTimeoutError, APIConnectionError, APIError } = require('@anthropic-ai/sdk');
|
|
2
|
+
const { API_CONFIG, ENV_VARS } = require('../constants');
|
|
2
3
|
|
|
3
4
|
let authTokenEnvLock = Promise.resolve();
|
|
4
5
|
|
|
5
6
|
// 状态缓存
|
|
6
7
|
const statusCache = new Map();
|
|
7
|
-
const DEFAULT_CACHE_TTL = 30000; // 30秒缓存
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {Object} StatusResult
|
|
11
|
+
* @property {string} status - 状态 ('online', 'offline', 'degraded', 'pending', 'unknown')
|
|
12
|
+
* @property {string} message - 状态消息
|
|
13
|
+
* @property {number|null} responseTime - 响应时间(毫秒)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 供应商状态检查器
|
|
18
|
+
* 检查 API 供应商的可用性和响应时间
|
|
19
|
+
*/
|
|
9
20
|
class ProviderStatusChecker {
|
|
21
|
+
/**
|
|
22
|
+
* 创建状态检查器实例
|
|
23
|
+
* @param {Object} [options={}] - 配置选项
|
|
24
|
+
* @param {number} [options.timeout] - 请求超时时间
|
|
25
|
+
* @param {string} [options.testMessage] - 测试消息
|
|
26
|
+
* @param {number} [options.maxTokens] - 最大 Token 数
|
|
27
|
+
* @param {number} [options.cacheTTL] - 缓存过期时间
|
|
28
|
+
*/
|
|
10
29
|
constructor(options = {}) {
|
|
11
|
-
this.timeout = options.timeout ??
|
|
12
|
-
this.testMessage = options.testMessage ??
|
|
13
|
-
this.maxTokens = options.maxTokens ??
|
|
14
|
-
this.defaultModel =
|
|
15
|
-
this.cacheTTL = options.cacheTTL ??
|
|
30
|
+
this.timeout = options.timeout ?? API_CONFIG.DEFAULT_TIMEOUT;
|
|
31
|
+
this.testMessage = options.testMessage ?? API_CONFIG.TEST_MESSAGE;
|
|
32
|
+
this.maxTokens = options.maxTokens ?? API_CONFIG.MAX_TOKENS;
|
|
33
|
+
this.defaultModel = API_CONFIG.DEFAULT_MODEL;
|
|
34
|
+
this.cacheTTL = options.cacheTTL ?? API_CONFIG.CACHE_TTL;
|
|
16
35
|
}
|
|
17
36
|
|
|
18
37
|
_getCacheKey(provider) {
|
|
@@ -50,15 +69,15 @@ class ProviderStatusChecker {
|
|
|
50
69
|
|
|
51
70
|
await previous;
|
|
52
71
|
|
|
53
|
-
const original = process.env.ANTHROPIC_AUTH_TOKEN;
|
|
72
|
+
const original = process.env[ENV_VARS.ANTHROPIC_AUTH_TOKEN];
|
|
54
73
|
try {
|
|
55
|
-
process.env.ANTHROPIC_AUTH_TOKEN = authToken;
|
|
74
|
+
process.env[ENV_VARS.ANTHROPIC_AUTH_TOKEN] = authToken;
|
|
56
75
|
return await operation();
|
|
57
76
|
} finally {
|
|
58
77
|
if (original !== undefined) {
|
|
59
|
-
process.env.ANTHROPIC_AUTH_TOKEN = original;
|
|
78
|
+
process.env[ENV_VARS.ANTHROPIC_AUTH_TOKEN] = original;
|
|
60
79
|
} else {
|
|
61
|
-
delete process.env.ANTHROPIC_AUTH_TOKEN;
|
|
80
|
+
delete process.env[ENV_VARS.ANTHROPIC_AUTH_TOKEN];
|
|
62
81
|
}
|
|
63
82
|
release();
|
|
64
83
|
}
|