agent-device 0.13.3 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +68 -63
  2. package/android-snapshot-helper/README.md +75 -0
  3. package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.14.1.apk +0 -0
  4. package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.14.1.apk.sha256 +1 -0
  5. package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.14.1.manifest.json +17 -0
  6. package/dist/src/221.js +4 -0
  7. package/dist/src/3918.js +29 -29
  8. package/dist/src/8161.js +3 -3
  9. package/dist/src/8656.js +1 -1
  10. package/dist/src/9152.js +1 -1
  11. package/dist/src/9542.js +2 -2
  12. package/dist/src/9818.js +1 -1
  13. package/dist/src/989.js +1 -1
  14. package/dist/src/android-snapshot-helper.d.ts +182 -0
  15. package/dist/src/android-snapshot-helper.js +1 -0
  16. package/dist/src/index.d.ts +19 -0
  17. package/dist/src/internal/bin.js +413 -69
  18. package/dist/src/internal/daemon.js +22 -20
  19. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +26 -2
  20. package/package.json +29 -9
  21. package/skills/agent-device/SKILL.md +20 -62
  22. package/skills/dogfood/SKILL.md +9 -168
  23. package/skills/react-devtools/SKILL.md +15 -31
  24. package/skills/agent-device/references/bootstrap-install.md +0 -244
  25. package/skills/agent-device/references/coordinate-system.md +0 -28
  26. package/skills/agent-device/references/debugging.md +0 -138
  27. package/skills/agent-device/references/exploration.md +0 -362
  28. package/skills/agent-device/references/macos-desktop.md +0 -88
  29. package/skills/agent-device/references/remote-tenancy.md +0 -188
  30. package/skills/agent-device/references/verification.md +0 -134
  31. package/skills/dogfood/references/issue-taxonomy.md +0 -83
  32. package/skills/dogfood/templates/dogfood-report-template.md +0 -52
  33. package/skills/react-devtools/references/commands.md +0 -91
  34. package/skills/react-devtools/references/profiling.md +0 -74
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  <a href="https://www.callstack.com/open-source?utm_campaign=generic&utm_source=github&utm_medium=referral&utm_content=agent-device" align="center">
2
2
  <picture>
3
- <img alt="agent-device" src="website/docs/public/agent-device-banner.jpg">
3
+ <img alt="agent-device: device automation CLI for AI agents" src="website/docs/public/agent-device-banner.jpg">
4
4
  </picture>
5
5
  </a>
6
6
 
@@ -8,104 +8,109 @@
8
8
 
9
9
  # agent-device
10
10
 
