free-coding-models 0.3.56 → 0.3.58

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,53 +1,10 @@
1
- ## [0.3.56] - 2026-05-04
2
-
3
- ### Added
4
-
5
- - **Major free-model catalog refresh** — Rebuilt the active catalog to **180 models across 16 vetted free or free-limited providers**.
6
- - **GitHub Models provider** — Added GitHub Models with 15 API models and `GITHUB_TOKEN` / `GH_TOKEN` / `GITHUB_MODELS_TOKEN` support.
7
- - **Mistral La Plateforme provider** — Added a general Mistral provider for Experiment-plan usage, separate from the existing Codestral shortcut provider for backward compatibility.
8
- - **OpenRouter live free-model coverage** — Added current free models such as `tencent/hy3-preview:free`, Poolside Laguna, Ling 2.6, Nemotron 3, OpenRouter Free, Owl Alpha, Gemma 4, and LFM free models.
9
- - **Smart Router Daemon** — Added a local OpenAI-compatible daemon with model failover, health tracking, daemon status, dashboard integration, and router set persistence.
10
- - **`--sync-set` router discovery** — Added automatic model discovery and live probing for configured providers so router sets can be rebuilt from working models.
11
- - **Router Dashboard** — Added a local web dashboard for router health, latency heatmaps, failover activity, and quota visibility.
12
- - **Kilo CLI integration** — Added Kilo as a launch target and endpoint install target.
13
- - **Discord commit log webhook** — Added `DISCORD_WEBHOOK_URL` support for compact push notifications.
14
- - **Graphify integration** — Added codebase graph analysis artifacts and tracking support for architecture inspection.
1
+ ## [0.3.58] - 2026-05-04
15
2
 
16
3
  ### Changed
17
4
 
18
- - **Free-provider policy tightened** — Removed paid-only, trial-credit-only, shutdown, extremely tiny-credit, unclear, and tool-specific providers from the active catalog.
19
- - **Providers removed from active core** `iFlow`, `Together AI`, `Perplexity API`, `DeepInfra`, `Replicate`, `Fireworks`, `Hyperbolic`, `Hugging Face`, `SiliconFlow`, `Chutes AI`, and `Rovo` are now documented outside the core free-provider list when useful.
20
- - **Provider catalogs refreshed** — Updated NVIDIA NIM, OpenRouter, Google AI Studio, Gemini CLI, Cloudflare Workers AI, OVHcloud AI Endpoints, ZAI, SambaNova, Scaleway, Qwen DashScope, and OpenCode Zen model IDs.
21
- - **SambaNova catalog corrected** — Reduced the provider to the current public model set and restored `DeepSeek-V3.2` after verifying it is present in the live `/v1/models` endpoint.
22
- - **Google/Gemini IDs corrected** — Replaced stale Gemma-style IDs with current Gemini API model IDs for Google AI Studio and Gemini CLI.
23
- - **ZAI catalog reduced** — Kept only free Flash models in the active provider list.
24
- - **Scaleway EOL cleanup** — Removed EOL entries from the active Scaleway list and kept only useful current text models.
25
- - **Codestral key handling normalized** — `MISTRAL_API_KEY` is now the primary environment variable; `CODESTRAL_API_KEY` remains accepted as an alias.
26
- - **OpenRouter free detection improved** — Live OpenRouter discovery now treats zero-priced public models as free even when their ID does not end in `:free`.
27
- - **Provider filters now derive from the catalog** — Command Palette provider filters are generated from `sources`, so new providers do not need a hardcoded UI update.
28
- - **Router model set expanded** — Default router candidate selection now supports up to 8 models and uses refreshed high-ranking defaults.
29
- - **External tool configs generalized** — OpenCode and Kilo can now auto-configure newly added OpenAI-compatible providers through shared provider metadata.
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.
30
7
 
31
8
  ### Fixed
32
9
 
33
- - **Router upstream hardening** — Fixed unsupported request parameter stripping, retryable failover behavior, content-type canonicalization, and long-stream timeout handling.
34
- - **Router auth/quota semantics** — Router now returns 401/429 when all candidates fail because of auth or quota instead of masking those cases as 503.
35
- - **OpenRouter sync-set filtering** — Router discovery no longer drops `openrouter/free` and `openrouter/owl-alpha` just because their IDs do not end with `:free`.
36
- - **Provider key probes** — Updated probe model priorities for NVIDIA, SambaNova, GitHub Models, and Mistral to avoid stale or slow first-choice checks.
37
- - **OpenCode Desktop launcher** — Fixed launcher setup for newly added OpenAI-compatible providers such as GitHub Models and Mistral.
38
- - **pnpm lock conflict** — Regenerated the lockfile from `package.json` to resolve the merge conflict state.
39
-
40
- ### Docs
41
-
42
- - **README provider table updated** — Updated badges, provider count, model count, free-tier descriptions, environment variables, audit cleanup notes, and caveats.
43
- - **Provider inclusion policy documented** — Added explicit notes explaining why Vercel AI Gateway, Cohere, Ollama Cloud, trial-credit providers, and tiny-credit providers are not treated as core free providers.
44
- - **Other Free AI Resources reorganized** — Moved curated external resources to the bottom of the README and separated permanent free tiers from trial-credit providers.
45
- - **Router PRD and sync-set docs** — Added and updated router design notes and `--sync-set` documentation.
46
- - **Contributors updated** — Added new contributor acknowledgements.
47
-
48
- ### Dependencies
49
-
50
- - **react** bumped to 19.2.5.
51
- - **vite** bumped to 8.0.10.
52
- - **vite-plus** bumped to 0.1.20.
53
- - **pnpm/action-setup** bumped to 6.
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.
package/README.md CHANGED
@@ -2,17 +2,21 @@
2
2
  <img src="https://img.shields.io/npm/v/free-coding-models?color=76b900&label=npm&logo=npm" alt="npm version">
