free-coding-models 0.3.19 → 0.3.21

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 CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  ---
4
4
 
5
+ ## 0.3.21
6
+
7
+ ### Changed
8
+ - **Responsive table layout**: the TUI now adapts to narrow terminals by progressively shrinking columns and hiding least-useful ones. Compact mode shortens headers (`Lat. P`, `Avg. P`, `StaB.`, `PrOD…`) and tightens Provider (10 cols), Health (13 cols), Latest Ping (10 cols), and Avg Ping (8 cols). Below compact, Rank → Up% → Tier → Stability are hidden one by one. Minimum usable width: ~116 cols.
9
+ - **SWE% column tightened**: reduced from 9 to 6 columns wide — trims excess right padding without losing data.
10
+ - **Provider truncation in compact mode**: provider names longer than 5 chars are truncated to 4 chars + `…` (e.g. `Cere…`).
11
+ - **Health truncation in compact mode**: status text longer than 6 chars is truncated with `…` (e.g. `🔥 429 TR…`).
12
+ - **Removed "Used" column from TUI**: the token usage column has been removed from the main table as it was outdated.
13
+ - **Width guardrail threshold tightened**: the narrow-terminal warning now triggers only below 80 columns (instead of broader widths) and keeps the same 2-second auto-hide behavior.
14
+ - **Width warning is always enforced in narrow terminals**: removed the `Small Width Warnings` Settings toggle and the `--disable-widths-warning` runtime flag so the startup guardrail stays consistent.
15
+
16
+ ### Fixed
17
+ - **`--premium` filter lock-in**: premium now applies a resettable startup preset (S-tier + verdict sort) instead of hard-locking an extra hidden elite-only filter.
18
+
5
19
  ## 0.3.19
6
20
 
7
21
  ### Added
package/README.md CHANGED
@@ -133,7 +133,7 @@ free-coding-models --goose --tier S
133
133
  # "I want NVIDIA's top models only"
134
134
  free-coding-models --origin nvidia --tier S
135
135
 
136
- # "Show me only elite models that are currently healthy"
136
+ # "Start with an elite-focused preset, then adjust filters live"
137
137
  free-coding-models --premium
138
138
 
139
139
  # "I want to script this — give me JSON"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-coding-models",
3
- "version": "0.3.19",
3
+ "version": "0.3.21",
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",
package/src/app.js CHANGED
@@ -105,7 +105,7 @@ import { usageForRow as _usageForRow } from '../src/usage-reader.js'
105
105
  import { buildProviderModelTokenKey, loadTokenUsageByProviderModel } from '../src/token-usage-reader.js'
106
106
  import { parseOpenRouterResponse, fetchProviderQuota as _fetchProviderQuotaFromModule } from '../src/provider-quota-fetchers.js'
107
107
  import { isKnownQuotaTelemetry } from '../src/quota-capabilities.js'
108
- import { ALT_ENTER, ALT_LEAVE, ALT_HOME, PING_TIMEOUT, PING_INTERVAL, FPS, COL_MODEL, COL_MS, CELL_W, FRAMES, TIER_CYCLE, SETTINGS_OVERLAY_BG, HELP_OVERLAY_BG, RECOMMEND_OVERLAY_BG, OVERLAY_PANEL_WIDTH, TABLE_HEADER_LINES, TABLE_FOOTER_LINES, TABLE_FIXED_LINES, msCell, spinCell } from '../src/constants.js'
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, WIDTH_WARNING_MIN_COLS, msCell, spinCell } from '../src/constants.js'
109
109
  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'
