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

```bash
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

```bash
# 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:**

```bash
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.
