Adom Bridge
Public Made by Adomby adom
Adom Bridge unleashes your AI onto your full PC: full power, full safety. The AI breaks out of the container it runs in and onto your real machine, managing and running your entire laptop as you: launch apps, move files, drive any window, control KiCad, Fusion 360 or your real signed-in browser. Works with any AI, cloud or local (Claude Desktop, Claude Code, Codex), no Hydrogen required. Bridge and its bridges are an operating system for AI, with managed Node and Python runtimes, and a human-onl
Adom Hydrogen verbs
Build, ship, screenshot, and drive the embedded Adom Hydrogen.
Part of the Adom Desktop verb reference. Invoke as adom-desktop <verb> '<json>'.
Note: v1.8.15+: hd_* verbs proxy to Adom Hydrogen's local API on 127.0.0.1:9001. Hydrogen 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 Hydrogen isn't running, hd_status returns {ok:false, error:'Hydrogen not running'} cleanly instead of crashing.
hd_api
Generic HTTP pass-through to Hydrogen's control API. The canonical way to call ANY Hydrogen endpoint from a script/AI without shelling out to curl (which the relay mangles). Auto-discovers Hydrogen's control port from %APPDATA%/adom-hydrogen/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.
Args:
body— optional object — JSON body for POST/PUT/PATCH. Passed through verbatim (no escaping).method— optional string (default 'GET') — 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'path— required string starting with '/' — e.g. '/setup/run-step', '/wsl/status', '/claude/start-auth'port— optional int — override discovered port; for testing
Returns: {success:bool, ok:bool, status:int, body:any, method:str, path:str, port:int} — body is the parsed JSON Hydrogen returned (or a string if non-JSON)
Example: adom-desktop hd_api '{"method":"POST","path":"/setup/run-step","body":{"id":"welcome"}}'
hd_browser_profiles
Enumerate browsers + profiles Hydrogen detects on the user's machine (GET /browser-profiles). Use the returned profileDir values when calling hd_open_url.
Returns: Hydrogen's /browser-profiles response — typically {browsers:[{name, profiles:[{dir, name, gaia?}, ...]}]}
hd_build
Full Hydrogen 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: as its final line so callers can detect completion without scraping tool-specific output. v1.8.16+.
Args:
lingerSecs— optional int (default 30 when show=true, 0 when show=false) — v1.8.23+. After the build's final BUILD_OK/BUILD_FAILED line, the visible window stays open for this many seconds before auto-closing. 30s default lets the user read the error; pass 0 to close immediately; pass a larger number for more reading time. Capped at 3600s. Ignored entirely when show=false (no window to linger).show— optional bool (default false) — true opens a visible console window the user can watch the build scroll in; false runs hidden
Returns: {ok, pid, logPath, flavor:'full', show, lingerSecs, _hint}
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
Frontend-only Hydrogen 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.
Args:
lingerSecs— optional int (default 30 when show=true) — see hd_build for details. v1.8.23+.show— optional bool (default false)
Returns: On dispatch success: {ok, pid, logPath, flavor:'frontend', show, lingerSecs, builtSha, exeMtimeBefore, exeSizeBefore, _hint}. On lock contention (v1.8.57+): {ok:false, refused:true, failedAt:'lock', verb, holderPid, holderVerb, sha, elapsedSec, narrative, next} — another hd_build*/hd_ship is already running. NO QUEUE: just refused immediately. The lock auto-releases when the holder's PowerShell PID dies.
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
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:'', lines, logPath, _hint}
hd_build_rust
Rust-only Hydrogen 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.
Args:
lingerSecs— optional int (default 30 when show=true) — see hd_build for details. v1.8.23+.show— optional bool (default false)
Returns: {ok, pid, logPath, flavor:'rust', show, lingerSecs, builtSha, exeMtimeBefore, exeSizeBefore, _hint}
Note: Builds the debug binary at target/debug/adom-hydrogen.exe. The frontend's build/ output is NOT regenerated — re-use whatever was last built by hd_build / hd_build_frontend.
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 adom-hydrogen.exe is held by another process — most often a still-running Hydrogen 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+.
Returns: {ok:bool, building:bool, succeeded:bool, failed:bool, lastLines:[string] (last ~40 lines of stderr), logPath:string, pid:int (cargo PID or 0), pidAlive:bool, failureReason:string (when failed:true — e.g. 'compile_error', 'exe_locked', 'orphaned_no_sentinel'), compiledCrates:[string] (names parsed from 'Compiling X v...' lines — EMPTY means relink-only, your code didn't actually rebuild), relinkOnly:bool (true iff succeeded:true && compiledCrates empty), exeChanged:bool (mtime OR size after differs from before — definitive proof a fresh binary was written), exeMtimeBefore:int (unix seconds), exeMtimeAfter:int (unix seconds), exeSizeBefore:int (bytes), exeSizeAfter:int (bytes), exeLocked:bool (true when build failed because adom-hydrogen.exe was held by another PID), holderPids:[int] (the PIDs holding the exe — taskkill these), builtSha:string|null (the git SHA hd_build was dispatched at — compare to /buildinfo.build_sha after launch), elapsedSecs:int, flavor:string ('rust'|'frontend'|'full'), _hint:string}
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 Hydrogen before building.
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+.
Args:
offset— optional int (default 0) — byte offset into the log file from the previous call's newOffset
Returns: {ok, lines:[], newOffset, totalBytes, done:bool, succeeded:bool, failureReason, _hint}
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
[MOVED to hd-dev in v1.8.72] Run a command inside Hydrogen's Docker container. Public Adom Desktop refuses with verb_moved_to_hd_dev. Workaround: hd_api '{"method":"POST","path":"/container-exec","body":{"command":"..."}}'.
Args:
command— required string
Returns: Public build: refusal payload. hd-dev internal build: Hydrogen's /container-exec response.
hd_eval
[MOVED to hd-dev in v1.8.72] Evaluate JS in Hydrogen'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":"..."}}'.
Args:
js— required string — JS expression or statement
Returns: Public build: refusal payload. hd-dev internal build: Hydrogen's /eval response.
hd_iframe_eval
[MOVED to hd-dev in v1.8.72] Evaluate JS inside Hydrogen'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":"..."}}'.
Args:
contextIndex— optional intjs— required string
Returns: Public build: refusal payload. hd-dev internal build: Hydrogen's /iframe-eval response.
hd_launch
Start Hydrogen'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 — adom-hydrogen.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 Hydrogen's actual PID (looked up via tasklist) since the cmd-wrapper PID is gone immediately. v1.8.16+.
Args:
killExisting— optional bool (default false). When false and Hydrogen is already running, refuse with reason=already_running so the caller doesn't accidentally attach to a stale process. When true, taskkill the existing instance with a 600ms grace period before launching fresh; killedFirst=true in the response.
Returns: On launch: {ok:true, launched:true, wasAlreadyRunning:bool, pid:int (Hydrogen's actual PID, not the cmd-wrapper), killedFirst:bool (true iff killExisting was honored), exePath:string, builtSha:string|null (the SHA the binary was built at — compare to /buildinfo.build_sha to confirm fresh code), _hint}. On refusal: {ok:false, launched:false, wasAlreadyRunning:bool, pid:int|null, killedFirst:false, reason:string, _hint}. reason ∈ {'build_in_progress' (a hd_build/hd_build_rust/hd_build_frontend is still running — wait for hd_build_status.building:false), 'build_failed' (last build ended with BUILD_FAILED — fix and retry hd_build), 'already_running' (Hydrogen is up — pass killExisting:true to force-kill+relaunch, or use hd_restart), 'binary_missing' (target/debug/adom-hydrogen.exe not on disk — run hd_build first)}.
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
Read the tail of Hydrogen's log file at %APPDATA%\adom-hydrogen\adom-hydrogen.log. Doesn't require Hydrogen to be running — reads the file directly from disk. Use this when Hydrogen seems stuck or has crashed to see what it said last.
Args:
tail— optional int (default 30) — number of trailing lines to return
Returns: {ok, lines:[...], path, totalLines, returnedLines}
hd_open_url
Open a URL in a specific browser profile via Hydrogen's /open-in-profile endpoint. Hydrogen knows which browsers + profiles are installed (see hd_browser_profiles) and dispatches to the right one with the right --profile-directory / -P flag.
Args:
browser— optional (default 'chrome') — 'chrome', 'edge', 'firefox', 'brave'profileDir— optional (default 'Default') — profile directory name from hd_browser_profilesurl— required string
Returns: Hydrogen's /open-in-profile response (typically {ok, pid, profile})
hd_reload_vscode
[MOVED to hd-dev in v1.8.72] Reload the VS Code iframe inside Hydrogen. 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: Hydrogen's /reload-vscode response.
hd_restart
Stop + launch in one call. Same guards as hd_launch (build_in_progress / build_failed / binary_missing all refuse). If Hydrogen is running it's taskkilled with a 600ms grace period before relaunch to release the exe lock. v1.8.16+.
Returns: {ok, killedPid?, pid, exePath, _hint}
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
Capture the Adom Hydrogen window as a lossless PNG. Finds the window by title ('Adom Hydrogen') via the same Win32 enum the desktop bridge uses, then captures via PrintWindow.
Returns: {success, savedTo, sizeKB, hwnd, data:{image (base64), format:'png', encoding, sizeBytes}}
Note: Requires Hydrogen to be running AND its window to be visible (PrintWindow can't capture a hidden window). If Hydrogen isn't found, returns 'Adom Hydrogen window not found' — verify Hydrogen is up via hd_status.
hd_send_files
Send local files DIRECTLY into Adom Hydrogen's WSL2 project. Relays the bytes to Hydrogen's control API, which writes them (as adom) into / (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. Hydrogen owns the write (it knows its container/bind-mount layout + runs as adom), so ownership + visibility are correct. Requires Hydrogen running (hd_launch) and an Hydrogen build exposing POST /files/inject.
Args:
filePaths— required array of absolute local file paths to send (same as send_files)subdir— optional string (default 'downloads') — subfolder under Hydrogen's project dir to write into; sits alongside the project's screenshots/ folder
Returns: send_files-style result: {success:bool, filesReceived:int, destinationPaths:[ '//', ... ], error}. destinationPaths are the WSL2 paths Hydrogen actually wrote, so the AI can find them straight away. Hydrogen down or endpoint missing → success:false + a clear error.
Note: Use this instead of send_files whenever the destination is Hydrogen's WSL2 project (it's the cure for the /mnt/c cache-lag 'file isn't there' confusion).
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 Hydrogen + 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.
Args:
clean— optional bool, default false. true forcescargo clean -p adom-hydrogenbefore build to defeat the stale-relink trap — use when build said 'Finished' but the running SHA doesn't match expectSha on a prior hd_ship attempt.expectSha— REQUIRED string — full or prefix SHA of the commit you want running. Matching is bidirectional-prefix (so 'abc123' matches the full SHA starting with abc123 and vice versa). v1.8.57+ pre-validates against origin/main; hallucinated/typo'd/not-yet-pushed SHAs are refused at zero cost before any destructive work.
Returns: On success: {ok:true, builtSha (== expectSha), runningSha (== expectSha per /buildinfo), compiled:bool, elapsedSecs, _hint}. On pipeline failure: {ok:false, failedAt: 'args'|'kill'|'git'|'clean'|'build'|'launch'|'verify', error, builtSha|null, runningSha|null, compiled|null, elapsedSecs, _hint}. On v1.8.57+ guard refusal: {ok:false, refused:true, failedAt: 'sha_validate'|'git_fetch'|'lock'|'lifecycle_lock', error, narrative, _youProbablyMeant|_next}. Hallucinated SHA → 'sha_validate' + _youProbablyMeant tells you what real SHAs look like. Lock held → 'lock' + holderPid/holderVerb/elapsedSec tells you who's holding it.
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 Hydrogen 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 Hydrogen to expose http://127.0.0.1:47084/buildinfo returning JSON with build_sha (or buildSha or sha).
hd_shot
v1.8.54+: Capture a named region of Adom Hydrogen, 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 Hydrogen window bounds + CDP element rects queried through Hydrogen's /eval endpoint. Each shot is self-identifying: it includes the running build's SHA + local build time read from Hydrogen'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/, 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).
Args:
format— optional string, default 'png' (only PNG supported in v1).region— REQUIRED string. One of: 'full' (whole Hydrogen window), 'titlebar' (top ~40px strip — build SHA / timestamp readout), 'vscode' (VS Code iframe pane via CDP element rect), 'setup-panel' (Setup Steps panel via .setup-panel or .setup-panel-output), 'wiki' (wiki/browser tab pane), 'claude' (Claude Code panel). If unknown, response is {ok:false, error, knownRegions:[...]}.resizeMax— optional int, default 1400. Longest side of the safe-resized copy. Use lower (≈800) if you want to fit more shots in one Claude turn; higher (1568) for max fidelity.
Returns: {ok, region, fullPath, safePath, fullSize:[w,h], safeSize:[w,h], clipRect:[x,y,w,h], capturedAtLocal, runningBuildSha, runningBuildLocal, hdWindowFound:bool, regionFound:bool, windowOuter:[w,h], capturedSize:[w,h], _hints:{pull, fullForArchive, staleWindow, regionMiss}}
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 Hydrogen's DOM — the panel/iframe may not be mounted yet, OR Hydrogen'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 Hydrogen's /buildinfo isn't exposed yet — same SHA Hydrogen'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
v1.8.58+ COMPOSED + RELAYED snapshot of Hydrogen's state; v1.8.129+ churn-honest. AD owns host/process/repo facts; Hydrogen owns runtime state (relayed verbatim from Hydrogen's control API, port discovered from %APPDATA%/adom-hydrogen/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 Hydrogen's constant build-relaunch churn: processPresent (adom-hydrogen.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 Hydrogen?'). running = processPresent||controlApiReachable.
Returns: {ok:true, running:bool (process||control), processPresent:bool, controlApiReachable:bool, controlPort:int (the dynamically-discovered port), embedded:bool (AD's own mode), embeddedOwner:string|null, embeddedVia:string|null ('LaunchFlag'|'RuntimeAdopt'), laptopHeadSha:string, originMainSha:string, shaMatchesOrigin:bool, runningSha:string|null, workspaceHealth:object|null (Hydrogen's /workspace/health relayed verbatim), buildinfo:object|null (Hydrogen's /buildinfo relayed verbatim), narrative:string (honest about up-serving vs up-churning vs down, plus AD's embedded mode), _next:string}
Note: v1.8.129 fixes the misleading 'Hydrogen is NOT running' that fired when a single tasklist snapshot landed mid-relaunch. If you see running:true + controlApiReachable:false, Hydrogen is mid-build/relaunch — retry in a few seconds, don't treat it as down. Use this as the entry point for 'what's Hydrogen's state?' — it tells you which downstream verb to run next (hd_ship, hd_launch, hd_shot, etc.).
hd_stop
Kill adom-hydrogen.exe. wasRunning distinguishes 'killed it' from 'nothing to do'. v1.8.16+.
Returns: {ok, wasRunning:bool, killedPid?, _hint}
# Adom Hydrogen verbs
Build, ship, screenshot, and drive the embedded Adom Hydrogen.
_Part of the [Adom Desktop verb reference](VERB-REFERENCE.md). Invoke as `adom-desktop <verb> '<json>'`._
> **Note:** v1.8.15+: hd_* verbs proxy to Adom Hydrogen's local API on 127.0.0.1:9001. Hydrogen 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 Hydrogen isn't running, hd_status returns {ok:false, error:'Hydrogen not running'} cleanly instead of crashing.
### `hd_api`
Generic HTTP pass-through to Hydrogen's control API. The canonical way to call ANY Hydrogen endpoint from a script/AI without shelling out to curl (which the relay mangles). Auto-discovers Hydrogen's control port from %APPDATA%/adom-hydrogen/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.
**Args:**
- `body` — optional object — JSON body for POST/PUT/PATCH. Passed through verbatim (no escaping).
- `method` — optional string (default 'GET') — 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
- `path` — required string starting with '/' — e.g. '/setup/run-step', '/wsl/status', '/claude/start-auth'
- `port` — optional int — override discovered port; for testing
**Returns:** {success:bool, ok:bool, status:int, body:any, method:str, path:str, port:int} — `body` is the parsed JSON Hydrogen returned (or a string if non-JSON)
**Example:** `adom-desktop hd_api '{"method":"POST","path":"/setup/run-step","body":{"id":"welcome"}}'`
### `hd_browser_profiles`
Enumerate browsers + profiles Hydrogen detects on the user's machine (GET /browser-profiles). Use the returned profileDir values when calling hd_open_url.
**Returns:** Hydrogen's /browser-profiles response — typically {browsers:[{name, profiles:[{dir, name, gaia?}, ...]}]}
### `hd_build`
Full Hydrogen 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+.
**Args:**
- `lingerSecs` — optional int (default 30 when show=true, 0 when show=false) — v1.8.23+. After the build's final BUILD_OK/BUILD_FAILED line, the visible window stays open for this many seconds before auto-closing. 30s default lets the user read the error; pass 0 to close immediately; pass a larger number for more reading time. Capped at 3600s. Ignored entirely when show=false (no window to linger).
- `show` — optional bool (default false) — true opens a visible console window the user can watch the build scroll in; false runs hidden
**Returns:** {ok, pid, logPath, flavor:'full', show, lingerSecs, _hint}
**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`
Frontend-only Hydrogen 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.
**Args:**
- `lingerSecs` — optional int (default 30 when show=true) — see hd_build for details. v1.8.23+.
- `show` — optional bool (default false)
**Returns:** On dispatch success: {ok, pid, logPath, flavor:'frontend', show, lingerSecs, builtSha, exeMtimeBefore, exeSizeBefore, _hint}. On lock contention (v1.8.57+): {ok:false, refused:true, failedAt:'lock', verb, holderPid, holderVerb, sha, elapsedSec, narrative, _next} — another hd_build_*/hd_ship is already running. NO QUEUE: just refused immediately. The lock auto-releases when the holder's PowerShell PID dies.
**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`
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`
Rust-only Hydrogen 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.
**Args:**
- `lingerSecs` — optional int (default 30 when show=true) — see hd_build for details. v1.8.23+.
- `show` — optional bool (default false)
**Returns:** {ok, pid, logPath, flavor:'rust', show, lingerSecs, builtSha, exeMtimeBefore, exeSizeBefore, _hint}
**Note:** Builds the debug binary at target/debug/adom-hydrogen.exe. The frontend's build/ output is NOT regenerated — re-use whatever was last built by hd_build / hd_build_frontend.
### `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 adom-hydrogen.exe is held by another process — most often a still-running Hydrogen 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+.
**Returns:** {ok:bool, building:bool, succeeded:bool, failed:bool, lastLines:[string] (last ~40 lines of stderr), logPath:string, pid:int (cargo PID or 0), pidAlive:bool, failureReason:string (when failed:true — e.g. 'compile_error', 'exe_locked', 'orphaned_no_sentinel'), compiledCrates:[string] (names parsed from 'Compiling X v...' lines — EMPTY means relink-only, your code didn't actually rebuild), relinkOnly:bool (true iff succeeded:true && compiledCrates empty), exeChanged:bool (mtime OR size after differs from before — definitive proof a fresh binary was written), exeMtimeBefore:int (unix seconds), exeMtimeAfter:int (unix seconds), exeSizeBefore:int (bytes), exeSizeAfter:int (bytes), exeLocked:bool (true when build failed because adom-hydrogen.exe was held by another PID), holderPids:[int] (the PIDs holding the exe — taskkill these), builtSha:string|null (the git SHA hd_build was dispatched at — compare to /buildinfo.build_sha after launch), elapsedSecs:int, flavor:string ('rust'|'frontend'|'full'), _hint:string}
**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 Hydrogen before building.
### `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+.
**Args:**
- `offset` — optional int (default 0) — byte offset into the log file from the previous call's newOffset
**Returns:** {ok, lines:[], newOffset, totalBytes, done:bool, succeeded:bool, failureReason, _hint}
**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`
[MOVED to hd-dev in v1.8.72] Run a command inside Hydrogen's Docker container. Public Adom Desktop refuses with verb_moved_to_hd_dev. Workaround: hd_api '{"method":"POST","path":"/container-exec","body":{"command":"..."}}'.
**Args:**
- `command` — required string
**Returns:** Public build: refusal payload. hd-dev internal build: Hydrogen's /container-exec response.
### `hd_eval`
[MOVED to hd-dev in v1.8.72] Evaluate JS in Hydrogen'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":"..."}}'.
**Args:**
- `js` — required string — JS expression or statement
**Returns:** Public build: refusal payload. hd-dev internal build: Hydrogen's /eval response.
### `hd_iframe_eval`
[MOVED to hd-dev in v1.8.72] Evaluate JS inside Hydrogen'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":"..."}}'.
**Args:**
- `contextIndex` — optional int
- `js` — required string
**Returns:** Public build: refusal payload. hd-dev internal build: Hydrogen's /iframe-eval response.
### `hd_launch`
Start Hydrogen'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 — adom-hydrogen.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 Hydrogen's actual PID (looked up via tasklist) since the cmd-wrapper PID is gone immediately. v1.8.16+.
**Args:**
- `killExisting` — optional bool (default false). When false and Hydrogen is already running, refuse with reason=already_running so the caller doesn't accidentally attach to a stale process. When true, taskkill the existing instance with a 600ms grace period before launching fresh; killedFirst=true in the response.
**Returns:** On launch: {ok:true, launched:true, wasAlreadyRunning:bool, pid:int (Hydrogen's actual PID, not the cmd-wrapper), killedFirst:bool (true iff killExisting was honored), exePath:string, builtSha:string|null (the SHA the binary was built at — compare to /buildinfo.build_sha to confirm fresh code), _hint}. On refusal: {ok:false, launched:false, wasAlreadyRunning:bool, pid:int|null, killedFirst:false, reason:string, _hint}. reason ∈ {'build_in_progress' (a hd_build/hd_build_rust/hd_build_frontend is still running — wait for hd_build_status.building:false), 'build_failed' (last build ended with BUILD_FAILED — fix and retry hd_build), 'already_running' (Hydrogen is up — pass killExisting:true to force-kill+relaunch, or use hd_restart), 'binary_missing' (target/debug/adom-hydrogen.exe not on disk — run hd_build first)}.
**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`
Read the tail of Hydrogen's log file at %APPDATA%\adom-hydrogen\adom-hydrogen.log. Doesn't require Hydrogen to be running — reads the file directly from disk. Use this when Hydrogen seems stuck or has crashed to see what it said last.
**Args:**
- `tail` — optional int (default 30) — number of trailing lines to return
**Returns:** {ok, lines:[...], path, totalLines, returnedLines}
### `hd_open_url`
Open a URL in a specific browser profile via Hydrogen's /open-in-profile endpoint. Hydrogen knows which browsers + profiles are installed (see hd_browser_profiles) and dispatches to the right one with the right --profile-directory / -P flag.
**Args:**
- `browser` — optional (default 'chrome') — 'chrome', 'edge', 'firefox', 'brave'
- `profileDir` — optional (default 'Default') — profile directory name from hd_browser_profiles
- `url` — required string
**Returns:** Hydrogen's /open-in-profile response (typically {ok, pid, profile})
### `hd_reload_vscode`
[MOVED to hd-dev in v1.8.72] Reload the VS Code iframe inside Hydrogen. 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: Hydrogen's /reload-vscode response.
### `hd_restart`
Stop + launch in one call. Same guards as hd_launch (build_in_progress / build_failed / binary_missing all refuse). If Hydrogen is running it's taskkilled with a 600ms grace period before relaunch to release the exe lock. v1.8.16+.
**Returns:** {ok, killedPid?, pid, exePath, _hint}
**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`
Capture the Adom Hydrogen window as a lossless PNG. Finds the window by title ('Adom Hydrogen') via the same Win32 enum the desktop bridge uses, then captures via PrintWindow.
**Returns:** {success, savedTo, sizeKB, hwnd, data:{image (base64), format:'png', encoding, sizeBytes}}
**Note:** Requires Hydrogen to be running AND its window to be visible (PrintWindow can't capture a hidden window). If Hydrogen isn't found, returns 'Adom Hydrogen window not found' — verify Hydrogen is up via hd_status.
### `hd_send_files`
Send local files DIRECTLY into Adom Hydrogen's WSL2 project. Relays the bytes to Hydrogen'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. Hydrogen owns the write (it knows its container/bind-mount layout + runs as adom), so ownership + visibility are correct. Requires Hydrogen running (hd_launch) and an Hydrogen build exposing POST /files/inject.
**Args:**
- `filePaths` — required array of absolute local file paths to send (same as send_files)
- `subdir` — optional string (default 'downloads') — subfolder under Hydrogen's project dir to write into; sits alongside the project's screenshots/ folder
**Returns:** send_files-style result: {success:bool, filesReceived:int, destinationPaths:[ '<project>/<subdir>/<name>', ... ], error}. destinationPaths are the WSL2 paths Hydrogen actually wrote, so the AI can find them straight away. Hydrogen down or endpoint missing → success:false + a clear error.
**Note:** Use this instead of send_files whenever the destination is Hydrogen's WSL2 project (it's the cure for the /mnt/c cache-lag 'file isn't there' confusion).
### `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 Hydrogen + 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.
**Args:**
- `clean` — optional bool, default false. true forces `cargo clean -p adom-hydrogen` before build to defeat the stale-relink trap — use when build said 'Finished' but the running SHA doesn't match expectSha on a prior hd_ship attempt.
- `expectSha` — REQUIRED string — full or prefix SHA of the commit you want running. Matching is bidirectional-prefix (so 'abc123' matches the full SHA starting with abc123 and vice versa). v1.8.57+ pre-validates against origin/main; hallucinated/typo'd/not-yet-pushed SHAs are refused at zero cost before any destructive work.
**Returns:** On success: {ok:true, builtSha (== expectSha), runningSha (== expectSha per /buildinfo), compiled:bool, elapsedSecs, _hint}. On pipeline failure: {ok:false, failedAt: 'args'|'kill'|'git'|'clean'|'build'|'launch'|'verify', error, builtSha|null, runningSha|null, compiled|null, elapsedSecs, _hint}. On v1.8.57+ guard refusal: {ok:false, refused:true, failedAt: 'sha_validate'|'git_fetch'|'lock'|'lifecycle_lock', error, narrative, _youProbablyMeant|_next}. Hallucinated SHA → 'sha_validate' + _youProbablyMeant tells you what real SHAs look like. Lock held → 'lock' + holderPid/holderVerb/elapsedSec tells you who's holding it.
**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 Hydrogen 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 Hydrogen to expose http://127.0.0.1:47084/buildinfo returning JSON with `build_sha` (or `buildSha` or `sha`).
### `hd_shot`
v1.8.54+: Capture a named region of Adom Hydrogen, 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 Hydrogen window bounds + CDP element rects queried through Hydrogen's /eval endpoint. Each shot is self-identifying: it includes the running build's SHA + local build time read from Hydrogen'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).
**Args:**
- `format` — optional string, default 'png' (only PNG supported in v1).
- `region` — REQUIRED string. One of: 'full' (whole Hydrogen window), 'titlebar' (top ~40px strip — build SHA / timestamp readout), 'vscode' (VS Code iframe pane via CDP element rect), 'setup-panel' (Setup Steps panel via .setup-panel or .setup-panel-output), 'wiki' (wiki/browser tab pane), 'claude' (Claude Code panel). If unknown, response is {ok:false, error, knownRegions:[...]}.
- `resizeMax` — optional int, default 1400. Longest side of the safe-resized copy. Use lower (≈800) if you want to fit more shots in one Claude turn; higher (1568) for max fidelity.
**Returns:** {ok, region, fullPath, safePath, fullSize:[w,h], safeSize:[w,h], clipRect:[x,y,w,h], capturedAtLocal, runningBuildSha, runningBuildLocal, hdWindowFound:bool, regionFound:bool, windowOuter:[w,h], capturedSize:[w,h], _hints:{pull, fullForArchive, staleWindow, regionMiss}}
**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 Hydrogen's DOM — the panel/iframe may not be mounted yet, OR Hydrogen'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 Hydrogen's /buildinfo isn't exposed yet — same SHA Hydrogen'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`
v1.8.58+ COMPOSED + RELAYED snapshot of Hydrogen's state; v1.8.129+ churn-honest. AD owns host/process/repo facts; Hydrogen owns runtime state (relayed verbatim from Hydrogen's control API, port discovered from %APPDATA%/adom-hydrogen/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 Hydrogen's constant build-relaunch churn: processPresent (adom-hydrogen.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 Hydrogen?'). `running` = processPresent||controlApiReachable.
**Returns:** {ok:true, running:bool (process||control), processPresent:bool, controlApiReachable:bool, controlPort:int (the dynamically-discovered port), embedded:bool (AD's own mode), embeddedOwner:string|null, embeddedVia:string|null ('LaunchFlag'|'RuntimeAdopt'), laptopHeadSha:string, originMainSha:string, shaMatchesOrigin:bool, runningSha:string|null, workspaceHealth:object|null (Hydrogen's /workspace/health relayed verbatim), buildinfo:object|null (Hydrogen's /buildinfo relayed verbatim), narrative:string (honest about up-serving vs up-churning vs down, plus AD's embedded mode), _next:string}
**Note:** v1.8.129 fixes the misleading 'Hydrogen is NOT running' that fired when a single tasklist snapshot landed mid-relaunch. If you see running:true + controlApiReachable:false, Hydrogen is mid-build/relaunch — retry in a few seconds, don't treat it as down. Use this as the entry point for 'what's Hydrogen's state?' — it tells you which downstream verb to run next (hd_ship, hd_launch, hd_shot, etc.).
### `hd_stop`
Kill adom-hydrogen.exe. `wasRunning` distinguishes 'killed it' from 'nothing to do'. v1.8.16+.
**Returns:** {ok, wasRunning:bool, killedPid?, _hint}