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.
- package/CHANGELOG.md +24 -0
- package/README.md +112 -1134
- package/bin/free-coding-models.js +18 -170
- package/package.json +2 -3
- package/src/cli-help.js +0 -18
- package/src/config.js +5 -117
- package/src/endpoint-installer.js +26 -64
- package/src/key-handler.js +90 -443
- package/src/legacy-proxy-cleanup.js +432 -0
- package/src/openclaw.js +69 -108
- package/src/opencode-config.js +48 -0
- package/src/opencode.js +6 -248
- package/src/overlays.js +28 -520
- package/src/product-flags.js +14 -0
- package/src/render-helpers.js +2 -34
- package/src/render-table.js +11 -19
- package/src/testfcm.js +90 -43
- package/src/token-usage-reader.js +9 -38
- package/src/tool-launchers.js +235 -409
- package/src/tool-metadata.js +0 -7
- package/src/utils.js +3 -68
- package/bin/fcm-proxy-daemon.js +0 -242
- package/src/account-manager.js +0 -634
- package/src/anthropic-translator.js +0 -440
- package/src/daemon-manager.js +0 -527
- package/src/error-classifier.js +0 -157
- package/src/log-reader.js +0 -195
- package/src/opencode-sync.js +0 -200
- package/src/proxy-foreground.js +0 -234
- package/src/proxy-server.js +0 -1506
- package/src/proxy-sync.js +0 -591
- package/src/proxy-topology.js +0 -85
- package/src/request-transformer.js +0 -180
- package/src/responses-translator.js +0 -423
- package/src/token-stats.js +0 -320
|
@@ -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,
|
|
102
|
-
import { loadConfig, saveConfig, getApiKey,
|
|
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 {
|
|
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,
|
|
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,
|
|
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
|
|
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 {
|
|
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, // 📖
|
|
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
|
|
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 |
|
|
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, // 📖
|
|
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
|
-
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
// 📖
|
|
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
|
-
|
|
1002
|
-
endpointInstalls:
|
|
889
|
+
telemetry: { enabled: null, consentVersion: 0, anonymousId: null },
|
|
890
|
+
endpointInstalls: [],
|
|
1003
891
|
settings: _emptyProfileSettings(),
|
|
1004
892
|
}
|
|
1005
893
|
}
|