free-coding-models 0.1.4 → 0.1.12
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 +42 -0
- package/bin/free-coding-models.js +117 -160
- package/package.json +5 -2
- package/patch-openclaw-models.js +111 -0
- package/patch-openclaw.js +172 -0
package/README.md
CHANGED
|
@@ -313,6 +313,9 @@ Or run without flags and choose **OpenClaw** from the startup menu.
|
|
|
313
313
|
"defaults": {
|
|
314
314
|
"model": {
|
|
315
315
|
"primary": "nvidia/deepseek-ai/deepseek-v3.2"
|
|
316
|
+
},
|
|
317
|
+
"models": {
|
|
318
|
+
"nvidia/deepseek-ai/deepseek-v3.2": {}
|
|
316
319
|
}
|
|
317
320
|
}
|
|
318
321
|
}
|
|
@@ -321,6 +324,8 @@ Or run without flags and choose **OpenClaw** from the startup menu.
|
|
|
321
324
|
|
|
322
325
|
> ⚠️ **Note:** `providers` must be nested under `models.providers` — not at the config root. A root-level `providers` key is ignored by OpenClaw.
|
|
323
326
|
|
|
327
|
+
> ⚠️ **Note:** The model must also be listed in `agents.defaults.models` (the allowlist). Without this entry, OpenClaw rejects the model with *"not allowed"* even if it is set as primary.
|
|
328
|
+
|
|
324
329
|
### After updating OpenClaw config
|
|
325
330
|
|
|
326
331
|
OpenClaw's gateway **auto-reloads** config file changes (depending on `gateway.reload.mode`). To apply manually:
|
|
@@ -337,6 +342,43 @@ openclaw configure
|
|
|
337
342
|
|
|
338
343
|
> 💡 **Why use remote NIM models with OpenClaw?** NVIDIA NIM serves models via a fast API — no local GPU required, no VRAM limits, free credits for developers. You get frontier-class coding models (DeepSeek V3, Kimi K2, Qwen3 Coder) without downloading anything.
|
|
339
344
|
|
|
345
|
+
### Patching OpenClaw for full NVIDIA model support
|
|
346
|
+
|
|
347
|
+
**Problem:** By default, OpenClaw only allows a few specific NVIDIA models in its allowlist. If you try to use a model that's not in the list, you'll get this error:
|
|
348
|
+
|
|
349
|
+
```
|
|
350
|
+
Model "nvidia/mistralai/devstral-2-123b-instruct-2512" is not allowed. Use /models to list providers, or /models <provider> to list models.
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
**Solution:** Patch OpenClaw's configuration to add ALL 47 NVIDIA models from `free-coding-models` to the allowlist:
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
# From the free-coding-models package directory
|
|
357
|
+
node patch-openclaw.js
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
This script:
|
|
361
|
+
- Backs up `~/.openclaw/agents/main/agent/models.json` and `~/.openclaw/openclaw.json`
|
|
362
|
+
- Adds all 47 NVIDIA models with proper context window and token limits
|
|
363
|
+
- Preserves existing models and configuration
|
|
364
|
+
- Prints a summary of what was added
|
|
365
|
+
|
|
366
|
+
**After patching:**
|
|
367
|
+
|
|
368
|
+
1. Restart OpenClaw gateway:
|
|
369
|
+
```bash
|
|
370
|
+
systemctl --user restart openclaw-gateway
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
2. Verify models are available:
|
|
374
|
+
```bash
|
|
375
|
+
free-coding-models --openclaw
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
3. Select any model — no more "not allowed" errors!
|
|
379
|
+
|
|
380
|
+
**Why this is needed:** OpenClaw uses a strict allowlist system to prevent typos and invalid models. The `patch-openclaw.js` script populates the allowlist with all known working NVIDIA models, so you can freely switch between them without manually editing config files.
|
|
381
|
+
|
|
340
382
|
---
|
|
341
383
|
|
|
342
384
|
## ⚙️ How it works
|
|
@@ -75,10 +75,44 @@ import { readFileSync, writeFileSync, existsSync, copyFileSync, mkdirSync } from
|
|
|
75
75
|
import { homedir } from 'os'
|
|
76
76
|
import { join } from 'path'
|
|
77
77
|
import { MODELS } from '../sources.js'
|
|
78
|
+
import { patchOpenClawModelsJson } from '../patch-openclaw-models.js'
|
|
79
|
+
import { getAvg, getVerdict, getUptime, sortResults, filterByTier, findBestModel, parseArgs, TIER_ORDER, VERDICT_ORDER, TIER_LETTER_MAP } from '../lib/utils.js'
|
|
78
80
|
|
|
79
81
|
const require = createRequire(import.meta.url)
|
|
80
82
|
const readline = require('readline')
|
|
81
83
|
|
|
84
|
+
// ─── Version check ────────────────────────────────────────────────────────────
|
|
85
|
+
const pkg = require('../package.json')
|
|
86
|
+
const LOCAL_VERSION = pkg.version
|
|
87
|
+
|
|
88
|
+
async function checkForUpdate() {
|
|
89
|
+
try {
|
|
90
|
+
const res = await fetch('https://registry.npmjs.org/free-coding-models/latest', { signal: AbortSignal.timeout(5000) })
|
|
91
|
+
if (!res.ok) return null
|
|
92
|
+
const data = await res.json()
|
|
93
|
+
if (data.version && data.version !== LOCAL_VERSION) return data.version
|
|
94
|
+
} catch {}
|
|
95
|
+
return null
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function runUpdate() {
|
|
99
|
+
const { execSync } = require('child_process')
|
|
100
|
+
console.log()
|
|
101
|
+
console.log(chalk.bold.cyan(' ⬆ Updating free-coding-models...'))
|
|
102
|
+
console.log()
|
|
103
|
+
try {
|
|
104
|
+
execSync('npm i -g free-coding-models', { stdio: 'inherit' })
|
|
105
|
+
console.log()
|
|
106
|
+
console.log(chalk.green(' ✅ Update complete! Please restart free-coding-models.'))
|
|
107
|
+
console.log()
|
|
108
|
+
} catch {
|
|
109
|
+
console.log()
|
|
110
|
+
console.log(chalk.red(' ✖ Update failed. Try manually: npm i -g free-coding-models'))
|
|
111
|
+
console.log()
|
|
112
|
+
}
|
|
113
|
+
process.exit(0)
|
|
114
|
+
}
|
|
115
|
+
|
|
82
116
|
// ─── Config path ──────────────────────────────────────────────────────────────
|
|
83
117
|
const CONFIG_PATH = join(homedir(), '.free-coding-models')
|
|
84
118
|
|
|
@@ -128,8 +162,8 @@ async function promptApiKey() {
|
|
|
128
162
|
// ─── Startup mode selection menu ──────────────────────────────────────────────
|
|
129
163
|
// 📖 Shown at startup when neither --opencode nor --openclaw flag is given.
|
|
130
164
|
// 📖 Simple arrow-key selector in normal terminal (not alt screen).
|
|
131
|
-
// 📖 Returns 'opencode' or '
|
|
132
|
-
async function promptModeSelection() {
|
|
165
|
+
// 📖 Returns 'opencode', 'openclaw', or 'update'.
|
|
166
|
+
async function promptModeSelection(latestVersion) {
|
|
133
167
|
const options = [
|
|
134
168
|
{
|
|
135
169
|
label: 'OpenCode',
|
|
@@ -143,6 +177,14 @@ async function promptModeSelection() {
|
|
|
143
177
|
},
|
|
144
178
|
]
|
|
145
179
|
|
|
180
|
+
if (latestVersion) {
|
|
181
|
+
options.push({
|
|
182
|
+
label: 'Update now',
|
|
183
|
+
icon: '⬆',
|
|
184
|
+
description: `Update free-coding-models to v${latestVersion}`,
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
|
|
146
188
|
return new Promise((resolve) => {
|
|
147
189
|
let selected = 0
|
|
148
190
|
|
|
@@ -150,6 +192,10 @@ async function promptModeSelection() {
|
|
|
150
192
|
const render = () => {
|
|
151
193
|
process.stdout.write('\x1b[2J\x1b[H') // clear screen + cursor home
|
|
152
194
|
console.log()
|
|
195
|
+
if (latestVersion) {
|
|
196
|
+
console.log(chalk.red(` ⚠ New version available (v${latestVersion}), please run npm i -g free-coding-models to install`))
|
|
197
|
+
console.log()
|
|
198
|
+
}
|
|
153
199
|
console.log(chalk.bold(' ⚡ Free Coding Models') + chalk.dim(' — Choose your tool'))
|
|
154
200
|
console.log()
|
|
155
201
|
for (let i = 0; i < options.length; i++) {
|
|
@@ -189,7 +235,9 @@ async function promptModeSelection() {
|
|
|
189
235
|
if (process.stdin.isTTY) process.stdin.setRawMode(false)
|
|
190
236
|
process.stdin.removeListener('keypress', onKey)
|
|
191
237
|
process.stdin.pause()
|
|
192
|
-
|
|
238
|
+
const choices = ['opencode', 'openclaw']
|
|
239
|
+
if (latestVersion) choices.push('update')
|
|
240
|
+
resolve(choices[selected])
|
|
193
241
|
}
|
|
194
242
|
}
|
|
195
243
|
|
|
@@ -252,90 +300,8 @@ const spinCell = (f, o = 0) => chalk.dim.yellow(FRAMES[(f + o) % FRAMES.length].
|
|
|
252
300
|
|
|
253
301
|
// ─── Table renderer ───────────────────────────────────────────────────────────
|
|
254
302
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
// 📖 Calculate average only from successful pings (code 200)
|
|
258
|
-
// 📖 pings are objects: { ms, code }
|
|
259
|
-
const successfulPings = (r.pings || []).filter(p => p.code === '200')
|
|
260
|
-
if (successfulPings.length === 0) return Infinity
|
|
261
|
-
return Math.round(successfulPings.reduce((a, b) => a + b.ms, 0) / successfulPings.length)
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// 📖 Verdict order for sorting
|
|
265
|
-
const VERDICT_ORDER = ['Perfect', 'Normal', 'Slow', 'Very Slow', 'Overloaded', 'Unstable', 'Not Active', 'Pending']
|
|
266
|
-
|
|
267
|
-
// 📖 Get verdict for a model result
|
|
268
|
-
const getVerdict = (r) => {
|
|
269
|
-
const avg = getAvg(r)
|
|
270
|
-
const wasUpBefore = r.pings.length > 0 && r.pings.some(p => p.code === '200')
|
|
271
|
-
|
|
272
|
-
// 📖 429 = rate limited = Overloaded
|
|
273
|
-
if (r.httpCode === '429') return 'Overloaded'
|
|
274
|
-
if ((r.status === 'timeout' || r.status === 'down') && wasUpBefore) return 'Unstable'
|
|
275
|
-
if (r.status === 'timeout' || r.status === 'down') return 'Not Active'
|
|
276
|
-
if (avg === Infinity) return 'Pending'
|
|
277
|
-
if (avg < 400) return 'Perfect'
|
|
278
|
-
if (avg < 1000) return 'Normal'
|
|
279
|
-
if (avg < 3000) return 'Slow'
|
|
280
|
-
if (avg < 5000) return 'Very Slow'
|
|
281
|
-
if (avg < 10000) return 'Unstable'
|
|
282
|
-
return 'Unstable'
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// 📖 Calculate uptime percentage (successful pings / total pings)
|
|
286
|
-
// 📖 Only count code 200 responses
|
|
287
|
-
const getUptime = (r) => {
|
|
288
|
-
if (r.pings.length === 0) return 0
|
|
289
|
-
const successful = r.pings.filter(p => p.code === '200').length
|
|
290
|
-
return Math.round((successful / r.pings.length) * 100)
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// 📖 Sort results using the same logic as renderTable - used for both display and selection
|
|
294
|
-
const sortResults = (results, sortColumn, sortDirection) => {
|
|
295
|
-
return [...results].sort((a, b) => {
|
|
296
|
-
let cmp = 0
|
|
297
|
-
|
|
298
|
-
switch (sortColumn) {
|
|
299
|
-
case 'rank':
|
|
300
|
-
cmp = a.idx - b.idx
|
|
301
|
-
break
|
|
302
|
-
case 'tier':
|
|
303
|
-
cmp = TIER_ORDER.indexOf(a.tier) - TIER_ORDER.indexOf(b.tier)
|
|
304
|
-
break
|
|
305
|
-
case 'origin':
|
|
306
|
-
cmp = 'NVIDIA NIM'.localeCompare('NVIDIA NIM') // All same for now
|
|
307
|
-
break
|
|
308
|
-
case 'model':
|
|
309
|
-
cmp = a.label.localeCompare(b.label)
|
|
310
|
-
break
|
|
311
|
-
case 'ping': {
|
|
312
|
-
const aLast = a.pings.length > 0 ? a.pings[a.pings.length - 1] : null
|
|
313
|
-
const bLast = b.pings.length > 0 ? b.pings[b.pings.length - 1] : null
|
|
314
|
-
const aPing = aLast?.code === '200' ? aLast.ms : Infinity
|
|
315
|
-
const bPing = bLast?.code === '200' ? bLast.ms : Infinity
|
|
316
|
-
cmp = aPing - bPing
|
|
317
|
-
break
|
|
318
|
-
}
|
|
319
|
-
case 'avg':
|
|
320
|
-
cmp = getAvg(a) - getAvg(b)
|
|
321
|
-
break
|
|
322
|
-
case 'status':
|
|
323
|
-
cmp = a.status.localeCompare(b.status)
|
|
324
|
-
break
|
|
325
|
-
case 'verdict': {
|
|
326
|
-
const aVerdict = getVerdict(a)
|
|
327
|
-
const bVerdict = getVerdict(b)
|
|
328
|
-
cmp = VERDICT_ORDER.indexOf(aVerdict) - VERDICT_ORDER.indexOf(bVerdict)
|
|
329
|
-
break
|
|
330
|
-
}
|
|
331
|
-
case 'uptime':
|
|
332
|
-
cmp = getUptime(a) - getUptime(b)
|
|
333
|
-
break
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
return sortDirection === 'asc' ? cmp : -cmp
|
|
337
|
-
})
|
|
338
|
-
}
|
|
303
|
+
// 📖 Core logic functions (getAvg, getVerdict, getUptime, sortResults, etc.)
|
|
304
|
+
// 📖 are imported from lib/utils.js for testability
|
|
339
305
|
|
|
340
306
|
// 📖 renderTable: mode param controls footer hint text (opencode vs openclaw)
|
|
341
307
|
function renderTable(results, pendingPings, frame, cursor = null, sortColumn = 'avg', sortDirection = 'asc', pingInterval = PING_INTERVAL, lastPingTime = Date.now(), mode = 'opencode') {
|
|
@@ -660,13 +626,21 @@ async function startOpenCode(model) {
|
|
|
660
626
|
const { spawn } = await import('child_process')
|
|
661
627
|
const child = spawn('opencode', [], {
|
|
662
628
|
stdio: 'inherit',
|
|
663
|
-
shell:
|
|
629
|
+
shell: true
|
|
664
630
|
})
|
|
665
631
|
|
|
666
632
|
// 📖 Wait for OpenCode to exit
|
|
667
633
|
await new Promise((resolve, reject) => {
|
|
668
634
|
child.on('exit', resolve)
|
|
669
|
-
child.on('error',
|
|
635
|
+
child.on('error', (err) => {
|
|
636
|
+
if (err.code === 'ENOENT') {
|
|
637
|
+
console.error(chalk.red('\n ✗ Could not find "opencode" — is it installed and in your PATH?'))
|
|
638
|
+
console.error(chalk.dim(' Install: npm i -g opencode or see https://opencode.ai'))
|
|
639
|
+
resolve(1)
|
|
640
|
+
} else {
|
|
641
|
+
reject(err)
|
|
642
|
+
}
|
|
643
|
+
})
|
|
670
644
|
})
|
|
671
645
|
} else {
|
|
672
646
|
// 📖 NVIDIA NIM not configured - show install prompt and launch
|
|
@@ -702,13 +676,21 @@ After installation, you can use: opencode --model nvidia/${model.modelId}`
|
|
|
702
676
|
const { spawn } = await import('child_process')
|
|
703
677
|
const child = spawn('opencode', [], {
|
|
704
678
|
stdio: 'inherit',
|
|
705
|
-
shell:
|
|
679
|
+
shell: true
|
|
706
680
|
})
|
|
707
681
|
|
|
708
682
|
// 📖 Wait for OpenCode to exit
|
|
709
683
|
await new Promise((resolve, reject) => {
|
|
710
684
|
child.on('exit', resolve)
|
|
711
|
-
child.on('error',
|
|
685
|
+
child.on('error', (err) => {
|
|
686
|
+
if (err.code === 'ENOENT') {
|
|
687
|
+
console.error(chalk.red('\n ✗ Could not find "opencode" — is it installed and in your PATH?'))
|
|
688
|
+
console.error(chalk.dim(' Install: npm i -g opencode or see https://opencode.ai'))
|
|
689
|
+
resolve(1)
|
|
690
|
+
} else {
|
|
691
|
+
reject(err)
|
|
692
|
+
}
|
|
693
|
+
})
|
|
712
694
|
})
|
|
713
695
|
}
|
|
714
696
|
}
|
|
@@ -755,6 +737,15 @@ async function startOpenClaw(model, apiKey) {
|
|
|
755
737
|
console.log(chalk.dim(` 💾 Backup: ${backupPath}`))
|
|
756
738
|
}
|
|
757
739
|
|
|
740
|
+
// 📖 Patch models.json to add all NVIDIA models (fixes "not allowed" errors)
|
|
741
|
+
const patchResult = patchOpenClawModelsJson()
|
|
742
|
+
if (patchResult.wasPatched) {
|
|
743
|
+
console.log(chalk.dim(` ✨ Added ${patchResult.added} NVIDIA models to allowlist (${patchResult.total} total)`))
|
|
744
|
+
if (patchResult.backup) {
|
|
745
|
+
console.log(chalk.dim(` 💾 models.json backup: ${patchResult.backup}`))
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
758
749
|
// 📖 Ensure models.providers section exists with nvidia NIM block.
|
|
759
750
|
// 📖 Per OpenClaw docs (docs.openclaw.ai/providers/nvidia), providers MUST be nested under
|
|
760
751
|
// 📖 "models.providers", NOT at the config root. Root-level "providers" is ignored by OpenClaw.
|
|
@@ -781,6 +772,8 @@ async function startOpenClaw(model, apiKey) {
|
|
|
781
772
|
}
|
|
782
773
|
}
|
|
783
774
|
|
|
775
|
+
// 📖 Set as the default primary model for all agents.
|
|
776
|
+
// 📖 Format: "provider/model-id" — e.g. "nvidia/deepseek-ai/deepseek-v3.2"
|
|
784
777
|
// 📖 Set as the default primary model for all agents.
|
|
785
778
|
// 📖 Format: "provider/model-id" — e.g. "nvidia/deepseek-ai/deepseek-v3.2"
|
|
786
779
|
if (!config.agents) config.agents = {}
|
|
@@ -788,6 +781,12 @@ async function startOpenClaw(model, apiKey) {
|
|
|
788
781
|
if (!config.agents.defaults.model) config.agents.defaults.model = {}
|
|
789
782
|
config.agents.defaults.model.primary = `nvidia/${model.modelId}`
|
|
790
783
|
|
|
784
|
+
// 📖 REQUIRED: OpenClaw requires the model to be explicitly listed in agents.defaults.models
|
|
785
|
+
// 📖 (the allowlist). Without this entry, OpenClaw rejects the model with "not allowed".
|
|
786
|
+
// 📖 See: https://docs.openclaw.ai/gateway/configuration-reference
|
|
787
|
+
if (!config.agents.defaults.models) config.agents.defaults.models = {}
|
|
788
|
+
config.agents.defaults.models[`nvidia/${model.modelId}`] = {}
|
|
789
|
+
|
|
791
790
|
saveOpenClawConfig(config)
|
|
792
791
|
|
|
793
792
|
console.log(chalk.rgb(255, 140, 0)(` ✓ Default model set to: nvidia/${model.modelId}`))
|
|
@@ -804,27 +803,7 @@ async function startOpenClaw(model, apiKey) {
|
|
|
804
803
|
}
|
|
805
804
|
|
|
806
805
|
// ─── Helper function to find best model after analysis ────────────────────────
|
|
807
|
-
|
|
808
|
-
// 📖 Sort by avg ping (fastest first), then by uptime percentage (most reliable)
|
|
809
|
-
const sorted = [...results].sort((a, b) => {
|
|
810
|
-
const avgA = getAvg(a)
|
|
811
|
-
const avgB = getAvg(b)
|
|
812
|
-
const uptimeA = getUptime(a)
|
|
813
|
-
const uptimeB = getUptime(b)
|
|
814
|
-
|
|
815
|
-
// 📖 Priority 1: Models that are up (status === 'up')
|
|
816
|
-
if (a.status === 'up' && b.status !== 'up') return -1
|
|
817
|
-
if (a.status !== 'up' && b.status === 'up') return 1
|
|
818
|
-
|
|
819
|
-
// 📖 Priority 2: Fastest average ping
|
|
820
|
-
if (avgA !== avgB) return avgA - avgB
|
|
821
|
-
|
|
822
|
-
// 📖 Priority 3: Highest uptime percentage
|
|
823
|
-
return uptimeB - uptimeA
|
|
824
|
-
})
|
|
825
|
-
|
|
826
|
-
return sorted.length > 0 ? sorted[0] : null
|
|
827
|
-
}
|
|
806
|
+
// 📖 findBestModel is imported from lib/utils.js
|
|
828
807
|
|
|
829
808
|
// ─── Function to run in fiable mode (10-second analysis then output best model) ──
|
|
830
809
|
async function runFiableMode(apiKey) {
|
|
@@ -883,64 +862,28 @@ async function runFiableMode(apiKey) {
|
|
|
883
862
|
process.exit(0)
|
|
884
863
|
}
|
|
885
864
|
|
|
886
|
-
//
|
|
887
|
-
// 📖
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
// 📖 --tier C → includes C only
|
|
892
|
-
const TIER_LETTER_MAP = {
|
|
893
|
-
'S': ['S+', 'S'],
|
|
894
|
-
'A': ['A+', 'A', 'A-'],
|
|
895
|
-
'B': ['B+', 'B'],
|
|
896
|
-
'C': ['C'],
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
function filterByTier(results, tierLetter) {
|
|
900
|
-
const letter = tierLetter.toUpperCase()
|
|
901
|
-
const allowed = TIER_LETTER_MAP[letter]
|
|
902
|
-
if (!allowed) {
|
|
865
|
+
// 📖 filterByTier and TIER_LETTER_MAP are imported from lib/utils.js
|
|
866
|
+
// 📖 Wrapper that exits on invalid tier (utils version returns null instead)
|
|
867
|
+
function filterByTierOrExit(results, tierLetter) {
|
|
868
|
+
const filtered = filterByTier(results, tierLetter)
|
|
869
|
+
if (filtered === null) {
|
|
903
870
|
console.error(chalk.red(` ✖ Unknown tier "${tierLetter}". Valid tiers: S, A, B, C`))
|
|
904
871
|
process.exit(1)
|
|
905
872
|
}
|
|
906
|
-
return
|
|
873
|
+
return filtered
|
|
907
874
|
}
|
|
908
875
|
|
|
909
876
|
async function main() {
|
|
910
|
-
// 📖 Parse CLI arguments
|
|
911
|
-
const
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
let apiKey = null
|
|
915
|
-
const flags = []
|
|
916
|
-
|
|
917
|
-
for (const arg of args) {
|
|
918
|
-
if (arg.startsWith('--')) {
|
|
919
|
-
flags.push(arg.toLowerCase())
|
|
920
|
-
} else if (!apiKey) {
|
|
921
|
-
apiKey = arg
|
|
922
|
-
}
|
|
923
|
-
}
|
|
877
|
+
// 📖 Parse CLI arguments using shared parseArgs utility
|
|
878
|
+
const parsed = parseArgs(process.argv)
|
|
879
|
+
let apiKey = parsed.apiKey
|
|
880
|
+
const { bestMode, fiableMode, openCodeMode, openClawMode, tierFilter } = parsed
|
|
924
881
|
|
|
925
882
|
// 📖 Priority: CLI arg > env var > saved config > wizard
|
|
926
883
|
if (!apiKey) {
|
|
927
884
|
apiKey = process.env.NVIDIA_API_KEY || loadApiKey()
|
|
928
885
|
}
|
|
929
886
|
|
|
930
|
-
// 📖 Check for CLI flags
|
|
931
|
-
const bestMode = flags.includes('--best')
|
|
932
|
-
const fiableMode = flags.includes('--fiable')
|
|
933
|
-
const openCodeMode = flags.includes('--opencode')
|
|
934
|
-
const openClawMode = flags.includes('--openclaw')
|
|
935
|
-
|
|
936
|
-
// 📖 Parse --tier X flag (e.g. --tier S, --tier A)
|
|
937
|
-
// 📖 Find "--tier" in flags array, then get the next raw arg as the tier value
|
|
938
|
-
let tierFilter = null
|
|
939
|
-
const tierIdx = args.findIndex(a => a.toLowerCase() === '--tier')
|
|
940
|
-
if (tierIdx !== -1 && args[tierIdx + 1] && !args[tierIdx + 1].startsWith('--')) {
|
|
941
|
-
tierFilter = args[tierIdx + 1].toUpperCase()
|
|
942
|
-
}
|
|
943
|
-
|
|
944
887
|
if (!apiKey) {
|
|
945
888
|
apiKey = await promptApiKey()
|
|
946
889
|
if (!apiKey) {
|
|
@@ -957,6 +900,9 @@ async function main() {
|
|
|
957
900
|
await runFiableMode(apiKey)
|
|
958
901
|
}
|
|
959
902
|
|
|
903
|
+
// 📖 Check for available update (non-blocking, 5s timeout)
|
|
904
|
+
const latestVersion = await checkForUpdate()
|
|
905
|
+
|
|
960
906
|
// 📖 Determine active mode:
|
|
961
907
|
// --opencode → opencode
|
|
962
908
|
// --openclaw → openclaw
|
|
@@ -968,7 +914,18 @@ async function main() {
|
|
|
968
914
|
mode = 'opencode'
|
|
969
915
|
} else {
|
|
970
916
|
// 📖 No mode flag given — ask user with the startup menu
|
|
971
|
-
mode = await promptModeSelection()
|
|
917
|
+
mode = await promptModeSelection(latestVersion)
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// 📖 Handle "update now" selection from the menu
|
|
921
|
+
if (mode === 'update') {
|
|
922
|
+
runUpdate()
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// 📖 When using flags (--opencode/--openclaw), show update warning in terminal
|
|
926
|
+
if (latestVersion && (openCodeMode || openClawMode)) {
|
|
927
|
+
console.log(chalk.red(` ⚠ New version available (v${latestVersion}), please run npm i -g free-coding-models to install`))
|
|
928
|
+
console.log()
|
|
972
929
|
}
|
|
973
930
|
|
|
974
931
|
// 📖 Filter models to only show top tiers if BEST mode is active
|
|
@@ -985,7 +942,7 @@ async function main() {
|
|
|
985
942
|
|
|
986
943
|
// 📖 Apply tier letter filter if --tier X was given
|
|
987
944
|
if (tierFilter) {
|
|
988
|
-
results =
|
|
945
|
+
results = filterByTierOrExit(results, tierFilter)
|
|
989
946
|
}
|
|
990
947
|
|
|
991
948
|
// 📖 Add interactive selection state - cursor index and user's choice
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "free-coding-models",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
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",
|
|
@@ -41,11 +41,14 @@
|
|
|
41
41
|
"files": [
|
|
42
42
|
"bin/",
|
|
43
43
|
"sources.js",
|
|
44
|
+
"patch-openclaw.js",
|
|
45
|
+
"patch-openclaw-models.js",
|
|
44
46
|
"README.md",
|
|
45
47
|
"LICENSE"
|
|
46
48
|
],
|
|
47
49
|
"scripts": {
|
|
48
|
-
"start": "node bin/free-coding-models.js"
|
|
50
|
+
"start": "node bin/free-coding-models.js",
|
|
51
|
+
"test": "node --test test/test.js"
|
|
49
52
|
},
|
|
50
53
|
"dependencies": {
|
|
51
54
|
"chalk": "^5.4.1"
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @file patch-openclaw-models.js
|
|
4
|
+
* @description Helper function to patch OpenClaw's models.json with all NVIDIA models
|
|
5
|
+
*
|
|
6
|
+
* This is imported by bin/free-coding-models.js and called automatically
|
|
7
|
+
* when setting a model in OpenClaw mode.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readFileSync, writeFileSync, existsSync, copyFileSync } from 'fs'
|
|
11
|
+
import { homedir } from 'os'
|
|
12
|
+
import { join } from 'path'
|
|
13
|
+
import { nvidiaNim } from './sources.js'
|
|
14
|
+
|
|
15
|
+
const MODELS_JSON = join(homedir(), '.openclaw', 'agents', 'main', 'agent', 'models.json')
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Patch models.json to add all NVIDIA models from sources.js
|
|
19
|
+
* @returns {Object} { added: number, total: number, wasPatched: boolean }
|
|
20
|
+
*/
|
|
21
|
+
export function patchOpenClawModelsJson() {
|
|
22
|
+
// Read existing config
|
|
23
|
+
let modelsConfig
|
|
24
|
+
if (!existsSync(MODELS_JSON)) {
|
|
25
|
+
return { added: 0, total: 0, wasPatched: false, error: 'models.json not found' }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
modelsConfig = JSON.parse(readFileSync(MODELS_JSON, 'utf8'))
|
|
30
|
+
} catch (err) {
|
|
31
|
+
return { added: 0, total: 0, wasPatched: false, error: err.message }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Ensure nvidia provider exists
|
|
35
|
+
if (!modelsConfig.providers) modelsConfig.providers = {}
|
|
36
|
+
if (!modelsConfig.providers.nvidia) {
|
|
37
|
+
modelsConfig.providers.nvidia = {
|
|
38
|
+
baseUrl: 'https://integrate.api.nvidia.com/v1',
|
|
39
|
+
api: 'openai-completions',
|
|
40
|
+
models: []
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Get existing model IDs
|
|
45
|
+
const existingModelIds = new Set(modelsConfig.providers.nvidia.models.map(m => m.id))
|
|
46
|
+
|
|
47
|
+
// Helper to get model config by tier
|
|
48
|
+
function getModelConfig(tier) {
|
|
49
|
+
if (tier === 'S+' || tier === 'S') {
|
|
50
|
+
return { contextWindow: 128000, maxTokens: 8192 }
|
|
51
|
+
}
|
|
52
|
+
if (tier === 'A+') {
|
|
53
|
+
return { contextWindow: 131072, maxTokens: 4096 }
|
|
54
|
+
}
|
|
55
|
+
if (tier === 'A' || tier === 'A-') {
|
|
56
|
+
return { contextWindow: 131072, maxTokens: 4096 }
|
|
57
|
+
}
|
|
58
|
+
return { contextWindow: 32768, maxTokens: 2048 }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Add all models from sources.js
|
|
62
|
+
let addedCount = 0
|
|
63
|
+
for (const [modelId, label, tier] of nvidiaNim) {
|
|
64
|
+
if (existingModelIds.has(modelId)) {
|
|
65
|
+
continue // Skip already existing models
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const config = getModelConfig(tier)
|
|
69
|
+
const isThinking = modelId.includes('thinking')
|
|
70
|
+
|
|
71
|
+
modelsConfig.providers.nvidia.models.push({
|
|
72
|
+
id: modelId,
|
|
73
|
+
name: label,
|
|
74
|
+
contextWindow: config.contextWindow,
|
|
75
|
+
maxTokens: config.maxTokens,
|
|
76
|
+
reasoning: isThinking,
|
|
77
|
+
input: ['text'],
|
|
78
|
+
cost: {
|
|
79
|
+
input: 0,
|
|
80
|
+
output: 0,
|
|
81
|
+
cacheRead: 0,
|
|
82
|
+
cacheWrite: 0
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
addedCount++
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Only write if we added something
|
|
90
|
+
if (addedCount > 0) {
|
|
91
|
+
// Backup
|
|
92
|
+
const backupPath = `${MODELS_JSON}.backup-${Date.now()}`
|
|
93
|
+
copyFileSync(MODELS_JSON, backupPath)
|
|
94
|
+
|
|
95
|
+
// Write updated config
|
|
96
|
+
writeFileSync(MODELS_JSON, JSON.stringify(modelsConfig, null, 2))
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
added: addedCount,
|
|
100
|
+
total: modelsConfig.providers.nvidia.models.length,
|
|
101
|
+
wasPatched: true,
|
|
102
|
+
backup: backupPath
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
added: 0,
|
|
108
|
+
total: modelsConfig.providers.nvidia.models.length,
|
|
109
|
+
wasPatched: false
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @file patch-openclaw.js
|
|
4
|
+
* @description Patch OpenClaw to allow all NVIDIA models from free-coding-models
|
|
5
|
+
*
|
|
6
|
+
* This script adds ALL models from sources.js to OpenClaw's allowlist
|
|
7
|
+
* so any NVIDIA model can be used without "not allowed" errors.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs'
|
|
11
|
+
import { homedir } from 'os'
|
|
12
|
+
import { join } from 'path'
|
|
13
|
+
import { nvidiaNim } from './sources.js'
|
|
14
|
+
|
|
15
|
+
const MODELS_JSON = join(homedir(), '.openclaw', 'agents', 'main', 'agent', 'models.json')
|
|
16
|
+
const OPENCLAW_JSON = join(homedir(), '.openclaw', 'openclaw.json')
|
|
17
|
+
|
|
18
|
+
console.log('🦞 Patching OpenClaw for full NVIDIA model support...\n')
|
|
19
|
+
|
|
20
|
+
// ─── Helper functions ───────────────────────────────────────────────────────────
|
|
21
|
+
function getModelConfig(tier) {
|
|
22
|
+
// S+/S tier: largest context
|
|
23
|
+
if (tier === 'S+' || tier === 'S') {
|
|
24
|
+
return { contextWindow: 128000, maxTokens: 8192 }
|
|
25
|
+
}
|
|
26
|
+
// A+ tier
|
|
27
|
+
if (tier === 'A+') {
|
|
28
|
+
return { contextWindow: 131072, maxTokens: 4096 }
|
|
29
|
+
}
|
|
30
|
+
// A/A- tier
|
|
31
|
+
if (tier === 'A' || tier === 'A-') {
|
|
32
|
+
return { contextWindow: 131072, maxTokens: 4096 }
|
|
33
|
+
}
|
|
34
|
+
// B+/B/C tier: smaller context
|
|
35
|
+
return { contextWindow: 32768, maxTokens: 2048 }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ─── Patch models.json ──────────────────────────────────────────────────────────
|
|
39
|
+
console.log('📄 Patching models.json...')
|
|
40
|
+
|
|
41
|
+
let modelsConfig
|
|
42
|
+
if (existsSync(MODELS_JSON)) {
|
|
43
|
+
try {
|
|
44
|
+
modelsConfig = JSON.parse(readFileSync(MODELS_JSON, 'utf8'))
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.error(' ✖ Failed to parse models.json:', err.message)
|
|
47
|
+
process.exit(1)
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
console.error(' ✖ models.json not found at:', MODELS_JSON)
|
|
51
|
+
process.exit(1)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Backup
|
|
55
|
+
const backupPath = `${MODELS_JSON}.backup-${Date.now()}`
|
|
56
|
+
writeFileSync(backupPath, readFileSync(MODELS_JSON))
|
|
57
|
+
console.log(` 💾 Backup: ${backupPath}`)
|
|
58
|
+
|
|
59
|
+
// Ensure nvidia provider exists
|
|
60
|
+
if (!modelsConfig.providers) modelsConfig.providers = {}
|
|
61
|
+
if (!modelsConfig.providers.nvidia) {
|
|
62
|
+
modelsConfig.providers.nvidia = {
|
|
63
|
+
baseUrl: 'https://integrate.api.nvidia.com/v1',
|
|
64
|
+
api: 'openai-completions',
|
|
65
|
+
models: []
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Get existing model IDs
|
|
70
|
+
const existingModelIds = new Set(modelsConfig.providers.nvidia.models.map(m => m.id))
|
|
71
|
+
|
|
72
|
+
// Add all models from sources.js
|
|
73
|
+
let addedCount = 0
|
|
74
|
+
for (const [modelId, label, tier] of nvidiaNim) {
|
|
75
|
+
if (existingModelIds.has(modelId)) {
|
|
76
|
+
continue // Skip already existing models
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const config = getModelConfig(tier)
|
|
80
|
+
const isThinking = modelId.includes('thinking')
|
|
81
|
+
|
|
82
|
+
modelsConfig.providers.nvidia.models.push({
|
|
83
|
+
id: modelId,
|
|
84
|
+
name: label,
|
|
85
|
+
contextWindow: config.contextWindow,
|
|
86
|
+
maxTokens: config.maxTokens,
|
|
87
|
+
reasoning: isThinking,
|
|
88
|
+
input: ['text'],
|
|
89
|
+
cost: {
|
|
90
|
+
input: 0,
|
|
91
|
+
output: 0,
|
|
92
|
+
cacheRead: 0,
|
|
93
|
+
cacheWrite: 0
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
addedCount++
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Write back
|
|
101
|
+
writeFileSync(MODELS_JSON, JSON.stringify(modelsConfig, null, 2))
|
|
102
|
+
console.log(` ✅ Added ${addedCount} models to models.json`)
|
|
103
|
+
console.log(` 📊 Total NVIDIA models: ${modelsConfig.providers.nvidia.models.length}`)
|
|
104
|
+
|
|
105
|
+
// ─── Patch openclaw.json ────────────────────────────────────────────────────────
|
|
106
|
+
console.log('\n📄 Patching openclaw.json...')
|
|
107
|
+
|
|
108
|
+
let openclawConfig
|
|
109
|
+
if (existsSync(OPENCLAW_JSON)) {
|
|
110
|
+
try {
|
|
111
|
+
openclawConfig = JSON.parse(readFileSync(OPENCLAW_JSON, 'utf8'))
|
|
112
|
+
} catch (err) {
|
|
113
|
+
console.error(' ✖ Failed to parse openclaw.json:', err.message)
|
|
114
|
+
process.exit(1)
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
console.error(' ✖ openclaw.json not found at:', OPENCLAW_JSON)
|
|
118
|
+
process.exit(1)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Backup
|
|
122
|
+
const openclawBackupPath = `${OPENCLAW_JSON}.backup-${Date.now()}`
|
|
123
|
+
writeFileSync(openclawBackupPath, readFileSync(OPENCLAW_JSON))
|
|
124
|
+
console.log(` 💾 Backup: ${openclawBackupPath}`)
|
|
125
|
+
|
|
126
|
+
// Ensure models.providers.nvidia exists
|
|
127
|
+
if (!openclawConfig.models) openclawConfig.models = {}
|
|
128
|
+
if (!openclawConfig.models.providers) openclawConfig.models.providers = {}
|
|
129
|
+
if (!openclawConfig.models.providers.nvidia) {
|
|
130
|
+
openclawConfig.models.providers.nvidia = {
|
|
131
|
+
baseUrl: 'https://integrate.api.nvidia.com/v1',
|
|
132
|
+
api: 'openai-completions',
|
|
133
|
+
models: []
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Get existing model IDs in openclaw.json
|
|
138
|
+
const existingOpenClawModelIds = new Set(
|
|
139
|
+
(openclawConfig.models.providers.nvidia.models || []).map(m => m.id)
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
// Add all models (simplified config for openclaw.json)
|
|
143
|
+
let addedOpenClawCount = 0
|
|
144
|
+
for (const [modelId, label, tier] of nvidiaNim) {
|
|
145
|
+
if (existingOpenClawModelIds.has(modelId)) {
|
|
146
|
+
continue
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const config = getModelConfig(tier)
|
|
150
|
+
|
|
151
|
+
openclawConfig.models.providers.nvidia.models.push({
|
|
152
|
+
id: modelId,
|
|
153
|
+
name: label,
|
|
154
|
+
contextWindow: config.contextWindow,
|
|
155
|
+
maxTokens: config.maxTokens
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
addedOpenClawCount++
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Write back
|
|
162
|
+
writeFileSync(OPENCLAW_JSON, JSON.stringify(openclawConfig, null, 2))
|
|
163
|
+
console.log(` ✅ Added ${addedOpenClawCount} models to openclaw.json`)
|
|
164
|
+
console.log(` 📊 Total NVIDIA models: ${openclawConfig.models.providers.nvidia.models.length}`)
|
|
165
|
+
|
|
166
|
+
// ─── Summary ────────────────────────────────────────────────────────────────────
|
|
167
|
+
console.log('\n✨ Patch complete!')
|
|
168
|
+
console.log('\n💡 Next steps:')
|
|
169
|
+
console.log(' 1. Restart OpenClaw gateway: systemctl --user restart openclaw-gateway')
|
|
170
|
+
console.log(' 2. Test with: free-coding-models --openclaw')
|
|
171
|
+
console.log(' 3. Select any model - no more "not allowed" errors!')
|
|
172
|
+
console.log()
|