free-coding-models 0.1.80 β†’ 0.1.82

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.
@@ -95,7 +95,7 @@ import { createServer as createHttpServer } from 'http'
95
95
  import { request as httpsRequest } from 'https'
96
96
  import { MODELS, sources } from '../sources.js'
97
97
  import { patchOpenClawModelsJson } from '../patch-openclaw-models.js'
98
- import { getAvg, getVerdict, getUptime, getP95, getJitter, getStabilityScore, sortResults, filterByTier, findBestModel, parseArgs, TIER_ORDER, VERDICT_ORDER, TIER_LETTER_MAP, scoreModelForTask, getTopRecommendations, TASK_TYPES, PRIORITY_TYPES, CONTEXT_BUDGETS } from '../lib/utils.js'
98
+ import { getAvg, getVerdict, getUptime, getP95, getJitter, getStabilityScore, sortResults, filterByTier, findBestModel, parseArgs, TIER_ORDER, VERDICT_ORDER, TIER_LETTER_MAP, scoreModelForTask, getTopRecommendations, TASK_TYPES, PRIORITY_TYPES, CONTEXT_BUDGETS, formatCtxWindow, labelFromId } from '../lib/utils.js'
99
99
  import { loadConfig, saveConfig, getApiKey, isProviderEnabled, saveAsProfile, loadProfile, listProfiles, deleteProfile, getActiveProfileName, setActiveProfile, _emptyProfileSettings } from '../lib/config.js'
100
100
 
101
101
  const require = createRequire(import.meta.url)
@@ -1384,7 +1384,10 @@ const OPENCODE_MODEL_MAP = {
1384
1384
  }
1385
1385
 
