free-coding-models 0.4.3 → 0.5.0

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 (101) hide show
  1. package/bin/free-coding-models.js +9 -9
  2. package/changelog/v0.5.0.md +15 -0
  3. package/package.json +1 -1
  4. package/src/{analysis.js → core/analysis.js} +5 -5
  5. package/src/{constants.js → core/constants.js} +1 -1
  6. package/src/{endpoint-installer.js → core/endpoint-installer.js} +1 -1
  7. package/src/{installed-models-manager.js → core/installed-models-manager.js} +1 -1
  8. package/src/{kilo.js → core/kilo.js} +1 -2
  9. package/src/{openclaw.js → core/openclaw.js} +1 -1
  10. package/src/{opencode.js → core/opencode.js} +2 -1
  11. package/src/{ping-loop.js → core/ping-loop.js} +1 -1
  12. package/src/{router-daemon.js → core/router-daemon.js} +3 -3
  13. package/src/{router-dashboard.js → core/router-dashboard.js} +2 -2
  14. package/src/{setup.js → core/setup.js} +1 -1
  15. package/src/{sync-set.js → core/sync-set.js} +1 -1
  16. package/src/{telemetry.js → core/telemetry.js} +1 -1
  17. package/src/{tool-launchers.js → core/tool-launchers.js} +2 -2
  18. package/src/{updater.js → core/updater.js} +1 -1
  19. package/src/{app.js → tui/app.js} +38 -38
  20. package/src/{cli-help.js → tui/cli-help.js} +1 -1
  21. package/src/{command-palette.js → tui/command-palette.js} +2 -2
  22. package/src/{key-handler.js → tui/key-handler.js} +11 -11
  23. package/src/{overlays.js → tui/overlays.js} +2 -2
  24. package/src/{render-helpers.js → tui/render-helpers.js} +2 -2
  25. package/src/{render-table.js → tui/render-table.js} +8 -8
  26. package/src/{tui-filters.js → tui/tui-filters.js} +3 -3
  27. package/src/{tui-state.js → tui/tui-state.js} +1 -1
  28. package/web/README.md +43 -0
  29. package/web/dist/assets/{index-Czwis3ab.js → index-CvMUM9Jr.js} +1 -1
  30. package/web/dist/index.html +1 -1
  31. package/web/server.js +3 -3
  32. package/src/graphify-out/cache/089db1c1def873cf6d112f1590da4490e61e691aff0db41e006aa2fb15ba0656.json +0 -1
  33. package/src/graphify-out/cache/0b510b53cf1a1393fb52b1fc3bbbf88b63938e961ec5b82119a2e9715fee8bd7.json +0 -1
  34. package/src/graphify-out/cache/0ec9a95a326bde58e0316889018b278062d06d494d0f31ba177c9de71e5fed2d.json +0 -1
  35. package/src/graphify-out/cache/1548663a24a68dce740ebab1bd1d3091048c9604e9d067a1650a42a6d82541d4.json +0 -1
  36. package/src/graphify-out/cache/1783af63cb6d0dfb4d469009f71ac83a74ba0b33d48186ff2c6e63f9429e900a.json +0 -1
  37. package/src/graphify-out/cache/1e109f5eb5dc4fd285871c3613e32b6b14a8c225f4080ee34b51c7e1a1764571.json +0 -1
  38. package/src/graphify-out/cache/1eb24dbeb69b46c8bc1caf925df2f2a964af0f33aea143adf8ddf88e017db6ca.json +0 -1
  39. package/src/graphify-out/cache/21e1bcfed11685e8347243f9d8516072dda183266a4bfe22c52fb31753a446c8.json +0 -1
  40. package/src/graphify-out/cache/2327473478b9c4b1940bf7ef66c9ee960b3cba8d5302e56b625df8274246e0b4.json +0 -1
  41. package/src/graphify-out/cache/25955b81fd25454c8fa90fb71a47db8d1215cf621beb8ff3cbd580aaf011b4f3.json +0 -1
  42. package/src/graphify-out/cache/2739677f19c702f88f3de0a0bac475066adbda98709907ad3de967aef689f86d.json +0 -1
  43. package/src/graphify-out/cache/2bba03422f6b3ee7f5b5d29cc90314a064d259e5822a176657bda3e04505cf00.json +0 -1
  44. package/src/graphify-out/cache/2ddf1d2c6d10147b0402446bc71a7988187b79b6210dd7e7250be8c555b9ff35.json +0 -1
  45. package/src/graphify-out/cache/2ee07457a5767c95a57f8e9eb95b28f800044f35666e0715e9d88ad1103a092e.json +0 -1
  46. package/src/graphify-out/cache/2fe9f75dc2951c417f2c8dd22749092cf550dc67599f1c8d1866900dc6e9154e.json +0 -1
  47. package/src/graphify-out/cache/41c4b7c27e7fc3e2948d3a4bf95a72de2ed9a6f0463994babdce8ed2cc84598c.json +0 -1
  48. package/src/graphify-out/cache/5028defd54b7fbd3c7e444973e493de036e097e9b1d2a7cae7f19b88d68aacde.json +0 -1
  49. package/src/graphify-out/cache/5b133aba3fb16410c5b1fdbd1730039fc7fa1ac93abd99d7be08f60da70fc8d4.json +0 -1
  50. package/src/graphify-out/cache/74252e5b0978d85ab3421a3de1a9384aa282ffd2be2cfe7db2530139089f4275.json +0 -1
  51. package/src/graphify-out/cache/7695ebeea056095edd14332963cc43354ef3a097caf46f1e28d0f01369642901.json +0 -1
  52. package/src/graphify-out/cache/777aa7085c395a935c6556bbde182cd871edb61f3a685ed8068ec0c8f6fb0075.json +0 -1
  53. package/src/graphify-out/cache/82a723881980e82273c113def8315533d7da28827e300413d9ad30f27b7407df.json +0 -1
  54. package/src/graphify-out/cache/86b87c9603e6cd188f42c7eed3b86c291d48a781c223a707e74f3e7ed0c02a21.json +0 -1
  55. package/src/graphify-out/cache/890fead9a78cadaed560a2d2453916121fa605c3e43a334910ac4bc951a9ef6d.json +0 -1
  56. package/src/graphify-out/cache/89d3ea66f52783caa775ef9a30923d7d6225e1d8ae9e962f4741b8c7785dab1e.json +0 -1
  57. package/src/graphify-out/cache/8cc82cd9edce41f0e1c092f14a94fd52bf847addf3237b616dc5a9e505bd05bd.json +0 -1
  58. package/src/graphify-out/cache/93ba2e25e3ff7ad525f397902345fbd375df7315de7b402e20cc803c14eccde8.json +0 -1
  59. package/src/graphify-out/cache/99beed29580b9c7bfecfee794cb3d8e535fcf0eb3b92113108f88bdd0a8e79b3.json +0 -1
  60. package/src/graphify-out/cache/aeeb931fa477c65ce2e51d8149957350fa54225c613222bbbe8448998d1afd3d.json +0 -1
  61. package/src/graphify-out/cache/baf91bef5b5ecb2a476433b6cc0c48c563c54ee2d07fc3c192e543685e3e7222.json +0 -1
  62. package/src/graphify-out/cache/bd98b94ac4e9b92b6336d47b26e0366b51a4eaf0711d722f05f98dfae23ab42b.json +0 -1
  63. package/src/graphify-out/cache/bfcb51e9328e9cbfbee4f6fee0f56635d7b03488addc9f6c4e4b190b70a73362.json +0 -1
  64. package/src/graphify-out/cache/c0d3dabeb093aa758c49eadf41b87ecc96a16c1449c2670aaf48cbfc891d8da6.json +0 -1
  65. package/src/graphify-out/cache/c20d6630236f473c1406068c3ae205853e649b216495c93dfec055dd222c55cf.json +0 -1
  66. package/src/graphify-out/cache/c22b9122816bebce0a2f79af41a986559d01e00163dbcd579c5755621b4cb483.json +0 -1
  67. package/src/graphify-out/cache/ca556ec14453ddb8f9e0c5a832dac90d77111b9bad5f8c2d80d272e2e7a06371.json +0 -1
  68. package/src/graphify-out/cache/d6dbc9135dfa35a756b3b09b06700e4bc229fdccba11bb963f2ba44028e0bbae.json +0 -1
  69. package/src/graphify-out/cache/e1cf71276f1779d0fa075f79bd7c8a9fd0b8eef6932ac043137451b7c7fa7cbe.json +0 -1
  70. package/src/graphify-out/cache/e4b3be14494467df2d2ed389bc4f18f099021cb5bc355b901fa88387b2d8b8a2.json +0 -1
  71. package/src/graphify-out/cache/eaea0dded097f6f9553b654220046c6ec0c9be592a5973d906564ee60af34e0d.json +0 -1
  72. package/src/graphify-out/cache/ef07d0cd2675d1f79d2a2fdbf3bc3319687638751e9ce89b0d0d97ed1cd9f7e1.json +0 -1
  73. package/src/graphify-out/cache/f81272d6eb8aaff9e96d5a1d9f06777db70ac3652a646b951ded51f79871d733.json +0 -1
  74. package/src/graphify-out/cache/f9619dd92186f75a6dbda937e0c606647153918524cdb5763f956e6ec2a9e386.json +0 -1
  75. package/src/graphify-out/cache/fd88b1b2ff4bfcae08559d9c2aaeeb9a3f1e2f5cd8928762c311196956c170a5.json +0 -1
  76. /package/src/{benchmark.js → core/benchmark.js} +0 -0
  77. /package/src/{cache.js → core/cache.js} +0 -0
  78. /package/src/{changelog-loader.js → core/changelog-loader.js} +0 -0
  79. /package/src/{config.js → core/config.js} +0 -0
  80. /package/src/{favorites.js → core/favorites.js} +0 -0
  81. /package/src/{kilo-config.js → core/kilo-config.js} +0 -0
  82. /package/src/{legacy-proxy-cleanup.js → core/legacy-proxy-cleanup.js} +0 -0
  83. /package/src/{model-merger.js → core/model-merger.js} +0 -0
  84. /package/src/{opencode-config.js → core/opencode-config.js} +0 -0
  85. /package/src/{ping.js → core/ping.js} +0 -0
  86. /package/src/{product-flags.js → core/product-flags.js} +0 -0
  87. /package/src/{provider-metadata.js → core/provider-metadata.js} +0 -0
  88. /package/src/{provider-quota-fetchers.js → core/provider-quota-fetchers.js} +0 -0
  89. /package/src/{quota-capabilities.js → core/quota-capabilities.js} +0 -0
  90. /package/src/{security.js → core/security.js} +0 -0
  91. /package/src/{shell-env.js → core/shell-env.js} +0 -0
  92. /package/src/{testfcm.js → core/testfcm.js} +0 -0
  93. /package/src/{token-usage-reader.js → core/token-usage-reader.js} +0 -0
  94. /package/src/{tool-bootstrap.js → core/tool-bootstrap.js} +0 -0
  95. /package/src/{tool-metadata.js → core/tool-metadata.js} +0 -0
  96. /package/src/{usage-reader.js → core/usage-reader.js} +0 -0
  97. /package/src/{utils.js → core/utils.js} +0 -0
  98. /package/src/{mouse.js → tui/mouse.js} +0 -0
  99. /package/src/{theme.js → tui/theme.js} +0 -0
  100. /package/src/{tier-colors.js → tui/tier-colors.js} +0 -0
  101. /package/src/{ui-config.js → tui/ui-config.js} +0 -0
