← Commit history

Remove all 40 em-dashes from the SDK doc (CLAUDE.md no-em-dash rule); republish all three served copies

John Lauer ·c717e4bfec ·2mo ago ·parent 85efd5e
3 files changed +99−99
README.md+33−33
@@ -10,60 +10,60 @@ description: >- ---  -# Bridge SDK — build, publish, and surface a bridge for Adom Bridge+# Bridge SDK: build, publish, and surface a bridge for Adom Bridge -A **bridge** is a small local program (Python, Node, Rust, anything) that ab spawns and routes a **namespace of verbs** to over HTTP. KiCad, Fusion 360, Puppeteer (pup), native-browser, Blender are bridges. The modern flow has THREE moving parts you own — get all three right and a cloud/WSL2 container can *discover* your bridge from a plain-English request, confirm it's installed, and drive it with zero tribal knowledge:+A **bridge** is a small local program (Python, Node, Rust, anything) that ab spawns and routes a **namespace of verbs** to over HTTP. KiCad, Fusion 360, Puppeteer (pup), native-browser, Blender are bridges. The modern flow has THREE moving parts you own. Get all three right and a cloud/WSL2 container can *discover* your bridge from a plain-English request, confirm it's installed, and drive it with zero tribal knowledge: -1. **Runtime** — a versioned **Release `.zip`** + a **manifest** on your wiki page. ab's `bridge_install` streams it onto the user's machine and spawns it.-2. **Skills** — an **`adom-wiki pkg`** tarball carrying your consumer SKILL.md(s). A container installs it into `~/.claude/skills/` (or `~/.codex/skills/`) so the AI *there* knows your verbs.-3. **Discoverability** — **`discovery_triggers`** on your wiki page so a user saying *"open my app in pup"* surfaces YOU via `adom-wiki discover find`.+1. **Runtime**: a versioned **Release `.zip`** + a **manifest** on your wiki page. ab's `bridge_install` streams it onto the user's machine and spawns it.+2. **Skills**: an **`adom-wiki pkg`** tarball carrying your consumer SKILL.md(s). A container installs it into `~/.claude/skills/` (or `~/.codex/skills/`) so the AI *there* knows your verbs.+3. **Discoverability**: **`discovery_triggers`** on your wiki page so a user saying *"open my app in pup"* surfaces YOU via `adom-wiki discover find`. -> **THE ONE PRINCIPLE THAT MATTERS MOST — read this before anything else.**+> **THE ONE PRINCIPLE THAT MATTERS MOST: read this before anything else.** > **The AI almost never reads your on-disk SKILL.md. It ALWAYS reads the OUTPUT of a CLI call.** > So your leverage is in the **`_hint` / `_next` / `related` / `pitfalls` you return on EVERY verb response.** A verb that returns `{success:true}` and nothing else leaves the AI guessing and retry-looping. A verb that returns *what just happened, the obvious next verb, the related verbs, and the pitfall to avoid* gets driven perfectly on the first try. ab relays every field of your response **verbatim** to the calling AI. Treat each verb's output as the documentation the AI will actually read. (Your SKILL.md is for the *human* author + the rare AI that does read it; your verb OUTPUT is for the AI in the loop.)  --- -## Where every file lives — the two artifacts, their deps, and which skills go where+## Where every file lives: the two artifacts, their deps, and which skills go where -**Two DISTINCT artifacts, two DIFFERENT homes, two DIFFERENT consumers. Crossing them is the #1 inconsistency across bridge repos — pin it here before anything else.** (Live audit 2026-07-06: fusion + pup do this right; kicad had **61** loose `adom-bridge-kicad-v*.zip` on `/files` + a `mesa-llvmpipe-x64.zip`; blender dumped 2 loose zips too.)+**Two DISTINCT artifacts, two DIFFERENT homes, two DIFFERENT consumers. Crossing them is the #1 inconsistency across bridge repos, so pin it here before anything else.** (Live audit 2026-07-06: fusion + pup do this right; kicad had **61** loose `adom-bridge-kicad-v*.zip` on `/files` + a `mesa-llvmpipe-x64.zip`; blender dumped 2 loose zips too.)  | | **RUNTIME artifact** (Artifact 1) | **SKILLS pkg** (Artifact 2) | |---|---|---| | **What it is** | your bridge's actual CODE | your consumer `SKILL.md` docs |-| **Runs on** | the user's **DESKTOP** — ab spawns it | **nothing runs it** — docs the CONTAINER reads |+| **Runs on** | the user's **DESKTOP** (ab spawns it) | **nothing runs it** (docs the CONTAINER reads) | | **Published as** | a versioned **Release** (`release create` + `release upload --platform any`) | an **`adom-wiki pkg`** (`pkg publish --org adom`) | | **Installed by** | `bridge_install {manifestUrl}` → ab streams, unzips, spawns | `pkg install adom/<slug>` / `sync_skills` → `~/.claude/skills` + `~/.codex/skills` |-| **Contains** | server code + `bridge.json` at the zip **ROOT** + assets. **Node: SOURCE ONLY, no `node_modules`.** | ONLY: ROOT `SKILL.md`, `skills/<name>/SKILL.md` (user skills), `package.json`, `install.sh`, `uninstall.sh`. **Text only — the whole tarball should be a few KB.** |-| **NEVER contains** | the skills pkg | **ANY image (hero / screenshots — those are WIKI PAGE assets, see below), any binary, the Release zip, `src/`, `node_modules`, dev/publish skills, or the server's npm deps.** If `tar tzf` shows a `.png`/`.jpg`/`.zip`/`.exe`, you bundled bloat — remove it. |-| **its `package.json`** | (node) the **bridge SERVER's runtime deps** ab installs at spawn: `dependencies:{ puppeteer, … }` | the **pkg manifest**: `files[]` (user skills), `discovery_triggers`, scripts, **AND `dependencies:{"adom/adom-bridge":"^1.9.x"}`** (the container needs the ab CLI + core skills to drive you) — but NOT the server's npm deps |+| **Contains** | server code + `bridge.json` at the zip **ROOT** + assets. **Node: SOURCE ONLY, no `node_modules`.** | ONLY: ROOT `SKILL.md`, `skills/<name>/SKILL.md` (user skills), `package.json`, `install.sh`, `uninstall.sh`. **Text only: the whole tarball should be a few KB.** |+| **NEVER contains** | the skills pkg | **ANY image (hero / screenshots: those are WIKI PAGE assets, see below), any binary, the Release zip, `src/`, `node_modules`, dev/publish skills, or the server's npm deps.** If `tar tzf` shows a `.png`/`.jpg`/`.zip`/`.exe`, you bundled bloat. Remove it. |+| **its `package.json`** | (node) the **bridge SERVER's runtime deps** ab installs at spawn: `dependencies:{ puppeteer, … }` | the **pkg manifest**: `files[]` (user skills), `discovery_triggers`, scripts, **AND `dependencies:{"adom/adom-bridge":"^1.9.x"}`** (the container needs the ab CLI + core skills to drive you), but NOT the server's npm deps | -**Yes, a node bridge has TWO `package.json` files and they are NOT interchangeable.** One rides inside the Release zip and lists the **bridge server's** runtime deps (puppeteer, sharp, ...) that ab installs on the *desktop* at spawn. The other is the **pkg manifest** in the skills tarball: it lists your user-skill `files[]` + `discovery_triggers` AND declares **`dependencies:{"adom/adom-bridge":"^1.9.x"}`** — `pkg install` resolves that npm-style, so installing your bridge's skill pkg also pulls the adom-bridge CLI + core skills the container needs to drive you. What the pkg manifest must NOT list is the **server's** npm deps (those are desktop-side, in the zip) — put them there and you bloat every container install, since the container never runs your server.+**Yes, a node bridge has TWO `package.json` files and they are NOT interchangeable.** One rides inside the Release zip and lists the **bridge server's** runtime deps (puppeteer, sharp, ...) that ab installs on the *desktop* at spawn. The other is the **pkg manifest** in the skills tarball: it lists your user-skill `files[]` + `discovery_triggers` AND declares **`dependencies:{"adom/adom-bridge":"^1.9.x"}`**. `pkg install` resolves that npm-style, so installing your bridge's skill pkg also pulls the adom-bridge CLI + core skills the container needs to drive you. What the pkg manifest must NOT list is the **server's** npm deps (those are desktop-side, in the zip). Put them there and you bloat every container install, since the container never runs your server.  **Where each file physically lives (this is the map that ends the inconsistency):** - **`bridge.json`** → the **root of the Release zip** (ab reads it post-unzip). Carries `version` + `updateManifestUrl`.-- **The Release `.zip`** → upload it as a **Release asset** (`release upload … --platform any`) and point the manifest `url` at its **release-download path**: `https://wiki.adom.inc/download/adom/<slug>/<ver>/<file>.zip` (verified to serve anon 206). **fusion + pup do exactly this — copy them.** A loose `/files` blob technically serves too (206), so this is a HYGIENE + right-home rule, not "it won't work": the `/files` store is your git **page repo** (for the manifest, text, images), so a binary there accumulates forever (kicad has **61** stale `-v*.zip`), isn't pinned to a Release, and the `/blob/...` URL is just an **alias of the same blob** (same sha) — you can't tidy it. **⚠ Migrating off `/files` is a SEQUENCE, not a delete:** (1) `release upload` the zip, (2) repoint the manifest `url` at the `/download/` path, (3) verify it serves anon, (4) *only then* `repo rm` the old `/files` copies. **NEVER `repo rm` a zip while your manifest still points at it** (or at its `/blob` alias) — that breaks `bridge_install` + auto-update instantly.-- **The manifest** (`adom-bridge-<name>-manifest.json`, this naming is the consistent one — keep it) → POSTed to your page **`/files`**. Its `url` points at the **Release download** (`https://wiki.adom.inc/download/adom/<slug>/<ver>/<file>.zip`), base-relative + extension-agnostic. `updateManifestUrl` (in the manifest AND in bridge.json) points back at this manifest.-- **A big RUNTIME ASSET** (a headless-GL pack like Mesa, a browser download) → declare it in **`prewarm.assets`** so ab downloads + manages it, or bundle it in your seed. **NOT a loose `/files` blob** — kicad's `mesa-llvmpipe-x64.zip` on `/files` is the anti-pattern.-- **Your hero image + screenshots** → **WIKI PAGE assets** — set them on the page (the page's hero / `repo push` the image to the page), **NEVER in the skills tarball.** A container that `pkg install`s your skills never renders a hero, so a bundled hero (a real one was **1 MB**) is pure download bloat on every install. **Do NOT put `hero: docs/hero.png` + `docs/**` in the pkg `package.json`** — that is exactly what stuffs the image into the tarball.+- **The Release `.zip`** → upload it as a **Release asset** (`release upload … --platform any`) and point the manifest `url` at its **release-download path**: `https://wiki.adom.inc/download/adom/<slug>/<ver>/<file>.zip` (verified to serve anon 206). **fusion + pup do exactly this, so copy them.** A loose `/files` blob technically serves too (206), so this is a HYGIENE + right-home rule, not "it won't work": the `/files` store is your git **page repo** (for the manifest, text, images), so a binary there accumulates forever (kicad has **61** stale `-v*.zip`), isn't pinned to a Release, and the `/blob/...` URL is just an **alias of the same blob** (same sha), so you can't tidy it. **⚠ Migrating off `/files` is a SEQUENCE, not a delete:** (1) `release upload` the zip, (2) repoint the manifest `url` at the `/download/` path, (3) verify it serves anon, (4) *only then* `repo rm` the old `/files` copies. **NEVER `repo rm` a zip while your manifest still points at it** (or at its `/blob` alias). That breaks `bridge_install` + auto-update instantly.+- **The manifest** (`adom-bridge-<name>-manifest.json`, this naming is the consistent one, keep it) → POSTed to your page **`/files`**. Its `url` points at the **Release download** (`https://wiki.adom.inc/download/adom/<slug>/<ver>/<file>.zip`), base-relative + extension-agnostic. `updateManifestUrl` (in the manifest AND in bridge.json) points back at this manifest.+- **A big RUNTIME ASSET** (a headless-GL pack like Mesa, a browser download) → declare it in **`prewarm.assets`** so ab downloads + manages it, or bundle it in your seed. **NOT a loose `/files` blob**: kicad's `mesa-llvmpipe-x64.zip` on `/files` is the anti-pattern.+- **Your hero image + screenshots** → **WIKI PAGE assets**: set them on the page (the page's hero / `repo push` the image to the page), **NEVER in the skills tarball.** A container that `pkg install`s your skills never renders a hero, so a bundled hero (a real one was **1 MB**) is pure download bloat on every install. **Do NOT put `hero: docs/hero.png` + `docs/**` in the pkg `package.json`**, because that is exactly what stuffs the image into the tarball. -**The three kinds of skill — where each goes (this IS the demarcation, one table):**+**The three kinds of skill, and where each goes (this IS the demarcation, one table):**  | Skill kind | Who reads it | Lives in | In the pkg tarball? | Public? | |---|---|---|---|---|-| **USER** (the public / consumer skill) | a consumer AI driving your verbs | your **pkg** | **YES** — that is the pkg's whole job | yes (the pkg is public) |+| **USER** (the public / consumer skill) | a consumer AI driving your verbs | your **pkg** | **YES**, that is the pkg's whole job | yes (the pkg is public) | | **DEV** | a maintainer editing the bridge | your **source repo** | **NO, never** | only if your repo is public | | **PUBLISH** | a maintainer releasing the bridge | your **source repo** | **NO, never** | only if your repo is public | -**The one rule:** the pkg tarball carries **USER skills only**; **DEV + PUBLISH skills live in the source repo.** Do NOT "scope" dev/publish into the pkg with `user-invocable:false` — that still ships them (pure bloat). Open-vs-closed source only sets your repo's *visibility*; it never changes what's in the pkg. adom-bridge core does exactly this: user/integration skills in the pkg; `RELEASE` / `WIKI_PUBLISH` / `SIGNING` / `AD_RELAUNCH` live only in the source repo.+**The one rule:** the pkg tarball carries **USER skills only**; **DEV + PUBLISH skills live in the source repo.** Do NOT "scope" dev/publish into the pkg with `user-invocable:false`, since that still ships them (pure bloat). Open-vs-closed source only sets your repo's *visibility*; it never changes what's in the pkg. adom-bridge core does exactly this: user/integration skills in the pkg; `RELEASE` / `WIKI_PUBLISH` / `SIGNING` / `AD_RELAUNCH` live only in the source repo.  --- -## The boundary — what ab owns vs what YOU own+## The boundary: what ab owns vs what YOU own  The #1 mistake is filing ab bugs for bridge issues. The split: -**ab core owns** (don't reimplement): the CLI/relay/`/command` passthrough (ab relays your JSON verbatim — every field); spawning/reaping your process, the **stable port**, single-instance, the persistent-respawn supervisor, cache install + auto-update; host/process facts (running? pid? port?), the status-chip rendering, the GUI card, the activity log; `status`/`_hint` classification for timeouts/disconnects (ab authors those — you never stamp them).+**ab core owns** (don't reimplement): the CLI/relay/`/command` passthrough (ab relays your JSON verbatim, every field); spawning/reaping your process, the **stable port**, single-instance, the persistent-respawn supervisor, cache install + auto-update; host/process facts (running? pid? port?), the status-chip rendering, the GUI card, the activity log; `status`/`_hint` classification for timeouts/disconnects (ab authors those, you never stamp them).  **You own**: your verbs + their behavior + their **rich hints**; your `/health` (+ optional chip fields); your `<prefix>describe` catalog; your wiki page (hero, brief, `discovery_triggers`, docs); your published **manifest + Release zip**; and your **skills pkg**. @@ -73,12 +73,12 @@ If a *bridge verb* misbehaves → fix the bridge. If a *generic ab capability* i  ## ⭐ REQUIRED: every bridge MUST expose a `<prefix>uninstall` verb (John, 2026-08-12) -An Adom employee reported that uninstalling left cruft behind, because ab's uninstaller could remove ab's own files but had no way to clean up what each BRIDGE created. Bridges install their own state OUTSIDE ab's directory — a host-app add-in (the Fusion add-in lives in `%APPDATA%`, not ab's dir), per-bridge caches, downloaded runtimes/models, registry keys, temp dirs. Only the bridge knows where all of that is.+An Adom employee reported that uninstalling left cruft behind, because ab's uninstaller could remove ab's own files but had no way to clean up what each BRIDGE created. Bridges install their own state OUTSIDE ab's directory: a host-app add-in (the Fusion add-in lives in `%APPDATA%`, not ab's dir), per-bridge caches, downloaded runtimes/models, registry keys, temp dirs. Only the bridge knows where all of that is. -So the contract now requires: **every bridge exposes a `<prefix>uninstall` verb** (e.g. `kicad_uninstall`, `fusion_uninstall`, `pup_uninstall`) that removes EVERYTHING that bridge created on the machine — its add-in/plugin files in host-app dirs, its caches, its registry entries, its temp files, anything outside ab's own `bridges-cache` (which ab removes). It must be:-- **Idempotent and non-interactive** (safe to call when already clean; never prompt/block — it runs during a silent `/S` uninstall cascade).+So the contract now requires: **every bridge exposes a `<prefix>uninstall` verb** (e.g. `kicad_uninstall`, `fusion_uninstall`, `pup_uninstall`) that removes EVERYTHING that bridge created on the machine: its add-in/plugin files in host-app dirs, its caches, its registry entries, its temp files, anything outside ab's own `bridges-cache` (which ab removes). It must be:+- **Idempotent and non-interactive** (safe to call when already clean; never prompt/block, since it runs during a silent `/S` uninstall cascade). - **Best-effort per-item** (one un-removable file must not abort the rest; report what was and wasn't removed).-- **Scoped to your own artifacts ONLY** — never touch the host app itself (don't uninstall KiCad/Fusion), other bridges, or user data.+- **Scoped to your own artifacts ONLY**: never touch the host app itself (don't uninstall KiCad/Fusion), other bridges, or user data.  ab's uninstaller enumerates the installed bridges and calls each bridge's `<prefix>uninstall` before removing ab, so an uninstall cleans up the whole ecosystem. Declare the verb in your `bridge.json` (an `uninstall` field naming the verb) so ab knows to call it; if absent, ab logs that the bridge left cleanup to the user. Add it to your `describe` catalog and skill like any other verb. @@ -143,20 +143,20 @@ This keeps the "ab renders, the bridge knows its app" boundary: ab does not hard  ## ⭐ SUGGESTED: expose a `<prefix>_state` verb that returns your app's FULL current state (John, 2026-08-14) -A user or AI should be able to ask a bridge, at any moment, "what does the app you control look like RIGHT NOW?" and get back the complete picture, not one guessed window. Today a caller that wants to see KiCad screenshots the first window whose title contains "KiCad" and gets ONE of its two windows; Fusion has a main window plus palettes, so a single title-match grabs a skinny panel. That is wrong, and it is wrong because only the BRIDGE knows the real window set (it tracks its app's parent/child hwnds) — a generic title match cannot.+A user or AI should be able to ask a bridge, at any moment, "what does the app you control look like RIGHT NOW?" and get back the complete picture, not one guessed window. Today a caller that wants to see KiCad screenshots the first window whose title contains "KiCad" and gets ONE of its two windows; Fusion has a main window plus palettes, so a single title-match grabs a skinny panel. That is wrong, and it is wrong because only the BRIDGE knows the real window set (it tracks its app's parent/child hwnds). A generic title match cannot.  So, when your app has a meaningful visual/logical state: **expose a `<prefix>_state` verb** (e.g. `kicad_state`, `fusion_state`, `blender_state`) that returns the app's current STATE OF THE UNION: -- **A screenshot ARRAY of EVERY window the app owns** — the parent + all child/related windows (project manager + symbol editor + PCB editor + any open dialog), each captured by its real hwnd. Return them as **base64** (`{ shots: [{ title, hwnd, image, width, height }] }`) so a WEB page or AI can render them inline without a file pull. (File paths are fine for the CLI too, but base64 is what makes this usable from the web-control demo and from an AI that cannot reach the desktop's filesystem.)-- **Plus the logical state** you already know: what document/board/design is open, which editor is focused, is a modal dialog blocking, unsaved-changes flag, current tool — whatever a caller would need to decide the next move.+- **A screenshot ARRAY of EVERY window the app owns**: the parent + all child/related windows (project manager + symbol editor + PCB editor + any open dialog), each captured by its real hwnd. Return them as **base64** (`{ shots: [{ title, hwnd, image, width, height }] }`) so a WEB page or AI can render them inline without a file pull. (File paths are fine for the CLI too, but base64 is what makes this usable from the web-control demo and from an AI that cannot reach the desktop's filesystem.)+- **Plus the logical state** you already know: what document/board/design is open, which editor is focused, is a modal dialog blocking, unsaved-changes flag, current tool, plus whatever else a caller would need to decide the next move. -Why this matters: it turns "drive blind and hope" into "look, then act." An AI can call `<prefix>_state` before and after an action to confirm it landed; a human demo (like the web-control page) can show the app's true state instead of a cropped sub-window. ab already has the generic pieces (`desktop_list_windows` + `desktop_screenshot_base64 {hwnd}`), but the bridge produces the CORRECT set because it owns the window identities — that is the whole point of the ab-renders / bridge-knows-its-app boundary. Keep it read-only and non-gated (it only observes), and make it cheap enough to poll.+Why this matters: it turns "drive blind and hope" into "look, then act." An AI can call `<prefix>_state` before and after an action to confirm it landed; a human demo (like the web-control page) can show the app's true state instead of a cropped sub-window. ab already has the generic pieces (`desktop_list_windows` + `desktop_screenshot_base64 {hwnd}`), but the bridge produces the CORRECT set because it owns the window identities, and that is the whole point of the ab-renders / bridge-knows-its-app boundary. Keep it read-only and non-gated (it only observes), and make it cheap enough to poll.  --- -## ⭐ EDA bridges: the EIGHT SURFACES contract — `<prefix>_show_*` (John, 2026-08-15)+## ⭐ EDA bridges: the EIGHT SURFACES contract, `<prefix>_show_*` (John, 2026-08-15) -If your bridge drives an **electronics design tool** (KiCad, Fusion 360, Altium, and whatever comes next), implement these eight verbs. They are not a demo checklist; they are the complete set of things a user has to be able to DO from the Adom Wiki to close the circle from a wiki **component / molecule / board** all the way to the end app the EE actually designs in. A bridge that implements all eight plus `<prefix>_state` gets the web-control demo, the wiki round-trip, and the cross-app parity test for free, with **zero ab-side work** — that is the whole point of keeping ab generic.+If your bridge drives an **electronics design tool** (KiCad, Fusion 360, Altium, and whatever comes next), implement these eight verbs. They are not a demo checklist; they are the complete set of things a user has to be able to DO from the Adom Wiki to close the circle from a wiki **component / molecule / board** all the way to the end app the EE actually designs in. A bridge that implements all eight plus `<prefix>_state` gets the web-control demo, the wiki round-trip, and the cross-app parity test for free, with **zero ab-side work**, which is the whole point of keeping ab generic.  | # | Surface | Verb | What it opens | |---|---|---|---|@@ -172,7 +172,7 @@ If your bridge drives an **electronics design tool** (KiCad, Fusion 360, Altium, Rules that make the set usable rather than merely present:  - **`show` is not `open`.** An `open` verb takes a file path or a cloud URN and has app-specific arg shapes; a `show` verb takes the smallest identifier that names the surface (a symbol name plus its library, a file path, nothing at all when the app already has the design loaded) and is responsible for getting the app into that view whatever state it starts from. Having `fusion_open_lbr` does NOT satisfy #4, and having `kicad_open_board` does NOT satisfy #6. Ship the symmetric `show` pair.-- **They open in the BACKGROUND and are therefore useless without `<prefix>_state`.** A `show` verb that returns `{success:true, activeWorkspace:"..."}` and no image leaves the caller unable to prove anything happened. The state verb above is what renders the result. **Treat the eight verbs and the state verb as ONE deliverable** — six show verbs with no state verb rendered zero surfaces in the demo for a full day, which read as "the feature does not work."+- **They open in the BACKGROUND and are therefore useless without `<prefix>_state`.** A `show` verb that returns `{success:true, activeWorkspace:"..."}` and no image leaves the caller unable to prove anything happened. The state verb above is what renders the result. **Treat the eight verbs and the state verb as ONE deliverable**: six show verbs with no state verb rendered zero surfaces in the demo for a full day, which read as "the feature does not work." - **Say when a surface is COLD-capable and when it is WARM-ONLY.** KiCad's 3D chip and 3D board are reliable only once KiCad is already up (kicad-bridge #26). That is a legitimate answer, but it must come back in the verb's own `_hint` so the caller can sequence rather than guess. - **A prerequisite miss returns a `_hint` naming the exact next call**, never a bare failure: "needs a library open, call `<prefix>_open_lbr` first" or "run the demo first to generate the sample project." - **Zero-setup where you can.** Prefer pointing the demo-facing default at the app's STOCK libraries (KiCad uses `Device` / `Resistor_SMD`) so a fresh box shows something real with nothing staged. Where stock content does not exist, bundle a sample in your zip like Fusion's sample `.lbr`.
SKILL.md+33−33
@@ -10,60 +10,60 @@ description: >- ---  -# Bridge SDK — build, publish, and surface a bridge for Adom Bridge+# Bridge SDK: build, publish, and surface a bridge for Adom Bridge -A **bridge** is a small local program (Python, Node, Rust, anything) that ab spawns and routes a **namespace of verbs** to over HTTP. KiCad, Fusion 360, Puppeteer (pup), native-browser, Blender are bridges. The modern flow has THREE moving parts you own — get all three right and a cloud/WSL2 container can *discover* your bridge from a plain-English request, confirm it's installed, and drive it with zero tribal knowledge:+A **bridge** is a small local program (Python, Node, Rust, anything) that ab spawns and routes a **namespace of verbs** to over HTTP. KiCad, Fusion 360, Puppeteer (pup), native-browser, Blender are bridges. The modern flow has THREE moving parts you own. Get all three right and a cloud/WSL2 container can *discover* your bridge from a plain-English request, confirm it's installed, and drive it with zero tribal knowledge: -1. **Runtime** — a versioned **Release `.zip`** + a **manifest** on your wiki page. ab's `bridge_install` streams it onto the user's machine and spawns it.-2. **Skills** — an **`adom-wiki pkg`** tarball carrying your consumer SKILL.md(s). A container installs it into `~/.claude/skills/` (or `~/.codex/skills/`) so the AI *there* knows your verbs.-3. **Discoverability** — **`discovery_triggers`** on your wiki page so a user saying *"open my app in pup"* surfaces YOU via `adom-wiki discover find`.+1. **Runtime**: a versioned **Release `.zip`** + a **manifest** on your wiki page. ab's `bridge_install` streams it onto the user's machine and spawns it.+2. **Skills**: an **`adom-wiki pkg`** tarball carrying your consumer SKILL.md(s). A container installs it into `~/.claude/skills/` (or `~/.codex/skills/`) so the AI *there* knows your verbs.+3. **Discoverability**: **`discovery_triggers`** on your wiki page so a user saying *"open my app in pup"* surfaces YOU via `adom-wiki discover find`. -> **THE ONE PRINCIPLE THAT MATTERS MOST — read this before anything else.**+> **THE ONE PRINCIPLE THAT MATTERS MOST: read this before anything else.** > **The AI almost never reads your on-disk SKILL.md. It ALWAYS reads the OUTPUT of a CLI call.** > So your leverage is in the **`_hint` / `_next` / `related` / `pitfalls` you return on EVERY verb response.** A verb that returns `{success:true}` and nothing else leaves the AI guessing and retry-looping. A verb that returns *what just happened, the obvious next verb, the related verbs, and the pitfall to avoid* gets driven perfectly on the first try. ab relays every field of your response **verbatim** to the calling AI. Treat each verb's output as the documentation the AI will actually read. (Your SKILL.md is for the *human* author + the rare AI that does read it; your verb OUTPUT is for the AI in the loop.)  --- -## Where every file lives — the two artifacts, their deps, and which skills go where+## Where every file lives: the two artifacts, their deps, and which skills go where -**Two DISTINCT artifacts, two DIFFERENT homes, two DIFFERENT consumers. Crossing them is the #1 inconsistency across bridge repos — pin it here before anything else.** (Live audit 2026-07-06: fusion + pup do this right; kicad had **61** loose `adom-bridge-kicad-v*.zip` on `/files` + a `mesa-llvmpipe-x64.zip`; blender dumped 2 loose zips too.)+**Two DISTINCT artifacts, two DIFFERENT homes, two DIFFERENT consumers. Crossing them is the #1 inconsistency across bridge repos, so pin it here before anything else.** (Live audit 2026-07-06: fusion + pup do this right; kicad had **61** loose `adom-bridge-kicad-v*.zip` on `/files` + a `mesa-llvmpipe-x64.zip`; blender dumped 2 loose zips too.)  | | **RUNTIME artifact** (Artifact 1) | **SKILLS pkg** (Artifact 2) | |---|---|---| | **What it is** | your bridge's actual CODE | your consumer `SKILL.md` docs |-| **Runs on** | the user's **DESKTOP** — ab spawns it | **nothing runs it** — docs the CONTAINER reads |+| **Runs on** | the user's **DESKTOP** (ab spawns it) | **nothing runs it** (docs the CONTAINER reads) | | **Published as** | a versioned **Release** (`release create` + `release upload --platform any`) | an **`adom-wiki pkg`** (`pkg publish --org adom`) | | **Installed by** | `bridge_install {manifestUrl}` → ab streams, unzips, spawns | `pkg install adom/<slug>` / `sync_skills` → `~/.claude/skills` + `~/.codex/skills` |-| **Contains** | server code + `bridge.json` at the zip **ROOT** + assets. **Node: SOURCE ONLY, no `node_modules`.** | ONLY: ROOT `SKILL.md`, `skills/<name>/SKILL.md` (user skills), `package.json`, `install.sh`, `uninstall.sh`. **Text only — the whole tarball should be a few KB.** |-| **NEVER contains** | the skills pkg | **ANY image (hero / screenshots — those are WIKI PAGE assets, see below), any binary, the Release zip, `src/`, `node_modules`, dev/publish skills, or the server's npm deps.** If `tar tzf` shows a `.png`/`.jpg`/`.zip`/`.exe`, you bundled bloat — remove it. |-| **its `package.json`** | (node) the **bridge SERVER's runtime deps** ab installs at spawn: `dependencies:{ puppeteer, … }` | the **pkg manifest**: `files[]` (user skills), `discovery_triggers`, scripts, **AND `dependencies:{"adom/adom-bridge":"^1.9.x"}`** (the container needs the ab CLI + core skills to drive you) — but NOT the server's npm deps |+| **Contains** | server code + `bridge.json` at the zip **ROOT** + assets. **Node: SOURCE ONLY, no `node_modules`.** | ONLY: ROOT `SKILL.md`, `skills/<name>/SKILL.md` (user skills), `package.json`, `install.sh`, `uninstall.sh`. **Text only: the whole tarball should be a few KB.** |+| **NEVER contains** | the skills pkg | **ANY image (hero / screenshots: those are WIKI PAGE assets, see below), any binary, the Release zip, `src/`, `node_modules`, dev/publish skills, or the server's npm deps.** If `tar tzf` shows a `.png`/`.jpg`/`.zip`/`.exe`, you bundled bloat. Remove it. |+| **its `package.json`** | (node) the **bridge SERVER's runtime deps** ab installs at spawn: `dependencies:{ puppeteer, … }` | the **pkg manifest**: `files[]` (user skills), `discovery_triggers`, scripts, **AND `dependencies:{"adom/adom-bridge":"^1.9.x"}`** (the container needs the ab CLI + core skills to drive you), but NOT the server's npm deps | -**Yes, a node bridge has TWO `package.json` files and they are NOT interchangeable.** One rides inside the Release zip and lists the **bridge server's** runtime deps (puppeteer, sharp, ...) that ab installs on the *desktop* at spawn. The other is the **pkg manifest** in the skills tarball: it lists your user-skill `files[]` + `discovery_triggers` AND declares **`dependencies:{"adom/adom-bridge":"^1.9.x"}`** — `pkg install` resolves that npm-style, so installing your bridge's skill pkg also pulls the adom-bridge CLI + core skills the container needs to drive you. What the pkg manifest must NOT list is the **server's** npm deps (those are desktop-side, in the zip) — put them there and you bloat every container install, since the container never runs your server.+**Yes, a node bridge has TWO `package.json` files and they are NOT interchangeable.** One rides inside the Release zip and lists the **bridge server's** runtime deps (puppeteer, sharp, ...) that ab installs on the *desktop* at spawn. The other is the **pkg manifest** in the skills tarball: it lists your user-skill `files[]` + `discovery_triggers` AND declares **`dependencies:{"adom/adom-bridge":"^1.9.x"}`**. `pkg install` resolves that npm-style, so installing your bridge's skill pkg also pulls the adom-bridge CLI + core skills the container needs to drive you. What the pkg manifest must NOT list is the **server's** npm deps (those are desktop-side, in the zip). Put them there and you bloat every container install, since the container never runs your server.  **Where each file physically lives (this is the map that ends the inconsistency):** - **`bridge.json`** → the **root of the Release zip** (ab reads it post-unzip). Carries `version` + `updateManifestUrl`.-- **The Release `.zip`** → upload it as a **Release asset** (`release upload … --platform any`) and point the manifest `url` at its **release-download path**: `https://wiki.adom.inc/download/adom/<slug>/<ver>/<file>.zip` (verified to serve anon 206). **fusion + pup do exactly this — copy them.** A loose `/files` blob technically serves too (206), so this is a HYGIENE + right-home rule, not "it won't work": the `/files` store is your git **page repo** (for the manifest, text, images), so a binary there accumulates forever (kicad has **61** stale `-v*.zip`), isn't pinned to a Release, and the `/blob/...` URL is just an **alias of the same blob** (same sha) — you can't tidy it. **⚠ Migrating off `/files` is a SEQUENCE, not a delete:** (1) `release upload` the zip, (2) repoint the manifest `url` at the `/download/` path, (3) verify it serves anon, (4) *only then* `repo rm` the old `/files` copies. **NEVER `repo rm` a zip while your manifest still points at it** (or at its `/blob` alias) — that breaks `bridge_install` + auto-update instantly.-- **The manifest** (`adom-bridge-<name>-manifest.json`, this naming is the consistent one — keep it) → POSTed to your page **`/files`**. Its `url` points at the **Release download** (`https://wiki.adom.inc/download/adom/<slug>/<ver>/<file>.zip`), base-relative + extension-agnostic. `updateManifestUrl` (in the manifest AND in bridge.json) points back at this manifest.-- **A big RUNTIME ASSET** (a headless-GL pack like Mesa, a browser download) → declare it in **`prewarm.assets`** so ab downloads + manages it, or bundle it in your seed. **NOT a loose `/files` blob** — kicad's `mesa-llvmpipe-x64.zip` on `/files` is the anti-pattern.-- **Your hero image + screenshots** → **WIKI PAGE assets** — set them on the page (the page's hero / `repo push` the image to the page), **NEVER in the skills tarball.** A container that `pkg install`s your skills never renders a hero, so a bundled hero (a real one was **1 MB**) is pure download bloat on every install. **Do NOT put `hero: docs/hero.png` + `docs/**` in the pkg `package.json`** — that is exactly what stuffs the image into the tarball.+- **The Release `.zip`** → upload it as a **Release asset** (`release upload … --platform any`) and point the manifest `url` at its **release-download path**: `https://wiki.adom.inc/download/adom/<slug>/<ver>/<file>.zip` (verified to serve anon 206). **fusion + pup do exactly this, so copy them.** A loose `/files` blob technically serves too (206), so this is a HYGIENE + right-home rule, not "it won't work": the `/files` store is your git **page repo** (for the manifest, text, images), so a binary there accumulates forever (kicad has **61** stale `-v*.zip`), isn't pinned to a Release, and the `/blob/...` URL is just an **alias of the same blob** (same sha), so you can't tidy it. **⚠ Migrating off `/files` is a SEQUENCE, not a delete:** (1) `release upload` the zip, (2) repoint the manifest `url` at the `/download/` path, (3) verify it serves anon, (4) *only then* `repo rm` the old `/files` copies. **NEVER `repo rm` a zip while your manifest still points at it** (or at its `/blob` alias). That breaks `bridge_install` + auto-update instantly.+- **The manifest** (`adom-bridge-<name>-manifest.json`, this naming is the consistent one, keep it) → POSTed to your page **`/files`**. Its `url` points at the **Release download** (`https://wiki.adom.inc/download/adom/<slug>/<ver>/<file>.zip`), base-relative + extension-agnostic. `updateManifestUrl` (in the manifest AND in bridge.json) points back at this manifest.+- **A big RUNTIME ASSET** (a headless-GL pack like Mesa, a browser download) → declare it in **`prewarm.assets`** so ab downloads + manages it, or bundle it in your seed. **NOT a loose `/files` blob**: kicad's `mesa-llvmpipe-x64.zip` on `/files` is the anti-pattern.+- **Your hero image + screenshots** → **WIKI PAGE assets**: set them on the page (the page's hero / `repo push` the image to the page), **NEVER in the skills tarball.** A container that `pkg install`s your skills never renders a hero, so a bundled hero (a real one was **1 MB**) is pure download bloat on every install. **Do NOT put `hero: docs/hero.png` + `docs/**` in the pkg `package.json`**, because that is exactly what stuffs the image into the tarball. -**The three kinds of skill — where each goes (this IS the demarcation, one table):**+**The three kinds of skill, and where each goes (this IS the demarcation, one table):**  | Skill kind | Who reads it | Lives in | In the pkg tarball? | Public? | |---|---|---|---|---|-| **USER** (the public / consumer skill) | a consumer AI driving your verbs | your **pkg** | **YES** — that is the pkg's whole job | yes (the pkg is public) |+| **USER** (the public / consumer skill) | a consumer AI driving your verbs | your **pkg** | **YES**, that is the pkg's whole job | yes (the pkg is public) | | **DEV** | a maintainer editing the bridge | your **source repo** | **NO, never** | only if your repo is public | | **PUBLISH** | a maintainer releasing the bridge | your **source repo** | **NO, never** | only if your repo is public | -**The one rule:** the pkg tarball carries **USER skills only**; **DEV + PUBLISH skills live in the source repo.** Do NOT "scope" dev/publish into the pkg with `user-invocable:false` — that still ships them (pure bloat). Open-vs-closed source only sets your repo's *visibility*; it never changes what's in the pkg. adom-bridge core does exactly this: user/integration skills in the pkg; `RELEASE` / `WIKI_PUBLISH` / `SIGNING` / `AD_RELAUNCH` live only in the source repo.+**The one rule:** the pkg tarball carries **USER skills only**; **DEV + PUBLISH skills live in the source repo.** Do NOT "scope" dev/publish into the pkg with `user-invocable:false`, since that still ships them (pure bloat). Open-vs-closed source only sets your repo's *visibility*; it never changes what's in the pkg. adom-bridge core does exactly this: user/integration skills in the pkg; `RELEASE` / `WIKI_PUBLISH` / `SIGNING` / `AD_RELAUNCH` live only in the source repo.  --- -## The boundary — what ab owns vs what YOU own+## The boundary: what ab owns vs what YOU own  The #1 mistake is filing ab bugs for bridge issues. The split: -**ab core owns** (don't reimplement): the CLI/relay/`/command` passthrough (ab relays your JSON verbatim — every field); spawning/reaping your process, the **stable port**, single-instance, the persistent-respawn supervisor, cache install + auto-update; host/process facts (running? pid? port?), the status-chip rendering, the GUI card, the activity log; `status`/`_hint` classification for timeouts/disconnects (ab authors those — you never stamp them).+**ab core owns** (don't reimplement): the CLI/relay/`/command` passthrough (ab relays your JSON verbatim, every field); spawning/reaping your process, the **stable port**, single-instance, the persistent-respawn supervisor, cache install + auto-update; host/process facts (running? pid? port?), the status-chip rendering, the GUI card, the activity log; `status`/`_hint` classification for timeouts/disconnects (ab authors those, you never stamp them).  **You own**: your verbs + their behavior + their **rich hints**; your `/health` (+ optional chip fields); your `<prefix>describe` catalog; your wiki page (hero, brief, `discovery_triggers`, docs); your published **manifest + Release zip**; and your **skills pkg**. @@ -73,12 +73,12 @@ If a *bridge verb* misbehaves → fix the bridge. If a *generic ab capability* i  ## ⭐ REQUIRED: every bridge MUST expose a `<prefix>uninstall` verb (John, 2026-08-12) -An Adom employee reported that uninstalling left cruft behind, because ab's uninstaller could remove ab's own files but had no way to clean up what each BRIDGE created. Bridges install their own state OUTSIDE ab's directory — a host-app add-in (the Fusion add-in lives in `%APPDATA%`, not ab's dir), per-bridge caches, downloaded runtimes/models, registry keys, temp dirs. Only the bridge knows where all of that is.+An Adom employee reported that uninstalling left cruft behind, because ab's uninstaller could remove ab's own files but had no way to clean up what each BRIDGE created. Bridges install their own state OUTSIDE ab's directory: a host-app add-in (the Fusion add-in lives in `%APPDATA%`, not ab's dir), per-bridge caches, downloaded runtimes/models, registry keys, temp dirs. Only the bridge knows where all of that is. -So the contract now requires: **every bridge exposes a `<prefix>uninstall` verb** (e.g. `kicad_uninstall`, `fusion_uninstall`, `pup_uninstall`) that removes EVERYTHING that bridge created on the machine — its add-in/plugin files in host-app dirs, its caches, its registry entries, its temp files, anything outside ab's own `bridges-cache` (which ab removes). It must be:-- **Idempotent and non-interactive** (safe to call when already clean; never prompt/block — it runs during a silent `/S` uninstall cascade).+So the contract now requires: **every bridge exposes a `<prefix>uninstall` verb** (e.g. `kicad_uninstall`, `fusion_uninstall`, `pup_uninstall`) that removes EVERYTHING that bridge created on the machine: its add-in/plugin files in host-app dirs, its caches, its registry entries, its temp files, anything outside ab's own `bridges-cache` (which ab removes). It must be:+- **Idempotent and non-interactive** (safe to call when already clean; never prompt/block, since it runs during a silent `/S` uninstall cascade). - **Best-effort per-item** (one un-removable file must not abort the rest; report what was and wasn't removed).-- **Scoped to your own artifacts ONLY** — never touch the host app itself (don't uninstall KiCad/Fusion), other bridges, or user data.+- **Scoped to your own artifacts ONLY**: never touch the host app itself (don't uninstall KiCad/Fusion), other bridges, or user data.  ab's uninstaller enumerates the installed bridges and calls each bridge's `<prefix>uninstall` before removing ab, so an uninstall cleans up the whole ecosystem. Declare the verb in your `bridge.json` (an `uninstall` field naming the verb) so ab knows to call it; if absent, ab logs that the bridge left cleanup to the user. Add it to your `describe` catalog and skill like any other verb. @@ -143,20 +143,20 @@ This keeps the "ab renders, the bridge knows its app" boundary: ab does not hard  ## ⭐ SUGGESTED: expose a `<prefix>_state` verb that returns your app's FULL current state (John, 2026-08-14) -A user or AI should be able to ask a bridge, at any moment, "what does the app you control look like RIGHT NOW?" and get back the complete picture, not one guessed window. Today a caller that wants to see KiCad screenshots the first window whose title contains "KiCad" and gets ONE of its two windows; Fusion has a main window plus palettes, so a single title-match grabs a skinny panel. That is wrong, and it is wrong because only the BRIDGE knows the real window set (it tracks its app's parent/child hwnds) — a generic title match cannot.+A user or AI should be able to ask a bridge, at any moment, "what does the app you control look like RIGHT NOW?" and get back the complete picture, not one guessed window. Today a caller that wants to see KiCad screenshots the first window whose title contains "KiCad" and gets ONE of its two windows; Fusion has a main window plus palettes, so a single title-match grabs a skinny panel. That is wrong, and it is wrong because only the BRIDGE knows the real window set (it tracks its app's parent/child hwnds). A generic title match cannot.  So, when your app has a meaningful visual/logical state: **expose a `<prefix>_state` verb** (e.g. `kicad_state`, `fusion_state`, `blender_state`) that returns the app's current STATE OF THE UNION: -- **A screenshot ARRAY of EVERY window the app owns** — the parent + all child/related windows (project manager + symbol editor + PCB editor + any open dialog), each captured by its real hwnd. Return them as **base64** (`{ shots: [{ title, hwnd, image, width, height }] }`) so a WEB page or AI can render them inline without a file pull. (File paths are fine for the CLI too, but base64 is what makes this usable from the web-control demo and from an AI that cannot reach the desktop's filesystem.)-- **Plus the logical state** you already know: what document/board/design is open, which editor is focused, is a modal dialog blocking, unsaved-changes flag, current tool — whatever a caller would need to decide the next move.+- **A screenshot ARRAY of EVERY window the app owns**: the parent + all child/related windows (project manager + symbol editor + PCB editor + any open dialog), each captured by its real hwnd. Return them as **base64** (`{ shots: [{ title, hwnd, image, width, height }] }`) so a WEB page or AI can render them inline without a file pull. (File paths are fine for the CLI too, but base64 is what makes this usable from the web-control demo and from an AI that cannot reach the desktop's filesystem.)+- **Plus the logical state** you already know: what document/board/design is open, which editor is focused, is a modal dialog blocking, unsaved-changes flag, current tool, plus whatever else a caller would need to decide the next move. -Why this matters: it turns "drive blind and hope" into "look, then act." An AI can call `<prefix>_state` before and after an action to confirm it landed; a human demo (like the web-control page) can show the app's true state instead of a cropped sub-window. ab already has the generic pieces (`desktop_list_windows` + `desktop_screenshot_base64 {hwnd}`), but the bridge produces the CORRECT set because it owns the window identities — that is the whole point of the ab-renders / bridge-knows-its-app boundary. Keep it read-only and non-gated (it only observes), and make it cheap enough to poll.+Why this matters: it turns "drive blind and hope" into "look, then act." An AI can call `<prefix>_state` before and after an action to confirm it landed; a human demo (like the web-control page) can show the app's true state instead of a cropped sub-window. ab already has the generic pieces (`desktop_list_windows` + `desktop_screenshot_base64 {hwnd}`), but the bridge produces the CORRECT set because it owns the window identities, and that is the whole point of the ab-renders / bridge-knows-its-app boundary. Keep it read-only and non-gated (it only observes), and make it cheap enough to poll.  --- -## ⭐ EDA bridges: the EIGHT SURFACES contract — `<prefix>_show_*` (John, 2026-08-15)+## ⭐ EDA bridges: the EIGHT SURFACES contract, `<prefix>_show_*` (John, 2026-08-15) -If your bridge drives an **electronics design tool** (KiCad, Fusion 360, Altium, and whatever comes next), implement these eight verbs. They are not a demo checklist; they are the complete set of things a user has to be able to DO from the Adom Wiki to close the circle from a wiki **component / molecule / board** all the way to the end app the EE actually designs in. A bridge that implements all eight plus `<prefix>_state` gets the web-control demo, the wiki round-trip, and the cross-app parity test for free, with **zero ab-side work** — that is the whole point of keeping ab generic.+If your bridge drives an **electronics design tool** (KiCad, Fusion 360, Altium, and whatever comes next), implement these eight verbs. They are not a demo checklist; they are the complete set of things a user has to be able to DO from the Adom Wiki to close the circle from a wiki **component / molecule / board** all the way to the end app the EE actually designs in. A bridge that implements all eight plus `<prefix>_state` gets the web-control demo, the wiki round-trip, and the cross-app parity test for free, with **zero ab-side work**, which is the whole point of keeping ab generic.  | # | Surface | Verb | What it opens | |---|---|---|---|@@ -172,7 +172,7 @@ If your bridge drives an **electronics design tool** (KiCad, Fusion 360, Altium, Rules that make the set usable rather than merely present:  - **`show` is not `open`.** An `open` verb takes a file path or a cloud URN and has app-specific arg shapes; a `show` verb takes the smallest identifier that names the surface (a symbol name plus its library, a file path, nothing at all when the app already has the design loaded) and is responsible for getting the app into that view whatever state it starts from. Having `fusion_open_lbr` does NOT satisfy #4, and having `kicad_open_board` does NOT satisfy #6. Ship the symmetric `show` pair.-- **They open in the BACKGROUND and are therefore useless without `<prefix>_state`.** A `show` verb that returns `{success:true, activeWorkspace:"..."}` and no image leaves the caller unable to prove anything happened. The state verb above is what renders the result. **Treat the eight verbs and the state verb as ONE deliverable** — six show verbs with no state verb rendered zero surfaces in the demo for a full day, which read as "the feature does not work."+- **They open in the BACKGROUND and are therefore useless without `<prefix>_state`.** A `show` verb that returns `{success:true, activeWorkspace:"..."}` and no image leaves the caller unable to prove anything happened. The state verb above is what renders the result. **Treat the eight verbs and the state verb as ONE deliverable**: six show verbs with no state verb rendered zero surfaces in the demo for a full day, which read as "the feature does not work." - **Say when a surface is COLD-capable and when it is WARM-ONLY.** KiCad's 3D chip and 3D board are reliable only once KiCad is already up (kicad-bridge #26). That is a legitimate answer, but it must come back in the verb's own `_hint` so the caller can sequence rather than guess. - **A prerequisite miss returns a `_hint` naming the exact next call**, never a bare failure: "needs a library open, call `<prefix>_open_lbr` first" or "run the demo first to generate the sample project." - **Zero-setup where you can.** Prefer pointing the demo-facing default at the app's STOCK libraries (KiCad uses `Device` / `Resistor_SMD`) so a fresh box shows something real with nothing staged. Where stock content does not exist, bundle a sample in your zip like Fusion's sample `.lbr`.
bridges-SDK.md+33−33
@@ -10,60 +10,60 @@ description: >- ---  -# Bridge SDK — build, publish, and surface a bridge for Adom Bridge+# Bridge SDK: build, publish, and surface a bridge for Adom Bridge -A **bridge** is a small local program (Python, Node, Rust, anything) that ab spawns and routes a **namespace of verbs** to over HTTP. KiCad, Fusion 360, Puppeteer (pup), native-browser, Blender are bridges. The modern flow has THREE moving parts you own — get all three right and a cloud/WSL2 container can *discover* your bridge from a plain-English request, confirm it's installed, and drive it with zero tribal knowledge:+A **bridge** is a small local program (Python, Node, Rust, anything) that ab spawns and routes a **namespace of verbs** to over HTTP. KiCad, Fusion 360, Puppeteer (pup), native-browser, Blender are bridges. The modern flow has THREE moving parts you own. Get all three right and a cloud/WSL2 container can *discover* your bridge from a plain-English request, confirm it's installed, and drive it with zero tribal knowledge: -1. **Runtime** — a versioned **Release `.zip`** + a **manifest** on your wiki page. ab's `bridge_install` streams it onto the user's machine and spawns it.-2. **Skills** — an **`adom-wiki pkg`** tarball carrying your consumer SKILL.md(s). A container installs it into `~/.claude/skills/` (or `~/.codex/skills/`) so the AI *there* knows your verbs.-3. **Discoverability** — **`discovery_triggers`** on your wiki page so a user saying *"open my app in pup"* surfaces YOU via `adom-wiki discover find`.+1. **Runtime**: a versioned **Release `.zip`** + a **manifest** on your wiki page. ab's `bridge_install` streams it onto the user's machine and spawns it.+2. **Skills**: an **`adom-wiki pkg`** tarball carrying your consumer SKILL.md(s). A container installs it into `~/.claude/skills/` (or `~/.codex/skills/`) so the AI *there* knows your verbs.+3. **Discoverability**: **`discovery_triggers`** on your wiki page so a user saying *"open my app in pup"* surfaces YOU via `adom-wiki discover find`. -> **THE ONE PRINCIPLE THAT MATTERS MOST — read this before anything else.**+> **THE ONE PRINCIPLE THAT MATTERS MOST: read this before anything else.** > **The AI almost never reads your on-disk SKILL.md. It ALWAYS reads the OUTPUT of a CLI call.** > So your leverage is in the **`_hint` / `_next` / `related` / `pitfalls` you return on EVERY verb response.** A verb that returns `{success:true}` and nothing else leaves the AI guessing and retry-looping. A verb that returns *what just happened, the obvious next verb, the related verbs, and the pitfall to avoid* gets driven perfectly on the first try. ab relays every field of your response **verbatim** to the calling AI. Treat each verb's output as the documentation the AI will actually read. (Your SKILL.md is for the *human* author + the rare AI that does read it; your verb OUTPUT is for the AI in the loop.)  --- -## Where every file lives — the two artifacts, their deps, and which skills go where+## Where every file lives: the two artifacts, their deps, and which skills go where -**Two DISTINCT artifacts, two DIFFERENT homes, two DIFFERENT consumers. Crossing them is the #1 inconsistency across bridge repos — pin it here before anything else.** (Live audit 2026-07-06: fusion + pup do this right; kicad had **61** loose `adom-bridge-kicad-v*.zip` on `/files` + a `mesa-llvmpipe-x64.zip`; blender dumped 2 loose zips too.)+**Two DISTINCT artifacts, two DIFFERENT homes, two DIFFERENT consumers. Crossing them is the #1 inconsistency across bridge repos, so pin it here before anything else.** (Live audit 2026-07-06: fusion + pup do this right; kicad had **61** loose `adom-bridge-kicad-v*.zip` on `/files` + a `mesa-llvmpipe-x64.zip`; blender dumped 2 loose zips too.)  | | **RUNTIME artifact** (Artifact 1) | **SKILLS pkg** (Artifact 2) | |---|---|---| | **What it is** | your bridge's actual CODE | your consumer `SKILL.md` docs |-| **Runs on** | the user's **DESKTOP** — ab spawns it | **nothing runs it** — docs the CONTAINER reads |+| **Runs on** | the user's **DESKTOP** (ab spawns it) | **nothing runs it** (docs the CONTAINER reads) | | **Published as** | a versioned **Release** (`release create` + `release upload --platform any`) | an **`adom-wiki pkg`** (`pkg publish --org adom`) | | **Installed by** | `bridge_install {manifestUrl}` → ab streams, unzips, spawns | `pkg install adom/<slug>` / `sync_skills` → `~/.claude/skills` + `~/.codex/skills` |-| **Contains** | server code + `bridge.json` at the zip **ROOT** + assets. **Node: SOURCE ONLY, no `node_modules`.** | ONLY: ROOT `SKILL.md`, `skills/<name>/SKILL.md` (user skills), `package.json`, `install.sh`, `uninstall.sh`. **Text only — the whole tarball should be a few KB.** |-| **NEVER contains** | the skills pkg | **ANY image (hero / screenshots — those are WIKI PAGE assets, see below), any binary, the Release zip, `src/`, `node_modules`, dev/publish skills, or the server's npm deps.** If `tar tzf` shows a `.png`/`.jpg`/`.zip`/`.exe`, you bundled bloat — remove it. |-| **its `package.json`** | (node) the **bridge SERVER's runtime deps** ab installs at spawn: `dependencies:{ puppeteer, … }` | the **pkg manifest**: `files[]` (user skills), `discovery_triggers`, scripts, **AND `dependencies:{"adom/adom-bridge":"^1.9.x"}`** (the container needs the ab CLI + core skills to drive you) — but NOT the server's npm deps |+| **Contains** | server code + `bridge.json` at the zip **ROOT** + assets. **Node: SOURCE ONLY, no `node_modules`.** | ONLY: ROOT `SKILL.md`, `skills/<name>/SKILL.md` (user skills), `package.json`, `install.sh`, `uninstall.sh`. **Text only: the whole tarball should be a few KB.** |+| **NEVER contains** | the skills pkg | **ANY image (hero / screenshots: those are WIKI PAGE assets, see below), any binary, the Release zip, `src/`, `node_modules`, dev/publish skills, or the server's npm deps.** If `tar tzf` shows a `.png`/`.jpg`/`.zip`/`.exe`, you bundled bloat. Remove it. |+| **its `package.json`** | (node) the **bridge SERVER's runtime deps** ab installs at spawn: `dependencies:{ puppeteer, … }` | the **pkg manifest**: `files[]` (user skills), `discovery_triggers`, scripts, **AND `dependencies:{"adom/adom-bridge":"^1.9.x"}`** (the container needs the ab CLI + core skills to drive you), but NOT the server's npm deps | -**Yes, a node bridge has TWO `package.json` files and they are NOT interchangeable.** One rides inside the Release zip and lists the **bridge server's** runtime deps (puppeteer, sharp, ...) that ab installs on the *desktop* at spawn. The other is the **pkg manifest** in the skills tarball: it lists your user-skill `files[]` + `discovery_triggers` AND declares **`dependencies:{"adom/adom-bridge":"^1.9.x"}`** — `pkg install` resolves that npm-style, so installing your bridge's skill pkg also pulls the adom-bridge CLI + core skills the container needs to drive you. What the pkg manifest must NOT list is the **server's** npm deps (those are desktop-side, in the zip) — put them there and you bloat every container install, since the container never runs your server.+**Yes, a node bridge has TWO `package.json` files and they are NOT interchangeable.** One rides inside the Release zip and lists the **bridge server's** runtime deps (puppeteer, sharp, ...) that ab installs on the *desktop* at spawn. The other is the **pkg manifest** in the skills tarball: it lists your user-skill `files[]` + `discovery_triggers` AND declares **`dependencies:{"adom/adom-bridge":"^1.9.x"}`**. `pkg install` resolves that npm-style, so installing your bridge's skill pkg also pulls the adom-bridge CLI + core skills the container needs to drive you. What the pkg manifest must NOT list is the **server's** npm deps (those are desktop-side, in the zip). Put them there and you bloat every container install, since the container never runs your server.  **Where each file physically lives (this is the map that ends the inconsistency):** - **`bridge.json`** → the **root of the Release zip** (ab reads it post-unzip). Carries `version` + `updateManifestUrl`.-- **The Release `.zip`** → upload it as a **Release asset** (`release upload … --platform any`) and point the manifest `url` at its **release-download path**: `https://wiki.adom.inc/download/adom/<slug>/<ver>/<file>.zip` (verified to serve anon 206). **fusion + pup do exactly this — copy them.** A loose `/files` blob technically serves too (206), so this is a HYGIENE + right-home rule, not "it won't work": the `/files` store is your git **page repo** (for the manifest, text, images), so a binary there accumulates forever (kicad has **61** stale `-v*.zip`), isn't pinned to a Release, and the `/blob/...` URL is just an **alias of the same blob** (same sha) — you can't tidy it. **⚠ Migrating off `/files` is a SEQUENCE, not a delete:** (1) `release upload` the zip, (2) repoint the manifest `url` at the `/download/` path, (3) verify it serves anon, (4) *only then* `repo rm` the old `/files` copies. **NEVER `repo rm` a zip while your manifest still points at it** (or at its `/blob` alias) — that breaks `bridge_install` + auto-update instantly.-- **The manifest** (`adom-bridge-<name>-manifest.json`, this naming is the consistent one — keep it) → POSTed to your page **`/files`**. Its `url` points at the **Release download** (`https://wiki.adom.inc/download/adom/<slug>/<ver>/<file>.zip`), base-relative + extension-agnostic. `updateManifestUrl` (in the manifest AND in bridge.json) points back at this manifest.-- **A big RUNTIME ASSET** (a headless-GL pack like Mesa, a browser download) → declare it in **`prewarm.assets`** so ab downloads + manages it, or bundle it in your seed. **NOT a loose `/files` blob** — kicad's `mesa-llvmpipe-x64.zip` on `/files` is the anti-pattern.-- **Your hero image + screenshots** → **WIKI PAGE assets** — set them on the page (the page's hero / `repo push` the image to the page), **NEVER in the skills tarball.** A container that `pkg install`s your skills never renders a hero, so a bundled hero (a real one was **1 MB**) is pure download bloat on every install. **Do NOT put `hero: docs/hero.png` + `docs/**` in the pkg `package.json`** — that is exactly what stuffs the image into the tarball.+- **The Release `.zip`** → upload it as a **Release asset** (`release upload … --platform any`) and point the manifest `url` at its **release-download path**: `https://wiki.adom.inc/download/adom/<slug>/<ver>/<file>.zip` (verified to serve anon 206). **fusion + pup do exactly this, so copy them.** A loose `/files` blob technically serves too (206), so this is a HYGIENE + right-home rule, not "it won't work": the `/files` store is your git **page repo** (for the manifest, text, images), so a binary there accumulates forever (kicad has **61** stale `-v*.zip`), isn't pinned to a Release, and the `/blob/...` URL is just an **alias of the same blob** (same sha), so you can't tidy it. **⚠ Migrating off `/files` is a SEQUENCE, not a delete:** (1) `release upload` the zip, (2) repoint the manifest `url` at the `/download/` path, (3) verify it serves anon, (4) *only then* `repo rm` the old `/files` copies. **NEVER `repo rm` a zip while your manifest still points at it** (or at its `/blob` alias). That breaks `bridge_install` + auto-update instantly.+- **The manifest** (`adom-bridge-<name>-manifest.json`, this naming is the consistent one, keep it) → POSTed to your page **`/files`**. Its `url` points at the **Release download** (`https://wiki.adom.inc/download/adom/<slug>/<ver>/<file>.zip`), base-relative + extension-agnostic. `updateManifestUrl` (in the manifest AND in bridge.json) points back at this manifest.+- **A big RUNTIME ASSET** (a headless-GL pack like Mesa, a browser download) → declare it in **`prewarm.assets`** so ab downloads + manages it, or bundle it in your seed. **NOT a loose `/files` blob**: kicad's `mesa-llvmpipe-x64.zip` on `/files` is the anti-pattern.+- **Your hero image + screenshots** → **WIKI PAGE assets**: set them on the page (the page's hero / `repo push` the image to the page), **NEVER in the skills tarball.** A container that `pkg install`s your skills never renders a hero, so a bundled hero (a real one was **1 MB**) is pure download bloat on every install. **Do NOT put `hero: docs/hero.png` + `docs/**` in the pkg `package.json`**, because that is exactly what stuffs the image into the tarball. -**The three kinds of skill — where each goes (this IS the demarcation, one table):**+**The three kinds of skill, and where each goes (this IS the demarcation, one table):**  | Skill kind | Who reads it | Lives in | In the pkg tarball? | Public? | |---|---|---|---|---|-| **USER** (the public / consumer skill) | a consumer AI driving your verbs | your **pkg** | **YES** — that is the pkg's whole job | yes (the pkg is public) |+| **USER** (the public / consumer skill) | a consumer AI driving your verbs | your **pkg** | **YES**, that is the pkg's whole job | yes (the pkg is public) | | **DEV** | a maintainer editing the bridge | your **source repo** | **NO, never** | only if your repo is public | | **PUBLISH** | a maintainer releasing the bridge | your **source repo** | **NO, never** | only if your repo is public | -**The one rule:** the pkg tarball carries **USER skills only**; **DEV + PUBLISH skills live in the source repo.** Do NOT "scope" dev/publish into the pkg with `user-invocable:false` — that still ships them (pure bloat). Open-vs-closed source only sets your repo's *visibility*; it never changes what's in the pkg. adom-bridge core does exactly this: user/integration skills in the pkg; `RELEASE` / `WIKI_PUBLISH` / `SIGNING` / `AD_RELAUNCH` live only in the source repo.+**The one rule:** the pkg tarball carries **USER skills only**; **DEV + PUBLISH skills live in the source repo.** Do NOT "scope" dev/publish into the pkg with `user-invocable:false`, since that still ships them (pure bloat). Open-vs-closed source only sets your repo's *visibility*; it never changes what's in the pkg. adom-bridge core does exactly this: user/integration skills in the pkg; `RELEASE` / `WIKI_PUBLISH` / `SIGNING` / `AD_RELAUNCH` live only in the source repo.  --- -## The boundary — what ab owns vs what YOU own+## The boundary: what ab owns vs what YOU own  The #1 mistake is filing ab bugs for bridge issues. The split: -**ab core owns** (don't reimplement): the CLI/relay/`/command` passthrough (ab relays your JSON verbatim — every field); spawning/reaping your process, the **stable port**, single-instance, the persistent-respawn supervisor, cache install + auto-update; host/process facts (running? pid? port?), the status-chip rendering, the GUI card, the activity log; `status`/`_hint` classification for timeouts/disconnects (ab authors those — you never stamp them).+**ab core owns** (don't reimplement): the CLI/relay/`/command` passthrough (ab relays your JSON verbatim, every field); spawning/reaping your process, the **stable port**, single-instance, the persistent-respawn supervisor, cache install + auto-update; host/process facts (running? pid? port?), the status-chip rendering, the GUI card, the activity log; `status`/`_hint` classification for timeouts/disconnects (ab authors those, you never stamp them).  **You own**: your verbs + their behavior + their **rich hints**; your `/health` (+ optional chip fields); your `<prefix>describe` catalog; your wiki page (hero, brief, `discovery_triggers`, docs); your published **manifest + Release zip**; and your **skills pkg**. @@ -73,12 +73,12 @@ If a *bridge verb* misbehaves → fix the bridge. If a *generic ab capability* i  ## ⭐ REQUIRED: every bridge MUST expose a `<prefix>uninstall` verb (John, 2026-08-12) -An Adom employee reported that uninstalling left cruft behind, because ab's uninstaller could remove ab's own files but had no way to clean up what each BRIDGE created. Bridges install their own state OUTSIDE ab's directory — a host-app add-in (the Fusion add-in lives in `%APPDATA%`, not ab's dir), per-bridge caches, downloaded runtimes/models, registry keys, temp dirs. Only the bridge knows where all of that is.+An Adom employee reported that uninstalling left cruft behind, because ab's uninstaller could remove ab's own files but had no way to clean up what each BRIDGE created. Bridges install their own state OUTSIDE ab's directory: a host-app add-in (the Fusion add-in lives in `%APPDATA%`, not ab's dir), per-bridge caches, downloaded runtimes/models, registry keys, temp dirs. Only the bridge knows where all of that is. -So the contract now requires: **every bridge exposes a `<prefix>uninstall` verb** (e.g. `kicad_uninstall`, `fusion_uninstall`, `pup_uninstall`) that removes EVERYTHING that bridge created on the machine — its add-in/plugin files in host-app dirs, its caches, its registry entries, its temp files, anything outside ab's own `bridges-cache` (which ab removes). It must be:-- **Idempotent and non-interactive** (safe to call when already clean; never prompt/block — it runs during a silent `/S` uninstall cascade).+So the contract now requires: **every bridge exposes a `<prefix>uninstall` verb** (e.g. `kicad_uninstall`, `fusion_uninstall`, `pup_uninstall`) that removes EVERYTHING that bridge created on the machine: its add-in/plugin files in host-app dirs, its caches, its registry entries, its temp files, anything outside ab's own `bridges-cache` (which ab removes). It must be:+- **Idempotent and non-interactive** (safe to call when already clean; never prompt/block, since it runs during a silent `/S` uninstall cascade). - **Best-effort per-item** (one un-removable file must not abort the rest; report what was and wasn't removed).-- **Scoped to your own artifacts ONLY** — never touch the host app itself (don't uninstall KiCad/Fusion), other bridges, or user data.+- **Scoped to your own artifacts ONLY**: never touch the host app itself (don't uninstall KiCad/Fusion), other bridges, or user data.  ab's uninstaller enumerates the installed bridges and calls each bridge's `<prefix>uninstall` before removing ab, so an uninstall cleans up the whole ecosystem. Declare the verb in your `bridge.json` (an `uninstall` field naming the verb) so ab knows to call it; if absent, ab logs that the bridge left cleanup to the user. Add it to your `describe` catalog and skill like any other verb. @@ -143,20 +143,20 @@ This keeps the "ab renders, the bridge knows its app" boundary: ab does not hard  ## ⭐ SUGGESTED: expose a `<prefix>_state` verb that returns your app's FULL current state (John, 2026-08-14) -A user or AI should be able to ask a bridge, at any moment, "what does the app you control look like RIGHT NOW?" and get back the complete picture, not one guessed window. Today a caller that wants to see KiCad screenshots the first window whose title contains "KiCad" and gets ONE of its two windows; Fusion has a main window plus palettes, so a single title-match grabs a skinny panel. That is wrong, and it is wrong because only the BRIDGE knows the real window set (it tracks its app's parent/child hwnds) — a generic title match cannot.+A user or AI should be able to ask a bridge, at any moment, "what does the app you control look like RIGHT NOW?" and get back the complete picture, not one guessed window. Today a caller that wants to see KiCad screenshots the first window whose title contains "KiCad" and gets ONE of its two windows; Fusion has a main window plus palettes, so a single title-match grabs a skinny panel. That is wrong, and it is wrong because only the BRIDGE knows the real window set (it tracks its app's parent/child hwnds). A generic title match cannot.  So, when your app has a meaningful visual/logical state: **expose a `<prefix>_state` verb** (e.g. `kicad_state`, `fusion_state`, `blender_state`) that returns the app's current STATE OF THE UNION: -- **A screenshot ARRAY of EVERY window the app owns** — the parent + all child/related windows (project manager + symbol editor + PCB editor + any open dialog), each captured by its real hwnd. Return them as **base64** (`{ shots: [{ title, hwnd, image, width, height }] }`) so a WEB page or AI can render them inline without a file pull. (File paths are fine for the CLI too, but base64 is what makes this usable from the web-control demo and from an AI that cannot reach the desktop's filesystem.)-- **Plus the logical state** you already know: what document/board/design is open, which editor is focused, is a modal dialog blocking, unsaved-changes flag, current tool — whatever a caller would need to decide the next move.+- **A screenshot ARRAY of EVERY window the app owns**: the parent + all child/related windows (project manager + symbol editor + PCB editor + any open dialog), each captured by its real hwnd. Return them as **base64** (`{ shots: [{ title, hwnd, image, width, height }] }`) so a WEB page or AI can render them inline without a file pull. (File paths are fine for the CLI too, but base64 is what makes this usable from the web-control demo and from an AI that cannot reach the desktop's filesystem.)+- **Plus the logical state** you already know: what document/board/design is open, which editor is focused, is a modal dialog blocking, unsaved-changes flag, current tool, plus whatever else a caller would need to decide the next move. -Why this matters: it turns "drive blind and hope" into "look, then act." An AI can call `<prefix>_state` before and after an action to confirm it landed; a human demo (like the web-control page) can show the app's true state instead of a cropped sub-window. ab already has the generic pieces (`desktop_list_windows` + `desktop_screenshot_base64 {hwnd}`), but the bridge produces the CORRECT set because it owns the window identities — that is the whole point of the ab-renders / bridge-knows-its-app boundary. Keep it read-only and non-gated (it only observes), and make it cheap enough to poll.+Why this matters: it turns "drive blind and hope" into "look, then act." An AI can call `<prefix>_state` before and after an action to confirm it landed; a human demo (like the web-control page) can show the app's true state instead of a cropped sub-window. ab already has the generic pieces (`desktop_list_windows` + `desktop_screenshot_base64 {hwnd}`), but the bridge produces the CORRECT set because it owns the window identities, and that is the whole point of the ab-renders / bridge-knows-its-app boundary. Keep it read-only and non-gated (it only observes), and make it cheap enough to poll.  --- -## ⭐ EDA bridges: the EIGHT SURFACES contract — `<prefix>_show_*` (John, 2026-08-15)+## ⭐ EDA bridges: the EIGHT SURFACES contract, `<prefix>_show_*` (John, 2026-08-15) -If your bridge drives an **electronics design tool** (KiCad, Fusion 360, Altium, and whatever comes next), implement these eight verbs. They are not a demo checklist; they are the complete set of things a user has to be able to DO from the Adom Wiki to close the circle from a wiki **component / molecule / board** all the way to the end app the EE actually designs in. A bridge that implements all eight plus `<prefix>_state` gets the web-control demo, the wiki round-trip, and the cross-app parity test for free, with **zero ab-side work** — that is the whole point of keeping ab generic.+If your bridge drives an **electronics design tool** (KiCad, Fusion 360, Altium, and whatever comes next), implement these eight verbs. They are not a demo checklist; they are the complete set of things a user has to be able to DO from the Adom Wiki to close the circle from a wiki **component / molecule / board** all the way to the end app the EE actually designs in. A bridge that implements all eight plus `<prefix>_state` gets the web-control demo, the wiki round-trip, and the cross-app parity test for free, with **zero ab-side work**, which is the whole point of keeping ab generic.  | # | Surface | Verb | What it opens | |---|---|---|---|@@ -172,7 +172,7 @@ If your bridge drives an **electronics design tool** (KiCad, Fusion 360, Altium, Rules that make the set usable rather than merely present:  - **`show` is not `open`.** An `open` verb takes a file path or a cloud URN and has app-specific arg shapes; a `show` verb takes the smallest identifier that names the surface (a symbol name plus its library, a file path, nothing at all when the app already has the design loaded) and is responsible for getting the app into that view whatever state it starts from. Having `fusion_open_lbr` does NOT satisfy #4, and having `kicad_open_board` does NOT satisfy #6. Ship the symmetric `show` pair.-- **They open in the BACKGROUND and are therefore useless without `<prefix>_state`.** A `show` verb that returns `{success:true, activeWorkspace:"..."}` and no image leaves the caller unable to prove anything happened. The state verb above is what renders the result. **Treat the eight verbs and the state verb as ONE deliverable** — six show verbs with no state verb rendered zero surfaces in the demo for a full day, which read as "the feature does not work."+- **They open in the BACKGROUND and are therefore useless without `<prefix>_state`.** A `show` verb that returns `{success:true, activeWorkspace:"..."}` and no image leaves the caller unable to prove anything happened. The state verb above is what renders the result. **Treat the eight verbs and the state verb as ONE deliverable**: six show verbs with no state verb rendered zero surfaces in the demo for a full day, which read as "the feature does not work." - **Say when a surface is COLD-capable and when it is WARM-ONLY.** KiCad's 3D chip and 3D board are reliable only once KiCad is already up (kicad-bridge #26). That is a legitimate answer, but it must come back in the verb's own `_hint` so the caller can sequence rather than guess. - **A prerequisite miss returns a `_hint` naming the exact next call**, never a bare failure: "needs a library open, call `<prefix>_open_lbr` first" or "run the demo first to generate the sample project." - **Zero-setup where you can.** Prefer pointing the demo-facing default at the app's STOCK libraries (KiCad uses `Device` / `Resistor_SMD`) so a fresh box shows something real with nothing staged. Where stock content does not exist, bundle a sample in your zip like Fusion's sample `.lbr`.