free-coding-models 0.1.73 β†’ 0.1.76

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/README.md CHANGED
@@ -78,6 +78,7 @@
78
78
  - **πŸ”€ Startup mode menu** β€” Choose between OpenCode and OpenClaw before the TUI launches
79
79
  - **πŸ’» OpenCode integration** β€” Auto-detects NIM setup, sets model as default, launches OpenCode
80
80
  - **🦞 OpenClaw integration** β€” Sets selected model as default provider in `~/.openclaw/openclaw.json`
81
+ - **πŸ“ Feature Request (J key)** β€” Send anonymous feedback directly to the project team via a full-screen overlay with multi-line input (includes anonymous OS/terminal metadata in message footer only)
81
82
  - **🎨 Clean output** β€” Zero scrollback pollution, interface stays open until Ctrl+C
82
83
  - **πŸ“Ά Status indicators** β€” UP βœ… Β· No Key πŸ”‘ Β· Timeout ⏳ Β· Overloaded πŸ”₯ Β· Not Found 🚫
83
84
  - **πŸ” Keyless latency** β€” Models are pinged even without an API key β€” a `πŸ”‘ NO KEY` status confirms the server is reachable with real latency shown, so you can compare providers before committing to a key
@@ -123,6 +123,54 @@ const TELEMETRY_CONSENT_ASCII = [
123
123
  const POSTHOG_PROJECT_KEY_DEFAULT = 'phc_5P1n8HaLof6nHM0tKJYt4bV5pj2XPb272fLVigwf1YQ'
124
124
  const POSTHOG_HOST_DEFAULT = 'https://eu.i.posthog.com'
125
125
 
126
+ // πŸ“– Discord feature request webhook configuration (anonymous feedback system)
127
+ const DISCORD_WEBHOOK_URL = 'https://discord.com/api/webhooks/1476709155992764427/hmnHNtpducvi5LClhv8DynENjUmmg9q8HI1Bx1lNix56UHqrqZf55rW95LGvNJ2W4j7D'
128
+ const DISCORD_BOT_NAME = 'TUI - Feature Requests'
129
+ const DISCORD_EMBED_COLOR = 0x39FF14 // Vert fluo (RGB: 57, 255, 20)
130
+
131
+ // πŸ“– sendFeatureRequest: Send anonymous feature request to Discord via webhook
132
+ // πŸ“– Called when user presses J key, types message, and presses Enter
133
+ // πŸ“– Returns success/error status for UI feedback
134
+ async function sendFeatureRequest(message) {
135
+ try {
136
+ // πŸ“– Collect anonymous telemetry for context (no personal data)
137
+ const system = getTelemetrySystem()
138
+ const terminal = getTelemetryTerminal()
139
+ const nodeVersion = process.version
140
+ const arch = process.arch
141
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || 'Unknown'
142
+
143
+ // πŸ“– Build Discord embed with rich metadata in footer (compact format)
144
+ const embed = {
145
+ description: message,
146
+ color: DISCORD_EMBED_COLOR,
147
+ timestamp: new Date().toISOString(),
148
+ footer: {
149
+ text: `v${LOCAL_VERSION} β€’ ${system} β€’ ${terminal} β€’ ${nodeVersion} β€’ ${arch} β€’ ${timezone}`
150
+ }
151
+ }
152
+
153
+ const response = await fetch(DISCORD_WEBHOOK_URL, {
154
+ method: 'POST',
155
+ headers: { 'content-type': 'application/json' },
156
+ body: JSON.stringify({
157
+ username: DISCORD_BOT_NAME,
158
+ embeds: [embed]
159
+ }),
160
+ signal: AbortSignal.timeout(10000) // πŸ“– 10s timeout for webhook
161
+ })
162
+
163
+ if (!response.ok) {
164
+ throw new Error(`HTTP ${response.status}`)
165
+ }
166
+
167
+ return { success: true, error: null }
168
+ } catch (error) {
169
+ const message = error instanceof Error ? error.message : 'Unknown error'
170
+ return { success: false, error: message }
171
+ }
172
+ }
173
+
126
174
  // πŸ“– parseTelemetryEnv: Convert env var strings into booleans.
127
175
  // πŸ“– Returns true/false when value is recognized, otherwise null.
128
176
  function parseTelemetryEnv(value) {
@@ -856,7 +904,7 @@ function sliceOverlayLines(lines, offset, terminalRows) {
856
904
  // πŸ“– Keep these constants in sync with renderTable() fixed shell lines.
857
905
  // πŸ“– If this drifts, model rows overflow and can push the title row out of view.
858
906
  const TABLE_HEADER_LINES = 4 // πŸ“– title, spacer, column headers, separator
859
- const TABLE_FOOTER_LINES = 7 // πŸ“– spacer, hints line 1, hints line 2, spacer, credit+contributors, discord, spacer
907
+ const TABLE_FOOTER_LINES = 5 // πŸ“– spacer, hints line 1, hints line 2, spacer, credit+links
860
908
  const TABLE_FIXED_LINES = TABLE_HEADER_LINES + TABLE_FOOTER_LINES
861
909
 
862
910
  // πŸ“– Computes the visible slice of model rows that fits in the terminal.
@@ -1274,12 +1322,12 @@ function renderTable(results, pendingPings, frame, cursor = null, sortColumn = '
1274
1322
  lines.push(chalk.dim(` ... ${sorted.length - vp.endIdx} more below ...`))
1275
1323
  }
1276
1324
 
1277
- // πŸ“– Profile save inline prompt β€” shown when Shift+S is pressed, replaces spacer line
1278
- if (profileSaveMode) {
1279
- lines.push(chalk.bgRgb(40, 20, 60)(` πŸ“‹ Save profile as: ${chalk.cyanBright(profileSaveBuffer + '▏')} ${chalk.dim('Enter save β€’ Esc cancel')}`))
1280
- } else {
1281
- lines.push('')
1282
- }
1325
+ // πŸ“– Profile save inline prompt β€” shown when Shift+S is pressed, replaces spacer line
1326
+ if (profileSaveMode) {
1327
+ lines.push(chalk.bgRgb(40, 20, 60)(` πŸ“‹ Save profile as: ${chalk.cyanBright(profileSaveBuffer + '▏')} ${chalk.dim('Enter save β€’ Esc cancel')}`))
1328
+ } else {
1329
+ lines.push('')
1330
+ }
1283
1331
  const intervalSec = Math.round(pingInterval / 1000)
1284
1332
 
1285
1333
  // πŸ“– Footer hints adapt based on active mode
@@ -1290,8 +1338,8 @@ function renderTable(results, pendingPings, frame, cursor = null, sortColumn = '
1290
1338
  : chalk.rgb(0, 200, 255)('Enterβ†’OpenCode')
1291
1339
  // πŸ“– Line 1: core navigation + sorting shortcuts
1292
1340
  lines.push(chalk.dim(` ↑↓ Navigate β€’ `) + actionHint + chalk.dim(` β€’ `) + chalk.yellow('F') + chalk.dim(` Favorite β€’ R/Y/O/M/L/A/S/C/H/V/B/U Sort β€’ `) + chalk.yellow('T') + chalk.dim(` Tier β€’ `) + chalk.yellow('N') + chalk.dim(` Origin β€’ W↓/X↑ (${intervalSec}s) β€’ `) + chalk.rgb(255, 100, 50).bold('Z') + chalk.dim(` Mode β€’ `) + chalk.yellow('P') + chalk.dim(` Settings β€’ `) + chalk.rgb(0, 255, 80).bold('K') + chalk.dim(` Help`))
1293
- // πŸ“– Line 2: profiles, recommend, and extended hints β€” gives visibility to less-obvious features
1294
- lines.push(chalk.dim(` `) + chalk.rgb(200, 150, 255).bold('⇧P') + chalk.dim(` Cycle profile β€’ `) + chalk.rgb(200, 150, 255).bold('⇧S') + chalk.dim(` Save profile β€’ `) + chalk.rgb(0, 200, 180).bold('Q') + chalk.dim(` Smart Recommend β€’ `) + chalk.yellow('E') + chalk.dim(`/`) + chalk.yellow('D') + chalk.dim(` Tier ↑↓ β€’ `) + chalk.yellow('Esc') + chalk.dim(` Close overlay β€’ Ctrl+C Exit`))
1341
+ // πŸ“– Line 2: profiles, recommend, feature request, and extended hints β€” gives visibility to less-obvious features
1342
+ lines.push(chalk.dim(` `) + chalk.rgb(200, 150, 255).bold('⇧P') + chalk.dim(` Cycle profile β€’ `) + chalk.rgb(200, 150, 255).bold('⇧S') + chalk.dim(` Save profile β€’ `) + chalk.rgb(0, 200, 180).bold('Q') + chalk.dim(` Smart Recommend β€’ `) + chalk.rgb(57, 255, 20).bold('J') + chalk.dim(` Request feature β€’ `) + chalk.yellow('E') + chalk.dim(`/`) + chalk.yellow('D') + chalk.dim(` Tier ↑↓ β€’ `) + chalk.yellow('Esc') + chalk.dim(` Close overlay β€’ Ctrl+C Exit`))
1295
1343
  lines.push('')
1296
1344
  lines.push(
1297
1345
  chalk.rgb(255, 150, 200)(' Made with πŸ’– & β˜• by \x1b]8;;https://github.com/vava-nessa\x1b\\vava-nessa\x1b]8;;\x1b\\') +
@@ -1309,9 +1357,7 @@ function renderTable(results, pendingPings, frame, cursor = null, sortColumn = '
1309
1357
  chalk.dim(' β€’ ') +
1310
1358
  chalk.dim('Ctrl+C Exit')
1311
1359
  )
1312
- lines.push('') // Empty line for terminal spacing
1313
- lines.push(chalk.dim(' (vava-nessa β€’ erwinh22 β€’ whit3rabbit β€’ skylaweber)'))
1314
- lines.push('')
1360
+
1315
1361
  // πŸ“– Append \x1b[K (erase to EOL) to each line so leftover chars from previous
1316
1362
  // πŸ“– frames are cleared. Then pad with blank cleared lines to fill the terminal,
1317
1363
  // πŸ“– preventing stale content from lingering at the bottom after resize.
@@ -2819,6 +2865,11 @@ async function main() {
2819
2865
  activeProfile: getActiveProfileName(config), // πŸ“– Currently loaded profile name (or null)
2820
2866
  profileSaveMode: false, // πŸ“– Whether the inline "Save profile" name input is active
2821
2867
  profileSaveBuffer: '', // πŸ“– Typed characters for the profile name being saved
2868
+ // πŸ“– Feature Request state (! key opens it)
2869
+ featureRequestOpen: false, // πŸ“– Whether the feature request overlay is active
2870
+ featureRequestBuffer: '', // πŸ“– Typed characters for the feature request message
2871
+ featureRequestStatus: 'idle', // πŸ“– 'idle'|'sending'|'success'|'error' β€” webhook send status
2872
+ featureRequestError: null, // πŸ“– Last webhook error message
2822
2873
  }
2823
2874
 
2824
2875
  // πŸ“– Re-clamp viewport on terminal resize
@@ -3095,6 +3146,7 @@ async function main() {
3095
3146
  lines.push(` ${chalk.yellow('Z')} Cycle launch mode ${chalk.dim('(OpenCode CLI β†’ OpenCode Desktop β†’ OpenClaw)')}`)
3096
3147
  lines.push(` ${chalk.yellow('F')} Toggle favorite on selected row ${chalk.dim('(⭐ pinned at top, persisted)')}`)
3097
3148
  lines.push(` ${chalk.yellow('Q')} Smart Recommend ${chalk.dim('(🎯 find the best model for your task β€” questionnaire + live analysis)')}`)
3149
+ lines.push(` ${chalk.rgb(57, 255, 20).bold('J')} Request Feature ${chalk.dim('(πŸ“ send anonymous feedback to the project team)')}`)
3098
3150
  lines.push(` ${chalk.yellow('P')} Open settings ${chalk.dim('(manage API keys, provider toggles, analytics, manual update)')}`)
3099
3151
  lines.push(` ${chalk.yellow('Shift+P')} Cycle config profile ${chalk.dim('(switch between saved profiles live)')}`)
3100
3152
  lines.push(` ${chalk.yellow('Shift+S')} Save current config as a named profile ${chalk.dim('(inline prompt β€” type name + Enter)')}`)
@@ -3333,6 +3385,112 @@ async function main() {
3333
3385
  }, PING_RATE)
3334
3386
  }
3335
3387
 
3388
+ // ─── Feature Request overlay renderer ─────────────────────────────────────
3389
+ // πŸ“– renderFeatureRequest: Draw the overlay for anonymous Discord feedback.
3390
+ // πŸ“– Shows an input field where users can type feature requests, then sends to Discord webhook.
3391
+ function renderFeatureRequest() {
3392
+ const EL = '\x1b[K'
3393
+ const lines = []
3394
+
3395
+ // πŸ“– Calculate available space for multi-line input
3396
+ const maxInputWidth = OVERLAY_PANEL_WIDTH - 8 // 8 = padding (4 spaces each side)
3397
+ const maxInputLines = 10 // Show up to 10 lines of input
3398
+
3399
+ // πŸ“– Split buffer into lines for display (with wrapping)
3400
+ const wrapText = (text, width) => {
3401
+ const words = text.split(' ')
3402
+ const lines = []
3403
+ let currentLine = ''
3404
+
3405
+ for (const word of words) {
3406
+ const testLine = currentLine ? currentLine + ' ' + word : word
3407
+ if (testLine.length <= width) {
3408
+ currentLine = testLine
3409
+ } else {
3410
+ if (currentLine) lines.push(currentLine)
3411
+ currentLine = word
3412
+ }
3413
+ }
3414
+ if (currentLine) lines.push(currentLine)
3415
+ return lines
3416
+ }
3417
+
3418
+ const inputLines = wrapText(state.featureRequestBuffer, maxInputWidth)
3419
+ const displayLines = inputLines.slice(0, maxInputLines)
3420
+
3421
+ // πŸ“– Header
3422
+ lines.push('')
3423
+ lines.push(` ${chalk.bold.rgb(57, 255, 20)('πŸ“ Feature Request')} ${chalk.dim('β€” send anonymous feedback to the project team')}`)
3424
+ lines.push('')
3425
+
3426
+ // πŸ“– Status messages (if any)
3427
+ if (state.featureRequestStatus === 'sending') {
3428
+ lines.push(` ${chalk.yellow('⏳ Sending...')}`)
3429
+ lines.push('')
3430
+ } else if (state.featureRequestStatus === 'success') {
3431
+ lines.push(` ${chalk.greenBright.bold('βœ… Successfully sent!')} ${chalk.dim('Closing overlay in 3 seconds...')}`)
3432
+ lines.push('')
3433
+ lines.push(` ${chalk.dim('Thank you for your feedback! Your feature request has been sent to the project team.')}`)
3434
+ lines.push('')
3435
+ } else if (state.featureRequestStatus === 'error') {
3436
+ lines.push(` ${chalk.red('❌ Error:')} ${chalk.yellow(state.featureRequestError || 'Failed to send')}`)
3437
+ lines.push(` ${chalk.dim('Press Backspace to edit, or Esc to close')}`)
3438
+ lines.push('')
3439
+ } else {
3440
+ lines.push(` ${chalk.dim('Type your feature request below. Press Enter to send, Esc to cancel.')}`)
3441
+ lines.push(` ${chalk.dim('Your message will be sent anonymously to the project team.')}`)
3442
+ lines.push('')
3443
+ }
3444
+
3445
+ // πŸ“– Input box with border
3446
+ lines.push(chalk.dim(` β”Œβ”€ ${chalk.cyan('Message')} ${chalk.dim(`(${state.featureRequestBuffer.length}/500 chars)`)} ─${'─'.repeat(maxInputWidth - 22)}┐`))
3447
+
3448
+ // πŸ“– Display input lines (or placeholder if empty)
3449
+ if (displayLines.length === 0 && state.featureRequestStatus === 'idle') {
3450
+ lines.push(chalk.dim(` β”‚${' '.repeat(maxInputWidth)}β”‚`))
3451
+ lines.push(chalk.dim(` β”‚ ${chalk.white.italic('Type your message here...')}${' '.repeat(Math.max(0, maxInputWidth - 28))}β”‚`))
3452
+ } else {
3453
+ for (const line of displayLines) {
3454
+ const padded = line.padEnd(maxInputWidth)
3455
+ lines.push(` β”‚ ${chalk.white(padded)} β”‚`)
3456
+ }
3457
+ }
3458
+
3459
+ // πŸ“– Fill remaining space if needed
3460
+ const linesToFill = Math.max(0, maxInputLines - Math.max(displayLines.length, 1))
3461
+ for (let i = 0; i < linesToFill; i++) {
3462
+ lines.push(chalk.dim(` β”‚${' '.repeat(maxInputWidth)}β”‚`))
3463
+ }
3464
+
3465
+ // πŸ“– Cursor indicator (only when not sending/success)
3466
+ if (state.featureRequestStatus === 'idle' || state.featureRequestStatus === 'error') {
3467
+ const cursorLine = inputLines.length > 0 ? inputLines.length - 1 : 0
3468
+ const lastDisplayLine = displayLines.length - 1
3469
+ // Add cursor indicator to the last line
3470
+ if (lines.length > 0 && displayLines.length > 0) {
3471
+ const lastLineIdx = lines.findIndex(l => l.includes('β”‚ ') && !l.includes('Message'))
3472
+ if (lastLineIdx >= 0 && lastLineIdx < lines.length) {
3473
+ // Add cursor blink
3474
+ const lastLine = lines[lastLineIdx]
3475
+ if (lastLine.includes('β”‚')) {
3476
+ lines[lastLineIdx] = lastLine.replace(/\s+β”‚$/, chalk.rgb(57, 255, 20).bold('▏') + ' β”‚')
3477
+ }
3478
+ }
3479
+ }
3480
+ }
3481
+
3482
+ lines.push(chalk.dim(` β””${'─'.repeat(maxInputWidth + 2)}β”˜`))
3483
+
3484
+ lines.push('')
3485
+ lines.push(chalk.dim(' Enter Send β€’ Esc Cancel β€’ Backspace Delete'))
3486
+
3487
+ // πŸ“– Apply overlay tint and return
3488
+ const FEATURE_REQUEST_OVERLAY_BG = chalk.bgRgb(26, 26, 46) // Dark blue-ish background (RGB: 26, 26, 46)
3489
+ const tintedLines = tintOverlayLines(lines, FEATURE_REQUEST_OVERLAY_BG)
3490
+ const cleared = tintedLines.map(l => l + EL)
3491
+ return cleared.join('\n')
3492
+ }
3493
+
3336
3494
  // πŸ“– stopRecommendAnalysis: cleanup timers if user cancels during analysis
3337
3495
  function stopRecommendAnalysis() {
3338
3496
  if (state.recommendAnalysisTimer) { clearInterval(state.recommendAnalysisTimer); state.recommendAnalysisTimer = null }
@@ -3449,6 +3607,73 @@ async function main() {
3449
3607
  return
3450
3608
  }
3451
3609
 
3610
+ // πŸ“– Feature Request overlay: intercept ALL keys while overlay is active.
3611
+ // πŸ“– Enter β†’ send to Discord, Esc β†’ cancel, Backspace β†’ delete char, printable β†’ append to buffer.
3612
+ if (state.featureRequestOpen) {
3613
+ if (key.ctrl && key.name === 'c') { exit(0); return }
3614
+
3615
+ if (key.name === 'escape') {
3616
+ // πŸ“– Cancel feature request β€” close overlay
3617
+ state.featureRequestOpen = false
3618
+ state.featureRequestBuffer = ''
3619
+ state.featureRequestStatus = 'idle'
3620
+ state.featureRequestError = null
3621
+ return
3622
+ }
3623
+
3624
+ if (key.name === 'return') {
3625
+ // πŸ“– Send feature request to Discord webhook
3626
+ const message = state.featureRequestBuffer.trim()
3627
+ if (message.length > 0 && state.featureRequestStatus !== 'sending') {
3628
+ state.featureRequestStatus = 'sending'
3629
+ const result = await sendFeatureRequest(message)
3630
+ if (result.success) {
3631
+ // πŸ“– Success β€” show confirmation briefly, then close overlay after 3 seconds
3632
+ state.featureRequestStatus = 'success'
3633
+ setTimeout(() => {
3634
+ state.featureRequestOpen = false
3635
+ state.featureRequestBuffer = ''
3636
+ state.featureRequestStatus = 'idle'
3637
+ state.featureRequestError = null
3638
+ }, 3000)
3639
+ } else {
3640
+ // πŸ“– Error β€” show error message, keep overlay open
3641
+ state.featureRequestStatus = 'error'
3642
+ state.featureRequestError = result.error || 'Unknown error'
3643
+ }
3644
+ }
3645
+ return
3646
+ }
3647
+
3648
+ if (key.name === 'backspace') {
3649
+ // πŸ“– Don't allow editing while sending or after success
3650
+ if (state.featureRequestStatus === 'sending' || state.featureRequestStatus === 'success') return
3651
+ state.featureRequestBuffer = state.featureRequestBuffer.slice(0, -1)
3652
+ // πŸ“– Clear error status when user starts editing again
3653
+ if (state.featureRequestStatus === 'error') {
3654
+ state.featureRequestStatus = 'idle'
3655
+ state.featureRequestError = null
3656
+ }
3657
+ return
3658
+ }
3659
+
3660
+ // πŸ“– Append printable characters (str is the raw character typed)
3661
+ // πŸ“– Limit to 500 characters (Discord embed description limit)
3662
+ if (str && str.length === 1 && !key.ctrl && !key.meta) {
3663
+ // πŸ“– Don't allow editing while sending or after success
3664
+ if (state.featureRequestStatus === 'sending' || state.featureRequestStatus === 'success') return
3665
+ if (state.featureRequestBuffer.length < 500) {
3666
+ state.featureRequestBuffer += str
3667
+ // πŸ“– Clear error status when user starts editing again
3668
+ if (state.featureRequestStatus === 'error') {
3669
+ state.featureRequestStatus = 'idle'
3670
+ state.featureRequestError = null
3671
+ }
3672
+ }
3673
+ }
3674
+ return
3675
+ }
3676
+
3452
3677
  // πŸ“– Help overlay: full keyboard navigation + key swallowing while overlay is open.
3453
3678
  if (state.helpVisible) {
3454
3679
  const pageStep = Math.max(1, (state.terminalRows || 1) - 2)
@@ -3921,6 +4146,15 @@ async function main() {
3921
4146
  return
3922
4147
  }
3923
4148
 
4149
+ // πŸ“– J key: open Feature Request overlay (anonymous Discord feedback)
4150
+ if (key.name === 'j') {
4151
+ state.featureRequestOpen = true
4152
+ state.featureRequestBuffer = ''
4153
+ state.featureRequestStatus = 'idle'
4154
+ state.featureRequestError = null
4155
+ return
4156
+ }
4157
+
3924
4158
  // πŸ“– Interval adjustment keys: W=decrease (faster), X=increase (slower)
3925
4159
  // πŸ“– Minimum 1s, maximum 60s
3926
4160
  if (key.name === 'w') {
@@ -4054,11 +4288,11 @@ async function main() {
4054
4288
 
4055
4289
  process.stdin.on('keypress', onKeyPress)
4056
4290
 
4057
- // πŸ“– Animation loop: render settings overlay, recommend overlay, help overlay, OR main table
4291
+ // πŸ“– Animation loop: render settings overlay, recommend overlay, help overlay, feature request overlay, OR main table
4058
4292
  const ticker = setInterval(() => {
4059
4293
  state.frame++
4060
4294
  // πŸ“– Cache visible+sorted models each frame so Enter handler always matches the display
4061
- if (!state.settingsOpen && !state.recommendOpen) {
4295
+ if (!state.settingsOpen && !state.recommendOpen && !state.featureRequestOpen) {
4062
4296
  const visible = state.results.filter(r => !r.hidden)
4063
4297
  state.visibleSorted = sortResultsWithPinnedFavorites(visible, state.sortColumn, state.sortDirection)
4064
4298
  }
@@ -4066,9 +4300,11 @@ async function main() {
4066
4300
  ? renderSettings()
4067
4301
  : state.recommendOpen
4068
4302
  ? renderRecommend()
4069
- : state.helpVisible
4070
- ? renderHelp()
4071
- : renderTable(state.results, state.pendingPings, state.frame, state.cursor, state.sortColumn, state.sortDirection, state.pingInterval, state.lastPingTime, state.mode, tierFilterMode, state.scrollOffset, state.terminalRows, originFilterMode, state.activeProfile, state.profileSaveMode, state.profileSaveBuffer)
4303
+ : state.featureRequestOpen
4304
+ ? renderFeatureRequest()
4305
+ : state.helpVisible
4306
+ ? renderHelp()
4307
+ : renderTable(state.results, state.pendingPings, state.frame, state.cursor, state.sortColumn, state.sortDirection, state.pingInterval, state.lastPingTime, state.mode, tierFilterMode, state.scrollOffset, state.terminalRows, originFilterMode, state.activeProfile, state.profileSaveMode, state.profileSaveBuffer)
4072
4308
  process.stdout.write(ALT_HOME + content)
4073
4309
  }, Math.round(1000 / FPS))
4074
4310
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-coding-models",
3
- "version": "0.1.73",
3
+ "version": "0.1.76",
4
4
  "description": "Find the fastest coding LLM models in seconds β€” ping free models from multiple providers, pick the best one for OpenCode, Cursor, or any AI coding assistant.",
5
5
  "keywords": [
6
6
  "nvidia",