@kikkimo/claude-launcher 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,47 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.4.0] - 2026-02-12
9
+
10
+ ### Added
11
+ - **GLM-5 Model Support**: Added GLM-5 model for ZhiPu AI providers (`zhipu` and `zai`)
12
+ - **Claude Opus 4.6 Model**: Added `claude-opus-4-6` model support with unified hyphen naming format
13
+ - **Model Upgrade Notification**: Automatic startup notification when newer model versions are available for configured APIs
14
+ - **Model Upgrade Settings Menu**: New submenu under API Management with:
15
+ - Auto Upgrade toggle (ON/OFF) - automatically use latest model versions
16
+ - Manual Upgrade option - review and confirm each model upgrade individually
17
+ - **Enhanced Usage Statistics**: Added success/failure rate tracking for API calls:
18
+ - Overall success rate display
19
+ - Per-API success rate in statistics table
20
+ - Time-based last used display (just now, minutes ago, hours ago, days ago)
21
+ - **Statistics Submenu**: Restructured statistics page with submenu:
22
+ - View Statistics Details
23
+ - Reset Statistics
24
+ - **Clear All APIs**: New bulk delete option in Remove API submenu:
25
+ - Delete Single API
26
+ - Clear All APIs (with CLEAR confirmation prompt)
27
+
28
+ ### Changed
29
+ - **Model Naming Convention**: Unified all model names to use hyphen format (e.g., `claude-opus-4-6` instead of mixed formats)
30
+ - **Auto Upgrade Toggle**: Changed to radio button style for better visual feedback
31
+ - **Statistics Display**: Enhanced table format with success rate column and relative time display
32
+ - **Menu Structure**: Reorganized API management with logical submenu groupings
33
+
34
+ ### Fixed
35
+ - **Auto Upgrade Execution**: Fixed auto upgrade to bypass cache and execute immediately when enabled
36
+ - **Model Upgrade Notification**: Corrected menu name reference in upgrade notification hint
37
+ - **i18n Synchronization**: Synced all 41 missing translation entries across 9 non-English locale files:
38
+ - Added `statistics` enhanced fields (15 entries) to all locales
39
+ - Added complete `model_upgrade` module (25 entries) to all locales
40
+ - Added missing `confirm_password_prompt` to affected locales
41
+
42
+ ### Documentation
43
+ - **README Updates**: Updated both English and Chinese README files with:
44
+ - Model upgrade feature documentation
45
+ - Updated API management menu structure
46
+ - Success/failure rate tracking description
47
+ - Updated supported providers list
48
+
8
49
  ## [2.3.0] - 2025-12-24
9
50
 
10
51
  ### Added
package/README.md CHANGED
@@ -25,11 +25,12 @@ An elegant interactive launcher for Claude Code with a beautiful Claude-style in
25
25
  - Strong password requirements and validation
26
26
 
27
27
  ### 🚀 **Third-party API Management**
28
- - Full support for multiple third-party API providers (OpenAI, Anthropic, DeepSeek, Kimi, MiniMax, GLM (ZhiPu AI), and more)
28
+ - Full support for multiple third-party API providers (OpenAI, Anthropic, DeepSeek, Kimi, MiniMax, GLM/ZhiPu AI, and custom APIs)
29
29
  - Interactive API configuration with validation
30
- - API usage statistics and tracking
30
+ - API usage statistics with success/failure tracking
31
+ - Model upgrade notifications and auto-upgrade support
31
32
  - Secure configuration backup and restore
32
- - Easy API switching and removal
33
+ - Easy API switching, removal, and bulk clear
33
34
 
34
35
  ### 🌍 **Enterprise-grade Features**
35
36
  - Global installation - use `claude-launcher` from anywhere
@@ -85,7 +86,11 @@ node claude-launcher
85
86
  2. **Launch Claude Code (Skip Permissions)** - Launch with `--dangerously-skip-permissions`
86
87
  3. **Launch Claude Code with Third-party API** - Use configured third-party API
87
88
  4. **Launch Claude Code with Third-party API (Skip Permissions)** - Combine third-party API with permission skipping
