@kikkimo/claude-launcher 2.4.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 +71 -0
- package/README.md +32 -17
- package/claude-launcher +638 -393
- package/docs/README-zh.md +32 -17
- 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 +93 -9
- package/lib/i18n/locales/en.js +93 -9
- package/lib/i18n/locales/es.js +93 -9
- package/lib/i18n/locales/fr.js +93 -9
- package/lib/i18n/locales/it.js +93 -9
- package/lib/i18n/locales/ja.js +93 -9
- package/lib/i18n/locales/ko.js +93 -9
- package/lib/i18n/locales/pt.js +93 -9
- package/lib/i18n/locales/ru.js +93 -9
- package/lib/i18n/locales/zh-TW.js +93 -9
- package/lib/i18n/locales/zh.js +93 -9
- package/lib/launcher.js +131 -93
- package/lib/presets/providers.js +39 -18
- package/lib/ui/api-editor.js +210 -0
- package/lib/ui/interactive-table.js +216 -99
- package/lib/ui/menu.js +78 -55
- 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/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
|
}
|
|
@@ -46,13 +46,16 @@ const {
|
|
|
46
46
|
const {
|
|
47
47
|
launchClaudeDefault,
|
|
48
48
|
launchClaudeSkipPermissions,
|
|
49
|
+
launchClaudeAutoMode,
|
|
49
50
|
launchClaudeWithApi
|
|
50
51
|
} = require('./lib/launcher');
|
|
51
52
|
const { getPasswordInput } = require('./lib/auth/password-input');
|
|
52
|
-
const {
|
|
53
|
+
const { passwordGuard, verifyCurrentPassword, setupNewPassword, changePassword: changePasswordModule } = require('./lib/auth/password-validator');
|
|
53
54
|
const { maskApiToken } = require('./lib/validators');
|
|
54
55
|
const { showApiSelectionTable, confirmDeletion } = require('./lib/ui/interactive-table');
|
|
56
|
+
const { editApi } = require('./lib/ui/api-editor');
|
|
55
57
|
const i18n = require('./lib/i18n');
|
|
58
|
+
const screen = require('./lib/ui/screen');
|
|
56
59
|
const fs = require('fs');
|
|
57
60
|
const path = require('path');
|
|
58
61
|
const os = require('os');
|
|
@@ -65,6 +68,7 @@ const apiManager = new ApiManager();
|
|
|
65
68
|
let globalMainMenu = null;
|
|
66
69
|
let globalConfirmMenu = null;
|
|
67
70
|
let globalApiManagementMenu = null;
|
|
71
|
+
let globalConfigMenu = null;
|
|
68
72
|
|
|
69
73
|
/**
|
|
70
74
|
* Initialize global menu objects to prevent recreation and screen flickering
|
|
@@ -79,6 +83,9 @@ function initializeGlobalMenus() {
|
|
|
79
83
|
if (!globalApiManagementMenu) {
|
|
80
84
|
globalApiManagementMenu = new Menu();
|
|
81
85
|
}
|
|
86
|
+
if (!globalConfigMenu) {
|
|
87
|
+
globalConfigMenu = new Menu();
|
|
88
|
+
}
|
|
82
89
|
}
|
|
83
90
|
|
|
84
91
|
/**
|
|
@@ -131,7 +138,7 @@ function openFileWithDefault(filePath) {
|
|
|
131
138
|
|
|
132
139
|
exec(command, (error) => {
|
|
133
140
|
if (error) {
|
|
134
|
-
|
|
141
|
+
screen.write(colors.yellow + `Could not open file automatically: ${error.message}` + colors.reset + '\n');
|
|
135
142
|
}
|
|
136
143
|
});
|
|
137
144
|
}
|
|
@@ -252,7 +259,7 @@ async function addNewThirdPartyApi() {
|
|
|
252
259
|
const cancelledMessage = await i18n.t('errors.general.cancelled_by_user');
|
|
253
260
|
if (error.message === cancelledMessage) {
|
|
254
261
|
// User cancelled - show neutral message instead of error
|
|
255
|
-
|
|
262
|
+
screen.write(colors.yellow + await i18n.t('messages.info.operation_cancelled') + colors.reset + '\n');
|
|
256
263
|
} else {
|
|
257
264
|
// Actual error occurred
|
|
258
265
|
showError(await i18n.t('errors.api.failed_add', error.message));
|
|
@@ -266,19 +273,22 @@ async function addNewThirdPartyApi() {
|
|
|
266
273
|
* Remove third-party API menu with submenu
|
|
267
274
|
*/
|
|
268
275
|
async function removeThirdPartyApi() {
|
|
269
|
-
console.clear();
|
|
270
|
-
console.log('');
|
|
271
|
-
console.log(colors.bright + colors.orange + '🗑️ ' + await i18n.t('menu.remove_api.title') + colors.reset);
|
|
272
|
-
console.log('');
|
|
273
|
-
|
|
274
276
|
const apis = apiManager.getApis();
|
|
275
277
|
|
|
278
|
+
const lines = [
|
|
279
|
+
'',
|
|
280
|
+
colors.bright + colors.orange + '🗑️ ' + await i18n.t('menu.remove_api.title') + colors.reset,
|
|
281
|
+
'',
|
|
282
|
+
];
|
|
283
|
+
|
|
276
284
|
// Show current API count
|
|
277
285
|
if (apis.length > 0) {
|
|
278
|
-
|
|
279
|
-
|
|
286
|
+
lines.push(colors.cyan + ' ' + await i18n.t('messages.info.current_api_count', apis.length) + colors.reset);
|
|
287
|
+
lines.push('');
|
|
280
288
|
}
|
|
281
289
|
|
|
290
|
+
screen.render(lines);
|
|
291
|
+
|
|
282
292
|
const menuOptions = [
|
|
283
293
|
await i18n.t('menu.remove_api.delete_single'),
|
|
284
294
|
await i18n.t('menu.remove_api.clear_all'),
|
|
@@ -336,7 +346,6 @@ async function deleteSingleApi() {
|
|
|
336
346
|
const selectedIndex = apis.findIndex(api => api.id === selectedApi.id);
|
|
337
347
|
apiManager.removeApi(selectedIndex);
|
|
338
348
|
|
|
339
|
-
console.clear();
|
|
340
349
|
showSuccess(await i18n.t('messages.success.api_removed'), [
|
|
341
350
|
`${await i18n.t('api.actions.removed_info', selectedApi.name)}`,
|
|
342
351
|
`${await i18n.t('api.details.provider')}: ${selectedApi.provider}`
|
|
@@ -375,24 +384,23 @@ async function clearAllApis() {
|
|
|
375
384
|
const count = apis.length;
|
|
376
385
|
|
|
377
386
|
if (count === 0) {
|
|
378
|
-
console.clear();
|
|
379
387
|
showInfo(await i18n.t('messages.info.no_apis'));
|
|
380
388
|
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
381
389
|
return;
|
|
382
390
|
}
|
|
383
391
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|
+
]);
|
|
390
399
|
|
|
391
400
|
const input = await simpleInput(colors.cyan + ' ' + await i18n.t('messages.prompts.confirm_clear_all_input') + colors.reset);
|
|
392
401
|
|
|
393
402
|
if (input === 'CLEAR') {
|
|
394
403
|
const clearedCount = apiManager.clearAllApis();
|
|
395
|
-
console.clear();
|
|
396
404
|
showSuccess(await i18n.t('messages.info.all_apis_cleared', clearedCount));
|
|
397
405
|
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
398
406
|
} else {
|
|
@@ -454,10 +462,11 @@ function formatRelativeTime(timestamp) {
|
|
|
454
462
|
}
|
|
455
463
|
|
|
456
464
|
async function viewStatistics() {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
465
|
+
screen.render([
|
|
466
|
+
'',
|
|
467
|
+
colors.bright + colors.orange + '📊 ' + await i18n.t('statistics.title') + colors.reset,
|
|
468
|
+
'',
|
|
469
|
+
]);
|
|
461
470
|
|
|
462
471
|
const menuOptions = [
|
|
463
472
|
await i18n.t('statistics.menu_view'),
|
|
@@ -479,7 +488,7 @@ async function viewStatistics() {
|
|
|
479
488
|
const confirm = await simpleInput(colors.yellow + ' ' + await i18n.t('statistics.reset_confirm') + ' ' + colors.reset);
|
|
480
489
|
if (confirm.toLowerCase() === 'y') {
|
|
481
490
|
apiManager.resetStatistics();
|
|
482
|
-
|
|
491
|
+
screen.write(colors.green + ' ✓ ' + await i18n.t('statistics.reset_success') + colors.reset + '\n');
|
|
483
492
|
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
484
493
|
}
|
|
485
494
|
return viewStatistics();
|
|
@@ -497,38 +506,38 @@ async function viewStatistics() {
|
|
|
497
506
|
async function showStatisticsDetails() {
|
|
498
507
|
const { padStringToWidth } = require('./lib/utils/string-width');
|
|
499
508
|
|
|
500
|
-
console.clear();
|
|
501
|
-
console.log('');
|
|
502
|
-
console.log(colors.bright + colors.orange + '📊 ' + await i18n.t('statistics.title') + colors.reset);
|
|
503
|
-
console.log('');
|
|
504
|
-
|
|
505
509
|
const stats = apiManager.getEnhancedStatistics();
|
|
506
510
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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
|
+
];
|
|
515
524
|
|
|
516
525
|
if (stats.apiStats.length > 0) {
|
|
517
|
-
|
|
518
|
-
|
|
526
|
+
lines.push(colors.cyan + ' ' + i18n.tSync('ui.general.configured_apis') + ':' + colors.reset);
|
|
527
|
+
lines.push('');
|
|
519
528
|
|
|
520
529
|
// Table header
|
|
521
|
-
|
|
530
|
+
lines.push(colors.dim + ' ' +
|
|
522
531
|
padStringToWidth(await i18n.t('statistics.header_name'), 20) +
|
|
523
532
|
padStringToWidth(await i18n.t('statistics.header_usage'), 10) +
|
|
524
533
|
padStringToWidth(await i18n.t('statistics.header_success'), 10) +
|
|
525
534
|
await i18n.t('statistics.header_last_used') +
|
|
526
535
|
colors.reset);
|
|
527
|
-
|
|
536
|
+
lines.push(colors.dim + ' ' + '─'.repeat(60) + colors.reset);
|
|
528
537
|
|
|
529
538
|
for (const api of stats.apiStats) {
|
|
530
539
|
const lastUsedText = formatRelativeTime(api.lastUsed);
|
|
531
|
-
|
|
540
|
+
lines.push(colors.gray + ' ' +
|
|
532
541
|
padStringToWidth(api.name, 20) +
|
|
533
542
|
padStringToWidth(String(api.usageCount), 10) +
|
|
534
543
|
padStringToWidth(api.successRate, 10) +
|
|
@@ -536,10 +545,11 @@ async function showStatisticsDetails() {
|
|
|
536
545
|
colors.reset);
|
|
537
546
|
}
|
|
538
547
|
} else {
|
|
539
|
-
|
|
548
|
+
lines.push(colors.gray + ' ' + await i18n.t('statistics.no_usage') + colors.reset);
|
|
540
549
|
}
|
|
541
550
|
|
|
542
|
-
|
|
551
|
+
lines.push('');
|
|
552
|
+
screen.render(lines);
|
|
543
553
|
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
544
554
|
}
|
|
545
555
|
|
|
@@ -550,36 +560,36 @@ async function showStatisticsDetails() {
|
|
|
550
560
|
async function handleFirstTimePasswordSetup() {
|
|
551
561
|
while (true) {
|
|
552
562
|
// Clear screen and show header
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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
|
+
];
|
|
560
570
|
const whyNeededItems = i18n.tSync('password.setup.why_needed_items');
|
|
561
571
|
if (Array.isArray(whyNeededItems)) {
|
|
562
572
|
whyNeededItems.forEach(item => {
|
|
563
|
-
|
|
573
|
+
lines.push(colors.gray + '• ' + item + colors.reset);
|
|
564
574
|
});
|
|
565
575
|
}
|
|
566
|
-
|
|
567
|
-
|
|
576
|
+
lines.push('');
|
|
577
|
+
lines.push(colors.cyan + '🔒 ' + i18n.tSync('password.setup.new_security_title') + colors.reset);
|
|
568
578
|
const securityItems = i18n.tSync('password.setup.security_items');
|
|
569
579
|
if (Array.isArray(securityItems)) {
|
|
570
580
|
securityItems.forEach(item => {
|
|
571
|
-
|
|
581
|
+
lines.push(colors.gray + '• ' + item + colors.reset);
|
|
572
582
|
});
|
|
573
583
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
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);
|
|
583
593
|
|
|
584
594
|
// Wait for user to read the information
|
|
585
595
|
await new Promise((resolve) => {
|
|
@@ -655,14 +665,16 @@ async function promptForPasswordSetup() {
|
|
|
655
665
|
* Confirm skip password setup
|
|
656
666
|
*/
|
|
657
667
|
async function confirmSkipPassword() {
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
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
|
+
]);
|
|
666
678
|
|
|
667
679
|
// Ensure global menus are initialized
|
|
668
680
|
initializeGlobalMenus();
|
|
@@ -677,12 +689,12 @@ async function confirmSkipPassword() {
|
|
|
677
689
|
if (choice === 0) {
|
|
678
690
|
try {
|
|
679
691
|
apiManager.skipPasswordSetup();
|
|
680
|
-
|
|
692
|
+
screen.write(colors.yellow + i18n.tSync('errors.password.setup_skipped') + colors.reset + '\n');
|
|
681
693
|
await waitForKey(i18n.tSync('messages.prompts.press_any_key'));
|
|
682
694
|
return true;
|
|
683
695
|
} catch (error) {
|
|
684
696
|
forceStdinCleanup();
|
|
685
|
-
|
|
697
|
+
screen.write(colors.red + `Operation failed: ${error.message}` + colors.reset + '\n');
|
|
686
698
|
await waitForKey(i18n.tSync('messages.prompts.press_any_key'));
|
|
687
699
|
return false;
|
|
688
700
|
}
|
|
@@ -698,147 +710,114 @@ async function showApiManagementMenu() {
|
|
|
698
710
|
// Force cleanup stdin state before showing API management menu
|
|
699
711
|
forceStdinCleanup();
|
|
700
712
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
713
|
+
screen.render([
|
|
714
|
+
'',
|
|
715
|
+
colors.bright + colors.orange + '📋 ' + await i18n.t('menu.api_management.title') + colors.reset,
|
|
716
|
+
'',
|
|
717
|
+
]);
|
|
705
718
|
|
|
706
719
|
// Check if this is first time usage and prompt for password setup
|
|
707
720
|
if (apiManager.isFirstTimeUsage()) {
|
|
708
721
|
const passwordChoice = await handleFirstTimePasswordSetup();
|
|
709
722
|
if (!passwordChoice) {
|
|
710
|
-
// User chose to skip or canceled, return to main menu
|
|
711
723
|
return showMenu();
|
|
712
724
|
}
|
|
713
725
|
}
|
|
714
726
|
|
|
715
727
|
// Build menu options based on password setup status
|
|
716
728
|
const menuOptions = [
|
|
717
|
-
await i18n.t('menu.api_management.add_new'),
|
|
718
|
-
await i18n.t('menu.api_management.
|
|
719
|
-
await i18n.t('menu.api_management.
|
|
720
|
-
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
|
|
721
735
|
];
|
|
722
736
|
|
|
723
737
|
// Add import/export options only if password is set
|
|
724
738
|
if (apiManager.canUseImportExport()) {
|
|
725
|
-
menuOptions.push(await i18n.t('menu.api_management.export')); //
|
|
726
|
-
menuOptions.push(await i18n.t('menu.api_management.import')); //
|
|
727
|
-
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
|
|
728
742
|
}
|
|
729
743
|
|
|
730
|
-
|
|
731
|
-
menuOptions.push(await i18n.t('model_upgrade.settings_title')); // 4 or 7 (depending on import/export)
|
|
732
|
-
|
|
733
|
-
menuOptions.push(await i18n.t('menu.api_management.back')); // 5 or 8
|
|
744
|
+
menuOptions.push(await i18n.t('menu.api_management.back')); // last
|
|
734
745
|
|
|
735
746
|
// Ensure global menus are initialized
|
|
736
747
|
initializeGlobalMenus();
|
|
737
748
|
|
|
738
749
|
globalApiManagementMenu.setOptions(menuOptions);
|
|
739
750
|
|
|
740
|
-
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);
|
|
741
764
|
|
|
742
765
|
// Handle menu choices based on current menu options
|
|
743
766
|
if (choice === 0) { // Add New API
|
|
744
767
|
await addNewThirdPartyApi();
|
|
745
768
|
return showMenu();
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
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
|
|
750
786
|
await switchThirdPartyApi();
|
|
751
787
|
return showMenu();
|
|
752
|
-
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
if (choice === 4) { // View API Statistics
|
|
753
791
|
await viewStatistics();
|
|
754
792
|
return showMenu();
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
|
|
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
|
|
758
802
|
await exportConfiguration();
|
|
759
|
-
return
|
|
760
|
-
}
|
|
803
|
+
return showApiManagementMenu();
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
if (choice === 7) { // Import Configuration
|
|
761
807
|
await importConfiguration();
|
|
762
|
-
return
|
|
763
|
-
} else if (choice === 6) { // Change Password
|
|
764
|
-
await changePassword();
|
|
765
|
-
return showMenu();
|
|
766
|
-
} else if (choice === 7) { // Model Upgrade Settings (NEW)
|
|
767
|
-
return await showModelUpgradeSettings();
|
|
768
|
-
} else if (choice === 8) { // Back to Main Menu
|
|
769
|
-
return showMenu();
|
|
808
|
+
return showApiManagementMenu();
|
|
770
809
|
}
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
return await showModelUpgradeSettings();
|
|
775
|
-
} else if (choice === 5) { // Back to Main Menu
|
|
810
|
+
|
|
811
|
+
if (choice === 8) { // Change Password
|
|
812
|
+
await changePassword();
|
|
776
813
|
return showMenu();
|
|
777
814
|
}
|
|
778
815
|
}
|
|
779
816
|
|
|
780
|
-
//
|
|
817
|
+
// Back to Main Menu (last item) or default fallback
|
|
781
818
|
return showMenu();
|
|
782
819
|
}
|
|
783
820
|
|
|
784
|
-
/**
|
|
785
|
-
* Show model upgrade settings menu
|
|
786
|
-
*/
|
|
787
|
-
async function showModelUpgradeSettings() {
|
|
788
|
-
const versionChecker = require('./lib/utils/version-checker');
|
|
789
|
-
const upgradeChecker = require('./lib/utils/model-upgrade-checker');
|
|
790
|
-
|
|
791
|
-
console.clear();
|
|
792
|
-
console.log('');
|
|
793
|
-
console.log(colors.bright + colors.orange + '⚙️ ' + await i18n.t('model_upgrade.settings_title') + colors.reset);
|
|
794
|
-
console.log('');
|
|
795
|
-
|
|
796
|
-
const config = await versionChecker.loadConfig();
|
|
797
|
-
const isAutoOn = config.autoModelUpgrade === true;
|
|
798
|
-
|
|
799
|
-
console.log(colors.cyan + ' ' + await i18n.t('model_upgrade.current_config') + ':' + colors.reset);
|
|
800
|
-
console.log(colors.gray + ' ' + await i18n.t('model_upgrade.auto_upgrade_label') + ': ' +
|
|
801
|
-
(isAutoOn
|
|
802
|
-
? colors.green + await i18n.t('model_upgrade.auto_upgrade_on')
|
|
803
|
-
: colors.dim + await i18n.t('model_upgrade.auto_upgrade_off')
|
|
804
|
-
) + colors.reset);
|
|
805
|
-
console.log('');
|
|
806
|
-
|
|
807
|
-
const menuOptions = [
|
|
808
|
-
isAutoOn
|
|
809
|
-
? await i18n.t('model_upgrade.menu_toggle_auto_on')
|
|
810
|
-
: await i18n.t('model_upgrade.menu_toggle_auto_off'),
|
|
811
|
-
await i18n.t('model_upgrade.menu_manual_upgrade'),
|
|
812
|
-
await i18n.t('model_upgrade.menu_back')
|
|
813
|
-
];
|
|
814
|
-
|
|
815
|
-
initializeGlobalMenus();
|
|
816
|
-
globalApiManagementMenu.setOptions(menuOptions);
|
|
817
|
-
const choice = await globalApiManagementMenu.navigate();
|
|
818
|
-
|
|
819
|
-
switch (choice) {
|
|
820
|
-
case 0: // Toggle auto upgrade
|
|
821
|
-
await versionChecker.setAutoModelUpgrade(!isAutoOn);
|
|
822
|
-
console.log('');
|
|
823
|
-
console.log(colors.green + '✓ ' + await i18n.t('model_upgrade.auto_upgrade_label') + ': ' +
|
|
824
|
-
(!isAutoOn
|
|
825
|
-
? await i18n.t('model_upgrade.auto_upgrade_on')
|
|
826
|
-
: await i18n.t('model_upgrade.auto_upgrade_off')
|
|
827
|
-
) + colors.reset);
|
|
828
|
-
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
829
|
-
return showModelUpgradeSettings();
|
|
830
|
-
|
|
831
|
-
case 1: // Manual upgrade
|
|
832
|
-
await performManualUpgrade();
|
|
833
|
-
return showModelUpgradeSettings();
|
|
834
|
-
|
|
835
|
-
case 2: // Back
|
|
836
|
-
case -1:
|
|
837
|
-
default:
|
|
838
|
-
return showApiManagementMenu();
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
|
|
842
821
|
/**
|
|
843
822
|
* Perform manual upgrade for all APIs with interactive confirmation
|
|
844
823
|
*/
|
|
@@ -846,21 +825,26 @@ async function performManualUpgrade() {
|
|
|
846
825
|
const { getLatestModel, getProvider } = require('./lib/presets/providers');
|
|
847
826
|
const { simpleInput } = require('./lib/ui/prompts');
|
|
848
827
|
|
|
849
|
-
console.clear();
|
|
850
|
-
console.log('');
|
|
851
|
-
console.log(colors.bright + colors.orange + '🔄 ' + await i18n.t('model_upgrade.manual_title') + colors.reset);
|
|
852
|
-
console.log('');
|
|
853
|
-
|
|
854
828
|
const apis = apiManager.getApis();
|
|
855
829
|
|
|
856
830
|
if (apis.length === 0) {
|
|
857
|
-
|
|
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
|
+
]);
|
|
858
837
|
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
859
838
|
return;
|
|
860
839
|
}
|
|
861
840
|
|
|
862
|
-
|
|
863
|
-
|
|
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
|
+
]);
|
|
864
848
|
|
|
865
849
|
let upgradedCount = 0;
|
|
866
850
|
let skippedUpToDate = 0;
|
|
@@ -871,87 +855,164 @@ async function performManualUpgrade() {
|
|
|
871
855
|
const api = apis[i];
|
|
872
856
|
const latestModel = getLatestModel(api.model, api.provider);
|
|
873
857
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
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');
|
|
877
861
|
|
|
878
862
|
if (latestModel) {
|
|
879
|
-
|
|
880
|
-
|
|
863
|
+
screen.write(colors.green + ' ' + await i18n.t('model_upgrade.manual_api_latest', latestModel) + colors.reset + '\n');
|
|
864
|
+
screen.write('\n');
|
|
881
865
|
|
|
882
866
|
// Ask for confirmation
|
|
883
867
|
const answer = await simpleInput(colors.yellow + ' ' + await i18n.t('model_upgrade.manual_confirm') + ' ' + colors.reset);
|
|
884
868
|
|
|
885
869
|
if (answer.toLowerCase() === 'y') {
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
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
|
+
}
|
|
889
878
|
} else {
|
|
890
|
-
|
|
879
|
+
screen.write(colors.dim + ' ' + await i18n.t('model_upgrade.manual_skipped') + colors.reset + '\n');
|
|
891
880
|
skippedByUser++;
|
|
892
881
|
}
|
|
893
882
|
} else {
|
|
894
883
|
// No upgrade info available - check if model exists in provider
|
|
895
884
|
const provider = getProvider(api.provider);
|
|
896
885
|
if (provider && provider.models && provider.models.includes(api.model)) {
|
|
897
|
-
|
|
898
|
-
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');
|
|
899
887
|
skippedUpToDate++;
|
|
900
888
|
} else {
|
|
901
|
-
|
|
889
|
+
screen.write(colors.dim + ' ' + await i18n.t('model_upgrade.manual_api_no_info') + colors.reset + '\n');
|
|
902
890
|
skippedNoInfo++;
|
|
903
891
|
}
|
|
904
892
|
}
|
|
905
893
|
|
|
906
|
-
|
|
894
|
+
screen.write('\n');
|
|
907
895
|
}
|
|
908
896
|
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
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',
|
|
914
902
|
skippedUpToDate + skippedNoInfo + skippedByUser,
|
|
915
903
|
skippedUpToDate,
|
|
916
|
-
skippedNoInfo) + colors.reset);
|
|
917
|
-
|
|
904
|
+
skippedNoInfo) + colors.reset + '\n');
|
|
905
|
+
screen.write('\n');
|
|
918
906
|
|
|
919
907
|
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
920
908
|
}
|
|
921
909
|
|
|
922
910
|
/**
|
|
923
|
-
*
|
|
911
|
+
* Show API selection menu for select launch mode
|
|
924
912
|
*/
|
|
925
|
-
async function
|
|
926
|
-
|
|
927
|
-
|
|
913
|
+
async function showApiSelectMenu(skipPermissions = false) {
|
|
914
|
+
const { padStringToWidth, getStringWidth } = require('./lib/utils/string-width');
|
|
915
|
+
const { getProvider } = require('./lib/presets/providers');
|
|
928
916
|
|
|
929
|
-
|
|
930
|
-
console.clear();
|
|
931
|
-
showInfo(i18n.tSync('launch.no_active_api'), [
|
|
932
|
-
i18n.tSync('launch.no_active_api_desc'),
|
|
933
|
-
i18n.tSync('launch.add_configure_first')
|
|
934
|
-
]);
|
|
917
|
+
const apis = apiManager.getApis();
|
|
935
918
|
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
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
|
+
}
|
|
939
924
|
|
|
940
|
-
|
|
941
|
-
apiManager.recordSuccessfulLaunch();
|
|
925
|
+
const activeApi = apiManager.getActiveApi();
|
|
942
926
|
|
|
943
|
-
|
|
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
|
+
});
|
|
944
934
|
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
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
|
+
};
|
|
954
|
+
|
|
955
|
+
screen.render([
|
|
956
|
+
'',
|
|
957
|
+
colors.bright + colors.orange + '🔗 ' + i18n.tSync('menu.api_select.title') + colors.reset,
|
|
958
|
+
'',
|
|
959
|
+
]);
|
|
948
960
|
|
|
949
|
-
|
|
961
|
+
const selectMenu = new Menu();
|
|
962
|
+
selectMenu.setOptions(menuItems);
|
|
963
|
+
const choice = await selectMenu.navigate(null, hintCallback);
|
|
950
964
|
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
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);
|
|
954
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
|
+
});
|
|
955
1016
|
}
|
|
956
1017
|
|
|
957
1018
|
/**
|
|
@@ -960,33 +1021,41 @@ async function handleThirdPartyApiLaunch(skipPermissions = false) {
|
|
|
960
1021
|
async function executeSelection(selectedIndex) {
|
|
961
1022
|
switch (selectedIndex) {
|
|
962
1023
|
case 0: // Launch Claude Code
|
|
1024
|
+
screen.exitForHandoff();
|
|
963
1025
|
launchClaudeDefault();
|
|
964
1026
|
break;
|
|
965
1027
|
|
|
966
1028
|
case 1: // Launch Claude Code (Skip Permissions)
|
|
1029
|
+
screen.exitForHandoff();
|
|
967
1030
|
launchClaudeSkipPermissions();
|
|
968
1031
|
break;
|
|
969
1032
|
|
|
970
|
-
case 2: // Launch Claude Code
|
|
1033
|
+
case 2: // Launch Claude Code (Enable Auto Mode)
|
|
1034
|
+
screen.exitForHandoff();
|
|
1035
|
+
launchClaudeAutoMode();
|
|
1036
|
+
break;
|
|
1037
|
+
|
|
1038
|
+
case 3: // Launch Claude Code with 3rd-party API
|
|
971
1039
|
await handleThirdPartyApiLaunch(false);
|
|
972
1040
|
break;
|
|
973
1041
|
|
|
974
|
-
case
|
|
1042
|
+
case 4: // Launch Claude Code with 3rd-party API (Skip Permissions)
|
|
975
1043
|
await handleThirdPartyApiLaunch(true);
|
|
976
1044
|
break;
|
|
977
1045
|
|
|
978
|
-
case
|
|
1046
|
+
case 5: // 3rd-party API Management
|
|
979
1047
|
return await showApiManagementMenu();
|
|
980
1048
|
|
|
981
|
-
case
|
|
982
|
-
return await
|
|
1049
|
+
case 6: // Configuration Management
|
|
1050
|
+
return await showConfigManagement();
|
|
983
1051
|
|
|
984
|
-
case
|
|
1052
|
+
case 7: // Version Update Check
|
|
985
1053
|
return await showVersionUpdateCheck();
|
|
986
1054
|
|
|
987
|
-
case
|
|
988
|
-
|
|
989
|
-
|
|
1055
|
+
case 8: // Exit
|
|
1056
|
+
screen.write('\n');
|
|
1057
|
+
screen.write(colors.green + '👋 ' + await i18n.t('menu.main.exit') + '!' + colors.reset + '\n');
|
|
1058
|
+
screen.exit();
|
|
990
1059
|
process.exit(0);
|
|
991
1060
|
break;
|
|
992
1061
|
|
|
@@ -996,6 +1065,103 @@ async function executeSelection(selectedIndex) {
|
|
|
996
1065
|
}
|
|
997
1066
|
}
|
|
998
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
|
+
|
|
999
1165
|
/**
|
|
1000
1166
|
* Show main menu
|
|
1001
1167
|
*/
|
|
@@ -1042,6 +1208,9 @@ async function showMenu() {
|
|
|
1042
1208
|
try {
|
|
1043
1209
|
const upgradeChecker = require('./lib/utils/model-upgrade-checker');
|
|
1044
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;
|
|
1045
1214
|
|
|
1046
1215
|
if (autoUpgrade) {
|
|
1047
1216
|
// Auto upgrade enabled: always check and upgrade (bypass cache)
|
|
@@ -1057,8 +1226,8 @@ async function showMenu() {
|
|
|
1057
1226
|
}
|
|
1058
1227
|
}
|
|
1059
1228
|
}
|
|
1060
|
-
} else {
|
|
1061
|
-
// Auto upgrade disabled: use cache for notification
|
|
1229
|
+
} else if (showUpgradeNotif) {
|
|
1230
|
+
// Auto upgrade disabled but notification enabled: use cache for notification
|
|
1062
1231
|
const result = await upgradeChecker.checkForModelUpgrades(apiManager);
|
|
1063
1232
|
if (result.needsCheck && result.upgrades.length > 0) {
|
|
1064
1233
|
const first = result.upgrades[0];
|
|
@@ -1093,20 +1262,110 @@ async function showMenu() {
|
|
|
1093
1262
|
menuOptions = [
|
|
1094
1263
|
await i18n.t('menu.main.launch_default'),
|
|
1095
1264
|
await i18n.t('menu.main.launch_skip'),
|
|
1265
|
+
await i18n.t('menu.main.launch_auto_mode'),
|
|
1096
1266
|
await i18n.t('menu.main.launch_api'),
|
|
1097
1267
|
await i18n.t('menu.main.launch_api_skip'),
|
|
1098
1268
|
await i18n.t('menu.main.api_management'),
|
|
1099
|
-
await i18n.t('menu.main.
|
|
1269
|
+
await i18n.t('menu.main.config_management'),
|
|
1100
1270
|
await i18n.t('menu.main.version_check'),
|
|
1101
1271
|
await i18n.t('menu.main.exit')
|
|
1102
1272
|
];
|
|
1103
1273
|
|
|
1274
|
+
// Pre-compute all hint data synchronously for menu callback
|
|
1275
|
+
const hintAutoMode = i18n.tSync('hints.auto_mode_info');
|
|
1276
|
+
|
|
1277
|
+
const activeApi = apiManager.getActiveApi();
|
|
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 = '';
|
|
1291
|
+
if (activeApi) {
|
|
1292
|
+
const providerConfig = getProvider(activeApi.provider);
|
|
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
|
+
}
|
|
1317
|
+
} else {
|
|
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);
|
|
1324
|
+
}
|
|
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
|
+
|
|
1339
|
+
// Synchronous hint callback — must not use await
|
|
1340
|
+
const hintCallback = (selectedIndex) => {
|
|
1341
|
+
switch (selectedIndex) {
|
|
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;
|
|
1359
|
+
}
|
|
1360
|
+
};
|
|
1361
|
+
|
|
1104
1362
|
globalMainMenu.setOptions(menuOptions);
|
|
1105
|
-
const selection = await globalMainMenu.navigate(
|
|
1363
|
+
const selection = await globalMainMenu.navigate(displayInfo || null, hintCallback);
|
|
1106
1364
|
|
|
1107
1365
|
if (selection === -1) {
|
|
1108
|
-
|
|
1109
|
-
|
|
1366
|
+
screen.write('\n');
|
|
1367
|
+
screen.write(colors.green + '👋 ' + await i18n.t('menu.main.exit') + '!' + colors.reset + '\n');
|
|
1368
|
+
screen.exit();
|
|
1110
1369
|
process.exit(0);
|
|
1111
1370
|
} else {
|
|
1112
1371
|
await executeSelection(selection);
|
|
@@ -1121,26 +1380,22 @@ async function showMenu() {
|
|
|
1121
1380
|
* Export configuration with password encryption
|
|
1122
1381
|
*/
|
|
1123
1382
|
async function exportConfiguration() {
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
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
|
+
]);
|
|
1136
1395
|
|
|
1137
1396
|
// Verify password before export
|
|
1138
|
-
const verified = await
|
|
1139
|
-
if (!verified)
|
|
1140
|
-
console.log('');
|
|
1141
|
-
await waitForKey(i18n.tSync('messages.prompts.press_any_key_menu'));
|
|
1142
|
-
return;
|
|
1143
|
-
}
|
|
1397
|
+
const verified = await passwordGuard(apiManager, 'export');
|
|
1398
|
+
if (!verified) return;
|
|
1144
1399
|
|
|
1145
1400
|
try {
|
|
1146
1401
|
// Get export data
|
|
@@ -1154,62 +1409,59 @@ async function exportConfiguration() {
|
|
|
1154
1409
|
// Write JSON file
|
|
1155
1410
|
fs.writeFileSync(filePath, exportData, 'utf8');
|
|
1156
1411
|
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
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');
|
|
1164
1419
|
|
|
1165
1420
|
// Open file with default application
|
|
1166
|
-
|
|
1421
|
+
screen.write(colors.yellow + '🔍 ' + i18n.tSync('import_export.export.opening_file') + colors.reset + '\n');
|
|
1167
1422
|
openFileWithDefault(filePath);
|
|
1168
1423
|
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
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');
|
|
1173
1428
|
|
|
1174
1429
|
} catch (error) {
|
|
1175
1430
|
forceStdinCleanup();
|
|
1176
|
-
|
|
1431
|
+
screen.write(colors.red + `❌ Export failed: ${error.message}` + colors.reset + '\n');
|
|
1177
1432
|
}
|
|
1178
1433
|
|
|
1179
|
-
|
|
1180
|
-
await waitForKey(i18n.tSync('
|
|
1434
|
+
screen.write('\n');
|
|
1435
|
+
await waitForKey(i18n.tSync('ui.general.press_any_key_continue'));
|
|
1181
1436
|
}
|
|
1182
1437
|
|
|
1183
1438
|
/**
|
|
1184
1439
|
* Import configuration from plaintext JSON
|
|
1185
1440
|
*/
|
|
1186
1441
|
async function importConfiguration() {
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
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
|
+
]);
|
|
1199
1454
|
|
|
1200
1455
|
// Verify password identity
|
|
1201
|
-
const passwordVerified = await
|
|
1202
|
-
if (!passwordVerified)
|
|
1203
|
-
await waitForKey(i18n.tSync('messages.prompts.press_any_key_menu'));
|
|
1204
|
-
return;
|
|
1205
|
-
}
|
|
1456
|
+
const passwordVerified = await passwordGuard(apiManager, 'import');
|
|
1457
|
+
if (!passwordVerified) return;
|
|
1206
1458
|
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
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');
|
|
1213
1465
|
|
|
1214
1466
|
const { simpleInput } = require('./lib/ui/prompts');
|
|
1215
1467
|
|
|
@@ -1224,85 +1476,86 @@ async function importConfiguration() {
|
|
|
1224
1476
|
const filePath = await simpleInput(colors.green + filePrompt + colors.reset);
|
|
1225
1477
|
|
|
1226
1478
|
if (!filePath) {
|
|
1227
|
-
|
|
1479
|
+
screen.write(colors.red + i18n.tSync('ui.general.file_path_empty') + colors.reset + '\n');
|
|
1228
1480
|
if (attempts < maxAttempts) {
|
|
1229
|
-
|
|
1481
|
+
screen.write('\n');
|
|
1230
1482
|
continue;
|
|
1231
1483
|
} else {
|
|
1232
|
-
|
|
1484
|
+
screen.write(colors.red + i18n.tSync('ui.general.max_attempts_import_cancelled') + colors.reset + '\n');
|
|
1233
1485
|
await waitForKey(i18n.tSync('messages.prompts.press_any_key_menu'));
|
|
1234
1486
|
return;
|
|
1235
1487
|
}
|
|
1236
1488
|
}
|
|
1237
1489
|
|
|
1238
1490
|
// Validate file
|
|
1239
|
-
|
|
1240
|
-
|
|
1491
|
+
screen.write('\n');
|
|
1492
|
+
screen.write(colors.yellow + i18n.tSync('ui.general.validating_file') + colors.reset + '\n');
|
|
1241
1493
|
const validation = validateImportFile(filePath);
|
|
1242
1494
|
|
|
1243
1495
|
if (!validation.valid) {
|
|
1244
|
-
|
|
1496
|
+
screen.write(colors.red + '❌ ' + i18n.tSync('ui.general.file_validation_failed', validation.error) + colors.reset + '\n');
|
|
1245
1497
|
if (attempts < maxAttempts) {
|
|
1246
|
-
|
|
1247
|
-
|
|
1498
|
+
screen.write(colors.yellow + i18n.tSync('ui.general.check_file_path_json') + colors.reset + '\n');
|
|
1499
|
+
screen.write('\n');
|
|
1248
1500
|
continue;
|
|
1249
1501
|
} else {
|
|
1250
|
-
|
|
1502
|
+
screen.write(colors.red + i18n.tSync('ui.general.max_attempts_import_cancelled') + colors.reset + '\n');
|
|
1251
1503
|
await waitForKey(i18n.tSync('messages.prompts.press_any_key_menu'));
|
|
1252
1504
|
return;
|
|
1253
1505
|
}
|
|
1254
1506
|
}
|
|
1255
1507
|
|
|
1256
1508
|
// File is valid, proceed with import
|
|
1257
|
-
|
|
1258
|
-
|
|
1509
|
+
screen.write(colors.green + i18n.tSync('ui.general.file_validation_successful') + colors.reset + '\n');
|
|
1510
|
+
screen.write('\n');
|
|
1259
1511
|
|
|
1260
1512
|
try {
|
|
1261
1513
|
// Import the validated configuration data
|
|
1262
1514
|
const result = apiManager.importConfigAuthenticated(validation.data);
|
|
1263
1515
|
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
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');
|
|
1267
1519
|
const importItems = i18n.tSync('ui.general.import_stats_items');
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
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');
|
|
1272
1524
|
|
|
1273
1525
|
break; // Success, exit the loop
|
|
1274
1526
|
|
|
1275
1527
|
} catch (error) {
|
|
1276
1528
|
forceStdinCleanup();
|
|
1277
|
-
|
|
1529
|
+
screen.write(colors.red + `❌ Import failed: ${error.message}` + colors.reset + '\n');
|
|
1278
1530
|
if (attempts < maxAttempts) {
|
|
1279
|
-
|
|
1280
|
-
|
|
1531
|
+
screen.write(colors.yellow + i18n.tSync('ui.general.import_tips')[0] + colors.reset + '\n');
|
|
1532
|
+
screen.write('\n');
|
|
1281
1533
|
continue;
|
|
1282
1534
|
} else {
|
|
1283
|
-
|
|
1535
|
+
screen.write(colors.red + i18n.tSync('ui.general.max_attempts_import_failed') + colors.reset + '\n');
|
|
1284
1536
|
}
|
|
1285
1537
|
}
|
|
1286
1538
|
}
|
|
1287
1539
|
|
|
1288
|
-
|
|
1289
|
-
await waitForKey(i18n.tSync('
|
|
1540
|
+
screen.write('\n');
|
|
1541
|
+
await waitForKey(i18n.tSync('ui.general.press_any_key_continue'));
|
|
1290
1542
|
}
|
|
1291
1543
|
|
|
1292
1544
|
/**
|
|
1293
1545
|
* Change password
|
|
1294
1546
|
*/
|
|
1295
1547
|
async function changePassword() {
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1548
|
+
screen.render([
|
|
1549
|
+
'',
|
|
1550
|
+
colors.bright + colors.orange + '🔑 ' + i18n.tSync('errors.password.change_password_title') + colors.reset,
|
|
1551
|
+
'',
|
|
1552
|
+
]);
|
|
1300
1553
|
|
|
1301
1554
|
// Use unified password change module
|
|
1302
1555
|
const success = await changePasswordModule(apiManager);
|
|
1303
1556
|
|
|
1304
1557
|
if (success) {
|
|
1305
|
-
|
|
1558
|
+
screen.write('\n');
|
|
1306
1559
|
}
|
|
1307
1560
|
|
|
1308
1561
|
await waitForKey(i18n.tSync('messages.prompts.press_any_key_menu'));
|
|
@@ -1317,16 +1570,16 @@ async function showLanguageSettings() {
|
|
|
1317
1570
|
const currentLanguage = i18n.getCurrentLanguage();
|
|
1318
1571
|
const currentLanguageName = i18n.getCurrentLanguageName();
|
|
1319
1572
|
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
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
|
+
]);
|
|
1330
1583
|
|
|
1331
1584
|
// Create menu options
|
|
1332
1585
|
const languageOptions = [];
|
|
@@ -1359,14 +1612,14 @@ async function showLanguageSettings() {
|
|
|
1359
1612
|
}
|
|
1360
1613
|
|
|
1361
1614
|
// Switch language
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1615
|
+
screen.render([
|
|
1616
|
+
'',
|
|
1617
|
+
colors.yellow + await i18n.t('status.switching_language') + colors.reset,
|
|
1618
|
+
]);
|
|
1365
1619
|
|
|
1366
1620
|
try {
|
|
1367
1621
|
await i18n.setLanguage(selectedLangCode);
|
|
1368
1622
|
|
|
1369
|
-
console.clear();
|
|
1370
1623
|
const newLanguageName = i18n.getCurrentLanguageName();
|
|
1371
1624
|
showSuccess(await i18n.t('messages.success.language_changed'), [
|
|
1372
1625
|
await i18n.t('menu.language.changed_success', newLanguageName)
|
|
@@ -1395,18 +1648,18 @@ async function showLanguageSettings() {
|
|
|
1395
1648
|
*/
|
|
1396
1649
|
async function showVersionUpdateCheck() {
|
|
1397
1650
|
try {
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
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
|
+
]);
|
|
1406
1659
|
|
|
1407
1660
|
// Show progress indicator
|
|
1408
1661
|
const progressInterval = setInterval(() => {
|
|
1409
|
-
|
|
1662
|
+
screen.write('.');
|
|
1410
1663
|
}, 500);
|
|
1411
1664
|
|
|
1412
1665
|
try {
|
|
@@ -1415,46 +1668,46 @@ async function showVersionUpdateCheck() {
|
|
|
1415
1668
|
|
|
1416
1669
|
// Stop progress indicator
|
|
1417
1670
|
clearInterval(progressInterval);
|
|
1418
|
-
|
|
1671
|
+
screen.write('\n\n');
|
|
1419
1672
|
|
|
1420
1673
|
if (result.error) {
|
|
1421
1674
|
// Handle errors (timeout, network, etc.)
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
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');
|
|
1426
1679
|
} else if (result.available) {
|
|
1427
1680
|
// Update available
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
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');
|
|
1435
1688
|
} else {
|
|
1436
1689
|
// Already up to date
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
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');
|
|
1441
1694
|
}
|
|
1442
1695
|
|
|
1443
1696
|
} catch (error) {
|
|
1444
1697
|
// Stop progress indicator
|
|
1445
1698
|
clearInterval(progressInterval);
|
|
1446
|
-
|
|
1699
|
+
screen.write('\n\n');
|
|
1447
1700
|
|
|
1448
|
-
|
|
1449
|
-
|
|
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');
|
|
1450
1703
|
}
|
|
1451
1704
|
|
|
1452
|
-
|
|
1705
|
+
screen.write('\n');
|
|
1453
1706
|
await waitForKey(await i18n.t('messages.prompts.press_any_key'));
|
|
1454
1707
|
return showMenu();
|
|
1455
1708
|
|
|
1456
1709
|
} catch (error) {
|
|
1457
|
-
|
|
1710
|
+
screen.write(colors.red + '❌ Failed to check version: ' + error.message + colors.reset + '\n');
|
|
1458
1711
|
await waitForKey(i18n.tSync('messages.prompts.press_any_key_menu'));
|
|
1459
1712
|
return showMenu();
|
|
1460
1713
|
}
|
|
@@ -1466,8 +1719,9 @@ async function showVersionUpdateCheck() {
|
|
|
1466
1719
|
* Graceful shutdown handlers
|
|
1467
1720
|
*/
|
|
1468
1721
|
process.on('SIGTERM', () => {
|
|
1469
|
-
|
|
1470
|
-
|
|
1722
|
+
screen.write('\n');
|
|
1723
|
+
screen.write(colors.green + i18n.tSync('ui.general.goodbye') + colors.reset + '\n');
|
|
1724
|
+
screen.exit();
|
|
1471
1725
|
process.exit(0);
|
|
1472
1726
|
});
|
|
1473
1727
|
|
|
@@ -1480,43 +1734,34 @@ const stdinManager = require('./lib/utils/stdin-manager');
|
|
|
1480
1734
|
let exiting = false;
|
|
1481
1735
|
|
|
1482
1736
|
process.on('SIGINT', () => {
|
|
1483
|
-
// During Claude run, ignore in launcher so child handles it
|
|
1484
|
-
// Check this BEFORE setting exiting flag to avoid breaking reentrancy protection
|
|
1485
1737
|
if (stdinManager.isSuspended && stdinManager.isSuspended()) {
|
|
1486
1738
|
return;
|
|
1487
1739
|
}
|
|
1488
|
-
|
|
1489
|
-
// Prevent re-entrance - ensure cleanup runs only once
|
|
1490
|
-
if (exiting) {
|
|
1491
|
-
return;
|
|
1492
|
-
}
|
|
1740
|
+
if (exiting) return;
|
|
1493
1741
|
exiting = true;
|
|
1494
|
-
|
|
1495
|
-
// Try to reset stdin state before handling
|
|
1496
1742
|
try {
|
|
1497
1743
|
if (process.stdin.isTTY) {
|
|
1498
1744
|
process.stdin.setRawMode(false);
|
|
1499
1745
|
process.stdin.pause();
|
|
1500
1746
|
}
|
|
1501
|
-
} catch (_) {
|
|
1502
|
-
// Ignore errors during emergency cleanup
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
|
-
// Use unified Ctrl+C handler from StdinManager (synchronous)
|
|
1747
|
+
} catch (_) {}
|
|
1506
1748
|
try {
|
|
1507
|
-
stdinManager.handleCtrlC();
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
// 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();
|
|
1515
1756
|
process.exit(130);
|
|
1516
1757
|
});
|
|
1517
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
|
+
|
|
1518
1762
|
// Initialize global menus and start the application
|
|
1519
1763
|
initializeGlobalMenus();
|
|
1520
1764
|
|
|
1521
1765
|
// Start the application
|
|
1766
|
+
screen.enter();
|
|
1522
1767
|
showMenu();
|