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 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
- // πŸ“– Only show response time for successful pings, "β€”" for errors (error code is in Status column)
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 === 'pending') {
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: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
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 NVIDIA NIM model
802
- // πŸ“– If NVIDIA NIM is configured, use --model flag, otherwise show install prompt
803
- // πŸ“– Model format: { modelId, label, tier }
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 hasNim = checkNvidiaNimConfig()
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 (hasNim) {
808
- // πŸ“– NVIDIA NIM already configured - launch with model flag
809
- console.log(chalk.green(` πŸš€ Setting ${chalk.bold(model.label)} as default…`))
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
- const config = loadOpenCodeConfig()
814
- const backupPath = `${getOpenCodeConfigPath()}.backup-${Date.now()}`
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
- // πŸ“– Backup current config
817
- if (existsSync(getOpenCodeConfigPath())) {
818
- copyFileSync(getOpenCodeConfigPath(), backupPath)
819
- console.log(chalk.dim(` πŸ’Ύ Backup: ${backupPath}`))
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
- // πŸ“– Update default model to nvidia/model_id
823
- config.model = `nvidia/${model.modelId}`
840
+ config.model = modelRef
824
841
 
825
- // πŸ“– Register the model in the nvidia provider's models section
826
- // πŸ“– OpenCode requires models to be explicitly listed in provider.models
827
- // πŸ“– to recognize them β€” without this, it falls back to the previous default
828
- if (config.provider?.nvidia) {
829
- if (!config.provider.nvidia.models) config.provider.nvidia.models = {}
830
- config.provider.nvidia.models[model.modelId] = {
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
- saveOpenCodeConfig(config)
850
+ saveOpenCodeConfig(config)
836
851
 
837
- // πŸ“– Verify config was saved correctly
838
- const savedConfig = loadOpenCodeConfig()
839
- console.log(chalk.dim(` πŸ“ Config saved to: ${getOpenCodeConfigPath()}`))
840
- console.log(chalk.dim(` πŸ“ Default model in config: ${savedConfig.model || 'NOT SET'}`))
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
- // πŸ“– Launch OpenCode and wait for it
853
- // πŸ“– Use --model flag to ensure the model is selected
854
- const { spawn } = await import('child_process')
855
- const child = spawn('opencode', ['--model', `nvidia/${model.modelId}`], {
856
- stdio: 'inherit',
857
- shell: true,
858
- detached: false
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
- // πŸ“– Wait for OpenCode to exit
862
- await new Promise((resolve, reject) => {
863
- child.on('exit', resolve)
864
- child.on('error', (err) => {
865
- if (err.code === 'ENOENT') {
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
- const configPath = getOpenCodeConfigPath()
882
- const installPrompt = `Please install NVIDIA NIM provider in OpenCode by adding this to ${configPath}:
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 nvidia/${model.modelId}`
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
- console.log(chalk.cyan(installPrompt))
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 hasNim = checkNvidiaNimConfig()
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
- if (hasNim) {
937
- console.log(chalk.green(` πŸ–₯ Setting ${chalk.bold(model.label)} as default for OpenCode Desktop…`))
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
- // πŸ“– On Linux, try different methods with model flag
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
- } else {
1006
- console.log(chalk.yellow(' ⚠ NVIDIA NIM not configured in OpenCode'))
1007
- console.log(chalk.dim(' Please configure it first. Config is shared between CLI and Desktop.'))
1008
- console.log()
1009
-
1010
- const configPath = getOpenCodeConfigPath()
1011
- const installPrompt = `Add this to ${configPath}:
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
- console.log(chalk.cyan(installPrompt))
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) ?? apiKey
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.48",
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",