---
name: adom-desktop-bridge-author
description: Use this skill when the user says they want to write/author/build a third-party bridge for adom-desktop, integrate a new desktop app (Altium, MATLAB, SolidWorks, an oscilloscope, etc.) with cloud Claude, fork the hello sample bridge, ship a bridge to the wiki, or asks how custom CLI verbs get routed into adom-desktop. Walks them through scaffolding a fresh bridge from the canonical template, wiring `bridge.json`, writing the HTTP server, packaging the zip, hosting it, and verifying install via `adom-desktop bridge_install`.
---

# Authoring an Adom Desktop bridge

A bridge is a small HTTP server (Python, Node, or any executable) that adom-desktop spawns on demand and proxies cloud CLI requests to. Once installed, every `adom-desktop <verb> '<json>'` whose verb starts with your bridge's prefix routes to your server. Adom Desktop v1.8.0+ supports dynamic bridges installed at runtime from a wiki manifest URL — no fork of adom-desktop required.

## When to use this skill

The user wants to write a bridge if they say any of:
- "I want to integrate Altium / MATLAB / Eagle / SolidWorks / OrCAD / my oscilloscope / our internal CAD tool with adom-desktop"
- "Help me build a third-party adom-desktop bridge"
- "How do I add a new CLI verb to adom-desktop?" (the answer is almost always "ship it as a bridge, not by forking adom-desktop")
- "Fork the hello sample bridge"
- "Make my bridge installable via the install modal"

If they want to USE existing bridges (kicad, fusion, puppeteer), that's the `adom-desktop` skill, not this one.

## Prerequisites

The user needs adom-desktop installed + running on their laptop and reachable from this container:

```bash
adom-desktop ping
```

If that fails, walk them through the install flow at https://wiki-ufypy5dpx93o.adom.cloud/wiki/apps/adom-desktop first.

## Workflow

### Step 1 — pick a bridge name + verb prefix

Ask the user:
- What's the host app this bridge integrates? (e.g. "Altium Designer")
- What lowercase one-word name should the bridge use? (e.g. `altium`) — this is the cache directory name + the manifest key
- What verb prefix should they own? Conventionally `<name>_*`, e.g. `altium_open_project`, `altium_export_step`. Prefixes are first-come-first-served across the registry; check existing bridges with `adom-desktop bridge_list` so you don't collide.

Reserved prefixes that are already taken:
- `kicad_*` (kicad bridge)
- `browser_*`, `desktop_recorder_*`, `desktop_record_*` (puppeteer bridge)
- `fusion_*` (fusion360 bridge)
- `hello_*` (sample bridge)

Reserved bridge ports: 8770–8779 (Adom-owned), 8851 (puppeteer). Pick something in 8800–9000 for new bridges.

### Step 2 — scaffold from the hello sample

The hello sample bridge is the minimum viable example — a ~80-line stdlib-Python HTTP server. Have the user download + extract it:

```bash
# On the user's laptop (run via adom-desktop shell_execute or have them run it themselves):
curl -L https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-desktop/hello-bridge-v1.0.0.zip \
  -o /tmp/hello-bridge.zip
mkdir -p ~/my-bridge && cd ~/my-bridge
python3 -c "import zipfile; zipfile.ZipFile('/tmp/hello-bridge.zip').extractall()"
ls -la
```

They now have `bridge.json`, `BRIDGE_VERSION`, `server.py`, `README.md`. Rename the directory + edit each file to match the new bridge name/verbs.

For Node-based bridges, look at the puppeteer bridge zip instead:
`https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-desktop/puppeteer-bridge-v1.0.0.zip`

For app-with-plugin-API bridges (Altium / SolidWorks / etc.), the fusion360 bridge is the right reference — it has both a bridge process AND an in-host add-in:
`https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-desktop/fusion360-bridge-v1.0.0.zip`

### Step 3 — edit `bridge.json`

The schema is documented at https://wiki-ufypy5dpx93o.adom.cloud/wiki/apps/adom-desktop-bridges#bridgejson-schema. Minimum required fields:

```json
{
  "manifest_version": 1,
  "name": "<lowercase-name>",
  "displayName": "<human-readable>",
  "version": "1.0.0",
  "description": "One-line description that shows up in bridge_list.",
  "spawn": {
    "kind": "python",
    "entrypoint": "server.py",
    "port": 8XXX,
    "healthEndpoint": "http://127.0.0.1:8XXX/status",
    "stopMethod": "kill",
    "killImageName": "python.exe"
  },
  "verbPrefixes": ["<name>_"],
  "verbs": ["<name>_verb1", "<name>_verb2"]
}
```

Keep `verbs` accurate — `bridge_list` advertises this to discovery. Don't list verbs you haven't implemented.

