← Commit history

pup hero + verb-heading typography fix

John Lauer ·a5c0f0bc23 · ·parent da141b1
1 file changed +227−225
README.md+227−225
@@ -33,6 +33,10 @@ Driving KiCad end-to-end from the container — symbol, footprint, 3D model, sch  ![KiCad driven by Adom Desktop](kicad-shot.png) +Driving real **Chrome-for-Testing** windows from the container — TI, ST, Mouser, Adafruit, SparkFun, Espressif, every one scriptable (navigate, eval, screenshot, record):++![Drive real Chrome from anywhere](pup-hero.png)+ The **laptop-side GUI** installs separately on the user's Windows machine — a signed NSIS installer served from this page (the package manifest's `assets` map / `version.json` carry the exact URL + SHA256; Hydrogen Desktop's setup installs it automatically). Once installed, the GUI auto-updates itself from the wiki's `version.json` manifest (prompt by default, every 4h + on launch).  ## How it fits together@@ -55,33 +59,31 @@ This adompkg version tracks the Adom Desktop release version (`X.Y.Z`). The pack  - Source: <https://github.com/adom-inc/adom-desktop> (private during beta) -![Drive the real Chrome on the laptop](pup.png)- ## Command reference -Every verb and subcommand `adom-desktop` supports — **210 across 12 categories** — inlined below (the same names are the dispatch keys the cloud AI uses over the relay). Invoke as `adom-desktop <verb> '<json args>'`; bridge verbs (`kicad_*`, `fusion_*`, `browser_*`, `hd_*`) auto-spawn their bridge on first call. Every *failing* response also carries a `_hint` telling you the next step — surface it verbatim. This list is generated from the live help-map; run `adom-desktop commands` for the machine-readable source.+Every verb and subcommand `adom-desktop` supports — **210 across 12 categories** — inlined below (the same names are the dispatch keys the cloud AI uses over the relay). Invoke as `adom-desktop <verb> '<json args>'`; bridge verbs (`kicad_*`, `fusion_*`, `browser_*`, `hd_*`) auto-spawn their bridge on first call. Every *failing* response also carries a `_hint` telling you the next step — surface it verbatim. Generated from the live help-map; run `adom-desktop commands` for the machine-readable source.  **Categories:** [Connectivity](#connectivity) · [Setup](#setup) · [Shell & process](#shell-process) · [File transfer](#file-transfer) · [Desktop & bridge mgmt](#desktop-bridge-mgmt) · [Screen recording](#screen-recording) · [Notifications](#notifications) · [Project watch](#project-watch) · [Browser (Puppeteer)](#browser-puppeteer) · [KiCad bridge](#kicad-bridge) · [Fusion 360 bridge](#fusion-360-bridge) · [Hydrogen Desktop](#hydrogen-desktop) -### Connectivity+## Connectivity  _Liveness, status, multi-AD targeting._ (5 verbs) -#### `health`+### `health`  Check if the relay server HTTP API is reachable  **Returns:** Relay server health status  -#### `kick_all`+### `kick_all`  Force-disconnect all WebSocket clients (they auto-reconnect in ~5s)  **Note:** Use when connections are stale or duplicated  -#### `ping`+### `ping`  Round-trip connectivity test (5s timeout) @@ -93,14 +95,14 @@ Round-trip connectivity test (5s timeout) **Note:** Always run this first to verify desktop connection  -#### `status`+### `status`  Query connected clients, capabilities, and app installation status  **Returns:** clients count, appName, hostname, capabilities[], desktop app status (kicad/fusion/browser installed/running)  -#### `targets`+### `targets`  v1.8.133+: list the Adom Desktops connected to this relay so you can address one by name. Each AD has a name (its --client-name / Settings value, defaulting to the OS hostname). Use this to discover names, then run any verb with the global --target <name> selector, or --target all to fan out to every AD. @@ -109,22 +111,22 @@ v1.8.133+: list the Adom Desktops connected to this relay so you can address one **Note:** See _targeting_note at the top of this map for how --target works.  -### Setup+## Setup  _One-time desktop setup._ (1 verbs) -#### `setup_desktop`+### `setup_desktop`  Generate installer URL and server config JSON for first-time desktop app setup  **Returns:** installer_url, server_config JSON, steps[]  -### Shell & process+## Shell & process  _Run shell commands, scripts, WSL, and launch/locate apps on the laptop._ (6 verbs) -#### `find_exe`+### `find_exe`  Find an executable without launching it. Searches: (1) absolute path as-is, (2) glob expansion for webdeploy-style version directories, (3) Start Menu .lnk targets, (4) PATH. Returns the resolved absolute path or an error. @@ -135,10 +137,10 @@ Find an executable without launching it. Searches: (1) absolute path as-is, (2) **Returns:** path, source (absolute|glob|startmenu|path)  -#### `args`+### `args`  -#### `run_script`+### `run_script`  v1.8.46+: Execute a base64-encoded script via a chosen interpreter (cmd / powershell / bash). The script bytes never touch a shell-string parser, so NO quoting/escaping survives to be mangled by cmd.exe, PowerShell's parser quirks, or MSYS path conversion — caller never hand-escapes anything. Host-side: cmd / powershell get a temp file (.cmd / .ps1); bash gets the script via stdin (no temp file). Output decoded as UTF-8 (UTF-16LE detection for PowerShell 5.1 output). Returns stdout/stderr/exitCode and cleans up any temp file. @@ -153,7 +155,7 @@ v1.8.46+: Execute a base64-encoded script via a chosen interpreter (cmd / powers **Note:** Requires shell auto-approve to be active — same trust level as shell_execute. Enable with `shell_auto_approve '{"duration_secs": 86400}'` (24h) or `'{"permanent": true}'` (no expiry). Embedded mode defaults to permanent. Refuses cleanly with an actionable error if not approved (no per-call modal — these are programmatic verbs, not interactive).  -#### `shell_execute`+### `shell_execute`  Execute a shell command on the user's desktop (requires user approval). Escape hatch for things that don't have a structured verb yet — most common operations are covered by desktop_install_node / desktop_install_kicad / desktop_list_files / desktop_watch_files / desktop_list_browsers etc., which DON'T require per-call approval. Use shell_execute only when no structured verb exists for the task. @@ -168,12 +170,12 @@ Execute a shell command on the user's desktop (requires user approval). Escape h **Note:** User must approve via in-app dialog unless auto-approve is active. v1.7.3+ timeout forwarding bug fixed — your timeoutSeconds is now respected by the desktop-side shell killer. v1.7.16+ approval-polling fixed — the CLI now polls the relay's deferred-result endpoint with the original requestId instead of re-sending the command every second (which v1.7.15 did, causing infinite 'Another shell command is already waiting for approval' loops because every retry created a fresh approval the user could never out-click).  -#### `shell_kill_all`+### `shell_kill_all`  Kill all running shell command processes  -#### `wsl_exec`+### `wsl_exec`  v1.8.46+: Execute a base64-encoded script inside a WSL distro via the canonical `wsl -d <distro> -u <user> -- bash -lc <decoded>` argv array. Eliminates the --, nested-quote, and $_ pitfalls because Rust's std::process::Command on Windows applies proper CommandLineToArgvW quoting per argv element — wsl.exe sees `bash -lc <decoded>` as three distinct argv items, then bash's -c sees the entire script as ONE string with no outer-shell interpretation. The exact 50×-per-session call pattern. @@ -186,11 +188,11 @@ v1.8.46+: Execute a base64-encoded script inside a WSL distro via the canonical **Returns:** On success: {success:true, ok:true, stdout, stderr, exitCode, durationMs, interpreter:'wsl', distro, user, [_hint?]}. wsl.exe's UTF-16LE output is decoded as text automatically (BOM-detect + odd-position-NULL heuristic, fall back to UTF-8 lossy). On failure: {success:false, ok:false, stdout:'', stderr:'', exitCode:-1, error, failedAt, _hint}. failedAt is one of: 'args' (distro/user/scriptB64 empty), 'decode' (base64 invalid), 'utf8' (decoded bytes not UTF-8), 'spawn' (wsl.exe couldn't launch — see wsl_error_hint), 'timeout' (script exceeded timeoutSeconds). EVERY failure carries an actionable _hint. The most common spawn failure is 'distro not installed' — the hint will tell you to run `wsl -l -v` to enumerate, or fall back to run_script interpreter:bash for the default distro. **v1.8.60+ ADVISORY**: success responses may carry an additive `_hint` when the script looks like a direct HD build (raw `cargo`/`pnpm` targeting `hydrogen-desktop`/`crates/hd-*`); the hint points at the dedicated `hd_*` verbs that handle the exe lock + SHA verification.  -### File transfer+## File transfer  _Move files between the cloud container and the laptop (streamed, SHA-verified)._ (2 verbs) -#### `pull_file`+### `pull_file`  Pull files from the desktop back to Docker. Streams as binary WS frames (1 MiB chunks) — works for files up to several hundred MB. The legacy 30s base64-JSON path is gone; pull_file v1.4.3+ has no practical size limit. @@ -203,7 +205,7 @@ Pull files from the desktop back to Docker. Streams as binary WS frames (1 MiB c **Note:** Per-file timeout is 600s (enough for ~600 MB at 1 MB/s). For 75 MB files expect ~12s on a typical home connection. errors[] is included alongside files[] when at least one but not all files succeed; success is true iff at least one file landed.  -#### `send_files`+### `send_files`  Send files from Docker container to the desktop @@ -213,11 +215,11 @@ Send files from Docker container to the desktop - `targetApp` — optional: general|kicad|fusion360  -### Desktop & bridge mgmt+## Desktop & bridge mgmt  _Windows, screenshots, files, embedded mode, relay servers, tunnels, and bridge lifecycle._ (44 verbs) -#### `bridge_check_updates`+### `bridge_check_updates`  READ-ONLY: report, for every installed bridge, the version AD runs now vs the latest version on the wiki, and whether an update is available. Downloads NOTHING — this is the 'what's updatable?' probe. v1.8.144+. Use it before refresh_bridges to decide what to pull. Mirrors the GUI's 'Check for updates' button. @@ -226,7 +228,7 @@ READ-ONLY: report, for every installed bridge, the version AD runs now vs the la **Note:** A bundled bridge shipped AHEAD of the wiki (and bundled-only puppeteer, whose deps exceed the wiki file limit) correctly reports updateAvailable=false — AD keeps the newer bundled copy. wikiReachable=false means offline or the bridge isn't wiki-hosted. To APPLY an update: refresh_bridges {"name":"<bridge>"} for one, or refresh_bridges for all.  -#### `bridge_install`+### `bridge_install`  Install a bridge from a wiki manifest URL. The manifest's referenced zip is downloaded, sha256-verified, extracted into the cache, and the runtime registry is rescanned. Third-party bridges (Altium, MATLAB, oscilloscope vendors, etc.) ship via wiki using the same scripts/release-bridge.sh format. v1.8.0+. @@ -238,7 +240,7 @@ Install a bridge from a wiki manifest URL. The manifest's referenced zip is down **Note:** The bridge zip MUST contain a bridge.json at its root with name + spawn.{kind,entrypoint,port,...} + verbPrefixes. See https://github.com/adom-inc/adom-desktop/blob/main/plugins/kicad/bridge.json for the schema. To update an existing bridge, install again — the cache atomic-swaps.  -#### `bridge_list`+### `bridge_list`  List every installed bridge (kicad, puppeteer, fusion360, plus any third-party bridges installed via bridge_install). v1.8.0+. Each entry reports name, displayName, version, source (bundled/cache/third_party), installPath, paused flag, spawn kind, ports, verbPrefixes the bridge owns, and verbCount. @@ -249,7 +251,7 @@ List every installed bridge (kicad, puppeteer, fusion360, plus any third-party b **Ports:** v1.8.31+: bridges use DYNAMIC ports. `manifestPort` is what the bridge.json declares (0 = 'let adom-desktop pick'). `runtimePort` is what the bridge is ACTUALLY bound to right now (null if not yet spawned). CALLERS NEVER NEED EITHER. Always route bridge verbs via adom-desktop's CLI/direct-API by bridge id (`kicad_*`, `fusion_*`, etc.) — adom-desktop forwards to whatever port the bridge is on. Bridge ports are pure internal plumbing; if you find yourself reading them, you're probably doing something wrong.  -#### `bridge_pause`+### `bridge_pause`  Pause a bridge: subsequent calls to its verb prefixes (e.g. kicad_*) will fail with bridge_paused until you bridge_resume. The running server process is NOT killed; this is a routing-layer flag. @@ -259,7 +261,7 @@ Pause a bridge: subsequent calls to its verb prefixes (e.g. kicad_*) will fail w **Returns:** {success, name, paused:true, _hint}  -#### `bridge_resume`+### `bridge_resume`  Resume a paused bridge — flip the routing flag back. Inverse of bridge_pause. @@ -269,7 +271,7 @@ Resume a paused bridge — flip the routing flag back. Inverse of bridge_pause. **Returns:** {success, name, paused:false, _hint}  -#### `bridge_uninstall`+### `bridge_uninstall`  Remove a bridge from the cache. v1.8.0+. Bundled bridges (kicad, puppeteer, fusion360 in current installer) revert to the bundled fallback; third-party bridges are fully removed. The running bridge server process is NOT killed automatically; the response _hint includes the kill command if you want it gone immediately. @@ -279,7 +281,7 @@ Remove a bridge from the cache. v1.8.0+. Bundled bridges (kicad, puppeteer, fusi **Returns:** {success, name, cacheRemoved, stillInstalledViaBundled, _hint}  -#### `desktop_bring_to_front`+### `desktop_bring_to_front`  Bring a window to the foreground @@ -289,7 +291,7 @@ Bring a window to the foreground - `titleContains` — optional string (case-insensitive)  -#### `desktop_caption`+### `desktop_caption`  Show a large always-on-top, click-through text overlay on the user's DESKTOP (not a chat caption) — for narrating what you're doing on their screen, screen-share / demo labels, status banners. Win32 layered window over every app. @@ -310,21 +312,21 @@ Show a large always-on-top, click-through text overlay on the user's DESKTOP (no **Note:** AUTO-EXPIRE by design so a forgotten caption can't be stranded on the user's screen. Default 30s. To keep one up LONGER: re-issue the same id before it expires (resets the timer — a heartbeat) OR pass a bigger duration (≤10 min). Only persist:true is truly sticky, and then you MUST clean up with {"action":"hide","id":"<id>"}. Always dismiss captions when the narrated action is done.  -#### `desktop_connect_all`+### `desktop_connect_all`  v1.8.42+: programmatically trigger 'Connect All' for every enabled server entry. Mirrors the tray-menu button and HD's tray-menu 'Connect All' item. Spawns ws_loop for any entry that isn't already connected.  **Returns:** {ok, success, totalEnabled, spawned, alreadyConnected, _hint}  -#### `desktop_disconnect_all`+### `desktop_disconnect_all`  v1.8.42+: programmatically drop every live WS connection. Server entries stay in config (use server_remove to delete). Mirrors HD's tray 'Disconnect All'.  **Returns:** {ok, success, disconnected, _hint}  -#### `desktop_embedded_enter`+### `desktop_embedded_enter`  v1.8.73+: explicit runtime adopt — HD calls this to switch a running standalone AD into embedded mode immediately (no 60s wait for the embedded_monitor tick). Always sets `entered_via=runtime-adopt`, meaning: when HD subsequently dies, AD will REVERT to standalone (not exit). If HD wants AD to exit on HD-death (the no-hanging-chad rule), HD must spawn AD with `--embedded` instead — that sets `entered_via=launch-flag`. Idempotent: returns `was_embedded:true` if AD was already embedded; no-ops in that case. @@ -333,7 +335,7 @@ v1.8.73+: explicit runtime adopt — HD calls this to switch a running standalon **See also:** desktop_embedded_release (revert), desktop_embedded_status (introspect). HD-side recipe in plugins/hd/SKILL.md.  -#### `desktop_embedded_release`+### `desktop_embedded_release`  v1.8.73+: explicit transition out of embedded mode. v1.8.77+ ALWAYS reverts to standalone, regardless of `entered_via`. AD's process REMAINS RUNNING after this call — tray restored, footer pill hidden, updater poller resumed, marker deleted. The two operations are now explicitly separate: this verb says 'stop being embedded, become standalone'; use `desktop_shutdown` to exit AD. Pre-v1.8.77 a launch-flag AD would exit on release, which fought HD's spawn supervisor (HD calls release → AD exits → supervisor respawns → looks like AD didn't exit). v1.8.77 decoupling fixes that. @@ -342,14 +344,14 @@ v1.8.73+: explicit transition out of embedded mode. v1.8.77+ ALWAYS reverts to s **See also:** desktop_embedded_enter (adopt), desktop_shutdown (explicit AD exit). The 60s embedded_monitor's HD-process-death branch is unchanged: ungraceful HD death still uses entered_via to decide exit-vs-revert (LaunchFlag→exit, RuntimeAdopt→revert).  -#### `desktop_embedded_status`+### `desktop_embedded_status`  v1.8.42+: introspect whether AD is currently running in embedded mode (HD bundling AD). v1.8.73+ also reports `enteredVia` — `launch-flag` means HD spawned AD as its child (AD exits when HD dies, no orphan); `runtime-adopt` means AD was running standalone and HD adopted it at runtime via `/embedded/enter` (AD reverts to standalone when HD dies, keeps running). Standalone AD returns embedded=false.  **Returns:** {ok, success, embedded:bool, owner:string|null, source:string|null, enteredVia:'launch-flag'|'runtime-adopt'|null, pendingRelayUrl, pendingRelayName, startHidden:bool, markerPath, markerExists:bool, _hint}  -#### `desktop_install_kicad`+### `desktop_install_kicad`  Unattended KiCad install via winget (Windows only, v1.6.0+). Single-purpose: runs `winget install --id KiCad.KiCad --silent --accept-package-agreements --accept-source-agreements`. Long-running (2-5 min — downloads ~700 MB then installs). Returns the winget exit code + stdout/stderr so the caller can decide what to do. Doesn't go through shell_execute approval — it's a fixed-command verb the AI calls deliberately, not a free-form shell. @@ -358,7 +360,7 @@ Unattended KiCad install via winget (Windows only, v1.6.0+). Single-purpose: run **Note:** Trigger this when kicad_list_versions returns errorCode:"kicad_not_installed". After install completes, restart Adom Desktop OR call kicad_list_versions again — the kicad bridge caches detection results, so a fresh detection scan may be needed to pick up the freshly-installed binaries.  -#### `desktop_install_node`+### `desktop_install_node`  Unattended Node.js LTS install via winget (Windows only, v1.7.0+). Single-purpose: runs `winget install --id OpenJS.NodeJS.LTS --silent --accept-package-agreements --accept-source-agreements`. ~1-2 min depending on network. Trigger this when ANY browser_* command returns errorCode:"node_not_found" — that error fires when the puppeteer bridge can't auto-spawn because Node isn't installed. After install, retry the original browser_* command — the bridge reads node.exe location from the Windows registry (which winget writes), so a restart isn't needed. @@ -367,7 +369,7 @@ Unattended Node.js LTS install via winget (Windows only, v1.7.0+). Single-purpos **Note:** v1.7.0 closes the fresh-Windows-machine pup gap. Pre-v1.7.0, a user without Node hit a generic 'bridge not running' error and had to debug it. Now: errorCode:"node_not_found" → desktop_install_node → retry. Adom Desktop also pre-fetches Node + Chrome for Testing on first launch after install (background task), so most users won't ever see node_not_found.  -#### `desktop_list_browsers`+### `desktop_list_browsers`  Catalog of every installed native browser on the desktop, with each browser's profiles + their signed-in account info (when known). Reads each browser's canonical metadata file directly — Chromium-based browsers (Chrome/Edge/Brave) all use the same `Local State` JSON layout; Firefox uses `profiles.ini`. No shell, no version-probing trial-and-error. Use this to introspect what's available BEFORE calling desktop_open_url with a specific browser+profile. @@ -376,7 +378,7 @@ Catalog of every installed native browser on the desktop, with each browser's pr **Note:** Typical flow: call desktop_list_browsers, find the profile whose gaia matches what the URL needs (e.g. work email for adom.inc Google Workspace links, personal Gmail address for [email protected] pages, media account for YouTube channel work), then pass that profile.id to desktop_open_url. Falls back gracefully — browsers with no install are absent from the list, profiles with no metadata file (browser never opened) return as an empty list. v1.7.1+.  -#### `desktop_list_files`+### `desktop_list_files`  List files in a directory matching a shell-glob, optionally filtered to those modified after a given timestamp. Pure filesystem op (no shell) — replaces the shell_execute + PowerShell-poll-Downloads pattern that callers like chip-fetcher used to use to wait for a download. NOT recursive; lists one directory only. @@ -390,19 +392,19 @@ List files in a directory matching a shell-glob, optionally filtered to those mo **Note:** Common pattern: record `since = $(date +%s)` BEFORE you click a download button, then list_files after. The newest-first sort means files[0] is your fresh download. For a watch-until-arrived loop, use desktop_watch_files instead — same args, but blocks until a match appears.  -#### `desktop_list_windows`+### `desktop_list_windows`  List all visible windows with HWNDs, titles, positions  -#### `desktop_logout`+### `desktop_logout`  v1.8.42+: clear AD's cached Adom Cloud session (~/.adom/session.json). HD calls this on its own sign-out so AD doesn't display a stale email in its footer. Equivalent to the GUI's Sign-out link.  **Returns:** {ok, success, _hint}. After logout, adom_whoami returns null until the user signs in again (via the GUI footer link or another handoff).  -#### `desktop_open_folder`+### `desktop_open_folder`  Open file/folder in Windows Explorer @@ -410,7 +412,7 @@ Open file/folder in Windows Explorer - `path` — required string  -#### `desktop_open_url`+### `desktop_open_url`  Open a URL in the user's NATIVE OS BROWSER — i.e. their everyday daily-driver Edge / Chrome / Firefox / Brave (whatever Windows has registered as the default URL handler), with their existing logged-in sessions, saved passwords, history, bookmarks, and extensions. This is NOT Puppeteer / Chrome for Testing — it's the real browser the human uses. Use this when a HUMAN needs to interact with a page that requires being signed in (claude.ai, GitHub, internal tools, banking, etc.). v1.7.1+ supports `profile` for picking a specific Chrome/Edge/Firefox profile (work vs personal vs media account, etc.) — call desktop_list_browsers first to discover available profile IDs. @@ -428,7 +430,7 @@ Open a URL in the user's NATIVE OS BROWSER — i.e. their everyday daily-driver   v1.7.1+: when the user has multiple Chrome profiles (work + personal + media account, etc.) call desktop_list_browsers first to enumerate, then pass the matching `profile` ID. The response surfaces gaia/email per profile so you can match by account context.  -#### `desktop_pull_glob`+### `desktop_pull_glob`  Convenience orchestrator: list_files (or watch_files if wait:true) → for each match, pull_file streams the bytes to a container-side absolute path. One command instead of three. Designed for the canonical chip-fetcher pattern: record before-timestamp, click, wait, pull. @@ -446,19 +448,19 @@ Convenience orchestrator: list_files (or watch_files if wait:true) → for each **Note:** Use this instead of shell_execute('powershell -c ... while not exists ...; sleep 1') — that pattern is deprecated for new code. shell_execute itself remains as an escape hatch but should not be used for download polling.  -#### `desktop_revoke_approvals`+### `desktop_revoke_approvals`  Clear all shell auto-approve permissions and deny pending approvals  -#### `desktop_screenshot_screen`+### `desktop_screenshot_screen`  Capture the entire desktop (all monitors). v1.8.62+ produces full + safe-resized PNG pair to %TEMP%\adom-desktop-shots\screen-<ts>.{full,safe}.png. v1.8.61+ universal screenshot delivery contract applies (CLI auto-pulls both to /tmp/ad-shots/, enriches response with local* + dimensions + narrative). See desktop_screenshot_window for the full return shape — same except hwnd-specific prefix is replaced with `screen-`.  **Returns:** Same shape as desktop_screenshot_window's returns (top-level fullPath + safePath + dimensions; CLI-side local* fields added by v1.8.61 auto-pull).  -#### `desktop_screenshot_window`+### `desktop_screenshot_window`  Capture a specific window by HWND. v1.8.62+ writes BOTH a lossless full-res PNG + a Claude-safe-resized copy (≤resizeMax) to %TEMP%\adom-desktop-shots\window-<hwnd>-<ts>.{full,safe}.png. v1.8.61+ universal screenshot delivery contract: the CLI auto-pulls both PNGs to /tmp/ad-shots/<basename> and enriches the response with localFullPath, localSafePath, localFullExists, localSafeExists, fullBytes, safeBytes, source, narrative. Backward compatible: existing data.image base64 + sizeBytes still inside `output` JSON string. @@ -469,7 +471,7 @@ Capture a specific window by HWND. v1.8.62+ writes BOTH a lossless full-res PNG **Returns:** Top-level: success, output (JSON string with the existing message/data/image/format/encoding/sizeBytes shape), error, fullPath, safePath, fullWidth, fullHeight, safeWidth, safeHeight, fullBytesHost, safeBytesHost, resizeMax. After v1.8.61+ CLI auto-pull: localFullPath, localSafePath, localFullExists, localSafeExists, fullBytes (= bytes on caller side), safeBytes, source, narrative. On disk-write failure (rare): same shape minus the file fields + `_hint` pointing at %TEMP% / AV diagnosis.  -#### `desktop_set_window_state`+### `desktop_set_window_state`  Set window state without necessarily foregrounding @@ -481,7 +483,7 @@ Set window state without necessarily foregrounding **Note:** Use bring_to_front to foreground + maximize in one call. Use this for minimize/hide/show.  -#### `desktop_show_ui`+### `desktop_show_ui`  v1.8.137+: stage an in-window UI element so it can be captured via desktop_screenshot_window (no computer-use). Emits the ad-show-ui frontend event, waits ~350ms for render, returns. @@ -492,14 +494,14 @@ v1.8.137+: stage an in-window UI element so it can be captured via desktop_scree **Note:** Workflow for docs screenshots: desktop_show_ui {"target":"settings"} then desktop_screenshot_window on the AD window. For embedded-vs-standalone, toggle desktop_embedded_enter / desktop_embedded_release first, then screenshot.  -#### `desktop_shutdown`+### `desktop_shutdown`  v1.8.42+: graceful AD exit — stop bridges (kicad, fusion, puppeteer) then exit. HD's tray 'Quit' uses this to take AD down with HD. Standalone callers can use it for clean reset between dev iterations.  **Returns:** {ok, success, output, _hint}. The exit happens ~500ms after the response is sent so the caller doesn't see connection-reset.  -#### `desktop_watch_files`+### `desktop_watch_files`  Block until a file matching the glob appears in the directory (mtime > since), or timeoutMs elapses. Pure filesystem op, no shell. Polls fs.read_dir every pollMs (default 1000ms). Replaces the shell_execute + PowerShell while-loop pattern for waiting on a download. @@ -515,21 +517,21 @@ Block until a file matching the glob appears in the directory (mtime > since), o **Note:** Returns the FIRST file that matches — sorted newest-first within a poll cycle. Use desktop_pull_glob if you want the watch + multi-file pull in one call.  -#### `desktop_window_hide`+### `desktop_window_hide`  v1.8.42+: hide AD's main window. App keeps running in the background; use desktop_window_show to bring it back. Standalone AD users typically use the window's X button which is bound to hide-not-quit.  **Returns:** {ok, success, _hint}  -#### `desktop_window_show`+### `desktop_window_show`  v1.8.42+: show + focus AD's main window. Used by Hydrogen Desktop's tray menu when bundling AD in embedded mode (HD's tray entry 'Open Adom Desktop' fires this). Also reachable standalone — any caller can bring AD to the foreground.  **Returns:** {ok, success, _hint}  -#### `direct_api`+### `direct_api`  INFORMATIONAL — not a CLI verb. v1.8.25+ adds a local HTTP listener on `http://127.0.0.1:47200` inside the adom-desktop GUI. Sibling apps on the same machine (Hydrogen Desktop, future Adom-family apps) can POST commands straight in without spawning the CLI or going through the WS relay. Loopback-only bind. The port (47200) lives in the IANA dynamic/private range (49152-65535-adjacent), squarely outside HD's 47083-47185 fallback range — clearly an Adom-family port, but no collision potential. Same `{app, command, args}` envelope as the WS protocol; same response payload (including `_hint` fields) as the CLI returns. Endpoints: `GET /health` (cheap probe), `GET /status` (capability inventory + `cliRequired` list), `POST /command` (verb dispatch). The dispatcher is `commands::handle_command` — exactly the same code the WS path uses. @@ -542,7 +544,7 @@ INFORMATIONAL — not a CLI verb. v1.8.25+ adds a local HTTP listener on `http:/ **Timeouts:** Direct API timeouts are now per-verb (matching the CLI dispatcher table): walk_cloud_tree + search_cloud_files = 620s; heavy exports = 320s; gerbers = 200s; drc/erc = 180s; bridge_install + fusion_start = 300s; default = 120s. Caller can override via `args.timeout` (seconds, clamped to 1800).  -#### `rdp`+### `rdp`  v1.8.85+: convenience wrapper around `tunnel` + `launch_rdp_client`. Opens a tunnel to the given RDP target then spawns mstsc.exe on the desktop pointed at the local end. One verb = RDP session established (the user still has to type credentials in mstsc). v1.8.86+: defaults local OFF 3389 (which collides with the user's own Windows RDP service on most laptops); spawns mstsc at the ACTUALLY-bound port when the requested port was in use. @@ -553,7 +555,7 @@ v1.8.85+: convenience wrapper around `tunnel` + `launch_rdp_client`. Opens a tun **Returns:** {ok, tunnelId, localPort, requestedLocal, fellBack, toHost, toPort, open, launch, _hint}. `localPort` is the actually-bound port (= what mstsc is connecting to). `fellBack:true` means the requested port was unavailable and AD picked ephemeral.  -#### `refresh_bridges`+### `refresh_bridges`  Force an immediate wiki sync of the bridge cache (kicad / puppeteer / fusion360 + any installed third-party bridges). v1.7.19+: bridges live as standalone wiki assets (decoupled from the Tauri installer ship cycle) and auto-update on launch + every 4 hours. This verb triggers the sync NOW. v1.8.144+: pass {"name":"<bridge>"} to update JUST that bridge; omit to sync all. Tip: run bridge_check_updates FIRST (read-only) to see what's actually updatable before downloading. @@ -565,7 +567,7 @@ Force an immediate wiki sync of the bridge cache (kicad / puppeteer / fusion360 **Note:** Updates the writable cache at %LOCALAPPDATA%\Adom Desktop\bridges-cache\<bridge>\. The bridge servers resolve their payload from there (cache-first, bundled-fallback). To FORCE the running bridge to pick up new cache content, follow with: taskkill /f /im python.exe (kicad+fusion) or taskkill /f /im node.exe (puppeteer). On next *_command call the bridge respawns with the new code.  -#### `register_container`+### `register_container`  v1.8.37+: register THIS cloud Docker container in the user's Adom Cloud profile so Adom Desktop on their Windows laptop auto-discovers + connects after sign-in. Runs entirely on the cloud Docker side — uses /var/run/adom/api-key (the Adom-cloud-mounted session token). PATCHes three carbon-prefs keys: desktop.container.<id> (full ServerEntry as JSON-string), desktop.last_slug, desktop.enabled=true. No relay/desktop dependency. The user's flow: run this verb from cloud Docker → install AD on Windows → sign in → container appears automatically. @@ -584,7 +586,7 @@ v1.8.37+: register THIS cloud Docker container in the user's Adom Cloud profile **See also:** Designed to work with Phase 1 (auth in AD), Phase 2 (cloud-config sync in AD), and Phase 4 (HD→AD session handoff if AD is bundled with Hydrogen Desktop). The full bundle landed across v1.8.33 - v1.8.37.  -#### `relaunch_self`+### `relaunch_self`  v1.8.103+: visible self-restart helper for dev iteration. Spawns the standalone ad-relauncher.exe (a small signed Rust app shipped in the NSIS bundle) which runs in HOST context and drives the redeploy + relaunch sequence: preflight → stop AD → deploy (atomic temp+rename) → launch (CreateProcessW DETACHED_PROCESS) → verify reachable (polls AD's ephemeral direct-api port from the discovery file, v1.8.118+) → verify version (running PE FileVersion matches source) → verify visible (EnumWindows for the new PID). A visible egui window shows per-step progress + a green HOST / red SANDBOX context banner so the sandbox trap is impossible to miss. Replaces all the prior ad-hoc shell glue — no taskkill, no `start`, no PowerShell, no `.bat`. AD itself goes down partway through Stop AD; the relauncher is detached and survives. Poll progress via `relauncher_status` — v1.8.118+ it returns the terminal outcome from a persisted file even after the relauncher's GUI auto-closes, so the poll loop always terminates. @@ -599,7 +601,7 @@ v1.8.103+: visible self-restart helper for dev iteration. Spawns the standalone **See also:** relauncher_status (poll progress), skills/AD_RELAUNCH.md (full recipe).  -#### `relauncher_status`+### `relauncher_status`  v1.8.103+: query the ad-relauncher's HTTP API and return its full state JSON. AD proxies GET http://127.0.0.1:47210/status. While AD is going down during Stop AD this call will fail with relauncherRunning:false — that's expected; once AD comes back up post-Launch the status appears again. Returns the relauncher's overall status, the current step, per-step progress + events, stopping/launching VersionInfo (PID, path, FileVersion, sha256, signature, window-visible), HOST/SANDBOX context, and the log tail. Poll every 1-2s. @@ -608,7 +610,7 @@ v1.8.103+: query the ad-relauncher's HTTP API and return its full state JSON. AD **See also:** relaunch_self (spawn), skills/AD_RELAUNCH.md (recipe).  -#### `server_add`+### `server_add`  Register a relay server connection programmatically — equivalent to the GUI's Quick Add paste-box + connect button, but driven by CLI. Upserts by `name`: if a server with the same name already exists, its URL (and optionally authToken) is updated rather than creating a duplicate. By default the connection is established immediately (autoConnect:true). If the URL is unchanged AND we're already connected, no churn — returns ok and leaves the live connection in place. Use this when an external app (e.g. Hydrogen Desktop's Docker container) needs to auto-register its relay at startup without the user pasting JSON. v1.8.22+. @@ -623,14 +625,14 @@ Register a relay server connection programmatically — equivalent to the GUI's **Note:** Persists to the same ~/.adom/config.json the GUI uses, so the entry survives a GUI restart. The ws_client supervisor (runs every 30s inside the GUI) auto-reconnects on next launch via the entry's auto_connect flag. To remove: use server_remove '{"name":"<the-name>"}'.  -#### `server_list`+### `server_list`  Return the current persisted server list with live connection status. Same data the GUI's server-list panel renders, exposed as JSON for scripts/external apps. v1.8.22+.  **Returns:** {ok:true, servers:[{name, url, id, autoConnect, enabled, connected:bool, clientCount:0|1, status:'connected'|'reconnecting'|'disconnected'}], count, _hint}. `connected` is the strict boolean (true only when the WS link is in the Connected state, not Reconnecting). `clientCount` is 0 or 1 per server (each adom-desktop contributes at most one client) — left in the schema for symmetry with the relay's own client-count terminology.  -#### `server_remove`+### `server_remove`  Disconnect (if connected) and delete a relay server entry by name. Inverse of server_add. Idempotent: removing a non-existent server returns ok with removed:false. v1.8.22+. @@ -640,7 +642,7 @@ Disconnect (if connected) and delete a relay server entry by name. Inverse of se **Returns:** {ok:true, removed:bool, wasConnected:bool, name, _hint}. `removed` is true iff an entry was actually deleted; false means no such server was registered.  -#### `shell_auto_approve`+### `shell_auto_approve`  Programmatically enable/revoke shell_execute auto-approve. v1.8.3+. v1.8.116+: `permanent` is the DURABLE user preference — it writes AD's Settings checkbox ('Auto-approve shell commands permanently', config-backed, default OFF), so it survives restarts by design. Timed grants (duration_secs) are session-scoped. In embedded mode HD owns this preference: pass `--shell-approval <prompt|1h|24h|permanent>` when spawning AD, and/or drive this verb at runtime; AD's own checkbox goes read-only ('Managed by Hydrogen Desktop'). @@ -657,7 +659,7 @@ Programmatically enable/revoke shell_execute auto-approve. v1.8.3+. v1.8.116+: `   - Revoke everything: `adom-desktop shell_auto_approve '{"duration_secs": 0}'`.  -#### `tunnel`+### `tunnel`  v1.8.85+: open a generic TCP port-forward tunnel from a desktop-side 127.0.0.1:<local> listener to a cloud-side `--to host:port` upstream, riding the existing AD↔relay WebSocket. First use case: RDP back into the laptop's user session via mstsc, but the path is identical for any TCP service (SSH, VNC, HTTP, postgres, ...). No new auth path — the WS is already authed; the tunnel is just bytes routed by tunnelId+connId on top of it. @@ -671,7 +673,7 @@ v1.8.85+: open a generic TCP port-forward tunnel from a desktop-side 127.0.0.1:< **See also:** rdp (one-shot RDP wrapper), tunnel_list, tunnel_close. For multi-connection tests, every fresh client TCP connection to 127.0.0.1:<local> gets its own connId routed independently.  -#### `tunnel_close`+### `tunnel_close`  v1.8.85+: tear down a tunnel (or one conn within a tunnel). Without `connId`, drops the listener + all conns. With `connId`, drops only that one TCP connection. @@ -682,32 +684,32 @@ v1.8.85+: tear down a tunnel (or one conn within a tunnel). Without `connId`, dr **Returns:** {ok, tunnelId, connId?, scope:'tunnel'|'conn', _hint}  -#### `tunnel_list`+### `tunnel_list`  v1.8.85+: list all active tunnels — both the relay's view and AD's view (should agree).  **Returns:** {ok, relay:[{tunnelId, toHost, toPort, clientId, connCount}, ...], desktop:{tunnels:[...]}, _hint}  -### Screen recording+## Screen recording  _Record the desktop/monitors to video; manage the recorder window._ (7 verbs) -#### `desktop_list_monitors`+### `desktop_list_monitors`  List available monitors. v1 returns the primary only; full enumeration is v1.1.  **Returns:** monitors: [{index, name, x, y, width, height, primary}]  -#### `desktop_record_list`+### `desktop_record_list`  List completed desktop recording .webm files on disk with their sidecar metadata.  **Returns:** recordings: [{recordingId, filePath, sizeKB, durationMs, reason, mtime}]  -#### `desktop_record_start`+### `desktop_record_start`  Start a WHOLE-DESKTOP recording (every pixel on screen, including unrelated apps) via Chrome MediaRecorder + getDisplayMedia. Opens HUD if needed. Reason is REQUIRED. HUD persists across multiple clips in one session — use desktop_recorder_close to dismiss. @@ -727,14 +729,14 @@ Start a WHOLE-DESKTOP recording (every pixel on screen, including unrelated apps **Error code:** desktop_record_needs_confirmation when confirmDesktopNotTabRecording is missing/false — read the error and decide if you really want full-desktop or pup-tab recording  -#### `desktop_record_status`+### `desktop_record_status`  Status of the desktop recorder HUD and any active clip.  **Returns:** hudOpen, reason, currentRecording, clipsThisSession  -#### `desktop_record_stop`+### `desktop_record_stop`  Stop the active desktop clip. Finalizes the .webm. HUD stays open in Ready state for the next clip in this session. @@ -744,14 +746,14 @@ Stop the active desktop clip. Finalizes the .webm. HUD stays open in Ready state **Returns:** recordingId, filePath, sizeKB, durationMs, stopReason  -#### `desktop_recorder_close`+### `desktop_recorder_close`  Close the desktop recorder HUD (ends the session). Auto-stops any active clip first.  **Returns:** sessionSummary: {clipCount, totalSizeKB, reason, clips}  -#### `desktop_recorder_open`+### `desktop_recorder_open`  Open the WHOLE-DESKTOP recorder HUD (visible bottom-right always-on-top window) without starting a clip. Reason is REQUIRED. Same scope as desktop_record_start — the HUD will record the entire desktop, NOT a pup tab. @@ -763,11 +765,11 @@ Open the WHOLE-DESKTOP recorder HUD (visible bottom-right always-on-top window) **Note:** PICK THE RIGHT VERB: for pup tab recording use browser_record_start (no HUD needed, tab-scoped). For generic 'record my screen' use Hydrogen's `recording start --share screen`. Use desktop_recorder_open only when you want a visible HUD on screen during a multi-clip session (e.g. parallel desktop video alongside Hydrogen tab capture).  -### Notifications+## Notifications  _Native desktop toast notifications._ (1 verbs) -#### `notify_user`+### `notify_user`  Send a Windows desktop toast notification @@ -778,18 +780,18 @@ Send a Windows desktop toast notification - `title` — required string  -### Project watch+## Project watch  _Watch a local folder + sync changes._ (1 verbs) -#### `args`+### `args`  -### Browser (Puppeteer)+## Browser (Puppeteer)  _Drive Chrome-for-Testing: navigate, eval, screenshot, record, tabs/windows, credentials._ (31 verbs) -#### `browser_alert_window`+### `browser_alert_window`  Flash the Chrome window's taskbar icon (does NOT steal foreground) @@ -797,7 +799,7 @@ Flash the Chrome window's taskbar icon (does NOT steal foreground) - `sessionId` — required  -#### `browser_close`+### `browser_close`  Close pup sessions. With sessionId: closes ONLY that session (Chrome window dies, others stay). Without sessionId: closes ALL sessions and Chromes (the bridge itself stays running). Use the sessionId form for targeted cleanup; use the no-arg form when you genuinely want a clean slate. @@ -807,7 +809,7 @@ Close pup sessions. With sessionId: closes ONLY that session (Chrome window dies **Note:** If you want the SESSION's tab closed but the Chrome window kept alive (other sessions on the same profile share Chrome), use browser_close_window or browser_close_tab instead.  -#### `browser_close_tab`+### `browser_close_tab`  Close one tab; session + other tabs remain @@ -818,7 +820,7 @@ Close one tab; session + other tabs remain **Returns:** remaining tab count  -#### `browser_close_window`+### `browser_close_window`  Close a session's Chrome window (closes ALL tabs in that session) @@ -826,7 +828,7 @@ Close a session's Chrome window (closes ALL tabs in that session) - `sessionId` — required  -#### `browser_errors`+### `browser_errors`  Get console errors and failed network requests. Optional tabId for per-tab errors. v1.4.10: a stale tabId now returns errorCode:"tab_not_found" instead of silently returning the active tab's errors. @@ -836,7 +838,7 @@ Get console errors and failed network requests. Optional tabId for per-tab error - `tabId` — optional  -#### `browser_eval`+### `browser_eval`  Evaluate JavaScript in page context. Pass optional tabId to target a specific tab; omit to use the active tab. v1.4.10: passing a stale tabId now returns errorCode:"tab_not_found" with currentTabs + lastKnownUrl (when the missing tab was a recently-closed popup) instead of silently evaluating against the active tab. @@ -846,7 +848,7 @@ Evaluate JavaScript in page context. Pass optional tabId to target a specific ta - `tabId` — optional  -#### `browser_fetch_url`+### `browser_fetch_url`  Fetch an arbitrary URL with the session's cookies and return the raw bytes. Bypasses Chrome's PDF Viewer wrapper — critical for grabbing PDF binaries from popup tabs where in-page fetch(location.href) returns the viewer HTML wrapper (~200 KB stub) instead of the actual PDF. Also handles ZIPs, CAD bundles, anything served as a binary content-type. @@ -865,7 +867,7 @@ Fetch an arbitrary URL with the session's cookies and return the raw bytes. Bypa **Note:** Use this when a click spawns a popup tab whose Content-Type is application/pdf — Chrome PDF Viewer wraps it and in-page fetch returns the wrapper HTML. browser_fetch_url re-issues the request through puppeteer's BrowserContext.request which doesn't go through the page render layer. RIGHT PATTERN with popup tabs: read the popup's url from browser_list_tabs (immediately — popups can auto-close), then browser_fetch_url with that URL. NEVER eval-against-popup-then-fetch (popup may have closed; eval would error with errorCode:tab_not_found and a hint pointing here).  -#### `browser_focus_window`+### `browser_focus_window`  Activate the session's tab WITHIN Chrome (Puppeteer page.bringToFront). Does NOT raise the OS window — for that, use browser_raise_os_window. @@ -873,7 +875,7 @@ Activate the session's tab WITHIN Chrome (Puppeteer page.bringToFront). Does NOT - `sessionId` — required  -#### `browser_input_dispatch`+### `browser_input_dispatch`  Dispatch TRUSTED input via Chromium's CDP-backed page.mouse / page.keyboard. Events have isTrusted=true (vs page-side dispatchEvent which is isTrusted=false and silently no-ops on framework gates / anti-automation checks). USE THIS instead of browser_eval-clicks when a click 'lands but does nothing' — most modern frameworks (React/Vue/Svelte) and vendor anti-automation gates check event.isTrusted before running handlers. @@ -897,7 +899,7 @@ Dispatch TRUSTED input via Chromium's CDP-backed page.mouse / page.keyboard. Eve **Note:** Common cases: vendor 'Generate' / 'Download' buttons that open popups (use type=click + selector); login forms (selector to focus + type=type for username/password + key=Enter); modal dismissal (key=Escape). After a click that should produce a popup tab, follow up with browser_list_tabs — popup tabs auto-track since v1.4.7 with opener="popup" + openerTabId. To grab bytes from a PDF popup (Chrome PDF viewer otherwise serves only the wrapper HTML on in-page fetch), use browser_fetch_url with the popup's URL. v1.4.10: when smart-pick lands on the wrong element, inspect the response's `modalRoot` field to see which DOM node won the modal-detection heuristic — useful for diagnosing toast-container false-positives.  -#### `browser_list_tabs`+### `browser_list_tabs`  List all tabs in a session, including auto-tracked popups. Tabs that the page itself opens via window.open() / target=_blank / form-submit-with-target=_blank are tracked automatically since v1.4.7 (Target.targetCreated listener) and appear here alongside Claude-opened tabs. @@ -907,12 +909,12 @@ List all tabs in a session, including auto-tracked popups. Tabs that the page it **Returns:** tabs: [{tabId, url, title, active, errorCount, opener, openerTabId}], activeTabId, count. opener="user" for tabs Claude opened (browser_open_window/browser_open_tab); opener="popup" for tabs the page spawned (openerTabId names the originating tab — e.g. a product page that spawned a datasheet popup).  -#### `browser_list_windows`+### `browser_list_windows`  List all open browser sessions with URLs and titles  -#### `browser_lower_os_window`+### `browser_lower_os_window`  Minimize the OS window hosting this pup session (give the user back their desktop after Claude is done with a flow). @@ -922,7 +924,7 @@ Minimize the OS window hosting this pup session (give the user back their deskto **Returns:** sessionId, hwnd, lowered, error?  -#### `browser_navigate`+### `browser_navigate`  Navigate to a new URL. Optional tabId to target a specific tab; omit to use the active tab. v1.4.10: passing a stale tabId now returns errorCode:"tab_not_found" with currentTabs + lastKnownUrl (when the missing tab was a recently-closed popup) instead of silently navigating the active tab. @@ -932,7 +934,7 @@ Navigate to a new URL. Optional tabId to target a specific tab; omit to use the - `url` — required  -#### `browser_open_tab`+### `browser_open_tab`  Add a tab to an existing session (same Chrome window as other tabs in that session — avoids new taskbar icon) @@ -943,7 +945,7 @@ Add a tab to an existing session (same Chrome window as other tabs in that sessi **Returns:** tabId, url, title, active  -#### `browser_open_window`+### `browser_open_window`  Open a Chrome window with a URL (auto-starts Puppeteer bridge). v1.6.3+: every session opens in FULL CAPABILITY MODE by default — JS-triggered downloads are allowed (Page.setDownloadBehavior=allow on every page + framenavigated), clipboard read/write is granted for paste-into-form flows, notifications + geolocation are DENIED so prompts don't pop up under automation. Pup is a tool-driving surface, not a user-facing browser; nearly every chip-fetcher-style flow needs these defaults. Pass strictPermissions:true to opt out (rare). @@ -960,7 +962,7 @@ Open a Chrome window with a URL (auto-starts Puppeteer bridge). v1.6.3+: every s **Note:** v1.6.3+ unblocks the silent-download-failure scenario. Before v1.6.3, JS-triggered downloads (clicks on `<a download>`, fetch+blob saves, vendor 'Download Symbol' buttons that do `window.location = url`) could be silently dropped by Chrome — no error, no UI, no file in ~/Downloads. The fix: applyDownloadBehavior() sets Page.setDownloadBehavior on every page + framenavigated. Combined with desktop_watch_files for the wait-for-arrival pattern, downloads now Just Work without any caller setup.  -#### `browser_raise_os_window`+### `browser_raise_os_window`  Raise the OS window hosting this pup session above all desktop apps (real foreground, defeats Windows foreground lock). Use BEFORE recording / screenshots / animations so Chrome doesn't throttle the page (document.hidden=true → ~1Hz rAF). Internally: focuses the tab, then EnumWindows-finds the Chrome window by title containing '(session: <sessionId>)' and runs the AttachThreadInput + keybd_event bypass. @@ -970,14 +972,14 @@ Raise the OS window hosting this pup session above all desktop apps (real foregr **Returns:** sessionId, hwnd, title, raised, error?  -#### `browser_record_list`+### `browser_record_list`  List completed tab recording .webm files on disk with sidecar metadata.  **Returns:** recordings: [{recordingId, filePath, sizeKB, durationMs, fps, sessionId, tabId, mtime}]  -#### `browser_record_start`+### `browser_record_start`  Record a pup tab via CDP Page.startScreencast + Page.screencastFrameAck (Chrome's native compositor pipeline). Tab-scoped at the protocol level — no cross-tab/cross-Chrome leakage. Output is a single finished VP9 .webm file (no tar bundle, no Docker-side mux). Real-time playback via VFR concat-mux. Concurrent: one recording per tab; many tabs OK. CALL browser_raise_os_window FIRST so Chrome doesn't paint-throttle the page (anti-throttle flags help when occluded but a foregrounded window is always best). @@ -991,7 +993,7 @@ Record a pup tab via CDP Page.startScreencast + Page.screencastFrameAck (Chrome' **Returns:** recordingId, sessionId, tabId, filePath (.webm), startedAt  -#### `browser_record_status`+### `browser_record_status`  List active tab recordings with live capture stats. @@ -1001,7 +1003,7 @@ List active tab recordings with live capture stats. **Returns:** active: [{recordingId, sessionId, tabId, filePath, fpsTarget, fpsActual, frameCount, durationMs, sizeBytesApprox, startedAt}]  -#### `browser_record_stop`+### `browser_record_stop`  Stop a tab recording. Mux runs synchronously on stop (ffmpeg concat demuxer with per-frame wall-clock durations). Returns a single finished .webm — pull_file directly, no extraction step. @@ -1014,7 +1016,7 @@ Stop a tab recording. Mux runs synchronously on stop (ffmpeg concat demuxer with **Note:** fpsActual reports the measured rate. If it falls below ~20 when target was 30, the pup window was occluded — call browser_raise_os_window before the next start, and verify the pup Chrome was launched with the anti-throttle flags (sessions opened before bridge v1.3.32 don't have them — close + reopen the session to refresh).  -#### `browser_reload`+### `browser_reload`  Reload page and clear error log. Optional tabId. v1.4.10: a stale tabId now returns errorCode:"tab_not_found" instead of silently reloading the active tab. @@ -1023,7 +1025,7 @@ Reload page and clear error log. Optional tabId. v1.4.10: a stale tabId now retu - `tabId` — optional  -#### `browser_rescan`+### `browser_rescan`  Recover orphaned Chrome windows whose CDP socket dropped (network blip, sleep/wake, puppeteer-side hiccup). Walks every known profile (in-memory + on-disk session files), reconnects via the persisted DevToolsActivePort, then walks every page and rebuilds session entries by parsing the (session: X) tag from each page's title. v1.5.1+ — paired with strict resolveSession (passing a known-but-disconnected sessionId now returns errorCode:"session_disconnected" instead of silently retargeting an unrelated window). @@ -1035,7 +1037,7 @@ Recover orphaned Chrome windows whose CDP socket dropped (network blip, sleep/wa **Note:** Idempotent — safe to call repeatedly. The 30s health check now also auto-attempts reconnect, so most transient blips recover before you notice. Use this manually when you suspect the bridge has lost a window (e.g. browser_navigate suddenly errors with errorCode:"session_disconnected").  -#### `browser_screenshot`+### `browser_screenshot`  Capture lossless PNG screenshot (auto-resized to <=1568px at the bridge, then capped to <=1500px in the CLI's defense-in-depth layer). Optional tabId for per-tab capture. Safe to Read into Claude — the two-layer resize is mandatory so the image always fits the many-image API limit. @@ -1048,7 +1050,7 @@ Capture lossless PNG screenshot (auto-resized to <=1568px at the bridge, then ca **Returns:** savedTo (file path), sizeKB, width (final on-disk px), height, origWidth (pre-resize, what Chrome captured), origHeight, resized (bool — true if the cap fired), tabId. v1.8.40+ adds the width/height/origWidth/origHeight/resized fields so the caller knows exactly what dims they got without having to probe the file.  -#### `browser_screenshot_full_res`+### `browser_screenshot_full_res`  Capture FULL RESOLUTION screenshot (NO resize). WARNING: Do NOT Read the resulting file in Claude — images >2000px crash many-image sessions at the Claude API limit. Use browser_screenshot for AI-readable screenshots. This verb is for saving to disk / wiki / external export only (e.g. handing a designer a high-DPI render). v1.8.40+. @@ -1062,12 +1064,12 @@ Capture FULL RESOLUTION screenshot (NO resize). WARNING: Do NOT Read the resulti **Note:** Implementation symmetry with browser_screenshot: same path resolution, same disk-save flow at the bridge. Only difference is the sharp resize+recompress step is skipped + the Rust-side save_screenshot's resize cap (defense-in-depth) is bypassed.  -#### `browser_status`+### `browser_status`  Check all sessions: URLs, error counts, alive status  -#### `browser_switch_tab`+### `browser_switch_tab`  Make a specific tab the active one (calls bringToFront within Chrome) @@ -1076,7 +1078,7 @@ Make a specific tab the active one (calls bringToFront within Chrome) - `tabId` — required  -#### `browser_switch_window`+### `browser_switch_window`  Switch the bridge's active session (which window commands without explicit sessionId target) @@ -1084,7 +1086,7 @@ Switch the bridge's active session (which window commands without explicit sessi - `sessionId` — required  -#### `browser_wait`+### `browser_wait`  Wait for content to settle @@ -1092,7 +1094,7 @@ Wait for content to settle - `ms` — optional int (default 3000)  -#### `credential_delete`+### `credential_delete`  Remove a credential entry by exact host pattern. Drops the password from the OS keychain and the entry from the index. @@ -1102,14 +1104,14 @@ Remove a credential entry by exact host pattern. Drops the password from the OS **Returns:** {ok, host, removedFromKeychain, removedFromIndex}  -#### `credential_list`+### `credential_list`  List stored host patterns + their associated usernames. Passwords are NEVER returned.  **Returns:** {ok, credentials: [{host, username, addedAt, updatedAt}]}  -#### `credential_set`+### `credential_set`  Store HTTP Basic Auth credentials for a host pattern. The password goes into the OS keychain (encrypted at rest by the user's session key); the index file holds host+username only. Subsequent pup navigations to a matching host auto-authenticate. @@ -1121,13 +1123,13 @@ Store HTTP Basic Auth credentials for a host pattern. The password goes into the **Returns:** {ok, host, username}  -### KiCad bridge+## KiCad bridge  _Drive KiCad 7/8/9/10: open designs, DRC/ERC, libraries, 3D, screenshots, lint._ (27 verbs)  > **Note:** All kicad_* commands are forwarded to the KiCad bridge (port 8772). Use kicad_<command> format. -#### `kicad_bridge_call`+### `kicad_bridge_call`  Generic passthrough: call any RPC method on the adom_bridge plugin running inside a specific KiCad exe. Picks the newest alive instance for that exe automatically (or use pid arg to pin to a specific PID). For export_svg / export_pdf use a larger timeout (default 10s; plot can take 5-15s). @@ -1143,7 +1145,7 @@ Generic passthrough: call any RPC method on the adom_bridge plugin running insid **Note:** Methods (v1.7.9): introspection — ping, list_frames, get_menu_ids, wm_command{frame_index,menu_id}, get_board_info / get_schematic_info / get_open_editors. PCB graph (pcbnew only) — get_pcb_footprints, get_pcb_layers, get_pcb_nets, get_pcb_design_rules, get_pcb_tracks (start/end/width/layer/netName), get_pcb_pads (per-footprint pads with position/size/drill/netName), get_pcb_vias (position/diameter/drill/viaType/top+bottom layer), get_pcb_drawings (silk text + edge cuts + dimensions), get_pcb_zones (copper pour inventory with priority/isFilled/layers/boundingBox), get_drc_markers (existing markers from last DRC run, grouped by severity), get_footprint_connections{ref} (net-graph: pin → netName → connected (ref,pin) list). Export (pcbnew only) — export_svg / export_pdf via in-process PLOT_CONTROLLER. Library nav — navigate_symbol{symbolName} / navigate_footprint{footprintName} (filter only — does NOT load). select_and_load_symbol{symbolName, useSendInput?:false} / select_and_load_footprint{footprintName, useSendInput?:false}: filter + Down+Enter keystroke. v0.7.0+ (Phase 2.ac) adds optional useSendInput:true that escalates from wx.PostEvent to real Win32 SendInput — more reliable but briefly steals foreground. v0.8.0+ (Phase 2.ad, bridge bundled in v1.7.17) hardens the foreground acquisition: AttachThreadInput dance + SendInput mouse-click at the search ctrl's screen center, with cursor pos snapshot/restore. Pre-v0.8.0 the naive SetForegroundWindow was frequently downgraded to a taskbar flash and SendInput keystrokes landed in the terminal instead of KiCad. Pair with verify_loaded{kind,expectedName?} to confirm load succeeded (reads frame title). DRC trigger — run_drc_inprocess (posts WM_COMMAND 20088 to PCB Editor; ~2-5s; then poll get_drc_markers). Length-matching — get_track_paths_by_net{netName}. Net graph — get_net_topology{netName} returns components on the net + their other-net bridges (1-hop graph).  -#### `kicad_bridge_status`+### `kicad_bridge_status`  Enumerate every running KiCad GUI process that has the adom_bridge.py reverse-bridge plugin loaded. v1.7.6+ supports multi-instance disambiguation: discovery files are per-PID (adom-kicad-bridge-<exe>-<pid>.json) so two pcbnew windows on two different boards each get their own row. Probes each /status to confirm live + returns the full inventory. @@ -1157,7 +1159,7 @@ Enumerate every running KiCad GUI process that has the adom_bridge.py reverse-br **Note:** Phase 2 reverse bridge (v1.7.x+). If summary.alive=0 but you expect a running KiCad, the plugin isn't deployed — call kicad_install_plugin to force-deploy. Stale discovery files (process exited mid-session) show alive=false; pass pruneStale:true to clean them up.  -#### `kicad_click`+### `kicad_click`  Click at coordinates in a KiCad window @@ -1170,32 +1172,32 @@ Click at coordinates in a KiCad window **Note:** Relative coords are scale-independent — estimate position as percentage from screenshot.  -#### `kicad_close`+### `kicad_close`  Close all KiCad windows  -#### `kicad_close_3d_viewer`+### `kicad_close_3d_viewer`  Close 3D Viewer window  -#### `kicad_close_footprint_editor`+### `kicad_close_footprint_editor`  Close Footprint Editor window  -#### `kicad_close_symbol_editor`+### `kicad_close_symbol_editor`  Close Symbol Editor window  -#### `kicad_fix_keyboard`+### `kicad_fix_keyboard`  Release stuck modifier keys + unlock foreground focus  -#### `kicad_format_upgrade`+### `kicad_format_upgrade`  Upgrade a KiCad file to the current format via kicad-cli. v1.7.13+. Wraps `kicad-cli {pcb,sch,sym,fp} upgrade`. MUTATES the file in place (kicad-cli has no --dry-run). Use kicad_lint_library/board/schematic first for inspection without write. @@ -1209,7 +1211,7 @@ Upgrade a KiCad file to the current format via kicad-cli. v1.7.13+. Wraps `kicad **Note:** Common use: lint_library reports fileFormatVersions includes old (pre-20231101) entries, surface that to the user, ask permission to upgrade, then call kicad_format_upgrade. After upgrade, ANY OPEN KiCad windows holding this file must be closed + reopened (they have stale in-memory state).  -#### `kicad_install_library`+### `kicad_install_library`  Install a symbol or footprint library @@ -1220,7 +1222,7 @@ Install a symbol or footprint library - `libraryType` — required  -#### `kicad_install_plugin`+### `kicad_install_plugin`  Explicitly (re)deploy the Phase 2 reverse-bridge plugin to every detected KiCad version's USER_SITE. Use when auto-install (which runs once per bridge boot) didn't fire or you need to force a fresh deploy after updating the plugin payload. @@ -1232,7 +1234,7 @@ Explicitly (re)deploy the Phase 2 reverse-bridge plugin to every detected KiCad **Note:** Idempotent — SHA256 hash compare skips a no-op deploy. If KiCad is currently running when this fires, the running processes still have the OLD plugin in memory until restart — surface result.runningKiCad to the user and tell them to close+reopen KiCad.  -#### `kicad_install_symbol`+### `kicad_install_symbol`  Decode base64 symbol, install as library, open editor @@ -1242,7 +1244,7 @@ Decode base64 symbol, install as library, open editor - `kicadVersion` — optional (default = newest installed)  -#### `kicad_lint_board`+### `kicad_lint_board`  Headless lint pre-flight for a .kicad_pcb via kicad-cli. v1.7.12+. Comprehensive: file-existence + magic-byte + DRC with --severity-all + --all-track-errors + --schematic-parity (default on). USE THIS BEFORE kicad_open_board for any file you didn't just generate — catches DRC errors, parse issues, format-upgrade-needed BEFORE the GUI opens and possibly hangs on a modal dialog. @@ -1255,7 +1257,7 @@ Headless lint pre-flight for a .kicad_pcb via kicad-cli. v1.7.12+. Comprehensive **Note:** Distinct from kicad_run_drc: lint_board adds file-format sanity + schematic-parity + an AI-friendly tiered _hint that EXPLICITLY tells you to proceed / fix / upgrade based on the result. For schematic + board cross-check during a full design pass: kicad_lint_schematic FIRST (catches ERC issues at the source), THEN kicad_lint_board (catches what made it to the board).  -#### `kicad_lint_library`+### `kicad_lint_library`  Validate a KiCad library file or directory before install. v1.7.13+. Accepts a single .kicad_sym, single .kicad_mod, .pretty directory, or directory containing .kicad_sym files. Parses S-expression structure (no kicad-cli call — sym/fp upgrade has no --dry-run and would rewrite files). USE BEFORE kicad_install_library / kicad_install_symbol / kicad_install_footprint for any file you didn't just generate. @@ -1267,7 +1269,7 @@ Validate a KiCad library file or directory before install. v1.7.13+. Accepts a s **Note:** Detects outdated file formats (pre-KiCad-7 versions <20231101) and suggests kicad_format_upgrade. Magic-byte sniff catches wrong-file-type (e.g. .kicad_pcb mis-named .kicad_sym). Failed-parse files surface as _error rows so you can pinpoint which file in a bulk install is broken.  -#### `kicad_lint_schematic`+### `kicad_lint_schematic`  Headless lint pre-flight for a .kicad_sch via kicad-cli. v1.7.12+. File-existence + magic-byte + ERC with --severity-all. USE THIS BEFORE kicad_open_schematic for any file you didn't just generate. @@ -1279,14 +1281,14 @@ Headless lint pre-flight for a .kicad_sch via kicad-cli. v1.7.12+. File-existenc **Note:** ERC catches schematic-only errors (unconnected pins, label mismatches, no-driver nets). For board-level validation use kicad_lint_board. Lint schematic FIRST so you fix issues at their source — DRC may surface them differently downstream.  -#### `kicad_list_versions`+### `kicad_list_versions`  List every installed KiCad version with paths. Returns versions:[{version, base_dir, kicad_exe, default}], default, count.  **Note:** Run this first if you're not sure which versions are installed. Pass the desired version as kicadVersion on any open_* command to override the default (newest).  -#### `kicad_open_3d_viewer`+### `kicad_open_3d_viewer`  Open KiCad 3D Viewer (board or footprint). @@ -1295,7 +1297,7 @@ Open KiCad 3D Viewer (board or footprint). - `kicadVersion` — optional (default = newest installed)  -#### `kicad_open_board`+### `kicad_open_board`  Open .kicad_pcb in PCB editor @@ -1304,7 +1306,7 @@ Open .kicad_pcb in PCB editor - `kicadVersion` — optional (e.g. "9.0" or "10.0"; default = newest installed)  -#### `kicad_open_editors`+### `kicad_open_editors`  Cross-process inventory of every open KiCad editor frame, sourced via the reverse bridge (NOT screen scraping). Hits each alive plugin's get_open_editors method and concatenates. Replaces the kicad_window_info screenshot-poll pattern for the common case. @@ -1313,7 +1315,7 @@ Cross-process inventory of every open KiCad editor frame, sourced via the revers **Note:** v1.7.6+. If totalEditors=0 but the user has KiCad open, the plugins likely aren't deployed or KiCad was launched before plugin install. Use kicad_bridge_status to confirm, kicad_install_plugin to deploy, then ask user to restart KiCad.  -#### `kicad_open_footprint_editor`+### `kicad_open_footprint_editor`  Open Footprint Editor @@ -1323,7 +1325,7 @@ Open Footprint Editor - `libraryName` — optional  -#### `kicad_open_schematic`+### `kicad_open_schematic`  Open .kicad_sch in schematic editor @@ -1332,7 +1334,7 @@ Open .kicad_sch in schematic editor - `kicadVersion` — optional (e.g. "9.0" or "10.0"; default = newest installed)  -#### `kicad_open_symbol_editor`+### `kicad_open_symbol_editor`  Open Symbol Editor @@ -1342,7 +1344,7 @@ Open Symbol Editor - `symbolName` — optional  -#### `kicad_run_drc`+### `kicad_run_drc`  Run Design Rule Check on a PCB via kicad-cli (headless, fast, structured JSON). Equivalent to right-clicking DRC in pcbnew but no GUI. Returns {data:{violations[], summary:{total,errors,warnings}}}. @@ -1353,7 +1355,7 @@ Run Design Rule Check on a PCB via kicad-cli (headless, fast, structured JSON). **Note:** v1.7.12+ ships kicad_lint_board with the same DRC + file-format checks + schematic-parity in one structured response — prefer it over kicad_run_drc for new code.  -#### `kicad_run_erc`+### `kicad_run_erc`  Run Electrical Rule Check on a .kicad_sch via kicad-cli. v1.7.12+. Mirrors kicad_run_drc but for schematics — catches unconnected pins, duplicate references, hierarchical-label mismatches, no-driver nets, etc. Headless, ~1-3s, structured JSON. @@ -1363,7 +1365,7 @@ Run Electrical Rule Check on a .kicad_sch via kicad-cli. v1.7.12+. Mirrors kicad **Returns:** {success, data:{violations[], summary:{total,errors,warnings,info}, fileFormat:{fileVersion}, source_file}, _hint guiding next steps}  -#### `kicad_screenshot_all`+### `kicad_screenshot_all`  Screenshot all KiCad windows in one shot @@ -1372,7 +1374,7 @@ Screenshot all KiCad windows in one shot **Note:** Images downscaled to max 1568px. Use relative coords (0.0-1.0) for clicking — they map to actual window size regardless of image scale.  -#### `kicad_send_key`+### `kicad_send_key`  Send keystroke to KiCad window. Supports modifier combos. @@ -1383,12 +1385,12 @@ Send keystroke to KiCad window. Supports modifier combos. **Note:** Menu hotkeys work via combos (e.g. "alt+3" = View>3D Viewer in pcbnew, "ctrl+s" = save). For dialogs prefer 'enter'/'escape'. Use click only if tab order doesn't reach the button.  -#### `kicad_window_info`+### `kicad_window_info`  List all KiCad windows (project manager, editors, dialogs) with HWNDs and rects  -### Fusion 360 bridge+## Fusion 360 bridge  _Drive Fusion 360: CAD export, cloud docs, EAGLE electronics, manufacturing, UI._ (63 verbs) @@ -1396,7 +1398,7 @@ _Drive Fusion 360: CAD export, cloud docs, EAGLE electronics, manufacturing, UI.  > **Workflow:** fusion_start -> fusion_open_cloud_file or fusion_open_board -> work -> fusion_close -#### `fusion_export_3mf`+### `fusion_export_3mf`  Export active design as 3MF @@ -1406,7 +1408,7 @@ Export active design as 3MF **Prerequisite:** Active 3D design  -#### `fusion_export_dwg`+### `fusion_export_dwg`  Export active design as DWG @@ -1418,7 +1420,7 @@ Export active design as DWG **Prerequisite:** Active 3D design  -#### `fusion_export_dxf`+### `fusion_export_dxf`  Export active design as DXF @@ -1430,7 +1432,7 @@ Export active design as DXF **Prerequisite:** Active 3D design  -#### `fusion_export_f3d`+### `fusion_export_f3d`  Export active design as Fusion 360 archive (.f3d) @@ -1442,7 +1444,7 @@ Export active design as Fusion 360 archive (.f3d) **Prerequisite:** Active 3D design  -#### `fusion_export_fbx`+### `fusion_export_fbx`  Export active design as FBX @@ -1454,7 +1456,7 @@ Export active design as FBX **Prerequisite:** Active 3D design  -#### `fusion_export_iges`+### `fusion_export_iges`  Export active design as IGES (.igs) @@ -1466,7 +1468,7 @@ Export active design as IGES (.igs) **Prerequisite:** Active 3D design  -#### `fusion_export_obj`+### `fusion_export_obj`  Export active design as OBJ @@ -1478,7 +1480,7 @@ Export active design as OBJ **Prerequisite:** Active 3D design  -#### `fusion_export_sat`+### `fusion_export_sat`  Export active design as SAT (.sat, ACIS solid model) @@ -1490,7 +1492,7 @@ Export active design as SAT (.sat, ACIS solid model) **Prerequisite:** Active 3D design  -#### `fusion_export_skp`+### `fusion_export_skp`  Export active design as SKP (SketchUp) @@ -1502,7 +1504,7 @@ Export active design as SKP (SketchUp) **Prerequisite:** Active 3D design  -#### `fusion_export_step`+### `fusion_export_step`  Export active design as STEP @@ -1512,7 +1514,7 @@ Export active design as STEP **Prerequisite:** Active 3D design (not Electronics)  -#### `fusion_export_stl`+### `fusion_export_stl`  Export active design as STL @@ -1522,7 +1524,7 @@ Export active design as STL **Prerequisite:** Active 3D design  -#### `fusion_export_usdz`+### `fusion_export_usdz`  Export active design as USDZ (best for digital twins + GLB) @@ -1534,19 +1536,19 @@ Export active design as USDZ (best for digital twins + GLB) **Prerequisite:** Active 3D design  -#### `fusion_get_design_info`+### `fusion_get_design_info`  Get active design metadata (bodies, components)  **Prerequisite:** Active 3D design  -#### `fusion_get_parameters`+### `fusion_get_parameters`  List user parameters from active design  -#### `fusion_import_step`+### `fusion_import_step`  Import STEP/STL/IGES/SAT/OBJ/F3D file into Fusion @@ -1554,7 +1556,7 @@ Import STEP/STL/IGES/SAT/OBJ/F3D file into Fusion - `filePath` — required Windows path  -#### `fusion_set_parameter`+### `fusion_set_parameter`  Modify a user parameter @@ -1563,7 +1565,7 @@ Modify a user parameter - `value` — required  -#### `fusion_take_screenshot`+### `fusion_take_screenshot`  Capture Fusion viewport as PNG with camera orientation control @@ -1576,7 +1578,7 @@ Capture Fusion viewport as PNG with camera orientation control **Note:** Uses Fusion's render API (Capture Image) — not a window screenshot. Supports all standard views for product images/icons.  -#### `fusion_check_recovery`+### `fusion_check_recovery`  Check if a cloud file has crash recovery data @@ -1588,7 +1590,7 @@ Check if a cloud file has crash recovery data **Note:** Check BEFORE opening files to avoid blocking recovery dialog  -#### `fusion_create_cloud_folder`+### `fusion_create_cloud_folder`  Create folder (idempotent) @@ -1598,7 +1600,7 @@ Create folder (idempotent) - `projectName` — optional  -#### `fusion_delete_cloud_file`+### `fusion_delete_cloud_file`  Delete a cloud file (must not be open) @@ -1608,7 +1610,7 @@ Delete a cloud file (must not be open) - `projectName` — optional  -#### `fusion_export_cloud_file`+### `fusion_export_cloud_file`  Export active document to local file (step/stl/f3d/iges/sat/smt) @@ -1617,7 +1619,7 @@ Export active document to local file (step/stl/f3d/iges/sat/smt) - `outputPath` — required  -#### `fusion_list_cloud_files`+### `fusion_list_cloud_files`  List files and subfolders in a cloud project/folder @@ -1626,12 +1628,12 @@ List files and subfolders in a cloud project/folder - `projectName` — optional  -#### `fusion_list_cloud_projects`+### `fusion_list_cloud_projects`  List all cloud projects in user's hub  -#### `fusion_open_cloud_file`+### `fusion_open_cloud_file`  Open a cloud file by name. STOPS if recovery exists — you must specify recovery='open' or recovery='discard' @@ -1642,7 +1644,7 @@ Open a cloud file by name. STOPS if recovery exists — you must specify recover - `recovery` — optional: open|discard  -#### `fusion_save_to_cloud`+### `fusion_save_to_cloud`  Save active document to Fusion cloud @@ -1653,7 +1655,7 @@ Save active document to Fusion cloud - `projectName` — optional  -#### `fusion_search_cloud_files`+### `fusion_search_cloud_files`  Search a cloud project / subfolder by file-name substring. Returns matches + a detailed scope analysis. Use this for targeted lookups; for exhaustive whole-cloud walks prefer fusion_walk_cloud_tree. @@ -1673,7 +1675,7 @@ Search a cloud project / subfolder by file-name substring. Returns matches + a d **Note:** All defenses (per-folder timeout, doEvents, per-file try/except, watchdog) make hard caps unnecessary. Defaults are conservative; raise maxFolders/maxDepth/maxResults for thorough searches.  -#### `fusion_walk_cloud_tree`+### `fusion_walk_cloud_tree`  Walk a cloud folder tree iteratively (BFS) and return a flat list of folders + files with full paths. @@ -1691,7 +1693,7 @@ Walk a cloud folder tree iteratively (BFS) and return a flat list of folders + f **Note:** Single add-in call does the entire walk. No sleeps, no batching. For targeted finds use nameContains; for exports use extensions.  -#### `fusion_board_info`+### `fusion_board_info`  Get structured board data: components, nets, traces, DRC violations @@ -1700,7 +1702,7 @@ Get structured board data: components, nets, traces, DRC violations **Prerequisite:** Board open in PCB Editor  -#### `fusion_electron_run`+### `fusion_electron_run`  Execute EAGLE command (WINDOW FIT, DISPLAY ALL, RATSNEST, DRC, etc.) @@ -1712,7 +1714,7 @@ Execute EAGLE command (WINDOW FIT, DISPLAY ALL, RATSNEST, DRC, etc.) **Note:** Fire-and-forget: never returns output. Use fusion_board_info for data. Full command reference with safe/blocking split: skills/eagle-commands.md (ships next to this CLI binary). Avoid blocking commands: WRITE, ADD, SHOW, MOVE, CHANGE, EDIT.  -#### `fusion_execute_text_command`+### `fusion_execute_text_command`  Low-level Fusion app.executeTextCommand() access @@ -1720,7 +1722,7 @@ Low-level Fusion app.executeTextCommand() access - `command` — required Fusion text command  -#### `fusion_export_eagle_source`+### `fusion_export_eagle_source`  Export active electronics document as plain EAGLE .sch or .brd XML — extracts from Fusion's .fsch/.fbrd ZIP container @@ -1732,7 +1734,7 @@ Export active electronics document as plain EAGLE .sch or .brd XML — extracts **Prerequisite:** MUST have correct editor active: PCB Editor / Board Layout for .brd, Schematic Editor for .sch  -#### `fusion_export_lbr`+### `fusion_export_lbr`  Export library as .scr script (no 3D refs) @@ -1740,7 +1742,7 @@ Export library as .scr script (no 3D refs) - `outputPath` — optional  -#### `fusion_export_source`+### `fusion_export_source`  Export active electronics document as .fsch (schematic), .fbrd (board), or .flbr (library) source file @@ -1752,7 +1754,7 @@ Export active electronics document as .fsch (schematic), .fbrd (board), or .flbr **Prerequisite:** MUST have correct editor active: Board Layout for .fbrd, Schematic Editor for .fsch  -#### `fusion_import_electronics`+### `fusion_import_electronics`  Import Fusion-native .fsch/.fbrd/.flbr file as new local electronics project @@ -1762,12 +1764,12 @@ Import Fusion-native .fsch/.fbrd/.flbr file as new local electronics project **Note:** Creates a NEW local document via Document.newDesignFromLocal. Use fusion_save_to_cloud to persist afterward. For legacy EAGLE files use fusion_open_schematic (.sch), fusion_open_board (.brd), or fusion_open_lbr (.lbr).  -#### `fusion_list_text_commands`+### `fusion_list_text_commands`  List available EAGLE text commands in current context  -#### `fusion_open_board`+### `fusion_open_board`  Open .brd in Board Layout editor @@ -1775,12 +1777,12 @@ Open .brd in Board Layout editor - `filePath` — required  -#### `fusion_open_electronics`+### `fusion_open_electronics`  Check if Electronics workspace is active  -#### `fusion_open_lbr`+### `fusion_open_lbr`  Open .lbr library in Electronics Library editor @@ -1788,7 +1790,7 @@ Open .lbr library in Electronics Library editor - `filePath` — required  -#### `fusion_open_schematic`+### `fusion_open_schematic`  Open .sch in Schematic Editor @@ -1796,21 +1798,21 @@ Open .sch in Schematic Editor - `filePath` — required  -#### `fusion_show_2d_board`+### `fusion_show_2d_board`  Switch back to 2D board layout from 3D view  **Note:** EAGLE commands only work in 2D view  -#### `fusion_show_3d_board`+### `fusion_show_3d_board`  Switch to 3D PCB view (board needs outline on layer 20)  **Prerequisite:** Board must be open in PCB Editor  -#### `fusion_activate_document`+### `fusion_activate_document`  Switch to an open document tab by name or type @@ -1819,12 +1821,12 @@ Switch to an open document tab by name or type - `name` — optional substring match  -#### `fusion_close`+### `fusion_close`  Close Fusion 360 gracefully  -#### `fusion_close_all_documents`+### `fusion_close_all_documents`  Close all open documents @@ -1834,7 +1836,7 @@ Close all open documents **Note:** Call before force-killing Fusion to prevent recovery files  -#### `fusion_close_document`+### `fusion_close_document`  Close a document without save dialog (avoids modal blocking) @@ -1843,7 +1845,7 @@ Close a document without save dialog (avoids modal blocking) - `save` — optional bool (default: false)  -#### `fusion_close_window`+### `fusion_close_window`  Close a specific Fusion dialog window by sending WM_CLOSE (equivalent to clicking its X button). Works on dialogs that Escape doesn't close — e.g. 'Recovered Documents'. @@ -1853,7 +1855,7 @@ Close a specific Fusion dialog window by sending WM_CLOSE (equivalent to clickin **Note:** Does NOT force-kill — the dialog can still prompt for confirmation. Use when fusion_dismiss_blocking_dialogs reports remaining dialogs that Escape couldn't clear.  -#### `fusion_dismiss_blocking_dialogs`+### `fusion_dismiss_blocking_dialogs`  First-class blocking-dialog killer. Enumerates all Fusion Qt windows, matches them against a known list of blocking/picker patterns ('What do you want to design?', error toasts, recovery prompts, etc.), and sends Escape to each offending HWND until the add-in main thread reports responsive. Whenever any fusion_* command returns errorCode 'fusion_addin_not_responding', call this FIRST before retrying. @@ -1866,21 +1868,21 @@ First-class blocking-dialog killer. Enumerates all Fusion Qt windows, matches th **Note:** Idempotent. Safe to call any time. Returns immediately with empty dismissed[] when no blocking dialogs are present.  -#### `fusion_document_info`+### `fusion_document_info`  List all open document tabs with cloud metadata for active doc  **Returns:** openDocuments[], activeDoc cloud details (project, folder, version)  -#### `fusion_get_app_state`+### `fusion_get_app_state`  Query open documents, active workspace, Electronics status  **Returns:** workspace, openDocuments[], electronicsActive  -#### `fusion_start`+### `fusion_start`  First-class Fusion 360 startup. Discovers Fusion360.exe via glob (so version-indirected webdeploy hashes don't matter), verifies it exists BEFORE spawning (no 'Windows cannot find' GUI dialogs), launches it if not already running, waits for the AdomBridge add-in to come online, and auto-dismisses any blocking dialogs via fusion_dismiss_blocking_dialogs. Returns when Fusion is fully ready. @@ -1893,7 +1895,7 @@ First-class Fusion 360 startup. Discovers Fusion360.exe via glob (so version-ind **Note:** ALWAYS use this instead of launching Fusion by hand. It's idempotent — safe to call when Fusion is already running. Process-detection gate means it will NEVER trigger Autodesk's 'Multiple instances are not supported' dialog.  -#### `fusion_detect_layers`+### `fusion_detect_layers`  Auto-detect PCB copper layer count (2 or 4) using ULP + CAM comparison @@ -1902,7 +1904,7 @@ Auto-detect PCB copper layer count (2 or 4) using ULP + CAM comparison **Note:** Run first — informs gerber CAM job selection and design rule choice  -#### `fusion_export_board_image`+### `fusion_export_board_image`  Export PNG image with layer presets (top_copper, assembly_top, fabrication, etc.) @@ -1917,7 +1919,7 @@ Export PNG image with layer presets (top_copper, assembly_top, fabrication, etc. **Returns:** outputPath, dpi, preset, fileSize, nextSteps[]  -#### `fusion_export_bom`+### `fusion_export_bom`  Export Bill of Materials as CSV (grouped by value+package) @@ -1930,7 +1932,7 @@ Export Bill of Materials as CSV (grouped by value+package) **Note:** Format: Comment, Designator, Footprint, Quantity, Library  -#### `fusion_export_cpl`+### `fusion_export_cpl`  Export Component Placement List (pick-and-place) as CSV @@ -1943,7 +1945,7 @@ Export Component Placement List (pick-and-place) as CSV **Note:** Format: Designator, Mid X, Mid Y, Layer, Rotation (mm). Compatible with JLCPCB/PCBWay.  -#### `fusion_export_gerbers`+### `fusion_export_gerbers`  Export Gerber RS-274X + Excellon drill files as ZIP (auto-detects layers, selects CAM job) @@ -1957,7 +1959,7 @@ Export Gerber RS-274X + Excellon drill files as ZIP (auto-detects layers, select **Note:** ZIP contains GTL, GBL, GTS, GBS, GTP, GBP, GTO, GBO, GKO, XLN. Ready for fab upload.  -#### `fusion_set_design_rules`+### `fusion_set_design_rules`  Apply Adom/JLCPCB design rules (.edru files) — auto-detects 2/4 layer @@ -1971,21 +1973,21 @@ Apply Adom/JLCPCB design rules (.edru files) — auto-detects 2/4 layer **Note:** Run DRC after applying rules to check for violations  -#### `fusion_dismiss_recovery`+### `fusion_dismiss_recovery`  Dismiss recovery dialogs + relocate recovery files to ~/.adom/recovery/  **Note:** Handles both 'Recovered Documents' list and 'Open recovery instead?' prompts  -#### `fusion_relocate_recovery`+### `fusion_relocate_recovery`  Move crash recovery files to ~/.adom/recovery/fusion/ (call before launch to prevent dialogs)  **Note:** Files preserved, not deleted. User can manually restore if needed.  -#### `fusion_click_fusion`+### `fusion_click_fusion`  Click at coordinates in Fusion window or a specific dialog @@ -1998,14 +2000,14 @@ Click at coordinates in Fusion window or a specific dialog **Note:** Relative coords (0.0-1.0) are scale-independent — estimate position as percentage from screenshot. Prefer fusion_send_key for dialogs (enter/escape/tab covers most cases).  -#### `fusion_screenshot_all`+### `fusion_screenshot_all`  Screenshot main window + list all dialog windows with HWNDs  **Note:** Use fusion_screenshot_fusion with dialog HWND, or desktop_screenshot_window  -#### `fusion_screenshot_fusion`+### `fusion_screenshot_fusion`  Capture Fusion main window or a specific dialog as PNG @@ -2017,7 +2019,7 @@ Capture Fusion main window or a specific dialog as PNG **Note:** Image is downscaled to max 1568px. Use relative coords (0.0-1.0) for clicking — they map to actual window size regardless of image scale.  -#### `fusion_send_key`+### `fusion_send_key`  Send keyboard input to Fusion or a specific dialog @@ -2028,7 +2030,7 @@ Send keyboard input to Fusion or a specific dialog **Note:** 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.  -#### `fusion_window_info`+### `fusion_window_info`  Get main window HWND, title, rect, and all Qt dialog windows @@ -2037,13 +2039,13 @@ Get main window HWND, title, rect, and all Qt dialog windows **Note:** Essential for detecting blocking dialogs after operations  -### Hydrogen Desktop+## Hydrogen Desktop  _Build, ship, screenshot, and drive the embedded Hydrogen Desktop._ (22 verbs)  > **Note:** v1.8.15+: hd_* verbs proxy to Hydrogen Desktop's local API on 127.0.0.1:9001. HD is a sibling Tauri v2 app that runs alongside adom-desktop on the user's machine. These are built-in proxy handlers (NOT a separate bridge process) — when HD isn't running, hd_status returns {ok:false, error:'HD not running'} cleanly instead of crashing. -#### `hd_api`+### `hd_api`  Generic HTTP pass-through to HD's control API. The canonical way to call ANY HD endpoint from a script/AI without shelling out to curl (which the relay mangles). Auto-discovers HD's control port from %APPDATA%/hydrogen-desktop/ports.json (key 'control'); falls back to 47084. Public-build denylist refuses destructive paths (e.g. /wsl/unregister, /setup/virgin-reset) — use the hd-dev internal build for those. @@ -2058,14 +2060,14 @@ Generic HTTP pass-through to HD's control API. The canonical way to call ANY HD **Example:** `adom-desktop hd_api '{"method":"POST","path":"/setup/run-step","body":{"id":"welcome"}}'`  -#### `hd_browser_profiles`+### `hd_browser_profiles`  Enumerate browsers + profiles HD detects on the user's machine (GET /browser-profiles). Use the returned profileDir values when calling hd_open_url.  **Returns:** HD's /browser-profiles response — typically {browsers:[{name, profiles:[{dir, name, gaia?}, ...]}]}  -#### `hd_build`+### `hd_build`  Full HD rebuild (async): git pull → pnpm build → cargo build (debug). Returns immediately with {pid, logPath}; the build runs detached so the relay's 30s HTTP timeout doesn't kill it. Each step appends to %TEMP%\hd-build.log (overwritten at start); the script writes BUILD_OK or BUILD_FAILED:<reason> as its final line so callers can detect completion without scraping tool-specific output. v1.8.16+. @@ -2078,7 +2080,7 @@ Full HD rebuild (async): git pull → pnpm build → cargo build (debug). Return **Note:** DO NOT call hd_launch until hd_build_status shows succeeded=true. Poll hd_build_status or stream output with hd_build_tail {offset:N}. Build PID is written to %TEMP%\hd-build.pid by the PowerShell script itself so it's authoritative regardless of the wrapper process tree. v1.8.23+: visible build windows now auto-close after lingerSecs (default 30) so build-heavy sessions don't accumulate orphan consoles.  -#### `hd_build_frontend`+### `hd_build_frontend`  Frontend-only HD build (async): `pnpm build` from the repo root. Same log/PID/sentinel conventions as hd_build. Use when only Svelte/TypeScript changed and the Rust binary is unchanged — much faster than a full build. @@ -2091,14 +2093,14 @@ Frontend-only HD build (async): `pnpm build` from the repo root. Same log/PID/se **Note:** Skip when the Rust src-tauri/ side changed — those need hd_build or hd_build_rust to rebuild the binary. v1.8.57+ adds a global build lock — two concurrent hd_build_*/hd_ship calls cannot race.  -#### `hd_build_log`+### `hd_build_log`  Return the FULL build log from %TEMP%\hd-build.log. Use this after hd_build_status reports failed to read the complete output and find the error line.  **Returns:** {ok, log:'<full text>', lines, logPath, _hint}  -#### `hd_build_rust`+### `hd_build_rust`  Rust-only HD build (async): `cargo build` from src-tauri/. Same log/PID/sentinel conventions as hd_build. Use when only Rust code changed (no frontend changes). v1.8.52+: dispatch response also carries builtSha + exeMtimeBefore + exeSizeBefore (captured at spawn time into %TEMP%\hd-build-meta.json) so the subsequent hd_build_status / hd_launch don't need a separate git rev-parse + stat call. @@ -2111,7 +2113,7 @@ Rust-only HD build (async): `cargo build` from src-tauri/. Same log/PID/sentinel **Note:** Builds the debug binary at target/debug/hydrogen-desktop.exe. The frontend's build/ output is NOT regenerated — re-use whatever was last built by hd_build / hd_build_frontend.  -#### `hd_build_status`+### `hd_build_status`  Synchronous build-state probe. v1.8.52+ enriched: returns compiledCrates (parsed from 'Compiling X' lines so you KNOW your crate recompiled vs cargo found nothing to do — eliminates the most common stale-relink trap), relinkOnly (true when succeeded but compiledCrates is empty), exeMtimeBefore/After + exeSizeBefore/After + exeChanged (proves the binary actually got rewritten), exeLocked + holderPids (when cargo failed because hydrogen-desktop.exe is held by another process — most often a still-running HD instance), builtSha (the SHA hd_build_rust / hd_build was dispatched at — compare to /buildinfo.build_sha after launch), elapsedSecs (from dispatch to now). State machine: no log → idle; log present without sentinel + PID alive → building; log ends with BUILD_OK → succeeded; log ends with BUILD_FAILED → failed; log present without sentinel + PID dead → treated as failed (orphaned). v1.8.16+. @@ -2120,7 +2122,7 @@ Synchronous build-state probe. v1.8.52+ enriched: returns compiledCrates (parsed **Note:** **relinkOnly:true on success is the #1 trap to look for** — your push didn't actually change the binary because cargo found nothing to recompile. Usually means your source changes weren't pulled, or you changed a file the workspace doesn't track. Use hd_ship with clean:true to force a fresh build. **exeLocked:true on failure** plus holderPids tells you exactly which PID to taskkill; or just use hd_ship which kills HD before building.  -#### `hd_build_tail`+### `hd_build_tail`  Streaming tail of the build log. Pass offset=0 on first call; the response includes newOffset which you pass back on the next call to get just the new bytes. Lines are split on \n; a partial trailing line (no newline yet) is held back and re-included on the next call. Use this to follow a running build like `tail -f` without re-fetching the whole log every poll. v1.8.16+. @@ -2132,7 +2134,7 @@ Streaming tail of the build log. Pass offset=0 on first call; the response inclu **Note:** done=true means BUILD_OK or BUILD_FAILED has appeared in the log — stop polling. done=false means more output expected; sleep 2-3s and call again with newOffset.  -#### `hd_container_exec`+### `hd_container_exec`  [MOVED to hd-dev in v1.8.72] Run a command inside HD's Docker container. Public Adom Desktop refuses with verb_moved_to_hd_dev. Workaround: hd_api '{"method":"POST","path":"/container-exec","body":{"command":"..."}}'. @@ -2142,7 +2144,7 @@ Streaming tail of the build log. Pass offset=0 on first call; the response inclu **Returns:** Public build: refusal payload. hd-dev internal build: HD's /container-exec response.  -#### `hd_eval`+### `hd_eval`  [MOVED to hd-dev in v1.8.72] Evaluate JS in HD's main webview. Public Adom Desktop returns {error:'verb_moved_to_hd_dev'} with a _hint pointing at the internal build. Workaround: hd_api '{"method":"POST","path":"/eval","body":{"js":"..."}}'. @@ -2152,7 +2154,7 @@ Streaming tail of the build log. Pass offset=0 on first call; the response inclu **Returns:** Public build: refusal payload. hd-dev internal build: HD's /eval response.  -#### `hd_iframe_eval`+### `hd_iframe_eval`  [MOVED to hd-dev in v1.8.72] Evaluate JS inside HD's code-server iframe via CDP. Public Adom Desktop refuses with verb_moved_to_hd_dev. Workaround: hd_api '{"method":"POST","path":"/iframe-eval","body":{"js":"..."}}'. @@ -2163,7 +2165,7 @@ Streaming tail of the build log. Pass offset=0 on first call; the response inclu **Returns:** Public build: refusal payload. hd-dev internal build: HD's /iframe-eval response.  -#### `hd_launch`+### `hd_launch`  Start HD's debug binary detached. v1.8.52+ returns structured launch state so you NEVER mistake 'attached to a stale running process' for 'launched fresh code'. Three guards refuse the launch: (1) build_in_progress — a hd_build/hd_build_frontend/hd_build_rust is still running; (2) build_failed — last build ended with BUILD_FAILED; (3) already_running — hydrogen-desktop.exe is in the process list. The already_running guard can be overridden with `killExisting:true` (taskkills the existing instance then spawns fresh; killedFirst=true in the response). On success, sleeps briefly then reports HD's actual PID (looked up via tasklist) since the cmd-wrapper PID is gone immediately. v1.8.16+. @@ -2175,7 +2177,7 @@ Start HD's debug binary detached. v1.8.52+ returns structured launch state so yo **Note:** **The launched + wasAlreadyRunning + killedFirst combination is the proof you need** — `launched:true, wasAlreadyRunning:false, killedFirst:false` means truly fresh code is running; any other combination requires you to think about whether your changes actually took effect. builtSha is included so you can immediately compare to /buildinfo.build_sha without a separate call.  -#### `hd_log`+### `hd_log`  Read the tail of HD's log file at %APPDATA%\hydrogen-desktop\hydrogen-desktop.log. Doesn't require HD to be running — reads the file directly from disk. Use this when HD seems stuck or has crashed to see what it said last. @@ -2185,7 +2187,7 @@ Read the tail of HD's log file at %APPDATA%\hydrogen-desktop\hydrogen-desktop.lo **Returns:** {ok, lines:[...], path, totalLines, returnedLines}  -#### `hd_open_url`+### `hd_open_url`  Open a URL in a specific browser profile via HD's /open-in-profile endpoint. HD knows which browsers + profiles are installed (see hd_browser_profiles) and dispatches to the right one with the right --profile-directory / -P flag. @@ -2197,14 +2199,14 @@ Open a URL in a specific browser profile via HD's /open-in-profile endpoint. HD **Returns:** HD's /open-in-profile response (typically {ok, pid, profile})  -#### `hd_reload_vscode`+### `hd_reload_vscode`  [MOVED to hd-dev in v1.8.72] Reload the VS Code iframe inside HD. Public Adom Desktop refuses with verb_moved_to_hd_dev. Workaround: hd_api '{"method":"POST","path":"/reload-vscode"}' (allowed by the public denylist).  **Returns:** Public build: refusal payload. hd-dev internal build: HD's /reload-vscode response.  -#### `hd_restart`+### `hd_restart`  Stop + launch in one call. Same guards as hd_launch (build_in_progress / build_failed / binary_missing all refuse). If HD is running it's taskkilled with a 600ms grace period before relaunch to release the exe lock. v1.8.16+. @@ -2213,7 +2215,7 @@ Stop + launch in one call. Same guards as hd_launch (build_in_progress / build_f **Note:** Use after `hd_build_status` shows succeeded=true to pick up the new binary. Don't call when build is in progress — the launch will fail.  -#### `hd_screenshot`+### `hd_screenshot`  Capture the Hydrogen Desktop window as a lossless PNG. Finds the window by title ('Hydrogen Desktop') via the same Win32 enum the desktop bridge uses, then captures via PrintWindow. @@ -2222,7 +2224,7 @@ Capture the Hydrogen Desktop window as a lossless PNG. Finds the window by title **Note:** Requires HD to be running AND its window to be visible (PrintWindow can't capture a hidden window). If HD isn't found, returns 'Hydrogen Desktop window not found' — verify HD is up via hd_status.  -#### `hd_send_files`+### `hd_send_files`  Send local files DIRECTLY into Hydrogen Desktop's WSL2 project. Relays the bytes to HD's control API, which writes them (as adom) into <project>/<subdir> (default 'downloads', created on first use). Lands in the WSL2 ext4 filesystem so the AI sees them IMMEDIATELY — avoids the plain send_files path (→ Windows Downloads → /mnt/c mount) whose ~5s WSL2 listing-cache makes freshly-dropped files look missing. HD owns the write (it knows its container/bind-mount layout + runs as adom), so ownership + visibility are correct. Requires HD running (hd_launch) and an HD build exposing POST /files/inject. @@ -2235,7 +2237,7 @@ Send local files DIRECTLY into Hydrogen Desktop's WSL2 project. Relays the bytes **Note:** Use this instead of send_files whenever the destination is HD's WSL2 project (it's the cure for the /mnt/c cache-lag 'file isn't there' confusion).  -#### `hd_ship`+### `hd_ship`  v1.8.49+: atomic 'rebuild from the exact SHA I just pushed + relaunch + verify it's actually running' sequence. v1.8.57+ adds three guardrails BEFORE doing destructive work: (a) expectSha pre-validation — `git fetch origin main` then `git cat-file -t <sha>` must be `commit` and `git merge-base --is-ancestor <sha> origin/main` must succeed; (b) global build lock at `%TEMP%\hd-build.lock` — refuses immediately if another hd_build_*/hd_ship is in flight (no queue); (c) single-flight lifecycle mutex — refuses if hd_launch/hd_stop/hd_restart/hd_ship is already running. Pipeline after guards pass: (1) taskkill HD + poll until zero (frees exe lock); (2) git fetch + reset --hard origin/main + assert HEAD == expectSha; (3) optional cargo clean; (4) cargo build; (5) launch + poll http://127.0.0.1:47084/buildinfo until SHA matches. @@ -2248,7 +2250,7 @@ v1.8.49+: atomic 'rebuild from the exact SHA I just pushed + relaunch + verify i **Note:** Long-running: cold cargo build can take 2-3 min. CLI WS timeout is 12 min — call as a single sync invocation and wait. v1.8.57+: arg validation, SHA validation, and lock acquisition all happen BEFORE any destructive action — if you get `refused:true` at the top, the running HD was not touched. `compiled:false` on success means cargo found nothing to recompile (either cached or HEAD didn't actually change). The verify step requires HD to expose http://127.0.0.1:47084/buildinfo returning JSON with `build_sha` (or `buildSha` or `sha`).  -#### `hd_shot`+### `hd_shot`  v1.8.54+: Capture a named region of Hydrogen Desktop, produce BOTH a full-res PNG and a Claude-safe-resized copy, and return rich metadata so the AI never has to chain screenshot→pull→convert→crop→resize→Read manually. Regions are deterministic (no AI-supplied pixel coords) — they resolve via HD window bounds + CDP element rects queried through HD's /eval endpoint. Each shot is self-identifying: it includes the running build's SHA + local build time read from HD's /buildinfo so you can confirm you're looking at the build you expect before trusting what you see. v1.8.61+ universal screenshot delivery contract: the CLI auto-pulls BOTH PNGs to /tmp/ad-shots/<basename>, verifies each landed + is non-empty, and enriches the response with `localFullPath` + `localSafePath` + `localFullExists` + `localSafeExists` + `fullBytes` + `safeBytes` + `source:'hd_shot'` + `narrative` telling you which path to Read for analysis (the safe one). @@ -2262,7 +2264,7 @@ v1.8.54+: Capture a named region of Hydrogen Desktop, produce BOTH a full-res PN **Note:** Always pull_file the SAFE path and Read THAT — it's already sized for Claude's image analysis. The FULL path is meant for shotlog / disk archival, NOT direct Read (may exceed Claude's per-image budget). **regionFound:false** when the element rect wasn't in HD's DOM — the panel/iframe may not be mounted yet, OR HD's markup changed and region_selectors() in hd_bridge.rs is out of date. Either way, the shot is still produced (falls back to the full window) so you have something to look at. **runningBuildSha** can be null if HD's /buildinfo isn't exposed yet — same SHA HD's launch path uses, so you can compare to hd_launch's builtSha. All child spawns use CREATE_NO_WINDOW (no cmd flashes). v1.8.54+.  -#### `hd_status`+### `hd_status`  v1.8.58+ COMPOSED + RELAYED snapshot of HD's state; v1.8.129+ churn-honest. AD owns host/process/repo facts; HD owns runtime state (relayed verbatim from HD's control API, port discovered from %APPDATA%/hydrogen-desktop/ports.json key 'control', fallback 47084 — NOT a hardcoded port). v1.8.129 reports THREE orthogonal signals instead of one boolean so you can reason about HD's constant build-relaunch churn: processPresent (hydrogen-desktop.exe exists, sampled with retry to ride the ~1s kill→respawn gap), controlApiReachable (control port actually listening), and embedded (AD's OWN mode — answers 'is AD embedded under HD?'). `running` = processPresent||controlApiReachable. @@ -2271,7 +2273,7 @@ v1.8.58+ COMPOSED + RELAYED snapshot of HD's state; v1.8.129+ churn-honest. AD o **Note:** v1.8.129 fixes the misleading 'HD is NOT running' that fired when a single tasklist snapshot landed mid-relaunch. If you see running:true + controlApiReachable:false, HD is mid-build/relaunch — retry in a few seconds, don't treat it as down. Use this as the entry point for 'what's HD's state?' — it tells you which downstream verb to run next (hd_ship, hd_launch, hd_shot, etc.).  -#### `hd_stop`+### `hd_stop`  Kill hydrogen-desktop.exe. `wasRunning` distinguishes 'killed it' from 'nothing to do'. v1.8.16+.