agent-device 0.1.1 → 0.1.3
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.
- package/README.md +17 -6
- package/dist/bin/axsnapshot +0 -0
- package/dist/src/861.js +1 -1
- package/dist/src/bin.js +61 -38
- package/dist/src/daemon.js +4 -4
- package/ios-runner/AXSnapshot/Sources/AXSnapshot/main.swift +305 -28
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +59 -20
- package/package.json +4 -3
- package/src/cli.ts +22 -0
- package/src/core/dispatch.ts +72 -4
- package/src/daemon.ts +358 -38
- package/src/platforms/android/devices.ts +13 -1
- package/src/platforms/android/index.ts +69 -0
- package/src/platforms/ios/ax-snapshot.ts +8 -10
- package/src/platforms/ios/index.ts +44 -30
- package/src/platforms/ios/runner-client.ts +13 -1
- package/src/utils/args.ts +55 -25
- package/src/utils/exec.ts +74 -3
- package/src/utils/interactors.ts +5 -0
- package/bin/axsnapshot +0 -0
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# agent-device
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CLI to control iOS and Android devices for AI agents.
|
|
4
4
|
|
|
5
5
|
This project mirrors the spirit of `agent-browser`, but targets iOS simulators/devices and Android emulators/devices.
|
|
6
6
|
|
|
7
7
|
## Current scope (v1)
|
|
8
8
|
- Platforms: iOS (simulator + limited device support) and Android (emulator + device).
|
|
9
|
-
- Core commands: `open`, `press`, `long-press`, `focus`, `type`, `fill`, `scroll`, `scrollintoview`, `screenshot`, `close`.
|
|
9
|
+
- Core commands: `open`, `back`, `home`, `app-switcher`, `press`, `long-press`, `focus`, `type`, `fill`, `scroll`, `scrollintoview`, `wait`, `alert`, `screenshot`, `close`.
|
|
10
10
|
- Inspection commands: `snapshot` (accessibility tree).
|
|
11
11
|
- Device tooling: `adb` (Android), `simctl`/`devicectl` (iOS via Xcode).
|
|
12
12
|
- Minimal dependencies; TypeScript executed directly on Node 22+ (no build step).
|
|
@@ -34,7 +34,11 @@ Examples:
|
|
|
34
34
|
```bash
|
|
35
35
|
agent-device open SampleApp
|
|
36
36
|
agent-device snapshot
|
|
37
|
+
agent-device snapshot -s @e7
|
|
37
38
|
agent-device click @e7
|
|
39
|
+
agent-device wait text "Camera"
|
|
40
|
+
agent-device alert wait 10000
|
|
41
|
+
agent-device back
|
|
38
42
|
agent-device type "hello"
|
|
39
43
|
agent-device screenshot --out ./screenshot.png
|
|
40
44
|
agent-device close SampleApp
|
|
@@ -43,9 +47,16 @@ agent-device close SampleApp
|
|
|
43
47
|
Best practice: run `snapshot` immediately before interactions to avoid stale coordinates if the Simulator window moves or UI changes.
|
|
44
48
|
When interacting with UI elements from a snapshot, prefer refs (e.g. `click @e7`) over raw coordinates. Refs are stable across runs and avoid coordinate drift.
|
|
45
49
|
|
|
50
|
+
Coordinates:
|
|
51
|
+
- All coordinate-based commands (`press`, `long-press`, `focus`, `fill`) use device coordinates with origin at top-left.
|
|
52
|
+
- X increases to the right, Y increases downward.
|
|
53
|
+
|
|
46
54
|
iOS snapshots:
|
|
47
55
|
- Default backend is `ax` (fast). It requires enabling Accessibility for the terminal app in System Settings.
|
|
48
56
|
- If AX is not available, use `--backend xctest` explicitly.
|
|
57
|
+
- If AX shows a container like `group` or `tab bar` without children, re-snapshot with XCTest and scope to the container label:
|
|
58
|
+
`snapshot --backend xctest -s "<label>"`
|
|
59
|
+
- Use `-s @ref` to scope the snapshot to the label of a prior ref in the current session.
|
|
49
60
|
|
|
50
61
|
Flags:
|
|
51
62
|
- `--platform ios|android`
|
|
@@ -59,13 +70,13 @@ Flags:
|
|
|
59
70
|
- `--backend ax|xctest` (snapshot only; defaults to `ax` on iOS)
|
|
60
71
|
|
|
61
72
|
Sessions:
|
|
62
|
-
- `open` starts a session.
|
|
73
|
+
- `open` starts a session. Without args boots/activates the target device/simulator without launching an app.
|
|
63
74
|
- All interaction commands require an open session.
|
|
64
75
|
- `close` stops the session and releases device resources. Pass an app to close it explicitly, or omit to just close the session.
|
|
65
76
|
- Use `--session <name>` to manage multiple sessions.
|
|
66
77
|
- Session logs are written to `~/.agent-device/sessions/<session>-<timestamp>.ad`.
|
|
67
78
|
|
|
68
|
-
Snapshot defaults to the AX backend on iOS simulators
|
|
79
|
+
Snapshot defaults to the AX backend on iOS simulators. Use `--backend xctest` if AX is unavailable.
|
|
69
80
|
|
|
70
81
|
## App resolution
|
|
71
82
|
- Bundle/package identifiers are accepted directly (e.g., `com.apple.Preferences`).
|
|
@@ -73,8 +84,8 @@ Snapshot defaults to the AX backend on iOS simulators and falls back to XCTest i
|
|
|
73
84
|
- Built-in aliases include `Settings` for both platforms.
|
|
74
85
|
|
|
75
86
|
## iOS notes
|
|
76
|
-
- Input commands (`press`, `type`, `scroll`, etc.) are supported only on simulators in v1.
|
|
77
|
-
-
|
|
87
|
+
- Input commands (`press`, `type`, `scroll`, etc.) are supported only on simulators in v1 and use the XCTest runner.
|
|
88
|
+
- `alert` and `scrollintoview` use the XCTest runner and are simulator-only in v1.
|
|
78
89
|
|
|
79
90
|
## Testing
|
|
80
91
|
|
|
Binary file
|
package/dist/src/861.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{spawn as e}from"node:child_process";function
|
|
1
|
+
import{spawn as e}from"node:child_process";function t(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}class n extends Error{constructor(e,n,o,r){super(n),t(this,"code",void 0),t(this,"details",void 0),t(this,"cause",void 0),this.code=e,this.details=o,this.cause=r}}function o(e){return e instanceof n?e:e instanceof Error?new n("UNKNOWN",e.message,void 0,e):new n("UNKNOWN","Unknown error",{err:e})}async function r(t,o,d={}){return new Promise((r,i)=>{let s=e(t,o,{cwd:d.cwd,env:d.env,stdio:["pipe","pipe","pipe"]}),u="",a=d.binaryStdout?Buffer.alloc(0):void 0,c="";d.binaryStdout||s.stdout.setEncoding("utf8"),s.stderr.setEncoding("utf8"),void 0!==d.stdin&&s.stdin.write(d.stdin),s.stdin.end(),s.stdout.on("data",e=>{d.binaryStdout?a=Buffer.concat([a??Buffer.alloc(0),Buffer.isBuffer(e)?e:Buffer.from(e)]):u+=e}),s.stderr.on("data",e=>{c+=e}),s.on("error",e=>{"ENOENT"===e.code?i(new n("TOOL_MISSING",`${t} not found in PATH`,{cmd:t},e)):i(new n("COMMAND_FAILED",`Failed to run ${t}`,{cmd:t,args:o},e))}),s.on("close",e=>{let s=e??1;0===s||d.allowFailure?r({stdout:u,stderr:c,exitCode:s,stdoutBuffer:a}):i(new n("COMMAND_FAILED",`${t} exited with code ${s}`,{cmd:t,args:o,stdout:u,stderr:c,exitCode:s}))})})}async function d(e){try{var t;let{shell:n,args:o}=(t=e,"win32"===process.platform?{shell:"cmd.exe",args:["/c","where",t]}:{shell:"bash",args:["-lc",`command -v ${t}`]}),d=await r(n,o,{allowFailure:!0});return 0===d.exitCode&&d.stdout.trim().length>0}catch{return!1}}function i(t,n,o={}){e(t,n,{cwd:o.cwd,env:o.env,stdio:"ignore",detached:!0}).unref()}async function s(t,o,r={}){return new Promise((d,i)=>{let s=e(t,o,{cwd:r.cwd,env:r.env,stdio:["pipe","pipe","pipe"]}),u="",a="",c=r.binaryStdout?Buffer.alloc(0):void 0;r.binaryStdout||s.stdout.setEncoding("utf8"),s.stderr.setEncoding("utf8"),void 0!==r.stdin&&s.stdin.write(r.stdin),s.stdin.end(),s.stdout.on("data",e=>{if(r.binaryStdout){c=Buffer.concat([c??Buffer.alloc(0),Buffer.isBuffer(e)?e:Buffer.from(e)]);return}let t=String(e);u+=t,r.onStdoutChunk?.(t)}),s.stderr.on("data",e=>{let t=String(e);a+=t,r.onStderrChunk?.(t)}),s.on("error",e=>{"ENOENT"===e.code?i(new n("TOOL_MISSING",`${t} not found in PATH`,{cmd:t},e)):i(new n("COMMAND_FAILED",`Failed to run ${t}`,{cmd:t,args:o},e))}),s.on("close",e=>{let s=e??1;0===s||r.allowFailure?d({stdout:u,stderr:a,exitCode:s,stdoutBuffer:c}):i(new n("COMMAND_FAILED",`${t} exited with code ${s}`,{cmd:t,args:o,stdout:u,stderr:a,exitCode:s}))})})}function u(t,o,r={}){let d=e(t,o,{cwd:r.cwd,env:r.env,stdio:["ignore","pipe","pipe"]}),i="",s="";d.stdout.setEncoding("utf8"),d.stderr.setEncoding("utf8"),d.stdout.on("data",e=>{i+=e}),d.stderr.on("data",e=>{s+=e});let a=new Promise((e,u)=>{d.on("error",e=>{"ENOENT"===e.code?u(new n("TOOL_MISSING",`${t} not found in PATH`,{cmd:t},e)):u(new n("COMMAND_FAILED",`Failed to run ${t}`,{cmd:t,args:o},e))}),d.on("close",d=>{let a=d??1;0===a||r.allowFailure?e({stdout:i,stderr:s,exitCode:a}):u(new n("COMMAND_FAILED",`${t} exited with code ${a}`,{cmd:t,args:o,stdout:i,stderr:s,exitCode:a}))})});return{child:d,wait:a}}export{fileURLToPath,pathToFileURL}from"node:url";export{default as node_net}from"node:net";export{default as node_fs,promises}from"node:fs";export{default as node_os}from"node:os";export{default as node_path}from"node:path";export{o as asAppError,n as errors_AppError,r as runCmd,u as runCmdBackground,i as runCmdDetached,s as runCmdStreaming,d as whichCmd};
|
package/dist/src/bin.js
CHANGED
|
@@ -1,50 +1,73 @@
|
|
|
1
|
-
import{node_path as e,fileURLToPath as t,asAppError as r,pathToFileURL as n,runCmdDetached as s,node_fs as o,node_os as i,node_net as a,errors_AppError as
|
|
2
|
-
`)}function
|
|
1
|
+
import{node_path as e,fileURLToPath as t,asAppError as r,pathToFileURL as n,runCmdDetached as s,node_fs as o,node_os as i,node_net as a,errors_AppError as l}from"./861.js";function c(e){process.stdout.write(`${JSON.stringify(e,null,2)}
|
|
2
|
+
`)}function d(e){let t=e.details?`
|
|
3
3
|
${JSON.stringify(e.details,null,2)}`:"";process.stderr.write(`Error (${e.code}): ${e.message}${t}
|
|
4
|
-
`)}let
|
|
5
|
-
`)}),o=setTimeout(()=>{s.destroy(),n(new
|
|
4
|
+
`)}let u=e.join(i.homedir(),".agent-device"),p=e.join(u,"daemon.json"),f=function(){let e=process.env.AGENT_DEVICE_DAEMON_TIMEOUT_MS;if(!e)return 6e4;let t=Number(e);return Number.isFinite(t)?Math.max(1e3,Math.floor(t)):6e4}();async function m(e){let t=await h(),r={...e,token:t.token};return await b(t,r)}async function h(){let t=w(),r=function(){try{let t=x();return JSON.parse(o.readFileSync(e.join(t,"package.json"),"utf8")).version??"0.0.0"}catch{return"0.0.0"}}();if(t&&t.version===r&&await y(t))return t;t&&(t.version!==r||!await y(t))&&o.existsSync(p)&&o.unlinkSync(p),await g();let n=Date.now();for(;Date.now()-n<5e3;){let e=w();if(e&&await y(e))return e;await new Promise(e=>setTimeout(e,100))}throw new l("COMMAND_FAILED","Failed to start daemon",{infoPath:p,hint:"Run pnpm build, or delete ~/.agent-device/daemon.json if stale."})}function w(){if(!o.existsSync(p))return null;try{let e=JSON.parse(o.readFileSync(p,"utf8"));if(!e.port||!e.token)return null;return e}catch{return null}}async function y(e){return new Promise(t=>{let r=a.createConnection({host:"127.0.0.1",port:e.port},()=>{r.destroy(),t(!0)});r.on("error",()=>{t(!1)})})}async function g(){let t=x(),r=e.join(t,"dist","src","daemon.js"),n=e.join(t,"src","daemon.ts"),i=o.existsSync(r);if(!i&&!o.existsSync(n))throw new l("COMMAND_FAILED","Daemon entry not found",{distPath:r,srcPath:n});let a=i?[r]:["--experimental-strip-types",n];s(process.execPath,a)}async function b(e,t){return new Promise((r,n)=>{let s=a.createConnection({host:"127.0.0.1",port:e.port},()=>{s.write(`${JSON.stringify(t)}
|
|
5
|
+
`)}),o=setTimeout(()=>{s.destroy(),n(new l("COMMAND_FAILED","Daemon request timed out",{timeoutMs:f}))},f),i="";s.setEncoding("utf8"),s.on("data",e=>{let t=(i+=e).indexOf("\n");if(-1===t)return;let a=i.slice(0,t).trim();if(a)try{let e=JSON.parse(a);s.end(),clearTimeout(o),r(e)}catch(e){clearTimeout(o),n(e)}}),s.on("error",e=>{clearTimeout(o),n(e)})})}function x(){let r=e.dirname(t(import.meta.url)),n=r;for(let t=0;t<6;t+=1){let t=e.join(n,"package.json");if(o.existsSync(t))return n;n=e.dirname(n)}return r}async function S(t){let n=function(e){let t={json:!1,help:!1},r=[];for(let n=0;n<e.length;n+=1){let s=e[n];if("--json"===s){t.json=!0;continue}if("--help"===s||"-h"===s){t.help=!0;continue}if("--verbose"===s||"-v"===s){t.verbose=!0;continue}if("-i"===s){t.snapshotInteractiveOnly=!0;continue}if("-c"===s){t.snapshotCompact=!0;continue}if("--raw"===s){t.snapshotRaw=!0;continue}if("--no-record"===s){t.noRecord=!0;continue}if("--record-json"===s){t.recordJson=!0;continue}if("--user-installed"===s){t.appsFilter="user-installed";continue}if("--all"===s){t.appsFilter="all";continue}if(s.startsWith("--backend")){let r=s.includes("=")?s.split("=")[1]:e[n+1];if(s.includes("=")||(n+=1),"ax"!==r&&"xctest"!==r)throw new l("INVALID_ARGS",`Invalid backend: ${r}`);t.snapshotBackend=r;continue}if(s.startsWith("--")){let[r,o]=s.split("="),i=o??e[n+1];switch(!o&&(n+=1),r){case"--platform":if("ios"!==i&&"android"!==i)throw new l("INVALID_ARGS",`Invalid platform: ${i}`);t.platform=i;break;case"--depth":{let e=Number(i);if(!Number.isFinite(e)||e<0)throw new l("INVALID_ARGS",`Invalid depth: ${i}`);t.snapshotDepth=Math.floor(e);break}case"--scope":t.snapshotScope=i;break;case"--device":t.device=i;break;case"--udid":t.udid=i;break;case"--serial":t.serial=i;break;case"--out":t.out=i;break;case"--session":t.session=i;break;default:throw new l("INVALID_ARGS",`Unknown flag: ${r}`)}continue}if("-d"===s){let r=e[n+1];n+=1;let s=Number(r);if(!Number.isFinite(s)||s<0)throw new l("INVALID_ARGS",`Invalid depth: ${r}`);t.snapshotDepth=Math.floor(s);continue}if("-s"===s){let r=e[n+1];n+=1,t.snapshotScope=r;continue}r.push(s)}return{command:r.shift()??null,positionals:r,flags:t}}(t);(n.flags.help||!n.command)&&(process.stdout.write(`agent-device <command> [args] [--json]
|
|
6
|
+
|
|
7
|
+
CLI to control iOS and Android devices for AI agents.
|
|
6
8
|
|
|
7
9
|
Commands:
|
|
8
|
-
open
|
|
9
|
-
close [app]
|
|
10
|
+
open [app] Boot device/simulator; optionally launch app
|
|
11
|
+
close [app] Close app or just end session
|
|
10
12
|
snapshot [-i] [-c] [-d <depth>] [-s <scope>] [--raw] [--backend ax|xctest]
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
13
|
+
Capture accessibility tree
|
|
14
|
+
-i Interactive elements only
|
|
15
|
+
-c Compact output (drop empty structure)
|
|
16
|
+
-d <depth> Limit snapshot depth
|
|
17
|
+
-s <scope> Scope snapshot to label/identifier
|
|
18
|
+
--raw Raw node output
|
|
19
|
+
--backend ax|xctest ax: macOS Accessibility tree (fast, recommended, needs permissions)
|
|
20
|
+
xctest: XCTest snapshot (slower, no permissions)
|
|
21
|
+
devices List available devices
|
|
22
|
+
apps [--user-installed|--all] List installed apps (Android launchable by default, iOS simulator)
|
|
23
|
+
back Navigate back (where supported)
|
|
24
|
+
home Go to home screen (where supported)
|
|
25
|
+
app-switcher Open app switcher (where supported)
|
|
26
|
+
wait <ms>|text <text>|@ref [timeoutMs] Wait for duration or text to appear
|
|
27
|
+
alert [get|accept|dismiss|wait] [timeout] Inspect or handle alert (iOS simulator)
|
|
28
|
+
click <@ref> Click element by snapshot ref
|
|
29
|
+
get text <@ref> Return element text by ref
|
|
30
|
+
get attrs <@ref> Return element attributes by ref
|
|
31
|
+
replay <path> Replay a recorded session
|
|
32
|
+
press <x> <y> Tap at coordinates
|
|
33
|
+
long-press <x> <y> [durationMs] Long press (where supported)
|
|
34
|
+
focus <x> <y> Focus input at coordinates
|
|
35
|
+
type <text> Type text in focused field
|
|
36
|
+
fill <x> <y> <text> | fill <@ref> <text> Tap then type
|
|
37
|
+
scroll <direction> [amount] Scroll in direction (0-1 amount)
|
|
38
|
+
scrollintoview <text> Scroll until text appears (Android only)
|
|
39
|
+
screenshot [--out path] Capture screenshot
|
|
40
|
+
record start [path] Start screen recording
|
|
41
|
+
record stop Stop screen recording
|
|
42
|
+
session list List active sessions
|
|
24
43
|
|
|
25
44
|
Flags:
|
|
26
|
-
--platform ios|android
|
|
27
|
-
--device <name>
|
|
28
|
-
--udid <udid>
|
|
29
|
-
--serial <serial>
|
|
30
|
-
--out <path>
|
|
31
|
-
--session <name>
|
|
32
|
-
--verbose
|
|
33
|
-
--json
|
|
34
|
-
--no-record
|
|
35
|
-
--record-json
|
|
45
|
+
--platform ios|android Platform to target
|
|
46
|
+
--device <name> Device name to target
|
|
47
|
+
--udid <udid> iOS device UDID
|
|
48
|
+
--serial <serial> Android device serial
|
|
49
|
+
--out <path> Output path for screenshots
|
|
50
|
+
--session <name> Named session
|
|
51
|
+
--verbose Stream daemon/runner logs
|
|
52
|
+
--json JSON output
|
|
53
|
+
--no-record Do not record this action
|
|
54
|
+
--record-json Record JSON session log
|
|
55
|
+
--user-installed Apps: list user-installed packages (Android only)
|
|
56
|
+
--all Apps: list all packages (Android only)
|
|
36
57
|
|
|
37
|
-
`),process.exit(+!n.flags.help));let{command:s,positionals:a,flags:
|
|
38
|
-
`),f&&f();return}let e=await m({session:p,command:s,positionals:a,flags:
|
|
39
|
-
`:"";if(!Array.isArray(r)||0===r.length)return`${
|
|
40
|
-
`;if(t.raw){let e=r.map(e=>JSON.stringify(e));return`${
|
|
58
|
+
`),process.exit(+!n.flags.help));let{command:s,positionals:a,flags:u}=n,p=u.session??process.env.AGENT_DEVICE_SESSION??"default",f=u.verbose&&!u.json?function(){try{let t=e.join(i.homedir(),".agent-device","daemon.log"),r=0,n=!1,s=setInterval(()=>{if(n||!o.existsSync(t))return;let e=o.statSync(t);if(e.size<=r)return;let s=o.openSync(t,"r"),i=Buffer.alloc(e.size-r);o.readSync(s,i,0,i.length,r),o.closeSync(s),r=e.size,i.length>0&&process.stdout.write(i.toString("utf8"))},200);return()=>{n=!0,clearInterval(s)}}catch{return null}}():null;try{if("session"===s){let e=a[0]??"list";if("list"!==e)throw new l("INVALID_ARGS","session only supports list");let t=await m({session:p,command:"session_list",positionals:[],flags:{}});if(!t.ok)throw new l(t.error.code,t.error.message);u.json?c({success:!0,data:t.data??{}}):process.stdout.write(`${JSON.stringify(t.data??{},null,2)}
|
|
59
|
+
`),f&&f();return}let e=await m({session:p,command:s,positionals:a,flags:u});if(e.ok){if(u.json){c({success:!0,data:e.data??{}}),f&&f();return}if("snapshot"===s){process.stdout.write(function(e,t={}){let r=e.nodes??[],n=!!e.truncated,s="string"==typeof e.appName?e.appName:void 0,o="string"==typeof e.appBundleId?e.appBundleId:void 0,i=[];s&&i.push(`Page: ${s}`),o&&i.push(`App: ${o}`);let a=`Snapshot: ${r.length} nodes${n?" (truncated)":""}`,l=i.length>0?`${i.join("\n")}
|
|
60
|
+
`:"";if(!Array.isArray(r)||0===r.length)return`${l}${a}
|
|
61
|
+
`;if(t.raw){let e=r.map(e=>JSON.stringify(e));return`${l}${a}
|
|
41
62
|
${e.join("\n")}
|
|
42
|
-
`}let
|
|
43
|
-
${
|
|
44
|
-
`}(e.data??{},{raw:
|
|
63
|
+
`}let c=[],d=[];for(let e of r){let t=e.depth??0;for(;c.length>0&&t<=c[c.length-1];)c.pop();let r=e.label?.trim()||e.value?.trim()||e.identifier?.trim()||"",n=function(e){let t=e.replace(/XCUIElementType/gi,"").toLowerCase();switch(t.startsWith("ax")&&(t=t.replace(/^ax/,"")),t){case"application":return"application";case"navigationbar":return"navigation-bar";case"tabbar":return"tab-bar";case"button":return"button";case"link":return"link";case"cell":return"cell";case"statictext":case"statictext":return"text";case"textfield":case"textfield":return"text-field";case"textview":case"textarea":return"text-view";case"switch":return"switch";case"slider":return"slider";case"image":return"image";case"table":return"list";case"collectionview":return"collection";case"searchfield":return"search";case"segmentedcontrol":return"segmented-control";case"group":return"group";case"window":return"window";case"checkbox":return"checkbox";case"radio":return"radio";case"menuitem":return"menu-item";case"toolbar":return"toolbar";case"scrollarea":return"scroll-area";case"table":return"table";default:return t||"element"}}(e.type??"Element"),s="group"===n&&!r;s&&c.push(t);let o=s?t:Math.max(0,t-c.length),i=" ".repeat(o),a=e.ref?`@${e.ref}`:"",l=[!1===e.enabled?"disabled":null].filter(Boolean).join(", "),u=l?` [${l}]`:"",p=r?` "${r}"`:"";if(s){d.push(`${i}${a} [${n}]${u}`.trimEnd());continue}d.push(`${i}${a} [${n}]${p}${u}`.trimEnd())}return`${l}${a}
|
|
64
|
+
${d.join("\n")}
|
|
65
|
+
`}(e.data??{},{raw:u.snapshotRaw})),f&&f();return}if("get"===s){let t=a[0];if("text"===t){let t=e.data?.text??"";process.stdout.write(`${t}
|
|
45
66
|
`),f&&f();return}if("attrs"===t){let t=e.data?.node??{};process.stdout.write(`${JSON.stringify(t,null,2)}
|
|
46
67
|
`),f&&f();return}}if("click"===s){let t=e.data?.ref??"",r=e.data?.x,n=e.data?.y;t&&"number"==typeof r&&"number"==typeof n&&process.stdout.write(`Clicked @${t} (${r}, ${n})
|
|
47
|
-
`),f&&f();return}
|
|
68
|
+
`),f&&f();return}if(e.data&&"object"==typeof e.data){let t=e.data;if("devices"===s){let e=(Array.isArray(t.devices)?t.devices:[]).map(e=>{let t=e?.name??e?.id??"unknown",r=e?.platform??"unknown",n=e?.kind?` ${e.kind}`:"",s="boolean"==typeof e?.booted?` booted=${e.booted}`:"";return`${t} (${r}${n})${s}`});process.stdout.write(`${e.join("\n")}
|
|
69
|
+
`),f&&f();return}if("apps"===s){let e=Array.isArray(t.apps)?t.apps:[];process.stdout.write(`${e.join("\n")}
|
|
70
|
+
`),f&&f();return}}f&&f();return}throw new l(e.error.code,e.error.message,e.error.details)}catch(t){let e=r(t);if(u.json)c({success:!1,error:{code:e.code,message:e.message,details:e.details}});else if(d(e),u.verbose)try{let e=await import("node:fs"),t=await import("node:os"),r=(await import("node:path")).join(t.homedir(),".agent-device","daemon.log");if(e.existsSync(r)){let t=e.readFileSync(r,"utf8").split("\n"),n=t.slice(Math.max(0,t.length-200)).join("\n");n.trim().length>0&&process.stderr.write(`
|
|
48
71
|
[daemon log]
|
|
49
72
|
${n}
|
|
50
|
-
`)}}catch{}f&&f(),process.exit(1)}}n(process.argv[1]??"").href===import.meta.url
|
|
73
|
+
`)}}catch{}f&&f(),process.exit(1)}}n(process.argv[1]??"").href===import.meta.url&&S(process.argv.slice(2)).catch(e=>{d(r(e)),process.exit(1)}),S(process.argv.slice(2));
|
package/dist/src/daemon.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
let e,t;import n from"node:crypto";import{isCancel as i,select as r}from"@clack/prompts";import{node_path as a,runCmdStreaming as o,promises as s,asAppError as l,fileURLToPath as c,node_fs as u,node_os as d,node_net as f,errors_AppError as p,runCmd as h,whichCmd as m}from"./861.js";async function w(e,t){let n=e,a=e=>e.toLowerCase().replace(/_/g," ").replace(/\s+/g," ").trim();if(t.platform&&(n=n.filter(e=>e.platform===t.platform)),t.udid){let e=n.find(e=>e.id===t.udid&&"ios"===e.platform);if(!e)throw new p("DEVICE_NOT_FOUND",`No iOS device with UDID ${t.udid}`);return e}if(t.serial){let e=n.find(e=>e.id===t.serial&&"android"===e.platform);if(!e)throw new p("DEVICE_NOT_FOUND",`No Android device with serial ${t.serial}`);return e}if(t.deviceName){let e=a(t.deviceName),i=n.find(t=>a(t.name)===e);if(!i)throw new p("DEVICE_NOT_FOUND",`No device named ${t.deviceName}`);return i}if(1===n.length)return n[0];if(0===n.length)throw new p("DEVICE_NOT_FOUND","No devices found",{selector:t});let o=n.filter(e=>e.booted);if(1===o.length)return o[0];if(!process.env.CI&&process.stdin.isTTY&&process.stdout.isTTY){let e=await r({message:"Multiple devices available. Choose a device to continue:",options:(o.length>0?o:n).map(e=>({label:`${e.name} (${e.platform}${e.kind?`, ${e.kind}`:""}${e.booted?", booted":""})`,value:e.id}))});if(i(e))throw new p("INVALID_ARGS","Device selection cancelled");if(e){let t=n.find(t=>t.id===e);if(t)return t}}return o[0]??n[0]}async function g(){if(!await m("adb"))throw new p("TOOL_MISSING","adb not found in PATH");let e=(await h("adb",["devices","-l"])).stdout.split("\n").map(e=>e.trim()),t=[];for(let n of e){if(!n||n.startsWith("List of devices"))continue;let e=n.split(/\s+/),i=e[0];if("device"!==e[1])continue;let r=(e.find(e=>e.startsWith("model:"))??"").replace("model:","").replace(/_/g," ").trim()||i;if(i.startsWith("emulator-")){let e=await h("adb",["-s",i,"emu","avd","name"],{allowFailure:!0}),t=e.stdout.trim();0===e.exitCode&&t&&(r=t.replace(/_/g," "))}let a=await y(i);t.push({platform:"android",id:i,name:r,kind:i.startsWith("emulator-")?"emulator":"device",booted:a})}return t}async function y(e){try{let t=await h("adb",["-s",e,"shell","getprop","sys.boot_completed"],{allowFailure:!0});return"1"===t.stdout.trim()}catch{return!1}}let N={settings:{type:"intent",value:"android.settings.SETTINGS"}};function b(e,t){return["-s",e.id,...t]}async function v(e,t){let n=t.trim();if(n.includes("."))return{type:"package",value:n};let i=N[n.toLowerCase()];if(i)return i;let r=(await h("adb",b(e,["shell","pm","list","packages"]))).stdout.split("\n").map(e=>e.replace("package:","").trim()).filter(Boolean).filter(e=>e.toLowerCase().includes(n.toLowerCase()));if(1===r.length)return{type:"package",value:r[0]};if(r.length>1)throw new p("INVALID_ARGS",`Multiple packages matched "${t}"`,{matches:r});throw new p("APP_NOT_INSTALLED",`No package found matching "${t}"`)}async function I(e,t){let n=await v(e,t);"intent"===n.type?await h("adb",b(e,["shell","am","start","-a",n.value])):await h("adb",b(e,["shell","monkey","-p",n.value,"-c","android.intent.category.LAUNCHER","1"]))}async function S(e,t){if("settings"===t.trim().toLowerCase())return void await h("adb",b(e,["shell","am","force-stop","com.android.settings"]));let n=await v(e,t);if("intent"===n.type)throw new p("INVALID_ARGS","Close requires a package name, not an intent");await h("adb",b(e,["shell","am","force-stop",n.value]))}async function A(e,t,n){await h("adb",b(e,["shell","input","tap",String(t),String(n)]))}async function O(e,t,n,i=800){await h("adb",b(e,["shell","input","swipe",String(t),String(n),String(t),String(n),String(i)]))}async function D(e,t){let n=t.replace(/ /g,"%s");await h("adb",b(e,["shell","input","text",n]))}async function x(e,t,n){await A(e,t,n)}async function _(e,t,n,i){await x(e,t,n),await D(e,i)}async function E(e,t,n=.6){let{width:i,height:r}=await T(e),a=Math.floor(i*n),o=Math.floor(r*n),s=Math.floor(i/2),l=Math.floor(r/2),c=s,u=l,d=s,f=l;switch(t){case"up":u=l-Math.floor(o/2),f=l+Math.floor(o/2);break;case"down":u=l+Math.floor(o/2),f=l-Math.floor(o/2);break;case"left":c=s-Math.floor(a/2),d=s+Math.floor(a/2);break;case"right":c=s+Math.floor(a/2),d=s-Math.floor(a/2);break;default:throw new p("INVALID_ARGS",`Unknown direction: ${t}`)}await h("adb",b(e,["shell","input","swipe",String(c),String(u),String(d),String(f),"300"]))}async function k(e,t){for(let n=0;n<8;n+=1){let n="";try{n=await L(e)}catch(t){let e=t instanceof Error?t.message:String(t);throw new p("UNSUPPORTED_OPERATION",`uiautomator dump failed: ${e}`)}if(function(e,t){let n=t.toLowerCase(),i=/<node[^>]+>/g,r=i.exec(e);for(;r;){let t=r[0],a=/text="([^"]*)"/.exec(t),o=/content-desc="([^"]*)"/.exec(t),s=(a?.[1]??"").toLowerCase(),l=(o?.[1]??"").toLowerCase();if(s.includes(n)||l.includes(n)){let e=/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/.exec(t);if(e){let t=Number(e[1]),n=Number(e[2]);return{x:Math.floor((t+Number(e[3]))/2),y:Math.floor((n+Number(e[4]))/2)}}return{x:0,y:0}}r=i.exec(e)}return null}(n,t))return;await E(e,"down",.5)}throw new p("COMMAND_FAILED",`Could not find element containing "${t}" after scrolling`)}async function C(e,t){let n=await h("adb",b(e,["exec-out","screencap","-p"]),{binaryStdout:!0});if(!n.stdoutBuffer)throw new p("COMMAND_FAILED","Failed to capture screenshot");await s.writeFile(t,n.stdoutBuffer)}async function R(e,t={}){return function(e,t,n){let i=function(e){let t={type:null,label:null,value:null,identifier:null,depth:-1,children:[]},n=[t],i=/<node\b[^>]*>|<\/node>/g,r=i.exec(e);for(;r;){let t=r[0];if(t.startsWith("</node")){n.length>1&&n.pop(),r=i.exec(e);continue}let a=function(e){let t=t=>{let n=RegExp(`${t}="([^"]*)"`).exec(e);return n?n[1]:null},n=e=>{let n=t(e);if(null!==n)return"true"===n};return{text:t("text"),desc:t("content-desc"),resourceId:t("resource-id"),className:t("class"),bounds:t("bounds"),clickable:n("clickable"),enabled:n("enabled"),focusable:n("focusable")}}(t),o=function(e){if(!e)return;let t=/\[(\d+),(\d+)\]\[(\d+),(\d+)\]/.exec(e);if(!t)return;let n=Number(t[1]),i=Number(t[2]);return{x:n,y:i,width:Math.max(0,Number(t[3])-n),height:Math.max(0,Number(t[4])-i)}}(a.bounds),s=n[n.length-1],l={type:a.className,label:a.text||a.desc,value:a.text,identifier:a.resourceId,rect:o,enabled:a.enabled,hittable:a.clickable??a.focusable,depth:s.depth+1,parentIndex:void 0,children:[]};s.children.push(l),t.endsWith("/>")||n.push(l),r=i.exec(e)}return t}(e),r=[],a=!1,o=n.depth??1/0,s=n.scope?function(e,t){let n=t.toLowerCase(),i=[...e.children];for(;i.length>0;){let e=i.shift(),t=e.label?.toLowerCase()??"",r=e.value?.toLowerCase()??"",a=e.identifier?.toLowerCase()??"";if(t.includes(n)||r.includes(n)||a.includes(n))return e;i.push(...e.children)}return null}(i,n.scope):null,l=s?[s]:i.children,c=(e,t)=>{if(r.length>=800){a=!0;return}if(!(t>o)){for(let i of((n.raw||function(e,t){if(t.interactiveOnly)return!!e.hittable;if(t.compact){let t=!!(e.label&&e.label.trim().length>0),n=!!(e.identifier&&e.identifier.trim().length>0);return t||n||!!e.hittable}return!0}(e,n))&&r.push({index:r.length,type:e.type??void 0,label:e.label??void 0,value:e.value??void 0,identifier:e.identifier??void 0,rect:e.rect,enabled:e.enabled,hittable:e.hittable,depth:t,parentIndex:e.parentIndex}),e.children))if(c(i,t+1),a)return}};for(let e of l)if(c(e,0),a)break;return a?{nodes:r,truncated:a}:{nodes:r}}(await L(e),800,t)}async function P(){if(!await m("adb"))throw new p("TOOL_MISSING","adb not found in PATH")}async function T(e){let t=(await h("adb",b(e,["shell","wm","size"]))).stdout.match(/Physical size:\s*(\d+)x(\d+)/);if(!t)throw new p("COMMAND_FAILED","Unable to read screen size");return{width:Number(t[1]),height:Number(t[2])}}async function L(e){return await h("adb",b(e,["shell","uiautomator","dump","/sdcard/window_dump.xml"])),(await h("adb",b(e,["shell","cat","/sdcard/window_dump.xml"]))).stdout}async function M(){if("darwin"!==process.platform)throw new p("UNSUPPORTED_PLATFORM","iOS tools are only available on macOS");if(!await m("xcrun"))throw new p("TOOL_MISSING","xcrun not found in PATH");let e=[],t=await h("xcrun",["simctl","list","devices","-j"]);try{let n=JSON.parse(t.stdout);for(let t of Object.values(n.devices))for(let n of t)n.isAvailable&&e.push({platform:"ios",id:n.udid,name:n.name,kind:"simulator",booted:"Booted"===n.state})}catch(e){throw new p("COMMAND_FAILED","Failed to parse simctl devices JSON",void 0,e)}if(await m("xcrun"))try{let t=await h("xcrun",["devicectl","list","devices","--json"]);for(let n of JSON.parse(t.stdout).devices??[])n.platform?.toLowerCase().includes("ios")&&e.push({platform:"ios",id:n.identifier,name:n.name,kind:"device",booted:!0})}catch{}return e}let F={settings:"com.apple.Preferences"};async function j(e,t){let n=t.trim();if(n.includes("."))return n;let i=F[n.toLowerCase()];if(i)return i;if("simulator"===e.kind){let i=(await Y(e)).filter(e=>e.name.toLowerCase()===n.toLowerCase());if(1===i.length)return i[0].bundleId;if(i.length>1)throw new p("INVALID_ARGS",`Multiple apps matched "${t}"`,{matches:i})}throw new p("APP_NOT_INSTALLED",`No app found matching "${t}"`)}async function U(e,t){let n=await j(e,t);if("simulator"===e.kind){await Z(e),await h("xcrun",["simctl","launch",e.id,n]);return}await h("xcrun",["devicectl","device","process","launch","--device",e.id,n])}async function V(e,t){let n=await j(e,t);if("simulator"===e.kind){await Z(e);let t=await h("xcrun",["simctl","terminate",e.id,n],{allowFailure:!0});if(0!==t.exitCode){if(t.stderr.toLowerCase().includes("found nothing to terminate"))return;throw new p("COMMAND_FAILED",`xcrun exited with code ${t.exitCode}`,{cmd:"xcrun",args:["simctl","terminate",e.id,n],stdout:t.stdout,stderr:t.stderr,exitCode:t.exitCode})}return}await h("xcrun",["devicectl","device","process","terminate","--device",e.id,n])}async function $(e,t,n){throw H(e,"press"),await Z(e),new p("UNSUPPORTED_OPERATION","simctl io tap is not available; use the XCTest runner for input")}async function G(e,t,n,i=800){throw H(e,"long-press"),await Z(e),new p("UNSUPPORTED_OPERATION","long-press is not supported on iOS simulators without XCTest runner support")}async function B(e,t,n){await $(e,t,n)}async function J(e,t){throw H(e,"type"),await Z(e),new p("UNSUPPORTED_OPERATION","simctl io keyboard is not available; use the XCTest runner for input")}async function q(e,t,n,i){await B(e,t,n),await J(e,i)}async function W(e,t,n=.6){throw H(e,"scroll"),await Z(e),new p("UNSUPPORTED_OPERATION","simctl io swipe is not available; use the XCTest runner for input")}async function X(e){throw new p("UNSUPPORTED_OPERATION",`scrollintoview is not supported on iOS without UI automation (${e})`)}async function z(e,t){if("simulator"===e.kind){await Z(e),await h("xcrun",["simctl","io",e.id,"screenshot",t]);return}await h("xcrun",["devicectl","device","screenshot","--device",e.id,t])}function H(e,t){if("simulator"!==e.kind)throw new p("UNSUPPORTED_OPERATION",`${t} is only supported on iOS simulators in v1`)}async function Y(e){let t=(await h("xcrun",["simctl","listapps",e.id],{allowFailure:!0})).stdout;if(!t.trim().startsWith("{"))return[];try{let e=JSON.parse(t);return Object.entries(e).map(([e,t])=>({bundleId:e,name:t.CFBundleDisplayName??t.CFBundleName??e}))}catch{return[]}}async function Z(e){"simulator"!==e.kind||"Booted"!==await K(e.id)&&(await h("xcrun",["simctl","boot",e.id],{allowFailure:!0}),await h("xcrun",["simctl","bootstatus",e.id,"-b"],{allowFailure:!0}))}async function K(e){let t=await h("xcrun",["simctl","list","devices","-j"],{allowFailure:!0});if(0!==t.exitCode)return null;try{let n=JSON.parse(t.stdout);for(let t of Object.values(n.devices??{})){let n=t.find(t=>t.udid===e);if(n)return n.state}}catch{}return null}let Q=new Map;async function ee(e,t,n={}){if("simulator"!==e.kind)throw new p("UNSUPPORTED_OPERATION","iOS runner only supports simulators in v1");try{let i=await ei(e,n),r=await es(e,i.port,t,n.logPath),a=await r.text(),o={};try{o=JSON.parse(a)}catch{throw new p("COMMAND_FAILED","Invalid runner response",{text:a})}if(!o.ok)throw new p("COMMAND_FAILED",o.error?.message??"Runner error",{runner:o,xcodebuild:{exitCode:1,stdout:"",stderr:""},logPath:n.logPath});return o.data??{}}catch(r){let i=r instanceof p?r:new p("COMMAND_FAILED",String(r));if("COMMAND_FAILED"===i.code&&"string"==typeof i.message&&i.message.includes("Runner did not accept connection")){await et(e.id);let i=await ei(e,n),r=await es(e,i.port,t,n.logPath),a=await r.text(),o={};try{o=JSON.parse(a)}catch{throw new p("COMMAND_FAILED","Invalid runner response",{text:a})}if(!o.ok)throw new p("COMMAND_FAILED",o.error?.message??"Runner error",{runner:o,xcodebuild:{exitCode:1,stdout:"",stderr:""},logPath:n.logPath});return o.data??{}}throw r}}async function et(e){let t=Q.get(e);if(t){try{await es(t.device,t.port,{command:"shutdown"})}catch{}try{await t.testPromise}catch{}ef(t.xctestrunPath),ef(t.jsonPath),Q.delete(e)}}async function en(e){await h("xcrun",["simctl","bootstatus",e,"-b"],{allowFailure:!0})}async function ei(e,t){let n=Q.get(e.id);if(n)return n;await en(e.id);let i=await er(e.id,t),r=await ec(),a=process.env.AGENT_DEVICE_RUNNER_TIMEOUT??"300",{xctestrunPath:s,jsonPath:l}=await ed(i,{AGENT_DEVICE_RUNNER_PORT:String(r),AGENT_DEVICE_RUNNER_TIMEOUT:a},`session-${e.id}-${r}`),c=o("xcodebuild",["test-without-building","-only-testing","AgentDeviceRunnerUITests/RunnerTests/testCommand","-parallel-testing-enabled","NO","-maximum-concurrent-test-simulator-destinations","1","-xctestrun",s,"-destination",`platform=iOS Simulator,id=${e.id}`],{onStdoutChunk:e=>{eo(e,t.logPath,t.verbose)},onStderrChunk:e=>{eo(e,t.logPath,t.verbose)},allowFailure:!0,env:{...process.env,AGENT_DEVICE_RUNNER_PORT:String(r),AGENT_DEVICE_RUNNER_TIMEOUT:a}}),u={device:e,deviceId:e.id,port:r,xctestrunPath:s,jsonPath:l,testPromise:c};return Q.set(e.id,u),u}async function er(e,t){let n,i=a.join(d.homedir(),".agent-device","ios-runner"),r=a.join(i,"derived");if((n=process.env.AGENT_DEVICE_IOS_CLEAN_DERIVED)&&["1","true","yes","on"].includes(n.toLowerCase()))try{u.rmSync(r,{recursive:!0,force:!0})}catch{}let s=ea(r);if(s)return s;let l=function(){let e=a.dirname(c(import.meta.url)),t=e;for(let e=0;e<6;e+=1){let e=a.join(t,"package.json");if(u.existsSync(e))return t;t=a.dirname(t)}return e}(),f=a.join(l,"ios-runner","AgentDeviceRunner","AgentDeviceRunner.xcodeproj");if(!u.existsSync(f))throw new p("COMMAND_FAILED","iOS runner project not found",{projectPath:f});try{await o("xcodebuild",["build-for-testing","-project",f,"-scheme","AgentDeviceRunner","-parallel-testing-enabled","NO","-maximum-concurrent-test-simulator-destinations","1","-destination",`platform=iOS Simulator,id=${e}`,"-derivedDataPath",r],{onStdoutChunk:e=>{eo(e,t.logPath,t.verbose)},onStderrChunk:e=>{eo(e,t.logPath,t.verbose)}})}catch(n){let e=n instanceof p?n:new p("COMMAND_FAILED",String(n));throw new p("COMMAND_FAILED","xcodebuild build-for-testing failed",{error:e.message,details:e.details,logPath:t.logPath})}let h=ea(r);if(!h)throw new p("COMMAND_FAILED","Failed to locate .xctestrun after build");return h}function ea(e){if(!u.existsSync(e))return null;let t=[],n=[e];for(;n.length>0;){let e=n.pop();for(let i of u.readdirSync(e,{withFileTypes:!0})){let r=a.join(e,i.name);if(i.isDirectory()){n.push(r);continue}if(i.isFile()&&i.name.endsWith(".xctestrun"))try{let e=u.statSync(r);t.push({path:r,mtimeMs:e.mtimeMs})}catch{}}}return 0===t.length?null:(t.sort((e,t)=>t.mtimeMs-e.mtimeMs),t[0]?.path??null)}function eo(e,t,n){t&&u.appendFileSync(t,e),n&&process.stderr.write(e)}async function es(e,t,n,i){i&&await eu(i,4e3);let r=Date.now(),a=null;for(;Date.now()-r<8e3;)try{return await fetch(`http://127.0.0.1:${t}/command`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})}catch(e){a=e,await new Promise(e=>setTimeout(e,100))}if("simulator"===e.kind){let i=await el(e.id,t,n);return new Response(i.body,{status:i.status})}let o=i?function(e){try{if(!u.existsSync(e))return null;let t=u.readFileSync(e,"utf8").match(/AGENT_DEVICE_RUNNER_PORT=(\d+)/);if(t)return Number(t[1])}catch{}return null}(i):null;if(o&&o!==t)try{return await fetch(`http://127.0.0.1:${o}/command`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})}catch(e){a=e}throw new p("COMMAND_FAILED","Runner did not accept connection",{port:t,fallbackPort:o,logPath:i,lastError:a?String(a):void 0})}async function el(e,t,n){let i=JSON.stringify(n),r=await h("xcrun",["simctl","spawn",e,"/usr/bin/curl","-s","-X","POST","-H","Content-Type: application/json","--data",i,`http://127.0.0.1:${t}/command`],{allowFailure:!0}),a=r.stdout;if(0!==r.exitCode)throw new p("COMMAND_FAILED","Runner did not accept connection (simctl spawn)",{port:t,stdout:r.stdout,stderr:r.stderr,exitCode:r.exitCode});return{status:200,body:a}}async function ec(){return await new Promise((e,t)=>{let n=f.createServer();n.listen(0,"127.0.0.1",()=>{let i=n.address();n.close(),"object"==typeof i&&i?.port?e(i.port):t(new p("COMMAND_FAILED","Failed to allocate port"))}),n.on("error",t)})}async function eu(e,t){if(!u.existsSync(e))return;let n=Date.now(),i=0;for(;Date.now()-n<t;){if(!u.existsSync(e))return;let t=u.statSync(e);if(t.size>i){let n=u.openSync(e,"r"),r=Buffer.alloc(t.size-i);u.readSync(n,r,0,r.length,i),u.closeSync(n),i=t.size;let a=r.toString("utf8");if(a.includes("AGENT_DEVICE_RUNNER_LISTENER_READY")||a.includes("AGENT_DEVICE_RUNNER_PORT="))return}await new Promise(e=>setTimeout(e,100))}}async function ed(e,t,n){let i,r=a.dirname(e),o=n.replace(/[^a-zA-Z0-9._-]/g,"_"),s=a.join(r,`AgentDeviceRunner.env.${o}.json`),l=a.join(r,`AgentDeviceRunner.env.${o}.xctestrun`),c=await h("plutil",["-convert","json","-o","-",e],{allowFailure:!0});if(0!==c.exitCode||!c.stdout.trim())throw new p("COMMAND_FAILED","Failed to read xctestrun plist",{xctestrunPath:e,stderr:c.stderr});try{i=JSON.parse(c.stdout)}catch(t){throw new p("COMMAND_FAILED","Failed to parse xctestrun JSON",{xctestrunPath:e,error:String(t)})}let d=e=>{e.EnvironmentVariables={...e.EnvironmentVariables??{},...t},e.UITestEnvironmentVariables={...e.UITestEnvironmentVariables??{},...t},e.UITargetAppEnvironmentVariables={...e.UITargetAppEnvironmentVariables??{},...t},e.TestingEnvironmentVariables={...e.TestingEnvironmentVariables??{},...t}},f=i.TestConfigurations;if(Array.isArray(f))for(let e of f){if(!e||"object"!=typeof e)continue;let t=e.TestTargets;if(Array.isArray(t))for(let e of t)e&&"object"==typeof e&&d(e)}for(let[e,t]of Object.entries(i))t&&"object"==typeof t&&t.TestBundlePath&&(d(t),i[e]=t);u.writeFileSync(s,JSON.stringify(i,null,2));let m=await h("plutil",["-convert","xml1","-o",l,s],{allowFailure:!0});if(0!==m.exitCode)throw new p("COMMAND_FAILED","Failed to write xctestrun plist",{tmpXctestrunPath:l,stderr:m.stderr});return{xctestrunPath:l,jsonPath:s}}function ef(e){try{u.existsSync(e)&&u.unlinkSync(e)}catch{}}async function ep(e){let t,n;if("ios"!==e.platform||"simulator"!==e.kind)throw new p("UNSUPPORTED_OPERATION","AX snapshot is only supported on iOS simulators");let i=await eh(),r=await h(i,[],{allowFailure:!0});if(0!==r.exitCode){let e=(r.stderr??"").toString(),t="";throw e.toLowerCase().includes("accessibility permission")&&(t=" Enable Accessibility for your terminal in System Settings > Privacy & Security > Accessibility, or use --backend xctest (slower snapshots via XCTest)."),new p("COMMAND_FAILED","AX snapshot failed",{stderr:`${e}${t}`,stdout:r.stdout})}try{let e=JSON.parse(r.stdout);if(e&&"object"==typeof e&&"root"in e){if(!e.root)throw Error("AX snapshot missing root");t=e.root,n=e.windowFrame??void 0}else t=e}catch(e){throw new p("COMMAND_FAILED","Invalid AX snapshot JSON",{error:String(e)})}let a=t.frame??n,o=[],s=[],l=(e,t)=>{e.frame&&o.push(e.frame);let n=e.frame&&a?{x:e.frame.x-a.x,y:e.frame.y-a.y,width:e.frame.width,height:e.frame.height}:e.frame;for(let i of(s.push({...e,frame:n,children:void 0,depth:t}),e.children??[]))l(i,t+1)};return l(t,0),{nodes:(function(e,t,n){if(!t||0===n.length)return e;let i=1/0,r=1/0;for(let e of n)e.x<i&&(i=e.x),e.y<r&&(r=e.y);return i<=5&&r<=5?e.map(e=>({...e,frame:e.frame?{x:e.frame.x+t.x,y:e.frame.y+t.y,width:e.frame.width,height:e.frame.height}:void 0})):e})(s,a,o).map((e,t)=>({index:t,type:e.subrole??e.role,label:e.label,value:e.value,identifier:e.identifier,rect:e.frame?{x:e.frame.x,y:e.frame.y,width:e.frame.width,height:e.frame.height}:void 0,depth:e.depth})),rootRect:a}}async function eh(){let e=function(){let e=process.cwd();for(let t=0;t<6;t+=1){let t=a.join(e,"package.json");if(u.existsSync(t))return e;e=a.dirname(e)}return process.cwd()}(),t=a.join(e,"ios-runner","AXSnapshot"),n=process.env.AGENT_DEVICE_AX_BINARY;if(n&&u.existsSync(n))return n;for(let t of[a.join(e,"bin","axsnapshot"),a.join(e,"dist","bin","axsnapshot"),a.join(e,"dist","axsnapshot")])if(u.existsSync(t))return t;let i=a.join(t,".build","release","axsnapshot");if(u.existsSync(i))return i;let r=await h("swift",["build","-c","release"],{cwd:t,allowFailure:!0});if(0!==r.exitCode||!u.existsSync(i))throw new p("COMMAND_FAILED","Failed to build AX snapshot tool",{stderr:r.stderr,stdout:r.stdout});return i}async function em(e){let t={platform:e.platform,deviceName:e.device,udid:e.udid,serial:e.serial};if("android"===t.platform){await P();let e=await g();return await w(e,t)}if("ios"===t.platform){let e=await M();return await w(e,t)}let n=[];try{n.push(...await g())}catch{}try{n.push(...await M())}catch{}return await w(n,t)}async function ew(e,t,n,i,r){let a=function(e){switch(e.platform){case"android":return{open:t=>I(e,t),close:t=>S(e,t),tap:(t,n)=>A(e,t,n),longPress:(t,n,i)=>O(e,t,n,i),focus:(t,n)=>x(e,t,n),type:t=>D(e,t),fill:(t,n,i)=>_(e,t,n,i),scroll:(t,n)=>E(e,t,n),scrollIntoView:t=>k(e,t),screenshot:t=>C(e,t)};case"ios":return{open:t=>U(e,t),close:t=>V(e,t),tap:(t,n)=>$(e,t,n),longPress:(t,n,i)=>G(e,t,n,i),focus:(t,n)=>B(e,t,n),type:t=>J(e,t),fill:(t,n,i)=>q(e,t,n,i),scroll:(t,n)=>W(e,t,n),scrollIntoView:e=>X(e),screenshot:t=>z(e,t)};default:throw new p("UNSUPPORTED_PLATFORM",`Unsupported platform: ${e.platform}`)}}(e);switch(t){case"open":{let e=n[0];if(!e)throw new p("INVALID_ARGS","open requires an app name or bundle/package id");return await a.open(e),{app:e}}case"close":{let e=n[0];if(!e)return{closed:"session"};return await a.close(e),{app:e}}case"press":{let[t,i]=n.map(Number);if(Number.isNaN(t)||Number.isNaN(i))throw new p("INVALID_ARGS","press requires x y");return"ios"===e.platform&&"simulator"===e.kind?await ee(e,{command:"tap",x:t,y:i,appBundleId:r?.appBundleId},{verbose:r?.verbose,logPath:r?.logPath}):await a.tap(t,i),{x:t,y:i}}case"long-press":{let e=Number(n[0]),t=Number(n[1]),i=n[2]?Number(n[2]):void 0;if(Number.isNaN(e)||Number.isNaN(t))throw new p("INVALID_ARGS","long-press requires x y [durationMs]");return await a.longPress(e,t,i),{x:e,y:t,durationMs:i}}case"focus":{let[t,i]=n.map(Number);if(Number.isNaN(t)||Number.isNaN(i))throw new p("INVALID_ARGS","focus requires x y");return"ios"===e.platform&&"simulator"===e.kind?await ee(e,{command:"tap",x:t,y:i,appBundleId:r?.appBundleId},{verbose:r?.verbose,logPath:r?.logPath}):await a.focus(t,i),{x:t,y:i}}case"type":{let t=n.join(" ");if(!t)throw new p("INVALID_ARGS","type requires text");return"ios"===e.platform&&"simulator"===e.kind?await ee(e,{command:"type",text:t,appBundleId:r?.appBundleId},{verbose:r?.verbose,logPath:r?.logPath}):await a.type(t),{text:t}}case"fill":{let t=Number(n[0]),i=Number(n[1]),o=n.slice(2).join(" ");if(Number.isNaN(t)||Number.isNaN(i)||!o)throw new p("INVALID_ARGS","fill requires x y text");return"ios"===e.platform&&"simulator"===e.kind?(await ee(e,{command:"tap",x:t,y:i,appBundleId:r?.appBundleId},{verbose:r?.verbose,logPath:r?.logPath}),await ee(e,{command:"type",text:o,appBundleId:r?.appBundleId},{verbose:r?.verbose,logPath:r?.logPath})):await a.fill(t,i,o),{x:t,y:i,text:o}}case"scroll":{let t=n[0],i=n[1]?Number(n[1]):void 0;if(!t)throw new p("INVALID_ARGS","scroll requires direction");if("ios"===e.platform&&"simulator"===e.kind){if(!["up","down","left","right"].includes(t))throw new p("INVALID_ARGS",`Unknown direction: ${t}`);let n=function(e){switch(e){case"up":return"down";case"down":return"up";case"left":return"right";case"right":return"left"}}(t);await ee(e,{command:"swipe",direction:n,appBundleId:r?.appBundleId},{verbose:r?.verbose,logPath:r?.logPath})}else await a.scroll(t,i);return{direction:t,amount:i}}case"scrollintoview":{let e=n.join(" ");if(!e)throw new p("INVALID_ARGS","scrollintoview requires text");return await a.scrollIntoView(e),{text:e}}case"screenshot":{let e=i??`./screenshot-${Date.now()}.png`;return await a.screenshot(e),{path:e}}case"snapshot":{let t=r?.snapshotBackend??"ax";if("ios"===e.platform){if("simulator"!==e.kind)throw new p("UNSUPPORTED_OPERATION","snapshot is only supported on iOS simulators in v1");if("ax"===t){let t=await ep(e);return{nodes:t.nodes??[],truncated:!1,backend:"ax",rootRect:t.rootRect}}let n=await ee(e,{command:"snapshot",appBundleId:r?.appBundleId,interactiveOnly:r?.snapshotInteractiveOnly,compact:r?.snapshotCompact,depth:r?.snapshotDepth,scope:r?.snapshotScope,raw:r?.snapshotRaw},{verbose:r?.verbose,logPath:r?.logPath});return{nodes:n.nodes??[],truncated:n.truncated??!1,backend:"xctest"}}let n=await R(e,{interactiveOnly:r?.snapshotInteractiveOnly,compact:r?.snapshotCompact,depth:r?.snapshotDepth,scope:r?.snapshotScope,raw:r?.snapshotRaw});return{nodes:n.nodes??[],truncated:n.truncated??!1,backend:"android"}}default:throw new p("INVALID_ARGS",`Unknown command: ${t}`)}}function eg(e){let t=e.trim();return t.startsWith("@")?t.slice(1)||null:t.startsWith("e")?t:null}function ey(e,t){return e.find(e=>e.ref===t)??null}function eN(e){return{x:Math.round(e.x+e.width/2),y:Math.round(e.y+e.height/2)}}let eb=new Map,ev=a.join(d.homedir(),".agent-device"),eI=a.join(ev,"daemon.json"),eS=a.join(ev,"daemon.log"),eA=a.join(ev,"sessions"),eO=function(){try{let e=function(){let e=a.dirname(c(import.meta.url)),t=e;for(let e=0;e<6;e+=1){let e=a.join(t,"package.json");if(u.existsSync(e))return t;t=a.dirname(t)}return e}();return JSON.parse(u.readFileSync(a.join(e,"package.json"),"utf8")).version??"0.0.0"}catch{return"0.0.0"}}(),eD=n.randomBytes(24).toString("hex");function ex(e,t){return{appBundleId:t,verbose:e?.verbose,logPath:eS,snapshotInteractiveOnly:e?.snapshotInteractiveOnly,snapshotCompact:e?.snapshotCompact,snapshotDepth:e?.snapshotDepth,snapshotScope:e?.snapshotScope,snapshotRaw:e?.snapshotRaw,snapshotBackend:e?.snapshotBackend}}async function e_(e){if(e.token!==eD)return{ok:!1,error:{code:"UNAUTHORIZED",message:"Invalid token"}};let t=e.command,n=e.session||"default";if("session_list"===t)return{ok:!0,data:{sessions:Array.from(eb.values()).map(e=>({name:e.name,platform:e.device.platform,device:e.device.name,id:e.device.id,createdAt:e.createdAt}))}};if("open"===t){let i,r=await em(e.flags??{}),a=e.positionals?.[0];if("ios"===r.platform)try{let{resolveIosApp:t}=await Promise.resolve().then(()=>({resolveIosApp:j}));i=await t(r,e.positionals?.[0]??"")}catch{i=void 0}await ew(r,"open",e.positionals??[],e.flags?.out,{...ex(e.flags,i)});let o={name:n,device:r,createdAt:Date.now(),appBundleId:i,appName:a,actions:[]};return eE(o,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{session:n}}),eb.set(n,o),{ok:!0,data:{session:n}}}if("replay"===t){let t=e.positionals?.[0];if(!t)return{ok:!1,error:{code:"INVALID_ARGS",message:"replay requires a path"}};try{var i;let e=(i=t).startsWith("~/")?a.join(d.homedir(),i.slice(2)):a.resolve(i),r=JSON.parse(u.readFileSync(e,"utf8")),o=r.optimizedActions??r.actions??[];for(let e of o)e&&"replay"!==e.command&&await e_({token:eD,session:n,command:e.command,positionals:e.positionals??[],flags:e.flags??{}});return{ok:!0,data:{replayed:o.length,session:n}}}catch(t){let e=l(t);return{ok:!1,error:{code:e.code,message:e.message}}}}if("close"===t){let i=eb.get(n);return i?(e.positionals&&e.positionals.length>0&&await ew(i.device,"close",e.positionals??[],e.flags?.out,{...ex(e.flags,i.appBundleId)}),"ios"===i.device.platform&&"simulator"===i.device.kind&&await et(i.device.id),eE(i,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{session:n}}),ek(i),eb.delete(n),{ok:!0,data:{session:n}}):{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session"}}}if("snapshot"===t){let i=eb.get(n),r=i?.device??await em(e.flags??{}),a=i?.appBundleId,o=await ew(r,"snapshot",[],e.flags?.out,{...ex(e.flags,a)}),s=(function(e){let t=[],n=[];for(let i of e){let e=i.depth??0;for(;t.length>0&&e<=t[t.length-1];)t.pop();let r=function(e){let t=e.replace(/XCUIElementType/gi,"").toLowerCase();return t.startsWith("ax")&&(t=t.replace(/^ax/,"")),t}(i.type??"");if("group"===r||"ioscontentgroup"===r){t.push(e);continue}let a=Math.max(0,e-t.length);n.push({...i,depth:a})}return n})(o?.nodes??[]).map((e,t)=>({...e,ref:`e${t+1}`})),l={nodes:s,truncated:o?.truncated,createdAt:Date.now(),backend:o?.backend},c={name:n,device:r,createdAt:i?.createdAt??Date.now(),appBundleId:a,snapshot:l,actions:i?.actions??[]};return eE(c,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{nodes:s.length,truncated:o?.truncated??!1}}),eb.set(n,c),{ok:!0,data:{nodes:s,truncated:o?.truncated??!1,appName:i?.appName??a??r.name,appBundleId:a}}}if("click"===t){let i=eb.get(n);if(!i?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"No snapshot in session. Run snapshot first."}};let r=e.positionals?.[0]??"",a=eg(r);if(!a)return{ok:!1,error:{code:"INVALID_ARGS",message:"click requires a ref like @e2"}};let o=ey(i.snapshot.nodes,a);if(!o?.rect&&e.positionals.length>1){let t=e.positionals.slice(1).join(" ").trim();t.length>0&&(o=eR(i.snapshot.nodes,t))}if(!o?.rect)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${r} not found or has no bounds`}};let s=eP(o,i.snapshot.nodes),l=o.label?.trim();if("ios"===i.device.platform&&"simulator"===i.device.kind&&l&&function(e,t){let n=t.trim().toLowerCase();if(!n)return!1;let i=0;for(let t of e)if((t.label??"").trim().toLowerCase()===n&&(i+=1)>1)return!1;return 1===i}(i.snapshot.nodes,l))return await ee(i.device,{command:"tap",text:l,appBundleId:i.appBundleId},{verbose:e.flags?.verbose,logPath:eS}),eE(i,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{ref:a,refLabel:l,mode:"text"}}),{ok:!0,data:{ref:a,mode:"text"}};let{x:c,y:u}=eN(o.rect);return await ew(i.device,"press",[String(c),String(u)],e.flags?.out,{...ex(e.flags,i.appBundleId)}),eE(i,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{ref:a,x:c,y:u,refLabel:s}}),{ok:!0,data:{ref:a,x:c,y:u}}}if("fill"===t){let i=eb.get(n);if(e.positionals?.[0]?.startsWith("@")){if(!i?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"No snapshot in session. Run snapshot first."}};let n=eg(e.positionals[0]);if(!n)return{ok:!1,error:{code:"INVALID_ARGS",message:"fill requires a ref like @e2"}};let r=e.positionals.length>=3?e.positionals[1]:"",a=e.positionals.length>=3?e.positionals.slice(2).join(" "):e.positionals.slice(1).join(" ");if(!a)return{ok:!1,error:{code:"INVALID_ARGS",message:"fill requires text after ref"}};let o=ey(i.snapshot.nodes,n);if(!o?.rect&&r&&(o=eR(i.snapshot.nodes,r)),!o?.rect)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${e.positionals[0]} not found or has no bounds`}};let s=eP(o,i.snapshot.nodes),{x:l,y:c}=eN(o.rect),u=await ew(i.device,"fill",[String(l),String(c),a],e.flags?.out,{...ex(e.flags,i.appBundleId)});return eE(i,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:u??{ref:n,x:l,y:c,refLabel:s}}),{ok:!0,data:u??{ref:n,x:l,y:c}}}}if("get"===t){let i=e.positionals?.[0],r=e.positionals?.[1];if("text"!==i&&"attrs"!==i)return{ok:!1,error:{code:"INVALID_ARGS",message:"get only supports text or attrs"}};let a=eb.get(n);if(!a?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"No snapshot in session. Run snapshot first."}};let o=eg(r??"");if(!o)return{ok:!1,error:{code:"INVALID_ARGS",message:"get text requires a ref like @e2"}};let s=ey(a.snapshot.nodes,o);if(!s&&e.positionals.length>2){let t=e.positionals.slice(2).join(" ").trim();t.length>0&&(s=eR(a.snapshot.nodes,t))}if(!s)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${r} not found`}};if("attrs"===i)return eE(a,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{ref:o}}),{ok:!0,data:{ref:o,node:s}};let l=[s.label,s.value,s.identifier].map(e=>"string"==typeof e?e.trim():"").filter(e=>e.length>0)[0]??"";return eE(a,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{ref:o,text:l,refLabel:l||void 0}}),{ok:!0,data:{ref:o,text:l,node:s}}}if("rect"===t){let i=eb.get(n);if(!i?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"No snapshot in session. Run snapshot first."}};let r=eg(e.positionals?.[0]??""),a="";if(r){let e=ey(i.snapshot.nodes,r);a=e?.label?.trim()??""}else a=e.positionals.join(" ").trim();if(!a)return{ok:!1,error:{code:"INVALID_ARGS",message:"rect requires a label or ref with label"}};if("ios"!==i.device.platform||"simulator"!==i.device.kind)return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"rect is only supported on iOS simulators"}};let o=await ee(i.device,{command:"rect",text:a,appBundleId:i.appBundleId},{verbose:e.flags?.verbose,logPath:eS});return eE(i,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{label:a,rect:o?.rect}}),{ok:!0,data:{label:a,rect:o?.rect}}}let r=eb.get(n);if(!r)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session. Run open first."}};let o=await ew(r.device,t,e.positionals??[],e.flags?.out,{...ex(e.flags,r.appBundleId)});return eE(r,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:o??{}}),{ok:!0,data:o??{}}}function eE(e,t){t.flags?.noRecord||e.actions.push({ts:Date.now(),command:t.command,positionals:t.positionals,flags:function(e){if(!e)return{};let{platform:t,device:n,udid:i,serial:r,out:a,verbose:o,snapshotInteractiveOnly:s,snapshotCompact:l,snapshotDepth:c,snapshotScope:u,snapshotRaw:d,snapshotBackend:f,noRecord:p,recordJson:h}=e;return{platform:t,device:n,udid:i,serial:r,out:a,verbose:o,snapshotInteractiveOnly:s,snapshotCompact:l,snapshotDepth:c,snapshotScope:u,snapshotRaw:d,snapshotBackend:f,noRecord:p,recordJson:h}}(t.flags),result:t.result})}function ek(e){try{u.existsSync(eA)||u.mkdirSync(eA,{recursive:!0});let t=e.name.replace(/[^a-zA-Z0-9._-]/g,"_"),n=new Date(e.createdAt).toISOString().replace(/[:.]/g,"-"),i=a.join(eA,`${t}-${n}.ad`),r=a.join(eA,`${t}-${n}.json`),o={name:e.name,device:e.device,createdAt:e.createdAt,appBundleId:e.appBundleId,actions:e.actions,optimizedActions:function(e){let t=[];for(let n of e.actions)if("snapshot"!==n.command){if("click"===n.command||"fill"===n.command||"get"===n.command){let i=n.result?.refLabel;"string"==typeof i&&i.trim().length>0&&t.push({ts:n.ts,command:"snapshot",positionals:[],flags:{platform:e.device.platform,snapshotInteractiveOnly:!0,snapshotCompact:!0,snapshotScope:i.trim()},result:{scope:i.trim()}})}t.push(n)}return t}(e)},s=function(e,t){let n=[],i=e.device.name.replace(/"/g,'\\"'),r=e.device.kind?` kind=${e.device.kind}`:"";for(let a of(n.push(`context platform=${e.device.platform} device="${i}"${r} theme=unknown`),t))a.flags?.noRecord||n.push(function(e){let t=[e.command];if("click"===e.command){let n=e.positionals?.[0];if(n){t.push(eC(n));let i=e.result?.refLabel;return"string"==typeof i&&i.trim().length>0&&t.push(eC(i)),t.join(" ")}}if("fill"===e.command){let n=e.positionals?.[0];if(n&&n.startsWith("@")){t.push(eC(n));let i=e.result?.refLabel,r=e.positionals.slice(1).join(" ");return"string"==typeof i&&i.trim().length>0&&t.push(eC(i)),r&&t.push(eC(r)),t.join(" ")}}if("get"===e.command){let n=e.positionals?.[0],i=e.positionals?.[1];if(n&&i){t.push(eC(n)),t.push(eC(i));let r=e.result?.refLabel;return"string"==typeof r&&r.trim().length>0&&t.push(eC(r)),t.join(" ")}}if("snapshot"===e.command)return e.flags?.snapshotInteractiveOnly&&t.push("-i"),e.flags?.snapshotCompact&&t.push("-c"),"number"==typeof e.flags?.snapshotDepth&&t.push("-d",String(e.flags.snapshotDepth)),e.flags?.snapshotScope&&t.push("-s",eC(e.flags.snapshotScope)),e.flags?.snapshotRaw&&t.push("--raw"),e.flags?.snapshotBackend&&t.push("--backend",e.flags.snapshotBackend),t.join(" ");for(let n of e.positionals??[])t.push(eC(n));return t.join(" ")}(a));return`${n.join("\n")}
|
|
2
|
-
`}(e,
|
|
3
|
-
`),
|
|
4
|
-
`)}}),t=async()=>{for(let e of Array.from(
|
|
1
|
+
let e,t;import i from"node:crypto";import{isCancel as a,select as n}from"@clack/prompts";import{node_path as o,runCmdStreaming as r,promises as s,asAppError as l,fileURLToPath as c,runCmdBackground as d,node_fs as u,node_os as p,errors_AppError as f,runCmd as m,node_net as h,whichCmd as w}from"./861.js";async function g(e,t){let i=e,o=e=>e.toLowerCase().replace(/_/g," ").replace(/\s+/g," ").trim();if(t.platform&&(i=i.filter(e=>e.platform===t.platform)),t.udid){let e=i.find(e=>e.id===t.udid&&"ios"===e.platform);if(!e)throw new f("DEVICE_NOT_FOUND",`No iOS device with UDID ${t.udid}`);return e}if(t.serial){let e=i.find(e=>e.id===t.serial&&"android"===e.platform);if(!e)throw new f("DEVICE_NOT_FOUND",`No Android device with serial ${t.serial}`);return e}if(t.deviceName){let e=o(t.deviceName),a=i.find(t=>o(t.name)===e);if(!a)throw new f("DEVICE_NOT_FOUND",`No device named ${t.deviceName}`);return a}if(1===i.length)return i[0];if(0===i.length)throw new f("DEVICE_NOT_FOUND","No devices found",{selector:t});let r=i.filter(e=>e.booted);if(1===r.length)return r[0];if(!process.env.CI&&process.stdin.isTTY&&process.stdout.isTTY){let e=await n({message:"Multiple devices available. Choose a device to continue:",options:(r.length>0?r:i).map(e=>({label:`${e.name} (${e.platform}${e.kind?`, ${e.kind}`:""}${e.booted?", booted":""})`,value:e.id}))});if(a(e))throw new f("INVALID_ARGS","Device selection cancelled");if(e){let t=i.find(t=>t.id===e);if(t)return t}}return r[0]??i[0]}async function y(){if(!await w("adb"))throw new f("TOOL_MISSING","adb not found in PATH");let e=(await m("adb",["devices","-l"])).stdout.split("\n").map(e=>e.trim()),t=[];for(let i of e){if(!i||i.startsWith("List of devices"))continue;let e=i.split(/\s+/),a=e[0];if("device"!==e[1])continue;let n=(e.find(e=>e.startsWith("model:"))??"").replace("model:","").replace(/_/g," ").trim()||a;if(a.startsWith("emulator-")){let e=await m("adb",["-s",a,"emu","avd","name"],{allowFailure:!0}),t=e.stdout.trim();0===e.exitCode&&t&&(n=t.replace(/_/g," "))}let o=await v(a);t.push({platform:"android",id:a,name:n,kind:a.startsWith("emulator-")?"emulator":"device",booted:o})}return t}async function v(e){try{let t=await m("adb",["-s",e,"shell","getprop","sys.boot_completed"],{allowFailure:!0});return"1"===t.stdout.trim()}catch{return!1}}async function N(e,t=6e4){let i=Date.now();for(;Date.now()-i<t;){if(await v(e))return;await new Promise(e=>setTimeout(e,1e3))}throw new f("COMMAND_FAILED","Android device did not finish booting in time",{serial:e,timeoutMs:t})}let I={settings:{type:"intent",value:"android.settings.SETTINGS"}};function b(e,t){return["-s",e.id,...t]}async function A(e,t){let i=t.trim();if(i.includes("."))return{type:"package",value:i};let a=I[i.toLowerCase()];if(a)return a;let n=(await m("adb",b(e,["shell","pm","list","packages"]))).stdout.split("\n").map(e=>e.replace("package:","").trim()).filter(Boolean).filter(e=>e.toLowerCase().includes(i.toLowerCase()));if(1===n.length)return{type:"package",value:n[0]};if(n.length>1)throw new f("INVALID_ARGS",`Multiple packages matched "${t}"`,{matches:n});throw new f("APP_NOT_INSTALLED",`No package found matching "${t}"`)}async function S(e,t="launchable"){if("launchable"===t){let t=await m("adb",b(e,["shell","cmd","package","query-activities","--brief","-a","android.intent.action.MAIN","-c","android.intent.category.LAUNCHER"]),{allowFailure:!0});if(0===t.exitCode&&t.stdout.trim().length>0){let e=new Set;for(let i of t.stdout.split("\n")){let t=i.trim();if(!t)continue;let a=t.split(/\s+/)[0],n=a.includes("/")?a.split("/")[0]:a;n&&e.add(n)}if(e.size>0)return Array.from(e)}}return(await m("adb",b(e,"user-installed"===t?["shell","pm","list","packages","-3"]:["shell","pm","list","packages"]))).stdout.split("\n").map(e=>e.replace("package:","").trim()).filter(Boolean)}async function D(e,t){e.booted||await N(e.id);let i=await A(e,t);"intent"===i.type?await m("adb",b(e,["shell","am","start","-a",i.value])):await m("adb",b(e,["shell","monkey","-p",i.value,"-c","android.intent.category.LAUNCHER","1"]))}async function O(e){e.booted||await N(e.id)}async function k(e,t){if("settings"===t.trim().toLowerCase())return void await m("adb",b(e,["shell","am","force-stop","com.android.settings"]));let i=await A(e,t);if("intent"===i.type)throw new f("INVALID_ARGS","Close requires a package name, not an intent");await m("adb",b(e,["shell","am","force-stop",i.value]))}async function x(e,t,i){await m("adb",b(e,["shell","input","tap",String(t),String(i)]))}async function _(e){await m("adb",b(e,["shell","input","keyevent","4"]))}async function E(e){await m("adb",b(e,["shell","input","keyevent","3"]))}async function P(e){await m("adb",b(e,["shell","input","keyevent","187"]))}async function R(e,t,i,a=800){await m("adb",b(e,["shell","input","swipe",String(t),String(i),String(t),String(i),String(a)]))}async function C(e,t){let i=t.replace(/ /g,"%s");await m("adb",b(e,["shell","input","text",i]))}async function T(e,t,i){await x(e,t,i)}async function L(e,t,i,a){await T(e,t,i),await C(e,a)}async function M(e,t,i=.6){let{width:a,height:n}=await V(e),o=Math.floor(a*i),r=Math.floor(n*i),s=Math.floor(a/2),l=Math.floor(n/2),c=s,d=l,u=s,p=l;switch(t){case"up":d=l-Math.floor(r/2),p=l+Math.floor(r/2);break;case"down":d=l+Math.floor(r/2),p=l-Math.floor(r/2);break;case"left":c=s-Math.floor(o/2),u=s+Math.floor(o/2);break;case"right":c=s+Math.floor(o/2),u=s-Math.floor(o/2);break;default:throw new f("INVALID_ARGS",`Unknown direction: ${t}`)}await m("adb",b(e,["shell","input","swipe",String(c),String(d),String(u),String(p),"300"]))}async function F(e,t){for(let i=0;i<8;i+=1){let i="";try{i=await B(e)}catch(t){let e=t instanceof Error?t.message:String(t);throw new f("UNSUPPORTED_OPERATION",`uiautomator dump failed: ${e}`)}if(function(e,t){let i=t.toLowerCase(),a=/<node[^>]+>/g,n=a.exec(e);for(;n;){let t=n[0],o=/text="([^"]*)"/.exec(t),r=/content-desc="([^"]*)"/.exec(t),s=(o?.[1]??"").toLowerCase(),l=(r?.[1]??"").toLowerCase();if(s.includes(i)||l.includes(i)){let e=/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/.exec(t);if(e){let t=Number(e[1]),i=Number(e[2]);return{x:Math.floor((t+Number(e[3]))/2),y:Math.floor((i+Number(e[4]))/2)}}return{x:0,y:0}}n=a.exec(e)}return null}(i,t))return;await M(e,"down",.5)}throw new f("COMMAND_FAILED",`Could not find element containing "${t}" after scrolling`)}async function U(e,t){let i=await m("adb",b(e,["exec-out","screencap","-p"]),{binaryStdout:!0});if(!i.stdoutBuffer)throw new f("COMMAND_FAILED","Failed to capture screenshot");await s.writeFile(t,i.stdoutBuffer)}async function j(e,t={}){return function(e,t,i){let a=function(e){let t={type:null,label:null,value:null,identifier:null,depth:-1,children:[]},i=[t],a=/<node\b[^>]*>|<\/node>/g,n=a.exec(e);for(;n;){let t=n[0];if(t.startsWith("</node")){i.length>1&&i.pop(),n=a.exec(e);continue}let o=function(e){let t=t=>{let i=RegExp(`${t}="([^"]*)"`).exec(e);return i?i[1]:null},i=e=>{let i=t(e);if(null!==i)return"true"===i};return{text:t("text"),desc:t("content-desc"),resourceId:t("resource-id"),className:t("class"),bounds:t("bounds"),clickable:i("clickable"),enabled:i("enabled"),focusable:i("focusable")}}(t),r=function(e){if(!e)return;let t=/\[(\d+),(\d+)\]\[(\d+),(\d+)\]/.exec(e);if(!t)return;let i=Number(t[1]),a=Number(t[2]);return{x:i,y:a,width:Math.max(0,Number(t[3])-i),height:Math.max(0,Number(t[4])-a)}}(o.bounds),s=i[i.length-1],l={type:o.className,label:o.text||o.desc,value:o.text,identifier:o.resourceId,rect:r,enabled:o.enabled,hittable:o.clickable??o.focusable,depth:s.depth+1,parentIndex:void 0,children:[]};s.children.push(l),t.endsWith("/>")||i.push(l),n=a.exec(e)}return t}(e),n=[],o=!1,r=i.depth??1/0,s=i.scope?function(e,t){let i=t.toLowerCase(),a=[...e.children];for(;a.length>0;){let e=a.shift(),t=e.label?.toLowerCase()??"",n=e.value?.toLowerCase()??"",o=e.identifier?.toLowerCase()??"";if(t.includes(i)||n.includes(i)||o.includes(i))return e;a.push(...e.children)}return null}(a,i.scope):null,l=s?[s]:a.children,c=(e,t)=>{if(n.length>=800){o=!0;return}if(!(t>r)){for(let a of((i.raw||function(e,t){if(t.interactiveOnly)return!!e.hittable;if(t.compact){let t=!!(e.label&&e.label.trim().length>0),i=!!(e.identifier&&e.identifier.trim().length>0);return t||i||!!e.hittable}return!0}(e,i))&&n.push({index:n.length,type:e.type??void 0,label:e.label??void 0,value:e.value??void 0,identifier:e.identifier??void 0,rect:e.rect,enabled:e.enabled,hittable:e.hittable,depth:t,parentIndex:e.parentIndex}),e.children))if(c(a,t+1),o)return}};for(let e of l)if(c(e,0),o)break;return o?{nodes:n,truncated:o}:{nodes:n}}(await B(e),800,t)}async function $(){if(!await w("adb"))throw new f("TOOL_MISSING","adb not found in PATH")}async function V(e){let t=(await m("adb",b(e,["shell","wm","size"]))).stdout.match(/Physical size:\s*(\d+)x(\d+)/);if(!t)throw new f("COMMAND_FAILED","Unable to read screen size");return{width:Number(t[1]),height:Number(t[2])}}async function B(e){return await m("adb",b(e,["shell","uiautomator","dump","/sdcard/window_dump.xml"])),(await m("adb",b(e,["shell","cat","/sdcard/window_dump.xml"]))).stdout}async function G(){if("darwin"!==process.platform)throw new f("UNSUPPORTED_PLATFORM","iOS tools are only available on macOS");if(!await w("xcrun"))throw new f("TOOL_MISSING","xcrun not found in PATH");let e=[],t=await m("xcrun",["simctl","list","devices","-j"]);try{let i=JSON.parse(t.stdout);for(let t of Object.values(i.devices))for(let i of t)i.isAvailable&&e.push({platform:"ios",id:i.udid,name:i.name,kind:"simulator",booted:"Booted"===i.state})}catch(e){throw new f("COMMAND_FAILED","Failed to parse simctl devices JSON",void 0,e)}if(await w("xcrun"))try{let t=await m("xcrun",["devicectl","list","devices","--json"]);for(let i of JSON.parse(t.stdout).devices??[])i.platform?.toLowerCase().includes("ios")&&e.push({platform:"ios",id:i.identifier,name:i.name,kind:"device",booted:!0})}catch{}return e}let J={settings:"com.apple.Preferences"};async function q(e,t){let i=t.trim();if(i.includes("."))return i;let a=J[i.toLowerCase()];if(a)return a;if("simulator"===e.kind){let a=(await en(e)).filter(e=>e.name.toLowerCase()===i.toLowerCase());if(1===a.length)return a[0].bundleId;if(a.length>1)throw new f("INVALID_ARGS",`Multiple apps matched "${t}"`,{matches:a})}throw new f("APP_NOT_INSTALLED",`No app found matching "${t}"`)}async function W(e,t){let i=await q(e,t);if("simulator"===e.kind){await eo(e),await m("open",["-a","Simulator"],{allowFailure:!0}),await m("xcrun",["simctl","launch",e.id,i]);return}await m("xcrun",["devicectl","device","process","launch","--device",e.id,i])}async function X(e){"simulator"!==e.kind||"Booted"!==await er(e.id)&&(await eo(e),await m("open",["-a","Simulator"],{allowFailure:!0}))}async function z(e,t){let i=await q(e,t);if("simulator"===e.kind){await eo(e);let t=await m("xcrun",["simctl","terminate",e.id,i],{allowFailure:!0});if(0!==t.exitCode){if(t.stderr.toLowerCase().includes("found nothing to terminate"))return;throw new f("COMMAND_FAILED",`xcrun exited with code ${t.exitCode}`,{cmd:"xcrun",args:["simctl","terminate",e.id,i],stdout:t.stdout,stderr:t.stderr,exitCode:t.exitCode})}return}await m("xcrun",["devicectl","device","process","terminate","--device",e.id,i])}async function H(e,t,i){throw ea(e,"press"),new f("UNSUPPORTED_OPERATION","simctl io tap is not available; use the XCTest runner for input")}async function Y(e,t,i,a=800){throw ea(e,"long-press"),new f("UNSUPPORTED_OPERATION","long-press is not supported on iOS simulators without XCTest runner support")}async function Z(e,t,i){await H(e,t,i)}async function K(e,t){throw ea(e,"type"),new f("UNSUPPORTED_OPERATION","simctl io keyboard is not available; use the XCTest runner for input")}async function Q(e,t,i,a){await Z(e,t,i),await K(e,a)}async function ee(e,t,i=.6){throw ea(e,"scroll"),new f("UNSUPPORTED_OPERATION","simctl io swipe is not available; use the XCTest runner for input")}async function et(e){throw new f("UNSUPPORTED_OPERATION",`scrollintoview is not supported on iOS without UI automation (${e})`)}async function ei(e,t){if("simulator"===e.kind){await eo(e),await m("xcrun",["simctl","io",e.id,"screenshot",t]);return}await m("xcrun",["devicectl","device","screenshot","--device",e.id,t])}function ea(e,t){if("simulator"!==e.kind)throw new f("UNSUPPORTED_OPERATION",`${t} is only supported on iOS simulators in v1`)}async function en(e){let t=(await m("xcrun",["simctl","listapps",e.id],{allowFailure:!0})).stdout.trim();if(!t)return[];let i=null;if(t.startsWith("{"))try{i=JSON.parse(t)}catch{i=null}if(!i&&t.startsWith("{"))try{let e=await m("plutil",["-convert","json","-o","-","-"],{allowFailure:!0,stdin:t});0===e.exitCode&&e.stdout.trim().startsWith("{")&&(i=JSON.parse(e.stdout))}catch{i=null}return i?Object.entries(i).map(([e,t])=>({bundleId:e,name:t.CFBundleDisplayName??t.CFBundleName??e})):[]}async function eo(e){"simulator"!==e.kind||"Booted"!==await er(e.id)&&(await m("xcrun",["simctl","boot",e.id],{allowFailure:!0}),await m("xcrun",["simctl","bootstatus",e.id,"-b"],{allowFailure:!0}))}async function er(e){let t=await m("xcrun",["simctl","list","devices","-j"],{allowFailure:!0});if(0!==t.exitCode)return null;try{let i=JSON.parse(t.stdout);for(let t of Object.values(i.devices??{})){let i=t.find(t=>t.udid===e);if(i)return i.state}}catch{}return null}let es=new Map;async function el(e,t,i={}){if("simulator"!==e.kind)throw new f("UNSUPPORTED_OPERATION","iOS runner only supports simulators in v1");try{let a=await eu(e,i),n=await eh(e,a.port,t,i.logPath),o=await n.text(),r={};try{r=JSON.parse(o)}catch{throw new f("COMMAND_FAILED","Invalid runner response",{text:o})}if(!r.ok)throw new f("COMMAND_FAILED",r.error?.message??"Runner error",{runner:r,xcodebuild:{exitCode:1,stdout:"",stderr:""},logPath:i.logPath});return r.data??{}}catch(n){let a=n instanceof f?n:new f("COMMAND_FAILED",String(n));if("COMMAND_FAILED"===a.code&&"string"==typeof a.message&&a.message.includes("Runner did not accept connection")){await ec(e.id);let a=await eu(e,i),n=await eh(e,a.port,t,i.logPath),o=await n.text(),r={};try{r=JSON.parse(o)}catch{throw new f("COMMAND_FAILED","Invalid runner response",{text:o})}if(!r.ok)throw new f("COMMAND_FAILED",r.error?.message??"Runner error",{runner:r,xcodebuild:{exitCode:1,stdout:"",stderr:""},logPath:i.logPath});return r.data??{}}throw n}}async function ec(e){let t=es.get(e);if(t){try{await eh(t.device,t.port,{command:"shutdown"})}catch{}try{await t.testPromise}catch{}eN(t.xctestrunPath),eN(t.jsonPath),es.delete(e)}}async function ed(e){await m("xcrun",["simctl","bootstatus",e,"-b"],{allowFailure:!0})}async function eu(e,t){let i=es.get(e.id);if(i)return i;await ed(e.id);let a=await ep(e.id,t),n=await eg(),o=process.env.AGENT_DEVICE_RUNNER_TIMEOUT??"300",{xctestrunPath:s,jsonPath:l}=await ev(a,{AGENT_DEVICE_RUNNER_PORT:String(n),AGENT_DEVICE_RUNNER_TIMEOUT:o},`session-${e.id}-${n}`),c=r("xcodebuild",["test-without-building","-only-testing","AgentDeviceRunnerUITests/RunnerTests/testCommand","-parallel-testing-enabled","NO","-maximum-concurrent-test-simulator-destinations","1","-xctestrun",s,"-destination",`platform=iOS Simulator,id=${e.id}`],{onStdoutChunk:e=>{em(e,t.logPath,t.verbose)},onStderrChunk:e=>{em(e,t.logPath,t.verbose)},allowFailure:!0,env:{...process.env,AGENT_DEVICE_RUNNER_PORT:String(n),AGENT_DEVICE_RUNNER_TIMEOUT:o}}),d={device:e,deviceId:e.id,port:n,xctestrunPath:s,jsonPath:l,testPromise:c};return es.set(e.id,d),d}async function ep(e,t){let i,a=o.join(p.homedir(),".agent-device","ios-runner"),n=o.join(a,"derived");if((i=process.env.AGENT_DEVICE_IOS_CLEAN_DERIVED)&&["1","true","yes","on"].includes(i.toLowerCase()))try{u.rmSync(n,{recursive:!0,force:!0})}catch{}let s=ef(n);if(s)return s;let l=function(){let e=o.dirname(c(import.meta.url)),t=e;for(let e=0;e<6;e+=1){let e=o.join(t,"package.json");if(u.existsSync(e))return t;t=o.dirname(t)}return e}(),d=o.join(l,"ios-runner","AgentDeviceRunner","AgentDeviceRunner.xcodeproj");if(!u.existsSync(d))throw new f("COMMAND_FAILED","iOS runner project not found",{projectPath:d});try{await r("xcodebuild",["build-for-testing","-project",d,"-scheme","AgentDeviceRunner","-parallel-testing-enabled","NO","-maximum-concurrent-test-simulator-destinations","1","-destination",`platform=iOS Simulator,id=${e}`,"-derivedDataPath",n],{onStdoutChunk:e=>{em(e,t.logPath,t.verbose)},onStderrChunk:e=>{em(e,t.logPath,t.verbose)}})}catch(i){let e=i instanceof f?i:new f("COMMAND_FAILED",String(i));throw new f("COMMAND_FAILED","xcodebuild build-for-testing failed",{error:e.message,details:e.details,logPath:t.logPath})}let m=ef(n);if(!m)throw new f("COMMAND_FAILED","Failed to locate .xctestrun after build");return m}function ef(e){if(!u.existsSync(e))return null;let t=[],i=[e];for(;i.length>0;){let e=i.pop();for(let a of u.readdirSync(e,{withFileTypes:!0})){let n=o.join(e,a.name);if(a.isDirectory()){i.push(n);continue}if(a.isFile()&&a.name.endsWith(".xctestrun"))try{let e=u.statSync(n);t.push({path:n,mtimeMs:e.mtimeMs})}catch{}}}return 0===t.length?null:(t.sort((e,t)=>t.mtimeMs-e.mtimeMs),t[0]?.path??null)}function em(e,t,i){t&&u.appendFileSync(t,e),i&&process.stderr.write(e)}async function eh(e,t,i,a){a&&await ey(a,4e3);let n=Date.now(),o=null;for(;Date.now()-n<8e3;)try{return await fetch(`http://127.0.0.1:${t}/command`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)})}catch(e){o=e,await new Promise(e=>setTimeout(e,100))}if("simulator"===e.kind){let a=await ew(e.id,t,i);return new Response(a.body,{status:a.status})}let r=a?function(e){try{if(!u.existsSync(e))return null;let t=u.readFileSync(e,"utf8").match(/AGENT_DEVICE_RUNNER_PORT=(\d+)/);if(t)return Number(t[1])}catch{}return null}(a):null;if(r&&r!==t)try{return await fetch(`http://127.0.0.1:${r}/command`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)})}catch(e){o=e}throw new f("COMMAND_FAILED","Runner did not accept connection",{port:t,fallbackPort:r,logPath:a,lastError:o?String(o):void 0})}async function ew(e,t,i){let a=JSON.stringify(i),n=await m("xcrun",["simctl","spawn",e,"/usr/bin/curl","-s","-X","POST","-H","Content-Type: application/json","--data",a,`http://127.0.0.1:${t}/command`],{allowFailure:!0}),o=n.stdout;if(0!==n.exitCode)throw new f("COMMAND_FAILED","Runner did not accept connection (simctl spawn)",{port:t,stdout:n.stdout,stderr:n.stderr,exitCode:n.exitCode});return{status:200,body:o}}async function eg(){return await new Promise((e,t)=>{let i=h.createServer();i.listen(0,"127.0.0.1",()=>{let a=i.address();i.close(),"object"==typeof a&&a?.port?e(a.port):t(new f("COMMAND_FAILED","Failed to allocate port"))}),i.on("error",t)})}async function ey(e,t){if(!u.existsSync(e))return;let i=Date.now(),a=0;for(;Date.now()-i<t;){if(!u.existsSync(e))return;let t=u.statSync(e);if(t.size>a){let i=u.openSync(e,"r"),n=Buffer.alloc(t.size-a);u.readSync(i,n,0,n.length,a),u.closeSync(i),a=t.size;let o=n.toString("utf8");if(o.includes("AGENT_DEVICE_RUNNER_LISTENER_READY")||o.includes("AGENT_DEVICE_RUNNER_PORT="))return}await new Promise(e=>setTimeout(e,100))}}async function ev(e,t,i){let a,n=o.dirname(e),r=i.replace(/[^a-zA-Z0-9._-]/g,"_"),s=o.join(n,`AgentDeviceRunner.env.${r}.json`),l=o.join(n,`AgentDeviceRunner.env.${r}.xctestrun`),c=await m("plutil",["-convert","json","-o","-",e],{allowFailure:!0});if(0!==c.exitCode||!c.stdout.trim())throw new f("COMMAND_FAILED","Failed to read xctestrun plist",{xctestrunPath:e,stderr:c.stderr});try{a=JSON.parse(c.stdout)}catch(t){throw new f("COMMAND_FAILED","Failed to parse xctestrun JSON",{xctestrunPath:e,error:String(t)})}let d=e=>{e.EnvironmentVariables={...e.EnvironmentVariables??{},...t},e.UITestEnvironmentVariables={...e.UITestEnvironmentVariables??{},...t},e.UITargetAppEnvironmentVariables={...e.UITargetAppEnvironmentVariables??{},...t},e.TestingEnvironmentVariables={...e.TestingEnvironmentVariables??{},...t}},p=a.TestConfigurations;if(Array.isArray(p))for(let e of p){if(!e||"object"!=typeof e)continue;let t=e.TestTargets;if(Array.isArray(t))for(let e of t)e&&"object"==typeof e&&d(e)}for(let[e,t]of Object.entries(a))t&&"object"==typeof t&&t.TestBundlePath&&(d(t),a[e]=t);u.writeFileSync(s,JSON.stringify(a,null,2));let h=await m("plutil",["-convert","xml1","-o",l,s],{allowFailure:!0});if(0!==h.exitCode)throw new f("COMMAND_FAILED","Failed to write xctestrun plist",{tmpXctestrunPath:l,stderr:h.stderr});return{xctestrunPath:l,jsonPath:s}}function eN(e){try{u.existsSync(e)&&u.unlinkSync(e)}catch{}}async function eI(e){let t,i;if("ios"!==e.platform||"simulator"!==e.kind)throw new f("UNSUPPORTED_OPERATION","AX snapshot is only supported on iOS simulators");let a=await eb(),n=await m(a,[],{allowFailure:!0});if(0!==n.exitCode){let e=(n.stderr??"").toString(),t="";throw e.toLowerCase().includes("accessibility permission")&&(t=" Enable Accessibility for your terminal in System Settings > Privacy & Security > Accessibility, or use --backend xctest (slower snapshots via XCTest)."),e.toLowerCase().includes("could not find ios app content")&&(t=" AX snapshot sometimes caches empty content. Try restarting the Simulator app."),new f("COMMAND_FAILED","AX snapshot failed",{stderr:`${e}${t}`,stdout:n.stdout})}try{let e=JSON.parse(n.stdout);if(e&&"object"==typeof e&&"root"in e){if(!e.root)throw Error("AX snapshot missing root");t=e.root,i=e.windowFrame??void 0}else t=e}catch(e){throw new f("COMMAND_FAILED","Invalid AX snapshot JSON",{error:String(e)})}let o=t.frame??i,r=[],s=[],l=(e,t)=>{e.frame&&r.push(e.frame);let i=e.frame&&o?{x:e.frame.x-o.x,y:e.frame.y-o.y,width:e.frame.width,height:e.frame.height}:e.frame;for(let a of(s.push({...e,frame:i,children:void 0,depth:t}),e.children??[]))l(a,t+1)};return l(t,0),{nodes:(function(e,t,i){if(!t||0===i.length)return e;let a=1/0,n=1/0;for(let e of i)e.x<a&&(a=e.x),e.y<n&&(n=e.y);return a<=5&&n<=5?e.map(e=>({...e,frame:e.frame?{x:e.frame.x+t.x,y:e.frame.y+t.y,width:e.frame.width,height:e.frame.height}:void 0})):e})(s,o,r).map((e,t)=>({index:t,type:e.subrole??e.role,label:e.label,value:e.value,identifier:e.identifier,rect:e.frame?{x:e.frame.x,y:e.frame.y,width:e.frame.width,height:e.frame.height}:void 0,depth:e.depth}))}}async function eb(){let e=function(){let e=process.cwd();for(let t=0;t<6;t+=1){let t=o.join(e,"package.json");if(u.existsSync(t))return e;e=o.dirname(e)}return process.cwd()}(),t=o.join(e,"ios-runner","AXSnapshot"),i=process.env.AGENT_DEVICE_AX_BINARY;if(i&&u.existsSync(i))return i;let a=o.join(e,"dist","bin","axsnapshot");if(u.existsSync(a))return a;let n=o.join(t,".build","release","axsnapshot");if(u.existsSync(n))return n;let r=await m("swift",["build","-c","release"],{cwd:t,allowFailure:!0});if(0!==r.exitCode||!u.existsSync(n))throw new f("COMMAND_FAILED","Failed to build AX snapshot tool",{stderr:r.stderr,stdout:r.stdout});return n}async function eA(e){let t={platform:e.platform,deviceName:e.device,udid:e.udid,serial:e.serial};if("android"===t.platform){await $();let e=await y();return await g(e,t)}if("ios"===t.platform){let e=await G();return await g(e,t)}let i=[];try{i.push(...await y())}catch{}try{i.push(...await G())}catch{}return await g(i,t)}async function eS(e,t,i,a,n){let o=function(e){switch(e.platform){case"android":return{open:t=>D(e,t),openDevice:()=>O(e),close:t=>k(e,t),tap:(t,i)=>x(e,t,i),longPress:(t,i,a)=>R(e,t,i,a),focus:(t,i)=>T(e,t,i),type:t=>C(e,t),fill:(t,i,a)=>L(e,t,i,a),scroll:(t,i)=>M(e,t,i),scrollIntoView:t=>F(e,t),screenshot:t=>U(e,t)};case"ios":return{open:t=>W(e,t),openDevice:()=>X(e),close:t=>z(e,t),tap:(t,i)=>H(e,t,i),longPress:(t,i,a)=>Y(e,t,i,a),focus:(t,i)=>Z(e,t,i),type:t=>K(e,t),fill:(t,i,a)=>Q(e,t,i,a),scroll:(t,i)=>ee(e,t,i),scrollIntoView:e=>et(e),screenshot:t=>ei(e,t)};default:throw new f("UNSUPPORTED_PLATFORM",`Unsupported platform: ${e.platform}`)}}(e);switch(t){case"open":{let e=i[0];if(!e)return await o.openDevice(),{app:null};return await o.open(e),{app:e}}case"close":{let e=i[0];if(!e)return{closed:"session"};return await o.close(e),{app:e}}case"press":{let[t,a]=i.map(Number);if(Number.isNaN(t)||Number.isNaN(a))throw new f("INVALID_ARGS","press requires x y");return"ios"===e.platform&&"simulator"===e.kind?await el(e,{command:"tap",x:t,y:a,appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath}):await o.tap(t,a),{x:t,y:a}}case"long-press":{let e=Number(i[0]),t=Number(i[1]),a=i[2]?Number(i[2]):void 0;if(Number.isNaN(e)||Number.isNaN(t))throw new f("INVALID_ARGS","long-press requires x y [durationMs]");return await o.longPress(e,t,a),{x:e,y:t,durationMs:a}}case"focus":{let[t,a]=i.map(Number);if(Number.isNaN(t)||Number.isNaN(a))throw new f("INVALID_ARGS","focus requires x y");return"ios"===e.platform&&"simulator"===e.kind?await el(e,{command:"tap",x:t,y:a,appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath}):await o.focus(t,a),{x:t,y:a}}case"type":{let t=i.join(" ");if(!t)throw new f("INVALID_ARGS","type requires text");return"ios"===e.platform&&"simulator"===e.kind?await el(e,{command:"type",text:t,appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath}):await o.type(t),{text:t}}case"fill":{let t=Number(i[0]),a=Number(i[1]),r=i.slice(2).join(" ");if(Number.isNaN(t)||Number.isNaN(a)||!r)throw new f("INVALID_ARGS","fill requires x y text");return"ios"===e.platform&&"simulator"===e.kind?(await el(e,{command:"tap",x:t,y:a,appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath}),await el(e,{command:"type",text:r,appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath})):await o.fill(t,a,r),{x:t,y:a,text:r}}case"scroll":{let t=i[0],a=i[1]?Number(i[1]):void 0;if(!t)throw new f("INVALID_ARGS","scroll requires direction");if("ios"===e.platform&&"simulator"===e.kind){if(!["up","down","left","right"].includes(t))throw new f("INVALID_ARGS",`Unknown direction: ${t}`);let i=function(e){switch(e){case"up":return"down";case"down":return"up";case"left":return"right";case"right":return"left"}}(t);await el(e,{command:"swipe",direction:i,appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath})}else await o.scroll(t,a);return{direction:t,amount:a}}case"scrollintoview":{let t=i.join(" ").trim();if(!t)throw new f("INVALID_ARGS","scrollintoview requires text");if("ios"===e.platform&&"simulator"===e.kind){for(let i=0;i<8;i+=1){let a=await el(e,{command:"findText",text:t,appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath});if(a?.found)return{text:t,attempts:i+1};await el(e,{command:"swipe",direction:"up",appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath}),await new Promise(e=>setTimeout(e,300))}throw new f("COMMAND_FAILED",`scrollintoview could not find text: ${t}`)}return await o.scrollIntoView(t),{text:t}}case"screenshot":{let e=a??`./screenshot-${Date.now()}.png`;return await o.screenshot(e),{path:e}}case"back":if("ios"===e.platform){if("simulator"!==e.kind)throw new f("UNSUPPORTED_OPERATION","back is only supported on iOS simulators in v1");return await el(e,{command:"back",appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath}),{action:"back"}}return await _(e),{action:"back"};case"home":if("ios"===e.platform){if("simulator"!==e.kind)throw new f("UNSUPPORTED_OPERATION","home is only supported on iOS simulators in v1");return await el(e,{command:"home",appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath}),{action:"home"}}return await E(e),{action:"home"};case"app-switcher":if("ios"===e.platform){if("simulator"!==e.kind)throw new f("UNSUPPORTED_OPERATION","app-switcher is only supported on iOS simulators in v1");return await el(e,{command:"appSwitcher",appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath}),{action:"app-switcher"}}return await P(e),{action:"app-switcher"};case"snapshot":{let t=n?.snapshotBackend??"ax";if("ios"===e.platform){if("simulator"!==e.kind)throw new f("UNSUPPORTED_OPERATION","snapshot is only supported on iOS simulators in v1");if("ax"===t)return{nodes:(await eI(e)).nodes??[],truncated:!1,backend:"ax"};let i=await el(e,{command:"snapshot",appBundleId:n?.appBundleId,interactiveOnly:n?.snapshotInteractiveOnly,compact:n?.snapshotCompact,depth:n?.snapshotDepth,scope:n?.snapshotScope,raw:n?.snapshotRaw},{verbose:n?.verbose,logPath:n?.logPath});return{nodes:i.nodes??[],truncated:i.truncated??!1,backend:"xctest"}}let i=await j(e,{interactiveOnly:n?.snapshotInteractiveOnly,compact:n?.snapshotCompact,depth:n?.snapshotDepth,scope:n?.snapshotScope,raw:n?.snapshotRaw});return{nodes:i.nodes??[],truncated:i.truncated??!1,backend:"android"}}default:throw new f("INVALID_ARGS",`Unknown command: ${t}`)}}function eD(e){return e.map((e,t)=>({...e,ref:`e${t+1}`}))}function eO(e){let t=e.trim();return t.startsWith("@")?t.slice(1)||null:t.startsWith("e")?t:null}function ek(e,t){return e.find(e=>e.ref===t)??null}function ex(e){return{x:Math.round(e.x+e.width/2),y:Math.round(e.y+e.height/2)}}let e_=new Map,eE=o.join(p.homedir(),".agent-device"),eP=o.join(eE,"daemon.json"),eR=o.join(eE,"daemon.log"),eC=o.join(eE,"sessions"),eT=function(){try{let e=function(){let e=o.dirname(c(import.meta.url)),t=e;for(let e=0;e<6;e+=1){let e=o.join(t,"package.json");if(u.existsSync(e))return t;t=o.dirname(t)}return e}();return JSON.parse(u.readFileSync(o.join(e,"package.json"),"utf8")).version??"0.0.0"}catch{return"0.0.0"}}(),eL=i.randomBytes(24).toString("hex");function eM(e,t){return{appBundleId:t,verbose:e?.verbose,logPath:eR,snapshotInteractiveOnly:e?.snapshotInteractiveOnly,snapshotCompact:e?.snapshotCompact,snapshotDepth:e?.snapshotDepth,snapshotScope:e?.snapshotScope,snapshotRaw:e?.snapshotRaw,snapshotBackend:e?.snapshotBackend}}async function eF(e){if(e.token!==eL)return{ok:!1,error:{code:"UNAUTHORIZED",message:"Invalid token"}};let t=e.command,i=e.session||"default";if("session_list"===t)return{ok:!0,data:{sessions:Array.from(e_.values()).map(e=>({name:e.name,platform:e.device.platform,device:e.device.name,id:e.device.id,createdAt:e.createdAt}))}};if("devices"===t)try{let t=[];if(e.flags?.platform==="android"){let{listAndroidDevices:e}=await Promise.resolve().then(()=>({listAndroidDevices:y}));t.push(...await e())}else if(e.flags?.platform==="ios"){let{listIosDevices:e}=await Promise.resolve().then(()=>({listIosDevices:G}));t.push(...await e())}else{let{listAndroidDevices:e}=await Promise.resolve().then(()=>({listAndroidDevices:y})),{listIosDevices:i}=await Promise.resolve().then(()=>({listIosDevices:G}));try{t.push(...await e())}catch{}try{t.push(...await i())}catch{}}return{ok:!0,data:{devices:t}}}catch(t){let e=l(t);return{ok:!1,error:{code:e.code,message:e.message,details:e.details}}}if("apps"===t){let t=e_.get(i),a=e.flags??{};if(!t&&!a.platform&&!a.device&&!a.udid&&!a.serial)return{ok:!1,error:{code:"INVALID_ARGS",message:"apps requires an active session or an explicit device selector (e.g. --platform ios)."}};let n=t?.device??await eA(a);if(await eJ(n),"ios"===n.platform){if("simulator"!==n.kind)return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"apps list is only supported on iOS simulators"}};let{listSimulatorApps:e}=await Promise.resolve().then(()=>({listSimulatorApps:en}));return{ok:!0,data:{apps:(await e(n)).map(e=>e.name&&e.name!==e.bundleId?`${e.name} (${e.bundleId})`:e.bundleId)}}}let{listAndroidApps:o}=await Promise.resolve().then(()=>({listAndroidApps:S}));return{ok:!0,data:{apps:await o(n,e.flags?.appsFilter)}}}if("open"===t){let a;if(e_.has(i))return{ok:!1,error:{code:"INVALID_ARGS",message:"Session already active. Close it first or pass a new --session name."}};let n=await eA(e.flags??{});await eJ(n);let o=Array.from(e_.values()).find(e=>e.device.id===n.id);if(o)return{ok:!1,error:{code:"DEVICE_IN_USE",message:`Device is already in use by session "${o.name}".`,details:{session:o.name,deviceId:n.id,deviceName:n.name}}};let r=e.positionals?.[0];if("ios"===n.platform)try{let{resolveIosApp:t}=await Promise.resolve().then(()=>({resolveIosApp:q}));a=await t(n,e.positionals?.[0]??"")}catch{a=void 0}await eS(n,"open",e.positionals??[],e.flags?.out,{...eM(e.flags,a)});let s={name:i,device:n,createdAt:Date.now(),appBundleId:a,appName:r,actions:[]};return eU(s,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{session:i}}),e_.set(i,s),{ok:!0,data:{session:i}}}if("replay"===t){let t=e.positionals?.[0];if(!t)return{ok:!1,error:{code:"INVALID_ARGS",message:"replay requires a path"}};try{var a;let e=(a=t).startsWith("~/")?o.join(p.homedir(),a.slice(2)):o.resolve(a),n=JSON.parse(u.readFileSync(e,"utf8")),r=n.optimizedActions??n.actions??[];for(let e of r)e&&"replay"!==e.command&&await eF({token:eL,session:i,command:e.command,positionals:e.positionals??[],flags:e.flags??{}});return{ok:!0,data:{replayed:r.length,session:i}}}catch(t){let e=l(t);return{ok:!1,error:{code:e.code,message:e.message}}}}if("close"===t){let a=e_.get(i);return a?(e.positionals&&e.positionals.length>0&&await eS(a.device,"close",e.positionals??[],e.flags?.out,{...eM(e.flags,a.appBundleId)}),"ios"===a.device.platform&&"simulator"===a.device.kind&&await ec(a.device.id),eU(a,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{session:i}}),ej(a),e_.delete(i),{ok:!0,data:{session:i}}):{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session"}}}if("snapshot"===t){let a=e_.get(i),n=a?.device??await eA(e.flags??{});a||await eJ(n);let o=a?.appBundleId,r=e.flags?.snapshotScope;if(r&&r.trim().startsWith("@")){if(!a?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"Ref scope requires an existing snapshot in session."}};let e=eO(r.trim());if(!e)return{ok:!1,error:{code:"INVALID_ARGS",message:`Invalid ref scope: ${r}`}};let t=ek(a.snapshot.nodes,e),i=t?eB(t,a.snapshot.nodes):void 0;if(!i)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${r} not found or has no label`}};r=i}let s=await eS(n,"snapshot",[],e.flags?.out,{...eM({...e.flags,snapshotScope:r},o)}),l=s?.nodes??[],c=eD(e.flags?.snapshotRaw?l:function(e){let t=[],i=[];for(let a of e){let e=a.depth??0;for(;t.length>0&&e<=t[t.length-1];)t.pop();let n=function(e){let t=e.replace(/XCUIElementType/gi,"").toLowerCase();return t.startsWith("ax")&&(t=t.replace(/^ax/,"")),t}(a.type??""),o=[a.label,a.value,a.identifier].map(e=>"string"==typeof e?e.trim():"").find(e=>e&&e.length>0),r=!!o&&eG(o);if(("group"===n||"ioscontentgroup"===n)&&!r){t.push(e);continue}let s=Math.max(0,e-t.length);i.push({...a,depth:s})}return i}(l)),d={nodes:c,truncated:s?.truncated,createdAt:Date.now(),backend:s?.backend},u={name:i,device:n,createdAt:a?.createdAt??Date.now(),appBundleId:o,snapshot:d,actions:a?.actions??[]};return eU(u,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{nodes:c.length,truncated:s?.truncated??!1}}),e_.set(i,u),{ok:!0,data:{nodes:c,truncated:s?.truncated??!1,appName:a?.appName??o??n.name,appBundleId:o}}}if("wait"===t){let a=e_.get(i),n=a?.device??await eA(e.flags??{});a||await eJ(n);let o=e.positionals??[];if(0===o.length)return{ok:!1,error:{code:"INVALID_ARGS",message:"wait requires a duration or text"}};let r=e=>{if(!e)return null;let t=Number(e);return Number.isFinite(t)?t:null},s=r(o[0]);if(null!==s)return await new Promise(e=>setTimeout(e,s)),a&&eU(a,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{waitedMs:s}}),{ok:!0,data:{waitedMs:s}};let l="",c=null;if("text"===o[0])l=null!==(c=r(o[o.length-1]))?o.slice(1,-1).join(" "):o.slice(1).join(" ");else if(o[0].startsWith("@")){if(!a?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"Ref wait requires an existing snapshot in session."}};let e=eO(o[0]);if(!e)return{ok:!1,error:{code:"INVALID_ARGS",message:`Invalid ref: ${o[0]}`}};let t=ek(a.snapshot.nodes,e),i=t?eB(t,a.snapshot.nodes):void 0;if(!i)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${o[0]} not found or has no label`}};c=r(o[o.length-1]),l=i}else l=null!==(c=r(o[o.length-1]))?o.slice(0,-1).join(" "):o.join(" ");if(!(l=l.trim()))return{ok:!1,error:{code:"INVALID_ARGS",message:"wait requires text"}};let d=c??1e4,u=Date.now();for(;Date.now()-u<d;){if("ios"===n.platform&&"simulator"===n.kind){let i=await el(n,{command:"findText",text:l,appBundleId:a?.appBundleId},{verbose:e.flags?.verbose,logPath:eR});if(i?.found)return a&&eU(a,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{text:l,waitedMs:Date.now()-u}}),{ok:!0,data:{text:l,waitedMs:Date.now()-u}}}else if("android"!==n.platform)return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"wait is not supported on this device"}};else if(eV(eD((await j(n,{scope:l})).nodes??[]),l))return a&&eU(a,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{text:l,waitedMs:Date.now()-u}}),{ok:!0,data:{text:l,waitedMs:Date.now()-u}};await new Promise(e=>setTimeout(e,300))}return{ok:!1,error:{code:"COMMAND_FAILED",message:`wait timed out for text: ${l}`}}}if("alert"===t){let a=e_.get(i),n=a?.device??await eA(e.flags??{});a||await eJ(n);let o=(e.positionals?.[0]??"get").toLowerCase();if("ios"!==n.platform||"simulator"!==n.kind)return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"alert is only supported on iOS simulators in v1"}};if("wait"===o){let i=(e=>{if(!e)return null;let t=Number(e);return Number.isFinite(t)?t:null})(e.positionals?.[1])??1e4,o=Date.now();for(;Date.now()-o<i;){try{let i=await el(n,{command:"alert",action:"get",appBundleId:a?.appBundleId},{verbose:e.flags?.verbose,logPath:eR});return a&&eU(a,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:i}),{ok:!0,data:i}}catch{}await new Promise(e=>setTimeout(e,300))}return{ok:!1,error:{code:"COMMAND_FAILED",message:"alert wait timed out"}}}let r=await el(n,{command:"alert",action:"accept"===o||"dismiss"===o?o:"get",appBundleId:a?.appBundleId},{verbose:e.flags?.verbose,logPath:eR});return a&&eU(a,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:r}),{ok:!0,data:r}}if("record"===t){let a=(e.positionals?.[0]??"").toLowerCase();if(!["start","stop"].includes(a))return{ok:!1,error:{code:"INVALID_ARGS",message:"record requires start|stop"}};let n=e_.get(i),r=n?.device??await eA(e.flags??{});n||await eJ(r);let s=n??{name:i,device:r,createdAt:Date.now(),actions:[]};if("start"===a){if(s.recording)return{ok:!1,error:{code:"INVALID_ARGS",message:"recording already in progress"}};let a=e.positionals?.[1]??`./recording-${Date.now()}.mp4`,n=o.resolve(a),l=o.dirname(n);if(u.existsSync(l)||u.mkdirSync(l,{recursive:!0}),"ios"===r.platform){if("simulator"!==r.kind)return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"record is only supported on iOS simulators in v1"}};let{child:e,wait:t}=d("xcrun",["simctl","io",r.id,"recordVideo",n],{allowFailure:!0});s.recording={platform:"ios",outPath:n,child:e,wait:t}}else{let e=`/sdcard/agent-device-recording-${Date.now()}.mp4`,{child:t,wait:i}=d("adb",["-s",r.id,"shell","screenrecord",e],{allowFailure:!0});s.recording={platform:"android",outPath:n,remotePath:e,child:t,wait:i}}return e_.set(i,s),eU(s,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{action:"start"}}),{ok:!0,data:{recording:"started",outPath:a}}}if(!s.recording)return{ok:!1,error:{code:"INVALID_ARGS",message:"no active recording"}};let l=s.recording;l.child.kill("SIGINT");try{await l.wait}catch{}if("android"===l.platform&&l.remotePath)try{await m("adb",["-s",r.id,"pull",l.remotePath,l.outPath],{allowFailure:!0}),await m("adb",["-s",r.id,"shell","rm","-f",l.remotePath],{allowFailure:!0})}catch{}return s.recording=void 0,eU(s,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{action:"stop",outPath:l.outPath}}),{ok:!0,data:{recording:"stopped",outPath:l.outPath}}}if("click"===t){let a=e_.get(i);if(!a?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"No snapshot in session. Run snapshot first."}};let n=e.positionals?.[0]??"",o=eO(n);if(!o)return{ok:!1,error:{code:"INVALID_ARGS",message:"click requires a ref like @e2"}};let r=ek(a.snapshot.nodes,o);if(!r?.rect&&e.positionals.length>1){let t=e.positionals.slice(1).join(" ").trim();t.length>0&&(r=eV(a.snapshot.nodes,t))}if(!r?.rect)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${n} not found or has no bounds`}};let s=eB(r,a.snapshot.nodes),l=r.label?.trim();if("ios"===a.device.platform&&"simulator"===a.device.kind&&l&&function(e,t){let i=t.trim().toLowerCase();if(!i)return!1;let a=0;for(let t of e)if((t.label??"").trim().toLowerCase()===i&&(a+=1)>1)return!1;return 1===a}(a.snapshot.nodes,l))return await el(a.device,{command:"tap",text:l,appBundleId:a.appBundleId},{verbose:e.flags?.verbose,logPath:eR}),eU(a,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{ref:o,refLabel:l,mode:"text"}}),{ok:!0,data:{ref:o,mode:"text"}};let{x:c,y:d}=ex(r.rect);return await eS(a.device,"press",[String(c),String(d)],e.flags?.out,{...eM(e.flags,a.appBundleId)}),eU(a,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{ref:o,x:c,y:d,refLabel:s}}),{ok:!0,data:{ref:o,x:c,y:d}}}if("fill"===t){let a=e_.get(i);if(e.positionals?.[0]?.startsWith("@")){if(!a?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"No snapshot in session. Run snapshot first."}};let i=eO(e.positionals[0]);if(!i)return{ok:!1,error:{code:"INVALID_ARGS",message:"fill requires a ref like @e2"}};let n=e.positionals.length>=3?e.positionals[1]:"",o=e.positionals.length>=3?e.positionals.slice(2).join(" "):e.positionals.slice(1).join(" ");if(!o)return{ok:!1,error:{code:"INVALID_ARGS",message:"fill requires text after ref"}};let r=ek(a.snapshot.nodes,i);if(!r?.rect&&n&&(r=eV(a.snapshot.nodes,n)),!r?.rect)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${e.positionals[0]} not found or has no bounds`}};let s=eB(r,a.snapshot.nodes),{x:l,y:c}=ex(r.rect),d=await eS(a.device,"fill",[String(l),String(c),o],e.flags?.out,{...eM(e.flags,a.appBundleId)});return eU(a,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:d??{ref:i,x:l,y:c,refLabel:s}}),{ok:!0,data:d??{ref:i,x:l,y:c}}}}if("get"===t){let a=e.positionals?.[0],n=e.positionals?.[1];if("text"!==a&&"attrs"!==a)return{ok:!1,error:{code:"INVALID_ARGS",message:"get only supports text or attrs"}};let o=e_.get(i);if(!o?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"No snapshot in session. Run snapshot first."}};let r=eO(n??"");if(!r)return{ok:!1,error:{code:"INVALID_ARGS",message:"get text requires a ref like @e2"}};let s=ek(o.snapshot.nodes,r);if(!s&&e.positionals.length>2){let t=e.positionals.slice(2).join(" ").trim();t.length>0&&(s=eV(o.snapshot.nodes,t))}if(!s)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${n} not found`}};if("attrs"===a)return eU(o,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{ref:r}}),{ok:!0,data:{ref:r,node:s}};let l=[s.label,s.value,s.identifier].map(e=>"string"==typeof e?e.trim():"").filter(e=>e.length>0)[0]??"";return eU(o,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{ref:r,text:l,refLabel:l||void 0}}),{ok:!0,data:{ref:r,text:l,node:s}}}let n=e_.get(i);if(!n)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session. Run open first."}};let r=await eS(n.device,t,e.positionals??[],e.flags?.out,{...eM(e.flags,n.appBundleId)});return eU(n,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:r??{}}),{ok:!0,data:r??{}}}function eU(e,t){t.flags?.noRecord||e.actions.push({ts:Date.now(),command:t.command,positionals:t.positionals,flags:function(e){if(!e)return{};let{platform:t,device:i,udid:a,serial:n,out:o,verbose:r,snapshotInteractiveOnly:s,snapshotCompact:l,snapshotDepth:c,snapshotScope:d,snapshotRaw:u,snapshotBackend:p,noRecord:f,recordJson:m}=e;return{platform:t,device:i,udid:a,serial:n,out:o,verbose:r,snapshotInteractiveOnly:s,snapshotCompact:l,snapshotDepth:c,snapshotScope:d,snapshotRaw:u,snapshotBackend:p,noRecord:f,recordJson:m}}(t.flags),result:t.result})}function ej(e){try{u.existsSync(eC)||u.mkdirSync(eC,{recursive:!0});let t=e.name.replace(/[^a-zA-Z0-9._-]/g,"_"),i=new Date(e.createdAt).toISOString().replace(/[:.]/g,"-"),a=o.join(eC,`${t}-${i}.ad`),n=o.join(eC,`${t}-${i}.json`),r={name:e.name,device:e.device,createdAt:e.createdAt,appBundleId:e.appBundleId,actions:e.actions,optimizedActions:function(e){let t=[];for(let i of e.actions)if("snapshot"!==i.command){if("click"===i.command||"fill"===i.command||"get"===i.command){let a=i.result?.refLabel;"string"==typeof a&&a.trim().length>0&&t.push({ts:i.ts,command:"snapshot",positionals:[],flags:{platform:e.device.platform,snapshotInteractiveOnly:!0,snapshotCompact:!0,snapshotScope:a.trim()},result:{scope:a.trim()}})}t.push(i)}return t}(e)},s=function(e,t){let i=[],a=e.device.name.replace(/"/g,'\\"'),n=e.device.kind?` kind=${e.device.kind}`:"";for(let o of(i.push(`context platform=${e.device.platform} device="${a}"${n} theme=unknown`),t))o.flags?.noRecord||i.push(function(e){let t=[e.command];if("click"===e.command){let i=e.positionals?.[0];if(i){t.push(e$(i));let a=e.result?.refLabel;return"string"==typeof a&&a.trim().length>0&&t.push(e$(a)),t.join(" ")}}if("fill"===e.command){let i=e.positionals?.[0];if(i&&i.startsWith("@")){t.push(e$(i));let a=e.result?.refLabel,n=e.positionals.slice(1).join(" ");return"string"==typeof a&&a.trim().length>0&&t.push(e$(a)),n&&t.push(e$(n)),t.join(" ")}}if("get"===e.command){let i=e.positionals?.[0],a=e.positionals?.[1];if(i&&a){t.push(e$(i)),t.push(e$(a));let n=e.result?.refLabel;return"string"==typeof n&&n.trim().length>0&&t.push(e$(n)),t.join(" ")}}if("snapshot"===e.command)return e.flags?.snapshotInteractiveOnly&&t.push("-i"),e.flags?.snapshotCompact&&t.push("-c"),"number"==typeof e.flags?.snapshotDepth&&t.push("-d",String(e.flags.snapshotDepth)),e.flags?.snapshotScope&&t.push("-s",e$(e.flags.snapshotScope)),e.flags?.snapshotRaw&&t.push("--raw"),e.flags?.snapshotBackend&&t.push("--backend",e.flags.snapshotBackend),t.join(" ");for(let i of e.positionals??[])t.push(e$(i));return t.join(" ")}(o));return`${i.join("\n")}
|
|
2
|
+
`}(e,r.optimizedActions);u.writeFileSync(a,s),e.actions.some(e=>e.flags?.recordJson)&&u.writeFileSync(n,JSON.stringify(r,null,2))}catch{}}function e$(e){let t=e.trim();return t.startsWith("@")||/^-?\d+(\.\d+)?$/.test(t)?t:JSON.stringify(t)}function eV(e,t){let i=t.toLowerCase();return e.find(e=>{let t=(e.label??"").toLowerCase(),a=(e.value??"").toLowerCase(),n=(e.identifier??"").toLowerCase();return t.includes(i)||a.includes(i)||n.includes(i)})??null}function eB(e,t){let i=[e.label,e.value,e.identifier].map(e=>"string"==typeof e?e.trim():"").find(e=>e&&e.length>0);return i&&eG(i)?i:function(e,t){if(!e.rect)return;let i=e.rect.y+e.rect.height/2,a=null;for(let e of t){if(!e.rect)continue;let t=[e.label,e.value,e.identifier].map(e=>"string"==typeof e?e.trim():"").find(e=>e&&e.length>0);if(!t||!eG(t))continue;let n=Math.abs(e.rect.y+e.rect.height/2-i);(!a||n<a.distance)&&(a={label:t,distance:n})}return a?.label}(e,t)??(i&&eG(i)?i:void 0)}function eG(e){let t=e.trim();return!(!t||/^(true|false)$/i.test(t)||/^\d+$/.test(t))}async function eJ(e){if("ios"===e.platform&&"simulator"===e.kind){let{ensureBootedSimulator:t}=await Promise.resolve().then(()=>({ensureBootedSimulator:eo}));await t(e);return}if("android"===e.platform){let{waitForAndroidBoot:t}=await Promise.resolve().then(()=>({waitForAndroidBoot:N}));await t(e.id)}}(e=h.createServer(e=>{let t="";e.setEncoding("utf8"),e.on("data",async i=>{let a=(t+=i).indexOf("\n");for(;-1!==a;){let i,n=t.slice(0,a).trim();if(t=t.slice(a+1),0===n.length){a=t.indexOf("\n");continue}try{let e=JSON.parse(n);i=await eF(e)}catch(t){let e=l(t);i={ok:!1,error:{code:e.code,message:e.message,details:e.details}}}e.write(`${JSON.stringify(i)}
|
|
3
|
+
`),a=t.indexOf("\n")}})})).listen(0,"127.0.0.1",()=>{let t=e.address();if("object"==typeof t&&t?.port){var i;i=t.port,u.existsSync(eE)||u.mkdirSync(eE,{recursive:!0}),u.writeFileSync(eR,""),u.writeFileSync(eP,JSON.stringify({port:i,token:eL,pid:process.pid,version:eT},null,2),{mode:384}),process.stdout.write(`AGENT_DEVICE_DAEMON_PORT=${t.port}
|
|
4
|
+
`)}}),t=async()=>{for(let e of Array.from(e_.values()))"ios"===e.device.platform&&"simulator"===e.device.kind&&await ec(e.device.id),ej(e);e.close(()=>{u.existsSync(eP)&&u.unlinkSync(eP),process.exit(0)})},process.on("SIGINT",()=>{t()}),process.on("SIGTERM",()=>{t()}),process.on("SIGHUP",()=>{t()}),process.on("uncaughtException",e=>{let i=e instanceof f?e:l(e);process.stderr.write(`Daemon error: ${i.message}
|
|
5
5
|
`),t()});
|