free-coding-models 0.3.37 → 0.3.41

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.
Files changed (62) hide show
  1. package/CHANGELOG.md +5 -1800
  2. package/README.md +10 -1
  3. package/bin/free-coding-models.js +8 -0
  4. package/package.json +13 -3
  5. package/src/app.js +30 -0
  6. package/src/cli-help.js +2 -0
  7. package/src/command-palette.js +3 -0
  8. package/src/config.js +7 -0
  9. package/src/endpoint-installer.js +1 -1
  10. package/src/key-handler.js +27 -1
  11. package/src/overlays.js +11 -1
  12. package/src/shell-env.js +393 -0
  13. package/src/tool-bootstrap.js +41 -0
  14. package/src/tool-launchers.js +166 -1
  15. package/src/tool-metadata.js +12 -0
  16. package/src/utils.js +12 -0
  17. package/web/app.legacy.js +900 -0
  18. package/web/index.html +20 -0
  19. package/web/server.js +443 -0
  20. package/web/src/App.jsx +150 -0
  21. package/web/src/components/analytics/AnalyticsView.jsx +109 -0
  22. package/web/src/components/analytics/AnalyticsView.module.css +186 -0
  23. package/web/src/components/atoms/Sparkline.jsx +44 -0
  24. package/web/src/components/atoms/StabilityCell.jsx +18 -0
  25. package/web/src/components/atoms/StabilityCell.module.css +8 -0
  26. package/web/src/components/atoms/StatusDot.jsx +10 -0
  27. package/web/src/components/atoms/StatusDot.module.css +17 -0
  28. package/web/src/components/atoms/TierBadge.jsx +10 -0
  29. package/web/src/components/atoms/TierBadge.module.css +18 -0
  30. package/web/src/components/atoms/Toast.jsx +25 -0
  31. package/web/src/components/atoms/Toast.module.css +35 -0
  32. package/web/src/components/atoms/ToastContainer.jsx +16 -0
  33. package/web/src/components/atoms/ToastContainer.module.css +10 -0
  34. package/web/src/components/atoms/VerdictBadge.jsx +13 -0
  35. package/web/src/components/atoms/VerdictBadge.module.css +19 -0
  36. package/web/src/components/dashboard/DetailPanel.jsx +131 -0
  37. package/web/src/components/dashboard/DetailPanel.module.css +99 -0
  38. package/web/src/components/dashboard/ExportModal.jsx +79 -0
  39. package/web/src/components/dashboard/ExportModal.module.css +99 -0
  40. package/web/src/components/dashboard/FilterBar.jsx +73 -0
  41. package/web/src/components/dashboard/FilterBar.module.css +43 -0
  42. package/web/src/components/dashboard/ModelTable.jsx +86 -0
  43. package/web/src/components/dashboard/ModelTable.module.css +46 -0
  44. package/web/src/components/dashboard/StatsBar.jsx +40 -0
  45. package/web/src/components/dashboard/StatsBar.module.css +28 -0
  46. package/web/src/components/layout/Footer.jsx +19 -0
  47. package/web/src/components/layout/Footer.module.css +10 -0
  48. package/web/src/components/layout/Header.jsx +38 -0
  49. package/web/src/components/layout/Header.module.css +73 -0
  50. package/web/src/components/layout/Sidebar.jsx +41 -0
  51. package/web/src/components/layout/Sidebar.module.css +76 -0
  52. package/web/src/components/settings/SettingsView.jsx +264 -0
  53. package/web/src/components/settings/SettingsView.module.css +377 -0
  54. package/web/src/global.css +199 -0
  55. package/web/src/hooks/useFilter.js +83 -0
  56. package/web/src/hooks/useSSE.js +49 -0
  57. package/web/src/hooks/useTheme.js +27 -0
  58. package/web/src/main.jsx +15 -0
  59. package/web/src/utils/download.js +15 -0
  60. package/web/src/utils/format.js +42 -0
  61. package/web/src/utils/ranks.js +37 -0
  62. package/web/styles.legacy.css +963 -0
package/README.md CHANGED
@@ -14,7 +14,7 @@
14
14
 
15
15
  <p align="center">
16
16
  <strong>Find the fastest free coding model in seconds</strong><br>