3
3
  <img src="https://img.shields.io/node/v/free-coding-models?color=76b900&logo=node.js" alt="node version">
4
4
  <img src="https://img.shields.io/npm/l/free-coding-models?color=76b900" alt="license">
5
- <img src="https://img.shields.io/badge/models-180-76b900?logo=nvidia" alt="models count">
5
+ <img src="https://img.shields.io/badge/models-170+-76b900?logo=nvidia" alt="models count">
6
6
  <img src="https://img.shields.io/badge/providers-16-blue" alt="providers count">
7
7
  </p>
8
8
 
9
+ <p align="center">
10
+ <img src="logo.webp" alt="free-coding-models logo" width="128">
11
+ </p>
12
+
9
13
  <h1 align="center">free-coding-models</h1>
10
14
 
11
15
 
12
16
 
13
17
  <p align="center">
14
18
  <strong>Find the fastest free coding model in seconds</strong><br>
15
- <sub>Track 180 models across 16 vetted free or free-limited AI providers in real time</sub><br> <sub> Install Free API endpoints to your favorite AI coding tool: <br>📦 OpenCode, 📦 OpenCode Desktop, 📦 OpenCode WebUI, 🦞 OpenClaw, 💘 Crush, 🪿 Goose, 🛠 Aider, ⚡️ Kilo CLI, 🐉 Qwen Code, 🤲 OpenHands, ⚡ Amp, 🔮 Hermes, ▶️ Continue, 🧠 Cline, 🛠️ Xcode, π Pi, 🦘 Rovo or ♊ Gemini in one keystroke</sub>
19
+ <sub>Track ~170 models across ~15 vetted free or free-limited AI providers in real time</sub><br> <sub> Install Free API endpoints to your favorite AI coding tool: <br>📦 OpenCode, 📦 OpenCode Desktop, 📦 OpenCode WebUI, 🦞 OpenClaw, 💘 Crush, 🪿 Goose, 🛠 Aider, ⚡️ Kilo CLI, 🐉 Qwen Code, 🤲 OpenHands, ⚡ Amp, 🔮 Hermes, ▶️ Continue, 🧠 Cline, 🛠️ Xcode, π Pi, 🦘 Rovo or ♊ Gemini in one keystroke</sub>
16
20
  </p>
17
21
 
18
22
 
