@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/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
  }
@@ -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 { verifyExportPassword, setupNewPassword, changePassword: changePasswordModule } = require('./lib/auth/password-validator');
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
- 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');
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
- 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');
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
- console.log(colors.cyan + ' ' + await i18n.t('messages.info.current_api_count', apis.length) + colors.reset);
279
- console.log('');
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
- console.clear();
385
- console.log('');
386
- console.log(colors.bright + colors.red + '⚠️ ' + await i18n.t('menu.remove_api.clear_all') + colors.reset);
387
- console.log('');
388
- console.log(colors.yellow + ' ' + await i18n.t('messages.prompts.confirm_clear_all', count) + colors.reset);
389
- 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
+ ]);
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
- console.clear();
458
- console.log('');
459
- console.log(colors.bright + colors.orange + '📊 ' + await i18n.t('statistics.title') + colors.reset);
460
- console.log('');
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
- 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');
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
- // Summary section
508
- console.log(colors.cyan + ' ' + i18n.tSync('ui.general.summary') + ':' + colors.reset);
509
- console.log(colors.gray + ` ${await i18n.t('statistics.total_apis', stats.totalApis)}` + colors.reset);
510
- console.log(colors.gray + ` ${await i18n.t('statistics.active_api', stats.activeApiName)}` + colors.reset);
511
- console.log(colors.gray + ` ${await i18n.t('statistics.most_used', stats.mostUsedApi)}` + colors.reset);
512
- console.log(colors.gray + ` ${await i18n.t('statistics.total_usage', stats.totalUsage)}` + colors.reset);
513
- console.log(colors.gray + ` ${await i18n.t('statistics.success_rate', stats.successRate)}` + colors.reset);
514
- 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
+ ];
515
524
 
516
525
  if (stats.apiStats.length > 0) {
517
- console.log(colors.cyan + ' ' + i18n.tSync('ui.general.configured_apis') + ':' + colors.reset);
518
- console.log('');
526
+ lines.push(colors.cyan + ' ' + i18n.tSync('ui.general.configured_apis') + ':' + colors.reset);
527
+ lines.push('');
519
528
 
520
529
  // Table header
521
- console.log(colors.dim + ' ' +
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
- console.log(colors.dim + ' ' + '─'.repeat(60) + colors.reset);
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
- console.log(colors.gray + ' ' +
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
- 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);
540
549
  }
541
550
 
542
- console.log('');
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
- console.clear();
554
- console.log('');
555
- console.log(colors.bright + colors.yellow + '🔐 ' + i18n.tSync('password.setup.first_time_title') + colors.reset);
556
- console.log('');
557
-
558
- // Show information
559
- 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
+ ];
560
570
  const whyNeededItems = i18n.tSync('password.setup.why_needed_items');
561
571
  if (Array.isArray(whyNeededItems)) {
562
572
  whyNeededItems.forEach(item => {
563
- console.log(colors.gray + '• ' + item + colors.reset);
573
+ lines.push(colors.gray + '• ' + item + colors.reset);
564
574
  });
565
575
  }
566
- console.log('');
567
- 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);
568
578
  const securityItems = i18n.tSync('password.setup.security_items');
569
579
  if (Array.isArray(securityItems)) {
570
580
  securityItems.forEach(item => {
571
- console.log(colors.gray + '• ' + item + colors.reset);
581
+ lines.push(colors.gray + '• ' + item + colors.reset);
572
582
  });
573
583
  }
574
- console.log('');
575
- console.log(colors.yellow + i18n.tSync('password.setup.options_title') + colors.reset);
576
- console.log(colors.gray + '• ' + i18n.tSync('password.setup.option_set') + colors.reset);
577
- console.log(colors.gray + '• ' + i18n.tSync('password.setup.option_skip') + colors.reset);
578
- console.log('');
579
- console.log(colors.red + i18n.tSync('password.setup.warning_skip') + colors.reset);
580
- console.log('');
581
-
582
- 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);
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
- console.log('');
659
- console.log(colors.bright + colors.red + '⚠️ ' + i18n.tSync('errors.password.confirm_skip_title') + colors.reset);
660
- console.log('');
661
- console.log(colors.gray + i18n.tSync('ui.general.after_skipping_password_setup') + colors.reset);
662
- console.log(colors.gray + '• ' + i18n.tSync('ui.general.password_skip_consequences')[0] + colors.reset);
663
- console.log(colors.gray + '• ' + i18n.tSync('ui.general.password_skip_consequences')[1] + colors.reset);
664
- console.log(colors.gray + '• ' + i18n.tSync('ui.general.password_skip_consequences')[2] + colors.reset);
665
- 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
+ ]);
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
- 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');
681
693
  await waitForKey(i18n.tSync('messages.prompts.press_any_key'));
