free-coding-models 0.1.87 โ 0.2.0
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/README.md +58 -45
- package/bin/free-coding-models.js +65 -14
- package/package.json +1 -1
- package/src/config.js +42 -4
- package/src/key-handler.js +105 -13
- package/src/opencode-sync.js +41 -0
- package/src/opencode.js +23 -2
- package/src/overlays.js +74 -13
- package/src/render-table.js +72 -16
- package/src/token-usage-reader.js +5 -5
- package/src/tool-launchers.js +319 -0
- package/src/tool-metadata.js +63 -0
- package/src/updater.js +128 -30
- package/src/utils.js +40 -3
package/README.md
CHANGED
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
|
|
68
68
|
- **๐ฏ Coding-focused** โ Only LLM models optimized for code generation, not chat or vision
|
|
69
69
|
- **๐ 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
|
|
70
|
-
- **โ๏ธ Settings screen** โ Press `P` to manage provider API keys, enable/disable providers,
|
|
70
|
+
- **โ๏ธ Settings screen** โ Press `P` to manage provider API keys, enable/disable providers, configure the proxy, clean OpenCode proxy sync, and manually check/install updates
|
|
71
71
|
- **๐ 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.
|
|
72
72
|
- **๐ Parallel pings** โ All models tested simultaneously via native `fetch`
|
|
73
73
|
- **๐ Real-time animation** โ Watch latency appear live in alternate screen buffer
|
|
@@ -77,13 +77,13 @@
|
|
|
77
77
|
- **๐ Uptime tracking** โ Percentage of successful pings shown in real-time
|
|
78
78
|
- **๐ Stability score** โ Composite 0โ100 score measuring consistency (p95, jitter, spikes, uptime)
|
|
79
79
|
- **๐ Usage tracking** โ Monitor remaining quota for each exact provider/model pair when the provider exposes it; otherwise the TUI shows a green dot instead of a misleading percentage.
|
|
80
|
-
- **๐
|
|
80
|
+
- **๐ Request Log Overlay** โ Press `X` to inspect recent proxied requests and token usage for exact provider/model pairs.
|
|
81
81
|
- **๐ MODEL_NOT_FOUND Rotation** โ If a specific provider returns a 404 for a model, the TUI intelligently rotates through other available providers for the same model.
|
|
82
82
|
- **๐ Auto-retry** โ Timeout models keep getting retried, nothing is ever "given up on"
|
|
83
83
|
- **๐ฎ Interactive selection** โ Navigate with arrow keys directly in the table, press Enter to act
|
|
84
|
-
- **๐ Startup mode menu** โ Choose between OpenCode and OpenClaw before the TUI launches
|
|
85
84
|
- **๐ป OpenCode integration** โ Auto-detects NIM setup, sets model as default, launches OpenCode
|
|
86
85
|
- **๐ฆ OpenClaw integration** โ Sets selected model as default provider in `~/.openclaw/openclaw.json`
|
|
86
|
+
- **๐งฐ Public tool launchers** โ `Enter` can auto-configure and launch `OpenCode CLI`, `OpenCode Desktop`, `OpenClaw`, `Crush`, and `Goose`
|
|
87
87
|
- **๐ Feature Request (J key)** โ Send anonymous feedback directly to the project team
|
|
88
88
|
- **๐ Bug Report (I key)** โ Send anonymous bug reports directly to the project team
|
|
89
89
|
- **๐จ Clean output** โ Zero scrollback pollution, interface stays open until Ctrl+C
|
|
@@ -150,7 +150,7 @@ bunx free-coding-models YOUR_API_KEY
|
|
|
150
150
|
## ๐ Usage
|
|
151
151
|
|
|
152
152
|
```bash
|
|
153
|
-
# Just run it โ
|
|
153
|
+
# Just run it โ starts in OpenCode CLI mode, prompts for API key if not set
|
|
154
154
|
free-coding-models
|
|
155
155
|
|
|
156
156
|
# Explicitly target OpenCode CLI (TUI + Enter launches OpenCode CLI)
|
|
@@ -162,6 +162,10 @@ free-coding-models --opencode-desktop
|
|
|
162
162
|
# Explicitly target OpenClaw (TUI + Enter sets model as default in OpenClaw)
|
|
163
163
|
free-coding-models --openclaw
|
|
164
164
|
|
|
165
|
+
# Launch other supported public tools with the selected model
|
|
166
|
+
free-coding-models --crush
|
|
167
|
+
free-coding-models --goose
|
|
168
|
+
|
|
165
169
|
# Show only top-tier models (A+, S, S+)
|
|
166
170
|
free-coding-models --best
|
|
167
171
|
|
|
@@ -179,30 +183,17 @@ free-coding-models --openclaw --tier S
|
|
|
179
183
|
free-coding-models --opencode --best
|
|
180
184
|
```
|
|
181
185
|
|
|
182
|
-
###
|
|
183
|
-
|
|
184
|
-
When you run `free-coding-models` without `--opencode` or `--openclaw`, you get an interactive startup menu:
|
|
185
|
-
|
|
186
|
-
```
|
|
187
|
-
โก Free Coding Models โ Choose your tool
|
|
188
|
-
|
|
189
|
-
โฏ ๐ป OpenCode CLI
|
|
190
|
-
Press Enter on a model โ launch OpenCode CLI with it as default
|
|
186
|
+
### Choosing the target tool
|
|
191
187
|
|
|
192
|
-
|
|
193
|
-
Press Enter on a model โ set model & open OpenCode Desktop app
|
|
188
|
+
Running `free-coding-models` with no launcher flag starts in **OpenCode CLI** mode.
|
|
194
189
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
โโ Navigate โข Enter Select โข Ctrl+C Exit
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
Use `โโ` arrows to select, `Enter` to confirm. Then the TUI launches with your chosen mode shown in the header badge.
|
|
190
|
+
- Press **`Z`** in the TUI to cycle the public launch targets: `OpenCode CLI` โ `OpenCode Desktop` โ `OpenClaw` โ `Crush` โ `Goose`
|
|
191
|
+
- Or start directly in the target mode with a CLI flag such as `--opencode-desktop`, `--openclaw`, `--crush`, or `--goose`
|
|
192
|
+
- The active target is always visible in the header badge before you press `Enter`
|
|
202
193
|
|
|
203
194
|
**How it works:**
|
|
204
|
-
1. **Ping phase** โ All enabled models are pinged in parallel (up to
|
|
205
|
-
2. **Continuous monitoring** โ Models start at 2s re-pings for 60s, then fall back to 10s automatically
|
|
195
|
+
1. **Ping phase** โ All enabled models are pinged in parallel (up to 159 across 20 providers)
|
|
196
|
+
2. **Continuous monitoring** โ Models start at 2s re-pings for 60s, then fall back to 10s automatically, and slow to 30s after 5 minutes idle unless you force 4s mode with `W`
|
|
206
197
|
3. **Real-time updates** โ Watch "Latest", "Avg", and "Up%" columns update live
|
|
207
198
|
4. **Select anytime** โ Use โโ arrows to navigate, press Enter on a model to act
|
|
208
199
|
5. **Smart detection** โ Automatically detects if NVIDIA NIM is configured in OpenCode or OpenClaw
|
|
@@ -257,14 +248,18 @@ Press **`P`** to open the Settings screen at any time:
|
|
|
257
248
|
2) Profile โ API Keys โ Generate
|
|
258
249
|
3) Press T to test your key
|
|
259
250
|
|
|
260
|
-
โโ Navigate โข Enter Edit key
|
|
251
|
+
โโ Navigate โข Enter Edit/Run โข + Add key โข - Remove key โข Space Toggle โข T Test key โข S SyncโOpenCode โข R Restore backup โข U Updates โข โซ Delete profile โข Esc Close
|
|
261
252
|
```
|
|
262
253
|
|
|
263
254
|
- **โโ** โ navigate providers
|
|
264
|
-
- **Enter** โ
|
|
255
|
+
- **Enter** โ edit the selected key, run maintenance actions, or load the selected profile
|
|
256
|
+
- **+ / -** โ add another key for the selected provider or remove one
|
|
265
257
|
- **Space** โ toggle provider enabled/disabled
|
|
266
258
|
- **T** โ fire a real test ping to verify the key works (shows โ
/โ)
|
|
259
|
+
- **S** โ sync `fcm-proxy` into OpenCode when proxy mode + persistence are enabled
|
|
260
|
+
- **R** โ restore the last OpenCode backup created by sync/cleanup flows
|
|
267
261
|
- **U** โ manually check npm for a newer version
|
|
262
|
+
- **Backspace** โ delete the selected saved profile
|
|
268
263
|
- **Esc** โ close settings and reload models list
|
|
269
264
|
|
|
270
265
|
Keys are saved to `~/.free-coding-models.json` (permissions `0600`).
|
|
@@ -364,7 +359,7 @@ TOGETHER_API_KEY=together_xxx free-coding-models
|
|
|
364
359
|
2. Create API key (`PERPLEXITY_API_KEY`)
|
|
365
360
|
|
|
366
361
|
**Alibaba Cloud (DashScope)** (8 models, Qwen3-Coder family):
|
|
367
|
-
1. Sign up at [
|
|
362
|
+
1. Sign up at [modelstudio.console.alibabacloud.com](https://modelstudio.console.alibabacloud.com)
|
|
368
363
|
2. Activate Model Studio (1M free tokens per model, Singapore region, 90 days)
|
|
369
364
|
3. Create API key (`DASHSCOPE_API_KEY`)
|
|
370
365
|
|
|
@@ -513,6 +508,11 @@ Stability = 0.30 ร p95_score
|
|
|
513
508
|
|
|
514
509
|
`free-coding-models` includes a built-in reverse proxy that can group all your provider accounts into a single virtual provider.
|
|
515
510
|
|
|
511
|
+
Important:
|
|
512
|
+
- **Disabled by default** โ proxy mode is now opt-in from the Settings screen (`P`)
|
|
513
|
+
- **Direct OpenCode launch remains the default** when proxy mode is off
|
|
514
|
+
- **Token/request logs are only populated by proxied requests** today
|
|
515
|
+
|
|
516
516
|
### Why use the proxy?
|
|
517
517
|
- **Unified Provider**: Instead of managing 20+ providers in your coding assistant, just use `fcm-proxy`.
|
|
518
518
|
- **Automatic Rotation**: When one account hits its rate limit (429), the proxy automatically swaps to the next available account for that model.
|
|
@@ -520,18 +520,26 @@ Stability = 0.30 ร p95_score
|
|
|
520
520
|
- **Transparent Bridging**: Automatically handles non-standard API paths (like ZAI's `/api/coding/paas/v4/`) and converts them to standard OpenAI-compatible `/v1/` calls.
|
|
521
521
|
|
|
522
522
|
### How to use it
|
|
523
|
-
|
|
523
|
+
1. Open **Settings** with `P`
|
|
524
|
+
2. Enable **Proxy mode (opt-in)**
|
|
525
|
+
3. Optionally enable **Persist proxy in OpenCode** if you explicitly want `fcm-proxy` written to `~/.config/opencode/opencode.json`
|
|
526
|
+
4. Optionally set **Preferred proxy port** (`0` = auto)
|
|
527
|
+
5. Use `S` in Settings to sync the proxy catalog into OpenCode only when you actually want that persistent config
|
|
528
|
+
|
|
529
|
+
Cleanup:
|
|
530
|
+
- Use the Settings action **Clean OpenCode proxy config**
|
|
531
|
+
- Or run `free-coding-models --clean-proxy`
|
|
524
532
|
|
|
525
533
|
---
|
|
526
534
|
|
|
527
|
-
## ๐ Log
|
|
535
|
+
## ๐ Request Log Overlay
|
|
528
536
|
|
|
529
|
-
Press **`X`** at any time to open the dedicated
|
|
537
|
+
Press **`X`** at any time to open the dedicated request-log overlay.
|
|
530
538
|
|
|
531
|
-
- **
|
|
532
|
-
- **
|
|
533
|
-
- **
|
|
534
|
-
- **
|
|
539
|
+
- **Proxy-only accounting**: Entries are written when requests flow through the multi-account proxy.
|
|
540
|
+
- **Exact token totals**: The overlay aggregates prompt+completion usage per proxied request.
|
|
541
|
+
- **Per-request visibility**: You can inspect provider, model, status, token count, and latency for recent requests.
|
|
542
|
+
- **Startup table reuse**: The `Used` column in the main table is derived from the same request log file.
|
|
535
543
|
|
|
536
544
|
Use **โโ** to scroll and **Esc** or **X** to return to the main table.
|
|
537
545
|
|
|
@@ -541,7 +549,7 @@ Use **โโ** to scroll and **Esc** or **X** to return to the main table.
|
|
|
541
549
|
|
|
542
550
|
**The easiest way** โ let `free-coding-models` do everything:
|
|
543
551
|
|
|
544
|
-
1. **Run**: `free-coding-models --opencode` (or
|
|
552
|
+
1. **Run**: `free-coding-models --opencode` (or launch with no flag to use the default OpenCode CLI mode)
|
|
545
553
|
2. **Wait** for models to be pinged (green โ
status)
|
|
546
554
|
3. **Navigate** with โโ arrows to your preferred model
|
|
547
555
|
4. **Press Enter** โ tool automatically:
|
|
@@ -624,7 +632,7 @@ OpenClaw is an autonomous AI agent daemon. `free-coding-models` can configure it
|
|
|
624
632
|
free-coding-models --openclaw
|
|
625
633
|
```
|
|
626
634
|
|
|
627
|
-
Or
|
|
635
|
+
Or press **`Z`** in the TUI until the header shows **OpenClaw**, then press **Enter** on a model.
|
|
628
636
|
|
|
629
637
|
1. **Wait** for models to be pinged
|
|
630
638
|
2. **Navigate** with โโ arrows to your preferred model
|
|
@@ -729,6 +737,7 @@ This script:
|
|
|
729
737
|
โ 2. Ping ALL models in parallel โ
|
|
730
738
|
โ 3. Display real-time table with Latest/Avg/Stability/Up% โ
|
|
731
739
|
โ 4. Re-ping ALL models at 2s on startup, then 10s steady-state โ
|
|
740
|
+
โ and 30s after 5m idle unless forced back to 4s with W โ
|
|
732
741
|
โ 5. Update rolling averages + stability scores per model โ
|
|
733
742
|
โ 6. User can navigate with โโ and select with Enter โ
|
|
734
743
|
โ 7. On Enter (OpenCode): set model, launch OpenCode โ
|
|
@@ -816,29 +825,32 @@ This script:
|
|
|
816
825
|
|
|
817
826
|
| Flag | Description |
|
|
818
827
|
|------|-------------|
|
|
819
|
-
| *(none)* |
|
|
828
|
+
| *(none)* | Start in OpenCode CLI mode |
|
|
820
829
|
| `--opencode` | OpenCode CLI mode โ Enter launches OpenCode CLI with selected model |
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
830
|
+
| `--opencode-desktop` | OpenCode Desktop mode โ Enter sets model and opens OpenCode Desktop |
|
|
831
|
+
| `--openclaw` | OpenClaw mode โ Enter sets selected model as default in OpenClaw |
|
|
832
|
+
| `--crush` | Crush mode โ Enter writes `crush.json` and launches Crush |
|
|
833
|
+
| `--goose` | Goose mode โ Enter launches Goose with env-based provider config |
|
|
834
|
+
| `--best` | Show only top-tier models (A+, S, S+) |
|
|
835
|
+
| `--fiable` | Analyze 10 seconds, output the most reliable model as `provider/model_id` |
|
|
836
|
+
| `--tier S` | Show only S+ and S tier models |
|
|
826
837
|
| `--tier A` | Show only A+, A, A- tier models |
|
|
827
838
|
| `--tier B` | Show only B+, B tier models |
|
|
828
839
|
| `--tier C` | Show only C tier models |
|
|
829
840
|
| `--profile <name>` | Load a saved config profile on startup |
|
|
830
841
|
| `--recommend` | Auto-open Smart Recommend overlay on start |
|
|
842
|
+
| `--clean-proxy` | Remove persisted `fcm-proxy` config from OpenCode |
|
|
831
843
|
|
|
832
844
|
**Keyboard shortcuts (main TUI):**
|
|
833
845
|
- **โโ** โ Navigate models
|
|
834
|
-
- **Enter** โ Select model
|
|
846
|
+
- **Enter** โ Select model and launch the current target tool from the header badge
|
|
835
847
|
- **R/Y/S/C/M/O/L/A/H/V/B/U/G** โ Sort by Rank/Tier/SWE/Ctx/Model/Provider/Latest/Avg/Health/Verdict/Stability/Up%/Usage
|
|
836
848
|
- **F** โ Toggle favorite on selected model (โญ in Model column, pinned at top)
|
|
837
849
|
- **T** โ Cycle tier filter (All โ S+ โ S โ A+ โ A โ A- โ B+ โ B โ C โ All)
|
|
838
850
|
- **D** โ Cycle provider filter (All โ NIM โ Groq โ ...)
|
|
839
851
|
- **E** โ Toggle configured-only mode (on by default, persisted across sessions and profiles)
|
|
840
|
-
- **Z** โ Cycle
|
|
841
|
-
- **X** โ
|
|
852
|
+
- **Z** โ Cycle target tool (OpenCode CLI โ OpenCode Desktop โ OpenClaw โ Crush โ Goose)
|
|
853
|
+
- **X** โ Toggle request logs (recent proxied request/token usage logs)
|
|
842
854
|
- **P** โ Open Settings (manage API keys, toggles, updates, profiles)
|
|
843
855
|
- **Shift+P** โ Cycle through saved profiles (switches live TUI settings)
|
|
844
856
|
- **Shift+S** โ Save current TUI settings as a named profile (inline prompt)
|
|
@@ -870,6 +882,7 @@ Profiles let you save and restore different TUI configurations โ useful if you
|
|
|
870
882
|
- Sort column and direction
|
|
871
883
|
- Tier filter
|
|
872
884
|
- Ping mode
|
|
885
|
+
- Configured-only filter
|
|
873
886
|
- API keys
|
|
874
887
|
|
|
875
888
|
**Saving a profile:**
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
* - Rolling averages calculated from ALL successful pings since start
|
|
16
16
|
* - Best-per-tier highlighting with medals (๐ฅ๐ฅ๐ฅ)
|
|
17
17
|
* - Interactive navigation with arrow keys directly in the table
|
|
18
|
-
* - Instant OpenCode
|
|
19
|
-
* -
|
|
18
|
+
* - Instant OpenCode / OpenClaw / external-tool action on Enter key press
|
|
19
|
+
* - Direct mode flags plus an in-app Z-cycle for the public launcher set
|
|
20
20
|
* - Automatic config detection and model setup for both tools
|
|
21
21
|
* - JSON config stored in ~/.free-coding-models.json (auto-migrates from old plain-text)
|
|
22
22
|
* - Multi-provider support via sources.js (NIM/Groq/Cerebras/OpenRouter/Hugging Face/Replicate/DeepInfra/... โ extensible)
|
|
@@ -34,7 +34,6 @@
|
|
|
34
34
|
* - `sendUsageTelemetry`: Fire-and-forget anonymous app-start event
|
|
35
35
|
* - `ensureFavoritesConfig` / `toggleFavoriteModel`: Persist and toggle pinned favorites
|
|
36
36
|
* - `promptApiKey`: Interactive wizard for first-time multi-provider API key setup
|
|
37
|
-
* - `promptModeSelection`: Startup menu to choose OpenCode vs OpenClaw
|
|
38
37
|
* - `buildPingRequest` / `ping`: Build provider-specific probe requests and measure latency
|
|
39
38
|
* - `renderTable`: Generate ASCII table with colored latency indicators and status emojis
|
|
40
39
|
* - `getAvg`: Calculate average latency from all successful pings
|
|
@@ -66,14 +65,15 @@
|
|
|
66
65
|
* - OpenCode config: ~/.config/opencode/opencode.json
|
|
67
66
|
* - OpenClaw config: ~/.openclaw/openclaw.json
|
|
68
67
|
* - Ping timeout: 15s per attempt
|
|
69
|
-
* - Ping
|
|
68
|
+
* - Ping cadence: 2s startup burst for 60s, 10s steady state, 30s after 5m idle, forced 4s via `W`
|
|
70
69
|
* - Animation: 12 FPS with braille spinners
|
|
71
70
|
*
|
|
72
71
|
* ๐ CLI flags:
|
|
73
|
-
* - (no flag):
|
|
72
|
+
* - (no flag): Start in OpenCode CLI mode
|
|
74
73
|
* - --opencode: OpenCode CLI mode (launch CLI with selected model)
|
|
75
74
|
* - --opencode-desktop: OpenCode Desktop mode (set model & open Desktop app)
|
|
76
75
|
* - --openclaw: OpenClaw mode (set selected model as default in OpenClaw)
|
|
76
|
+
* - --crush / --goose: launch the currently selected model in the supported external CLI
|
|
77
77
|
* - --best: Show only top-tier models (A+, S, S+)
|
|
78
78
|
* - --fiable: Analyze 10s and output the most reliable model
|
|
79
79
|
* - --no-telemetry: Disable anonymous usage analytics for this run
|
|
@@ -92,10 +92,10 @@ import { homedir } from 'os'
|
|
|
92
92
|
import { join, dirname } from 'path'
|
|
93
93
|
import { MODELS, sources } from '../sources.js'
|
|
94
94
|
import { getAvg, getVerdict, getUptime, getP95, getJitter, getStabilityScore, sortResults, filterByTier, findBestModel, parseArgs, TIER_ORDER, VERDICT_ORDER, TIER_LETTER_MAP, scoreModelForTask, getTopRecommendations, TASK_TYPES, PRIORITY_TYPES, CONTEXT_BUDGETS, formatCtxWindow, labelFromId, getProxyStatusInfo } from '../src/utils.js'
|
|
95
|
-
import { loadConfig, saveConfig, getApiKey, resolveApiKeys, addApiKey, removeApiKey, isProviderEnabled, saveAsProfile, loadProfile, listProfiles, deleteProfile, getActiveProfileName, setActiveProfile, _emptyProfileSettings } from '../src/config.js'
|
|
95
|
+
import { loadConfig, saveConfig, getApiKey, getProxySettings, resolveApiKeys, addApiKey, removeApiKey, isProviderEnabled, saveAsProfile, loadProfile, listProfiles, deleteProfile, getActiveProfileName, setActiveProfile, _emptyProfileSettings } from '../src/config.js'
|
|
96
96
|
import { buildMergedModels } from '../src/model-merger.js'
|
|
97
97
|
import { ProxyServer } from '../src/proxy-server.js'
|
|
98
|
-
import { loadOpenCodeConfig, saveOpenCodeConfig, syncToOpenCode, restoreOpenCodeBackup } from '../src/opencode-sync.js'
|
|
98
|
+
import { loadOpenCodeConfig, saveOpenCodeConfig, syncToOpenCode, restoreOpenCodeBackup, cleanupOpenCodeProxyConfig } from '../src/opencode-sync.js'
|
|
99
99
|
import { usageForRow as _usageForRow } from '../src/usage-reader.js'
|
|
100
100
|
import { loadRecentLogs } from '../src/log-reader.js'
|
|
101
101
|
import { buildProviderModelTokenKey, loadTokenUsageByProviderModel } from '../src/token-usage-reader.js'
|
|
@@ -112,10 +112,12 @@ import { checkForUpdateDetailed, checkForUpdate, runUpdate, promptUpdateNotifica
|
|
|
112
112
|
import { promptApiKey } from '../src/setup.js'
|
|
113
113
|
import { stripAnsi, maskApiKey, displayWidth, padEndDisplay, tintOverlayLines, keepOverlayTargetVisible, sliceOverlayLines, calculateViewport, sortResultsWithPinnedFavorites, renderProxyStatusLine, adjustScrollOffset } from '../src/render-helpers.js'
|
|
114
114
|
import { renderTable } from '../src/render-table.js'
|
|
115
|
-
import { setOpenCodeModelData, startOpenCode, startOpenCodeDesktop, startProxyAndLaunch, autoStartProxyIfSynced, ensureProxyRunning, buildProxyTopologyFromConfig } from '../src/opencode.js'
|
|
115
|
+
import { setOpenCodeModelData, startOpenCode, startOpenCodeDesktop, startProxyAndLaunch, autoStartProxyIfSynced, ensureProxyRunning, buildProxyTopologyFromConfig, isProxyEnabledForConfig } from '../src/opencode.js'
|
|
116
116
|
import { startOpenClaw } from '../src/openclaw.js'
|
|
117
117
|
import { createOverlayRenderers } from '../src/overlays.js'
|
|
118
118
|
import { createKeyHandler } from '../src/key-handler.js'
|
|
119
|
+
import { getToolModeOrder } from '../src/tool-metadata.js'
|
|
120
|
+
import { startExternalTool } from '../src/tool-launchers.js'
|
|
119
121
|
|
|
120
122
|
// ๐ mergedModels: cross-provider grouped model list (one entry per label, N providers each)
|
|
121
123
|
// ๐ mergedModelByLabel: fast lookup map from display label โ merged model entry
|
|
@@ -176,6 +178,16 @@ async function main() {
|
|
|
176
178
|
ensureTelemetryConfig(config)
|
|
177
179
|
ensureFavoritesConfig(config)
|
|
178
180
|
|
|
181
|
+
if (cliArgs.cleanProxyMode) {
|
|
182
|
+
const cleaned = cleanupOpenCodeProxyConfig()
|
|
183
|
+
console.log()
|
|
184
|
+
console.log(chalk.green(' โ
OpenCode proxy cleanup complete'))
|
|
185
|
+
console.log(chalk.dim(` Config: ${cleaned.path}`))
|
|
186
|
+
console.log(chalk.dim(` Removed provider: ${cleaned.removedProvider ? 'yes' : 'no'} โข Removed default model: ${cleaned.removedModel ? 'yes' : 'no'}`))
|
|
187
|
+
console.log()
|
|
188
|
+
process.exit(0)
|
|
189
|
+
}
|
|
190
|
+
|
|
179
191
|
// ๐ If --profile <name> was passed, load that profile into the live config
|
|
180
192
|
let startupProfileSettings = null
|
|
181
193
|
if (cliArgs.profileName) {
|
|
@@ -204,11 +216,28 @@ async function main() {
|
|
|
204
216
|
// ๐ Backward-compat: keep apiKey var for startOpenClaw() which still needs it
|
|
205
217
|
let apiKey = getApiKey(config, 'nvidia')
|
|
206
218
|
|
|
207
|
-
// ๐ Default mode: OpenCode CLI
|
|
219
|
+
// ๐ Default mode: OpenCode CLI.
|
|
220
|
+
// ๐ Additional external tools can now be selected via dedicated flags.
|
|
208
221
|
let mode = 'opencode'
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
222
|
+
const requestedMode = getToolModeOrder().find((toolMode) => {
|
|
223
|
+
const flagByMode = {
|
|
224
|
+
opencode: cliArgs.openCodeMode,
|
|
225
|
+
'opencode-desktop': cliArgs.openCodeDesktopMode,
|
|
226
|
+
openclaw: cliArgs.openClawMode,
|
|
227
|
+
aider: cliArgs.aiderMode,
|
|
228
|
+
crush: cliArgs.crushMode,
|
|
229
|
+
goose: cliArgs.gooseMode,
|
|
230
|
+
'claude-code': cliArgs.claudeCodeMode,
|
|
231
|
+
codex: cliArgs.codexMode,
|
|
232
|
+
gemini: cliArgs.geminiMode,
|
|
233
|
+
qwen: cliArgs.qwenMode,
|
|
234
|
+
openhands: cliArgs.openHandsMode,
|
|
235
|
+
amp: cliArgs.ampMode,
|
|
236
|
+
pi: cliArgs.piMode,
|
|
237
|
+
}
|
|
238
|
+
return flagByMode[toolMode] === true
|
|
239
|
+
})
|
|
240
|
+
if (requestedMode) mode = requestedMode
|
|
212
241
|
|
|
213
242
|
// ๐ Track app opening early so fast exits are still counted.
|
|
214
243
|
// ๐ Must run before update checks because npm registry lookups can add startup delay.
|
|
@@ -353,6 +382,8 @@ async function main() {
|
|
|
353
382
|
scrollOffset: 0, // ๐ First visible model index in viewport
|
|
354
383
|
terminalRows: process.stdout.rows || 24, // ๐ Current terminal height
|
|
355
384
|
terminalCols: process.stdout.columns || 80, // ๐ Current terminal width
|
|
385
|
+
widthWarningStartedAt: (process.stdout.columns || 80) < 166 ? now : null, // ๐ Start the narrow-terminal countdown immediately when booting in a small viewport.
|
|
386
|
+
widthWarningDismissed: false, // ๐ Esc hides the narrow-terminal warning early for the current narrow-width session.
|
|
356
387
|
// ๐ Settings screen state (P key opens it)
|
|
357
388
|
settingsOpen: false, // ๐ Whether settings overlay is active
|
|
358
389
|
settingsCursor: 0, // ๐ Which provider row is selected in settings
|
|
@@ -364,6 +395,8 @@ async function main() {
|
|
|
364
395
|
settingsUpdateState: 'idle', // ๐ 'idle'|'checking'|'available'|'up-to-date'|'error'|'installing'
|
|
365
396
|
settingsUpdateLatestVersion: null, // ๐ Latest npm version discovered from manual check
|
|
366
397
|
settingsUpdateError: null, // ๐ Last update-check error message for maintenance row
|
|
398
|
+
settingsProxyPortEditMode: false, // ๐ Whether Settings is editing the preferred proxy port field.
|
|
399
|
+
settingsProxyPortBuffer: '', // ๐ Inline input buffer for the preferred proxy port (0 = auto).
|
|
367
400
|
config, // ๐ Live reference to the config object (updated on save)
|
|
368
401
|
visibleSorted: [], // ๐ Cached visible+sorted models โ shared between render loop and key handlers
|
|
369
402
|
helpVisible: false, // ๐ Whether the help overlay (K key) is active
|
|
@@ -410,8 +443,20 @@ async function main() {
|
|
|
410
443
|
|
|
411
444
|
// ๐ Re-clamp viewport on terminal resize
|
|
412
445
|
process.stdout.on('resize', () => {
|
|
446
|
+
const prevCols = state.terminalCols
|
|
413
447
|
state.terminalRows = process.stdout.rows || 24
|
|
414
448
|
state.terminalCols = process.stdout.columns || 80
|
|
449
|
+
if (state.terminalCols < 166) {
|
|
450
|
+
if (prevCols >= 166 || state.widthWarningDismissed) {
|
|
451
|
+
state.widthWarningStartedAt = Date.now()
|
|
452
|
+
state.widthWarningDismissed = false
|
|
453
|
+
} else if (!state.widthWarningStartedAt) {
|
|
454
|
+
state.widthWarningStartedAt = Date.now()
|
|
455
|
+
}
|
|
456
|
+
} else {
|
|
457
|
+
state.widthWarningStartedAt = null
|
|
458
|
+
state.widthWarningDismissed = false
|
|
459
|
+
}
|
|
415
460
|
adjustScrollOffset(state)
|
|
416
461
|
})
|
|
417
462
|
|
|
@@ -524,6 +569,7 @@ async function main() {
|
|
|
524
569
|
PROVIDER_METADATA,
|
|
525
570
|
LOCAL_VERSION,
|
|
526
571
|
getApiKey,
|
|
572
|
+
getProxySettings,
|
|
527
573
|
resolveApiKeys,
|
|
528
574
|
isProviderEnabled,
|
|
529
575
|
listProfiles,
|
|
@@ -557,6 +603,7 @@ async function main() {
|
|
|
557
603
|
MODELS,
|
|
558
604
|
sources,
|
|
559
605
|
getApiKey,
|
|
606
|
+
getProxySettings,
|
|
560
607
|
resolveApiKeys,
|
|
561
608
|
addApiKey,
|
|
562
609
|
removeApiKey,
|
|
@@ -578,6 +625,7 @@ async function main() {
|
|
|
578
625
|
ENV_VAR_NAMES,
|
|
579
626
|
ensureProxyRunning,
|
|
580
627
|
syncToOpenCode,
|
|
628
|
+
cleanupOpenCodeProxyConfig,
|
|
581
629
|
restoreOpenCodeBackup,
|
|
582
630
|
checkForUpdateDetailed,
|
|
583
631
|
runUpdate,
|
|
@@ -585,7 +633,10 @@ async function main() {
|
|
|
585
633
|
startOpenCodeDesktop,
|
|
586
634
|
startOpenCode,
|
|
587
635
|
startProxyAndLaunch,
|
|
636
|
+
startExternalTool,
|
|
588
637
|
buildProxyTopologyFromConfig,
|
|
638
|
+
isProxyEnabledForConfig,
|
|
639
|
+
getToolModeOrder,
|
|
589
640
|
startRecommendAnalysis: overlays.startRecommendAnalysis,
|
|
590
641
|
stopRecommendAnalysis: overlays.stopRecommendAnalysis,
|
|
591
642
|
sendFeatureRequest,
|
|
@@ -652,7 +703,7 @@ async function main() {
|
|
|
652
703
|
? overlays.renderHelp()
|
|
653
704
|
: state.logVisible
|
|
654
705
|
? overlays.renderLog()
|
|
655
|
-
: renderTable(state.results, state.pendingPings, state.frame, state.cursor, state.sortColumn, state.sortDirection, state.pingInterval, state.lastPingTime, state.mode, state.tierFilterMode, state.scrollOffset, state.terminalRows, state.terminalCols, state.originFilterMode, state.activeProfile, state.profileSaveMode, state.profileSaveBuffer, state.proxyStartupStatus, state.pingMode, state.pingModeSource, state.hideUnconfiguredModels)
|
|
706
|
+
: renderTable(state.results, state.pendingPings, state.frame, state.cursor, state.sortColumn, state.sortDirection, state.pingInterval, state.lastPingTime, state.mode, state.tierFilterMode, state.scrollOffset, state.terminalRows, state.terminalCols, state.originFilterMode, state.activeProfile, state.profileSaveMode, state.profileSaveBuffer, state.proxyStartupStatus, state.pingMode, state.pingModeSource, state.hideUnconfiguredModels, state.widthWarningStartedAt, state.widthWarningDismissed)
|
|
656
707
|
process.stdout.write(ALT_HOME + content)
|
|
657
708
|
}, Math.round(1000 / FPS))
|
|
658
709
|
|
|
@@ -660,7 +711,7 @@ async function main() {
|
|
|
660
711
|
const initialVisible = state.results.filter(r => !r.hidden)
|
|
661
712
|
state.visibleSorted = sortResultsWithPinnedFavorites(initialVisible, state.sortColumn, state.sortDirection)
|
|
662
713
|
|
|
663
|
-
process.stdout.write(ALT_HOME + renderTable(state.results, state.pendingPings, state.frame, state.cursor, state.sortColumn, state.sortDirection, state.pingInterval, state.lastPingTime, state.mode, state.tierFilterMode, state.scrollOffset, state.terminalRows, state.terminalCols, state.originFilterMode, state.activeProfile, state.profileSaveMode, state.profileSaveBuffer, state.proxyStartupStatus, state.pingMode, state.pingModeSource, state.hideUnconfiguredModels))
|
|
714
|
+
process.stdout.write(ALT_HOME + renderTable(state.results, state.pendingPings, state.frame, state.cursor, state.sortColumn, state.sortDirection, state.pingInterval, state.lastPingTime, state.mode, state.tierFilterMode, state.scrollOffset, state.terminalRows, state.terminalCols, state.originFilterMode, state.activeProfile, state.profileSaveMode, state.profileSaveBuffer, state.proxyStartupStatus, state.pingMode, state.pingModeSource, state.hideUnconfiguredModels, state.widthWarningStartedAt, state.widthWarningDismissed))
|
|
664
715
|
|
|
665
716
|
// ๐ If --recommend was passed, auto-open the Smart Recommend overlay on start
|
|
666
717
|
if (cliArgs.recommendMode) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "free-coding-models",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Find the fastest coding LLM models in seconds โ ping free models from multiple providers, pick the best one for OpenCode, Cursor, or any AI coding assistant.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nvidia",
|
package/src/config.js
CHANGED
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
* - apiKeys: API keys per provider (can differ between work/personal setups)
|
|
72
72
|
* - providers: enabled/disabled state per provider
|
|
73
73
|
* - favorites: list of pinned favorite models
|
|
74
|
-
* - settings: extra TUI preferences (tierFilter, sortColumn, sortAsc, pingInterval, hideUnconfiguredModels)
|
|
74
|
+
* - settings: extra TUI preferences (tierFilter, sortColumn, sortAsc, pingInterval, hideUnconfiguredModels, proxy)
|
|
75
75
|
*
|
|
76
76
|
* ๐ When a profile is loaded via --profile <name> or Shift+P, the main config's
|
|
77
77
|
* apiKeys/providers/favorites are replaced with the profile's values. The profile
|
|
@@ -96,11 +96,12 @@
|
|
|
96
96
|
* โ getActiveProfileName(config) โ Get the currently active profile name (or null)
|
|
97
97
|
* โ setActiveProfile(config, name) โ Set which profile is active (null to clear)
|
|
98
98
|
* โ _emptyProfileSettings() โ Default TUI settings for a profile
|
|
99
|
+
* โ getProxySettings(config) โ Return normalized proxy settings from config
|
|
99
100
|
*
|
|
100
101
|
* @exports loadConfig, saveConfig, getApiKey, isProviderEnabled
|
|
101
102
|
* @exports addApiKey, removeApiKey, listApiKeys โ multi-key management helpers
|
|
102
103
|
* @exports saveAsProfile, loadProfile, listProfiles, deleteProfile
|
|
103
|
-
* @exports getActiveProfileName, setActiveProfile
|
|
104
|
+
* @exports getActiveProfileName, setActiveProfile, getProxySettings
|
|
104
105
|
* @exports CONFIG_PATH โ path to the JSON config file
|
|
105
106
|
*
|
|
106
107
|
* @see bin/free-coding-models.js โ main CLI that uses these functions
|
|
@@ -166,6 +167,7 @@ export function loadConfig() {
|
|
|
166
167
|
if (!parsed.providers) parsed.providers = {}
|
|
167
168
|
if (!parsed.settings || typeof parsed.settings !== 'object') parsed.settings = {}
|
|
168
169
|
if (typeof parsed.settings.hideUnconfiguredModels !== 'boolean') parsed.settings.hideUnconfiguredModels = true
|
|
170
|
+
parsed.settings.proxy = normalizeProxySettings(parsed.settings.proxy)
|
|
169
171
|
// ๐ Favorites: list of "providerKey/modelId" pinned rows.
|
|
170
172
|
if (!Array.isArray(parsed.favorites)) parsed.favorites = []
|
|
171
173
|
parsed.favorites = parsed.favorites.filter((fav) => typeof fav === 'string' && fav.trim().length > 0)
|
|
@@ -177,7 +179,9 @@ export function loadConfig() {
|
|
|
177
179
|
if (!parsed.profiles || typeof parsed.profiles !== 'object') parsed.profiles = {}
|
|
178
180
|
for (const profile of Object.values(parsed.profiles)) {
|
|
179
181
|
if (!profile || typeof profile !== 'object') continue
|
|
180
|
-
profile.settings = profile.settings
|
|
182
|
+
profile.settings = profile.settings
|
|
183
|
+
? { ..._emptyProfileSettings(), ...profile.settings, proxy: normalizeProxySettings(profile.settings.proxy) }
|
|
184
|
+
: _emptyProfileSettings()
|
|
181
185
|
}
|
|
182
186
|
if (parsed.activeProfile && typeof parsed.activeProfile !== 'string') parsed.activeProfile = null
|
|
183
187
|
return parsed
|
|
@@ -400,9 +404,40 @@ export function _emptyProfileSettings() {
|
|
|
400
404
|
sortAsc: true, // ๐ true = ascending (fastest first for latency)
|
|
401
405
|
pingInterval: 10000, // ๐ default ms between pings in the steady "normal" mode
|
|
402
406
|
hideUnconfiguredModels: true, // ๐ true = default to providers that are actually configured
|
|
407
|
+
proxy: normalizeProxySettings(),
|
|
403
408
|
}
|
|
404
409
|
}
|
|
405
410
|
|
|
411
|
+
/**
|
|
412
|
+
* ๐ normalizeProxySettings: keep proxy-related preferences stable across old configs,
|
|
413
|
+
* ๐ new installs, and profile switches. Proxy is opt-in by default.
|
|
414
|
+
*
|
|
415
|
+
* @param {object|undefined|null} proxy
|
|
416
|
+
* @returns {{ enabled: boolean, syncToOpenCode: boolean, preferredPort: number }}
|
|
417
|
+
*/
|
|
418
|
+
export function normalizeProxySettings(proxy = null) {
|
|
419
|
+
const preferredPort = Number.isInteger(proxy?.preferredPort) && proxy.preferredPort >= 0 && proxy.preferredPort <= 65535
|
|
420
|
+
? proxy.preferredPort
|
|
421
|
+
: 0
|
|
422
|
+
|
|
423
|
+
return {
|
|
424
|
+
enabled: proxy?.enabled === true,
|
|
425
|
+
syncToOpenCode: proxy?.syncToOpenCode === true,
|
|
426
|
+
preferredPort,
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* ๐ getProxySettings: return normalized proxy settings from the live config.
|
|
432
|
+
* ๐ This centralizes the opt-in default so launchers do not guess.
|
|
433
|
+
*
|
|
434
|
+
* @param {object} config
|
|
435
|
+
* @returns {{ enabled: boolean, syncToOpenCode: boolean, preferredPort: number }}
|
|
436
|
+
*/
|
|
437
|
+
export function getProxySettings(config) {
|
|
438
|
+
return normalizeProxySettings(config?.settings?.proxy)
|
|
439
|
+
}
|
|
440
|
+
|
|
406
441
|
/**
|
|
407
442
|
* ๐ saveAsProfile: Snapshot the current config state into a named profile.
|
|
408
443
|
*
|
|
@@ -447,14 +482,16 @@ export function saveAsProfile(config, name, settings = null) {
|
|
|
447
482
|
export function loadProfile(config, name) {
|
|
448
483
|
const profile = config?.profiles?.[name]
|
|
449
484
|
if (!profile) return null
|
|
485
|
+
const nextSettings = profile.settings ? { ..._emptyProfileSettings(), ...profile.settings, proxy: normalizeProxySettings(profile.settings.proxy) } : _emptyProfileSettings()
|
|
450
486
|
|
|
451
487
|
// ๐ Deep-copy the profile data into the live config (don't share references)
|
|
452
488
|
config.apiKeys = JSON.parse(JSON.stringify(profile.apiKeys || {}))
|
|
453
489
|
config.providers = JSON.parse(JSON.stringify(profile.providers || {}))
|
|
454
490
|
config.favorites = [...(profile.favorites || [])]
|
|
491
|
+
config.settings = nextSettings
|
|
455
492
|
config.activeProfile = name
|
|
456
493
|
|
|
457
|
-
return
|
|
494
|
+
return nextSettings
|
|
458
495
|
}
|
|
459
496
|
|
|
460
497
|
/**
|
|
@@ -515,6 +552,7 @@ function _emptyConfig() {
|
|
|
515
552
|
// ๐ Global TUI preferences that should persist even without a named profile.
|
|
516
553
|
settings: {
|
|
517
554
|
hideUnconfiguredModels: true,
|
|
555
|
+
proxy: normalizeProxySettings(),
|
|
518
556
|
},
|
|
519
557
|
// ๐ Pinned favorites rendered at top of the table ("providerKey/modelId").
|
|
520
558
|
favorites: [],
|