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
Update 4 file(s)
4 files changed
+157−31
dashboard/kicad_dashboard.py+48−2@@ -684,6 +684,32 @@ def ensure_ready(target, surface): return False ++def _live_progress_poller(target, surface, stop_ev):+ """While a surface verb runs, relay the BRIDGE's live progress frames+ (kicad_progress active[]) onto the card's SSE channel.++ This is the piece John flagged missing (2026-08-21): the card used to show+ only the dashboard's own coarse percent emits (5/40/80), while the bridge+ was publishing real measured frames - stepLabel, etaSec, confidence,+ overdue - that only web control rendered. One poll every 2.5s, read-only,+ and the frames carry `live:true` so the UI can style them (dim non-measured,+ amber overdue/blocked, marquee when percent is null - a guessed number+ presented as a measurement is the failure mode the timing system kills)."""+ while not stop_ev.wait(2.5):+ try:+ r = ab(target, "kicad_progress", {"all": True}, timeout=20)+ frames = r.get("active") or []+ if not frames:+ continue+ f = frames[0]+ _emit(surface, live=True,+ percent=f.get("percent"), stepLabel=f.get("stepLabel"),+ etaSec=f.get("etaSec"), confidence=f.get("confidence"),+ overdue=bool(f.get("overdue")), blockedBy=f.get("blockedBy"))+ except Exception:+ pass+ def run_surface(target, key): """Stage what this surface needs, open it in the BACKGROUND, then render it from kicad_state. Streams progress over SSE and onto the taskbar button."""@@ -714,7 +740,13 @@ def run_surface(target, key): if need: args["capture"] = True # the #45 shot contract: the verb args["captureMaxWidth"] = 900 # hands us the frame, no 2nd trip- r = ab(target, verb, args, timeout=300)+ _stop = threading.Event()+ threading.Thread(target=_live_progress_poller, args=(target, key, _stop),+ daemon=True).start()+ try:+ r = ab(target, verb, args, timeout=300)+ finally:+ _stop.set() # RECOVERY, branching on KEYS never message text (Colby's client shape # from #47, which this dashboard never picked up - web control healed # these invisibly while this card surfaced call-1 failures raw).@@ -725,7 +757,13 @@ def run_surface(target, key): _emit(key, log=f"KiCad restarted to rebuild its library cache - " f"retrying in {int(wait_s)}s (measured)", percent=70) time.sleep(wait_s)- r = ab(target, verb, args, timeout=300)+ _stop = threading.Event()+ threading.Thread(target=_live_progress_poller, args=(target, key, _stop),+ daemon=True).start()+ try:+ r = ab(target, verb, args, timeout=300)+ finally:+ _stop.set() if r.get("success") is False: if r.get("errorCode") == "library_cache_stale" and r.get("rescanRefused"): raise RuntimeError("a KiCad window you opened yourself is in the way - "@@ -912,6 +950,14 @@ class Handler(BaseHTTPRequestHandler): return self._sse() if path == "/api/targets": return self._json(probe_targets())+ m = re.match(r"^/api/verb_times(?:/([^/]+))?$", path)+ if m:+ # The verb-times panel (fusion parity). Proxied per target so the+ # numbers are THAT box's measured history, not this container's.+ t = unquote(m.group(1) or "") or (get_targets() or ["-"])[0]+ r = ab(t, "kicad_verb_times", {}, timeout=30)+ return self._json({"target": t, "verbs": r.get("verbs") or {},+ "error": r.get("error") or None}) m = re.match(r"^/(?:api/status|state)(?:/([^/]+))?(/force)?$", path) if m: target = unquote(m.group(1) or "-")
dashboard/ui/index.html+51−1@@ -59,6 +59,14 @@ input:focus-visible{outline:none;border-color:var(--accent) !important} .copy{flex:none} .bar{height:8px;background:var(--elevated);border:1px solid var(--border);border-radius:999px;overflow:hidden;margin-top:10px;display:none} .bar>div{height:100%;width:0;background:var(--accent);transition:width .5s ease}+/* live-frame states (fusion timing contract): a non-measured estimate renders+ DIMMED so a guess never looks as authoritative as a measurement; overdue or+ waiting-on-a-human renders AMBER; percent:null renders an indeterminate+ marquee, never a fake number. */+.sbar.dim>div{opacity:.45}+.sbar.amber>div{background:#d29922}+.sbar.indet>div{width:30%!important;animation:marquee 1.2s linear infinite}+@keyframes marquee{0%{margin-left:-30%}100%{margin-left:100%}} .barlabel{font-size:12.5px;color:var(--text-2);margin-top:6px} .scard{display:flex;flex-direction:column;gap:8px} .scard .btn{width:100%;justify-content:center;text-align:center}@@ -154,6 +162,11 @@ main a:hover{border-bottom-color:var(--accent)} <div class="note" id="surfaceIntro">A real wiki part, end to end. Each button fetches the part's files from its wiki page, installs them into your real KiCad, opens JUST that surface in the background, and renders it from kicad_state. This is the whole circle: a wiki component to the app the EE designs in.</div> <div class="grid" id="surfaceGrid"></div> + <h2 style="display:flex;align-items:center;gap:10px">Verb times on this box+ <button class="btn" id="btnVerbTimes" style="font-size:12.5px" data-tip="Measured run times per verb from this box's append-only run log. p90 is the column for hunting slow paths.">refresh</button></h2>+ <div class="note">Every dispatch is logged with its outcome; these numbers converge just by the bridge being used. p50 15s beside p90 400s is two code paths wearing one name.</div>+ <div class="card" id="verbTimes" style="overflow-x:auto"><div class="note">not loaded - press refresh</div></div>+ <h2>Copy-paste prompts for your AI</h2> <div id="prompts"></div> </main>@@ -371,8 +384,23 @@ async function buildSurfaces(){ } function onSurfaceEvent(e){ const el=SURF_CARDS[e.surface];if(!el)return;+ const bar=el.querySelector('.sbar');+ if(e.live){ // a real measured frame from the bridge (kicad_progress)+ bar.style.display='block';+ bar.classList.toggle('indet', e.percent==null); // unknown -> marquee, never 0%+ bar.classList.toggle('dim', e.confidence!=='measured'); // a guess never looks measured+ bar.classList.toggle('amber', !!(e.overdue||e.blockedBy));+ if(e.percent!=null)bar.querySelector('div').style.width=e.percent+'%';+ let lbl=e.stepLabel||'';+ if(e.blockedBy)lbl='waiting on you: '+e.blockedBy;+ else if(e.etaSec!=null&&!e.overdue)lbl+=' · ~'+Math.round(e.etaSec)+'s left';+ else if(e.overdue)lbl+=' · taking longer than usual';+ if(lbl)el.querySelector('.slog').textContent=lbl;+ return;+ }+ bar.classList.remove('indet','dim','amber'); if(e.log)el.querySelector('.slog').textContent=e.log;- if(typeof e.percent==='number'){el.querySelector('.sbar').style.display='block';el.querySelector('.sbar>div').style.width=e.percent+'%';}+ if(typeof e.percent==='number'){bar.style.display='block';bar.querySelector('div').style.width=e.percent+'%';} if(e.status==='error'){el.classList.add('err');el.querySelector('.sbar').style.display='none';toast(e.surface+': '+e.log,'err');} if(e.status==='done'){ el.classList.remove('err');@@ -385,6 +413,28 @@ function onSurfaceEvent(e){ // Live channel (SSE): state pushes, AI toasts, CLI click directives. const CLICK_TARGETS={setup:'#btnSetup',launch:'#btnLaunch',close:'#btnClose',demo:'#btnDemo','part-tour':'#btnPartTour',settings:'#btnSettings',refresh:'#btnRefresh'};+async function loadVerbTimes(){+ const box=document.getElementById('verbTimes');+ box.innerHTML='<div class="note">loading...</div>';+ try{+ const r=await fetch('api/verb_times/'+encodeURIComponent(curTarget||''));+ const d=await r.json();+ const verbs=d.verbs||{};+ const keys=Object.keys(verbs);+ if(!keys.length){box.innerHTML='<div class="note">'+(d.error||'no runs logged yet on '+(d.target||'this box'))+'</div>';return;}+ let h='<table style="width:100%;border-collapse:collapse;font-size:12.5px">'+ +'<tr style="text-align:left;color:var(--text-2)"><th>verb</th><th>count</th><th>p50</th><th>p90</th><th>max</th><th>failures</th></tr>';+ for(const k of keys){const v=verbs[k];+ const slow=(v.p90||0)>3*(v.p50||1)&&(v.p90||0)>10; // two code paths wearing one name+ h+='<tr'+(v.failures?' style="color:#f85149"':(slow?' style="color:#d29922"':''))+'>'+ +'<td style="padding:3px 10px 3px 0">'+k+'</td><td>'+v.count+'</td>'+ +'<td>'+(v.p50==null?'-':v.p50+'s')+'</td><td>'+(v.p90==null?'-':v.p90+'s')+'</td>'+ +'<td>'+(v.max==null?'-':v.max+'s')+'</td><td>'+v.failures+'</td></tr>';}+ box.innerHTML=h+'</table>';+ }catch(e){box.innerHTML='<div class="note">verb times unavailable: '+e+'</div>';}+}+document.getElementById('btnVerbTimes').onclick=loadVerbTimes;+ function connectSSE(){ const es=new EventSource('events'); es.addEventListener('hello',()=>{console.log('sse connected');});
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "kicad-bridge",- "version": "1.0.39",+ "version": "1.0.40", "type": "app", "description": "Skills for your container so your AI knows how to drive the KiCad bridge. The bridge runtime itself is the release zip; Adom Bridge loads that.", "tags": [
page.json+57−27@@ -3,8 +3,8 @@ "type": "app", "slug": "kicad-bridge", "title": "KiCad - the KiCad Bridge",- "brief": "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.",- "version": "1.0.39",+ "brief": "Reference implementation of the KiCad bridge \u2014 multi-instance Python server, forward path via kicad-cli, reverse path via in-process plugin. Most complex of the three bundled bridges.",+ "version": "1.0.40", "tags": [ "kicad", "pcb",@@ -17,34 +17,69 @@ ], "license": "MIT", "discovery_triggers": [- "open kicad",- "control kicad",- "kicad dashboard",- "show me a footprint",- "show me a symbol",- "drive kicad from the cloud", "kicad bridge",- "open the pcb editor",- "view a part in 3d",- "install a kicad symbol"+ "open my pcb",+ "open my pcb design",+ "open my board in kicad",+ "open my schematic",+ "open my circuit board",+ "design a circuit board",+ "edit my pcb",+ "edit my schematic",+ "open a kicad board",+ "open a kicad project",+ "run drc",+ "run drc on my board",+ "run erc on my schematic",+ "check my board for errors",+ "export gerbers",+ "export gerber files",+ "export gerbers for jlcpcb",+ "make gerbers for my pcb",+ "export a step file from my board",+ "export bom",+ "export a bom from my schematic",+ "show my board in 3d",+ "open the 3d viewer",+ "view my pcb in 3d",+ "open kicad",+ "launch kicad",+ "install kicad",+ "upgrade kicad",+ "is kicad installed",+ "what kicad versions do i have",+ "install a kicad symbol",+ "install a footprint in kicad",+ "add a library to kicad",+ "open the symbol editor",+ "open the footprint editor",+ "screenshot kicad",+ "take a screenshot of my board",+ "place a footprint",+ "open a kicad_pcb file",+ "open a kicad_sch file" ],- "discovery_pitch": "Drive KiCad on your own Windows desktop from an AI thread: open symbols, footprints, boards and 3D views in the background, and install parts into your libraries.",+ "discovery_pitch": "Drive KiCad on the user's desktop: open boards/schematics/symbols/footprints, run DRC/ERC, export gerbers/STEP/BOM, view in 3D. First run `adom-bridge-cli bridge_list` to confirm the kicad bridge is installed; if so, drive its `kicad_*` verbs (kicad_describe lists them, kicad_readiness checks KiCad is present). If this container lacks the skill, run `adom-wiki pkg install adom/kicad-bridge` or `adom-bridge-cli sync_skills`.", "sample_prompts": [ {- "label": "Show a footprint",- "prompt": "Show me the IN-S42ATR footprint in KiCad"+ "label": "Read kicad bridge source",+ "prompt": "Download the kicad-bridge zip from the wiki and walk me through its server.py"+ },+ {+ "label": "Add a new kicad verb",+ "prompt": "Help me add a new kicad_get_layer_image verb to the in-process bridge plugin" }, {- "label": "Show a symbol",- "prompt": "Open the RP2040 symbol in the KiCad symbol editor"+ "label": "Understand reverse bridge",+ "prompt": "Explain how kicad_bridge_call routes from cloud Docker into the running pcbnew.exe" }, {- "label": "See it in 3D",- "prompt": "Show me that part in 3D"+ "label": "List kicad verbs",+ "prompt": "adom-bridge-cli bridge_list and show me the kicad verbs" }, {- "label": "Open the dashboard",- "prompt": "Open the KiCad dashboard and tell me if anything is red"+ "label": "Try the watcher pip",+ "prompt": "Enable project watch in Adom Bridge settings for a kicad project" } ], "install": {@@ -53,7 +88,7 @@ "install_hint": "", "version_cmd": "" },- "readme": "# Adom Desktop — KiCad Bridge\n\nA **reverse bridge** that lets Adom Desktop (AD) — and the AI driving it — control the user's own\nKiCad on Windows: open and drive every editor, install symbol/footprint libraries, place parts, run\nDRC/ERC, export manufacturing files, and screenshot any window back to the AI.\n\nKiCad is the user's **host app**. The bridge *detects* an existing KiCad and, if none is present,\n*installs* one for them — it never asks the user to download or click through anything by hand.\n\n- **Page / docs:** https://wiki.adom.inc/adom/adom-desktop-kicad-bridge\n- **Bridge SDK:** https://wiki.adom.inc/adom/adom-desktop-bridges\n- **Verb namespace:** `kicad_*` · **Status verb:** `kicad_bridge_status` · **Health:** `GET /status`\n\n> Every screenshot in this README was captured on **ADOMBASELINE**, a stock Hyper-V VM with **no\n> GPU**, driven end-to-end through the bridge — install → libraries → editors → 2D → 3D. If it\n> renders there, it renders on a real laptop.\n\n---\n\n## Demo — narrated live editor tour (real screen recording)\n\n[](https://wiki.adom.inc/blob/app/adom-desktop-kicad-bridge/kicad-bridge-demo.mp4)\n\n**▶ [Watch the demo](https://wiki.adom.inc/blob/app/adom-desktop-kicad-bridge/kicad-bridge-demo.mp4)** (48s, 🔊 narrated) — the bridge driving real KiCad on a Windows VM: **select** a footprint and **zoom** the copper in the PCB editor, **rotate** the assembled board in 3D, then tour the schematic, symbol, and footprint editors. Genuine screen recording of the live windows (zoom / select / 3D rotation), narrated section by section — not animated stills.\n\n\n---\n\n## The demo verb — `kicad_demo`\n\nOne verb that shows the whole bridge off, built for the AI that demos Adom Desktop\nduring Hydrogen Desktop's install. Six beats, in the order a hardware person thinks:\n\n| Beat | Shows |\n|---|---|\n| 1 | the **schematic symbol** in the user's own Symbol Editor |\n| 2 | the **footprint** for that same part |\n| 3 | that part in **3D** |\n| 4 | a **schematic** using it (U1 + two 10k resistors) |\n| 5 | the **2D board layout** |\n| 6 | the finished **board in 3D** |\n\n```bash\nadom-desktop kicad_demo '{}' # start: prepares, warms KiCad, opens beat 1\nadom-desktop kicad_demo '{\"step\":\"footprint\"}' # ...then follow data.nextCall each time\nadom-desktop kicad_demo '{\"all\":true}' # bulk (non-interactive; slower than one request budget)\nadom-desktop kicad_close '{\"force\":true}' # tear down\n```\n\n**It narrates itself.** Every beat returns `say` (a line to speak), `pointOut`\n(what's actually on screen), `window` (match it in `kicad_screenshot_all`) and\n`nextCall`. One beat per call is the default precisely so the AI can talk, screenshot,\nthen move on.\n\n**It ships nothing.** The two demo parts, the schematic and the board are all\ngenerated in code, and the 3D bodies come from KiCad's *own* bundled\n`3dmodels/*.3dshapes` — so the runtime zip stays small and the 3D views are real.\nParts install into the user's real `Adom` library; the project lands in\n`Documents/adom-kicad-demo`.\n\n**No KiCad? It offers to install it.** `kicad_demo` returns an offer instead of an\nerror; `kicad_demo '{\"installKiCad\": true}'` silently installs the official build\n(per-user, no UAC) and then runs the tour. Watching the AI install your EDA tool is\nitself a good demo beat.\n\n**It never takes your screen.** Every window opens in the background and all input is\nwindow-targeted (UIA / `PostMessage`), so you can keep working while it runs.\n\n---\n\n## How it fits together\n\nThe bridge is a `spawn.kind: python` process AD launches on the user's machine (`entrypoint:\nserver.py`, `port: 0` — AD picks the port and passes `ADOM_BIND_HOST`). It speaks HTTP\n(`POST /command`, `GET /status`) and reaches KiCad through **four control surfaces**, picking the\nlightest one that can do the job.\n\n\n\n| # | Surface | Used for | Where |\n|---|---------|----------|-------|\n| 1 | **kicad-cli** (subprocess) | headless DRC/ERC, gerber/pdf/svg/step/bom export, format upgrade | `handlers/export.py`, `run_drc`, `run_erc`, `lint_*` |\n| 2 | **KiCad IPC (kipy)** | board/schematic introspection, confirmed footprint placement (KiCad 9+) | `handlers/place_footprint.py` |\n| 3 | **Embedded Python** | JSON-RPC *inside* each KiCad process, dispatched on its wx UI thread | `plugin_payload/adom_bridge.py`, `handlers/bridge_client.py` |\n| 4 | **Win32 / UIA / SendKeys** | open editors, screenshots, clicks/keys, dialog handling, window management | `handlers/kicad_ui.py`, `handlers/close_windows.py` |\n\n---\n\n## The window tour\n\nEverything below was opened and captured on the GPU-less VM. Sample data is the **RP2040 breakout**\ngenerated by `tour-pack-rp2040/` (an `AdomRP2040` symbol + `QFN-56_AdomRP2040` footprint + a board).\n\n### Schematic editor — `kicad_open_schematic`\n\nThe RP2040 breakout: the MCU (U1), USB-C, a 12 MHz crystal, decoupling, and mounting pins.\n\n\n\n### Symbol editor — `kicad_open_symbol_editor`\n\nOpens straight to a symbol (`kicad_install_symbol` puts it in a user library first).\n\n\n\n### Footprint editor — `kicad_open_footprint_editor`\n\n`{\"footprintName\":\"QFN-56_AdomRP2040\",\"library\":\"AdomRP2040\"}` loads the part directly — 56 pins +\nthermal pad, 7×7 mm, 0.4 mm pitch, courtyard and silkscreen.\n\n\n\n### PCB editor (2D) — `kicad_open_board`\n\nThe routed breakout — copper, silkscreen, the QFN-56 land pattern, mounting holes.\n\n\n\n### 3D viewer — `kicad_open_3d_viewer {\"editor\":\"pcb\"}`\n\nThe full board in 3D — board body, the USB-C connector's 3D model, the QFN chip body, SMD parts,\nplated through-holes. **Rendered on the CPU via the software-OpenGL fallback (below); reload 1.7 s.**\n\n\n\n---\n\n## Install & upgrade — zero manual steps\n\nThe bridge never tells the user to go download KiCad. `kicad_upgrade` fetches the official installer\nand runs it silently, picking the scope automatically:\n\n- **Elevated AD** → `/allusers /S` (system-wide, `%ProgramFiles%\\KiCad`).\n- **Non-elevated AD** → `/currentuser /S` (`%LocalAppData%\\Programs\\KiCad`, **no UAC prompt**).\n\n`kicad_readiness` reports whether KiCad is installed and ready without side effects; the AI routes on\nit before offering to install. Hard-won install details (all handled for you):\n\n- KiCad 10's NsisMultiUser installer **requires** a scope flag — bare `/S` errors `rc=666660`.\n- AD's portable Python has **no CA bundle**; the bridge ships `certs/cacert.pem` and uses it for TLS.\n- Downloads are size-checked against `Content-Length` (a truncated installer otherwise fails at NSIS).\n- `%APPDATA%/kicad/<ver>/` lib tables don't exist until first launch — the bridge **bootstraps** them\n so `install_library`/`install_footprint` work on a never-opened KiCad.\n- `kicad_upgrade {\"diagnoseOnly\":true}` reports token-elevation type + `EnableLUA` without installing.\n\n---\n\n## Dialogs & error handling — the bridge clears the pointless ones\n\nKiCad throws modal dialogs that stall automation. The bridge **scans every window owned by a running\nKiCad process** (by PID — reliable, unlike title matching), auto-expires the benign ones, screenshots\nwhat it dismissed, and returns a hint so the AI can decide what (if anything) to tell the user.\n\nAuto-expired benign dialogs include:\n\n- **\"Could not use OpenGL / falling back to software rendering\"** (GPU-less hosts).\n- **\"Welcome to KiCad — starting for the first time\"** first-run wizard (dismissing accepts defaults).\n- **\"This file was created by an older version of KiCad\"** conversion notice.\n\n\n\nVerbs:\n\n- `kicad_window_info` — lists windows and **self-heals** (auto-expires benign dialogs) by default.\n- `kicad_dismiss_dialogs {\"all\":true}` — clear everything blocking; `{\"screenshot\":true}` returns\n images of each; `{\"forceSoftwareCanvas\":true}` persists Cairo canvas; `{\"debug\":true}` dumps the\n raw PID-based scan.\n- `kicad_screenshot_all` — one call returns **every** open KiCad window, so the AI can spot an error\n dialog it didn't expect and read the message.\n\n---\n\n## Software-OpenGL fallback (last resort)\n\n`kicad_enable_software_opengl` deploys Mesa's `llvmpipe` (a CPU OpenGL rasterizer) into KiCad's `bin`\nso a box with **no usable GPU** — Hyper-V, RDP, headless CI — can still render the editors and the 3D\nviewer. This is how every 3D shot above exists.\n\n> ⚠️ It renders on the CPU and is **slow**. It is a worst-case fallback only — a real GPU (or GPU-P /\n> DDA passthrough) is vastly better. The bridge keeps it so a GPU-less box isn't a dead end; it is\n> never suggested proactively.\n\n---\n\n## Verb reference\n\nAll 41 verbs (prefix `kicad_`). Call `kicad_describe` for the live catalog with per-verb hints,\nrelated verbs, and pitfalls.\n\n**Windows & UI** — `open_board`, `open_schematic`, `open_symbol_editor`, `open_footprint_editor`,\n`open_3d_viewer`, `open_editors`, `close_symbol_editor`, `close_footprint_editor`, `close_3d_viewer`,\n`close`, `window_info`, `dismiss_dialogs`, `screenshot_all`, `send_key`, `click`, `fix_keyboard`\n\n**Libraries & parts** — `install_library`, `install_symbol`, `install_footprint`, `install_plugin`,\n`place_footprint`, `adom_library_status`\n\n**Checks & export** — `run_drc`, `run_erc`, `lint_board`, `lint_schematic`, `lint_library`,\n`format_upgrade`, `export_gerber`, `export_pdf`, `export_svg`, `export_step`, `export_bom_csv`\n\n**Detect, install & meta** — `list_versions`, `readiness`, `describe`, `enable_software_opengl`,\n`check_for_updates`, `upgrade`, `bridge_status`, `bridge_call`\n\n---\n\n## Running / dependencies\n\nAD provisions everything; there is nothing to install by hand.\n\n- **Python** ≥ 3.11 (AD provisions it) — stdlib only, plus the bundled `certs/cacert.pem`.\n- **KiCad** ≥ 7.0 (host app; `kicad_upgrade` installs it if absent).\n- **OS:** Windows (macOS/Linux detection stubs exist; the GUI surfaces are Windows-first).\n\nThe bridge binds `ADOM_BIND_HOST` on an AD-assigned port — never `0.0.0.0`. Auto-updates via\n`updateManifestUrl` in `bridge.json`.\n\n---\n\n*Developer & publish notes live in `dev-skills/` and `publish-skills/` (source-only, never shipped to\na user install).*\n",+ "readme": "# Adom Desktop \u2014 KiCad Bridge\n\nA **reverse bridge** that lets Adom Desktop (AD) \u2014 and the AI driving it \u2014 control the user's own\nKiCad on Windows: open and drive every editor, install symbol/footprint libraries, place parts, run\nDRC/ERC, export manufacturing files, and screenshot any window back to the AI.\n\nKiCad is the user's **host app**. The bridge *detects* an existing KiCad and, if none is present,\n*installs* one for them \u2014 it never asks the user to download or click through anything by hand.\n\n- **Page / docs:** https://wiki.adom.inc/adom/adom-desktop-kicad-bridge\n- **Bridge SDK:** https://wiki.adom.inc/adom/adom-desktop-bridges\n- **Verb namespace:** `kicad_*` \u00b7 **Status verb:** `kicad_bridge_status` \u00b7 **Health:** `GET /status`\n\n> Every screenshot in this README was captured on **ADOMBASELINE**, a stock Hyper-V VM with **no\n> GPU**, driven end-to-end through the bridge \u2014 install \u2192 libraries \u2192 editors \u2192 2D \u2192 3D. If it\n> renders there, it renders on a real laptop.\n\n---\n\n## Demo \u2014 narrated live editor tour (real screen recording)\n\n[](https://wiki.adom.inc/blob/app/adom-desktop-kicad-bridge/kicad-bridge-demo.mp4)\n\n**\u25b6 [Watch the demo](https://wiki.adom.inc/blob/app/adom-desktop-kicad-bridge/kicad-bridge-demo.mp4)** (48s, \ud83d\udd0a narrated) \u2014 the bridge driving real KiCad on a Windows VM: **select** a footprint and **zoom** the copper in the PCB editor, **rotate** the assembled board in 3D, then tour the schematic, symbol, and footprint editors. Genuine screen recording of the live windows (zoom / select / 3D rotation), narrated section by section \u2014 not animated stills.\n\n\n---\n\n## The demo verb \u2014 `kicad_demo`\n\nOne verb that shows the whole bridge off, built for the AI that demos Adom Desktop\nduring Hydrogen Desktop's install. Six beats, in the order a hardware person thinks:\n\n| Beat | Shows |\n|---|---|\n| 1 | the **schematic symbol** in the user's own Symbol Editor |\n| 2 | the **footprint** for that same part |\n| 3 | that part in **3D** |\n| 4 | a **schematic** using it (U1 + two 10k resistors) |\n| 5 | the **2D board layout** |\n| 6 | the finished **board in 3D** |\n\n```bash\nadom-desktop kicad_demo '{}' # start: prepares, warms KiCad, opens beat 1\nadom-desktop kicad_demo '{\"step\":\"footprint\"}' # ...then follow data.nextCall each time\nadom-desktop kicad_demo '{\"all\":true}' # bulk (non-interactive; slower than one request budget)\nadom-desktop kicad_close '{\"force\":true}' # tear down\n```\n\n**It narrates itself.** Every beat returns `say` (a line to speak), `pointOut`\n(what's actually on screen), `window` (match it in `kicad_screenshot_all`) and\n`nextCall`. One beat per call is the default precisely so the AI can talk, screenshot,\nthen move on.\n\n**It ships nothing.** The two demo parts, the schematic and the board are all\ngenerated in code, and the 3D bodies come from KiCad's *own* bundled\n`3dmodels/*.3dshapes` \u2014 so the runtime zip stays small and the 3D views are real.\nParts install into the user's real `Adom` library; the project lands in\n`Documents/adom-kicad-demo`.\n\n**No KiCad? It offers to install it.** `kicad_demo` returns an offer instead of an\nerror; `kicad_demo '{\"installKiCad\": true}'` silently installs the official build\n(per-user, no UAC) and then runs the tour. Watching the AI install your EDA tool is\nitself a good demo beat.\n\n**It never takes your screen.** Every window opens in the background and all input is\nwindow-targeted (UIA / `PostMessage`), so you can keep working while it runs.\n\n---\n\n## How it fits together\n\nThe bridge is a `spawn.kind: python` process AD launches on the user's machine (`entrypoint:\nserver.py`, `port: 0` \u2014 AD picks the port and passes `ADOM_BIND_HOST`). It speaks HTTP\n(`POST /command`, `GET /status`) and reaches KiCad through **four control surfaces**, picking the\nlightest one that can do the job.\n\n\n\n| # | Surface | Used for | Where |\n|---|---------|----------|-------|\n| 1 | **kicad-cli** (subprocess) | headless DRC/ERC, gerber/pdf/svg/step/bom export, format upgrade | `handlers/export.py`, `run_drc`, `run_erc`, `lint_*` |\n| 2 | **KiCad IPC (kipy)** | board/schematic introspection, confirmed footprint placement (KiCad 9+) | `handlers/place_footprint.py` |\n| 3 | **Embedded Python** | JSON-RPC *inside* each KiCad process, dispatched on its wx UI thread | `plugin_payload/adom_bridge.py`, `handlers/bridge_client.py` |\n| 4 | **Win32 / UIA / SendKeys** | open editors, screenshots, clicks/keys, dialog handling, window management | `handlers/kicad_ui.py`, `handlers/close_windows.py` |\n\n---\n\n## The window tour\n\nEverything below was opened and captured on the GPU-less VM. Sample data is the **RP2040 breakout**\ngenerated by `tour-pack-rp2040/` (an `AdomRP2040` symbol + `QFN-56_AdomRP2040` footprint + a board).\n\n### Schematic editor \u2014 `kicad_open_schematic`\n\nThe RP2040 breakout: the MCU (U1), USB-C, a 12 MHz crystal, decoupling, and mounting pins.\n\n\n\n### Symbol editor \u2014 `kicad_open_symbol_editor`\n\nOpens straight to a symbol (`kicad_install_symbol` puts it in a user library first).\n\n\n\n### Footprint editor \u2014 `kicad_open_footprint_editor`\n\n`{\"footprintName\":\"QFN-56_AdomRP2040\",\"library\":\"AdomRP2040\"}` loads the part directly \u2014 56 pins +\nthermal pad, 7\u00d77 mm, 0.4 mm pitch, courtyard and silkscreen.\n\n\n\n### PCB editor (2D) \u2014 `kicad_open_board`\n\nThe routed breakout \u2014 copper, silkscreen, the QFN-56 land pattern, mounting holes.\n\n\n\n### 3D viewer \u2014 `kicad_open_3d_viewer {\"editor\":\"pcb\"}`\n\nThe full board in 3D \u2014 board body, the USB-C connector's 3D model, the QFN chip body, SMD parts,\nplated through-holes. **Rendered on the CPU via the software-OpenGL fallback (below); reload 1.7 s.**\n\n\n\n---\n\n## Install & upgrade \u2014 zero manual steps\n\nThe bridge never tells the user to go download KiCad. `kicad_upgrade` fetches the official installer\nand runs it silently, picking the scope automatically:\n\n- **Elevated AD** \u2192 `/allusers /S` (system-wide, `%ProgramFiles%\\KiCad`).\n- **Non-elevated AD** \u2192 `/currentuser /S` (`%LocalAppData%\\Programs\\KiCad`, **no UAC prompt**).\n\n`kicad_readiness` reports whether KiCad is installed and ready without side effects; the AI routes on\nit before offering to install. Hard-won install details (all handled for you):\n\n- KiCad 10's NsisMultiUser installer **requires** a scope flag \u2014 bare `/S` errors `rc=666660`.\n- AD's portable Python has **no CA bundle**; the bridge ships `certs/cacert.pem` and uses it for TLS.\n- Downloads are size-checked against `Content-Length` (a truncated installer otherwise fails at NSIS).\n- `%APPDATA%/kicad/<ver>/` lib tables don't exist until first launch \u2014 the bridge **bootstraps** them\n so `install_library`/`install_footprint` work on a never-opened KiCad.\n- `kicad_upgrade {\"diagnoseOnly\":true}` reports token-elevation type + `EnableLUA` without installing.\n\n---\n\n## Dialogs & error handling \u2014 the bridge clears the pointless ones\n\nKiCad throws modal dialogs that stall automation. The bridge **scans every window owned by a running\nKiCad process** (by PID \u2014 reliable, unlike title matching), auto-expires the benign ones, screenshots\nwhat it dismissed, and returns a hint so the AI can decide what (if anything) to tell the user.\n\nAuto-expired benign dialogs include:\n\n- **\"Could not use OpenGL / falling back to software rendering\"** (GPU-less hosts).\n- **\"Welcome to KiCad \u2014 starting for the first time\"** first-run wizard (dismissing accepts defaults).\n- **\"This file was created by an older version of KiCad\"** conversion notice.\n\n\n\nVerbs:\n\n- `kicad_window_info` \u2014 lists windows and **self-heals** (auto-expires benign dialogs) by default.\n- `kicad_dismiss_dialogs {\"all\":true}` \u2014 clear everything blocking; `{\"screenshot\":true}` returns\n images of each; `{\"forceSoftwareCanvas\":true}` persists Cairo canvas; `{\"debug\":true}` dumps the\n raw PID-based scan.\n- `kicad_screenshot_all` \u2014 one call returns **every** open KiCad window, so the AI can spot an error\n dialog it didn't expect and read the message.\n\n---\n\n## Software-OpenGL fallback (last resort)\n\n`kicad_enable_software_opengl` deploys Mesa's `llvmpipe` (a CPU OpenGL rasterizer) into KiCad's `bin`\nso a box with **no usable GPU** \u2014 Hyper-V, RDP, headless CI \u2014 can still render the editors and the 3D\nviewer. This is how every 3D shot above exists.\n\n> \u26a0\ufe0f It renders on the CPU and is **slow**. It is a worst-case fallback only \u2014 a real GPU (or GPU-P /\n> DDA passthrough) is vastly better. The bridge keeps it so a GPU-less box isn't a dead end; it is\n> never suggested proactively.\n\n---\n\n## Verb reference\n\nAll 41 verbs (prefix `kicad_`). Call `kicad_describe` for the live catalog with per-verb hints,\nrelated verbs, and pitfalls.\n\n**Windows & UI** \u2014 `open_board`, `open_schematic`, `open_symbol_editor`, `open_footprint_editor`,\n`open_3d_viewer`, `open_editors`, `close_symbol_editor`, `close_footprint_editor`, `close_3d_viewer`,\n`close`, `window_info`, `dismiss_dialogs`, `screenshot_all`, `send_key`, `click`, `fix_keyboard`\n\n**Libraries & parts** \u2014 `install_library`, `install_symbol`, `install_footprint`, `install_plugin`,\n`place_footprint`, `adom_library_status`\n\n**Checks & export** \u2014 `run_drc`, `run_erc`, `lint_board`, `lint_schematic`, `lint_library`,\n`format_upgrade`, `export_gerber`, `export_pdf`, `export_svg`, `export_step`, `export_bom_csv`\n\n**Detect, install & meta** \u2014 `list_versions`, `readiness`, `describe`, `enable_software_opengl`,\n`check_for_updates`, `upgrade`, `bridge_status`, `bridge_call`\n\n---\n\n## Running / dependencies\n\nAD provisions everything; there is nothing to install by hand.\n\n- **Python** \u2265 3.11 (AD provisions it) \u2014 stdlib only, plus the bundled `certs/cacert.pem`.\n- **KiCad** \u2265 7.0 (host app; `kicad_upgrade` installs it if absent).\n- **OS:** Windows (macOS/Linux detection stubs exist; the GUI surfaces are Windows-first).\n\nThe bridge binds `ADOM_BIND_HOST` on an AD-assigned port \u2014 never `0.0.0.0`. Auto-updates via\n`updateManifestUrl` in `bridge.json`.\n\n---\n\n*Developer & publish notes live in `dev-skills/` and `publish-skills/` (source-only, never shipped to\na user install).*\n", "author": { "name": "John Lauer", "email": "[email protected]"@@ -95,12 +130,7 @@ "skills/kicad-bridge-hero/compose_hero.py", "skills/kicad-bridge-test/SKILL.md", "skills/kicad-bridge-test/run_verb_tests.py",- "skills/kicad-bridge-background/SKILL.md",- "dashboard/kicad_dashboard.py",- "dashboard/ui/index.html",- "dashboard/ui/kicad-logo.png",- "kicad-bridge-hero-v6.png",- "docs/img/dock-kicad.svg"+ "skills/kicad-bridge-background/SKILL.md" ], "org": "adom", "name": "kicad-bridge",