If your bridge supports project-watching (file watcher → auto-resync to Docker), add a `watcher` block:
```json
"watcher": {
  "supports": true,
  "displayName": "<Watcher name>",
  "defaultGlobs": ["*.your-ext"],
  "configKey": "project_watch"
}
```

The GUI renders an eye-pip on the chip when watching is active.

### Recommended: `docs` URL + `platforms` map (v1.8.14+)

These two optional fields make your bridge a first-class citizen of the GUI:

```json
"docs": "https://wiki-ufypy5dpx93o.adom.cloud/wiki/apps/your-bridge-name",
"platforms": {
  "windows": { "supported": true },
  "macos":   { "supported": false, "reason": "Mac port pending — Win32 deps in handlers/window_automation.py need a Quartz equivalent." },
  "linux":   { "supported": false, "reason": "Same as macOS." }
}
```

- **`docs`**: surfaces in the chip's right-click context menu as "View on wiki" (top item). Use it. If you don't have a wiki page yet, publish one first (see `adom-wiki page publish` via the `adom-wiki` skill).
- **`platforms`**: an honest declaration of which OSes the bridge actually works on. Keys are `windows` / `macos` / `linux`. The GUI shows the support status in the tooltip + warns (non-blocking) in the install modal if the user is on a `supported: false` OS. Provide a `reason` string when something doesn't work — the user sees it verbatim, so explain why.

Tell the user **don't lie in `platforms`**. If they haven't tested macOS, leave it as `{"supported": false, "reason": "Untested on macOS so far"}` rather than claiming support. The whole point of the field is honesty — better to set expectations than burn a user who installs a bridge that silently fails.

### Step 4 — implement the server

The HTTP contract is two endpoints:

- `GET /status` → `{"ok": true, "version": "..."}` — used by adom-desktop to confirm the bridge is alive
- `POST /command` body `{"verb": "<name>_<verb>", "args": {...}}` → `{"success": true, "output": "..."}` or `{"success": false, "error": "..."}`

The hello sample's `server.py` is the canonical shape — fork it and replace the verb handlers with yours.

When implementing handlers:
- Validate `args` strictly. Return `{"success": false, "error": "..."}` for invalid input; don't 500.
- Keep responses small. If returning large payloads (file contents, binary blobs), write to disk first and return the path; Docker will use `pull_file` to fetch.
- Long-running operations (>5s) should return immediately with a job handle, then expose a `<name>_status` verb for polling.

### Step 5 — test locally via a localhost manifest

Before publishing to the wiki, test with a local HTTP server:

```bash
# Build the zip
cd ~/my-bridge
python3 -c "
import zipfile, os, fnmatch
EXCLUDE = ['*/__pycache__/*', '*.pyc', '.history/*']
with zipfile.ZipFile('../my-bridge-v1.0.0.zip', 'w', zipfile.ZIP_DEFLATED) as zf:
    for root, dirs, files in os.walk('.'):
        for f in files:
            rel = os.path.relpath(os.path.join(root, f), '.')
            if any(fnmatch.fnmatch(rel.replace(os.sep, '/'), g) for g in EXCLUDE):
                continue
            zf.write(os.path.join(root, f), rel)
"

# Compute sha256 + size
SHA=$(sha256sum ../my-bridge-v1.0.0.zip | awk '{print $1}')
SIZE=$(stat -c%s ../my-bridge-v1.0.0.zip)

# Write the manifest
cat > ../my-bridge-manifest.json <<EOF
{
  "manifest_version": 1,
  "version": "1.0.0",
  "url": "http://127.0.0.1:9999/my-bridge-v1.0.0.zip",
  "sha256": "$SHA",
  "size": $SIZE,
  "released_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
}
EOF

# Serve them on a local HTTP port
cd .. && python3 -m http.server 9999 &
LOCAL_SERVE_PID=$!

# Install through the localhost URL
adom-desktop bridge_install '{"manifestUrl":"http://127.0.0.1:9999/my-bridge-manifest.json"}'

# Try a verb
adom-desktop <name>_<verb> '{...}'

# Stop the local server when done
kill $LOCAL_SERVE_PID
```

This roundtrip exercises the entire install path (download + sha256 verify + extract + registry rescan + spawn-on-first-call) without uploading anything yet.

### Step 6 — ship to the wiki (recommended) or any HTTPS host

Two options:

**Option A — Adom Wiki** (recommended for bridges they want the community to find):

```bash
adom-wiki asset upload apps/adom-desktop \
  --asset-type file \
  --file ../my-bridge-v1.0.0.zip \
  --caption "my-bridge v1.0.0"

# Then upload a manifest pointing at that zip's wiki URL
# (Replace the local-server URL with the wiki static URL the upload command prints.)
adom-wiki asset upload apps/adom-desktop \
  --asset-type file \
  --file ../my-bridge-manifest.json \
  --caption "my-bridge manifest v1.0.0"
```