88
- 5. **Third-party API Management** - Configure, switch, remove APIs, view statistics
89
+ 5. **Third-party API Management** - Full API lifecycle management:
90
+ - Add, switch, and remove APIs
91
+ - View usage statistics with success/failure rates
92
+ - Model upgrade settings (auto/manual upgrade)
93
+ - Import/export configurations
89
94
  6. **Language Settings** - Switch between 11 supported languages
90
95
  7. **Version Update Check** - Check for launcher updates
91
96
  8. **Exit** - Close the launcher
@@ -126,15 +131,23 @@ Access comprehensive API management through the dedicated menu:
126
131
  📋 Third-party API Management
127
132
 
128
133
  → Add New API
129
- Remove API
134
+ Remove API → Delete Single API / Clear All APIs
130
135
  Switch Active API
131
- View Statistics
136
+ View Statistics → View Details / Reset Statistics
137
+ Model Upgrade → Auto Upgrade [ON/OFF] / Manual Upgrade
132
138
  Export Configuration
133
139
  Import Configuration
134
140
  Change Password
135
141
  Back to Main Menu
136
142
  ```
137
143
 
144
+ ### Model Upgrade Feature
145
+
146
+ The launcher automatically checks for model upgrades when you start:
147
+ - **Auto Upgrade**: Automatically use the latest model version
148
+ - **Manual Upgrade**: Review and confirm each model upgrade
149
+ - **Startup Notifications**: Get notified when newer model versions are available
150
+
138
151
  ## ⚙️ Configuration
139
152
 
140
153
  ### Modern Configuration System
@@ -158,10 +171,11 @@ Claude Launcher 2.0 uses an advanced configuration system:
158
171
 
159
172
  Configure any third-party API provider through the interactive interface:
160
173
 
161
- - **Supported Providers**: OpenAI, Anthropic, DeepSeek, Kimi, MiniMax (CN/Global), GLM (ZhiPu AI), and custom APIs
174
+ - **Supported Providers**: Anthropic, OpenAI, DeepSeek, Moonshot/Kimi, MiniMax (CN/Global), GLM/ZhiPu AI (GLM-4, GLM-5), and custom Anthropic-compatible APIs
162
175
  - **Secure Storage**: All API tokens encrypted before storage
163
176
  - **Validation**: Real-time validation of URLs, tokens, and models
164
- - **Usage Tracking**: Monitor API usage statistics
177
+ - **Usage Tracking**: Monitor API usage statistics with success/failure rates
178
+ - **Model Upgrade**: Automatic detection and upgrade to latest model versions
165
179
  - **Provider-specific Features**: Optimized configuration for each provider with helpful notes and recommendations
166
180
 
167
181
  ### Configuration Import/Export
package/claude-launcher CHANGED
@@ -263,9 +263,52 @@ async function addNewThirdPartyApi() {
263
263
  }
264
264
 
265
265
  /**
266
- * Remove third-party API
266
+ * Remove third-party API menu with submenu
267
267
  */
268
268
  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
+ const apis = apiManager.getApis();
275
+
276
+ // Show current API count
277
+ if (apis.length > 0) {
278
+ console.log(colors.cyan + ' ' + await i18n.t('messages.info.current_api_count', apis.length) + colors.reset);
279
+ console.log('');
280
+ }
281
+
282
+ const menuOptions = [
283
+ await i18n.t('menu.remove_api.delete_single'),
284
+ await i18n.t('menu.remove_api.clear_all'),
285
+ await i18n.t('menu.remove_api.back')
286
+ ];
287
+
288
+ initializeGlobalMenus();
289
+ globalApiManagementMenu.setOptions(menuOptions);
290
+ const choice = await globalApiManagementMenu.navigate();
291
+
292
+ switch (choice) {
293
+ case 0: // Delete Single API
294
+ await deleteSingleApi();
295
+ return;
296
+
297
+ case 1: // Clear All APIs
298
+ await clearAllApis();
299
+ return;
300
+
301
+ case 2: // Back
302
+ case -1:
303
+ default:
304
+ return;
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Delete a single API
310
+ */
311
+ async function deleteSingleApi() {
269
312
  try {
270
313
  // Get API list
271
314
  const apis = apiManager.getApis();
@@ -281,7 +324,7 @@ async function removeThirdPartyApi() {
281
324
  );
282
325
 
283
326
  if (!selectedApi) {
284
- return showMenu();
327
+ return;
285
328
  }
286
329
 
287
330
  // Show confirmation dialog
@@ -299,23 +342,20 @@ async function removeThirdPartyApi() {
299
342
  `${await i18n.t('api.details.provider')}: ${selectedApi.provider}`
300
343
  ]);
301
344
 
302
- // Show success message and return to main menu
345
+ // Show success message
303
346
  const remainingApis = apiManager.getApis();
304
347
  if (remainingApis.length === 0) {
305
348
  showInfo(await i18n.t('messages.info.all_apis_removed'));
306
349
  }
307
350
  await waitForKey(await i18n.t('messages.prompts.press_any_key'));
308
- return showMenu();
309
351
 
310
352
  } catch (removeError) {
311
353
  showError(await i18n.t('errors.api.failed_remove', removeError.message));
312
354
  await waitForKey(await i18n.t('messages.prompts.press_any_key'));
313
- return showMenu();
314
355
  }
315
356
  } else {
316
357
  showInfo(await i18n.t('messages.info.removal_cancelled'));
317
358
  await waitForKey(await i18n.t('messages.prompts.press_any_key'));
318
- return showMenu();
319
359
  }
320
360
 
321
361
  } catch (error) {
@@ -325,6 +365,42 @@ async function removeThirdPartyApi() {
325
365
  }
326
366
  }
327
367
 
368
+ /**
369
+ * Clear all APIs with confirmation
370
+ */
371
+ async function clearAllApis() {
372
+ const { simpleInput } = require('./lib/ui/prompts');
373
+
374
+ const apis = apiManager.getApis();
375
+ const count = apis.length;
376
+
377
+ if (count === 0) {
378
+ console.clear();
379
+ showInfo(await i18n.t('messages.info.no_apis'));
380
+ await waitForKey(await i18n.t('messages.prompts.press_any_key'));
381
+ return;
382
+ }
383
+
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('');
390
+
391
+ const input = await simpleInput(colors.cyan + ' ' + await i18n.t('messages.prompts.confirm_clear_all_input') + colors.reset);
392
+
393
+ if (input === 'CLEAR') {
394
+ const clearedCount = apiManager.clearAllApis();
395
+ console.clear();
396
+ showSuccess(await i18n.t('messages.info.all_apis_cleared', clearedCount));
397
+ await waitForKey(await i18n.t('messages.prompts.press_any_key'));
398
+ } else {
399
+ showInfo(await i18n.t('messages.info.clear_cancelled'));
400
+ await waitForKey(await i18n.t('messages.prompts.press_any_key'));
401
+ }
402
+ }
403
+
328
404
  /**
329
405
  * Switch active third-party API
330
406
  */
@@ -357,40 +433,110 @@ async function switchThirdPartyApi() {
357
433
  /**
358
434
  * View API statistics
359
435
  */
436
+ /**
437
+ * Format timestamp to relative time
438
+ * @param {string|null} timestamp - ISO timestamp or null
439
+ * @returns {string} Relative time string
440
+ */
441
+ function formatRelativeTime(timestamp) {
442
+ if (!timestamp) return i18n.tSync('statistics.time_never');
443
+
444
+ const now = Date.now();
445
+ const diff = now - new Date(timestamp).getTime();
446
+ const minutes = Math.floor(diff / 60000);
447
+ const hours = Math.floor(diff / 3600000);
448
+ const days = Math.floor(diff / 86400000);
449
+
450
+ if (minutes < 1) return i18n.tSync('statistics.time_just_now');
451
+ if (minutes < 60) return i18n.tSync('statistics.time_minutes_ago', minutes);
452
+ if (hours < 24) return i18n.tSync('statistics.time_hours_ago', hours);
453
+ return i18n.tSync('statistics.time_days_ago', days);
454
+ }
455
+
360
456
  async function viewStatistics() {
361
457
  console.clear();
362
458
  console.log('');
363
459
  console.log(colors.bright + colors.orange + '📊 ' + await i18n.t('statistics.title') + colors.reset);
364
460
  console.log('');
365
461
 
366
- const stats = apiManager.getStatistics();
367
- const apis = apiManager.getApis();
462
+ const menuOptions = [
463
+ await i18n.t('statistics.menu_view'),
464
+ await i18n.t('statistics.menu_reset'),
465
+ await i18n.t('statistics.menu_back')
466
+ ];
467
+
468
+ initializeGlobalMenus();
469
+ globalApiManagementMenu.setOptions(menuOptions);
470
+ const choice = await globalApiManagementMenu.navigate();
471
+
472
+ switch (choice) {
473
+ case 0: // View Statistics Details
474
+ await showStatisticsDetails();
475
+ return viewStatistics();
476
+
477
+ case 1: // Reset Statistics
478
+ const { simpleInput } = require('./lib/ui/prompts');
479
+ const confirm = await simpleInput(colors.yellow + ' ' + await i18n.t('statistics.reset_confirm') + ' ' + colors.reset);
480
+ if (confirm.toLowerCase() === 'y') {
481
+ apiManager.resetStatistics();
482
+ console.log(colors.green + ' ✓ ' + await i18n.t('statistics.reset_success') + colors.reset);
483
+ await waitForKey(await i18n.t('messages.prompts.press_any_key'));
484
+ }
485
+ return viewStatistics();
486
+
487
+ case 2: // Back
488
+ case -1:
489
+ default:
490
+ return;
491
+ }
492
+ }
493
+
494
+ /**
495
+ * Show detailed statistics
496
+ */
497
+ async function showStatisticsDetails() {
498
+ const { padStringToWidth } = require('./lib/utils/string-width');
368
499
 
369
- console.log(colors.cyan + ' ' + i18n.tSync('ui.general.summary') + colors.reset);
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
+ const stats = apiManager.getEnhancedStatistics();
506
+
507
+ // Summary section
508
+ console.log(colors.cyan + ' ' + i18n.tSync('ui.general.summary') + ':' + colors.reset);
370
509
  console.log(colors.gray + ` ${await i18n.t('statistics.total_apis', stats.totalApis)}` + colors.reset);
371
510
  console.log(colors.gray + ` ${await i18n.t('statistics.active_api', stats.activeApiName)}` + colors.reset);
372
511
  console.log(colors.gray + ` ${await i18n.t('statistics.most_used', stats.mostUsedApi)}` + colors.reset);
373
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);
374
514
  console.log('');
375
515
 
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
- });
516
+ if (stats.apiStats.length > 0) {
517
+ console.log(colors.cyan + ' ' + i18n.tSync('ui.general.configured_apis') + ':' + colors.reset);
518
+ console.log('');
519
+
520
+ // Table header
521
+ console.log(colors.dim + ' ' +
522
+ padStringToWidth(await i18n.t('statistics.header_name'), 20) +
523
+ padStringToWidth(await i18n.t('statistics.header_usage'), 10) +
524
+ padStringToWidth(await i18n.t('statistics.header_success'), 10) +
525
+ await i18n.t('statistics.header_last_used') +
526
+ colors.reset);
527
+ console.log(colors.dim + ' ' + '─'.repeat(60) + colors.reset);
528
+
529
+ for (const api of stats.apiStats) {
530
+ const lastUsedText = formatRelativeTime(api.lastUsed);
531
+ console.log(colors.gray + ' ' +
532
+ padStringToWidth(api.name, 20) +
533
+ padStringToWidth(String(api.usageCount), 10) +
534
+ padStringToWidth(api.successRate, 10) +
535
+ lastUsedText +
536
+ colors.reset);
537
+ }
538
+ } else {
539
+ console.log(colors.gray + ' ' + await i18n.t('statistics.no_usage') + colors.reset);
394
540
  }
395
541
 
396
542
  console.log('');
@@ -568,20 +714,23 @@ async function showApiManagementMenu() {
568
714
 
569
715
  // Build menu options based on password setup status
570
716
  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')
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
575
721
  ];
576
722
 
577
723
  // Add import/export options only if password is set
578
724
  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'));
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
582
728
  }
583
729
 
584
- menuOptions.push(await i18n.t('menu.api_management.back'));
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
585
734
 
586
735
  // Ensure global menus are initialized
587
736
  initializeGlobalMenus();
@@ -604,7 +753,7 @@ async function showApiManagementMenu() {
604
753
  await viewStatistics();
605
754
  return showMenu();
606
755
  } else if (apiManager.canUseImportExport()) {
607
- // If import/export is available, handle those options
756
+ // With import/export enabled: indices 4-8
608
757
  if (choice === 4) { // Export Configuration
609
758
  await exportConfiguration();
610
759
  return showMenu();
@@ -614,12 +763,16 @@ async function showApiManagementMenu() {
614
763
  } else if (choice === 6) { // Change Password
615
764
  await changePassword();
616
765
  return showMenu();
617
- } else if (choice === 7) { // Back to Main Menu
766
+ } else if (choice === 7) { // Model Upgrade Settings (NEW)
767
+ return await showModelUpgradeSettings();
768
+ } else if (choice === 8) { // Back to Main Menu
618
769
  return showMenu();
619
770
  }
620
771
  } else {
621
- // If import/export is not available, only Back to Main Menu
622
- if (choice === 4) { // Back to Main Menu
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
623
776
  return showMenu();
624
777
  }
625
778
  }
@@ -628,6 +781,144 @@ async function showApiManagementMenu() {
628
781
  return showMenu();
629
782
  }
630
783
 
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
+ /**
843
+ * Perform manual upgrade for all APIs with interactive confirmation
844
+ */
845
+ async function performManualUpgrade() {
846
+ const { getLatestModel, getProvider } = require('./lib/presets/providers');
847
+ const { simpleInput } = require('./lib/ui/prompts');
848
+
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
+ const apis = apiManager.getApis();
855
+
856
+ if (apis.length === 0) {
857
+ console.log(colors.yellow + ' ' + await i18n.t('messages.info.no_apis') + colors.reset);
858
+ await waitForKey(await i18n.t('messages.prompts.press_any_key'));
859
+ return;
860
+ }
861
+
862
+ console.log(colors.gray + ' ' + await i18n.t('model_upgrade.manual_checking', apis.length) + colors.reset);
863
+ console.log('');
864
+
865
+ let upgradedCount = 0;
866
+ let skippedUpToDate = 0;
867
+ let skippedNoInfo = 0;
868
+ let skippedByUser = 0;
869
+
870
+ for (let i = 0; i < apis.length; i++) {
871
+ const api = apis[i];
872
+ const latestModel = getLatestModel(api.model, api.provider);
873
+
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);
877
+
878
+ if (latestModel) {
879
+ console.log(colors.green + ' ' + await i18n.t('model_upgrade.manual_api_latest', latestModel) + colors.reset);
880
+ console.log('');
881
+
882
+ // Ask for confirmation
883
+ const answer = await simpleInput(colors.yellow + ' ' + await i18n.t('model_upgrade.manual_confirm') + ' ' + colors.reset);
884
+
885
+ 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++;
889
+ } else {
890
+ console.log(colors.dim + ' ' + await i18n.t('model_upgrade.manual_skipped') + colors.reset);
891
+ skippedByUser++;
892
+ }
893
+ } else {
894
+ // No upgrade info available - check if model exists in provider
895
+ const provider = getProvider(api.provider);
896
+ 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);
899
+ skippedUpToDate++;
900
+ } else {
901
+ console.log(colors.dim + ' ' + await i18n.t('model_upgrade.manual_api_no_info') + colors.reset);
902
+ skippedNoInfo++;
903
+ }
904
+ }
905
+
906
+ console.log('');
907
+ }
908
+
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',
914
+ skippedUpToDate + skippedNoInfo + skippedByUser,
915
+ skippedUpToDate,
916
+ skippedNoInfo) + colors.reset);
917
+ console.log('');
918
+
919
+ await waitForKey(await i18n.t('messages.prompts.press_any_key'));
920
+ }
921
+
631
922
  /**
632
923
  * Handle third-party API launch
633
924
  */
@@ -646,12 +937,15 @@ async function handleThirdPartyApiLaunch(skipPermissions = false) {
646
937
  return showMenu();
647
938
  }
648
939
 
649
- // Increment usage count for the active API since we're actually using it
650
- apiManager.incrementActiveApiUsage();
940
+ // Record successful launch BEFORE launching (since process exits after)
941
+ apiManager.recordSuccessfulLaunch();
651
942
 
652
943
  launchClaudeWithApi(activeApi, skipPermissions);
653
944
 
654
945
  } catch (error) {
946
+ // Record failed launch
947
+ apiManager.recordFailedLaunch(error.message);
948
+
655
949
  showError('Failed to launch with third-party API', [error.message]);
656
950
 
657
951
  setTimeout(() => {
@@ -741,6 +1035,60 @@ async function showMenu() {
741
1035
  // Silently ignore update check errors
742
1036
  }
743
1037
 
1038
+ // ========================================
1039
+ // Model upgrade check (new feature)
1040
+ // ========================================
1041
+ let modelUpgradeInfo = null;
1042
+ try {
1043
+ const upgradeChecker = require('./lib/utils/model-upgrade-checker');
1044
+ const autoUpgrade = await upgradeChecker.isAutoUpgradeEnabled();
1045
+
1046
+ if (autoUpgrade) {
1047
+ // Auto upgrade enabled: always check and upgrade (bypass cache)
1048
+ const upgrades = upgradeChecker.checkAllApiUpgrades(apiManager);
1049
+ if (upgrades.length > 0) {
1050
+ const upgraded = upgradeChecker.performAutoUpgrade(apiManager, upgrades);
1051
+ if (upgraded.length > 0) {
1052
+ modelUpgradeInfo = colors.green + ' ✓ ' +
1053
+ i18n.tSync('model_upgrade.auto_upgraded', upgraded[0].from, upgraded[0].to) + colors.reset;
1054
+ if (upgraded.length > 1) {
1055
+ modelUpgradeInfo += '\n' + colors.green + ' ' +
1056
+ `(+${upgraded.length - 1} more)` + colors.reset;
1057
+ }
1058
+ }
1059
+ }
1060
+ } else {
1061
+ // Auto upgrade disabled: use cache for notification
1062
+ const result = await upgradeChecker.checkForModelUpgrades(apiManager);
1063
+ if (result.needsCheck && result.upgrades.length > 0) {
1064
+ const first = result.upgrades[0];
1065
+ modelUpgradeInfo = colors.yellow + ' ⚠️ ' +
1066
+ i18n.tSync('model_upgrade.notification', first.currentModel, first.latestModel) +
1067
+ colors.reset + '\n' +
1068
+ colors.yellow + ' ' +
1069
+ i18n.tSync('model_upgrade.notification_api', first.apiName) +
1070
+ colors.reset + '\n' +
1071
+ colors.gray + ' ' +
1072
+ i18n.tSync('model_upgrade.notification_hint') +
1073
+ colors.reset;
1074
+ }
1075
+ }
1076
+ } catch (error) {
1077
+ // Silently ignore model upgrade check errors
1078
+ }
1079
+
1080
+ // Combine version info and model upgrade info
1081
+ let displayInfo = '';
1082
+ if (versionInfo) {
1083
+ displayInfo += versionInfo;
1084
+ }
1085
+ if (modelUpgradeInfo) {
1086
+ if (displayInfo) {
1087
+ displayInfo += '\n';
1088
+ }
1089
+ displayInfo += modelUpgradeInfo;
1090
+ }
1091
+
744
1092
  // Populate menu options dynamically with i18n translations
745
1093
  menuOptions = [
746
1094
  await i18n.t('menu.main.launch_default'),
@@ -754,7 +1102,7 @@ async function showMenu() {
754
1102
  ];
755
1103
 
756
1104
  globalMainMenu.setOptions(menuOptions);
757
- const selection = await globalMainMenu.navigate(false, versionInfo); // Pass version info to display between banner and nav
1105
+ const selection = await globalMainMenu.navigate(false, displayInfo || null); // Pass combined info to display between banner and nav
758
1106
 
759
1107
  if (selection === -1) {
760
1108
  console.log('');