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, cold-start self-heal, bridge readiness internals, bridge release, bridge manifest, updateManifestUrl, pkg vs release vs bundled."

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 "":

  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 skillPkgpkg installs 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 moduledetectDependency() (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_founddesktop_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_founddesktop_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

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.