682
694
  return true;
683
695
  } catch (error) {
684
696
  forceStdinCleanup();
685
- console.log(colors.red + `Operation failed: ${error.message}` + colors.reset);
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
- console.clear();
702
- console.log('');
703
- console.log(colors.bright + colors.orange + '📋 ' + await i18n.t('menu.api_management.title') + colors.reset);
704
- console.log('');
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'), // 0
718
- await i18n.t('menu.api_management.remove'), // 1
719
- await i18n.t('menu.api_management.switch'), // 2
720
- 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
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')); // 4
726
- menuOptions.push(await i18n.t('menu.api_management.import')); // 5
727
- 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
728
742
  }
729
743
 
730
- // Add model upgrade settings (always available)
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 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);
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
- } else if (choice === 1) { // Remove API
747
- await removeThirdPartyApi();
748
- return showMenu();
749
- } 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
750
786
  await switchThirdPartyApi();
751
787
  return showMenu();
752
- } else if (choice === 3) { // View API Statistics
788
+ }
789
+
790
+ if (choice === 4) { // View API Statistics
753
791
  await viewStatistics();
754
792
  return showMenu();
755
- } else if (apiManager.canUseImportExport()) {
756
- // With import/export enabled: indices 4-8
757
- 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
758
802
  await exportConfiguration();
759
- return showMenu();
760
- } else if (choice === 5) { // Import Configuration
803
+ return showApiManagementMenu();
804
+ }
805
+
806
+ if (choice === 7) { // Import Configuration
761
807
  await importConfiguration();
762
- return showMenu();
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
- } else {
772
- // Without import/export: indices 4-5
773
- if (choice === 4) { // Model Upgrade Settings (NEW)
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
- // Default fallback to main menu
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
- 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
+ ]);
858
837
  await waitForKey(await i18n.t('messages.prompts.press_any_key'));
859
838
  return;
860
839
  }
861
840
 
862
- console.log(colors.gray + ' ' + await i18n.t('model_upgrade.manual_checking', apis.length) + colors.reset);
863
- 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
+ ]);
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
- console.log(colors.cyan + ' ─────────────────────────────────────────────────' + colors.reset);
875
- console.log(colors.bright + ` ${i + 1}/${apis.length} ${api.name}` + colors.reset);
876
- 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');
877
861
 
878
862
  if (latestModel) {
879
- console.log(colors.green + ' ' + await i18n.t('model_upgrade.manual_api_latest', latestModel) + colors.reset);
880
- console.log('');
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
- apiManager.updateApiModel(api.id, latestModel);
887
- console.log(colors.green + ' ✓ ' + await i18n.t('model_upgrade.manual_upgraded', api.model, latestModel) + colors.reset);
888
- 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
+ }
889
878
  } else {
890
- 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');
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
- // Model exists in provider, likely already latest or no alias defined
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
- 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');
902
890
  skippedNoInfo++;
903
891
  }
904
892
  }
905
893
 
906
- console.log('');
894
+ screen.write('\n');
907
895
  }
908
896
 
909
- console.log(colors.cyan + ' ─────────────────────────────────────────────────' + colors.reset);
910
- console.log('');
911
- console.log(colors.green + ' ' + await i18n.t('model_upgrade.manual_complete') + colors.reset);
912
- console.log(colors.gray + ' ' + await i18n.t('model_upgrade.manual_stats_upgraded', upgradedCount) + colors.reset);
913
- 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',
914
902
  skippedUpToDate + skippedNoInfo + skippedByUser,
915
903
  skippedUpToDate,
