free-coding-models 0.3.9 → 0.3.12

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, saveAsProfile, loadProfile, listProfiles, deleteProfile, getActiveProfileName, setActiveProfile, _emptyProfileSettings, 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,15 +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 { getConfiguredInstallableProviders, installProviderEndpoints, refreshInstalledEndpoints, getInstallTargetModes, getProviderCatalogModels, CONNECTION_MODES } from '../src/endpoint-installer.js'
125
+ import { getConfiguredInstallableProviders, installProviderEndpoints, refreshInstalledEndpoints, getInstallTargetModes, getProviderCatalogModels } from '../src/endpoint-installer.js'
130
126
  import { loadCache, saveCache, clearCache, getCacheAge } from '../src/cache.js'
131
127
  import { checkConfigSecurity } from '../src/security.js'
132
128
  import { buildCliHelpText } from '../src/cli-help.js'
@@ -219,115 +215,7 @@ async function main() {
219
215
  config.settings.sortAsc = true
220
216
  }
221
217
 
222
- if (cliArgs.cleanProxyMode) {
223
- const cleaned = cleanupOpenCodeProxyConfig()
224
- console.log()
225
- console.log(chalk.green(' ✅ OpenCode proxy cleanup complete'))
226
- console.log(chalk.dim(` Config: ${cleaned.path}`))
227
- console.log(chalk.dim(` Removed provider: ${cleaned.removedProvider ? 'yes' : 'no'} • Removed default model: ${cleaned.removedModel ? 'yes' : 'no'}`))
228
- console.log()
229
- process.exit(0)
230
- }
231
-
232
- // 📖 CLI subcommand: free-coding-models daemon <action>
233
- const daemonSubcmd = process.argv[2] === 'daemon' ? (process.argv[3] || 'status') : null
234
- if (daemonSubcmd) {
235
- const dm = await import('../src/daemon-manager.js')
236
- if (daemonSubcmd === 'status') {
237
- const s = await dm.getDaemonStatus()
238
- console.log()
239
- if (s.status === 'running') {
240
- console.log(chalk.greenBright(` 📡 FCM Proxy V2: Running`))
241
- console.log(chalk.dim(` PID: ${s.info.pid} • Port: ${s.info.port} • Accounts: ${s.info.accountCount} • Version: ${s.info.version}`))
242
- console.log(chalk.dim(` Started: ${s.info.startedAt}`))
243
- } else if (s.status === 'stopped') {
244
- console.log(chalk.yellow(` 📡 FCM Proxy V2: Stopped (service installed but not running)`))
245
- } else if (s.status === 'stale') {
246
- console.log(chalk.red(` 📡 FCM Proxy V2: Stale (crashed — PID ${s.info?.pid} no longer alive)`))
247
- } else if (s.status === 'unhealthy') {
248
- console.log(chalk.red(` 📡 FCM Proxy V2: Unhealthy (PID alive but health check failed)`))
249
- } else {
250
- console.log(chalk.dim(` 📡 FCM Proxy V2: Not installed`))
251
- console.log(chalk.dim(` Install via: free-coding-models daemon install`))
252
- }
253
- console.log()
254
- process.exit(0)
255
- }
256
- if (daemonSubcmd === 'install') {
257
- const result = dm.installDaemon()
258
- console.log()
259
- if (result.success) {
260
- console.log(chalk.greenBright(' ✅ FCM Proxy V2 background service installed and started!'))
261
- console.log(chalk.dim(' The proxy will now run automatically at login.'))
262
- } else {
263
- console.log(chalk.red(` ❌ Install failed: ${result.error}`))
264
- }
265
- console.log()
266
- process.exit(result.success ? 0 : 1)
267
- }
268
- if (daemonSubcmd === 'uninstall') {
269
- const result = dm.uninstallDaemon()
270
- console.log()
271
- if (result.success) {
272
- console.log(chalk.greenBright(' ✅ FCM Proxy V2 background service uninstalled.'))
273
- } else {
274
- console.log(chalk.red(` ❌ Uninstall failed: ${result.error}`))
275
- }
276
- console.log()
277
- process.exit(result.success ? 0 : 1)
278
- }
279
- if (daemonSubcmd === 'restart') {
280
- const result = dm.restartDaemon()
281
- console.log()
282
- if (result.success) {
283
- console.log(chalk.greenBright(' ✅ FCM Proxy V2 service restarted.'))
284
- } else {
285
- console.log(chalk.red(` ❌ Restart failed: ${result.error}`))
286
- }
287
- console.log()
288
- process.exit(result.success ? 0 : 1)
289
- }
290
- if (daemonSubcmd === 'stop') {
291
- const result = dm.stopDaemon()
292
- console.log()
293
- if (result.success) {
294
- console.log(chalk.greenBright(' ✅ FCM Proxy V2 service stopped.'))
295
- console.log(chalk.dim(' The service stays installed and can be restarted later.'))
296
- } else {
297
- console.log(chalk.red(` ❌ Stop failed: ${result.error}`))
298
- }
299
- console.log()
300
- process.exit(result.success ? 0 : 1)
301
- }
302
- if (daemonSubcmd === 'logs') {
303
- const logPath = dm.getDaemonLogPath()
304
- console.log(chalk.dim(` Log file: ${logPath}`))
305
- try {
306
- const { execSync } = await import('child_process')
307
- execSync(`tail -50 "${logPath}"`, { stdio: 'inherit' })
308
- } catch {
309
- console.log(chalk.dim(' (no logs yet)'))
310
- }
311
- process.exit(0)
312
- }
313
- console.log(chalk.red(` Unknown command: ${daemonSubcmd}`))
314
- console.log(chalk.dim(' Usage: free-coding-models daemon [status|install|uninstall|restart|stop|logs]'))
315
- process.exit(1)
316
- }
317
-
318
- // 📖 If --profile <name> was passed, load that profile into the live config
319
- let startupProfileSettings = null
320
- if (cliArgs.profileName) {
321
- startupProfileSettings = loadProfile(config, cliArgs.profileName)
322
- if (!startupProfileSettings) {
323
- console.error(chalk.red(` Unknown profile "${cliArgs.profileName}". Available: ${listProfiles(config).join(', ') || '(none)'}`))
324
- process.exit(1)
325
- }
326
- saveConfig(config, {
327
- replaceApiKeys: true,
328
- replaceFavorites: true,
329
- })
330
- }
218
+ // 📖 Profile system removed - API keys now persist permanently across all sessions
331
219
 
