free-coding-models 0.1.22 β†’ 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.
@@ -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(' πŸ“ Please restart free-coding-models to use the new version.'))
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(0)
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
- // ─── Startup mode selection menu ──────────────────────────────────────────────
169
- // πŸ“– Shown at startup when neither --opencode nor --openclaw flag is given.
170
- // πŸ“– Simple arrow-key selector in normal terminal (not alt screen).
171
- // πŸ“– Returns 'opencode', 'openclaw', or 'update'.
172
- async function promptModeSelection(latestVersion) {
173
- const options = [
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 local CHANGELOG.md file',
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
- // πŸ“– Render the menu to stdout (clear + redraw)
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
- if (latestVersion) {
216
- console.log(chalk.red(` ⚠ New version available (v${latestVersion}), please run npm i -g free-coding-models to install`))
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.yellow.bold(' ⚠️ Warning: ') + chalk.yellow('Small terminals may break the layout β€” maximize your window for best results!'))
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
- const desc = chalk.dim(' ' + options[i].description)
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
- console.log(chalk.dim(' ↑↓ Navigate β€’ Enter Select β€’ Ctrl+C Exit'))
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
- process.exit(0)
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
- const choices = ['opencode', 'opencode-desktop', 'openclaw']
261
- if (latestVersion) choices.push('update')
262
- if (latestVersion) choices.push('changelogs')
263
- resolve(choices[selected])
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
- // πŸ“– Parse CLI arguments using shared parseArgs utility
998
- const parsed = parseArgs(process.argv)
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,49 +1007,17 @@ async function main() {
1015
1007
  }
1016
1008
  }
1017
1009
 
1018
- // πŸ“– Handle fiable mode first (it exits after analysis)
1019
- if (fiableMode) {
1020
- await runFiableMode(apiKey)
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
- // πŸ“– Check for available update (non-blocking, 5s timeout)
1024
- const latestVersion = await checkForUpdate()
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
- // πŸ“– Handle "Read Changelogs" selection β€” open local CHANGELOG.md file
1048
- if (mode === 'changelogs') {
1049
- const { exec } = await import('child_process')
1050
- const changelogPath = join(process.cwd(), 'CHANGELOG.md')
1051
- exec(`open "${changelogPath}"`)
1052
- console.log(chalk.dim(' πŸ“‹ Opening local changelogs file…'))
1053
- process.exit(0)
1054
- }
1017
+ // πŸ“– AUTO-UPDATE: Disabled during development
1018
+ // πŸ“– Will be re-enabled when versions are properly published
1055
1019
 
1056
- // πŸ“– When using flags (--opencode/--openclaw), show update warning in terminal
1057
- if (latestVersion && (openCodeMode || openCodeDesktopMode || openClawMode)) {
1058
- console.log(chalk.red(` ⚠ New version available (v${latestVersion}), please run npm i -g free-coding-models to install`))
1059
- console.log()
1060
- }
1020
+ // πŸ“– This section is now handled by the update notification menu above
1061
1021
 
1062
1022
  // πŸ“– Create results array with all models initially visible
1063
1023
  let results = MODELS.map(([modelId, label, tier], i) => ({
@@ -1068,25 +1028,7 @@ async function main() {
1068
1028
  hidden: false, // πŸ“– Simple flag to hide/show models
1069
1029
  }))
1070
1030
 
1071
- // πŸ“– Apply filters by setting hidden flag
1072
- if (bestMode) {
1073
- results.forEach(r => {
1074
- r.hidden = !(r.tier === 'S+' || r.tier === 'S' || r.tier === 'A+')
1075
- })
1076
- }
1077
-
1078
- // πŸ“– Apply tier letter filter if --tier X was given (just sets initial state)
1079
- // πŸ“– User can still change filter with E/D keys later
1080
- if (tierFilter) {
1081
- const tierSet = TIER_LETTER_MAP[tierFilter]
1082
- if (!tierSet) {
1083
- console.error(chalk.red(` βœ– Unknown tier "${tierFilter}". Valid tiers: S, A, B, C`))
1084
- process.exit(1)
1085
- }
1086
- results.forEach(r => {
1087
- r.hidden = !tierSet.includes(r.tier)
1088
- })
1089
- }
1031
+ // πŸ“– No initial filters - all models visible by default
1090
1032
 
1091
1033
  // πŸ“– Add interactive selection state - cursor index and user's choice
1092
1034
  // πŸ“– sortColumn: 'rank'|'tier'|'origin'|'model'|'ping'|'avg'|'status'|'verdict'|'uptime'
@@ -1103,9 +1045,7 @@ async function main() {
1103
1045
  sortDirection: 'asc',
1104
1046
  pingInterval: PING_INTERVAL, // πŸ“– Track current interval for W/X keys
1105
1047
  lastPingTime: Date.now(), // πŸ“– Track when last ping cycle started
1106
- fiableMode, // πŸ“– Pass fiable mode to state
1107
1048
  mode, // πŸ“– 'opencode' or 'openclaw' β€” controls Enter action
1108
- tierFilter: tierFilter || null, // πŸ“– Track current tier filter (null if no --tier flag)
1109
1049
  }
1110
1050
 
1111
1051
  // πŸ“– Enter alternate screen β€” animation runs here, zero scrollback pollution
@@ -1121,17 +1061,14 @@ async function main() {
1121
1061
  process.on('SIGINT', () => exit(0))
1122
1062
  process.on('SIGTERM', () => exit(0))
1123
1063
 
1124
- // πŸ“– Apply tier filter based on current state.tierFilter
1125
- // πŸ“– This preserves all ping history by merging with existing results
1064
+ // πŸ“– No tier filtering by default - all models visible
1126
1065
  function applyTierFilter() {
1127
- const tierSet = state.tierFilter ? TIER_LETTER_MAP[state.tierFilter] : null
1128
-
1129
- // πŸ“– Simple approach: just update hidden flag on existing results
1066
+ // πŸ“– All models visible by default
1130
1067
  state.results.forEach(r => {
1131
- r.hidden = tierSet ? !tierSet.includes(r.tier) : false
1068
+ r.hidden = false
1132
1069
  })
1133
1070
 
1134
- return state.results // πŸ“– Return same array, just updated hidden flags
1071
+ return state.results
1135
1072
  }
1136
1073
 
1137
1074
  // πŸ“– Setup keyboard input for interactive selection during pings
@@ -1170,22 +1107,15 @@ async function main() {
1170
1107
  state.pingInterval = Math.min(60000, state.pingInterval + 1000)
1171
1108
  }
1172
1109
 
1173
- // πŸ“– Tier filtering keys: E=elevate (more restrictive), D=descend (less restrictive)
1174
- // πŸ“– Cycle through: null β†’ 'S' β†’ 'A' β†’ 'B' β†’ 'C' β†’ null (all tiers)
1175
- if (key.name === 'e') {
1176
- const tierOrder = [null, 'S', 'A', 'B', 'C']
1177
- const currentIndex = tierOrder.indexOf(state.tierFilter)
1178
- const nextIndex = (currentIndex + 1) % tierOrder.length
1179
- state.tierFilter = tierOrder[nextIndex]
1180
- state.results = applyTierFilter()
1181
- state.cursor = Math.min(state.cursor, state.results.length - 1)
1182
- } else if (key.name === 'd') {
1183
- const tierOrder = [null, 'C', 'B', 'A', 'S']
1184
- const currentIndex = tierOrder.indexOf(state.tierFilter)
1185
- const nextIndex = (currentIndex - 1 + tierOrder.length) % tierOrder.length
1186
- state.tierFilter = tierOrder[nextIndex]
1187
- state.results = applyTierFilter()
1188
- 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
1189
1119
  }
1190
1120
 
1191
1121
  if (key.name === 'x') {
@@ -1261,10 +1191,10 @@ async function main() {
1261
1191
  // πŸ“– Animation loop: clear alt screen + redraw table at FPS with cursor
1262
1192
  const ticker = setInterval(() => {
1263
1193
  state.frame++
1264
- 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))
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))
1265
1195
  }, Math.round(1000 / FPS))
1266
1196
 
1267
- 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))
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))
1268
1198
 
1269
1199
  // ── Continuous ping loop β€” ping all models every N seconds forever ──────────
1270
1200
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-coding-models",
3
- "version": "0.1.22",
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",