@pikecode/api-key-manager 2.0.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/LICENSE +21 -0
- package/README.md +232 -0
- package/bin/akm.js +101 -0
- package/bin/cc.js +101 -0
- package/package.json +71 -0
- package/src/CommandRegistry.js +74 -0
- package/src/commands/BaseCommand.js +212 -0
- package/src/commands/add.js +514 -0
- package/src/commands/current.js +94 -0
- package/src/commands/edit.js +208 -0
- package/src/commands/list.js +122 -0
- package/src/commands/remove.js +150 -0
- package/src/commands/switch.js +1479 -0
- package/src/config.js +250 -0
- package/src/index.js +19 -0
- package/src/navigation/EscNavigationManager.js +213 -0
- package/src/utils/claude-settings.js +150 -0
- package/src/utils/config-opener.js +44 -0
- package/src/utils/env-launcher.js +80 -0
- package/src/utils/error-handler.js +53 -0
- package/src/utils/inquirer-setup.js +89 -0
- package/src/utils/logger.js +41 -0
- package/src/utils/provider-status-checker.js +210 -0
- package/src/utils/storage.js +55 -0
- package/src/utils/terminal-format.js +41 -0
- package/src/utils/ui-helper.js +227 -0
- package/src/utils/update-checker.js +121 -0
- package/src/utils/validator.js +157 -0
|
@@ -0,0 +1,1479 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const inquirer = require('inquirer');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const Choices = require('inquirer/lib/objects/choices');
|
|
5
|
+
const { ConfigManager } = require('../config');
|
|
6
|
+
const { executeWithEnv } = require('../utils/env-launcher');
|
|
7
|
+
const { Logger } = require('../utils/logger');
|
|
8
|
+
const { UIHelper } = require('../utils/ui-helper');
|
|
9
|
+
const { findSettingsConflict, backupSettingsFile, clearConflictKeys, saveSettingsFile } = require('../utils/claude-settings');
|
|
10
|
+
const { BaseCommand } = require('./BaseCommand');
|
|
11
|
+
const { validator } = require('../utils/validator');
|
|
12
|
+
const { ProviderStatusChecker } = require('../utils/provider-status-checker');
|
|
13
|
+
|
|
14
|
+
class EnvSwitcher extends BaseCommand {
|
|
15
|
+
constructor() {
|
|
16
|
+
super();
|
|
17
|
+
this.configManager = new ConfigManager();
|
|
18
|
+
this.statusChecker = new ProviderStatusChecker();
|
|
19
|
+
this.latestStatusMap = {};
|
|
20
|
+
this.currentPromptContext = null;
|
|
21
|
+
this.activeStatusRefresh = null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async validateProvider(providerName) {
|
|
25
|
+
await this.configManager.load();
|
|
26
|
+
const provider = this.configManager.getProvider(providerName);
|
|
27
|
+
if (!provider) {
|
|
28
|
+
throw new Error(`供应商 '${providerName}' 不存在`);
|
|
29
|
+
}
|
|
30
|
+
return provider;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async showLaunchArgsSelection(providerName) {
|
|
34
|
+
try {
|
|
35
|
+
this.clearScreen();
|
|
36
|
+
const provider = await this.validateProvider(providerName);
|
|
37
|
+
const availableArgs = this.getAvailableLaunchArgs();
|
|
38
|
+
|
|
39
|
+
console.log(UIHelper.createTitle('启动配置', UIHelper.icons.launch));
|
|
40
|
+
console.log();
|
|
41
|
+
console.log(UIHelper.createCard('供应商', UIHelper.formatProvider(provider), UIHelper.icons.info));
|
|
42
|
+
console.log();
|
|
43
|
+
console.log(UIHelper.createHintLine([
|
|
44
|
+
['空格', '切换选中'],
|
|
45
|
+
['A', '全选'],
|
|
46
|
+
['I', '反选'],
|
|
47
|
+
['Enter', '启动 Claude Code'],
|
|
48
|
+
['ESC', '返回供应商选择']
|
|
49
|
+
]));
|
|
50
|
+
console.log();
|
|
51
|
+
|
|
52
|
+
// 设置 ESC 键监听
|
|
53
|
+
const escListener = this.createESCListener(() => {
|
|
54
|
+
Logger.info('返回供应商选择');
|
|
55
|
+
this.showProviderSelection();
|
|
56
|
+
}, '返回供应商选择');
|
|
57
|
+
|
|
58
|
+
// 显示启动参数选择界面
|
|
59
|
+
const choices = [
|
|
60
|
+
{
|
|
61
|
+
type: 'checkbox',
|
|
62
|
+
name: 'selectedArgs',
|
|
63
|
+
message: '选择启动参数:',
|
|
64
|
+
choices: availableArgs.map(arg => {
|
|
65
|
+
const commandText = UIHelper.colors.muted(`(${arg.name})`);
|
|
66
|
+
const descriptionText = arg.description
|
|
67
|
+
? ` ${UIHelper.colors.muted(arg.description)}`
|
|
68
|
+
: '';
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
name: `${UIHelper.colors.accent(arg.label || arg.name)} ${commandText}${descriptionText}`,
|
|
72
|
+
value: arg.name,
|
|
73
|
+
checked: arg.checked || false
|
|
74
|
+
};
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
let answers;
|
|
80
|
+
try {
|
|
81
|
+
answers = await this.prompt(choices);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
this.removeESCListener(escListener);
|
|
84
|
+
if (this.isEscCancelled(error)) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
this.removeESCListener(escListener);
|
|
91
|
+
|
|
92
|
+
// 选择参数后直接启动
|
|
93
|
+
await this.launchProvider(provider, answers.selectedArgs);
|
|
94
|
+
|
|
95
|
+
} catch (error) {
|
|
96
|
+
await this.handleError(error, '选择启动参数');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async ensureClaudeSettingsCompatibility(provider) {
|
|
101
|
+
try {
|
|
102
|
+
const conflict = await findSettingsConflict();
|
|
103
|
+
if (!conflict) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const keyList = conflict.keys.map((key) => `• ${key}`).join('\n');
|
|
108
|
+
|
|
109
|
+
const backupDir = path.dirname(conflict.filePath);
|
|
110
|
+
|
|
111
|
+
this.clearScreen();
|
|
112
|
+
console.log(UIHelper.createTitle('检测到环境变量冲突', UIHelper.icons.warning));
|
|
113
|
+
console.log();
|
|
114
|
+
console.log(UIHelper.createCard('冲突文件', conflict.filePath, UIHelper.icons.info));
|
|
115
|
+
console.log();
|
|
116
|
+
console.log(UIHelper.createCard('备份目录', `${backupDir}\n备份文件将命名为 settings.backup-YYYYMMDD_HHmmss.json`, UIHelper.icons.info));
|
|
117
|
+
console.log();
|
|
118
|
+
console.log(UIHelper.createCard('可能覆盖的变量', keyList, UIHelper.icons.warning));
|
|
119
|
+
console.log();
|
|
120
|
+
console.log(UIHelper.createTooltip('Claude 会优先读取该设置文件中的 env 配置,可能覆盖本次为供应商设置的变量。'));
|
|
121
|
+
console.log();
|
|
122
|
+
|
|
123
|
+
let answer;
|
|
124
|
+
try {
|
|
125
|
+
answer = await this.prompt([
|
|
126
|
+
{
|
|
127
|
+
type: 'list',
|
|
128
|
+
name: 'action',
|
|
129
|
+
message: `在 ${conflict.filePath} 中发现 env 配置会覆盖供应商 '${provider.displayName || provider.name}' 的变量,选择处理方式:`,
|
|
130
|
+
choices: [
|
|
131
|
+
{ name: '🔧 备份并清空这些变量', value: 'fix' },
|
|
132
|
+
{ name: '⚠️ 忽略并继续(可能导致切换失败)', value: 'ignore' },
|
|
133
|
+
{ name: '❌ 取消启动', value: 'cancel' }
|
|
134
|
+
],
|
|
135
|
+
default: 'fix'
|
|
136
|
+
}
|
|
137
|
+
]);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
if (this.isEscCancelled(error)) {
|
|
140
|
+
Logger.info('已取消启动');
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (answer.action === 'fix') {
|
|
147
|
+
let confirmBackup;
|
|
148
|
+
try {
|
|
149
|
+
confirmBackup = await this.prompt([
|
|
150
|
+
{
|
|
151
|
+
type: 'confirm',
|
|
152
|
+
name: 'confirmed',
|
|
153
|
+
message: `将在 ${backupDir} 中创建备份文件 (settings.backup-YYYYMMDD_HHmmss.json),并清空冲突变量。是否继续?`,
|
|
154
|
+
default: true
|
|
155
|
+
}
|
|
156
|
+
]);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
if (this.isEscCancelled(error)) {
|
|
159
|
+
Logger.info('已取消启动');
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!confirmBackup.confirmed) {
|
|
166
|
+
Logger.info('已取消启动');
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
const backupPath = await backupSettingsFile(conflict.filePath);
|
|
172
|
+
const updatedSettings = clearConflictKeys(
|
|
173
|
+
{
|
|
174
|
+
...conflict.settings,
|
|
175
|
+
env: conflict.settings.env ? { ...conflict.settings.env } : undefined
|
|
176
|
+
},
|
|
177
|
+
conflict.keys
|
|
178
|
+
);
|
|
179
|
+
await saveSettingsFile(conflict.filePath, updatedSettings);
|
|
180
|
+
Logger.success(`已将 ${conflict.filePath} 备份至 '${backupPath}' 并清空冲突变量。`);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
throw new Error(`清理 Claude 设置文件失败: ${error.message}`);
|
|
183
|
+
}
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (answer.action === 'ignore') {
|
|
188
|
+
Logger.warning(`已忽略 ${conflict.filePath} 中的冲突,Claude 可能仍会使用该文件里的旧变量。`);
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
Logger.info('已取消启动');
|
|
193
|
+
return false;
|
|
194
|
+
} catch (error) {
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async launchProvider(provider, selectedLaunchArgs) {
|
|
200
|
+
try {
|
|
201
|
+
const shouldContinue = await this.ensureClaudeSettingsCompatibility(provider);
|
|
202
|
+
if (!shouldContinue) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
this.clearScreen();
|
|
207
|
+
console.log(UIHelper.createTitle('正在启动', UIHelper.icons.loading));
|
|
208
|
+
console.log();
|
|
209
|
+
console.log(UIHelper.createCard('目标供应商', UIHelper.formatProvider(provider), UIHelper.icons.launch));
|
|
210
|
+
|
|
211
|
+
if (selectedLaunchArgs.length > 0) {
|
|
212
|
+
console.log(UIHelper.createCard('启动参数', selectedLaunchArgs.join(', '), UIHelper.icons.settings));
|
|
213
|
+
}
|
|
214
|
+
console.log();
|
|
215
|
+
|
|
216
|
+
// 显示进度
|
|
217
|
+
const loadingInterval = UIHelper.createLoadingAnimation('正在设置环境...');
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
// 设置为当前供应商
|
|
221
|
+
await this.configManager.setCurrentProvider(provider.name);
|
|
222
|
+
|
|
223
|
+
// 更新使用统计
|
|
224
|
+
provider.usageCount = (provider.usageCount || 0) + 1;
|
|
225
|
+
provider.lastUsed = new Date().toISOString();
|
|
226
|
+
await this.configManager.save();
|
|
227
|
+
|
|
228
|
+
UIHelper.clearLoadingAnimation(loadingInterval);
|
|
229
|
+
|
|
230
|
+
console.log(UIHelper.createCard('准备就绪', '环境配置完成,正在启动 Claude Code...', UIHelper.icons.success));
|
|
231
|
+
console.log();
|
|
232
|
+
|
|
233
|
+
// 设置环境变量并启动Claude Code
|
|
234
|
+
await executeWithEnv(provider, selectedLaunchArgs);
|
|
235
|
+
|
|
236
|
+
} catch (error) {
|
|
237
|
+
UIHelper.clearLoadingAnimation(loadingInterval);
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
} catch (error) {
|
|
242
|
+
await this.handleError(error, '启动供应商');
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
getAvailableLaunchArgs() {
|
|
247
|
+
return [
|
|
248
|
+
{
|
|
249
|
+
name: '--continue',
|
|
250
|
+
label: '继续上次对话',
|
|
251
|
+
description: '恢复上次的对话记录',
|
|
252
|
+
checked: false
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
name: '--dangerously-skip-permissions',
|
|
256
|
+
label: '最高权限',
|
|
257
|
+
description: '仅在沙盒环境中使用',
|
|
258
|
+
checked: false
|
|
259
|
+
}
|
|
260
|
+
];
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// getArgDescription 方法已被移除,直接使用 arg.description
|
|
264
|
+
|
|
265
|
+
async showProviderSelection() {
|
|
266
|
+
try {
|
|
267
|
+
// 并行加载配置和准备界面
|
|
268
|
+
const providers = await this.configManager.ensureLoaded().then(() => this.configManager.listProviders());
|
|
269
|
+
|
|
270
|
+
const initialStatusMap = this._buildInitialStatusMap(providers);
|
|
271
|
+
// 显示欢迎界面(立即渲染)
|
|
272
|
+
this.showWelcomeScreen(providers, initialStatusMap, null);
|
|
273
|
+
|
|
274
|
+
if (providers.length === 0) {
|
|
275
|
+
Logger.warning('暂无配置的供应商');
|
|
276
|
+
Logger.info('请先运行 "akm add" 添加供应商配置');
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const choices = this.createProviderChoices(providers, false, initialStatusMap);
|
|
281
|
+
|
|
282
|
+
this.currentPromptContext = 'selection';
|
|
283
|
+
|
|
284
|
+
// 异步更新供应商状态,不阻塞界面展示
|
|
285
|
+
if (providers.length > 0) {
|
|
286
|
+
this._startStatusRefresh(providers);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// 添加特殊选项
|
|
290
|
+
choices.push(
|
|
291
|
+
new inquirer.Separator(),
|
|
292
|
+
{ name: `${UIHelper.icons.add} 添加新供应商`, value: '__ADD__' },
|
|
293
|
+
{ name: `${UIHelper.icons.list} 供应商管理 (编辑/删除)`, value: '__MANAGE__' },
|
|
294
|
+
{ name: `${UIHelper.icons.config} 打开配置文件`, value: '__OPEN_CONFIG__' },
|
|
295
|
+
{ name: `${UIHelper.icons.error} 退出`, value: '__EXIT__' }
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
// 获取当前供应商作为默认选项
|
|
299
|
+
const currentProvider = providers.find(p => p.current);
|
|
300
|
+
const defaultChoice = currentProvider ? currentProvider.name : providers[0]?.name;
|
|
301
|
+
|
|
302
|
+
// 设置 ESC 键监听
|
|
303
|
+
const escListener = this.createESCListener(() => {
|
|
304
|
+
Logger.info('退出程序');
|
|
305
|
+
this.showExitScreen();
|
|
306
|
+
process.exit(0);
|
|
307
|
+
}, '退出程序');
|
|
308
|
+
|
|
309
|
+
const answer = await this.prompt([
|
|
310
|
+
{
|
|
311
|
+
type: 'list',
|
|
312
|
+
name: 'provider',
|
|
313
|
+
message: `请选择要切换的供应商 (总计 ${providers.length} 个):`,
|
|
314
|
+
choices,
|
|
315
|
+
default: defaultChoice,
|
|
316
|
+
pageSize: 12
|
|
317
|
+
}
|
|
318
|
+
]);
|
|
319
|
+
|
|
320
|
+
// 移除 ESC 键监听
|
|
321
|
+
this.removeESCListener(escListener);
|
|
322
|
+
|
|
323
|
+
this._cancelStatusRefresh();
|
|
324
|
+
|
|
325
|
+
if (answer.provider === '__OPEN_CONFIG__') {
|
|
326
|
+
await this.openConfigFile();
|
|
327
|
+
return await this.showProviderSelection();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const result = await this.handleSelection(answer.provider);
|
|
331
|
+
this.currentPromptContext = null;
|
|
332
|
+
return result;
|
|
333
|
+
|
|
334
|
+
} catch (error) {
|
|
335
|
+
await this.handleError(error, '显示供应商选择');
|
|
336
|
+
} finally {
|
|
337
|
+
if (this.currentPromptContext === 'selection') {
|
|
338
|
+
this.currentPromptContext = null;
|
|
339
|
+
}
|
|
340
|
+
this._cancelStatusRefresh();
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async openConfigFile() {
|
|
345
|
+
const { openAKMConfigFile } = require('../utils/config-opener');
|
|
346
|
+
try {
|
|
347
|
+
await openAKMConfigFile();
|
|
348
|
+
} catch (err) {
|
|
349
|
+
Logger.error(`打开配置文件失败: ${err.message}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
showWelcomeScreen(providers, statusMap = {}, statusError = null) {
|
|
354
|
+
this.clearScreen();
|
|
355
|
+
|
|
356
|
+
if (providers.length > 0) {
|
|
357
|
+
console.log(UIHelper.colors.info(`总共 ${providers.length} 个供应商配置`));
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (statusError) {
|
|
361
|
+
console.log();
|
|
362
|
+
console.log(UIHelper.createCard('状态检测', `检测失败: ${statusError.message}`, UIHelper.icons.warning));
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
console.log();
|
|
366
|
+
console.log(UIHelper.createHintLine([
|
|
367
|
+
['↑ / ↓', '选择供应商'],
|
|
368
|
+
['Enter', '确认'],
|
|
369
|
+
['Tab', '切换选项'],
|
|
370
|
+
['ESC', '退出程序'],
|
|
371
|
+
['Ctrl+C', '强制退出']
|
|
372
|
+
]));
|
|
373
|
+
console.log();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
async handleSelection(selection) {
|
|
377
|
+
switch (selection) {
|
|
378
|
+
case '__ADD__':
|
|
379
|
+
// 使用CommandRegistry避免循环引用
|
|
380
|
+
const { registry } = require('../CommandRegistry');
|
|
381
|
+
return await registry.executeCommand('add');
|
|
382
|
+
case '__MANAGE__':
|
|
383
|
+
return await this.showManageMenu();
|
|
384
|
+
case '__EXIT__':
|
|
385
|
+
this.showExitScreen();
|
|
386
|
+
process.exit(0);
|
|
387
|
+
default:
|
|
388
|
+
return await this.showLaunchArgsSelection(selection);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async showQuickSettings() {
|
|
393
|
+
this.clearScreen();
|
|
394
|
+
console.log(UIHelper.createHintLine([
|
|
395
|
+
['↑ / ↓', '选择项目'],
|
|
396
|
+
['Enter', '确认'],
|
|
397
|
+
['ESC', '返回主菜单']
|
|
398
|
+
]));
|
|
399
|
+
console.log();
|
|
400
|
+
const choices = [
|
|
401
|
+
{ name: `${UIHelper.icons.search} 搜索供应商`, value: 'search' },
|
|
402
|
+
{ name: `${UIHelper.icons.edit} 批量编辑`, value: 'batch' },
|
|
403
|
+
{ name: `${UIHelper.icons.settings} 全局设置`, value: 'global' },
|
|
404
|
+
{ name: `${UIHelper.icons.info} 查看统计`, value: 'stats' },
|
|
405
|
+
{ name: `${UIHelper.icons.back} 返回主菜单`, value: 'back' }
|
|
406
|
+
];
|
|
407
|
+
|
|
408
|
+
// 设置 ESC 键监听
|
|
409
|
+
const escListener = this.createESCListener(() => {
|
|
410
|
+
Logger.info('返回供应商选择');
|
|
411
|
+
this.showProviderSelection();
|
|
412
|
+
}, '返回供应商选择');
|
|
413
|
+
|
|
414
|
+
let answer;
|
|
415
|
+
try {
|
|
416
|
+
answer = await this.prompt([
|
|
417
|
+
{
|
|
418
|
+
type: 'list',
|
|
419
|
+
name: 'setting',
|
|
420
|
+
message: '快速设置:',
|
|
421
|
+
choices,
|
|
422
|
+
pageSize: 8
|
|
423
|
+
}
|
|
424
|
+
]);
|
|
425
|
+
} catch (error) {
|
|
426
|
+
this.removeESCListener(escListener);
|
|
427
|
+
if (this.isEscCancelled(error)) {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
throw error;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
this.removeESCListener(escListener);
|
|
434
|
+
|
|
435
|
+
switch (answer.setting) {
|
|
436
|
+
case 'search':
|
|
437
|
+
return await this.showSearchProvider();
|
|
438
|
+
case 'batch':
|
|
439
|
+
return await this.showBatchEdit();
|
|
440
|
+
case 'global':
|
|
441
|
+
return await this.showGlobalSettings();
|
|
442
|
+
case 'stats':
|
|
443
|
+
return await this.showStatistics();
|
|
444
|
+
case 'back':
|
|
445
|
+
return await this.showProviderSelection();
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
async showBatchEdit() {
|
|
450
|
+
this.clearScreen();
|
|
451
|
+
console.log(UIHelper.createTitle('批量编辑', UIHelper.icons.edit));
|
|
452
|
+
console.log();
|
|
453
|
+
console.log(UIHelper.createTooltip('此功能正在开发中...'));
|
|
454
|
+
console.log();
|
|
455
|
+
console.log(UIHelper.createHintLine([
|
|
456
|
+
['Enter', '返回上一页'],
|
|
457
|
+
['ESC', '返回快速设置']
|
|
458
|
+
]));
|
|
459
|
+
console.log();
|
|
460
|
+
|
|
461
|
+
// 设置 ESC 键监听
|
|
462
|
+
const escListener = this.createESCListener(() => {
|
|
463
|
+
Logger.info('返回快速设置');
|
|
464
|
+
this.showQuickSettings();
|
|
465
|
+
}, '返回快速设置');
|
|
466
|
+
|
|
467
|
+
try {
|
|
468
|
+
await this.prompt([
|
|
469
|
+
{
|
|
470
|
+
type: 'input',
|
|
471
|
+
name: 'continue',
|
|
472
|
+
message: '按回车键返回...'
|
|
473
|
+
}
|
|
474
|
+
]);
|
|
475
|
+
} catch (error) {
|
|
476
|
+
this.removeESCListener(escListener);
|
|
477
|
+
if (this.isEscCancelled(error)) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
throw error;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
this.removeESCListener(escListener);
|
|
484
|
+
|
|
485
|
+
return await this.showQuickSettings();
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
async showGlobalSettings() {
|
|
489
|
+
this.clearScreen();
|
|
490
|
+
console.log(UIHelper.createTitle('全局设置', UIHelper.icons.settings));
|
|
491
|
+
console.log();
|
|
492
|
+
console.log(UIHelper.createTooltip('此功能正在开发中...'));
|
|
493
|
+
console.log();
|
|
494
|
+
console.log(UIHelper.createHintLine([
|
|
495
|
+
['Enter', '返回上一页'],
|
|
496
|
+
['ESC', '返回快速设置']
|
|
497
|
+
]));
|
|
498
|
+
console.log();
|
|
499
|
+
|
|
500
|
+
// 设置 ESC 键监听
|
|
501
|
+
const escListener = this.createESCListener(() => {
|
|
502
|
+
Logger.info('返回快速设置');
|
|
503
|
+
this.showQuickSettings();
|
|
504
|
+
}, '返回快速设置');
|
|
505
|
+
|
|
506
|
+
try {
|
|
507
|
+
await this.prompt([
|
|
508
|
+
{
|
|
509
|
+
type: 'input',
|
|
510
|
+
name: 'continue',
|
|
511
|
+
message: '按回车键返回...'
|
|
512
|
+
}
|
|
513
|
+
]);
|
|
514
|
+
} catch (error) {
|
|
515
|
+
this.removeESCListener(escListener);
|
|
516
|
+
if (this.isEscCancelled(error)) {
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
throw error;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
this.removeESCListener(escListener);
|
|
523
|
+
|
|
524
|
+
return await this.showQuickSettings();
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
async showSearchProvider() {
|
|
528
|
+
this.clearScreen();
|
|
529
|
+
console.log(UIHelper.createHintLine([
|
|
530
|
+
['Enter', '执行搜索'],
|
|
531
|
+
['ESC', '返回快速设置']
|
|
532
|
+
]));
|
|
533
|
+
console.log(UIHelper.createTooltip('示例: claude、demo 或供应商别名'));
|
|
534
|
+
console.log();
|
|
535
|
+
// 设置 ESC 键监听
|
|
536
|
+
const escListener = this.createESCListener(() => {
|
|
537
|
+
Logger.info('返回快速设置');
|
|
538
|
+
this.showQuickSettings();
|
|
539
|
+
}, '返回快速设置');
|
|
540
|
+
|
|
541
|
+
let answer;
|
|
542
|
+
try {
|
|
543
|
+
answer = await this.prompt([
|
|
544
|
+
{
|
|
545
|
+
type: 'input',
|
|
546
|
+
name: 'search',
|
|
547
|
+
message: '输入供应商名称搜索:',
|
|
548
|
+
validate: input => input.trim() !== '' || '请输入搜索内容'
|
|
549
|
+
}
|
|
550
|
+
]);
|
|
551
|
+
} catch (error) {
|
|
552
|
+
this.removeESCListener(escListener);
|
|
553
|
+
if (this.isEscCancelled(error)) {
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
throw error;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
this.removeESCListener(escListener);
|
|
560
|
+
|
|
561
|
+
await this.configManager.load();
|
|
562
|
+
const providers = this.configManager.listProviders();
|
|
563
|
+
const searchResults = providers.filter(p =>
|
|
564
|
+
p.name.toLowerCase().includes(answer.search.toLowerCase()) ||
|
|
565
|
+
p.displayName.toLowerCase().includes(answer.search.toLowerCase())
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
if (searchResults.length === 0) {
|
|
569
|
+
Logger.warning('未找到匹配的供应商');
|
|
570
|
+
return await this.showQuickSettings();
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const choices = searchResults.map(p => ({
|
|
574
|
+
name: UIHelper.formatProvider(p),
|
|
575
|
+
value: p.name
|
|
576
|
+
}));
|
|
577
|
+
|
|
578
|
+
choices.push(
|
|
579
|
+
new inquirer.Separator(),
|
|
580
|
+
{ name: `${UIHelper.icons.back} 返回设置`, value: 'back' }
|
|
581
|
+
);
|
|
582
|
+
|
|
583
|
+
// 获取当前供应商作为默认选项(在搜索结果中)
|
|
584
|
+
const currentProvider = searchResults.find(p => p.current);
|
|
585
|
+
const defaultChoice = currentProvider ? currentProvider.name : searchResults[0]?.name;
|
|
586
|
+
|
|
587
|
+
// 设置 ESC 键监听
|
|
588
|
+
const escListener2 = this.createESCListener(() => {
|
|
589
|
+
Logger.info('返回快速设置');
|
|
590
|
+
this.showQuickSettings();
|
|
591
|
+
}, '返回快速设置');
|
|
592
|
+
|
|
593
|
+
console.log();
|
|
594
|
+
console.log(UIHelper.createHintLine([
|
|
595
|
+
['↑ / ↓', '选择结果'],
|
|
596
|
+
['Enter', '查看详情'],
|
|
597
|
+
['ESC', '返回快速设置']
|
|
598
|
+
]));
|
|
599
|
+
|
|
600
|
+
let result;
|
|
601
|
+
try {
|
|
602
|
+
result = await this.prompt([
|
|
603
|
+
{
|
|
604
|
+
type: 'list',
|
|
605
|
+
name: 'provider',
|
|
606
|
+
message: '搜索结果:',
|
|
607
|
+
choices,
|
|
608
|
+
default: defaultChoice,
|
|
609
|
+
pageSize: 10
|
|
610
|
+
}
|
|
611
|
+
]);
|
|
612
|
+
} catch (error) {
|
|
613
|
+
this.removeESCListener(escListener2);
|
|
614
|
+
if (this.isEscCancelled(error)) {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
throw error;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
this.removeESCListener(escListener2);
|
|
621
|
+
|
|
622
|
+
if (result.provider === 'back') {
|
|
623
|
+
return await this.showQuickSettings();
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
return await this.showProviderDetails(result.provider);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
async showStatistics() {
|
|
630
|
+
await this.configManager.load();
|
|
631
|
+
const providers = this.configManager.listProviders();
|
|
632
|
+
this.clearScreen();
|
|
633
|
+
|
|
634
|
+
const totalProviders = providers.length;
|
|
635
|
+
const currentProvider = providers.find(p => p.current);
|
|
636
|
+
const totalUsage = providers.reduce((sum, p) => sum + (p.usageCount || 0), 0);
|
|
637
|
+
const mostUsed = providers.reduce((max, p) => (p.usageCount || 0) > (max.usageCount || 0) ? p : max, providers[0]);
|
|
638
|
+
|
|
639
|
+
console.log(UIHelper.createTitle('使用统计', UIHelper.icons.info));
|
|
640
|
+
console.log();
|
|
641
|
+
|
|
642
|
+
const stats = [
|
|
643
|
+
['总供应商数', totalProviders],
|
|
644
|
+
['当前供应商', currentProvider ? currentProvider.displayName : '无'],
|
|
645
|
+
['总使用次数', totalUsage],
|
|
646
|
+
['最常用供应商', mostUsed ? mostUsed.displayName : '无'],
|
|
647
|
+
['创建时间', providers.length > 0 ? UIHelper.formatTime(providers[0].createdAt) : '无']
|
|
648
|
+
];
|
|
649
|
+
|
|
650
|
+
console.log(UIHelper.createTable(['项目', '数据'], stats));
|
|
651
|
+
console.log();
|
|
652
|
+
console.log(UIHelper.createHintLine([
|
|
653
|
+
['Enter', '返回快速设置'],
|
|
654
|
+
['ESC', '返回快速设置']
|
|
655
|
+
]));
|
|
656
|
+
console.log();
|
|
657
|
+
|
|
658
|
+
// 设置 ESC 键监听
|
|
659
|
+
const escListener = this.createESCListener(() => {
|
|
660
|
+
Logger.info('返回快速设置');
|
|
661
|
+
this.showQuickSettings();
|
|
662
|
+
}, '返回快速设置');
|
|
663
|
+
|
|
664
|
+
try {
|
|
665
|
+
await this.prompt([
|
|
666
|
+
{
|
|
667
|
+
type: 'input',
|
|
668
|
+
name: 'continue',
|
|
669
|
+
message: '按回车键继续...'
|
|
670
|
+
}
|
|
671
|
+
]);
|
|
672
|
+
} catch (error) {
|
|
673
|
+
this.removeESCListener(escListener);
|
|
674
|
+
if (this.isEscCancelled(error)) {
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
throw error;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
this.removeESCListener(escListener);
|
|
681
|
+
|
|
682
|
+
return await this.showQuickSettings();
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
showExitScreen() {
|
|
686
|
+
this.clearScreen();
|
|
687
|
+
console.log(UIHelper.createTitle('感谢使用', UIHelper.icons.home));
|
|
688
|
+
console.log();
|
|
689
|
+
console.log(UIHelper.colors.info('再见!期待下次使用 🎉'));
|
|
690
|
+
console.log();
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
async showHelp() {
|
|
694
|
+
this.clearScreen();
|
|
695
|
+
console.log(UIHelper.createTitle('快捷键帮助', UIHelper.icons.info));
|
|
696
|
+
console.log();
|
|
697
|
+
|
|
698
|
+
const sections = [
|
|
699
|
+
{
|
|
700
|
+
title: '通用操作',
|
|
701
|
+
items: [
|
|
702
|
+
UIHelper.createShortcutHint('↑ / ↓', '在选项中移动'),
|
|
703
|
+
UIHelper.createShortcutHint('Enter', '确认/继续'),
|
|
704
|
+
UIHelper.createShortcutHint('ESC', '返回上一层'),
|
|
705
|
+
UIHelper.createShortcutHint('Ctrl+C', '随时强制退出')
|
|
706
|
+
]
|
|
707
|
+
},
|
|
708
|
+
{
|
|
709
|
+
title: '供应商列表',
|
|
710
|
+
items: [
|
|
711
|
+
UIHelper.createShortcutHint('Tab', '切换特殊选项'),
|
|
712
|
+
UIHelper.createShortcutHint('A', '在启动参数列表中全选'),
|
|
713
|
+
UIHelper.createShortcutHint('I', '在启动参数列表中反选')
|
|
714
|
+
]
|
|
715
|
+
},
|
|
716
|
+
{
|
|
717
|
+
title: '搜索界面',
|
|
718
|
+
items: [
|
|
719
|
+
UIHelper.createShortcutHint('Enter', '执行搜索或确认结果'),
|
|
720
|
+
UIHelper.createShortcutHint('ESC', '取消搜索返回上一页')
|
|
721
|
+
]
|
|
722
|
+
}
|
|
723
|
+
];
|
|
724
|
+
|
|
725
|
+
sections.forEach(section => {
|
|
726
|
+
console.log(UIHelper.createCard(section.title, section.items.join('\n'), UIHelper.icons.info));
|
|
727
|
+
console.log();
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
const escListener = this.createESCListener(() => {
|
|
731
|
+
Logger.info('返回主菜单');
|
|
732
|
+
this.showProviderSelection();
|
|
733
|
+
}, '返回主菜单');
|
|
734
|
+
|
|
735
|
+
console.log(UIHelper.createHintLine([
|
|
736
|
+
['Enter', '返回主菜单'],
|
|
737
|
+
['ESC', '返回主菜单']
|
|
738
|
+
]));
|
|
739
|
+
console.log();
|
|
740
|
+
|
|
741
|
+
try {
|
|
742
|
+
await this.prompt([
|
|
743
|
+
{
|
|
744
|
+
type: 'input',
|
|
745
|
+
name: 'continue',
|
|
746
|
+
message: '按回车键返回主菜单'
|
|
747
|
+
}
|
|
748
|
+
]);
|
|
749
|
+
} catch (error) {
|
|
750
|
+
this.removeESCListener(escListener);
|
|
751
|
+
if (this.isEscCancelled(error)) {
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
throw error;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
this.removeESCListener(escListener);
|
|
758
|
+
return await this.showProviderSelection();
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
async showManageMenu() {
|
|
762
|
+
let escListener;
|
|
763
|
+
try {
|
|
764
|
+
await this.configManager.load();
|
|
765
|
+
const providers = this.configManager.listProviders();
|
|
766
|
+
this.clearScreen();
|
|
767
|
+
console.log(UIHelper.createHintLine([
|
|
768
|
+
['↑ / ↓', '选择供应商或操作'],
|
|
769
|
+
['Enter', '确认'],
|
|
770
|
+
['ESC', '返回主菜单']
|
|
771
|
+
]));
|
|
772
|
+
console.log();
|
|
773
|
+
|
|
774
|
+
console.log(UIHelper.createTitle('供应商管理', UIHelper.icons.list));
|
|
775
|
+
console.log();
|
|
776
|
+
|
|
777
|
+
if (providers.length === 0) {
|
|
778
|
+
console.log(UIHelper.createCard('提示', '暂无配置的供应商\n请先运行 "akm add" 添加供应商配置', UIHelper.icons.warning));
|
|
779
|
+
return await this.showProviderSelection();
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
const statusMap = this._buildInitialStatusMap(providers);
|
|
783
|
+
const choices = this.createProviderChoices(providers, true, statusMap);
|
|
784
|
+
|
|
785
|
+
this.currentPromptContext = 'manage';
|
|
786
|
+
|
|
787
|
+
if (providers.length > 0) {
|
|
788
|
+
this._startStatusRefresh(providers);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// 设置 ESC 键监听
|
|
792
|
+
escListener = this.createESCListener(() => {
|
|
793
|
+
Logger.info('返回供应商选择');
|
|
794
|
+
this.showProviderSelection();
|
|
795
|
+
}, '返回供应商选择');
|
|
796
|
+
|
|
797
|
+
let answer;
|
|
798
|
+
try {
|
|
799
|
+
answer = await this.prompt([
|
|
800
|
+
{
|
|
801
|
+
type: 'list',
|
|
802
|
+
name: 'action',
|
|
803
|
+
message: `选择供应商或操作 (总计 ${providers.length} 个):`,
|
|
804
|
+
choices,
|
|
805
|
+
pageSize: 12
|
|
806
|
+
}
|
|
807
|
+
]);
|
|
808
|
+
} catch (error) {
|
|
809
|
+
this.removeESCListener(escListener);
|
|
810
|
+
if (this.isEscCancelled(error)) {
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
throw error;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
this.removeESCListener(escListener);
|
|
817
|
+
|
|
818
|
+
this._cancelStatusRefresh();
|
|
819
|
+
|
|
820
|
+
const result = await this.handleManageAction(answer.action);
|
|
821
|
+
this.currentPromptContext = null;
|
|
822
|
+
return result;
|
|
823
|
+
|
|
824
|
+
} catch (error) {
|
|
825
|
+
await this.handleError(error, '显示供应商管理');
|
|
826
|
+
} finally {
|
|
827
|
+
if (this.currentPromptContext === 'manage') {
|
|
828
|
+
this.currentPromptContext = null;
|
|
829
|
+
}
|
|
830
|
+
this._cancelStatusRefresh();
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
createProviderChoices(providers, includeActions = false, statusMap = {}) {
|
|
835
|
+
const lastUsedProvider = providers.reduce((latest, current) => {
|
|
836
|
+
if (!current || !current.lastUsed) {
|
|
837
|
+
return latest;
|
|
838
|
+
}
|
|
839
|
+
if (!latest || !latest.lastUsed) {
|
|
840
|
+
return current;
|
|
841
|
+
}
|
|
842
|
+
return new Date(current.lastUsed) > new Date(latest.lastUsed) ? current : latest;
|
|
843
|
+
}, null);
|
|
844
|
+
|
|
845
|
+
const choices = providers.map(provider => {
|
|
846
|
+
const isLastUsed = lastUsedProvider && lastUsedProvider.name === provider.name;
|
|
847
|
+
const availability = statusMap[provider.name];
|
|
848
|
+
const icon = this._iconForState(availability?.state);
|
|
849
|
+
const statusText = this._formatAvailability(availability);
|
|
850
|
+
const statusLabel = chalk.gray('-') + ' ' + statusText;
|
|
851
|
+
const label = `${icon} ${UIHelper.formatProvider(provider)}${isLastUsed ? UIHelper.colors.muted(' --- 上次使用') : ''} ${statusLabel}`;
|
|
852
|
+
|
|
853
|
+
return {
|
|
854
|
+
name: label,
|
|
855
|
+
value: provider.name,
|
|
856
|
+
short: provider.name
|
|
857
|
+
};
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
if (includeActions) {
|
|
861
|
+
choices.push(
|
|
862
|
+
new inquirer.Separator(),
|
|
863
|
+
{ name: `${UIHelper.icons.back} 返回供应商选择`, value: 'back' },
|
|
864
|
+
{ name: `${UIHelper.icons.error} 退出`, value: 'exit' }
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
return choices;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
_iconForState(state) {
|
|
872
|
+
if (state === 'online') {
|
|
873
|
+
return '🟢';
|
|
874
|
+
}
|
|
875
|
+
if (state === 'degraded') {
|
|
876
|
+
return '🟡';
|
|
877
|
+
}
|
|
878
|
+
if (state === 'offline') {
|
|
879
|
+
return '🔴';
|
|
880
|
+
}
|
|
881
|
+
if (state === 'pending') {
|
|
882
|
+
return '⏳';
|
|
883
|
+
}
|
|
884
|
+
return '⚪';
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
_formatAvailability(availability) {
|
|
888
|
+
if (!availability) {
|
|
889
|
+
return chalk.gray('测试中...');
|
|
890
|
+
}
|
|
891
|
+
if (availability.state === 'online') {
|
|
892
|
+
return chalk.green(availability.label || '可用');
|
|
893
|
+
}
|
|
894
|
+
if (availability.state === 'degraded') {
|
|
895
|
+
return chalk.yellow(availability.label || '有限可用');
|
|
896
|
+
}
|
|
897
|
+
if (availability.state === 'offline') {
|
|
898
|
+
return chalk.red(availability.label || '不可用');
|
|
899
|
+
}
|
|
900
|
+
if (availability.state === 'pending') {
|
|
901
|
+
return chalk.gray(availability.label || '测试中...');
|
|
902
|
+
}
|
|
903
|
+
return chalk.gray(availability.label || '未知');
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
_buildInitialStatusMap(providers) {
|
|
907
|
+
const cached = this.latestStatusMap || {};
|
|
908
|
+
const map = {};
|
|
909
|
+
providers.forEach(provider => {
|
|
910
|
+
map[provider.name] = cached[provider.name] || {
|
|
911
|
+
state: 'pending',
|
|
912
|
+
label: '测试中...',
|
|
913
|
+
latency: null
|
|
914
|
+
};
|
|
915
|
+
});
|
|
916
|
+
return map;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
_buildErrorStatusMap(providers, error) {
|
|
920
|
+
const message = error ? `检测失败: ${error.message}` : '检测失败';
|
|
921
|
+
const map = {};
|
|
922
|
+
providers.forEach(provider => {
|
|
923
|
+
map[provider.name] = {
|
|
924
|
+
state: 'offline',
|
|
925
|
+
label: message,
|
|
926
|
+
latency: null
|
|
927
|
+
};
|
|
928
|
+
});
|
|
929
|
+
return map;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
_startStatusRefresh(providers) {
|
|
933
|
+
this._cancelStatusRefresh();
|
|
934
|
+
|
|
935
|
+
const refreshToken = Symbol('statusRefresh');
|
|
936
|
+
this.activeStatusRefresh = refreshToken;
|
|
937
|
+
|
|
938
|
+
const latestMap = { ...this.latestStatusMap };
|
|
939
|
+
this.statusChecker
|
|
940
|
+
.checkAllStreaming(providers, (providerName, status) => {
|
|
941
|
+
if (this.activeStatusRefresh !== refreshToken) {
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
latestMap[providerName] = status;
|
|
945
|
+
this.latestStatusMap = latestMap;
|
|
946
|
+
this._applyIncrementalStatus(providerName, status, refreshToken);
|
|
947
|
+
})
|
|
948
|
+
.then(finalMap => {
|
|
949
|
+
if (this.activeStatusRefresh !== refreshToken) {
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
this.latestStatusMap = finalMap;
|
|
953
|
+
})
|
|
954
|
+
.catch(error => {
|
|
955
|
+
if (this.activeStatusRefresh !== refreshToken) {
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
Logger.error(`供应商状态检测失败: ${error.message}`);
|
|
959
|
+
const fallback = this._buildErrorStatusMap(providers, error);
|
|
960
|
+
Object.assign(latestMap, fallback);
|
|
961
|
+
this.latestStatusMap = latestMap;
|
|
962
|
+
this._applyStatusUpdate(providers, fallback, error, refreshToken);
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
_cancelStatusRefresh() {
|
|
967
|
+
this.activeStatusRefresh = null;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
_applyStatusUpdate(providers, statusMap, error, refreshToken = null) {
|
|
971
|
+
if (refreshToken && this.activeStatusRefresh !== refreshToken) {
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
if (this.currentPromptContext !== 'selection' && this.currentPromptContext !== 'manage') {
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
const activePrompt = this.activePrompt?.promise?.ui?.activePrompt;
|
|
979
|
+
if (!activePrompt || activePrompt.status === 'answered') {
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
const includeActions = this.currentPromptContext === 'manage';
|
|
984
|
+
const updatedChoicesBase = this.createProviderChoices(providers, includeActions, statusMap);
|
|
985
|
+
const updatedChoices = [...updatedChoicesBase];
|
|
986
|
+
|
|
987
|
+
if (!includeActions) {
|
|
988
|
+
updatedChoices.push(
|
|
989
|
+
new inquirer.Separator(),
|
|
990
|
+
{ name: `${UIHelper.icons.add} 添加新供应商`, value: '__ADD__' },
|
|
991
|
+
{ name: `${UIHelper.icons.list} 供应商管理 (编辑/删除)`, value: '__MANAGE__' },
|
|
992
|
+
{ name: `${UIHelper.icons.config} 打开配置文件`, value: '__OPEN_CONFIG__' },
|
|
993
|
+
{ name: `${UIHelper.icons.error} 退出`, value: '__EXIT__' }
|
|
994
|
+
);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
const previousValue = (() => {
|
|
998
|
+
try {
|
|
999
|
+
return activePrompt.opt.choices?.getChoice(activePrompt.selected)?.value ?? null;
|
|
1000
|
+
} catch (err) {
|
|
1001
|
+
return null;
|
|
1002
|
+
}
|
|
1003
|
+
})();
|
|
1004
|
+
|
|
1005
|
+
activePrompt.opt.choices = new Choices(updatedChoices, activePrompt.answers);
|
|
1006
|
+
|
|
1007
|
+
if (previousValue != null) {
|
|
1008
|
+
const newIndex = activePrompt.opt.choices.realChoices.findIndex(choice => choice.value === previousValue);
|
|
1009
|
+
if (newIndex >= 0) {
|
|
1010
|
+
activePrompt.selected = newIndex;
|
|
1011
|
+
} else if (activePrompt.selected >= activePrompt.opt.choices.realLength) {
|
|
1012
|
+
activePrompt.selected = Math.max(activePrompt.opt.choices.realLength - 1, 0);
|
|
1013
|
+
}
|
|
1014
|
+
} else if (activePrompt.selected >= activePrompt.opt.choices.realLength) {
|
|
1015
|
+
activePrompt.selected = Math.max(activePrompt.opt.choices.realLength - 1, 0);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
if (error) {
|
|
1019
|
+
if (this.currentPromptContext === 'selection') {
|
|
1020
|
+
activePrompt.opt.message = `请选择要切换的供应商 (总计 ${providers.length} 个,状态检测失败,使用默认配置):`;
|
|
1021
|
+
} else if (this.currentPromptContext === 'manage') {
|
|
1022
|
+
activePrompt.opt.message = `选择供应商或操作 (总计 ${providers.length} 个,状态检测失败,使用默认配置):`;
|
|
1023
|
+
}
|
|
1024
|
+
} else {
|
|
1025
|
+
if (this.currentPromptContext === 'selection') {
|
|
1026
|
+
activePrompt.opt.message = `请选择要切换的供应商 (总计 ${providers.length} 个):`;
|
|
1027
|
+
} else if (this.currentPromptContext === 'manage') {
|
|
1028
|
+
activePrompt.opt.message = `选择供应商或操作 (总计 ${providers.length} 个):`;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
activePrompt.render();
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
_applyIncrementalStatus(providerName, status, refreshToken) {
|
|
1036
|
+
if (refreshToken && this.activeStatusRefresh !== refreshToken) {
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
if (this.currentPromptContext !== 'selection' && this.currentPromptContext !== 'manage') {
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
const activePrompt = this.activePrompt?.promise?.ui?.activePrompt;
|
|
1044
|
+
if (!activePrompt || activePrompt.status === 'answered') {
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
const includeActions = this.currentPromptContext === 'manage';
|
|
1049
|
+
const providers = this.configManager.listProviders();
|
|
1050
|
+
const statusMap = this.latestStatusMap || {};
|
|
1051
|
+
const updatedChoicesBase = this.createProviderChoices(providers, includeActions, statusMap);
|
|
1052
|
+
const updatedChoices = [...updatedChoicesBase];
|
|
1053
|
+
|
|
1054
|
+
if (!includeActions) {
|
|
1055
|
+
updatedChoices.push(
|
|
1056
|
+
new inquirer.Separator(),
|
|
1057
|
+
{ name: `${UIHelper.icons.add} 添加新供应商`, value: '__ADD__' },
|
|
1058
|
+
{ name: `${UIHelper.icons.list} 供应商管理 (编辑/删除)`, value: '__MANAGE__' },
|
|
1059
|
+
{ name: `${UIHelper.icons.config} 打开配置文件`, value: '__OPEN_CONFIG__' },
|
|
1060
|
+
{ name: `${UIHelper.icons.error} 退出`, value: '__EXIT__' }
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
const previousValue = (() => {
|
|
1065
|
+
try {
|
|
1066
|
+
return activePrompt.opt.choices?.getChoice(activePrompt.selected)?.value ?? null;
|
|
1067
|
+
} catch (err) {
|
|
1068
|
+
return null;
|
|
1069
|
+
}
|
|
1070
|
+
})();
|
|
1071
|
+
|
|
1072
|
+
activePrompt.opt.choices = new Choices(updatedChoices, activePrompt.answers);
|
|
1073
|
+
|
|
1074
|
+
if (previousValue != null) {
|
|
1075
|
+
const newIndex = activePrompt.opt.choices.realChoices.findIndex(choice => choice.value === previousValue);
|
|
1076
|
+
if (newIndex >= 0) {
|
|
1077
|
+
activePrompt.selected = newIndex;
|
|
1078
|
+
} else if (activePrompt.selected >= activePrompt.opt.choices.realLength) {
|
|
1079
|
+
activePrompt.selected = Math.max(activePrompt.opt.choices.realLength - 1, 0);
|
|
1080
|
+
}
|
|
1081
|
+
} else if (activePrompt.selected >= activePrompt.opt.choices.realLength) {
|
|
1082
|
+
activePrompt.selected = Math.max(activePrompt.opt.choices.realLength - 1, 0);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
if (this.currentPromptContext === 'selection') {
|
|
1086
|
+
activePrompt.opt.message = `请选择要切换的供应商 (总计 ${providers.length} 个):`;
|
|
1087
|
+
} else if (this.currentPromptContext === 'manage') {
|
|
1088
|
+
activePrompt.opt.message = `选择供应商或操作 (总计 ${providers.length} 个):`;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
activePrompt.render();
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
async handleManageAction(action) {
|
|
1095
|
+
switch (action) {
|
|
1096
|
+
case 'back':
|
|
1097
|
+
return await this.showProviderSelection();
|
|
1098
|
+
case 'exit':
|
|
1099
|
+
Logger.info('👋 再见!');
|
|
1100
|
+
process.exit(0);
|
|
1101
|
+
default:
|
|
1102
|
+
// 如果选择的是供应商名称,显示该供应商的详细信息
|
|
1103
|
+
return await this.showProviderDetails(action);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
async showProviderDetails(providerName) {
|
|
1108
|
+
let escListener;
|
|
1109
|
+
try {
|
|
1110
|
+
const provider = await this.validateProvider(providerName);
|
|
1111
|
+
this.clearScreen();
|
|
1112
|
+
|
|
1113
|
+
console.log(UIHelper.createTitle('供应商详情', UIHelper.icons.info));
|
|
1114
|
+
console.log();
|
|
1115
|
+
console.log(UIHelper.createHintLine([
|
|
1116
|
+
['↑ / ↓', '选择操作'],
|
|
1117
|
+
['Enter', '确认'],
|
|
1118
|
+
['ESC', '返回管理列表']
|
|
1119
|
+
]));
|
|
1120
|
+
console.log();
|
|
1121
|
+
|
|
1122
|
+
const authModeDisplay = {
|
|
1123
|
+
api_key: '通用API密钥模式',
|
|
1124
|
+
auth_token: '认证令牌模式',
|
|
1125
|
+
oauth_token: 'OAuth令牌模式'
|
|
1126
|
+
};
|
|
1127
|
+
|
|
1128
|
+
const details = [
|
|
1129
|
+
['供应商名称', provider.name],
|
|
1130
|
+
['显示名称', provider.displayName],
|
|
1131
|
+
['认证模式', authModeDisplay[provider.authMode] || provider.authMode],
|
|
1132
|
+
];
|
|
1133
|
+
|
|
1134
|
+
// 如果是 api_key 模式,添加 tokenType 信息
|
|
1135
|
+
if (provider.authMode === 'api_key' && provider.tokenType) {
|
|
1136
|
+
const tokenTypeDisplay = provider.tokenType === 'auth_token' ? 'ANTHROPIC_AUTH_TOKEN' : 'ANTHROPIC_API_KEY';
|
|
1137
|
+
details.push(['Token类型', tokenTypeDisplay]);
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
// 继续添加其他信息
|
|
1141
|
+
details.push(
|
|
1142
|
+
['基础URL', provider.baseUrl || (provider.authMode === 'oauth_token' ? '✨ 官方默认服务器' : '⚠️ 未设置')],
|
|
1143
|
+
['认证令牌', provider.authToken || '未设置'],
|
|
1144
|
+
['主模型', provider.models?.primary || '未设置'],
|
|
1145
|
+
['快速模型', provider.models?.smallFast || '未设置'],
|
|
1146
|
+
['创建时间', UIHelper.formatTime(provider.createdAt)],
|
|
1147
|
+
['最后使用', UIHelper.formatTime(provider.lastUsed)],
|
|
1148
|
+
['当前状态', provider.current ? '✅ 使用中' : '⚫ 未使用'],
|
|
1149
|
+
['使用次数', provider.usageCount || 0]
|
|
1150
|
+
);
|
|
1151
|
+
|
|
1152
|
+
console.log(UIHelper.createTable(['项目', '信息'], details));
|
|
1153
|
+
console.log();
|
|
1154
|
+
|
|
1155
|
+
if (provider.launchArgs && provider.launchArgs.length > 0) {
|
|
1156
|
+
console.log(UIHelper.createCard('默认启动参数', provider.launchArgs.join(', '), UIHelper.icons.settings));
|
|
1157
|
+
console.log();
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
// 设置 ESC 键监听
|
|
1161
|
+
escListener = this.createESCListener(() => {
|
|
1162
|
+
Logger.info('返回管理列表');
|
|
1163
|
+
this.showManageMenu();
|
|
1164
|
+
}, '返回管理列表');
|
|
1165
|
+
|
|
1166
|
+
let answer;
|
|
1167
|
+
try {
|
|
1168
|
+
answer = await this.prompt([
|
|
1169
|
+
{
|
|
1170
|
+
type: 'list',
|
|
1171
|
+
name: 'action',
|
|
1172
|
+
message: '选择操作:',
|
|
1173
|
+
choices: [
|
|
1174
|
+
{ name: `${UIHelper.icons.launch} 立即启动`, value: 'launch' },
|
|
1175
|
+
{ name: `${UIHelper.icons.edit} 编辑供应商`, value: 'edit' },
|
|
1176
|
+
{ name: `${UIHelper.icons.delete} 删除供应商`, value: 'remove' },
|
|
1177
|
+
{ name: `${UIHelper.icons.back} 返回管理列表`, value: 'back' }
|
|
1178
|
+
]
|
|
1179
|
+
}
|
|
1180
|
+
]);
|
|
1181
|
+
} catch (error) {
|
|
1182
|
+
this.removeESCListener(escListener);
|
|
1183
|
+
if (this.isEscCancelled(error)) {
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
throw error;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
switch (answer.action) {
|
|
1190
|
+
case 'back':
|
|
1191
|
+
// 移除 ESC 键监听
|
|
1192
|
+
this.removeESCListener(escListener);
|
|
1193
|
+
return await this.showManageMenu();
|
|
1194
|
+
case 'edit':
|
|
1195
|
+
// 移除 ESC 键监听
|
|
1196
|
+
this.removeESCListener(escListener);
|
|
1197
|
+
return await this.editProvider(providerName);
|
|
1198
|
+
case 'remove':
|
|
1199
|
+
// 移除 ESC 键监听
|
|
1200
|
+
this.removeESCListener(escListener);
|
|
1201
|
+
return await this.removeProvider(providerName);
|
|
1202
|
+
case 'launch':
|
|
1203
|
+
// 移除 ESC 键监听
|
|
1204
|
+
this.removeESCListener(escListener);
|
|
1205
|
+
return await this.showLaunchArgsSelection(providerName);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
} catch (error) {
|
|
1209
|
+
// 移除 ESC 键监听
|
|
1210
|
+
this.removeESCListener(escListener);
|
|
1211
|
+
await this.handleError(error, '显示供应商详情');
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
async editProvider(providerName) {
|
|
1216
|
+
let escListener;
|
|
1217
|
+
try {
|
|
1218
|
+
await this.configManager.load();
|
|
1219
|
+
const provider = this.configManager.getProvider(providerName);
|
|
1220
|
+
this.clearScreen();
|
|
1221
|
+
|
|
1222
|
+
if (!provider) {
|
|
1223
|
+
Logger.error(`供应商 '${providerName}' 不存在`);
|
|
1224
|
+
return await this.showManageMenu();
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
// 设置 ESC 键监听
|
|
1228
|
+
escListener = this.createESCListener(() => {
|
|
1229
|
+
Logger.info('取消编辑供应商');
|
|
1230
|
+
this.showManageMenu();
|
|
1231
|
+
}, '取消编辑');
|
|
1232
|
+
|
|
1233
|
+
let answers;
|
|
1234
|
+
try {
|
|
1235
|
+
answers = await this.prompt([
|
|
1236
|
+
{
|
|
1237
|
+
type: 'input',
|
|
1238
|
+
name: 'name',
|
|
1239
|
+
message: '请输入供应商名称 (用于命令行):',
|
|
1240
|
+
default: provider.name,
|
|
1241
|
+
validate: (input) => {
|
|
1242
|
+
const error = validator.validateName(input);
|
|
1243
|
+
if (error) return error;
|
|
1244
|
+
return true;
|
|
1245
|
+
}
|
|
1246
|
+
},
|
|
1247
|
+
{
|
|
1248
|
+
type: 'input',
|
|
1249
|
+
name: 'displayName',
|
|
1250
|
+
message: '显示名称:',
|
|
1251
|
+
default: provider.displayName,
|
|
1252
|
+
prefillDefault: true
|
|
1253
|
+
},
|
|
1254
|
+
{
|
|
1255
|
+
type: 'list',
|
|
1256
|
+
name: 'authMode',
|
|
1257
|
+
message: '认证模式:',
|
|
1258
|
+
choices: [
|
|
1259
|
+
{ name: '🔑 通用API密钥模式 - 支持 ANTHROPIC_API_KEY 和 ANTHROPIC_AUTH_TOKEN', value: 'api_key' },
|
|
1260
|
+
{ name: '🔐 认证令牌模式 (仅 ANTHROPIC_AUTH_TOKEN) - 适用于某些服务商', value: 'auth_token' },
|
|
1261
|
+
{ name: '🌐 OAuth令牌模式 (CLAUDE_CODE_OAUTH_TOKEN) - 适用于官方Claude Code', value: 'oauth_token' }
|
|
1262
|
+
],
|
|
1263
|
+
default: provider.authMode || 'api_key'
|
|
1264
|
+
},
|
|
1265
|
+
{
|
|
1266
|
+
type: 'list',
|
|
1267
|
+
name: 'tokenType',
|
|
1268
|
+
message: 'Token类型:',
|
|
1269
|
+
choices: [
|
|
1270
|
+
{ name: '🔑 ANTHROPIC_API_KEY - 通用API密钥', value: 'api_key' },
|
|
1271
|
+
{ name: '🔐 ANTHROPIC_AUTH_TOKEN - 认证令牌', value: 'auth_token' }
|
|
1272
|
+
],
|
|
1273
|
+
default: provider.tokenType || 'api_key',
|
|
1274
|
+
when: (answers) => answers.authMode === 'api_key'
|
|
1275
|
+
},
|
|
1276
|
+
{
|
|
1277
|
+
type: 'input',
|
|
1278
|
+
name: 'baseUrl',
|
|
1279
|
+
message: '基础URL:',
|
|
1280
|
+
default: provider.baseUrl,
|
|
1281
|
+
prefillDefault: true,
|
|
1282
|
+
when: (answers) => answers.authMode === 'api_key' || answers.authMode === 'auth_token'
|
|
1283
|
+
},
|
|
1284
|
+
{
|
|
1285
|
+
type: 'input',
|
|
1286
|
+
name: 'authToken',
|
|
1287
|
+
message: (answers) => {
|
|
1288
|
+
switch (answers.authMode) {
|
|
1289
|
+
case 'api_key':
|
|
1290
|
+
const tokenTypeLabel = answers.tokenType === 'auth_token' ? 'ANTHROPIC_AUTH_TOKEN' : 'ANTHROPIC_API_KEY';
|
|
1291
|
+
return `Token (${tokenTypeLabel}):`;
|
|
1292
|
+
case 'auth_token':
|
|
1293
|
+
return '认证令牌 (ANTHROPIC_AUTH_TOKEN):';
|
|
1294
|
+
case 'oauth_token':
|
|
1295
|
+
return 'OAuth令牌 (CLAUDE_CODE_OAUTH_TOKEN):';
|
|
1296
|
+
default:
|
|
1297
|
+
return '认证令牌:';
|
|
1298
|
+
}
|
|
1299
|
+
},
|
|
1300
|
+
default: provider.authToken,
|
|
1301
|
+
prefillDefault: true
|
|
1302
|
+
},
|
|
1303
|
+
{
|
|
1304
|
+
type: 'input',
|
|
1305
|
+
name: 'primaryModel',
|
|
1306
|
+
message: '主模型 (ANTHROPIC_MODEL):',
|
|
1307
|
+
default: provider.models?.primary || '',
|
|
1308
|
+
prefillDefault: true,
|
|
1309
|
+
allowEmpty: true,
|
|
1310
|
+
validate: (input) => {
|
|
1311
|
+
const error = validator.validateModel(input);
|
|
1312
|
+
if (error) return error;
|
|
1313
|
+
return true;
|
|
1314
|
+
}
|
|
1315
|
+
},
|
|
1316
|
+
{
|
|
1317
|
+
type: 'input',
|
|
1318
|
+
name: 'smallFastModel',
|
|
1319
|
+
message: '快速模型 (ANTHROPIC_SMALL_FAST_MODEL):',
|
|
1320
|
+
default: provider.models?.smallFast || '',
|
|
1321
|
+
prefillDefault: true,
|
|
1322
|
+
allowEmpty: true,
|
|
1323
|
+
validate: (input) => {
|
|
1324
|
+
const error = validator.validateModel(input);
|
|
1325
|
+
if (error) return error;
|
|
1326
|
+
return true;
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
]);
|
|
1330
|
+
} catch (error) {
|
|
1331
|
+
this.removeESCListener(escListener);
|
|
1332
|
+
if (this.isEscCancelled(error)) {
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
throw error;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
const originalName = provider.name;
|
|
1339
|
+
const newName = answers.name;
|
|
1340
|
+
|
|
1341
|
+
// 处理重命名逻辑
|
|
1342
|
+
if (newName !== originalName) {
|
|
1343
|
+
await this.configManager.ensureLoaded();
|
|
1344
|
+
const providersMap = this.configManager.config.providers;
|
|
1345
|
+
|
|
1346
|
+
if (providersMap[newName] && providersMap[newName] !== provider) {
|
|
1347
|
+
Logger.error(`供应商名称 '${newName}' 已存在,请使用其他名称`);
|
|
1348
|
+
return await this.showManageMenu();
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
providersMap[newName] = {
|
|
1352
|
+
...provider,
|
|
1353
|
+
name: newName
|
|
1354
|
+
};
|
|
1355
|
+
|
|
1356
|
+
delete providersMap[originalName];
|
|
1357
|
+
|
|
1358
|
+
if (this.configManager.config.currentProvider === originalName) {
|
|
1359
|
+
this.configManager.config.currentProvider = newName;
|
|
1360
|
+
providersMap[newName].current = true;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
provider = providersMap[newName];
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
// 更新供应商配置
|
|
1367
|
+
provider.displayName = answers.displayName || newName;
|
|
1368
|
+
provider.baseUrl = answers.baseUrl;
|
|
1369
|
+
provider.authToken = answers.authToken;
|
|
1370
|
+
provider.authMode = answers.authMode;
|
|
1371
|
+
if (answers.tokenType) {
|
|
1372
|
+
provider.tokenType = answers.tokenType; // 仅在 authMode 为 'api_key' 时使用
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
// 更新模型配置
|
|
1376
|
+
if (!provider.models) {
|
|
1377
|
+
provider.models = {};
|
|
1378
|
+
}
|
|
1379
|
+
provider.models.primary = answers.primaryModel || null;
|
|
1380
|
+
provider.models.smallFast = answers.smallFastModel || null;
|
|
1381
|
+
|
|
1382
|
+
await this.configManager.save();
|
|
1383
|
+
Logger.success(`供应商 '${newName}' 已更新`);
|
|
1384
|
+
|
|
1385
|
+
// 移除 ESC 键监听
|
|
1386
|
+
this.removeESCListener(escListener);
|
|
1387
|
+
return await this.showManageMenu();
|
|
1388
|
+
|
|
1389
|
+
} catch (error) {
|
|
1390
|
+
// 移除 ESC 键监听
|
|
1391
|
+
this.removeESCListener(escListener);
|
|
1392
|
+
Logger.error(`编辑供应商失败: ${error.message}`);
|
|
1393
|
+
throw error;
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
async removeProvider(providerName) {
|
|
1398
|
+
let escListener;
|
|
1399
|
+
try {
|
|
1400
|
+
await this.configManager.load();
|
|
1401
|
+
const provider = this.configManager.getProvider(providerName);
|
|
1402
|
+
this.clearScreen();
|
|
1403
|
+
|
|
1404
|
+
if (!provider) {
|
|
1405
|
+
Logger.error(`供应商 '${providerName}' 不存在`);
|
|
1406
|
+
return await this.showManageMenu();
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
// 设置 ESC 键监听
|
|
1410
|
+
escListener = this.createESCListener(() => {
|
|
1411
|
+
Logger.info('取消删除供应商');
|
|
1412
|
+
this.showManageMenu();
|
|
1413
|
+
}, '取消删除');
|
|
1414
|
+
|
|
1415
|
+
let confirm;
|
|
1416
|
+
try {
|
|
1417
|
+
confirm = await this.prompt([
|
|
1418
|
+
{
|
|
1419
|
+
type: 'confirm',
|
|
1420
|
+
name: 'confirmed',
|
|
1421
|
+
message: `确定要删除供应商 '${providerName}' 吗?`,
|
|
1422
|
+
default: false
|
|
1423
|
+
}
|
|
1424
|
+
]);
|
|
1425
|
+
} catch (error) {
|
|
1426
|
+
this.removeESCListener(escListener);
|
|
1427
|
+
if (this.isEscCancelled(error)) {
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
throw error;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
if (confirm.confirmed) {
|
|
1434
|
+
await this.configManager.removeProvider(providerName);
|
|
1435
|
+
Logger.success(`供应商 '${providerName}' 已删除`);
|
|
1436
|
+
} else {
|
|
1437
|
+
Logger.info('删除操作已取消');
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
// 移除 ESC 键监听
|
|
1441
|
+
this.removeESCListener(escListener);
|
|
1442
|
+
return await this.showManageMenu();
|
|
1443
|
+
|
|
1444
|
+
} catch (error) {
|
|
1445
|
+
// 移除 ESC 键监听
|
|
1446
|
+
this.removeESCListener(escListener);
|
|
1447
|
+
Logger.error(`删除供应商失败: ${error.message}`);
|
|
1448
|
+
throw error;
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
async function switchCommand(providerName) {
|
|
1454
|
+
const switcher = new EnvSwitcher();
|
|
1455
|
+
|
|
1456
|
+
try {
|
|
1457
|
+
if (providerName) {
|
|
1458
|
+
await switcher.showLaunchArgsSelection(providerName);
|
|
1459
|
+
} else {
|
|
1460
|
+
await switcher.showProviderSelection();
|
|
1461
|
+
}
|
|
1462
|
+
} finally {
|
|
1463
|
+
// 确保资源清理
|
|
1464
|
+
switcher.destroy();
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
async function editCommand(providerName) {
|
|
1469
|
+
const switcher = new EnvSwitcher();
|
|
1470
|
+
|
|
1471
|
+
try {
|
|
1472
|
+
await switcher.editProvider(providerName);
|
|
1473
|
+
} finally {
|
|
1474
|
+
// 确保资源清理
|
|
1475
|
+
switcher.destroy();
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
module.exports = { switchCommand, editCommand, EnvSwitcher };
|