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 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, configure the proxy, clean OpenCode proxy sync, and manually check/install updates
73
- - **🔀 Multi-account Proxy (`fcm-proxy`)** — Automatically starts a local reverse proxy that groups all your accounts into a single provider in OpenCode; supports multi-account rotation and auto-detects usage limits to swap between providers.
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 any of the 13 supported tools, with a choice between **Direct Provider** (pure API) or **FCM Proxy** (key rotation + usage tracking), then pick all models or a curated subset
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.2.6 brings powerful new features:**
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
- - **Provider colors everywhere** — Provider names are now colored consistently in logs, settings, and the main table for better visual recognition
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
- # Output results as JSON (for scripting/automation)
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
- ## 🔀 Multi-Account Proxy (`fcm-proxy`)
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
- `free-coding-models` includes a built-in reverse proxy that can group all your provider accounts into a single virtual provider.
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
- Important:
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
- ### Why use the proxy?
567
- - **Unified Provider**: Instead of managing 20+ providers in your coding assistant, just use `fcm-proxy`.
568
- - **Automatic Rotation**: When one account hits its rate limit (429), the proxy automatically swaps to the next available account for that model.
569
- - **Quota Awareness**: The proxy tracks usage in real-time and prioritizes accounts with the most remaining bandwidth.
570
- - **Transparent Bridging**: Automatically handles non-standard API paths (like ZAI's `/api/coding/paas/v4/`) and converts them to standard OpenAI-compatible `/v1/` calls.
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
- ### How to use it
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
- - Use the Settings action **Clean OpenCode proxy config**
581
- - Or run `free-coding-models --clean-proxy`
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` | Environment variables |
674
+ | Goose | `--goose` | ~/.config/goose/config.yaml + custom_providers/ |
609
675
  | **Aider** | `--aider` | ~/.aider.conf.yml |
610
- | **Claude Code** | `--claude-code` | CLI flag |
611
- | **Codex** | `--codex` | CLI flag |
612
- | **Gemini** | `--gemini` | ~/.gemini/settings.json |
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
- All tools are also available as install targets in the **Install Endpoints** flow (`Y` key) install an entire provider catalog into any tool with one flow, choosing between Direct Provider or FCM Proxy connection.
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 / I** — Request feature / Report bug
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 all 13 supported tools:
951
- - Config-based: `OpenCode CLI`, `OpenCode Desktop`, `OpenClaw`, `Crush`, `Goose`, `Pi`, `Aider`, `Amp`, `Gemini`, `Qwen`
952
- - Env-file based: `Claude Code`, `Codex CLI`, `OpenHands` (writes `~/.fcm-{tool}-env` — source it before launching)
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 the local FCM proxy with key rotation and usage tracking
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
- - For env-based tools (Claude Code, Codex, OpenHands), FCM writes a sourceable file at `~/.fcm-{tool}-env` run `source ~/.fcm-claude-code-env` before launching
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
+ })