CLI to control iOS and Android devices for AI agents influenced by Vercel’s agent-browser.
The project is in early development and considered experimental. Pull requests are welcome!
- Platforms: iOS/tvOS (simulator + physical device core automation) and Android/AndroidTV (emulator + device).
- Core commands:
open,back,home,app-switcher,press,long-press,focus,type,fill,scroll,scrollintoview,wait,alert,screenshot,close,install,reinstall,push,trigger-app-event. - Inspection commands:
snapshot(accessibility tree),diff snapshot(structural baseline diff),appstate,apps,devices. - Clipboard commands:
clipboard read,clipboard write <text>. - Keyboard commands:
keyboard status|get|dismiss(Android). - Performance command:
perf(alias:metrics) returns a metrics JSON blob for the active session; startup timing is currently sampled. - App logs and traffic inspection:
logs pathreturns session log metadata;logs start/logs stopstream app output;logs cleartruncates session app logs;logs clear --restartresets and restarts stream in one step;logs doctorchecks readiness;logs markwrites timeline markers;network dumpparses recent HTTP(s) entries from session logs. - Device tooling:
adb(Android),simctl/devicectl(iOS via Xcode). - Minimal dependencies; TypeScript executed directly on Node 22+ (no build step).
npm install -g agent-deviceOr use it without installing:
npx agent-device open SampleAppThe skill is also accessible on ClawHub. For structured exploratory QA workflows, use the dogfood skill at skills/dogfood/SKILL.md.
Use refs for agent-driven exploration and normal automation flows.
Use press as the canonical tap command; click is an equivalent alias.
agent-device open Contacts --platform ios # creates session on iOS Simulator
agent-device snapshot
agent-device press @e5
agent-device diff snapshot # subsequent runs compare against previous baseline
agent-device fill @e6 "John"
agent-device fill @e7 "Doe"
agent-device press @e3
agent-device closeUse batch to execute multiple commands in a single daemon request.
CLI examples:
agent-device batch \
--session sim \
--platform ios \
--udid 00008150-001849640CF8401C \
--steps-file /tmp/batch-steps.json \
--jsonSmall inline payloads are also supported:
agent-device batch --steps '[{"command":"open","positionals":["settings"]},{"command":"wait","positionals":["100"]}]'Batch payload format:
[
{ "command": "open", "positionals": ["settings"], "flags": {} },
{ "command": "wait", "positionals": ["label=\"Privacy & Security\"", "3000"], "flags": {} },
{ "command": "click", "positionals": ["label=\"Privacy & Security\""], "flags": {} },
{ "command": "get", "positionals": ["text", "label=\"Tracking\""], "flags": {} }
]Batch response includes:
total,executed,totalDurationMs- per-step
results[]withdurationMs - failure context with failing
stepandpartialResults
Agent usage guidelines:
- Keep each batch to one screen-local workflow.
- Add sync guards (
wait,is exists) after mutating steps (open,click,fill,swipe). - Treat refs/snapshot assumptions as stale after UI mutations.
- Prefer
--steps-fileover inline JSON for reliability. - Keep batches moderate (about 5-20 steps) and stop on first error.
agent-device <command> [args] [--json]Basic flow:
agent-device open SampleApp
agent-device snapshot
agent-device press @e7
agent-device fill @e8 "hello"
agent-device close SampleAppDebug flow:
agent-device trace start
agent-device snapshot -s "Sample App"
agent-device find label "Wi-Fi" click
agent-device trace stop ./trace.logCoordinates:
- All coordinate-based commands (
press,longpress,swipe,focus,fill) use device coordinates with origin at top-left. - X increases to the right, Y increases downward.
pressis the canonical tap command.clickis an equivalent alias and accepts the same targets (x y,@ref, selector) and flags.
Gesture series examples:
agent-device press 300 500 --count 12 --interval-ms 45
agent-device press 300 500 --count 6 --hold-ms 120 --interval-ms 30 --jitter-px 2
agent-device press @e5 --count 5 --double-tap
agent-device swipe 540 1500 540 500 120 --count 8 --pause-ms 30 --pattern ping-pong
agent-device scrollintoview "Sign in"
agent-device scrollintoview @e42boot,open,close,install,reinstall,home,back,app-switcherpushbatchsnapshot,diff snapshot,find,getpress(alias:click),focus,type,fill,long-press,swipe,scroll,scrollintoview,pinch,isalert,wait,screenshottrigger-app-event <event> [payloadJson]trace start,trace stoplogs path,logs start,logs stop,logs clear,logs clear --restart,logs doctor,logs mark(session app log file for grep; iOS simulator + iOS device + Android)clipboard read,clipboard write <text>(iOS simulator + Android)keyboard [status|get|dismiss](Android emulator/device)network dump [limit] [summary|headers|body|all],network log ...(best-effort HTTP(s) parsing from session app log)settings wifi|airplane|location on|offsettings appearance light|dark|togglesettings faceid match|nonmatch|enroll|unenroll(iOS simulator only)settings touchid match|nonmatch|enroll|unenroll(iOS simulator only)settings fingerprint match|nonmatch(Android emulator/device where supported)settings permission grant|deny|reset camera|microphone|photos|contacts|notifications [full|limited]appstate,apps,devices,session listperf(alias:metrics)
Push notification simulation:
# iOS simulator: app bundle + payload file
agent-device push com.example.app ./payload.apns --platform ios --device "iPhone 16"
# iOS simulator: inline JSON payload
agent-device push com.example.app '{"aps":{"alert":"Welcome","badge":1}}' --platform ios
# Android: package + payload (action/extras map)
agent-device push com.example.app '{"action":"com.example.app.PUSH","extras":{"title":"Welcome","unread":3,"promo":true}}' --platform androidPayload notes:
- iOS uses
xcrun simctl push <device> <bundle> <payload>and requires APNs-style JSON object (for example{"aps":{"alert":"..."}}). - Android uses
adb shell am broadcastwith payload JSON shape:{"action":"<intent-action>","receiver":"<optional component>","extras":{"key":"value","flag":true,"count":3}}. - Android extras support string/boolean/number values.
pushworks with session context (uses session device) or explicit device selectors.
App event triggers (app hook):
agent-device trigger-app-event screenshot_taken '{"source":"qa"}'trigger-app-eventdispatches an app event via deep link and requires an app-side test/debug hook.trigger-app-eventrequires either an active session or explicit device selectors (--platform,--device,--udid,--serial).- On iOS physical devices, custom-scheme deep links require active app context (open the app in-session first).
- Configure one of:
AGENT_DEVICE_APP_EVENT_URL_TEMPLATEAGENT_DEVICE_IOS_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, not an OS-global notification injector.
- Canonical trigger contract lives in
website/docs/docs/commands.mdunder App event triggers.
Notes:
- iOS snapshots use XCTest on simulators and physical devices.
- Scope snapshots with
-s "<label>"or-s @ref. - If XCTest returns 0 nodes (e.g., foreground app changed), agent-device fails explicitly.
diff snapshotuses the same snapshot flags and compares the current capture with the previous session baseline, then updates baseline.
Diff snapshots:
- Run
diff snapshotonce to initialize baseline for the current session. - Run
diff snapshotagain after UI changes to get unified-style output (-removed,+added, unchanged context). - Use
--jsonto get{ mode, baselineInitialized, summary, lines }.
Efficient snapshot usage:
- Default to
snapshot -ifor iterative agent loops. - Add
-s "<label>"(or-s @ref) for screen-local work to reduce payload size. - Add
-d <depth>when lower tree levels are not needed. - Re-snapshot after UI mutations before reusing refs.
- Use
diff snapshotfor low-noise structural change verification between adjacent states. - Reserve
--rawfor troubleshooting and parser/debug investigations.
Flags:
--version, -Vprint version and exit--platform ios|android|apple(applealiases the iOS/tvOS backend)--target mobile|tvselect device class within platform (requires--platform; for example AndroidTV/tvOS)--device <name>--udid <udid>(iOS)--serial <serial>(Android)--ios-simulator-device-set <path>constrain iOS simulator discovery/commands to one simulator set (xcrun simctl --set)--android-device-allowlist <serials>constrain Android discovery/selection to comma/space-separated serials--activity <component>(Android app launch only; package/Activity or package/.Activity; not for URL opens)--session <name>--state-dir <path>daemon state directory override (default:~/.agent-device)--daemon-base-url <url>explicit remote HTTP daemon base URL; skips local daemon discovery/startup--daemon-auth-token <token>remote HTTP daemon auth token; sent in both the JSON-RPC request token and HTTP auth headers (Authorization: Bearerandx-agent-device-token)--daemon-transport auto|socket|httpdaemon client transport preference--daemon-server-mode socket|http|dualdaemon server mode (httpanddualexpose JSON-RPC over HTTP at/rpc)--tenant <id>tenant identifier used with session isolation--session-isolation none|tenantexplicit session isolation mode (tenantscopes session namespace as<tenant>:<session>)--run-id <id>run identifier used with tenant-scoped lease admission--lease-id <id>active lease identifier used with tenant-scoped lease admission--count <n>repeat count forpress/swipe--interval-ms <ms>delay betweenpressiterations--hold-ms <ms>hold duration perpressiteration--jitter-px <n>deterministic coordinate jitter forpress--double-tapuse a double-tap gesture perpress/clickiteration (cannot be combined with--hold-msor--jitter-px)--pause-ms <ms>delay betweenswipeiterations--pattern one-way|ping-pongrepeat pattern forswipe--debug(alias:--verbose) for debug diagnostics + daemon/runner logs--jsonfor structured output--steps <json>batch: JSON array of steps--steps-file <path>batch: read step JSON from file--on-error stopbatch: stop when a step fails--max-steps <n>batch: max allowed steps per request
Isolation precedence:
- Discovery scope (
--ios-simulator-device-set,--android-device-allowlist) is applied before selector matching (--device,--udid,--serial). - If a selector points outside the scoped set/allowlist, command resolution fails with
DEVICE_NOT_FOUND(no host-global fallback). - When
--ios-simulator-device-setis set (or its env equivalent), iOS discovery is simulator-set only (physical iOS devices are not enumerated).
TV targets:
- Use
--target tvtogether with--platform ios|android|apple. - TV target selection supports both simulator/emulator and connected physical devices (AppleTV + AndroidTV).
- AndroidTV app launch/app listing use TV launcher discovery (
LEANBACK_LAUNCHER) and fallback component resolution when needed. - tvOS uses the same runner-driven interaction/snapshot flow as iOS (
snapshot,wait,press,fill,get,scroll,back,home,app-switcher,record, and related selector flows). - tvOS back/home/app-switcher use Siri Remote semantics in the runner (
menu,home, double-home). - tvOS follows iOS simulator-only command semantics for helpers like
pinch,settings, andpush.
Examples:
agent-device open YouTube --platform android --target tvagent-device apps --platform android --target tvagent-device open Settings --platform ios --target tvagent-device screenshot ./apple-tv.png --platform ios --target tv
Pinch:
pinchis supported on iOS simulators (including tvOS simulator targets).- On Android,
pinchcurrently returnsUNSUPPORTED_OPERATIONin the adb backend.
Swipe timing:
swipeaccepts optionaldurationMs(default250, range16..10000).- Android uses requested swipe duration directly.
- iOS clamps swipe duration to a safe range (
16..60ms) to avoid longpress side effects. scrollintoviewaccepts either plain text or a snapshot ref (@eN); ref mode uses best-effort geometry-based scrolling without post-scroll verification. Runsnapshotagain before follow-up@refcommands.
Install the automation skills listed in SKILL.md.
npx skills add https://github.com/callstackincubator/agent-device --skill agent-deviceSessions:
openstarts a session. Without args boots/activates the target device/simulator without launching an app.- All interaction commands require an open session.
- If a session is already open,
open <app|url>switches the active app or opens a deep link URL. closestops the session and releases device resources. Pass an app to close it explicitly, or omit to just close the session.- Use
--session <name>to manage multiple sessions. - Session scripts are written to
<state-dir>/sessions/<session>-<timestamp>.adwhen recording is enabled with--save-script. --save-scriptaccepts an optional path:--save-script ./workflows/my-flow.ad.- For ambiguous bare values, use an explicit form:
--save-script=workflow.ador a path-like value such as./workflow.ad. - Deterministic replay is
.ad-based; usereplay --update(-u) to update selector drift and rewrite the replay file in place. - On iOS,
appstateis session-scoped and requires an active session on the target device.
Navigation helpers:
boot --platform ios|android|appleensures the target is ready without launching an app.- Use
bootmainly when starting a new session andopenfails because no booted simulator/emulator is available. open [app|url] [url]already boots/activates the selected target when needed.install <app> <path>installs app binary without uninstalling first (Android + iOS simulator/device).reinstall <app> <path>uninstalls and installs the app binary in one command (Android + iOS simulator/device).install/reinstallaccept package/bundle id style app names and support~in paths.- When
AGENT_DEVICE_DAEMON_BASE_URLtargets a remote daemon, local.apk/.aab/.ipafiles and.appbundles are uploaded automatically beforeinstall/reinstall. - Remote daemon clients can persist session-scoped runtime hints with
runtime setbeforeopen, or pass a typedruntimeobject on theopendaemon request to replace the session runtime atomically for that launch. Android launches write React Native dev prefs, and iOS simulator launches write React Native bundle defaults before app start. CLI example:agent-device runtime set --session my-session --platform android --metro-host 10.0.0.10 --metro-port 8081 --launch-url "myapp://dev". - Remote daemon screenshots and recordings are materialized back to the caller path instead of returning host-local daemon paths.
- To force a daemon-side path instead of uploading a local file, prefix it with
remote:, for exampleremote:/srv/builds/MyApp.app. - Supported binary formats for
install/reinstall: Android.apkand.aab, iOS.appand.ipa. .aabrequiresbundletoolinPATH, orAGENT_DEVICE_BUNDLETOOL_JAR=<path-to-bundletool-all.jar>(withjavainPATH).- For Android
.aab, setAGENT_DEVICE_ANDROID_BUNDLETOOL_MODE=<mode>to override bundletool--mode(default:universal). .ipainstall extractsPayload/*.app; when an IPA contains multiple app bundles,<app>is used as a bundle id/name hint to select the target bundle.
Deep links:
open <url>supports deep links withscheme://....open <app> <url>opens a deep link on iOS.- Android opens deep links via
VIEWintent. - iOS simulator opens deep links via
simctl openurl. - iOS device opens deep links via
devicectl --payload-url. - On iOS devices,
http(s)://URLs open in Safari when no app is active. Custom scheme URLs (myapp://) require an active app in the session. --activitycannot be combined with URL opens.
agent-device open "myapp://home" --platform android
agent-device open "https://example.com" --platform ios # open link in web browser
agent-device open MyApp "myapp://screen/to" --platform ios # open deep link to MyAppFind (semantic):
find <text> <action> [value]finds by any text (label/value/identifier) using a scoped snapshot.find text|label|value|role|id <value> <action> [value]for specific locators.- Actions:
click(default),fill,type,focus,get text,get attrs,wait [timeout],exists.
Assertions:
ispredicates:visible,hidden,exists,editable,selected,text.is textuses exact equality.
Performance metrics:
perf(ormetrics) requires an active session and returns a JSON metrics blob.- Current metric:
startupsampled from the elapsed wall-clock time around each sessionopencommand dispatch (open-command-roundtrip), unitms. - Startup samples are session-scoped and include sample history from recent
openactions. - Platform support for current sampling: iOS simulator, iOS physical device, Android emulator/device.
fps,memory, andcpuare reported as not yet implemented in this release.- Quick usage:
agent-device open Settings --platform ios
agent-device perf --json- How to read it:
metrics.startup.lastDurationMs: most recent startup sample in milliseconds.metrics.startup.samples[]: recent startup history for this session.sampling.startup.method: currentlyopen-command-roundtrip.
- Caveat: startup here is command-to-launch round-trip timing, not true app TTI/first-interactive telemetry.
Replay update:
replay <path>runs deterministic replay from.adscripts.replay -u <path>attempts selector updates on failures and atomically rewrites the same file.- Refs are the default/core mechanism for interactive agent flows.
- Update targets:
click,fill,get,is,wait. - Selector matching is a replay-update internal: replay parses
.adlines into actions, tries them, snapshots on failure, resolves a better selector, then rewrites that failing line.
Update examples:
# Before (stale selector)
click "id=\"old_continue\" || label=\"Continue\""
# After replay -u (rewritten in place)
click "id=\"auth_continue\" || label=\"Continue\""# Before (ref-based action from discovery)
snapshot -i -c -s "Continue"
click @e13 "Continue"
# After replay -u (upgraded to selector-based action)
snapshot -i -c -s "Continue"
click "id=\"auth_continue\" || label=\"Continue\""Android fill reliability:
fillclears the current value, then enters text.typeenters text into the focused field without clearing.fillnow verifies the entered value on Android.- If value does not match, agent-device clears the field and retries once with slower typing.
- This reduces IME-related character swaps on long strings (e.g. emails and IDs).
- Some Android system images cannot inject non-ASCII text (for example Chinese or emoji) through shell input.
- If this occurs, install an ADB keyboard IME from a trusted source, verify checksum/signature, and enable it only for test sessions:
- Trusted sources: https://github.com/senzhk/ADBKeyBoard or https://f-droid.org/packages/com.android.adbkeyboard/
adb -s <serial> install <path-to-adbkeyboard.apk>adb -s <serial> shell ime enable com.android.adbkeyboard/.AdbIMEadb -s <serial> shell ime set com.android.adbkeyboard/.AdbIMEadb -s <serial> shell ime list -s(verify current/default IME)
Settings helpers:
settings wifi on|offsettings airplane on|offsettings location on|off(iOS uses per-app permission for the current session app)settings appearance light|dark|toggle(iOS simulator appearance + Android night mode)settings faceid|touchid match|nonmatch|enroll|unenroll(iOS simulator only)settings fingerprint match|nonmatch(Android emulator/device where supported) On physical Android devices, fingerprint simulation depends oncmd fingerprintsupport.settings permission grant|deny|reset <camera|microphone|photos|contacts|notifications> [full|limited](session app required) Note: iOS supports these only on simulators. iOS wifi/airplane toggles status bar indicators, not actual network state. Airplane off clears status bar overrides.- iOS permission targets map to
simctl privacy:camera,microphone,photos(full=>photos,limited=>photos-add),contacts,notifications. - Android permission targets:
camera,microphone,photos,contactsusepm grant|revoke(resetmaps topm revoke);notificationsusesappops set POST_NOTIFICATION allow|deny|default. full|limitedmode is valid only for iOSphotos; other targets reject mode.
App state:
appstateshows the foreground app/activity (Android).- On iOS,
appstatereturns the currently tracked session app (source: session) and requires an active session on the selected device. appsincludes default/system apps by default (use--user-installedto filter).
Clipboard:
clipboard readreturns current clipboard text.clipboard write <text>sets clipboard text (clipboard write ""clears it).- Supported on Android emulator/device and iOS simulator.
- iOS physical devices currently return
UNSUPPORTED_OPERATIONfor clipboard commands.
Keyboard:
keyboard status(orkeyboard get) reports Android keyboard visibility and best-effort input type classification (text,number,email,phone,password,datetime).keyboard dismississues Android back keyevent only when keyboard is visible, then verifies hidden state.- Works with an active session device or explicit selectors (
--platform,--device,--udid,--serial). - Supported on Android emulator/device.
- App logs (token-efficient): Logging is off by default in normal flows. Enable it on demand when debugging. With an active session, run
logs pathto get path + state metadata (e.g.<state-dir>/sessions/<session>/app.log). Runlogs startto stream app output to that file; uselogs stopto stop. Runlogs clearto truncateapp.log(and remove rotatedapp.log.Nfiles) before a new repro window. Runlogs doctorfor tool/runtime checks andlogs mark "step"to insert timeline markers. Grep the file when you need to inspect errors (e.g.grep -n "Error\|Exception" <path>) instead of pulling full logs into context. Supported on iOS simulator, iOS physical device, and Android. - Use
logs clear --restartwhen you want one command to stop an active stream, clear current logs, and immediately resume streaming. logs startappends toapp.logand rotates toapp.log.1when the file exceeds 5 MB.- Network dump (best-effort):
network dump [limit] [summary|headers|body|all]parses recent HTTP(s) lines from the same session app log file and returns method/url/status with optional headers/bodies.network log ...is an alias. Current limits: scans up to 4000 recent log lines, returns up to 200 entries, truncates payload/header fields at 2048 characters. - Android log streaming automatically rebinds to the app PID after process restarts.
- Detailed playbook:
skills/agent-device/references/logs-and-debug.md - iOS log capture relies on Unified Logging signals (for example
os_log); plain stdout/stderr output may be limited depending on app/runtime. - 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. agent-device trace startagent-device trace stop ./trace.log- The trace log includes snapshot logs and XCTest runner logs for the session.
- Built-in retries cover transient runner connection failures and Android UI dumps.
- For snapshot issues (missing elements), compare with
--rawflag for unaltered output and scope with-s "<label>". - If startup fails with stale metadata hints, remove stale
<state-dir>/daemon.json/<state-dir>/daemon.lockand retry (state dir defaults to~/.agent-deviceunless overridden).
Boot diagnostics:
- Boot failures include normalized reason codes in
error.details.reason(JSON mode) and verbose logs. - Reason codes:
IOS_BOOT_TIMEOUT,IOS_RUNNER_CONNECT_TIMEOUT,ANDROID_BOOT_TIMEOUT,ADB_TRANSPORT_UNAVAILABLE,CI_RESOURCE_STARVATION_SUSPECTED,BOOT_COMMAND_FAILED,UNKNOWN. - Android boot waits fail fast for permission/tooling issues and do not always collapse into timeout errors.
- Use
agent-device boot --platform ios|android|applewhen starting a new session only ifopencannot find/connect to an available target. - Android emulator boot by AVD name (GUI):
agent-device boot --platform android --device Pixel_9_Pro_XL. - Android headless emulator boot:
agent-device boot --platform android --device Pixel_9_Pro_XL --headless. --debugcaptures retry telemetry in diagnostics logs.- Set
AGENT_DEVICE_RETRY_LOGS=1to also print retry telemetry directly to stderr (ad-hoc troubleshooting).
Diagnostics files:
- Failed commands persist diagnostics in
~/.agent-device/logs/<session>/<date>/<timestamp>-<diagnosticId>.ndjson. --debugpersists diagnostics for successful commands too and streams live diagnostic events.- JSON failures include
error.hint,error.diagnosticId, anderror.logPath.
- Bundle/package identifiers are accepted directly (e.g.,
com.apple.Preferences). - Human-readable names are resolved when possible (e.g.,
Settings). - Built-in aliases include
Settingsfor both platforms.
- Core runner commands:
snapshot,wait,click,fill,get,is,find,press,longpress,focus,type,scroll,scrollintoview,back,home,app-switcher. - Simulator-only commands:
alert,pinch,settings. - tvOS targets are selectable (
--platform ios --target tvor--platform apple --target tv) and support runner-driven interaction/snapshot commands. recordsupports iOS simulators and physical iOS devices.- iOS simulator recording uses native
simctl io ... recordVideo. - Physical iOS device recording is runner-based and built from repeated
XCUIScreen.main.screenshot()frames (no native video stream/audio capture). - Physical iOS device recording requires an active app session context (
open <app>first) so capture targets your app instead of the runner host app. - Physical iOS device capture is best-effort: dropped frames are expected and true 60 FPS is not guaranteed even with
--fps 60. - Physical iOS device recording defaults to uncapped (max available) FPS.
- Use
agent-device record start [path] --fps <n>(1-120) to set an explicit FPS cap on physical iOS devices.
- iOS simulator recording uses native
- iOS device runs require valid signing/provisioning (Automatic Signing recommended). Optional overrides:
AGENT_DEVICE_IOS_TEAM_ID,AGENT_DEVICE_IOS_SIGNING_IDENTITY,AGENT_DEVICE_IOS_PROVISIONING_PROFILE,AGENT_DEVICE_IOS_BUNDLE_ID. - Free Apple Developer (Personal Team) accounts may need a unique runner bundle id; set
AGENT_DEVICE_IOS_BUNDLE_IDto a reverse-DNS identifier unique to your team (for examplecom.yourname.agentdevice.runner).
pnpm testUseful local checks:
pnpm typecheck
pnpm test:unit
pnpm test:smokepnpm buildEnvironment selectors:
ANDROID_DEVICE=Pixel_9_Pro_XLorANDROID_SERIAL=emulator-5554IOS_DEVICE="iPhone 17 Pro"orIOS_UDID=<udid>AGENT_DEVICE_IOS_SIMULATOR_DEVICE_SET=<path>(orIOS_SIMULATOR_DEVICE_SET=<path>) to scope all iOS simulator discovery/commands to one simulator set.AGENT_DEVICE_ANDROID_DEVICE_ALLOWLIST=<serials>(orANDROID_DEVICE_ALLOWLIST=<serials>) to scope Android discovery to allowlisted serials.AGENT_DEVICE_BUNDLETOOL_JAR=<path-to-bundletool-all.jar>optional bundletool jar path used for Android.aabinstalls whenbundletoolis not inPATH.AGENT_DEVICE_ANDROID_BUNDLETOOL_MODE=<mode>optional bundletoolbuild-apks --modeoverride for Android.aabinstalls (default:universal).- CLI flags
--ios-simulator-device-set/--android-device-allowlistoverride environment values. AGENT_DEVICE_IOS_BOOT_TIMEOUT_MS=<ms>to adjust iOS simulator boot timeout (default:120000, minimum:5000).AGENT_DEVICE_DAEMON_TIMEOUT_MS=<ms>to override daemon request timeout (default90000). Increase for slow physical-device setup (for example120000).AGENT_DEVICE_STATE_DIR=<path>override daemon state directory (metadata, logs, session artifacts).AGENT_DEVICE_DAEMON_BASE_URL=http(s)://host:port[/base-path]connect directly to a remote HTTP daemon and skip local daemon metadata/startup.- Remote daemon installs upload local artifacts through
POST /upload; use aremote:path prefix when you need the daemon to read an existing server-side artifact path as-is. - HTTP JSON-RPC also exposes
agent_device.install_from_sourcefor typed host-side download/materialize/install flows. It accepts{ platform, source: { kind: "url" | "path", ... }, session?, requestId?, retainPaths?, retentionMs? }and returns normalized app identity (packageName/bundleId,launchTarget). - Set
retainPaths: trueto opt into daemon-managed retained materialization. In that mode the response also includesinstallablePath, optionalarchivePath,materializationId, andmaterializationExpiresAt. - Retained paths are server-side paths intended for later daemon-side reuse. They are cleaned automatically on TTL expiry, on session close when bound to a session, or immediately via
agent_device.release_materialized_paths({ materializationId, session?, requestId? }). AGENT_DEVICE_SOURCE_DOWNLOAD_TIMEOUT_MS=<ms>timeout for host-sideinstall_from_sourceURL downloads (default:120000).AGENT_DEVICE_INSTALL_SOURCE_RETAIN_TTL_MS=<ms>default retention TTL forinstall_from_source retainPaths:truematerializations (default:900000).AGENT_DEVICE_DAEMON_AUTH_TOKEN=<token>auth token for remote HTTP daemon mode; sent in both the JSON-RPC request token and HTTP auth headers (Authorization: Bearerandx-agent-device-token).AGENT_DEVICE_DAEMON_SERVER_MODE=socket|http|dualdaemon server mode.httpanddualexpose JSON-RPC 2.0 atPOST /rpc(GET /healthavailable for liveness).AGENT_DEVICE_DAEMON_TRANSPORT=auto|socket|httpclient preference when connecting to daemon metadata.AGENT_DEVICE_HTTP_AUTH_HOOK=<module-path>optional HTTP auth hook module path for JSON-RPC server mode.AGENT_DEVICE_HTTP_AUTH_EXPORT=<export-name>optional export name from auth hook module (default:default).AGENT_DEVICE_MAX_SIMULATOR_LEASES=<n>optional max concurrent simulator leases for HTTP lease allocation (default: unlimited).AGENT_DEVICE_LEASE_TTL_MS=<ms>default lease TTL used byagent_device.lease.allocateandagent_device.lease.heartbeat(default:60000).AGENT_DEVICE_LEASE_MIN_TTL_MS=<ms>minimum accepted lease TTL (default:5000).AGENT_DEVICE_LEASE_MAX_TTL_MS=<ms>maximum accepted lease TTL (default:600000).AGENT_DEVICE_IOS_TEAM_ID=<team-id>optional Team ID override for iOS device runner signing.AGENT_DEVICE_IOS_SIGNING_IDENTITY=<identity>optional signing identity override.AGENT_DEVICE_IOS_PROVISIONING_PROFILE=<profile>optional provisioning profile specifier for iOS device runner signing.AGENT_DEVICE_IOS_BUNDLE_ID=<reverse-dns-id>optional iOS runner app bundle id base. Tests derive from this as<id>.uitests.AGENT_DEVICE_IOS_RUNNER_DERIVED_PATH=<path>optional override for iOS runner derived data root. By default, simulator uses~/.agent-device/ios-runner/derivedand physical device uses~/.agent-device/ios-runner/derived/device. If you set this override, use separate paths per kind to avoid simulator/device artifact collisions.AGENT_DEVICE_IOS_CLEAN_DERIVED=1rebuild iOS runner artifacts from scratch for runtime daemon-triggered builds (pnpm ad ...) on the selected path.pnpm build:xcuitest(alias ofpnpm build:xcuitest:ios),pnpm build:xcuitest:tvos, andpnpm build:allalready clear their default derived paths and do not require this variable. WhenAGENT_DEVICE_IOS_RUNNER_DERIVED_PATHis set, cleanup is blocked by default; setAGENT_DEVICE_IOS_ALLOW_OVERRIDE_DERIVED_CLEAN=1only for trusted custom paths.
Test screenshots are written to:
test/screenshots/android-settings.pngtest/screenshots/ios-settings.png
See CONTRIBUTING.md.
agent-device is an open source project and will always remain free to use. Callstack is a group of React and React Native geeks. Contact us at hello@callstack.com if you need any help with these technologies or just want to say hi.