free-coding-models 0.3.62 → 0.3.63

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
@@ -1,10 +1,20 @@
1
- ## [0.3.58] - 2026-05-04
1
+ ## [0.3.63] - 2026-05-05
2
+
3
+ ### Fixed
4
+
5
+ - **Router daemon auth error false positives** — Fixed `health.every([])` returning `true` for empty arrays, which caused all models to be incorrectly marked as AUTH_ERROR when the daemon had no health data yet. Added explicit `health.length > 0` guards before `.every()` checks.
6
+ - **Daemon API key priority** — `getApiKeyForProvider` no longer falls back to shell env vars when a config key exists. The daemon now exclusively uses keys from `~/.free-coding-models.json`, preventing test/fake keys in shell env from overriding real configured keys.
2
7
 
3
8
  ### Changed
4
9
 
5
- - **E key filter ("Working only") now also hides `noauth` and `auth_error` models** Previously E only filtered by missing API key. Now it also filters out models whose provider returned a 401/403 auth rejection, while still keeping `timeout` and `429` (rate-limited) models visible.
6
- - **Google AI Studio renamed to Google AI** — Shortened to avoid column overflow in the provider column.
10
+ - **Shift+R now launches OpenCode** Pressing Shift+R opens the Router Dashboard AND launches OpenCode with the currently selected model from the main table. If dashboard is already open, just resets scroll. Previously Shift+R only opened the dashboard without launching anything.
11
+ - **Favorites sync to router on launch** — When a model is launched from the TUI, it and the user's full favorites chain are synced to the daemon as the active set via `/sets/fast-coding`.
12
+ - **Router Dashboard install flow** — The "Install Router Endpoint to CLI Tool" button now opens the Install Endpoints overlay directly with `fcm_router` pre-selected, skipping the provider selection phase.
7
13
 
8
- ### Fixed
14
+ ### Added
15
+
16
+ - **README_ROUTER.md** — New comprehensive documentation for the FCM Router daemon, covering setup, endpoints, routing behavior, and tool configuration.
17
+
18
+ ### Changed (general)
9
19
 
10
- - **NVIDIA NIM column label** — The provider column in the TUI table now shows `NVIDIA NIM` instead of `NIM` to match the official provider branding.
20
+ - **CTX column gradient improved** — Context window colorization now goes from red (32k) orange (64k) yellow (128k) green (256k) → cyan/teal fluo (400k) → bold cyan+underline (1M+) so the biggest context windows stand out visually.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-coding-models",
3
- "version": "0.3.62",
3
+ "version": "0.3.63",
4
4
  "description": "Find the fastest coding LLM models in seconds — ping free models from multiple providers, pick the best one for OpenCode, Cursor, or any AI coding assistant.",
5
5
  "keywords": [
6
6
  "nvidia",
package/src/app.js CHANGED
@@ -111,7 +111,7 @@ import { resolveCloudflareUrl, buildPingRequest, ping, extractQuotaPercent, getP
111
111
  import { runFiableMode, filterByTierOrExit, fetchOpenRouterFreeModels } from '../src/analysis.js'
112
112
  import { PROVIDER_METADATA, ENV_VAR_NAMES, isWindows, isMac } from '../src/provider-metadata.js'
113
113
  import { parseTelemetryEnv, isTelemetryDebugEnabled, telemetryDebug, ensureTelemetryConfig, getTelemetryDistinctId, getTelemetrySystem, getTelemetryTerminal, isTelemetryEnabled, sendUsageTelemetry } from '../src/telemetry.js'
114
- import { ensureFavoritesConfig, toFavoriteKey, syncFavoriteFlags, toggleFavoriteModel, reorderFavorite } from '../src/favorites.js'
114
+ import { ensureFavoritesConfig, toFavoriteKey, syncFavoriteFlags, toggleFavoriteModel, reorderFavorite, pruneOrphanedFavorites } from '../src/favorites.js'
115
115
  import { checkForUpdateDetailed, checkForUpdate, runUpdate, promptUpdateNotification, fetchLastReleaseDate } from './updater.js'
116
116
  import { promptApiKey } from '../src/setup.js'
117
117
  import { syncShellEnv, ensureShellRcSource, promptShellEnvMigration, removeShellEnv } from '../src/shell-env.js'
@@ -372,6 +372,9 @@ export async function runApp(cliArgs, config) {
372
372
  hidden: false, // 📖 Simple flag to hide/show models
373
373
  }))