17
- <sub>Ping 238 models across 25 AI Free providers in real-time </sub><br><sub> Install Free API endpoints to your favorite AI coding tool: <br>📦 OpenCode, 🦞 OpenClaw, 💘 Crush, 🪿 Goose, 🛠 Aider, 🐉 Qwen Code, 🤲 OpenHands, ⚡ Amp, π Pi, 🦘 Rovo or ♊ Gemini in one keystroke</sub>
17
+ <sub>Ping 238 models across 25 AI Free providers in real-time </sub><br> <sub> Install Free API endpoints to your favorite AI coding tool: <br>📦 OpenCode, 🦞 OpenClaw, 💘 Crush, 🪿 Goose, 🛠 Aider, 🐉 Qwen Code, 🤲 OpenHands, ⚡ Amp, 🔮 Hermes, ▶️ Continue, 🧠 Cline, 🛠️ Xcode, π Pi, 🦘 Rovo or ♊ Gemini in one keystroke</sub>
18
18
  </p>
19
19
 
20
20
 
@@ -159,6 +159,9 @@ free-coding-models --goose --tier S
159
159
  # "I want NVIDIA's top models only"
160
160
  free-coding-models --origin nvidia --tier S
161
161
 
162
+ # "I want the local web dashboard"
163
+ free-coding-models --web
164
+
162
165
  # "Start with an elite-focused preset, then adjust filters live"
163
166
  free-coding-models --premium
164
167
 
@@ -169,6 +172,8 @@ free-coding-models --tier S --json | jq -r '.[0].modelId'
169
172
  free-coding-models --openclaw --origin groq
170
173
  ```
171
174
 
175
+ When launching the web dashboard, `free-coding-models` prefers `http://localhost:3333`. If that port is already used by another app, it now auto-picks the next free local port and prints the exact URL to open.
176
+
172
177
  ### Tool launcher flags
173
178
 
174
179
  | Flag | Launches |
@@ -182,6 +187,10 @@ free-coding-models --openclaw --origin groq
182
187
  | `--qwen` | 🐉 Qwen Code |
183
188
  | `--openhands` | 🤲 OpenHands |
184
189
  | `--amp` | ⚡ Amp |
190
+ | `--hermes` | 🔮 Hermes |
191
+ | `--continue` | ▶️ Continue CLI |
192
+ | `--cline` | 🧠 Cline |
193
+ | `--xcode` | 🛠️ Xcode Intelligence |
185
194
  | `--pi` | π Pi |
186
195
  | `--rovo` | 🦘 Rovo Dev CLI |
187
196
  | `--gemini` | ♊ Gemini CLI |
@@ -46,6 +46,14 @@ async function main() {
46
46
  process.exit(1);
47
47
  }
48
48
 
49
+ // 📖 --web mode: launch the web dashboard instead of the TUI
50
+ if (cliArgs.webMode) {
51
+ const { startWebServer } = await import('../web/server.js')
52
+ const port = parseInt(process.env.FCM_PORT || '3333', 10)
53
+ await startWebServer(port, { open: true })
54
+ return
55
+ }
56
+
49
57
  // 📖 Load JSON config
50
58
  const config = loadConfig();
51
59
  ensureTelemetryConfig(config);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-coding-models",
3
- "version": "0.3.37",
3
+ "version": "0.3.41",
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,6 +41,7 @@
41
41
  "files": [
42
42
  "bin/",
43
43
  "src/",
44
+ "web/",
44
45
  "sources.js",
45
46
  "patch-openclaw.js",
46
47
  "patch-openclaw-models.js",
@@ -51,16 +52,25 @@
51
52
  "scripts": {
52
53
  "start": "node bin/free-coding-models.js",
53
54
  "test": "node --test test/test.js",
55
+ "dev:web": "node scripts/dev-web.mjs",
56
+ "build:web": "vite build",
57
+ "preview:web": "vite preview",
54
58
  "test:fcm": "node scripts/testfcm-runner.mjs",
55
59
  "test:fcm:mock": "node scripts/testfcm-runner.mjs --tool crush --tool-bin-dir test/fixtures/mock-bin"
56
60
  },
57
61
  "dependencies": {
58
- "chalk": "^5.4.1"
62
+ "chalk": "^5.6.2"
59
63
  },
64
+ "packageManager": "pnpm@10.24.0",
60
65
  "engines": {
61
66
  "node": ">=18.0.0"
62
67
  },
63
68
  "devDependencies": {
64
- "@mariozechner/terminalcp": "^1.3.3"
69
+ "@vitejs/plugin-react": "^6.0.1",
70
+ "agent-tui": "^1.0.1",
71
+ "react": "^19.2.4",
72
+ "react-dom": "^19.2.4",
73
+ "vite": "^8.0.5",
74
+ "vite-plus": "^0.1.16"
65
75
  }
66
76
  }
