app
Adom Bridge
Public Made by Adomby adom
Adom Bridge unleashes your AI onto your full PC: full power, full safety. The AI breaks out of the container it runs in and onto your real machine, managing and running your entire laptop as you: launch apps, move files, drive any window, control KiCad, Fusion 360 or your real signed-in browser. Works with any AI, cloud or local (Claude Desktop, Claude Code, Codex), no Hydrogen required. Bridge and its bridges are an operating system for AI, with managed Node and Python runtimes, and a human-onl
← Commit history
README.md+227−225@@ -33,6 +33,10 @@ Driving KiCad end-to-end from the container — symbol, footprint, 3D model, sch  +Driving real **Chrome-for-Testing** windows from the container — TI, ST, Mouser, Adafruit, SparkFun, Espressif, every one scriptable (navigate, eval, screenshot, record):+++ 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) -- ## 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+.