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:

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:

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

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

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

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

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

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. Use the adom-wiki skill for the publish flow.

Option B — any HTTPS host (GitHub Releases, your own CDN, S3):

# 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

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

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.