package/src/app.js CHANGED
@@ -114,6 +114,7 @@ import { parseTelemetryEnv, isTelemetryDebugEnabled, telemetryDebug, ensureTelem
114
114
  import { ensureFavoritesConfig, toFavoriteKey, syncFavoriteFlags, toggleFavoriteModel } from '../src/favorites.js'
115
115
  import { checkForUpdateDetailed, checkForUpdate, runUpdate, promptUpdateNotification, fetchLastReleaseDate } from './updater.js'
116
116
  import { promptApiKey } from '../src/setup.js'
117
+ import { syncShellEnv, ensureShellRcSource, promptShellEnvMigration, removeShellEnv } from '../src/shell-env.js'
117
118
  import { stripAnsi, maskApiKey, displayWidth, padEndDisplay, tintOverlayLines, keepOverlayTargetVisible, sliceOverlayLines, calculateViewport, sortResultsWithPinnedFavorites, adjustScrollOffset } from '../src/render-helpers.js'
118
119
  import { renderTable, PROVIDER_COLOR } from '../src/render-table.js'
119
120
  import { setOpenCodeModelData, startOpenCode, startOpenCodeDesktop } from '../src/opencode.js'
@@ -215,6 +216,31 @@ export async function runApp(cliArgs, config) {
215
216
  console.log()
216
217
  process.exit(1)
217
218
  }
219
+ // 📖 New users get shell env enabled by default
220
+ if (config.settings.shellEnvEnabled === undefined) {
221
+ config.settings.shellEnvEnabled = true
222
+ saveConfig(config)
223
+ syncShellEnv(config)
224
+ ensureShellRcSource()
225
+ }
226
+ }
227
+
228
+ // 📖 Shell env migration popup for existing users who haven't been asked yet
229
+ // 📖 Only show when user has keys but shellEnvEnabled is still undefined (never prompted)
230
+ if (hasAnyKey && config.settings.shellEnvEnabled === undefined) {
231
+ const choice = await promptShellEnvMigration(config)
232
+ if (choice === 'enable') {
233
+ if (!config.settings) config.settings = {}
234
+ config.settings.shellEnvEnabled = true
235
+ saveConfig(config)
236
+ syncShellEnv(config)
237
+ ensureShellRcSource()
238
+ } else if (choice === 'never') {
239
+ if (!config.settings) config.settings = {}
240
+ config.settings.shellEnvEnabled = false
241
+ saveConfig(config)
242
+ }
243
+ // 📖 'skip' leaves shellEnvEnabled undefined — will prompt again next launch
218
244
  }
219
245
 
220
246
  // 📖 Default mode: use the last persisted launcher choice when valid,
@@ -233,6 +259,10 @@ export async function runApp(cliArgs, config) {
233
259
  qwen: cliArgs.qwenMode,
234
260
  openhands: cliArgs.openHandsMode,
235
261
  amp: cliArgs.ampMode,
262
+ hermes: cliArgs.hermesMode,
263
+ 'continue': cliArgs.continueMode,
264
+ cline: cliArgs.clineMode,
265
+ xcode: cliArgs.xcodeMode,
236
266
  pi: cliArgs.piMode,
237
267
  rovo: cliArgs.rovoMode,
238
268
  gemini: cliArgs.geminiMode,
package/src/cli-help.js CHANGED
@@ -36,12 +36,14 @@ const ANALYSIS_FLAGS = [
36
36
  ]
37
37
 
38
38
  const CONFIG_FLAGS = [
39
+ { flag: '--web', description: 'Launch the web dashboard in your browser' },
39
40
  { flag: '--no-telemetry', description: 'Disable anonymous telemetry for this run' },
40
41
  { flag: '--help, -h', description: 'Print this help and exit' },
41
42
  ]
42
43
 
43
44
  const EXAMPLES = [
44
45
  'free-coding-models --help',
46
+ 'free-coding-models --web',
45
47
  'free-coding-models --openclaw --tier S',
46
48
  "free-coding-models --json | jq '.[0]'",
47
49
  ]
@@ -28,6 +28,9 @@ const TOOL_MODE_DESCRIPTIONS = {
28
28
  qwen: 'Launch Qwen Code using the selected provider model.',
29
29
  openhands: 'Launch OpenHands with the selected model endpoint.',
30
30
  amp: 'Launch Amp with this model as active target.',
31
+ hermes: 'Launch Hermes Agent with the selected model.',
32
+ 'continue': 'Launch Continue CLI with the selected model.',
33
+ cline: 'Launch Cline CLI with the selected model.',
31
34
  rovo: 'Rovo Dev CLI model (launch with Rovo tool only).',
32
35
  gemini: 'Gemini CLI model (launch with Gemini tool only).',
33
36
  }
package/src/config.js CHANGED
@@ -102,6 +102,7 @@
102
102
  import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync, unlinkSync, renameSync } from 'node:fs'