@@ -58,7 +62,7 @@ create a free account on one of the [providers](#-list-of-free-ai-providers)
58
62
 
59
63
  ## 💡 Why this tool?
60
64
 
61
- There are **180 cataloged free or free-limited coding models** across 16 vetted providers. Which one is fastest right now? Which one is actually stable versus just lucky on the last ping?
65
+ There are **~170 cataloged free or free-limited coding models** across ~15 vetted providers. Which one is fastest right now? Which one is actually stable versus just lucky on the last ping?
62
66
 
63
67
  This CLI pings them all in parallel, shows live latency, and calculates a **live Stability Score (0-100)**. Average latency alone is misleading if a model randomly spikes to 6 seconds; the stability score measures true reliability by combining **p95 latency** (30%), **jitter/variance** (30%), **spike rate** (20%), and **uptime** (20%).
64
68
 
@@ -72,7 +76,7 @@ It then writes the model you pick directly into your coding tool's config — so
72
76
 
73
77
  Create a free account on one provider below to get started:
74
78
 
75
- **180 coding models** across 16 active providers, ranked by practical free-tier usefulness.
79
+ **~170 coding models** across ~15 active providers, ranked by practical free-tier usefulness.
76
80
 
77
81
  | # | Provider | Models | Tier range | Free tier | Env var |
78
82
  |---|----------|--------|-----------|-----------|--------|
@@ -163,10 +167,6 @@ free-coding-models --web
163
167
  free-coding-models --daemon-bg
164
168
  free-coding-models --daemon-status
165
169
 
166
- # "I want to inspect the router without leaving the TUI"
167
- free-coding-models
168
- # then press Shift+R
169
-
170
170
  # "Start with an elite-focused preset, then adjust filters live"
171
171
  free-coding-models --premium
172
172
 
@@ -198,27 +198,6 @@ free-coding-models --sync-set
198
198
  free-coding-models --sync-set my-coding-set
199
199
  ```
200
200
 
201
- Inside the TUI, press **`Shift+R`** to open the Router Dashboard. It polls `/health` and `/stats`, listens to `/stream/events`, and shows daemon state, active set, probe mode, circuit breaker health, token totals, and the live routed request log.
202
-
203
- Dashboard keys:
204
-
205
- | Key | Action |
206
- |-----|--------|
207
- | `S` | Switch to the next router set |
208
- | `I` | Cycle probe mode (`eco → balanced → aggressive`) |
209
- | `C` | Clear the local dashboard request log |
210
- | `R` | Reserved for Phase 7 service-manager restart |
211
- | `P` | Reserved until probe pause/resume backend support exists |
212
- | `Esc` | Return to the main model table |
213
-
214
- Press **`Shift+S`** to open the Set Manager — create, rename, duplicate, delete model sets, and reorder models within each set with `Shift+↑` / `Shift+↓`.
215
-
216
- Press **`Shift+T`** to open the Token Usage screen — shows today/all-time token totals and a 7-day history chart with top models per day.
217
-
218
- When you first start the TUI with no router configured, an onboarding prompt appears asking if you want to enable the Smart Router. Existing users who haven't yet opted in see a dismissable upgrade banner at the top of the table.
219
-
220
- **`Shift+A`** opens the position picker — navigate where a model lands in the priority order within a set.
221
-
222
201
  Configure tools with:
223
202
 
224
203
  | Field | Value |
@@ -346,7 +325,6 @@ When a tool mode is active (via `Z`), models incompatible with that tool are hig
346
325
  | `G` | Cycle global theme (`Auto → Dark → Light`) |
347
326
  | `Ctrl+P` | Open ⚡️ command palette (search + run actions) |
348
327
  | `R/S/C/M/O/L/A/H/V/B/U` | Sort columns |
349
- | `Shift+R` | Router Dashboard (daemon health, circuits, tokens, request log) |
350
328
  | `Shift+U` | Update to latest version (when update available) |
351
329
  | `P` | Settings (API keys, providers, updates, theme) |
352
330
  | `Q` | Smart Recommend overlay |
@@ -379,7 +357,7 @@ When a tool mode is active (via `Z`), models incompatible with that tool are hig
379
357
 
380
358
  ## ✨ Features
381
359
 
382
- - **Parallel pings** — all 174 API/Zen-callable models tested simultaneously via native `fetch` (180 total cataloged models including CLI-only Gemini rows)
360
+ - **Parallel pings** — all ~165 API/Zen-callable models tested simultaneously via native `fetch` (~170 total cataloged models including CLI-only Gemini rows)
383
361
  - **Adaptive monitoring** — 2s burst for 60s → 10s normal → 30s idle
384
362
  - **Stability score** — composite 0–100 (p95 latency, jitter, spike rate, uptime)
385
363
  - **Smart ranking** — top 3 highlighted 🥇🥈🥉
@@ -418,7 +396,7 @@ We welcome contributions — issues, PRs, new provider integrations.
418
396
 
419
397
  ## ⚖️ Model Licensing & Commercial Use
420
398
 
421
- **Short answer:** The 180 cataloged models are API/CLI-served models where generated-output ownership is generally granted by the provider/model terms. Always verify current provider terms for high-stakes commercial use.
399
+ **Short answer:** The ~170 cataloged models are API/CLI-served models where generated-output ownership is generally granted by the provider/model terms. Always verify current provider terms for high-stakes commercial use.
422
400
 
423
401
  ### Output Ownership
424
402
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-coding-models",
3
- "version": "0.3.56",
3
+ "version": "0.3.58",
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/sources.js CHANGED
@@ -358,7 +358,7 @@ export const opencodeZen = [
358
358
  // 📖 See README for full tier-by-tier comparison
359
359
  export const sources = {
360
360
  nvidia: {
361
- name: 'NIM',
361
+ name: 'NVIDIA NIM',
362
362
  url: 'https://integrate.api.nvidia.com/v1/chat/completions',
363
363
  models: nvidiaNim,
364
364
  },
@@ -373,7 +373,7 @@ export const sources = {
373
373
  models: cerebras,
374
374
  },
375
375
  googleai: {
376
- name: 'Google AI Studio',
376
+ name: 'Google AI',
377
377
  url: 'https://generativelanguage.googleapis.com/v1beta/openai/chat/completions',
378
378
  models: googleai,
379
379
  },
@@ -383,7 +383,7 @@ export const sources = {
383
383
  models: githubModels,
384
384
  },
385
385
  mistral: {
386
- name: 'Mistral La Plateforme',
386
+ name: 'Mistral LP',
387
387
  url: 'https://api.mistral.ai/v1/chat/completions',
388
388
  models: mistral,
389
389
  },
package/src/app.js CHANGED
@@ -110,7 +110,7 @@ import { TIER_COLOR } from '../src/tier-colors.js'
110
110
  import { resolveCloudflareUrl, buildPingRequest, ping, extractQuotaPercent, getProviderQuotaPercentCached, usagePlaceholderForProvider } from '../src/ping.js'
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
- import { parseTelemetryEnv, isTelemetryDebugEnabled, telemetryDebug, ensureTelemetryConfig, getTelemetryDistinctId, getTelemetrySystem, getTelemetryTerminal, isTelemetryEnabled, sendUsageTelemetry, sendBugReport } from '../src/telemetry.js'
113
+ import { parseTelemetryEnv, isTelemetryDebugEnabled, telemetryDebug, ensureTelemetryConfig, getTelemetryDistinctId, getTelemetrySystem, getTelemetryTerminal, isTelemetryEnabled, sendUsageTelemetry } from '../src/telemetry.js'
114
114
  import { ensureFavoritesConfig, toFavoriteKey, syncFavoriteFlags, toggleFavoriteModel, reorderFavorite } from '../src/favorites.js'
115
115
  import { checkForUpdateDetailed, checkForUpdate, runUpdate, promptUpdateNotification, fetchLastReleaseDate } from './updater.js'
116
116
  import { promptApiKey } from '../src/setup.js'
@@ -229,21 +229,21 @@ export async function runApp(cliArgs, config) {
229
229
 
230
230
  // 📖 Shell env migration popup for existing users who haven't been asked yet
231
231
  // 📖 Only show when user has keys but shellEnvEnabled is still undefined (never prompted)
232
- if (hasAnyKey && config.settings.shellEnvEnabled === undefined) {
232
+ // 📖 shellEnvPromptSeen flag ensures it only shows ONCE even after adding new keys
233
+ if (hasAnyKey && config.settings.shellEnvEnabled === undefined && config.settings.shellEnvPromptSeen !== true) {
233
234
  const choice = await promptShellEnvMigration(config)
235
+ if (!config.settings) config.settings = {}
236
+ config.settings.shellEnvPromptSeen = true
234
237
  if (choice === 'enable') {
235
- if (!config.settings) config.settings = {}
236
238
  config.settings.shellEnvEnabled = true
237
239
  saveConfig(config)
238
240
  syncShellEnv(config)
239
241
  ensureShellRcSource()
240
242
  } else if (choice === 'never') {
241
- if (!config.settings) config.settings = {}
242
243
  config.settings.shellEnvEnabled = false
243
244
  saveConfig(config)
244
245
  }
245
246
  if (choice === 'skip') {
246
- if (!config.settings) config.settings = {}
247
247
  config.settings.shellEnvEnabled = false
248
248
  saveConfig(config)
249
249
  }
@@ -435,7 +435,6 @@ export async function runApp(cliArgs, config) {
435
435
  healthFilterMode: 0, // 📖 Index into HEALTH_CYCLE (0=All, then health states)
436
436
  hideUnconfiguredModels: config.settings?.hideUnconfiguredModels === true, // 📖 Hide providers with no configured API key when true.
437
437
  favoritesPinnedAndSticky: config.settings?.favoritesPinnedAndSticky === true, // 📖 false by default: favorites follow normal sort/filter rules until Y enables pinned+sticky mode.
438
- footerHidden: config.settings?.footerHidden === true, // 📖 true = footer is collapsed to a single toggle hint
439
438
  scrollOffset: 0, // 📖 First visible model index in viewport
440
439
  terminalRows: process.stdout.rows || 24, // 📖 Current terminal height
441
440
  terminalCols: process.stdout.columns || 80, // 📖 Current terminal width
@@ -508,11 +507,7 @@ export async function runApp(cliArgs, config) {
508
507
  recommendAnalysisTimer: null, // 📖 setInterval handle for the 10s analysis phase
509
508
  recommendPingTimer: null, // 📖 setInterval handle for 2 pings/sec during analysis
510
509
  recommendedKeys: new Set(), // 📖 Set of "providerKey/modelId" for recommended models (shown in main table)
511
- // 📖 Feedback state (J/I keys open it)
512
- feedbackOpen: false, // 📖 Whether the feedback overlay is active
513
- bugReportBuffer: '', // 📖 Typed characters for the feedback message
514
- bugReportStatus: 'idle', // 📖 'idle'|'sending'|'success'|'error' — webhook send status
515
- bugReportError: null, // 📖 Last webhook error message
510
+
516
511
  // 📖 OpenCode sync status (S key in settings)
517
512
  settingsSyncStatus: null, // 📖 { type: 'success'|'error', msg: string } — shown in settings footer
518
513
  // 📖 Changelog overlay state (N key opens it)
@@ -548,9 +543,6 @@ export async function runApp(cliArgs, config) {
548
543
  routerDashboardNotice: null,
549
544
  routerDashboardNoticeTimer: null,
550
545
  routerOnboardingScrollOffset: 0,
551
- // 📖 Router upgrade banner (shown once to existing users who haven't seen router)
552
- routerUpgradeBannerShownAt: 0, // 📖 Timestamp when banner was shown (0 = not shown)
553
- routerUpgradeBannerDismissedAt: 0, // 📖 Timestamp when banner was dismissed (0 = not dismissed)
554
546
  routerDashboardEverOpened: false, // 📖 Set to true the first time dashboard opens (used for upgrade-path telemetry)
555
547
  // 📖 Custom text filter (Ctrl+P palette → type text → Enter). Ephemeral — not saved to config.
556
548
  customTextFilter: null, // 📖 Active free-text filter string (null = off). Matches model name, ctx, provider key/name.
@@ -787,11 +779,14 @@ export async function runApp(cliArgs, config) {
787
779
  // 📖 they authenticate via their own CLI login flow, so "configured only" should never hide them.
788
780
  const providerMeta = PROVIDER_METADATA[r.providerKey]
789
781
  const noKeyNeeded = providerMeta?.cliOnly || providerMeta?.zenOnly
790
- const unconfiguredHide = state.hideUnconfiguredModels && !noKeyNeeded && !getApiKey(state.config, r.providerKey)
791
- if (unconfiguredHide) {
792
- r.hidden = true
793
- return
794
- }
782
+ // 📖 E toggles "Show only configured & working models":
783
+ // 📖 hide models where provider has no key, or where the health status is noauth/auth_error (but keep timeout and 429)
784
+ const badHealth = r.status === 'noauth' || r.status === 'auth_error'
785
+ const unconfiguredHide = state.hideUnconfiguredModels && !noKeyNeeded && (!getApiKey(state.config, r.providerKey) || badHealth)
786
+ if (unconfiguredHide) {
787
+ r.hidden = true
788
+ return
789
+ }
795
790
  // 📖 Apply tier, origin, verdict, and health filters — model is hidden if it fails any
796
791
  const allowedTiers = (activeTier && TIER_LETTER_MAP[activeTier]) ? TIER_LETTER_MAP[activeTier] : [activeTier]
797
792
  const tierHide = activeTier !== null && !allowedTiers.includes(r.tier)
@@ -920,7 +915,6 @@ export async function runApp(cliArgs, config) {
920
915
  sendUsageTelemetry,
921
916
  startRecommendAnalysis: overlays.startRecommendAnalysis,
922
917
  stopRecommendAnalysis: overlays.stopRecommendAnalysis,
923
- sendBugReport,
924
918
  stopUi,
925
919
  ping,
926
920
  TASK_TYPES,
@@ -1024,7 +1018,7 @@ export async function runApp(cliArgs, config) {
1024
1018
  process.stdout.write(ALT_LEAVE);
1025
1019
  console.error(chalk.red('\n[TUI Error] An error occurred while handling a keypress.'));
1026
1020
  console.error(err);
1027
- console.error(chalk.yellow('\nPlease file an issue at https://github.com/vava-nessa/free-coding-models/issues or use the feedback form (I key) to report this to the author.'));
1021
+ console.error(chalk.yellow('\nPlease file an issue at https://github.com/vava-nessa/free-coding-models/issues or join the Discord to report this to the author.'));
1028
1022
  process.exit(1);
1029
1023
  }
1030
1024
  })
@@ -1048,12 +1042,14 @@ export async function runApp(cliArgs, config) {
1048
1042
  refreshAutoPingMode()
1049
1043
  state.frame++
1050
1044
  // 📖 Cache visible+sorted models each frame so Enter handler always matches the display
1051
- if (!state.settingsOpen && !state.installEndpointsOpen && !state.toolInstallPromptOpen && !state.incompatibleFallbackOpen && !state.recommendOpen && !state.feedbackOpen && !state.changelogOpen && !state.installedModelsOpen && !state.routerDashboardOpen && !state.commandPaletteOpen) {
1045
+ if (!state.settingsOpen && !state.installEndpointsOpen && !state.toolInstallPromptOpen && !state.incompatibleFallbackOpen && !state.recommendOpen && !state.changelogOpen && !state.installedModelsOpen && !state.routerDashboardOpen && !state.commandPaletteOpen) {
1052
1046
  const visible = state.results.filter(r => !r.hidden)
1053
1047
  state.visibleSorted = sortResultsWithPinnedFavorites(visible, state.sortColumn, state.sortDirection, {
1054
1048
  pinFavorites: state.favoritesPinnedAndSticky,
1055
1049
  })
1056
1050
  }
1051
+ const tableTerminalRows = state.terminalRows
1052
+
1057
1053
  let tableContent = null
1058
1054
  if (state.commandPaletteOpen) {
1059
1055
  if (!state.commandPaletteFrozenTable) {
@@ -1071,7 +1067,7 @@ export async function runApp(cliArgs, config) {
1071
1067
  state.mode,
1072
1068
  state.tierFilterMode,
1073
1069
  state.scrollOffset,
1074
- state.terminalRows,
1070
+ tableTerminalRows,
1075
1071
  state.terminalCols,
1076
1072
  state.originFilterMode,
1077
1073
  null,
@@ -1089,14 +1085,9 @@ export async function runApp(cliArgs, config) {
1089
1085
  state.favoritesPinnedAndSticky,
1090
1086
  state.customTextFilter,
1091
1087
  state.lastReleaseDate,
1092
- state.footerHidden,
1088
+ false,
1093
1089
  state.verdictFilterMode,
1094
- state.healthFilterMode,
1095
- state.routerFooterRunning,
1096
- state.routerFooterActiveSet,
1097
- state.routerFooterTodayTokens,
1098
- state.routerFooterAllTimeTokens,
1099
- state.routerFooterRequests
1090
+ state.healthFilterMode
1100
1091
  )
1101
1092
  }
1102
1093
  tableContent = state.commandPaletteFrozenTable
@@ -1114,7 +1105,7 @@ export async function runApp(cliArgs, config) {
1114
1105
  state.mode,
1115
1106
  state.tierFilterMode,
1116
1107
  state.scrollOffset,
1117
- state.terminalRows,
1108
+ tableTerminalRows,
1118
1109
  state.terminalCols,
1119
1110
  state.originFilterMode,
1120
1111
  null,
@@ -1132,23 +1123,12 @@ export async function runApp(cliArgs, config) {
1132
1123
  state.favoritesPinnedAndSticky,
1133
1124
  state.customTextFilter,
1134
1125
  state.lastReleaseDate,
1135
- state.footerHidden,
1126
+ false,
1136
1127
  state.verdictFilterMode,
1137
- state.healthFilterMode,
1138
- state.routerFooterRunning,
1139
- state.routerFooterActiveSet,
1140
- state.routerFooterTodayTokens,
1141
- state.routerFooterAllTimeTokens,
1142
- state.routerFooterRequests
1128
+ state.healthFilterMode
1143
1129
  )
1144
1130
  }
1145
1131
 
1146
- // 📖 Router upgrade banner: inline notification for existing users not yet seen router
1147
- if (!state.routerOnboardingOpen && !state.settingsOpen && !state.installEndpointsOpen && !state.toolInstallPromptOpen && !state.installedModelsOpen && !state.routerDashboardOpen && !state.tokenUsageOpen && !state.commandPaletteOpen && !state.recommendOpen && !state.feedbackOpen && !state.helpVisible && !state.changelogOpen && !state.incompatibleFallbackOpen) {
1148
- const banner = overlays.renderRouterUpgradeBanner()
1149
- if (banner) tableContent = banner + '\n' + tableContent
1150
- }
1151
-
1152
1132
  const content = state.settingsOpen
1153
1133
  ? overlays.renderSettings()
1154
1134
  : state.installEndpointsOpen
@@ -1169,9 +1149,7 @@ export async function runApp(cliArgs, config) {
1169
1149
  ? tableContent + overlays.renderCommandPalette()
1170
1150
  : state.recommendOpen
1171
1151
  ? overlays.renderRecommend()
1172
- : state.feedbackOpen
1173
- ? overlays.renderFeedback()
1174
- : state.helpVisible
1152
+ : state.helpVisible
1175
1153
  ? overlays.renderHelp()
1176
1154
  : state.changelogOpen
1177
1155
  ? overlays.renderChangelog()
@@ -1184,7 +1162,7 @@ export async function runApp(cliArgs, config) {
1184
1162
  process.stdout.write(ALT_LEAVE);
1185
1163
  console.error(chalk.red('\n[TUI Render Error] An error occurred during UI rendering.'));
1186
1164
  console.error(err);
1187
- console.error(chalk.yellow('\nPlease file an issue at https://github.com/vava-nessa/free-coding-models/issues or use the feedback form (I key) to report this to the author.'));
1165
+ console.error(chalk.yellow('\nPlease file an issue at https://github.com/vava-nessa/free-coding-models/issues or join the Discord to report this to the author.'));
1188
1166
  process.exit(1);
1189
1167
  }
1190
1168
  }, Math.round(1000 / FPS))
@@ -1195,7 +1173,7 @@ export async function runApp(cliArgs, config) {
1195
1173
  pinFavorites: state.favoritesPinnedAndSticky,
1196
1174
  })
1197
1175
 
1198
- process.stdout.write(ALT_HOME + renderTable(state.results, state.pendingPings, state.frame, state.cursor, state.sortColumn, state.sortDirection, state.pingInterval, state.lastPingTime, state.mode, state.tierFilterMode, state.scrollOffset, state.terminalRows, state.terminalCols, state.originFilterMode, null, state.pingMode, state.pingModeSource, state.hideUnconfiguredModels, state.widthWarningStartedAt, state.widthWarningDismissed, state.widthWarningShowCount, state.settingsUpdateState, state.settingsUpdateLatestVersion, false, state.startupLatestVersion, state.versionAlertsEnabled, state.favoritesPinnedAndSticky, state.customTextFilter, state.lastReleaseDate, state.footerHidden, state.verdictFilterMode, state.healthFilterMode))
1176
+ process.stdout.write(ALT_HOME + renderTable(state.results, state.pendingPings, state.frame, state.cursor, state.sortColumn, state.sortDirection, state.pingInterval, state.lastPingTime, state.mode, state.tierFilterMode, state.scrollOffset, state.terminalRows, state.terminalCols, state.originFilterMode, null, state.pingMode, state.pingModeSource, state.hideUnconfiguredModels, state.widthWarningStartedAt, state.widthWarningDismissed, state.widthWarningShowCount, state.settingsUpdateState, state.settingsUpdateLatestVersion, false, state.startupLatestVersion, state.versionAlertsEnabled, state.favoritesPinnedAndSticky, state.customTextFilter, state.lastReleaseDate, false, state.verdictFilterMode, state.healthFilterMode))
1199
1177
  if (process.stdout.isTTY) {
1200
1178
  process.stdout.flush && process.stdout.flush()
1201
1179
  }
@@ -1256,7 +1234,7 @@ export async function runApp(cliArgs, config) {
1256
1234
  process.stdout.write(ALT_LEAVE);
1257
1235
  console.error(chalk.red('\n[TUI Error] An error occurred in the ping loop.'));
1258
1236
  console.error(err);
1259
- console.error(chalk.yellow('\nPlease file an issue at https://github.com/vava-nessa/free-coding-models/issues or use the feedback form (I key) to report this to the author.'));
1237
+ console.error(chalk.yellow('\nPlease file an issue at https://github.com/vava-nessa/free-coding-models/issues or join the Discord to report this to the author.'));
1260
1238
  process.exit(1);
1261
1239
  }
1262
1240
  }
@@ -1283,48 +1261,6 @@ export async function runApp(cliArgs, config) {
1283
1261
  } catch {}
1284
1262
  }, VERSION_RECHECK_INTERVAL_MS)
1285
1263
 
1286
- // 📖 Router footer stats: poll daemon every 30s so the main table footer always
1287
- // 📖 shows live token counts and daemon status even when the Router Dashboard is closed.
1288
- const ROUTER_FOOTER_POLL_INTERVAL_MS = 30_000
1289
- const ROUTER_FOOTER_FETCH_TIMEOUT_MS = 1200
1290
-
1291
- async function fetchRouterFooterStats() {
1292
- try {
1293
- const controller = new AbortController()
1294
- const timer = setTimeout(() => controller.abort(), ROUTER_FOOTER_FETCH_TIMEOUT_MS)
1295
- const pidPath = `${process.env.HOME}/.free-coding-models-daemon.pid`
1296
- const portPath = `${process.env.HOME}/.free-coding-models-daemon.port`
1297
- let port = 19280
1298
- try {
1299
- const { readFileSync: rfs } = await import('node:fs')
1300
- const savedPort = rfs(portPath, 'utf8').trim()
1301
- if (/^\d+$/.test(savedPort)) port = Number(savedPort)
1302
- } catch {}
1303
- const res = await globalThis.fetch(`http://127.0.0.1:${port}/stats`, {
1304
- signal: controller.signal,
1305
- })
1306
- clearTimeout(timer)
1307
- if (!res.ok) { state.routerFooterRunning = false; return }
1308
- const raw = await res.json()
1309
- const tokens = raw.tokens || {}
1310
- const today = tokens.today || {}
1311
- const allTime = tokens.all_time || {}
1312
- state.routerFooterRunning = true
1313
- state.routerFooterActiveSet = raw.activeSet || null
1314
- state.routerFooterTodayTokens = today.total_tokens || 0
1315
- state.routerFooterAllTimeTokens = allTime.total_tokens || 0
1316
- state.routerFooterRequests = today.requests || 0
1317
- state.routerFooterLastFetchAt = Date.now()
1318
- } catch {
1319
- state.routerFooterRunning = false
1320
- }
1321
- }
1322
-
1323
- state.routerFooterPollTimer = setInterval(() => {
1324
- void fetchRouterFooterStats()
1325
- }, ROUTER_FOOTER_POLL_INTERVAL_MS)
1326
- void fetchRouterFooterStats() // 📖 Initial fetch immediately so footer is populated on first render
1327
-
1328
1264
  // 📖 Router ON by default — no onboarding prompt, just auto-enable silently.
