free-coding-models 0.3.22 → 0.3.24

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,54 @@
1
1
  # Changelog
2
2
  ---
3
3
 
4
+ ## [0.3.24] - 2026-03-19
5
+
6
+ ### Added
7
+ - **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)
8
+ - **Merged compat column** — OpenCode CLI and Desktop share 📦 in a single slot (11 slots instead of 12 separate initials)
9
+ - **COMPAT_COLUMN_SLOTS** — New export in tool-metadata.js for merged compatible-column rendering
10
+ - **Width warning now always shows** - Terminal width warning displays every time terminal is resized below 80 columns (previously limited to 2 shows per session)
11
+ - **Gemini CLI integration** - New CLI-only tool provider with 3 models (Gemini 3 Pro 🆕, Gemini 2.5 Pro, Gemini 2.5 Flash)
12
+ - **Rovo Dev CLI integration** - New CLI-only tool provider with Claude Sonnet 4 🆕
13
+ - **Tool compatibility alerts** - When trying to launch Rovo/Gemini models with wrong tool, shows alert and offers to switch
14
+ - **Auto-install detection** - Prompt to install CLI tools when binary not found (Rovo/Gemini)
15
+ - **OpenAI-compatible API support for Gemini** - Gemini CLI can use custom providers via environment variables
16
+ - **New CLI flags** - Added `--rovo` and `--gemini` launch options
17
+ - **"🆕" badges** - Mark newly added models in the table (Claude Sonnet 4, Gemini 3 Pro)
18
+ - **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
19
+ - **"Compatible with" column** - New TUI column showing colored emojis for each tool a model supports; incompatible tools show dim spaces
20
+ - **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
21
+ - **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)
22
+ - **Tool compatibility functions** - `getCompatibleTools()` and `isModelCompatibleWithTool()` in tool-metadata.js for programmatic compatibility checks
23
+ - **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
24
+ - **findSimilarCompatibleModels()** - New function in tool-metadata.js that finds models with closest SWE scores compatible with the active tool
25
+ - **Updated provider/model counts** - Now 23 providers with 171 models (was 20/160)
26
+
27
+ ### Changed
28
+ - **Z key cycle** - Rovo and Gemini added to tool mode cycle (last in order)
29
+ - **Tool metadata** - Removed `initial` field (replaced by emojis), added `cliOnly` flag for CLI-only tools, `emoji` and `color` properties for all 12 tools
30
+ - **Provider metadata** - Added Rovo, Gemini, and OpenCode Zen provider information
31
+ - **Responsive column hiding** - Compatible column hides first (before Rank) on narrow terminals
32
+ - **Key handler** - Zen models auto-switch to OpenCode CLI on launch; API key warnings skip Zen models
33
+ - **Documentation** - Updated README with CLI-only tools section, Zen models, compatibility matrix
34
+
35
+ ### Fixed
36
+ - **Missing import error** - Fixed `getToolMeta` not defined in key-handler.js
37
+ - **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
38
+
39
+ ## 0.3.23
40
+
41
+ ### Added
42
+ - **Favorites display mode (`Y`)**: Added a global toggle to switch favorites between `pinned + always visible` and `normal rows` (starred only, fully obeying active filters/sort).
43
+ - **Favorites mode row in Settings**: Added a dedicated Settings row to inspect/toggle favorites display behavior without leaving the maintenance screen.
44
+ - **Expanded command palette action menus**: Added nested menus for Ping Mode (speed/normal/slow/forced with explanations), Target Tool (all supported tools with explanations), and Favorites Mode (including the new `Y` flow).
45
+ - **Active text-filter footer alert**: When a custom search filter is active (for example `deep`), the last TUI footer line now shows a high-visibility inline badge (between `N Changelog` and `Ctrl+C Exit`) with the exact query and an `X` shortcut to clear it instantly.
46
+
47
+ ### Changed
48
+ - **Favorites sorting/filtering behavior**: Sort/filter logic now respects the selected favorites mode across the table refresh loop, hotkeys, and renderer so non-pinned mode behaves consistently everywhere.
49
+ - **Favorites default mode**: New/legacy configs now default to `Normal filter/sort` favorites mode (not pinned) until users press `Y` to opt into pinned+sticky behavior.
50
+ - **Footer/help/docs shortcut hints**: Surfaced `Y` in the TUI footer, Help overlay, and README so the new favorites mode is discoverable.
51
+
4
52
  ## 0.3.22