103
103
  import { homedir } from 'node:os'
104
104
  import { join } from 'node:path'
105
+ import { syncShellEnv } from './shell-env.js'
105
106
 
106
107
  // 📖 New JSON config path — stores all providers' API keys + enabled state
107
108
  export const CONFIG_PATH = join(homedir(), '.free-coding-models.json')
@@ -484,6 +485,12 @@ export function saveConfig(config, options = {}) {
484
485
  }
485
486
 
486
487
  replaceConfigContents(config, persistedConfig)
488
+
489
+ // 📖 Keep shell env file in sync when enabled
490
+ if (persistedConfig.settings?.shellEnvEnabled) {
491
+ try { syncShellEnv(persistedConfig) } catch { /* non-critical */ }
492
+ }
493
+
487
494
  return { success: true, backupCreated }
488
495
  } catch (verifyError) {
489
496
  // 📖 Verification failed - this is critical!
@@ -52,7 +52,7 @@ import { getToolMeta } from './tool-metadata.js'
52
52
  const DIRECT_INSTALL_UNSUPPORTED_PROVIDERS = new Set(['replicate', 'zai', 'rovo', 'gemini', 'opencode-zen'])
53
53
  // 📖 Install Endpoints only lists tools whose persisted config shape is actually supported here.
54
54
  // 📖 Claude Code, Codex, and Gemini stay out while their dedicated bridges are being rebuilt.
55
- const INSTALL_TARGET_MODES = ['opencode', 'opencode-desktop', 'openclaw', 'crush', 'goose', 'pi', 'aider', 'qwen', 'openhands', 'amp']
55
+ const INSTALL_TARGET_MODES = ['opencode', 'opencode-desktop', 'openclaw', 'crush', 'goose', 'pi', 'aider', 'qwen', 'openhands', 'amp', 'hermes', 'continue', 'cline']
56
56
 
57
57
  function getDefaultPaths() {
58
58
  const home = homedir()
@@ -33,6 +33,7 @@ import { loadConfig, replaceConfigContents } from './config.js'
33
33
  import { cleanupLegacyProxyArtifacts } from './legacy-proxy-cleanup.js'
34
34
  import { getLastLayout, COLUMN_SORT_MAP } from './render-table.js'
35
35
  import { cycleThemeSetting, detectActiveTheme } from './theme.js'
36
+ import { syncShellEnv, ensureShellRcSource, removeShellEnv } from './shell-env.js'
36
37
  import { buildCommandPaletteTree, flattenCommandTree, filterCommandPaletteEntries } from './command-palette.js'
37
38
  import { WIDTH_WARNING_MIN_COLS } from './constants.js'
38
39
  import { scanAllToolConfigs, softDeleteModel } from './installed-models-manager.js'
@@ -553,6 +554,19 @@ export function createKeyHandler(ctx) {
553
554
  applyThemeSetting(cycleThemeSetting(currentTheme))
554
555
  }
555
556
 
557
+ function toggleShellEnv() {
558
+ if (!state.config.settings) state.config.settings = {}
559
+ const currentlyEnabled = state.config.settings.shellEnvEnabled === true
560
+ state.config.settings.shellEnvEnabled = !currentlyEnabled
561
+ saveConfig(state.config)
562
+ if (!currentlyEnabled) {
563
+ syncShellEnv(state.config)
564
+ ensureShellRcSource()
565
+ } else {
566
+ removeShellEnv()
567
+ }
568
+ }
569
+
556
570
  function resetInstallEndpointsOverlay() {
557
571
  state.installEndpointsOpen = false
558
572
  state.installEndpointsPhase = 'providers'
@@ -1828,8 +1842,9 @@ export function createKeyHandler(ctx) {
1828
1842
  const favoritesModeRowIdx = themeRowIdx + 1
1829
1843
  const cleanupLegacyProxyRowIdx = favoritesModeRowIdx + 1
1830
1844
  const changelogViewRowIdx = cleanupLegacyProxyRowIdx + 1
1845
+ const shellEnvRowIdx = changelogViewRowIdx + 1
1831
1846
  // 📖 Profile system removed - API keys now persist permanently across all sessions
1832
- const maxRowIdx = changelogViewRowIdx
1847
+ const maxRowIdx = shellEnvRowIdx
1833
1848
 
1834
1849
  // 📖 Edit/Add-key mode: capture typed characters for the API key
1835
1850
  if (state.settingsEditMode || state.settingsAddKeyMode) {
@@ -1993,6 +2008,12 @@ export function createKeyHandler(ctx) {
1993
2008
  return
1994
2009
  }
1995
2010
 
2011
+ // 📖 Shell env row: Enter → toggle shell env export
2012
+ if (state.settingsCursor === shellEnvRowIdx) {
2013
+ toggleShellEnv()
2014
+ return
2015
+ }
2016
+
1996
2017
  // 📖 Profile system removed - API keys now persist permanently across all sessions
1997
2018
 
1998
2019
  // 📖 Enter edit mode for the selected provider's key
@@ -2010,6 +2031,11 @@ export function createKeyHandler(ctx) {
2010
2031
  || state.settingsCursor === cleanupLegacyProxyRowIdx
2011
2032
  || state.settingsCursor === changelogViewRowIdx
2012
2033
  ) return
2034
+ // 📖 Shell env toggle
2035
+ if (state.settingsCursor === shellEnvRowIdx) {
2036
+ toggleShellEnv()
2037
+ return
2038
+ }
2013
2039
  // 📖 Theme configuration cycle inside settings
2014
2040
  if (state.settingsCursor === themeRowIdx) {
2015
2041
  cycleGlobalTheme()
package/src/overlays.js CHANGED
@@ -120,6 +120,7 @@ export function createOverlayRenderers(state, deps) {
120
120
  const favoritesModeRowIdx = themeRowIdx + 1
121
121
  const cleanupLegacyProxyRowIdx = favoritesModeRowIdx + 1
122
122
  const changelogViewRowIdx = cleanupLegacyProxyRowIdx + 1
123
+ const shellEnvRowIdx = changelogViewRowIdx + 1
123
124
  const EL = '\x1b[K'
124
125
  const lines = []
125
126
  const cursorLineByRow = {}
@@ -272,6 +273,15 @@ export function createOverlayRenderers(state, deps) {
272
273
  cursorLineByRow[changelogViewRowIdx] = lines.length
273
274
  lines.push(state.settingsCursor === changelogViewRowIdx ? themeColors.bgCursorSettingsList(changelogViewRow) : changelogViewRow)
274
275
 
276
+ // 📖 Shell env toggle — expose API keys as shell environment variables
277
+ const shellEnvEnabled = state.config.settings?.shellEnvEnabled === true
278
+ const shellEnvStatus = shellEnvEnabled
279
+ ? themeColors.successBold('✅ Enabled — keys available in shell')
280
+ : themeColors.dim('❌ Disabled')
281
+ const shellEnvRow = `${bullet(state.settingsCursor === shellEnvRowIdx)}${themeColors.textBold('Shell Env Export').padEnd(44)} ${shellEnvStatus}`
282
+ cursorLineByRow[shellEnvRowIdx] = lines.length
283
+ lines.push(state.settingsCursor === shellEnvRowIdx ? themeColors.bgCursorSettingsList(shellEnvRow) : shellEnvRow)
284
+
275
285
  // 📖 Profile system removed - API keys now persist permanently across all sessions
276
286
 
277
287
  lines.push('')
@@ -313,7 +323,7 @@ export function createOverlayRenderers(state, deps) {
313
323
  // 📖 Mouse support: record layout so click handler can map Y → settingsCursor
314
324
  overlayLayout.settingsCursorToLine = { ...cursorLineByRow }
315
325
  overlayLayout.settingsScrollOffset = offset
316
- overlayLayout.settingsMaxRow = changelogViewRowIdx
326
+ overlayLayout.settingsMaxRow = shellEnvRowIdx
317
327
 
318
328
  const tintedLines = tintOverlayLines(visible, themeColors.overlayBgSettings, state.terminalCols)
319
329
  const cleared = tintedLines.map(l => l + EL)