free-coding-models 0.3.11 → 0.3.13

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.
@@ -80,7 +80,6 @@
80
80
  * - --json: Output results as JSON (for scripting/automation)
81
81
  * - --recommend: Open Smart Recommend immediately on startup
82
82
  * - --profile <name>: Load a saved config profile before entering the TUI
83
- * - --clean-proxy / --proxy-clean: Remove persisted fcm-proxy config from OpenCode
84
83
  * - --no-telemetry: Disable anonymous usage analytics for this run
85
84
  * - --help / -h: Print the full CLI help and exit
86
85
  * - --tier S/A/B/C: Filter models by tier letter (S=S+/S, A=A+/A/A-, B=B+/B, C=C)
@@ -98,18 +97,15 @@ import { randomUUID } from 'crypto'
98
97
  import { homedir } from 'os'
99
98
  import { join, dirname } from 'path'
100
99
  import { MODELS, sources } from '../sources.js'
101
- 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, getProxyStatusInfo, formatResultsAsJSON } from '../src/utils.js'
102
- import { loadConfig, saveConfig, getApiKey, getProxySettings, resolveApiKeys, addApiKey, removeApiKey, isProviderEnabled, persistApiKeysForProvider } from '../src/config.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'
103
102
  import { buildMergedModels } from '../src/model-merger.js'
104
- import { ProxyServer } from '../src/proxy-server.js'
105
- import { loadOpenCodeConfig, saveOpenCodeConfig, syncToOpenCode, restoreOpenCodeBackup, cleanupOpenCodeProxyConfig } from '../src/opencode-sync.js'
106
- import { syncProxyToTool, cleanupToolConfig, PROXY_SYNCABLE_TOOLS } from '../src/proxy-sync.js'
103
+ import { loadOpenCodeConfig, saveOpenCodeConfig } from '../src/opencode-config.js'
107
104
  import { usageForRow as _usageForRow } from '../src/usage-reader.js'
108
- import { loadRecentLogs } from '../src/log-reader.js'
109
105
  import { buildProviderModelTokenKey, loadTokenUsageByProviderModel } from '../src/token-usage-reader.js'
110
106
  import { parseOpenRouterResponse, fetchProviderQuota as _fetchProviderQuotaFromModule } from '../src/provider-quota-fetchers.js'
111
107
  import { isKnownQuotaTelemetry } from '../src/quota-capabilities.js'
112
- import { ALT_ENTER, ALT_LEAVE, ALT_HOME, PING_TIMEOUT, PING_INTERVAL, FPS, COL_MODEL, COL_MS, CELL_W, FRAMES, TIER_CYCLE, SETTINGS_OVERLAY_BG, HELP_OVERLAY_BG, RECOMMEND_OVERLAY_BG, LOG_OVERLAY_BG, OVERLAY_PANEL_WIDTH, TABLE_HEADER_LINES, TABLE_FOOTER_LINES, TABLE_FIXED_LINES, msCell, spinCell } from '../src/constants.js'
108
+ import { ALT_ENTER, ALT_LEAVE, ALT_HOME, PING_TIMEOUT, PING_INTERVAL, FPS, COL_MODEL, COL_MS, CELL_W, FRAMES, TIER_CYCLE, SETTINGS_OVERLAY_BG, HELP_OVERLAY_BG, RECOMMEND_OVERLAY_BG, OVERLAY_PANEL_WIDTH, TABLE_HEADER_LINES, TABLE_FOOTER_LINES, TABLE_FIXED_LINES, msCell, spinCell } from '../src/constants.js'
113
109
  import { TIER_COLOR } from '../src/tier-colors.js'
114
110
  import { resolveCloudflareUrl, buildPingRequest, ping, extractQuotaPercent, getProviderQuotaPercentCached, usagePlaceholderForProvider } from '../src/ping.js'
115
111
  import { runFiableMode, filterByTierOrExit, fetchOpenRouterFreeModels } from '../src/analysis.js'
