free-coding-models 0.2.17 → 0.3.1
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 +71 -0
- package/README.md +118 -44
- package/bin/fcm-proxy-daemon.js +239 -0
- package/bin/free-coding-models.js +146 -37
- package/package.json +3 -2
- package/src/account-manager.js +34 -0
- package/src/anthropic-translator.js +440 -0
- package/src/cli-help.js +108 -0
- package/src/config.js +25 -1
- package/src/daemon-manager.js +527 -0
- package/src/endpoint-installer.js +45 -19
- package/src/key-handler.js +324 -148
- package/src/opencode.js +47 -44
- package/src/overlays.js +282 -207
- package/src/proxy-server.js +746 -10
- package/src/proxy-sync.js +564 -0
- package/src/proxy-topology.js +80 -0
- package/src/render-helpers.js +4 -2
- package/src/render-table.js +56 -49
- package/src/responses-translator.js +423 -0
- package/src/tool-launchers.js +343 -26
- package/src/utils.js +31 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,77 @@
|
|
|
2
2
|
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
+
## 0.3.1
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **CLI `--help` output**: `free-coding-models --help` now prints the full launcher, analysis, config, and daemon command matrix in a non-interactive format.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **Outdated-version footer alert**: The main TUI now shows a full-width red footer line with manual `npm install -g free-coding-models@latest` recovery instructions, but only when a newer npm version is actually known.
|
|
12
|
+
- **Claude Code proxy auth conflict**: Proxy launches now sanitize inherited env vars and use only `ANTHROPIC_AUTH_TOKEN` + `ANTHROPIC_BASE_URL`, matching the `free-claude-code` contract instead of mixing Anthropic auth modes.
|
|
13
|
+
- **Codex CLI proxy routing**: Codex launches now force an explicit custom provider config and the proxy now supports `POST /v1/responses`, so `codex-cli 0.114.0` no longer depends on the broken built-in OAuth/base-url path.
|
|
14
|
+
- **Anthropic token counting**: Added `POST /v1/messages/count_tokens` with a fast local estimate so Claude-compatible clients keep their budgeting flow through FCM Proxy V2.
|
|
15
|
+
- **Gemini proxy failure mode**: Gemini launch now preflights the installed CLI/config, blocks incompatible builds like `0.33.0`, and surfaces `~/.gemini/settings.json` schema errors instead of pretending proxy mode works.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- **Proxy auto-sync now follows the current tool**: The proxy overlay no longer asks for a separate active tool; cleanup and auto-sync now target the current `Z` mode whenever that tool supports persisted proxy config.
|
|
19
|
+
- **Install Endpoints (`Y`) is narrower on purpose**: `Claude Code`, `Codex`, and `Gemini` were removed from the install-target menu so the flow only lists tools with a stable persisted-config contract.
|
|
20
|
+
- **Proxy model listing is more Codex-friendly**: `GET /v1/models` now returns both the usual OpenAI `data` array and a `models` array with `slug` fields for clients that expect a richer catalog shape.
|
|
21
|
+
- **Launcher diagnostics now mention the beta state clearly**: Proxy-backed external tools now remind users that the integration is still stabilizing when a launch is blocked.
|
|
22
|
+
|
|
23
|
+
## 0.3.0
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- **Always-on background proxy service**: FCM Proxy V2 can run as a persistent background service via `launchd` (macOS) or `systemd` (Linux). All tools get free model access 24/7 — no need to keep the TUI open.
|
|
27
|
+
- **Anthropic wire format translation**: Native bidirectional translation between Anthropic Messages API (`POST /v1/messages`) and OpenAI Chat Completions. Claude Code works natively through FCM Proxy V2.
|
|
28
|
+
- **Dedicated FCM Proxy V2 overlay**: New full-page overlay (from Settings → "FCM Proxy V2 settings →" or `J` key) with proxy config, service status, restart, stop, force-kill, and log viewer.
|
|
29
|
+
- **`J` key — FCM Proxy V2 shortcut**: Opens the proxy settings directly from the main view. Footer shows a green `📡 FCM Proxy V2 On` badge when active, red `📡 FCM Proxy V2 Off` when disabled.
|
|
30
|
+
- **CLI daemon subcommand**: `free-coding-models daemon [status|install|uninstall|restart|stop|logs]` for headless service control.
|
|
31
|
+
- **Stable proxy identity**: Persistent token and preferred port (`18045`) survive daemon restarts — env files and tool configs remain valid across reboots.
|
|
32
|
+
- **Health endpoint**: `GET /v1/health` returns uptime, version, and account/model counts for liveness probes.
|
|
33
|
+
- **`GET /v1/stats` endpoint**: Authenticated endpoint returning per-account health, token stats, totals, and proxy uptime for monitoring and debugging.
|
|
34
|
+
- **Hot-reload**: FCM Proxy V2 watches `~/.free-coding-models.json` and reloads proxy topology automatically when config changes.
|
|
35
|
+
- **Version mismatch detection**: The overlay warns when the running service version differs from the installed FCM version.
|
|
36
|
+
- **Dev environment guard**: `installDaemon()` is blocked when running from a git checkout to prevent hardcoding local repo paths in OS service files.
|
|
37
|
+
- **Generalized proxy sync module** (`src/proxy-sync.js`): Single-endpoint proxy config sync for 12 tools (OpenCode, OpenClaw, Crush, Goose, Pi, Aider, Amp, Qwen, Claude Code, Codex, OpenHands). Writes one `fcm-proxy` provider with all models, cleans up old per-provider `fcm-*` vestiges.
|
|
38
|
+
- **Retry backoff with jitter**: Progressive delays between retries (0ms, 300ms, 800ms + random jitter) to avoid re-hitting the same rate-limit window on 429s.
|
|
39
|
+
- **Automatic account cooldown on consecutive failures**: When an account accumulates 3+ consecutive non-429 failures, it enters graduated cooldown (30s → 60s → 120s). Proxy routes around failing accounts automatically. Resets on success.
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
- **Rebranded to FCM Proxy V2**: All user-facing references to "daemon", "FCM Proxy", and "Proxy & Daemon" renamed to "FCM Proxy V2" across CLI messages, TUI overlays, endpoint installer, and service descriptions.
|
|
43
|
+
- **Proxy overlay generalized for all tools**: "Persist proxy in OpenCode" → "Auto-sync proxy to {tool}", "Clean OpenCode proxy config" → "Clean {tool} proxy config". New "Active tool" selector row cycles through all 12 proxy-syncable tools.
|
|
44
|
+
- **Feedback overlay redesigned**: Renamed "Report bug" to "Feedback, bugs & requests", input is now left-aligned with a visible cursor, framed by horizontal separator lines. `I` key now covers all feedback types.
|
|
45
|
+
- **Proxy settings moved to dedicated overlay**: The 5 proxy rows in Settings are replaced by a single entry that opens a full-page manager.
|
|
46
|
+
- **Proxy topology extracted to shared module**: `src/proxy-topology.js` is now used by both TUI and daemon, eliminating code duplication.
|
|
47
|
+
- **TUI delegates to background service**: `ensureProxyRunning()` checks for a running service first and reuses its port/token instead of starting an in-process proxy.
|
|
48
|
+
- **Endpoint installer supports proxy mode**: When installing endpoints (Y key) with "FCM Proxy V2" connection mode, env files point to the service's stable token/port.
|
|
49
|
+
- **Claude Code / Codex / Gemini require proxy**: These tools now refuse to launch without FCM Proxy V2 enabled, showing clear instructions to enable it. When proxy is on, they route through it automatically.
|
|
50
|
+
- **Goose launcher rewritten**: Now writes proper custom provider JSON + updates `config.yaml` with `GOOSE_PROVIDER`/`GOOSE_MODEL` for guaranteed auto-selection (replaces obsolete `OPENAI_HOST`/`OPENAI_MODEL` env vars).
|
|
51
|
+
- **Crush launcher improved**: Removed `disable_default_providers` flag, sets both `models.large` and `models.small` defaults for reliable auto-selection.
|
|
52
|
+
- **Pi launcher improved**: Now passes `--provider` and `--model` CLI flags for guaranteed model pre-selection.
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
- **Terminal width warning behavior**: Warning now shows max 2 times per session with emojis and double spacing.
|
|
56
|
+
- **Body size limit** (security): `readBody()` now enforces a 10 MB limit. Oversized payloads receive 413.
|
|
57
|
+
- **Stack trace leak prevention** (security): Error responses no longer include `err.message`.
|
|
58
|
+
- **SSE buffer overflow guard** (security): Anthropic SSE transformer limits buffer to 1 MB.
|
|
59
|
+
- **`new URL()` crash protection**: Malformed upstream URLs caught instead of crashing.
|
|
60
|
+
- **`execSync` timeout safety**: All `execSync` calls in daemon-manager use a 15-second timeout via `execSyncSafe()`.
|
|
61
|
+
- **Daemon startup crash protection**: `loadConfig()`, `buildMergedModelsForDaemon()`, and `buildProxyTopologyFromConfig()` wrapped in try/catch.
|
|
62
|
+
- **`resolveCloudflareUrl()` null guard**: Empty provider URLs skipped instead of crashing.
|
|
63
|
+
- **Health check buffer limit**: Responses capped at 64 KB.
|
|
64
|
+
- **SSE line buffering**: SSE tap now correctly handles lines split across chunk boundaries.
|
|
65
|
+
- **Empty choices fallback**: `translateOpenAIToAnthropic` returns fallback content block when OpenAI response has empty choices.
|
|
66
|
+
- **Tool calls streaming index tracking**: Proper `nextBlockIndex`/`currentBlockIndex` counters for correct indexing across multiple tool calls.
|
|
67
|
+
- **Pipe error propagation**: Error handlers on both sides of SSE pipes to prevent uncaught errors on mid-stream disconnects.
|
|
68
|
+
- **Input validation**: `translateAnthropicToOpenAI` guards against null/undefined/non-object input.
|
|
69
|
+
- **Hot-reload race condition**: Config watcher uses `reloadInProgress` flag to prevent concurrent reloads.
|
|
70
|
+
- **Fake response stubs**: Added `destroy()`, `removeListener()`, and `socket: null` for better compatibility.
|
|
71
|
+
- **API key trimming**: Whitespace-trimmed and empty keys filtered out in topology builder.
|
|
72
|
+
- **`writeFileSync` error messages**: Plist and systemd service file write failures now throw clear error messages.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
5
76
|
## 0.2.17
|
|
6
77
|
|
|
7
78
|
### Added
|
package/README.md
CHANGED
|
@@ -46,6 +46,9 @@ By Vanessa Depraute
|
|
|
46
46
|
<sub>Ping free coding models from 20 providers in real-time — pick the best one for OpenCode, OpenClaw, or any AI coding assistant</sub>
|
|
47
47
|
</p>
|
|
48
48
|
|
|
49
|
+
> ⚠️ **Beta notice**
|
|
50
|
+
> FCM Proxy V2 support for external tools is still in beta. Claude Code, Codex, Gemini, and the other proxy-backed launchers already work in many setups, but auth and startup edge cases can still fail while the integration stabilizes.
|
|
51
|
+
|
|
49
52
|
<p align="center">
|
|
50
53
|
<img src="demo.gif" alt="free-coding-models demo" width="100%">
|
|
51
54
|
</p>
|
|
@@ -69,8 +72,8 @@ By Vanessa Depraute
|
|
|
69
72
|
|
|
70
73
|
- **🎯 Coding-focused** — Only LLM models optimized for code generation, not chat or vision
|
|
71
74
|
- **🌐 Multi-provider** — Models from NVIDIA NIM, Groq, Cerebras, SambaNova, OpenRouter, Hugging Face Inference, Replicate, DeepInfra, Fireworks AI, Codestral, Hyperbolic, Scaleway, Google AI, SiliconFlow, Together AI, Cloudflare Workers AI, Perplexity API, Alibaba Cloud (DashScope), ZAI, and iFlow
|
|
72
|
-
- **⚙️ Settings screen** — Press `P` to manage provider API keys, enable/disable providers,
|
|
73
|
-
-
|
|
75
|
+
- **⚙️ Settings screen** — Press `P` to manage provider API keys, enable/disable providers, access FCM Proxy V2 settings, and check/install updates
|
|
76
|
+
- **📡 FCM Proxy V2** — Built-in reverse proxy with multi-key rotation, rate-limit failover, and Anthropic wire format translation for Claude Code. Optional always-on background service (`launchd`/`systemd`) keeps the proxy running 24/7 — even without the TUI. Dedicated overlay with full status, restart, stop, force-kill, and log viewer.
|
|
74
77
|
- **🚀 Parallel pings** — All models tested simultaneously via native `fetch`
|
|
75
78
|
- **📊 Real-time animation** — Watch latency appear live in alternate screen buffer
|
|
76
79
|
- **🏆 Smart ranking** — Top 3 fastest models highlighted with medals 🥇🥈🥉
|
|
@@ -87,7 +90,7 @@ By Vanessa Depraute
|
|
|
87
90
|
- **💻 OpenCode integration** — Auto-detects NIM setup, sets model as default, launches OpenCode
|
|
88
91
|
- **🦞 OpenClaw integration** — Sets selected model as default provider in `~/.openclaw/openclaw.json`
|
|
89
92
|
- **🧰 Public tool launchers** — `Enter` auto-configures and launches 10+ tools: `OpenCode CLI`, `OpenCode Desktop`, `OpenClaw`, `Crush`, `Goose`, `Aider`, `Claude Code`, `Codex`, `Gemini`, `Qwen`, `OpenHands`, `Amp`, and `Pi`. All tools auto-select the chosen model on launch.
|
|
90
|
-
- **🔌 Install Endpoints flow** — Press `Y` to install one configured provider into
|
|
93
|
+
- **🔌 Install Endpoints flow** — Press `Y` to install one configured provider into the compatible persisted-config tools, with a choice between **Direct Provider** (pure API) or **FCM Proxy V2** (key rotation + usage tracking), then pick all models or a curated subset
|
|
91
94
|
- **📝 Feature Request (J key)** — Send anonymous feedback directly to the project team
|
|
92
95
|
- **🐛 Bug Report (I key)** — Send anonymous bug reports directly to the project team
|
|
93
96
|
- **🎨 Clean output** — Zero scrollback pollution, interface stays open until Ctrl+C
|
|
@@ -179,15 +182,13 @@ bunx free-coding-models YOUR_API_KEY
|
|
|
179
182
|
|
|
180
183
|
### 🆕 What's New
|
|
181
184
|
|
|
182
|
-
**Version 0.
|
|
183
|
-
|
|
184
|
-
- **`--json` flag** — Output model results as JSON for scripting, CI/CD pipelines, and monitoring dashboards. Perfect for automation: `free-coding-models --tier S --json | jq '.[0].modelId'`
|
|
185
|
-
|
|
186
|
-
- **Persistent ping cache** — Results are cached for 5 minutes between runs. Startup is nearly instant when cache is fresh, and you save API rate limits. Cache file: `~/.free-coding-models.cache.json`
|
|
187
|
-
|
|
188
|
-
- **Config security check** — Automatically warns if your API key config file has insecure permissions and offers one-click auto-fix with `chmod 600`
|
|
185
|
+
**Version 0.3.1 tightens the proxy/tooling path and ships the missing diagnostics:**
|
|
189
186
|
|
|
190
|
-
- **
|
|
187
|
+
- **Claude Code proxy launches are cleaner** — FCM now launches Claude Code with an Anthropic-only proxy contract (`ANTHROPIC_BASE_URL` + `ANTHROPIC_AUTH_TOKEN`) instead of mixing auth modes.
|
|
188
|
+
- **Codex proxy launches now use the right API path** — Codex is forced into an explicit custom provider config and the proxy now implements `POST /v1/responses`.
|
|
189
|
+
- **Gemini proxy launches fail fast when unsupported** — Older Gemini CLI builds and invalid local config are detected up front, with a clear message instead of a misleading broken launch.
|
|
190
|
+
- **Proxy auto-sync follows the current tool** — The FCM Proxy V2 overlay no longer relies on a separate active-tool picker, and `Y` now lists only stable persisted-config install targets.
|
|
191
|
+
- **Beta messaging is explicit** — The README and runtime launcher diagnostics now call out that proxy-backed external tool support is still stabilizing.
|
|
191
192
|
|
|
192
193
|
---
|
|
193
194
|
|
|
@@ -216,11 +217,14 @@ free-coding-models --best
|
|
|
216
217
|
# Analyze for 10 seconds and output the most reliable model
|
|
217
218
|
free-coding-models --fiable
|
|
218
219
|
|
|
219
|
-
|
|
220
|
+
# Output results as JSON (for scripting/automation)
|
|
220
221
|
free-coding-models --json
|
|
221
222
|
free-coding-models --tier S --json | jq '.[0].modelId' # Get fastest S-tier model ID
|
|
222
223
|
free-coding-models --json | jq '.[] | select(.avgPing < 500)' # Filter by latency
|
|
223
224
|
|
|
225
|
+
# Print the complete CLI help with every supported flag and daemon command
|
|
226
|
+
free-coding-models --help
|
|
227
|
+
|
|
224
228
|
# Filter models by tier letter
|
|
225
229
|
free-coding-models --tier S # S+ and S only
|
|
226
230
|
free-coding-models --tier A # A+, A, A- only
|
|
@@ -315,6 +319,7 @@ Press **`P`** to open the Settings screen at any time:
|
|
|
315
319
|
Keys are saved to `~/.free-coding-models.json` (permissions `0600`).
|
|
316
320
|
|
|
317
321
|
Manual update is in the same Settings screen (`P`) under **Maintenance** (Enter to check, Enter again to install when an update is available).
|
|
322
|
+
When a newer npm release is known, the main footer also adds a full-width red warning line with the manual recovery command `npm install -g free-coding-models@latest`.
|
|
318
323
|
Favorites are also persisted in the same config file and survive restarts.
|
|
319
324
|
The main table now starts in `Configured Only` mode, so if nothing is set up yet you can press `P` and add your first API key immediately.
|
|
320
325
|
|
|
@@ -554,31 +559,92 @@ Stability = 0.30 × p95_score
|
|
|
554
559
|
|
|
555
560
|
---
|
|
556
561
|
|
|
557
|
-
##
|
|
562
|
+
## 📡 FCM Proxy V2
|
|
563
|
+
|
|
564
|
+
`free-coding-models` includes a local reverse proxy that merges all your provider API keys into one endpoint. Optional background service mode keeps it running 24/7 — even without the TUI.
|
|
565
|
+
|
|
566
|
+
> **Disabled by default** — enable in Settings (`P`) → FCM Proxy V2 settings.
|
|
567
|
+
|
|
568
|
+
### What the proxy does
|
|
569
|
+
|
|
570
|
+
| Feature | Description |
|
|
571
|
+
|---------|-------------|
|
|
572
|
+
| **Unified endpoint** | One URL (`http://127.0.0.1:18045/v1`) replaces 20+ provider endpoints |
|
|
573
|
+
| **Key rotation** | Automatically swaps to the next API key when one hits rate limits (429) |
|
|
574
|
+
| **Usage tracking** | Tracks token consumption per provider/model pair in real-time |
|
|
575
|
+
| **Anthropic translation** | Claude Code sends `POST /v1/messages` — the proxy translates to OpenAI format upstream |
|
|
576
|
+
| **Path normalization** | Converts non-standard API paths (ZAI, Cloudflare) to standard `/v1/` calls |
|
|
577
|
+
|
|
578
|
+
### In-process vs Background Service mode
|
|
579
|
+
|
|
580
|
+
| | In-process (default) | Background Service (always-on) |
|
|
581
|
+
|---|---|---|
|
|
582
|
+
| **Lifetime** | Starts/stops with TUI | Survives reboots |
|
|
583
|
+
| **Use case** | Quick sessions | 24/7 access from any tool |
|
|
584
|
+
| **Setup** | Toggle in Settings | One-time install via TUI or CLI |
|
|
585
|
+
| **Port** | Random or configured | Stable (`18045` by default) |
|
|
586
|
+
| **Token** | New each session | Persistent (env files stay valid) |
|
|
587
|
+
|
|
588
|
+
### Quick setup
|
|
589
|
+
|
|
590
|
+
**Via TUI (recommended):**
|
|
591
|
+
1. Press `P` to open Settings
|
|
592
|
+
2. Select **FCM Proxy V2 settings →** and press Enter
|
|
593
|
+
3. Enable **Proxy mode**, then select **Install background service**
|
|
558
594
|
|
|
559
|
-
|
|
595
|
+
**Via CLI:**
|
|
596
|
+
```bash
|
|
597
|
+
free-coding-models daemon install # Install + start as OS service
|
|
598
|
+
free-coding-models daemon status # Check running status
|
|
599
|
+
free-coding-models daemon restart # Restart after config changes
|
|
600
|
+
free-coding-models daemon stop # Graceful stop (SIGTERM)
|
|
601
|
+
free-coding-models daemon uninstall # Remove OS service completely
|
|
602
|
+
free-coding-models daemon logs # Show recent service logs
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
### Service management
|
|
606
|
+
|
|
607
|
+
The dedicated **FCM Proxy V2** overlay (accessible via `J` from main TUI, or Settings → Enter) provides full control:
|
|
608
|
+
|
|
609
|
+
- **Current tool hint** — Shows which Z-selected tool will receive persisted proxy config (when that mode supports it)
|
|
610
|
+
- **Auto-sync toggle** — Automatically write the `fcm-proxy` provider to the current tool's config when the proxy starts
|
|
611
|
+
- **Cleanup** — Remove `fcm-proxy` entries from the current tool's config
|
|
612
|
+
- **Status display** — Running/Stopped/Stale/Unhealthy with PID, port, uptime, account/model counts
|
|
613
|
+
- **Version mismatch detection** — warns if service version differs from installed FCM version
|
|
614
|
+
- **Restart** — stop + start via the OS service manager
|
|
615
|
+
- **Stop** — graceful SIGTERM (service may auto-restart if installed)
|
|
616
|
+
- **Force kill** — emergency SIGKILL for stuck processes
|
|
617
|
+
- **View logs** — last 50 lines from `~/.free-coding-models/daemon-stdout.log`
|
|
618
|
+
|
|
619
|
+
### Platform support
|
|
620
|
+
|
|
621
|
+
| Platform | Service type | Config path |
|
|
622
|
+
|----------|-------------|-------------|
|
|
623
|
+
| macOS | `launchd` LaunchAgent | `~/Library/LaunchAgents/com.fcm.proxy.plist` |
|
|
624
|
+
| Linux | `systemd` user service | `~/.config/systemd/user/fcm-proxy.service` |
|
|
625
|
+
| Windows | Not supported | Falls back to in-process proxy |
|
|
560
626
|
|
|
561
|
-
|
|
562
|
-
- **Disabled by default** — proxy mode is now opt-in from the Settings screen (`P`)
|
|
563
|
-
- **Direct OpenCode launch remains the default** when proxy mode is off
|
|
564
|
-
- **Token/request logs are only populated by proxied requests** today
|
|
627
|
+
### Config files
|
|
565
628
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
-
|
|
569
|
-
-
|
|
570
|
-
|
|
629
|
+
| File | Purpose |
|
|
630
|
+
|------|---------|
|
|
631
|
+
| `~/.free-coding-models.json` | API keys, proxy settings, service consent |
|
|
632
|
+
| `~/.free-coding-models/daemon.json` | Status file (PID, port, token) — written by the background service |
|
|
633
|
+
| `~/.free-coding-models/daemon-stdout.log` | Service output log |
|
|
571
634
|
|
|
572
|
-
|
|
573
|
-
1. Open **Settings** with `P`
|
|
574
|
-
2. Enable **Proxy mode (opt-in)**
|
|
575
|
-
3. Optionally enable **Persist proxy in OpenCode** if you explicitly want `fcm-proxy` written to `~/.config/opencode/opencode.json`
|
|
576
|
-
4. Optionally set **Preferred proxy port** (`0` = auto)
|
|
577
|
-
5. Use `S` in Settings to sync the proxy catalog into OpenCode only when you actually want that persistent config
|
|
635
|
+
The `proxy.activeTool` field is now legacy-only. FCM Proxy V2 follows the current **Z-selected** tool automatically whenever that mode supports persisted proxy sync.
|
|
578
636
|
|
|
579
|
-
Cleanup
|
|
580
|
-
|
|
581
|
-
-
|
|
637
|
+
### Cleanup
|
|
638
|
+
|
|
639
|
+
- From the FCM Proxy V2 overlay: **Clean {tool} proxy config** — removes `fcm-proxy` entries from whichever tool is currently selected
|
|
640
|
+
- Or: `free-coding-models --clean-proxy`
|
|
641
|
+
|
|
642
|
+
### Safety
|
|
643
|
+
|
|
644
|
+
- **Dev guard**: `installDaemon()` is blocked when running from a git checkout — prevents hardcoding local repo paths in OS service files
|
|
645
|
+
- **Localhost only**: The proxy listens on `127.0.0.1`, never exposed to the network
|
|
646
|
+
- **Consent required**: Service installation requires explicit user action — never auto-installs
|
|
647
|
+
- **Hot-reload**: Config changes are picked up automatically without restarting the service
|
|
582
648
|
|
|
583
649
|
---
|
|
584
650
|
|
|
@@ -605,11 +671,11 @@ You can use `free-coding-models` with 12+ AI coding tools. When you select a mod
|
|
|
605
671
|
| OpenCode Desktop | `--opencode-desktop` | Opens Desktop app |
|
|
606
672
|
| OpenClaw | `--openclaw` | ~/.openclaw/openclaw.json |
|
|
607
673
|
| Crush | `--crush` | ~/.config/crush/crush.json |
|
|
608
|
-
| Goose | `--goose` |
|
|
674
|
+
| Goose | `--goose` | ~/.config/goose/config.yaml + custom_providers/ |
|
|
609
675
|
| **Aider** | `--aider` | ~/.aider.conf.yml |
|
|
610
|
-
| **Claude Code** | `--claude-code` |
|
|
611
|
-
| **Codex** | `--codex` |
|
|
612
|
-
| **Gemini** | `--gemini` |
|
|
676
|
+
| **Claude Code** ⚡ | `--claude-code` | Requires FCM Proxy V2 |
|
|
677
|
+
| **Codex** ⚡ | `--codex` | Requires FCM Proxy V2 |
|
|
678
|
+
| **Gemini** ⚡ | `--gemini` | Requires FCM Proxy V2 |
|
|
613
679
|
| **Qwen** | `--qwen` | ~/.qwen/settings.json |
|
|
614
680
|
| **OpenHands** | `--openhands` | LLM_MODEL env var |
|
|
615
681
|
| **Amp** | `--amp` | ~/.config/amp/settings.json |
|
|
@@ -617,7 +683,13 @@ You can use `free-coding-models` with 12+ AI coding tools. When you select a mod
|
|
|
617
683
|
|
|
618
684
|
Press **Z** to cycle through all 13 tool modes in the TUI, or use flags to start in your preferred mode.
|
|
619
685
|
|
|
620
|
-
|
|
686
|
+
⚡ = Requires FCM Proxy V2 background service (press `J` to enable). These tools cannot connect to free providers without the proxy.
|
|
687
|
+
|
|
688
|
+
Proxy-backed external tool support is still beta. Expect occasional launch/auth rough edges while third-party CLI contracts are still settling.
|
|
689
|
+
|
|
690
|
+
`Codex` is launched through an explicit custom provider config so it stays in API-key mode through the proxy. `Gemini` proxy launches are version-gated: older builds like `0.33.0` are blocked with a clear diagnostic instead of being misconfigured silently.
|
|
691
|
+
|
|
692
|
+
The **Install Endpoints** flow (`Y` key) now targets only the tools with a stable persisted config contract. `Claude Code`, `Codex`, and `Gemini` stay launcher-only and should be started directly from FCM.
|
|
621
693
|
|
|
622
694
|
---
|
|
623
695
|
|
|
@@ -930,13 +1002,14 @@ This script:
|
|
|
930
1002
|
- **X** — Toggle request logs (recent proxied request/token usage logs, up to 500 entries)
|
|
931
1003
|
- **A (in logs)** — Toggle between showing 500 entries or ALL logs
|
|
932
1004
|
- **P** — Open Settings (manage API keys, toggles, updates, profiles)
|
|
933
|
-
- **Y** — Open Install Endpoints (`provider → tool → connection mode → scope → models`, Direct or FCM Proxy)
|
|
1005
|
+
- **Y** — Open Install Endpoints (`provider → tool → connection mode → scope → models`, Direct or FCM Proxy V2)
|
|
934
1006
|
- **Shift+P** — Cycle through saved profiles (switches live TUI settings)
|
|
935
1007
|
- **Shift+S** — Save current TUI settings as a named profile (inline prompt)
|
|
936
1008
|
- **Q** — Open Smart Recommend overlay (find the best model for your task)
|
|
937
1009
|
- **N** — Open Changelog overlay (browse index of all versions, `Enter` to view details, `B` to go back)
|
|
938
1010
|
- **W** — Cycle ping mode (`FAST` 2s → `NORMAL` 10s → `SLOW` 30s → `FORCED` 4s)
|
|
939
|
-
- **J
|
|
1011
|
+
- **J** — Open FCM Proxy V2 settings (shows green "Proxy On" / red "Proxy Off" badge in footer)
|
|
1012
|
+
- **I** — Feedback, bugs & requests
|
|
940
1013
|
- **K / Esc** — Show help overlay / Close overlay
|
|
941
1014
|
- **Ctrl+C** — Exit
|
|
942
1015
|
|
|
@@ -947,12 +1020,12 @@ Pressing **K** now shows a full in-app reference: main hotkeys, settings hotkeys
|
|
|
947
1020
|
`Y` opens a dedicated install flow for configured providers. The 5-step flow is:
|
|
948
1021
|
|
|
949
1022
|
1. **Provider** — Pick one provider that already has an API key in Settings
|
|
950
|
-
2. **Tool** — Pick the target tool from
|
|
951
|
-
- Config-based: `OpenCode CLI`, `OpenCode Desktop`, `OpenClaw`, `Crush`, `Goose`, `Pi`, `Aider`, `Amp`, `
|
|
952
|
-
- Env-file based: `
|
|
1023
|
+
2. **Tool** — Pick the target tool from the compatible install targets:
|
|
1024
|
+
- Config-based: `OpenCode CLI`, `OpenCode Desktop`, `OpenClaw`, `Crush`, `Goose`, `Pi`, `Aider`, `Amp`, `Qwen`
|
|
1025
|
+
- Env-file based: `OpenHands` (writes `~/.fcm-openhands-env` — source it before launching)
|
|
953
1026
|
3. **Connection Mode** — Choose how the tool connects to the provider:
|
|
954
1027
|
- **⚡ Direct Provider** — pure API connection, no proxy involved
|
|
955
|
-
- **🔄 FCM Proxy** — route through
|
|
1028
|
+
- **🔄 FCM Proxy V2** — route through FCM Proxy V2 with key rotation and usage tracking
|
|
956
1029
|
4. **Scope** — Choose `Install all models` or `Install selected models only`
|
|
957
1030
|
5. **Models** (if scope = selected) — Multi-select individual models from the provider catalog
|
|
958
1031
|
|
|
@@ -962,7 +1035,8 @@ Important behavior:
|
|
|
962
1035
|
- `Install all models` is the recommended path because FCM can refresh that catalog automatically on later launches when the provider model list changes
|
|
963
1036
|
- `Install selected models only` is useful when you want a smaller curated picker inside the target tool
|
|
964
1037
|
- `OpenCode CLI` and `OpenCode Desktop` share the same `opencode.json`, so the managed provider appears in both
|
|
965
|
-
-
|
|
1038
|
+
- `Claude Code`, `Codex`, and `Gemini` are launcher-only in this flow for now. Use the normal `Enter` launcher path so FCM can apply the right proxy/runtime contract automatically.
|
|
1039
|
+
- For env-based install targets like `OpenHands`, FCM writes a sourceable helper file at `~/.fcm-{tool}-env`
|
|
966
1040
|
|
|
967
1041
|
**Keyboard shortcuts (Settings screen — `P` key):**
|
|
968
1042
|
- **↑↓** — Navigate providers, maintenance row, and profile rows
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file bin/fcm-proxy-daemon.js
|
|
5
|
+
* @description Standalone headless FCM proxy daemon — runs independently of the TUI.
|
|
6
|
+
*
|
|
7
|
+
* 📖 This is the always-on background proxy server. It reads the user's config
|
|
8
|
+
* (~/.free-coding-models.json), builds the proxy topology (merged models × API keys),
|
|
9
|
+
* and starts a ProxyServer on a stable port with a stable token.
|
|
10
|
+
*
|
|
11
|
+
* 📖 When installed as a launchd LaunchAgent (macOS) or systemd user service (Linux),
|
|
12
|
+
* this daemon starts at login and persists across reboots, allowing Claude Code,
|
|
13
|
+
* Gemini CLI, OpenCode, and all other tools to access free models 24/7.
|
|
14
|
+
*
|
|
15
|
+
* 📖 Status file: ~/.free-coding-models/daemon.json
|
|
16
|
+
* Contains PID, port, token, version, model/account counts. The TUI reads this
|
|
17
|
+
* to detect a running daemon and delegate instead of starting an in-process proxy.
|
|
18
|
+
*
|
|
19
|
+
* 📖 Hot-reload: Watches ~/.free-coding-models.json for changes and reloads the
|
|
20
|
+
* proxy topology (accounts, models) without restarting the process.
|
|
21
|
+
*
|
|
22
|
+
* @see src/proxy-topology.js — shared topology builder
|
|
23
|
+
* @see src/proxy-server.js — ProxyServer implementation
|
|
24
|
+
* @see src/daemon-manager.js — install/uninstall/status management
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync, watch } from 'node:fs'
|
|
28
|
+
import { join } from 'node:path'
|
|
29
|
+
import { homedir } from 'node:os'
|
|
30
|
+
import { createRequire } from 'node:module'
|
|
31
|
+
import { fileURLToPath } from 'node:url'
|
|
32
|
+
|
|
33
|
+
// 📖 Resolve package.json for version info
|
|
34
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
|
35
|
+
let PKG_VERSION = 'unknown'
|
|
36
|
+
try {
|
|
37
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'))
|
|
38
|
+
PKG_VERSION = pkg.version || 'unknown'
|
|
39
|
+
} catch { /* ignore */ }
|
|
40
|
+
|
|
41
|
+
// 📖 Config + data paths
|
|
42
|
+
const CONFIG_PATH = join(homedir(), '.free-coding-models.json')
|
|
43
|
+
const DATA_DIR = join(homedir(), '.free-coding-models')
|
|
44
|
+
const DAEMON_STATUS_FILE = join(DATA_DIR, 'daemon.json')
|
|
45
|
+
const LOG_PREFIX = '[fcm-daemon]'
|
|
46
|
+
|
|
47
|
+
// 📖 Default daemon port — high port unlikely to conflict
|
|
48
|
+
const DEFAULT_DAEMON_PORT = 18045
|
|
49
|
+
|
|
50
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
function log(msg) {
|
|
53
|
+
console.log(`${LOG_PREFIX} ${new Date().toISOString()} ${msg}`)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function logError(msg) {
|
|
57
|
+
console.error(`${LOG_PREFIX} ${new Date().toISOString()} ERROR: ${msg}`)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 📖 Write daemon status file so TUI and tools can discover the running daemon.
|
|
62
|
+
*/
|
|
63
|
+
function writeDaemonStatus(info) {
|
|
64
|
+
if (!existsSync(DATA_DIR)) {
|
|
65
|
+
mkdirSync(DATA_DIR, { mode: 0o700, recursive: true })
|
|
66
|
+
}
|
|
67
|
+
writeFileSync(DAEMON_STATUS_FILE, JSON.stringify(info, null, 2), { mode: 0o600 })
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 📖 Remove daemon status file on shutdown.
|
|
72
|
+
*/
|
|
73
|
+
function removeDaemonStatus() {
|
|
74
|
+
try {
|
|
75
|
+
if (existsSync(DAEMON_STATUS_FILE)) unlinkSync(DAEMON_STATUS_FILE)
|
|
76
|
+
} catch { /* best-effort */ }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ─── Main ────────────────────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
async function main() {
|
|
82
|
+
log(`Starting FCM Proxy V2 v${PKG_VERSION} (PID: ${process.pid})`)
|
|
83
|
+
|
|
84
|
+
// 📖 Dynamic imports — keep startup fast, avoid loading TUI-specific modules
|
|
85
|
+
const { loadConfig, getProxySettings } = await import('../src/config.js')
|
|
86
|
+
const { ProxyServer } = await import('../src/proxy-server.js')
|
|
87
|
+
const { buildProxyTopologyFromConfig, buildMergedModelsForDaemon } = await import('../src/proxy-topology.js')
|
|
88
|
+
const { sources } = await import('../sources.js')
|
|
89
|
+
|
|
90
|
+
// 📖 Load config and build initial topology — wrapped in try/catch to provide clear error on startup failures
|
|
91
|
+
let fcmConfig, proxySettings, mergedModels, accounts, proxyModels
|
|
92
|
+
try {
|
|
93
|
+
fcmConfig = loadConfig()
|
|
94
|
+
proxySettings = getProxySettings(fcmConfig)
|
|
95
|
+
} catch (err) {
|
|
96
|
+
logError(`Fatal: Failed to load config: ${err.message}`)
|
|
97
|
+
process.exit(1)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!proxySettings.stableToken) {
|
|
101
|
+
logError('No stableToken in proxy settings — run the TUI first to initialize config.')
|
|
102
|
+
process.exit(1)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const port = proxySettings.preferredPort || DEFAULT_DAEMON_PORT
|
|
106
|
+
const token = proxySettings.stableToken
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
log(`Building merged model catalog...`)
|
|
110
|
+
mergedModels = await buildMergedModelsForDaemon()
|
|
111
|
+
log(`Merged ${mergedModels.length} model groups`)
|
|
112
|
+
|
|
113
|
+
const topology = buildProxyTopologyFromConfig(fcmConfig, mergedModels, sources)
|
|
114
|
+
accounts = topology.accounts
|
|
115
|
+
proxyModels = topology.proxyModels
|
|
116
|
+
} catch (err) {
|
|
117
|
+
logError(`Fatal: Failed to build initial topology: ${err.message}`)
|
|
118
|
+
process.exit(1)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (accounts.length === 0) {
|
|
122
|
+
logError('No API keys configured — FCM Proxy V2 has no accounts to serve. Add keys via the TUI.')
|
|
123
|
+
process.exit(1)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
log(`Built proxy topology: ${accounts.length} accounts across ${Object.keys(proxyModels).length} models`)
|
|
127
|
+
|
|
128
|
+
// 📖 Start the proxy server
|
|
129
|
+
const proxy = new ProxyServer({
|
|
130
|
+
port,
|
|
131
|
+
accounts,
|
|
132
|
+
proxyApiKey: token,
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const { port: listeningPort } = await proxy.start()
|
|
137
|
+
log(`Proxy listening on 127.0.0.1:${listeningPort}`)
|
|
138
|
+
|
|
139
|
+
// 📖 Write status file for TUI discovery
|
|
140
|
+
const statusInfo = {
|
|
141
|
+
pid: process.pid,
|
|
142
|
+
port: listeningPort,
|
|
143
|
+
token,
|
|
144
|
+
startedAt: new Date().toISOString(),
|
|
145
|
+
version: PKG_VERSION,
|
|
146
|
+
modelCount: Object.keys(proxyModels).length,
|
|
147
|
+
accountCount: accounts.length,
|
|
148
|
+
}
|
|
149
|
+
writeDaemonStatus(statusInfo)
|
|
150
|
+
log(`Status file written to ${DAEMON_STATUS_FILE}`)
|
|
151
|
+
|
|
152
|
+
// 📖 Set up config file watcher for hot-reload
|
|
153
|
+
let reloadTimeout = null
|
|
154
|
+
// 📖 Prevents concurrent reloads — if a reload is in progress, the next
|
|
155
|
+
// watcher event will be queued (one pending max) instead of stacking
|
|
156
|
+
let reloadInProgress = false
|
|
157
|
+
let reloadQueued = false
|
|
158
|
+
const configWatcher = watch(CONFIG_PATH, () => {
|
|
159
|
+
// 📖 Debounce 1s — config writes can trigger multiple fs events
|
|
160
|
+
if (reloadTimeout) clearTimeout(reloadTimeout)
|
|
161
|
+
reloadTimeout = setTimeout(async () => {
|
|
162
|
+
if (reloadInProgress) {
|
|
163
|
+
reloadQueued = true
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
reloadInProgress = true
|
|
167
|
+
try {
|
|
168
|
+
log('Config file changed — reloading topology...')
|
|
169
|
+
fcmConfig = loadConfig()
|
|
170
|
+
mergedModels = await buildMergedModelsForDaemon()
|
|
171
|
+
const newTopology = buildProxyTopologyFromConfig(fcmConfig, mergedModels, sources)
|
|
172
|
+
|
|
173
|
+
if (newTopology.accounts.length === 0) {
|
|
174
|
+
log('Warning: new topology has 0 accounts — keeping current topology')
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
proxy.updateAccounts(newTopology.accounts)
|
|
179
|
+
accounts = newTopology.accounts
|
|
180
|
+
proxyModels = newTopology.proxyModels
|
|
181
|
+
|
|
182
|
+
// 📖 Update status file
|
|
183
|
+
writeDaemonStatus({
|
|
184
|
+
...statusInfo,
|
|
185
|
+
modelCount: Object.keys(proxyModels).length,
|
|
186
|
+
accountCount: accounts.length,
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
log(`Topology reloaded: ${accounts.length} accounts, ${Object.keys(proxyModels).length} models`)
|
|
190
|
+
} catch (err) {
|
|
191
|
+
logError(`Hot-reload failed: ${err.message}`)
|
|
192
|
+
} finally {
|
|
193
|
+
reloadInProgress = false
|
|
194
|
+
// 📖 If another reload was queued during this one, trigger it now
|
|
195
|
+
if (reloadQueued) {
|
|
196
|
+
reloadQueued = false
|
|
197
|
+
configWatcher.emit('change')
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}, 1000)
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
// 📖 Graceful shutdown
|
|
204
|
+
const shutdown = async (signal) => {
|
|
205
|
+
log(`Received ${signal} — shutting down...`)
|
|
206
|
+
if (reloadTimeout) clearTimeout(reloadTimeout)
|
|
207
|
+
configWatcher.close()
|
|
208
|
+
try {
|
|
209
|
+
await proxy.stop()
|
|
210
|
+
} catch { /* best-effort */ }
|
|
211
|
+
removeDaemonStatus()
|
|
212
|
+
log('FCM Proxy V2 stopped cleanly.')
|
|
213
|
+
process.exit(0)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
process.on('SIGINT', () => shutdown('SIGINT'))
|
|
217
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'))
|
|
218
|
+
process.on('exit', () => removeDaemonStatus())
|
|
219
|
+
|
|
220
|
+
// 📖 Keep the process alive
|
|
221
|
+
log('FCM Proxy V2 ready. Waiting for requests...')
|
|
222
|
+
|
|
223
|
+
} catch (err) {
|
|
224
|
+
if (err.code === 'EADDRINUSE') {
|
|
225
|
+
logError(`Port ${port} is already in use. Another FCM Proxy V2 instance may be running, or another process occupies this port.`)
|
|
226
|
+
logError(`Change proxy.preferredPort in ~/.free-coding-models.json or stop the conflicting process.`)
|
|
227
|
+
process.exit(2)
|
|
228
|
+
}
|
|
229
|
+
logError(`Failed to start proxy: ${err.message}`)
|
|
230
|
+
removeDaemonStatus()
|
|
231
|
+
process.exit(1)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
main().catch(err => {
|
|
236
|
+
logError(`Fatal: ${err.message}`)
|
|
237
|
+
removeDaemonStatus()
|
|
238
|
+
process.exit(1)
|
|
239
|
+
})
|