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 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-160-76b900?logo=nvidia" alt="models count">
6
- <img src="https://img.shields.io/badge/providers-20-blue" alt="providers count">
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 160 models across 20 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 or Pi in one keystroke</sub>
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 **160+ free coding models** scattered across 20 providers. Which one is fastest right now? Which one is actually stable versus just lucky on the last ping?
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
- **160 coding models** across 20 providers, ranked by [SWE-bench Verified](https://www.swebench.com).
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 160 models tested simultaneously via native `fetch`
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.23",
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, // 📖 Counter for how many times the narrow-terminal warning has been shown (max 2 per session).
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
- const unconfiguredHide = state.hideUnconfiguredModels && !getApiKey(state.config, r.providerKey)
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
- 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))
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
  }
@@ -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
- export const ALT_ENTER = '\x1b[?1049h\x1b[?25l\x1b[?7l'
51
- export const ALT_LEAVE = '\x1b[?7h\x1b[?1049l\x1b[?25h'
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
- const DIRECT_INSTALL_UNSUPPORTED_PROVIDERS = new Set(['replicate', 'zai'])
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']