app
DEPRECATED: Bridge SDK (moved into Adom Bridge)
Public Made by Adomby adom
Bridge SDK guide: bridge.json schema, kind:python/node/exe, hello-python + hello-rust reference templates, packaging + lifecycle commands.
← Commit history
Add bridge-author + 3 fill-in-the-blanks templates as sub-skills (consolidate bridge-author)
4 files changed
+800
skills/bridge-author/SKILL.mdadded+264@@ -0,0 +1,264 @@+--- +name: bridge-author +description: Use this skill when the user says they want to write/author/build a third-party bridge for adom-desktop, integrate a new desktop app (Altium, MATLAB, SolidWorks, an oscilloscope, etc.) with cloud Claude, fork the hello sample bridge, ship a bridge to the wiki, or asks how custom CLI verbs get routed into adom-desktop. Walks them through scaffolding a fresh bridge from the canonical template, wiring `bridge.json`, writing the HTTP server, packaging the zip, hosting it, and verifying install via `adom-desktop bridge_install`. +--- + +# Authoring an Adom Desktop bridge + +A bridge is a small HTTP server (Python, Node, or any executable) that adom-desktop spawns on demand and proxies cloud CLI requests to. Once installed, every `adom-desktop <verb> '<json>'` whose verb starts with your bridge's prefix routes to your server. Adom Desktop v1.8.0+ supports dynamic bridges installed at runtime from a wiki manifest URL — no fork of adom-desktop required. + +## When to use this skill + +The user wants to write a bridge if they say any of: +- "I want to integrate Altium / MATLAB / Eagle / SolidWorks / OrCAD / my oscilloscope / our internal CAD tool with adom-desktop" +- "Help me build a third-party adom-desktop bridge" +- "How do I add a new CLI verb to adom-desktop?" (the answer is almost always "ship it as a bridge, not by forking adom-desktop") +- "Fork the hello sample bridge" +- "Make my bridge installable via the install modal" + +If they want to USE existing bridges (kicad, fusion, puppeteer), that's the `adom-desktop` skill, not this one. + +## Prerequisites + +The user needs adom-desktop installed + running on their laptop and reachable from this container: + +```bash +adom-desktop ping +``` + +If that fails, walk them through the install flow at https://wiki.adom.inc/wiki/apps/adom-desktop first. + +## Workflow + +### Step 1 — pick a bridge name + verb prefix + +Ask the user: +- What's the host app this bridge integrates? (e.g. "Altium Designer") +- What lowercase one-word name should the bridge use? (e.g. `altium`) — this is the cache directory name + the manifest key +- What verb prefix should they own? Conventionally `<name>_*`, e.g. `altium_open_project`, `altium_export_step`. Prefixes are first-come-first-served across the registry; check existing bridges with `adom-desktop bridge_list` so you don't collide. + +Reserved prefixes that are already taken: +- `kicad_*` (kicad bridge) +- `browser_*`, `desktop_recorder_*`, `desktop_record_*` (puppeteer bridge) +- `fusion_*` (fusion360 bridge) +- `hello_*` (sample bridge) + +Reserved bridge ports: 8770–8779 (Adom-owned), 8851 (puppeteer). Pick something in 8800–9000 for new bridges. + +### Step 2 — scaffold from the hello sample + +The hello sample bridge is the minimum viable example — a ~80-line stdlib-Python HTTP server. Have the user download + extract it: + +```bash +# On the user's laptop (run via adom-desktop shell_execute or have them run it themselves): +curl -L https://wiki.adom.inc/static/apps/adom-desktop/hello-bridge-v1.0.0.zip \ + -o /tmp/hello-bridge.zip +mkdir -p ~/my-bridge && cd ~/my-bridge +python3 -c "import zipfile; zipfile.ZipFile('/tmp/hello-bridge.zip').extractall()" +ls -la +``` + +They now have `bridge.json`, `BRIDGE_VERSION`, `server.py`, `README.md`. Rename the directory + edit each file to match the new bridge name/verbs. + +For Node-based bridges, look at the puppeteer bridge zip instead: +`https://wiki.adom.inc/static/apps/adom-desktop/puppeteer-bridge-v1.0.0.zip` + +For app-with-plugin-API bridges (Altium / SolidWorks / etc.), the fusion360 bridge is the right reference — it has both a bridge process AND an in-host add-in: +`https://wiki.adom.inc/static/apps/adom-desktop/fusion360-bridge-v1.0.0.zip` + +### Step 3 — edit `bridge.json` + +The schema is documented at https://wiki.adom.inc/wiki/apps/adom-desktop-bridges#bridgejson-schema. Minimum required fields: + +```json +{ + "manifest_version": 1, + "name": "<lowercase-name>", + "displayName": "<human-readable>", + "version": "1.0.0", + "description": "One-line description that shows up in bridge_list.", + "spawn": { + "kind": "python", + "entrypoint": "server.py", + "port": 8XXX, + "healthEndpoint": "http://127.0.0.1:8XXX/status", + "stopMethod": "kill", + "killImageName": "python.exe" + }, + "verbPrefixes": ["<name>_"], + "verbs": ["<name>_verb1", "<name>_verb2"] +} +``` + +Keep `verbs` accurate — `bridge_list` advertises this to discovery. Don't list verbs you haven't implemented. + +If your bridge supports project-watching (file watcher → auto-resync to Docker), add a `watcher` block: +```json +"watcher": { + "supports": true, + "displayName": "<Watcher name>", + "defaultGlobs": ["*.your-ext"], + "configKey": "project_watch" +} +``` + +The GUI renders an eye-pip on the chip when watching is active. + +### Recommended: `docs` URL + `platforms` map (v1.8.14+) + +These two optional fields make your bridge a first-class citizen of the GUI: + +```json +"docs": "https://wiki.adom.inc/wiki/apps/your-bridge-name", +"platforms": { + "windows": { "supported": true }, + "macos": { "supported": false, "reason": "Mac port pending — Win32 deps in handlers/window_automation.py need a Quartz equivalent." }, + "linux": { "supported": false, "reason": "Same as macOS." } +} +``` + +- **`docs`**: surfaces in the chip's right-click context menu as "View on wiki" (top item). Use it. If you don't have a wiki page yet, publish one first (see `adompkg publish` via the `adompkg` skill). +- **`platforms`**: an honest declaration of which OSes the bridge actually works on. Keys are `windows` / `macos` / `linux`. The GUI shows the support status in the tooltip + warns (non-blocking) in the install modal if the user is on a `supported: false` OS. Provide a `reason` string when something doesn't work — the user sees it verbatim, so explain why. + +Tell the user **don't lie in `platforms`**. If they haven't tested macOS, leave it as `{"supported": false, "reason": "Untested on macOS so far"}` rather than claiming support. The whole point of the field is honesty — better to set expectations than burn a user who installs a bridge that silently fails. + +### Step 4 — implement the server + +The HTTP contract is two endpoints: + +- `GET /status` → `{"ok": true, "version": "..."}` — used by adom-desktop to confirm the bridge is alive +- `POST /command` body `{"verb": "<name>_<verb>", "args": {...}}` → `{"success": true, "output": "..."}` or `{"success": false, "error": "..."}` + +The hello sample's `server.py` is the canonical shape — fork it and replace the verb handlers with yours. + +When implementing handlers: +- Validate `args` strictly. Return `{"success": false, "error": "..."}` for invalid input; don't 500. +- Keep responses small. If returning large payloads (file contents, binary blobs), write to disk first and return the path; Docker will use `pull_file` to fetch. +- Long-running operations (>5s) should return immediately with a job handle, then expose a `<name>_status` verb for polling. + +### Step 5 — test locally via a localhost manifest + +Before publishing to the wiki, test with a local HTTP server: + +```bash +# Build the zip +cd ~/my-bridge +python3 -c " +import zipfile, os, fnmatch +EXCLUDE = ['*/__pycache__/*', '*.pyc', '.history/*'] +with zipfile.ZipFile('../my-bridge-v1.0.0.zip', 'w', zipfile.ZIP_DEFLATED) as zf: + for root, dirs, files in os.walk('.'): + for f in files: + rel = os.path.relpath(os.path.join(root, f), '.') + if any(fnmatch.fnmatch(rel.replace(os.sep, '/'), g) for g in EXCLUDE): + continue + zf.write(os.path.join(root, f), rel) +" + +# Compute sha256 + size +SHA=$(sha256sum ../my-bridge-v1.0.0.zip | awk '{print $1}') +SIZE=$(stat -c%s ../my-bridge-v1.0.0.zip) + +# Write the manifest +cat > ../my-bridge-manifest.json <<EOF +{ + "manifest_version": 1, + "version": "1.0.0", + "url": "http://127.0.0.1:9999/my-bridge-v1.0.0.zip", + "sha256": "$SHA", + "size": $SIZE, + "released_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)" +} +EOF + +# Serve them on a local HTTP port +cd .. && python3 -m http.server 9999 & +LOCAL_SERVE_PID=$! + +# Install through the localhost URL +adom-desktop bridge_install '{"manifestUrl":"http://127.0.0.1:9999/my-bridge-manifest.json"}' + +# Try a verb +adom-desktop <name>_<verb> '{...}' + +# Stop the local server when done +kill $LOCAL_SERVE_PID +``` + +This roundtrip exercises the entire install path (download + sha256 verify + extract + registry rescan + spawn-on-first-call) without uploading anything yet. + +### Step 6 — ship to the wiki (recommended) or any HTTPS host + +Two options: + +**Option A — Adom Wiki** (recommended for bridges they want the community to find): + +Publish the bridge zip and its manifest as a wiki package with `adompkg publish` (list both files in `package.json`'s `files` array, then run the publish from the package directory). The manifest should point at the zip's wiki static URL. + +Suggest the user also write a dedicated `apps/<my-bridge>-bridge` wiki page documenting their bridge — same shape as the [reference bridges](https://wiki.adom.inc/wiki/apps/adom-desktop-bridges#reference-implementations). Use the `adompkg` skill for the publish flow. + +**Option B — any HTTPS host** (GitHub Releases, your own CDN, S3): + +```bash +# Upload both files anywhere reachable by HTTPS, then: +adom-desktop bridge_install '{"manifestUrl":"https://your-host/my-bridge-manifest.json"}' +``` + +Cloud users can install from any reachable URL — they're not locked to the wiki. + +### Step 7 — verify end-to-end + iterate + +```bash +# See it in the registry +adom-desktop bridge_list + +# Trigger your verbs +adom-desktop <name>_<verb> '{...}' + +# Inspect the install dir (right-click the chip in the GUI → Open install folder) +# OR programmatically: +adom-desktop shell_execute '{"command":"powershell -NoProfile -Command \"Get-ChildItem $env:LOCALAPPDATA\\\\\\\"Adom Desktop\\\\\\\"\\\\bridges-cache\\\\<name>\"","timeout_secs":5}' + +# After edits, bump bridge.json's version + BRIDGE_VERSION, rebuild the zip, re-upload, then: +adom-desktop refresh_bridges +``` + +To ship a breaking change without leaving users on the old version: bump the manifest's `version` field. On next launch + refresh, adom-desktop fetches the new manifest, compares versions, downloads + verifies the new zip, atomically swaps the cache dir. + +## Lifecycle controls available to your bridge users + +These verbs are built into adom-desktop — you don't implement them, just know they exist so you can mention them when users ask: + +| Verb | What it does | +|---|---| +| `bridge_list` | List all installed bridges + their metadata | +| `bridge_install` | Install from a manifest URL | +| `bridge_uninstall` | Remove from cache (returns to the bundled version if there was one) | +| `bridge_pause` | Stop routing verbs (process stays running, verbs return "paused") | +| `bridge_resume` | Resume routing | +| `refresh_bridges` | Re-poll all known manifest URLs for updates | + +From the Adom Desktop GUI: right-click any chip in the bridge bar for Pause / Resume / Open install folder / Uninstall. + +## Reference pages on the wiki + +These pages contain full architecture writeups for the three bundled bridges. Use them when the user is stuck on a pattern you've already seen one of these solve: + +- **[Bridge SDK guide](https://wiki.adom.inc/wiki/apps/adom-desktop-bridges)** — schema, packaging, lifecycle, trust model +- **[KiCad bridge reference](https://wiki.adom.inc/wiki/apps/adom-desktop-kicad-bridge)** — multi-instance, reverse-bridge into the host app +- **[Puppeteer bridge reference](https://wiki.adom.inc/wiki/apps/adom-desktop-puppeteer-bridge)** — single-instance Node + session-profile isolation + recording +- **[Fusion 360 bridge reference](https://wiki.adom.inc/wiki/apps/adom-desktop-fusion-bridge)** — passthrough-to-add-in pattern (use for Altium, SolidWorks, Eagle) + +## Trust + security gotchas to warn users about + +- The trust model is SHA256-verification of the zip against the manifest. **Users install with their own privileges** — bridges can read/write any file the user can. Tell them to install only from sources they trust. +- If you publish to the wiki, anyone in the org can see it. For internal-only bridges, host the manifest behind auth (HTTPS basic, signed URLs, etc.) — adom-desktop's `bridge_install` honors HTTP redirects + auth headers passed via `--auth-header`. +- Don't put secrets in the bridge zip. The unpacked cache dir is readable by anyone on the same machine. Read secrets at runtime from env vars / OS keychain / a `.env` next to the bridge cache that the user populates manually. + +## When something goes wrong + +1. **`bridge_install` says SHA mismatch** — the zip you uploaded doesn't match the manifest's `sha256`. Recompute and re-upload the manifest. +2. **Verb returns "bridge not found"** — the prefix you declared doesn't match the verb. Check `adom-desktop bridge_list` to see what prefixes you actually registered. +3. **Bridge process won't start** — adom-desktop reports the spawn failure. Common causes: wrong `entrypoint`, `spawn.kind` mismatch (Python file as `node`), missing dependencies. Check the GUI's activity log. +4. **Bridge runs but verbs return "process exited"** — your handler probably crashed. Add `try/except` around your dispatcher and log to stderr; adom-desktop forwards stderr lines to the GUI log. +5. **Open install folder shows a "Location is not available" dialog** — the cache dir doesn't exist on the user's real filesystem (probably installed from a sandboxed context). Uninstall + reinstall from a clean session. See https://wiki.adom.inc/wiki/apps/adom-desktop-bridges for the full troubleshooting.
skills/bridge-dev-template/SKILL.mdadded+150@@ -0,0 +1,150 @@+--- +name: bridge-dev-template +user-invocable: false +description: "TEMPLATE / reference for building, publishing, and maintaining ANY Adom Desktop bridge (the cloud Claude/Codex thread that owns a bridge copies this into its own dev-skills/ and fills it in). NOT a user-facing skill — general users want the bridge's own user skill. Read this when editing bridge code, cutting a new version, publishing to the wiki, wiring a runtime/heavy-dependency cold-start self-heal, or understanding the pkg-vs-release-vs-bundled artifact model. Trigger words: bridge dev, build adom-desktop bridge, publish bridge, ship bridge, bridge_install, adom-desktop-<bridge>-bridge, cold-start self-heal, bridge readiness internals, bridge release, bridge manifest, updateManifestUrl, pkg vs release vs bundled." +--- + +<!-- Template — copy into your bridge's own dev-skills/, replace <bridge>/<prefix>/<Bridge Name>, keep the structure. --> + +# bridge-dev-template — building & shipping an Adom Desktop bridge + +The canonical source of truth for the `<bridge>` bridge is **its wiki repo** +(`wiki.adom.inc/adom/adom-desktop-<bridge>-bridge`), cloud-owned (like the kicad/fusion/puppeteer +bridges) so bridge iteration is decoupled from adom-desktop core. Read `CLAUDE.md` (ownership boundary) +and `PUBLISHING.md` (step-by-step recipe) in this repo alongside this skill. + +## The THREE artifacts — know which is which (this is the #1 confusion) + +A user installs TWO different things from this one page. They are not the same: + +| Artifact | What it is | Who installs it | How | +|---|---|---|---| +| **pkg tarball** (Packages tab) | the **container-side Claude/Codex skills** (`SKILL.md` + `skills/*`) — docs that teach the cloud AI how to drive `<bridge>`. **No bridge runtime.** | a cloud **container** | `adom-wiki pkg install adom/adom-desktop-<bridge>-bridge`, or auto via AD's `sync_skills` | +| **release zip** (Releases tab) | the **bridge RUNTIME** (entrypoint + bridge code + `bridge.json` + `BRIDGE_VERSION`; heavy deps reconstructed on spawn) — the code that actually runs on the desktop | **Adom Desktop**, streamed into `bridges-cache\<bridge>` | `bridge_install {manifestUrl}` / `refresh_bridges` (via `updateManifestUrl`) | +| **bundled seed** (inside the AD installer) | a copy of the same runtime, shipped in the NSIS for first-run/offline | every AD install | ships with Adom Desktop; superseded by a newer cache copy (cache-over-bundled, numeric version) | + +So: **the pkg is skills (container). The bridge is the zip (desktop) + the bundled seed.** Keep the +pkg description saying exactly that, or people think the tarball contains the bridge — it does not. + +## How `<bridge>` reaches a container — auto-discovery → auto-install → verbs + +The full chain when a user on a fresh container says *"<the phrase that triggers your bridge>"*: + +1. **Discovery.** The wiki's `discover` index (fed by this page's `discovery_triggers` — which should lead + with your bridge's natural phrases, e.g. its short name + the top user actions) matches the phrase. The + container's regenerated `adom-wiki-discover` skill surfaces this page, so the AI proposes it. +2. **Auto-install the pkg.** The AI runs `adom-wiki skills install adom/adom-desktop-<bridge>-bridge` + (a.k.a. `pkg install`). That extracts the tarball and runs `install.sh`, which drops `SKILL.md` + + `skills/*` into **both `~/.claude/skills/` and `~/.codex/skills/`**. The `<bridge>` user skill is now loaded. +3. **The skill calls the bridge.** The user skill's `SKILL.md` tells the AI to run `adom-desktop <prefix>_*` + verbs. AD routes every `<prefix>_*` verb to the `<bridge>` bridge. +4. **The bridge runtime.** It's already on the desktop: AD ships `<bridge>` as a **bundled seed** (and + supersedes it from this page's release zip via `updateManifestUrl` / `bridge_install`). On a truly fresh + PC the cold-start self-heal installs `<your runtime / heavy dependency>` on first use. + +**Two install paths, keep BOTH healthy:** +- **Discovery path (above)** — works once `discovery_triggers` include the user phrases + the discover + index is regenerated. This is the "user said `<bridge>` → AI installs the skill" path. +- **AD `sync_skills` path** — AD's daily `maybe_background_skill_sync` reads `bridge_list` → each bridge's + `skillPkg` → `pkg install`s it into the container. For this to work AD must report + `skillPkg = adom/adom-desktop-<bridge>-bridge` for your bridge. AD now derives that slug from the bridge's + `docs` field automatically, **except** for any bridge hardcoded into `AD_CORE_SKILL_BRIDGES` in + `bridge_registry.rs` (that list forces `skillPkg = null` for bridges whose skill historically lived in + the AD-core pkg). If your bridge's skill ships from THIS page (not core), make sure your name is NOT in + that list — if it is, removing it is an AD-core handoff item. + +## Repo layout + +- `src/` — the runnable bridge (what the release zip contains, at zip-root). Entrypoint + bridge modules, + `bridge.json`, `BRIDGE_VERSION`, dependency manifest/lockfile, any html/icons the runtime needs. +- `SKILL.md` (root) — the **user** skill (`name: <bridge>`). Deployed to `~/.claude/skills/adom-desktop-<bridge>-bridge/`. +- `dev-skills/<bridge>-bridge-dev/` — THIS dev skill. `dev-skills/<bridge>-bridge-publish/` — the publish recipe. + Both are **source-only** (in `dev-skills/`, NOT `skills/`): NOT in the installable pkg and NOT on the + wiki Skills tab — you get them via `adom-wiki repo clone`. Everyday users only get the `<bridge>` user skill. +- `adom-bridge-<bridge>-manifest.json` (root) — the streaming manifest AD's `updateManifestUrl` points at. +- `package.json` (root) — the **adompkg descriptor** (slug/version/type/description) for `pkg publish`. + NOT a language/runtime package descriptor (e.g. not the node `package.json`) — keep them distinct. +- `install.sh` / `uninstall.sh` — deploy `SKILL.md` + `skills/*` into the container's `~/.claude/skills/`. +- `page.json`, `README.md`, `*.png` heroes — the wiki page presentation (the heroes are gitignored so the + container pkg stays lean — they're already uploaded as page assets). + +## Cold-start self-heal — the architecture pattern (the reason a cloud-owned repo earns its keep) + +On a fresh PC the installer pre-installs neither your bridge's language runtime nor its heavy dependency. +Without a self-heal, the first `<prefix>_<verb>` fails with a generic launch error + a dev string. The +pattern that fixes it — adapt the names to your runtime: + +- **A detect/install/ready module** — `detectDependency()` (validate the ACTUAL artifact, not "a dir + exists"), `installDependency()` (programmatic, **pinned to the exact version your code expects** — never + a floating 'latest'/'stable', or the launch path won't find what it pinned against), an install-state + singleton (dedup concurrent installs + report progress), `ensureReady()` (the gate — kicks off a + background install and returns immediately), `readiness()` (powers the verb + the status chip). +- **The first action verb gates on readiness:** missing dependency → kick off a **non-blocking** install + + return `{errorCode:"<dependency>_installing", statusVerb:"<prefix>_readiness", _hint:"poll then retry"}`. + Install fail → a specific `<dependency>_install_failed` / `<dependency>_download_failed` error — **never + silently continue into a doomed launch.** +- **A `<prefix>_readiness` verb** (poll) + a **`<prefix>_prewarm` verb** (install/warm without doing the + user-visible action). +- **`/status` (or your `healthEndpoint`)** emits `led`/`summary`/`tooltip` truthfully (yellow installing, + red failed, green ready). AD paints these verbatim. +- **The language runtime itself is handled by AD core BEFORE the bridge spawns** (e.g. a `node_not_found` → + `desktop_install_node` flow) — NOT your bridge's job. The bridge can't bootstrap the very runtime it + needs to run; AD installs the runtime, then spawns you. +- **The one thing the bridge can't do:** pre-warm before first use (it isn't running until the first call). + AD/HD must call `<prefix>_prewarm` on embedded first-run — track that as a handoff item. + +> **Worked example (pup's case):** the puppeteer bridge's runtime dependency is Chrome for Testing. Its +> module is `src/chrome.js` (`detectChrome` / `installChrome` via the bundled `@puppeteer/browsers` pinned +> to puppeteer's expected buildId / `ensureChromeReady` / `readiness`); `browser_open_window` is the gated +> first verb (CfT path only; its native-drive path skips the gate), returning +> `chrome_for_testing_installing`; and it exposes `browser_readiness` + `browser_prewarm`. Node is the +> language runtime AD core installs first (`node_not_found` → `desktop_install_node`). That's the concrete +> shape of every bullet above. + +## Publishing (the wiki has TWO storage layers — don't mix them) + +Full recipe in `PUBLISHING.md`. The load-bearing rules: + +1. **The bridge `.zip` → a RELEASE asset** (`adom-wiki release create` + `release upload`). NEVER + `repo push` a `.zip` — it's gitignored and silently skipped; the manifest URL would 404. +2. **The manifest JSON + source → the git repo** (`adom-wiki repo push`). The manifest's `url` is the + RELEASE download URL; its `sha256`/`size` MUST match the served asset (releases preserve your sha; + re-download to confirm or `bridge_install` rejects it). +3. **The skills → a PKG** (`adom-wiki pkg publish`). The pkg description (root `package.json`) must say + "container-side skills." `pkg pack` honors `.gitignore` for exclusion (no separate ignore file) — that's + why the heroes are gitignored, to keep the tarball lean. Set per-version notes with `adom-wiki pkg notes`. +4. **Version display gotcha:** a RELEASE shows up in `adom-wiki pkg info` as a row with `type:null` (a + wiki UI conflation). The real pkg rows have `type:"app"`. Use `release list` for true releases. +5. **`bridge.json` + `BRIDGE_VERSION` lockstep**, and keep the version **>** the AD bundled seed so + cache-over-bundled wins. +6. **Declare `updateManifestUrl` in your `bridge.json`** (the full URL of this page's manifest) so a + user's AD re-polls THIS page for updates — and the same value lives in the bundled seed AD ships, so a + fresh install auto-updates with no user opt-in. A republish that drops it makes AD fall back to the + legacy convention and stop seeing your updates. + +## Testing a new version on a real desktop + +```bash +adom-desktop targets # multiple desktops → --target <name> is required +adom-desktop --target <name> bridge_install '{"manifestUrl":"https://wiki.adom.inc/api/v1/pages/adom-desktop-<bridge>-bridge/files/adom-bridge-<bridge>-manifest.json","force":true}' +adom-desktop --target <name> <prefix>_readiness '{}' # proves the new code is live +``` + +- **The language runtime is missing on the box** → `<prefix>_*` returns the runtime-not-found error from + AD core *before* the bridge spawns (correct). That's a genuine cold-start demo of the FIRST link, but it + can't exercise your self-heal module (there's no runtime to run it). +- **A box WITH the runtime but WITHOUT the heavy dependency** is the only place to see the full dependency + self-install live (`<prefix>_<verb>` → `<dependency>_installing` → poll `<prefix>_readiness` → succeeds). + Most dev boxes already have the dependency, so `readiness()` just returns `ready:true`. +- The streamed zip is **source-only**; AD reconstructs heavy deps in the cache on first spawn (ship + prebuilt native deps where possible). The bundled seed already has its deps materialized. **Don't put + reconstructable dependency trees (node_modules, venvs, vendored build output) in the zip.** + +## Don't + +- Don't edit the bundled seed in the adom-desktop repo (`plugins/<bridge>`) as if it's live, and don't run + `release-bridge.sh <bridge>` from the adom-desktop repo — that clobbers this cloud-owned page. (The + adom-desktop release script hard-refuses the cloud-owned bridges; the rule is "don't try.") +- Don't ship the bridge runtime in the pkg tarball or the skills in the release zip. Two artifacts, two homes. +- Don't pin your heavy dependency to a floating channel ('latest'/'stable') — pin to the exact version your + code launches against, or the install and the launch disagree.
skills/bridge-publish-template/SKILL.mdadded+243@@ -0,0 +1,243 @@+--- +name: bridge-publish-template +user-invocable: false +description: "TEMPLATE — the canonical, battle-tested recipe for publishing a new version of ANY Adom Desktop bridge to wiki.adom.inc, plus every gotcha hit live. Copy into your own bridge's dev-skills/, replace the <bridge>/<prefix> placeholders, and follow it. NOT for general users. Read before cutting a release: which artifact goes where (pkg=skills, release=bridge zip, repo=manifest+source), version-lockstep (kept above the AD bundled seed), the `files` allowlist + `skills/**` glob trap, keeping the tarball lean (NO zip / NO src / NO heroes), keeping dev/publish skills SOURCE-ONLY (not SKILL.md), deploying skills to .claude AND .codex, scrubbing retired-wiki host URLs, user-first discovery triggers, sha matching, and verify steps. Trigger words: publish bridge, ship bridge, release bridge, pkg publish, repo push, adom-wiki release, bridge manifest, bridge version bump, no zip in tarball, dev skill source only, scrub old wiki url, codex skills, discovery triggers, adom-desktop bridge publish template." +--- + +# bridge-publish-template — shipping a new `<bridge>` bridge version + +> **Template — copy into your bridge's own `dev-skills/`, replace the `<bridge>`/`<prefix>` placeholders, follow the steps.** + +This is the step-by-step ship recipe for a cloud-owned Adom Desktop bridge living at +`wiki.adom.inc/adom/adom-desktop-<bridge>-bridge`, plus the traps that cost real time. Companion docs in +your repo: a `*-dev.md` dev skill (architecture / ownership), a terse `PUBLISHING.md`, and `CLAUDE.md` +(ownership boundary). + +Placeholder legend — replace consistently throughout before you ship: + +| Placeholder | Meaning | Example (Puppeteer bridge) | +|---|---|---| +| `<bridge>` | lowercase bridge id | `puppeteer` | +| `<Bridge Name>` | display name | `Puppeteer` | +| `<prefix>_` | verb prefix | `browser_` | +| `adom-desktop-<bridge>-bridge` | wiki slug | `adom-desktop-puppeteer-bridge` | +| `adom-bridge-<bridge>-manifest.json` | streaming manifest filename | `adom-bridge-puppeteer-manifest.json` | +| `adom-bridge-<bridge>-v<version>.zip` | release zip filename | `adom-bridge-puppeteer-v1.1.1.zip` | +| `<your bridge's files>` | the runtime source files | `server.js`, `chrome.js`, `bridge.json` | +| `<your runtime>` | the language/runtime AD reconstructs | `node` + `node_modules` | + +## What goes WHERE — three artifacts, three homes (memorize this) + +| Artifact | Home | Command | Carries | +|---|---|---|---| +| **bridge runtime `.zip`** | a **Release** asset | `adom-wiki release upload` | `src/` at zip-root (`<your bridge's files>` — the thing AD runs). | +| **streaming manifest JSON** | the **git repo** | `adom-wiki repo push` | points at the release zip URL + its sha256/size | +| **container skills tarball** | a **Package** | `adom-wiki pkg publish` | `SKILL.md` + `skills/*` (the Claude/Codex skills). NO runtime. | + +> The `.zip` is a RELEASE, never a git file: `*.zip` is gitignored and `repo push --files X.zip` +> reports `ok` but silently skips it (the URL then 404s). Binaries → `release upload`. + +## Auth (once per container) + +```bash +export ADOM_WIKI_TOKEN=$(cat /var/run/adom/api-key 2>/dev/null) # container default; or session.json's login session_token +adom-wiki whoami # MUST show a real user (e.g. [email protected]). Strict-auth rejects the shared "Developer" token. +``` +`ADOM_WIKI_TOKEN` is the user's **login `session_token`** — the container default lives at +`/var/run/adom/api-key`; otherwise pull it from `~/.adom/session.json`. Every mutation needs a real +`--changelog` / `-m` (≥10 chars, ≥2 words) or the server 400s. + +## The recipe + +### 1. Bump version IN LOCKSTEP, and keep it > the AD bundled seed + +```bash +printf '1.1.1' > src/BRIDGE_VERSION +sed -i 's/"version": "1.1.0"/"version": "1.1.1"/' src/bridge.json # bridge runtime +sed -i 's/"version": "1.1.0"/"version": "1.1.1"/' package.json # adompkg descriptor (the pkg) +``` +`src/BRIDGE_VERSION` and `src/bridge.json` MUST match (AD uses `BRIDGE_VERSION` to detect a stale running +process). Keep the number **above** the seed in `adom-desktop/plugins/<bridge>` so cache-over-bundled wins. + +### 2. Build the bridge zip (source-only, `src/` at zip ROOT — no top-level dir) + +`zip` may be absent → Python. Your `<your runtime>` deps (e.g. `node_modules`) are NEVER included (AD +reconstructs them on spawn; the seed ships them; a fat zip would blow wiki limits): + +```python +import zipfile, os, hashlib +root, out = "src", "adom-bridge-<bridge>-v1.1.1.zip" +with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as z: + for dp, dirs, files in os.walk(root): + for skip in ("node_modules","screenshots","profiles"): # your runtime's heavy/generated dirs + if skip in dirs: dirs.remove(skip) + for f in files: z.write(os.path.join(dp,f), os.path.relpath(os.path.join(dp,f), root)) +print("sha256:", hashlib.sha256(open(out,"rb").read()).hexdigest(), "size:", os.path.getsize(out)) +# SANITY: assert your changed file(s) are inside before shipping. +``` + +> **Concrete pup example (so the commands aren't abstract):** the Puppeteer bridge skips +> `node_modules`/`screenshots`/`profiles`, and its sanity check asserts `chrome.js` + the change are +> inside the zip before upload. `node_modules` is excluded because AD `npm install`s it on spawn (an +> 88 MB zip would exceed wiki limits). + +### 3. Publish the zip as a Release (releases PRESERVE your sha) + +```bash +adom-wiki release create adom/adom-desktop-<bridge>-bridge 1.1.1 --title "..." --changelog "<≥2 words>" +adom-wiki release upload adom/adom-desktop-<bridge>-bridge 1.1.1 adom-bridge-<bridge>-v1.1.1.zip --json +# the response's assets[].sha256/size are what the manifest MUST carry. Then VERIFY the PUBLIC url: +curl -sL https://wiki.adom.inc/download/adom/adom-desktop-<bridge>-bridge/1.1.1/adom-bridge-<bridge>-v1.1.1.zip | sha256sum +``` + +### 4. Point the manifest at the release + push it (git repo) + +Edit `adom-bridge-<bridge>-manifest.json` → `version`, `url` (the release download URL), `sha256`, +`size` (from step 3 — **must match the served asset or `bridge_install` rejects it**): + +```json +{ "manifest_version": 1, "name": "<bridge>", "version": "1.1.1", + "url": "https://wiki.adom.inc/download/adom/adom-desktop-<bridge>-bridge/1.1.1/adom-bridge-<bridge>-v1.1.1.zip", + "sha256": "<the release asset's sha256>", "size": <bytes>, + "verbPrefixes": ["<prefix>_"], + "healthEndpoint": "/status", "statusVerb": "<prefix>_readiness", + "updateManifestUrl": "https://wiki.adom.inc/api/v1/pages/adom-desktop-<bridge>-bridge/files/adom-bridge-<bridge>-manifest.json", + "released_at": "<UTC ISO8601>" } +``` + +> Declare `updateManifestUrl` (the full URL of THIS manifest on your own page) in both the manifest and +> `src/bridge.json` so `refresh_bridges` / the auto-poll reach YOUR page instead of the legacy +> AD-owned convention. Then push: + +```bash +adom-wiki repo push adom/adom-desktop-<bridge>-bridge \ + --files adom-bridge-<bridge>-manifest.json src/bridge.json src/BRIDGE_VERSION <your bridge's files> SKILL.md \ + -m "v1.1.1: <what changed>" +``` + +> `sha256`/`size` MUST match the served asset or `bridge_install` rejects it. Re-download to confirm: +> `curl -sL <download_url> | sha256sum`. + +### 5. Publish the container skills as a pkg — keep it LEAN, and MIND THE TRAPS + +```bash +adom-wiki pkg pack # ALWAYS pack + inspect BEFORE publish +tar tzf *.tgz # confirm: SKILL.md + skills/*/SKILL.md + install.sh, and NOTHING heavy +adom-wiki pkg publish --org adom --version 1.1.1 +adom-wiki pkg notes adom/adom-desktop-<bridge>[email protected] -m "Container skills: <what changed>" +``` + +The pkg description lives in the root **`package.json`** `description` — keep it explicit that this is +**container-side skills only; the bridge runtime is the Release zip + bundled in Adom Desktop.** + +## Traps hit live (don't relearn these) + +- **`pkg pack` includes EVERYTHING by default** → a fat tarball (all the hero PNGs + `src/` runtime). + Fix: the heroes are gitignored (`*.png`) since they're already page assets, AND `package.json` has a + **`files` allowlist**. With both, the tarball drops to ~12 KB. (Pup's was 16 MB before the guards.) +- **`files` directory entries need a GLOB.** Listing `"skills"` (bare dir) silently includes ZERO skill + files. Use **`"skills/**"`** (and/or the explicit `"skills/<name>/SKILL.md"`). Always `tar tzf` to + confirm your skills actually made it in — a missing skill = an empty Skills tab. +- **`pkg pack` honors `.gitignore`, not a separate npmignore.** So you can't gitignore something you + need in git (e.g. `src/`) to drop it from the pkg — use the `files` allowlist for that. +- **A RELEASE shows up in `adom-wiki pkg info` as a row with `type:null`** (wiki UI conflation). The real + pkg rows are `type:"app"`. Use `adom-wiki release list` for true releases; don't think your pkg shipped + just because a version appears in `pkg info`. +- **sha/size mismatch → `bridge_install` rejects the manifest.** Releases preserve the uploaded sha, so + use the `release upload` response values verbatim and re-download to confirm. +- **The Skills tab + `skill_count` + `adom-wiki skills install` come from the GIT REPO, not the pkg.** + The wiki indexes **every `SKILL.md`** in the page repo (root = main skill; `<dir>/SKILL.md` = sub-skills) + — `page.skills` is null; it's a repo scan. So a file named `SKILL.md` ANYWHERE in the repo becomes an + indexed, tab-listed, `skills install`-able skill. (This is *separate* from `pkg install`, which installs + the pkg tarball governed by the `files` allowlist.) Corollary → see "Keep dev/publish skills source-only". + +## Verify (every ship) + +```bash +# manifest serves at the updateManifestUrl path: +curl -s https://wiki.adom.inc/api/v1/pages/adom-desktop-<bridge>-bridge/files/adom-bridge-<bridge>-manifest.json | jq .sha256 +# anon-fetch the PUBLIC release zip URL and confirm the sha matches the manifest (curl 200 is NOT enough): +curl -sL https://wiki.adom.inc/download/adom/adom-desktop-<bridge>-bridge/1.1.1/adom-bridge-<bridge>-v1.1.1.zip | sha256sum +# live install + new verbs on a real desktop (multi-desktop → --target; `adom-desktop targets` lists them): +adom-desktop --target <name> bridge_install '{"manifestUrl":".../files/adom-bridge-<bridge>-manifest.json","force":true}' +adom-desktop --target <name> <prefix>_readiness '{}' +# bridge_list shows the new version + skillPkg; bridge_check_updates reaches your page; sync_skills pulls the user skill: +adom-desktop --target <name> bridge_list '{}' # confirm version + skillPkg (adom/adom-desktop-<bridge>-bridge) +adom-desktop --target <name> bridge_check_updates '{}' # wikiHosted:true, latest = your new version +adom-desktop --target <name> sync_skills '{}' # installs the user skill pkg into the container +# render the page in pup + READ the screenshot. Confirm version + Skills tab. +``` +> Server-only change → AD reaps + respawns the bridge from the new cache on the next `<prefix>_*` call. + +## Keep dev/publish skills SOURCE-ONLY (not on the Skills tab, not installed) + +Everyday users should get ONLY the `<bridge>` user skill — not the dev/publish skills. But the wiki indexes +every `SKILL.md` in the repo (above), so a dev skill named `SKILL.md` would show on the Skills tab AND +install via `skills install`. The fix: keep dev/publish skills as **source-only docs that are NOT named +`SKILL.md`** — they live in `dev-skills/` as `<bridge>-bridge-dev.md` / `<bridge>-bridge-publish.md`. Then +they are: + +- **not indexed** (`skill_count` stays 1), **not on the Skills tab**, **not `skills install`-able**; +- still in git → available via `adom-wiki repo clone` (and the Files tab); +- excluded from the pkg too (the `files` allowlist lists only `SKILL.md` + `skills/**`). + +`dev-skills/README.md` tells a developer how to load one as a live skill (copy it to +`~/.claude/skills/<name>/SKILL.md`). **Verify after publish:** `skill_count` should be **1** — +`curl -s https://wiki.adom.inc/api/pages/adom/adom-desktop-<bridge>-bridge | jq '.page.skill_count'`. + +## Keep the pkg tarball LEAN — NO zip, NO src/, NO heroes + +The pkg tarball must be the `<bridge>` user skill ONLY — never the bridge runtime zip, `src/`, or hero PNGs. +Guards: the `package.json` `files` allowlist (`SKILL.md`, `skills/**`, `install.sh`, `uninstall.sh`, +`README.md`, `package.json`) **plus** `.gitignore` (`*.zip`, `*.png`, `*.bundle`, `*.tgz`, the old +`*-source.tar.gz`). **ALWAYS** `adom-wiki pkg pack` then `tar tzf *.tgz` and confirm there is **no +`*.zip`, no `src/`, no `*.png`**. A lean tarball is ~10 KB; if it's megabytes, something heavy leaked in +(it was 16 MB before the guards). `rm -f *.tgz` after inspecting. + +## Skills install to BOTH .claude AND .codex + +`install.sh` deploys the user skill into **both `~/.claude/skills/` and `~/.codex/skills/`** (Codex only +if `~/.codex` exists); `uninstall.sh` removes from both. Codex is a real second agent skills home now — +don't ship an install.sh that only does `.claude`. + +## Scrub retired-wiki URLs (the #1 fork/extract gotcha) + +**The only wiki host is `wiki.adom.inc`.** Any `wiki-*.adom.cloud` host is RETIRED — as are the +`/static/apps/...` and `/wiki/apps/...` path formats. When you fork/extract a page, the inherited +`README.md` and the `page.json` `readme` body carry OLD URLs (this bit pup live — a dead `static/apps` +zip link). Before every publish: + +```bash +grep -rnI 'adom\.cloud\|/static/apps\|/wiki/apps' . --exclude-dir=node_modules +``` + +Fix every hit (README + the `page.json` `readme` body are the usual culprits), then VERIFY the **live** +entity (not just local): + +```bash +curl -s https://wiki.adom.inc/api/pages/adom/adom-desktop-<bridge>-bridge | grep -c '\.adom\.cloud' # want 0 +``` + +Also `repo rm` any stale source-mirror artifacts left on the page (old `*-source.tar.gz` / `*.bundle`). +(`https://adom.inc/fonts/...` is fine — that's the brand font CDN, not a wiki host.) + +## Canonical wiki.adom.inc URL formats + +- **Page:** `https://wiki.adom.inc/adom/<slug>` +- **File (served, anon):** `https://wiki.adom.inc/api/v1/pages/<slug>/files/<name>` +- **Release download:** `https://wiki.adom.inc/download/adom/<slug>/<ver>/<file>` +- **Sibling pages:** `/adom/adom-desktop-bridges`, `/adom/adom-desktop-kicad-bridge`, + `/adom/adom-desktop-fusion-bridge`, `/adom/adom-desktop` + +## Discovery triggers — lead with everyday USER phrases + +Lead `discovery_triggers` with everyday phrases, not dev jargon — the single most important is the bare +bridge name users actually say. The wiki **auto-generates** the discover snippet from `discovery_triggers` ++ `discovery_pitch` (there is no separate "upload a snippet" — those two fields ARE the snippet). Set them +via `adom-wiki discover triggers <ref> --add ...` OR `page.json` `discovery_triggers`/`discovery_pitch` ++ push. **Verify:** `adom-wiki discover preview "<a natural user phrase for your bridge>"` should rank this +page #1. + +> **Concrete pup example:** the most important trigger is the bare word **`pup`**, plus `open in pup`, +> `open my app in pup`, `screenshot the page`, `record a browser window`. `adom-wiki discover preview +> "open my app in pup"` must rank the Puppeteer page #1.
skills/bridge-user-skill-template/SKILL.mdadded+143@@ -0,0 +1,143 @@+--- +name: bridge-user-skill-template +user-invocable: true +description: "TEMPLATE for an Adom Desktop bridge's CONSUMER skill — copy it, then replace <bridge>/<Bridge Name>/<prefix> and the verb glosses. It teaches a cloud Claude/Codex AI how to DRIVE <Bridge Name> on the user's desktop via the adom-desktop CLI: <one-line value prop — what the bridge controls and why>. Includes a FIRST-USE / COLD-START warm-up section so the AI doesn't panic on a fresh machine. Keep a trigger-words line like this one so the skill activates. Trigger words: <bridge>, <Bridge Name>, <prefix>_<verb1>, <prefix>_<verb2>, <prefix>_readiness, <prefix>_prewarm, <2-3 plain-English phrases a user would say, e.g. 'open in <bridge>', '<bridge> screenshot'>." +--- + +> Template — copy into your bridge's pkg as `skills/<bridge>/SKILL.md`, replace `<bridge>`/`<prefix>`. + +# <Bridge Name> — <one-line capability summary> (with first-use warm-up) + +Drive <Bridge Name> on the user's desktop from the cloud via the `adom-desktop` CLI. All verbs are +Bash commands (NOT MCP tools): + +```bash +adom-desktop <command> '<json_args>' +``` + +## ⭐ FIRST-TIME / COLD-START — read this BEFORE you panic that "<prefix>_<verb> did nothing" + +On a brand-new machine (e.g. the user just installed the app that bundles Adom Desktop), +**something <Bridge Name> needs may NOT be pre-installed by the installer** — a runtime, a large +dependency, an external app, or a one-time download. The bridge handles this gracefully — your job is +to **read the structured response and follow it, not to give up.** + +When you run a `<prefix>_<verb>` on a cold machine, one of these happens — handle each: + +| Response | What it means | What you do | +|---|---|---| +| `errorCode: "<dependency>_not_found"` | A required runtime/dependency isn't installed (the bridge can't start). | Run the bridge's documented install verb (e.g. `adom-desktop <prefix>_install_<dep> '{}'`), wait for it, then retry the original verb. | +| `errorCode: "<resource>_installing"` (`installing:true`) | **A needed resource is missing; the bridge is auto-downloading/installing it now (one time).** This is EXPECTED on a fresh machine — NOT a failure. | **Poll `<prefix>_readiness` until `ready:true`**, THEN re-issue the exact same verb. You do NOT install anything yourself. Do NOT report failure to the user. | +| `errorCode: "<resource>_install_failed"` / `<resource>_download_failed` | The download/install itself failed. | Read `readiness.lastError`. It's usually no network / a proxy / low disk — NOT your fault. Retry `<prefix>_prewarm`; if it persists, surface `lastError` to the user, and use any documented fallback mode. | +| `ok:true` / a real session | Everything was already warm. | Proceed normally. | + +### The clean cold-start recipe + +```bash +# 1) (optional but nice) Warm the bridge WITHOUT doing real work first: +adom-desktop <prefix>_prewarm '{}' # returns fast; kicks off any first-use install + +# 2) Poll readiness until ready (the bridge sets itself up): +adom-desktop <prefix>_readiness '{}' # → {ready:false, installing:true, installProgressPct:42, ...} +# ...wait a few seconds, poll again until → {ready:true, ...} + +# 3) Now do the real work — it's instant: +adom-desktop <prefix>_<verb> '{"sessionId":"<id>", ...}' +``` + +`<prefix>_prewarm` and `<prefix>_readiness` are the two cold-start verbs. `<prefix>_prewarm '{"wait":true}'` +blocks until setup finishes instead of returning immediately. **Always surface a verb's `_hint` +field verbatim to the user when something isn't ready** — it tells you (and them) exactly what's happening. + +> **Concrete example (the Puppeteer/pup bridge):** on a fresh PC, `browser_open_window` returns +> `errorCode: "chrome_for_testing_installing"` (`installing:true`) because Chrome for Testing (~150 MB) +> is auto-downloading. That is NOT a failure — you poll `browser_readiness` until `{ready:true}`, then +> re-issue the same `browser_open_window`. Before the bridge handled this, a cold PC failed with a +> generic "Failed to launch after 3 attempts" and the AI flailed. Don't reintroduce the panic; read +> the structured response and follow the table. + +## How to Run Commands + +```bash +adom-desktop <prefix>_<verb> '{"sessionId":"myjob", ...}' # do a unit of work +adom-desktop <prefix>_screenshot '{"sessionId":"myjob"}' # capture current state (lossless PNG) +adom-desktop <prefix>_status '{}' # list sessions + readiness +``` + +**These are Bash commands, not MCP tools.** If your verbs are session-scoped, always pass `sessionId` — +`'{}'` targets the *active* session, which may not be the one you mean. + +## Background by default — don't disrupt the user + +**<Bridge Name> is the AI's workspace, not the user's. Keep its windows/output OUT of the user's way unless they asked to watch.** + +- After opening/driving something for automation (a screenshot, a scrape, a debug loop), send it to the + back / leave it un-focused so it doesn't sit on top of what the user is doing (e.g. a + `<prefix>_lower_os_window` or equivalent). +- **Never** force your window to the foreground unless the user must SEE it — a recording, a demo, or a + finished result you're explicitly showing them. +- Use a **non-intrusive nudge** (flash the taskbar via a `<prefix>_alert_window`-style verb, or just + report in chat) for "it's ready / done" instead of stealing focus. +- For long-running output the user WILL look at, a dedicated window is fine — but still don't re-raise + it on every update; nudge instead of grabbing focus. + +```bash +adom-desktop <prefix>_<verb> '{"sessionId":"job", ...}' +adom-desktop <prefix>_lower_os_window '{"sessionId":"job"}' # get it off the user's screen; keep driving headlessly +# ...do work as needed (works while lowered/backgrounded)... +adom-desktop <prefix>_alert_window '{"sessionId":"job"}' # non-intrusive "done" when you want their attention +``` + +## Core verbs + +| Command | Description | Key args | +|---|---|---| +| `<prefix>_readiness` | Cold-start probe: is the bridge set up / still installing? | none | +| `<prefix>_prewarm` | Do first-use setup WITHOUT starting real work | `wait` (optional bool) | +| `<prefix>_open_window` *(or your "start a session" verb)* | Spawn a new session/window | `sessionId`, `profile`, ... | +| `<prefix>_<do-work>` | The bridge's primary action verb(s) | `sessionId`, ... | +| `<prefix>_screenshot` | Capture current state (lossless PNG) | `sessionId`, `fullPage?` | +| `<prefix>_status` | All sessions + readiness | none | +| `<prefix>_lower_os_window` | Send the window to the back (keep it off the user's screen) | `sessionId` | +| `<prefix>_raise_os_window` / `<prefix>_focus_window` | Bring to foreground (use sparingly — only to show the user) | `sessionId` | +| `<prefix>_alert_window` | Flash the taskbar (non-intrusive attention) | `sessionId` | +| `<prefix>_close_window` | Close one session + its window (destructive) | `sessionId` | +| `<prefix>_close` | Close ALL sessions | none | + +Run `adom-desktop <prefix>_describe '{}'` for the full machine-readable verb catalog. + +## Sessions & persistence (if your bridge has sessions) + +If one session = one long-lived context (a window, a connection, a document), say so here, and tell the +AI **how to reuse instead of re-open**: + +- New context entirely → `<prefix>_open_window` (or your start verb). +- Reuse an existing one → drive it directly (a `<prefix>_navigate`/equivalent), don't open a second. + +**Run `<prefix>_status '{}'` before opening a new session** at the start of a conversation (especially +after a context compaction) — if a session already matches what you're about to do, reuse it. Opening a +duplicate leaves the user staring at two identical windows. Pick a **stable `profile`/identifier across +runs** so any persisted state (logins, cookies, caches) survives — the user sets up once, reuses for +months. + +## Verify what you did — `ok:true` is not enough + +Many start/action verbs return `ok:true` as soon as the operation *begins*, not when it *finished* / +rendered. Sanity-check with the cheapest signal: a `<prefix>_eval`/read verb that reflects real state +(fastest), or a `<prefix>_screenshot` when a value isn't discriminating. Don't report success to the +user on `ok:true` alone for anything that can silently land on an error/empty state. + +## Key rules + +- **Always pass `sessionId`** for session-scoped verbs. Never `'{}'` when you mean a specific session. +- **It's `adom-desktop <prefix>_<cmd> <json>`** — there is no top-level `<bridge>` subcommand. +- **Surface `_hint` verbatim** whenever a verb reports it isn't ready or failed — it's written for both + you and the user. +- **Reuse, don't reflexively close+reopen** — persisted state is keyed by `profile`/identifier; prefer a + reload/navigate over a destroy+recreate. +- **Never broadly kill the underlying app/process** — use the bridge's per-session close + (`<prefix>_close_window`) so you don't nuke unrelated work. +- **Document any per-verb gotchas** here (escaping rules, arg quirks, timeouts) — e.g. pup's + "no semicolons in `browser_eval` — use the comma operator or an IIFE." + +---