free-coding-models 0.3.55 → 0.3.57
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 +55 -56
- package/README.md +214 -160
- package/bin/free-coding-models.js +46 -0
- package/package.json +2 -2
- package/sources.js +134 -310
- package/src/analysis.js +23 -10
- package/src/app.js +66 -27
- package/src/cache.js +1 -1
- package/src/cli-help.js +9 -0
- package/src/command-palette.js +15 -13
- package/src/config.js +201 -35
- package/src/constants.js +4 -4
- package/src/endpoint-installer.js +45 -1
- package/src/favorites.js +22 -0
- package/src/graphify-out/cache/089db1c1def873cf6d112f1590da4490e61e691aff0db41e006aa2fb15ba0656.json +1 -0
- package/src/graphify-out/cache/0b510b53cf1a1393fb52b1fc3bbbf88b63938e961ec5b82119a2e9715fee8bd7.json +1 -0
- package/src/graphify-out/cache/0ec9a95a326bde58e0316889018b278062d06d494d0f31ba177c9de71e5fed2d.json +1 -0
- package/src/graphify-out/cache/1548663a24a68dce740ebab1bd1d3091048c9604e9d067a1650a42a6d82541d4.json +1 -0
- package/src/graphify-out/cache/1783af63cb6d0dfb4d469009f71ac83a74ba0b33d48186ff2c6e63f9429e900a.json +1 -0
- package/src/graphify-out/cache/1e109f5eb5dc4fd285871c3613e32b6b14a8c225f4080ee34b51c7e1a1764571.json +1 -0
- package/src/graphify-out/cache/1eb24dbeb69b46c8bc1caf925df2f2a964af0f33aea143adf8ddf88e017db6ca.json +1 -0
- package/src/graphify-out/cache/21e1bcfed11685e8347243f9d8516072dda183266a4bfe22c52fb31753a446c8.json +1 -0
- package/src/graphify-out/cache/2327473478b9c4b1940bf7ef66c9ee960b3cba8d5302e56b625df8274246e0b4.json +1 -0
- package/src/graphify-out/cache/25955b81fd25454c8fa90fb71a47db8d1215cf621beb8ff3cbd580aaf011b4f3.json +1 -0
- package/src/graphify-out/cache/2739677f19c702f88f3de0a0bac475066adbda98709907ad3de967aef689f86d.json +1 -0
- package/src/graphify-out/cache/2bba03422f6b3ee7f5b5d29cc90314a064d259e5822a176657bda3e04505cf00.json +1 -0
- package/src/graphify-out/cache/2ddf1d2c6d10147b0402446bc71a7988187b79b6210dd7e7250be8c555b9ff35.json +1 -0
- package/src/graphify-out/cache/2ee07457a5767c95a57f8e9eb95b28f800044f35666e0715e9d88ad1103a092e.json +1 -0
- package/src/graphify-out/cache/2fe9f75dc2951c417f2c8dd22749092cf550dc67599f1c8d1866900dc6e9154e.json +1 -0
- package/src/graphify-out/cache/41c4b7c27e7fc3e2948d3a4bf95a72de2ed9a6f0463994babdce8ed2cc84598c.json +1 -0
- package/src/graphify-out/cache/5028defd54b7fbd3c7e444973e493de036e097e9b1d2a7cae7f19b88d68aacde.json +1 -0
- package/src/graphify-out/cache/5b133aba3fb16410c5b1fdbd1730039fc7fa1ac93abd99d7be08f60da70fc8d4.json +1 -0
- package/src/graphify-out/cache/74252e5b0978d85ab3421a3de1a9384aa282ffd2be2cfe7db2530139089f4275.json +1 -0
- package/src/graphify-out/cache/7695ebeea056095edd14332963cc43354ef3a097caf46f1e28d0f01369642901.json +1 -0
- package/src/graphify-out/cache/777aa7085c395a935c6556bbde182cd871edb61f3a685ed8068ec0c8f6fb0075.json +1 -0
- package/src/graphify-out/cache/82a723881980e82273c113def8315533d7da28827e300413d9ad30f27b7407df.json +1 -0
- package/src/graphify-out/cache/86b87c9603e6cd188f42c7eed3b86c291d48a781c223a707e74f3e7ed0c02a21.json +1 -0
- package/src/graphify-out/cache/890fead9a78cadaed560a2d2453916121fa605c3e43a334910ac4bc951a9ef6d.json +1 -0
- package/src/graphify-out/cache/89d3ea66f52783caa775ef9a30923d7d6225e1d8ae9e962f4741b8c7785dab1e.json +1 -0
- package/src/graphify-out/cache/8cc82cd9edce41f0e1c092f14a94fd52bf847addf3237b616dc5a9e505bd05bd.json +1 -0
- package/src/graphify-out/cache/93ba2e25e3ff7ad525f397902345fbd375df7315de7b402e20cc803c14eccde8.json +1 -0
- package/src/graphify-out/cache/99beed29580b9c7bfecfee794cb3d8e535fcf0eb3b92113108f88bdd0a8e79b3.json +1 -0
- package/src/graphify-out/cache/aeeb931fa477c65ce2e51d8149957350fa54225c613222bbbe8448998d1afd3d.json +1 -0
- package/src/graphify-out/cache/baf91bef5b5ecb2a476433b6cc0c48c563c54ee2d07fc3c192e543685e3e7222.json +1 -0
- package/src/graphify-out/cache/bd98b94ac4e9b92b6336d47b26e0366b51a4eaf0711d722f05f98dfae23ab42b.json +1 -0
- package/src/graphify-out/cache/bfcb51e9328e9cbfbee4f6fee0f56635d7b03488addc9f6c4e4b190b70a73362.json +1 -0
- package/src/graphify-out/cache/c0d3dabeb093aa758c49eadf41b87ecc96a16c1449c2670aaf48cbfc891d8da6.json +1 -0
- package/src/graphify-out/cache/c20d6630236f473c1406068c3ae205853e649b216495c93dfec055dd222c55cf.json +1 -0
- package/src/graphify-out/cache/c22b9122816bebce0a2f79af41a986559d01e00163dbcd579c5755621b4cb483.json +1 -0
- package/src/graphify-out/cache/ca556ec14453ddb8f9e0c5a832dac90d77111b9bad5f8c2d80d272e2e7a06371.json +1 -0
- package/src/graphify-out/cache/d6dbc9135dfa35a756b3b09b06700e4bc229fdccba11bb963f2ba44028e0bbae.json +1 -0
- package/src/graphify-out/cache/e1cf71276f1779d0fa075f79bd7c8a9fd0b8eef6932ac043137451b7c7fa7cbe.json +1 -0
- package/src/graphify-out/cache/e4b3be14494467df2d2ed389bc4f18f099021cb5bc355b901fa88387b2d8b8a2.json +1 -0
- package/src/graphify-out/cache/eaea0dded097f6f9553b654220046c6ec0c9be592a5973d906564ee60af34e0d.json +1 -0
- package/src/graphify-out/cache/ef07d0cd2675d1f79d2a2fdbf3bc3319687638751e9ce89b0d0d97ed1cd9f7e1.json +1 -0
- package/src/graphify-out/cache/f81272d6eb8aaff9e96d5a1d9f06777db70ac3652a646b951ded51f79871d733.json +1 -0
- package/src/graphify-out/cache/f9619dd92186f75a6dbda937e0c606647153918524cdb5763f956e6ec2a9e386.json +1 -0
- package/src/graphify-out/cache/fd88b1b2ff4bfcae08559d9c2aaeeb9a3f1e2f5cd8928762c311196956c170a5.json +1 -0
- package/src/key-handler.js +322 -114
- package/src/kilo.js +20 -1
- package/src/opencode.js +23 -2
- package/src/overlays.js +199 -98
- package/src/provider-metadata.js +26 -17
- package/src/quota-capabilities.js +6 -10
- package/src/render-helpers.js +38 -8
- package/src/render-table.js +119 -248
- package/src/router-daemon.js +1986 -0
- package/src/router-dashboard.js +902 -0
- package/src/sync-set.js +479 -0
- package/src/theme.js +4 -0
- package/src/tool-launchers.js +1 -0
- package/src/tool-metadata.js +6 -2
- package/src/utils.js +30 -6
- package/web/dist/assets/{index-C03JjCgA.js → index-DKHCzbK1.js} +2 -2
- package/web/dist/index.html +1 -1
package/src/analysis.js
CHANGED
|
@@ -138,32 +138,45 @@ export function filterByTierOrExit(results, tierLetter) {
|
|
|
138
138
|
const OPENROUTER_TIER_MAP = {
|
|
139
139
|
'qwen/qwen3-coder': ['S+', '70.6%'],
|
|
140
140
|
'mistralai/devstral-2': ['S+', '72.2%'],
|
|
141
|
-
'
|
|
142
|
-
'
|
|
141
|
+
'minimax/minimax-m2.5': ['S+', '74.0%'],
|
|
142
|
+
'z-ai/glm-4.5-air': ['S+', '72.0%'],
|
|
143
|
+
'tencent/hy3-preview': ['S+', '70.0%'],
|
|
144
|
+
'poolside/laguna-m.1': ['S+', '70.0%'],
|
|
145
|
+
'poolside/laguna-xs.2': ['S+', '70.0%'],
|
|
143
146
|
'qwen/qwen3-next-80b-a3b-instruct': ['S', '65.0%'],
|
|
144
147
|
'openai/gpt-oss-120b': ['S', '60.0%'],
|
|
148
|
+
'inclusionai/ling-2.6-1t': ['S', '60.0%'],
|
|
149
|
+
'nvidia/nemotron-3-super-120b-a12b': ['A+', '56.0%'],
|
|
150
|
+
'nvidia/nemotron-3-nano-omni-30b-a3b-reasoning': ['A+', '52.0%'],
|
|
145
151
|
'openai/gpt-oss-20b': ['A', '42.0%'],
|
|
146
152
|
'nvidia/nemotron-3-nano-30b-a3b': ['A', '43.0%'],
|
|
147
153
|
'meta-llama/llama-3.3-70b-instruct': ['A-', '39.5%'],
|
|
148
|
-
'
|
|
154
|
+
'google/gemma-4-31b-it': ['A', '45.0%'],
|
|
155
|
+
'google/gemma-4-26b-a4b-it': ['A-', '38.0%'],
|
|
149
156
|
'google/gemma-3-27b-it': ['A-', '36.0%'],
|
|
150
157
|
'google/gemma-3-12b-it': ['B+', '30.0%'],
|
|
151
158
|
'google/gemma-3-4b-it': ['B', '22.0%'],
|
|
152
159
|
'google/gemma-3n-e4b-it': ['B', '22.0%'],
|
|
153
160
|
'google/gemma-3n-e2b-it': ['B', '18.0%'],
|
|
154
161
|
'meta-llama/llama-3.2-3b-instruct': ['B', '20.0%'],
|
|
155
|
-
'mistralai/mistral-small-3.1-24b-instruct': ['A-', '35.0%'],
|
|
156
|
-
'qwen/qwen3-4b': ['B', '22.0%'],
|
|
157
162
|
'nousresearch/hermes-3-llama-3.1-405b': ['A', '40.0%'],
|
|
158
163
|
'nvidia/nemotron-nano-9b-v2': ['B+', '28.0%'],
|
|
159
164
|
'nvidia/nemotron-nano-12b-v2-vl': ['B+', '30.0%'],
|
|
160
|
-
'z-ai/glm-4.5-air': ['A-', '38.0%'],
|
|
161
|
-
'arcee-ai/trinity-large-preview': ['A', '40.0%'],
|
|
162
|
-
'arcee-ai/trinity-mini': ['B+', '28.0%'],
|
|
163
|
-
'upstage/solar-pro-3': ['A-', '35.0%'],
|
|
164
165
|
'cognitivecomputations/dolphin-mistral-24b-venice-edition': ['B+', '28.0%'],
|
|
165
166
|
'liquid/lfm-2.5-1.2b-thinking': ['B', '18.0%'],
|
|
166
167
|
'liquid/lfm-2.5-1.2b-instruct': ['B', '18.0%'],
|
|
168
|
+
'openrouter/free': ['B', '25.0%'],
|
|
169
|
+
'openrouter/owl-alpha': ['A+', '50.0%'],
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function isOpenRouterFreeModel(model) {
|
|
173
|
+
if (!model?.id) return false
|
|
174
|
+
if (model.id.endsWith(':free')) return true
|
|
175
|
+
const promptPrice = Number(model.pricing?.prompt)
|
|
176
|
+
const completionPrice = Number(model.pricing?.completion)
|
|
177
|
+
return Number.isFinite(promptPrice) && Number.isFinite(completionPrice)
|
|
178
|
+
&& promptPrice === 0
|
|
179
|
+
&& completionPrice === 0
|
|
167
180
|
}
|
|
168
181
|
|
|
169
182
|
// 📖 fetchOpenRouterFreeModels: Fetch live free models from OpenRouter API.
|
|
@@ -186,7 +199,7 @@ export async function fetchOpenRouterFreeModels() {
|
|
|
186
199
|
const json = await res.json()
|
|
187
200
|
if (!json.data || !Array.isArray(json.data)) return null
|
|
188
201
|
|
|
189
|
-
const freeModels = json.data.filter(
|
|
202
|
+
const freeModels = json.data.filter(isOpenRouterFreeModel)
|
|
190
203
|
|
|
191
204
|
return freeModels.map(m => {
|
|
192
205
|
const baseId = m.id.replace(/:free$/, '')
|
package/src/app.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* - Direct mode flags plus an in-app Z-cycle for the public launcher set
|
|
20
20
|
* - Automatic config detection and model setup for both tools
|
|
21
21
|
* - JSON config stored in ~/.free-coding-models.json (auto-migrates from old plain-text)
|
|
22
|
-
* - Multi-provider support via sources.js (NIM/Groq/Cerebras/
|
|
22
|
+
* - Multi-provider support via sources.js (NIM/Groq/Cerebras/GitHub Models/Mistral/OpenRouter/... — extensible)
|
|
23
23
|
* - Settings screen (P key) to manage API keys, provider toggles, manual updates, and provider-key diagnostics
|
|
24
24
|
* - Install Endpoints flow (Settings / Command Palette) to push provider catalogs into OpenCode, OpenClaw, Crush, and Goose
|
|
25
25
|
* - Favorites system: toggle with F, switch pinning mode with Y, persist between sessions
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
* ⚙️ Configuration:
|
|
60
60
|
* - API keys stored per-provider in ~/.free-coding-models.json (0600 perms)
|
|
61
61
|
* - Old ~/.free-coding-models plain-text auto-migrated as nvidia key on first run
|
|
62
|
-
* - Env vars override config: NVIDIA_API_KEY, GROQ_API_KEY, CEREBRAS_API_KEY, OPENROUTER_API_KEY,
|
|
62
|
+
* - Env vars override config: NVIDIA_API_KEY, GROQ_API_KEY, CEREBRAS_API_KEY, OPENROUTER_API_KEY, GITHUB_TOKEN, MISTRAL_API_KEY, SCALEWAY_API_KEY, GOOGLE_API_KEY, CLOUDFLARE_API_TOKEN, DASHSCOPE_API_KEY, ZAI_API_KEY, etc.
|
|
63
63
|
* - ZAI (z.ai) uses a non-standard base path; cloudflare needs CLOUDFLARE_ACCOUNT_ID in env.
|
|
64
64
|
* - Cloudflare Workers AI requires both CLOUDFLARE_API_TOKEN (or CLOUDFLARE_API_KEY) and CLOUDFLARE_ACCOUNT_ID
|
|
65
65
|
* - Models loaded from sources.js — all provider/model definitions are centralized there
|
|
@@ -110,8 +110,8 @@ import { TIER_COLOR } from '../src/tier-colors.js'
|
|
|
110
110
|
import { resolveCloudflareUrl, buildPingRequest, ping, extractQuotaPercent, getProviderQuotaPercentCached, usagePlaceholderForProvider } from '../src/ping.js'
|
|
111
111
|
import { runFiableMode, filterByTierOrExit, fetchOpenRouterFreeModels } from '../src/analysis.js'
|
|
112
112
|
import { PROVIDER_METADATA, ENV_VAR_NAMES, isWindows, isMac } from '../src/provider-metadata.js'
|
|
113
|
-
import { parseTelemetryEnv, isTelemetryDebugEnabled, telemetryDebug, ensureTelemetryConfig, getTelemetryDistinctId, getTelemetrySystem, getTelemetryTerminal, isTelemetryEnabled, sendUsageTelemetry
|
|
114
|
-
import { ensureFavoritesConfig, toFavoriteKey, syncFavoriteFlags, toggleFavoriteModel } from '../src/favorites.js'
|
|
113
|
+
import { parseTelemetryEnv, isTelemetryDebugEnabled, telemetryDebug, ensureTelemetryConfig, getTelemetryDistinctId, getTelemetrySystem, getTelemetryTerminal, isTelemetryEnabled, sendUsageTelemetry } from '../src/telemetry.js'
|
|
114
|
+
import { ensureFavoritesConfig, toFavoriteKey, syncFavoriteFlags, toggleFavoriteModel, reorderFavorite } from '../src/favorites.js'
|
|
115
115
|
import { checkForUpdateDetailed, checkForUpdate, runUpdate, promptUpdateNotification, fetchLastReleaseDate } from './updater.js'
|
|
116
116
|
import { promptApiKey } from '../src/setup.js'
|
|
117
117
|
import { syncShellEnv, ensureShellRcSource, promptShellEnvMigration, removeShellEnv } from '../src/shell-env.js'
|
|
@@ -123,6 +123,7 @@ import { startOpenClaw } from '../src/openclaw.js'
|
|
|
123
123
|
import { createOverlayRenderers } from '../src/overlays.js'
|
|
124
124
|
import { createKeyHandler, createMouseEventHandler } from '../src/key-handler.js'
|
|
125
125
|
import { createMouseHandler, containsMouseSequence } from '../src/mouse.js'
|
|
126
|
+
import { stopRouterDashboardClient } from '../src/router-dashboard.js'
|
|
126
127
|
import { getToolModeOrder, getToolMeta } from '../src/tool-metadata.js'
|
|
127
128
|
import { startExternalTool } from '../src/tool-launchers.js'
|
|
128
129
|
import { getToolInstallPlan, installToolWithPlan, isToolInstalled } from '../src/tool-bootstrap.js'
|
|
@@ -228,20 +229,24 @@ export async function runApp(cliArgs, config) {
|
|
|
228
229
|
|
|
229
230
|
// 📖 Shell env migration popup for existing users who haven't been asked yet
|
|
230
231
|
// 📖 Only show when user has keys but shellEnvEnabled is still undefined (never prompted)
|
|
231
|
-
|
|
232
|
+
// 📖 shellEnvPromptSeen flag ensures it only shows ONCE even after adding new keys
|
|
233
|
+
if (hasAnyKey && config.settings.shellEnvEnabled === undefined && config.settings.shellEnvPromptSeen !== true) {
|
|
232
234
|
const choice = await promptShellEnvMigration(config)
|
|
235
|
+
if (!config.settings) config.settings = {}
|
|
236
|
+
config.settings.shellEnvPromptSeen = true
|
|
233
237
|
if (choice === 'enable') {
|
|
234
|
-
if (!config.settings) config.settings = {}
|
|
235
238
|
config.settings.shellEnvEnabled = true
|
|
236
239
|
saveConfig(config)
|
|
237
240
|
syncShellEnv(config)
|
|
238
241
|
ensureShellRcSource()
|
|
239
242
|
} else if (choice === 'never') {
|
|
240
|
-
if (!config.settings) config.settings = {}
|
|
241
243
|
config.settings.shellEnvEnabled = false
|
|
242
244
|
saveConfig(config)
|
|
243
245
|
}
|
|
244
|
-
|
|
246
|
+
if (choice === 'skip') {
|
|
247
|
+
config.settings.shellEnvEnabled = false
|
|
248
|
+
saveConfig(config)
|
|
249
|
+
}
|
|
245
250
|
}
|
|
246
251
|
|
|
247
252
|
// 📖 Default mode: use the last persisted launcher choice when valid,
|
|
@@ -430,7 +435,6 @@ export async function runApp(cliArgs, config) {
|
|
|
430
435
|
healthFilterMode: 0, // 📖 Index into HEALTH_CYCLE (0=All, then health states)
|
|
431
436
|
hideUnconfiguredModels: config.settings?.hideUnconfiguredModels === true, // 📖 Hide providers with no configured API key when true.
|
|
432
437
|
favoritesPinnedAndSticky: config.settings?.favoritesPinnedAndSticky === true, // 📖 false by default: favorites follow normal sort/filter rules until Y enables pinned+sticky mode.
|
|
433
|
-
footerHidden: config.settings?.footerHidden === true, // 📖 true = footer is collapsed to a single toggle hint
|
|
434
438
|
scrollOffset: 0, // 📖 First visible model index in viewport
|
|
435
439
|
terminalRows: process.stdout.rows || 24, // 📖 Current terminal height
|
|
436
440
|
terminalCols: process.stdout.columns || 80, // 📖 Current terminal width
|
|
@@ -503,11 +507,7 @@ export async function runApp(cliArgs, config) {
|
|
|
503
507
|
recommendAnalysisTimer: null, // 📖 setInterval handle for the 10s analysis phase
|
|
504
508
|
recommendPingTimer: null, // 📖 setInterval handle for 2 pings/sec during analysis
|
|
505
509
|
recommendedKeys: new Set(), // 📖 Set of "providerKey/modelId" for recommended models (shown in main table)
|
|
506
|
-
|
|
507
|
-
feedbackOpen: false, // 📖 Whether the feedback overlay is active
|
|
508
|
-
bugReportBuffer: '', // 📖 Typed characters for the feedback message
|
|
509
|
-
bugReportStatus: 'idle', // 📖 'idle'|'sending'|'success'|'error' — webhook send status
|
|
510
|
-
bugReportError: null, // 📖 Last webhook error message
|
|
510
|
+
|
|
511
511
|
// 📖 OpenCode sync status (S key in settings)
|
|
512
512
|
settingsSyncStatus: null, // 📖 { type: 'success'|'error', msg: string } — shown in settings footer
|
|
513
513
|
// 📖 Changelog overlay state (N key opens it)
|
|
@@ -522,6 +522,28 @@ export async function runApp(cliArgs, config) {
|
|
|
522
522
|
installedModelsScrollOffset: 0, // 📖 Vertical scroll offset for overlay viewport
|
|
523
523
|
installedModelsData: [], // 📖 Cached scan results
|
|
524
524
|
installedModelsErrorMsg: null, // 📖 Error or status message
|
|
525
|
+
// 📖 Router Dashboard overlay state (Shift+R opens it).
|
|
526
|
+
routerDashboardOpen: false,
|
|
527
|
+
routerDashboardStatus: 'idle', // 📖 idle | loading | ready | partial | stopped | stale | unreachable | malformed
|
|
528
|
+
routerDashboardBaseUrl: null,
|
|
529
|
+
routerDashboardPort: null,
|
|
530
|
+
routerDashboardHealth: null,
|
|
531
|
+
routerDashboardStats: null,
|
|
532
|
+
routerDashboardError: null,
|
|
533
|
+
routerDashboardScrollOffset: 0,
|
|
534
|
+
routerDashboardEvents: [],
|
|
535
|
+
routerDashboardLiveRequests: [],
|
|
536
|
+
routerDashboardClearedAt: 0,
|
|
537
|
+
routerDashboardLastUpdatedAt: null,
|
|
538
|
+
routerDashboardLastRefreshStartedAt: null,
|
|
539
|
+
routerDashboardPollTimer: null,
|
|
540
|
+
routerDashboardEventAbort: null,
|
|
541
|
+
routerDashboardEventStatus: 'idle',
|
|
542
|
+
routerDashboardEventError: null,
|
|
543
|
+
routerDashboardNotice: null,
|
|
544
|
+
routerDashboardNoticeTimer: null,
|
|
545
|
+
routerOnboardingScrollOffset: 0,
|
|
546
|
+
routerDashboardEverOpened: false, // 📖 Set to true the first time dashboard opens (used for upgrade-path telemetry)
|
|
525
547
|
// 📖 Custom text filter (Ctrl+P palette → type text → Enter). Ephemeral — not saved to config.
|
|
526
548
|
customTextFilter: null, // 📖 Active free-text filter string (null = off). Matches model name, ctx, provider key/name.
|
|
527
549
|
}
|
|
@@ -725,6 +747,7 @@ export async function runApp(cliArgs, config) {
|
|
|
725
747
|
clearInterval(ticker)
|
|
726
748
|
clearTimeout(state.pingIntervalObj)
|
|
727
749
|
clearInterval(state.versionRecheckTimer)
|
|
750
|
+
stopRouterDashboardClient(state)
|
|
728
751
|
process.stdout.write(ALT_LEAVE)
|
|
729
752
|
if (process.stdout.isTTY) {
|
|
730
753
|
process.stdout.flush && process.stdout.flush()
|
|
@@ -798,6 +821,7 @@ export async function runApp(cliArgs, config) {
|
|
|
798
821
|
if (ticker) clearInterval(ticker)
|
|
799
822
|
clearTimeout(state.pingIntervalObj)
|
|
800
823
|
clearInterval(state.versionRecheckTimer)
|
|
824
|
+
stopRouterDashboardClient(state)
|
|
801
825
|
if (onKeyPress) process.stdin.removeListener('keypress', onKeyPress)
|
|
802
826
|
if (onMouseData) process.stdin.removeListener('data', onMouseData)
|
|
803
827
|
if (process.stdin.isTTY && resetRawMode) process.stdin.setRawMode(false)
|
|
@@ -864,6 +888,7 @@ export async function runApp(cliArgs, config) {
|
|
|
864
888
|
installProviderEndpoints,
|
|
865
889
|
syncFavoriteFlags,
|
|
866
890
|
toggleFavoriteModel,
|
|
891
|
+
reorderFavorite,
|
|
867
892
|
sortResultsWithPinnedFavorites,
|
|
868
893
|
adjustScrollOffset,
|
|
869
894
|
applyTierFilter,
|
|
@@ -887,7 +912,6 @@ export async function runApp(cliArgs, config) {
|
|
|
887
912
|
sendUsageTelemetry,
|
|
888
913
|
startRecommendAnalysis: overlays.startRecommendAnalysis,
|
|
889
914
|
stopRecommendAnalysis: overlays.stopRecommendAnalysis,
|
|
890
|
-
sendBugReport,
|
|
891
915
|
stopUi,
|
|
892
916
|
ping,
|
|
893
917
|
TASK_TYPES,
|
|
@@ -991,7 +1015,7 @@ export async function runApp(cliArgs, config) {
|
|
|
991
1015
|
process.stdout.write(ALT_LEAVE);
|
|
992
1016
|
console.error(chalk.red('\n[TUI Error] An error occurred while handling a keypress.'));
|
|
993
1017
|
console.error(err);
|
|
994
|
-
console.error(chalk.yellow('\nPlease file an issue at https://github.com/vava-nessa/free-coding-models/issues or
|
|
1018
|
+
console.error(chalk.yellow('\nPlease file an issue at https://github.com/vava-nessa/free-coding-models/issues or join the Discord to report this to the author.'));
|
|
995
1019
|
process.exit(1);
|
|
996
1020
|
}
|
|
997
1021
|
})
|
|
@@ -1015,12 +1039,14 @@ export async function runApp(cliArgs, config) {
|
|
|
1015
1039
|
refreshAutoPingMode()
|
|
1016
1040
|
state.frame++
|
|
1017
1041
|
// 📖 Cache visible+sorted models each frame so Enter handler always matches the display
|
|
1018
|
-
if (!state.settingsOpen && !state.installEndpointsOpen && !state.toolInstallPromptOpen && !state.incompatibleFallbackOpen && !state.recommendOpen && !state.
|
|
1042
|
+
if (!state.settingsOpen && !state.installEndpointsOpen && !state.toolInstallPromptOpen && !state.incompatibleFallbackOpen && !state.recommendOpen && !state.changelogOpen && !state.installedModelsOpen && !state.routerDashboardOpen && !state.commandPaletteOpen) {
|
|
1019
1043
|
const visible = state.results.filter(r => !r.hidden)
|
|
1020
1044
|
state.visibleSorted = sortResultsWithPinnedFavorites(visible, state.sortColumn, state.sortDirection, {
|
|
1021
1045
|
pinFavorites: state.favoritesPinnedAndSticky,
|
|
1022
1046
|
})
|
|
1023
1047
|
}
|
|
1048
|
+
const tableTerminalRows = state.terminalRows
|
|
1049
|
+
|
|
1024
1050
|
let tableContent = null
|
|
1025
1051
|
if (state.commandPaletteOpen) {
|
|
1026
1052
|
if (!state.commandPaletteFrozenTable) {
|
|
@@ -1038,7 +1064,7 @@ export async function runApp(cliArgs, config) {
|
|
|
1038
1064
|
state.mode,
|
|
1039
1065
|
state.tierFilterMode,
|
|
1040
1066
|
state.scrollOffset,
|
|
1041
|
-
|
|
1067
|
+
tableTerminalRows,
|
|
1042
1068
|
state.terminalCols,
|
|
1043
1069
|
state.originFilterMode,
|
|
1044
1070
|
null,
|
|
@@ -1056,7 +1082,7 @@ export async function runApp(cliArgs, config) {
|
|
|
1056
1082
|
state.favoritesPinnedAndSticky,
|
|
1057
1083
|
state.customTextFilter,
|
|
1058
1084
|
state.lastReleaseDate,
|
|
1059
|
-
|
|
1085
|
+
false,
|
|
1060
1086
|
state.verdictFilterMode,
|
|
1061
1087
|
state.healthFilterMode
|
|
1062
1088
|
)
|
|
@@ -1076,7 +1102,7 @@ export async function runApp(cliArgs, config) {
|
|
|
1076
1102
|
state.mode,
|
|
1077
1103
|
state.tierFilterMode,
|
|
1078
1104
|
state.scrollOffset,
|
|
1079
|
-
|
|
1105
|
+
tableTerminalRows,
|
|
1080
1106
|
state.terminalCols,
|
|
1081
1107
|
state.originFilterMode,
|
|
1082
1108
|
null,
|
|
@@ -1094,7 +1120,7 @@ export async function runApp(cliArgs, config) {
|
|
|
1094
1120
|
state.favoritesPinnedAndSticky,
|
|
1095
1121
|
state.customTextFilter,
|
|
1096
1122
|
state.lastReleaseDate,
|
|
1097
|
-
|
|
1123
|
+
false,
|
|
1098
1124
|
state.verdictFilterMode,
|
|
1099
1125
|
state.healthFilterMode
|
|
1100
1126
|
)
|
|
@@ -1108,15 +1134,19 @@ export async function runApp(cliArgs, config) {
|
|
|
1108
1134
|
? overlays.renderToolInstallPrompt()
|
|
1109
1135
|
: state.installedModelsOpen
|
|
1110
1136
|
? overlays.renderInstalledModels()
|
|
1137
|
+
: state.routerDashboardOpen
|
|
1138
|
+
? overlays.renderRouterDashboard()
|
|
1139
|
+
: state.tokenUsageOpen
|
|
1140
|
+
? overlays.renderTokenUsage()
|
|
1141
|
+
: state.routerOnboardingOpen
|
|
1142
|
+
? overlays.renderRouterOnboarding()
|
|
1111
1143
|
: state.incompatibleFallbackOpen
|
|
1112
1144
|
? overlays.renderIncompatibleFallback()
|
|
1113
1145
|
: state.commandPaletteOpen
|
|
1114
1146
|
? tableContent + overlays.renderCommandPalette()
|
|
1115
1147
|
: state.recommendOpen
|
|
1116
1148
|
? overlays.renderRecommend()
|
|
1117
|
-
: state.
|
|
1118
|
-
? overlays.renderFeedback()
|
|
1119
|
-
: state.helpVisible
|
|
1149
|
+
: state.helpVisible
|
|
1120
1150
|
? overlays.renderHelp()
|
|
1121
1151
|
: state.changelogOpen
|
|
1122
1152
|
? overlays.renderChangelog()
|
|
@@ -1129,7 +1159,7 @@ export async function runApp(cliArgs, config) {
|
|
|
1129
1159
|
process.stdout.write(ALT_LEAVE);
|
|
1130
1160
|
console.error(chalk.red('\n[TUI Render Error] An error occurred during UI rendering.'));
|
|
1131
1161
|
console.error(err);
|
|
1132
|
-
console.error(chalk.yellow('\nPlease file an issue at https://github.com/vava-nessa/free-coding-models/issues or
|
|
1162
|
+
console.error(chalk.yellow('\nPlease file an issue at https://github.com/vava-nessa/free-coding-models/issues or join the Discord to report this to the author.'));
|
|
1133
1163
|
process.exit(1);
|
|
1134
1164
|
}
|
|
1135
1165
|
}, Math.round(1000 / FPS))
|
|
@@ -1140,7 +1170,7 @@ export async function runApp(cliArgs, config) {
|
|
|
1140
1170
|
pinFavorites: state.favoritesPinnedAndSticky,
|
|
1141
1171
|
})
|
|
1142
1172
|
|
|
1143
|
-
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.favoritesPinnedAndSticky, state.customTextFilter, state.lastReleaseDate,
|
|
1173
|
+
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.favoritesPinnedAndSticky, state.customTextFilter, state.lastReleaseDate, false, state.verdictFilterMode, state.healthFilterMode))
|
|
1144
1174
|
if (process.stdout.isTTY) {
|
|
1145
1175
|
process.stdout.flush && process.stdout.flush()
|
|
1146
1176
|
}
|
|
@@ -1201,7 +1231,7 @@ export async function runApp(cliArgs, config) {
|
|
|
1201
1231
|
process.stdout.write(ALT_LEAVE);
|
|
1202
1232
|
console.error(chalk.red('\n[TUI Error] An error occurred in the ping loop.'));
|
|
1203
1233
|
console.error(err);
|
|
1204
|
-
console.error(chalk.yellow('\nPlease file an issue at https://github.com/vava-nessa/free-coding-models/issues or
|
|
1234
|
+
console.error(chalk.yellow('\nPlease file an issue at https://github.com/vava-nessa/free-coding-models/issues or join the Discord to report this to the author.'));
|
|
1205
1235
|
process.exit(1);
|
|
1206
1236
|
}
|
|
1207
1237
|
}
|
|
@@ -1228,6 +1258,15 @@ export async function runApp(cliArgs, config) {
|
|
|
1228
1258
|
} catch {}
|
|
1229
1259
|
}, VERSION_RECHECK_INTERVAL_MS)
|
|
1230
1260
|
|
|
1261
|
+
// 📖 Router ON by default — no onboarding prompt, just auto-enable silently.
|
|
1262
|
+
const routerCfg = state.config?.router
|
|
1263
|
+
if (!routerCfg || routerCfg.onboardingSeen !== true || routerCfg.enabled !== true) {
|
|
1264
|
+
if (!state.config.router) state.config.router = {}
|
|
1265
|
+
state.config.router.enabled = true
|
|
1266
|
+
state.config.router.onboardingSeen = true
|
|
1267
|
+
saveConfig(state.config)
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1231
1270
|
// 📖 Keep interface running forever - user can select anytime or Ctrl+C to exit
|
|
1232
1271
|
// 📖 The pings continue running in background with dynamic interval
|
|
1233
1272
|
// 📖 User can press W to decrease interval (faster pings) or = to increase (slower)
|
package/src/cache.js
CHANGED
package/src/cli-help.js
CHANGED
|
@@ -37,6 +37,11 @@ const ANALYSIS_FLAGS = [
|
|
|
37
37
|
|
|
38
38
|
const CONFIG_FLAGS = [
|
|
39
39
|
{ flag: '--web', description: 'Launch the web dashboard in your browser' },
|
|
40
|
+
{ flag: '--daemon', description: 'Start the FCM Router daemon in the foreground' },
|
|
41
|
+
{ flag: '--daemon-bg', description: 'Start the FCM Router daemon in the background' },
|
|
42
|
+
{ flag: '--daemon-status', description: 'Print FCM Router daemon status JSON' },
|
|
43
|
+
{ flag: '--daemon-stop', description: 'Gracefully stop the FCM Router daemon' },
|
|
44
|
+
{ flag: '--sync-set [name]', description: 'Auto-discover and live-probe models into a router set' },
|
|
40
45
|
{ flag: '--no-telemetry', description: 'Disable anonymous telemetry for this run' },
|
|
41
46
|
{ flag: '--help, -h', description: 'Print this help and exit' },
|
|
42
47
|
]
|
|
@@ -44,6 +49,10 @@ const CONFIG_FLAGS = [
|
|
|
44
49
|
const EXAMPLES = [
|
|
45
50
|
'free-coding-models --help',
|
|
46
51
|
'free-coding-models --web',
|
|
52
|
+
'free-coding-models --daemon-bg',
|
|
53
|
+
'free-coding-models --daemon-status',
|
|
54
|
+
'free-coding-models --sync-set',
|
|
55
|
+
'free-coding-models --sync-set my-coding-set',
|
|
47
56
|
'free-coding-models --openclaw --tier S',
|
|
48
57
|
"free-coding-models --json | jq '.[0]'",
|
|
49
58
|
]
|
package/src/command-palette.js
CHANGED
|
@@ -16,6 +16,18 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { TOOL_METADATA, TOOL_MODE_ORDER } from './tool-metadata.js'
|
|
19
|
+
import { sources } from '../sources.js'
|
|
20
|
+
|
|
21
|
+
const PROVIDER_FILTER_COMMANDS = Object.entries(sources).map(([providerKey, source]) => {
|
|
22
|
+
const label = source?.name || providerKey
|
|
23
|
+
return {
|
|
24
|
+
id: `filter-provider-${providerKey.replace(/[^a-z0-9]+/gi, '-')}`,
|
|
25
|
+
label,
|
|
26
|
+
providerKey,
|
|
27
|
+
description: `${label} models`,
|
|
28
|
+
keywords: ['filter', 'provider', 'origin', providerKey, label.toLowerCase()],
|
|
29
|
+
}
|
|
30
|
+
})
|
|
19
31
|
|
|
20
32
|
const TOOL_MODE_DESCRIPTIONS = {
|
|
21
33
|
opencode: 'Launch in OpenCode CLI with the selected model.',
|
|
@@ -119,17 +131,7 @@ const BASE_COMMAND_TREE = [
|
|
|
119
131
|
children: [
|
|
120
132
|
{ id: 'filter-provider-cycle', label: 'Cycle provider', shortcut: 'D', description: 'Switch between providers', keywords: ['filter', 'provider', 'origin'] },
|
|
121
133
|
{ id: 'filter-provider-all', label: 'All providers', providerKey: null, description: 'Show all providers', keywords: ['filter', 'provider', 'all'] },
|
|
122
|
-
|
|
123
|
-
{ id: 'filter-provider-groq', label: 'Groq', providerKey: 'groq', description: 'Groq models', keywords: ['filter', 'provider', 'groq'] },
|
|
124
|
-
{ id: 'filter-provider-cerebras', label: 'Cerebras', providerKey: 'cerebras', description: 'Cerebras models', keywords: ['filter', 'provider', 'cerebras'] },
|
|
125
|
-
{ id: 'filter-provider-sambanova', label: 'SambaNova', providerKey: 'sambanova', description: 'SambaNova models', keywords: ['filter', 'provider', 'sambanova'] },
|
|
126
|
-
{ id: 'filter-provider-openrouter', label: 'OpenRouter', providerKey: 'openrouter', description: 'OpenRouter models', keywords: ['filter', 'provider', 'openrouter'] },
|
|
127
|
-
{ id: 'filter-provider-together', label: 'Together AI', providerKey: 'together', description: 'Together models', keywords: ['filter', 'provider', 'together'] },
|
|
128
|
-
{ id: 'filter-provider-deepinfra', label: 'DeepInfra', providerKey: 'deepinfra', description: 'DeepInfra models', keywords: ['filter', 'provider', 'deepinfra'] },
|
|
129
|
-
{ id: 'filter-provider-fireworks', label: 'Fireworks AI', providerKey: 'fireworks', description: 'Fireworks models', keywords: ['filter', 'provider', 'fireworks'] },
|
|
130
|
-
{ id: 'filter-provider-hyperbolic', label: 'Hyperbolic', providerKey: 'hyperbolic', description: 'Hyperbolic models', keywords: ['filter', 'provider', 'hyperbolic'] },
|
|
131
|
-
{ id: 'filter-provider-google', label: 'Google AI', providerKey: 'google', description: 'Google models', keywords: ['filter', 'provider', 'google'] },
|
|
132
|
-
{ id: 'filter-provider-huggingface', label: 'Hugging Face', providerKey: 'huggingface', description: 'Hugging Face models', keywords: ['filter', 'provider', 'huggingface'] },
|
|
134
|
+
...PROVIDER_FILTER_COMMANDS,
|
|
133
135
|
]
|
|
134
136
|
},
|
|
135
137
|
{
|
|
@@ -199,14 +201,14 @@ const BASE_COMMAND_TREE = [
|
|
|
199
201
|
],
|
|
200
202
|
},
|
|
201
203
|
{ id: 'action-cycle-theme', label: 'Cycle theme', shortcut: 'G', icon: '🌗', description: 'Switch dark/light/auto', keywords: ['theme', 'dark', 'light', 'auto'] },
|
|
202
|
-
{ id: 'action-reset-view', label: 'Reset view',
|
|
204
|
+
{ id: 'action-reset-view', label: 'Reset view', icon: '🔄', description: 'Reset filters and sort', keywords: ['reset', 'view', 'sort', 'filters'] },
|
|
203
205
|
],
|
|
204
206
|
},
|
|
205
207
|
// 📖 Pages - directly at root level, not in submenu
|
|
206
208
|
{ id: 'open-settings', label: 'Settings', shortcut: 'P', icon: '⚙️', type: 'page', description: 'API keys and preferences', keywords: ['settings', 'config', 'api key'] },
|
|
207
209
|
{ id: 'open-help', label: 'Help', shortcut: 'K', icon: '❓', type: 'page', description: 'Show all shortcuts', keywords: ['help', 'shortcuts', 'hotkeys'] },
|
|
208
210
|
{ id: 'open-changelog', label: 'Changelog', shortcut: 'N', icon: '📋', type: 'page', description: 'Version history', keywords: ['changelog', 'release'] },
|
|
209
|
-
|
|
211
|
+
|
|
210
212
|
{ id: 'open-recommend', label: 'Smart recommend', shortcut: 'Q', icon: '🎯', type: 'page', description: 'Find best model for task', keywords: ['recommend', 'best model'] },
|
|
211
213
|
{ id: 'open-install-endpoints', label: 'Install endpoints', icon: '🔌', type: 'page', description: 'Install provider catalogs', keywords: ['install', 'endpoints', 'providers'] },
|
|
212
214
|
{ id: 'open-installed-models', label: 'Installed models', icon: '🗂️', type: 'page', description: 'View models configured in tools', keywords: ['installed', 'models', 'configured', 'tools', 'manager', 'goose', 'crush', 'aider'] },
|