@kikkimo/claude-launcher 2.3.0 → 2.5.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
@@ -46,6 +46,7 @@ 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');
@@ -263,9 +264,52 @@ async function addNewThirdPartyApi() {
263
264
  }
264
265
 
265
266
  /**
266
- * Remove third-party API
267
+ * Remove third-party API menu with submenu
267
268
  */
268
269
  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
+ const apis = apiManager.getApis();
276
+
277
+ // Show current API count
278
+ if (apis.length > 0) {
279
+ console.log(colors.cyan + ' ' + await i18n.t('messages.info.current_api_count', apis.length) + colors.reset);
280
+ console.log('');
281
+ }
282
+
283
+ const menuOptions = [
284
+ await i18n.t('menu.remove_api.delete_single'),
285
+ await i18n.t('menu.remove_api.clear_all'),
286
+ await i18n.t('menu.remove_api.back')
287
+ ];
288
+
289
+ initializeGlobalMenus();
290
+ globalApiManagementMenu.setOptions(menuOptions);
291
+ const choice = await globalApiManagementMenu.navigate();
292
+
293
+ switch (choice) {
294
+ case 0: // Delete Single API
295
+ await deleteSingleApi();
296
+ return;
297
+
298
+ case 1: // Clear All APIs
299
+ await clearAllApis();
300
+ return;
301
+
302
+ case 2: // Back
303
+ case -1:
304
+ default:
305
+ return;
306
+ }
307
+ }
308
+
309
+ /**
310
+ * Delete a single API
311
+ */
312
+ async function deleteSingleApi() {
269
313
  try {
270
314
  // Get API list
271
315
  const apis = apiManager.getApis();
@@ -281,7 +325,7 @@ async function removeThirdPartyApi() {
281
325
  );
282
326
 
283
327
  if (!selectedApi) {
284
- return showMenu();
328
+ return;
285
329
  }
286
330
 
287
331
  // Show confirmation dialog
@@ -299,23 +343,20 @@ async function removeThirdPartyApi() {
299
343
  `${await i18n.t('api.details.provider')}: ${selectedApi.provider}`
300
344
  ]);
301
345
 
302
- // Show success message and return to main menu
346
+ // Show success message
303
347
  const remainingApis = apiManager.getApis();
304
348
  if (remainingApis.length === 0) {
305
349
  showInfo(await i18n.t('messages.info.all_apis_removed'));
306
350
  }
307
351
  await waitForKey(await i18n.t('messages.prompts.press_any_key'));
308
- return showMenu();
309
352
 
310
353
  } catch (removeError) {
311
354
  showError(await i18n.t('errors.api.failed_remove', removeError.message));
312
355
  await waitForKey(await i18n.t('messages.prompts.press_any_key'));
313
- return showMenu();
314
356
  }
315
357
  } else {
316
358
  showInfo(await i18n.t('messages.info.removal_cancelled'));
317
359
  await waitForKey(await i18n.t('messages.prompts.press_any_key'));
318
- return showMenu();
319
360
  }
320
361
 
321
362
  } catch (error) {
@@ -325,6 +366,42 @@ async function removeThirdPartyApi() {
325
366
  }
326
367
  }
327
368
 
369
+ /**
370
+ * Clear all APIs with confirmation
371
+ */
372
+ async function clearAllApis() {
373
+ const { simpleInput } = require('./lib/ui/prompts');
374
+
375
+ const apis = apiManager.getApis();
376
+ const count = apis.length;
377
+
378
+ if (count === 0) {
379
+ console.clear();
380
+ showInfo(await i18n.t('messages.info.no_apis'));
381
+ await waitForKey(await i18n.t('messages.prompts.press_any_key'));
382
+ return;
383
+ }
384
+
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('');
391
+
392
+ const input = await simpleInput(colors.cyan + ' ' + await i18n.t('messages.prompts.confirm_clear_all_input') + colors.reset);
393
+
394
+ if (input === 'CLEAR') {
395
+ const clearedCount = apiManager.clearAllApis();
396
+ console.clear();
397
+ showSuccess(await i18n.t('messages.info.all_apis_cleared', clearedCount));
398
+ await waitForKey(await i18n.t('messages.prompts.press_any_key'));
399
+ } else {
400
+ showInfo(await i18n.t('messages.info.clear_cancelled'));
401
+ await waitForKey(await i18n.t('messages.prompts.press_any_key'));
402
+ }
403
+ }
404
+
328
405
  /**
329
406
  * Switch active third-party API
330
407
  */
