free-coding-models 0.5.1 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -5
- package/bin/free-coding-models.js +23 -8
- package/changelog/v0.5.4.md +24 -0
- package/changelog/v0.5.5.md +15 -0
- package/package.json +5 -2
- package/sources.js +2 -0
- package/src/core/updater.js +174 -10
- package/src/tui/app.js +11 -31
- package/src/tui/key-handler.js +1 -1
- package/src/tui/render-table.js +9 -3
- package/src/tui/tui-state.js +6 -0
- package/web/assets/providers/aistudio/aistudio-text.svg +1 -0
- package/web/assets/providers/aistudio/aistudio.svg +1 -0
- package/web/assets/providers/cerebras/cerebras-brand-color.svg +1 -0
- package/web/assets/providers/cerebras/cerebras-brand.svg +1 -0
- package/web/assets/providers/cerebras/cerebras-color.svg +1 -0
- package/web/assets/providers/cerebras/cerebras-text.svg +1 -0
- package/web/assets/providers/cerebras/cerebras.svg +1 -0
- package/web/assets/providers/cloudflare/cloudflare-color.svg +1 -0
- package/web/assets/providers/cloudflare/cloudflare-text.svg +1 -0
- package/web/assets/providers/cloudflare/cloudflare.svg +1 -0
- package/web/assets/providers/gemini/gemini-color.svg +1 -0
- package/web/assets/providers/gemini/gemini-text.svg +1 -0
- package/web/assets/providers/gemini/gemini.svg +1 -0
- package/web/assets/providers/github/github-text.svg +1 -0
- package/web/assets/providers/github/github.svg +1 -0
- package/web/assets/providers/groq/groq-text.svg +1 -0
- package/web/assets/providers/groq/groq.svg +1 -0
- package/web/assets/providers/mistral/mistral-color.svg +1 -0
- package/web/assets/providers/mistral/mistral-text.svg +1 -0
- package/web/assets/providers/mistral/mistral.svg +1 -0
- package/web/assets/providers/nvidia/nvidia-color.svg +1 -0
- package/web/assets/providers/nvidia/nvidia-text.svg +1 -0
- package/web/assets/providers/nvidia/nvidia.svg +1 -0
- package/web/assets/providers/opencode/opencode-text.svg +1 -0
- package/web/assets/providers/opencode/opencode.svg +1 -0
- package/web/assets/providers/openrouter/openrouter (1).svg +1 -0
- package/web/assets/providers/openrouter/openrouter-text.svg +1 -0
- package/web/assets/providers/openrouter/openrouter.svg +1 -0
- package/web/assets/providers/ovhcloud/ovhcloud.svg +12 -0
- package/web/assets/providers/qwen/qwen-color.svg +1 -0
- package/web/assets/providers/qwen/qwen-text.svg +1 -0
- package/web/assets/providers/qwen/qwen.svg +1 -0
- package/web/assets/providers/sambanova/sambanova-color.svg +1 -0
- package/web/assets/providers/sambanova/sambanova-text.svg +1 -0
- package/web/assets/providers/sambanova/sambanova.svg +1 -0
- package/web/assets/providers/scalewaylogo/ScalewayLogo.svg +13 -0
- package/web/assets/providers/zai/zai-text.svg +1 -0
- package/web/assets/providers/zai/zai.svg +1 -0
- package/web/dist/assets/index-uD3faN3G.js +39 -0
- package/web/dist/assets/index-uTifVKX1.css +1 -0
- package/web/dist/favicon.ico +0 -0
- package/web/dist/favicons/apple-touch-icon.png +0 -0
- package/web/dist/favicons/browserconfig.xml +12 -0
- package/web/dist/favicons/favicon-16x16.png +0 -0
- package/web/dist/favicons/favicon-192x192.png +0 -0
- package/web/dist/favicons/favicon-32x32.png +0 -0
- package/web/dist/favicons/favicon-48x48.png +0 -0
- package/web/dist/favicons/favicon-512x512.png +0 -0
- package/web/dist/favicons/favicon-96x96.png +0 -0
- package/web/dist/favicons/favicon.ico +0 -0
- package/web/dist/favicons/mstile-150x150.png +0 -0
- package/web/dist/favicons/mstile-310x150.png +0 -0
- package/web/dist/favicons/mstile-310x310.png +0 -0
- package/web/dist/favicons/mstile-512x512.png +0 -0
- package/web/dist/favicons/mstile-70x70.png +0 -0
- package/web/dist/favicons/site.webmanifest +25 -0
- package/web/dist/index.html +42 -3
- package/web/index.html +40 -1
- package/web/public/favicon.ico +0 -0
- package/web/public/favicons/apple-touch-icon.png +0 -0
- package/web/public/favicons/browserconfig.xml +12 -0
- package/web/public/favicons/favicon-16x16.png +0 -0
- package/web/public/favicons/favicon-192x192.png +0 -0
- package/web/public/favicons/favicon-32x32.png +0 -0
- package/web/public/favicons/favicon-48x48.png +0 -0
- package/web/public/favicons/favicon-512x512.png +0 -0
- package/web/public/favicons/favicon-96x96.png +0 -0
- package/web/public/favicons/favicon.ico +0 -0
- package/web/public/favicons/mstile-150x150.png +0 -0
- package/web/public/favicons/mstile-310x150.png +0 -0
- package/web/public/favicons/mstile-310x310.png +0 -0
- package/web/public/favicons/mstile-512x512.png +0 -0
- package/web/public/favicons/mstile-70x70.png +0 -0
- package/web/public/favicons/site.webmanifest +25 -0
- package/web/server.js +83 -2
- package/web/src/App.jsx +210 -84
- package/web/src/components/analytics/AnalyticsView.jsx +30 -2
- package/web/src/components/analytics/AnalyticsView.module.css +4 -0
- package/web/src/components/atoms/AILatencyCell.module.css +9 -1
- package/web/src/components/atoms/HealthCell.jsx +113 -28
- package/web/src/components/atoms/HealthCell.module.css +43 -2
- package/web/src/components/atoms/LastPingCell.module.css +10 -0
- package/web/src/components/atoms/NoKeyIcon.jsx +65 -0
- package/web/src/components/atoms/NoKeyIcon.module.css +30 -0
- package/web/src/components/atoms/ProviderLogo.jsx +188 -0
- package/web/src/components/atoms/ProviderLogo.module.css +103 -0
- package/web/src/components/atoms/Sparkline.jsx +21 -2
- package/web/src/components/atoms/StabilityCell.module.css +5 -0
- package/web/src/components/atoms/StatusDot.module.css +7 -0
- package/web/src/components/atoms/TPSCell.module.css +7 -1
- package/web/src/components/atoms/TierBadge.module.css +11 -0
- package/web/src/components/atoms/Toast.module.css +6 -0
- package/web/src/components/atoms/VerdictBadge.module.css +12 -0
- package/web/src/components/dashboard/DetailPanel.jsx +63 -2
- package/web/src/components/dashboard/DetailPanel.module.css +130 -0
- package/web/src/components/dashboard/FilterBar.jsx +146 -53
- package/web/src/components/dashboard/FilterBar.module.css +138 -58
- package/web/src/components/dashboard/ModelTable.jsx +235 -30
- package/web/src/components/dashboard/ModelTable.module.css +205 -16
- package/web/src/components/layout/Header.jsx +140 -12
- package/web/src/components/layout/Header.module.css +191 -27
- package/web/src/components/palette/CommandPalette.jsx +146 -0
- package/web/src/components/palette/CommandPalette.module.css +121 -0
- package/web/src/global.css +67 -15
- package/web/src/hooks/useColumnSizing.js +211 -0
- package/web/src/hooks/useFavorites.js +150 -0
- package/web/src/hooks/useFilter.js +157 -24
- package/web/src/hooks/useSocket.js +3 -0
- package/web/src/hooks/useTheme.js +72 -16
- package/web/src/hooks/useUrlState.js +93 -0
- package/web/dist/assets/index-ByGf4Kq-.js +0 -14
- package/web/dist/assets/index-Ds7wmHBv.css +0 -1
- package/web/src/components/dashboard/StatsBar.jsx +0 -42
- package/web/src/components/dashboard/StatsBar.module.css +0 -28
- package/web/src/components/layout/Sidebar.jsx +0 -50
- package/web/src/components/layout/Sidebar.module.css +0 -86
- package/web/src/components/map/MapView.jsx +0 -17
- package/web/src/components/map/MapView.module.css +0 -25
package/README.md
CHANGED
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
Track ~153 models across 16 trusted free or free-limited AI providers in real time<br><br>
|
|
10
10
|
<strong>Install Free API endpoints to your favorite AI coding tools:</strong><br>
|
|
11
11
|
OpenCode CLI / Desktop / WebUI, OpenClaw, Crush, Goose, Aider, Kilo CLI, Qwen Code, OpenHands, Amp, Hermes, Continue, Cline, Xcode, Pi, Rovo, Gemini and more...<br><br>
|
|
12
|
-
<strong>Use Kimi K2, DeepSeek V3, GPT-OSS, Qwen3, MiniMax
|
|
12
|
+
<strong>Use Kimi K2, DeepSeek V3, GPT-OSS, Qwen3, MiniMax M3, GLM, Llama 4, Gemma 4, Devstral and more — for free</strong>
|
|
13
13
|
</p>
|
|
14
14
|
|
|
15
15
|
<p align="center">
|
|
16
16
|
<img src="https://img.shields.io/npm/v/free-coding-models?color=3d6b00&label=npm&logo=npm" alt="npm version" width="200"><br>
|
|
17
17
|
<img src="https://img.shields.io/node/v/free-coding-models?color=3d6b00&logo=node.js" alt="node version" width="200"><br>
|
|
18
18
|
<img src="https://img.shields.io/npm/l/free-coding-models?color=3d6b00" alt="license" width="200"><br>
|
|
19
|
-
<img src="https://img.shields.io/badge/models-
|
|
19
|
+
<img src="https://img.shields.io/badge/models-154+-3d6b00?logo=nvidia" alt="models count" width="200"><br>
|
|
20
20
|
<img src="https://img.shields.io/badge/providers-16-1a56db" alt="providers count" width="200">
|
|
21
21
|
</p>
|
|
22
22
|
|
|
@@ -155,6 +155,31 @@ free-coding-models web
|
|
|
155
155
|
|
|
156
156
|
This starts the realtime Web Dashboard locally, opens it in your browser, and uses `http://localhost:3333/` by default. Override the port with `FCM_WEB_PORT=3334 free-coding-models web`.
|
|
157
157
|
|
|
158
|
+
#### Web Dashboard features (M1 parity with TUI)
|
|
159
|
+
|
|
160
|
+
The local Web Dashboard is a real-time control center for the model catalog
|
|
161
|
+
— not just a static table. The header holds every navigation entry, the
|
|
162
|
+
model table uses **100% of the viewport width** (no left rail, no right rail),
|
|
163
|
+
and every TUI capability that's safe to port ships behind a button or chip.
|
|
164
|
+
|
|
165
|
+
| Area | What's there |
|
|
166
|
+
|---|---|
|
|
167
|
+
| **Header** | Logo + version · primary nav (Dashboard, Settings, Analytics, Recommend, Router) · kebab menu (Help, Changelog, Install Endpoints, Installed Models) · `⌘K` command palette · AI Latency · theme · export |
|
|
168
|
+
| **Model table** | Full-bleed (no left/right border, no margin) under the sticky header + filter bar · 16 columns, resizable widths persisted in localStorage, ⭐ star per row, medal borders for top-3, dark-red row class for tool-incompatible models, click AI Lat. cell to run a per-row benchmark · table header row stays sticky while scrolling |
|
|
169
|
+
| **Filter bar** | Sticky right below the header (always visible) · Tier / Status / Verdict / Health chip rows · Visibility dropdown (Normal / Configured only / Usable only) · Provider select · custom text filter chip with `X` clear · Reset button (TUI `N`) · ping mode (Speed / Normal / Slow / Forced) · "next ping in Xs" countdown (TUI style, always shown) |
|
|
170
|
+
| **Stats bar** | Removed in M1 (users found it noisy; the table + chips carry the same info at a glance) |
|
|
171
|
+
| **Detail panel** | Slide-in from the right on row click · per-row benchmark button (TUI `Ctrl+A`) · favorite toggle (TUI `F`) + up/down reorder (TUI `Shift+↑↓`) · latency trend chart · all stats |
|
|
172
|
+
| **Command palette** | `⌘K` / `Ctrl+P` (the only global keyboard shortcut) · fuzzy search across views, theme, ping mode, reset, export |
|
|
173
|
+
| **Keyboard** | `Esc` closes any modal · `Cmd+K` toggles the palette — that's it. Everything else is mouse-first. |
|
|
174
|
+
| **URL deep-linking** | `?tier=S+&sort=verdict&origin=groq&view=dashboard&q=…` hydrates the dashboard on load. CLI flags become shareable links. |
|
|
175
|
+
| **Favorites** | Shared with the TUI through `~/.free-coding-models.json` — a star in the Web is a star in the TUI. Includes pinned+sticky display mode (TUI `Y`). |
|
|
176
|
+
| **Theme** | Tri-state `auto / dark / light` cycle (TUI `G`) — auto follows the OS preference. |
|
|
177
|
+
|
|
178
|
+
Roadmap items (header menu badges show "M2 / M3 / M4" until they land):
|
|
179
|
+
- **M2** — Help modal, Changelog viewer, Settings parity (theme toggle, test key, shell-env, legacy cleanup, startup AI scan), Update flow, URL write-back, full command palette backed by the TUI registry.
|
|
180
|
+
- **M3** — Smart Recommend (3-question wizard → 10s analysis → Top 3), tool mode picker, per-row Launch, missing-tool install prompt, incompatible-fallback modal.
|
|
181
|
+
- **M4** — Router Dashboard, Token Usage, Install Endpoints wizard, Installed Models manager.
|
|
182
|
+
|
|
158
183
|
### Available Image Tags
|
|
159
184
|
|
|
160
185
|
| Tag | Description |
|
|
@@ -387,7 +412,7 @@ Press **`Z`** in the TUI to cycle between tools without restarting.
|
|
|
387
412
|
|
|
388
413
|
### OpenCode Zen Free Models
|
|
389
414
|
|
|
390
|
-
[OpenCode Zen](https://opencode.ai/zen) is a hosted AI gateway offering **
|
|
415
|
+
[OpenCode Zen](https://opencode.ai/zen) is a hosted AI gateway offering **5 free coding models** exclusively through OpenCode CLI and OpenCode Desktop. These models are **not** available through other tools.
|
|
391
416
|
|
|
392
417
|
| Model | Tier | SWE-bench | Context |
|
|
393
418
|
|-------|------|-----------|---------|
|
|
@@ -395,6 +420,7 @@ Press **`Z`** in the TUI to cycle between tools without restarting.
|
|
|
395
420
|
| DeepSeek V4 Flash Free | S+ | 79.0% | 200k |
|
|
396
421
|
| MiMo-V2.5 Free | S+ | - | 200k |
|
|
397
422
|
| Nemotron 3 Super Free | A+ | 52.0% | 200k |
|
|
423
|
+
| MiniMax M3 Free | S+ | 59.0% | 1M |
|
|
398
424
|
|
|
399
425
|
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.
|
|
400
426
|
|
|
@@ -522,9 +548,9 @@ For every model in this tool, **you own the generated output** — code, text, o
|
|
|
522
548
|
| **Llama Community License** | Llama 3.3 70B, Llama 4 Scout/Maverick | ✅ Attribution required. >700M MAU → separate Meta license |
|
|
523
549
|
| **DeepSeek License** | DeepSeek V3/V3.1/V3.2/V4 family | ✅ Use restrictions on model (no military, no harm) — output is yours |
|
|
524
550
|
| **NVIDIA Nemotron License** | Nemotron Super/Ultra/Nano | ✅ Updated Mar 2026, now near-Apache 2.0 permissive |
|
|
525
|
-
| **MiniMax Model License** | MiniMax M2, M2.5 | ✅ Royalty-free, non-exclusive. Prohibited uses policy applies to model |
|
|
551
|
+
| **MiniMax Model License** | MiniMax M2, M2.5, M3 | ✅ Royalty-free, non-exclusive. Prohibited uses policy applies to model |
|
|
526
552
|
| **Proprietary / hosted API terms** | Gemini, GitHub Models, Mistral/Codestral, OpenRouter-hosted models | ✅ Provider ToS applies |
|
|
527
|
-
| **OpenCode Zen** | Big Pickle, GPT 5 Nano, MiniMax
|
|
553
|
+
| **OpenCode Zen** | Big Pickle, GPT 5 Nano, MiniMax M3 Free, Nemotron 3 Super Free, HY3/Ling/Trinity previews | ✅ Per OpenCode Zen ToS |
|
|
528
554
|
|
|
529
555
|
### Key Points
|
|
530
556
|
|
|
@@ -11,11 +11,12 @@ if (process.argv.includes('--dev')) {
|
|
|
11
11
|
|
|
12
12
|
import chalk from 'chalk';
|
|
13
13
|
import { parseArgs, TIER_LETTER_MAP } from '../src/core/utils.js';
|
|
14
|
-
import { loadConfig } from '../src/core/config.js';
|
|
14
|
+
import { loadConfig, saveConfig } from '../src/core/config.js';
|
|
15
15
|
import { ensureTelemetryConfig } from '../src/core/telemetry.js';
|
|
16
16
|
import { ensureFavoritesConfig } from '../src/core/favorites.js';
|
|
17
17
|
import { buildCliHelpText } from '../src/tui/cli-help.js';
|
|
18
18
|
import { ALT_LEAVE } from '../src/core/constants.js';
|
|
19
|
+
import { enforceMandatoryStartupUpdate, isPackageDevMode } from '../src/core/updater.js';
|
|
19
20
|
import { runApp } from '../src/tui/app.js';
|
|
20
21
|
|
|
21
22
|
// Global error handlers to ensure terminal is restored if something crashes catastrophically
|
|
@@ -53,13 +54,32 @@ async function main() {
|
|
|
53
54
|
process.exit(0);
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
// Load JSON config before operational modes so the mandatory update policy can
|
|
58
|
+
// 📖 persist failure counters for TUI, Web Dashboard, Docker daemon, and Desktop sidecar launches.
|
|
59
|
+
const config = loadConfig();
|
|
60
|
+
ensureTelemetryConfig(config);
|
|
61
|
+
ensureFavoritesConfig(config);
|
|
62
|
+
|
|
63
|
+
const isDevMode = isPackageDevMode();
|
|
64
|
+
const shouldEnforceUpdate = !cliArgs.daemonStopMode && !cliArgs.daemonStatusMode;
|
|
65
|
+
const startupUpdate = shouldEnforceUpdate
|
|
66
|
+
? await enforceMandatoryStartupUpdate(config, {
|
|
67
|
+
saveConfig,
|
|
68
|
+
isDevMode,
|
|
69
|
+
surface: cliArgs.webMode ? 'web dashboard' : cliArgs.daemonMode ? 'router daemon' : 'TUI',
|
|
70
|
+
})
|
|
71
|
+
: { latestVersion: null, allowedOutdated: false, warningMessage: null, failures: 0, checked: false, updated: false, blocked: false };
|
|
72
|
+
|
|
73
|
+
if (startupUpdate.updated) return;
|
|
74
|
+
if (startupUpdate.blocked) process.exit(1);
|
|
75
|
+
|
|
56
76
|
// 📖 Standalone web dashboard: same full-catalog ping UI as the TUI, served
|
|
57
77
|
// 📖 locally with Socket.IO/SSE/REST realtime updates.
|
|
58
78
|
if (cliArgs.webMode) {
|
|
59
79
|
const { startWebServer } = await import('../web/server.js');
|
|
60
80
|
const parsedPort = Number.parseInt(process.env.FCM_WEB_PORT || process.env.FCM_PORT || '3333', 10);
|
|
61
81
|
const port = Number.isFinite(parsedPort) && parsedPort > 0 ? parsedPort : 3333;
|
|
62
|
-
await startWebServer(port, { open: true, startPingLoop: true });
|
|
82
|
+
await startWebServer(port, { open: true, startPingLoop: true, updateStatus: startupUpdate });
|
|
63
83
|
return;
|
|
64
84
|
}
|
|
65
85
|
|
|
@@ -102,12 +122,7 @@ async function main() {
|
|
|
102
122
|
process.exit(1);
|
|
103
123
|
}
|
|
104
124
|
|
|
105
|
-
|
|
106
|
-
const config = loadConfig();
|
|
107
|
-
ensureTelemetryConfig(config);
|
|
108
|
-
ensureFavoritesConfig(config);
|
|
109
|
-
|
|
110
|
-
await runApp(cliArgs, config);
|
|
125
|
+
await runApp(cliArgs, config, { startupUpdate, isDevMode });
|
|
111
126
|
}
|
|
112
127
|
|
|
113
128
|
main().catch((err) => {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Changelog v0.5.4 - 2026-06-01
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
- Added `minimax-m3-free` to the OpenCode Zen provider — **MiniMax M3 Free**, the flagship free Zen coding model with **1M token context** and MiniMax Sparse Attention for long-context inference.
|
|
5
|
+
- Added the MiniMax M3 model license row to the README license breakdown table, covering M2, M2.5, and M3 under the same permissive royalty-free terms.
|
|
6
|
+
- Added the Web Dashboard favicon/PWA asset pipeline: `icon.png` is now the single source of truth for classic `.ico`, browser PNG sizes, Apple touch icon, Android/PWA icons, Microsoft tiles, `site.webmanifest`, and `browserconfig.xml`.
|
|
7
|
+
- Added provider logo assets and Web Dashboard atoms for provider wordmarks, the `NO KEY` icon, resizable persisted table columns, and stronger health/status iconography.
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- Merged the unreleased `0.5.2` and failed `0.5.3` release notes into this real `0.5.4` release so GitHub Releases and npm history match what actually shipped.
|
|
11
|
+
- Updated the OpenCode Zen README section to list 5 free models and bumped the catalog badge to `154+ models`.
|
|
12
|
+
- Updated the README hero to reference **MiniMax M3** as a headline supported model.
|
|
13
|
+
- Improved Web Dashboard light-theme contrast across latency, TPS, tier, verdict, health, stability, sparkline, status dot, toast, and analytics chart visuals.
|
|
14
|
+
- Improved Web Dashboard model table polish with provider logos, an explicit missing-key icon, and column sizing persistence through `localStorage`.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Fixed the npm release workflow failure from `build:favicons`: CI installed ImageMagick, but Ubuntu exposes the v6 `convert` binary while the script only searched for the v7 `magick` binary. The script now supports both `magick` and `convert`.
|
|
18
|
+
- Fixed the Docker image workflow so it installs ImageMagick before running `pnpm build:web`; tag-triggered Docker publishing now has the same prerequisite as npm publishing.
|
|
19
|
+
- Fixed the release pre-flight health check so an empty/truncated GitHub Status response becomes `unknown` instead of crashing the workflow before checkout.
|
|
20
|
+
- Fixed the commit-log Discord workflow so multiline commit bodies containing apostrophes or quotes no longer break shell parsing.
|
|
21
|
+
- Kept generated favicon files in `web/public/` so the published npm tarball and Docker image contain the expected dashboard icons.
|
|
22
|
+
|
|
23
|
+
### Why
|
|
24
|
+
Versions `0.5.2` and `0.5.3` never reached npm/GitHub Release successfully, so `0.5.4` is the consolidation release: it ships the MiniMax M3 catalog update, the Web Dashboard visual/icon polish, and the CI fixes needed for npm + Docker publishing to complete reliably.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changelog v0.5.5 - 2026-06-01
|
|
2
|
+
|
|
3
|
+
### Changed
|
|
4
|
+
- ⬆️ **deps-dev**: bump `vite` from `8.0.14` → `8.0.16` (#105)
|
|
5
|
+
- ⬆️ **deps-dev**: bump `vite-plus` from `0.1.20` → `0.1.23` (#106)
|
|
6
|
+
- ⬆️ **deps-dev**: bump `@vitejs/plugin-react` from `6.0.1` → `6.0.2` (#107)
|
|
7
|
+
|
|
8
|
+
### CI
|
|
9
|
+
- ⬆️ bump `docker/setup-buildx-action` from `3` → `4` (#104)
|
|
10
|
+
- ⬆️ bump `docker/login-action` from `3` → `4` (#103)
|
|
11
|
+
- ⬆️ bump `pnpm/action-setup` from `4` → `6` (#102)
|
|
12
|
+
|
|
13
|
+
### Notes
|
|
14
|
+
- All 6 dependabot PRs merged in a single batch with conflict resolution on `pnpm-lock.yaml`.
|
|
15
|
+
- 440/440 tests passing.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "free-coding-models",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
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",
|
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
"bin/",
|
|
43
43
|
"src/",
|
|
44
44
|
"web/",
|
|
45
|
+
"!web/node_modules/",
|
|
46
|
+
"!web/node_modules/**",
|
|
45
47
|
"sources.js",
|
|
46
48
|
"patch-openclaw.js",
|
|
47
49
|
"patch-openclaw-models.js",
|
|
@@ -55,7 +57,8 @@
|
|
|
55
57
|
"prepack": "npm run build:web",
|
|
56
58
|
"dev": "node scripts/dev-web.mjs",
|
|
57
59
|
"dev:web": "node scripts/dev-web.mjs",
|
|
58
|
-
"build:
|
|
60
|
+
"build:favicons": "node scripts/build-favicons.mjs",
|
|
61
|
+
"build:web": "npm run build:favicons && vite build",
|
|
59
62
|
"preview:web": "vite preview",
|
|
60
63
|
"test:fcm": "node scripts/testfcm-runner.mjs",
|
|
61
64
|
"test:fcm:mock": "node scripts/testfcm-runner.mjs --tool crush --tool-bin-dir test/fixtures/mock-bin"
|
package/sources.js
CHANGED
|
@@ -360,6 +360,7 @@ export const opencodeZen = [
|
|
|
360
360
|
['deepseek-v4-flash-free', 'DeepSeek V4 Flash Free', 'S+', '79.0%', '200k'],
|
|
361
361
|
['mimo-v2.5-free', 'MiMo-V2.5 Free', 'S+', '-', '200k'],
|
|
362
362
|
['nemotron-3-super-free', 'Nemotron 3 Super Free', 'A+', '52.0%', '200k'],
|
|
363
|
+
['minimax-m3-free', 'MiniMax M3 Free', 'S+', '59.0%', '1M'],
|
|
363
364
|
// Removed (2026-05-28): qwen3.6-plus-free (free promotion ended — now requires OpenCode Go)
|
|
364
365
|
// Removed (2026-05-28): minimax-m2.5-free (7-day launch promo expired — now paid)
|
|
365
366
|
// Removed (2026-05-28): gpt-5-nano (was never free — incorrectly cataloged as free, requires Zen PAYG)
|
|
@@ -368,6 +369,7 @@ export const opencodeZen = [
|
|
|
368
369
|
// Removed (2026-05-26): trinity-mini-free (deleted from Zen)
|
|
369
370
|
// Removed (2026-05-26): trinity-large-preview-free (deleted from Zen)
|
|
370
371
|
// Fix (2026-05-26): nematron-3-super-free ctx 1M → 200k (Zen limits it)
|
|
372
|
+
// Added (2026-06-01): minimax-m3-free — MiniMax M3, 1M context, MSA attention (SWE-Bench Pro 59.0%)
|
|
371
373
|
]
|
|
372
374
|
|
|
373
375
|
// 📖 All sources combined - used by the main script
|
package/src/core/updater.js
CHANGED
|
@@ -32,22 +32,29 @@
|
|
|
32
32
|
* → getManualInstallCmd(pm, version) — Human-readable install command string for error messages
|
|
33
33
|
* → checkForUpdateDetailed() — Fetch npm latest with explicit error info
|
|
34
34
|
* → checkForUpdate() — Startup wrapper, returns version string or null
|
|
35
|
+
* → isPackageDevMode() — Detect git/dev checkouts that must not self-update
|
|
36
|
+
* → enforceMandatoryStartupUpdate() — Mandatory startup self-update with two-failure fallback
|
|
35
37
|
* → runUpdate(latestVersion) — Install new version via detected PM + relaunch
|
|
36
38
|
* @exports
|
|
37
39
|
* detectPackageManager, getInstallArgs, getManualInstallCmd,
|
|
38
|
-
* checkForUpdateDetailed, checkForUpdate,
|
|
40
|
+
* checkForUpdateDetailed, checkForUpdate, isPackageDevMode,
|
|
41
|
+
* enforceMandatoryStartupUpdate, runUpdate, fetchLastReleaseDate
|
|
39
42
|
*
|
|
40
43
|
* @see bin/free-coding-models.js — calls checkForUpdate() at startup and runUpdate() on confirm
|
|
41
44
|
*/
|
|
42
45
|
|
|
43
46
|
import chalk from 'chalk'
|
|
44
47
|
import { createRequire } from 'module'
|
|
45
|
-
import {
|
|
48
|
+
import { fileURLToPath } from 'url'
|
|
49
|
+
import { dirname, join } from 'path'
|
|
50
|
+
import { accessSync, constants, existsSync } from 'fs'
|
|
46
51
|
|
|
47
52
|
const require = createRequire(import.meta.url)
|
|
48
53
|
const readline = require('readline')
|
|
49
54
|
const pkg = require('../../package.json')
|
|
50
55
|
const LOCAL_VERSION = pkg.version
|
|
56
|
+
const PACKAGE_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..')
|
|
57
|
+
export const UPDATE_FAILURE_THRESHOLD = 2
|
|
51
58
|
|
|
52
59
|
/**
|
|
53
60
|
* 📖 detectPackageManager: figure out which package manager owns the current installation.
|
|
@@ -65,6 +72,70 @@ export function detectPackageManager() {
|
|
|
65
72
|
return 'npm'
|
|
66
73
|
}
|
|
67
74
|
|
|
75
|
+
/**
|
|
76
|
+
* 📖 isPackageDevMode: true for repo checkouts and explicit --dev runs.
|
|
77
|
+
* 📖 Self-updating a git checkout creates noisy loops during local development,
|
|
78
|
+
* 📖 while published npm installs do not ship a .git directory and can update safely.
|
|
79
|
+
* @returns {boolean}
|
|
80
|
+
*/
|
|
81
|
+
export function isPackageDevMode() {
|
|
82
|
+
return process.env.FCM_DEV === '1' || existsSync(join(PACKAGE_ROOT, '.git'))
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 📖 getUpdateInstallFailureCount: sanitized persistent failure counter.
|
|
87
|
+
* @param {object} config
|
|
88
|
+
* @returns {number}
|
|
89
|
+
*/
|
|
90
|
+
export function getUpdateInstallFailureCount(config) {
|
|
91
|
+
const raw = Number(config?.settings?.updateInstallFailures || 0)
|
|
92
|
+
if (!Number.isFinite(raw) || raw < 0) return 0
|
|
93
|
+
return Math.floor(raw)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function ensureUpdateSettings(config) {
|
|
97
|
+
if (!config.settings || typeof config.settings !== 'object') config.settings = {}
|
|
98
|
+
return config.settings
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function persistUpdateSettings(config, saveConfig) {
|
|
102
|
+
if (typeof saveConfig !== 'function') return
|
|
103
|
+
try { saveConfig(config) } catch {}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function resetUpdateInstallFailures(config, saveConfig) {
|
|
107
|
+
const settings = ensureUpdateSettings(config)
|
|
108
|
+
if (!settings.updateInstallFailures && !settings.updateLastFailureAt && !settings.updateLastFailureMessage) return
|
|
109
|
+
settings.updateInstallFailures = 0
|
|
110
|
+
delete settings.updateLastFailureAt
|
|
111
|
+
delete settings.updateLastFailureMessage
|
|
112
|
+
delete settings.updateInstallFailureVersion
|
|
113
|
+
persistUpdateSettings(config, saveConfig)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function recordUpdateInstallFailure(config, latestVersion, error, saveConfig) {
|
|
117
|
+
const settings = ensureUpdateSettings(config)
|
|
118
|
+
const nextFailures = Math.min(getUpdateInstallFailureCount(config) + 1, UPDATE_FAILURE_THRESHOLD)
|
|
119
|
+
settings.updateInstallFailures = nextFailures
|
|
120
|
+
settings.updateInstallFailureVersion = latestVersion
|
|
121
|
+
settings.updateLastFailureAt = new Date().toISOString()
|
|
122
|
+
settings.updateLastFailureMessage = error instanceof Error ? error.message : String(error || 'Unknown update error')
|
|
123
|
+
persistUpdateSettings(config, saveConfig)
|
|
124
|
+
return nextFailures
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* 📖 buildOutdatedWarningMessage: one-line message shown when mandatory updates
|
|
129
|
+
* 📖 failed twice and FCM must let the UI start instead of trapping the user.
|
|
130
|
+
* @param {string|null} latestVersion
|
|
131
|
+
* @param {number} failures
|
|
132
|
+
* @returns {string}
|
|
133
|
+
*/
|
|
134
|
+
export function buildOutdatedWarningMessage(latestVersion, failures = UPDATE_FAILURE_THRESHOLD) {
|
|
135
|
+
const target = latestVersion ? `v${LOCAL_VERSION} → v${latestVersion}` : `v${LOCAL_VERSION}`
|
|
136
|
+
return `⚠️ OUTDATED VERSION (${target}) — automatic update failed ${failures} times. Models and free quotas change often; update as soon as possible for the freshest catalog.`
|
|
137
|
+
}
|
|
138
|
+
|
|
68
139
|
/**
|
|
69
140
|
* 📖 getInstallArgs: return the correct binary and argument list for a given PM.
|
|
70
141
|
* 📖 Each PM has different syntax for global install — this normalises them.
|
|
@@ -120,6 +191,89 @@ export async function checkForUpdate() {
|
|
|
120
191
|
return latestVersion
|
|
121
192
|
}
|
|
122
193
|
|
|
194
|
+
/**
|
|
195
|
+
* 📖 enforceMandatoryStartupUpdate: startup policy for every user-facing surface.
|
|
196
|
+
* 📖 If npm has a newer release, FCM installs it immediately without asking.
|
|
197
|
+
* 📖 The first failed install blocks startup so the next launch retries. After two
|
|
198
|
+
* 📖 consecutive install failures, startup is allowed with a loud outdated warning
|
|
199
|
+
* 📖 so offline/proxy/permission users are not permanently locked out.
|
|
200
|
+
*
|
|
201
|
+
* @param {object} config
|
|
202
|
+
* @param {{ saveConfig?: Function, isDevMode?: boolean, surface?: string }} [options]
|
|
203
|
+
* @returns {Promise<{ latestVersion: string|null, allowedOutdated: boolean, warningMessage: string|null, failures: number, checked: boolean, updated: boolean, blocked: boolean }>}
|
|
204
|
+
*/
|
|
205
|
+
export async function enforceMandatoryStartupUpdate(config, options = {}) {
|
|
206
|
+
const { saveConfig, surface = 'app' } = options
|
|
207
|
+
const devMode = typeof options.isDevMode === 'boolean' ? options.isDevMode : isPackageDevMode()
|
|
208
|
+
const base = {
|
|
209
|
+
latestVersion: null,
|
|
210
|
+
allowedOutdated: false,
|
|
211
|
+
warningMessage: null,
|
|
212
|
+
failures: getUpdateInstallFailureCount(config),
|
|
213
|
+
checked: false,
|
|
214
|
+
updated: false,
|
|
215
|
+
blocked: false,
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (devMode) return base
|
|
219
|
+
|
|
220
|
+
const { latestVersion, error } = await checkForUpdateDetailed()
|
|
221
|
+
base.checked = true
|
|
222
|
+
|
|
223
|
+
if (error) {
|
|
224
|
+
const settings = ensureUpdateSettings(config)
|
|
225
|
+
settings.updateCheckFailures = Math.min(Number(settings.updateCheckFailures || 0) + 1, UPDATE_FAILURE_THRESHOLD)
|
|
226
|
+
persistUpdateSettings(config, saveConfig)
|
|
227
|
+
return base
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const settings = ensureUpdateSettings(config)
|
|
231
|
+
if (settings.updateCheckFailures) {
|
|
232
|
+
settings.updateCheckFailures = 0
|
|
233
|
+
persistUpdateSettings(config, saveConfig)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (!latestVersion) {
|
|
237
|
+
resetUpdateInstallFailures(config, saveConfig)
|
|
238
|
+
return base
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
base.latestVersion = latestVersion
|
|
242
|
+
const failuresBeforeInstall = getUpdateInstallFailureCount(config)
|
|
243
|
+
if (failuresBeforeInstall >= UPDATE_FAILURE_THRESHOLD) {
|
|
244
|
+
base.allowedOutdated = true
|
|
245
|
+
base.failures = failuresBeforeInstall
|
|
246
|
+
base.warningMessage = buildOutdatedWarningMessage(latestVersion, failuresBeforeInstall)
|
|
247
|
+
return base
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
console.log(chalk.dim(` ⬆ New version v${latestVersion} detected for ${surface}; updating automatically...`))
|
|
251
|
+
const updateResult = runUpdate(latestVersion, { exitOnFailure: false })
|
|
252
|
+
if (updateResult?.ok) {
|
|
253
|
+
resetUpdateInstallFailures(config, saveConfig)
|
|
254
|
+
base.updated = true
|
|
255
|
+
return base
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const failures = recordUpdateInstallFailure(config, latestVersion, updateResult?.error, saveConfig)
|
|
259
|
+
base.failures = failures
|
|
260
|
+
|
|
261
|
+
if (failures >= UPDATE_FAILURE_THRESHOLD) {
|
|
262
|
+
base.allowedOutdated = true
|
|
263
|
+
base.warningMessage = buildOutdatedWarningMessage(latestVersion, failures)
|
|
264
|
+
console.log(chalk.red(` ${base.warningMessage}`))
|
|
265
|
+
console.log(chalk.dim(` Manual update: ${getManualInstallCmd(detectPackageManager(), latestVersion)}`))
|
|
266
|
+
console.log()
|
|
267
|
+
return base
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
base.blocked = true
|
|
271
|
+
console.log(chalk.red(' ✖ Mandatory update failed. FCM will retry on the next launch.'))
|
|
272
|
+
console.log(chalk.dim(` Attempt ${failures}/${UPDATE_FAILURE_THRESHOLD}. Manual update: ${getManualInstallCmd(detectPackageManager(), latestVersion)}`))
|
|
273
|
+
console.log()
|
|
274
|
+
return base
|
|
275
|
+
}
|
|
276
|
+
|
|
123
277
|
/**
|
|
124
278
|
* 📖 fetchLastReleaseDate: Get the human-readable publish date of the latest npm release.
|
|
125
279
|
* 📖 Used in the TUI footer to show users how fresh the package is.
|
|
@@ -265,10 +419,15 @@ function installUpdateCommand(latestVersion, useSudo) {
|
|
|
265
419
|
/**
|
|
266
420
|
* 📖 runUpdate: Run npm global install to update to latestVersion.
|
|
267
421
|
* 📖 Retries with sudo on permission errors.
|
|
268
|
-
* 📖 Relaunches the process on success
|
|
422
|
+
* 📖 Relaunches the process on success. Manual update actions keep the historic
|
|
423
|
+
* 📖 behavior and exit on failure; mandatory startup checks pass exitOnFailure=false
|
|
424
|
+
* 📖 so they can persist failure counters and decide whether to let the UI start.
|
|
269
425
|
* @param {string} latestVersion
|
|
426
|
+
* @param {{ exitOnFailure?: boolean, relaunchOnSuccess?: boolean }} [options]
|
|
427
|
+
* @returns {{ ok: boolean, error?: unknown }}
|
|
270
428
|
*/
|
|
271
|
-
export function runUpdate(latestVersion) {
|
|
429
|
+
export function runUpdate(latestVersion, options = {}) {
|
|
430
|
+
const { exitOnFailure = true, relaunchOnSuccess = true } = options
|
|
272
431
|
console.log()
|
|
273
432
|
console.log(chalk.bold.cyan(' ⬆ Updating free-coding-models to v' + latestVersion + '...'))
|
|
274
433
|
console.log()
|
|
@@ -276,6 +435,7 @@ export function runUpdate(latestVersion) {
|
|
|
276
435
|
const pm = detectPackageManager()
|
|
277
436
|
const { needsSudo, checkedPath } = detectGlobalInstallPermission(pm)
|
|
278
437
|
const sudoAvailable = process.platform !== 'win32' && hasSudoCommand()
|
|
438
|
+
let lastError = null
|
|
279
439
|
|
|
280
440
|
if (needsSudo && checkedPath && sudoAvailable) {
|
|
281
441
|
console.log(chalk.yellow(` ⚠ Global ${pm} path is not writable: ${checkedPath}`))
|
|
@@ -288,9 +448,10 @@ export function runUpdate(latestVersion) {
|
|
|
288
448
|
console.log()
|
|
289
449
|
console.log(chalk.green(` ✅ Update complete! Version ${latestVersion} installed.`))
|
|
290
450
|
console.log()
|
|
291
|
-
relaunchCurrentProcess()
|
|
292
|
-
return
|
|
451
|
+
if (relaunchOnSuccess) relaunchCurrentProcess()
|
|
452
|
+
return { ok: true }
|
|
293
453
|
} catch (err) {
|
|
454
|
+
lastError = err
|
|
294
455
|
const manualCmd = getManualInstallCmd(pm, latestVersion)
|
|
295
456
|
console.log()
|
|
296
457
|
if (isPermissionError(err) && !needsSudo && sudoAvailable) {
|
|
@@ -301,9 +462,10 @@ export function runUpdate(latestVersion) {
|
|
|
301
462
|
console.log()
|
|
302
463
|
console.log(chalk.green(` ✅ Update complete with sudo! Version ${latestVersion} installed.`))
|
|
303
464
|
console.log()
|
|
304
|
-
relaunchCurrentProcess()
|
|
305
|
-
return
|
|
306
|
-
} catch {
|
|
465
|
+
if (relaunchOnSuccess) relaunchCurrentProcess()
|
|
466
|
+
return { ok: true }
|
|
467
|
+
} catch (sudoErr) {
|
|
468
|
+
lastError = sudoErr
|
|
307
469
|
console.log()
|
|
308
470
|
console.log(chalk.red(' ✖ Update failed even with sudo. Try manually:'))
|
|
309
471
|
console.log(chalk.dim(` sudo ${manualCmd}`))
|
|
@@ -318,7 +480,9 @@ export function runUpdate(latestVersion) {
|
|
|
318
480
|
console.log()
|
|
319
481
|
}
|
|
320
482
|
}
|
|
321
|
-
|
|
483
|
+
|
|
484
|
+
if (exitOnFailure) process.exit(1)
|
|
485
|
+
return { ok: false, error: lastError }
|
|
322
486
|
}
|
|
323
487
|
|
|
324
488
|
|
package/src/tui/app.js
CHANGED
|
@@ -91,11 +91,7 @@
|
|
|
91
91
|
|
|
92
92
|
import chalk from 'chalk'
|
|
93
93
|
import { createRequire } from 'module'
|
|
94
|
-
import { fileURLToPath } from 'url'
|
|
95
|
-
import { readFileSync, writeFileSync, existsSync, copyFileSync, mkdirSync } from 'fs'
|
|
96
94
|
import { randomUUID } from 'crypto'
|
|
97
|
-
import { homedir } from 'os'
|
|
98
|
-
import { join, dirname } from 'path'
|
|
99
95
|
import { MODELS, sources } from '../../sources.js'
|
|
100
96
|
import { getAvg, getVerdict, getUptime, getP95, getJitter, getStabilityScore, sortResults, filterByTier, findBestModel, parseArgs, TIER_ORDER, VERDICT_ORDER, TIER_LETTER_MAP, scoreModelForTask, getTopRecommendations, TASK_TYPES, PRIORITY_TYPES, CONTEXT_BUDGETS, formatCtxWindow, labelFromId, formatResultsAsJSON } from '../core/utils.js'
|
|
101
97
|
import { loadConfig, saveConfig, getApiKey, resolveApiKeys, addApiKey, removeApiKey, isProviderEnabled, persistApiKeysForProvider } from '../core/config.js'
|
|
@@ -181,7 +177,7 @@ const LOCAL_VERSION = pkg.version
|
|
|
181
177
|
// ─── OpenCode integration ──────────────────────────────────────────────────────
|
|
182
178
|
// 📖 OpenCode helpers are imported from ../src/opencode.js
|
|
183
179
|
|
|
184
|
-
export async function runApp(cliArgs, config) {
|
|
180
|
+
export async function runApp(cliArgs, config, startupOptions = {}) {
|
|
185
181
|
|
|
186
182
|
// 📖 Detect user active terminal theme
|
|
187
183
|
detectActiveTheme(config.settings?.theme || 'auto')
|
|
@@ -278,32 +274,12 @@ export async function runApp(cliArgs, config) {
|
|
|
278
274
|
},
|
|
279
275
|
})
|
|
280
276
|
|
|
281
|
-
// 📖
|
|
282
|
-
// 📖
|
|
283
|
-
// 📖
|
|
284
|
-
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
latestVersion = await checkForUpdate()
|
|
288
|
-
// 📖 Reset failure counter on successful check
|
|
289
|
-
if (config.settings?.updateCheckFailures) {
|
|
290
|
-
config.settings.updateCheckFailures = 0
|
|
291
|
-
saveConfig(config)
|
|
292
|
-
}
|
|
293
|
-
} catch (err) {
|
|
294
|
-
const failures = (config.settings?.updateCheckFailures || 0) + 1
|
|
295
|
-
if (!config.settings) config.settings = {}
|
|
296
|
-
config.settings.updateCheckFailures = Math.min(failures, 3)
|
|
297
|
-
saveConfig(config)
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// 📖 Auto-update: if a new version is available, install it immediately (skip in dev mode)
|
|
301
|
-
// 📖 runUpdate() will relaunch the process with the new version after install completes
|
|
302
|
-
if (latestVersion && !isDevMode) {
|
|
303
|
-
console.log(chalk.dim(` ⬆ New version v${latestVersion} detected, updating...`))
|
|
304
|
-
runUpdate(latestVersion)
|
|
305
|
-
return // 📖 runUpdate relaunches the process — this line is a safety guard
|
|
306
|
-
}
|
|
277
|
+
// 📖 Mandatory startup update is enforced in the bin entry before any user-facing
|
|
278
|
+
// 📖 surface starts. runApp receives the result so the TUI can display the
|
|
279
|
+
// 📖 two-failure fallback warning without repeating npm registry/install work.
|
|
280
|
+
const startupUpdate = startupOptions.startupUpdate || {}
|
|
281
|
+
const latestVersion = startupUpdate.latestVersion || null
|
|
282
|
+
const isDevMode = startupOptions.isDevMode === true
|
|
307
283
|
|
|
308
284
|
// 📖 Dynamic OpenRouter free model discovery — fetch live free models from API
|
|
309
285
|
// 📖 Replaces static openrouter entries in MODELS with fresh data.
|
|
@@ -363,6 +339,8 @@ export async function runApp(cliArgs, config) {
|
|
|
363
339
|
sessionId,
|
|
364
340
|
latestVersion,
|
|
365
341
|
isDevMode,
|
|
342
|
+
updateWarningMessage: startupUpdate.warningMessage || null,
|
|
343
|
+
updateInstallFailures: startupUpdate.failures || 0,
|
|
366
344
|
})
|
|
367
345
|
|
|
368
346
|
// 📖 Apply the pre-fetched last release date now that state is initialized
|
|
@@ -823,6 +801,7 @@ export async function runApp(cliArgs, config) {
|
|
|
823
801
|
settingsUpdateLatestVersion: state.settingsUpdateLatestVersion,
|
|
824
802
|
startupLatestVersion: state.startupLatestVersion,
|
|
825
803
|
versionAlertsEnabled: state.versionAlertsEnabled,
|
|
804
|
+
updateWarningMessage: state.updateWarningMessage,
|
|
826
805
|
favoritesPinnedAndSticky: state.favoritesPinnedAndSticky,
|
|
827
806
|
customTextFilter: state.customTextFilter,
|
|
828
807
|
lastReleaseDate: state.lastReleaseDate,
|
|
@@ -914,6 +893,7 @@ export async function runApp(cliArgs, config) {
|
|
|
914
893
|
settingsUpdateLatestVersion: state.settingsUpdateLatestVersion,
|
|
915
894
|
startupLatestVersion: state.startupLatestVersion,
|
|
916
895
|
versionAlertsEnabled: state.versionAlertsEnabled,
|
|
896
|
+
updateWarningMessage: state.updateWarningMessage,
|
|
917
897
|
favoritesPinnedAndSticky: state.favoritesPinnedAndSticky,
|
|
918
898
|
customTextFilter: state.customTextFilter,
|
|
919
899
|
lastReleaseDate: state.lastReleaseDate,
|
package/src/tui/key-handler.js
CHANGED
|
@@ -82,7 +82,7 @@ const PROVIDER_AUTH_ENDPOINTS = {
|
|
|
82
82
|
groq: { url: 'https://api.groq.com/v1/models', method: 'GET' },
|
|
83
83
|
cerebras: { url: 'https://api.cerebras.ai/v1/models', method: 'GET' },
|
|
84
84
|
sambanova: { url: 'https://api.sambanova.ai/v1/models', method: 'GET' },
|
|
85
|
-
openrouter: { url: 'https://openrouter.ai/api/v1/
|
|
85
|
+
openrouter: { url: 'https://openrouter.ai/api/v1/key', method: 'GET' },
|
|
86
86
|
mistral: { url: 'https://api.mistral.ai/v1/models', method: 'GET' },
|
|
87
87
|
huggingface: { url: 'https://router.huggingface.co/v1/models', method: 'GET' },
|
|
88
88
|
deepinfra: { url: 'https://api.deepinfra.com/v1/models', method: 'GET' },
|
package/src/tui/render-table.js
CHANGED
|
@@ -136,6 +136,7 @@ export const PROVIDER_COLOR = new Proxy({}, {
|
|
|
136
136
|
* settingsUpdateLatestVersion: string|null,
|
|
137
137
|
* startupLatestVersion: string|null,
|
|
138
138
|
* versionAlertsEnabled: boolean,
|
|
139
|
+
* updateWarningMessage?: string|null,
|
|
139
140
|
* favoritesPinnedAndSticky: boolean,
|
|
140
141
|
* customTextFilter: string|null,
|
|
141
142
|
* lastReleaseDate: string|null,
|
|
@@ -174,6 +175,7 @@ export function renderTable({
|
|
|
174
175
|
settingsUpdateLatestVersion = null,
|
|
175
176
|
startupLatestVersion = null,
|
|
176
177
|
versionAlertsEnabled = true,
|
|
178
|
+
updateWarningMessage = null,
|
|
177
179
|
favoritesPinnedAndSticky = false,
|
|
178
180
|
customTextFilter = null,
|
|
179
181
|
lastReleaseDate = null,
|
|
@@ -1073,15 +1075,19 @@ export function renderTable({
|
|
|
1073
1075
|
)
|
|
1074
1076
|
|
|
1075
1077
|
if (versionStatus.isOutdated) {
|
|
1076
|
-
const updateMsg =
|
|
1078
|
+
const updateMsg = updateWarningMessage
|
|
1079
|
+
? ` ${updateWarningMessage} • Press Shift+U to retry update `
|
|
1080
|
+
: ` 🚀⬆️ UPDATE AVAILABLE — v${LOCAL_VERSION} → v${versionStatus.latestVersion} • Click here or press Shift+U to update 🚀⬆️ `
|
|
1077
1081
|
const paddedBanner = terminalCols > 0
|
|
1078
1082
|
? updateMsg + ' '.repeat(Math.max(0, terminalCols - displayWidth(updateMsg)))
|
|
1079
1083
|
: updateMsg
|
|
1080
|
-
const
|
|
1084
|
+
const updateBanner = updateWarningMessage
|
|
1085
|
+
? chalk.bgRed.white.bold(paddedBanner)
|
|
1086
|
+
: chalk.bgRgb(57, 255, 20).rgb(0, 0, 0).bold(paddedBanner)
|
|
1081
1087
|
const updateBannerRow = lines.length + 1
|
|
1082
1088
|
_lastLayout.updateBannerRow = updateBannerRow
|
|
1083
1089
|
footerHotkeys.push({ key: 'update-click', row: updateBannerRow, xStart: 1, xEnd: Math.max(terminalCols, displayWidth(updateMsg)) })
|
|
1084
|
-
lines.push(
|
|
1090
|
+
lines.push(updateBanner)
|
|
1085
1091
|
} else {
|
|
1086
1092
|
_lastLayout.updateBannerRow = 0
|
|
1087
1093
|
}
|
package/src/tui/tui-state.js
CHANGED
|
@@ -70,6 +70,8 @@ export function intervalToPingMode(intervalMs) {
|
|
|
70
70
|
* sessionId: string,
|
|
71
71
|
* latestVersion: string|null,
|
|
72
72
|
* isDevMode: boolean,
|
|
73
|
+
* updateWarningMessage?: string|null,
|
|
74
|
+
* updateInstallFailures?: number,
|
|
73
75
|
* }} opts
|
|
74
76
|
* @returns {object} The complete TUI state (mutable)
|
|
75
77
|
*/
|
|
@@ -80,6 +82,8 @@ export function createTuiState({
|
|
|
80
82
|
sessionId,
|
|
81
83
|
latestVersion,
|
|
82
84
|
isDevMode,
|
|
85
|
+
updateWarningMessage = null,
|
|
86
|
+
updateInstallFailures = 0,
|
|
83
87
|
}) {
|
|
84
88
|
const now = Date.now()
|
|
85
89
|
|
|
@@ -110,6 +114,8 @@ export function createTuiState({
|
|
|
110
114
|
|
|
111
115
|
// 📖 Version tracking — startup auto-check + periodic re-check
|
|
112
116
|
startupLatestVersion: latestVersion,
|
|
117
|
+
updateWarningMessage,
|
|
118
|
+
updateInstallFailures,
|
|
113
119
|
lastReleaseDate: null,
|
|
114
120
|
versionAlertsEnabled: !isDevMode,
|
|
115
121
|
|