app
DEPRECATED: Bridge SDK (moved into Adom Bridge)
Public Made by Adomby adom
Bridge SDK guide: bridge.json schema, kind:python/node/exe, hello-python + hello-rust reference templates, packaging + lifecycle commands.
← Commit history
README.md+309−231@@ -1,232 +1,310 @@-# Adom Desktop - Bridge SDK--------> **Public version of this guide:** [`apps/adom-desktop-bridges` on the Adom Wiki](https://wiki-ufypy5dpx93o.adom.cloud/wiki/apps/adom-desktop-bridges). The wiki page is the canonical user-facing copy and is what the GUI's "+ Bridge" install modal links to (the adom-inc GitHub repo is private). When you update this file with breaking schema changes or new lifecycle commands, also republish the wiki page via `adom-wiki page edit apps/adom-desktop-bridges --field content --body-md <this file>`.--v1.8.0+ supports dynamic bridges: any zip with a `bridge.json` at its root can be installed via `adom-desktop bridge_install --manifestUrl <wiki-or-github-url>`. This lets community authors ship bridges for Altium, MATLAB, Rohde & Schwarz oscilloscopes, Keysight, OrCAD, anything — without coordinating with the Adom Desktop release cycle.--## Try it now — the `hello-python` and `hello-rust` sample bridges--Two parallel reference bridges are hosted on the Adom Wiki, one per language. Both expose the same two verbs (`*_ping` / `*_echo`) under different prefixes (`hellopy_` / `hellors_`), so they can be installed simultaneously for a side-by-side demo. Both use `port: 0` (dynamic OS-assigned ports — v1.8.31+) so no collision is possible.--| Flavor | Manifest URL | Verb prefix | Port | When to fork |-|---|---|---|---|---|-| **Python** (stdlib only, ~80 lines) | `…/hello-python-bridge-manifest.json` | `hellopy_` | 8890 | Vendor has Python bindings, fast dev iter, I/O-bound work |-| **Rust** (`tiny_http` + serde, single static binary, ~120 lines) | `…/hello-rust-bridge-manifest.json` | `hellors_` | 8891 | No Python runtime on user's machine, CPU-bound, type safety |--Both manifest URLs are under `https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-desktop/`.--### Install from the GUI--1. Click **+ Bridge** in Adom Desktop's bridge bar-2. Paste one (or both) of the manifest URLs into the dialog, click **Install**-3. New chips appear ~2 seconds later with a small ✦ marker (third-party indicator)--### Install both from the CLI--```bash-# Install the Python flavor-adom-desktop bridge_install '{"manifestUrl":"https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-desktop/hello-python-bridge-manifest.json"}'-adom-desktop hellopy_ping-# → {"success":true,"output":"Hello from the Python sample bridge v1.1.0!","language":"python",...}--# Install the Rust flavor (can coexist with hello-python)-adom-desktop bridge_install '{"manifestUrl":"https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-desktop/hello-rust-bridge-manifest.json"}'-adom-desktop hellors_ping-# → {"success":true,"output":"Hello from the Rust sample bridge v1.1.0!","language":"rust",...}--# Echo through both-adom-desktop hellopy_echo '{"message":"world"}'-# → {"success":true,"output":"echo (python): world",...}-adom-desktop hellors_echo '{"message":"world"}'-# → {"success":true,"output":"echo (rust): world",...}--# Both appear in bridge_list-adom-desktop bridge_list-# → bundled three + "hello-python" + "hello-rust"-```--### Uninstall--```bash-adom-desktop bridge_uninstall '{"name":"hello-python"}'-adom-desktop bridge_uninstall '{"name":"hello-rust"}'-```--Each bridge's cache dir + registry entry are removed; the chips disappear from the bar.--The sources live at:-- [`scripts/sample-bridges/hello-python/`](https://github.com/adom-inc/adom-desktop/tree/main/scripts/sample-bridges/hello-python) — `bridge.json` + `server.py` (~120 lines of `http.server.BaseHTTPRequestHandler`)-- [`scripts/sample-bridges/hello-rust/`](https://github.com/adom-inc/adom-desktop/tree/main/scripts/sample-bridges/hello-rust) — `bridge.json` + `Cargo.toml` + `src/main.rs` (~140 lines of `tiny_http`) + pre-built `hello-rust.exe` (~300 KB)--Fork whichever flavor matches your target's natural ecosystem.--## Directory layout--```-my-bridge/-├── bridge.json ← REQUIRED — schema below-├── BRIDGE_VERSION ← optional, semver line (kept in sync with bridge.json's version field)-├── server.py ← or server.js, or my-bridge.exe — your bridge's entrypoint-├── handlers/ ← your code-│ ├── ...-└── README.md ← optional, recommended-```--## `bridge.json` schema--```json-{- "manifest_version": 1,- "name": "altium",- "displayName": "Altium Designer",- "version": "1.0.0",- "description": "One-line description shown in bridge_list output.",- "homepage": "https://github.com/your-org/adom-bridge-altium",- "author": "Your Org",- "license": "MIT",- "spawn": {- "kind": "python",- "entrypoint": "server.py",- "port": 0,- "healthEndpoint": "/status",- "stopMethod": "kill",- "killImageName": "python.exe"- },- "verbPrefixes": ["altium_"],- "verbs": [- "altium_open_project",- "altium_export_step",- "altium_run_drc"- ],- "dependencies": {- "python": ">=3.11",- "altium-designer": ">=24"- },- "category": "EDA",- "tags": ["pcb", "altium", "commercial"]-}-```--### Field reference--- **`name`** (required, lowercase, no spaces). The directory name + cache key. Must be unique across the registry.-- **`displayName`** — human-friendly shown in `bridge_list` + future GUI bridge panel.-- **`version`** (required, semver). Each ship bumps this.-- **`spawn.kind`** — `python` | `node` | `exe`. Determines how Adom Desktop launches the bridge.- - `python` → Adom Desktop finds Python in PATH and runs `python <entrypoint> --port <port>`. Good when the vendor's SDK has Python bindings.- - `node` → Adom Desktop finds Node in PATH and runs `node <entrypoint> --port <port>`. Good for ecosystem reuse (Puppeteer, Playwright, etc.).- - `exe` → Adom Desktop runs `<entrypoint> --port <port>` directly. The entrypoint must be a pre-built executable in the zip. Good for Rust, Go, .NET single-file builds, etc. — no runtime install required on the user's machine.-- **`spawn.entrypoint`** — relative path inside the bridge dir (e.g. `server.py`, `server.js`, `my-bridge.exe`).-- **`spawn.port`** — **set to `0`** (v1.8.31+ recommended). Adom Desktop picks an OS-assigned ephemeral port at spawn time and passes it to your `--port` arg. No collision possible between bridges. Hardcoded ports still work for back-compat but are deprecated — anything you pick will eventually collide with someone (Hydrogen Desktop's bridges already used 8772/8773/8851; static-port collisions are the #1 source of bridge "won't start" bugs). Bridge ports are internal plumbing — callers always route through `adom-desktop <prefix>_<verb>`, never reach a bridge directly.-- **`spawn.healthEndpoint`** — path Adom Desktop polls to confirm the bridge is alive (e.g. `/status`, `/health`). v1.8.31+ accepts a path-only fragment (recommended) and constructs the URL using the runtime port. Legacy full URLs (`http://127.0.0.1:8881/status`) still parse — the host+port get replaced at runtime.-- **`spawn.stopMethod`** — `kill` (process-handle Child::kill, NOT `taskkill /im` — that would nuke ALL Pythons on the box), `sigterm` (graceful), or `graceful_endpoint` (POST to an endpoint).-- **`spawn.killImageName`** — legacy field, only used if Adom Desktop falls back to image-name kill. With v1.8.31's dynamic ports + process-handle kill this is rarely needed. Set to your process name for safety (`python.exe` / `node.exe` / your binary name).-- **`verbPrefixes`** — list of CLI verb prefixes this bridge owns (e.g. `["altium_"]`). All `altium_*` verbs route here.-- **`verbs`** — explicit verb list. Used in `bridge_list` to advertise capabilities; doesn't affect routing.--## Packaging + shipping--Use the same `scripts/release-bridge.sh` flow Adom Desktop uses for its own bridges. Adapted for third parties:--```bash-# In your bridge repo:-python3 -c "-import zipfile, os, fnmatch-EXCLUDE = ['*/__pycache__/*', '*.pyc', '.history/*', '*/node_modules/*']-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 of the zip-sha256sum my-bridge-v1.0.0.zip--# Write the manifest the GUI polls-cat > my-bridge-manifest.json <<EOF-{- "manifest_version": 1,- "version": "1.0.0",- "url": "https://your-host/my-bridge-v1.0.0.zip",- "sha256": "<sha256 from above>",- "size": <bytes>,- "released_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"-}-EOF-```--Host both files anywhere reachable by HTTPS — GitHub Pages, your own CDN, the Adom Wiki if you contribute to the official registry.--## Installing--Users install your bridge with one command:--```bash-adom-desktop bridge_install '{"manifestUrl":"https://your-host/my-bridge-manifest.json"}'-```--The GUI:-1. Fetches `my-bridge-manifest.json`-2. Downloads the referenced zip-3. SHA256-verifies against the manifest-4. Extracts to `%LOCALAPPDATA%\Adom Desktop\bridges-cache\<name>\` (the `name` comes from your `bridge.json`)-5. Rescans the runtime registry — your bridge is now discoverable via `bridge_list`--The next CLI call to one of your `verbPrefixes` auto-spawns the bridge process.--## Updating--Bump `bridge.json`'s `version` + `BRIDGE_VERSION`, rebuild the zip, re-upload, update the manifest. Users:--```bash-adom-desktop refresh_bridges # picks up new versions from manifest URLs they previously installed from-```--(Currently `refresh_bridges` only polls the three core wiki manifests. The follow-up work — tracking third-party manifest URLs in user state so they auto-refresh — lands in v1.8.x.)--## Lifecycle controls (from cloud Docker, or local GUI)--```bash-adom-desktop bridge_list # see everything-adom-desktop bridge_pause '{"name":"altium"}' # stop routing verbs-adom-desktop bridge_resume '{"name":"altium"}'-adom-desktop bridge_uninstall '{"name":"altium"}' # remove from cache-```--## Trust + security--For v1.8.0 the trust model is SHA256-verification of the wiki-hosted zip against the manifest. Bridges run with the user's privileges; install from sources you trust.--Future work (v1.9.0+):-- Optional Ed25519 signing of bridge zips with the Adom-Inc public key for "verified" status-- Per-bridge sandboxing (limited filesystem / network access declared in manifest `permissions` field)-- Community submission flow on the wiki so contributors don't need a GitHub account--## Reference implementations--Every bridge below has its own dedicated wiki page with hero, install/uninstall recipes, and a "fork this" walkthrough. The **[bridges registry](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-bridges-registry)** is the canonical directory — both humans browse it and the GUI's Browse tab parses it.--**Hello-world starter templates** — fork these for a 5-minute "does this thing work?" loop:-- **[hello-python](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-hello-python-bridge)**: `scripts/sample-bridges/hello-python/` — ~80 lines, stdlib only, `kind:python`-- **[hello-rust](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-hello-rust-bridge)**: `scripts/sample-bridges/hello-rust/` — ~140 lines, `tiny_http` + serde, `kind:exe`, single 300-KB static binary--**Adom-shipped bridges** — canonical full-complexity examples; fork their layout for a real vendor integration:-- **[kicad](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-kicad-bridge)**: `plugins/kicad/` — Python, complex, multi-instance (one bridge per KiCad GUI exe)-- **[puppeteer](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-puppeteer-bridge)**: `plugins/puppeteer/` — Node, single-instance, recording-capable-- **[fusion360](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-fusion-bridge)**: `plugins/fusion360/` — Python, simpler, command-passthrough to Fusion add-in--## Get listed--Once your bridge is stable, add it to the **[bridges registry](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-bridges-registry)** — that's the canonical machine-readable directory the GUI's Browse tab parses to discover what's installable. Two paths:--1. **PR-based** (preferred): open a PR against [`adom-inc/adom-desktop`](https://github.com/adom-inc/adom-desktop) adding your row to the "Community bridges" table in this file (`plugins/BRIDGES.md`). A maintainer mirrors it to the wiki on the next sync. See the registry page's [submission section](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-bridges-registry#how-to-submit-a-bridge) for the row format + review criteria.+# Adom Desktop - Bridge SDK + +## Build your own bridge + +A **bridge** is any small local program that exposes a few verbs over a tiny HTTP server. Adom Desktop (AD) discovers it from a `bridge.json`, spawns it, and relays your verbs to it from anywhere \u2014 including a cloud Claude session. If you can write a 50-line HTTP server in any language, you can ship a bridge. + +The fastest path is to **let Claude build it for you**. Open a cloud Claude (or Claude Code) session that has the `adom-desktop` CLI installed, and paste a prompt like the one below. + +### Starting prompt (paste into a cloud Claude session) + +``` +I want to build an Adom Desktop bridge for <YOUR APP / DEVICE / SERVICE>. + +A bridge is a small local HTTP server described by a bridge.json; AD spawns it on a +stable port and relays verbs to it. Before writing anything, study the reference +bridges for design ideas and copy the conventions you see: + + - START SIMPLE \u2014 read the two "hello" reference bridges first (one Rust, one + Python). They are the minimal, correct shape: a /command POST handler, a /status + health endpoint that self-reports its LED, and a bridge.json. Mirror them. + - THEN study an advanced bridge for the patterns you need: a long-running native + app (KiCad / Fusion 360), a process you supervise, or an external client that + dials back in (native-browser). Steal their bridge.json fields and verb shapes. + +My bridge should expose these verbs: <list them, e.g. foo_status, foo_do_thing>. + +Follow the Adom Desktop Bridge SDK conventions on this page: + - Self-report {led, summary, tooltip} from the health endpoint (AD is a pure + renderer \u2014 it never invents your status). + - Return rich, structured JSON with an _hint on every response so the calling AI + knows what to do next; on a slow operation, return a statusVerb to poll. + - Declare statusVerb in bridge.json if any verb can run longer than ~60s (AD will + relay it so a timeout reads as "still running, poll this", not "failed"). + +When it works locally, package it with release-bridge.sh and tell me the +bridge_install command so I can install it on my Adom Desktop from here. +``` + +Claude will read the reference bridges, scaffold your `bridge.json` + server, run it locally, and hand you a one-line install command. You stay in the cloud session the whole time. + +### Reference bridges \u2014 copy these + +Start with a **hello** bridge (minimal + correct), then borrow from an **advanced** one for the hard parts. + +| Bridge | Language | Why read it | +|---|---|---| +| **hello-python** | Python | The minimal bridge: `/command` + `/status` self-report + `bridge.json`. The shape every bridge follows. | +| **hello-rust** | Rust | Same minimal shape in Rust \u2014 single-binary, no runtime deps. | +| **native-browser** | \u2014 | Advanced: an **external client** (a Chrome extension) dials back into the bridge; shows the stable-port + reconnect pattern. | +| **kicad** / **fusion360** | Python | Advanced: supervising a **long-running native app**, multi-screenshot arrays, `statusVerb` for slow cloud operations. | + +The hello bridges live in the AD repo under `scripts/sample-bridges/`; each advanced bridge has its own wiki page (open it from AD's Bridges panel, or the apps index). + +### The whole dev loop is remote \u2014 install & uninstall from anywhere + +This is the part that makes bridge development easy: **you never touch the Windows box by hand.** From any cloud Claude session with the `adom-desktop` CLI: + +```bash +# Install (or update) a bridge from its published wiki manifest: +adom-desktop bridge_install '{"manifestUrl":"https://wiki.adom.inc/api/v1/pages/<your-bridge-page>/files/<your>-bridge-manifest.json"}' + +# It appears in AD's Bridges panel immediately and starts on first verb call. +adom-desktop bridge_list # see it registered + its LED +adom-desktop <your_verb> '{...}' # call it \u2014 AD spawns + relays + +# Iterate: re-publish a higher version, then re-install \u2014 AD reaps the old +# instance and streams the new one onto the SAME stable port (no port churn). +adom-desktop bridge_install '{"manifestUrl":"...","force":true}' + +# Remove it cleanly when done: +adom-desktop bridge_uninstall '{"name":"<your-bridge>"}' +``` + +In the AD GUI, the same flows are one click: **"+ Bridge"** (top of the Bridges panel) installs from a manifest URL, and each bridge card has **Uninstall** in its pop-out. Publishing the manifest + zip is a single `release-bridge.sh <name>` run (see the SDK reference below). + +That's the full toolchain \u2014 author in a cloud prompt, publish once, `bridge_install` from anywhere, iterate by re-installing. No RDP, no copying files to the desktop. + +--- + + + + + + + + +> **Public version of this guide:** [`apps/adom-desktop-bridges` on the Adom Wiki](https://wiki-ufypy5dpx93o.adom.cloud/wiki/apps/adom-desktop-bridges). The wiki page is the canonical user-facing copy and is what the GUI's "+ Bridge" install modal links to (the adom-inc GitHub repo is private). When you update this file with breaking schema changes or new lifecycle commands, also republish the wiki page via `adom-wiki page edit apps/adom-desktop-bridges --field content --body-md <this file>`. + +v1.8.0+ supports dynamic bridges: any zip with a `bridge.json` at its root can be installed via `adom-desktop bridge_install --manifestUrl <wiki-or-github-url>`. This lets community authors ship bridges for Altium, MATLAB, Rohde & Schwarz oscilloscopes, Keysight, OrCAD, anything \u2014 without coordinating with the Adom Desktop release cycle. + +## Try it now \u2014 the `hello-python` and `hello-rust` sample bridges + +Two parallel reference bridges are hosted on the Adom Wiki, one per language. Both expose the same two verbs (`*_ping` / `*_echo`) under different prefixes (`hellopy_` / `hellors_`), so they can be installed simultaneously for a side-by-side demo. Both use `port: 0` (dynamic OS-assigned ports \u2014 v1.8.31+) so no collision is possible. + +| Flavor | Manifest URL | Verb prefix | Port | When to fork | +|---|---|---|---|---| +| **Python** (stdlib only, ~80 lines) | `\u2026/hello-python-bridge-manifest.json` | `hellopy_` | 8890 | Vendor has Python bindings, fast dev iter, I/O-bound work | +| **Rust** (`tiny_http` + serde, single static binary, ~120 lines) | `\u2026/hello-rust-bridge-manifest.json` | `hellors_` | 8891 | No Python runtime on user's machine, CPU-bound, type safety | + +Both manifest URLs are under `https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-desktop/`. + +### Install from the GUI + +1. Click **+ Bridge** in Adom Desktop's bridge bar +2. Paste one (or both) of the manifest URLs into the dialog, click **Install** +3. New chips appear ~2 seconds later with a small \u2726 marker (third-party indicator) + +### Install both from the CLI + +```bash +# Install the Python flavor +adom-desktop bridge_install '{"manifestUrl":"https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-desktop/hello-python-bridge-manifest.json"}' +adom-desktop hellopy_ping +# \u2192 {"success":true,"output":"Hello from the Python sample bridge v1.1.0!","language":"python",...} + +# Install the Rust flavor (can coexist with hello-python) +adom-desktop bridge_install '{"manifestUrl":"https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-desktop/hello-rust-bridge-manifest.json"}' +adom-desktop hellors_ping +# \u2192 {"success":true,"output":"Hello from the Rust sample bridge v1.1.0!","language":"rust",...} + +# Echo through both +adom-desktop hellopy_echo '{"message":"world"}' +# \u2192 {"success":true,"output":"echo (python): world",...} +adom-desktop hellors_echo '{"message":"world"}' +# \u2192 {"success":true,"output":"echo (rust): world",...} + +# Both appear in bridge_list +adom-desktop bridge_list +# \u2192 bundled three + "hello-python" + "hello-rust" +``` + +### Uninstall + +```bash +adom-desktop bridge_uninstall '{"name":"hello-python"}' +adom-desktop bridge_uninstall '{"name":"hello-rust"}' +``` + +Each bridge's cache dir + registry entry are removed; the chips disappear from the bar. + +The sources live at: +- [`scripts/sample-bridges/hello-python/`](https://github.com/adom-inc/adom-desktop/tree/main/scripts/sample-bridges/hello-python) \u2014 `bridge.json` + `server.py` (~120 lines of `http.server.BaseHTTPRequestHandler`) +- [`scripts/sample-bridges/hello-rust/`](https://github.com/adom-inc/adom-desktop/tree/main/scripts/sample-bridges/hello-rust) \u2014 `bridge.json` + `Cargo.toml` + `src/main.rs` (~140 lines of `tiny_http`) + pre-built `hello-rust.exe` (~300 KB) + +Fork whichever flavor matches your target's natural ecosystem. + +## Directory layout + +``` +my-bridge/ +\u251c\u2500\u2500 bridge.json \u2190 REQUIRED \u2014 schema below +\u251c\u2500\u2500 BRIDGE_VERSION \u2190 optional, semver line (kept in sync with bridge.json's version field) +\u251c\u2500\u2500 server.py \u2190 or server.js, or my-bridge.exe \u2014 your bridge's entrypoint +\u251c\u2500\u2500 handlers/ \u2190 your code +\u2502 \u251c\u2500\u2500 ... +\u2514\u2500\u2500 README.md \u2190 optional, recommended +``` + +## `bridge.json` schema + +```json +{ + "manifest_version": 1, + "name": "altium", + "displayName": "Altium Designer", + "version": "1.0.0", + "description": "One-line description shown in bridge_list output.", + "homepage": "https://github.com/your-org/adom-bridge-altium", + "author": "Your Org", + "license": "MIT", + "spawn": { + "kind": "python", + "entrypoint": "server.py", + "port": 0, + "healthEndpoint": "/status", + "stopMethod": "kill", + "killImageName": "python.exe" + }, + "verbPrefixes": ["altium_"], + "verbs": [ + "altium_open_project", + "altium_export_step", + "altium_run_drc" + ], + "dependencies": { + "python": ">=3.11", + "altium-designer": ">=24" + }, + "category": "EDA", + "tags": ["pcb", "altium", "commercial"] +} +``` + +### Field reference + +- **`name`** (required, lowercase, no spaces). The directory name + cache key. Must be unique across the registry. +- **`displayName`** \u2014 human-friendly shown in `bridge_list` + future GUI bridge panel. +- **`version`** (required, semver). Each ship bumps this. +- **`spawn.kind`** \u2014 `python` | `node` | `exe`. Determines how Adom Desktop launches the bridge. + - `python` \u2192 Adom Desktop finds Python in PATH and runs `python <entrypoint> --port <port>`. Good when the vendor's SDK has Python bindings. + - `node` \u2192 Adom Desktop finds Node in PATH and runs `node <entrypoint> --port <port>`. Good for ecosystem reuse (Puppeteer, Playwright, etc.). + - `exe` \u2192 Adom Desktop runs `<entrypoint> --port <port>` directly. The entrypoint must be a pre-built executable in the zip. Good for Rust, Go, .NET single-file builds, etc. \u2014 no runtime install required on the user's machine. +- **`spawn.entrypoint`** \u2014 relative path inside the bridge dir (e.g. `server.py`, `server.js`, `my-bridge.exe`). +- **`spawn.port`** \u2014 **set to `0`** (v1.8.31+ recommended). Adom Desktop picks an OS-assigned ephemeral port at spawn time and passes it to your `--port` arg. No collision possible between bridges. Hardcoded ports still work for back-compat but are deprecated \u2014 anything you pick will eventually collide with someone (Hydrogen Desktop's bridges already used 8772/8773/8851; static-port collisions are the #1 source of bridge "won't start" bugs). Bridge ports are internal plumbing \u2014 callers always route through `adom-desktop <prefix>_<verb>`, never reach a bridge directly. +- **`spawn.healthEndpoint`** \u2014 path Adom Desktop polls to confirm the bridge is alive (e.g. `/status`, `/health`). v1.8.31+ accepts a path-only fragment (recommended) and constructs the URL using the runtime port. Legacy full URLs (`http://127.0.0.1:8881/status`) still parse \u2014 the host+port get replaced at runtime. +- **`spawn.stopMethod`** \u2014 `kill` (process-handle Child::kill, NOT `taskkill /im` \u2014 that would nuke ALL Pythons on the box), `sigterm` (graceful), or `graceful_endpoint` (POST to an endpoint). +- **`spawn.killImageName`** \u2014 legacy field, only used if Adom Desktop falls back to image-name kill. With v1.8.31's dynamic ports + process-handle kill this is rarely needed. Set to your process name for safety (`python.exe` / `node.exe` / your binary name). +- **`verbPrefixes`** \u2014 list of CLI verb prefixes this bridge owns (e.g. `["altium_"]`). All `altium_*` verbs route here. +- **`verbs`** \u2014 explicit verb list. Used in `bridge_list` to advertise capabilities; doesn't affect routing. + +## Packaging + shipping + +Use the same `scripts/release-bridge.sh` flow Adom Desktop uses for its own bridges. Adapted for third parties: + +```bash +# In your bridge repo: +python3 -c " +import zipfile, os, fnmatch +EXCLUDE = ['*/__pycache__/*', '*.pyc', '.history/*', '*/node_modules/*'] +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 of the zip +sha256sum my-bridge-v1.0.0.zip + +# Write the manifest the GUI polls +cat > my-bridge-manifest.json <<EOF +{ + "manifest_version": 1, + "version": "1.0.0", + "url": "https://your-host/my-bridge-v1.0.0.zip", + "sha256": "<sha256 from above>", + "size": <bytes>, + "released_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)" +} +EOF +``` + +Host both files anywhere reachable by HTTPS \u2014 GitHub Pages, your own CDN, the Adom Wiki if you contribute to the official registry. + +## Installing + +Users install your bridge with one command: + +```bash +adom-desktop bridge_install '{"manifestUrl":"https://your-host/my-bridge-manifest.json"}' +``` + +The GUI: +1. Fetches `my-bridge-manifest.json` +2. Downloads the referenced zip +3. SHA256-verifies against the manifest +4. Extracts to `%LOCALAPPDATA%\Adom Desktop\bridges-cache\<name>\` (the `name` comes from your `bridge.json`) +5. Rescans the runtime registry \u2014 your bridge is now discoverable via `bridge_list` + +The next CLI call to one of your `verbPrefixes` auto-spawns the bridge process. + +## Updating + +Bump `bridge.json`'s `version` + `BRIDGE_VERSION`, rebuild the zip, re-upload, update the manifest. Users: + +```bash +adom-desktop refresh_bridges # picks up new versions from manifest URLs they previously installed from +``` + +(Currently `refresh_bridges` only polls the three core wiki manifests. The follow-up work \u2014 tracking third-party manifest URLs in user state so they auto-refresh \u2014 lands in v1.8.x.) + +## Lifecycle controls (from cloud Docker, or local GUI) + +```bash +adom-desktop bridge_list # see everything +adom-desktop bridge_pause '{"name":"altium"}' # stop routing verbs +adom-desktop bridge_resume '{"name":"altium"}' +adom-desktop bridge_uninstall '{"name":"altium"}' # remove from cache +``` + +## Trust + security + +For v1.8.0 the trust model is SHA256-verification of the wiki-hosted zip against the manifest. Bridges run with the user's privileges; install from sources you trust. + +Future work (v1.9.0+): +- Optional Ed25519 signing of bridge zips with the Adom-Inc public key for "verified" status +- Per-bridge sandboxing (limited filesystem / network access declared in manifest `permissions` field) +- Community submission flow on the wiki so contributors don't need a GitHub account + +## Reference implementations + +Every bridge below has its own dedicated wiki page with hero, install/uninstall recipes, and a "fork this" walkthrough. The **[bridges registry](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-bridges-registry)** is the canonical directory \u2014 both humans browse it and the GUI's Browse tab parses it. + +**Hello-world starter templates** \u2014 fork these for a 5-minute "does this thing work?" loop: +- **[hello-python](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-hello-python-bridge)**: `scripts/sample-bridges/hello-python/` \u2014 ~80 lines, stdlib only, `kind:python` +- **[hello-rust](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-hello-rust-bridge)**: `scripts/sample-bridges/hello-rust/` \u2014 ~140 lines, `tiny_http` + serde, `kind:exe`, single 300-KB static binary + +**Adom-shipped bridges** \u2014 canonical full-complexity examples; fork their layout for a real vendor integration: +- **[kicad](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-kicad-bridge)**: `plugins/kicad/` \u2014 Python, complex, multi-instance (one bridge per KiCad GUI exe) +- **[puppeteer](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-puppeteer-bridge)**: `plugins/puppeteer/` \u2014 Node, single-instance, recording-capable +- **[fusion360](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-fusion-bridge)**: `plugins/fusion360/` \u2014 Python, simpler, command-passthrough to Fusion add-in + +## Get listed + +Once your bridge is stable, add it to the **[bridges registry](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-bridges-registry)** \u2014 that's the canonical machine-readable directory the GUI's Browse tab parses to discover what's installable. Two paths: + +1. **PR-based** (preferred): open a PR against [`adom-inc/adom-desktop`](https://github.com/adom-inc/adom-desktop) adding your row to the "Community bridges" table in this file (`plugins/BRIDGES.md`). A maintainer mirrors it to the wiki on the next sync. See the registry page's [submission section](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-bridges-registry#how-to-submit-a-bridge) for the row format + review criteria. 2. **Direct wiki edit**: if you have an Adom account, `adom-wiki page edit apps/adom-desktop-bridges-registry --field content --body-md <patch>`. Maintainers review and either keep or revert.