name: adom-desktop-bridges-guide description: > Consumer guide to Adom Bridge bridges: what a bridge is, the Bridges sidebar cards, bridge status and the bridge LED colors (green/yellow/red/gray), bridge lifecycle (spawn, stable port, single instance, auto-respawn, duplicate collapse), how to install a bridge (bridge_install with a manifestUrl), bridge updates (refresh_bridges, bridge_check_updates, the 4-hour auto-sync from each bridge's wiki page), the bridge console and logs (bridge_log_read and the lifecycle log), bridge management verbs (bridge_list, bridge_info, bridge_detail, bridge_kill, bridge_pause, bridge_resume, bridge_readiness), and sync_skills for pulling each bridge's own skill package. Trigger words: bridge status, bridge LED, install a bridge, bridge_install, refresh_bridges, bridge console, bridge lifecycle, sync_skills, bridge updates, bridge_list, bridge_kill, bridge stuck, bridge offline, bridge not running.

Adom Bridge bridges: the consumer lifecycle guide

This is the USER/CONSUMER side of bridges: how they run, how you see their health, how they install and update, and how you manage them with verbs. Building your own bridge is a different doc: the Bridge SDK (skills/BRIDGE_SDK.md, published on the adom-desktop-bridges wiki page).

What a bridge is

A bridge is a small, independently-versioned separate process that Adom Bridge (the app) spawns, supervises, health-checks, and auto-updates. Each bridge owns a verb prefix (browser_* for the Puppeteer browser, kicad_*, fusion_*, nbrowser_* for your real signed-in browser, hd_*, plus any third-party prefix) and a small HTTP endpoint on loopback. The app relays verbs to the bridge verbatim and renders its health; the bridge validates its own args and answers with the same JSON shape on every path (relay, direct API, CLI). The app stays generic: it never bakes in app-specific logic.

The Mac install ships these bridges: Puppeteer, native-browser, KiCad, and Fusion 360 (+ APS), plus the Adom Hydrogen stub. Bundled seeds work offline on day one; the wiki is for updates, not first-install (see skills/FIRST_INSTALL.md).

The Bridges sidebar (what the user sees)

Each installed bridge is a mini-app card in the Bridge window's right sidebar: hero image (the bridge's own wiki billboard), a health LED, language and source badges, star count. A ? help icon carries the full tooltip; clicking a card opens a wide pop-out with a live console (the per-bridge log + command history), a ports/PID debug view, and all management actions (install/uninstall/kill/restart/pause/resume). + Bridge installs a new one by manifest URL; Check for updates is the on-demand sync button. (Bridge runs as a menu-bar agent; summon the window from the menu-bar item.)

LED colors: the bridge self-reports, the app renders

The app is a pure renderer of bridge health. A live bridge decides its own LED, short summary label, and hover tooltip on its health endpoint; the app paints them verbatim, re-polling about every 15 seconds.

LED Meaning
green Healthy. The bridge said so (or answered 2xx without self-reporting, which defaults to green).
yellow Degraded/warning, per the bridge itself (e.g. an upstream partially down).
red The bridge itself knows it is broken (e.g. it lost its upstream).
gray (offline) The app owns this ONE state: the health endpoint is unreachable. A not-running bridge reads as off, not failed. A dead bridge cannot self-report.

Lifecycle: one instance, stable port, supervised

  • Spawn on first verb. A bridge is auto-started on the first command of its prefix (e.g. the first browser_* call). No manual start.
  • Stable persisted port. Each bridge keeps ONE runtime port across respawns AND app restarts (persisted in ~/.adom/bridge-ports.json). External clients (a browser extension) keep dialing the same port. Ports are OS-assigned; never hardcode one. The verb prefix is the contract.
  • Single instance. Spawns are serialized behind a per-bridge lock; the app tracks every spawned PID and reaps a stale/wedged instance before respawning. A background sweep (every ~15 s) collapses duplicates: it scans for any process (tracked or not) bound to a bridge port, health-probes it, keeps the instance on the assigned port, and reaps the rest, so relay verbs never round-robin between two copies.
  • Auto-respawn for persistent bridges. spawn.persistent: true (surfaced in bridge_list) means a supervisor respawns the bridge on death, on its stable port. "Kill it to load new code" just brings it right back.

Install levels: how eagerly each bridge gets onto the machine

The AI always does the install; the level only sets timing and consent:

Level Bridge(s) Behavior
0 built-in Adom Hydrogen (hd) The platform itself; a manifest stub only, never installed by Bridge.
1 fundamental, pre-emptive Puppeteer (pup) Bundled seed; the app pre-installs its runtime (Node + Chrome-for-Testing) in the background on first boot.
2 recommended, proactive offer native-browser The desktop-bridge half is bundled (works extension-free day one); the in-browser extension is proactively offered and installs on a yes.
3 on-request host app KiCad, Fusion, ... The app detects the host app; it never pre-installs a multi-GB app unasked, and never tells the user to install it manually. The AI installs it the moment the user wants it.
4 explicit third-party any bridge_install'd bridge The user opts in by installing it from its wiki manifest URL.

Updates: each bridge from its OWN wiki page

