@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/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
- console.error('[DEBUG] forceStdinCleanup error:', error.message);
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 { verifyExportPassword, setupNewPassword, changePassword: changePasswordModule } = require('./lib/auth/password-validator');
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
- console.log(colors.yellow + `Could not open file automatically: ${error.message}` + colors.reset);
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
- console.log(colors.yellow + await i18n.t('messages.info.operation_cancelled') + colors.reset);
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
- console.log(colors.cyan + ' ' + await i18n.t('messages.info.current_api_count', apis.length) + colors.reset);
280
- console.log('');
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
- console.clear();
386
- console.log('');
387
- console.log(colors.bright + colors.red + '⚠️ ' + await i18n.t('menu.remove_api.clear_all') + colors.reset);
388
- console.log('');
389
- console.log(colors.yellow + ' ' + await i18n.t('messages.prompts.confirm_clear_all', count) + colors.reset);
390
- console.log('');
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
- console.clear();
459
- console.log('');
460
- console.log(colors.bright + colors.orange + '📊 ' + await i18n.t('statistics.title') + colors.reset);
461
- console.log('');
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
- console.log(colors.green + ' ✓ ' + await i18n.t('statistics.reset_success') + colors.reset);
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
- // Summary section
509
- console.log(colors.cyan + ' ' + i18n.tSync('ui.general.summary') + ':' + colors.reset);
510
- console.log(colors.gray + ` ${await i18n.t('statistics.total_apis', stats.totalApis)}` + colors.reset);
511
- console.log(colors.gray + ` ${await i18n.t('statistics.active_api', stats.activeApiName)}` + colors.reset);
512
- console.log(colors.gray + ` ${await i18n.t('statistics.most_used', stats.mostUsedApi)}` + colors.reset);
513
- console.log(colors.gray + ` ${await i18n.t('statistics.total_usage', stats.totalUsage)}` + colors.reset);
514
- console.log(colors.gray + ` ${await i18n.t('statistics.success_rate', stats.successRate)}` + colors.reset);
515
- console.log('');
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
- console.log(colors.cyan + ' ' + i18n.tSync('ui.general.configured_apis') + ':' + colors.reset);
519
- console.log('');
526
+ lines.push(colors.cyan + ' ' + i18n.tSync('ui.general.configured_apis') + ':' + colors.reset);
527
+ lines.push('');
520
528
 
521
529
  // Table header
522
- console.log(colors.dim + ' ' +
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
- console.log(colors.dim + ' ' + '─'.repeat(60) + colors.reset);
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
- console.log(colors.gray + ' ' +
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
- console.log(colors.gray + ' ' + await i18n.t('statistics.no_usage') + colors.reset);
548
+ lines.push(colors.gray + ' ' + await i18n.t('statistics.no_usage') + colors.reset);
541
549
  }
542
550
 
543
- console.log('');
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
- console.clear();
555
- console.log('');
556
- console.log(colors.bright + colors.yellow + '🔐 ' + i18n.tSync('password.setup.first_time_title') + colors.reset);
557
- console.log('');
558
-
559
- // Show information
560
- console.log(colors.cyan + i18n.tSync('password.setup.why_needed') + colors.reset);
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
- console.log(colors.gray + '• ' + item + colors.reset);
573
+ lines.push(colors.gray + '• ' + item + colors.reset);
565
574
  });
566
575
  }
567
- console.log('');
568
- console.log(colors.cyan + '🔒 ' + i18n.tSync('password.setup.new_security_title') + colors.reset);
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
- console.log(colors.gray + '• ' + item + colors.reset);
581
+ lines.push(colors.gray + '• ' + item + colors.reset);
573
582
  });
574
583
  }
