free-coding-models 0.1.48 β 0.1.50
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 +3 -2
- package/bin/free-coding-models.js +249 -136
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,7 +61,8 @@
|
|
|
61
61
|
- **π» OpenCode integration** β Auto-detects NIM setup, sets model as default, launches OpenCode
|
|
62
62
|
- **π¦ OpenClaw integration** β Sets selected model as default provider in `~/.openclaw/openclaw.json`
|
|
63
63
|
- **π¨ Clean output** β Zero scrollback pollution, interface stays open until Ctrl+C
|
|
64
|
-
- **πΆ Status indicators** β UP β
Β· Timeout β³ Β· Overloaded π₯ Β· Not Found π«
|
|
64
|
+
- **πΆ Status indicators** β UP β
Β· No Key π Β· Timeout β³ Β· Overloaded π₯ Β· Not Found π«
|
|
65
|
+
- **π Keyless latency** β Models are pinged even without an API key β a `π NO KEY` status confirms the server is reachable with real latency shown, so you can compare providers before committing to a key
|
|
65
66
|
- **π· Tier filtering** β Filter models by tier letter (S, A, B, C) with `--tier` flag or dynamically with `T` key
|
|
66
67
|
|
|
67
68
|
---
|
|
@@ -78,7 +79,7 @@ Before using `free-coding-models`, make sure you have:
|
|
|
78
79
|
3. **OpenCode** *(optional)* β [Install OpenCode](https://github.com/opencode-ai/opencode) to use the OpenCode integration
|
|
79
80
|
4. **OpenClaw** *(optional)* β [Install OpenClaw](https://openclaw.ai) to use the OpenClaw integration
|
|
80
81
|
|
|
81
|
-
> π‘ **Tip:** You don't need all three providers. One key is enough to get started. Add more later via the Settings screen (`P` key).
|
|
82
|
+
> π‘ **Tip:** You don't need all three 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.
|
|
82
83
|
|
|
83
84
|
---
|
|
84
85
|
|
|
@@ -574,7 +574,7 @@ function renderTable(results, pendingPings, frame, cursor = null, sortColumn = '
|
|
|
574
574
|
: chalk.dim(ctxRaw.padEnd(W_CTX))
|
|
575
575
|
|
|
576
576
|
// π Latest ping - pings are objects: { ms, code }
|
|
577
|
-
// π
|
|
577
|
+
// π Show response time for 200 (success) and 401 (no-auth but server is reachable)
|
|
578
578
|
const latestPing = r.pings.length > 0 ? r.pings[r.pings.length - 1] : null
|
|
579
579
|
let pingCell
|
|
580
580
|
if (!latestPing) {
|
|
@@ -583,6 +583,9 @@ function renderTable(results, pendingPings, frame, cursor = null, sortColumn = '
|
|
|
583
583
|
// π Success - show response time
|
|
584
584
|
const str = String(latestPing.ms).padEnd(W_PING)
|
|
585
585
|
pingCell = latestPing.ms < 500 ? chalk.greenBright(str) : latestPing.ms < 1500 ? chalk.yellow(str) : chalk.red(str)
|
|
586
|
+
} else if (latestPing.code === '401') {
|
|
587
|
+
// π 401 = no API key but server IS reachable β still show latency in dim
|
|
588
|
+
pingCell = chalk.dim(String(latestPing.ms).padEnd(W_PING))
|
|
586
589
|
} else {
|
|
587
590
|
// π Error or timeout - show "β" (error code is already in Status column)
|
|
588
591
|
pingCell = chalk.dim('β'.padEnd(W_PING))
|
|
@@ -601,7 +604,11 @@ function renderTable(results, pendingPings, frame, cursor = null, sortColumn = '
|
|
|
601
604
|
// π Status column - build plain text with emoji, pad, then colorize
|
|
602
605
|
// π Different emojis for different error codes
|
|
603
606
|
let statusText, statusColor
|
|
604
|
-
if (r.status === '
|
|
607
|
+
if (r.status === 'noauth') {
|
|
608
|
+
// π Server responded but needs an API key β shown dimly since it IS reachable
|
|
609
|
+
statusText = `π NO KEY`
|
|
610
|
+
statusColor = (s) => chalk.dim(s)
|
|
611
|
+
} else if (r.status === 'pending') {
|
|
605
612
|
statusText = `${FRAMES[frame % FRAMES.length]} wait`
|
|
606
613
|
statusColor = (s) => chalk.dim.yellow(s)
|
|
607
614
|
} else if (r.status === 'up') {
|
|
@@ -718,14 +725,19 @@ function renderTable(results, pendingPings, frame, cursor = null, sortColumn = '
|
|
|
718
725
|
|
|
719
726
|
// π ping: Send a single chat completion request to measure model availability and latency.
|
|
720
727
|
// π url param is the provider's endpoint URL β differs per provider (NIM, Groq, Cerebras).
|
|
728
|
+
// π apiKey can be null β in that case no Authorization header is sent.
|
|
729
|
+
// π A 401 response still tells us the server is UP and gives us real latency.
|
|
721
730
|
async function ping(apiKey, modelId, url) {
|
|
722
731
|
const ctrl = new AbortController()
|
|
723
732
|
const timer = setTimeout(() => ctrl.abort(), PING_TIMEOUT)
|
|
724
733
|
const t0 = performance.now()
|
|
725
734
|
try {
|
|
735
|
+
// π Only attach Authorization header when a key is available
|
|
736
|
+
const headers = { 'Content-Type': 'application/json' }
|
|
737
|
+
if (apiKey) headers['Authorization'] = `Bearer ${apiKey}`
|
|
726
738
|
const resp = await fetch(url, {
|
|
727
739
|
method: 'POST', signal: ctrl.signal,
|
|
728
|
-
headers
|
|
740
|
+
headers,
|
|
729
741
|
body: JSON.stringify({ model: modelId, messages: [{ role: 'user', content: 'hi' }], max_tokens: 1 }),
|
|
730
742
|
})
|
|
731
743
|
return { code: String(resp.status), ms: Math.round(performance.now() - t0) }
|
|
@@ -798,88 +810,87 @@ function checkNvidiaNimConfig() {
|
|
|
798
810
|
}
|
|
799
811
|
|
|
800
812
|
// βββ Start OpenCode ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
801
|
-
// π Launches OpenCode with the selected
|
|
802
|
-
// π
|
|
803
|
-
// π
|
|
813
|
+
// π Launches OpenCode with the selected model.
|
|
814
|
+
// π Handles all 3 providers: nvidia (needs custom provider config), groq & cerebras (built-in in OpenCode).
|
|
815
|
+
// π For nvidia: checks if NIM is configured, sets provider.models entry, spawns with nvidia/model-id.
|
|
816
|
+
// π For groq/cerebras: OpenCode has built-in support β just sets model in config and spawns.
|
|
817
|
+
// π Model format: { modelId, label, tier, providerKey }
|
|
804
818
|
async function startOpenCode(model) {
|
|
805
|
-
const
|
|
819
|
+
const providerKey = model.providerKey ?? 'nvidia'
|
|
820
|
+
// π Full model reference string used in OpenCode config and --model flag
|
|
821
|
+
const modelRef = `${providerKey}/${model.modelId}`
|
|
806
822
|
|
|
807
|
-
if (
|
|
808
|
-
// π NVIDIA NIM
|
|
809
|
-
|
|
810
|
-
console.log(chalk.dim(` Model: nvidia/${model.modelId}`))
|
|
811
|
-
console.log()
|
|
823
|
+
if (providerKey === 'nvidia') {
|
|
824
|
+
// π NVIDIA NIM needs a custom provider block in OpenCode config (not built-in)
|
|
825
|
+
const hasNim = checkNvidiaNimConfig()
|
|
812
826
|
|
|
813
|
-
|
|
814
|
-
|
|
827
|
+
if (hasNim) {
|
|
828
|
+
console.log(chalk.green(` π Setting ${chalk.bold(model.label)} as defaultβ¦`))
|
|
829
|
+
console.log(chalk.dim(` Model: ${modelRef}`))
|
|
830
|
+
console.log()
|
|
815
831
|
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
832
|
+
const config = loadOpenCodeConfig()
|
|
833
|
+
const backupPath = `${getOpenCodeConfigPath()}.backup-${Date.now()}`
|
|
834
|
+
|
|
835
|
+
if (existsSync(getOpenCodeConfigPath())) {
|
|
836
|
+
copyFileSync(getOpenCodeConfigPath(), backupPath)
|
|
837
|
+
console.log(chalk.dim(` πΎ Backup: ${backupPath}`))
|
|
838
|
+
}
|
|
821
839
|
|
|
822
|
-
|
|
823
|
-
config.model = `nvidia/${model.modelId}`
|
|
840
|
+
config.model = modelRef
|
|
824
841
|
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
name: model.label,
|
|
842
|
+
// π Register the model in the nvidia provider's models section
|
|
843
|
+
// π OpenCode requires models to be explicitly listed in provider.models
|
|
844
|
+
// π to recognize them β without this, it falls back to the previous default
|
|
845
|
+
if (config.provider?.nvidia) {
|
|
846
|
+
if (!config.provider.nvidia.models) config.provider.nvidia.models = {}
|
|
847
|
+
config.provider.nvidia.models[model.modelId] = { name: model.label }
|
|
832
848
|
}
|
|
833
|
-
}
|
|
834
849
|
|
|
835
|
-
|
|
850
|
+
saveOpenCodeConfig(config)
|
|
836
851
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
console.log()
|
|
842
|
-
|
|
843
|
-
if (savedConfig.model === config.model) {
|
|
844
|
-
console.log(chalk.green(` β Default model set to: nvidia/${model.modelId}`))
|
|
845
|
-
} else {
|
|
846
|
-
console.log(chalk.yellow(` β Config might not have been saved correctly`))
|
|
847
|
-
}
|
|
848
|
-
console.log()
|
|
849
|
-
console.log(chalk.dim(' Starting OpenCodeβ¦'))
|
|
850
|
-
console.log()
|
|
852
|
+
const savedConfig = loadOpenCodeConfig()
|
|
853
|
+
console.log(chalk.dim(` π Config saved to: ${getOpenCodeConfigPath()}`))
|
|
854
|
+
console.log(chalk.dim(` π Default model in config: ${savedConfig.model || 'NOT SET'}`))
|
|
855
|
+
console.log()
|
|
851
856
|
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
857
|
+
if (savedConfig.model === config.model) {
|
|
858
|
+
console.log(chalk.green(` β Default model set to: ${modelRef}`))
|
|
859
|
+
} else {
|
|
860
|
+
console.log(chalk.yellow(` β Config might not have been saved correctly`))
|
|
861
|
+
}
|
|
862
|
+
console.log()
|
|
863
|
+
console.log(chalk.dim(' Starting OpenCodeβ¦'))
|
|
864
|
+
console.log()
|
|
860
865
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
console.error(chalk.red('\n β Could not find "opencode" β is it installed and in your PATH?'))
|
|
867
|
-
console.error(chalk.dim(' Install: npm i -g opencode or see https://opencode.ai'))
|
|
868
|
-
resolve(1)
|
|
869
|
-
} else {
|
|
870
|
-
reject(err)
|
|
871
|
-
}
|
|
866
|
+
const { spawn } = await import('child_process')
|
|
867
|
+
const child = spawn('opencode', ['--model', modelRef], {
|
|
868
|
+
stdio: 'inherit',
|
|
869
|
+
shell: true,
|
|
870
|
+
detached: false
|
|
872
871
|
})
|
|
873
|
-
})
|
|
874
|
-
} else {
|
|
875
|
-
// π NVIDIA NIM not configured - show install prompt and launch
|
|
876
|
-
console.log(chalk.yellow(' β NVIDIA NIM not configured in OpenCode'))
|
|
877
|
-
console.log()
|
|
878
|
-
console.log(chalk.dim(' Starting OpenCode with installation promptβ¦'))
|
|
879
|
-
console.log()
|
|
880
872
|
|
|
881
|
-
|
|
882
|
-
|
|
873
|
+
await new Promise((resolve, reject) => {
|
|
874
|
+
child.on('exit', resolve)
|
|
875
|
+
child.on('error', (err) => {
|
|
876
|
+
if (err.code === 'ENOENT') {
|
|
877
|
+
console.error(chalk.red('\n β Could not find "opencode" β is it installed and in your PATH?'))
|
|
878
|
+
console.error(chalk.dim(' Install: npm i -g opencode or see https://opencode.ai'))
|
|
879
|
+
resolve(1)
|
|
880
|
+
} else {
|
|
881
|
+
reject(err)
|
|
882
|
+
}
|
|
883
|
+
})
|
|
884
|
+
})
|
|
885
|
+
} else {
|
|
886
|
+
// π NVIDIA NIM not configured β show install prompt
|
|
887
|
+
console.log(chalk.yellow(' β NVIDIA NIM not configured in OpenCode'))
|
|
888
|
+
console.log()
|
|
889
|
+
console.log(chalk.dim(' Starting OpenCode with installation promptβ¦'))
|
|
890
|
+
console.log()
|
|
891
|
+
|
|
892
|
+
const configPath = getOpenCodeConfigPath()
|
|
893
|
+
const installPrompt = `Please install NVIDIA NIM provider in OpenCode by adding this to ${configPath}:
|
|
883
894
|
|
|
884
895
|
{
|
|
885
896
|
"provider": {
|
|
@@ -896,21 +907,73 @@ async function startOpenCode(model) {
|
|
|
896
907
|
|
|
897
908
|
${isWindows ? 'set NVIDIA_API_KEY=your_key_here' : 'export NVIDIA_API_KEY=your_key_here'}
|
|
898
909
|
|
|
899
|
-
After installation, you can use: opencode --model
|
|
910
|
+
After installation, you can use: opencode --model ${modelRef}`
|
|
911
|
+
|
|
912
|
+
console.log(chalk.cyan(installPrompt))
|
|
913
|
+
console.log()
|
|
914
|
+
console.log(chalk.dim(' Starting OpenCodeβ¦'))
|
|
915
|
+
console.log()
|
|
916
|
+
|
|
917
|
+
const { spawn } = await import('child_process')
|
|
918
|
+
const child = spawn('opencode', [], {
|
|
919
|
+
stdio: 'inherit',
|
|
920
|
+
shell: true,
|
|
921
|
+
detached: false
|
|
922
|
+
})
|
|
923
|
+
|
|
924
|
+
await new Promise((resolve, reject) => {
|
|
925
|
+
child.on('exit', resolve)
|
|
926
|
+
child.on('error', (err) => {
|
|
927
|
+
if (err.code === 'ENOENT') {
|
|
928
|
+
console.error(chalk.red('\n β Could not find "opencode" β is it installed and in your PATH?'))
|
|
929
|
+
console.error(chalk.dim(' Install: npm i -g opencode or see https://opencode.ai'))
|
|
930
|
+
resolve(1)
|
|
931
|
+
} else {
|
|
932
|
+
reject(err)
|
|
933
|
+
}
|
|
934
|
+
})
|
|
935
|
+
})
|
|
936
|
+
}
|
|
937
|
+
} else {
|
|
938
|
+
// π Groq and Cerebras are built-in OpenCode providers β no custom provider config needed.
|
|
939
|
+
// π OpenCode discovers them via GROQ_API_KEY / CEREBRAS_API_KEY env vars automatically.
|
|
940
|
+
// π Just set the model in config and launch with --model groq/model-id.
|
|
941
|
+
console.log(chalk.green(` π Setting ${chalk.bold(model.label)} as defaultβ¦`))
|
|
942
|
+
console.log(chalk.dim(` Model: ${modelRef}`))
|
|
943
|
+
console.log()
|
|
944
|
+
|
|
945
|
+
const config = loadOpenCodeConfig()
|
|
946
|
+
const backupPath = `${getOpenCodeConfigPath()}.backup-${Date.now()}`
|
|
947
|
+
|
|
948
|
+
if (existsSync(getOpenCodeConfigPath())) {
|
|
949
|
+
copyFileSync(getOpenCodeConfigPath(), backupPath)
|
|
950
|
+
console.log(chalk.dim(` πΎ Backup: ${backupPath}`))
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
config.model = modelRef
|
|
954
|
+
saveOpenCodeConfig(config)
|
|
900
955
|
|
|
901
|
-
|
|
956
|
+
const savedConfig = loadOpenCodeConfig()
|
|
957
|
+
console.log(chalk.dim(` π Config saved to: ${getOpenCodeConfigPath()}`))
|
|
958
|
+
console.log(chalk.dim(` π Default model in config: ${savedConfig.model || 'NOT SET'}`))
|
|
959
|
+
console.log()
|
|
960
|
+
|
|
961
|
+
if (savedConfig.model === config.model) {
|
|
962
|
+
console.log(chalk.green(` β Default model set to: ${modelRef}`))
|
|
963
|
+
} else {
|
|
964
|
+
console.log(chalk.yellow(` β Config might not have been saved correctly`))
|
|
965
|
+
}
|
|
902
966
|
console.log()
|
|
903
967
|
console.log(chalk.dim(' Starting OpenCodeβ¦'))
|
|
904
968
|
console.log()
|
|
905
969
|
|
|
906
970
|
const { spawn } = await import('child_process')
|
|
907
|
-
const child = spawn('opencode', [], {
|
|
971
|
+
const child = spawn('opencode', ['--model', modelRef], {
|
|
908
972
|
stdio: 'inherit',
|
|
909
973
|
shell: true,
|
|
910
974
|
detached: false
|
|
911
975
|
})
|
|
912
976
|
|
|
913
|
-
// π Wait for OpenCode to exit
|
|
914
977
|
await new Promise((resolve, reject) => {
|
|
915
978
|
child.on('exit', resolve)
|
|
916
979
|
child.on('error', (err) => {
|
|
@@ -929,67 +992,25 @@ After installation, you can use: opencode --model nvidia/${model.modelId}`
|
|
|
929
992
|
// βββ Start OpenCode Desktop βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
930
993
|
// π startOpenCodeDesktop: Same config logic as startOpenCode, but opens the Desktop app.
|
|
931
994
|
// π OpenCode Desktop shares config at the same location as CLI.
|
|
995
|
+
// π Handles all 3 providers: nvidia (needs custom provider config), groq & cerebras (built-in).
|
|
932
996
|
// π No need to wait for exit β Desktop app stays open independently.
|
|
933
997
|
async function startOpenCodeDesktop(model) {
|
|
934
|
-
const
|
|
998
|
+
const providerKey = model.providerKey ?? 'nvidia'
|
|
999
|
+
// π Full model reference string used in OpenCode config and --model flag
|
|
1000
|
+
const modelRef = `${providerKey}/${model.modelId}`
|
|
935
1001
|
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
console.log(chalk.dim(` Model: nvidia/${model.modelId}`))
|
|
939
|
-
console.log()
|
|
940
|
-
|
|
941
|
-
const config = loadOpenCodeConfig()
|
|
942
|
-
const backupPath = `${getOpenCodeConfigPath()}.backup-${Date.now()}`
|
|
943
|
-
|
|
944
|
-
if (existsSync(getOpenCodeConfigPath())) {
|
|
945
|
-
copyFileSync(getOpenCodeConfigPath(), backupPath)
|
|
946
|
-
console.log(chalk.dim(` πΎ Backup: ${backupPath}`))
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
config.model = `nvidia/${model.modelId}`
|
|
950
|
-
|
|
951
|
-
if (config.provider?.nvidia) {
|
|
952
|
-
if (!config.provider.nvidia.models) config.provider.nvidia.models = {}
|
|
953
|
-
config.provider.nvidia.models[model.modelId] = {
|
|
954
|
-
name: model.label,
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
saveOpenCodeConfig(config)
|
|
959
|
-
|
|
960
|
-
// π Verify config was saved correctly
|
|
961
|
-
const savedConfig = loadOpenCodeConfig()
|
|
962
|
-
console.log(chalk.dim(` π Config saved to: ${getOpenCodeConfigPath()}`))
|
|
963
|
-
console.log(chalk.dim(` π Default model in config: ${savedConfig.model || 'NOT SET'}`))
|
|
964
|
-
console.log()
|
|
965
|
-
|
|
966
|
-
if (savedConfig.model === config.model) {
|
|
967
|
-
console.log(chalk.green(` β Default model set to: nvidia/${model.modelId}`))
|
|
968
|
-
} else {
|
|
969
|
-
console.log(chalk.yellow(` β Config might not have been saved correctly`))
|
|
970
|
-
}
|
|
971
|
-
console.log()
|
|
972
|
-
console.log(chalk.dim(' Opening OpenCode Desktopβ¦'))
|
|
973
|
-
console.log()
|
|
974
|
-
|
|
975
|
-
// π Launch Desktop app based on platform
|
|
1002
|
+
// π Helper to open the Desktop app based on platform
|
|
1003
|
+
const launchDesktop = async () => {
|
|
976
1004
|
const { exec } = await import('child_process')
|
|
977
|
-
|
|
978
1005
|
let command
|
|
979
1006
|
if (isMac) {
|
|
980
1007
|
command = 'open -a OpenCode'
|
|
981
1008
|
} else if (isWindows) {
|
|
982
|
-
// π On Windows, try common installation paths
|
|
983
|
-
// π User installation: %LOCALAPPDATA%\Programs\OpenCode\OpenCode.exe
|
|
984
|
-
// π System installation: C:\Program Files\OpenCode\OpenCode.exe
|
|
985
1009
|
command = 'start "" "%LOCALAPPDATA%\\Programs\\OpenCode\\OpenCode.exe" 2>nul || start "" "%PROGRAMFILES%\\OpenCode\\OpenCode.exe" 2>nul || start OpenCode'
|
|
986
1010
|
} else if (isLinux) {
|
|
987
|
-
|
|
988
|
-
// π Check if opencode-desktop exists, otherwise try xdg-open
|
|
989
|
-
command = `opencode-desktop --model nvidia/${model.modelId} 2>/dev/null || flatpak run ai.opencode.OpenCode --model nvidia/${model.modelId} 2>/dev/null || snap run opencode --model nvidia/${model.modelId} 2>/dev/null || xdg-open /usr/share/applications/opencode.desktop 2>/dev/null || echo "OpenCode not found"`
|
|
1011
|
+
command = `opencode-desktop --model ${modelRef} 2>/dev/null || flatpak run ai.opencode.OpenCode --model ${modelRef} 2>/dev/null || snap run opencode --model ${modelRef} 2>/dev/null || xdg-open /usr/share/applications/opencode.desktop 2>/dev/null || echo "OpenCode not found"`
|
|
990
1012
|
}
|
|
991
|
-
|
|
992
|
-
exec(command, (err, stdout, stderr) => {
|
|
1013
|
+
exec(command, (err) => {
|
|
993
1014
|
if (err) {
|
|
994
1015
|
console.error(chalk.red(' β Could not open OpenCode Desktop'))
|
|
995
1016
|
if (isWindows) {
|
|
@@ -1002,13 +1023,56 @@ async function startOpenCodeDesktop(model) {
|
|
|
1002
1023
|
}
|
|
1003
1024
|
}
|
|
1004
1025
|
})
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
if (providerKey === 'nvidia') {
|
|
1029
|
+
// π NVIDIA NIM needs a custom provider block in OpenCode config (not built-in)
|
|
1030
|
+
const hasNim = checkNvidiaNimConfig()
|
|
1031
|
+
|
|
1032
|
+
if (hasNim) {
|
|
1033
|
+
console.log(chalk.green(` π₯ Setting ${chalk.bold(model.label)} as default for OpenCode Desktopβ¦`))
|
|
1034
|
+
console.log(chalk.dim(` Model: ${modelRef}`))
|
|
1035
|
+
console.log()
|
|
1036
|
+
|
|
1037
|
+
const config = loadOpenCodeConfig()
|
|
1038
|
+
const backupPath = `${getOpenCodeConfigPath()}.backup-${Date.now()}`
|
|
1039
|
+
|
|
1040
|
+
if (existsSync(getOpenCodeConfigPath())) {
|
|
1041
|
+
copyFileSync(getOpenCodeConfigPath(), backupPath)
|
|
1042
|
+
console.log(chalk.dim(` πΎ Backup: ${backupPath}`))
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
config.model = modelRef
|
|
1046
|
+
|
|
1047
|
+
if (config.provider?.nvidia) {
|
|
1048
|
+
if (!config.provider.nvidia.models) config.provider.nvidia.models = {}
|
|
1049
|
+
config.provider.nvidia.models[model.modelId] = { name: model.label }
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
saveOpenCodeConfig(config)
|
|
1053
|
+
|
|
1054
|
+
const savedConfig = loadOpenCodeConfig()
|
|
1055
|
+
console.log(chalk.dim(` π Config saved to: ${getOpenCodeConfigPath()}`))
|
|
1056
|
+
console.log(chalk.dim(` π Default model in config: ${savedConfig.model || 'NOT SET'}`))
|
|
1057
|
+
console.log()
|
|
1058
|
+
|
|
1059
|
+
if (savedConfig.model === config.model) {
|
|
1060
|
+
console.log(chalk.green(` β Default model set to: ${modelRef}`))
|
|
1061
|
+
} else {
|
|
1062
|
+
console.log(chalk.yellow(` β Config might not have been saved correctly`))
|
|
1063
|
+
}
|
|
1064
|
+
console.log()
|
|
1065
|
+
console.log(chalk.dim(' Opening OpenCode Desktopβ¦'))
|
|
1066
|
+
console.log()
|
|
1067
|
+
|
|
1068
|
+
await launchDesktop()
|
|
1069
|
+
} else {
|
|
1070
|
+
console.log(chalk.yellow(' β NVIDIA NIM not configured in OpenCode'))
|
|
1071
|
+
console.log(chalk.dim(' Please configure it first. Config is shared between CLI and Desktop.'))
|
|
1072
|
+
console.log()
|
|
1073
|
+
|
|
1074
|
+
const configPath = getOpenCodeConfigPath()
|
|
1075
|
+
const installPrompt = `Add this to ${configPath}:
|
|
1012
1076
|
|
|
1013
1077
|
{
|
|
1014
1078
|
"provider": {
|
|
@@ -1024,8 +1088,41 @@ async function startOpenCodeDesktop(model) {
|
|
|
1024
1088
|
}
|
|
1025
1089
|
|
|
1026
1090
|
${isWindows ? 'set NVIDIA_API_KEY=your_key_here' : 'export NVIDIA_API_KEY=your_key_here'}`
|
|
1027
|
-
|
|
1091
|
+
console.log(chalk.cyan(installPrompt))
|
|
1092
|
+
console.log()
|
|
1093
|
+
}
|
|
1094
|
+
} else {
|
|
1095
|
+
// π Groq and Cerebras are built-in OpenCode providers β just set model and open Desktop.
|
|
1096
|
+
console.log(chalk.green(` π₯ Setting ${chalk.bold(model.label)} as default for OpenCode Desktopβ¦`))
|
|
1097
|
+
console.log(chalk.dim(` Model: ${modelRef}`))
|
|
1098
|
+
console.log()
|
|
1099
|
+
|
|
1100
|
+
const config = loadOpenCodeConfig()
|
|
1101
|
+
const backupPath = `${getOpenCodeConfigPath()}.backup-${Date.now()}`
|
|
1102
|
+
|
|
1103
|
+
if (existsSync(getOpenCodeConfigPath())) {
|
|
1104
|
+
copyFileSync(getOpenCodeConfigPath(), backupPath)
|
|
1105
|
+
console.log(chalk.dim(` πΎ Backup: ${backupPath}`))
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
config.model = modelRef
|
|
1109
|
+
saveOpenCodeConfig(config)
|
|
1110
|
+
|
|
1111
|
+
const savedConfig = loadOpenCodeConfig()
|
|
1112
|
+
console.log(chalk.dim(` π Config saved to: ${getOpenCodeConfigPath()}`))
|
|
1113
|
+
console.log(chalk.dim(` π Default model in config: ${savedConfig.model || 'NOT SET'}`))
|
|
1114
|
+
console.log()
|
|
1115
|
+
|
|
1116
|
+
if (savedConfig.model === config.model) {
|
|
1117
|
+
console.log(chalk.green(` β Default model set to: ${modelRef}`))
|
|
1118
|
+
} else {
|
|
1119
|
+
console.log(chalk.yellow(` β Config might not have been saved correctly`))
|
|
1120
|
+
}
|
|
1028
1121
|
console.log()
|
|
1122
|
+
console.log(chalk.dim(' Opening OpenCode Desktopβ¦'))
|
|
1123
|
+
console.log()
|
|
1124
|
+
|
|
1125
|
+
await launchDesktop()
|
|
1029
1126
|
}
|
|
1030
1127
|
}
|
|
1031
1128
|
|
|
@@ -1524,6 +1621,16 @@ async function main() {
|
|
|
1524
1621
|
results.forEach((r, i) => { r.idx = i + 1 })
|
|
1525
1622
|
state.results = results
|
|
1526
1623
|
adjustScrollOffset(state)
|
|
1624
|
+
// π Re-ping all models that were 'noauth' (got 401 without key) but now have a key
|
|
1625
|
+
// π This makes the TUI react immediately when a user adds an API key in settings
|
|
1626
|
+
state.results.forEach(r => {
|
|
1627
|
+
if (r.status === 'noauth' && getApiKey(state.config, r.providerKey)) {
|
|
1628
|
+
r.status = 'pending'
|
|
1629
|
+
r.pings = []
|
|
1630
|
+
r.httpCode = null
|
|
1631
|
+
pingModel(r).catch(() => {})
|
|
1632
|
+
}
|
|
1633
|
+
})
|
|
1527
1634
|
return
|
|
1528
1635
|
}
|
|
1529
1636
|
|
|
@@ -1651,7 +1758,7 @@ async function main() {
|
|
|
1651
1758
|
const sorted = sortResults(results, state.sortColumn, state.sortDirection)
|
|
1652
1759
|
const selected = sorted[state.cursor]
|
|
1653
1760
|
// π Allow selecting ANY model (even timeout/down) - user knows what they're doing
|
|
1654
|
-
userSelected = { modelId: selected.modelId, label: selected.label, tier: selected.tier }
|
|
1761
|
+
userSelected = { modelId: selected.modelId, label: selected.label, tier: selected.tier, providerKey: selected.providerKey }
|
|
1655
1762
|
|
|
1656
1763
|
// π Stop everything and act on selection immediately
|
|
1657
1764
|
clearInterval(ticker)
|
|
@@ -1707,8 +1814,9 @@ async function main() {
|
|
|
1707
1814
|
|
|
1708
1815
|
// π Single ping function that updates result
|
|
1709
1816
|
// π Uses per-provider API key and URL from sources.js
|
|
1817
|
+
// π If no API key is configured, pings without auth β a 401 still tells us latency + server is up
|
|
1710
1818
|
const pingModel = async (r) => {
|
|
1711
|
-
const providerApiKey = getApiKey(state.config, r.providerKey) ??
|
|
1819
|
+
const providerApiKey = getApiKey(state.config, r.providerKey) ?? null
|
|
1712
1820
|
const providerUrl = sources[r.providerKey]?.url ?? sources.nvidia.url
|
|
1713
1821
|
const { code, ms } = await ping(providerApiKey, r.modelId, providerUrl)
|
|
1714
1822
|
|
|
@@ -1722,6 +1830,11 @@ async function main() {
|
|
|
1722
1830
|
r.status = 'up'
|
|
1723
1831
|
} else if (code === '000') {
|
|
1724
1832
|
r.status = 'timeout'
|
|
1833
|
+
} else if (code === '401') {
|
|
1834
|
+
// π 401 = server is reachable but no API key set (or wrong key)
|
|
1835
|
+
// π Treated as 'noauth' β server is UP, latency is real, just needs a key
|
|
1836
|
+
r.status = 'noauth'
|
|
1837
|
+
r.httpCode = code
|
|
1725
1838
|
} else {
|
|
1726
1839
|
r.status = 'down'
|
|
1727
1840
|
r.httpCode = code
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "free-coding-models",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.50",
|
|
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",
|