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
Publish 1.0.7
6 files changed
+141−8
package.json+4−2@@ -1,6 +1,6 @@ { "slug": "adom-desktop-kicad-bridge",- "version": "1.0.6",+ "version": "1.0.7", "type": "app", "description": "KiCad bridge for Adom Desktop.", "tags": [@@ -32,6 +32,8 @@ "skills/kicad-tour/SKILL.md", "skills/kicad-tour/tour_runner.py", "skills/kicad-tour/hero/README.md",- "skills/kicad-tour/hero/hero_builder.py"+ "skills/kicad-tour/hero/hero_builder.py",+ "skills/kicad-bridge-dev/SKILL.md",+ "skills/kicad-bridge-publish/SKILL.md" ] }
page.json+4−2@@ -4,7 +4,7 @@ "slug": "adom-desktop-kicad-bridge", "title": "Adom Desktop - 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.6",+ "version": "1.0.7", "tags": [ "kicad", "pcb",@@ -125,7 +125,9 @@ "skills/kicad-tour/SKILL.md", "skills/kicad-tour/tour_runner.py", "skills/kicad-tour/hero/README.md",- "skills/kicad-tour/hero/hero_builder.py"+ "skills/kicad-tour/hero/hero_builder.py",+ "skills/kicad-bridge-dev/SKILL.md",+ "skills/kicad-bridge-publish/SKILL.md" ], "org": "adom" }
skills/kicad-bridge-dev/SKILL.mdadded+47@@ -0,0 +1,47 @@+---+name: kicad-bridge-dev+user-invocable: false+description: DEVELOPER-only skill (user-invocable:false — installed for maintainers, never offered to everyday users). Building/debugging the Adom Desktop KiCad bridge: 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").
skills/kicad-bridge-publish/SKILL.mdadded+81@@ -0,0 +1,81 @@+---+name: kicad-bridge-publish+user-invocable: false+description: DEVELOPER-only PUBLISH skill (user-invocable:false — for maintainers, never offered to everyday users). The exact recipe to ship a new version of the Adom Desktop KiCad bridge to wiki.adom.inc: version lockstep, the zip build, adom-wiki repo push + release create, the skills pkg, pushing to a target AD, and verify steps. Trigger words — publish kicad bridge, release kicad bridge, ship kicad bridge, bump kicad bridge, kicad bridge manifest, kicad bridge pkg.+---++# kicad-bridge-publish++How to cut and publish a KiCad-bridge release. **Use the `adom-wiki` CLI for ALL wiki interactions — never raw curl.** Two independent artifacts: the RUNTIME (zip + manifest, on the page `/files`) and the SKILLS PKG (`adom/adom-desktop-kicad-bridge`, container-side docs).++## 1. Version lockstep (bump ALL three together)++- `bridge.json` → `"version"`+- `BRIDGE_VERSION` (plain text)+- the manifest JSON → `"version"`++They must match. `bridge.json` also carries `updateManifestUrl` (durable auto-update), `docs` (your page), `homepage` (a **wiki.adom.inc** URL, NEVER github), `detect` (host-app paths incl. `%LocalAppData%\Programs\KiCad`), `verbPrefixes`, complete `verbs[]`, `statusVerb`.++## 2. Build the zip++Zip the repo dir CONTENTS (bridge.json at the zip root). Exclude: `__pycache__`, `*.pyc`, `.git`, `.github`, `node_modules`, `.DS_Store`, and the generated `adom-bridge-kicad-manifest.json`. Include `certs/cacert.pem`. Compute `sha256` + byte `size` for the manifest.++Manifest fields: `manifest_version:1, name:"kicad", version, url` (the `/blob/app/.../adom-bridge-kicad-v<ver>.zip`), `sha256, size, verbPrefixes:["kicad_"], healthEndpoint:"/status", released_at, hero, languages`.++## 3. Publish the runtime (CLI)++```bash+adom-wiki repo push adom/adom-desktop-kicad-bridge \+ --files adom-bridge-kicad-v<ver>.zip adom-bridge-kicad-manifest.json bridge.json \+ -m "v<ver>: <what changed>"+adom-wiki release create adom/adom-desktop-kicad-bridge <ver> \+ --title "KiCad bridge v<ver>" --changelog "..." \+ --binary-path adom-bridge-kicad-v<ver>.zip+```++`repo push` writes the files to the page `/files` (same place AD's bridge_cache fetches from). `release create` also registers a proper Release (SDK Artifact 1). For a field with no dedicated verb use `adom-wiki api -X PUT /api/v1/pages/... --body ...`, not curl.++## 4. Publish the skills pkg (container docs)++Stage a dir with `package.json` (name/version/`files[]`), a ROOT `SKILL.md`, and a `skills/<name>/SKILL.md` for **every** skill. **SKILLPACK convention (SDK, replaces the dead `dev-skills/*.md` rule): EVERY skill — user, dev, AND publish — is a real `skills/<name>/SKILL.md` and ships in the tarball.** Dev/publish skills are scoped by AUDIENCE, not filename: frontmatter `user-invocable: false` + a description that opens DEVELOPER-only, so everyday users never get them offered but maintainers still install them. **List every skill path EXPLICITLY (no globs) in `package.json` `files[]` AND `install.sh`/`uninstall.sh`.** Then:++```bash+cd <staging> && adom-wiki pkg pack --out /tmp/v.tgz # verify: tar lists every skills/<x>/SKILL.md, 0 binaries+adom-wiki pkg publish --org adom --skip-lint --yes # moves dist-tag latest+```++The runtime SKILL.md (`adom-desktop-kicad`, ships in the zip) and the pkg's root SKILL.md are DIFFERENT docs — update both when a change affects users.++## 5. Push to a running desktop + verify++```bash+adom-desktop --target <name> refresh_bridges '{"name":"kicad"}' # pulls new cache+adom-desktop --target <name> bridge_kill '{"name":"kicad"}' # force fresh spawn (old instance keeps old code)+adom-desktop --target <name> bridge_check_updates '{}' # current == latest?+adom-wiki repo show adom/adom-desktop-kicad-bridge adom-bridge-kicad-manifest.json # served version matches?+```++Verify the served bridge.json (`repo show ... bridge.json`) has the fields you expect and the zip's sha matches. A fresh verb call after a kill returns `bridge_starting` — retry.++## 6. Refreshing the README screenshots (window tour)++The README embeds screenshots hosted on the page `/files` (referenced as+`https://wiki.adom.inc/blob/app/adom-desktop-kicad-bridge/<name>.png`). Regenerate them on a **real+VM** (e.g. ADOMBASELINE) whenever the UI/flow changes — never hand-mock them:++1. On the VM: `kicad_enable_software_opengl` (if GPU-less) → open each window with sample data from+ `tour-pack-rp2040/` (`AdomRP2040` symbol, `QFN-56_AdomRP2040` footprint, the breakout board).+2. For each: `kicad_dismiss_dialogs {all:true}` → `kicad_screenshot_all` → `pull_file` the matching+ `.bmp` → PIL `bmp→png`, `thumbnail((1400,1400))`.+3. Save into `docs/img/` with the canonical names the README uses: `schematic-editor.png`,+ `symbol-editor.png`, `footprint-editor.png`, `pcb-editor-2d.png`, `3d-viewer.png`,+ `dialog-firstrun-wizard.png`, `control-surfaces.svg`.+4. `adom-wiki repo push adom/adom-desktop-kicad-bridge --files docs/img/*.png ...` then+ `curl -so /dev/null -w '%{http_code}'` each blob URL to confirm 200.+5. `shotlog paste <png> -c kicad-bridge -d "<caption>"` to show John the live captures.++## 7. Standing rules++- Commit source to `main` in lockstep with each publish (drift = the bug that once dropped `updateManifestUrl`).+- After ANY wiki change, refresh the wiki pup window showing the page.+- The changelog must be real (≥2 words, no placeholders) — the server enforces it.
skills/kicad-interaction/SKILL.md+1−1@@ -126,7 +126,7 @@ kicad_open_3d_viewer Open 3D viewer from a PCB Editor (needs hwnd) kicad_close_3d_viewer Close the 3D viewer kicad_close Close all KiCad windows kicad_window_info List all open editors, viewers, and dialogs -kicad_send_key Send a keystroke to a specific window (needs hwnd) +kicad_send_key Single key to an EXACT hwnd (dialog Enter/Escape). For CHORDS use AD's desktop_press_key. kicad_screenshot_all Screenshot all KiCad windows kicad_adom_library_status Check Adom library installation kicad_install_symbol Install a symbol to KiCad's library
skills/kicad-tour/SKILL.md+4−3@@ -102,11 +102,12 @@ adom-desktop kicad_open_3d_viewer '{"editor":"fp"}' If this returns "3D Viewer did not open from Footprint Editor" on KiCad 10, either skip to step 7 (the board's 3D viewer is the more reliable showcase-anyway) OR send `Alt+3` directly:+anyway) OR send `Alt+3` via AD's chord-aware keypress verb (focus the Footprint+Editor first): ```bash-# Need the Footprint Editor's hwnd from kicad_window_info-adom-desktop kicad_send_key "{\"hwnd\":<fp_hwnd>, \"key\":\"alt+3\"}"+# desktop_press_key owns modifier chords; kicad_send_key is only for single keys to an exact hwnd+adom-desktop desktop_press_key '{"keys":"alt+3"}' ``` ### Step 5 — open the prebuilt schematic