free-coding-models 0.3.66 → 0.3.67
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 +39 -9
- package/README.md +111 -12
- package/bin/free-coding-models.js +1 -9
- package/package.json +1 -1
- package/src/cli-help.js +2 -3
- package/src/router-daemon.js +351 -15
- package/web/dist/assets/{index-BKwbbLPp.js → index-DCwSuNgI.js} +1 -1
- package/web/dist/index.html +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,15 +1,45 @@
|
|
|
1
|
-
## [0.3.
|
|
1
|
+
## [0.3.67] - 2026-05-17
|
|
2
2
|
|
|
3
3
|
### Added
|
|
4
|
-
|
|
5
|
-
-
|
|
6
|
-
-
|
|
4
|
+
|
|
5
|
+
- **🐳 Docker Packaging**: First-class Docker support so you can run FCM without installing Node.js. The official image is published to `ghcr.io/vava-nessa/free-coding-models` on every release and tag push.
|
|
6
|
+
- Multi-arch friendly `Dockerfile` based on `node:20-alpine`, running as a non-root `fcm` user.
|
|
7
|
+
- `docker-entrypoint.sh` auto-generates `~/.free-coding-models.json` from any `*_API_KEY` / `*_API_TOKEN` env vars you pass to the container — no manual config step.
|
|
8
|
+
- `docker-compose.yml` template wired up for every supported provider.
|
|
9
|
+
- GitHub Actions workflow (`.github/workflows/docker.yml`) handles build + publish to GHCR on `release: published`, `push: v*.*.*` tags, and `workflow_dispatch` (with a `test_mode` dry-run input).
|
|
10
|
+
- Trivy vulnerability scan blocks releases that introduce any CRITICAL/HIGH CVEs.
|
|
11
|
+
- Quick start: `docker run -p 19280:19280 -e OPENROUTER_API_KEY=... ghcr.io/vava-nessa/free-coding-models:latest`.
|
|
12
|
+
- **🌐 Combined Daemon + Web Dashboard**: The router daemon now serves the web dashboard from the same port — no more juggling two processes. New REST surface area baked into the daemon:
|
|
13
|
+
- `GET /api/models` — full model catalog with latency stats, status, p95, jitter, stability, verdict, uptime, and `inRouterSet` flag.
|
|
14
|
+
- `GET /api/config` — provider catalog with masked API keys (`••••••••XXXX`) and enabled state.
|
|
15
|
+
- `GET /api/events` — SSE stream the dashboard subscribes to for live updates.
|
|
16
|
+
- `GET /api/key/<provider>` — reveal the raw API key for a configured provider (same-origin only).
|
|
17
|
+
- `POST /api/settings` — save API keys and per-provider enabled flags from the dashboard, then trigger a probe burst.
|
|
18
|
+
- When you add a new provider's API key from the dashboard, FCM now mirrors `--sync-set` behavior and automatically adds that provider's best-tier model to your active router set.
|
|
7
19
|
|
|
8
20
|
### Changed
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
- **
|
|
21
|
+
|
|
22
|
+
- **`--web` flag removed**: replaced by `--daemon`, which now serves both the OpenAI-compatible router API and the dashboard on the same port. Existing tooling using `--web` should switch to `--daemon`.
|
|
23
|
+
- **Preserve user-created router sets on daemon start**: previously the daemon rebuilt the active set from favorites or defaults on every restart, silently overwriting sets created with `--sync-set`. Named sets are now preserved.
|
|
24
|
+
- **Faster config reload**: `CONFIG_RELOAD_INTERVAL_MS` shortened from 60s → 10s so dashboard-driven changes (toggling providers, adding keys) propagate quickly.
|
|
25
|
+
- **Contributors**: welcome [@stgreenb](https://github.com/stgreenb) 🎉 — author of the daemon-web merge and Docker packaging work.
|
|
12
26
|
|
|
13
27
|
### Fixed
|
|
14
|
-
|
|
15
|
-
-
|
|
28
|
+
|
|
29
|
+
- **🔒 Path traversal in dashboard static file serving (security)**: requests like `GET /../../etc/passwd` could escape `web/dist/` and read arbitrary files reachable by the daemon user. The mitigation (`127.0.0.1` bind) was bypassed inside Docker where the daemon binds `0.0.0.0`. All static paths are now resolved against `WEB_DIST_DIR` and rejected with 403 when they escape.
|
|
30
|
+
- **🔒 Cross-site write / key exfiltration on dashboard endpoints (security)**: `POST /api/settings` (writes API keys) and `GET /api/key/<provider>` (reveals raw keys) previously accepted any request, including those triggered by malicious tabs visiting a page that fetched `http://localhost:19280/...`. Both now enforce a same-origin / loopback `Origin` header check. Header-less CLI callers (curl, native apps) keep working.
|
|
31
|
+
- **🔒 Config file permissions tightened in Docker**: the entrypoint previously created `~/.free-coding-models.json` with mode `0666`. Tightened to `0600` since it stores plaintext API keys.
|
|
32
|
+
- **🐛 `/api/key/:provider` route never matched**: the handler compared `url.pathname === '/api/key/:provider'` literally, so the endpoint was unreachable. Now correctly matches via `startsWith` and 404s unknown providers.
|
|
33
|
+
- **🐛 "Excellent" verdict for fully-down models**: when a model had probe history but every ping failed, `avg` collapsed to `0` and the dashboard showed "Excellent". Verdict now correctly returns `—` whenever no usable latency sample exists.
|
|
34
|
+
- **🐛 `--daemon-bg` mode signalled "down" for everything in dashboard**: the type-mismatch comparison (`===` between string code and number) made every model render as `down`. Codes are now compared as strings.
|
|
35
|
+
- **⚙️ Docker GitHub Actions workflow YAML repaired**: the top-level `env:` block and the GHCR login step were incorrectly indented and would have prevented GitHub from parsing the workflow at all.
|
|
36
|
+
- **⚙️ Docker container lifecycle**: the container no longer outlives a crashed daemon. The entrypoint now runs the daemon in the foreground (`--daemon` instead of `--daemon-bg`) so Docker's restart policy can recover from crashes instead of waiting for the healthcheck to time out.
|
|
37
|
+
|
|
38
|
+
### Internal
|
|
39
|
+
|
|
40
|
+
- Removed a dead `createReadStream` import in `router-daemon.js`.
|
|
41
|
+
- Hoisted `routerConfig()` and `getSet()` lookups out of the per-model loop in `getWebModelsPayload()` — saves ~200 redundant runtime calls per dashboard refresh — and uses a `Set` index for in-set membership checks.
|
|
42
|
+
- Renamed a local `window` variable to `probeWindow` to avoid shadowing globals.
|
|
43
|
+
- Removed a nested `router` shadow in `/api/settings`.
|
|
44
|
+
- Added `X-Content-Type-Options: nosniff` header on all static dashboard responses.
|
|
45
|
+
- Added 6 new tests covering path-traversal blocking, cross-origin write rejection, same-origin write success, CLI key fetch, and unknown-provider 404. Total: 371 tests, all green.
|
package/README.md
CHANGED
|
@@ -125,6 +125,94 @@ Use ⚡️ Command Palette! with **Ctrl+P**.
|
|
|
125
125
|
<img src="https://img.shields.io/badge/USE_%E2%9A%A1%EF%B8%8F%20COMMAND%20PALETTE-CTRL%2BP-22c55e?style=for-the-badge" alt="Use ⚡️ Command Palette with Ctrl+P">
|
|
126
126
|
</p>
|
|
127
127
|
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## 🐳 Docker
|
|
131
|
+
|
|
132
|
+
Run FCM without installing Node.js using the official Docker image:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
# Quick start (daemon + web UI on port 19280)
|
|
136
|
+
docker run -p 19280:19280 ghcr.io/vava-nessa/free-coding-models:latest
|
|
137
|
+
|
|
138
|
+
# With an API key
|
|
139
|
+
docker run -p 19280:19280 -e OPENROUTER_API_KEY=your_key ghcr.io/vava-nessa/free-coding-models:latest
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Access the web dashboard at `http://localhost:19280/` and configure your coding tool to use `http://localhost:19280/v1` with model `fcm`.
|
|
143
|
+
|
|
144
|
+
### Available Image Tags
|
|
145
|
+
|
|
146
|
+
| Tag | Description |
|
|
147
|
+
|-----|-------------|
|
|
148
|
+
| `latest` | Most recent release |
|
|
149
|
+
| `v{major}.{minor}.{patch}` | Specific version (e.g., `v0.3.70`) |
|
|
150
|
+
| `v{major}.{minor}` | Minor version (e.g., `v0.3`) |
|
|
151
|
+
| `v{major}` | Major version (e.g., `v0`) |
|
|
152
|
+
|
|
153
|
+
### Environment Variables
|
|
154
|
+
|
|
155
|
+
| Variable | Default | Description |
|
|
156
|
+
|----------|---------|-------------|
|
|
157
|
+
| `FCM_HOST` | `0.0.0.0` | Host to bind to (set `127.0.0.1` for localhost-only) |
|
|
158
|
+
| `FCM_PORT` | `19280` | Port to listen on |
|
|
159
|
+
| `FREE_CODING_MODELS_TELEMETRY` | `0` | Disable telemetry |
|
|
160
|
+
|
|
161
|
+
Provider API keys (all optional):
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
docker run -p 19280:19280 \
|
|
165
|
+
-e NVIDIA_API_KEY=your_key \
|
|
166
|
+
-e GROQ_API_KEY=your_key \
|
|
167
|
+
-e OPENROUTER_API_KEY=your_key \
|
|
168
|
+
ghcr.io/vava-nessa/free-coding-models:latest
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Docker Compose
|
|
172
|
+
|
|
173
|
+
Create a `docker-compose.yml`:
|
|
174
|
+
|
|
175
|
+
```yaml
|
|
176
|
+
version: '3.8'
|
|
177
|
+
services:
|
|
178
|
+
fcm:
|
|
179
|
+
image: ghcr.io/vava-nessa/free-coding-models:latest
|
|
180
|
+
container_name: fcm
|
|
181
|
+
restart: unless-stopped
|
|
182
|
+
ports:
|
|
183
|
+
- "19280:19280"
|
|
184
|
+
environment:
|
|
185
|
+
FREE_CODING_MODELS_TELEMETRY: "0"
|
|
186
|
+
FCM_HOST: "0.0.0.0"
|
|
187
|
+
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY:-}
|
|
188
|
+
volumes:
|
|
189
|
+
- fcm-data:/home/fcm
|
|
190
|
+
volumes:
|
|
191
|
+
fcm-data:
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Run with `docker-compose up -d`. API keys can be passed via a `.env` file or environment variables.
|
|
195
|
+
|
|
196
|
+
### Troubleshooting
|
|
197
|
+
|
|
198
|
+
**Container won't start:**
|
|
199
|
+
- Check logs: `docker logs fcm`
|
|
200
|
+
- Verify port 19280 is not in use: `docker ps | grep 19280`
|
|
201
|
+
|
|
202
|
+
**Health check fails:**
|
|
203
|
+
- Wait 30s for initial probe cycle
|
|
204
|
+
- Verify API keys are valid: `docker exec fcm curl http://localhost:19280/health`
|
|
205
|
+
|
|
206
|
+
**Cannot connect from host:**
|
|
207
|
+
- Ensure `FCM_HOST=0.0.0.0` (default)
|
|
208
|
+
- Check firewall allows localhost connections
|
|
209
|
+
|
|
210
|
+
**Data persistence:**
|
|
211
|
+
- Config is stored in Docker volume `fcm-data`
|
|
212
|
+
- Recreate the volume with `docker-compose down -v` to reset
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
128
216
|
Need to fix contrast because your terminal theme is fighting the TUI? Press **`G`** at any time to cycle **Auto → Dark → Light**. The switch recolors the full interface live: table, Settings, Help, Smart Recommend, Feedback, and Changelog.
|
|
129
217
|
|
|
130
218
|
**② Pick a model and launch your tool:**
|
|
@@ -148,17 +236,8 @@ If the active CLI tool is missing, FCM now catches it before launch, offers a ti
|
|
|
148
236
|
### Common scenarios
|
|
149
237
|
|
|
150
238
|
```bash
|
|
151
|
-
# "I want the most reliable model right now"
|
|
152
|
-
free-coding-models --fiable
|
|
153
|
-
|
|
154
|
-
# "I want to configure Goose with an S-tier model"
|
|
155
|
-
free-coding-models --goose --tier S
|
|
156
|
-
|
|
157
|
-
# "I want NVIDIA's top models only"
|
|
158
|
-
free-coding-models --origin nvidia --tier S
|
|
159
|
-
|
|
160
239
|
# "I want the local web dashboard"
|
|
161
|
-
free-coding-models --
|
|
240
|
+
free-coding-models --daemon
|
|
162
241
|
|
|
163
242
|
# "I want one local endpoint that fails over between free models"
|
|
164
243
|
free-coding-models --daemon-bg
|
|
@@ -174,7 +253,14 @@ free-coding-models --tier S --json | jq -r '.[0].modelId'
|
|
|
174
253
|
free-coding-models --openclaw --origin groq
|
|
175
254
|
```
|
|
176
255
|
|
|
177
|
-
When launching the
|
|
256
|
+
When launching the daemon (with `--daemon`), the web dashboard and router API are served from the same port. Configure tools with:
|
|
257
|
+
|
|
258
|
+
| Field | Value |
|
|
259
|
+
|-------|-------|
|
|
260
|
+
| Router Base URL | `http://localhost:19280/v1` |
|
|
261
|
+
| Dashboard URL | `http://localhost:19280/` |
|
|
262
|
+
| Model | `fcm` |
|
|
263
|
+
| API key | `fcm-local` |
|
|
178
264
|
|
|
179
265
|
### Smart Model Router
|
|
180
266
|
|
|
@@ -214,9 +300,20 @@ Router endpoints:
|
|
|
214
300
|
| `GET /v1/models` | Return virtual models (`fcm`, `fcm:set-name`) |
|
|
215
301
|
| `GET /health` | Daemon status JSON |
|
|
216
302
|
| `GET /stats` | Routing, health, request log, and token stats |
|
|
217
|
-
| `GET /stream/events` | Live SSE events for
|
|
303
|
+
| `GET /stream/events` | Live SSE events for router updates |
|
|
218
304
|
| `POST /daemon/probe-mode` | Set probe mode with `{ "probeMode": "eco" | "balanced" | "aggressive" }` |
|
|
219
305
|
|
|
306
|
+
**Web Dashboard endpoints** (served from the same port in `--daemon` mode):
|
|
307
|
+
|
|
308
|
+
| Endpoint | Purpose |
|
|
309
|
+
|----------|---------|
|
|
310
|
+
| `GET /` | Web dashboard HTML |
|
|
311
|
+
| `GET /api/models` | All model data with latency stats |
|
|
312
|
+
| `GET /api/config` | Provider config (keys masked) |
|
|
313
|
+
| `GET /api/events` | Live SSE events for dashboard |
|
|
314
|
+
| `GET /api/key/:provider` | Reveal full API key for provider |
|
|
315
|
+
| `POST /api/settings` | Save API keys and provider toggles |
|
|
316
|
+
|
|
220
317
|
Routing behavior:
|
|
221
318
|
|
|
222
319
|
- Priority order works immediately on cold start, then probes refine health scores over time.
|
|
@@ -505,6 +602,7 @@ Telemetry is enabled by default and can be disabled with any of the following:
|
|
|
505
602
|
<td align="center" width="120"><a href="https://github.com/PhucTruong-ctrl"><img src="https://github.com/PhucTruong-ctrl.png?s=80" width="80" height="80" style="border-radius:50%" alt="PhucTruong-ctrl"></a></td>
|
|
506
603
|
<td align="center" width="120"><a href="https://github.com/chindris-mihai-alexandru"><img src="https://avatars.githubusercontent.com/u/12643176?v=4&s=80" width="80" height="80" style="border-radius:50%" alt="chindris-mihai-alexandru"></a></td>
|
|
507
604
|
<td align="center" width="120"><a href="https://github.com/serajbaltu"><img src="https://avatars.githubusercontent.com/u/90699173?v=4&s=80" width="80" height="80" style="border-radius:50%" alt="serajbaltu"></a></td>
|
|
605
|
+
<td align="center" width="120"><a href="https://github.com/stgreenb"><img src="https://avatars.githubusercontent.com/u/18483964?v=4&s=80" width="80" height="80" style="border-radius:50%" alt="stgreenb"></a></td>
|
|
508
606
|
</tr>
|
|
509
607
|
<tr>
|
|
510
608
|
<td align="center"><a href="https://github.com/vava-nessa"><sub><b>vava-nessa</b></sub></a></td>
|
|
@@ -514,6 +612,7 @@ Telemetry is enabled by default and can be disabled with any of the following:
|
|
|
514
612
|
<td align="center"><a href="https://github.com/PhucTruong-ctrl"><sub><b>PhucTruong-ctrl</b></sub></a></td>
|
|
515
613
|
<td align="center"><a href="https://github.com/chindris-mihai-alexandru"><sub><b>chindris-mihai-alexandru</b></sub></a></td>
|
|
516
614
|
<td align="center"><a href="https://github.com/serajbaltu"><sub><b>serajbaltu</b></sub></a></td>
|
|
615
|
+
<td align="center"><a href="https://github.com/stgreenb"><sub><b>stgreenb</b></sub></a></td>
|
|
517
616
|
</tr>
|
|
518
617
|
</table>
|
|
519
618
|
|
|
@@ -92,15 +92,7 @@ async function main() {
|
|
|
92
92
|
process.exit(1);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
//
|
|
96
|
-
if (cliArgs.webMode) {
|
|
97
|
-
const { startWebServer } = await import('../web/server.js')
|
|
98
|
-
const port = parseInt(process.env.FCM_PORT || '3333', 10)
|
|
99
|
-
await startWebServer(port, { open: true })
|
|
100
|
-
return
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// 📖 Load JSON config
|
|
95
|
+
// Load JSON config
|
|
104
96
|
const config = loadConfig();
|
|
105
97
|
ensureTelemetryConfig(config);
|
|
106
98
|
ensureFavoritesConfig(config);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "free-coding-models",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.67",
|
|
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/cli-help.js
CHANGED
|
@@ -36,8 +36,7 @@ const ANALYSIS_FLAGS = [
|
|
|
36
36
|
]
|
|
37
37
|
|
|
38
38
|
const CONFIG_FLAGS = [
|
|
39
|
-
{ flag: '--
|
|
40
|
-
{ flag: '--daemon', description: 'Start the FCM Router daemon in the foreground' },
|
|
39
|
+
{ flag: '--daemon', description: 'Start the FCM Router daemon + web dashboard (same port)' },
|
|
41
40
|
{ flag: '--daemon-bg', description: 'Start the FCM Router daemon in the background' },
|
|
42
41
|
{ flag: '--daemon-status', description: 'Print FCM Router daemon status JSON' },
|
|
43
42
|
{ flag: '--daemon-stop', description: 'Gracefully stop the FCM Router daemon' },
|
|
@@ -48,7 +47,7 @@ const CONFIG_FLAGS = [
|
|
|
48
47
|
|
|
49
48
|
const EXAMPLES = [
|
|
50
49
|
'free-coding-models --help',
|
|
51
|
-
'free-coding-models --
|
|
50
|
+
'free-coding-models --daemon',
|
|
52
51
|
'free-coding-models --daemon-bg',
|
|
53
52
|
'free-coding-models --daemon-status',
|
|
54
53
|
'free-coding-models --sync-set',
|