← Commit history

Templates are now fetchable from this page (the old pointer went to AD's private repo); new human sub-readme on skill propagation; caller-identity in the templates

John Lauer ·877aa7dc24 ·3mo ago ·parent 1ad2f2d
8 files changed +873−3
README.md+14−1
@@ -650,7 +650,20 @@ Don't cram everything into one file. Ship **three kinds** of skill, each with a  **Only the USER skill(s) go in your pkg** (Artifact 2), each a real `skills/<name>/SKILL.md`. **The DEV + PUBLISH skills stay in your source repo** — a developer gets them by cloning to edit the bridge; they are NOT in the pkg's `files[]`, and you do NOT "scope" them into the pkg with `user-invocable:false` (that still ships them and bloats every container install). Open-vs-closed source doesn't change this — it only sets whether your repo's dev/publish files are publicly readable. -**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.+**Start from the templates — don't write these from scratch.** Fill-in-the-blanks files for each of the three skill kinds, generalized from pup's battle-tested set. **Fetch them from THIS page** (they used to be cited as a path inside AD's own repo, which is private, so the pointer was unfollowable for exactly the cloud authors it addressed — fixed 2026-07-25):++```bash+BASE=https://wiki.adom.inc/api/v1/pages/adom-desktop-bridges/files+curl -fsSL $BASE/bridge-dev-template.md        -o dev-skills/<bridge>-dev.md+curl -fsSL $BASE/bridge-publish-template.md    -o dev-skills/<bridge>-publish.md+curl -fsSL $BASE/bridge-user-skill-template.md -o skills/<bridge>/SKILL.md+```++- **`bridge-dev-template.md`** — architecture, the rich-hints rule, caller-provenance handling (logging the thread, arbitrating sessions, forwarding on your own AD callbacks).+- **`bridge-publish-template.md`** — the whole release/pkg/discovery dance and every trap (the `.gitignore` set-aside, the lean-tarball rule, release-and-pkg-ship-together).+- **`bridge-user-skill-template.md`** — the consumer skill, already encoding all eight conventions below plus the `--ai-thread` identity line.++Copy the one you need, swap `<bridge>`/`<prefix>` for your names, delete what doesn't apply. **How your skills reach a container in the first place** (the runtime installs on the desktop, the skills install in the container, and the two travel by different routes): [`docs/bridge-skill-propagation.md`](https://wiki.adom.inc/api/v1/pages/adom-desktop-bridges/files/docs/bridge-skill-propagation.md) on this page.  --- 
SKILL.md+14−1
@@ -650,7 +650,20 @@ Don't cram everything into one file. Ship **three kinds** of skill, each with a  **Only the USER skill(s) go in your pkg** (Artifact 2), each a real `skills/<name>/SKILL.md`. **The DEV + PUBLISH skills stay in your source repo** — a developer gets them by cloning to edit the bridge; they are NOT in the pkg's `files[]`, and you do NOT "scope" them into the pkg with `user-invocable:false` (that still ships them and bloats every container install). Open-vs-closed source doesn't change this — it only sets whether your repo's dev/publish files are publicly readable. -**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.+**Start from the templates — don't write these from scratch.** Fill-in-the-blanks files for each of the three skill kinds, generalized from pup's battle-tested set. **Fetch them from THIS page** (they used to be cited as a path inside AD's own repo, which is private, so the pointer was unfollowable for exactly the cloud authors it addressed — fixed 2026-07-25):++```bash+BASE=https://wiki.adom.inc/api/v1/pages/adom-desktop-bridges/files+curl -fsSL $BASE/bridge-dev-template.md        -o dev-skills/<bridge>-dev.md+curl -fsSL $BASE/bridge-publish-template.md    -o dev-skills/<bridge>-publish.md+curl -fsSL $BASE/bridge-user-skill-template.md -o skills/<bridge>/SKILL.md+```++- **`bridge-dev-template.md`** — architecture, the rich-hints rule, caller-provenance handling (logging the thread, arbitrating sessions, forwarding on your own AD callbacks).+- **`bridge-publish-template.md`** — the whole release/pkg/discovery dance and every trap (the `.gitignore` set-aside, the lean-tarball rule, release-and-pkg-ship-together).+- **`bridge-user-skill-template.md`** — the consumer skill, already encoding all eight conventions below plus the `--ai-thread` identity line.++Copy the one you need, swap `<bridge>`/`<prefix>` for your names, delete what doesn't apply. **How your skills reach a container in the first place** (the runtime installs on the desktop, the skills install in the container, and the two travel by different routes): [`docs/bridge-skill-propagation.md`](https://wiki.adom.inc/api/v1/pages/adom-desktop-bridges/files/docs/bridge-skill-propagation.md) on this page.  --- 
bridge-dev-template.mdadded+252
@@ -0,0 +1,252 @@+---+name: bridge-dev-template+user-invocable: false+description: "TEMPLATE / reference for building, publishing, and maintaining ANY Adom Desktop bridge (the cloud Claude/Codex thread that owns a bridge copies this into its own dev-skills/ and fills it in). NOT a user-facing skill — general users want the bridge's own user skill. Read this when editing bridge code, cutting a new version, publishing to the wiki, wiring a runtime/heavy-dependency cold-start self-heal, or understanding the pkg-vs-release-vs-bundled artifact model. Trigger words: bridge dev, build adom-desktop bridge, publish bridge, ship bridge, bridge_install, adom-desktop-<bridge>-bridge, cold-start self-heal, bridge readiness internals, bridge release, bridge manifest, updateManifestUrl, pkg vs release vs bundled."+---++<!-- Template — copy into your bridge's own dev-skills/, replace <bridge>/<prefix>/<Bridge Name>, keep the structure. -->++# bridge-dev-template — building & shipping an Adom Desktop bridge++The canonical source of truth for the `<bridge>` bridge is **its wiki repo**+(`wiki.adom.inc/adom/adom-desktop-<bridge>-bridge`), cloud-owned (like the kicad/fusion/puppeteer+bridges) so bridge iteration is decoupled from adom-desktop core. Read `CLAUDE.md` (ownership boundary)+and `PUBLISHING.md` (step-by-step recipe) in this repo alongside this skill.++## The THREE artifacts — know which is which (this is the #1 confusion)++A user installs TWO different things from this one page. They are not the same:++| Artifact | What it is | Who installs it | How |+|---|---|---|---|+| **pkg tarball** (Packages tab) | the **container-side Claude/Codex skills** (`SKILL.md` + `skills/*`) — docs that teach the cloud AI how to drive `<bridge>`. **No bridge runtime.** | a cloud **container** | `adom-wiki pkg install adom/adom-desktop-<bridge>-bridge`, or auto via AD's `sync_skills` |+| **release zip** (Releases tab) | the **bridge RUNTIME** (entrypoint + bridge code + `bridge.json` + `BRIDGE_VERSION`; heavy deps reconstructed on spawn) — the code that actually runs on the desktop | **Adom Desktop**, streamed into `bridges-cache\<bridge>` | `bridge_install {manifestUrl}` / `refresh_bridges` (via `updateManifestUrl`) |+| **bundled seed** (inside the AD installer) | a copy of the same runtime, shipped in the NSIS for first-run/offline | every AD install | ships with Adom Desktop; superseded by a newer cache copy (cache-over-bundled, numeric version) |++So: **the pkg is skills (container). The bridge is the zip (desktop) + the bundled seed.** Keep the+pkg description saying exactly that, or people think the tarball contains the bridge — it does not.++## How `<bridge>` reaches a container — auto-discovery → auto-install → verbs++The full chain when a user on a fresh container says *"<the phrase that triggers your bridge>"*:++1. **Discovery.** The wiki's `discover` index (fed by this page's `discovery_triggers` — which should lead+   with your bridge's natural phrases, e.g. its short name + the top user actions) matches the phrase. The+   container's regenerated `adom-wiki-discover` skill surfaces this page, so the AI proposes it.+2. **Auto-install the pkg.** The AI runs `adom-wiki skills install adom/adom-desktop-<bridge>-bridge`+   (a.k.a. `pkg install`). That extracts the tarball and runs `install.sh`, which drops `SKILL.md` ++   `skills/*` into **both `~/.claude/skills/` and `~/.codex/skills/`**. The `<bridge>` user skill is now loaded.+3. **The skill calls the bridge.** The user skill's `SKILL.md` tells the AI to run `adom-desktop <prefix>_*`+   verbs. AD routes every `<prefix>_*` verb to the `<bridge>` bridge.+4. **The bridge runtime.** It's already on the desktop: AD ships `<bridge>` as a **bundled seed** (and+   supersedes it from this page's release zip via `updateManifestUrl` / `bridge_install`). On a truly fresh+   PC the cold-start self-heal installs `<your runtime / heavy dependency>` on first use.++**Two install paths, keep BOTH healthy:**+- **Discovery path (above)** — works once `discovery_triggers` include the user phrases + the discover+  index is regenerated. This is the "user said `<bridge>` → AI installs the skill" path.+- **AD `sync_skills` path** — AD's daily `maybe_background_skill_sync` reads `bridge_list` → each bridge's+  `skillPkg` → `pkg install`s it into the container. For this to work AD must report+  `skillPkg = adom/adom-desktop-<bridge>-bridge` for your bridge. AD now derives that slug from the bridge's+  `docs` field automatically, **except** for any bridge hardcoded into `AD_CORE_SKILL_BRIDGES` in+  `bridge_registry.rs` (that list forces `skillPkg = null` for bridges whose skill historically lived in+  the AD-core pkg). If your bridge's skill ships from THIS page (not core), make sure your name is NOT in+  that list — if it is, removing it is an AD-core handoff item.++## Repo layout++- `src/` — the runnable bridge (what the release zip contains, at zip-root). Entrypoint + bridge modules,+  `bridge.json`, `BRIDGE_VERSION`, dependency manifest/lockfile, any html/icons the runtime needs.+- `SKILL.md` (root) — the **user** skill (`name: <bridge>`). Deployed to `~/.claude/skills/adom-desktop-<bridge>-bridge/`.+- `dev-skills/<bridge>-bridge-dev/` — THIS dev skill. `dev-skills/<bridge>-bridge-publish/` — the publish recipe.+  Both are **source-only** (in `dev-skills/`, NOT `skills/`): NOT in the installable pkg and NOT on the+  wiki Skills tab — you get them via `adom-wiki repo clone`. Everyday users only get the `<bridge>` user skill.+- `adom-bridge-<bridge>-manifest.json` (root) — the streaming manifest AD's `updateManifestUrl` points at.+- `package.json` (root) — the **adompkg descriptor** (slug/version/type/description) for `pkg publish`.+  NOT a language/runtime package descriptor (e.g. not the node `package.json`) — keep them distinct.+- `install.sh` / `uninstall.sh` — deploy `SKILL.md` + `skills/*` into the container's `~/.claude/skills/`.+- `page.json`, `README.md`, `*.png` heroes — the wiki page presentation (the heroes are gitignored so the+  container pkg stays lean — they're already uploaded as page assets).++## Cold-start self-heal — the architecture pattern (the reason a cloud-owned repo earns its keep)++On a fresh PC the installer pre-installs neither your bridge's language runtime nor its heavy dependency.+Without a self-heal, the first `<prefix>_<verb>` fails with a generic launch error + a dev string. The+pattern that fixes it — adapt the names to your runtime:++- **A detect/install/ready module** — `detectDependency()` (validate the ACTUAL artifact, not "a dir+  exists"), `installDependency()` (programmatic, **pinned to the exact version your code expects** — never+  a floating 'latest'/'stable', or the launch path won't find what it pinned against), an install-state+  singleton (dedup concurrent installs + report progress), `ensureReady()` (the gate — kicks off a+  background install and returns immediately), `readiness()` (powers the verb + the status chip).+- **The first action verb gates on readiness:** missing dependency → kick off a **non-blocking** install ++  return `{errorCode:"<dependency>_installing", statusVerb:"<prefix>_readiness", _hint:"poll then retry"}`.+  Install fail → a specific `<dependency>_install_failed` / `<dependency>_download_failed` error — **never+  silently continue into a doomed launch.**+- **A `<prefix>_readiness` verb** (poll) + a **`<prefix>_prewarm` verb** (install/warm without doing the+  user-visible action).+- **`/status` (or your `healthEndpoint`)** emits `led`/`summary`/`tooltip` truthfully (yellow installing,+  red failed, green ready). AD paints these verbatim.+- **The language runtime itself is handled by AD core BEFORE the bridge spawns** (e.g. a `node_not_found` →+  `desktop_install_node` flow) — NOT your bridge's job. The bridge can't bootstrap the very runtime it+  needs to run; AD installs the runtime, then spawns you.+- **The one thing the bridge can't do:** pre-warm before first use (it isn't running until the first call).+  AD/HD must call `<prefix>_prewarm` on embedded first-run — track that as a handoff item.++> **Worked example (pup's case):** the puppeteer bridge's runtime dependency is Chrome for Testing. Its+> module is `src/chrome.js` (`detectChrome` / `installChrome` via the bundled `@puppeteer/browsers` pinned+> to puppeteer's expected buildId / `ensureChromeReady` / `readiness`); `browser_open_window` is the gated+> first verb (CfT path only; its native-drive path skips the gate), returning+> `chrome_for_testing_installing`; and it exposes `browser_readiness` + `browser_prewarm`. Node is the+> language runtime AD core installs first (`node_not_found` → `desktop_install_node`). That's the concrete+> shape of every bullet above.++## Caller provenance: knowing WHICH AI thread asked++A typical user has ~20 AI conversations pointed at one desktop. Since AD v1.9.180 every relayed command+carries the identity of the conversation that made it, and AD hands that identity down to your bridge.+(Full contract: AD's `adom-desktop-caller-identity` skill.)++**1. What you receive for free.** AD stamps four headers on every request it dispatches to your bridge.+No manifest field, no opt-in, nothing to declare:++| Header | Carries |+|---|---|+| `X-Adom-Caller-Thread` | the AI conversation/tab name, e.g. `chip-fetcher tab 3` |+| `X-Adom-Caller-Container` | the container that thread runs in |+| `X-Adom-Caller-Reason` | the per-call justification, when the caller supplied one |+| `X-Adom-Caller-Delegate` | present when the call itself came second-hand (another bridge or HD relayed it) |++Read them at the top of your request handler, and **always code for absent** (`|| 'unidentified'`): a local+CLI call, an older caller, or your own internal timer will have none.++**2. Log the thread.** Highest value per line of code in this whole section, and it is three lines:++```js+const thread = req.headers['x-adom-caller-thread'] || 'unidentified';+log(`[verb] <prefix>_eval session=${sessionId} caller="${thread}"`);+```++Why it matters: `[verb] <prefix>_eval session=rev caller="chip-fetcher tab 3"` makes an interference report+reconstructable (two tabs were driving one session, and the log says which did what, when). A bare+`[verb] <prefix>_eval session=rev` never can, no matter how carefully anyone reads it afterwards.++**3. Arbitrate, but only if your bridge owns sessions or resources an agent can own.** If two threads can+fight over the same window, session, document, or device:++- Record `ownerThread` at creation time (the thread header on the verb that created it).+- When a **different** thread arrives, warn loudly in the response (`_hint` / a `warning` field). Do not+  fail silently, and do not quietly let the second thread stomp the first.+- **Refuse** only for destructive ops (close, reset, delete, overwrite). Reads and ordinary drive verbs+  should warn and proceed.+- **Always name the current owner** in the response. The second agent can then go coordinate ("session+  `rev` belongs to `chip-fetcher tab 3`") instead of guessing or forcing.++**4. REQUIRED: forward the chain on your own AD callbacks.** When your bridge calls an AD verb+(`desktop_screenshot_window`, `desktop_set_window_identity`, `notify_user`, `desktop_taskbar`) while+carrying out a verb an AI asked for, you are acting **on that thread's behalf**. Echo the three headers you+received and **add your own `X-Adom-Caller-Delegate: <bridge>`**. If you don't, the chain dies at your+doorstep: the user's Activity Log credits your bridge for work someone else asked for, and an approval toast+asks them to authorize a nameless component. Forward it and the user sees+`chip-fetcher tab 3 (via <bridge>)`, which is the whole point.++```js+// node. `incoming` is the request AD dispatched to your verb handler.+const FORWARD = ['thread', 'container', 'reason'];+const headers = {+  'Content-Type': 'application/json',+  'X-Adom-Bridge-Token': process.env.ADOM_BRIDGE_TOKEN,+  'X-Adom-Caller-Delegate': '<bridge>',     // name YOURSELF; never forward this one+};+for (const part of FORWARD) {+  const v = incoming.headers[`x-adom-caller-${part}`];+  if (v) headers[`X-Adom-Caller-${part[0].toUpperCase()}${part.slice(1)}`] = v;+}+await fetch(`${process.env.ADOM_DIRECT_API_URL}/command`, {+  method: 'POST', headers,+  body: JSON.stringify({ command: 'desktop_screenshot_window', args: { hwnd } }),+});+```++```python+# python, same shape+FORWARD = ("thread", "container", "reason")+headers = {+    "X-Adom-Bridge-Token": os.environ["ADOM_BRIDGE_TOKEN"],+    "X-Adom-Caller-Delegate": "<bridge>",   # name YOURSELF+}+for part in FORWARD:+    value = incoming_headers.get(f"x-adom-caller-{part}")+    if value:+        headers[f"X-Adom-Caller-{part.capitalize()}"] = value++requests.post(+    f"{os.environ['ADOM_DIRECT_API_URL']}/command",+    headers=headers,+    json={"command": "notify_user", "args": {"title": "<Bridge Name>: export finished"}},+)+```++When you act on your **own** behalf (a health poll, a timer, a cleanup sweep), do NOT forward a stale+identity from some earlier request. Send an explicit caller block in the args instead, which always beats+the forwarded headers:++```json+{ "caller": { "aiThread": "<bridge> bridge (self)", "containerName": "local" } }+```++**5. The trust rule, stated plainly: these values are SELF-ASSERTED.** They are for attribution, logging,+UX, and arbitration hints. They are **never** authorization and never a security boundary. AD records and+displays them; it cannot verify them, and neither can you. `if (thread === "admin-tab") allow(...)` is a+hole, not a check.++## Publishing (the wiki has TWO storage layers — don't mix them)++Full recipe in `PUBLISHING.md`. The load-bearing rules:++1. **The bridge `.zip` → a RELEASE asset** (`adom-wiki release create` + `release upload`). NEVER+   `repo push` a `.zip` — it's gitignored and silently skipped; the manifest URL would 404.+2. **The manifest JSON + source → the git repo** (`adom-wiki repo push`). The manifest's `url` is the+   RELEASE download URL; its `sha256`/`size` MUST match the served asset (releases preserve your sha;+   re-download to confirm or `bridge_install` rejects it).+3. **The skills → a PKG** (`adom-wiki pkg publish`). The pkg description (root `package.json`) must say+   "container-side skills." `pkg pack` honors `.gitignore` for exclusion (no separate ignore file) — that's+   why the heroes are gitignored, to keep the tarball lean. Set per-version notes with `adom-wiki pkg notes`.+4. **Version display gotcha:** a RELEASE shows up in `adom-wiki pkg info` as a row with `type:null` (a+   wiki UI conflation). The real pkg rows have `type:"app"`. Use `release list` for true releases.+5. **`bridge.json` + `BRIDGE_VERSION` lockstep**, and keep the version **>** the AD bundled seed so+   cache-over-bundled wins.+6. **Declare `updateManifestUrl` in your `bridge.json`** (the full URL of this page's manifest) so a+   user's AD re-polls THIS page for updates — and the same value lives in the bundled seed AD ships, so a+   fresh install auto-updates with no user opt-in. A republish that drops it makes AD fall back to the+   legacy convention and stop seeing your updates.++## Testing a new version on a real desktop++```bash+adom-desktop targets    # multiple desktops → --target <name> is required+adom-desktop --target <name> bridge_install '{"manifestUrl":"https://wiki.adom.inc/api/v1/pages/adom-desktop-<bridge>-bridge/files/adom-bridge-<bridge>-manifest.json","force":true}'+adom-desktop --target <name> <prefix>_readiness '{}'   # proves the new code is live+```++- **The language runtime is missing on the box** → `<prefix>_*` returns the runtime-not-found error from+  AD core *before* the bridge spawns (correct). That's a genuine cold-start demo of the FIRST link, but it+  can't exercise your self-heal module (there's no runtime to run it).+- **A box WITH the runtime but WITHOUT the heavy dependency** is the only place to see the full dependency+  self-install live (`<prefix>_<verb>` → `<dependency>_installing` → poll `<prefix>_readiness` → succeeds).+  Most dev boxes already have the dependency, so `readiness()` just returns `ready:true`.+- The streamed zip is **source-only**; AD reconstructs heavy deps in the cache on first spawn (ship+  prebuilt native deps where possible). The bundled seed already has its deps materialized. **Don't put+  reconstructable dependency trees (node_modules, venvs, vendored build output) in the zip.**++## Don't++- Don't edit the bundled seed in the adom-desktop repo (`plugins/<bridge>`) as if it's live, and don't run+  `release-bridge.sh <bridge>` from the adom-desktop repo — that clobbers this cloud-owned page. (The+  adom-desktop release script hard-refuses the cloud-owned bridges; the rule is "don't try.")+- Don't ship the bridge runtime in the pkg tarball or the skills in the release zip. Two artifacts, two homes.+- Don't pin your heavy dependency to a floating channel ('latest'/'stable') — pin to the exact version your+  code launches against, or the install and the launch disagree.+- Don't gate any bridge behavior on a caller's `aiThread` / `X-Adom-Caller-Delegate` value, and don't drop+  the `X-Adom-Caller-*` headers on your own AD callbacks. The first is a security hole; the second breaks+  the user's attribution chain at your bridge.
bridge-publish-template.mdadded+255
@@ -0,0 +1,255 @@+---+name: bridge-publish-template+user-invocable: false+description: "TEMPLATE — the canonical, battle-tested recipe for publishing a new version of ANY Adom Desktop bridge to wiki.adom.inc, plus every gotcha hit live. Copy into your own bridge's dev-skills/, replace the <bridge>/<prefix> placeholders, and follow it. NOT for general users. Read before cutting a release: which artifact goes where (pkg=skills, release=bridge zip, repo=manifest+source), version-lockstep (kept above the AD bundled seed), the `files` allowlist + `skills/**` glob trap, keeping the tarball lean (NO zip / NO src / NO heroes), keeping dev/publish skills SOURCE-ONLY (not SKILL.md), deploying skills to .claude AND .codex, scrubbing retired-wiki host URLs, user-first discovery triggers, sha matching, and verify steps. Trigger words: publish bridge, ship bridge, release bridge, pkg publish, repo push, adom-wiki release, bridge manifest, bridge version bump, no zip in tarball, dev skill source only, scrub old wiki url, codex skills, discovery triggers, adom-desktop bridge publish template."+---++# bridge-publish-template — shipping a new `<bridge>` bridge version++> **Template — copy into your bridge's own `dev-skills/`, replace the `<bridge>`/`<prefix>` placeholders, follow the steps.**++This is the step-by-step ship recipe for a cloud-owned Adom Desktop bridge living at+`wiki.adom.inc/adom/adom-desktop-<bridge>-bridge`, plus the traps that cost real time. Companion docs in+your repo: a `*-dev.md` dev skill (architecture / ownership), a terse `PUBLISHING.md`, and `CLAUDE.md`+(ownership boundary).++Placeholder legend — replace consistently throughout before you ship:++| Placeholder | Meaning | Example (Puppeteer bridge) |+|---|---|---|+| `<bridge>` | lowercase bridge id | `puppeteer` |+| `<Bridge Name>` | display name | `Puppeteer` |+| `<prefix>_` | verb prefix | `browser_` |+| `adom-desktop-<bridge>-bridge` | wiki slug | `adom-desktop-puppeteer-bridge` |+| `adom-bridge-<bridge>-manifest.json` | streaming manifest filename | `adom-bridge-puppeteer-manifest.json` |+| `adom-bridge-<bridge>-v<version>.zip` | release zip filename | `adom-bridge-puppeteer-v1.1.1.zip` |+| `<your bridge's files>` | the runtime source files | `server.js`, `chrome.js`, `bridge.json` |+| `<your runtime>` | the language/runtime AD reconstructs | `node` + `node_modules` |++## What goes WHERE — three artifacts, three homes (memorize this)++| Artifact | Home | Command | Carries |+|---|---|---|---|+| **bridge runtime `.zip`** | a **Release** asset | `adom-wiki release upload` | `src/` at zip-root (`<your bridge's files>` — the thing AD runs). |+| **streaming manifest JSON** | the **git repo** | `adom-wiki repo push` | points at the release zip URL + its sha256/size |+| **container skills tarball** | a **Package** | `adom-wiki pkg publish` | `SKILL.md` + `skills/*` (the Claude/Codex skills). NO runtime. |++> The `.zip` is a RELEASE, never a git file: `*.zip` is gitignored and `repo push --files X.zip`+> reports `ok` but silently skips it (the URL then 404s). Binaries → `release upload`.++## Auth (once per container)++```bash+export ADOM_WIKI_TOKEN=$(cat /var/run/adom/api-key 2>/dev/null)   # container default; or session.json's login session_token+adom-wiki whoami    # MUST show a real user (e.g. [email protected]). Strict-auth rejects the shared "Developer" token.+```+`ADOM_WIKI_TOKEN` is the user's **login `session_token`** — the container default lives at+`/var/run/adom/api-key`; otherwise pull it from `~/.adom/session.json`. Every mutation needs a real+`--changelog` / `-m` (≥10 chars, ≥2 words) or the server 400s.++## The recipe++### 1. Bump version IN LOCKSTEP, and keep it > the AD bundled seed++```bash+printf '1.1.1' > src/BRIDGE_VERSION+sed -i 's/"version": "1.1.0"/"version": "1.1.1"/' src/bridge.json    # bridge runtime+sed -i 's/"version": "1.1.0"/"version": "1.1.1"/' package.json       # adompkg descriptor (the pkg)+```+`src/BRIDGE_VERSION` and `src/bridge.json` MUST match (AD uses `BRIDGE_VERSION` to detect a stale running+process). Keep the number **above** the seed in `adom-desktop/plugins/<bridge>` so cache-over-bundled wins.++### 2. Build the bridge zip (source-only, `src/` at zip ROOT — no top-level dir)++`zip` may be absent → Python. Your `<your runtime>` deps (e.g. `node_modules`) are NEVER included (AD+reconstructs them on spawn; the seed ships them; a fat zip would blow wiki limits):++```python+import zipfile, os, hashlib+root, out = "src", "adom-bridge-<bridge>-v1.1.1.zip"+with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as z:+    for dp, dirs, files in os.walk(root):+        for skip in ("node_modules","screenshots","profiles"):   # your runtime's heavy/generated dirs+            if skip in dirs: dirs.remove(skip)+        for f in files: z.write(os.path.join(dp,f), os.path.relpath(os.path.join(dp,f), root))+print("sha256:", hashlib.sha256(open(out,"rb").read()).hexdigest(), "size:", os.path.getsize(out))+# SANITY: assert your changed file(s) are inside before shipping.+```++> **Concrete pup example (so the commands aren't abstract):** the Puppeteer bridge skips+> `node_modules`/`screenshots`/`profiles`, and its sanity check asserts `chrome.js` + the change are+> inside the zip before upload. `node_modules` is excluded because AD `npm install`s it on spawn (an+> 88 MB zip would exceed wiki limits).++### 3. Publish the zip as a Release (releases PRESERVE your sha)++```bash+adom-wiki release create adom/adom-desktop-<bridge>-bridge 1.1.1 --title "..." --changelog "<≥2 words>"+adom-wiki release upload adom/adom-desktop-<bridge>-bridge 1.1.1 adom-bridge-<bridge>-v1.1.1.zip --json+# the response's assets[].sha256/size are what the manifest MUST carry. Then VERIFY the PUBLIC url:+curl -sL https://wiki.adom.inc/download/adom/adom-desktop-<bridge>-bridge/1.1.1/adom-bridge-<bridge>-v1.1.1.zip | sha256sum+```++### 4. Point the manifest at the release + push it (git repo)++Edit `adom-bridge-<bridge>-manifest.json` → `version`, `url` (the release download URL), `sha256`,+`size` (from step 3 — **must match the served asset or `bridge_install` rejects it**):++```json+{ "manifest_version": 1, "name": "<bridge>", "version": "1.1.1",+  "url": "https://wiki.adom.inc/download/adom/adom-desktop-<bridge>-bridge/1.1.1/adom-bridge-<bridge>-v1.1.1.zip",+  "sha256": "<the release asset's sha256>", "size": <bytes>,+  "verbPrefixes": ["<prefix>_"],+  "healthEndpoint": "/status", "statusVerb": "<prefix>_readiness",+  "updateManifestUrl": "https://wiki.adom.inc/api/v1/pages/adom-desktop-<bridge>-bridge/files/adom-bridge-<bridge>-manifest.json",+  "released_at": "<UTC ISO8601>" }+```++> Declare `updateManifestUrl` (the full URL of THIS manifest on your own page) in both the manifest and+> `src/bridge.json` so `refresh_bridges` / the auto-poll reach YOUR page instead of the legacy+> AD-owned convention. Then push:++```bash+adom-wiki repo push adom/adom-desktop-<bridge>-bridge \+  --files adom-bridge-<bridge>-manifest.json src/bridge.json src/BRIDGE_VERSION <your bridge's files> SKILL.md \+  -m "v1.1.1: <what changed>"+```++> `sha256`/`size` MUST match the served asset or `bridge_install` rejects it. Re-download to confirm:+> `curl -sL <download_url> | sha256sum`.++### 5. Publish the container skills as a pkg — keep it LEAN, and MIND THE TRAPS++```bash+adom-wiki pkg pack            # ALWAYS pack + inspect BEFORE publish+tar tzf *.tgz                 # confirm: SKILL.md + skills/*/SKILL.md + install.sh, and NOTHING heavy+adom-wiki pkg publish --org adom --version 1.1.1+adom-wiki pkg notes adom/adom-desktop-<bridge>[email protected] -m "Container skills: <what changed>"+```++The pkg description lives in the root **`package.json`** `description` — keep it explicit that this is+**container-side skills only; the bridge runtime is the Release zip + bundled in Adom Desktop.**++## Traps hit live (don't relearn these)++- **⛔ A RELEASE and its PKG must ship TOGETHER. Never leave a release as your MAX version row+  with no matching pkg tarball.** Both pillars share ONE version list, and the pkg resolver picks+  the **max satisfying row** and then fails if that row has no tarball, rather than skipping it or+  honoring `distTags.latest`. So publishing a release at `1.4.0` while your last pkg sat at `1.3.9`+  makes a bare `pkg install adom/<your-bridge>` **and every constraint install** (`@^1.3.0`,+  `@^1.4.0`) return 404, breaking every consumer at once. It can look fine for months, purely+  because your previous max row happened to exist as both. AD core hit this live (wiki `adom/wiki`+  #22) and it transitively broke unrelated installs.+  **If you cannot publish both in one session, hold the release.** Do NOT relabel an older tarball+  to close the gap: a stale skill pkg silently omits the very verbs the new release added, which is+  worse than the 404 because nothing reports it. Interim unblock for a stuck consumer: pin an+  explicit version (`adom/<your-bridge>@1.3.9`), which skips the constraint resolver.+- **`pkg pack` includes EVERYTHING by default** → a fat tarball (all the hero PNGs + `src/` runtime).+  Fix: the heroes are gitignored (`*.png`) since they're already page assets, AND `package.json` has a+  **`files` allowlist**. With both, the tarball drops to ~12 KB. (Pup's was 16 MB before the guards.)+- **`files` directory entries need a GLOB.** Listing `"skills"` (bare dir) silently includes ZERO skill+  files. Use **`"skills/**"`** (and/or the explicit `"skills/<name>/SKILL.md"`). Always `tar tzf` to+  confirm your skills actually made it in — a missing skill = an empty Skills tab.+- **`pkg pack` honors `.gitignore`, not a separate npmignore.** So you can't gitignore something you+  need in git (e.g. `src/`) to drop it from the pkg — use the `files` allowlist for that.+- **A RELEASE shows up in `adom-wiki pkg info` as a row with `type:null`** (wiki UI conflation). The real+  pkg rows are `type:"app"`. Use `adom-wiki release list` for true releases; don't think your pkg shipped+  just because a version appears in `pkg info`.+- **sha/size mismatch → `bridge_install` rejects the manifest.** Releases preserve the uploaded sha, so+  use the `release upload` response values verbatim and re-download to confirm.+- **The Skills tab + `skill_count` + `adom-wiki skills install` come from the GIT REPO, not the pkg.**+  The wiki indexes **every `SKILL.md`** in the page repo (root = main skill; `<dir>/SKILL.md` = sub-skills)+  — `page.skills` is null; it's a repo scan. So a file named `SKILL.md` ANYWHERE in the repo becomes an+  indexed, tab-listed, `skills install`-able skill. (This is *separate* from `pkg install`, which installs+  the pkg tarball governed by the `files` allowlist.) Corollary → see "Keep dev/publish skills source-only".++## Verify (every ship)++```bash+# manifest serves at the updateManifestUrl path:+curl -s https://wiki.adom.inc/api/v1/pages/adom-desktop-<bridge>-bridge/files/adom-bridge-<bridge>-manifest.json | jq .sha256+# anon-fetch the PUBLIC release zip URL and confirm the sha matches the manifest (curl 200 is NOT enough):+curl -sL https://wiki.adom.inc/download/adom/adom-desktop-<bridge>-bridge/1.1.1/adom-bridge-<bridge>-v1.1.1.zip | sha256sum+# live install + new verbs on a real desktop (multi-desktop → --target; `adom-desktop targets` lists them):+adom-desktop --target <name> bridge_install '{"manifestUrl":".../files/adom-bridge-<bridge>-manifest.json","force":true}'+adom-desktop --target <name> <prefix>_readiness '{}'+# bridge_list shows the new version + skillPkg; bridge_check_updates reaches your page; sync_skills pulls the user skill:+adom-desktop --target <name> bridge_list '{}'            # confirm version + skillPkg (adom/adom-desktop-<bridge>-bridge)+adom-desktop --target <name> bridge_check_updates '{}'   # wikiHosted:true, latest = your new version+adom-desktop --target <name> sync_skills '{}'            # installs the user skill pkg into the container+# render the page in pup + READ the screenshot. Confirm version + Skills tab.+```+> Server-only change → AD reaps + respawns the bridge from the new cache on the next `<prefix>_*` call.++## Keep dev/publish skills SOURCE-ONLY (not on the Skills tab, not installed)++Everyday users should get ONLY the `<bridge>` user skill — not the dev/publish skills. But the wiki indexes+every `SKILL.md` in the repo (above), so a dev skill named `SKILL.md` would show on the Skills tab AND+install via `skills install`. The fix: keep dev/publish skills as **source-only docs that are NOT named+`SKILL.md`** — they live in `dev-skills/` as `<bridge>-bridge-dev.md` / `<bridge>-bridge-publish.md`. Then+they are:++- **not indexed** (`skill_count` stays 1), **not on the Skills tab**, **not `skills install`-able**;+- still in git → available via `adom-wiki repo clone` (and the Files tab);+- excluded from the pkg too (the `files` allowlist lists only `SKILL.md` + `skills/**`).++`dev-skills/README.md` tells a developer how to load one as a live skill (copy it to+`~/.claude/skills/<name>/SKILL.md`). **Verify after publish:** `skill_count` should be **1** —+`curl -s https://wiki.adom.inc/api/pages/adom/adom-desktop-<bridge>-bridge | jq '.page.skill_count'`.++## Keep the pkg tarball LEAN — NO zip, NO src/, NO heroes++The pkg tarball must be the `<bridge>` user skill ONLY — never the bridge runtime zip, `src/`, or hero PNGs.+Guards: the `package.json` `files` allowlist (`SKILL.md`, `skills/**`, `install.sh`, `uninstall.sh`,+`README.md`, `package.json`) **plus** `.gitignore` (`*.zip`, `*.png`, `*.bundle`, `*.tgz`, the old+`*-source.tar.gz`). **ALWAYS** `adom-wiki pkg pack` then `tar tzf *.tgz` and confirm there is **no+`*.zip`, no `src/`, no `*.png`**. A lean tarball is ~10 KB; if it's megabytes, something heavy leaked in+(it was 16 MB before the guards). `rm -f *.tgz` after inspecting.++## Skills install to BOTH .claude AND .codex++`install.sh` deploys the user skill into **both `~/.claude/skills/` and `~/.codex/skills/`** (Codex only+if `~/.codex` exists); `uninstall.sh` removes from both. Codex is a real second agent skills home now —+don't ship an install.sh that only does `.claude`.++## Scrub retired-wiki URLs (the #1 fork/extract gotcha)++**The only wiki host is `wiki.adom.inc`.** Any `wiki-*.adom.cloud` host is RETIRED — as are the+`/static/apps/...` and `/wiki/apps/...` path formats. When you fork/extract a page, the inherited+`README.md` and the `page.json` `readme` body carry OLD URLs (this bit pup live — a dead `static/apps`+zip link). Before every publish:++```bash+grep -rnI 'adom\.cloud\|/static/apps\|/wiki/apps' . --exclude-dir=node_modules+```++Fix every hit (README + the `page.json` `readme` body are the usual culprits), then VERIFY the **live**+entity (not just local):++```bash+curl -s https://wiki.adom.inc/api/pages/adom/adom-desktop-<bridge>-bridge | grep -c '\.adom\.cloud'   # want 0+```++Also `repo rm` any stale source-mirror artifacts left on the page (old `*-source.tar.gz` / `*.bundle`).+(`https://adom.inc/fonts/...` is fine — that's the brand font CDN, not a wiki host.)++## Canonical wiki.adom.inc URL formats++- **Page:** `https://wiki.adom.inc/adom/<slug>`+- **File (served, anon):** `https://wiki.adom.inc/api/v1/pages/<slug>/files/<name>`+- **Release download:** `https://wiki.adom.inc/download/adom/<slug>/<ver>/<file>`+- **Sibling pages:** `/adom/adom-desktop-bridges`, `/adom/adom-desktop-kicad-bridge`,+  `/adom/adom-desktop-fusion-bridge`, `/adom/adom-desktop`++## Discovery triggers — lead with everyday USER phrases++Lead `discovery_triggers` with everyday phrases, not dev jargon — the single most important is the bare+bridge name users actually say. The wiki **auto-generates** the discover snippet from `discovery_triggers`++ `discovery_pitch` (there is no separate "upload a snippet" — those two fields ARE the snippet). Set them+via `adom-wiki discover triggers <ref> --add ...` OR `page.json` `discovery_triggers`/`discovery_pitch`++ push. **Verify:** `adom-wiki discover preview "<a natural user phrase for your bridge>"` should rank this+page #1.++> **Concrete pup example:** the most important trigger is the bare word **`pup`**, plus `open in pup`,+> `open my app in pup`, `screenshot the page`, `record a browser window`. `adom-wiki discover preview+> "open my app in pup"` must rank the Puppeteer page #1.
bridge-templates-README.mdadded+35
@@ -0,0 +1,35 @@+# Bridge skill templates++Fill-in-the-blanks starting points for the **three kinds of skill** every Adom Desktop+bridge ships (see [`../BRIDGE_SDK.md`](../BRIDGE_SDK.md) → "The SKILL SET you should ship").+Generalized from the Puppeteer (pup) bridge's battle-tested set.++| Template | For whom | Goes where |+|---|---|---|+| [`bridge-dev-template.md`](bridge-dev-template.md) | whoever **edits** the bridge | your wiki page, source-only as `dev-skills/*.md` |+| [`bridge-publish-template.md`](bridge-publish-template.md) | your **publish/release** thread | your wiki page, source-only as `dev-skills/*.md` |+| [`bridge-user-skill-template.md`](bridge-user-skill-template.md) | a **cloud container** driving your verbs | your `adom-wiki pkg` tarball as `SKILL.md` |++## How to use++1. Copy the template you need.+2. Replace `<bridge>` (your bridge's slug, e.g. `blender`) and `<prefix>` (your verb prefix,+   e.g. `blender_`) everywhere.+3. Delete sections that don't apply; fill in your verbs.++**Keep the caller-provenance sections.** The dev template's "Caller provenance" section (the+`X-Adom-Caller-*` headers you receive, and the ones you must forward on your own AD callbacks) and the+user template's "Identify yourself on every call" section are contract, not decoration: AD refuses an+unidentified relayed call with `errorCode: caller_identity_required`, and a bridge that drops the headers+breaks the user's attribution chain. Full detail lives in AD's `adom-desktop-caller-identity` skill.++## The two naming rules that bite++- **Only the USER skill may be named `SKILL.md`.** The wiki indexes EVERY `SKILL.md` in your+  pkg tarball as an installable, Skills-tab-listed user skill — so the DEV + PUBLISH skills must+  be named `dev-skills/*.md` (source-only on your page), or every user who installs your pkg also+  installs your dev/publish docs.+- **THE ONE PRINCIPLE:** the AI almost never reads your on-disk skill — it reads your verb+  **OUTPUT**. The templates exist for the human author and the publish flow; your real leverage is+  the `_hint`/`_next`/`related`/`pitfalls` you return on **every** verb response. The+  user-skill template's eight conventions all reinforce this.
bridge-user-skill-template.mdadded+172
@@ -0,0 +1,172 @@+---
+name: bridge-user-skill-template
+user-invocable: true
+description: "TEMPLATE for an Adom Desktop bridge's CONSUMER skill — copy it, then replace <bridge>/<Bridge Name>/<prefix> and the verb glosses. It teaches a cloud Claude/Codex AI how to DRIVE <Bridge Name> on the user's desktop via the adom-desktop CLI: <one-line value prop — what the bridge controls and why>. Includes a FIRST-USE / COLD-START warm-up section so the AI doesn't panic on a fresh machine. Keep a trigger-words line like this one so the skill activates. Trigger words: <bridge>, <Bridge Name>, <prefix>_<verb1>, <prefix>_<verb2>, <prefix>_readiness, <prefix>_prewarm, <2-3 plain-English phrases a user would say, e.g. 'open in <bridge>', '<bridge> screenshot'>."
+---
+
+> Template — copy into your bridge's pkg as `skills/<bridge>/SKILL.md`, replace `<bridge>`/`<prefix>`.
+
+# <Bridge Name> — <one-line capability summary> (with first-use warm-up)
+
+Drive <Bridge Name> on the user's desktop from the cloud via the `adom-desktop` CLI. All verbs are
+Bash commands (NOT MCP tools):
+
+```bash
+adom-desktop <command> '<json_args>'
+```
+
+## ⭐ FIRST-TIME / COLD-START — read this BEFORE you panic that "<prefix>_<verb> did nothing"
+
+On a brand-new machine (e.g. the user just installed the app that bundles Adom Desktop),
+**something <Bridge Name> needs may NOT be pre-installed by the installer** — a runtime, a large
+dependency, an external app, or a one-time download. The bridge handles this gracefully — your job is
+to **read the structured response and follow it, not to give up.**
+
+When you run a `<prefix>_<verb>` on a cold machine, one of these happens — handle each:
+
+| Response | What it means | What you do |
+|---|---|---|
+| `errorCode: "<dependency>_not_found"` | A required runtime/dependency isn't installed (the bridge can't start). | Run the bridge's documented install verb (e.g. `adom-desktop <prefix>_install_<dep> '{}'`), wait for it, then retry the original verb. |
+| `errorCode: "<resource>_installing"` (`installing:true`) | **A needed resource is missing; the bridge is auto-downloading/installing it now (one time).** This is EXPECTED on a fresh machine — NOT a failure. | **Poll `<prefix>_readiness` until `ready:true`**, THEN re-issue the exact same verb. You do NOT install anything yourself. Do NOT report failure to the user. |
+| `errorCode: "<resource>_install_failed"` / `<resource>_download_failed` | The download/install itself failed. | Read `readiness.lastError`. It's usually no network / a proxy / low disk — NOT your fault. Retry `<prefix>_prewarm`; if it persists, surface `lastError` to the user, and use any documented fallback mode. |
+| `ok:true` / a real session | Everything was already warm. | Proceed normally. |
+
+### The clean cold-start recipe
+
+```bash
+# 1) (optional but nice) Warm the bridge WITHOUT doing real work first:
+adom-desktop <prefix>_prewarm '{}'             # returns fast; kicks off any first-use install
+
+# 2) Poll readiness until ready (the bridge sets itself up):
+adom-desktop <prefix>_readiness '{}'           # → {ready:false, installing:true, installProgressPct:42, ...}
+#    ...wait a few seconds, poll again until → {ready:true, ...}
+
+# 3) Now do the real work — it's instant:
+adom-desktop <prefix>_<verb> '{"sessionId":"<id>", ...}'
+```
+
+`<prefix>_prewarm` and `<prefix>_readiness` are the two cold-start verbs. `<prefix>_prewarm '{"wait":true}'`
+blocks until setup finishes instead of returning immediately. **Always surface a verb's `_hint`
+field verbatim to the user when something isn't ready** — it tells you (and them) exactly what's happening.
+
+> **Concrete example (the Puppeteer/pup bridge):** on a fresh PC, `browser_open_window` returns
+> `errorCode: "chrome_for_testing_installing"` (`installing:true`) because Chrome for Testing (~150 MB)
+> is auto-downloading. That is NOT a failure — you poll `browser_readiness` until `{ready:true}`, then
+> re-issue the same `browser_open_window`. Before the bridge handled this, a cold PC failed with a
+> generic "Failed to launch after 3 attempts" and the AI flailed. Don't reintroduce the panic; read
+> the structured response and follow the table.
+
+## How to Run Commands
+
+```bash
+adom-desktop <prefix>_<verb>      '{"sessionId":"myjob", ...}'   # do a unit of work
+adom-desktop <prefix>_screenshot  '{"sessionId":"myjob"}'        # capture current state (lossless PNG)
+adom-desktop <prefix>_status      '{}'                           # list sessions + readiness
+```
+
+**These are Bash commands, not MCP tools.** If your verbs are session-scoped, always pass `sessionId` —
+`'{}'` targets the *active* session, which may not be the one you mean.
+
+## Identify yourself on every call
+
+Say which AI conversation you are on every single call:
+
+```bash
+adom-desktop --ai-thread "<your conversation name>" <prefix>_<verb> '<json>'
+```
+
+`--container-name "<your container>"` is optional. Both are **global flags** like `--target`: they go
+before the verb and work on every verb. AD refuses an unidentified relayed call with
+`errorCode: caller_identity_required`, so this is not optional politeness, it is the contract.
+
+**Why it matters to you:** the user runs roughly 20 AI threads against this one desktop. Without a name,
+every thread looks identical to them: the Activity Log is an undifferentiated stream and an approval toast
+says only that "some AI" wants to do something. With a name, they can see which tab drove an action, and
+they can approve yours instead of denying everything. Your thread name is one of the two things they read
+before clicking Approve.
+
+- Pick a name **the user would recognise for this tab**: `chip-fetcher tab 3`, `kicad board review`,
+  `nightly wiki sync`. Never a UUID, never `session_1`, never `agent`.
+- It is **attribution, never authorization.** Nothing is gated on it, it grants you nothing, and you should
+  never tell a user that a thread name proves anything.
+- Do **not** `export ADOM_AI_THREAD`. That advice was withdrawn: roughly 20 threads share one container and
+  one `$HOME`, so the variable names the shell rather than the thread and every thread then reports the
+  same name (worse than no name, because it is confident and wrong). A non-interactive tool shell also
+  skips `~/.bashrc` past its `case $- in *i*` early return, so the export silently never runs at all.
+
+## Background by default — don't disrupt the user
+
+**<Bridge Name> is the AI's workspace, not the user's. Keep its windows/output OUT of the user's way unless they asked to watch.**
+
+- After opening/driving something for automation (a screenshot, a scrape, a debug loop), send it to the
+  back / leave it un-focused so it doesn't sit on top of what the user is doing (e.g. a
+  `<prefix>_lower_os_window` or equivalent).
+- **Never** force your window to the foreground unless the user must SEE it — a recording, a demo, or a
+  finished result you're explicitly showing them.
+- Use a **non-intrusive nudge** (flash the taskbar via a `<prefix>_alert_window`-style verb, or just
+  report in chat) for "it's ready / done" instead of stealing focus.
+- For long-running output the user WILL look at, a dedicated window is fine — but still don't re-raise
+  it on every update; nudge instead of grabbing focus.
+
+```bash
+adom-desktop <prefix>_<verb>          '{"sessionId":"job", ...}'
+adom-desktop <prefix>_lower_os_window '{"sessionId":"job"}'   # get it off the user's screen; keep driving headlessly
+# ...do work as needed (works while lowered/backgrounded)...
+adom-desktop <prefix>_alert_window    '{"sessionId":"job"}'   # non-intrusive "done" when you want their attention
+```
+
+## Core verbs
+
+| Command | Description | Key args |
+|---|---|---|
+| `<prefix>_readiness` | Cold-start probe: is the bridge set up / still installing? | none |
+| `<prefix>_prewarm` | Do first-use setup WITHOUT starting real work | `wait` (optional bool) |
+| `<prefix>_open_window` *(or your "start a session" verb)* | Spawn a new session/window | `sessionId`, `profile`, ... |
+| `<prefix>_<do-work>` | The bridge's primary action verb(s) | `sessionId`, ... |
+| `<prefix>_screenshot` | Capture current state (lossless PNG) | `sessionId`, `fullPage?` |
+| `<prefix>_status` | All sessions + readiness | none |
+| `<prefix>_lower_os_window` | Send the window to the back (keep it off the user's screen) | `sessionId` |
+| `<prefix>_raise_os_window` / `<prefix>_focus_window` | Bring to foreground (use sparingly — only to show the user) | `sessionId` |
+| `<prefix>_alert_window` | Flash the taskbar (non-intrusive attention) | `sessionId` |
+| `<prefix>_close_window` | Close one session + its window (destructive) | `sessionId` |
+| `<prefix>_close` | Close ALL sessions | none |
+
+Run `adom-desktop <prefix>_describe '{}'` for the full machine-readable verb catalog.
+
+## Sessions & persistence (if your bridge has sessions)
+
+If one session = one long-lived context (a window, a connection, a document), say so here, and tell the
+AI **how to reuse instead of re-open**:
+
+- New context entirely → `<prefix>_open_window` (or your start verb).
+- Reuse an existing one → drive it directly (a `<prefix>_navigate`/equivalent), don't open a second.
+
+**Run `<prefix>_status '{}'` before opening a new session** at the start of a conversation (especially
+after a context compaction) — if a session already matches what you're about to do, reuse it. Opening a
+duplicate leaves the user staring at two identical windows. Pick a **stable `profile`/identifier across
+runs** so any persisted state (logins, cookies, caches) survives — the user sets up once, reuses for
+months.
+
+## Verify what you did — `ok:true` is not enough
+
+Many start/action verbs return `ok:true` as soon as the operation *begins*, not when it *finished* /
+rendered. Sanity-check with the cheapest signal: a `<prefix>_eval`/read verb that reflects real state
+(fastest), or a `<prefix>_screenshot` when a value isn't discriminating. Don't report success to the
+user on `ok:true` alone for anything that can silently land on an error/empty state.
+
+## Key rules
+
+- **Always pass `--ai-thread "<your conversation name>"`** so the user can see which tab is driving their
+  machine. An unidentified relayed call is refused with `errorCode: caller_identity_required`.
+- **Always pass `sessionId`** for session-scoped verbs. Never `'{}'` when you mean a specific session.
+- **It's `adom-desktop <prefix>_<cmd> <json>`** — there is no top-level `<bridge>` subcommand.
+- **Surface `_hint` verbatim** whenever a verb reports it isn't ready or failed — it's written for both
+  you and the user.
+- **Reuse, don't reflexively close+reopen** — persisted state is keyed by `profile`/identifier; prefer a
+  reload/navigate over a destroy+recreate.
+- **Never broadly kill the underlying app/process** — use the bridge's per-session close
+  (`<prefix>_close_window`) so you don't nuke unrelated work.
+- **Document any per-verb gotchas** here (escaping rules, arg quirks, timeouts) — e.g. pup's
+  "no semicolons in `browser_eval` — use the comma operator or an IIFE."
+
+---
bridges-SDK.md+14−1
@@ -650,7 +650,20 @@ Don't cram everything into one file. Ship **three kinds** of skill, each with a  **Only the USER skill(s) go in your pkg** (Artifact 2), each a real `skills/<name>/SKILL.md`. **The DEV + PUBLISH skills stay in your source repo** — a developer gets them by cloning to edit the bridge; they are NOT in the pkg's `files[]`, and you do NOT "scope" them into the pkg with `user-invocable:false` (that still ships them and bloats every container install). Open-vs-closed source doesn't change this — it only sets whether your repo's dev/publish files are publicly readable. -**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.+**Start from the templates — don't write these from scratch.** Fill-in-the-blanks files for each of the three skill kinds, generalized from pup's battle-tested set. **Fetch them from THIS page** (they used to be cited as a path inside AD's own repo, which is private, so the pointer was unfollowable for exactly the cloud authors it addressed — fixed 2026-07-25):++```bash+BASE=https://wiki.adom.inc/api/v1/pages/adom-desktop-bridges/files+curl -fsSL $BASE/bridge-dev-template.md        -o dev-skills/<bridge>-dev.md+curl -fsSL $BASE/bridge-publish-template.md    -o dev-skills/<bridge>-publish.md+curl -fsSL $BASE/bridge-user-skill-template.md -o skills/<bridge>/SKILL.md+```++- **`bridge-dev-template.md`** — architecture, the rich-hints rule, caller-provenance handling (logging the thread, arbitrating sessions, forwarding on your own AD callbacks).+- **`bridge-publish-template.md`** — the whole release/pkg/discovery dance and every trap (the `.gitignore` set-aside, the lean-tarball rule, release-and-pkg-ship-together).+- **`bridge-user-skill-template.md`** — the consumer skill, already encoding all eight conventions below plus the `--ai-thread` identity line.++Copy the one you need, swap `<bridge>`/`<prefix>` for your names, delete what doesn't apply. **How your skills reach a container in the first place** (the runtime installs on the desktop, the skills install in the container, and the two travel by different routes): [`docs/bridge-skill-propagation.md`](https://wiki.adom.inc/api/v1/pages/adom-desktop-bridges/files/docs/bridge-skill-propagation.md) on this page.  --- 
docs/bridge-skill-propagation.mdadded+117
@@ -0,0 +1,117 @@+# Bridge skill propagation: how a bridge's docs reach the AI++A bridge is code that runs on your Windows desktop, right next to Adom Desktop. It drives+KiCad, a browser, Blender, whatever app it was written for. It has to run there, because+that is where the app is.++The AI that calls the bridge does not run there. It runs in a cloud container, and that+is where it looks for its local skills (its documentation), in `~/.claude/skills/` or+`~/.codex/skills/`.++So installing a bridge onto your desktop hands the AI a set of new abilities it has no+documentation for. The runtime and the docs are two separate things, they end up in two+separate places, and they get there by two different routes. This page explains how they+find each other.++## The two artifacts++Every bridge publishes two things to its own wiki page:++| | The release zip | The skills package |+|---|---|---|+| What it contains | Server code plus `bridge.json` | Text documentation only, a few KB |+| Where it goes | The desktop | The cloud container |+| How it gets there | `bridge_install {manifestUrl}` streams it, unzips it, and spawns it | `adom-wiki pkg install adom/<slug>` |+| Where it lands | Adom Desktop's bridge cache | `~/.claude/skills/` and `~/.codex/skills/` |++Same bridge, two destinations, two mechanisms. Neither one substitutes for the other.+Installing the runtime does not give the AI docs, and installing the skills does not give+your desktop anything to run.++## How a container finds out which skill it needs++This is the piece that connects the two halves.++1. A bridge declares `docs` in its `bridge.json`, pointing at its own wiki page.+2. Adom Desktop derives the skill package name, `adom/<slug>`, from that `docs` URL. It+   takes the last path segment. (It deliberately does not derive the name from the+   manifest URL, which is owner-qualified and would give the wrong answer.)+3. `bridge_list` returns, for each bridge, a `skillPkg` field (the package name) and an+   `installSkill` field (the exact, ready-to-run install line), plus a top-level+   `_skillsHint`.++So an AI in any container can ask your desktop what it has, and get back the literal+command that fixes its own missing documentation. It never has to guess a package name.++A `skillPkg` of null means there is nothing separate to install: the skill is already part+of the Adom Desktop core package, or the bridge does not ship one.++## The one command, and the self-healing++```bash+adom-desktop sync_skills+```++That collects every non-null `skillPkg` from every connected desktop and installs each+one. It is idempotent, so running it again costs nothing. It fans out across multiple+desktops and takes the union, so a container talking to two machines gets the skills for+both.++You do not normally have to run it. The CLI runs it opportunistically in the background,+at most once a day. That runs in containers only (the desktop host itself is skipped), and+`ADOM_SKILL_SYNC=0` opts out.++The daily run is what makes the awkward case work. A container that connects a week after+you installed a bridge, and that never asked your desktop to install anything, still ends+up with the right documentation, hands-off.++## Why bridge skills are not in Adom Desktop's own package++The cloud-owned bridges (KiCad, Fusion, Blender, Puppeteer, the browser extension) each+own their wiki page and their own skill package. Their docs are not bundled into the+adom-desktop core package, and that is deliberate.++Bundling would mean the docs go stale the moment a bridge ships an update, and it would+mean cutting an Adom Desktop release every time someone else's project changed a+paragraph. Each bridge ships its own skills on its own cadence, which is the only+arrangement that keeps a fast-moving bridge's documentation accurate.++## What does not belong in the skills tarball++The skills package is text and nothing else. Specifically, it should not contain:++- **Hero images or screenshots.** A container never renders them. One real bridge shipped+  a 1 MB hero in its tarball, which was pure download bloat on every single install.+- **The release zip.** That is the other artifact, and it goes to the desktop.+- **The source tree or any binaries.** A consumer container does not build the bridge.+- **Dev skills and publish skills.** Dev skills are for whoever edits the bridge, publish+  skills are for whoever releases it. Both stay in the source repo, where a developer who+  checks out the code gets them automatically. A consumer container has no use for either.++Only user skills ship. If you are looking at a bridge's tarball and it is measured in+megabytes, something is in there that should not be.++## What this looks like when it works++You install a bridge on your desktop, from the Bridges sidebar or with `bridge_install`.+The card appears, and the bridge is ready to run.++The AI in your container asks the desktop what is available. It gets back the bridge, and+alongside it, the install line for that bridge's skills. It runs that line. The skills+land in its own skills folder.++From that point on it drives the bridge's verbs with real documentation in front of it,+instead of guessing at argument names and reading error messages to find out what it got+wrong.++One thing to expect: the runtime on your desktop and the docs in your container can+legitimately sit at different versions, because they update on independent schedules. The+desktop's copy refreshes from the bridge's wiki page on a periodic sweep; the container's+copy refreshes when skills are synced. That is normal, and usually invisible.++## For bridge authors++If you are publishing a bridge rather than using one, the Bridge SDK on the+`adom-desktop-bridges` wiki page has the publishing detail: what belongs in each artifact,+how to declare `docs` so the skill package name resolves, and how to ship both halves+together.