Suggest the user also write a dedicated `apps/<my-bridge>-bridge` wiki page documenting their bridge — same shape as the [reference bridges](https://wiki-ufypy5dpx93o.adom.cloud/wiki/apps/adom-desktop-bridges#reference-implementations). Use the `adom-wiki` skill for the publish flow.

**Option B — any HTTPS host** (GitHub Releases, your own CDN, S3):

```bash
# Upload both files anywhere reachable by HTTPS, then:
adom-desktop bridge_install '{"manifestUrl":"https://your-host/my-bridge-manifest.json"}'
```

Cloud users can install from any reachable URL — they're not locked to the wiki.

### Step 7 — verify end-to-end + iterate

```bash
# See it in the registry
adom-desktop bridge_list

# Trigger your verbs
adom-desktop <name>_<verb> '{...}'

# Inspect the install dir (right-click the chip in the GUI → Open install folder)
# OR programmatically:
adom-desktop shell_execute '{"command":"powershell -NoProfile -Command \"Get-ChildItem $env:LOCALAPPDATA\\\\\\\"Adom Desktop\\\\\\\"\\\\bridges-cache\\\\<name>\"","timeout_secs":5}'

# After edits, bump bridge.json's version + BRIDGE_VERSION, rebuild the zip, re-upload, then:
adom-desktop refresh_bridges
```

To ship a breaking change without leaving users on the old version: bump the manifest's `version` field. On next launch + refresh, adom-desktop fetches the new manifest, compares versions, downloads + verifies the new zip, atomically swaps the cache dir.

## Lifecycle controls available to your bridge users

These verbs are built into adom-desktop — you don't implement them, just know they exist so you can mention them when users ask:

| Verb | What it does |
|---|---|
| `bridge_list` | List all installed bridges + their metadata |
| `bridge_install` | Install from a manifest URL |
| `bridge_uninstall` | Remove from cache (returns to the bundled version if there was one) |
| `bridge_pause` | Stop routing verbs (process stays running, verbs return "paused") |
| `bridge_resume` | Resume routing |
| `refresh_bridges` | Re-poll all known manifest URLs for updates |

From the Adom Desktop GUI: right-click any chip in the bridge bar for Pause / Resume / Open install folder / Uninstall.

## Reference pages on the wiki

These pages contain full architecture writeups for the three bundled bridges. Use them when the user is stuck on a pattern you've already seen one of these solve:

- **[Bridge SDK guide](https://wiki-ufypy5dpx93o.adom.cloud/wiki/apps/adom-desktop-bridges)** — schema, packaging, lifecycle, trust model
- **[KiCad bridge reference](https://wiki-ufypy5dpx93o.adom.cloud/wiki/apps/adom-desktop-kicad-bridge)** — multi-instance, reverse-bridge into the host app
- **[Puppeteer bridge reference](https://wiki-ufypy5dpx93o.adom.cloud/wiki/apps/adom-desktop-puppeteer-bridge)** — single-instance Node + session-profile isolation + recording
- **[Fusion 360 bridge reference](https://wiki-ufypy5dpx93o.adom.cloud/wiki/apps/adom-desktop-fusion-bridge)** — passthrough-to-add-in pattern (use for Altium, SolidWorks, Eagle)

## Trust + security gotchas to warn users about

- The trust model is SHA256-verification of the zip against the manifest. **Users install with their own privileges** — bridges can read/write any file the user can. Tell them to install only from sources they trust.
- If you publish to the wiki, anyone in the org can see it. For internal-only bridges, host the manifest behind auth (HTTPS basic, signed URLs, etc.) — adom-desktop's `bridge_install` honors HTTP redirects + auth headers passed via `--auth-header`.
- Don't put secrets in the bridge zip. The unpacked cache dir is readable by anyone on the same machine. Read secrets at runtime from env vars / OS keychain / a `.env` next to the bridge cache that the user populates manually.

## When something goes wrong

1. **`bridge_install` says SHA mismatch** — the zip you uploaded doesn't match the manifest's `sha256`. Recompute and re-upload the manifest.
2. **Verb returns "bridge not found"** — the prefix you declared doesn't match the verb. Check `adom-desktop bridge_list` to see what prefixes you actually registered.
3. **Bridge process won't start** — adom-desktop reports the spawn failure. Common causes: wrong `entrypoint`, `spawn.kind` mismatch (Python file as `node`), missing dependencies. Check the GUI's activity log.
4. **Bridge runs but verbs return "process exited"** — your handler probably crashed. Add `try/except` around your dispatcher and log to stderr; adom-desktop forwards stderr lines to the GUI log.
5. **Open install folder shows a "Location is not available" dialog** — the cache dir doesn't exist on the user's real filesystem (probably installed from a sandboxed context). Uninstall + reinstall from a clean session. See https://wiki-ufypy5dpx93o.adom.cloud/wiki/apps/adom-desktop-bridges for the full troubleshooting.
