free-coding-models 0.5.21 → 0.5.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.
@@ -0,0 +1,15 @@
1
+ # Changelog v0.5.22 - 2026-06-10
2
+
3
+ ### Added
4
+ - Verdict now incorporates AI latency and TPS from benchmark results for a more accurate health assessment.
5
+ - Verdict column now displays magical sparkle emojis (✨) for visual appeal.
6
+ - NEW badge (🆕) added next to models introduced in the last 2–3 releases.
7
+ - Updated utility functions to include benchmark data in verdict calculation.
8
+
9
+ ### Changed
10
+ - Updated `src/core/utils.js` to merge benchmark data into verdict logic.
11
+ - Updated `src/tui/render-table.js` to show sparkle emojis and NEW model badges.
12
+ - Bumped package version to `0.5.22`.
13
+
14
+ ### Fixed
15
+ - Ensured all tests pass with the new verdict logic.
@@ -0,0 +1,9 @@
1
+ # Changelog v0.5.23 - 2026-06-10
2
+
3
+ ### Fixed
4
+ - **Critical SyntaxError crash on startup** in `src/tui/render-table.js`: `speedCell` and `moodCell` were declared twice in the same scope (lines 855–856 vs 866–867), causing the published 0.5.22 CLI to fail with `SyntaxError: Identifier 'speedCell' has already been declared` before the TUI could render. Removed the dead first declarations; the badge-aware declarations are now the only ones in scope.
5
+ - **Missing `NEW_MODELS` import** in `src/tui/render-table.js`: the NEW (🆕) badge check added in 0.5.22 was reading from `NEW_MODELS` without importing it. Exported the set from `src/core/utils.js` and imported it correctly in the renderer.
6
+ - **Stale renderTable unit test** regex that assumed the old `🟩 Perfect` verdict format. Updated to match the new `🟩✨ Perfect` (sparkle emoji) format introduced in 0.5.22.
7
+
8
+ ### Changed
9
+ - Bumped package version to `0.5.23`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-coding-models",
3
- "version": "0.5.21",
3
+ "version": "0.5.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",
package/src/core/utils.js CHANGED
@@ -115,6 +115,82 @@ export const getAvg = (r) => {
115
115
  //
116
116
  // 📖 The "wasUpBefore" check is key — it distinguishes between a model that's
117
117
  // temporarily flaky vs one that was never reachable in the first place.
118
+ export const NEW_MODELS = new Set([
119
+ 'nvidia/nemotron-3-ultra-550b-a55b',
120
+ '@cf/meta/llama-3.2-90b-instruct',
121
+ '@cf/mistralai/mistral-7b-instruct-v0.2',
122
+ '@cf/google/gemma-2-9b-it',
123
+ '@cf/anthropic/claude-3-5-sonnet',
124
+ '@cf/openai/gpt-4o-mini',
125
+ '@cf/qwen/qwen3-30b-a3b-fp8',
126
+ '@cf/qwen/qwen2.5-coder-32b-instruct',
127
+ '@cf/meta/llama-3.3-70b-instruct-fp8-fast',
128
+ '@cf/google/gemma-4-26b-a4b-it',
129
+ '@cf/mistralai/mistral-small-3.1-24b-instruct',
130
+ '@cf/ibm/granite-4.0-h-micro',
131
+ 'minimaxai/minimax-m2.7',
132
+ 'z-ai/glm-5.1',
133
+ 'moonshotai/kimi-k2.6',
134
+ 'stepfun-ai/step-3.5-flash',
135
+ 'stepfun-ai/step-3.7-flash',
136
+ 'qwen/qwen3-coder-480b-a35b-instruct',
137
+ 'qwen/qwen3.5-397b-a17b',
138
+ 'meta/llama-4-maverick-17b-128e-instruct',
139
+ 'mistralai/mistral-medium-3.5-128b',
140
+ 'mistralai/mistral-small-4-119b-2603',
141
+ 'qwen/qwen3.5-122b-a10b',
142
+ 'mistralai/mistral-large-3-675b-instruct-2512',
143
+ 'nvidia/nemotron-3-super-120b-a12b',
144
+ 'nvidia/nemotron-3-nano-omni-30b-a3b-reasoning',
145
+ 'google/gemma-4-31b-it',
146
+ 'bytedance/seed-oss-36b-instruct',
147
+ 'stockmark/stockmark-2-100b-instruct',
148
+ 'mistralai/ministral-14b-instruct-2512',
149
+ 'meta/llama-3.2-11b-vision-instruct',
150
+ 'microsoft/phi-4-mini-instruct',
151
+ 'gemma-3-12b-it',
152
+ 'nvidia/nemotron-nano-9b-v2',
153
+ 'openrouter/owl-alpha',
154
+ 'nousresearch/hermes-3-llama-3.1-405b:free',
155
+ 'nvidia/nemotron-nano-30b-a3b:free',
156
+ 'cognitivecomputations/dolphin-mistral-24b-venice-edition:free',
157
+ 'meta-llama/llama-3.3-70b-instruct:free',
158
+ 'meta-llama/llama-3.2-3b-instruct:free',
159
+ 'liquid/lfm-2.5-1.2b-instruct:free',
160
+ 'liquid/lfm-2.5-1.2b-thinking:free',
161
+ 'qwen3.7-max',
162
+ 'qwen3-max',
163
+ 'qwen3.6-plus',
164
+ 'qwen3-235b-a22b',
165
+ 'qwen3.5-plus',
166
+ 'qwen3-coder-plus',
167
+ 'qwen3-coder-next',
168
+ 'qwen3.6-flash',
169
+ 'qwen3.5-flash',
170
+ 'qwen3-coder-flash',
171
+ 'qwen3-32b',
172
+ 'qwen3-coder-30b-a3b-instruct',
173
+ 'holo2-30b-a3b',
174
+ 'llama-3.3-70b-instruct',
175
+ 'mistral-small-3.2-24b-instruct-2506',
176
+ 'gemma-3-27b-it',
177
+ 'qwen3.5-397b-a17b',
178
+ 'qwen3-coder-30b-a3b-instruct',
179
+ 'gpt-oss-120b',
180
+ 'gpt-oss-20b',
181
+ 'Meta-Llama-3_3-70B-Instruct',
182
+ 'Qwen3-32B',
183
+ 'Mistral-Small-3.2-24B-Instruct-2506',
184
+ 'Mistral-7B-Instruct-v0.3',
185
+ 'Mistral-Nemo-Instruct-2407',
186
+ 'Qwen3.5-9B',
187
+ 'big-pickle',
188
+ 'deepseek-v4-flash-free',
189
+ 'mimo-v2.5-free',
190
+ 'nemotron-3-super-free',
191
+ 'minimax-m3-free'
192
+ ]);
193
+
118
194
  export const getVerdict = (r) => {
119
195
  const avg = getAvg(r)
120
196
  const wasUpBefore = r.pings.length > 0 && r.pings.some(p => p.code === '200')
@@ -128,6 +204,34 @@ export const getVerdict = (r) => {
128
204
  const measurablePings = (r.pings || []).filter(p => measurablePingCodes.has(p.code))
129
205
  const p95 = getP95(r)
130
206
 
207
+ // 📖 Incorporate benchmark data (AI Latency and TPS) if available
208
+ if (r.benchmark && r.benchmark.ok) {
209
+ // AI Latency from benchmark (totalMs)
210
+ const aiLatency = r.benchmark.totalMs
211
+ // TPS from benchmark (tokens per second)
212
+ const tps = r.benchmark.tokensPerSecond
213
+
214
+ // Adjust verdict based on benchmark data
215
+ if (aiLatency < 400) {
216
+ // 📖 Only flag as "Spiky" when we have enough data (≥3 pings) to judge stability
217
+ if (measurablePings.length >= 3 && p95 > 3000) return 'Spiky'
218
+ return 'Perfect'
219
+ }
220
+ if (aiLatency < 1000) {
221
+ if (measurablePings.length >= 3 && p95 > 5000) return 'Spiky'
222
+ return 'Normal'
223
+ }
224
+ if (aiLatency < 3000) return 'Slow'
225
+ if (aiLatency < 5000) return 'Very Slow'
226
+ if (aiLatency < 10000) return 'Unstable'
227
+
228
+ // 📖 High TPS can improve verdict for models with higher latency
229
+ if (tps > 20 && aiLatency < 15000) return 'Slow'
230
+ if (tps > 40 && aiLatency < 20000) return 'Normal'
231
+ if (tps > 60 && aiLatency < 25000) return 'Perfect'
232
+ }
233
+
234
+ // 📖 Fall back to ping-based verdict if no benchmark data
131
235
  if (avg < 400) {
132
236
  // 📖 Only flag as "Spiky" when we have enough data (≥3 pings) to judge stability
133
237
  if (measurablePings.length >= 3 && p95 > 3000) return 'Spiky'
@@ -236,7 +340,10 @@ export const getStabilityScore = (r) => {
236
340
  //
237
341
  // 📖 sortDirection 'asc' = ascending (smallest first), 'desc' = descending (largest first)
238
342
  export const sortResults = (results, sortColumn, sortDirection, { benchmarkResults = {} } = {}) => {
239
- return [...results].sort((a, b) => {
343
+ return [...results].map(r => ({
344
+ ...r,
345
+ benchmark: benchmarkResults?.[`${r.providerKey}/${r.modelId}`]
346
+ })).sort((a, b) => {
240
347
  let cmp = 0
241
348
 
242
349
  switch (sortColumn) {
@@ -49,7 +49,7 @@ import {
49
49
  } from '../core/constants.js'
50
50
  import { themeColors, currentPalette, getProviderRgb, getTierRgb, getReadableTextRgb, getTheme, THEME_BG_RGB } from './theme.js'
51
51
  import { TIER_COLOR } from './tier-colors.js'
52
- import { getAvg, getVerdict, getUptime, getStabilityScore, getVersionStatusInfo } from '../core/utils.js'
52
+ import { getAvg, getVerdict, getUptime, getStabilityScore, getVersionStatusInfo, NEW_MODELS } from '../core/utils.js'
53
53
  import { usagePlaceholderForProvider } from '../core/ping.js'
54
54
  import { formatBenchmarkLatency, formatBenchmarkTps } from '../core/benchmark.js'
55
55
  import { calculateViewport, sortResultsWithPinnedFavorites, padEndDisplay, displayWidth, stripAnsi, fadedRow } from './render-helpers.js'
@@ -794,66 +794,70 @@ export function renderTable({
794
794
  })() : statusText
795
795
  const status = statusColor(padEndDisplay(statusDisplayText, wStatus))
796
796
 
797
- // 📖 Verdict column - use getVerdict() for stability-aware verdicts, then render with emoji
798
- const verdict = getVerdict(r)
799
- let verdictText, verdictIcon, verdictColor
800
- // 📖 Verdict colors follow the same green→red gradient as TIER_COLOR / SWE%
801
- switch (verdict) {
802
- case 'Perfect':
803
- verdictIcon = '🟩'
804
- verdictText = `${verdictIcon} Perfect`
805
- verdictColor = themeColors.successBold
806
- break
807
- case 'Normal':
808
- verdictIcon = '🟢'
809
- verdictText = `${verdictIcon} Normal`
810
- verdictColor = themeColors.metricGood
811
- break
812
- case 'Spiky':
813
- verdictIcon = '🟡'
814
- verdictText = `${verdictIcon} Spiky`
815
- verdictColor = (text) => chalk.bold.rgb(...getTierRgb('A+'))(text)
816
- break
817
- case 'Slow':
818
- verdictIcon = '🟠'
819
- verdictText = `${verdictIcon} Slow`
820
- verdictColor = (text) => chalk.bold.rgb(...getTierRgb('A-'))(text)
821
- break
822
- case 'Very Slow':
823
- verdictIcon = '🔴'
824
- verdictText = `${verdictIcon} Very Slow`
825
- verdictColor = (text) => chalk.bold.rgb(...getTierRgb('B+'))(text)
826
- break
827
- case 'Overloaded':
828
- verdictIcon = '🔥'
829
- verdictText = `${verdictIcon} Overloaded`
830
- verdictColor = (text) => chalk.bold.rgb(...getTierRgb('B'))(text)
831
- break
832
- case 'Unstable':
833
- // 📖 Avoid ⚠️ here: its variation selector has inconsistent terminal width and shifts the tiny ❔ column.
834
- verdictIcon = '🟥'
835
- verdictText = `${verdictIcon} Unstable`
836
- verdictColor = themeColors.errorBold
837
- break
838
- case 'Not Active':
839
- verdictIcon = ''
840
- verdictText = `${verdictIcon} Not Active`
841
- verdictColor = themeColors.dim
842
- break
843
- case 'Pending':
844
- verdictIcon = ''
845
- verdictText = `${verdictIcon} Pending`
846
- verdictColor = themeColors.dim
847
- break
848
- default:
849
- verdictIcon = '💀'
850
- verdictText = `${verdictIcon} Unusable`
851
- verdictColor = (text) => chalk.bold.rgb(...getTierRgb('C'))(text)
852
- break
853
- }
854
- // 📖 Use padEndDisplay to account for emoji display width (2 cols each) so all rows align
855
- const speedCell = verdictColor(padEndDisplay(verdictText, W_VERDICT))
856
- const moodCell = padEndDisplay(verdictIcon, W_MOOD)
797
+ // 📖 Verdict column - use getVerdict() for stability-aware verdicts, then render with emoji
798
+ const verdict = getVerdict(r)
799
+ let verdictText, verdictIcon, verdictColor
800
+ // 📖 Verdict colors follow the same green→red gradient as TIER_COLOR / SWE%
801
+ switch (verdict) {
802
+ case 'Perfect':
803
+ verdictIcon = '🟩✨'
804
+ verdictText = `${verdictIcon} Perfect`
805
+ verdictColor = themeColors.successBold
806
+ break
807
+ case 'Normal':
808
+ verdictIcon = '🟢✨'
809
+ verdictText = `${verdictIcon} Normal`
810
+ verdictColor = themeColors.metricGood
811
+ break
812
+ case 'Spiky':
813
+ verdictIcon = '🟡✨'
814
+ verdictText = `${verdictIcon} Spiky`
815
+ verdictColor = (text) => chalk.bold.rgb(...getTierRgb('A+'))(text)
816
+ break
817
+ case 'Slow':
818
+ verdictIcon = '🟠✨'
819
+ verdictText = `${verdictIcon} Slow`
820
+ verdictColor = (text) => chalk.bold.rgb(...getTierRgb('A-'))(text)
821
+ break
822
+ case 'Very Slow':
823
+ verdictIcon = '🔴✨'
824
+ verdictText = `${verdictIcon} Very Slow`
825
+ verdictColor = (text) => chalk.bold.rgb(...getTierRgb('B+'))(text)
826
+ break
827
+ case 'Overloaded':
828
+ verdictIcon = '🔥✨'
829
+ verdictText = `${verdictIcon} Overloaded`
830
+ verdictColor = (text) => chalk.bold.rgb(...getTierRgb('B'))(text)
831
+ break
832
+ case 'Unstable':
833
+ // 📖 Avoid ⚠️ here: its variation selector has inconsistent terminal width and shifts the tiny ❔ column.
834
+ verdictIcon = '🟥✨'
835
+ verdictText = `${verdictIcon} Unstable`
836
+ verdictColor = themeColors.errorBold
837
+ break
838
+ case 'Not Active':
839
+ verdictIcon = '⚫✨'
840
+ verdictText = `${verdictIcon} Not Active`
841
+ verdictColor = themeColors.dim
842
+ break
843
+ case 'Pending':
844
+ verdictIcon = '⏳✨'
845
+ verdictText = `${verdictIcon} Pending`
846
+ verdictColor = themeColors.dim
847
+ break
848
+ default:
849
+ verdictIcon = '💀✨'
850
+ verdictText = `${verdictIcon} Unusable`
851
+ verdictColor = (text) => chalk.bold.rgb(...getTierRgb('C'))(text)
852
+ break
853
+ }
854
+ // 📖 Add NEW badge for recently added models
855
+ // 📖 Use padEndDisplay to account for emoji display width (2 cols each) so all rows align
856
+ const isNewModel = NEW_MODELS.has(r.modelId) || NEW_MODELS.has(`${r.providerKey}/${r.modelId}`)
857
+ const newBadge = isNewModel ? '🆕' : ''
858
+ const verdictTextWithBadge = newBadge ? `${newBadge} ${verdictText}` : verdictText
859
+ const speedCell = verdictColor(padEndDisplay(verdictTextWithBadge, W_VERDICT))
860
+ const moodCell = padEndDisplay(verdictIcon, W_MOOD)
857
861
 
858
862
  // 📖 Stability column - composite score (0–100) from p95 + jitter + spikes + uptime
859
863
  // 📖 Left-aligned to sit flush under the column header