← Commit history

bridges-SDK.md: sync to the skillpack-convention SDK (was a stale pre-1.9.76 copy — the 3rd SDK copy on this page, missed by the README/SKILL push)

John Lauer ·b73c94be92 ·4mo ago ·parent a03f913
1 file changed +351−302
bridges-SDK.md+351−302
@@ -1,302 +1,351 @@-# Bridge SDK — build, publish, and surface a bridge for Adom Desktop
-
-A **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:
-
-1. **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.
-2. **Skills** — an **`adom-wiki pkg`** tarball carrying your consumer SKILL.md(s). A container installs it into `~/.claude/skills/` (or `~/.codex/skills/`) so the AI *there* knows your verbs.
-3. **Discoverability** — **`discovery_triggers`** on your wiki page so a user saying *"open my app in pup"* surfaces YOU via `adom-wiki discover find`.
-
-> **THE ONE PRINCIPLE THAT MATTERS MOST — read this before anything else.**
-> **The AI almost never reads your on-disk SKILL.md. It ALWAYS reads the OUTPUT of a CLI call.**
-> So your leverage is in the **`_hint` / `_next` / `related` / `pitfalls` you return on EVERY verb response.** A verb that returns `{success:true}` and nothing else leaves the AI guessing and retry-looping. A verb that returns *what just happened, the obvious next verb, the related verbs, and the pitfall to avoid* gets driven perfectly on the first try. 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.)
-
----
-
-## The boundary — what AD owns vs what YOU own
-
-The #1 mistake is filing AD bugs for bridge issues. The split:
-
-**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).
-
-**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**.
-
-If 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.
-
----
-
-## Self-audit checklist — run this when asked to "audit against the SDK"
-
-When 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.
-
-**bridge.json** (the manifest in your zip):
-- [ ] `spawn.kind` ∈ `python|node|exe|external-http`, with **`entrypoint`** (NOT `process`/`cmd`/`args`); `healthEndpoint` is **inside `spawn`**; `port:0` lets AD pick. *(→ bridge.json)*
-- [ ] `updateManifestUrl` → YOUR page's manifest (durable auto-update); `docs` → your own page; **`homepage` is a `wiki.adom.inc` URL, NEVER `github.com`.**
-- [ ] `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)*
-
-**Verbs & output** (what the AI actually reads):
-- [ ] EVERY verb returns a rich **`_hint`** (+ `_next`/`related`/`pitfalls`). *(→ THE ONE PRINCIPLE)*
-- [ ] A `<prefix>_describe` verb lists your whole catalog. *(→ Verb contract)*
-- [ ] No verb blocks on a heavy install/download — background it, report `installing`/`warming`; `<prefix>_readiness` is read-only. *(→ Cold-start tiers)*
-- [ ] `/health` returns `led`/`summary`/`tooltip` truthfully. *(→ Health + status chip)*
-
-**Discovery** (can a plain request find you?):
-- [ ] **10+ user-task `discovery_triggers`** (everyday phrasings, NOT dev jargon). **No triggers = invisible.** *(→ Artifact 3)*
-- [ ] You ran `discover preview --json` on 5-7 real user queries and land **top-3** for each.
-- [ ] Triggers stay in YOUR niche — you don't poach a sibling's (login/forms → native-browser, not pup). `discovery_pitch` is an INSTRUCTION.
-
-**Skills & docs** (what the container + humans read):
-- [ ] 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)*
-- [ ] 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)*
-- [ ] README + wiki page + skills all AGREE with the above (no stale github links, no wrong schema, triggers vetted).
-
----
-
-## bridge.json — the manifest that ships INSIDE your zip
-
-```jsonc
-{
-  "manifest_version": 1,
-  "name": "blender",                        // registry name → verb prefix "blender_"
-  "displayName": "Blender",                 // in-app chip label (the tool's real name)
-  "version": "1.2.0",                       // numeric; a newer CACHE copy supersedes the bundled seed
-  "description": "Drive Blender headless + GUI",
-  "author": "you",
-  "license": "MIT",
-  "docs": "https://wiki.adom.inc/adom/adom-desktop-blender-bridge",  // your OWN page (drives skillPkg + hero)
-  "hero": "https://.../hero.png",           // optional; else AD uses your wiki page hero, else a monogram
-  "spawn": {
-    "kind": "python",                       // python | node | exe | external-http  (NOT "process")
-    "entrypoint": "server.py",              // the file/exe AD launches; AD AUTO-APPENDS `--port <port>` to argv
-    "port": 0,                              // 0 = AD picks a STABLE port (recommended). Bind 127.0.0.1:<that port>.
-    "persistent": true,                     // AD's supervisor respawns it if it dies
-    "healthEndpoint": "/health",            // INSIDE spawn. AD probes it for the chip (default /health)
-    "stopMethod": "kill",                   // kill | sigterm | graceful_endpoint
-    "killImageName": "python.exe"           // image name for taskkill on stop (kind:python/node)
-  },
-  "verbPrefixes": ["blender_"],             // any verb with these prefixes routes to you
-  "verbs": ["blender_render", "blender_status", "blender_describe"],  // every verb (routing + Verbs tab)
-  "statusVerb": "blender_status",           // verb a caller polls after a long op times out
-  "updateManifestUrl": "https://wiki.adom.inc/api/v1/pages/adom-desktop-blender-bridge/files/blender-manifest.json",
-
-  // v1.9.47 cold-start (see "Cold-start tiers" below):
-  "detect": {                               // Tier 2 — how AD DETECTS your HOST APP. AD NEVER installs it.
-    "hostApp": "Blender",                   // omit the whole `detect` block if your bridge brings its own runtime
-    "appPathsExe": "blender.exe",           //   (e.g. pup ships its own browser → no host app)
-    "paths": { "windows": ["%ProgramFiles%\\Blender Foundation\\*\\blender.exe"] }
-  },
-  "prewarm": { "assets": [] }               // Tier 1 — heavy runtime assets AD prewarms (e.g. ["chrome-for-testing"])
-}
-```
-
-- **`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.)
-- **`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).
-- **`spawn.healthEndpoint`** lives **inside `spawn`** (not at the top level), default `/health`.
-- **`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.)
-- **`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.
-- **`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.)
-- **`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.
-
----
-
-## Artifact 1 — the RUNTIME: publish your `.zip` as a RELEASE + a manifest
-
-This is the code AD streams onto the user's machine. Two files on your page:
-
-1. **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:
-   ```bash
-   adom-wiki release create adom/adom-desktop-<bridge>-bridge <version> --title "..." --changelog "..."
-   adom-wiki release upload adom/adom-desktop-<bridge>-bridge <version> your-bridge-v<version>.zip --platform any
-   ```
-   (Node bridges: the zip carries SOURCE only — keep `node_modules` out; AD/the seed handle deps. See Prewarm.)
-2. **The manifest JSON** (e.g. `blender-manifest.json`) — identity + where the zip is — POSTed to your page `/files`:
-   ```jsonc
-   { "name":"blender", "version":"1.2.0",
-     "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)
-     "sha256":"…", "size":123456, "verbPrefixes":["blender_"], "healthEndpoint":"/health",
-     "updateManifestUrl":"https://wiki.adom.inc/api/v1/pages/adom-desktop-blender-bridge/files/blender-manifest.json" }
-   ```
-
-**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.
-
-**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).
-
----
-
-## Artifact 2 — the SKILLS pkg: container-side docs (`.claude/skills` / `.codex/skills`)
-
-Your 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):
-
-```bash
-# Stage dir: a ROOT SKILL.md (required) + USER skills under skills/<slug>/SKILL.md, and
-# package.json {name:"adom-desktop-<bridge>-bridge", version, discovery_triggers, files:[...]}.
-# THREE traps that bite — verify each with `tar tzf` before publishing:
-#  1. ⚠ The wiki indexes EVERY file named SKILL.md in the tarball as an INSTALLABLE,
-#     Skills-tab-listed USER skill (it counts them as skill_count). So your DEV + PUBLISH
-#     skills must NOT be named SKILL.md — put them in dev-skills/*.md, source-only — else
-#     every user who installs your pkg also installs your dev/publish docs.
-#  2. ⚠ pkg pack/publish HONORS the stage dir's .gitignore AND the package.json `files`
-#     allowlist needs the GLOB: a bare "skills" entry ships ZERO skill files — use "skills/**".
-#     (Set .gitignore aside across pack+publish, then restore.)
-#  3. ⚠ Keep the tarball LEAN — NO release zip, NO src/ tree, NO hero PNGs (it was 16 MB
-#     before guards). Skills + docs only.
-adom-wiki pkg pack                       # then: tar tzf <tgz> → every USER SKILL.md present,
-                                         #   ZERO binaries, ZERO dev/publish SKILL.md
-adom-wiki pkg publish --org adom         # publishes adom/adom-desktop-<bridge>-bridge
-```
-
-A container gets it **without anyone asking**:
-- `bridge_install` returns `skillPkg` + `installSkill` (AD derives `adom/<slug>` from your `docs`) and the `_hint` tells the AI to run it.
-- **`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.
-- The wiki page's **Skills tab** lists the skills your pkg ships, so the page itself advertises them.
-
-**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.
-
----
-
-## Artifact 3 — DISCOVERABILITY: `discovery_triggers` so a plain request finds you
-
-The 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.
-
-```bash
-adom-wiki discover triggers adom/adom-desktop-<bridge>-bridge --add "open my pcb" "export gerbers"  # add triggers
-adom-wiki discover preview --json "open my pcb design"          # what would surface + the SCORE + WHY
-adom-wiki discover find    "screenshot my website"              # the live find an AI runs
-```
-
-**How scoring actually works (measured 2026-06-30 — know this or you'll guess wrong):**
-- `score = 3×(distinct triggers matched) + 1×(tags) + 1×(brief/text)`. **Trigger matches dominate (3× each);** tags/title/brief barely move it.
-- **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.)
-- **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.
-
-**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.
-
-**VET it — never ship triggers you didn't test:**
-1. Write **5-7 queries a typical user would type** to get your tool (these double as your page `sample_prompts`).
-2. `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.)
-3. 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.
-
-**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.
-
-- **`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.
-- **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`)."*
-
-### The end-to-end flow your three artifacts enable
-
-User says *"show me my app in pup"* → the AI:
-1. `adom-wiki discover find "show me my app in pup"` → your bridge surfaces (Artifact 3).
-2. `adom-desktop bridge_list` → is your bridge installed in AD? If not, `bridge_install {your manifestUrl}` (Artifact 1).
-3. 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).
-4. Drive your verbs — guided by the **rich hints you return** (the principle at the top).
-
----
-
-## Prewarm — make the first call instant
-
-A 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:
-
-- **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.
-- **Idempotent warm-up.** Make spawning + a no-op health/setup pass idempotent so a background prewarm is a no-op once warm.
-- **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.
-- **`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.
-
----
-
-## Cold-start tiers, host-app detection & the `bridge_readiness` probe (v1.9.47)
-
-A 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.
-
-**Tier 1 — your RUNTIME (Node, Python, your own browser download): AD prewarms + auto-installs it.** This is your plumbing. On first boot AD installs Node/Python in the background (winget) 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).
-
-**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:
-- **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."
-- **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.
-
-**Bridge install LEVELS — how eagerly each bridge gets onto the machine. The AI ALWAYS does the install; the level only sets the TIMING/CONSENT:**
-
-| Level | Bridge(s) | Behavior |
-|---|---|---|
-| **0 · Built-in / MUST** | Hydrogen Desktop (`hd`) | The platform itself — the editor AD lives in. Always present; not "installed" by AD. |
-| **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. |
-| **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). |
-| **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. |
-| **4 · Explicit third-party** | any `bridge_install`'d bridge | The user opts in by installing it. |
-
-The 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).
-
-**Declare both in `bridge.json`:**
-```json
-"detect": {                          // Tier 2 — how AD finds your HOST APP (detection only)
-  "hostApp": "KiCad",
-  "appPathsExe": "kicad.exe",        // optional: Windows App Paths registry lookup (most reliable)
-  "paths": {                         // optional: candidate paths, %VAR% expanded, one * = a version dir
-    "windows": ["%ProgramFiles%\\KiCad\\*\\bin\\kicad.exe"],
-    "macos":   ["/Applications/KiCad/KiCad.app"],
-    "linux":   ["/usr/bin/kicad"]
-  }
-},
-"prewarm": { "assets": ["chrome-for-testing"] }   // Tier 1 — heavy runtime assets AD prewarms + reports
-```
-A 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).
-
-**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.
-
-Your 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.
-
----
-
-## The SKILL SET you should ship (not one SKILL.md — a small library)
-
-Don't cram everything into one file. Ship **three kinds** of skill, each with a different reader:
-
-1. **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.
-2. **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.
-3. **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.)
-
-Publish 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).
-
-**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.
-
----
-
-## USER-skill conventions — what every consumer skill must teach (from pup's shape)
-
-These 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:
-
-1. **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).
-2. **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.)
-3. **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).
-4. **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.
-5. **"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`.
-6. **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.
-7. **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.
-8. **"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.
-
----
-
-## Verb contract (quick reference)
-
-- **Naming:** every verb is `<name>_<verb>` (`blender_render`, `blender_status`). AD routes any verb with your prefix to you.
-- **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`.
-- **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}`.
-- **`<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.
-
-## Health + status chip
-
-Your `/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.
-
-## Lifecycle verbs (AD gives these to USERS — know them to guide users)
-
-`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.
-
----
-
-## Publish checklist (the whole modern flow)
-
-1. **Page** — `adom-wiki page create adom/adom-desktop-<bridge>-bridge` with a hero + an **instructional `brief`** + **`discovery_triggers`**.
-2. **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.
-3. **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).
-4. **Discovery** — `discover triggers` to confirm; `discover preview "<a real user phrase>"` to confirm you surface.
-5. **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.
-6. **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.
-
-**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/Puppeteer are cloud-owned — don't edit them from the AD repo.
+---+name: adom-desktop-bridge-sdk+description: 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.+---++# Bridge SDK — build, publish, and surface a bridge for Adom Desktop++A **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:++1. **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.+2. **Skills** — an **`adom-wiki pkg`** tarball carrying your consumer SKILL.md(s). A container installs it into `~/.claude/skills/` (or `~/.codex/skills/`) so the AI *there* knows your verbs.+3. **Discoverability** — **`discovery_triggers`** on your wiki page so a user saying *"open my app in pup"* surfaces YOU via `adom-wiki discover find`.++> **THE ONE PRINCIPLE THAT MATTERS MOST — read this before anything else.**+> **The AI almost never reads your on-disk SKILL.md. It ALWAYS reads the OUTPUT of a CLI call.**+> So your leverage is in the **`_hint` / `_next` / `related` / `pitfalls` you return on EVERY verb response.** A verb that returns `{success:true}` and nothing else leaves the AI guessing and retry-looping. A verb that returns *what just happened, the obvious next verb, the related verbs, and the pitfall to avoid* gets driven perfectly on the first try. 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.)++---++## The boundary — what AD owns vs what YOU own++The #1 mistake is filing AD bugs for bridge issues. The split:++**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).++**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**.++If 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.++---++## Runtime contract — AD provisions Node/Python; you bind loopback (v1.9.63 / v1.9.69)++**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:++1. **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.++2. **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**.+   - Node: `server.listen(PORT, process.env.ADOM_BIND_HOST || '127.0.0.1', cb)`+   - Python: `HTTPServer((os.environ.get('ADOM_BIND_HOST', '127.0.0.1'), PORT), Handler)`++3. **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.)++4. **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.++**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`.++---++## Self-audit checklist — run this when asked to "audit against the SDK"++When 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.++**bridge.json** (the manifest in your zip):+- [ ] `spawn.kind` ∈ `python|node|exe|external-http`, with **`entrypoint`** (NOT `process`/`cmd`/`args`); `healthEndpoint` is **inside `spawn`**; `port:0` lets AD pick. *(→ bridge.json)*+- [ ] `updateManifestUrl` → YOUR page's manifest (durable auto-update); `docs` → your own page; **`homepage` is a `wiki.adom.inc` URL, NEVER `github.com`.**+- [ ] `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)*++**Runtime** (Node/Python bridges) *(→ Runtime contract)*:+- [ ] You bind **`ADOM_BIND_HOST`** (loopback default), never `0.0.0.0`.+- [ ] 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.+- [ ] 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.++**Verbs & output** (what the AI actually reads):+- [ ] EVERY verb returns a rich **`_hint`** (+ `_next`/`related`/`pitfalls`). *(→ THE ONE PRINCIPLE)*+- [ ] A `<prefix>_describe` verb lists your whole catalog. *(→ Verb contract)*+- [ ] No verb blocks on a heavy install/download — background it, report `installing`/`warming`; `<prefix>_readiness` is read-only. *(→ Cold-start tiers)*+- [ ] `/health` returns `led`/`summary`/`tooltip` truthfully. *(→ Health + status chip)*++**Discovery** (can a plain request find you?):+- [ ] **10+ user-task `discovery_triggers`** (everyday phrasings, NOT dev jargon). **No triggers = invisible.** *(→ Artifact 3)*+- [ ] You ran `discover preview --json` on 5-7 real user queries and land **top-3** for each.+- [ ] Triggers stay in YOUR niche — you don't poach a sibling's (login/forms → native-browser, not pup). `discovery_pitch` is an INSTRUCTION.++**Skills & docs** (what the container + humans read):+- [ ] You ship the publish/dev/user **3-skill set** in the SKILLPACK layout: EVERY skill (user + dev + publish) is a real `skills/<name>/SKILL.md` with a `Parent skill:` first body line; dev/publish are scoped by `user-invocable: false` + a DEVELOPER-only description (NOT by hiding them as `dev-skills/*.md` — that convention is dead); every path listed EXPLICITLY (no globs) in `files[]` + install.sh/uninstall.sh. *(→ Artifact 2 + The SKILL SET)*+- [ ] 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)*+- [ ] README + wiki page + skills all AGREE with the above (no stale github links, no wrong schema, triggers vetted).++---++## bridge.json — the manifest that ships INSIDE your zip++```jsonc+{+  "manifest_version": 1,+  "name": "blender",                        // registry name → verb prefix "blender_"+  "displayName": "Blender",                 // in-app chip label (the tool's real name)+  "version": "1.2.0",                       // numeric; a newer CACHE copy supersedes the bundled seed+  "description": "Drive Blender headless + GUI",+  "author": "you",+  "license": "MIT",+  "docs": "https://wiki.adom.inc/adom/adom-desktop-blender-bridge",  // your OWN page (drives skillPkg + hero)+  "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"+  "spawn": {+    "kind": "python",                       // python | node | exe | external-http  (NOT "process")+    "entrypoint": "server.py",              // the file/exe AD launches; AD AUTO-APPENDS `--port <port>` to argv+    "port": 0,                              // 0 = AD picks a STABLE port (recommended). Bind 127.0.0.1:<that port>.+    "persistent": true,                     // AD's supervisor respawns it if it dies+    "healthEndpoint": "/health",            // INSIDE spawn. AD probes it for the chip (default /health)+    "stopMethod": "kill",                   // kill | sigterm | graceful_endpoint+    "killImageName": "python.exe"           // image name for taskkill on stop (kind:python/node)+  },+  "verbPrefixes": ["blender_"],             // any verb with these prefixes routes to you+  "verbs": ["blender_render", "blender_status", "blender_describe"],  // every verb (routing + Verbs tab)+  "statusVerb": "blender_status",           // verb a caller polls after a long op times out+  "updateManifestUrl": "https://wiki.adom.inc/api/v1/pages/adom-desktop-blender-bridge/files/blender-manifest.json",++  // v1.9.47 cold-start (see "Cold-start tiers" below):+  "detect": {                               // Tier 2 — how AD DETECTS your HOST APP. AD NEVER installs it.+    "hostApp": "Blender",                   // omit the whole `detect` block if your bridge brings its own runtime+    "appPathsExe": "blender.exe",           //   (e.g. pup ships its own browser → no host app)+    "paths": { "windows": ["%ProgramFiles%\\Blender Foundation\\*\\blender.exe"] }+  },+  "prewarm": { "assets": [] }               // Tier 1 — heavy runtime assets AD prewarms (e.g. ["chrome-for-testing"])+}+```++- **`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.)+- **`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).+- **`spawn.healthEndpoint`** lives **inside `spawn`** (not at the top level), default `/health`.+- **`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.)+- **`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.+- **`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.)+- **`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.++---++## Artifact 1 — the RUNTIME: publish your `.zip` as a RELEASE + a manifest++This is the code AD streams onto the user's machine. Two files on your page:++1. **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:+   ```bash+   adom-wiki release create adom/adom-desktop-<bridge>-bridge <version> --title "..." --changelog "..."+   adom-wiki release upload adom/adom-desktop-<bridge>-bridge <version> your-bridge-v<version>.zip --platform any+   ```+   (Node bridges: the zip carries SOURCE only — keep `node_modules` out; AD/the seed handle deps. See Prewarm.)+2. **The manifest JSON** (e.g. `blender-manifest.json`) — identity + where the zip is — POSTed to your page `/files`:+   ```jsonc+   { "name":"blender", "version":"1.2.0",+     "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)+     "sha256":"…", "size":123456, "verbPrefixes":["blender_"], "healthEndpoint":"/health",+     "updateManifestUrl":"https://wiki.adom.inc/api/v1/pages/adom-desktop-blender-bridge/files/blender-manifest.json" }+   ```++**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.++**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).++---++## Artifact 2 — the SKILLS pkg: container-side docs (`.claude/skills` / `.codex/skills`)++Your 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):++**Layout = the wiki SKILLPACK convention** (reference: `adom/adom-wiki-skillpack`; supersedes the old "hide dev docs in `dev-skills/*.md`" guidance — pup migrated 2026-07-03):++- **EVERY skill — user, dev, AND publish — is a real `skills/<name>/SKILL.md`.** Nothing is scoped by hiding files. A ROOT `SKILL.md` (required by the registry) is the pack entry point: `user-invocable: true`, a `description` full of trigger words.+- **Each sub-skill's body opens with a `Parent skill:` line** (first line after the frontmatter): `Parent skill: **adom-desktop-<bridge>-bridge**`.+- **Dev/publish skills are scoped by AUDIENCE, not by filename:** frontmatter `user-invocable: false` + a description that opens DEVELOPER-only (e.g. "DEVELOPER skill for maintainers of this bridge — not for driving it"). Everyday users never get them offered; maintainers still install them.+- **List every skill path EXPLICITLY — no globs — in `package.json` `files[]` AND `install.sh`/`uninstall.sh`** (one `install_skill <dest> <src>` line each, kept in sync). The publish linter REQUIRES explicit listing; the skillpack's own install.sh says so. (The old "use `skills/**`" advice is dead.)++```bash+# Stage dir: ROOT SKILL.md + skills/<name>/SKILL.md per skill (user + dev + publish),+# package.json {name:"adom-desktop-<bridge>-bridge", version, discovery_triggers,+#               files:[ every path, explicitly ], scripts:{install,uninstall}}.+# TWO traps that still bite — verify each with `tar tzf` before publishing:+#  1. ⚠ pkg pack/publish HONORS the stage dir's .gitignore — a gitignored skills/ ships+#     a 3 MB tarball with ZERO skills. Set .gitignore aside across pack+publish, restore after.+#  2. ⚠ Keep the tarball LEAN — NO release zip, NO src/ tree, NO hero PNGs (it was 16 MB+#     before guards). Skills + docs only.+adom-wiki pkg pack                       # then: tar tzf <tgz> → every skills/<name>/SKILL.md+                                         #   listed in files[] present, ZERO binaries+adom-wiki pkg publish --org adom         # publishes adom/adom-desktop-<bridge>-bridge+```++**What `skill_count` on your page actually shows** (measured 2026-07-03): pup — a `type:app` page whose PKG ships 3 `SKILL.md`s — shows `skill_count: 3` (correct by design: it counts the skills your published PKG carries). But the reference `adom-wiki-skillpack` — `type:skill`, 15 `SKILL.md`s in its repo — shows `skill_count: 0`, so the metric is NOT "SKILL.md files in the repo" and looks inconsistent across page types. Treat your own pkg's count as the meaningful one; don't chase parity with skill-type pages (wiki-side question, flagged).++A container gets it **without anyone asking**:+- `bridge_install` returns `skillPkg` + `installSkill` (AD derives `adom/<slug>` from your `docs`) and the `_hint` tells the AI to run it.+- **`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.+- The wiki page's **Skills tab** lists the skills your pkg ships, so the page itself advertises them.++**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.++---++## Artifact 3 — DISCOVERABILITY: `discovery_triggers` so a plain request finds you++The 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.++```bash+adom-wiki discover triggers adom/adom-desktop-<bridge>-bridge --add "open my pcb" "export gerbers"  # add triggers+adom-wiki discover preview --json "open my pcb design"          # what would surface + the SCORE + WHY+adom-wiki discover find    "screenshot my website"              # the live find an AI runs+```++**How scoring actually works (measured 2026-06-30 — know this or you'll guess wrong):**+- `score = 3×(distinct triggers matched) + 1×(tags) + 1×(brief/text)`. **Trigger matches dominate (3× each);** tags/title/brief barely move it.+- **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.)+- **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.++**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.++**VET it — never ship triggers you didn't test:**+1. Write **5-7 queries a typical user would type** to get your tool (these double as your page `sample_prompts`).+2. `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.)+3. 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.++**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.++- **`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.+- **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`)."*++### The end-to-end flow your three artifacts enable++User says *"show me my app in pup"* → the AI:+1. `adom-wiki discover find "show me my app in pup"` → your bridge surfaces (Artifact 3).+2. `adom-desktop bridge_list` → is your bridge installed in AD? If not, `bridge_install {your manifestUrl}` (Artifact 1).+3. 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).+4. Drive your verbs — guided by the **rich hints you return** (the principle at the top).++---++## Prewarm — make the first call instant++A 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:++- **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.+- **Idempotent warm-up.** Make spawning + a no-op health/setup pass idempotent so a background prewarm is a no-op once warm.+- **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.+- **`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.++---++## Card hero + update cost — two gotchas that bite++**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:+- Ship a **1.6-aspect** hero (≈**2000×1250**), full-bleed — like pup & kicad. It fills the banner cleanly.+- 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.)+- 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.++**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:+- **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.+- 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.+- 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*.++---++## Cold-start tiers, host-app detection & the `bridge_readiness` probe (v1.9.47)++A 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.++**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).++**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:+- **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."+- **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.++**Bridge install LEVELS — how eagerly each bridge gets onto the machine. The AI ALWAYS does the install; the level only sets the TIMING/CONSENT:**++| Level | Bridge(s) | Behavior |+|---|---|---|+| **0 · Built-in / MUST** | Hydrogen Desktop (`hd`) | The platform itself — the editor AD lives in. Always present; not "installed" by AD. |+| **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. |+| **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). |+| **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. |+| **4 · Explicit third-party** | any `bridge_install`'d bridge | The user opts in by installing it. |++The 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).++**Declare both in `bridge.json`:**+```json+"detect": {                          // Tier 2 — how AD finds your HOST APP (detection only)+  "hostApp": "KiCad",+  "appPathsExe": "kicad.exe",        // optional: Windows App Paths registry lookup (most reliable)+  "paths": {                         // optional: candidate paths, %VAR% expanded, one * = a version dir+    "windows": ["%ProgramFiles%\\KiCad\\*\\bin\\kicad.exe"],+    "macos":   ["/Applications/KiCad/KiCad.app"],+    "linux":   ["/usr/bin/kicad"]+  }+},+"prewarm": { "assets": ["chrome-for-testing"] }   // Tier 1 — heavy runtime assets AD prewarms + reports+```+A 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).++**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.++Your 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.++---++## The SKILL SET you should ship (not one SKILL.md — a small library)++Don't cram everything into one file. Ship **three kinds** of skill, each with a different reader:++1. **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.+2. **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.+3. **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.)++Ship ALL of them — user + dev + publish — in your **pkg**, each as a real `skills/<name>/SKILL.md` (the SKILLPACK layout, Artifact 2). **Scoping is by AUDIENCE, not filename:** dev/publish skills carry `user-invocable: false` + a description that opens DEVELOPER-only, so everyday users are never offered them while your maintainer thread still installs the full set. (The old "name dev docs `dev-skills/*.md` so they don't ship" rule is DEAD — pup migrated to the skillpack layout 2026-07-03 and its `skill_count: 3` is by design.)++**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.++---++## USER-skill conventions — what every consumer skill must teach (from pup's shape)++These 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:++1. **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).+2. **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.)+3. **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).+4. **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.+5. **"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`.+6. **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.+7. **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.+8. **"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.++---++## Verb contract (quick reference)++- **Naming:** every verb is `<name>_<verb>` (`blender_render`, `blender_status`). AD routes any verb with your prefix to you.+- **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`.+- **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}`.+- **`<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.+- **Keyboard input: route through AD's `desktop_press_key` — do NOT reimplement chords.** AD's key verb already does full modifier chords (`"shift+s"`, `"ctrl+shift+p"`, `"alt+f4"` — modifiers held via SendInput, key tapped, modifiers released in reverse), foregrounds the target window first (`{window|titleContains|hwnd}`) so CEF/Qt apps like Fusion receive it, and every bridge inherits it via the `/command` desktop passthrough (`{"command":"press_key", ...}` on the desktop namespace). A bridge-local `*_send_key` that parses only single keys (the Fusion bridge's `fusion_send_key` circa v1.6.x — `"shift+s"` → "Unknown key") duplicates AD's work and drifts; keep such a verb only for SendMessage-to-exact-hwnd cases AD's focus-first model can't cover, and say so in its `describe`.+- **Host-app-optional verbs (v1.9.76):** declare `detect.hostAppOptionalVerbs` (bare names, e.g. `["readiness","describe"]`) in `bridge.json` so AD forwards those verbs to your running bridge even when your host app is absent — AD's built-in read-only set {readiness, describe, get_app_state, status} is always forwarded. And declare **`detect.installVerb`** (e.g. `"kicad_upgrade"`) so `bridge_readiness` recommends YOUR installer over the generic winget fallback.++## Health + status chip++Your `/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.++## Lifecycle verbs (AD gives these to USERS — know them to guide users)++`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.++---++## Publish checklist (the whole modern flow)++1. **Page** — `adom-wiki page create adom/adom-desktop-<bridge>-bridge` with a hero + an **instructional `brief`** + **`discovery_triggers`**.+2. **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.+3. **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).+4. **Discovery** — `discover triggers` to confirm; `discover preview "<a real user phrase>"` to confirm you surface.+5. **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.+6. **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.++**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 **— or when THIS SDK doc itself changes —** AD posts a migration notice to **each** of their wiki discussions (`adom-desktop-{kicad,fusion,blender,puppeteer}-bridge` + `adom-browser-extension`). **Author-facing takeaway: watch your bridge's own wiki DISCUSSION/issues list — that's where an "SDK changed, please re-audit against the billboard" notice lands, and where you reply with your plan or blockers.** It's the only channel AD uses to reach you; AD never edits your repo.