Commands
This page summarizes the primary command groups.
For persistent defaults and project-scoped CLI settings, see Configuration.
For agent workflow guidance that is matched to the installed CLI, run:
Skills are recommended for auto-routing when your agent runtime supports them, but they are not required. The CLI help topics are the version-matched operating contract.
For MCP-aware clients that support direct tools, run:
The MCP server exposes direct structured tools for installed commands. Tools use structured input contracts through AgentDeviceClient; local-only workflows stay CLI-only rather than subprocess fallbacks. It does not expose generic shell execution over MCP. MCP tools can target platform: "web" after agent-device web setup, but setup and doctor stay CLI-only.
Navigation
bootensures the selected target is ready without launching an app.bootrequires either an active session or an explicit device selector.shutdownturns off the selected Apple simulator or Android emulator.shutdownmust not target an active session device; useclose --shutdownto end the session and turn it off.daemon stop --state-dir <path>verifies the daemon PID/start-time identity, requests graceful shutdown, and reports whether provider-release state is known. Usedaemon stop --cleanto also remove retained Apple runner processes and leases owned by that daemon.device statusreads host-local device claims without starting or contacting a daemon. Normal output shows live and attention-needed claims, then summarizes proven-stale records in one line; usedevice status --staleto inspect the hidden records. Scope either view with--platformplus--udid(Apple) or--serial(Android). A foreign live or uncertain claim blocksopen; a proven-dead owner is replaced only after its session's exact-owner durable resources reconcile successfully.device release --stalesettles a provably dead owner's durable resources through the same exact-owner reconciliationopenuses and clears its claim last, all without a daemon. Live, uncertain, PID-reused, and corrupt claims always fail closed and are reported with the reason; a live owner is released by closing its session from its own workspace or withdaemon stop --state-dir <owner state dir>.--platform appleis an alias for the Apple automation backend (ios,tvOS,macOSselection).- Use
--target mobile|tv|desktopwith--platform(required) to select phone/tablet vs TV-class vs desktop-class targets. bootis mainly needed when starting a new session andopenfails because no booted simulator/emulator is available.- Android:
boot --platform android --device <avd-name>launches that emulator in GUI mode when needed. - Android: add
--headlessto launch without opening a GUI window. - Android:
shutdown --platform android --device <avd-name>stops a running emulator. open [app|url] [url]already boots/activates the selected target when needed.open <url>deep links are supported on Android and iOS.open <app> <url>opens a deep link on iOS.open <app> --launch-console <path>captures launch-time stdout/stderr for direct iOS simulator app launches. It is not valid for URL opens or non-simulator targets.open --device-hubuses Xcode Device Hub when surfacing Apple simulators.open --platform macos --surface app|frontmost-app|desktop|menubarselects the macOS session surface explicitly.appis the default when an app argument is provided.backnow defaults to app-owned back navigation. On Apple targets that means visible in-app back UI only. On Android this currently maps to the same back keyevent because Android routes in-app back through that platform event.back --in-appis an explicit alias for the default app-owned behavior.back --systemasks for system back input explicitly. On Android this is the normal back keyevent. On iOS and tvOS it uses the platform back gesture or Siri Remote menu action. On macOS, where there is no generic system back input,back --systemreports unavailable instead of falling back to app-owned navigation.orientation <orientation>forces a mobile device intoportrait,portrait-upside-down,landscape-left, orlandscape-right.orientationis supported on iOS and Android mobile targets. macOS and tvOS do not expose it.- On iOS devices,
http(s)://URLs open in Safari when no app is active. Custom scheme URLs require an active app in the session. - Commands that need one concrete device refuse to guess: if no
--device/--udid/--serialis given and several candidates are equally preferred (for example two booted emulators), the command fails withAMBIGUOUS_MATCHand lists them, rather than picking one and returning a successful answer about a device you did not select. Preferences still apply first — virtual over physical, booted over offline — so one booted emulator beside offline ones resolves normally, as does any command running inside an existing session.deviceslists everything as before. - Commands that omit
--sessionuse an implicitdefaultsession scoped to the caller's current git worktree or working directory. This keeps independent local agents from accidentally attaching to each other's default session. --session <name>orAGENT_DEVICE_SESSIONopt into an explicitly named session when a script intentionally wants to share or reuse that session name.- A configured
AGENT_DEVICE_SESSIONimplies bound-session lock mode by default. The CLI forwards that policy to the daemon, which enforces the same conflict handling for CLI, typed client, and direct RPC requests. --session-lock reject|stripandAGENT_DEVICE_SESSION_LOCK=reject|stripremain available for explicit named-session automation.stripresolves conflicts by dropping platform and scope selectors (--platform,--target,--ios-simulator-device-set,--android-device-allowlist) only. A selector that names a different device than the lock —--udid,--serial,--device— is never dropped: the request fails withINVALID_ARGSnaming both the requested and the bound device, because continuing would run the command against a device the caller did not select. Recover by closing the bound session if the requested device is the one you want, or by removing the selector if the bound device is.- Direct RPC callers can pass
meta.lockPolicyand optionalmeta.lockPlatformonagent_device.commandrequests for the same daemon-enforced behavior. - In
batch, steps that omitplatformstill inherit the parent batch--platform; lock-mode defaults do not override that parent setting. - Tenant-scoped daemon runs can pass
--tenant,--session-isolation tenant,--run-id, and--lease-idto enforce lease admission. - Remote daemon clients can pass
--daemon-base-url http(s)://host:port[/base-path]to skip local daemon discovery/startup and call a remote HTTP daemon directly. - Use
--daemon-auth-token <token>(orAGENT_DEVICE_DAEMON_AUTH_TOKEN) for explicit service/API-token automation against non-loopback remote daemon URLs; the client sends it in both the JSON-RPC request token and HTTP auth headers. - Use Remote Proxy when you need to run
agent-device proxyon a Mac with simulator/device access and drive it from another machine through cloudflared, ngrok, or another HTTP tunnel. - Use BrowserStack or AWS Device Farm when a CI agent needs a hosted device session without interactive login.
- For human cloud access,
connectcan discover a cloud connection profile, whileconnect --remote-config ...uses a local profile. Both refresh a stored CLI session into a short-livedadc_agent_...token when needed. If no CLI session exists, interactive shells start login automatically; CI and non-interactive shells fail with API-token setup instructions. Use--no-loginto disable implicit login.AGENT_DEVICE_CLOUD_BASE_URLis the bridge/control-plane API origin; its/api-keysroute may redirect to the dashboard for token creation. - For remote
connectandconnect --remote-configflows, see Remote Metro workflow. - Android React Native relaunch flows require an installed package name for
open --relaunch; install/reinstall the APK first, then relaunch by package.open <apk|aab> --relaunchis rejected because runtime hints are written through the installed app sandbox. - For Metro-backed React Native JS changes, use
metro reloadbeforeopen <app> --relaunch; it mirrors pressingrin the Metro terminal and keeps the native process alive. - Remote daemon screenshots and recordings are downloaded back to the caller path, so
screenshot page.pngandrecord start session.mp4remain usable when the daemon runs on another host.
Human Takeover
Use takeover with an active remote connection when a person needs to interact with its leased
device without racing the agent:
The command uses the device from the admitted remote lease, installs a short-lived hold, keeps it
alive in the foreground, and releases it on Ctrl+C. Activation waits for admitted mutations to finish
before reporting active. While held, state-changing commands fail with
DEVICE_IN_USE and details.reason: "human_control_active", explaining that agent interactions are
temporarily disabled. Snapshots, screenshots, selector reads, logs, and other read-only diagnostics
remain available. The hold also
protects an existing remote device lease from inactivity expiry so the human does not accidentally
hand the simulator to a different agent.
A foreground hold expires automatically if its process disappears. Releasing or expiring the final hold refreshes the existing lease's inactivity window. Tenant commands can modify only holds owned by their admitted lease, not provider-host administrative holds.
Holds do not survive daemon restart; reconnect and re-establish them before continuing human interaction. Local takeover without a device-scoped remote lease is not supported in this version. See remote takeover and host administration for the VM-side API.
Web Automation
Minimal --platform web support reuses agent-browser. agent-device owns command/session/replay integration, refs/selectors, and artifact routing; agent-browser owns browser launch, page control, screenshots, and browser-specific mechanics.
Use --platform web when a browser step belongs inside an agent-device session, replay, batch, MCP, or typed-client flow. Use agent-browser directly for standalone web automation.
Set up and verify the managed web backend before relying on web sessions:
- Web automation uses a managed, pinned
agent-browserbackend as an implementation detail. - Run
web setupbefore first use and in CI bootstrap steps. Normal--platform webcommands do not install the backend implicitly. - Runtime web commands resolve the backend only from the managed install in the effective agent-device state dir.
web setupis idempotent and reuses the pinned backend when it is already installed.web doctorverifies the managed backend after setup.- The managed install respects
--state-dirandAGENT_DEVICE_STATE_DIR. - Web automation requires Node 24+.
- Supported through
agent-device: URL open, snapshot refs,get text/attrs,is visible/exists/text,find text/selector, click/press, hover, fill/type, wait,network dump,audio probe, screenshot, close, and replay scripts composed from those commands. hover <@ref|selector|x y>moves the pointer without pressing so hover-gated UI (row toolbars, menus) appears. Add--settleto read what it revealed instead of taking another snapshot.hover @refhovers the browser's own element handle; likeclick @ref --settle, the--settlediff needs a selector or coordinate target on web because web refs carry no geometry.audio probe start [durationSeconds] [bucketMs]samples HTML media elements into compact RMS/peak dBFS buckets while the page keeps running. The first timing positional is seconds; the second is milliseconds.- URL-backed web media may be routed through the probe
AudioContextwhile observed. Useaudio probe statusto poll partial buckets andaudio probe stopto end the probe early. - Out of scope for
agent-deviceweb support: tab/window/devtools control, network routing/interception/HAR, cookies/storage, downloads/uploads, arbitrary page scripting, multi-page orchestration, and raw browser diagnostics. Useagent-browserdirectly for those browser-specific workflows.
Device isolation scopes
--ios-simulator-device-set <path>constrains simulator discovery and simulator command execution viaxcrun simctl --set <path> ....--android-device-allowlist <serials>constrains Android discovery/selection to comma or space separated serials.- Scope is applied before selectors (
--device,--udid,--serial), so out-of-scope selectors fail withDEVICE_NOT_FOUND. - With iOS simulator-set scope enabled, iOS physical devices are not enumerated.
- Device scoping can also be configured with
iosSimulatorDeviceSetandandroidDeviceAllowlistconfig keys. Android allowlists can useAGENT_DEVICE_ANDROID_DEVICE_ALLOWLIST. - CLI scope flags override environment values unless bound-session lock mode is active with
strip, in which case conflicting per-call selectors are ignored.
Device discovery
deviceslists available targets after applying any platform selector or isolation scope flags.- Use
--platformto narrow discovery to Apple-family (ios,tvOS,macOS), Android, HarmonyOS, or Vega OS targets. - Use
--ios-simulator-device-setand--android-device-allowlistwhen you need tenant- or lab-scoped discovery. capabilitiesreports the command names supported by the selected session device or an explicit--platform/--device/--udid/--serialtarget.- In JSON output,
capabilitiesreturns{ device, availableCommands }. UseavailableCommandsfor dynamic integrations instead of maintaining a separate platform support table.
HarmonyOS command boundary
HarmonyOS support uses HDC and ArkUI uitest. On current API 24 devices it supports lifecycle and HAP deployment, ArkUI snapshot/screenshot and selector reads, one-pointer touch and text actions, keyboard enter/dismiss, app logs, foreground app state, process RSS samples, and settings clear-app-state. Run agent-device capabilities --platform harmonyos for the authoritative command list for a selected device.
gesture pan|fling|swipeandswipeuse HDC's single-pointer input primitives. Multi-touch gestures and target-authoredgesture dragreturnUNSUPPORTED_OPERATIONrather than approximating the interaction.- Physical HarmonyOS devices support whole-screen recording through
record start <path> --scope deviceor--scope system. This uses the device ScreenRecorder service, not HDCscreenrecord; emulator recording is explicitly rejected. HarmonyOS recording does not support--fps,--quality, or--hide-touches. - Orientation control, clipboard, alert automation, network/audio capture, push and app-event delivery, React Native helpers, and trace capture are not advertised for HarmonyOS. The public API 24 HDC surface has no usable
pasteboard, notification, oraa sendcommand on the supported emulator and physical device. settingsintentionally supports onlyclear-app-state; other system settings are not changed through undocumented parameter writes.perf memory sampleprovides process RSS; frame health and memory-snapshot artifacts remain unavailable.
Diagnostics
doctordiagnoses device, app, Metro, and React Native setup/readiness issues for the selected target.- Use
--platform ios|android|vega|macos|linux|web|appleto scope the checks to one backend; without it,doctorreports across the discoverable targets. --app <id-or-name>focuses app-specific checks (install state, Metro/React Native wiring) on a single bundle id or app name.--remoteruns the environment-only checks that do not require a booted device, which is what CI bootstrap and the packaged-CLI smoke use.doctoris read-only: it never boots, installs, or mutates the session device.
Prepare Apple runner
prepare ios-runneris intended for Apple-platform CI setup beforesnapshot,replay, ortest.- Run it after the simulator/device is booted and the app is installed, but before the first snapshot, replay, or test command.
- It builds or reuses the local XCTest runner, starts a runner session, and verifies that the runner can answer a lightweight health command.
- In JSON output, top-level
buildMs,connectMs, andhealthCheckMsare diagnostic fields and may overlap; usetiming.additivePartsfor additive wall-clock phase totals.connectMscontainsbuildMswhen a runner artifact is built or rebuilt. - If health checking exposes a bad restored runner artifact, Agent Device marks that artifact bad and rebuilds once.
- If a fresh runner launch gets stuck before accepting connections, Agent Device invalidates that runner session and launches it once more without forcing a rebuild.
- CI may cache
~/.agent-device/apple-runner/derivedwhen the cache key includes the exact Agent Device package contents and selected Xcode version. - Avoid broad
restore-keysfallbacks for runner caches. Reusing runner artifacts across Agent Device or Xcode versions can restore stale.xctestrunproducts;prepare ios-runneralready handles bad exact-cache artifacts and one retryable non-connecting runner launch. - Runner build/start output is written to the session's
runner.log. The top-leveldaemon.logis reserved for daemon lifecycle/startup issues.
TV targets
- AndroidTV app launch and app listing resolve TV launchable activities via
LEANBACK_LAUNCHER. - TV target selection supports Apple TV and Android TV simulators/emulators and connected devices. Initial Vega OS support is limited to the Vega Virtual Device.
- TV targets are focus-first. Use
tv-remoteto move D-pad/remote focus before selecting a control; avoid rawadb shell input keyeventin command plans. - On Android TV,
tv-remotemaps to ADB keyevents.tv-remote longpress <button>is CLI sugar for a 500ms hold;--duration-msoverrides the preset and uses Android's longpress keyevent form for any positive duration because the platform command does not expose exact hold timing. - tvOS supports the same runner-driven interaction/snapshot flow as iOS (
snapshot,wait,press,fill,get,scroll,back,home,app-switcher,record, and related selector flows). - On tvOS,
tv-remote, runnerback/home/app-switchermap to Siri Remote actions (backis Menu,homeis Home, app switcher is double-home).--duration-msis an exact remote-button hold duration. - Vega OS discovery and remote input use the SDK-matched Vega CLI and VDA. Initial support is VVD-only; use
--platform vega --target tv, and use Vega component IDs such ascom.example.app.main. - Use
--serial VirtualDevicefor explicit VVD selection. - The Vega VVD supports app open/close,
back,home, and all sharedtv-remotebuttons. Exact holds are sent throughinputd-cli. - Physical Fire TV, app inventory, snapshot, screenshot, selector, install, touch/text/gesture, logs, and performance backends are not part of the initial support and report unsupported.
- On Android TV and tvOS, use
screenshot --overlay-refswhen visual focus evidence is useful or when focus metadata is unavailable/transient. On Vega OS, use the VVD display as visual truth. - tvOS follows iOS simulator-only command semantics for helpers like
gesture pinch,settings, andpush.
Desktop targets
--platform macosselects the host Mac as adesktoptarget.--platform apple --target desktopselects the same macOS backend through the Apple-family alias.- Use
appsessions for normal app control:open,snapshot,click,fill,press,scroll,back,screenshot,record. - Use
frontmost-app,desktop, andmenubarwhen you need to inspect desktop-global UI before choosing one app. open --platform macos --surface frontmost-appinspects the currently focused app without naming it first.open --platform macos --surface desktopinspects visible windows across the desktop.open --platform macos --surface menubarinspects the active app menu bar and system menu extras.open <app> --platform macos --surface menubartargets one menu bar app's extras bar, which is useful for status-item apps.- Status-item apps often expose little or no useful UI through the default macOS
appsurface. Prefer--surface menubarfor discovery when the app lives in the top menu bar. - Use
frontmost-app,desktop, andmenubarmainly forsnapshot,get,is, andwait. - If you inspect with
desktopormenubarand then need to click or fill inside one app, open that app in a normalappsession. - macOS also supports
clipboard read|write,trigger-app-event,logs,network dump,audio probe,alert,settings appearance, andsettings permission <grant|reset> <accessibility|screen-recording|input-monitoring>. audio probe start 10 1000 --platform macossamples host system audio through ScreenCaptureKit. The same host-system audio backend is used for iOS simulators and Android emulators on macOS hosts; grant Screen Recording permission before relying on it in a run.- In macOS app sessions,
screenshotcaptures the target app window bounds rather than the full desktop. - Prefer selector or
@ref-driven interactions on macOS. Window position can shift between runs, so raw x/y point commands are less stable than snapshot-derived targets. - Use
click --button secondaryfor context menus on macOS, then runsnapshot -iagain. - Mobile-only helpers remain unsupported on macOS:
boot,shutdown,home,orientation,app-switcher,install,reinstall,install-from-source, andpush.
Recommended loops:
Snapshot and inspect
- iOS snapshots use XCTest on simulators and physical devices. iOS
--rawis the acquired tree on whichever backend serves the capture: it keeps offscreen nodes, decorations, and structural wrappers the default and-iviews fold away, so a recovered raw capture shows the same hierarchy a healthy one does.--depthstill applies to raw (it counts traversal depth there), while-inarrows the default projection only —--raw -ireturns the acquired tree. - Android snapshots require the bundled Android snapshot helper. The first snapshot verifies and
installs the helper APK if it is missing or outdated. Local ADB-backed sessions keep the helper
process warm over an
adb forwardsocket and reportandroidSnapshot.helperTransportaspersistent-session; if that transport is unavailable, capture retries through one-shot instrumentation in the same helper. SetAGENT_DEVICE_ANDROID_SNAPSHOT_HELPER_SESSION=0to disable the persistent fast path. Missing or failed helper artifacts are reported directly; a source checkout must runpnpm build:androidbefore Android verification. The helper serializes Android interactive window roots when available, so keyboard and system-overlay nodes can appear alongside the app root;androidSnapshot.captureModeandandroidSnapshot.windowCountdescribe the capture. Default and-isnapshots keep same-window covered surfaces visible for diagnosis and mark exactly ordered covered controlsinteractionBlocked: "covered", so selectors cannot act on stale React Native screens. API 23 cannot report siblingdrawing-order, so this scan fails conservative andandroidSnapshot.occlusionScanUnavailable: truediscloses the difference. Android--rawis the acquired tree: it also keeps nodes Android marks invisible and stale application windows. The helper does not reportchecked/selectedstate, and it caps captures at 5000 nodes before any--scopeapplies (truncated: true). --scope <text|@ref>returns the subtree of the first node in document order whose label, value, or identifier contains the scope text (case-insensitive) and whose subtree still has content in the requested projection, re-rooted at depth 0; no match returns an empty snapshot rather than the full tree. Under-ithat means scoping to a layout container returns the actionable elements inside it, even when the container itself is filtered out.--depththen counts from the scope root.@refscopes by that element's label from the last snapshot. Android resolves scope inside its TypeScript presentation; iOS keeps acquisition broad and resolves scope once inside the runner's Swift presentation. The daemon does not reapply scope after either platform returns.--actionsnames the custom accessibility affordances an element merged away (iOSUIAccessibilityCustomAction, React NativeaccessibilityActions), so a card whose reply/options controls are not separate elements still lists them. It is iOS-simulator-only and exists for planning, not invocation: there is no API to trigger a named action, so reach the affordance through the element's detail screen, the same control exposed as a labeled element elsewhere, or coordinates from its rect. It is mutually exclusive with--raw, which takes a capture path that cannot carry custom actions: the pair is rejected asINVALID_ARGSbefore any device work. See Snapshots for the full constraints.diff snapshotcompares the current snapshot with the previous session baseline and then updates baseline.snapshot --diffis an alias fordiff snapshot.- Default snapshot text is an agent-facing, token-efficient view for planning and targeting actions. It may collapse helper/accessibility noise; use
--rawor--jsonwhen you need the full provider tree.
Wait and alerts
waitaccepts a millisecond duration,text <value>, a snapshot ref (@eN), or a selector.wait <selector> [timeoutMs]polls until the selector resolves or the timeout expires.wait @ref [timeoutMs]requires an existing session snapshot from a priorsnapshotcommand.wait @refresolves the ref to its label/text from that stored snapshot, then polls for that text; it does not track the original node identity.- Because
wait @refis text-based after resolution, duplicate labels can match a different element than the original ref target. waitshares the selector/snapshot resolution flow used byclick,fill,get, andis.- Wait failures carry a structured
error.details.reasonin--jsonoutput:wait_target_absentproves at least one readable capture saw no match;wait_capture_stalledmeans no readable capture arrived and is retriable;wait_deadline_exceededmeans a later capture consumed the remaining budget after an earlier readable capture;wait_landmark_identity_mismatchis a replay destination-guard refusal; andwait_stable_timeoutmeans the UI did not settle. UsereadableCapturesandwaitedMsinstead of parsing error text. alertinspects or handles system alerts on iOS simulator, macOS desktop, and Android native/runtime permission dialogs.alertwithout an action is equivalent toalert get.- Use
alert getfor an immediate cheap check. Usealert wait <short-ms>only when a prompt may appear after async work. - Android support is snapshot-derived. If
alertreports no alert but a sheet is visible, treat it as app-owned UI and usesnapshot -ipluspressby visible label/ref. - If an iOS permission sheet is visible in
snapshotorscreenshotbutalert acceptreports no alert, fall back to a scopedsnapshot -i -s "<visible label>"pluspress @ref; not every simulator permission surface is exposed as a native XCTest alert.
Interactions
fill clears then types. type does not clear.
type accepts text only. Do not pass @ref to type; use fill @ref "text" to target a field directly, or press @ref then type "text" to append in the focused field.
If type reports TEXT_INPUT_NOT_FOCUSED, focus a visible text input and retry; when accessibility does not expose the input, use a coordinate focus command before typing.
On iOS, if type reports TEXT_INPUT_SYNTHESIS_UNAVAILABLE while the software keyboard is hidden, show the software keyboard, then retry type or fill. The runner reports this error instead of risking partial input through an unreliable text-entry path.
On iOS, if type or fill reports TEXT_INPUT_COMMIT_NOT_OBSERVED, the runner could not confirm the typed text reached the field — either it did not land before the runner's deadline, or the expected final text is identical to the field's placeholder. In the latter case, accessibility cannot distinguish committed text from an empty field rendering that placeholder, even if the field held content before dispatch. The field may hold none, part, or all of the text: run snapshot -i and inspect it. If it already matches, continue; otherwise retry with the full text quoted and fill --delay-ms 80, which replaces the whole value. Do not use type, which appends to whatever committed. This covers the bare-type route and the coordinate-driven fill route taken when the accessibility channel is under load, both of which observe the field after synthesizing; it is not a guarantee that every text-entry route verifies its result.
Use plain fill or type first for ordinary login and form fields. Use --delay-ms on type or fill only when a debounced search field or search-as-you-type input actually misses characters, or when the app must receive incremental updates.
Delayed typing intentionally prefers paced character entry over clipboard-style fallbacks so the target field receives each incremental update.
On Android, fill also verifies text and treats IME-owned capture as a terminal failure instead of retrying against the wrong field.
Android text entry is owned by agent-device: provider-native injection when available, then chunk-safe ASCII shell input. Do not switch to raw adb, clipboard, or paste as an agent fallback. If non-ASCII is unsupported in the current backend, report the tool/device gap.
click --button secondary is the desktop context-menu flow on macOS.
click --button middle is reserved for future runner support and currently returns an explicit unsupported-operation error on macOS.
swipe is a quick, fixed-duration directional throw. Use gesture pan for deliberate timed movement.
Neither swipe nor gesture fling takes a duration, and gesture rotate takes no velocity — see
Migrating Gestures if you have scripts or recordings that still pass one.
Repeated coordinate swipes accept at most 200 repetitions and 10000ms pauses, and their combined
gesture/pause schedule must fit within 60000ms.
gesture pan accepts x y dx dy [durationMs] for deliberate drags. It uses one pointer by default. Add --pointer-count 2 for a parallel two-finger pan with constant contact span and angle; this shares the bounded two-contact synthesizer used by transform while retaining pan intent. Android preserves the requested travel duration; iOS uses XCTest drag primitives for one-pointer pan and private XCTest synthesis for two-pointer pan.
gesture drag accepts source destination [sourceHoldMs] [moveMs] [destinationHoldMs], where each endpoint is a selector or a snapshot ref. It resolves both endpoints before dispatch and keeps one pointer down continuously through activation, movement, and the optional destination hold. Defaults are 800ms, 500ms, and 0ms; the combined gesture is capped at 10000ms. Recordings convert refs to selector chains so saved .ad scripts remain portable.
Target-authored drag is supported on Android touch devices and iOS/iPadOS. Backends that cannot preserve all three authored phases reject it before injection.
gesture fling accepts up|down|left|right x y [distance] for fast directional throws.
gesture rotate accepts degrees [x] [y]; the degree sign controls direction. Pacing is derived from the requested rotation.
gesture transform accepts x y dx dy scale degrees [durationMs] for one combined two-finger pan/zoom/rotate gesture on Android and iOS simulators. Pinch, rotate, two-finger pan, and transform use the same viewport-aware pointer planning; impossible paths fail before injection instead of clamping or distorting the requested motion.
On iOS simulators it uses private XCTest synthesis for a continuous two-finger pan/scale/rotation path, so verify app-level metrics instead of assuming the requested values map exactly to recognizer output.
On Android, gesture transform injects a geometric two-finger path. App recognizers may report non-exact pan, scale, and rotation values, so verify qualitative state such as pan changed yes, pinch changed yes, and rotate changed yes unless the app explicitly promises exact centroid metrics. If exact app-state values matter, prefer isolated gesture pan, gesture pinch, or gesture rotate commands.
scroll accepts either a relative amount (0.5 means a finger path spanning half of the viewport on that axis) or --pixels <n> for a fixed-distance gesture. The final content offset can differ because apps apply pan-recognition thresholds, collapsing headers, bounds, and their own scroll physics. Large distances are clamped to the usable drag band so the gesture stays reliable across Android, iOS, and macOS.
Default snapshot text output is visible-first, so off-screen interactive content is summarized instead of shown as tappable refs.
When a target only appears in an off-screen summary, use scroll <direction> --settle: the response waits for the UI to go quiet and returns the diff against the tree you last observed, with fresh refs on the added lines, so no follow-up snapshot -i is needed. back --settle does the same for navigation. Both are best-effort and never fail the action. For repeated checks without settle, a small shell loop is enough:
longpress is supported on iOS and Android.
hover is supported on web only. It moves the pointer over a target (@ref, selector, or coordinates) without pressing, so hover-gated UI such as row toolbars and menus appears; touch platforms have no hover state and reject it. Use --settle to get the diff of what the hover revealed and act on the fresh refs; use longpress for the mobile hold-gesture equivalent.
gesture pinch is supported on Android and iOS simulator app sessions.
gesture rotate is supported on Android and iOS simulator app sessions. Use orientation for device orientation.
Two-finger gesture pan and gesture transform are supported on Android and iOS simulator app sessions. One-finger gesture pan keeps the broader platform support of ordinary coordinate drags.
Find (semantic)
Actions: click (default; press/tap are aliases), list, focus, fill, type, exists, wait, get text, get attrs. list is read-only — it returns every match with its @ref and never taps, so use it to inspect before acting. Ambiguous matches are rejected with a candidates listing for text and selector queries alike; --first/--last opt into positional narrowing explicitly.
Assertions
isevaluates UI predicates against a selector expression and exits non-zero on failure.- Supported predicates are
visible,hidden,exists,editable,selected, andtext. is visiblechecks whether the resolved element is present in the current visible snapshot viewport. A node without its own rect still passes when a visible ancestor within the viewport provides the on-screen geometry.is existsonly checks whether the selector matches in the current snapshot.wait textis a text-presence wait, not a hittability assertion.is text <selector> <value>compares the resolved element text against the expected value.isdoes not accept snapshot refs like@e3; use a selector expression instead.isaccepts the same selector-oriented snapshot flags asclick,fill,get, andwait.
Replay
replayruns deterministic.adscripts.- Script paths belong to the caller:
replay <path>andtest <path-or-glob>are resolved and read by the client, which sends the script content (and any MaestrorunFlowincludes) with the request. The same command therefore works against a local daemon and against a remote one (AGENT_DEVICE_DAEMON_BASE_URL) with no copy step, and a script missing on the calling machine fails immediately, naming the path you typed. replay --keep-sessionsuppresses exactly an authored terminalclosein native.ad; interior closes still run, and a close-less script is unchanged. The option is rejected bytestand Maestro YAML.testruns one or more.adscripts as a serial suite from files, directories, or glob inputs.test --platform <platform>filters suite files bycontext platform=...metadata instead of overriding the script target.test --timeout <ms>andtest --retries <n>apply per script attempt;context timeout=...andcontext retries=...can be declared inside the.adheader. Retries are capped at3, duplicate metadata keys are rejected, and timeouts are cooperative.test --artifacts-dir <path>overrides the default suite artifact root at.agent-device/test-artifacts.testprints a shortRunning replay suite...line before dispatch, then streams one-linepass,fail, orskipprogress on stderr as each suite entry finishes or retries. Each line includes current/total suite position and elapsed seconds such aspass 3/6 ... duration=12.34s. The final summary still prints failures and flaky passed-on-retry tests by default; add--verboseto print every final result.- A failing step returns a
REPLAY_DIVERGENCEreport (screen digest, ranked selector suggestions, and aresumefield);replay --from <n> --plan-digest <sha256>resumes at and executes plan stepnwithout re-running1..n-1. If the failed action was completed manually, resume from the next safe plan index using the matching digest.replay-only;testrejects--from. replay -u/--updateno longer rewrites the script (retired — see Replay & E2E); it is a no-op kept for compatibility, since every divergence already carries the same ranked suggestions.--save-scriptrecords a replay script onclose; optional path is a file path and parent directories are created. It writes on the daemon host, so it is rejected against a remote daemon.
See Replay & E2E for recording, Maestro compatibility, and CI workflow details.
Batch
batchruns a JSON array of steps in a single daemon request.- Each step has
command,input, and optionalruntime. inputuses the same fields as the matching MCP/Node command.- Legacy CLI step payloads with
positionals/flagswere removed in 0.21. Use structured input such as{"command":"open","input":{"app":"settings","platform":"ios"}}. - Unknown top-level step fields are rejected.
- Stop-on-first-error is the supported behavior (
--on-error stop). - Use
--max-steps <n>to tighten per-request safety limits. - Batch requests inherit the same daemon lock policy and session binding metadata as the parent command.
- In non-JSON mode, successful batches print a short per-step summary.
See Batching for payload format, response shape, and usage guidelines.
App install (in-place)
install <app> <path>installs from binary path without uninstalling first.- Supports Android devices/emulators, iOS simulators, and CoreDevice-backed iOS physical devices. On xctrace-only devices, install the app with Xcode before opening it by bundle ID.
- Useful for upgrade flows where you want to keep existing app data when supported by the platform.
- Remote daemons automatically upload local app artifacts for
install; prefix the path withremote:to use a daemon-side path verbatim. - Supported binary formats: Android
.apk/.aab, iOS.app/.ipa. .aabrequiresbundletoolinPATH, orAGENT_DEVICE_BUNDLETOOL_JAR=<absolute-path-to-bundletool-all.jar>withjavainPATH..aabinstalls use bundletoolbuild-apks --mode universal..ipainstalls by extractingPayload/*.app; if multiple app bundles exist,<app>is used as a bundle id/name hint to select one.
App reinstall (fresh state)
reinstall <app> <path>uninstalls and installs in one command.- Supports Android devices/emulators, iOS simulators, and CoreDevice-backed iOS physical devices. XCTest-backed xctrace-only devices do not expose install or app inventory operations.
- Useful for login/logout reset flows and deterministic test setup.
- Remote daemons automatically upload local app artifacts for
reinstall; prefix the path withremote:to use a daemon-side path verbatim. - Supported binary formats: Android
.apk/.aab, iOS.app/.ipa. .aabaccepts the same bundletool requirements asinstall..ipauses<app>as the selection hint when multiplePayload/*.appbundles are present.
App install from source URL
install-from-source <url>installs from a URL source through the normal daemon artifact flow.install-from-source --github-actions-artifact <owner/repo:artifact>passes a typed GitHub Actions artifact source through to a compatible remote daemon. Numeric artifacts are sent asartifactId; non-numeric artifacts are sent asartifactName.- Repeat
--header <name:value>for authenticated or signed artifact requests. - Supports the same device coverage as
install: Android devices/emulators, iOS simulators, and CoreDevice-backed iOS physical devices. - Use
installorreinstallfor local.apk,.aab,.app, and.ipapaths; useinstall-from-sourcewhen the artifact already exists at a URL reachable by the daemon. - Direct Android URL sources may be
.apkor.aab. - Trusted artifact service URLs may resolve to archives containing one installable
.apk,.aab,.ipa, or iOS.apptar archive. Prefer--github-actions-artifactfor GitHub Actions artifacts that a compatible remote daemon can resolve with its own credentials. - Downloads resolve and approve every redirect destination, pin each connection to the approved address, reject HTTPS downgrades, and follow at most five redirects. Sensitive caller headers are not forwarded across origins.
- Downloaded artifacts are limited to 2 GiB compressed. Archive materialization is limited to 4 GiB expanded data, 100,000 entries, and three nested archive layers; links and special archive entries are rejected.
- Standard
HTTP_PROXY,HTTPS_PROXY, andNO_PROXYconfiguration is honored without delegating destination DNS resolution to the proxy. --retain-pathskeeps retained materialized artifact paths after install, and--retention-ms <ms>sets their TTL.- URL downloads follow the same
installFromSource()safety checks and host restrictions as the JS client API.
Push notification simulation
push <bundle|package> <payload.json|inline-json>simulates push notification delivery.- iOS push simulation is simulator-only (
xcrun simctl push) and requires an APNs-style JSON object payload. - Android uses
adb shell am broadcastand accepts payload shape:{"action":"<intent-action>","receiver":"<optional component>","extras":{"key":"value","flag":true,"count":3}}. - Android extras support
string,boolean, andnumbervalues. pushworks with the active session device, or with explicit selectors (--platform,--device,--udid,--serial).
App event triggers (app hook)
trigger-app-event <event> [payloadJson]dispatches app-defined events via deep link.trigger-app-eventrequires either an active session or explicit device selectors (--platform,--device,--udid,--serial).- On macOS, use
AGENT_DEVICE_MACOS_APP_EVENT_URL_TEMPLATEto override the desktop deep-link template. - On iOS physical devices, custom-scheme deep links require active app context (open app first in the session).
- Configure one of:
AGENT_DEVICE_APP_EVENT_URL_TEMPLATEAGENT_DEVICE_IOS_APP_EVENT_URL_TEMPLATEAGENT_DEVICE_MACOS_APP_EVENT_URL_TEMPLATEAGENT_DEVICE_ANDROID_APP_EVENT_URL_TEMPLATE
- Template placeholders:
{event},{payload},{platform}. - Example template:
myapp://agent-device/event?name={event}&payload={payload}. payloadJsonmust be a JSON object.- This is app-hook-based simulation and does not inject OS-global notifications.
Settings helpers
- iOS
settingssupport is simulator-only except forsettings appearanceand the macOS permission subset on macOS. - macOS supports only
settings appearance <light|dark|toggle>andsettings permission <grant|reset> <accessibility|screen-recording|input-monitoring>. settings wifi|airplane|location|animationsremain intentionally unsupported on macOS.- Android
settings animations off|ontoggles the globalwindow_animation_scale,transition_animation_scale, andanimator_duration_scalevalues. Use it as an opt-in stabilizer for automation runs with heavy system or app animations, then restore withsettings animations onwhen needed. settings appearancemaps to macOS appearance, iOS simulator appearance, and Android night mode.settings location set <lat> <lon>sets precise coordinates on iOS simulators and Android emulators.settings clear-app-state [app-id]clears the active session app data, or the provided app id. Android usespm clear, which removes SharedPreferences, databases, files, and cache. iOS simulator removes the app data container contents. iOS physical devices and macOS are unsupported.- Face ID and Touch ID controls are iOS simulator-only.
- Fingerprint simulation is supported on Android targets where
cmd fingerprintoradb emu fingeris available. On physical Android devices, onlycmd fingerprintis attempted. - Permission actions are scoped to the active session app.
- iOS permission targets:
camera,microphone,photos(fullorlimited),contacts,notifications. - Android permission targets:
camera,microphone,photos,contacts,notifications. - macOS permission targets:
accessibility,screen-recording,input-monitoring. - On macOS,
settings permission grant ...checks/request access and opens System Settings guidance when needed; it does not silently grant TCC permissions. - On macOS,
settings permission deny ...is intentionally unsupported. - Android uses
pm grant|revokefor runtime permissions (resetmaps to revoke) andappopsfor notifications. Every permission mutation names the foreground user explicitly (--user <id>, resolved witham get-current-user):pmdefaults these operations to user 0, so on a device whose foreground user is nonzero an unscoped revoke would edit user 0 and leave the running app's permission untouched. Resolving that user is a prerequisite — ifam get-current-userdoes not answer,settings permissionfails withCOMMAND_FAILEDand changes nothing rather than applying the mutation to user 0. - Android kills a running app whenever a runtime permission it currently holds is revoked, so
settings permission deny|resetafter a grant leaves the session app no longer running. The response reports the prior state of the revoked permission for the acting user aspriorGrantState: granted | not_granted | unknown, and carries a warning namingopen <app> --relaunchfor bothgrantedandunknown—unknownmeans the device did not report a readable state, not that the app was left alone. Revoking a permission the app does not hold (not_granted) is harmless and warns nothing. full|limitedmode is supported only for iOSphotos; other targets reject mode.- Use
match/nonmatchto simulate valid/invalid Face ID, Touch ID, and Android fingerprint outcomes.
App state and app lists
- Android
appstatereports live foreground package/activity. - iOS
appstateis session-scoped and reports the app tracked by the active session on the target device. appsshows user-installed apps by default. Use--allwhen you need the full inventory, including system/OEM apps.
Clipboard
clipboard readreturns clipboard text for the selected target.- Treat
clipboard readoutput as sensitive data; it can include secrets copied by the user or app. clipboard write <text>updates clipboard text on the selected target.- Works with an active session device or explicit selectors (
--platform,--device,--udid,--serial). - Supported on macOS, Android emulator/device, and iOS simulator.
- iOS physical devices currently return
UNSUPPORTED_OPERATIONfor clipboard commands.
Keyboard
keyboard status(orkeyboard get) returns keyboard visibility and best-effort input type classification on Android.- To hide the keyboard, use
keyboard dismiss. It taps safe controls likeDonewhen available and verifies the keyboard closed. - If it reports
UNSUPPORTED_OPERATION, press a visible app control such asDoneonly when that is the intended fallback. - Works with active sessions and explicit selectors (
--platform,--device,--udid,--serial). keyboard status|getis supported on Android emulator/device.keyboard dismissis supported on Android emulator/device and best-effort on iOS simulator/device.
Performance diagnostics
- Use an explicit
frames,memory,cpu, ortracearea so each request answers one profiling question. In 0.21, bareperf,perf sample,perf metrics, and themetricsalias fail with guidance to the focused replacements. perf framesreturns a focused, bounded frame/jank-health JSON blob.perf memory samplereturns a compact memory-only JSON blob for agents investigating growth/leaks without collecting a large artifact. It is better than raw memory command output for first-pass diagnosis because arrays and top offenders are bounded.- Example sample shape:
{"metrics":{"memory":{"available":true,"totalPssKb":562958,"totalRssKb":570304,"topConsumers":[{"name":"Dalvik Heap","pssKb":213456}]}}}. perf memory snapshotwrites a heap/memgraph artifact to disk and returns path, size, kind, method, and support metadata. Large artifacts are never dumped into CLI/MCP/default JSON output.- Example default snapshot output:
Memory artifact (android-hprof): /tmp/app.hprof (42MB). cdptargets React Native JavaScript heap evidence through Metro CDP. Use it for JS heap usage samples and heap snapshots; useperf memory sampleandperf memory snapshotfor native/process memory. See Debugging & Profiling for the bounded leak workflow.perf cpu profile ... --kind xctracerecords an Apple.tracewith the requested xctrace template.reportaggregates every run, writes compact JSON with at most ten weighted top self-time functions, and prints at most five while the raw trace stays on disk.perf trace ... --kind xctracerecords an Apple.tracesuch as Animation Hitches for native diagnosis.- xctrace perf commands return artifact paths and compact metadata only; inspect
.tracefiles in Instruments/Xcode instead of dumping trace contents into agent context. perf cpu profile ... --kind simpleperfstarts/stops Android native CPU profiling for the active session package. Its report artifact keeps up to 50 parsed rows, while the response returns at most ten top functions and the CLI prints at most five.perf trace ... --kind perfettostarts/stops Android Perfetto trace capture for the active session package.- Native profile/trace outputs are compact agent evidence: state, artifact path, size, and method. Raw
.perf.dataand.perfetto-tracecontents stay on disk. - Without
--json, each explicit perf area prints a compact focused summary. - App startup duration is measured by
openand returned inopen'sstartupresult. Use that result directly instead of the removed aggregate perf form. - Use native perf stop/report results as compact agent evidence, not raw profiler output. A successful Perfetto stop can return
state: "stopped",outPath: "/tmp/app.perfetto-trace",sizeBytes: 5392410, andmethod: "adb-shell-perfetto"while the 5.3 MB raw trace stays on disk as the artifact. - Android app sessions with an active package support:
fpsframe health fromadb shell dumpsys gfxinfo <package> framestats, withdroppedFramePercentas the primary value andworstWindowsfor dropped-frame clustersmemoryfromadb shell dumpsys meminfo <package>with values reported in kilobytes (kB)
- Apple app sessions with an active bundle ID support:
fpsframe health fromxcrun xctraceAnimation Hitches on connected iOS devices, withdroppedFramePercentas the primary value andworstWindowsfor hitch clustersmemoryfrom process RSS snapshots reported in kilobytes (kB)
- Platform support:
memory: Android emulator/device, HarmonyOS device, macOS app sessions, iOS simulators with an active app session (open <app>first), and iOS physical devices with an active app sessionfps: Android emulator/device app sessions and connected iOS device app sessions. iOS simulator and macOS frame health is reported unavailable because Apple tooling does not expose trustworthy app hitch data there.perf memory snapshot --kind android-hprof: Android emulator/device app sessions with a running debuggable/profileable process and permitted heap dumpingperf memory snapshot --kind memgraph: iOS simulator and macOS app sessions with a running app process. Physical iOS devices report memgraph unavailable with a recovery hint.perf memory trace --kind heapprofd: deferred until Android Perfetto/heapprofd plumbing is available.perf cpu profile --kind xctrace: iOS simulator app sessions, connected iOS device app sessions where xctrace can attach to the active process, and macOS app sessions when the app process can be resolved from the bundle ID.perf trace --kind xctrace: iOS simulator app sessions, connected iOS device app sessions where xctrace can attach to the active process, and macOS app sessions when the selected xctrace template supports the target.- Android native profiling is not implemented under Apple xctrace perf; Android profiling is tracked separately.
- HarmonyOS performance evidence is memory-only on the current public HDC surface: CPU profiling, frame sampling, and memory-snapshot artifacts are unavailable.
- Android URL/deep-link opens infer the foreground package after launch when possible, including Expo Go/dev-client shells. If the session still has no app package/bundle ID, package-bound metrics remain unavailable until you
open <app>. - Android frame health is reset after each successful
perf framesread and afteropen <app>, so runperf frames, perform the interaction, then runperf framesagain for a focused window. - Android Simpleperf and Perfetto collectors require an active Android app session with a running package process. They return artifact paths, sizes, and compact state summaries; they do not print profile or trace contents into the agent context. iOS native Simpleperf/Perfetto support is not provided by these commands.
- On CoreDevice-backed physical iOS devices,
perf framesrecords a shortxcrun xctracesample. Keep the device unlocked, connected, and the app active in the foreground while sampling.
React Native component internals
react-devtoolsdynamically runs pinned[email protected]through npm and passes arguments through 1:1.- The first run may download the pinned package from npm; later runs can reuse the npm cache.
agent-deviceglobal flags work before or afterreact-devtools. Use--before downstream flags only when they intentionally share anagent-deviceglobal flag name.- Use it when a React Native workflow needs component hierarchy, props, state, hooks, render causes, slow components, or re-render counts.
- For profiling, keep the window narrow and make one bounded first-pass survey: use the
profile stopsummary, runprofile slow --limit 5andprofile rerenders --limit 5once, addprofile timeline --limit 20only when commit timing matters, then drill into a specific@cref withprofile report. - Do not repeatedly raise broad
profile slowlimits such as--limit 50,--limit 200, or--limit 500unless you have a specific target that needs more rows. - Keep using
snapshot,press,fill,logs,network,audio probe,perf frames, andperf memoryfor device/app runtime evidence. Usereact-devtoolsfor React internals. - For React Native apps, overlays, Metro/Fast Refresh blockers, and routing to React DevTools or debugging evidence, start with
agent-device help react-native. - On Android, use
alert get,alert wait <short-ms>,alert accept, andalert dismissfor runtime permission prompts and native alerts. On iOS, use the same alert commands for XCTest alerts, app-owned modal popups with native blocking markers, and blocking system dialogs. Do not usesettings permissionto answer a dialog already on screen; reserve it for setup or resetting permission state before a flow. - React Native development builds can connect to the DevTools daemon on port 8097. For Android emulators or physical devices, run
adb reverse tcp:8097 tcp:8097if the app cannot reach the host. - Direct Android
openURL targets for local Metro hosts with a port auto-configure host reachability. For app/package launches or unsupported flows, runadb reverse tcp:8081 tcp:8081if the app cannot reach local Metro. - For Android and iOS sessions connected through a remote bridge profile,
react-devtoolsregisters a lease-scoped companion tunnel to the sandbox-local DevTools daemon at127.0.0.1:8097. Android bridge profiles use the bridge-owned remoteadb reversemapping; iOS bridge profiles use the bridge-owned wildcard Metro host tunnel. The CLI keeps the companion alive untilagent-device react-devtools stoporagent-device disconnect. - For remote iOS bridge sessions, open the app once to create the bridge session, run
agent-device react-devtools start, then relaunch the same bundle id withagent-device open <bundle-id> --platform ios --relaunchbeforewait --connected. React Native attempts the legacy DevTools websocket during JavaScript startup, so starting DevTools after the first launch can miss that connection attempt. - Remote bridge React DevTools assumes the React Native-bundled DevTools behavior in React Native 0.83+. Older browser/Chromium DevTools workflows are not assumed to exist inside remote sandboxes. Expo projects should be verified against the SDK's bundled React Native version before relying on this path; this release does not claim a separately verified Expo SDK version.
- For cross-platform validation with explicit target selectors, use separate sessions/devices and restart
react-devtoolsbetween iOS and Android runs.
react-native dismiss-overlayclears a React Native development overlay (a redbox/LogBox error or a collapsed warning banner) that is blocking interaction, then returns without changing app state otherwise.- Use it when a snapshot or interaction is blocked by a dev-only overlay; it is a no-op when no overlay is present.
- It is supported on iOS simulators/devices and Android emulators/devices;
react-nativecurrently exposes only thedismiss-overlayhelper.
Multiple React Native worktrees
You can reuse one installed iOS simulator debug build across multiple local worktrees when the native binary is compatible with both JavaScript trees. Run one Metro server per worktree on a unique port, then open the same app on different simulators with explicit Metro runtime hints:
- Use different simulators and sessions for each worktree. One simulator cannot run two copies of the same bundle id at the same time.
- On iOS simulators,
openwrites React Native's per-simulator debug server settings before launching, sorn-acan use port8081whilern-buses port8082.open's--metro-host/--metro-portalso bind each session's dev server, so a later flaglessmetro reload --session rn-areloads the port8081server and--session rn-breloads8082— no need to repeat the flags. - This covers JavaScript and Metro-resolved workspace changes. Rebuild/reinstall the app when native code, native dependencies, bundle identifiers, entitlements, or generated native project files change.
- Close every manually opened session when done:
Metro reload
metro reloadtriggers a dev-server reload, the same mechanism used by pressingrin the Metro terminal.- Use it for React Native dev builds that are already connected to Metro when JS changes should be loaded without restarting the native app process.
- A flagless
metro reload --session <s>resolves against the dev server that session last bound — viametro prepareoropen's--metro-host/--metro-port/--bundle-urlhint flags — so it never silently reloads a different project's server on the default port. Resolution priority is per-call flags, then that session's saved binding, thenhttp://localhost:8081/reload; a host or port flag overrides only that field, while--bundle-urlsupplies the target bundle origin and route. - Session bindings are updated by each hinted
openormetro prepare, cleared byclose, and also cleared when a fresh same-nameopenhas no Metro hint flags. This prevents a reused session name from reloading a previous project's dev server. - The reload URL keeps the bound bundle URL's mount prefix instead of collapsing to the host root. This applies to both
index.bundleand Expo's virtual entry:http://host/tenant-42/.expo/.virtual-metro-entry.bundlemaps tohttp://host/tenant-42/reload. - When the dev server has no HTTP
/reloadroute and answers with the app page instead (Expo does this),metro reloadbroadcasts{"version":2,"method":"reload"}over the server's/messagewebsocket — the channel the dev-server CLIs use for therkey — instead of reporting the app-page response as a successful reload. The result'stransportfield says which channel delivered the reload. - Pass
--metro-host,--metro-port, or--bundle-urlwhen you need to target a specific Metro instance for one call; explicit flags override the session binding. - Fall back to
open <app> --relaunchwhen the app is not connected to Metro, reload fails, or the native process itself must restart.
Media and logs
- Recordings always produce a video artifact.
record startdefaults to app scope and requires an active session fromopen <app>; use--scope deviceor--scope systemto explicitly request whole-screen capture where the selected backend supports it, such as recordings that intentionally span the full screen, multiple apps, settings, home screen, or app transitions. When touch visualization is enabled, recordings also produce a gesture telemetry sidecar that can be used for post-processing or inspection. screenshot --scale <factor>proportionally resizes both dimensions. The accepted range is0.01through1; use1for full resolution. The former--max-size <px>flag was removed and is refused with migration guidance wherever it appears (CLI,.adscripts, Node options, config, and the retiredAGENT_DEVICE_SCREENSHOT_MAX_SIZEenv var).- Set
AGENT_DEVICE_SCREENSHOT_SCALE=0.3(orscreenshotScalein config) as a token-conscious screenshot default for agent workflows. An explicit--scaleoverrides it. - Keep the scale default unset, or use
--scale 1, when full-resolution screenshots are required for reusable pixel-diff baselines. screenshot --overlay-refscaptures a fresh full snapshot and burns visible@eNrefs plus their target rectangles into the saved PNG.screenshot --normalize-status-bartemporarily normalizes iOS simulator status-bar chrome for deterministic screenshot baselines; ordinary screenshots leave the simulator's current chrome visible.screenshot --scale <factor> --overlay-refswrites a smaller image and draws refs for that final image size; avoid very small scales when text, icons, or labels need to remain readable.diff screenshotcompares the current live screenshot to--baseline, or compares--baselineto an optional savedcurrent.pngpath without requiring an active session. Its text output reports ranked changed regions with screen-space rectangles, changed-pixel counts, and each region's share of the diff; JSON also includes normalized rectangles. The earlier best-effortocrandnonTextDeltasanalyzers are retired; their optional result fields remain for source compatibility but are no longer emitted, so use the baseline/current images and diff artifact with vision for qualitative interpretation. It writes a diff PNG with a light grayscale current-screen context, red-tinted changed pixels, and outlined changed regions when--outis provided. Live iOS simulator diffs normalize status-bar chrome by default; usescreenshot --normalize-status-barwhen capturing reusable baselines.diff screenshot --overlay-refsadditionally writes a separate current-screen overlay guide for live captures without using that annotated image for the pixel comparison. If current-screen refs intersect changed regions, the output lists the best ref matches under those regions. Saved-image comparisons do not have live accessibility refs, so--overlay-refsis unavailable when acurrent.pngpath is provided.- In
--jsonmode, each overlay ref also includes a screenshot-spacecenterpoint for coordinate fallback likepress <x> <y>. - Burned-in touch overlays are exported only on macOS hosts, because the overlay pipeline depends on Swift + AVFoundation helpers.
- On Linux or other non-macOS hosts,
record stopstill succeeds and returns the raw video plus telemetry sidecar, and includesoverlayWarningwhen burn-in overlays were skipped. - Android uses
adb shell screenrecord, which has a 180s platform limit.record startpublishes a durable device manifest. Longer recordings are split into MP4 chunks while the daemon stays alive; after daemon restart,record stoprecovers only manifest-owned chunks and warns when gesture overlay telemetry was lost.
Session app logs (token-efficient debugging): Logging is off by default in normal flows. Enable it on demand for debugging. Logs are written to a file so agents can grep instead of loading full output into context.
- Supported on iOS simulator, iOS physical device, and Android.
- Preferred debug entrypoint:
logs clear --restartfor clean-window repro loops. logs startappends toapp.logand rotates toapp.log.1when the file exceeds 5 MB.openprintsSession state: <path>and JSON includessessionStateDir,runnerLogPath,requestLogPath, andeventLogPath. Use the session directory to inspect concurrent runs without parsing global daemon logs.events.ndjsoncontains the session event timeline;requests/<request-id>.ndjsoncontains daemon request diagnostics;runner.logcontains Apple runner andxcodebuildoutput.events.ndjsonrotates toevents.ndjson.1when it exceeds 5 MB (AGENT_DEVICE_EVENT_LOG_MAX_BYTESoverrides, in whole bytes); one rotated generation is kept.eventscursors stay absolute across rotation, sonextCursorstill resumes; a cursor older than the retained window fails withCOMMAND_FAILEDanddetails.reason: "EVENT_LOG_CURSOR_EXPIRED", withdetails.earliestCursornaming the oldest cursor that still resolves. If the retained files and their window record disagree — a hand-deleted generation, an edited file, a corruptevents.ndjson.window.json—eventsfails withdetails.reason: "EVENT_LOG_WINDOW_UNVERIFIED"rather than answering from a guessed offset; appends continue regardless.- Event timeline entries preserve command names, status, durations, bounded device/app inventory previews, lifecycle outcomes, artifact basenames, and structural action details such as scroll distance/direction, safe refs, and coordinates. User-entered text, clipboard contents, push/event payloads, selector values, free-form flags/messages/paths, and raw unknown command arguments are omitted or replaced with content-free placeholders.
--no-recordsuppressesaction.recordedentries, but request start/finish entries still record command/status/timing. network dump [limit] [summary|headers|body|all]parses recent HTTP(s) entries fromapp.logfor app/device sessions and from managedagent-browserrequest history for web sessions;network log ...is an alias.- Prefer
--include headers|body|allwhen you want explicit detail level without relying on positional ordering. - On macOS,
logsandnetwork dumpare app-scoped and parse Unified Logging output associated with the active session app. - Network dump limits: scans up to 4000 recent log lines, returns up to 200 entries, and truncates payload/header fields at 2048 characters.
- On web,
network dumpusesagent-browser network requests; request/response bodies are not exposed by that backend path, so use directagent-browserHAR workflows for browser-specific body capture. - Android
network dumpalso surfaces logcat timestamps and can backfill status and duration from adjacent GIBSDK packet lines when the URL is logged separately. - Android log streaming automatically rebinds to the app PID after process restarts.
- iOS simulator log capture now streams from inside the simulator with
simctl spawn <udid> log ..., andnetwork dumpcan recover recent simulator log history withsimctl log showwhen the live app-log window is sparse. - iOS log capture still relies on Unified Logging signals (for example
os_log); plain stdout/stderr output may be limited depending on app/runtime. - On iOS,
network dumpcan return zero HTTP entries for real app activity when the app does not emit request metadata into Unified Logging. The response notes now distinguish between an empty repro window and a non-network app log window. - Retention knobs: set
AGENT_DEVICE_APP_LOG_MAX_BYTESandAGENT_DEVICE_APP_LOG_MAX_FILESto override rotation limits. - Optional write-time redaction patterns: set
AGENT_DEVICE_APP_LOG_REDACT_PATTERNSto a comma-separated regex list.
Crash symbols (bounded local symbolication): Use debug symbols when you already have an Apple crash artifact and local dSYMs and need the failing code path. The command matches crash Binary Images / IPS usedImages UUIDs to dwarfdump --uuid output, runs atos, writes a symbolicated artifact, and prints only the output path plus a compact crash report with app/thread, exception or termination, top symbolicated frames, and the first actionable frame finding. This is better than pasting raw crash logs because the agent sees the diagnosis and artifact path without ingesting the full crash body.
Crash routing: use logs for the lead-up timeline, debug symbols for a failing frame from crash.ips/crash.log plus matching dSYMs, and Xcode/LLDB for live state, breakpoints, variables, memory, or stepping.
debugis intentionally narrow: do not use it for app logs, network/audio evidence, performance samples, recordings, traces, or React Native internals.- Android Java/R8
mapping.txtand nativendk-stack/addr2linesymbolication are deferred; capture Android crash evidence withlogsand symbolicate externally for now. - The crash artifact body is written to
--out; it is not dumped into agent context or default JSON.
Grepping app logs: Use logs path to get the file path, then run grep (or grep -E) on that path so only matching lines enter context—keeping token use low.
-
Use
-nto include line numbers. Use-Efor extended regex and|without escaping in the pattern. -
Prefer targeted patterns (e.g.
Error,Exception, your log tags) over reading the whole file. -
logs mark "before submit"lines are prefixed with[agent-device][mark][...], so grep foragent-device.*markwhen you need timing markers back quickly. -
iOS
recordworks on simulators and CoreDevice-backed physical devices. -
iOS simulator recording uses native
simctl io ... recordVideo. -
Physical iOS device capture is runner-based and built from repeated
XCUIScreen.main.screenshot()frames (no native video stream/audio capture). -
App-scoped recording requires an active app session context (
open <app>first). Use--scope device/--scope systemonly when whole-screen capture is the intended artifact. -
Physical iOS device capture is best-effort: dropped frames are expected and true 60 FPS is not guaranteed even with
--fps 60. -
Physical-device capture defaults to 15 FPS.
-
--fps <n>(1-120) applies to physical iOS device recording as an explicit FPS cap. -
--quality <medium|high>controls recording output quality. Android maps it toadb shell screenrecord --bit-rate; Apple targets use it for export/encoding.mediumis the default; passhighfor evidence, release notes, or debugging visual artifacts. Legacy numeric values are still accepted for compatibility:5-7map tomedium, and8-10map tohigh.
Tracing
trace start [path]begins trace-log capture for the active session.trace stop [path]stops capture and optionally writes or finalizes the trace artifact at the provided path.traceis intended for lower-level session diagnostics thanrecordorlogs.
Remote Metro workflow
When the cloud control plane owns the connection profile, connect can discover it directly:
For local profile files, create an agent-device.remote.json:
For self-contained scripts, pass the same profile to each step:
connectwithout--remote-configauthenticates to cloud when needed, fetches the connection profile, writes a generated local profile, stores the remote scope locally, and defers tenant lease allocation plus Metro preparation until a later command needs them.- Cloud connection profile responses must return a JSON object at
connection.remoteConfigProfile. The olderconnection.remoteConfigJSON string shape is no longer accepted. --remote-config <path>points to a local remote workflow profile that captures stable host, tenant/run, and any optional session, platform, lease backend, or Metro overrides forconnect.connect --remote-config ...follows the same verification, state, and deferred-preparation flow using the local profile instead of cloud discovery. Direct-provider profiles therefore require their provider credentials whenconnectruns; no device lease is created until a later device command.- Auth management commands are available for inspection and recovery:
agent-device auth status,agent-device auth login, andagent-device auth logout. Human login stores a revocable CLI session locally; it does not create or persist anadc_live_...service token. - Cloud auth uses three credential classes:
adc_agent_...short-lived command tokens, revocable CLI session refresh credentials, and explicitadc_live_...service/API tokens for CI. The CLI implements credential selection, CI refusal, local storage permissions, logout, and output redaction; the cloud API must enforce token expiry, tenant/run scope, revocation, one-time device approval, polling rate limits, and dashboard/API separation. AGENT_DEVICE_CLOUD_BASE_URLshould point at the bridge/control-plane API origin, not necessarily the dashboard origin. API-token setup links use/api-keyson that origin so the bridge can redirect users to the right dashboard page.- Deferred Metro preparation also applies to
batchwhen any step opens an app and the batch does not provide its own per-step runtime. connectwithout--sessionalways creates a fresh remote session. Its human and JSON next steps include the generated--session; concurrent processes must preserve that value on every command so they cannot adopt another process's ambient connection. The active connection fallback remains a convenience for one sequential workflow only. To replace a connection, pass its returned session explicitly with--session <name> --force;--forcewithout a session creates another connection without overwriting or releasing the previous one.- After
connect,install-from-source,open,snapshot,devices,press,fill,screenshot, and other normal commands can reuse active connection state in a single sequential workflow so agents do not repeat remote host/session/lease selectors inline. Ifconnection statusshowsleaseId=pending, the first platform-bound command allocates or refreshes the lease. Passing the same--remote-configto a normal command is also supported for self-contained scripts; the CLI reuses matching saved state or creates it before dispatch. - Self-contained remote scripts should end with
disconnect --remote-config <path>ordisconnectto release the lease and stop the owned Metro companion. - Explicit command-line flags override connected defaults. When
openuses explicit remote daemon or tenant flags without saved runtime hints, the CLI warns because React Native apps may launch without Metro bundle/runtime hints. metroProxyBaseUrlis the bridge origin. Do not prebuild/api/metro/...paths in the client profile; the CLI calls the bridge endpoints itself.- For cloud stock React Native iOS, the bridge descriptor supplies direct wildcard HTTPS Metro hints such as
<runtime>.metro.agent-device.dev:443. The XCTest runner package is still used for runner-backed device commands, not for Metro reachability. - Android keeps using bridge-provided runtime routes such as
/api/metro/runtimes/<runtimeId>/.... metroPublicBaseUrlis only needed for direct/non-bridge bundle hints. Bridged profiles can omit it and rely onmetroProxyBaseUrl.metro prepare --remote-config ...remains an advanced inspection/debug path and can still write a--runtime-file <path>artifact when needed.- The local Metro companion runs on the same machine as the React Native project and Metro.
disconnectstops the companion owned by the connection, but it does not stop the user’s Metro server.
Cloud profile response migration
/api/control-plane/connection-profile must return an object at connection.remoteConfigProfile, for example {"connection":{"remoteConfigProfile":{"daemonBaseUrl":"https://bridge.example.com/agent-device","daemonTransport":"http","tenant":"acme","runId":"run-123"}}}. The old connection.remoteConfig JSON-string wrapper is rejected.
Session inspection
session listshows active daemon sessions for the caller's implicit workspace scope, or the explicitly named session scope when--session/AGENT_DEVICE_SESSIONis configured.- Use
--jsonwhen you want to inspect or script against the raw session metadata.
Cloud provider artifacts
artifactslists provider-hosted cloud artifacts such as videos, Appium logs, device logs, automation logs, and provider dashboard links.- The response uses
cloudArtifactsso it stays separate from daemon-managed localartifactsreturned by screenshot, recording, install, replay, and remote materialization flows. - Plain text output prints ready provider URLs. Use
--jsonwhen scripts need the structuredcloudArtifactsarray. - Historical lookup requires
--provider-session <id>and--provider <name>. BrowserStack usesBROWSERSTACK_USERNAMEandBROWSERSTACK_ACCESS_KEY. AWS Device Farm uses the AWS CLI credential chain and infers the region from the session ARN when possible. See BrowserStack and AWS Device Farm for CI credential setup. - When a cloud runtime is registered in-process by an embedding host,
artifactscan infer the active provider session from the current lease before disconnect. disconnect --jsonandclose --jsoninclude provider release data when the runtime returns final cloud artifacts after session teardown. Some providers only finalize video/log URLs after the remote session is stopped, so retryagent-device artifacts <provider-session-id> --provider <name> --jsonif the first response ispending.
iOS physical-device prerequisites
For CLI-discoverable setup guidance, run agent-device help physical-device.
- Xcode with
xcrun devicectlandxcrun xctraceavailable. - Paired/trusted physical device, connected, unlocked when needed, with Developer Mode enabled.
- Older devices discovered only through
xctraceuse the XCTest backend automatically; its runner commands travel through macOSusbmuxd, so keep the device connected by cable. - XCTest-backed devices support open/close, interactions, snapshots, and screenshots. App inventory, install/reinstall, logs, performance sampling, recording, deep links, and launch arguments require CoreDevice.
- The
AgentDeviceRunnerXCTest host must be signed before commands can run on a physical device. - Start with Automatic Signing and only these env vars:
AGENT_DEVICE_IOS_TEAM_IDAGENT_DEVICE_IOS_BUNDLE_ID(runner bundle-id base; tests use<id>.uitests)
- Find team ids and Apple Development signing certificates with
security find-identity -v -p codesigning. - If Xcode cannot choose a profile, set
AGENT_DEVICE_IOS_PROVISIONING_PROFILEto the profile name/specifier, not a file path. AGENT_DEVICE_IOS_SIGNING_IDENTITYis optional; omit it unlessxcodebuildasks for a specific identity.- The profile/team must allow
AGENT_DEVICE_IOS_BUNDLE_IDand<id>.uitests. - First-run XCTest setup/build can take longer than normal commands; keep the device connected and use
--debugto inspect signing/build diagnostics if setup times out. - If you override the iOS runner derived-data path and also force cleanup, keep
AGENT_DEVICE_IOS_RUNNER_DERIVED_PATHunder the project.tmp/directory. Other cleanup override paths are rejected with a recovery hint. - For daemon startup troubleshooting:
- follow stale metadata hints for
<state-dir>/daemon.jsonand<state-dir>/daemon.lock(state-dirdefaults to~/.agent-devicefor packaged installs, or a worktree-scoped dir under~/.agent-device/dev/from source)
- follow stale metadata hints for
iOS SpringBoard, widgets, and system-UI surfaces
For CLI-discoverable workflow guidance, run agent-device help ios-system-ui.
agent-device open com.apple.springboard --platform iosbinds the session to SpringBoard today; this is verified on iOS simulator only. Physical-iPhone SpringBoard support is not yet verified — see #1296.- The full widget add/edit/remove flow is selector-driven from a fresh
snapshot -i, except two coordinate-based steps: the empty-space long-press that enters edit mode, and (until fixed) the widget-gallery search-result rows, which currently return unlabeled accessibility nodes. - SpringBoard labels vary by iOS version and locale; discover them from the current snapshot rather than hard-coding strings like
EditorAdd Widget. - Reopen the app bundle under test to return to normal app automation after a SpringBoard step.
