free-coding-models 0.3.21 β†’ 0.3.23

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.
@@ -1,14 +1,9 @@
1
1
  /**
2
2
  * @file src/product-flags.js
3
- * @description Product-level copy for temporarily unavailable surfaces.
3
+ * @description Product-level flags and feature gates.
4
4
  *
5
5
  * @details
6
- * πŸ“– The proxy bridge is being rebuilt from scratch. The main TUI still
7
- * πŸ“– shows a clear status line so users know the missing integration is
8
- * πŸ“– intentional instead of silently broken.
9
- *
10
- * @exports PROXY_DISABLED_NOTICE
6
+ * πŸ“– Previously held PROXY_DISABLED_NOTICE for the proxy bridge rebuild.
7
+ * πŸ“– That notice was removed in 0.3.22 after the proxy surface was fully
8
+ * πŸ“– retired. File kept as a home for future product-level flags.
11
9
  */
12
-
13
- // πŸ“– Public note rendered in the main TUI footer and reused in CLI/runtime guards.
14
- export const PROXY_DISABLED_NOTICE = 'ℹ️ Proxy is temporarily disabled while we rebuild it into a much more stable bridge for external tools.'
@@ -168,10 +168,23 @@ export function calculateViewport(terminalRows, scrollOffset, totalModels, extra
168
168
 
169
169
  // πŸ“– sortResultsWithPinnedFavorites: Recommended models are pinned above favorites, favorites above non-favorites.
170
170
  // πŸ“– Recommended: sorted by recommendation score (highest first).
171
- // πŸ“– Favorites: keep insertion order (favoriteRank).
171
+ // πŸ“– Favorites: keep insertion order (favoriteRank) when pinFavorites=true.
172
172
  // πŸ“– Non-favorites: active sort column/direction.
173
173
  // πŸ“– Models that are both recommended AND favorite β€” show in recommended section.
174
- export function sortResultsWithPinnedFavorites(results, sortColumn, sortDirection) {
174
+ // πŸ“– pinFavorites=false keeps favorites highlighted but lets normal sort/filter order apply.
175
+ export function sortResultsWithPinnedFavorites(results, sortColumn, sortDirection, { pinFavorites = true } = {}) {
176
+ if (!pinFavorites) {
177
+ const recommendedRows = results
178
+ .filter((r) => r.isRecommended)
179
+ .sort((a, b) => (b.recommendScore || 0) - (a.recommendScore || 0))
180
+ const nonRecommendedRows = sortResults(
181
+ results.filter((r) => !r.isRecommended),
182
+ sortColumn,
183
+ sortDirection
184
+ )
185
+ return [...recommendedRows, ...nonRecommendedRows]
186
+ }
187
+
175
188
  const recommendedRows = results
176
189
  .filter((r) => r.isRecommended && !r.isFavorite)
177
190
  .sort((a, b) => (b.recommendScore || 0) - (a.recommendScore || 0))
@@ -13,7 +13,8 @@
13
13
  * - Emoji-aware padding via padEndDisplay for aligned verdict/status cells
14
14
  * - Viewport clipping with above/below indicators
15
15
  * - Smart badges (mode, tier filter, origin filter)
16
- * - Install-endpoints shortcut surfaced directly in the footer hints
16
+ * - Favorites mode hint surfaced directly in footer hints (`Y`)
17
+ * - High-visibility active text-filter banner with one-key clear action (`X`)
17
18
  * - Full-width red outdated-version banner when a newer npm release is known
18
19
  * - Distinct auth-failure vs missing-key health labels so configured providers stay honest
19
20
  *
@@ -50,7 +51,6 @@ import { getAvg, getVerdict, getUptime, getStabilityScore, getVersionStatusInfo
50
51
  import { usagePlaceholderForProvider } from './ping.js'
51
52
  import { calculateViewport, sortResultsWithPinnedFavorites, padEndDisplay, displayWidth } from './render-helpers.js'
52
53
  import { getToolMeta } from './tool-metadata.js'
53
- import { PROXY_DISABLED_NOTICE } from './product-flags.js'
54
54
  import { getColumnSpacing } from './ui-config.js'
55
55
 
56
56
  const require = createRequire(import.meta.url)
@@ -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) {
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, favoritesPinnedAndSticky = false, customTextFilter = null) {
71
71
  // πŸ“– Filter out hidden models for display
72
72
  const visibleResults = results.filter(r => !r.hidden)
73
73
 
@@ -149,11 +149,11 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
149
149
  const SEP_W = 3 // ' β”‚ ' display width
150
150
  const ROW_MARGIN = 2 // left margin ' '
151
151
  const W_RANK = 6
152
- const W_TIER = 6
153
- const W_CTX = 6
152
+ const W_TIER = 5
153
+ const W_CTX = 4
154
154
  const W_SOURCE = 14
155
155
  const W_MODEL = 26
156
- const W_SWE = 6
156
+ const W_SWE = 5
157
157
  const W_STATUS = 18
158
158
  const W_VERDICT = 14
159
159
  const W_UPTIME = 6
@@ -164,8 +164,9 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
164
164
  // πŸ“– Responsive column visibility: progressively hide least-useful columns
165
165
  // πŸ“– and shorten header labels when terminal width is insufficient.
166
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)
167
+ // πŸ“– Compact mode shrinks: Latest Pingβ†’Lat. P (9), Avg Pingβ†’Avg. P (8),
168
+ // πŸ“– Stabilityβ†’StaB. (8), Providerβ†’4chars+… (7), Healthβ†’6chars+… (13)
169
+ // πŸ“– Breakpoints: full=169 | compact=146 | -Rank=137 | -Up%=128 | -Tier=120 | -Stab=109
169
170
  let wPing = 14
170
171
  let wAvg = 11
171
172
  let wStab = 11
@@ -192,10 +193,10 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
192
193
  // πŸ“– Step 1: Compact mode β€” shorten labels and reduce column widths
193
194
  if (calcWidth() > terminalCols) {
194
195
  isCompact = true
195
- wPing = 10 // 'Lat. P' instead of 'Latest Ping'
196
+ wPing = 9 // 'Lat. P' instead of 'Latest Ping'
196
197
  wAvg = 8 // 'Avg. P' instead of 'Avg Ping'
197
198
  wStab = 8 // 'StaB.' instead of 'Stability'
198
- wSource = 10 // Provider truncated to 4 chars + '…'
199
+ wSource = 7 // Provider truncated to 4 chars + '…', 7 cols total
199
200
  wStatus = 13 // Health truncated after 6 chars + '…'
200
201
  }
201
202
  // πŸ“– Steps 2–5: Progressive column hiding (least useful first)
@@ -237,7 +238,9 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
237
238
  }
238
239
 
239
240
  // πŸ“– Sort models using the shared helper
240
- const sorted = sortResultsWithPinnedFavorites(visibleResults, sortColumn, sortDirection)
241
+ const sorted = sortResultsWithPinnedFavorites(visibleResults, sortColumn, sortDirection, {
242
+ pinFavorites: favoritesPinnedAndSticky,
243
+ })
241
244
 
242
245
  const lines = [
243
246
  ` ${themeColors.accentBold(`πŸš€ free-coding-models v${LOCAL_VERSION}`)}${modeBadge}${pingControlBadge}${tierBadge}${originBadge}${chalk.reset('')} ` +
@@ -339,6 +342,7 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
339
342
  }
340
343
 
341
344
  // πŸ“– Viewport clipping: only render models that fit on screen
345
+ const hasCustomFilter = typeof customTextFilter === 'string' && customTextFilter.trim().length > 0
342
346
  const extraFooterLines = versionStatus.isOutdated ? 1 : 0
343
347
  const vp = calculateViewport(terminalRows, scrollOffset, sorted.length, extraFooterLines)
344
348
  const paintSweScore = (score, paddedText) => {
@@ -618,9 +622,13 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
618
622
  // πŸ“– states are obvious even when the user misses the smaller header badges.
619
623
  const configuredBadgeBg = getTheme() === 'dark' ? [52, 120, 88] : [195, 234, 206]
620
624
  const activeHotkey = (keyLabel, text, bg) => themeColors.badge(`${keyLabel}${text}`, bg, getReadableTextRgb(bg))
625
+ const favoritesModeBg = favoritesPinnedAndSticky ? [157, 122, 48] : [95, 95, 95]
626
+ const favoritesModeLabel = favoritesPinnedAndSticky ? ' Favorites Pinned' : ' Favorites Normal'
621
627
  // πŸ“– Line 1: core navigation + filtering shortcuts
622
628
  lines.push(
623
- hotkey('F', ' Toggle Favorite') +
629
+ ' ' + hotkey('F', ' Toggle Favorite') +
630
+ themeColors.dim(` β€’ `) +
631
+ activeHotkey('Y', favoritesModeLabel, favoritesModeBg) +
624
632
  themeColors.dim(` β€’ `) +
625
633
  (tierFilterMode > 0
626
634
  ? activeHotkey('T', ` Tier (${activeTierLabel})`, getTierRgb(activeTierLabel))
@@ -636,11 +644,11 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
636
644
  themeColors.dim(` β€’ `) +
637
645
  hotkey('K', ' Help')
638
646
  )
639
- // πŸ“– Line 2: install flow, recommend, feedback, and extended hints.
647
+ // πŸ“– Line 2: command palette (highlighted as new), recommend, feedback, and extended hints.
648
+ // πŸ“– CTRL+P ⚑️ Command Palette uses neon-green-on-dark-green background to highlight the feature.
649
+ const paletteLabel = chalk.bgRgb(0, 60, 0).rgb(57, 255, 20).bold(' NEW ! CTRL+P ⚑️ Command Palette ')
640
650
  lines.push(
641
- themeColors.dim(` `) +
642
- hotkey('Ctrl+P', ' Command palette') + themeColors.dim(` β€’ `) +
643
- hotkey('Y', ' Install endpoints') + themeColors.dim(` β€’ `) +
651
+ ' ' + paletteLabel + themeColors.dim(` β€’ `) +
644
652
  hotkey('Q', ' Smart Recommend') + themeColors.dim(` β€’ `) +
645
653
  hotkey('G', ' Theme') + themeColors.dim(` β€’ `) +
646
654
  hotkey('I', ' Feedback, bugs & requests')
@@ -661,11 +669,7 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
661
669
  'πŸ’¬ ' +
662
670
  themeColors.footerDiscord('\x1b]8;;https://discord.gg/ZTNFHvvCkU\x1b\\Discord\x1b]8;;\x1b\\') +
663
671
  themeColors.dim(' β†’ ') +
664
- themeColors.footerDiscord('https://discord.gg/ZTNFHvvCkU') +
665
- themeColors.dim(' β€’ ') +
666
- themeColors.hotkey('N') + themeColors.dim(' Changelog') +
667
- themeColors.dim(' β€’ ') +
668
- themeColors.dim('Ctrl+C Exit')
672
+ themeColors.footerDiscord('https://discord.gg/ZTNFHvvCkU')
669
673
  lines.push(footerLine)
670
674
 
671
675
  if (versionStatus.isOutdated) {
@@ -677,10 +681,32 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
677
681
  lines.push(chalk.bgRed.white.bold(paddedBanner))
678
682
  }
679
683
 
680
- // πŸ“– Stable release notice: keep the bridge rebuild status explicit in the main UI
681
- // πŸ“– so users do not go hunting for hidden controls that are disabled on purpose.
682
- const bridgeNotice = chalk.italic.rgb(...getTierRgb('A-'))(` ${PROXY_DISABLED_NOTICE}`)
683
- lines.push(bridgeNotice)
684
+ // πŸ“– Final footer line: changelog + optional active text-filter badge + exit hint.
685
+ let filterBadge = ''
686
+ if (hasCustomFilter) {
687
+ const normalizedFilter = customTextFilter.trim().replace(/\s+/g, ' ')
688
+ const filterPrefix = 'X Disable filter: "'
689
+ const filterSuffix = '"'
690
+ const separatorPlain = ' β€’ '
691
+ const baseFooterPlain = ' N Changelog' + separatorPlain + 'Ctrl+C Exit'
692
+ const baseBadgeWidth = displayWidth(` ${filterPrefix}${filterSuffix} `)
693
+ const availableFilterWidth = terminalCols > 0
694
+ ? Math.max(8, terminalCols - displayWidth(baseFooterPlain) - displayWidth(separatorPlain) - baseBadgeWidth)
695
+ : normalizedFilter.length
696
+ const visibleFilter = normalizedFilter.length > availableFilterWidth
697
+ ? `${normalizedFilter.slice(0, Math.max(3, availableFilterWidth - 3))}...`
698
+ : normalizedFilter
699
+ filterBadge = chalk.bgYellow.black.bold(` ${filterPrefix}${visibleFilter}${filterSuffix} `)
700
+ }
701
+
702
+ lines.push(
703
+ ' ' + themeColors.hotkey('N') + themeColors.dim(' Changelog') +
704
+ (filterBadge
705
+ ? themeColors.dim(' β€’ ') + filterBadge
706
+ : '') +
707
+ themeColors.dim(' β€’ ') +
708
+ themeColors.dim('Ctrl+C Exit')
709
+ )
684
710
 
685
711
  // πŸ“– Append \x1b[K (erase to EOL) to each line so leftover chars from previous
686
712
  // πŸ“– frames are cleared. Then pad with blank cleared lines to fill the terminal,
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
@@ -220,7 +220,7 @@ export const getStabilityScore = (r) => {
220
220
  //
221
221
  // πŸ“– Supported columns in the sorter.
222
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.
223
+ // πŸ“– while `Y` is used by the live UI for favorites display mode.
224
224
  // - 'rank' (R key) β€” original index from sources.js
225
225
  // - 'tier' (internal) β€” tier hierarchy (S+ first, C last)
226
226
  // - 'origin' (O key) β€” provider name (all NIM for now, future-proofed)