@x-all-in-one/coding-helper 0.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.
@@ -0,0 +1,673 @@
1
+ import inquirer from 'inquirer';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import { configManager } from './config.js';
5
+ import { toolManager, SUPPORTED_TOOLS } from './tool-manager.js';
6
+ import { claudeCodeManager, DEFAULT_CONFIG } from './claude-code-manager.js';
7
+ import { i18n } from './i18n.js';
8
+ import { createBorderLine, createContentLine } from '../utils/string-width.js';
9
+ import { execSync } from 'child_process';
10
+ import { validateApiKey } from './api-validator.js';
11
+ export class Wizard {
12
+ static instance;
13
+ BOX_WIDTH = 63; // Default box width for UI elements
14
+ cachedModels = [];
15
+ constructor() { }
16
+ static getInstance() {
17
+ if (!Wizard.instance) {
18
+ Wizard.instance = new Wizard();
19
+ }
20
+ return Wizard.instance;
21
+ }
22
+ /**
23
+ * Create a simple box with title using double-line border style
24
+ */
25
+ createBox(title) {
26
+ console.log(chalk.cyan.bold('\n' + createBorderLine('╔', '╗', '═', this.BOX_WIDTH)));
27
+ console.log(chalk.cyan.bold(createContentLine(title, '║', '║', this.BOX_WIDTH, 'center')));
28
+ console.log(chalk.cyan.bold(createBorderLine('╚', '╝', '═', this.BOX_WIDTH)));
29
+ console.log('');
30
+ }
31
+ /**
32
+ * Display operation hints
33
+ */
34
+ showOperationHints() {
35
+ const hints = [
36
+ chalk.gray(i18n.t('wizard.hint_navigate')),
37
+ chalk.gray(i18n.t('wizard.hint_confirm'))
38
+ ];
39
+ console.log(chalk.gray('💡 ') + hints.join(chalk.gray(' | ')) + '\n');
40
+ }
41
+ /**
42
+ * Prompt wrapper that shows operation hints
43
+ */
44
+ async promptWithHints(questions) {
45
+ this.showOperationHints();
46
+ return inquirer.prompt(questions);
47
+ }
48
+ printBanner() {
49
+ const BANNER_WIDTH = 65;
50
+ const subtitle = i18n.t('wizard.banner_subtitle');
51
+ const subtitleLine = createContentLine(subtitle, '║', '║', BANNER_WIDTH, 'center');
52
+ const emptyLine = createContentLine('', '║', '║', BANNER_WIDTH, 'center');
53
+ const titleLine = createContentLine('Coding Helper v0.0.1', '║', '║', BANNER_WIDTH, 'center');
54
+ const asciiLines = [
55
+ ' █ █ ▄▀▄ █ ▄▀▄ ',
56
+ ' █ ▄▄ █▀█ █ █ █ ',
57
+ ' █ █ █ █ █ ▀▄▀ '
58
+ ].map(line => createContentLine(line, '║', '║', BANNER_WIDTH, 'center'));
59
+ const bannerLines = [
60
+ createBorderLine('╔', '╗', '═', BANNER_WIDTH),
61
+ emptyLine,
62
+ ...asciiLines,
63
+ emptyLine,
64
+ titleLine,
65
+ subtitleLine,
66
+ createBorderLine('╚', '╝', '═', BANNER_WIDTH)
67
+ ];
68
+ console.log(chalk.cyan.bold('\n' + bannerLines.join('\n')));
69
+ }
70
+ resetScreen() {
71
+ console.clear();
72
+ this.printBanner();
73
+ }
74
+ async runFirstTimeSetup() {
75
+ // 清屏并显示欢迎信息
76
+ this.resetScreen();
77
+ console.log(chalk.cyan.bold('\n' + i18n.t('wizard.welcome')));
78
+ console.log(chalk.gray(i18n.t('wizard.privacy_note') + '\n'));
79
+ // Step 1: Select language
80
+ await this.configLanguage();
81
+ // Step 2: Input API key
82
+ await this.configApiKey();
83
+ // Step 3: Configure models
84
+ await this.configModels();
85
+ // Step 4: Select and configure tool
86
+ await this.selectAndConfigureTool();
87
+ }
88
+ async configLanguage() {
89
+ while (true) {
90
+ this.resetScreen();
91
+ this.createBox(i18n.t('wizard.select_language'));
92
+ const currentLanguage = i18n.getLocale();
93
+ const { language } = await this.promptWithHints([
94
+ {
95
+ type: 'list',
96
+ name: 'language',
97
+ message: '✨ ' + i18n.t('wizard.select_language'),
98
+ choices: [
99
+ { name: '[EN] English' + (currentLanguage === 'en_US' ? chalk.green(' ✓ (' + i18n.t('wizard.current_active') + ')') : ''), value: 'en_US' },
100
+ { name: '[CN] 中文' + (currentLanguage === 'zh_CN' ? chalk.green(' ✓ (' + i18n.t('wizard.current_active') + ')') : ''), value: 'zh_CN' },
101
+ new inquirer.Separator(),
102
+ { name: '<- ' + i18n.t('wizard.nav_return'), value: 'back' },
103
+ { name: 'x ' + i18n.t('wizard.nav_exit'), value: 'exit' }
104
+ ],
105
+ default: 'zh_CN'
106
+ }
107
+ ]);
108
+ if (language === 'exit') {
109
+ console.log(chalk.green('\n👋 ' + i18n.t('wizard.goodbye_message')));
110
+ process.exit(0);
111
+ }
112
+ else if (language === 'back') {
113
+ return;
114
+ }
115
+ configManager.setLang(language);
116
+ i18n.setLocale(language);
117
+ return;
118
+ }
119
+ }
120
+ async configApiKey() {
121
+ while (true) {
122
+ this.resetScreen();
123
+ this.createBox(i18n.t('wizard.config_api_key'));
124
+ const currentConfig = configManager.getConfig();
125
+ if (currentConfig.api_key) {
126
+ console.log(chalk.gray(' ' + i18n.t('wizard.config_api_key') + ' ') + chalk.gray(i18n.t('wizard.api_key_set') + ' (' + currentConfig.api_key.slice(0, 4) + '****)'));
127
+ console.log('');
128
+ }
129
+ const { action } = await this.promptWithHints([
130
+ {
131
+ type: 'list',
132
+ name: 'action',
133
+ message: i18n.t('wizard.select_action'),
134
+ choices: [
135
+ { name: '> ' + (currentConfig.api_key ? i18n.t("wizard.update_api_key") : i18n.t('wizard.input_api_key')), value: 'input' },
136
+ new inquirer.Separator(),
137
+ { name: '<- ' + i18n.t('wizard.nav_return'), value: 'back' },
138
+ { name: 'x ' + i18n.t('wizard.nav_exit'), value: 'exit' }
139
+ ]
140
+ }
141
+ ]);
142
+ if (action === 'exit') {
143
+ console.log(chalk.green('\n👋 ' + i18n.t('wizard.goodbye_message')));
144
+ process.exit(0);
145
+ }
146
+ else if (action === 'back') {
147
+ return;
148
+ }
149
+ else if (action === 'input') {
150
+ this.resetScreen();
151
+ this.createBox(i18n.t('wizard.config_api_key'));
152
+ const { apiKey } = await inquirer.prompt([
153
+ {
154
+ type: 'password',
155
+ name: 'apiKey',
156
+ mask: '●',
157
+ message: i18n.t('wizard.input_your_api_key'),
158
+ validate: (input) => {
159
+ if (!input || input.trim().length === 0) {
160
+ return '[!] ' + i18n.t('wizard.api_key_required');
161
+ }
162
+ return true;
163
+ }
164
+ }
165
+ ]);
166
+ // Validate API Key
167
+ const spinner = ora({
168
+ text: i18n.t('wizard.validating_api_key'),
169
+ spinner: 'star2'
170
+ }).start();
171
+ const validationResult = await validateApiKey(apiKey.trim());
172
+ await new Promise(resolve => setTimeout(resolve, 800));
173
+ if (!validationResult.valid) {
174
+ if (validationResult.error === 'invalid_api_key') {
175
+ spinner.fail(chalk.red(i18n.t('wizard.api_key_invalid')));
176
+ }
177
+ else {
178
+ spinner.fail(chalk.red(i18n.t('wizard.api_key_network_error')));
179
+ }
180
+ await new Promise(resolve => setTimeout(resolve, 1500));
181
+ continue; // Return to action menu
182
+ }
183
+ configManager.setApiKey(apiKey.trim());
184
+ // Clear cached models when API key changes
185
+ this.cachedModels = [];
186
+ spinner.succeed("✅ " + i18n.t('wizard.set_success'));
187
+ await new Promise(resolve => setTimeout(resolve, 600));
188
+ // After setting API key, go to model configuration
189
+ await this.configModels();
190
+ }
191
+ }
192
+ }
193
+ /**
194
+ * Fetch and cache available models
195
+ */
196
+ async fetchModels() {
197
+ const apiKey = configManager.getApiKey();
198
+ if (!apiKey) {
199
+ return [];
200
+ }
201
+ if (this.cachedModels.length > 0) {
202
+ return this.cachedModels;
203
+ }
204
+ const models = await claudeCodeManager.fetchAvailableModels(apiKey);
205
+ this.cachedModels = models;
206
+ return models;
207
+ }
208
+ /**
209
+ * Configure models menu
210
+ */
211
+ async configModels() {
212
+ while (true) {
213
+ this.resetScreen();
214
+ this.createBox(i18n.t('wizard.select_models'));
215
+ const currentConfig = configManager.getConfig();
216
+ const models = currentConfig;
217
+ // Display current model configuration
218
+ console.log(chalk.cyan.bold(i18n.t('wizard.models_config_title') + ':'));
219
+ console.log(chalk.gray(' ' + i18n.t('wizard.current_haiku_model') + ': ') + (models.haikuModel ? chalk.green(models.haikuModel) : chalk.red(i18n.t('wizard.not_set'))));
220
+ console.log(chalk.gray(' ' + i18n.t('wizard.current_sonnet_model') + ': ') + (models.sonnetModel ? chalk.green(models.sonnetModel) : chalk.red(i18n.t('wizard.not_set'))));
221
+ console.log(chalk.gray(' ' + i18n.t('wizard.current_opus_model') + ': ') + (models.opusModel ? chalk.green(models.opusModel) : chalk.red(i18n.t('wizard.not_set'))));
222
+ console.log('');
223
+ const { action } = await this.promptWithHints([
224
+ {
225
+ type: 'list',
226
+ name: 'action',
227
+ message: i18n.t('wizard.select_action'),
228
+ choices: [
229
+ { name: '> ' + i18n.t('wizard.configure_models'), value: 'configure' },
230
+ new inquirer.Separator(),
231
+ { name: '<- ' + i18n.t('wizard.nav_return'), value: 'back' },
232
+ { name: 'x ' + i18n.t('wizard.nav_exit'), value: 'exit' }
233
+ ]
234
+ }
235
+ ]);
236
+ if (action === 'exit') {
237
+ console.log(chalk.green('\n👋 ' + i18n.t('wizard.goodbye_message')));
238
+ process.exit(0);
239
+ }
240
+ else if (action === 'back') {
241
+ return;
242
+ }
243
+ else if (action === 'configure') {
244
+ await this.selectModels();
245
+ }
246
+ }
247
+ }
248
+ /**
249
+ * Step-by-step model selection
250
+ */
251
+ async selectModels() {
252
+ const apiKey = configManager.getApiKey();
253
+ if (!apiKey) {
254
+ console.log(chalk.red('\n' + i18n.t('wizard.no_models_available')));
255
+ await new Promise(resolve => setTimeout(resolve, 1500));
256
+ return;
257
+ }
258
+ // Fetch available models
259
+ const spinner = ora({
260
+ text: i18n.t('wizard.fetching_models'),
261
+ spinner: 'star2'
262
+ }).start();
263
+ const availableModels = await this.fetchModels();
264
+ if (availableModels.length === 0) {
265
+ spinner.fail(chalk.red(i18n.t('wizard.fetch_models_failed')));
266
+ await new Promise(resolve => setTimeout(resolve, 1500));
267
+ return;
268
+ }
269
+ spinner.succeed(chalk.green(i18n.t('wizard.api_key_valid')));
270
+ await new Promise(resolve => setTimeout(resolve, 300));
271
+ const currentConfig = configManager.getConfig();
272
+ // Step 1: Select Haiku model
273
+ this.resetScreen();
274
+ this.createBox(i18n.t('wizard.select_haiku_model'));
275
+ const haikuChoices = availableModels.map(model => ({
276
+ name: model + (currentConfig.haikuModel === model ? chalk.green(' ✓ (' + i18n.t('wizard.current_active') + ')') : ''),
277
+ value: model
278
+ }));
279
+ haikuChoices.push(new inquirer.Separator(), { name: '<- ' + i18n.t('wizard.nav_return'), value: 'back' }, { name: 'x ' + i18n.t('wizard.nav_exit'), value: 'exit' });
280
+ const { haikuModel } = await this.promptWithHints([
281
+ {
282
+ type: 'list',
283
+ name: 'haikuModel',
284
+ message: i18n.t('wizard.select_haiku_model'),
285
+ choices: haikuChoices,
286
+ default: currentConfig.haikuModel || DEFAULT_CONFIG.ANTHROPIC_DEFAULT_HAIKU_MODEL
287
+ }
288
+ ]);
289
+ if (haikuModel === 'exit') {
290
+ console.log(chalk.green('\n👋 ' + i18n.t('wizard.goodbye_message')));
291
+ process.exit(0);
292
+ }
293
+ else if (haikuModel === 'back') {
294
+ return;
295
+ }
296
+ // Step 2: Select Sonnet model
297
+ this.resetScreen();
298
+ this.createBox(i18n.t('wizard.select_sonnet_model'));
299
+ const sonnetChoices = availableModels.map(model => ({
300
+ name: model + (currentConfig.sonnetModel === model ? chalk.green(' ✓ (' + i18n.t('wizard.current_active') + ')') : ''),
301
+ value: model
302
+ }));
303
+ sonnetChoices.push(new inquirer.Separator(), { name: '<- ' + i18n.t('wizard.nav_return'), value: 'back' }, { name: 'x ' + i18n.t('wizard.nav_exit'), value: 'exit' });
304
+ const { sonnetModel } = await this.promptWithHints([
305
+ {
306
+ type: 'list',
307
+ name: 'sonnetModel',
308
+ message: i18n.t('wizard.select_sonnet_model'),
309
+ choices: sonnetChoices,
310
+ default: currentConfig.sonnetModel || DEFAULT_CONFIG.ANTHROPIC_DEFAULT_SONNET_MODEL
311
+ }
312
+ ]);
313
+ if (sonnetModel === 'exit') {
314
+ console.log(chalk.green('\n👋 ' + i18n.t('wizard.goodbye_message')));
315
+ process.exit(0);
316
+ }
317
+ else if (sonnetModel === 'back') {
318
+ return;
319
+ }
320
+ // Step 3: Select Opus model
321
+ this.resetScreen();
322
+ this.createBox(i18n.t('wizard.select_opus_model'));
323
+ const opusChoices = availableModels.map(model => ({
324
+ name: model + (currentConfig.opusModel === model ? chalk.green(' ✓ (' + i18n.t('wizard.current_active') + ')') : ''),
325
+ value: model
326
+ }));
327
+ opusChoices.push(new inquirer.Separator(), { name: '<- ' + i18n.t('wizard.nav_return'), value: 'back' }, { name: 'x ' + i18n.t('wizard.nav_exit'), value: 'exit' });
328
+ const { opusModel } = await this.promptWithHints([
329
+ {
330
+ type: 'list',
331
+ name: 'opusModel',
332
+ message: i18n.t('wizard.select_opus_model'),
333
+ choices: opusChoices,
334
+ default: currentConfig.opusModel || DEFAULT_CONFIG.ANTHROPIC_DEFAULT_OPUS_MODEL
335
+ }
336
+ ]);
337
+ if (opusModel === 'exit') {
338
+ console.log(chalk.green('\n👋 ' + i18n.t('wizard.goodbye_message')));
339
+ process.exit(0);
340
+ }
341
+ else if (opusModel === 'back') {
342
+ return;
343
+ }
344
+ // Save the configuration
345
+ const saveSpinner = ora({
346
+ text: i18n.t('wizard.saving_model_config'),
347
+ spinner: 'star2'
348
+ }).start();
349
+ try {
350
+ configManager.setModels({
351
+ haikuModel,
352
+ sonnetModel,
353
+ opusModel
354
+ });
355
+ await new Promise(resolve => setTimeout(resolve, 600));
356
+ saveSpinner.succeed(chalk.green(i18n.t('wizard.model_config_saved')));
357
+ await new Promise(resolve => setTimeout(resolve, 1500));
358
+ }
359
+ catch (error) {
360
+ saveSpinner.fail(i18n.t('wizard.model_config_failed'));
361
+ console.error(error);
362
+ await new Promise(resolve => setTimeout(resolve, 1500));
363
+ }
364
+ }
365
+ async selectAndConfigureTool() {
366
+ while (true) {
367
+ this.resetScreen();
368
+ this.createBox(i18n.t('wizard.select_tool'));
369
+ const supportedTools = toolManager.getSupportedTools();
370
+ const toolChoices = supportedTools.map(tool => ({
371
+ name: `> ${tool.displayName}`,
372
+ value: tool.name
373
+ }));
374
+ toolChoices.push(new inquirer.Separator(), { name: '<- ' + i18n.t('wizard.nav_return'), value: 'back' }, { name: 'x ' + i18n.t('wizard.nav_exit'), value: 'exit' });
375
+ const { selectedTool } = await this.promptWithHints([
376
+ {
377
+ type: 'list',
378
+ name: 'selectedTool',
379
+ message: i18n.t('wizard.select_tool'),
380
+ choices: toolChoices
381
+ }
382
+ ]);
383
+ if (selectedTool === 'exit') {
384
+ console.log(chalk.green('\n👋 ' + i18n.t('wizard.goodbye_message')));
385
+ process.exit(0);
386
+ }
387
+ else if (selectedTool === 'back') {
388
+ return;
389
+ }
390
+ await this.configureTool(selectedTool);
391
+ }
392
+ }
393
+ async configureTool(toolName) {
394
+ // 检查工具是否安装
395
+ if (!toolManager.isToolInstalled(toolName)) {
396
+ console.log(chalk.yellow(`\n${i18n.t('wizard.tool_not_installed', { tool: SUPPORTED_TOOLS[toolName].displayName })}`));
397
+ const { shouldInstall } = await this.promptWithHints([
398
+ {
399
+ type: 'confirm',
400
+ name: 'shouldInstall',
401
+ message: i18n.t('wizard.install_tool_confirm'),
402
+ default: true
403
+ }
404
+ ]);
405
+ if (shouldInstall) {
406
+ try {
407
+ await toolManager.installTool(toolName);
408
+ await new Promise(resolve => setTimeout(resolve, 600));
409
+ }
410
+ catch (error) {
411
+ console.error(chalk.red(i18n.t('install.install_failed_detail')));
412
+ if (error.message) {
413
+ console.error(chalk.gray(error.message));
414
+ }
415
+ await new Promise(resolve => setTimeout(resolve, 600));
416
+ // 询问是否跳过安装
417
+ const { skipInstall } = await this.promptWithHints([
418
+ {
419
+ type: 'list',
420
+ name: 'skipInstall',
421
+ message: i18n.t('install.skip_install_confirm'),
422
+ choices: [
423
+ { name: i18n.t('install.skip_install_yes'), value: true },
424
+ { name: i18n.t('install.skip_install_no'), value: false }
425
+ ]
426
+ }
427
+ ]);
428
+ if (!skipInstall) {
429
+ return;
430
+ }
431
+ }
432
+ }
433
+ else {
434
+ console.log(chalk.yellow(i18n.t('wizard.install_skipped')));
435
+ return;
436
+ }
437
+ }
438
+ // 进入工具管理菜单
439
+ await this.showToolMenu(toolName);
440
+ }
441
+ async showMainMenu() {
442
+ const cfg = configManager.getConfig();
443
+ i18n.loadFromConfig(cfg.lang);
444
+ while (true) {
445
+ this.resetScreen();
446
+ const currentCfg = configManager.getConfig();
447
+ this.createBox(i18n.t('wizard.main_menu_title'));
448
+ // 显示当前配置状态
449
+ console.log(chalk.gray(' ' + i18n.t('wizard.config_api_key') + ': ') + (currentCfg.api_key ? chalk.gray(i18n.t('wizard.api_key_set') + ' (' + currentCfg.api_key.slice(0, 4) + '****)') : chalk.red(i18n.t('wizard.not_set'))));
450
+ console.log(chalk.gray(' ' + i18n.t('wizard.current_haiku_model') + ': ') + (currentCfg.haikuModel ? chalk.green(currentCfg.haikuModel) : chalk.red(i18n.t('wizard.not_set'))));
451
+ console.log(chalk.gray(' ' + i18n.t('wizard.current_sonnet_model') + ': ') + (currentCfg.sonnetModel ? chalk.green(currentCfg.sonnetModel) : chalk.red(i18n.t('wizard.not_set'))));
452
+ console.log(chalk.gray(' ' + i18n.t('wizard.current_opus_model') + ': ') + (currentCfg.opusModel ? chalk.green(currentCfg.opusModel) : chalk.red(i18n.t('wizard.not_set'))));
453
+ console.log('');
454
+ const choices = [
455
+ { name: '> ' + i18n.t('wizard.menu_config_language'), value: 'lang' },
456
+ { name: '> ' + i18n.t('wizard.menu_config_api_key'), value: 'apikey' },
457
+ { name: '> ' + i18n.t('wizard.menu_config_models'), value: 'models' },
458
+ { name: '> ' + i18n.t('wizard.menu_config_tool'), value: 'tool' },
459
+ new inquirer.Separator(),
460
+ { name: 'x ' + i18n.t('wizard.menu_exit'), value: 'exit' }
461
+ ];
462
+ const { action } = await this.promptWithHints([
463
+ {
464
+ type: 'list',
465
+ name: 'action',
466
+ message: i18n.t('wizard.select_operation'),
467
+ choices
468
+ }
469
+ ]);
470
+ if (action === 'exit') {
471
+ console.log(chalk.green('\n👋 ' + i18n.t('wizard.goodbye_message')));
472
+ process.exit(0);
473
+ }
474
+ else if (action === 'lang') {
475
+ await this.configLanguage();
476
+ }
477
+ else if (action === 'apikey') {
478
+ await this.configApiKey();
479
+ }
480
+ else if (action === 'models') {
481
+ await this.configModels();
482
+ }
483
+ else if (action === 'tool') {
484
+ await this.selectAndConfigureTool();
485
+ }
486
+ }
487
+ }
488
+ async showToolMenu(toolName) {
489
+ while (true) {
490
+ // for claude code now
491
+ this.resetScreen();
492
+ const title = `${SUPPORTED_TOOLS[toolName].displayName} ${i18n.t('wizard.menu_title')}`;
493
+ this.createBox(title);
494
+ if (toolName === 'claude-code') {
495
+ console.log(chalk.yellow.bold(i18n.t('wizard.global_config_warning', { tool: 'Claude Code' })));
496
+ console.log('');
497
+ }
498
+ let actionText = '';
499
+ const chelperConfig = configManager.getConfig();
500
+ const detectedConfig = claudeCodeManager.getModelConfig();
501
+ // 显示 chelper 配置
502
+ console.log(chalk.cyan.bold(i18n.t('wizard.chelper_config_title') + ':'));
503
+ if (chelperConfig.api_key) {
504
+ console.log(chalk.gray(' ' + i18n.t('wizard.config_api_key') + ': ') + chalk.gray(i18n.t('wizard.api_key_set') + ' (' + chelperConfig.api_key.slice(0, 4) + '****)'));
505
+ }
506
+ else {
507
+ console.log(chalk.gray(' ' + i18n.t('wizard.config_api_key') + ': ') + chalk.red(i18n.t('wizard.not_set')));
508
+ }
509
+ console.log(chalk.gray(' ' + i18n.t('wizard.current_haiku_model') + ': ') + (chelperConfig.haikuModel ? chalk.green(chelperConfig.haikuModel) : chalk.red(i18n.t('wizard.not_set'))));
510
+ console.log(chalk.gray(' ' + i18n.t('wizard.current_sonnet_model') + ': ') + (chelperConfig.sonnetModel ? chalk.green(chelperConfig.sonnetModel) : chalk.red(i18n.t('wizard.not_set'))));
511
+ console.log(chalk.gray(' ' + i18n.t('wizard.current_opus_model') + ': ') + (chelperConfig.opusModel ? chalk.green(chelperConfig.opusModel) : chalk.red(i18n.t('wizard.not_set'))));
512
+ console.log('');
513
+ // 显示 Claude Code 当前配置
514
+ console.log(chalk.yellow.bold(i18n.t('wizard.claude_code_config_title') + ':'));
515
+ if (detectedConfig) {
516
+ console.log(chalk.gray(' ' + i18n.t('wizard.config_api_key') + ': ') + chalk.gray(i18n.t('wizard.api_key_set') + ' (' + detectedConfig.apiKey.slice(0, 4) + '****)'));
517
+ console.log(chalk.gray(' ' + i18n.t('wizard.current_haiku_model') + ': ') + chalk.green(detectedConfig.haikuModel));
518
+ console.log(chalk.gray(' ' + i18n.t('wizard.current_sonnet_model') + ': ') + chalk.green(detectedConfig.sonnetModel));
519
+ console.log(chalk.gray(' ' + i18n.t('wizard.current_opus_model') + ': ') + chalk.green(detectedConfig.opusModel));
520
+ }
521
+ else {
522
+ console.log(chalk.gray(' ' + i18n.t('wizard.config_api_key') + ': ') + chalk.red(i18n.t('wizard.not_set')));
523
+ }
524
+ console.log('');
525
+ // 判断是否需要刷新配置
526
+ const configMatches = detectedConfig &&
527
+ detectedConfig.apiKey === chelperConfig.api_key &&
528
+ detectedConfig.haikuModel === chelperConfig.haikuModel &&
529
+ detectedConfig.sonnetModel === chelperConfig.sonnetModel &&
530
+ detectedConfig.opusModel === chelperConfig.opusModel;
531
+ if (detectedConfig && configMatches) {
532
+ // 配置已同步
533
+ console.log(chalk.green('✅ ' + i18n.t('wizard.config_synced')));
534
+ actionText = i18n.t('wizard.action_refresh_config', { 'tool': SUPPORTED_TOOLS[toolName].displayName });
535
+ }
536
+ else if (detectedConfig) {
537
+ // 配置不一致,需要刷新
538
+ console.log(chalk.yellow('⚠️ ' + i18n.t('wizard.config_out_of_sync')));
539
+ actionText = i18n.t('wizard.action_refresh_config', { 'tool': SUPPORTED_TOOLS[toolName].displayName });
540
+ }
541
+ else {
542
+ // 未配置,需要装载
543
+ console.log(chalk.blue('ℹ️ ' + i18n.t('wizard.config_not_loaded')));
544
+ actionText = i18n.t('wizard.action_load_config', { 'tool': SUPPORTED_TOOLS[toolName].displayName });
545
+ }
546
+ console.log('');
547
+ const choices = [];
548
+ choices.push({ name: '> ' + actionText, value: 'load_config' });
549
+ // 如果已经配置了,显示卸载选项
550
+ if (detectedConfig) {
551
+ choices.push({ name: '> ' + i18n.t('wizard.action_unload_config', { 'tool': SUPPORTED_TOOLS[toolName].displayName }), value: 'unload_config' });
552
+ }
553
+ // 如果已经配置了,显示启动选项
554
+ if (detectedConfig) {
555
+ choices.push({ name: '> ' + i18n.t('wizard.start_tool', { 'tool': SUPPORTED_TOOLS[toolName].displayName, 'shell': SUPPORTED_TOOLS[toolName].command }), value: 'start_tool' });
556
+ }
557
+ choices.push(new inquirer.Separator(), { name: '<- ' + i18n.t('wizard.nav_return'), value: 'back' }, { name: 'x ' + i18n.t('wizard.nav_exit'), value: 'exit' });
558
+ const { action } = await this.promptWithHints([
559
+ {
560
+ type: 'list',
561
+ name: 'action',
562
+ message: i18n.t('wizard.select_action'),
563
+ choices
564
+ }
565
+ ]);
566
+ if (action === 'exit') {
567
+ console.log(chalk.green('\n👋 ' + i18n.t('wizard.goodbye_message')));
568
+ process.exit(0);
569
+ }
570
+ else if (action === 'back') {
571
+ return;
572
+ }
573
+ else if (action === 'load_config') {
574
+ await this.loadModelConfig(toolName);
575
+ }
576
+ else if (action === 'unload_config') {
577
+ await this.unloadModelConfig(toolName);
578
+ }
579
+ else if (action === 'start_tool') {
580
+ await this.startTool(toolName);
581
+ }
582
+ }
583
+ }
584
+ async startTool(toolName) {
585
+ const tool = SUPPORTED_TOOLS[toolName];
586
+ if (!tool) {
587
+ throw new Error(`Unknown tool: ${toolName}`);
588
+ }
589
+ const spinner = ora({
590
+ text: i18n.t('wizard.starting_tool'),
591
+ spinner: 'star2'
592
+ }).start();
593
+ try {
594
+ // 开启新窗口 执行 tool.command
595
+ execSync(tool.command, { stdio: 'inherit' });
596
+ spinner.succeed(i18n.t('wizard.tool_started'));
597
+ }
598
+ catch (error) {
599
+ spinner.fail(i18n.t('wizard.tool_start_failed'));
600
+ throw error;
601
+ }
602
+ }
603
+ async loadModelConfig(toolName) {
604
+ const spinner = ora({
605
+ text: i18n.t('wizard.loading_config'),
606
+ spinner: 'star2'
607
+ }).start();
608
+ try {
609
+ const config = configManager.getConfig();
610
+ if (!config.api_key) {
611
+ spinner.fail(i18n.t('wizard.missing_config'));
612
+ await new Promise(resolve => setTimeout(resolve, 800));
613
+ return;
614
+ }
615
+ // Use default models if not set
616
+ const haikuModel = config.haikuModel || DEFAULT_CONFIG.ANTHROPIC_DEFAULT_HAIKU_MODEL;
617
+ const sonnetModel = config.sonnetModel || DEFAULT_CONFIG.ANTHROPIC_DEFAULT_SONNET_MODEL;
618
+ const opusModel = config.opusModel || DEFAULT_CONFIG.ANTHROPIC_DEFAULT_OPUS_MODEL;
619
+ toolManager.loadModelConfig(toolName, {
620
+ apiKey: config.api_key,
621
+ haikuModel,
622
+ sonnetModel,
623
+ opusModel
624
+ });
625
+ await new Promise(resolve => setTimeout(resolve, 800));
626
+ spinner.succeed(chalk.green(i18n.t('wizard.config_loaded', { tool: SUPPORTED_TOOLS[toolName].displayName })));
627
+ await new Promise(resolve => setTimeout(resolve, 2000));
628
+ }
629
+ catch (error) {
630
+ spinner.fail(i18n.t('wizard.config_load_failed'));
631
+ await new Promise(resolve => setTimeout(resolve, 800));
632
+ console.error(error);
633
+ }
634
+ }
635
+ async unloadModelConfig(toolName) {
636
+ // 确认卸载操作
637
+ const { confirm } = await this.promptWithHints([
638
+ {
639
+ type: 'confirm',
640
+ name: 'confirm',
641
+ message: i18n.t('wizard.confirm_unload_config', { tool: SUPPORTED_TOOLS[toolName].displayName }),
642
+ default: false
643
+ }
644
+ ]);
645
+ if (!confirm) {
646
+ return;
647
+ }
648
+ const spinner = ora({
649
+ text: i18n.t('wizard.unloading_config'),
650
+ spinner: 'star2'
651
+ }).start();
652
+ try {
653
+ if (toolName === 'claude-code') {
654
+ // 添加短暂延迟,让动画效果更流畅
655
+ await new Promise(resolve => setTimeout(resolve, 300));
656
+ claudeCodeManager.clearModelConfig();
657
+ await new Promise(resolve => setTimeout(resolve, 500));
658
+ spinner.succeed(chalk.green(i18n.t('wizard.config_unloaded')));
659
+ await new Promise(resolve => setTimeout(resolve, 800));
660
+ }
661
+ else {
662
+ spinner.fail(i18n.t('wizard.tool_not_supported'));
663
+ await new Promise(resolve => setTimeout(resolve, 800));
664
+ }
665
+ }
666
+ catch (error) {
667
+ spinner.fail(i18n.t('wizard.config_unload_failed'));
668
+ await new Promise(resolve => setTimeout(resolve, 800));
669
+ console.error(error);
670
+ }
671
+ }
672
+ }
673
+ export const wizard = Wizard.getInstance();