916
- skippedNoInfo) + colors.reset);
917
- console.log('');
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
- * Handle third-party API launch
911
+ * Show API selection menu for select launch mode
924
912
  */
925
- async function handleThirdPartyApiLaunch(skipPermissions = false) {
926
- try {
927
- 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');
928
916
 
929
- if (!activeApi) {
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
- await waitForKey(i18n.tSync('launch.press_key_return'));
937
- return showMenu();
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
- // Record successful launch BEFORE launching (since process exits after)
941
- apiManager.recordSuccessfulLaunch();
925
+ const activeApi = apiManager.getActiveApi();
942
926
 
943
- 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
+ });
944
934
 
945
- } catch (error) {
946
- // Record failed launch
947
- 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
+ };
954
+
955
+ screen.render([
956
+ '',
957
+ colors.bright + colors.orange + '🔗 ' + i18n.tSync('menu.api_select.title') + colors.reset,
958
+ '',
959
+ ]);
948
960
 
949
- showError('Failed to launch with third-party API', [error.message]);
961
+ const selectMenu = new Menu();
962
+ selectMenu.setOptions(menuItems);
963
+ const choice = await selectMenu.navigate(null, hintCallback);
950
964
 
951
- setTimeout(() => {
952
- showMenu();
953
- }, 2000);
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 with 3rd-party API
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 3: // Launch Claude Code with 3rd-party API (Skip Permissions)
1042
+ case 4: // Launch Claude Code with 3rd-party API (Skip Permissions)
975
1043
  await handleThirdPartyApiLaunch(true);
976
1044
  break;
977
1045
 
978
- case 4: // 3rd-party API Management
1046
+ case 5: // 3rd-party API Management
979
1047
  return await showApiManagementMenu();
980
1048
 
981
- case 5: // Language Settings
982
- return await showLanguageSettings();
1049
+ case 6: // Configuration Management
1050
+ return await showConfigManagement();
983
1051
 
984
- case 6: // Version Update Check
1052
+ case 7: // Version Update Check
985
1053
  return await showVersionUpdateCheck();
986
1054
 
987
- case 7: // Exit
988
- console.log('');
989
- console.log(colors.green + '👋 ' + await i18n.t('menu.main.exit') + '!' + colors.reset);
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.language_settings'),
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(false, displayInfo || null); // Pass combined info to display between banner and nav
1363
+ const selection = await globalMainMenu.navigate(displayInfo || null, hintCallback);
1106
1364
 
1107
1365
  if (selection === -1) {
1108
- console.log('');
1109
- 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();
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
- console.clear();
1125
- console.log('');
1126
- console.log(colors.bright + colors.orange + '💾 ' + await i18n.t('import_export.export.title') + colors.reset);
1127
- console.log('');
1128
-
1129
- // Add export function description
1130
- console.log(colors.cyan + '📄 ' + i18n.tSync('import_export.export.description_title') + colors.reset);
1131
- console.log(colors.gray + ' • ' + i18n.tSync('import_export.export.description_items')[0] + colors.reset);
1132
- console.log(colors.gray + ' • ' + i18n.tSync('import_export.export.description_items')[1] + colors.reset);
1133
- console.log(colors.gray + ' • ' + i18n.tSync('import_export.export.description_items')[2] + colors.reset);
1134
- console.log(colors.gray + '' + i18n.tSync('import_export.export.description_items')[3] + colors.reset);
1135
- 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
+ ]);
1136
1395
 
1137
1396
  // Verify password before export
1138
- const verified = await verifyExportPassword(apiManager, 'export');
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
- console.log(colors.green + '✓ ' + i18n.tSync('import_export.export.success_title') + colors.reset);
1158
- console.log('');
1159
- console.log(colors.cyan + '📁 ' + i18n.tSync('import_export.export.details_title') + colors.reset);
1160
- console.log(colors.gray + ` • ` + i18n.tSync('import_export.export.details_file_saved', filePath) + colors.reset);
1161
- console.log(colors.gray + ` • ` + i18n.tSync('import_export.export.details_export_dir', exportDir) + colors.reset);
1162
- console.log(colors.gray + ` • ` + i18n.tSync('import_export.export.details_filename', filename) + colors.reset);
1163
- 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');
1164
1419
 
