free-coding-models 0.1.48 โ†’ 0.1.49

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 CHANGED
@@ -61,7 +61,8 @@
61
61
  - **๐Ÿ’ป OpenCode integration** โ€” Auto-detects NIM setup, sets model as default, launches OpenCode
62
62
  - **๐Ÿฆž OpenClaw integration** โ€” Sets selected model as default provider in `~/.openclaw/openclaw.json`
63
63
  - **๐ŸŽจ Clean output** โ€” Zero scrollback pollution, interface stays open until Ctrl+C
64
- - **๐Ÿ“ถ Status indicators** โ€” UP โœ… ยท Timeout โณ ยท Overloaded ๐Ÿ”ฅ ยท Not Found ๐Ÿšซ
64
+ - **๐Ÿ“ถ Status indicators** โ€” UP โœ… ยท No Key ๐Ÿ”‘ ยท Timeout โณ ยท Overloaded ๐Ÿ”ฅ ยท Not Found ๐Ÿšซ
65
+ - **๐Ÿ” Keyless latency** โ€” Models are pinged even without an API key โ€” a `๐Ÿ”‘ NO KEY` status confirms the server is reachable with real latency shown, so you can compare providers before committing to a key
65
66
  - **๐Ÿท Tier filtering** โ€” Filter models by tier letter (S, A, B, C) with `--tier` flag or dynamically with `T` key
66
67
 
67
68
  ---