@@ -192,9 +192,8 @@ export async function runApp(cliArgs, config) {
192
192
  if (cliArgs.pingInterval) config.settings.pingInterval = cliArgs.pingInterval
193
193
  if (cliArgs.hideUnconfigured) config.settings.hideUnconfiguredModels = true
194
194
  if (cliArgs.showUnconfigured) config.settings.hideUnconfiguredModels = false
195
- if (cliArgs.disableWidthsWarning) config.settings.disableWidthsWarning = true
196
195
 
197
- // 📖 Apply premium mode: show only S‑tier models sorted by verdict
196
+ // 📖 Apply premium mode as an initial, user-resettable view preset.
198
197
  if (cliArgs.premiumMode) {
199
198
  config.settings.tierFilter = 'S'
200
199
  config.settings.sortColumn = 'verdict'
@@ -383,13 +382,11 @@ export async function runApp(cliArgs, config) {
383
382
  mode, // 📖 'opencode' or 'openclaw' — controls Enter action
384
383
  tierFilterMode: 0, // 📖 Index into TIER_CYCLE (0=All, 1=S+, 2=S, ...)
385
384
  originFilterMode: 0, // 📖 Index into ORIGIN_CYCLE (0=All, then providers)
386
- premiumMode: cliArgs.premiumMode, // 📖 Special elite-only mode: S/S+ only, Health UP only, Perfect/Normal/Slow verdict only.
387
385
  hideUnconfiguredModels: config.settings?.hideUnconfiguredModels === true, // 📖 Hide providers with no configured API key when true.
388
- disableWidthsWarning: config.settings?.disableWidthsWarning ?? false, // 📖 Cached for runtime checks; keep it in sync with config.settings.
389
386
  scrollOffset: 0, // 📖 First visible model index in viewport
390
387
  terminalRows: process.stdout.rows || 24, // 📖 Current terminal height
391
388
  terminalCols: process.stdout.columns || 80, // 📖 Current terminal width
392
- widthWarningStartedAt: (process.stdout.columns || 80) < 166 && !(config.settings?.disableWidthsWarning ?? false) ? now : null, // 📖 Start immediately only when warnings are enabled in a narrow viewport.
389
+ widthWarningStartedAt: (process.stdout.columns || 80) < WIDTH_WARNING_MIN_COLS ? now : null, // 📖 Start immediately in very narrow viewports.
393
390
  widthWarningDismissed: false, // 📖 Esc hides the narrow-terminal warning early for the current narrow-width session.
394
391
  widthWarningShowCount: 0, // 📖 Counter for how many times the narrow-terminal warning has been shown (max 2 per session).
395
392
  // 📖 Settings screen state (P key opens it)
@@ -465,12 +462,10 @@ export async function runApp(cliArgs, config) {
465
462
  // 📖 Re-clamp viewport on terminal resize
466
463
  process.stdout.on('resize', () => {
467
464
  const prevCols = state.terminalCols
468
- const widthsWarningDisabled = state.config.settings?.disableWidthsWarning === true
469
465
  state.terminalRows = process.stdout.rows || 24
470
466
  state.terminalCols = process.stdout.columns || 80
471
- state.disableWidthsWarning = widthsWarningDisabled
472
- if (state.terminalCols < 166 && !widthsWarningDisabled) {
473
- if (prevCols >= 166 || state.widthWarningDismissed) {
467
+ if (state.terminalCols < WIDTH_WARNING_MIN_COLS) {
468
+ if (prevCols >= WIDTH_WARNING_MIN_COLS || state.widthWarningDismissed) {
474
469
  state.widthWarningStartedAt = Date.now()
475
470
  state.widthWarningDismissed = false
476
471
  state.widthWarningShowCount++ // 📖 Increment counter when showing the warning again
@@ -630,15 +625,10 @@ export async function runApp(cliArgs, config) {
630
625
  outputResults = outputResults.filter(r => ['S+', 'S', 'A+'].includes(r.tier))
631
626
  }
632
627
 
633
- // 📖 Apply premium mode filter if specified: elite-only (S/S+, UP, Good Verdict)
628
+ // 📖 Apply premium mode as a preselected tier family in JSON mode as well.
634
629
  if (cliArgs.premiumMode) {
635
- outputResults = outputResults.filter(r => {
636
- const isEliteTier = r.tier === 'S' || r.tier === 'S+'
637
- const isHealthUp = r.status === 'up'
638
- const verdict = getVerdict(r)
639
- const isGoodVerdict = ['Perfect', 'Normal', 'Slow'].includes(verdict)
640
- return isEliteTier && isHealthUp && isGoodVerdict
641
- })
630
+ const premiumTiers = TIER_LETTER_MAP.S || ['S+', 'S']
631
+ outputResults = outputResults.filter(r => premiumTiers.includes(r.tier))
642
632
  }
643
633
 
644
634
  // 📖 Sort by avg ping (ascending)
@@ -702,17 +692,6 @@ export async function runApp(cliArgs, config) {
702
692
  const originHide = activeOrigin !== null && r.providerKey !== activeOrigin
703
693
  r.hidden = tierHide || originHide
704
694
 
705
- // 📖 Premium Mode: elite-only constraints (Health UP, Good Verdict, S/S+ only)
706
- if (state.premiumMode && !r.hidden) {
707
- const isEliteTier = r.tier === 'S' || r.tier === 'S+'
708
- const isHealthUp = r.status === 'up'
709
- const verdict = getVerdict(r)
710
- const isGoodVerdict = ['Perfect', 'Normal', 'Slow'].includes(verdict)
711
-
712
- if (!isEliteTier || !isHealthUp || !isGoodVerdict) {
713
- r.hidden = true
714
- }
715
- }
716
695
  })
717
696
  return state.results
718
697
  }
@@ -900,8 +879,7 @@ export async function runApp(cliArgs, config) {
900
879
  state.settingsUpdateLatestVersion,
901
880
  false,
902
881
  state.startupLatestVersion,
903
- state.versionAlertsEnabled,
904
- state.config.settings?.disableWidthsWarning ?? false
882
+ state.versionAlertsEnabled
905
883
  )
906
884
  }
907
885
  tableContent = state.commandPaletteFrozenTable
@@ -933,8 +911,7 @@ export async function runApp(cliArgs, config) {
933
911
  state.settingsUpdateLatestVersion,
934
912
  false,
935
913
  state.startupLatestVersion,
936
- state.versionAlertsEnabled,
937
- state.config.settings?.disableWidthsWarning ?? false
914
+ state.versionAlertsEnabled
938
915
  )
939
916
  }
940
917
 
@@ -972,7 +949,7 @@ export async function runApp(cliArgs, config) {
972
949
  const initialVisible = state.results.filter(r => !r.hidden)
973
950
  state.visibleSorted = sortResultsWithPinnedFavorites(initialVisible, state.sortColumn, state.sortDirection)
974
951
 
975
- 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))
952
+ 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))
976
953
  if (process.stdout.isTTY) {
977
954
  process.stdout.flush && process.stdout.flush()
978
955
  }
package/src/cli-help.js CHANGED
@@ -26,14 +26,13 @@ const ANALYSIS_FLAGS = [
26
26
  { flag: '--json', description: 'Output results as JSON for scripts/automation' },
27
27
  { flag: '--tier <S|A|B|C>', description: 'Filter models by tier family' },
28
28
  { flag: '--recommend', description: 'Open Smart Recommend immediately on startup' },
29
- { flag: '--premium', description: 'Show only S/S+ models with perfect health and good verdict' },
29
+ { flag: '--premium', description: 'Start with S-tier filter + verdict sort (you can reset it in-app)' },
30
30
  { flag: '--sort <column>', description: 'Sort by column (rank, tier, origin, model, ping, avg, swe, ctx, condition, verdict, uptime, stability, usage)' },
31
31
  { flag: '--desc | --asc', description: 'Set sort direction (descending or ascending)' },
32
32
  { flag: '--origin <provider>', description: 'Filter models by provider origin' },
33
33
  { flag: '--ping-interval <ms>', description: 'Override ping interval in milliseconds' },
34
34
  { flag: '--hide-unconfigured', description: 'Hide models without configured API keys' },
35
35
  { flag: '--show-unconfigured', description: 'Show all models regardless of API key config' },
36
- { flag: '--disable-widths-warning', description: 'Disable terminal width warning' },
37
36
  ]
38
37
 
39
38
  const CONFIG_FLAGS = [
package/src/config.js CHANGED
@@ -209,7 +209,6 @@ function normalizeSettingsSection(settings) {
209
209
  return {
210
210
  ...safeSettings,
211
211
  hideUnconfiguredModels: typeof safeSettings.hideUnconfiguredModels === 'boolean' ? safeSettings.hideUnconfiguredModels : true,
212
- disableWidthsWarning: safeSettings.disableWidthsWarning === true,
213
212
  theme: ['dark', 'light', 'auto'].includes(safeSettings.theme) ? safeSettings.theme : 'auto',
214
213
  }
215
214
  }
@@ -230,7 +229,6 @@ function normalizeProfileSettings(settings) {
230
229
  return {
231
230
  ..._emptyProfileSettings(),
232
231
  ...safeSettings,
233
- disableWidthsWarning: safeSettings.disableWidthsWarning === true,
234
232
  theme: ['dark', 'light', 'auto'].includes(safeSettings.theme) ? safeSettings.theme : 'auto',
235
233
  }
236
234
  }
@@ -843,7 +841,6 @@ export function _emptyProfileSettings() {
843
841
  pingInterval: 10000, // 📖 default ms between pings in the steady "normal" mode
844
842
  hideUnconfiguredModels: true, // 📖 true = default to providers that are actually configured
845
843
  preferredToolMode: 'opencode', // 📖 remember the last Z-selected launcher across app restarts
846
- disableWidthsWarning: false, // 📖 Disable widths warning (default off)
847
844
  theme: 'auto', // 📖 'auto' follows the terminal/OS theme, override with 'dark' or 'light' if needed
848
845
  }
849
846
  }
package/src/constants.js CHANGED
@@ -17,6 +17,7 @@
17
17
  * - `CELL_W` is derived from `COL_MS` and used by `msCell` / `spinCell`.
18
18
  * - `TABLE_HEADER_LINES` + `TABLE_FOOTER_LINES` = `TABLE_FIXED_LINES` must stay in sync
19
19
  * with the actual number of lines rendered by `renderTable()` in bin/.
20
+ * - `WIDTH_WARNING_MIN_COLS` controls when the narrow-terminal startup warning appears.
20
21
  * - Overlay background colours (chalk.bgRgb) make each overlay panel visually distinct.
21
22
  *
22
23
  * @functions
@@ -30,6 +31,7 @@
30
31
  * FRAMES, TIER_CYCLE,
31
32
  * SETTINGS_OVERLAY_BG, HELP_OVERLAY_BG, RECOMMEND_OVERLAY_BG, LOG_OVERLAY_BG,
32
33
  * OVERLAY_PANEL_WIDTH,
34
+ * WIDTH_WARNING_MIN_COLS,
33
35
  * TABLE_HEADER_LINES, TABLE_FOOTER_LINES, TABLE_FIXED_LINES,
34
36
  * msCell, spinCell
35
37
  *
@@ -83,6 +85,9 @@ export const LOG_OVERLAY_BG = chalk.bgRgb(0, 0, 0) // 📖 Dark blue-gree
83
85
  // 📖 tint fills the panel consistently regardless of content length.
84
86
  export const OVERLAY_PANEL_WIDTH = 116
85
87
 
88
+ // 📖 Narrow-terminal warning appears only below this width.
89
+ export const WIDTH_WARNING_MIN_COLS = 80
90
+
86
91
  // 📖 Table row-budget constants — must stay in sync with renderTable()'s actual output.
87
92
  // 📖 If this drifts, model rows overflow and can push the title row out of view.
88
93
  export const TABLE_HEADER_LINES = 4 // 📖 title, spacer, column headers, separator
@@ -32,6 +32,7 @@ import { loadConfig, replaceConfigContents } from './config.js'
32
32
  import { cleanupLegacyProxyArtifacts } from './legacy-proxy-cleanup.js'
33
33
  import { cycleThemeSetting, detectActiveTheme } from './theme.js'
34
34
  import { buildCommandPaletteEntries, filterCommandPaletteEntries } from './command-palette.js'
35
+ import { WIDTH_WARNING_MIN_COLS } from './constants.js'
35
36
 
36
37
  // 📖 Some providers need an explicit probe model because the first catalog entry
37
38
  // 📖 is not guaranteed to be accepted by their chat endpoint.
@@ -447,38 +448,6 @@ export function createKeyHandler(ctx) {
447
448
  }
448
449
  }
449
450
 
450
- // 📖 Keep the width-warning runtime state synced with the persisted Settings toggle
451
- // 📖 so the overlay reacts immediately when the user enables or disables it.
452
- function syncWidthsWarningState() {
453
- const widthsWarningDisabled = state.config.settings?.disableWidthsWarning === true
454
- state.disableWidthsWarning = widthsWarningDisabled
455
-
456
- if (widthsWarningDisabled) {
457
- state.widthWarningStartedAt = null
458
- state.widthWarningDismissed = false
459
- return
460
- }
461
-
462
- state.widthWarningShowCount = 0
463
- if ((state.terminalCols || 80) < 166) {
464
- state.widthWarningStartedAt = Date.now()
465
- state.widthWarningDismissed = false
466
- return
467
- }
468
-
469
- state.widthWarningStartedAt = null
470
- state.widthWarningDismissed = false
471
- }
472
-
473
- // 📖 Toggle the width-warning setting and apply the effect immediately instead
474
- // 📖 of waiting for a resize or restart.
475
- function toggleWidthsWarningSetting() {
476
- if (!state.config.settings) state.config.settings = {}
477
- state.config.settings.disableWidthsWarning = !state.config.settings.disableWidthsWarning
478
- syncWidthsWarningState()
479
- saveConfig(state.config)
480
- }
481
-
482
451
  // 📖 Theme switches need to update both persisted preference and the live
483
452
  // 📖 semantic palette immediately so every screen redraw adopts the new colors.
484
453
  function applyThemeSetting(nextTheme) {
@@ -1384,8 +1353,7 @@ export function createKeyHandler(ctx) {
1384
1353
  if (state.settingsOpen) {
1385
1354
  const providerKeys = Object.keys(sources)
1386
1355
  const updateRowIdx = providerKeys.length
1387
- const widthWarningRowIdx = updateRowIdx + 1
1388
- const themeRowIdx = widthWarningRowIdx + 1
1356
+ const themeRowIdx = updateRowIdx + 1
1389
1357
  const cleanupLegacyProxyRowIdx = themeRowIdx + 1
1390
1358
  const changelogViewRowIdx = cleanupLegacyProxyRowIdx + 1
1391
1359
  // 📖 Profile system removed - API keys now persist permanently across all sessions
@@ -1530,12 +1498,6 @@ export function createKeyHandler(ctx) {
1530
1498
  return
1531
1499
  }
1532
1500
 
1533
- // 📖 Widths Warning toggle (Enter to toggle)
1534
- if (state.settingsCursor === widthWarningRowIdx) {
1535
- toggleWidthsWarningSetting()
1536
- return
1537
- }
1538
-
1539
1501
  if (state.settingsCursor === themeRowIdx) {
1540
1502
  cycleGlobalTheme()
1541
1503
  return
@@ -1579,11 +1541,6 @@ export function createKeyHandler(ctx) {
1579
1541
  cycleGlobalTheme()
1580
1542
  return
1581
1543
  }
1582
- // 📖 Widths Warning toggle (disable/enable)
1583
- if (state.settingsCursor === widthWarningRowIdx) {
1584
- toggleWidthsWarningSetting()
1585
- return
1586
- }
1587
1544
  // 📖 Profile system removed - API keys now persist permanently across all sessions
1588
1545
 
1589
1546
  // 📖 Toggle enabled/disabled for selected provider
@@ -1598,7 +1555,6 @@ export function createKeyHandler(ctx) {
1598
1555
  if (key.name === 't') {
1599
1556
  if (
1600
1557
  state.settingsCursor === updateRowIdx
1601
- || state.settingsCursor === widthWarningRowIdx
1602
1558
  || state.settingsCursor === themeRowIdx
1603
1559
  || state.settingsCursor === cleanupLegacyProxyRowIdx
1604
1560
  || state.settingsCursor === changelogViewRowIdx
@@ -1805,7 +1761,7 @@ export function createKeyHandler(ctx) {
1805
1761
  }
1806
1762
 
1807
1763
  // 📖 Esc can dismiss the narrow-terminal warning immediately without quitting the app.
1808
- if (key.name === 'escape' && state.terminalCols > 0 && state.terminalCols < 166) {
1764
+ if (key.name === 'escape' && state.terminalCols > 0 && state.terminalCols < WIDTH_WARNING_MIN_COLS) {
1809
1765
  state.widthWarningDismissed = true
1810
1766
  return
1811
1767
  }
package/src/overlays.js CHANGED
@@ -93,8 +93,7 @@ export function createOverlayRenderers(state, deps) {
93
93
  function renderSettings() {
94
94
  const providerKeys = Object.keys(sources)
95
95
  const updateRowIdx = providerKeys.length
96
- const widthWarningRowIdx = updateRowIdx + 1
97
- const themeRowIdx = widthWarningRowIdx + 1
96
+ const themeRowIdx = updateRowIdx + 1
98
97
  const cleanupLegacyProxyRowIdx = themeRowIdx + 1
99
98
  const changelogViewRowIdx = cleanupLegacyProxyRowIdx + 1
100
99
  const EL = '\x1b[K'
@@ -220,14 +219,6 @@ export function createOverlayRenderers(state, deps) {
220
219
  const updateRow = `${bullet(updateCursor)}${themeColors.textBold(updateActionLabel).padEnd(44)} ${updateStatus}`
221
220
  cursorLineByRow[updateRowIdx] = lines.length
222
221
  lines.push(updateCursor ? themeColors.bgCursor(updateRow) : updateRow)
223
- // 📖 Width warning visibility row for the startup narrow-terminal overlay.
224
- const disableWidthsWarning = Boolean(state.config.settings?.disableWidthsWarning)
225
- const widthWarningStatus = disableWidthsWarning
226
- ? themeColors.errorBold('🙈 Disabled')
227
- : themeColors.successBold('👁 Enabled')
228
- const widthWarningRow = `${bullet(state.settingsCursor === widthWarningRowIdx)}${themeColors.textBold('Small Width Warnings').padEnd(44)} ${widthWarningStatus}`
229
- cursorLineByRow[widthWarningRowIdx] = lines.length
230
- lines.push(state.settingsCursor === widthWarningRowIdx ? themeColors.bgCursor(widthWarningRow) : widthWarningRow)
231
222
  const themeStatus = getThemeStatusLabel(activeThemeSetting())
232
223
  const themeStatusColor = themeStatus.includes('Dark') ? themeColors.warningBold : themeColors.info
233
224
  const themeRow = `${bullet(state.settingsCursor === themeRowIdx)}${themeColors.textBold('Global Theme').padEnd(44)} ${themeStatusColor(themeStatus)}`
@@ -41,13 +41,13 @@ 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
53
  import { PROXY_DISABLED_NOTICE } from './product-flags.js'
@@ -67,7 +67,7 @@ export const PROVIDER_COLOR = new Proxy({}, {
67
67
  })
68
68
 
69
69
  // ─── 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, disableWidthsWarning = false) {
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) {
71
71
  // 📖 Filter out hidden models for display
72
72
  const visibleResults = results.filter(r => !r.hidden)
73
73
 
@@ -146,25 +146,68 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
146
146
 
147
147
  // 📖 Column widths (generous spacing with margins)
148
148
  const COL_SEP = getColumnSpacing()
149
+ const SEP_W = 3 // ' │ ' display width
150
+ const ROW_MARGIN = 2 // left margin ' '
149
151
  const W_RANK = 6
150
152
  const W_TIER = 6
151
153
  const W_CTX = 6
152
154
  const W_SOURCE = 14
153
155
  const W_MODEL = 26
154
- const W_SWE = 9
155
- const W_PING = 14
156
- const W_AVG = 11
156
+ const W_SWE = 6
157
157
  const W_STATUS = 18
158
158
  const W_VERDICT = 14
159
- const W_STAB = 11
160
159
  const W_UPTIME = 6
161
- const W_TOKENS = 7
160
+ // const W_TOKENS = 7 // Used column removed
162
161
  // const W_USAGE = 7 // Usage column removed
163
- const MIN_TABLE_WIDTH = 166
162
+ const MIN_TABLE_WIDTH = WIDTH_WARNING_MIN_COLS
163
+
164
+ // 📖 Responsive column visibility: progressively hide least-useful columns
165
+ // 📖 and shorten header labels when terminal width is insufficient.
166
+ // 📖 Hiding order (least useful first): Rank → Up% → Tier → Stability
167
+ // 📖 Compact mode shrinks: Latest Ping→Lat. P (10), Avg Ping→Avg. P (8),
168
+ // 📖 Stability→StaB. (8), Provider→4chars+… (10), Health→6chars+… (13)
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 = 10 // 'Lat. P' instead of 'Latest Ping'
196
+ wAvg = 8 // 'Avg. P' instead of 'Avg Ping'
197
+ wStab = 8 // 'StaB.' instead of 'Stability'
198
+ wSource = 10 // Provider truncated to 4 chars + '…'
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 && !disableWidthsWarning && !widthWarningDismissed && widthWarningShowCount < 2 && remainingMs > 0
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
- const pingH = sortColumn === 'ping' ? dir + ' Latest Ping' : 'Latest Ping'
221
- const avgH = sortColumn === 'avg' ? dir + ' Avg Ping' : 'Avg Ping'
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 + ' Stability' : 'Stability'
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(W_SOURCE))
244
- : (originFilterMode > 0 ? themeColors.accentBold(originLabel.padEnd(W_SOURCE)) : (() => {
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
- const plain = 'PrOviDer'
247
- const padding = ' '.repeat(Math.max(0, W_SOURCE - plain.length))
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(W_PING)) : colorFirst('Latest Ping', W_PING)
254
- const avgH_c = sortColumn === 'avg' ? themeColors.accentBold(avgH.padEnd(W_AVG)) : colorFirst('Avg Ping', W_AVG)
255
- const healthH_c = sortColumn === 'condition' ? themeColors.accentBold(healthH.padEnd(W_STATUS)) : colorFirst('Health', W_STATUS)
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(W_STAB)) : (() => {
259
- const plain = 'Stability'
260
- const padding = ' '.repeat(Math.max(0, W_STAB - plain.length))
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 without Usage column (column order: Rank, Tier, SWE%, CTX, Model, Provider, Latest Ping, Avg Ping, Health, Verdict, Stability, Up%, Used)
272
- lines.push(' ' + rankH_c + COL_SEP + tierH_c + COL_SEP + sweH_c + COL_SEP + ctxH_c + COL_SEP + modelH_c + COL_SEP + originH_c + COL_SEP + pingH_c + COL_SEP + avgH_c + COL_SEP + healthH_c + COL_SEP + verdictH_c + COL_SEP + stabH_c + COL_SEP + uptimeH_c + COL_SEP + tokensH_c)
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 source = themeColors.provider(r.providerKey, providerName.padEnd(W_SOURCE))
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(W_PING)
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(W_PING)
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(W_PING)
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(W_AVG)
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(W_AVG))
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
- const status = statusColor(padEndDisplay(statusText, W_STATUS))
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(W_STAB))
552
+ stabCell = themeColors.dim('———'.padEnd(wStab))
483
553
  } else if (stabScore >= 80) {
484
- stabCell = themeColors.metricGood(String(stabScore).padEnd(W_STAB))
554
+ stabCell = themeColors.metricGood(String(stabScore).padEnd(wStab))
485
555
  } else if (stabScore >= 60) {
486
- stabCell = themeColors.metricOk(String(stabScore).padEnd(W_STAB))
556
+ stabCell = themeColors.metricOk(String(stabScore).padEnd(wStab))
487
557
  } else if (stabScore >= 40) {
488
- stabCell = themeColors.metricWarn(String(stabScore).padEnd(W_STAB))
558
+ stabCell = themeColors.metricWarn(String(stabScore).padEnd(wStab))
489
559
  } else {
490
- stabCell = themeColors.metricBad(String(stabScore).padEnd(W_STAB))
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 = providerName.padEnd(W_SOURCE)
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
- // 📖 Used column total historical prompt+completion tokens consumed for this
519
- // 📖 exact provider/model pair, loaded from the local usage snapshot file at startup.
520
- const tokenTotal = Number(r.totalTokens) || 0
521
- const tokensCell = tokenTotal > 0
522
- ? themeColors.metricOk(formatTokenTotalCompact(tokenTotal).padEnd(W_TOKENS))
523
- : themeColors.dim('0'.padEnd(W_TOKENS))
524
-
525
- // 📖 Build row with double space between columns (order: Rank, Tier, SWE%, CTX, Model, Provider, Latest Ping, Avg Ping, Health, Verdict, Stability, Up%, Used)
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))
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 the width warning disabled in the isolated config or force a wider PTY before sending Enter.',
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/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,