name: adom-desktop-cli-guide description: Use when working with the adom-desktop CLI itself - how to run desktop verbs, discover commands (adom-desktop commands, help topics), address targets with --target, start the serve relay with --supervise, read the status/durationMs/timeoutSeconds response shape, stream long ops with watch, or sweep the screenshot cache with janitor.

The adom-desktop CLI

One Rust binary, two jobs: it runs the relay server (adom-desktop serve) and it sends verbs to the Adom Bridge app (adom-desktop <verb> '<json>'). Everything an AI does to the user's Mac goes through this surface. (The app is named Adom Bridge, formerly Adom Desktop; the CLI binary name is unchanged.)

The two binaries (same CLI, two homes)

Where Binary How it got there How it reaches the desktop
Cloud Linux container adom-desktop adom-wiki pkg install adom/adom-desktop (installs the CLI + the skill set) Over the relay it serves (WS :8765 / HTTP :8766)
The Mac, inside the app bundle adom-desktop-cli at Adom Bridge.app/Contents/Resources/ Bundled by the Bridge installer (standalone: /Applications; Hydrogen-managed: ~/Library/Application Support/adom/bridge/) Auto-probes the local app's loopback direct API (127.0.0.1:47200, discovery file ~/.adom/direct-api-port, fallback scan 47200-47209)

Verify which build you have:

adom-desktop --version
# adom-desktop 1.9.x (<git-sha>, built <timestamp>)

Discovering verbs: commands is THE catalog, not --help

adom-desktop --help lists only the real clap subcommands: serve and install. Every actual capability (200+ verbs) is a dynamic verb forwarded to the desktop app, and the authoritative machine-readable catalog is:

adom-desktop commands                    # full catalog: categories, args, returns, _hint per verb
adom-desktop --target <name> commands    # plus THAT box's installed bridges
adom-desktop help <topic>                # details for one verb or namespace (e.g. help pup, help shell)

Run commands FIRST when unsure what a verb is called. There is almost certainly a structured verb with typed args (process_list, process_kill, run_script, desktop_list_files) so you never hand-quote a command through JSON into a shell. shell_execute is the escape hatch for things with no verb yet, not the default.

Two kinds of verbs, do not confuse them:

  • Built-in core verbs are on every Adom Bridge, no install: screenshots, windows, input, files, shell, process, tunnel, update, notify, and the desktop_ui_* / desktop_find_control family (drive controls by NAME via macOS Accessibility; needs the Accessibility permission, granted to "Adom Bridge").
  • Bridge verbs (browser_*, kicad_*, fusion_*, nbrowser_*, ...) need that bridge installed. Check installed_bridges_on_target in commands, or run bridge_list.

An unknown verb returns errorCode:"unknown_verb" with a fuzzy did-you-mean _hint. Read it; do not conclude a capability is missing from a guessed name.

Recognized help namespaces (adom-desktop help <topic> or adom-desktop <topic> help): pup/browser, kicad, fusion, hd, shell, files, notifications, watch, setup, connectivity, system, record, server, bridge, wiki.

Invoking a verb

adom-desktop <verb> '<json-args>'
adom-desktop ping
adom-desktop shell_execute '{"command":"echo hello","reason":"smoke-testing the shell path"}'
adom-desktop desktop_screenshot_window '{"hwnd": 12345}'

Output is pretty-printed JSON on stdout.

Grouped verbs take a bare-word subcommand before the JSON:

adom-desktop tunnel open '{"to":"127.0.0.1:5900"}'
adom-desktop tunnel list

Gated verbs need a reason. Verbs that require human approval over the relay (shell_execute, run_script, and the rest of the permission class) require a reason arg: one plain sentence the user reads verbatim in the approval prompt. A missing reason returns errorCode:"reason_required".

The uniform response shape

Every result carries, on every path (relay, direct API, bundled Mac CLI):

  • status: ok | timeout | busy | error. One authoritative field. An empty stdout with status:ok is a real empty-output success, NOT a timeout.
  • durationMs: the CLI's measured round-trip.
  • _hint: actionable guidance authored by Bridge core (recovery steps on timeout/busy, next-verb pointers on success). The CLI only authors a hint for the one case Bridge cannot: the call never reached the desktop at all.
  • timeoutSeconds: the HTTP budget that governed this call. On a timeout, bridge verbs return errorCode:"timed_out" with stillRunning:true: the operation usually keeps running on the bridge. Poll the response's statusVerb instead of re-issuing.
  • Exec verbs additionally carry exitCode / stdout / stderr.

adom-desktop timeouts returns the per-verb budget registry (long verbs, their budgets, and the status verb to poll).

