{
  "schema_version": 1,
  "type": "app",
  "slug": "adom-desktop-bridges",
  "title": "Adom Desktop - Bridge SDK",
  "brief": "Bridge SDK guide: bridge.json schema, kind:python/node/exe, hello-python + hello-rust reference templates, packaging + lifecycle commands.",
  "version": "1.1.4",
  "tags": [],
  "license": "MIT",
  "discovery_triggers": [
    "third-party bridge",
    "adom-desktop bridge",
    "bridge SDK",
    "bridge.json",
    "bridge manifest",
    "write a bridge",
    "custom bridge",
    "altium bridge",
    "matlab bridge",
    "oscilloscope bridge",
    "hello sample bridge",
    "bridge_install",
    "bridge_uninstall",
    "bridge_list",
    "bridge_pause",
    "bridge_resume",
    "refresh_bridges",
    "dynamic bridge",
    "ship a bridge",
    "host a bridge",
    "bridges-registry",
    "extend adom-desktop"
  ],
  "discovery_pitch": "Write a third-party bridge for adom-desktop — ship a custom EDA / lab / vendor integration as a stdlib-Python or Node server, install it via a wiki manifest URL, no fork of adom-desktop required.",
  "sample_prompts": [
    {
      "label": "Write a bridge",
      "prompt": "Help me write a third-party Adom Desktop bridge for X"
    },
    {
      "label": "Install bridge",
      "prompt": "Install the hello-python sample bridge so I can see how third-party bridges work"
    },
    {
      "label": "Try Rust bridge",
      "prompt": "Install the hello-rust sample bridge — I want to see a single-binary Rust example"
    },
    {
      "label": "Package bridge",
      "prompt": "Package my bridge dir as a wiki-installable manifest+zip pair"
    }
  ],
  "install": {
    "binary_name": "adom-desktop-bridges",
    "install_dir": "",
    "install_hint": "",
    "version_cmd": ""
  },
  "readme": "---\nname: adom-desktop-bridge-sdk\ndescription: Author, PUBLISH, and make-discoverable an adom-desktop bridge — a small local server that adds a namespace of verbs (kicad_, fusion_, browser_, your_prefix_) to adom-desktop. Use when building, publishing, updating, prewarming, or debugging a bridge, or deciding what skills to ship. Covers the AD-core-vs-author boundary; the #1 rule (rich hints in EVERY verb's OUTPUT, because the AI reads CLI output, never your on-disk skill); bridge.json; the THREE things you publish (runtime Release zip + manifest, container skills pkg, wiki discovery_triggers); prewarm; the publish/dev/user SKILL set you must ship; and the auto-discover → install-check → use flow.\n---\n\n# Bridge SDK — build, publish, and surface a bridge for Adom Desktop\n\nA **bridge** is a small local program (Python, Node, Rust, anything) that AD 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:\n\n1. **Runtime** — a versioned **Release `.zip`** + a **manifest** on your wiki page. AD's `bridge_install` streams it onto the user's machine and spawns it.\n2. **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.\n3. **Discoverability** — **`discovery_triggers`** on your wiki page so a user saying *\"open my app in pup\"* surfaces YOU via `adom-wiki discover find`.\n\n> **THE ONE PRINCIPLE THAT MATTERS MOST — read this before anything else.**\n> **The AI almost never reads your on-disk SKILL.md. It ALWAYS reads the OUTPUT of a CLI call.**\n> 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. AD 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.)\n\n---\n\n## The boundary — what AD owns vs what YOU own\n\nThe #1 mistake is filing AD bugs for bridge issues. The split:\n\n**AD core owns** (don't reimplement): the CLI/relay/`/command` passthrough (AD 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 (AD authors those — you never stamp them).\n\n**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**.\n\nIf a *bridge verb* misbehaves → fix the bridge. If a *generic AD capability* is missing (a new lifecycle verb, a relay field) → file it against AD, but don't re-absorb the bridge into AD.\n\n---\n\n## Runtime contract — AD provisions Node/Python; you bind loopback (v1.9.63 / v1.9.69)\n\n**The single most important change for a Node or Python bridge.** As of AD **v1.9.63** you no longer bootstrap your own interpreter, and you MUST bind loopback. Four rules:\n\n1. **Don't bootstrap your own Node/Python.** AD owns a global, portable, **no-UAC** runtime under `~/.adom/adom-runtimes/`: a system install if one's on PATH, else a portable copy it downloads + prewarms in the background on first launch. AD spawns your `entrypoint` with that interpreter **by absolute path**. So **delete every `winget install node` / `desktop_install_node` / \"please install Python\" / self-download step** from your bridge and its skills. `spawn.kind: \"node\"|\"python\"` IS your runtime declaration — AD reads it and provisions the runtime. Anyone can check status with the **`runtimes`** verb (`state ∈ absent|installing|ready|failed`, `source ∈ system|cache`, version). AD pins the version (Node 22.11.0 / Python 3.12.13 today) — need a different major? ask the AD-core thread; don't ship your own.\n\n2. **Bind `ADOM_BIND_HOST`, never `0.0.0.0`.** AD passes an **`ADOM_BIND_HOST`** env var (always `127.0.0.1`) to every bridge it spawns. A bare listen that defaults to `0.0.0.0` pops a **Windows Firewall \"allow access?\" dialog** on first run — and AD's hard guarantee to users is **no firewall prompt by default**.\n   - Node: `server.listen(PORT, process.env.ADOM_BIND_HOST || '127.0.0.1', cb)`\n   - Python: `HTTPServer((os.environ.get('ADOM_BIND_HOST', '127.0.0.1'), PORT), Handler)`\n\n3. **Ship the Release zip source-only — AD installs your deps, and v1.9.69 makes it bulletproof.** A Node bridge's zip carries SOURCE only (no `node_modules`). AD reuses the bundled seed's `node_modules` via `NODE_PATH` when versions match, else runs **`npm install --include=optional`** for you with the managed runtime's own npm. **As of v1.9.69 AD puts the managed runtime's dir on the child PATH for that install AND for your spawned process** — so a dep's postinstall / `node-gyp` / `prebuild-install` that shells out to a **bare** `node`/`npm`/`npx` (puppeteer's own `node install.mjs`, `sharp`, `keytar`, `better-sqlite3`, `bcrypt`) resolves even on a machine with **no system Node**. **You do NOT add the runtime to PATH yourself** — AD guarantees it. (Python: the interpreter dir + `Scripts/` are on PATH too, so pip can build wheels.)\n\n4. **Bundle deps in your SEED for an instant first spawn.** Source-only zip + a bundled-seed `node_modules` at the same version = AD reuses the seed via `NODE_PATH` (instant); a wiki version newer than the seed forces one cold `npm install` (now PATH-correct, but slower). Bump the seed in lockstep with each Release.\n\n**Net:** declare `spawn.kind`, bind `ADOM_BIND_HOST`, ship source-only. AD does the rest — no UAC, no firewall prompt, no `'node' is not recognized`.\n\n---\n\n## Self-audit checklist — run this when asked to \"audit against the SDK\"\n\nWhen a maintainer says *\"audit your code, skills, README, and wiki page against the Bridge SDK,\"* **this is the checklist.** Each item names its section below; fix every miss, then re-check.\n\n**bridge.json** (the manifest in your zip):\n- [ ] `spawn.kind` ∈ `python|node|exe|external-http`, with **`entrypoint`** (NOT `process`/`cmd`/`args`); `healthEndpoint` is **inside `spawn`**; `port:0` lets AD pick. *(→ bridge.json)*\n- [ ] `updateManifestUrl` → YOUR page's manifest (durable auto-update); `docs` → your own page; **`homepage` is a `wiki.adom.inc` URL, NEVER `github.com`.**\n- [ ] `detect` declared iff you drive a HOST APP (KiCad/Fusion/Altium…); omitted if you bring your own runtime (pup). `prewarm.assets` declared for heavy downloads (e.g. `chrome-for-testing`). *(→ Cold-start tiers)*\n\n**Runtime** (Node/Python bridges) *(→ Runtime contract)*:\n- [ ] You bind **`ADOM_BIND_HOST`** (loopback default), never `0.0.0.0`.\n- [ ] You do **NOT** bootstrap your own Node/Python — no `winget`/`desktop_install_node`/self-download in code or skills. `spawn.kind` is the declaration; AD provisions it.\n- [ ] Release zip is **source-only** (no `node_modules`); you rely on AD's `npm install` (PATH-correct as of v1.9.69) + a lockstep bundled seed for instant spawn. You do NOT put the runtime on PATH yourself.\n\n**Verbs & output** (what the AI actually reads):\n- [ ] EVERY verb returns a rich **`_hint`** (+ `_next`/`related`/`pitfalls`). *(→ THE ONE PRINCIPLE)*\n- [ ] A `<prefix>_describe` verb lists your whole catalog. *(→ Verb contract)*\n- [ ] No verb blocks on a heavy install/download — background it, report `installing`/`warming`; `<prefix>_readiness` is read-only. *(→ Cold-start tiers)*\n- [ ] `/health` returns `led`/`summary`/`tooltip` truthfully. *(→ Health + status chip)*\n\n**Discovery** (can a plain request find you?):\n- [ ] **10+ user-task `discovery_triggers`** (everyday phrasings, NOT dev jargon). **No triggers = invisible.** *(→ Artifact 3)*\n- [ ] You ran `discover preview --json` on 5-7 real user queries and land **top-3** for each.\n- [ ] Triggers stay in YOUR niche — you don't poach a sibling's (login/forms → native-browser, not pup). `discovery_pitch` is an INSTRUCTION.\n\n**Skills & docs** (what the container + humans read):\n- [ ] You ship the publish/dev/user **3-skill set**; only the USER skill is named `SKILL.md` (dev/publish are `dev-skills/*.md`, source-only). *(→ The SKILL SET)*\n- [ ] Your USER skill leads with the cold-start \"read before you panic\" section + the 8 conventions, and points the AI at `bridge_readiness`. *(→ USER-skill conventions)*\n- [ ] README + wiki page + skills all AGREE with the above (no stale github links, no wrong schema, triggers vetted).\n\n---\n\n## bridge.json — the manifest that ships INSIDE your zip\n\n```jsonc\n{\n  \"manifest_version\": 1,\n  \"name\": \"blender\",                        // registry name → verb prefix \"blender_\"\n  \"displayName\": \"Blender\",                 // in-app chip label (the tool's real name)\n  \"version\": \"1.2.0\",                       // numeric; a newer CACHE copy supersedes the bundled seed\n  \"description\": \"Drive Blender headless + GUI\",\n  \"author\": \"you\",\n  \"license\": \"MIT\",\n  \"docs\": \"https://wiki.adom.inc/adom/adom-desktop-blender-bridge\",  // your OWN page (drives skillPkg + hero)\n  \"hero\": \"https://.../hero.png\",           // optional; else AD uses your wiki page hero, else a monogram. Ship it at 1.6 aspect (≈2000×1250) — see \"Card hero + update cost\"\n  \"spawn\": {\n    \"kind\": \"python\",                       // python | node | exe | external-http  (NOT \"process\")\n    \"entrypoint\": \"server.py\",              // the file/exe AD launches; AD AUTO-APPENDS `--port <port>` to argv\n    \"port\": 0,                              // 0 = AD picks a STABLE port (recommended). Bind 127.0.0.1:<that port>.\n    \"persistent\": true,                     // AD's supervisor respawns it if it dies\n    \"healthEndpoint\": \"/health\",            // INSIDE spawn. AD probes it for the chip (default /health)\n    \"stopMethod\": \"kill\",                   // kill | sigterm | graceful_endpoint\n    \"killImageName\": \"python.exe\"           // image name for taskkill on stop (kind:python/node)\n  },\n  \"verbPrefixes\": [\"blender_\"],             // any verb with these prefixes routes to you\n  \"verbs\": [\"blender_render\", \"blender_status\", \"blender_describe\"],  // every verb (routing + Verbs tab)\n  \"statusVerb\": \"blender_status\",           // verb a caller polls after a long op times out\n  \"updateManifestUrl\": \"https://wiki.adom.inc/api/v1/pages/adom-desktop-blender-bridge/files/blender-manifest.json\",\n\n  // v1.9.47 cold-start (see \"Cold-start tiers\" below):\n  \"detect\": {                               // Tier 2 — how AD DETECTS your HOST APP. AD NEVER installs it.\n    \"hostApp\": \"Blender\",                   // omit the whole `detect` block if your bridge brings its own runtime\n    \"appPathsExe\": \"blender.exe\",           //   (e.g. pup ships its own browser → no host app)\n    \"paths\": { \"windows\": [\"%ProgramFiles%\\\\Blender Foundation\\\\*\\\\blender.exe\"] }\n  },\n  \"prewarm\": { \"assets\": [] }               // Tier 1 — heavy runtime assets AD prewarms (e.g. [\"chrome-for-testing\"])\n}\n```\n\n- **`spawn.kind`** is `python` | `node` | `exe` | `external-http` — there is **no `\"process\"` kind**, and no `cmd`/`args`/`cwd`. AD launches `spawn.entrypoint` and **automatically appends `--port <port>`** to its argv; your server parses `--port`. (`external-http` = AD doesn't spawn, only probes a port you declare via a `discovery` block.)\n- **`spawn.port: 0`** → AD allocates a STABLE port (persisted across respawns + AD restarts) and passes it. Bind `127.0.0.1:<port>`. NEVER hardcode a port (Hyper-V/Docker silently reserve ranges — see PORTS.md).\n- **`spawn.healthEndpoint`** lives **inside `spawn`** (not at the top level), default `/health`.\n- **`updateManifestUrl`** — the full URL of YOUR manifest on YOUR page. **Declare it in `bridge.json`** so it survives a republish; AD polls it (on launch + every 4h) and auto-updates users. (AD also remembers the install URL in `~/.adom/bridge-install-urls.json` as a fallback, but the declared field is the durable, author-intended path.)\n- **`docs`** — point at YOUR dedicated wiki page. AD derives your **skillPkg** (`adom/<slug>`) and your card hero from this. Don't point it at a shared/structural page.\n- **`homepage`** — a **`wiki.adom.inc`** URL (your own page, or `https://wiki.adom.inc/adom/adom-desktop` for the platform). **NEVER `github.com/...`** — GitHub is source-backup only; the wiki is the canonical home. (A `homepage: github.com/adom-inc/adom-desktop` is a stale default to fix.)\n- **`detect` / `prewarm`** — the v1.9.47 cold-start declarations (full rules in \"Cold-start tiers\" below). `detect` = your HOST APP (AD detects, never installs); `prewarm` = your heavy RUNTIME assets (AD prewarms/auto-installs). A node bridge that ships its deps still works without `prewarm`; declare `prewarm.assets` only for big *downloaded* assets like a browser.\n\n---\n\n## Artifact 1 — the RUNTIME: publish your `.zip` as a RELEASE + a manifest\n\nThis is the code AD streams onto the user's machine. Two files on your page:\n\n1. **A versioned Release `.zip`** of your bridge dir (server + `bridge.json` at the zip ROOT + any assets). Ship it as a **Release** (the downloadable, version-pinned artifact), not a loose `/files` blob:\n   ```bash\n   adom-wiki release create adom/adom-desktop-<bridge>-bridge <version> --title \"...\" --changelog \"...\"\n   adom-wiki release upload adom/adom-desktop-<bridge>-bridge <version> your-bridge-v<version>.zip --platform any\n   ```\n   (Node bridges: the zip carries SOURCE only — keep `node_modules` out; AD/the seed handle deps. See Prewarm.)\n2. **The manifest JSON** (e.g. `blender-manifest.json`) — identity + where the zip is — POSTed to your page `/files`:\n   ```jsonc\n   { \"name\":\"blender\", \"version\":\"1.2.0\",\n     \"url\":\"https://wiki.adom.inc/download/adom/adom-desktop-blender-bridge/1.2.0/blender-v1.2.0.zip\",  // base-relative, extension-agnostic (.bin works where wiki blocks .zip)\n     \"sha256\":\"…\", \"size\":123456, \"verbPrefixes\":[\"blender_\"], \"healthEndpoint\":\"/health\",\n     \"updateManifestUrl\":\"https://wiki.adom.inc/api/v1/pages/adom-desktop-blender-bridge/files/blender-manifest.json\" }\n   ```\n\n**Users install/update** with AD's `bridge_install {manifestUrl}` → AD downloads the zip, sha256-verifies, unzips into `%LOCALAPPDATA%\\Adom Desktop\\bridges-cache\\<name>\\`, rescans, and remembers the URL for auto-update.\n\n**To ship a new version:** bump `version` in BOTH `bridge.json` (in the zip) and the manifest, re-`release upload` the zip, re-POST the manifest. Users auto-get it on the next 4h poll, or immediately via `refresh_bridges {name}`. **A bundled SEED** (a `plugins/<name>/` folder shipped in AD's NSIS) is the offline/first-run fallback; your published cache copy with a higher `version` supersedes it (cache-over-bundled, version-aware).\n\n---\n\n## Artifact 2 — the SKILLS pkg: container-side docs (`.claude/skills` / `.codex/skills`)\n\nYour runtime runs on the user's DESKTOP; the AI driving it runs in a cloud/WSL2 CONTAINER. That container needs your **consumer SKILL.md** locally to know your verbs. Ship it as its own **`adom-wiki pkg`** (skills only, NO binaries):\n\n```bash\n# Stage dir: a ROOT SKILL.md (required) + USER skills under skills/<slug>/SKILL.md, and\n# package.json {name:\"adom-desktop-<bridge>-bridge\", version, discovery_triggers, files:[...]}.\n# THREE traps that bite — verify each with `tar tzf` before publishing:\n#  1. ⚠ The wiki indexes EVERY file named SKILL.md in the tarball as an INSTALLABLE,\n#     Skills-tab-listed USER skill (it counts them as skill_count). So your DEV + PUBLISH\n#     skills must NOT be named SKILL.md — put them in dev-skills/*.md, source-only — else\n#     every user who installs your pkg also installs your dev/publish docs.\n#  2. ⚠ pkg pack/publish HONORS the stage dir's .gitignore AND the package.json `files`\n#     allowlist needs the GLOB: a bare \"skills\" entry ships ZERO skill files — use \"skills/**\".\n#     (Set .gitignore aside across pack+publish, then restore.)\n#  3. ⚠ Keep the tarball LEAN — NO release zip, NO src/ tree, NO hero PNGs (it was 16 MB\n#     before guards). Skills + docs only.\nadom-wiki pkg pack                       # then: tar tzf <tgz> → every USER SKILL.md present,\n                                         #   ZERO binaries, ZERO dev/publish SKILL.md\nadom-wiki pkg publish --org adom         # publishes adom/adom-desktop-<bridge>-bridge\n```\n\nA container gets it **without anyone asking**:\n- `bridge_install` returns `skillPkg` + `installSkill` (AD derives `adom/<slug>` from your `docs`) and the `_hint` tells the AI to run it.\n- **`adom-desktop sync_skills`** installs every connected desktop's bridge skillPkgs (the CLI also runs it once/day in the background), so a container that connects a week later self-heals your skill.\n- The wiki page's **Skills tab** lists the skills your pkg ships, so the page itself advertises them.\n\n**Install to BOTH agent homes.** A container may run Claude OR Codex — your pkg's `install.sh` must deploy the user skill to **`~/.claude/skills/`** AND **`~/.codex/skills/`** (Codex is a real second skills home now). `adom-wiki pkg update` keeps both fresh.\n\n---\n\n## Artifact 3 — DISCOVERABILITY: `discovery_triggers` so a plain request finds you\n\nThe wiki has an **auto-discover** index, and it is the difference between a usable bridge and an **invisible** one. Declare `discovery_triggers` (the phrases a user actually says) on your page, and a plain request matches them via `adom-wiki discover find` / the in-app discovery an AI runs.\n\n```bash\nadom-wiki discover triggers adom/adom-desktop-<bridge>-bridge --add \"open my pcb\" \"export gerbers\"  # add triggers\nadom-wiki discover preview --json \"open my pcb design\"          # what would surface + the SCORE + WHY\nadom-wiki discover find    \"screenshot my website\"              # the live find an AI runs\n```\n\n**How scoring actually works (measured 2026-06-30 — know this or you'll guess wrong):**\n- `score = 3×(distinct triggers matched) + 1×(tags) + 1×(brief/text)`. **Trigger matches dominate (3× each);** tags/title/brief barely move it.\n- **NO TRIGGERS = INVISIBLE.** A bridge with a perfect title (\"Adom Desktop - Blender Bridge\") + a matching tag scored **ZERO** for the literal query *\"blender\"* — it never appeared in `discover preview`. The scorer ignores title/tags when there are no triggers. **Every shipped bridge MUST carry `discovery_triggers` or it cannot be found by the discovery an AI uses.** (`discover search` full-texts the readme, but that is NOT the path discovery takes.)\n- **More good triggers = higher rank.** Each match is +3, so 10 user-task phrasings beat 2. The healthy bundled bridges carry **30-40** triggers; a struggling one had 14 — all dev jargon — and lost EVERY user query.\n\n**The cardinal rule: triggers are USER-TASK phrasings, NOT dev jargon.** A real user types *\"open my pcb design\"* / *\"export gerber files\"* / *\"design a circuit board\"* — NOT *\"reverse bridge\"* / *\"kicad_bridge_call\"* / *\"fork the kicad bridge\"*. A trigger list full of architecture terms scores 0 on every real query (kicad's actual failure: 7/7 user queries missed, and the **Fusion** bridge out-ranked KiCad on PCB queries because Fusion happened to carry a couple of fuzzy \"pcb\"/\"export\" triggers). Put **10+ everyday task phrasings first**; technical aliases (verb names) are a small minority at the end.\n\n**VET it — never ship triggers you didn't test:**\n1. Write **5-7 queries a typical user would type** to get your tool (these double as your page `sample_prompts`).\n2. `adom-wiki discover preview --json \"<each query>\"` → read `data.scoring[]` (array order = rank). **You must land in the top 3.** (Rate-limited: space calls ~2 s; expect a 429 after ~25.)\n3. For every query where you're NOT top-3, **add that exact phrasing as a trigger** and re-test. If a sibling bridge out-ranks you on a shared word (Fusion vs KiCad on \"pcb\"), add MORE exact phrasings so your 3×-per-trigger total wins.\n\n**Claim YOUR niche — don't poach a sibling's territory.** Discovery routes by whose triggers match; if two bridges match a query the user lands on whichever has *more* triggers, NOT whichever is actually right. So your triggers must describe what YOU are genuinely best at — and you must LEAVE OUT cases a sibling owns. **Canonical example: logging into a site / filling a form on a signed-in page is the native-browser extension's job — it drives the user's REAL browser with their REAL logins — NOT puppeteer's.** Pup is a fresh Chrome-for-Testing profile with no saved logins; it's for *non-login* automation (scraping, screenshots, testing, public pages). So pup must NOT carry `log in` / `fill a form on a site I'm signed into` triggers, and native-browser MUST. When a task needs a capability you DON'T have, make your `discovery_pitch` *point at the bridge that does* — and tell the user to install it (e.g. \"for logged-in form-filling, install the native-browser extension\") — rather than claim it and do it badly.\n\n- **`discovery_pitch`** is the one-liner the snippet quotes back to the AI — auto-generated from `discovery_triggers` + `discovery_pitch`, **no separate upload step.** Make it an INSTRUCTION, not a tagline.\n- **Make your `brief` / `discovery_pitch` an instruction** — it's what the AI reads from the find result. Tell it the next moves: *\"…check `adom-desktop bridge_list` to confirm the bridge is installed; if it is, drive its `<prefix>_*` verbs. If the container lacks the skill, `adom-wiki pkg install adom/adom-desktop-<bridge>-bridge` (or `adom-desktop sync_skills`).\"*\n\n### The end-to-end flow your three artifacts enable\n\nUser says *\"show me my app in pup\"* → the AI:\n1. `adom-wiki discover find \"show me my app in pup\"` → your bridge surfaces (Artifact 3).\n2. `adom-desktop bridge_list` → is your bridge installed in AD? If not, `bridge_install {your manifestUrl}` (Artifact 1).\n3. Is your skill pkg installed in THIS container (`~/.claude/skills/` or `~/.codex/skills/`)? If not, `adom-wiki pkg install adom/<slug>` / `sync_skills` (Artifact 2).\n4. Drive your verbs — guided by the **rich hints you return** (the principle at the top).\n\n---\n\n## Prewarm — make the first call instant\n\nA cold first call that pays multi-minute setup (npm install, downloading a runtime, a heavy import) blows the caller's timeout. Pay that cost in the background BEFORE the user asks:\n\n- **Bundle deps in your SEED.** Pup ships a bundled `node_modules/` in its NSIS seed so a fresh PC can spawn with no `npm install`. When you re-seed from a Release zip, keep the bundled deps.\n- **Idempotent warm-up.** Make spawning + a no-op health/setup pass idempotent so a background prewarm is a no-op once warm.\n- **AD prefetches for you on first launch — standalone AND embedded (HD-bundled) — gated on availability.** (Pup's Node + Chrome-for-Testing prefetch runs once per install in both modes as of AD v1.9.45.) If your bridge needs a heavy runtime, expose a cheap idempotent \"ensure ready\" path AD/your seed can trigger, and surface a clean `errorCode` (e.g. `node_not_found`) when a prereq is missing so the AI can install it (`desktop_install_node`) instead of hanging.\n- **`browser_readiness`-style verb.** Offer a fast verb that reports `{ready:true|false, …}` so a caller (or a fresh-PC test) can confirm warmth without triggering work.\n\n---\n\n## Card hero + update cost — two gotchas that bite\n\n**Hero: ship a 1.6-aspect image, or AD crops the OG-card fallback.** AD's bridge card renders the hero in a banner that is `aspect-ratio: 1.6` with `object-fit: cover`. So:\n- Ship a **1.6-aspect** hero (≈**2000×1250**), full-bleed — like pup & kicad. It fills the banner cleanly.\n- If you ship **no** hero (`bridge.json` has no `hero` AND your wiki page has no `hero_path`), AD falls back to your page's **auto-generated OG card** (1200×630 = **1.90** aspect, the social-card size). `cover` scales that wider image to the banner's height and **clips the sides** — the left text panel gets cut off. (That cropped, off-center look is the tell of a hero-less bridge — it's not an AD bug.)\n- Fix: publish a real hero at 1.6 (the `adom-wiki` page hero, or a `hero` URL in `bridge.json`). Verify by opening your card in AD — the title/text must not be clipped.\n\n**Update cost: keep the Release zip source-only; re-seed the NSIS bundle in lockstep.** When a user clicks the card's \"↑ v…\" update badge, AD downloads your Release zip into the cache and (for a node bridge) provisions deps:\n- **The Release zip MUST be source-only — no `node_modules`.** AD provisions node deps at spawn: it **reuses the bundled seed's `node_modules` via `NODE_PATH`** when the cache version ≤ the seed version (instant), and runs a real **`npm install`** only when the wiki version is *newer* than the bundled seed.\n- So a wiki version much newer than your bundled seed forces a **cold `npm install`** on the next spawn — slow for a heavy tree. **When you publish a new Release, bump the bundled NSIS seed in lockstep** (same version) so most users get the instant NODE_PATH reuse, not a cold install.\n- AD (**v1.9.52+**) never blocks the UI on this: the update downloads straight to cache (no wasteful seed re-copy — the old code deep-copied pup's 73 MB / 4698-file seed and then discarded it, the \"updating… forever\" hang), the badge shows live progress (`downloading…` → done) with a 30s \"installing…\" fallback, and your bridge **keeps serving the old version** until the new one is ready. A lean zip + a lockstep seed is what keeps it *fast*.\n\n---\n\n## Cold-start tiers, host-app detection & the `bridge_readiness` probe (v1.9.47)\n\nA bridge has TWO kinds of dependency, and **AD treats them OPPOSITELY.** Get this right or a first-install probe (e.g. HD's \"what tools do I have\") triggers a slow install at the worst moment.\n\n**Tier 1 — your RUNTIME (Node, Python, your own browser download): AD prewarms + auto-installs it.** This is your plumbing. On first boot AD **provisions** Node/Python in the background — a **portable, no-UAC** copy under `~/.adom/adom-runtimes/` (or a system install if one's on PATH), **NOT winget/MSI** (see **Runtime contract** above) — and prewarms heavy assets you declare. You may also download your own runtime asset (pup downloads Chrome-for-Testing) — fine, it's yours. **But do it in the BACKGROUND: a `*_status` / `*_readiness` / ANY probe verb must NEVER block on a heavy download.** Spawn your server fast (it usually doesn't need the asset to *start*), kick the download off detached, and report `state:\"warming\"` until ready. A synchronous-download-on-first-verb is the exact footgun this release fixed (pup's 150 MB Chrome fetch used to block the probe).\n\n**Tier 2 — the HOST APP (KiCad, Fusion, Altium, …): AD DETECTS it by default and installs it ON REQUEST — never pre-emptively, and NEVER by telling the user to do it themselves.** Two rules that sound opposed but aren't:\n- **Don't install it unasked.** A multi-GB app nobody asked for is rude, so by default AD only DETECTS presence (a machine with KiCad belongs to a KiCad user; EDA users are mono-tool — a Fusion user isn't a KiCad user). AD won't prewarm/spawn your bridge while the app is absent — a resource decision, not a \"go away.\"\n- **But the cardinal Adom rule overrides everything: the AI does EVERYTHING for the user — it must NEVER tell the user to do something the AI can do itself.** So your bridge MUST know how to install the host app (`desktop_install_kicad` / winget / your own install verb) and just DO it the instant the user wants it. **Your \"not installed\" hint must OFFER to install it** (\"KiCad isn't installed — want me to install it for you?\"), then do it on a yes — it must NEVER say *\"download it from kicad.org.\"* Pointing the user at a manual download is the breach.\n\n**Bridge install LEVELS — how eagerly each bridge gets onto the machine. The AI ALWAYS does the install; the level only sets the TIMING/CONSENT:**\n\n| Level | Bridge(s) | Behavior |\n|---|---|---|\n| **0 · Built-in / MUST** | Hydrogen Desktop (`hd`) | The platform itself — the editor AD lives in. Always present; not \"installed\" by AD. |\n| **1 · Fundamental — pre-emptive** | Puppeteer (`pup`) | Browser automation is foundational to Adom. AD pre-installs its RUNTIME (Node + Chrome-for-Testing) ASAP on first boot, no asking. |\n| **2 · Recommended — proactive offer** | native-browser (the extension) | The user's REAL logged-in browser (login/forms). AD proactively SUGGESTS it and installs on a yes (it needs a browser-extension step the user confirms). |\n| **3 · On-request host app** | KiCad, Fusion, Altium, … | Detect by default; NEVER pre-install (huge app, rude unasked). The moment the user wants it, the AI installs it AUTOMATICALLY (winget / `desktop_install_*`) — NEVER tells the user to do it manually. |\n| **4 · Explicit third-party** | any `bridge_install`'d bridge | The user opts in by installing it. |\n\nThe through-line: **the AI never makes the user do something it can do itself.** The only knob is *when* — pre-emptive (pup), proactively-offered (native-browser), or on-request (host apps).\n\n**Declare both in `bridge.json`:**\n```json\n\"detect\": {                          // Tier 2 — how AD finds your HOST APP (detection only)\n  \"hostApp\": \"KiCad\",\n  \"appPathsExe\": \"kicad.exe\",        // optional: Windows App Paths registry lookup (most reliable)\n  \"paths\": {                         // optional: candidate paths, %VAR% expanded, one * = a version dir\n    \"windows\": [\"%ProgramFiles%\\\\KiCad\\\\*\\\\bin\\\\kicad.exe\"],\n    \"macos\":   [\"/Applications/KiCad/KiCad.app\"],\n    \"linux\":   [\"/usr/bin/kicad\"]\n  }\n},\n\"prewarm\": { \"assets\": [\"chrome-for-testing\"] }   // Tier 1 — heavy runtime assets AD prewarms + reports\n```\nA bridge that brings its OWN runtime (pup ships its own browser) **omits `detect`** — it has no external host app. AD infers the runtime from `spawn.kind` (node→Node, python→Python).\n\n**The read-only probe: `bridge_readiness`.** AD aggregates all of this into ONE side-effect-free verb the setup AI calls — it **never spawns a bridge, never installs/downloads**. Per bridge: `{hostApp, hostAppInstalled, runtime, runtimeReady, assets, state}` where `state` ∈ **`ready`** (use it) | **`warming`** (runtime ok, a heavy asset still downloading in the background — re-poll, don't trigger it) | **`needs-runtime`** (node/python installing) | **`no-app`** (host app not installed → not relevant here). Plus a top-level `edaToolsInstalled` list — the one-line answer to \"what EDA tools do I have.\" **Tell your users (in your skill): for a \"what do I have / is it ready\" question call `bridge_readiness`, NOT your `*_status` verb** — a `*_status` may spawn your bridge; readiness never does.\n\nYour two own cold-start verbs (above) still apply: `<prefix>_readiness` (read-only `{ready, installing, installProgressPct, lastError}`) + `<prefix>_prewarm` (`{wait}`-able). Those drive YOUR bridge specifically; `bridge_readiness` is the AD-level aggregate that detects + summarizes across all bridges without spawning any.\n\n---\n\n## The SKILL SET you should ship (not one SKILL.md — a small library)\n\nDon't cram everything into one file. Ship **three kinds** of skill, each with a different reader:\n\n1. **A PUBLISH skill** (for the bridge's OWN cloud/dev thread): how to cut the Release zip, POST the manifest, bump versions, publish the skills pkg (with the `.gitignore` trap), set `discovery_triggers`, and re-seed AD's bundled copy. This keeps *your* future self from getting the publish dance wrong.\n2. **A DEV skill** (for whoever edits the bridge): architecture, the verb contract, how to test locally, the boundary with AD — and, front and center, **the rich-hints-in-output rule**: *the AI never reads this file; it reads your verb OUTPUT, so every verb must return `_hint`/`_next`/`related`/`pitfalls`.* Make that the loudest thing in the dev skill.\n3. **USER skill(s)** (shipped in your pkg → the container): how a cloud AI drives your verbs to get real work done. **If the verb surface is large, split it:** a small **core USER skill** (the 80% workflow + a pointer to the sub-skills) plus **sub-skills** by area (e.g. `…-recording`, `…-electronics`, `…-aps-search`). A 2,000-line monolith won't get read; a tight core skill that names the sub-skills will. (Pup ships exactly this shape: a core bridge skill + sub-skills.)\n\nPublish the PUBLISH + DEV skills on your page for your thread; ship the USER skills in your **pkg** so containers get them. **Critical naming rule:** only USER skills may be named `SKILL.md` — the wiki indexes every `SKILL.md` in your tarball as an installable user skill, so name your DEV/PUBLISH docs `dev-skills/*.md` (source-only) or every user installs them (Artifact 2, trap #1).\n\n**Start from the templates — don't write these from scratch.** `skills/bridge-templates/` in the AD repo has a fill-in-the-blanks file for each of the three skill kinds, generalized from pup's battle-tested set: **`bridge-dev-template.md`** (architecture + the rich-hints rule), **`bridge-publish-template.md`** (the whole release/pkg/discovery dance + every trap), **`bridge-user-skill-template.md`** (the consumer skill, already encoding all eight conventions below). Copy the one you need, swap `<bridge>`/`<prefix>` for your names, delete what doesn't apply.\n\n---\n\n## USER-skill conventions — what every consumer skill must teach (from pup's shape)\n\nThese eight patterns are what make a cloud AI actually succeed with your bridge on the first try. The user-skill template encodes them; bake them into whatever you ship:\n\n1. **Open with a \"FIRST-TIME / COLD-START — read before you panic\" section.** A fresh PC may lack your runtime or owe a one-time download, and the AI's default failure mode is to declare defeat. Lead with a **response → meaning → what-you-do** table that frames `*_installing` / `not_ready` as EXPECTED, keyed off `errorCode` (not prose).\n2. **Standardize two cold-start verbs: `<prefix>_readiness` and `<prefix>_prewarm`.** `readiness` → `{ready, installing, installProgressPct, lastError}`; `prewarm` kicks off setup without real work and supports `{\"wait\":true}` to block. Gives every bridge a uniform \"poll until ready, then proceed\" loop. (Complements the runtime-side Prewarm section above.)\n3. **End the `description:` frontmatter with a `Trigger words:` line** mixing verb names AND plain-English phrases a user would actually type (\"take a screenshot of\", \"log into\"). This is what makes a user-invocable skill activate — and it feeds your `discovery_triggers` (Artifact 3).\n4. **Include a short \"Core verbs\" table** (Command | Description | Key args) for the 80% path, and point at `<prefix>_describe '{}'` for the full machine-readable catalog. The AI reads the table to act fast; `describe` is the completeness escape hatch — don't bloat the skill with every verb.\n5. **\"Background by default — don't disrupt the user.\"** Your bridge drives the AI's workspace, not the user's screen: lower/un-focus after automation, never force-foreground except to SHOW the user something, nudge via a taskbar flash (`*_alert_window`) instead of stealing focus. Standardize `*_lower_os_window` / `*_raise_os_window` / `*_alert_window`.\n6. **Teach \"`ok:true` is not enough\" — verify what you did.** Verbs that return on operation *start* (not completion) need a cheap post-check (an eval/read verb, else a screenshot) before reporting success — 404s, login walls, and empty states land silently.\n7. **Sessions: reuse-don't-reopen + stable identifiers.** Tell the AI to `*_status` before opening a new session (especially after compaction), reuse a match, and pick a stable `profile` so logins/state persist across runs. Prevents duplicate windows and the lost-login footgun.\n8. **\"Surface `_hint` verbatim.\"** Every verb returns an actionable `_hint` on not-ready/failure; instruct the AI to relay it to the user unchanged, and branch on the stable `errorCode` (not the prose). This closes the loop with THE ONE PRINCIPLE at the top — the hint IS your UI.\n\n---\n\n## Verb contract (quick reference)\n\n- **Naming:** every verb is `<name>_<verb>` (`blender_render`, `blender_status`). AD routes any verb with your prefix to you.\n- **Request:** AD POSTs `{ \"command\":\"<verb>\", \"args\":{…} }` to your port. **Response:** JSON with at least `success` (bool); human/AI output in `output`, errors in `error` (+ an `errorCode` for machine-actionable failures). AD relays everything else verbatim — so ALWAYS add `_hint`/`_next`/`related`/`pitfalls`.\n- **Long-running verbs:** return promptly with a job id + set `statusVerb`; the caller polls instead of blocking. On a timeout AD auto-returns `{stillRunning:true, statusVerb, timeoutSeconds}`.\n- **`<prefix>describe`:** return your full verb catalog (`{verbs:[{name,summary,input,output,timeoutSeconds,statusVerb,longRunning,example,hint,related,pitfalls}]}`). AD caches it (`~/.adom/bridge-verbs/<name>.json`) and shows it in the GUI Verbs tab — but remember, `describe` is read rarely; the per-call hints are what carry the AI.\n\n## Health + status chip\n\nYour `/health` (200) makes the chip green. Optionally return `led` (`green`/`yellow`/`red`), `summary` (short label), `tooltip` (full hover). AD owns the **offline** (gray) state when your endpoint is unreachable — you can never assert offline.\n\n## Lifecycle verbs (AD gives these to USERS — know them to guide users)\n\n`bridge_install {manifestUrl}` · `bridge_list` · `bridge_info`/`bridge_detail {name}` · `bridge_log_read {name, sinceOffset?}` · `bridge_kill {name}` · `bridge_pause {name}` · `refresh_bridges {name?}` · `bridge_check_updates`. A bridge installed via `bridge_install` auto-updates with no further opt-in; `updateManifestUrl` in `bridge.json` makes it durable across republish.\n\n---\n\n## Publish checklist (the whole modern flow)\n\n1. **Page** — `adom-wiki page create adom/adom-desktop-<bridge>-bridge` with a hero + an **instructional `brief`** + **`discovery_triggers`**.\n2. **Runtime** — `release create` + `release upload` the versioned `.zip`; POST the manifest to `/files`; set `updateManifestUrl` in BOTH the zip's `bridge.json` and the manifest.\n3. **Skills pkg** — stage ROOT SKILL.md + `skills/<slug>/SKILL.md` (core USER skill + sub-skills) + `discovery_triggers`; `pkg pack` (inspect!) → `pkg publish --org adom` (mind the `.gitignore` trap).\n4. **Discovery** — `discover triggers` to confirm; `discover preview \"<a real user phrase>\"` to confirm you surface.\n5. **Re-seed AD's bundled copy** (AD-core thread, not you) — extract your Release zip over `plugins/<name>/` keeping bundled deps; ships in the next NSIS as the offline fallback.\n6. **Verify** — `bridge_install {manifestUrl}` on a desktop → `bridge_list` shows your `version` + `skillPkg`; `bridge_check_updates` reaches your page; a container `sync_skills` pulls your USER skill; `discover find \"<phrase>\"` surfaces you with an instructional brief.\n\n**Reference bridge = pup** (`adom/adom-desktop-puppeteer-bridge`): live `discovery_triggers`, a skills pkg with a core + sub-skills, a Release zip + manifest with `updateManifestUrl`, a bundled seed with prewarmed `node_modules`, and rich per-verb hints. Copy its shape. (`scripts/sample-bridges/hello-python` + `hello-rust` are the minimal skeletons.) KiCad, Fusion, Blender, Puppeteer, and the **native-browser** bridge (in the `adom-browser-extension` repo) are cloud-owned — don't edit them from the AD repo. When an AD release changes a bridge-facing contract, AD posts a migration notice to **each** of their wiki discussions (`adom-desktop-{kicad,fusion,blender,puppeteer}-bridge` + `adom-browser-extension`).\n",
  "author": {
    "name": "Kyle Bergstedt",
    "email": "[email protected]"
  },
  "visibility": {
    "public": true
  },
  "hero": {
    "type": "image",
    "path": "hero-v2.png"
  },
  "metadata": {},
  "created_at": "2026-05-28T05:28:40.558Z",
  "updated_at": "2026-06-26T16:11:19.648Z",
  "skills": [],
  "author_name": "John Lauer",
  "changelog": "Set descriptive title to the Adom Desktop - <name> naming convention for app-index consistency",
  "content": "---\nname: adom-desktop-bridge-sdk\ndescription: Author, PUBLISH, and make-discoverable an adom-desktop bridge — a small local server that adds a namespace of verbs (kicad_, fusion_, browser_, your_prefix_) to adom-desktop. Use when building, publishing, updating, prewarming, or debugging a bridge, or deciding what skills to ship. Covers the AD-core-vs-author boundary; the #1 rule (rich hints in EVERY verb's OUTPUT, because the AI reads CLI output, never your on-disk skill); bridge.json; the THREE things you publish (runtime Release zip + manifest, container skills pkg, wiki discovery_triggers); prewarm; the publish/dev/user SKILL set you must ship; and the auto-discover → install-check → use flow.\n---\n\n# Bridge SDK — build, publish, and surface a bridge for Adom Desktop\n\nA **bridge** is a small local program (Python, Node, Rust, anything) that AD 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:\n\n1. **Runtime** — a versioned **Release `.zip`** + a **manifest** on your wiki page. AD's `bridge_install` streams it onto the user's machine and spawns it.\n2. **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.\n3. **Discoverability** — **`discovery_triggers`** on your wiki page so a user saying *\"open my app in pup\"* surfaces YOU via `adom-wiki discover find`.\n\n> **THE ONE PRINCIPLE THAT MATTERS MOST — read this before anything else.**\n> **The AI almost never reads your on-disk SKILL.md. It ALWAYS reads the OUTPUT of a CLI call.**\n> 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. AD 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.)\n\n---\n\n## The boundary — what AD owns vs what YOU own\n\nThe #1 mistake is filing AD bugs for bridge issues. The split:\n\n**AD core owns** (don't reimplement): the CLI/relay/`/command` passthrough (AD 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 (AD authors those — you never stamp them).\n\n**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**.\n\nIf a *bridge verb* misbehaves → fix the bridge. If a *generic AD capability* is missing (a new lifecycle verb, a relay field) → file it against AD, but don't re-absorb the bridge into AD.\n\n---\n\n## Runtime contract — AD provisions Node/Python; you bind loopback (v1.9.63 / v1.9.69)\n\n**The single most important change for a Node or Python bridge.** As of AD **v1.9.63** you no longer bootstrap your own interpreter, and you MUST bind loopback. Four rules:\n\n1. **Don't bootstrap your own Node/Python.** AD owns a global, portable, **no-UAC** runtime under `~/.adom/adom-runtimes/`: a system install if one's on PATH, else a portable copy it downloads + prewarms in the background on first launch. AD spawns your `entrypoint` with that interpreter **by absolute path**. So **delete every `winget install node` / `desktop_install_node` / \"please install Python\" / self-download step** from your bridge and its skills. `spawn.kind: \"node\"|\"python\"` IS your runtime declaration — AD reads it and provisions the runtime. Anyone can check status with the **`runtimes`** verb (`state ∈ absent|installing|ready|failed`, `source ∈ system|cache`, version). AD pins the version (Node 22.11.0 / Python 3.12.13 today) — need a different major? ask the AD-core thread; don't ship your own.\n\n2. **Bind `ADOM_BIND_HOST`, never `0.0.0.0`.** AD passes an **`ADOM_BIND_HOST`** env var (always `127.0.0.1`) to every bridge it spawns. A bare listen that defaults to `0.0.0.0` pops a **Windows Firewall \"allow access?\" dialog** on first run — and AD's hard guarantee to users is **no firewall prompt by default**.\n   - Node: `server.listen(PORT, process.env.ADOM_BIND_HOST || '127.0.0.1', cb)`\n   - Python: `HTTPServer((os.environ.get('ADOM_BIND_HOST', '127.0.0.1'), PORT), Handler)`\n\n3. **Ship the Release zip source-only — AD installs your deps, and v1.9.69 makes it bulletproof.** A Node bridge's zip carries SOURCE only (no `node_modules`). AD reuses the bundled seed's `node_modules` via `NODE_PATH` when versions match, else runs **`npm install --include=optional`** for you with the managed runtime's own npm. **As of v1.9.69 AD puts the managed runtime's dir on the child PATH for that install AND for your spawned process** — so a dep's postinstall / `node-gyp` / `prebuild-install` that shells out to a **bare** `node`/`npm`/`npx` (puppeteer's own `node install.mjs`, `sharp`, `keytar`, `better-sqlite3`, `bcrypt`) resolves even on a machine with **no system Node**. **You do NOT add the runtime to PATH yourself** — AD guarantees it. (Python: the interpreter dir + `Scripts/` are on PATH too, so pip can build wheels.)\n\n4. **Bundle deps in your SEED for an instant first spawn.** Source-only zip + a bundled-seed `node_modules` at the same version = AD reuses the seed via `NODE_PATH` (instant); a wiki version newer than the seed forces one cold `npm install` (now PATH-correct, but slower). Bump the seed in lockstep with each Release.\n\n**Net:** declare `spawn.kind`, bind `ADOM_BIND_HOST`, ship source-only. AD does the rest — no UAC, no firewall prompt, no `'node' is not recognized`.\n\n---\n\n## Self-audit checklist — run this when asked to \"audit against the SDK\"\n\nWhen a maintainer says *\"audit your code, skills, README, and wiki page against the Bridge SDK,\"* **this is the checklist.** Each item names its section below; fix every miss, then re-check.\n\n**bridge.json** (the manifest in your zip):\n- [ ] `spawn.kind` ∈ `python|node|exe|external-http`, with **`entrypoint`** (NOT `process`/`cmd`/`args`); `healthEndpoint` is **inside `spawn`**; `port:0` lets AD pick. *(→ bridge.json)*\n- [ ] `updateManifestUrl` → YOUR page's manifest (durable auto-update); `docs` → your own page; **`homepage` is a `wiki.adom.inc` URL, NEVER `github.com`.**\n- [ ] `detect` declared iff you drive a HOST APP (KiCad/Fusion/Altium…); omitted if you bring your own runtime (pup). `prewarm.assets` declared for heavy downloads (e.g. `chrome-for-testing`). *(→ Cold-start tiers)*\n\n**Runtime** (Node/Python bridges) *(→ Runtime contract)*:\n- [ ] You bind **`ADOM_BIND_HOST`** (loopback default), never `0.0.0.0`.\n- [ ] You do **NOT** bootstrap your own Node/Python — no `winget`/`desktop_install_node`/self-download in code or skills. `spawn.kind` is the declaration; AD provisions it.\n- [ ] Release zip is **source-only** (no `node_modules`); you rely on AD's `npm install` (PATH-correct as of v1.9.69) + a lockstep bundled seed for instant spawn. You do NOT put the runtime on PATH yourself.\n\n**Verbs & output** (what the AI actually reads):\n- [ ] EVERY verb returns a rich **`_hint`** (+ `_next`/`related`/`pitfalls`). *(→ THE ONE PRINCIPLE)*\n- [ ] A `<prefix>_describe` verb lists your whole catalog. *(→ Verb contract)*\n- [ ] No verb blocks on a heavy install/download — background it, report `installing`/`warming`; `<prefix>_readiness` is read-only. *(→ Cold-start tiers)*\n- [ ] `/health` returns `led`/`summary`/`tooltip` truthfully. *(→ Health + status chip)*\n\n**Discovery** (can a plain request find you?):\n- [ ] **10+ user-task `discovery_triggers`** (everyday phrasings, NOT dev jargon). **No triggers = invisible.** *(→ Artifact 3)*\n- [ ] You ran `discover preview --json` on 5-7 real user queries and land **top-3** for each.\n- [ ] Triggers stay in YOUR niche — you don't poach a sibling's (login/forms → native-browser, not pup). `discovery_pitch` is an INSTRUCTION.\n\n**Skills & docs** (what the container + humans read):\n- [ ] You ship the publish/dev/user **3-skill set**; only the USER skill is named `SKILL.md` (dev/publish are `dev-skills/*.md`, source-only). *(→ The SKILL SET)*\n- [ ] Your USER skill leads with the cold-start \"read before you panic\" section + the 8 conventions, and points the AI at `bridge_readiness`. *(→ USER-skill conventions)*\n- [ ] README + wiki page + skills all AGREE with the above (no stale github links, no wrong schema, triggers vetted).\n\n---\n\n## bridge.json — the manifest that ships INSIDE your zip\n\n```jsonc\n{\n  \"manifest_version\": 1,\n  \"name\": \"blender\",                        // registry name → verb prefix \"blender_\"\n  \"displayName\": \"Blender\",                 // in-app chip label (the tool's real name)\n  \"version\": \"1.2.0\",                       // numeric; a newer CACHE copy supersedes the bundled seed\n  \"description\": \"Drive Blender headless + GUI\",\n  \"author\": \"you\",\n  \"license\": \"MIT\",\n  \"docs\": \"https://wiki.adom.inc/adom/adom-desktop-blender-bridge\",  // your OWN page (drives skillPkg + hero)\n  \"hero\": \"https://.../hero.png\",           // optional; else AD uses your wiki page hero, else a monogram. Ship it at 1.6 aspect (≈2000×1250) — see \"Card hero + update cost\"\n  \"spawn\": {\n    \"kind\": \"python\",                       // python | node | exe | external-http  (NOT \"process\")\n    \"entrypoint\": \"server.py\",              // the file/exe AD launches; AD AUTO-APPENDS `--port <port>` to argv\n    \"port\": 0,                              // 0 = AD picks a STABLE port (recommended). Bind 127.0.0.1:<that port>.\n    \"persistent\": true,                     // AD's supervisor respawns it if it dies\n    \"healthEndpoint\": \"/health\",            // INSIDE spawn. AD probes it for the chip (default /health)\n    \"stopMethod\": \"kill\",                   // kill | sigterm | graceful_endpoint\n    \"killImageName\": \"python.exe\"           // image name for taskkill on stop (kind:python/node)\n  },\n  \"verbPrefixes\": [\"blender_\"],             // any verb with these prefixes routes to you\n  \"verbs\": [\"blender_render\", \"blender_status\", \"blender_describe\"],  // every verb (routing + Verbs tab)\n  \"statusVerb\": \"blender_status\",           // verb a caller polls after a long op times out\n  \"updateManifestUrl\": \"https://wiki.adom.inc/api/v1/pages/adom-desktop-blender-bridge/files/blender-manifest.json\",\n\n  // v1.9.47 cold-start (see \"Cold-start tiers\" below):\n  \"detect\": {                               // Tier 2 — how AD DETECTS your HOST APP. AD NEVER installs it.\n    \"hostApp\": \"Blender\",                   // omit the whole `detect` block if your bridge brings its own runtime\n    \"appPathsExe\": \"blender.exe\",           //   (e.g. pup ships its own browser → no host app)\n    \"paths\": { \"windows\": [\"%ProgramFiles%\\\\Blender Foundation\\\\*\\\\blender.exe\"] }\n  },\n  \"prewarm\": { \"assets\": [] }               // Tier 1 — heavy runtime assets AD prewarms (e.g. [\"chrome-for-testing\"])\n}\n```\n\n- **`spawn.kind`** is `python` | `node` | `exe` | `external-http` — there is **no `\"process\"` kind**, and no `cmd`/`args`/`cwd`. AD launches `spawn.entrypoint` and **automatically appends `--port <port>`** to its argv; your server parses `--port`. (`external-http` = AD doesn't spawn, only probes a port you declare via a `discovery` block.)\n- **`spawn.port: 0`** → AD allocates a STABLE port (persisted across respawns + AD restarts) and passes it. Bind `127.0.0.1:<port>`. NEVER hardcode a port (Hyper-V/Docker silently reserve ranges — see PORTS.md).\n- **`spawn.healthEndpoint`** lives **inside `spawn`** (not at the top level), default `/health`.\n- **`updateManifestUrl`** — the full URL of YOUR manifest on YOUR page. **Declare it in `bridge.json`** so it survives a republish; AD polls it (on launch + every 4h) and auto-updates users. (AD also remembers the install URL in `~/.adom/bridge-install-urls.json` as a fallback, but the declared field is the durable, author-intended path.)\n- **`docs`** — point at YOUR dedicated wiki page. AD derives your **skillPkg** (`adom/<slug>`) and your card hero from this. Don't point it at a shared/structural page.\n- **`homepage`** — a **`wiki.adom.inc`** URL (your own page, or `https://wiki.adom.inc/adom/adom-desktop` for the platform). **NEVER `github.com/...`** — GitHub is source-backup only; the wiki is the canonical home. (A `homepage: github.com/adom-inc/adom-desktop` is a stale default to fix.)\n- **`detect` / `prewarm`** — the v1.9.47 cold-start declarations (full rules in \"Cold-start tiers\" below). `detect` = your HOST APP (AD detects, never installs); `prewarm` = your heavy RUNTIME assets (AD prewarms/auto-installs). A node bridge that ships its deps still works without `prewarm`; declare `prewarm.assets` only for big *downloaded* assets like a browser.\n\n---\n\n## Artifact 1 — the RUNTIME: publish your `.zip` as a RELEASE + a manifest\n\nThis is the code AD streams onto the user's machine. Two files on your page:\n\n1. **A versioned Release `.zip`** of your bridge dir (server + `bridge.json` at the zip ROOT + any assets). Ship it as a **Release** (the downloadable, version-pinned artifact), not a loose `/files` blob:\n   ```bash\n   adom-wiki release create adom/adom-desktop-<bridge>-bridge <version> --title \"...\" --changelog \"...\"\n   adom-wiki release upload adom/adom-desktop-<bridge>-bridge <version> your-bridge-v<version>.zip --platform any\n   ```\n   (Node bridges: the zip carries SOURCE only — keep `node_modules` out; AD/the seed handle deps. See Prewarm.)\n2. **The manifest JSON** (e.g. `blender-manifest.json`) — identity + where the zip is — POSTed to your page `/files`:\n   ```jsonc\n   { \"name\":\"blender\", \"version\":\"1.2.0\",\n     \"url\":\"https://wiki.adom.inc/download/adom/adom-desktop-blender-bridge/1.2.0/blender-v1.2.0.zip\",  // base-relative, extension-agnostic (.bin works where wiki blocks .zip)\n     \"sha256\":\"…\", \"size\":123456, \"verbPrefixes\":[\"blender_\"], \"healthEndpoint\":\"/health\",\n     \"updateManifestUrl\":\"https://wiki.adom.inc/api/v1/pages/adom-desktop-blender-bridge/files/blender-manifest.json\" }\n   ```\n\n**Users install/update** with AD's `bridge_install {manifestUrl}` → AD downloads the zip, sha256-verifies, unzips into `%LOCALAPPDATA%\\Adom Desktop\\bridges-cache\\<name>\\`, rescans, and remembers the URL for auto-update.\n\n**To ship a new version:** bump `version` in BOTH `bridge.json` (in the zip) and the manifest, re-`release upload` the zip, re-POST the manifest. Users auto-get it on the next 4h poll, or immediately via `refresh_bridges {name}`. **A bundled SEED** (a `plugins/<name>/` folder shipped in AD's NSIS) is the offline/first-run fallback; your published cache copy with a higher `version` supersedes it (cache-over-bundled, version-aware).\n\n---\n\n## Artifact 2 — the SKILLS pkg: container-side docs (`.claude/skills` / `.codex/skills`)\n\nYour runtime runs on the user's DESKTOP; the AI driving it runs in a cloud/WSL2 CONTAINER. That container needs your **consumer SKILL.md** locally to know your verbs. Ship it as its own **`adom-wiki pkg`** (skills only, NO binaries):\n\n```bash\n# Stage dir: a ROOT SKILL.md (required) + USER skills under skills/<slug>/SKILL.md, and\n# package.json {name:\"adom-desktop-<bridge>-bridge\", version, discovery_triggers, files:[...]}.\n# THREE traps that bite — verify each with `tar tzf` before publishing:\n#  1. ⚠ The wiki indexes EVERY file named SKILL.md in the tarball as an INSTALLABLE,\n#     Skills-tab-listed USER skill (it counts them as skill_count). So your DEV + PUBLISH\n#     skills must NOT be named SKILL.md — put them in dev-skills/*.md, source-only — else\n#     every user who installs your pkg also installs your dev/publish docs.\n#  2. ⚠ pkg pack/publish HONORS the stage dir's .gitignore AND the package.json `files`\n#     allowlist needs the GLOB: a bare \"skills\" entry ships ZERO skill files — use \"skills/**\".\n#     (Set .gitignore aside across pack+publish, then restore.)\n#  3. ⚠ Keep the tarball LEAN — NO release zip, NO src/ tree, NO hero PNGs (it was 16 MB\n#     before guards). Skills + docs only.\nadom-wiki pkg pack                       # then: tar tzf <tgz> → every USER SKILL.md present,\n                                         #   ZERO binaries, ZERO dev/publish SKILL.md\nadom-wiki pkg publish --org adom         # publishes adom/adom-desktop-<bridge>-bridge\n```\n\nA container gets it **without anyone asking**:\n- `bridge_install` returns `skillPkg` + `installSkill` (AD derives `adom/<slug>` from your `docs`) and the `_hint` tells the AI to run it.\n- **`adom-desktop sync_skills`** installs every connected desktop's bridge skillPkgs (the CLI also runs it once/day in the background), so a container that connects a week later self-heals your skill.\n- The wiki page's **Skills tab** lists the skills your pkg ships, so the page itself advertises them.\n\n**Install to BOTH agent homes.** A container may run Claude OR Codex — your pkg's `install.sh` must deploy the user skill to **`~/.claude/skills/`** AND **`~/.codex/skills/`** (Codex is a real second skills home now). `adom-wiki pkg update` keeps both fresh.\n\n---\n\n## Artifact 3 — DISCOVERABILITY: `discovery_triggers` so a plain request finds you\n\nThe wiki has an **auto-discover** index, and it is the difference between a usable bridge and an **invisible** one. Declare `discovery_triggers` (the phrases a user actually says) on your page, and a plain request matches them via `adom-wiki discover find` / the in-app discovery an AI runs.\n\n```bash\nadom-wiki discover triggers adom/adom-desktop-<bridge>-bridge --add \"open my pcb\" \"export gerbers\"  # add triggers\nadom-wiki discover preview --json \"open my pcb design\"          # what would surface + the SCORE + WHY\nadom-wiki discover find    \"screenshot my website\"              # the live find an AI runs\n```\n\n**How scoring actually works (measured 2026-06-30 — know this or you'll guess wrong):**\n- `score = 3×(distinct triggers matched) + 1×(tags) + 1×(brief/text)`. **Trigger matches dominate (3× each);** tags/title/brief barely move it.\n- **NO TRIGGERS = INVISIBLE.** A bridge with a perfect title (\"Adom Desktop - Blender Bridge\") + a matching tag scored **ZERO** for the literal query *\"blender\"* — it never appeared in `discover preview`. The scorer ignores title/tags when there are no triggers. **Every shipped bridge MUST carry `discovery_triggers` or it cannot be found by the discovery an AI uses.** (`discover search` full-texts the readme, but that is NOT the path discovery takes.)\n- **More good triggers = higher rank.** Each match is +3, so 10 user-task phrasings beat 2. The healthy bundled bridges carry **30-40** triggers; a struggling one had 14 — all dev jargon — and lost EVERY user query.\n\n**The cardinal rule: triggers are USER-TASK phrasings, NOT dev jargon.** A real user types *\"open my pcb design\"* / *\"export gerber files\"* / *\"design a circuit board\"* — NOT *\"reverse bridge\"* / *\"kicad_bridge_call\"* / *\"fork the kicad bridge\"*. A trigger list full of architecture terms scores 0 on every real query (kicad's actual failure: 7/7 user queries missed, and the **Fusion** bridge out-ranked KiCad on PCB queries because Fusion happened to carry a couple of fuzzy \"pcb\"/\"export\" triggers). Put **10+ everyday task phrasings first**; technical aliases (verb names) are a small minority at the end.\n\n**VET it — never ship triggers you didn't test:**\n1. Write **5-7 queries a typical user would type** to get your tool (these double as your page `sample_prompts`).\n2. `adom-wiki discover preview --json \"<each query>\"` → read `data.scoring[]` (array order = rank). **You must land in the top 3.** (Rate-limited: space calls ~2 s; expect a 429 after ~25.)\n3. For every query where you're NOT top-3, **add that exact phrasing as a trigger** and re-test. If a sibling bridge out-ranks you on a shared word (Fusion vs KiCad on \"pcb\"), add MORE exact phrasings so your 3×-per-trigger total wins.\n\n**Claim YOUR niche — don't poach a sibling's territory.** Discovery routes by whose triggers match; if two bridges match a query the user lands on whichever has *more* triggers, NOT whichever is actually right. So your triggers must describe what YOU are genuinely best at — and you must LEAVE OUT cases a sibling owns. **Canonical example: logging into a site / filling a form on a signed-in page is the native-browser extension's job — it drives the user's REAL browser with their REAL logins — NOT puppeteer's.** Pup is a fresh Chrome-for-Testing profile with no saved logins; it's for *non-login* automation (scraping, screenshots, testing, public pages). So pup must NOT carry `log in` / `fill a form on a site I'm signed into` triggers, and native-browser MUST. When a task needs a capability you DON'T have, make your `discovery_pitch` *point at the bridge that does* — and tell the user to install it (e.g. \"for logged-in form-filling, install the native-browser extension\") — rather than claim it and do it badly.\n\n- **`discovery_pitch`** is the one-liner the snippet quotes back to the AI — auto-generated from `discovery_triggers` + `discovery_pitch`, **no separate upload step.** Make it an INSTRUCTION, not a tagline.\n- **Make your `brief` / `discovery_pitch` an instruction** — it's what the AI reads from the find result. Tell it the next moves: *\"…check `adom-desktop bridge_list` to confirm the bridge is installed; if it is, drive its `<prefix>_*` verbs. If the container lacks the skill, `adom-wiki pkg install adom/adom-desktop-<bridge>-bridge` (or `adom-desktop sync_skills`).\"*\n\n### The end-to-end flow your three artifacts enable\n\nUser says *\"show me my app in pup\"* → the AI:\n1. `adom-wiki discover find \"show me my app in pup\"` → your bridge surfaces (Artifact 3).\n2. `adom-desktop bridge_list` → is your bridge installed in AD? If not, `bridge_install {your manifestUrl}` (Artifact 1).\n3. Is your skill pkg installed in THIS container (`~/.claude/skills/` or `~/.codex/skills/`)? If not, `adom-wiki pkg install adom/<slug>` / `sync_skills` (Artifact 2).\n4. Drive your verbs — guided by the **rich hints you return** (the principle at the top).\n\n---\n\n## Prewarm — make the first call instant\n\nA cold first call that pays multi-minute setup (npm install, downloading a runtime, a heavy import) blows the caller's timeout. Pay that cost in the background BEFORE the user asks:\n\n- **Bundle deps in your SEED.** Pup ships a bundled `node_modules/` in its NSIS seed so a fresh PC can spawn with no `npm install`. When you re-seed from a Release zip, keep the bundled deps.\n- **Idempotent warm-up.** Make spawning + a no-op health/setup pass idempotent so a background prewarm is a no-op once warm.\n- **AD prefetches for you on first launch — standalone AND embedded (HD-bundled) — gated on availability.** (Pup's Node + Chrome-for-Testing prefetch runs once per install in both modes as of AD v1.9.45.) If your bridge needs a heavy runtime, expose a cheap idempotent \"ensure ready\" path AD/your seed can trigger, and surface a clean `errorCode` (e.g. `node_not_found`) when a prereq is missing so the AI can install it (`desktop_install_node`) instead of hanging.\n- **`browser_readiness`-style verb.** Offer a fast verb that reports `{ready:true|false, …}` so a caller (or a fresh-PC test) can confirm warmth without triggering work.\n\n---\n\n## Card hero + update cost — two gotchas that bite\n\n**Hero: ship a 1.6-aspect image, or AD crops the OG-card fallback.** AD's bridge card renders the hero in a banner that is `aspect-ratio: 1.6` with `object-fit: cover`. So:\n- Ship a **1.6-aspect** hero (≈**2000×1250**), full-bleed — like pup & kicad. It fills the banner cleanly.\n- If you ship **no** hero (`bridge.json` has no `hero` AND your wiki page has no `hero_path`), AD falls back to your page's **auto-generated OG card** (1200×630 = **1.90** aspect, the social-card size). `cover` scales that wider image to the banner's height and **clips the sides** — the left text panel gets cut off. (That cropped, off-center look is the tell of a hero-less bridge — it's not an AD bug.)\n- Fix: publish a real hero at 1.6 (the `adom-wiki` page hero, or a `hero` URL in `bridge.json`). Verify by opening your card in AD — the title/text must not be clipped.\n\n**Update cost: keep the Release zip source-only; re-seed the NSIS bundle in lockstep.** When a user clicks the card's \"↑ v…\" update badge, AD downloads your Release zip into the cache and (for a node bridge) provisions deps:\n- **The Release zip MUST be source-only — no `node_modules`.** AD provisions node deps at spawn: it **reuses the bundled seed's `node_modules` via `NODE_PATH`** when the cache version ≤ the seed version (instant), and runs a real **`npm install`** only when the wiki version is *newer* than the bundled seed.\n- So a wiki version much newer than your bundled seed forces a **cold `npm install`** on the next spawn — slow for a heavy tree. **When you publish a new Release, bump the bundled NSIS seed in lockstep** (same version) so most users get the instant NODE_PATH reuse, not a cold install.\n- AD (**v1.9.52+**) never blocks the UI on this: the update downloads straight to cache (no wasteful seed re-copy — the old code deep-copied pup's 73 MB / 4698-file seed and then discarded it, the \"updating… forever\" hang), the badge shows live progress (`downloading…` → done) with a 30s \"installing…\" fallback, and your bridge **keeps serving the old version** until the new one is ready. A lean zip + a lockstep seed is what keeps it *fast*.\n\n---\n\n## Cold-start tiers, host-app detection & the `bridge_readiness` probe (v1.9.47)\n\nA bridge has TWO kinds of dependency, and **AD treats them OPPOSITELY.** Get this right or a first-install probe (e.g. HD's \"what tools do I have\") triggers a slow install at the worst moment.\n\n**Tier 1 — your RUNTIME (Node, Python, your own browser download): AD prewarms + auto-installs it.** This is your plumbing. On first boot AD **provisions** Node/Python in the background — a **portable, no-UAC** copy under `~/.adom/adom-runtimes/` (or a system install if one's on PATH), **NOT winget/MSI** (see **Runtime contract** above) — and prewarms heavy assets you declare. You may also download your own runtime asset (pup downloads Chrome-for-Testing) — fine, it's yours. **But do it in the BACKGROUND: a `*_status` / `*_readiness` / ANY probe verb must NEVER block on a heavy download.** Spawn your server fast (it usually doesn't need the asset to *start*), kick the download off detached, and report `state:\"warming\"` until ready. A synchronous-download-on-first-verb is the exact footgun this release fixed (pup's 150 MB Chrome fetch used to block the probe).\n\n**Tier 2 — the HOST APP (KiCad, Fusion, Altium, …): AD DETECTS it by default and installs it ON REQUEST — never pre-emptively, and NEVER by telling the user to do it themselves.** Two rules that sound opposed but aren't:\n- **Don't install it unasked.** A multi-GB app nobody asked for is rude, so by default AD only DETECTS presence (a machine with KiCad belongs to a KiCad user; EDA users are mono-tool — a Fusion user isn't a KiCad user). AD won't prewarm/spawn your bridge while the app is absent — a resource decision, not a \"go away.\"\n- **But the cardinal Adom rule overrides everything: the AI does EVERYTHING for the user — it must NEVER tell the user to do something the AI can do itself.** So your bridge MUST know how to install the host app (`desktop_install_kicad` / winget / your own install verb) and just DO it the instant the user wants it. **Your \"not installed\" hint must OFFER to install it** (\"KiCad isn't installed — want me to install it for you?\"), then do it on a yes — it must NEVER say *\"download it from kicad.org.\"* Pointing the user at a manual download is the breach.\n\n**Bridge install LEVELS — how eagerly each bridge gets onto the machine. The AI ALWAYS does the install; the level only sets the TIMING/CONSENT:**\n\n| Level | Bridge(s) | Behavior |\n|---|---|---|\n| **0 · Built-in / MUST** | Hydrogen Desktop (`hd`) | The platform itself — the editor AD lives in. Always present; not \"installed\" by AD. |\n| **1 · Fundamental — pre-emptive** | Puppeteer (`pup`) | Browser automation is foundational to Adom. AD pre-installs its RUNTIME (Node + Chrome-for-Testing) ASAP on first boot, no asking. |\n| **2 · Recommended — proactive offer** | native-browser (the extension) | The user's REAL logged-in browser (login/forms). AD proactively SUGGESTS it and installs on a yes (it needs a browser-extension step the user confirms). |\n| **3 · On-request host app** | KiCad, Fusion, Altium, … | Detect by default; NEVER pre-install (huge app, rude unasked). The moment the user wants it, the AI installs it AUTOMATICALLY (winget / `desktop_install_*`) — NEVER tells the user to do it manually. |\n| **4 · Explicit third-party** | any `bridge_install`'d bridge | The user opts in by installing it. |\n\nThe through-line: **the AI never makes the user do something it can do itself.** The only knob is *when* — pre-emptive (pup), proactively-offered (native-browser), or on-request (host apps).\n\n**Declare both in `bridge.json`:**\n```json\n\"detect\": {                          // Tier 2 — how AD finds your HOST APP (detection only)\n  \"hostApp\": \"KiCad\",\n  \"appPathsExe\": \"kicad.exe\",        // optional: Windows App Paths registry lookup (most reliable)\n  \"paths\": {                         // optional: candidate paths, %VAR% expanded, one * = a version dir\n    \"windows\": [\"%ProgramFiles%\\\\KiCad\\\\*\\\\bin\\\\kicad.exe\"],\n    \"macos\":   [\"/Applications/KiCad/KiCad.app\"],\n    \"linux\":   [\"/usr/bin/kicad\"]\n  }\n},\n\"prewarm\": { \"assets\": [\"chrome-for-testing\"] }   // Tier 1 — heavy runtime assets AD prewarms + reports\n```\nA bridge that brings its OWN runtime (pup ships its own browser) **omits `detect`** — it has no external host app. AD infers the runtime from `spawn.kind` (node→Node, python→Python).\n\n**The read-only probe: `bridge_readiness`.** AD aggregates all of this into ONE side-effect-free verb the setup AI calls — it **never spawns a bridge, never installs/downloads**. Per bridge: `{hostApp, hostAppInstalled, runtime, runtimeReady, assets, state}` where `state` ∈ **`ready`** (use it) | **`warming`** (runtime ok, a heavy asset still downloading in the background — re-poll, don't trigger it) | **`needs-runtime`** (node/python installing) | **`no-app`** (host app not installed → not relevant here). Plus a top-level `edaToolsInstalled` list — the one-line answer to \"what EDA tools do I have.\" **Tell your users (in your skill): for a \"what do I have / is it ready\" question call `bridge_readiness`, NOT your `*_status` verb** — a `*_status` may spawn your bridge; readiness never does.\n\nYour two own cold-start verbs (above) still apply: `<prefix>_readiness` (read-only `{ready, installing, installProgressPct, lastError}`) + `<prefix>_prewarm` (`{wait}`-able). Those drive YOUR bridge specifically; `bridge_readiness` is the AD-level aggregate that detects + summarizes across all bridges without spawning any.\n\n---\n\n## The SKILL SET you should ship (not one SKILL.md — a small library)\n\nDon't cram everything into one file. Ship **three kinds** of skill, each with a different reader:\n\n1. **A PUBLISH skill** (for the bridge's OWN cloud/dev thread): how to cut the Release zip, POST the manifest, bump versions, publish the skills pkg (with the `.gitignore` trap), set `discovery_triggers`, and re-seed AD's bundled copy. This keeps *your* future self from getting the publish dance wrong.\n2. **A DEV skill** (for whoever edits the bridge): architecture, the verb contract, how to test locally, the boundary with AD — and, front and center, **the rich-hints-in-output rule**: *the AI never reads this file; it reads your verb OUTPUT, so every verb must return `_hint`/`_next`/`related`/`pitfalls`.* Make that the loudest thing in the dev skill.\n3. **USER skill(s)** (shipped in your pkg → the container): how a cloud AI drives your verbs to get real work done. **If the verb surface is large, split it:** a small **core USER skill** (the 80% workflow + a pointer to the sub-skills) plus **sub-skills** by area (e.g. `…-recording`, `…-electronics`, `…-aps-search`). A 2,000-line monolith won't get read; a tight core skill that names the sub-skills will. (Pup ships exactly this shape: a core bridge skill + sub-skills.)\n\nPublish the PUBLISH + DEV skills on your page for your thread; ship the USER skills in your **pkg** so containers get them. **Critical naming rule:** only USER skills may be named `SKILL.md` — the wiki indexes every `SKILL.md` in your tarball as an installable user skill, so name your DEV/PUBLISH docs `dev-skills/*.md` (source-only) or every user installs them (Artifact 2, trap #1).\n\n**Start from the templates — don't write these from scratch.** `skills/bridge-templates/` in the AD repo has a fill-in-the-blanks file for each of the three skill kinds, generalized from pup's battle-tested set: **`bridge-dev-template.md`** (architecture + the rich-hints rule), **`bridge-publish-template.md`** (the whole release/pkg/discovery dance + every trap), **`bridge-user-skill-template.md`** (the consumer skill, already encoding all eight conventions below). Copy the one you need, swap `<bridge>`/`<prefix>` for your names, delete what doesn't apply.\n\n---\n\n## USER-skill conventions — what every consumer skill must teach (from pup's shape)\n\nThese eight patterns are what make a cloud AI actually succeed with your bridge on the first try. The user-skill template encodes them; bake them into whatever you ship:\n\n1. **Open with a \"FIRST-TIME / COLD-START — read before you panic\" section.** A fresh PC may lack your runtime or owe a one-time download, and the AI's default failure mode is to declare defeat. Lead with a **response → meaning → what-you-do** table that frames `*_installing` / `not_ready` as EXPECTED, keyed off `errorCode` (not prose).\n2. **Standardize two cold-start verbs: `<prefix>_readiness` and `<prefix>_prewarm`.** `readiness` → `{ready, installing, installProgressPct, lastError}`; `prewarm` kicks off setup without real work and supports `{\"wait\":true}` to block. Gives every bridge a uniform \"poll until ready, then proceed\" loop. (Complements the runtime-side Prewarm section above.)\n3. **End the `description:` frontmatter with a `Trigger words:` line** mixing verb names AND plain-English phrases a user would actually type (\"take a screenshot of\", \"log into\"). This is what makes a user-invocable skill activate — and it feeds your `discovery_triggers` (Artifact 3).\n4. **Include a short \"Core verbs\" table** (Command | Description | Key args) for the 80% path, and point at `<prefix>_describe '{}'` for the full machine-readable catalog. The AI reads the table to act fast; `describe` is the completeness escape hatch — don't bloat the skill with every verb.\n5. **\"Background by default — don't disrupt the user.\"** Your bridge drives the AI's workspace, not the user's screen: lower/un-focus after automation, never force-foreground except to SHOW the user something, nudge via a taskbar flash (`*_alert_window`) instead of stealing focus. Standardize `*_lower_os_window` / `*_raise_os_window` / `*_alert_window`.\n6. **Teach \"`ok:true` is not enough\" — verify what you did.** Verbs that return on operation *start* (not completion) need a cheap post-check (an eval/read verb, else a screenshot) before reporting success — 404s, login walls, and empty states land silently.\n7. **Sessions: reuse-don't-reopen + stable identifiers.** Tell the AI to `*_status` before opening a new session (especially after compaction), reuse a match, and pick a stable `profile` so logins/state persist across runs. Prevents duplicate windows and the lost-login footgun.\n8. **\"Surface `_hint` verbatim.\"** Every verb returns an actionable `_hint` on not-ready/failure; instruct the AI to relay it to the user unchanged, and branch on the stable `errorCode` (not the prose). This closes the loop with THE ONE PRINCIPLE at the top — the hint IS your UI.\n\n---\n\n## Verb contract (quick reference)\n\n- **Naming:** every verb is `<name>_<verb>` (`blender_render`, `blender_status`). AD routes any verb with your prefix to you.\n- **Request:** AD POSTs `{ \"command\":\"<verb>\", \"args\":{…} }` to your port. **Response:** JSON with at least `success` (bool); human/AI output in `output`, errors in `error` (+ an `errorCode` for machine-actionable failures). AD relays everything else verbatim — so ALWAYS add `_hint`/`_next`/`related`/`pitfalls`.\n- **Long-running verbs:** return promptly with a job id + set `statusVerb`; the caller polls instead of blocking. On a timeout AD auto-returns `{stillRunning:true, statusVerb, timeoutSeconds}`.\n- **`<prefix>describe`:** return your full verb catalog (`{verbs:[{name,summary,input,output,timeoutSeconds,statusVerb,longRunning,example,hint,related,pitfalls}]}`). AD caches it (`~/.adom/bridge-verbs/<name>.json`) and shows it in the GUI Verbs tab — but remember, `describe` is read rarely; the per-call hints are what carry the AI.\n\n## Health + status chip\n\nYour `/health` (200) makes the chip green. Optionally return `led` (`green`/`yellow`/`red`), `summary` (short label), `tooltip` (full hover). AD owns the **offline** (gray) state when your endpoint is unreachable — you can never assert offline.\n\n## Lifecycle verbs (AD gives these to USERS — know them to guide users)\n\n`bridge_install {manifestUrl}` · `bridge_list` · `bridge_info`/`bridge_detail {name}` · `bridge_log_read {name, sinceOffset?}` · `bridge_kill {name}` · `bridge_pause {name}` · `refresh_bridges {name?}` · `bridge_check_updates`. A bridge installed via `bridge_install` auto-updates with no further opt-in; `updateManifestUrl` in `bridge.json` makes it durable across republish.\n\n---\n\n## Publish checklist (the whole modern flow)\n\n1. **Page** — `adom-wiki page create adom/adom-desktop-<bridge>-bridge` with a hero + an **instructional `brief`** + **`discovery_triggers`**.\n2. **Runtime** — `release create` + `release upload` the versioned `.zip`; POST the manifest to `/files`; set `updateManifestUrl` in BOTH the zip's `bridge.json` and the manifest.\n3. **Skills pkg** — stage ROOT SKILL.md + `skills/<slug>/SKILL.md` (core USER skill + sub-skills) + `discovery_triggers`; `pkg pack` (inspect!) → `pkg publish --org adom` (mind the `.gitignore` trap).\n4. **Discovery** — `discover triggers` to confirm; `discover preview \"<a real user phrase>\"` to confirm you surface.\n5. **Re-seed AD's bundled copy** (AD-core thread, not you) — extract your Release zip over `plugins/<name>/` keeping bundled deps; ships in the next NSIS as the offline fallback.\n6. **Verify** — `bridge_install {manifestUrl}` on a desktop → `bridge_list` shows your `version` + `skillPkg`; `bridge_check_updates` reaches your page; a container `sync_skills` pulls your USER skill; `discover find \"<phrase>\"` surfaces you with an instructional brief.\n\n**Reference bridge = pup** (`adom/adom-desktop-puppeteer-bridge`): live `discovery_triggers`, a skills pkg with a core + sub-skills, a Release zip + manifest with `updateManifestUrl`, a bundled seed with prewarmed `node_modules`, and rich per-verb hints. Copy its shape. (`scripts/sample-bridges/hello-python` + `hello-rust` are the minimal skeletons.) KiCad, Fusion, Blender, Puppeteer, and the **native-browser** bridge (in the `adom-browser-extension` repo) are cloud-owned — don't edit them from the AD repo. When an AD release changes a bridge-facing contract, AD posts a migration notice to **each** of their wiki discussions (`adom-desktop-{kicad,fusion,blender,puppeteer}-bridge` + `adom-browser-extension`).\n",
  "body_md": "---\nname: adom-desktop-bridge-sdk\ndescription: Author, PUBLISH, and make-discoverable an adom-desktop bridge — a small local server that adds a namespace of verbs (kicad_, fusion_, browser_, your_prefix_) to adom-desktop. Use when building, publishing, updating, prewarming, or debugging a bridge, or deciding what skills to ship. Covers the AD-core-vs-author boundary; the #1 rule (rich hints in EVERY verb's OUTPUT, because the AI reads CLI output, never your on-disk skill); bridge.json; the THREE things you publish (runtime Release zip + manifest, container skills pkg, wiki discovery_triggers); prewarm; the publish/dev/user SKILL set you must ship; and the auto-discover → install-check → use flow.\n---\n\n# Bridge SDK — build, publish, and surface a bridge for Adom Desktop\n\nA **bridge** is a small local program (Python, Node, Rust, anything) that AD 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:\n\n1. **Runtime** — a versioned **Release `.zip`** + a **manifest** on your wiki page. AD's `bridge_install` streams it onto the user's machine and spawns it.\n2. **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.\n3. **Discoverability** — **`discovery_triggers`** on your wiki page so a user saying *\"open my app in pup\"* surfaces YOU via `adom-wiki discover find`.\n\n> **THE ONE PRINCIPLE THAT MATTERS MOST — read this before anything else.**\n> **The AI almost never reads your on-disk SKILL.md. It ALWAYS reads the OUTPUT of a CLI call.**\n> 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. AD 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.)\n\n---\n\n## The boundary — what AD owns vs what YOU own\n\nThe #1 mistake is filing AD bugs for bridge issues. The split:\n\n**AD core owns** (don't reimplement): the CLI/relay/`/command` passthrough (AD 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 (AD authors those — you never stamp them).\n\n**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**.\n\nIf a *bridge verb* misbehaves → fix the bridge. If a *generic AD capability* is missing (a new lifecycle verb, a relay field) → file it against AD, but don't re-absorb the bridge into AD.\n\n---\n\n## Runtime contract — AD provisions Node/Python; you bind loopback (v1.9.63 / v1.9.69)\n\n**The single most important change for a Node or Python bridge.** As of AD **v1.9.63** you no longer bootstrap your own interpreter, and you MUST bind loopback. Four rules:\n\n1. **Don't bootstrap your own Node/Python.** AD owns a global, portable, **no-UAC** runtime under `~/.adom/adom-runtimes/`: a system install if one's on PATH, else a portable copy it downloads + prewarms in the background on first launch. AD spawns your `entrypoint` with that interpreter **by absolute path**. So **delete every `winget install node` / `desktop_install_node` / \"please install Python\" / self-download step** from your bridge and its skills. `spawn.kind: \"node\"|\"python\"` IS your runtime declaration — AD reads it and provisions the runtime. Anyone can check status with the **`runtimes`** verb (`state ∈ absent|installing|ready|failed`, `source ∈ system|cache`, version). AD pins the version (Node 22.11.0 / Python 3.12.13 today) — need a different major? ask the AD-core thread; don't ship your own.\n\n2. **Bind `ADOM_BIND_HOST`, never `0.0.0.0`.** AD passes an **`ADOM_BIND_HOST`** env var (always `127.0.0.1`) to every bridge it spawns. A bare listen that defaults to `0.0.0.0` pops a **Windows Firewall \"allow access?\" dialog** on first run — and AD's hard guarantee to users is **no firewall prompt by default**.\n   - Node: `server.listen(PORT, process.env.ADOM_BIND_HOST || '127.0.0.1', cb)`\n   - Python: `HTTPServer((os.environ.get('ADOM_BIND_HOST', '127.0.0.1'), PORT), Handler)`\n\n3. **Ship the Release zip source-only — AD installs your deps, and v1.9.69 makes it bulletproof.** A Node bridge's zip carries SOURCE only (no `node_modules`). AD reuses the bundled seed's `node_modules` via `NODE_PATH` when versions match, else runs **`npm install --include=optional`** for you with the managed runtime's own npm. **As of v1.9.69 AD puts the managed runtime's dir on the child PATH for that install AND for your spawned process** — so a dep's postinstall / `node-gyp` / `prebuild-install` that shells out to a **bare** `node`/`npm`/`npx` (puppeteer's own `node install.mjs`, `sharp`, `keytar`, `better-sqlite3`, `bcrypt`) resolves even on a machine with **no system Node**. **You do NOT add the runtime to PATH yourself** — AD guarantees it. (Python: the interpreter dir + `Scripts/` are on PATH too, so pip can build wheels.)\n\n4. **Bundle deps in your SEED for an instant first spawn.** Source-only zip + a bundled-seed `node_modules` at the same version = AD reuses the seed via `NODE_PATH` (instant); a wiki version newer than the seed forces one cold `npm install` (now PATH-correct, but slower). Bump the seed in lockstep with each Release.\n\n**Net:** declare `spawn.kind`, bind `ADOM_BIND_HOST`, ship source-only. AD does the rest — no UAC, no firewall prompt, no `'node' is not recognized`.\n\n---\n\n## Self-audit checklist — run this when asked to \"audit against the SDK\"\n\nWhen a maintainer says *\"audit your code, skills, README, and wiki page against the Bridge SDK,\"* **this is the checklist.** Each item names its section below; fix every miss, then re-check.\n\n**bridge.json** (the manifest in your zip):\n- [ ] `spawn.kind` ∈ `python|node|exe|external-http`, with **`entrypoint`** (NOT `process`/`cmd`/`args`); `healthEndpoint` is **inside `spawn`**; `port:0` lets AD pick. *(→ bridge.json)*\n- [ ] `updateManifestUrl` → YOUR page's manifest (durable auto-update); `docs` → your own page; **`homepage` is a `wiki.adom.inc` URL, NEVER `github.com`.**\n- [ ] `detect` declared iff you drive a HOST APP (KiCad/Fusion/Altium…); omitted if you bring your own runtime (pup). `prewarm.assets` declared for heavy downloads (e.g. `chrome-for-testing`). *(→ Cold-start tiers)*\n\n**Runtime** (Node/Python bridges) *(→ Runtime contract)*:\n- [ ] You bind **`ADOM_BIND_HOST`** (loopback default), never `0.0.0.0`.\n- [ ] You do **NOT** bootstrap your own Node/Python — no `winget`/`desktop_install_node`/self-download in code or skills. `spawn.kind` is the declaration; AD provisions it.\n- [ ] Release zip is **source-only** (no `node_modules`); you rely on AD's `npm install` (PATH-correct as of v1.9.69) + a lockstep bundled seed for instant spawn. You do NOT put the runtime on PATH yourself.\n\n**Verbs & output** (what the AI actually reads):\n- [ ] EVERY verb returns a rich **`_hint`** (+ `_next`/`related`/`pitfalls`). *(→ THE ONE PRINCIPLE)*\n- [ ] A `<prefix>_describe` verb lists your whole catalog. *(→ Verb contract)*\n- [ ] No verb blocks on a heavy install/download — background it, report `installing`/`warming`; `<prefix>_readiness` is read-only. *(→ Cold-start tiers)*\n- [ ] `/health` returns `led`/`summary`/`tooltip` truthfully. *(→ Health + status chip)*\n\n**Discovery** (can a plain request find you?):\n- [ ] **10+ user-task `discovery_triggers`** (everyday phrasings, NOT dev jargon). **No triggers = invisible.** *(→ Artifact 3)*\n- [ ] You ran `discover preview --json` on 5-7 real user queries and land **top-3** for each.\n- [ ] Triggers stay in YOUR niche — you don't poach a sibling's (login/forms → native-browser, not pup). `discovery_pitch` is an INSTRUCTION.\n\n**Skills & docs** (what the container + humans read):\n- [ ] You ship the publish/dev/user **3-skill set**; only the USER skill is named `SKILL.md` (dev/publish are `dev-skills/*.md`, source-only). *(→ The SKILL SET)*\n- [ ] Your USER skill leads with the cold-start \"read before you panic\" section + the 8 conventions, and points the AI at `bridge_readiness`. *(→ USER-skill conventions)*\n- [ ] README + wiki page + skills all AGREE with the above (no stale github links, no wrong schema, triggers vetted).\n\n---\n\n## bridge.json — the manifest that ships INSIDE your zip\n\n```jsonc\n{\n  \"manifest_version\": 1,\n  \"name\": \"blender\",                        // registry name → verb prefix \"blender_\"\n  \"displayName\": \"Blender\",                 // in-app chip label (the tool's real name)\n  \"version\": \"1.2.0\",                       // numeric; a newer CACHE copy supersedes the bundled seed\n  \"description\": \"Drive Blender headless + GUI\",\n  \"author\": \"you\",\n  \"license\": \"MIT\",\n  \"docs\": \"https://wiki.adom.inc/adom/adom-desktop-blender-bridge\",  // your OWN page (drives skillPkg + hero)\n  \"hero\": \"https://.../hero.png\",           // optional; else AD uses your wiki page hero, else a monogram. Ship it at 1.6 aspect (≈2000×1250) — see \"Card hero + update cost\"\n  \"spawn\": {\n    \"kind\": \"python\",                       // python | node | exe | external-http  (NOT \"process\")\n    \"entrypoint\": \"server.py\",              // the file/exe AD launches; AD AUTO-APPENDS `--port <port>` to argv\n    \"port\": 0,                              // 0 = AD picks a STABLE port (recommended). Bind 127.0.0.1:<that port>.\n    \"persistent\": true,                     // AD's supervisor respawns it if it dies\n    \"healthEndpoint\": \"/health\",            // INSIDE spawn. AD probes it for the chip (default /health)\n    \"stopMethod\": \"kill\",                   // kill | sigterm | graceful_endpoint\n    \"killImageName\": \"python.exe\"           // image name for taskkill on stop (kind:python/node)\n  },\n  \"verbPrefixes\": [\"blender_\"],             // any verb with these prefixes routes to you\n  \"verbs\": [\"blender_render\", \"blender_status\", \"blender_describe\"],  // every verb (routing + Verbs tab)\n  \"statusVerb\": \"blender_status\",           // verb a caller polls after a long op times out\n  \"updateManifestUrl\": \"https://wiki.adom.inc/api/v1/pages/adom-desktop-blender-bridge/files/blender-manifest.json\",\n\n  // v1.9.47 cold-start (see \"Cold-start tiers\" below):\n  \"detect\": {                               // Tier 2 — how AD DETECTS your HOST APP. AD NEVER installs it.\n    \"hostApp\": \"Blender\",                   // omit the whole `detect` block if your bridge brings its own runtime\n    \"appPathsExe\": \"blender.exe\",           //   (e.g. pup ships its own browser → no host app)\n    \"paths\": { \"windows\": [\"%ProgramFiles%\\\\Blender Foundation\\\\*\\\\blender.exe\"] }\n  },\n  \"prewarm\": { \"assets\": [] }               // Tier 1 — heavy runtime assets AD prewarms (e.g. [\"chrome-for-testing\"])\n}\n```\n\n- **`spawn.kind`** is `python` | `node` | `exe` | `external-http` — there is **no `\"process\"` kind**, and no `cmd`/`args`/`cwd`. AD launches `spawn.entrypoint` and **automatically appends `--port <port>`** to its argv; your server parses `--port`. (`external-http` = AD doesn't spawn, only probes a port you declare via a `discovery` block.)\n- **`spawn.port: 0`** → AD allocates a STABLE port (persisted across respawns + AD restarts) and passes it. Bind `127.0.0.1:<port>`. NEVER hardcode a port (Hyper-V/Docker silently reserve ranges — see PORTS.md).\n- **`spawn.healthEndpoint`** lives **inside `spawn`** (not at the top level), default `/health`.\n- **`updateManifestUrl`** — the full URL of YOUR manifest on YOUR page. **Declare it in `bridge.json`** so it survives a republish; AD polls it (on launch + every 4h) and auto-updates users. (AD also remembers the install URL in `~/.adom/bridge-install-urls.json` as a fallback, but the declared field is the durable, author-intended path.)\n- **`docs`** — point at YOUR dedicated wiki page. AD derives your **skillPkg** (`adom/<slug>`) and your card hero from this. Don't point it at a shared/structural page.\n- **`homepage`** — a **`wiki.adom.inc`** URL (your own page, or `https://wiki.adom.inc/adom/adom-desktop` for the platform). **NEVER `github.com/...`** — GitHub is source-backup only; the wiki is the canonical home. (A `homepage: github.com/adom-inc/adom-desktop` is a stale default to fix.)\n- **`detect` / `prewarm`** — the v1.9.47 cold-start declarations (full rules in \"Cold-start tiers\" below). `detect` = your HOST APP (AD detects, never installs); `prewarm` = your heavy RUNTIME assets (AD prewarms/auto-installs). A node bridge that ships its deps still works without `prewarm`; declare `prewarm.assets` only for big *downloaded* assets like a browser.\n\n---\n\n## Artifact 1 — the RUNTIME: publish your `.zip` as a RELEASE + a manifest\n\nThis is the code AD streams onto the user's machine. Two files on your page:\n\n1. **A versioned Release `.zip`** of your bridge dir (server + `bridge.json` at the zip ROOT + any assets). Ship it as a **Release** (the downloadable, version-pinned artifact), not a loose `/files` blob:\n   ```bash\n   adom-wiki release create adom/adom-desktop-<bridge>-bridge <version> --title \"...\" --changelog \"...\"\n   adom-wiki release upload adom/adom-desktop-<bridge>-bridge <version> your-bridge-v<version>.zip --platform any\n   ```\n   (Node bridges: the zip carries SOURCE only — keep `node_modules` out; AD/the seed handle deps. See Prewarm.)\n2. **The manifest JSON** (e.g. `blender-manifest.json`) — identity + where the zip is — POSTed to your page `/files`:\n   ```jsonc\n   { \"name\":\"blender\", \"version\":\"1.2.0\",\n     \"url\":\"https://wiki.adom.inc/download/adom/adom-desktop-blender-bridge/1.2.0/blender-v1.2.0.zip\",  // base-relative, extension-agnostic (.bin works where wiki blocks .zip)\n     \"sha256\":\"…\", \"size\":123456, \"verbPrefixes\":[\"blender_\"], \"healthEndpoint\":\"/health\",\n     \"updateManifestUrl\":\"https://wiki.adom.inc/api/v1/pages/adom-desktop-blender-bridge/files/blender-manifest.json\" }\n   ```\n\n**Users install/update** with AD's `bridge_install {manifestUrl}` → AD downloads the zip, sha256-verifies, unzips into `%LOCALAPPDATA%\\Adom Desktop\\bridges-cache\\<name>\\`, rescans, and remembers the URL for auto-update.\n\n**To ship a new version:** bump `version` in BOTH `bridge.json` (in the zip) and the manifest, re-`release upload` the zip, re-POST the manifest. Users auto-get it on the next 4h poll, or immediately via `refresh_bridges {name}`. **A bundled SEED** (a `plugins/<name>/` folder shipped in AD's NSIS) is the offline/first-run fallback; your published cache copy with a higher `version` supersedes it (cache-over-bundled, version-aware).\n\n---\n\n## Artifact 2 — the SKILLS pkg: container-side docs (`.claude/skills` / `.codex/skills`)\n\nYour runtime runs on the user's DESKTOP; the AI driving it runs in a cloud/WSL2 CONTAINER. That container needs your **consumer SKILL.md** locally to know your verbs. Ship it as its own **`adom-wiki pkg`** (skills only, NO binaries):\n\n```bash\n# Stage dir: a ROOT SKILL.md (required) + USER skills under skills/<slug>/SKILL.md, and\n# package.json {name:\"adom-desktop-<bridge>-bridge\", version, discovery_triggers, files:[...]}.\n# THREE traps that bite — verify each with `tar tzf` before publishing:\n#  1. ⚠ The wiki indexes EVERY file named SKILL.md in the tarball as an INSTALLABLE,\n#     Skills-tab-listed USER skill (it counts them as skill_count). So your DEV + PUBLISH\n#     skills must NOT be named SKILL.md — put them in dev-skills/*.md, source-only — else\n#     every user who installs your pkg also installs your dev/publish docs.\n#  2. ⚠ pkg pack/publish HONORS the stage dir's .gitignore AND the package.json `files`\n#     allowlist needs the GLOB: a bare \"skills\" entry ships ZERO skill files — use \"skills/**\".\n#     (Set .gitignore aside across pack+publish, then restore.)\n#  3. ⚠ Keep the tarball LEAN — NO release zip, NO src/ tree, NO hero PNGs (it was 16 MB\n#     before guards). Skills + docs only.\nadom-wiki pkg pack                       # then: tar tzf <tgz> → every USER SKILL.md present,\n                                         #   ZERO binaries, ZERO dev/publish SKILL.md\nadom-wiki pkg publish --org adom         # publishes adom/adom-desktop-<bridge>-bridge\n```\n\nA container gets it **without anyone asking**:\n- `bridge_install` returns `skillPkg` + `installSkill` (AD derives `adom/<slug>` from your `docs`) and the `_hint` tells the AI to run it.\n- **`adom-desktop sync_skills`** installs every connected desktop's bridge skillPkgs (the CLI also runs it once/day in the background), so a container that connects a week later self-heals your skill.\n- The wiki page's **Skills tab** lists the skills your pkg ships, so the page itself advertises them.\n\n**Install to BOTH agent homes.** A container may run Claude OR Codex — your pkg's `install.sh` must deploy the user skill to **`~/.claude/skills/`** AND **`~/.codex/skills/`** (Codex is a real second skills home now). `adom-wiki pkg update` keeps both fresh.\n\n---\n\n## Artifact 3 — DISCOVERABILITY: `discovery_triggers` so a plain request finds you\n\nThe wiki has an **auto-discover** index, and it is the difference between a usable bridge and an **invisible** one. Declare `discovery_triggers` (the phrases a user actually says) on your page, and a plain request matches them via `adom-wiki discover find` / the in-app discovery an AI runs.\n\n```bash\nadom-wiki discover triggers adom/adom-desktop-<bridge>-bridge --add \"open my pcb\" \"export gerbers\"  # add triggers\nadom-wiki discover preview --json \"open my pcb design\"          # what would surface + the SCORE + WHY\nadom-wiki discover find    \"screenshot my website\"              # the live find an AI runs\n```\n\n**How scoring actually works (measured 2026-06-30 — know this or you'll guess wrong):**\n- `score = 3×(distinct triggers matched) + 1×(tags) + 1×(brief/text)`. **Trigger matches dominate (3× each);** tags/title/brief barely move it.\n- **NO TRIGGERS = INVISIBLE.** A bridge with a perfect title (\"Adom Desktop - Blender Bridge\") + a matching tag scored **ZERO** for the literal query *\"blender\"* — it never appeared in `discover preview`. The scorer ignores title/tags when there are no triggers. **Every shipped bridge MUST carry `discovery_triggers` or it cannot be found by the discovery an AI uses.** (`discover search` full-texts the readme, but that is NOT the path discovery takes.)\n- **More good triggers = higher rank.** Each match is +3, so 10 user-task phrasings beat 2. The healthy bundled bridges carry **30-40** triggers; a struggling one had 14 — all dev jargon — and lost EVERY user query.\n\n**The cardinal rule: triggers are USER-TASK phrasings, NOT dev jargon.** A real user types *\"open my pcb design\"* / *\"export gerber files\"* / *\"design a circuit board\"* — NOT *\"reverse bridge\"* / *\"kicad_bridge_call\"* / *\"fork the kicad bridge\"*. A trigger list full of architecture terms scores 0 on every real query (kicad's actual failure: 7/7 user queries missed, and the **Fusion** bridge out-ranked KiCad on PCB queries because Fusion happened to carry a couple of fuzzy \"pcb\"/\"export\" triggers). Put **10+ everyday task phrasings first**; technical aliases (verb names) are a small minority at the end.\n\n**VET it — never ship triggers you didn't test:**\n1. Write **5-7 queries a typical user would type** to get your tool (these double as your page `sample_prompts`).\n2. `adom-wiki discover preview --json \"<each query>\"` → read `data.scoring[]` (array order = rank). **You must land in the top 3.** (Rate-limited: space calls ~2 s; expect a 429 after ~25.)\n3. For every query where you're NOT top-3, **add that exact phrasing as a trigger** and re-test. If a sibling bridge out-ranks you on a shared word (Fusion vs KiCad on \"pcb\"), add MORE exact phrasings so your 3×-per-trigger total wins.\n\n**Claim YOUR niche — don't poach a sibling's territory.** Discovery routes by whose triggers match; if two bridges match a query the user lands on whichever has *more* triggers, NOT whichever is actually right. So your triggers must describe what YOU are genuinely best at — and you must LEAVE OUT cases a sibling owns. **Canonical example: logging into a site / filling a form on a signed-in page is the native-browser extension's job — it drives the user's REAL browser with their REAL logins — NOT puppeteer's.** Pup is a fresh Chrome-for-Testing profile with no saved logins; it's for *non-login* automation (scraping, screenshots, testing, public pages). So pup must NOT carry `log in` / `fill a form on a site I'm signed into` triggers, and native-browser MUST. When a task needs a capability you DON'T have, make your `discovery_pitch` *point at the bridge that does* — and tell the user to install it (e.g. \"for logged-in form-filling, install the native-browser extension\") — rather than claim it and do it badly.\n\n- **`discovery_pitch`** is the one-liner the snippet quotes back to the AI — auto-generated from `discovery_triggers` + `discovery_pitch`, **no separate upload step.** Make it an INSTRUCTION, not a tagline.\n- **Make your `brief` / `discovery_pitch` an instruction** — it's what the AI reads from the find result. Tell it the next moves: *\"…check `adom-desktop bridge_list` to confirm the bridge is installed; if it is, drive its `<prefix>_*` verbs. If the container lacks the skill, `adom-wiki pkg install adom/adom-desktop-<bridge>-bridge` (or `adom-desktop sync_skills`).\"*\n\n### The end-to-end flow your three artifacts enable\n\nUser says *\"show me my app in pup\"* → the AI:\n1. `adom-wiki discover find \"show me my app in pup\"` → your bridge surfaces (Artifact 3).\n2. `adom-desktop bridge_list` → is your bridge installed in AD? If not, `bridge_install {your manifestUrl}` (Artifact 1).\n3. Is your skill pkg installed in THIS container (`~/.claude/skills/` or `~/.codex/skills/`)? If not, `adom-wiki pkg install adom/<slug>` / `sync_skills` (Artifact 2).\n4. Drive your verbs — guided by the **rich hints you return** (the principle at the top).\n\n---\n\n## Prewarm — make the first call instant\n\nA cold first call that pays multi-minute setup (npm install, downloading a runtime, a heavy import) blows the caller's timeout. Pay that cost in the background BEFORE the user asks:\n\n- **Bundle deps in your SEED.** Pup ships a bundled `node_modules/` in its NSIS seed so a fresh PC can spawn with no `npm install`. When you re-seed from a Release zip, keep the bundled deps.\n- **Idempotent warm-up.** Make spawning + a no-op health/setup pass idempotent so a background prewarm is a no-op once warm.\n- **AD prefetches for you on first launch — standalone AND embedded (HD-bundled) — gated on availability.** (Pup's Node + Chrome-for-Testing prefetch runs once per install in both modes as of AD v1.9.45.) If your bridge needs a heavy runtime, expose a cheap idempotent \"ensure ready\" path AD/your seed can trigger, and surface a clean `errorCode` (e.g. `node_not_found`) when a prereq is missing so the AI can install it (`desktop_install_node`) instead of hanging.\n- **`browser_readiness`-style verb.** Offer a fast verb that reports `{ready:true|false, …}` so a caller (or a fresh-PC test) can confirm warmth without triggering work.\n\n---\n\n## Card hero + update cost — two gotchas that bite\n\n**Hero: ship a 1.6-aspect image, or AD crops the OG-card fallback.** AD's bridge card renders the hero in a banner that is `aspect-ratio: 1.6` with `object-fit: cover`. So:\n- Ship a **1.6-aspect** hero (≈**2000×1250**), full-bleed — like pup & kicad. It fills the banner cleanly.\n- If you ship **no** hero (`bridge.json` has no `hero` AND your wiki page has no `hero_path`), AD falls back to your page's **auto-generated OG card** (1200×630 = **1.90** aspect, the social-card size). `cover` scales that wider image to the banner's height and **clips the sides** — the left text panel gets cut off. (That cropped, off-center look is the tell of a hero-less bridge — it's not an AD bug.)\n- Fix: publish a real hero at 1.6 (the `adom-wiki` page hero, or a `hero` URL in `bridge.json`). Verify by opening your card in AD — the title/text must not be clipped.\n\n**Update cost: keep the Release zip source-only; re-seed the NSIS bundle in lockstep.** When a user clicks the card's \"↑ v…\" update badge, AD downloads your Release zip into the cache and (for a node bridge) provisions deps:\n- **The Release zip MUST be source-only — no `node_modules`.** AD provisions node deps at spawn: it **reuses the bundled seed's `node_modules` via `NODE_PATH`** when the cache version ≤ the seed version (instant), and runs a real **`npm install`** only when the wiki version is *newer* than the bundled seed.\n- So a wiki version much newer than your bundled seed forces a **cold `npm install`** on the next spawn — slow for a heavy tree. **When you publish a new Release, bump the bundled NSIS seed in lockstep** (same version) so most users get the instant NODE_PATH reuse, not a cold install.\n- AD (**v1.9.52+**) never blocks the UI on this: the update downloads straight to cache (no wasteful seed re-copy — the old code deep-copied pup's 73 MB / 4698-file seed and then discarded it, the \"updating… forever\" hang), the badge shows live progress (`downloading…` → done) with a 30s \"installing…\" fallback, and your bridge **keeps serving the old version** until the new one is ready. A lean zip + a lockstep seed is what keeps it *fast*.\n\n---\n\n## Cold-start tiers, host-app detection & the `bridge_readiness` probe (v1.9.47)\n\nA bridge has TWO kinds of dependency, and **AD treats them OPPOSITELY.** Get this right or a first-install probe (e.g. HD's \"what tools do I have\") triggers a slow install at the worst moment.\n\n**Tier 1 — your RUNTIME (Node, Python, your own browser download): AD prewarms + auto-installs it.** This is your plumbing. On first boot AD **provisions** Node/Python in the background — a **portable, no-UAC** copy under `~/.adom/adom-runtimes/` (or a system install if one's on PATH), **NOT winget/MSI** (see **Runtime contract** above) — and prewarms heavy assets you declare. You may also download your own runtime asset (pup downloads Chrome-for-Testing) — fine, it's yours. **But do it in the BACKGROUND: a `*_status` / `*_readiness` / ANY probe verb must NEVER block on a heavy download.** Spawn your server fast (it usually doesn't need the asset to *start*), kick the download off detached, and report `state:\"warming\"` until ready. A synchronous-download-on-first-verb is the exact footgun this release fixed (pup's 150 MB Chrome fetch used to block the probe).\n\n**Tier 2 — the HOST APP (KiCad, Fusion, Altium, …): AD DETECTS it by default and installs it ON REQUEST — never pre-emptively, and NEVER by telling the user to do it themselves.** Two rules that sound opposed but aren't:\n- **Don't install it unasked.** A multi-GB app nobody asked for is rude, so by default AD only DETECTS presence (a machine with KiCad belongs to a KiCad user; EDA users are mono-tool — a Fusion user isn't a KiCad user). AD won't prewarm/spawn your bridge while the app is absent — a resource decision, not a \"go away.\"\n- **But the cardinal Adom rule overrides everything: the AI does EVERYTHING for the user — it must NEVER tell the user to do something the AI can do itself.** So your bridge MUST know how to install the host app (`desktop_install_kicad` / winget / your own install verb) and just DO it the instant the user wants it. **Your \"not installed\" hint must OFFER to install it** (\"KiCad isn't installed — want me to install it for you?\"), then do it on a yes — it must NEVER say *\"download it from kicad.org.\"* Pointing the user at a manual download is the breach.\n\n**Bridge install LEVELS — how eagerly each bridge gets onto the machine. The AI ALWAYS does the install; the level only sets the TIMING/CONSENT:**\n\n| Level | Bridge(s) | Behavior |\n|---|---|---|\n| **0 · Built-in / MUST** | Hydrogen Desktop (`hd`) | The platform itself — the editor AD lives in. Always present; not \"installed\" by AD. |\n| **1 · Fundamental — pre-emptive** | Puppeteer (`pup`) | Browser automation is foundational to Adom. AD pre-installs its RUNTIME (Node + Chrome-for-Testing) ASAP on first boot, no asking. |\n| **2 · Recommended — proactive offer** | native-browser (the extension) | The user's REAL logged-in browser (login/forms). AD proactively SUGGESTS it and installs on a yes (it needs a browser-extension step the user confirms). |\n| **3 · On-request host app** | KiCad, Fusion, Altium, … | Detect by default; NEVER pre-install (huge app, rude unasked). The moment the user wants it, the AI installs it AUTOMATICALLY (winget / `desktop_install_*`) — NEVER tells the user to do it manually. |\n| **4 · Explicit third-party** | any `bridge_install`'d bridge | The user opts in by installing it. |\n\nThe through-line: **the AI never makes the user do something it can do itself.** The only knob is *when* — pre-emptive (pup), proactively-offered (native-browser), or on-request (host apps).\n\n**Declare both in `bridge.json`:**\n```json\n\"detect\": {                          // Tier 2 — how AD finds your HOST APP (detection only)\n  \"hostApp\": \"KiCad\",\n  \"appPathsExe\": \"kicad.exe\",        // optional: Windows App Paths registry lookup (most reliable)\n  \"paths\": {                         // optional: candidate paths, %VAR% expanded, one * = a version dir\n    \"windows\": [\"%ProgramFiles%\\\\KiCad\\\\*\\\\bin\\\\kicad.exe\"],\n    \"macos\":   [\"/Applications/KiCad/KiCad.app\"],\n    \"linux\":   [\"/usr/bin/kicad\"]\n  }\n},\n\"prewarm\": { \"assets\": [\"chrome-for-testing\"] }   // Tier 1 — heavy runtime assets AD prewarms + reports\n```\nA bridge that brings its OWN runtime (pup ships its own browser) **omits `detect`** — it has no external host app. AD infers the runtime from `spawn.kind` (node→Node, python→Python).\n\n**The read-only probe: `bridge_readiness`.** AD aggregates all of this into ONE side-effect-free verb the setup AI calls — it **never spawns a bridge, never installs/downloads**. Per bridge: `{hostApp, hostAppInstalled, runtime, runtimeReady, assets, state}` where `state` ∈ **`ready`** (use it) | **`warming`** (runtime ok, a heavy asset still downloading in the background — re-poll, don't trigger it) | **`needs-runtime`** (node/python installing) | **`no-app`** (host app not installed → not relevant here). Plus a top-level `edaToolsInstalled` list — the one-line answer to \"what EDA tools do I have.\" **Tell your users (in your skill): for a \"what do I have / is it ready\" question call `bridge_readiness`, NOT your `*_status` verb** — a `*_status` may spawn your bridge; readiness never does.\n\nYour two own cold-start verbs (above) still apply: `<prefix>_readiness` (read-only `{ready, installing, installProgressPct, lastError}`) + `<prefix>_prewarm` (`{wait}`-able). Those drive YOUR bridge specifically; `bridge_readiness` is the AD-level aggregate that detects + summarizes across all bridges without spawning any.\n\n---\n\n## The SKILL SET you should ship (not one SKILL.md — a small library)\n\nDon't cram everything into one file. Ship **three kinds** of skill, each with a different reader:\n\n1. **A PUBLISH skill** (for the bridge's OWN cloud/dev thread): how to cut the Release zip, POST the manifest, bump versions, publish the skills pkg (with the `.gitignore` trap), set `discovery_triggers`, and re-seed AD's bundled copy. This keeps *your* future self from getting the publish dance wrong.\n2. **A DEV skill** (for whoever edits the bridge): architecture, the verb contract, how to test locally, the boundary with AD — and, front and center, **the rich-hints-in-output rule**: *the AI never reads this file; it reads your verb OUTPUT, so every verb must return `_hint`/`_next`/`related`/`pitfalls`.* Make that the loudest thing in the dev skill.\n3. **USER skill(s)** (shipped in your pkg → the container): how a cloud AI drives your verbs to get real work done. **If the verb surface is large, split it:** a small **core USER skill** (the 80% workflow + a pointer to the sub-skills) plus **sub-skills** by area (e.g. `…-recording`, `…-electronics`, `…-aps-search`). A 2,000-line monolith won't get read; a tight core skill that names the sub-skills will. (Pup ships exactly this shape: a core bridge skill + sub-skills.)\n\nPublish the PUBLISH + DEV skills on your page for your thread; ship the USER skills in your **pkg** so containers get them. **Critical naming rule:** only USER skills may be named `SKILL.md` — the wiki indexes every `SKILL.md` in your tarball as an installable user skill, so name your DEV/PUBLISH docs `dev-skills/*.md` (source-only) or every user installs them (Artifact 2, trap #1).\n\n**Start from the templates — don't write these from scratch.** `skills/bridge-templates/` in the AD repo has a fill-in-the-blanks file for each of the three skill kinds, generalized from pup's battle-tested set: **`bridge-dev-template.md`** (architecture + the rich-hints rule), **`bridge-publish-template.md`** (the whole release/pkg/discovery dance + every trap), **`bridge-user-skill-template.md`** (the consumer skill, already encoding all eight conventions below). Copy the one you need, swap `<bridge>`/`<prefix>` for your names, delete what doesn't apply.\n\n---\n\n## USER-skill conventions — what every consumer skill must teach (from pup's shape)\n\nThese eight patterns are what make a cloud AI actually succeed with your bridge on the first try. The user-skill template encodes them; bake them into whatever you ship:\n\n1. **Open with a \"FIRST-TIME / COLD-START — read before you panic\" section.** A fresh PC may lack your runtime or owe a one-time download, and the AI's default failure mode is to declare defeat. Lead with a **response → meaning → what-you-do** table that frames `*_installing` / `not_ready` as EXPECTED, keyed off `errorCode` (not prose).\n2. **Standardize two cold-start verbs: `<prefix>_readiness` and `<prefix>_prewarm`.** `readiness` → `{ready, installing, installProgressPct, lastError}`; `prewarm` kicks off setup without real work and supports `{\"wait\":true}` to block. Gives every bridge a uniform \"poll until ready, then proceed\" loop. (Complements the runtime-side Prewarm section above.)\n3. **End the `description:` frontmatter with a `Trigger words:` line** mixing verb names AND plain-English phrases a user would actually type (\"take a screenshot of\", \"log into\"). This is what makes a user-invocable skill activate — and it feeds your `discovery_triggers` (Artifact 3).\n4. **Include a short \"Core verbs\" table** (Command | Description | Key args) for the 80% path, and point at `<prefix>_describe '{}'` for the full machine-readable catalog. The AI reads the table to act fast; `describe` is the completeness escape hatch — don't bloat the skill with every verb.\n5. **\"Background by default — don't disrupt the user.\"** Your bridge drives the AI's workspace, not the user's screen: lower/un-focus after automation, never force-foreground except to SHOW the user something, nudge via a taskbar flash (`*_alert_window`) instead of stealing focus. Standardize `*_lower_os_window` / `*_raise_os_window` / `*_alert_window`.\n6. **Teach \"`ok:true` is not enough\" — verify what you did.** Verbs that return on operation *start* (not completion) need a cheap post-check (an eval/read verb, else a screenshot) before reporting success — 404s, login walls, and empty states land silently.\n7. **Sessions: reuse-don't-reopen + stable identifiers.** Tell the AI to `*_status` before opening a new session (especially after compaction), reuse a match, and pick a stable `profile` so logins/state persist across runs. Prevents duplicate windows and the lost-login footgun.\n8. **\"Surface `_hint` verbatim.\"** Every verb returns an actionable `_hint` on not-ready/failure; instruct the AI to relay it to the user unchanged, and branch on the stable `errorCode` (not the prose). This closes the loop with THE ONE PRINCIPLE at the top — the hint IS your UI.\n\n---\n\n## Verb contract (quick reference)\n\n- **Naming:** every verb is `<name>_<verb>` (`blender_render`, `blender_status`). AD routes any verb with your prefix to you.\n- **Request:** AD POSTs `{ \"command\":\"<verb>\", \"args\":{…} }` to your port. **Response:** JSON with at least `success` (bool); human/AI output in `output`, errors in `error` (+ an `errorCode` for machine-actionable failures). AD relays everything else verbatim — so ALWAYS add `_hint`/`_next`/`related`/`pitfalls`.\n- **Long-running verbs:** return promptly with a job id + set `statusVerb`; the caller polls instead of blocking. On a timeout AD auto-returns `{stillRunning:true, statusVerb, timeoutSeconds}`.\n- **`<prefix>describe`:** return your full verb catalog (`{verbs:[{name,summary,input,output,timeoutSeconds,statusVerb,longRunning,example,hint,related,pitfalls}]}`). AD caches it (`~/.adom/bridge-verbs/<name>.json`) and shows it in the GUI Verbs tab — but remember, `describe` is read rarely; the per-call hints are what carry the AI.\n\n## Health + status chip\n\nYour `/health` (200) makes the chip green. Optionally return `led` (`green`/`yellow`/`red`), `summary` (short label), `tooltip` (full hover). AD owns the **offline** (gray) state when your endpoint is unreachable — you can never assert offline.\n\n## Lifecycle verbs (AD gives these to USERS — know them to guide users)\n\n`bridge_install {manifestUrl}` · `bridge_list` · `bridge_info`/`bridge_detail {name}` · `bridge_log_read {name, sinceOffset?}` · `bridge_kill {name}` · `bridge_pause {name}` · `refresh_bridges {name?}` · `bridge_check_updates`. A bridge installed via `bridge_install` auto-updates with no further opt-in; `updateManifestUrl` in `bridge.json` makes it durable across republish.\n\n---\n\n## Publish checklist (the whole modern flow)\n\n1. **Page** — `adom-wiki page create adom/adom-desktop-<bridge>-bridge` with a hero + an **instructional `brief`** + **`discovery_triggers`**.\n2. **Runtime** — `release create` + `release upload` the versioned `.zip`; POST the manifest to `/files`; set `updateManifestUrl` in BOTH the zip's `bridge.json` and the manifest.\n3. **Skills pkg** — stage ROOT SKILL.md + `skills/<slug>/SKILL.md` (core USER skill + sub-skills) + `discovery_triggers`; `pkg pack` (inspect!) → `pkg publish --org adom` (mind the `.gitignore` trap).\n4. **Discovery** — `discover triggers` to confirm; `discover preview \"<a real user phrase>\"` to confirm you surface.\n5. **Re-seed AD's bundled copy** (AD-core thread, not you) — extract your Release zip over `plugins/<name>/` keeping bundled deps; ships in the next NSIS as the offline fallback.\n6. **Verify** — `bridge_install {manifestUrl}` on a desktop → `bridge_list` shows your `version` + `skillPkg`; `bridge_check_updates` reaches your page; a container `sync_skills` pulls your USER skill; `discover find \"<phrase>\"` surfaces you with an instructional brief.\n\n**Reference bridge = pup** (`adom/adom-desktop-puppeteer-bridge`): live `discovery_triggers`, a skills pkg with a core + sub-skills, a Release zip + manifest with `updateManifestUrl`, a bundled seed with prewarmed `node_modules`, and rich per-verb hints. Copy its shape. (`scripts/sample-bridges/hello-python` + `hello-rust` are the minimal skeletons.) KiCad, Fusion, Blender, Puppeteer, and the **native-browser** bridge (in the `adom-browser-extension` repo) are cloud-owned — don't edit them from the AD repo. When an AD release changes a bridge-facing contract, AD posts a migration notice to **each** of their wiki discussions (`adom-desktop-{kicad,fusion,blender,puppeteer}-bridge` + `adom-browser-extension`).\n",
  "markdown": "---\nname: adom-desktop-bridge-sdk\ndescription: Author, PUBLISH, and make-discoverable an adom-desktop bridge — a small local server that adds a namespace of verbs (kicad_, fusion_, browser_, your_prefix_) to adom-desktop. Use when building, publishing, updating, prewarming, or debugging a bridge, or deciding what skills to ship. Covers the AD-core-vs-author boundary; the #1 rule (rich hints in EVERY verb's OUTPUT, because the AI reads CLI output, never your on-disk skill); bridge.json; the THREE things you publish (runtime Release zip + manifest, container skills pkg, wiki discovery_triggers); prewarm; the publish/dev/user SKILL set you must ship; and the auto-discover → install-check → use flow.\n---\n\n# Bridge SDK — build, publish, and surface a bridge for Adom Desktop\n\nA **bridge** is a small local program (Python, Node, Rust, anything) that AD 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:\n\n1. **Runtime** — a versioned **Release `.zip`** + a **manifest** on your wiki page. AD's `bridge_install` streams it onto the user's machine and spawns it.\n2. **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.\n3. **Discoverability** — **`discovery_triggers`** on your wiki page so a user saying *\"open my app in pup\"* surfaces YOU via `adom-wiki discover find`.\n\n> **THE ONE PRINCIPLE THAT MATTERS MOST — read this before anything else.**\n> **The AI almost never reads your on-disk SKILL.md. It ALWAYS reads the OUTPUT of a CLI call.**\n> 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. AD 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.)\n\n---\n\n## The boundary — what AD owns vs what YOU own\n\nThe #1 mistake is filing AD bugs for bridge issues. The split:\n\n**AD core owns** (don't reimplement): the CLI/relay/`/command` passthrough (AD 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 (AD authors those — you never stamp them).\n\n**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**.\n\nIf a *bridge verb* misbehaves → fix the bridge. If a *generic AD capability* is missing (a new lifecycle verb, a relay field) → file it against AD, but don't re-absorb the bridge into AD.\n\n---\n\n## Runtime contract — AD provisions Node/Python; you bind loopback (v1.9.63 / v1.9.69)\n\n**The single most important change for a Node or Python bridge.** As of AD **v1.9.63** you no longer bootstrap your own interpreter, and you MUST bind loopback. Four rules:\n\n1. **Don't bootstrap your own Node/Python.** AD owns a global, portable, **no-UAC** runtime under `~/.adom/adom-runtimes/`: a system install if one's on PATH, else a portable copy it downloads + prewarms in the background on first launch. AD spawns your `entrypoint` with that interpreter **by absolute path**. So **delete every `winget install node` / `desktop_install_node` / \"please install Python\" / self-download step** from your bridge and its skills. `spawn.kind: \"node\"|\"python\"` IS your runtime declaration — AD reads it and provisions the runtime. Anyone can check status with the **`runtimes`** verb (`state ∈ absent|installing|ready|failed`, `source ∈ system|cache`, version). AD pins the version (Node 22.11.0 / Python 3.12.13 today) — need a different major? ask the AD-core thread; don't ship your own.\n\n2. **Bind `ADOM_BIND_HOST`, never `0.0.0.0`.** AD passes an **`ADOM_BIND_HOST`** env var (always `127.0.0.1`) to every bridge it spawns. A bare listen that defaults to `0.0.0.0` pops a **Windows Firewall \"allow access?\" dialog** on first run — and AD's hard guarantee to users is **no firewall prompt by default**.\n   - Node: `server.listen(PORT, process.env.ADOM_BIND_HOST || '127.0.0.1', cb)`\n   - Python: `HTTPServer((os.environ.get('ADOM_BIND_HOST', '127.0.0.1'), PORT), Handler)`\n\n3. **Ship the Release zip source-only — AD installs your deps, and v1.9.69 makes it bulletproof.** A Node bridge's zip carries SOURCE only (no `node_modules`). AD reuses the bundled seed's `node_modules` via `NODE_PATH` when versions match, else runs **`npm install --include=optional`** for you with the managed runtime's own npm. **As of v1.9.69 AD puts the managed runtime's dir on the child PATH for that install AND for your spawned process** — so a dep's postinstall / `node-gyp` / `prebuild-install` that shells out to a **bare** `node`/`npm`/`npx` (puppeteer's own `node install.mjs`, `sharp`, `keytar`, `better-sqlite3`, `bcrypt`) resolves even on a machine with **no system Node**. **You do NOT add the runtime to PATH yourself** — AD guarantees it. (Python: the interpreter dir + `Scripts/` are on PATH too, so pip can build wheels.)\n\n4. **Bundle deps in your SEED for an instant first spawn.** Source-only zip + a bundled-seed `node_modules` at the same version = AD reuses the seed via `NODE_PATH` (instant); a wiki version newer than the seed forces one cold `npm install` (now PATH-correct, but slower). Bump the seed in lockstep with each Release.\n\n**Net:** declare `spawn.kind`, bind `ADOM_BIND_HOST`, ship source-only. AD does the rest — no UAC, no firewall prompt, no `'node' is not recognized`.\n\n---\n\n## Self-audit checklist — run this when asked to \"audit against the SDK\"\n\nWhen a maintainer says *\"audit your code, skills, README, and wiki page against the Bridge SDK,\"* **this is the checklist.** Each item names its section below; fix every miss, then re-check.\n\n**bridge.json** (the manifest in your zip):\n- [ ] `spawn.kind` ∈ `python|node|exe|external-http`, with **`entrypoint`** (NOT `process`/`cmd`/`args`); `healthEndpoint` is **inside `spawn`**; `port:0` lets AD pick. *(→ bridge.json)*\n- [ ] `updateManifestUrl` → YOUR page's manifest (durable auto-update); `docs` → your own page; **`homepage` is a `wiki.adom.inc` URL, NEVER `github.com`.**\n- [ ] `detect` declared iff you drive a HOST APP (KiCad/Fusion/Altium…); omitted if you bring your own runtime (pup). `prewarm.assets` declared for heavy downloads (e.g. `chrome-for-testing`). *(→ Cold-start tiers)*\n\n**Runtime** (Node/Python bridges) *(→ Runtime contract)*:\n- [ ] You bind **`ADOM_BIND_HOST`** (loopback default), never `0.0.0.0`.\n- [ ] You do **NOT** bootstrap your own Node/Python — no `winget`/`desktop_install_node`/self-download in code or skills. `spawn.kind` is the declaration; AD provisions it.\n- [ ] Release zip is **source-only** (no `node_modules`); you rely on AD's `npm install` (PATH-correct as of v1.9.69) + a lockstep bundled seed for instant spawn. You do NOT put the runtime on PATH yourself.\n\n**Verbs & output** (what the AI actually reads):\n- [ ] EVERY verb returns a rich **`_hint`** (+ `_next`/`related`/`pitfalls`). *(→ THE ONE PRINCIPLE)*\n- [ ] A `<prefix>_describe` verb lists your whole catalog. *(→ Verb contract)*\n- [ ] No verb blocks on a heavy install/download — background it, report `installing`/`warming`; `<prefix>_readiness` is read-only. *(→ Cold-start tiers)*\n- [ ] `/health` returns `led`/`summary`/`tooltip` truthfully. *(→ Health + status chip)*\n\n**Discovery** (can a plain request find you?):\n- [ ] **10+ user-task `discovery_triggers`** (everyday phrasings, NOT dev jargon). **No triggers = invisible.** *(→ Artifact 3)*\n- [ ] You ran `discover preview --json` on 5-7 real user queries and land **top-3** for each.\n- [ ] Triggers stay in YOUR niche — you don't poach a sibling's (login/forms → native-browser, not pup). `discovery_pitch` is an INSTRUCTION.\n\n**Skills & docs** (what the container + humans read):\n- [ ] You ship the publish/dev/user **3-skill set**; only the USER skill is named `SKILL.md` (dev/publish are `dev-skills/*.md`, source-only). *(→ The SKILL SET)*\n- [ ] Your USER skill leads with the cold-start \"read before you panic\" section + the 8 conventions, and points the AI at `bridge_readiness`. *(→ USER-skill conventions)*\n- [ ] README + wiki page + skills all AGREE with the above (no stale github links, no wrong schema, triggers vetted).\n\n---\n\n## bridge.json — the manifest that ships INSIDE your zip\n\n```jsonc\n{\n  \"manifest_version\": 1,\n  \"name\": \"blender\",                        // registry name → verb prefix \"blender_\"\n  \"displayName\": \"Blender\",                 // in-app chip label (the tool's real name)\n  \"version\": \"1.2.0\",                       // numeric; a newer CACHE copy supersedes the bundled seed\n  \"description\": \"Drive Blender headless + GUI\",\n  \"author\": \"you\",\n  \"license\": \"MIT\",\n  \"docs\": \"https://wiki.adom.inc/adom/adom-desktop-blender-bridge\",  // your OWN page (drives skillPkg + hero)\n  \"hero\": \"https://.../hero.png\",           // optional; else AD uses your wiki page hero, else a monogram. Ship it at 1.6 aspect (≈2000×1250) — see \"Card hero + update cost\"\n  \"spawn\": {\n    \"kind\": \"python\",                       // python | node | exe | external-http  (NOT \"process\")\n    \"entrypoint\": \"server.py\",              // the file/exe AD launches; AD AUTO-APPENDS `--port <port>` to argv\n    \"port\": 0,                              // 0 = AD picks a STABLE port (recommended). Bind 127.0.0.1:<that port>.\n    \"persistent\": true,                     // AD's supervisor respawns it if it dies\n    \"healthEndpoint\": \"/health\",            // INSIDE spawn. AD probes it for the chip (default /health)\n    \"stopMethod\": \"kill\",                   // kill | sigterm | graceful_endpoint\n    \"killImageName\": \"python.exe\"           // image name for taskkill on stop (kind:python/node)\n  },\n  \"verbPrefixes\": [\"blender_\"],             // any verb with these prefixes routes to you\n  \"verbs\": [\"blender_render\", \"blender_status\", \"blender_describe\"],  // every verb (routing + Verbs tab)\n  \"statusVerb\": \"blender_status\",           // verb a caller polls after a long op times out\n  \"updateManifestUrl\": \"https://wiki.adom.inc/api/v1/pages/adom-desktop-blender-bridge/files/blender-manifest.json\",\n\n  // v1.9.47 cold-start (see \"Cold-start tiers\" below):\n  \"detect\": {                               // Tier 2 — how AD DETECTS your HOST APP. AD NEVER installs it.\n    \"hostApp\": \"Blender\",                   // omit the whole `detect` block if your bridge brings its own runtime\n    \"appPathsExe\": \"blender.exe\",           //   (e.g. pup ships its own browser → no host app)\n    \"paths\": { \"windows\": [\"%ProgramFiles%\\\\Blender Foundation\\\\*\\\\blender.exe\"] }\n  },\n  \"prewarm\": { \"assets\": [] }               // Tier 1 — heavy runtime assets AD prewarms (e.g. [\"chrome-for-testing\"])\n}\n```\n\n- **`spawn.kind`** is `python` | `node` | `exe` | `external-http` — there is **no `\"process\"` kind**, and no `cmd`/`args`/`cwd`. AD launches `spawn.entrypoint` and **automatically appends `--port <port>`** to its argv; your server parses `--port`. (`external-http` = AD doesn't spawn, only probes a port you declare via a `discovery` block.)\n- **`spawn.port: 0`** → AD allocates a STABLE port (persisted across respawns + AD restarts) and passes it. Bind `127.0.0.1:<port>`. NEVER hardcode a port (Hyper-V/Docker silently reserve ranges — see PORTS.md).\n- **`spawn.healthEndpoint`** lives **inside `spawn`** (not at the top level), default `/health`.\n- **`updateManifestUrl`** — the full URL of YOUR manifest on YOUR page. **Declare it in `bridge.json`** so it survives a republish; AD polls it (on launch + every 4h) and auto-updates users. (AD also remembers the install URL in `~/.adom/bridge-install-urls.json` as a fallback, but the declared field is the durable, author-intended path.)\n- **`docs`** — point at YOUR dedicated wiki page. AD derives your **skillPkg** (`adom/<slug>`) and your card hero from this. Don't point it at a shared/structural page.\n- **`homepage`** — a **`wiki.adom.inc`** URL (your own page, or `https://wiki.adom.inc/adom/adom-desktop` for the platform). **NEVER `github.com/...`** — GitHub is source-backup only; the wiki is the canonical home. (A `homepage: github.com/adom-inc/adom-desktop` is a stale default to fix.)\n- **`detect` / `prewarm`** — the v1.9.47 cold-start declarations (full rules in \"Cold-start tiers\" below). `detect` = your HOST APP (AD detects, never installs); `prewarm` = your heavy RUNTIME assets (AD prewarms/auto-installs). A node bridge that ships its deps still works without `prewarm`; declare `prewarm.assets` only for big *downloaded* assets like a browser.\n\n---\n\n## Artifact 1 — the RUNTIME: publish your `.zip` as a RELEASE + a manifest\n\nThis is the code AD streams onto the user's machine. Two files on your page:\n\n1. **A versioned Release `.zip`** of your bridge dir (server + `bridge.json` at the zip ROOT + any assets). Ship it as a **Release** (the downloadable, version-pinned artifact), not a loose `/files` blob:\n   ```bash\n   adom-wiki release create adom/adom-desktop-<bridge>-bridge <version> --title \"...\" --changelog \"...\"\n   adom-wiki release upload adom/adom-desktop-<bridge>-bridge <version> your-bridge-v<version>.zip --platform any\n   ```\n   (Node bridges: the zip carries SOURCE only — keep `node_modules` out; AD/the seed handle deps. See Prewarm.)\n2. **The manifest JSON** (e.g. `blender-manifest.json`) — identity + where the zip is — POSTed to your page `/files`:\n   ```jsonc\n   { \"name\":\"blender\", \"version\":\"1.2.0\",\n     \"url\":\"https://wiki.adom.inc/download/adom/adom-desktop-blender-bridge/1.2.0/blender-v1.2.0.zip\",  // base-relative, extension-agnostic (.bin works where wiki blocks .zip)\n     \"sha256\":\"…\", \"size\":123456, \"verbPrefixes\":[\"blender_\"], \"healthEndpoint\":\"/health\",\n     \"updateManifestUrl\":\"https://wiki.adom.inc/api/v1/pages/adom-desktop-blender-bridge/files/blender-manifest.json\" }\n   ```\n\n**Users install/update** with AD's `bridge_install {manifestUrl}` → AD downloads the zip, sha256-verifies, unzips into `%LOCALAPPDATA%\\Adom Desktop\\bridges-cache\\<name>\\`, rescans, and remembers the URL for auto-update.\n\n**To ship a new version:** bump `version` in BOTH `bridge.json` (in the zip) and the manifest, re-`release upload` the zip, re-POST the manifest. Users auto-get it on the next 4h poll, or immediately via `refresh_bridges {name}`. **A bundled SEED** (a `plugins/<name>/` folder shipped in AD's NSIS) is the offline/first-run fallback; your published cache copy with a higher `version` supersedes it (cache-over-bundled, version-aware).\n\n---\n\n## Artifact 2 — the SKILLS pkg: container-side docs (`.claude/skills` / `.codex/skills`)\n\nYour runtime runs on the user's DESKTOP; the AI driving it runs in a cloud/WSL2 CONTAINER. That container needs your **consumer SKILL.md** locally to know your verbs. Ship it as its own **`adom-wiki pkg`** (skills only, NO binaries):\n\n```bash\n# Stage dir: a ROOT SKILL.md (required) + USER skills under skills/<slug>/SKILL.md, and\n# package.json {name:\"adom-desktop-<bridge>-bridge\", version, discovery_triggers, files:[...]}.\n# THREE traps that bite — verify each with `tar tzf` before publishing:\n#  1. ⚠ The wiki indexes EVERY file named SKILL.md in the tarball as an INSTALLABLE,\n#     Skills-tab-listed USER skill (it counts them as skill_count). So your DEV + PUBLISH\n#     skills must NOT be named SKILL.md — put them in dev-skills/*.md, source-only — else\n#     every user who installs your pkg also installs your dev/publish docs.\n#  2. ⚠ pkg pack/publish HONORS the stage dir's .gitignore AND the package.json `files`\n#     allowlist needs the GLOB: a bare \"skills\" entry ships ZERO skill files — use \"skills/**\".\n#     (Set .gitignore aside across pack+publish, then restore.)\n#  3. ⚠ Keep the tarball LEAN — NO release zip, NO src/ tree, NO hero PNGs (it was 16 MB\n#     before guards). Skills + docs only.\nadom-wiki pkg pack                       # then: tar tzf <tgz> → every USER SKILL.md present,\n                                         #   ZERO binaries, ZERO dev/publish SKILL.md\nadom-wiki pkg publish --org adom         # publishes adom/adom-desktop-<bridge>-bridge\n```\n\nA container gets it **without anyone asking**:\n- `bridge_install` returns `skillPkg` + `installSkill` (AD derives `adom/<slug>` from your `docs`) and the `_hint` tells the AI to run it.\n- **`adom-desktop sync_skills`** installs every connected desktop's bridge skillPkgs (the CLI also runs it once/day in the background), so a container that connects a week later self-heals your skill.\n- The wiki page's **Skills tab** lists the skills your pkg ships, so the page itself advertises them.\n\n**Install to BOTH agent homes.** A container may run Claude OR Codex — your pkg's `install.sh` must deploy the user skill to **`~/.claude/skills/`** AND **`~/.codex/skills/`** (Codex is a real second skills home now). `adom-wiki pkg update` keeps both fresh.\n\n---\n\n## Artifact 3 — DISCOVERABILITY: `discovery_triggers` so a plain request finds you\n\nThe wiki has an **auto-discover** index, and it is the difference between a usable bridge and an **invisible** one. Declare `discovery_triggers` (the phrases a user actually says) on your page, and a plain request matches them via `adom-wiki discover find` / the in-app discovery an AI runs.\n\n```bash\nadom-wiki discover triggers adom/adom-desktop-<bridge>-bridge --add \"open my pcb\" \"export gerbers\"  # add triggers\nadom-wiki discover preview --json \"open my pcb design\"          # what would surface + the SCORE + WHY\nadom-wiki discover find    \"screenshot my website\"              # the live find an AI runs\n```\n\n**How scoring actually works (measured 2026-06-30 — know this or you'll guess wrong):**\n- `score = 3×(distinct triggers matched) + 1×(tags) + 1×(brief/text)`. **Trigger matches dominate (3× each);** tags/title/brief barely move it.\n- **NO TRIGGERS = INVISIBLE.** A bridge with a perfect title (\"Adom Desktop - Blender Bridge\") + a matching tag scored **ZERO** for the literal query *\"blender\"* — it never appeared in `discover preview`. The scorer ignores title/tags when there are no triggers. **Every shipped bridge MUST carry `discovery_triggers` or it cannot be found by the discovery an AI uses.** (`discover search` full-texts the readme, but that is NOT the path discovery takes.)\n- **More good triggers = higher rank.** Each match is +3, so 10 user-task phrasings beat 2. The healthy bundled bridges carry **30-40** triggers; a struggling one had 14 — all dev jargon — and lost EVERY user query.\n\n**The cardinal rule: triggers are USER-TASK phrasings, NOT dev jargon.** A real user types *\"open my pcb design\"* / *\"export gerber files\"* / *\"design a circuit board\"* — NOT *\"reverse bridge\"* / *\"kicad_bridge_call\"* / *\"fork the kicad bridge\"*. A trigger list full of architecture terms scores 0 on every real query (kicad's actual failure: 7/7 user queries missed, and the **Fusion** bridge out-ranked KiCad on PCB queries because Fusion happened to carry a couple of fuzzy \"pcb\"/\"export\" triggers). Put **10+ everyday task phrasings first**; technical aliases (verb names) are a small minority at the end.\n\n**VET it — never ship triggers you didn't test:**\n1. Write **5-7 queries a typical user would type** to get your tool (these double as your page `sample_prompts`).\n2. `adom-wiki discover preview --json \"<each query>\"` → read `data.scoring[]` (array order = rank). **You must land in the top 3.** (Rate-limited: space calls ~2 s; expect a 429 after ~25.)\n3. For every query where you're NOT top-3, **add that exact phrasing as a trigger** and re-test. If a sibling bridge out-ranks you on a shared word (Fusion vs KiCad on \"pcb\"), add MORE exact phrasings so your 3×-per-trigger total wins.\n\n**Claim YOUR niche — don't poach a sibling's territory.** Discovery routes by whose triggers match; if two bridges match a query the user lands on whichever has *more* triggers, NOT whichever is actually right. So your triggers must describe what YOU are genuinely best at — and you must LEAVE OUT cases a sibling owns. **Canonical example: logging into a site / filling a form on a signed-in page is the native-browser extension's job — it drives the user's REAL browser with their REAL logins — NOT puppeteer's.** Pup is a fresh Chrome-for-Testing profile with no saved logins; it's for *non-login* automation (scraping, screenshots, testing, public pages). So pup must NOT carry `log in` / `fill a form on a site I'm signed into` triggers, and native-browser MUST. When a task needs a capability you DON'T have, make your `discovery_pitch` *point at the bridge that does* — and tell the user to install it (e.g. \"for logged-in form-filling, install the native-browser extension\") — rather than claim it and do it badly.\n\n- **`discovery_pitch`** is the one-liner the snippet quotes back to the AI — auto-generated from `discovery_triggers` + `discovery_pitch`, **no separate upload step.** Make it an INSTRUCTION, not a tagline.\n- **Make your `brief` / `discovery_pitch` an instruction** — it's what the AI reads from the find result. Tell it the next moves: *\"…check `adom-desktop bridge_list` to confirm the bridge is installed; if it is, drive its `<prefix>_*` verbs. If the container lacks the skill, `adom-wiki pkg install adom/adom-desktop-<bridge>-bridge` (or `adom-desktop sync_skills`).\"*\n\n### The end-to-end flow your three artifacts enable\n\nUser says *\"show me my app in pup\"* → the AI:\n1. `adom-wiki discover find \"show me my app in pup\"` → your bridge surfaces (Artifact 3).\n2. `adom-desktop bridge_list` → is your bridge installed in AD? If not, `bridge_install {your manifestUrl}` (Artifact 1).\n3. Is your skill pkg installed in THIS container (`~/.claude/skills/` or `~/.codex/skills/`)? If not, `adom-wiki pkg install adom/<slug>` / `sync_skills` (Artifact 2).\n4. Drive your verbs — guided by the **rich hints you return** (the principle at the top).\n\n---\n\n## Prewarm — make the first call instant\n\nA cold first call that pays multi-minute setup (npm install, downloading a runtime, a heavy import) blows the caller's timeout. Pay that cost in the background BEFORE the user asks:\n\n- **Bundle deps in your SEED.** Pup ships a bundled `node_modules/` in its NSIS seed so a fresh PC can spawn with no `npm install`. When you re-seed from a Release zip, keep the bundled deps.\n- **Idempotent warm-up.** Make spawning + a no-op health/setup pass idempotent so a background prewarm is a no-op once warm.\n- **AD prefetches for you on first launch — standalone AND embedded (HD-bundled) — gated on availability.** (Pup's Node + Chrome-for-Testing prefetch runs once per install in both modes as of AD v1.9.45.) If your bridge needs a heavy runtime, expose a cheap idempotent \"ensure ready\" path AD/your seed can trigger, and surface a clean `errorCode` (e.g. `node_not_found`) when a prereq is missing so the AI can install it (`desktop_install_node`) instead of hanging.\n- **`browser_readiness`-style verb.** Offer a fast verb that reports `{ready:true|false, …}` so a caller (or a fresh-PC test) can confirm warmth without triggering work.\n\n---\n\n## Card hero + update cost — two gotchas that bite\n\n**Hero: ship a 1.6-aspect image, or AD crops the OG-card fallback.** AD's bridge card renders the hero in a banner that is `aspect-ratio: 1.6` with `object-fit: cover`. So:\n- Ship a **1.6-aspect** hero (≈**2000×1250**), full-bleed — like pup & kicad. It fills the banner cleanly.\n- If you ship **no** hero (`bridge.json` has no `hero` AND your wiki page has no `hero_path`), AD falls back to your page's **auto-generated OG card** (1200×630 = **1.90** aspect, the social-card size). `cover` scales that wider image to the banner's height and **clips the sides** — the left text panel gets cut off. (That cropped, off-center look is the tell of a hero-less bridge — it's not an AD bug.)\n- Fix: publish a real hero at 1.6 (the `adom-wiki` page hero, or a `hero` URL in `bridge.json`). Verify by opening your card in AD — the title/text must not be clipped.\n\n**Update cost: keep the Release zip source-only; re-seed the NSIS bundle in lockstep.** When a user clicks the card's \"↑ v…\" update badge, AD downloads your Release zip into the cache and (for a node bridge) provisions deps:\n- **The Release zip MUST be source-only — no `node_modules`.** AD provisions node deps at spawn: it **reuses the bundled seed's `node_modules` via `NODE_PATH`** when the cache version ≤ the seed version (instant), and runs a real **`npm install`** only when the wiki version is *newer* than the bundled seed.\n- So a wiki version much newer than your bundled seed forces a **cold `npm install`** on the next spawn — slow for a heavy tree. **When you publish a new Release, bump the bundled NSIS seed in lockstep** (same version) so most users get the instant NODE_PATH reuse, not a cold install.\n- AD (**v1.9.52+**) never blocks the UI on this: the update downloads straight to cache (no wasteful seed re-copy — the old code deep-copied pup's 73 MB / 4698-file seed and then discarded it, the \"updating… forever\" hang), the badge shows live progress (`downloading…` → done) with a 30s \"installing…\" fallback, and your bridge **keeps serving the old version** until the new one is ready. A lean zip + a lockstep seed is what keeps it *fast*.\n\n---\n\n## Cold-start tiers, host-app detection & the `bridge_readiness` probe (v1.9.47)\n\nA bridge has TWO kinds of dependency, and **AD treats them OPPOSITELY.** Get this right or a first-install probe (e.g. HD's \"what tools do I have\") triggers a slow install at the worst moment.\n\n**Tier 1 — your RUNTIME (Node, Python, your own browser download): AD prewarms + auto-installs it.** This is your plumbing. On first boot AD **provisions** Node/Python in the background — a **portable, no-UAC** copy under `~/.adom/adom-runtimes/` (or a system install if one's on PATH), **NOT winget/MSI** (see **Runtime contract** above) — and prewarms heavy assets you declare. You may also download your own runtime asset (pup downloads Chrome-for-Testing) — fine, it's yours. **But do it in the BACKGROUND: a `*_status` / `*_readiness` / ANY probe verb must NEVER block on a heavy download.** Spawn your server fast (it usually doesn't need the asset to *start*), kick the download off detached, and report `state:\"warming\"` until ready. A synchronous-download-on-first-verb is the exact footgun this release fixed (pup's 150 MB Chrome fetch used to block the probe).\n\n**Tier 2 — the HOST APP (KiCad, Fusion, Altium, …): AD DETECTS it by default and installs it ON REQUEST — never pre-emptively, and NEVER by telling the user to do it themselves.** Two rules that sound opposed but aren't:\n- **Don't install it unasked.** A multi-GB app nobody asked for is rude, so by default AD only DETECTS presence (a machine with KiCad belongs to a KiCad user; EDA users are mono-tool — a Fusion user isn't a KiCad user). AD won't prewarm/spawn your bridge while the app is absent — a resource decision, not a \"go away.\"\n- **But the cardinal Adom rule overrides everything: the AI does EVERYTHING for the user — it must NEVER tell the user to do something the AI can do itself.** So your bridge MUST know how to install the host app (`desktop_install_kicad` / winget / your own install verb) and just DO it the instant the user wants it. **Your \"not installed\" hint must OFFER to install it** (\"KiCad isn't installed — want me to install it for you?\"), then do it on a yes — it must NEVER say *\"download it from kicad.org.\"* Pointing the user at a manual download is the breach.\n\n**Bridge install LEVELS — how eagerly each bridge gets onto the machine. The AI ALWAYS does the install; the level only sets the TIMING/CONSENT:**\n\n| Level | Bridge(s) | Behavior |\n|---|---|---|\n| **0 · Built-in / MUST** | Hydrogen Desktop (`hd`) | The platform itself — the editor AD lives in. Always present; not \"installed\" by AD. |\n| **1 · Fundamental — pre-emptive** | Puppeteer (`pup`) | Browser automation is foundational to Adom. AD pre-installs its RUNTIME (Node + Chrome-for-Testing) ASAP on first boot, no asking. |\n| **2 · Recommended — proactive offer** | native-browser (the extension) | The user's REAL logged-in browser (login/forms). AD proactively SUGGESTS it and installs on a yes (it needs a browser-extension step the user confirms). |\n| **3 · On-request host app** | KiCad, Fusion, Altium, … | Detect by default; NEVER pre-install (huge app, rude unasked). The moment the user wants it, the AI installs it AUTOMATICALLY (winget / `desktop_install_*`) — NEVER tells the user to do it manually. |\n| **4 · Explicit third-party** | any `bridge_install`'d bridge | The user opts in by installing it. |\n\nThe through-line: **the AI never makes the user do something it can do itself.** The only knob is *when* — pre-emptive (pup), proactively-offered (native-browser), or on-request (host apps).\n\n**Declare both in `bridge.json`:**\n```json\n\"detect\": {                          // Tier 2 — how AD finds your HOST APP (detection only)\n  \"hostApp\": \"KiCad\",\n  \"appPathsExe\": \"kicad.exe\",        // optional: Windows App Paths registry lookup (most reliable)\n  \"paths\": {                         // optional: candidate paths, %VAR% expanded, one * = a version dir\n    \"windows\": [\"%ProgramFiles%\\\\KiCad\\\\*\\\\bin\\\\kicad.exe\"],\n    \"macos\":   [\"/Applications/KiCad/KiCad.app\"],\n    \"linux\":   [\"/usr/bin/kicad\"]\n  }\n},\n\"prewarm\": { \"assets\": [\"chrome-for-testing\"] }   // Tier 1 — heavy runtime assets AD prewarms + reports\n```\nA bridge that brings its OWN runtime (pup ships its own browser) **omits `detect`** — it has no external host app. AD infers the runtime from `spawn.kind` (node→Node, python→Python).\n\n**The read-only probe: `bridge_readiness`.** AD aggregates all of this into ONE side-effect-free verb the setup AI calls — it **never spawns a bridge, never installs/downloads**. Per bridge: `{hostApp, hostAppInstalled, runtime, runtimeReady, assets, state}` where `state` ∈ **`ready`** (use it) | **`warming`** (runtime ok, a heavy asset still downloading in the background — re-poll, don't trigger it) | **`needs-runtime`** (node/python installing) | **`no-app`** (host app not installed → not relevant here). Plus a top-level `edaToolsInstalled` list — the one-line answer to \"what EDA tools do I have.\" **Tell your users (in your skill): for a \"what do I have / is it ready\" question call `bridge_readiness`, NOT your `*_status` verb** — a `*_status` may spawn your bridge; readiness never does.\n\nYour two own cold-start verbs (above) still apply: `<prefix>_readiness` (read-only `{ready, installing, installProgressPct, lastError}`) + `<prefix>_prewarm` (`{wait}`-able). Those drive YOUR bridge specifically; `bridge_readiness` is the AD-level aggregate that detects + summarizes across all bridges without spawning any.\n\n---\n\n## The SKILL SET you should ship (not one SKILL.md — a small library)\n\nDon't cram everything into one file. Ship **three kinds** of skill, each with a different reader:\n\n1. **A PUBLISH skill** (for the bridge's OWN cloud/dev thread): how to cut the Release zip, POST the manifest, bump versions, publish the skills pkg (with the `.gitignore` trap), set `discovery_triggers`, and re-seed AD's bundled copy. This keeps *your* future self from getting the publish dance wrong.\n2. **A DEV skill** (for whoever edits the bridge): architecture, the verb contract, how to test locally, the boundary with AD — and, front and center, **the rich-hints-in-output rule**: *the AI never reads this file; it reads your verb OUTPUT, so every verb must return `_hint`/`_next`/`related`/`pitfalls`.* Make that the loudest thing in the dev skill.\n3. **USER skill(s)** (shipped in your pkg → the container): how a cloud AI drives your verbs to get real work done. **If the verb surface is large, split it:** a small **core USER skill** (the 80% workflow + a pointer to the sub-skills) plus **sub-skills** by area (e.g. `…-recording`, `…-electronics`, `…-aps-search`). A 2,000-line monolith won't get read; a tight core skill that names the sub-skills will. (Pup ships exactly this shape: a core bridge skill + sub-skills.)\n\nPublish the PUBLISH + DEV skills on your page for your thread; ship the USER skills in your **pkg** so containers get them. **Critical naming rule:** only USER skills may be named `SKILL.md` — the wiki indexes every `SKILL.md` in your tarball as an installable user skill, so name your DEV/PUBLISH docs `dev-skills/*.md` (source-only) or every user installs them (Artifact 2, trap #1).\n\n**Start from the templates — don't write these from scratch.** `skills/bridge-templates/` in the AD repo has a fill-in-the-blanks file for each of the three skill kinds, generalized from pup's battle-tested set: **`bridge-dev-template.md`** (architecture + the rich-hints rule), **`bridge-publish-template.md`** (the whole release/pkg/discovery dance + every trap), **`bridge-user-skill-template.md`** (the consumer skill, already encoding all eight conventions below). Copy the one you need, swap `<bridge>`/`<prefix>` for your names, delete what doesn't apply.\n\n---\n\n## USER-skill conventions — what every consumer skill must teach (from pup's shape)\n\nThese eight patterns are what make a cloud AI actually succeed with your bridge on the first try. The user-skill template encodes them; bake them into whatever you ship:\n\n1. **Open with a \"FIRST-TIME / COLD-START — read before you panic\" section.** A fresh PC may lack your runtime or owe a one-time download, and the AI's default failure mode is to declare defeat. Lead with a **response → meaning → what-you-do** table that frames `*_installing` / `not_ready` as EXPECTED, keyed off `errorCode` (not prose).\n2. **Standardize two cold-start verbs: `<prefix>_readiness` and `<prefix>_prewarm`.** `readiness` → `{ready, installing, installProgressPct, lastError}`; `prewarm` kicks off setup without real work and supports `{\"wait\":true}` to block. Gives every bridge a uniform \"poll until ready, then proceed\" loop. (Complements the runtime-side Prewarm section above.)\n3. **End the `description:` frontmatter with a `Trigger words:` line** mixing verb names AND plain-English phrases a user would actually type (\"take a screenshot of\", \"log into\"). This is what makes a user-invocable skill activate — and it feeds your `discovery_triggers` (Artifact 3).\n4. **Include a short \"Core verbs\" table** (Command | Description | Key args) for the 80% path, and point at `<prefix>_describe '{}'` for the full machine-readable catalog. The AI reads the table to act fast; `describe` is the completeness escape hatch — don't bloat the skill with every verb.\n5. **\"Background by default — don't disrupt the user.\"** Your bridge drives the AI's workspace, not the user's screen: lower/un-focus after automation, never force-foreground except to SHOW the user something, nudge via a taskbar flash (`*_alert_window`) instead of stealing focus. Standardize `*_lower_os_window` / `*_raise_os_window` / `*_alert_window`.\n6. **Teach \"`ok:true` is not enough\" — verify what you did.** Verbs that return on operation *start* (not completion) need a cheap post-check (an eval/read verb, else a screenshot) before reporting success — 404s, login walls, and empty states land silently.\n7. **Sessions: reuse-don't-reopen + stable identifiers.** Tell the AI to `*_status` before opening a new session (especially after compaction), reuse a match, and pick a stable `profile` so logins/state persist across runs. Prevents duplicate windows and the lost-login footgun.\n8. **\"Surface `_hint` verbatim.\"** Every verb returns an actionable `_hint` on not-ready/failure; instruct the AI to relay it to the user unchanged, and branch on the stable `errorCode` (not the prose). This closes the loop with THE ONE PRINCIPLE at the top — the hint IS your UI.\n\n---\n\n## Verb contract (quick reference)\n\n- **Naming:** every verb is `<name>_<verb>` (`blender_render`, `blender_status`). AD routes any verb with your prefix to you.\n- **Request:** AD POSTs `{ \"command\":\"<verb>\", \"args\":{…} }` to your port. **Response:** JSON with at least `success` (bool); human/AI output in `output`, errors in `error` (+ an `errorCode` for machine-actionable failures). AD relays everything else verbatim — so ALWAYS add `_hint`/`_next`/`related`/`pitfalls`.\n- **Long-running verbs:** return promptly with a job id + set `statusVerb`; the caller polls instead of blocking. On a timeout AD auto-returns `{stillRunning:true, statusVerb, timeoutSeconds}`.\n- **`<prefix>describe`:** return your full verb catalog (`{verbs:[{name,summary,input,output,timeoutSeconds,statusVerb,longRunning,example,hint,related,pitfalls}]}`). AD caches it (`~/.adom/bridge-verbs/<name>.json`) and shows it in the GUI Verbs tab — but remember, `describe` is read rarely; the per-call hints are what carry the AI.\n\n## Health + status chip\n\nYour `/health` (200) makes the chip green. Optionally return `led` (`green`/`yellow`/`red`), `summary` (short label), `tooltip` (full hover). AD owns the **offline** (gray) state when your endpoint is unreachable — you can never assert offline.\n\n## Lifecycle verbs (AD gives these to USERS — know them to guide users)\n\n`bridge_install {manifestUrl}` · `bridge_list` · `bridge_info`/`bridge_detail {name}` · `bridge_log_read {name, sinceOffset?}` · `bridge_kill {name}` · `bridge_pause {name}` · `refresh_bridges {name?}` · `bridge_check_updates`. A bridge installed via `bridge_install` auto-updates with no further opt-in; `updateManifestUrl` in `bridge.json` makes it durable across republish.\n\n---\n\n## Publish checklist (the whole modern flow)\n\n1. **Page** — `adom-wiki page create adom/adom-desktop-<bridge>-bridge` with a hero + an **instructional `brief`** + **`discovery_triggers`**.\n2. **Runtime** — `release create` + `release upload` the versioned `.zip`; POST the manifest to `/files`; set `updateManifestUrl` in BOTH the zip's `bridge.json` and the manifest.\n3. **Skills pkg** — stage ROOT SKILL.md + `skills/<slug>/SKILL.md` (core USER skill + sub-skills) + `discovery_triggers`; `pkg pack` (inspect!) → `pkg publish --org adom` (mind the `.gitignore` trap).\n4. **Discovery** — `discover triggers` to confirm; `discover preview \"<a real user phrase>\"` to confirm you surface.\n5. **Re-seed AD's bundled copy** (AD-core thread, not you) — extract your Release zip over `plugins/<name>/` keeping bundled deps; ships in the next NSIS as the offline fallback.\n6. **Verify** — `bridge_install {manifestUrl}` on a desktop → `bridge_list` shows your `version` + `skillPkg`; `bridge_check_updates` reaches your page; a container `sync_skills` pulls your USER skill; `discover find \"<phrase>\"` surfaces you with an instructional brief.\n\n**Reference bridge = pup** (`adom/adom-desktop-puppeteer-bridge`): live `discovery_triggers`, a skills pkg with a core + sub-skills, a Release zip + manifest with `updateManifestUrl`, a bundled seed with prewarmed `node_modules`, and rich per-verb hints. Copy its shape. (`scripts/sample-bridges/hello-python` + `hello-rust` are the minimal skeletons.) KiCad, Fusion, Blender, Puppeteer, and the **native-browser** bridge (in the `adom-browser-extension` repo) are cloud-owned — don't edit them from the AD repo. When an AD release changes a bridge-facing contract, AD posts a migration notice to **each** of their wiki discussions (`adom-desktop-{kicad,fusion,blender,puppeteer}-bridge` + `adom-browser-extension`).\n",
  "body": "---\nname: adom-desktop-bridge-sdk\ndescription: Author, PUBLISH, and make-discoverable an adom-desktop bridge — a small local server that adds a namespace of verbs (kicad_, fusion_, browser_, your_prefix_) to adom-desktop. Use when building, publishing, updating, prewarming, or debugging a bridge, or deciding what skills to ship. Covers the AD-core-vs-author boundary; the #1 rule (rich hints in EVERY verb's OUTPUT, because the AI reads CLI output, never your on-disk skill); bridge.json; the THREE things you publish (runtime Release zip + manifest, container skills pkg, wiki discovery_triggers); prewarm; the publish/dev/user SKILL set you must ship; and the auto-discover → install-check → use flow.\n---\n\n# Bridge SDK — build, publish, and surface a bridge for Adom Desktop\n\nA **bridge** is a small local program (Python, Node, Rust, anything) that AD 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:\n\n1. **Runtime** — a versioned **Release `.zip`** + a **manifest** on your wiki page. AD's `bridge_install` streams it onto the user's machine and spawns it.\n2. **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.\n3. **Discoverability** — **`discovery_triggers`** on your wiki page so a user saying *\"open my app in pup\"* surfaces YOU via `adom-wiki discover find`.\n\n> **THE ONE PRINCIPLE THAT MATTERS MOST — read this before anything else.**\n> **The AI almost never reads your on-disk SKILL.md. It ALWAYS reads the OUTPUT of a CLI call.**\n> 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. AD 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.)\n\n---\n\n## The boundary — what AD owns vs what YOU own\n\nThe #1 mistake is filing AD bugs for bridge issues. The split:\n\n**AD core owns** (don't reimplement): the CLI/relay/`/command` passthrough (AD 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 (AD authors those — you never stamp them).\n\n**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**.\n\nIf a *bridge verb* misbehaves → fix the bridge. If a *generic AD capability* is missing (a new lifecycle verb, a relay field) → file it against AD, but don't re-absorb the bridge into AD.\n\n---\n\n## Runtime contract — AD provisions Node/Python; you bind loopback (v1.9.63 / v1.9.69)\n\n**The single most important change for a Node or Python bridge.** As of AD **v1.9.63** you no longer bootstrap your own interpreter, and you MUST bind loopback. Four rules:\n\n1. **Don't bootstrap your own Node/Python.** AD owns a global, portable, **no-UAC** runtime under `~/.adom/adom-runtimes/`: a system install if one's on PATH, else a portable copy it downloads + prewarms in the background on first launch. AD spawns your `entrypoint` with that interpreter **by absolute path**. So **delete every `winget install node` / `desktop_install_node` / \"please install Python\" / self-download step** from your bridge and its skills. `spawn.kind: \"node\"|\"python\"` IS your runtime declaration — AD reads it and provisions the runtime. Anyone can check status with the **`runtimes`** verb (`state ∈ absent|installing|ready|failed`, `source ∈ system|cache`, version). AD pins the version (Node 22.11.0 / Python 3.12.13 today) — need a different major? ask the AD-core thread; don't ship your own.\n\n2. **Bind `ADOM_BIND_HOST`, never `0.0.0.0`.** AD passes an **`ADOM_BIND_HOST`** env var (always `127.0.0.1`) to every bridge it spawns. A bare listen that defaults to `0.0.0.0` pops a **Windows Firewall \"allow access?\" dialog** on first run — and AD's hard guarantee to users is **no firewall prompt by default**.\n   - Node: `server.listen(PORT, process.env.ADOM_BIND_HOST || '127.0.0.1', cb)`\n   - Python: `HTTPServer((os.environ.get('ADOM_BIND_HOST', '127.0.0.1'), PORT), Handler)`\n\n3. **Ship the Release zip source-only — AD installs your deps, and v1.9.69 makes it bulletproof.** A Node bridge's zip carries SOURCE only (no `node_modules`). AD reuses the bundled seed's `node_modules` via `NODE_PATH` when versions match, else runs **`npm install --include=optional`** for you with the managed runtime's own npm. **As of v1.9.69 AD puts the managed runtime's dir on the child PATH for that install AND for your spawned process** — so a dep's postinstall / `node-gyp` / `prebuild-install` that shells out to a **bare** `node`/`npm`/`npx` (puppeteer's own `node install.mjs`, `sharp`, `keytar`, `better-sqlite3`, `bcrypt`) resolves even on a machine with **no system Node**. **You do NOT add the runtime to PATH yourself** — AD guarantees it. (Python: the interpreter dir + `Scripts/` are on PATH too, so pip can build wheels.)\n\n4. **Bundle deps in your SEED for an instant first spawn.** Source-only zip + a bundled-seed `node_modules` at the same version = AD reuses the seed via `NODE_PATH` (instant); a wiki version newer than the seed forces one cold `npm install` (now PATH-correct, but slower). Bump the seed in lockstep with each Release.\n\n**Net:** declare `spawn.kind`, bind `ADOM_BIND_HOST`, ship source-only. AD does the rest — no UAC, no firewall prompt, no `'node' is not recognized`.\n\n---\n\n## Self-audit checklist — run this when asked to \"audit against the SDK\"\n\nWhen a maintainer says *\"audit your code, skills, README, and wiki page against the Bridge SDK,\"* **this is the checklist.** Each item names its section below; fix every miss, then re-check.\n\n**bridge.json** (the manifest in your zip):\n- [ ] `spawn.kind` ∈ `python|node|exe|external-http`, with **`entrypoint`** (NOT `process`/`cmd`/`args`); `healthEndpoint` is **inside `spawn`**; `port:0` lets AD pick. *(→ bridge.json)*\n- [ ] `updateManifestUrl` → YOUR page's manifest (durable auto-update); `docs` → your own page; **`homepage` is a `wiki.adom.inc` URL, NEVER `github.com`.**\n- [ ] `detect` declared iff you drive a HOST APP (KiCad/Fusion/Altium…); omitted if you bring your own runtime (pup). `prewarm.assets` declared for heavy downloads (e.g. `chrome-for-testing`). *(→ Cold-start tiers)*\n\n**Runtime** (Node/Python bridges) *(→ Runtime contract)*:\n- [ ] You bind **`ADOM_BIND_HOST`** (loopback default), never `0.0.0.0`.\n- [ ] You do **NOT** bootstrap your own Node/Python — no `winget`/`desktop_install_node`/self-download in code or skills. `spawn.kind` is the declaration; AD provisions it.\n- [ ] Release zip is **source-only** (no `node_modules`); you rely on AD's `npm install` (PATH-correct as of v1.9.69) + a lockstep bundled seed for instant spawn. You do NOT put the runtime on PATH yourself.\n\n**Verbs & output** (what the AI actually reads):\n- [ ] EVERY verb returns a rich **`_hint`** (+ `_next`/`related`/`pitfalls`). *(→ THE ONE PRINCIPLE)*\n- [ ] A `<prefix>_describe` verb lists your whole catalog. *(→ Verb contract)*\n- [ ] No verb blocks on a heavy install/download — background it, report `installing`/`warming`; `<prefix>_readiness` is read-only. *(→ Cold-start tiers)*\n- [ ] `/health` returns `led`/`summary`/`tooltip` truthfully. *(→ Health + status chip)*\n\n**Discovery** (can a plain request find you?):\n- [ ] **10+ user-task `discovery_triggers`** (everyday phrasings, NOT dev jargon). **No triggers = invisible.** *(→ Artifact 3)*\n- [ ] You ran `discover preview --json` on 5-7 real user queries and land **top-3** for each.\n- [ ] Triggers stay in YOUR niche — you don't poach a sibling's (login/forms → native-browser, not pup). `discovery_pitch` is an INSTRUCTION.\n\n**Skills & docs** (what the container + humans read):\n- [ ] You ship the publish/dev/user **3-skill set**; only the USER skill is named `SKILL.md` (dev/publish are `dev-skills/*.md`, source-only). *(→ The SKILL SET)*\n- [ ] Your USER skill leads with the cold-start \"read before you panic\" section + the 8 conventions, and points the AI at `bridge_readiness`. *(→ USER-skill conventions)*\n- [ ] README + wiki page + skills all AGREE with the above (no stale github links, no wrong schema, triggers vetted).\n\n---\n\n## bridge.json — the manifest that ships INSIDE your zip\n\n```jsonc\n{\n  \"manifest_version\": 1,\n  \"name\": \"blender\",                        // registry name → verb prefix \"blender_\"\n  \"displayName\": \"Blender\",                 // in-app chip label (the tool's real name)\n  \"version\": \"1.2.0\",                       // numeric; a newer CACHE copy supersedes the bundled seed\n  \"description\": \"Drive Blender headless + GUI\",\n  \"author\": \"you\",\n  \"license\": \"MIT\",\n  \"docs\": \"https://wiki.adom.inc/adom/adom-desktop-blender-bridge\",  // your OWN page (drives skillPkg + hero)\n  \"hero\": \"https://.../hero.png\",           // optional; else AD uses your wiki page hero, else a monogram. Ship it at 1.6 aspect (≈2000×1250) — see \"Card hero + update cost\"\n  \"spawn\": {\n    \"kind\": \"python\",                       // python | node | exe | external-http  (NOT \"process\")\n    \"entrypoint\": \"server.py\",              // the file/exe AD launches; AD AUTO-APPENDS `--port <port>` to argv\n    \"port\": 0,                              // 0 = AD picks a STABLE port (recommended). Bind 127.0.0.1:<that port>.\n    \"persistent\": true,                     // AD's supervisor respawns it if it dies\n    \"healthEndpoint\": \"/health\",            // INSIDE spawn. AD probes it for the chip (default /health)\n    \"stopMethod\": \"kill\",                   // kill | sigterm | graceful_endpoint\n    \"killImageName\": \"python.exe\"           // image name for taskkill on stop (kind:python/node)\n  },\n  \"verbPrefixes\": [\"blender_\"],             // any verb with these prefixes routes to you\n  \"verbs\": [\"blender_render\", \"blender_status\", \"blender_describe\"],  // every verb (routing + Verbs tab)\n  \"statusVerb\": \"blender_status\",           // verb a caller polls after a long op times out\n  \"updateManifestUrl\": \"https://wiki.adom.inc/api/v1/pages/adom-desktop-blender-bridge/files/blender-manifest.json\",\n\n  // v1.9.47 cold-start (see \"Cold-start tiers\" below):\n  \"detect\": {                               // Tier 2 — how AD DETECTS your HOST APP. AD NEVER installs it.\n    \"hostApp\": \"Blender\",                   // omit the whole `detect` block if your bridge brings its own runtime\n    \"appPathsExe\": \"blender.exe\",           //   (e.g. pup ships its own browser → no host app)\n    \"paths\": { \"windows\": [\"%ProgramFiles%\\\\Blender Foundation\\\\*\\\\blender.exe\"] }\n  },\n  \"prewarm\": { \"assets\": [] }               // Tier 1 — heavy runtime assets AD prewarms (e.g. [\"chrome-for-testing\"])\n}\n```\n\n- **`spawn.kind`** is `python` | `node` | `exe` | `external-http` — there is **no `\"process\"` kind**, and no `cmd`/`args`/`cwd`. AD launches `spawn.entrypoint` and **automatically appends `--port <port>`** to its argv; your server parses `--port`. (`external-http` = AD doesn't spawn, only probes a port you declare via a `discovery` block.)\n- **`spawn.port: 0`** → AD allocates a STABLE port (persisted across respawns + AD restarts) and passes it. Bind `127.0.0.1:<port>`. NEVER hardcode a port (Hyper-V/Docker silently reserve ranges — see PORTS.md).\n- **`spawn.healthEndpoint`** lives **inside `spawn`** (not at the top level), default `/health`.\n- **`updateManifestUrl`** — the full URL of YOUR manifest on YOUR page. **Declare it in `bridge.json`** so it survives a republish; AD polls it (on launch + every 4h) and auto-updates users. (AD also remembers the install URL in `~/.adom/bridge-install-urls.json` as a fallback, but the declared field is the durable, author-intended path.)\n- **`docs`** — point at YOUR dedicated wiki page. AD derives your **skillPkg** (`adom/<slug>`) and your card hero from this. Don't point it at a shared/structural page.\n- **`homepage`** — a **`wiki.adom.inc`** URL (your own page, or `https://wiki.adom.inc/adom/adom-desktop` for the platform). **NEVER `github.com/...`** — GitHub is source-backup only; the wiki is the canonical home. (A `homepage: github.com/adom-inc/adom-desktop` is a stale default to fix.)\n- **`detect` / `prewarm`** — the v1.9.47 cold-start declarations (full rules in \"Cold-start tiers\" below). `detect` = your HOST APP (AD detects, never installs); `prewarm` = your heavy RUNTIME assets (AD prewarms/auto-installs). A node bridge that ships its deps still works without `prewarm`; declare `prewarm.assets` only for big *downloaded* assets like a browser.\n\n---\n\n## Artifact 1 — the RUNTIME: publish your `.zip` as a RELEASE + a manifest\n\nThis is the code AD streams onto the user's machine. Two files on your page:\n\n1. **A versioned Release `.zip`** of your bridge dir (server + `bridge.json` at the zip ROOT + any assets). Ship it as a **Release** (the downloadable, version-pinned artifact), not a loose `/files` blob:\n   ```bash\n   adom-wiki release create adom/adom-desktop-<bridge>-bridge <version> --title \"...\" --changelog \"...\"\n   adom-wiki release upload adom/adom-desktop-<bridge>-bridge <version> your-bridge-v<version>.zip --platform any\n   ```\n   (Node bridges: the zip carries SOURCE only — keep `node_modules` out; AD/the seed handle deps. See Prewarm.)\n2. **The manifest JSON** (e.g. `blender-manifest.json`) — identity + where the zip is — POSTed to your page `/files`:\n   ```jsonc\n   { \"name\":\"blender\", \"version\":\"1.2.0\",\n     \"url\":\"https://wiki.adom.inc/download/adom/adom-desktop-blender-bridge/1.2.0/blender-v1.2.0.zip\",  // base-relative, extension-agnostic (.bin works where wiki blocks .zip)\n     \"sha256\":\"…\", \"size\":123456, \"verbPrefixes\":[\"blender_\"], \"healthEndpoint\":\"/health\",\n     \"updateManifestUrl\":\"https://wiki.adom.inc/api/v1/pages/adom-desktop-blender-bridge/files/blender-manifest.json\" }\n   ```\n\n**Users install/update** with AD's `bridge_install {manifestUrl}` → AD downloads the zip, sha256-verifies, unzips into `%LOCALAPPDATA%\\Adom Desktop\\bridges-cache\\<name>\\`, rescans, and remembers the URL for auto-update.\n\n**To ship a new version:** bump `version` in BOTH `bridge.json` (in the zip) and the manifest, re-`release upload` the zip, re-POST the manifest. Users auto-get it on the next 4h poll, or immediately via `refresh_bridges {name}`. **A bundled SEED** (a `plugins/<name>/` folder shipped in AD's NSIS) is the offline/first-run fallback; your published cache copy with a higher `version` supersedes it (cache-over-bundled, version-aware).\n\n---\n\n## Artifact 2 — the SKILLS pkg: container-side docs (`.claude/skills` / `.codex/skills`)\n\nYour runtime runs on the user's DESKTOP; the AI driving it runs in a cloud/WSL2 CONTAINER. That container needs your **consumer SKILL.md** locally to know your verbs. Ship it as its own **`adom-wiki pkg`** (skills only, NO binaries):\n\n```bash\n# Stage dir: a ROOT SKILL.md (required) + USER skills under skills/<slug>/SKILL.md, and\n# package.json {name:\"adom-desktop-<bridge>-bridge\", version, discovery_triggers, files:[...]}.\n# THREE traps that bite — verify each with `tar tzf` before publishing:\n#  1. ⚠ The wiki indexes EVERY file named SKILL.md in the tarball as an INSTALLABLE,\n#     Skills-tab-listed USER skill (it counts them as skill_count). So your DEV + PUBLISH\n#     skills must NOT be named SKILL.md — put them in dev-skills/*.md, source-only — else\n#     every user who installs your pkg also installs your dev/publish docs.\n#  2. ⚠ pkg pack/publish HONORS the stage dir's .gitignore AND the package.json `files`\n#     allowlist needs the GLOB: a bare \"skills\" entry ships ZERO skill files — use \"skills/**\".\n#     (Set .gitignore aside across pack+publish, then restore.)\n#  3. ⚠ Keep the tarball LEAN — NO release zip, NO src/ tree, NO hero PNGs (it was 16 MB\n#     before guards). Skills + docs only.\nadom-wiki pkg pack                       # then: tar tzf <tgz> → every USER SKILL.md present,\n                                         #   ZERO binaries, ZERO dev/publish SKILL.md\nadom-wiki pkg publish --org adom         # publishes adom/adom-desktop-<bridge>-bridge\n```\n\nA container gets it **without anyone asking**:\n- `bridge_install` returns `skillPkg` + `installSkill` (AD derives `adom/<slug>` from your `docs`) and the `_hint` tells the AI to run it.\n- **`adom-desktop sync_skills`** installs every connected desktop's bridge skillPkgs (the CLI also runs it once/day in the background), so a container that connects a week later self-heals your skill.\n- The wiki page's **Skills tab** lists the skills your pkg ships, so the page itself advertises them.\n\n**Install to BOTH agent homes.** A container may run Claude OR Codex — your pkg's `install.sh` must deploy the user skill to **`~/.claude/skills/`** AND **`~/.codex/skills/`** (Codex is a real second skills home now). `adom-wiki pkg update` keeps both fresh.\n\n---\n\n## Artifact 3 — DISCOVERABILITY: `discovery_triggers` so a plain request finds you\n\nThe wiki has an **auto-discover** index, and it is the difference between a usable bridge and an **invisible** one. Declare `discovery_triggers` (the phrases a user actually says) on your page, and a plain request matches them via `adom-wiki discover find` / the in-app discovery an AI runs.\n\n```bash\nadom-wiki discover triggers adom/adom-desktop-<bridge>-bridge --add \"open my pcb\" \"export gerbers\"  # add triggers\nadom-wiki discover preview --json \"open my pcb design\"          # what would surface + the SCORE + WHY\nadom-wiki discover find    \"screenshot my website\"              # the live find an AI runs\n```\n\n**How scoring actually works (measured 2026-06-30 — know this or you'll guess wrong):**\n- `score = 3×(distinct triggers matched) + 1×(tags) + 1×(brief/text)`. **Trigger matches dominate (3× each);** tags/title/brief barely move it.\n- **NO TRIGGERS = INVISIBLE.** A bridge with a perfect title (\"Adom Desktop - Blender Bridge\") + a matching tag scored **ZERO** for the literal query *\"blender\"* — it never appeared in `discover preview`. The scorer ignores title/tags when there are no triggers. **Every shipped bridge MUST carry `discovery_triggers` or it cannot be found by the discovery an AI uses.** (`discover search` full-texts the readme, but that is NOT the path discovery takes.)\n- **More good triggers = higher rank.** Each match is +3, so 10 user-task phrasings beat 2. The healthy bundled bridges carry **30-40** triggers; a struggling one had 14 — all dev jargon — and lost EVERY user query.\n\n**The cardinal rule: triggers are USER-TASK phrasings, NOT dev jargon.** A real user types *\"open my pcb design\"* / *\"export gerber files\"* / *\"design a circuit board\"* — NOT *\"reverse bridge\"* / *\"kicad_bridge_call\"* / *\"fork the kicad bridge\"*. A trigger list full of architecture terms scores 0 on every real query (kicad's actual failure: 7/7 user queries missed, and the **Fusion** bridge out-ranked KiCad on PCB queries because Fusion happened to carry a couple of fuzzy \"pcb\"/\"export\" triggers). Put **10+ everyday task phrasings first**; technical aliases (verb names) are a small minority at the end.\n\n**VET it — never ship triggers you didn't test:**\n1. Write **5-7 queries a typical user would type** to get your tool (these double as your page `sample_prompts`).\n2. `adom-wiki discover preview --json \"<each query>\"` → read `data.scoring[]` (array order = rank). **You must land in the top 3.** (Rate-limited: space calls ~2 s; expect a 429 after ~25.)\n3. For every query where you're NOT top-3, **add that exact phrasing as a trigger** and re-test. If a sibling bridge out-ranks you on a shared word (Fusion vs KiCad on \"pcb\"), add MORE exact phrasings so your 3×-per-trigger total wins.\n\n**Claim YOUR niche — don't poach a sibling's territory.** Discovery routes by whose triggers match; if two bridges match a query the user lands on whichever has *more* triggers, NOT whichever is actually right. So your triggers must describe what YOU are genuinely best at — and you must LEAVE OUT cases a sibling owns. **Canonical example: logging into a site / filling a form on a signed-in page is the native-browser extension's job — it drives the user's REAL browser with their REAL logins — NOT puppeteer's.** Pup is a fresh Chrome-for-Testing profile with no saved logins; it's for *non-login* automation (scraping, screenshots, testing, public pages). So pup must NOT carry `log in` / `fill a form on a site I'm signed into` triggers, and native-browser MUST. When a task needs a capability you DON'T have, make your `discovery_pitch` *point at the bridge that does* — and tell the user to install it (e.g. \"for logged-in form-filling, install the native-browser extension\") — rather than claim it and do it badly.\n\n- **`discovery_pitch`** is the one-liner the snippet quotes back to the AI — auto-generated from `discovery_triggers` + `discovery_pitch`, **no separate upload step.** Make it an INSTRUCTION, not a tagline.\n- **Make your `brief` / `discovery_pitch` an instruction** — it's what the AI reads from the find result. Tell it the next moves: *\"…check `adom-desktop bridge_list` to confirm the bridge is installed; if it is, drive its `<prefix>_*` verbs. If the container lacks the skill, `adom-wiki pkg install adom/adom-desktop-<bridge>-bridge` (or `adom-desktop sync_skills`).\"*\n\n### The end-to-end flow your three artifacts enable\n\nUser says *\"show me my app in pup\"* → the AI:\n1. `adom-wiki discover find \"show me my app in pup\"` → your bridge surfaces (Artifact 3).\n2. `adom-desktop bridge_list` → is your bridge installed in AD? If not, `bridge_install {your manifestUrl}` (Artifact 1).\n3. Is your skill pkg installed in THIS container (`~/.claude/skills/` or `~/.codex/skills/`)? If not, `adom-wiki pkg install adom/<slug>` / `sync_skills` (Artifact 2).\n4. Drive your verbs — guided by the **rich hints you return** (the principle at the top).\n\n---\n\n## Prewarm — make the first call instant\n\nA cold first call that pays multi-minute setup (npm install, downloading a runtime, a heavy import) blows the caller's timeout. Pay that cost in the background BEFORE the user asks:\n\n- **Bundle deps in your SEED.** Pup ships a bundled `node_modules/` in its NSIS seed so a fresh PC can spawn with no `npm install`. When you re-seed from a Release zip, keep the bundled deps.\n- **Idempotent warm-up.** Make spawning + a no-op health/setup pass idempotent so a background prewarm is a no-op once warm.\n- **AD prefetches for you on first launch — standalone AND embedded (HD-bundled) — gated on availability.** (Pup's Node + Chrome-for-Testing prefetch runs once per install in both modes as of AD v1.9.45.) If your bridge needs a heavy runtime, expose a cheap idempotent \"ensure ready\" path AD/your seed can trigger, and surface a clean `errorCode` (e.g. `node_not_found`) when a prereq is missing so the AI can install it (`desktop_install_node`) instead of hanging.\n- **`browser_readiness`-style verb.** Offer a fast verb that reports `{ready:true|false, …}` so a caller (or a fresh-PC test) can confirm warmth without triggering work.\n\n---\n\n## Card hero + update cost — two gotchas that bite\n\n**Hero: ship a 1.6-aspect image, or AD crops the OG-card fallback.** AD's bridge card renders the hero in a banner that is `aspect-ratio: 1.6` with `object-fit: cover`. So:\n- Ship a **1.6-aspect** hero (≈**2000×1250**), full-bleed — like pup & kicad. It fills the banner cleanly.\n- If you ship **no** hero (`bridge.json` has no `hero` AND your wiki page has no `hero_path`), AD falls back to your page's **auto-generated OG card** (1200×630 = **1.90** aspect, the social-card size). `cover` scales that wider image to the banner's height and **clips the sides** — the left text panel gets cut off. (That cropped, off-center look is the tell of a hero-less bridge — it's not an AD bug.)\n- Fix: publish a real hero at 1.6 (the `adom-wiki` page hero, or a `hero` URL in `bridge.json`). Verify by opening your card in AD — the title/text must not be clipped.\n\n**Update cost: keep the Release zip source-only; re-seed the NSIS bundle in lockstep.** When a user clicks the card's \"↑ v…\" update badge, AD downloads your Release zip into the cache and (for a node bridge) provisions deps:\n- **The Release zip MUST be source-only — no `node_modules`.** AD provisions node deps at spawn: it **reuses the bundled seed's `node_modules` via `NODE_PATH`** when the cache version ≤ the seed version (instant), and runs a real **`npm install`** only when the wiki version is *newer* than the bundled seed.\n- So a wiki version much newer than your bundled seed forces a **cold `npm install`** on the next spawn — slow for a heavy tree. **When you publish a new Release, bump the bundled NSIS seed in lockstep** (same version) so most users get the instant NODE_PATH reuse, not a cold install.\n- AD (**v1.9.52+**) never blocks the UI on this: the update downloads straight to cache (no wasteful seed re-copy — the old code deep-copied pup's 73 MB / 4698-file seed and then discarded it, the \"updating… forever\" hang), the badge shows live progress (`downloading…` → done) with a 30s \"installing…\" fallback, and your bridge **keeps serving the old version** until the new one is ready. A lean zip + a lockstep seed is what keeps it *fast*.\n\n---\n\n## Cold-start tiers, host-app detection & the `bridge_readiness` probe (v1.9.47)\n\nA bridge has TWO kinds of dependency, and **AD treats them OPPOSITELY.** Get this right or a first-install probe (e.g. HD's \"what tools do I have\") triggers a slow install at the worst moment.\n\n**Tier 1 — your RUNTIME (Node, Python, your own browser download): AD prewarms + auto-installs it.** This is your plumbing. On first boot AD **provisions** Node/Python in the background — a **portable, no-UAC** copy under `~/.adom/adom-runtimes/` (or a system install if one's on PATH), **NOT winget/MSI** (see **Runtime contract** above) — and prewarms heavy assets you declare. You may also download your own runtime asset (pup downloads Chrome-for-Testing) — fine, it's yours. **But do it in the BACKGROUND: a `*_status` / `*_readiness` / ANY probe verb must NEVER block on a heavy download.** Spawn your server fast (it usually doesn't need the asset to *start*), kick the download off detached, and report `state:\"warming\"` until ready. A synchronous-download-on-first-verb is the exact footgun this release fixed (pup's 150 MB Chrome fetch used to block the probe).\n\n**Tier 2 — the HOST APP (KiCad, Fusion, Altium, …): AD DETECTS it by default and installs it ON REQUEST — never pre-emptively, and NEVER by telling the user to do it themselves.** Two rules that sound opposed but aren't:\n- **Don't install it unasked.** A multi-GB app nobody asked for is rude, so by default AD only DETECTS presence (a machine with KiCad belongs to a KiCad user; EDA users are mono-tool — a Fusion user isn't a KiCad user). AD won't prewarm/spawn your bridge while the app is absent — a resource decision, not a \"go away.\"\n- **But the cardinal Adom rule overrides everything: the AI does EVERYTHING for the user — it must NEVER tell the user to do something the AI can do itself.** So your bridge MUST know how to install the host app (`desktop_install_kicad` / winget / your own install verb) and just DO it the instant the user wants it. **Your \"not installed\" hint must OFFER to install it** (\"KiCad isn't installed — want me to install it for you?\"), then do it on a yes — it must NEVER say *\"download it from kicad.org.\"* Pointing the user at a manual download is the breach.\n\n**Bridge install LEVELS — how eagerly each bridge gets onto the machine. The AI ALWAYS does the install; the level only sets the TIMING/CONSENT:**\n\n| Level | Bridge(s) | Behavior |\n|---|---|---|\n| **0 · Built-in / MUST** | Hydrogen Desktop (`hd`) | The platform itself — the editor AD lives in. Always present; not \"installed\" by AD. |\n| **1 · Fundamental — pre-emptive** | Puppeteer (`pup`) | Browser automation is foundational to Adom. AD pre-installs its RUNTIME (Node + Chrome-for-Testing) ASAP on first boot, no asking. |\n| **2 · Recommended — proactive offer** | native-browser (the extension) | The user's REAL logged-in browser (login/forms). AD proactively SUGGESTS it and installs on a yes (it needs a browser-extension step the user confirms). |\n| **3 · On-request host app** | KiCad, Fusion, Altium, … | Detect by default; NEVER pre-install (huge app, rude unasked). The moment the user wants it, the AI installs it AUTOMATICALLY (winget / `desktop_install_*`) — NEVER tells the user to do it manually. |\n| **4 · Explicit third-party** | any `bridge_install`'d bridge | The user opts in by installing it. |\n\nThe through-line: **the AI never makes the user do something it can do itself.** The only knob is *when* — pre-emptive (pup), proactively-offered (native-browser), or on-request (host apps).\n\n**Declare both in `bridge.json`:**\n```json\n\"detect\": {                          // Tier 2 — how AD finds your HOST APP (detection only)\n  \"hostApp\": \"KiCad\",\n  \"appPathsExe\": \"kicad.exe\",        // optional: Windows App Paths registry lookup (most reliable)\n  \"paths\": {                         // optional: candidate paths, %VAR% expanded, one * = a version dir\n    \"windows\": [\"%ProgramFiles%\\\\KiCad\\\\*\\\\bin\\\\kicad.exe\"],\n    \"macos\":   [\"/Applications/KiCad/KiCad.app\"],\n    \"linux\":   [\"/usr/bin/kicad\"]\n  }\n},\n\"prewarm\": { \"assets\": [\"chrome-for-testing\"] }   // Tier 1 — heavy runtime assets AD prewarms + reports\n```\nA bridge that brings its OWN runtime (pup ships its own browser) **omits `detect`** — it has no external host app. AD infers the runtime from `spawn.kind` (node→Node, python→Python).\n\n**The read-only probe: `bridge_readiness`.** AD aggregates all of this into ONE side-effect-free verb the setup AI calls — it **never spawns a bridge, never installs/downloads**. Per bridge: `{hostApp, hostAppInstalled, runtime, runtimeReady, assets, state}` where `state` ∈ **`ready`** (use it) | **`warming`** (runtime ok, a heavy asset still downloading in the background — re-poll, don't trigger it) | **`needs-runtime`** (node/python installing) | **`no-app`** (host app not installed → not relevant here). Plus a top-level `edaToolsInstalled` list — the one-line answer to \"what EDA tools do I have.\" **Tell your users (in your skill): for a \"what do I have / is it ready\" question call `bridge_readiness`, NOT your `*_status` verb** — a `*_status` may spawn your bridge; readiness never does.\n\nYour two own cold-start verbs (above) still apply: `<prefix>_readiness` (read-only `{ready, installing, installProgressPct, lastError}`) + `<prefix>_prewarm` (`{wait}`-able). Those drive YOUR bridge specifically; `bridge_readiness` is the AD-level aggregate that detects + summarizes across all bridges without spawning any.\n\n---\n\n## The SKILL SET you should ship (not one SKILL.md — a small library)\n\nDon't cram everything into one file. Ship **three kinds** of skill, each with a different reader:\n\n1. **A PUBLISH skill** (for the bridge's OWN cloud/dev thread): how to cut the Release zip, POST the manifest, bump versions, publish the skills pkg (with the `.gitignore` trap), set `discovery_triggers`, and re-seed AD's bundled copy. This keeps *your* future self from getting the publish dance wrong.\n2. **A DEV skill** (for whoever edits the bridge): architecture, the verb contract, how to test locally, the boundary with AD — and, front and center, **the rich-hints-in-output rule**: *the AI never reads this file; it reads your verb OUTPUT, so every verb must return `_hint`/`_next`/`related`/`pitfalls`.* Make that the loudest thing in the dev skill.\n3. **USER skill(s)** (shipped in your pkg → the container): how a cloud AI drives your verbs to get real work done. **If the verb surface is large, split it:** a small **core USER skill** (the 80% workflow + a pointer to the sub-skills) plus **sub-skills** by area (e.g. `…-recording`, `…-electronics`, `…-aps-search`). A 2,000-line monolith won't get read; a tight core skill that names the sub-skills will. (Pup ships exactly this shape: a core bridge skill + sub-skills.)\n\nPublish the PUBLISH + DEV skills on your page for your thread; ship the USER skills in your **pkg** so containers get them. **Critical naming rule:** only USER skills may be named `SKILL.md` — the wiki indexes every `SKILL.md` in your tarball as an installable user skill, so name your DEV/PUBLISH docs `dev-skills/*.md` (source-only) or every user installs them (Artifact 2, trap #1).\n\n**Start from the templates — don't write these from scratch.** `skills/bridge-templates/` in the AD repo has a fill-in-the-blanks file for each of the three skill kinds, generalized from pup's battle-tested set: **`bridge-dev-template.md`** (architecture + the rich-hints rule), **`bridge-publish-template.md`** (the whole release/pkg/discovery dance + every trap), **`bridge-user-skill-template.md`** (the consumer skill, already encoding all eight conventions below). Copy the one you need, swap `<bridge>`/`<prefix>` for your names, delete what doesn't apply.\n\n---\n\n## USER-skill conventions — what every consumer skill must teach (from pup's shape)\n\nThese eight patterns are what make a cloud AI actually succeed with your bridge on the first try. The user-skill template encodes them; bake them into whatever you ship:\n\n1. **Open with a \"FIRST-TIME / COLD-START — read before you panic\" section.** A fresh PC may lack your runtime or owe a one-time download, and the AI's default failure mode is to declare defeat. Lead with a **response → meaning → what-you-do** table that frames `*_installing` / `not_ready` as EXPECTED, keyed off `errorCode` (not prose).\n2. **Standardize two cold-start verbs: `<prefix>_readiness` and `<prefix>_prewarm`.** `readiness` → `{ready, installing, installProgressPct, lastError}`; `prewarm` kicks off setup without real work and supports `{\"wait\":true}` to block. Gives every bridge a uniform \"poll until ready, then proceed\" loop. (Complements the runtime-side Prewarm section above.)\n3. **End the `description:` frontmatter with a `Trigger words:` line** mixing verb names AND plain-English phrases a user would actually type (\"take a screenshot of\", \"log into\"). This is what makes a user-invocable skill activate — and it feeds your `discovery_triggers` (Artifact 3).\n4. **Include a short \"Core verbs\" table** (Command | Description | Key args) for the 80% path, and point at `<prefix>_describe '{}'` for the full machine-readable catalog. The AI reads the table to act fast; `describe` is the completeness escape hatch — don't bloat the skill with every verb.\n5. **\"Background by default — don't disrupt the user.\"** Your bridge drives the AI's workspace, not the user's screen: lower/un-focus after automation, never force-foreground except to SHOW the user something, nudge via a taskbar flash (`*_alert_window`) instead of stealing focus. Standardize `*_lower_os_window` / `*_raise_os_window` / `*_alert_window`.\n6. **Teach \"`ok:true` is not enough\" — verify what you did.** Verbs that return on operation *start* (not completion) need a cheap post-check (an eval/read verb, else a screenshot) before reporting success — 404s, login walls, and empty states land silently.\n7. **Sessions: reuse-don't-reopen + stable identifiers.** Tell the AI to `*_status` before opening a new session (especially after compaction), reuse a match, and pick a stable `profile` so logins/state persist across runs. Prevents duplicate windows and the lost-login footgun.\n8. **\"Surface `_hint` verbatim.\"** Every verb returns an actionable `_hint` on not-ready/failure; instruct the AI to relay it to the user unchanged, and branch on the stable `errorCode` (not the prose). This closes the loop with THE ONE PRINCIPLE at the top — the hint IS your UI.\n\n---\n\n## Verb contract (quick reference)\n\n- **Naming:** every verb is `<name>_<verb>` (`blender_render`, `blender_status`). AD routes any verb with your prefix to you.\n- **Request:** AD POSTs `{ \"command\":\"<verb>\", \"args\":{…} }` to your port. **Response:** JSON with at least `success` (bool); human/AI output in `output`, errors in `error` (+ an `errorCode` for machine-actionable failures). AD relays everything else verbatim — so ALWAYS add `_hint`/`_next`/`related`/`pitfalls`.\n- **Long-running verbs:** return promptly with a job id + set `statusVerb`; the caller polls instead of blocking. On a timeout AD auto-returns `{stillRunning:true, statusVerb, timeoutSeconds}`.\n- **`<prefix>describe`:** return your full verb catalog (`{verbs:[{name,summary,input,output,timeoutSeconds,statusVerb,longRunning,example,hint,related,pitfalls}]}`). AD caches it (`~/.adom/bridge-verbs/<name>.json`) and shows it in the GUI Verbs tab — but remember, `describe` is read rarely; the per-call hints are what carry the AI.\n\n## Health + status chip\n\nYour `/health` (200) makes the chip green. Optionally return `led` (`green`/`yellow`/`red`), `summary` (short label), `tooltip` (full hover). AD owns the **offline** (gray) state when your endpoint is unreachable — you can never assert offline.\n\n## Lifecycle verbs (AD gives these to USERS — know them to guide users)\n\n`bridge_install {manifestUrl}` · `bridge_list` · `bridge_info`/`bridge_detail {name}` · `bridge_log_read {name, sinceOffset?}` · `bridge_kill {name}` · `bridge_pause {name}` · `refresh_bridges {name?}` · `bridge_check_updates`. A bridge installed via `bridge_install` auto-updates with no further opt-in; `updateManifestUrl` in `bridge.json` makes it durable across republish.\n\n---\n\n## Publish checklist (the whole modern flow)\n\n1. **Page** — `adom-wiki page create adom/adom-desktop-<bridge>-bridge` with a hero + an **instructional `brief`** + **`discovery_triggers`**.\n2. **Runtime** — `release create` + `release upload` the versioned `.zip`; POST the manifest to `/files`; set `updateManifestUrl` in BOTH the zip's `bridge.json` and the manifest.\n3. **Skills pkg** — stage ROOT SKILL.md + `skills/<slug>/SKILL.md` (core USER skill + sub-skills) + `discovery_triggers`; `pkg pack` (inspect!) → `pkg publish --org adom` (mind the `.gitignore` trap).\n4. **Discovery** — `discover triggers` to confirm; `discover preview \"<a real user phrase>\"` to confirm you surface.\n5. **Re-seed AD's bundled copy** (AD-core thread, not you) — extract your Release zip over `plugins/<name>/` keeping bundled deps; ships in the next NSIS as the offline fallback.\n6. **Verify** — `bridge_install {manifestUrl}` on a desktop → `bridge_list` shows your `version` + `skillPkg`; `bridge_check_updates` reaches your page; a container `sync_skills` pulls your USER skill; `discover find \"<phrase>\"` surfaces you with an instructional brief.\n\n**Reference bridge = pup** (`adom/adom-desktop-puppeteer-bridge`): live `discovery_triggers`, a skills pkg with a core + sub-skills, a Release zip + manifest with `updateManifestUrl`, a bundled seed with prewarmed `node_modules`, and rich per-verb hints. Copy its shape. (`scripts/sample-bridges/hello-python` + `hello-rust` are the minimal skeletons.) KiCad, Fusion, Blender, Puppeteer, and the **native-browser** bridge (in the `adom-browser-extension` repo) are cloud-owned — don't edit them from the AD repo. When an AD release changes a bridge-facing contract, AD posts a migration notice to **each** of their wiki discussions (`adom-desktop-{kicad,fusion,blender,puppeteer}-bridge` + `adom-browser-extension`).\n",
  "org": "adom"
}