free-coding-models 0.5.87 → 0.5.89
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/README.md +7 -9
- package/bin/free-coding-models.js +33 -2
- package/changelog/v0.5.88.md +16 -0
- package/changelog/v0.5.89.md +20 -0
- package/package.json +7 -2
- package/src/core/router-daemon.js +1129 -500
- package/src/core/router-v2/anthropic-compat.js +473 -0
- package/src/core/router-v2/bench.js +171 -0
- package/src/core/router-v2/breaker-store.js +265 -0
- package/src/core/router-v2/constants.js +108 -0
- package/src/core/router-v2/decision-trace.js +134 -0
- package/src/core/router-v2/failure-classifier.js +231 -0
- package/src/core/router-v2/request-history.js +137 -0
- package/src/core/router-v2/response-gate.js +175 -0
- package/src/core/router-v2/tui-dashboard.js +632 -0
- package/src/core/schema-normalizer.js +23 -6
- package/src/core/security.js +170 -24
- package/src/core/utils.js +109 -0
- package/src/tui/app.js +7 -2
- package/src/tui/cli-help.js +4 -0
- package/src/tui/key-handler.js +117 -2
- package/src/tui/overlays.js +10 -0
- package/src/tui/tui-state.js +22 -0
- package/web/dist/assets/index-C5hgQLYN.js +48 -0
- package/web/dist/assets/index-CCaxIOti.css +1 -0
- package/web/dist/index.html +2 -2
- package/web/server.js +82 -0
- package/web/dist/assets/index-BpTpPS0g.js +0 -44
- package/web/dist/assets/index-CAzFIt8P.css +0 -1
package/README.md
CHANGED
|
@@ -27,10 +27,6 @@ free-coding-models
|
|
|
27
27
|
<sub>Then create a free account on one of the <a href="#-providers">24 providers</a> to grab an API key.</sub>
|
|
28
28
|
</p>
|
|
29
29
|
|
|
30
|
-
<p align="center">
|
|
31
|
-
<img src="website/public/demo.gif" alt="free-coding-models demo" width="100%">
|
|
32
|
-
</p>
|
|
33
|
-
|
|
34
30
|
<p align="center">
|
|
35
31
|
<sub>Made with ❤️ and ☕ by <a href="https://vanessadepraute.dev">Vanessa Depraute</a> (aka <a href="https://vavanessa.dev">Vava-Nessa</a>)</sub>
|
|
36
32
|
</p>
|
|
@@ -87,10 +83,6 @@ free-coding-models --fiable # print the single most reliable model
|
|
|
87
83
|
- 🔀 Want one endpoint that never dies? `free-coding-models --daemon-bg` starts the [Smart Model Router](./docs/router.md).
|
|
88
84
|
- 🤖 Live inside an agent? Install the [OpenCode plugin](./packages/fcm-opencode/README.md) or the [Pi extension](./packages/fcm-pi/README.md).
|
|
89
85
|
|
|
90
|
-
<p align="center">
|
|
91
|
-
<img src="demo2.gif" alt="free-coding-models TUI demo" width="100%">
|
|
92
|
-
</p>
|
|
93
|
-
|
|
94
86
|
---
|
|
95
87
|
|
|
96
88
|
## 🟢 Providers
|
|
@@ -159,6 +151,12 @@ free-coding-models --daemon-stop # stop
|
|
|
159
151
|
|
|
160
152
|
> 📖 **Full guide (probes, circuit breaker, failover, Playground, `--sync-set`, REST API):** [`docs/router.md`](./docs/router.md)
|
|
161
153
|
|
|
154
|
+
### 🧪 Router v2 engine (built in)
|
|
155
|
+
|
|
156
|
+
The router daemon now runs the hardened v2 engine internally: content-validated failover (a 200 with empty or garbage output is a failure and fails over), quota-aware routing with `Retry-After` pauses, persisted circuit breakers with a DEGRADED warning state, per-request decision traces, an Anthropic `/v1/messages` endpoint, and "test via router" actions that exercise the real routing chain. Same command, same port, same endpoints - existing setups upgrade by updating the package.
|
|
157
|
+
|
|
158
|
+
Open the TUI with `Shift+V` (or the "Router v2" entry in the Web Dashboard) to see the live fallback chain, per-request attempt chains and run pinned-model tests (`Ctrl+T`). Docs: [`docs/router-v2.md`](./docs/router-v2.md).
|
|
159
|
+
|
|
162
160
|
---
|
|
163
161
|
|
|
164
162
|
## 📖 CLI Flags
|
|
@@ -179,7 +177,7 @@ Flags combine freely in any order. The most common:
|
|
|
179
177
|
| `--reprobe` / `--probe-ttl <ms>` / `--show-broken` | Probe cache control |
|
|
180
178
|
| `--check-drift` | Diff `sources.js` vs models.dev, exit 1 on mismatch |
|
|
181
179
|
| `--config-dir <dir>` | Custom config location |
|
|
182
|
-
| `--fix-permissions` / `--yes` | Auto-fix config file permissions (chmod 600) |
|
|
180
|
+
| `--fix-permissions` / `--yes` | Auto-fix config file permissions (chmod 600, NTFS ACLs via icacls on Windows) |
|
|
183
181
|
| `--web` | Open the Web Dashboard instead of the TUI |
|
|
184
182
|
| `--daemon` / `--daemon-bg` / `--daemon-status` / `--daemon-stop` | Smart Model Router lifecycle |
|
|
185
183
|
| `--sync-set [name]` | Auto-populate a router set with the currently best models |
|
|
@@ -118,7 +118,7 @@ async function main() {
|
|
|
118
118
|
ensureFavoritesConfig(config);
|
|
119
119
|
|
|
120
120
|
const isDevMode = isPackageDevMode();
|
|
121
|
-
const shouldEnforceUpdate = !cliArgs.daemonStopMode && !cliArgs.daemonStatusMode;
|
|
121
|
+
const shouldEnforceUpdate = !cliArgs.daemonStopMode && !cliArgs.daemonStatusMode && !cliArgs.routerV2StopMode && !cliArgs.routerV2StatusMode;
|
|
122
122
|
const startupUpdate = shouldEnforceUpdate
|
|
123
123
|
? await enforceMandatoryStartupUpdate(config, {
|
|
124
124
|
saveConfig,
|
|
@@ -168,7 +168,11 @@ async function main() {
|
|
|
168
168
|
&& !cliArgs.daemonMode
|
|
169
169
|
&& !cliArgs.daemonBackgroundMode
|
|
170
170
|
&& !cliArgs.daemonStopMode
|
|
171
|
-
&& !cliArgs.daemonStatusMode
|
|
171
|
+
&& !cliArgs.daemonStatusMode
|
|
172
|
+
&& !cliArgs.routerV2Mode
|
|
173
|
+
&& !cliArgs.routerV2BackgroundMode
|
|
174
|
+
&& !cliArgs.routerV2StopMode
|
|
175
|
+
&& !cliArgs.routerV2StatusMode;
|
|
172
176
|
try {
|
|
173
177
|
await checkConfigSecurity({ autoFix: cliArgs.fixPermissionsMode, promptAllowed });
|
|
174
178
|
} catch {
|
|
@@ -210,6 +214,33 @@ async function main() {
|
|
|
210
214
|
process.exit(result.ok ? 0 : 1);
|
|
211
215
|
}
|
|
212
216
|
|
|
217
|
+
// 📖 Router v2 lifecycle flags are now ALIASES of the main daemon: the v2
|
|
218
|
+
// engine (content-validated failover, persisted breakers, decision traces,
|
|
219
|
+
// Anthropic /v1/messages) is merged into the regular router daemon on the
|
|
220
|
+
// historical port. The flags keep working for existing scripts.
|
|
221
|
+
if (cliArgs.routerV2Mode || cliArgs.routerV2BackgroundMode || cliArgs.routerV2StopMode || cliArgs.routerV2StatusMode) {
|
|
222
|
+
const {
|
|
223
|
+
getRouterDaemonStatus,
|
|
224
|
+
runRouterDaemon,
|
|
225
|
+
startRouterDaemonBackground,
|
|
226
|
+
stopRouterDaemon,
|
|
227
|
+
} = await import('../src/core/router-daemon.js');
|
|
228
|
+
|
|
229
|
+
if (cliArgs.routerV2Mode) {
|
|
230
|
+
await runRouterDaemon();
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const result = cliArgs.routerV2BackgroundMode
|
|
235
|
+
? await startRouterDaemonBackground()
|
|
236
|
+
: cliArgs.routerV2StopMode
|
|
237
|
+
? await stopRouterDaemon()
|
|
238
|
+
: await getRouterDaemonStatus();
|
|
239
|
+
|
|
240
|
+
console.log(JSON.stringify(result, null, 2));
|
|
241
|
+
process.exit(result.ok ? 0 : 1);
|
|
242
|
+
}
|
|
243
|
+
|
|
213
244
|
// 📖 --sync-set [name] — auto-discover, probe, and populate a router set
|
|
214
245
|
if (cliArgs.syncSetMode) {
|
|
215
246
|
const { syncSet } = await import('../src/core/sync-set.js');
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changelog v0.5.88 - 2026-09-06
|
|
2
|
+
|
|
3
|
+
### Fixed
|
|
4
|
+
- **Windows: the config permissions warning no longer re-fires on every launch** (issue #173 follow-up reported by @rutexd). On Windows, Node can only report file modes 0666 (writable) or 0444 (read-only), so the old "is it 600?" check could never pass: the app warned, you answered yes, it said "fixed!", and the next launch warned again in an endless loop.
|
|
5
|
+
- The Windows security verdict now comes from the real NTFS ACL, read with `icacls <file>` and parsed by a new pure helper (`parseIcaclsOutput`). The file counts as protected when NTFS inheritance is disabled and only your user (plus trusted SYSTEM/Administrators entries, matched by name or well-known SID, locale-proof) has access.
|
|
6
|
+
- Fixing on Windows now runs `icacls "<file>" /inheritance:r /grant:r "<user>:F"` (no shell, injection-safe) instead of the no-op chmod, and the fix is **verified by re-reading the ACL** before the app claims success. No more fake "✅ Permissions fixed!".
|
|
7
|
+
- The warning now explains *what* is wrong on NTFS (inherited access, or which other principals have a grant) instead of showing a meaningless octal mode.
|
|
8
|
+
- **Anti-nag guard (all platforms)**: when the fix genuinely cannot be applied or verified (rare), the app now writes a small ack marker next to the config file and stays quiet about the same warning for 30 days, instead of nagging on every single launch. Passing `--fix-permissions` / `--yes` / `-y` always bypasses the marker and retries. The marker is cleaned up as soon as a fix succeeds.
|
|
9
|
+
- The manual fix hint on Windows now shows the exact `icacls` command with your real username, and the POSIX path (chmod 600) additionally re-checks the mode after chmod so success messages are always truthful.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- `--fix-permissions` documentation now mentions it applies NTFS ACLs on Windows (README flags table).
|
|
13
|
+
|
|
14
|
+
### For contributors
|
|
15
|
+
- New pure helpers in `src/core/utils.js`, fully unit-tested: `parseIcaclsOutput` (icacls output parser, handles the echoed path glued to the first ACE, `(I)` inheritance markers, SIDs and localized group names) and `shouldSkipSecurityWarn` (30-day anti-nag gate, ISO string or epoch input, clock-skew safe).
|
|
16
|
+
- `checkConfigSecurity()` gained an integration test covering the full lifecycle: warn once, ack, stay silent on relaunch, auto-fix bypasses the ack, marker cleanup after a successful fix.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Changelog v0.5.89 - 2026-09-06
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
- 🧪 **Smart Model Router v2 engine (built in)**: the regular router daemon (`--daemon-bg`, historical port) now runs the hardened v2 engine - same command, same port, same endpoints, existing setups upgrade transparently. A hardened parallel beta ran on port 19380 during development; it is retired by this release. Open the TUI overlay with `Shift+V`, or use the "Router v2" page in the Web Dashboard (BETA badge on both surfaces).
|
|
5
|
+
- 🛡️ **Content-validated failover**: a 200 only counts as a success when the answer holds real content. Empty `choices`, embedded `error` objects or content-less replies now fail over to the next model, in both streaming and non-streaming. This closes the biggest v1 blind spot where free providers serving empty 200s were reported as successes.
|
|
6
|
+
- ⏱️ **Body-read timeout**: a provider that sends headers but trickles (or never sends) the body can no longer hang your coding tool; the read is raced against `router.failover.bodyReadTimeoutMs` (default 30s) and fails over.
|
|
7
|
+
- 🔥 **Quota-aware routing**: models answering 429 are paused for their `Retry-After` window and skipped; when every model is paused the client gets a proper `429` + `Retry-After` instead of a generic 503.
|
|
8
|
+
- ⚖️ **Blame attribution**: client-caused 4xx (bad payload, unsupported fields) fail over but never damage healthy models' circuit breakers, and client disconnects never mark failures.
|
|
9
|
+
- 💾 **Persisted circuit breakers**: breaker state survives daemon restarts, with a new amber `DEGRADED` warning state (60% of the failure threshold) and escalating cooldowns for repeatedly-tripping models.
|
|
10
|
+
- 🔍 **Decision traces**: every response carries `x-fcm-v2-model`, `x-fcm-v2-attempts` and `x-fcm-v2-decision` headers, and every request chain (attempts, statuses, skip reasons) is persisted locally and rendered in the TUI overlay and the web page. Routing metadata only: no prompts, no keys.
|
|
11
|
+
- 🤖 **Anthropic `/v1/messages` support** on the v2 port: request, response and SSE stream translation (including `tool_use` / `input_json_delta`), so Anthropic-protocol agents can use the router directly.
|
|
12
|
+
- 📌 **Pinned-model requests**: `model: "fcm:@provider/modelId"` routes to one exact model through the full chain with failover disabled. New `Ctrl+T` (test selected model) and `Ctrl+Shift+T` (test all visible models) TUI hotkeys use it to test the real routing path, unlike the direct-to-provider `Ctrl+A` / `Ctrl+U` benchmarks.
|
|
13
|
+
- 🚑 **Last-resort model**: optional `router.failover.lastResortModel` config gives a designated model one final shot when the whole set fails.
|
|
14
|
+
- ⚡ **Fast first boot**: v2 listens before its first probe pass, removing the old first-boot stall where the router port stayed dark for up to ~36 seconds.
|
|
15
|
+
|
|
16
|
+
### Security
|
|
17
|
+
- 🔒 Client `x-api-key` headers are now stripped before proxying to upstream providers on v2, so the local router token can never leak to a provider.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- 🐛 Ghost "active requests" no longer accumulate in v2 stats when a request is rejected before routing (v1 leaks one entry per rejected request until restart).
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "free-coding-models",
|
|
3
|
-
"version": "0.5.
|
|
4
|
-
"description": "Find the fastest coding LLM models in seconds
|
|
3
|
+
"version": "0.5.89",
|
|
4
|
+
"description": "Find the fastest coding LLM models in seconds \u2014 ping free models from multiple providers, pick the best one for OpenCode, Cursor, or any AI coding assistant.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nvidia",
|
|
7
7
|
"nim",
|
|
@@ -94,5 +94,10 @@
|
|
|
94
94
|
"react": "^19.2.7",
|
|
95
95
|
"react-dom": "^19.2.7",
|
|
96
96
|
"vite": "^8.0.16"
|
|
97
|
+
},
|
|
98
|
+
"pnpm": {
|
|
99
|
+
"onlyBuiltDependencies": [
|
|
100
|
+
"esbuild"
|
|
101
|
+
]
|
|
97
102
|
}
|
|
98
103
|
}
|