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 +12 -1
- package/bin/free-coding-models.js +78 -13
- 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
|
---
|
|
@@ -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
|
-
|
|
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!
|
|
110
|
+
console.log(chalk.green(' β
Update complete! Version ' + latestVersion + ' installed.'))
|
|
109
111
|
console.log()
|
|
110
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|