@@ -10,13 +10,13 @@ if (process.argv.includes('--dev')) {
10
10
  }
11
11
 
12
12
  import chalk from 'chalk';
13
- import { parseArgs, TIER_LETTER_MAP } from '../src/utils.js';
14
- import { loadConfig } from '../src/config.js';
15
- import { ensureTelemetryConfig } from '../src/telemetry.js';
16
- import { ensureFavoritesConfig } from '../src/favorites.js';
17
- import { buildCliHelpText } from '../src/cli-help.js';
18
- import { ALT_LEAVE } from '../src/constants.js';
19
- import { runApp } from '../src/app.js';
13
+ import { parseArgs, TIER_LETTER_MAP } from '../src/core/utils.js';
14
+ import { loadConfig } from '../src/core/config.js';
15
+ import { ensureTelemetryConfig } from '../src/core/telemetry.js';
16
+ import { ensureFavoritesConfig } from '../src/core/favorites.js';
17
+ import { buildCliHelpText } from '../src/tui/cli-help.js';
18
+ import { ALT_LEAVE } from '../src/core/constants.js';
19
+ import { runApp } from '../src/tui/app.js';
20
20
 
21
21
  // Global error handlers to ensure terminal is restored if something crashes catastrophically
22
22
  process.on('uncaughtException', (err) => {
@@ -61,7 +61,7 @@ async function main() {
61
61
  runRouterDaemon,
62
62
  startRouterDaemonBackground,
63
63
  stopRouterDaemon,
64
- } = await import('../src/router-daemon.js');
64
+ } = await import('../src/core/router-daemon.js');
65
65
 
66
66
  if (cliArgs.daemonMode) {
67
67
  await runRouterDaemon();
@@ -80,7 +80,7 @@ async function main() {
80
80
 
81
81
  // 📖 --sync-set [name] — auto-discover, probe, and populate a router set
82
82
  if (cliArgs.syncSetMode) {
83
- const { syncSet } = await import('../src/sync-set.js');
83
+ const { syncSet } = await import('../src/core/sync-set.js');
84
84
  const result = await syncSet({ name: cliArgs.syncSetName || 'auto' });
85
85
  console.log(JSON.stringify(result, null, 2));
86
86
  process.exit(result.ok ? 0 : 1);
@@ -0,0 +1,15 @@
1
+ # Changelog v0.5.0 - 2026-05-31
2
+
3
+ ### Added
4
+ - **Final Desktop PRD**: Completed `desktop/prd-desktop.md` defining Tauri v2 shell, Bun/Node sidecar integration, zombie process prevention (parent-death binding), and loopback-only security models.
5
+ - **Sub-Project Documentation**: Added `/web/README.md` and `/desktop/README.md` to guide modular frontend and desktop sidecar development.
6
+
7
+ ### Changed
8
+ - **Architectural Segregation (Core vs. TUI)**: Partitioned the codebase into isolated layers:
9
+ - `src/core/` for 100% shared business logic (routing, scoring, pings, daemon, quota, and config management) shared 1:1 with CLI, Docker, and Tauri sidecar.
10
+ - `src/tui/` for terminal-only layouts, keystroke handlers, ANSI renderers, and interactive loops.
11
+ - **Relative Import Pathways**: Restructured and re-routed relative imports across all entry points, TUI assets, and core logic files.
12
+ - **Port Consistency**: Standardized loopback proxy and daemon endpoints on port `19280` across all CLI, Docker, and Desktop configurations.
13
+
14
+ ### Fixed
15
+ - **Clean workspace**: Fully purged `.kandown` task manager logs, `.claude-mcp.json`, and stale planning/analysis documents to ensure a zero-noise, lightweight npm package profile.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-coding-models",
3
- "version": "0.4.3",
3
+ "version": "0.5.0",
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",
@@ -35,11 +35,11 @@
35
35
  * @see {@link ../src/ping.js} ping implementation
36
36
  */
37
37
 
38
- import { MODELS, sources } from '../sources.js'
39
- import { findBestModel, filterByTier, formatCtxWindow, labelFromId, TIER_LETTER_MAP } from '../src/utils.js'
40
- import { isProviderEnabled, getApiKey } from '../src/config.js'
41
- import { ping } from '../src/ping.js'
42
- import { PROVIDER_COLOR } from './render-table.js'
38
+ import { MODELS, sources } from '../../sources.js'
39
+ import { findBestModel, filterByTier, formatCtxWindow, labelFromId, TIER_LETTER_MAP } from './utils.js'
40
+ import { isProviderEnabled, getApiKey } from './config.js'
41
+ import { ping } from './ping.js'
42
+ import { PROVIDER_COLOR } from '../tui/render-table.js'
43
43
  import chalk from 'chalk'
44
44
 
45
45
  // 📖 runFiableMode: Analyze models for reliability over 10 seconds, output the best one.
@@ -48,7 +48,7 @@ import chalk from 'chalk'
48
48
  // 📖 \x1b[?7l disables auto-wrap so wide rows clip at the right edge instead of
49
49
  // 📖 wrapping to the next line (which would double the row height and overflow).
50
50
  // 📖 Mouse tracking sequences are appended/prepended so clicks and scroll work in the TUI.
51
- import { MOUSE_ENABLE, MOUSE_DISABLE } from './mouse.js'
51
+ import { MOUSE_ENABLE, MOUSE_DISABLE } from '../tui/mouse.js'
52
52
 
53
53
  export const ALT_ENTER = '\x1b[?1049h\x1b[?25l\x1b[?7l' + MOUSE_ENABLE
54
54
  export const ALT_LEAVE = MOUSE_DISABLE + '\x1b[?7h\x1b[?1049l\x1b[?25h'
@@ -43,7 +43,7 @@
43
43
  import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
44
44
  import { homedir } from 'node:os'
45
45
  import { dirname, join } from 'node:path'
46
- import { MODELS, sources } from '../sources.js'
46
+ import { MODELS, sources } from '../../sources.js'
47
47
  import { getApiKey, saveConfig } from './config.js'
48
48
  import { ENV_VAR_NAMES, PROVIDER_METADATA } from './provider-metadata.js'
49
49
  import { getToolMeta } from './tool-metadata.js'
@@ -48,7 +48,7 @@
48
48
  import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'node:fs'
49
49
  import { homedir } from 'node:os'
50
50
  import { join, dirname } from 'node:path'
51
- import { sources } from '../sources.js'
51
+ import { sources } from '../../sources.js'
52
52
 
53
53
  const BACKUP_PATH = join(homedir(), '.free-coding-models-backups.json')
54
54
 
@@ -4,12 +4,11 @@
4
4
  */
5
5
 
6
6
  import chalk from 'chalk'
7
- import { PROVIDER_COLOR } from './render-table.js'
7
+ import { sources } from '../../sources.js'
8
8
  import { loadKiloConfig, saveKiloConfig, getKiloConfigPath } from './kilo-config.js'
9
9
  import { getApiKey } from './config.js'
10
10
  import { ENV_VAR_NAMES, OPENCODE_MODEL_MAP } from './provider-metadata.js'
11
11
  import { resolveToolBinaryPath } from './tool-bootstrap.js'
12
- import { sources } from '../sources.js'
13
12
 
14
13
  // 📖 Map source model IDs to Kilo built-in IDs (same as OpenCode).
15
14
  function getKiloModelId(providerKey, modelId) {
@@ -31,7 +31,7 @@ import { homedir } from 'os'
31
31
  import { dirname, join } from 'path'
32
32
  import { installProviderEndpoints } from './endpoint-installer.js'
33
33
  import { ENV_VAR_NAMES } from './provider-metadata.js'
34
- import { PROVIDER_COLOR } from './render-table.js'
34
+ import { PROVIDER_COLOR } from '../tui/render-table.js'
35
35
  import { resolveToolBinaryPath } from './tool-bootstrap.js'
36
36
  import { getApiKey } from './config.js'
37
37
  import { syncShellEnv } from './shell-env.js'
@@ -28,7 +28,8 @@ import { request as httpsRequest } from 'https'
28
28
  import { homedir } from 'os'
29
29
  import { join } from 'path'
30
30
  import { copyFileSync, existsSync } from 'fs'
31
- import { PROVIDER_COLOR } from './render-table.js'
31
+ import { PROVIDER_COLOR } from '../tui/render-table.js'
32
+ import { sources } from '../../sources.js'
32
33
  import { loadOpenCodeConfig, saveOpenCodeConfig } from './opencode-config.js'
33
34
  import { getApiKey } from './config.js'
34
35
  import { ENV_VAR_NAMES, OPENCODE_MODEL_MAP, isWindows, isMac, isLinux } from './provider-metadata.js'
@@ -28,7 +28,7 @@ import {
28
28
  PING_MODE_INTERVALS,
29
29
  SPEED_MODE_DURATION_MS,
30
30
  IDLE_SLOW_AFTER_MS,
31
- } from './tui-state.js'
31
+ } from '../tui/tui-state.js'
32
32
 
33
33
  /**
34
34
  * 📖 createPingLoop: Build the ping loop control functions for a given TUI state.
@@ -37,7 +37,7 @@ import { randomUUID } from 'node:crypto'
37
37
  import { appendFileSync, renameSync, statSync, unlinkSync, writeFileSync } from 'node:fs'
38
38
  import { homedir } from 'node:os'
39
39
  import { fileURLToPath } from 'node:url'
40
- import { MODELS, sources } from '../sources.js'
40
+ import { MODELS, sources } from '../../sources.js'
41
41
  import {
42
42
  CONFIG_PATH,
43
43
  DEFAULT_ROUTER_SETTINGS,
@@ -71,7 +71,7 @@ export function getRouterPortRange() {
71
71
  }
72
72
 
73
73
  const __dirname = dirname(fileURLToPath(import.meta.url))
74
- const CLI_ENTRY_PATH = join(__dirname, '..', 'bin', 'free-coding-models.js')
74
+ const CLI_ENTRY_PATH = join(__dirname, '..', '..', 'bin', 'free-coding-models.js')
75
75
  const MAX_BODY_BYTES = 10 * 1024 * 1024
76
76
  const MAX_REQUEST_LOG = 200
77
77
  const MAX_SSE_CLIENTS = 10
@@ -365,7 +365,7 @@ function getWebConfigPayload(runtime) {
365
365
  return { providers, totalModels: MODELS.length }
366
366
  }
367
367
 
368
- const WEB_DIST_DIR = resolvePath(__dirname, '..', 'web', 'dist')
368
+ const WEB_DIST_DIR = resolvePath(__dirname, '..', '..', 'web', 'dist')
369
369
 
370
370
  function serveStaticFromDist(res, absPath) {
371
371
  const ext = absPath.slice(absPath.lastIndexOf('.'))
@@ -41,9 +41,9 @@
41
41
 
42
42
  import chalk from 'chalk'
43
43
  import { existsSync, readFileSync } from 'node:fs'
44
- import { displayWidth, padEndDisplay, sliceOverlayLines, tintOverlayLines } from './render-helpers.js'
44
+ import { displayWidth, padEndDisplay, sliceOverlayLines, tintOverlayLines } from '../tui/render-helpers.js'
45
45
  import { ROUTER_DEFAULT_PORT, ROUTER_MAX_PORT, ROUTER_PID_PATH, ROUTER_PORT_PATH, getRouterPortRange } from './router-daemon.js'
46
- import { themeColors, getTierRgb } from './theme.js'
46
+ import { themeColors, getTierRgb } from '../tui/theme.js'
47
47
  import { formatTokenTotalCompact } from './token-usage-reader.js'
48
48
  import { sendUsageTelemetry } from './telemetry.js'
49
49
  import { getAvg, getVerdict } from './utils.js'
@@ -35,7 +35,7 @@
35
35
 
36
36
  import chalk from 'chalk'
37
37
  import { createRequire } from 'module'
38
- import { sources } from '../sources.js'
38
+ import { sources } from '../../sources.js'
39
39
  import { PROVIDER_METADATA } from './provider-metadata.js'
40
40
  import { saveConfig } from './config.js'
41
41
 
@@ -28,7 +28,7 @@
28
28
  * @see ../sources.js — model catalog
29
29
  */
30
30
 
31
- import { sources } from '../sources.js'
31
+ import { sources } from '../../sources.js'
32
32
  import {
33
33
  CONFIG_PATH,
34
34
  getApiKey,
@@ -51,7 +51,7 @@ import { createRequire } from 'module'
51
51
  import { saveConfig } from './config.js'
52
52
 
53
53
  const require = createRequire(import.meta.url)
54
- const pkg = require('../package.json')
54
+ const pkg = require('../../package.json')
55
55
  const LOCAL_VERSION = pkg.version
56
56
 
57
57
  // 📖 PostHog capture endpoint and defaults.
@@ -41,8 +41,8 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync } from
41
41
  import { homedir } from 'os'
42
42
  import { dirname, join } from 'path'
43
43
  import { spawn, spawnSync } from 'child_process'
44
- import { sources } from '../sources.js'
45
- import { PROVIDER_COLOR } from './render-table.js'
44
+ import { sources } from '../../sources.js'
45
+ import { PROVIDER_COLOR } from '../tui/render-table.js'
46
46
  import { getApiKey } from './config.js'
47
47
  import { ENV_VAR_NAMES, isWindows } from './provider-metadata.js'
48
48
  import { getToolMeta, TOOL_METADATA } from './tool-metadata.js'
@@ -46,7 +46,7 @@ import { accessSync, constants } from 'fs'
46
46
 
47
47
  const require = createRequire(import.meta.url)
48
48
  const readline = require('readline')
49
- const pkg = require('../package.json')
49
+ const pkg = require('../../package.json')
50
50
  const LOCAL_VERSION = pkg.version
51
51
 
52
52
  /**
@@ -96,45 +96,45 @@ import { readFileSync, writeFileSync, existsSync, copyFileSync, mkdirSync } from
96
96
  import { randomUUID } from 'crypto'
97
97
  import { homedir } from 'os'
98
98
  import { join, dirname } from 'path'
99
- import { MODELS, sources } from '../sources.js'
100
- 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, formatResultsAsJSON } from '../src/utils.js'
101
- import { loadConfig, saveConfig, getApiKey, resolveApiKeys, addApiKey, removeApiKey, isProviderEnabled, persistApiKeysForProvider } from '../src/config.js'
102
- import { buildMergedModels } from '../src/model-merger.js'
103
- import { loadOpenCodeConfig, saveOpenCodeConfig } from '../src/opencode-config.js'
104
- import { usageForRow as _usageForRow } from '../src/usage-reader.js'
105
- import { buildProviderModelTokenKey, loadTokenUsageByProviderModel } from '../src/token-usage-reader.js'
106
- import { parseOpenRouterResponse, fetchProviderQuota as _fetchProviderQuotaFromModule } from '../src/provider-quota-fetchers.js'
107
- import { isKnownQuotaTelemetry } from '../src/quota-capabilities.js'
108
- import { ALT_ENTER, ALT_LEAVE, ALT_HOME, PING_TIMEOUT, PING_INTERVAL, FPS, COL_MODEL, COL_MS, CELL_W, FRAMES, TIER_CYCLE, VERDICT_CYCLE, HEALTH_CYCLE, SETTINGS_OVERLAY_BG, HELP_OVERLAY_BG, RECOMMEND_OVERLAY_BG, OVERLAY_PANEL_WIDTH, TABLE_HEADER_LINES, TABLE_FOOTER_LINES, TABLE_FIXED_LINES, WIDTH_WARNING_MIN_COLS, msCell, spinCell } from '../src/constants.js'
109
- import { TIER_COLOR } from '../src/tier-colors.js'
110
- import { resolveCloudflareUrl, buildPingRequest, ping, extractQuotaPercent, getProviderQuotaPercentCached, usagePlaceholderForProvider } from '../src/ping.js'
111
- import { runFiableMode, filterByTierOrExit, fetchOpenRouterFreeModels } from '../src/analysis.js'
112
- import { PROVIDER_METADATA, ENV_VAR_NAMES, isWindows, isMac } from '../src/provider-metadata.js'
113
- import { parseTelemetryEnv, isTelemetryDebugEnabled, telemetryDebug, ensureTelemetryConfig, getTelemetryDistinctId, getTelemetrySystem, getTelemetryTerminal, isTelemetryEnabled, sendUsageTelemetry } from '../src/telemetry.js'
114
- import { ensureFavoritesConfig, toFavoriteKey, syncFavoriteFlags, toggleFavoriteModel, reorderFavorite, pruneOrphanedFavorites } from '../src/favorites.js'
115
- import { checkForUpdateDetailed, checkForUpdate, runUpdate, fetchLastReleaseDate } from './updater.js'
99
+ import { MODELS, sources } from '../../sources.js'
100
+ 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, formatResultsAsJSON } from '../core/utils.js'
101
+ import { loadConfig, saveConfig, getApiKey, resolveApiKeys, addApiKey, removeApiKey, isProviderEnabled, persistApiKeysForProvider } from '../core/config.js'
102
+ import { buildMergedModels } from '../core/model-merger.js'
103
+ import { loadOpenCodeConfig, saveOpenCodeConfig } from '../core/opencode-config.js'
104
+ import { usageForRow as _usageForRow } from '../core/usage-reader.js'
105
+ import { buildProviderModelTokenKey, loadTokenUsageByProviderModel } from '../core/token-usage-reader.js'
106
+ import { parseOpenRouterResponse, fetchProviderQuota as _fetchProviderQuotaFromModule } from '../core/provider-quota-fetchers.js'
107
+ import { isKnownQuotaTelemetry } from '../core/quota-capabilities.js'
108
+ import { ALT_ENTER, ALT_LEAVE, ALT_HOME, PING_TIMEOUT, PING_INTERVAL, FPS, COL_MODEL, COL_MS, CELL_W, FRAMES, TIER_CYCLE, VERDICT_CYCLE, HEALTH_CYCLE, SETTINGS_OVERLAY_BG, HELP_OVERLAY_BG, RECOMMEND_OVERLAY_BG, OVERLAY_PANEL_WIDTH, TABLE_HEADER_LINES, TABLE_FOOTER_LINES, TABLE_FIXED_LINES, WIDTH_WARNING_MIN_COLS, msCell, spinCell } from '../core/constants.js'
109
+ import { TIER_COLOR } from './tier-colors.js'
110
+ import { resolveCloudflareUrl, buildPingRequest, ping, extractQuotaPercent, getProviderQuotaPercentCached, usagePlaceholderForProvider } from '../core/ping.js'
111
+ import { runFiableMode, filterByTierOrExit, fetchOpenRouterFreeModels } from '../core/analysis.js'
112
+ import { PROVIDER_METADATA, ENV_VAR_NAMES, isWindows, isMac } from '../core/provider-metadata.js'
113
+ import { parseTelemetryEnv, isTelemetryDebugEnabled, telemetryDebug, ensureTelemetryConfig, getTelemetryDistinctId, getTelemetrySystem, getTelemetryTerminal, isTelemetryEnabled, sendUsageTelemetry } from '../core/telemetry.js'
114
+ import { ensureFavoritesConfig, toFavoriteKey, syncFavoriteFlags, toggleFavoriteModel, reorderFavorite, pruneOrphanedFavorites } from '../core/favorites.js'
115
+ import { checkForUpdateDetailed, checkForUpdate, runUpdate, fetchLastReleaseDate } from '../core/updater.js'
116
116
  import { createTuiState, PING_MODE_INTERVALS, PING_MODE_CYCLE, SPEED_MODE_DURATION_MS, IDLE_SLOW_AFTER_MS, intervalToPingMode } from './tui-state.js'
117
- import { createPingLoop } from './ping-loop.js'
117
+ import { createPingLoop } from '../core/ping-loop.js'
118
118
  import { createTuiFilters } from './tui-filters.js'
119
- import { promptApiKey } from '../src/setup.js'
120
- import { syncShellEnv, ensureShellRcSource, removeShellEnv } from '../src/shell-env.js'
121
- import { stripAnsi, maskApiKey, displayWidth, padEndDisplay, tintOverlayLines, keepOverlayTargetVisible, sliceOverlayLines, calculateViewport, sortResultsWithPinnedFavorites, adjustScrollOffset } from '../src/render-helpers.js'
122
- import { renderTable, PROVIDER_COLOR } from '../src/render-table.js'
123
- import { setOpenCodeModelData, startOpenCode, startOpenCodeDesktop, startOpenCodeWeb } from '../src/opencode.js'
124
- import { startKilo } from '../src/kilo.js'
125
- import { startOpenClaw } from '../src/openclaw.js'
126
- import { createOverlayRenderers } from '../src/overlays.js'
127
- import { createKeyHandler, createMouseEventHandler } from '../src/key-handler.js'
128
- import { createMouseHandler, containsMouseSequence } from '../src/mouse.js'
129
- import { stopRouterDashboardClient } from '../src/router-dashboard.js'
130
- import { getToolModeOrder, getToolMeta } from '../src/tool-metadata.js'
131
- import { startExternalTool } from '../src/tool-launchers.js'
132
- import { getToolInstallPlan, installToolWithPlan, isToolInstalled } from '../src/tool-bootstrap.js'
133
- import { getConfiguredInstallableProviders, installProviderEndpoints, refreshInstalledEndpoints, getInstallTargetModes, getProviderCatalogModels } from '../src/endpoint-installer.js'
134
- import { loadCache, saveCache, clearCache, getCacheAge } from '../src/cache.js'
135
- import { checkConfigSecurity } from '../src/security.js'
136
- import { buildCliHelpText } from '../src/cli-help.js'
137
- import { detectActiveTheme } from '../src/theme.js'
119
+ import { promptApiKey } from '../core/setup.js'
120
+ import { syncShellEnv, ensureShellRcSource, removeShellEnv } from '../core/shell-env.js'
121
+ import { stripAnsi, maskApiKey, displayWidth, padEndDisplay, tintOverlayLines, keepOverlayTargetVisible, sliceOverlayLines, calculateViewport, sortResultsWithPinnedFavorites, adjustScrollOffset } from './render-helpers.js'
122
+ import { renderTable, PROVIDER_COLOR } from './render-table.js'
123
+ import { setOpenCodeModelData, startOpenCode, startOpenCodeDesktop, startOpenCodeWeb } from '../core/opencode.js'
124
+ import { startKilo } from '../core/kilo.js'
125
+ import { startOpenClaw } from '../core/openclaw.js'
126
+ import { createOverlayRenderers } from './overlays.js'
127
+ import { createKeyHandler, createMouseEventHandler } from './key-handler.js'
128
+ import { createMouseHandler, containsMouseSequence } from './mouse.js'
129
+ import { stopRouterDashboardClient } from '../core/router-dashboard.js'
130
+ import { getToolModeOrder, getToolMeta } from '../core/tool-metadata.js'
131
+ import { startExternalTool } from '../core/tool-launchers.js'
132
+ import { getToolInstallPlan, installToolWithPlan, isToolInstalled } from '../core/tool-bootstrap.js'
133
+ import { getConfiguredInstallableProviders, installProviderEndpoints, refreshInstalledEndpoints, getInstallTargetModes, getProviderCatalogModels } from '../core/endpoint-installer.js'
134
+ import { loadCache, saveCache, clearCache, getCacheAge } from '../core/cache.js'
135
+ import { checkConfigSecurity } from '../core/security.js'
136
+ import { buildCliHelpText } from './cli-help.js'
137
+ import { detectActiveTheme } from './theme.js'
138
138
 
139
139
  // 📖 mergedModels: cross-provider grouped model list (one entry per label, N providers each)
140
140
  // 📖 mergedModelByLabel: fast lookup map from display label → merged model entry
@@ -149,7 +149,7 @@ const require = createRequire(import.meta.url)
149
149
  const readline = require('readline')
150
150
 
151
151
  // ─── Version check ────────────────────────────────────────────────────────────
152
- const pkg = require('../package.json')
152
+ const pkg = require('../../package.json')
153
153
  const LOCAL_VERSION = pkg.version
154
154
 
155
155
  // 📖 sendBugReport → imported from ../src/telemetry.js
@@ -18,7 +18,7 @@
18
18
  * @see ./tool-metadata.js — source of truth for launcher modes and their CLI flags
19
19
  */
20
20
 
21
- import { getToolModeOrder, getToolMeta } from './tool-metadata.js'
21
+ import { getToolModeOrder, getToolMeta } from '../core/tool-metadata.js'
22
22
 
23
23
  const ANALYSIS_FLAGS = [
24
24
  { flag: '--best', description: 'Show only top tiers (A+, S, S+)' },
@@ -15,8 +15,8 @@
15
15
  * @see src/overlays.js
16
16
  */
17
17
 
18
- import { TOOL_METADATA, TOOL_MODE_ORDER } from './tool-metadata.js'
19
- import { sources } from '../sources.js'
18
+ import { TOOL_METADATA, TOOL_MODE_ORDER } from '../core/tool-metadata.js'
19
+ import { sources } from '../../sources.js'
20
20
 
21
21
  const PROVIDER_FILTER_COMMANDS = Object.entries(sources).map(([providerKey, source]) => {
22
22
  const label = source?.name || providerKey
@@ -31,21 +31,21 @@
31
31
  * @exports { buildProviderModelsUrl, parseProviderModelIds, listProviderTestModels, classifyProviderTestOutcome, buildProviderTestDetail, createKeyHandler }
32
32
  */
33
33
 
34
- import { loadChangelog } from './changelog-loader.js'
35
- import { getToolMeta, isModelCompatibleWithTool, getCompatibleTools, findSimilarCompatibleModels } from './tool-metadata.js'
36
- import { loadConfig, saveConfig, replaceConfigContents, getApiKey } from './config.js'
37
- import { sources } from '../sources.js'
34
+ import { loadChangelog } from '../core/changelog-loader.js'
35
+ import { getToolMeta, isModelCompatibleWithTool, getCompatibleTools, findSimilarCompatibleModels } from '../core/tool-metadata.js'
36
+ import { loadConfig, saveConfig, replaceConfigContents, getApiKey } from '../core/config.js'
37
+ import { sources } from '../../sources.js'
38
38
  import { join, dirname } from 'node:path'
39
39
  import { fileURLToPath } from 'node:url'
40
40
  import { spawn } from 'node:child_process'
41
- import { cleanupLegacyProxyArtifacts } from './legacy-proxy-cleanup.js'
41
+ import { cleanupLegacyProxyArtifacts } from '../core/legacy-proxy-cleanup.js'
42
42
  import { getLastLayout, COLUMN_SORT_MAP } from './render-table.js'
43
43
  import { cycleThemeSetting, detectActiveTheme } from './theme.js'
44
- import { syncShellEnv, ensureShellRcSource, removeShellEnv } from './shell-env.js'
44
+ import { syncShellEnv, ensureShellRcSource, removeShellEnv } from '../core/shell-env.js'
45
45
  import { buildCommandPaletteTree, flattenCommandTree, filterCommandPaletteEntries } from './command-palette.js'
46
- import { WIDTH_WARNING_MIN_COLS, VERDICT_CYCLE, HEALTH_CYCLE } from './constants.js'
47
- import { scanAllToolConfigs, softDeleteModel } from './installed-models-manager.js'
48
- import { startExternalTool } from './tool-launchers.js'
46
+ import { WIDTH_WARNING_MIN_COLS, VERDICT_CYCLE, HEALTH_CYCLE } from '../core/constants.js'
47
+ import { scanAllToolConfigs, softDeleteModel } from '../core/installed-models-manager.js'
48
+ import { startExternalTool } from '../core/tool-launchers.js'
49
49
  import {
50
50
  clearRouterDashboardRequestLog,
51
51
  closeRouterDashboardOverlay,
@@ -54,8 +54,8 @@ import {
54
54
  openRouterDashboardOverlay,
55
55
  restartRouterDashboardDaemon,
56
56
  toggleRouterDashboardProbePause,
57
- } from './router-dashboard.js'
58
- import { benchmarkModel } from './benchmark.js'
57
+ } from '../core/router-dashboard.js'
58
+ import { benchmarkModel } from '../core/benchmark.js'
59
59
 
60
60
  // 📖 Some providers need an explicit probe model because the first catalog entry
61
61
  // 📖 is not guaranteed to be accepted by their chat endpoint.
@@ -19,9 +19,9 @@
19
19
  * @see ./key-handler.js — handles keypresses for all overlay interactions
20
20
  */
21
21
 
22
- import { loadChangelog } from './changelog-loader.js'
22
+ import { loadChangelog } from '../core/changelog-loader.js'
23
23
  import { buildCliHelpLines } from './cli-help.js'
24
- import { renderRouterDashboard as renderRouterDashboardOverlay } from './router-dashboard.js'
24
+ import { renderRouterDashboard as renderRouterDashboardOverlay } from '../core/router-dashboard.js'
25
25
  import { themeColors, getThemeStatusLabel, getProviderRgb } from './theme.js'
26
26
 
27
27
  export function createOverlayRenderers(state, deps) {
@@ -47,8 +47,8 @@
47
47
  */
48
48
 
49
49
  import chalk from 'chalk'
50
- import { OVERLAY_PANEL_WIDTH, TABLE_FIXED_LINES, TABLE_HEADER_LINES, TABLE_FOOTER_LINES } from './constants.js'
51
- import { sortResults } from './utils.js'
50
+ import { OVERLAY_PANEL_WIDTH, TABLE_FIXED_LINES, TABLE_HEADER_LINES, TABLE_FOOTER_LINES } from '../core/constants.js'
51
+ import { sortResults } from '../core/utils.js'
52
52
 
53
53
  // 📖 stripAnsi: Remove ANSI color/control sequences to estimate visible text width before padding.
54
54
  // 📖 Strips CSI sequences (SGR colors) and OSC sequences (hyperlinks).
@@ -34,7 +34,7 @@
34
34
 
35
35
  import chalk from 'chalk'
36
36
  import { createRequire } from 'module'
37
- import { sources } from '../sources.js'
37
+ import { sources } from '../../sources.js'
38
38
  import {
39
39
  COL_MODEL,
40
40
  TIER_CYCLE,
@@ -44,19 +44,19 @@ import {
44
44
  WIDTH_WARNING_MIN_COLS,
45
45
  TABLE_FOOTER_LINES,
46
46
  FRAMES
47
- } from './constants.js'
47
+ } from '../core/constants.js'
48
48
  import { themeColors, currentPalette, getProviderRgb, getTierRgb, getReadableTextRgb, getTheme } from './theme.js'
49
49
  import { TIER_COLOR } from './tier-colors.js'
50
- import { getAvg, getVerdict, getUptime, getStabilityScore, getVersionStatusInfo } from './utils.js'
51
- import { usagePlaceholderForProvider } from './ping.js'
52
- import { formatBenchmarkLatency, formatBenchmarkTps } from './benchmark.js'
50
+ import { getAvg, getVerdict, getUptime, getStabilityScore, getVersionStatusInfo } from '../core/utils.js'
51
+ import { usagePlaceholderForProvider } from '../core/ping.js'
52
+ import { formatBenchmarkLatency, formatBenchmarkTps } from '../core/benchmark.js'
53
53
  import { calculateViewport, sortResultsWithPinnedFavorites, padEndDisplay, displayWidth, stripAnsi } from './render-helpers.js'
54
- import { getToolMeta, TOOL_METADATA, TOOL_MODE_ORDER, isModelCompatibleWithTool } from './tool-metadata.js'
54
+ import { getToolMeta, TOOL_METADATA, TOOL_MODE_ORDER, isModelCompatibleWithTool } from '../core/tool-metadata.js'
55
55
  import { getColumnSpacing } from './ui-config.js'
56
- import { detectPackageManager, getManualInstallCmd } from './updater.js'
56
+ import { detectPackageManager, getManualInstallCmd } from '../core/updater.js'
57
57
 
58
58
  const require = createRequire(import.meta.url)
59
- const { version: LOCAL_VERSION } = require('../package.json')
59
+ const { version: LOCAL_VERSION } = require('../../package.json')
60
60
 
61
61
  // 📖 Mouse support: column boundary map updated every frame by renderTable().
62
62
  // 📖 Each entry maps a column name to its display X-start and X-end (1-based, inclusive).
@@ -26,9 +26,9 @@
26
26
  * @see src/tui-state.js — state shape for filter mode indices
27
27
  */
28
28
 
29
- import { TIER_CYCLE, VERDICT_CYCLE, HEALTH_CYCLE } from './constants.js'
30
- import { TIER_LETTER_MAP } from './utils.js'
31
- import { getVerdict } from './utils.js'
29
+ import { TIER_CYCLE, VERDICT_CYCLE, HEALTH_CYCLE } from '../core/constants.js'
30
+ import { TIER_LETTER_MAP } from '../core/utils.js'
31
+ import { getVerdict } from '../core/utils.js'
32
32
 
33
33
  /**
34
34
  * 📖 createTuiFilters: Build the filter functions for a given TUI state + dependencies.
@@ -24,7 +24,7 @@
24
24
  * @see src/ping-loop.js — reads ping mode fields from the state
25
25
  */
26
26
 
27
- import { WIDTH_WARNING_MIN_COLS } from './constants.js'
27
+ import { WIDTH_WARNING_MIN_COLS } from '../core/constants.js'
28
28
 
29
29
  // 📖 Ping cadence intervals per mode (ms). Speed = startup burst, normal = steady,
30
30
  // 📖 slow = idle throttle, forced = user-triggered fast burst.
package/web/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # 📦 free-coding-models-web
2
+
3
+ This directory contains the shared React SPA (Single Page Application) dashboard for `free-coding-models`.
4
+
5
+ ## 🌐 Architecture
6
+
7
+ The frontend is a single SPA that is served in two distinct scenarios:
8
+ 1. **Web Dashboard / Docker Mode (`--daemon`):** Served directly by the local Node.js `router-daemon` process on `http://localhost:19280/`.
9
+ 2. **Desktop Mode (Tauri App):** Loaded locally inside Tauri's native webview from embedded assets in `web/dist/`, communicating via HTTP fetch to the background engine.
10
+
11
+ To maintain maximum code sharing, **95%+ of all components and logic are kept completely identical** between the two distributions.
12
+
13
+ ---
14
+
15
+ ## ⚡ API & Event Integration
16
+
17
+ The React app relies on HTTP and Server-Sent Events (SSE) to talk to the engine:
18
+ * `GET /api/models`: Fetches the live model catalog, complete with stability scores and latency details.
19
+ * `GET /api/config`: Retrieves active provider toggles (keys are masked).
20
+ * `POST /api/settings`: Updates API keys and provider preferences.
21
+ * `GET /api/events` / `EventSource`: Listens for real-time SSE updates broadcasted by the ping and benchmark loops.
22
+
23
+ ---
24
+
25
+ ## 🛠️ Development & Building
26
+
27
+ ### Prerequisites
28
+ Make sure you have `pnpm` installed and dependencies initialized at the root of the project.
29
+
30
+ ### 1. Dev Server (HMR)
31
+ To start the React frontend with Vite HMR (Hot Module Replacement):
32
+ ```bash
33
+ cd web
34
+ pnpm dev
35
+ ```
36
+ By default, the dev server runs on `http://localhost:5173/`. Ensure a background daemon is running on port `19280` so the API requests proxy correctly.
37
+
38
+ ### 2. Production Build
39
+ To compile the production-ready SPA:
40
+ ```bash
41
+ pnpm build
42
+ ```
43
+ This bundles the HTML, JS, and CSS assets into the `web/dist/` directory, which is then embedded inside both the CLI daemon and the Tauri desktop app binary.