@@ -118,16 +114,15 @@ import { parseTelemetryEnv, isTelemetryDebugEnabled, telemetryDebug, ensureTelem
118
114
  import { ensureFavoritesConfig, toFavoriteKey, syncFavoriteFlags, toggleFavoriteModel } from '../src/favorites.js'
119
115
  import { checkForUpdateDetailed, checkForUpdate, runUpdate, promptUpdateNotification } from '../src/updater.js'
120
116
  import { promptApiKey } from '../src/setup.js'
121
- import { stripAnsi, maskApiKey, displayWidth, padEndDisplay, tintOverlayLines, keepOverlayTargetVisible, sliceOverlayLines, calculateViewport, sortResultsWithPinnedFavorites, renderProxyStatusLine, adjustScrollOffset } from '../src/render-helpers.js'
117
+ import { stripAnsi, maskApiKey, displayWidth, padEndDisplay, tintOverlayLines, keepOverlayTargetVisible, sliceOverlayLines, calculateViewport, sortResultsWithPinnedFavorites, adjustScrollOffset } from '../src/render-helpers.js'
122
118
  import { renderTable, PROVIDER_COLOR } from '../src/render-table.js'
123
- import { setOpenCodeModelData, startOpenCode, startOpenCodeDesktop, startProxyAndLaunch, autoStartProxyIfSynced, ensureProxyRunning, buildProxyTopologyFromConfig, isProxyEnabledForConfig } from '../src/opencode.js'
119
+ import { setOpenCodeModelData, startOpenCode, startOpenCodeDesktop } from '../src/opencode.js'
124
120
  import { startOpenClaw } from '../src/openclaw.js'
125
121
  import { createOverlayRenderers } from '../src/overlays.js'
126
122
  import { createKeyHandler } from '../src/key-handler.js'
127
123
  import { getToolModeOrder, getToolMeta } from '../src/tool-metadata.js'
128
124
  import { startExternalTool } from '../src/tool-launchers.js'
129
- import { startForegroundProxy } from '../src/proxy-foreground.js'
130
- import { getConfiguredInstallableProviders, installProviderEndpoints, refreshInstalledEndpoints, getInstallTargetModes, getProviderCatalogModels, CONNECTION_MODES } from '../src/endpoint-installer.js'
125
+ import { getConfiguredInstallableProviders, installProviderEndpoints, refreshInstalledEndpoints, getInstallTargetModes, getProviderCatalogModels } from '../src/endpoint-installer.js'
131
126
  import { loadCache, saveCache, clearCache, getCacheAge } from '../src/cache.js'
132
127
  import { checkConfigSecurity } from '../src/security.js'
133
128
  import { buildCliHelpText } from '../src/cli-help.js'
@@ -220,108 +215,6 @@ async function main() {
220
215
  config.settings.sortAsc = true
221
216
  }
222
217
 
223
- if (cliArgs.cleanProxyMode) {
224
- const cleaned = cleanupOpenCodeProxyConfig()
225
- console.log()
226
- console.log(chalk.green(' ✅ OpenCode proxy cleanup complete'))
227
- console.log(chalk.dim(` Config: ${cleaned.path}`))
228
- console.log(chalk.dim(` Removed provider: ${cleaned.removedProvider ? 'yes' : 'no'} • Removed default model: ${cleaned.removedModel ? 'yes' : 'no'}`))
229
- console.log()
230
- process.exit(0)
231
- }
232
-
233
- // 📖 Foreground proxy mode — starts the proxy in the current terminal with live dashboard
234
- if (cliArgs.proxyForegroundMode) {
235
- await startForegroundProxy(config, chalk)
236
- return // 📖 startForegroundProxy keeps the process alive via signal handlers
237
- }
238
-
239
- // 📖 CLI subcommand: free-coding-models daemon <action>
240
- const daemonSubcmd = process.argv[2] === 'daemon' ? (process.argv[3] || 'status') : null
241
- if (daemonSubcmd) {
242
- const dm = await import('../src/daemon-manager.js')
243
- if (daemonSubcmd === 'status') {
244
- const s = await dm.getDaemonStatus()
245
- console.log()
246
- if (s.status === 'running') {
247
- console.log(chalk.greenBright(` 📡 FCM Proxy V2: Running`))
248
- console.log(chalk.dim(` PID: ${s.info.pid} • Port: ${s.info.port} • Accounts: ${s.info.accountCount} • Version: ${s.info.version}`))
249
- console.log(chalk.dim(` Started: ${s.info.startedAt}`))
250
- } else if (s.status === 'stopped') {
251
- console.log(chalk.yellow(` 📡 FCM Proxy V2: Stopped (service installed but not running)`))
252
- } else if (s.status === 'stale') {
253
- console.log(chalk.red(` 📡 FCM Proxy V2: Stale (crashed — PID ${s.info?.pid} no longer alive)`))
254
- } else if (s.status === 'unhealthy') {
255
- console.log(chalk.red(` 📡 FCM Proxy V2: Unhealthy (PID alive but health check failed)`))
256
- } else {
257
- console.log(chalk.dim(` 📡 FCM Proxy V2: Not installed`))
258
- console.log(chalk.dim(` Install via: free-coding-models daemon install`))
259
- }
260
- console.log()
261
- process.exit(0)
262
- }
263
- if (daemonSubcmd === 'install') {
264
- const result = dm.installDaemon()
265
- console.log()
266
- if (result.success) {
267
- console.log(chalk.greenBright(' ✅ FCM Proxy V2 background service installed and started!'))
268
- console.log(chalk.dim(' The proxy will now run automatically at login.'))
269
- } else {
270
- console.log(chalk.red(` ❌ Install failed: ${result.error}`))
271
- }
272
- console.log()
273
- process.exit(result.success ? 0 : 1)
274
- }
275
- if (daemonSubcmd === 'uninstall') {
276
- const result = dm.uninstallDaemon()
277
- console.log()
278
- if (result.success) {
279
- console.log(chalk.greenBright(' ✅ FCM Proxy V2 background service uninstalled.'))
280
- } else {
281
- console.log(chalk.red(` ❌ Uninstall failed: ${result.error}`))
282
- }
283
- console.log()
284
- process.exit(result.success ? 0 : 1)
285
- }
286
- if (daemonSubcmd === 'restart') {
287
- const result = dm.restartDaemon()
288
- console.log()
289
- if (result.success) {
290
- console.log(chalk.greenBright(' ✅ FCM Proxy V2 service restarted.'))
291
- } else {
292
- console.log(chalk.red(` ❌ Restart failed: ${result.error}`))
293
- }
294
- console.log()
295
- process.exit(result.success ? 0 : 1)
296
- }
297
- if (daemonSubcmd === 'stop') {
298
- const result = dm.stopDaemon()
299
- console.log()
300
- if (result.success) {
301
- console.log(chalk.greenBright(' ✅ FCM Proxy V2 service stopped.'))
302
- console.log(chalk.dim(' The service stays installed and can be restarted later.'))
303
- } else {
304
- console.log(chalk.red(` ❌ Stop failed: ${result.error}`))
305
- }
306
- console.log()
307
- process.exit(result.success ? 0 : 1)
308
- }
309
- if (daemonSubcmd === 'logs') {
310
- const logPath = dm.getDaemonLogPath()
311
- console.log(chalk.dim(` Log file: ${logPath}`))
312
- try {
313
- const { execSync } = await import('child_process')
314
- execSync(`tail -50 "${logPath}"`, { stdio: 'inherit' })
315
- } catch {
316
- console.log(chalk.dim(' (no logs yet)'))
317
- }
318
- process.exit(0)
319
- }
320
- console.log(chalk.red(` Unknown command: ${daemonSubcmd}`))
321
- console.log(chalk.dim(' Usage: free-coding-models daemon [status|install|uninstall|restart|stop|logs]'))
322
- process.exit(1)
323
- }
324
-
325
218
  // 📖 Profile system removed - API keys now persist permanently across all sessions
326
219
 
327
220
  // 📖 Check if any provider has a key — if not, run the first-time setup wizard
@@ -338,9 +231,6 @@ async function main() {
338
231
  }
339
232
  }
