free-coding-models 0.1.19 β 0.1.21
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 +12 -1
- package/bin/free-coding-models.js +72 -17
- package/package.json +1 -1
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,11 +327,14 @@ 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') {
|
|
331
|
-
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
const
|
|
330
|
+
function renderTable(results, pendingPings, frame, cursor = null, sortColumn = 'avg', sortDirection = 'asc', pingInterval = PING_INTERVAL, lastPingTime = Date.now(), mode = 'opencode', tierFilter = null) {
|
|
331
|
+
// π Filter out hidden models for display
|
|
332
|
+
const visibleResults = results.filter(r => !r.hidden)
|
|
333
|
+
|
|
334
|
+
const up = visibleResults.filter(r => r.status === 'up').length
|
|
335
|
+
const down = visibleResults.filter(r => r.status === 'down').length
|
|
336
|
+
const timeout = visibleResults.filter(r => r.status === 'timeout').length
|
|
337
|
+
const pending = visibleResults.filter(r => r.status === 'pending').length
|
|
335
338
|
|
|
336
339
|
// π Calculate seconds until next ping
|
|
337
340
|
const timeSinceLastPing = Date.now() - lastPingTime
|
|
@@ -354,6 +357,12 @@ function renderTable(results, pendingPings, frame, cursor = null, sortColumn = '
|
|
|
354
357
|
modeBadge = chalk.bold.rgb(0, 200, 255)(' [π» CLI]')
|
|
355
358
|
}
|
|
356
359
|
|
|
360
|
+
// π Tier filter badge shown when filtering is active
|
|
361
|
+
let tierBadge = ''
|
|
362
|
+
if (tierFilter) {
|
|
363
|
+
tierBadge = chalk.bold.rgb(255, 200, 0)(` [Tier ${tierFilter}]`)
|
|
364
|
+
}
|
|
365
|
+
|
|
357
366
|
// π Column widths (generous spacing with margins)
|
|
358
367
|
const W_RANK = 6
|
|
359
368
|
const W_TIER = 6
|
|
@@ -366,11 +375,11 @@ function renderTable(results, pendingPings, frame, cursor = null, sortColumn = '
|
|
|
366
375
|
const W_UPTIME = 6
|
|
367
376
|
|
|
368
377
|
// π Sort models using the shared helper
|
|
369
|
-
const sorted = sortResults(
|
|
378
|
+
const sorted = sortResults(visibleResults, sortColumn, sortDirection)
|
|
370
379
|
|
|
371
380
|
const lines = [
|
|
372
381
|
'',
|
|
373
|
-
` ${chalk.bold('β‘ Free Coding Models')} ${chalk.dim('v' + LOCAL_VERSION)}${modeBadge} ` +
|
|
382
|
+
` ${chalk.bold('β‘ Free Coding Models')} ${chalk.dim('v' + LOCAL_VERSION)}${modeBadge}${tierBadge} ` +
|
|
374
383
|
chalk.greenBright(`β
${up}`) + chalk.dim(' up ') +
|
|
375
384
|
chalk.yellow(`β± ${timeout}`) + chalk.dim(' timeout ') +
|
|
376
385
|
chalk.red(`β ${down}`) + chalk.dim(' down ') +
|
|
@@ -556,7 +565,7 @@ function renderTable(results, pendingPings, frame, cursor = null, sortColumn = '
|
|
|
556
565
|
: mode === 'opencode-desktop'
|
|
557
566
|
? chalk.rgb(0, 200, 255)('EnterβOpenDesktop')
|
|
558
567
|
: 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`))
|
|
568
|
+
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
569
|
lines.push('')
|
|
561
570
|
lines.push(chalk.dim(' made with ') + 'π©·' + chalk.dim(' by vava-nessa β’ ') + chalk.dim.underline('https://github.com/vava-nessa/free-coding-models'))
|
|
562
571
|
lines.push('')
|
|
@@ -1049,27 +1058,40 @@ async function main() {
|
|
|
1049
1058
|
console.log()
|
|
1050
1059
|
}
|
|
1051
1060
|
|
|
1052
|
-
// π
|
|
1061
|
+
// π Create results array with all models initially visible
|
|
1053
1062
|
let results = MODELS.map(([modelId, label, tier], i) => ({
|
|
1054
1063
|
idx: i + 1, modelId, label, tier,
|
|
1055
1064
|
status: 'pending',
|
|
1056
1065
|
pings: [], // π All ping results (ms or 'TIMEOUT')
|
|
1057
1066
|
httpCode: null,
|
|
1067
|
+
hidden: false, // π Simple flag to hide/show models
|
|
1058
1068
|
}))
|
|
1059
1069
|
|
|
1070
|
+
// π Apply filters by setting hidden flag
|
|
1060
1071
|
if (bestMode) {
|
|
1061
|
-
results
|
|
1072
|
+
results.forEach(r => {
|
|
1073
|
+
r.hidden = !(r.tier === 'S+' || r.tier === 'S' || r.tier === 'A+')
|
|
1074
|
+
})
|
|
1062
1075
|
}
|
|
1063
1076
|
|
|
1064
|
-
// π Apply tier letter filter if --tier X was given
|
|
1077
|
+
// π Apply tier letter filter if --tier X was given (just sets initial state)
|
|
1078
|
+
// π User can still change filter with E/D keys later
|
|
1065
1079
|
if (tierFilter) {
|
|
1066
|
-
|
|
1080
|
+
const tierSet = TIER_LETTER_MAP[tierFilter]
|
|
1081
|
+
if (!tierSet) {
|
|
1082
|
+
console.error(chalk.red(` β Unknown tier "${tierFilter}". Valid tiers: S, A, B, C`))
|
|
1083
|
+
process.exit(1)
|
|
1084
|
+
}
|
|
1085
|
+
results.forEach(r => {
|
|
1086
|
+
r.hidden = !tierSet.includes(r.tier)
|
|
1087
|
+
})
|
|
1067
1088
|
}
|
|
1068
1089
|
|
|
1069
1090
|
// π Add interactive selection state - cursor index and user's choice
|
|
1070
1091
|
// π sortColumn: 'rank'|'tier'|'origin'|'model'|'ping'|'avg'|'status'|'verdict'|'uptime'
|
|
1071
1092
|
// π sortDirection: 'asc' (default) or 'desc'
|
|
1072
1093
|
// π pingInterval: current interval in ms (default 2000, adjustable with W/X keys)
|
|
1094
|
+
// π tierFilter: current tier filter letter (null = all, 'S' = S+/S, 'A' = A+/A/A-, etc.)
|
|
1073
1095
|
const state = {
|
|
1074
1096
|
results,
|
|
1075
1097
|
pendingPings: 0,
|
|
@@ -1082,6 +1104,7 @@ async function main() {
|
|
|
1082
1104
|
lastPingTime: Date.now(), // π Track when last ping cycle started
|
|
1083
1105
|
fiableMode, // π Pass fiable mode to state
|
|
1084
1106
|
mode, // π 'opencode' or 'openclaw' β controls Enter action
|
|
1107
|
+
tierFilter: tierFilter || null, // π Track current tier filter (null if no --tier flag)
|
|
1085
1108
|
}
|
|
1086
1109
|
|
|
1087
1110
|
// π Enter alternate screen β animation runs here, zero scrollback pollution
|
|
@@ -1097,6 +1120,19 @@ async function main() {
|
|
|
1097
1120
|
process.on('SIGINT', () => exit(0))
|
|
1098
1121
|
process.on('SIGTERM', () => exit(0))
|
|
1099
1122
|
|
|
1123
|
+
// π Apply tier filter based on current state.tierFilter
|
|
1124
|
+
// π This preserves all ping history by merging with existing results
|
|
1125
|
+
function applyTierFilter() {
|
|
1126
|
+
const tierSet = state.tierFilter ? TIER_LETTER_MAP[state.tierFilter] : null
|
|
1127
|
+
|
|
1128
|
+
// π Simple approach: just update hidden flag on existing results
|
|
1129
|
+
state.results.forEach(r => {
|
|
1130
|
+
r.hidden = tierSet ? !tierSet.includes(r.tier) : false
|
|
1131
|
+
})
|
|
1132
|
+
|
|
1133
|
+
return state.results // π Return same array, just updated hidden flags
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1100
1136
|
// π Setup keyboard input for interactive selection during pings
|
|
1101
1137
|
// π Use readline with keypress event for arrow key handling
|
|
1102
1138
|
process.stdin.setEncoding('utf8')
|
|
@@ -1129,7 +1165,26 @@ async function main() {
|
|
|
1129
1165
|
// π Minimum 1s, maximum 60s
|
|
1130
1166
|
if (key.name === 'w') {
|
|
1131
1167
|
state.pingInterval = Math.max(1000, state.pingInterval - 1000)
|
|
1132
|
-
|
|
1168
|
+
} else if (key.name === 'x') {
|
|
1169
|
+
state.pingInterval = Math.min(60000, state.pingInterval + 1000)
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// π Tier filtering keys: E=elevate (more restrictive), D=descend (less restrictive)
|
|
1173
|
+
// π Cycle through: null β 'S' β 'A' β 'B' β 'C' β null (all tiers)
|
|
1174
|
+
if (key.name === 'e') {
|
|
1175
|
+
const tierOrder = [null, 'S', 'A', 'B', 'C']
|
|
1176
|
+
const currentIndex = tierOrder.indexOf(state.tierFilter)
|
|
1177
|
+
const nextIndex = (currentIndex + 1) % tierOrder.length
|
|
1178
|
+
state.tierFilter = tierOrder[nextIndex]
|
|
1179
|
+
state.results = applyTierFilter()
|
|
1180
|
+
state.cursor = Math.min(state.cursor, state.results.length - 1)
|
|
1181
|
+
} else if (key.name === 'd') {
|
|
1182
|
+
const tierOrder = [null, 'C', 'B', 'A', 'S']
|
|
1183
|
+
const currentIndex = tierOrder.indexOf(state.tierFilter)
|
|
1184
|
+
const nextIndex = (currentIndex - 1 + tierOrder.length) % tierOrder.length
|
|
1185
|
+
state.tierFilter = tierOrder[nextIndex]
|
|
1186
|
+
state.results = applyTierFilter()
|
|
1187
|
+
state.cursor = Math.min(state.cursor, state.results.length - 1)
|
|
1133
1188
|
}
|
|
1134
1189
|
|
|
1135
1190
|
if (key.name === 'x') {
|
|
@@ -1205,10 +1260,10 @@ async function main() {
|
|
|
1205
1260
|
// π Animation loop: clear alt screen + redraw table at FPS with cursor
|
|
1206
1261
|
const ticker = setInterval(() => {
|
|
1207
1262
|
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))
|
|
1263
|
+
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
1264
|
}, Math.round(1000 / FPS))
|
|
1210
1265
|
|
|
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))
|
|
1266
|
+
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
1267
|
|
|
1213
1268
|
// ββ Continuous ping loop β ping all models every N seconds forever ββββββββββ
|
|
1214
1269
|
|
|
@@ -1233,14 +1288,14 @@ async function main() {
|
|
|
1233
1288
|
}
|
|
1234
1289
|
|
|
1235
1290
|
// π Initial ping of all models
|
|
1236
|
-
const initialPing = Promise.all(results.map(r => pingModel(r)))
|
|
1291
|
+
const initialPing = Promise.all(state.results.map(r => pingModel(r)))
|
|
1237
1292
|
|
|
1238
1293
|
// π Continuous ping loop with dynamic interval (adjustable with W/X keys)
|
|
1239
1294
|
const schedulePing = () => {
|
|
1240
1295
|
state.pingIntervalObj = setTimeout(async () => {
|
|
1241
1296
|
state.lastPingTime = Date.now()
|
|
1242
1297
|
|
|
1243
|
-
results.forEach(r => {
|
|
1298
|
+
state.results.forEach(r => {
|
|
1244
1299
|
pingModel(r).catch(() => {
|
|
1245
1300
|
// Individual ping failures don't crash the loop
|
|
1246
1301
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "free-coding-models",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
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",
|