free-coding-models 0.3.19 → 0.3.22
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 +44 -1
- package/README.md +17 -8
- package/package.json +1 -1
- package/src/app.js +29 -35
- package/src/cli-help.js +1 -2
- package/src/command-palette.js +232 -59
- package/src/config.js +0 -3
- package/src/constants.js +5 -0
- package/src/key-handler.js +100 -60
- package/src/overlays.js +85 -60
- package/src/product-flags.js +4 -9
- package/src/render-table.js +134 -67
- package/src/testfcm.js +1 -1
- package/src/theme.js +2 -0
- package/src/utils.js +0 -2
package/src/render-table.js
CHANGED
|
@@ -41,16 +41,15 @@ import {
|
|
|
41
41
|
msCell,
|
|
42
42
|
spinCell,
|
|
43
43
|
PING_INTERVAL,
|
|
44
|
+
WIDTH_WARNING_MIN_COLS,
|
|
44
45
|
FRAMES
|
|
45
46
|
} from './constants.js'
|
|
46
47
|
import { themeColors, getProviderRgb, getTierRgb, getReadableTextRgb, getTheme } from './theme.js'
|
|
47
48
|
import { TIER_COLOR } from './tier-colors.js'
|
|
48
49
|
import { getAvg, getVerdict, getUptime, getStabilityScore, getVersionStatusInfo } from './utils.js'
|
|
49
50
|
import { usagePlaceholderForProvider } from './ping.js'
|
|
50
|
-
import { formatTokenTotalCompact } from './token-usage-reader.js'
|
|
51
51
|
import { calculateViewport, sortResultsWithPinnedFavorites, padEndDisplay, displayWidth } from './render-helpers.js'
|
|
52
52
|
import { getToolMeta } from './tool-metadata.js'
|
|
53
|
-
import { PROXY_DISABLED_NOTICE } from './product-flags.js'
|
|
54
53
|
import { getColumnSpacing } from './ui-config.js'
|
|
55
54
|
|
|
56
55
|
const require = createRequire(import.meta.url)
|
|
@@ -67,7 +66,7 @@ export const PROVIDER_COLOR = new Proxy({}, {
|
|
|
67
66
|
})
|
|
68
67
|
|
|
69
68
|
// ─── renderTable: mode param controls footer hint text (opencode vs openclaw) ─────────
|
|
70
|
-
export function renderTable(results, pendingPings, frame, cursor = null, sortColumn = 'avg', sortDirection = 'asc', pingInterval = PING_INTERVAL, lastPingTime = Date.now(), mode = 'opencode', tierFilterMode = 0, scrollOffset = 0, terminalRows = 0, terminalCols = 0, originFilterMode = 0, legacyStatus = null, pingMode = 'normal', pingModeSource = 'auto', hideUnconfiguredModels = false, widthWarningStartedAt = null, widthWarningDismissed = false, widthWarningShowCount = 0, settingsUpdateState = 'idle', settingsUpdateLatestVersion = null, legacyFlag = false, startupLatestVersion = null, versionAlertsEnabled = true
|
|
69
|
+
export function renderTable(results, pendingPings, frame, cursor = null, sortColumn = 'avg', sortDirection = 'asc', pingInterval = PING_INTERVAL, lastPingTime = Date.now(), mode = 'opencode', tierFilterMode = 0, scrollOffset = 0, terminalRows = 0, terminalCols = 0, originFilterMode = 0, legacyStatus = null, pingMode = 'normal', pingModeSource = 'auto', hideUnconfiguredModels = false, widthWarningStartedAt = null, widthWarningDismissed = false, widthWarningShowCount = 0, settingsUpdateState = 'idle', settingsUpdateLatestVersion = null, legacyFlag = false, startupLatestVersion = null, versionAlertsEnabled = true) {
|
|
71
70
|
// 📖 Filter out hidden models for display
|
|
72
71
|
const visibleResults = results.filter(r => !r.hidden)
|
|
73
72
|
|
|
@@ -146,25 +145,69 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
146
145
|
|
|
147
146
|
// 📖 Column widths (generous spacing with margins)
|
|
148
147
|
const COL_SEP = getColumnSpacing()
|
|
148
|
+
const SEP_W = 3 // ' │ ' display width
|
|
149
|
+
const ROW_MARGIN = 2 // left margin ' '
|
|
149
150
|
const W_RANK = 6
|
|
150
|
-
const W_TIER =
|
|
151
|
-
const W_CTX =
|
|
151
|
+
const W_TIER = 5
|
|
152
|
+
const W_CTX = 4
|
|
152
153
|
const W_SOURCE = 14
|
|
153
154
|
const W_MODEL = 26
|
|
154
|
-
const W_SWE =
|
|
155
|
-
const W_PING = 14
|
|
156
|
-
const W_AVG = 11
|
|
155
|
+
const W_SWE = 5
|
|
157
156
|
const W_STATUS = 18
|
|
158
157
|
const W_VERDICT = 14
|
|
159
|
-
const W_STAB = 11
|
|
160
158
|
const W_UPTIME = 6
|
|
161
|
-
const W_TOKENS = 7
|
|
159
|
+
// const W_TOKENS = 7 // Used column removed
|
|
162
160
|
// const W_USAGE = 7 // Usage column removed
|
|
163
|
-
const MIN_TABLE_WIDTH =
|
|
161
|
+
const MIN_TABLE_WIDTH = WIDTH_WARNING_MIN_COLS
|
|
162
|
+
|
|
163
|
+
// 📖 Responsive column visibility: progressively hide least-useful columns
|
|
164
|
+
// 📖 and shorten header labels when terminal width is insufficient.
|
|
165
|
+
// 📖 Hiding order (least useful first): Rank → Up% → Tier → Stability
|
|
166
|
+
// 📖 Compact mode shrinks: Latest Ping→Lat. P (9), Avg Ping→Avg. P (8),
|
|
167
|
+
// 📖 Stability→StaB. (8), Provider→4chars+… (7), Health→6chars+… (13)
|
|
168
|
+
// 📖 Breakpoints: full=169 | compact=146 | -Rank=137 | -Up%=128 | -Tier=120 | -Stab=109
|
|
169
|
+
let wPing = 14
|
|
170
|
+
let wAvg = 11
|
|
171
|
+
let wStab = 11
|
|
172
|
+
let wSource = W_SOURCE
|
|
173
|
+
let wStatus = W_STATUS
|
|
174
|
+
let showRank = true
|
|
175
|
+
let showUptime = true
|
|
176
|
+
let showTier = true
|
|
177
|
+
let showStability = true
|
|
178
|
+
let isCompact = false
|
|
179
|
+
|
|
180
|
+
if (terminalCols > 0) {
|
|
181
|
+
// 📖 Dynamically compute needed row width from visible columns
|
|
182
|
+
const calcWidth = () => {
|
|
183
|
+
const cols = []
|
|
184
|
+
if (showRank) cols.push(W_RANK)
|
|
185
|
+
if (showTier) cols.push(W_TIER)
|
|
186
|
+
cols.push(W_SWE, W_CTX, W_MODEL, wSource, wPing, wAvg, wStatus, W_VERDICT)
|
|
187
|
+
if (showStability) cols.push(wStab)
|
|
188
|
+
if (showUptime) cols.push(W_UPTIME)
|
|
189
|
+
return ROW_MARGIN + cols.reduce((a, b) => a + b, 0) + (cols.length - 1) * SEP_W
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 📖 Step 1: Compact mode — shorten labels and reduce column widths
|
|
193
|
+
if (calcWidth() > terminalCols) {
|
|
194
|
+
isCompact = true
|
|
195
|
+
wPing = 9 // 'Lat. P' instead of 'Latest Ping'
|
|
196
|
+
wAvg = 8 // 'Avg. P' instead of 'Avg Ping'
|
|
197
|
+
wStab = 8 // 'StaB.' instead of 'Stability'
|
|
198
|
+
wSource = 7 // Provider truncated to 4 chars + '…', 7 cols total
|
|
199
|
+
wStatus = 13 // Health truncated after 6 chars + '…'
|
|
200
|
+
}
|
|
201
|
+
// 📖 Steps 2–5: Progressive column hiding (least useful first)
|
|
202
|
+
if (calcWidth() > terminalCols) showRank = false
|
|
203
|
+
if (calcWidth() > terminalCols) showUptime = false
|
|
204
|
+
if (calcWidth() > terminalCols) showTier = false
|
|
205
|
+
if (calcWidth() > terminalCols) showStability = false
|
|
206
|
+
}
|
|
164
207
|
const warningDurationMs = 2_000
|
|
165
208
|
const elapsed = widthWarningStartedAt ? Math.max(0, Date.now() - widthWarningStartedAt) : warningDurationMs
|
|
166
209
|
const remainingMs = Math.max(0, warningDurationMs - elapsed)
|
|
167
|
-
const showWidthWarning = terminalCols > 0 && terminalCols < MIN_TABLE_WIDTH && !
|
|
210
|
+
const showWidthWarning = terminalCols > 0 && terminalCols < MIN_TABLE_WIDTH && !widthWarningDismissed && widthWarningShowCount < 2 && remainingMs > 0
|
|
168
211
|
|
|
169
212
|
if (showWidthWarning) {
|
|
170
213
|
const lines = []
|
|
@@ -217,13 +260,16 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
217
260
|
const modelH = 'Model'
|
|
218
261
|
const sweH = sortColumn === 'swe' ? dir + ' SWE%' : 'SWE%'
|
|
219
262
|
const ctxH = sortColumn === 'ctx' ? dir + ' CTX' : 'CTX'
|
|
220
|
-
|
|
221
|
-
const
|
|
263
|
+
// 📖 Compact labels: 'Lat. P' / 'Avg. P' / 'StaB.' to save horizontal space
|
|
264
|
+
const pingLabel = isCompact ? 'Lat. P' : 'Latest Ping'
|
|
265
|
+
const avgLabel = isCompact ? 'Avg. P' : 'Avg Ping'
|
|
266
|
+
const stabLabel = isCompact ? 'StaB.' : 'Stability'
|
|
267
|
+
const pingH = sortColumn === 'ping' ? dir + ' ' + pingLabel : pingLabel
|
|
268
|
+
const avgH = sortColumn === 'avg' ? dir + ' ' + avgLabel : avgLabel
|
|
222
269
|
const healthH = sortColumn === 'condition' ? dir + ' Health' : 'Health'
|
|
223
270
|
const verdictH = sortColumn === 'verdict' ? dir + ' Verdict' : 'Verdict'
|
|
224
|
-
const stabH = sortColumn === 'stability' ? dir + '
|
|
271
|
+
const stabH = sortColumn === 'stability' ? dir + ' ' + stabLabel : stabLabel
|
|
225
272
|
const uptimeH = sortColumn === 'uptime' ? dir + ' Up%' : 'Up%'
|
|
226
|
-
const tokensH = 'Used'
|
|
227
273
|
|
|
228
274
|
// 📖 Helper to colorize first letter for keyboard shortcuts
|
|
229
275
|
// 📖 IMPORTANT: Pad PLAIN TEXT first, then apply colors to avoid alignment issues
|
|
@@ -238,27 +284,31 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
238
284
|
// 📖 Now colorize after padding is calculated on plain text
|
|
239
285
|
const rankH_c = colorFirst(rankH, W_RANK)
|
|
240
286
|
const tierH_c = colorFirst('Tier', W_TIER)
|
|
241
|
-
const originLabel = 'Provider'
|
|
287
|
+
const originLabel = isCompact ? 'PrOD…' : 'Provider'
|
|
242
288
|
const originH_c = sortColumn === 'origin'
|
|
243
|
-
? themeColors.accentBold(originLabel.padEnd(
|
|
244
|
-
: (originFilterMode > 0 ? themeColors.accentBold(originLabel.padEnd(
|
|
289
|
+
? themeColors.accentBold(originLabel.padEnd(wSource))
|
|
290
|
+
: (originFilterMode > 0 ? themeColors.accentBold(originLabel.padEnd(wSource)) : (() => {
|
|
245
291
|
// 📖 Provider keeps O for sorting and D for provider-filter cycling.
|
|
246
|
-
|
|
247
|
-
const
|
|
292
|
+
// 📖 In compact mode, shorten to 'PrOD…' (4 chars + ellipsis) to save space.
|
|
293
|
+
const plain = isCompact ? 'PrOD…' : 'PrOviDer'
|
|
294
|
+
const padding = ' '.repeat(Math.max(0, wSource - plain.length))
|
|
295
|
+
if (isCompact) {
|
|
296
|
+
return themeColors.dim('Pr') + themeColors.hotkey('O') + themeColors.hotkey('D') + themeColors.dim('…' + padding)
|
|
297
|
+
}
|
|
248
298
|
return themeColors.dim('Pr') + themeColors.hotkey('O') + themeColors.dim('vi') + themeColors.hotkey('D') + themeColors.dim('er' + padding)
|
|
249
299
|
})())
|
|
250
300
|
const modelH_c = colorFirst(modelH, W_MODEL)
|
|
251
301
|
const sweH_c = sortColumn === 'swe' ? themeColors.accentBold(sweH.padEnd(W_SWE)) : colorFirst(sweH, W_SWE)
|
|
252
302
|
const ctxH_c = sortColumn === 'ctx' ? themeColors.accentBold(ctxH.padEnd(W_CTX)) : colorFirst(ctxH, W_CTX)
|
|
253
|
-
const pingH_c = sortColumn === 'ping' ? themeColors.accentBold(pingH.padEnd(
|
|
254
|
-
const avgH_c = sortColumn === 'avg' ? themeColors.accentBold(avgH.padEnd(
|
|
255
|
-
const healthH_c = sortColumn === 'condition' ? themeColors.accentBold(healthH.padEnd(
|
|
303
|
+
const pingH_c = sortColumn === 'ping' ? themeColors.accentBold(pingH.padEnd(wPing)) : colorFirst(pingLabel, wPing)
|
|
304
|
+
const avgH_c = sortColumn === 'avg' ? themeColors.accentBold(avgH.padEnd(wAvg)) : colorFirst(avgLabel, wAvg)
|
|
305
|
+
const healthH_c = sortColumn === 'condition' ? themeColors.accentBold(healthH.padEnd(wStatus)) : colorFirst('Health', wStatus)
|
|
256
306
|
const verdictH_c = sortColumn === 'verdict' ? themeColors.accentBold(verdictH.padEnd(W_VERDICT)) : colorFirst(verdictH, W_VERDICT)
|
|
257
307
|
// 📖 Custom colorization for Stability: highlight 'B' (the sort key) since 'S' is taken by SWE
|
|
258
|
-
const stabH_c = sortColumn === 'stability' ? themeColors.accentBold(stabH.padEnd(
|
|
259
|
-
const plain =
|
|
260
|
-
const padding = ' '.repeat(Math.max(0,
|
|
261
|
-
return themeColors.dim('Sta') + themeColors.hotkey('B') + themeColors.dim('ility' + padding)
|
|
308
|
+
const stabH_c = sortColumn === 'stability' ? themeColors.accentBold(stabH.padEnd(wStab)) : (() => {
|
|
309
|
+
const plain = stabLabel
|
|
310
|
+
const padding = ' '.repeat(Math.max(0, wStab - plain.length))
|
|
311
|
+
return themeColors.dim('Sta') + themeColors.hotkey('B') + themeColors.dim((isCompact ? '.' : 'ility') + padding)
|
|
262
312
|
})()
|
|
263
313
|
// 📖 Up% sorts on U, so keep the highlighted shortcut in the shared yellow sort-key color.
|
|
264
314
|
const uptimeH_c = sortColumn === 'uptime' ? themeColors.accentBold(uptimeH.padEnd(W_UPTIME)) : (() => {
|
|
@@ -266,10 +316,15 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
266
316
|
const padding = ' '.repeat(Math.max(0, W_UPTIME - plain.length))
|
|
267
317
|
return themeColors.hotkey('U') + themeColors.dim('p%' + padding)
|
|
268
318
|
})()
|
|
269
|
-
const tokensH_c = themeColors.dim(tokensH.padEnd(W_TOKENS))
|
|
270
319
|
// 📖 Usage column removed from UI – no header or separator for it.
|
|
271
|
-
// Header
|
|
272
|
-
|
|
320
|
+
// 📖 Header row: conditionally include columns based on responsive visibility
|
|
321
|
+
const headerParts = []
|
|
322
|
+
if (showRank) headerParts.push(rankH_c)
|
|
323
|
+
if (showTier) headerParts.push(tierH_c)
|
|
324
|
+
headerParts.push(sweH_c, ctxH_c, modelH_c, originH_c, pingH_c, avgH_c, healthH_c, verdictH_c)
|
|
325
|
+
if (showStability) headerParts.push(stabH_c)
|
|
326
|
+
if (showUptime) headerParts.push(uptimeH_c)
|
|
327
|
+
lines.push(' ' + headerParts.join(COL_SEP))
|
|
273
328
|
|
|
274
329
|
|
|
275
330
|
|
|
@@ -311,9 +366,13 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
311
366
|
const num = themeColors.dim(String(r.idx).padEnd(W_RANK))
|
|
312
367
|
const tier = tierFn(r.tier.padEnd(W_TIER))
|
|
313
368
|
// 📖 Keep terminal view provider-specific so each row is monitorable per provider
|
|
369
|
+
// 📖 In compact mode, truncate provider name to 4 chars + '…'
|
|
314
370
|
const providerNameRaw = sources[r.providerKey]?.name ?? r.providerKey ?? 'NIM'
|
|
315
371
|
const providerName = normalizeOriginLabel(providerNameRaw, r.providerKey)
|
|
316
|
-
const
|
|
372
|
+
const providerDisplay = isCompact && providerName.length > 5
|
|
373
|
+
? providerName.slice(0, 4) + '…'
|
|
374
|
+
: providerName
|
|
375
|
+
const source = themeColors.provider(r.providerKey, providerDisplay.padEnd(wSource))
|
|
317
376
|
// 📖 Favorites: always reserve 2 display columns at the start of Model column.
|
|
318
377
|
// 📖 🎯 (2 cols) for recommended, ⭐ (2 cols) for favorites, ' ' (2 spaces) for non-favorites — keeps alignment stable.
|
|
319
378
|
const favoritePrefix = r.isRecommended ? '🎯' : r.isFavorite ? '⭐' : ' '
|
|
@@ -345,7 +404,7 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
345
404
|
// 📖 Keep the row-local spinner small and inline so users can still read the last measured latency.
|
|
346
405
|
const buildLatestPingDisplay = (value) => {
|
|
347
406
|
const spinner = r.isPinging ? ` ${FRAMES[frame % FRAMES.length]}` : ''
|
|
348
|
-
return `${value}${spinner}`.padEnd(
|
|
407
|
+
return `${value}${spinner}`.padEnd(wPing)
|
|
349
408
|
}
|
|
350
409
|
|
|
351
410
|
// 📖 Latest ping - pings are objects: { ms, code }
|
|
@@ -353,7 +412,7 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
353
412
|
const latestPing = r.pings.length > 0 ? r.pings[r.pings.length - 1] : null
|
|
354
413
|
let pingCell
|
|
355
414
|
if (!latestPing) {
|
|
356
|
-
const placeholder = r.isPinging ? buildLatestPingDisplay('———') : '———'.padEnd(
|
|
415
|
+
const placeholder = r.isPinging ? buildLatestPingDisplay('———') : '———'.padEnd(wPing)
|
|
357
416
|
pingCell = themeColors.dim(placeholder)
|
|
358
417
|
} else if (latestPing.code === '200') {
|
|
359
418
|
// 📖 Success - show response time
|
|
@@ -364,7 +423,7 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
364
423
|
pingCell = themeColors.dim(buildLatestPingDisplay(String(latestPing.ms)))
|
|
365
424
|
} else {
|
|
366
425
|
// 📖 Error or timeout - show "———" (error code is already in Status column)
|
|
367
|
-
const placeholder = r.isPinging ? buildLatestPingDisplay('———') : '———'.padEnd(
|
|
426
|
+
const placeholder = r.isPinging ? buildLatestPingDisplay('———') : '———'.padEnd(wPing)
|
|
368
427
|
pingCell = themeColors.dim(placeholder)
|
|
369
428
|
}
|
|
370
429
|
|
|
@@ -372,10 +431,10 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
372
431
|
const avg = getAvg(r)
|
|
373
432
|
let avgCell
|
|
374
433
|
if (avg !== Infinity) {
|
|
375
|
-
const str = String(avg).padEnd(
|
|
434
|
+
const str = String(avg).padEnd(wAvg)
|
|
376
435
|
avgCell = avg < 500 ? themeColors.metricGood(str) : avg < 1500 ? themeColors.metricWarn(str) : themeColors.metricBad(str)
|
|
377
436
|
} else {
|
|
378
|
-
avgCell = themeColors.dim('———'.padEnd(
|
|
437
|
+
avgCell = themeColors.dim('———'.padEnd(wAvg))
|
|
379
438
|
}
|
|
380
439
|
|
|
381
440
|
// 📖 Status column - build plain text with emoji, pad, then colorize
|
|
@@ -423,7 +482,18 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
423
482
|
statusText = '?'
|
|
424
483
|
statusColor = themeColors.dim
|
|
425
484
|
}
|
|
426
|
-
|
|
485
|
+
// 📖 In compact mode, truncate health text after 6 visible chars + '…' to fit wStatus
|
|
486
|
+
const statusDisplayText = isCompact ? (() => {
|
|
487
|
+
// 📖 Strip emoji prefix to measure text length, then truncate if needed
|
|
488
|
+
const plainText = statusText.replace(/^[\p{Emoji_Presentation}\p{Extended_Pictographic}]\s*/u, '')
|
|
489
|
+
if (plainText.length > 6) {
|
|
490
|
+
const emojiMatch = statusText.match(/^([\p{Emoji_Presentation}\p{Extended_Pictographic}]\s*)/u)
|
|
491
|
+
const prefix = emojiMatch ? emojiMatch[1] : ''
|
|
492
|
+
return prefix + plainText.slice(0, 6) + '…'
|
|
493
|
+
}
|
|
494
|
+
return statusText
|
|
495
|
+
})() : statusText
|
|
496
|
+
const status = statusColor(padEndDisplay(statusDisplayText, wStatus))
|
|
427
497
|
|
|
428
498
|
// 📖 Verdict column - use getVerdict() for stability-aware verdicts, then render with emoji
|
|
429
499
|
const verdict = getVerdict(r)
|
|
@@ -479,15 +549,15 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
479
549
|
const stabScore = getStabilityScore(r)
|
|
480
550
|
let stabCell
|
|
481
551
|
if (stabScore < 0) {
|
|
482
|
-
stabCell = themeColors.dim('———'.padEnd(
|
|
552
|
+
stabCell = themeColors.dim('———'.padEnd(wStab))
|
|
483
553
|
} else if (stabScore >= 80) {
|
|
484
|
-
stabCell = themeColors.metricGood(String(stabScore).padEnd(
|
|
554
|
+
stabCell = themeColors.metricGood(String(stabScore).padEnd(wStab))
|
|
485
555
|
} else if (stabScore >= 60) {
|
|
486
|
-
stabCell = themeColors.metricOk(String(stabScore).padEnd(
|
|
556
|
+
stabCell = themeColors.metricOk(String(stabScore).padEnd(wStab))
|
|
487
557
|
} else if (stabScore >= 40) {
|
|
488
|
-
stabCell = themeColors.metricWarn(String(stabScore).padEnd(
|
|
558
|
+
stabCell = themeColors.metricWarn(String(stabScore).padEnd(wStab))
|
|
489
559
|
} else {
|
|
490
|
-
stabCell = themeColors.metricBad(String(stabScore).padEnd(
|
|
560
|
+
stabCell = themeColors.metricBad(String(stabScore).padEnd(wStab))
|
|
491
561
|
}
|
|
492
562
|
|
|
493
563
|
// 📖 Uptime column - percentage of successful pings
|
|
@@ -508,22 +578,21 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
508
578
|
// 📖 Model text now mirrors the provider hue so provider affinity is visible
|
|
509
579
|
// 📖 even before the eye reaches the Provider column.
|
|
510
580
|
const nameCell = themeColors.provider(r.providerKey, name, { bold: isCursor })
|
|
511
|
-
const sourceCursorText =
|
|
581
|
+
const sourceCursorText = providerDisplay.padEnd(wSource)
|
|
512
582
|
const sourceCell = isCursor ? themeColors.provider(r.providerKey, sourceCursorText, { bold: true }) : source
|
|
513
583
|
|
|
514
584
|
// 📖 Usage column removed from UI – no usage data displayed.
|
|
515
585
|
// (We keep the logic but do not render it.)
|
|
516
586
|
const usageCell = ''
|
|
517
587
|
|
|
518
|
-
// 📖
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
const row = ' ' + num + COL_SEP + tier + COL_SEP + sweCell + COL_SEP + ctxCell + COL_SEP + nameCell + COL_SEP + sourceCell + COL_SEP + pingCell + COL_SEP + avgCell + COL_SEP + status + COL_SEP + speedCell + COL_SEP + stabCell + COL_SEP + uptimeCell + COL_SEP + tokensCell
|
|
588
|
+
// 📖 Build row: conditionally include columns based on responsive visibility
|
|
589
|
+
const rowParts = []
|
|
590
|
+
if (showRank) rowParts.push(num)
|
|
591
|
+
if (showTier) rowParts.push(tier)
|
|
592
|
+
rowParts.push(sweCell, ctxCell, nameCell, sourceCell, pingCell, avgCell, status, speedCell)
|
|
593
|
+
if (showStability) rowParts.push(stabCell)
|
|
594
|
+
if (showUptime) rowParts.push(uptimeCell)
|
|
595
|
+
const row = ' ' + rowParts.join(COL_SEP)
|
|
527
596
|
|
|
528
597
|
if (isCursor) {
|
|
529
598
|
lines.push(themeColors.bgModelCursor(row))
|
|
@@ -551,7 +620,7 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
551
620
|
const activeHotkey = (keyLabel, text, bg) => themeColors.badge(`${keyLabel}${text}`, bg, getReadableTextRgb(bg))
|
|
552
621
|
// 📖 Line 1: core navigation + filtering shortcuts
|
|
553
622
|
lines.push(
|
|
554
|
-
hotkey('F', ' Toggle Favorite') +
|
|
623
|
+
' ' + hotkey('F', ' Toggle Favorite') +
|
|
555
624
|
themeColors.dim(` • `) +
|
|
556
625
|
(tierFilterMode > 0
|
|
557
626
|
? activeHotkey('T', ` Tier (${activeTierLabel})`, getTierRgb(activeTierLabel))
|
|
@@ -567,11 +636,11 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
567
636
|
themeColors.dim(` • `) +
|
|
568
637
|
hotkey('K', ' Help')
|
|
569
638
|
)
|
|
570
|
-
// 📖 Line 2:
|
|
639
|
+
// 📖 Line 2: command palette (highlighted as new), recommend, feedback, and extended hints.
|
|
640
|
+
// 📖 CTRL+P ⚡️ Command Palette uses neon-green-on-dark-green background to highlight the feature.
|
|
641
|
+
const paletteLabel = chalk.bgRgb(0, 60, 0).rgb(57, 255, 20).bold(' NEW ! CTRL+P ⚡️ Command Palette ')
|
|
571
642
|
lines.push(
|
|
572
|
-
themeColors.dim(` `) +
|
|
573
|
-
hotkey('Ctrl+P', ' Command palette') + themeColors.dim(` • `) +
|
|
574
|
-
hotkey('Y', ' Install endpoints') + themeColors.dim(` • `) +
|
|
643
|
+
' ' + paletteLabel + themeColors.dim(` • `) +
|
|
575
644
|
hotkey('Q', ' Smart Recommend') + themeColors.dim(` • `) +
|
|
576
645
|
hotkey('G', ' Theme') + themeColors.dim(` • `) +
|
|
577
646
|
hotkey('I', ' Feedback, bugs & requests')
|
|
@@ -592,11 +661,7 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
592
661
|
'💬 ' +
|
|
593
662
|
themeColors.footerDiscord('\x1b]8;;https://discord.gg/ZTNFHvvCkU\x1b\\Discord\x1b]8;;\x1b\\') +
|
|
594
663
|
themeColors.dim(' → ') +
|
|
595
|
-
themeColors.footerDiscord('https://discord.gg/ZTNFHvvCkU')
|
|
596
|
-
themeColors.dim(' • ') +
|
|
597
|
-
themeColors.hotkey('N') + themeColors.dim(' Changelog') +
|
|
598
|
-
themeColors.dim(' • ') +
|
|
599
|
-
themeColors.dim('Ctrl+C Exit')
|
|
664
|
+
themeColors.footerDiscord('https://discord.gg/ZTNFHvvCkU')
|
|
600
665
|
lines.push(footerLine)
|
|
601
666
|
|
|
602
667
|
if (versionStatus.isOutdated) {
|
|
@@ -608,10 +673,12 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
608
673
|
lines.push(chalk.bgRed.white.bold(paddedBanner))
|
|
609
674
|
}
|
|
610
675
|
|
|
611
|
-
// 📖
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
676
|
+
// 📖 Final footer line: changelog shortcut + exit hint (replaces the old proxy notice).
|
|
677
|
+
lines.push(
|
|
678
|
+
' ' + themeColors.hotkey('N') + themeColors.dim(' Changelog') +
|
|
679
|
+
themeColors.dim(' • ') +
|
|
680
|
+
themeColors.dim('Ctrl+C Exit')
|
|
681
|
+
)
|
|
615
682
|
|
|
616
683
|
// 📖 Append \x1b[K (erase to EOL) to each line so leftover chars from previous
|
|
617
684
|
// 📖 frames are cleared. Then pad with blank cleared lines to fill the terminal,
|
package/src/testfcm.js
CHANGED
|
@@ -107,7 +107,7 @@ const TRANSCRIPT_FINDING_RULES = [
|
|
|
107
107
|
title: 'PTY width warning blocked the TUI flow',
|
|
108
108
|
severity: 'high',
|
|
109
109
|
regex: /please maximize your terminal|terminal is too small|reduce font size or maximize width/i,
|
|
110
|
-
task: 'Run `/testfcm` with
|
|
110
|
+
task: 'Run `/testfcm` with a wider PTY (at least 80 columns) before sending Enter.',
|
|
111
111
|
},
|
|
112
112
|
{
|
|
113
113
|
id: 'tool_missing',
|
package/src/theme.js
CHANGED
|
@@ -278,11 +278,13 @@ function paintBg(bgRgb, text, fgRgb = null, options = {}) {
|
|
|
278
278
|
export const themeColors = {
|
|
279
279
|
text: (text) => paintRgb(currentPalette().text, text),
|
|
280
280
|
textBold: (text) => paintRgb(currentPalette().textStrong, text, { bold: true }),
|
|
281
|
+
headerBold: (text) => paintRgb([142, 200, 255], text, { bold: true }),
|
|
281
282
|
dim: (text) => paintRgb(currentPalette().muted, text),
|
|
282
283
|
soft: (text) => paintRgb(currentPalette().soft, text),
|
|
283
284
|
accent: (text) => paintRgb(currentPalette().accent, text),
|
|
284
285
|
accentBold: (text) => paintRgb(currentPalette().accentStrong, text, { bold: true }),
|
|
285
286
|
info: (text) => paintRgb(currentPalette().info, text),
|
|
287
|
+
infoBold: (text) => paintRgb([100, 180, 255], text, { bold: true }),
|
|
286
288
|
success: (text) => paintRgb(currentPalette().success, text),
|
|
287
289
|
successBold: (text) => paintRgb(currentPalette().successStrong, text, { bold: true }),
|
|
288
290
|
warning: (text) => paintRgb(currentPalette().warning, text),
|
package/src/utils.js
CHANGED
|
@@ -464,7 +464,6 @@ export function parseArgs(argv) {
|
|
|
464
464
|
const sortAscFlag = flags.includes('--asc')
|
|
465
465
|
const hideUnconfigured = flags.includes('--hide-unconfigured')
|
|
466
466
|
const showUnconfigured = flags.includes('--show-unconfigured')
|
|
467
|
-
const disableWidthsWarning = flags.includes('--disable-widths-warning')
|
|
468
467
|
|
|
469
468
|
let tierFilter = tierValueIdx !== -1 ? args[tierValueIdx].toUpperCase() : null
|
|
470
469
|
let sortColumn = sortValueIdx !== -1 ? args[sortValueIdx].toLowerCase() : null
|
|
@@ -501,7 +500,6 @@ export function parseArgs(argv) {
|
|
|
501
500
|
pingInterval,
|
|
502
501
|
hideUnconfigured,
|
|
503
502
|
showUnconfigured,
|
|
504
|
-
disableWidthsWarning,
|
|
505
503
|
premiumMode,
|
|
506
504
|
// 📖 Profile system removed - API keys now persist permanently across all sessions
|
|
507
505
|
recommendMode,
|