@@ -357,40 +434,110 @@ async function switchThirdPartyApi() {
357
434
  /**
358
435
  * View API statistics
359
436
  */
437
+ /**
438
+ * Format timestamp to relative time
439
+ * @param {string|null} timestamp - ISO timestamp or null
440
+ * @returns {string} Relative time string
441
+ */
442
+ function formatRelativeTime(timestamp) {
443
+ if (!timestamp) return i18n.tSync('statistics.time_never');
444
+
445
+ const now = Date.now();
446
+ const diff = now - new Date(timestamp).getTime();
447
+ const minutes = Math.floor(diff / 60000);
448
+ const hours = Math.floor(diff / 3600000);
449
+ const days = Math.floor(diff / 86400000);
450
+
451
+ if (minutes < 1) return i18n.tSync('statistics.time_just_now');
452
+ if (minutes < 60) return i18n.tSync('statistics.time_minutes_ago', minutes);
453
+ if (hours < 24) return i18n.tSync('statistics.time_hours_ago', hours);
454
+ return i18n.tSync('statistics.time_days_ago', days);
455
+ }
456
+
360
457
  async function viewStatistics() {
361
458
  console.clear();
362
459
  console.log('');
363
460
  console.log(colors.bright + colors.orange + '📊 ' + await i18n.t('statistics.title') + colors.reset);
364
461
  console.log('');
365
462
 
366
- const stats = apiManager.getStatistics();
367
- const apis = apiManager.getApis();
463
+ const menuOptions = [
464
+ await i18n.t('statistics.menu_view'),
465
+ await i18n.t('statistics.menu_reset'),
466
+ await i18n.t('statistics.menu_back')
467
+ ];
468
+
469
+ initializeGlobalMenus();
470
+ globalApiManagementMenu.setOptions(menuOptions);
471
+ const choice = await globalApiManagementMenu.navigate();
472
+
473
+ switch (choice) {
474
+ case 0: // View Statistics Details
475
+ await showStatisticsDetails();
476
+ return viewStatistics();
477
+
478
+ case 1: // Reset Statistics
479
+ const { simpleInput } = require('./lib/ui/prompts');
480
+ const confirm = await simpleInput(colors.yellow + ' ' + await i18n.t('statistics.reset_confirm') + ' ' + colors.reset);
481
+ if (confirm.toLowerCase() === 'y') {
482
+ apiManager.resetStatistics();
483
+ console.log(colors.green + ' ✓ ' + await i18n.t('statistics.reset_success') + colors.reset);
484
+ await waitForKey(await i18n.t('messages.prompts.press_any_key'));
485
+ }
486
+ return viewStatistics();
487
+
488
+ case 2: // Back
489
+ case -1:
490
+ default:
491
+ return;
492
+ }
493
+ }
494
+
495
+ /**
496
+ * Show detailed statistics
497
+ */
498
+ async function showStatisticsDetails() {
499
+ const { padStringToWidth } = require('./lib/utils/string-width');
500
+
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
+ const stats = apiManager.getEnhancedStatistics();
368
507
 
369
- console.log(colors.cyan + ' ' + i18n.tSync('ui.general.summary') + colors.reset);
508
+ // Summary section
509
+ console.log(colors.cyan + ' ' + i18n.tSync('ui.general.summary') + ':' + colors.reset);
370
510
  console.log(colors.gray + ` ${await i18n.t('statistics.total_apis', stats.totalApis)}` + colors.reset);
371
511
  console.log(colors.gray + ` ${await i18n.t('statistics.active_api', stats.activeApiName)}` + colors.reset);
372
512
  console.log(colors.gray + ` ${await i18n.t('statistics.most_used', stats.mostUsedApi)}` + colors.reset);
373
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);
374
515
  console.log('');
375
516
 
376
- if (apis.length > 0) {
377
- console.log(colors.cyan + ' ' + i18n.tSync('ui.general.configured_apis') + colors.reset);
378
-
379
- // Pre-fetch translations to avoid await in forEach
380
- const currentlyActiveText = await i18n.t('api.details.currently_active');
381
- const providerText = await i18n.t('api.details.provider');
382
- const usageText = await i18n.t('api.details.usage');
383
- const timesSuffixText = await i18n.t('api.details.times_suffix');
384
- const createdAtText = await i18n.t('api.details.created_at');
385
-
386
- apis.forEach((api, index) => {
387
- const isActive = apiManager.getActiveApi()?.id === api.id;
388
- const activeText = isActive ? ` (${currentlyActiveText})` : '';
389
- console.log(colors.gray + ` ${index + 1}. ${api.name}${activeText}` + colors.reset);
390
- console.log(colors.dim + ` ${providerText}: ${api.provider}` + colors.reset);
391
- console.log(colors.dim + ` ${usageText}: ${api.usageCount || 0} ${timesSuffixText}` + colors.reset);
392
- console.log(colors.dim + ` ${createdAtText}: ${api.createdAt}` + colors.reset);
393
- });
517
+ if (stats.apiStats.length > 0) {
518
+ console.log(colors.cyan + ' ' + i18n.tSync('ui.general.configured_apis') + ':' + colors.reset);
519
+ console.log('');
520
+
521
+ // Table header
522
+ console.log(colors.dim + ' ' +
523
+ padStringToWidth(await i18n.t('statistics.header_name'), 20) +
524
+ padStringToWidth(await i18n.t('statistics.header_usage'), 10) +
525
+ padStringToWidth(await i18n.t('statistics.header_success'), 10) +
526
+ await i18n.t('statistics.header_last_used') +
527
+ colors.reset);
528
+ console.log(colors.dim + ' ' + '─'.repeat(60) + colors.reset);
529
+
530
+ for (const api of stats.apiStats) {
531
+ const lastUsedText = formatRelativeTime(api.lastUsed);
532
+ console.log(colors.gray + ' ' +
533
+ padStringToWidth(api.name, 20) +
534
+ padStringToWidth(String(api.usageCount), 10) +
535
+ padStringToWidth(api.successRate, 10) +
536
+ lastUsedText +
537
+ colors.reset);
538
+ }
539
+ } else {
540
+ console.log(colors.gray + ' ' + await i18n.t('statistics.no_usage') + colors.reset);
394
541
  }
395
542
 
396
543
  console.log('');
@@ -568,20 +715,23 @@ async function showApiManagementMenu() {
568
715
 
569
716
  // Build menu options based on password setup status
570
717
  const menuOptions = [
571
- await i18n.t('menu.api_management.add_new'),
572
- await i18n.t('menu.api_management.remove'),
573
- await i18n.t('menu.api_management.switch'),
574
- await i18n.t('menu.api_management.statistics')
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
575
722
  ];
576
723
 
577
724
  // Add import/export options only if password is set
578
725
  if (apiManager.canUseImportExport()) {
579
- menuOptions.push(await i18n.t('menu.api_management.export'));
580
- menuOptions.push(await i18n.t('menu.api_management.import'));
581
- menuOptions.push(await i18n.t('menu.api_management.change_password'));
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
582
729
  }
583
730
 
584
- menuOptions.push(await i18n.t('menu.api_management.back'));
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
585
735
 
586
736
  // Ensure global menus are initialized
587
737
  initializeGlobalMenus();
@@ -604,7 +754,7 @@ async function showApiManagementMenu() {
604
754
  await viewStatistics();
605
755
  return showMenu();
606
756
  } else if (apiManager.canUseImportExport()) {
607
- // If import/export is available, handle those options
757
+ // With import/export enabled: indices 4-8
608
758
  if (choice === 4) { // Export Configuration
609
759
  await exportConfiguration();
610
760
  return showMenu();
@@ -614,12 +764,16 @@ async function showApiManagementMenu() {
614
764
  } else if (choice === 6) { // Change Password
615
765
  await changePassword();
616
766
  return showMenu();
617
- } else if (choice === 7) { // Back to Main Menu
767
+ } else if (choice === 7) { // Model Upgrade Settings (NEW)
768
+ return await showModelUpgradeSettings();
769
+ } else if (choice === 8) { // Back to Main Menu
618
770
  return showMenu();
619
771
  }
620
772
  } else {
621
- // If import/export is not available, only Back to Main Menu
622
- if (choice === 4) { // Back to Main Menu
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
623
777
  return showMenu();
624
778
  }
625
779
  }
@@ -628,6 +782,144 @@ async function showApiManagementMenu() {
628
782
  return showMenu();
629
783
  }
630
784
 
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
+ /**
844
+ * Perform manual upgrade for all APIs with interactive confirmation
845
+ */
846
+ async function performManualUpgrade() {
847
+ const { getLatestModel, getProvider } = require('./lib/presets/providers');
848
+ const { simpleInput } = require('./lib/ui/prompts');
849
+
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
+ const apis = apiManager.getApis();
856
+
857
+ if (apis.length === 0) {
858
+ console.log(colors.yellow + ' ' + await i18n.t('messages.info.no_apis') + colors.reset);
859
+ await waitForKey(await i18n.t('messages.prompts.press_any_key'));
860
+ return;
861
+ }
862
+
863
+ console.log(colors.gray + ' ' + await i18n.t('model_upgrade.manual_checking', apis.length) + colors.reset);
864
+ console.log('');
865
+
866
+ let upgradedCount = 0;
867
+ let skippedUpToDate = 0;
868
+ let skippedNoInfo = 0;
869
+ let skippedByUser = 0;
870
+
871
+ for (let i = 0; i < apis.length; i++) {
872
+ const api = apis[i];
873
+ const latestModel = getLatestModel(api.model, api.provider);
874
+
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);
878
+
879
+ if (latestModel) {
880
+ console.log(colors.green + ' ' + await i18n.t('model_upgrade.manual_api_latest', latestModel) + colors.reset);
881
+ console.log('');
882
+
883
+ // Ask for confirmation
884
+ const answer = await simpleInput(colors.yellow + ' ' + await i18n.t('model_upgrade.manual_confirm') + ' ' + colors.reset);
885
+
886
+ 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++;
890
+ } else {
891
+ console.log(colors.dim + ' ' + await i18n.t('model_upgrade.manual_skipped') + colors.reset);
892
+ skippedByUser++;
893
+ }
894
+ } else {
895
+ // No upgrade info available - check if model exists in provider
896
+ const provider = getProvider(api.provider);
897
+ 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);
900
+ skippedUpToDate++;
901
+ } else {
902
+ console.log(colors.dim + ' ' + await i18n.t('model_upgrade.manual_api_no_info') + colors.reset);
903
+ skippedNoInfo++;
904
+ }
905
+ }
906
+
907
+ console.log('');
908
+ }
909
+
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',
915
+ skippedUpToDate + skippedNoInfo + skippedByUser,
916
+ skippedUpToDate,
917
+ skippedNoInfo) + colors.reset);
918
+ console.log('');
919
+
920
+ await waitForKey(await i18n.t('messages.prompts.press_any_key'));
921
+ }
922
+
631
923
  /**
632
924
  * Handle third-party API launch
633
925
  */