1386
1386
  function getOpenCodeModelId(providerKey, modelId) {
1387
- // πŸ“– ZAI models stored as "zai/glm-..." but OpenCode expects just "glm-..."
1387
+ // πŸ“– Model IDs in sources.js include the provider prefix (e.g. "nvidia/llama-3.1-...")
1388
+ // πŸ“– but OpenCode expects just the model part after provider/ since we build
1389
+ // πŸ“– the full ref as `${providerKey}/${ocModelId}` in startOpenCode
1390
+ if (providerKey === 'nvidia') return modelId.replace(/^nvidia\//, '')
1388
1391
  if (providerKey === 'zai') return modelId.replace(/^zai\//, '')
1389
1392
  return OPENCODE_MODEL_MAP[providerKey]?.[modelId] || modelId
1390
1393
  }
@@ -1438,8 +1441,8 @@ const PROVIDER_METADATA = {
1438
1441
  sambanova: {
1439
1442
  label: 'SambaNova',
1440
1443
  color: chalk.rgb(255, 165, 0),
1441
- signupUrl: 'https://sambanova.ai/developers',
1442
- signupHint: 'Developers portal β†’ Create API key',
1444
+ signupUrl: 'https://cloud.sambanova.ai/apis',
1445
+ signupHint: 'SambaCloud portal β†’ Create API key',
1443
1446
  rateLimits: 'Dev tier generous quota',
1444
1447
  },
1445
1448
  openrouter: {
@@ -1536,7 +1539,7 @@ const PROVIDER_METADATA = {
1536
1539
  qwen: {
1537
1540
  label: 'Alibaba Cloud (DashScope)',
1538
1541
  color: chalk.rgb(255, 140, 0),
1539
- signupUrl: 'https://dashscope.console.alibabacloud.com',
1542
+ signupUrl: 'https://modelstudio.console.alibabacloud.com',
1540
1543
  signupHint: 'Model Studio β†’ API Key β†’ Create (1M free tokens, 90 days)',
1541
1544
  rateLimits: '1M free tokens per model (Singapore region, 90 days)',
1542
1545
  },
@@ -1622,22 +1625,6 @@ function saveOpenCodeConfig(config) {
1622
1625
  writeFileSync(configPath, JSON.stringify(config, null, 2))
1623
1626
  }
1624
1627
 
1625
- // ─── Check NVIDIA NIM in OpenCode config ───────────────────────────────────────
1626
- // πŸ“– Checks if NVIDIA NIM provider is configured in OpenCode config file
1627
- // πŸ“– OpenCode uses 'provider' (singular) not 'providers' (plural)
1628
- // πŸ“– Returns true if found, false otherwise
1629
- function checkNvidiaNimConfig() {
1630
- const config = loadOpenCodeConfig()
1631
- if (!config.provider) return false
1632
- // πŸ“– Check for nvidia/nim provider by key name or display name (case-insensitive)
1633
- const providerKeys = Object.keys(config.provider)
1634
- return providerKeys.some(key =>
1635
- key === 'nvidia' || key === 'nim' ||
1636
- config.provider[key]?.name?.toLowerCase().includes('nvidia') ||
1637
- config.provider[key]?.name?.toLowerCase().includes('nim')
1638
- )
1639
- }
1640
-
1641
1628
  // ─── Shared OpenCode spawn helper ──────────────────────────────────────────────
1642
1629
  // πŸ“– Resolves the actual API key from config/env and passes it as an env var
1643
1630
  // πŸ“– to the child process so OpenCode's {env:GROQ_API_KEY} references work
@@ -1773,83 +1760,60 @@ async function startOpenCode(model, fcmConfig) {
1773
1760
 
1774
1761
  if (providerKey === 'nvidia') {
1775
1762
  // πŸ“– NVIDIA NIM needs a custom provider block in OpenCode config (not built-in)
1776
- const hasNim = checkNvidiaNimConfig()
1777
-
1778
- if (hasNim) {
1779
- console.log(chalk.green(` πŸš€ Setting ${chalk.bold(model.label)} as default…`))
1780
- console.log(chalk.dim(` Model: ${modelRef}`))
1781
- console.log()
1763
+ // πŸ“– Auto-create it if missing β€” same pattern as all other providers
1764
+ const config = loadOpenCodeConfig()
1765
+ const backupPath = `${getOpenCodeConfigPath()}.backup-${Date.now()}`
1782
1766
 
1783
- const config = loadOpenCodeConfig()
1784
- const backupPath = `${getOpenCodeConfigPath()}.backup-${Date.now()}`
1767
+ if (existsSync(getOpenCodeConfigPath())) {
1768
+ copyFileSync(getOpenCodeConfigPath(), backupPath)
1769
+ console.log(chalk.dim(` Backup: ${backupPath}`))
1770
+ }
1785
1771
 
1786
- if (existsSync(getOpenCodeConfigPath())) {
1787
- copyFileSync(getOpenCodeConfigPath(), backupPath)
1788
- console.log(chalk.dim(` πŸ’Ύ Backup: ${backupPath}`))
1772
+ // πŸ“– Ensure nvidia provider block exists β€” auto-create if missing
1773
+ if (!config.provider) config.provider = {}
1774
+ if (!config.provider.nvidia) {
1775
+ config.provider.nvidia = {
1776
+ npm: '@ai-sdk/openai-compatible',
1777
+ name: 'NVIDIA NIM',
1778
+ options: {
1779
+ baseURL: 'https://integrate.api.nvidia.com/v1',
1780
+ apiKey: '{env:NVIDIA_API_KEY}'
1781
+ },
1782
+ models: {}
1789
1783
  }
1784
+ console.log(chalk.green(' + Auto-configured NVIDIA NIM provider in OpenCode'))
1785
+ }
1790
1786
 
1791
- config.model = modelRef
1787
+ console.log(chalk.green(` Setting ${chalk.bold(model.label)} as default...`))
1788
+ console.log(chalk.dim(` Model: ${modelRef}`))
1789
+ console.log()
1792
1790
 
1793
- // πŸ“– Register the model in the nvidia provider's models section
1794
- if (config.provider?.nvidia) {
1795
- if (!config.provider.nvidia.models) config.provider.nvidia.models = {}
1796
- config.provider.nvidia.models[ocModelId] = { name: model.label }
1797
- }
1791
+ config.model = modelRef
1798
1792
 
1799
- saveOpenCodeConfig(config)
1793
+ // πŸ“– Register the model in the nvidia provider's models section
1794
+ if (!config.provider.nvidia.models) config.provider.nvidia.models = {}
1795
+ config.provider.nvidia.models[ocModelId] = { name: model.label }
1800
1796
 
1801
- const savedConfig = loadOpenCodeConfig()
1802
- console.log(chalk.dim(` πŸ“ Config saved to: ${getOpenCodeConfigPath()}`))
1803
- console.log(chalk.dim(` πŸ“ Default model in config: ${savedConfig.model || 'NOT SET'}`))
1804
- console.log()
1797
+ saveOpenCodeConfig(config)
1805
1798
 
1806
- if (savedConfig.model === config.model) {
1807
- console.log(chalk.green(` βœ“ Default model set to: ${modelRef}`))
1808
- } else {
1809
- console.log(chalk.yellow(` ⚠ Config might not have been saved correctly`))
1810
- }
1811
- console.log()
1812
- console.log(chalk.dim(' Starting OpenCode…'))
1813
- console.log()
1799
+ const savedConfig = loadOpenCodeConfig()
1800
+ console.log(chalk.dim(` Config saved to: ${getOpenCodeConfigPath()}`))
1801
+ console.log(chalk.dim(` Default model in config: ${savedConfig.model || 'NOT SET'}`))
1802
+ console.log()
1814
1803
 
1815
- await spawnOpenCode(['--model', modelRef], providerKey, fcmConfig)
1804
+ if (savedConfig.model === config.model) {
1805
+ console.log(chalk.green(` Default model set to: ${modelRef}`))
1816
1806
  } else {
1817
- // πŸ“– NVIDIA NIM not configured -- show install prompt
1818
- console.log(chalk.yellow(' ⚠ NVIDIA NIM not configured in OpenCode'))
1819
- console.log()
1820
- console.log(chalk.dim(' Starting OpenCode with installation prompt…'))
1821
- console.log()
1822
-
1823
- const configPath = getOpenCodeConfigPath()
1824
- const installPrompt = `Please install NVIDIA NIM provider in OpenCode by adding this to ${configPath}:
1825
-
1826
- {
1827
- "provider": {
1828
- "nvidia": {
1829
- "npm": "@ai-sdk/openai-compatible",
1830
- "name": "NVIDIA NIM",
1831
- "options": {
1832
- "baseURL": "https://integrate.api.nvidia.com/v1",
1833
- "apiKey": "{env:NVIDIA_API_KEY}"
1834
- }
1807
+ console.log(chalk.yellow(` Config might not have been saved correctly`))
1835
1808
  }
1836
- }
1837
- }
1838
-
1839
- ${isWindows ? 'set NVIDIA_API_KEY=your_key_here' : 'export NVIDIA_API_KEY=your_key_here'}
1840
-
1841
- After installation, you can use: opencode --model ${modelRef}`
1842
-
1843
- console.log(chalk.cyan(installPrompt))
1844
- console.log()
1845
- console.log(chalk.dim(' Starting OpenCode…'))
1846
- console.log()
1809
+ console.log()
1810
+ console.log(chalk.dim(' Starting OpenCode...'))
1811
+ console.log()
1847
1812
 
1848
- await spawnOpenCode([], providerKey, fcmConfig)
1849
- }
1813
+ await spawnOpenCode(['--model', modelRef], providerKey, fcmConfig)
1850
1814
  } else {
1851
1815
  if (providerKey === 'replicate') {
1852
- console.log(chalk.yellow(' ⚠ Replicate models are monitor-only for now in OpenCode mode.'))
1816
+ console.log(chalk.yellow(' Replicate models are monitor-only for now in OpenCode mode.'))
1853
1817
  console.log(chalk.dim(' Reason: Replicate uses /v1/predictions instead of OpenAI chat-completions.'))
1854
1818
  console.log(chalk.dim(' You can still benchmark this model in the TUI and use other providers for OpenCode launch.'))
1855
1819
  console.log()
@@ -1863,16 +1827,16 @@ After installation, you can use: opencode --model ${modelRef}`
1863
1827
  if (providerKey === 'zai') {
1864
1828
  const resolvedKey = getApiKey(fcmConfig, providerKey)
1865
1829
  if (!resolvedKey) {
1866
- console.log(chalk.yellow(' ⚠ ZAI API key not found. Set ZAI_API_KEY environment variable.'))
1830
+ console.log(chalk.yellow(' ZAI API key not found. Set ZAI_API_KEY environment variable.'))
1867
1831
  console.log()
1868
1832
  return
1869
1833
  }
1870
1834
 
1871
1835
  // πŸ“– Start proxy FIRST to get the port for config
1872
1836
  const { server: zaiProxyServer, port: zaiProxyPort } = await createZaiProxy(resolvedKey)
1873
- console.log(chalk.dim(` πŸ”€ ZAI proxy listening on port ${zaiProxyPort} (rewrites /v1/* β†’ ZAI API)`))
1837
+ console.log(chalk.dim(` ZAI proxy listening on port ${zaiProxyPort} (rewrites /v1/* -> ZAI API)`))
1874
1838
 
1875
- console.log(chalk.green(` πŸš€ Setting ${chalk.bold(model.label)} as default…`))
1839
+ console.log(chalk.green(` Setting ${chalk.bold(model.label)} as default...`))
1876
1840
  console.log(chalk.dim(` Model: ${modelRef}`))
1877
1841
  console.log()
1878
1842
 
@@ -1881,7 +1845,7 @@ After installation, you can use: opencode --model ${modelRef}`
1881
1845
 
1882
1846
  if (existsSync(getOpenCodeConfigPath())) {
1883
1847
  copyFileSync(getOpenCodeConfigPath(), backupPath)
1884
- console.log(chalk.dim(` πŸ’Ύ Backup: ${backupPath}`))
1848
+ console.log(chalk.dim(` Backup: ${backupPath}`))
1885
1849
  }
1886
1850
 
1887
1851
  // πŸ“– Register ZAI as an openai-compatible provider pointing to our localhost proxy
@@ -1902,17 +1866,17 @@ After installation, you can use: opencode --model ${modelRef}`
1902
1866
  saveOpenCodeConfig(config)
1903
1867
 
1904
1868
  const savedConfig = loadOpenCodeConfig()
1905
- console.log(chalk.dim(` πŸ“ Config saved to: ${getOpenCodeConfigPath()}`))
1906
- console.log(chalk.dim(` πŸ“ Default model in config: ${savedConfig.model || 'NOT SET'}`))
1869
+ console.log(chalk.dim(` Config saved to: ${getOpenCodeConfigPath()}`))
1870
+ console.log(chalk.dim(` Default model in config: ${savedConfig.model || 'NOT SET'}`))
1907
1871
  console.log()
1908
1872
 
1909
1873
  if (savedConfig.model === config.model) {
1910
- console.log(chalk.green(` βœ“ Default model set to: ${modelRef}`))
1874
+ console.log(chalk.green(` Default model set to: ${modelRef}`))
1911
1875
  } else {
1912
- console.log(chalk.yellow(` ⚠ Config might not have been saved correctly`))
1876
+ console.log(chalk.yellow(` Config might not have been saved correctly`))
1913
1877
  }
1914
1878
  console.log()
1915
- console.log(chalk.dim(' Starting OpenCode…'))
1879
+ console.log(chalk.dim(' Starting OpenCode...'))
1916
1880
  console.log()
1917
1881
 
1918
1882
  // πŸ“– Pass existing proxy to spawnOpenCode so it doesn't start a second one
@@ -1923,7 +1887,7 @@ After installation, you can use: opencode --model ${modelRef}`
1923
1887
  // πŸ“– Groq: built-in OpenCode provider β€” needs provider block with apiKey in opencode.json.
1924
1888
  // πŸ“– Cerebras: NOT built-in β€” needs @ai-sdk/openai-compatible + baseURL, like NVIDIA.
1925
1889
  // πŸ“– Both need the model registered in provider.<key>.models so OpenCode can find it.
1926
- console.log(chalk.green(` πŸš€ Setting ${chalk.bold(model.label)} as default…`))
1890
+ console.log(chalk.green(` Setting ${chalk.bold(model.label)} as default...`))
1927
1891
  console.log(chalk.dim(` Model: ${modelRef}`))
1928
1892
  console.log()
1929
1893
 
@@ -1932,7 +1896,7 @@ After installation, you can use: opencode --model ${modelRef}`
1932
1896
 
1933
1897
  if (existsSync(getOpenCodeConfigPath())) {
1934
1898
  copyFileSync(getOpenCodeConfigPath(), backupPath)
1935
- console.log(chalk.dim(` πŸ’Ύ Backup: ${backupPath}`))
1899
+ console.log(chalk.dim(` Backup: ${backupPath}`))
1936
1900
  }
1937
1901
 
1938
1902
  // πŸ“– Ensure the provider block exists in config β€” create it if missing
@@ -2069,7 +2033,7 @@ After installation, you can use: opencode --model ${modelRef}`
2069
2033
  } else if (providerKey === 'cloudflare') {
2070
2034
  const cloudflareAccountId = (process.env.CLOUDFLARE_ACCOUNT_ID || '').trim()
2071
2035
  if (!cloudflareAccountId) {
2072
- console.log(chalk.yellow(' ⚠ Cloudflare Workers AI requires CLOUDFLARE_ACCOUNT_ID for OpenCode integration.'))
2036
+ console.log(chalk.yellow(' Cloudflare Workers AI requires CLOUDFLARE_ACCOUNT_ID for OpenCode integration.'))
2073
2037
  console.log(chalk.dim(' Export CLOUDFLARE_ACCOUNT_ID and retry this selection.'))
2074
2038
  console.log()
2075
2039
  return
@@ -2118,17 +2082,17 @@ After installation, you can use: opencode --model ${modelRef}`
2118
2082
  saveOpenCodeConfig(config)
2119
2083
 
2120
2084
  const savedConfig = loadOpenCodeConfig()
2121
- console.log(chalk.dim(` πŸ“ Config saved to: ${getOpenCodeConfigPath()}`))
2122
- console.log(chalk.dim(` πŸ“ Default model in config: ${savedConfig.model || 'NOT SET'}`))
2085
+ console.log(chalk.dim(` Config saved to: ${getOpenCodeConfigPath()}`))
2086
+ console.log(chalk.dim(` Default model in config: ${savedConfig.model || 'NOT SET'}`))
2123
2087
  console.log()
2124
2088
 
2125
2089
  if (savedConfig.model === config.model) {
2126
- console.log(chalk.green(` βœ“ Default model set to: ${modelRef}`))
2090
+ console.log(chalk.green(` Default model set to: ${modelRef}`))
2127
2091
  } else {
2128
- console.log(chalk.yellow(` ⚠ Config might not have been saved correctly`))
2092
+ console.log(chalk.yellow(` Config might not have been saved correctly`))
2129
2093
  }
2130
2094
  console.log()
2131
- console.log(chalk.dim(' Starting OpenCode…'))
2095
+ console.log(chalk.dim(' Starting OpenCode...'))
2132
2096
  console.log()
2133
2097
 
2134
2098
  await spawnOpenCode(['--model', modelRef], providerKey, fcmConfig)
@@ -2159,7 +2123,7 @@ async function startOpenCodeDesktop(model, fcmConfig) {
2159
2123
  }
2160
2124
  exec(command, (err) => {
2161
2125
  if (err) {
2162
- console.error(chalk.red(' βœ— Could not open OpenCode Desktop'))
2126
+ console.error(chalk.red(' Could not open OpenCode Desktop'))
2163
2127
  if (isWindows) {
2164
2128
  console.error(chalk.dim(' Make sure OpenCode is installed from https://opencode.ai'))
2165
2129
  } else if (isLinux) {
@@ -2174,73 +2138,60 @@ async function startOpenCodeDesktop(model, fcmConfig) {
2174
2138
 
2175
2139
  if (providerKey === 'nvidia') {
2176
2140
  // πŸ“– NVIDIA NIM needs a custom provider block in OpenCode config (not built-in)
2177
- const hasNim = checkNvidiaNimConfig()
2178
-
2179
- if (hasNim) {
2180
- console.log(chalk.green(` πŸ–₯ Setting ${chalk.bold(model.label)} as default for OpenCode Desktop…`))
2181
- console.log(chalk.dim(` Model: ${modelRef}`))
2182
- console.log()
2141
+ // πŸ“– Auto-create it if missing β€” same pattern as all other providers
2142
+ const config = loadOpenCodeConfig()
2143
+ const backupPath = `${getOpenCodeConfigPath()}.backup-${Date.now()}`
2183
2144
 
2184
- const config = loadOpenCodeConfig()
2185
- const backupPath = `${getOpenCodeConfigPath()}.backup-${Date.now()}`
2145
+ if (existsSync(getOpenCodeConfigPath())) {
2146
+ copyFileSync(getOpenCodeConfigPath(), backupPath)
2147
+ console.log(chalk.dim(` Backup: ${backupPath}`))
2148
+ }
2186
2149
 
2187
- if (existsSync(getOpenCodeConfigPath())) {
2188
- copyFileSync(getOpenCodeConfigPath(), backupPath)
2189
- console.log(chalk.dim(` πŸ’Ύ Backup: ${backupPath}`))
2150
+ // πŸ“– Ensure nvidia provider block exists β€” auto-create if missing
2151
+ if (!config.provider) config.provider = {}
2152
+ if (!config.provider.nvidia) {
2153
+ config.provider.nvidia = {
2154
+ npm: '@ai-sdk/openai-compatible',
2155
+ name: 'NVIDIA NIM',
2156
+ options: {
2157
+ baseURL: 'https://integrate.api.nvidia.com/v1',
2158
+ apiKey: '{env:NVIDIA_API_KEY}'
2159
+ },
2160
+ models: {}
2190
2161
  }
2162
+ console.log(chalk.green(' + Auto-configured NVIDIA NIM provider in OpenCode'))
2163
+ }
2191
2164
 
2192
- config.model = modelRef
2165
+ console.log(chalk.green(` Setting ${chalk.bold(model.label)} as default for OpenCode Desktop...`))
2166
+ console.log(chalk.dim(` Model: ${modelRef}`))
2167
+ console.log()
2193
2168
 
2194
- if (config.provider?.nvidia) {
2195
- if (!config.provider.nvidia.models) config.provider.nvidia.models = {}
2196
- config.provider.nvidia.models[ocModelId] = { name: model.label }
2197
- }
2169
+ config.model = modelRef
2198
2170
 
2199
- saveOpenCodeConfig(config)
2171
+ // πŸ“– Register the model in the nvidia provider's models section
2172
+ if (!config.provider.nvidia.models) config.provider.nvidia.models = {}
2173
+ config.provider.nvidia.models[ocModelId] = { name: model.label }
2200
2174
 
2201
- const savedConfig = loadOpenCodeConfig()
2202
- console.log(chalk.dim(` πŸ“ Config saved to: ${getOpenCodeConfigPath()}`))
2203
- console.log(chalk.dim(` πŸ“ Default model in config: ${savedConfig.model || 'NOT SET'}`))
2204
- console.log()
2175
+ saveOpenCodeConfig(config)
2205
2176
 
2206
- if (savedConfig.model === config.model) {
2207
- console.log(chalk.green(` βœ“ Default model set to: ${modelRef}`))
2208
- } else {
2209
- console.log(chalk.yellow(` ⚠ Config might not have been saved correctly`))
2210
- }
2211
- console.log()
2212
- console.log(chalk.dim(' Opening OpenCode Desktop…'))
2213
- console.log()
2177
+ const savedConfig = loadOpenCodeConfig()
2178
+ console.log(chalk.dim(` Config saved to: ${getOpenCodeConfigPath()}`))
2179
+ console.log(chalk.dim(` Default model in config: ${savedConfig.model || 'NOT SET'}`))
2180
+ console.log()
2214
2181
 
2215
- await launchDesktop()
2182
+ if (savedConfig.model === config.model) {
2183
+ console.log(chalk.green(` Default model set to: ${modelRef}`))
2216
2184
  } else {
2217
- console.log(chalk.yellow(' ⚠ NVIDIA NIM not configured in OpenCode'))
2218
- console.log(chalk.dim(' Please configure it first. Config is shared between CLI and Desktop.'))
2219
- console.log()
2220
-
2221
- const configPath = getOpenCodeConfigPath()
2222
- const installPrompt = `Add this to ${configPath}:
2223
-
2224
- {
2225
- "provider": {
2226
- "nvidia": {
2227
- "npm": "@ai-sdk/openai-compatible",
2228
- "name": "NVIDIA NIM",
2229
- "options": {
2230
- "baseURL": "https://integrate.api.nvidia.com/v1",
2231
- "apiKey": "{env:NVIDIA_API_KEY}"
2232
- }
2185
+ console.log(chalk.yellow(` Config might not have been saved correctly`))
2233
2186
  }
2234
- }
2235
- }
2187
+ console.log()
2188
+ console.log(chalk.dim(' Opening OpenCode Desktop...'))
2189
+ console.log()
2236
2190
 
2237
- ${isWindows ? 'set NVIDIA_API_KEY=your_key_here' : 'export NVIDIA_API_KEY=your_key_here'}`
2238
- console.log(chalk.cyan(installPrompt))
2239
- console.log()
2240
- }
2191
+ await launchDesktop()
2241
2192
  } else {
2242
2193
  if (providerKey === 'replicate') {
2243
- console.log(chalk.yellow(' ⚠ Replicate models are monitor-only for now in OpenCode Desktop mode.'))
2194
+ console.log(chalk.yellow(' Replicate models are monitor-only for now in OpenCode Desktop mode.'))
2244
2195
  console.log(chalk.dim(' Reason: Replicate uses /v1/predictions instead of OpenAI chat-completions.'))
2245
2196
  console.log(chalk.dim(' You can still benchmark this model in the TUI and use other providers for Desktop launch.'))
2246
2197
  console.log()
@@ -2250,7 +2201,7 @@ ${isWindows ? 'set NVIDIA_API_KEY=your_key_here' : 'export NVIDIA_API_KEY=your_k
2250
2201
  // πŸ“– ZAI: Desktop mode can't use the localhost proxy (Desktop is a standalone app).
2251
2202
  // πŸ“– Direct the user to use OpenCode CLI mode instead, which supports ZAI via proxy.
2252
2203
  if (providerKey === 'zai') {
2253
- console.log(chalk.yellow(' ⚠ ZAI models are supported in OpenCode CLI mode only (not Desktop).'))
2204
+ console.log(chalk.yellow(' ZAI models are supported in OpenCode CLI mode only (not Desktop).'))
2254
2205
  console.log(chalk.dim(' Reason: ZAI requires a localhost proxy that only works with the CLI spawn.'))
2255
2206
  console.log(chalk.dim(' Use OpenCode CLI mode (default) to launch ZAI models.'))
2256
2207
  console.log()
@@ -2260,7 +2211,7 @@ ${isWindows ? 'set NVIDIA_API_KEY=your_key_here' : 'export NVIDIA_API_KEY=your_k
2260
2211
  // πŸ“– Groq: built-in OpenCode provider β€” needs provider block with apiKey in opencode.json.
2261
2212
  // πŸ“– Cerebras: NOT built-in β€” needs @ai-sdk/openai-compatible + baseURL, like NVIDIA.
2262
2213
  // πŸ“– Both need the model registered in provider.<key>.models so OpenCode can find it.
2263
- console.log(chalk.green(` πŸ–₯ Setting ${chalk.bold(model.label)} as default for OpenCode Desktop…`))
2214
+ console.log(chalk.green(` Setting ${chalk.bold(model.label)} as default for OpenCode Desktop...`))
2264
2215
  console.log(chalk.dim(` Model: ${modelRef}`))
2265
2216
  console.log()
2266
2217
 
@@ -2269,7 +2220,7 @@ ${isWindows ? 'set NVIDIA_API_KEY=your_key_here' : 'export NVIDIA_API_KEY=your_k
2269
2220
 
2270
2221
  if (existsSync(getOpenCodeConfigPath())) {
2271
2222
  copyFileSync(getOpenCodeConfigPath(), backupPath)
2272
- console.log(chalk.dim(` πŸ’Ύ Backup: ${backupPath}`))
2223
+ console.log(chalk.dim(` Backup: ${backupPath}`))
2273
2224
  }
2274
2225
 
2275
2226
  // πŸ“– Ensure the provider block exists in config β€” create it if missing
@@ -2396,17 +2347,17 @@ ${isWindows ? 'set NVIDIA_API_KEY=your_key_here' : 'export NVIDIA_API_KEY=your_k
2396
2347
  saveOpenCodeConfig(config)
2397
2348
 
2398
2349
  const savedConfig = loadOpenCodeConfig()
2399
- console.log(chalk.dim(` πŸ“ Config saved to: ${getOpenCodeConfigPath()}`))
2400
- console.log(chalk.dim(` πŸ“ Default model in config: ${savedConfig.model || 'NOT SET'}`))
2350
+ console.log(chalk.dim(` Config saved to: ${getOpenCodeConfigPath()}`))
2351
+ console.log(chalk.dim(` Default model in config: ${savedConfig.model || 'NOT SET'}`))
2401
2352
  console.log()
2402
2353
 
2403
2354
  if (savedConfig.model === config.model) {
2404
- console.log(chalk.green(` βœ“ Default model set to: ${modelRef}`))
2355
+ console.log(chalk.green(` Default model set to: ${modelRef}`))
2405
2356
  } else {
2406
- console.log(chalk.yellow(` ⚠ Config might not have been saved correctly`))
2357
+ console.log(chalk.yellow(` Config might not have been saved correctly`))
2407
2358
  }
2408
2359
  console.log()
2409
- console.log(chalk.dim(' Opening OpenCode Desktop…'))
2360
+ console.log(chalk.dim(' Opening OpenCode Desktop...'))
2410
2361
  console.log()
2411
2362
 
2412
2363
  await launchDesktop()
@@ -2605,6 +2556,76 @@ function filterByTierOrExit(results, tierLetter) {
2605
2556
  return filtered
2606
2557
  }
2607
2558
 
2559
+ // ─── Dynamic OpenRouter free model discovery ──────────────────────────────────
2560
+ // πŸ“– Fetches the live list of free models from OpenRouter's public API at startup.
2561
+ // πŸ“– Replaces the static openrouter entries in MODELS with fresh data so new free
2562
+ // πŸ“– models appear automatically without a code update.
2563
+ // πŸ“– Falls back silently to the static list on network failure.
2564
+
2565
+ // πŸ“– Known SWE-bench scores for OpenRouter free models.
2566
+ // πŸ“– Keyed by base model ID (without the :free suffix).
2567
+ // πŸ“– Unknown models default to tier 'B' / '25.0%'.
2568
+ const OPENROUTER_TIER_MAP = {
2569
+ 'qwen/qwen3-coder': ['S+', '70.6%'],
2570
+ 'mistralai/devstral-2': ['S+', '72.2%'],
2571
+ 'stepfun/step-3.5-flash': ['S+', '74.4%'],
2572
+ 'deepseek/deepseek-r1-0528': ['S', '61.0%'],
2573
+ 'qwen/qwen3-next-80b-a3b-instruct': ['S', '65.0%'],
2574
+ 'openai/gpt-oss-120b': ['S', '60.0%'],
2575
+ 'openai/gpt-oss-20b': ['A', '42.0%'],
2576
+ 'nvidia/nemotron-3-nano-30b-a3b': ['A', '43.0%'],
2577
+ 'meta-llama/llama-3.3-70b-instruct': ['A-', '39.5%'],
2578
+ 'mimo-v2-flash': ['A', '45.0%'],
2579
+ 'google/gemma-3-27b-it': ['A-', '36.0%'],
2580
+ 'google/gemma-3-12b-it': ['B+', '30.0%'],
2581
+ 'google/gemma-3-4b-it': ['B', '22.0%'],
2582
+ 'google/gemma-3n-e4b-it': ['B', '22.0%'],
2583
+ 'google/gemma-3n-e2b-it': ['B', '18.0%'],
2584
+ 'meta-llama/llama-3.2-3b-instruct': ['B', '20.0%'],
2585
+ 'mistralai/mistral-small-3.1-24b-instruct': ['A-', '35.0%'],
2586
+ 'qwen/qwen3-4b': ['B', '22.0%'],
2587
+ 'nousresearch/hermes-3-llama-3.1-405b': ['A', '40.0%'],
2588
+ 'nvidia/nemotron-nano-9b-v2': ['B+', '28.0%'],
2589
+ 'nvidia/nemotron-nano-12b-v2-vl': ['B+', '30.0%'],
2590
+ 'z-ai/glm-4.5-air': ['A-', '38.0%'],
2591
+ 'arcee-ai/trinity-large-preview': ['A', '40.0%'],
2592
+ 'arcee-ai/trinity-mini': ['B+', '28.0%'],
2593
+ 'upstage/solar-pro-3': ['A-', '35.0%'],
2594
+ 'cognitivecomputations/dolphin-mistral-24b-venice-edition': ['B+', '28.0%'],
2595
+ 'liquid/lfm-2.5-1.2b-thinking': ['B', '18.0%'],
2596
+ 'liquid/lfm-2.5-1.2b-instruct': ['B', '18.0%'],
2597
+ }
2598
+
2599
+ async function fetchOpenRouterFreeModels() {
2600
+ try {
2601
+ const controller = new AbortController()
2602
+ const timeout = setTimeout(() => controller.abort(), 5000)
2603
+ const res = await fetch('https://openrouter.ai/api/v1/models', {
2604
+ signal: controller.signal,
2605
+ headers: {
2606
+ 'HTTP-Referer': 'https://github.com/vava-nessa/free-coding-models',
2607
+ 'X-Title': 'free-coding-models',
2608
+ },
2609
+ })
2610
+ clearTimeout(timeout)
2611
+ if (!res.ok) return null
2612
+ const json = await res.json()
2613
+ if (!json.data || !Array.isArray(json.data)) return null
2614
+
2615
+ const freeModels = json.data.filter(m => m.id && m.id.endsWith(':free'))
2616
+
2617
+ return freeModels.map(m => {
2618
+ const baseId = m.id.replace(/:free$/, '')
2619
+ const [tier, swe] = OPENROUTER_TIER_MAP[baseId] || ['B', '25.0%']
2620
+ const ctx = formatCtxWindow(m.context_length)
2621
+ const label = labelFromId(m.id)
2622
+ return [m.id, label, tier, swe, ctx]
2623
+ })
2624
+ } catch {
2625
+ return null
2626
+ }
2627
+ }
2628
+
2608
2629
  async function main() {
2609
2630
  const cliArgs = parseArgs(process.argv)
2610
2631
 
@@ -2670,7 +2691,11 @@ async function main() {
2670
2691
  }
2671
2692
 
2672
2693
  // πŸ“– Auto-update system: force updates and handle changelog automatically
2673
- if (latestVersion) {
2694
+ // πŸ“– Skip when running from source (dev mode) β€” .git means we're in a repo checkout,
2695
+ // πŸ“– not a global npm install. Auto-update would overwrite the global copy but restart
2696
+ // πŸ“– the local one, causing an infinite update loop since LOCAL_VERSION never changes.
2697
+ const isDevMode = existsSync(join(dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, '$1')), '..', '.git'))
2698
+ if (latestVersion && !isDevMode) {
2674
2699
  console.log()
2675
2700
  console.log(chalk.bold.red(' ⚠ AUTO-UPDATE AVAILABLE'))
2676
2701
  console.log(chalk.red(` Version ${latestVersion} will be installed automatically`))
@@ -2700,6 +2725,23 @@ async function main() {
2700
2725
  return // runUpdate will restart the process
2701
2726
  }
2702
2727
 
2728
+ // πŸ“– Dynamic OpenRouter free model discovery β€” fetch live free models from API
2729
+ // πŸ“– Replaces static openrouter entries in MODELS with fresh data.
2730
+ // πŸ“– Fallback: if fetch fails, the static list from sources.js stays intact + warning shown.
2731
+ const dynamicModels = await fetchOpenRouterFreeModels()
2732
+ if (dynamicModels) {
2733
+ // πŸ“– Remove all existing openrouter entries from MODELS
2734
+ for (let i = MODELS.length - 1; i >= 0; i--) {
2735
+ if (MODELS[i][5] === 'openrouter') MODELS.splice(i, 1)
2736
+ }
2737
+ // πŸ“– Push fresh entries with 'openrouter' providerKey
2738
+ for (const [modelId, label, tier, swe, ctx] of dynamicModels) {
2739
+ MODELS.push([modelId, label, tier, swe, ctx, 'openrouter'])
2740
+ }
2741
+ } else {
2742
+ console.log(chalk.yellow(' OpenRouter: using cached model list (live fetch failed)'))
2743
+ }
2744
+
2703
2745
  // πŸ“– Build results from MODELS β€” only include enabled providers
2704
2746
  // πŸ“– Each result gets providerKey so ping() knows which URL + API key to use
2705
2747
 
@@ -2764,6 +2806,7 @@ async function main() {
2764
2806
  settingsCursor: 0, // πŸ“– Which provider row is selected in settings
2765
2807
  settingsEditMode: false, // πŸ“– Whether we're in inline key editing mode
2766
2808
  settingsEditBuffer: '', // πŸ“– Typed characters for the API key being edited
2809
+ settingsErrorMsg: null, // πŸ“– Temporary error message to display in settings
2767
2810
  settingsTestResults: {}, // πŸ“– { providerKey: 'pending'|'ok'|'fail'|null }
2768
2811
  settingsUpdateState: 'idle', // πŸ“– 'idle'|'checking'|'available'|'up-to-date'|'error'|'installing'
2769
2812
  settingsUpdateLatestVersion: null, // πŸ“– Latest npm version discovered from manual check
@@ -2860,6 +2903,9 @@ async function main() {
2860
2903
 
2861
2904
  lines.push('')
2862
2905
  lines.push(` ${chalk.bold('βš™ Settings')} ${chalk.dim('β€” free-coding-models v' + LOCAL_VERSION)}`)
2906
+ if (state.settingsErrorMsg) {
2907
+ lines.push(` ${chalk.red.bold(state.settingsErrorMsg)}`)
2908
+ }
2863
2909
  lines.push('')
2864
2910
  lines.push(` ${chalk.bold('🧩 Providers')}`)
2865
2911
  lines.push(` ${chalk.dim(' ' + '─'.repeat(112))}`)
@@ -3900,6 +3946,15 @@ async function main() {
3900
3946
  const pk = providerKeys[state.settingsCursor]
3901
3947
  const newKey = state.settingsEditBuffer.trim()
3902
3948
  if (newKey) {
3949
+ // πŸ“– Validate OpenRouter keys start with "sk-or-" to detect corruption
3950
+ if (pk === 'openrouter' && !newKey.startsWith('sk-or-')) {
3951
+ // πŸ“– Don't save corrupted keys - show warning and cancel
3952
+ state.settingsEditMode = false
3953
+ state.settingsEditBuffer = ''
3954
+ state.settingsErrorMsg = '⚠️ OpenRouter keys must start with "sk-or-". Key not saved.'
3955
+ setTimeout(() => { state.settingsErrorMsg = null }, 3000)
3956
+ return
3957
+ }
3903
3958
  state.config.apiKeys[pk] = newKey
3904
3959
  saveConfig(state.config)
3905
3960
  }
package/lib/utils.js CHANGED
@@ -468,6 +468,25 @@ function parseCtxToK(ctx) {
468
468
  return 0
469
469
  }
470
470
 
471
+ // πŸ“– formatCtxWindow: Convert context_length number to compact string (256000 β†’ '256k', 1048576 β†’ '1M')
472
+ // πŸ“– Used by dynamic OpenRouter model discovery to convert API response to our display format.
473
+ export function formatCtxWindow(n) {
474
+ if (typeof n !== 'number' || n <= 0) return '128k'
475
+ if (n >= 1_000_000) return Math.round(n / 1_000_000) + 'M'
476
+ return Math.round(n / 1000) + 'k'
477
+ }
478
+
479
+ // πŸ“– labelFromId: Build a human-readable label from an OpenRouter model ID.
480
+ // πŸ“– 'qwen/qwen3-coder:free' β†’ 'Qwen3 Coder'
481
+ export function labelFromId(id) {
482
+ const base = id.replace(/:free$/, '')
483
+ const name = base.includes('/') ? base.split('/').pop() : base
484
+ return name
485
+ .split(/[-_]/)
486
+ .map(w => w.charAt(0).toUpperCase() + w.slice(1))
487
+ .join(' ')
488
+ }
489
+
471
490
  // πŸ“– parseSweToNum: Convert SWE-bench score string ("49.2%", "73.1%") into a 0–100 number.
472
491
  // πŸ“– Returns 0 for missing or invalid scores.
473
492
  function parseSweToNum(sweScore) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-coding-models",
3
- "version": "0.1.80",
3
+ "version": "0.1.82",
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
@@ -293,7 +293,7 @@ export const perplexity = [
293
293
  // πŸ“– Alibaba Cloud (DashScope) source - https://dashscope-intl.aliyuncs.com
294
294
  // πŸ“– OpenAI-compatible endpoint: https://dashscope-intl.aliyuncs.com/compatible-mode/v1
295
295
  // πŸ“– Free tier: 1M tokens per model (Singapore region only), valid for 90 days
296
- // πŸ“– Get API key: https://dashscope.console.alibabacloud.com
296
+ // πŸ“– Get API key: https://modelstudio.console.alibabacloud.com
297
297
  // πŸ“– Env var: DASHSCOPE_API_KEY
298
298
  // πŸ“– Qwen3-Coder models: optimized coding models with excellent SWE-bench scores
299
299
  export const qwen = [