1165
1420
  // Open file with default application
1166
- 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');
1167
1422
  openFileWithDefault(filePath);
1168
1423
 
1169
- console.log('');
1170
- console.log(colors.cyan + '💡 ' + i18n.tSync('import_export.export.tips_title') + colors.reset);
1171
- console.log(colors.gray + ' • ' + i18n.tSync('import_export.export.tips_items')[0] + colors.reset);
1172
- 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');
1173
1428
 
1174
1429
  } catch (error) {
1175
1430
  forceStdinCleanup();
1176
- console.log(colors.red + `❌ Export failed: ${error.message}` + colors.reset);
1431
+ screen.write(colors.red + `❌ Export failed: ${error.message}` + colors.reset + '\n');
1177
1432
  }
1178
1433
 
1179
- console.log('');
1180
- 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'));
1181
1436
  }
1182
1437
 
1183
1438
  /**
1184
1439
  * Import configuration from plaintext JSON
1185
1440
  */
1186
1441
  async function importConfiguration() {
1187
- console.clear();
1188
- console.log('');
1189
- console.log(colors.bright + colors.orange + '📥 ' + await i18n.t('import_export.import.title') + colors.reset);
1190
- console.log('');
1191
-
1192
- // Add import function description
1193
- console.log(colors.cyan + '📄 ' + i18n.tSync('ui.general.import_function_description') + colors.reset);
1194
- console.log(colors.gray + ' • ' + i18n.tSync('import_export.export.description_items')[0] + colors.reset);
1195
- console.log(colors.gray + ' • ' + i18n.tSync('ui.general.import_description_items')[0] + colors.reset);
1196
- console.log(colors.gray + ' • ' + i18n.tSync('ui.general.import_description_items')[1] + colors.reset);
1197
- console.log(colors.gray + '' + i18n.tSync('ui.general.import_description_items')[2] + colors.reset);
1198
- 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
+ ]);
1199
1454
 
1200
1455
  // Verify password identity
1201
- const passwordVerified = await verifyExportPassword(apiManager, 'import');
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
- console.log('');
1208
- console.log(colors.cyan + '📁 ' + i18n.tSync('ui.general.file_input_required') + colors.reset);
1209
- console.log(colors.gray + ' • ' + i18n.tSync('ui.general.file_input_items')[0] + colors.reset);
1210
- console.log(colors.gray + ' • ' + i18n.tSync('ui.general.file_input_items')[1] + colors.reset);
1211
- console.log(colors.gray + ' • ' + i18n.tSync('ui.general.file_input_items')[2] + colors.reset);
1212
- 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');
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
- 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');
1228
1480
  if (attempts < maxAttempts) {
1229
- console.log('');
1481
+ screen.write('\n');
1230
1482
  continue;
1231
1483
  } else {
1232
- 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');
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
- console.log('');
1240
- 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');
1241
1493
  const validation = validateImportFile(filePath);
1242
1494
 
1243
1495
  if (!validation.valid) {
1244
- 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');
1245
1497
  if (attempts < maxAttempts) {
1246
- console.log(colors.yellow + i18n.tSync('ui.general.check_file_path_json') + colors.reset);
1247
- console.log('');
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
- 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');
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
- console.log(colors.green + i18n.tSync('ui.general.file_validation_successful') + colors.reset);
1258
- console.log('');
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
- console.log(colors.green + i18n.tSync('ui.general.import_successful') + colors.reset);
1265
- console.log('');
1266
- 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');
1267
1519
  const importItems = i18n.tSync('ui.general.import_stats_items');
1268
- console.log(colors.gray + ` • ` + importItems[0].replace('{0}', result.imported) + colors.reset);
1269
- console.log(colors.gray + ` • ` + importItems[1].replace('{1}', result.skipped) + colors.reset);
1270
- console.log(colors.gray + ` • ` + importItems[2] + colors.reset);
1271
- 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');
1272
1524
 
1273
1525
  break; // Success, exit the loop
1274
1526
 
1275
1527
  } catch (error) {
1276
1528
  forceStdinCleanup();
1277
- console.log(colors.red + `❌ Import failed: ${error.message}` + colors.reset);
1529
+ screen.write(colors.red + `❌ Import failed: ${error.message}` + colors.reset + '\n');
1278
1530
  if (attempts < maxAttempts) {
1279
- console.log(colors.yellow + i18n.tSync('ui.general.import_tips')[0] + colors.reset);
1280
- console.log('');
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
- 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');
1284
1536
  }
1285
1537
  }
1286
1538
  }
1287
1539
 
1288
- console.log('');
1289
- 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'));
1290
1542
  }
