@pikecode/api-key-manager 1.0.39 → 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 +396 -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 +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,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usage Statistics Command
|
|
3
|
+
* 显示供应商使用统计信息
|
|
4
|
+
* @module commands/stats
|
|
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
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 格式化时长
|
|
14
|
+
* @param {number} ms - 毫秒
|
|
15
|
+
* @returns {string} 格式化后的时长
|
|
16
|
+
*/
|
|
17
|
+
function formatDuration(ms) {
|
|
18
|
+
if (!ms) return '0分钟';
|
|
19
|
+
|
|
20
|
+
const hours = Math.floor(ms / (1000 * 60 * 60));
|
|
21
|
+
const minutes = Math.floor((ms % (1000 * 60 * 60)) / (1000 * 60));
|
|
22
|
+
|
|
23
|
+
if (hours > 0) {
|
|
24
|
+
return `${hours}小时 ${minutes}分钟`;
|
|
25
|
+
}
|
|
26
|
+
return `${minutes}分钟`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 格式化日期
|
|
31
|
+
* @param {string} isoDate - ISO日期字符串
|
|
32
|
+
* @returns {string} 格式化后的日期
|
|
33
|
+
*/
|
|
34
|
+
function formatDate(isoDate) {
|
|
35
|
+
if (!isoDate) return '从未使用';
|
|
36
|
+
|
|
37
|
+
const date = new Date(isoDate);
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
const diff = now - date.getTime();
|
|
40
|
+
|
|
41
|
+
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
|
42
|
+
const hours = Math.floor(diff / (1000 * 60 * 60));
|
|
43
|
+
const minutes = Math.floor(diff / (1000 * 60));
|
|
44
|
+
|
|
45
|
+
if (days > 7) {
|
|
46
|
+
return date.toLocaleDateString('zh-CN');
|
|
47
|
+
} else if (days > 0) {
|
|
48
|
+
return `${days}天前`;
|
|
49
|
+
} else if (hours > 0) {
|
|
50
|
+
return `${hours}小时前`;
|
|
51
|
+
} else if (minutes > 0) {
|
|
52
|
+
return `${minutes}分钟前`;
|
|
53
|
+
} else {
|
|
54
|
+
return '刚刚';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 显示单个供应商的统计信息
|
|
60
|
+
* @param {string} providerName - 供应商名称或别名
|
|
61
|
+
*/
|
|
62
|
+
async function showProviderStats(providerName) {
|
|
63
|
+
try {
|
|
64
|
+
await configManager.ensureLoaded();
|
|
65
|
+
|
|
66
|
+
// 支持别名查找
|
|
67
|
+
let provider = configManager.getProvider(providerName);
|
|
68
|
+
if (!provider) {
|
|
69
|
+
provider = configManager.getProviderByNameOrAlias(providerName);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!provider) {
|
|
73
|
+
Logger.error(`供应商 '${providerName}' 不存在`);
|
|
74
|
+
Logger.info('使用 "akm list" 查看所有已配置的供应商');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const stats = configManager.getUsageStats(provider.name);
|
|
79
|
+
|
|
80
|
+
console.log(chalk.blue(`\n📊 ${stats.displayName} (${stats.name}) 使用统计`));
|
|
81
|
+
console.log(chalk.gray('═'.repeat(60)));
|
|
82
|
+
console.log();
|
|
83
|
+
|
|
84
|
+
// 基础统计
|
|
85
|
+
console.log(chalk.white('基础信息:'));
|
|
86
|
+
console.log(` 使用次数: ${chalk.cyan(stats.usageCount || 0)} 次`);
|
|
87
|
+
console.log(` 最后使用: ${chalk.yellow(formatDate(stats.lastUsed))}`);
|
|
88
|
+
console.log();
|
|
89
|
+
|
|
90
|
+
// 详细统计
|
|
91
|
+
if (stats.stats) {
|
|
92
|
+
console.log(chalk.white('会话统计:'));
|
|
93
|
+
console.log(` 总会话数: ${chalk.cyan(stats.stats.totalSessions || 0)} 次`);
|
|
94
|
+
console.log(` 累计时长: ${chalk.cyan(formatDuration(stats.stats.totalDurationMs))}`);
|
|
95
|
+
console.log(` 平均时长: ${chalk.cyan(formatDuration(stats.stats.averageDurationMs))}`);
|
|
96
|
+
console.log(` 首次使用: ${chalk.yellow(formatDate(stats.stats.firstUsed))}`);
|
|
97
|
+
console.log();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 配置信息
|
|
101
|
+
console.log(chalk.gray('配置详情:'));
|
|
102
|
+
console.log(chalk.gray(` IDE类型: ${provider.ideName === 'codex' ? 'Codex CLI' : 'Claude Code'}`));
|
|
103
|
+
if (provider.alias) {
|
|
104
|
+
console.log(chalk.gray(` 别名: ${provider.alias}`));
|
|
105
|
+
}
|
|
106
|
+
if (provider.ideName !== 'codex') {
|
|
107
|
+
console.log(chalk.gray(` 认证模式: ${provider.authMode}`));
|
|
108
|
+
}
|
|
109
|
+
if (provider.baseUrl) {
|
|
110
|
+
console.log(chalk.gray(` 基础URL: ${provider.baseUrl}`));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
} catch (error) {
|
|
114
|
+
Logger.error(`获取统计信息失败: ${error.message}`);
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 显示所有供应商的统计概览
|
|
121
|
+
* @param {Object} options - 选项
|
|
122
|
+
* @param {string|null} options.filter - 过滤器
|
|
123
|
+
* @param {string} options.sort - 排序方式
|
|
124
|
+
*/
|
|
125
|
+
async function showAllStats(options = {}) {
|
|
126
|
+
try {
|
|
127
|
+
await configManager.ensureLoaded();
|
|
128
|
+
let allStats = configManager.getUsageStats();
|
|
129
|
+
|
|
130
|
+
// 应用过滤器
|
|
131
|
+
if (options.filter === 'codex') {
|
|
132
|
+
allStats = allStats.filter(s => {
|
|
133
|
+
const provider = configManager.getProvider(s.name);
|
|
134
|
+
return provider && provider.ideName === 'codex';
|
|
135
|
+
});
|
|
136
|
+
} else if (options.filter === 'claude') {
|
|
137
|
+
allStats = allStats.filter(s => {
|
|
138
|
+
const provider = configManager.getProvider(s.name);
|
|
139
|
+
return provider && provider.ideName !== 'codex';
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (allStats.length === 0) {
|
|
144
|
+
if (options.filter) {
|
|
145
|
+
const filterName = options.filter === 'codex' ? 'Codex CLI' : 'Claude Code';
|
|
146
|
+
Logger.warning(`暂无 ${filterName} 供应商配置`);
|
|
147
|
+
} else {
|
|
148
|
+
Logger.warning('暂无配置的供应商');
|
|
149
|
+
}
|
|
150
|
+
Logger.info('请使用 "akm add" 添加供应商配置');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 排序
|
|
155
|
+
if (options.sort === 'time') {
|
|
156
|
+
allStats.sort((a, b) => {
|
|
157
|
+
const timeA = a.lastUsed ? new Date(a.lastUsed).getTime() : 0;
|
|
158
|
+
const timeB = b.lastUsed ? new Date(b.lastUsed).getTime() : 0;
|
|
159
|
+
return timeB - timeA;
|
|
160
|
+
});
|
|
161
|
+
} else if (options.sort === 'name') {
|
|
162
|
+
allStats.sort((a, b) => a.name.localeCompare(b.name));
|
|
163
|
+
}
|
|
164
|
+
// 默认已经按使用次数排序
|
|
165
|
+
|
|
166
|
+
const titleSuffix = options.filter === 'codex' ? ' (Codex CLI)' : (options.filter === 'claude' ? ' (Claude Code)' : '');
|
|
167
|
+
console.log(chalk.blue(`\n📊 供应商使用统计${titleSuffix}`));
|
|
168
|
+
console.log(chalk.gray('═'.repeat(60)));
|
|
169
|
+
console.log();
|
|
170
|
+
|
|
171
|
+
const currentProvider = configManager.getCurrentProvider();
|
|
172
|
+
|
|
173
|
+
// 显示表格
|
|
174
|
+
allStats.forEach((stats, index) => {
|
|
175
|
+
const provider = configManager.getProvider(stats.name);
|
|
176
|
+
const isCurrent = provider.name === currentProvider?.name;
|
|
177
|
+
const statusIcon = isCurrent ? '✅' : '🔹';
|
|
178
|
+
const ideTag = provider.ideName === 'codex'
|
|
179
|
+
? chalk.cyan('[Codex]')
|
|
180
|
+
: chalk.magenta('[Claude]');
|
|
181
|
+
const nameColor = isCurrent ? chalk.green : chalk.white;
|
|
182
|
+
const aliasText = provider.alias ? chalk.yellow(` [${provider.alias}]`) : '';
|
|
183
|
+
|
|
184
|
+
console.log(`${statusIcon} ${ideTag} ${nameColor(stats.name)} (${stats.displayName})${aliasText}`);
|
|
185
|
+
console.log(` 使用次数: ${chalk.cyan(stats.usageCount || 0)} 次 | 最后使用: ${chalk.yellow(formatDate(stats.lastUsed))}`);
|
|
186
|
+
|
|
187
|
+
if (stats.stats) {
|
|
188
|
+
console.log(` 会话数: ${chalk.cyan(stats.stats.totalSessions || 0)} | 累计: ${chalk.cyan(formatDuration(stats.stats.totalDurationMs))} | 平均: ${chalk.cyan(formatDuration(stats.stats.averageDurationMs))}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
console.log();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// 统计汇总
|
|
195
|
+
const totalUsage = allStats.reduce((sum, s) => sum + (s.usageCount || 0), 0);
|
|
196
|
+
const totalSessions = allStats.reduce((sum, s) => sum + (s.stats?.totalSessions || 0), 0);
|
|
197
|
+
const totalDuration = allStats.reduce((sum, s) => sum + (s.stats?.totalDurationMs || 0), 0);
|
|
198
|
+
|
|
199
|
+
console.log(chalk.gray('═'.repeat(60)));
|
|
200
|
+
console.log(chalk.blue('汇总统计:'));
|
|
201
|
+
console.log(` 总供应商数: ${chalk.cyan(allStats.length)}`);
|
|
202
|
+
console.log(` 总使用次数: ${chalk.cyan(totalUsage)} 次`);
|
|
203
|
+
console.log(` 总会话数: ${chalk.cyan(totalSessions)} 次`);
|
|
204
|
+
console.log(` 累计使用时长: ${chalk.cyan(formatDuration(totalDuration))}`);
|
|
205
|
+
|
|
206
|
+
} catch (error) {
|
|
207
|
+
Logger.error(`获取统计信息失败: ${error.message}`);
|
|
208
|
+
throw error;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* 显示推荐的供应商
|
|
214
|
+
* @param {Object} options - 选项
|
|
215
|
+
*/
|
|
216
|
+
async function showRecommendations(options = {}) {
|
|
217
|
+
try {
|
|
218
|
+
await configManager.ensureLoaded();
|
|
219
|
+
|
|
220
|
+
const limit = options.limit || 5;
|
|
221
|
+
const filter = options.filter || null;
|
|
222
|
+
|
|
223
|
+
const recommendations = configManager.getRecommendedProviders({ limit, filter });
|
|
224
|
+
|
|
225
|
+
if (recommendations.length === 0) {
|
|
226
|
+
Logger.warning('暂无可推荐的供应商');
|
|
227
|
+
Logger.info('使用 "akm add" 添加供应商配置');
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const titleSuffix = filter === 'codex' ? ' (Codex CLI)' : (filter === 'claude' ? ' (Claude Code)' : '');
|
|
232
|
+
console.log(chalk.blue(`\n⭐ 推荐供应商${titleSuffix}`));
|
|
233
|
+
console.log(chalk.gray('基于使用频率、最近使用时间和会话时长的智能推荐'));
|
|
234
|
+
console.log(chalk.gray('═'.repeat(60)));
|
|
235
|
+
console.log();
|
|
236
|
+
|
|
237
|
+
const currentProvider = configManager.getCurrentProvider();
|
|
238
|
+
|
|
239
|
+
recommendations.forEach((provider, index) => {
|
|
240
|
+
const isCurrent = provider.name === currentProvider?.name;
|
|
241
|
+
const rankIcon = index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : `${index + 1}.`;
|
|
242
|
+
const statusIcon = isCurrent ? '✅' : '🔹';
|
|
243
|
+
const ideTag = provider.ideName === 'codex'
|
|
244
|
+
? chalk.cyan('[Codex]')
|
|
245
|
+
: chalk.magenta('[Claude]');
|
|
246
|
+
const nameColor = isCurrent ? chalk.green : chalk.white;
|
|
247
|
+
const aliasText = provider.alias ? chalk.yellow(` [${provider.alias}]`) : '';
|
|
248
|
+
|
|
249
|
+
console.log(`${rankIcon} ${statusIcon} ${ideTag} ${nameColor(provider.name)} (${provider.displayName})${aliasText}`);
|
|
250
|
+
console.log(` 推荐分数: ${chalk.cyan(provider.recommendScore)} | 使用: ${chalk.cyan(provider.usageCount || 0)}次 | 最后: ${chalk.yellow(formatDate(provider.lastUsed))}`);
|
|
251
|
+
console.log();
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
console.log(chalk.gray('💡 提示: 使用 "akm <供应商名称>" 快速切换到推荐的供应商'));
|
|
255
|
+
|
|
256
|
+
} catch (error) {
|
|
257
|
+
Logger.error(`获取推荐失败: ${error.message}`);
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* 统计命令
|
|
264
|
+
* @param {string|null} providerName - 供应商名称
|
|
265
|
+
* @param {Object} options - 选项
|
|
266
|
+
*/
|
|
267
|
+
async function statsCommand(providerName, options = {}) {
|
|
268
|
+
try {
|
|
269
|
+
if (options.recommend) {
|
|
270
|
+
await showRecommendations(options);
|
|
271
|
+
} else if (providerName) {
|
|
272
|
+
await showProviderStats(providerName);
|
|
273
|
+
} else {
|
|
274
|
+
await showAllStats(options);
|
|
275
|
+
}
|
|
276
|
+
} catch (error) {
|
|
277
|
+
Logger.error(`统计命令执行失败: ${error.message}`);
|
|
278
|
+
throw error;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
module.exports = { statsCommand };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Launch Args Helper
|
|
3
|
+
* 启动参数辅助函数
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { getClaudeLaunchArgs, getCodexLaunchArgs, checkExclusiveArgs } = require('../../utils/launch-args');
|
|
7
|
+
|
|
8
|
+
class LaunchArgsHelper {
|
|
9
|
+
/**
|
|
10
|
+
* 获取可用的启动参数
|
|
11
|
+
* @param {boolean} isCodex - 是否是 Codex
|
|
12
|
+
* @returns {Array} 启动参数列表
|
|
13
|
+
*/
|
|
14
|
+
static getAvailableLaunchArgs(isCodex) {
|
|
15
|
+
return isCodex ? getCodexLaunchArgs() : getClaudeLaunchArgs();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 合并默认参数和自定义参数
|
|
20
|
+
* @param {Array} availableArgs - 可用参数列表
|
|
21
|
+
* @param {Array} defaultLaunchArgs - 默认启动参数
|
|
22
|
+
* @returns {Array} 合并后的参数列表
|
|
23
|
+
*/
|
|
24
|
+
static mergeArgsWithDefaults(availableArgs, defaultLaunchArgs) {
|
|
25
|
+
const knownArgNames = new Set(availableArgs.map(arg => arg.name));
|
|
26
|
+
const customLaunchArgs = defaultLaunchArgs
|
|
27
|
+
.filter(arg => typeof arg === 'string' && !knownArgNames.has(arg));
|
|
28
|
+
|
|
29
|
+
return [
|
|
30
|
+
...availableArgs.map(arg => ({
|
|
31
|
+
...arg,
|
|
32
|
+
checked: defaultLaunchArgs.includes(arg.name) || Boolean(arg.checked)
|
|
33
|
+
})),
|
|
34
|
+
...customLaunchArgs.map(name => ({
|
|
35
|
+
name,
|
|
36
|
+
label: name,
|
|
37
|
+
description: '自定义启动参数',
|
|
38
|
+
checked: true
|
|
39
|
+
}))
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 验证启动参数是否有冲突
|
|
45
|
+
* @param {Array} selectedArgs - 已选择的参数
|
|
46
|
+
* @param {Array} availableArgs - 可用参数列表
|
|
47
|
+
* @returns {string|null} 错误信息,无冲突返回 null
|
|
48
|
+
*/
|
|
49
|
+
static validateArgsConflict(selectedArgs, availableArgs) {
|
|
50
|
+
return checkExclusiveArgs(selectedArgs, availableArgs);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 格式化参数选项用于显示
|
|
55
|
+
* @param {Array} args - 参数列表
|
|
56
|
+
* @param {Object} UIHelper - UI 辅助工具
|
|
57
|
+
* @returns {Array} 格式化后的选项列表
|
|
58
|
+
*/
|
|
59
|
+
static formatArgsForDisplay(args, UIHelper) {
|
|
60
|
+
return args.map(arg => {
|
|
61
|
+
const commandText = UIHelper.colors.muted(`(${arg.name})`);
|
|
62
|
+
const descriptionText = arg.description
|
|
63
|
+
? ` ${UIHelper.colors.muted(arg.description)}`
|
|
64
|
+
: '';
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
name: `${UIHelper.colors.accent(arg.label || arg.name)} ${commandText}${descriptionText}`,
|
|
68
|
+
value: arg.name,
|
|
69
|
+
checked: Boolean(arg.checked)
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 获取 IDE 显示名称
|
|
76
|
+
* @param {boolean} isCodex - 是否是 Codex
|
|
77
|
+
* @returns {string} IDE 显示名称
|
|
78
|
+
*/
|
|
79
|
+
static getIDEDisplayName(isCodex) {
|
|
80
|
+
return isCodex ? 'Codex CLI' : 'Claude Code';
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = { LaunchArgsHelper };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status Helper
|
|
3
|
+
* 供应商状态管理辅助函数
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
|
|
8
|
+
class StatusHelper {
|
|
9
|
+
/**
|
|
10
|
+
* 根据状态获取图标
|
|
11
|
+
* @param {string} state - 状态 (online/degraded/offline/pending)
|
|
12
|
+
* @returns {string} 状态图标
|
|
13
|
+
*/
|
|
14
|
+
static getIconForState(state) {
|
|
15
|
+
const iconMap = {
|
|
16
|
+
online: '🟢',
|
|
17
|
+
degraded: '🟡',
|
|
18
|
+
offline: '🔴',
|
|
19
|
+
pending: '⏳'
|
|
20
|
+
};
|
|
21
|
+
return iconMap[state] || '⚪';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 格式化可用性状态显示
|
|
26
|
+
* @param {Object} availability - 可用性对象 {state, label, latency}
|
|
27
|
+
* @returns {string} 格式化后的状态文本
|
|
28
|
+
*/
|
|
29
|
+
static formatAvailability(availability) {
|
|
30
|
+
if (!availability) {
|
|
31
|
+
return chalk.gray('测试中...');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const colorMap = {
|
|
35
|
+
online: chalk.green,
|
|
36
|
+
degraded: chalk.yellow,
|
|
37
|
+
offline: chalk.red,
|
|
38
|
+
pending: chalk.gray
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const defaultLabelMap = {
|
|
42
|
+
online: '可用',
|
|
43
|
+
degraded: '有限可用',
|
|
44
|
+
offline: '不可用',
|
|
45
|
+
pending: '测试中...'
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const color = colorMap[availability.state] || chalk.gray;
|
|
49
|
+
const label = availability.label || defaultLabelMap[availability.state] || '未知';
|
|
50
|
+
|
|
51
|
+
return color(label);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 构建初始状态映射
|
|
56
|
+
* @param {Array} providers - 供应商列表
|
|
57
|
+
* @param {Object} cachedStatusMap - 缓存的状态映射
|
|
58
|
+
* @returns {Object} 状态映射
|
|
59
|
+
*/
|
|
60
|
+
static buildInitialStatusMap(providers, cachedStatusMap = {}) {
|
|
61
|
+
const map = {};
|
|
62
|
+
providers.forEach(provider => {
|
|
63
|
+
map[provider.name] = cachedStatusMap[provider.name] || {
|
|
64
|
+
state: 'pending',
|
|
65
|
+
label: '测试中...',
|
|
66
|
+
latency: null
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
return map;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 构建错误状态映射
|
|
74
|
+
* @param {Array} providers - 供应商列表
|
|
75
|
+
* @param {Error} error - 错误对象
|
|
76
|
+
* @returns {Object} 错误状态映射
|
|
77
|
+
*/
|
|
78
|
+
static buildErrorStatusMap(providers, error) {
|
|
79
|
+
const message = error ? `检测失败: ${error.message}` : '检测失败';
|
|
80
|
+
const map = {};
|
|
81
|
+
providers.forEach(provider => {
|
|
82
|
+
map[provider.name] = {
|
|
83
|
+
state: 'offline',
|
|
84
|
+
label: message,
|
|
85
|
+
latency: null
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
return map;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 格式化延迟显示
|
|
93
|
+
* @param {number} latency - 延迟毫秒数
|
|
94
|
+
* @returns {string} 格式化后的延迟文本
|
|
95
|
+
*/
|
|
96
|
+
static formatLatency(latency) {
|
|
97
|
+
if (latency === null || latency === undefined) {
|
|
98
|
+
return '';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (latency < 100) {
|
|
102
|
+
return chalk.green(`${latency}ms`);
|
|
103
|
+
} else if (latency < 300) {
|
|
104
|
+
return chalk.yellow(`${latency}ms`);
|
|
105
|
+
} else {
|
|
106
|
+
return chalk.red(`${latency}ms`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 判断状态是否需要刷新
|
|
112
|
+
* @param {Object} status - 状态对象
|
|
113
|
+
* @param {number} maxAge - 最大有效期(毫秒)
|
|
114
|
+
* @returns {boolean} 是否需要刷新
|
|
115
|
+
*/
|
|
116
|
+
static needsRefresh(status, maxAge = 30000) {
|
|
117
|
+
if (!status || !status.timestamp) {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
return Date.now() - status.timestamp > maxAge;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = { StatusHelper };
|