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 CHANGED
@@ -1,15 +1,45 @@
1
- ## [0.3.66] - 2026-05-16
1
+ ## [0.3.67] - 2026-05-17
2
2
 
3
3
  ### Added
4
- - **ForgeCode Integration**: You can now seamlessly launch and install provider endpoints directly into ForgeCode's TOML config. Use the `--forgecode` flag to run it.
5
- - **GitHub Copilot CLI Support**: Added direct integration with GitHub Copilot CLI (`--copilot`), dynamically setting `COPILOT_*` environment variables for seamless Bring Your Own Key (BYOK) execution.
6
- - **Security Audit Report**: Added comprehensive security audit documentation from Jules.
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
- - **NVIDIA NIM Model Catalog Update**: Removed 11 deprecated models and updated GLM to the new `glm5` to keep the catalog fresh and fully operational.
10
- - **README Optimization**: Refactored README layout and updated image sizes for a cleaner documentation experience.
11
- - **Testing Architecture Refactoring**: Replaced `agent-tui` with native `tmux` to streamline visual TUI testing.
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
- - **Repository Maintenance**: Cleaned up obsolete tooling artifacts to maintain a bloat-free codebase.
15
- - **Dependencies**: Bumped internal UI framework tools (React `19.2.6`, Vite `8.0.13`).
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 --web
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 web dashboard, `free-coding-models` prefers `http://localhost:3333`. If that port is already used by another app, it now auto-picks the next free local port and prints the exact URL to open.
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 dashboard updates |
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
- // 📖 --web mode: launch the web dashboard instead of the TUI
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.66",
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: '--web', description: 'Launch the web dashboard in your browser' },
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 --web',
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',