free-coding-models 0.1.19 β†’ 0.1.20

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
@@ -56,7 +56,7 @@
56
56
  - **🎨 Clean output** β€” Zero scrollback pollution, interface stays open until Ctrl+C
57
57
  - **πŸ“Ά Status indicators** β€” UP βœ… Β· Timeout ⏳ Β· Overloaded πŸ”₯ Β· Not Found 🚫
58
58
  - **πŸ”§ Multi-source support** β€” Extensible architecture via `sources.js` (add new providers easily)
59
- - **🏷 Tier filtering** β€” Filter models by tier letter (S, A, B, C) with `--tier`
59
+ - **🏷 Tier filtering** β€” Filter models by tier letter (S, A, B, C) with `--tier` flag or dynamically with E/D keys
60
60
 
61
61
  ---
62
62
 
@@ -226,6 +226,15 @@ free-coding-models --tier B # Only B+, B (lightweight options)
226
226
  free-coding-models --tier C # Only C (edge/minimal models)
227
227
  ```
228
228
 
229
+ #### Dynamic tier filtering with E/D keys
230
+
231
+ During runtime, use **E** and **D** keys to dynamically adjust the tier filter:
232
+
233
+ - **E** (Elevate) β€” Show fewer, higher-tier models (cycle: All β†’ S β†’ A β†’ B β†’ C β†’ All)
234
+ - **D** (Descend) β€” Show more, lower-tier models (cycle: All β†’ C β†’ B β†’ A β†’ S β†’ All)
235
+
236
+ Current tier filter is shown in the header badge (e.g., `[Tier S]`)
237
+
229
238
  ---
230
239
 
231
240
  ## πŸ”Œ OpenCode Integration
@@ -439,6 +448,8 @@ This script:
439
448
  - **R/T/O/M/P/A/S/V/U** β€” Sort by Rank/Tier/Origin/Model/Ping/Avg/Status/Verdict/Uptime
440
449
  - **W** β€” Decrease ping interval (faster pings)
441
450
  - **X** β€” Increase ping interval (slower pings)
451
+ - **E** β€” Elevate tier filter (show fewer, higher-tier models)
452
+ - **D** β€” Descend tier filter (show more, lower-tier models)
442
453
  - **Ctrl+C** β€” Exit
443
454
 
444
455
  ---
@@ -327,7 +327,7 @@ const spinCell = (f, o = 0) => chalk.dim.yellow(FRAMES[(f + o) % FRAMES.length].
327
327
  // πŸ“– are imported from lib/utils.js for testability
328
328
 
329
329
  // πŸ“– renderTable: mode param controls footer hint text (opencode vs openclaw)
330
- function renderTable(results, pendingPings, frame, cursor = null, sortColumn = 'avg', sortDirection = 'asc', pingInterval = PING_INTERVAL, lastPingTime = Date.now(), mode = 'opencode') {
330
+ function renderTable(results, pendingPings, frame, cursor = null, sortColumn = 'avg', sortDirection = 'asc', pingInterval = PING_INTERVAL, lastPingTime = Date.now(), mode = 'opencode', tierFilter = null) {
331
331
  const up = results.filter(r => r.status === 'up').length
332
332
  const down = results.filter(r => r.status === 'down').length
333
333
  const timeout = results.filter(r => r.status === 'timeout').length
@@ -354,6 +354,12 @@ function renderTable(results, pendingPings, frame, cursor = null, sortColumn = '
354
354
  modeBadge = chalk.bold.rgb(0, 200, 255)(' [πŸ’» CLI]')
355
355
  }
356
356
 
357
+ // πŸ“– Tier filter badge shown when filtering is active
358
+ let tierBadge = ''
359
+ if (tierFilter) {
360
+ tierBadge = chalk.bold.rgb(255, 200, 0)(` [Tier ${tierFilter}]`)
361
+ }
362
+
357
363
  // πŸ“– Column widths (generous spacing with margins)
358
364
  const W_RANK = 6
359
365
  const W_TIER = 6
@@ -370,7 +376,7 @@ function renderTable(results, pendingPings, frame, cursor = null, sortColumn = '
370
376
 
371
377
  const lines = [
372
378
  '',
373
- ` ${chalk.bold('⚑ Free Coding Models')} ${chalk.dim('v' + LOCAL_VERSION)}${modeBadge} ` +
379
+ ` ${chalk.bold('⚑ Free Coding Models')} ${chalk.dim('v' + LOCAL_VERSION)}${modeBadge}${tierBadge} ` +
374
380
  chalk.greenBright(`βœ… ${up}`) + chalk.dim(' up ') +
375
381
  chalk.yellow(`⏱ ${timeout}`) + chalk.dim(' timeout ') +
376
382
  chalk.red(`❌ ${down}`) + chalk.dim(' down ') +
@@ -556,7 +562,7 @@ function renderTable(results, pendingPings, frame, cursor = null, sortColumn = '
556
562
  : mode === 'opencode-desktop'
557
563
  ? chalk.rgb(0, 200, 255)('Enterβ†’OpenDesktop')
558
564
  : chalk.rgb(0, 200, 255)('Enterβ†’OpenCode')
559
- lines.push(chalk.dim(` ↑↓ Navigate β€’ `) + actionHint + chalk.dim(` β€’ R/T/O/M/P/A/S/V/U Sort β€’ W↓/X↑ Interval (${intervalSec}s) β€’ Ctrl+C Exit`))
565
+ lines.push(chalk.dim(` ↑↓ Navigate β€’ `) + actionHint + chalk.dim(` β€’ R/T/O/M/P/A/S/V/U Sort β€’ W↓/X↑ Interval (${intervalSec}s) β€’ E↑/D↓ Tier β€’ Ctrl+C Exit`))
560
566
  lines.push('')
561
567
  lines.push(chalk.dim(' made with ') + '🩷' + chalk.dim(' by vava-nessa β€’ ') + chalk.dim.underline('https://github.com/vava-nessa/free-coding-models'))
562
568
  lines.push('')
@@ -1070,6 +1076,7 @@ async function main() {
1070
1076
  // πŸ“– sortColumn: 'rank'|'tier'|'origin'|'model'|'ping'|'avg'|'status'|'verdict'|'uptime'
1071
1077
  // πŸ“– sortDirection: 'asc' (default) or 'desc'
1072
1078
  // πŸ“– pingInterval: current interval in ms (default 2000, adjustable with W/X keys)
1079
+ // πŸ“– tierFilter: current tier filter letter (null = all, 'S' = S+/S, 'A' = A+/A/A-, etc.)
1073
1080
  const state = {
1074
1081
  results,
1075
1082
  pendingPings: 0,
@@ -1082,6 +1089,7 @@ async function main() {
1082
1089
  lastPingTime: Date.now(), // πŸ“– Track when last ping cycle started
1083
1090
  fiableMode, // πŸ“– Pass fiable mode to state
1084
1091
  mode, // πŸ“– 'opencode' or 'openclaw' β€” controls Enter action
1092
+ tierFilter: tierFilter || null, // πŸ“– Track current tier filter (null if no --tier flag)
1085
1093
  }
1086
1094
 
1087
1095
  // πŸ“– Enter alternate screen β€” animation runs here, zero scrollback pollution
@@ -1097,6 +1105,40 @@ async function main() {
1097
1105
  process.on('SIGINT', () => exit(0))
1098
1106
  process.on('SIGTERM', () => exit(0))
1099
1107
 
1108
+ // πŸ“– Apply tier filter to results based on current state.tierFilter
1109
+ // πŸ“– Preserves existing ping history when filtering
1110
+ function applyTierFilter() {
1111
+ const allModels = MODELS.map(([modelId, label, tier], i) => ({
1112
+ idx: i + 1, modelId, label, tier,
1113
+ status: 'pending',
1114
+ pings: [],
1115
+ httpCode: null,
1116
+ }))
1117
+
1118
+ if (!state.tierFilter) {
1119
+ return allModels
1120
+ }
1121
+
1122
+ // πŸ“– Filter models by tier and preserve existing ping history
1123
+ const filteredModels = allModels.filter(model =>
1124
+ TIER_LETTER_MAP[state.tierFilter].includes(model.tier)
1125
+ )
1126
+
1127
+ // πŸ“– Try to preserve existing ping data from current results
1128
+ return filteredModels.map(model => {
1129
+ const existingResult = state.results.find(r => r.modelId === model.modelId)
1130
+ if (existingResult) {
1131
+ return {
1132
+ ...model,
1133
+ status: existingResult.status,
1134
+ pings: [...existingResult.pings],
1135
+ httpCode: existingResult.httpCode
1136
+ }
1137
+ }
1138
+ return model
1139
+ })
1140
+ }
1141
+
1100
1142
  // πŸ“– Setup keyboard input for interactive selection during pings
1101
1143
  // πŸ“– Use readline with keypress event for arrow key handling
1102
1144
  process.stdin.setEncoding('utf8')
@@ -1129,7 +1171,26 @@ async function main() {
1129
1171
  // πŸ“– Minimum 1s, maximum 60s
1130
1172
  if (key.name === 'w') {
1131
1173
  state.pingInterval = Math.max(1000, state.pingInterval - 1000)
1132
- return
1174
+ } else if (key.name === 'x') {
1175
+ state.pingInterval = Math.min(60000, state.pingInterval + 1000)
1176
+ }
1177
+
1178
+ // πŸ“– Tier filtering keys: E=elevate (more restrictive), D=descend (less restrictive)
1179
+ // πŸ“– Cycle through: null β†’ 'S' β†’ 'A' β†’ 'B' β†’ 'C' β†’ null (all tiers)
1180
+ if (key.name === 'e') {
1181
+ const tierOrder = [null, 'S', 'A', 'B', 'C']
1182
+ const currentIndex = tierOrder.indexOf(state.tierFilter)
1183
+ const nextIndex = (currentIndex + 1) % tierOrder.length
1184
+ state.tierFilter = tierOrder[nextIndex]
1185
+ state.results = applyTierFilter()
1186
+ state.cursor = Math.min(state.cursor, state.results.length - 1)
1187
+ } else if (key.name === 'd') {
1188
+ const tierOrder = [null, 'C', 'B', 'A', 'S']
1189
+ const currentIndex = tierOrder.indexOf(state.tierFilter)
1190
+ const nextIndex = (currentIndex - 1 + tierOrder.length) % tierOrder.length
1191
+ state.tierFilter = tierOrder[nextIndex]
1192
+ state.results = applyTierFilter()
1193
+ state.cursor = Math.min(state.cursor, state.results.length - 1)
1133
1194
  }
1134
1195
 
1135
1196
  if (key.name === 'x') {
@@ -1205,10 +1266,10 @@ async function main() {
1205
1266
  // πŸ“– Animation loop: clear alt screen + redraw table at FPS with cursor
1206
1267
  const ticker = setInterval(() => {
1207
1268
  state.frame++
1208
- process.stdout.write(ALT_CLEAR + renderTable(state.results, state.pendingPings, state.frame, state.cursor, state.sortColumn, state.sortDirection, state.pingInterval, state.lastPingTime, state.mode))
1269
+ process.stdout.write(ALT_CLEAR + renderTable(state.results, state.pendingPings, state.frame, state.cursor, state.sortColumn, state.sortDirection, state.pingInterval, state.lastPingTime, state.mode, state.tierFilter))
1209
1270
  }, Math.round(1000 / FPS))
1210
1271
 
1211
- process.stdout.write(ALT_CLEAR + renderTable(state.results, state.pendingPings, state.frame, state.cursor, state.sortColumn, state.sortDirection, state.pingInterval, state.lastPingTime, state.mode))
1272
+ process.stdout.write(ALT_CLEAR + renderTable(state.results, state.pendingPings, state.frame, state.cursor, state.sortColumn, state.sortDirection, state.pingInterval, state.lastPingTime, state.mode, state.tierFilter))
1212
1273
 
1213
1274
  // ── Continuous ping loop β€” ping all models every N seconds forever ──────────
1214
1275
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-coding-models",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
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",