5
53
 
6
54
  ### 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
+ The TUI shows a **"Compatible with"** column displaying colored emojis for each supported tool. 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
  ---
@@ -185,6 +238,8 @@ Press **`Z`** in the TUI to cycle between tools without restarting.
185
238
  | `D` | Cycle provider filter |
186
239
  | `E` | Toggle configured-only mode |
187
240
  | `F` | Favorite / unfavorite model |
241
+ | `Y` | Toggle favorites mode (`Normal filter/sort` default ↔ `Pinned + always visible`) |
242
+ | `X` | Clear active custom text filter |
188
243
  | `G` | Cycle global theme (`Auto → Dark → Light`) |
189
244
  | `Ctrl+P` | Open ⚡️ command palette (search + run actions) |
190
245
  | `R/S/C/M/O/L/A/H/V/B/U` | Sort columns |
@@ -202,17 +257,19 @@ Press **`Z`** in the TUI to cycle between tools without restarting.
202
257
 
203
258
  ## ✨ Features
204
259
 
205
- - **Parallel pings** — all 160 models tested simultaneously via native `fetch`
260
+ - **Parallel pings** — all 174 models tested simultaneously via native `fetch`
206
261
  - **Adaptive monitoring** — 2s burst for 60s → 10s normal → 30s idle
207
262
  - **Stability score** — composite 0–100 (p95 latency, jitter, spike rate, uptime)
208
263
  - **Smart ranking** — top 3 highlighted 🥇🥈🥉
209
- - **Favorites** — pin models with `F`, persisted across sessions
264
+ - **Favorites** — star models with `F`, persisted across sessions, default to normal rows, and switch display mode with `Y` (pinned+sticky vs normal rows)
210
265
  - **Configured-only default** — only shows providers you have keys for
211
266
  - **Keyless latency** — models ping even without an API key (show 🔑 NO KEY)
212
267
  - **Smart Recommend** — questionnaire picks the best model for your task type
213
268
  - **⚡️ Command Palette** — `Ctrl+P` opens a searchable action launcher for filters, sorting, overlays, and quick toggles
214
- - **Install Endpoints** — push a full provider catalog into any tool's config (`Y`)
269
+ - **Install Endpoints** — push a full provider catalog into any tool's config (from Settings `P` or ⚡️ Command Palette)
215
270
  - **Missing tool bootstrap** — detect absent CLIs, offer one-click install, then continue the selected launch automatically
271
+ - **Tool compatibility matrix** — colored emojis show which tools each model supports; 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
216
273
  - **Width guardrail** — shows a warning instead of a broken table in narrow terminals
217
274
  - **Readable everywhere** — semantic theme palette keeps table rows, overlays, badges, and help screens legible in dark and light terminals
218
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.22",
3
+ "version": "0.3.24",
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
@@ -21,8 +21,8 @@
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)
23
23
  * - Settings screen (P key) to manage API keys, provider toggles, manual updates, and provider-key diagnostics
24
- * - Install Endpoints flow (Y key) to push provider catalogs into OpenCode, OpenClaw, Crush, and Goose
25
- * - Favorites system: toggle with F, pin rows to top, persist between sessions
24
+ * - Install Endpoints flow (Settings / Command Palette) to push provider catalogs into OpenCode, OpenClaw, Crush, and Goose
25
+ * - Favorites system: toggle with F, switch pinning mode with Y, persist between sessions
26
26
  * - Uptime percentage tracking (successful pings / total pings)
27
27
  * - Sortable columns (R/O/M/L/A/S/C/H/V/B/U/G keys)
28
28
  * - Tier filtering via T key (cycles S+→S→A+→A→A-→B+→B→C→All)
