free-coding-models 0.1.21 β 0.1.23
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/bin/free-coding-models.js +85 -154
- package/package.json +1 -1
|
@@ -109,14 +109,19 @@ function runUpdate(latestVersion) {
|
|
|
109
109
|
console.log()
|
|
110
110
|
console.log(chalk.green(' β
Update complete! Version ' + latestVersion + ' installed.'))
|
|
111
111
|
console.log()
|
|
112
|
-
console.log(chalk.dim('
|
|
112
|
+
console.log(chalk.dim(' π Restarting with new version...'))
|
|
113
113
|
console.log()
|
|
114
|
+
|
|
115
|
+
// π Relaunch automatically with the same arguments
|
|
116
|
+
const args = process.argv.slice(2)
|
|
117
|
+
execSync(`node bin/free-coding-models.js ${args.join(' ')}`, { stdio: 'inherit' })
|
|
118
|
+
process.exit(0)
|
|
114
119
|
} catch (err) {
|
|
115
120
|
console.log()
|
|
116
121
|
console.log(chalk.red(' β Update failed. Try manually: npm i -g free-coding-models@' + latestVersion))
|
|
117
122
|
console.log()
|
|
118
123
|
}
|
|
119
|
-
process.exit(
|
|
124
|
+
process.exit(1)
|
|
120
125
|
}
|
|
121
126
|
|
|
122
127
|
// βββ Config path ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -165,73 +170,62 @@ async function promptApiKey() {
|
|
|
165
170
|
})
|
|
166
171
|
}
|
|
167
172
|
|
|
168
|
-
// βββ
|
|
169
|
-
// π Shown
|
|
170
|
-
// π
|
|
171
|
-
// π Returns '
|
|
172
|
-
async function
|
|
173
|
-
|
|
174
|
-
{
|
|
175
|
-
label: 'OpenCode CLI',
|
|
176
|
-
icon: 'π»',
|
|
177
|
-
description: 'Press Enter on a model β launch OpenCode CLI with it as default',
|
|
178
|
-
},
|
|
179
|
-
{
|
|
180
|
-
label: 'OpenCode Desktop',
|
|
181
|
-
icon: 'π₯',
|
|
182
|
-
description: 'Press Enter on a model β set model & open OpenCode Desktop app',
|
|
183
|
-
},
|
|
184
|
-
{
|
|
185
|
-
label: 'OpenClaw',
|
|
186
|
-
icon: 'π¦',
|
|
187
|
-
description: 'Press Enter on a model β set it as default in OpenClaw config',
|
|
188
|
-
},
|
|
189
|
-
]
|
|
190
|
-
|
|
191
|
-
if (latestVersion) {
|
|
192
|
-
options.push({
|
|
193
|
-
label: 'Update now',
|
|
194
|
-
icon: 'β¬',
|
|
195
|
-
description: `Update free-coding-models to v${latestVersion}`,
|
|
196
|
-
})
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// π Add "Read Changelogs" option when an update is available or was just updated
|
|
200
|
-
if (latestVersion) {
|
|
201
|
-
options.push({
|
|
202
|
-
label: 'Read Changelogs',
|
|
203
|
-
icon: 'π',
|
|
204
|
-
description: 'Open the GitHub releases page in your browser',
|
|
205
|
-
})
|
|
206
|
-
}
|
|
173
|
+
// βββ Update notification menu ββββββββββββββββββββββββββββββββββββββββββββββ
|
|
174
|
+
// π Shown ONLY when a new version is available, to prompt user to update
|
|
175
|
+
// π Centered, clean presentation that doesn't block normal usage
|
|
176
|
+
// π Returns 'update', 'changelogs', or null to continue without update
|
|
177
|
+
async function promptUpdateNotification(latestVersion) {
|
|
178
|
+
if (!latestVersion) return null
|
|
207
179
|
|
|
208
180
|
return new Promise((resolve) => {
|
|
209
181
|
let selected = 0
|
|
210
|
-
|
|
211
|
-
|
|
182
|
+
const options = [
|
|
183
|
+
{
|
|
184
|
+
label: 'Update now',
|
|
185
|
+
icon: 'β¬',
|
|
186
|
+
description: `Update free-coding-models to v${latestVersion}`,
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
label: 'Read Changelogs',
|
|
190
|
+
icon: 'π',
|
|
191
|
+
description: 'Open GitHub changelog',
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
label: 'Continue without update',
|
|
195
|
+
icon: 'βΆ',
|
|
196
|
+
description: 'Use current version',
|
|
197
|
+
},
|
|
198
|
+
]
|
|
199
|
+
|
|
200
|
+
// π Centered render function
|
|
212
201
|
const render = () => {
|
|
213
202
|
process.stdout.write('\x1b[2J\x1b[H') // clear screen + cursor home
|
|
203
|
+
|
|
204
|
+
// π Calculate centering
|
|
205
|
+
const terminalWidth = process.stdout.columns || 80
|
|
206
|
+
const maxWidth = Math.min(terminalWidth - 4, 70)
|
|
207
|
+
const centerPad = ' '.repeat(Math.max(0, Math.floor((terminalWidth - maxWidth) / 2)))
|
|
208
|
+
|
|
214
209
|
console.log()
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
console.log()
|
|
218
|
-
}
|
|
219
|
-
console.log(chalk.bold(' β‘ Free Coding Models') + chalk.dim(` v${LOCAL_VERSION} β Choose your tool`))
|
|
210
|
+
console.log(centerPad + chalk.bold.red(' β UPDATE AVAILABLE'))
|
|
211
|
+
console.log(centerPad + chalk.red(` Version ${latestVersion} is ready to install`))
|
|
220
212
|
console.log()
|
|
221
|
-
console.log(chalk.
|
|
213
|
+
console.log(centerPad + chalk.bold(' β‘ Free Coding Models') + chalk.dim(` v${LOCAL_VERSION}`))
|
|
222
214
|
console.log()
|
|
215
|
+
|
|
223
216
|
for (let i = 0; i < options.length; i++) {
|
|
224
217
|
const isSelected = i === selected
|
|
225
218
|
const bullet = isSelected ? chalk.bold.cyan(' β― ') : chalk.dim(' ')
|
|
226
219
|
const label = isSelected
|
|
227
220
|
? chalk.bold.white(options[i].icon + ' ' + options[i].label)
|
|
228
221
|
: chalk.dim(options[i].icon + ' ' + options[i].label)
|
|
229
|
-
|
|
230
|
-
console.log(bullet + label)
|
|
231
|
-
console.log(chalk.dim(' ' + options[i].description))
|
|
222
|
+
|
|
223
|
+
console.log(centerPad + bullet + label)
|
|
224
|
+
console.log(centerPad + chalk.dim(' ' + options[i].description))
|
|
232
225
|
console.log()
|
|
233
226
|
}
|
|
234
|
-
|
|
227
|
+
|
|
228
|
+
console.log(centerPad + chalk.dim(' ββ Navigate β’ Enter Select β’ Ctrl+C Continue'))
|
|
235
229
|
console.log()
|
|
236
230
|
}
|
|
237
231
|
|
|
@@ -245,7 +239,8 @@ async function promptModeSelection(latestVersion) {
|
|
|
245
239
|
if (key.ctrl && key.name === 'c') {
|
|
246
240
|
if (process.stdin.isTTY) process.stdin.setRawMode(false)
|
|
247
241
|
process.stdin.removeListener('keypress', onKey)
|
|
248
|
-
|
|
242
|
+
resolve(null) // Continue without update
|
|
243
|
+
return
|
|
249
244
|
}
|
|
250
245
|
if (key.name === 'up' && selected > 0) {
|
|
251
246
|
selected--
|
|
@@ -257,10 +252,10 @@ async function promptModeSelection(latestVersion) {
|
|
|
257
252
|
if (process.stdin.isTTY) process.stdin.setRawMode(false)
|
|
258
253
|
process.stdin.removeListener('keypress', onKey)
|
|
259
254
|
process.stdin.pause()
|
|
260
|
-
|
|
261
|
-
if (
|
|
262
|
-
if (
|
|
263
|
-
resolve(
|
|
255
|
+
|
|
256
|
+
if (selected === 0) resolve('update')
|
|
257
|
+
else if (selected === 1) resolve('changelogs')
|
|
258
|
+
else resolve(null) // Continue without update
|
|
264
259
|
}
|
|
265
260
|
}
|
|
266
261
|
|
|
@@ -348,6 +343,7 @@ function renderTable(results, pendingPings, frame, cursor = null, sortColumn = '
|
|
|
348
343
|
: chalk.dim(`next ping ${secondsUntilNext}s`)
|
|
349
344
|
|
|
350
345
|
// π Mode badge shown in header so user knows what Enter will do
|
|
346
|
+
// π Now includes key hint for mode toggle
|
|
351
347
|
let modeBadge
|
|
352
348
|
if (mode === 'openclaw') {
|
|
353
349
|
modeBadge = chalk.bold.rgb(255, 100, 50)(' [π¦ OpenClaw]')
|
|
@@ -356,6 +352,9 @@ function renderTable(results, pendingPings, frame, cursor = null, sortColumn = '
|
|
|
356
352
|
} else {
|
|
357
353
|
modeBadge = chalk.bold.rgb(0, 200, 255)(' [π» CLI]')
|
|
358
354
|
}
|
|
355
|
+
|
|
356
|
+
// π Add mode toggle hint
|
|
357
|
+
const modeHint = chalk.dim.yellow(' (Z to toggle)')
|
|
359
358
|
|
|
360
359
|
// π Tier filter badge shown when filtering is active
|
|
361
360
|
let tierBadge = ''
|
|
@@ -379,7 +378,7 @@ function renderTable(results, pendingPings, frame, cursor = null, sortColumn = '
|
|
|
379
378
|
|
|
380
379
|
const lines = [
|
|
381
380
|
'',
|
|
382
|
-
` ${chalk.bold('β‘ Free Coding Models')} ${chalk.dim('v' + LOCAL_VERSION)}${modeBadge}${tierBadge} ` +
|
|
381
|
+
` ${chalk.bold('β‘ Free Coding Models')} ${chalk.dim('v' + LOCAL_VERSION)}${modeBadge}${modeHint}${tierBadge} ` +
|
|
383
382
|
chalk.greenBright(`β
${up}`) + chalk.dim(' up ') +
|
|
384
383
|
chalk.yellow(`β± ${timeout}`) + chalk.dim(' timeout ') +
|
|
385
384
|
chalk.red(`β ${down}`) + chalk.dim(' down ') +
|
|
@@ -565,7 +564,7 @@ function renderTable(results, pendingPings, frame, cursor = null, sortColumn = '
|
|
|
565
564
|
: mode === 'opencode-desktop'
|
|
566
565
|
? chalk.rgb(0, 200, 255)('EnterβOpenDesktop')
|
|
567
566
|
: chalk.rgb(0, 200, 255)('EnterβOpenCode')
|
|
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`))
|
|
567
|
+
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 β’ Z Mode β’ Ctrl+C Exit`))
|
|
569
568
|
lines.push('')
|
|
570
569
|
lines.push(chalk.dim(' made with ') + 'π©·' + chalk.dim(' by vava-nessa β’ ') + chalk.dim.underline('https://github.com/vava-nessa/free-coding-models'))
|
|
571
570
|
lines.push('')
|
|
@@ -994,15 +993,8 @@ function filterByTierOrExit(results, tierLetter) {
|
|
|
994
993
|
}
|
|
995
994
|
|
|
996
995
|
async function main() {
|
|
997
|
-
// π
|
|
998
|
-
|
|
999
|
-
let apiKey = parsed.apiKey
|
|
1000
|
-
const { bestMode, fiableMode, openCodeMode, openCodeDesktopMode, openClawMode, tierFilter } = parsed
|
|
1001
|
-
|
|
1002
|
-
// π Priority: CLI arg > env var > saved config > wizard
|
|
1003
|
-
if (!apiKey) {
|
|
1004
|
-
apiKey = process.env.NVIDIA_API_KEY || loadApiKey()
|
|
1005
|
-
}
|
|
996
|
+
// π Simple CLI without flags - just API key handling
|
|
997
|
+
let apiKey = process.env.NVIDIA_API_KEY || loadApiKey()
|
|
1006
998
|
|
|
1007
999
|
if (!apiKey) {
|
|
1008
1000
|
apiKey = await promptApiKey()
|
|
@@ -1015,48 +1007,17 @@ async function main() {
|
|
|
1015
1007
|
}
|
|
1016
1008
|
}
|
|
1017
1009
|
|
|
1018
|
-
// π
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
}
|
|
1010
|
+
// π Skip update check during development to avoid blocking menus
|
|
1011
|
+
// π In production, this will work correctly when versions are published
|
|
1012
|
+
const latestVersion = null // Skip update check for now
|
|
1022
1013
|
|
|
1023
|
-
// π
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
// π Determine active mode:
|
|
1027
|
-
// --opencode β opencode
|
|
1028
|
-
// --openclaw β openclaw
|
|
1029
|
-
// neither β show interactive startup menu
|
|
1030
|
-
let mode
|
|
1031
|
-
if (openClawMode) {
|
|
1032
|
-
mode = 'openclaw'
|
|
1033
|
-
} else if (openCodeDesktopMode) {
|
|
1034
|
-
mode = 'opencode-desktop'
|
|
1035
|
-
} else if (openCodeMode) {
|
|
1036
|
-
mode = 'opencode'
|
|
1037
|
-
} else {
|
|
1038
|
-
// π No mode flag given β ask user with the startup menu
|
|
1039
|
-
mode = await promptModeSelection(latestVersion)
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
// π Handle "update now" selection from the menu
|
|
1043
|
-
if (mode === 'update') {
|
|
1044
|
-
runUpdate(latestVersion)
|
|
1045
|
-
}
|
|
1014
|
+
// π Default mode: OpenCode CLI
|
|
1015
|
+
let mode = 'opencode'
|
|
1046
1016
|
|
|
1047
|
-
// π
|
|
1048
|
-
|
|
1049
|
-
const { exec } = await import('child_process')
|
|
1050
|
-
exec('open https://github.com/vava-nessa/free-coding-models/releases')
|
|
1051
|
-
console.log(chalk.dim(' π Opening changelogs in browserβ¦'))
|
|
1052
|
-
process.exit(0)
|
|
1053
|
-
}
|
|
1017
|
+
// π AUTO-UPDATE: Disabled during development
|
|
1018
|
+
// π Will be re-enabled when versions are properly published
|
|
1054
1019
|
|
|
1055
|
-
// π
|
|
1056
|
-
if (latestVersion && (openCodeMode || openCodeDesktopMode || openClawMode)) {
|
|
1057
|
-
console.log(chalk.red(` β New version available (v${latestVersion}), please run npm i -g free-coding-models to install`))
|
|
1058
|
-
console.log()
|
|
1059
|
-
}
|
|
1020
|
+
// π This section is now handled by the update notification menu above
|
|
1060
1021
|
|
|
1061
1022
|
// π Create results array with all models initially visible
|
|
1062
1023
|
let results = MODELS.map(([modelId, label, tier], i) => ({
|
|
@@ -1067,25 +1028,7 @@ async function main() {
|
|
|
1067
1028
|
hidden: false, // π Simple flag to hide/show models
|
|
1068
1029
|
}))
|
|
1069
1030
|
|
|
1070
|
-
// π
|
|
1071
|
-
if (bestMode) {
|
|
1072
|
-
results.forEach(r => {
|
|
1073
|
-
r.hidden = !(r.tier === 'S+' || r.tier === 'S' || r.tier === 'A+')
|
|
1074
|
-
})
|
|
1075
|
-
}
|
|
1076
|
-
|
|
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
|
|
1079
|
-
if (tierFilter) {
|
|
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
|
-
})
|
|
1088
|
-
}
|
|
1031
|
+
// π No initial filters - all models visible by default
|
|
1089
1032
|
|
|
1090
1033
|
// π Add interactive selection state - cursor index and user's choice
|
|
1091
1034
|
// π sortColumn: 'rank'|'tier'|'origin'|'model'|'ping'|'avg'|'status'|'verdict'|'uptime'
|
|
@@ -1102,9 +1045,7 @@ async function main() {
|
|
|
1102
1045
|
sortDirection: 'asc',
|
|
1103
1046
|
pingInterval: PING_INTERVAL, // π Track current interval for W/X keys
|
|
1104
1047
|
lastPingTime: Date.now(), // π Track when last ping cycle started
|
|
1105
|
-
fiableMode, // π Pass fiable mode to state
|
|
1106
1048
|
mode, // π 'opencode' or 'openclaw' β controls Enter action
|
|
1107
|
-
tierFilter: tierFilter || null, // π Track current tier filter (null if no --tier flag)
|
|
1108
1049
|
}
|
|
1109
1050
|
|
|
1110
1051
|
// π Enter alternate screen β animation runs here, zero scrollback pollution
|
|
@@ -1120,17 +1061,14 @@ async function main() {
|
|
|
1120
1061
|
process.on('SIGINT', () => exit(0))
|
|
1121
1062
|
process.on('SIGTERM', () => exit(0))
|
|
1122
1063
|
|
|
1123
|
-
// π
|
|
1124
|
-
// π This preserves all ping history by merging with existing results
|
|
1064
|
+
// π No tier filtering by default - all models visible
|
|
1125
1065
|
function applyTierFilter() {
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
// π Simple approach: just update hidden flag on existing results
|
|
1066
|
+
// π All models visible by default
|
|
1129
1067
|
state.results.forEach(r => {
|
|
1130
|
-
r.hidden =
|
|
1068
|
+
r.hidden = false
|
|
1131
1069
|
})
|
|
1132
1070
|
|
|
1133
|
-
return state.results
|
|
1071
|
+
return state.results
|
|
1134
1072
|
}
|
|
1135
1073
|
|
|
1136
1074
|
// π Setup keyboard input for interactive selection during pings
|
|
@@ -1169,22 +1107,15 @@ async function main() {
|
|
|
1169
1107
|
state.pingInterval = Math.min(60000, state.pingInterval + 1000)
|
|
1170
1108
|
}
|
|
1171
1109
|
|
|
1172
|
-
// π Tier filtering
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
const
|
|
1177
|
-
const
|
|
1178
|
-
|
|
1179
|
-
state.
|
|
1180
|
-
|
|
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)
|
|
1110
|
+
// π Tier filtering removed for simplicity - all models visible by default
|
|
1111
|
+
|
|
1112
|
+
// π Mode toggle key: Z = cycle through modes (CLI β Desktop β OpenClaw)
|
|
1113
|
+
if (key.name === 'z') {
|
|
1114
|
+
const modeOrder = ['opencode', 'opencode-desktop', 'openclaw']
|
|
1115
|
+
const currentIndex = modeOrder.indexOf(state.mode)
|
|
1116
|
+
const nextIndex = (currentIndex + 1) % modeOrder.length
|
|
1117
|
+
state.mode = modeOrder[nextIndex]
|
|
1118
|
+
return
|
|
1188
1119
|
}
|
|
1189
1120
|
|
|
1190
1121
|
if (key.name === 'x') {
|
|
@@ -1260,10 +1191,10 @@ async function main() {
|
|
|
1260
1191
|
// π Animation loop: clear alt screen + redraw table at FPS with cursor
|
|
1261
1192
|
const ticker = setInterval(() => {
|
|
1262
1193
|
state.frame++
|
|
1263
|
-
|
|
1194
|
+
process.stdout.write(ALT_CLEAR + renderTable(state.results, state.pendingPings, state.frame, state.cursor, state.sortColumn, state.sortDirection, state.pingInterval, state.lastPingTime, state.mode, null))
|
|
1264
1195
|
}, Math.round(1000 / FPS))
|
|
1265
1196
|
|
|
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,
|
|
1197
|
+
process.stdout.write(ALT_CLEAR + renderTable(state.results, state.pendingPings, state.frame, state.cursor, state.sortColumn, state.sortDirection, state.pingInterval, state.lastPingTime, state.mode, null))
|
|
1267
1198
|
|
|
1268
1199
|
// ββ Continuous ping loop β ping all models every N seconds forever ββββββββββ
|
|
1269
1200
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "free-coding-models",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.23",
|
|
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",
|