1329
1265
  const routerCfg = state.config?.router
1330
1266
  if (!routerCfg || routerCfg.onboardingSeen !== true || routerCfg.enabled !== true) {
@@ -145,7 +145,7 @@ const BASE_COMMAND_TREE = [
145
145
  label: 'Other filters',
146
146
  icon: '⚙️',
147
147
  children: [
148
- { id: 'filter-configured-toggle', label: 'Toggle configured-only', shortcut: 'E', description: 'Show only configured providers', keywords: ['filter', 'configured', 'keys'] },
148
+ { id: 'filter-configured-toggle', label: 'Show only configured & working', shortcut: 'E', description: 'Show only configured providers that are responding (not noauth/auth error)', keywords: ['filter', 'configured', 'keys', 'working', 'active'] },
149
149
  ]
150
150
  },
151
151
  ]
@@ -208,10 +208,8 @@ const BASE_COMMAND_TREE = [
208
208
  { id: 'open-settings', label: 'Settings', shortcut: 'P', icon: '⚙️', type: 'page', description: 'API keys and preferences', keywords: ['settings', 'config', 'api key'] },
209
209
  { id: 'open-help', label: 'Help', shortcut: 'K', icon: '❓', type: 'page', description: 'Show all shortcuts', keywords: ['help', 'shortcuts', 'hotkeys'] },
210
210
  { id: 'open-changelog', label: 'Changelog', shortcut: 'N', icon: '📋', type: 'page', description: 'Version history', keywords: ['changelog', 'release'] },
211
- { id: 'open-feedback', label: 'Feedback', shortcut: 'I', icon: '📝', type: 'page', description: 'Report bugs or requests', keywords: ['feedback', 'bug', 'request'] },
211
+
212
212
  { id: 'open-recommend', label: 'Smart recommend', shortcut: 'Q', icon: '🎯', type: 'page', description: 'Find best model for task', keywords: ['recommend', 'best model'] },
213
- { id: 'open-router-dashboard', label: 'Router dashboard', shortcut: 'Shift+R', icon: '🔀', type: 'page', description: 'Inspect daemon health, circuits, tokens, and request log', keywords: ['router', 'daemon', 'dashboard', 'health', 'stats', 'tokens', 'circuit'] },
214
- { id: 'open-token-usage', label: 'Token usage', shortcut: 'Shift+T', icon: '📊', type: 'page', description: 'View token usage history, 7-day chart, today/all-time totals', keywords: ['token', 'usage', 'chart', 'history', 'router'] },
215
213
  { id: 'open-install-endpoints', label: 'Install endpoints', icon: '🔌', type: 'page', description: 'Install provider catalogs', keywords: ['install', 'endpoints', 'providers'] },
216
214
  { id: 'open-installed-models', label: 'Installed models', icon: '🗂️', type: 'page', description: 'View models configured in tools', keywords: ['installed', 'models', 'configured', 'tools', 'manager', 'goose', 'crush', 'aider'] },
217
215
  ]
