@pikecode/api-key-manager 1.1.3 → 1.2.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/README.md +35 -5
- package/package.json +1 -1
- package/src/commands/add/codexImporter.js +48 -0
- package/src/commands/add/prompts.js +262 -0
- package/src/commands/add/providerSaver.js +75 -0
- package/src/commands/add/summaryPrinter.js +29 -0
- package/src/commands/add.js +10 -390
- package/src/commands/edit.js +1 -1
- package/src/commands/remove.js +63 -18
- package/src/config.js +117 -48
- package/src/constants/ui.js +108 -0
- package/src/utils/validator.js +2 -15
package/src/commands/add.js
CHANGED
|
@@ -11,10 +11,10 @@ const { validator } = require('../utils/validator');
|
|
|
11
11
|
const { Logger } = require('../utils/logger');
|
|
12
12
|
const { UIHelper } = require('../utils/ui-helper');
|
|
13
13
|
const { BaseCommand } = require('./BaseCommand');
|
|
14
|
-
const {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
} = require('
|
|
14
|
+
const { promptProviderInfo, promptLaunchArgs, promptModelConfig, promptCodexLaunchArgs } = require('./add/prompts');
|
|
15
|
+
const { importCodexConfig } = require('./add/codexImporter');
|
|
16
|
+
const { saveProvider } = require('./add/providerSaver');
|
|
17
|
+
const { printProviderSummary } = require('./add/summaryPrinter');
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* 供应商添加器类
|
|
@@ -62,134 +62,7 @@ class ProviderAdder extends BaseCommand {
|
|
|
62
62
|
|
|
63
63
|
async addCustomProvider() {
|
|
64
64
|
try {
|
|
65
|
-
const answers = await this
|
|
66
|
-
{
|
|
67
|
-
type: 'list',
|
|
68
|
-
name: 'ideName',
|
|
69
|
-
message: '选择要管理的 IDE:',
|
|
70
|
-
choices: [
|
|
71
|
-
{ name: 'Claude Code (Anthropic)', value: 'claude' },
|
|
72
|
-
{ name: 'Codex CLI (OpenAI)', value: 'codex' }
|
|
73
|
-
],
|
|
74
|
-
default: this.presetIdeName || 'claude',
|
|
75
|
-
when: () => !this.presetIdeName
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
type: 'list',
|
|
79
|
-
name: 'importFromExisting',
|
|
80
|
-
message: '是否从现有 Codex 配置导入?',
|
|
81
|
-
choices: [
|
|
82
|
-
{ name: '从 ~/.codex 导入现有配置', value: 'import' },
|
|
83
|
-
{ name: '手动输入配置', value: 'manual' }
|
|
84
|
-
],
|
|
85
|
-
default: 'import',
|
|
86
|
-
when: (answers) => (answers.ideName || this.presetIdeName) === 'codex'
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
type: 'input',
|
|
90
|
-
name: 'name',
|
|
91
|
-
message: '请输入供应商名称:',
|
|
92
|
-
validate: (input) => {
|
|
93
|
-
const error = validator.validateName(input);
|
|
94
|
-
if (error) return error;
|
|
95
|
-
return true;
|
|
96
|
-
}
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
type: 'list',
|
|
100
|
-
name: 'authMode',
|
|
101
|
-
message: '选择认证模式:',
|
|
102
|
-
choices: [
|
|
103
|
-
{ name: '🔑 ANTHROPIC_API_KEY - 大多数第三方代理使用', value: 'api_key' },
|
|
104
|
-
{ name: '🔐 ANTHROPIC_AUTH_TOKEN - 部分服务商使用', value: 'auth_token' }
|
|
105
|
-
],
|
|
106
|
-
default: 'api_key',
|
|
107
|
-
when: (answers) => (answers.ideName || this.presetIdeName) !== 'codex'
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
type: 'input',
|
|
111
|
-
name: 'baseUrl',
|
|
112
|
-
message: '请输入 API 基础URL (ANTHROPIC_BASE_URL):',
|
|
113
|
-
validate: (input) => {
|
|
114
|
-
if (!input) return 'API 基础URL不能为空';
|
|
115
|
-
const error = validator.validateUrl(input);
|
|
116
|
-
if (error) return error;
|
|
117
|
-
return true;
|
|
118
|
-
},
|
|
119
|
-
when: (answers) => (answers.ideName || this.presetIdeName) !== 'codex'
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
type: 'input',
|
|
123
|
-
name: 'authToken',
|
|
124
|
-
message: (answers) => {
|
|
125
|
-
const envVar = answers.authMode === 'auth_token' ? 'ANTHROPIC_AUTH_TOKEN' : 'ANTHROPIC_API_KEY';
|
|
126
|
-
return `请输入 Token (${envVar}):`;
|
|
127
|
-
},
|
|
128
|
-
validate: (input) => {
|
|
129
|
-
const error = validator.validateToken(input);
|
|
130
|
-
if (error) return error;
|
|
131
|
-
return true;
|
|
132
|
-
},
|
|
133
|
-
when: (answers) => (answers.ideName || this.presetIdeName) !== 'codex'
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
type: 'input',
|
|
137
|
-
name: 'baseUrl',
|
|
138
|
-
message: '请输入 OpenAI API 基础URL (如使用官方API可留空):',
|
|
139
|
-
default: '',
|
|
140
|
-
validate: (input) => {
|
|
141
|
-
if (!input) return true;
|
|
142
|
-
const error = validator.validateUrl(input);
|
|
143
|
-
if (error) return error;
|
|
144
|
-
return true;
|
|
145
|
-
},
|
|
146
|
-
when: (answers) => (answers.ideName || this.presetIdeName) === 'codex' && answers.importFromExisting === 'manual'
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
type: 'input',
|
|
150
|
-
name: 'authToken',
|
|
151
|
-
message: '请输入 OpenAI API Key (OPENAI_API_KEY):',
|
|
152
|
-
validate: (input) => {
|
|
153
|
-
if (!input) return 'API Key 不能为空';
|
|
154
|
-
const error = validator.validateToken(input);
|
|
155
|
-
if (error) return error;
|
|
156
|
-
return true;
|
|
157
|
-
},
|
|
158
|
-
when: (answers) => (answers.ideName || this.presetIdeName) === 'codex' && answers.importFromExisting === 'manual'
|
|
159
|
-
},
|
|
160
|
-
{
|
|
161
|
-
type: 'confirm',
|
|
162
|
-
name: 'setAsDefault',
|
|
163
|
-
message: '是否设置为当前供应商?',
|
|
164
|
-
default: true
|
|
165
|
-
},
|
|
166
|
-
{
|
|
167
|
-
type: 'confirm',
|
|
168
|
-
name: 'configureLaunchArgs',
|
|
169
|
-
message: '是否配置启动参数?',
|
|
170
|
-
default: false,
|
|
171
|
-
when: (answers) => (answers.ideName || this.presetIdeName) !== 'codex'
|
|
172
|
-
},
|
|
173
|
-
{
|
|
174
|
-
type: 'confirm',
|
|
175
|
-
name: 'configureCodexLaunchArgs',
|
|
176
|
-
message: '是否配置 Codex 启动参数?',
|
|
177
|
-
default: false,
|
|
178
|
-
when: (answers) => (answers.ideName || this.presetIdeName) === 'codex'
|
|
179
|
-
},
|
|
180
|
-
{
|
|
181
|
-
type: 'confirm',
|
|
182
|
-
name: 'configureModels',
|
|
183
|
-
message: '是否配置模型参数?',
|
|
184
|
-
default: false,
|
|
185
|
-
when: (answers) => (answers.ideName || this.presetIdeName) !== 'codex'
|
|
186
|
-
}
|
|
187
|
-
], '取消添加', () => {
|
|
188
|
-
Logger.info('取消添加供应商');
|
|
189
|
-
// 使用CommandRegistry避免循环引用
|
|
190
|
-
const { registry } = require('../CommandRegistry');
|
|
191
|
-
registry.executeCommand('switch');
|
|
192
|
-
});
|
|
65
|
+
const answers = await promptProviderInfo(this);
|
|
193
66
|
|
|
194
67
|
// 如果是预设的 ideName,设置到 answers 中
|
|
195
68
|
if (!answers.ideName && this.presetIdeName) {
|
|
@@ -202,7 +75,7 @@ class ProviderAdder extends BaseCommand {
|
|
|
202
75
|
|
|
203
76
|
// 从现有配置导入
|
|
204
77
|
if (answers.importFromExisting === 'import') {
|
|
205
|
-
const importedConfig = await
|
|
78
|
+
const importedConfig = await importCodexConfig();
|
|
206
79
|
if (importedConfig) {
|
|
207
80
|
answers.authToken = importedConfig.apiKey;
|
|
208
81
|
answers.baseUrl = importedConfig.baseUrl;
|
|
@@ -229,62 +102,15 @@ class ProviderAdder extends BaseCommand {
|
|
|
229
102
|
|
|
230
103
|
// Codex 启动参数配置
|
|
231
104
|
if (answers.configureCodexLaunchArgs) {
|
|
232
|
-
answers.launchArgs = await this
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
await this.saveProvider(answers);
|
|
237
|
-
} catch (error) {
|
|
238
|
-
if (this.isEscCancelled(error)) {
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
throw error;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
async saveProvider(answers) {
|
|
246
|
-
try {
|
|
247
|
-
await this.configManager.load();
|
|
248
|
-
|
|
249
|
-
if (this.configManager.getProvider(answers.name)) {
|
|
250
|
-
const shouldOverwrite = await this.confirmOverwrite(answers.name);
|
|
251
|
-
if (!shouldOverwrite) {
|
|
252
|
-
Logger.warning('操作已取消');
|
|
253
|
-
return;
|
|
105
|
+
answers.launchArgs = await promptCodexLaunchArgs(this);
|
|
254
106
|
}
|
|
255
107
|
}
|
|
256
108
|
|
|
257
|
-
|
|
258
|
-
? (Array.isArray(answers.launchArgs) ? answers.launchArgs : [])
|
|
259
|
-
: (answers.configureLaunchArgs ? await this.promptLaunchArgsSelection() : []);
|
|
260
|
-
|
|
261
|
-
const modelConfig = answers.configureModels
|
|
262
|
-
? await this.promptModelConfiguration()
|
|
263
|
-
: { primaryModel: null, smallFastModel: null };
|
|
264
|
-
|
|
265
|
-
await this.configManager.addProvider(answers.name, {
|
|
266
|
-
displayName: answers.displayName || answers.name,
|
|
267
|
-
ideName: answers.ideName || 'claude',
|
|
268
|
-
baseUrl: answers.baseUrl,
|
|
269
|
-
authToken: answers.authToken,
|
|
270
|
-
authMode: answers.authMode,
|
|
271
|
-
codexFiles: answers.codexFiles || null,
|
|
272
|
-
launchArgs,
|
|
273
|
-
primaryModel: modelConfig.primaryModel,
|
|
274
|
-
smallFastModel: modelConfig.smallFastModel,
|
|
275
|
-
setAsDefault: answers.setAsDefault
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
this.printProviderSummary(answers, launchArgs, modelConfig);
|
|
279
|
-
await this.pauseBeforeReturn();
|
|
280
|
-
|
|
281
|
-
const { registry } = require('../CommandRegistry');
|
|
282
|
-
return await registry.executeCommand('switch');
|
|
109
|
+
await saveProvider(this, answers);
|
|
283
110
|
} catch (error) {
|
|
284
111
|
if (this.isEscCancelled(error)) {
|
|
285
112
|
return;
|
|
286
113
|
}
|
|
287
|
-
Logger.error(`添加供应商失败: ${error.message}`);
|
|
288
114
|
throw error;
|
|
289
115
|
}
|
|
290
116
|
}
|
|
@@ -300,8 +126,8 @@ class ProviderAdder extends BaseCommand {
|
|
|
300
126
|
}
|
|
301
127
|
], '取消覆盖', () => {
|
|
302
128
|
Logger.info('取消覆盖供应商');
|
|
303
|
-
const {
|
|
304
|
-
|
|
129
|
+
const { registry } = require('../CommandRegistry');
|
|
130
|
+
registry.executeCommand('switch');
|
|
305
131
|
});
|
|
306
132
|
|
|
307
133
|
return overwrite;
|
|
@@ -310,212 +136,6 @@ class ProviderAdder extends BaseCommand {
|
|
|
310
136
|
}
|
|
311
137
|
}
|
|
312
138
|
|
|
313
|
-
async promptLaunchArgsSelection() {
|
|
314
|
-
console.log(UIHelper.createTitle('配置启动参数', UIHelper.icons.settings));
|
|
315
|
-
console.log();
|
|
316
|
-
console.log(UIHelper.createTooltip('选择要使用的启动参数'));
|
|
317
|
-
console.log();
|
|
318
|
-
console.log(UIHelper.createStepIndicator(2, 2, '可选: 配置启动参数'));
|
|
319
|
-
console.log(UIHelper.createHintLine([
|
|
320
|
-
['空格', '切换选中'],
|
|
321
|
-
['A', '全选'],
|
|
322
|
-
['I', '反选'],
|
|
323
|
-
['Enter', '确认选择'],
|
|
324
|
-
['ESC', '跳过配置']
|
|
325
|
-
]));
|
|
326
|
-
console.log();
|
|
327
|
-
|
|
328
|
-
const result = await this.promptWithESCAndDefault([
|
|
329
|
-
{
|
|
330
|
-
type: 'checkbox',
|
|
331
|
-
name: 'launchArgs',
|
|
332
|
-
message: '请选择启动参数:',
|
|
333
|
-
choices: validator.getAvailableLaunchArgs().map(arg => ({
|
|
334
|
-
name: `${arg.name} - ${arg.description}`,
|
|
335
|
-
value: arg.name,
|
|
336
|
-
checked: false
|
|
337
|
-
}))
|
|
338
|
-
}
|
|
339
|
-
], '跳过配置', () => {
|
|
340
|
-
Logger.info('跳过启动参数配置');
|
|
341
|
-
}, { launchArgs: [] });
|
|
342
|
-
|
|
343
|
-
return result.launchArgs;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
async promptModelConfiguration() {
|
|
347
|
-
console.log(UIHelper.createTitle('配置模型参数', UIHelper.icons.settings));
|
|
348
|
-
console.log();
|
|
349
|
-
console.log(UIHelper.createTooltip('配置主模型和快速模型(可选)'));
|
|
350
|
-
console.log();
|
|
351
|
-
console.log(UIHelper.createStepIndicator(2, 2, '可选: 配置模型参数'));
|
|
352
|
-
console.log(UIHelper.createHintLine([
|
|
353
|
-
['Enter', '确认输入'],
|
|
354
|
-
['ESC', '跳过配置']
|
|
355
|
-
]));
|
|
356
|
-
console.log();
|
|
357
|
-
|
|
358
|
-
const responses = await this.promptWithESCAndDefault([
|
|
359
|
-
{
|
|
360
|
-
type: 'input',
|
|
361
|
-
name: 'primaryModel',
|
|
362
|
-
message: '主模型 (ANTHROPIC_MODEL):',
|
|
363
|
-
default: '',
|
|
364
|
-
validate: (input) => {
|
|
365
|
-
const error = validator.validateModel(input);
|
|
366
|
-
if (error) return error;
|
|
367
|
-
return true;
|
|
368
|
-
}
|
|
369
|
-
},
|
|
370
|
-
{
|
|
371
|
-
type: 'input',
|
|
372
|
-
name: 'smallFastModel',
|
|
373
|
-
message: '快速模型 (ANTHROPIC_SMALL_FAST_MODEL):',
|
|
374
|
-
default: '',
|
|
375
|
-
validate: (input) => {
|
|
376
|
-
const error = validator.validateModel(input);
|
|
377
|
-
if (error) return error;
|
|
378
|
-
return true;
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
], '跳过配置', () => {
|
|
382
|
-
Logger.info('跳过模型参数配置');
|
|
383
|
-
}, { primaryModel: null, smallFastModel: null });
|
|
384
|
-
|
|
385
|
-
return {
|
|
386
|
-
primaryModel: responses.primaryModel,
|
|
387
|
-
smallFastModel: responses.smallFastModel
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
async importCodexConfig() {
|
|
392
|
-
try {
|
|
393
|
-
const { readCodexFiles, extractBaseUrlFromConfigToml } = require('../utils/codex-files');
|
|
394
|
-
const codexFiles = await readCodexFiles();
|
|
395
|
-
|
|
396
|
-
if (!codexFiles.authJson) {
|
|
397
|
-
return null;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// 解析 auth.json 获取 API Key
|
|
401
|
-
const authData = JSON.parse(codexFiles.authJson);
|
|
402
|
-
const apiKey = authData.api_key || authData.openai_api_key || authData.OPENAI_API_KEY;
|
|
403
|
-
|
|
404
|
-
if (!apiKey) {
|
|
405
|
-
return null;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// 从 config.toml 中读取当前激活 provider 的 base_url
|
|
409
|
-
// 优先从 [model_providers.<key>] section 读取,兼容旧的顶层 api_base_url 格式
|
|
410
|
-
let baseUrl = null;
|
|
411
|
-
if (codexFiles.configToml) {
|
|
412
|
-
baseUrl = extractBaseUrlFromConfigToml(codexFiles.configToml);
|
|
413
|
-
if (!baseUrl) {
|
|
414
|
-
// 兼容旧格式(akm 之前错误写入的顶层字段)
|
|
415
|
-
const legacyMatch = codexFiles.configToml.match(/^api_base_url\s*=\s*["']([^"']+)["']/m);
|
|
416
|
-
if (legacyMatch) baseUrl = legacyMatch[1];
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
Logger.success(`成功从 ${codexFiles.codexHome} 导入配置`);
|
|
421
|
-
return { apiKey, baseUrl };
|
|
422
|
-
} catch (error) {
|
|
423
|
-
Logger.warning(`导入配置失败: ${error.message}`);
|
|
424
|
-
return null;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
async promptCodexLaunchArgsSelection() {
|
|
429
|
-
console.log(UIHelper.createTitle('配置 Codex 启动参数', UIHelper.icons.settings));
|
|
430
|
-
console.log();
|
|
431
|
-
console.log(UIHelper.createTooltip('选择要使用的 Codex 启动参数'));
|
|
432
|
-
console.log();
|
|
433
|
-
console.log(UIHelper.createHintLine([
|
|
434
|
-
['空格', '切换选中'],
|
|
435
|
-
['A', '全选'],
|
|
436
|
-
['I', '反选'],
|
|
437
|
-
['Enter', '确认选择'],
|
|
438
|
-
['ESC', '跳过配置']
|
|
439
|
-
]));
|
|
440
|
-
console.log();
|
|
441
|
-
|
|
442
|
-
try {
|
|
443
|
-
const { getCodexLaunchArgs, checkExclusiveArgs } = require('../utils/launch-args');
|
|
444
|
-
const codexArgs = getCodexLaunchArgs();
|
|
445
|
-
|
|
446
|
-
const result = await this.promptWithESCAndDefault([
|
|
447
|
-
{
|
|
448
|
-
type: 'checkbox',
|
|
449
|
-
name: 'launchArgs',
|
|
450
|
-
message: '请选择 Codex 启动参数:',
|
|
451
|
-
choices: codexArgs.map(arg => ({
|
|
452
|
-
name: `${arg.label} (${arg.name}) - ${arg.description}`,
|
|
453
|
-
value: arg.name,
|
|
454
|
-
checked: arg.checked
|
|
455
|
-
}))
|
|
456
|
-
}
|
|
457
|
-
], '跳过配置', () => {
|
|
458
|
-
Logger.info('跳过 Codex 启动参数配置');
|
|
459
|
-
}, { launchArgs: [] });
|
|
460
|
-
|
|
461
|
-
const { launchArgs } = result;
|
|
462
|
-
|
|
463
|
-
const conflictError = checkExclusiveArgs(launchArgs, codexArgs);
|
|
464
|
-
if (conflictError) {
|
|
465
|
-
Logger.warning(conflictError);
|
|
466
|
-
return await this.promptCodexLaunchArgsSelection();
|
|
467
|
-
}
|
|
468
|
-
return launchArgs;
|
|
469
|
-
} catch (error) {
|
|
470
|
-
throw error;
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
printProviderSummary(answers, launchArgs, modelConfig) {
|
|
475
|
-
const finalDisplayName = answers.displayName || answers.name;
|
|
476
|
-
Logger.success(`供应商 '${finalDisplayName}' 添加成功!`);
|
|
477
|
-
|
|
478
|
-
console.log(chalk.blue('\n配置详情:'));
|
|
479
|
-
console.log(chalk.gray(` 名称: ${answers.name}`));
|
|
480
|
-
console.log(chalk.gray(` 显示名称: ${finalDisplayName}`));
|
|
481
|
-
|
|
482
|
-
if (answers.ideName === 'codex') {
|
|
483
|
-
console.log(chalk.gray(' IDE: Codex CLI'));
|
|
484
|
-
if (answers.baseUrl) {
|
|
485
|
-
console.log(chalk.gray(` OPENAI_BASE_URL: ${answers.baseUrl}`));
|
|
486
|
-
}
|
|
487
|
-
if (answers.authToken) {
|
|
488
|
-
console.log(chalk.gray(` OPENAI_API_KEY: ${answers.authToken}`));
|
|
489
|
-
}
|
|
490
|
-
if (answers.launchArgs && answers.launchArgs.length > 0) {
|
|
491
|
-
console.log(chalk.gray(` 启动参数: ${answers.launchArgs.join(' ')}`));
|
|
492
|
-
}
|
|
493
|
-
console.log(chalk.green('\n🎉 供应商添加完成!正在返回主界面...'));
|
|
494
|
-
return;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
console.log(chalk.gray(` 认证模式: ${AUTH_MODE_DISPLAY_DETAILED[answers.authMode] || answers.authMode}`));
|
|
498
|
-
|
|
499
|
-
if (answers.baseUrl) {
|
|
500
|
-
console.log(chalk.gray(` 基础URL: ${answers.baseUrl}`));
|
|
501
|
-
}
|
|
502
|
-
console.log(chalk.gray(` Token: ${answers.authToken}`));
|
|
503
|
-
|
|
504
|
-
if (launchArgs.length > 0) {
|
|
505
|
-
console.log(chalk.gray(` 启动参数: ${launchArgs.join(' ')}`));
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
if (modelConfig.primaryModel) {
|
|
509
|
-
console.log(chalk.gray(` 主模型: ${modelConfig.primaryModel}`));
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
if (modelConfig.smallFastModel) {
|
|
513
|
-
console.log(chalk.gray(` 快速模型: ${modelConfig.smallFastModel}`));
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
console.log(chalk.green('\n🎉 供应商添加完成!正在返回主界面...'));
|
|
517
|
-
}
|
|
518
|
-
|
|
519
139
|
async pauseBeforeReturn(delay = 1500) {
|
|
520
140
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|
521
141
|
}
|
package/src/commands/edit.js
CHANGED
|
@@ -99,7 +99,7 @@ class ProviderEditor extends BaseCommand {
|
|
|
99
99
|
{
|
|
100
100
|
type: 'input',
|
|
101
101
|
name: 'displayName',
|
|
102
|
-
message: '
|
|
102
|
+
message: '供应商名称:',
|
|
103
103
|
default: providerToEdit.displayName,
|
|
104
104
|
validate: (input) => validator.validateDisplayName(input) || true
|
|
105
105
|
}
|
package/src/commands/remove.js
CHANGED
|
@@ -90,24 +90,15 @@ class ProviderRemover extends BaseCommand {
|
|
|
90
90
|
|
|
91
91
|
console.log(UIHelper.createTitle('删除供应商', UIHelper.icons.delete));
|
|
92
92
|
console.log();
|
|
93
|
-
console.log(UIHelper.createTooltip('
|
|
93
|
+
console.log(UIHelper.createTooltip('选择要删除的供应商(可多选)'));
|
|
94
94
|
console.log();
|
|
95
95
|
|
|
96
96
|
const choices = providers.map(provider => ({
|
|
97
97
|
name: `${provider.current ? '✅' : '🔹'} ${provider.name} (${provider.displayName})${provider.current ? ' - 当前使用中' : ''}`,
|
|
98
98
|
value: provider.name,
|
|
99
|
-
|
|
99
|
+
checked: false
|
|
100
100
|
}));
|
|
101
101
|
|
|
102
|
-
choices.push(
|
|
103
|
-
new inquirer.Separator(),
|
|
104
|
-
{ name: '❌ 取消删除', value: '__CANCEL__' }
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
// 对于删除操作,不默认选中当前供应商,而是选中第一个非当前的供应商
|
|
108
|
-
const nonCurrentProvider = providers.find(p => !p.current);
|
|
109
|
-
const defaultChoice = nonCurrentProvider ? nonCurrentProvider.name : providers[0]?.name;
|
|
110
|
-
|
|
111
102
|
// 设置 ESC 键监听
|
|
112
103
|
const escListener = this.createESCListener(() => {
|
|
113
104
|
Logger.info('取消删除供应商');
|
|
@@ -121,12 +112,17 @@ class ProviderRemover extends BaseCommand {
|
|
|
121
112
|
try {
|
|
122
113
|
answer = await this.prompt([
|
|
123
114
|
{
|
|
124
|
-
type: '
|
|
125
|
-
name: '
|
|
126
|
-
message: '
|
|
115
|
+
type: 'checkbox',
|
|
116
|
+
name: 'providers',
|
|
117
|
+
message: '选择要删除的供应商(空格选择,Enter确认):',
|
|
127
118
|
choices,
|
|
128
|
-
|
|
129
|
-
|
|
119
|
+
pageSize: 10,
|
|
120
|
+
validate: (selected) => {
|
|
121
|
+
if (selected.length === 0) {
|
|
122
|
+
return '请至少选择一个供应商';
|
|
123
|
+
}
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
130
126
|
}
|
|
131
127
|
]);
|
|
132
128
|
} catch (error) {
|
|
@@ -139,12 +135,61 @@ class ProviderRemover extends BaseCommand {
|
|
|
139
135
|
|
|
140
136
|
this.removeESCListener(escListener);
|
|
141
137
|
|
|
142
|
-
if (answer.
|
|
138
|
+
if (!answer.providers || answer.providers.length === 0) {
|
|
143
139
|
Logger.info('删除操作已取消');
|
|
144
140
|
return;
|
|
145
141
|
}
|
|
146
142
|
|
|
147
|
-
|
|
143
|
+
// 显示将要删除的供应商列表
|
|
144
|
+
console.log();
|
|
145
|
+
console.log(UIHelper.createTitle(`即将删除 ${answer.providers.length} 个供应商`, '⚠️'));
|
|
146
|
+
answer.providers.forEach(name => {
|
|
147
|
+
const provider = this.configManager.getProvider(name);
|
|
148
|
+
console.log(` • ${provider.displayName} (${name})`);
|
|
149
|
+
});
|
|
150
|
+
console.log();
|
|
151
|
+
|
|
152
|
+
// 最终确认
|
|
153
|
+
let finalConfirm;
|
|
154
|
+
try {
|
|
155
|
+
finalConfirm = await this.prompt([
|
|
156
|
+
{
|
|
157
|
+
type: 'confirm',
|
|
158
|
+
name: 'confirm',
|
|
159
|
+
message: `确定要删除这 ${answer.providers.length} 个供应商吗?`,
|
|
160
|
+
default: false
|
|
161
|
+
}
|
|
162
|
+
]);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
if (this.isEscCancelled(error)) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!finalConfirm.confirm) {
|
|
171
|
+
Logger.warning('删除操作已取消');
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 批量删除
|
|
176
|
+
let successCount = 0;
|
|
177
|
+
let failCount = 0;
|
|
178
|
+
for (const providerName of answer.providers) {
|
|
179
|
+
try {
|
|
180
|
+
const provider = this.configManager.getProvider(providerName);
|
|
181
|
+
await this.configManager.removeProvider(providerName);
|
|
182
|
+
Logger.success(`✓ 已删除: ${provider.displayName}`);
|
|
183
|
+
successCount++;
|
|
184
|
+
} catch (error) {
|
|
185
|
+
Logger.error(`✗ 删除失败: ${providerName} - ${error.message}`);
|
|
186
|
+
failCount++;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
console.log();
|
|
191
|
+
Logger.success(`删除完成: 成功 ${successCount} 个${failCount > 0 ? `, 失败 ${failCount} 个` : ''}`);
|
|
192
|
+
|
|
148
193
|
} catch (error) {
|
|
149
194
|
this.removeESCListener(escListener);
|
|
150
195
|
throw error;
|