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.76: kicad_launch (cold-start, #19) + kicad_uninstall (SDK cascade, #18) + open_editors teaching hint; SKILL.md CLI-name sweep
10 files changed
+363−38
BRIDGE_VERSION+1−1@@ -1 +1 @@-0.9.75+0.9.76
README.md+3−3@@ -197,10 +197,10 @@ viewer. This is how every 3D shot above exists. ## Verb reference -All 45 verbs (prefix `kicad_`). Call `kicad_describe` for the live catalog with per-verb hints,+All 47 verbs (prefix `kicad_`). Call `kicad_describe` for the live catalog with per-verb hints, related verbs, and pitfalls. -**Windows & UI** — `open_board`, `open_schematic`, `open_symbol_editor`, `open_footprint_editor`,+**Windows & UI** — `launch`, `open_board`, `open_schematic`, `open_symbol_editor`, `open_footprint_editor`, `open_3d_viewer`, `open_editors`, `close_symbol_editor`, `close_footprint_editor`, `close_3d_viewer`, `close`, `window_info`, `dismiss_dialogs`, `screenshot_all`, `send_key`, `click`, `fix_keyboard`, `demo` @@ -212,7 +212,7 @@ related verbs, and pitfalls. `export_molecule` **Detect, install & meta** — `list_versions`, `readiness`, `describe`, `diagnostics`, `status`,-`enable_software_opengl`, `check_for_updates`, `upgrade`, `bridge_status`, `bridge_call`+`enable_software_opengl`, `check_for_updates`, `upgrade`, `uninstall`, `bridge_status`, `bridge_call` ---
SKILL.md+18−16@@ -1,15 +1,15 @@ --- name: adom-desktop-kicad public: true-description: "Launch and drive KiCad on the user's laptop (KiCad 7/8/9/10 supported, multi-version side-by-side) from this container via the adom-desktop CLI. Open schematics/boards/symbols/footprints, run DRC, install sym-lib-table / fp-lib-table libraries, show the 3D viewer, capture KiCad window screenshots, send keyboard shortcuts and click coordinates. Trigger words: kicad, open schematic, open board, open pcb, open symbol, open footprint, KiCad symbol editor, KiCad footprint editor, KiCad 3D viewer, run DRC, install KiCad library, sym-lib-table, fp-lib-table, KiCad screenshot, kicad_send_key, kicad_click, kicad_window_info, kicad_screenshot_all, close KiCad, kicad versions, kicad_list_versions, KiCad bridge."+description: "Launch and drive KiCad on the user's laptop (KiCad 7/8/9/10 supported, multi-version side-by-side) from this container via the adom-bridge-cli CLI. Open schematics/boards/symbols/footprints, run DRC, install sym-lib-table / fp-lib-table libraries, show the 3D viewer, capture KiCad window screenshots, send keyboard shortcuts and click coordinates. Trigger words: kicad, open schematic, open board, open pcb, open symbol, open footprint, KiCad symbol editor, KiCad footprint editor, KiCad 3D viewer, run DRC, install KiCad library, sym-lib-table, fp-lib-table, KiCad screenshot, kicad_send_key, kicad_click, kicad_window_info, kicad_screenshot_all, close KiCad, kicad versions, kicad_list_versions, KiCad bridge." --- -# adom-desktop — KiCad bridge+# adom-bridge-cli — KiCad bridge All commands dispatch to the KiCad Python bridge running alongside the desktop app. Invoke from this container via Bash: ```bash-adom-desktop kicad_<action> '<json_args>'+adom-bridge-cli kicad_<action> '<json_args>' ``` The leading `kicad_` routes the call to the KiCad plugin; the `<action>` names below are the plugin-side command names.@@ -28,6 +28,8 @@ A fresh machine can be missing KiCad, or the reverse-bridge plugin auto-installs **Readiness before you drive:** - `kicad_readiness '{}'` — read-only, zero side effects: is KiCad installed/ready? (`{detected, ready, versionCount, defaultVersion}`.) There's **no prewarm** — KiCad is a host app you install on request via `kicad_upgrade`, not a downloaded runtime asset. - `kicad_describe '{}'` — the full machine-readable verb catalog (the completeness escape hatch; the per-call `_hint`s are what carry you in practice).+- `kicad_launch '{}'` — cold-start KiCad's project manager when nothing is running (idempotent, opens in the background). NOTE: `kicad_open_editors` only LISTS editors in a running KiCad — it never launches; use `kicad_launch` for "open KiCad".+- `kicad_uninstall '{}'` — remove every artifact the bridge created on this machine (plugin, Adom libraries, mesa fallback, caches). Part of Adom Bridge's uninstall cascade; `dryRun:true` previews. - For **"what EDA tools do I have"** across all bridges, use Bridge's `bridge_readiness` (see below). ## Installing KiCad on a fresh machine (hard-won, v0.9.26+)@@ -45,7 +47,7 @@ Users can have multiple KiCad versions installed at the same time. The bridge au ```bash # Discover what's installed (run this first if unsure)-adom-desktop kicad_list_versions '{}'+adom-bridge-cli kicad_list_versions '{}' # → {"versions":[{"version":"10.0", "default":true, ...}, {"version":"9.0", ...}], "default":"10.0", "count":2} ``` @@ -53,10 +55,10 @@ Every `kicad_open_*` (and `install_*`, `run_drc`) command accepts an optional ** ```bash # Open in KiCad 10 (newest — default if you omit kicadVersion)-adom-desktop kicad_open_board '{"filePath":"C:/designs/foo.kicad_pcb"}'+adom-bridge-cli kicad_open_board '{"filePath":"C:/designs/foo.kicad_pcb"}' # Open in KiCad 9 explicitly (e.g. to check that an older project still works)-adom-desktop kicad_open_board '{"filePath":"C:/designs/foo.kicad_pcb","kicadVersion":"9.0"}'+adom-bridge-cli kicad_open_board '{"filePath":"C:/designs/foo.kicad_pcb","kicadVersion":"9.0"}' ``` If you pass a version that's not installed, the bridge returns a hint listing the installed versions. The success response includes `kicadVersionUsed` so you can confirm which install actually ran.@@ -67,8 +69,8 @@ If you pass a version that's not installed, the bridge returns a hint listing th **KiCad is the user's HOST APP. Adom Bridge *detects* it — it won't silently auto-install it as a prewarm/dependency** (Bridge v1.9.47+). But if KiCad isn't found, **do NOT hand the user a manual download** — offer to install it for them and run `kicad_upgrade '{}'`. That verb installs the latest stable from the official downloads.kicad.org installer (silent `/S`, Windows only; may raise a UAC prompt) and **installs from scratch, not just upgrades**. The distinction: Bridge-core never auto-installs a host app, but a user-requested, agent-run install is exactly right. -- To answer **"what EDA tools / host apps do I have?"** across every bridge, call the Bridge-level verb **`adom-desktop bridge_readiness '{}'`** — Bridge aggregates each bridge's host-app detection (KiCad, Fusion 360, …) into one report. Start here for any "do I have X installed?" question.-- For **KiCad specifically**, `adom-desktop kicad_readiness '{}'` is a zero-side-effect read-only probe: `{hostApp, detected, ready, versionCount, defaultVersion, versions[]}`. It does not install the plugin or spawn subprocesses, and works even when KiCad is absent (`detected:false`). Use `kicad_list_versions` when you need the full per-version detail (paths, IPC API, lib tables).+- To answer **"what EDA tools / host apps do I have?"** across every bridge, call the Bridge-level verb **`adom-bridge-cli bridge_readiness '{}'`** — Bridge aggregates each bridge's host-app detection (KiCad, Fusion 360, …) into one report. Start here for any "do I have X installed?" question.+- For **KiCad specifically**, `adom-bridge-cli kicad_readiness '{}'` is a zero-side-effect read-only probe: `{hostApp, detected, ready, versionCount, defaultVersion, versions[]}`. It does not install the plugin or spawn subprocesses, and works even when KiCad is absent (`detected:false`). Use `kicad_list_versions` when you need the full per-version detail (paths, IPC API, lib tables). ## Commands @@ -102,19 +104,19 @@ If you pass a version that's not installed, the bridge returns a hint listing th ```bash # Open a schematic-adom-desktop kicad_open_schematic '{"path":"C:/designs/foo.kicad_sch"}'+adom-bridge-cli kicad_open_schematic '{"path":"C:/designs/foo.kicad_sch"}' # List open KiCad windows-adom-desktop kicad_window_info '{}'+adom-bridge-cli kicad_window_info '{}' # Screenshot every KiCad window-adom-desktop kicad_screenshot_all '{}'+adom-bridge-cli kicad_screenshot_all '{}' # Run DRC-adom-desktop kicad_run_drc '{"board_path":"C:/designs/foo.kicad_pcb"}'+adom-bridge-cli kicad_run_drc '{"board_path":"C:/designs/foo.kicad_pcb"}' # Install a library pair-adom-desktop kicad_install_library '{+adom-bridge-cli kicad_install_library '{ "name":"my-lib", "sym_path":"C:/libs/my-lib.kicad_sym", "fp_path":"C:/libs/my-lib.pretty"@@ -129,10 +131,10 @@ Every failing response includes a `_hint` field with a human-readable next step Earlier versions of the KiCad bridge listened on hardcoded port `8772`. **As of v1.8.31, bridges use OS-assigned ephemeral ports** — every spawn gets a new free port. You never need to know it. -- The CLI (`adom-desktop kicad_*`) automatically routes through adom-desktop's direct API (default port 47200 as of v1.8.33, with auto-fallback to 47201-47209 if 47200 is taken). adom-desktop then forwards to the bridge's actual runtime port.+- The CLI (`adom-bridge-cli kicad_*`) automatically routes through adom-desktop's direct API (default port 47200 as of v1.8.33, with auto-fallback to 47201-47209 if 47200 is taken). adom-bridge-cli then forwards to the bridge's actual runtime port. - The bridge port is internal plumbing and can change every spawn. `bridge_list` reports it as `spawn.runtimePort` for debugging only — do NOT hardcode that anywhere.-- Why: this lets adom-desktop coexist with Hydrogen (which used to claim 8772 itself) and any other tool that grabs ports in the 8000 range. Side-by-side bridges from different apps no longer collide.-- The relay path (Docker → wss proxy → Windows GUI) is unchanged — same `adom-desktop kicad_*` verbs, same JSON shape.+- Why: this lets adom-bridge-cli coexist with Hydrogen (which used to claim 8772 itself) and any other tool that grabs ports in the 8000 range. Side-by-side bridges from different apps no longer collide.+- The relay path (Docker → wss proxy → Windows GUI) is unchanged — same `adom-bridge-cli kicad_*` verbs, same JSON shape. If you ever find yourself wanting to talk directly to the KiCad bridge process: don't. Go through the verb dispatch. If the verb you need doesn't exist, request a feature in `adom-inc/adom-desktop`.
adom-bridge-kicad-manifest.json+5−5@@ -1,15 +1,15 @@ { "manifest_version": 1, "name": "kicad",- "version": "0.9.75",- "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.75/adom-bridge-kicad-v0.9.75.zip",- "sha256": "3a64441f363b2c8c504c7531288f41cbd61063ff6f4eb30428c3954947004722",- "size": 367957,+ "version": "0.9.76",+ "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.76/adom-bridge-kicad-v0.9.76.zip",+ "sha256": "cb2d1a1388017d5b6f5bf14c9720fc079af175c1480f7080e648c19efe2bd460",+ "size": 373323, "verbPrefixes": [ "kicad_" ], "healthEndpoint": "/status",- "released_at": "2026-08-09T10:29:21Z",+ "released_at": "2026-08-13T23:46:06Z", "hero": "https://wiki.adom.inc/blob/app/kicad-bridge/kicad-hero.png", "languages": [ "Python",
adom-bridge-kicad-v0.9.76.zipadded⋯ 1 unchanged line ⋯
bridge.json+6−3@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "kicad", "displayName": "KiCad EDA",- "version": "0.9.75",+ "version": "0.9.76", "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.",@@ -62,7 +62,9 @@ "kicad_export_bom_csv", "kicad_check_for_updates", "kicad_upgrade",- "kicad_demo"+ "kicad_demo",+ "kicad_launch",+ "kicad_uninstall" ], "statusVerb": "kicad_status", "timeouts": {@@ -149,5 +151,6 @@ "Python", "PowerShell" ],- "releasedAt": "2026-06-26T16:00:00Z"+ "releasedAt": "2026-06-26T16:00:00Z",+ "uninstall": "kicad_uninstall" }
handlers/bridge_client.py+17−7@@ -391,23 +391,33 @@ def handle_open_editors(kicad_info: dict, args: dict) -> dict: out_editors.append(row) by_kind.setdefault(row["kind"], []).append(row) + if not out_editors and probed_plugins == 0:+ hint = (+ "0 editors and 0 live plugins: KiCad is almost certainly NOT RUNNING. This verb only "+ "INVENTORIES editors inside a running KiCad (despite its name) — it never launches "+ "anything. To cold-start KiCad use kicad_launch; kicad_open_board / "+ "kicad_open_symbol_editor also cold-launch when opening a specific target."+ )+ else:+ hint = (+ "Cross-process inventory: each KiCad GUI exe's plugin contributes its own "+ "wx top-level frames. If totalEditors=0 but the user has KiCad windows open, "+ "the plugins likely haven't deployed yet OR KiCad was running before "+ "v1.7.5 plugin install (those instances need a restart to load the bridge). "+ "Call kicad_bridge_status to confirm plugins are alive."+ ) return { "success": True, "editors": out_editors, "byKind": by_kind,+ "kicadRunning": probed_plugins > 0 or bool(out_editors), "summary": { "totalEditors": len(out_editors), "probedPlugins": probed_plugins, "failedPlugins": failed_plugins, "byKindCounts": {k: len(v) for k, v in by_kind.items()}, },- "_hint": (- "Cross-process inventory: each KiCad GUI exe's plugin contributes its own "- "wx top-level frames. If totalEditors=0 but the user has KiCad windows open, "- "the plugins likely haven't deployed yet OR KiCad was running before "- "v1.7.5 plugin install (those instances need a restart to load the bridge). "- "Call kicad_bridge_status to confirm plugins are alive."- ),+ "_hint": hint, }
handlers/launch.pyadded+110@@ -0,0 +1,110 @@+"""launch.py — cold-start KiCad (the project manager) from nothing.++Fills the gap found in issue #19: `kicad_open_editors` only INVENTORIES editors+inside already-running KiCad instances, and every other open_* verb launches a+specific editor. This is the verb whose one job is "KiCad is closed; open it."++Windows-first (Popen on kicad.exe with a no-activate startupinfo so the window+opens in the BACKGROUND per the bridge's focus rules); macOS falls back to+`open -a KiCad`.+"""+import os+import subprocess+import sys+import time+from pathlib import Path++IS_WINDOWS = sys.platform == "win32"+++def _kicad_windows() -> list:+ try:+ from handlers.kicad_ui import _find_all_kicad_windows+ return _find_all_kicad_windows()+ except Exception:+ return []+++def handle_launch(kicad_info: dict, args: dict) -> dict:+ """Cold-start KiCad's project manager (idempotent).++ Args (all optional):+ project: path to a .kicad_pro to open with it+ waitSeconds: how long to wait for a KiCad window (default 15)+ """+ existing = _kicad_windows()+ if existing:+ return {+ "success": True,+ "alreadyRunning": True,+ "windows": [w.get("title") for w in existing][:10],+ "output": f"KiCad is already running ({len(existing)} window(s)) — nothing launched.",+ "_hint": ("Idempotent no-op: KiCad was already up. Use kicad_open_editors to inventory "+ "editors, or the open_* verbs to open a specific one."),+ }++ project = (args.get("project") or "").strip()+ if project and not Path(project).exists():+ return {"success": False, "error": f"Project not found: {project}",+ "_hint": "Absolute Windows path required; %VAR% expansion is not performed."}++ if not IS_WINDOWS:+ try:+ cmd = ["open", "-a", "KiCad"] + ([project] if project else [])+ subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)+ return {"success": True, "launched": True,+ "output": "KiCad launch requested via `open -a KiCad`."}+ except Exception as e:+ return {"success": False, "error": f"Failed to launch KiCad: {e}"}++ exe = kicad_info.get("kicad_exe")+ if not exe or not Path(exe).exists():+ return {+ "success": False,+ "error": "kicad.exe not found",+ "_hint": ("No KiCad installation detected. kicad_readiness reports install state; "+ "kicad_upgrade installs KiCad silently (per-user, no UAC)."),+ }++ _si = None+ try:+ from handlers import win_focus+ _si = win_focus.background_startupinfo()+ except Exception:+ pass+ try:+ subprocess.Popen([exe] + ([project] if project else []),+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,+ startupinfo=_si)+ except Exception as e:+ return {"success": False, "error": f"Failed to launch KiCad: {e}"}++ wait_s = float(args.get("waitSeconds") or 15)+ deadline = time.monotonic() + max(3.0, wait_s)+ windows: list = []+ while time.monotonic() < deadline:+ windows = _kicad_windows()+ if windows:+ break+ time.sleep(0.5)++ if windows:+ return {+ "success": True,+ "launched": True,+ "windows": [w.get("title") for w in windows][:10],+ "output": f"KiCad launched — {len(windows)} window(s) up.",+ "_hint": ("Launched in the BACKGROUND (no focus steal). First run on a fresh install "+ "may show a setup wizard — kicad_window_info self-heals benign dialogs; "+ "kicad_screenshot_all shows every window if something looks stuck."),+ "_next": ["kicad_window_info", "kicad_open_editors", "kicad_open_board"],+ }+ return {+ "success": False,+ "error": f"kicad.exe started but no KiCad window appeared within {int(wait_s)}s",+ "_hint": ("Cold start on a slow box (or software-OpenGL) can exceed the wait — retry "+ "kicad_window_info in a few seconds before treating this as a failure. "+ "A modal (crash-recovery / first-run) can also hold the window back: "+ "kicad_screenshot_all will show it."),+ "statusVerb": "kicad_window_info",+ }
handlers/uninstall.pyadded+190@@ -0,0 +1,190 @@+"""uninstall.py — remove everything this bridge ever created on the machine.++Implements the Bridge SDK uninstall contract (kicad-bridge issue #18, ab 2.0.28):+Adom Bridge's uninstaller CASCADES — it calls each bridge's `<prefix>uninstall`+before removing itself — so this verb must leave nothing of ours behind.++Contract requirements honored here:+- IDEMPOTENT + NON-INTERACTIVE: safe when already clean; never prompts, never+ blocks; everything goes to the log/report, not a dialog.+- BEST-EFFORT PER ITEM: one locked file doesn't abort the rest; the report says+ exactly what was and wasn't removed.+- SCOPED TO OUR OWN ARTIFACTS: never touches KiCad itself, other bridges, or+ the user's own projects/data.++What this bridge creates (and this verb removes):+ 1. Plugin payload — adom_bridge.py + usercustomize.py (+ their __pycache__)+ in Documents/KiCad/<ver>/3rdparty/Python311/site-packages/+ 2. Adom symbol lib — Documents/KiCad/<ver>/symbols/Adom.kicad_sym + its+ "Adom" row in %APPDATA%/kicad/<ver>/sym-lib-table+ 3. Adom footprints — Documents/KiCad/<ver>/footprints/Adom.pretty/ + its+ "Adom" row in fp-lib-table+ 4. Mesa fallback — opengl32.dll / libgallium_wgl.dll / dxil.dll (+ *.local)+ that kicad_enable_software_opengl dropped into a KiCad bin/ (KiCad never+ ships these in bin, so presence == our deploy)+ 5. Discovery files — %TEMP%/adom-kicad-bridge-*.json+ 6. Screenshot dir — C:/tmp/adom-desktop-screenshots+ 7. Demo project — Documents/adom-kicad-demo (generated by kicad_demo)++Deliberately NOT removed: the user's KiCad config beyond our two lib-table rows,+any user project, KiCad itself.+"""+import os+import shutil+import sys+import tempfile+from pathlib import Path++IS_WINDOWS = sys.platform == "win32"++_MESA_DLLS = ("opengl32.dll", "libgallium_wgl.dll", "dxil.dll")+++def _kicad_user_roots() -> list[Path]:+ """Documents/KiCad/<ver> dirs (all versions, not just the active one)."""+ up = os.environ.get("USERPROFILE")+ if not up:+ return []+ root = Path(up) / "Documents" / "KiCad"+ if not root.is_dir():+ return []+ return [d for d in root.iterdir() if d.is_dir()]+++def _kicad_config_roots() -> list[Path]:+ """%APPDATA%/kicad/<ver> dirs."""+ ap = os.environ.get("APPDATA")+ if not ap:+ return []+ root = Path(ap) / "kicad"+ if not root.is_dir():+ return []+ return [d for d in root.iterdir() if d.is_dir()]+++def _kicad_bin_dirs(kicad_info: dict) -> list[Path]:+ """Every KiCad bin/ we can find (all installs, both scopes)."""+ dirs: list[Path] = []+ bd = kicad_info.get("bin_dir")+ if bd:+ dirs.append(Path(bd))+ for env, sub in (("ProgramFiles", "KiCad"), ("LOCALAPPDATA", "Programs/KiCad")):+ base = os.environ.get(env)+ if not base:+ continue+ root = Path(base) / sub+ if root.is_dir():+ for ver in root.iterdir():+ b = ver / "bin"+ if b.is_dir():+ dirs.append(b)+ seen, out = set(), []+ for d in dirs:+ k = str(d).lower()+ if k not in seen:+ seen.add(k)+ out.append(d)+ return out+++def handle_uninstall(kicad_info: dict, args: dict) -> dict:+ """Remove every artifact this bridge created. Args: dryRun (default false)."""+ dry = bool(args.get("dryRun"))+ removed: list[str] = []+ failed: list[dict] = []+ absent = 0++ def rm_file(p: Path, label: str):+ nonlocal absent+ try:+ if not p.exists():+ absent += 1+ return+ if not dry:+ p.unlink()+ removed.append(f"{label}: {p}")+ except Exception as e:+ failed.append({"item": f"{label}: {p}", "error": str(e)})++ def rm_tree(p: Path, label: str):+ nonlocal absent+ try:+ if not p.exists():+ absent += 1+ return+ if not dry:+ shutil.rmtree(p, ignore_errors=False)+ removed.append(f"{label}: {p}")+ except Exception as e:+ failed.append({"item": f"{label}: {p}", "error": str(e)})++ # 1) plugin payload (+ pycache), 2) Adom symbol lib file, 3) Adom.pretty+ for ver_dir in _kicad_user_roots():+ site = ver_dir / "3rdparty" / "Python311" / "site-packages"+ rm_file(site / "adom_bridge.py", "plugin payload")+ rm_file(site / "usercustomize.py", "plugin loader")+ pyc = site / "__pycache__"+ if pyc.is_dir():+ for f in pyc.glob("adom_bridge*.pyc"):+ rm_file(f, "plugin pycache")+ for f in pyc.glob("usercustomize*.pyc"):+ rm_file(f, "plugin pycache")+ rm_file(ver_dir / "symbols" / "Adom.kicad_sym", "Adom symbol library")+ rm_tree(ver_dir / "footprints" / "Adom.pretty", "Adom footprint library")++ # lib-table rows (sym + fp) in every config version+ try:+ from lib_table import LibTable+ for cfg in _kicad_config_roots():+ for table_name in ("sym-lib-table", "fp-lib-table"):+ tp = cfg / table_name+ if not tp.exists():+ absent += 1+ continue+ try:+ table = LibTable.parse_file(str(tp))+ if table.has_library("Adom"):+ if not dry:+ table.remove_library("Adom")+ table.write_file(str(tp))+ removed.append(f"'Adom' row in {tp}")+ else:+ absent += 1+ except Exception as e:+ failed.append({"item": f"'Adom' row in {tp}", "error": str(e)})+ except Exception as e:+ failed.append({"item": "lib-table cleanup", "error": str(e)})++ # 4) Mesa software-OpenGL deploy+ for b in _kicad_bin_dirs(kicad_info):+ for dll in _MESA_DLLS:+ rm_file(b / dll, "mesa fallback")+ rm_file(b / (dll + ".local"), "mesa fallback marker")+ for loc in b.glob("*.exe.local"):+ rm_file(loc, "mesa dotlocal")++ # 5) discovery files+ tmp = Path(tempfile.gettempdir())+ for f in tmp.glob("adom-kicad-bridge-*.json"):+ rm_file(f, "plugin discovery file")++ # 6) screenshot cache, 7) demo project+ rm_tree(Path("C:/tmp/adom-desktop-screenshots"), "screenshot cache")+ up = os.environ.get("USERPROFILE")+ if up:+ rm_tree(Path(up) / "Documents" / "adom-kicad-demo", "demo project")++ ok = len(failed) == 0+ return {+ "success": ok,+ "dryRun": dry,+ "removed": removed,+ "failed": failed,+ "alreadyClean": len(removed) == 0 and ok,+ "summary": f"{'would remove' if dry else 'removed'} {len(removed)} item(s), "+ f"{len(failed)} failed, {absent} already absent",+ "_hint": ("Bridge-created artifacts only — KiCad itself, user projects, and other bridges are "+ "untouched. Idempotent: safe to re-run. A running KiCad may hold the plugin's "+ ".pyc open; re-run after KiCad exits if anything failed. Bridge removes its own "+ "bridges-cache during the cascade."),+ }
server.py+13−3@@ -44,6 +44,8 @@ from handlers.run_drc import handle_run_drc from handlers.fix_keyboard import handle_fix_keyboard from handlers.kicad_ui import handle_screenshot_all, handle_send_key, handle_click from handlers.bridge_client import handle_bridge_status, handle_bridge_call, handle_open_editors+from handlers.launch import handle_launch+from handlers.uninstall import handle_uninstall from handlers.kicad_cli_lint import ( handle_run_erc, handle_lint_board, handle_lint_schematic, handle_lint_library, handle_format_upgrade,@@ -312,8 +314,14 @@ _VERB_CATALOG = { "hint": "Low-level IPC into a live pcbnew/eeschema; use bridge_status first to find a live instance.", "related": ["kicad_bridge_status", "kicad_install_plugin"], "pitfalls": ["fails if no plugin instance is live — open a board first"]}, "open_editors": {"summary": "List currently open KiCad editor windows.", "long": False,- "hint": "Read-only; which editors are open right now. Use to reuse a window instead of opening a duplicate.",- "related": ["kicad_window_info", "kicad_screenshot_all", "kicad_close"], "pitfalls": []},+ "hint": "Read-only INVENTORY (despite the name): lists editors inside a running KiCad. It never launches anything — to cold-start KiCad use kicad_launch.",+ "related": ["kicad_launch", "kicad_window_info", "kicad_screenshot_all", "kicad_close"], "pitfalls": ["with KiCad closed it succeeds with 0 editors — that is not an error; kicad_launch is the verb that starts KiCad"]},+ "launch": {"summary": "Cold-start KiCad (project manager) when nothing is running.", "long": True,+ "hint": "Idempotent: no-op with a hint if KiCad is already up. Opens in the BACKGROUND (no focus steal). Optional project: path to a .kicad_pro.",+ "related": ["kicad_open_editors", "kicad_window_info", "kicad_readiness", "kicad_upgrade"], "pitfalls": ["first launch on a fresh install can show a setup wizard — kicad_window_info self-heals benign dialogs"]},+ "uninstall": {"summary": "Remove every artifact this bridge created on the machine (SDK uninstall cascade).", "long": False,+ "hint": "Idempotent, non-interactive, best-effort per item. Removes plugin payload, Adom libraries + lib-table rows, mesa fallback DLLs, discovery files, screenshot cache, demo project. Never touches KiCad itself or user projects. dryRun:true previews.",+ "related": ["kicad_bridge_status"], "pitfalls": ["a running KiCad can hold plugin .pyc files open — re-run after it exits if items fail"]}, "export_gerber": {"summary": "Export Gerbers from a board via kicad-cli.", "long": False, "hint": "Absolute board path + output dir; writes Gerber + drill files (JLCPCB/InstaPCB ready).", "related": ["kicad_export_step", "kicad_export_bom_csv", "kicad_run_drc"], "pitfalls": ["run kicad_run_drc first — don't ship Gerbers from a board with DRC violations"]},@@ -470,6 +478,8 @@ COMMAND_HANDLERS = { "status": handle_bridge_status, "bridge_call": handle_bridge_call, "open_editors": handle_open_editors,+ "launch": handle_launch,+ "uninstall": handle_uninstall, "install_plugin": plugin_install.handle_install_plugin, # v1.7.12+: headless lint pre-checks via kicad-cli "run_erc": handle_run_erc,@@ -654,7 +664,7 @@ def dispatch_command(command: str, args: dict) -> dict: # Only verbs that actually SPAWN a window get the ~3s settle-poll (they can # trigger an async dialog). Read-only reporters (open_editors, screenshot_all, # window_info) don't spawn windows → one cheap scan, no needless 3s poll.- _SPAWNS_WINDOW = {"open_board", "open_schematic", "open_symbol_editor",+ _SPAWNS_WINDOW = {"launch", "open_board", "open_schematic", "open_symbol_editor", "open_footprint_editor", "open_3d_viewer", "place_footprint", "install_symbol", "install_footprint", "install_plugin", "upgrade",