← Commit history

Root SKILL.md = full Bridge SDK guide; scrub retired wiki-*.adom.cloud host; v1.1.0

John Lauer ·8cc05468f9 ·4mo ago ·parent ac34ade
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 
 ![What you can ask Claude to do](outcomes.png)
 
-> **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.\ No newline at end of file
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![How the Adom Desktop bridges bridge works](arch.png)\n\n![Adom Desktop bridges verbs Claude can call](verbs.png)\n\n![What you can ask Claude to do](outcomes.png)\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![How the Adom Desktop bridges bridge works](arch.png)\n\n![Adom Desktop bridges verbs Claude can call](verbs.png)\n\n![What you can ask Claude to do](outcomes.png)\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![How the Adom Desktop bridges bridge works](arch.png)\n\n![Adom Desktop bridges verbs Claude can call](verbs.png)\n\n![What you can ask Claude to do](outcomes.png)\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![How the Adom Desktop bridges bridge works](arch.png)\n\n![Adom Desktop bridges verbs Claude can call](verbs.png)\n\n![What you can ask Claude to do](outcomes.png)\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![How the Adom Desktop bridges bridge works](arch.png)\n\n![Adom Desktop bridges verbs Claude can call](verbs.png)\n\n![What you can ask Claude to do](outcomes.png)\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."-}\ No newline at end of file+{
+  "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![How the Adom Desktop bridges bridge works](arch.png)\n\n![Adom Desktop bridges verbs Claude can call](verbs.png)\n\n![What you can ask Claude to do](outcomes.png)\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![How the Adom Desktop bridges bridge works](arch.png)\n\n![Adom Desktop bridges verbs Claude can call](verbs.png)\n\n![What you can ask Claude to do](outcomes.png)\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![How the Adom Desktop bridges bridge works](arch.png)\n\n![Adom Desktop bridges verbs Claude can call](verbs.png)\n\n![What you can ask Claude to do](outcomes.png)\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![How the Adom Desktop bridges bridge works](arch.png)\n\n![Adom Desktop bridges verbs Claude can call](verbs.png)\n\n![What you can ask Claude to do](outcomes.png)\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![How the Adom Desktop bridges bridge works](arch.png)\n\n![Adom Desktop bridges verbs Claude can call](verbs.png)\n\n![What you can ask Claude to do](outcomes.png)\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."
+}