← Commit history
README.md+11−11
@@ -21,7 +21,7 @@ The cloud AI never touches your machine directly. It sends a verb to the **relay ``` Adom container (this package)            User's laptop +--------------------------+             +--------------------------+-| adom-bridge-cli + skills|  <-relay->  | Adom Bridge GUI (Tauri) |+| adom-bridge + skills|  <-relay->  | Adom Bridge GUI (Tauri) | | (Claude drives verbs)    |             | KiCad, Fusion, Chrome    | +--------------------------+             | shell, files, screens    |                                          +--------------------------+@@ -33,7 +33,7 @@ Adom container (this package)            User's laptop adom-wiki pkg install adom/adom-bridge ``` -That installs the **container-side** pieces: the `adom-bridge-cli` binary (SHA256-verified against this page's `version.json`) and the Claude skill bundle into `~/.claude/skills/`:+That installs the **container-side** pieces: the `adom-bridge` binary (SHA256-verified against this page's `version.json`) and the Claude skill bundle into `~/.claude/skills/`:  | Skill | What Claude learns | |---|---|@@ -78,7 +78,7 @@ Everything a real user installs comes from this page. | Artifact | Platform | What it is | How to get it | |---|---|---|---| | Signed installer (NSIS) | Windows x64 | The GUI desktop app. Azure Trusted Signing. Installs to `%LOCALAPPDATA%\Adom Bridge\` and auto-updates. | The *Download for your machine* box at the top of this page. This is the one promoted Windows download. |-| CLI + skills package | Linux (container) | The `adom-bridge-cli` binary and Claude skill set for a cloud container driving your desktop remotely. Not needed if you use Hydrogen, which bundles Adom Bridge. SHA256-verified on install. | `adom-wiki pkg install adom/adom-bridge` |+| CLI + skills package | Linux (container) | The `adom-bridge` binary and Claude skill set for a cloud container driving your desktop remotely. Not needed if you use Hydrogen, which bundles Adom Bridge. SHA256-verified on install. | `adom-wiki pkg install adom/adom-bridge` | | `version.json` | manifest | The auto-update manifest the GUI polls: per-platform installer URL, SHA256, and size. | Read automatically by the app; you never fetch it by hand. |  The Windows GUI installer is the only promoted Windows download. There is intentionally no standalone Windows CLI download. macOS is planned; the manifest reserves a slot for it.@@ -212,13 +212,13 @@ Adom Bridge speaks to you through native Windows toasts via `notify_user`, under ![Basic alert toast](docs/toast-basic.png)  ```bash-adom-bridge-cli notify_user '{"title":"Export done","body":"gerbers.zip is ready in C:/exports."}'+adom-bridge notify_user '{"title":"Export done","body":"gerbers.zip is ready in C:/exports."}' ```  **Click-to-focus: send them where the notification points.** Most notifications are about something else on screen: a browser waiting on a login, a dialog, an app that needs a click. Pass `focus` and clicking the notification brings THAT window to the front, instead of opening Adom Bridge. Name the window by `hwnd` (exact, from `desktop_list_windows`), by `cacheKey`, or by `titleContains`.  ```bash-adom-bridge-cli notify_user '{"title":"Sign-in needed","body":"Confirm in the browser and I will continue.","focus":{"titleContains":"Sign in - Google Chrome"}}'+adom-bridge notify_user '{"title":"Sign-in needed","body":"Confirm in the browser and I will continue.","focus":{"titleContains":"Sign in - Google Chrome"}}' ```  The response says whether the target resolved, so the AI finds out at send time rather than you finding out by clicking a notification that does nothing. If the window is gone by the time you click, it falls back to opening Adom Bridge, so a click is never dead. The click also counts as an acknowledgement, so the AI learns you engaged even with no buttons on the toast.@@ -228,7 +228,7 @@ The response says whether the target resolved, so the AI finds out at send time ![Sticky toast with buttons](docs/toast-buttons.png)  ```bash-adom-bridge-cli notify_user '{"id":"deploy-42","title":"Ship v2 to production?","body":"The build is green and staged.","scenario":"reminder","buttons":[{"label":"Ship it"},{"label":"Hold"}]}'+adom-bridge notify_user '{"id":"deploy-42","title":"Ship v2 to production?","body":"The build is green and staged.","scenario":"reminder","buttons":[{"label":"Ship it"},{"label":"Hold"}]}' ```  **Progress bar.** For a long job (a gerber export, a render). Re-fire with the SAME `id` as the job advances toward `1.0`. `body` is optional here.@@ -236,7 +236,7 @@ adom-bridge-cli notify_user '{"id":"deploy-42","title":"Ship v2 to production?", ![Progress bar toast](docs/toast-progress.png)  ```bash-adom-bridge-cli notify_user '{"id":"job-1","title":"Exporting gerbers","progress":{"title":"myboard.kicad_pcb","value":0.6,"status":"Plotting copper layers...","valueOverride":"6 / 10 layers"}}'+adom-bridge notify_user '{"id":"job-1","title":"Exporting gerbers","progress":{"title":"myboard.kicad_pcb","value":0.6,"status":"Plotting copper layers...","valueOverride":"6 / 10 layers"}}' ```  **Reply input.** The text field renders, but Windows does not return typed text over protocol activation, so it is display-only today. For a real choice, use buttons.@@ -244,7 +244,7 @@ adom-bridge-cli notify_user '{"id":"job-1","title":"Exporting gerbers","progress ![Reply input toast](docs/toast-input.png)  ```bash-adom-bridge-cli notify_user '{"title":"Quick note","body":"Leave a note for the next run.","scenario":"reminder","inputs":[{"id":"reply","type":"text","placeholder":"Type a reply..."}],"buttons":[{"label":"Send"}]}'+adom-bridge notify_user '{"title":"Quick note","body":"Leave a note for the next run.","scenario":"reminder","inputs":[{"id":"reply","type":"text","placeholder":"Type a reply..."}],"buttons":[{"label":"Send"}]}' ```  **Shell-approval.** Fired by Adom Bridge itself when a gated command needs a human: *Approve 1 hr / 24 hr / Bypass Permissions*, granted straight from the toast without opening the window.@@ -274,13 +274,13 @@ Deep dive: [notifications](docs/notifications.md). Adom Bridge core owns the generic verbs (`desktop_*`, `shell`, `ui_*`, files, `notify_user`, multi-machine). Each bridge owns its own prefix and ships its own verbs; the app relays them verbatim and stays generic. Invoke the same way on every transport, for identical JSON back:  ```bash-adom-bridge-cli screenshot_window '{"hwnd": 12345}'              # CLI (relay / direct API)-adom-bridge-cli --target AzureVM list_windows                    # target a named machine+adom-bridge screenshot_window '{"hwnd": 12345}'              # CLI (relay / direct API)+adom-bridge --target AzureVM list_windows                    # target a named machine curl -s -X POST 127.0.0.1:24680/command \   -d '{"command":"notify_user","args":{"title":"Hi"}}'        # loopback direct API ``` -`adom-bridge-cli help` and `GET /commands` are the live discovery surfaces for the full verb set.+`adom-bridge help` and `GET /commands` are the live discovery surfaces for the full verb set.  Deep dive on the CLI itself: [the CLI guide](docs/cli.md). 
SKILL.md+216−214
@@ -7,13 +7,15 @@ description: Use when the user wants to send files to their desktop, control KiC 
 Bridge between Claude Code (running in an Adom Docker container) and the user's desktop applications via WebSocket.
 
+> **The CLI is `adom-bridge`** (renamed from `adom-bridge-cli` in v2.0.33 — the `-cli` was noise on Linux). Use `adom-bridge <verb>`. The old `adom-bridge-cli` name still works (the pkg links both), so existing scripts don't break — but prefer `adom-bridge` going forward. On Windows the CLI file stays `adom-bridge-cli.exe` (the GUI app owns `adom-bridge.exe` there).
+
 **Install surface:** the canonical install page is `apps/adom-bridge` on the Adom wiki — it hosts the Linux CLI (`docker_binary`), the Windows installer (`.exe`), and the four sibling skills (pup, kicad-bridge, fusion-bridge, + this one). The gallia `adom-bridge-discovery` skill surfaces this page on any related user query. `adom-bridge setup_desktop` also returns a dynamic installer URL pulled from the latest GitHub release.
 
 **First-time setup?** If the user hasn't installed the desktop app yet, see the Setup section below to walk them through downloading, installing, and connecting the app.
 
 Quick check if desktop is connected:
 ```bash
-adom-bridge-cli ping
+adom-bridge ping
 ```
 
 **Companion skills** (installed alongside this one from the wiki):
@@ -28,7 +30,7 @@ Claude Code -> adom-bridge <command> -> Relay Server (HTTP :8766) -> WebSocket : ```
 
 The `adom-bridge` binary is a single Rust CLI that does everything:
-- `adom-bridge-cli serve` -- Start the relay server (WebSocket :8765 + HTTP API :8766)
+- `adom-bridge serve` -- Start the relay server (WebSocket :8765 + HTTP API :8766)
 - `adom-bridge <command> '<json>'` -- Send commands to the desktop app via the running relay
 
 The relay server runs in the Docker container. The Adom Bridge app runs on the user's PC and connects via WebSocket.
@@ -38,17 +40,17 @@ The relay server runs in the Docker container. The Adom Bridge app runs on the u The relay must be running before the desktop app can connect. **In a container, start it SUPERVISED** (v1.9.168+):
 
 ```bash
-adom-bridge-cli serve --supervise
+adom-bridge serve --supervise
 ```
 
-**Why not `adom-bridge-cli serve &`?** A backgrounded (`&`) relay is still a child of your shell/session. When your session is torn down (you finish, or the container reaps it), the relay dies with it — and then every connected desktop hits HTTP 500 on `/proxy/8765/` and loops "Reconnecting in 30s" **for hours** until a human restarts it (wiki issue #24, hit live 2026-07-23). `--supervise` **detaches the relay into its own session (reparented to init)** so it survives your session, and starts a watchdog that:
+**Why not `adom-bridge serve &`?** A backgrounded (`&`) relay is still a child of your shell/session. When your session is torn down (you finish, or the container reaps it), the relay dies with it — and then every connected desktop hits HTTP 500 on `/proxy/8765/` and loops "Reconnecting in 30s" **for hours** until a human restarts it (wiki issue #24, hit live 2026-07-23). `--supervise` **detaches the relay into its own session (reparented to init)** so it survives your session, and starts a watchdog that:
 - restarts the relay with **exponential backoff** (2s → … → 5 min cap; a clean run resets it — a broken relay is never bludgeoned), and
 - **toasts the connected desktops on every restart** with the reason from the relay log, so the owner finds out something went wrong.
 
 ```bash
-adom-bridge-cli serve --supervise            # detached + self-healing (use this in containers)
-adom-bridge-cli serve --supervise --owner AdomLapper   # send restart toasts to one desktop
-adom-bridge-cli serve --stop                 # stop the watchdog + relay
+adom-bridge serve --supervise            # detached + self-healing (use this in containers)
+adom-bridge serve --supervise --owner AdomLapper   # send restart toasts to one desktop
+adom-bridge serve --stop                 # stop the watchdog + relay
 ```
 
 `serve` (plain, foreground) still works and prints a loud hint telling you it will die with the session — fine for a quick local relay you'll sit and watch. **Caveat:** a full CONTAINER REBOOT still needs a boot hook (cron `@reboot` / the container's init); the watchdog covers session teardown, not host reboot.
@@ -66,7 +68,7 @@ curl -sf http://127.0.0.1:8766/health 
 **You are running on a Docker container. You have NO access to the user's desktop.** Guide them step-by-step, ask questions, wait for answers, and verify each step.
 
-> **As soon as you're connected, run `adom-bridge-cli sync_skills` (v1.9.37+).** Installing the
+> **As soon as you're connected, run `adom-bridge sync_skills` (v1.9.37+).** Installing the
 > adom-bridge core pkg gives you only CORE skills — it does NOT give you a DYNAMIC/cloud
 > bridge's skill (kicad, fusion, blender, any third-party). `sync_skills` runs `bridge_list`
 > and installs each bridge's own skill pkg into `~/.claude/skills/`, so you can actually
@@ -78,7 +80,7 @@ curl -sf http://127.0.0.1:8766/health ### Step 1: Check if already connected
 
 ```bash
-adom-bridge-cli ping
+adom-bridge ping
 ```
 
 If this returns `{ "status": "connected" }`, the desktop is already set up -- skip to "Verify the connection" below. If it errors, continue.
@@ -89,7 +91,7 @@ If this returns `{ "status": "connected" }`, the desktop is already set up -- sk curl -sf http://127.0.0.1:8766/health
 ```
 
-If not running (in a container, ALWAYS use `--supervise` so it survives your session): `adom-bridge-cli serve --supervise`
+If not running (in a container, ALWAYS use `--supervise` so it survives your session): `adom-bridge serve --supervise`
 
 ### Step 3: Ask what OS they use
 
