app
DEPRECATED: Bridge SDK (moved into Adom Bridge)
Public Made by Adomby adom
Bridge SDK guide: bridge.json schema, kind:python/node/exe, hello-python + hello-rust reference templates, packaging + lifecycle commands.
← Commit history
sync billboard to current Bridge SDK (Card hero + update cost, self-audit, install-on-request)
1 file changed
+317−302
README.md+317−302@@ -1,302 +1,317 @@-# 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. ++# 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. 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):++```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.++---++## 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 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.