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
Root SKILL.md = full Bridge SDK guide; scrub retired wiki-*.adom.cloud host; v1.1.0
5 files changed
+550−350
README.md+12−12@@ -84,7 +84,7 @@ That's the full toolchain \u2014 author in a cloud prompt, publish once, `bridge  -> **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>`. +> **Public version of this guide:** [`apps/adom-desktop-bridges` on the Adom Wiki](https://wiki.adom.inc/adom/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. @@ -97,7 +97,7 @@ Two parallel reference bridges are hosted on the Adom Wiki, one per language. Bo | **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/`. +Both manifest URLs are under `https://wiki.adom.inc/api/v1/pages/<bridge-slug>/files/`. ### Install from the GUI @@ -109,12 +109,12 @@ Both manifest URLs are under `https://wiki-ufypy5dpx93o.adom.cloud/static/apps/a ```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 bridge_install '{"manifestUrl":"https://wiki.adom.inc/api/v1/pages/adom-desktop-hello-python-bridge/files/adom-bridge-hello-python-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 bridge_install '{"manifestUrl":"https://wiki.adom.inc/api/v1/pages/adom-desktop-hello-rust-bridge/files/adom-bridge-hello-rust-manifest.json"}' adom-desktop hellors_ping # \u2192 {"success":true,"output":"Hello from the Rust sample bridge v1.1.0!","language":"rust",...} @@ -291,20 +291,20 @@ Future work (v1.9.0+): ## 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. +Every bridge below has its own dedicated wiki page with hero, install/uninstall recipes, and a "fork this" walkthrough. The **[bridges registry](https://wiki.adom.inc/adom/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 +- **[hello-python](https://wiki.adom.inc/adom/adom-desktop-hello-python-bridge)**: `scripts/sample-bridges/hello-python/` \u2014 ~80 lines, stdlib only, `kind:python` +- **[hello-rust](https://wiki.adom.inc/adom/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 +- **[kicad](https://wiki.adom.inc/adom/adom-desktop-kicad-bridge)**: `plugins/kicad/` \u2014 Python, complex, multi-instance (one bridge per KiCad GUI exe) +- **[puppeteer](https://wiki.adom.inc/adom/adom-desktop-puppeteer-bridge)**: `plugins/puppeteer/` \u2014 Node, single-instance, recording-capable +- **[fusion360](https://wiki.adom.inc/adom/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: +Once your bridge is stable, add it to the **[bridges registry](https://wiki.adom.inc/adom/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. +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.adom.inc/adom/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.
SKILL.md+207−7@@ -1,7 +1,207 @@-name: adom-desktop-bridges-description: >- Bridge SDK guide: bridge.json schema, kind:python/node/exe, hello-python + hello-rust reference templates, packaging + lifecycle commands.-# Writing a third-party Adom Desktop bridge-Bridge SDK guide: bridge.json schema, kind:python/node/exe, hello-python + hello-rust reference templates, packaging + lifecycle commands.+--- +name: adom-desktop-bridges +description: Author, PUBLISH, and make-discoverable an adom-desktop bridge — a small local server that adds a namespace of verbs (kicad_, fusion_, browser_, your_prefix_) to adom-desktop. Use when building, publishing, updating, prewarming, or debugging a bridge, or deciding what skills to ship. Covers the AD-core-vs-author boundary; the #1 rule (rich hints in EVERY verb's OUTPUT, because the AI reads CLI output, never your on-disk skill); bridge.json; the THREE things you publish (runtime Release zip + manifest, container skills pkg, wiki discovery_triggers); prewarm; the publish/dev/user SKILL set you must ship; and the auto-discover → install-check → use flow. +--- + +# Bridge SDK — build, publish, and surface a bridge for Adom Desktop + +A **bridge** is a small local program (Python, Node, Rust, anything) that AD spawns and routes a **namespace of verbs** to over HTTP. KiCad, Fusion 360, Puppeteer (pup), native-browser, Blender are bridges. The modern flow has THREE moving parts you own — get all three right and a cloud/WSL2 container can *discover* your bridge from a plain-English request, confirm it's installed, and drive it with zero tribal knowledge: + +1. **Runtime** — a versioned **Release `.zip`** + a **manifest** on your wiki page. AD's `bridge_install` streams it onto the user's machine and spawns it. +2. **Skills** — an **`adom-wiki pkg`** tarball carrying your consumer SKILL.md(s). A container installs it into `~/.claude/skills/` (or `~/.codex/skills/`) so the AI *there* knows your verbs. +3. **Discoverability** — **`discovery_triggers`** on your wiki page so a user saying *"open my app in pup"* surfaces YOU via `adom-wiki discover find`. + +> **THE ONE PRINCIPLE THAT MATTERS MOST — read this before anything else.** +> **The AI almost never reads your on-disk SKILL.md. It ALWAYS reads the OUTPUT of a CLI call.** +> So your leverage is in the **`_hint` / `_next` / `related` / `pitfalls` you return on EVERY verb response.** A verb that returns `{success:true}` and nothing else leaves the AI guessing and retry-looping. A verb that returns *what just happened, the obvious next verb, the related verbs, and the pitfall to avoid* gets driven perfectly on the first try. AD relays every field of your response **verbatim** to the calling AI. Treat each verb's output as the documentation the AI will actually read. (Your SKILL.md is for the *human* author + the rare AI that does read it; your verb OUTPUT is for the AI in the loop.) + +--- + +## The boundary — what AD owns vs what YOU own + +The #1 mistake is filing AD bugs for bridge issues. The split: + +**AD core owns** (don't reimplement): the CLI/relay/`/command` passthrough (AD relays your JSON verbatim — every field); spawning/reaping your process, the **stable port**, single-instance, the persistent-respawn supervisor, cache install + auto-update; host/process facts (running? pid? port?), the status-chip rendering, the GUI card, the activity log; `status`/`_hint` classification for timeouts/disconnects (AD authors those — you never stamp them). + +**You own**: your verbs + their behavior + their **rich hints**; your `/health` (+ optional chip fields); your `<prefix>describe` catalog; your wiki page (hero, brief, `discovery_triggers`, docs); your published **manifest + Release zip**; and your **skills pkg**. + +If a *bridge verb* misbehaves → fix the bridge. If a *generic AD capability* is missing (a new lifecycle verb, a relay field) → file it against AD, but don't re-absorb the bridge into AD. + +--- + +## bridge.json — the manifest that ships INSIDE your zip + +```jsonc +{ + "name": "blender", // registry name → verb prefix "blender_" + "version": "1.2.0", // numeric; a newer CACHE copy supersedes the bundled seed + "description": "Drive Blender headless + GUI", + "author": "you", + "docs": "https://wiki.adom.inc/adom/adom-desktop-blender-bridge", // your OWN dedicated page (drives skillPkg + hero) + "hero": "https://.../hero.png", // optional; else AD uses your wiki page hero, else a monogram + "spawn": { + "kind": "process", // "process" (AD spawns it) | "external-http" (AD only probes) + "cmd": "python", // or a bundled interpreter (e.g. ship node_modules; see Prewarm) + "args": ["server.py", "--port", "{port}"], // {port} ← AD's STABLE allocated port + "persistent": true, // AD's supervisor respawns it if it dies + "cwd": "." + }, + "healthEndpoint": "/health", // default /health; AD probes it for the chip + "statusVerb": "blender_status", // verb a caller polls after a long op times out + "updateManifestUrl": "https://wiki.adom.inc/api/v1/pages/adom-desktop-blender-bridge/files/blender-manifest.json" +} +``` + +- **`{port}`** — AD allocates a STABLE port (persisted across respawns + AD restarts) and substitutes it. Bind `127.0.0.1:{port}`. NEVER hardcode a port (Hyper-V/Docker silently reserve ranges — see PORTS.md). +- **`updateManifestUrl`** — the full URL of YOUR manifest on YOUR page. **Declare it in `bridge.json`** so it survives a republish; AD polls it (on launch + every 4h) and auto-updates users. (AD also remembers the install URL in `~/.adom/bridge-install-urls.json` as a fallback, but the declared field is the durable, author-intended path.) +- **`docs`** — point at YOUR dedicated wiki page. AD derives your **skillPkg** (`adom/<slug>`) and your card hero from this. Don't point it at a shared/structural page. + +--- + +## Artifact 1 — the RUNTIME: publish your `.zip` as a RELEASE + a manifest + +This is the code AD streams onto the user's machine. Two files on your page: + +1. **A versioned Release `.zip`** of your bridge dir (server + `bridge.json` at the zip ROOT + any assets). Ship it as a **Release** (the downloadable, version-pinned artifact), not a loose `/files` blob: + ```bash + adom-wiki release create adom/adom-desktop-<bridge>-bridge <version> --title "..." --changelog "..." + adom-wiki release upload adom/adom-desktop-<bridge>-bridge <version> your-bridge-v<version>.zip --platform any + ``` + (Node bridges: the zip carries SOURCE only — keep `node_modules` out; AD/the seed handle deps. See Prewarm.) +2. **The manifest JSON** (e.g. `blender-manifest.json`) — identity + where the zip is — POSTed to your page `/files`: + ```jsonc + { "name":"blender", "version":"1.2.0", + "url":"https://wiki.adom.inc/download/adom/adom-desktop-blender-bridge/1.2.0/blender-v1.2.0.zip", // base-relative, extension-agnostic (.bin works where wiki blocks .zip) + "sha256":"…", "size":123456, "verbPrefixes":["blender_"], "healthEndpoint":"/health", + "updateManifestUrl":"https://wiki.adom.inc/api/v1/pages/adom-desktop-blender-bridge/files/blender-manifest.json" } + ``` + +**Users install/update** with AD's `bridge_install {manifestUrl}` → AD downloads the zip, sha256-verifies, unzips into `%LOCALAPPDATA%\Adom Desktop\bridges-cache\<name>\`, rescans, and remembers the URL for auto-update. + +**To ship a new version:** bump `version` in BOTH `bridge.json` (in the zip) and the manifest, re-`release upload` the zip, re-POST the manifest. Users auto-get it on the next 4h poll, or immediately via `refresh_bridges {name}`. **A bundled SEED** (a `plugins/<name>/` folder shipped in AD's NSIS) is the offline/first-run fallback; your published cache copy with a higher `version` supersedes it (cache-over-bundled, version-aware). + +--- + +## Artifact 2 — the SKILLS pkg: container-side docs (`.claude/skills` / `.codex/skills`) + +Your runtime runs on the user's DESKTOP; the AI driving it runs in a cloud/WSL2 CONTAINER. That container needs your **consumer SKILL.md** locally to know your verbs. Ship it as its own **`adom-wiki pkg`** (skills only, NO binaries): + +```bash +# Stage dir: a ROOT SKILL.md (required) + USER skills under skills/<slug>/SKILL.md, and +# package.json {name:"adom-desktop-<bridge>-bridge", version, discovery_triggers, files:[...]}. +# THREE traps that bite — verify each with `tar tzf` before publishing: +# 1. ⚠ The wiki indexes EVERY file named SKILL.md in the tarball as an INSTALLABLE, +# Skills-tab-listed USER skill (it counts them as skill_count). So your DEV + PUBLISH +# skills must NOT be named SKILL.md — put them in dev-skills/*.md, source-only — else +# every user who installs your pkg also installs your dev/publish docs. +# 2. ⚠ pkg pack/publish HONORS the stage dir's .gitignore AND the package.json `files` +# allowlist needs the GLOB: a bare "skills" entry ships ZERO skill files — use "skills/**". +# (Set .gitignore aside across pack+publish, then restore.) +# 3. ⚠ Keep the tarball LEAN — NO release zip, NO src/ tree, NO hero PNGs (it was 16 MB +# before guards). Skills + docs only. +adom-wiki pkg pack # then: tar tzf <tgz> → every USER SKILL.md present, + # ZERO binaries, ZERO dev/publish SKILL.md +adom-wiki pkg publish --org adom # publishes adom/adom-desktop-<bridge>-bridge +``` + +A container gets it **without anyone asking**: +- `bridge_install` returns `skillPkg` + `installSkill` (AD derives `adom/<slug>` from your `docs`) and the `_hint` tells the AI to run it. +- **`adom-desktop sync_skills`** installs every connected desktop's bridge skillPkgs (the CLI also runs it once/day in the background), so a container that connects a week later self-heals your skill. +- The wiki page's **Skills tab** lists the skills your pkg ships, so the page itself advertises them. + +**Install to BOTH agent homes.** A container may run Claude OR Codex — your pkg's `install.sh` must deploy the user skill to **`~/.claude/skills/`** AND **`~/.codex/skills/`** (Codex is a real second skills home now). `adom-wiki pkg update` keeps both fresh. + +--- + +## Artifact 3 — DISCOVERABILITY: `discovery_triggers` so a plain request finds you + +The wiki has an **auto-discover** index. Declare `discovery_triggers` (the phrases a user might say) on your page/pkg manifest, and a request matches them via `adom-wiki discover find`: + +```bash +adom-wiki discover triggers adom/adom-desktop-<bridge>-bridge # show/manage your triggers +adom-wiki discover preview "open my app in pup" # what would surface + WHY +adom-wiki discover find "screenshot my website" # the live find an AI runs +``` +Pup's live example (great model to copy): triggers `"pup"`, `"open in pup"`, `"open my app in pup"`, `"open a browser window"`, `"screenshot my app"`, `"browser screenshot"`, … → `discover find` returns the pup page with its `title`, `brief`, `version`, and `discovery_triggers`. + +- **Lead `discovery_triggers` with everyday USER phrases, not dev jargon.** A user says *"open my app in pup"* / *"screenshot my website"* — NOT *"spawn the puppeteer CDP bridge"*. Front-load the natural-language phrases so a plain request surfaces you; put any technical aliases after. +- **`discovery_pitch`** is the one-liner the discover snippet quotes back to the AI — there's **no separate snippet upload step**; the snippet is auto-generated from `discovery_triggers` + `discovery_pitch`. Make the pitch an INSTRUCTION (see below), not a tagline. +- **Make your `brief` / `discovery_pitch` an instruction, not just a description** — it's what the AI actually reads from the find result. Write it so the AI knows the next moves: *"…**check `adom-desktop bridge_list`** to confirm the bridge is installed in Adom Desktop; if it is, drive its `browser_*` verbs. If the container is missing the skill, `adom-wiki pkg install adom/adom-desktop-puppeteer-bridge` (or `adom-desktop sync_skills`)."* + +### The end-to-end flow your three artifacts enable + +User says *"show me my app in pup"* → the AI: +1. `adom-wiki discover find "show me my app in pup"` → your bridge surfaces (Artifact 3). +2. `adom-desktop bridge_list` → is your bridge installed in AD? If not, `bridge_install {your manifestUrl}` (Artifact 1). +3. Is your skill pkg installed in THIS container (`~/.claude/skills/` or `~/.codex/skills/`)? If not, `adom-wiki pkg install adom/<slug>` / `sync_skills` (Artifact 2). +4. Drive your verbs — guided by the **rich hints you return** (the principle at the top). + +--- + +## Prewarm — make the first call instant + +A cold first call that pays multi-minute setup (npm install, downloading a runtime, a heavy import) blows the caller's timeout. Pay that cost in the background BEFORE the user asks: + +- **Bundle deps in your SEED.** Pup ships a bundled `node_modules/` in its NSIS seed so a fresh PC can spawn with no `npm install`. When you re-seed from a Release zip, keep the bundled deps. +- **Idempotent warm-up.** Make spawning + a no-op health/setup pass idempotent so a background prewarm is a no-op once warm. +- **AD prefetches for you on first launch — standalone AND embedded (HD-bundled) — gated on availability.** (Pup's Node + Chrome-for-Testing prefetch runs once per install in both modes as of AD v1.9.45.) If your bridge needs a heavy runtime, expose a cheap idempotent "ensure ready" path AD/your seed can trigger, and surface a clean `errorCode` (e.g. `node_not_found`) when a prereq is missing so the AI can install it (`desktop_install_node`) instead of hanging. +- **`browser_readiness`-style verb.** Offer a fast verb that reports `{ready:true|false, …}` so a caller (or a fresh-PC test) can confirm warmth without triggering work. + +--- + +## The SKILL SET you should ship (not one SKILL.md — a small library) + +Don't cram everything into one file. Ship **three kinds** of skill, each with a different reader: + +1. **A PUBLISH skill** (for the bridge's OWN cloud/dev thread): how to cut the Release zip, POST the manifest, bump versions, publish the skills pkg (with the `.gitignore` trap), set `discovery_triggers`, and re-seed AD's bundled copy. This keeps *your* future self from getting the publish dance wrong. +2. **A DEV skill** (for whoever edits the bridge): architecture, the verb contract, how to test locally, the boundary with AD — and, front and center, **the rich-hints-in-output rule**: *the AI never reads this file; it reads your verb OUTPUT, so every verb must return `_hint`/`_next`/`related`/`pitfalls`.* Make that the loudest thing in the dev skill. +3. **USER skill(s)** (shipped in your pkg → the container): how a cloud AI drives your verbs to get real work done. **If the verb surface is large, split it:** a small **core USER skill** (the 80% workflow + a pointer to the sub-skills) plus **sub-skills** by area (e.g. `…-recording`, `…-electronics`, `…-aps-search`). A 2,000-line monolith won't get read; a tight core skill that names the sub-skills will. (Pup ships exactly this shape: a core bridge skill + sub-skills.) + +Publish the PUBLISH + DEV skills on your page for your thread; ship the USER skills in your **pkg** so containers get them. **Critical naming rule:** only USER skills may be named `SKILL.md` — the wiki indexes every `SKILL.md` in your tarball as an installable user skill, so name your DEV/PUBLISH docs `dev-skills/*.md` (source-only) or every user installs them (Artifact 2, trap #1). + +**Start from the templates — don't write these from scratch.** This page's **Skills tab** carries a fill-in-the-blanks sub-skill for each of the three skill kinds, generalized from pup's battle-tested set: **`bridge-dev-template`** (architecture + the rich-hints rule), **`bridge-publish-template`** (the whole release/pkg/discovery dance + every trap), **`bridge-user-skill-template`** (the consumer skill, already encoding all eight conventions below). Copy the one you need, swap `<bridge>`/`<prefix>` for your names, delete what doesn't apply. + +--- + +## USER-skill conventions — what every consumer skill must teach (from pup's shape) + +These eight patterns are what make a cloud AI actually succeed with your bridge on the first try. The user-skill template encodes them; bake them into whatever you ship: + +1. **Open with a "FIRST-TIME / COLD-START — read before you panic" section.** A fresh PC may lack your runtime or owe a one-time download, and the AI's default failure mode is to declare defeat. Lead with a **response → meaning → what-you-do** table that frames `*_installing` / `not_ready` as EXPECTED, keyed off `errorCode` (not prose). +2. **Standardize two cold-start verbs: `<prefix>_readiness` and `<prefix>_prewarm`.** `readiness` → `{ready, installing, installProgressPct, lastError}`; `prewarm` kicks off setup without real work and supports `{"wait":true}` to block. Gives every bridge a uniform "poll until ready, then proceed" loop. (Complements the runtime-side Prewarm section above.) +3. **End the `description:` frontmatter with a `Trigger words:` line** mixing verb names AND plain-English phrases a user would actually type ("take a screenshot of", "log into"). This is what makes a user-invocable skill activate — and it feeds your `discovery_triggers` (Artifact 3). +4. **Include a short "Core verbs" table** (Command | Description | Key args) for the 80% path, and point at `<prefix>_describe '{}'` for the full machine-readable catalog. The AI reads the table to act fast; `describe` is the completeness escape hatch — don't bloat the skill with every verb. +5. **"Background by default — don't disrupt the user."** Your bridge drives the AI's workspace, not the user's screen: lower/un-focus after automation, never force-foreground except to SHOW the user something, nudge via a taskbar flash (`*_alert_window`) instead of stealing focus. Standardize `*_lower_os_window` / `*_raise_os_window` / `*_alert_window`. +6. **Teach "`ok:true` is not enough" — verify what you did.** Verbs that return on operation *start* (not completion) need a cheap post-check (an eval/read verb, else a screenshot) before reporting success — 404s, login walls, and empty states land silently. +7. **Sessions: reuse-don't-reopen + stable identifiers.** Tell the AI to `*_status` before opening a new session (especially after compaction), reuse a match, and pick a stable `profile` so logins/state persist across runs. Prevents duplicate windows and the lost-login footgun. +8. **"Surface `_hint` verbatim."** Every verb returns an actionable `_hint` on not-ready/failure; instruct the AI to relay it to the user unchanged, and branch on the stable `errorCode` (not the prose). This closes the loop with THE ONE PRINCIPLE at the top — the hint IS your UI. + +--- + +## Verb contract (quick reference) + +- **Naming:** every verb is `<name>_<verb>` (`blender_render`, `blender_status`). AD routes any verb with your prefix to you. +- **Request:** AD POSTs `{ "command":"<verb>", "args":{…} }` to your port. **Response:** JSON with at least `success` (bool); human/AI output in `output`, errors in `error` (+ an `errorCode` for machine-actionable failures). AD relays everything else verbatim — so ALWAYS add `_hint`/`_next`/`related`/`pitfalls`. +- **Long-running verbs:** return promptly with a job id + set `statusVerb`; the caller polls instead of blocking. On a timeout AD auto-returns `{stillRunning:true, statusVerb, timeoutSeconds}`. +- **`<prefix>describe`:** return your full verb catalog (`{verbs:[{name,summary,input,output,timeoutSeconds,statusVerb,longRunning,example,hint,related,pitfalls}]}`). AD caches it (`~/.adom/bridge-verbs/<name>.json`) and shows it in the GUI Verbs tab — but remember, `describe` is read rarely; the per-call hints are what carry the AI. + +## Health + status chip + +Your `/health` (200) makes the chip green. Optionally return `led` (`green`/`yellow`/`red`), `summary` (short label), `tooltip` (full hover). AD owns the **offline** (gray) state when your endpoint is unreachable — you can never assert offline. + +## Lifecycle verbs (AD gives these to USERS — know them to guide users) + +`bridge_install {manifestUrl}` · `bridge_list` · `bridge_info`/`bridge_detail {name}` · `bridge_log_read {name, sinceOffset?}` · `bridge_kill {name}` · `bridge_pause {name}` · `refresh_bridges {name?}` · `bridge_check_updates`. A bridge installed via `bridge_install` auto-updates with no further opt-in; `updateManifestUrl` in `bridge.json` makes it durable across republish. + +--- + +## Publish checklist (the whole modern flow) + +1. **Page** — `adom-wiki page create adom/adom-desktop-<bridge>-bridge` with a hero + an **instructional `brief`** + **`discovery_triggers`**. +2. **Runtime** — `release create` + `release upload` the versioned `.zip`; POST the manifest to `/files`; set `updateManifestUrl` in BOTH the zip's `bridge.json` and the manifest. +3. **Skills pkg** — stage ROOT SKILL.md + `skills/<slug>/SKILL.md` (core USER skill + sub-skills) + `discovery_triggers`; `pkg pack` (inspect!) → `pkg publish --org adom` (mind the `.gitignore` trap). +4. **Discovery** — `discover triggers` to confirm; `discover preview "<a real user phrase>"` to confirm you surface. +5. **Re-seed AD's bundled copy** (AD-core thread, not you) — extract your Release zip over `plugins/<name>/` keeping bundled deps; ships in the next NSIS as the offline fallback. +6. **Verify** — `bridge_install {manifestUrl}` on a desktop → `bridge_list` shows your `version` + `skillPkg`; `bridge_check_updates` reaches your page; a container `sync_skills` pulls your USER skill; `discover find "<phrase>"` surfaces you with an instructional brief. + +**Reference bridge = pup** (`adom/adom-desktop-puppeteer-bridge`): live `discovery_triggers`, a skills pkg with a core + sub-skills, a Release zip + manifest with `updateManifestUrl`, a bundled seed with prewarmed `node_modules`, and rich per-verb hints. Copy its shape. (`scripts/sample-bridges/hello-python` + `hello-rust` are the minimal skeletons.) KiCad/Fusion/Puppeteer are cloud-owned — don't edit them from the AD repo.
bridges-SDK.md+238−238@@ -1,238 +1,238 @@-# Writing a third-party Adom Desktop bridge--> **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, or the Adom Wiki.--> ⚠️ **Hosting on the Adom Wiki: the files API silently rejects `.zip`** (the upload returns `ok:true`-> but the file never lands; a `.png` through the same path commits fine). Host the zip **bytes under a-> `.bin` name** (`my-bridge-v1.0.0.bin`) and point the manifest `url` at that - AD GETs the `url` and-> unzips it regardless of extension. And give AD the **canonical owner-scoped file URL**,-> `https://wiki.adom.inc/api/pages/<owner>/<slug>/files/<file>`, NOT the legacy `/api/v1/pages/<slug>/...`-> alias (it still resolves but is fragile). Note `bridge.json` must sit at the **zip root**, so build the-> zip from your bridge dir's contents, not a parent folder. The full Adom-Wiki publish flow (with the-> `.bin` upload via `adom-wiki api`) is the **shipping-a-bridge** skill on this page.--## 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` (and `package.json` `version` if you ship one), rebuild-the zip, re-upload, update the manifest. Users pick it up with:--```bash-adom-desktop refresh_bridges '{"name":"<your-bridge>"}' # omit name to refresh all-```--**Third-party bridges auto-update from their OWN manifest URL (AD 1.9.26+).** `bridge_install` persists the-`manifestUrl` you installed from, so `refresh_bridges` (and AD's on-launch + 4-hourly auto-sync) polls-*your* page too, not just the bundled core bridges. And **AD 1.9.27+ self-reaps the running bridge** and-respawns it on the new code, so **no manual `bridge_kill` is needed** - the result is-`action:"updated", from, to`, and the bridge respawns on the next verb call. Verify with-`bridge_check_updates` (your bridge shows `wikiHosted:true`, `current` == the new version). If it shows-`wikiHosted:false` / `latest:null`, AD has no tracked URL for it yet - re-run `bridge_install` once from-the canonical manifest URL to register it.--## 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)-- `bridges-registry.json` on the Adom Wiki listing community-vetted bridges--## Reference implementations--**Hello-world starter templates** — fork these for a 5-minute "does this thing work?" loop:-- **hello-python**: `scripts/sample-bridges/hello-python/` — ~80 lines, stdlib only, `kind:python`-- **hello-rust**: `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**: `plugins/kicad/` — Python, complex, multi-instance (one bridge per KiCad GUI exe)-- **puppeteer**: `plugins/puppeteer/` — Node, single-instance, recording-capable-- **fusion360**: `plugins/fusion360/` — Python, simpler, command-passthrough to Fusion add-in--## Get listed--Once your bridge is stable: open a PR adding an entry to `plugins/BRIDGES.md` (this file) so other users find it. Or once we set up the wiki `bridges-registry.json`, add yours there.+# Writing a third-party Adom Desktop bridge + +> **Public version of this guide:** [`apps/adom-desktop-bridges` on the Adom Wiki](https://wiki.adom.inc/adom/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.adom.inc/api/v1/pages/<bridge-slug>/files/`. + +### 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.adom.inc/api/v1/pages/adom-desktop-hello-python-bridge/files/adom-bridge-hello-python-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.adom.inc/api/v1/pages/adom-desktop-hello-rust-bridge/files/adom-bridge-hello-rust-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, or the Adom Wiki. + +> ⚠️ **Hosting on the Adom Wiki: the files API silently rejects `.zip`** (the upload returns `ok:true` +> but the file never lands; a `.png` through the same path commits fine). Host the zip **bytes under a +> `.bin` name** (`my-bridge-v1.0.0.bin`) and point the manifest `url` at that - AD GETs the `url` and +> unzips it regardless of extension. And give AD the **canonical owner-scoped file URL**, +> `https://wiki.adom.inc/api/pages/<owner>/<slug>/files/<file>`, NOT the legacy `/api/v1/pages/<slug>/...` +> alias (it still resolves but is fragile). Note `bridge.json` must sit at the **zip root**, so build the +> zip from your bridge dir's contents, not a parent folder. The full Adom-Wiki publish flow (with the +> `.bin` upload via `adom-wiki api`) is the **shipping-a-bridge** skill on this page. + +## 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` (and `package.json` `version` if you ship one), rebuild +the zip, re-upload, update the manifest. Users pick it up with: + +```bash +adom-desktop refresh_bridges '{"name":"<your-bridge>"}' # omit name to refresh all +``` + +**Third-party bridges auto-update from their OWN manifest URL (AD 1.9.26+).** `bridge_install` persists the +`manifestUrl` you installed from, so `refresh_bridges` (and AD's on-launch + 4-hourly auto-sync) polls +*your* page too, not just the bundled core bridges. And **AD 1.9.27+ self-reaps the running bridge** and +respawns it on the new code, so **no manual `bridge_kill` is needed** - the result is +`action:"updated", from, to`, and the bridge respawns on the next verb call. Verify with +`bridge_check_updates` (your bridge shows `wikiHosted:true`, `current` == the new version). If it shows +`wikiHosted:false` / `latest:null`, AD has no tracked URL for it yet - re-run `bridge_install` once from +the canonical manifest URL to register it. + +## 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) +- `bridges-registry.json` on the Adom Wiki listing community-vetted bridges + +## Reference implementations + +**Hello-world starter templates** — fork these for a 5-minute "does this thing work?" loop: +- **hello-python**: `scripts/sample-bridges/hello-python/` — ~80 lines, stdlib only, `kind:python` +- **hello-rust**: `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**: `plugins/kicad/` — Python, complex, multi-instance (one bridge per KiCad GUI exe) +- **puppeteer**: `plugins/puppeteer/` — Node, single-instance, recording-capable +- **fusion360**: `plugins/fusion360/` — Python, simpler, command-passthrough to Fusion add-in + +## Get listed + +Once your bridge is stable: open a PR adding an entry to `plugins/BRIDGES.md` (this file) so other users find it. Or once we set up the wiki `bridges-registry.json`, add yours there.
package.json+11−11@@ -1,11 +1,11 @@-{- "slug": "adom-desktop-bridges",- "version": "1.0.0",- "type": "app",- "description": "Bridge SDK guide: bridge.json schema, kind:python/node/exe, hello-python + hello-rust reference templates, packaging + lifecycle commands.",- "dependencies": {},- "scripts": {- "install": "./install.sh",- "uninstall": "./uninstall.sh"- }-}+{ + "slug": "adom-desktop-bridges", + "version": "1.1.0", + "type": "app", + "description": "Bridge SDK: build/publish/discover an adom-desktop bridge. Root skill = the full SDK guide; sub-skills = shipping-a-bridge, bridge-author (authoring walkthrough), and 3 fill-in-the-blanks templates (dev/publish/user). bridge.json schema, lifecycle verbs, rich-hints-in-output rule.", + "dependencies": {}, + "scripts": { + "install": "./install.sh", + "uninstall": "./uninstall.sh" + } +}
page.json+82−82@@ -1,82 +1,82 @@-{- "schema_version": 1,- "type": "app",- "slug": "adom-desktop-bridges",- "title": "Adom Desktop - Bridge SDK",- "brief": "Bridge SDK guide: bridge.json schema, kind:python/node/exe, hello-python + hello-rust reference templates, packaging + lifecycle commands.",- "version": "1.1.0",- "tags": [],- "license": "MIT",- "discovery_triggers": [- "third-party bridge",- "adom-desktop bridge",- "bridge SDK",- "bridge.json",- "bridge manifest",- "write a bridge",- "custom bridge",- "altium bridge",- "matlab bridge",- "oscilloscope bridge",- "hello sample bridge",- "bridge_install",- "bridge_uninstall",- "bridge_list",- "bridge_pause",- "bridge_resume",- "refresh_bridges",- "dynamic bridge",- "ship a bridge",- "host a bridge",- "bridges-registry",- "extend adom-desktop"- ],- "discovery_pitch": "Write a third-party bridge for adom-desktop — ship a custom EDA / lab / vendor integration as a stdlib-Python or Node server, install it via a wiki manifest URL, no fork of adom-desktop required.",- "sample_prompts": [- {- "label": "Write a bridge",- "prompt": "Help me write a third-party Adom Desktop bridge for X"- },- {- "label": "Install bridge",- "prompt": "Install the hello-python sample bridge so I can see how third-party bridges work"- },- {- "label": "Try Rust bridge",- "prompt": "Install the hello-rust sample bridge — I want to see a single-binary Rust example"- },- {- "label": "Package bridge",- "prompt": "Package my bridge dir as a wiki-installable manifest+zip pair"- }- ],- "install": {- "binary_name": "adom-desktop-bridges",- "install_dir": "",- "install_hint": "",- "version_cmd": ""- },- "readme": "# Adom Desktop - Bridge SDK\n\n## Build your own bridge\n\nA **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 — including a cloud Claude session. If you can write a 50-line HTTP server in any language, you can ship a bridge.\n\nThe 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.\n\n### Starting prompt (paste into a cloud Claude session)\n\n```\nI want to build an Adom Desktop bridge for <YOUR APP / DEVICE / SERVICE>.\n\nA bridge is a small local HTTP server described by a bridge.json; AD spawns it on a\nstable port and relays verbs to it. Before writing anything, study the reference\nbridges for design ideas and copy the conventions you see:\n\n - START SIMPLE — read the two \"hello\" reference bridges first (one Rust, one\n Python). They are the minimal, correct shape: a /command POST handler, a /status\n health endpoint that self-reports its LED, and a bridge.json. Mirror them.\n - THEN study an advanced bridge for the patterns you need: a long-running native\n app (KiCad / Fusion 360), a process you supervise, or an external client that\n dials back in (native-browser). Steal their bridge.json fields and verb shapes.\n\nMy bridge should expose these verbs: <list them, e.g. foo_status, foo_do_thing>.\n\nFollow the Adom Desktop Bridge SDK conventions on this page:\n - Self-report {led, summary, tooltip} from the health endpoint (AD is a pure\n renderer — it never invents your status).\n - Return rich, structured JSON with an _hint on every response so the calling AI\n knows what to do next; on a slow operation, return a statusVerb to poll.\n - Declare statusVerb in bridge.json if any verb can run longer than ~60s (AD will\n relay it so a timeout reads as \"still running, poll this\", not \"failed\").\n\nWhen it works locally, package it with release-bridge.sh and tell me the\nbridge_install command so I can install it on my Adom Desktop from here.\n```\n\nClaude 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.\n\n### Reference bridges — copy these\n\nStart with a **hello** bridge (minimal + correct), then borrow from an **advanced** one for the hard parts.\n\n| Bridge | Language | Why read it |\n|---|---|---|\n| **hello-python** | Python | The minimal bridge: `/command` + `/status` self-report + `bridge.json`. The shape every bridge follows. |\n| **hello-rust** | Rust | Same minimal shape in Rust — single-binary, no runtime deps. |\n| **native-browser** | — | Advanced: an **external client** (a Chrome extension) dials back into the bridge; shows the stable-port + reconnect pattern. |\n| **kicad** / **fusion360** | Python | Advanced: supervising a **long-running native app**, multi-screenshot arrays, `statusVerb` for slow cloud operations. |\n\nThe 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).\n\n### The whole dev loop is remote — install & uninstall from anywhere\n\nThis 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:\n\n```bash\n# Install (or update) a bridge from its published wiki manifest:\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki.adom.inc/api/v1/pages/<your-bridge-page>/files/<your>-bridge-manifest.json\"}'\n\n# It appears in AD's Bridges panel immediately and starts on first verb call.\nadom-desktop bridge_list # see it registered + its LED\nadom-desktop <your_verb> '{...}' # call it — AD spawns + relays\n\n# Iterate: re-publish a higher version, then re-install — AD reaps the old\n# instance and streams the new one onto the SAME stable port (no port churn).\nadom-desktop bridge_install '{\"manifestUrl\":\"...\",\"force\":true}'\n\n# Remove it cleanly when done:\nadom-desktop bridge_uninstall '{\"name\":\"<your-bridge>\"}'\n```\n\nIn 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).\n\nThat's the full toolchain — author in a cloud prompt, publish once, `bridge_install` from anywhere, iterate by re-installing. No RDP, no copying files to the desktop.\n\n---\n\n\n\n\n\n\n\n\n> **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>`.\n\nv1.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.\n\n## Try it now — the `hello-python` and `hello-rust` sample bridges\n\nTwo 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.\n\n| Flavor | Manifest URL | Verb prefix | Port | When to fork |\n|---|---|---|---|---|\n| **Python** (stdlib only, ~80 lines) | `…/hello-python-bridge-manifest.json` | `hellopy_` | 8890 | Vendor has Python bindings, fast dev iter, I/O-bound work |\n| **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 |\n\nBoth manifest URLs are under `https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-desktop/`.\n\n### Install from the GUI\n\n1. Click **+ Bridge** in Adom Desktop's bridge bar\n2. Paste one (or both) of the manifest URLs into the dialog, click **Install**\n3. New chips appear ~2 seconds later with a small ✦ marker (third-party indicator)\n\n### Install both from the CLI\n\n```bash\n# Install the Python flavor\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-desktop/hello-python-bridge-manifest.json\"}'\nadom-desktop hellopy_ping\n# → {\"success\":true,\"output\":\"Hello from the Python sample bridge v1.1.0!\",\"language\":\"python\",...}\n\n# Install the Rust flavor (can coexist with hello-python)\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-desktop/hello-rust-bridge-manifest.json\"}'\nadom-desktop hellors_ping\n# → {\"success\":true,\"output\":\"Hello from the Rust sample bridge v1.1.0!\",\"language\":\"rust\",...}\n\n# Echo through both\nadom-desktop hellopy_echo '{\"message\":\"world\"}'\n# → {\"success\":true,\"output\":\"echo (python): world\",...}\nadom-desktop hellors_echo '{\"message\":\"world\"}'\n# → {\"success\":true,\"output\":\"echo (rust): world\",...}\n\n# Both appear in bridge_list\nadom-desktop bridge_list\n# → bundled three + \"hello-python\" + \"hello-rust\"\n```\n\n### Uninstall\n\n```bash\nadom-desktop bridge_uninstall '{\"name\":\"hello-python\"}'\nadom-desktop bridge_uninstall '{\"name\":\"hello-rust\"}'\n```\n\nEach bridge's cache dir + registry entry are removed; the chips disappear from the bar.\n\nThe sources live at:\n- [`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`)\n- [`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)\n\nFork whichever flavor matches your target's natural ecosystem.\n\n## Directory layout\n\n```\nmy-bridge/\n├── bridge.json ← REQUIRED — schema below\n├── BRIDGE_VERSION ← optional, semver line (kept in sync with bridge.json's version field)\n├── server.py ← or server.js, or my-bridge.exe — your bridge's entrypoint\n├── handlers/ ← your code\n│ ├── ...\n└── README.md ← optional, recommended\n```\n\n## `bridge.json` schema\n\n```json\n{\n \"manifest_version\": 1,\n \"name\": \"altium\",\n \"displayName\": \"Altium Designer\",\n \"version\": \"1.0.0\",\n \"description\": \"One-line description shown in bridge_list output.\",\n \"homepage\": \"https://github.com/your-org/adom-bridge-altium\",\n \"author\": \"Your Org\",\n \"license\": \"MIT\",\n \"spawn\": {\n \"kind\": \"python\",\n \"entrypoint\": \"server.py\",\n \"port\": 0,\n \"healthEndpoint\": \"/status\",\n \"stopMethod\": \"kill\",\n \"killImageName\": \"python.exe\"\n },\n \"verbPrefixes\": [\"altium_\"],\n \"verbs\": [\n \"altium_open_project\",\n \"altium_export_step\",\n \"altium_run_drc\"\n ],\n \"dependencies\": {\n \"python\": \">=3.11\",\n \"altium-designer\": \">=24\"\n },\n \"category\": \"EDA\",\n \"tags\": [\"pcb\", \"altium\", \"commercial\"]\n}\n```\n\n### Field reference\n\n- **`name`** (required, lowercase, no spaces). The directory name + cache key. Must be unique across the registry.\n- **`displayName`** — human-friendly shown in `bridge_list` + future GUI bridge panel.\n- **`version`** (required, semver). Each ship bumps this.\n- **`spawn.kind`** — `python` | `node` | `exe`. Determines how Adom Desktop launches the bridge.\n - `python` → Adom Desktop finds Python in PATH and runs `python <entrypoint> --port <port>`. Good when the vendor's SDK has Python bindings.\n - `node` → Adom Desktop finds Node in PATH and runs `node <entrypoint> --port <port>`. Good for ecosystem reuse (Puppeteer, Playwright, etc.).\n - `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.\n- **`spawn.entrypoint`** — relative path inside the bridge dir (e.g. `server.py`, `server.js`, `my-bridge.exe`).\n- **`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.\n- **`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.\n- **`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).\n- **`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).\n- **`verbPrefixes`** — list of CLI verb prefixes this bridge owns (e.g. `[\"altium_\"]`). All `altium_*` verbs route here.\n- **`verbs`** — explicit verb list. Used in `bridge_list` to advertise capabilities; doesn't affect routing.\n\n## Packaging + shipping\n\nUse the same `scripts/release-bridge.sh` flow Adom Desktop uses for its own bridges. Adapted for third parties:\n\n```bash\n# In your bridge repo:\npython3 -c \"\nimport zipfile, os, fnmatch\nEXCLUDE = ['*/__pycache__/*', '*.pyc', '.history/*', '*/node_modules/*']\nwith zipfile.ZipFile('my-bridge-v1.0.0.zip', 'w', zipfile.ZIP_DEFLATED) as zf:\n for root, dirs, files in os.walk('.'):\n for f in files:\n rel = os.path.relpath(os.path.join(root, f), '.')\n if any(fnmatch.fnmatch(rel.replace(os.sep, '/'), g) for g in EXCLUDE):\n continue\n zf.write(os.path.join(root, f), rel)\n\"\n\n# Compute SHA256 of the zip\nsha256sum my-bridge-v1.0.0.zip\n\n# Write the manifest the GUI polls\ncat > my-bridge-manifest.json <<EOF\n{\n \"manifest_version\": 1,\n \"version\": \"1.0.0\",\n \"url\": \"https://your-host/my-bridge-v1.0.0.zip\",\n \"sha256\": \"<sha256 from above>\",\n \"size\": <bytes>,\n \"released_at\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"\n}\nEOF\n```\n\nHost both files anywhere reachable by HTTPS — GitHub Pages, your own CDN, the Adom Wiki if you contribute to the official registry.\n\n## Installing\n\nUsers install your bridge with one command:\n\n```bash\nadom-desktop bridge_install '{\"manifestUrl\":\"https://your-host/my-bridge-manifest.json\"}'\n```\n\nThe GUI:\n1. Fetches `my-bridge-manifest.json`\n2. Downloads the referenced zip\n3. SHA256-verifies against the manifest\n4. Extracts to `%LOCALAPPDATA%\\Adom Desktop\\bridges-cache\\<name>\\` (the `name` comes from your `bridge.json`)\n5. Rescans the runtime registry — your bridge is now discoverable via `bridge_list`\n\nThe next CLI call to one of your `verbPrefixes` auto-spawns the bridge process.\n\n## Updating\n\nBump `bridge.json`'s `version` + `BRIDGE_VERSION`, rebuild the zip, re-upload, update the manifest. Users:\n\n```bash\nadom-desktop refresh_bridges # picks up new versions from manifest URLs they previously installed from\n```\n\n(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.)\n\n## Lifecycle controls (from cloud Docker, or local GUI)\n\n```bash\nadom-desktop bridge_list # see everything\nadom-desktop bridge_pause '{\"name\":\"altium\"}' # stop routing verbs\nadom-desktop bridge_resume '{\"name\":\"altium\"}'\nadom-desktop bridge_uninstall '{\"name\":\"altium\"}' # remove from cache\n```\n\n## Trust + security\n\nFor 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.\n\nFuture work (v1.9.0+):\n- Optional Ed25519 signing of bridge zips with the Adom-Inc public key for \"verified\" status\n- Per-bridge sandboxing (limited filesystem / network access declared in manifest `permissions` field)\n- Community submission flow on the wiki so contributors don't need a GitHub account\n\n## Reference implementations\n\nEvery 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.\n\n**Hello-world starter templates** — fork these for a 5-minute \"does this thing work?\" loop:\n- **[hello-python](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-hello-python-bridge)**: `scripts/sample-bridges/hello-python/` — ~80 lines, stdlib only, `kind:python`\n- **[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\n\n**Adom-shipped bridges** — canonical full-complexity examples; fork their layout for a real vendor integration:\n- **[kicad](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-kicad-bridge)**: `plugins/kicad/` — Python, complex, multi-instance (one bridge per KiCad GUI exe)\n- **[puppeteer](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-puppeteer-bridge)**: `plugins/puppeteer/` — Node, single-instance, recording-capable\n- **[fusion360](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-fusion-bridge)**: `plugins/fusion360/` — Python, simpler, command-passthrough to Fusion add-in\n\n## Get listed\n\nOnce 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:\n\n1. **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.\n2. **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.",- "author": {- "id": "695820315b5f1e4db2fcf602",- "name": "Kyle Bergstedt",- "email": "[email protected]"- },- "visibility": {- "public": true- },- "hero": {- "type": "image",- "path": "hero-v2.png"- },- "metadata": {},- "created_at": "2026-05-28T05:28:40.558Z",- "updated_at": "2026-06-26T16:11:19.648Z",- "skills": [],- "author_name": "John Lauer",- "changelog": "Set descriptive title to the Adom Desktop - <name> naming convention for app-index consistency",- "content": "# Adom Desktop - Bridge SDK\n\n## Build your own bridge\n\nA **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 — including a cloud Claude session. If you can write a 50-line HTTP server in any language, you can ship a bridge.\n\nThe 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.\n\n### Starting prompt (paste into a cloud Claude session)\n\n```\nI want to build an Adom Desktop bridge for <YOUR APP / DEVICE / SERVICE>.\n\nA bridge is a small local HTTP server described by a bridge.json; AD spawns it on a\nstable port and relays verbs to it. Before writing anything, study the reference\nbridges for design ideas and copy the conventions you see:\n\n - START SIMPLE — read the two \"hello\" reference bridges first (one Rust, one\n Python). They are the minimal, correct shape: a /command POST handler, a /status\n health endpoint that self-reports its LED, and a bridge.json. Mirror them.\n - THEN study an advanced bridge for the patterns you need: a long-running native\n app (KiCad / Fusion 360), a process you supervise, or an external client that\n dials back in (native-browser). Steal their bridge.json fields and verb shapes.\n\nMy bridge should expose these verbs: <list them, e.g. foo_status, foo_do_thing>.\n\nFollow the Adom Desktop Bridge SDK conventions on this page:\n - Self-report {led, summary, tooltip} from the health endpoint (AD is a pure\n renderer — it never invents your status).\n - Return rich, structured JSON with an _hint on every response so the calling AI\n knows what to do next; on a slow operation, return a statusVerb to poll.\n - Declare statusVerb in bridge.json if any verb can run longer than ~60s (AD will\n relay it so a timeout reads as \"still running, poll this\", not \"failed\").\n\nWhen it works locally, package it with release-bridge.sh and tell me the\nbridge_install command so I can install it on my Adom Desktop from here.\n```\n\nClaude 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.\n\n### Reference bridges — copy these\n\nStart with a **hello** bridge (minimal + correct), then borrow from an **advanced** one for the hard parts.\n\n| Bridge | Language | Why read it |\n|---|---|---|\n| **hello-python** | Python | The minimal bridge: `/command` + `/status` self-report + `bridge.json`. The shape every bridge follows. |\n| **hello-rust** | Rust | Same minimal shape in Rust — single-binary, no runtime deps. |\n| **native-browser** | — | Advanced: an **external client** (a Chrome extension) dials back into the bridge; shows the stable-port + reconnect pattern. |\n| **kicad** / **fusion360** | Python | Advanced: supervising a **long-running native app**, multi-screenshot arrays, `statusVerb` for slow cloud operations. |\n\nThe 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).\n\n### The whole dev loop is remote — install & uninstall from anywhere\n\nThis 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:\n\n```bash\n# Install (or update) a bridge from its published wiki manifest:\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki.adom.inc/api/v1/pages/<your-bridge-page>/files/<your>-bridge-manifest.json\"}'\n\n# It appears in AD's Bridges panel immediately and starts on first verb call.\nadom-desktop bridge_list # see it registered + its LED\nadom-desktop <your_verb> '{...}' # call it — AD spawns + relays\n\n# Iterate: re-publish a higher version, then re-install — AD reaps the old\n# instance and streams the new one onto the SAME stable port (no port churn).\nadom-desktop bridge_install '{\"manifestUrl\":\"...\",\"force\":true}'\n\n# Remove it cleanly when done:\nadom-desktop bridge_uninstall '{\"name\":\"<your-bridge>\"}'\n```\n\nIn 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).\n\nThat's the full toolchain — author in a cloud prompt, publish once, `bridge_install` from anywhere, iterate by re-installing. No RDP, no copying files to the desktop.\n\n---\n\n\n\n\n\n\n\n\n> **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>`.\n\nv1.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.\n\n## Try it now — the `hello-python` and `hello-rust` sample bridges\n\nTwo 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.\n\n| Flavor | Manifest URL | Verb prefix | Port | When to fork |\n|---|---|---|---|---|\n| **Python** (stdlib only, ~80 lines) | `…/hello-python-bridge-manifest.json` | `hellopy_` | 8890 | Vendor has Python bindings, fast dev iter, I/O-bound work |\n| **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 |\n\nBoth manifest URLs are under `https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-desktop/`.\n\n### Install from the GUI\n\n1. Click **+ Bridge** in Adom Desktop's bridge bar\n2. Paste one (or both) of the manifest URLs into the dialog, click **Install**\n3. New chips appear ~2 seconds later with a small ✦ marker (third-party indicator)\n\n### Install both from the CLI\n\n```bash\n# Install the Python flavor\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-desktop/hello-python-bridge-manifest.json\"}'\nadom-desktop hellopy_ping\n# → {\"success\":true,\"output\":\"Hello from the Python sample bridge v1.1.0!\",\"language\":\"python\",...}\n\n# Install the Rust flavor (can coexist with hello-python)\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-desktop/hello-rust-bridge-manifest.json\"}'\nadom-desktop hellors_ping\n# → {\"success\":true,\"output\":\"Hello from the Rust sample bridge v1.1.0!\",\"language\":\"rust\",...}\n\n# Echo through both\nadom-desktop hellopy_echo '{\"message\":\"world\"}'\n# → {\"success\":true,\"output\":\"echo (python): world\",...}\nadom-desktop hellors_echo '{\"message\":\"world\"}'\n# → {\"success\":true,\"output\":\"echo (rust): world\",...}\n\n# Both appear in bridge_list\nadom-desktop bridge_list\n# → bundled three + \"hello-python\" + \"hello-rust\"\n```\n\n### Uninstall\n\n```bash\nadom-desktop bridge_uninstall '{\"name\":\"hello-python\"}'\nadom-desktop bridge_uninstall '{\"name\":\"hello-rust\"}'\n```\n\nEach bridge's cache dir + registry entry are removed; the chips disappear from the bar.\n\nThe sources live at:\n- [`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`)\n- [`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)\n\nFork whichever flavor matches your target's natural ecosystem.\n\n## Directory layout\n\n```\nmy-bridge/\n├── bridge.json ← REQUIRED — schema below\n├── BRIDGE_VERSION ← optional, semver line (kept in sync with bridge.json's version field)\n├── server.py ← or server.js, or my-bridge.exe — your bridge's entrypoint\n├── handlers/ ← your code\n│ ├── ...\n└── README.md ← optional, recommended\n```\n\n## `bridge.json` schema\n\n```json\n{\n \"manifest_version\": 1,\n \"name\": \"altium\",\n \"displayName\": \"Altium Designer\",\n \"version\": \"1.0.0\",\n \"description\": \"One-line description shown in bridge_list output.\",\n \"homepage\": \"https://github.com/your-org/adom-bridge-altium\",\n \"author\": \"Your Org\",\n \"license\": \"MIT\",\n \"spawn\": {\n \"kind\": \"python\",\n \"entrypoint\": \"server.py\",\n \"port\": 0,\n \"healthEndpoint\": \"/status\",\n \"stopMethod\": \"kill\",\n \"killImageName\": \"python.exe\"\n },\n \"verbPrefixes\": [\"altium_\"],\n \"verbs\": [\n \"altium_open_project\",\n \"altium_export_step\",\n \"altium_run_drc\"\n ],\n \"dependencies\": {\n \"python\": \">=3.11\",\n \"altium-designer\": \">=24\"\n },\n \"category\": \"EDA\",\n \"tags\": [\"pcb\", \"altium\", \"commercial\"]\n}\n```\n\n### Field reference\n\n- **`name`** (required, lowercase, no spaces). The directory name + cache key. Must be unique across the registry.\n- **`displayName`** — human-friendly shown in `bridge_list` + future GUI bridge panel.\n- **`version`** (required, semver). Each ship bumps this.\n- **`spawn.kind`** — `python` | `node` | `exe`. Determines how Adom Desktop launches the bridge.\n - `python` → Adom Desktop finds Python in PATH and runs `python <entrypoint> --port <port>`. Good when the vendor's SDK has Python bindings.\n - `node` → Adom Desktop finds Node in PATH and runs `node <entrypoint> --port <port>`. Good for ecosystem reuse (Puppeteer, Playwright, etc.).\n - `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.\n- **`spawn.entrypoint`** — relative path inside the bridge dir (e.g. `server.py`, `server.js`, `my-bridge.exe`).\n- **`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.\n- **`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.\n- **`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).\n- **`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).\n- **`verbPrefixes`** — list of CLI verb prefixes this bridge owns (e.g. `[\"altium_\"]`). All `altium_*` verbs route here.\n- **`verbs`** — explicit verb list. Used in `bridge_list` to advertise capabilities; doesn't affect routing.\n\n## Packaging + shipping\n\nUse the same `scripts/release-bridge.sh` flow Adom Desktop uses for its own bridges. Adapted for third parties:\n\n```bash\n# In your bridge repo:\npython3 -c \"\nimport zipfile, os, fnmatch\nEXCLUDE = ['*/__pycache__/*', '*.pyc', '.history/*', '*/node_modules/*']\nwith zipfile.ZipFile('my-bridge-v1.0.0.zip', 'w', zipfile.ZIP_DEFLATED) as zf:\n for root, dirs, files in os.walk('.'):\n for f in files:\n rel = os.path.relpath(os.path.join(root, f), '.')\n if any(fnmatch.fnmatch(rel.replace(os.sep, '/'), g) for g in EXCLUDE):\n continue\n zf.write(os.path.join(root, f), rel)\n\"\n\n# Compute SHA256 of the zip\nsha256sum my-bridge-v1.0.0.zip\n\n# Write the manifest the GUI polls\ncat > my-bridge-manifest.json <<EOF\n{\n \"manifest_version\": 1,\n \"version\": \"1.0.0\",\n \"url\": \"https://your-host/my-bridge-v1.0.0.zip\",\n \"sha256\": \"<sha256 from above>\",\n \"size\": <bytes>,\n \"released_at\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"\n}\nEOF\n```\n\nHost both files anywhere reachable by HTTPS — GitHub Pages, your own CDN, the Adom Wiki if you contribute to the official registry.\n\n## Installing\n\nUsers install your bridge with one command:\n\n```bash\nadom-desktop bridge_install '{\"manifestUrl\":\"https://your-host/my-bridge-manifest.json\"}'\n```\n\nThe GUI:\n1. Fetches `my-bridge-manifest.json`\n2. Downloads the referenced zip\n3. SHA256-verifies against the manifest\n4. Extracts to `%LOCALAPPDATA%\\Adom Desktop\\bridges-cache\\<name>\\` (the `name` comes from your `bridge.json`)\n5. Rescans the runtime registry — your bridge is now discoverable via `bridge_list`\n\nThe next CLI call to one of your `verbPrefixes` auto-spawns the bridge process.\n\n## Updating\n\nBump `bridge.json`'s `version` + `BRIDGE_VERSION`, rebuild the zip, re-upload, update the manifest. Users:\n\n```bash\nadom-desktop refresh_bridges # picks up new versions from manifest URLs they previously installed from\n```\n\n(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.)\n\n## Lifecycle controls (from cloud Docker, or local GUI)\n\n```bash\nadom-desktop bridge_list # see everything\nadom-desktop bridge_pause '{\"name\":\"altium\"}' # stop routing verbs\nadom-desktop bridge_resume '{\"name\":\"altium\"}'\nadom-desktop bridge_uninstall '{\"name\":\"altium\"}' # remove from cache\n```\n\n## Trust + security\n\nFor 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.\n\nFuture work (v1.9.0+):\n- Optional Ed25519 signing of bridge zips with the Adom-Inc public key for \"verified\" status\n- Per-bridge sandboxing (limited filesystem / network access declared in manifest `permissions` field)\n- Community submission flow on the wiki so contributors don't need a GitHub account\n\n## Reference implementations\n\nEvery 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.\n\n**Hello-world starter templates** — fork these for a 5-minute \"does this thing work?\" loop:\n- **[hello-python](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-hello-python-bridge)**: `scripts/sample-bridges/hello-python/` — ~80 lines, stdlib only, `kind:python`\n- **[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\n\n**Adom-shipped bridges** — canonical full-complexity examples; fork their layout for a real vendor integration:\n- **[kicad](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-kicad-bridge)**: `plugins/kicad/` — Python, complex, multi-instance (one bridge per KiCad GUI exe)\n- **[puppeteer](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-puppeteer-bridge)**: `plugins/puppeteer/` — Node, single-instance, recording-capable\n- **[fusion360](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-fusion-bridge)**: `plugins/fusion360/` — Python, simpler, command-passthrough to Fusion add-in\n\n## Get listed\n\nOnce 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:\n\n1. **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.\n2. **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.",- "body_md": "# Adom Desktop - Bridge SDK\n\n## Build your own bridge\n\nA **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 — including a cloud Claude session. If you can write a 50-line HTTP server in any language, you can ship a bridge.\n\nThe 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.\n\n### Starting prompt (paste into a cloud Claude session)\n\n```\nI want to build an Adom Desktop bridge for <YOUR APP / DEVICE / SERVICE>.\n\nA bridge is a small local HTTP server described by a bridge.json; AD spawns it on a\nstable port and relays verbs to it. Before writing anything, study the reference\nbridges for design ideas and copy the conventions you see:\n\n - START SIMPLE — read the two \"hello\" reference bridges first (one Rust, one\n Python). They are the minimal, correct shape: a /command POST handler, a /status\n health endpoint that self-reports its LED, and a bridge.json. Mirror them.\n - THEN study an advanced bridge for the patterns you need: a long-running native\n app (KiCad / Fusion 360), a process you supervise, or an external client that\n dials back in (native-browser). Steal their bridge.json fields and verb shapes.\n\nMy bridge should expose these verbs: <list them, e.g. foo_status, foo_do_thing>.\n\nFollow the Adom Desktop Bridge SDK conventions on this page:\n - Self-report {led, summary, tooltip} from the health endpoint (AD is a pure\n renderer — it never invents your status).\n - Return rich, structured JSON with an _hint on every response so the calling AI\n knows what to do next; on a slow operation, return a statusVerb to poll.\n - Declare statusVerb in bridge.json if any verb can run longer than ~60s (AD will\n relay it so a timeout reads as \"still running, poll this\", not \"failed\").\n\nWhen it works locally, package it with release-bridge.sh and tell me the\nbridge_install command so I can install it on my Adom Desktop from here.\n```\n\nClaude 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.\n\n### Reference bridges — copy these\n\nStart with a **hello** bridge (minimal + correct), then borrow from an **advanced** one for the hard parts.\n\n| Bridge | Language | Why read it |\n|---|---|---|\n| **hello-python** | Python | The minimal bridge: `/command` + `/status` self-report + `bridge.json`. The shape every bridge follows. |\n| **hello-rust** | Rust | Same minimal shape in Rust — single-binary, no runtime deps. |\n| **native-browser** | — | Advanced: an **external client** (a Chrome extension) dials back into the bridge; shows the stable-port + reconnect pattern. |\n| **kicad** / **fusion360** | Python | Advanced: supervising a **long-running native app**, multi-screenshot arrays, `statusVerb` for slow cloud operations. |\n\nThe 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).\n\n### The whole dev loop is remote — install & uninstall from anywhere\n\nThis 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:\n\n```bash\n# Install (or update) a bridge from its published wiki manifest:\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki.adom.inc/api/v1/pages/<your-bridge-page>/files/<your>-bridge-manifest.json\"}'\n\n# It appears in AD's Bridges panel immediately and starts on first verb call.\nadom-desktop bridge_list # see it registered + its LED\nadom-desktop <your_verb> '{...}' # call it — AD spawns + relays\n\n# Iterate: re-publish a higher version, then re-install — AD reaps the old\n# instance and streams the new one onto the SAME stable port (no port churn).\nadom-desktop bridge_install '{\"manifestUrl\":\"...\",\"force\":true}'\n\n# Remove it cleanly when done:\nadom-desktop bridge_uninstall '{\"name\":\"<your-bridge>\"}'\n```\n\nIn 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).\n\nThat's the full toolchain — author in a cloud prompt, publish once, `bridge_install` from anywhere, iterate by re-installing. No RDP, no copying files to the desktop.\n\n---\n\n\n\n\n\n\n\n\n> **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>`.\n\nv1.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.\n\n## Try it now — the `hello-python` and `hello-rust` sample bridges\n\nTwo 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.\n\n| Flavor | Manifest URL | Verb prefix | Port | When to fork |\n|---|---|---|---|---|\n| **Python** (stdlib only, ~80 lines) | `…/hello-python-bridge-manifest.json` | `hellopy_` | 8890 | Vendor has Python bindings, fast dev iter, I/O-bound work |\n| **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 |\n\nBoth manifest URLs are under `https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-desktop/`.\n\n### Install from the GUI\n\n1. Click **+ Bridge** in Adom Desktop's bridge bar\n2. Paste one (or both) of the manifest URLs into the dialog, click **Install**\n3. New chips appear ~2 seconds later with a small ✦ marker (third-party indicator)\n\n### Install both from the CLI\n\n```bash\n# Install the Python flavor\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-desktop/hello-python-bridge-manifest.json\"}'\nadom-desktop hellopy_ping\n# → {\"success\":true,\"output\":\"Hello from the Python sample bridge v1.1.0!\",\"language\":\"python\",...}\n\n# Install the Rust flavor (can coexist with hello-python)\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-desktop/hello-rust-bridge-manifest.json\"}'\nadom-desktop hellors_ping\n# → {\"success\":true,\"output\":\"Hello from the Rust sample bridge v1.1.0!\",\"language\":\"rust\",...}\n\n# Echo through both\nadom-desktop hellopy_echo '{\"message\":\"world\"}'\n# → {\"success\":true,\"output\":\"echo (python): world\",...}\nadom-desktop hellors_echo '{\"message\":\"world\"}'\n# → {\"success\":true,\"output\":\"echo (rust): world\",...}\n\n# Both appear in bridge_list\nadom-desktop bridge_list\n# → bundled three + \"hello-python\" + \"hello-rust\"\n```\n\n### Uninstall\n\n```bash\nadom-desktop bridge_uninstall '{\"name\":\"hello-python\"}'\nadom-desktop bridge_uninstall '{\"name\":\"hello-rust\"}'\n```\n\nEach bridge's cache dir + registry entry are removed; the chips disappear from the bar.\n\nThe sources live at:\n- [`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`)\n- [`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)\n\nFork whichever flavor matches your target's natural ecosystem.\n\n## Directory layout\n\n```\nmy-bridge/\n├── bridge.json ← REQUIRED — schema below\n├── BRIDGE_VERSION ← optional, semver line (kept in sync with bridge.json's version field)\n├── server.py ← or server.js, or my-bridge.exe — your bridge's entrypoint\n├── handlers/ ← your code\n│ ├── ...\n└── README.md ← optional, recommended\n```\n\n## `bridge.json` schema\n\n```json\n{\n \"manifest_version\": 1,\n \"name\": \"altium\",\n \"displayName\": \"Altium Designer\",\n \"version\": \"1.0.0\",\n \"description\": \"One-line description shown in bridge_list output.\",\n \"homepage\": \"https://github.com/your-org/adom-bridge-altium\",\n \"author\": \"Your Org\",\n \"license\": \"MIT\",\n \"spawn\": {\n \"kind\": \"python\",\n \"entrypoint\": \"server.py\",\n \"port\": 0,\n \"healthEndpoint\": \"/status\",\n \"stopMethod\": \"kill\",\n \"killImageName\": \"python.exe\"\n },\n \"verbPrefixes\": [\"altium_\"],\n \"verbs\": [\n \"altium_open_project\",\n \"altium_export_step\",\n \"altium_run_drc\"\n ],\n \"dependencies\": {\n \"python\": \">=3.11\",\n \"altium-designer\": \">=24\"\n },\n \"category\": \"EDA\",\n \"tags\": [\"pcb\", \"altium\", \"commercial\"]\n}\n```\n\n### Field reference\n\n- **`name`** (required, lowercase, no spaces). The directory name + cache key. Must be unique across the registry.\n- **`displayName`** — human-friendly shown in `bridge_list` + future GUI bridge panel.\n- **`version`** (required, semver). Each ship bumps this.\n- **`spawn.kind`** — `python` | `node` | `exe`. Determines how Adom Desktop launches the bridge.\n - `python` → Adom Desktop finds Python in PATH and runs `python <entrypoint> --port <port>`. Good when the vendor's SDK has Python bindings.\n - `node` → Adom Desktop finds Node in PATH and runs `node <entrypoint> --port <port>`. Good for ecosystem reuse (Puppeteer, Playwright, etc.).\n - `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.\n- **`spawn.entrypoint`** — relative path inside the bridge dir (e.g. `server.py`, `server.js`, `my-bridge.exe`).\n- **`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.\n- **`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.\n- **`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).\n- **`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).\n- **`verbPrefixes`** — list of CLI verb prefixes this bridge owns (e.g. `[\"altium_\"]`). All `altium_*` verbs route here.\n- **`verbs`** — explicit verb list. Used in `bridge_list` to advertise capabilities; doesn't affect routing.\n\n## Packaging + shipping\n\nUse the same `scripts/release-bridge.sh` flow Adom Desktop uses for its own bridges. Adapted for third parties:\n\n```bash\n# In your bridge repo:\npython3 -c \"\nimport zipfile, os, fnmatch\nEXCLUDE = ['*/__pycache__/*', '*.pyc', '.history/*', '*/node_modules/*']\nwith zipfile.ZipFile('my-bridge-v1.0.0.zip', 'w', zipfile.ZIP_DEFLATED) as zf:\n for root, dirs, files in os.walk('.'):\n for f in files:\n rel = os.path.relpath(os.path.join(root, f), '.')\n if any(fnmatch.fnmatch(rel.replace(os.sep, '/'), g) for g in EXCLUDE):\n continue\n zf.write(os.path.join(root, f), rel)\n\"\n\n# Compute SHA256 of the zip\nsha256sum my-bridge-v1.0.0.zip\n\n# Write the manifest the GUI polls\ncat > my-bridge-manifest.json <<EOF\n{\n \"manifest_version\": 1,\n \"version\": \"1.0.0\",\n \"url\": \"https://your-host/my-bridge-v1.0.0.zip\",\n \"sha256\": \"<sha256 from above>\",\n \"size\": <bytes>,\n \"released_at\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"\n}\nEOF\n```\n\nHost both files anywhere reachable by HTTPS — GitHub Pages, your own CDN, the Adom Wiki if you contribute to the official registry.\n\n## Installing\n\nUsers install your bridge with one command:\n\n```bash\nadom-desktop bridge_install '{\"manifestUrl\":\"https://your-host/my-bridge-manifest.json\"}'\n```\n\nThe GUI:\n1. Fetches `my-bridge-manifest.json`\n2. Downloads the referenced zip\n3. SHA256-verifies against the manifest\n4. Extracts to `%LOCALAPPDATA%\\Adom Desktop\\bridges-cache\\<name>\\` (the `name` comes from your `bridge.json`)\n5. Rescans the runtime registry — your bridge is now discoverable via `bridge_list`\n\nThe next CLI call to one of your `verbPrefixes` auto-spawns the bridge process.\n\n## Updating\n\nBump `bridge.json`'s `version` + `BRIDGE_VERSION`, rebuild the zip, re-upload, update the manifest. Users:\n\n```bash\nadom-desktop refresh_bridges # picks up new versions from manifest URLs they previously installed from\n```\n\n(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.)\n\n## Lifecycle controls (from cloud Docker, or local GUI)\n\n```bash\nadom-desktop bridge_list # see everything\nadom-desktop bridge_pause '{\"name\":\"altium\"}' # stop routing verbs\nadom-desktop bridge_resume '{\"name\":\"altium\"}'\nadom-desktop bridge_uninstall '{\"name\":\"altium\"}' # remove from cache\n```\n\n## Trust + security\n\nFor 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.\n\nFuture work (v1.9.0+):\n- Optional Ed25519 signing of bridge zips with the Adom-Inc public key for \"verified\" status\n- Per-bridge sandboxing (limited filesystem / network access declared in manifest `permissions` field)\n- Community submission flow on the wiki so contributors don't need a GitHub account\n\n## Reference implementations\n\nEvery 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.\n\n**Hello-world starter templates** — fork these for a 5-minute \"does this thing work?\" loop:\n- **[hello-python](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-hello-python-bridge)**: `scripts/sample-bridges/hello-python/` — ~80 lines, stdlib only, `kind:python`\n- **[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\n\n**Adom-shipped bridges** — canonical full-complexity examples; fork their layout for a real vendor integration:\n- **[kicad](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-kicad-bridge)**: `plugins/kicad/` — Python, complex, multi-instance (one bridge per KiCad GUI exe)\n- **[puppeteer](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-puppeteer-bridge)**: `plugins/puppeteer/` — Node, single-instance, recording-capable\n- **[fusion360](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-fusion-bridge)**: `plugins/fusion360/` — Python, simpler, command-passthrough to Fusion add-in\n\n## Get listed\n\nOnce 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:\n\n1. **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.\n2. **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.",- "markdown": "# Adom Desktop - Bridge SDK\n\n## Build your own bridge\n\nA **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 — including a cloud Claude session. If you can write a 50-line HTTP server in any language, you can ship a bridge.\n\nThe 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.\n\n### Starting prompt (paste into a cloud Claude session)\n\n```\nI want to build an Adom Desktop bridge for <YOUR APP / DEVICE / SERVICE>.\n\nA bridge is a small local HTTP server described by a bridge.json; AD spawns it on a\nstable port and relays verbs to it. Before writing anything, study the reference\nbridges for design ideas and copy the conventions you see:\n\n - START SIMPLE — read the two \"hello\" reference bridges first (one Rust, one\n Python). They are the minimal, correct shape: a /command POST handler, a /status\n health endpoint that self-reports its LED, and a bridge.json. Mirror them.\n - THEN study an advanced bridge for the patterns you need: a long-running native\n app (KiCad / Fusion 360), a process you supervise, or an external client that\n dials back in (native-browser). Steal their bridge.json fields and verb shapes.\n\nMy bridge should expose these verbs: <list them, e.g. foo_status, foo_do_thing>.\n\nFollow the Adom Desktop Bridge SDK conventions on this page:\n - Self-report {led, summary, tooltip} from the health endpoint (AD is a pure\n renderer — it never invents your status).\n - Return rich, structured JSON with an _hint on every response so the calling AI\n knows what to do next; on a slow operation, return a statusVerb to poll.\n - Declare statusVerb in bridge.json if any verb can run longer than ~60s (AD will\n relay it so a timeout reads as \"still running, poll this\", not \"failed\").\n\nWhen it works locally, package it with release-bridge.sh and tell me the\nbridge_install command so I can install it on my Adom Desktop from here.\n```\n\nClaude 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.\n\n### Reference bridges — copy these\n\nStart with a **hello** bridge (minimal + correct), then borrow from an **advanced** one for the hard parts.\n\n| Bridge | Language | Why read it |\n|---|---|---|\n| **hello-python** | Python | The minimal bridge: `/command` + `/status` self-report + `bridge.json`. The shape every bridge follows. |\n| **hello-rust** | Rust | Same minimal shape in Rust — single-binary, no runtime deps. |\n| **native-browser** | — | Advanced: an **external client** (a Chrome extension) dials back into the bridge; shows the stable-port + reconnect pattern. |\n| **kicad** / **fusion360** | Python | Advanced: supervising a **long-running native app**, multi-screenshot arrays, `statusVerb` for slow cloud operations. |\n\nThe 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).\n\n### The whole dev loop is remote — install & uninstall from anywhere\n\nThis 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:\n\n```bash\n# Install (or update) a bridge from its published wiki manifest:\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki.adom.inc/api/v1/pages/<your-bridge-page>/files/<your>-bridge-manifest.json\"}'\n\n# It appears in AD's Bridges panel immediately and starts on first verb call.\nadom-desktop bridge_list # see it registered + its LED\nadom-desktop <your_verb> '{...}' # call it — AD spawns + relays\n\n# Iterate: re-publish a higher version, then re-install — AD reaps the old\n# instance and streams the new one onto the SAME stable port (no port churn).\nadom-desktop bridge_install '{\"manifestUrl\":\"...\",\"force\":true}'\n\n# Remove it cleanly when done:\nadom-desktop bridge_uninstall '{\"name\":\"<your-bridge>\"}'\n```\n\nIn 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).\n\nThat's the full toolchain — author in a cloud prompt, publish once, `bridge_install` from anywhere, iterate by re-installing. No RDP, no copying files to the desktop.\n\n---\n\n\n\n\n\n\n\n\n> **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>`.\n\nv1.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.\n\n## Try it now — the `hello-python` and `hello-rust` sample bridges\n\nTwo 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.\n\n| Flavor | Manifest URL | Verb prefix | Port | When to fork |\n|---|---|---|---|---|\n| **Python** (stdlib only, ~80 lines) | `…/hello-python-bridge-manifest.json` | `hellopy_` | 8890 | Vendor has Python bindings, fast dev iter, I/O-bound work |\n| **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 |\n\nBoth manifest URLs are under `https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-desktop/`.\n\n### Install from the GUI\n\n1. Click **+ Bridge** in Adom Desktop's bridge bar\n2. Paste one (or both) of the manifest URLs into the dialog, click **Install**\n3. New chips appear ~2 seconds later with a small ✦ marker (third-party indicator)\n\n### Install both from the CLI\n\n```bash\n# Install the Python flavor\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-desktop/hello-python-bridge-manifest.json\"}'\nadom-desktop hellopy_ping\n# → {\"success\":true,\"output\":\"Hello from the Python sample bridge v1.1.0!\",\"language\":\"python\",...}\n\n# Install the Rust flavor (can coexist with hello-python)\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-desktop/hello-rust-bridge-manifest.json\"}'\nadom-desktop hellors_ping\n# → {\"success\":true,\"output\":\"Hello from the Rust sample bridge v1.1.0!\",\"language\":\"rust\",...}\n\n# Echo through both\nadom-desktop hellopy_echo '{\"message\":\"world\"}'\n# → {\"success\":true,\"output\":\"echo (python): world\",...}\nadom-desktop hellors_echo '{\"message\":\"world\"}'\n# → {\"success\":true,\"output\":\"echo (rust): world\",...}\n\n# Both appear in bridge_list\nadom-desktop bridge_list\n# → bundled three + \"hello-python\" + \"hello-rust\"\n```\n\n### Uninstall\n\n```bash\nadom-desktop bridge_uninstall '{\"name\":\"hello-python\"}'\nadom-desktop bridge_uninstall '{\"name\":\"hello-rust\"}'\n```\n\nEach bridge's cache dir + registry entry are removed; the chips disappear from the bar.\n\nThe sources live at:\n- [`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`)\n- [`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)\n\nFork whichever flavor matches your target's natural ecosystem.\n\n## Directory layout\n\n```\nmy-bridge/\n├── bridge.json ← REQUIRED — schema below\n├── BRIDGE_VERSION ← optional, semver line (kept in sync with bridge.json's version field)\n├── server.py ← or server.js, or my-bridge.exe — your bridge's entrypoint\n├── handlers/ ← your code\n│ ├── ...\n└── README.md ← optional, recommended\n```\n\n## `bridge.json` schema\n\n```json\n{\n \"manifest_version\": 1,\n \"name\": \"altium\",\n \"displayName\": \"Altium Designer\",\n \"version\": \"1.0.0\",\n \"description\": \"One-line description shown in bridge_list output.\",\n \"homepage\": \"https://github.com/your-org/adom-bridge-altium\",\n \"author\": \"Your Org\",\n \"license\": \"MIT\",\n \"spawn\": {\n \"kind\": \"python\",\n \"entrypoint\": \"server.py\",\n \"port\": 0,\n \"healthEndpoint\": \"/status\",\n \"stopMethod\": \"kill\",\n \"killImageName\": \"python.exe\"\n },\n \"verbPrefixes\": [\"altium_\"],\n \"verbs\": [\n \"altium_open_project\",\n \"altium_export_step\",\n \"altium_run_drc\"\n ],\n \"dependencies\": {\n \"python\": \">=3.11\",\n \"altium-designer\": \">=24\"\n },\n \"category\": \"EDA\",\n \"tags\": [\"pcb\", \"altium\", \"commercial\"]\n}\n```\n\n### Field reference\n\n- **`name`** (required, lowercase, no spaces). The directory name + cache key. Must be unique across the registry.\n- **`displayName`** — human-friendly shown in `bridge_list` + future GUI bridge panel.\n- **`version`** (required, semver). Each ship bumps this.\n- **`spawn.kind`** — `python` | `node` | `exe`. Determines how Adom Desktop launches the bridge.\n - `python` → Adom Desktop finds Python in PATH and runs `python <entrypoint> --port <port>`. Good when the vendor's SDK has Python bindings.\n - `node` → Adom Desktop finds Node in PATH and runs `node <entrypoint> --port <port>`. Good for ecosystem reuse (Puppeteer, Playwright, etc.).\n - `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.\n- **`spawn.entrypoint`** — relative path inside the bridge dir (e.g. `server.py`, `server.js`, `my-bridge.exe`).\n- **`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.\n- **`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.\n- **`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).\n- **`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).\n- **`verbPrefixes`** — list of CLI verb prefixes this bridge owns (e.g. `[\"altium_\"]`). All `altium_*` verbs route here.\n- **`verbs`** — explicit verb list. Used in `bridge_list` to advertise capabilities; doesn't affect routing.\n\n## Packaging + shipping\n\nUse the same `scripts/release-bridge.sh` flow Adom Desktop uses for its own bridges. Adapted for third parties:\n\n```bash\n# In your bridge repo:\npython3 -c \"\nimport zipfile, os, fnmatch\nEXCLUDE = ['*/__pycache__/*', '*.pyc', '.history/*', '*/node_modules/*']\nwith zipfile.ZipFile('my-bridge-v1.0.0.zip', 'w', zipfile.ZIP_DEFLATED) as zf:\n for root, dirs, files in os.walk('.'):\n for f in files:\n rel = os.path.relpath(os.path.join(root, f), '.')\n if any(fnmatch.fnmatch(rel.replace(os.sep, '/'), g) for g in EXCLUDE):\n continue\n zf.write(os.path.join(root, f), rel)\n\"\n\n# Compute SHA256 of the zip\nsha256sum my-bridge-v1.0.0.zip\n\n# Write the manifest the GUI polls\ncat > my-bridge-manifest.json <<EOF\n{\n \"manifest_version\": 1,\n \"version\": \"1.0.0\",\n \"url\": \"https://your-host/my-bridge-v1.0.0.zip\",\n \"sha256\": \"<sha256 from above>\",\n \"size\": <bytes>,\n \"released_at\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"\n}\nEOF\n```\n\nHost both files anywhere reachable by HTTPS — GitHub Pages, your own CDN, the Adom Wiki if you contribute to the official registry.\n\n## Installing\n\nUsers install your bridge with one command:\n\n```bash\nadom-desktop bridge_install '{\"manifestUrl\":\"https://your-host/my-bridge-manifest.json\"}'\n```\n\nThe GUI:\n1. Fetches `my-bridge-manifest.json`\n2. Downloads the referenced zip\n3. SHA256-verifies against the manifest\n4. Extracts to `%LOCALAPPDATA%\\Adom Desktop\\bridges-cache\\<name>\\` (the `name` comes from your `bridge.json`)\n5. Rescans the runtime registry — your bridge is now discoverable via `bridge_list`\n\nThe next CLI call to one of your `verbPrefixes` auto-spawns the bridge process.\n\n## Updating\n\nBump `bridge.json`'s `version` + `BRIDGE_VERSION`, rebuild the zip, re-upload, update the manifest. Users:\n\n```bash\nadom-desktop refresh_bridges # picks up new versions from manifest URLs they previously installed from\n```\n\n(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.)\n\n## Lifecycle controls (from cloud Docker, or local GUI)\n\n```bash\nadom-desktop bridge_list # see everything\nadom-desktop bridge_pause '{\"name\":\"altium\"}' # stop routing verbs\nadom-desktop bridge_resume '{\"name\":\"altium\"}'\nadom-desktop bridge_uninstall '{\"name\":\"altium\"}' # remove from cache\n```\n\n## Trust + security\n\nFor 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.\n\nFuture work (v1.9.0+):\n- Optional Ed25519 signing of bridge zips with the Adom-Inc public key for \"verified\" status\n- Per-bridge sandboxing (limited filesystem / network access declared in manifest `permissions` field)\n- Community submission flow on the wiki so contributors don't need a GitHub account\n\n## Reference implementations\n\nEvery 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.\n\n**Hello-world starter templates** — fork these for a 5-minute \"does this thing work?\" loop:\n- **[hello-python](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-hello-python-bridge)**: `scripts/sample-bridges/hello-python/` — ~80 lines, stdlib only, `kind:python`\n- **[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\n\n**Adom-shipped bridges** — canonical full-complexity examples; fork their layout for a real vendor integration:\n- **[kicad](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-kicad-bridge)**: `plugins/kicad/` — Python, complex, multi-instance (one bridge per KiCad GUI exe)\n- **[puppeteer](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-puppeteer-bridge)**: `plugins/puppeteer/` — Node, single-instance, recording-capable\n- **[fusion360](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-fusion-bridge)**: `plugins/fusion360/` — Python, simpler, command-passthrough to Fusion add-in\n\n## Get listed\n\nOnce 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:\n\n1. **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.\n2. **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.",- "body": "# Adom Desktop - Bridge SDK\n\n## Build your own bridge\n\nA **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 — including a cloud Claude session. If you can write a 50-line HTTP server in any language, you can ship a bridge.\n\nThe 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.\n\n### Starting prompt (paste into a cloud Claude session)\n\n```\nI want to build an Adom Desktop bridge for <YOUR APP / DEVICE / SERVICE>.\n\nA bridge is a small local HTTP server described by a bridge.json; AD spawns it on a\nstable port and relays verbs to it. Before writing anything, study the reference\nbridges for design ideas and copy the conventions you see:\n\n - START SIMPLE — read the two \"hello\" reference bridges first (one Rust, one\n Python). They are the minimal, correct shape: a /command POST handler, a /status\n health endpoint that self-reports its LED, and a bridge.json. Mirror them.\n - THEN study an advanced bridge for the patterns you need: a long-running native\n app (KiCad / Fusion 360), a process you supervise, or an external client that\n dials back in (native-browser). Steal their bridge.json fields and verb shapes.\n\nMy bridge should expose these verbs: <list them, e.g. foo_status, foo_do_thing>.\n\nFollow the Adom Desktop Bridge SDK conventions on this page:\n - Self-report {led, summary, tooltip} from the health endpoint (AD is a pure\n renderer — it never invents your status).\n - Return rich, structured JSON with an _hint on every response so the calling AI\n knows what to do next; on a slow operation, return a statusVerb to poll.\n - Declare statusVerb in bridge.json if any verb can run longer than ~60s (AD will\n relay it so a timeout reads as \"still running, poll this\", not \"failed\").\n\nWhen it works locally, package it with release-bridge.sh and tell me the\nbridge_install command so I can install it on my Adom Desktop from here.\n```\n\nClaude 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.\n\n### Reference bridges — copy these\n\nStart with a **hello** bridge (minimal + correct), then borrow from an **advanced** one for the hard parts.\n\n| Bridge | Language | Why read it |\n|---|---|---|\n| **hello-python** | Python | The minimal bridge: `/command` + `/status` self-report + `bridge.json`. The shape every bridge follows. |\n| **hello-rust** | Rust | Same minimal shape in Rust — single-binary, no runtime deps. |\n| **native-browser** | — | Advanced: an **external client** (a Chrome extension) dials back into the bridge; shows the stable-port + reconnect pattern. |\n| **kicad** / **fusion360** | Python | Advanced: supervising a **long-running native app**, multi-screenshot arrays, `statusVerb` for slow cloud operations. |\n\nThe 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).\n\n### The whole dev loop is remote — install & uninstall from anywhere\n\nThis 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:\n\n```bash\n# Install (or update) a bridge from its published wiki manifest:\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki.adom.inc/api/v1/pages/<your-bridge-page>/files/<your>-bridge-manifest.json\"}'\n\n# It appears in AD's Bridges panel immediately and starts on first verb call.\nadom-desktop bridge_list # see it registered + its LED\nadom-desktop <your_verb> '{...}' # call it — AD spawns + relays\n\n# Iterate: re-publish a higher version, then re-install — AD reaps the old\n# instance and streams the new one onto the SAME stable port (no port churn).\nadom-desktop bridge_install '{\"manifestUrl\":\"...\",\"force\":true}'\n\n# Remove it cleanly when done:\nadom-desktop bridge_uninstall '{\"name\":\"<your-bridge>\"}'\n```\n\nIn 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).\n\nThat's the full toolchain — author in a cloud prompt, publish once, `bridge_install` from anywhere, iterate by re-installing. No RDP, no copying files to the desktop.\n\n---\n\n\n\n\n\n\n\n\n> **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>`.\n\nv1.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.\n\n## Try it now — the `hello-python` and `hello-rust` sample bridges\n\nTwo 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.\n\n| Flavor | Manifest URL | Verb prefix | Port | When to fork |\n|---|---|---|---|---|\n| **Python** (stdlib only, ~80 lines) | `…/hello-python-bridge-manifest.json` | `hellopy_` | 8890 | Vendor has Python bindings, fast dev iter, I/O-bound work |\n| **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 |\n\nBoth manifest URLs are under `https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-desktop/`.\n\n### Install from the GUI\n\n1. Click **+ Bridge** in Adom Desktop's bridge bar\n2. Paste one (or both) of the manifest URLs into the dialog, click **Install**\n3. New chips appear ~2 seconds later with a small ✦ marker (third-party indicator)\n\n### Install both from the CLI\n\n```bash\n# Install the Python flavor\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-desktop/hello-python-bridge-manifest.json\"}'\nadom-desktop hellopy_ping\n# → {\"success\":true,\"output\":\"Hello from the Python sample bridge v1.1.0!\",\"language\":\"python\",...}\n\n# Install the Rust flavor (can coexist with hello-python)\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-desktop/hello-rust-bridge-manifest.json\"}'\nadom-desktop hellors_ping\n# → {\"success\":true,\"output\":\"Hello from the Rust sample bridge v1.1.0!\",\"language\":\"rust\",...}\n\n# Echo through both\nadom-desktop hellopy_echo '{\"message\":\"world\"}'\n# → {\"success\":true,\"output\":\"echo (python): world\",...}\nadom-desktop hellors_echo '{\"message\":\"world\"}'\n# → {\"success\":true,\"output\":\"echo (rust): world\",...}\n\n# Both appear in bridge_list\nadom-desktop bridge_list\n# → bundled three + \"hello-python\" + \"hello-rust\"\n```\n\n### Uninstall\n\n```bash\nadom-desktop bridge_uninstall '{\"name\":\"hello-python\"}'\nadom-desktop bridge_uninstall '{\"name\":\"hello-rust\"}'\n```\n\nEach bridge's cache dir + registry entry are removed; the chips disappear from the bar.\n\nThe sources live at:\n- [`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`)\n- [`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)\n\nFork whichever flavor matches your target's natural ecosystem.\n\n## Directory layout\n\n```\nmy-bridge/\n├── bridge.json ← REQUIRED — schema below\n├── BRIDGE_VERSION ← optional, semver line (kept in sync with bridge.json's version field)\n├── server.py ← or server.js, or my-bridge.exe — your bridge's entrypoint\n├── handlers/ ← your code\n│ ├── ...\n└── README.md ← optional, recommended\n```\n\n## `bridge.json` schema\n\n```json\n{\n \"manifest_version\": 1,\n \"name\": \"altium\",\n \"displayName\": \"Altium Designer\",\n \"version\": \"1.0.0\",\n \"description\": \"One-line description shown in bridge_list output.\",\n \"homepage\": \"https://github.com/your-org/adom-bridge-altium\",\n \"author\": \"Your Org\",\n \"license\": \"MIT\",\n \"spawn\": {\n \"kind\": \"python\",\n \"entrypoint\": \"server.py\",\n \"port\": 0,\n \"healthEndpoint\": \"/status\",\n \"stopMethod\": \"kill\",\n \"killImageName\": \"python.exe\"\n },\n \"verbPrefixes\": [\"altium_\"],\n \"verbs\": [\n \"altium_open_project\",\n \"altium_export_step\",\n \"altium_run_drc\"\n ],\n \"dependencies\": {\n \"python\": \">=3.11\",\n \"altium-designer\": \">=24\"\n },\n \"category\": \"EDA\",\n \"tags\": [\"pcb\", \"altium\", \"commercial\"]\n}\n```\n\n### Field reference\n\n- **`name`** (required, lowercase, no spaces). The directory name + cache key. Must be unique across the registry.\n- **`displayName`** — human-friendly shown in `bridge_list` + future GUI bridge panel.\n- **`version`** (required, semver). Each ship bumps this.\n- **`spawn.kind`** — `python` | `node` | `exe`. Determines how Adom Desktop launches the bridge.\n - `python` → Adom Desktop finds Python in PATH and runs `python <entrypoint> --port <port>`. Good when the vendor's SDK has Python bindings.\n - `node` → Adom Desktop finds Node in PATH and runs `node <entrypoint> --port <port>`. Good for ecosystem reuse (Puppeteer, Playwright, etc.).\n - `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.\n- **`spawn.entrypoint`** — relative path inside the bridge dir (e.g. `server.py`, `server.js`, `my-bridge.exe`).\n- **`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.\n- **`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.\n- **`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).\n- **`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).\n- **`verbPrefixes`** — list of CLI verb prefixes this bridge owns (e.g. `[\"altium_\"]`). All `altium_*` verbs route here.\n- **`verbs`** — explicit verb list. Used in `bridge_list` to advertise capabilities; doesn't affect routing.\n\n## Packaging + shipping\n\nUse the same `scripts/release-bridge.sh` flow Adom Desktop uses for its own bridges. Adapted for third parties:\n\n```bash\n# In your bridge repo:\npython3 -c \"\nimport zipfile, os, fnmatch\nEXCLUDE = ['*/__pycache__/*', '*.pyc', '.history/*', '*/node_modules/*']\nwith zipfile.ZipFile('my-bridge-v1.0.0.zip', 'w', zipfile.ZIP_DEFLATED) as zf:\n for root, dirs, files in os.walk('.'):\n for f in files:\n rel = os.path.relpath(os.path.join(root, f), '.')\n if any(fnmatch.fnmatch(rel.replace(os.sep, '/'), g) for g in EXCLUDE):\n continue\n zf.write(os.path.join(root, f), rel)\n\"\n\n# Compute SHA256 of the zip\nsha256sum my-bridge-v1.0.0.zip\n\n# Write the manifest the GUI polls\ncat > my-bridge-manifest.json <<EOF\n{\n \"manifest_version\": 1,\n \"version\": \"1.0.0\",\n \"url\": \"https://your-host/my-bridge-v1.0.0.zip\",\n \"sha256\": \"<sha256 from above>\",\n \"size\": <bytes>,\n \"released_at\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"\n}\nEOF\n```\n\nHost both files anywhere reachable by HTTPS — GitHub Pages, your own CDN, the Adom Wiki if you contribute to the official registry.\n\n## Installing\n\nUsers install your bridge with one command:\n\n```bash\nadom-desktop bridge_install '{\"manifestUrl\":\"https://your-host/my-bridge-manifest.json\"}'\n```\n\nThe GUI:\n1. Fetches `my-bridge-manifest.json`\n2. Downloads the referenced zip\n3. SHA256-verifies against the manifest\n4. Extracts to `%LOCALAPPDATA%\\Adom Desktop\\bridges-cache\\<name>\\` (the `name` comes from your `bridge.json`)\n5. Rescans the runtime registry — your bridge is now discoverable via `bridge_list`\n\nThe next CLI call to one of your `verbPrefixes` auto-spawns the bridge process.\n\n## Updating\n\nBump `bridge.json`'s `version` + `BRIDGE_VERSION`, rebuild the zip, re-upload, update the manifest. Users:\n\n```bash\nadom-desktop refresh_bridges # picks up new versions from manifest URLs they previously installed from\n```\n\n(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.)\n\n## Lifecycle controls (from cloud Docker, or local GUI)\n\n```bash\nadom-desktop bridge_list # see everything\nadom-desktop bridge_pause '{\"name\":\"altium\"}' # stop routing verbs\nadom-desktop bridge_resume '{\"name\":\"altium\"}'\nadom-desktop bridge_uninstall '{\"name\":\"altium\"}' # remove from cache\n```\n\n## Trust + security\n\nFor 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.\n\nFuture work (v1.9.0+):\n- Optional Ed25519 signing of bridge zips with the Adom-Inc public key for \"verified\" status\n- Per-bridge sandboxing (limited filesystem / network access declared in manifest `permissions` field)\n- Community submission flow on the wiki so contributors don't need a GitHub account\n\n## Reference implementations\n\nEvery 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.\n\n**Hello-world starter templates** — fork these for a 5-minute \"does this thing work?\" loop:\n- **[hello-python](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-hello-python-bridge)**: `scripts/sample-bridges/hello-python/` — ~80 lines, stdlib only, `kind:python`\n- **[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\n\n**Adom-shipped bridges** — canonical full-complexity examples; fork their layout for a real vendor integration:\n- **[kicad](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-kicad-bridge)**: `plugins/kicad/` — Python, complex, multi-instance (one bridge per KiCad GUI exe)\n- **[puppeteer](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-puppeteer-bridge)**: `plugins/puppeteer/` — Node, single-instance, recording-capable\n- **[fusion360](https://wiki-ufypy5dpx93o.adom.cloud/apps/adom-desktop-fusion-bridge)**: `plugins/fusion360/` — Python, simpler, command-passthrough to Fusion add-in\n\n## Get listed\n\nOnce 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:\n\n1. **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.\n2. **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."-}+{ + "schema_version": 1, + "type": "app", + "slug": "adom-desktop-bridges", + "title": "Adom Desktop - Bridge SDK", + "brief": "Bridge SDK guide: bridge.json schema, kind:python/node/exe, hello-python + hello-rust reference templates, packaging + lifecycle commands.", + "version": "1.1.0", + "tags": [], + "license": "MIT", + "discovery_triggers": [ + "third-party bridge", + "adom-desktop bridge", + "bridge SDK", + "bridge.json", + "bridge manifest", + "write a bridge", + "custom bridge", + "altium bridge", + "matlab bridge", + "oscilloscope bridge", + "hello sample bridge", + "bridge_install", + "bridge_uninstall", + "bridge_list", + "bridge_pause", + "bridge_resume", + "refresh_bridges", + "dynamic bridge", + "ship a bridge", + "host a bridge", + "bridges-registry", + "extend adom-desktop" + ], + "discovery_pitch": "Write a third-party bridge for adom-desktop — ship a custom EDA / lab / vendor integration as a stdlib-Python or Node server, install it via a wiki manifest URL, no fork of adom-desktop required.", + "sample_prompts": [ + { + "label": "Write a bridge", + "prompt": "Help me write a third-party Adom Desktop bridge for X" + }, + { + "label": "Install bridge", + "prompt": "Install the hello-python sample bridge so I can see how third-party bridges work" + }, + { + "label": "Try Rust bridge", + "prompt": "Install the hello-rust sample bridge — I want to see a single-binary Rust example" + }, + { + "label": "Package bridge", + "prompt": "Package my bridge dir as a wiki-installable manifest+zip pair" + } + ], + "install": { + "binary_name": "adom-desktop-bridges", + "install_dir": "", + "install_hint": "", + "version_cmd": "" + }, + "readme": "# Adom Desktop - Bridge SDK\n\n## Build your own bridge\n\nA **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 — including a cloud Claude session. If you can write a 50-line HTTP server in any language, you can ship a bridge.\n\nThe 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.\n\n### Starting prompt (paste into a cloud Claude session)\n\n```\nI want to build an Adom Desktop bridge for <YOUR APP / DEVICE / SERVICE>.\n\nA bridge is a small local HTTP server described by a bridge.json; AD spawns it on a\nstable port and relays verbs to it. Before writing anything, study the reference\nbridges for design ideas and copy the conventions you see:\n\n - START SIMPLE — read the two \"hello\" reference bridges first (one Rust, one\n Python). They are the minimal, correct shape: a /command POST handler, a /status\n health endpoint that self-reports its LED, and a bridge.json. Mirror them.\n - THEN study an advanced bridge for the patterns you need: a long-running native\n app (KiCad / Fusion 360), a process you supervise, or an external client that\n dials back in (native-browser). Steal their bridge.json fields and verb shapes.\n\nMy bridge should expose these verbs: <list them, e.g. foo_status, foo_do_thing>.\n\nFollow the Adom Desktop Bridge SDK conventions on this page:\n - Self-report {led, summary, tooltip} from the health endpoint (AD is a pure\n renderer — it never invents your status).\n - Return rich, structured JSON with an _hint on every response so the calling AI\n knows what to do next; on a slow operation, return a statusVerb to poll.\n - Declare statusVerb in bridge.json if any verb can run longer than ~60s (AD will\n relay it so a timeout reads as \"still running, poll this\", not \"failed\").\n\nWhen it works locally, package it with release-bridge.sh and tell me the\nbridge_install command so I can install it on my Adom Desktop from here.\n```\n\nClaude 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.\n\n### Reference bridges — copy these\n\nStart with a **hello** bridge (minimal + correct), then borrow from an **advanced** one for the hard parts.\n\n| Bridge | Language | Why read it |\n|---|---|---|\n| **hello-python** | Python | The minimal bridge: `/command` + `/status` self-report + `bridge.json`. The shape every bridge follows. |\n| **hello-rust** | Rust | Same minimal shape in Rust — single-binary, no runtime deps. |\n| **native-browser** | — | Advanced: an **external client** (a Chrome extension) dials back into the bridge; shows the stable-port + reconnect pattern. |\n| **kicad** / **fusion360** | Python | Advanced: supervising a **long-running native app**, multi-screenshot arrays, `statusVerb` for slow cloud operations. |\n\nThe 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).\n\n### The whole dev loop is remote — install & uninstall from anywhere\n\nThis 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:\n\n```bash\n# Install (or update) a bridge from its published wiki manifest:\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki.adom.inc/api/v1/pages/<your-bridge-page>/files/<your>-bridge-manifest.json\"}'\n\n# It appears in AD's Bridges panel immediately and starts on first verb call.\nadom-desktop bridge_list # see it registered + its LED\nadom-desktop <your_verb> '{...}' # call it — AD spawns + relays\n\n# Iterate: re-publish a higher version, then re-install — AD reaps the old\n# instance and streams the new one onto the SAME stable port (no port churn).\nadom-desktop bridge_install '{\"manifestUrl\":\"...\",\"force\":true}'\n\n# Remove it cleanly when done:\nadom-desktop bridge_uninstall '{\"name\":\"<your-bridge>\"}'\n```\n\nIn 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).\n\nThat's the full toolchain — author in a cloud prompt, publish once, `bridge_install` from anywhere, iterate by re-installing. No RDP, no copying files to the desktop.\n\n---\n\n\n\n\n\n\n\n\n> **Public version of this guide:** [`apps/adom-desktop-bridges` on the Adom Wiki](https://wiki.adom.inc/adom/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>`.\n\nv1.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.\n\n## Try it now — the `hello-python` and `hello-rust` sample bridges\n\nTwo 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.\n\n| Flavor | Manifest URL | Verb prefix | Port | When to fork |\n|---|---|---|---|---|\n| **Python** (stdlib only, ~80 lines) | `…/hello-python-bridge-manifest.json` | `hellopy_` | 8890 | Vendor has Python bindings, fast dev iter, I/O-bound work |\n| **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 |\n\nBoth manifest URLs are under `https://wiki.adom.inc/api/v1/pages/<bridge-slug>/files/`.\n\n### Install from the GUI\n\n1. Click **+ Bridge** in Adom Desktop's bridge bar\n2. Paste one (or both) of the manifest URLs into the dialog, click **Install**\n3. New chips appear ~2 seconds later with a small ✦ marker (third-party indicator)\n\n### Install both from the CLI\n\n```bash\n# Install the Python flavor\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki.adom.inc/api/v1/pages/adom-desktop-hello-python-bridge/files/adom-bridge-hello-python-manifest.json\"}'\nadom-desktop hellopy_ping\n# → {\"success\":true,\"output\":\"Hello from the Python sample bridge v1.1.0!\",\"language\":\"python\",...}\n\n# Install the Rust flavor (can coexist with hello-python)\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki.adom.inc/api/v1/pages/adom-desktop-hello-rust-bridge/files/adom-bridge-hello-rust-manifest.json\"}'\nadom-desktop hellors_ping\n# → {\"success\":true,\"output\":\"Hello from the Rust sample bridge v1.1.0!\",\"language\":\"rust\",...}\n\n# Echo through both\nadom-desktop hellopy_echo '{\"message\":\"world\"}'\n# → {\"success\":true,\"output\":\"echo (python): world\",...}\nadom-desktop hellors_echo '{\"message\":\"world\"}'\n# → {\"success\":true,\"output\":\"echo (rust): world\",...}\n\n# Both appear in bridge_list\nadom-desktop bridge_list\n# → bundled three + \"hello-python\" + \"hello-rust\"\n```\n\n### Uninstall\n\n```bash\nadom-desktop bridge_uninstall '{\"name\":\"hello-python\"}'\nadom-desktop bridge_uninstall '{\"name\":\"hello-rust\"}'\n```\n\nEach bridge's cache dir + registry entry are removed; the chips disappear from the bar.\n\nThe sources live at:\n- [`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`)\n- [`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)\n\nFork whichever flavor matches your target's natural ecosystem.\n\n## Directory layout\n\n```\nmy-bridge/\n├── bridge.json ← REQUIRED — schema below\n├── BRIDGE_VERSION ← optional, semver line (kept in sync with bridge.json's version field)\n├── server.py ← or server.js, or my-bridge.exe — your bridge's entrypoint\n├── handlers/ ← your code\n│ ├── ...\n└── README.md ← optional, recommended\n```\n\n## `bridge.json` schema\n\n```json\n{\n \"manifest_version\": 1,\n \"name\": \"altium\",\n \"displayName\": \"Altium Designer\",\n \"version\": \"1.0.0\",\n \"description\": \"One-line description shown in bridge_list output.\",\n \"homepage\": \"https://github.com/your-org/adom-bridge-altium\",\n \"author\": \"Your Org\",\n \"license\": \"MIT\",\n \"spawn\": {\n \"kind\": \"python\",\n \"entrypoint\": \"server.py\",\n \"port\": 0,\n \"healthEndpoint\": \"/status\",\n \"stopMethod\": \"kill\",\n \"killImageName\": \"python.exe\"\n },\n \"verbPrefixes\": [\"altium_\"],\n \"verbs\": [\n \"altium_open_project\",\n \"altium_export_step\",\n \"altium_run_drc\"\n ],\n \"dependencies\": {\n \"python\": \">=3.11\",\n \"altium-designer\": \">=24\"\n },\n \"category\": \"EDA\",\n \"tags\": [\"pcb\", \"altium\", \"commercial\"]\n}\n```\n\n### Field reference\n\n- **`name`** (required, lowercase, no spaces). The directory name + cache key. Must be unique across the registry.\n- **`displayName`** — human-friendly shown in `bridge_list` + future GUI bridge panel.\n- **`version`** (required, semver). Each ship bumps this.\n- **`spawn.kind`** — `python` | `node` | `exe`. Determines how Adom Desktop launches the bridge.\n - `python` → Adom Desktop finds Python in PATH and runs `python <entrypoint> --port <port>`. Good when the vendor's SDK has Python bindings.\n - `node` → Adom Desktop finds Node in PATH and runs `node <entrypoint> --port <port>`. Good for ecosystem reuse (Puppeteer, Playwright, etc.).\n - `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.\n- **`spawn.entrypoint`** — relative path inside the bridge dir (e.g. `server.py`, `server.js`, `my-bridge.exe`).\n- **`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.\n- **`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.\n- **`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).\n- **`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).\n- **`verbPrefixes`** — list of CLI verb prefixes this bridge owns (e.g. `[\"altium_\"]`). All `altium_*` verbs route here.\n- **`verbs`** — explicit verb list. Used in `bridge_list` to advertise capabilities; doesn't affect routing.\n\n## Packaging + shipping\n\nUse the same `scripts/release-bridge.sh` flow Adom Desktop uses for its own bridges. Adapted for third parties:\n\n```bash\n# In your bridge repo:\npython3 -c \"\nimport zipfile, os, fnmatch\nEXCLUDE = ['*/__pycache__/*', '*.pyc', '.history/*', '*/node_modules/*']\nwith zipfile.ZipFile('my-bridge-v1.0.0.zip', 'w', zipfile.ZIP_DEFLATED) as zf:\n for root, dirs, files in os.walk('.'):\n for f in files:\n rel = os.path.relpath(os.path.join(root, f), '.')\n if any(fnmatch.fnmatch(rel.replace(os.sep, '/'), g) for g in EXCLUDE):\n continue\n zf.write(os.path.join(root, f), rel)\n\"\n\n# Compute SHA256 of the zip\nsha256sum my-bridge-v1.0.0.zip\n\n# Write the manifest the GUI polls\ncat > my-bridge-manifest.json <<EOF\n{\n \"manifest_version\": 1,\n \"version\": \"1.0.0\",\n \"url\": \"https://your-host/my-bridge-v1.0.0.zip\",\n \"sha256\": \"<sha256 from above>\",\n \"size\": <bytes>,\n \"released_at\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"\n}\nEOF\n```\n\nHost both files anywhere reachable by HTTPS — GitHub Pages, your own CDN, the Adom Wiki if you contribute to the official registry.\n\n## Installing\n\nUsers install your bridge with one command:\n\n```bash\nadom-desktop bridge_install '{\"manifestUrl\":\"https://your-host/my-bridge-manifest.json\"}'\n```\n\nThe GUI:\n1. Fetches `my-bridge-manifest.json`\n2. Downloads the referenced zip\n3. SHA256-verifies against the manifest\n4. Extracts to `%LOCALAPPDATA%\\Adom Desktop\\bridges-cache\\<name>\\` (the `name` comes from your `bridge.json`)\n5. Rescans the runtime registry — your bridge is now discoverable via `bridge_list`\n\nThe next CLI call to one of your `verbPrefixes` auto-spawns the bridge process.\n\n## Updating\n\nBump `bridge.json`'s `version` + `BRIDGE_VERSION`, rebuild the zip, re-upload, update the manifest. Users:\n\n```bash\nadom-desktop refresh_bridges # picks up new versions from manifest URLs they previously installed from\n```\n\n(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.)\n\n## Lifecycle controls (from cloud Docker, or local GUI)\n\n```bash\nadom-desktop bridge_list # see everything\nadom-desktop bridge_pause '{\"name\":\"altium\"}' # stop routing verbs\nadom-desktop bridge_resume '{\"name\":\"altium\"}'\nadom-desktop bridge_uninstall '{\"name\":\"altium\"}' # remove from cache\n```\n\n## Trust + security\n\nFor 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.\n\nFuture work (v1.9.0+):\n- Optional Ed25519 signing of bridge zips with the Adom-Inc public key for \"verified\" status\n- Per-bridge sandboxing (limited filesystem / network access declared in manifest `permissions` field)\n- Community submission flow on the wiki so contributors don't need a GitHub account\n\n## Reference implementations\n\nEvery bridge below has its own dedicated wiki page with hero, install/uninstall recipes, and a \"fork this\" walkthrough. The **[bridges registry](https://wiki.adom.inc/adom/adom-desktop-bridges-registry)** is the canonical directory — both humans browse it and the GUI's Browse tab parses it.\n\n**Hello-world starter templates** — fork these for a 5-minute \"does this thing work?\" loop:\n- **[hello-python](https://wiki.adom.inc/adom/adom-desktop-hello-python-bridge)**: `scripts/sample-bridges/hello-python/` — ~80 lines, stdlib only, `kind:python`\n- **[hello-rust](https://wiki.adom.inc/adom/adom-desktop-hello-rust-bridge)**: `scripts/sample-bridges/hello-rust/` — ~140 lines, `tiny_http` + serde, `kind:exe`, single 300-KB static binary\n\n**Adom-shipped bridges** — canonical full-complexity examples; fork their layout for a real vendor integration:\n- **[kicad](https://wiki.adom.inc/adom/adom-desktop-kicad-bridge)**: `plugins/kicad/` — Python, complex, multi-instance (one bridge per KiCad GUI exe)\n- **[puppeteer](https://wiki.adom.inc/adom/adom-desktop-puppeteer-bridge)**: `plugins/puppeteer/` — Node, single-instance, recording-capable\n- **[fusion360](https://wiki.adom.inc/adom/adom-desktop-fusion-bridge)**: `plugins/fusion360/` — Python, simpler, command-passthrough to Fusion add-in\n\n## Get listed\n\nOnce your bridge is stable, add it to the **[bridges registry](https://wiki.adom.inc/adom/adom-desktop-bridges-registry)** — that's the canonical machine-readable directory the GUI's Browse tab parses to discover what's installable. Two paths:\n\n1. **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.adom.inc/adom/adom-desktop-bridges-registry#how-to-submit-a-bridge) for the row format + review criteria.\n2. **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.", + "author": { + "id": "695820315b5f1e4db2fcf602", + "name": "Kyle Bergstedt", + "email": "[email protected]" + }, + "visibility": { + "public": true + }, + "hero": { + "type": "image", + "path": "hero-v2.png" + }, + "metadata": {}, + "created_at": "2026-05-28T05:28:40.558Z", + "updated_at": "2026-06-26T16:11:19.648Z", + "skills": [], + "author_name": "John Lauer", + "changelog": "Set descriptive title to the Adom Desktop - <name> naming convention for app-index consistency", + "content": "# Adom Desktop - Bridge SDK\n\n## Build your own bridge\n\nA **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 — including a cloud Claude session. If you can write a 50-line HTTP server in any language, you can ship a bridge.\n\nThe 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.\n\n### Starting prompt (paste into a cloud Claude session)\n\n```\nI want to build an Adom Desktop bridge for <YOUR APP / DEVICE / SERVICE>.\n\nA bridge is a small local HTTP server described by a bridge.json; AD spawns it on a\nstable port and relays verbs to it. Before writing anything, study the reference\nbridges for design ideas and copy the conventions you see:\n\n - START SIMPLE — read the two \"hello\" reference bridges first (one Rust, one\n Python). They are the minimal, correct shape: a /command POST handler, a /status\n health endpoint that self-reports its LED, and a bridge.json. Mirror them.\n - THEN study an advanced bridge for the patterns you need: a long-running native\n app (KiCad / Fusion 360), a process you supervise, or an external client that\n dials back in (native-browser). Steal their bridge.json fields and verb shapes.\n\nMy bridge should expose these verbs: <list them, e.g. foo_status, foo_do_thing>.\n\nFollow the Adom Desktop Bridge SDK conventions on this page:\n - Self-report {led, summary, tooltip} from the health endpoint (AD is a pure\n renderer — it never invents your status).\n - Return rich, structured JSON with an _hint on every response so the calling AI\n knows what to do next; on a slow operation, return a statusVerb to poll.\n - Declare statusVerb in bridge.json if any verb can run longer than ~60s (AD will\n relay it so a timeout reads as \"still running, poll this\", not \"failed\").\n\nWhen it works locally, package it with release-bridge.sh and tell me the\nbridge_install command so I can install it on my Adom Desktop from here.\n```\n\nClaude 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.\n\n### Reference bridges — copy these\n\nStart with a **hello** bridge (minimal + correct), then borrow from an **advanced** one for the hard parts.\n\n| Bridge | Language | Why read it |\n|---|---|---|\n| **hello-python** | Python | The minimal bridge: `/command` + `/status` self-report + `bridge.json`. The shape every bridge follows. |\n| **hello-rust** | Rust | Same minimal shape in Rust — single-binary, no runtime deps. |\n| **native-browser** | — | Advanced: an **external client** (a Chrome extension) dials back into the bridge; shows the stable-port + reconnect pattern. |\n| **kicad** / **fusion360** | Python | Advanced: supervising a **long-running native app**, multi-screenshot arrays, `statusVerb` for slow cloud operations. |\n\nThe 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).\n\n### The whole dev loop is remote — install & uninstall from anywhere\n\nThis 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:\n\n```bash\n# Install (or update) a bridge from its published wiki manifest:\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki.adom.inc/api/v1/pages/<your-bridge-page>/files/<your>-bridge-manifest.json\"}'\n\n# It appears in AD's Bridges panel immediately and starts on first verb call.\nadom-desktop bridge_list # see it registered + its LED\nadom-desktop <your_verb> '{...}' # call it — AD spawns + relays\n\n# Iterate: re-publish a higher version, then re-install — AD reaps the old\n# instance and streams the new one onto the SAME stable port (no port churn).\nadom-desktop bridge_install '{\"manifestUrl\":\"...\",\"force\":true}'\n\n# Remove it cleanly when done:\nadom-desktop bridge_uninstall '{\"name\":\"<your-bridge>\"}'\n```\n\nIn 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).\n\nThat's the full toolchain — author in a cloud prompt, publish once, `bridge_install` from anywhere, iterate by re-installing. No RDP, no copying files to the desktop.\n\n---\n\n\n\n\n\n\n\n\n> **Public version of this guide:** [`apps/adom-desktop-bridges` on the Adom Wiki](https://wiki.adom.inc/adom/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>`.\n\nv1.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.\n\n## Try it now — the `hello-python` and `hello-rust` sample bridges\n\nTwo 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.\n\n| Flavor | Manifest URL | Verb prefix | Port | When to fork |\n|---|---|---|---|---|\n| **Python** (stdlib only, ~80 lines) | `…/hello-python-bridge-manifest.json` | `hellopy_` | 8890 | Vendor has Python bindings, fast dev iter, I/O-bound work |\n| **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 |\n\nBoth manifest URLs are under `https://wiki.adom.inc/api/v1/pages/<bridge-slug>/files/`.\n\n### Install from the GUI\n\n1. Click **+ Bridge** in Adom Desktop's bridge bar\n2. Paste one (or both) of the manifest URLs into the dialog, click **Install**\n3. New chips appear ~2 seconds later with a small ✦ marker (third-party indicator)\n\n### Install both from the CLI\n\n```bash\n# Install the Python flavor\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki.adom.inc/api/v1/pages/adom-desktop-hello-python-bridge/files/adom-bridge-hello-python-manifest.json\"}'\nadom-desktop hellopy_ping\n# → {\"success\":true,\"output\":\"Hello from the Python sample bridge v1.1.0!\",\"language\":\"python\",...}\n\n# Install the Rust flavor (can coexist with hello-python)\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki.adom.inc/api/v1/pages/adom-desktop-hello-rust-bridge/files/adom-bridge-hello-rust-manifest.json\"}'\nadom-desktop hellors_ping\n# → {\"success\":true,\"output\":\"Hello from the Rust sample bridge v1.1.0!\",\"language\":\"rust\",...}\n\n# Echo through both\nadom-desktop hellopy_echo '{\"message\":\"world\"}'\n# → {\"success\":true,\"output\":\"echo (python): world\",...}\nadom-desktop hellors_echo '{\"message\":\"world\"}'\n# → {\"success\":true,\"output\":\"echo (rust): world\",...}\n\n# Both appear in bridge_list\nadom-desktop bridge_list\n# → bundled three + \"hello-python\" + \"hello-rust\"\n```\n\n### Uninstall\n\n```bash\nadom-desktop bridge_uninstall '{\"name\":\"hello-python\"}'\nadom-desktop bridge_uninstall '{\"name\":\"hello-rust\"}'\n```\n\nEach bridge's cache dir + registry entry are removed; the chips disappear from the bar.\n\nThe sources live at:\n- [`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`)\n- [`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)\n\nFork whichever flavor matches your target's natural ecosystem.\n\n## Directory layout\n\n```\nmy-bridge/\n├── bridge.json ← REQUIRED — schema below\n├── BRIDGE_VERSION ← optional, semver line (kept in sync with bridge.json's version field)\n├── server.py ← or server.js, or my-bridge.exe — your bridge's entrypoint\n├── handlers/ ← your code\n│ ├── ...\n└── README.md ← optional, recommended\n```\n\n## `bridge.json` schema\n\n```json\n{\n \"manifest_version\": 1,\n \"name\": \"altium\",\n \"displayName\": \"Altium Designer\",\n \"version\": \"1.0.0\",\n \"description\": \"One-line description shown in bridge_list output.\",\n \"homepage\": \"https://github.com/your-org/adom-bridge-altium\",\n \"author\": \"Your Org\",\n \"license\": \"MIT\",\n \"spawn\": {\n \"kind\": \"python\",\n \"entrypoint\": \"server.py\",\n \"port\": 0,\n \"healthEndpoint\": \"/status\",\n \"stopMethod\": \"kill\",\n \"killImageName\": \"python.exe\"\n },\n \"verbPrefixes\": [\"altium_\"],\n \"verbs\": [\n \"altium_open_project\",\n \"altium_export_step\",\n \"altium_run_drc\"\n ],\n \"dependencies\": {\n \"python\": \">=3.11\",\n \"altium-designer\": \">=24\"\n },\n \"category\": \"EDA\",\n \"tags\": [\"pcb\", \"altium\", \"commercial\"]\n}\n```\n\n### Field reference\n\n- **`name`** (required, lowercase, no spaces). The directory name + cache key. Must be unique across the registry.\n- **`displayName`** — human-friendly shown in `bridge_list` + future GUI bridge panel.\n- **`version`** (required, semver). Each ship bumps this.\n- **`spawn.kind`** — `python` | `node` | `exe`. Determines how Adom Desktop launches the bridge.\n - `python` → Adom Desktop finds Python in PATH and runs `python <entrypoint> --port <port>`. Good when the vendor's SDK has Python bindings.\n - `node` → Adom Desktop finds Node in PATH and runs `node <entrypoint> --port <port>`. Good for ecosystem reuse (Puppeteer, Playwright, etc.).\n - `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.\n- **`spawn.entrypoint`** — relative path inside the bridge dir (e.g. `server.py`, `server.js`, `my-bridge.exe`).\n- **`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.\n- **`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.\n- **`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).\n- **`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).\n- **`verbPrefixes`** — list of CLI verb prefixes this bridge owns (e.g. `[\"altium_\"]`). All `altium_*` verbs route here.\n- **`verbs`** — explicit verb list. Used in `bridge_list` to advertise capabilities; doesn't affect routing.\n\n## Packaging + shipping\n\nUse the same `scripts/release-bridge.sh` flow Adom Desktop uses for its own bridges. Adapted for third parties:\n\n```bash\n# In your bridge repo:\npython3 -c \"\nimport zipfile, os, fnmatch\nEXCLUDE = ['*/__pycache__/*', '*.pyc', '.history/*', '*/node_modules/*']\nwith zipfile.ZipFile('my-bridge-v1.0.0.zip', 'w', zipfile.ZIP_DEFLATED) as zf:\n for root, dirs, files in os.walk('.'):\n for f in files:\n rel = os.path.relpath(os.path.join(root, f), '.')\n if any(fnmatch.fnmatch(rel.replace(os.sep, '/'), g) for g in EXCLUDE):\n continue\n zf.write(os.path.join(root, f), rel)\n\"\n\n# Compute SHA256 of the zip\nsha256sum my-bridge-v1.0.0.zip\n\n# Write the manifest the GUI polls\ncat > my-bridge-manifest.json <<EOF\n{\n \"manifest_version\": 1,\n \"version\": \"1.0.0\",\n \"url\": \"https://your-host/my-bridge-v1.0.0.zip\",\n \"sha256\": \"<sha256 from above>\",\n \"size\": <bytes>,\n \"released_at\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"\n}\nEOF\n```\n\nHost both files anywhere reachable by HTTPS — GitHub Pages, your own CDN, the Adom Wiki if you contribute to the official registry.\n\n## Installing\n\nUsers install your bridge with one command:\n\n```bash\nadom-desktop bridge_install '{\"manifestUrl\":\"https://your-host/my-bridge-manifest.json\"}'\n```\n\nThe GUI:\n1. Fetches `my-bridge-manifest.json`\n2. Downloads the referenced zip\n3. SHA256-verifies against the manifest\n4. Extracts to `%LOCALAPPDATA%\\Adom Desktop\\bridges-cache\\<name>\\` (the `name` comes from your `bridge.json`)\n5. Rescans the runtime registry — your bridge is now discoverable via `bridge_list`\n\nThe next CLI call to one of your `verbPrefixes` auto-spawns the bridge process.\n\n## Updating\n\nBump `bridge.json`'s `version` + `BRIDGE_VERSION`, rebuild the zip, re-upload, update the manifest. Users:\n\n```bash\nadom-desktop refresh_bridges # picks up new versions from manifest URLs they previously installed from\n```\n\n(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.)\n\n## Lifecycle controls (from cloud Docker, or local GUI)\n\n```bash\nadom-desktop bridge_list # see everything\nadom-desktop bridge_pause '{\"name\":\"altium\"}' # stop routing verbs\nadom-desktop bridge_resume '{\"name\":\"altium\"}'\nadom-desktop bridge_uninstall '{\"name\":\"altium\"}' # remove from cache\n```\n\n## Trust + security\n\nFor 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.\n\nFuture work (v1.9.0+):\n- Optional Ed25519 signing of bridge zips with the Adom-Inc public key for \"verified\" status\n- Per-bridge sandboxing (limited filesystem / network access declared in manifest `permissions` field)\n- Community submission flow on the wiki so contributors don't need a GitHub account\n\n## Reference implementations\n\nEvery bridge below has its own dedicated wiki page with hero, install/uninstall recipes, and a \"fork this\" walkthrough. The **[bridges registry](https://wiki.adom.inc/adom/adom-desktop-bridges-registry)** is the canonical directory — both humans browse it and the GUI's Browse tab parses it.\n\n**Hello-world starter templates** — fork these for a 5-minute \"does this thing work?\" loop:\n- **[hello-python](https://wiki.adom.inc/adom/adom-desktop-hello-python-bridge)**: `scripts/sample-bridges/hello-python/` — ~80 lines, stdlib only, `kind:python`\n- **[hello-rust](https://wiki.adom.inc/adom/adom-desktop-hello-rust-bridge)**: `scripts/sample-bridges/hello-rust/` — ~140 lines, `tiny_http` + serde, `kind:exe`, single 300-KB static binary\n\n**Adom-shipped bridges** — canonical full-complexity examples; fork their layout for a real vendor integration:\n- **[kicad](https://wiki.adom.inc/adom/adom-desktop-kicad-bridge)**: `plugins/kicad/` — Python, complex, multi-instance (one bridge per KiCad GUI exe)\n- **[puppeteer](https://wiki.adom.inc/adom/adom-desktop-puppeteer-bridge)**: `plugins/puppeteer/` — Node, single-instance, recording-capable\n- **[fusion360](https://wiki.adom.inc/adom/adom-desktop-fusion-bridge)**: `plugins/fusion360/` — Python, simpler, command-passthrough to Fusion add-in\n\n## Get listed\n\nOnce your bridge is stable, add it to the **[bridges registry](https://wiki.adom.inc/adom/adom-desktop-bridges-registry)** — that's the canonical machine-readable directory the GUI's Browse tab parses to discover what's installable. Two paths:\n\n1. **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.adom.inc/adom/adom-desktop-bridges-registry#how-to-submit-a-bridge) for the row format + review criteria.\n2. **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.", + "body_md": "# Adom Desktop - Bridge SDK\n\n## Build your own bridge\n\nA **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 — including a cloud Claude session. If you can write a 50-line HTTP server in any language, you can ship a bridge.\n\nThe 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.\n\n### Starting prompt (paste into a cloud Claude session)\n\n```\nI want to build an Adom Desktop bridge for <YOUR APP / DEVICE / SERVICE>.\n\nA bridge is a small local HTTP server described by a bridge.json; AD spawns it on a\nstable port and relays verbs to it. Before writing anything, study the reference\nbridges for design ideas and copy the conventions you see:\n\n - START SIMPLE — read the two \"hello\" reference bridges first (one Rust, one\n Python). They are the minimal, correct shape: a /command POST handler, a /status\n health endpoint that self-reports its LED, and a bridge.json. Mirror them.\n - THEN study an advanced bridge for the patterns you need: a long-running native\n app (KiCad / Fusion 360), a process you supervise, or an external client that\n dials back in (native-browser). Steal their bridge.json fields and verb shapes.\n\nMy bridge should expose these verbs: <list them, e.g. foo_status, foo_do_thing>.\n\nFollow the Adom Desktop Bridge SDK conventions on this page:\n - Self-report {led, summary, tooltip} from the health endpoint (AD is a pure\n renderer — it never invents your status).\n - Return rich, structured JSON with an _hint on every response so the calling AI\n knows what to do next; on a slow operation, return a statusVerb to poll.\n - Declare statusVerb in bridge.json if any verb can run longer than ~60s (AD will\n relay it so a timeout reads as \"still running, poll this\", not \"failed\").\n\nWhen it works locally, package it with release-bridge.sh and tell me the\nbridge_install command so I can install it on my Adom Desktop from here.\n```\n\nClaude 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.\n\n### Reference bridges — copy these\n\nStart with a **hello** bridge (minimal + correct), then borrow from an **advanced** one for the hard parts.\n\n| Bridge | Language | Why read it |\n|---|---|---|\n| **hello-python** | Python | The minimal bridge: `/command` + `/status` self-report + `bridge.json`. The shape every bridge follows. |\n| **hello-rust** | Rust | Same minimal shape in Rust — single-binary, no runtime deps. |\n| **native-browser** | — | Advanced: an **external client** (a Chrome extension) dials back into the bridge; shows the stable-port + reconnect pattern. |\n| **kicad** / **fusion360** | Python | Advanced: supervising a **long-running native app**, multi-screenshot arrays, `statusVerb` for slow cloud operations. |\n\nThe 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).\n\n### The whole dev loop is remote — install & uninstall from anywhere\n\nThis 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:\n\n```bash\n# Install (or update) a bridge from its published wiki manifest:\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki.adom.inc/api/v1/pages/<your-bridge-page>/files/<your>-bridge-manifest.json\"}'\n\n# It appears in AD's Bridges panel immediately and starts on first verb call.\nadom-desktop bridge_list # see it registered + its LED\nadom-desktop <your_verb> '{...}' # call it — AD spawns + relays\n\n# Iterate: re-publish a higher version, then re-install — AD reaps the old\n# instance and streams the new one onto the SAME stable port (no port churn).\nadom-desktop bridge_install '{\"manifestUrl\":\"...\",\"force\":true}'\n\n# Remove it cleanly when done:\nadom-desktop bridge_uninstall '{\"name\":\"<your-bridge>\"}'\n```\n\nIn 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).\n\nThat's the full toolchain — author in a cloud prompt, publish once, `bridge_install` from anywhere, iterate by re-installing. No RDP, no copying files to the desktop.\n\n---\n\n\n\n\n\n\n\n\n> **Public version of this guide:** [`apps/adom-desktop-bridges` on the Adom Wiki](https://wiki.adom.inc/adom/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>`.\n\nv1.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.\n\n## Try it now — the `hello-python` and `hello-rust` sample bridges\n\nTwo 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.\n\n| Flavor | Manifest URL | Verb prefix | Port | When to fork |\n|---|---|---|---|---|\n| **Python** (stdlib only, ~80 lines) | `…/hello-python-bridge-manifest.json` | `hellopy_` | 8890 | Vendor has Python bindings, fast dev iter, I/O-bound work |\n| **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 |\n\nBoth manifest URLs are under `https://wiki.adom.inc/api/v1/pages/<bridge-slug>/files/`.\n\n### Install from the GUI\n\n1. Click **+ Bridge** in Adom Desktop's bridge bar\n2. Paste one (or both) of the manifest URLs into the dialog, click **Install**\n3. New chips appear ~2 seconds later with a small ✦ marker (third-party indicator)\n\n### Install both from the CLI\n\n```bash\n# Install the Python flavor\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki.adom.inc/api/v1/pages/adom-desktop-hello-python-bridge/files/adom-bridge-hello-python-manifest.json\"}'\nadom-desktop hellopy_ping\n# → {\"success\":true,\"output\":\"Hello from the Python sample bridge v1.1.0!\",\"language\":\"python\",...}\n\n# Install the Rust flavor (can coexist with hello-python)\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki.adom.inc/api/v1/pages/adom-desktop-hello-rust-bridge/files/adom-bridge-hello-rust-manifest.json\"}'\nadom-desktop hellors_ping\n# → {\"success\":true,\"output\":\"Hello from the Rust sample bridge v1.1.0!\",\"language\":\"rust\",...}\n\n# Echo through both\nadom-desktop hellopy_echo '{\"message\":\"world\"}'\n# → {\"success\":true,\"output\":\"echo (python): world\",...}\nadom-desktop hellors_echo '{\"message\":\"world\"}'\n# → {\"success\":true,\"output\":\"echo (rust): world\",...}\n\n# Both appear in bridge_list\nadom-desktop bridge_list\n# → bundled three + \"hello-python\" + \"hello-rust\"\n```\n\n### Uninstall\n\n```bash\nadom-desktop bridge_uninstall '{\"name\":\"hello-python\"}'\nadom-desktop bridge_uninstall '{\"name\":\"hello-rust\"}'\n```\n\nEach bridge's cache dir + registry entry are removed; the chips disappear from the bar.\n\nThe sources live at:\n- [`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`)\n- [`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)\n\nFork whichever flavor matches your target's natural ecosystem.\n\n## Directory layout\n\n```\nmy-bridge/\n├── bridge.json ← REQUIRED — schema below\n├── BRIDGE_VERSION ← optional, semver line (kept in sync with bridge.json's version field)\n├── server.py ← or server.js, or my-bridge.exe — your bridge's entrypoint\n├── handlers/ ← your code\n│ ├── ...\n└── README.md ← optional, recommended\n```\n\n## `bridge.json` schema\n\n```json\n{\n \"manifest_version\": 1,\n \"name\": \"altium\",\n \"displayName\": \"Altium Designer\",\n \"version\": \"1.0.0\",\n \"description\": \"One-line description shown in bridge_list output.\",\n \"homepage\": \"https://github.com/your-org/adom-bridge-altium\",\n \"author\": \"Your Org\",\n \"license\": \"MIT\",\n \"spawn\": {\n \"kind\": \"python\",\n \"entrypoint\": \"server.py\",\n \"port\": 0,\n \"healthEndpoint\": \"/status\",\n \"stopMethod\": \"kill\",\n \"killImageName\": \"python.exe\"\n },\n \"verbPrefixes\": [\"altium_\"],\n \"verbs\": [\n \"altium_open_project\",\n \"altium_export_step\",\n \"altium_run_drc\"\n ],\n \"dependencies\": {\n \"python\": \">=3.11\",\n \"altium-designer\": \">=24\"\n },\n \"category\": \"EDA\",\n \"tags\": [\"pcb\", \"altium\", \"commercial\"]\n}\n```\n\n### Field reference\n\n- **`name`** (required, lowercase, no spaces). The directory name + cache key. Must be unique across the registry.\n- **`displayName`** — human-friendly shown in `bridge_list` + future GUI bridge panel.\n- **`version`** (required, semver). Each ship bumps this.\n- **`spawn.kind`** — `python` | `node` | `exe`. Determines how Adom Desktop launches the bridge.\n - `python` → Adom Desktop finds Python in PATH and runs `python <entrypoint> --port <port>`. Good when the vendor's SDK has Python bindings.\n - `node` → Adom Desktop finds Node in PATH and runs `node <entrypoint> --port <port>`. Good for ecosystem reuse (Puppeteer, Playwright, etc.).\n - `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.\n- **`spawn.entrypoint`** — relative path inside the bridge dir (e.g. `server.py`, `server.js`, `my-bridge.exe`).\n- **`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.\n- **`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.\n- **`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).\n- **`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).\n- **`verbPrefixes`** — list of CLI verb prefixes this bridge owns (e.g. `[\"altium_\"]`). All `altium_*` verbs route here.\n- **`verbs`** — explicit verb list. Used in `bridge_list` to advertise capabilities; doesn't affect routing.\n\n## Packaging + shipping\n\nUse the same `scripts/release-bridge.sh` flow Adom Desktop uses for its own bridges. Adapted for third parties:\n\n```bash\n# In your bridge repo:\npython3 -c \"\nimport zipfile, os, fnmatch\nEXCLUDE = ['*/__pycache__/*', '*.pyc', '.history/*', '*/node_modules/*']\nwith zipfile.ZipFile('my-bridge-v1.0.0.zip', 'w', zipfile.ZIP_DEFLATED) as zf:\n for root, dirs, files in os.walk('.'):\n for f in files:\n rel = os.path.relpath(os.path.join(root, f), '.')\n if any(fnmatch.fnmatch(rel.replace(os.sep, '/'), g) for g in EXCLUDE):\n continue\n zf.write(os.path.join(root, f), rel)\n\"\n\n# Compute SHA256 of the zip\nsha256sum my-bridge-v1.0.0.zip\n\n# Write the manifest the GUI polls\ncat > my-bridge-manifest.json <<EOF\n{\n \"manifest_version\": 1,\n \"version\": \"1.0.0\",\n \"url\": \"https://your-host/my-bridge-v1.0.0.zip\",\n \"sha256\": \"<sha256 from above>\",\n \"size\": <bytes>,\n \"released_at\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"\n}\nEOF\n```\n\nHost both files anywhere reachable by HTTPS — GitHub Pages, your own CDN, the Adom Wiki if you contribute to the official registry.\n\n## Installing\n\nUsers install your bridge with one command:\n\n```bash\nadom-desktop bridge_install '{\"manifestUrl\":\"https://your-host/my-bridge-manifest.json\"}'\n```\n\nThe GUI:\n1. Fetches `my-bridge-manifest.json`\n2. Downloads the referenced zip\n3. SHA256-verifies against the manifest\n4. Extracts to `%LOCALAPPDATA%\\Adom Desktop\\bridges-cache\\<name>\\` (the `name` comes from your `bridge.json`)\n5. Rescans the runtime registry — your bridge is now discoverable via `bridge_list`\n\nThe next CLI call to one of your `verbPrefixes` auto-spawns the bridge process.\n\n## Updating\n\nBump `bridge.json`'s `version` + `BRIDGE_VERSION`, rebuild the zip, re-upload, update the manifest. Users:\n\n```bash\nadom-desktop refresh_bridges # picks up new versions from manifest URLs they previously installed from\n```\n\n(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.)\n\n## Lifecycle controls (from cloud Docker, or local GUI)\n\n```bash\nadom-desktop bridge_list # see everything\nadom-desktop bridge_pause '{\"name\":\"altium\"}' # stop routing verbs\nadom-desktop bridge_resume '{\"name\":\"altium\"}'\nadom-desktop bridge_uninstall '{\"name\":\"altium\"}' # remove from cache\n```\n\n## Trust + security\n\nFor 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.\n\nFuture work (v1.9.0+):\n- Optional Ed25519 signing of bridge zips with the Adom-Inc public key for \"verified\" status\n- Per-bridge sandboxing (limited filesystem / network access declared in manifest `permissions` field)\n- Community submission flow on the wiki so contributors don't need a GitHub account\n\n## Reference implementations\n\nEvery bridge below has its own dedicated wiki page with hero, install/uninstall recipes, and a \"fork this\" walkthrough. The **[bridges registry](https://wiki.adom.inc/adom/adom-desktop-bridges-registry)** is the canonical directory — both humans browse it and the GUI's Browse tab parses it.\n\n**Hello-world starter templates** — fork these for a 5-minute \"does this thing work?\" loop:\n- **[hello-python](https://wiki.adom.inc/adom/adom-desktop-hello-python-bridge)**: `scripts/sample-bridges/hello-python/` — ~80 lines, stdlib only, `kind:python`\n- **[hello-rust](https://wiki.adom.inc/adom/adom-desktop-hello-rust-bridge)**: `scripts/sample-bridges/hello-rust/` — ~140 lines, `tiny_http` + serde, `kind:exe`, single 300-KB static binary\n\n**Adom-shipped bridges** — canonical full-complexity examples; fork their layout for a real vendor integration:\n- **[kicad](https://wiki.adom.inc/adom/adom-desktop-kicad-bridge)**: `plugins/kicad/` — Python, complex, multi-instance (one bridge per KiCad GUI exe)\n- **[puppeteer](https://wiki.adom.inc/adom/adom-desktop-puppeteer-bridge)**: `plugins/puppeteer/` — Node, single-instance, recording-capable\n- **[fusion360](https://wiki.adom.inc/adom/adom-desktop-fusion-bridge)**: `plugins/fusion360/` — Python, simpler, command-passthrough to Fusion add-in\n\n## Get listed\n\nOnce your bridge is stable, add it to the **[bridges registry](https://wiki.adom.inc/adom/adom-desktop-bridges-registry)** — that's the canonical machine-readable directory the GUI's Browse tab parses to discover what's installable. Two paths:\n\n1. **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.adom.inc/adom/adom-desktop-bridges-registry#how-to-submit-a-bridge) for the row format + review criteria.\n2. **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.", + "markdown": "# Adom Desktop - Bridge SDK\n\n## Build your own bridge\n\nA **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 — including a cloud Claude session. If you can write a 50-line HTTP server in any language, you can ship a bridge.\n\nThe 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.\n\n### Starting prompt (paste into a cloud Claude session)\n\n```\nI want to build an Adom Desktop bridge for <YOUR APP / DEVICE / SERVICE>.\n\nA bridge is a small local HTTP server described by a bridge.json; AD spawns it on a\nstable port and relays verbs to it. Before writing anything, study the reference\nbridges for design ideas and copy the conventions you see:\n\n - START SIMPLE — read the two \"hello\" reference bridges first (one Rust, one\n Python). They are the minimal, correct shape: a /command POST handler, a /status\n health endpoint that self-reports its LED, and a bridge.json. Mirror them.\n - THEN study an advanced bridge for the patterns you need: a long-running native\n app (KiCad / Fusion 360), a process you supervise, or an external client that\n dials back in (native-browser). Steal their bridge.json fields and verb shapes.\n\nMy bridge should expose these verbs: <list them, e.g. foo_status, foo_do_thing>.\n\nFollow the Adom Desktop Bridge SDK conventions on this page:\n - Self-report {led, summary, tooltip} from the health endpoint (AD is a pure\n renderer — it never invents your status).\n - Return rich, structured JSON with an _hint on every response so the calling AI\n knows what to do next; on a slow operation, return a statusVerb to poll.\n - Declare statusVerb in bridge.json if any verb can run longer than ~60s (AD will\n relay it so a timeout reads as \"still running, poll this\", not \"failed\").\n\nWhen it works locally, package it with release-bridge.sh and tell me the\nbridge_install command so I can install it on my Adom Desktop from here.\n```\n\nClaude 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.\n\n### Reference bridges — copy these\n\nStart with a **hello** bridge (minimal + correct), then borrow from an **advanced** one for the hard parts.\n\n| Bridge | Language | Why read it |\n|---|---|---|\n| **hello-python** | Python | The minimal bridge: `/command` + `/status` self-report + `bridge.json`. The shape every bridge follows. |\n| **hello-rust** | Rust | Same minimal shape in Rust — single-binary, no runtime deps. |\n| **native-browser** | — | Advanced: an **external client** (a Chrome extension) dials back into the bridge; shows the stable-port + reconnect pattern. |\n| **kicad** / **fusion360** | Python | Advanced: supervising a **long-running native app**, multi-screenshot arrays, `statusVerb` for slow cloud operations. |\n\nThe 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).\n\n### The whole dev loop is remote — install & uninstall from anywhere\n\nThis 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:\n\n```bash\n# Install (or update) a bridge from its published wiki manifest:\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki.adom.inc/api/v1/pages/<your-bridge-page>/files/<your>-bridge-manifest.json\"}'\n\n# It appears in AD's Bridges panel immediately and starts on first verb call.\nadom-desktop bridge_list # see it registered + its LED\nadom-desktop <your_verb> '{...}' # call it — AD spawns + relays\n\n# Iterate: re-publish a higher version, then re-install — AD reaps the old\n# instance and streams the new one onto the SAME stable port (no port churn).\nadom-desktop bridge_install '{\"manifestUrl\":\"...\",\"force\":true}'\n\n# Remove it cleanly when done:\nadom-desktop bridge_uninstall '{\"name\":\"<your-bridge>\"}'\n```\n\nIn 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).\n\nThat's the full toolchain — author in a cloud prompt, publish once, `bridge_install` from anywhere, iterate by re-installing. No RDP, no copying files to the desktop.\n\n---\n\n\n\n\n\n\n\n\n> **Public version of this guide:** [`apps/adom-desktop-bridges` on the Adom Wiki](https://wiki.adom.inc/adom/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>`.\n\nv1.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.\n\n## Try it now — the `hello-python` and `hello-rust` sample bridges\n\nTwo 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.\n\n| Flavor | Manifest URL | Verb prefix | Port | When to fork |\n|---|---|---|---|---|\n| **Python** (stdlib only, ~80 lines) | `…/hello-python-bridge-manifest.json` | `hellopy_` | 8890 | Vendor has Python bindings, fast dev iter, I/O-bound work |\n| **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 |\n\nBoth manifest URLs are under `https://wiki.adom.inc/api/v1/pages/<bridge-slug>/files/`.\n\n### Install from the GUI\n\n1. Click **+ Bridge** in Adom Desktop's bridge bar\n2. Paste one (or both) of the manifest URLs into the dialog, click **Install**\n3. New chips appear ~2 seconds later with a small ✦ marker (third-party indicator)\n\n### Install both from the CLI\n\n```bash\n# Install the Python flavor\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki.adom.inc/api/v1/pages/adom-desktop-hello-python-bridge/files/adom-bridge-hello-python-manifest.json\"}'\nadom-desktop hellopy_ping\n# → {\"success\":true,\"output\":\"Hello from the Python sample bridge v1.1.0!\",\"language\":\"python\",...}\n\n# Install the Rust flavor (can coexist with hello-python)\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki.adom.inc/api/v1/pages/adom-desktop-hello-rust-bridge/files/adom-bridge-hello-rust-manifest.json\"}'\nadom-desktop hellors_ping\n# → {\"success\":true,\"output\":\"Hello from the Rust sample bridge v1.1.0!\",\"language\":\"rust\",...}\n\n# Echo through both\nadom-desktop hellopy_echo '{\"message\":\"world\"}'\n# → {\"success\":true,\"output\":\"echo (python): world\",...}\nadom-desktop hellors_echo '{\"message\":\"world\"}'\n# → {\"success\":true,\"output\":\"echo (rust): world\",...}\n\n# Both appear in bridge_list\nadom-desktop bridge_list\n# → bundled three + \"hello-python\" + \"hello-rust\"\n```\n\n### Uninstall\n\n```bash\nadom-desktop bridge_uninstall '{\"name\":\"hello-python\"}'\nadom-desktop bridge_uninstall '{\"name\":\"hello-rust\"}'\n```\n\nEach bridge's cache dir + registry entry are removed; the chips disappear from the bar.\n\nThe sources live at:\n- [`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`)\n- [`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)\n\nFork whichever flavor matches your target's natural ecosystem.\n\n## Directory layout\n\n```\nmy-bridge/\n├── bridge.json ← REQUIRED — schema below\n├── BRIDGE_VERSION ← optional, semver line (kept in sync with bridge.json's version field)\n├── server.py ← or server.js, or my-bridge.exe — your bridge's entrypoint\n├── handlers/ ← your code\n│ ├── ...\n└── README.md ← optional, recommended\n```\n\n## `bridge.json` schema\n\n```json\n{\n \"manifest_version\": 1,\n \"name\": \"altium\",\n \"displayName\": \"Altium Designer\",\n \"version\": \"1.0.0\",\n \"description\": \"One-line description shown in bridge_list output.\",\n \"homepage\": \"https://github.com/your-org/adom-bridge-altium\",\n \"author\": \"Your Org\",\n \"license\": \"MIT\",\n \"spawn\": {\n \"kind\": \"python\",\n \"entrypoint\": \"server.py\",\n \"port\": 0,\n \"healthEndpoint\": \"/status\",\n \"stopMethod\": \"kill\",\n \"killImageName\": \"python.exe\"\n },\n \"verbPrefixes\": [\"altium_\"],\n \"verbs\": [\n \"altium_open_project\",\n \"altium_export_step\",\n \"altium_run_drc\"\n ],\n \"dependencies\": {\n \"python\": \">=3.11\",\n \"altium-designer\": \">=24\"\n },\n \"category\": \"EDA\",\n \"tags\": [\"pcb\", \"altium\", \"commercial\"]\n}\n```\n\n### Field reference\n\n- **`name`** (required, lowercase, no spaces). The directory name + cache key. Must be unique across the registry.\n- **`displayName`** — human-friendly shown in `bridge_list` + future GUI bridge panel.\n- **`version`** (required, semver). Each ship bumps this.\n- **`spawn.kind`** — `python` | `node` | `exe`. Determines how Adom Desktop launches the bridge.\n - `python` → Adom Desktop finds Python in PATH and runs `python <entrypoint> --port <port>`. Good when the vendor's SDK has Python bindings.\n - `node` → Adom Desktop finds Node in PATH and runs `node <entrypoint> --port <port>`. Good for ecosystem reuse (Puppeteer, Playwright, etc.).\n - `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.\n- **`spawn.entrypoint`** — relative path inside the bridge dir (e.g. `server.py`, `server.js`, `my-bridge.exe`).\n- **`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.\n- **`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.\n- **`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).\n- **`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).\n- **`verbPrefixes`** — list of CLI verb prefixes this bridge owns (e.g. `[\"altium_\"]`). All `altium_*` verbs route here.\n- **`verbs`** — explicit verb list. Used in `bridge_list` to advertise capabilities; doesn't affect routing.\n\n## Packaging + shipping\n\nUse the same `scripts/release-bridge.sh` flow Adom Desktop uses for its own bridges. Adapted for third parties:\n\n```bash\n# In your bridge repo:\npython3 -c \"\nimport zipfile, os, fnmatch\nEXCLUDE = ['*/__pycache__/*', '*.pyc', '.history/*', '*/node_modules/*']\nwith zipfile.ZipFile('my-bridge-v1.0.0.zip', 'w', zipfile.ZIP_DEFLATED) as zf:\n for root, dirs, files in os.walk('.'):\n for f in files:\n rel = os.path.relpath(os.path.join(root, f), '.')\n if any(fnmatch.fnmatch(rel.replace(os.sep, '/'), g) for g in EXCLUDE):\n continue\n zf.write(os.path.join(root, f), rel)\n\"\n\n# Compute SHA256 of the zip\nsha256sum my-bridge-v1.0.0.zip\n\n# Write the manifest the GUI polls\ncat > my-bridge-manifest.json <<EOF\n{\n \"manifest_version\": 1,\n \"version\": \"1.0.0\",\n \"url\": \"https://your-host/my-bridge-v1.0.0.zip\",\n \"sha256\": \"<sha256 from above>\",\n \"size\": <bytes>,\n \"released_at\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"\n}\nEOF\n```\n\nHost both files anywhere reachable by HTTPS — GitHub Pages, your own CDN, the Adom Wiki if you contribute to the official registry.\n\n## Installing\n\nUsers install your bridge with one command:\n\n```bash\nadom-desktop bridge_install '{\"manifestUrl\":\"https://your-host/my-bridge-manifest.json\"}'\n```\n\nThe GUI:\n1. Fetches `my-bridge-manifest.json`\n2. Downloads the referenced zip\n3. SHA256-verifies against the manifest\n4. Extracts to `%LOCALAPPDATA%\\Adom Desktop\\bridges-cache\\<name>\\` (the `name` comes from your `bridge.json`)\n5. Rescans the runtime registry — your bridge is now discoverable via `bridge_list`\n\nThe next CLI call to one of your `verbPrefixes` auto-spawns the bridge process.\n\n## Updating\n\nBump `bridge.json`'s `version` + `BRIDGE_VERSION`, rebuild the zip, re-upload, update the manifest. Users:\n\n```bash\nadom-desktop refresh_bridges # picks up new versions from manifest URLs they previously installed from\n```\n\n(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.)\n\n## Lifecycle controls (from cloud Docker, or local GUI)\n\n```bash\nadom-desktop bridge_list # see everything\nadom-desktop bridge_pause '{\"name\":\"altium\"}' # stop routing verbs\nadom-desktop bridge_resume '{\"name\":\"altium\"}'\nadom-desktop bridge_uninstall '{\"name\":\"altium\"}' # remove from cache\n```\n\n## Trust + security\n\nFor 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.\n\nFuture work (v1.9.0+):\n- Optional Ed25519 signing of bridge zips with the Adom-Inc public key for \"verified\" status\n- Per-bridge sandboxing (limited filesystem / network access declared in manifest `permissions` field)\n- Community submission flow on the wiki so contributors don't need a GitHub account\n\n## Reference implementations\n\nEvery bridge below has its own dedicated wiki page with hero, install/uninstall recipes, and a \"fork this\" walkthrough. The **[bridges registry](https://wiki.adom.inc/adom/adom-desktop-bridges-registry)** is the canonical directory — both humans browse it and the GUI's Browse tab parses it.\n\n**Hello-world starter templates** — fork these for a 5-minute \"does this thing work?\" loop:\n- **[hello-python](https://wiki.adom.inc/adom/adom-desktop-hello-python-bridge)**: `scripts/sample-bridges/hello-python/` — ~80 lines, stdlib only, `kind:python`\n- **[hello-rust](https://wiki.adom.inc/adom/adom-desktop-hello-rust-bridge)**: `scripts/sample-bridges/hello-rust/` — ~140 lines, `tiny_http` + serde, `kind:exe`, single 300-KB static binary\n\n**Adom-shipped bridges** — canonical full-complexity examples; fork their layout for a real vendor integration:\n- **[kicad](https://wiki.adom.inc/adom/adom-desktop-kicad-bridge)**: `plugins/kicad/` — Python, complex, multi-instance (one bridge per KiCad GUI exe)\n- **[puppeteer](https://wiki.adom.inc/adom/adom-desktop-puppeteer-bridge)**: `plugins/puppeteer/` — Node, single-instance, recording-capable\n- **[fusion360](https://wiki.adom.inc/adom/adom-desktop-fusion-bridge)**: `plugins/fusion360/` — Python, simpler, command-passthrough to Fusion add-in\n\n## Get listed\n\nOnce your bridge is stable, add it to the **[bridges registry](https://wiki.adom.inc/adom/adom-desktop-bridges-registry)** — that's the canonical machine-readable directory the GUI's Browse tab parses to discover what's installable. Two paths:\n\n1. **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.adom.inc/adom/adom-desktop-bridges-registry#how-to-submit-a-bridge) for the row format + review criteria.\n2. **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.", + "body": "# Adom Desktop - Bridge SDK\n\n## Build your own bridge\n\nA **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 — including a cloud Claude session. If you can write a 50-line HTTP server in any language, you can ship a bridge.\n\nThe 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.\n\n### Starting prompt (paste into a cloud Claude session)\n\n```\nI want to build an Adom Desktop bridge for <YOUR APP / DEVICE / SERVICE>.\n\nA bridge is a small local HTTP server described by a bridge.json; AD spawns it on a\nstable port and relays verbs to it. Before writing anything, study the reference\nbridges for design ideas and copy the conventions you see:\n\n - START SIMPLE — read the two \"hello\" reference bridges first (one Rust, one\n Python). They are the minimal, correct shape: a /command POST handler, a /status\n health endpoint that self-reports its LED, and a bridge.json. Mirror them.\n - THEN study an advanced bridge for the patterns you need: a long-running native\n app (KiCad / Fusion 360), a process you supervise, or an external client that\n dials back in (native-browser). Steal their bridge.json fields and verb shapes.\n\nMy bridge should expose these verbs: <list them, e.g. foo_status, foo_do_thing>.\n\nFollow the Adom Desktop Bridge SDK conventions on this page:\n - Self-report {led, summary, tooltip} from the health endpoint (AD is a pure\n renderer — it never invents your status).\n - Return rich, structured JSON with an _hint on every response so the calling AI\n knows what to do next; on a slow operation, return a statusVerb to poll.\n - Declare statusVerb in bridge.json if any verb can run longer than ~60s (AD will\n relay it so a timeout reads as \"still running, poll this\", not \"failed\").\n\nWhen it works locally, package it with release-bridge.sh and tell me the\nbridge_install command so I can install it on my Adom Desktop from here.\n```\n\nClaude 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.\n\n### Reference bridges — copy these\n\nStart with a **hello** bridge (minimal + correct), then borrow from an **advanced** one for the hard parts.\n\n| Bridge | Language | Why read it |\n|---|---|---|\n| **hello-python** | Python | The minimal bridge: `/command` + `/status` self-report + `bridge.json`. The shape every bridge follows. |\n| **hello-rust** | Rust | Same minimal shape in Rust — single-binary, no runtime deps. |\n| **native-browser** | — | Advanced: an **external client** (a Chrome extension) dials back into the bridge; shows the stable-port + reconnect pattern. |\n| **kicad** / **fusion360** | Python | Advanced: supervising a **long-running native app**, multi-screenshot arrays, `statusVerb` for slow cloud operations. |\n\nThe 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).\n\n### The whole dev loop is remote — install & uninstall from anywhere\n\nThis 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:\n\n```bash\n# Install (or update) a bridge from its published wiki manifest:\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki.adom.inc/api/v1/pages/<your-bridge-page>/files/<your>-bridge-manifest.json\"}'\n\n# It appears in AD's Bridges panel immediately and starts on first verb call.\nadom-desktop bridge_list # see it registered + its LED\nadom-desktop <your_verb> '{...}' # call it — AD spawns + relays\n\n# Iterate: re-publish a higher version, then re-install — AD reaps the old\n# instance and streams the new one onto the SAME stable port (no port churn).\nadom-desktop bridge_install '{\"manifestUrl\":\"...\",\"force\":true}'\n\n# Remove it cleanly when done:\nadom-desktop bridge_uninstall '{\"name\":\"<your-bridge>\"}'\n```\n\nIn 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).\n\nThat's the full toolchain — author in a cloud prompt, publish once, `bridge_install` from anywhere, iterate by re-installing. No RDP, no copying files to the desktop.\n\n---\n\n\n\n\n\n\n\n\n> **Public version of this guide:** [`apps/adom-desktop-bridges` on the Adom Wiki](https://wiki.adom.inc/adom/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>`.\n\nv1.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.\n\n## Try it now — the `hello-python` and `hello-rust` sample bridges\n\nTwo 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.\n\n| Flavor | Manifest URL | Verb prefix | Port | When to fork |\n|---|---|---|---|---|\n| **Python** (stdlib only, ~80 lines) | `…/hello-python-bridge-manifest.json` | `hellopy_` | 8890 | Vendor has Python bindings, fast dev iter, I/O-bound work |\n| **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 |\n\nBoth manifest URLs are under `https://wiki.adom.inc/api/v1/pages/<bridge-slug>/files/`.\n\n### Install from the GUI\n\n1. Click **+ Bridge** in Adom Desktop's bridge bar\n2. Paste one (or both) of the manifest URLs into the dialog, click **Install**\n3. New chips appear ~2 seconds later with a small ✦ marker (third-party indicator)\n\n### Install both from the CLI\n\n```bash\n# Install the Python flavor\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki.adom.inc/api/v1/pages/adom-desktop-hello-python-bridge/files/adom-bridge-hello-python-manifest.json\"}'\nadom-desktop hellopy_ping\n# → {\"success\":true,\"output\":\"Hello from the Python sample bridge v1.1.0!\",\"language\":\"python\",...}\n\n# Install the Rust flavor (can coexist with hello-python)\nadom-desktop bridge_install '{\"manifestUrl\":\"https://wiki.adom.inc/api/v1/pages/adom-desktop-hello-rust-bridge/files/adom-bridge-hello-rust-manifest.json\"}'\nadom-desktop hellors_ping\n# → {\"success\":true,\"output\":\"Hello from the Rust sample bridge v1.1.0!\",\"language\":\"rust\",...}\n\n# Echo through both\nadom-desktop hellopy_echo '{\"message\":\"world\"}'\n# → {\"success\":true,\"output\":\"echo (python): world\",...}\nadom-desktop hellors_echo '{\"message\":\"world\"}'\n# → {\"success\":true,\"output\":\"echo (rust): world\",...}\n\n# Both appear in bridge_list\nadom-desktop bridge_list\n# → bundled three + \"hello-python\" + \"hello-rust\"\n```\n\n### Uninstall\n\n```bash\nadom-desktop bridge_uninstall '{\"name\":\"hello-python\"}'\nadom-desktop bridge_uninstall '{\"name\":\"hello-rust\"}'\n```\n\nEach bridge's cache dir + registry entry are removed; the chips disappear from the bar.\n\nThe sources live at:\n- [`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`)\n- [`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)\n\nFork whichever flavor matches your target's natural ecosystem.\n\n## Directory layout\n\n```\nmy-bridge/\n├── bridge.json ← REQUIRED — schema below\n├── BRIDGE_VERSION ← optional, semver line (kept in sync with bridge.json's version field)\n├── server.py ← or server.js, or my-bridge.exe — your bridge's entrypoint\n├── handlers/ ← your code\n│ ├── ...\n└── README.md ← optional, recommended\n```\n\n## `bridge.json` schema\n\n```json\n{\n \"manifest_version\": 1,\n \"name\": \"altium\",\n \"displayName\": \"Altium Designer\",\n \"version\": \"1.0.0\",\n \"description\": \"One-line description shown in bridge_list output.\",\n \"homepage\": \"https://github.com/your-org/adom-bridge-altium\",\n \"author\": \"Your Org\",\n \"license\": \"MIT\",\n \"spawn\": {\n \"kind\": \"python\",\n \"entrypoint\": \"server.py\",\n \"port\": 0,\n \"healthEndpoint\": \"/status\",\n \"stopMethod\": \"kill\",\n \"killImageName\": \"python.exe\"\n },\n \"verbPrefixes\": [\"altium_\"],\n \"verbs\": [\n \"altium_open_project\",\n \"altium_export_step\",\n \"altium_run_drc\"\n ],\n \"dependencies\": {\n \"python\": \">=3.11\",\n \"altium-designer\": \">=24\"\n },\n \"category\": \"EDA\",\n \"tags\": [\"pcb\", \"altium\", \"commercial\"]\n}\n```\n\n### Field reference\n\n- **`name`** (required, lowercase, no spaces). The directory name + cache key. Must be unique across the registry.\n- **`displayName`** — human-friendly shown in `bridge_list` + future GUI bridge panel.\n- **`version`** (required, semver). Each ship bumps this.\n- **`spawn.kind`** — `python` | `node` | `exe`. Determines how Adom Desktop launches the bridge.\n - `python` → Adom Desktop finds Python in PATH and runs `python <entrypoint> --port <port>`. Good when the vendor's SDK has Python bindings.\n - `node` → Adom Desktop finds Node in PATH and runs `node <entrypoint> --port <port>`. Good for ecosystem reuse (Puppeteer, Playwright, etc.).\n - `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.\n- **`spawn.entrypoint`** — relative path inside the bridge dir (e.g. `server.py`, `server.js`, `my-bridge.exe`).\n- **`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.\n- **`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.\n- **`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).\n- **`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).\n- **`verbPrefixes`** — list of CLI verb prefixes this bridge owns (e.g. `[\"altium_\"]`). All `altium_*` verbs route here.\n- **`verbs`** — explicit verb list. Used in `bridge_list` to advertise capabilities; doesn't affect routing.\n\n## Packaging + shipping\n\nUse the same `scripts/release-bridge.sh` flow Adom Desktop uses for its own bridges. Adapted for third parties:\n\n```bash\n# In your bridge repo:\npython3 -c \"\nimport zipfile, os, fnmatch\nEXCLUDE = ['*/__pycache__/*', '*.pyc', '.history/*', '*/node_modules/*']\nwith zipfile.ZipFile('my-bridge-v1.0.0.zip', 'w', zipfile.ZIP_DEFLATED) as zf:\n for root, dirs, files in os.walk('.'):\n for f in files:\n rel = os.path.relpath(os.path.join(root, f), '.')\n if any(fnmatch.fnmatch(rel.replace(os.sep, '/'), g) for g in EXCLUDE):\n continue\n zf.write(os.path.join(root, f), rel)\n\"\n\n# Compute SHA256 of the zip\nsha256sum my-bridge-v1.0.0.zip\n\n# Write the manifest the GUI polls\ncat > my-bridge-manifest.json <<EOF\n{\n \"manifest_version\": 1,\n \"version\": \"1.0.0\",\n \"url\": \"https://your-host/my-bridge-v1.0.0.zip\",\n \"sha256\": \"<sha256 from above>\",\n \"size\": <bytes>,\n \"released_at\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"\n}\nEOF\n```\n\nHost both files anywhere reachable by HTTPS — GitHub Pages, your own CDN, the Adom Wiki if you contribute to the official registry.\n\n## Installing\n\nUsers install your bridge with one command:\n\n```bash\nadom-desktop bridge_install '{\"manifestUrl\":\"https://your-host/my-bridge-manifest.json\"}'\n```\n\nThe GUI:\n1. Fetches `my-bridge-manifest.json`\n2. Downloads the referenced zip\n3. SHA256-verifies against the manifest\n4. Extracts to `%LOCALAPPDATA%\\Adom Desktop\\bridges-cache\\<name>\\` (the `name` comes from your `bridge.json`)\n5. Rescans the runtime registry — your bridge is now discoverable via `bridge_list`\n\nThe next CLI call to one of your `verbPrefixes` auto-spawns the bridge process.\n\n## Updating\n\nBump `bridge.json`'s `version` + `BRIDGE_VERSION`, rebuild the zip, re-upload, update the manifest. Users:\n\n```bash\nadom-desktop refresh_bridges # picks up new versions from manifest URLs they previously installed from\n```\n\n(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.)\n\n## Lifecycle controls (from cloud Docker, or local GUI)\n\n```bash\nadom-desktop bridge_list # see everything\nadom-desktop bridge_pause '{\"name\":\"altium\"}' # stop routing verbs\nadom-desktop bridge_resume '{\"name\":\"altium\"}'\nadom-desktop bridge_uninstall '{\"name\":\"altium\"}' # remove from cache\n```\n\n## Trust + security\n\nFor 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.\n\nFuture work (v1.9.0+):\n- Optional Ed25519 signing of bridge zips with the Adom-Inc public key for \"verified\" status\n- Per-bridge sandboxing (limited filesystem / network access declared in manifest `permissions` field)\n- Community submission flow on the wiki so contributors don't need a GitHub account\n\n## Reference implementations\n\nEvery bridge below has its own dedicated wiki page with hero, install/uninstall recipes, and a \"fork this\" walkthrough. The **[bridges registry](https://wiki.adom.inc/adom/adom-desktop-bridges-registry)** is the canonical directory — both humans browse it and the GUI's Browse tab parses it.\n\n**Hello-world starter templates** — fork these for a 5-minute \"does this thing work?\" loop:\n- **[hello-python](https://wiki.adom.inc/adom/adom-desktop-hello-python-bridge)**: `scripts/sample-bridges/hello-python/` — ~80 lines, stdlib only, `kind:python`\n- **[hello-rust](https://wiki.adom.inc/adom/adom-desktop-hello-rust-bridge)**: `scripts/sample-bridges/hello-rust/` — ~140 lines, `tiny_http` + serde, `kind:exe`, single 300-KB static binary\n\n**Adom-shipped bridges** — canonical full-complexity examples; fork their layout for a real vendor integration:\n- **[kicad](https://wiki.adom.inc/adom/adom-desktop-kicad-bridge)**: `plugins/kicad/` — Python, complex, multi-instance (one bridge per KiCad GUI exe)\n- **[puppeteer](https://wiki.adom.inc/adom/adom-desktop-puppeteer-bridge)**: `plugins/puppeteer/` — Node, single-instance, recording-capable\n- **[fusion360](https://wiki.adom.inc/adom/adom-desktop-fusion-bridge)**: `plugins/fusion360/` — Python, simpler, command-passthrough to Fusion add-in\n\n## Get listed\n\nOnce your bridge is stable, add it to the **[bridges registry](https://wiki.adom.inc/adom/adom-desktop-bridges-registry)** — that's the canonical machine-readable directory the GUI's Browse tab parses to discover what's installable. Two paths:\n\n1. **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.adom.inc/adom/adom-desktop-bridges-registry#how-to-submit-a-bridge) for the row format + review criteria.\n2. **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." +}