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
0.9.278: Wiki #72: kicad_install_library handed a single-part .kicad_sym now returns singlePartFile:true plus a hint naming kicad_install_symbol {filePath}, on both the fresh and already-registered success paths; a real multi-symbol library gets no hint. The verb still does what it did (registers the file as its own library); it just stops being silent about the shared Adom library being untouched.
4 files changed
+44−4
BRIDGE_VERSION+1−1@@ -1 +1 @@-0.9.277+0.9.278
SKILL.md+1@@ -38,6 +38,7 @@ A fresh machine can be missing KiCad, or the reverse-bridge plugin auto-installs - **Install scope is mandatory in silent mode.** KiCad 10's installer is NsisMultiUser: bare `/S` exits instantly with **`rc=666660`** ("invalid command-line parameters") and installs nothing. The bridge auto-picks the scope: **`/allusers`** when the Bridge process is elevated (installs to Program Files), else **`/currentuser`** (installs to `%LOCALAPPDATA%\Programs\KiCad` — **no admin, no UAC prompt at all**). Force it with `kicad_upgrade '{"scope":"user"}'` — that's your answer to *"install KiCad without a UAC prompt."* (Elevation note: on a VM/kiosk the Bridge process may already be elevated even though nobody clicked UAC — an elevated installer that launches the app hands down its admin token. `kicad_upgrade '{"diagnoseOnly":true}'` reports the real token/UAC facts.) - **Library installs work before KiCad's first launch.** `%APPDATA%/kicad/<ver>/` (the per-user lib tables) doesn't exist until KiCad's first GUI run, so `kicad_install_library`/`_footprint` used to fail *"config directory not found."* The bridge now bootstraps that config (seeding the lib tables from the install template) automatically — installs just work on a never-launched KiCad.+ - From 0.9.278: handed a single-part `.kicad_sym`, it still succeeds but returns `singlePartFile: true` and a hint naming `kicad_install_symbol {"filePath": ...}`, because that call registers the PART as its own library and leaves Adom untouched (wiki #72). **GPU-less hosts (VM/RDP/server):** KiCad pops a blocking *"Could not use OpenGL"* dialog and the heavy editors may not render. The bridge auto-expires that dialog and switches KiCad to the Cairo software canvas — see **[kicad-interaction](skills/kicad-interaction/SKILL.md)** for the dialog/canvas details.
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "kicad", "displayName": "KiCad EDA",- "version": "0.9.277",+ "version": "0.9.278", "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/install_library.py+41−2@@ -276,6 +276,13 @@ def handle_install_library(kicad_info: dict, args: dict) -> dict: if not lib_name: lib_name = source.stem + # Wiki #72: a caller who wants "put this part into the Adom library" often+ # reaches for install_library with a single-part .kicad_sym. This verb then+ # registers that PART as its own library and the shared Adom library stays+ # empty, and every call says ok. Say so on the success path and name the+ # right verb; do not refuse, because what this verb did is not wrong.+ wrong_tool = _single_part_hint(source, lib_name)+ type_config = LIB_TYPE_CONFIG.get(lib_type) if not type_config: return {@@ -342,7 +349,9 @@ def handle_install_library(kicad_info: dict, args: dict) -> dict: "success": True, "alreadyRegistered": True, "output": f"Library '{lib_name}' already registered in {type_config['table_file']} (file refreshed at {dest}).",- "_hint": "Library was already present — nothing to change. This is a successful no-op.",+ "_hint": "Library was already present — nothing to change. This is a successful no-op."+ + (" " + wrong_tool["_hint"] if wrong_tool else ""),+ **(wrong_tool or {}), } table.add_library(entry)@@ -363,5 +372,35 @@ def handle_install_library(kicad_info: dict, args: dict) -> dict: f".kicad_sym surprises in ~100ms — much faster than discovering a broken " f"library inside the GUI later. Any KiCad windows already open need to be " f"closed + reopened to pick up the new library entry (sym-lib-table is read at startup)."- ),+ ) + ((" " + wrong_tool["_hint"]) if wrong_tool else ""),+ **(wrong_tool or {}), }+++def _single_part_hint(source: Path, lib_name: str) -> dict | None:+ """Return {'singlePartFile', 'symbolNames', '_hint'} when `source` is one+ .kicad_sym holding a single symbol (or a symbol named after the file), the+ shape that means the caller wanted kicad_install_symbol. None otherwise."""+ try:+ if source.is_dir() or source.suffix.lower() != ".kicad_sym":+ return None+ from adom_library import list_symbols+ names = list_symbols(Path(source)) or []+ except Exception:+ return None+ if not names or len(names) > 3:+ return None+ if len(names) == 1 or lib_name in names:+ shown = ", ".join(names)+ return {+ "singlePartFile": True,+ "symbolNames": names,+ "_hint": (+ f"NOTE: this file holds {len(names)} symbol(s) ({shown}), so KiCad now has a whole "+ f"library named '{lib_name}' containing only that part, and the shared Adom library was NOT touched. "+ f"If you meant 'put this part into the Adom library', the verb is kicad_install_symbol "+ f"'{{\"filePath\":\"{str(source).replace(chr(92), '/')}\"}}' (then kicad_show_library Adom). "+ f"Both are valid; only one matches what a shared-library caller usually means."+ ),+ }+ return None