@@ -646,12 +938,15 @@ async function handleThirdPartyApiLaunch(skipPermissions = false) {
646
938
  return showMenu();
647
939
  }
648
940
 
649
- // Increment usage count for the active API since we're actually using it
650
- apiManager.incrementActiveApiUsage();
941
+ // Record successful launch BEFORE launching (since process exits after)
942
+ apiManager.recordSuccessfulLaunch();
651
943
 
652
944
  launchClaudeWithApi(activeApi, skipPermissions);
653
945
 
654
946
  } catch (error) {
947
+ // Record failed launch
948
+ apiManager.recordFailedLaunch(error.message);
949
+
655
950
  showError('Failed to launch with third-party API', [error.message]);
656
951
 
657
952
  setTimeout(() => {
@@ -673,24 +968,28 @@ async function executeSelection(selectedIndex) {
673
968
  launchClaudeSkipPermissions();
674
969
  break;
675
970
 
676
- case 2: // Launch Claude Code with 3rd-party API
971
+ case 2: // Launch Claude Code (Enable Auto Mode)
972
+ launchClaudeAutoMode();
973
+ break;
974
+
975
+ case 3: // Launch Claude Code with 3rd-party API
677
976
  await handleThirdPartyApiLaunch(false);
678
977
  break;
679
978
 
680
- case 3: // Launch Claude Code with 3rd-party API (Skip Permissions)
979
+ case 4: // Launch Claude Code with 3rd-party API (Skip Permissions)
681
980
  await handleThirdPartyApiLaunch(true);
682
981
  break;
683
982
 
684
- case 4: // 3rd-party API Management
983
+ case 5: // 3rd-party API Management
685
984
  return await showApiManagementMenu();
686
985
 
687
- case 5: // Language Settings
986
+ case 6: // Language Settings
688
987
  return await showLanguageSettings();
689
988
 
690
- case 6: // Version Update Check
989
+ case 7: // Version Update Check
691
990
  return await showVersionUpdateCheck();
692
991
 
693
- case 7: // Exit
992
+ case 8: // Exit
694
993
  console.log('');
695
994
  console.log(colors.green + '👋 ' + await i18n.t('menu.main.exit') + '!' + colors.reset);
696
995
  process.exit(0);
@@ -741,10 +1040,65 @@ async function showMenu() {
741
1040
  // Silently ignore update check errors
742
1041
  }
743
1042
 
1043
+ // ========================================
1044
+ // Model upgrade check (new feature)
1045
+ // ========================================
1046
+ let modelUpgradeInfo = null;
1047
+ try {
1048
+ const upgradeChecker = require('./lib/utils/model-upgrade-checker');
1049
+ const autoUpgrade = await upgradeChecker.isAutoUpgradeEnabled();
1050
+
1051
+ if (autoUpgrade) {
1052
+ // Auto upgrade enabled: always check and upgrade (bypass cache)
1053
+ const upgrades = upgradeChecker.checkAllApiUpgrades(apiManager);
1054
+ if (upgrades.length > 0) {
1055
+ const upgraded = upgradeChecker.performAutoUpgrade(apiManager, upgrades);
1056
+ if (upgraded.length > 0) {
1057
+ modelUpgradeInfo = colors.green + ' ✓ ' +
1058
+ i18n.tSync('model_upgrade.auto_upgraded', upgraded[0].from, upgraded[0].to) + colors.reset;
1059
+ if (upgraded.length > 1) {
1060
+ modelUpgradeInfo += '\n' + colors.green + ' ' +
1061
+ `(+${upgraded.length - 1} more)` + colors.reset;
1062
+ }
1063
+ }
1064
+ }
1065
+ } else {
1066
+ // Auto upgrade disabled: use cache for notification
1067
+ const result = await upgradeChecker.checkForModelUpgrades(apiManager);
1068
+ if (result.needsCheck && result.upgrades.length > 0) {
1069
+ const first = result.upgrades[0];
1070
+ modelUpgradeInfo = colors.yellow + ' ⚠️ ' +
1071
+ i18n.tSync('model_upgrade.notification', first.currentModel, first.latestModel) +
1072
+ colors.reset + '\n' +
1073
+ colors.yellow + ' ' +
1074
+ i18n.tSync('model_upgrade.notification_api', first.apiName) +
1075
+ colors.reset + '\n' +
1076
+ colors.gray + ' ' +
1077
+ i18n.tSync('model_upgrade.notification_hint') +
1078
+ colors.reset;
1079
+ }
1080
+ }
1081
+ } catch (error) {
1082
+ // Silently ignore model upgrade check errors
1083
+ }
1084
+
1085
+ // Combine version info and model upgrade info
1086
+ let displayInfo = '';
1087
+ if (versionInfo) {
1088
+ displayInfo += versionInfo;
1089
+ }
1090
+ if (modelUpgradeInfo) {
1091
+ if (displayInfo) {
1092
+ displayInfo += '\n';
1093
+ }
1094
+ displayInfo += modelUpgradeInfo;
1095
+ }
1096
+
744
1097
  // Populate menu options dynamically with i18n translations
745
1098
  menuOptions = [
746
1099
  await i18n.t('menu.main.launch_default'),
747
1100
  await i18n.t('menu.main.launch_skip'),
1101
+ await i18n.t('menu.main.launch_auto_mode'),
748
1102
  await i18n.t('menu.main.launch_api'),
749
1103
  await i18n.t('menu.main.launch_api_skip'),
750
1104
  await i18n.t('menu.main.api_management'),
@@ -753,8 +1107,31 @@ async function showMenu() {
753
1107
  await i18n.t('menu.main.exit')
754
1108
  ];
755
1109
 
1110
+ // Pre-compute hint texts synchronously for menu callback
1111
+ const hintAutoMode = i18n.tSync('hints.auto_mode_info');
1112
+ const activeApi = apiManager.getActiveApi();
1113
+ let hintApiInfo = null;
1114
+ if (activeApi) {
1115
+ const { getProvider } = require('./lib/presets/providers');
1116
+ 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);
1119
+ } else {
1120
+ hintApiInfo = i18n.tSync('hints.no_active_api');
1121
+ }
1122
+
1123
+ // Synchronous hint callback — must not use await
1124
+ const hintCallback = (selectedIndex) => {
1125
+ switch (selectedIndex) {
1126
+ case 2: return hintAutoMode;
1127
+ case 3: return hintApiInfo;
1128
+ case 4: return hintApiInfo;
1129
+ default: return null;
1130
+ }
1131
+ };
1132
+
756
1133
  globalMainMenu.setOptions(menuOptions);
757
- const selection = await globalMainMenu.navigate(false, versionInfo); // Pass version info to display between banner and nav
1134
+ const selection = await globalMainMenu.navigate(false, displayInfo || null, hintCallback);
758
1135
 
759
1136
  if (selection === -1) {
760
1137
  console.log('');