@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/lib/ui/menu.js CHANGED
@@ -60,8 +60,9 @@ class Menu {
60
60
  * Display menu with current selection
61
61
  * @param {boolean} clearScreen - Whether to clear screen before displaying (default: true)
62
62
  * @param {string} versionInfo - Optional version info to display between banner and navigation
63
+ * @param {Function|null} hintCallback - Optional sync callback(selectedIndex) returning hint string or null
63
64
  */
64
- displayMenu(clearScreen = true, versionInfo = null) {
65
+ displayMenu(clearScreen = true, versionInfo = null, hintCallback = null) {
65
66
  // Clear screen and display header + menu together (like old version)
66
67
  if (clearScreen) {
67
68
  console.clear();
@@ -92,6 +93,15 @@ class Menu {
92
93
  }
93
94
  });
94
95
 
96
+ // Render dynamic hint if callback provided
97
+ if (hintCallback) {
98
+ const hintText = hintCallback(this.selectedIndex);
99
+ if (hintText) {
100
+ console.log('');
101
+ console.log(colors.green + ' \u2139 ' + hintText + colors.reset);
102
+ }
103
+ }
104
+
95
105
  console.log('');
96
106
  }
97
107
 
@@ -107,8 +117,9 @@ class Menu {
107
117
  * Handle keyboard navigation
108
118
  * @param {boolean} clearScreen - Whether to clear screen on initial display (default: true)
109
119
  * @param {string} versionInfo - Optional version info to display
120
+ * @param {Function|null} hintCallback - Optional sync callback(selectedIndex) returning hint string or null
110
121
  */
111
- async navigate(clearScreen = true, versionInfo = null) {
122
+ async navigate(clearScreen = true, versionInfo = null, hintCallback = null) {
112
123
  // Guard against empty menu to prevent NaN from modulo operations
113
124
  if (!this.menuOptions || this.menuOptions.length === 0) {
114
125
  console.log(colors.yellow + ' Warning: No menu options available' + colors.reset);
@@ -116,9 +127,10 @@ class Menu {
116
127
  }
117
128
 
118
129
  this.versionInfo = versionInfo; // Store for redrawing
130
+ this.hintCallback = hintCallback; // Store for redrawing
119
131
 
120
132
  return new Promise((resolve, reject) => {
121
- this.displayMenu(clearScreen, versionInfo);
133
+ this.displayMenu(clearScreen, versionInfo, hintCallback);
122
134
 
123
135
  if (process.stdin.isTTY) {
124
136
  const scope = stdinManager.acquire('raw', {
@@ -171,12 +183,12 @@ class Menu {
171
183
  switch (key) {
172
184
  case '\u001b[A': // Up arrow
173
185
  this.selectedIndex = (this.selectedIndex - 1 + this.menuOptions.length) % this.menuOptions.length;
174
- this.displayMenu(true, this.versionInfo);
186
+ this.displayMenu(true, this.versionInfo, this.hintCallback);
175
187
  break;
176
188
 
177
189
  case '\u001b[B': // Down arrow
178
190
  this.selectedIndex = (this.selectedIndex + 1) % this.menuOptions.length;
179
- this.displayMenu(true, this.versionInfo);
191
+ this.displayMenu(true, this.versionInfo, this.hintCallback);
180
192
  break;
181
193
 
182
194
  case '\r': // Enter
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Model Upgrade Checker
3
+ * Detects available model upgrades with time-based caching (same pattern as version-checker)
4
+ */
5
+
6
+ const { getLatestModel } = require('../presets/providers');
7
+ const versionChecker = require('./version-checker');
8
+
9
+ const CHECK_INTERVAL_HOURS = 12;
10
+
11
+ /**
12
+ * Check all APIs for available upgrades
13
+ * @param {ApiManager} apiManager - ApiManager instance (dependency injection)
14
+ * @returns {Array} Array of upgrade info objects
15
+ */
16
+ function checkAllApiUpgrades(apiManager) {
17
+ const apis = apiManager.getApis();
18
+ const upgrades = [];
19
+
20
+ for (const api of apis) {
21
+ const latestModel = getLatestModel(api.model, api.provider);
22
+ if (latestModel) {
23
+ upgrades.push({
24
+ apiId: api.id,
25
+ apiName: api.name,
26
+ providerId: api.provider,
27
+ currentModel: api.model,
28
+ latestModel: latestModel
29
+ });
30
+ }
31
+ }
32
+
33
+ return upgrades;
34
+ }
35
+
36
+ /**
37
+ * Check for model upgrades with caching
38
+ * @param {ApiManager} apiManager - ApiManager instance
39
+ * @param {boolean} force - Force check regardless of cache
40
+ * @returns {Promise<{upgrades: Array, needsCheck: boolean}>}
41
+ */
42
+ async function checkForModelUpgrades(apiManager, force = false) {
43
+ const config = await versionChecker.loadConfig();
44
+ const now = Date.now();
45
+ const cacheDuration = CHECK_INTERVAL_HOURS * 60 * 60 * 1000;
46
+
47
+ const needsCheck = force ||
48
+ !config.lastModelUpgradeCheck ||
49
+ (now - config.lastModelUpgradeCheck > cacheDuration);
50
+
51
+ if (!needsCheck) {
52
+ return { upgrades: [], needsCheck: false };
53
+ }
54
+
55
+ const upgrades = checkAllApiUpgrades(apiManager);
56
+
57
+ // Update last check time
58
+ config.lastModelUpgradeCheck = now;
59
+ await versionChecker.saveConfig(config);
60
+
61
+ return { upgrades, needsCheck: true };
62
+ }
63
+
64
+ /**
65
+ * Get auto upgrade setting
66
+ * @returns {Promise<boolean>} Whether auto upgrade is enabled
67
+ */
68
+ async function isAutoUpgradeEnabled() {
69
+ const config = await versionChecker.loadConfig();
70
+ return config.autoModelUpgrade === true;
71
+ }
72
+
73
+ /**
74
+ * Perform auto upgrade for all APIs with available upgrades
75
+ * @param {ApiManager} apiManager - ApiManager instance
76
+ * @param {Array} upgrades - Array of upgrade info from checkForModelUpgrades
77
+ * @returns {Array} Array of upgraded API info
78
+ */
79
+ function performAutoUpgrade(apiManager, upgrades) {
80
+ const upgraded = [];
81
+
82
+ for (const upgrade of upgrades) {
83
+ try {
84
+ apiManager.updateApiModel(upgrade.apiId, upgrade.latestModel);
85
+ upgraded.push({
86
+ apiName: upgrade.apiName,
87
+ from: upgrade.currentModel,
88
+ to: upgrade.latestModel
89
+ });
90
+ } catch (error) {
91
+ // Skip failed upgrades silently
92
+ }
93
+ }
94
+
95
+ return upgraded;
96
+ }
97
+
98
+ module.exports = {
99
+ checkAllApiUpgrades,
100
+ checkForModelUpgrades,
101
+ isAutoUpgradeEnabled,
102
+ performAutoUpgrade
103
+ };
@@ -22,13 +22,21 @@ async function loadConfig() {
22
22
  try {
23
23
  const configPath = getConfigPath();
24
24
  const data = await fs.readFile(configPath, 'utf8');
25
- return JSON.parse(data);
25
+ const config = JSON.parse(data);
26
+
27
+ // Add new fields for backward compatibility
28
+ if (config.autoModelUpgrade === undefined) config.autoModelUpgrade = false;
29
+ if (config.lastModelUpgradeCheck === undefined) config.lastModelUpgradeCheck = 0;
30
+
31
+ return config;
26
32
  } catch (error) {
27
33
  // Return default config if file doesn't exist
28
34
  return {
29
35
  language: 'zh',
30
36
  lastVersionCheck: 0,
31
- cachedLatestVersion: null
37
+ cachedLatestVersion: null,
38
+ autoModelUpgrade: false,
39
+ lastModelUpgradeCheck: 0
32
40
  };
33
41
  }
34
42
  }
@@ -231,10 +239,21 @@ async function forceCheckForUpdates(timeoutMs = 15000) {
231
239
  }
232
240
  }
233
241
 
242
+ /**
243
+ * Set auto model upgrade setting
244
+ * @param {boolean} enabled - Whether to enable auto model upgrade
245
+ */
246
+ async function setAutoModelUpgrade(enabled) {
247
+ const config = await loadConfig();
248
+ config.autoModelUpgrade = enabled;
249
+ await saveConfig(config);
250
+ }
251
+
234
252
  module.exports = {
235
253
  checkForUpdates,
236
254
  forceCheckForUpdates,
237
255
  clearCache,
238
256
  loadConfig,
239
- saveConfig
257
+ saveConfig,
258
+ setAutoModelUpgrade
240
259
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kikkimo/claude-launcher",
3
- "version": "2.3.0",
3
+ "version": "2.5.0",
4
4
  "description": "Interactive launcher for Claude Code with beautiful Claude-style interface",
5
5
  "main": "claude-launcher",
6
6
  "bin": {
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "scripts": {
10
10
  "start": "node claude-launcher",
11
- "test": "echo \"No tests specified\" && exit 0",
11
+ "test": "node test/providers.test.js && node test/menu-hints.test.js",
12
12
  "prepublishOnly": "echo \"Publishing claude-launcher...\"",
13
13
  "postinstall": "echo \"Claude Launcher installed successfully! Run 'claude-launcher' to start.\"",
14
14
  "publish:public": "npm publish --access public"