package/src/config.js CHANGED
@@ -237,12 +237,12 @@ function normalizeProvidersSection(providers) {
237
237
 
238
238
  function normalizeSettingsSection(settings) {
239
239
  const safeSettings = isPlainObject(settings) ? { ...settings } : {}
240
+ delete safeSettings.footerHidden
240
241
  return {
241
242
  ...safeSettings,
242
243
  hideUnconfiguredModels: typeof safeSettings.hideUnconfiguredModels === 'boolean' ? safeSettings.hideUnconfiguredModels : true,
243
244
  favoritesPinnedAndSticky: typeof safeSettings.favoritesPinnedAndSticky === 'boolean' ? safeSettings.favoritesPinnedAndSticky : false,
244
245
  theme: ['dark', 'light', 'auto'].includes(safeSettings.theme) ? safeSettings.theme : 'auto',
245
- footerHidden: typeof safeSettings.footerHidden === 'boolean' ? safeSettings.footerHidden : false,
246
246
  }
247
247
  }
248
248
 
@@ -1007,7 +1007,7 @@ export function isProviderEnabled(config, providerKey) {
1007
1007
  /**
1008
1008
  * 📖 _emptyProfileSettings: Default TUI settings.
1009
1009
  *
1010
- * @returns {{ tierFilter: string|null, sortColumn: string, sortAsc: boolean, pingInterval: number, hideUnconfiguredModels: boolean, favoritesPinnedAndSticky: boolean, preferredToolMode: string, theme: string, footerHidden: boolean }}
1010
+ * @returns {{ tierFilter: string|null, sortColumn: string, sortAsc: boolean, pingInterval: number, hideUnconfiguredModels: boolean, favoritesPinnedAndSticky: boolean, preferredToolMode: string, theme: string }}
1011
1011
  */
1012
1012
  export function _emptyProfileSettings() {
1013
1013
  return {
@@ -1019,7 +1019,6 @@ export function _emptyProfileSettings() {
1019
1019
  favoritesPinnedAndSticky: false, // 📖 default mode keeps favorites as normal starred rows; press Y to pin+stick them.
1020
1020
  preferredToolMode: 'opencode', // 📖 remember the last Z-selected launcher across app restarts
1021
1021
  theme: 'auto', // 📖 'auto' follows the terminal/OS theme, override with 'dark' or 'light' if needed
1022
- footerHidden: false, // 📖 false = full footer shown; true = collapsed to a single "(W) Toggle Footer" hint
1023
1022
  }
1024
1023
  }
1025
1024
 
package/src/constants.js CHANGED
@@ -15,8 +15,8 @@
15
15
  * - `FPS` controls animation frame rate (braille spinner).
16
16
  * - `COL_MODEL` / `COL_MS` control legacy ping-column widths (retained for compat).
17
17
  * - `CELL_W` is derived from `COL_MS` and used by `msCell` / `spinCell`.
18
- * - `TABLE_HEADER_LINES` + `TABLE_FOOTER_LINES` = `TABLE_FIXED_LINES` must stay in sync
19
- * with the actual number of lines rendered by `renderTable()` in bin/.
18
+ * - `TABLE_HEADER_LINES` and footer line counts must stay in sync with the
19
+ * actual number of lines rendered by `renderTable()`.
20
20
  * - `WIDTH_WARNING_MIN_COLS` controls when the narrow-terminal startup warning appears.
21
21
  * - Overlay background colours (chalk.bgRgb) make each overlay panel visually distinct.
22
22
  *
@@ -101,8 +101,8 @@ export const WIDTH_WARNING_MIN_COLS = 80
101
101
 
102
102
  // 📖 Table row-budget constants — must stay in sync with renderTable()'s actual output.
103
103
  // 📖 If this drifts, model rows overflow and can push the title row out of view.
104
- export const TABLE_HEADER_LINES = 5 // 📖 title, filter bar, spacer, column headers, separator
105
- export const TABLE_FOOTER_LINES = 1 // 📖 single toggle-hint line when collapsed, full footer otherwise
104
+ export const TABLE_HEADER_LINES = 2 // 📖 title, column headers
105
+ export const TABLE_FOOTER_LINES = 2 // 📖 actions, links
106
106
  export const TABLE_FIXED_LINES = TABLE_HEADER_LINES + TABLE_FOOTER_LINES
107
107
 
108
108
  // ─── Small cell-formatting helpers ────────────────────────────────────────────