free-coding-models 0.5.56 โ†’ 0.5.58

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
@@ -466,6 +466,57 @@ When a tool mode is active (via `Z`), models incompatible with that tool are hig
466
466
 
467
467
  ---
468
468
 
469
+ ## ๐Ÿง  Persistent probe cache
470
+
471
+ Every health probe result is cached to `~/.free-coding-models/probe-cache.json` for **24 hours** and **shared across all surfaces** (CLI TUI, Web Dashboard / daemon, Tauri Desktop).
472
+
473
+ - **Warm start** renders the full ranking in <500ms using the cached results, then re-pings only the models that are due (broken or past TTL).
474
+ - **Broken models** are auto-hidden across sessions โ€” a model that 401s today stays out of tomorrow's default view. Recovery is automatic: if it comes back `ok`, it un-hides on the next probe.
475
+ - **Cross-process safe**: a debounced flush + atomic rename + read-merge-write means the CLI and the daemon can share the file without clobbering each other.
476
+
477
+ ### Where the cache lives
478
+
479
+ | Env / OS | Path |
480
+ |----------|------|
481
+ | `XDG_CACHE_HOME` set | `$XDG_CACHE_HOME/free-coding-models/probe-cache.json` |
482
+ | Default (macOS / Linux) | `~/.free-coding-models/probe-cache.json` |
483
+ | Windows | `%USERPROFILE%\.free-coding-models\probe-cache.json` |
484
+
485
+ Inspect or wipe it manually any time โ€” it's plain JSON with `0600` perms.
486
+
487
+ ### CLI flags
488
+
489
+ | Flag | Effect |
490
+ |------|--------|
491
+ | `--reprobe` / `--no-cache` | Nuke the cache before this run; ping everything fresh |
492
+ | `--probe-ttl <ms>` | Override the 24h TTL (e.g. `--probe-ttl 3600000` for 1h) |
493
+ | `--show-broken` | Don't auto-hide broken models this run (one-shot override) |
494
+
495
+ ### TUI keys
496
+
497
+ | Key | Effect |
498
+ |-----|--------|
499
+ | **Shift+B** | Toggle visibility of broken models (footer chip shows `โšก N cached ยท ๐Ÿ”ด M broken`) |
500
+
501
+ ### Daemon `/stats` shape
502
+
503
+ ```json
504
+ {
505
+ "probeCache": {
506
+ "total": 191,
507
+ "ok": 178,
508
+ "broken": 13,
509
+ "freshCount": 165,
510
+ "staleCount": 13,
511
+ "dueCount": 26,
512
+ "hiddenCount": 13,
513
+ "providers": 9
514
+ }
515
+ }
516
+ ```
517
+
518
+ ---
519
+
469
520
  ## ฯ€ Pi Extension โ€” FCM-Pi โš ๏ธ BETA
470
521
 
