free-coding-models 0.1.18 β†’ 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
  ---
@@ -97,19 +97,23 @@ async function checkForUpdate() {
97
97
  return null
98
98
  }
99
99
 
100
- function runUpdate() {
100
+ function runUpdate(latestVersion) {
101
101
  const { execSync } = require('child_process')
102
102
  console.log()
103
- console.log(chalk.bold.cyan(' ⬆ Updating free-coding-models...'))
103
+ console.log(chalk.bold.cyan(' ⬆ Updating free-coding-models to v' + latestVersion + '...'))
104
104
  console.log()
105
105
  try {
106
- execSync('npm i -g free-coding-models', { stdio: 'inherit' })
106
+ // πŸ“– Force install from npm registry (ignore local cache)
107
+ // πŸ“– Use --prefer-online to ensure we get the latest published version
108
+ execSync(`npm i -g free-coding-models@${latestVersion} --prefer-online`, { stdio: 'inherit' })
107
109
  console.log()
108
- console.log(chalk.green(' βœ… Update complete! Please restart free-coding-models.'))
110
+ console.log(chalk.green(' βœ… Update complete! Version ' + latestVersion + ' installed.'))
109
111
  console.log()
110
- } catch {
112
+ console.log(chalk.dim(' πŸ“ Please restart free-coding-models to use the new version.'))
113
+ console.log()
114
+ } catch (err) {
111
115
  console.log()
112
- console.log(chalk.red(' βœ– Update failed. Try manually: npm i -g free-coding-models'))
116
+ console.log(chalk.red(' βœ– Update failed. Try manually: npm i -g free-coding-models@' + latestVersion))
113
117
  console.log()
114
118
  }
115
119
  process.exit(0)
@@ -323,7 +327,7 @@ const spinCell = (f, o = 0) => chalk.dim.yellow(FRAMES[(f + o) % FRAMES.length].
323
327
  // πŸ“– are imported from lib/utils.js for testability
324
328
 
325
329
  // πŸ“– renderTable: mode param controls footer hint text (opencode vs openclaw)
326
- 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) {
327
331
  const up = results.filter(r => r.status === 'up').length
328
332
  const down = results.filter(r => r.status === 'down').length
329
333
  const timeout = results.filter(r => r.status === 'timeout').length
@@ -350,6 +354,12 @@ function renderTable(results, pendingPings, frame, cursor = null, sortColumn = '
350
354
  modeBadge = chalk.bold.rgb(0, 200, 255)(' [πŸ’» CLI]')
351
355
  }
352
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
+
353
363
  // πŸ“– Column widths (generous spacing with margins)
354
364
  const W_RANK = 6
355
365
  const W_TIER = 6
@@ -366,7 +376,7 @@ function renderTable(results, pendingPings, frame, cursor = null, sortColumn = '
366
376
 
367
377
  const lines = [
368
378
  '',
369
- ` ${chalk.bold('⚑ Free Coding Models')} ${chalk.dim('v' + LOCAL_VERSION)}${modeBadge} ` +
379
+ ` ${chalk.bold('⚑ Free Coding Models')} ${chalk.dim('v' + LOCAL_VERSION)}${modeBadge}${tierBadge} ` +
370
380
  chalk.greenBright(`βœ… ${up}`) + chalk.dim(' up ') +
371
381
  chalk.yellow(`⏱ ${timeout}`) + chalk.dim(' timeout ') +
372
382
  chalk.red(`❌ ${down}`) + chalk.dim(' down ') +
@@ -552,7 +562,7 @@ function renderTable(results, pendingPings, frame, cursor = null, sortColumn = '
552
562
  : mode === 'opencode-desktop'
553
563
  ? chalk.rgb(0, 200, 255)('Enterβ†’OpenDesktop')
554
564
  : chalk.rgb(0, 200, 255)('Enterβ†’OpenCode')
555
- 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`))
556
566
  lines.push('')
557
567
  lines.push(chalk.dim(' made with ') + '🩷' + chalk.dim(' by vava-nessa β€’ ') + chalk.dim.underline('https://github.com/vava-nessa/free-coding-models'))
558
568
  lines.push('')
@@ -1028,7 +1038,7 @@ async function main() {
1028
1038
 
1029
1039
  // πŸ“– Handle "update now" selection from the menu
1030
1040
  if (mode === 'update') {
1031
- runUpdate()
1041
+ runUpdate(latestVersion)
1032
1042
  }
1033
1043
 
1034
1044
  // πŸ“– Handle "Read Changelogs" selection β€” open GitHub releases in browser
@@ -1066,6 +1076,7 @@ async function main() {
1066
1076
  // πŸ“– sortColumn: 'rank'|'tier'|'origin'|'model'|'ping'|'avg'|'status'|'verdict'|'uptime'
1067
1077
  // πŸ“– sortDirection: 'asc' (default) or 'desc'
1068
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.)
1069
1080
  const state = {
1070
1081
  results,
1071
1082
  pendingPings: 0,
@@ -1078,6 +1089,7 @@ async function main() {
1078
1089
  lastPingTime: Date.now(), // πŸ“– Track when last ping cycle started
1079
1090
  fiableMode, // πŸ“– Pass fiable mode to state
1080
1091
  mode, // πŸ“– 'opencode' or 'openclaw' β€” controls Enter action
1092
+ tierFilter: tierFilter || null, // πŸ“– Track current tier filter (null if no --tier flag)
1081
1093
  }
1082
1094
 
1083
1095
  // πŸ“– Enter alternate screen β€” animation runs here, zero scrollback pollution
@@ -1093,6 +1105,40 @@ async function main() {
1093
1105
  process.on('SIGINT', () => exit(0))
1094
1106
  process.on('SIGTERM', () => exit(0))
1095
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
+
1096
1142
  // πŸ“– Setup keyboard input for interactive selection during pings
1097
1143
  // πŸ“– Use readline with keypress event for arrow key handling
1098
1144
  process.stdin.setEncoding('utf8')
@@ -1125,7 +1171,26 @@ async function main() {
1125
1171
  // πŸ“– Minimum 1s, maximum 60s
1126
1172
  if (key.name === 'w') {
1127
1173
  state.pingInterval = Math.max(1000, state.pingInterval - 1000)
1128
- 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)
1129
1194
  }
1130
1195
 
1131
1196
  if (key.name === 'x') {
@@ -1201,10 +1266,10 @@ async function main() {
1201
1266
  // πŸ“– Animation loop: clear alt screen + redraw table at FPS with cursor
1202
1267
  const ticker = setInterval(() => {
1203
1268
  state.frame++
1204
- 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))
1205
1270
  }, Math.round(1000 / FPS))
1206
1271
 
1207
- 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))
1208
1273
 
1209
1274
  // ── Continuous ping loop β€” ping all models every N seconds forever ──────────
1210
1275
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-coding-models",
3
- "version": "0.1.18",
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",