340
233
 
341
- // 📖 Backward-compat: keep apiKey var for startOpenClaw() which still needs it
342
- let apiKey = getApiKey(config, 'nvidia')
343
-
344
234
  // 📖 Default mode: use the last persisted launcher choice when valid,
345
235
  // 📖 otherwise fall back to OpenCode CLI.
346
236
  let mode = getToolModeOrder().includes(config.settings?.preferredToolMode)
@@ -354,9 +244,6 @@ async function main() {
354
244
  aider: cliArgs.aiderMode,
355
245
  crush: cliArgs.crushMode,
356
246
  goose: cliArgs.gooseMode,
357
- 'claude-code': cliArgs.claudeCodeMode,
358
- codex: cliArgs.codexMode,
359
- gemini: cliArgs.geminiMode,
360
247
  qwen: cliArgs.qwenMode,
361
248
  openhands: cliArgs.openHandsMode,
362
249
  amp: cliArgs.ampMode,
@@ -512,11 +399,11 @@ async function main() {
512
399
  originFilterMode: 0, // 📖 Index into ORIGIN_CYCLE (0=All, then providers)
513
400
  premiumMode: cliArgs.premiumMode, // 📖 Special elite-only mode: S/S+ only, Health UP only, Perfect/Normal/Slow verdict only.
514
401
  hideUnconfiguredModels: config.settings?.hideUnconfiguredModels === true, // 📖 Hide providers with no configured API key when true.
515
- disableWidthsWarning: config.settings?.disableWidthsWarning ?? false, // 📖 Disable widths warning toggle (default off)
402
+ disableWidthsWarning: config.settings?.disableWidthsWarning ?? false, // 📖 Cached for runtime checks; keep it in sync with config.settings.
516
403
  scrollOffset: 0, // 📖 First visible model index in viewport
517
404
  terminalRows: process.stdout.rows || 24, // 📖 Current terminal height
518
405
  terminalCols: process.stdout.columns || 80, // 📖 Current terminal width
519
- widthWarningStartedAt: (process.stdout.columns || 80) < 166 ? now : null, // 📖 Start the narrow-terminal countdown immediately when booting in a small viewport.
406
+ widthWarningStartedAt: (process.stdout.columns || 80) < 166 && !(config.settings?.disableWidthsWarning ?? false) ? now : null, // 📖 Start immediately only when warnings are enabled in a narrow viewport.
520
407
  widthWarningDismissed: false, // 📖 Esc hides the narrow-terminal warning early for the current narrow-width session.
521
408
  widthWarningShowCount: 0, // 📖 Counter for how many times the narrow-terminal warning has been shown (max 2 per session).
522
409
  // 📖 Settings screen state (P key opens it)
@@ -531,15 +418,6 @@ async function main() {
531
418
  settingsUpdateState: 'idle', // 📖 'idle'|'checking'|'available'|'up-to-date'|'error'|'installing'
532
419
  settingsUpdateLatestVersion: null, // 📖 Latest npm version discovered from manual check
533
420
  settingsUpdateError: null, // 📖 Last update-check error message for maintenance row
534
- settingsProxyPortEditMode: false, // 📖 Whether Settings is editing the preferred proxy port field.
535
- settingsProxyPortBuffer: '', // 📖 Inline input buffer for the preferred proxy port (0 = auto).
536
- daemonStatus: 'not-installed', // 📖 Background daemon status: 'running'|'stopped'|'stale'|'unhealthy'|'not-installed'
537
- daemonInfo: null, // 📖 daemon.json contents when daemon is running
538
- // 📖 Proxy & Daemon overlay state (opened from Settings)
539
- proxyDaemonOpen: false, // 📖 Whether the dedicated Proxy & Daemon overlay is active
540
- proxyDaemonCursor: 0, // 📖 Selected row in the proxy/daemon overlay
541
- proxyDaemonScrollOffset: 0, // 📖 Vertical scroll offset for the proxy/daemon overlay
542
- proxyDaemonMessage: null, // 📖 Feedback message { type: 'success'|'warning'|'error', msg: string, ts: number }
543
421
  config, // 📖 Live reference to the config object (updated on save)
544
422
  visibleSorted: [], // 📖 Cached visible+sorted models — shared between render loop and key handlers
545
423
  helpVisible: false, // 📖 Whether the help overlay (K key) is active
@@ -547,12 +425,12 @@ async function main() {
547
425
  helpScrollOffset: 0, // 📖 Vertical scroll offset for Help overlay viewport
548
426
  // 📖 Install Endpoints overlay state (Y key opens it)
549
427
  installEndpointsOpen: false, // 📖 Whether the install-endpoints overlay is active
550
- installEndpointsPhase: 'providers', // 📖 providers | tools | connection | scope | models | result
428
+ installEndpointsPhase: 'providers', // 📖 providers | tools | scope | models | result
551
429
  installEndpointsCursor: 0, // 📖 Selected row within the current install phase
552
430
  installEndpointsScrollOffset: 0, // 📖 Vertical scroll offset for the install overlay viewport
553
431
  installEndpointsProviderKey: null, // 📖 Selected provider for endpoint installation
554
432
  installEndpointsToolMode: null, // 📖 Selected target tool mode
555
- installEndpointsConnectionMode: null, // 📖 'direct' | 'proxy' how the tool connects to the provider
433
+ installEndpointsConnectionMode: null, // 📖 Direct provider path retained for future install flow state.
556
434
  installEndpointsScope: null, // 📖 all | selected
557
435
  installEndpointsSelectedModelIds: new Set(), // 📖 Multi-select buffer for the selected-models phase
558
436
  installEndpointsErrorMsg: null, // 📖 Temporary validation/error message inside the install flow
@@ -576,30 +454,22 @@ async function main() {
576
454
  bugReportError: null, // 📖 Last webhook error message
577
455
  // 📖 OpenCode sync status (S key in settings)
578
456
  settingsSyncStatus: null, // 📖 { type: 'success'|'error', msg: string } — shown in settings footer
579
- // 📖 Log page overlay state (X key opens it)
580
- logVisible: false, // 📖 Whether the log page overlay is active
581
- logScrollOffset: 0, // 📖 Vertical scroll offset for log overlay viewport
582
- logShowAll: false, // 📖 Show all logs (true) or limited to 500 (false)
583
457
  // 📖 Changelog overlay state (N key opens it)
584
458
  changelogOpen: false, // 📖 Whether the changelog overlay is active
585
459
  changelogScrollOffset: 0, // 📖 Vertical scroll offset for changelog overlay viewport
586
460
  changelogPhase: 'index', // 📖 'index' (all versions) | 'details' (specific version)
587
461
  changelogCursor: 0, // 📖 Selected row in index phase
588
462
  changelogSelectedVersion: null, // 📖 Which version to show details for
589
- // 📖 Proxy startup status — set by autoStartProxyIfSynced, consumed by Task 3 indicator
590
- // 📖 null = not configured/not attempted
591
- // 📖 { phase: 'starting' } — proxy start in progress
592
- // 📖 { phase: 'running', port, accountCount } — proxy is live
593
- // 📖 { phase: 'failed', reason } — proxy failed to start
594
- proxyStartupStatus: null, // 📖 Startup-phase proxy status (null | { phase, ...details })
595
463
  }
596
464
 
597
465
  // 📖 Re-clamp viewport on terminal resize
598
466
  process.stdout.on('resize', () => {
599
467
  const prevCols = state.terminalCols
468
+ const widthsWarningDisabled = state.config.settings?.disableWidthsWarning === true
600
469
  state.terminalRows = process.stdout.rows || 24
601
470
  state.terminalCols = process.stdout.columns || 80
602
- if (state.terminalCols < 166 && !state.disableWidthsWarning) {
471
+ state.disableWidthsWarning = widthsWarningDisabled
472
+ if (state.terminalCols < 166 && !widthsWarningDisabled) {
603
473
  if (prevCols >= 166 || state.widthWarningDismissed) {
604
474
  state.widthWarningStartedAt = Date.now()
605
475
  state.widthWarningDismissed = false
@@ -661,10 +531,6 @@ async function main() {
661
531
  }
662
532
  }
663
533
 
664
- // 📖 Auto-start proxy on launch when proxy auto-sync is enabled for the current tool.
665
- // 📖 Fire-and-forget: does not block UI startup. state.proxyStartupStatus is updated async.
666
- void autoStartProxyIfSynced(config, state)
667
-
668
534
  // 📖 Load cache if available (for faster startup with cached ping results)
669
535
  const cached = loadCache()
670
536
  if (cached && cached.models) {
@@ -874,19 +740,16 @@ async function main() {
874
740
  PROVIDER_COLOR,
875
741
  LOCAL_VERSION,
876
742
  getApiKey,
877
- getProxySettings,
878
743
  resolveApiKeys,
879
744
  isProviderEnabled,
880
745
  TIER_CYCLE,
881
746
  SETTINGS_OVERLAY_BG,
882
747
  HELP_OVERLAY_BG,
883
748
  RECOMMEND_OVERLAY_BG,
884
- LOG_OVERLAY_BG,
885
749
  OVERLAY_PANEL_WIDTH,
886
750
  keepOverlayTargetVisible,
887
751
  sliceOverlayLines,
888
752
  tintOverlayLines,
889
- loadRecentLogs,
890
753
  TASK_TYPES,
891
754
  PRIORITY_TYPES,
892
755
  CONTEXT_BUDGETS,
@@ -901,7 +764,6 @@ async function main() {
901
764
  getConfiguredInstallableProviders,
902
765
  getInstallTargetModes,
903
766
  getProviderCatalogModels,
904
- CONNECTION_MODES,
905
767
  getToolMeta,
906
768
  })
907
769
 
@@ -912,7 +774,6 @@ async function main() {
912
774
  MODELS,
913
775
  sources,
914
776
  getApiKey,
915
- getProxySettings,
916
777
  resolveApiKeys,
917
778
  addApiKey,
918
779
  removeApiKey,
@@ -923,7 +784,6 @@ async function main() {
923
784
  getInstallTargetModes,
924
785
  getProviderCatalogModels,
925
786
  installProviderEndpoints,
926
- CONNECTION_MODES,
927
787
  syncFavoriteFlags,
928
788
  toggleFavoriteModel,
929
789
  sortResultsWithPinnedFavorites,
@@ -933,19 +793,12 @@ async function main() {
933
793
  TIER_CYCLE,
934
794
  ORIGIN_CYCLE,
935
795
  ENV_VAR_NAMES,
936
- ensureProxyRunning,
937
- syncToOpenCode,
938
- cleanupToolConfig,
939
- restoreOpenCodeBackup,
940
796
  checkForUpdateDetailed,
941
797
  runUpdate,
942
798
  startOpenClaw,
943
799
  startOpenCodeDesktop,
944
800
  startOpenCode,
945
- startProxyAndLaunch,
946
801
  startExternalTool,
947
- buildProxyTopologyFromConfig,
948
- isProxyEnabledForConfig,
949
802
  getToolModeOrder,
950
803
  startRecommendAnalysis: overlays.startRecommendAnalysis,
951
804
  stopRecommendAnalysis: overlays.stopRecommendAnalysis,
@@ -957,7 +810,6 @@ async function main() {
957
810
  CONTEXT_BUDGETS,
958
811
  toFavoriteKey,
959
812
  mergedModels,
960
- apiKey,
961
813
  chalk,
962
814
  setPingMode,
963
815
  noteUserActivity,
@@ -996,14 +848,12 @@ async function main() {
996
848
  refreshAutoPingMode()
997
849
  state.frame++
998
850
  // 📖 Cache visible+sorted models each frame so Enter handler always matches the display
999
- if (!state.settingsOpen && !state.installEndpointsOpen && !state.recommendOpen && !state.feedbackOpen && !state.changelogOpen && !state.proxyDaemonOpen) {
851
+ if (!state.settingsOpen && !state.installEndpointsOpen && !state.recommendOpen && !state.feedbackOpen && !state.changelogOpen) {
1000
852
  const visible = state.results.filter(r => !r.hidden)
1001
853
  state.visibleSorted = sortResultsWithPinnedFavorites(visible, state.sortColumn, state.sortDirection)
1002
854
  }
1003
855
  const content = state.settingsOpen
1004
856
  ? overlays.renderSettings()
1005
- : state.proxyDaemonOpen
1006
- ? overlays.renderProxyDaemon()
1007
857
  : state.installEndpointsOpen
1008
858
  ? overlays.renderInstallEndpoints()
1009
859
  : state.recommendOpen
@@ -1012,11 +862,9 @@ async function main() {
1012
862
  ? overlays.renderFeedback()
1013
863
  : state.helpVisible
1014
864
  ? overlays.renderHelp()
1015
- : state.logVisible
1016
- ? overlays.renderLog()
1017
865
  : state.changelogOpen
1018
866
  ? overlays.renderChangelog()
1019
- : renderTable(state.results, state.pendingPings, state.frame, state.cursor, state.sortColumn, state.sortDirection, state.pingInterval, state.lastPingTime, state.mode, state.tierFilterMode, state.scrollOffset, state.terminalRows, state.terminalCols, state.originFilterMode, state.proxyStartupStatus, state.pingMode, state.pingModeSource, state.hideUnconfiguredModels, state.widthWarningStartedAt, state.widthWarningDismissed, state.widthWarningShowCount, state.settingsUpdateState, state.settingsUpdateLatestVersion, getProxySettings(state.config).enabled === true, state.startupLatestVersion, state.versionAlertsEnabled)
867
+ : renderTable(state.results, state.pendingPings, state.frame, state.cursor, state.sortColumn, state.sortDirection, state.pingInterval, state.lastPingTime, state.mode, state.tierFilterMode, state.scrollOffset, state.terminalRows, state.terminalCols, state.originFilterMode, null, state.pingMode, state.pingModeSource, state.hideUnconfiguredModels, state.widthWarningStartedAt, state.widthWarningDismissed, state.widthWarningShowCount, state.settingsUpdateState, state.settingsUpdateLatestVersion, false, state.startupLatestVersion, state.versionAlertsEnabled, state.config.settings?.disableWidthsWarning ?? false)
1020
868
  process.stdout.write(ALT_HOME + content)
1021
869
  if (process.stdout.isTTY) {
1022
870
  process.stdout.flush && process.stdout.flush()
@@ -1027,7 +875,7 @@ async function main() {
1027
875
  const initialVisible = state.results.filter(r => !r.hidden)
1028
876
  state.visibleSorted = sortResultsWithPinnedFavorites(initialVisible, state.sortColumn, state.sortDirection)
1029
877
 
1030
- process.stdout.write(ALT_HOME + renderTable(state.results, state.pendingPings, state.frame, state.cursor, state.sortColumn, state.sortDirection, state.pingInterval, state.lastPingTime, state.mode, state.tierFilterMode, state.scrollOffset, state.terminalRows, state.terminalCols, state.originFilterMode, state.proxyStartupStatus, state.pingMode, state.pingModeSource, state.hideUnconfiguredModels, state.widthWarningStartedAt, state.widthWarningDismissed, state.widthWarningShowCount, state.settingsUpdateState, state.settingsUpdateLatestVersion, getProxySettings(state.config).enabled === true, state.startupLatestVersion, state.versionAlertsEnabled))
878
+ process.stdout.write(ALT_HOME + renderTable(state.results, state.pendingPings, state.frame, state.cursor, state.sortColumn, state.sortDirection, state.pingInterval, state.lastPingTime, state.mode, state.tierFilterMode, state.scrollOffset, state.terminalRows, state.terminalCols, state.originFilterMode, null, state.pingMode, state.pingModeSource, state.hideUnconfiguredModels, state.widthWarningStartedAt, state.widthWarningDismissed, state.widthWarningShowCount, state.settingsUpdateState, state.settingsUpdateLatestVersion, false, state.startupLatestVersion, state.versionAlertsEnabled, state.config.settings?.disableWidthsWarning ?? false))
1031
879
  if (process.stdout.isTTY) {
1032
880
  process.stdout.flush && process.stdout.flush()
1033
881
  }
@@ -1054,7 +902,7 @@ async function main() {
1054
902
  refreshAutoPingMode()
1055
903
  state.lastPingTime = Date.now()
1056
904
 
1057
- // 📖 Refresh persisted usage snapshots each cycle so proxy writes appear live in table.
905
+ // 📖 Refresh persisted usage snapshots each cycle so background usage data appears live in table.
1058
906
  // 📖 Freshness-aware: stale snapshots (>30m) are excluded and row reverts to undefined.
1059
907
  for (const r of state.results) {
1060
908
  const pct = _usageForRow(r.providerKey, r.modelId)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-coding-models",
3
- "version": "0.3.11",
3
+ "version": "0.3.13",
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",
@@ -36,8 +36,7 @@
36
36
  "type": "module",
37
37
  "main": "bin/free-coding-models.js",
38
38
  "bin": {
39
- "free-coding-models": "./bin/free-coding-models.js",
40
- "fcm-proxy-daemon": "./bin/fcm-proxy-daemon.js"
39
+ "free-coding-models": "./bin/free-coding-models.js"
41
40
  },
42
41
  "files": [
43
42
  "bin/",
package/src/cli-help.js CHANGED
@@ -37,26 +37,14 @@ const ANALYSIS_FLAGS = [
37
37
  ]
38
38
 
39
39
  const CONFIG_FLAGS = [
40
- { flag: '--proxy', description: 'Start FCM Proxy V2 in foreground with live dashboard (no daemon)' },
41
40
  { flag: '--no-telemetry', description: 'Disable anonymous telemetry for this run' },
42
- { flag: '--clean-proxy, --proxy-clean', description: 'Remove persisted fcm-proxy config from OpenCode' },
43
41
  { flag: '--help, -h', description: 'Print this help and exit' },
44
42
  ]
45
43
 
46
- const COMMANDS = [
47
- { command: 'daemon status', description: 'Show background FCM Proxy V2 service status' },
48
- { command: 'daemon install', description: 'Install and start the background service' },
49
- { command: 'daemon uninstall', description: 'Remove the background service' },
50
- { command: 'daemon restart', description: 'Restart the background service' },
51
- { command: 'daemon stop', description: 'Gracefully stop the background service without uninstalling it' },
52
- { command: 'daemon logs', description: 'Print the latest daemon log lines' },
53
- ]
54
-
55
44
  const EXAMPLES = [
56
45
  'free-coding-models --help',
57
46
  'free-coding-models --openclaw --tier S',
58
47
  "free-coding-models --json | jq '.[0]'",
59
- 'free-coding-models daemon status',
60
48
  ]
61
49
 
62
50
  function paint(chalk, formatter, text) {
@@ -79,7 +67,6 @@ export function buildCliHelpLines({ chalk = null, indent = '', title = 'CLI Help
79
67
 
80
68
  lines.push(`${indent}${paint(chalk, chalk?.bold, title)}`)
81
69
  lines.push(`${indent}${paint(chalk, chalk?.dim, 'Usage: free-coding-models [apiKey] [options]')}`)
82
- lines.push(`${indent}${paint(chalk, chalk?.dim, ' free-coding-models daemon [status|install|uninstall|restart|stop|logs]')}`)
83
70
  lines.push('')
84
71
  lines.push(`${indent}${paint(chalk, chalk?.bold, 'Tool Flags')}`)
85
72
  for (const entry of launchFlags) {
@@ -96,11 +83,6 @@ export function buildCliHelpLines({ chalk = null, indent = '', title = 'CLI Help
96
83
  lines.push(formatEntry(entry.flag, entry.description, { chalk, indent }))
97
84
  }
98
85
  lines.push('')
99
- lines.push(`${indent}${paint(chalk, chalk?.bold, 'Commands')}`)
100
- for (const entry of COMMANDS) {
101
- lines.push(formatEntry(entry.command, entry.description, { chalk, indent }))
102
- }
103
- lines.push('')
104
86
  lines.push(`${indent}${paint(chalk, chalk?.dim, 'Default launcher with no tool flag: OpenCode CLI')}`)
105
87
  lines.push(`${indent}${paint(chalk, chalk?.dim, 'Flags can be combined: --openclaw --tier S --json')}`)
106
88
  lines.push('')
package/src/config.js CHANGED
@@ -87,14 +87,11 @@
87
87
  * → buildPersistedConfig(incomingConfig, diskConfig, options?) — Merge a live snapshot with the latest disk state safely
88
88
  * → replaceConfigContents(targetConfig, nextConfig) — Refresh an in-memory config object from a normalized snapshot
89
89
  * → persistApiKeysForProvider(config, providerKey) — Persist one provider's API keys without clobbering the rest of the file
90
-
91
- * → getProxySettings(config) — Return normalized proxy settings from config
92
- * → setClaudeProxyModelRouting(config, modelId) — Mirror free-claude-code MODEL/MODEL_* routing onto one selected FCM model
93
90
  * → normalizeEndpointInstalls(endpointInstalls) — Keep tracked endpoint installs stable across app versions
94
91
  *
95
92
  * @exports loadConfig, saveConfig, validateConfigFile, getApiKey, isProviderEnabled
96
93
  * @exports addApiKey, removeApiKey, listApiKeys — multi-key management helpers
97
- * @exports getProxySettings, setClaudeProxyModelRouting, normalizeEndpointInstalls
94
+ * @exports normalizeEndpointInstalls
98
95
  * @exports buildPersistedConfig, replaceConfigContents, persistApiKeysForProvider
99
96
  * @exports CONFIG_PATH — path to the JSON config file
100
97
  *
@@ -103,14 +100,13 @@
103
100
  */
104
101
 
105
102
  import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync, unlinkSync, renameSync } from 'node:fs'
106
- import { randomBytes } from 'node:crypto'
107
103
  import { homedir } from 'node:os'
108
104
  import { join } from 'node:path'
109
105
 
110
106
  // 📖 New JSON config path — stores all providers' API keys + enabled state
111
107
  export const CONFIG_PATH = join(homedir(), '.free-coding-models.json')
112
108
 
113
- // 📖 Daemon data directory — PID file, logs, etc.
109
+ // 📖 Runtime data directory — backups and local snapshots live here.
114
110
  export const DAEMON_DATA_DIR = join(homedir(), '.free-coding-models')
115
111
 
116
112
  // 📖 Old plain-text config path — used only for migration
@@ -213,7 +209,6 @@ function normalizeSettingsSection(settings) {
213
209
  return {
214
210
  ...safeSettings,
215
211
  hideUnconfiguredModels: typeof safeSettings.hideUnconfiguredModels === 'boolean' ? safeSettings.hideUnconfiguredModels : true,
216
- proxy: normalizeProxySettings(safeSettings.proxy),
217
212
  disableWidthsWarning: safeSettings.disableWidthsWarning === true,
218
213
  }
219
214
  }
@@ -234,7 +229,6 @@ function normalizeProfileSettings(settings) {
234
229
  return {
235
230
  ..._emptyProfileSettings(),
236
231
  ...safeSettings,
237
- proxy: normalizeProxySettings(safeSettings.proxy),
238
232
  disableWidthsWarning: safeSettings.disableWidthsWarning === true,
239
233
  }
240
234
  }
@@ -847,117 +841,10 @@ export function _emptyProfileSettings() {
847
841
  pingInterval: 10000, // 📖 default ms between pings in the steady "normal" mode
848
842
  hideUnconfiguredModels: true, // 📖 true = default to providers that are actually configured
849
843
  preferredToolMode: 'opencode', // 📖 remember the last Z-selected launcher across app restarts
850
- proxy: normalizeProxySettings(),
851
844
  disableWidthsWarning: false, // 📖 Disable widths warning (default off)
852
845
  }
853
846
  }
854
847
 
855
- function normalizeAnthropicRouting(anthropicRouting = null) {
856
- const normalizeModelId = (value) => {
857
- if (typeof value !== 'string') return null
858
- const trimmed = value.trim().replace(/^fcm-proxy\//, '')
859
- return trimmed || null
860
- }
861
-
862
- return {
863
- // 📖 Mirror free-claude-code naming: MODEL is the fallback, and MODEL_* are
864
- // 📖 Claude-family overrides. FCM currently pins all four to one selected model.
865
- model: normalizeModelId(anthropicRouting?.model),
866
- modelOpus: normalizeModelId(anthropicRouting?.modelOpus),
867
- modelSonnet: normalizeModelId(anthropicRouting?.modelSonnet),
868
- modelHaiku: normalizeModelId(anthropicRouting?.modelHaiku),
869
- }
870
- }
871
-
872
- /**
873
- * 📖 normalizeProxySettings: keep proxy-related preferences stable across old configs,
874
- * 📖 new installs, and profile switches. Proxy is opt-in by default.
875
- *
876
- * 📖 stableToken — persisted bearer token shared between TUI and daemon. Generated once
877
- * on first access so env files and tool configs remain valid across restarts.
878
- * 📖 daemonEnabled — opt-in for the always-on background proxy daemon (launchd / systemd).
879
- * 📖 daemonConsent — ISO timestamp of when user consented to daemon install, or null.
880
- *
881
- * @param {object|undefined|null} proxy
882
- * @returns {{ enabled: boolean, syncToOpenCode: boolean, preferredPort: number, stableToken: string, daemonEnabled: boolean, daemonConsent: string|null, anthropicRouting: { model: string|null, modelOpus: string|null, modelSonnet: string|null, modelHaiku: string|null } }}
883
- */
884
- export function normalizeProxySettings(proxy = null) {
885
- const preferredPort = Number.isInteger(proxy?.preferredPort) && proxy.preferredPort >= 0 && proxy.preferredPort <= 65535
886
- ? proxy.preferredPort
887
- : 0
888
-
889
- // 📖 Generate a stable proxy token once and persist it forever
890
- const stableToken = (typeof proxy?.stableToken === 'string' && proxy.stableToken.length > 0)
891
- ? proxy.stableToken
892
- : `fcm_${randomBytes(24).toString('hex')}`
893
-
894
- return {
895
- enabled: proxy?.enabled === true,
896
- syncToOpenCode: proxy?.syncToOpenCode === true,
897
- preferredPort,
898
- stableToken,
899
- daemonEnabled: proxy?.daemonEnabled === true,
900
- daemonConsent: (typeof proxy?.daemonConsent === 'string' && proxy.daemonConsent.length > 0)
901
- ? proxy.daemonConsent
902
- : null,
903
- anthropicRouting: normalizeAnthropicRouting(proxy?.anthropicRouting),
904
- // 📖 activeTool — legacy field kept only for backward compatibility.
905
- // 📖 Runtime sync now follows the current Z-selected tool automatically.
906
- activeTool: (typeof proxy?.activeTool === 'string' && proxy.activeTool.length > 0)
907
- ? proxy.activeTool
908
- : null,
909
- }
910
- }
911
-
912
- /**
913
- * 📖 getProxySettings: return normalized proxy settings from the live config.
914
- * 📖 This centralizes the opt-in default so launchers do not guess.
915
- *
916
- * @param {object} config
917
- * @returns {{ enabled: boolean, syncToOpenCode: boolean, preferredPort: number }}
918
- */
919
- export function getProxySettings(config) {
920
- return normalizeProxySettings(config?.settings?.proxy)
921
- }
922
-
923
- /**
924
- * 📖 Persist the free-claude-code style MODEL / MODEL_OPUS / MODEL_SONNET /
925
- * 📖 MODEL_HAIKU routing onto one selected proxy model. Claude Code itself then
926
- * 📖 keeps speaking in fake Claude model ids while the proxy chooses the backend.
927
- *
928
- * @param {object} config
929
- * @param {string} modelId
930
- * @returns {boolean} true when the normalized proxy settings changed
931
- */
932
- export function setClaudeProxyModelRouting(config, modelId) {
933
- const normalizedModelId = typeof modelId === 'string' ? modelId.trim().replace(/^fcm-proxy\//, '') : ''
934
- if (!normalizedModelId) return false
935
-
936
- if (!config.settings || typeof config.settings !== 'object') config.settings = {}
937
-
938
- const current = getProxySettings(config)
939
- const nextAnthropicRouting = {
940
- model: normalizedModelId,
941
- modelOpus: normalizedModelId,
942
- modelSonnet: normalizedModelId,
943
- modelHaiku: normalizedModelId,
944
- }
945
-
946
- const changed = current.enabled !== true
947
- || current.anthropicRouting.model !== nextAnthropicRouting.model
948
- || current.anthropicRouting.modelOpus !== nextAnthropicRouting.modelOpus
949
- || current.anthropicRouting.modelSonnet !== nextAnthropicRouting.modelSonnet
950
- || current.anthropicRouting.modelHaiku !== nextAnthropicRouting.modelHaiku
951
-
952
- config.settings.proxy = {
953
- ...current,
954
- enabled: true,
955
- anthropicRouting: nextAnthropicRouting,
956
- }
957
-
958
- return changed
959
- }
960
-
961
848
  /**
962
849
  * 📖 normalizeEndpointInstalls keeps the endpoint-install tracking list safe to replay.
963
850
  *
@@ -997,9 +884,10 @@ export function normalizeEndpointInstalls(endpointInstalls) {
997
884
  function _emptyConfig() {
998
885
  return {
999
886
  apiKeys: {},
887
+ providers: {},
1000
888
  favorites: [],
1001
- proxySettings: { enabled: false, routing: {} },
1002
- endpointInstalls: {},
889
+ telemetry: { enabled: null, consentVersion: 0, anonymousId: null },
890
+ endpointInstalls: [],
1003
891
  settings: _emptyProfileSettings(),
1004
892
  }
1005
893
  }