1291
1543
 
1292
1544
  /**
1293
1545
  * Change password
1294
1546
  */
1295
1547
  async function changePassword() {
1296
- console.clear();
1297
- console.log('');
1298
- console.log(colors.bright + colors.orange + '🔑 ' + i18n.tSync('errors.password.change_password_title') + colors.reset);
1299
- console.log('');
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
- console.log('');
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
- console.clear();
1321
- console.log('');
1322
- console.log(colors.bright + colors.orange + '🌍 ' + await i18n.t('menu.language.title') + colors.reset);
1323
- console.log('');
1324
-
1325
- // Show current language
1326
- console.log(colors.cyan + await i18n.t('menu.language.current', currentLanguageName) + colors.reset);
1327
- console.log('');
1328
- console.log(colors.yellow + await i18n.t('menu.language.select_prompt') + colors.reset);
1329
- 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
+ ]);
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
- console.clear();
1363
- console.log('');
1364
- 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
+ ]);
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
- console.clear();
1399
- console.log('');
1400
- console.log(colors.bright + colors.orange + '🔄 ' + await i18n.t('version_check.title') + colors.reset);
1401
- console.log('');
1402
-
1403
- console.log(colors.cyan + await i18n.t('version_check.checking') + colors.reset);
1404
- console.log(colors.gray + await i18n.t('version_check.please_wait') + colors.reset);
1405
- 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
+ ]);
1406
1659
 
1407
1660
  // Show progress indicator
1408
1661
  const progressInterval = setInterval(() => {
1409
- process.stdout.write('.');
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
- console.log('\n');
1671
+ screen.write('\n\n');
1419
1672
 
1420
1673
  if (result.error) {
1421
1674
  // Handle errors (timeout, network, etc.)
1422
- console.log(colors.red + '❌ ' + await i18n.t('version_check.error') + colors.reset);
1423
- console.log(colors.red + ' ' + result.error + colors.reset);
1424
- console.log('');
1425
- 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');
1426
1679
  } else if (result.available) {
1427
1680
  // Update available
1428
- console.log(colors.yellow + '🎉 ' + await i18n.t('version_check.update_available') + colors.reset);
1429
- console.log('');
1430
- console.log(colors.cyan + ' ' + await i18n.t('version_check.current_version', result.currentVersion) + colors.reset);
1431
- console.log(colors.green + ' ' + await i18n.t('version_check.latest_version', result.latestVersion) + colors.reset);
1432
- console.log('');
1433
- console.log(colors.yellow + '💡 ' + await i18n.t('version_check.update_command') + colors.reset);
1434
- 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');
1435
1688
  } else {
1436
1689
  // Already up to date
1437
- console.log(colors.green + '✅ ' + await i18n.t('version_check.up_to_date') + colors.reset);
1438
- console.log('');
1439
- console.log(colors.cyan + ' ' + await i18n.t('version_check.current_version', result.currentVersion) + colors.reset);
1440
- 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');
1441
1694
  }
1442
1695
 
1443
1696
  } catch (error) {
1444
1697
  // Stop progress indicator
1445
1698
  clearInterval(progressInterval);
1446
- console.log('\n');
1699
+ screen.write('\n\n');
1447
1700
 
1448
- console.log(colors.red + '❌ ' + await i18n.t('version_check.unexpected_error') + colors.reset);
1449
- 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');
1450
1703
  }
1451
1704
 
1452
- console.log('');
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
- 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');
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
- console.log('');
1470
- 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();
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
- } catch (_) {
1509
- // Ignore errors during Ctrl+C handling
1510
- }
1511
-
1512
- // Exit with standard SIGINT exit code (128 + 2 = 130)
1513
- // Note: If handleCtrlC() calls process.exit(0) for second Ctrl+C,
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();