Targeting: one relay, many desktops

One relay can hold several Adom Bridges (a Mac laptop + another machine + ...). Each has a name (--client-name launch flag / Settings field / hostname default).

adom-desktop targets                              # {count, targets:[{name, hostname, version, os, connectedAt}]}
adom-desktop --target mymac desktop_screenshot_screen
adom-desktop --target all ping                    # fan out; results keyed by name + summary
  • --target is a global flag: works before or after the verb, case-insensitive, matches clientName then hostname. ADOM_TARGET=<name> sets a default; the flag wins.
  • Exactly ONE desktop connected: no --target needed.
  • Multiple connected, no --target: the call fails with error:"ambiguous_target" (exit 1) and lists the connected names. It never guesses. A bad name returns target_not_found.
  • --target all returns {broadcast:true, results:{<name>:...}, summary:{total,ok,failed}}; a per-desktop failure is isolated to that desktop's entry.
  • Caveat: ping has its own direct-API path and does NOT honor --target. Smoke-test routing with a relay-path verb (server_list, desktop_*).
  • Mac clients report os: macOS in the targets listing.

Local vs relay routing

  • No --target on the Mac: the bundled CLI short-circuits to the local app's loopback direct API. No relay required. This is the right path for anything on the same machine.
  • --target <name> (or ADOM_TARGET): the call traverses the relay, even from the desktop itself, so it can reach a named or broadcast set of machines.
  • In a container: everything goes through the relay the container runs.

The relay: serve --supervise in containers

The relay must be running before any desktop can connect. In a container, ALWAYS start it supervised:

adom-desktop serve --supervise                       # detached + self-healing
adom-desktop serve --supervise --owner MyMac         # send restart toasts to one desktop
adom-desktop serve --stop                            # stop watchdog + relay

Why not adom-desktop serve &? A backgrounded relay is still a child of your session. When the session is torn down, the relay dies with it, and every connected desktop loops "Reconnecting in 30s" for hours until a human restarts it. --supervise detaches the relay into its own session (reparented to init) and runs a watchdog that restarts it with exponential backoff (2s up to a 5 min cap; a clean run resets it) and toasts the connected desktops with the reason on each restart.

Plain foreground serve still works and prints a loud warning that it dies with the session. Caveat: a full container reboot still needs a boot hook (cron @reboot or the container's init); the watchdog covers session teardown, not host reboot.

What serve binds:

  • WebSocket on 0.0.0.0:8765 (desktop apps connect here)
  • HTTP API on 127.0.0.1:8766 (CLI commands go here)

Health check: curl -sf http://127.0.0.1:8766/health. If another relay is already answering, serve skips with a friendly message; --force-bind overrides (only for deliberate parallel relays).

watch: stream long operations

For long-running walks, use the built-in watch wrapper instead of a polling loop. It spawns the inner command, polls status internally, and emits one JSON event per line to stdout as progress happens:

adom-desktop watch '{"command":"fusion_walk_cloud_tree","args":{"projectName":"Main","nameContains":"BQ25792"}}'
adom-desktop watch '{"command":"fusion_walk_cloud_tree","args":{...},"interval":1}'

Watchable commands (whitelist): fusion_walk_cloud_tree, fusion_search_cloud_files. Others return success:false with a _hint listing the watchable set. Do not use it for short operations that return in under 2 seconds. From Claude Code, pipe watch into the Monitor tool for real-time events.

janitor: the screenshot cache

Every screenshot verb auto-pulls its PNGs to a local cache on the caller's machine (~/project/screenshots/adom-desktop/ when ~/project exists, else ~/.adom/screenshots/; never /tmp), bucketed into window/ and screen/ with a JSON sidecar per shot. The CLI-local janitor verb (no relay) keeps it bounded:

adom-desktop janitor                     # status: managed dirs, sizes, policy, last sweep
adom-desktop janitor '{"sub":"run"}'     # force a sweep now

A sweep also runs opportunistically at most once per 24h on any CLI call. Policy is age + size capped, newest files kept; sidecars follow their PNG. Do not build your own cleaner: verify with janitor status instead.

Environment variables

Variable Effect
ADOM_API Relay HTTP API base URL the CLI talks to (default http://127.0.0.1:8766). Point it at a proxied relay API to reach a remote relay directly.
ADOM_TARGET Default --target value; the flag wins.
ADOM_WS_PORT / ADOM_HTTP_PORT serve port overrides (defaults 8765 / 8766).
ADOM_AUTH_TOKEN serve auth token.
ADOM_JANITOR=0 Disable the opportunistic cache sweep.
ADOM_SHOTS_DIR Override the screenshot cache directory.