471
522
  **FCM-Pi** is a native [Pi coding agent](https://pi.dev) extension that integrates `free-coding-models` directly into your Pi session. It stays silent by default, scans only when you run `/fcm`, and lets you explicitly hot-swap models mid-session.
@@ -678,6 +729,7 @@ See [`packages/fcm-agent-core/README.md`](./packages/fcm-agent-core/README.md) f
678
729
  - **Auto-retry** โ€” timeout models keep getting retried
679
730
  - **Mandatory self-update policy** โ€” startup checks npm for a newer FCM and installs it automatically without a prompt. If the install fails twice in a row (offline, proxy, or permissions), FCM still starts but shows a red outdated-version warning until the user retries with `Shift+U` or runs the displayed install command.
680
731
  - **Last release timestamp** โ€” light pink footer shows `Last release: Mar 27, 2026, 09:42 PM` from npm so users know how fresh the data is
732
+ - **Persistent probe-cache (t1)** โ€” every health probe result is cached to `~/.free-coding-models/probe-cache.json` for 24h. Warm starts render the full ranking in <500ms, only re-ping the models that are due (broken or TTL-expired). Broken models are auto-hidden across sessions โ€” toggle visibility with **Shift+B**. See [Persistent probe cache](#-persistent-probe-cache) below for `--reprobe`, `--probe-ttl`, `--show-broken`.
681
733
 
682
734
  ---
683
735
 
@@ -0,0 +1,20 @@
1
+ # Changelog v0.5.57 - 2026-07-25
2
+
3
+ ### Fixed
4
+ - ๐Ÿ“ฆ **`patch-openclaw.js` now finds `sources.js` in more locations** ([#35](https://github.com/vava-nessa/free-coding-models/issues/35)) โ€” when users copy just `patch-openclaw.js` to another directory (e.g. `~/.free-coding-models/`) and run it, the static `import './sources.js'` failed with `ERR_MODULE_NOT_FOUND` because ESM resolves imports relative to the importer file at parse time. Switched to a runtime search over multiple candidate paths:
5
+
6
+ 1. Same directory as the script (primary โ€” FCM source / global install layout)
7
+ 2. Parent directory (covers `tools/`-style subdirs)
8
+ 3. Two levels up
9
+ 4. Three levels up
10
+ 5. `process.cwd()` (last-resort fallback)
11
+
12
+ If none resolve, the script now prints a clear error pointing to the script's actual location and how to fix it, instead of a cryptic `ERR_MODULE_NOT_FOUND`.
13
+
14
+ ### Maintenance
15
+ - ๐Ÿงช **+1 unit test** (`test/patch-openclaw.test.js`) locking in the search order so any future reordering is caught.
16
+ - ๐Ÿงช **592/592 tests pass** (`pnpm test`).
17
+ - ๐Ÿงน `vite build` succeeds.
18
+
19
+ ### Why a top-level `await import()`?
20
+ ESM `import './sources.js'` is resolved at parse-time relative to the importer file's directory. There's no way to fall back at parse time, so the only way to support multiple candidate paths is to compute the path at runtime and use dynamic `import(pathToFileURL(sourcesPath).href)`. Top-level `await` keeps the rest of the script's top-level statements unchanged.
@@ -0,0 +1,42 @@
1
+ # Changelog v0.5.58 - 2026-07-26
2
+
3
+ ### Added
4
+
5
+ - โšก **Persistent probe-cache with TTL + auto-hide broken models** ([#144](https://github.com/vava-nessa/free-coding-models/issues/144)) โ€” every health probe result is now cached to `~/.free-coding-models/probe-cache.json` for **24 hours**, shared across all surfaces (CLI TUI, Web Dashboard / daemon, Tauri Desktop).
6
+
7
+ - **Warm start in <500ms**: the full ranking renders from the cache instantly, then only models that are due (broken or TTL-expired) get re-pinged. Cold start with no cache behaves identically to before โ€” no regression.
8
+ - **Broken models stay hidden across sessions**: a model that 401s today won't appear in tomorrow's default view. Recovery is automatic โ€” if a previously-broken model comes back `ok`, the next probe un-hides it.
9
+ - **Honors `XDG_CACHE_HOME`** when set (Linux/macOS convention), else falls back to `~/.free-coding-models/`. File uses `0600` perms and is written atomically (tmp + rename) to survive crashes mid-flush.
10
+ - **Concurrency-safe**: debounced flush + atomic rename + read-merge-write means a running daemon and an interactive CLI can share the file without clobbering each other. Worst case: one batch of deltas is lost, never the whole file.
11
+ - **`probeVersion` constant** (currently `2`): bump this when ping behaviour changes (new endpoint, new prompt, etc.) and the entire cache invalidates automatically โ€” no manual purge.
12
+
13
+ - ๐Ÿ”˜ **Shift+B hotkey** toggles visibility of probe-cache-broken models in the TUI. Footer chip shows `โšก N cached ยท ๐Ÿ”ด M broken (Shift+B)` (becomes `๐Ÿ”ด M broken (visible)` when toggled on).
14
+
15
+ - ๐Ÿ› ๏ธ **New CLI flags**:
16
+ - `--reprobe` / `--no-cache` โ€” force-rebuild the probe-cache this run (ping everything fresh).
17
+ - `--probe-ttl <ms>` โ€” override the 24h TTL (e.g. `--probe-ttl 3600000` for 1h, useful when debugging model health).
18
+ - `--show-broken` โ€” don't auto-hide broken models this run (one-shot override for `--reprobe` style workflows).
19
+
20
+ - ๐Ÿ“Š **Daemon `/stats` now exposes `probeCache`** with `total`, `ok`, `broken`, `freshCount`, `staleCount`, `dueCount`, `hiddenCount`, `providers` โ€” the Web Dashboard renders it live so users can see cache hit rate at a glance.
21
+
22
+ ### Changed
23
+
24
+ - ๐Ÿฉบ **Daemon health-probe loop** (`runProbeBurst`) now skips models that are fresh + ok in the persistent cache. Broken models naturally pass through, so recovery detection keeps working unchanged. This cuts daemon-side probe traffic by ~85% on warm starts.
25
+
26
+ - ๐Ÿ“ **Per-provider probe results** are mirrored from the in-memory circuit-breaker windows into the persistent cache via `recordProbeResult` in `src/core/router-daemon.js`. Debounced 2s flush avoids filesystem thrash during probe bursts.
27
+
28
+ ### Maintenance
29
+
30
+ - ๐Ÿงช **+40 unit tests** (`test/probe-cache.test.js`, 36 โ†’ 76): covers freshness rules 1โ€“5, path resolution (XDG_CACHE_HOME), corrupt-JSON recovery, version migration, multi-model fan-out, `isCacheFresh` per-condition checks, `recordProbeResults` validation + garbage dropping, `getCacheStats` aggregates, `getCachedResultsForProvider` filtering, `pruneStaleEntries`, end-to-end reload, 4 concurrency tests (other-process deltas survive, stale `lastProbedAt` is overwritten, missing-file path, corrupt-file recovery).
31
+ - ๐Ÿงช **628 โ†’ 632 tests passing** (`pnpm test`), **107 โ†’ 118 suites**.
32
+ - ๐Ÿงน `vite build` succeeds.
33
+ - ๐Ÿ“– README updated with a new **๐Ÿง  Persistent probe cache** section (where the file lives, CLI flags, TUI keys, daemon `/stats` shape).
34
+
35
+ ### Files
36
+
37
+ - **New**: `src/core/probe-cache.js` (335 lines), `test/probe-cache.test.js` (332 lines), `changelog/v0.5.58.md`.
38
+ - **Modified**: `src/tui/app.js` (probe-cache load + apply + record), `src/tui/key-handler.js` (Shift+B handler), `src/tui/render-table.js` (footer chip), `src/tui/tui-state.js` (5 new state fields), `src/tui/cli-help.js` (3 new flag entries), `src/core/utils.js` (`parseArgs` gains `--reprobe` / `--probe-ttl` / `--show-broken`), `src/core/router-daemon.js` (full integration: load on boot, mirror probe results, skip fresh in `runProbeBurst`, expose in `/stats`, flush on shutdown), `README.md`, `tasks/t1.md`, `package.json` (test script), `changelog/`.
39
+
40
+ ### Inspiration
41
+
42
+ This implementation is informed by [`apmantza/pi-free`](https://github.com/apmantza/pi-free)'s `lib/provider-probe.ts` + `lib/probe-cache.ts` (which itself credits us: *"Inspired by free-coding-models' `extractQuotaPercent`"*). The inspiration flows both ways โ€” we're reclaiming the lead on the probe-cache axis by going cross-surface (CLI / daemon / Tauri share one file) and concurrency-safe (read-merge-write vs pi-free's single-writer Pi-only model).
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "free-coding-models",
3
- "version": "0.5.56",
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.",
3
+ "version": "0.5.58",
4
+ "description": "Find the fastest coding LLM models in seconds \u2014 ping free models from multiple providers, pick the best one for OpenCode, Cursor, or any AI coding assistant.",
5
5
  "keywords": [
6
6
  "nvidia",
7
7
  "nim",
@@ -53,7 +53,7 @@
53
53
  ],
54
54
  "scripts": {
55
55
  "start": "node bin/free-coding-models.js",
56
- "test": "node --test test/test.js test/fcm-agent-core.test.js test/patch-openclaw.test.js test/provider-metadata.test.js test/config-permission-hint.test.js",
56
+ "test": "node --test test/test.js test/fcm-agent-core.test.js test/patch-openclaw.test.js test/provider-metadata.test.js test/config-permission-hint.test.js test/probe-cache.test.js",
57
57
  "prepack": "npm run build:web",
58
58
  "dev": "node scripts/dev-web.mjs",
59
59
  "dev:web": "node scripts/dev-web.mjs",
@@ -83,4 +83,4 @@
83
83
  "vite": "^8.0.16",
84
84
  "vite-plus": "^0.2.6"
85
85
  }
86
- }
86
+ }
package/patch-openclaw.js CHANGED
@@ -9,8 +9,39 @@
9
9
 
10
10
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs'
11
11
  import { homedir } from 'os'
12
- import { join } from 'path'
13
- import { nvidiaNim } from './sources.js'
12
+ import { join, dirname } from 'path'
13
+ import { fileURLToPath, pathToFileURL } from 'url'
14
+
15
+ // ๐Ÿ“– Issue #35: when users copy just patch-openclaw.js to another directory
16
+ // ๐Ÿ“– (e.g. ~/.free-coding-models/) and try to run it, the relative import of
17
+ // ๐Ÿ“– sources.js fails with ERR_MODULE_NOT_FOUND. Look up the tree + check CWD
18
+ // ๐Ÿ“– before giving up with a helpful message.
19
+ const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url))
20
+
21
+ function findSourcesPath() {
22
+ const candidates = [
23
+ join(SCRIPT_DIR, 'sources.js'),
24
+ join(SCRIPT_DIR, '..', 'sources.js'),
25
+ join(SCRIPT_DIR, '..', '..', 'sources.js'),
26
+ join(SCRIPT_DIR, '..', '..', '..', 'sources.js'),
27
+ join(process.cwd(), 'sources.js'),
28
+ ]
29
+ for (const p of candidates) {
30
+ if (existsSync(p)) return p
31
+ }
32
+ return null
33
+ }
34
+
35
+ const sourcesPath = findSourcesPath()
36
+ if (!sourcesPath) {
37
+ console.error(' โœ– Could not locate sources.js next to this script.')
38
+ console.error(` Script location: ${SCRIPT_DIR}`)
39
+ console.error(' Make sure sources.js is in the same directory as patch-openclaw.js,')
40
+ console.error(' or run this from the free-coding-models repo root.')
41
+ process.exit(1)
42
+ }
43
+
44
+ const { nvidiaNim } = await import(pathToFileURL(sourcesPath).href)
14
45
 
15
46
  const MODELS_JSON = join(homedir(), '.openclaw', 'agents', 'main', 'agent', 'models.json')
16
47
  const OPENCLAW_JSON = join(homedir(), '.openclaw', 'openclaw.json')