332
220
  // 📖 Check if any provider has a key — if not, run the first-time setup wizard
333
221
  const hasAnyKey = Object.keys(sources).some(pk => !!getApiKey(config, pk))
@@ -343,9 +231,6 @@ async function main() {
343
231
  }
344
232
  }
345
233
 
346
- // 📖 Backward-compat: keep apiKey var for startOpenClaw() which still needs it
347
- let apiKey = getApiKey(config, 'nvidia')
348
-
349
234
  // 📖 Default mode: use the last persisted launcher choice when valid,
350
235
  // 📖 otherwise fall back to OpenCode CLI.
351
236
  let mode = getToolModeOrder().includes(config.settings?.preferredToolMode)
@@ -359,9 +244,6 @@ async function main() {
359
244
  aider: cliArgs.aiderMode,
360
245
  crush: cliArgs.crushMode,
361
246
  goose: cliArgs.gooseMode,
362
- 'claude-code': cliArgs.claudeCodeMode,
363
- codex: cliArgs.codexMode,
364
- gemini: cliArgs.geminiMode,
365
247
  qwen: cliArgs.qwenMode,
366
248
  openhands: cliArgs.openHandsMode,
367
249
  amp: cliArgs.ampMode,
@@ -494,15 +376,15 @@ async function main() {
494
376
  return 'normal'
495
377
  }
496
378
 
497
- // 📖 tierFilter: current tier filter letter (null = all, 'S' = S+/S, 'A' = A+/A/A-, etc.)
379
+ // 📖 tierFilter: current tier filter letter (null = all, 'S' = S+/S, 'A' = A+/A/A-, etc.)
498
380
  const state = {
499
381
  results,
500
382
  pendingPings: 0,
501
383
  frame: 0,
502
384
  cursor: 0,
503
385
  selectedModel: null,
504
- sortColumn: startupProfileSettings?.sortColumn ?? config.settings?.sortColumn ?? 'avg',
505
- sortDirection: (startupProfileSettings?.sortAsc ?? config.settings?.sortAsc ?? true) ? 'asc' : 'desc',
386
+ sortColumn: config.settings?.sortColumn ?? 'avg',
387
+ sortDirection: (config.settings?.sortAsc ?? true) ? 'asc' : 'desc',
506
388
  pingInterval: PING_MODE_INTERVALS.speed, // 📖 Effective live interval derived from the active ping mode.
507
389
  pingMode: 'speed', // 📖 Current ping mode: speed | normal | slow | forced.
508
390
  pingModeSource: 'startup', // 📖 Why this mode is active: startup | manual | auto | idle | activity.
@@ -516,7 +398,7 @@ async function main() {
516
398
  tierFilterMode: 0, // 📖 Index into TIER_CYCLE (0=All, 1=S+, 2=S, ...)
517
399
  originFilterMode: 0, // 📖 Index into ORIGIN_CYCLE (0=All, then providers)
518
400
  premiumMode: cliArgs.premiumMode, // 📖 Special elite-only mode: S/S+ only, Health UP only, Perfect/Normal/Slow verdict only.
519
- hideUnconfiguredModels: startupProfileSettings?.hideUnconfiguredModels === true || config.settings?.hideUnconfiguredModels === true, // 📖 Hide providers with no configured API key when true.
401
+ hideUnconfiguredModels: config.settings?.hideUnconfiguredModels === true, // 📖 Hide providers with no configured API key when true.
520
402
  disableWidthsWarning: config.settings?.disableWidthsWarning ?? false, // 📖 Disable widths warning toggle (default off)
521
403
  scrollOffset: 0, // 📖 First visible model index in viewport
522
404
  terminalRows: process.stdout.rows || 24, // 📖 Current terminal height
@@ -536,15 +418,6 @@ async function main() {
536
418
  settingsUpdateState: 'idle', // 📖 'idle'|'checking'|'available'|'up-to-date'|'error'|'installing'
537
419
  settingsUpdateLatestVersion: null, // 📖 Latest npm version discovered from manual check
538
420
  settingsUpdateError: null, // 📖 Last update-check error message for maintenance row
539
- settingsProxyPortEditMode: false, // 📖 Whether Settings is editing the preferred proxy port field.
540
- settingsProxyPortBuffer: '', // 📖 Inline input buffer for the preferred proxy port (0 = auto).
541
- daemonStatus: 'not-installed', // 📖 Background daemon status: 'running'|'stopped'|'stale'|'unhealthy'|'not-installed'
542
- daemonInfo: null, // 📖 daemon.json contents when daemon is running
543
- // 📖 Proxy & Daemon overlay state (opened from Settings)
544
- proxyDaemonOpen: false, // 📖 Whether the dedicated Proxy & Daemon overlay is active
545
- proxyDaemonCursor: 0, // 📖 Selected row in the proxy/daemon overlay
546
- proxyDaemonScrollOffset: 0, // 📖 Vertical scroll offset for the proxy/daemon overlay
547
- proxyDaemonMessage: null, // 📖 Feedback message { type: 'success'|'warning'|'error', msg: string, ts: number }
548
421
  config, // 📖 Live reference to the config object (updated on save)
549
422
  visibleSorted: [], // 📖 Cached visible+sorted models — shared between render loop and key handlers
550
423
  helpVisible: false, // 📖 Whether the help overlay (K key) is active
@@ -552,12 +425,12 @@ async function main() {
552
425
  helpScrollOffset: 0, // 📖 Vertical scroll offset for Help overlay viewport
553
426
  // 📖 Install Endpoints overlay state (Y key opens it)
554
427
  installEndpointsOpen: false, // 📖 Whether the install-endpoints overlay is active
555
- installEndpointsPhase: 'providers', // 📖 providers | tools | connection | scope | models | result
428
+ installEndpointsPhase: 'providers', // 📖 providers | tools | scope | models | result
556
429
  installEndpointsCursor: 0, // 📖 Selected row within the current install phase
557
430
  installEndpointsScrollOffset: 0, // 📖 Vertical scroll offset for the install overlay viewport
558
431
  installEndpointsProviderKey: null, // 📖 Selected provider for endpoint installation
559
432
  installEndpointsToolMode: null, // 📖 Selected target tool mode
560
- installEndpointsConnectionMode: null, // 📖 'direct' | 'proxy' how the tool connects to the provider
433
+ installEndpointsConnectionMode: null, // 📖 Direct provider path retained for future install flow state.
561
434
  installEndpointsScope: null, // 📖 all | selected
562
435
  installEndpointsSelectedModelIds: new Set(), // 📖 Multi-select buffer for the selected-models phase
563
436
  installEndpointsErrorMsg: null, // 📖 Temporary validation/error message inside the install flow
@@ -574,10 +447,6 @@ async function main() {
574
447
  recommendAnalysisTimer: null, // 📖 setInterval handle for the 10s analysis phase
575
448
  recommendPingTimer: null, // 📖 setInterval handle for 2 pings/sec during analysis
576
449
  recommendedKeys: new Set(), // 📖 Set of "providerKey/modelId" for recommended models (shown in main table)
577
- // 📖 Config Profiles state
578
- activeProfile: getActiveProfileName(config), // 📖 Currently loaded profile name (or null)
579
- profileSaveMode: false, // 📖 Whether the inline "Save profile" name input is active
580
- profileSaveBuffer: '', // 📖 Typed characters for the profile name being saved
581
450
  // 📖 Feedback state (J/I keys open it)
582
451
  feedbackOpen: false, // 📖 Whether the feedback overlay is active
583
452
  bugReportBuffer: '', // 📖 Typed characters for the feedback message
@@ -585,22 +454,12 @@ async function main() {
585
454
  bugReportError: null, // 📖 Last webhook error message
586
455
  // 📖 OpenCode sync status (S key in settings)
587
456
  settingsSyncStatus: null, // 📖 { type: 'success'|'error', msg: string } — shown in settings footer
588
- // 📖 Log page overlay state (X key opens it)
589
- logVisible: false, // 📖 Whether the log page overlay is active
590
- logScrollOffset: 0, // 📖 Vertical scroll offset for log overlay viewport
591
- logShowAll: false, // 📖 Show all logs (true) or limited to 500 (false)
592
457
  // 📖 Changelog overlay state (N key opens it)
593
458
  changelogOpen: false, // 📖 Whether the changelog overlay is active
594
459
  changelogScrollOffset: 0, // 📖 Vertical scroll offset for changelog overlay viewport
595
460
  changelogPhase: 'index', // 📖 'index' (all versions) | 'details' (specific version)
596
461
  changelogCursor: 0, // 📖 Selected row in index phase
597
462
  changelogSelectedVersion: null, // 📖 Which version to show details for
598
- // 📖 Proxy startup status — set by autoStartProxyIfSynced, consumed by Task 3 indicator
599
- // 📖 null = not configured/not attempted
600
- // 📖 { phase: 'starting' } — proxy start in progress
601
- // 📖 { phase: 'running', port, accountCount } — proxy is live
602
- // 📖 { phase: 'failed', reason } — proxy failed to start
603
- proxyStartupStatus: null, // 📖 Startup-phase proxy status (null | { phase, ...details })
604
463
  }
605
464
 
606
465
  // 📖 Re-clamp viewport on terminal resize
@@ -670,10 +529,6 @@ async function main() {
670
529
  }
671
530
  }
672
531
 
673
- // 📖 Auto-start proxy on launch when proxy auto-sync is enabled for the current tool.
674
- // 📖 Fire-and-forget: does not block UI startup. state.proxyStartupStatus is updated async.
675
- void autoStartProxyIfSynced(config, state)
676
-
677
532
  // 📖 Load cache if available (for faster startup with cached ping results)
678
533
  const cached = loadCache()
679
534
  if (cached && cached.models) {
@@ -798,6 +653,9 @@ async function main() {
798
653
 
799
654
  // 📖 Enter alternate screen — animation runs here, zero scrollback pollution
800
655
  process.stdout.write(ALT_ENTER)
656
+ if (process.stdout.isTTY) {
657
+ process.stdout.flush && process.stdout.flush()
658
+ }
801
659
 
802
660
  // 📖 Ensure we always leave alt screen cleanly (Ctrl+C, crash, normal exit)
803
661
  const exit = (code = 0) => {
@@ -806,6 +664,9 @@ async function main() {
806
664
  clearInterval(ticker)
807
665
  clearTimeout(state.pingIntervalObj)
808
666
  process.stdout.write(ALT_LEAVE)
667
+ if (process.stdout.isTTY) {
668
+ process.stdout.flush && process.stdout.flush()
669
+ }
809
670
  process.exit(code)
810
671
  }
811
672
  process.on('SIGINT', () => exit(0))
@@ -813,7 +674,7 @@ async function main() {
813
674
 
814
675
  // 📖 originFilterMode: index into ORIGIN_CYCLE, 0=All, then each provider key in order
815
676
  const ORIGIN_CYCLE = [null, ...Object.keys(sources)]
816
- const resolvedTierFilter = startupProfileSettings?.tierFilter ?? config.settings?.tierFilter
677
+ const resolvedTierFilter = config.settings?.tierFilter
817
678
  state.tierFilterMode = resolvedTierFilter ? Math.max(0, TIER_CYCLE.indexOf(resolvedTierFilter)) : 0
818
679
  const resolvedOriginFilter = config.settings?.originFilter
819
680
  state.originFilterMode = resolvedOriginFilter ? Math.max(0, ORIGIN_CYCLE.indexOf(resolvedOriginFilter)) : 0
@@ -865,6 +726,9 @@ async function main() {
865
726
  if (process.stdin.isTTY && resetRawMode) process.stdin.setRawMode(false)
866
727
  process.stdin.pause()
867
728
  process.stdout.write(ALT_LEAVE)
729
+ if (process.stdout.isTTY) {
730
+ process.stdout.flush && process.stdout.flush()
731
+ }
868
732
  }
869
733
 
870
734
  const overlays = createOverlayRenderers(state, {
@@ -874,20 +738,16 @@ async function main() {
874
738
  PROVIDER_COLOR,
875
739
  LOCAL_VERSION,
876
740
  getApiKey,
877
- getProxySettings,
878
741
  resolveApiKeys,
879
742
  isProviderEnabled,
880
- listProfiles,
881
743
  TIER_CYCLE,
882
744
  SETTINGS_OVERLAY_BG,
883
745
  HELP_OVERLAY_BG,
884
746
  RECOMMEND_OVERLAY_BG,
885
- LOG_OVERLAY_BG,
886
747
  OVERLAY_PANEL_WIDTH,
887
748
  keepOverlayTargetVisible,
888
749
  sliceOverlayLines,
889
750
  tintOverlayLines,
890
- loadRecentLogs,
891
751
  TASK_TYPES,
892
752
  PRIORITY_TYPES,
893
753
  CONTEXT_BUDGETS,
@@ -902,7 +762,6 @@ async function main() {
902
762
  getConfiguredInstallableProviders,
903
763
  getInstallTargetModes,
904
764
  getProviderCatalogModels,
905
- CONNECTION_MODES,
906
765
  getToolMeta,
907
766
  })
908
767
 
@@ -913,23 +772,16 @@ async function main() {
913
772
  MODELS,
914
773
  sources,
915
774
  getApiKey,
916
- getProxySettings,
917
775
  resolveApiKeys,
918
776
  addApiKey,
919
777
  removeApiKey,
920
778
  persistApiKeysForProvider,
921
779
  isProviderEnabled,
922
- listProfiles,
923
- loadProfile,
924
- deleteProfile,
925
- saveAsProfile,
926
- setActiveProfile,
927
780
  saveConfig,
928
781
  getConfiguredInstallableProviders,
929
782
  getInstallTargetModes,
930
783
  getProviderCatalogModels,
931
784
  installProviderEndpoints,
932
- CONNECTION_MODES,
933
785
  syncFavoriteFlags,
934
786
  toggleFavoriteModel,
935
787
  sortResultsWithPinnedFavorites,
@@ -939,19 +791,12 @@ async function main() {
939
791
  TIER_CYCLE,
940
792
  ORIGIN_CYCLE,
941
793
  ENV_VAR_NAMES,
942
- ensureProxyRunning,
943
- syncToOpenCode,
944
- cleanupToolConfig,
945
- restoreOpenCodeBackup,
946
794
  checkForUpdateDetailed,
947
795
  runUpdate,
948
796
  startOpenClaw,
949
797
  startOpenCodeDesktop,
950
798
  startOpenCode,
951
- startProxyAndLaunch,
952
799
  startExternalTool,
953
- buildProxyTopologyFromConfig,
954
- isProxyEnabledForConfig,
955
800
  getToolModeOrder,
956
801
  startRecommendAnalysis: overlays.startRecommendAnalysis,
957
802
  stopRecommendAnalysis: overlays.stopRecommendAnalysis,
@@ -963,7 +808,6 @@ async function main() {
963
808
  CONTEXT_BUDGETS,
964
809
  toFavoriteKey,
965
810
  mergedModels,
966
- apiKey,
967
811
  chalk,
968
812
  setPingMode,
969
813
  noteUserActivity,
@@ -1002,14 +846,12 @@ async function main() {
1002
846
  refreshAutoPingMode()
1003
847
  state.frame++
1004
848
  // 📖 Cache visible+sorted models each frame so Enter handler always matches the display
1005
- if (!state.settingsOpen && !state.installEndpointsOpen && !state.recommendOpen && !state.feedbackOpen && !state.changelogOpen && !state.proxyDaemonOpen) {
849
+ if (!state.settingsOpen && !state.installEndpointsOpen && !state.recommendOpen && !state.feedbackOpen && !state.changelogOpen) {
1006
850
  const visible = state.results.filter(r => !r.hidden)
1007
851
  state.visibleSorted = sortResultsWithPinnedFavorites(visible, state.sortColumn, state.sortDirection)
1008
852
  }
1009
853
  const content = state.settingsOpen
1010
854
  ? overlays.renderSettings()
1011
- : state.proxyDaemonOpen
1012
- ? overlays.renderProxyDaemon()
1013
855
  : state.installEndpointsOpen
1014
856
  ? overlays.renderInstallEndpoints()
1015
857
  : state.recommendOpen
@@ -1018,19 +860,23 @@ async function main() {
1018
860
  ? overlays.renderFeedback()
1019
861
  : state.helpVisible
1020
862
  ? overlays.renderHelp()
1021
- : state.logVisible
1022
- ? overlays.renderLog()
1023
863
  : state.changelogOpen
1024
864
  ? overlays.renderChangelog()
1025
- : 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.activeProfile, state.profileSaveMode, state.profileSaveBuffer, 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)
865
+ : 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)
1026
866
  process.stdout.write(ALT_HOME + content)
867
+ if (process.stdout.isTTY) {
868
+ process.stdout.flush && process.stdout.flush()
869
+ }
1027
870
  }, Math.round(1000 / FPS))
1028
871
 
1029
872
  // 📖 Populate visibleSorted before the first frame so Enter works immediately
1030
873
  const initialVisible = state.results.filter(r => !r.hidden)
1031
874
  state.visibleSorted = sortResultsWithPinnedFavorites(initialVisible, state.sortColumn, state.sortDirection)
1032
875
 
1033
- 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.activeProfile, state.profileSaveMode, state.profileSaveBuffer, 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))
876
+ 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))
877
+ if (process.stdout.isTTY) {
878
+ process.stdout.flush && process.stdout.flush()
879
+ }
1034
880
 
1035
881
  // 📖 If --recommend was passed, auto-open the Smart Recommend overlay on start
1036
882
  if (cliArgs.recommendMode) {
@@ -1054,7 +900,7 @@ async function main() {
1054
900
  refreshAutoPingMode()
1055
901
  state.lastPingTime = Date.now()
1056
902
 
1057
- // 📖 Refresh persisted usage snapshots each cycle so proxy writes appear live in table.
903
+ // 📖 Refresh persisted usage snapshots each cycle so background usage data appears live in table.
1058
904
  // 📖 Freshness-aware: stale snapshots (>30m) are excluded and row reverts to undefined.
1059
905
  for (const r of state.results) {
1060
906
  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.9",
3
+ "version": "0.3.12",
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: '--profile <name>', description: 'Load a saved config profile before startup' },
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('')