free-coding-models 0.5.58 → 0.5.60

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.
@@ -836,6 +836,40 @@ export function createKeyHandler(ctx) {
836
836
  state.tokenUsageError = null
837
837
  }
838
838
 
839
+ // 📖 Runtime Report overlay (t3): per-model real-world breakdown + recent calls.
840
+ // 📖 Reads the local ~/.free-coding-models/runtime-telemetry.json file directly
841
+ // 📖 (the daemon writes to the same file; the CLI doesn't need to talk to it).
842
+ async function openRuntimeReportOverlay() {
843
+ state.runtimeReportOpen = true
844
+ state.runtimeReportScrollOffset = 0
845
+ state.runtimeReportError = null
846
+ state.runtimeReportData = null
847
+ state.runtimeReportSelectedKey = null
848
+ try {
849
+ const { loadRuntimeTelemetry, getAllModelTelemetry } = await import('../core/runtime-telemetry.js')
850
+ const cache = loadRuntimeTelemetry()
851
+ const all = getAllModelTelemetry({ cache })
852
+ const entries = Object.entries(all).sort((a, b) => (b[1].totalCalls || 0) - (a[1].totalCalls || 0))
853
+ state.runtimeReportData = entries
854
+ // 📖 Pre-select the currently-focused model if it has telemetry.
855
+ const focused = state.visibleSorted?.[state.cursor]
856
+ if (focused) {
857
+ const key = `${focused.providerKey}/${focused.modelId}`
858
+ if (entries.find(([k]) => k === key)) state.runtimeReportSelectedKey = key
859
+ }
860
+ } catch (err) {
861
+ state.runtimeReportError = err?.message || 'Failed to load runtime telemetry'
862
+ }
863
+ }
864
+
865
+ function closeRuntimeReportOverlay() {
866
+ state.runtimeReportOpen = false
867
+ state.runtimeReportScrollOffset = 0
868
+ state.runtimeReportError = null
869
+ state.runtimeReportData = null
870
+ state.runtimeReportSelectedKey = null
871
+ }
872
+
839
873
 