11
- `agent-device` is a CLI for UI automation and app observability on iOS, tvOS, macOS, Android, and AndroidTV. It is built for agent-driven workflows: inspect the UI, interact deterministically, collect logs/network/perf evidence when behavior breaks, and keep the whole flow session-aware and replayable.
11
+ [![npm version](https://img.shields.io/npm/v/agent-device.svg)](https://www.npmjs.com/package/agent-device)
12
+ [![CI](https://github.com/callstackincubator/agent-device/actions/workflows/ci.yml/badge.svg)](https://github.com/callstackincubator/agent-device/actions/workflows/ci.yml)
13
+ [![License: MIT](https://img.shields.io/badge/license-MIT-black.svg)](LICENSE)
12
14
 
13
- If you know Vercel's [agent-browser](https://github.com/vercel-labs/agent-browser), this project applies the same broad idea to mobile apps and devices.
15
+ Device automation CLI for AI agents. Mobile, TV, and desktop apps.
14
16
 
15
- [![Watch the demo video](./website/docs/public/agent-device-contacts.gif)](./website/docs/public/agent-device-contacts.mp4)
17
+ `agent-device` lets coding agents run real apps, inspect UI state, interact with visible elements, and collect debugging evidence through one CLI.
16
18
 
17
- ## Project Goals
19
+ It is built around token-efficient accessibility snapshots, not pixel-first screenshots. Agents read compact UI trees, locate elements through refs like `@e3`, perform touch and text actions, and capture screenshots, video, logs, network, perf, and React profiles only when evidence is needed.
18
20
 
19
- - Give agents a practical way to understand mobile UI state through structured snapshots.
20
- - Keep automation flows token-efficient enough for real agent loops.
21
- - Make common interactions reliable enough for repeated automation runs.
22
- - Make debugging evidence easy to collect through logs, network inspection, and performance snapshots.
23
- - Keep automation grounded in sessions, selectors, and replayable flows instead of one-off scripts.
21
+ ## Agentic QA And Development
24
22
 
25
- ## Core Ideas
23
+ - **Quality Assurance**: dogfood flows, validate PR builds, check accessibility coverage, capture evidence, and turn stable explorations into `.ad` e2e tests.
24
+ - **Development**: build from specs, reproduce crashes and support issues, inspect logs/network/perf data, and iterate until the UI matches the work.
26
25
 
27
- - Sessions: open a target once, interact within that session, then close it cleanly.
28
- - Snapshots: inspect the current accessibility tree in a compact form and get current-screen refs for exploration.
29
- - Refs vs selectors: use refs for discovery, use selectors for durable replay and assertions.
30
- - Observability: collect session logs, inspect recent HTTP traffic with `network dump`, and sample CPU/memory with `perf`.
31
- - Tests: run deterministic `.ad` scripts as a light e2e test suite.
32
- - Replay scripts: save `.ad` flows with `--save-script`, replay one script with `replay`, or run a folder/glob as a serial suite with `test`.
33
- `test` supports metadata-aware retries up to 3 additional attempts, per-test timeouts, flaky pass reporting, and runner-managed artifacts under `.agent-device/test-artifacts` by default. Each attempt writes `replay.ad` and `result.txt`; failed attempts also keep copied logs and artifacts when available.
34
- - Human docs vs agent skills: docs explain the system for people; skills provide compact operating guidance for agents.
26
+ If you know Vercel's [agent-browser](https://github.com/vercel-labs/agent-browser), this is the same idea for apps and devices.
35
27
 
36
- ## Complementary Tooling
28
+ ![agent-device demo showing an agent inspecting and interacting with a contacts app](./website/docs/public/agent-device-contacts.gif)
37
29
 
38
- Use `agent-device` for on-device UI automation, screenshots/recordings, app logs, network inspection, and performance snapshots.
30
+ ## Quick Start
39
31
 
40
- When the task needs the React Native component tree, props, state, hooks, or render profiling, use the bundled passthrough:
32
+ Install the CLI first:
41
33
 
42
34
  ```bash
43
- agent-device react-devtools status
44
- agent-device react-devtools get tree --depth 3
45
- agent-device react-devtools profile start
46
- agent-device react-devtools profile stop
47
- agent-device react-devtools profile slow --limit 5
35
+ npm install -g agent-device
36
+ agent-device --version
37
+ agent-device help workflow
48
38
  ```
49
39
 
50
- `react-devtools` dynamically runs pinned `agent-react-devtools@0.4.0` commands 1:1, so `agent-device` covers both the device/app runtime layer and React component internals without making React DevTools part of the daemon.
40
+ The CLI help is the source of truth for agents and is shipped with the installed version. Skills are optional but recommended when your agent runtime supports them: they auto-route device, React DevTools, and dogfood tasks to the right `agent-device help <topic>` page and verify the CLI is new enough before acting.
51
41
 
52
- When an Android session is connected through a remote bridge profile, `react-devtools` automatically opens a lease-scoped companion tunnel for the local DevTools daemon on port 8097 and cleans it up when the command exits.
42
+ If you install skills separately, keep the CLI on `agent-device >= 0.14.0`. Older CLIs do not include the workflow help topics that the router skills expect.
53
43
 
54
- Remote Android 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.
44
+ ```bash
45
+ npm install -g agent-device@latest
46
+ agent-device --version
47
+ agent-device help
48
+ ```
55
49
 
56
- ## Command Flow
50
+ `agent-device` performs a lightweight background upgrade check for interactive CLI runs and, when a newer package is available, suggests a global reinstall command. Updating the package also refreshes the bundled `skills/` shipped with the CLI.
57
51
 
58
- The canonical loop is:
52
+ Prerequisites: Node.js 22+, Xcode for iOS/tvOS/macOS targets, Android SDK + ADB for Android, and macOS Accessibility permission for desktop automation. See [Installation](https://incubator.callstack.com/agent-device/docs/installation).
53
+
54
+ Try the loop.
59
55
 
60
56
  ```bash
57
+ # Find the app.
61
58
  agent-device apps --platform ios
59
+
60
+ # Start a session.
62
61
  agent-device open SampleApp --platform ios
62
+
63
+ # Inspect the current screen. -i returns interactive elements only.
63
64
  agent-device snapshot -i
64
- agent-device press @e3
65
- agent-device diff snapshot -i
66
- agent-device fill @e5 "test"
67
- agent-device press @e5
68
- agent-device type " more" --delay-ms 80
65
+ # @e1 [heading] "Settings"
66
+ # @e2 [button] "Sign In"
67
+ # @e3 [text-field] "Email"
68
+
69
+ # Act, capture a screenshot, and close.
70
+ agent-device fill @e3 "test"
71
+ agent-device screenshot ./artifacts/settings.png
69
72
  agent-device close
70
73
  ```
71
74
 
72
- In practice, most work follows the same pattern:
75
+ Snapshots assign refs like `@e1`, `@e2`, and `@e3` to current-screen elements. Refs from the default snapshot are immediately actionable; for hidden content, scroll and re-snapshot.
73
76
 
74
- 1. Discover the exact app id with `apps` if the package or bundle name is uncertain.
75
- 2. `open` a target app or URL.
76
- 3. `snapshot -i` to inspect the current screen.
77
- 4. `press`, `fill`, `scroll`, `get`, or `wait` using refs or selectors. On iOS and Android, default snapshot text follows the same visible-first contract: refs shown in default output are actionable now, while hidden content is surfaced as scroll/list discovery hints instead of tappable off-screen refs. If the target only appears in a hidden-content hint, use `scroll <direction>` and re-snapshot.
78
- Use `rotate <orientation>` when a flow needs a deterministic portrait or landscape state on mobile targets.
79
- 5. `diff snapshot` or re-snapshot after UI changes.
80
- 6. `close` when the session is finished.
77
+ ## Where To Run agent-device
81
78
 
82
- In non-JSON mode, core mutating commands print a short success acknowledgment so agents and humans can distinguish successful actions from dropped or silent no-ops.
79
+ | Path | Best for | Start with |
80
+ | --- | --- | --- |
81
+ | Local | Exploration, debugging, and development loops on simulators, emulators, physical devices, macOS apps, and Linux desktop targets. | Follow the Quick Start. |
82
+ | CI/CD | Automated PR and merge validation with replay scripts and captured artifacts. | Start with the [EAS workflow template](https://github.com/callstackincubator/eas-agent-device/blob/main/.eas/workflows/agent-qa-mobile.yml). GitHub Actions template coming soon. |
83
+ | Cloud | Linux runners, managed devices, and remote execution. | Use [Agent Device Cloud](https://agent-device.dev/cloud) or [contact Callstack](mailto:hello@callstack.com) for team-scale QA. |
83
84
 
84
- ## Where To Go Next
85
+ ## Capabilities
85
86
 
86
- For people:
87
+ - **Platforms**: iOS, Android, tvOS, Android TV, macOS, and Linux. Real devices and simulators are supported.
88
+ - **Capture**: screenshots, video, logs, network traffic, performance data, accessibility snapshots, and React render profiles.
89
+ - **Produce**: replayable `.ad` scripts (recorded replay files that run locally or in CI), e2e test runs, snapshot and screenshot diffs, and debugging artifacts.
90
+ - **React Native and Expo**: component tree inspection, props/state/hooks, and render profiling.
91
+ - **License**: MIT. Free to use.
87
92
 
88
- - [Website](https://agent-device.dev/)
89
- - [Docs](https://incubator.callstack.com/agent-device/docs/introduction)
93
+ ## How It Works
90
94
 
91
- For agents:
95
+ `agent-device` runs session-aware commands through platform backends: XCTest for iOS and tvOS, ADB plus the Android snapshot helper for Android, a local helper for macOS desktop automation, and AT-SPI for Linux desktop targets. See [Introduction](https://incubator.callstack.com/agent-device/docs/introduction) and [Commands](https://incubator.callstack.com/agent-device/docs/commands) for platform details.
92
96
 
93
- - [agent-device skill](skills/agent-device/SKILL.md)
94
- - [react-devtools skill](skills/react-devtools/SKILL.md)
95
- - [dogfood skill](skills/dogfood/SKILL.md)
96
- - [agent-device skill on ClawHub](https://clawhub.ai/okwasniewski/agent-device)
97
+ ## Used By
97
98
 
98
- ## Install
99
+ Used by teams and developers at Callstack, Expensify, Shopify, Kindred, Total Wine & More, LegendList, HerLyfe, App & Flow, and more.
99
100
 
100
- ```bash
101
- npm install -g agent-device
102
- ```
101
+ ## Documentation
103
102
 
104
- `agent-device` now performs a lightweight background upgrade check for interactive CLI runs and, when a newer package is available, suggests a global reinstall command. Updating the package also refreshes the bundled `skills/` shipped with the CLI.
103
+ - [Installation](https://incubator.callstack.com/agent-device/docs/installation)
104
+ - [Commands](https://incubator.callstack.com/agent-device/docs/commands)
105
+ - [Replay & E2E](https://incubator.callstack.com/agent-device/docs/replay-e2e)
106
+ - [Known limitations](https://incubator.callstack.com/agent-device/docs/known-limitations)
105
107
 
106
- Set `AGENT_DEVICE_NO_UPDATE_NOTIFIER=1` to disable the notice.
108
+ Agent integration:
107
109
 
108
- On macOS, `agent-device` includes a local `agent-device-macos-helper` source package that is built on demand for desktop permission checks, alert handling, and helper-backed desktop snapshot surfaces. Release distribution should use a signed/notarized helper build; source checkouts fall back to a local Swift build. Local helper overrides through `AGENT_DEVICE_MACOS_HELPER_BIN` must use an absolute executable path.
110
+ - [agent-device skill](skills/agent-device/SKILL.md)
111
+ - [react-devtools skill](skills/react-devtools/SKILL.md)
112
+ - [dogfood skill](skills/dogfood/SKILL.md)
113
+ - [agent-device skill on ClawHub](https://clawhub.ai/okwasniewski/agent-device)
109
114
 
110
115
  ## Contributing
111
116
 
@@ -113,4 +118,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md).
113
118
 
114
119
  ## Made at Callstack
115
120
 
116
- 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.
121
+ agent-device is open source and MIT licensed. Try the [EAS workflow template](https://github.com/callstackincubator/eas-agent-device/blob/main/.eas/workflows/agent-qa-mobile.yml), use [Agent Device Cloud](https://agent-device.dev/cloud), or contact us at hello@callstack.com.
@@ -0,0 +1,75 @@
1
+ # Android Snapshot Helper
2
+
3
+ Small instrumentation APK used to capture Android accessibility snapshots without relying on
4
+ `uiautomator dump`'s fixed idle wait behavior. The helper enables Android's interactive-window
5
+ retrieval flag and serializes every accessible window root returned by `UiAutomation.getWindows()`
6
+ so keyboards and system overlays can appear in the same snapshot. If interactive window roots are
7
+ unavailable, it falls back to the active-window root.
8
+
9
+ The helper is intentionally provider-neutral. Local `adb`, cloud ADB tunnels, and remote device
10
+ providers can all install and run the same APK as long as they can execute ADB-style operations.
11
+ Released helper APKs use the committed `debug.keystore`; do not rotate it casually, because Android
12
+ requires a stable signing certificate for `adb install -r` upgrades.
13
+
14
+ ## Build
15
+
16
+ ```sh
17
+ sh ./scripts/build-android-snapshot-helper.sh 0.13.3 .tmp/android-snapshot-helper
18
+ ```
19
+
20
+ The build uses Android SDK command-line tools directly. It expects `ANDROID_HOME` or
21
+ `ANDROID_SDK_ROOT` to point at an SDK with `platforms/android-36` and matching build tools.
22
+ `pnpm prepack` builds the npm-bundled helper into `android-snapshot-helper/dist`; npm users get
23
+ that APK in the package and the first helper-backed `snapshot` installs it automatically when
24
+ missing or outdated.
25
+
26
+ ## Run
27
+
28
+ ```sh
29
+ adb install -r -t .tmp/android-snapshot-helper/agent-device-android-snapshot-helper-0.13.3.apk
30
+ adb shell am instrument -w \
31
+ -e waitForIdleTimeoutMs 500 \
32
+ -e timeoutMs 8000 \
33
+ -e maxDepth 128 \
34
+ -e maxNodes 5000 \
35
+ com.callstack.agentdevice.snapshothelper/.SnapshotInstrumentation
36
+ ```
37
+
38
+ `maxDepth` also caps recursive traversal depth inside the helper.
39
+ The `-t` install flag is required because the helper is a test-only instrumentation APK.
40
+ Devices or providers that block test-package installs must allow this package before helper capture
41
+ can run.
42
+
43
+ ## Output Contract
44
+
45
+ The APK emits instrumentation status records using
46
+ `agentDeviceProtocol=android-snapshot-helper-v1`.
47
+
48
+ Each XML chunk is sent with:
49
+
50
+ - `outputFormat=uiautomator-xml`
51
+ - `chunkIndex`
52
+ - `chunkCount`
53
+ - `payloadBase64`
54
+
55
+ The final instrumentation result includes:
56
+
57
+ - `ok=true`
58
+ - `helperApiVersion=1`
59
+ - `waitForIdleTimeoutMs`
60
+ - `timeoutMs`
61
+ - `maxDepth`
62
+ - `maxNodes`
63
+ - `rootPresent`
64
+ - `captureMode` (`interactive-windows` or `active-window`)
65
+ - `windowCount`
66
+ - `nodeCount`
67
+ - `truncated`
68
+ - `elapsedMs`
69
+
70
+ Failures return `ok=false`, `errorType`, and `message` in the final result.
71
+
72
+ The release manifest is a stable provider contract for the current helper protocol. Providers should
73
+ resolve the APK from `apkUrl`, verify `sha256`, install using `installArgs`, and run
74
+ `instrumentationRunner`. `installArgs` must start with `install`; extra arguments are limited to the
75
+ allowlisted adb install flags `-r`, `-t`, `-d`, and `-g`, and the consumer appends the APK path.
@@ -0,0 +1 @@
1
+ d7645d58e96f0b6b06ddebf43b904e879a26a86985f927cbb804a09bafa6393b agent-device-android-snapshot-helper-0.14.1.apk
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "android-snapshot-helper",
3
+ "version": "0.14.1",
4
+ "releaseTag": "v0.14.1",
5
+ "assetName": "agent-device-android-snapshot-helper-0.14.1.apk",
6
+ "apkUrl": null,
7
+ "sha256": "d7645d58e96f0b6b06ddebf43b904e879a26a86985f927cbb804a09bafa6393b",
8
+ "checksumName": "agent-device-android-snapshot-helper-0.14.1.apk.sha256",
9
+ "packageName": "com.callstack.agentdevice.snapshothelper",
10
+ "versionCode": 14001,
11
+ "instrumentationRunner": "com.callstack.agentdevice.snapshothelper/.SnapshotInstrumentation",
12
+ "minSdk": 23,
13
+ "targetSdk": 36,
14
+ "outputFormat": "uiautomator-xml",
15
+ "statusProtocol": "android-snapshot-helper-v1",
16
+ "installArgs": ["install", "-r", "-t"]
17
+ }
@@ -0,0 +1,4 @@
1
+ import e from"node:crypto";import t from"node:fs";import n from"node:fs/promises";import r from"node:os";import i from"node:path";import{AppError as o}from"./9152.js";let a="android-snapshot-helper",s="com.callstack.agentdevice.snapshothelper",l="com.callstack.agentdevice.snapshothelper/.SnapshotInstrumentation",u="android-snapshot-helper-v1",d="uiautomator-xml",c=new Set(["-r","-t","-d","-g"]);async function h(e){let t=await w(e.apkPath);if(t!==e.manifest.sha256)throw new o("COMMAND_FAILED","Android snapshot helper APK checksum mismatch",{apkPath:e.apkPath,expectedSha256:e.manifest.sha256,actualSha256:t})}async function f(e){let t=e.fetch??fetch,a=await t(e.manifestUrl);if(!a.ok)throw new o("COMMAND_FAILED","Failed to download Android snapshot helper manifest",{manifestUrl:e.manifestUrl,status:a.status,statusText:a.statusText});let s=p(JSON.parse((await m(a,65536,"Android snapshot helper manifest")).toString("utf8")));if(!s.apkUrl)throw new o("COMMAND_FAILED","Android snapshot helper manifest does not include apkUrl",{manifestUrl:e.manifestUrl});let l=e.cacheDir??i.join(r.tmpdir(),`agent-device-android-snapshot-helper-${s.version}`),u=!e.cacheDir;await n.mkdir(l,{recursive:!0});let d=s.assetName??`agent-device-android-snapshot-helper-${s.version}.apk`,c=i.join(l,d),f=await t(s.apkUrl);if(!f.ok)throw new o("COMMAND_FAILED","Failed to download Android snapshot helper APK",{apkUrl:s.apkUrl,status:f.status,statusText:f.statusText});await n.writeFile(c,await m(f,0x1400000,"Android snapshot helper APK"));let A={apkPath:c,manifest:s};return await h(A),{...A,cleanup:async()=>{await n.rm(u?l:c,{recursive:u,force:!0})}}}function p(e){var t,n;if(!e||"object"!=typeof e||Array.isArray(e))throw new o("INVALID_ARGS","Android snapshot helper manifest must be an object.");return{name:v(e.name,"name",a),version:g(e.version,"version"),releaseTag:N(e.releaseTag),assetName:N(e.assetName),apkUrl:(t=e.apkUrl,n="apkUrl",null===t?null:g(t,n)),sha256:function(e){let t=g(e,"sha256").trim().toLowerCase();if(64!==t.length||!function(e){for(let t of e){let e=t.charCodeAt(0),n=e>=48&&e<=57,r=e>=97&&e<=102;if(!n&&!r)return!1}return!0}(t))throw new o("INVALID_ARGS","Android snapshot helper manifest sha256 must be a 64-character hex string.");return t}(e.sha256),checksumName:N(e.checksumName),packageName:g(e.packageName,"packageName"),versionCode:M(e.versionCode,"versionCode"),instrumentationRunner:g(e.instrumentationRunner,"instrumentationRunner"),minSdk:M(e.minSdk,"minSdk"),targetSdk:void 0===e.targetSdk?void 0:M(e.targetSdk,"targetSdk"),outputFormat:v(e.outputFormat,"outputFormat",d),statusProtocol:v(e.statusProtocol,"statusProtocol",u),installArgs:A(e.installArgs)}}async function m(e,t,n){let r=e.headers.get("content-length");if(null!==r){let e=Number(r);if(Number.isFinite(e)&&e>t)throw new o("COMMAND_FAILED",`${n} download exceeds size limit`,{contentLength:e,maxBytes:t})}if(!e.body){let r=Buffer.from(await e.arrayBuffer());if(r.length>t)throw new o("COMMAND_FAILED",`${n} download exceeds size limit`,{contentLength:r.length,maxBytes:t});return r}let i=e.body.getReader(),a=[],s=0;try{for(;;){let{done:e,value:r}=await i.read();if(e)break;if((s+=r.byteLength)>t)throw new o("COMMAND_FAILED",`${n} download exceeds size limit`,{contentLength:s,maxBytes:t});a.push(Buffer.from(r))}}finally{i.releaseLock()}return Buffer.concat(a,s)}function A(e){let t=function(e,t){if(!Array.isArray(e)||!e.every(e=>"string"==typeof e))throw new o("INVALID_ARGS",`Android snapshot helper manifest ${t} must be a string array.`);return e}(e,"installArgs");if("install"!==t[0])throw new o("INVALID_ARGS",'Android snapshot helper manifest installArgs must start with "install".');if(t.some(e=>e.includes("\0")))throw new o("INVALID_ARGS","Android snapshot helper manifest installArgs must not contain null bytes.");let n=t.slice(1).find(e=>{var t;return t=e,!c.has(t)});if(n)throw new o("INVALID_ARGS",`Android snapshot helper manifest installArgs contains unsupported install flag "${n}".`);return t}async function w(n){return await new Promise((r,i)=>{let o=e.createHash("sha256"),a=t.createReadStream(n);a.on("error",i),a.on("data",e=>o.update(e)),a.on("end",()=>r(o.digest("hex")))})}function g(e,t){if("string"!=typeof e||0===e.trim().length)throw new o("INVALID_ARGS",`Android snapshot helper manifest ${t} is required.`);return e}function N(e){return"string"==typeof e&&e.trim().length>0?e:void 0}function M(e,t){if("number"!=typeof e||!Number.isInteger(e))throw new o("INVALID_ARGS",`Android snapshot helper manifest ${t} must be an integer.`);return e}function v(e,t,n){if(e!==n)throw new o("INVALID_ARGS",`Android snapshot helper manifest ${t} must be "${n}".`);return n}function x(e){let t=`${e??""}`.toLowerCase();return t.includes("scroll")||t.includes("recyclerview")||t.includes("listview")||t.includes("gridview")||t.includes("collectionview")||"table"===t}function b(e){return!!x(e.type)||`${e.role??""} ${e.subrole??""}`.toLowerCase().includes("scroll")}function I(e,t,n){let{sourceNodes:r,...i}=D(_(e),t,n);return i}function D(e,t,n){let r={nodes:[],sourceNodes:[],maxNodes:t,maxDepth:n.depth??1/0,options:n,analysis:function(e){let t=0,n=0,r=[...e.children];for(;r.length>0;){let e=r.pop();t+=1,n=Math.max(n,e.depth),r.push(...e.children)}return{rawNodeCount:t,maxDepth:n}}(e),interactiveDescendantMemo:new Map,truncated:!1},i=n.scope?function(e,t){let n=t.toLowerCase(),r=[...e.children],i=0;for(;i<r.length;){let e=r[i++],t=e.label?.toLowerCase()??"",o=e.value?.toLowerCase()??"",a=e.identifier?.toLowerCase()??"";if(t.includes(n)||o.includes(n)||a.includes(n))return e;r.push(...e.children)}return null}(e,n.scope):null;for(let t of i?[i]:e.children)if(function e(t,n,r,i,o=!1,a=!1){if(t.nodes.length>=t.maxNodes){t.truncated=!0;return}if(r>t.maxDepth)return;let s=t.options.raw||function(e,t,n,r,i){var o,a,s;let l=function(e){let t=T(e.type),n=!!(e.label&&e.label.trim().length>0),r=!!(e.identifier&&e.identifier.trim().length>0);return{type:t,hasMeaningfulText:n&&!O(e.label??""),hasMeaningfulId:r&&!O(e.identifier??""),isStructural:function(e){let t=e.split(".").pop()??e;return t.includes("layout")||"viewgroup"===t||"view"===t}(t),isVisual:"imageview"===t||"imagebutton"===t}}(e);return t.interactiveOnly?function(e,t,n,r,i){var o,a,s,l;return!!(e.hittable||x(t.type)&&r)||(o=t,a=n,s=r,l=i,(!!o.hasMeaningfulText||!!o.hasMeaningfulId)&&!o.isVisual&&(!o.isStructural||!!l)&&(a||s||l))}(e,l,n,r,i):t.compact?l.hasMeaningfulText||l.hasMeaningfulId||!!e.hittable:!l.isStructural&&!l.isVisual||(o=e,a=l,s=r,!!o.hittable||!!a.hasMeaningfulText||!!a.hasMeaningfulId&&!!s||s)}(n,t.options,o,function e(t,n){let r=t.interactiveDescendantMemo.get(n);if(void 0!==r)return r;for(let r of n.children)if(r.hittable||e(t,r))return t.interactiveDescendantMemo.set(n,!0),!0;return t.interactiveDescendantMemo.set(n,!1),!1}(t,n),a)?function(e,t,n,r){let i=e.nodes.length;return e.sourceNodes.push(t),e.nodes.push({index:i,type:t.type??void 0,label:t.label??void 0,value:t.value??void 0,identifier:t.identifier??void 0,rect:t.rect,enabled:t.enabled,hittable:t.hittable,depth:n,parentIndex:r,...t.hiddenContentAbove?{hiddenContentAbove:!0}:{},...t.hiddenContentBelow?{hiddenContentBelow:!0}:{}}),i}(t,n,r,i):i,l=o||!!n.hittable,u=a||function(e){if(!e)return!1;let t=T(e);return t.includes("recyclerview")||t.includes("listview")||t.includes("gridview")}(n.type);for(let i of n.children)if(e(t,i,r+1,s,l,u),t.truncated)return}(r,t,0),r.truncated)break;let o={nodes:r.nodes,sourceNodes:r.sourceNodes,analysis:r.analysis};return r.truncated?{...o,truncated:!0}:o}function S(e){let t=function(e){let t=new Map,n=e.indexOf(" "),r=e.lastIndexOf(">");if(n<0||r<=n)return t;let i=n;for(;i<r&&!((i=C(e,i,r))>=r);){var o;let n=e[i];if("/"===n||">"===n)break;let a=i;for(;i<r&&!("="===(o=e[i]??"")||"/"===o||">"===o||k(o));)i+=1;let s=e.slice(a,i);if(i=C(e,i,r),!s||"="!==e[i])break;i=C(e,i+1,r);let l=e[i];if('"'!==l&&"'"!==l)break;let u=i+=1;for(;i<r&&e[i]!==l;)i+=1;if(i>=r)break;t.set(s,function(e){let t="",n=0;for(;n<e.length;){let r=e.indexOf("&",n);if(r<0){t+=e.slice(n);break}t+=e.slice(n,r);let i=e.indexOf(";",r+1);if(i<0){t+=e.slice(r);break}t+=function(e){switch(e){case"amp":return"&";case"lt":return"<";case"gt":return">";case"quot":return'"';case"apos":return"'";default:return function(e){if(!e.startsWith("#"))return;let t=e[1]?.toLowerCase()==="x"?16:10,n=16===t?e.slice(2):e.slice(1);if(!n||!function(e,t){for(let n of e){let e=n.charCodeAt(0),r=e>=48&&e<=57;if(10===t){if(!r)return!1;continue}let i=e>=65&&e<=70,o=e>=97&&e<=102;if(!r&&!i&&!o)return!1}return!0}(n,t))return;let r=Number.parseInt(n,t);if(Number.isFinite(r))try{return String.fromCodePoint(r)}catch{return}}(e)}}(e.slice(r+1,i))??e.slice(r,i+1),n=i+1}return t}(e.slice(u,i))),i+=1}return t}(e),n=e=>{let n=y(t,e);if(null!==n)return"true"===n};return{text:y(t,"text"),desc:y(t,"content-desc"),resourceId:y(t,"resource-id"),className:y(t,"class"),bounds:y(t,"bounds"),clickable:n("clickable"),enabled:n("enabled"),focusable:n("focusable"),focused:n("focused")}}function C(e,t,n){for(;t<n&&k(e[t]??"");)t+=1;return t}function k(e){return" "===e||"\n"===e||"\r"===e||" "===e}function y(e,t){return e.get(t)??null}function L(e){if(!e)return;let t=/\[(\d+),(\d+)\]\[(\d+),(\d+)\]/.exec(e);if(!t)return;let n=Number(t[1]),r=Number(t[2]);return{x:n,y:r,width:Math.max(0,Number(t[3])-n),height:Math.max(0,Number(t[4])-r)}}function _(e){let t={type:null,label:null,value:null,identifier:null,depth:-1,children:[]},n=[t],r=/<node\b[^>]*>|<\/node>/g,i=r.exec(e);for(;i;){let t=i[0];if(t.startsWith("</node")){n.length>1&&n.pop(),i=r.exec(e);continue}let o=S(t),a=L(o.bounds),s=n[n.length-1],l={type:o.className,label:o.text||o.desc,value:o.text,identifier:o.resourceId,rect:a,enabled:o.enabled,hittable:o.clickable??o.focusable,depth:s.depth+1,parentIndex:void 0,children:[]};s.children.push(l),t.endsWith("/>")||n.push(l),i=r.exec(e)}return t}function T(e){return e?e.toLowerCase():""}function O(e){let t=e.trim();return!!t&&/^[\w.]+:id\/[\w.-]+$/i.test(t)}async function E(e){let t,n=e.waitForIdleTimeoutMs??500,r=e.timeoutMs??8e3,i=e.commandTimeoutMs??r+5e3,a=e.maxDepth??128,l=e.maxNodes??5e3,u=e.packageName??s,d=e.instrumentationRunner??`${u}/.SnapshotInstrumentation`,c=["shell","am","instrument","-w","-e","waitForIdleTimeoutMs",String(n),"-e","timeoutMs",String(r),"-e","maxDepth",String(a),"-e","maxNodes",String(l),d],h=await e.adb(c,{allowFailure:!0,timeoutMs:i});try{t=F(`${h.stdout}
2
+ ${h.stderr}`)}catch(e){throw new o("COMMAND_FAILED",0===h.exitCode?"Android snapshot helper output could not be parsed":"Android snapshot helper failed before returning parseable output",{stdout:h.stdout,stderr:h.stderr,exitCode:h.exitCode},e)}if(0!==h.exitCode)throw new o("COMMAND_FAILED","Android snapshot helper failed",{stdout:h.stdout,stderr:h.stderr,exitCode:h.exitCode,helper:t.metadata});return t}function F(e){var t,n;let r=function(e){var t;let n={status:[],results:[],currentStatus:null,currentResult:null};for(let t of e.split(/\r?\n/))!function(e,t){if(e.startsWith("INSTRUMENTATION_STATUS: ")){t.currentStatus??={},$(e.slice(24),t.currentStatus);return}if(e.startsWith("INSTRUMENTATION_STATUS_CODE: "))return P(t);if(e.startsWith("INSTRUMENTATION_RESULT: ")){t.currentResult??={},$(e.slice(24),t.currentResult);return}e.startsWith("INSTRUMENTATION_CODE: ")&&U(t)}(t,n);return P(t=n),U(t),{status:n.status,results:n.results}}(e),i=function(e){let t=e.find(e=>e.agentDeviceProtocol===u);if(!t)throw new o("COMMAND_FAILED","Android snapshot helper did not return a final result");if("true"!==t.ok){var n;throw new o("COMMAND_FAILED",(n=t).message&&"null"!==n.message?n.message:n.errorType||"Android snapshot helper returned an error",{errorType:t.errorType,helper:t})}return t}(r.results);return{xml:function(e,t){if(0===e.length)throw new o("COMMAND_FAILED","Android snapshot helper did not return XML chunks",{helper:t});let n=function(e){let t=e[0]?.count??e.length;if(t<1||e.length!==t||e.some(e=>e.count!==t))throw new o("COMMAND_FAILED","Android snapshot helper returned incomplete XML chunks",{expectedChunks:t,actualChunks:e.length});return t}(e),r=Buffer.concat(function(e,t){let n=[];for(let r=0;r<t;r+=1){let i=e.get(r);if(void 0===i)throw new o("COMMAND_FAILED","Android snapshot helper returned incomplete XML chunks",{missingChunkIndex:r,expectedChunks:t});n.push(Buffer.from(i,"base64"))}return n}(function(e,t){let n=new Map;for(let r of e){if(void 0===r.index||r.index<0||r.index>=t)throw new o("COMMAND_FAILED","Android snapshot helper returned invalid chunk index",{chunkIndex:r.index,expectedChunks:t});if(n.has(r.index))throw new o("COMMAND_FAILED","Android snapshot helper returned duplicate XML chunks",{chunkIndex:r.index});n.set(r.index,r.payloadBase64)}return n}(e,n),n)).toString("utf8");if(!r.includes("<hierarchy")||!r.includes("</hierarchy>"))throw new o("COMMAND_FAILED","Android snapshot helper output did not contain XML",{xml:r});return r}(r.status.filter(e=>e.agentDeviceProtocol===u&&e.outputFormat===d&&"string"==typeof e.payloadBase64).map(e=>({index:H(e.chunkIndex),count:H(e.chunkCount),payloadBase64:e.payloadBase64})),i),metadata:{helperApiVersion:(t=i).helperApiVersion,outputFormat:d,waitForIdleTimeoutMs:H(t.waitForIdleTimeoutMs),timeoutMs:H(t.timeoutMs),maxDepth:H(t.maxDepth),maxNodes:H(t.maxNodes),rootPresent:B(t.rootPresent),captureMode:"interactive-windows"===(n=t.captureMode)||"active-window"===n?n:void 0,windowCount:H(t.windowCount),nodeCount:H(t.nodeCount),truncated:B(t.truncated),elapsedMs:H(t.elapsedMs)}}}function R(e,t={outputFormat:d},n={},r=800){return{...I(e,r,n),metadata:t}}function P(e){e.currentStatus&&(e.status.push(e.currentStatus),e.currentStatus=null)}function U(e){e.currentResult&&(e.results.push(e.currentResult),e.currentResult=null)}function $(e,t){let n=e.indexOf("=");n<0||(t[e.slice(0,n)]=e.slice(n+1))}function H(e){if(void 0===e)return;let t=Number(e);return Number.isFinite(t)?t:void 0}function B(e){return"true"===e||"false"!==e&&void 0}let V=new Map;function G(e){K(W(e.deviceKey,e.packageName,e.versionCode))}function W(e,t,n){return e?`${e}\0${t}\0${n}`:void 0}function K(e){e&&V.delete(e)}async function X(e){var t,n,r;let{adb:i,artifact:a}=e,s=e.installPolicy??"missing-or-outdated",l=a.manifest.packageName,u=a.manifest.versionCode;if("never"===s)return{packageName:l,versionCode:u,installed:!1,reason:"skipped"};let d=W(e.deviceKey,l,u),c=d?V.get(d):void 0;if(d&&"always"!==s&&void 0!==c)return{packageName:l,versionCode:u,installedVersionCode:c,installed:!1,reason:"current"};let f=await j(i,l,e.timeoutMs),p=(t=s,n=f,r=u,"never"===t?"skipped":"always"===t?"forced":void 0===n?"missing":n<r?"outdated":"current");if("current"===p){if(void 0===f)throw Error("Expected installed versionCode for current Android snapshot helper");return d&&V.set(d,f),{packageName:l,versionCode:u,installedVersionCode:f,installed:!1,reason:p}}await h(a);let m=[...A(a.manifest.installArgs),a.apkPath],w=await z(i,m,{packageName:l,timeoutMs:e.timeoutMs});if(0!==w.exitCode)throw K(d),new o("COMMAND_FAILED","Failed to install Android snapshot helper",{packageName:l,versionCode:u,stdout:w.stdout,stderr:w.stderr,exitCode:w.exitCode});return d&&V.set(d,u),{packageName:l,versionCode:u,installedVersionCode:f,installed:!0,reason:p}}async function j(e,t,n){let r=await e(["shell","cmd","package","list","packages","--show-versioncode",t],{allowFailure:!0,timeoutMs:n});if(0===r.exitCode){var i=`${r.stdout}
3
+ ${r.stderr}`,o=t;let e=`package:${o}`;for(let t of i.split(/\r?\n/)){if(!t.startsWith(e)||t.length>e.length&&!/\s/.test(t[e.length]??""))continue;let n=/(?:^|\s)versionCode:(\d+)(?:\s|$)/.exec(t);if(n)return Number(n[1])}return}}async function z(e,t,n){var r;let i=await e(t,{allowFailure:!0,timeoutMs:n.timeoutMs});if(0===i.exitCode||(r=i,!`${r.stdout}
4
+ ${r.stderr}`.includes("INSTALL_FAILED_UPDATE_INCOMPATIBLE")))return i;let o=await e(["uninstall",n.packageName],{allowFailure:!0,timeoutMs:n.timeoutMs}),a=await e(t,{allowFailure:!0,timeoutMs:n.timeoutMs});return 0===a.exitCode?a:{...a,stderr:[a.stderr,o.stderr?`Previous uninstall stderr after INSTALL_FAILED_UPDATE_INCOMPATIBLE: ${o.stderr}`:""].filter(Boolean).join("\n")}}export{a as ANDROID_SNAPSHOT_HELPER_NAME,d as ANDROID_SNAPSHOT_HELPER_OUTPUT_FORMAT,s as ANDROID_SNAPSHOT_HELPER_PACKAGE,u as ANDROID_SNAPSHOT_HELPER_PROTOCOL,l as ANDROID_SNAPSHOT_HELPER_RUNNER,D as buildUiHierarchySnapshot,E as captureAndroidSnapshotWithHelper,X as ensureAndroidSnapshotHelper,G as forgetAndroidSnapshotHelperInstall,b as isScrollableNodeLike,x as isScrollableType,p as parseAndroidSnapshotHelperManifest,F as parseAndroidSnapshotHelperOutput,R as parseAndroidSnapshotHelperXml,L as parseBounds,I as parseUiHierarchy,_ as parseUiHierarchyTree,f as prepareAndroidSnapshotHelperArtifactFromManifestUrl,S as readNodeAttributes,h as verifyAndroidSnapshotHelperArtifact};