575
- console.log('');
576
- console.log(colors.yellow + i18n.tSync('password.setup.options_title') + colors.reset);
577
- console.log(colors.gray + '• ' + i18n.tSync('password.setup.option_set') + colors.reset);
578
- console.log(colors.gray + '• ' + i18n.tSync('password.setup.option_skip') + colors.reset);
579
- console.log('');
580
- console.log(colors.red + i18n.tSync('password.setup.warning_skip') + colors.reset);
581
- console.log('');
582
-
583
- console.log(colors.gray + '按任意键继续...' + colors.reset);
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
- console.log('');
660
- console.log(colors.bright + colors.red + '⚠️ ' + i18n.tSync('errors.password.confirm_skip_title') + colors.reset);
661
- console.log('');
662
- console.log(colors.gray + i18n.tSync('ui.general.after_skipping_password_setup') + colors.reset);
663
- console.log(colors.gray + '• ' + i18n.tSync('ui.general.password_skip_consequences')[0] + colors.reset);
664
- console.log(colors.gray + '• ' + i18n.tSync('ui.general.password_skip_consequences')[1] + colors.reset);
665
- console.log(colors.gray + '• ' + i18n.tSync('ui.general.password_skip_consequences')[2] + colors.reset);
666
- console.log('');
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
- console.log(colors.yellow + i18n.tSync('errors.password.setup_skipped') + colors.reset);
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
- console.log(colors.red + `Operation failed: ${error.message}` + colors.reset);
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
- console.clear();
703
- console.log('');
704
- console.log(colors.bright + colors.orange + '📋 ' + await i18n.t('menu.api_management.title') + colors.reset);
705
- console.log('');
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'), // 0
719
- await i18n.t('menu.api_management.remove'), // 1
720
- await i18n.t('menu.api_management.switch'), // 2
721
- await i18n.t('menu.api_management.statistics') // 3
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')); // 4
727
- menuOptions.push(await i18n.t('menu.api_management.import')); // 5
728
- menuOptions.push(await i18n.t('menu.api_management.change_password')); // 6
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
- // Add model upgrade settings (always available)
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 choice = await globalApiManagementMenu.navigate();
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
- } else if (choice === 1) { // Remove API
748
- await removeThirdPartyApi();
749
- return showMenu();
750
- } else if (choice === 2) { // Switch Active API
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
- } else if (choice === 3) { // View API Statistics
788
+ }
789
+
790
+ if (choice === 4) { // View API Statistics
754
791
  await viewStatistics();
755
792
  return showMenu();
756
- } else if (apiManager.canUseImportExport()) {
757
- // With import/export enabled: indices 4-8
758
- if (choice === 4) { // Export Configuration
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 showMenu();
761
- } else if (choice === 5) { // Import Configuration
803
+ return showApiManagementMenu();
804
+ }
805
+
806
+ if (choice === 7) { // Import Configuration
762
807
  await importConfiguration();
763
- return showMenu();
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
- } else {
773
- // Without import/export: indices 4-5
774
- if (choice === 4) { // Model Upgrade Settings (NEW)
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
- // Default fallback to main menu
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
- console.log(colors.yellow + ' ' + await i18n.t('messages.info.no_apis') + colors.reset);
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
- console.log(colors.gray + ' ' + await i18n.t('model_upgrade.manual_checking', apis.length) + colors.reset);
864
- console.log('');
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
- console.log(colors.cyan + ' ─────────────────────────────────────────────────' + colors.reset);
876
- console.log(colors.bright + ` ${i + 1}/${apis.length} ${api.name}` + colors.reset);
877
- console.log(colors.gray + ' ' + await i18n.t('model_upgrade.manual_api_current', api.model) + colors.reset);
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
- console.log(colors.green + ' ' + await i18n.t('model_upgrade.manual_api_latest', latestModel) + colors.reset);
881
- console.log('');
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
- apiManager.updateApiModel(api.id, latestModel);
888
- console.log(colors.green + ' ✓ ' + await i18n.t('model_upgrade.manual_upgraded', api.model, latestModel) + colors.reset);
889
- upgradedCount++;
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
- console.log(colors.dim + ' ' + await i18n.t('model_upgrade.manual_skipped') + colors.reset);
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
- // Model exists in provider, likely already latest or no alias defined
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
- console.log(colors.dim + ' ' + await i18n.t('model_upgrade.manual_api_no_info') + colors.reset);
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
- console.log('');
894
+ screen.write('\n');
908
895
  }
909
896
 
910
- console.log(colors.cyan + ' ─────────────────────────────────────────────────' + colors.reset);
911
- console.log('');
912
- console.log(colors.green + ' ' + await i18n.t('model_upgrade.manual_complete') + colors.reset);
913
- console.log(colors.gray + ' ' + await i18n.t('model_upgrade.manual_stats_upgraded', upgradedCount) + colors.reset);
914
- console.log(colors.gray + ' ' + await i18n.t('model_upgrade.manual_stats_skipped',
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
- console.log('');
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
- * Handle third-party API launch
911
+ * Show API selection menu for select launch mode
925
912
  */
926
- async function handleThirdPartyApiLaunch(skipPermissions = false) {
927
- try {
928
- const activeApi = apiManager.getActiveApi();
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
- if (!activeApi) {
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
- await waitForKey(i18n.tSync('launch.press_key_return'));
938
- return showMenu();
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
- // Record successful launch BEFORE launching (since process exits after)
942
- apiManager.recordSuccessfulLaunch();
925
+ const activeApi = apiManager.getActiveApi();
943
926
 
944
- launchClaudeWithApi(activeApi, skipPermissions);
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
- } catch (error) {
947
- // Record failed launch
948
- apiManager.recordFailedLaunch(error.message);
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
- showError('Failed to launch with third-party API', [error.message]);
955
+ screen.render([
956
+ '',
957
+ colors.bright + colors.orange + '🔗 ' + i18n.tSync('menu.api_select.title') + colors.reset,
958
+ '',
959
+ ]);
951
960
 
952
- setTimeout(() => {
953
- showMenu();
954
- }, 2000);
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: // Language Settings
987
- return await showLanguageSettings();
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
- console.log('');
994
- console.log(colors.green + '👋 ' + await i18n.t('menu.main.exit') + '!' + colors.reset);
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.language_settings'),
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 texts synchronously for menu callback
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
- let hintApiInfo = null;
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
- const providerName = providerConfig ? providerConfig.name : (activeApi.provider || 'Custom');
1118
- hintApiInfo = i18n.tSync('hints.active_api_info', providerName, activeApi.model);
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
- hintApiInfo = i18n.tSync('hints.no_active_api');
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 2: return hintAutoMode;
1127
- case 3: return hintApiInfo;
1128
- case 4: return hintApiInfo;
1129
- default: return null;
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(false, displayInfo || null, hintCallback);
1363
+ const selection = await globalMainMenu.navigate(displayInfo || null, hintCallback);
1135
1364
 
1136
1365
  if (selection === -1) {
1137
- console.log('');
1138
- console.log(colors.green + '👋 ' + await i18n.t('menu.main.exit') + '!' + colors.reset);
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
- console.clear();
1154
- console.log('');
1155
- console.log(colors.bright + colors.orange + '💾 ' + await i18n.t('import_export.export.title') + colors.reset);
1156
- console.log('');
1157
-
1158
- // Add export function description
1159
- console.log(colors.cyan + '📄 ' + i18n.tSync('import_export.export.description_title') + colors.reset);
1160
- console.log(colors.gray + ' • ' + i18n.tSync('import_export.export.description_items')[0] + colors.reset);
1161
- console.log(colors.gray + ' • ' + i18n.tSync('import_export.export.description_items')[1] + colors.reset);
1162
- console.log(colors.gray + ' • ' + i18n.tSync('import_export.export.description_items')[2] + colors.reset);
1163
- console.log(colors.gray + '' + i18n.tSync('import_export.export.description_items')[3] + colors.reset);
1164
- console.log('');
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 verifyExportPassword(apiManager, 'export');
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
- console.log(colors.green + '✓ ' + i18n.tSync('import_export.export.success_title') + colors.reset);
1187
- console.log('');
1188
- console.log(colors.cyan + '📁 ' + i18n.tSync('import_export.export.details_title') + colors.reset);
1189
- console.log(colors.gray + ` • ` + i18n.tSync('import_export.export.details_file_saved', filePath) + colors.reset);
1190
- console.log(colors.gray + ` • ` + i18n.tSync('import_export.export.details_export_dir', exportDir) + colors.reset);
1191
- console.log(colors.gray + ` • ` + i18n.tSync('import_export.export.details_filename', filename) + colors.reset);
1192
- console.log('');
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
- console.log(colors.yellow + '🔍 ' + i18n.tSync('import_export.export.opening_file') + colors.reset);
1421
+ screen.write(colors.yellow + '🔍 ' + i18n.tSync('import_export.export.opening_file') + colors.reset + '\n');
1196
1422
  openFileWithDefault(filePath);
1197
1423
 
1198
- console.log('');
1199
- console.log(colors.cyan + '💡 ' + i18n.tSync('import_export.export.tips_title') + colors.reset);
1200
- console.log(colors.gray + ' • ' + i18n.tSync('import_export.export.tips_items')[0] + colors.reset);
1201
- console.log(colors.gray + ' • ' + i18n.tSync('import_export.export.tips_items')[1] + colors.reset);
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
- console.log(colors.red + `❌ Export failed: ${error.message}` + colors.reset);
1431
+ screen.write(colors.red + `❌ Export failed: ${error.message}` + colors.reset + '\n');
1206
1432
  }
1207
1433
 
1208
- console.log('');
1209
- await waitForKey(i18n.tSync('messages.prompts.press_any_key_menu'));
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
- console.clear();
1217
- console.log('');
1218
- console.log(colors.bright + colors.orange + '📥 ' + await i18n.t('import_export.import.title') + colors.reset);
1219
- console.log('');
1220
-
1221
- // Add import function description
1222
- console.log(colors.cyan + '📄 ' + i18n.tSync('ui.general.import_function_description') + colors.reset);
1223
- console.log(colors.gray + ' • ' + i18n.tSync('import_export.export.description_items')[0] + colors.reset);
1224
- console.log(colors.gray + ' • ' + i18n.tSync('ui.general.import_description_items')[0] + colors.reset);
1225
- console.log(colors.gray + ' • ' + i18n.tSync('ui.general.import_description_items')[1] + colors.reset);
1226
- console.log(colors.gray + '' + i18n.tSync('ui.general.import_description_items')[2] + colors.reset);
1227
- console.log('');
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 verifyExportPassword(apiManager, 'import');
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
- console.log('');
1237
- console.log(colors.cyan + '📁 ' + i18n.tSync('ui.general.file_input_required') + colors.reset);
1238
- console.log(colors.gray + ' • ' + i18n.tSync('ui.general.file_input_items')[0] + colors.reset);
1239
- console.log(colors.gray + ' • ' + i18n.tSync('ui.general.file_input_items')[1] + colors.reset);
1240
- console.log(colors.gray + ' • ' + i18n.tSync('ui.general.file_input_items')[2] + colors.reset);
1241
- console.log('');
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
- console.log(colors.red + i18n.tSync('ui.general.file_path_empty') + colors.reset);
1479
+ screen.write(colors.red + i18n.tSync('ui.general.file_path_empty') + colors.reset + '\n');
1257
1480
  if (attempts < maxAttempts) {
1258
- console.log('');
1481
+ screen.write('\n');
1259
1482
  continue;
1260
1483
  } else {
1261
- console.log(colors.red + i18n.tSync('ui.general.max_attempts_import_cancelled') + colors.reset);
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
- console.log('');
1269
- console.log(colors.yellow + i18n.tSync('ui.general.validating_file') + colors.reset);
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
- console.log(colors.red + '❌ ' + i18n.tSync('ui.general.file_validation_failed', validation.error) + colors.reset);
1496
+ screen.write(colors.red + '❌ ' + i18n.tSync('ui.general.file_validation_failed', validation.error) + colors.reset + '\n');
1274
1497
  if (attempts < maxAttempts) {
1275
- console.log(colors.yellow + i18n.tSync('ui.general.check_file_path_json') + colors.reset);
1276
- console.log('');
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
- console.log(colors.red + i18n.tSync('ui.general.max_attempts_import_cancelled') + colors.reset);
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
- console.log(colors.green + i18n.tSync('ui.general.file_validation_successful') + colors.reset);
1287
- console.log('');
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
- console.log(colors.green + i18n.tSync('ui.general.import_successful') + colors.reset);
1294
- console.log('');
1295
- console.log(colors.cyan + i18n.tSync('ui.general.import_statistics') + colors.reset);
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
- console.log(colors.gray + ` • ` + importItems[0].replace('{0}', result.imported) + colors.reset);
1298
- console.log(colors.gray + ` • ` + importItems[1].replace('{1}', result.skipped) + colors.reset);
1299
- console.log(colors.gray + ` • ` + importItems[2] + colors.reset);
1300
- console.log(colors.gray + ` • ` + importItems[3].replace('{0}', path.resolve(filePath)) + colors.reset);
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
- console.log(colors.red + `❌ Import failed: ${error.message}` + colors.reset);
1529
+ screen.write(colors.red + `❌ Import failed: ${error.message}` + colors.reset + '\n');
1307
1530
  if (attempts < maxAttempts) {
1308
- console.log(colors.yellow + i18n.tSync('ui.general.import_tips')[0] + colors.reset);
1309
- console.log('');
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
- console.log(colors.red + i18n.tSync('ui.general.max_attempts_import_failed') + colors.reset);
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
- console.log('');
1318
- await waitForKey(i18n.tSync('messages.prompts.press_any_key_menu'));
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
- console.clear();
1326
- console.log('');
1327
- console.log(colors.bright + colors.orange + '🔑 ' + i18n.tSync('errors.password.change_password_title') + colors.reset);
1328
- console.log('');
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
- console.log('');
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
- console.clear();
1350
- console.log('');
1351
- console.log(colors.bright + colors.orange + '🌍 ' + await i18n.t('menu.language.title') + colors.reset);
1352
- console.log('');
1353
-
1354
- // Show current language
1355
- console.log(colors.cyan + await i18n.t('menu.language.current', currentLanguageName) + colors.reset);
1356
- console.log('');
1357
- console.log(colors.yellow + await i18n.t('menu.language.select_prompt') + colors.reset);
1358
- console.log('');
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
- console.clear();
1392
- console.log('');
1393
- console.log(colors.yellow + await i18n.t('status.switching_language') + colors.reset);
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
- console.clear();
1428
- console.log('');
1429
- console.log(colors.bright + colors.orange + '🔄 ' + await i18n.t('version_check.title') + colors.reset);
1430
- console.log('');
1431
-
1432
- console.log(colors.cyan + await i18n.t('version_check.checking') + colors.reset);
1433
- console.log(colors.gray + await i18n.t('version_check.please_wait') + colors.reset);
1434
- console.log('');
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
- process.stdout.write('.');
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
- console.log('\n');
1671
+ screen.write('\n\n');
1448
1672
 
1449
1673
  if (result.error) {
1450
1674
  // Handle errors (timeout, network, etc.)
1451
- console.log(colors.red + '❌ ' + await i18n.t('version_check.error') + colors.reset);
1452
- console.log(colors.red + ' ' + result.error + colors.reset);
1453
- console.log('');
1454
- console.log(colors.gray + await i18n.t('version_check.error_tips') + colors.reset);
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
- console.log(colors.yellow + '🎉 ' + await i18n.t('version_check.update_available') + colors.reset);
1458
- console.log('');
1459
- console.log(colors.cyan + ' ' + await i18n.t('version_check.current_version', result.currentVersion) + colors.reset);
1460
- console.log(colors.green + ' ' + await i18n.t('version_check.latest_version', result.latestVersion) + colors.reset);
1461
- console.log('');
1462
- console.log(colors.yellow + '💡 ' + await i18n.t('version_check.update_command') + colors.reset);
1463
- console.log(colors.yellow + ' npm update -g @kikkimo/claude-launcher' + colors.reset);
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
- console.log(colors.green + '✅ ' + await i18n.t('version_check.up_to_date') + colors.reset);
1467
- console.log('');
1468
- console.log(colors.cyan + ' ' + await i18n.t('version_check.current_version', result.currentVersion) + colors.reset);
1469
- console.log(colors.cyan + ' ' + await i18n.t('version_check.latest_version', result.latestVersion) + colors.reset);
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
- console.log('\n');
1699
+ screen.write('\n\n');
1476
1700
 
1477
- console.log(colors.red + '❌ ' + await i18n.t('version_check.unexpected_error') + colors.reset);
1478
- console.log(colors.red + ' ' + error.message + colors.reset);
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
- console.log('');
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
- console.log(colors.red + '❌ Failed to check version: ' + error.message + colors.reset);
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
- console.log('');
1499
- console.log(colors.green + i18n.tSync('ui.general.goodbye') + colors.reset);
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
- } catch (_) {
1538
- // Ignore errors during Ctrl+C handling
1539
- }
1540
-
1541
- // Exit with standard SIGINT exit code (128 + 2 = 130)
1542
- // Note: If handleCtrlC() calls process.exit(0) for second Ctrl+C,
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();