free-coding-models 0.2.4 β 0.2.8
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 +1242 -0
- package/README.md +57 -9
- package/bin/free-coding-models.js +83 -2
- package/package.json +6 -2
- package/sources.js +8 -1
- package/src/analysis.js +5 -1
- package/src/cache.js +165 -0
- package/src/config.js +247 -11
- package/src/constants.js +4 -4
- package/src/key-handler.js +6 -0
- package/src/openclaw.js +6 -1
- package/src/opencode.js +9 -2
- package/src/overlays.js +190 -50
- package/src/provider-metadata.js +8 -7
- package/src/render-helpers.js +6 -4
- package/src/render-table.js +7 -7
- package/src/security.js +210 -0
- package/src/tool-launchers.js +6 -1
- package/src/utils.js +67 -2
package/src/overlays.js
CHANGED
|
@@ -22,6 +22,7 @@ export function createOverlayRenderers(state, deps) {
|
|
|
22
22
|
chalk,
|
|
23
23
|
sources,
|
|
24
24
|
PROVIDER_METADATA,
|
|
25
|
+
PROVIDER_COLOR,
|
|
25
26
|
LOCAL_VERSION,
|
|
26
27
|
getApiKey,
|
|
27
28
|
getProxySettings,
|
|
@@ -56,7 +57,9 @@ export function createOverlayRenderers(state, deps) {
|
|
|
56
57
|
|
|
57
58
|
// π Wrap plain diagnostic text so long Settings messages stay readable inside
|
|
58
59
|
// π the overlay instead of turning into one truncated red line.
|
|
59
|
-
|
|
60
|
+
// π Uses 100% of terminal width minus padding for better readability.
|
|
61
|
+
const wrapPlainText = (text, width = null) => {
|
|
62
|
+
const effectiveWidth = width || (state.terminalCols - 16)
|
|
60
63
|
const normalized = typeof text === 'string' ? text.trim() : ''
|
|
61
64
|
if (!normalized) return []
|
|
62
65
|
const words = normalized.split(/\s+/)
|
|
@@ -64,7 +67,7 @@ export function createOverlayRenderers(state, deps) {
|
|
|
64
67
|
let current = ''
|
|
65
68
|
for (const word of words) {
|
|
66
69
|
const next = current ? `${current} ${word}` : word
|
|
67
|
-
if (next.length >
|
|
70
|
+
if (next.length > effectiveWidth && current) {
|
|
68
71
|
lines.push(current)
|
|
69
72
|
current = word
|
|
70
73
|
} else {
|
|
@@ -85,6 +88,44 @@ export function createOverlayRenderers(state, deps) {
|
|
|
85
88
|
return String(Math.floor(safeTotal))
|
|
86
89
|
}
|
|
87
90
|
|
|
91
|
+
// π Colorize latency with gradient: green (<500ms) β orange (<1000ms) β yellow (<1500ms) β red (>=1500ms)
|
|
92
|
+
const colorizeLatency = (latency, text) => {
|
|
93
|
+
const ms = Number(latency) || 0
|
|
94
|
+
if (ms <= 0) return chalk.dim(text)
|
|
95
|
+
if (ms < 500) return chalk.greenBright(text)
|
|
96
|
+
if (ms < 1000) return chalk.rgb(255, 165, 0)(text) // Orange
|
|
97
|
+
if (ms < 1500) return chalk.yellow(text)
|
|
98
|
+
return chalk.red(text)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// π Colorize tokens with gradient: dim green (few) β bright green (many)
|
|
102
|
+
const colorizeTokens = (tokens, text) => {
|
|
103
|
+
const tok = Number(tokens) || 0
|
|
104
|
+
if (tok <= 0) return chalk.dim(text)
|
|
105
|
+
// Gradient: light green (low) β medium green β bright green (high, >30k)
|
|
106
|
+
if (tok < 10_000) return chalk.hex('#90EE90')(text) // Light green
|
|
107
|
+
if (tok < 30_000) return chalk.hex('#32CD32')(text) // Lime green
|
|
108
|
+
return chalk.greenBright(text) // Full brightness green
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// π Get model color based on status code - distinct colors for each error type
|
|
112
|
+
const getModelColorByStatus = (status) => {
|
|
113
|
+
const sc = String(status)
|
|
114
|
+
if (sc === '200') return chalk.greenBright // Success - bright green
|
|
115
|
+
if (sc === '404') return chalk.rgb(139, 0, 0) // Not found - dark red
|
|
116
|
+
if (sc === '400') return chalk.hex('#8B008B') // Bad request - dark magenta
|
|
117
|
+
if (sc === '401') return chalk.hex('#9932CC') // Unauthorized - dark orchid
|
|
118
|
+
if (sc === '403') return chalk.hex('#BA55D3') // Forbidden - medium orchid
|
|
119
|
+
if (sc === '413') return chalk.hex('#FF6347') // Payload too large - tomato red
|
|
120
|
+
if (sc === '429') return chalk.hex('#FFB90F') // Rate limit - dark orange
|
|
121
|
+
if (sc === '500') return chalk.hex('#DC143C') // Internal server error - crimson
|
|
122
|
+
if (sc === '502') return chalk.hex('#C71585') // Bad gateway - medium violet red
|
|
123
|
+
if (sc === '503') return chalk.hex('#9370DB') // Service unavailable - medium purple
|
|
124
|
+
if (sc.startsWith('5')) return chalk.magenta // Other 5xx - magenta
|
|
125
|
+
if (sc === '0') return chalk.hex('#696969') // Timeout/error - dim gray
|
|
126
|
+
return chalk.white // Unknown - white
|
|
127
|
+
}
|
|
128
|
+
|
|
88
129
|
// βββ Settings screen renderer βββββββββββββββββββββββββββββββββββββββββββββ
|
|
89
130
|
// π renderSettings: Draw the settings overlay in the alt screen buffer.
|
|
90
131
|
// π Shows all providers with their API key (masked) + enabled state.
|
|
@@ -102,14 +143,19 @@ export function createOverlayRenderers(state, deps) {
|
|
|
102
143
|
const lines = []
|
|
103
144
|
const cursorLineByRow = {}
|
|
104
145
|
|
|
105
|
-
|
|
106
|
-
lines.push(` ${chalk.
|
|
146
|
+
// π Branding header
|
|
147
|
+
lines.push(` ${chalk.cyanBright('π')} ${chalk.bold.cyanBright('free-coding-models')} ${chalk.dim(`v${LOCAL_VERSION}`)}`)
|
|
148
|
+
lines.push(` ${chalk.bold('β Settings')}`)
|
|
149
|
+
|
|
107
150
|
if (state.settingsErrorMsg) {
|
|
108
151
|
lines.push(` ${chalk.red.bold(state.settingsErrorMsg)}`)
|
|
152
|
+
lines.push('')
|
|
109
153
|
}
|
|
110
|
-
|
|
154
|
+
|
|
111
155
|
lines.push(` ${chalk.bold('π§© Providers')}`)
|
|
112
|
-
|
|
156
|
+
// π Dynamic separator line using 100% terminal width
|
|
157
|
+
const separatorWidth = Math.max(20, state.terminalCols - 10)
|
|
158
|
+
lines.push(` ${chalk.dim(' ' + 'β'.repeat(separatorWidth))}`)
|
|
113
159
|
lines.push('')
|
|
114
160
|
|
|
115
161
|
for (let i = 0; i < providerKeys.length; i++) {
|
|
@@ -153,10 +199,13 @@ export function createOverlayRenderers(state, deps) {
|
|
|
153
199
|
else if (testResult === 'rate_limited') testBadge = chalk.yellow('[Rate limit β³]')
|
|
154
200
|
else if (testResult === 'no_callable_model') testBadge = chalk.magenta('[No model β ]')
|
|
155
201
|
else if (testResult === 'fail') testBadge = chalk.red('[Test β]')
|
|
156
|
-
|
|
202
|
+
// π No truncation of rate limits - overlay now uses 100% terminal width
|
|
203
|
+
const rateSummary = chalk.dim(meta.rateLimits || 'No limit info')
|
|
157
204
|
|
|
158
205
|
const enabledBadge = enabled ? chalk.greenBright('β
') : chalk.redBright('β')
|
|
159
|
-
|
|
206
|
+
// π Color provider names the same way as in the main table
|
|
207
|
+
const providerRgb = PROVIDER_COLOR[pk] ?? [105, 190, 245]
|
|
208
|
+
const providerName = chalk.bold.rgb(...providerRgb)((meta.label || src.name || pk).slice(0, 22).padEnd(22))
|
|
160
209
|
const bullet = isCursor ? chalk.bold.cyan(' β― ') : chalk.dim(' ')
|
|
161
210
|
|
|
162
211
|
const row = `${bullet}[ ${enabledBadge} ] ${providerName} ${keyDisplay.padEnd(30)} ${testBadge} ${rateSummary}`
|
|
@@ -171,7 +220,10 @@ export function createOverlayRenderers(state, deps) {
|
|
|
171
220
|
if (selectedSource && state.settingsCursor < providerKeys.length) {
|
|
172
221
|
const selectedKey = getApiKey(state.config, selectedProviderKey)
|
|
173
222
|
const setupStatus = selectedKey ? chalk.green('API key detected β
') : chalk.yellow('API key missing β ')
|
|
174
|
-
|
|
223
|
+
// π Color the provider name in the setup instructions header
|
|
224
|
+
const selectedProviderRgb = PROVIDER_COLOR[selectedProviderKey] ?? [105, 190, 245]
|
|
225
|
+
const coloredProviderName = chalk.bold.rgb(...selectedProviderRgb)(selectedMeta.label || selectedSource.name || selectedProviderKey)
|
|
226
|
+
lines.push(` ${chalk.bold('Setup Instructions')} β ${coloredProviderName}`)
|
|
175
227
|
lines.push(chalk.dim(` 1) Create a ${selectedMeta.label || selectedSource.name} account: ${selectedMeta.signupUrl || 'signup link missing'}`))
|
|
176
228
|
lines.push(chalk.dim(` 2) ${selectedMeta.signupHint || 'Generate an API key and paste it with Enter on this row'}`))
|
|
177
229
|
lines.push(chalk.dim(` 3) Press ${chalk.yellow('T')} to test your key. Status: ${setupStatus}`))
|
|
@@ -193,7 +245,7 @@ export function createOverlayRenderers(state, deps) {
|
|
|
193
245
|
|
|
194
246
|
lines.push('')
|
|
195
247
|
lines.push(` ${chalk.bold('π Maintenance')}`)
|
|
196
|
-
lines.push(` ${chalk.dim(' ' + 'β'.repeat(
|
|
248
|
+
lines.push(` ${chalk.dim(' ' + 'β'.repeat(separatorWidth))}`)
|
|
197
249
|
lines.push('')
|
|
198
250
|
|
|
199
251
|
const updateCursor = state.settingsCursor === updateRowIdx
|
|
@@ -218,7 +270,7 @@ export function createOverlayRenderers(state, deps) {
|
|
|
218
270
|
|
|
219
271
|
lines.push('')
|
|
220
272
|
lines.push(` ${chalk.bold('π Proxy')}`)
|
|
221
|
-
lines.push(` ${chalk.dim(' ' + 'β'.repeat(
|
|
273
|
+
lines.push(` ${chalk.dim(' ' + 'β'.repeat(separatorWidth))}`)
|
|
222
274
|
lines.push('')
|
|
223
275
|
|
|
224
276
|
const proxyEnabledBullet = state.settingsCursor === proxyEnabledRowIdx ? chalk.bold.cyan(' β― ') : chalk.dim(' ')
|
|
@@ -251,7 +303,7 @@ export function createOverlayRenderers(state, deps) {
|
|
|
251
303
|
|
|
252
304
|
lines.push('')
|
|
253
305
|
lines.push(` ${chalk.bold('π Profiles')} ${chalk.dim(savedProfiles.length > 0 ? `(${savedProfiles.length} saved)` : '(none β press Shift+S in main view to save)')}`)
|
|
254
|
-
lines.push(` ${chalk.dim(' ' + 'β'.repeat(
|
|
306
|
+
lines.push(` ${chalk.dim(' ' + 'β'.repeat(separatorWidth))}`)
|
|
255
307
|
lines.push('')
|
|
256
308
|
|
|
257
309
|
if (savedProfiles.length === 0) {
|
|
@@ -287,6 +339,18 @@ export function createOverlayRenderers(state, deps) {
|
|
|
287
339
|
}
|
|
288
340
|
lines.push('')
|
|
289
341
|
|
|
342
|
+
// π Footer with credits
|
|
343
|
+
lines.push('')
|
|
344
|
+
lines.push(
|
|
345
|
+
chalk.dim(' ') +
|
|
346
|
+
chalk.rgb(255, 150, 200)('Made with π & β by ') +
|
|
347
|
+
chalk.cyanBright('\x1b]8;;https://github.com/vava-nessa\x1b\\vava-nessa\x1b]8;;\x1b\\') +
|
|
348
|
+
chalk.dim(' β’ β ') +
|
|
349
|
+
chalk.rgb(255, 200, 100)('\x1b]8;;https://buymeacoffee.com/vavanessadev\x1b\\Buy me a coffee\x1b]8;;\x1b\\') +
|
|
350
|
+
chalk.dim(' β’ ') +
|
|
351
|
+
'Esc to close'
|
|
352
|
+
)
|
|
353
|
+
|
|
290
354
|
// π Keep selected Settings row visible on small terminals by scrolling the overlay viewport.
|
|
291
355
|
const targetLine = cursorLineByRow[state.settingsCursor] ?? 0
|
|
292
356
|
state.settingsScrollOffset = keepOverlayTargetVisible(
|
|
@@ -298,7 +362,7 @@ export function createOverlayRenderers(state, deps) {
|
|
|
298
362
|
const { visible, offset } = sliceOverlayLines(lines, state.settingsScrollOffset, state.terminalRows)
|
|
299
363
|
state.settingsScrollOffset = offset
|
|
300
364
|
|
|
301
|
-
const tintedLines = tintOverlayLines(visible, SETTINGS_OVERLAY_BG)
|
|
365
|
+
const tintedLines = tintOverlayLines(visible, SETTINGS_OVERLAY_BG, state.terminalCols)
|
|
302
366
|
const cleared = tintedLines.map(l => l + EL)
|
|
303
367
|
return cleared.join('\n')
|
|
304
368
|
}
|
|
@@ -341,7 +405,11 @@ export function createOverlayRenderers(state, deps) {
|
|
|
341
405
|
: 'β'
|
|
342
406
|
|
|
343
407
|
lines.push('')
|
|
344
|
-
|
|
408
|
+
// π Branding header
|
|
409
|
+
lines.push(` ${chalk.cyanBright('π')} ${chalk.bold.cyanBright('free-coding-models')} ${chalk.dim(`v${LOCAL_VERSION}`)}`)
|
|
410
|
+
lines.push(` ${chalk.bold('π Install Endpoints')}`)
|
|
411
|
+
lines.push('')
|
|
412
|
+
lines.push(chalk.dim(' β install provider catalogs into supported coding tools'))
|
|
345
413
|
if (state.installEndpointsErrorMsg) {
|
|
346
414
|
lines.push(` ${chalk.yellow(state.installEndpointsErrorMsg)}`)
|
|
347
415
|
}
|
|
@@ -460,7 +528,7 @@ export function createOverlayRenderers(state, deps) {
|
|
|
460
528
|
const { visible, offset } = sliceOverlayLines(lines, state.installEndpointsScrollOffset, state.terminalRows)
|
|
461
529
|
state.installEndpointsScrollOffset = offset
|
|
462
530
|
|
|
463
|
-
const tintedLines = tintOverlayLines(visible, SETTINGS_OVERLAY_BG)
|
|
531
|
+
const tintedLines = tintOverlayLines(visible, SETTINGS_OVERLAY_BG, state.terminalCols)
|
|
464
532
|
const cleared = tintedLines.map((line) => line + EL)
|
|
465
533
|
return cleared.join('\n')
|
|
466
534
|
}
|
|
@@ -471,9 +539,12 @@ export function createOverlayRenderers(state, deps) {
|
|
|
471
539
|
function renderHelp() {
|
|
472
540
|
const EL = '\x1b[K'
|
|
473
541
|
const lines = []
|
|
542
|
+
|
|
543
|
+
// π Branding header
|
|
544
|
+
lines.push(` ${chalk.cyanBright('π')} ${chalk.bold.cyanBright('free-coding-models')} ${chalk.dim(`v${LOCAL_VERSION}`)}`)
|
|
545
|
+
lines.push(` ${chalk.bold('β Help & Keyboard Shortcuts')}`)
|
|
474
546
|
lines.push('')
|
|
475
|
-
lines.push(` ${chalk.
|
|
476
|
-
lines.push('')
|
|
547
|
+
lines.push(` ${chalk.dim('β ββ / PgUp / PgDn / Home / End scroll β’ K or Esc close')}`)
|
|
477
548
|
lines.push(` ${chalk.bold('Columns')}`)
|
|
478
549
|
lines.push('')
|
|
479
550
|
lines.push(` ${chalk.cyan('Rank')} SWE-bench rank (1 = best coding score) ${chalk.dim('Sort:')} ${chalk.yellow('R')}`)
|
|
@@ -580,7 +651,7 @@ export function createOverlayRenderers(state, deps) {
|
|
|
580
651
|
// π Help overlay can be longer than viewport, so keep a dedicated scroll offset.
|
|
581
652
|
const { visible, offset } = sliceOverlayLines(lines, state.helpScrollOffset, state.terminalRows)
|
|
582
653
|
state.helpScrollOffset = offset
|
|
583
|
-
const tintedLines = tintOverlayLines(visible, HELP_OVERLAY_BG)
|
|
654
|
+
const tintedLines = tintOverlayLines(visible, HELP_OVERLAY_BG, state.terminalCols)
|
|
584
655
|
const cleared = tintedLines.map(l => l + EL)
|
|
585
656
|
return cleared.join('\n')
|
|
586
657
|
}
|
|
@@ -592,14 +663,19 @@ export function createOverlayRenderers(state, deps) {
|
|
|
592
663
|
function renderLog() {
|
|
593
664
|
const EL = '\x1b[K'
|
|
594
665
|
const lines = []
|
|
666
|
+
|
|
667
|
+
// π Branding header
|
|
668
|
+
lines.push(` ${chalk.cyanBright('π')} ${chalk.bold.cyanBright('free-coding-models')} ${chalk.dim(`v${LOCAL_VERSION}`)}`)
|
|
669
|
+
lines.push(` ${chalk.bold('π Request Log')}`)
|
|
595
670
|
lines.push('')
|
|
596
|
-
lines.push(
|
|
671
|
+
lines.push(chalk.dim(' β recent requests β’ ββ scroll β’ A toggle all/500 β’ X or Esc close'))
|
|
597
672
|
lines.push(chalk.dim(' Works only when the multi-account proxy is enabled and requests go through it.'))
|
|
598
673
|
lines.push(chalk.dim(' Direct provider launches do not currently write into this log.'))
|
|
599
|
-
lines.push('')
|
|
600
674
|
|
|
601
675
|
// π Load recent log entries β bounded read, newest-first, malformed lines skipped.
|
|
602
|
-
|
|
676
|
+
// π Show up to 500 entries by default, or all if logShowAll is true.
|
|
677
|
+
const logLimit = state.logShowAll ? Number.MAX_SAFE_INTEGER : 500
|
|
678
|
+
const logRows = loadRecentLogs({ limit: logLimit })
|
|
603
679
|
const totalTokens = logRows.reduce((sum, row) => sum + (Number(row.tokens) || 0), 0)
|
|
604
680
|
|
|
605
681
|
if (logRows.length === 0) {
|
|
@@ -627,8 +703,15 @@ export function createOverlayRenderers(state, deps) {
|
|
|
627
703
|
const hStatus = chalk.dim('Status'.padEnd(W_STATUS))
|
|
628
704
|
const hTok = chalk.dim('Tokens Used'.padEnd(W_TOKENS))
|
|
629
705
|
const hLat = chalk.dim('Latency'.padEnd(W_LAT))
|
|
706
|
+
|
|
707
|
+
// π Show mode indicator (all vs limited)
|
|
708
|
+
const modeBadge = state.logShowAll
|
|
709
|
+
? chalk.yellow.bold('ALL')
|
|
710
|
+
: chalk.cyan.bold('500')
|
|
711
|
+
const countBadge = chalk.dim(`Showing ${logRows.length} entries`)
|
|
712
|
+
|
|
630
713
|
lines.push(` ${hTime} ${hProv} ${hModel} ${hRoute} ${hStatus} ${hTok} ${hLat}`)
|
|
631
|
-
lines.push(chalk.dim('
|
|
714
|
+
lines.push(` ${chalk.dim('β'.repeat(W_TIME + W_PROV + W_MODEL + W_ROUTE + W_STATUS + W_TOKENS + W_LAT + 12))} ${modeBadge} ${countBadge}`)
|
|
632
715
|
|
|
633
716
|
for (const row of logRows) {
|
|
634
717
|
// π Format time as HH:MM:SS (strip the date part for compactness)
|
|
@@ -645,15 +728,33 @@ export function createOverlayRenderers(state, deps) {
|
|
|
645
728
|
? `${requestedModelLabel} β ${row.model}`
|
|
646
729
|
: row.model
|
|
647
730
|
|
|
648
|
-
// π Color-code status
|
|
731
|
+
// π Color-code status with distinct colors for each error type
|
|
649
732
|
let statusCell
|
|
650
733
|
const sc = String(row.status)
|
|
651
734
|
if (sc === '200') {
|
|
652
735
|
statusCell = chalk.greenBright(sc.padEnd(W_STATUS))
|
|
736
|
+
} else if (sc === '404') {
|
|
737
|
+
statusCell = chalk.rgb(139, 0, 0).bold(sc.padEnd(W_STATUS)) // Dark red for 404
|
|
738
|
+
} else if (sc === '400') {
|
|
739
|
+
statusCell = chalk.hex('#8B008B').bold(sc.padEnd(W_STATUS)) // Dark magenta
|
|
740
|
+
} else if (sc === '401') {
|
|
741
|
+
statusCell = chalk.hex('#9932CC').bold(sc.padEnd(W_STATUS)) // Dark orchid
|
|
742
|
+
} else if (sc === '403') {
|
|
743
|
+
statusCell = chalk.hex('#BA55D3').bold(sc.padEnd(W_STATUS)) // Medium orchid
|
|
744
|
+
} else if (sc === '413') {
|
|
745
|
+
statusCell = chalk.hex('#FF6347').bold(sc.padEnd(W_STATUS)) // Tomato red
|
|
653
746
|
} else if (sc === '429') {
|
|
654
|
-
statusCell = chalk.
|
|
655
|
-
} else if (sc
|
|
656
|
-
statusCell = chalk.
|
|
747
|
+
statusCell = chalk.hex('#FFB90F').bold(sc.padEnd(W_STATUS)) // Dark orange
|
|
748
|
+
} else if (sc === '500') {
|
|
749
|
+
statusCell = chalk.hex('#DC143C').bold(sc.padEnd(W_STATUS)) // Crimson
|
|
750
|
+
} else if (sc === '502') {
|
|
751
|
+
statusCell = chalk.hex('#C71585').bold(sc.padEnd(W_STATUS)) // Medium violet red
|
|
752
|
+
} else if (sc === '503') {
|
|
753
|
+
statusCell = chalk.hex('#9370DB').bold(sc.padEnd(W_STATUS)) // Medium purple
|
|
754
|
+
} else if (sc.startsWith('5')) {
|
|
755
|
+
statusCell = chalk.magenta(sc.padEnd(W_STATUS)) // Other 5xx - magenta
|
|
756
|
+
} else if (sc === '0') {
|
|
757
|
+
statusCell = chalk.hex('#696969')(sc.padEnd(W_STATUS)) // Dim gray for timeout
|
|
657
758
|
} else {
|
|
658
759
|
statusCell = chalk.dim(sc.padEnd(W_STATUS))
|
|
659
760
|
}
|
|
@@ -664,18 +765,47 @@ export function createOverlayRenderers(state, deps) {
|
|
|
664
765
|
? `SWITCHED β» ${row.switchReason || 'fallback'}`
|
|
665
766
|
: 'direct'
|
|
666
767
|
|
|
768
|
+
// π Detect failed requests with zero tokens - these get special red highlighting
|
|
769
|
+
const isFailedWithZeroTokens = row.status !== '200' && (!row.tokens || Number(row.tokens) === 0)
|
|
770
|
+
|
|
667
771
|
const timeCell = chalk.dim(timeStr.slice(0, W_TIME).padEnd(W_TIME))
|
|
668
|
-
|
|
669
|
-
const
|
|
670
|
-
|
|
671
|
-
|
|
772
|
+
// π Color provider the same way as in the main table (row.provider is already the providerKey, e.g. "nvidia")
|
|
773
|
+
const providerRgb = PROVIDER_COLOR[row.provider] ?? [105, 190, 245]
|
|
774
|
+
const provCell = chalk.bold.rgb(...providerRgb)(row.provider.slice(0, W_PROV).padEnd(W_PROV))
|
|
775
|
+
|
|
776
|
+
// π Color model based on status - red for failed requests with zero tokens
|
|
777
|
+
let modelCell
|
|
778
|
+
if (isFailedWithZeroTokens) {
|
|
779
|
+
modelCell = chalk.red.bold(displayModel.slice(0, W_MODEL).padEnd(W_MODEL))
|
|
780
|
+
} else {
|
|
781
|
+
const modelColorFn = getModelColorByStatus(row.status)
|
|
782
|
+
modelCell = row.switched
|
|
783
|
+
? chalk.bold.rgb(255, 210, 90)(displayModel.slice(0, W_MODEL).padEnd(W_MODEL))
|
|
784
|
+
: modelColorFn(displayModel.slice(0, W_MODEL).padEnd(W_MODEL))
|
|
785
|
+
}
|
|
786
|
+
|
|
672
787
|
const routeCell = row.switched
|
|
673
788
|
? chalk.bgRgb(120, 25, 25).yellow.bold(` ${routeLabel.slice(0, W_ROUTE - 2).padEnd(W_ROUTE - 2)} `)
|
|
674
789
|
: chalk.dim(routeLabel.padEnd(W_ROUTE))
|
|
675
|
-
const tokCell = chalk.dim(tokStr.padEnd(W_TOKENS))
|
|
676
|
-
const latCell = chalk.dim(latStr.padEnd(W_LAT))
|
|
677
790
|
|
|
678
|
-
|
|
791
|
+
// π Colorize tokens - red cross emoji for failed requests with zero tokens
|
|
792
|
+
let tokCell
|
|
793
|
+
if (isFailedWithZeroTokens) {
|
|
794
|
+
tokCell = chalk.red.bold('β'.padEnd(W_TOKENS))
|
|
795
|
+
} else {
|
|
796
|
+
tokCell = colorizeTokens(row.tokens, tokStr.padEnd(W_TOKENS))
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// π Colorize latency with gradient (green β orange β yellow β red)
|
|
800
|
+
const latCell = colorizeLatency(row.latency, latStr.padEnd(W_LAT))
|
|
801
|
+
|
|
802
|
+
// π Build the row line - add dark red background for failed requests with zero tokens
|
|
803
|
+
const rowText = ` ${timeCell} ${provCell} ${modelCell} ${routeCell} ${statusCell} ${tokCell} ${latCell}`
|
|
804
|
+
if (isFailedWithZeroTokens) {
|
|
805
|
+
lines.push(chalk.bgRgb(40, 0, 0)(rowText))
|
|
806
|
+
} else {
|
|
807
|
+
lines.push(rowText)
|
|
808
|
+
}
|
|
679
809
|
}
|
|
680
810
|
}
|
|
681
811
|
|
|
@@ -685,7 +815,7 @@ export function createOverlayRenderers(state, deps) {
|
|
|
685
815
|
|
|
686
816
|
const { visible, offset } = sliceOverlayLines(lines, state.logScrollOffset, state.terminalRows)
|
|
687
817
|
state.logScrollOffset = offset
|
|
688
|
-
const tintedLines = tintOverlayLines(visible, LOG_OVERLAY_BG)
|
|
818
|
+
const tintedLines = tintOverlayLines(visible, LOG_OVERLAY_BG, state.terminalCols)
|
|
689
819
|
const cleared = tintedLines.map(l => l + EL)
|
|
690
820
|
return cleared.join('\n')
|
|
691
821
|
}
|
|
@@ -698,8 +828,12 @@ export function createOverlayRenderers(state, deps) {
|
|
|
698
828
|
const EL = '\x1b[K'
|
|
699
829
|
const lines = []
|
|
700
830
|
|
|
831
|
+
// π Branding header
|
|
832
|
+
lines.push('')
|
|
833
|
+
lines.push(` ${chalk.cyanBright('π')} ${chalk.bold.cyanBright('free-coding-models')} ${chalk.dim(`v${LOCAL_VERSION}`)}`)
|
|
834
|
+
lines.push(` ${chalk.bold('π― Smart Recommend')}`)
|
|
701
835
|
lines.push('')
|
|
702
|
-
lines.push(
|
|
836
|
+
lines.push(chalk.dim(' β find the best model for your task'))
|
|
703
837
|
lines.push('')
|
|
704
838
|
|
|
705
839
|
if (state.recommendPhase === 'questionnaire') {
|
|
@@ -825,7 +959,7 @@ export function createOverlayRenderers(state, deps) {
|
|
|
825
959
|
lines.push('')
|
|
826
960
|
const { visible, offset } = sliceOverlayLines(lines, state.recommendScrollOffset, state.terminalRows)
|
|
827
961
|
state.recommendScrollOffset = offset
|
|
828
|
-
const tintedLines = tintOverlayLines(visible, RECOMMEND_OVERLAY_BG)
|
|
962
|
+
const tintedLines = tintOverlayLines(visible, RECOMMEND_OVERLAY_BG, state.terminalCols)
|
|
829
963
|
const cleared2 = tintedLines.map(l => l + EL)
|
|
830
964
|
return cleared2.join('\n')
|
|
831
965
|
}
|
|
@@ -898,8 +1032,8 @@ export function createOverlayRenderers(state, deps) {
|
|
|
898
1032
|
const EL = '\x1b[K'
|
|
899
1033
|
const lines = []
|
|
900
1034
|
|
|
901
|
-
// π Calculate available space for multi-line input
|
|
902
|
-
const maxInputWidth =
|
|
1035
|
+
// π Calculate available space for multi-line input (dynamic based on terminal width)
|
|
1036
|
+
const maxInputWidth = state.terminalCols - 8 // 8 = padding (4 spaces each side)
|
|
903
1037
|
const maxInputLines = 10 // Show up to 10 lines of input
|
|
904
1038
|
|
|
905
1039
|
// π Split buffer into lines for display (with wrapping)
|
|
@@ -923,10 +1057,13 @@ export function createOverlayRenderers(state, deps) {
|
|
|
923
1057
|
|
|
924
1058
|
const inputLines = wrapText(state.featureRequestBuffer, maxInputWidth)
|
|
925
1059
|
const displayLines = inputLines.slice(0, maxInputLines)
|
|
926
|
-
|
|
927
|
-
// π
|
|
1060
|
+
|
|
1061
|
+
// π Branding header
|
|
928
1062
|
lines.push('')
|
|
929
|
-
lines.push(` ${chalk.bold.
|
|
1063
|
+
lines.push(` ${chalk.cyanBright('π')} ${chalk.bold.cyanBright('free-coding-models')} ${chalk.dim(`v${LOCAL_VERSION}`)}`)
|
|
1064
|
+
lines.push(` ${chalk.bold.rgb(57, 255, 20)('π Feature Request')}`)
|
|
1065
|
+
lines.push('')
|
|
1066
|
+
lines.push(chalk.dim(' β send anonymous feedback to the project team'))
|
|
930
1067
|
lines.push('')
|
|
931
1068
|
|
|
932
1069
|
// π Status messages (if any)
|
|
@@ -989,8 +1126,8 @@ export function createOverlayRenderers(state, deps) {
|
|
|
989
1126
|
lines.push(chalk.dim(' Enter Send β’ Esc Cancel β’ Backspace Delete'))
|
|
990
1127
|
|
|
991
1128
|
// π Apply overlay tint and return
|
|
992
|
-
const FEATURE_REQUEST_OVERLAY_BG = chalk.bgRgb(
|
|
993
|
-
const tintedLines = tintOverlayLines(lines, FEATURE_REQUEST_OVERLAY_BG)
|
|
1129
|
+
const FEATURE_REQUEST_OVERLAY_BG = chalk.bgRgb(0, 0, 0) // Dark blue-ish background (RGB: 26, 26, 46)
|
|
1130
|
+
const tintedLines = tintOverlayLines(lines, FEATURE_REQUEST_OVERLAY_BG, state.terminalCols)
|
|
994
1131
|
const cleared = tintedLines.map(l => l + EL)
|
|
995
1132
|
return cleared.join('\n')
|
|
996
1133
|
}
|
|
@@ -1002,8 +1139,8 @@ export function createOverlayRenderers(state, deps) {
|
|
|
1002
1139
|
const EL = '\x1b[K'
|
|
1003
1140
|
const lines = []
|
|
1004
1141
|
|
|
1005
|
-
// π Calculate available space for multi-line input
|
|
1006
|
-
const maxInputWidth =
|
|
1142
|
+
// π Calculate available space for multi-line input (dynamic based on terminal width)
|
|
1143
|
+
const maxInputWidth = state.terminalCols - 8 // 8 = padding (4 spaces each side)
|
|
1007
1144
|
const maxInputLines = 10 // Show up to 10 lines of input
|
|
1008
1145
|
|
|
1009
1146
|
// π Split buffer into lines for display (with wrapping)
|
|
@@ -1027,10 +1164,13 @@ export function createOverlayRenderers(state, deps) {
|
|
|
1027
1164
|
|
|
1028
1165
|
const inputLines = wrapText(state.bugReportBuffer, maxInputWidth)
|
|
1029
1166
|
const displayLines = inputLines.slice(0, maxInputLines)
|
|
1030
|
-
|
|
1031
|
-
// π
|
|
1167
|
+
|
|
1168
|
+
// π Branding header
|
|
1169
|
+
lines.push('')
|
|
1170
|
+
lines.push(` ${chalk.cyanBright('π')} ${chalk.bold.cyanBright('free-coding-models')} ${chalk.dim(`v${LOCAL_VERSION}`)}`)
|
|
1171
|
+
lines.push(` ${chalk.bold.rgb(255, 87, 51)('π Bug Report')}`)
|
|
1032
1172
|
lines.push('')
|
|
1033
|
-
lines.push(
|
|
1173
|
+
lines.push(chalk.dim(' β send anonymous bug reports to the project team'))
|
|
1034
1174
|
lines.push('')
|
|
1035
1175
|
|
|
1036
1176
|
// π Status messages (if any)
|
|
@@ -1093,8 +1233,8 @@ export function createOverlayRenderers(state, deps) {
|
|
|
1093
1233
|
lines.push(chalk.dim(' Enter Send β’ Esc Cancel β’ Backspace Delete'))
|
|
1094
1234
|
|
|
1095
1235
|
// π Apply overlay tint and return
|
|
1096
|
-
const BUG_REPORT_OVERLAY_BG = chalk.bgRgb(
|
|
1097
|
-
const tintedLines = tintOverlayLines(lines, BUG_REPORT_OVERLAY_BG)
|
|
1236
|
+
const BUG_REPORT_OVERLAY_BG = chalk.bgRgb(0, 0, 0) // Dark red-ish background (RGB: 46, 20, 20)
|
|
1237
|
+
const tintedLines = tintOverlayLines(lines, BUG_REPORT_OVERLAY_BG, state.terminalCols)
|
|
1098
1238
|
const cleared = tintedLines.map(l => l + EL)
|
|
1099
1239
|
return cleared.join('\n')
|
|
1100
1240
|
}
|
package/src/provider-metadata.js
CHANGED
|
@@ -80,21 +80,21 @@ export const PROVIDER_METADATA = {
|
|
|
80
80
|
color: chalk.rgb(178, 235, 190),
|
|
81
81
|
signupUrl: 'https://build.nvidia.com',
|
|
82
82
|
signupHint: 'Profile β API Keys β Generate',
|
|
83
|
-
rateLimits: 'Free tier (
|
|
83
|
+
rateLimits: 'Free tier: 40 requests/min (no credit card needed)',
|
|
84
84
|
},
|
|
85
85
|
groq: {
|
|
86
86
|
label: 'Groq',
|
|
87
87
|
color: chalk.rgb(255, 204, 188),
|
|
88
88
|
signupUrl: 'https://console.groq.com/keys',
|
|
89
89
|
signupHint: 'API Keys β Create API Key',
|
|
90
|
-
rateLimits: 'Free
|
|
90
|
+
rateLimits: 'Free tier: 30β50 RPM per model (varies by model)',
|
|
91
91
|
},
|
|
92
92
|
cerebras: {
|
|
93
93
|
label: 'Cerebras',
|
|
94
94
|
color: chalk.rgb(179, 229, 252),
|
|
95
95
|
signupUrl: 'https://cloud.cerebras.ai',
|
|
96
96
|
signupHint: 'API Keys β Create',
|
|
97
|
-
rateLimits: 'Free
|
|
97
|
+
rateLimits: 'Free tier: generous (developer tier 10Γ higher limits)',
|
|
98
98
|
},
|
|
99
99
|
sambanova: {
|
|
100
100
|
label: 'SambaNova',
|
|
@@ -108,7 +108,8 @@ export const PROVIDER_METADATA = {
|
|
|
108
108
|
color: chalk.rgb(225, 190, 231),
|
|
109
109
|
signupUrl: 'https://openrouter.ai/keys',
|
|
110
110
|
signupHint: 'API Keys β Create',
|
|
111
|
-
rateLimits: '50
|
|
111
|
+
rateLimits: 'Free on :free: 50/day <$10, 1000/day β₯$10 (20 req/min)',
|
|
112
|
+
detailedLimits: 'No credits (or <$10) β 50 requests/day (20 req/min)\nβ₯ $10 in credits β 1000 requests/day (20 req/min)\nβ’ Free models (:free) never consume credits\nβ’ Failed requests count toward quota\nβ’ Quota resets daily at midnight UTC\nβ’ Free-tier models may be rate-limited during peak hours',
|
|
112
113
|
},
|
|
113
114
|
huggingface: {
|
|
114
115
|
label: 'Hugging Face Inference',
|
|
@@ -124,21 +125,21 @@ export const PROVIDER_METADATA = {
|
|
|
124
125
|
color: chalk.rgb(187, 222, 251),
|
|
125
126
|
signupUrl: 'https://replicate.com/account/api-tokens',
|
|
126
127
|
signupHint: 'Account β API Tokens',
|
|
127
|
-
rateLimits: '
|
|
128
|
+
rateLimits: 'Free tier: 6β―req/min (no payment) β up to 3,000β―RPM (API) / 600β―RPM (predictions) with payment',
|
|
128
129
|
},
|
|
129
130
|
deepinfra: {
|
|
130
131
|
label: 'DeepInfra',
|
|
131
132
|
color: chalk.rgb(178, 223, 219),
|
|
132
133
|
signupUrl: 'https://deepinfra.com/login',
|
|
133
134
|
signupHint: 'Login β API keys',
|
|
134
|
-
rateLimits: 'Free
|
|
135
|
+
rateLimits: 'Free tier: 200 concurrent requests (default)',
|
|
135
136
|
},
|
|
136
137
|
fireworks: {
|
|
137
138
|
label: 'Fireworks AI',
|
|
138
139
|
color: chalk.rgb(255, 205, 210),
|
|
139
140
|
signupUrl: 'https://fireworks.ai',
|
|
140
141
|
signupHint: 'Create account β Generate API key',
|
|
141
|
-
rateLimits: '$1
|
|
142
|
+
rateLimits: 'Free tier: $1 credits β 10β―req/min without payment method (full limits with payment)',
|
|
142
143
|
},
|
|
143
144
|
codestral: {
|
|
144
145
|
label: 'Mistral Codestral',
|
package/src/render-helpers.js
CHANGED
|
@@ -106,13 +106,15 @@ export function padEndDisplay(str, width) {
|
|
|
106
106
|
return str + ' '.repeat(need)
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
// π tintOverlayLines: Tint overlay lines with a
|
|
110
|
-
// π Applies bgColor to each line and pads to
|
|
111
|
-
|
|
109
|
+
// π tintOverlayLines: Tint overlay lines with a terminal width so the background is clearly visible.
|
|
110
|
+
// π Applies bgColor to each line and pads to terminalCols for full-width panel look.
|
|
111
|
+
// π If terminalCols is not provided, falls back to OVERLAY_PANEL_WIDTH for compatibility.
|
|
112
|
+
export function tintOverlayLines(lines, bgColor, terminalCols = null) {
|
|
113
|
+
const panelWidth = terminalCols || OVERLAY_PANEL_WIDTH
|
|
112
114
|
return lines.map((line) => {
|
|
113
115
|
const text = String(line)
|
|
114
116
|
const visibleWidth = stripAnsi(text).length
|
|
115
|
-
const padding = ' '.repeat(Math.max(0,
|
|
117
|
+
const padding = ' '.repeat(Math.max(0, panelWidth - visibleWidth))
|
|
116
118
|
return bgColor(text + padding)
|
|
117
119
|
})
|
|
118
120
|
}
|
package/src/render-table.js
CHANGED
|
@@ -59,7 +59,8 @@ const { version: LOCAL_VERSION } = require('../package.json')
|
|
|
59
59
|
|
|
60
60
|
// π Provider column palette: soft pastel rainbow so each provider stays easy
|
|
61
61
|
// π to spot without turning the table into a harsh neon wall.
|
|
62
|
-
|
|
62
|
+
// π Exported for use in overlays (settings screen) and logs.
|
|
63
|
+
export const PROVIDER_COLOR = {
|
|
63
64
|
nvidia: [178, 235, 190],
|
|
64
65
|
groq: [255, 204, 188],
|
|
65
66
|
cerebras: [179, 229, 252],
|
|
@@ -170,11 +171,6 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
170
171
|
}
|
|
171
172
|
}
|
|
172
173
|
|
|
173
|
-
let configuredBadge = ''
|
|
174
|
-
if (hideUnconfiguredModels) {
|
|
175
|
-
configuredBadge = ` ${activeHeaderBadge('CONFIGURED ONLY')}`
|
|
176
|
-
}
|
|
177
|
-
|
|
178
174
|
// π Profile badge β shown when a named profile is active (Shift+P to cycle, Shift+S to save)
|
|
179
175
|
let profileBadge = ''
|
|
180
176
|
if (activeProfile) {
|
|
@@ -227,7 +223,8 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
227
223
|
const sorted = sortResultsWithPinnedFavorites(visibleResults, sortColumn, sortDirection)
|
|
228
224
|
|
|
229
225
|
const lines = [
|
|
230
|
-
` ${chalk.
|
|
226
|
+
` ${chalk.cyanBright.bold(`π free-coding-models v${LOCAL_VERSION}`)}${modeBadge}${pingControlBadge}${tierBadge}${originBadge}${profileBadge}${chalk.reset('')} ` +
|
|
227
|
+
chalk.dim('π¦ ') + chalk.cyanBright.bold(`${completedPings}/${totalVisible}`) + chalk.dim(' ') +
|
|
231
228
|
chalk.greenBright(`β
${up}`) + chalk.dim(' up ') +
|
|
232
229
|
chalk.yellow(`β³ ${timeout}`) + chalk.dim(' timeout ') +
|
|
233
230
|
chalk.red(`β ${down}`) + chalk.dim(' down ') +
|
|
@@ -660,6 +657,9 @@ export function renderTable(results, pendingPings, frame, cursor = null, sortCol
|
|
|
660
657
|
'π€ ' +
|
|
661
658
|
chalk.rgb(255, 165, 0)('\x1b]8;;https://github.com/vava-nessa/free-coding-models/graphs/contributors\x1b\\Contributors\x1b]8;;\x1b\\') +
|
|
662
659
|
chalk.dim(' β’ ') +
|
|
660
|
+
'β ' +
|
|
661
|
+
chalk.rgb(255, 200, 100)('\x1b]8;;https://buymeacoffee.com/vavanessadev\x1b\\Buy me a coffee\x1b]8;;\x1b\\') +
|
|
662
|
+
chalk.dim(' β’ ') +
|
|
663
663
|
'π¬ ' +
|
|
664
664
|
chalk.rgb(200, 150, 255)('\x1b]8;;https://discord.gg/5MbTnDC3Md\x1b\\Discord\x1b]8;;\x1b\\') +
|
|
665
665
|
chalk.dim(' β ') +
|