free-coding-models 0.3.65 → 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 +40 -5
- package/README.md +132 -35
- package/bin/free-coding-models.js +1 -9
- package/package.json +4 -5
- package/sources.js +1 -10
- package/src/app.js +2 -0
- package/src/cli-help.js +2 -3
- package/src/endpoint-installer.js +63 -1
- package/src/key-handler.js +1 -1
- package/src/router-daemon.js +351 -15
- package/src/tool-bootstrap.js +22 -0
- package/src/tool-launchers.js +144 -0
- package/src/tool-metadata.js +7 -2
- package/src/utils.js +8 -2
- package/web/dist/assets/{index-DqMmOpV2.js → index-DCwSuNgI.js} +2 -2
- package/web/dist/index.html +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,45 @@
|
|
|
1
|
-
## [0.3.
|
|
1
|
+
## [0.3.67] - 2026-05-17
|
|
2
2
|
|
|
3
|
-
###
|
|
3
|
+
### Added
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
-
|
|
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
21
|
|
|
10
|
-
-
|
|
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.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
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
|
@@ -1,36 +1,32 @@
|
|
|
1
|
-
<
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
<img src="logo.webp" alt="free-coding-models logo" width="128"><br><br>
|
|
5
|
-
<img src="https://img.shields.io/npm/v/free-coding-models?color=3d6b00&label=npm&logo=npm" alt="npm version" width="200"><br>
|
|
6
|
-
<img src="https://img.shields.io/node/v/free-coding-models?color=3d6b00&logo=node.js" alt="node version" width="200"><br>
|
|
7
|
-
<img src="https://img.shields.io/npm/l/free-coding-models?color=3d6b00" alt="license" width="200"><br>
|
|
8
|
-
<img src="https://img.shields.io/badge/models-170+-3d6b00?logo=nvidia" alt="models count" width="200"><br>
|
|
9
|
-
<img src="https://img.shields.io/badge/providers-16-1a56db" alt="providers count" width="200">
|
|
10
|
-
</td>
|
|
11
|
-
<td style="vertical-align: middle;">
|
|
12
|
-
<h1 style="margin-top: 0;">free-coding-models</h1>
|
|
13
|
-
<strong>Find the fastest free coding model in seconds</strong><br>
|
|
14
|
-
Track ~170 models across ~15 trusted free or free-limited AI providers in real time<br><br>
|
|
15
|
-
<strong>Install Free API endpoints to your favorite AI coding tools:</strong><br>
|
|
16
|
-
OpenCode CLI / Desktop / WebUI, OpenClaw, Crush, Goose, Aider, Kilo CLI, Qwen Code, OpenHands, Amp, Hermes, Continue, Cline, Xcode, Pi, Rovo, Gemini and more...<br><br>
|
|
17
|
-
<strong>Use Kimi K2, DeepSeek V3, GPT-OSS, Qwen3, MiniMax M2, GLM, Llama 4, Gemma 4, Devstral and more — for free</strong>
|
|
18
|
-
</td>
|
|
19
|
-
</tr>
|
|
20
|
-
</table>
|
|
21
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="logo.webp" alt="free-coding-models logo" width="328">
|
|
3
|
+
</p>
|
|
22
4
|
|
|
5
|
+
<h1 align="center">free-coding-models</h1>
|
|
23
6
|
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>Find the fastest free coding model in seconds</strong><br>
|
|
9
|
+
Track ~170 models across ~15 trusted free or free-limited AI providers in real time<br><br>
|
|
10
|
+
<strong>Install Free API endpoints to your favorite AI coding tools:</strong><br>
|
|
11
|
+
OpenCode CLI / Desktop / WebUI, OpenClaw, Crush, Goose, Aider, Kilo CLI, Qwen Code, OpenHands, Amp, Hermes, Continue, Cline, Xcode, Pi, Rovo, Gemini and more...<br><br>
|
|
12
|
+
<strong>Use Kimi K2, DeepSeek V3, GPT-OSS, Qwen3, MiniMax M2, GLM, Llama 4, Gemma 4, Devstral and more — for free</strong>
|
|
13
|
+
</p>
|
|
24
14
|
|
|
25
15
|
<p align="center">
|
|
16
|
+
<img src="https://img.shields.io/npm/v/free-coding-models?color=3d6b00&label=npm&logo=npm" alt="npm version" width="200"><br>
|
|
17
|
+
<img src="https://img.shields.io/node/v/free-coding-models?color=3d6b00&logo=node.js" alt="node version" width="200"><br>
|
|
18
|
+
<img src="https://img.shields.io/npm/l/free-coding-models?color=3d6b00" alt="license" width="200"><br>
|
|
19
|
+
<img src="https://img.shields.io/badge/models-170+-3d6b00?logo=nvidia" alt="models count" width="200"><br>
|
|
20
|
+
<img src="https://img.shields.io/badge/providers-16-1a56db" alt="providers count" width="200">
|
|
21
|
+
</p>
|
|
26
22
|
|
|
27
23
|
```bash
|
|
28
24
|
npm install -g free-coding-models
|
|
29
25
|
free-coding-models
|
|
30
26
|
```
|
|
31
27
|
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
<p align="center">
|
|
29
|
+
create a free account on one of the <a href="#-list-of-free-ai-providers">providers</a>
|
|
34
30
|
</p>
|
|
35
31
|
|
|
36
32
|
<p align="center">
|
|
@@ -129,6 +125,94 @@ Use ⚡️ Command Palette! with **Ctrl+P**.
|
|
|
129
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">
|
|
130
126
|
</p>
|
|
131
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
|
+
|
|
132
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.
|
|
133
217
|
|
|
134
218
|
**② Pick a model and launch your tool:**
|
|
@@ -152,17 +236,8 @@ If the active CLI tool is missing, FCM now catches it before launch, offers a ti
|
|
|
152
236
|
### Common scenarios
|
|
153
237
|
|
|
154
238
|
```bash
|
|
155
|
-
# "I want the most reliable model right now"
|
|
156
|
-
free-coding-models --fiable
|
|
157
|
-
|
|
158
|
-
# "I want to configure Goose with an S-tier model"
|
|
159
|
-
free-coding-models --goose --tier S
|
|
160
|
-
|
|
161
|
-
# "I want NVIDIA's top models only"
|
|
162
|
-
free-coding-models --origin nvidia --tier S
|
|
163
|
-
|
|
164
239
|
# "I want the local web dashboard"
|
|
165
|
-
free-coding-models --
|
|
240
|
+
free-coding-models --daemon
|
|
166
241
|
|
|
167
242
|
# "I want one local endpoint that fails over between free models"
|
|
168
243
|
free-coding-models --daemon-bg
|
|
@@ -178,7 +253,14 @@ free-coding-models --tier S --json | jq -r '.[0].modelId'
|
|
|
178
253
|
free-coding-models --openclaw --origin groq
|
|
179
254
|
```
|
|
180
255
|
|
|
181
|
-
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` |
|
|
182
264
|
|
|
183
265
|
### Smart Model Router
|
|
184
266
|
|
|
@@ -218,9 +300,20 @@ Router endpoints:
|
|
|
218
300
|
| `GET /v1/models` | Return virtual models (`fcm`, `fcm:set-name`) |
|
|
219
301
|
| `GET /health` | Daemon status JSON |
|
|
220
302
|
| `GET /stats` | Routing, health, request log, and token stats |
|
|
221
|
-
| `GET /stream/events` | Live SSE events for
|
|
303
|
+
| `GET /stream/events` | Live SSE events for router updates |
|
|
222
304
|
| `POST /daemon/probe-mode` | Set probe mode with `{ "probeMode": "eco" | "balanced" | "aggressive" }` |
|
|
223
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
|
+
|
|
224
317
|
Routing behavior:
|
|
225
318
|
|
|
226
319
|
- Priority order works immediately on cold start, then probes refine health scores over time.
|
|
@@ -253,6 +346,8 @@ Routing behavior:
|
|
|
253
346
|
| `--pi` | π Pi |
|
|
254
347
|
| `--rovo` | 🦘 Rovo Dev CLI |
|
|
255
348
|
| `--gemini` | ♊ Gemini CLI |
|
|
349
|
+
| `--copilot` | 🤖 Copilot CLI |
|
|
350
|
+
| `--forgecode` | 🔥 ForgeCode |
|
|
256
351
|
|
|
257
352
|
Press **`Z`** in the TUI to cycle between tools without restarting.
|
|
258
353
|
|
|
@@ -507,6 +602,7 @@ Telemetry is enabled by default and can be disabled with any of the following:
|
|
|
507
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>
|
|
508
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>
|
|
509
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>
|
|
510
606
|
</tr>
|
|
511
607
|
<tr>
|
|
512
608
|
<td align="center"><a href="https://github.com/vava-nessa"><sub><b>vava-nessa</b></sub></a></td>
|
|
@@ -516,6 +612,7 @@ Telemetry is enabled by default and can be disabled with any of the following:
|
|
|
516
612
|
<td align="center"><a href="https://github.com/PhucTruong-ctrl"><sub><b>PhucTruong-ctrl</b></sub></a></td>
|
|
517
613
|
<td align="center"><a href="https://github.com/chindris-mihai-alexandru"><sub><b>chindris-mihai-alexandru</b></sub></a></td>
|
|
518
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>
|
|
519
616
|
</tr>
|
|
520
617
|
</table>
|
|
521
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",
|
|
@@ -68,10 +68,9 @@
|
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
70
|
"@vitejs/plugin-react": "^6.0.1",
|
|
71
|
-
"
|
|
72
|
-
"react": "^19.2.
|
|
73
|
-
"
|
|
74
|
-
"vite": "^8.0.5",
|
|
71
|
+
"react": "^19.2.6",
|
|
72
|
+
"react-dom": "^19.2.6",
|
|
73
|
+
"vite": "^8.0.13",
|
|
75
74
|
"vite-plus": "^0.1.16"
|
|
76
75
|
}
|
|
77
76
|
}
|
package/sources.js
CHANGED
|
@@ -46,15 +46,10 @@ export const nvidiaNim = [
|
|
|
46
46
|
['moonshotai/kimi-k2.6', 'Kimi K2.6', 'S+', '76.8%', '256k'],
|
|
47
47
|
['deepseek-ai/deepseek-v4-pro', 'DeepSeek V4 Pro', 'S+', '73.1%', '128k'],
|
|
48
48
|
['deepseek-ai/deepseek-v4-flash', 'DeepSeek V4 Flash', 'S+', '72.0%', '128k'],
|
|
49
|
-
['z-ai/
|
|
50
|
-
['moonshotai/kimi-k2-thinking', 'Kimi K2 Thinking', 'S+', '71.3%', '256k'], // ⚠️ Deprecation pending
|
|
51
|
-
['minimaxai/minimax-m2.5', 'MiniMax M2.5', 'S+', '80.2%', '200k'],
|
|
49
|
+
['z-ai/glm5', 'GLM 5', 'S+', '73.8%', '200k'],
|
|
52
50
|
['stepfun-ai/step-3.5-flash', 'Step 3.5 Flash', 'S+', '74.4%', '256k'],
|
|
53
51
|
['qwen/qwen3-coder-480b-a35b-instruct', 'Qwen3 Coder 480B', 'S+', '70.6%', '256k'],
|
|
54
|
-
['mistralai/devstral-2-123b-instruct-2512', 'Devstral 2 123B', 'S+', '72.2%', '256k'],
|
|
55
52
|
// ── S tier — SWE-bench Verified 60–70% ──
|
|
56
|
-
['moonshotai/kimi-k2-instruct-0905', 'Kimi K2 Instruct 0905', 'S', '65.8%', '256k'],
|
|
57
|
-
['moonshotai/kimi-k2-instruct', 'Kimi K2 Instruct', 'S', '65.8%', '128k'],
|
|
58
53
|
['minimaxai/minimax-m2', 'MiniMax M2', 'S', '69.4%', '128k'],
|
|
59
54
|
['qwen/qwen3-next-80b-a3b-thinking', 'Qwen3 80B Thinking', 'S', '68.0%', '128k'],
|
|
60
55
|
['qwen/qwen3-next-80b-a3b-instruct', 'Qwen3 80B Instruct', 'S', '65.0%', '128k'],
|
|
@@ -70,13 +65,9 @@ export const nvidiaNim = [
|
|
|
70
65
|
['nvidia/nemotron-3-super-120b-a12b', 'Nemotron 3 Super', 'A+', '56.0%', '128k'],
|
|
71
66
|
['nvidia/nemotron-3-nano-omni-30b-a3b-reasoning','Nemotron 3 Omni', 'A+', '52.0%', '128k'],
|
|
72
67
|
// ── A tier — SWE-bench Verified 40–50% ──
|
|
73
|
-
['mistralai/mistral-medium-3-instruct', 'Mistral Medium 3', 'A', '48.0%', '128k'],
|
|
74
|
-
['mistralai/magistral-small-2506', 'Magistral Small', 'A', '45.0%', '32k'],
|
|
75
68
|
['nvidia/llama-3.3-nemotron-super-49b-v1.5', 'Nemotron Super 49B', 'A', '49.0%', '128k'],
|
|
76
69
|
['nvidia/nemotron-3-nano-30b-a3b', 'Nemotron Nano 30B', 'A', '43.0%', '128k'],
|
|
77
70
|
['openai/gpt-oss-20b', 'GPT OSS 20B', 'A', '42.0%', '128k'],
|
|
78
|
-
['qwen/qwen2.5-coder-32b-instruct', 'Qwen2.5 Coder 32B', 'A', '46.0%', '32k'],
|
|
79
|
-
['meta/llama-3.1-405b-instruct', 'Llama 3.1 405B', 'A', '44.0%', '128k'],
|
|
80
71
|
['google/gemma-4-31b-it', 'Gemma 4 31B', 'A', '45.0%', '256k'],
|
|
81
72
|
// ── A- tier — SWE-bench Verified 35–40% ──
|
|
82
73
|
['meta/llama-3.3-70b-instruct', 'Llama 3.3 70B', 'A-', '39.5%', '128k'],
|
package/src/app.js
CHANGED
|
@@ -274,6 +274,8 @@ export async function runApp(cliArgs, config) {
|
|
|
274
274
|
pi: cliArgs.piMode,
|
|
275
275
|
rovo: cliArgs.rovoMode,
|
|
276
276
|
gemini: cliArgs.geminiMode,
|
|
277
|
+
copilot: cliArgs.copilotMode,
|
|
278
|
+
forgecode: cliArgs.forgecodeMode,
|
|
277
279
|
}
|
|
278
280
|
return flagByMode[toolMode] === true
|
|
279
281
|
})
|
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',
|
|
@@ -52,7 +52,7 @@ import { getToolMeta } from './tool-metadata.js'
|
|
|
52
52
|
const DIRECT_INSTALL_UNSUPPORTED_PROVIDERS = new Set(['replicate', 'zai', 'rovo', 'gemini', 'opencode-zen'])
|
|
53
53
|
// 📖 Install Endpoints only lists tools whose persisted config shape is actually supported here.
|
|
54
54
|
// 📖 Claude Code, Codex, and Gemini stay out while their dedicated bridges are being rebuilt.
|
|
55
|
-
const INSTALL_TARGET_MODES = ['opencode', 'opencode-desktop', 'opencode-web', 'openclaw', 'kilo', 'crush', 'goose', 'pi', 'aider', 'qwen', 'openhands', 'amp', 'hermes', 'continue', 'cline', 'fcm_router']
|
|
55
|
+
const INSTALL_TARGET_MODES = ['opencode', 'opencode-desktop', 'opencode-web', 'openclaw', 'kilo', 'crush', 'goose', 'pi', 'aider', 'qwen', 'openhands', 'amp', 'hermes', 'continue', 'cline', 'forgecode', 'fcm_router']
|
|
56
56
|
|
|
57
57
|
function getDefaultPaths() {
|
|
58
58
|
const home = homedir()
|
|
@@ -67,6 +67,7 @@ function getDefaultPaths() {
|
|
|
67
67
|
aiderConfigPath: join(home, '.aider.conf.yml'),
|
|
68
68
|
ampConfigPath: join(home, '.config', 'amp', 'settings.json'),
|
|
69
69
|
qwenConfigPath: join(home, '.qwen', 'settings.json'),
|
|
70
|
+
forgeCodeConfigPath: join(home, '.forge', '.forge.toml'),
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
73
|
|
|
@@ -526,6 +527,65 @@ function installIntoEnvBasedTool(providerKey, models, apiKey, toolMode) {
|
|
|
526
527
|
return { path: envFilePath, backupPath, providerId, modelCount: models.length }
|
|
527
528
|
}
|
|
528
529
|
|
|
530
|
+
// 📖 installIntoForgeCode: writes a managed [[providers]] block into ~/.forge/.forge.toml.
|
|
531
|
+
// 📖 ForgeCode uses TOML config with [[providers]] entries for custom OpenAI-compatible endpoints.
|
|
532
|
+
// 📖 Each provider gets one [[providers]] entry with the model catalog noted in comments.
|
|
533
|
+
// 📖 The API key is referenced via an env var so ForgeCode picks it up at runtime.
|
|
534
|
+
function installIntoForgeCode(providerKey, models, apiKey, paths) {
|
|
535
|
+
const filePath = paths.forgeCodeConfigPath
|
|
536
|
+
const providerId = getManagedProviderId(providerKey)
|
|
537
|
+
const secretEnvName = `FCM_${providerKey.toUpperCase().replace(/[^A-Z0-9]+/g, '_')}_API_KEY`
|
|
538
|
+
const baseUrl = resolveProviderBaseUrl(providerKey)
|
|
539
|
+
|
|
540
|
+
if (!baseUrl) {
|
|
541
|
+
throw new Error(`Cannot resolve base URL for ${getProviderLabel(providerKey)}`)
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// 📖 Ensure the API key is in env for ForgeCode to use
|
|
545
|
+
process.env[secretEnvName] = apiKey
|
|
546
|
+
|
|
547
|
+
const completionsUrl = baseUrl.endsWith('/chat/completions') ? baseUrl : `${baseUrl}/chat/completions`
|
|
548
|
+
|
|
549
|
+
// 📖 Read existing content
|
|
550
|
+
let content = ''
|
|
551
|
+
if (existsSync(filePath)) {
|
|
552
|
+
content = readFileSync(filePath, 'utf8')
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// 📖 Remove any previous FCM-managed provider block for this provider
|
|
556
|
+
const markerStart = `# >>> FCM managed provider: ${providerId}`
|
|
557
|
+
const markerEnd = `# <<< FCM managed provider: ${providerId}`
|
|
558
|
+
const markerRegex = new RegExp(
|
|
559
|
+
`\\n?${markerStart.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?${markerEnd.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\n?`,
|
|
560
|
+
'g'
|
|
561
|
+
)
|
|
562
|
+
content = content.replace(markerRegex, '\n')
|
|
563
|
+
|
|
564
|
+
// 📖 Build a fresh [[providers]] TOML block with model catalog comments
|
|
565
|
+
const modelComments = models.map(m => `# 📖 Model: ${m.label} (${m.modelId}) — ${m.tier}`).join('\n')
|
|
566
|
+
const providerBlock = [
|
|
567
|
+
'',
|
|
568
|
+
markerStart,
|
|
569
|
+
`# 📖 Provider: ${getManagedProviderLabel(providerKey)} (${models.length} models)`,
|
|
570
|
+
modelComments,
|
|
571
|
+
'[[providers]]',
|
|
572
|
+
`id = "${providerId}"`,
|
|
573
|
+
`url = "${completionsUrl}"`,
|
|
574
|
+
`api_key_vars = "${secretEnvName}"`,
|
|
575
|
+
'response_type = "OpenAI"',
|
|
576
|
+
'auth_methods = ["api_key"]',
|
|
577
|
+
markerEnd,
|
|
578
|
+
].join('\n')
|
|
579
|
+
|
|
580
|
+
content = content.trimEnd() + '\n' + providerBlock + '\n'
|
|
581
|
+
|
|
582
|
+
ensureDirFor(filePath)
|
|
583
|
+
const backupPath = backupIfExists(filePath)
|
|
584
|
+
writeFileSync(filePath, content)
|
|
585
|
+
|
|
586
|
+
return { path: filePath, backupPath, providerId, modelCount: models.length }
|
|
587
|
+
}
|
|
588
|
+
|
|
529
589
|
// 📖 installIntoFcmRouter: adds provider endpoints to the running FCM Router daemon
|
|
530
590
|
// 📖 via the /sets API so the router can use them for failover routing.
|
|
531
591
|
// 📖 Uses the daemon's expected schema: { provider, model, priority } per model entry.
|
|
@@ -605,6 +665,8 @@ export function installProviderEndpoints(config, providerKey, toolMode, options
|
|
|
605
665
|
installResult = installIntoEnvBasedTool(providerKey, models, apiKey, canonicalToolMode, paths)
|
|
606
666
|
} else if (canonicalToolMode === 'fcm_router') {
|
|
607
667
|
installResult = installIntoFcmRouter(providerKey, models, apiKey)
|
|
668
|
+
} else if (canonicalToolMode === 'forgecode') {
|
|
669
|
+
installResult = installIntoForgeCode(providerKey, models, apiKey, paths)
|
|
608
670
|
} else {
|
|
609
671
|
throw new Error(`Unsupported install target: ${toolMode}`)
|
|
610
672
|
}
|
package/src/key-handler.js
CHANGED
|
@@ -415,7 +415,7 @@ export function createKeyHandler(ctx) {
|
|
|
415
415
|
|
|
416
416
|
async function launchSelectedModel(selected, options = {}) {
|
|
417
417
|
const { uiAlreadyStopped = false } = options
|
|
418
|
-
userSelected = { modelId: selected.modelId, label: selected.label, tier: selected.tier, providerKey: selected.providerKey }
|
|
418
|
+
userSelected = { modelId: selected.modelId, label: selected.label, tier: selected.tier, providerKey: selected.providerKey, ctx: selected.ctx }
|
|
419
419
|
|
|
420
420
|
if (!uiAlreadyStopped) {
|
|
421
421
|
readline.emitKeypressEvents(process.stdin)
|