free-coding-models 0.3.23 → 0.3.25
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 +41 -0
- package/README.md +72 -17
- package/package.json +1 -1
- package/sources.js +60 -0
- package/src/app.js +97 -6
- package/src/command-palette.js +3 -1
- package/src/constants.js +5 -2
- package/src/endpoint-installer.js +2 -1
- package/src/key-handler.js +607 -5
- package/src/mouse.js +186 -0
- package/src/overlays.js +159 -2
- package/src/provider-metadata.js +25 -0
- package/src/render-helpers.js +1 -1
- package/src/render-table.js +181 -8
- package/src/theme.js +6 -0
- package/src/tool-bootstrap.js +22 -0
- package/src/tool-launchers.js +93 -2
- package/src/tool-metadata.js +94 -11
- package/src/utils.js +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
---
|
|
3
3
|
|
|
4
|
+
## [0.3.25] - 2026-03-19
|
|
5
|
+
|
|
6
|
+
### Changed
|
|
7
|
+
- **Removed "CLI Tools" column** — The compat emoji column has been removed from the TUI table, freeing ~22 characters of horizontal space for other columns
|
|
8
|
+
- **Cleaner table layout** — Responsive column hiding no longer needs to drop the compat column first on narrow terminals
|
|
9
|
+
|
|
10
|
+
## [0.3.24] - 2026-03-19
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Unique emoji per tool** — Every CLI tool now has a dedicated emoji shown in the Compatible column, Z-cycle badge, command palette, help overlay, and README (📦 OpenCode, 🦞 OpenClaw, 💘 Crush, 🪿 Goose, π Pi, 🛠 Aider, 🐉 Qwen, 🤲 OpenHands, ⚡ Amp, 🦘 Rovo, ♊ Gemini)
|
|
14
|
+
- **Merged compat column** — OpenCode CLI and Desktop share 📦 in a single slot (11 slots instead of 12 separate initials)
|
|
15
|
+
- **COMPAT_COLUMN_SLOTS** — New export in tool-metadata.js for merged compatible-column rendering
|
|
16
|
+
- **Width warning now always shows** - Terminal width warning displays every time terminal is resized below 80 columns (previously limited to 2 shows per session)
|
|
17
|
+
- **Gemini CLI integration** - New CLI-only tool provider with 3 models (Gemini 3 Pro 🆕, Gemini 2.5 Pro, Gemini 2.5 Flash)
|
|
18
|
+
- **Rovo Dev CLI integration** - New CLI-only tool provider with Claude Sonnet 4 🆕
|
|
19
|
+
- **Tool compatibility alerts** - When trying to launch Rovo/Gemini models with wrong tool, shows alert and offers to switch
|
|
20
|
+
- **Auto-install detection** - Prompt to install CLI tools when binary not found (Rovo/Gemini)
|
|
21
|
+
- **OpenAI-compatible API support for Gemini** - Gemini CLI can use custom providers via environment variables
|
|
22
|
+
- **New CLI flags** - Added `--rovo` and `--gemini` launch options
|
|
23
|
+
- **"🆕" badges** - Mark newly added models in the table (Claude Sonnet 4, Gemini 3 Pro)
|
|
24
|
+
- **OpenCode Zen free models** - 5 new free models (Big Pickle, GPT 5 Nano, MiMo V2 Flash Free, MiniMax M2.5 Free, Nemotron 3 Super Free) exclusive to OpenCode CLI/Desktop via `opencode-zen` provider
|
|
25
|
+
- **"Compatible with" column** - New TUI column showing colored emojis for each tool a model supports; incompatible tools show dim spaces
|
|
26
|
+
- **Tool color system** - Each of the 12 supported tools now has a unique RGB color and emoji used in the Z-cycle badge and compatibility column
|
|
27
|
+
- **Incompatible model highlighting** - When a tool mode is active (via Z), models that can't run with that tool get a dark red background for instant visibility — they stay in their normal sorted position (not pushed to the bottom)
|
|
28
|
+
- **Tool compatibility functions** - `getCompatibleTools()` and `isModelCompatibleWithTool()` in tool-metadata.js for programmatic compatibility checks
|
|
29
|
+
- **Incompatible model fallback overlay** - When pressing Enter on a model that can't run on the active tool (red-highlighted row), an in-TUI overlay appears with two options: (1) switch to a compatible tool, or (2) pick a similar model by SWE score that works with the current tool
|
|
30
|
+
- **findSimilarCompatibleModels()** - New function in tool-metadata.js that finds models with closest SWE scores compatible with the active tool
|
|
31
|
+
- **Updated provider/model counts** - Now 23 providers with 171 models (was 20/160)
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
- **Z key cycle** - Rovo and Gemini added to tool mode cycle (last in order)
|
|
35
|
+
- **Tool metadata** - Removed `initial` field (replaced by emojis), added `cliOnly` flag for CLI-only tools, `emoji` and `color` properties for all 12 tools
|
|
36
|
+
- **Provider metadata** - Added Rovo, Gemini, and OpenCode Zen provider information
|
|
37
|
+
- **Responsive column hiding** - Compatible column hides first (before Rank) on narrow terminals
|
|
38
|
+
- **Key handler** - Zen models auto-switch to OpenCode CLI on launch; API key warnings skip Zen models
|
|
39
|
+
- **Documentation** - Updated README with CLI-only tools section, Zen models, compatibility matrix
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
- **Missing import error** - Fixed `getToolMeta` not defined in key-handler.js
|
|
43
|
+
- **CLI-only tools API key requirement** - Gemini CLI and Rovo Dev CLI no longer require API keys to be configured before launching; these tools manage their own authentication
|
|
44
|
+
|
|
4
45
|
## 0.3.23
|
|
5
46
|
|
|
6
47
|
### Added
|
package/README.md
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
<img src="https://img.shields.io/npm/v/free-coding-models?color=76b900&label=npm&logo=npm" alt="npm version">
|
|
3
3
|
<img src="https://img.shields.io/node/v/free-coding-models?color=76b900&logo=node.js" alt="node version">
|
|
4
4
|
<img src="https://img.shields.io/npm/l/free-coding-models?color=76b900" alt="license">
|
|
5
|
-
<img src="https://img.shields.io/badge/models-
|
|
6
|
-
<img src="https://img.shields.io/badge/providers-
|
|
5
|
+
<img src="https://img.shields.io/badge/models-174-76b900?logo=nvidia" alt="models count">
|
|
6
|
+
<img src="https://img.shields.io/badge/providers-23-blue" alt="providers count">
|
|
7
7
|
</p>
|
|
8
8
|
|
|
9
9
|
<h1 align="center">free-coding-models</h1>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
12
|
<strong>Find the fastest free coding model in seconds</strong><br>
|
|
13
|
-
<sub>Ping
|
|
13
|
+
<sub>Ping 174 models across 23 AI Free providers in real-time </sub><br><sub> Install Free API endpoints to your favorite AI coding tool: <br>📦 OpenCode, 🦞 OpenClaw, 💘 Crush, 🪿 Goose, 🛠 Aider, 🐉 Qwen Code, 🤲 OpenHands, ⚡ Amp, π Pi, 🦘 Rovo or ♊ Gemini in one keystroke</sub>
|
|
14
14
|
</p>
|
|
15
15
|
|
|
16
16
|
|
|
@@ -47,7 +47,7 @@ create a free account on one of the [providers](#-list-of-free-ai-providers)
|
|
|
47
47
|
|
|
48
48
|
## 💡 Why this tool?
|
|
49
49
|
|
|
50
|
-
There are **
|
|
50
|
+
There are **174+ free coding models** scattered across 23 providers. Which one is fastest right now? Which one is actually stable versus just lucky on the last ping?
|
|
51
51
|
|
|
52
52
|
This CLI pings them all in parallel, shows live latency, and calculates a **live Stability Score (0-100)**. Average latency alone is misleading if a model randomly spikes to 6 seconds; the stability score measures true reliability by combining **p95 latency** (30%), **jitter/variance** (30%), **spike rate** (20%), and **uptime** (20%).
|
|
53
53
|
|
|
@@ -61,7 +61,7 @@ It then writes the model you pick directly into your coding tool's config — so
|
|
|
61
61
|
|
|
62
62
|
Create a free account on one provider below to get started:
|
|
63
63
|
|
|
64
|
-
**
|
|
64
|
+
**174 coding models** across 23 providers, ranked by [SWE-bench Verified](https://www.swebench.com).
|
|
65
65
|
|
|
66
66
|
| Provider | Models | Tier range | Free tier | Env var |
|
|
67
67
|
|----------|--------|-----------|-----------|--------|
|
|
@@ -85,6 +85,9 @@ Create a free account on one provider below to get started:
|
|
|
85
85
|
| [Cloudflare Workers AI](https://dash.cloudflare.com) | 6 | S → B | Free: 10k neurons/day, text-gen 300 RPM | `CLOUDFLARE_API_TOKEN` + `CLOUDFLARE_ACCOUNT_ID` |
|
|
86
86
|
| [Perplexity API](https://www.perplexity.ai/settings/api) | 4 | A+ → B | Tiered limits by spend (default ~50 RPM) | `PERPLEXITY_API_KEY` |
|
|
87
87
|
| [Replicate](https://replicate.com/account/api-tokens) | 1 | A- | 6 req/min (no payment) – up to 3,000 RPM with payment | `REPLICATE_API_TOKEN` |
|
|
88
|
+
| [Rovo Dev CLI](https://www.atlassian.com/rovo) | 1 | S+ | 5M tokens/day (beta) | CLI tool 🦘 |
|
|
89
|
+
| [Gemini CLI](https://github.com/google-gemini/gemini-cli) | 3 | S+ → A+ | 1,000 req/day | CLI tool ♊ |
|
|
90
|
+
| [OpenCode Zen](https://opencode.ai/zen) | 8 | S+ → A+ | Free with OpenCode account | Zen models ✨ |
|
|
88
91
|
|
|
89
92
|
> 💡 One key is enough. Add more at any time with **`P`** inside the TUI.
|
|
90
93
|
|
|
@@ -121,7 +124,7 @@ Need to fix contrast because your terminal theme is fighting the TUI? Press **`G
|
|
|
121
124
|
↑↓ navigate → Enter to launch
|
|
122
125
|
```
|
|
123
126
|
|
|
124
|
-
The model you select is automatically written into your tool's config (OpenCode, OpenClaw, Crush, etc.) and the tool opens immediately. Done.
|
|
127
|
+
The model you select is automatically written into your tool's config (📦 OpenCode, 🦞 OpenClaw, 💘 Crush, etc.) and the tool opens immediately. Done.
|
|
125
128
|
|
|
126
129
|
If the active CLI tool is missing, FCM now catches it before launch, offers a tiny Yes/No install prompt, installs the tool with its official global command, then resumes the same model launch automatically.
|
|
127
130
|
|
|
@@ -157,19 +160,69 @@ free-coding-models --openclaw --origin groq
|
|
|
157
160
|
|
|
158
161
|
| Flag | Launches |
|
|
159
162
|
|------|----------|
|
|
160
|
-
| `--opencode` | OpenCode CLI |
|
|
161
|
-
| `--opencode-desktop` | OpenCode Desktop |
|
|
162
|
-
| `--openclaw` | OpenClaw |
|
|
163
|
-
| `--crush` | Crush |
|
|
164
|
-
| `--goose` | Goose |
|
|
165
|
-
| `--aider` | Aider |
|
|
166
|
-
| `--qwen` | Qwen Code |
|
|
167
|
-
| `--openhands` | OpenHands |
|
|
168
|
-
| `--amp` | Amp |
|
|
169
|
-
| `--pi` | Pi |
|
|
163
|
+
| `--opencode` | 📦 OpenCode CLI |
|
|
164
|
+
| `--opencode-desktop` | 📦 OpenCode Desktop |
|
|
165
|
+
| `--openclaw` | 🦞 OpenClaw |
|
|
166
|
+
| `--crush` | 💘 Crush |
|
|
167
|
+
| `--goose` | 🪿 Goose |
|
|
168
|
+
| `--aider` | 🛠 Aider |
|
|
169
|
+
| `--qwen` | 🐉 Qwen Code |
|
|
170
|
+
| `--openhands` | 🤲 OpenHands |
|
|
171
|
+
| `--amp` | ⚡ Amp |
|
|
172
|
+
| `--pi` | π Pi |
|
|
173
|
+
| `--rovo` | 🦘 Rovo Dev CLI |
|
|
174
|
+
| `--gemini` | ♊ Gemini CLI |
|
|
170
175
|
|
|
171
176
|
Press **`Z`** in the TUI to cycle between tools without restarting.
|
|
172
177
|
|
|
178
|
+
### CLI-Only Tools
|
|
179
|
+
|
|
180
|
+
**🦘 Rovo Dev CLI**
|
|
181
|
+
- Provider: [Atlassian Rovo](https://www.atlassian.com/rovo)
|
|
182
|
+
- Install: [Installation Guide](https://support.atlassian.com/rovo/docs/install-and-run-rovo-dev-cli-on-your-device/)
|
|
183
|
+
- Free tier: 5M tokens/day (beta, requires Atlassian account)
|
|
184
|
+
- Model: Claude Sonnet 4 (72.7% SWE-bench)
|
|
185
|
+
- Launch: `free-coding-models --rovo` or press `Z` until Rovo mode
|
|
186
|
+
- Features: Jira/Confluence integration, MCP server support
|
|
187
|
+
|
|
188
|
+
**♊ Gemini CLI**
|
|
189
|
+
- Provider: [Google Gemini](https://github.com/google-gemini/gemini-cli)
|
|
190
|
+
- Install: `npm install -g @google/gemini-cli`
|
|
191
|
+
- Free tier: 1,000 requests/day (personal Google account, no credit card)
|
|
192
|
+
- Models: Gemini 3 Pro (76.2% SWE-bench), Gemini 2.5 Pro, Gemini 2.5 Flash
|
|
193
|
+
- Launch: `free-coding-models --gemini` or press `Z` until Gemini mode
|
|
194
|
+
- Features: OpenAI-compatible API support, MCP server support, Google Search grounding
|
|
195
|
+
|
|
196
|
+
**Note:** When launching these tools via `Z` key or command palette, if the current mode doesn't match the tool, you'll see a confirmation alert asking to switch to the correct tool before launching.
|
|
197
|
+
|
|
198
|
+
### OpenCode Zen Free Models
|
|
199
|
+
|
|
200
|
+
[OpenCode Zen](https://opencode.ai/zen) is a hosted AI gateway offering 8 free coding models exclusively through OpenCode CLI and OpenCode Desktop. These models are **not** available through other tools.
|
|
201
|
+
|
|
202
|
+
| Model | Tier | SWE-bench | Context |
|
|
203
|
+
|-------|------|-----------|---------|
|
|
204
|
+
| Big Pickle | S+ | 72.0% | 200k |
|
|
205
|
+
| MiniMax M2.5 Free | S+ | 80.2% | 200k |
|
|
206
|
+
| MiMo V2 Pro Free | S+ | 78.0% | 1M |
|
|
207
|
+
| MiMo V2 Omni Free | S | 64.0% | 128k |
|
|
208
|
+
| MiMo V2 Flash Free | S+ | 73.4% | 256k |
|
|
209
|
+
| Nemotron 3 Super Free | A+ | 52.0% | 128k |
|
|
210
|
+
| GPT 5 Nano | S | 65.0% | 128k |
|
|
211
|
+
| Trinity Large Preview Free | S | 62.0% | 128k |
|
|
212
|
+
|
|
213
|
+
To use Zen models: sign up at [opencode.ai/auth](https://opencode.ai/auth) and enter your Zen API key via `P` (Settings). Zen models appear in the main table and auto-switch to OpenCode CLI on launch.
|
|
214
|
+
|
|
215
|
+
### Tool Compatibility
|
|
216
|
+
|
|
217
|
+
When a tool mode is active (via `Z`), models incompatible with that tool are highlighted with a dark red background so you can instantly see which models work with your current tool.
|
|
218
|
+
|
|
219
|
+
| Model Type | Compatible Tools |
|
|
220
|
+
|------------|-----------------|
|
|
221
|
+
| Regular (NVIDIA, Groq, etc.) | All tools except 🦘 Rovo and ♊ Gemini |
|
|
222
|
+
| Rovo | 🦘 Rovo Dev CLI only |
|
|
223
|
+
| Gemini | ♊ Gemini CLI only |
|
|
224
|
+
| OpenCode Zen | 📦 OpenCode CLI and 📦 OpenCode Desktop only |
|
|
225
|
+
|
|
173
226
|
→ **[Full flags reference](./docs/flags.md)**
|
|
174
227
|
|
|
175
228
|
---
|
|
@@ -204,7 +257,7 @@ Press **`Z`** in the TUI to cycle between tools without restarting.
|
|
|
204
257
|
|
|
205
258
|
## ✨ Features
|
|
206
259
|
|
|
207
|
-
- **Parallel pings** — all
|
|
260
|
+
- **Parallel pings** — all 174 models tested simultaneously via native `fetch`
|
|
208
261
|
- **Adaptive monitoring** — 2s burst for 60s → 10s normal → 30s idle
|
|
209
262
|
- **Stability score** — composite 0–100 (p95 latency, jitter, spike rate, uptime)
|
|
210
263
|
- **Smart ranking** — top 3 highlighted 🥇🥈🥉
|
|
@@ -215,6 +268,8 @@ Press **`Z`** in the TUI to cycle between tools without restarting.
|
|
|
215
268
|
- **⚡️ Command Palette** — `Ctrl+P` opens a searchable action launcher for filters, sorting, overlays, and quick toggles
|
|
216
269
|
- **Install Endpoints** — push a full provider catalog into any tool's config (from Settings `P` or ⚡️ Command Palette)
|
|
217
270
|
- **Missing tool bootstrap** — detect absent CLIs, offer one-click install, then continue the selected launch automatically
|
|
271
|
+
- **Tool compatibility matrix** — incompatible rows highlighted in dark red when a tool mode is active
|
|
272
|
+
- **OpenCode Zen models** — 8 free models exclusive to OpenCode CLI/Desktop, powered by the Zen AI gateway
|
|
218
273
|
- **Width guardrail** — shows a warning instead of a broken table in narrow terminals
|
|
219
274
|
- **Readable everywhere** — semantic theme palette keeps table rows, overlays, badges, and help screens legible in dark and light terminals
|
|
220
275
|
- **Global theme switch** — `G` cycles `auto`, `dark`, and `light` live without restarting
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "free-coding-models",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.25",
|
|
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/sources.js
CHANGED
|
@@ -344,6 +344,40 @@ export const iflow = [
|
|
|
344
344
|
['qwen3-max', 'Qwen3 Max', 'A+', '55.0%', '256k'],
|
|
345
345
|
]
|
|
346
346
|
|
|
347
|
+
// 📖 Rovo Dev CLI source - https://www.atlassian.com/rovo
|
|
348
|
+
// 📖 CLI tool only - no API endpoint - requires 'acli rovodev run'
|
|
349
|
+
// 📖 Install: https://support.atlassian.com/rovo/docs/install-and-run-rovo-dev-cli-on-your-device/
|
|
350
|
+
// 📖 Free tier: 5M tokens/day (beta) - Claude Sonnet 4 (72.7% SWE-bench)
|
|
351
|
+
// 📖 Requires Atlassian account + Rovo Dev activated on your site
|
|
352
|
+
export const rovo = [
|
|
353
|
+
['anthropic/claude-sonnet-4', 'Claude Sonnet 4 🆕', 'S+', '72.7%', '200k'],
|
|
354
|
+
]
|
|
355
|
+
|
|
356
|
+
// 📖 Gemini CLI source - https://github.com/google-gemini/gemini-cli
|
|
357
|
+
// 📖 CLI tool with OpenAI-compatible API support
|
|
358
|
+
// 📖 Install: npm install -g @google/gemini-cli
|
|
359
|
+
// 📖 Free tier: 1,000 req/day with personal Google account (no credit card)
|
|
360
|
+
// 📖 Models: Gemini 3 Pro (76.2% SWE-bench), Gemini 2.5 Pro, Gemini 2.5 Flash
|
|
361
|
+
// 📖 Supports custom OpenAI-compatible providers via GEMINI_API_BASE_URL
|
|
362
|
+
export const gemini = [
|
|
363
|
+
['google/gemini-3-pro', 'Gemini 3 Pro 🆕', 'S+', '76.2%', '1M'],
|
|
364
|
+
['google/gemini-2.5-pro', 'Gemini 2.5 Pro', 'S+', '63.2%', '1M'],
|
|
365
|
+
['google/gemini-2.5-flash', 'Gemini 2.5 Flash', 'A+', '50.0%', '1M'],
|
|
366
|
+
]
|
|
367
|
+
|
|
368
|
+
// 📖 OpenCode Zen free models — hosted AI gateway accessed through OpenCode CLI/Desktop
|
|
369
|
+
// 📖 Endpoint: https://opencode.ai/zen/v1/... — requires OpenCode Zen API key
|
|
370
|
+
// 📖 These models are FREE on the Zen platform and only run on OpenCode CLI or OpenCode Desktop
|
|
371
|
+
// 📖 Login: https://opencode.ai/auth — get your Zen API key
|
|
372
|
+
// 📖 Config: set provider to opencode/<model-id> in OpenCode config
|
|
373
|
+
export const opencodeZen = [
|
|
374
|
+
['big-pickle', 'Big Pickle 🆕', 'S+', '72.0%', '200k'],
|
|
375
|
+
['gpt-5-nano', 'GPT 5 Nano 🆕', 'S', '65.0%', '128k'],
|
|
376
|
+
['mimo-v2-flash-free', 'MiMo V2 Flash Free 🆕', 'S+', '73.4%', '256k'],
|
|
377
|
+
['minimax-m2.5-free', 'MiniMax M2.5 Free 🆕', 'S+', '80.2%', '200k'],
|
|
378
|
+
['nemotron-3-super-free', 'Nemotron 3 Super Free 🆕', 'A+', '52.0%', '128k'],
|
|
379
|
+
]
|
|
380
|
+
|
|
347
381
|
// 📖 All sources combined - used by the main script
|
|
348
382
|
// 📖 Each source has: name (display), url (API endpoint), models (array of model tuples)
|
|
349
383
|
export const sources = {
|
|
@@ -447,6 +481,32 @@ export const sources = {
|
|
|
447
481
|
url: 'https://apis.iflow.cn/v1/chat/completions',
|
|
448
482
|
models: iflow,
|
|
449
483
|
},
|
|
484
|
+
// 📖 CLI-only tools (no API endpoint - launched directly)
|
|
485
|
+
rovo: {
|
|
486
|
+
name: 'Rovo Dev CLI',
|
|
487
|
+
url: null, // CLI tool - no API endpoint
|
|
488
|
+
models: rovo,
|
|
489
|
+
cliOnly: true,
|
|
490
|
+
installUrl: 'https://support.atlassian.com/rovo/docs/install-and-run-rovo-dev-cli-on-your-device/',
|
|
491
|
+
binary: 'acli',
|
|
492
|
+
checkArgs: ['rovodev', '--help'],
|
|
493
|
+
},
|
|
494
|
+
gemini: {
|
|
495
|
+
name: 'Gemini CLI',
|
|
496
|
+
url: null, // CLI tool - no API endpoint (can use OpenAI-compatible via env)
|
|
497
|
+
models: gemini,
|
|
498
|
+
cliOnly: true,
|
|
499
|
+
installUrl: 'https://github.com/google-gemini/gemini-cli',
|
|
500
|
+
binary: 'gemini',
|
|
501
|
+
checkArgs: ['--version'],
|
|
502
|
+
},
|
|
503
|
+
// 📖 OpenCode Zen free models — hosted AI gateway, only runs on OpenCode CLI / Desktop
|
|
504
|
+
'opencode-zen': {
|
|
505
|
+
name: 'OpenCode Zen',
|
|
506
|
+
url: 'https://opencode.ai/zen/v1/chat/completions',
|
|
507
|
+
models: opencodeZen,
|
|
508
|
+
zenOnly: true,
|
|
509
|
+
},
|
|
450
510
|
}
|
|
451
511
|
|
|
452
512
|
// 📖 Flatten all models from all sources — each entry includes providerKey as 6th element
|
package/src/app.js
CHANGED
|
@@ -119,7 +119,8 @@ import { renderTable, PROVIDER_COLOR } from '../src/render-table.js'
|
|
|
119
119
|
import { setOpenCodeModelData, startOpenCode, startOpenCodeDesktop } from '../src/opencode.js'
|
|
120
120
|
import { startOpenClaw } from '../src/openclaw.js'
|
|
121
121
|
import { createOverlayRenderers } from '../src/overlays.js'
|
|
122
|
-
import { createKeyHandler } from '../src/key-handler.js'
|
|
122
|
+
import { createKeyHandler, createMouseEventHandler } from '../src/key-handler.js'
|
|
123
|
+
import { createMouseHandler, containsMouseSequence } from '../src/mouse.js'
|
|
123
124
|
import { getToolModeOrder, getToolMeta } from '../src/tool-metadata.js'
|
|
124
125
|
import { startExternalTool } from '../src/tool-launchers.js'
|
|
125
126
|
import { getToolInstallPlan, installToolWithPlan, isToolInstalled } from '../src/tool-bootstrap.js'
|
|
@@ -233,6 +234,8 @@ export async function runApp(cliArgs, config) {
|
|
|
233
234
|
openhands: cliArgs.openHandsMode,
|
|
234
235
|
amp: cliArgs.ampMode,
|
|
235
236
|
pi: cliArgs.piMode,
|
|
237
|
+
rovo: cliArgs.rovoMode,
|
|
238
|
+
gemini: cliArgs.geminiMode,
|
|
236
239
|
}
|
|
237
240
|
return flagByMode[toolMode] === true
|
|
238
241
|
})
|
|
@@ -389,7 +392,7 @@ export async function runApp(cliArgs, config) {
|
|
|
389
392
|
terminalCols: process.stdout.columns || 80, // 📖 Current terminal width
|
|
390
393
|
widthWarningStartedAt: (process.stdout.columns || 80) < WIDTH_WARNING_MIN_COLS ? now : null, // 📖 Start immediately in very narrow viewports.
|
|
391
394
|
widthWarningDismissed: false, // 📖 Esc hides the narrow-terminal warning early for the current narrow-width session.
|
|
392
|
-
widthWarningShowCount: 0, // 📖
|
|
395
|
+
widthWarningShowCount: 0, // 📖 No longer used — kept for backward compatibility. Warning now shows every time terminal is too small.
|
|
393
396
|
// 📖 Settings screen state (P key opens it)
|
|
394
397
|
settingsOpen: false, // 📖 Whether settings overlay is active
|
|
395
398
|
settingsCursor: 0, // 📖 Which provider row is selected in settings
|
|
@@ -434,6 +437,15 @@ export async function runApp(cliArgs, config) {
|
|
|
434
437
|
toolInstallPromptModel: null,
|
|
435
438
|
toolInstallPromptPlan: null,
|
|
436
439
|
toolInstallPromptErrorMsg: null,
|
|
440
|
+
// 📖 Incompatible model fallback overlay — shown when user presses Enter on a red-highlighted model.
|
|
441
|
+
// 📖 Offers two options: switch to a compatible tool, or pick a similar SWE-scored model.
|
|
442
|
+
incompatibleFallbackOpen: false,
|
|
443
|
+
incompatibleFallbackCursor: 0,
|
|
444
|
+
incompatibleFallbackScrollOffset: 0,
|
|
445
|
+
incompatibleFallbackModel: null, // 📖 The incompatible model the user tried to launch
|
|
446
|
+
incompatibleFallbackTools: [], // 📖 Compatible tools for the selected model
|
|
447
|
+
incompatibleFallbackSimilarModels: [], // 📖 Similar SWE models compatible with current tool
|
|
448
|
+
incompatibleFallbackSection: 'tools', // 📖 'tools' or 'models' — which section cursor is in
|
|
437
449
|
// 📖 Smart Recommend overlay state (Q key opens it)
|
|
438
450
|
recommendOpen: false, // 📖 Whether the recommend overlay is active
|
|
439
451
|
recommendPhase: 'questionnaire', // 📖 'questionnaire'|'analyzing'|'results' — current phase
|
|
@@ -472,7 +484,6 @@ export async function runApp(cliArgs, config) {
|
|
|
472
484
|
if (prevCols >= WIDTH_WARNING_MIN_COLS || state.widthWarningDismissed) {
|
|
473
485
|
state.widthWarningStartedAt = Date.now()
|
|
474
486
|
state.widthWarningDismissed = false
|
|
475
|
-
state.widthWarningShowCount++ // 📖 Increment counter when showing the warning again
|
|
476
487
|
} else if (!state.widthWarningStartedAt) {
|
|
477
488
|
state.widthWarningStartedAt = Date.now()
|
|
478
489
|
}
|
|
@@ -485,6 +496,7 @@ export async function runApp(cliArgs, config) {
|
|
|
485
496
|
|
|
486
497
|
let ticker = null
|
|
487
498
|
let onKeyPress = null
|
|
499
|
+
let onMouseData = null // 📖 Mouse data listener — set after createMouseEventHandler
|
|
488
500
|
let pingModel = null
|
|
489
501
|
|
|
490
502
|
const scheduleNextPing = () => {
|
|
@@ -684,7 +696,11 @@ export async function runApp(cliArgs, config) {
|
|
|
684
696
|
r.hidden = false
|
|
685
697
|
return
|
|
686
698
|
}
|
|
687
|
-
|
|
699
|
+
// 📖 CLI-only tools (rovo, gemini) and Zen models don't need traditional API keys —
|
|
700
|
+
// 📖 they authenticate via their own CLI login flow, so "configured only" should never hide them.
|
|
701
|
+
const providerMeta = PROVIDER_METADATA[r.providerKey]
|
|
702
|
+
const noKeyNeeded = providerMeta?.cliOnly || providerMeta?.zenOnly
|
|
703
|
+
const unconfiguredHide = state.hideUnconfiguredModels && !noKeyNeeded && !getApiKey(state.config, r.providerKey)
|
|
688
704
|
if (unconfiguredHide) {
|
|
689
705
|
r.hidden = true
|
|
690
706
|
return
|
|
@@ -722,6 +738,7 @@ export async function runApp(cliArgs, config) {
|
|
|
722
738
|
if (ticker) clearInterval(ticker)
|
|
723
739
|
clearTimeout(state.pingIntervalObj)
|
|
724
740
|
if (onKeyPress) process.stdin.removeListener('keypress', onKeyPress)
|
|
741
|
+
if (onMouseData) process.stdin.removeListener('data', onMouseData)
|
|
725
742
|
if (process.stdin.isTTY && resetRawMode) process.stdin.setRawMode(false)
|
|
726
743
|
process.stdin.pause()
|
|
727
744
|
process.stdout.write(ALT_LEAVE)
|
|
@@ -800,6 +817,7 @@ export async function runApp(cliArgs, config) {
|
|
|
800
817
|
startOpenCode,
|
|
801
818
|
startExternalTool,
|
|
802
819
|
getToolModeOrder,
|
|
820
|
+
getToolMeta,
|
|
803
821
|
getToolInstallPlan,
|
|
804
822
|
isToolInstalled,
|
|
805
823
|
installToolWithPlan,
|
|
@@ -822,6 +840,38 @@ export async function runApp(cliArgs, config) {
|
|
|
822
840
|
readline,
|
|
823
841
|
})
|
|
824
842
|
|
|
843
|
+
// 📖 Mouse event handler: translates parsed mouse events into TUI actions (sort, cursor, scroll).
|
|
844
|
+
const onMouseEvent = createMouseEventHandler({
|
|
845
|
+
state,
|
|
846
|
+
adjustScrollOffset,
|
|
847
|
+
applyTierFilter,
|
|
848
|
+
TIER_CYCLE,
|
|
849
|
+
ORIGIN_CYCLE,
|
|
850
|
+
noteUserActivity,
|
|
851
|
+
sortResultsWithPinnedFavorites,
|
|
852
|
+
saveConfig,
|
|
853
|
+
overlayLayout: overlays.overlayLayout, // 📖 Overlay cursor-to-line maps for click handling
|
|
854
|
+
// 📖 Favorite toggle — right-click on model rows
|
|
855
|
+
toggleFavoriteModel,
|
|
856
|
+
syncFavoriteFlags,
|
|
857
|
+
toFavoriteKey,
|
|
858
|
+
// 📖 Tool mode cycling — compat header click
|
|
859
|
+
cycleToolMode: () => {
|
|
860
|
+
// 📖 Inline cycle matching the Z-key handler in createKeyHandler
|
|
861
|
+
const modeOrder = getToolModeOrder()
|
|
862
|
+
const currentIndex = modeOrder.indexOf(state.mode)
|
|
863
|
+
const nextIndex = (currentIndex + 1) % modeOrder.length
|
|
864
|
+
state.mode = modeOrder[nextIndex]
|
|
865
|
+
if (!state.config.settings || typeof state.config.settings !== 'object') state.config.settings = {}
|
|
866
|
+
state.config.settings.preferredToolMode = state.mode
|
|
867
|
+
saveConfig(state.config)
|
|
868
|
+
},
|
|
869
|
+
})
|
|
870
|
+
|
|
871
|
+
// 📖 Wire the raw stdin data listener for mouse events.
|
|
872
|
+
// 📖 createMouseHandler returns a function that parses SGR sequences and calls onMouseEvent.
|
|
873
|
+
onMouseData = createMouseHandler({ onMouseEvent })
|
|
874
|
+
|
|
825
875
|
// Apply CLI --tier filter if provided
|
|
826
876
|
if (cliArgs.tierFilter) {
|
|
827
877
|
const allowed = TIER_LETTER_MAP[cliArgs.tierFilter]
|
|
@@ -843,8 +893,35 @@ export async function runApp(cliArgs, config) {
|
|
|
843
893
|
process.stdin.setRawMode(true)
|
|
844
894
|
}
|
|
845
895
|
|
|
896
|
+
// 📖 Mouse sequence suppression: readline.emitKeypressEvents() registers its own
|
|
897
|
+
// 📖 internal `data` listener that parses bytes and fires `keypress` events.
|
|
898
|
+
// 📖 When a mouse SGR sequence like \x1b[<0;35;20m arrives, readline fragments it
|
|
899
|
+
// 📖 and emits individual keypress events for chars like 'm', '0', ';' etc.
|
|
900
|
+
// 📖 The 'm' at the end of a release event maps to the Model sort hotkey!
|
|
901
|
+
// 📖
|
|
902
|
+
// 📖 Fix: use prependListener to register a `data` handler BEFORE readline's,
|
|
903
|
+
// 📖 so we can set a suppression flag before any keypress events fire.
|
|
904
|
+
// 📖 The flag is cleared on the next tick via setImmediate after all synchronous
|
|
905
|
+
// 📖 keypress emissions from readline have completed.
|
|
906
|
+
let _suppressMouseKeypresses = false
|
|
907
|
+
|
|
908
|
+
process.stdin.prependListener('data', (data) => {
|
|
909
|
+
const str = typeof data === 'string' ? data : data.toString('utf8')
|
|
910
|
+
if (str.includes('\x1b[<')) {
|
|
911
|
+
_suppressMouseKeypresses = true
|
|
912
|
+
// 📖 Reset after current tick — all synchronous keypress events from this data
|
|
913
|
+
// 📖 chunk will have fired by then.
|
|
914
|
+
setImmediate(() => { _suppressMouseKeypresses = false })
|
|
915
|
+
}
|
|
916
|
+
})
|
|
917
|
+
|
|
846
918
|
process.stdin.on('keypress', async (str, key) => {
|
|
847
919
|
try {
|
|
920
|
+
// 📖 Skip keypress events that originate from mouse escape sequences.
|
|
921
|
+
// 📖 readline may partially parse SGR mouse sequences as garbage keypresses.
|
|
922
|
+
if (str && containsMouseSequence(str)) return
|
|
923
|
+
// 📖 Suppress fragmented mouse bytes that readline emits as individual keypresses.
|
|
924
|
+
if (_suppressMouseKeypresses) return
|
|
848
925
|
await onKeyPress(str, key);
|
|
849
926
|
} catch (err) {
|
|
850
927
|
process.stdout.write(ALT_LEAVE);
|
|
@@ -854,6 +931,18 @@ export async function runApp(cliArgs, config) {
|
|
|
854
931
|
process.exit(1);
|
|
855
932
|
}
|
|
856
933
|
})
|
|
934
|
+
|
|
935
|
+
// 📖 Mouse data listener: parses SGR mouse escape sequences from raw stdin
|
|
936
|
+
// 📖 and dispatches structured events (click, scroll, double-click) to the mouse handler.
|
|
937
|
+
process.stdin.on('data', (data) => {
|
|
938
|
+
try {
|
|
939
|
+
if (onMouseData) onMouseData(data)
|
|
940
|
+
} catch (err) {
|
|
941
|
+
// 📖 Mouse errors are non-fatal — log and continue so the TUI doesn't crash.
|
|
942
|
+
// 📖 This could happen on terminals that send unexpected mouse sequences.
|
|
943
|
+
}
|
|
944
|
+
})
|
|
945
|
+
|
|
857
946
|
process.on('SIGCONT', noteUserActivity)
|
|
858
947
|
|
|
859
948
|
// 📖 Animation loop: render settings overlay, recommend overlay, help overlay, feature request overlay, bug report overlay, changelog overlay, OR main table
|
|
@@ -862,7 +951,7 @@ export async function runApp(cliArgs, config) {
|
|
|
862
951
|
refreshAutoPingMode()
|
|
863
952
|
state.frame++
|
|
864
953
|
// 📖 Cache visible+sorted models each frame so Enter handler always matches the display
|
|
865
|
-
if (!state.settingsOpen && !state.installEndpointsOpen && !state.toolInstallPromptOpen && !state.recommendOpen && !state.feedbackOpen && !state.changelogOpen && !state.commandPaletteOpen) {
|
|
954
|
+
if (!state.settingsOpen && !state.installEndpointsOpen && !state.toolInstallPromptOpen && !state.incompatibleFallbackOpen && !state.recommendOpen && !state.feedbackOpen && !state.changelogOpen && !state.commandPaletteOpen) {
|
|
866
955
|
const visible = state.results.filter(r => !r.hidden)
|
|
867
956
|
state.visibleSorted = sortResultsWithPinnedFavorites(visible, state.sortColumn, state.sortDirection, {
|
|
868
957
|
pinFavorites: state.favoritesPinnedAndSticky,
|
|
@@ -945,6 +1034,8 @@ export async function runApp(cliArgs, config) {
|
|
|
945
1034
|
? overlays.renderInstallEndpoints()
|
|
946
1035
|
: state.toolInstallPromptOpen
|
|
947
1036
|
? overlays.renderToolInstallPrompt()
|
|
1037
|
+
: state.incompatibleFallbackOpen
|
|
1038
|
+
? overlays.renderIncompatibleFallback()
|
|
948
1039
|
: state.commandPaletteOpen
|
|
949
1040
|
? tableContent + overlays.renderCommandPalette()
|
|
950
1041
|
: state.recommendOpen
|
|
@@ -975,7 +1066,7 @@ export async function runApp(cliArgs, config) {
|
|
|
975
1066
|
pinFavorites: state.favoritesPinnedAndSticky,
|
|
976
1067
|
})
|
|
977
1068
|
|
|
978
|
-
|
|
1069
|
+
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, null, state.pingMode, state.pingModeSource, state.hideUnconfiguredModels, state.widthWarningStartedAt, state.widthWarningDismissed, state.widthWarningShowCount, state.settingsUpdateState, state.settingsUpdateLatestVersion, false, state.startupLatestVersion, state.versionAlertsEnabled, state.favoritesPinnedAndSticky, state.customTextFilter))
|
|
979
1070
|
if (process.stdout.isTTY) {
|
|
980
1071
|
process.stdout.flush && process.stdout.flush()
|
|
981
1072
|
}
|
package/src/command-palette.js
CHANGED
|
@@ -28,13 +28,15 @@ const TOOL_MODE_DESCRIPTIONS = {
|
|
|
28
28
|
qwen: 'Launch Qwen Code using the selected provider model.',
|
|
29
29
|
openhands: 'Launch OpenHands with the selected model endpoint.',
|
|
30
30
|
amp: 'Launch Amp with this model as active target.',
|
|
31
|
+
rovo: 'Rovo Dev CLI model (launch with Rovo tool only).',
|
|
32
|
+
gemini: 'Gemini CLI model (launch with Gemini tool only).',
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
const TOOL_MODE_COMMANDS = TOOL_MODE_ORDER.map((toolMode) => {
|
|
34
36
|
const meta = TOOL_METADATA[toolMode] || { label: toolMode, emoji: '🧰' }
|
|
35
37
|
return {
|
|
36
38
|
id: `action-set-tool-${toolMode}`,
|
|
37
|
-
label: meta.label
|
|
39
|
+
label: `${meta.emoji} ${meta.label}`,
|
|
38
40
|
toolMode,
|
|
39
41
|
icon: meta.emoji,
|
|
40
42
|
description: TOOL_MODE_DESCRIPTIONS[toolMode] || 'Set this as the active launch target.',
|
package/src/constants.js
CHANGED
|
@@ -47,8 +47,11 @@ import chalk from 'chalk'
|
|
|
47
47
|
// 📖 \x1b[H = cursor to top
|
|
48
48
|
// 📖 \x1b[?7l disables auto-wrap so wide rows clip at the right edge instead of
|
|
49
49
|
// 📖 wrapping to the next line (which would double the row height and overflow).
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
// 📖 Mouse tracking sequences are appended/prepended so clicks and scroll work in the TUI.
|
|
51
|
+
import { MOUSE_ENABLE, MOUSE_DISABLE } from './mouse.js'
|
|
52
|
+
|
|
53
|
+
export const ALT_ENTER = '\x1b[?1049h\x1b[?25l\x1b[?7l' + MOUSE_ENABLE
|
|
54
|
+
export const ALT_LEAVE = MOUSE_DISABLE + '\x1b[?7h\x1b[?1049l\x1b[?25h'
|
|
52
55
|
export const ALT_HOME = '\x1b[H'
|
|
53
56
|
|
|
54
57
|
// 📖 Timing constants — control how fast the health-check loop runs.
|
|
@@ -48,7 +48,8 @@ import { getApiKey, saveConfig } from './config.js'
|
|
|
48
48
|
import { ENV_VAR_NAMES, PROVIDER_METADATA } from './provider-metadata.js'
|
|
49
49
|
import { getToolMeta } from './tool-metadata.js'
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
// 📖 CLI-only providers (rovo, gemini) and Zen-only (opencode-zen) cannot be installed into other tools.
|
|
52
|
+
const DIRECT_INSTALL_UNSUPPORTED_PROVIDERS = new Set(['replicate', 'zai', 'rovo', 'gemini', 'opencode-zen'])
|
|
52
53
|
// 📖 Install Endpoints only lists tools whose persisted config shape is actually supported here.
|
|
53
54
|
// 📖 Claude Code, Codex, and Gemini stay out while their dedicated bridges are being rebuilt.
|
|
54
55
|
const INSTALL_TARGET_MODES = ['opencode', 'opencode-desktop', 'openclaw', 'crush', 'goose', 'pi', 'aider', 'qwen', 'openhands', 'amp']
|