Bundled seeds auto-update from their own wiki pages: on launch + every 4 hours, the app checks each registered bridge's updateManifestUrl; a strictly-newer version downloads (sha256-verified) into ~/.adom/bridges-cache/ and cache-over-bundled supersedes the seed. On demand:

adom-desktop bridge_check_updates            # READ-ONLY: current vs wiki latest per bridge
# -> {bridges:[{name,current,latest,updateAvailable,source,wikiReachable}], updatesAvailable:N}
adom-desktop refresh_bridges                 # apply updates for ALL bridges that are behind
adom-desktop refresh_bridges '{"name":"kicad"}'   # or just one

Version semantics: current = what the app actually runs = the newer of cache and bundled (numeric semver). After an update, the running server process still holds old code until it respawns; the version-aware liveness check forces a respawn on the next verb call, so you normally do not need to kill anything.

The key verbs

# Inventory: start here. Includes per-bridge skillPkg (adom/<slug>) and installSkill
# (a ready-made `adom-wiki pkg install` line), plus a top-level _skillsHint.
adom-desktop bridge_list

# Rich detail for ONE bridge (alias bridge_detail): log tail, resolved ports, full verb
# list, manifestUrl: and HONEST liveness: processLive is true only when the process is
# actually alive. (bridge_list's status is a cheap port-allocated heuristic; after a
# kill the stable port stays reserved by design, so trust bridge_info/bridge_detail.)
adom-desktop bridge_info '{"name":"puppeteer"}'

# Cursor-streaming remote tail -f of the bridge console. The tail also appends the app's
# own append-only lifecycle log (under an "===== AD bridge lifecycle =====" header) which
# SURVIVES respawns and records why a bridge started/stopped: an "[AD] REAPED ... as
# duplicates" line means the app killed it; an "[AD] spawned" line with NO preceding reap
# means the process exited on its own (a bridge-side crash).
adom-desktop bridge_log_read '{"name":"kicad"}'
adom-desktop bridge_log_read '{"name":"kicad","sinceOffset":41230}'

# Kill a bridge's processes (tracked PIDs + any image-verified orphan still bound to its
# port). Persistent bridges come right back on the same port.
adom-desktop bridge_kill '{"name":"native-browser"}'

# Pause/resume: a ROUTING flag only. Paused verbs fail fast with errorCode
# "bridge_paused"; the process is NOT killed.
adom-desktop bridge_pause '{"name":"fusion360"}'
adom-desktop bridge_resume '{"name":"fusion360"}'

# READ-ONLY readiness probe across every bridge: "what tools do I have / are they
# ready." NEVER spawns a bridge, never installs or downloads anything. Per bridge:
# {hostApp, hostAppInstalled, runtime, runtimeReady, assets, state} with state in
# ready | warming | needs-runtime | no-app, plus a top-level edaToolsInstalled list.
# Use THIS for "what's installed" questions, not a *_status verb (which may spawn).
adom-desktop bridge_readiness

# Install a third-party bridge from its wiki manifest URL. On success the card appears
# in the GUI immediately (gray until first use). On failure NOTHING is registered , 
# read `error`; confirm with bridge_list. Never treat a parsed manifest as an install.
adom-desktop bridge_install '{"manifestUrl":"https://wiki.adom.inc/api/v1/pages/<bridge-page>/files/<bridge>-bridge-manifest.json"}'

# Remove one. Bundled bridges revert to the bundled copy; third-party are fully removed.
adom-desktop bridge_uninstall '{"name":"my-bridge"}'

sync_skills: get each bridge's OWN skill

Installing the adom-desktop core pkg gives a container only CORE skills. A dynamic/cloud bridge (kicad, fusion, any third-party) ships its consumer SKILL.md in its OWN wiki skill pkg. Run this right after connecting:

adom-desktop sync_skills

It runs bridge_list, collects every non-null skillPkg, and installs each bridge's own skill pkg into ~/.claude/skills/ so you can discover and drive whatever bridges THIS desktop actually has. Idempotent; on a multi-machine relay it unions bridges across all desktops. The CLI also runs it automatically about once a day in the background (containers only; opt out with ADOM_SKILL_SYNC=0), but running it on connect makes the skills available to the current session immediately. Re-run any time the desktop gains a bridge. Do not hand-copy a bridge's SKILL.md into a container: sync_skills is the path.

Prewarm: why a fresh Mac is briefly "warming"

On launch the app prewarms in the background: managed Node and Python runtimes (downloaded only if not already on PATH, a few seconds after boot, gated by config prewarm_runtimes, default on), and on install/upgrade a detached pup prefetch (npm install + the ~150 MB Chrome-for-Testing) so the first browser_open_window is not a 3-minute hang. A bridge verb during the download gets an honest runtime_warming response (stillRunning:true, poll the runtimes verb, then retry): not a hang, not a failure. Details: skills/PREWARM.md and skills/FIRST_INSTALL.md.

The demarcation rule

Each bridge ships its own FULL consumer skill from its own wiki page (installed via sync_skills). Adom Bridge's docs, this guide included, only cover the bridge LIFECYCLE and management surface, and point at each bridge's page for how to actually drive it. For KiCad workflows read the kicad bridge's skill; for browser automation read pup's; and so on. bridge_list's installSkill field gives you the exact install line per bridge.