app
Adom Bridge
Public Made by Adomby adom
Adom Bridge unleashes your AI onto your full PC: full power, full safety. The AI breaks out of the container it runs in and onto your real machine, managing and running your entire laptop as you: launch apps, move files, drive any window, control KiCad, Fusion 360 or your real signed-in browser. Works with any AI, cloud or local (Claude Desktop, Claude Code, Codex), no Hydrogen required. Bridge and its bridges are an operating system for AI, with managed Node and Python runtimes, and a human-onl
← Commit history
README.md+273−25@@ -54,7 +54,7 @@ This adompkg version tracks the Adom Desktop release version (`X.Y.Z`). The pack ## Command reference -Every verb and subcommand `adom-desktop` supports — **210 across 12 categories** — inlined below (the same names are the dispatch keys the cloud AI uses over the relay). Invoke as `adom-desktop <verb> '<json args>'`; bridge verbs (`kicad_*`, `fusion_*`, `browser_*`, `hd_*`) auto-spawn their bridge on first call. Every *failing* response also carries a `_hint` telling you the next step — surface it verbatim. Generated from the live help-map; run `adom-desktop commands` for the machine-readable source.+Every verb and subcommand `adom-desktop` supports — **230 across 12 categories** — inlined below (the same names are the dispatch keys the cloud AI uses over the relay). Invoke as `adom-desktop <verb> '<json args>'`; bridge verbs (`kicad_*`, `fusion_*`, `browser_*`, `hd_*`) auto-spawn their bridge on first call. Every *failing* response also carries a `_hint` telling you the next step — surface it verbatim. Generated from the live help-map; run `adom-desktop commands` for the machine-readable source. **Categories:** [Connectivity](#connectivity) · [Setup](#setup) · [Shell & process](#shell-process) · [File transfer](#file-transfer) · [Desktop & bridge mgmt](#desktop-bridge-mgmt) · [Screen recording](#screen-recording) · [Notifications](#notifications) · [Project watch](#project-watch) · [Browser (Puppeteer)](#browser-puppeteer) · [KiCad bridge](#kicad-bridge) · [Fusion 360 bridge](#fusion-360-bridge) · [Hydrogen Desktop](#hydrogen-desktop) @@ -117,7 +117,7 @@ Generate installer URL and server config JSON for first-time desktop app setup ## Shell & process -_Run shell commands, scripts, WSL, and launch/locate apps on the laptop._ (6 verbs)+_Run shell commands, scripts, WSL, and launch/locate apps on the laptop._ (15 verbs) ### find_exe @@ -178,7 +178,117 @@ v1.8.46+: Execute a base64-encoded script inside a WSL distro via the canonical - `timeoutSeconds` — optional int (default 60). The CLI's WS timeout auto-sizes to scriptTimeout + 15s margin. - `user` — required string: WSL user to run as (e.g. 'adom', 'root'). Must exist in the distro. -**Returns:** On success: {success:true, ok:true, stdout, stderr, exitCode, durationMs, interpreter:'wsl', distro, user, [_hint?]}. wsl.exe's UTF-16LE output is decoded as text automatically (BOM-detect + odd-position-NULL heuristic, fall back to UTF-8 lossy). On failure: {success:false, ok:false, stdout:'', stderr:'', exitCode:-1, error, failedAt, _hint}. failedAt is one of: 'args' (distro/user/scriptB64 empty), 'decode' (base64 invalid), 'utf8' (decoded bytes not UTF-8), 'spawn' (wsl.exe couldn't launch — see wsl_error_hint), 'timeout' (script exceeded timeoutSeconds). EVERY failure carries an actionable _hint. The most common spawn failure is 'distro not installed' — the hint will tell you to run `wsl -l -v` to enumerate, or fall back to run_script interpreter:bash for the default distro. **v1.8.60+ ADVISORY**: success responses may carry an additive `_hint` when the script looks like a direct HD build (raw `cargo`/`pnpm` targeting `hydrogen-desktop`/`crates/hd-*`); the hint points at the dedicated `hd_*` verbs that handle the exe lock + SHA verification.+**Returns:** On success: {success:true, ok:true, stdout, stderr, exitCode, durationMs, interpreter:'wsl', distro, user, [_hint?]}. wsl.exe's UTF-16LE output is decoded as text automatically (BOM-detect + odd-position-NULL heuristic, fall back to UTF-8 lossy). On failure: {success:false, ok:false, stdout:'', stderr:'', exitCode:-1, error, failedAt, _hint}. failedAt is one of: 'args' (distro/user/scriptB64 empty), 'decode' (base64 invalid), 'utf8' (decoded bytes not UTF-8), 'spawn' (wsl.exe couldn't launch — see wsl_error_hint), 'timeout' (script exceeded timeoutSeconds). EVERY failure carries an actionable _hint. The most common spawn failure is 'distro not installed' — the hint will tell you to run `wsl -l -v` to enumerate, or fall back to run_script interpreter:bash for the default distro. **v1.8.157**: if WSL is wedged (too many piled-up wsl.exe), wsl_exec/run_script return errorCode:'wsl_wedged' fast instead of hanging — run `wsl_recover` then retry. **v1.8.60+ ADVISORY**: success responses may carry an additive `_hint` when the script looks like a direct HD build (raw `cargo`/`pnpm` targeting `hydrogen-desktop`/`crates/hd-*`); the hint points at the dedicated `hd_*` verbs that handle the exe lock + SHA verification.+++### wsl_exec_async++v1.8.161: ASYNC held-session sibling of wsl_exec — THE primitive for multi-minute in-distro work (golden-image bakes, big apt/downloads). Spawns `wsl -d <distro> -u <user> -- bash -l` DETACHED, holds that one wsl.exe in the foreground for the command's whole lifetime (so WSL keeps the distro alive — /tmp survives, no mid-run shutdown), streams stdout+stderr (UTF-8, WSL_UTF8=1) to a log file, and returns a jobId IMMEDIATELY. No relay/HTTP EOF: the request returns in ~1s; the work runs in the background. Poll with wsl_job_status. This replaces hand-rolled `Start-Process wsl … -RedirectStandardOutput` + `nohup &` (the latter dies because backgrounding inside the distro lets the session close and WSL shuts the distro down).++**Args:**+- `distro` — required string: distro name (see wsl_list).+- `scriptB64` — required string: base64 of the bash script BODY (UTF-8).+- `user` — required string: linux user ('root', 'adom', …).+- `viaRoot` — optional bool (default false). true → run `-u root -- runuser -u <user> -- bash -l` instead of `-u <user>` — drops to the user WITHOUT starting [email protected]. Use this when `-u <user>` fails 'Failed to start the systemd user session' on a systemd=true distro.++**Returns:** {ok, success, jobId, pid, distro, kind:'exec', outputPath, _hint}. outputPath is the Windows path to the live UTF-8 log — `pull_file` it for the full output. errorCode:'wsl_wedged' if WSL is at the spawn cap (run wsl_recover first).++**Note:** Requires shell auto-approve (same trust as wsl_exec). Job ids are in AD's memory only — not persisted across an AD restart (the log file survives). Stop early with wsl_job_kill.+++### wsl_export++v1.8.161: Export a named distro to a tarball (`wsl --export <distro> <tarball>`; add vhd:true for a .vhdx). Long op → returns a jobId; poll wsl_job_status, then `pull_file` the tarball. THE capture step for a finished golden image.++**Args:**+- `distro` — required string: distro to export.+- `tarball` — required string: Windows output path (parent dir created if missing).+- `vhd` — optional bool (default false): export as .vhdx instead of .tar.++**Returns:** {ok, jobId, distro, kind:'export', outputPath, _hint}. Poll until done; exitCode 0 = exported.++**Note:** Requires shell auto-approve.+++### wsl_import++v1.8.161: Import a tarball as a NEW named WSL2 distro (`wsl --import <distro> <installDir> <tarball> --version 2`). Long op → returns a jobId immediately; poll wsl_job_status. THE setup step for a throwaway build distro.++**Args:**+- `distro` — required string: NEW distro name to create.+- `installDir` — required string: Windows dir for the distro's vhdx (created if missing).+- `tarball` — required string: Windows path to an existing .tar/.tar.gz (send_files it from Docker first if needed).+- `version` — optional int (default 2): WSL version.++**Returns:** {ok, jobId, distro, kind:'import', outputPath, _hint}. Poll wsl_job_status until done; exitCode 0 = imported. Then wsl_list shows it.++**Note:** Requires shell auto-approve. Scoped to the named distro.+++### wsl_job_kill++v1.8.161: Stop a running async WSL job by killing its held wsl.exe (taskkill /T — ends the session, stops the in-distro command). Scoped to that job's pid only.++**Args:**+- `jobId` — required string.++**Returns:** {ok, jobId, pid, killed, _hint}. wsl_job_status will show running:false shortly after.+++### wsl_job_list++v1.8.161: List all in-memory async WSL jobs (id, running, exitCode, distro, kind, elapsedMs, outputPath).++**Returns:** {ok, jobs:[…], count, _hint}. In-memory only (lost on AD restart).++**Note:** Read-only.+++### wsl_job_status++v1.8.161: Poll an async WSL job (from wsl_exec_async / wsl_import / wsl_export / wsl_unregister).++**Args:**+- `jobId` — required string: the id returned by the async verb.+- `tailBytes` — optional int (default 4000): how many trailing bytes of the log to include as outputTail.++**Returns:** {ok, jobId, running, done, exitCode (null until done; 0=success), pid, distro, kind, label, elapsedMs, outputPath, outputTail, _hint}. Loop until running:false; read progress from outputTail, or `pull_file` outputPath for the full log.++**Note:** Read-only — no auto-approve needed.+++### wsl_list++v1.8.161: List installed WSL distros (`wsl -l -v`, parsed). Quick sync read.++**Returns:** {ok, distros:[{name, state, version, default}], count, _hint}. state=Running means up (a held job or in use). Use these names with wsl_exec_async / wsl_export / wsl_unregister.++**Note:** Read-only.+++### wsl_recover++v1.8.157: Recover a wedged WSL by reaping orphaned/runaway wsl.exe CLI clients (a wedged LxssManager leaves hung clients that taskkill can't always clear, and they pile up — 116 once — until every `wsl …` hangs). DRY-RUN by default: reports the wsl.exe it WOULD reap (childless + 0-CPU-old 'hung' ones, plus CPU 'runaway' spinners) with their command line, so you can confirm the live distro isn't among them. SPARES wsl.exe that have Windows child processes (likely the live distro) and never touches wslservice.exe/wslhost.exe/vmmemWSL.++**Args:**+- `confirm` — optional bool (default false = dry-run). true = actually Stop-Process the candidates.+- `sparePids` — optional int[] — wsl.exe PIDs to never kill (e.g. the live Adom-Workspace pid). Use after reviewing the dry-run `candidates[].cmd` if any looks like your workspace.++**Returns:** {ok, dryRun, candidates:[{pid,cpuSec,ageMin,hasChildren,cmd,reason}], killed:[…], spared:[{pid,…,spared}], wslCountBefore, _hint}. Flow: call once (dry-run) → review candidates → wsl_recover '{"confirm":true}' (optionally sparePids:[…]). See also `adom-desktop status` → wsl block for the live count + wedged flag.++**Note:** Reaping doesn't always clear a deeply service-wedged client; if `wsl …` still hangs after, the last resort is `wsl --shutdown` (kills the live workspace too) or a reboot. The v1.8.157 spawn cap (refuse new wsl spawns at 12 wsl.exe) prevents the pile-up recurring.+++### wsl_unregister++v1.8.161: Unregister (DELETE) ONE named distro (`wsl --unregister <distro>`). Long op → returns a jobId; poll wsl_job_status. Irreversible (deletes the vhdx). Strictly scoped to the named distro — never a global `wsl --shutdown`. REFUSES 'Adom-Workspace' (the live HD workspace).++**Args:**+- `distro` — required string: the throwaway build distro to remove.++**Returns:** {ok, jobId, distro, kind:'unregister', _hint} or errorCode:'refused_live_distro'. Poll until done.++**Note:** Requires shell auto-approve. ## File transfer@@ -200,17 +310,20 @@ Pull files from the desktop back to Docker. Streams as binary WS frames (1 MiB c ### send_files -Send files from Docker container to the desktop+Send files from Docker container to the desktop. By default lands in the desktop's configured receive dir (Downloads). Pass `dest` for an absolute destination. Multi-MB to hundreds-of-MB files work (v1.8.160 raised the relay HTTP body limit + the AD<->relay WS message/frame limits to 256 MB — earlier builds 413'd past ~1.5 MB because the payload is base64 in the POST body and axum's default body cap was 2 MB). **Args:**-- `destinationFolder` — optional relative subfolder+- `dest` — optional ABSOLUTE destination dir (v1.8.148+); created if missing; must be under a safe user-writable root (%USERPROFILE%/%TEMP%/%LOCALAPPDATA%/%APPDATA%). Overrides destinationFolder. e.g. C:/Users/john/.adom/native-browser+- `destinationFolder` — optional RELATIVE subfolder under the receive dir - `filePaths` — required array of absolute paths - `targetApp` — optional: general|kicad|fusion360 +**Note:** Cap is 256 MB of wire (base64) ~= 190 MB of real file, total across the batch. Over-cap returns a clear error pre-POST (not a 413). For a single very large file you can also tar it on the desktop and `pull_file` it (that path streams in 1 MiB binary frames with no practical limit). Needs relay + AD both on v1.8.160+; an older relay still 413s (rebuild + RESTART the relay — a long-running `serve` holds the old binary).+ ## Desktop & bridge mgmt -_Windows, screenshots, files, embedded mode, relay servers, tunnels, and bridge lifecycle._ (44 verbs)+_Windows, screenshots, files, embedded mode, relay servers, tunnels, and bridge lifecycle._ (52 verbs) ### bridge_check_updates @@ -230,7 +343,7 @@ Install a bridge from a wiki manifest URL. The manifest's referenced zip is down **Returns:** {success, ok, bridge:<name from manifest>, version, installPath, _hint} -**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.+**Note:** The bridge zip MUST contain a bridge.json at its root with name + spawn.{kind,entrypoint,port,...} + verbPrefixes. See the bridge SDK guide at https://wiki.adom.inc/adom/adom-desktop-bridges for the schema. To update an existing bridge, install again — the cache atomic-swaps. ### bridge_list@@ -264,6 +377,20 @@ Resume a paused bridge — flip the routing flag back. Inverse of bridge_pause. **Returns:** {success, name, paused:false, _hint} +### bridge_start_all++Re-enable bridge spawning after bridge_stop_all / --safe-mode (v1.8.150+): flip the kill-switch ON. Bridges lazy-spawn on the next routed verb (or call bridge_resume <name>).++**Returns:** {success, bridgesEnabled:true, _hint}+++### bridge_stop_all++GLOBAL kill-switch (v1.8.150+): flip the desktop's bridge kill-switch OFF — disables ALL bridge spawning (boot auto-start, lazy spawn, resume, dynamic/cache dispatch) AND stops the running bundled bridges (kicad/fusion/browser). The one-call cure for a runaway/misbehaving bridge, without hand-editing the autostart shortcut. Same state as launching with --safe-mode, but at runtime.++**Returns:** {success, bridgesEnabled:false, _hint}++ ### 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.@@ -274,6 +401,20 @@ Remove a bridge from the cache. v1.8.0+. Bundled bridges (kicad, puppeteer, fusi **Returns:** {success, name, cacheRemoved, stillInstalledViaBundled, _hint} +### desktop_activity_filter++Scope the USER's GUI activity-log so they watch the slice you care about while you work (e.g. filter to one bridge before debugging it). Drives the human's GUI view only — does NOT change which verbs you can call or what they return.++**Args:**+- `bridge` — a bridge registry name (from bridge_list) | "none" (only global/non-bridge AD commands) | "all" or omit (everything)+- `clear` — optional bool — reset both+- `text` — optional free-text substring (ANDs with bridge)++**Returns:** {ok, bridge, text}++**Note:** Example: desktop_activity_filter '{"bridge":"kicad"}' focuses the log on KiCad; '{"clear":true}' resets. The GUI also shows a status bar saying exactly what's shown + what's hidden.++ ### desktop_bring_to_front Bring a window to the foreground@@ -344,6 +485,35 @@ v1.8.42+: introspect whether AD is currently running in embedded mode (HD bundli **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_find_window++Resolve a window's hwnd by title/class in Z-order (best = topmost). Replaces the racy 'diff desktop_list_windows before/after' pattern for a just-opened window.++**Args:**+- `className` — optional case-insensitive substring (e.g. Chrome_WidgetWin_1)+- `titleContains` — optional case-insensitive substring++**Returns:** {ok, count, best:{hwnd,title,className,z,rect}|null, windows[]}++**Note:** Provide at least one of titleContains/className. ok=true only when ≥1 matched. Pass best.hwnd to desktop_taskbar / desktop_flash_window / desktop_screenshot_window.+++### desktop_flash_window++Flash a window's taskbar button (FlashWindowEx) — orange 'attention' cue. Lightweight (no process spawn); safe to call after every bridge verb.++**Args:**+- `count` — optional int (mode:count only; default 3)+- `hwnd` — int (from desktop_list_windows) OR titleContains+- `mode` — optional: until_focused (default — flashes until the user focuses it) | count+- `stop` — optional bool — clear an active flash+- `titleContains` — optional string (case-insensitive)++**Returns:** {ok, hwnd, title, mode}++**Note:** until_focused = FLASHW_TRAY|FLASHW_TIMERNOFG; stop:true = FLASHW_STOP. Resolve hwnd via desktop_list_windows first. For progress + overlay-icon status use desktop_taskbar.++ ### 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.@@ -387,7 +557,7 @@ List files in a directory matching a shell-glob, optionally filtered to those mo ### desktop_list_windows -List all visible windows with HWNDs, titles, positions+List all visible windows with HWNDs, titles, positions. Each entry includes `z` (Z-order index, 0 = topmost). ### desktop_logout@@ -494,6 +664,22 @@ v1.8.42+: graceful AD exit — stop bridges (kicad, fusion, puppeteer) then exit **Returns:** {ok, success, output, _hint}. The exit happens ~500ms after the response is sent so the caller doesn't see connection-reset. +### desktop_taskbar++Paint glanceable status on ANY window's taskbar button (ITaskbarList3 progress + overlay icon + FlashWindowEx). A bridge sets this on the real app window it drives (e.g. native-browser on the Chrome/Edge button). Persistent COM (init once, reuse) — safe to tick progress frequently.++**Args:**+- `flash` — optional: until_focused | stop+- `hwnd` — int (from desktop_list_windows) OR titleContains+- `overlay` — optional {badge: adom|none, icon?: <.ico path or base64>, tooltip?: string}+- `progress` — optional {state: normal|paused|error|indeterminate|none, value?: 0..100}+- `titleContains` — optional string++**Returns:** {ok, hwnd, title, applied[]}++**Note:** States→colors: normal=green, paused=YELLOW (human-in-the-loop: captcha/2FA/login), error=red, indeterminate=marquee. Clears: flash:stop, progress.state:none, overlay.badge:none. Built-in overlay badges: adom | none (custom via overlay.icon, .ico only for now). Works on other processes' windows.++ ### 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.@@ -537,15 +723,29 @@ 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). +### display++v1.8.147+: this desktop's monitor geometry — bounds, work area, per-monitor DPI/scale, which is primary, and the virtual-screen bounding box. Powers display-aware RDP sizing (so you never hardcode a resolution) and general screen-layout queries. Honors --target.++**Returns:** {ok, primary:{x,y,width,height,dpi,scale}, virtual:{x,y,width,height}, monitors:[{index, primary, bounds:{x,y,width,height}, workArea, dpi, scale, device}], count, _hint}++ ### 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.+Back-compat alias for `tunnel rdp`. v1.8.147: opens a VERIFIED tunnel (both legs probed) then launches mstsc on the SAME --target machine, sized to the laptop's real screen. Default size 90% of primary; pass size:"fullscreen" / size:"maximized" / monitor:<i> / width+height. The split-brain bug (bind on one machine, mstsc on another) is fixed — every leg resolves to one client. **Args:**-- `local` — optional int — local listener port on the desktop. Default 13389 (NOT 3389 — Windows' own Remote Desktop service usually owns 3389 and the bind silently coexists pre-v1.8.86). If the requested local is taken, AD falls back to an OS-assigned ephemeral port; the response's `localPort` + `actualPort` reflect what mstsc was actually pointed at.-- `to` — REQUIRED string — RDP target in `host:port` form. If port omitted, defaults to 3389.+- `height` — optional int — explicit window height (with width).+- `local` — optional int — local listener port. Default 13389. Falls back to ephemeral if taken; response.localPort is the real one.+- `monitor` — optional int — target monitor index (0=primary). See `display`.+- `size` — optional — "90%" (default) / "100%" / "fullscreen" / "maximized". Resolved laptop-side against the chosen monitor.+- `target` — optional string — which desktop; REQUIRED when >1 AD connected.+- `to` — REQUIRED string — RDP target `host:port`. Port omitted ⇒ 3389.+- `width` — optional int — explicit window width (with height); overrides size. -**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.+**Returns:** {ok, tunnelId, localPort, requestedLocal, fellBack, toHost, toPort, listening, upstreamReachable, mstscAlive, launch:{size,width,height,...}, _hint, _next}. ok=false names the failed leg.++**See also:** display, tunnel (the grouped verb), tunnel_check, tunnel_close ### refresh_bridges@@ -654,39 +854,54 @@ Programmatically enable/revoke shell_execute auto-approve. v1.8.3+. v1.8.116+: ` ### 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.+v1.8.85+ / reworked v1.8.147: generic TCP-over-the-relay tunneling, now a GROUPED verb: `tunnel open|close|list|check|rdp '<json>'`. AD binds a 127.0.0.1:<local> listener on the chosen desktop; the relay dials the `to` upstream; bytes ride the existing authed AD↔relay WebSocket (routed by tunnelId+connId). Works for ANY TCP service (RDP, SSH, VNC, HTTP, postgres…). v1.8.147 fixes the false-success bug: every leg resolves to the SAME --target machine, the open path VERIFIES both legs (AD self-probes the listener; the relay probes the upstream with retry/backoff) before reporting ok, and a failed open tears everything down (no leaked rows). Every response carries `_hint` (what's true now) + `_next` (the exact next verb, real values filled in). **Args:**-- `local` — REQUIRED int — the local 127.0.0.1 port the desktop should bind. mstsc / your TCP client connects HERE.-- `to` — REQUIRED string — upstream target in `host:port` form. The relay dials this when bytes arrive on the local port.-- `tunnelId` — optional u32 — caller-chosen ID. Defaults to a fresh random u32. Reuse across re-invocations to address the same tunnel.+- `local` — optional int — local port to bind. open: omitted ⇒ OS-ephemeral. rdp: default 13389 (NOT 3389 — that's the laptop's own RDP service). Falls back to ephemeral if taken; the response's localPort is what was actually bound.+- `size/monitor/width/height` — rdp only — display-aware sizing, resolved against the laptop's real screen. `size`:"90%"/"fullscreen"/"maximized"; `monitor`:<index> (0=primary); or explicit `width`+`height`. Default 90% of primary.+- `target` — optional string — which connected desktop (clientName/hostname). REQUIRED when >1 AD is connected (else returns ambiguous_target listing the names). Threaded so bind+probe+launch all hit this one machine.+- `to` — REQUIRED for open/rdp — upstream target in `host:port` form. The relay dials this.+- `tunnelId` — optional u32 — caller-chosen id (close/check address it). Defaults to a fresh random u32.++**Returns:** Per subcommand (see subcommands). On failure: {ok:false, failedLeg:'relay-to-ad'|'local-bind'|'upstream-refused', error, _hint, _next}. `_next` is copy-pasteable. -**Returns:** {ok, tunnelId, toHost, toPort, clientId, actualPort, error?, _hint}. On success, mstsc/anything connecting to 127.0.0.1:<actualPort> on the desktop has its bytes relayed.+**See also:** display (screen geometry for sizing), rdp/tunnel_close/tunnel_list (back-compat aliases), targets (list connected desktops). -**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_check++v1.8.147+: on-demand per-leg health for one tunnel. AD self-probes its local listener; the relay probes the upstream. Names exactly which leg is unhealthy. (Same as `tunnel check`.)++**Args:**+- `local` — optional int — select by bound local port instead+- `tunnelId` — u32 — from `tunnel list`++**Returns:** {ok, tunnelId, listening, relayConnected, upstreamReachable, connCount, failedLeg, _hint, _next} ### 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.+Back-compat alias for `tunnel close`. Tear down a tunnel (or one conn). Selector: tunnelId, local port, or all:true. Routed to the owning machine. **Args:**-- `connId` — optional u32 — if given, close only this conn; otherwise close the whole tunnel-- `tunnelId` — REQUIRED u32 — from `tunnel` / `tunnel_list`+- `all` — optional bool — true tears down EVERY tunnel + reaps leaks+- `connId` — optional u32 — close only this conn (needs tunnelId)+- `local` — optional int — close the tunnel bound to this local port+- `tunnelId` — u32 from `tunnel list` (or the string "all") -**Returns:** {ok, tunnelId, connId?, scope:'tunnel'|'conn', _hint}+**Returns:** {ok, tunnelId?, scope:'tunnel'|'conn'|'all', closed?, _hint, _next} ### tunnel_list -v1.8.85+: list all active tunnels — both the relay's view and AD's view (should agree).+Back-compat alias for `tunnel list`. Live tunnels with relay+desktop reconciliation; flags leaked rows so `tunnel close {all:true}` can reap them. -**Returns:** {ok, relay:[{tunnelId, toHost, toPort, clientId, connCount}, ...], desktop:{tunnels:[...]}, _hint}+**Returns:** {ok, count, tunnels:[{tunnelId, toHost, toPort, clientId, localPort, connCount}], desktop:[{client, tunnels}], leaks:[...], _hint, _next} ## Screen recording -_Record the desktop/monitors to video; manage the recorder window._ (7 verbs)+_Record the desktop/monitors to video; manage the recorder window._ (10 verbs) ### desktop_list_monitors @@ -739,6 +954,39 @@ Stop the active desktop clip. Finalizes the .webm. HUD stays open in Ready state **Returns:** recordingId, filePath, sizeKB, durationMs, stopReason +### desktop_record_window_start++Start a NATIVE single-window recording via Windows Graphics Capture (WGC) + Media Foundation. No picker, no capture banner (border suppressed on Win11), background-capturable (the target window can be occluded or off-screen). Distinct from desktop_record_start, which records the WHOLE desktop via Chrome getDisplayMedia (picker + banner + wake-lock). Resolve the window by hwnd (from desktop_list_windows) OR titleContains — same resolution as desktop_screenshot_window.++**Args:**+- `codec` — optional: 'h264' (default) | 'hevc'/'h265'+- `fps` — optional int 1-60 (default 30)+- `hwnd` — int window handle from desktop_list_windows (OR use titleContains)+- `titleContains` — case-insensitive substring of the window title (OR use hwnd)++**Returns:** recordingId, path (real Windows .mp4 under %TEMP%\adom-desktop-recordings\ — pull with pull_file), hwnd, title, fps, codec, width, height+++### desktop_record_window_status++Status of a WGC window recording.++**Args:**+- `recordingId` — required string from desktop_record_window_start++**Returns:** running, elapsedMs, path, frames, fps, codec, width, height, captureError+++### desktop_record_window_stop++Finalize a WGC window recording: stops capture, writes the MP4 moov atom, returns the on-disk path.++**Args:**+- `recordingId` — required string from desktop_record_window_start++**Returns:** path, elapsedMs, frames, width, height, fileExists++ ### desktop_recorder_close Close the desktop recorder HUD (ends the session). Auto-stops any active clip first.