free-coding-models 0.2.1 β 0.2.2
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/README.md +19 -2
- package/bin/free-coding-models.js +36 -9
- package/package.json +1 -1
- package/src/config.js +42 -2
- package/src/endpoint-installer.js +459 -0
- package/src/key-handler.js +336 -16
- package/src/overlays.js +204 -3
- package/src/provider-metadata.js +3 -1
- package/src/render-table.js +9 -2
- package/src/utils.js +4 -2
package/src/overlays.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
*
|
|
5
5
|
* @details
|
|
6
6
|
* This module centralizes all overlay rendering in one place:
|
|
7
|
-
* - Settings, Help, Log, Smart Recommend, Feature Request, Bug Report
|
|
7
|
+
* - Settings, Install Endpoints, Help, Log, Smart Recommend, Feature Request, Bug Report
|
|
8
|
+
* - Settings diagnostics for provider key tests, including wrapped retry/error details
|
|
8
9
|
* - Recommend analysis timer orchestration and progress updates
|
|
9
10
|
*
|
|
10
11
|
* The factory pattern keeps stateful UI logic isolated while still
|
|
@@ -48,8 +49,32 @@ export function createOverlayRenderers(state, deps) {
|
|
|
48
49
|
getTopRecommendations,
|
|
49
50
|
adjustScrollOffset,
|
|
50
51
|
getPingModel,
|
|
52
|
+
getConfiguredInstallableProviders,
|
|
53
|
+
getInstallTargetModes,
|
|
54
|
+
getProviderCatalogModels,
|
|
51
55
|
} = deps
|
|
52
56
|
|
|
57
|
+
// π Wrap plain diagnostic text so long Settings messages stay readable inside
|
|
58
|
+
// π the overlay instead of turning into one truncated red line.
|
|
59
|
+
const wrapPlainText = (text, width = 104) => {
|
|
60
|
+
const normalized = typeof text === 'string' ? text.trim() : ''
|
|
61
|
+
if (!normalized) return []
|
|
62
|
+
const words = normalized.split(/\s+/)
|
|
63
|
+
const lines = []
|
|
64
|
+
let current = ''
|
|
65
|
+
for (const word of words) {
|
|
66
|
+
const next = current ? `${current} ${word}` : word
|
|
67
|
+
if (next.length > width && current) {
|
|
68
|
+
lines.push(current)
|
|
69
|
+
current = word
|
|
70
|
+
} else {
|
|
71
|
+
current = next
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (current) lines.push(current)
|
|
75
|
+
return lines
|
|
76
|
+
}
|
|
77
|
+
|
|
53
78
|
// π Keep log token formatting aligned with the main table so the same totals
|
|
54
79
|
// π read the same everywhere in the TUI.
|
|
55
80
|
const formatLogTokens = (totalTokens) => {
|
|
@@ -118,9 +143,13 @@ export function createOverlayRenderers(state, deps) {
|
|
|
118
143
|
|
|
119
144
|
// π Test result badge
|
|
120
145
|
const testResult = state.settingsTestResults[pk]
|
|
121
|
-
|
|
146
|
+
// π Default badge reflects configuration first: a saved key should look
|
|
147
|
+
// π ready to test even before the user has run the probe once.
|
|
148
|
+
let testBadge = keyCount > 0 ? chalk.cyan('[Test]') : chalk.dim('[Missing Key π]')
|
|
122
149
|
if (testResult === 'pending') testBadge = chalk.yellow('[Testingβ¦]')
|
|
123
150
|
else if (testResult === 'ok') testBadge = chalk.greenBright('[Test β
]')
|
|
151
|
+
else if (testResult === 'missing_key') testBadge = chalk.dim('[Missing Key π]')
|
|
152
|
+
else if (testResult === 'auth_error') testBadge = chalk.red('[Auth β]')
|
|
124
153
|
else if (testResult === 'rate_limited') testBadge = chalk.yellow('[Rate limit β³]')
|
|
125
154
|
else if (testResult === 'no_callable_model') testBadge = chalk.magenta('[No model β ]')
|
|
126
155
|
else if (testResult === 'fail') testBadge = chalk.red('[Test β]')
|
|
@@ -151,6 +180,14 @@ export function createOverlayRenderers(state, deps) {
|
|
|
151
180
|
const accountIdStatus = hasAccountId ? chalk.green('CLOUDFLARE_ACCOUNT_ID detected β
') : chalk.yellow('Set CLOUDFLARE_ACCOUNT_ID β ')
|
|
152
181
|
lines.push(chalk.dim(` 4) Export ${chalk.yellow('CLOUDFLARE_ACCOUNT_ID')} in your shell. Status: ${accountIdStatus}`))
|
|
153
182
|
}
|
|
183
|
+
const testDetail = state.settingsTestDetails?.[selectedProviderKey]
|
|
184
|
+
if (testDetail) {
|
|
185
|
+
lines.push('')
|
|
186
|
+
lines.push(chalk.red.bold(' Test Diagnostics'))
|
|
187
|
+
for (const detailLine of wrapPlainText(testDetail)) {
|
|
188
|
+
lines.push(chalk.red(` ${detailLine}`))
|
|
189
|
+
}
|
|
190
|
+
}
|
|
154
191
|
lines.push('')
|
|
155
192
|
}
|
|
156
193
|
|
|
@@ -266,6 +303,168 @@ export function createOverlayRenderers(state, deps) {
|
|
|
266
303
|
return cleared.join('\n')
|
|
267
304
|
}
|
|
268
305
|
|
|
306
|
+
// βββ Install Endpoints overlay renderer βββββββββββββββββββββββββββββββββββ
|
|
307
|
+
// π renderInstallEndpoints drives the provider β tool β scope β model flow
|
|
308
|
+
// π behind the `Y` hotkey. It deliberately reuses the same overlay viewport
|
|
309
|
+
// π helpers as Settings so long provider/model lists stay navigable.
|
|
310
|
+
function renderInstallEndpoints() {
|
|
311
|
+
const EL = '\x1b[K'
|
|
312
|
+
const lines = []
|
|
313
|
+
const cursorLineByRow = {}
|
|
314
|
+
const providerChoices = getConfiguredInstallableProviders(state.config)
|
|
315
|
+
const toolChoices = getInstallTargetModes()
|
|
316
|
+
const scopeChoices = [
|
|
317
|
+
{
|
|
318
|
+
key: 'all',
|
|
319
|
+
label: 'Install all models',
|
|
320
|
+
hint: 'Recommended β FCM will refresh this provider catalog automatically later.',
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
key: 'selected',
|
|
324
|
+
label: 'Install selected models only',
|
|
325
|
+
hint: 'Choose a smaller curated subset for a cleaner model picker.',
|
|
326
|
+
},
|
|
327
|
+
]
|
|
328
|
+
const selectedProviderLabel = state.installEndpointsProviderKey
|
|
329
|
+
? (sources[state.installEndpointsProviderKey]?.name || state.installEndpointsProviderKey)
|
|
330
|
+
: 'β'
|
|
331
|
+
const selectedToolLabel = state.installEndpointsToolMode
|
|
332
|
+
? (state.installEndpointsToolMode === 'opencode-desktop'
|
|
333
|
+
? 'OpenCode Desktop (shared opencode.json)'
|
|
334
|
+
: (state.installEndpointsToolMode === 'opencode'
|
|
335
|
+
? 'OpenCode CLI (shared opencode.json)'
|
|
336
|
+
: state.installEndpointsToolMode === 'openclaw'
|
|
337
|
+
? 'OpenClaw'
|
|
338
|
+
: state.installEndpointsToolMode === 'crush'
|
|
339
|
+
? 'Crush'
|
|
340
|
+
: 'Goose'))
|
|
341
|
+
: 'β'
|
|
342
|
+
|
|
343
|
+
lines.push('')
|
|
344
|
+
lines.push(` ${chalk.bold('π Install Endpoints')} ${chalk.dim('β install provider catalogs into supported coding tools')}`)
|
|
345
|
+
if (state.installEndpointsErrorMsg) {
|
|
346
|
+
lines.push(` ${chalk.yellow(state.installEndpointsErrorMsg)}`)
|
|
347
|
+
}
|
|
348
|
+
lines.push('')
|
|
349
|
+
|
|
350
|
+
if (state.installEndpointsPhase === 'providers') {
|
|
351
|
+
lines.push(` ${chalk.bold('Step 1/4')} ${chalk.cyan('Choose a configured provider')}`)
|
|
352
|
+
lines.push('')
|
|
353
|
+
|
|
354
|
+
if (providerChoices.length === 0) {
|
|
355
|
+
lines.push(chalk.dim(' No configured providers can be installed directly right now.'))
|
|
356
|
+
lines.push(chalk.dim(' Add an API key in Settings (`P`) first, then reopen this screen.'))
|
|
357
|
+
} else {
|
|
358
|
+
providerChoices.forEach((provider, idx) => {
|
|
359
|
+
const isCursor = idx === state.installEndpointsCursor
|
|
360
|
+
const bullet = isCursor ? chalk.bold.cyan(' β― ') : chalk.dim(' ')
|
|
361
|
+
const row = `${bullet}${chalk.bold(provider.label.padEnd(24))} ${chalk.dim(`${provider.modelCount} models`)}`
|
|
362
|
+
cursorLineByRow[idx] = lines.length
|
|
363
|
+
lines.push(isCursor ? chalk.bgRgb(24, 44, 62)(row) : row)
|
|
364
|
+
})
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
lines.push('')
|
|
368
|
+
lines.push(chalk.dim(' ββ Navigate β’ Enter Choose provider β’ Esc Close'))
|
|
369
|
+
} else if (state.installEndpointsPhase === 'tools') {
|
|
370
|
+
lines.push(` ${chalk.bold('Step 2/4')} ${chalk.cyan('Choose the target tool')}`)
|
|
371
|
+
lines.push(chalk.dim(` Provider: ${selectedProviderLabel}`))
|
|
372
|
+
lines.push('')
|
|
373
|
+
|
|
374
|
+
toolChoices.forEach((toolMode, idx) => {
|
|
375
|
+
const isCursor = idx === state.installEndpointsCursor
|
|
376
|
+
const label = toolMode === 'opencode-desktop'
|
|
377
|
+
? 'OpenCode Desktop'
|
|
378
|
+
: toolMode === 'opencode'
|
|
379
|
+
? 'OpenCode CLI'
|
|
380
|
+
: toolMode === 'openclaw'
|
|
381
|
+
? 'OpenClaw'
|
|
382
|
+
: toolMode === 'crush'
|
|
383
|
+
? 'Crush'
|
|
384
|
+
: 'Goose'
|
|
385
|
+
const note = toolMode.startsWith('opencode')
|
|
386
|
+
? chalk.dim('shared config file')
|
|
387
|
+
: chalk.dim('managed provider install')
|
|
388
|
+
const bullet = isCursor ? chalk.bold.cyan(' β― ') : chalk.dim(' ')
|
|
389
|
+
const row = `${bullet}${chalk.bold(label.padEnd(22))} ${note}`
|
|
390
|
+
cursorLineByRow[idx] = lines.length
|
|
391
|
+
lines.push(isCursor ? chalk.bgRgb(24, 44, 62)(row) : row)
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
lines.push('')
|
|
395
|
+
lines.push(chalk.dim(' ββ Navigate β’ Enter Choose tool β’ Esc Back'))
|
|
396
|
+
} else if (state.installEndpointsPhase === 'scope') {
|
|
397
|
+
lines.push(` ${chalk.bold('Step 3/4')} ${chalk.cyan('Choose the install scope')}`)
|
|
398
|
+
lines.push(chalk.dim(` Provider: ${selectedProviderLabel} β’ Tool: ${selectedToolLabel}`))
|
|
399
|
+
lines.push('')
|
|
400
|
+
|
|
401
|
+
scopeChoices.forEach((scope, idx) => {
|
|
402
|
+
const isCursor = idx === state.installEndpointsCursor
|
|
403
|
+
const bullet = isCursor ? chalk.bold.cyan(' β― ') : chalk.dim(' ')
|
|
404
|
+
const row = `${bullet}${chalk.bold(scope.label)}`
|
|
405
|
+
cursorLineByRow[idx] = lines.length
|
|
406
|
+
lines.push(isCursor ? chalk.bgRgb(24, 44, 62)(row) : row)
|
|
407
|
+
lines.push(chalk.dim(` ${scope.hint}`))
|
|
408
|
+
lines.push('')
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
lines.push(chalk.dim(' Enter Continue β’ Esc Back'))
|
|
412
|
+
} else if (state.installEndpointsPhase === 'models') {
|
|
413
|
+
const models = getProviderCatalogModels(state.installEndpointsProviderKey)
|
|
414
|
+
const selectedCount = state.installEndpointsSelectedModelIds.size
|
|
415
|
+
|
|
416
|
+
lines.push(` ${chalk.bold('Step 4/4')} ${chalk.cyan('Choose which models to install')}`)
|
|
417
|
+
lines.push(chalk.dim(` Provider: ${selectedProviderLabel} β’ Tool: ${selectedToolLabel}`))
|
|
418
|
+
lines.push(chalk.dim(` Selected: ${selectedCount}/${models.length}`))
|
|
419
|
+
lines.push('')
|
|
420
|
+
|
|
421
|
+
models.forEach((model, idx) => {
|
|
422
|
+
const isCursor = idx === state.installEndpointsCursor
|
|
423
|
+
const selected = state.installEndpointsSelectedModelIds.has(model.modelId)
|
|
424
|
+
const bullet = isCursor ? chalk.bold.cyan(' β― ') : chalk.dim(' ')
|
|
425
|
+
const checkbox = selected ? chalk.greenBright('[β]') : chalk.dim('[ ]')
|
|
426
|
+
const tier = chalk.cyan(model.tier.padEnd(2))
|
|
427
|
+
const row = `${bullet}${checkbox} ${chalk.bold(model.label.padEnd(26))} ${tier} ${chalk.dim(model.ctx.padEnd(6))} ${chalk.dim(model.modelId)}`
|
|
428
|
+
cursorLineByRow[idx] = lines.length
|
|
429
|
+
lines.push(isCursor ? chalk.bgRgb(24, 44, 62)(row) : row)
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
lines.push('')
|
|
433
|
+
lines.push(chalk.dim(' ββ Navigate β’ Space Toggle model β’ A All/None β’ Enter Install β’ Esc Back'))
|
|
434
|
+
} else if (state.installEndpointsPhase === 'result') {
|
|
435
|
+
const result = state.installEndpointsResult
|
|
436
|
+
const accent = result?.type === 'success' ? chalk.greenBright : chalk.redBright
|
|
437
|
+
lines.push(` ${chalk.bold('Result')} ${accent(result?.title || 'Install result unavailable')}`)
|
|
438
|
+
lines.push('')
|
|
439
|
+
|
|
440
|
+
for (const detail of result?.lines || []) {
|
|
441
|
+
lines.push(` ${detail}`)
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (result?.type === 'success') {
|
|
445
|
+
lines.push('')
|
|
446
|
+
lines.push(chalk.dim(' Future FCM launches will refresh this catalog automatically when the provider list evolves.'))
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
lines.push('')
|
|
450
|
+
lines.push(chalk.dim(' Enter or Esc Close'))
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const targetLine = cursorLineByRow[state.installEndpointsCursor] ?? 0
|
|
454
|
+
state.installEndpointsScrollOffset = keepOverlayTargetVisible(
|
|
455
|
+
state.installEndpointsScrollOffset,
|
|
456
|
+
targetLine,
|
|
457
|
+
lines.length,
|
|
458
|
+
state.terminalRows
|
|
459
|
+
)
|
|
460
|
+
const { visible, offset } = sliceOverlayLines(lines, state.installEndpointsScrollOffset, state.terminalRows)
|
|
461
|
+
state.installEndpointsScrollOffset = offset
|
|
462
|
+
|
|
463
|
+
const tintedLines = tintOverlayLines(visible, SETTINGS_OVERLAY_BG)
|
|
464
|
+
const cleared = tintedLines.map((line) => line + EL)
|
|
465
|
+
return cleared.join('\n')
|
|
466
|
+
}
|
|
467
|
+
|
|
269
468
|
// βββ Help overlay renderer ββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
270
469
|
// π renderHelp: Draw the help overlay listing all key bindings.
|
|
271
470
|
// π Toggled with K key. Gives users a quick reference without leaving the TUI.
|
|
@@ -280,7 +479,7 @@ export function createOverlayRenderers(state, deps) {
|
|
|
280
479
|
lines.push(` ${chalk.cyan('Rank')} SWE-bench rank (1 = best coding score) ${chalk.dim('Sort:')} ${chalk.yellow('R')}`)
|
|
281
480
|
lines.push(` ${chalk.dim('Quick glance at which model is objectively the best coder right now.')}`)
|
|
282
481
|
lines.push('')
|
|
283
|
-
lines.push(` ${chalk.cyan('Tier')} S+ / S / A+ / A / A- / B+ / B / C based on SWE-bench score ${chalk.dim('
|
|
482
|
+
lines.push(` ${chalk.cyan('Tier')} S+ / S / A+ / A / A- / B+ / B / C based on SWE-bench score ${chalk.dim('Cycle:')} ${chalk.yellow('T')}`)
|
|
284
483
|
lines.push(` ${chalk.dim('Skip the noise β S/S+ models solve real GitHub issues, C models are for light tasks.')}`)
|
|
285
484
|
lines.push('')
|
|
286
485
|
lines.push(` ${chalk.cyan('SWE%')} SWE-bench score β coding ability benchmark (color-coded) ${chalk.dim('Sort:')} ${chalk.yellow('S')}`)
|
|
@@ -331,6 +530,7 @@ export function createOverlayRenderers(state, deps) {
|
|
|
331
530
|
lines.push(` ${chalk.yellow('X')} Toggle token log page ${chalk.dim('(shows recent request usage from request-log.jsonl)')}`)
|
|
332
531
|
lines.push(` ${chalk.yellow('Z')} Cycle tool mode ${chalk.dim('(OpenCode β Desktop β OpenClaw β Crush β Goose)')}`)
|
|
333
532
|
lines.push(` ${chalk.yellow('F')} Toggle favorite on selected row ${chalk.dim('(β pinned at top, persisted)')}`)
|
|
533
|
+
lines.push(` ${chalk.yellow('Y')} Install endpoints ${chalk.dim('(provider catalog β OpenCode/OpenClaw/Crush/Goose, no proxy)')}`)
|
|
334
534
|
lines.push(` ${chalk.yellow('Q')} Smart Recommend ${chalk.dim('(π― find the best model for your task β questionnaire + live analysis)')}`)
|
|
335
535
|
lines.push(` ${chalk.rgb(57, 255, 20).bold('J')} Request Feature ${chalk.dim('(π send anonymous feedback to the project team)')}`)
|
|
336
536
|
lines.push(` ${chalk.rgb(255, 87, 51).bold('I')} Report Bug ${chalk.dim('(π send anonymous bug report to the project team)')}`)
|
|
@@ -907,6 +1107,7 @@ export function createOverlayRenderers(state, deps) {
|
|
|
907
1107
|
|
|
908
1108
|
return {
|
|
909
1109
|
renderSettings,
|
|
1110
|
+
renderInstallEndpoints,
|
|
910
1111
|
renderHelp,
|
|
911
1112
|
renderLog,
|
|
912
1113
|
renderRecommend,
|
package/src/provider-metadata.js
CHANGED
|
@@ -114,7 +114,9 @@ export const PROVIDER_METADATA = {
|
|
|
114
114
|
label: 'Hugging Face Inference',
|
|
115
115
|
color: chalk.rgb(255, 245, 157),
|
|
116
116
|
signupUrl: 'https://huggingface.co/settings/tokens',
|
|
117
|
-
|
|
117
|
+
// π Hugging Face serverless inference now expects a fine-grained token with
|
|
118
|
+
// π the dedicated Inference Providers permission, not a generic read token.
|
|
119
|
+
signupHint: 'Settings β Access Tokens β Fine-grained β enable "Make calls to Inference Providers"',
|
|
118
120
|
rateLimits: 'Free monthly credits (~$0.10)',
|
|
119
121
|
},
|
|
120
122
|
replicate: {
|
package/src/render-table.js
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* - Viewport clipping with above/below indicators
|
|
15
15
|
* - Smart badges (mode, tier filter, origin filter, profile)
|
|
16
16
|
* - Proxy status line integrated in footer
|
|
17
|
+
* - Install-endpoints shortcut surfaced directly in the footer hints
|
|
18
|
+
* - Distinct auth-failure vs missing-key health labels so configured providers stay honest
|
|
17
19
|
*
|
|
18
20
|
* β Functions:
|
|
19
21
|
* - `setActiveProxy` β Provide the active proxy instance for footer status rendering
|
|
@@ -434,6 +436,11 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
434
436
|
// π Server responded but needs an API key β shown dimly since it IS reachable
|
|
435
437
|
statusText = `π NO KEY`
|
|
436
438
|
statusColor = (s) => chalk.dim(s)
|
|
439
|
+
} else if (r.status === 'auth_error') {
|
|
440
|
+
// π A key is configured but the provider rejected it β keep this distinct
|
|
441
|
+
// π from "no key" so configured-only mode does not look misleading.
|
|
442
|
+
statusText = `π AUTH FAIL`
|
|
443
|
+
statusColor = (s) => chalk.redBright(s)
|
|
437
444
|
} else if (r.status === 'pending') {
|
|
438
445
|
statusText = `${FRAMES[frame % FRAMES.length]} wait`
|
|
439
446
|
statusColor = (s) => chalk.dim.yellow(s)
|
|
@@ -635,8 +642,8 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
635
642
|
chalk.dim(` β’ `) +
|
|
636
643
|
hotkey('K', ' Help')
|
|
637
644
|
)
|
|
638
|
-
// π Line 2: profiles, recommend, feature request, bug report, and extended hints
|
|
639
|
-
lines.push(chalk.dim(` `) + hotkey('β§P', ' Cycle profile') + chalk.dim(` β’ `) + hotkey('β§S', ' Save profile') + chalk.dim(` β’ `) + hotkey('Q', ' Smart Recommend') + chalk.dim(` β’ `) + hotkey('J', ' Request feature') + chalk.dim(` β’ `) + hotkey('I', ' Report bug'))
|
|
645
|
+
// π Line 2: profiles, install flow, recommend, feature request, bug report, and extended hints.
|
|
646
|
+
lines.push(chalk.dim(` `) + hotkey('β§P', ' Cycle profile') + chalk.dim(` β’ `) + hotkey('β§S', ' Save profile') + chalk.dim(` β’ `) + hotkey('Y', ' Install endpoints') + chalk.dim(` β’ `) + hotkey('Q', ' Smart Recommend') + chalk.dim(` β’ `) + hotkey('J', ' Request feature') + chalk.dim(` β’ `) + hotkey('I', ' Report bug'))
|
|
640
647
|
// π Proxy status line β always rendered with explicit state (starting/running/failed/stopped)
|
|
641
648
|
lines.push(renderProxyStatusLine(proxyStartupStatus, activeProxyRef, proxyEnabled))
|
|
642
649
|
if (versionStatus.isOutdated) {
|
package/src/utils.js
CHANGED
|
@@ -218,9 +218,11 @@ export const getStabilityScore = (r) => {
|
|
|
218
218
|
// π sortResults: Sort the results array by any column the user can click/press in the TUI.
|
|
219
219
|
// π Returns a NEW array β never mutates the original (important for React-style re-renders).
|
|
220
220
|
//
|
|
221
|
-
// π Supported columns
|
|
221
|
+
// π Supported columns in the sorter.
|
|
222
|
+
// π Most map directly to visible TUI sort hotkeys; `tier` remains available internally
|
|
223
|
+
// π even though the live TUI now reserves `Y` for the install-endpoints flow.
|
|
222
224
|
// - 'rank' (R key) β original index from sources.js
|
|
223
|
-
// - 'tier' (
|
|
225
|
+
// - 'tier' (internal) β tier hierarchy (S+ first, C last)
|
|
224
226
|
// - 'origin' (O key) β provider name (all NIM for now, future-proofed)
|
|
225
227
|
// - 'model' (M key) β alphabetical by display label
|
|
226
228
|
// - 'ping' (L key) β last ping latency (only successful ones count)
|