---
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.

## 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.