@@ -112,7 +114,7 @@ Run the **`register_command`** that `setup_desktop` returned (it's `adom-bridge 
 and the connection appears in their list and auto-connects — **no JSON paste at all**. This is the modern path and the one to try first. (`register_container` needs an Adom cloud container — it uses the mounted account token — and returns `ok:false` on a non-Adom box; if so, use Option A/B below.)
 
-**Already-running desktop? `server_sync` (v1.9.215+).** A signed-in ab reads the cloud profile at boot, at sign-in, and hourly — and `adom-bridge-cli server_sync` (optionally `--target <name>`) makes it re-read RIGHT NOW. The merge is cloud-wins, whole entry including `authToken`. So the flow for adding a server or **rotating a relay token** on live desktops is: update the cloud key (`register_container` writes them) → `server_sync` per desktop → verify with `server_list`. Do NOT use `server_remove` + `server_add` for rotation — removing the entry you are talking through severs your own transport (wiki #387).
+**Already-running desktop? `server_sync` (v1.9.215+).** A signed-in ab reads the cloud profile at boot, at sign-in, and hourly — and `adom-bridge server_sync` (optionally `--target <name>`) makes it re-read RIGHT NOW. The merge is cloud-wins, whole entry including `authToken`. So the flow for adding a server or **rotating a relay token** on live desktops is: update the cloud key (`register_container` writes them) → `server_sync` per desktop → verify with `server_list`. Do NOT use `server_remove` + `server_add` for rotation — removing the entry you are talking through severs your own transport (wiki #387).
 
 ---
 
@@ -148,13 +150,13 @@ Wait for the user to confirm they see a green dot next to the server name before ### Step 6: Verify the connection
 
 ```bash
-adom-bridge-cli ping
+adom-bridge ping
 # Expected: { "echo": "pong", "roundTripMs": ..., "status": "connected" }
 
-adom-bridge-cli status
+adom-bridge status
 # Expected: one client with the user's hostname and capabilities
 
-adom-bridge-cli notify_user '{"title":"Hello from Docker!","body":"Your desktop is connected."}'
+adom-bridge notify_user '{"title":"Hello from Docker!","body":"Your desktop is connected."}'
 ```
 
 Tell the user what you see. If `ping` succeeds:
@@ -175,7 +177,7 @@ Ask: **"Would you like to use the visual browser debugging feature? This opens C |---------|-----|
 | `ping` returns `"No desktop client connected"` | User hasn't added this container in the desktop app yet |
 | Desktop app shows "disconnected" | Check the URL uses `wss://` (not `ws://` or `https://`), port is `8765` |
-| Relay server not running | `adom-bridge-cli serve --supervise` (survives your session; see Starting the Relay Server) |
+| Relay server not running | `adom-bridge serve --supervise` (survives your session; see Starting the Relay Server) |
 | Multiple stale connections | `adom-bridge kick_all` -- app auto-reconnects within seconds |
 | Desktop app not installed | Download from [github.com/adom-inc/adom-bridge/releases](https://github.com/adom-inc/adom-bridge/releases) |
 
@@ -187,16 +189,16 @@ adom-bridge <command> '<json-args>' 
 **Examples:**
 ```bash
-adom-bridge-cli ping
-adom-bridge-cli status
-adom-bridge-cli pup_open_window '{"sessionId":"dart2","url":"https://example.com"}'
-adom-bridge-cli pup_eval '{"sessionId":"dart2","expr":"document.title"}'
-adom-bridge-cli pup_screenshot '{"sessionId":"dart2"}'
-adom-bridge-cli pup_list_windows
-adom-bridge-cli pup_close_window '{"sessionId":"dart2"}'
-adom-bridge-cli notify_user '{"title":"Hello","body":"From Docker"}'
-adom-bridge-cli shell_execute '{"command":"echo hello"}'
-adom-bridge-cli pull_file '{"filePaths":["C:\\Users\\john\\Downloads\\image.png"],"saveTo":"/tmp"}'
+adom-bridge ping
+adom-bridge status
+adom-bridge pup_open_window '{"sessionId":"dart2","url":"https://example.com"}'
+adom-bridge pup_eval '{"sessionId":"dart2","expr":"document.title"}'
+adom-bridge pup_screenshot '{"sessionId":"dart2"}'
+adom-bridge pup_list_windows
+adom-bridge pup_close_window '{"sessionId":"dart2"}'
+adom-bridge notify_user '{"title":"Hello","body":"From Docker"}'
+adom-bridge shell_execute '{"command":"echo hello"}'
+adom-bridge pull_file '{"filePaths":["C:\\Users\\john\\Downloads\\image.png"],"saveTo":"/tmp"}'
 ```
 
 **Output:** JSON to stdout. Screenshots save to `/tmp/adom-bridge-screenshots/` and return the file path.
@@ -207,8 +209,8 @@ adom-bridge-cli pull_file '{"filePaths":["C:\\Users\\john\\Downloads\\image.png" 
 **Get the full structured command list with descriptions, args, and prerequisites:**
 ```bash
-adom-bridge-cli commands            # aliases: list_commands, help
-adom-bridge-cli --target <name> commands   # + THAT box's installed bridges (installed_bridges_on_target)
+adom-bridge commands            # aliases: list_commands, help
+adom-bridge --target <name> commands   # + THAT box's installed bridges (installed_bridges_on_target)
 ```
 Returns categorized JSON with every command, its required/optional args, return values, prerequisites, and workflow notes. Run this FIRST when you're unsure what a verb is called.
 
@@ -233,7 +235,7 @@ Three verbs let external apps (Hydrogen's WSL2 workspace, in particular) registe 
 - `server_add` -- Upsert a relay server by `name`. If a server with that name already exists, the URL (and optionally authToken) is updated rather than creating a duplicate.
   ```bash
-  adom-bridge-cli server_add '{"name":"hydrogen-workspace","url":"ws://localhost:8765","autoConnect":true}'
+  adom-bridge server_add '{"name":"hydrogen-workspace","url":"ws://localhost:8765","autoConnect":true}'
   # → {ok:true, name, url, id, connected:bool, created:bool, _hint}
   ```
   - `name` (required) — dedup key. Repeat calls with the same name are idempotent.
@@ -243,7 +245,7 @@ Three verbs let external apps (Hydrogen's WSL2 workspace, in particular) registe   - Behavior: same URL + already connected → no churn, returns ok. URL changed + autoConnect=true → disconnect old loop + spawn new one. URL changed + autoConnect=false → disconnect old, leave entry registered but not connected.
 - `server_remove` -- Disconnect (if connected) and delete an entry by name. Returns `{ok, removed:bool, wasConnected:bool}`. Idempotent (removing a non-existent entry returns ok with removed:false).
   ```bash
-  adom-bridge-cli server_remove '{"name":"hydrogen-workspace"}'
+  adom-bridge server_remove '{"name":"hydrogen-workspace"}'
   ```
 - `server_list` -- Persisted server list with live connection status: `{servers:[{name, url, id, autoConnect, enabled, connected:bool, clientCount:0|1, status}]}`. `status` is the fine-grained `connected | reconnecting | disconnected` state machine value.
 
@@ -252,24 +254,24 @@ Persists to the same `~/.adom/config.json` the GUI uses, so entries survive a GU Hydrogen-style usage:
 ```bash
 # At Hydrogen container startup
-adom-bridge-cli server_add '{"name":"hydrogen-workspace","url":"ws://localhost:8765","autoConnect":true}'
+adom-bridge server_add '{"name":"hydrogen-workspace","url":"ws://localhost:8765","autoConnect":true}'
 
 # At Hydrogen container shutdown
-adom-bridge-cli server_remove '{"name":"hydrogen-workspace"}'
+adom-bridge server_remove '{"name":"hydrogen-workspace"}'
 ```
 
 #### Addressing multiple Adom Bridges on one relay (v1.8.133+)
 
 One relay can have **several** Adom Bridges connected at once — e.g. a laptop and an Azure VM, eventually many. Each ab has a **name** (its `--client-name` / Settings value, defaulting to the OS hostname). The cloud CLI addresses a specific one, or fans out to all:
 
-- `adom-bridge-cli targets` — list connected ABs: `{count, targets:[{name, hostname, version, os, connectedAt}]}`. Discover names here first.
-- `adom-bridge-cli --target <name> <verb>` — route the verb to that ab only. `--target` is a **global** flag (goes before the verb; case-insensitive; matches `clientName`, else `hostname`). `ADOM_TARGET=<name>` sets a default (the flag wins).
-- `adom-bridge-cli --target all <verb>` — fan out to **every** connected ab; returns `{broadcast:true, targets:[...], results:{<name>:<verb-result>}, summary:{total,ok,failed}}`. A per-ab timeout/failure is isolated to that ab's entry.
+- `adom-bridge targets` — list connected ABs: `{count, targets:[{name, hostname, version, os, connectedAt}]}`. Discover names here first.
+- `adom-bridge --target <name> <verb>` — route the verb to that ab only. `--target` is a **global** flag (goes before the verb; case-insensitive; matches `clientName`, else `hostname`). `ADOM_TARGET=<name>` sets a default (the flag wins).
+- `adom-bridge --target all <verb>` — fan out to **every** connected ab; returns `{broadcast:true, targets:[...], results:{<name>:<verb-result>}, summary:{total,ok,failed}}`. A per-ab timeout/failure is isolated to that ab's entry.
 
 ```bash
-adom-bridge-cli targets
-adom-bridge-cli --target azurevm desktop_screenshot_screen
-adom-bridge-cli --target all hd_status        # every box at once, results keyed by name
+adom-bridge targets
+adom-bridge --target azurevm desktop_screenshot_screen
+adom-bridge --target all hd_status        # every box at once, results keyed by name
 ```
 
 **No-target behavior:** with exactly ONE ab connected, no `--target` is needed (back-compat). With MULTIPLE connected and no `--target`, the call FAILS with `{error:"ambiguous_target", connected:[...], _hint}` (exit 1) — it won't guess which box. A bad name → `{error:"target_not_found", requested, connected:[...]}` (exit 1). Set each ab's name with the `--client-name <name>` launch flag (Hydrogen passes this when spawning the embedded ab), the Settings field, or `config.json` `client_name`. Full design + the relay-routing internals: [`skills/MULTI_AB_ADDRESSING.md`](MULTI_AB_ADDRESSING.md).
@@ -300,7 +302,7 @@ Example — programmatic server registration without the CLI: curl -X POST http://127.0.0.1:47200/command \
   -H 'Content-Type: application/json' \
   -d '{"app":"desktop","command":"server_add","args":{"name":"hydrogen-workspace","url":"ws://localhost:8765","autoConnect":true}}'
-# → identical JSON to `adom-bridge-cli server_add '{...}'`, including the _hint
+# → identical JSON to `adom-bridge server_add '{...}'`, including the _hint
 ```
 
 **What's safe to send directly:** essentially everything sync (`server_*`, `bridge_list`, `hd_status`, `hd_build_status`, `desktop_list_windows`, `desktop_screenshot_*`, `kicad_*`, `fusion_*`, `pup_screenshot`, `notify_user`, ...) plus async-dispatching verbs that return a job id in <500 ms (`bridge_install`, `hd_build`, `desktop_install_kicad`, ...).
@@ -334,7 +336,7 @@ adom-bridge.exe --embedded --start-hidden \ - `--relay-name <name>` → override the default name
 - `--session-token <tok>` → already covered by Phase 4 handoff (env / CLI arg / file)
 
-**Docker introspection** — `adom-bridge-cli desktop_embedded_status` returns `{embedded, owner, source, enteredVia, pendingRelayUrl, pendingRelayName, startHidden, markerPath, markerExists}` so cloud-side callers can branch on whether ab is standalone or Hydrogen-managed. **v1.8.73+:** `enteredVia` reports `"launch-flag"` (Hydrogen spawned ab as a child via `--embedded`) or `"runtime-adopt"` (ab was running standalone; Hydrogen adopted it at runtime) — see "Runtime adopt + release" below.
+**Docker introspection** — `adom-bridge desktop_embedded_status` returns `{embedded, owner, source, enteredVia, pendingRelayUrl, pendingRelayName, startHidden, markerPath, markerExists}` so cloud-side callers can branch on whether ab is standalone or Hydrogen-managed. **v1.8.73+:** `enteredVia` reports `"launch-flag"` (Hydrogen spawned ab as a child via `--embedded`) or `"runtime-adopt"` (ab was running standalone; Hydrogen adopted it at runtime) — see "Runtime adopt + release" below.
 
 **New verbs Hydrogen uses to drive ab** (all also usable standalone):
 
@@ -423,7 +425,7 @@ Bridges are ab's plugins (kicad, puppeteer, fusion360 ship bundled; others insta 
 **Typical "am I on the latest bridges?" flow:**
 ```bash
-adom-bridge-cli bridge_check_updates            # read-only: see current vs wiki per bridge
+adom-bridge bridge_check_updates            # read-only: see current vs wiki per bridge
 # → {bridges:[{name,current,latest,updateAvailable,source,wikiReachable}], updatesAvailable:N}
 adom-bridge refresh_bridges                  # update ALL that are behind
 adom-bridge refresh_bridges '{"name":"kicad"}'   # or just one
@@ -493,32 +495,32 @@ Reference implementations: [`scripts/sample-bridges/hello-python/server.py`](../ 
 ### KiCad (`kicad_*`) — bridge-owned; light pointer
 
-The KiCad bridge is **cloud-owned** and ships its OWN full skill (wiki pkg `adom/kicad-bridge`). This page keeps only the pointer: run `adom-bridge-cli sync_skills` after connecting and the full KiCad guide (project workflows, three-tier `kicad_open_*` routing, DRC recipes, library install) lands in `~/.claude/skills/`.
+The KiCad bridge is **cloud-owned** and ships its OWN full skill (wiki pkg `adom/kicad-bridge`). This page keeps only the pointer: run `adom-bridge sync_skills` after connecting and the full KiCad guide (project workflows, three-tier `kicad_open_*` routing, DRC recipes, library install) lands in `~/.claude/skills/`.
 
 Most-used verbs to get moving:
 
 ```bash
-adom-bridge-cli kicad_open_board '{"path": "C:/projects/board/board.kicad_pcb"}'
-adom-bridge-cli kicad_screenshot_all '{}'          # every KiCad window incl. error dialogs, as an array
-adom-bridge-cli kicad_run_drc '{}'
-adom-bridge-cli kicad_window_info '{}'
-adom-bridge-cli kicad_open_symbol_editor '{"library": "Device"}'
+adom-bridge kicad_open_board '{"path": "C:/projects/board/board.kicad_pcb"}'
+adom-bridge kicad_screenshot_all '{}'          # every KiCad window incl. error dialogs, as an array
+adom-bridge kicad_run_drc '{}'
+adom-bridge kicad_window_info '{}'
+adom-bridge kicad_open_symbol_editor '{"library": "Device"}'
 ```
 
 If KiCad is not installed, the bridge knows how to install it for the user — offer, never point them at a download page. Full details: the bridge's own skill.
 
 ### Fusion 360 (`fusion_*`) — bridge-owned; light pointer
 
-The Fusion 360 bridge is **cloud-owned** and ships its OWN full skill (wiki pkg `adom/fusion-bridge`). Run `adom-bridge-cli sync_skills` and the full guide (cloud files, import/export, add-in troubleshooting, APS search) lands in `~/.claude/skills/`.
+The Fusion 360 bridge is **cloud-owned** and ships its OWN full skill (wiki pkg `adom/fusion-bridge`). Run `adom-bridge sync_skills` and the full guide (cloud files, import/export, add-in troubleshooting, APS search) lands in `~/.claude/skills/`.
 
 Most-used verbs:
 
 ```bash
-adom-bridge-cli fusion_start '{}'                       # launches Fusion; slow on cold start (minutes)
-adom-bridge-cli fusion_get_app_state '{}'               # the status/poll verb
-adom-bridge-cli fusion_addin_status '{}'
-adom-bridge-cli fusion_dismiss_blocking_dialogs '{}'
-adom-bridge-cli fusion_send_key '{"key": "Escape"}'
+adom-bridge fusion_start '{}'                       # launches Fusion; slow on cold start (minutes)
+adom-bridge fusion_get_app_state '{}'               # the status/poll verb
+adom-bridge fusion_addin_status '{}'
+adom-bridge fusion_dismiss_blocking_dialogs '{}'
+adom-bridge fusion_send_key '{"key": "Escape"}'
 ```
 
 Fusion launches take 2-4 minutes cold; respect `timeoutSeconds` + `statusVerb` on responses instead of re-issuing. Full details: the bridge's own skill.
@@ -527,8 +529,8 @@ Fusion launches take 2-4 minutes cold; respect `timeoutSeconds` + `statusVerb` o 
 Two generic CLI commands exist for launching any Windows executable safely — they verify the target exists BEFORE handing it to the OS, so you never trigger a "Windows cannot find" GUI dialog:
 
-- `adom-bridge-cli find_exe '{"name":"..."}'` -- Resolve an exe by absolute path, glob (e.g. `C:/.../webdeploy/production/*/Fusion360.exe` returns newest), bare name (searches PATH + Start Menu `.lnk` targets). Returns `{path, source}`. Does NOT launch.
-- `adom-bridge-cli launch '{"path":"...", "args":[...], "cwd":"...", "detached":true}'` -- Same resolution rules as `find_exe`, then spawns. Fails in terminal (exit 1) with a clear error if the path doesn't exist. **Always prefer this over raw `start`.**
+- `adom-bridge find_exe '{"name":"..."}'` -- Resolve an exe by absolute path, glob (e.g. `C:/.../webdeploy/production/*/Fusion360.exe` returns newest), bare name (searches PATH + Start Menu `.lnk` targets). Returns `{path, source}`. Does NOT launch.
+- `adom-bridge launch '{"path":"...", "args":[...], "cwd":"...", "detached":true}'` -- Same resolution rules as `find_exe`, then spawns. Fails in terminal (exit 1) with a clear error if the path doesn't exist. **Always prefer this over raw `start`.**
 
 For Fusion specifically, use `fusion_start` — it wraps `launch` plus the full startup-picker / add-in-readiness dance.
 - `fusion_import_step` -- Import a STEP/STL/IGES file into Fusion 360
@@ -547,15 +549,15 @@ Executes EAGLE commands inside Fusion 360's Electronics workspace. Works in **Sc **Usage:**
 ```bash
 # Basic command
-adom-bridge-cli fusion_electron_run '{"command": "WINDOW FIT"}'
+adom-bridge fusion_electron_run '{"command": "WINDOW FIT"}'
 
 # Multiple commands in sequence (use semicolons)
-adom-bridge-cli fusion_electron_run '{"command": "DISPLAY NONE; DISPLAY 1 16 17 18 20 21"}'
+adom-bridge fusion_electron_run '{"command": "DISPLAY NONE; DISPLAY 1 16 17 18 20 21"}'
 
 # Navigate in library editor
-adom-bridge-cli fusion_electron_run '{"command": "EDIT SOIC8.pac"}'
-adom-bridge-cli fusion_electron_run '{"command": "EDIT RESISTOR.sym"}'
-adom-bridge-cli fusion_electron_run '{"command": "EDIT MYDEVICE.dev"}'
+adom-bridge fusion_electron_run '{"command": "EDIT SOIC8.pac"}'
+adom-bridge fusion_electron_run '{"command": "EDIT RESISTOR.sym"}'
+adom-bridge fusion_electron_run '{"command": "EDIT MYDEVICE.dev"}'
 ```
 
 **Response fields:**
@@ -656,9 +658,9 @@ Fusion 360's .lbr format supports `package3d` elements that link footprints to 3 To open a built-in example library for reference:
 ```bash
 # Find the examples directory first
-adom-bridge-cli fusion_execute_text_command '{"command": "Python.RunScript C:/tmp/find_eagle_libs.py"}'
+adom-bridge fusion_execute_text_command '{"command": "Python.RunScript C:/tmp/find_eagle_libs.py"}'
 # Then open one
-adom-bridge-cli fusion_open_lbr '{"filePath": "<path>/Connector_USB.lbr"}'
+adom-bridge fusion_open_lbr '{"filePath": "<path>/Connector_USB.lbr"}'
 ```
 - `fusion_open_schematic` -- Open a .sch schematic in Fusion's Schematic Editor. Args: `filePath`.
 - `fusion_open_board` -- Open a .brd board layout in Fusion's Board Layout editor. Args: `filePath`.
@@ -677,35 +679,35 @@ adom-bridge-cli fusion_open_lbr '{"filePath": "<path>/Connector_USB.lbr"}' Commands for interacting with Fusion 360's UI — detecting dialogs, taking screenshots, clicking, and sending keyboard input. Essential for handling blocking dialogs and automating CEF-based UI elements.
 
 - `fusion_window_info` -- Returns the Fusion main window HWND, title, rect, and a list of all Qt dialog windows (recovery dialogs, wizards, file pickers). Essential for detecting blocking dialogs before/after operations.
-  - Example: `adom-bridge-cli fusion_window_info`
+  - Example: `adom-bridge fusion_window_info`
   - Returns: `{hwnd, title, rect, dialogs: [{hwnd, title, className, rect}]}`
 
 - `fusion_screenshot_fusion` -- Captures the Fusion main window or a specific dialog by HWND. Uses PrintWindow (works without bringing to foreground). Saves WebP to `C:/tmp/adom-bridge-screenshots/` (falls back to PNG if WebP unavailable). Downscaled to ≤1568px — use relative coords (0.0-1.0) for clicking, they're scale-independent. DPI-aware.
   - Args: `hwnd` (optional — dialog HWND to screenshot instead of main window)
-  - Example: `adom-bridge-cli fusion_screenshot_fusion` (main window)
-  - Example: `adom-bridge-cli fusion_screenshot_fusion '{"hwnd": 12345}'` (specific dialog)
+  - Example: `adom-bridge fusion_screenshot_fusion` (main window)
+  - Example: `adom-bridge fusion_screenshot_fusion '{"hwnd": 12345}'` (specific dialog)
 
 - `fusion_screenshot_all` -- Screenshots the main Fusion window and lists all dialog windows with their HWNDs. Use `fusion_screenshot_fusion {"hwnd": ...}` to capture each dialog.
-  - Example: `adom-bridge-cli fusion_screenshot_all`
+  - Example: `adom-bridge fusion_screenshot_all`
 
 - `fusion_click_fusion` -- Click at coordinates within the Fusion window or a specific dialog. x/y are relative (0.0-1.0) by default. Set `"relative": false` for pixel offsets. Uses SendInput for CEF dialog compatibility. **Prefer `fusion_send_key` for dialogs** — enter/escape/tab covers most cases.
   - Args: `x` (required), `y` (required), `relative` (optional, default true), `hwnd` (optional — target a specific dialog HWND instead of main window)
-  - Example: `adom-bridge-cli fusion_click_fusion '{"x": 0.5, "y": 0.7}'` (main window)
-  - Example: `adom-bridge-cli fusion_click_fusion '{"hwnd": 12345, "x": 0.75, "y": 0.85}'` (dialog button)
+  - Example: `adom-bridge fusion_click_fusion '{"x": 0.5, "y": 0.7}'` (main window)
+  - Example: `adom-bridge fusion_click_fusion '{"hwnd": 12345, "x": 0.75, "y": 0.85}'` (dialog button)
 
 - `fusion_send_key` -- Send keyboard input to Fusion or a specific dialog via SendInput. **Preferred way to dismiss dialogs**: enter to confirm, escape to cancel, tab to cycle buttons. Use click only if tab order doesn't reach the right button.
   - Args: `key` (required), `hwnd` (optional — target a specific dialog HWND)
-  - Example: `adom-bridge-cli fusion_send_key '{"key": "escape"}'` (dismiss CEF dialog)
-  - Example: `adom-bridge-cli fusion_send_key '{"key": "enter", "hwnd": 12345}'` (confirm dialog)
+  - Example: `adom-bridge fusion_send_key '{"key": "escape"}'` (dismiss CEF dialog)
+  - Example: `adom-bridge fusion_send_key '{"key": "enter", "hwnd": 12345}'` (confirm dialog)
 
 #### Fusion 360 Workflow Guide
 
 ##### Opening Electronics Projects
-1. Open the `.fprj` file: `adom-bridge-cli fusion_open_cloud_file '{"projectName":"Main","fileName":"DRV8411A","fileExtension":"fprj","folderPath":"Molecules/XRP/DRV8411A"}'`
+1. Open the `.fprj` file: `adom-bridge fusion_open_cloud_file '{"projectName":"Main","fileName":"DRV8411A","fileExtension":"fprj","folderPath":"Molecules/XRP/DRV8411A"}'`
 2. Do NOT try to open `.fbrd` or `.fsch` directly — they fail or show a "Select Electronics Design File" dialog
-3. Enter the board editor: `adom-bridge-cli fusion_show_2d_board`
-4. Enter the schematic editor: After step 3, use `adom-bridge-cli fusion_electron_run '{"command":"EDIT .sch"}'`
-5. Switch back to board: `adom-bridge-cli fusion_electron_run '{"command":"EDIT .brd"}'`
+3. Enter the board editor: `adom-bridge fusion_show_2d_board`
+4. Enter the schematic editor: After step 3, use `adom-bridge fusion_electron_run '{"command":"EDIT .sch"}'`
+5. Switch back to board: `adom-bridge fusion_electron_run '{"command":"EDIT .brd"}'`
 
 ##### Auto-Screenshot on Open Commands
 
@@ -734,15 +736,15 @@ Screenshots are saved as WebP (lossless, ~20-40KB each, downscaled to ≤1568px) ##### Detecting and Handling Blocking Dialogs
 - The auto-screenshot captures dialog windows separately (look for `type: "dialog"` entries in `postOpenScreenshot.screenshots[]`)
 - Common blocking dialogs: "What do you want to design?", "Recovered Documents", "Open recovery document instead?", "Select Electronics Design File"
-- Dismiss recovery dialogs: `adom-bridge-cli fusion_dismiss_recovery`
-- Dismiss CEF dialogs (inside main window): `adom-bridge-cli fusion_send_key '{"key": "escape"}'` or `fusion_click_fusion`
-- Dismiss "What do you want to design?" wizard: `adom-bridge-cli fusion_send_key '{"key": "escape"}'`
-- For manual screenshot of specific dialogs: `adom-bridge-cli desktop_screenshot_window '{"hwnd": <DIALOG_HWND>}'`
+- Dismiss recovery dialogs: `adom-bridge fusion_dismiss_recovery`
+- Dismiss CEF dialogs (inside main window): `adom-bridge fusion_send_key '{"key": "escape"}'` or `fusion_click_fusion`
+- Dismiss "What do you want to design?" wizard: `adom-bridge fusion_send_key '{"key": "escape"}'`
+- For manual screenshot of specific dialogs: `adom-bridge desktop_screenshot_window '{"hwnd": <DIALOG_HWND>}'`
 
 ##### Preventing Recovery Documents
 - Recovery files are at: `%LOCALAPPDATA%\Autodesk\Autodesk Fusion 360\<USER_ID>\CrashRecovery\`
-- Before force-killing Fusion, close all documents: `adom-bridge-cli fusion_close_all_documents '{"saveChanges": false}'`
-- **Best practice**: Call `adom-bridge-cli fusion_relocate_recovery` before launching Fusion. This moves recovery files to `~/.adom/recovery/fusion/<timestamp>/` — preserving them for the user while preventing modal dialogs.
+- Before force-killing Fusion, close all documents: `adom-bridge fusion_close_all_documents '{"saveChanges": false}'`
+- **Best practice**: Call `adom-bridge fusion_relocate_recovery` before launching Fusion. This moves recovery files to `~/.adom/recovery/fusion/<timestamp>/` — preserving them for the user while preventing modal dialogs.
 - The `fusion_start` command also auto-relocates recovery files before starting Fusion.
 - The `fusion_dismiss_recovery` command handles both "Recovered Documents" list and "Open recovery document instead?" prompts, and also relocates files.
 
@@ -774,7 +776,7 @@ Screenshots are saved as WebP (lossless, ~20-40KB each, downscaled to ≤1568px)   - Example: Capture all 6 standard views for use as product images/icons:
     ```bash
     for orient in home front back top bottom left right; do
-      adom-bridge-cli fusion_take_screenshot "{\"outputPath\": \"C:/tmp/3d-${orient}.png\", \"width\": 1920, \"height\": 1080, \"orientation\": \"${orient}\"}"
+      adom-bridge fusion_take_screenshot "{\"outputPath\": \"C:/tmp/3d-${orient}.png\", \"width\": 1920, \"height\": 1080, \"orientation\": \"${orient}\"}"
     done
     ```
 
@@ -787,25 +789,25 @@ Screenshots are saved as WebP (lossless, ~20-40KB each, downscaled to ≤1568px) - **Full workflow to export both board and schematic from a cloud project:**
   ```bash
   # Step 1: Open the .fprj (NOT .fbrd/.fsch directly — those fail)
-  adom-bridge-cli fusion_open_cloud_file '{"projectName":"Main", "fileName":"MyDesign", "fileExtension":"fprj", "folderPath":"Molecules/MyDesign"}'
+  adom-bridge fusion_open_cloud_file '{"projectName":"Main", "fileName":"MyDesign", "fileExtension":"fprj", "folderPath":"Molecules/MyDesign"}'
 
   # Step 2: Screenshot to check for blocking dialogs (always do this after open)
-  adom-bridge-cli fusion_screenshot_fusion
+  adom-bridge fusion_screenshot_fusion
 
   # Step 3: Enter board view (MUST be Board Layout workspace, not 3D)
-  adom-bridge-cli fusion_show_2d_board
+  adom-bridge fusion_show_2d_board
 
   # Step 4: Export .fbrd (board first — it's already in board view)
-  adom-bridge-cli fusion_export_source '{"outputPath": "C:/tmp/exports/MyDesign.fbrd"}'
+  adom-bridge fusion_export_source '{"outputPath": "C:/tmp/exports/MyDesign.fbrd"}'
 
   # Step 5: Switch to schematic (EDIT .s1 = first schematic sheet)
-  adom-bridge-cli fusion_electron_run '{"command": "EDIT .s1"}'
+  adom-bridge fusion_electron_run '{"command": "EDIT .s1"}'
 
   # Step 6: Export .fsch
-  adom-bridge-cli fusion_export_source '{"outputPath": "C:/tmp/exports/MyDesign.fsch"}'
+  adom-bridge fusion_export_source '{"outputPath": "C:/tmp/exports/MyDesign.fsch"}'
 
   # Step 7: Pull files to Docker
-  adom-bridge-cli pull_file '{"filePaths":["C:/tmp/exports/MyDesign.fbrd","C:/tmp/exports/MyDesign.fsch"], "saveTo":"/tmp/exports"}'
+  adom-bridge pull_file '{"filePaths":["C:/tmp/exports/MyDesign.fbrd","C:/tmp/exports/MyDesign.fsch"], "saveTo":"/tmp/exports"}'
   ```
 
 - **Critical gotchas:**
@@ -827,12 +829,12 @@ Screenshots are saved as WebP (lossless, ~20-40KB each, downscaled to ≤1568px) 
   ```bash
   # Board (.brd) — must be in PCB Editor / Board Layout
-  adom-bridge-cli fusion_show_2d_board
-  adom-bridge-cli fusion_export_eagle_source '{"outputPath": "C:/tmp/exports/MyDesign.brd"}'
+  adom-bridge fusion_show_2d_board
+  adom-bridge fusion_export_eagle_source '{"outputPath": "C:/tmp/exports/MyDesign.brd"}'
 
   # Schematic (.sch) — must be in Schematic Editor
-  adom-bridge-cli fusion_electron_run '{"command": "EDIT .s1"}'
-  adom-bridge-cli fusion_export_eagle_source '{"outputPath": "C:/tmp/exports/MyDesign.sch"}'
+  adom-bridge fusion_electron_run '{"command": "EDIT .s1"}'
+  adom-bridge fusion_export_eagle_source '{"outputPath": "C:/tmp/exports/MyDesign.sch"}'
   ```
 
 ##### Dialog Dismissal (`fusion_close_window`)
@@ -856,21 +858,21 @@ Screenshots are saved as WebP (lossless, ~20-40KB each, downscaled to ≤1568px) Export and re-import Fusion electronics libraries:
 ```bash
 # 1. Open library from cloud
-adom-bridge-cli fusion_open_cloud_file '{"projectName":"Main","fileName":"Adom Common Components","folderPath":"Molecules/Libraries"}'
+adom-bridge fusion_open_cloud_file '{"projectName":"Main","fileName":"Adom Common Components","folderPath":"Molecules/Libraries"}'
 
 # 2. Export as .flbr (Fusion-native binary) and .scr (EAGLE script text)
-adom-bridge-cli fusion_save_lbr '{"outputPath":"C:/tmp/library.flbr"}'
-adom-bridge-cli fusion_export_lbr '{"outputPath":"C:/tmp/library.scr"}'
-adom-bridge-cli fusion_close_document
+adom-bridge fusion_save_lbr '{"outputPath":"C:/tmp/library.flbr"}'
+adom-bridge fusion_export_lbr '{"outputPath":"C:/tmp/library.scr"}'
+adom-bridge fusion_close_document
 
 # 3. Re-import the .flbr
-adom-bridge-cli fusion_import_electronics '{"filePath":"C:/tmp/library.flbr"}'
+adom-bridge fusion_import_electronics '{"filePath":"C:/tmp/library.flbr"}'
 
 # 4. Verify symbols survived round-trip
-adom-bridge-cli fusion_export_lbr '{"outputPath":"C:/tmp/library-verify.scr"}'
+adom-bridge fusion_export_lbr '{"outputPath":"C:/tmp/library-verify.scr"}'
 
 # 5. Save to cloud with new name
-adom-bridge-cli fusion_save_to_cloud '{"name":"Library-copy"}'
+adom-bridge fusion_save_to_cloud '{"name":"Library-copy"}'
 ```
 
 ##### Demo Projects
@@ -903,14 +905,14 @@ Manage Fusion 360 cloud documents (hub projects, files, versions). Required for   - **Per-folder timeout (30s):** If a single folder's cloud API calls take >30s (common for large projects), the folder is skipped and counted in `foldersSkipped`. This prevents indefinite hangs.
   - **Progress tracking:** While running, `fusion_addin_status` returns `walkProgress` with `{foldersVisited, filesFound, currentFolder, queueSize}` — poll this every 1–10s to monitor progress (see "Live folder progress streaming" below).
   - **Non-blocking alternative:** Use `fusion_search_cloud_files` for targeted searches, or `fusion_list_cloud_files` for single-folder listings (these are faster but don't recurse).
-  - Example: `adom-bridge-cli fusion_walk_cloud_tree '{"projectName":"Main","folderPath":"Molecules","nameContains":"DRV","extensions":["fprj"]}'`
+  - Example: `adom-bridge fusion_walk_cloud_tree '{"projectName":"Main","folderPath":"Molecules","nameContains":"DRV","extensions":["fprj"]}'`
 
 #### Live folder progress streaming with `watch`
 
 For long walks, use the built-in `watch` wrapper. It spawns the inner search command on a worker thread, polls `fusion_addin_status` internally, and emits one JSON event per line to stdout as the walker visits each folder. **No manual polling loop needed.**
 
 ```bash
-adom-bridge-cli watch '{"command":"fusion_walk_cloud_tree","args":{"projectName":"Main","folderPath":"Molecules","nameContains":"BQ25792"}}'
+adom-bridge watch '{"command":"fusion_walk_cloud_tree","args":{"projectName":"Main","folderPath":"Molecules","nameContains":"BQ25792"}}'
 ```
 
 Output is one JSON object per line, three event types:
@@ -929,7 +931,7 @@ Read stdout line-by-line. Stop when you see `event:complete` or `event:error`. T **Optional `interval` arg** (default 2s, clamped to [1, 30]):
 
 ```bash
-adom-bridge-cli watch '{"command":"fusion_walk_cloud_tree","args":{...},"interval":1}'
+adom-bridge watch '{"command":"fusion_walk_cloud_tree","args":{...},"interval":1}'
 ```
 
 **For Claude Code consumers**: pipe `watch` directly into the `Monitor` tool. Each JSON line becomes a real-time event notification in the chat — you (and the user) see folder names appear one-by-one as the walker visits them. No bash loop, no `disown`, no subprocess gymnastics. Verified live in 1.3.16 on a 169-folder walk that returned all 12 BQ25792 cloud files cleanly.
@@ -984,26 +986,26 @@ Also supported for import via `fusion_import_step`: STEP (.step/.stp), STL (.stl ```bash
 # === workspace to Fusion Cloud ===
 # 1. Send file from Docker to Windows desktop
-adom-bridge-cli send_files '{"files": [{"path": "/home/user/component.step"}]}'
+adom-bridge send_files '{"files": [{"path": "/home/user/component.step"}]}'
 # 2. Import into Fusion
-adom-bridge-cli fusion_import_step '{"filePath": "C:/Users/john/Downloads/component.step"}'
+adom-bridge fusion_import_step '{"filePath": "C:/Users/john/Downloads/component.step"}'
 # 3. Save to cloud (gets a wip_urn for 3D library linking)
-adom-bridge-cli fusion_save_to_cloud '{"name": "my-component", "projectName": "Personal"}'
+adom-bridge fusion_save_to_cloud '{"name": "my-component", "projectName": "Personal"}'
 
 # === Fusion Cloud to Docker ===
 # 1. Find and open the cloud file
-adom-bridge-cli fusion_walk_cloud_tree '{"projectName": "Personal", "nameContains": "my-component"}'
-adom-bridge-cli fusion_open_cloud_file '{"fileName": "my-component", "projectName": "Personal"}'
+adom-bridge fusion_walk_cloud_tree '{"projectName": "Personal", "nameContains": "my-component"}'
+adom-bridge fusion_open_cloud_file '{"fileName": "my-component", "projectName": "Personal"}'
 # 2. Export to local filesystem
-adom-bridge-cli fusion_export_cloud_file '{"outputPath": "C:/tmp/export/my-component.step", "format": "step"}'
+adom-bridge fusion_export_cloud_file '{"outputPath": "C:/tmp/export/my-component.step", "format": "step"}'
 # 3. Pull back to Docker
-adom-bridge-cli pull_file '{"path": "C:/tmp/export/my-component.step"}'
+adom-bridge pull_file '{"path": "C:/tmp/export/my-component.step"}'
 
 # === Cloud management ===
-adom-bridge-cli fusion_list_cloud_projects '{}'
-adom-bridge-cli fusion_create_cloud_folder '{"folderName": "Electronics", "projectName": "Main"}'
-adom-bridge-cli fusion_list_cloud_files '{"projectName": "Main", "folderPath": "Electronics"}'
-adom-bridge-cli fusion_delete_cloud_file '{"fileName": "old-file", "projectName": "Main"}'
+adom-bridge fusion_list_cloud_projects '{}'
+adom-bridge fusion_create_cloud_folder '{"folderName": "Electronics", "projectName": "Main"}'
+adom-bridge fusion_list_cloud_files '{"projectName": "Main", "folderPath": "Electronics"}'
+adom-bridge fusion_delete_cloud_file '{"fileName": "old-file", "projectName": "Main"}'
 ```
 
 #### Manufacturing Exports (Gerbers, BOM, CPL)
@@ -1101,28 +1103,28 @@ Every manufacturing command returns structured JSON with: 
 ```bash
 # 1. Open the board
-adom-bridge-cli fusion_open_board '{"filePath": "C:/projects/myboard.brd"}'
+adom-bridge fusion_open_board '{"filePath": "C:/projects/myboard.brd"}'
 
 # 2. Detect layer count (auto-selects 2-layer or 4-layer rules/CAM)
-adom-bridge-cli fusion_detect_layers
+adom-bridge fusion_detect_layers
 
 # 3. Apply Adom design rules for the detected layer count + run DRC
-adom-bridge-cli fusion_set_design_rules '{"action": "apply"}'
-adom-bridge-cli fusion_electron_run '{"command": "DRC"}'
+adom-bridge fusion_set_design_rules '{"action": "apply"}'
+adom-bridge fusion_electron_run '{"command": "DRC"}'
 
 # 4. Export manufacturing files (gerbers auto-select correct CAM job)
-adom-bridge-cli fusion_export_gerbers '{"outputDir": "C:/tmp/mfg"}'
-adom-bridge-cli fusion_export_bom '{"outputPath": "C:/tmp/mfg/bom.csv"}'
-adom-bridge-cli fusion_export_cpl '{"outputPath": "C:/tmp/mfg/cpl.csv"}'
+adom-bridge fusion_export_gerbers '{"outputDir": "C:/tmp/mfg"}'
+adom-bridge fusion_export_bom '{"outputPath": "C:/tmp/mfg/bom.csv"}'
+adom-bridge fusion_export_cpl '{"outputPath": "C:/tmp/mfg/cpl.csv"}'
 
 # 5. Export board images for review
-adom-bridge-cli fusion_export_board_image '{"outputPath": "C:/tmp/mfg/top-copper.png", "preset": "top_copper"}'
-adom-bridge-cli fusion_export_board_image '{"outputPath": "C:/tmp/mfg/assembly-top.png", "preset": "assembly_top"}'
-adom-bridge-cli fusion_export_board_image '{"outputPath": "C:/tmp/mfg/board-outline.png", "preset": "board_outline"}'
+adom-bridge fusion_export_board_image '{"outputPath": "C:/tmp/mfg/top-copper.png", "preset": "top_copper"}'
+adom-bridge fusion_export_board_image '{"outputPath": "C:/tmp/mfg/assembly-top.png", "preset": "assembly_top"}'
+adom-bridge fusion_export_board_image '{"outputPath": "C:/tmp/mfg/board-outline.png", "preset": "board_outline"}'
 
 # 6. Pull files back to Docker for Adom PCBA ordering
-adom-bridge-cli pull_file '{"path": "C:/tmp/mfg/bom.csv"}'
-adom-bridge-cli pull_file '{"path": "C:/tmp/mfg/cpl.csv"}'
+adom-bridge pull_file '{"path": "C:/tmp/mfg/bom.csv"}'
+adom-bridge pull_file '{"path": "C:/tmp/mfg/cpl.csv"}'
 ```
 
 ### Desktop Tools
@@ -1131,8 +1133,8 @@ adom-bridge-cli pull_file '{"path": "C:/tmp/mfg/cpl.csv"}' - `desktop_open_url` -- Open a URL in the user's **NATIVE OS BROWSER** — i.e. the real Edge / Chrome / Firefox / Brave they use every day, with their saved logins, history, bookmarks, and extensions. This is NOT pup, NOT Chrome for Testing — it's the browser the human actually uses. Hand it off when the user needs to interact with a logged-in account themselves.
   - Args: `url` (required string), `browser` (optional: `"default"` (Windows-registered default — could be Edge, Chrome, Firefox, Brave; whatever they set), `"chrome"`, `"edge"`, `"firefox"`, `"brave"`).
   - Examples:
-    - `adom-bridge-cli desktop_open_url '{"url":"https://claude.ai/"}'` -- opens in user's default native browser, already signed in
-    - `adom-bridge-cli desktop_open_url '{"url":"https://docs.example.com","browser":"edge"}'` -- force native Edge
+    - `adom-bridge desktop_open_url '{"url":"https://claude.ai/"}'` -- opens in user's default native browser, already signed in
+    - `adom-bridge desktop_open_url '{"url":"https://docs.example.com","browser":"edge"}'` -- force native Edge
   - Returns `{ok, browser, url, exePath?}`. Allowed schemes: http, https, mailto, ftp, ftps. Other schemes refused.
 
 #### `desktop_open_url` vs `pup_open_window` — two completely different browsers
@@ -1154,8 +1156,8 @@ Default `pup_open_window` (no native args) launches Chrome for Testing — same Native mode drives the user's **REAL browser** — their actual signed-in profile, with live cookies, org policies, and device enrollment intact (NOT a clone — v1.0.6 replaced the old clone approach). Opt-in via TWO additive args from `desktop_list_browsers`:
 
 ```bash
-adom-bridge-cli desktop_list_browsers     # installed browsers + profiles (+ the signed-in account per profile)
-adom-bridge-cli pup_open_window '{
+adom-bridge desktop_list_browsers     # installed browsers + profiles (+ the signed-in account per profile)
+adom-bridge pup_open_window '{
   "sessionId":"gcp",
   "nativeBrowser":"chrome",            # chrome / edge / brave
   "nativeProfile":"Default",           # profile id: Default / Profile 1 / Profile 2
@@ -1219,7 +1221,7 @@ python3 -m http.server 8786 --bind 0.0.0.0 & #    Slug looks like: <username>-<container>-<random-id>
 
 # 3. Open the public URL — NOT localhost:
-adom-bridge-cli pup_open_window '{
+adom-bridge pup_open_window '{
   "sessionId":"demo",
   "url":"https://<your-container-slug>.adom.cloud/proxy/8786/foo.html"
 }'
@@ -1230,12 +1232,12 @@ If you don't have the suffix handy, just ask the user. Or if the file you wanted 
 - `desktop_bring_to_front` -- Bring a window to the foreground by HWND or title substring. Uses keybd_event + AttachThreadInput to bypass Windows foreground lock. Preserves maximized state.
   - Args: `hwnd` (int) OR `titleContains` (string, case-insensitive). One required.
-  - Example: `adom-bridge-cli desktop_bring_to_front '{"titleContains": "Fusion"}'`
+  - Example: `adom-bridge desktop_bring_to_front '{"titleContains": "Fusion"}'`
 - `desktop_set_window_state` -- Change a window's show state without bringing it to the foreground (or both, if combined with `desktop_bring_to_front`). Args: `hwnd` (int) OR `titleContains` (string) OR `cacheKey`, plus `state` (one of `"maximize"`, `"minimize"`, `"restore"`, `"show"`, `"hide"`, `"bottom"`). Use to ensure Fusion or KiCad windows are maximized for screenshots, or to hide noisy background windows during demos.
-  - Example: `adom-bridge-cli desktop_set_window_state '{"hwnd":133560,"state":"maximize"}'`
+  - Example: `adom-bridge desktop_set_window_state '{"hwnd":133560,"state":"maximize"}'`
   - **`state:"bottom"` (v1.9.114) -- z-order send-to-back, background.** Drops the window BEHIND all others without focusing, moving, or resizing it (`SetWindowPos HWND_BOTTOM` + `SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE`); works even on an off-screen window. It's an ATOMIC check-and-demote: it reads `GetForegroundWindow()` first, and if the target IS the OS foreground (the user is interacting with it) it SKIPS the demotion and returns `wasForeground:true, sentToBack:false` so the caller backs off with no race; otherwise `wasForeground:false, sentToBack:true`. Use to park a background window behind the user's windows (still on-screen, taskbar-clickable, rendering for screenshots) without stealing focus.
   - **`force:true` (v1.9.115)** sends it to back EVEN IF the window is currently the OS foreground (still no activation change; `sentToBack:true` always, `wasForeground` still reported). Use at window-LAUNCH time: Windows reassigns the foreground to a brand-new window when the previous foreground is destroyed, so "foreground at launch" is not a real user signal.
-  - Example: `adom-bridge-cli desktop_set_window_state '{"titleContains":"(session: abc","state":"bottom","force":true}'`
+  - Example: `adom-bridge desktop_set_window_state '{"titleContains":"(session: abc","state":"bottom","force":true}'`
 
 #### Real OS input — mouse + keyboard (v1.8.170+)
 
@@ -1252,9 +1254,9 @@ Verbs: 
 Canonical "navigate a browser to a URL" flow (works on `chrome://` where CDP can't):
 ```bash
-adom-bridge-cli desktop_press_key '{"keys":["ctrl+l"],"window":"Edge"}'   # focus Edge + select the address bar
-adom-bridge-cli desktop_type        '{"text":"edge://extensions","window":"Edge"}'
-adom-bridge-cli desktop_press_key   '{"keys":["enter"],"window":"Edge"}'
+adom-bridge desktop_press_key '{"keys":["ctrl+l"],"window":"Edge"}'   # focus Edge + select the address bar
+adom-bridge desktop_type        '{"text":"edge://extensions","window":"Edge"}'
+adom-bridge desktop_press_key   '{"keys":["enter"],"window":"Edge"}'
 ```
 For a control that isn't reachable by CDP/coords (e.g. the Reload button on `edge://extensions`, which lives in a shadow DOM), Tab-navigate to it then Enter: `desktop_press_key {"keys":["tab","tab","enter"]}`.
 
@@ -1330,16 +1332,16 @@ Two ways to use it: - **`display: {virtualScreen, primary{w,h,dpi,scale}, monitors[], monitorOfWindow}`** — the full monitor topology: total virtual bounds, and per-monitor `bounds`/`dpi`/`scale`/`primary`. `monitorOfWindow` indexes into `monitors[]`. (Same data as the standalone `desktop_display` verb, folded in.)
 - **`drive: "background"`** — screenshots never foreground the window (PrintWindow/WGC). The companion `_related` field lists sibling screenshot/window verbs.
 
-**Screenshot cache is self-cleaning + self-describing (v1.9.107/108/113):** the CLI auto-pulls every screenshot to ONE user-visible, namespaced dir on the caller's machine (`~/project/screenshots/adom-bridge/`, else `~/.adom/screenshots/`; `ADOM_SHOTS_DIR` overrides) — never `/tmp` anymore. Shots are **bucketed by kind into subfolders**: `window/` (a specific window / app / owned popup) and `screen/` (a full-desktop grab). **Human-readable filenames (v1.9.113):** `<YYYY-MM-DDThh-mm-ss.mmm>[-<hwnd>].png` — a sortable ISO stamp (lexicographic == chronological), plus the `hwnd` for a window capture; the `window`/`screen` kind is NOT repeated in the name because the folder already says it. **One file when small (v1.9.114):** if the shot was already within the safe size cap (the common case) it's a single `<stem>.png` and `localFullPath == localSafePath` — no redundant re-encoded copy. Only a shot large enough to be downscaled produces a `<stem>.full.png` + `<stem>.safe.png` pair (Read the `.safe` one for image analysis; use `.full` for archive). Each shot gets a **`<stem>.json` sidecar** (shotlog-style: `source`, `timestamp`, `kind`, dims + `bytes`, `coordMap`, `rect`; a full+safe pair shares one). **Parent/child grouping (v1.9.111/113):** when `desktop_screenshot_window` returns owned popups in `screenshots[]`, the parent AND each pulled child share the same stem, and each child gets a **`.popup-NN` label** — e.g. parent `2026-07-15T01-14-16.937-986710.safe.png` and children `2026-07-15T01-14-16.937-986710.popup-01.safe.png`, `...popup-02...`. So a plain `ls` shows the whole capture grouped (shared stem) and the role (`.popup-NN`) without opening a sidecar; the sidecar also carries `parent` + `popupIndex`. (`kicad_screenshot_all` returns sibling windows with no parent, so those keep their own names.) A janitor prunes it (7 days / 2 GB / newest kept; sidecars follow their PNG, orphans swept) and drains the old `/tmp/ad-shots` + `/tmp/adom-bridge-screenshots` backlogs, running opportunistically at most once/24h on any CLI call. **You do not need to clean it up or write your own cron.** Every screenshot response carries a `_cacheHint`; verify coverage with **`adom-bridge-cli janitor`** (status: each subfolder, size, policy, last sweep) or force a sweep with **`adom-bridge-cli janitor '{"sub":"run"}'`**. It never touches other tools' files under `~/project/screenshots/` (e.g. shotlog). Tune via `ADOM_SHOTS_MAX_AGE_DAYS` / `ADOM_SHOTS_MAX_GB` / `ADOM_SHOTS_MAX_FILES`, or disable with `ADOM_JANITOR=0`.
+**Screenshot cache is self-cleaning + self-describing (v1.9.107/108/113):** the CLI auto-pulls every screenshot to ONE user-visible, namespaced dir on the caller's machine (`~/project/screenshots/adom-bridge/`, else `~/.adom/screenshots/`; `ADOM_SHOTS_DIR` overrides) — never `/tmp` anymore. Shots are **bucketed by kind into subfolders**: `window/` (a specific window / app / owned popup) and `screen/` (a full-desktop grab). **Human-readable filenames (v1.9.113):** `<YYYY-MM-DDThh-mm-ss.mmm>[-<hwnd>].png` — a sortable ISO stamp (lexicographic == chronological), plus the `hwnd` for a window capture; the `window`/`screen` kind is NOT repeated in the name because the folder already says it. **One file when small (v1.9.114):** if the shot was already within the safe size cap (the common case) it's a single `<stem>.png` and `localFullPath == localSafePath` — no redundant re-encoded copy. Only a shot large enough to be downscaled produces a `<stem>.full.png` + `<stem>.safe.png` pair (Read the `.safe` one for image analysis; use `.full` for archive). Each shot gets a **`<stem>.json` sidecar** (shotlog-style: `source`, `timestamp`, `kind`, dims + `bytes`, `coordMap`, `rect`; a full+safe pair shares one). **Parent/child grouping (v1.9.111/113):** when `desktop_screenshot_window` returns owned popups in `screenshots[]`, the parent AND each pulled child share the same stem, and each child gets a **`.popup-NN` label** — e.g. parent `2026-07-15T01-14-16.937-986710.safe.png` and children `2026-07-15T01-14-16.937-986710.popup-01.safe.png`, `...popup-02...`. So a plain `ls` shows the whole capture grouped (shared stem) and the role (`.popup-NN`) without opening a sidecar; the sidecar also carries `parent` + `popupIndex`. (`kicad_screenshot_all` returns sibling windows with no parent, so those keep their own names.) A janitor prunes it (7 days / 2 GB / newest kept; sidecars follow their PNG, orphans swept) and drains the old `/tmp/ad-shots` + `/tmp/adom-bridge-screenshots` backlogs, running opportunistically at most once/24h on any CLI call. **You do not need to clean it up or write your own cron.** Every screenshot response carries a `_cacheHint`; verify coverage with **`adom-bridge janitor`** (status: each subfolder, size, policy, last sweep) or force a sweep with **`adom-bridge janitor '{"sub":"run"}'`**. It never touches other tools' files under `~/project/screenshots/` (e.g. shotlog). Tune via `ADOM_SHOTS_MAX_AGE_DAYS` / `ADOM_SHOTS_MAX_GB` / `ADOM_SHOTS_MAX_FILES`, or disable with `ADOM_JANITOR=0`.
 
-**Background vs foreground driving (v1.8.178):** every desktop driving verb carries a structured **`drive`** field (`"background"` | `"foreground"` | `"n/a"`) — in `GET /commands`, in `adom-bridge-cli help <verb>`, and echoed in the verb's own response. **Background** (no foreground / focus steal / cursor move — UIA + capture): `desktop_ui_click`, `desktop_ui_set`, `desktop_navigate`, `desktop_find_control`, `desktop_screenshot_window/_screen`, `desktop_record_window_start`. **Foreground** (SendInput → goes to the focused window, so ab foregrounds first): `desktop_click`/`_double_click`/`_right_click`/`_hover`, `desktop_type`, `desktop_press_key`, `desktop_bring_to_front`. Run **`adom-bridge-cli help background`** for the full grouping. Rule: to act without disturbing the user's window, drive by NAME (`desktop_find_control` → `desktop_ui_click`/`ui_set`); fall back to coordinate clicks/keys only when the control isn't in the UIA tree.
+**Background vs foreground driving (v1.8.178):** every desktop driving verb carries a structured **`drive`** field (`"background"` | `"foreground"` | `"n/a"`) — in `GET /commands`, in `adom-bridge help <verb>`, and echoed in the verb's own response. **Background** (no foreground / focus steal / cursor move — UIA + capture): `desktop_ui_click`, `desktop_ui_set`, `desktop_navigate`, `desktop_find_control`, `desktop_screenshot_window/_screen`, `desktop_record_window_start`. **Foreground** (SendInput → goes to the focused window, so ab foregrounds first): `desktop_click`/`_double_click`/`_right_click`/`_hover`, `desktop_type`, `desktop_press_key`, `desktop_bring_to_front`. Run **`adom-bridge help background`** for the full grouping. Rule: to act without disturbing the user's window, drive by NAME (`desktop_find_control` → `desktop_ui_click`/`ui_set`); fall back to coordinate clicks/keys only when the control isn't in the UIA tree.
 
 **Click a control by name → coords:** `desktop_find_control` returns `best.rect{x,y,w,h,centerX,centerY}` in screen px — `desktop_click {x:centerX, y:centerY}` to SendInput-click it (handy when `invokable`/`settable` are both false). Target by `name`, `contains`, `role`, or `automationId` (stable, locale-independent).
 
 **Keyboard chords:** `desktop_press_key` accepts a chord as one string `["ctrl+w"]` OR separate tokens `["ctrl","w"]` — both mean Ctrl held + W. A *sequence* of distinct chords uses `+` per element: `["ctrl+l","enter"]`. The reply echoes `chords` showing how ab interpreted your `keys`.
 
 - `desktop_flash_window` (v1.8.152+) -- Flash an **arbitrary** window's taskbar button orange (Win32 `FlashWindowEx`) as an "attention / ongoing activity" cue. Resolve the HWND with `desktop_list_windows` first. Args: `hwnd` (int) OR `titleContains` (string); `mode` (`"until_focused"` default — flashes until the user clicks the window; or `"count"`); `count` (int, `mode:"count"` only, default 3); `stop` (bool — clear an active flash). Returns `{ok, hwnd, title, mode}`. **Lightweight by design** (direct FlashWindowEx, no process spawn) so a bridge can call it after every verb. Distinct from the puppeteer bridge's `pup_alert_window`, which only flashes pup's OWN tracked windows — this flashes any window on the host. For progress + overlay-icon status, use `desktop_taskbar`.
-  - Example: `adom-bridge-cli desktop_flash_window '{"hwnd":133560}'` then `adom-bridge-cli desktop_flash_window '{"hwnd":133560,"stop":true}'` to clear.
+  - Example: `adom-bridge desktop_flash_window '{"hwnd":133560}'` then `adom-bridge desktop_flash_window '{"hwnd":133560,"stop":true}'` to clear.
 - `desktop_taskbar` (v1.8.152+) -- Paint **glanceable status** on any window's taskbar button via `ITaskbarList3` (progress bar + overlay icon) + `FlashWindowEx`, so a bridge can show what the AI is doing on the **real app window it drives** (e.g. native-browser putting an "Adom is driving" badge + task progress on the Chrome/Edge button). Works on windows owned by **other processes**. Persistent COM (init once, reuse) — safe to tick progress frequently; no process spawn. Resolve the HWND via `desktop_list_windows`. Args (all channels optional; provide ≥1):
   - `hwnd` (int) OR `titleContains` (string)
   - `flash`: `"until_focused"` | `"stop"`
@@ -1351,9 +1353,9 @@ Two ways to use it:   - Returns `{ok, hwnd, title, applied:[...]}`. Clears: `flash:"stop"`, `progress.state:"none"`, `overlay.badge:"none"`, `thumbnailTooltip:""`, `thumbnailClip:null` — call these on task-end / window-close to clean up.
   - Example (long task, human gate, then done):
     ```bash
-    adom-bridge-cli desktop_taskbar '{"hwnd":133560,"overlay":{"badge":"adom","tooltip":"Adom is driving this browser"},"progress":{"state":"indeterminate"}}'
-    adom-bridge-cli desktop_taskbar '{"hwnd":133560,"progress":{"state":"paused"},"flash":"until_focused"}'   # captcha — come look
-    adom-bridge-cli desktop_taskbar '{"hwnd":133560,"progress":{"state":"none"},"overlay":{"badge":"none"},"flash":"stop"}'  # done — clear all
+    adom-bridge desktop_taskbar '{"hwnd":133560,"overlay":{"badge":"adom","tooltip":"Adom is driving this browser"},"progress":{"state":"indeterminate"}}'
+    adom-bridge desktop_taskbar '{"hwnd":133560,"progress":{"state":"paused"},"flash":"until_focused"}'   # captcha — come look
+    adom-bridge desktop_taskbar '{"hwnd":133560,"progress":{"state":"none"},"overlay":{"badge":"none"},"flash":"stop"}'  # done — clear all
     ```
 - `desktop_set_window_identity` + `desktop_register_app_identity` (v1.9.134+) -- **Full window-identity takeover.** Make a window you drive present itself as YOUR app: the taskbar button, Alt-Tab entry, hover tooltip and any user pin read your name and icon instead of the process that happens to own the HWND. Built for a bridge driving a borrowed host app (a Chrome for Testing window otherwise reads "Test" with Chrome's icon, which is two brands and a confusing word). ab is generic here — it applies whatever `appId` / `iconPath` / `displayName` you pass and knows nothing about which app that is.
 
@@ -1363,12 +1365,12 @@ Two ways to use it:     # Once per install. Ship the .ico in your bridge's release zip; pass its cache path.
     # `target`/`args` are what a user's PINNED tile launches — point it at a real verb so
     # the pin opens a managed window instead of an orphan app.
-    adom-bridge-cli desktop_register_app_identity '{"appId":"Adom.Pup","displayName":"Adom Pup",
+    adom-bridge desktop_register_app_identity '{"appId":"Adom.Pup","displayName":"Adom Pup",
       "iconPath":"C:/.../adom-pup.ico",
       "args":"pup_open_window {\"sessionId\":\"pinned\",\"owner\":\"pinned-launch\"}"}'
 
     # Per window, e.g. at park time. cacheKey survives page-title churn.
-    adom-bridge-cli desktop_set_window_identity '{"cacheKey":"<sessionId>",
+    adom-bridge desktop_set_window_identity '{"cacheKey":"<sessionId>",
       "titleContains":"(session: <sessionId>)","appId":"Adom.Pup",
       "displayName":"Adom Pup","iconPath":"C:/.../adom-pup.ico"}'
     ```
@@ -1381,22 +1383,22 @@ Two ways to use it:   - **`desktop_set_window_jumplist`** (v1.9.139+) adds the right-click menu on the taskbar button (per-AUMID; `tasks:[]` clears) and, with `hwnd`+`headerIcon`, brands the header row (the app tile above Pin/Close).
   - **Icon format — read the `iconCheck` these verbs return (v1.9.152).** `desktop_set_window_identity` / `desktop_register_app_identity` / `desktop_set_window_jumplist` return an `iconCheck {kind,sizes,issues,ok,hint}` (or `headerIconCheck`) that magic-sniffs the file and explains why an icon would render as a generic white document: a **256px `.ico` size not PNG-compressed** (the Vista+ trap), **SVG** (the Win32 shell can't load it), a `.ico` with no small size, an `.exe` where an image is wanted. If `iconCheck.ok` is false, fix the file and re-stamp. Ship a multi-size `.ico` (16/24/32/48/256, the 256 PNG-compressed). **Full guide:** the `adom-bridge-window-identity` skill (AUMID model, the register+stamp two-halves rule, jump-list header, taskbar overlay + thumbnail, the live-tile trap, a recipe).
 - `desktop_revoke_approvals` -- Revoke all shell auto-approve permissions and deny pending approvals. The next shell command will show the approval dialog.
-  - Example: `adom-bridge-cli desktop_revoke_approvals`
+  - Example: `adom-bridge desktop_revoke_approvals`
 - `desktop_caption` -- Show a global caption overlay that stays visible above ALL windows (KiCad, Fusion, Chrome). Native Win32 overlay — not a browser DOM element. Click-through: mouse events pass to windows below. Captured by screen recordings (browser `getDisplayMedia`, OBS, Game Bar).
-  - Show: `adom-bridge-cli desktop_caption '{"text":"Step 1: Opening the board","position":"top","size":"large","duration":3000}'`
-  - Custom position: `adom-bridge-cli desktop_caption '{"text":"Look here","x":0.3,"y":0.2,"size":"large","persist":true}'`
-  - Hide: `adom-bridge-cli desktop_caption '{"action":"hide"}'`
-  - List what's on screen (v1.9.217): `adom-bridge-cli desktop_caption '{"action":"list"}'` — returns `{captions:[{id,ageMs,expiresInMs,persist,overdue}]}`; the authoritative check (caption overlays do NOT appear in `desktop_list_windows`). TTLs are fail-safe: a 15s reaper closes any caption that outlives its deadline. Do not keep a caption alive with a same-id refresh loop as a nag — the user experiences one endless caption.
+  - Show: `adom-bridge desktop_caption '{"text":"Step 1: Opening the board","position":"top","size":"large","duration":3000}'`
+  - Custom position: `adom-bridge desktop_caption '{"text":"Look here","x":0.3,"y":0.2,"size":"large","persist":true}'`
+  - Hide: `adom-bridge desktop_caption '{"action":"hide"}'`
+  - List what's on screen (v1.9.217): `adom-bridge desktop_caption '{"action":"list"}'` — returns `{captions:[{id,ageMs,expiresInMs,persist,overdue}]}`; the authoritative check (caption overlays do NOT appear in `desktop_list_windows`). TTLs are fail-safe: a 15s reaper closes any caption that outlives its deadline. Do not keep a caption alive with a same-id refresh loop as a nag — the user experiences one endless caption.
   - Multiple simultaneous captions (use `id` to keep them independent):
     ```
-    adom-bridge-cli desktop_caption '{"text":"Step 1: Opening board","id":"step","position":"center","size":"large","persist":true}'
-    adom-bridge-cli desktop_caption '{"text":"REC ●","id":"status","position":"bottom-left","size":"medium","persist":true}'
+    adom-bridge desktop_caption '{"text":"Step 1: Opening board","id":"step","position":"center","size":"large","persist":true}'
+    adom-bridge desktop_caption '{"text":"REC ●","id":"status","position":"bottom-left","size":"medium","persist":true}'
     ```
   - Replace only the step caption (status stays):
     ```
-    adom-bridge-cli desktop_caption '{"text":"Step 2: Routing","id":"step","position":"center","size":"large","persist":true}'
+    adom-bridge desktop_caption '{"text":"Step 2: Routing","id":"step","position":"center","size":"large","persist":true}'
     ```
-  - Hide just the step caption: `adom-bridge-cli desktop_caption '{"action":"hide","id":"step"}'`
+  - Hide just the step caption: `adom-bridge desktop_caption '{"action":"hide","id":"step"}'`
   - Args:
     - `text` (string) — caption text
     - `id` (string, optional) — identifies this caption. Captions with the same id replace each other; different ids coexist simultaneously. Default `"_default"` — so bare calls without id still replace each other for backward compat.
@@ -1422,9 +1424,9 @@ Take screenshots of the user's desktop or individual windows. All screenshots us 
 - `desktop_list_windows` -- List all visible windows (returns HWND, title, class name, position/size, and `z` = Z-order index, 0 = topmost)
 - `desktop_find_window` (v1.8.153+) -- Resolve a window's HWND by `titleContains` and/or `className` (case-insensitive substrings), returned in Z-order with `best` = the topmost match. Use this instead of diffing `desktop_list_windows` before/after launching something — it's race-free for "the window I just opened." Returns `{ok, count, best:{hwnd,title,className,z,rect}|null, windows[]}` (`ok` true only when ≥1 matched). Pass `best.hwnd` to `desktop_taskbar` / `desktop_flash_window` / `desktop_screenshot_window`.
-  - Example: `adom-bridge-cli desktop_find_window '{"className":"Chrome_WidgetWin_1","titleContains":"GitHub"}'`
+  - Example: `adom-bridge desktop_find_window '{"className":"Chrome_WidgetWin_1","titleContains":"GitHub"}'`
 - `desktop_activity_filter` (v1.8.155+) -- Scope the **user's GUI activity log** so they watch the slice you care about while you work (e.g. focus it on one bridge before a debugging session). Args: `bridge` (a bridge registry name from `bridge_list` | `"none"` = only global/non-bridge ab commands | `"all"`/omit = everything), `text` (free-text substring, ANDs with bridge), `clear` (bool — reset). **Drives the human's GUI view only — it does NOT change which verbs you can call or what they return.** The GUI shows a status bar stating exactly what's shown + what's hidden.
-  - Example: `adom-bridge-cli desktop_activity_filter '{"bridge":"kicad"}'` then `adom-bridge-cli desktop_activity_filter '{"clear":true}'`
+  - Example: `adom-bridge desktop_activity_filter '{"bridge":"kicad"}'` then `adom-bridge desktop_activity_filter '{"clear":true}'`
 - `desktop_screenshot_window` -- Capture a specific window by HWND
 - `desktop_screenshot_screen` -- Capture the entire desktop (all monitors)
 - `desktop_ad_scroll` (v1.9.86+) -- **Scroll ab's OWN window panels.** ab is a fully AI-controllable app, so you can drive its own scrollbars — most usefully to page the **bridges sidebar** and screenshot cards that are below the fold. Args: `panel` (`bridges` default | `activity` | `connections`), `to` (`top`|`bottom`, absolute — wins over `dy`), `dy` (relative px, default 400; negative = up). Returns `{ok, panel, scrolled, state:{scrollTop,scrollHeight,clientHeight,atTop,atBottom,found}|null, _hint}` — round-tripped from the live window so you can **loop until `state.atBottom==true`**. A collapsed bridges sidebar is auto-expanded first; `state==null` means the panel didn't report (collapsed/empty).
@@ -1455,8 +1457,8 @@ When a HUMAN opens ab on a machine where Hydrogen is detected (embedded marker, ab's Settings-panel preferences are readable and settable over the API. This is the sanctioned channel for an embedding app (Hydrogen) to render ab's settings in its own panel and forward the user's changes; it also lets a fleet operator read or fix a box's boot/update prefs remotely.
 
 ```bash
-adom-bridge-cli settings_get
-adom-bridge-cli settings_set '{"settings": {"launch_on_boot": true, "update_mode": "auto"}}'
+adom-bridge settings_get
+adom-bridge settings_set '{"settings": {"launch_on_boot": true, "update_mode": "auto"}}'
 ```
 
 - Settable keys (whitelist): `launch_on_boot`, `always_show_window_on_launch`, `update_mode` (auto|prompt|off), `auto_restart_for_updates`, `update_idle_minutes` (1..1440), `prewarm_runtimes`, `prewarm_cft` (auto|always|never), `file_receive_directory`. Anything else lands in `rejected` — servers, shell approval (grants are human-only), and blocked peers have their own surfaces and are deliberately not settable here.
@@ -1466,16 +1468,16 @@ adom-bridge-cli settings_set '{"settings": {"launch_on_boot": true, "update_mode 
 ### Browser automation (`pup_*`, Puppeteer/pup) — bridge-owned; light pointer
 
-The pup bridge is **cloud-owned** and ships its OWN full skill (its wiki page / skill pkg — `bridge_list` names it; `adom-bridge-cli sync_skills` installs it). This page keeps only the pointer plus first-touch examples; sessions/profiles, credential vault, downloads, recording, CDP details are the bridge's skill.
+The pup bridge is **cloud-owned** and ships its OWN full skill (its wiki page / skill pkg — `bridge_list` names it; `adom-bridge sync_skills` installs it). This page keeps only the pointer plus first-touch examples; sessions/profiles, credential vault, downloads, recording, CDP details are the bridge's skill.
 
 Most-used verbs:
 
 ```bash
-adom-bridge-cli pup_open_window '{"sessionId": "s1", "profile": "p1", "url": "https://example.com"}'
-adom-bridge-cli pup_navigate '{"sessionId": "s1", "url": "https://example.com/page2"}'
-adom-bridge-cli pup_screenshot '{"sessionId": "s1"}'
-adom-bridge-cli pup_eval '{"sessionId": "s1", "expression": "document.title"}'
-adom-bridge-cli pup_fetch_url '{"url": "https://example.com/data.json"}'
+adom-bridge pup_open_window '{"sessionId": "s1", "profile": "p1", "url": "https://example.com"}'
+adom-bridge pup_navigate '{"sessionId": "s1", "url": "https://example.com/page2"}'
+adom-bridge pup_screenshot '{"sessionId": "s1"}'
+adom-bridge pup_eval '{"sessionId": "s1", "expression": "document.title"}'
+adom-bridge pup_fetch_url '{"url": "https://example.com/data.json"}'
 ```
 
 `pup_readiness` is the read-only probe (never spawns or downloads). Full details: the bridge's own skill via `sync_skills`.
@@ -1485,12 +1487,12 @@ adom-bridge-cli pup_fetch_url '{"url": "https://example.com/data.json"}' ab owns DESKTOP recording: a visible HUD opens with the REASON you supply, so the user always sees that recording is on and why. Full guide: the `adom-bridge-recording` skill.
 
 ```bash
-adom-bridge-cli desktop_recorder_open '{"reason": "record the DRC walkthrough for review"}'
-adom-bridge-cli desktop_record_start '{"confirmDesktopNotTabRecording": true}'
+adom-bridge desktop_recorder_open '{"reason": "record the DRC walkthrough for review"}'
+adom-bridge desktop_record_start '{"confirmDesktopNotTabRecording": true}'
 # ... drive the desktop ...
-adom-bridge-cli desktop_record_stop '{}'
-adom-bridge-cli desktop_record_list '{}'
-adom-bridge-cli desktop_recorder_close '{}'
+adom-bridge desktop_record_stop '{}'
+adom-bridge desktop_record_list '{}'
+adom-bridge desktop_recorder_close '{}'
 ```
 
 `desktop_record_start` without `confirmDesktopNotTabRecording: true` refuses with `errorCode: desktop_record_needs_confirmation` — that guard exists because recording a BROWSER TAB is the pup bridge's separate feature (`pup_record_start` etc., see pup's own skill).
@@ -1515,7 +1517,7 @@ becomes one call: 
 ```bash
 # NEW — single primitive, no shell:
-adom-bridge-cli desktop_watch_files '{
+adom-bridge desktop_watch_files '{
   "path": "%USERPROFILE%\\Downloads",
   "glob": "ul_*.zip",
   "timeoutMs": 60000
@@ -1548,7 +1550,7 @@ Composes `desktop_list_files` (or `desktop_watch_files` if `wait:true`) with the ```bash
 BEFORE=$(date +%s)
 # … trigger the download click via pup_input_dispatch …
-adom-bridge-cli desktop_pull_glob "{
+adom-bridge desktop_pull_glob "{
   \"path\": \"%USERPROFILE%\\\\Downloads\",
   \"glob\": \"ul_*.zip\",
   \"since\": $BEFORE,
@@ -1596,9 +1598,9 @@ When the user has **Hydrogen** running (a sibling Tauri v2 app at `C:\Github\hyd #### The canonical generic verb — `hd_api` (v1.8.72+)
 
 ```bash
-adom-bridge-cli hd_api '{"method":"GET","path":"/health"}'
-adom-bridge-cli hd_api '{"method":"POST","path":"/setup/run-step","body":{"id":"welcome"}}'
-adom-bridge-cli hd_api '{"method":"GET","path":"/wsl/status"}'
+adom-bridge hd_api '{"method":"GET","path":"/health"}'
+adom-bridge hd_api '{"method":"POST","path":"/setup/run-step","body":{"id":"welcome"}}'
+adom-bridge hd_api '{"method":"GET","path":"/wsl/status"}'
 ```
 
 Generic HTTP pass-through to any Hydrogen endpoint. Raw JSON in `body` — no curl-through-the-relay quote mangling. Returns `{success, status, body, port, method, path}` mirroring Hydrogen's response. If Hydrogen isn't running, returns `{ok:false, error:"Hydrogen not reachable on :47084", _hint:"..."}` instead of timing out.
@@ -1642,7 +1644,7 @@ Sequence inside one call: ```bash
 # End-to-end after a push:
 git push && SHA=$(git rev-parse HEAD) && \
-  adom-bridge-cli hd_ship "{\"expectSha\":\"$SHA\"}"
+  adom-bridge hd_ship "{\"expectSha\":\"$SHA\"}"
 ```
 
 Args:
@@ -1673,19 +1675,19 @@ The relay's `shell_execute` has a 30s timeout; Hydrogen's `pnpm build` + `cargo 
 ```bash
 # Frontend-only rebuild, watch progress, relaunch (no hd_ship since hd_ship is Rust-build-only)
-adom-bridge-cli hd_stop
-adom-bridge-cli hd_build_frontend '{"show":false}'      # returns {ok, pid, logPath} immediately
+adom-bridge hd_stop
+adom-bridge hd_build_frontend '{"show":false}'      # returns {ok, pid, logPath} immediately
 OFFSET=0
 while true; do
-  RESP=$(adom-bridge-cli hd_build_tail "{\"offset\":$OFFSET}")
+  RESP=$(adom-bridge hd_build_tail "{\"offset\":$OFFSET}")
   echo "$RESP" | jq -r '.lines[]'
   DONE=$(echo "$RESP" | jq -r '.done')
   OFFSET=$(echo "$RESP" | jq -r '.newOffset')
   [[ "$DONE" == "true" ]] && break
   sleep 3
 done
-adom-bridge-cli hd_build_status      # confirm succeeded:true && relinkOnly:false
-adom-bridge-cli hd_launch            # start the new debug binary
+adom-bridge hd_build_status      # confirm succeeded:true && relinkOnly:false
+adom-bridge hd_launch            # start the new debug binary
 ```
 
 ##### `hd_build` / `hd_build_frontend` / `hd_build_rust` — async-spawn build verbs
@@ -1800,14 +1802,14 @@ Recipe — verify-which-build-is-running + confirm-config-change-took-effect + f 
 ```bash
 # After hd_ship, confirm the titlebar shows the SHA you expect:
-adom-bridge-cli hd_shot '{"region":"titlebar"}'
+adom-bridge hd_shot '{"region":"titlebar"}'
 # → runningBuildSha lets you assert this before trusting any other shot
 
 # After flipping a Setup Steps toggle, screenshot the panel to confirm:
-adom-bridge-cli hd_shot '{"region":"setup-panel","resizeMax":1568}'
+adom-bridge hd_shot '{"region":"setup-panel","resizeMax":1568}'
 
 # Full-window dump for general debugging:
-adom-bridge-cli hd_shot '{"region":"full"}'
+adom-bridge hd_shot '{"region":"full"}'
 ```
 
 Edge cases:
@@ -1827,21 +1829,21 @@ Reach a TCP service addressable only from the **cloud side** through the **lapto 
 ```bash
 # Open a VERIFIED tunnel (both legs probed before ok). Auto-picks a free local port.
-adom-bridge-cli --target <name> tunnel open '{"to":"127.0.0.1:13389"}'
+adom-bridge --target <name> tunnel open '{"to":"127.0.0.1:13389"}'
 
 # RDP: open + verify + launch mstsc, sized to the laptop's real screen.
-adom-bridge-cli --target <name> tunnel rdp '{"to":"127.0.0.1:13389","size":"90%"}'
+adom-bridge --target <name> tunnel rdp '{"to":"127.0.0.1:13389","size":"90%"}'
 #   size: "90%" (default) | "100%" | "fullscreen" | "maximized"
 #   OR monitor:<index> (0=primary)  OR explicit width+height
 
-adom-bridge-cli --target <name> tunnel list           # live tunnels + leak detection
-adom-bridge-cli --target <name> tunnel check '{"tunnelId":<id>}'   # per-leg health
-adom-bridge-cli --target <name> tunnel close '{"tunnelId":<id>}'   # or {"all":true}
+adom-bridge --target <name> tunnel list           # live tunnels + leak detection
+adom-bridge --target <name> tunnel check '{"tunnelId":<id>}'   # per-leg health
+adom-bridge --target <name> tunnel close '{"tunnelId":<id>}'   # or {"all":true}
 
-adom-bridge-cli --target <name> display               # monitor geometry (for sizing)
+adom-bridge --target <name> display               # monitor geometry (for sizing)
 ```
 
-**`--target` is mandatory when >1 ab is connected.** v1.8.147 fixed a false-success bug: the bind, the upstream probe, and the mstsc launch now ALL resolve to the SAME machine. (Before, the bind could land on one ab and mstsc launch on another → "port bound" on the VM, "nothing listening" on the laptop, a bogus `ok:true`.) No target + multiple ABs → `ambiguous_target` listing the names; `--target all` is rejected (a tunnel binds one machine). `adom-bridge-cli targets` lists connected ABs.
+**`--target` is mandatory when >1 ab is connected.** v1.8.147 fixed a false-success bug: the bind, the upstream probe, and the mstsc launch now ALL resolve to the SAME machine. (Before, the bind could land on one ab and mstsc launch on another → "port bound" on the VM, "nothing listening" on the laptop, a bogus `ok:true`.) No target + multiple ABs → `ambiguous_target` listing the names; `--target all` is rejected (a tunnel binds one machine). `adom-bridge targets` lists connected ABs.
 
 **Active health verification — every `open`/`rdp`/`check` PROVES the path before reporting `ok`:**
 - **local-bind** — ab self-probes `127.0.0.1:<boundPort>` right after binding. If it can't connect, ab tears the tunnel down and returns `ok:false` (this alone kills the false-success class).
@@ -1885,7 +1887,7 @@ Both verbs require shell auto-approve to be active — same trust level as `shel |---|---|---|
 | `ok` | completed (stdout may legitimately be empty) | use `exitCode`/`stdout`/`stderr` |
 | `timeout` | did NOT finish in time (NOT an empty success) | raise `timeoutSeconds`, or use a detached job (below) |
-| `busy` | desktop couldn't service it (WSL wedged / no client connected) | check `adom-bridge-cli status`; `wsl_recover` for WSL; retry |
+| `busy` | desktop couldn't service it (WSL wedged / no client connected) | check `adom-bridge status`; `wsl_recover` for WSL; retry |
 | `error` | the command/verb failed | read `error` / `stderr` / `_hint` |
 
 `durationMs` is the wall-clock of the call. `exitCode`/`stdout`/`stderr` pass through unchanged; on `timeout` the verb's `partialStdout`/`partialStderr` hold what was written before the kill. A `timeout`/`busy` result also carries a recovery `_hint`. **For multi-minute work don't fight the timeout** — use a detached job that returns instantly + polls: `wsl_exec_async` / `wsl_import` / `wsl_export` → poll `wsl_job_status` (or `launch_process` for a general long-running command). This is the cure for the "1.4 GB import EOFs the sync call" problem.
@@ -1899,7 +1901,7 @@ VAR=\"value with spaces and \$dollars\" echo \"VAR=\$VAR\"
 exit 0
 ''').decode())")
-adom-bridge-cli run_script "{\"interpreter\":\"bash\",\"scriptB64\":\"$SCRIPT_B64\"}"
+adom-bridge run_script "{\"interpreter\":\"bash\",\"scriptB64\":\"$SCRIPT_B64\"}"
 # → {success, ok, stdout, stderr, exitCode}
 ```
 
@@ -1917,7 +1919,7 @@ cd /mnt/c/Github/adom-bridge git status --short | head -10
 exit 0
 ''').decode())")
-adom-bridge-cli wsl_exec "{\"distro\":\"Ubuntu\",\"user\":\"root\",\"scriptB64\":\"$SCRIPT_B64\"}"
+adom-bridge wsl_exec "{\"distro\":\"Ubuntu\",\"user\":\"root\",\"scriptB64\":\"$SCRIPT_B64\"}"
 # → {success, ok, stdout, stderr, exitCode}
 ```
 
@@ -1941,12 +1943,12 @@ Every `wsl_exec` / `run_script bash` spawns a `wsl.exe` client. If WSL's service 1. **Refuse-cap.** When there are already too many `wsl.exe` (`WSL_REFUSE_AT=80`), ab refuses NEW wsl spawns with `errorCode:"wsl_wedged"` instead of adding another doomed client. If you get `wsl_wedged`, don't retry the same command — run `wsl_recover` first.
 2. **`wsl_recover`** — reap orphaned/hung `wsl.exe`, **DRY-RUN by default** (nothing is killed unless you pass `confirm:true`):
    ```bash
-   adom-bridge-cli wsl_recover                       # dry-run: lists `candidates` (would-reap) + `spared`
-   adom-bridge-cli wsl_recover '{"confirm":true}'    # reap the candidates
-   adom-bridge-cli wsl_recover '{"confirm":true,"sparePids":[1234]}'   # also protect pid 1234
+   adom-bridge wsl_recover                       # dry-run: lists `candidates` (would-reap) + `spared`
+   adom-bridge wsl_recover '{"confirm":true}'    # reap the candidates
+   adom-bridge wsl_recover '{"confirm":true,"sparePids":[1234]}'   # also protect pid 1234
    ```
    It classifies each `wsl.exe`: childless + (0-CPU & old | runaway-CPU) → **candidate**; everything else → **spared**. It **spares any `wsl.exe` with child processes** (that's almost certainly the live distro, e.g. the Hydrogen `Adom-Workspace`), honors `sparePids`, and never touches `wslservice`/`wslhost`/`vmmem`. Review each candidate's `cmd` in the dry-run to confirm the live workspace isn't among them, then confirm. **Never** `wsl --shutdown` (it kills the live workspace too). Reaping childless orphans often cascades — their now-parentless children exit on their own (observed 114 → 18 from reaping 54).
-3. **status wsl block** — `{wslProcessCount, wedged, _hint}` lets you see a pile-up *before* it blocks work. Path differs by transport: **`status.desktop.wsl`** via the CLI/relay (`adom-bridge-cli status` — nested like `desktop.apps`), or top-level **`status.wsl`** on the local direct API (`GET /status`). `wedged:true` at/above `WSL_WARN_AT=50`.
+3. **status wsl block** — `{wslProcessCount, wedged, _hint}` lets you see a pile-up *before* it blocks work. Path differs by transport: **`status.desktop.wsl`** via the CLI/relay (`adom-bridge status` — nested like `desktop.apps`), or top-level **`status.wsl`** on the local direct API (`GET /status`). `wedged:true` at/above `WSL_WARN_AT=50`.
 
 #### Long-running in-distro work + distro lifecycle — golden-image bakes (v1.8.161+)
 
@@ -1957,14 +1959,14 @@ Every `wsl_exec` / `run_script bash` spawns a `wsl.exe` client. If WSL's service 
 ```bash
 # golden-image bake: import → run (async) → poll → export → unregister, all via ab
-adom-bridge-cli wsl_import '{"distro":"bake","installDir":"C:/Users/<you>/.adom/wsl/bake","tarball":"C:/.../base.tar"}'
+adom-bridge wsl_import '{"distro":"bake","installDir":"C:/Users/<you>/.adom/wsl/bake","tarball":"C:/.../base.tar"}'
 #   → {jobId}; poll wsl_job_status until done (exitCode 0)
 B64=$(printf 'set -euo pipefail\napt-get update && apt-get install -y build-essential\n' | base64 -w0)
-adom-bridge-cli wsl_exec_async "{\"distro\":\"bake\",\"user\":\"root\",\"scriptB64\":\"$B64\"}"
-adom-bridge-cli wsl_job_status '{"jobId":"wsljob-xxxxxxxx"}'   # repeat until running:false; outputTail = progress
-adom-bridge-cli wsl_export '{"distro":"bake","tarball":"C:/.../golden.tar"}'   # poll, then pull_file the tarball
-adom-bridge-cli wsl_unregister '{"distro":"bake"}'            # cleanup (REFUSES Adom-Workspace)
-adom-bridge-cli wsl_list                                      # {distros:[{name,state,version,default}]}
+adom-bridge wsl_exec_async "{\"distro\":\"bake\",\"user\":\"root\",\"scriptB64\":\"$B64\"}"
+adom-bridge wsl_job_status '{"jobId":"wsljob-xxxxxxxx"}'   # repeat until running:false; outputTail = progress
+adom-bridge wsl_export '{"distro":"bake","tarball":"C:/.../golden.tar"}'   # poll, then pull_file the tarball
+adom-bridge wsl_unregister '{"distro":"bake"}'            # cleanup (REFUSES Adom-Workspace)
+adom-bridge wsl_list                                      # {distros:[{name,state,version,default}]}
 ```
 
 **systemd user session — `viaRoot`.** On a `systemd=true` distro, `-u <user>` can fail "Failed to start the systemd user session for '<user>'". Pass `"viaRoot": true` to `wsl_exec_async` — ab runs `-u root -- runuser -u <user> -- bash -l`, dropping to the user WITHOUT depending on `[email protected]`.
@@ -1973,10 +1975,10 @@ adom-bridge-cli wsl_list                                      # {distros:[{name, 
 ## Detecting App Installation
 
-After connecting, always run `adom-bridge-cli status` to check what's installed. The `desktop.apps` object tells you exactly what the user has:
+After connecting, always run `adom-bridge status` to check what's installed. The `desktop.apps` object tells you exactly what the user has:
 
 ```bash
-adom-bridge-cli status
+adom-bridge status
 # Look at the desktop.apps field in the response
 ```
 
@@ -1990,7 +1992,7 @@ When you need to open a URL in the user's NATIVE browser AND it matters which ac 
 ```bash
 # 1. Discover what's installed + which profiles are configured.
-adom-bridge-cli desktop_list_browsers '{}'
+adom-bridge desktop_list_browsers '{}'
 # → {
 #     "default": "chrome",
 #     "browsers": [
@@ -2008,13 +2010,13 @@ adom-bridge-cli desktop_list_browsers '{}' 
 # 2. Match the URL's context to the right profile, then open it there.
 # E.g. opening a Google Doc shared by your work team → use the work profile:
-adom-bridge-cli desktop_open_url '{
+adom-bridge desktop_open_url '{
   "url":"https://docs.google.com/document/d/...",
   "browser":"chrome",
   "profile":"Default"
 }'
 # Or YouTube channel management for the media account:
-adom-bridge-cli desktop_open_url '{
+adom-bridge desktop_open_url '{
   "url":"https://studio.youtube.com",
   "browser":"chrome",
   "profile":"Profile 2"
@@ -2035,7 +2037,7 @@ Profile-picking heuristics for Docker Claude: So pup can run an **"adom-you" Chrome profile signed in AS the user** — surfing the web on the user's behalf — it needs the user's real saved logins/cookies to seed that profile. Chrome 127+ encrypts these with **App-Bound Encryption** (the master key is DPAPI-wrapped under the SYSTEM account), so only an elevated helper can decrypt them. This verb is that path.
 
 ```bash
-adom-bridge-cli desktop_decrypt_browser_credentials '{
+adom-bridge desktop_decrypt_browser_credentials '{
   "browsers": ["chrome", "edge"],   # optional; default = all installed Chromium browsers
   "include": ["passwords", "cookies"] # optional; default = both
 }'
@@ -2058,7 +2060,7 @@ adom-bridge-cli desktop_decrypt_browser_credentials '{ v1.7.0+ — any `pup_*` command returning `errorCode:"node_not_found"` means Node isn't on the desktop machine. Trigger the unattended winget install:
 
 ```bash
-adom-bridge-cli desktop_install_node '{}'
+adom-bridge desktop_install_node '{}'
 # → runs `winget install --id OpenJS.NodeJS.LTS --silent ...` (Windows only).
 # → ~1-2 min; downloads + installs the Node.js LTS line (well-tested with puppeteer).
 # → returns {ok:true, exitCode:0, _hint:"Retry the original pup_* command"} on success.
@@ -2087,7 +2089,7 @@ If detection still misses an install on a user's machine, get the actual `node.e v1.6.0+ — try the unattended winget install FIRST before asking the user to download manually:
 
 ```bash
-adom-bridge-cli desktop_install_kicad '{}'
+adom-bridge desktop_install_kicad '{}'
 # → runs `winget install --id KiCad.KiCad --silent ...` (Windows only).
 # → 2-5 minutes; downloads ~700 MB then installs.
 # → returns {ok:true, exitCode:0, _hint:"Run kicad_list_versions to verify"} on success.
@@ -2119,13 +2121,13 @@ If detection still misses an install on a user's machine, that's a real bug to f > It's free for personal/hobby use (requires an Autodesk account).
 > Let me know when the install is done and I'll verify the connection.
 
-After install, have them restart Adom Bridge, then run `adom-bridge-cli status` to verify.
+After install, have them restart Adom Bridge, then run `adom-bridge status` to verify.
 
 ### Handling "not running" errors
 
 When `errorCode: "fusion_not_running"`, launch it programmatically with the first-class startup command:
 ```bash
-adom-bridge-cli fusion_start
+adom-bridge fusion_start
 # On Windows: discovers exe via %LOCALAPPDATA% env var webdeploy glob.
 # On Docker: delegates to the bridge on the connected desktop via relay.
 # ~15-30s typical. Auto-dismisses startup dialogs.
@@ -2155,7 +2157,7 @@ When `errorCode: "main_thread_busy"`, the Fusion add-in's main thread is occupie - Force-kill Fusion — the walk will complete on its own
 
 **Do:**
-- Wait for the walk/search to finish. If you started it, you should be using `adom-bridge-cli watch` (see "Live folder progress streaming with `watch`" above) which streams live progress automatically. If another session started it, poll `fusion_addin_status` every 2–5s to check progress.
+- Wait for the walk/search to finish. If you started it, you should be using `adom-bridge watch` (see "Live folder progress streaming with `watch`" above) which streams live progress automatically. If another session started it, poll `fusion_addin_status` every 2–5s to check progress.
 - Use commands that **don't need the main thread** while waiting:
   - `fusion_addin_status` — check busy state and walk progress
   - `fusion_window_info` — get window HWND, title, dialogs
@@ -2211,7 +2213,7 @@ Use `kick_all` to reset -- active Adom Bridge apps reconnect within seconds. 
 ```bash
 curl -sf http://127.0.0.1:8766/health
-# If fails: adom-bridge-cli serve --supervise
+# If fails: adom-bridge serve --supervise
 ```
 
 ### Shell commands on Windows
@@ -2234,7 +2236,7 @@ cd cli && cargo build --release The ab installer bundles the `adom-wiki` CLI next to the app, and `wiki_exec` runs it ON THE PC — so version-controlling a local project (KiCad board, docs folder) on wiki.adom.inc needs NO per-file `pull_file` round trip to the container and there is no container/PC drift.
 
 ```bash
-adom-bridge-cli wiki_exec '{"args":["repo","push","adom/my-board","--files","board.kicad_pcb","board.kicad_sch","-m","rev B"],"cwd":"C:\Users\me\boards\my-board"}'
+adom-bridge wiki_exec '{"args":["repo","push","adom/my-board","--files","board.kicad_pcb","board.kicad_sch","-m","rev B"],"cwd":"C:\Users\me\boards\my-board"}'
 ```
 
 - `args` = adom-wiki argv (any pillar: repo, pkg, release, discussion). `cwd` = PC-side project dir so `--files` are project-relative. `timeoutSec` optional (default 120, max 600).
dist/linux/adom-bridge
⋯ 1 unchanged line ⋯
package.json+1−1
@@ -1,6 +1,6 @@ {
   "slug": "adom-bridge",
-  "version": "2.0.35",
+  "version": "2.0.37",
   "type": "app",
   "brief": "Adom Bridge unleashes your AI onto your full PC: full power, full safety. The AI breaks out of the container it runs in and onto your real machine, managing and running your entire laptop as you: launch apps, move files, drive any window, control KiCad, Fusion 360 or your real signed-in browser. Works with any AI, cloud or local (Claude Desktop, Claude Code, Codex), no Hydrogen required. Bridge and its bridges are an operating system for AI, with managed Node and Python runtimes, and a human-only permission system keeps you in charge of every side-effecting command.",
   "description": "Install this package in a cloud or Hydrogen Desktop container, not on the PC. It delivers two things: the Linux adom-bridge-cli (linked onto PATH) and the Claude skill set that teaches an AI every desktop verb (shell, files, screenshots, windows, notifications, KiCad, Fusion 360, browser automation). The CLI drives the Adom Bridge app running on your Windows PC over the relay; the Windows app itself is the separate installer in the Download box, not part of this package. Run adom-wiki pkg update to keep the CLI and skills current.",
page.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "adom-bridge",-  "version": "2.0.35",+  "version": "2.0.37",   "type": "app",   "description": "Install this package in a cloud or Hydrogen Desktop container, not on the PC. It delivers two things: the Linux adom-bridge-cli (linked onto PATH) and the Claude skill set that teaches an AI every desktop verb (shell, files, screenshots, windows, notifications, KiCad, Fusion 360, browser automation). The CLI drives the Adom Bridge app running on your Windows PC over the relay; the Windows app itself is the separate installer in the Download box, not part of this package. Run adom-wiki pkg update to keep the CLI and skills current.",   "keywords": [
readme.html+236−43
@@ -297,24 +297,53 @@ curl -s -X POST 127.0.0.1:24680/command \ </li> </ul> <h3>Try it live</h3>-<div id="abwc" style="font-family:system-ui,Segoe UI,sans-serif;border:1px solid #30363d;border-radius:10px;background:#0d1117;color:#e6edf3;padding:18px;max-width:860px">+<div id="abwc" style="font-family:system-ui,Segoe UI,sans-serif;border:1px solid #30363d;border-radius:10px;background:#0d1117;color:#e6edf3;padding:18px;max-width:900px">+  <style>+    #abwc button{ padding:8px 13px;border-radius:6px;border:1px solid #30363d;background:#21262d;color:#e6edf3;cursor:pointer;font-size:13px;+      transition:filter .12s ease, transform .06s ease, box-shadow .15s ease, opacity .15s ease; }+    #abwc button:hover:not(:disabled){ filter:brightness(1.2); border-color:#3d444d; }+    #abwc button:active:not(:disabled){ transform:translateY(1px); }+    #abwc button:disabled{ opacity:.4; cursor:default; }+    #abwc button.abwc-primary{ border-color:#1f6feb;background:#1f6feb;color:#fff; }+    #abwc button.abwc-danger{ border-color:#b1442e;background:#3d1c16;color:#ffb3a3; }+    #abwc button.abwc-running{ opacity:1 !important;box-shadow:0 0 0 2px rgba(0,184,176,.55); }+    #abwc .abwc-row{ display:flex;flex-wrap:wrap;gap:8px;margin-bottom:10px; }+    #abwc .abwc-cap{ font-size:11px;color:#6e7681;margin:8px 0 4px; }+    @keyframes abwc-spin{ to{ transform:rotate(360deg); } }+    .abwc-spinner{ display:inline-block;width:11px;height:11px;margin-right:7px;border:2px solid rgba(255,255,255,.3);+      border-top-color:#00b8b0;border-radius:50%;animation:abwc-spin .7s linear infinite;vertical-align:-1px; }+  </style>+   <div style="display:flex;align-items:center;gap:10px;margin-bottom:6px">     <span id="abwc-dot" style="width:12px;height:12px;border-radius:50%;background:#484f58;display:inline-block"></span>     <strong>Try Web Control (live)</strong>   </div>-  <div style="font-size:12px;color:#8b949e;margin-bottom:12px">This runs against <code>http://127.0.0.1:24680</code> on <em>your</em> machine. If Adom Bridge is running, the buttons below reach it. Nothing changes your machine without a permission prompt you click.</div>+  <div style="font-size:12px;color:#8b949e;margin-bottom:12px">This runs against <code>http://127.0.0.1:24680</code> on <em>your</em> machine. If Adom Bridge is running, the buttons below reach it. Nothing changes your machine without a permission prompt you click. The long ones (launching KiCad, Fusion, a board) run <em>synchronously on Adom Bridge</em> and stream live status back over one connection, then show a screenshot to prove it really happened.</div>++  <div class="abwc-row">+    <button id="abwc-detect">Detect Adom Bridge</button>+    <button id="abwc-status">Read status</button>+  </div> -  <div style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:10px">-    <button id="abwc-detect" style="padding:8px 13px;border-radius:6px;border:1px solid #30363d;background:#21262d;color:#e6edf3;cursor:pointer">Detect Adom Bridge</button>-    <button id="abwc-status" style="padding:8px 13px;border-radius:6px;border:1px solid #30363d;background:#21262d;color:#e6edf3;cursor:pointer">Read status</button>+  <div class="abwc-cap">Quick actions (each asks your permission):</div>+  <div class="abwc-row">+    <button class="abwc-primary" data-act="screenshot">Screenshot my screen</button>+    <button data-act="notify">Notify me (track status)</button>+    <button data-act="windows">List open windows (top 3)</button>+    <button data-act="apps">What apps are installed (top 3)</button>   </div>-  <div style="font-size:11px;color:#6e7681;margin-bottom:4px">Do something (each asks your permission):</div>-  <div style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px">-    <button data-verb="desktop_screenshot_base64" data-args='{"maxWidth":760}' data-label="Screenshot my screen" class="abwc-do" style="padding:8px 13px;border-radius:6px;border:1px solid #1f6feb;background:#1f6feb;color:#fff;cursor:pointer">Screenshot my screen</button>-    <button data-verb="desktop_open_url" data-args='{"url":"https://wiki.adom.inc"}' data-label="Open a URL" class="abwc-do" style="padding:8px 13px;border-radius:6px;border:1px solid #30363d;background:#21262d;color:#e6edf3;cursor:pointer">Open a URL</button>-    <button data-verb="notify_user" data-args='{"title":"Hello from the web","body":"A web page asked Adom Bridge to notify you."}' data-label="Notify me" class="abwc-do" style="padding:8px 13px;border-radius:6px;border:1px solid #30363d;background:#21262d;color:#e6edf3;cursor:pointer">Notify me</button>-    <button data-verb="desktop_list_windows" data-args='{}' data-label="List open windows" class="abwc-do" style="padding:8px 13px;border-radius:6px;border:1px solid #30363d;background:#21262d;color:#e6edf3;cursor:pointer">List open windows</button>-    <button data-verb="kicad_open_symbol_editor" data-args='{}' data-label="Open KiCad" class="abwc-do" style="padding:8px 13px;border-radius:6px;border:1px solid #30363d;background:#21262d;color:#e6edf3;cursor:pointer">Open KiCad</button>++  <div class="abwc-cap">Long actions (run on Adom Bridge, stream live status, then prove it with a screenshot):</div>+  <div class="abwc-row">+    <button data-stream="url">Open a URL, then screenshot it</button>+    <button data-stream="kicad">Open KiCad, then screenshot it</button>+    <button data-stream="board">Open a PCB board, then screenshot it</button>+    <button data-stream="fusion">Open Fusion, then screenshot it</button>+  </div>++  <div class="abwc-cap">When you are done:</div>+  <div class="abwc-row">+    <button class="abwc-danger" data-act="cleanup">Clean up (close KiCad and Fusion)</button>   </div>    <div style="border:1px solid #21262d;border-radius:6px;padding:10px 12px;margin-bottom:12px;font-size:13px">@@ -324,7 +353,7 @@ curl -s -X POST 127.0.0.1:24680/command \     <div id="abwc-mode-note" style="color:#8b949e;margin-top:8px;font-size:12px"></div>   </div> -  <pre id="abwc-log" style="background:#010409;border:1px solid #21262d;border-radius:6px;padding:12px;font-size:12px;max-height:280px;overflow:auto;white-space:pre-wrap;margin:0">Ready. Click "Detect Adom Bridge" to begin.</pre>+  <pre id="abwc-log" style="background:#010409;border:1px solid #21262d;border-radius:6px;padding:12px;font-size:12px;max-height:260px;overflow:auto;white-space:pre-wrap;margin:0">Ready. Click "Detect Adom Bridge" to begin.</pre>   <img id="abwc-img" alt="" style="display:none;margin-top:10px;max-width:100%;border:1px solid #30363d;border-radius:6px" />   <script>   (function () {@@ -332,7 +361,8 @@ curl -s -X POST 127.0.0.1:24680/command \     var log = document.getElementById("abwc-log"), dot = document.getElementById("abwc-dot"),         note = document.getElementById("abwc-mode-note"), img = document.getElementById("abwc-img");     function line(m){ log.textContent += "\n" + m; log.scrollTop = log.scrollHeight; }-    function reset(m){ log.textContent = m; img.style.display = "none"; }+    function reset(m){ log.textContent = m; img.style.display = "none"; img.removeAttribute("src"); }+    function showImg(b64){ img.src = "data:image/png;base64," + b64; img.style.display = "block"; img.scrollIntoView({block:"nearest"}); }     function mode(){ var r = document.querySelector('input[name="abwc-mode"]:checked'); return r ? r.value : "consent"; }     function refreshNote(){       note.textContent = mode() === "handshake"@@ -341,37 +371,200 @@ curl -s -X POST 127.0.0.1:24680/command \     }     Array.prototype.forEach.call(document.querySelectorAll('input[name="abwc-mode"]'), function(el){ el.onchange = refreshNote; });     refreshNote();-    document.getElementById("abwc-detect").onclick = function () {-      reset("GET " + AB + "/health ...");-      fetch(AB + "/health").then(function(r){return r.ok?r.json():Promise.reject("HTTP "+r.status);})-        .then(function(j){ dot.style.background = j.service==="adom-bridge" ? "#00b8b0" : "#e6c86c"; line("RUNNING: " + JSON.stringify(j)); })-        .catch(function(e){ dot.style.background = "#484f58"; line("Not reachable (" + e + "). Is Adom Bridge running on this machine?"); });++    // ---- Button busy-state: disable all, spinner on the clicked one, restore on completion ----+    function allBtns(){ return Array.prototype.slice.call(document.querySelectorAll("#abwc button")); }+    var runningBtn = null, runningOrig = "";+    function setBusy(btn, label){+      runningBtn = btn; runningOrig = btn.innerHTML;+      allBtns().forEach(function(b){ b.disabled = true; });+      btn.classList.add("abwc-running");+      btn.innerHTML = '<span class="abwc-spinner"></span>' + (label || "Working...");+    }+    function clearBusy(){+      allBtns().forEach(function(b){ b.disabled = false; });+      if (runningBtn){ runningBtn.classList.remove("abwc-running"); runningBtn.innerHTML = runningOrig; runningBtn = null; }+    }++    function post(verb, args){+      return fetch(AB + "/command", { method:"POST", headers:{ "Content-Type":"application/json" }, body: JSON.stringify({ command: verb, args: args || {} }) })+        .then(function(r){ return r.json().then(function(j){ return { status:r.status, body:j }; }); });+    }+    function refused(res){+      var b = res.body || {};+      if (b.errorCode === "handshake_required") { line("REFUSED (handshake_required): this page has no secret, so Adom Bridge refused BEFORE the prompt. On the real wiki, wiki.adom.inc's backend supplies it. evil.com is stuck exactly here."); return true; }+      if (b.errorCode === "web_control_disabled") { line("Web Control is turned OFF in Adom Bridge > Settings."); return true; }+      if (b.errorCode === "web_denied") { line("You clicked Deny (or the prompt timed out). Nothing ran."); return true; }+      if (res.status !== 200) { line("Not approved (HTTP " + res.status + "): " + JSON.stringify(b).slice(0,300)); return true; }+      return false;+    }+    function windowsOf(body){+      var out = body && body.output;+      try { if (typeof out === "string") out = JSON.parse(out); } catch(e){}+      return (out && out.data && out.data.windows) || (out && out.windows) || (body && body.windows) || [];+    }++    // ---- Quick (non-streaming) actions — each returns a promise ----+    var quick = {+      screenshot: function(){+        reset('"Screenshot my screen": POST /command {desktop_screenshot_base64} - approve in Adom Bridge ...');+        return post("desktop_screenshot_base64", { maxWidth: 780 }).then(function(res){+          if (refused(res)) return;+          line("APPROVED. Captured " + res.body.width + "x" + res.body.height + "."); showImg(res.body.image);+        });+      },+      notify: function(){+        reset('"Notify me": POST /command {notify_user} - approve in Adom Bridge ...');+        return post("notify_user", { title: "Hello from the web", body: "A web page asked Adom Bridge to notify you.", id: "webdemo-notify" }).then(function(res){+          if (refused(res)) return;+          line("APPROVED. Notification sent (action: " + (res.body.action || "displayed") + "). Tracking status ...");+          return new Promise(function(r){ setTimeout(r, 1200); }).then(function(){+            return post("notify_state", { id: "webdemo-notify" }).then(function(s){+              if (s.status === 200) line("  status: " + JSON.stringify(s.body).slice(0,220));+            });+          });+        });+      },+      windows: function(){+        reset('"List open windows": POST /command {desktop_list_windows} - approve in Adom Bridge ...');+        return post("desktop_list_windows", {}).then(function(res){+          if (refused(res)) return;+          var w = windowsOf(res.body);+          line("APPROVED. " + w.length + " open windows. Top 3:");+          w.slice(0,3).forEach(function(x){ line("  - " + (x.title||"").slice(0,64)); });+        });+      },+      apps: function(){+        reset('"What apps are installed": POST /command {desktop_list_browsers} - approve in Adom Bridge ...');+        return post("desktop_list_browsers", {}).then(function(res){+          if (refused(res)) return;+          var out = res.body && res.body.output; try { if (typeof out === "string") out = JSON.parse(out); } catch(e){}+          var list = (out && out.data && (out.data.browsers||out.data)) || (out && out.browsers) || res.body.browsers || [];+          if (!Array.isArray(list)) list = [];+          line("APPROVED. " + list.length + " browsers installed. Top 3:");+          list.slice(0,3).forEach(function(b){ line("  - " + (b.name || b.browser || b.displayName || b.id || "browser")); });+        });+      },+      cleanup: function(){+        reset('"Clean up": closing KiCad and Fusion via {process_kill} - approve in Adom Bridge ...');+        var imgs = ["kicad.exe","Fusion360.exe"];+        var killed = [];+        return imgs.reduce(function(chain, name, i){+          return chain.then(function(){+            return post("process_kill", { name: name }).then(function(res){+              if (i === 0 && refused(res)) throw new Error("refused");+              var b = res.body || {};+              var did = (b.exitCode === 0) || (typeof b.output === "string" && /killed|terminated/i.test(b.output)) || b.success === true;+              if (did && !/no( running)? (tasks|process)/i.test(JSON.stringify(b))) killed.push(name);+            }).catch(function(e){ if (String(e).indexOf("refused")>=0) throw e; });+          });+        }, Promise.resolve()).then(function(){+          line(killed.length ? "Closed: " + killed.join(", ") : "Nothing to close (KiCad / Fusion were not running).");+        }).catch(function(){ /* refusal already logged */ });+      }+    };++    // ---- Streaming (long) actions over /command/stream (Server-Sent Events) — return a promise ----+    // usuallySec = a rough "this normally takes about N seconds" hint shown in+    // the live status. Ideally the BRIDGE reports these (see BRIDGE_SDK.md); the+    // demo forwards what it knows. The stream always reports the ACTUAL tookMs.+    var recipes = {+      url: { label: "Open a URL and screenshot it", steps: [+        { command:"desktop_open_url", args:{ url:"https://wiki.adom.inc" }, label:"Opening wiki.adom.inc in your browser", usuallySec:2 },+        { waitForWindow:{ titleContains:"Adom Wiki", timeoutSec:30 }, label:"Waiting for the page to load", usuallySec:4 },+        { command:"desktop_screenshot_base64", args:{ titleContains:"Adom Wiki", maxWidth:1000 }, emitImage:true, label:"Screenshotting the page", usuallySec:1 }+      ]},+      kicad: { label: "Open KiCad and screenshot it", steps: [+        { command:"kicad_open_symbol_editor", args:{}, label:"Launching KiCad", usuallySec:30 },+        { waitForWindow:{ titleContains:"Symbol Editor", timeoutSec:90 }, label:"Opening the Symbol Editor window", usuallySec:8 },+        { command:"desktop_screenshot_base64", args:{ titleContains:"Symbol Editor", maxWidth:1000 }, emitImage:true, label:"Screenshotting KiCad", usuallySec:1 }+      ]},+      board: { label: "Open a PCB board and screenshot it", steps: [+        { command:"kicad_open_board", args:{}, label:"Launching the KiCad PCB Editor", usuallySec:30 },+        { waitForWindow:{ titleContains:"PCB Editor", timeoutSec:120 }, label:"Loading the PCB board", usuallySec:10 },+        { command:"desktop_screenshot_base64", args:{ titleContains:"PCB Editor", maxWidth:1000 }, emitImage:true, label:"Screenshotting the board", usuallySec:1 }+      ]},+      fusion: { label: "Open Fusion and screenshot it", steps: [+        { command:"fusion_start", args:{}, label:"Launching Autodesk Fusion", usuallySec:90 },+        { waitForWindow:{ titleContains:"Fusion", timeoutSec:280 }, label:"Waiting for the Fusion window", usuallySec:120 },+        { command:"desktop_screenshot_base64", args:{ titleContains:"Fusion", maxWidth:1000 }, emitImage:true, label:"Screenshotting Fusion", usuallySec:2 }+      ]}+    };+    function usually(d){ return (d.usuallySec != null) ? " (usually ~" + d.usuallySec + "s)" : ""; }+    function took(d){ return (d.tookMs != null) ? " (took " + (d.tookMs/1000).toFixed(1) + "s)" : ""; }+    function onEvent(ev, dataStr){+      var d = {}; try { d = JSON.parse(dataStr); } catch(e){}+      var t = Math.round((d.elapsedMs||0)/1000);+      if (ev === "start"){ line("Adom Bridge accepted the recipe (" + d.steps + " steps). Running server-side, streaming status back ..."); return; }+      if (ev === "progress"){+        if (d.state === "running") line("step " + d.step + "/" + d.of + ": " + d.label + usually(d) + " ...");+        else if (d.state === "working") line("  [" + (d.waitedSec!=null?d.waitedSec:t) + "s] " + (d.label||"working") + usually(d) + " - still working ...");+        else if (d.state === "waiting") line("  [" + (d.waitedSec!=null?d.waitedSec:t) + "s] " + (d.label||"waiting") + usually(d) + " - still waiting ...");+        else if (d.state === "appeared") line("  window appeared: \"" + d.window + "\"" + took(d));+        else if (d.state === "ok") line("  step " + d.step + "/" + d.of + " done" + took(d));+        else if (d.state === "failed") line("  step " + d.step + " FAILED" + took(d) + ": " + JSON.stringify(d.result||{}).slice(0,220));+        return;+      }+      if (ev === "image"){ line("  proof screenshot received (" + d.width + "x" + d.height + "). Rendering below."); showImg(d.image); return; }+      if (ev === "error"){ line("  ERROR: " + (d.error || JSON.stringify(d))); return; }+      if (ev === "done"){ line(d.ok ? ("DONE - proven it worked (total " + t + "s).") : "DONE - did not complete (" + (d.reason || ("failed step " + d.failedStep)) + ")."); return; }+    }+    function stream(key){+      var r = recipes[key];+      reset('"' + r.label + '": opening a long-lived connection to Adom Bridge; approve the prompt, then watch the live status ...');+      return fetch(AB + "/command/stream", { method:"POST", headers:{ "Content-Type":"application/json" }, body: JSON.stringify({ label: r.label, steps: r.steps }) })+        .then(function(resp){+          if (!resp.ok){ return resp.json().catch(function(){ return {}; }).then(function(j){ refused({ status: resp.status, body: j }); }); }+          var reader = resp.body.getReader(), dec = new TextDecoder(), buf = "";+          function pump(){+            return reader.read().then(function(res){+              if (res.done) return;+              buf += dec.decode(res.value, { stream:true });+              var idx;+              while ((idx = buf.indexOf("\n\n")) >= 0){+                var raw = buf.slice(0, idx); buf = buf.slice(idx + 2);+                var ev = "message", data = "";+                raw.split("\n").forEach(function(l){+                  if (l.indexOf("event:") === 0) ev = l.slice(6).trim();+                  else if (l.indexOf("data:") === 0) data += l.slice(5).trim();+                });+                if (data) onEvent(ev, data);+              }+              return pump();+            });+          }+          return pump();+        });+    }++    // ---- Wire buttons with busy feedback ----+    function run(btn, fn, label){+      if (runningBtn) return;               // one action at a time+      setBusy(btn, label);+      Promise.resolve().then(fn).catch(function(e){ line("error: " + e); }).then(clearBusy, clearBusy);+    }+    document.getElementById("abwc-detect").onclick = function(){+      run(this, function(){+        reset("GET " + AB + "/health ...");+        return fetch(AB + "/health").then(function(r){ return r.ok ? r.json() : Promise.reject("HTTP " + r.status); })+          .then(function(j){ dot.style.background = j.service === "adom-bridge" ? "#00b8b0" : "#e6c86c"; line("RUNNING: " + JSON.stringify(j)); })+          .catch(function(e){ dot.style.background = "#484f58"; line("Not reachable (" + e + "). Is Adom Bridge running on this machine?"); });+      }, "Detecting...");     };-    document.getElementById("abwc-status").onclick = function () {-      reset("GET " + AB + "/status ...");-      fetch(AB + "/status").then(function(r){return r.json();}).then(function(j){-        var wc = (j && j.webControl) || {};-        line("Web Control enabled: " + wc.enabled + "  |  requireHandshake: " + wc.requireHandshake + "  |  gate: " + wc.commandGate);-      }).catch(function(e){ line("failed: " + e); });+    document.getElementById("abwc-status").onclick = function(){+      run(this, function(){+        reset("GET " + AB + "/status ...");+        return fetch(AB + "/status").then(function(r){ return r.json(); }).then(function(j){+          var wc = (j && j.webControl) || {};+          line("Web Control enabled: " + wc.enabled + "  |  requireHandshake: " + wc.requireHandshake + "  |  gate: " + wc.commandGate);+        }).catch(function(e){ line("failed: " + e); });+      }, "Reading...");     };-    Array.prototype.forEach.call(document.querySelectorAll(".abwc-do"), function (btn) {-      btn.onclick = function () {-        var verb = btn.getAttribute("data-verb"), label = btn.getAttribute("data-label");-        var args; try { args = JSON.parse(btn.getAttribute("data-args") || "{}"); } catch (e) { args = {}; }-        reset('"' + label + '": POST ' + AB + "/command {" + verb + "}" + (mode()==="handshake" ? "  (handshake mode: no secret sent)" : " - Adom Bridge will ask YOU to approve ..."));-        fetch(AB + "/command", { method:"POST", headers:{ "Content-Type":"application/json" }, body: JSON.stringify({ command: verb, args: args }) })-          .then(function(r){ return r.json().then(function(j){ return { status:r.status, body:j }; }); })-          .then(function(res){-            if (res.body && res.body.errorCode === "handshake_required") { line("REFUSED (handshake_required): this page has no secret, so Adom Bridge refused BEFORE the prompt. On the real wiki, wiki.adom.inc's backend supplies it. evil.com is stuck exactly here."); return; }-            if (res.body && res.body.errorCode === "web_control_disabled") { line("Web Control is turned OFF in Adom Bridge > Settings."); return; }-            if (res.body && res.body.errorCode === "web_denied") { line("You clicked Deny (or the prompt timed out). Nothing ran."); return; }-            if (res.status !== 200) { line("Not approved (HTTP " + res.status + "): " + JSON.stringify(res.body).slice(0,300)); return; }-            // success-            if (res.body && res.body.image) { line("APPROVED. Captured " + res.body.width + "x" + res.body.height + "."); img.src = "data:image/png;base64," + res.body.image; img.style.display = "block"; return; }-            line("APPROVED and ran: " + JSON.stringify(res.body).slice(0, 400));-          })-          .catch(function(e){ line("blocked before Adom Bridge answered: " + e); });-      };+    Array.prototype.forEach.call(document.querySelectorAll("[data-act]"), function(b){+      b.onclick = function(){ run(b, function(){ return quick[b.getAttribute("data-act")](); }); };+    });+    Array.prototype.forEach.call(document.querySelectorAll("[data-stream]"), function(b){+      b.onclick = function(){ run(b, function(){ return stream(b.getAttribute("data-stream")); }, "Running..."); };     });   })();   </script>
version.json+22−22
@@ -1,22 +1,22 @@-{
-  "manifest_version": 1,
-  "version": "2.0.35",
-  "released_at": "2026-08-14T01:15:00Z",
-  "windows": {
-    "installer_url": "https://wiki.adom.inc/download/adom/adom-bridge/2.0.35/Adom%20Bridge_2.0.35_x64-setup.exe",
-    "installer_filename": "Adom Bridge_2.0.35_x64-setup.exe",
-    "installer_sha256": "df2eb0cafca8e89374629840fcb21bd46ecbda8d08588190a2666aa3834fe9f6",
-    "installer_size": 17671936
-  },
-  "changelog_url": "https://wiki.adom.inc/adom/adom-bridge",
-  "minimum_required": "1.3.30",
-  "note": "Adom Bridge 2.0.34. Web-consent overhaul: ONE consent prompt per origin (a page firing several verbs no longer stacks a toast per verb; Allow 1 hr covers the whole burst). Clicking a consent toast's BODY opens ab with the 'Looking for Hydrogen?' companion splash rolled DOWN to the rail and a proper Web Control approval dialog (which was invisible since 2.0.32 due to a display:none CSS trap).",
-  "macos": {
-    "installer_url": "https://wiki.adom.inc/download/adom/adom-bridge-macos/1.9.162/Adom.Desktop_1.9.162_macos_aarch64.dmg",
-    "installer_filename": "Adom.Desktop_1.9.162_macos_aarch64.dmg",
-    "installer_sha256": "2ff68eab7f80111fd5eb407c270d9b73b44084e3e9e90c0f0c0397617494f253",
-    "installer_size": 12241094,
-    "arch": "aarch64",
-    "note": "The macOS build predates the rename; the ab mac thread ships the renamed dmg from adom/adom-bridge-macos."
-  }
-}\ No newline at end of file+{+  "manifest_version": 1,+  "version": "2.0.37",+  "released_at": "2026-08-14T01:15:00Z",+  "windows": {+    "installer_url": "https://wiki.adom.inc/download/adom/adom-bridge/2.0.37/Adom.Bridge_2.0.37_windows_x64-setup.exe",+    "installer_filename": "Adom.Bridge_2.0.37_windows_x64-setup.exe",+    "installer_sha256": "ed70675a662c0427d18841652566f65327da473aff176caae97e325bcae4c60c",+    "installer_size": 17720488+  },+  "changelog_url": "https://wiki.adom.inc/adom/adom-bridge",+  "minimum_required": "1.3.30",+  "note": "Adom Bridge 2.0.34. Web-consent overhaul: ONE consent prompt per origin (a page firing several verbs no longer stacks a toast per verb; Allow 1 hr covers the whole burst). Clicking a consent toast's BODY opens ab with the 'Looking for Hydrogen?' companion splash rolled DOWN to the rail and a proper Web Control approval dialog (which was invisible since 2.0.32 due to a display:none CSS trap).",+  "macos": {+    "installer_url": "https://wiki.adom.inc/download/adom/adom-bridge-macos/1.9.162/Adom.Desktop_1.9.162_macos_aarch64.dmg",+    "installer_filename": "Adom.Desktop_1.9.162_macos_aarch64.dmg",+    "installer_sha256": "2ff68eab7f80111fd5eb407c270d9b73b44084e3e9e90c0f0c0397617494f253",+    "installer_size": 12241094,+    "arch": "aarch64",+    "note": "The macOS build predates the rename; the ab mac thread ships the renamed dmg from adom/adom-bridge-macos."+  }+}