@@ -78,7 +79,7 @@ Before using `free-coding-models`, make sure you have:
78
79
  3. **OpenCode** *(optional)* โ€” [Install OpenCode](https://github.com/opencode-ai/opencode) to use the OpenCode integration
79
80
  4. **OpenClaw** *(optional)* โ€” [Install OpenClaw](https://openclaw.ai) to use the OpenClaw integration
80
81
 
81
- > ๐Ÿ’ก **Tip:** You don't need all three providers. One key is enough to get started. Add more later via the Settings screen (`P` key).
82
+ > ๐Ÿ’ก **Tip:** You don't need all three providers. One key is enough to get started. Add more later via the Settings screen (`P` key). Models without a key still show real latency (`๐Ÿ”‘ NO KEY`) so you can evaluate providers before signing up.
82
83
 
83
84
  ---
84
85
 
@@ -574,7 +574,7 @@ function renderTable(results, pendingPings, frame, cursor = null, sortColumn = '
574
574
  : chalk.dim(ctxRaw.padEnd(W_CTX))
575
575
 
576
576
  // ๐Ÿ“– Latest ping - pings are objects: { ms, code }
577
- // ๐Ÿ“– Only show response time for successful pings, "โ€”" for errors (error code is in Status column)
577
+ // ๐Ÿ“– Show response time for 200 (success) and 401 (no-auth but server is reachable)
578
578
  const latestPing = r.pings.length > 0 ? r.pings[r.pings.length - 1] : null
579
579
  let pingCell
580
580
  if (!latestPing) {
@@ -583,6 +583,9 @@ function renderTable(results, pendingPings, frame, cursor = null, sortColumn = '
583
583
  // ๐Ÿ“– Success - show response time
584
584
  const str = String(latestPing.ms).padEnd(W_PING)
585
585
  pingCell = latestPing.ms < 500 ? chalk.greenBright(str) : latestPing.ms < 1500 ? chalk.yellow(str) : chalk.red(str)
586
+ } else if (latestPing.code === '401') {
587
+ // ๐Ÿ“– 401 = no API key but server IS reachable โ€” still show latency in dim
588
+ pingCell = chalk.dim(String(latestPing.ms).padEnd(W_PING))
586
589
  } else {
587
590
  // ๐Ÿ“– Error or timeout - show "โ€”" (error code is already in Status column)
588
591
  pingCell = chalk.dim('โ€”'.padEnd(W_PING))
@@ -601,7 +604,11 @@ function renderTable(results, pendingPings, frame, cursor = null, sortColumn = '
601
604
  // ๐Ÿ“– Status column - build plain text with emoji, pad, then colorize
602
605
  // ๐Ÿ“– Different emojis for different error codes
603
606
  let statusText, statusColor
604
- if (r.status === 'pending') {
607
+ if (r.status === 'noauth') {
608
+ // ๐Ÿ“– Server responded but needs an API key โ€” shown dimly since it IS reachable
609
+ statusText = `๐Ÿ”‘ NO KEY`
610
+ statusColor = (s) => chalk.dim(s)
611
+ } else if (r.status === 'pending') {
605
612
  statusText = `${FRAMES[frame % FRAMES.length]} wait`
606
613
  statusColor = (s) => chalk.dim.yellow(s)
607
614
  } else if (r.status === 'up') {
@@ -718,14 +725,19 @@ function renderTable(results, pendingPings, frame, cursor = null, sortColumn = '
718
725
 
719
726
  // ๐Ÿ“– ping: Send a single chat completion request to measure model availability and latency.
720
727
  // ๐Ÿ“– url param is the provider's endpoint URL โ€” differs per provider (NIM, Groq, Cerebras).
728
+ // ๐Ÿ“– apiKey can be null โ€” in that case no Authorization header is sent.
729
+ // ๐Ÿ“– A 401 response still tells us the server is UP and gives us real latency.
721
730
  async function ping(apiKey, modelId, url) {
722
731
  const ctrl = new AbortController()
723
732
  const timer = setTimeout(() => ctrl.abort(), PING_TIMEOUT)
724
733
  const t0 = performance.now()
725
734
  try {
735
+ // ๐Ÿ“– Only attach Authorization header when a key is available
736
+ const headers = { 'Content-Type': 'application/json' }
737
+ if (apiKey) headers['Authorization'] = `Bearer ${apiKey}`
726
738
  const resp = await fetch(url, {
727
739
  method: 'POST', signal: ctrl.signal,
728
- headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
740
+ headers,
729
741
  body: JSON.stringify({ model: modelId, messages: [{ role: 'user', content: 'hi' }], max_tokens: 1 }),
730
742
  })
731
743
  return { code: String(resp.status), ms: Math.round(performance.now() - t0) }
@@ -1524,6 +1536,16 @@ async function main() {
1524
1536
  results.forEach((r, i) => { r.idx = i + 1 })
1525
1537
  state.results = results
1526
1538
  adjustScrollOffset(state)
1539
+ // ๐Ÿ“– Re-ping all models that were 'noauth' (got 401 without key) but now have a key
1540
+ // ๐Ÿ“– This makes the TUI react immediately when a user adds an API key in settings
1541
+ state.results.forEach(r => {
1542
+ if (r.status === 'noauth' && getApiKey(state.config, r.providerKey)) {
1543
+ r.status = 'pending'
1544
+ r.pings = []
1545
+ r.httpCode = null
1546
+ pingModel(r).catch(() => {})
1547
+ }
1548
+ })
1527
1549
  return
1528
1550
  }
1529
1551
 
@@ -1707,8 +1729,9 @@ async function main() {
1707
1729
 
1708
1730
  // ๐Ÿ“– Single ping function that updates result
1709
1731
  // ๐Ÿ“– Uses per-provider API key and URL from sources.js
1732
+ // ๐Ÿ“– If no API key is configured, pings without auth โ€” a 401 still tells us latency + server is up
1710
1733
  const pingModel = async (r) => {
1711
- const providerApiKey = getApiKey(state.config, r.providerKey) ?? apiKey
1734
+ const providerApiKey = getApiKey(state.config, r.providerKey) ?? null
1712
1735
  const providerUrl = sources[r.providerKey]?.url ?? sources.nvidia.url
1713
1736
  const { code, ms } = await ping(providerApiKey, r.modelId, providerUrl)
1714
1737
 
@@ -1722,6 +1745,11 @@ async function main() {
1722
1745
  r.status = 'up'
1723
1746
  } else if (code === '000') {
1724
1747
  r.status = 'timeout'
1748
+ } else if (code === '401') {
1749
+ // ๐Ÿ“– 401 = server is reachable but no API key set (or wrong key)
1750
+ // ๐Ÿ“– Treated as 'noauth' โ€” server is UP, latency is real, just needs a key
1751
+ r.status = 'noauth'
1752
+ r.httpCode = code
1725
1753
  } else {
1726
1754
  r.status = 'down'
1727
1755
  r.httpCode = code
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-coding-models",
3
- "version": "0.1.48",
3
+ "version": "0.1.49",
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",