diff --git a/README.ko.md b/README.ko.md index 421d490..e747317 100644 --- a/README.ko.md +++ b/README.ko.md @@ -72,18 +72,18 @@ FREE_ROUTER_METRICS_CACHE=0 free-router ### 컬럼 설명 -| 컬럼 | 설명 | -| ---------- | ------------------------------------------------------ | -| `#` | 순위 | -| `Tier` | SWE-bench 점수 기반 성능 등급 (S+ → C) | -| `Provider` | NIM 또는 OpenRouter | -| `Model` | 모델 이름 | -| `Ctx` | 컨텍스트 윈도우 크기 | -| `AA` | Arena Elo / 지능 점수 | -| `Avg` | HTTP 200 응답만을 기준으로 한 평균 응답 속도 | -| `Lat` | 마지막으로 측정된 핑 응답 속도 | -| `Up%` | 현재 세션 가동률 | -| `Verdict` | 상태 요약 (✓ Perfect / ✓ Normal / x Overloaded / …) | +| 컬럼 | 설명 | +| ---------- | --------------------------------------------------- | +| `#` | 순위 | +| `Tier` | SWE-bench 점수 기반 성능 등급 (S+ → C) | +| `Provider` | NIM 또는 OpenRouter | +| `Model` | 모델 이름 | +| `Ctx` | 컨텍스트 윈도우 크기 | +| `AA` | Arena Elo / 지능 점수 | +| `Avg` | HTTP 200 응답만을 기준으로 한 평균 응답 속도 | +| `Lat` | 마지막으로 측정된 핑 응답 속도 | +| `Up%` | 현재 세션 가동률 | +| `Verdict` | 상태 요약 (✓ Perfect / ✓ Normal / x Overloaded / …) | 기본 정렬 기준: **응답 가능 모델 우선**, 그 다음 **높은 등급 우선** (S+ → S → A+ …), 그 다음 낮은 응답 속도. diff --git a/README.md b/README.md index 6513123..a5d5313 100644 --- a/README.md +++ b/README.md @@ -72,18 +72,18 @@ The selected row uses a stable marker, and redraws are deferred while the termin ### Columns -| Column | Description | -| ---------- | -------------------------------------------------------------- | -| `#` | Rank | -| `Tier` | Capability tier derived from SWE-bench score (S+ → C) | -| `Provider` | NIM or OpenRouter | -| `Model` | Display name | -| `Ctx` | Context window size | -| `AA` | Arena Elo / intelligence score | -| `Avg` | Rolling average latency (HTTP 200 only) | -| `Lat` | Latest measured ping latency | -| `Up%` | Uptime percentage this session | -| `Verdict` | Condition summary (✓ Perfect / ✓ Normal / x Overloaded / …) | +| Column | Description | +| ---------- | ----------------------------------------------------------- | +| `#` | Rank | +| `Tier` | Capability tier derived from SWE-bench score (S+ → C) | +| `Provider` | NIM or OpenRouter | +| `Model` | Display name | +| `Ctx` | Context window size | +| `AA` | Arena Elo / intelligence score | +| `Avg` | Rolling average latency (HTTP 200 only) | +| `Lat` | Latest measured ping latency | +| `Up%` | Uptime percentage this session | +| `Verdict` | Condition summary (✓ Perfect / ✓ Normal / x Overloaded / …) | Default ranking: **availability first**, then **higher tier first** (S+ → S → A+ …), then lower latency. @@ -111,17 +111,17 @@ uses the same foreground color as the table rows. **Actions** -| Key | Action | -| -------------- | ---------------------------------------------------------- | -| `Enter` | Configure current model for OpenCode / OpenClaw / Hermes | +| Key | Action | +| -------------- | ----------------------------------------------------------- | +| `Enter` | Configure current model for OpenCode / OpenClaw / Hermes | | `/` | Search / filter models (Enter in search = configure target) | -| `A` | Quick API key add/change (opens key editor in Settings) | -| `R` | Edit API key for likely expired/missing provider | -| `T` | Cycle tier filter: All → S+ → S → A+ → … | -| `P` | Settings screen (edit keys, toggle providers, test) | -| `W` / `X` | Faster / slower ping interval | -| `?` | Help overlay | -| `q` / `Ctrl+C` | Quit | +| `A` | Quick API key add/change (opens key editor in Settings) | +| `R` | Edit API key for likely expired/missing provider | +| `T` | Cycle tier filter: All → S+ → S → A+ → … | +| `P` | Settings screen (edit keys, toggle providers, test) | +| `W` / `X` | Faster / slower ping interval | +| `?` | Help overlay | +| `q` / `Ctrl+C` | Quit | **Sort** (press to sort, press again to reverse) diff --git a/data/model-rankings.md b/data/model-rankings.md index 923c138..e7a0402 100644 --- a/data/model-rankings.md +++ b/data/model-rankings.md @@ -8,136 +8,136 @@ **100 models** · Free API via [build.nvidia.com](https://build.nvidia.com) -| Model | Tier | SWE | AA Intel | AA Speed | Context | -| ----- | ---- | --- | -------- | -------- | ------- | -| GLM 5 | S+ | 77.8% | 49.8 | 68.4 t/s | 128k | -| Kimi K2.5 | S+ | 76.8% | 46.8 | 33.8 t/s | 128k | -| GLM 4.7 | S+ | 73.8% | 42.1 | 93.3 t/s | 200k | -| Kimi K2 Thinking | S+ | 71.3% | 40.9 | 75.2 t/s | 256k | -| DeepSeek V3.2 | S+ | 73.1% | 32.1 | 80 t/s | 128k | -| Devstral 2 123B | S+ | 72.2% | 22 | 75.8 t/s | 256k | -| Qwen3 235B | S+ | 70.0% | — | — | 128k | -| Qwen3 Coder 480B | S+ | 70.6% | 24.8 | 69.9 t/s | 256k | -| Step 3.5 Flash | S+ | 74.4% | 38.5 | 140.5 t/s | 256k | -| MiniMax M2 | S | 69.4% | 36.1 | 89.9 t/s | 128k | -| GPT OSS 120B | S | 60.0% | 33.3 | 221 t/s | 131k | -| DeepSeek V3.1 Term. | S | 68.4% | 28.5 | 0 t/s | 128k | -| Kimi K2 0905 | A | — | 26.3 | 36.3 t/s | 128k | -| Kimi K2 Instruct | S | 65.8% | 26.3 | 36.3 t/s | 128k | -| Llama 4 Maverick | S | 62.0% | 18.4 | 111 t/s | 1m | -| Qwen3 80B Instruct | S | 65.0% | 20.1 | 174.5 t/s | 128k | -| Qwen3 80B Thinking | S | 68.0% | — | — | 128k | -| Qwen3.5 400B | S | 68.0% | 45 | 50.9 t/s | 128k | -| Mistral Large 675B | A+ | 58.0% | 23 | 56 t/s | 256k | -| Nemotron Ultra 253B | A+ | 56.0% | — | — | 128k | -| GPT OSS 20B | A | 42.0% | 24.5 | 277.7 t/s | 131k | -| Mistral Medium 3 | A | 48.0% | 18.8 | 65.6 t/s | 128k | -| Magistral Small | A | 45.0% | 16.8 | 0 t/s | 32k | -| Llama 3.1 405B | A | 44.0% | — | — | 128k | -| Mistral Large 2 | A- | — | 15.1 | 38.6 t/s | 128k | -| Mistral Nemotron | A | — | — | — | 128k | -| Nemotron 4 340B | A | — | — | — | 4k | -| Nemotron Nano 3 30B | A | — | — | — | 128k | -| Nemotron Nano 30B | A | 43.0% | — | — | 128k | -| Nemotron Super 49B | A | 49.0% | 14.3 | 0 t/s | 128k | -| Nemotron Super 49B v1 | B+ | — | 14.3 | 0 t/s | 128k | -| Qwen2.5 Coder 32B | A | 46.0% | 12.9 | 0 t/s | 32k | -| Llama 3.2 90B Vision | A- | — | — | — | 128k | -| Llama 3.3 70B | A- | 39.5% | — | — | 128k | -| Mistral Large | B | — | 9.9 | 0 t/s | 32k | -| Nemotron 51B | A- | — | — | — | 128k | -| Nemotron 70B | A- | — | — | — | 128k | -| Palmyra Creative 122B | A- | — | — | — | 32k | -| Seed OSS 36B | A- | 38.0% | 25.2 | 42.4 t/s | 32k | -| Stockmark 100B | A- | 36.0% | — | — | 32k | -| ChatQA 1.5 70B | B+ | — | — | — | 128k | -| Code Llama 70B | B+ | — | — | — | 4k | -| Codestral 22B | B+ | — | — | — | 32k | -| Dracarys 70B | B+ | — | — | — | 128k | -| Granite 34B Code | B+ | 30.0% | — | — | 32k | -| Llama 3.1 70B | B+ | — | — | — | 128k | -| Ministral 14B | B+ | 34.0% | — | — | 32k | -| Mixtral 8x22B | B+ | 32.0% | — | — | 64k | -| Palmyra Fin 70B | B+ | — | — | — | 32k | -| Palmyra Med 70B | B+ | — | — | — | 32k | -| Palmyra Med 70B 32K | B+ | — | — | — | 32k | -| Yi Large | B+ | — | — | — | 32k | -| Cosmos Reason2 8B | B | — | — | — | 128k | -| Llama 2 70B | B | — | — | — | 4k | -| Llama 3.1 8B | B | — | — | — | 128k | -| Llama 3.2 11B Vision | B | — | — | — | 128k | -| Mistral Nemo 12B | B | — | — | — | 128k | -| Nemotron Nano 12B VL | B | — | — | — | 128k | -| Nemotron Nano 9B v2 | B+ | — | 13.2 | 152.7 t/s | 128k | -| Phi 3 Vision | B | — | — | — | 128k | -| Phi 3.5 MoE | B | — | — | — | 128k | -| Phi 4 Multimodal | B | — | 10 | 16.9 t/s | 128k | -| Jamba 1.5 Large | B | — | 10.7 | 0 t/s | 256k | -| Phi 4 Mini | C | 14.0% | 8.4 | 44.5 t/s | 128k | -| DBRX Instruct | B | — | 8.3 | 0 t/s | 33k | -| Mixtral 8x7B | C | — | 8 | — | 33k | -| CodeGemma 7B | C | — | — | — | 8k | -| DeepSeek Coder 6.7B | C | — | — | — | 16k | -| Gemma 2 2B | C | — | — | — | 8k | -| Gemma 3n 2B | C | — | 4.8 | 60.9 t/s | 8k | -| Gemma 3n 4B | C | — | 6.4 | 14.3 t/s | 8k | -| Granite 3.0 3B | C | — | — | — | 128k | -| Granite 3.0 8B | C | — | — | — | 128k | -| Granite 8B Code | C | — | — | — | 8k | -| Llama 3.2 1B | C | — | — | — | 128k | -| Llama 3.2 3B | C | — | — | — | 128k | -| Minitron 8B | C | — | — | — | 8k | -| Mistral 7B v0.3 | C | — | — | — | 32k | -| Nemotron Mini 4B | C | — | — | — | 4k | -| Nemotron Nano 8B | C | — | — | — | 128k | -| Sarvam M | C | — | — | — | 8k | -| SEA-LION 7B | C | — | — | — | 4k | -| Solar 10.7B | C | — | — | — | 4k | -| Zamba2 7B | C | — | — | — | 8k | -| Nemotron Nano VL 8B | C | — | — | — | 128k | -| Gemma 3 12B | B | — | 8.8 | 25 t/s | 33k | -| Gemma 3 4B | C | — | 6.3 | 26.1 t/s | 33k | -| MiniMax M2.5 | S | — | 41.9 | 92.8 t/s | 205k | -| GLiNER PII | C | — | — | — | 33k | -| Nemotron 3 Super 120B | S | 60.5% | — | 449.5 t/s | 262k | -| Qwen3.5 122B | S | — | 41.6 | 156.2 t/s | 131k | -| Deepseek V4 Flash | S+ | — | 46.5 | 79.3 t/s | 33k | -| Deepseek V4 Pro | S+ | — | 51.5 | 35.7 t/s | 33k | -| Gemma 3 27b It | B | — | 10.3 | 25.9 t/s | 33k | -| Gemma 4 31b It | S | — | 39.2 | 35.5 t/s | 33k | -| Minimax M2.7 | S+ | — | 49.6 | 45.5 t/s | 33k | -| Mistral Small 4 119b 2603 | ? | — | — | — | 33k | -| Ising Calibration 1 35b A3b | ? | — | — | — | 33k | +| Model | Tier | SWE | AA Intel | AA Speed | Context | +| -------------------------------------- | ---- | ----- | -------- | --------- | ------- | +| GLM 5 | S+ | 77.8% | 49.8 | 68.4 t/s | 128k | +| Kimi K2.5 | S+ | 76.8% | 46.8 | 33.8 t/s | 128k | +| GLM 4.7 | S+ | 73.8% | 42.1 | 93.3 t/s | 200k | +| Kimi K2 Thinking | S+ | 71.3% | 40.9 | 75.2 t/s | 256k | +| DeepSeek V3.2 | S+ | 73.1% | 32.1 | 80 t/s | 128k | +| Devstral 2 123B | S+ | 72.2% | 22 | 75.8 t/s | 256k | +| Qwen3 235B | S+ | 70.0% | — | — | 128k | +| Qwen3 Coder 480B | S+ | 70.6% | 24.8 | 69.9 t/s | 256k | +| Step 3.5 Flash | S+ | 74.4% | 38.5 | 140.5 t/s | 256k | +| MiniMax M2 | S | 69.4% | 36.1 | 89.9 t/s | 128k | +| GPT OSS 120B | S | 60.0% | 33.3 | 221 t/s | 131k | +| DeepSeek V3.1 Term. | S | 68.4% | 28.5 | 0 t/s | 128k | +| Kimi K2 0905 | A | — | 26.3 | 36.3 t/s | 128k | +| Kimi K2 Instruct | S | 65.8% | 26.3 | 36.3 t/s | 128k | +| Llama 4 Maverick | S | 62.0% | 18.4 | 111 t/s | 1m | +| Qwen3 80B Instruct | S | 65.0% | 20.1 | 174.5 t/s | 128k | +| Qwen3 80B Thinking | S | 68.0% | — | — | 128k | +| Qwen3.5 400B | S | 68.0% | 45 | 50.9 t/s | 128k | +| Mistral Large 675B | A+ | 58.0% | 23 | 56 t/s | 256k | +| Nemotron Ultra 253B | A+ | 56.0% | — | — | 128k | +| GPT OSS 20B | A | 42.0% | 24.5 | 277.7 t/s | 131k | +| Mistral Medium 3 | A | 48.0% | 18.8 | 65.6 t/s | 128k | +| Magistral Small | A | 45.0% | 16.8 | 0 t/s | 32k | +| Llama 3.1 405B | A | 44.0% | — | — | 128k | +| Mistral Large 2 | A- | — | 15.1 | 38.6 t/s | 128k | +| Mistral Nemotron | A | — | — | — | 128k | +| Nemotron 4 340B | A | — | — | — | 4k | +| Nemotron Nano 3 30B | A | — | — | — | 128k | +| Nemotron Nano 30B | A | 43.0% | — | — | 128k | +| Nemotron Super 49B | A | 49.0% | 14.3 | 0 t/s | 128k | +| Nemotron Super 49B v1 | B+ | — | 14.3 | 0 t/s | 128k | +| Qwen2.5 Coder 32B | A | 46.0% | 12.9 | 0 t/s | 32k | +| Llama 3.2 90B Vision | A- | — | — | — | 128k | +| Llama 3.3 70B | A- | 39.5% | — | — | 128k | +| Mistral Large | B | — | 9.9 | 0 t/s | 32k | +| Nemotron 51B | A- | — | — | — | 128k | +| Nemotron 70B | A- | — | — | — | 128k | +| Palmyra Creative 122B | A- | — | — | — | 32k | +| Seed OSS 36B | A- | 38.0% | 25.2 | 42.4 t/s | 32k | +| Stockmark 100B | A- | 36.0% | — | — | 32k | +| ChatQA 1.5 70B | B+ | — | — | — | 128k | +| Code Llama 70B | B+ | — | — | — | 4k | +| Codestral 22B | B+ | — | — | — | 32k | +| Dracarys 70B | B+ | — | — | — | 128k | +| Granite 34B Code | B+ | 30.0% | — | — | 32k | +| Llama 3.1 70B | B+ | — | — | — | 128k | +| Ministral 14B | B+ | 34.0% | — | — | 32k | +| Mixtral 8x22B | B+ | 32.0% | — | — | 64k | +| Palmyra Fin 70B | B+ | — | — | — | 32k | +| Palmyra Med 70B | B+ | — | — | — | 32k | +| Palmyra Med 70B 32K | B+ | — | — | — | 32k | +| Yi Large | B+ | — | — | — | 32k | +| Cosmos Reason2 8B | B | — | — | — | 128k | +| Llama 2 70B | B | — | — | — | 4k | +| Llama 3.1 8B | B | — | — | — | 128k | +| Llama 3.2 11B Vision | B | — | — | — | 128k | +| Mistral Nemo 12B | B | — | — | — | 128k | +| Nemotron Nano 12B VL | B | — | — | — | 128k | +| Nemotron Nano 9B v2 | B+ | — | 13.2 | 152.7 t/s | 128k | +| Phi 3 Vision | B | — | — | — | 128k | +| Phi 3.5 MoE | B | — | — | — | 128k | +| Phi 4 Multimodal | B | — | 10 | 16.9 t/s | 128k | +| Jamba 1.5 Large | B | — | 10.7 | 0 t/s | 256k | +| Phi 4 Mini | C | 14.0% | 8.4 | 44.5 t/s | 128k | +| DBRX Instruct | B | — | 8.3 | 0 t/s | 33k | +| Mixtral 8x7B | C | — | 8 | — | 33k | +| CodeGemma 7B | C | — | — | — | 8k | +| DeepSeek Coder 6.7B | C | — | — | — | 16k | +| Gemma 2 2B | C | — | — | — | 8k | +| Gemma 3n 2B | C | — | 4.8 | 60.9 t/s | 8k | +| Gemma 3n 4B | C | — | 6.4 | 14.3 t/s | 8k | +| Granite 3.0 3B | C | — | — | — | 128k | +| Granite 3.0 8B | C | — | — | — | 128k | +| Granite 8B Code | C | — | — | — | 8k | +| Llama 3.2 1B | C | — | — | — | 128k | +| Llama 3.2 3B | C | — | — | — | 128k | +| Minitron 8B | C | — | — | — | 8k | +| Mistral 7B v0.3 | C | — | — | — | 32k | +| Nemotron Mini 4B | C | — | — | — | 4k | +| Nemotron Nano 8B | C | — | — | — | 128k | +| Sarvam M | C | — | — | — | 8k | +| SEA-LION 7B | C | — | — | — | 4k | +| Solar 10.7B | C | — | — | — | 4k | +| Zamba2 7B | C | — | — | — | 8k | +| Nemotron Nano VL 8B | C | — | — | — | 128k | +| Gemma 3 12B | B | — | 8.8 | 25 t/s | 33k | +| Gemma 3 4B | C | — | 6.3 | 26.1 t/s | 33k | +| MiniMax M2.5 | S | — | 41.9 | 92.8 t/s | 205k | +| GLiNER PII | C | — | — | — | 33k | +| Nemotron 3 Super 120B | S | 60.5% | — | 449.5 t/s | 262k | +| Qwen3.5 122B | S | — | 41.6 | 156.2 t/s | 131k | +| Deepseek V4 Flash | S+ | — | 46.5 | 79.3 t/s | 33k | +| Deepseek V4 Pro | S+ | — | 51.5 | 35.7 t/s | 33k | +| Gemma 3 27b It | B | — | 10.3 | 25.9 t/s | 33k | +| Gemma 4 31b It | S | — | 39.2 | 35.5 t/s | 33k | +| Minimax M2.7 | S+ | — | 49.6 | 45.5 t/s | 33k | +| Mistral Small 4 119b 2603 | ? | — | — | — | 33k | +| Ising Calibration 1 35b A3b | ? | — | — | — | 33k | | Nemotron 3 Nano Omni 30b A3b Reasoning | ? | — | — | — | 33k | -| Glm 5.1 | S+ | — | 51.4 | 50.5 t/s | 33k | +| Glm 5.1 | S+ | — | 51.4 | 50.5 t/s | 33k | ## Free OpenRouter Models **24 models** · Free tier via [openrouter.ai](https://openrouter.ai) -| Model | Tier | SWE | AA Intel | AA Speed | Context | -| ----- | ---- | --- | -------- | -------- | ------- | -| Qwen3 Coder 480B | A+ | — | — | — | 262k | -| Qwen3 80B Instruct | A | — | 20.1 | 174.5 t/s | 262k | -| Llama 3.3 70B | A- | — | — | — | 128k | -| GPT OSS 120B | A+ | — | 33.3 | 221 t/s | 131k | -| Nemotron Nano 30B | B+ | — | — | — | 256k | -| GPT OSS 20B | A | — | 24.5 | 277.7 t/s | 131k | -| GLM 4.5 Air | A | — | 23.2 | 60 t/s | 128k | -| Nemotron Nano 9B | B | — | — | — | 128k | -| NVIDIA: Nemotron 3 Super (free) | S | 60.5% | 36 | 449.5 t/s | 262k | -| inclusionAI: Ling-2.6-1T (free) | A+ | — | 33.6 | 0 t/s | 262k | -| inclusionAI: Ling-2.6-flash (free) | A | — | 26.2 | 206.6 t/s | 262k | -| MiniMax: MiniMax M2.5 (free) | S | — | 41.9 | 92.8 t/s | 197k | -| Poolside: Laguna M.1 (free) | ? | — | — | — | 131k | -| Poolside: Laguna XS.2 (free) | ? | — | — | — | 131k | -| Tencent: Hy3 preview (free) | S | — | 41.9 | 84.5 t/s | 262k | -| Venice: Uncensored (free) | ? | — | — | — | 33k | -| Google: Gemma 3 12B (free) | B | — | 8.8 | 25 t/s | 33k | -| Google: Gemma 3 27B (free) | B | — | 10.3 | 25.9 t/s | 131k | -| Google: Gemma 3 4B (free) | C | — | 6.3 | 26.1 t/s | 33k | -| Google: Gemma 3n 2B (free) | C | — | 4.8 | 60.9 t/s | 8k | +| Model | Tier | SWE | AA Intel | AA Speed | Context | +| ------------------------------------- | ---- | ----- | -------- | --------- | ------- | +| Qwen3 Coder 480B | A+ | — | — | — | 262k | +| Qwen3 80B Instruct | A | — | 20.1 | 174.5 t/s | 262k | +| Llama 3.3 70B | A- | — | — | — | 128k | +| GPT OSS 120B | A+ | — | 33.3 | 221 t/s | 131k | +| Nemotron Nano 30B | B+ | — | — | — | 256k | +| GPT OSS 20B | A | — | 24.5 | 277.7 t/s | 131k | +| GLM 4.5 Air | A | — | 23.2 | 60 t/s | 128k | +| Nemotron Nano 9B | B | — | — | — | 128k | +| NVIDIA: Nemotron 3 Super (free) | S | 60.5% | 36 | 449.5 t/s | 262k | +| inclusionAI: Ling-2.6-1T (free) | A+ | — | 33.6 | 0 t/s | 262k | +| inclusionAI: Ling-2.6-flash (free) | A | — | 26.2 | 206.6 t/s | 262k | +| MiniMax: MiniMax M2.5 (free) | S | — | 41.9 | 92.8 t/s | 197k | +| Poolside: Laguna M.1 (free) | ? | — | — | — | 131k | +| Poolside: Laguna XS.2 (free) | ? | — | — | — | 131k | +| Tencent: Hy3 preview (free) | S | — | 41.9 | 84.5 t/s | 262k | +| Venice: Uncensored (free) | ? | — | — | — | 33k | +| Google: Gemma 3 12B (free) | B | — | 8.8 | 25 t/s | 33k | +| Google: Gemma 3 27B (free) | B | — | 10.3 | 25.9 t/s | 131k | +| Google: Gemma 3 4B (free) | C | — | 6.3 | 26.1 t/s | 33k | +| Google: Gemma 3n 2B (free) | C | — | 4.8 | 60.9 t/s | 8k | | LiquidAI: LFM2.5-1.2B-Instruct (free) | B | — | 8 | 0 t/s | 33k | | LiquidAI: LFM2.5-1.2B-Thinking (free) | B | — | 8.1 | 0 t/s | 33k | -| Meta: Llama 3.2 3B Instruct (free) | B | — | 9.7 | 51.8 t/s | 131k | -| Nous: Hermes 3 405B Instruct (free) | A | — | 18.6 | 34.2 t/s | 131k | +| Meta: Llama 3.2 3B Instruct (free) | B | — | 9.7 | 51.8 t/s | 131k | +| Nous: Hermes 3 405B Instruct (free) | A | — | 18.6 | 34.2 t/s | 131k | diff --git a/package.json b/package.json index 6ff7a9b..1e8407d 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "devDependencies": { "@types/react": "^19.2.14", "@eslint/js": "^10.0.1", - "@types/node": "^25.8.0", + "@types/node": "^26.0.1", "eslint": "^10.4.0", "globals": "^17.6.0", "typescript": "^6.0.3", diff --git a/publish/frouter-cli-shim/bin.js b/publish/frouter-cli-shim/bin.js index eaf6133..69bc800 100644 --- a/publish/frouter-cli-shim/bin.js +++ b/publish/frouter-cli-shim/bin.js @@ -27,13 +27,19 @@ try { process.stderr.write( `\x1b[31m Failed to locate ${NEW_PACKAGE}. Reinstall manually: npm install -g ${NEW_PACKAGE}${RESET}\n`, ); - process.stderr.write(`${DIM} ${err && err.message ? err.message : String(err)}${RESET}\n`); + process.stderr.write( + `${DIM} ${err && err.message ? err.message : String(err)}${RESET}\n`, + ); process.exit(1); } -const child = spawn(process.execPath, [freeRouterBin, ...process.argv.slice(2)], { - stdio: "inherit", -}); +const child = spawn( + process.execPath, + [freeRouterBin, ...process.argv.slice(2)], + { + stdio: "inherit", + }, +); child.on("exit", (code, signal) => { if (signal) { @@ -44,6 +50,8 @@ child.on("exit", (code, signal) => { }); child.on("error", (err) => { - process.stderr.write(`\x1b[31m free-router failed to start: ${err.message}${RESET}\n`); + process.stderr.write( + `\x1b[31m free-router failed to start: ${err.message}${RESET}\n`, + ); process.exit(1); }); diff --git a/publish/legacy-scope-shim/bin.js b/publish/legacy-scope-shim/bin.js index dc5d5e3..75198a6 100644 --- a/publish/legacy-scope-shim/bin.js +++ b/publish/legacy-scope-shim/bin.js @@ -27,13 +27,19 @@ try { process.stderr.write( `\x1b[31m Failed to locate ${NEW_PACKAGE}. Reinstall manually: npm install -g ${NEW_PACKAGE}${RESET}\n`, ); - process.stderr.write(`${DIM} ${err && err.message ? err.message : String(err)}${RESET}\n`); + process.stderr.write( + `${DIM} ${err && err.message ? err.message : String(err)}${RESET}\n`, + ); process.exit(1); } -const child = spawn(process.execPath, [freeRouterBin, ...process.argv.slice(2)], { - stdio: "inherit", -}); +const child = spawn( + process.execPath, + [freeRouterBin, ...process.argv.slice(2)], + { + stdio: "inherit", + }, +); child.on("exit", (code, signal) => { if (signal) { @@ -44,6 +50,8 @@ child.on("exit", (code, signal) => { }); child.on("error", (err) => { - process.stderr.write(`\x1b[31m free-router failed to start: ${err.message}${RESET}\n`); + process.stderr.write( + `\x1b[31m free-router failed to start: ${err.message}${RESET}\n`, + ); process.exit(1); }); diff --git a/scripts/update-models.ts b/scripts/update-models.ts index e153d06..7ffb571 100644 --- a/scripts/update-models.ts +++ b/scripts/update-models.ts @@ -1015,8 +1015,7 @@ async function main() { console.log("Fetching OpenCode-supported models (Models.dev)..."); let support = loadExistingSupportFile(); let supportFetched = false; - let supportSource: "models.dev" | "opencode-github" | "existing" = - "existing"; + let supportSource: "models.dev" | "opencode-github" | "existing" = "existing"; try { const modelsDev = await fetchJson("models.dev", "/api.json"); @@ -1072,10 +1071,7 @@ async function main() { console.log("\n(dry run — pass --apply to write OpenCode support data)"); } else { console.log("\n═══ APPLYING OPENCODE SUPPORT ONLY ═══"); - const supportChanged = applyOpenCodeSupportToRankings( - rankings, - support, - ); + const supportChanged = applyOpenCodeSupportToRankings(rankings, support); if (supportChanged) { writeFileSync(RANKINGS_PATH, JSON.stringify(rankings, null, 2) + "\n"); report.rankings.changed = true; @@ -1157,7 +1153,9 @@ async function main() { orFetchOk = true; report.providers.openrouter.fetched = true; report.providers.openrouter.total = orApiModels.length; - console.log(` Found ${orApiModels.length} OpenRouter free LLM/VLM models\n`); + console.log( + ` Found ${orApiModels.length} OpenRouter free LLM/VLM models\n`, + ); } catch (err: any) { console.error(` Failed to fetch OpenRouter: ${err.message}\n`); } @@ -1220,9 +1218,7 @@ async function main() { report.providers.openrouter.new_rankings = orNew.length; report.providers.openrouter.removed_rankings = orRemoved.length; report.added_models.push(...orNew.map((m) => `openrouter/${m.id}`)); - report.removed_models.push( - ...orRemoved.map((id) => `openrouter/${id}`), - ); + report.removed_models.push(...orRemoved.map((id) => `openrouter/${id}`)); console.log("\n═══ OPENROUTER DIFF ═══"); if (!orFetchOk) { @@ -1462,7 +1458,8 @@ async function main() { aaHit?.aa_benchmark_score ?? nimTwin?.aa_benchmark_score ?? null, aa_benchmark_name: aaHit?.aa_benchmark_name ?? nimTwin?.aa_benchmark_name ?? null, - aa_coding_index: aaHit?.aa_coding_index ?? nimTwin?.aa_coding_index ?? null, + aa_coding_index: + aaHit?.aa_coding_index ?? nimTwin?.aa_coding_index ?? null, aa_intelligence: aaHit?.aa_intelligence ?? nimTwin?.aa_intelligence ?? null, aa_speed_tps: aaHit?.aa_speed_tps ?? nimTwin?.aa_speed_tps ?? null, diff --git a/src/bin/free-router.ts b/src/bin/free-router.ts index 23ee4a1..ad10b9a 100755 --- a/src/bin/free-router.ts +++ b/src/bin/free-router.ts @@ -425,18 +425,23 @@ function blockWidthLines( const c = cols(); const guard = WRAP_GUARD_COLS; const maxW = Math.max(0, c - guard); - if (maxW <= 4) return [fullWidthLine(""), fullWidthLine(""), fullWidthLine("")]; + if (maxW <= 4) + return [fullWidthLine(""), fullWidthLine(""), fullWidthLine("")]; const innerW = maxW - 2; const rightPart = right ? truncAnsi(right, innerW) : ""; - const leftMaxW = Math.max(0, innerW - visLen(rightPart) - (rightPart ? 1 : 0)); + const leftMaxW = Math.max( + 0, + innerW - visLen(rightPart) - (rightPart ? 1 : 0), + ); const leftPart = truncAnsi(left, leftMaxW); const gapW = Math.max(0, innerW - visLen(leftPart) - visLen(rightPart)); const top = `${borderStyle}╭${"─".repeat(Math.max(0, innerW))}╮${R}`; const middle = `${borderStyle}│${R}${leftPart}${" ".repeat(gapW)}${rightPart}${borderStyle}│${R}`; const bottom = `${borderStyle}╰${"─".repeat(Math.max(0, innerW))}╯${R}`; - return [top, middle, bottom].map((line) => - `${line}${" ".repeat(Math.max(0, maxW - visLen(line)))}${R}${CLEAR_TO_EOL}`, + return [top, middle, bottom].map( + (line) => + `${line}${" ".repeat(Math.max(0, maxW - visLen(line)))}${R}${CLEAR_TO_EOL}`, ); } @@ -461,7 +466,9 @@ function tableLine(cells: string[], separatorStyle = ""): string { function tableHeaderLine(): string { const cells = TABLE_COLUMNS.map((col) => tableCell( - col.sortCol ? colHdr(col.label, col.sortCol, col.width, col.right) : col.label, + col.sortCol + ? colHdr(col.label, col.sortCol, col.width, col.right) + : col.label, col.width, `${BG_TABLE_HDR}${WHITE}${B}`, col.right, @@ -471,7 +478,9 @@ function tableHeaderLine(): string { } function tableSeparatorLine(): string { - return fullWidthLine(`${D}${"─".repeat(Math.max(0, cols() - WRAP_GUARD_COLS))}${R}`); + return fullWidthLine( + `${D}${"─".repeat(Math.max(0, cols() - WRAP_GUARD_COLS))}${R}`, + ); } function tableRowStyle(selected: boolean): string { @@ -491,8 +500,10 @@ function tableRowLine( function formatVerdict(verdict: string, selected: boolean): string { const rowStyle = tableRowStyle(selected); - if (verdict.startsWith("✓ ")) return `${GREEN}✓${R}${rowStyle}${verdict.slice(1)}`; - if (verdict.startsWith("x ")) return `${RED}x${R}${rowStyle}${verdict.slice(1)}`; + if (verdict.startsWith("✓ ")) + return `${GREEN}✓${R}${rowStyle}${verdict.slice(1)}`; + if (verdict.startsWith("x ")) + return `${RED}x${R}${rowStyle}${verdict.slice(1)}`; return verdict; } @@ -676,7 +687,10 @@ function modeTagLine(label: string): string { return fullWidthLine(`${BG_OFF}${WHITE} ${label} ${R}`); } -function modalFooterLine(items: Array<[string, string]>, lastLine = true): string { +function modalFooterLine( + items: Array<[string, string]>, + lastLine = true, +): string { return fullWidthLine( ` ${items.map(([key, label]) => footerKey(key, label)).join(" ")} `, lastLine, @@ -739,8 +753,9 @@ function renderMain() { for (let i = 0; i < rowsAvailable; i++) { if (filtered.length === 0) { out += - (i === 0 ? centeredWidthLine(`${D}not found${R}`) : fullWidthLine("")) + - "\n"; + (i === 0 + ? centeredWidthLine(`${D}not found${R}`) + : fullWidthLine("")) + "\n"; continue; } @@ -969,8 +984,10 @@ function clampCursor(next: number) { function resolveUserScrollSortPauseMs(cfg: FrouterConfig): number { // Env overrides config so users can tune behavior per terminal/session. const raw = - readEnv("FREE_ROUTER_SCROLL_SORT_PAUSE_MS", "FROUTER_SCROLL_SORT_PAUSE_MS") ?? - cfg?.ui?.scrollSortPauseMs; + readEnv( + "FREE_ROUTER_SCROLL_SORT_PAUSE_MS", + "FROUTER_SCROLL_SORT_PAUSE_MS", + ) ?? cfg?.ui?.scrollSortPauseMs; if (raw == null || raw === "") return DEFAULT_USER_SCROLL_SORT_PAUSE_MS; const parsed = Number(raw); if (!Number.isFinite(parsed) || parsed < 0) { @@ -1111,9 +1128,7 @@ async function applySelectionToTarget(targetId: TargetId, launch: boolean) { return; } if (notice) w(`\n${notice}\n`); - w( - `${GREEN} ✓ Wrote ${targetLabel(targetId)} config: ${writtenPath}${R}\n`, - ); + w(`${GREEN} ✓ Wrote ${targetLabel(targetId)} config: ${writtenPath}${R}\n`); // Guard: missing API key → offer to add it if (shouldLaunch && !targetApiKey) { @@ -1131,9 +1146,7 @@ async function applySelectionToTarget(targetId: TargetId, launch: boolean) { openApiKeyEditorFromMain(targetPk); return; } - w( - `${YELLOW} Launch cancelled. Set ${envVar} with A, then retry.${R}\n`, - ); + w(`${YELLOW} Launch cancelled. Set ${envVar} with A, then retry.${R}\n`); shouldLaunch = false; } @@ -1166,7 +1179,9 @@ async function applySelectionToTarget(targetId: TargetId, launch: boolean) { } function targetLabel(targetId: TargetId) { - return CONFIG_TARGETS.find((target) => target.id === targetId)?.label ?? targetId; + return ( + CONFIG_TARGETS.find((target) => target.id === targetId)?.label ?? targetId + ); } function resolveTargetApplySelection(selectedModel: Model, targetId: TargetId) { @@ -1974,7 +1989,8 @@ async function runUpdateApp( } async function checkForUpdate(): Promise { - if (readEnv(UPDATE_SKIP_ONCE_ENV, LEGACY_UPDATE_SKIP_ONCE_ENV) === "1") return; + if (readEnv(UPDATE_SKIP_ONCE_ENV, LEGACY_UPDATE_SKIP_ONCE_ENV) === "1") + return; const latest = await fetchLatestVersion(); if (!latest || !isStrictlyNewerVersion(PKG_VERSION, latest)) return; diff --git a/src/lib/config.ts b/src/lib/config.ts index bafe1fd..1b7d84f 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -111,7 +111,8 @@ export function loadConfig(): FrouterConfig { }, }; if (existsSync(CONFIG_PATH)) return readConfigFile(CONFIG_PATH, defaults); - if (existsSync(LEGACY_CONFIG_PATH)) return migrateLegacyConfigIfNeeded(defaults); + if (existsSync(LEGACY_CONFIG_PATH)) + return migrateLegacyConfigIfNeeded(defaults); return defaults; } diff --git a/src/lib/models.ts b/src/lib/models.ts index 94839b7..63353d7 100644 --- a/src/lib/models.ts +++ b/src/lib/models.ts @@ -905,7 +905,9 @@ async function fetchNimModels(apiKey: string | null): Promise { ); if (!data) return null; - const allowedIds = new Set(getRankingsBySource("nim").map((entry) => entry.model_id)); + const allowedIds = new Set( + getRankingsBySource("nim").map((entry) => entry.model_id), + ); const result = data .filter((m) => { @@ -941,7 +943,8 @@ async function fetchOpenRouterModels(apiKey: string | null): Promise { ); return data .filter((m) => { - if (m?.pricing?.prompt !== "0" || m?.pricing?.completion !== "0") return false; + if (m?.pricing?.prompt !== "0" || m?.pricing?.completion !== "0") + return false; const output = Array.isArray(m?.architecture?.output_modalities) ? m.architecture.output_modalities : []; @@ -950,10 +953,13 @@ async function fetchOpenRouterModels(apiKey: string | null): Promise { const input = Array.isArray(m?.architecture?.input_modalities) ? m.architecture.input_modalities : ["text"]; - if (input.some((value: string) => value !== "text" && value !== "image")) { + if ( + input.some((value: string) => value !== "text" && value !== "image") + ) { return false; } - const haystack = `${m?.id || ""} ${m?.name || ""} ${m?.description || ""}`.toLowerCase(); + const haystack = + `${m?.id || ""} ${m?.name || ""} ${m?.description || ""}`.toLowerCase(); if (haystack.includes("openrouter/free")) return false; return !/ocr|video|audio|speech|voice|speaker|detector|detection|translate|translation|embed|rerank|guard|safety|retriever/i.test( haystack, diff --git a/src/lib/targets.ts b/src/lib/targets.ts index ea6a200..6d2c1b7 100644 --- a/src/lib/targets.ts +++ b/src/lib/targets.ts @@ -174,7 +174,9 @@ function envLineValue(value: string) { function setEnvFileValue(path: string, key: string, value: string) { const dir = dirname(path); if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); - const lines = existsSync(path) ? readFileSync(path, "utf8").split(/\r?\n/) : []; + const lines = existsSync(path) + ? readFileSync(path, "utf8").split(/\r?\n/) + : []; const nextLine = `${key}=${envLineValue(value)}`; let replaced = false; const nextLines = lines.map((line) => { diff --git a/src/lib/utils.ts b/src/lib/utils.ts index bb5bea8..54ff95d 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -13,7 +13,9 @@ export const ORANGE = "\x1b[38;5;208m"; export const BG_SEL = "\x1b[48;5;235m"; // subtle selection highlight export function readEnv(name: string, legacyName?: string): string | undefined { - return process.env[name] ?? (legacyName ? process.env[legacyName] : undefined); + return ( + process.env[name] ?? (legacyName ? process.env[legacyName] : undefined) + ); } // ─── Shared types ─────────────────────────────────────────────────────────────