app
KiCad - the KiCad Bridge
Public Made by Adomby adom
Reference implementation of the KiCad bridge: multi-instance Python server, forward path via kicad-cli, reverse path via in-process plugin. Most complex of the three bundled bridges.
← Commit history
v0.9.116: proactive KiCad update nudge - throttled (24h) async check, surfaced as kicadUpdate{installed,latest,_hint} on status/readiness/state/bridge_status/window_info so the AI can OFFER the user the update; kicad_upgrade remains the one-call runner
6 files changed
+90−7
BRIDGE_VERSION+1−1@@ -1 +1 @@-0.9.115+0.9.116
adom-bridge-kicad-manifest.json+5−5@@ -1,15 +1,15 @@ { "manifest_version": 1, "name": "kicad",- "version": "0.9.115",- "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.115/adom-bridge-kicad-v0.9.115.zip",- "sha256": "733b6ddad46b92a6ec97fc74909ca917ea63f7b610269c90209d687966d6af87",- "size": 409411,+ "version": "0.9.116",+ "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.116/adom-bridge-kicad-v0.9.116.zip",+ "sha256": "bb1c9f8d334e299e69ea75099bc020815baf327e02eba97b3f1e9ca1078c4fd6",+ "size": 410551, "verbPrefixes": [ "kicad_" ], "healthEndpoint": "/status",- "released_at": "2026-08-16T13:29:16Z",+ "released_at": "2026-08-16T13:47:30Z", "hero": "https://wiki.adom.inc/blob/app/kicad-bridge/kicad-hero.png", "languages": [ "Python",
adom-bridge-kicad-v0.9.116.zipadded⋯ 1 unchanged line ⋯
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "kicad", "displayName": "KiCad EDA",- "version": "0.9.115",+ "version": "0.9.116", "description": "Reverse bridge for KiCad \u2014 board/schematic introspection, lint via kicad-cli, plugin install, multi-instance probe, in-process DRC.", "homepage": "https://wiki.adom.inc/adom/adom-bridge", "author": "Adom Inc.",
handlers/upgrade.py+71@@ -449,3 +449,74 @@ def handle_upgrade(kicad_info: dict, args: dict) -> dict: ) ), }++# ── Proactive update nudge (0.9.116, John 2026-08-16: "occasionally check the+# latest kicad version number and send a hint to the ai") ────────────────────+# The AI only ever reads verb OUTPUT, so the nudge rides as a `kicadUpdate`+# field on the everyday status verbs (injected centrally by the dispatcher).+# The network check runs at most once per 24h, always in a background thread —+# a verb response is never delayed by it.+_NUDGE_INTERVAL_S = 24 * 3600+_nudge_cache = {"claimed": 0.0, "latest": None, "installed_full": None}+++def _installed_full_version(kicad_info) -> str:+ """Full X.Y.Z of the active install via `kicad-cli version` (cached per boot;+ kicad_info['version'] is only major.minor, useless for point-release compares)."""+ if _nudge_cache["installed_full"]:+ return _nudge_cache["installed_full"]+ try:+ import subprocess+ cli = (kicad_info or {}).get("kicad_cli_exe")+ if cli:+ out = subprocess.run([cli, "version"], capture_output=True, text=True,+ timeout=15).stdout.strip()+ import re as _re+ m = _re.search(r"(\d+\.\d+\.\d+)", out)+ if m:+ _nudge_cache["installed_full"] = m.group(1)+ return m.group(1)+ except Exception:+ pass+ return (kicad_info or {}).get("version") or ""+++def kicad_update_nudge(kicad_info) -> dict | None:+ """Compact nudge dict when a newer KiCad exists, else None. Non-blocking."""+ import threading+ import time++ now = time.time()+ if now - _nudge_cache["claimed"] > _NUDGE_INTERVAL_S:+ _nudge_cache["claimed"] = now # claim the slot so only one thread refreshes++ def _refresh():+ try:+ latest, _src = latest_stable_version()+ _nudge_cache["latest"] = latest+ except Exception:+ pass++ try:+ threading.Thread(target=_refresh, daemon=True, name="kicad-update-nudge").start()+ except Exception:+ pass++ latest = _nudge_cache.get("latest")+ if not latest or not kicad_info or not kicad_info.get("installed"):+ return None+ installed = _installed_full_version(kicad_info)+ try:+ if kicad_detect._version_tuple(latest) <= kicad_detect._version_tuple(installed):+ return None+ except Exception:+ return None+ return {+ "installed": installed,+ "latest": latest,+ "_hint": (f"KiCad {latest} is out (this box runs {installed}). Not urgent: mention it "+ f"to the user when convenient and ask if they want it. If yes: kicad_close "+ f"first, then kicad_upgrade {{}} — official downloads.kicad.org installer, "+ f"silent, per-user, no UAC. Never update mid-design-work."),+ }+
server.py+12@@ -755,6 +755,18 @@ def dispatch_command(command: str, args: dict) -> dict: # Stamp the version actually used so callers can see which install ran if isinstance(result, dict): result.setdefault("kicadVersionUsed", selected_info.get("version"))+ # Proactive update nudge (0.9.116): at most one network check per 24h+ # (async), surfaced on the everyday status verbs so the AI can OFFER the+ # user an update — the AI only ever reads verb output.+ if isinstance(result, dict) and command in ("status", "readiness", "state",+ "bridge_status", "window_info"):+ try:+ from handlers.upgrade import kicad_update_nudge+ nudge = kicad_update_nudge(selected_info)+ if nudge:+ result.setdefault("kicadUpdate", nudge)+ except Exception:+ pass # CONSTANT DIALOG COVERAGE: after EVERY GUI-affecting verb, sweep for KiCad # dialogs (KiCad throws modal error boxes constantly — "Cannot enumerate # ...Templates (Access denied)", save prompts, format warnings — that