← Commit history

verb reference: mirror inline catalog to README.md

John Lauer ·82db062eb2 · ·parent a5c0f0b
1 file changed +210−210
README.md+210−210
@@ -69,21 +69,21 @@ Every verb and subcommand `adom-desktop` supports — **210 across 12 categories  _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) @@ -95,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. @@ -115,7 +115,7 @@ v1.8.133+: list the Adom Desktops connected to this relay so you can address one  _One-time desktop setup._ (1 verbs) -### `setup_desktop`+### setup_desktop  Generate installer URL and server config JSON for first-time desktop app setup @@ -126,7 +126,7 @@ Generate installer URL and server config JSON for first-time desktop app setup  _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. @@ -137,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. @@ -155,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. @@ -170,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. @@ -192,7 +192,7 @@ v1.8.46+: Execute a base64-encoded script inside a WSL distro via the canonical  _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. @@ -205,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 @@ -219,7 +219,7 @@ Send files from Docker container to the desktop  _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. @@ -228,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+. @@ -240,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. @@ -251,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. @@ -261,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. @@ -271,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. @@ -281,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 @@ -291,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. @@ -312,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. @@ -335,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. @@ -344,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. @@ -360,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. @@ -369,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. @@ -378,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. @@ -392,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 @@ -412,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. @@ -430,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. @@ -448,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. @@ -471,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 @@ -483,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. @@ -494,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. @@ -517,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. @@ -544,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. @@ -555,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. @@ -567,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. @@ -586,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. @@ -601,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. @@ -610,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+. @@ -625,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+. @@ -642,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'). @@ -659,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. @@ -673,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. @@ -684,7 +684,7 @@ 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). @@ -695,21 +695,21 @@ v1.8.85+: list all active tunnels — both the relay's view and AD's view (shoul  _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. @@ -729,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. @@ -746,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. @@ -769,7 +769,7 @@ Open the WHOLE-DESKTOP recorder HUD (visible bottom-right always-on-top window)  _Native desktop toast notifications._ (1 verbs) -### `notify_user`+### notify_user  Send a Windows desktop toast notification @@ -784,14 +784,14 @@ Send a Windows desktop toast notification  _Watch a local folder + sync changes._ (1 verbs) -### `args`+### args   ## 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) @@ -799,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. @@ -809,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 @@ -820,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) @@ -828,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. @@ -838,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. @@ -848,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. @@ -867,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. @@ -875,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. @@ -899,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. @@ -909,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). @@ -924,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. @@ -934,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) @@ -945,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). @@ -962,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. @@ -972,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). @@ -993,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. @@ -1003,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. @@ -1016,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. @@ -1025,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). @@ -1037,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. @@ -1050,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+. @@ -1064,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) @@ -1078,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) @@ -1086,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 @@ -1094,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. @@ -1104,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. @@ -1129,7 +1129,7 @@ _Drive KiCad 7/8/9/10: open designs, DRC/ERC, libraries, 3D, screenshots, lint._  > **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). @@ -1145,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. @@ -1159,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 @@ -1172,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. @@ -1211,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 @@ -1222,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. @@ -1234,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 @@ -1244,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. @@ -1257,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. @@ -1269,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. @@ -1281,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). @@ -1297,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 @@ -1306,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. @@ -1315,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 @@ -1325,7 +1325,7 @@ Open Footprint Editor - `libraryName` — optional  -### `kicad_open_schematic`+### kicad_open_schematic  Open .kicad_sch in schematic editor @@ -1334,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 @@ -1344,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}}}. @@ -1355,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. @@ -1365,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 @@ -1374,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. @@ -1385,7 +1385,7 @@ 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 @@ -1398,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 @@ -1408,7 +1408,7 @@ Export active design as 3MF **Prerequisite:** Active 3D design  -### `fusion_export_dwg`+### fusion_export_dwg  Export active design as DWG @@ -1420,7 +1420,7 @@ Export active design as DWG **Prerequisite:** Active 3D design  -### `fusion_export_dxf`+### fusion_export_dxf  Export active design as DXF @@ -1432,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) @@ -1444,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 @@ -1456,7 +1456,7 @@ Export active design as FBX **Prerequisite:** Active 3D design  -### `fusion_export_iges`+### fusion_export_iges  Export active design as IGES (.igs) @@ -1468,7 +1468,7 @@ Export active design as IGES (.igs) **Prerequisite:** Active 3D design  -### `fusion_export_obj`+### fusion_export_obj  Export active design as OBJ @@ -1480,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) @@ -1492,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) @@ -1504,7 +1504,7 @@ Export active design as SKP (SketchUp) **Prerequisite:** Active 3D design  -### `fusion_export_step`+### fusion_export_step  Export active design as STEP @@ -1514,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 @@ -1524,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) @@ -1536,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 @@ -1556,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 @@ -1565,7 +1565,7 @@ Modify a user parameter - `value` — required  -### `fusion_take_screenshot`+### fusion_take_screenshot  Capture Fusion viewport as PNG with camera orientation control @@ -1578,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 @@ -1590,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) @@ -1600,7 +1600,7 @@ Create folder (idempotent) - `projectName` — optional  -### `fusion_delete_cloud_file`+### fusion_delete_cloud_file  Delete a cloud file (must not be open) @@ -1610,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) @@ -1619,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 @@ -1628,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' @@ -1644,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 @@ -1655,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. @@ -1675,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. @@ -1693,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 @@ -1702,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.) @@ -1714,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 @@ -1722,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 @@ -1734,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) @@ -1742,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 @@ -1754,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 @@ -1764,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 @@ -1777,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 @@ -1790,7 +1790,7 @@ Open .lbr library in Electronics Library editor - `filePath` — required  -### `fusion_open_schematic`+### fusion_open_schematic  Open .sch in Schematic Editor @@ -1798,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 @@ -1821,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 @@ -1836,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) @@ -1845,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'. @@ -1855,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. @@ -1868,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. @@ -1895,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 @@ -1904,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.) @@ -1919,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) @@ -1932,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 @@ -1945,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) @@ -1959,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 @@ -1973,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 @@ -2000,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 @@ -2019,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 @@ -2030,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 @@ -2045,7 +2045,7 @@ _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. @@ -2060,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+. @@ -2080,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. @@ -2093,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. @@ -2113,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+. @@ -2122,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+. @@ -2134,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":"..."}}'. @@ -2144,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":"..."}}'. @@ -2154,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":"..."}}'. @@ -2165,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+. @@ -2177,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. @@ -2187,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. @@ -2199,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+. @@ -2215,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. @@ -2224,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. @@ -2237,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. @@ -2250,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). @@ -2264,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. @@ -2273,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+.