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/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
- const wrapPlainText = (text, width = 104) => {
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 > width && current) {
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
- lines.push('')
106
- lines.push(` ${chalk.bold('βš™ Settings')} ${chalk.dim('β€” free-coding-models v' + LOCAL_VERSION)}`)
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
- lines.push('')
154
+
111
155
  lines.push(` ${chalk.bold('🧩 Providers')}`)
112
- lines.push(` ${chalk.dim(' ' + '─'.repeat(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
- const rateSummary = chalk.dim((meta.rateLimits || 'No limit info').slice(0, 36))
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
- const providerName = chalk.bold((meta.label || src.name || pk).slice(0, 22).padEnd(22))
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
- lines.push(` ${chalk.bold('Setup Instructions')} β€” ${selectedMeta.label || selectedSource.name || selectedProviderKey}`)
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(112))}`)
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(112))}`)
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(112))}`)
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
- lines.push(` ${chalk.bold('πŸ”Œ Install Endpoints')} ${chalk.dim('β€” install provider catalogs into supported coding tools')}`)
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.bold('❓ Keyboard Shortcuts')} ${chalk.dim('β€” ↑↓ / PgUp / PgDn / Home / End scroll β€’ K or Esc close')}`)
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(` ${chalk.bold('πŸ“‹ Request Log')} ${chalk.dim('β€” recent requests β€’ ↑↓ scroll β€’ X or Esc close')}`)
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
- const logRows = loadRecentLogs({ limit: 200 })
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(' ' + '─'.repeat(W_TIME + W_PROV + W_MODEL + W_ROUTE + W_STATUS + W_TOKENS + W_LAT + 12)))
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.yellow(sc.padEnd(W_STATUS))
655
- } else if (sc.startsWith('5') || sc === 'error') {
656
- statusCell = chalk.red(sc.padEnd(W_STATUS))
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
- const provCell = chalk.cyan(row.provider.slice(0, W_PROV).padEnd(W_PROV))
669
- const modelCell = row.switched
670
- ? chalk.bold.rgb(255, 210, 90)(displayModel.slice(0, W_MODEL).padEnd(W_MODEL))
671
- : chalk.white(displayModel.slice(0, W_MODEL).padEnd(W_MODEL))
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
- lines.push(` ${timeCell} ${provCell} ${modelCell} ${routeCell} ${statusCell} ${tokCell} ${latCell}`)
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(` ${chalk.bold('🎯 Smart Recommend')} ${chalk.dim('β€” find the best model for your task')}`)
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 = OVERLAY_PANEL_WIDTH - 8 // 8 = padding (4 spaces each side)
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
- // πŸ“– Header
1060
+
1061
+ // πŸ“– Branding header
928
1062
  lines.push('')
929
- lines.push(` ${chalk.bold.rgb(57, 255, 20)('πŸ“ Feature Request')} ${chalk.dim('β€” send anonymous feedback to the project team')}`)
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(26, 26, 46) // Dark blue-ish background (RGB: 26, 26, 46)
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 = OVERLAY_PANEL_WIDTH - 8 // 8 = padding (4 spaces each side)
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
- // πŸ“– Header
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(` ${chalk.bold.rgb(255, 87, 51)('πŸ› Bug Report')} ${chalk.dim('β€” send anonymous bug reports to the project team')}`)
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(46, 20, 20) // Dark red-ish background (RGB: 46, 20, 20)
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
  }
@@ -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 (provider quota by model)',
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 dev tier (provider quota)',
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 dev tier (provider quota)',
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 req/day, 20/min (:free shared quota)',
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: 'Developer free quota',
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 dev tier (low-latency quota)',
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 free credits (new dev accounts)',
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',
@@ -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 fixed dark panel width so the background is clearly visible.
110
- // πŸ“– Applies bgColor to each line and pads to OVERLAY_PANEL_WIDTH for consistent panel look.
111
- export function tintOverlayLines(lines, bgColor) {
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, OVERLAY_PANEL_WIDTH - visibleWidth))
117
+ const padding = ' '.repeat(Math.max(0, panelWidth - visibleWidth))
116
118
  return bgColor(text + padding)
117
119
  })
118
120
  }
@@ -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
- const PROVIDER_COLOR = {
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.greenBright.bold(`βœ… Free-Coding-Models v${LOCAL_VERSION}`)}${modeBadge}${pingControlBadge}${tierBadge}${originBadge}${configuredBadge}${profileBadge}${chalk.reset('')} ` +
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(' β†’ ') +