free-coding-models 0.1.63 → 0.1.64
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 +82 -18
- package/bin/free-coding-models.js +320 -103
- package/lib/config.js +16 -1
- package/package.json +1 -1
- package/sources.js +57 -4
package/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
<img src="https://img.shields.io/npm/v/free-coding-models?color=76b900&label=npm&logo=npm" alt="npm version">
|
|
3
3
|
<img src="https://img.shields.io/node/v/free-coding-models?color=76b900&logo=node.js" alt="node version">
|
|
4
4
|
<img src="https://img.shields.io/npm/l/free-coding-models?color=76b900" alt="license">
|
|
5
|
-
<img src="https://img.shields.io/badge/models-
|
|
6
|
-
<img src="https://img.shields.io/badge/providers-
|
|
5
|
+
<img src="https://img.shields.io/badge/models-111-76b900?logo=nvidia" alt="models count">
|
|
6
|
+
<img src="https://img.shields.io/badge/providers-13-blue" alt="providers count">
|
|
7
7
|
</p>
|
|
8
8
|
|
|
9
9
|
<h1 align="center">free-coding-models</h1>
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
<p align="center">
|
|
16
16
|
|
|
17
17
|
```
|
|
18
|
-
1. Create a free API key (NVIDIA,
|
|
18
|
+
1. Create a free API key (NVIDIA, OpenRouter, Hugging Face, etc.)
|
|
19
19
|
2. npm i -g free-coding-models
|
|
20
20
|
3. free-coding-models
|
|
21
21
|
```
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
<p align="center">
|
|
26
26
|
<strong>Find the fastest coding LLM models in seconds</strong><br>
|
|
27
|
-
<sub>Ping free models from
|
|
27
|
+
<sub>Ping free coding models from 13 providers in real-time — pick the best one for OpenCode, OpenClaw, or any AI coding assistant</sub>
|
|
28
28
|
</p>
|
|
29
29
|
|
|
30
30
|
<p align="center">
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
## ✨ Features
|
|
48
48
|
|
|
49
49
|
- **🎯 Coding-focused** — Only LLM models optimized for code generation, not chat or vision
|
|
50
|
-
- **🌐 Multi-provider** —
|
|
50
|
+
- **🌐 Multi-provider** — 111 models from NVIDIA NIM, Groq, Cerebras, SambaNova, OpenRouter, Hugging Face Inference, Replicate, DeepInfra, Fireworks AI, Codestral, Hyperbolic, Scaleway, and Google AI — all free to use
|
|
51
51
|
- **⚙️ Settings screen** — Press `P` to manage provider API keys, enable/disable providers, and test keys live
|
|
52
52
|
- **🚀 Parallel pings** — All models tested simultaneously via native `fetch`
|
|
53
53
|
- **📊 Real-time animation** — Watch latency appear live in alternate screen buffer
|
|
@@ -77,8 +77,12 @@ Before using `free-coding-models`, make sure you have:
|
|
|
77
77
|
- **NVIDIA NIM** — [build.nvidia.com](https://build.nvidia.com) → Profile → API Keys → Generate
|
|
78
78
|
- **Groq** — [console.groq.com/keys](https://console.groq.com/keys) → Create API Key
|
|
79
79
|
- **Cerebras** — [cloud.cerebras.ai](https://cloud.cerebras.ai) → API Keys → Create
|
|
80
|
-
- **SambaNova** — [
|
|
81
|
-
- **OpenRouter** — [openrouter.ai/
|
|
80
|
+
- **SambaNova** — [sambanova.ai/developers](https://sambanova.ai/developers) → Developers portal → API key (dev tier generous)
|
|
81
|
+
- **OpenRouter** — [openrouter.ai/keys](https://openrouter.ai/keys) → Create key (50 req/day, 20/min on `:free`)
|
|
82
|
+
- **Hugging Face Inference** — [huggingface.co/settings/tokens](https://huggingface.co/settings/tokens) → Access Tokens (free monthly credits)
|
|
83
|
+
- **Replicate** — [replicate.com/account/api-tokens](https://replicate.com/account/api-tokens) → Create token (dev quota)
|
|
84
|
+
- **DeepInfra** — [deepinfra.com/login](https://deepinfra.com/login) → Login → API key (free dev tier)
|
|
85
|
+
- **Fireworks AI** — [fireworks.ai](https://fireworks.ai) → Settings → Access Tokens ($1 free credits)
|
|
82
86
|
- **Mistral Codestral** — [codestral.mistral.ai](https://codestral.mistral.ai) → API Keys (30 req/min, 2000/day — phone required)
|
|
83
87
|
- **Hyperbolic** — [app.hyperbolic.ai/settings](https://app.hyperbolic.ai/settings) → API Keys ($1 free trial)
|
|
84
88
|
- **Scaleway** — [console.scaleway.com/iam/api-keys](https://console.scaleway.com/iam/api-keys) → IAM → API Keys (1M free tokens)
|
|
@@ -86,7 +90,7 @@ Before using `free-coding-models`, make sure you have:
|
|
|
86
90
|
3. **OpenCode** *(optional)* — [Install OpenCode](https://github.com/opencode-ai/opencode) to use the OpenCode integration
|
|
87
91
|
4. **OpenClaw** *(optional)* — [Install OpenClaw](https://openclaw.ai) to use the OpenClaw integration
|
|
88
92
|
|
|
89
|
-
> 💡 **Tip:** You don't need all
|
|
93
|
+
> 💡 **Tip:** You don't need all thirteen providers. One key is enough to get started. Add more later via the Settings screen (`P` key). Models without a key still show real latency (`🔑 NO KEY`) so you can evaluate providers before signing up.
|
|
90
94
|
|
|
91
95
|
---
|
|
92
96
|
|
|
@@ -167,13 +171,13 @@ When you run `free-coding-models` without `--opencode` or `--openclaw`, you get
|
|
|
167
171
|
Use `↑↓` arrows to select, `Enter` to confirm. Then the TUI launches with your chosen mode shown in the header badge.
|
|
168
172
|
|
|
169
173
|
**How it works:**
|
|
170
|
-
1. **Ping phase** — All enabled models are pinged in parallel (up to
|
|
174
|
+
1. **Ping phase** — All enabled models are pinged in parallel (up to 111 across 13 providers)
|
|
171
175
|
2. **Continuous monitoring** — Models are re-pinged every 2 seconds forever
|
|
172
176
|
3. **Real-time updates** — Watch "Latest", "Avg", and "Up%" columns update live
|
|
173
177
|
4. **Select anytime** — Use ↑↓ arrows to navigate, press Enter on a model to act
|
|
174
178
|
5. **Smart detection** — Automatically detects if NVIDIA NIM is configured in OpenCode or OpenClaw
|
|
175
179
|
|
|
176
|
-
Setup wizard (first run — walks through all
|
|
180
|
+
Setup wizard (first run — walks through all 13 providers):
|
|
177
181
|
|
|
178
182
|
```
|
|
179
183
|
🔑 First-time setup — API keys
|
|
@@ -203,7 +207,7 @@ Setup wizard (first run — walks through all 9 providers):
|
|
|
203
207
|
You can add or change keys anytime with the P key in the TUI.
|
|
204
208
|
```
|
|
205
209
|
|
|
206
|
-
You don't need all
|
|
210
|
+
You don't need all thirteen — skip any provider by pressing Enter. At least one key is required.
|
|
207
211
|
|
|
208
212
|
### Adding or changing keys later
|
|
209
213
|
|
|
@@ -214,9 +218,14 @@ Press **`P`** to open the Settings screen at any time:
|
|
|
214
218
|
|
|
215
219
|
Providers
|
|
216
220
|
|
|
217
|
-
❯ [ ✅ ] NIM
|
|
218
|
-
[ ✅ ]
|
|
219
|
-
[ ✅ ]
|
|
221
|
+
❯ [ ✅ ] NVIDIA NIM nvapi-••••••••••••3f9a [Test ✅] Free tier (provider quota by model)
|
|
222
|
+
[ ✅ ] OpenRouter (no key set) [Test —] 50 req/day, 20/min (:free shared quota)
|
|
223
|
+
[ ✅ ] Hugging Face Inference (no key set) [Test —] Free monthly credits (~$0.10)
|
|
224
|
+
|
|
225
|
+
Setup Instructions — NVIDIA NIM
|
|
226
|
+
1) Create a NVIDIA NIM account: https://build.nvidia.com
|
|
227
|
+
2) Profile → API Keys → Generate
|
|
228
|
+
3) Press T to test your key
|
|
220
229
|
|
|
221
230
|
↑↓ Navigate • Enter Edit key • Space Toggle enabled • T Test key • Esc Close
|
|
222
231
|
```
|
|
@@ -239,6 +248,11 @@ Env vars always take priority over the config file:
|
|
|
239
248
|
NVIDIA_API_KEY=nvapi-xxx free-coding-models
|
|
240
249
|
GROQ_API_KEY=gsk_xxx free-coding-models
|
|
241
250
|
CEREBRAS_API_KEY=csk_xxx free-coding-models
|
|
251
|
+
OPENROUTER_API_KEY=sk-or-xxx free-coding-models
|
|
252
|
+
HUGGINGFACE_API_KEY=hf_xxx free-coding-models
|
|
253
|
+
REPLICATE_API_TOKEN=r8_xxx free-coding-models
|
|
254
|
+
DEEPINFRA_API_KEY=di_xxx free-coding-models
|
|
255
|
+
FIREWORKS_API_KEY=fw_xxx free-coding-models
|
|
242
256
|
FREE_CODING_MODELS_TELEMETRY=0 free-coding-models
|
|
243
257
|
```
|
|
244
258
|
|
|
@@ -268,13 +282,33 @@ When enabled, telemetry events include: event name, app version, selected mode,
|
|
|
268
282
|
1. Sign up at [cloud.cerebras.ai](https://cloud.cerebras.ai)
|
|
269
283
|
2. Go to API Keys → Create
|
|
270
284
|
|
|
271
|
-
|
|
285
|
+
**OpenRouter** (`:free` models):
|
|
286
|
+
1. Sign up at [openrouter.ai/keys](https://openrouter.ai/keys)
|
|
287
|
+
2. Create API key (`sk-or-...`)
|
|
288
|
+
|
|
289
|
+
**Hugging Face Inference**:
|
|
290
|
+
1. Sign up at [huggingface.co/settings/tokens](https://huggingface.co/settings/tokens)
|
|
291
|
+
2. Create Access Token (`hf_...`)
|
|
292
|
+
|
|
293
|
+
**Replicate**:
|
|
294
|
+
1. Sign up at [replicate.com/account/api-tokens](https://replicate.com/account/api-tokens)
|
|
295
|
+
2. Create API token (`r8_...`)
|
|
296
|
+
|
|
297
|
+
**DeepInfra**:
|
|
298
|
+
1. Sign up at [deepinfra.com/login](https://deepinfra.com/login)
|
|
299
|
+
2. Create API key from your account dashboard
|
|
300
|
+
|
|
301
|
+
**Fireworks AI**:
|
|
302
|
+
1. Sign up at [fireworks.ai](https://fireworks.ai)
|
|
303
|
+
2. Open Settings → Access Tokens and create a token
|
|
304
|
+
|
|
305
|
+
> 💡 **Free tiers** — each provider exposes a dev/free tier with its own quotas.
|
|
272
306
|
|
|
273
307
|
---
|
|
274
308
|
|
|
275
309
|
## 🤖 Coding Models
|
|
276
310
|
|
|
277
|
-
**
|
|
311
|
+
**111 coding models** across 13 providers and 8 tiers, ranked by [SWE-bench Verified](https://www.swebench.com) — the industry-standard benchmark measuring real GitHub issue resolution. Scores are self-reported by providers unless noted.
|
|
278
312
|
|
|
279
313
|
### NVIDIA NIM (44 models)
|
|
280
314
|
|
|
@@ -347,6 +381,19 @@ Current tier filter is shown in the header badge (e.g., `[Tier S]`)
|
|
|
347
381
|
- Sets your selected model as default in `~/.config/opencode/opencode.json`
|
|
348
382
|
- Launches OpenCode with the model ready to use
|
|
349
383
|
|
|
384
|
+
### tmux sub-agent panes
|
|
385
|
+
|
|
386
|
+
When launched from an existing `tmux` session, `free-coding-models` now auto-adds an OpenCode `--port` argument so OpenCode/oh-my-opencode can spawn sub-agents in panes.
|
|
387
|
+
|
|
388
|
+
- Priority 1: reuse `OPENCODE_PORT` if it is valid and free
|
|
389
|
+
- Priority 2: auto-pick the first free port in `4096-5095`
|
|
390
|
+
|
|
391
|
+
You can force a specific port:
|
|
392
|
+
|
|
393
|
+
```bash
|
|
394
|
+
OPENCODE_PORT=4098 free-coding-models --opencode
|
|
395
|
+
```
|
|
396
|
+
|
|
350
397
|
### Manual OpenCode Setup (Optional)
|
|
351
398
|
|
|
352
399
|
Create or edit `~/.config/opencode/opencode.json`:
|
|
@@ -522,6 +569,15 @@ This script:
|
|
|
522
569
|
| `NVIDIA_API_KEY` | NVIDIA NIM key |
|
|
523
570
|
| `GROQ_API_KEY` | Groq key |
|
|
524
571
|
| `CEREBRAS_API_KEY` | Cerebras key |
|
|
572
|
+
| `SAMBANOVA_API_KEY` | SambaNova key |
|
|
573
|
+
| `OPENROUTER_API_KEY` | OpenRouter key |
|
|
574
|
+
| `HUGGINGFACE_API_KEY` / `HF_TOKEN` | Hugging Face token |
|
|
575
|
+
| `REPLICATE_API_TOKEN` | Replicate token |
|
|
576
|
+
| `DEEPINFRA_API_KEY` / `DEEPINFRA_TOKEN` | DeepInfra key |
|
|
577
|
+
| `CODESTRAL_API_KEY` | Mistral Codestral key |
|
|
578
|
+
| `HYPERBOLIC_API_KEY` | Hyperbolic key |
|
|
579
|
+
| `SCALEWAY_API_KEY` | Scaleway key |
|
|
580
|
+
| `GOOGLE_API_KEY` | Google AI Studio key |
|
|
525
581
|
| `FREE_CODING_MODELS_TELEMETRY` | `0` disables analytics, `1` enables analytics |
|
|
526
582
|
| `FREE_CODING_MODELS_POSTHOG_KEY` | PostHog project API key used for anonymous event capture |
|
|
527
583
|
| `FREE_CODING_MODELS_POSTHOG_HOST` | Optional PostHog ingest host (`https://eu.i.posthog.com` default) |
|
|
@@ -533,12 +589,20 @@ This script:
|
|
|
533
589
|
"apiKeys": {
|
|
534
590
|
"nvidia": "nvapi-xxx",
|
|
535
591
|
"groq": "gsk_xxx",
|
|
536
|
-
"cerebras": "csk_xxx"
|
|
592
|
+
"cerebras": "csk_xxx",
|
|
593
|
+
"openrouter": "sk-or-xxx",
|
|
594
|
+
"huggingface": "hf_xxx",
|
|
595
|
+
"replicate": "r8_xxx",
|
|
596
|
+
"deepinfra": "di_xxx"
|
|
537
597
|
},
|
|
538
598
|
"providers": {
|
|
539
599
|
"nvidia": { "enabled": true },
|
|
540
600
|
"groq": { "enabled": true },
|
|
541
|
-
"cerebras": { "enabled": true }
|
|
601
|
+
"cerebras": { "enabled": true },
|
|
602
|
+
"openrouter": { "enabled": true },
|
|
603
|
+
"huggingface": { "enabled": true },
|
|
604
|
+
"replicate": { "enabled": true },
|
|
605
|
+
"deepinfra": { "enabled": true }
|
|
542
606
|
},
|
|
543
607
|
"telemetry": {
|
|
544
608
|
"enabled": true,
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* During benchmarking, users can navigate with arrow keys and press Enter to act on the selected model.
|
|
11
11
|
*
|
|
12
12
|
* 🎯 Key features:
|
|
13
|
-
* - Parallel pings across all models with animated real-time updates (
|
|
13
|
+
* - Parallel pings across all models with animated real-time updates (multi-provider)
|
|
14
14
|
* - Continuous monitoring with 2-second ping intervals (never stops)
|
|
15
15
|
* - Rolling averages calculated from ALL successful pings since start
|
|
16
16
|
* - Best-per-tier highlighting with medals (🥇🥈🥉)
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* - Startup mode menu (OpenCode CLI vs OpenCode Desktop vs OpenClaw) when no flag is given
|
|
20
20
|
* - Automatic config detection and model setup for both tools
|
|
21
21
|
* - JSON config stored in ~/.free-coding-models.json (auto-migrates from old plain-text)
|
|
22
|
-
* - Multi-provider support via sources.js (NIM
|
|
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 per provider, enable/disable, test keys
|
|
24
24
|
* - Uptime percentage tracking (successful pings / total pings)
|
|
25
25
|
* - Sortable columns (R/Y/O/M/L/A/S/N/H/V/U keys)
|
|
@@ -32,15 +32,16 @@
|
|
|
32
32
|
* - `getTelemetryTerminal`: Infer terminal family (Terminal.app, iTerm2, kitty, etc.)
|
|
33
33
|
* - `isTelemetryDebugEnabled` / `telemetryDebug`: Optional runtime telemetry diagnostics via env
|
|
34
34
|
* - `sendUsageTelemetry`: Fire-and-forget anonymous app-start event
|
|
35
|
-
* - `promptApiKey`: Interactive wizard for first-time
|
|
35
|
+
* - `promptApiKey`: Interactive wizard for first-time multi-provider API key setup
|
|
36
36
|
* - `promptModeSelection`: Startup menu to choose OpenCode vs OpenClaw
|
|
37
|
-
* - `ping`:
|
|
37
|
+
* - `buildPingRequest` / `ping`: Build provider-specific probe requests and measure latency
|
|
38
38
|
* - `renderTable`: Generate ASCII table with colored latency indicators and status emojis
|
|
39
39
|
* - `getAvg`: Calculate average latency from all successful pings
|
|
40
40
|
* - `getVerdict`: Determine verdict string based on average latency (Overloaded for 429)
|
|
41
41
|
* - `getUptime`: Calculate uptime percentage from ping history
|
|
42
42
|
* - `sortResults`: Sort models by various columns
|
|
43
43
|
* - `checkNvidiaNimConfig`: Check if NVIDIA NIM provider is configured in OpenCode
|
|
44
|
+
* - `isTcpPortAvailable` / `resolveOpenCodeTmuxPort`: Pick a safe OpenCode port when running in tmux
|
|
44
45
|
* - `startOpenCode`: Launch OpenCode CLI with selected model (configures if needed)
|
|
45
46
|
* - `startOpenCodeDesktop`: Set model in shared config & open OpenCode Desktop app
|
|
46
47
|
* - `loadOpenClawConfig` / `saveOpenClawConfig`: Manage ~/.openclaw/openclaw.json
|
|
@@ -57,8 +58,8 @@
|
|
|
57
58
|
* ⚙️ Configuration:
|
|
58
59
|
* - API keys stored per-provider in ~/.free-coding-models.json (0600 perms)
|
|
59
60
|
* - Old ~/.free-coding-models plain-text auto-migrated as nvidia key on first run
|
|
60
|
-
* - Env vars override config: NVIDIA_API_KEY, GROQ_API_KEY, CEREBRAS_API_KEY
|
|
61
|
-
* - Models loaded from sources.js —
|
|
61
|
+
* - Env vars override config: NVIDIA_API_KEY, GROQ_API_KEY, CEREBRAS_API_KEY, OPENROUTER_API_KEY, HUGGINGFACE_API_KEY/HF_TOKEN, REPLICATE_API_TOKEN, DEEPINFRA_API_KEY/DEEPINFRA_TOKEN, FIREWORKS_API_KEY, etc.
|
|
62
|
+
* - Models loaded from sources.js — all provider/model definitions are centralized there
|
|
62
63
|
* - OpenCode config: ~/.config/opencode/opencode.json
|
|
63
64
|
* - OpenClaw config: ~/.openclaw/openclaw.json
|
|
64
65
|
* - Ping timeout: 15s per attempt
|
|
@@ -86,6 +87,7 @@ import { readFileSync, writeFileSync, existsSync, copyFileSync, mkdirSync } from
|
|
|
86
87
|
import { randomUUID } from 'crypto'
|
|
87
88
|
import { homedir } from 'os'
|
|
88
89
|
import { join, dirname } from 'path'
|
|
90
|
+
import { createServer } from 'net'
|
|
89
91
|
import { MODELS, sources } from '../sources.js'
|
|
90
92
|
import { patchOpenClawModelsJson } from '../patch-openclaw-models.js'
|
|
91
93
|
import { getAvg, getVerdict, getUptime, sortResults, filterByTier, findBestModel, parseArgs, TIER_ORDER, VERDICT_ORDER, TIER_LETTER_MAP } from '../lib/utils.js'
|
|
@@ -486,7 +488,7 @@ function runUpdate(latestVersion) {
|
|
|
486
488
|
|
|
487
489
|
// ─── First-run wizard ─────────────────────────────────────────────────────────
|
|
488
490
|
// 📖 Shown when NO provider has a key configured yet.
|
|
489
|
-
// 📖 Steps through all
|
|
491
|
+
// 📖 Steps through all configured providers sequentially — each is optional (Enter to skip).
|
|
490
492
|
// 📖 At least one key must be entered to proceed. Keys saved to ~/.free-coding-models.json.
|
|
491
493
|
// 📖 Returns the nvidia key (or null) for backward-compat with the rest of main().
|
|
492
494
|
async function promptApiKey(config) {
|
|
@@ -495,81 +497,17 @@ async function promptApiKey(config) {
|
|
|
495
497
|
console.log(chalk.dim(' Enter keys for any provider you want to use. Press Enter to skip one.'))
|
|
496
498
|
console.log()
|
|
497
499
|
|
|
498
|
-
// 📖
|
|
499
|
-
const providers =
|
|
500
|
-
{
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
key: 'groq',
|
|
510
|
-
label: 'Groq',
|
|
511
|
-
color: chalk.rgb(249, 103, 20),
|
|
512
|
-
url: 'https://console.groq.com/keys',
|
|
513
|
-
hint: 'API Keys → Create API Key',
|
|
514
|
-
prefix: 'gsk_',
|
|
515
|
-
},
|
|
516
|
-
{
|
|
517
|
-
key: 'cerebras',
|
|
518
|
-
label: 'Cerebras',
|
|
519
|
-
color: chalk.rgb(0, 180, 255),
|
|
520
|
-
url: 'https://cloud.cerebras.ai',
|
|
521
|
-
hint: 'API Keys → Create',
|
|
522
|
-
prefix: 'csk_ / cauth_',
|
|
523
|
-
},
|
|
524
|
-
{
|
|
525
|
-
key: 'sambanova',
|
|
526
|
-
label: 'SambaNova',
|
|
527
|
-
color: chalk.rgb(255, 165, 0),
|
|
528
|
-
url: 'https://cloud.sambanova.ai/apis',
|
|
529
|
-
hint: 'API Keys → Create ($5 free trial, 3 months)',
|
|
530
|
-
prefix: 'sn-',
|
|
531
|
-
},
|
|
532
|
-
{
|
|
533
|
-
key: 'openrouter',
|
|
534
|
-
label: 'OpenRouter',
|
|
535
|
-
color: chalk.rgb(120, 80, 255),
|
|
536
|
-
url: 'https://openrouter.ai/settings/keys',
|
|
537
|
-
hint: 'API Keys → Create key (50 free req/day, shared quota)',
|
|
538
|
-
prefix: 'sk-or-',
|
|
539
|
-
},
|
|
540
|
-
{
|
|
541
|
-
key: 'codestral',
|
|
542
|
-
label: 'Mistral Codestral',
|
|
543
|
-
color: chalk.rgb(255, 100, 100),
|
|
544
|
-
url: 'https://codestral.mistral.ai',
|
|
545
|
-
hint: 'API Keys → Create key (30 req/min, 2000/day — phone required)',
|
|
546
|
-
prefix: 'csk-',
|
|
547
|
-
},
|
|
548
|
-
{
|
|
549
|
-
key: 'hyperbolic',
|
|
550
|
-
label: 'Hyperbolic',
|
|
551
|
-
color: chalk.rgb(0, 200, 150),
|
|
552
|
-
url: 'https://app.hyperbolic.ai/settings',
|
|
553
|
-
hint: 'Settings → API Keys ($1 free trial)',
|
|
554
|
-
prefix: 'eyJ',
|
|
555
|
-
},
|
|
556
|
-
{
|
|
557
|
-
key: 'scaleway',
|
|
558
|
-
label: 'Scaleway',
|
|
559
|
-
color: chalk.rgb(130, 0, 250),
|
|
560
|
-
url: 'https://console.scaleway.com/iam/api-keys',
|
|
561
|
-
hint: 'IAM → API Keys (1M free tokens)',
|
|
562
|
-
prefix: 'scw-',
|
|
563
|
-
},
|
|
564
|
-
{
|
|
565
|
-
key: 'googleai',
|
|
566
|
-
label: 'Google AI Studio',
|
|
567
|
-
color: chalk.rgb(66, 133, 244),
|
|
568
|
-
url: 'https://aistudio.google.com/apikey',
|
|
569
|
-
hint: 'Get API key (free Gemma models, 14.4K req/day)',
|
|
570
|
-
prefix: 'AIza',
|
|
571
|
-
},
|
|
572
|
-
]
|
|
500
|
+
// 📖 Build providers from sources to keep setup in sync with actual supported providers.
|
|
501
|
+
const providers = Object.keys(sources).map((key) => {
|
|
502
|
+
const meta = PROVIDER_METADATA[key] || {}
|
|
503
|
+
return {
|
|
504
|
+
key,
|
|
505
|
+
label: meta.label || sources[key]?.name || key,
|
|
506
|
+
color: meta.color || chalk.white,
|
|
507
|
+
url: meta.signupUrl || 'https://example.com',
|
|
508
|
+
hint: meta.signupHint || 'Create API key',
|
|
509
|
+
}
|
|
510
|
+
})
|
|
573
511
|
|
|
574
512
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
575
513
|
|
|
@@ -1121,23 +1059,50 @@ function renderTable(results, pendingPings, frame, cursor = null, sortColumn = '
|
|
|
1121
1059
|
// ─── HTTP ping ────────────────────────────────────────────────────────────────
|
|
1122
1060
|
|
|
1123
1061
|
// 📖 ping: Send a single chat completion request to measure model availability and latency.
|
|
1124
|
-
// 📖
|
|
1062
|
+
// 📖 providerKey and url determine provider-specific request format.
|
|
1125
1063
|
// 📖 apiKey can be null — in that case no Authorization header is sent.
|
|
1126
1064
|
// 📖 A 401 response still tells us the server is UP and gives us real latency.
|
|
1127
|
-
|
|
1065
|
+
function buildPingRequest(apiKey, modelId, providerKey, url) {
|
|
1066
|
+
if (providerKey === 'replicate') {
|
|
1067
|
+
// 📖 Replicate uses /v1/predictions with a different payload than OpenAI chat-completions.
|
|
1068
|
+
const replicateHeaders = { 'Content-Type': 'application/json', Prefer: 'wait=4' }
|
|
1069
|
+
if (apiKey) replicateHeaders.Authorization = `Token ${apiKey}`
|
|
1070
|
+
return {
|
|
1071
|
+
url,
|
|
1072
|
+
headers: replicateHeaders,
|
|
1073
|
+
body: { version: modelId, input: { prompt: 'hi' } },
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
const headers = { 'Content-Type': 'application/json' }
|
|
1078
|
+
if (apiKey) headers.Authorization = `Bearer ${apiKey}`
|
|
1079
|
+
if (providerKey === 'openrouter') {
|
|
1080
|
+
// 📖 OpenRouter recommends optional app identification headers.
|
|
1081
|
+
headers['HTTP-Referer'] = 'https://github.com/vava-nessa/free-coding-models'
|
|
1082
|
+
headers['X-Title'] = 'free-coding-models'
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
return {
|
|
1086
|
+
url,
|
|
1087
|
+
headers,
|
|
1088
|
+
body: { model: modelId, messages: [{ role: 'user', content: 'hi' }], max_tokens: 1 },
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
async function ping(apiKey, modelId, providerKey, url) {
|
|
1128
1093
|
const ctrl = new AbortController()
|
|
1129
1094
|
const timer = setTimeout(() => ctrl.abort(), PING_TIMEOUT)
|
|
1130
1095
|
const t0 = performance.now()
|
|
1131
1096
|
try {
|
|
1132
|
-
|
|
1133
|
-
const
|
|
1134
|
-
if (apiKey) headers['Authorization'] = `Bearer ${apiKey}`
|
|
1135
|
-
const resp = await fetch(url, {
|
|
1097
|
+
const req = buildPingRequest(apiKey, modelId, providerKey, url)
|
|
1098
|
+
const resp = await fetch(req.url, {
|
|
1136
1099
|
method: 'POST', signal: ctrl.signal,
|
|
1137
|
-
headers,
|
|
1138
|
-
body: JSON.stringify(
|
|
1100
|
+
headers: req.headers,
|
|
1101
|
+
body: JSON.stringify(req.body),
|
|
1139
1102
|
})
|
|
1140
|
-
|
|
1103
|
+
// 📖 Normalize all HTTP 2xx statuses to "200" so existing verdict/avg logic still works.
|
|
1104
|
+
const code = resp.status >= 200 && resp.status < 300 ? '200' : String(resp.status)
|
|
1105
|
+
return { code, ms: Math.round(performance.now() - t0) }
|
|
1141
1106
|
} catch (err) {
|
|
1142
1107
|
const isTimeout = err.name === 'AbortError'
|
|
1143
1108
|
return {
|
|
@@ -1178,12 +1143,112 @@ const ENV_VAR_NAMES = {
|
|
|
1178
1143
|
cerebras: 'CEREBRAS_API_KEY',
|
|
1179
1144
|
sambanova: 'SAMBANOVA_API_KEY',
|
|
1180
1145
|
openrouter: 'OPENROUTER_API_KEY',
|
|
1146
|
+
huggingface:'HUGGINGFACE_API_KEY',
|
|
1147
|
+
replicate: 'REPLICATE_API_TOKEN',
|
|
1148
|
+
deepinfra: 'DEEPINFRA_API_KEY',
|
|
1149
|
+
fireworks: 'FIREWORKS_API_KEY',
|
|
1181
1150
|
codestral: 'CODESTRAL_API_KEY',
|
|
1182
1151
|
hyperbolic: 'HYPERBOLIC_API_KEY',
|
|
1183
1152
|
scaleway: 'SCALEWAY_API_KEY',
|
|
1184
1153
|
googleai: 'GOOGLE_API_KEY',
|
|
1185
1154
|
}
|
|
1186
1155
|
|
|
1156
|
+
// 📖 Provider metadata used by the setup wizard and Settings details panel.
|
|
1157
|
+
// 📖 Keeps signup links + rate limits centralized so UI stays consistent.
|
|
1158
|
+
const PROVIDER_METADATA = {
|
|
1159
|
+
nvidia: {
|
|
1160
|
+
label: 'NVIDIA NIM',
|
|
1161
|
+
color: chalk.rgb(118, 185, 0),
|
|
1162
|
+
signupUrl: 'https://build.nvidia.com',
|
|
1163
|
+
signupHint: 'Profile → API Keys → Generate',
|
|
1164
|
+
rateLimits: 'Free tier (provider quota by model)',
|
|
1165
|
+
},
|
|
1166
|
+
groq: {
|
|
1167
|
+
label: 'Groq',
|
|
1168
|
+
color: chalk.rgb(249, 103, 20),
|
|
1169
|
+
signupUrl: 'https://console.groq.com/keys',
|
|
1170
|
+
signupHint: 'API Keys → Create API Key',
|
|
1171
|
+
rateLimits: 'Free dev tier (provider quota)',
|
|
1172
|
+
},
|
|
1173
|
+
cerebras: {
|
|
1174
|
+
label: 'Cerebras',
|
|
1175
|
+
color: chalk.rgb(0, 180, 255),
|
|
1176
|
+
signupUrl: 'https://cloud.cerebras.ai',
|
|
1177
|
+
signupHint: 'API Keys → Create',
|
|
1178
|
+
rateLimits: 'Free dev tier (provider quota)',
|
|
1179
|
+
},
|
|
1180
|
+
sambanova: {
|
|
1181
|
+
label: 'SambaNova',
|
|
1182
|
+
color: chalk.rgb(255, 165, 0),
|
|
1183
|
+
signupUrl: 'https://sambanova.ai/developers',
|
|
1184
|
+
signupHint: 'Developers portal → Create API key',
|
|
1185
|
+
rateLimits: 'Dev tier generous quota',
|
|
1186
|
+
},
|
|
1187
|
+
openrouter: {
|
|
1188
|
+
label: 'OpenRouter',
|
|
1189
|
+
color: chalk.rgb(120, 80, 255),
|
|
1190
|
+
signupUrl: 'https://openrouter.ai/keys',
|
|
1191
|
+
signupHint: 'API Keys → Create',
|
|
1192
|
+
rateLimits: '50 req/day, 20/min (:free shared quota)',
|
|
1193
|
+
},
|
|
1194
|
+
huggingface: {
|
|
1195
|
+
label: 'Hugging Face Inference',
|
|
1196
|
+
color: chalk.rgb(255, 182, 0),
|
|
1197
|
+
signupUrl: 'https://huggingface.co/settings/tokens',
|
|
1198
|
+
signupHint: 'Settings → Access Tokens',
|
|
1199
|
+
rateLimits: 'Free monthly credits (~$0.10)',
|
|
1200
|
+
},
|
|
1201
|
+
replicate: {
|
|
1202
|
+
label: 'Replicate',
|
|
1203
|
+
color: chalk.rgb(120, 160, 255),
|
|
1204
|
+
signupUrl: 'https://replicate.com/account/api-tokens',
|
|
1205
|
+
signupHint: 'Account → API Tokens',
|
|
1206
|
+
rateLimits: 'Developer free quota',
|
|
1207
|
+
},
|
|
1208
|
+
deepinfra: {
|
|
1209
|
+
label: 'DeepInfra',
|
|
1210
|
+
color: chalk.rgb(0, 180, 140),
|
|
1211
|
+
signupUrl: 'https://deepinfra.com/login',
|
|
1212
|
+
signupHint: 'Login → API keys',
|
|
1213
|
+
rateLimits: 'Free dev tier (low-latency quota)',
|
|
1214
|
+
},
|
|
1215
|
+
fireworks: {
|
|
1216
|
+
label: 'Fireworks AI',
|
|
1217
|
+
color: chalk.rgb(255, 80, 50),
|
|
1218
|
+
signupUrl: 'https://fireworks.ai',
|
|
1219
|
+
signupHint: 'Create account → Generate API key',
|
|
1220
|
+
rateLimits: '$1 free credits (new dev accounts)',
|
|
1221
|
+
},
|
|
1222
|
+
codestral: {
|
|
1223
|
+
label: 'Mistral Codestral',
|
|
1224
|
+
color: chalk.rgb(255, 100, 100),
|
|
1225
|
+
signupUrl: 'https://codestral.mistral.ai',
|
|
1226
|
+
signupHint: 'API Keys → Create',
|
|
1227
|
+
rateLimits: '30 req/min, 2000/day',
|
|
1228
|
+
},
|
|
1229
|
+
hyperbolic: {
|
|
1230
|
+
label: 'Hyperbolic',
|
|
1231
|
+
color: chalk.rgb(0, 200, 150),
|
|
1232
|
+
signupUrl: 'https://app.hyperbolic.ai/settings',
|
|
1233
|
+
signupHint: 'Settings → API Keys',
|
|
1234
|
+
rateLimits: '$1 free trial credits',
|
|
1235
|
+
},
|
|
1236
|
+
scaleway: {
|
|
1237
|
+
label: 'Scaleway',
|
|
1238
|
+
color: chalk.rgb(130, 0, 250),
|
|
1239
|
+
signupUrl: 'https://console.scaleway.com/iam/api-keys',
|
|
1240
|
+
signupHint: 'IAM → API Keys',
|
|
1241
|
+
rateLimits: '1M free tokens',
|
|
1242
|
+
},
|
|
1243
|
+
googleai: {
|
|
1244
|
+
label: 'Google AI Studio',
|
|
1245
|
+
color: chalk.rgb(66, 133, 244),
|
|
1246
|
+
signupUrl: 'https://aistudio.google.com/apikey',
|
|
1247
|
+
signupHint: 'Get API key',
|
|
1248
|
+
rateLimits: '14.4K req/day, 30/min',
|
|
1249
|
+
},
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1187
1252
|
// 📖 OpenCode config location varies by platform
|
|
1188
1253
|
// 📖 Windows: %APPDATA%\opencode\opencode.json (or sometimes ~/.config/opencode)
|
|
1189
1254
|
// 📖 macOS/Linux: ~/.config/opencode/opencode.json
|
|
@@ -1193,6 +1258,45 @@ const OPENCODE_CONFIG = isWindows
|
|
|
1193
1258
|
|
|
1194
1259
|
// 📖 Fallback to .config on Windows if AppData doesn't exist
|
|
1195
1260
|
const OPENCODE_CONFIG_FALLBACK = join(homedir(), '.config', 'opencode', 'opencode.json')
|
|
1261
|
+
const OPENCODE_PORT_RANGE_START = 4096
|
|
1262
|
+
const OPENCODE_PORT_RANGE_END = 5096
|
|
1263
|
+
|
|
1264
|
+
// 📖 isTcpPortAvailable: checks if a local TCP port is free for OpenCode.
|
|
1265
|
+
// 📖 Used to avoid tmux sub-agent port conflicts when multiple projects run in parallel.
|
|
1266
|
+
function isTcpPortAvailable(port) {
|
|
1267
|
+
return new Promise((resolve) => {
|
|
1268
|
+
const server = createServer()
|
|
1269
|
+
server.once('error', () => resolve(false))
|
|
1270
|
+
server.once('listening', () => {
|
|
1271
|
+
server.close(() => resolve(true))
|
|
1272
|
+
})
|
|
1273
|
+
server.listen(port)
|
|
1274
|
+
})
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
// 📖 resolveOpenCodeTmuxPort: selects a safe port for OpenCode when inside tmux.
|
|
1278
|
+
// 📖 Priority:
|
|
1279
|
+
// 📖 1) OPENCODE_PORT from env (if valid and available)
|
|
1280
|
+
// 📖 2) First available port in 4096-5095
|
|
1281
|
+
async function resolveOpenCodeTmuxPort() {
|
|
1282
|
+
const envPortRaw = process.env.OPENCODE_PORT
|
|
1283
|
+
const envPort = Number.parseInt(envPortRaw || '', 10)
|
|
1284
|
+
|
|
1285
|
+
if (Number.isInteger(envPort) && envPort > 0 && envPort <= 65535) {
|
|
1286
|
+
if (await isTcpPortAvailable(envPort)) {
|
|
1287
|
+
return { port: envPort, source: 'env' }
|
|
1288
|
+
}
|
|
1289
|
+
console.log(chalk.yellow(` ⚠ OPENCODE_PORT=${envPort} is already in use; selecting another port for this run.`))
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
for (let port = OPENCODE_PORT_RANGE_START; port < OPENCODE_PORT_RANGE_END; port++) {
|
|
1293
|
+
if (await isTcpPortAvailable(port)) {
|
|
1294
|
+
return { port, source: 'auto' }
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
return null
|
|
1299
|
+
}
|
|
1196
1300
|
|
|
1197
1301
|
function getOpenCodeConfigPath() {
|
|
1198
1302
|
if (existsSync(OPENCODE_CONFIG)) return OPENCODE_CONFIG
|
|
@@ -1243,10 +1347,30 @@ async function spawnOpenCode(args, providerKey, fcmConfig) {
|
|
|
1243
1347
|
const envVarName = ENV_VAR_NAMES[providerKey]
|
|
1244
1348
|
const resolvedKey = getApiKey(fcmConfig, providerKey)
|
|
1245
1349
|
const childEnv = { ...process.env }
|
|
1350
|
+
const finalArgs = [...args]
|
|
1351
|
+
const hasExplicitPortArg = finalArgs.includes('--port')
|
|
1246
1352
|
if (envVarName && resolvedKey) childEnv[envVarName] = resolvedKey
|
|
1247
1353
|
|
|
1354
|
+
// 📖 In tmux, OpenCode sub-agents need a listening port to open extra panes.
|
|
1355
|
+
// 📖 We auto-pick one if the user did not provide --port explicitly.
|
|
1356
|
+
if (process.env.TMUX && !hasExplicitPortArg) {
|
|
1357
|
+
const tmuxPort = await resolveOpenCodeTmuxPort()
|
|
1358
|
+
if (tmuxPort) {
|
|
1359
|
+
const portValue = String(tmuxPort.port)
|
|
1360
|
+
childEnv.OPENCODE_PORT = portValue
|
|
1361
|
+
finalArgs.push('--port', portValue)
|
|
1362
|
+
if (tmuxPort.source === 'env') {
|
|
1363
|
+
console.log(chalk.dim(` 📺 tmux detected — using OPENCODE_PORT=${portValue}.`))
|
|
1364
|
+
} else {
|
|
1365
|
+
console.log(chalk.dim(` 📺 tmux detected — using OpenCode port ${portValue} for sub-agent panes.`))
|
|
1366
|
+
}
|
|
1367
|
+
} else {
|
|
1368
|
+
console.log(chalk.yellow(` ⚠ tmux detected but no free OpenCode port found in ${OPENCODE_PORT_RANGE_START}-${OPENCODE_PORT_RANGE_END - 1}; launching without --port.`))
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1248
1372
|
const { spawn } = await import('child_process')
|
|
1249
|
-
const child = spawn('opencode',
|
|
1373
|
+
const child = spawn('opencode', finalArgs, {
|
|
1250
1374
|
stdio: 'inherit',
|
|
1251
1375
|
shell: true,
|
|
1252
1376
|
detached: false,
|
|
@@ -1269,7 +1393,7 @@ async function spawnOpenCode(args, providerKey, fcmConfig) {
|
|
|
1269
1393
|
|
|
1270
1394
|
// ─── Start OpenCode ────────────────────────────────────────────────────────────
|
|
1271
1395
|
// 📖 Launches OpenCode with the selected model.
|
|
1272
|
-
// 📖 Handles
|
|
1396
|
+
// 📖 Handles nvidia + all OpenAI-compatible providers defined in sources.js.
|
|
1273
1397
|
// 📖 For nvidia: checks if NIM is configured, sets provider.models entry, spawns with nvidia/model-id.
|
|
1274
1398
|
// 📖 For groq/cerebras: OpenCode has built-in support -- just sets model in config and spawns.
|
|
1275
1399
|
// 📖 Model format: { modelId, label, tier, providerKey }
|
|
@@ -1357,6 +1481,14 @@ After installation, you can use: opencode --model ${modelRef}`
|
|
|
1357
1481
|
await spawnOpenCode([], providerKey, fcmConfig)
|
|
1358
1482
|
}
|
|
1359
1483
|
} else {
|
|
1484
|
+
if (providerKey === 'replicate') {
|
|
1485
|
+
console.log(chalk.yellow(' ⚠ Replicate models are monitor-only for now in OpenCode mode.'))
|
|
1486
|
+
console.log(chalk.dim(' Reason: Replicate uses /v1/predictions instead of OpenAI chat-completions.'))
|
|
1487
|
+
console.log(chalk.dim(' You can still benchmark this model in the TUI and use other providers for OpenCode launch.'))
|
|
1488
|
+
console.log()
|
|
1489
|
+
return
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1360
1492
|
// 📖 Groq: built-in OpenCode provider -- needs provider block with apiKey in opencode.json.
|
|
1361
1493
|
// 📖 Cerebras: NOT built-in -- needs @ai-sdk/openai-compatible + baseURL, like NVIDIA.
|
|
1362
1494
|
// 📖 Both need the model registered in provider.<key>.models so OpenCode can find it.
|
|
@@ -1413,6 +1545,36 @@ After installation, you can use: opencode --model ${modelRef}`
|
|
|
1413
1545
|
},
|
|
1414
1546
|
models: {}
|
|
1415
1547
|
}
|
|
1548
|
+
} else if (providerKey === 'huggingface') {
|
|
1549
|
+
config.provider.huggingface = {
|
|
1550
|
+
npm: '@ai-sdk/openai-compatible',
|
|
1551
|
+
name: 'Hugging Face Inference',
|
|
1552
|
+
options: {
|
|
1553
|
+
baseURL: 'https://router.huggingface.co/v1',
|
|
1554
|
+
apiKey: '{env:HUGGINGFACE_API_KEY}'
|
|
1555
|
+
},
|
|
1556
|
+
models: {}
|
|
1557
|
+
}
|
|
1558
|
+
} else if (providerKey === 'deepinfra') {
|
|
1559
|
+
config.provider.deepinfra = {
|
|
1560
|
+
npm: '@ai-sdk/openai-compatible',
|
|
1561
|
+
name: 'DeepInfra',
|
|
1562
|
+
options: {
|
|
1563
|
+
baseURL: 'https://api.deepinfra.com/v1/openai',
|
|
1564
|
+
apiKey: '{env:DEEPINFRA_API_KEY}'
|
|
1565
|
+
},
|
|
1566
|
+
models: {}
|
|
1567
|
+
}
|
|
1568
|
+
} else if (providerKey === 'fireworks') {
|
|
1569
|
+
config.provider.fireworks = {
|
|
1570
|
+
npm: '@ai-sdk/openai-compatible',
|
|
1571
|
+
name: 'Fireworks AI',
|
|
1572
|
+
options: {
|
|
1573
|
+
baseURL: 'https://api.fireworks.ai/inference/v1',
|
|
1574
|
+
apiKey: '{env:FIREWORKS_API_KEY}'
|
|
1575
|
+
},
|
|
1576
|
+
models: {}
|
|
1577
|
+
}
|
|
1416
1578
|
} else if (providerKey === 'codestral') {
|
|
1417
1579
|
config.provider.codestral = {
|
|
1418
1580
|
npm: '@ai-sdk/openai-compatible',
|
|
@@ -1488,7 +1650,7 @@ After installation, you can use: opencode --model ${modelRef}`
|
|
|
1488
1650
|
// ─── Start OpenCode Desktop ─────────────────────────────────────────────────────
|
|
1489
1651
|
// 📖 startOpenCodeDesktop: Same config logic as startOpenCode, but opens the Desktop app.
|
|
1490
1652
|
// 📖 OpenCode Desktop shares config at the same location as CLI.
|
|
1491
|
-
// 📖 Handles
|
|
1653
|
+
// 📖 Handles nvidia + all OpenAI-compatible providers defined in sources.js.
|
|
1492
1654
|
// 📖 No need to wait for exit — Desktop app stays open independently.
|
|
1493
1655
|
async function startOpenCodeDesktop(model, fcmConfig) {
|
|
1494
1656
|
const providerKey = model.providerKey ?? 'nvidia'
|
|
@@ -1589,6 +1751,14 @@ ${isWindows ? 'set NVIDIA_API_KEY=your_key_here' : 'export NVIDIA_API_KEY=your_k
|
|
|
1589
1751
|
console.log()
|
|
1590
1752
|
}
|
|
1591
1753
|
} else {
|
|
1754
|
+
if (providerKey === 'replicate') {
|
|
1755
|
+
console.log(chalk.yellow(' ⚠ Replicate models are monitor-only for now in OpenCode Desktop mode.'))
|
|
1756
|
+
console.log(chalk.dim(' Reason: Replicate uses /v1/predictions instead of OpenAI chat-completions.'))
|
|
1757
|
+
console.log(chalk.dim(' You can still benchmark this model in the TUI and use other providers for Desktop launch.'))
|
|
1758
|
+
console.log()
|
|
1759
|
+
return
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1592
1762
|
// 📖 Groq: built-in OpenCode provider — needs provider block with apiKey in opencode.json.
|
|
1593
1763
|
// 📖 Cerebras: NOT built-in — needs @ai-sdk/openai-compatible + baseURL, like NVIDIA.
|
|
1594
1764
|
// 📖 Both need the model registered in provider.<key>.models so OpenCode can find it.
|
|
@@ -1643,6 +1813,36 @@ ${isWindows ? 'set NVIDIA_API_KEY=your_key_here' : 'export NVIDIA_API_KEY=your_k
|
|
|
1643
1813
|
},
|
|
1644
1814
|
models: {}
|
|
1645
1815
|
}
|
|
1816
|
+
} else if (providerKey === 'huggingface') {
|
|
1817
|
+
config.provider.huggingface = {
|
|
1818
|
+
npm: '@ai-sdk/openai-compatible',
|
|
1819
|
+
name: 'Hugging Face Inference',
|
|
1820
|
+
options: {
|
|
1821
|
+
baseURL: 'https://router.huggingface.co/v1',
|
|
1822
|
+
apiKey: '{env:HUGGINGFACE_API_KEY}'
|
|
1823
|
+
},
|
|
1824
|
+
models: {}
|
|
1825
|
+
}
|
|
1826
|
+
} else if (providerKey === 'deepinfra') {
|
|
1827
|
+
config.provider.deepinfra = {
|
|
1828
|
+
npm: '@ai-sdk/openai-compatible',
|
|
1829
|
+
name: 'DeepInfra',
|
|
1830
|
+
options: {
|
|
1831
|
+
baseURL: 'https://api.deepinfra.com/v1/openai',
|
|
1832
|
+
apiKey: '{env:DEEPINFRA_API_KEY}'
|
|
1833
|
+
},
|
|
1834
|
+
models: {}
|
|
1835
|
+
}
|
|
1836
|
+
} else if (providerKey === 'fireworks') {
|
|
1837
|
+
config.provider.fireworks = {
|
|
1838
|
+
npm: '@ai-sdk/openai-compatible',
|
|
1839
|
+
name: 'Fireworks AI',
|
|
1840
|
+
options: {
|
|
1841
|
+
baseURL: 'https://api.fireworks.ai/inference/v1',
|
|
1842
|
+
apiKey: '{env:FIREWORKS_API_KEY}'
|
|
1843
|
+
},
|
|
1844
|
+
models: {}
|
|
1845
|
+
}
|
|
1646
1846
|
} else if (providerKey === 'codestral') {
|
|
1647
1847
|
config.provider.codestral = {
|
|
1648
1848
|
npm: '@ai-sdk/openai-compatible',
|
|
@@ -1854,7 +2054,7 @@ async function runFiableMode(config) {
|
|
|
1854
2054
|
const pingPromises = results.map(r => {
|
|
1855
2055
|
const rApiKey = getApiKey(config, r.providerKey)
|
|
1856
2056
|
const url = sources[r.providerKey]?.url
|
|
1857
|
-
return ping(rApiKey, r.modelId, url).then(({ code, ms }) => {
|
|
2057
|
+
return ping(rApiKey, r.modelId, r.providerKey, url).then(({ code, ms }) => {
|
|
1858
2058
|
r.pings.push({ ms, code })
|
|
1859
2059
|
if (code === '200') {
|
|
1860
2060
|
r.status = 'up'
|
|
@@ -2111,12 +2311,14 @@ async function main() {
|
|
|
2111
2311
|
lines.push('')
|
|
2112
2312
|
lines.push(` ${chalk.bold('⚙ Settings')} ${chalk.dim('— free-coding-models v' + LOCAL_VERSION)}`)
|
|
2113
2313
|
lines.push('')
|
|
2114
|
-
lines.push(` ${chalk.bold('Providers')}`)
|
|
2314
|
+
lines.push(` ${chalk.bold('🧩 Providers')}`)
|
|
2315
|
+
lines.push(` ${chalk.dim(' ' + '─'.repeat(112))}`)
|
|
2115
2316
|
lines.push('')
|
|
2116
2317
|
|
|
2117
2318
|
for (let i = 0; i < providerKeys.length; i++) {
|
|
2118
2319
|
const pk = providerKeys[i]
|
|
2119
2320
|
const src = sources[pk]
|
|
2321
|
+
const meta = PROVIDER_METADATA[pk] || {}
|
|
2120
2322
|
const isCursor = i === state.settingsCursor
|
|
2121
2323
|
const enabled = isProviderEnabled(state.config, pk)
|
|
2122
2324
|
const keyVal = state.config.apiKeys?.[pk] ?? ''
|
|
@@ -2140,22 +2342,37 @@ async function main() {
|
|
|
2140
2342
|
if (testResult === 'pending') testBadge = chalk.yellow('[Testing…]')
|
|
2141
2343
|
else if (testResult === 'ok') testBadge = chalk.greenBright('[Test ✅]')
|
|
2142
2344
|
else if (testResult === 'fail') testBadge = chalk.red('[Test ❌]')
|
|
2345
|
+
const rateSummary = chalk.dim((meta.rateLimits || 'No limit info').slice(0, 36))
|
|
2143
2346
|
|
|
2144
|
-
const enabledBadge = enabled ? chalk.greenBright('✅') : chalk.
|
|
2145
|
-
const providerName = chalk.bold(src.name.padEnd(
|
|
2347
|
+
const enabledBadge = enabled ? chalk.greenBright('✅') : chalk.redBright('❌')
|
|
2348
|
+
const providerName = chalk.bold((meta.label || src.name || pk).slice(0, 22).padEnd(22))
|
|
2146
2349
|
const bullet = isCursor ? chalk.bold.cyan(' ❯ ') : chalk.dim(' ')
|
|
2147
2350
|
|
|
2148
|
-
const row = `${bullet}[ ${enabledBadge} ] ${providerName} ${keyDisplay.padEnd(30)} ${testBadge}`
|
|
2351
|
+
const row = `${bullet}[ ${enabledBadge} ] ${providerName} ${keyDisplay.padEnd(30)} ${testBadge} ${rateSummary}`
|
|
2149
2352
|
lines.push(isCursor ? chalk.bgRgb(30, 30, 60)(row) : row)
|
|
2150
2353
|
}
|
|
2151
2354
|
|
|
2152
2355
|
lines.push('')
|
|
2153
|
-
|
|
2356
|
+
const selectedProviderKey = providerKeys[Math.min(state.settingsCursor, providerKeys.length - 1)]
|
|
2357
|
+
const selectedSource = sources[selectedProviderKey]
|
|
2358
|
+
const selectedMeta = PROVIDER_METADATA[selectedProviderKey] || {}
|
|
2359
|
+
if (selectedSource && state.settingsCursor < telemetryRowIdx) {
|
|
2360
|
+
const selectedKey = getApiKey(state.config, selectedProviderKey)
|
|
2361
|
+
const setupStatus = selectedKey ? chalk.green('API key detected ✅') : chalk.yellow('API key missing ⚠')
|
|
2362
|
+
lines.push(` ${chalk.bold('Setup Instructions')} — ${selectedMeta.label || selectedSource.name || selectedProviderKey}`)
|
|
2363
|
+
lines.push(chalk.dim(` 1) Create a ${selectedMeta.label || selectedSource.name} account: ${selectedMeta.signupUrl || 'signup link missing'}`))
|
|
2364
|
+
lines.push(chalk.dim(` 2) ${selectedMeta.signupHint || 'Generate an API key and paste it with Enter on this row'}`))
|
|
2365
|
+
lines.push(chalk.dim(` 3) Press ${chalk.yellow('T')} to test your key. Status: ${setupStatus}`))
|
|
2366
|
+
lines.push('')
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
lines.push(` ${chalk.bold('📊 Analytics')}`)
|
|
2370
|
+
lines.push(` ${chalk.dim(' ' + '─'.repeat(112))}`)
|
|
2154
2371
|
lines.push('')
|
|
2155
2372
|
|
|
2156
2373
|
const telemetryCursor = state.settingsCursor === telemetryRowIdx
|
|
2157
2374
|
const telemetryEnabled = state.config.telemetry?.enabled === true
|
|
2158
|
-
const telemetryStatus = telemetryEnabled ? chalk.greenBright('✅ Enabled') : chalk.
|
|
2375
|
+
const telemetryStatus = telemetryEnabled ? chalk.greenBright('✅ Enabled') : chalk.redBright('❌ Disabled')
|
|
2159
2376
|
const telemetryRowBullet = telemetryCursor ? chalk.bold.cyan(' ❯ ') : chalk.dim(' ')
|
|
2160
2377
|
const telemetryEnv = parseTelemetryEnv(process.env.FREE_CODING_MODELS_TELEMETRY)
|
|
2161
2378
|
const telemetrySource = telemetryEnv === null
|
|
@@ -2227,7 +2444,7 @@ async function main() {
|
|
|
2227
2444
|
if (!testModel) { state.settingsTestResults[providerKey] = 'fail'; return }
|
|
2228
2445
|
|
|
2229
2446
|
state.settingsTestResults[providerKey] = 'pending'
|
|
2230
|
-
const { code } = await ping(testKey, testModel, src.url)
|
|
2447
|
+
const { code } = await ping(testKey, testModel, providerKey, src.url)
|
|
2231
2448
|
state.settingsTestResults[providerKey] = code === '200' ? 'ok' : 'fail'
|
|
2232
2449
|
}
|
|
2233
2450
|
|
|
@@ -2566,7 +2783,7 @@ async function main() {
|
|
|
2566
2783
|
const pingModel = async (r) => {
|
|
2567
2784
|
const providerApiKey = getApiKey(state.config, r.providerKey) ?? null
|
|
2568
2785
|
const providerUrl = sources[r.providerKey]?.url ?? sources.nvidia.url
|
|
2569
|
-
const { code, ms } = await ping(providerApiKey, r.modelId, providerUrl)
|
|
2786
|
+
const { code, ms } = await ping(providerApiKey, r.modelId, r.providerKey, providerUrl)
|
|
2570
2787
|
|
|
2571
2788
|
// 📖 Store ping result as object with ms and code
|
|
2572
2789
|
// 📖 ms = actual response time (even for errors like 429)
|
package/lib/config.js
CHANGED
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
* "cerebras": "csk_xxx",
|
|
18
18
|
* "sambanova": "sn-xxx",
|
|
19
19
|
* "openrouter": "sk-or-xxx",
|
|
20
|
+
* "huggingface":"hf_xxx",
|
|
21
|
+
* "replicate": "r8_xxx",
|
|
22
|
+
* "deepinfra": "di_xxx",
|
|
23
|
+
* "fireworks": "fw_xxx",
|
|
20
24
|
* "codestral": "csk-xxx",
|
|
21
25
|
* "hyperbolic": "eyJ...",
|
|
22
26
|
* "scaleway": "scw-xxx",
|
|
@@ -28,6 +32,10 @@
|
|
|
28
32
|
* "cerebras": { "enabled": true },
|
|
29
33
|
* "sambanova": { "enabled": true },
|
|
30
34
|
* "openrouter": { "enabled": true },
|
|
35
|
+
* "huggingface":{ "enabled": true },
|
|
36
|
+
* "replicate": { "enabled": true },
|
|
37
|
+
* "deepinfra": { "enabled": true },
|
|
38
|
+
* "fireworks": { "enabled": true },
|
|
31
39
|
* "codestral": { "enabled": true },
|
|
32
40
|
* "hyperbolic": { "enabled": true },
|
|
33
41
|
* "scaleway": { "enabled": true },
|
|
@@ -74,6 +82,10 @@ const ENV_VARS = {
|
|
|
74
82
|
cerebras: 'CEREBRAS_API_KEY',
|
|
75
83
|
sambanova: 'SAMBANOVA_API_KEY',
|
|
76
84
|
openrouter: 'OPENROUTER_API_KEY',
|
|
85
|
+
huggingface:['HUGGINGFACE_API_KEY', 'HF_TOKEN'],
|
|
86
|
+
replicate: 'REPLICATE_API_TOKEN',
|
|
87
|
+
deepinfra: ['DEEPINFRA_API_KEY', 'DEEPINFRA_TOKEN'],
|
|
88
|
+
fireworks: 'FIREWORKS_API_KEY',
|
|
77
89
|
codestral: 'CODESTRAL_API_KEY',
|
|
78
90
|
hyperbolic: 'HYPERBOLIC_API_KEY',
|
|
79
91
|
scaleway: 'SCALEWAY_API_KEY',
|
|
@@ -163,7 +175,10 @@ export function saveConfig(config) {
|
|
|
163
175
|
export function getApiKey(config, providerKey) {
|
|
164
176
|
// 📖 Env var override — takes precedence over everything
|
|
165
177
|
const envVar = ENV_VARS[providerKey]
|
|
166
|
-
|
|
178
|
+
const envCandidates = Array.isArray(envVar) ? envVar : [envVar]
|
|
179
|
+
for (const candidate of envCandidates) {
|
|
180
|
+
if (candidate && process.env[candidate]) return process.env[candidate]
|
|
181
|
+
}
|
|
167
182
|
|
|
168
183
|
// 📖 Config file value
|
|
169
184
|
const key = config?.apiKeys?.[providerKey]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "free-coding-models",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.64",
|
|
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
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
* 📖 Secondary: https://swe-rebench.com (independent evals, scores are lower)
|
|
28
28
|
* 📖 Leaderboard tracker: https://www.marc0.dev/en/leaderboard
|
|
29
29
|
*
|
|
30
|
-
* @exports nvidiaNim, groq, cerebras, sambanova, openrouter, codestral, hyperbolic, scaleway, googleai — model arrays per provider
|
|
31
|
-
* @exports sources — map of { nvidia, groq, cerebras, sambanova, openrouter, codestral, hyperbolic, scaleway, googleai } each with { name, url, models }
|
|
30
|
+
* @exports nvidiaNim, groq, cerebras, sambanova, openrouter, huggingface, replicate, deepinfra, fireworks, codestral, hyperbolic, scaleway, googleai — model arrays per provider
|
|
31
|
+
* @exports sources — map of { nvidia, groq, cerebras, sambanova, openrouter, huggingface, replicate, deepinfra, fireworks, codestral, hyperbolic, scaleway, googleai } each with { name, url, models }
|
|
32
32
|
* @exports MODELS — flat array of [modelId, label, tier, sweScore, ctx, providerKey]
|
|
33
33
|
*
|
|
34
34
|
* 📖 MODELS now includes providerKey as 6th element so ping() knows which
|
|
@@ -139,13 +139,17 @@ export const sambanova = [
|
|
|
139
139
|
['Meta-Llama-3.3-70B-Instruct', 'Llama 3.3 70B', 'A-', '39.5%', '128k'],
|
|
140
140
|
// ── B tier ──
|
|
141
141
|
['Meta-Llama-3.1-8B-Instruct', 'Llama 3.1 8B', 'B', '28.8%', '128k'],
|
|
142
|
+
// ── A tier — requested Llama3-Groq coding tuned family ──
|
|
143
|
+
['Llama-3-Groq-70B-Tool-Use', 'Llama3-Groq 70B', 'A', '43.0%', '128k'],
|
|
142
144
|
]
|
|
143
145
|
|
|
144
146
|
// 📖 OpenRouter source - https://openrouter.ai
|
|
145
147
|
// 📖 Free :free models with shared quota — 50 free req/day
|
|
146
|
-
// 📖 API keys at https://openrouter.ai/
|
|
148
|
+
// 📖 API keys at https://openrouter.ai/keys
|
|
147
149
|
export const openrouter = [
|
|
148
|
-
['qwen/qwen3-coder:free',
|
|
150
|
+
['qwen/qwen3-coder:480b-free', 'Qwen3 Coder 480B', 'S+', '70.6%', '256k'],
|
|
151
|
+
['mistralai/devstral-2-free', 'Devstral 2', 'S+', '72.2%', '256k'],
|
|
152
|
+
['mimo-v2-flash-free', 'Mimo V2 Flash', 'A', '45.0%', '128k'],
|
|
149
153
|
['stepfun/step-3.5-flash:free', 'Step 3.5 Flash', 'S+', '74.4%', '256k'],
|
|
150
154
|
['deepseek/deepseek-r1-0528:free', 'DeepSeek R1 0528', 'S', '61.0%', '128k'],
|
|
151
155
|
['qwen/qwen3-next-80b-a3b-instruct:free', 'Qwen3 80B Instruct', 'S', '65.0%', '128k'],
|
|
@@ -155,6 +159,35 @@ export const openrouter = [
|
|
|
155
159
|
['meta-llama/llama-3.3-70b-instruct:free', 'Llama 3.3 70B', 'A-', '39.5%', '128k'],
|
|
156
160
|
]
|
|
157
161
|
|
|
162
|
+
// 📖 Hugging Face Inference source - https://huggingface.co
|
|
163
|
+
// 📖 OpenAI-compatible endpoint via router.huggingface.co/v1
|
|
164
|
+
// 📖 Free monthly credits on developer accounts (~$0.10) — token at https://huggingface.co/settings/tokens
|
|
165
|
+
export const huggingface = [
|
|
166
|
+
['deepseek-ai/DeepSeek-V3-Coder', 'DeepSeek V3 Coder', 'S', '62.0%', '128k'],
|
|
167
|
+
['bigcode/starcoder2-15b', 'StarCoder2 15B', 'B', '25.0%', '16k'],
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
// 📖 Replicate source - https://replicate.com
|
|
171
|
+
// 📖 Uses predictions endpoint (not OpenAI chat-completions) with token auth
|
|
172
|
+
export const replicate = [
|
|
173
|
+
['codellama/CodeLlama-70b-Instruct-hf', 'CodeLlama 70B', 'A-', '39.0%', '16k'],
|
|
174
|
+
]
|
|
175
|
+
|
|
176
|
+
// 📖 DeepInfra source - https://deepinfra.com
|
|
177
|
+
// 📖 OpenAI-compatible endpoint: https://api.deepinfra.com/v1/openai/chat/completions
|
|
178
|
+
export const deepinfra = [
|
|
179
|
+
['mistralai/Mixtral-8x22B-Instruct-v0.1', 'Mixtral Code', 'B+', '32.0%', '64k'],
|
|
180
|
+
['meta-llama/Meta-Llama-3.1-70B-Instruct', 'Llama 3.1 70B', 'A-', '39.5%', '128k'],
|
|
181
|
+
]
|
|
182
|
+
|
|
183
|
+
// 📖 Fireworks AI source - https://fireworks.ai
|
|
184
|
+
// 📖 OpenAI-compatible endpoint: https://api.fireworks.ai/inference/v1/chat/completions
|
|
185
|
+
// 📖 Free trial credits: $1 for new developers
|
|
186
|
+
export const fireworks = [
|
|
187
|
+
['accounts/fireworks/models/deepseek-v3', 'DeepSeek V3', 'S', '62.0%', '128k'],
|
|
188
|
+
['accounts/fireworks/models/deepseek-r1', 'DeepSeek R1', 'S', '61.0%', '128k'],
|
|
189
|
+
]
|
|
190
|
+
|
|
158
191
|
// 📖 Mistral Codestral source - https://codestral.mistral.ai
|
|
159
192
|
// 📖 Free coding model — 30 req/min, 2000/day (phone number required for key)
|
|
160
193
|
// 📖 API keys at https://codestral.mistral.ai
|
|
@@ -225,6 +258,26 @@ export const sources = {
|
|
|
225
258
|
url: 'https://openrouter.ai/api/v1/chat/completions',
|
|
226
259
|
models: openrouter,
|
|
227
260
|
},
|
|
261
|
+
huggingface: {
|
|
262
|
+
name: 'Hugging Face',
|
|
263
|
+
url: 'https://router.huggingface.co/v1/chat/completions',
|
|
264
|
+
models: huggingface,
|
|
265
|
+
},
|
|
266
|
+
replicate: {
|
|
267
|
+
name: 'Replicate',
|
|
268
|
+
url: 'https://api.replicate.com/v1/predictions',
|
|
269
|
+
models: replicate,
|
|
270
|
+
},
|
|
271
|
+
deepinfra: {
|
|
272
|
+
name: 'DeepInfra',
|
|
273
|
+
url: 'https://api.deepinfra.com/v1/openai/chat/completions',
|
|
274
|
+
models: deepinfra,
|
|
275
|
+
},
|
|
276
|
+
fireworks: {
|
|
277
|
+
name: 'Fireworks',
|
|
278
|
+
url: 'https://api.fireworks.ai/inference/v1/chat/completions',
|
|
279
|
+
models: fireworks,
|
|
280
|
+
},
|
|
228
281
|
codestral: {
|
|
229
282
|
name: 'Codestral',
|
|
230
283
|
url: 'https://codestral.mistral.ai/v1/chat/completions',
|