374
374
  syncFavoriteFlags(results, config)
375
+ // 📖 Garbage-collect favorites that reference models no longer in sources.js,
376
+ // 📖 so the router dashboard only shows real, launchable models.
377
+ pruneOrphanedFavorites(results, config)
375
378
 
376
379
  // 📖 Load usage data from token-stats.json and attach usagePercent to each result row.
377
380
  // 📖 usagePercent is the quota percent remaining (0–100). undefined = no data available.
@@ -579,7 +582,8 @@ export async function runApp(cliArgs, config) {
579
582
  const scheduleNextPing = () => {
580
583
  clearTimeout(state.pingIntervalObj)
581
584
  const elapsed = Date.now() - state.lastPingTime
582
- const delay = Math.max(0, state.pingInterval - elapsed)
585
+ const interval = state.routerDashboardOpen ? 1000 : state.pingInterval
586
+ const delay = Math.max(0, interval - elapsed)
583
587
  state.pingIntervalObj = setTimeout(runPingCycle, delay)
584
588
  }
585
589
 
@@ -1224,6 +1228,13 @@ if (unconfiguredHide) {
1224
1228
  }
1225
1229
 
1226
1230
  state.results.forEach(r => {
1231
+ // 📖 When router dashboard is open, ONLY ping favorites every second
1232
+ // 📖 to prevent massive rate limiting across the entire 90+ model catalog.
1233
+ if (state.routerDashboardOpen) {
1234
+ const favKey = `${r.providerKey}/${r.modelId}`
1235
+ if (!state.config.favorites.includes(favKey)) return
1236
+ }
1237
+
1227
1238
  pingModel(r).catch(() => {
1228
1239
  // Individual ping failures don't crash the loop
1229
1240
  })
@@ -136,10 +136,12 @@ function getManagedProviderId(providerKey) {
136
136
  }
137
137
 
138
138
  function getProviderLabel(providerKey) {
139
+ if (providerKey === 'fcm_router') return 'Smart Router Daemon'
139
140
  return PROVIDER_METADATA[providerKey]?.label || sources[providerKey]?.name || providerKey
140
141
  }
141
142
 
142
143
  function getManagedProviderLabel(providerKey) {
144
+ if (providerKey === 'fcm_router') return 'FCM Smart Router'
143
145
  return `FCM ${getProviderLabel(providerKey)}`
144
146
  }
145
147
 
@@ -157,6 +159,10 @@ function getDefaultMaxTokens(contextWindow) {
157
159
  }
158
160
 
159
161
  function resolveProviderBaseUrl(providerKey) {
162
+ if (providerKey === 'fcm_router') {
163
+ return `http://localhost:${process.env.FCM_ROUTER_PORT || '19280'}/v1`
164
+ }
165
+
160
166
  const providerUrl = sources[providerKey]?.url
161
167
  if (!providerUrl) return null
162
168
 
@@ -173,6 +179,9 @@ function resolveProviderBaseUrl(providerKey) {
173
179
  }
174
180
 
175
181
  function resolveGooseBaseUrl(providerKey) {
182
+ if (providerKey === 'fcm_router') {
183
+ return `http://localhost:${process.env.FCM_ROUTER_PORT || '19280'}/v1`
184
+ }
176
185
  const providerUrl = sources[providerKey]?.url
177
186
  if (!providerUrl) return null
178
187
  if (providerKey === 'cloudflare') {
@@ -184,6 +193,7 @@ function resolveGooseBaseUrl(providerKey) {
184
193
  }
185
194
 
186
195
  function getDirectInstallSupport(providerKey) {
196
+ if (providerKey === 'fcm_router') return { supported: true, reason: null }
187
197
  if (!sources[providerKey]) {
188
198
  return { supported: false, reason: 'Unknown provider' }
189
199
  }
@@ -220,6 +230,12 @@ function buildCatalogModel(modelId, label, tier, sweScore, ctx) {
220
230
  }
221
231
 
222
232
  export function getProviderCatalogModels(providerKey) {
233
+ if (providerKey === 'fcm_router') {
234
+ return [
235
+ buildCatalogModel('fcm', 'FCM Smart Router', 'S+', 100, '200k')
236
+ ]
237
+ }
238
+
223
239
  const seen = new Set()
224
240
  return MODELS
225
241
  .filter((entry) => entry[5] === providerKey)
@@ -252,6 +268,7 @@ export function getInstallTargetModes() {
252
268
  }
253
269
 
254
270
  function requireConfiguredProviderKey(config, providerKey) {
271
+ if (providerKey === 'fcm_router') return 'fcm-local'
255
272
  const apiKey = getApiKey(config, providerKey)
256
273
  if (!apiKey) {
257
274
  throw new Error(`No configured API key found for ${getProviderLabel(providerKey)}`)
package/src/favorites.js CHANGED
@@ -21,9 +21,10 @@
21
21
  * → toFavoriteKey(providerKey, modelId) — Build the canonical "providerKey/modelId" string
22
22
  * → syncFavoriteFlags(results, config) — Attach isFavorite/favoriteRank to result rows
23
23
  * → toggleFavoriteModel(config, providerKey, modelId) — Add/remove favorite and persist
24
+ * → pruneOrphanedFavorites(results, config) — Remove favorites referencing models no longer in sources
24
25
  *
25
26
  * @exports
26
- * ensureFavoritesConfig, toFavoriteKey, syncFavoriteFlags, toggleFavoriteModel
27
+ * ensureFavoritesConfig, toFavoriteKey, syncFavoriteFlags, toggleFavoriteModel, pruneOrphanedFavorites
27
28
  *
28
29
  * @see src/config.js — load/save helpers keep favorite persistence atomic and merge-safe
29
30
  * @see bin/free-coding-models.js — calls syncFavoriteFlags on startup and toggleFavoriteModel on F key
@@ -125,3 +126,23 @@ export function reorderFavorite(config, providerKey, modelId, direction) {
125
126
  if (saveResult.success) replaceConfigContents(config, latestConfig)
126
127
  return true
127
128
  }
129
+
130
+ /**
131
+ * 📖 Remove favorites that reference models no longer present in the active sources.
132
+ * 📖 Called once at startup so the router dashboard does not show stale/removed models.
133
+ * 📖 Persists immediately if any orphaned entries are found.
134
+ * @param {Array<Record<string, unknown>>} results — the full result rows from sources
135
+ * @param {Record<string, unknown>} config
136
+ * @returns {number} count of removed orphaned entries
137
+ */
138
+ export function pruneOrphanedFavorites(results, config) {
139
+ ensureFavoritesConfig(config)
140
+ const validKeys = new Set(results.map(r => toFavoriteKey(r.providerKey, r.modelId)))
141
+ const before = config.favorites.length
142
+ config.favorites = config.favorites.filter(key => validKeys.has(key))
143
+ const removed = before - config.favorites.length
144
+ if (removed > 0) {
145
+ saveConfig(config, { replaceFavorites: true })
146
+ }
147
+ return removed
148
+ }
@@ -269,6 +269,7 @@ export function createKeyHandler(ctx) {
269
269
  installProviderEndpoints,
270
270
  syncFavoriteFlags,
271
271
  toggleFavoriteModel,
272
+ reorderFavorite,
272
273
  sortResultsWithPinnedFavorites,
273
274
  adjustScrollOffset,
274
275
  applyTierFilter,
@@ -1439,7 +1440,8 @@ export function createKeyHandler(ctx) {
1439
1440
  if (state.routerDashboardOpen) {
1440
1441
  if (key.ctrl && key.name === 'c') { exit(0); return }
1441
1442
  const favorites = Array.isArray(state.config?.favorites) ? state.config.favorites : []
1442
- const maxCursor = Math.max(0, favorites.length - 1)
1443
+ // 📖 maxCursor accounts for the favorites list + 2 buttons (Start/Stop Daemon and Install Endpoint)
1444
+ const maxCursor = Math.max(0, favorites.length + 1)
1443
1445
  const pageStep = Math.max(1, (state.terminalRows || 1) - 4)
1444
1446
 
1445
1447
  if (key.name === 'escape') {
@@ -1447,10 +1449,10 @@ export function createKeyHandler(ctx) {
1447
1449
  return
1448
1450
  }
1449
1451
 
1450
- // 📖 Ctrl+↑: move the selected favorite UP in fallback priority
1451
- if (key.ctrl && key.name === 'up') {
1452
- if (favorites.length > 0) {
1453
- const cursorIdx = state.routerDashboardCursorIndex ?? 0
1452
+ // 📖 Shift+↑: move the selected favorite UP in fallback priority
1453
+ if (key.shift && key.name === 'up') {
1454
+ const cursorIdx = state.routerDashboardCursorIndex ?? 0
1455
+ if (favorites.length > 0 && cursorIdx < favorites.length) {
1454
1456
  const favKey = favorites[cursorIdx]
1455
1457
  if (favKey) {
1456
1458
  const slashIdx = favKey.indexOf('/')
@@ -1466,10 +1468,10 @@ export function createKeyHandler(ctx) {
1466
1468
  return
1467
1469
  }
1468
1470
 
1469
- // 📖 Ctrl+↓: move the selected favorite DOWN in fallback priority
1470
- if (key.ctrl && key.name === 'down') {
1471
- if (favorites.length > 0) {
1472
- const cursorIdx = state.routerDashboardCursorIndex ?? 0
1471
+ // 📖 Shift+↓: move the selected favorite DOWN in fallback priority
1472
+ if (key.shift && key.name === 'down') {
1473
+ const cursorIdx = state.routerDashboardCursorIndex ?? 0
1474
+ if (favorites.length > 0 && cursorIdx < favorites.length) {
1473
1475
  const favKey = favorites[cursorIdx]
1474
1476
  if (favKey) {
1475
1477
  const slashIdx = favKey.indexOf('/')
@@ -1477,7 +1479,7 @@ export function createKeyHandler(ctx) {
1477
1479
  const modelId = slashIdx >= 0 ? favKey.slice(slashIdx + 1) : favKey
1478
1480
  const moved = reorderFavorite(state.config, providerKey, modelId, 'down')
1479
1481
  if (moved) {
1480
- state.routerDashboardCursorIndex = Math.min(maxCursor, cursorIdx + 1)
1482
+ state.routerDashboardCursorIndex = Math.min(favorites.length - 1, cursorIdx + 1)
1481
1483
  syncFavoriteFlags(state.results, state.config)
1482
1484
  }
1483
1485
  }
@@ -1485,17 +1487,56 @@ export function createKeyHandler(ctx) {
1485
1487
  return
1486
1488
  }
1487
1489
 
1490
+ // 📖 S: Toggle daemon start/stop
1491
+ if (key.name === 's') {
1492
+ const isRunning = state.routerDashboardStatus === 'ready' || state.routerDashboardStatus === 'partial'
1493
+ const binPath = join(dirname(fileURLToPath(import.meta.url)), '..', 'bin', 'free-coding-models.js')
1494
+ const args = isRunning ? ['--daemon-stop'] : ['--daemon-bg']
1495
+
1496
+ state.routerDashboardStatus = 'loading'
1497
+ const child = spawn('node', [binPath, ...args], {
1498
+ detached: true,
1499
+ stdio: 'ignore',
1500
+ })
1501
+ child.unref()
1502
+ return
1503
+ }
1504
+
1505
+ // 📖 Enter/Return: Toggle daemon or open Install Endpoints if cursor is on a button
1506
+ if (key.name === 'return' || key.name === 'enter') {
1507
+ const btnCursor = favorites.length
1508
+ const installBtnCursor = favorites.length + 1
1509
+
1510
+ if ((state.routerDashboardCursorIndex ?? 0) === btnCursor) {
1511
+ const isRunning = state.routerDashboardStatus === 'ready' || state.routerDashboardStatus === 'partial'
1512
+ const binPath = join(dirname(fileURLToPath(import.meta.url)), '..', 'bin', 'free-coding-models.js')
1513
+ const args = isRunning ? ['--daemon-stop'] : ['--daemon-bg']
1514
+
1515
+ state.routerDashboardStatus = 'loading'
1516
+ const child = spawn('node', [binPath, ...args], {
1517
+ detached: true,
1518
+ stdio: 'ignore',
1519
+ })
1520
+ child.unref()
1521
+ } else if ((state.routerDashboardCursorIndex ?? 0) === installBtnCursor) {
1522
+ state.routerDashboardOpen = false
1523
+ state.installEndpointsOpen = true
1524
+ state.installEndpointsPhase = 'tools' // skip the provider selection phase
1525
+ state.installEndpointsCursor = 0
1526
+ state.installEndpointsProviderKey = 'fcm_router' // special provider key handled by endpoint-installer
1527
+ state.installEndpointsScrollOffset = 0
1528
+ state.installEndpointsErrorMsg = null
1529
+ }
1530
+ return
1531
+ }
1532
+
1488
1533
  // 📖 ↑/↓: navigate the favorites list cursor
1489
1534
  if (key.name === 'up' || key.name === 'k') {
1490
- if (favorites.length > 0) {
1491
- state.routerDashboardCursorIndex = Math.max(0, (state.routerDashboardCursorIndex ?? 0) - 1)
1492
- }
1535
+ state.routerDashboardCursorIndex = Math.max(0, (state.routerDashboardCursorIndex ?? 0) - 1)
1493
1536
  return
1494
1537
  }
1495
1538
  if (key.name === 'down' || key.name === 'j') {
1496
- if (favorites.length > 0) {
1497
- state.routerDashboardCursorIndex = Math.min(maxCursor, (state.routerDashboardCursorIndex ?? 0) + 1)
1498
- }
1539
+ state.routerDashboardCursorIndex = Math.min(maxCursor, (state.routerDashboardCursorIndex ?? 0) + 1)
1499
1540
  return
1500
1541
  }
1501
1542
  if (key.name === 'pageup') {
@@ -1527,7 +1568,7 @@ export function createKeyHandler(ctx) {
1527
1568
  if (key.ctrl && key.name === 'c') { exit(0); return }
1528
1569
 
1529
1570
  const providerChoices = getConfiguredInstallableProviders(state.config)
1530
- const toolChoices = getInstallTargetModes()
1571
+ const toolChoices = getInstallTargetModes().filter(t => !(state.installEndpointsProviderKey === 'fcm_router' && t === 'fcm_router'))
1531
1572
  const modelChoices = state.installEndpointsProviderKey
1532
1573
  ? getProviderCatalogModels(state.installEndpointsProviderKey)
1533
1574
  : []
@@ -1569,10 +1610,19 @@ export function createKeyHandler(ctx) {
1569
1610
  if (key.name === 'escape') {
1570
1611
  state.installEndpointsErrorMsg = null
1571
1612
  if (state.installEndpointsPhase === 'providers' || state.installEndpointsPhase === 'result') {
1613
+ const wasFcmRouter = state.installEndpointsProviderKey === 'fcm_router'
1572
1614
  resetInstallEndpointsOverlay()
1615
+ if (wasFcmRouter) {
1616
+ state.routerDashboardOpen = true
1617
+ }
1573
1618
  return
1574
1619
  }
1575
1620
  if (state.installEndpointsPhase === 'tools') {
1621
+ if (state.installEndpointsProviderKey === 'fcm_router') {
1622
+ resetInstallEndpointsOverlay()
1623
+ state.routerDashboardOpen = true
1624
+ return
1625
+ }
1576
1626
  state.installEndpointsPhase = 'providers'
1577
1627
  state.installEndpointsCursor = 0
1578
1628
  state.installEndpointsScrollOffset = 0
@@ -1617,10 +1667,25 @@ export function createKeyHandler(ctx) {
1617
1667
  if (!selectedToolMode) return
1618
1668
  state.installEndpointsToolMode = selectedToolMode
1619
1669
  state.installEndpointsConnectionMode = 'direct'
1620
- state.installEndpointsPhase = 'scope'
1621
- state.installEndpointsCursor = 0
1622
- state.installEndpointsScrollOffset = 0
1623
- state.installEndpointsErrorMsg = null
1670
+
1671
+ if (state.installEndpointsProviderKey === 'fcm_router') {
1672
+ state.installEndpointsScope = 'all'
1673
+ try {
1674
+ await runInstallEndpointsFlow()
1675
+ } catch (error) {
1676
+ state.installEndpointsResult = {
1677
+ type: 'error',
1678
+ title: 'Install failed',
1679
+ lines: [error instanceof Error ? error.message : String(error)],
1680
+ }
1681
+ state.installEndpointsPhase = 'result'
1682
+ }
1683
+ } else {
1684
+ state.installEndpointsPhase = 'scope'
1685
+ state.installEndpointsCursor = 0
1686
+ state.installEndpointsScrollOffset = 0
1687
+ state.installEndpointsErrorMsg = null
1688
+ }
1624
1689
  }
1625
1690
  return
1626
1691
  }
@@ -1695,7 +1760,11 @@ export function createKeyHandler(ctx) {
1695
1760
 
1696
1761
  if (state.installEndpointsPhase === 'result') {
1697
1762
  if (key.name === 'return' || key.name === 'y') {
1763
+ const wasFcmRouter = state.installEndpointsProviderKey === 'fcm_router'
1698
1764
  resetInstallEndpointsOverlay()
1765
+ if (wasFcmRouter) {
1766
+ state.routerDashboardOpen = true
1767
+ }
1699
1768
  }
1700
1769
  return
1701
1770
  }
@@ -2593,10 +2662,26 @@ export function createKeyHandler(ctx) {
2593
2662
 
2594
2663
  // 📖 Profile system removed - API keys now persist permanently across all sessions
2595
2664
 
2596
- // 📖 Shift+R intentionally stays unadvertised in the main UI, but remains
2597
- // 📖 available as a tester entry point for the Router Dashboard.
2665
+ // 📖 Shift+R: Open Router Dashboard AND launch OpenCode with the selected model.
2666
+ // 📖 If the dashboard is already open, just bring it to front.
2598
2667
  if (key.name === 'r' && key.shift && !key.ctrl && !key.meta) {
2668
+ if (state.routerDashboardOpen) {
2669
+ state.routerDashboardScrollOffset = 0
2670
+ return
2671
+ }
2599
2672
  openRouterDashboardOverlay(state)
2673
+ // 📖 If a model is selected in the main table, launch OpenCode with it after opening dashboard
2674
+ const selected = state.visibleSorted?.[state.cursor]
2675
+ if (selected && selected.providerKey && selected.modelId) {
2676
+ const launchModel = {
2677
+ modelId: selected.modelId,
2678
+ label: selected.label,
2679
+ tier: selected.tier,
2680
+ providerKey: selected.providerKey,
2681
+ }
2682
+ // 📖 Launch asynchronously — don't await, dashboard renders while OpenCode starts
2683
+ void startOpenCode(launchModel, state.config)
2684
+ }
2600
2685
  return
2601
2686
  }
2602
2687
 
package/src/overlays.js CHANGED
@@ -343,7 +343,7 @@ export function createOverlayRenderers(state, deps) {
343
343
  const lines = []
344
344
  const cursorLineByRow = {}
345
345
  const providerChoices = getConfiguredInstallableProviders(state.config)
346
- const toolChoices = getInstallTargetModes()
346
+ const toolChoices = getInstallTargetModes().filter(t => !(state.installEndpointsProviderKey === 'fcm_router' && t === 'fcm_router'))
347
347
  const totalSteps = 4
348
348
  const scopeChoices = [
349
349
  {
@@ -357,9 +357,11 @@ export function createOverlayRenderers(state, deps) {
357
357
  hint: 'Choose a smaller curated subset for a cleaner model picker.',
358
358
  },
359
359
  ]
360
- const selectedProviderLabel = state.installEndpointsProviderKey
361
- ? (sources[state.installEndpointsProviderKey]?.name || state.installEndpointsProviderKey)
362
- : '—'
360
+ const selectedProviderLabel = state.installEndpointsProviderKey === 'fcm_router'
361
+ ? 'Smart Router Daemon'
362
+ : state.installEndpointsProviderKey
363
+ ? (sources[state.installEndpointsProviderKey]?.name || state.installEndpointsProviderKey)
364
+ : '—'
363
365
 
364
366
  // 📖 Resolve tool label from metadata instead of hard-coded switch
365
367
  const selectedToolLabel = state.installEndpointsToolMode
@@ -825,7 +825,7 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
825
825
  const starLink = '⭐ ' + themeColors.link('\x1b]8;;https://github.com/vava-nessa/free-coding-models\x1b\\GitHub\x1b]8;;\x1b\\')
826
826
  lines.push(
827
827
  ' ' + paletteLabel + themeColors.dim(` • `) + starLink + themeColors.dim(` • `) +
828
- chalk.rgb(255, 168, 209).bold('\x1b]8;;https://x.com/vavanessadev\x1b\\Support me by following me on X ! @vavanessadev\x1b]8;;\x1b\\')
828
+ chalk.rgb(255, 168, 209).bold('\x1b]8;;https://x.com/vavanessadev\x1b\\Follow @vavanessadev on X for updates and support\x1b]8;;\x1b\\')
829
829
  )
830
830
 
831
831
  if (versionStatus.isOutdated) {
@@ -637,7 +637,8 @@ class RouterRuntime {
637
637
  reloadConfigFromDisk() {
638
638
  try {
639
639
  const nextConfig = loadConfig()
640
- if (!nextConfig.router) nextConfig.router = this.routerConfig()
640
+ // 📖 Always rebuild the router set from favorites so UI toggles apply dynamically
641
+ ensureRouterConfigForDaemon(nextConfig, true)
641
642
  this.config = nextConfig
642
643
  this.refreshRouteState()
643
644
  this.scheduleProbeLoop()
@@ -649,12 +650,10 @@ class RouterRuntime {
649
650
  }
650
651
 
651
652
  getApiKeyForProvider(providerKey) {
652
- // 📖 Router background startup should work without inherited shell env, so
653
- // 📖 config keys are primary. Env is only a fallback for headless sessions.
654
653
  const configured = this.config?.apiKeys?.[providerKey]
655
654
  if (Array.isArray(configured)) return configured.find(Boolean) || null
656
655
  if (typeof configured === 'string' && configured.trim()) return configured.trim()
657
- return getApiKey({ apiKeys: {}, providers: {} }, providerKey)
656
+ return null
658
657
  }
659
658
 
660
659
  getSet(setName = null) {
@@ -1052,15 +1051,18 @@ class RouterRuntime {
1052
1051
  let errorCode = 'all_models_unavailable'
1053
1052
  let errorType = 'service_unavailable'
1054
1053
  if (health.length > 0) {
1055
- if (health.every((h) => h.state === 'AUTH_ERROR')) {
1054
+ const allAuthError = health.length > 0 && health.every((h) => h.state === 'AUTH_ERROR')
1055
+ const allAuthOrQuota = health.length > 0 && health.every((h) => h.state === 'AUTH_ERROR' || quotaExhausted.includes(h.key))
1056
+ const allStaleOrUnsupported = health.every((h) => h.state === 'STALE' || h.state === 'UNSUPPORTED')
1057
+ if (allAuthError) {
1056
1058
  statusCode = 401
1057
1059
  errorCode = 'invalid_api_key'
1058
1060
  errorType = 'invalid_request_error'
1059
- } else if (health.every((h) => h.state === 'AUTH_ERROR' || quotaExhausted.includes(h.key))) {
1061
+ } else if (allAuthOrQuota) {
1060
1062
  statusCode = 429
1061
1063
  errorCode = 'insufficient_quota'
1062
1064
  errorType = 'insufficient_quota'
1063
- } else if (health.every((h) => h.state === 'STALE' || h.state === 'UNSUPPORTED')) {
1065
+ } else if (allStaleOrUnsupported) {
1064
1066
  statusCode = 400
1065
1067
  errorCode = 'invalid_model'
1066
1068
  errorType = 'invalid_request_error'
@@ -1808,7 +1810,7 @@ export function createRouterRuntimeForTest({ config, port = 0, logger = null, to
1808
1810
  })
1809
1811
  }
1810
1812
 
1811
- function ensureRouterConfigForDaemon(config) {
1813
+ function ensureRouterConfigForDaemon(config, skipSave = false) {
1812
1814
  // 📖 Always rebuild from favorites or defaults — no more manual set management
1813
1815
  const favSet = buildRouterSetFromFavorites(config)
1814
1816
  const activeSet = favSet || buildDefaultRouterSet(config)
@@ -1819,7 +1821,7 @@ function ensureRouterConfigForDaemon(config) {
1819
1821
  activeSet: activeSet.name,
1820
1822
  sets: { [activeSet.name]: activeSet },
1821
1823
  })
1822
- saveConfig(config)
1824
+ if (!skipSave) saveConfig(config)
1823
1825
  return config.router
1824
1826
  }
1825
1827