@kikkimo/claude-launcher 2.5.0 → 3.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/CHANGELOG.md +42 -0
- package/README.md +17 -10
- package/claude-launcher +614 -398
- package/docs/README-zh.md +17 -10
- package/lib/api-manager.js +136 -11
- package/lib/auth/password-input.js +8 -4
- package/lib/auth/password-validator.js +83 -48
- package/lib/i18n/index.js +4 -3
- package/lib/i18n/language-manager.js +4 -3
- package/lib/i18n/locales/de.js +89 -11
- package/lib/i18n/locales/en.js +89 -11
- package/lib/i18n/locales/es.js +89 -11
- package/lib/i18n/locales/fr.js +89 -11
- package/lib/i18n/locales/it.js +89 -11
- package/lib/i18n/locales/ja.js +89 -11
- package/lib/i18n/locales/ko.js +89 -11
- package/lib/i18n/locales/pt.js +89 -11
- package/lib/i18n/locales/ru.js +89 -11
- package/lib/i18n/locales/zh-TW.js +89 -11
- package/lib/i18n/locales/zh.js +89 -11
- package/lib/launcher.js +121 -93
- package/lib/ui/api-editor.js +210 -0
- package/lib/ui/interactive-table.js +216 -99
- package/lib/ui/menu.js +73 -62
- package/lib/ui/prompts.js +168 -139
- package/lib/ui/screen.js +125 -0
- package/lib/utils/stdin-manager.js +11 -9
- package/lib/utils/version-checker.js +63 -3
- package/package.json +2 -2
- package/docs/superpowers/plans/2026-03-31-update-models-and-auto-mode.md +0 -1414
- package/docs/superpowers/specs/2026-03-31-update-models-and-auto-mode-design.md +0 -187
package/claude-launcher
CHANGED
|
@@ -25,7 +25,7 @@ function forceStdinCleanup() {
|
|
|
25
25
|
} catch (error) {
|
|
26
26
|
// Ignore cleanup errors but log for debugging
|
|
27
27
|
if (process.env.DEBUG_STDIN) {
|
|
28
|
-
|
|
28
|
+
screen.debug('[DEBUG] forceStdinCleanup error: ' + error.message);
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
}
|
|
@@ -50,10 +50,12 @@ const {
|
|
|
50
50
|
launchClaudeWithApi
|
|
51
51
|
} = require('./lib/launcher');
|
|
52
52
|
const { getPasswordInput } = require('./lib/auth/password-input');
|
|
53
|
-
const {
|
|
53
|
+
const { passwordGuard, verifyCurrentPassword, setupNewPassword, changePassword: changePasswordModule } = require('./lib/auth/password-validator');
|
|
54
54
|
const { maskApiToken } = require('./lib/validators');
|
|
55
55
|
const { showApiSelectionTable, confirmDeletion } = require('./lib/ui/interactive-table');
|
|
56
|
+
const { editApi } = require('./lib/ui/api-editor');
|
|
56
57
|
const i18n = require('./lib/i18n');
|
|
58
|
+
const screen = require('./lib/ui/screen');
|
|
57
59
|
const fs = require('fs');
|
|
58
60
|
const path = require('path');
|
|
59
61
|
const os = require('os');
|
|
@@ -66,6 +68,7 @@ const apiManager = new ApiManager();
|
|
|
66
68
|
let globalMainMenu = null;
|
|
67
69
|
let globalConfirmMenu = null;
|
|
68
70
|
let globalApiManagementMenu = null;
|
|
71
|
+
let globalConfigMenu = null;
|
|
69
72
|
|
|
70
73
|
/**
|
|
71
74
|
* Initialize global menu objects to prevent recreation and screen flickering
|
|
@@ -80,6 +83,9 @@ function initializeGlobalMenus() {
|
|
|
80
83
|
if (!globalApiManagementMenu) {
|
|
81
84
|
globalApiManagementMenu = new Menu();
|
|
82
85
|
}
|
|
86
|
+
if (!globalConfigMenu) {
|
|
87
|
+
globalConfigMenu = new Menu();
|
|
88
|
+
}
|
|
83
89
|
}
|
|
84
90
|
|
|
85
91
|
/**
|
|
@@ -132,7 +138,7 @@ function openFileWithDefault(filePath) {
|
|
|
132
138
|
|
|
133
139
|
exec(command, (error) => {
|
|
134
140
|
if (error) {
|
|
135
|
-
|
|
141
|
+
screen.write(colors.yellow + `Could not open file automatically: ${error.message}` + colors.reset + '\n');
|
|
136
142
|
}
|
|
137
143
|
});
|
|
138
144
|
}
|
|
@@ -253,7 +259,7 @@ async function addNewThirdPartyApi() {
|
|
|
253
259
|
const cancelledMessage = await i18n.t('errors.general.cancelled_by_user');
|
|
254
260
|
if (error.message === cancelledMessage) {
|
|
255
261
|
// User cancelled - show neutral message instead of error
|
|
256
|
-
|
|
262
|
+
screen.write(colors.yellow + await i18n.t('messages.info.operation_cancelled') + colors.reset + '\n');
|
|
257
263
|
} else {
|
|
258
264
|
// Actual error occurred
|
|
259
265
|
showError(await i18n.t('errors.api.failed_add', error.message));
|
|
@@ -267,19 +273,22 @@ async function addNewThirdPartyApi() {
|
|
|
267
273
|
* Remove third-party API menu with submenu
|
|
268
274
|
*/
|
|
269
275
|
async function removeThirdPartyApi() {
|
|
270
|
-
console.clear();
|
|
271
|
-
console.log('');
|
|
272
|
-
console.log(colors.bright + colors.orange + '🗑️ ' + await i18n.t('menu.remove_api.title') + colors.reset);
|
|
273
|
-
console.log('');
|
|
274
|
-
|
|
275
276
|
const apis = apiManager.getApis();
|
|
276
277
|
|
|
278
|
+
const lines = [
|
|
279
|
+
'',
|
|
280
|
+
colors.bright + colors.orange + '🗑️ ' + await i18n.t('menu.remove_api.title') + colors.reset,
|
|
281
|
+
'',
|
|
282
|
+
];
|
|
283
|
+
|
|
277
284
|
// Show current API count
|
|
278
285
|
if (apis.length > 0) {
|
|
279
|
-
|
|
280
|
-
|
|
286
|
+
lines.push(colors.cyan + ' ' + await i18n.t('messages.info.current_api_count', apis.length) + colors.reset);
|
|
287
|
+
lines.push('');
|
|
281
288
|
}
|
|
282
289
|
|
|
290
|
+
screen.render(lines);
|
|
291
|
+
|
|
283
292
|
const menuOptions = [
|
|
284
293
|
await i18n.t('menu.remove_api.delete_single'),
|
|
285
294
|
await i18n.t('menu.remove_api.clear_all'),
|
|
@@ -337,7 +346,6 @@ async function deleteSingleApi() {
|
|
|
337
346
|
const selectedIndex = apis.findIndex(api => api.id === selectedApi.id);
|
|
338
347
|
apiManager.removeApi(selectedIndex);
|
|
339
348
|
|
|
340
|
-
console.clear();
|
|
341
349
|
showSuccess(await i18n.t('messages.success.api_removed'), [
|
|
342
350
|
`${await i18n.t('api.actions.removed_info', selectedApi.name)}`,
|
|
343
351
|
`${await i18n.t('api.details.provider')}: ${selectedApi.provider}`
|
|
@@ -376,24 +384,23 @@ async function clearAllApis() {
|
|
|
376
384
|
const count = apis.length;
|
|
377
385
|
|
|
378
386
|
if (count === 0) {
|
|
379
|
-
console.clear();
|
|
380
387
|
showInfo(await i18n.t('messages.info.no_apis'));
|
|
381
388
|
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
382
389
|
return;
|
|
383
390
|
}
|
|
384
391
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
392
|
+
screen.render([
|
|
393
|
+
'',
|
|
394
|
+
colors.bright + colors.red + '⚠️ ' + await i18n.t('menu.remove_api.clear_all') + colors.reset,
|
|
395
|
+
'',
|
|
396
|
+
colors.yellow + ' ' + await i18n.t('messages.prompts.confirm_clear_all', count) + colors.reset,
|
|
397
|
+
'',
|
|
398
|
+
]);
|
|
391
399
|
|
|
392
400
|
const input = await simpleInput(colors.cyan + ' ' + await i18n.t('messages.prompts.confirm_clear_all_input') + colors.reset);
|
|
393
401
|
|
|
394
402
|
if (input === 'CLEAR') {
|
|
395
403
|
const clearedCount = apiManager.clearAllApis();
|
|
396
|
-
console.clear();
|
|
397
404
|
showSuccess(await i18n.t('messages.info.all_apis_cleared', clearedCount));
|
|
398
405
|
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
399
406
|
} else {
|
|
@@ -455,10 +462,11 @@ function formatRelativeTime(timestamp) {
|
|
|
455
462
|
}
|
|
456
463
|
|
|
457
464
|
async function viewStatistics() {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
465
|
+
screen.render([
|
|
466
|
+
'',
|
|
467
|
+
colors.bright + colors.orange + '📊 ' + await i18n.t('statistics.title') + colors.reset,
|
|
468
|
+
'',
|
|
469
|
+
]);
|
|
462
470
|
|
|
463
471
|
const menuOptions = [
|
|
464
472
|
await i18n.t('statistics.menu_view'),
|
|
@@ -480,7 +488,7 @@ async function viewStatistics() {
|
|
|
480
488
|
const confirm = await simpleInput(colors.yellow + ' ' + await i18n.t('statistics.reset_confirm') + ' ' + colors.reset);
|
|
481
489
|
if (confirm.toLowerCase() === 'y') {
|
|
482
490
|
apiManager.resetStatistics();
|
|
483
|
-
|
|
491
|
+
screen.write(colors.green + ' ✓ ' + await i18n.t('statistics.reset_success') + colors.reset + '\n');
|
|
484
492
|
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
485
493
|
}
|
|
486
494
|
return viewStatistics();
|
|
@@ -498,38 +506,38 @@ async function viewStatistics() {
|
|
|
498
506
|
async function showStatisticsDetails() {
|
|
499
507
|
const { padStringToWidth } = require('./lib/utils/string-width');
|
|
500
508
|
|
|
501
|
-
console.clear();
|
|
502
|
-
console.log('');
|
|
503
|
-
console.log(colors.bright + colors.orange + '📊 ' + await i18n.t('statistics.title') + colors.reset);
|
|
504
|
-
console.log('');
|
|
505
|
-
|
|
506
509
|
const stats = apiManager.getEnhancedStatistics();
|
|
507
510
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
511
|
+
const lines = [
|
|
512
|
+
'',
|
|
513
|
+
colors.bright + colors.orange + '📊 ' + await i18n.t('statistics.title') + colors.reset,
|
|
514
|
+
'',
|
|
515
|
+
// Summary section
|
|
516
|
+
colors.cyan + ' ' + i18n.tSync('ui.general.summary') + ':' + colors.reset,
|
|
517
|
+
colors.gray + ` ${await i18n.t('statistics.total_apis', stats.totalApis)}` + colors.reset,
|
|
518
|
+
colors.gray + ` ${await i18n.t('statistics.active_api', stats.activeApiName)}` + colors.reset,
|
|
519
|
+
colors.gray + ` ${await i18n.t('statistics.most_used', stats.mostUsedApi)}` + colors.reset,
|
|
520
|
+
colors.gray + ` ${await i18n.t('statistics.total_usage', stats.totalUsage)}` + colors.reset,
|
|
521
|
+
colors.gray + ` ${await i18n.t('statistics.success_rate', stats.successRate)}` + colors.reset,
|
|
522
|
+
'',
|
|
523
|
+
];
|
|
516
524
|
|
|
517
525
|
if (stats.apiStats.length > 0) {
|
|
518
|
-
|
|
519
|
-
|
|
526
|
+
lines.push(colors.cyan + ' ' + i18n.tSync('ui.general.configured_apis') + ':' + colors.reset);
|
|
527
|
+
lines.push('');
|
|
520
528
|
|
|
521
529
|
// Table header
|
|
522
|
-
|
|
530
|
+
lines.push(colors.dim + ' ' +
|
|
523
531
|
padStringToWidth(await i18n.t('statistics.header_name'), 20) +
|
|
524
532
|
padStringToWidth(await i18n.t('statistics.header_usage'), 10) +
|
|
525
533
|
padStringToWidth(await i18n.t('statistics.header_success'), 10) +
|
|
526
534
|
await i18n.t('statistics.header_last_used') +
|
|
527
535
|
colors.reset);
|
|
528
|
-
|
|
536
|
+
lines.push(colors.dim + ' ' + '─'.repeat(60) + colors.reset);
|
|
529
537
|
|
|
530
538
|
for (const api of stats.apiStats) {
|
|
531
539
|
const lastUsedText = formatRelativeTime(api.lastUsed);
|
|
532
|
-
|
|
540
|
+
lines.push(colors.gray + ' ' +
|
|
533
541
|
padStringToWidth(api.name, 20) +
|
|
534
542
|
padStringToWidth(String(api.usageCount), 10) +
|
|
535
543
|
padStringToWidth(api.successRate, 10) +
|
|
@@ -537,10 +545,11 @@ async function showStatisticsDetails() {
|
|
|
537
545
|
colors.reset);
|
|
538
546
|
}
|
|
539
547
|
} else {
|
|
540
|
-
|
|
548
|
+
lines.push(colors.gray + ' ' + await i18n.t('statistics.no_usage') + colors.reset);
|
|
541
549
|
}
|
|
542
550
|
|
|
543
|
-
|
|
551
|
+
lines.push('');
|
|
552
|
+
screen.render(lines);
|
|
544
553
|
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
545
554
|
}
|
|
546
555
|
|
|
@@ -551,36 +560,36 @@ async function showStatisticsDetails() {
|
|
|
551
560
|
async function handleFirstTimePasswordSetup() {
|
|
552
561
|
while (true) {
|
|
553
562
|
// Clear screen and show header
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
563
|
+
const lines = [
|
|
564
|
+
'',
|
|
565
|
+
colors.bright + colors.yellow + '🔐 ' + i18n.tSync('password.setup.first_time_title') + colors.reset,
|
|
566
|
+
'',
|
|
567
|
+
// Show information
|
|
568
|
+
colors.cyan + i18n.tSync('password.setup.why_needed') + colors.reset,
|
|
569
|
+
];
|
|
561
570
|
const whyNeededItems = i18n.tSync('password.setup.why_needed_items');
|
|
562
571
|
if (Array.isArray(whyNeededItems)) {
|
|
563
572
|
whyNeededItems.forEach(item => {
|
|
564
|
-
|
|
573
|
+
lines.push(colors.gray + '• ' + item + colors.reset);
|
|
565
574
|
});
|
|
566
575
|
}
|
|
567
|
-
|
|
568
|
-
|
|
576
|
+
lines.push('');
|
|
577
|
+
lines.push(colors.cyan + '🔒 ' + i18n.tSync('password.setup.new_security_title') + colors.reset);
|
|
569
578
|
const securityItems = i18n.tSync('password.setup.security_items');
|
|
570
579
|
if (Array.isArray(securityItems)) {
|
|
571
580
|
securityItems.forEach(item => {
|
|
572
|
-
|
|
581
|
+
lines.push(colors.gray + '• ' + item + colors.reset);
|
|
573
582
|
});
|
|
574
583
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
+
lines.push('');
|
|
585
|
+
lines.push(colors.yellow + i18n.tSync('password.setup.options_title') + colors.reset);
|
|
586
|
+
lines.push(colors.gray + '• ' + i18n.tSync('password.setup.option_set') + colors.reset);
|
|
587
|
+
lines.push(colors.gray + '• ' + i18n.tSync('password.setup.option_skip') + colors.reset);
|
|
588
|
+
lines.push('');
|
|
589
|
+
lines.push(colors.red + i18n.tSync('password.setup.warning_skip') + colors.reset);
|
|
590
|
+
lines.push('');
|
|
591
|
+
lines.push(colors.gray + '按任意键继续...' + colors.reset);
|
|
592
|
+
screen.render(lines);
|
|
584
593
|
|
|
585
594
|
// Wait for user to read the information
|
|
586
595
|
await new Promise((resolve) => {
|
|
@@ -656,14 +665,16 @@ async function promptForPasswordSetup() {
|
|
|
656
665
|
* Confirm skip password setup
|
|
657
666
|
*/
|
|
658
667
|
async function confirmSkipPassword() {
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
668
|
+
screen.render([
|
|
669
|
+
'',
|
|
670
|
+
colors.bright + colors.red + '⚠️ ' + i18n.tSync('errors.password.confirm_skip_title') + colors.reset,
|
|
671
|
+
'',
|
|
672
|
+
colors.gray + i18n.tSync('ui.general.after_skipping_password_setup') + colors.reset,
|
|
673
|
+
colors.gray + '• ' + i18n.tSync('ui.general.password_skip_consequences')[0] + colors.reset,
|
|
674
|
+
colors.gray + '• ' + i18n.tSync('ui.general.password_skip_consequences')[1] + colors.reset,
|
|
675
|
+
colors.gray + '• ' + i18n.tSync('ui.general.password_skip_consequences')[2] + colors.reset,
|
|
676
|
+
'',
|
|
677
|
+
]);
|
|
667
678
|
|
|
668
679
|
// Ensure global menus are initialized
|
|
669
680
|
initializeGlobalMenus();
|
|
@@ -678,12 +689,12 @@ async function confirmSkipPassword() {
|
|
|
678
689
|
if (choice === 0) {
|
|
679
690
|
try {
|
|
680
691
|
apiManager.skipPasswordSetup();
|
|
681
|
-
|
|
692
|
+
screen.write(colors.yellow + i18n.tSync('errors.password.setup_skipped') + colors.reset + '\n');
|
|
682
693
|
await waitForKey(i18n.tSync('messages.prompts.press_any_key'));
|
|
683
694
|
return true;
|
|
684
695
|
} catch (error) {
|
|
685
696
|
forceStdinCleanup();
|
|
686
|
-
|
|
697
|
+
screen.write(colors.red + `Operation failed: ${error.message}` + colors.reset + '\n');
|
|
687
698
|
await waitForKey(i18n.tSync('messages.prompts.press_any_key'));
|
|
688
699
|
return false;
|
|
689
700
|
}
|
|
@@ -699,147 +710,114 @@ async function showApiManagementMenu() {
|
|
|
699
710
|
// Force cleanup stdin state before showing API management menu
|
|
700
711
|
forceStdinCleanup();
|
|
701
712
|
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
713
|
+
screen.render([
|
|
714
|
+
'',
|
|
715
|
+
colors.bright + colors.orange + '📋 ' + await i18n.t('menu.api_management.title') + colors.reset,
|
|
716
|
+
'',
|
|
717
|
+
]);
|
|
706
718
|
|
|
707
719
|
// Check if this is first time usage and prompt for password setup
|
|
708
720
|
if (apiManager.isFirstTimeUsage()) {
|
|
709
721
|
const passwordChoice = await handleFirstTimePasswordSetup();
|
|
710
722
|
if (!passwordChoice) {
|
|
711
|
-
// User chose to skip or canceled, return to main menu
|
|
712
723
|
return showMenu();
|
|
713
724
|
}
|
|
714
725
|
}
|
|
715
726
|
|
|
716
727
|
// Build menu options based on password setup status
|
|
717
728
|
const menuOptions = [
|
|
718
|
-
await i18n.t('menu.api_management.add_new'),
|
|
719
|
-
await i18n.t('menu.api_management.
|
|
720
|
-
await i18n.t('menu.api_management.
|
|
721
|
-
await i18n.t('menu.api_management.
|
|
729
|
+
await i18n.t('menu.api_management.add_new'), // 0
|
|
730
|
+
await i18n.t('menu.api_management.edit'), // 1
|
|
731
|
+
await i18n.t('menu.api_management.remove'), // 2
|
|
732
|
+
await i18n.t('menu.api_management.switch'), // 3
|
|
733
|
+
await i18n.t('menu.api_management.statistics'), // 4
|
|
734
|
+
await i18n.t('menu.api_management.manual_upgrade') // 5
|
|
722
735
|
];
|
|
723
736
|
|
|
724
737
|
// Add import/export options only if password is set
|
|
725
738
|
if (apiManager.canUseImportExport()) {
|
|
726
|
-
menuOptions.push(await i18n.t('menu.api_management.export')); //
|
|
727
|
-
menuOptions.push(await i18n.t('menu.api_management.import')); //
|
|
728
|
-
menuOptions.push(await i18n.t('menu.api_management.change_password')); //
|
|
739
|
+
menuOptions.push(await i18n.t('menu.api_management.export')); // 6
|
|
740
|
+
menuOptions.push(await i18n.t('menu.api_management.import')); // 7
|
|
741
|
+
menuOptions.push(await i18n.t('menu.api_management.change_password')); // 8
|
|
729
742
|
}
|
|
730
743
|
|
|
731
|
-
|
|
732
|
-
menuOptions.push(await i18n.t('model_upgrade.settings_title')); // 4 or 7 (depending on import/export)
|
|
733
|
-
|
|
734
|
-
menuOptions.push(await i18n.t('menu.api_management.back')); // 5 or 8
|
|
744
|
+
menuOptions.push(await i18n.t('menu.api_management.back')); // last
|
|
735
745
|
|
|
736
746
|
// Ensure global menus are initialized
|
|
737
747
|
initializeGlobalMenus();
|
|
738
748
|
|
|
739
749
|
globalApiManagementMenu.setOptions(menuOptions);
|
|
740
750
|
|
|
741
|
-
const
|
|
751
|
+
const hintCallback = (index) => {
|
|
752
|
+
if (!apiManager.hasExportPassword()) return null;
|
|
753
|
+
const passwordHints = {};
|
|
754
|
+
passwordHints[1] = i18n.tSync('hints.edit_password_required');
|
|
755
|
+
passwordHints[2] = i18n.tSync('hints.remove_password_required');
|
|
756
|
+
if (apiManager.canUseImportExport()) {
|
|
757
|
+
passwordHints[6] = i18n.tSync('hints.export_password_required');
|
|
758
|
+
passwordHints[7] = i18n.tSync('hints.import_password_required');
|
|
759
|
+
}
|
|
760
|
+
return passwordHints[index] || null;
|
|
761
|
+
};
|
|
762
|
+
|
|
763
|
+
const choice = await globalApiManagementMenu.navigate(null, hintCallback);
|
|
742
764
|
|
|
743
765
|
// Handle menu choices based on current menu options
|
|
744
766
|
if (choice === 0) { // Add New API
|
|
745
767
|
await addNewThirdPartyApi();
|
|
746
768
|
return showMenu();
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
if (choice === 1) { // Edit API
|
|
772
|
+
if (await passwordGuard(apiManager, 'edit')) {
|
|
773
|
+
await editApi(apiManager);
|
|
774
|
+
}
|
|
775
|
+
return showApiManagementMenu();
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
if (choice === 2) { // Remove API
|
|
779
|
+
if (await passwordGuard(apiManager, 'delete')) {
|
|
780
|
+
await removeThirdPartyApi();
|
|
781
|
+
}
|
|
782
|
+
return showApiManagementMenu();
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
if (choice === 3) { // Switch Active API
|
|
751
786
|
await switchThirdPartyApi();
|
|
752
787
|
return showMenu();
|
|
753
|
-
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
if (choice === 4) { // View API Statistics
|
|
754
791
|
await viewStatistics();
|
|
755
792
|
return showMenu();
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
if (choice === 5) { // Manual Model Upgrade
|
|
796
|
+
await performManualUpgrade();
|
|
797
|
+
return showApiManagementMenu();
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
if (apiManager.canUseImportExport()) {
|
|
801
|
+
if (choice === 6) { // Export Configuration
|
|
759
802
|
await exportConfiguration();
|
|
760
|
-
return
|
|
761
|
-
}
|
|
803
|
+
return showApiManagementMenu();
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
if (choice === 7) { // Import Configuration
|
|
762
807
|
await importConfiguration();
|
|
763
|
-
return
|
|
764
|
-
} else if (choice === 6) { // Change Password
|
|
765
|
-
await changePassword();
|
|
766
|
-
return showMenu();
|
|
767
|
-
} else if (choice === 7) { // Model Upgrade Settings (NEW)
|
|
768
|
-
return await showModelUpgradeSettings();
|
|
769
|
-
} else if (choice === 8) { // Back to Main Menu
|
|
770
|
-
return showMenu();
|
|
808
|
+
return showApiManagementMenu();
|
|
771
809
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
return await showModelUpgradeSettings();
|
|
776
|
-
} else if (choice === 5) { // Back to Main Menu
|
|
810
|
+
|
|
811
|
+
if (choice === 8) { // Change Password
|
|
812
|
+
await changePassword();
|
|
777
813
|
return showMenu();
|
|
778
814
|
}
|
|
779
815
|
}
|
|
780
816
|
|
|
781
|
-
//
|
|
817
|
+
// Back to Main Menu (last item) or default fallback
|
|
782
818
|
return showMenu();
|
|
783
819
|
}
|
|
784
820
|
|
|
785
|
-
/**
|
|
786
|
-
* Show model upgrade settings menu
|
|
787
|
-
*/
|
|
788
|
-
async function showModelUpgradeSettings() {
|
|
789
|
-
const versionChecker = require('./lib/utils/version-checker');
|
|
790
|
-
const upgradeChecker = require('./lib/utils/model-upgrade-checker');
|
|
791
|
-
|
|
792
|
-
console.clear();
|
|
793
|
-
console.log('');
|
|
794
|
-
console.log(colors.bright + colors.orange + '⚙️ ' + await i18n.t('model_upgrade.settings_title') + colors.reset);
|
|
795
|
-
console.log('');
|
|
796
|
-
|
|
797
|
-
const config = await versionChecker.loadConfig();
|
|
798
|
-
const isAutoOn = config.autoModelUpgrade === true;
|
|
799
|
-
|
|
800
|
-
console.log(colors.cyan + ' ' + await i18n.t('model_upgrade.current_config') + ':' + colors.reset);
|
|
801
|
-
console.log(colors.gray + ' ' + await i18n.t('model_upgrade.auto_upgrade_label') + ': ' +
|
|
802
|
-
(isAutoOn
|
|
803
|
-
? colors.green + await i18n.t('model_upgrade.auto_upgrade_on')
|
|
804
|
-
: colors.dim + await i18n.t('model_upgrade.auto_upgrade_off')
|
|
805
|
-
) + colors.reset);
|
|
806
|
-
console.log('');
|
|
807
|
-
|
|
808
|
-
const menuOptions = [
|
|
809
|
-
isAutoOn
|
|
810
|
-
? await i18n.t('model_upgrade.menu_toggle_auto_on')
|
|
811
|
-
: await i18n.t('model_upgrade.menu_toggle_auto_off'),
|
|
812
|
-
await i18n.t('model_upgrade.menu_manual_upgrade'),
|
|
813
|
-
await i18n.t('model_upgrade.menu_back')
|
|
814
|
-
];
|
|
815
|
-
|
|
816
|
-
initializeGlobalMenus();
|
|
817
|
-
globalApiManagementMenu.setOptions(menuOptions);
|
|
818
|
-
const choice = await globalApiManagementMenu.navigate();
|
|
819
|
-
|
|
820
|
-
switch (choice) {
|
|
821
|
-
case 0: // Toggle auto upgrade
|
|
822
|
-
await versionChecker.setAutoModelUpgrade(!isAutoOn);
|
|
823
|
-
console.log('');
|
|
824
|
-
console.log(colors.green + '✓ ' + await i18n.t('model_upgrade.auto_upgrade_label') + ': ' +
|
|
825
|
-
(!isAutoOn
|
|
826
|
-
? await i18n.t('model_upgrade.auto_upgrade_on')
|
|
827
|
-
: await i18n.t('model_upgrade.auto_upgrade_off')
|
|
828
|
-
) + colors.reset);
|
|
829
|
-
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
830
|
-
return showModelUpgradeSettings();
|
|
831
|
-
|
|
832
|
-
case 1: // Manual upgrade
|
|
833
|
-
await performManualUpgrade();
|
|
834
|
-
return showModelUpgradeSettings();
|
|
835
|
-
|
|
836
|
-
case 2: // Back
|
|
837
|
-
case -1:
|
|
838
|
-
default:
|
|
839
|
-
return showApiManagementMenu();
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
|
|
843
821
|
/**
|
|
844
822
|
* Perform manual upgrade for all APIs with interactive confirmation
|
|
845
823
|
*/
|
|
@@ -847,21 +825,26 @@ async function performManualUpgrade() {
|
|
|
847
825
|
const { getLatestModel, getProvider } = require('./lib/presets/providers');
|
|
848
826
|
const { simpleInput } = require('./lib/ui/prompts');
|
|
849
827
|
|
|
850
|
-
console.clear();
|
|
851
|
-
console.log('');
|
|
852
|
-
console.log(colors.bright + colors.orange + '🔄 ' + await i18n.t('model_upgrade.manual_title') + colors.reset);
|
|
853
|
-
console.log('');
|
|
854
|
-
|
|
855
828
|
const apis = apiManager.getApis();
|
|
856
829
|
|
|
857
830
|
if (apis.length === 0) {
|
|
858
|
-
|
|
831
|
+
screen.render([
|
|
832
|
+
'',
|
|
833
|
+
colors.bright + colors.orange + '🔄 ' + await i18n.t('model_upgrade.manual_title') + colors.reset,
|
|
834
|
+
'',
|
|
835
|
+
colors.yellow + ' ' + await i18n.t('messages.info.no_apis') + colors.reset,
|
|
836
|
+
]);
|
|
859
837
|
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
860
838
|
return;
|
|
861
839
|
}
|
|
862
840
|
|
|
863
|
-
|
|
864
|
-
|
|
841
|
+
screen.render([
|
|
842
|
+
'',
|
|
843
|
+
colors.bright + colors.orange + '🔄 ' + await i18n.t('model_upgrade.manual_title') + colors.reset,
|
|
844
|
+
'',
|
|
845
|
+
colors.gray + ' ' + await i18n.t('model_upgrade.manual_checking', apis.length) + colors.reset,
|
|
846
|
+
'',
|
|
847
|
+
]);
|
|
865
848
|
|
|
866
849
|
let upgradedCount = 0;
|
|
867
850
|
let skippedUpToDate = 0;
|
|
@@ -872,87 +855,164 @@ async function performManualUpgrade() {
|
|
|
872
855
|
const api = apis[i];
|
|
873
856
|
const latestModel = getLatestModel(api.model, api.provider);
|
|
874
857
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
858
|
+
screen.write(colors.cyan + ' ─────────────────────────────────────────────────' + colors.reset + '\n');
|
|
859
|
+
screen.write(colors.bright + ` ${i + 1}/${apis.length} ${api.name}` + colors.reset + '\n');
|
|
860
|
+
screen.write(colors.gray + ' ' + await i18n.t('model_upgrade.manual_api_current', api.model) + colors.reset + '\n');
|
|
878
861
|
|
|
879
862
|
if (latestModel) {
|
|
880
|
-
|
|
881
|
-
|
|
863
|
+
screen.write(colors.green + ' ' + await i18n.t('model_upgrade.manual_api_latest', latestModel) + colors.reset + '\n');
|
|
864
|
+
screen.write('\n');
|
|
882
865
|
|
|
883
866
|
// Ask for confirmation
|
|
884
867
|
const answer = await simpleInput(colors.yellow + ' ' + await i18n.t('model_upgrade.manual_confirm') + ' ' + colors.reset);
|
|
885
868
|
|
|
886
869
|
if (answer.toLowerCase() === 'y') {
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
870
|
+
try {
|
|
871
|
+
apiManager.updateApiModel(api.id, latestModel);
|
|
872
|
+
screen.write(colors.green + ' ✓ ' + await i18n.t('model_upgrade.manual_upgraded', api.model, latestModel) + colors.reset + '\n');
|
|
873
|
+
upgradedCount++;
|
|
874
|
+
} catch (error) {
|
|
875
|
+
screen.write(colors.yellow + ' ⚠️ Skipped: ' + error.message + colors.reset + '\n');
|
|
876
|
+
skippedByUser++;
|
|
877
|
+
}
|
|
890
878
|
} else {
|
|
891
|
-
|
|
879
|
+
screen.write(colors.dim + ' ' + await i18n.t('model_upgrade.manual_skipped') + colors.reset + '\n');
|
|
892
880
|
skippedByUser++;
|
|
893
881
|
}
|
|
894
882
|
} else {
|
|
895
883
|
// No upgrade info available - check if model exists in provider
|
|
896
884
|
const provider = getProvider(api.provider);
|
|
897
885
|
if (provider && provider.models && provider.models.includes(api.model)) {
|
|
898
|
-
|
|
899
|
-
console.log(colors.dim + ' ' + await i18n.t('model_upgrade.manual_api_uptodate') + colors.reset);
|
|
886
|
+
screen.write(colors.dim + ' ' + await i18n.t('model_upgrade.manual_api_uptodate') + colors.reset + '\n');
|
|
900
887
|
skippedUpToDate++;
|
|
901
888
|
} else {
|
|
902
|
-
|
|
889
|
+
screen.write(colors.dim + ' ' + await i18n.t('model_upgrade.manual_api_no_info') + colors.reset + '\n');
|
|
903
890
|
skippedNoInfo++;
|
|
904
891
|
}
|
|
905
892
|
}
|
|
906
893
|
|
|
907
|
-
|
|
894
|
+
screen.write('\n');
|
|
908
895
|
}
|
|
909
896
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
897
|
+
screen.write(colors.cyan + ' ─────────────────────────────────────────────────' + colors.reset + '\n');
|
|
898
|
+
screen.write('\n');
|
|
899
|
+
screen.write(colors.green + ' ' + await i18n.t('model_upgrade.manual_complete') + colors.reset + '\n');
|
|
900
|
+
screen.write(colors.gray + ' ' + await i18n.t('model_upgrade.manual_stats_upgraded', upgradedCount) + colors.reset + '\n');
|
|
901
|
+
screen.write(colors.gray + ' ' + await i18n.t('model_upgrade.manual_stats_skipped',
|
|
915
902
|
skippedUpToDate + skippedNoInfo + skippedByUser,
|
|
916
903
|
skippedUpToDate,
|
|
917
|
-
skippedNoInfo) + colors.reset);
|
|
918
|
-
|
|
904
|
+
skippedNoInfo) + colors.reset + '\n');
|
|
905
|
+
screen.write('\n');
|
|
919
906
|
|
|
920
907
|
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
921
908
|
}
|
|
922
909
|
|
|
923
910
|
/**
|
|
924
|
-
*
|
|
911
|
+
* Show API selection menu for select launch mode
|
|
925
912
|
*/
|
|
926
|
-
async function
|
|
927
|
-
|
|
928
|
-
|
|
913
|
+
async function showApiSelectMenu(skipPermissions = false) {
|
|
914
|
+
const { padStringToWidth, getStringWidth } = require('./lib/utils/string-width');
|
|
915
|
+
const { getProvider } = require('./lib/presets/providers');
|
|
929
916
|
|
|
930
|
-
|
|
931
|
-
console.clear();
|
|
932
|
-
showInfo(i18n.tSync('launch.no_active_api'), [
|
|
933
|
-
i18n.tSync('launch.no_active_api_desc'),
|
|
934
|
-
i18n.tSync('launch.add_configure_first')
|
|
935
|
-
]);
|
|
917
|
+
const apis = apiManager.getApis();
|
|
936
918
|
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
919
|
+
if (apis.length === 0) {
|
|
920
|
+
showInfo(i18n.tSync('hints.no_api_configured'), []);
|
|
921
|
+
await waitForKey(i18n.tSync('messages.prompts.press_any_key'));
|
|
922
|
+
return showMenu();
|
|
923
|
+
}
|
|
940
924
|
|
|
941
|
-
|
|
942
|
-
apiManager.recordSuccessfulLaunch();
|
|
925
|
+
const activeApi = apiManager.getActiveApi();
|
|
943
926
|
|
|
944
|
-
|
|
927
|
+
// Build menu items with two-column alignment
|
|
928
|
+
// Compute max name width (including ● prefix) for consistent column alignment
|
|
929
|
+
let maxNameWidth = 0;
|
|
930
|
+
apis.forEach(api => {
|
|
931
|
+
const w = getStringWidth('● ' + api.name);
|
|
932
|
+
if (w > maxNameWidth) maxNameWidth = w;
|
|
933
|
+
});
|
|
945
934
|
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
935
|
+
const menuItems = apis.map((api) => {
|
|
936
|
+
const prefix = (activeApi && activeApi.id === api.id) ? '● ' : ' ';
|
|
937
|
+
const nameCol = prefix + api.name;
|
|
938
|
+
return padStringToWidth(nameCol, maxNameWidth + 4) + api.model;
|
|
939
|
+
});
|
|
940
|
+
menuItems.push(i18n.tSync('menu.api_select.back'));
|
|
941
|
+
|
|
942
|
+
// Hint callback for each API item
|
|
943
|
+
const hintCallback = (selectedIndex) => {
|
|
944
|
+
if (selectedIndex >= apis.length) return null; // "Back" item
|
|
945
|
+
const api = apis[selectedIndex];
|
|
946
|
+
const providerConfig = getProvider(api.provider);
|
|
947
|
+
const providerName = providerConfig ? providerConfig.name : (api.provider || 'Custom');
|
|
948
|
+
const lastUsed = formatRelativeTime(api.lastUsed || null);
|
|
949
|
+
const usageCount = api.usageCount || 0;
|
|
950
|
+
return i18n.tSync('hints.api_select.info', api.name) + '\n' +
|
|
951
|
+
i18n.tSync('hints.api_select.detail', providerName, api.model) + '\n' +
|
|
952
|
+
i18n.tSync('hints.api_select.usage', usageCount, lastUsed);
|
|
953
|
+
};
|
|
949
954
|
|
|
950
|
-
|
|
955
|
+
screen.render([
|
|
956
|
+
'',
|
|
957
|
+
colors.bright + colors.orange + '🔗 ' + i18n.tSync('menu.api_select.title') + colors.reset,
|
|
958
|
+
'',
|
|
959
|
+
]);
|
|
951
960
|
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
961
|
+
const selectMenu = new Menu();
|
|
962
|
+
selectMenu.setOptions(menuItems);
|
|
963
|
+
const choice = await selectMenu.navigate(null, hintCallback);
|
|
964
|
+
|
|
965
|
+
if (choice === -1 || choice === apis.length) {
|
|
966
|
+
// Back or Esc
|
|
967
|
+
return showMenu();
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// User selected an API
|
|
971
|
+
const selectedApi = apis[choice];
|
|
972
|
+
apiManager.setActiveApi(choice);
|
|
973
|
+
apiManager.recordLaunchAttempt();
|
|
974
|
+
screen.exitForHandoff();
|
|
975
|
+
launchClaudeWithApi(selectedApi, skipPermissions, {
|
|
976
|
+
rollbackFn: (errorMessage) => apiManager.rollbackLaunchAttempt(errorMessage)
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
/**
|
|
981
|
+
* Handle third-party API launch
|
|
982
|
+
*/
|
|
983
|
+
async function handleThirdPartyApiLaunch(skipPermissions = false) {
|
|
984
|
+
const { loadConfigSync } = require('./lib/utils/version-checker');
|
|
985
|
+
const config = loadConfigSync();
|
|
986
|
+
|
|
987
|
+
if (config.apiLaunchMode === 'select') {
|
|
988
|
+
return showApiSelectMenu(skipPermissions);
|
|
955
989
|
}
|
|
990
|
+
|
|
991
|
+
// Direct mode
|
|
992
|
+
const apis = apiManager.getApis();
|
|
993
|
+
|
|
994
|
+
if (apis.length === 0) {
|
|
995
|
+
showInfo(i18n.tSync('hints.no_api_configured'), []);
|
|
996
|
+
await waitForKey(i18n.tSync('messages.prompts.press_any_key'));
|
|
997
|
+
return showMenu();
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
const activeApi = apiManager.getActiveApi();
|
|
1001
|
+
|
|
1002
|
+
if (activeApi === null) {
|
|
1003
|
+
showInfo(i18n.tSync('launch.no_active_api'), [
|
|
1004
|
+
i18n.tSync('launch.no_active_api_desc'),
|
|
1005
|
+
i18n.tSync('launch.add_configure_first')
|
|
1006
|
+
]);
|
|
1007
|
+
await waitForKey(i18n.tSync('launch.press_key_return'));
|
|
1008
|
+
return showMenu();
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
apiManager.recordLaunchAttempt();
|
|
1012
|
+
screen.exitForHandoff();
|
|
1013
|
+
launchClaudeWithApi(activeApi, skipPermissions, {
|
|
1014
|
+
rollbackFn: (errorMessage) => apiManager.rollbackLaunchAttempt(errorMessage)
|
|
1015
|
+
});
|
|
956
1016
|
}
|
|
957
1017
|
|
|
958
1018
|
/**
|
|
@@ -961,14 +1021,17 @@ async function handleThirdPartyApiLaunch(skipPermissions = false) {
|
|
|
961
1021
|
async function executeSelection(selectedIndex) {
|
|
962
1022
|
switch (selectedIndex) {
|
|
963
1023
|
case 0: // Launch Claude Code
|
|
1024
|
+
screen.exitForHandoff();
|
|
964
1025
|
launchClaudeDefault();
|
|
965
1026
|
break;
|
|
966
1027
|
|
|
967
1028
|
case 1: // Launch Claude Code (Skip Permissions)
|
|
1029
|
+
screen.exitForHandoff();
|
|
968
1030
|
launchClaudeSkipPermissions();
|
|
969
1031
|
break;
|
|
970
1032
|
|
|
971
1033
|
case 2: // Launch Claude Code (Enable Auto Mode)
|
|
1034
|
+
screen.exitForHandoff();
|
|
972
1035
|
launchClaudeAutoMode();
|
|
973
1036
|
break;
|
|
974
1037
|
|
|
@@ -983,15 +1046,16 @@ async function executeSelection(selectedIndex) {
|
|
|
983
1046
|
case 5: // 3rd-party API Management
|
|
984
1047
|
return await showApiManagementMenu();
|
|
985
1048
|
|
|
986
|
-
case 6: //
|
|
987
|
-
return await
|
|
1049
|
+
case 6: // Configuration Management
|
|
1050
|
+
return await showConfigManagement();
|
|
988
1051
|
|
|
989
1052
|
case 7: // Version Update Check
|
|
990
1053
|
return await showVersionUpdateCheck();
|
|
991
1054
|
|
|
992
1055
|
case 8: // Exit
|
|
993
|
-
|
|
994
|
-
|
|
1056
|
+
screen.write('\n');
|
|
1057
|
+
screen.write(colors.green + '👋 ' + await i18n.t('menu.main.exit') + '!' + colors.reset + '\n');
|
|
1058
|
+
screen.exit();
|
|
995
1059
|
process.exit(0);
|
|
996
1060
|
break;
|
|
997
1061
|
|
|
@@ -1001,6 +1065,103 @@ async function executeSelection(selectedIndex) {
|
|
|
1001
1065
|
}
|
|
1002
1066
|
}
|
|
1003
1067
|
|
|
1068
|
+
/**
|
|
1069
|
+
* Show configuration management submenu
|
|
1070
|
+
*/
|
|
1071
|
+
async function showConfigManagement(restoreIndex = 0) {
|
|
1072
|
+
const versionChecker = require('./lib/utils/version-checker');
|
|
1073
|
+
const { getStringWidth, padStringToWidth } = require('./lib/utils/string-width');
|
|
1074
|
+
|
|
1075
|
+
const config = await versionChecker.loadConfig();
|
|
1076
|
+
|
|
1077
|
+
// Build labels with padding to align current values
|
|
1078
|
+
const labelWidth = 40;
|
|
1079
|
+
|
|
1080
|
+
const langName = i18n.getCurrentLanguageName();
|
|
1081
|
+
const autoUpgradeVal = config.autoModelUpgrade
|
|
1082
|
+
? i18n.tSync('config.values.on')
|
|
1083
|
+
: i18n.tSync('config.values.off');
|
|
1084
|
+
const upgradeNotifVal = config.showModelUpgradeNotification !== false
|
|
1085
|
+
? i18n.tSync('config.values.on')
|
|
1086
|
+
: i18n.tSync('config.values.off');
|
|
1087
|
+
const telemetryVal = config.disableTelemetry !== false
|
|
1088
|
+
? i18n.tSync('config.values.recommended_off')
|
|
1089
|
+
: i18n.tSync('config.values.on');
|
|
1090
|
+
const launchModeVal = config.apiLaunchMode === 'select'
|
|
1091
|
+
? i18n.tSync('config.values.select_mode')
|
|
1092
|
+
: i18n.tSync('config.values.direct_mode');
|
|
1093
|
+
|
|
1094
|
+
const menuOptions = [
|
|
1095
|
+
padStringToWidth(i18n.tSync('menu.config.language'), labelWidth) + langName,
|
|
1096
|
+
padStringToWidth(i18n.tSync('menu.config.auto_model_upgrade'), labelWidth) + autoUpgradeVal,
|
|
1097
|
+
padStringToWidth(i18n.tSync('menu.config.model_upgrade_notification'), labelWidth) + upgradeNotifVal,
|
|
1098
|
+
padStringToWidth(i18n.tSync('menu.config.telemetry'), labelWidth) + telemetryVal,
|
|
1099
|
+
padStringToWidth(i18n.tSync('menu.config.api_launch_mode'), labelWidth) + launchModeVal,
|
|
1100
|
+
i18n.tSync('menu.config.back')
|
|
1101
|
+
];
|
|
1102
|
+
|
|
1103
|
+
// Synchronous hint callback for config items
|
|
1104
|
+
const hintCallback = (selectedIndex) => {
|
|
1105
|
+
switch (selectedIndex) {
|
|
1106
|
+
case 0: return i18n.tSync('hints.config.language', langName);
|
|
1107
|
+
case 1: return i18n.tSync('hints.config.auto_upgrade');
|
|
1108
|
+
case 2: return i18n.tSync('hints.config.upgrade_notification');
|
|
1109
|
+
case 3: return i18n.tSync('hints.config.telemetry');
|
|
1110
|
+
case 4: return i18n.tSync('hints.config.launch_mode');
|
|
1111
|
+
default: return null;
|
|
1112
|
+
}
|
|
1113
|
+
};
|
|
1114
|
+
|
|
1115
|
+
screen.render([
|
|
1116
|
+
'',
|
|
1117
|
+
colors.bright + colors.orange + '⚙️ ' + i18n.tSync('menu.config.title') + colors.reset,
|
|
1118
|
+
'',
|
|
1119
|
+
]);
|
|
1120
|
+
|
|
1121
|
+
initializeGlobalMenus();
|
|
1122
|
+
globalConfigMenu.setOptions(menuOptions);
|
|
1123
|
+
globalConfigMenu.selectedIndex = restoreIndex;
|
|
1124
|
+
const choice = await globalConfigMenu.navigate(null, hintCallback);
|
|
1125
|
+
|
|
1126
|
+
switch (choice) {
|
|
1127
|
+
case 0: // Language Settings
|
|
1128
|
+
return await showLanguageSettings();
|
|
1129
|
+
|
|
1130
|
+
case 1: { // Auto Model Upgrade toggle
|
|
1131
|
+
const newVal = !config.autoModelUpgrade;
|
|
1132
|
+
config.autoModelUpgrade = newVal;
|
|
1133
|
+
await versionChecker.saveConfig(config);
|
|
1134
|
+
return showConfigManagement(1);
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
case 2: { // Model Upgrade Notification toggle
|
|
1138
|
+
const newVal = config.showModelUpgradeNotification === false ? true : false;
|
|
1139
|
+
config.showModelUpgradeNotification = newVal;
|
|
1140
|
+
await versionChecker.saveConfig(config);
|
|
1141
|
+
return showConfigManagement(2);
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
case 3: { // Telemetry toggle
|
|
1145
|
+
const newVal = config.disableTelemetry === false ? true : false;
|
|
1146
|
+
config.disableTelemetry = newVal;
|
|
1147
|
+
await versionChecker.saveConfig(config);
|
|
1148
|
+
return showConfigManagement(3);
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
case 4: { // API Launch Mode toggle
|
|
1152
|
+
const newVal = config.apiLaunchMode === 'select' ? 'direct' : 'select';
|
|
1153
|
+
config.apiLaunchMode = newVal;
|
|
1154
|
+
await versionChecker.saveConfig(config);
|
|
1155
|
+
return showConfigManagement(4);
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
case 5: // Back
|
|
1159
|
+
case -1:
|
|
1160
|
+
default:
|
|
1161
|
+
return showMenu();
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1004
1165
|
/**
|
|
1005
1166
|
* Show main menu
|
|
1006
1167
|
*/
|
|
@@ -1047,6 +1208,9 @@ async function showMenu() {
|
|
|
1047
1208
|
try {
|
|
1048
1209
|
const upgradeChecker = require('./lib/utils/model-upgrade-checker');
|
|
1049
1210
|
const autoUpgrade = await upgradeChecker.isAutoUpgradeEnabled();
|
|
1211
|
+
const versionChecker = require('./lib/utils/version-checker');
|
|
1212
|
+
const launcherConfig = await versionChecker.loadConfig();
|
|
1213
|
+
const showUpgradeNotif = launcherConfig.showModelUpgradeNotification !== false;
|
|
1050
1214
|
|
|
1051
1215
|
if (autoUpgrade) {
|
|
1052
1216
|
// Auto upgrade enabled: always check and upgrade (bypass cache)
|
|
@@ -1062,8 +1226,8 @@ async function showMenu() {
|
|
|
1062
1226
|
}
|
|
1063
1227
|
}
|
|
1064
1228
|
}
|
|
1065
|
-
} else {
|
|
1066
|
-
// Auto upgrade disabled: use cache for notification
|
|
1229
|
+
} else if (showUpgradeNotif) {
|
|
1230
|
+
// Auto upgrade disabled but notification enabled: use cache for notification
|
|
1067
1231
|
const result = await upgradeChecker.checkForModelUpgrades(apiManager);
|
|
1068
1232
|
if (result.needsCheck && result.upgrades.length > 0) {
|
|
1069
1233
|
const first = result.upgrades[0];
|
|
@@ -1102,40 +1266,106 @@ async function showMenu() {
|
|
|
1102
1266
|
await i18n.t('menu.main.launch_api'),
|
|
1103
1267
|
await i18n.t('menu.main.launch_api_skip'),
|
|
1104
1268
|
await i18n.t('menu.main.api_management'),
|
|
1105
|
-
await i18n.t('menu.main.
|
|
1269
|
+
await i18n.t('menu.main.config_management'),
|
|
1106
1270
|
await i18n.t('menu.main.version_check'),
|
|
1107
1271
|
await i18n.t('menu.main.exit')
|
|
1108
1272
|
];
|
|
1109
1273
|
|
|
1110
|
-
// Pre-compute hint
|
|
1274
|
+
// Pre-compute all hint data synchronously for menu callback
|
|
1111
1275
|
const hintAutoMode = i18n.tSync('hints.auto_mode_info');
|
|
1276
|
+
|
|
1112
1277
|
const activeApi = apiManager.getActiveApi();
|
|
1113
|
-
|
|
1278
|
+
const apis = apiManager.getApis();
|
|
1279
|
+
const apiCount = apis.length;
|
|
1280
|
+
const { getProvider } = require('./lib/presets/providers');
|
|
1281
|
+
|
|
1282
|
+
// Pre-compute config for hints
|
|
1283
|
+
const hintConfig = require('./lib/utils/version-checker').loadConfigSync();
|
|
1284
|
+
const hintApiLaunchMode = hintConfig.apiLaunchMode || 'direct';
|
|
1285
|
+
|
|
1286
|
+
// Pre-compute active API details
|
|
1287
|
+
let activeApiName = i18n.tSync('hints.select_mode_active_none');
|
|
1288
|
+
let activeProviderName = '';
|
|
1289
|
+
let activeModel = '';
|
|
1290
|
+
let activeLastUsed = '';
|
|
1114
1291
|
if (activeApi) {
|
|
1115
|
-
const { getProvider } = require('./lib/presets/providers');
|
|
1116
1292
|
const providerConfig = getProvider(activeApi.provider);
|
|
1117
|
-
|
|
1118
|
-
|
|
1293
|
+
activeProviderName = providerConfig ? providerConfig.name : (activeApi.provider || 'Custom');
|
|
1294
|
+
activeModel = activeApi.model;
|
|
1295
|
+
activeApiName = activeApi.name;
|
|
1296
|
+
activeLastUsed = formatRelativeTime(activeApi.lastUsed || null);
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
// Pre-compute API hints for indices 3, 4
|
|
1300
|
+
let hintApiLines = null;
|
|
1301
|
+
if (apiCount === 0) {
|
|
1302
|
+
hintApiLines = i18n.tSync('hints.no_api_configured');
|
|
1303
|
+
} else if (hintApiLaunchMode === 'direct') {
|
|
1304
|
+
if (activeApi) {
|
|
1305
|
+
hintApiLines =
|
|
1306
|
+
i18n.tSync('hints.direct_mode_desc') + '\n' +
|
|
1307
|
+
i18n.tSync('hints.direct_mode_api_info', activeApiName, activeProviderName) + '\n' +
|
|
1308
|
+
i18n.tSync('hints.direct_mode_api_detail', activeModel, activeLastUsed) + '\n' +
|
|
1309
|
+
i18n.tSync('hints.direct_mode_change');
|
|
1310
|
+
} else {
|
|
1311
|
+
hintApiLines =
|
|
1312
|
+
i18n.tSync('hints.direct_mode_no_active') + '\n' +
|
|
1313
|
+
i18n.tSync('hints.direct_mode_no_active_detail', apiCount) + '\n' +
|
|
1314
|
+
'' + '\n' +
|
|
1315
|
+
i18n.tSync('hints.direct_mode_change');
|
|
1316
|
+
}
|
|
1119
1317
|
} else {
|
|
1120
|
-
|
|
1318
|
+
// select mode
|
|
1319
|
+
hintApiLines =
|
|
1320
|
+
i18n.tSync('hints.select_mode_desc') + '\n' +
|
|
1321
|
+
i18n.tSync('hints.select_mode_change') + '\n' +
|
|
1322
|
+
'' + '\n' +
|
|
1323
|
+
i18n.tSync('hints.select_mode_api_count', apiCount, activeApiName);
|
|
1121
1324
|
}
|
|
1122
1325
|
|
|
1326
|
+
// Pre-compute API management hint (index 5)
|
|
1327
|
+
const hintApiMgmt = i18n.tSync('hints.api_management_info', apiCount, activeApiName);
|
|
1328
|
+
|
|
1329
|
+
// Pre-compute config summary hint (index 6)
|
|
1330
|
+
const cfgLangName = i18n.getCurrentLanguageName();
|
|
1331
|
+
const cfgLaunchMode = hintApiLaunchMode === 'select'
|
|
1332
|
+
? i18n.tSync('config.values.select_mode')
|
|
1333
|
+
: i18n.tSync('config.values.direct_mode');
|
|
1334
|
+
const cfgTelemetry = hintConfig.disableTelemetry !== false
|
|
1335
|
+
? i18n.tSync('config.values.off')
|
|
1336
|
+
: i18n.tSync('config.values.on');
|
|
1337
|
+
const hintConfigSummary = i18n.tSync('hints.config_summary', cfgLangName, cfgLaunchMode, cfgTelemetry);
|
|
1338
|
+
|
|
1123
1339
|
// Synchronous hint callback — must not use await
|
|
1124
1340
|
const hintCallback = (selectedIndex) => {
|
|
1125
1341
|
switch (selectedIndex) {
|
|
1126
|
-
case
|
|
1127
|
-
case
|
|
1128
|
-
|
|
1129
|
-
|
|
1342
|
+
case 0:
|
|
1343
|
+
case 1:
|
|
1344
|
+
return null;
|
|
1345
|
+
case 2:
|
|
1346
|
+
return hintAutoMode;
|
|
1347
|
+
case 3:
|
|
1348
|
+
case 4:
|
|
1349
|
+
return hintApiLines;
|
|
1350
|
+
case 5:
|
|
1351
|
+
return hintApiMgmt;
|
|
1352
|
+
case 6:
|
|
1353
|
+
return hintConfigSummary;
|
|
1354
|
+
case 7:
|
|
1355
|
+
case 8:
|
|
1356
|
+
return null;
|
|
1357
|
+
default:
|
|
1358
|
+
return null;
|
|
1130
1359
|
}
|
|
1131
1360
|
};
|
|
1132
1361
|
|
|
1133
1362
|
globalMainMenu.setOptions(menuOptions);
|
|
1134
|
-
const selection = await globalMainMenu.navigate(
|
|
1363
|
+
const selection = await globalMainMenu.navigate(displayInfo || null, hintCallback);
|
|
1135
1364
|
|
|
1136
1365
|
if (selection === -1) {
|
|
1137
|
-
|
|
1138
|
-
|
|
1366
|
+
screen.write('\n');
|
|
1367
|
+
screen.write(colors.green + '👋 ' + await i18n.t('menu.main.exit') + '!' + colors.reset + '\n');
|
|
1368
|
+
screen.exit();
|
|
1139
1369
|
process.exit(0);
|
|
1140
1370
|
} else {
|
|
1141
1371
|
await executeSelection(selection);
|
|
@@ -1150,26 +1380,22 @@ async function showMenu() {
|
|
|
1150
1380
|
* Export configuration with password encryption
|
|
1151
1381
|
*/
|
|
1152
1382
|
async function exportConfiguration() {
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1383
|
+
screen.render([
|
|
1384
|
+
'',
|
|
1385
|
+
colors.bright + colors.orange + '💾 ' + await i18n.t('import_export.export.title') + colors.reset,
|
|
1386
|
+
'',
|
|
1387
|
+
// Export function description
|
|
1388
|
+
colors.cyan + '📄 ' + i18n.tSync('import_export.export.description_title') + colors.reset,
|
|
1389
|
+
colors.gray + ' • ' + i18n.tSync('import_export.export.description_items')[0] + colors.reset,
|
|
1390
|
+
colors.gray + ' • ' + i18n.tSync('import_export.export.description_items')[1] + colors.reset,
|
|
1391
|
+
colors.gray + ' • ' + i18n.tSync('import_export.export.description_items')[2] + colors.reset,
|
|
1392
|
+
colors.gray + ' • ' + i18n.tSync('import_export.export.description_items')[3] + colors.reset,
|
|
1393
|
+
'',
|
|
1394
|
+
]);
|
|
1165
1395
|
|
|
1166
1396
|
// Verify password before export
|
|
1167
|
-
const verified = await
|
|
1168
|
-
if (!verified)
|
|
1169
|
-
console.log('');
|
|
1170
|
-
await waitForKey(i18n.tSync('messages.prompts.press_any_key_menu'));
|
|
1171
|
-
return;
|
|
1172
|
-
}
|
|
1397
|
+
const verified = await passwordGuard(apiManager, 'export');
|
|
1398
|
+
if (!verified) return;
|
|
1173
1399
|
|
|
1174
1400
|
try {
|
|
1175
1401
|
// Get export data
|
|
@@ -1183,62 +1409,59 @@ async function exportConfiguration() {
|
|
|
1183
1409
|
// Write JSON file
|
|
1184
1410
|
fs.writeFileSync(filePath, exportData, 'utf8');
|
|
1185
1411
|
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1412
|
+
screen.write(colors.green + '✓ ' + i18n.tSync('import_export.export.success_title') + colors.reset + '\n');
|
|
1413
|
+
screen.write('\n');
|
|
1414
|
+
screen.write(colors.cyan + '📁 ' + i18n.tSync('import_export.export.details_title') + colors.reset + '\n');
|
|
1415
|
+
screen.write(colors.gray + ` • ` + i18n.tSync('import_export.export.details_file_saved', filePath) + colors.reset + '\n');
|
|
1416
|
+
screen.write(colors.gray + ` • ` + i18n.tSync('import_export.export.details_export_dir', exportDir) + colors.reset + '\n');
|
|
1417
|
+
screen.write(colors.gray + ` • ` + i18n.tSync('import_export.export.details_filename', filename) + colors.reset + '\n');
|
|
1418
|
+
screen.write('\n');
|
|
1193
1419
|
|
|
1194
1420
|
// Open file with default application
|
|
1195
|
-
|
|
1421
|
+
screen.write(colors.yellow + '🔍 ' + i18n.tSync('import_export.export.opening_file') + colors.reset + '\n');
|
|
1196
1422
|
openFileWithDefault(filePath);
|
|
1197
1423
|
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1424
|
+
screen.write('\n');
|
|
1425
|
+
screen.write(colors.cyan + '💡 ' + i18n.tSync('import_export.export.tips_title') + colors.reset + '\n');
|
|
1426
|
+
screen.write(colors.gray + ' • ' + i18n.tSync('import_export.export.tips_items')[0] + colors.reset + '\n');
|
|
1427
|
+
screen.write(colors.gray + ' • ' + i18n.tSync('import_export.export.tips_items')[1] + colors.reset + '\n');
|
|
1202
1428
|
|
|
1203
1429
|
} catch (error) {
|
|
1204
1430
|
forceStdinCleanup();
|
|
1205
|
-
|
|
1431
|
+
screen.write(colors.red + `❌ Export failed: ${error.message}` + colors.reset + '\n');
|
|
1206
1432
|
}
|
|
1207
1433
|
|
|
1208
|
-
|
|
1209
|
-
await waitForKey(i18n.tSync('
|
|
1434
|
+
screen.write('\n');
|
|
1435
|
+
await waitForKey(i18n.tSync('ui.general.press_any_key_continue'));
|
|
1210
1436
|
}
|
|
1211
1437
|
|
|
1212
1438
|
/**
|
|
1213
1439
|
* Import configuration from plaintext JSON
|
|
1214
1440
|
*/
|
|
1215
1441
|
async function importConfiguration() {
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1442
|
+
screen.render([
|
|
1443
|
+
'',
|
|
1444
|
+
colors.bright + colors.orange + '📥 ' + await i18n.t('import_export.import.title') + colors.reset,
|
|
1445
|
+
'',
|
|
1446
|
+
// Import function description
|
|
1447
|
+
colors.cyan + '📄 ' + i18n.tSync('ui.general.import_function_description') + colors.reset,
|
|
1448
|
+
colors.gray + ' • ' + i18n.tSync('import_export.export.description_items')[0] + colors.reset,
|
|
1449
|
+
colors.gray + ' • ' + i18n.tSync('ui.general.import_description_items')[0] + colors.reset,
|
|
1450
|
+
colors.gray + ' • ' + i18n.tSync('ui.general.import_description_items')[1] + colors.reset,
|
|
1451
|
+
colors.gray + ' • ' + i18n.tSync('ui.general.import_description_items')[2] + colors.reset,
|
|
1452
|
+
'',
|
|
1453
|
+
]);
|
|
1228
1454
|
|
|
1229
1455
|
// Verify password identity
|
|
1230
|
-
const passwordVerified = await
|
|
1231
|
-
if (!passwordVerified)
|
|
1232
|
-
await waitForKey(i18n.tSync('messages.prompts.press_any_key_menu'));
|
|
1233
|
-
return;
|
|
1234
|
-
}
|
|
1456
|
+
const passwordVerified = await passwordGuard(apiManager, 'import');
|
|
1457
|
+
if (!passwordVerified) return;
|
|
1235
1458
|
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1459
|
+
screen.write('\n');
|
|
1460
|
+
screen.write(colors.cyan + '📁 ' + i18n.tSync('ui.general.file_input_required') + colors.reset + '\n');
|
|
1461
|
+
screen.write(colors.gray + ' • ' + i18n.tSync('ui.general.file_input_items')[0] + colors.reset + '\n');
|
|
1462
|
+
screen.write(colors.gray + ' • ' + i18n.tSync('ui.general.file_input_items')[1] + colors.reset + '\n');
|
|
1463
|
+
screen.write(colors.gray + ' • ' + i18n.tSync('ui.general.file_input_items')[2] + colors.reset + '\n');
|
|
1464
|
+
screen.write('\n');
|
|
1242
1465
|
|
|
1243
1466
|
const { simpleInput } = require('./lib/ui/prompts');
|
|
1244
1467
|
|
|
@@ -1253,85 +1476,86 @@ async function importConfiguration() {
|
|
|
1253
1476
|
const filePath = await simpleInput(colors.green + filePrompt + colors.reset);
|
|
1254
1477
|
|
|
1255
1478
|
if (!filePath) {
|
|
1256
|
-
|
|
1479
|
+
screen.write(colors.red + i18n.tSync('ui.general.file_path_empty') + colors.reset + '\n');
|
|
1257
1480
|
if (attempts < maxAttempts) {
|
|
1258
|
-
|
|
1481
|
+
screen.write('\n');
|
|
1259
1482
|
continue;
|
|
1260
1483
|
} else {
|
|
1261
|
-
|
|
1484
|
+
screen.write(colors.red + i18n.tSync('ui.general.max_attempts_import_cancelled') + colors.reset + '\n');
|
|
1262
1485
|
await waitForKey(i18n.tSync('messages.prompts.press_any_key_menu'));
|
|
1263
1486
|
return;
|
|
1264
1487
|
}
|
|
1265
1488
|
}
|
|
1266
1489
|
|
|
1267
1490
|
// Validate file
|
|
1268
|
-
|
|
1269
|
-
|
|
1491
|
+
screen.write('\n');
|
|
1492
|
+
screen.write(colors.yellow + i18n.tSync('ui.general.validating_file') + colors.reset + '\n');
|
|
1270
1493
|
const validation = validateImportFile(filePath);
|
|
1271
1494
|
|
|
1272
1495
|
if (!validation.valid) {
|
|
1273
|
-
|
|
1496
|
+
screen.write(colors.red + '❌ ' + i18n.tSync('ui.general.file_validation_failed', validation.error) + colors.reset + '\n');
|
|
1274
1497
|
if (attempts < maxAttempts) {
|
|
1275
|
-
|
|
1276
|
-
|
|
1498
|
+
screen.write(colors.yellow + i18n.tSync('ui.general.check_file_path_json') + colors.reset + '\n');
|
|
1499
|
+
screen.write('\n');
|
|
1277
1500
|
continue;
|
|
1278
1501
|
} else {
|
|
1279
|
-
|
|
1502
|
+
screen.write(colors.red + i18n.tSync('ui.general.max_attempts_import_cancelled') + colors.reset + '\n');
|
|
1280
1503
|
await waitForKey(i18n.tSync('messages.prompts.press_any_key_menu'));
|
|
1281
1504
|
return;
|
|
1282
1505
|
}
|
|
1283
1506
|
}
|
|
1284
1507
|
|
|
1285
1508
|
// File is valid, proceed with import
|
|
1286
|
-
|
|
1287
|
-
|
|
1509
|
+
screen.write(colors.green + i18n.tSync('ui.general.file_validation_successful') + colors.reset + '\n');
|
|
1510
|
+
screen.write('\n');
|
|
1288
1511
|
|
|
1289
1512
|
try {
|
|
1290
1513
|
// Import the validated configuration data
|
|
1291
1514
|
const result = apiManager.importConfigAuthenticated(validation.data);
|
|
1292
1515
|
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1516
|
+
screen.write(colors.green + i18n.tSync('ui.general.import_successful') + colors.reset + '\n');
|
|
1517
|
+
screen.write('\n');
|
|
1518
|
+
screen.write(colors.cyan + i18n.tSync('ui.general.import_statistics') + colors.reset + '\n');
|
|
1296
1519
|
const importItems = i18n.tSync('ui.general.import_stats_items');
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1520
|
+
screen.write(colors.gray + ` • ` + importItems[0].replace('{0}', result.imported) + colors.reset + '\n');
|
|
1521
|
+
screen.write(colors.gray + ` • ` + importItems[1].replace('{1}', result.skipped) + colors.reset + '\n');
|
|
1522
|
+
screen.write(colors.gray + ` • ` + importItems[2] + colors.reset + '\n');
|
|
1523
|
+
screen.write(colors.gray + ` • ` + importItems[3].replace('{0}', path.resolve(filePath)) + colors.reset + '\n');
|
|
1301
1524
|
|
|
1302
1525
|
break; // Success, exit the loop
|
|
1303
1526
|
|
|
1304
1527
|
} catch (error) {
|
|
1305
1528
|
forceStdinCleanup();
|
|
1306
|
-
|
|
1529
|
+
screen.write(colors.red + `❌ Import failed: ${error.message}` + colors.reset + '\n');
|
|
1307
1530
|
if (attempts < maxAttempts) {
|
|
1308
|
-
|
|
1309
|
-
|
|
1531
|
+
screen.write(colors.yellow + i18n.tSync('ui.general.import_tips')[0] + colors.reset + '\n');
|
|
1532
|
+
screen.write('\n');
|
|
1310
1533
|
continue;
|
|
1311
1534
|
} else {
|
|
1312
|
-
|
|
1535
|
+
screen.write(colors.red + i18n.tSync('ui.general.max_attempts_import_failed') + colors.reset + '\n');
|
|
1313
1536
|
}
|
|
1314
1537
|
}
|
|
1315
1538
|
}
|
|
1316
1539
|
|
|
1317
|
-
|
|
1318
|
-
await waitForKey(i18n.tSync('
|
|
1540
|
+
screen.write('\n');
|
|
1541
|
+
await waitForKey(i18n.tSync('ui.general.press_any_key_continue'));
|
|
1319
1542
|
}
|
|
1320
1543
|
|
|
1321
1544
|
/**
|
|
1322
1545
|
* Change password
|
|
1323
1546
|
*/
|
|
1324
1547
|
async function changePassword() {
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1548
|
+
screen.render([
|
|
1549
|
+
'',
|
|
1550
|
+
colors.bright + colors.orange + '🔑 ' + i18n.tSync('errors.password.change_password_title') + colors.reset,
|
|
1551
|
+
'',
|
|
1552
|
+
]);
|
|
1329
1553
|
|
|
1330
1554
|
// Use unified password change module
|
|
1331
1555
|
const success = await changePasswordModule(apiManager);
|
|
1332
1556
|
|
|
1333
1557
|
if (success) {
|
|
1334
|
-
|
|
1558
|
+
screen.write('\n');
|
|
1335
1559
|
}
|
|
1336
1560
|
|
|
1337
1561
|
await waitForKey(i18n.tSync('messages.prompts.press_any_key_menu'));
|
|
@@ -1346,16 +1570,16 @@ async function showLanguageSettings() {
|
|
|
1346
1570
|
const currentLanguage = i18n.getCurrentLanguage();
|
|
1347
1571
|
const currentLanguageName = i18n.getCurrentLanguageName();
|
|
1348
1572
|
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1573
|
+
screen.render([
|
|
1574
|
+
'',
|
|
1575
|
+
colors.bright + colors.orange + '🌍 ' + await i18n.t('menu.language.title') + colors.reset,
|
|
1576
|
+
'',
|
|
1577
|
+
// Show current language
|
|
1578
|
+
colors.cyan + await i18n.t('menu.language.current', currentLanguageName) + colors.reset,
|
|
1579
|
+
'',
|
|
1580
|
+
colors.yellow + await i18n.t('menu.language.select_prompt') + colors.reset,
|
|
1581
|
+
'',
|
|
1582
|
+
]);
|
|
1359
1583
|
|
|
1360
1584
|
// Create menu options
|
|
1361
1585
|
const languageOptions = [];
|
|
@@ -1388,14 +1612,14 @@ async function showLanguageSettings() {
|
|
|
1388
1612
|
}
|
|
1389
1613
|
|
|
1390
1614
|
// Switch language
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1615
|
+
screen.render([
|
|
1616
|
+
'',
|
|
1617
|
+
colors.yellow + await i18n.t('status.switching_language') + colors.reset,
|
|
1618
|
+
]);
|
|
1394
1619
|
|
|
1395
1620
|
try {
|
|
1396
1621
|
await i18n.setLanguage(selectedLangCode);
|
|
1397
1622
|
|
|
1398
|
-
console.clear();
|
|
1399
1623
|
const newLanguageName = i18n.getCurrentLanguageName();
|
|
1400
1624
|
showSuccess(await i18n.t('messages.success.language_changed'), [
|
|
1401
1625
|
await i18n.t('menu.language.changed_success', newLanguageName)
|
|
@@ -1424,18 +1648,18 @@ async function showLanguageSettings() {
|
|
|
1424
1648
|
*/
|
|
1425
1649
|
async function showVersionUpdateCheck() {
|
|
1426
1650
|
try {
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1651
|
+
screen.render([
|
|
1652
|
+
'',
|
|
1653
|
+
colors.bright + colors.orange + '🔄 ' + await i18n.t('version_check.title') + colors.reset,
|
|
1654
|
+
'',
|
|
1655
|
+
colors.cyan + await i18n.t('version_check.checking') + colors.reset,
|
|
1656
|
+
colors.gray + await i18n.t('version_check.please_wait') + colors.reset,
|
|
1657
|
+
'',
|
|
1658
|
+
]);
|
|
1435
1659
|
|
|
1436
1660
|
// Show progress indicator
|
|
1437
1661
|
const progressInterval = setInterval(() => {
|
|
1438
|
-
|
|
1662
|
+
screen.write('.');
|
|
1439
1663
|
}, 500);
|
|
1440
1664
|
|
|
1441
1665
|
try {
|
|
@@ -1444,46 +1668,46 @@ async function showVersionUpdateCheck() {
|
|
|
1444
1668
|
|
|
1445
1669
|
// Stop progress indicator
|
|
1446
1670
|
clearInterval(progressInterval);
|
|
1447
|
-
|
|
1671
|
+
screen.write('\n\n');
|
|
1448
1672
|
|
|
1449
1673
|
if (result.error) {
|
|
1450
1674
|
// Handle errors (timeout, network, etc.)
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1675
|
+
screen.write(colors.red + '❌ ' + await i18n.t('version_check.error') + colors.reset + '\n');
|
|
1676
|
+
screen.write(colors.red + ' ' + result.error + colors.reset + '\n');
|
|
1677
|
+
screen.write('\n');
|
|
1678
|
+
screen.write(colors.gray + await i18n.t('version_check.error_tips') + colors.reset + '\n');
|
|
1455
1679
|
} else if (result.available) {
|
|
1456
1680
|
// Update available
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1681
|
+
screen.write(colors.yellow + '🎉 ' + await i18n.t('version_check.update_available') + colors.reset + '\n');
|
|
1682
|
+
screen.write('\n');
|
|
1683
|
+
screen.write(colors.cyan + ' ' + await i18n.t('version_check.current_version', result.currentVersion) + colors.reset + '\n');
|
|
1684
|
+
screen.write(colors.green + ' ' + await i18n.t('version_check.latest_version', result.latestVersion) + colors.reset + '\n');
|
|
1685
|
+
screen.write('\n');
|
|
1686
|
+
screen.write(colors.yellow + '💡 ' + await i18n.t('version_check.update_command') + colors.reset + '\n');
|
|
1687
|
+
screen.write(colors.yellow + ' npm update -g @kikkimo/claude-launcher' + colors.reset + '\n');
|
|
1464
1688
|
} else {
|
|
1465
1689
|
// Already up to date
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1690
|
+
screen.write(colors.green + '✅ ' + await i18n.t('version_check.up_to_date') + colors.reset + '\n');
|
|
1691
|
+
screen.write('\n');
|
|
1692
|
+
screen.write(colors.cyan + ' ' + await i18n.t('version_check.current_version', result.currentVersion) + colors.reset + '\n');
|
|
1693
|
+
screen.write(colors.cyan + ' ' + await i18n.t('version_check.latest_version', result.latestVersion) + colors.reset + '\n');
|
|
1470
1694
|
}
|
|
1471
1695
|
|
|
1472
1696
|
} catch (error) {
|
|
1473
1697
|
// Stop progress indicator
|
|
1474
1698
|
clearInterval(progressInterval);
|
|
1475
|
-
|
|
1699
|
+
screen.write('\n\n');
|
|
1476
1700
|
|
|
1477
|
-
|
|
1478
|
-
|
|
1701
|
+
screen.write(colors.red + '❌ ' + await i18n.t('version_check.unexpected_error') + colors.reset + '\n');
|
|
1702
|
+
screen.write(colors.red + ' ' + error.message + colors.reset + '\n');
|
|
1479
1703
|
}
|
|
1480
1704
|
|
|
1481
|
-
|
|
1705
|
+
screen.write('\n');
|
|
1482
1706
|
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
1483
1707
|
return showMenu();
|
|
1484
1708
|
|
|
1485
1709
|
} catch (error) {
|
|
1486
|
-
|
|
1710
|
+
screen.write(colors.red + '❌ Failed to check version: ' + error.message + colors.reset + '\n');
|
|
1487
1711
|
await waitForKey(i18n.tSync('messages.prompts.press_any_key_menu'));
|
|
1488
1712
|
return showMenu();
|
|
1489
1713
|
}
|
|
@@ -1495,8 +1719,9 @@ async function showVersionUpdateCheck() {
|
|
|
1495
1719
|
* Graceful shutdown handlers
|
|
1496
1720
|
*/
|
|
1497
1721
|
process.on('SIGTERM', () => {
|
|
1498
|
-
|
|
1499
|
-
|
|
1722
|
+
screen.write('\n');
|
|
1723
|
+
screen.write(colors.green + i18n.tSync('ui.general.goodbye') + colors.reset + '\n');
|
|
1724
|
+
screen.exit();
|
|
1500
1725
|
process.exit(0);
|
|
1501
1726
|
});
|
|
1502
1727
|
|
|
@@ -1509,43 +1734,34 @@ const stdinManager = require('./lib/utils/stdin-manager');
|
|
|
1509
1734
|
let exiting = false;
|
|
1510
1735
|
|
|
1511
1736
|
process.on('SIGINT', () => {
|
|
1512
|
-
// During Claude run, ignore in launcher so child handles it
|
|
1513
|
-
// Check this BEFORE setting exiting flag to avoid breaking reentrancy protection
|
|
1514
1737
|
if (stdinManager.isSuspended && stdinManager.isSuspended()) {
|
|
1515
1738
|
return;
|
|
1516
1739
|
}
|
|
1517
|
-
|
|
1518
|
-
// Prevent re-entrance - ensure cleanup runs only once
|
|
1519
|
-
if (exiting) {
|
|
1520
|
-
return;
|
|
1521
|
-
}
|
|
1740
|
+
if (exiting) return;
|
|
1522
1741
|
exiting = true;
|
|
1523
|
-
|
|
1524
|
-
// Try to reset stdin state before handling
|
|
1525
1742
|
try {
|
|
1526
1743
|
if (process.stdin.isTTY) {
|
|
1527
1744
|
process.stdin.setRawMode(false);
|
|
1528
1745
|
process.stdin.pause();
|
|
1529
1746
|
}
|
|
1530
|
-
} catch (_) {
|
|
1531
|
-
// Ignore errors during emergency cleanup
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
// Use unified Ctrl+C handler from StdinManager (synchronous)
|
|
1747
|
+
} catch (_) {}
|
|
1535
1748
|
try {
|
|
1536
|
-
stdinManager.handleCtrlC();
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
// this line won't be reached, which is expected behavior
|
|
1749
|
+
const shouldExit = stdinManager.handleCtrlC();
|
|
1750
|
+
if (shouldExit === false) {
|
|
1751
|
+
exiting = false;
|
|
1752
|
+
return;
|
|
1753
|
+
}
|
|
1754
|
+
} catch (_) {}
|
|
1755
|
+
screen.exit();
|
|
1544
1756
|
process.exit(130);
|
|
1545
1757
|
});
|
|
1546
1758
|
|
|
1759
|
+
process.on('uncaughtException', (err) => { screen.exit(); console.error(err); process.exit(1); });
|
|
1760
|
+
process.on('unhandledRejection', (err) => { screen.exit(); console.error(err); process.exit(1); });
|
|
1761
|
+
|
|
1547
1762
|
// Initialize global menus and start the application
|
|
1548
1763
|
initializeGlobalMenus();
|
|
1549
1764
|
|
|
1550
1765
|
// Start the application
|
|
1766
|
+
screen.enter();
|
|
1551
1767
|
showMenu();
|