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.
- package/CHANGELOG.md +43 -1
- package/README.md +20 -9
- package/package.json +1 -1
- package/src/app.js +40 -14
- package/src/command-palette.js +327 -59
- package/src/config.js +4 -2
- package/src/endpoint-installer.js +1 -1
- package/src/key-handler.js +205 -31
- package/src/overlays.js +105 -57
- package/src/product-flags.js +4 -9
- package/src/render-helpers.js +15 -2
- package/src/render-table.js +51 -25
- package/src/theme.js +2 -0
- package/src/utils.js +1 -1
package/src/product-flags.js
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file src/product-flags.js
|
|
3
|
-
* @description Product-level
|
|
3
|
+
* @description Product-level flags and feature gates.
|
|
4
4
|
*
|
|
5
5
|
* @details
|
|
6
|
-
* π
|
|
7
|
-
* π
|
|
8
|
-
* π
|
|
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.'
|
package/src/render-helpers.js
CHANGED
|
@@ -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
|
-
|
|
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))
|
package/src/render-table.js
CHANGED
|
@@ -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
|
-
* -
|
|
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 =
|
|
153
|
-
const W_CTX =
|
|
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 =
|
|
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 (
|
|
168
|
-
// π StabilityβStaB. (8), Providerβ4chars+β¦ (
|
|
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 =
|
|
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 =
|
|
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:
|
|
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
|
-
// π
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
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
|
-
// π
|
|
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)
|