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 +4 -47
- package/README.md +10 -32
- package/package.json +1 -1
- package/sources.js +3 -3
- package/src/app.js +28 -92
- package/src/command-palette.js +2 -4
- package/src/config.js +2 -3
- package/src/constants.js +4 -4
- package/src/key-handler.js +13 -105
- package/src/overlays.js +20 -120
- package/src/provider-metadata.js +1 -1
- package/src/render-helpers.js +38 -8
- package/src/render-table.js +116 -278
- package/src/router-dashboard.js +10 -1
- package/web/dist/assets/{index-DNRCaWPi.js → index-Bg2AldyN.js} +1 -1
- package/web/dist/index.html +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,53 +1,10 @@
|
|
|
1
|
-
## [0.3.
|
|
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
|
-
- **
|
|
19
|
-
- **
|
|
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
|
-
- **
|
|
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-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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,
|
|
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
|
|
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) {
|
package/src/command-palette.js
CHANGED
|
@@ -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: '
|
|
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
|
-
|
|
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
|
|
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`
|
|
19
|
-
*
|
|
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 =
|
|
105
|
-
export const TABLE_FOOTER_LINES =
|
|
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 ────────────────────────────────────────────
|