@@ -233,6 +233,8 @@ export async function runApp(cliArgs, config) {
233
233
  openhands: cliArgs.openHandsMode,
234
234
  amp: cliArgs.ampMode,
235
235
  pi: cliArgs.piMode,
236
+ rovo: cliArgs.rovoMode,
237
+ gemini: cliArgs.geminiMode,
236
238
  }
237
239
  return flagByMode[toolMode] === true
238
240
  })
@@ -307,7 +309,7 @@ export async function runApp(cliArgs, config) {
307
309
  }
308
310
 
309
311
  // 📖 Re-sync tracked external-tool catalogs after the live provider catalog has settled.
310
- // 📖 This keeps prior `Y` installs aligned with the current FCM model list.
312
+ // 📖 This keeps prior endpoint installs aligned with the current FCM model list.
311
313
  refreshInstalledEndpoints(config)
312
314
 
313
315
  // 📖 Build results from MODELS — only include enabled providers
@@ -383,12 +385,13 @@ export async function runApp(cliArgs, config) {
383
385
  tierFilterMode: 0, // 📖 Index into TIER_CYCLE (0=All, 1=S+, 2=S, ...)
384
386
  originFilterMode: 0, // 📖 Index into ORIGIN_CYCLE (0=All, then providers)
385
387
  hideUnconfiguredModels: config.settings?.hideUnconfiguredModels === true, // 📖 Hide providers with no configured API key when true.
388
+ favoritesPinnedAndSticky: config.settings?.favoritesPinnedAndSticky === true, // 📖 false by default: favorites follow normal sort/filter rules until Y enables pinned+sticky mode.
386
389
  scrollOffset: 0, // 📖 First visible model index in viewport
387
390
  terminalRows: process.stdout.rows || 24, // 📖 Current terminal height
388
391
  terminalCols: process.stdout.columns || 80, // 📖 Current terminal width
389
392
  widthWarningStartedAt: (process.stdout.columns || 80) < WIDTH_WARNING_MIN_COLS ? now : null, // 📖 Start immediately in very narrow viewports.
390
393
  widthWarningDismissed: false, // 📖 Esc hides the narrow-terminal warning early for the current narrow-width session.
391
- widthWarningShowCount: 0, // 📖 Counter for how many times the narrow-terminal warning has been shown (max 2 per session).
394
+ widthWarningShowCount: 0, // 📖 No longer used kept for backward compatibility. Warning now shows every time terminal is too small.
392
395
  // 📖 Settings screen state (P key opens it)
393
396
  settingsOpen: false, // 📖 Whether settings overlay is active
394
397
  settingsCursor: 0, // 📖 Which provider row is selected in settings
@@ -409,11 +412,11 @@ export async function runApp(cliArgs, config) {
409
412
  commandPaletteScrollOffset: 0, // 📖 Vertical scroll offset for the command palette result viewport.
410
413
  commandPaletteResults: [], // 📖 Cached fuzzy-filtered command entries for the command palette.
411
414
  commandPaletteFrozenTable: null, // 📖 Frozen table snapshot rendered behind the command palette overlay.
412
- commandPaletteExpandedIds: new Set(['filters']), // 📖 Set of expanded category/subcategory IDs (filters expanded by default for quick access).
415
+ commandPaletteExpandedIds: new Set(['filters', 'actions']), // 📖 Expanded category IDs (filters + actions open by default for quick access).
413
416
  helpVisible: false, // 📖 Whether the help overlay (K key) is active
414
417
  settingsScrollOffset: 0, // 📖 Vertical scroll offset for Settings overlay viewport
415
418
  helpScrollOffset: 0, // 📖 Vertical scroll offset for Help overlay viewport
416
- // 📖 Install Endpoints overlay state (Y key opens it)
419
+ // 📖 Install Endpoints overlay state (opened from Settings or Command Palette)
417
420
  installEndpointsOpen: false, // 📖 Whether the install-endpoints overlay is active
418
421
  installEndpointsPhase: 'providers', // 📖 providers | tools | scope | models | result
419
422
  installEndpointsCursor: 0, // 📖 Selected row within the current install phase
@@ -433,6 +436,15 @@ export async function runApp(cliArgs, config) {
433
436
  toolInstallPromptModel: null,
434
437
  toolInstallPromptPlan: null,
435
438
  toolInstallPromptErrorMsg: null,
439
+ // 📖 Incompatible model fallback overlay — shown when user presses Enter on a red-highlighted model.
440
+ // 📖 Offers two options: switch to a compatible tool, or pick a similar SWE-scored model.
441
+ incompatibleFallbackOpen: false,
442
+ incompatibleFallbackCursor: 0,
443
+ incompatibleFallbackScrollOffset: 0,
444
+ incompatibleFallbackModel: null, // 📖 The incompatible model the user tried to launch
445
+ incompatibleFallbackTools: [], // 📖 Compatible tools for the selected model
446
+ incompatibleFallbackSimilarModels: [], // 📖 Similar SWE models compatible with current tool
447
+ incompatibleFallbackSection: 'tools', // 📖 'tools' or 'models' — which section cursor is in
436
448
  // 📖 Smart Recommend overlay state (Q key opens it)
437
449
  recommendOpen: false, // 📖 Whether the recommend overlay is active
438
450
  recommendPhase: 'questionnaire', // 📖 'questionnaire'|'analyzing'|'results' — current phase
@@ -471,7 +483,6 @@ export async function runApp(cliArgs, config) {
471
483
  if (prevCols >= WIDTH_WARNING_MIN_COLS || state.widthWarningDismissed) {
472
484
  state.widthWarningStartedAt = Date.now()
473
485
  state.widthWarningDismissed = false
474
- state.widthWarningShowCount++ // 📖 Increment counter when showing the warning again
475
486
  } else if (!state.widthWarningStartedAt) {
476
487
  state.widthWarningStartedAt = Date.now()
477
488
  }
@@ -678,12 +689,16 @@ export async function runApp(cliArgs, config) {
678
689
  const activeTier = TIER_CYCLE[state.tierFilterMode]
679
690
  const activeOrigin = ORIGIN_CYCLE[state.originFilterMode]
680
691
  state.results.forEach(r => {
681
- // 📖 Favorites stay visible and pinned regardless of configured-only, tier, or provider filters.
682
- if (r.isFavorite) {
692
+ // 📖 Sticky-favorites mode keeps favorites visible regardless of configured-only, tier, or provider filters.
693
+ if (state.favoritesPinnedAndSticky && r.isFavorite) {
683
694
  r.hidden = false
684
695
  return
685
696
  }
686
- const unconfiguredHide = state.hideUnconfiguredModels && !getApiKey(state.config, r.providerKey)
697
+ // 📖 CLI-only tools (rovo, gemini) and Zen models don't need traditional API keys —
698
+ // 📖 they authenticate via their own CLI login flow, so "configured only" should never hide them.
699
+ const providerMeta = PROVIDER_METADATA[r.providerKey]
700
+ const noKeyNeeded = providerMeta?.cliOnly || providerMeta?.zenOnly
701
+ const unconfiguredHide = state.hideUnconfiguredModels && !noKeyNeeded && !getApiKey(state.config, r.providerKey)
687
702
  if (unconfiguredHide) {
688
703
  r.hidden = true
689
704
  return
@@ -799,6 +814,7 @@ export async function runApp(cliArgs, config) {
799
814
  startOpenCode,
800
815
  startExternalTool,
801
816
  getToolModeOrder,
817
+ getToolMeta,
802
818
  getToolInstallPlan,
803
819
  isToolInstalled,
804
820
  installToolWithPlan,
@@ -825,7 +841,7 @@ export async function runApp(cliArgs, config) {
825
841
  if (cliArgs.tierFilter) {
826
842
  const allowed = TIER_LETTER_MAP[cliArgs.tierFilter]
827
843
  state.results.forEach(r => {
828
- r.hidden = r.isFavorite ? false : !allowed.includes(r.tier)
844
+ r.hidden = (state.favoritesPinnedAndSticky && r.isFavorite) ? false : !allowed.includes(r.tier)
829
845
  })
830
846
  }
831
847
 
@@ -861,9 +877,11 @@ export async function runApp(cliArgs, config) {
861
877
  refreshAutoPingMode()
862
878
  state.frame++
863
879
  // 📖 Cache visible+sorted models each frame so Enter handler always matches the display
864
- if (!state.settingsOpen && !state.installEndpointsOpen && !state.toolInstallPromptOpen && !state.recommendOpen && !state.feedbackOpen && !state.changelogOpen && !state.commandPaletteOpen) {
880
+ if (!state.settingsOpen && !state.installEndpointsOpen && !state.toolInstallPromptOpen && !state.incompatibleFallbackOpen && !state.recommendOpen && !state.feedbackOpen && !state.changelogOpen && !state.commandPaletteOpen) {
865
881
  const visible = state.results.filter(r => !r.hidden)
866
- state.visibleSorted = sortResultsWithPinnedFavorites(visible, state.sortColumn, state.sortDirection)
882
+ state.visibleSorted = sortResultsWithPinnedFavorites(visible, state.sortColumn, state.sortDirection, {
883
+ pinFavorites: state.favoritesPinnedAndSticky,
884
+ })
867
885
  }
868
886
  let tableContent = null
869
887
  if (state.commandPaletteOpen) {
@@ -896,7 +914,9 @@ export async function runApp(cliArgs, config) {
896
914
  state.settingsUpdateLatestVersion,
897
915
  false,
898
916
  state.startupLatestVersion,
899
- state.versionAlertsEnabled
917
+ state.versionAlertsEnabled,
918
+ state.favoritesPinnedAndSticky,
919
+ state.customTextFilter
900
920
  )
901
921
  }
902
922
  tableContent = state.commandPaletteFrozenTable
@@ -928,7 +948,9 @@ export async function runApp(cliArgs, config) {
928
948
  state.settingsUpdateLatestVersion,
929
949
  false,
930
950
  state.startupLatestVersion,
931
- state.versionAlertsEnabled
951
+ state.versionAlertsEnabled,
952
+ state.favoritesPinnedAndSticky,
953
+ state.customTextFilter
932
954
  )
933
955
  }
934
956
 
@@ -938,6 +960,8 @@ export async function runApp(cliArgs, config) {
938
960
  ? overlays.renderInstallEndpoints()
939
961
  : state.toolInstallPromptOpen
940
962
  ? overlays.renderToolInstallPrompt()
963
+ : state.incompatibleFallbackOpen
964
+ ? overlays.renderIncompatibleFallback()
941
965
  : state.commandPaletteOpen
942
966
  ? tableContent + overlays.renderCommandPalette()
943
967
  : state.recommendOpen
@@ -964,9 +988,11 @@ export async function runApp(cliArgs, config) {
964
988
 
965
989
  // 📖 Populate visibleSorted before the first frame so Enter works immediately
966
990
  const initialVisible = state.results.filter(r => !r.hidden)
967
- state.visibleSorted = sortResultsWithPinnedFavorites(initialVisible, state.sortColumn, state.sortDirection)
991
+ state.visibleSorted = sortResultsWithPinnedFavorites(initialVisible, state.sortColumn, state.sortDirection, {
992
+ pinFavorites: state.favoritesPinnedAndSticky,
993
+ })
968
994
 
969
- 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))
995
+ 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))
970
996
  if (process.stdout.isTTY) {
971
997
  process.stdout.flush && process.stdout.flush()
972
998
  }
@@ -15,6 +15,74 @@
15
15
  * @see src/overlays.js
16
16
  */
17
17
 
18
+ import { TOOL_METADATA, TOOL_MODE_ORDER } from './tool-metadata.js'
19
+
20
+ const TOOL_MODE_DESCRIPTIONS = {
21
+ opencode: 'Launch in OpenCode CLI with the selected model.',
22
+ 'opencode-desktop': 'Set model in shared config, then open OpenCode Desktop.',
23
+ openclaw: 'Set default model in OpenClaw and launch it.',
24
+ crush: 'Launch Crush with this provider/model pair.',
25
+ goose: 'Launch Goose and preselect the active model.',
26
+ pi: 'Launch Pi with model/provider flags.',
27
+ aider: 'Launch Aider configured on the selected model.',
28
+ qwen: 'Launch Qwen Code using the selected provider model.',
29
+ openhands: 'Launch OpenHands with the selected model endpoint.',
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).',
33
+ }
34
+
35
+ const TOOL_MODE_COMMANDS = TOOL_MODE_ORDER.map((toolMode) => {
36
+ const meta = TOOL_METADATA[toolMode] || { label: toolMode, emoji: '🧰' }
37
+ return {
38
+ id: `action-set-tool-${toolMode}`,
39
+ label: `${meta.emoji} ${meta.label}`,
40
+ toolMode,
41
+ icon: meta.emoji,
42
+ description: TOOL_MODE_DESCRIPTIONS[toolMode] || 'Set this as the active launch target.',
43
+ keywords: ['tool', 'target', 'mode', toolMode, meta.label.toLowerCase()],
44
+ }
45
+ })
46
+
47
+ const PING_MODE_COMMANDS = [
48
+ {
49
+ id: 'action-cycle-ping-mode',
50
+ label: 'Cycle ping mode',
51
+ shortcut: 'W',
52
+ icon: '⚡',
53
+ description: 'Rotate speed → normal → slow → forced.',
54
+ keywords: ['ping', 'mode', 'cycle', 'speed', 'normal', 'slow', 'forced'],
55
+ },
56
+ {
57
+ id: 'action-set-ping-speed',
58
+ label: 'Speed mode (2s)',
59
+ pingMode: 'speed',
60
+ description: 'Fast 2s bursts for short live checks.',
61
+ keywords: ['ping', 'mode', 'speed', '2s', 'fast'],
62
+ },
63
+ {
64
+ id: 'action-set-ping-normal',
65
+ label: 'Normal mode (10s)',
66
+ pingMode: 'normal',
67
+ description: 'Balanced default cadence for daily use.',
68
+ keywords: ['ping', 'mode', 'normal', '10s', 'default'],
69
+ },
70
+ {
71
+ id: 'action-set-ping-slow',
72
+ label: 'Slow mode (30s)',
73
+ pingMode: 'slow',
74
+ description: 'Lower refresh cost when you are mostly idle.',
75
+ keywords: ['ping', 'mode', 'slow', '30s', 'idle'],
76
+ },
77
+ {
78
+ id: 'action-set-ping-forced',
79
+ label: 'Forced mode (4s)',
80
+ pingMode: 'forced',
81
+ description: 'Keeps 4s cadence until manually changed.',
82
+ keywords: ['ping', 'mode', 'forced', '4s', 'manual'],
83
+ },
84
+ ]
85
+
18
86
  // 📖 Base command tree template (will be enhanced with dynamic model list)
19
87
  const BASE_COMMAND_TREE = [
20
88
  {
@@ -93,6 +161,41 @@ const BASE_COMMAND_TREE = [
93
161
  { id: 'sort-uptime', label: 'Sort by uptime', shortcut: 'U', description: 'Success rate', keywords: ['sort', 'uptime'] },
94
162
  ]
95
163
  },
164
+ {
165
+ id: 'actions',
166
+ label: 'Actions',
167
+ icon: '⚙️',
168
+ children: [
169
+ {
170
+ id: 'action-target-tool',
171
+ label: 'Target tool',
172
+ icon: '🧰',
173
+ children: [
174
+ { id: 'action-cycle-tool-mode', label: 'Cycle target tool', shortcut: 'Z', icon: '🔄', description: 'Rotate through every launcher mode.', keywords: ['tool', 'mode', 'cycle', 'target'] },
175
+ ...TOOL_MODE_COMMANDS,
176
+ ],
177
+ },
178
+ {
179
+ id: 'action-ping-mode',
180
+ label: 'Ping mode',
181
+ icon: '📶',
182
+ children: PING_MODE_COMMANDS,
183
+ },
184
+ {
185
+ id: 'action-favorites-mode',
186
+ label: 'Favorites mode',
187
+ icon: '⭐',
188
+ children: [
189
+ { id: 'action-toggle-favorite-mode', label: 'Toggle favorites mode', shortcut: 'Y', icon: '⭐', description: 'Switch pinned+sticky ↔ normal list behavior.', keywords: ['favorite', 'favorites', 'mode', 'toggle', 'y'] },
190
+ { id: 'action-favorites-mode-pinned', label: 'Pinned + always visible', favoritesPinned: true, description: 'Favorites stay on top and bypass current filters.', keywords: ['favorite', 'favorites', 'pinned', 'sticky', 'always visible'] },
191
+ { id: 'action-favorites-mode-normal', label: 'Normal rows (starred only)', favoritesPinned: false, description: 'Favorites keep ⭐ but follow active filters and sort.', keywords: ['favorite', 'favorites', 'normal', 'sort', 'filter'] },
192
+ { id: 'action-toggle-favorite', label: 'Toggle favorite on selected row', shortcut: 'F', icon: '⭐', description: 'Star/unstar the highlighted model.', keywords: ['favorite', 'star', 'toggle'] },
193
+ ],
194
+ },
195
+ { id: 'action-cycle-theme', label: 'Cycle theme', shortcut: 'G', icon: '🌗', description: 'Switch dark/light/auto', keywords: ['theme', 'dark', 'light', 'auto'] },
196
+ { id: 'action-reset-view', label: 'Reset view', shortcut: 'Shift+R', icon: '🔄', description: 'Reset filters and sort', keywords: ['reset', 'view', 'sort', 'filters'] },
197
+ ],
198
+ },
96
199
  // 📖 Pages - directly at root level, not in submenu
97
200
  { id: 'open-settings', label: 'Settings', shortcut: 'P', icon: '⚙️', type: 'page', description: 'API keys and preferences', keywords: ['settings', 'config', 'api key'] },
98
201
  { id: 'open-help', label: 'Help', shortcut: 'K', icon: '❓', type: 'page', description: 'Show all shortcuts', keywords: ['help', 'shortcuts', 'hotkeys'] },
@@ -100,12 +203,6 @@ const BASE_COMMAND_TREE = [
100
203
  { id: 'open-feedback', label: 'Feedback', shortcut: 'I', icon: '📝', type: 'page', description: 'Report bugs or requests', keywords: ['feedback', 'bug', 'request'] },
101
204
  { id: 'open-recommend', label: 'Smart recommend', shortcut: 'Q', icon: '🎯', type: 'page', description: 'Find best model for task', keywords: ['recommend', 'best model'] },
102
205
  { id: 'open-install-endpoints', label: 'Install endpoints', icon: '🔌', type: 'page', description: 'Install provider catalogs', keywords: ['install', 'endpoints', 'providers'] },
103
- // 📖 Actions - directly at root level, not in submenu
104
- { id: 'action-cycle-theme', label: 'Cycle theme', shortcut: 'G', icon: '🌗', type: 'action', description: 'Switch dark/light/auto', keywords: ['theme', 'dark', 'light', 'auto'] },
105
- { id: 'action-cycle-tool-mode', label: 'Target Tool', shortcut: 'Z', icon: '🔄', type: 'action', description: 'Change target AI Coding CLI Tool.', keywords: ['tool', 'mode', 'launcher', 'target'] },
106
- { id: 'action-cycle-ping-mode', label: 'Cycle ping mode', shortcut: 'W', icon: '⚡', type: 'action', description: 'Adjust ping speed', keywords: ['ping', 'cadence', 'speed', 'slow'] },
107
- { id: 'action-toggle-favorite', label: 'Toggle favorite', shortcut: 'F', icon: '⭐', type: 'action', description: 'Pin to favorites', keywords: ['favorite', 'star'] },
108
- { id: 'action-reset-view', label: 'Reset view', shortcut: 'Shift+R', icon: '🔄', type: 'action', description: 'Reset filters and sort', keywords: ['reset', 'view', 'sort', 'filters'] },
109
206
  ]
110
207
 
111
208
  /**