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.20
6 files changed
+260−10
install.sh+11−3@@ -1,6 +1,14 @@ #!/usr/bin/env bash set -euo pipefail H="$(cd "$(dirname "$0")"&&pwd)"-mkdir -p "$HOME/.claude/skills/adom-desktop-kicad-bridge"-cp -f "$H/SKILL.md" "$HOME/.claude/skills/adom-desktop-kicad-bridge/SKILL.md"-echo "[adom-desktop-kicad-bridge] skill installed."+D="$HOME/.claude/skills"+mkdir -p "$D/kicad-bridge"+cp -f "$H/SKILL.md" "$D/kicad-bridge/SKILL.md"+for sk in kicad-interaction kicad-3d-models kicad-tour kicad-bridge-dev kicad-bridge-publish kicad-bridge-hero; do+ mkdir -p "$D/$sk"+ cp -f "$H/skills/$sk/SKILL.md" "$D/$sk/SKILL.md"+done+cp -f "$H/skills/kicad-tour/tour_runner.py" "$D/kicad-tour/tour_runner.py"+cp -f "$H/skills/kicad-bridge-hero/compose_hero.py" "$D/kicad-bridge-hero/compose_hero.py"+rm -rf "$D/adom-desktop-kicad-bridge" # legacy install dir from pre-rename versions+echo "[kicad-bridge] skills installed."
package.json+6−3@@ -1,6 +1,6 @@ { "slug": "kicad-bridge",- "version": "1.0.19",+ "version": "1.0.20", "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": [@@ -24,10 +24,13 @@ "package.json", "install.sh", "uninstall.sh",- "page.json", "skills/kicad-interaction/SKILL.md", "skills/kicad-3d-models/SKILL.md", "skills/kicad-tour/SKILL.md",- "skills/kicad-tour/tour_runner.py"+ "skills/kicad-tour/tour_runner.py",+ "skills/kicad-bridge-dev/SKILL.md",+ "skills/kicad-bridge-publish/SKILL.md",+ "skills/kicad-bridge-hero/SKILL.md",+ "skills/kicad-bridge-hero/compose_hero.py" ] }
page.json+6−3@@ -4,7 +4,7 @@ "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.19",+ "version": "1.0.20", "tags": [ "kicad", "pcb",@@ -116,11 +116,14 @@ "package.json", "install.sh", "uninstall.sh",- "page.json", "skills/kicad-interaction/SKILL.md", "skills/kicad-3d-models/SKILL.md", "skills/kicad-tour/SKILL.md",- "skills/kicad-tour/tour_runner.py"+ "skills/kicad-tour/tour_runner.py",+ "skills/kicad-bridge-dev/SKILL.md",+ "skills/kicad-bridge-publish/SKILL.md",+ "skills/kicad-bridge-hero/SKILL.md",+ "skills/kicad-bridge-hero/compose_hero.py" ], "org": "adom", "name": "kicad-bridge"
skills/kicad-bridge-hero/SKILL.mdadded+111@@ -0,0 +1,111 @@+---+name: kicad-bridge-hero+user-invocable: false+description: DEVELOPER-only skill (user-invocable:false — maintainers, never everyday users). The complete recipe for making/refreshing the kicad-bridge page hero in the Adom bridge-family style (matching Pup and Fusion), getting it through the Hero Studio human gate, and applying it to the page + pkg. Encodes every measured layout number, the brand font doctrine, the real-window vitrine capture workflow, and the gate/publish traps we hit shipping the first one. Trigger words — kicad hero, hero image, new hero, refresh hero, hero studio, HERO_STUDIO_REQUIRED, HERO_REQUIRED, bridge family hero, page billboard.+---++# kicad-bridge-hero++How the kicad-bridge hero gets made. First shipped 2026-08-09 after ~14 iterations;+this skill exists so the next one takes two. Full papercut list we filed upstream:+**adom/hero-studio issue #566** — read it before assuming the studio guides you.++## The one-command core++```bash+python3 skills/kicad-bridge-hero/compose_hero.py <vitrine-capture.png> /tmp/hero-vNN.png+```++`compose_hero.py` (in this directory) clones Pup's measured geometry, renders the+title/subtitle, drops in your capture, and **asserts the ink alignment** before saving.+It needs `reference-pup-hero.png` — Pup's true published asset, the source of the logo+crop, pill crop, background gradient corners, and every layout number. It lives in the+kicad-bridge **page repo** next to this skill (not in the pkg tarball — no-images rule);+if missing locally: `adom-wiki repo download pup-bridge --extract /tmp/pup-repo` and+copy `hero-v9.png` in (NOT `repo show` — it mangles binaries to UTF-8).++## Brand doctrine (things John corrected us on — do not rediscover)++- **Title font is Familjen Grotesk BOLD (700)**, not Satoshi. The rule lives in+ hero-studio's own billboard CSS (`.h-title`), nowhere else. Satoshi is for the+ subtitle (inherited from body) and UI chrome. Fonts live in+ `skills/kicad-tour/hero/fonts/`.+- **Title is just "KiCad"** — the one word users type to their AI. The wiki page+ title ("KiCad - the KiCad Bridge") carries the rest dynamically.+- **One pill, baked, at Pup's exact spot** (crop `(1796,36,1964,108)` from the+ reference, pasted verbatim). No second pill, no baked wiki URL: the homepage+ overlay adds its own dynamic pill/slug at render time.+- **Left ink edges must be logo 79 / title 97 / subtitle 83** (Pup's exact stagger).+ Fonts lie about bearings (Familjen reported 0 for "K", drew 16px) — always+ measure rendered pixels and correct; the script does this automatically.+- **Vitrine = Pup's floating-window rect**: (920,150), 1030x1025, radius 24, soft+ shadow. It does NOT bleed any edge (we assumed bleed for days; measurement says no).+- NO em-dashes in any hero/subtitle copy.++## Vitrine capture (real window, never a mockup)++1. Board: `tour-pack-rp2040/template/rp2040-breakout.kicad_pcb` (the annotated+ J1 USB-C / U1 QFN / Y1 crystal board). Ship it over if needed:+ `send_files {"filePaths":[...], "dest":"C:/Users/<u>/Documents/adom-kicad-rp2040"}`+ (the arg is `dest`, not destDir — wrong arg silently lands in Downloads).+2. `kicad_open_board {"filePath":...}` (absolute Windows path; no %VAR% expansion),+ then `kicad_open_3d_viewer`.+3. **Resize the 3D viewer to the slot before capturing** so the capture drops in+ 1:1 with zero scaling: `desktop_set_window_bounds {"hwnd":N,"x":200,"y":20,"w":1030,"h":1025}`+ (args are w/h, not width/height).+4. Dark mode: Windows OS dark mode gives KiCad 10 dark titlebar/menus/panels. The+ 3D viewport background is theme-bound (`_builtin_default` isn't editable), so it+ stays lavender — that's fine; Pup's vitrine also pairs a dark card with a light+ window interior.+5. A human-posed camera angle beats anything scripted. Ask John to frame it, then+ `desktop_screenshot_window {"hwnd":N}` — the PNG lands in the container at+ `screenshots/adom-bridge/window/`. Zoom-to-fit is a toolbar click+ (`kicad_click {"hwnd":N,"x":375,"y":100,"relative":false}`); there is no Home-key fit.++## Hero Studio gate (mandatory — registry enforces provenance)++Only the studio's export passes `HERO_STUDIO_REQUIRED`; a human must click Generate.++```bash+node <hero-studio>/server.js --port 88XX & # OWN port; check `ps` first — port+ # collisions once stomped pup's gate+curl -X POST :88XX/state -d '{"mode":"app","page":"adom/kicad-bridge", ...,+ "overlay":{"pill":false,"scores":false,"author":false,"slug":false}}'+hero-studio vitrine --file /tmp/hero-vNN.png --full --port 88XX+```++Traps (all in issue #566):+- **Turn the overlay sim OFF via /state** (above). Its flags default ON, persist+ server-side across reloads, and draw a fake pill + slug URL over your hero —+ three review rounds were lost to "why 2 pills / why a wiki url".+- The reviewer views the gate through **pup**: open the container-proxy URL in a+ session YOU own, `foreground:true` needs a `foregroundReason` quoting the user.+ Signal updates with `pup_alert_window` (taskbar flash), never re-foreground.+- "broken_images_6" on import = empty placeholder slots, not your hero. Verify with+ one screenshot, don't chase it.+- Page renamed recently? The studio caches page data at boot: run its+ `refresh-data.sh`, restart your instance, re-POST state + re-import the vitrine+ (restart wipes in-memory state AND the reviewer's ticked checkboxes — warn them).++## Apply after Generate++```bash+cp <hero-studio>/generated/hero-kicad-bridge-<ts>.png kicad-bridge-hero-vN.png+adom-wiki repo push adom/kicad-bridge --files kicad-bridge-hero-vN.png -m "hero vN: ..."+adom-wiki page hero adom/kicad-bridge --image kicad-bridge-hero-vN.png+```++The generated PNG carries a `tEXt` provenance comment (`generator: hero-studio`) —+that's what the registry checks. Keep the exact file; recompressing strips it.++Then refresh the live page in the reviewer's pup window (cache-bust query param)+and verify the render with a screenshot — never just say "applied".++## Pkg gotcha that looked like a hero problem++`pkg publish` failing `HERO_STUDIO_REQUIRED` with `"reason":"no_hero"` ++`"new_page":true` means your staging `package.json` still has a **stale slug**+(ours said `adom-desktop-kicad-bridge` post-rename, so the registry saw a hero-less+new page). Fix the slug; the pkg needs no `hero` field at all — the check reads the+page. Also: stage from a clean dir; a stale `page.json` overlaid from an old tarball+draws a `PAGE_JSON_IGNORED` warning and can resurrect deleted files.
skills/kicad-bridge-hero/compose_hero.pyadded+120@@ -0,0 +1,120 @@+#!/usr/bin/env python3+"""Compose the kicad-bridge hero in the Adom bridge-family style (Pup/Fusion/KiCad).++Clones the Pup hero's measured geometry exactly, drops in a fresh vitrine capture,+and ASSERTS the ink alignment before saving. See SKILL.md in this directory for the+full workflow (capture -> compose -> Hero Studio gate -> apply).++Usage:+ python3 compose_hero.py <vitrine-capture.png> <out.png>++The vitrine capture should be a real KiCad window screenshotted at exactly+1030x1025 (resize it first with desktop_set_window_bounds {"w":1030,"h":1025}).+Anything else gets LANCZOS-scaled, which softens UI text.+"""+import sys+from pathlib import Path++import numpy as np+from PIL import Image, ImageDraw, ImageFilter, ImageFont++HERE = Path(__file__).parent+PUP = HERE / "reference-pup-hero.png" # Pup's true published asset (hero-v9)+FONTS = HERE.parent / "kicad-tour" / "hero" / "fonts"++W, H = 2000, 1250+TITLE = "KiCad"+SUB_1A, SUB_1B = "Your real KiCad, ", "driven by AI"+SUB_2A, SUB_2B = "entirely in the ", "background." # accent word(s) in teal+TEAL = (0, 230, 220)++# Measured off Pup's asset — every bridge hero shares these (see SKILL.md "Layout numbers"):+LOGO_CROP, LOGO_AT = (60, 48, 335, 130), (60, 48) # ADOM wordmark, verbatim from Pup+PILL_CROP, PILL_AT = (1796, 36, 1964, 108), (1796, 36) # BRIDGE pill, verbatim from Pup+INK_EDGES = (79, 97, 83) # logo / title / subtitle left ink edges+CAP_TOP = 263 # title cap line+SUB_Y1, SUB_Y2 = 501, 565 # subtitle line tops+VITRINE_AT, VITRINE_W, VITRINE_H = (920, 150), 1030, 1025+RADIUS, SHADOW_BLUR, SHADOW_DY = 24, 28, 12+++def ink_edges(im):+ a = np.array(im).astype(int)+ diff = np.abs(a - a[700, 400]).sum(axis=2) > 45+ def left(y0, y1):+ for x in range(40, 900):+ if diff[y0:y1, x].sum() > 2:+ return x+ return left(48, 132), left(263, 460), left(495, 640)+++def compose(title_dx, sub_dx, pup, base, tf, sf):+ hero = base.copy()+ hero.paste(pup.crop(LOGO_CROP), LOGO_AT)+ hero.paste(pup.crop(PILL_CROP), PILL_AT)+ d = ImageDraw.Draw(hero)+ x, y = title_dx, CAP_TOP - tf.getbbox(TITLE[0])[1]+ for ch in TITLE: # -5px tracking, per the studio's .h-title letter-spacing+ d.text((x, y), ch, font=tf, fill=(230, 237, 243))+ x += d.textlength(ch, font=tf) - 5+ scol = (232, 238, 244)+ w = d.textlength(SUB_1A, font=sf)+ d.text((sub_dx, SUB_Y1), SUB_1A, font=sf, fill=scol)+ d.text((sub_dx + w, SUB_Y1), SUB_1B, font=sf, fill=scol)+ w = d.textlength(SUB_2A, font=sf)+ d.text((sub_dx, SUB_Y2), SUB_2A, font=sf, fill=scol)+ d.text((sub_dx + w, SUB_Y2), SUB_2B, font=sf, fill=TEAL)+ return hero+++def main(capture_path, out_path):+ if not PUP.exists():+ sys.exit(f"missing {PUP}\nFetch it: adom-wiki repo download pup-bridge --extract /tmp/pup-repo\n"+ f"then copy hero-v9.png here. (Do NOT use `repo show` — it mangles binaries.)")+ pup = Image.open(PUP).convert("RGB")+ pa = np.array(pup)+ # Background: bilinear gradient through Pup's four corner colors.+ tl, tr = pa[6, 6].astype(float), pa[6, W - 7].astype(float)+ bl, br = pa[H - 7, 6].astype(float), pa[H - 7, W - 7].astype(float)+ xs = np.linspace(0, 1, W)[None, :, None]+ ys = np.linspace(0, 1, H)[:, None, None]+ base = Image.fromarray((tl * (1 - xs) * (1 - ys) + tr * xs * (1 - ys)+ + bl * (1 - xs) * ys + br * xs * ys).astype(np.uint8))++ # Title: Familjen Grotesk BOLD (brand doctrine, hero-studio .h-title = 700).+ # 242px makes cap height match Pup's measured 155px.+ tf = ImageFont.truetype(str(FONTS / "FamiljenGrotesk-Bold.ttf"), 242)+ sf = ImageFont.truetype(str(FONTS / "Satoshi-Regular.ttf"), 52)++ # Calibrate: fonts lie about left bearings (Familjen reported 0 for K, drew 16).+ # Render, measure actual ink, correct, re-render, assert.+ t_dx, s_dx = 98, 84+ lo, ti, su = ink_edges(compose(t_dx, s_dx, pup, base, tf, sf))+ t_dx += INK_EDGES[1] - ti+ s_dx += INK_EDGES[2] - su+ hero = compose(t_dx, s_dx, pup, base, tf, sf)+ assert ink_edges(hero) == INK_EDGES, f"alignment drifted: {ink_edges(hero)} != {INK_EDGES}"++ # Vitrine: real window capture in Pup's exact floating-window rect.+ win = Image.open(capture_path).convert("RGB")+ if win.size != (VITRINE_W, VITRINE_H):+ print(f"WARNING: capture is {win.size}, scaling to {(VITRINE_W, VITRINE_H)} (softens text; "+ f"prefer resizing the real window before capture)")+ win = win.resize((VITRINE_W, VITRINE_H), Image.LANCZOS)+ mask = Image.new("L", (VITRINE_W, VITRINE_H), 0)+ ImageDraw.Draw(mask).rounded_rectangle((0, 0, VITRINE_W - 1, VITRINE_H - 1), radius=RADIUS, fill=255)+ sh = Image.new("L", (W, H), 0)+ vx, vy = VITRINE_AT+ ImageDraw.Draw(sh).rounded_rectangle((vx, vy + SHADOW_DY, vx + VITRINE_W, vy + SHADOW_DY + VITRINE_H),+ radius=RADIUS, fill=110)+ sh = sh.filter(ImageFilter.GaussianBlur(SHADOW_BLUR))+ hero = Image.composite(Image.new("RGB", (W, H), (0, 0, 0)), hero, sh)+ hero.paste(win, VITRINE_AT, mask)+ hero.save(out_path)+ print(f"saved {out_path} (alignment {INK_EDGES} verified)")+++if __name__ == "__main__":+ if len(sys.argv) != 3:+ sys.exit(__doc__)+ main(sys.argv[1], sys.argv[2])
uninstall.sh+6−1@@ -1,2 +1,7 @@ #!/usr/bin/env bash-rm -rf "$HOME/.claude/skills/adom-desktop-kicad-bridge"+set -euo pipefail+D="$HOME/.claude/skills"+rm -rf "$D/kicad-bridge" "$D/kicad-interaction" "$D/kicad-3d-models" "$D/kicad-tour" \+ "$D/kicad-bridge-dev" "$D/kicad-bridge-publish" "$D/kicad-bridge-hero" \+ "$D/adom-desktop-kicad-bridge"+echo "[kicad-bridge] skills removed."