Adom Desktop - 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.
name: kicad-bridge-dev description: DEVELOPER skill for building/debugging the Adom Desktop KiCad bridge. NOT shipped to users (source-only). Read before editing bridge code — architecture, the 4 control surfaces, how to add a verb, and every fresh-machine gotcha this bridge has hit. Trigger words — kicad bridge dev, edit kicad bridge, add kicad verb, kicad bridge architecture, debug kicad bridge, kicad control surfaces, kicad fresh machine.
kicad-bridge-dev
Developer notes for the adom-desktop-kicad-bridge. Source-only (belongs in the repo, never shipped to a user install). Read adom-desktop-bridge-sdk first for the AD-core-vs-author boundary.
What it is
A reverse bridge AD spawns on the user's laptop (spawn.kind: python, entrypoint: server.py, port: 0). It adds the kicad_* verb namespace. AD provisions Python (Runtime contract v1.9.63+) and passes ADOM_BIND_HOST — bind that, never 0.0.0.0.
The 4 control surfaces (how a verb reaches KiCad)
- kicad-cli (
subprocess→kicad-cli.exe) — headless:pcb drc,sch erc,pcb/sch export(gerber/pdf/svg/step/bom),pcb/sch upgrade. No GUI. Handlers:run_drc,run_erc,lint_*,export.py,format_upgrade,bom.py,netlist.py. - KiCad IPC API (kipy) — KiCad 9+. Board/schematic introspection + automation (e.g.
place_footprintconfirms viakipy get_footprints()). Expands with KiCad 11. - Embedded-Python reverse bridge (
plugin_payload/adom_bridge.py) —usercustomize.pyauto-injects at KiCad's USER_SITE → runs an HTTP server INSIDE each KiCad process, writes a%TEMP%discovery file; container-sidehandlers/bridge_client.pyPOSTs JSON-RPC to/rpc, dispatched onto KiCad's wx UI thread viawx.CallAfter. Methods:ping,list_frames,get_menu_ids,wm_command. Verbs:bridge_status,bridge_call. - Win32 / UIA / SendKeys (
handlers/kicad_ui.py, ctypes/user32) — the last resort:WM_COMMAND(open editors),SendInput/keybd_event(alt+3,ctrl+s),PrintWindow+EnumWindows(screenshots),GetWindowRect. For opening the 3D viewer, screenshots, clicks, keystrokes, window management.
Adding a verb (3 places, keep in sync)
- Write
handle_<verb>in a handler; return a dict withsuccess+ a rich_hint(mandatory — the AI reads verb OUTPUT, not this skill). - Register in
server.pyCOMMAND_HANDLERS. - Add
kicad_<verb>tobridge.jsonverbs[]AND an entry (summary/hint/related/pitfalls) to_VERB_CATALOGinserver.py(thekicad_describecatalog). A test checks these three stay aligned.
Dispatcher special cases (dispatch_command): readiness, describe, check_for_updates return BEFORE the plugin auto-install + the KiCad-not-installed pre-check (they must work with KiCad absent). upgrade also bypasses that pre-check (it INSTALLS KiCad — do not gate the installer behind "KiCad not installed") and refreshes the detection cache on success.
Fresh-machine gotchas (all hard-won, all fixed — don't regress them)
- Install scope is mandatory. KiCad 10's NsisMultiUser installer: bare
/S=rc=666660(invalid params). Pass/allusers /S(elevated) or/currentuser /S(per-user,%LOCALAPPDATA%\Programs\KiCad, no UAC). Seehandlers/upgrade.py. - TLS on the portable Python. AD's provisioned Python has no CA bundle →
CERTIFICATE_VERIFY_FAILED. We bundlecerts/cacert.pem(certifi) and load it in_ssl_context(). Any new https fetch must use that context. - Truncated downloads read as EOF and pass MZ/size checks, then NSIS fails at install.
upgrade.pyHEADsContent-Lengthand refuses/deletes a size-mismatched file. - Config doesn't exist pre-first-launch.
%APPDATA%/kicad/<ver>/(lib tables) is created on KiCad's first GUI run.kicad_detect.ensure_win_user_config()bootstraps it (seeds tables from the install template) soinstall_library/_footprintwork on a never-launched KiCad. - Detection cache is built once at startup. After
kicad_upgradeinstalls KiCad,dispatch_commandre-runsdetect_all_kicad_versions()so subsequent verbs see it without a bridge restart. Detect paths include%LocalAppData%\Programs\KiCad(per-user installs). - Blocking dialogs stall everything.
handlers/close_windows.pyscans EVERY window owned by a running KiCad process (by PID —_win_scan_kicad_dialogs, reliable; title/owner matching is NOT), auto-expires benign ones (OpenGL notice), screenshots them, and returns hints.window_infoself-heals by default;kicad_dismiss_dialogs {all|forceSoftwareCanvas}. - GPU-less hosts. No OpenGL → the notice pops and pcbnew/3D may not render. On expiring that notice we persist
graphics.canvas_type=2(Cairo) tokicad_common.json. NOTE: a truly minimal VM (Hyper-V, no GPU) may still not render the heavy editors even in Cairo — that's the hardware. Validate GUI/screenshot work on a real-GPU machine.
Diagnostics you can call
kicad_upgrade {"diagnoseOnly":true}— token elevation type + UACEnableLUA+ cached-installer/truncation facts (no install).kicad_dismiss_dialogs {"debug":true}— raw process-based dialog scan (PIDs + every KiCad window + class + isDialog).
Testing on a fresh AD / VM
refresh_bridges {name:kicad} pulls the new cache; the RUNNING instance keeps its old code until it dies — bridge_kill {name:kicad} forces a fresh spawn that loads the new version and re-detects. The first verb after a kill returns a bridge_starting envelope — retry. Confirm the running version via bridge_log_read ("starting v0.9.xx").
---
name: kicad-bridge-dev
description: DEVELOPER skill for building/debugging the Adom Desktop KiCad bridge. NOT shipped to users (source-only). Read before editing bridge code — architecture, the 4 control surfaces, how to add a verb, and every fresh-machine gotcha this bridge has hit. Trigger words — kicad bridge dev, edit kicad bridge, add kicad verb, kicad bridge architecture, debug kicad bridge, kicad control surfaces, kicad fresh machine.
---
# kicad-bridge-dev
Developer notes for the `adom-desktop-kicad-bridge`. Source-only (belongs in the repo, never shipped to a user install). Read `adom-desktop-bridge-sdk` first for the AD-core-vs-author boundary.
## What it is
A reverse bridge AD spawns on the user's laptop (`spawn.kind: python`, `entrypoint: server.py`, `port: 0`). It adds the `kicad_*` verb namespace. AD provisions Python (Runtime contract v1.9.63+) and passes `ADOM_BIND_HOST` — bind that, never `0.0.0.0`.
## The 4 control surfaces (how a verb reaches KiCad)
1. **kicad-cli** (`subprocess` → `kicad-cli.exe`) — headless: `pcb drc`, `sch erc`, `pcb/sch export` (gerber/pdf/svg/step/bom), `pcb/sch upgrade`. No GUI. Handlers: `run_drc`, `run_erc`, `lint_*`, `export.py`, `format_upgrade`, `bom.py`, `netlist.py`.
2. **KiCad IPC API (kipy)** — KiCad 9+. Board/schematic introspection + automation (e.g. `place_footprint` confirms via `kipy get_footprints()`). Expands with KiCad 11.
3. **Embedded-Python reverse bridge** (`plugin_payload/adom_bridge.py`) — `usercustomize.py` auto-injects at KiCad's USER_SITE → runs an HTTP server INSIDE each KiCad process, writes a `%TEMP%` discovery file; container-side `handlers/bridge_client.py` POSTs JSON-RPC to `/rpc`, dispatched onto KiCad's wx UI thread via `wx.CallAfter`. Methods: `ping`, `list_frames`, `get_menu_ids`, `wm_command`. Verbs: `bridge_status`, `bridge_call`.
4. **Win32 / UIA / SendKeys** (`handlers/kicad_ui.py`, ctypes/user32) — the last resort: `WM_COMMAND` (open editors), `SendInput`/`keybd_event` (`alt+3`, `ctrl+s`), `PrintWindow`+`EnumWindows` (screenshots), `GetWindowRect`. For opening the 3D viewer, screenshots, clicks, keystrokes, window management.
## Adding a verb (3 places, keep in sync)
1. Write `handle_<verb>` in a handler; return a dict with `success` + a rich **`_hint`** (mandatory — the AI reads verb OUTPUT, not this skill).
2. Register in `server.py` `COMMAND_HANDLERS`.
3. Add `kicad_<verb>` to `bridge.json` `verbs[]` AND an entry (summary/hint/related/pitfalls) to `_VERB_CATALOG` in `server.py` (the `kicad_describe` catalog). A test checks these three stay aligned.
**Dispatcher special cases** (`dispatch_command`): `readiness`, `describe`, `check_for_updates` return BEFORE the plugin auto-install + the KiCad-not-installed pre-check (they must work with KiCad absent). `upgrade` also bypasses that pre-check (it INSTALLS KiCad — do not gate the installer behind "KiCad not installed") and refreshes the detection cache on success.
## Fresh-machine gotchas (all hard-won, all fixed — don't regress them)
- **Install scope is mandatory.** KiCad 10's NsisMultiUser installer: bare `/S` = `rc=666660` (invalid params). Pass `/allusers /S` (elevated) or `/currentuser /S` (per-user, `%LOCALAPPDATA%\Programs\KiCad`, **no UAC**). See `handlers/upgrade.py`.
- **TLS on the portable Python.** AD's provisioned Python has no CA bundle → `CERTIFICATE_VERIFY_FAILED`. We bundle `certs/cacert.pem` (certifi) and load it in `_ssl_context()`. Any new https fetch must use that context.
- **Truncated downloads** read as EOF and pass MZ/size checks, then NSIS fails at install. `upgrade.py` HEADs `Content-Length` and refuses/deletes a size-mismatched file.
- **Config doesn't exist pre-first-launch.** `%APPDATA%/kicad/<ver>/` (lib tables) is created on KiCad's first GUI run. `kicad_detect.ensure_win_user_config()` bootstraps it (seeds tables from the install template) so `install_library`/`_footprint` work on a never-launched KiCad.
- **Detection cache is built once at startup.** After `kicad_upgrade` installs KiCad, `dispatch_command` re-runs `detect_all_kicad_versions()` so subsequent verbs see it without a bridge restart. Detect paths include `%LocalAppData%\Programs\KiCad` (per-user installs).
- **Blocking dialogs stall everything.** `handlers/close_windows.py` scans EVERY window owned by a running KiCad process (by PID — `_win_scan_kicad_dialogs`, reliable; title/owner matching is NOT), auto-expires benign ones (OpenGL notice), screenshots them, and returns hints. `window_info` self-heals by default; `kicad_dismiss_dialogs {all|forceSoftwareCanvas}`.
- **GPU-less hosts.** No OpenGL → the notice pops and pcbnew/3D may not render. On expiring that notice we persist `graphics.canvas_type=2` (Cairo) to `kicad_common.json`. NOTE: a truly minimal VM (Hyper-V, no GPU) may still not render the heavy editors even in Cairo — that's the hardware. Validate GUI/screenshot work on a real-GPU machine.
## Diagnostics you can call
- `kicad_upgrade {"diagnoseOnly":true}` — token elevation type + UAC `EnableLUA` + cached-installer/truncation facts (no install).
- `kicad_dismiss_dialogs {"debug":true}` — raw process-based dialog scan (PIDs + every KiCad window + class + isDialog).
## Testing on a fresh AD / VM
`refresh_bridges {name:kicad}` pulls the new cache; the RUNNING instance keeps its old code until it dies — `bridge_kill {name:kicad}` forces a fresh spawn that loads the new version and re-detects. The first verb after a kill returns a `bridge_starting` envelope — retry. Confirm the running version via `bridge_log_read` ("starting v0.9.xx").