840
874
  function cycleToolMode() {
841
875
  const modeOrder = getToolModeOrder()
@@ -1355,6 +1389,7 @@ export function createKeyHandler(ctx) {
1355
1389
  case 'sort-verdict': return setSortColumnFromCommand('verdict')
1356
1390
  case 'sort-stability': return setSortColumnFromCommand('stability')
1357
1391
  case 'sort-uptime': return setSortColumnFromCommand('uptime')
1392
+ case 'sort-realworld': return setSortColumnFromCommand('realworld')
1358
1393
  case 'open-settings': return openSettingsOverlay()
1359
1394
  case 'open-help':
1360
1395
  state.helpVisible = true
@@ -1366,6 +1401,7 @@ export function createKeyHandler(ctx) {
1366
1401
  case 'open-router-dashboard': return openRouterDashboardOverlay(state)
1367
1402
  case 'open-playground': return openPlaygroundOverlay(state)
1368
1403
  case 'open-token-usage': return openTokenUsageOverlay()
1404
+ case 'open-runtime-report': return openRuntimeReportOverlay()
1369
1405
  case 'open-install-endpoints': return openInstallEndpointsOverlay()
1370
1406
  case 'open-installed-models': return openInstalledModelsOverlay()
1371
1407
  case 'action-cycle-theme': return cycleGlobalTheme()
@@ -2120,6 +2156,42 @@ export function createKeyHandler(ctx) {
2120
2156
  return
2121
2157
  }
2122
2158
 
2159
+ // 📖 Runtime Report overlay (t3): Shift+W. Per-model real-world breakdown +
2160
+ // 📖 recent calls. Scrolling with up/down/pageup/pagedown/escape.
2161
+ if (state.runtimeReportOpen) {
2162
+ if (key.ctrl && key.name === 'c') { exit(0); return }
2163
+ const pageStep = Math.max(1, (state.terminalRows || 1) - 4)
2164
+ if (key.name === 'escape') {
2165
+ closeRuntimeReportOverlay()
2166
+ return
2167
+ }
2168
+ if (key.name === 'up' || key.name === 'k') {
2169
+ state.runtimeReportScrollOffset = Math.max(0, state.runtimeReportScrollOffset - 1)
2170
+ return
2171
+ }
2172
+ if (key.name === 'down' || key.name === 'j') {
2173
+ state.runtimeReportScrollOffset += 1
2174
+ return
2175
+ }
2176
+ if (key.name === 'pageup') {
2177
+ state.runtimeReportScrollOffset = Math.max(0, state.runtimeReportScrollOffset - pageStep)
2178
+ return
2179
+ }
2180
+ if (key.name === 'pagedown') {
2181
+ state.runtimeReportScrollOffset += pageStep
2182
+ return
2183
+ }
2184
+ if (key.name === 'home') {
2185
+ state.runtimeReportScrollOffset = 0
2186
+ return
2187
+ }
2188
+ if (key.name === 'end') {
2189
+ state.runtimeReportScrollOffset = Number.MAX_SAFE_INTEGER
2190
+ return
2191
+ }
2192
+ return
2193
+ }
2194
+
2123
2195
  // 📖 Router Onboarding overlay: shown on first launch. Y=yes enable, N=not now, Esc=cancel.
2124
2196
  if (state.routerOnboardingOpen) {
2125
2197
  if (key.ctrl && key.name === 'c') { exit(0); return }
@@ -2872,6 +2944,14 @@ export function createKeyHandler(ctx) {
2872
2944
  return
2873
2945
  }
2874
2946
 
2947
+ // 📖 Shift+W: open the Runtime Report overlay (t3) — per-model real-world
2948
+ // 📖 success rate + throughput + recent calls. See command palette
2949
+ // 📖 'open-runtime-report' for the same action.
2950
+ if (key.name === 'w' && key.shift && !key.alt && !key.ctrl && !key.meta) {
2951
+ openRuntimeReportOverlay()
2952
+ return
2953
+ }
2954
+
2875
2955
  // 📖 E cycles: Normal → Configured only → Usable only → Normal
2876
2956
  // 📖 Configured only: hides models with no key or noauth/auth_error health
2877
2957
  // 📖 Usable only: only shows models with Health UP and Verdict ≤ Slow (Perfect/Normal/Slow)
@@ -155,6 +155,10 @@ export const PROVIDER_COLOR = new Proxy({}, {
155
155
  * probeCacheMisses?: number, // t1: number of models that needed a live probe
156
156
  * probeCacheBrokenHidden?: number, // t1: number of broken models auto-hidden this session
157
157
  * showBrokenMode?: boolean, // t1: true when Shift+B has un-hidden broken models
158
+ * quota?: Record<string, { // t2: live quota from response headers
159
+ * remaining: number, limit: number, percent: number,
160
+ * source: 'header'|'endpoint', lastUpdated: number, windowType?: string,
161
+ * }>,
158
162
  * }} opts
159
163
  * @returns {string}
160
164
  */
@@ -205,6 +209,7 @@ export function renderTable({
205
209
  probeCacheMisses = 0,
206
210
  probeCacheBrokenHidden = 0,
207
211
  showBrokenMode = false,
212
+ quota = {},
208
213
  } = _) {
209
214
  // 📖 Filter out hidden models for display
210
215
  const visibleResults = results.filter(r => !r.hidden)
@@ -1219,8 +1224,27 @@ export function renderTable({
1219
1224
  probeCacheLabel = chalk.bgRgb(40, 90, 140).rgb(220, 235, 255).bold(` ${cachedTxt}${brokenTxt} `)
1220
1225
  }
1221
1226
 
1222
- // 📖 Line 3: Speed Test + Global Benchmark + Probe + Probe-cache + Last release
1223
- if (releaseLabel || speedTestLabel || globalBenchmarkLabel || probeLabel || probeCacheLabel) {
1227
+ // 📖 Passive quota chip (t2): `📊 groq 78% · sambanova 41%` (top-N depleted first).
1228
+ // 📖 Shows live rate-limit headers that the daemon / pings collected from upstream
1229
+ // 📖 responses. Zero extra network requests — see provider-quota-fetchers.js.
1230
+ // 📖 Cap at 5 providers to avoid footer bloat; pick most-depleted first.
1231
+ let quotaLabel = ''
1232
+ if (quota && typeof quota === 'object') {
1233
+ const entries = Object.entries(quota)
1234
+ .filter(([, s]) => s && typeof s.percent === 'number')
1235
+ .sort((a, b) => a[1].percent - b[1].percent) // 📖 most depleted first
1236
+ .slice(0, 5)
1237
+ if (entries.length > 0) {
1238
+ const parts = entries.map(([providerKey, snap]) => {
1239
+ const icon = snap.percent <= 10 ? '🚨' : snap.percent <= 25 ? '⚠️ ' : '📊'
1240
+ return `${icon} ${providerKey} ${snap.percent}%`
1241
+ })
1242
+ quotaLabel = chalk.bgRgb(60, 100, 60).rgb(220, 255, 220).bold(` ${parts.join(' · ')} `)
1243
+ }
1244
+ }
1245
+
1246
+ // 📖 Line 3: Speed Test + Global Benchmark + Probe + Probe-cache + Quota + Last release
1247
+ if (releaseLabel || speedTestLabel || globalBenchmarkLabel || probeLabel || probeCacheLabel || quotaLabel) {
1224
1248
  const parts = [
1225
1249
  { text: ' ', key: null },
1226
1250
  { text: speedTestLabel, key: 'a' },
@@ -1230,6 +1254,8 @@ export function renderTable({
1230
1254
  { text: probeLabel, key: null },
1231
1255
  { text: probeCacheLabel ? ' ' : '', key: null },
1232
1256
  { text: probeCacheLabel, key: null },
1257
+ { text: quotaLabel ? ' ' : '', key: null },
1258
+ { text: quotaLabel, key: null },
1233
1259
  { text: ' ', key: null },
1234
1260
  { text: releaseLabel, key: null },
1235
1261
  ]
@@ -297,6 +297,15 @@ export function createTuiState({
297
297
  probeCacheTtlMs: null, // 📖 Set in app.js from cliArgs.probeTtlMs || DEFAULT
298
298
  showBrokenMode: false,
299
299
 
300
+ // 📖 Runtime telemetry overlay state (t3): Shift+W opens the Runtime Report
301
+ // 📖 overlay. Data is loaded fresh from ~/.free-coding-models/runtime-telemetry.json
302
+ // 📖 when the overlay opens — no polling, no daemon dependency.
303
+ runtimeReportOpen: false,
304
+ runtimeReportScrollOffset: 0,
305
+ runtimeReportError: null,
306
+ runtimeReportData: null, // 📖 Array<[key, ModelTelemetry]>
307
+ runtimeReportSelectedKey: null, // 📖 Pre-selected model key
308
+
300
309
  // 📖 Header click flash animation: briefly highlights the clicked column header
301
310
  // 📖 with an inverse/bright style for ~250ms (3 frames at 12 FPS).
302
311
  headerFlashColumn: null, // 📖 Column name being flashed (null = no flash active)