agent-device 0.1.2 → 0.1.4

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 CHANGED
@@ -6,7 +6,7 @@ This project mirrors the spirit of `agent-browser`, but targets iOS simulators/d
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
- - Default backend is `ax` (fast). It requires enabling Accessibility for the terminal app in System Settings.
48
- - If AX is not available, use `--backend xctest` explicitly.
55
+ - Default backend is `hybrid` because it provides the best speed vs correctness trade-off: AX is fast but can miss UI details, while XCTest is slower but more complete. Hybrid uses the fast AX snapshot first, then fills empty containers (tab bars/toolbars/groups) with scoped XCTest snapshots.
56
+ - `ax` is the fast AX-only backend and requires enabling Accessibility for the terminal app in System Settings.
57
+ - `xctest` is the slower XCTest-only backend that avoids Accessibility permissions.
58
+ - You can scope snapshots to a label or identifier with `-s "<label>"` or to a previous ref with `-s @ref`.
59
+ In practice, if AX returns a `Tab Bar` group with no children, hybrid will run a scoped XCTest snapshot for `Tab Bar` and insert those nodes under the group.
49
60
 
50
61
  Flags:
51
62
  - `--platform ios|android`
@@ -56,17 +67,44 @@ Flags:
56
67
  - `--session <name>`
57
68
  - `--verbose` for daemon and runner logs
58
69
  - `--json` for structured output
59
- - `--backend ax|xctest` (snapshot only; defaults to `ax` on iOS)
60
- - `open` without args boots/activates the target device/simulator without launching an app.
70
+ - `--backend ax|xctest|hybrid` (snapshot only; defaults to `hybrid` on iOS)
71
+
72
+ Tracing:
73
+ - `trace start [path]` to begin capturing AX/XCTest logs for the session.
74
+ - `trace stop [path]` to stop capture and optionally move the trace log.
61
75
 
62
76
  Sessions:
63
- - `open` starts a session.
77
+ - `open` starts a session. Without args boots/activates the target device/simulator without launching an app.
64
78
  - All interaction commands require an open session.
65
79
  - `close` stops the session and releases device resources. Pass an app to close it explicitly, or omit to just close the session.
66
80
  - Use `--session <name>` to manage multiple sessions.
67
81
  - Session logs are written to `~/.agent-device/sessions/<session>-<timestamp>.ad`.
68
82
 
69
- Snapshot defaults to the AX backend on iOS simulators and falls back to XCTest if AX is unavailable.
83
+ Snapshot defaults to the hybrid backend on iOS simulators. Use `--backend ax` for AX-only or `--backend xctest` for XCTest-only.
84
+
85
+ Find (semantic):
86
+ - `find <text> <action> [value]` finds by any text (label/value/identifier) using a scoped snapshot.
87
+ - `find text|label|value|role|id <value> <action> [value]` for specific locators.
88
+ - Actions: `click` (default), `fill`, `type`, `focus`, `get text`, `get attrs`, `wait [timeout]`, `exists`.
89
+
90
+ Settings helpers (simulators):
91
+ - `settings wifi on|off`
92
+ - `settings airplane on|off`
93
+ - `settings location on|off` (iOS uses per‑app permission for the current session app)
94
+ - Note: iOS wifi/airplane toggles status bar indicators, not actual network state. Airplane off clears status bar overrides.
95
+
96
+ App state:
97
+ - `appstate` shows the foreground app/activity (Android). On iOS it uses the current session app when available, otherwise it falls back to a snapshot-based guess (AX first, XCTest if AX can’t identify).
98
+ - `apps --metadata` returns app list with minimal metadata.
99
+
100
+ ## Debug
101
+
102
+ - Start trace capture before a flaky sequence:
103
+ - `agent-device trace start`
104
+ - `agent-device trace stop ./trace.log`
105
+ - The trace log includes AX snapshot stderr and XCTest runner logs for the session.
106
+ - Built-in retries cover transient runner connection failures, AX snapshot hiccups, and Android UI dumps.
107
+ - For snapshot issues, compare `--backend ax` vs `--backend xctest` and scope with `-s "<label>"`.
70
108
 
71
109
  ## App resolution
72
110
  - Bundle/package identifiers are accepted directly (e.g., `com.apple.Preferences`).
@@ -74,8 +112,8 @@ Snapshot defaults to the AX backend on iOS simulators and falls back to XCTest i
74
112
  - Built-in aliases include `Settings` for both platforms.
75
113
 
76
114
  ## iOS notes
77
- - Input commands (`press`, `type`, `scroll`, etc.) are supported only on simulators in v1.
78
- - Support depends on your Xcode version; the CLI reports `UNSUPPORTED_OPERATION` if `simctl io` lacks input operations.
115
+ - Input commands (`press`, `type`, `scroll`, etc.) are supported only on simulators in v1 and use the XCTest runner.
116
+ - `alert` and `scrollintoview` use the XCTest runner and are simulator-only in v1.
79
117
 
80
118
  ## Testing
81
119
 
Binary file
package/dist/src/861.js CHANGED
@@ -1 +1 @@
1
- import{spawn as e}from"node:child_process";function o(e,o,t){return o in e?Object.defineProperty(e,o,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[o]=t,e}class t extends Error{constructor(e,t,r,n){super(t),o(this,"code",void 0),o(this,"details",void 0),o(this,"cause",void 0),this.code=e,this.details=r,this.cause=n}}function r(e){return e instanceof t?e:e instanceof Error?new t("UNKNOWN",e.message,void 0,e):new t("UNKNOWN","Unknown error",{err:e})}async function n(o,r,d={}){return new Promise((n,i)=>{let a=e(o,r,{cwd:d.cwd,env:d.env,stdio:["ignore","pipe","pipe"]}),u="",s=d.binaryStdout?Buffer.alloc(0):void 0,f="";d.binaryStdout||a.stdout.setEncoding("utf8"),a.stderr.setEncoding("utf8"),a.stdout.on("data",e=>{d.binaryStdout?s=Buffer.concat([s??Buffer.alloc(0),Buffer.isBuffer(e)?e:Buffer.from(e)]):u+=e}),a.stderr.on("data",e=>{f+=e}),a.on("error",e=>{"ENOENT"===e.code?i(new t("TOOL_MISSING",`${o} not found in PATH`,{cmd:o},e)):i(new t("COMMAND_FAILED",`Failed to run ${o}`,{cmd:o,args:r},e))}),a.on("close",e=>{let a=e??1;0===a||d.allowFailure?n({stdout:u,stderr:f,exitCode:a,stdoutBuffer:s}):i(new t("COMMAND_FAILED",`${o} exited with code ${a}`,{cmd:o,args:r,stdout:u,stderr:f,exitCode:a}))})})}async function d(e){try{var o;let{shell:t,args:r}=(o=e,"win32"===process.platform?{shell:"cmd.exe",args:["/c","where",o]}:{shell:"bash",args:["-lc",`command -v ${o}`]}),d=await n(t,r,{allowFailure:!0});return 0===d.exitCode&&d.stdout.trim().length>0}catch{return!1}}function i(o,t,r={}){e(o,t,{cwd:r.cwd,env:r.env,stdio:"ignore",detached:!0}).unref()}async function a(o,r,n={}){return new Promise((d,i)=>{let a=e(o,r,{cwd:n.cwd,env:n.env,stdio:["ignore","pipe","pipe"]}),u="",s="",f=n.binaryStdout?Buffer.alloc(0):void 0;n.binaryStdout||a.stdout.setEncoding("utf8"),a.stderr.setEncoding("utf8"),a.stdout.on("data",e=>{if(n.binaryStdout){f=Buffer.concat([f??Buffer.alloc(0),Buffer.isBuffer(e)?e:Buffer.from(e)]);return}let o=String(e);u+=o,n.onStdoutChunk?.(o)}),a.stderr.on("data",e=>{let o=String(e);s+=o,n.onStderrChunk?.(o)}),a.on("error",e=>{"ENOENT"===e.code?i(new t("TOOL_MISSING",`${o} not found in PATH`,{cmd:o},e)):i(new t("COMMAND_FAILED",`Failed to run ${o}`,{cmd:o,args:r},e))}),a.on("close",e=>{let a=e??1;0===a||n.allowFailure?d({stdout:u,stderr:s,exitCode:a,stdoutBuffer:f}):i(new t("COMMAND_FAILED",`${o} exited with code ${a}`,{cmd:o,args:r,stdout:u,stderr:s,exitCode: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{r as asAppError,t as errors_AppError,n as runCmd,i as runCmdDetached,a as runCmdStreaming,d as whichCmd};
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,25 +1,33 @@
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 c}from"./861.js";function l(e){process.stdout.write(`${JSON.stringify(e,null,2)}
2
- `)}function u(e){let t=e.details?`
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 a,node_net as i,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 d=e.join(i.homedir(),".agent-device"),p=e.join(d,"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 c("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 c("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 c("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(s.startsWith("--backend")){let r=s.includes("=")?s.split("=")[1]:e[n+1];if(s.includes("=")||(n+=1),"ax"!==r&&"xctest"!==r)throw new c("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 c("INVALID_ARGS",`Invalid platform: ${i}`);t.platform=i;break;case"--depth":{let e=Number(i);if(!Number.isFinite(e)||e<0)throw new c("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 c("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 c("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]
4
+ `)}let u=e.join(a.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=y(),r=function(){try{let t=v();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 w(t))return t;t&&(t.version!==r||!await w(t))&&o.existsSync(p)&&o.unlinkSync(p),await g();let n=Date.now();for(;Date.now()-n<5e3;){let e=y();if(e&&await w(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 y(){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 w(e){return new Promise(t=>{let r=i.createConnection({host:"127.0.0.1",port:e.port},()=>{r.destroy(),t(!0)});r.on("error",()=>{t(!1)})})}async function g(){let t=v(),r=e.join(t,"dist","src","daemon.js"),n=e.join(t,"src","daemon.ts"),a=o.existsSync(r);if(!a&&!o.existsSync(n))throw new l("COMMAND_FAILED","Daemon entry not found",{distPath:r,srcPath:n});let i=a?[r]:["--experimental-strip-types",n];s(process.execPath,i)}async function b(e,t){return new Promise((r,n)=>{let s=i.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),a="";s.setEncoding("utf8"),s.on("data",e=>{let t=(a+=e).indexOf("\n");if(-1===t)return;let i=a.slice(0,t).trim();if(i)try{let e=JSON.parse(i);s.end(),clearTimeout(o),r(e)}catch(e){clearTimeout(o),n(e)}}),s.on("error",e=>{clearTimeout(o),n(e)})})}function v(){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 $(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("--metadata"===s){t.appsMetadata=!0;continue}if(s.startsWith("--backend")){let r=s.includes("=")?s.split("=")[1]:e[n+1];if(s.includes("=")||(n+=1),"ax"!==r&&"xctest"!==r&&"hybrid"!==r)throw new l("INVALID_ARGS",`Invalid backend: ${r}`);t.snapshotBackend=r;continue}if(s.startsWith("--")){let[r,o]=s.split("="),a=o??e[n+1];switch(!o&&(n+=1),r){case"--platform":if("ios"!==a&&"android"!==a)throw new l("INVALID_ARGS",`Invalid platform: ${a}`);t.platform=a;break;case"--depth":{let e=Number(a);if(!Number.isFinite(e)||e<0)throw new l("INVALID_ARGS",`Invalid depth: ${a}`);t.snapshotDepth=Math.floor(e);break}case"--scope":t.snapshotScope=a;break;case"--device":t.device=a;break;case"--udid":t.udid=a;break;case"--serial":t.serial=a;break;case"--out":t.out=a;break;case"--session":t.session=a;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
6
 
7
7
  CLI to control iOS and Android devices for AI agents.
8
8
 
9
9
  Commands:
10
10
  open [app] Boot device/simulator; optionally launch app
11
11
  close [app] Close app or just end session
12
- snapshot [-i] [-c] [-d <depth>] [-s <scope>] [--raw] [--backend ax|xctest]
12
+ snapshot [-i] [-c] [-d <depth>] [-s <scope>] [--raw] [--backend ax|xctest|hybrid]
13
13
  Capture accessibility tree
14
14
  -i Interactive elements only
15
15
  -c Compact output (drop empty structure)
16
16
  -d <depth> Limit snapshot depth
17
17
  -s <scope> Scope snapshot to label/identifier
18
18
  --raw Raw node output
19
- --backend ax|xctest ax: macOS Accessibility tree (fast, recommended, needs permissions)
19
+ --backend ax|xctest|hybrid hybrid: default; AX snapshot with XCTest fill for empty containers
20
+ ax: macOS Accessibility tree (fast, needs permissions)
20
21
  xctest: XCTest snapshot (slower, no permissions)
22
+ devices List available devices
23
+ apps [--user-installed|--all|--metadata] List installed apps (Android launchable by default, iOS simulator)
24
+ appstate Show foreground app/activity
25
+ back Navigate back (where supported)
26
+ home Go to home screen (where supported)
27
+ app-switcher Open app switcher (where supported)
28
+ wait <ms>|text <text>|@ref [timeoutMs] Wait for duration or text to appear
29
+ alert [get|accept|dismiss|wait] [timeout] Inspect or handle alert (iOS simulator)
21
30
  click <@ref> Click element by snapshot ref
22
- rect <label|@ref> Fetch element frame by label or ref (iOS sim)
23
31
  get text <@ref> Return element text by ref
24
32
  get attrs <@ref> Return element attributes by ref
25
33
  replay <path> Replay a recorded session
@@ -31,6 +39,17 @@ Commands:
31
39
  scroll <direction> [amount] Scroll in direction (0-1 amount)
32
40
  scrollintoview <text> Scroll until text appears (Android only)
33
41
  screenshot [--out path] Capture screenshot
42
+ record start [path] Start screen recording
43
+ record stop Stop screen recording
44
+ trace start [path] Start trace log capture
45
+ trace stop [path] Stop trace log capture
46
+ find <text> <action> [value] Find by any text (label/value/id)
47
+ find text <text> <action> [value] Find by text content
48
+ find label <label> <action> [value] Find by label
49
+ find value <value> <action> [value] Find by value
50
+ find role <role> <action> [value] Find by role/type
51
+ find id <id> <action> [value] Find by identifier/resource-id
52
+ settings <wifi|airplane|location> <on|off> Toggle OS settings (simulators)
34
53
  session list List active sessions
35
54
 
36
55
  Flags:
@@ -44,18 +63,30 @@ Flags:
44
63
  --json JSON output
45
64
  --no-record Do not record this action
46
65
  --record-json Record JSON session log
66
+ --user-installed Apps: list user-installed packages (Android only)
67
+ --all Apps: list all packages (Android only)
47
68
 
48
- `),process.exit(+!n.flags.help));let{command:s,positionals:a,flags:d}=n,p=d.session??process.env.AGENT_DEVICE_SESSION??"default",f=d.verbose&&!d.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 c("INVALID_ARGS","session only supports list");let t=await m({session:p,command:"session_list",positionals:[],flags:{}});if(!t.ok)throw new c(t.error.code,t.error.message);d.json?l({success:!0,data:t.data??{}}):process.stdout.write(`${JSON.stringify(t.data??{},null,2)}
49
- `),f&&f();return}let e=await m({session:p,command:s,positionals:a,flags:d});if(e.ok){if(d.json){l({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)":""}`,c=i.length>0?`${i.join("\n")}
50
- `:"";if(!Array.isArray(r)||0===r.length)return`${c}${a}
51
- `;if(t.raw){let e=r.map(e=>JSON.stringify(e));return`${c}${a}
69
+ `),process.exit(+!n.flags.help));let{command:s,positionals:i,flags:u}=n,p=u.session??process.env.AGENT_DEVICE_SESSION??"default",f=u.verbose&&!u.json?function(){try{let t=e.join(a.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"),a=Buffer.alloc(e.size-r);o.readSync(s,a,0,a.length,r),o.closeSync(s),r=e.size,a.length>0&&process.stdout.write(a.toString("utf8"))},200);return()=>{n=!0,clearInterval(s)}}catch{return null}}():null;try{if("session"===s){let e=i[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)}
70
+ `),f&&f();return}let e=await m({session:p,command:s,positionals:i,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,a=[];s&&a.push(`Page: ${s}`),o&&a.push(`App: ${o}`);let i=`Snapshot: ${r.length} nodes${n?" (truncated)":""}`,l=a.length>0?`${a.join("\n")}
71
+ `:"";if(!Array.isArray(r)||0===r.length)return`${l}${i}
72
+ `;if(t.raw){let e=r.map(e=>JSON.stringify(e));return`${l}${i}
52
73
  ${e.join("\n")}
53
- `}let l=[],u=[];for(let e of r){let t=e.depth??0;for(;l.length>0&&t<=l[l.length-1];)l.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&&l.push(t);let o=s?t:Math.max(0,t-l.length),i=" ".repeat(o),a=e.ref?`@${e.ref}`:"",c=[!1===e.enabled?"disabled":null].filter(Boolean).join(", "),d=c?` [${c}]`:"",p=r?` "${r}"`:"";if(s){u.push(`${i}${a} [${n}]${d}`.trimEnd());continue}u.push(`${i}${a} [${n}]${p}${d}`.trimEnd())}return`${c}${a}
54
- ${u.join("\n")}
55
- `}(e.data??{},{raw:d.snapshotRaw})),f&&f();return}if("get"===s){let t=a[0];if("text"===t){let t=e.data?.text??"";process.stdout.write(`${t}
74
+ `}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),a=" ".repeat(o),i=e.ref?`@${e.ref}`:"",l=[!1===e.enabled?"disabled":null].filter(Boolean).join(", "),u=l?` [${l}]`:"",p=r?` "${r}"`:"";if(s){d.push(`${a}${i} [${n}]${u}`.trimEnd());continue}d.push(`${a}${i} [${n}]${p}${u}`.trimEnd())}return`${l}${i}
75
+ ${d.join("\n")}
76
+ `}(e.data??{},{raw:u.snapshotRaw})),f&&f();return}if("get"===s){let t=i[0];if("text"===t){let t=e.data?.text??"";process.stdout.write(`${t}
56
77
  `),f&&f();return}if("attrs"===t){let t=e.data?.node??{};process.stdout.write(`${JSON.stringify(t,null,2)}
78
+ `),f&&f();return}}if("find"===s){let t=e.data;if("string"==typeof t?.text){process.stdout.write(`${t.text}
79
+ `),f&&f();return}if("boolean"==typeof t?.found){process.stdout.write(`Found: ${t.found}
80
+ `),f&&f();return}if(t?.node){process.stdout.write(`${JSON.stringify(t.node,null,2)}
57
81
  `),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})
58
- `),f&&f();return}f&&f();return}throw new c(e.error.code,e.error.message,e.error.details)}catch(t){let e=r(t);if(d.json)l({success:!1,error:{code:e.code,message:e.message,details:e.details}});else if(u(e),d.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(`
82
+ `),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")}
83
+ `),f&&f();return}if("apps"===s){let e=(Array.isArray(t.apps)?t.apps:[]).map(e=>{if("string"==typeof e)return e;if(e&&"object"==typeof e){let t=e.bundleId??e.package,r=e.name??e.label;return r&&t?`${r} (${t})`:t&&"boolean"==typeof e.launchable?`${t} (launchable=${e.launchable})`:t?String(t):JSON.stringify(e)}return String(e)});process.stdout.write(`${e.join("\n")}
84
+ `),f&&f();return}if("appstate"===s){let e=t?.platform,r=t?.appBundleId,n=t?.appName,s=t?.source,o=t?.package,a=t?.activity;if("ios"===e){process.stdout.write(`Foreground app: ${n??r}
85
+ `),r&&process.stdout.write(`Bundle: ${r}
86
+ `),s&&process.stdout.write(`Source: ${s}
87
+ `),f&&f();return}if("android"===e){process.stdout.write(`Foreground app: ${o??"unknown"}
88
+ `),a&&process.stdout.write(`Activity: ${a}
89
+ `),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(`
59
90
  [daemon log]
60
91
  ${n}
61
- `)}}catch{}f&&f(),process.exit(1)}}n(process.argv[1]??"").href===import.meta.url&&S(process.argv.slice(2)).catch(e=>{u(r(e)),process.exit(1)}),S(process.argv.slice(2));
92
+ `)}}catch{}f&&f(),process.exit(1)}}n(process.argv[1]??"").href===import.meta.url&&$(process.argv.slice(2)).catch(e=>{d(r(e)),process.exit(1)}),$(process.argv.slice(2));
@@ -1,5 +1,9 @@
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}}async function N(e,t=6e4){let n=Date.now();for(;Date.now()-n<t;){if(await y(e))return;await new Promise(e=>setTimeout(e,1e3))}throw new p("COMMAND_FAILED","Android device did not finish booting in time",{serial:e,timeoutMs:t})}let v={settings:{type:"intent",value:"android.settings.SETTINGS"}};function b(e,t){return["-s",e.id,...t]}async function S(e,t){let n=t.trim();if(n.includes("."))return{type:"package",value:n};let i=v[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 A(e,t){e.booted||await N(e.id);let n=await S(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 I(e){e.booted||await N(e.id)}async function D(e,t){if("settings"===t.trim().toLowerCase())return void await h("adb",b(e,["shell","am","force-stop","com.android.settings"]));let n=await S(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 O(e,t,n){await h("adb",b(e,["shell","input","tap",String(t),String(n)]))}async function x(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 E(e,t){let n=t.replace(/ /g,"%s");await h("adb",b(e,["shell","input","text",n]))}async function _(e,t,n){await O(e,t,n)}async function k(e,t,n,i){await _(e,t,n),await E(e,i)}async function C(e,t,n=.6){let{width:i,height:r}=await M(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 R(e,t){for(let n=0;n<8;n+=1){let n="";try{n=await F(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 C(e,"down",.5)}throw new p("COMMAND_FAILED",`Could not find element containing "${t}" after scrolling`)}async function P(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 T(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 F(e),800,t)}async function L(){if(!await m("adb"))throw new p("TOOL_MISSING","adb not found in PATH")}async function M(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 F(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 j(){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 U={settings:"com.apple.Preferences"};async function V(e,t){let n=t.trim();if(n.includes("."))return n;let i=U[n.toLowerCase()];if(i)return i;if("simulator"===e.kind){let i=(await Q(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 $(e,t){let n=await V(e,t);if("simulator"===e.kind){await ee(e),await h("open",["-a","Simulator"],{allowFailure:!0}),await h("xcrun",["simctl","launch",e.id,n]);return}await h("xcrun",["devicectl","device","process","launch","--device",e.id,n])}async function B(e){"simulator"!==e.kind||"Booted"!==await et(e.id)&&(await ee(e),await h("open",["-a","Simulator"],{allowFailure:!0}))}async function G(e,t){let n=await V(e,t);if("simulator"===e.kind){await ee(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 J(e,t,n){throw K(e,"press"),new p("UNSUPPORTED_OPERATION","simctl io tap is not available; use the XCTest runner for input")}async function W(e,t,n,i=800){throw K(e,"long-press"),new p("UNSUPPORTED_OPERATION","long-press is not supported on iOS simulators without XCTest runner support")}async function q(e,t,n){await J(e,t,n)}async function X(e,t){throw K(e,"type"),new p("UNSUPPORTED_OPERATION","simctl io keyboard is not available; use the XCTest runner for input")}async function z(e,t,n,i){await q(e,t,n),await X(e,i)}async function H(e,t,n=.6){throw K(e,"scroll"),new p("UNSUPPORTED_OPERATION","simctl io swipe is not available; use the XCTest runner for input")}async function Y(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 ee(e),await h("xcrun",["simctl","io",e.id,"screenshot",t]);return}await h("xcrun",["devicectl","device","screenshot","--device",e.id,t])}function K(e,t){if("simulator"!==e.kind)throw new p("UNSUPPORTED_OPERATION",`${t} is only supported on iOS simulators in v1`)}async function Q(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 ee(e){"simulator"!==e.kind||"Booted"!==await et(e.id)&&(await h("xcrun",["simctl","boot",e.id],{allowFailure:!0}),await h("xcrun",["simctl","bootstatus",e.id,"-b"],{allowFailure:!0}))}async function et(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 en=new Map;async function ei(e,t,n={}){if("simulator"!==e.kind)throw new p("UNSUPPORTED_OPERATION","iOS runner only supports simulators in v1");try{let i=await eo(e,n),r=await eu(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 er(e.id);let i=await eo(e,n),r=await eu(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 er(e){let t=en.get(e);if(t){try{await eu(t.device,t.port,{command:"shutdown"})}catch{}try{await t.testPromise}catch{}em(t.xctestrunPath),em(t.jsonPath),en.delete(e)}}async function ea(e){await h("xcrun",["simctl","bootstatus",e,"-b"],{allowFailure:!0})}async function eo(e,t){let n=en.get(e.id);if(n)return n;await ea(e.id);let i=await es(e.id,t),r=await ef(),a=process.env.AGENT_DEVICE_RUNNER_TIMEOUT??"300",{xctestrunPath:s,jsonPath:l}=await eh(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=>{ec(e,t.logPath,t.verbose)},onStderrChunk:e=>{ec(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 en.set(e.id,u),u}async function es(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=el(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=>{ec(e,t.logPath,t.verbose)},onStderrChunk:e=>{ec(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=el(r);if(!h)throw new p("COMMAND_FAILED","Failed to locate .xctestrun after build");return h}function el(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 ec(e,t,n){t&&u.appendFileSync(t,e),n&&process.stderr.write(e)}async function eu(e,t,n,i){i&&await ep(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 ed(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 ed(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 ef(){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 ep(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 eh(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 em(e){try{u.existsSync(e)&&u.unlinkSync(e)}catch{}}async function ew(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 eg(),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}))}}async function eg(){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 ey(e){let t={platform:e.platform,deviceName:e.device,udid:e.udid,serial:e.serial};if("android"===t.platform){await L();let e=await g();return await w(e,t)}if("ios"===t.platform){let e=await j();return await w(e,t)}let n=[];try{n.push(...await g())}catch{}try{n.push(...await j())}catch{}return await w(n,t)}async function eN(e,t,n,i,r){let a=function(e){switch(e.platform){case"android":return{open:t=>A(e,t),openDevice:()=>I(e),close:t=>D(e,t),tap:(t,n)=>O(e,t,n),longPress:(t,n,i)=>x(e,t,n,i),focus:(t,n)=>_(e,t,n),type:t=>E(e,t),fill:(t,n,i)=>k(e,t,n,i),scroll:(t,n)=>C(e,t,n),scrollIntoView:t=>R(e,t),screenshot:t=>P(e,t)};case"ios":return{open:t=>$(e,t),openDevice:()=>B(e),close:t=>G(e,t),tap:(t,n)=>J(e,t,n),longPress:(t,n,i)=>W(e,t,n,i),focus:(t,n)=>q(e,t,n),type:t=>X(e,t),fill:(t,n,i)=>z(e,t,n,i),scroll:(t,n)=>H(e,t,n),scrollIntoView:e=>Y(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)return await a.openDevice(),{app:null};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 ei(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 ei(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 ei(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 ei(e,{command:"tap",x:t,y:i,appBundleId:r?.appBundleId},{verbose:r?.verbose,logPath:r?.logPath}),await ei(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 ei(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)return{nodes:(await ew(e)).nodes??[],truncated:!1,backend:"ax"};let n=await ei(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 T(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 ev(e){let t=e.trim();return t.startsWith("@")?t.slice(1)||null:t.startsWith("e")?t:null}function eb(e,t){return e.find(e=>e.ref===t)??null}function eS(e){return{x:Math.round(e.x+e.width/2),y:Math.round(e.y+e.height/2)}}let eA=new Map,eI=a.join(d.homedir(),".agent-device"),eD=a.join(eI,"daemon.json"),eO=a.join(eI,"daemon.log"),ex=a.join(eI,"sessions"),eE=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"}}(),e_=n.randomBytes(24).toString("hex");function ek(e,t){return{appBundleId:t,verbose:e?.verbose,logPath:eO,snapshotInteractiveOnly:e?.snapshotInteractiveOnly,snapshotCompact:e?.snapshotCompact,snapshotDepth:e?.snapshotDepth,snapshotScope:e?.snapshotScope,snapshotRaw:e?.snapshotRaw,snapshotBackend:e?.snapshotBackend}}async function eC(e){if(e.token!==e_)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(eA.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;if(eA.has(n))return{ok:!1,error:{code:"INVALID_ARGS",message:"Session already active. Close it first or pass a new --session name."}};let r=await ey(e.flags??{});await ej(r);let a=Array.from(eA.values()).find(e=>e.device.id===r.id);if(a)return{ok:!1,error:{code:"DEVICE_IN_USE",message:`Device is already in use by session "${a.name}".`,details:{session:a.name,deviceId:r.id,deviceName:r.name}}};let o=e.positionals?.[0];if("ios"===r.platform)try{let{resolveIosApp:t}=await Promise.resolve().then(()=>({resolveIosApp:V}));i=await t(r,e.positionals?.[0]??"")}catch{i=void 0}await eN(r,"open",e.positionals??[],e.flags?.out,{...ek(e.flags,i)});let s={name:n,device:r,createdAt:Date.now(),appBundleId:i,appName:o,actions:[]};return eR(s,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{session:n}}),eA.set(n,s),{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 eC({token:e_,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=eA.get(n);return i?(e.positionals&&e.positionals.length>0&&await eN(i.device,"close",e.positionals??[],e.flags?.out,{...ek(e.flags,i.appBundleId)}),"ios"===i.device.platform&&"simulator"===i.device.kind&&await er(i.device.id),eR(i,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{session:n}}),eP(i),eA.delete(n),{ok:!0,data:{session:n}}):{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session"}}}if("snapshot"===t){let i=eA.get(n),r=i?.device??await ey(e.flags??{});i||await ej(r);let a=i?.appBundleId,o=await eN(r,"snapshot",[],e.flags?.out,{...ek(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 eR(c,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{nodes:s.length,truncated:o?.truncated??!1}}),eA.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=eA.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=ev(r);if(!a)return{ok:!1,error:{code:"INVALID_ARGS",message:"click requires a ref like @e2"}};let o=eb(i.snapshot.nodes,a);if(!o?.rect&&e.positionals.length>1){let t=e.positionals.slice(1).join(" ").trim();t.length>0&&(o=eL(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=eM(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 ei(i.device,{command:"tap",text:l,appBundleId:i.appBundleId},{verbose:e.flags?.verbose,logPath:eO}),eR(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}=eS(o.rect);return await eN(i.device,"press",[String(c),String(u)],e.flags?.out,{...ek(e.flags,i.appBundleId)}),eR(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=eA.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=ev(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=eb(i.snapshot.nodes,n);if(!o?.rect&&r&&(o=eL(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=eM(o,i.snapshot.nodes),{x:l,y:c}=eS(o.rect),u=await eN(i.device,"fill",[String(l),String(c),a],e.flags?.out,{...ek(e.flags,i.appBundleId)});return eR(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=eA.get(n);if(!a?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"No snapshot in session. Run snapshot first."}};let o=ev(r??"");if(!o)return{ok:!1,error:{code:"INVALID_ARGS",message:"get text requires a ref like @e2"}};let s=eb(a.snapshot.nodes,o);if(!s&&e.positionals.length>2){let t=e.positionals.slice(2).join(" ").trim();t.length>0&&(s=eL(a.snapshot.nodes,t))}if(!s)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${r} not found`}};if("attrs"===i)return eR(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 eR(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=eA.get(n);if(!i?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"No snapshot in session. Run snapshot first."}};let r=ev(e.positionals?.[0]??""),a="";if(r){let e=eb(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 ei(i.device,{command:"rect",text:a,appBundleId:i.appBundleId},{verbose:e.flags?.verbose,logPath:eO});return eR(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=eA.get(n);if(!r)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session. Run open first."}};let o=await eN(r.device,t,e.positionals??[],e.flags?.out,{...ek(e.flags,r.appBundleId)});return eR(r,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:o??{}}),{ok:!0,data:o??{}}}function eR(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 eP(e){try{u.existsSync(ex)||u.mkdirSync(ex,{recursive:!0});let t=e.name.replace(/[^a-zA-Z0-9._-]/g,"_"),n=new Date(e.createdAt).toISOString().replace(/[:.]/g,"-"),i=a.join(ex,`${t}-${n}.ad`),r=a.join(ex,`${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(eT(n));let i=e.result?.refLabel;return"string"==typeof i&&i.trim().length>0&&t.push(eT(i)),t.join(" ")}}if("fill"===e.command){let n=e.positionals?.[0];if(n&&n.startsWith("@")){t.push(eT(n));let i=e.result?.refLabel,r=e.positionals.slice(1).join(" ");return"string"==typeof i&&i.trim().length>0&&t.push(eT(i)),r&&t.push(eT(r)),t.join(" ")}}if("get"===e.command){let n=e.positionals?.[0],i=e.positionals?.[1];if(n&&i){t.push(eT(n)),t.push(eT(i));let r=e.result?.refLabel;return"string"==typeof r&&r.trim().length>0&&t.push(eT(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",eT(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(eT(n));return t.join(" ")}(a));return`${n.join("\n")}
2
- `}(e,o.optimizedActions);u.writeFileSync(i,s),e.actions.some(e=>e.flags?.recordJson)&&u.writeFileSync(r,JSON.stringify(o,null,2))}catch{}}function eT(e){let t=e.trim();return t.startsWith("@")||/^-?\d+(\.\d+)?$/.test(t)?t:JSON.stringify(t)}function eL(e,t){let n=t.toLowerCase();return e.find(e=>{let t=(e.label??"").toLowerCase(),i=(e.value??"").toLowerCase(),r=(e.identifier??"").toLowerCase();return t.includes(n)||i.includes(n)||r.includes(n)})??null}function eM(e,t){let n=[e.label,e.value,e.identifier].map(e=>"string"==typeof e?e.trim():"").find(e=>e&&e.length>0);return n&&eF(n)?n:function(e,t){if(!e.rect)return;let n=e.rect.y+e.rect.height/2,i=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||!eF(t))continue;let r=Math.abs(e.rect.y+e.rect.height/2-n);(!i||r<i.distance)&&(i={label:t,distance:r})}return i?.label}(e,t)??(n&&eF(n)?n:void 0)}function eF(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:ee}));await t(e);return}if("android"===e.platform){let{waitForAndroidBoot:t}=await Promise.resolve().then(()=>({waitForAndroidBoot:N}));await t(e.id)}}(e=f.createServer(e=>{let t="";e.setEncoding("utf8"),e.on("data",async n=>{let i=(t+=n).indexOf("\n");for(;-1!==i;){let n,r=t.slice(0,i).trim();if(t=t.slice(i+1),0===r.length){i=t.indexOf("\n");continue}try{let e=JSON.parse(r);n=await eC(e)}catch(t){let e=l(t);n={ok:!1,error:{code:e.code,message:e.message,details:e.details}}}e.write(`${JSON.stringify(n)}
3
- `),i=t.indexOf("\n")}})})).listen(0,"127.0.0.1",()=>{let t=e.address();if("object"==typeof t&&t?.port){var n;n=t.port,u.existsSync(eI)||u.mkdirSync(eI,{recursive:!0}),u.writeFileSync(eO,""),u.writeFileSync(eD,JSON.stringify({port:n,token:e_,pid:process.pid,version:eE},null,2),{mode:384}),process.stdout.write(`AGENT_DEVICE_DAEMON_PORT=${t.port}
4
- `)}}),t=async()=>{for(let e of Array.from(eA.values()))"ios"===e.device.platform&&"simulator"===e.device.kind&&await er(e.device.id),eP(e);e.close(()=>{u.existsSync(eD)&&u.unlinkSync(eD),process.exit(0)})},process.on("SIGINT",()=>{t()}),process.on("SIGTERM",()=>{t()}),process.on("SIGHUP",()=>{t()}),process.on("uncaughtException",e=>{let n=e instanceof p?e:l(e);process.stderr.write(`Daemon error: ${n.message}
1
+ let e,t;import a from"node:crypto";import{isCancel as i,select as n}from"@clack/prompts";import{node_path as r,runCmdStreaming as o,promises as s,asAppError as l,fileURLToPath as c,runCmdBackground as u,node_fs as d,node_os as p,errors_AppError as f,runCmd as h,node_net as m,whichCmd as w}from"./861.js";async function g(e,t){let a=e,r=e=>e.toLowerCase().replace(/_/g," ").replace(/\s+/g," ").trim();if(t.platform&&(a=a.filter(e=>e.platform===t.platform)),t.udid){let e=a.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=a.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=r(t.deviceName),i=a.find(t=>r(t.name)===e);if(!i)throw new f("DEVICE_NOT_FOUND",`No device named ${t.deviceName}`);return i}if(1===a.length)return a[0];if(0===a.length)throw new f("DEVICE_NOT_FOUND","No devices found",{selector:t});let o=a.filter(e=>e.booted);if(1===o.length)return o[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:(o.length>0?o:a).map(e=>({label:`${e.name} (${e.platform}${e.kind?`, ${e.kind}`:""}${e.booted?", booted":""})`,value:e.id}))});if(i(e))throw new f("INVALID_ARGS","Device selection cancelled");if(e){let t=a.find(t=>t.id===e);if(t)return t}}return o[0]??a[0]}async function y(){if(!await w("adb"))throw new f("TOOL_MISSING","adb not found in PATH");let e=(await h("adb",["devices","-l"])).stdout.split("\n").map(e=>e.trim()),t=[];for(let a of e){if(!a||a.startsWith("List of devices"))continue;let e=a.split(/\s+/),i=e[0];if("device"!==e[1])continue;let n=(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&&(n=t.replace(/_/g," "))}let r=await v(i);t.push({platform:"android",id:i,name:n,kind:i.startsWith("emulator-")?"emulator":"device",booted:r})}return t}async function v(e){try{let t=await h("adb",["-s",e,"shell","getprop","sys.boot_completed"],{allowFailure:!0});return"1"===t.stdout.trim()}catch{return!1}}async function I(e,t=6e4){let a=Date.now();for(;Date.now()-a<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})}async function N(e,t={}){let a,i=t.attempts??3,n=t.baseDelayMs??200,r=t.maxDelayMs??2e3,o=t.jitter??.2;for(let s=1;s<=i;s+=1)try{return await e()}catch(l){if(a=l,s>=i||t.shouldRetry&&!t.shouldRetry(l,s))break;let e=function(e,t,a,i){let n=Math.min(t,e*2**(i-1));return Math.max(0,n+n*a*(2*Math.random()-1))}(n,r,o,s);await function(e){return new Promise(t=>setTimeout(t,e))}(e)}if(a)throw a;throw new f("COMMAND_FAILED","retry failed")}let b={settings:{type:"intent",value:"android.settings.SETTINGS"}};function A(e,t){return["-s",e.id,...t]}async function S(e,t){let a=t.trim();if(a.includes("."))return{type:"package",value:a};let i=b[a.toLowerCase()];if(i)return i;let n=(await h("adb",A(e,["shell","pm","list","packages"]))).stdout.split("\n").map(e=>e.replace("package:","").trim()).filter(Boolean).filter(e=>e.toLowerCase().includes(a.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 D(e,t="launchable"){if("launchable"===t){let t=await h("adb",A(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 a of t.stdout.split("\n")){let t=a.trim();if(!t)continue;let i=t.split(/\s+/)[0],n=i.includes("/")?i.split("/")[0]:i;n&&e.add(n)}if(e.size>0)return Array.from(e)}}return(await h("adb",A(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 k(e,t="launchable"){let a=await D(e,t),i=new Set("launchable"===t?a:await D(e,"launchable"));return a.map(e=>({package:e,launchable:i.has(e)}))}async function P(e){let t=await O(e,[["shell","dumpsys","window","windows"],["shell","dumpsys","window"]]);if(t)return t;let a=await O(e,[["shell","dumpsys","activity","activities"],["shell","dumpsys","activity"]]);return a||{}}async function O(e,t){for(let a of t){let t=function(e){for(let t of[/mCurrentFocus=Window\{[^}]*\s([\w.]+)\/([\w.$]+)/,/mFocusedApp=AppWindowToken\{[^}]*\s([\w.]+)\/([\w.$]+)/,/mResumedActivity:.*?\s([\w.]+)\/([\w.$]+)/,/ResumedActivity:.*?\s([\w.]+)\/([\w.$]+)/]){let a=t.exec(e);if(a)return{package:a[1],activity:a[2]}}return null}((await h("adb",A(e,a),{allowFailure:!0})).stdout??"");if(t)return t}return null}async function _(e,t){e.booted||await I(e.id);let a=await S(e,t);"intent"===a.type?await h("adb",A(e,["shell","am","start","-a",a.value])):await h("adb",A(e,["shell","monkey","-p",a.value,"-c","android.intent.category.LAUNCHER","1"]))}async function x(e){e.booted||await I(e.id)}async function L(e,t){if("settings"===t.trim().toLowerCase())return void await h("adb",A(e,["shell","am","force-stop","com.android.settings"]));let a=await S(e,t);if("intent"===a.type)throw new f("INVALID_ARGS","Close requires a package name, not an intent");await h("adb",A(e,["shell","am","force-stop",a.value]))}async function E(e,t,a){await h("adb",A(e,["shell","input","tap",String(t),String(a)]))}async function R(e){await h("adb",A(e,["shell","input","keyevent","4"]))}async function C(e){await h("adb",A(e,["shell","input","keyevent","3"]))}async function M(e){await h("adb",A(e,["shell","input","keyevent","187"]))}async function T(e,t,a,i=800){await h("adb",A(e,["shell","input","swipe",String(t),String(a),String(t),String(a),String(i)]))}async function F(e,t){let a=t.replace(/ /g,"%s");await h("adb",A(e,["shell","input","text",a]))}async function B(e,t,a){await E(e,t,a)}async function $(e,t,a,i){await B(e,t,a),await F(e,i)}async function U(e,t,a=.6){let{width:i,height:n}=await W(e),r=Math.floor(i*a),o=Math.floor(n*a),s=Math.floor(i/2),l=Math.floor(n/2),c=s,u=l,d=s,p=l;switch(t){case"up":u=l-Math.floor(o/2),p=l+Math.floor(o/2);break;case"down":u=l+Math.floor(o/2),p=l-Math.floor(o/2);break;case"left":c=s-Math.floor(r/2),d=s+Math.floor(r/2);break;case"right":c=s+Math.floor(r/2),d=s-Math.floor(r/2);break;default:throw new f("INVALID_ARGS",`Unknown direction: ${t}`)}await h("adb",A(e,["shell","input","swipe",String(c),String(u),String(d),String(p),"300"]))}async function V(e,t){for(let a=0;a<8;a+=1){let a="";try{a=await X(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 a=t.toLowerCase(),i=/<node[^>]+>/g,n=i.exec(e);for(;n;){let t=n[0],r=/text="([^"]*)"/.exec(t),o=/content-desc="([^"]*)"/.exec(t),s=(r?.[1]??"").toLowerCase(),l=(o?.[1]??"").toLowerCase();if(s.includes(a)||l.includes(a)){let e=/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/.exec(t);if(e){let t=Number(e[1]),a=Number(e[2]);return{x:Math.floor((t+Number(e[3]))/2),y:Math.floor((a+Number(e[4]))/2)}}return{x:0,y:0}}n=i.exec(e)}return null}(a,t))return;await U(e,"down",.5)}throw new f("COMMAND_FAILED",`Could not find element containing "${t}" after scrolling`)}async function j(e,t){let a=await h("adb",A(e,["exec-out","screencap","-p"]),{binaryStdout:!0});if(!a.stdoutBuffer)throw new f("COMMAND_FAILED","Failed to capture screenshot");await s.writeFile(t,a.stdoutBuffer)}async function G(e,t,a){let i=t.toLowerCase(),n=function(e){let t=e.toLowerCase();if("on"===t||"true"===t||"1"===t)return!0;if("off"===t||"false"===t||"0"===t)return!1;throw new f("INVALID_ARGS",`Invalid setting state: ${e}`)}(a);switch(i){case"wifi":return void await h("adb",A(e,["shell","svc","wifi",n?"enable":"disable"]));case"airplane":await h("adb",A(e,["shell","settings","put","global","airplane_mode_on",n?"1":"0"])),await h("adb",A(e,["shell","am","broadcast","-a","android.intent.action.AIRPLANE_MODE","--ez","state",n?"true":"false"]));return;case"location":return void await h("adb",A(e,["shell","settings","put","secure","location_mode",n?"3":"0"]));default:throw new f("INVALID_ARGS",`Unsupported setting: ${t}`)}}async function q(e,t={}){return function(e,t,a){let i=function(e){let t={type:null,label:null,value:null,identifier:null,depth:-1,children:[]},a=[t],i=/<node\b[^>]*>|<\/node>/g,n=i.exec(e);for(;n;){let t=n[0];if(t.startsWith("</node")){a.length>1&&a.pop(),n=i.exec(e);continue}let r=function(e){let t=t=>{let a=RegExp(`${t}="([^"]*)"`).exec(e);return a?a[1]:null},a=e=>{let a=t(e);if(null!==a)return"true"===a};return{text:t("text"),desc:t("content-desc"),resourceId:t("resource-id"),className:t("class"),bounds:t("bounds"),clickable:a("clickable"),enabled:a("enabled"),focusable:a("focusable")}}(t),o=function(e){if(!e)return;let t=/\[(\d+),(\d+)\]\[(\d+),(\d+)\]/.exec(e);if(!t)return;let a=Number(t[1]),i=Number(t[2]);return{x:a,y:i,width:Math.max(0,Number(t[3])-a),height:Math.max(0,Number(t[4])-i)}}(r.bounds),s=a[a.length-1],l={type:r.className,label:r.text||r.desc,value:r.text,identifier:r.resourceId,rect:o,enabled:r.enabled,hittable:r.clickable??r.focusable,depth:s.depth+1,parentIndex:void 0,children:[]};s.children.push(l),t.endsWith("/>")||a.push(l),n=i.exec(e)}return t}(e),n=[],r=!1,o=a.depth??1/0,s=a.scope?function(e,t){let a=t.toLowerCase(),i=[...e.children];for(;i.length>0;){let e=i.shift(),t=e.label?.toLowerCase()??"",n=e.value?.toLowerCase()??"",r=e.identifier?.toLowerCase()??"";if(t.includes(a)||n.includes(a)||r.includes(a))return e;i.push(...e.children)}return null}(i,a.scope):null,l=s?[s]:i.children,c=(e,t)=>{if(n.length>=800){r=!0;return}if(!(t>o)){for(let i of((a.raw||function(e,t){if(t.interactiveOnly)return!!e.hittable;if(t.compact){let t=!!(e.label&&e.label.trim().length>0),a=!!(e.identifier&&e.identifier.trim().length>0);return t||a||!!e.hittable}return!0}(e,a))&&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(i,t+1),r)return}};for(let e of l)if(c(e,0),r)break;return r?{nodes:n,truncated:r}:{nodes:n}}(await X(e),800,t)}async function J(){if(!await w("adb"))throw new f("TOOL_MISSING","adb not found in PATH")}async function W(e){let t=(await h("adb",A(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 X(e){return N(()=>z(e),{shouldRetry:H})}async function z(e){return await h("adb",A(e,["shell","uiautomator","dump","/sdcard/window_dump.xml"])),(await h("adb",A(e,["shell","cat","/sdcard/window_dump.xml"]))).stdout}function H(e){if(!(e instanceof f)||"COMMAND_FAILED"!==e.code)return!1;let t=`${e.details?.stderr??""}`.toLowerCase();return!!(t.includes("device offline")||t.includes("device not found")||t.includes("transport error")||t.includes("connection reset")||t.includes("broken pipe")||t.includes("timed out"))}async function Y(){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 h("xcrun",["simctl","list","devices","-j"]);try{let a=JSON.parse(t.stdout);for(let t of Object.values(a.devices))for(let a of t)a.isAvailable&&e.push({platform:"ios",id:a.udid,name:a.name,kind:"simulator",booted:"Booted"===a.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 h("xcrun",["devicectl","list","devices","--json"]);for(let a of JSON.parse(t.stdout).devices??[])a.platform?.toLowerCase().includes("ios")&&e.push({platform:"ios",id:a.identifier,name:a.name,kind:"device",booted:!0})}catch{}return e}let Z={settings:"com.apple.Preferences"};async function K(e,t){let a=t.trim();if(a.includes("."))return a;let i=Z[a.toLowerCase()];if(i)return i;if("simulator"===e.kind){let i=(await ep(e)).filter(e=>e.name.toLowerCase()===a.toLowerCase());if(1===i.length)return i[0].bundleId;if(i.length>1)throw new f("INVALID_ARGS",`Multiple apps matched "${t}"`,{matches:i})}throw new f("APP_NOT_INSTALLED",`No app found matching "${t}"`)}async function Q(e,t){let a=await K(e,t);if("simulator"===e.kind){await ef(e),await h("open",["-a","Simulator"],{allowFailure:!0}),await h("xcrun",["simctl","launch",e.id,a]);return}await h("xcrun",["devicectl","device","process","launch","--device",e.id,a])}async function ee(e){"simulator"!==e.kind||"Booted"!==await eh(e.id)&&(await ef(e),await h("open",["-a","Simulator"],{allowFailure:!0}))}async function et(e,t){let a=await K(e,t);if("simulator"===e.kind){await ef(e);let t=await h("xcrun",["simctl","terminate",e.id,a],{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,a],stdout:t.stdout,stderr:t.stderr,exitCode:t.exitCode})}return}await h("xcrun",["devicectl","device","process","terminate","--device",e.id,a])}async function ea(e,t,a){throw ed(e,"press"),new f("UNSUPPORTED_OPERATION","simctl io tap is not available; use the XCTest runner for input")}async function ei(e,t,a,i=800){throw ed(e,"long-press"),new f("UNSUPPORTED_OPERATION","long-press is not supported on iOS simulators without XCTest runner support")}async function en(e,t,a){await ea(e,t,a)}async function er(e,t){throw ed(e,"type"),new f("UNSUPPORTED_OPERATION","simctl io keyboard is not available; use the XCTest runner for input")}async function eo(e,t,a,i){await en(e,t,a),await er(e,i)}async function es(e,t,a=.6){throw ed(e,"scroll"),new f("UNSUPPORTED_OPERATION","simctl io swipe is not available; use the XCTest runner for input")}async function el(e){throw new f("UNSUPPORTED_OPERATION",`scrollintoview is not supported on iOS without UI automation (${e})`)}async function ec(e,t){if("simulator"===e.kind){await ef(e),await h("xcrun",["simctl","io",e.id,"screenshot",t]);return}await h("xcrun",["devicectl","device","screenshot","--device",e.id,t])}async function eu(e,t,a,i){ed(e,"settings"),await ef(e);let n=t.toLowerCase(),r=function(e){let t=e.toLowerCase();if("on"===t||"true"===t||"1"===t)return!0;if("off"===t||"false"===t||"0"===t)return!1;throw new f("INVALID_ARGS",`Invalid setting state: ${e}`)}(a);switch(n){case"wifi":return void await h("xcrun",["simctl","status_bar",e.id,"override","--wifiMode",r?"active":"failed"]);case"airplane":r?await h("xcrun",["simctl","status_bar",e.id,"override","--dataNetwork","hide","--wifiMode","failed","--wifiBars","0","--cellularMode","failed","--cellularBars","0","--operatorName",""]):await h("xcrun",["simctl","status_bar",e.id,"clear"]);return;case"location":if(!i)throw new f("INVALID_ARGS","location setting requires an active app in session");await h("xcrun",["simctl","privacy",e.id,r?"grant":"revoke","location",i]);return;default:throw new f("INVALID_ARGS",`Unsupported setting: ${t}`)}}function ed(e,t){if("simulator"!==e.kind)throw new f("UNSUPPORTED_OPERATION",`${t} is only supported on iOS simulators in v1`)}async function ep(e){let t=(await h("xcrun",["simctl","listapps",e.id],{allowFailure:!0})).stdout.trim();if(!t)return[];let a=null;if(t.startsWith("{"))try{a=JSON.parse(t)}catch{a=null}if(!a&&t.startsWith("{"))try{let e=await h("plutil",["-convert","json","-o","-","-"],{allowFailure:!0,stdin:t});0===e.exitCode&&e.stdout.trim().startsWith("{")&&(a=JSON.parse(e.stdout))}catch{a=null}return a?Object.entries(a).map(([e,t])=>({bundleId:e,name:t.CFBundleDisplayName??t.CFBundleName??e})):[]}async function ef(e){"simulator"!==e.kind||"Booted"!==await eh(e.id)&&(await h("xcrun",["simctl","boot",e.id],{allowFailure:!0}),await h("xcrun",["simctl","bootstatus",e.id,"-b"],{allowFailure:!0}))}async function eh(e){let t=await h("xcrun",["simctl","list","devices","-j"],{allowFailure:!0});if(0!==t.exitCode)return null;try{let a=JSON.parse(t.stdout);for(let t of Object.values(a.devices??{})){let a=t.find(t=>t.udid===e);if(a)return a.state}}catch{}return null}let em=new Map;async function ew(e,t,a={}){var i;return"snapshot"===(i=t.command)||"findText"===i||"listTappables"===i||"alert"===i?N(()=>eg(e,t,a),{shouldRetry:eS}):eg(e,t,a)}async function eg(e,t,a={}){if("simulator"!==e.kind)throw new f("UNSUPPORTED_OPERATION","iOS runner only supports simulators in v1");try{let i=await eI(e,a),n=await eD(e,i.port,t,a.logPath),r=await n.text(),o={};try{o=JSON.parse(r)}catch{throw new f("COMMAND_FAILED","Invalid runner response",{text:r})}if(!o.ok)throw new f("COMMAND_FAILED",o.error?.message??"Runner error",{runner:o,xcodebuild:{exitCode:1,stdout:"",stderr:""},logPath:a.logPath});return o.data??{}}catch(n){let i=n instanceof f?n:new f("COMMAND_FAILED",String(n));if("COMMAND_FAILED"===i.code&&"string"==typeof i.message&&i.message.includes("Runner did not accept connection")){await ey(e.id);let i=await eI(e,a),n=await eD(e,i.port,t,a.logPath),r=await n.text(),o={};try{o=JSON.parse(r)}catch{throw new f("COMMAND_FAILED","Invalid runner response",{text:r})}if(!o.ok)throw new f("COMMAND_FAILED",o.error?.message??"Runner error",{runner:o,xcodebuild:{exitCode:1,stdout:"",stderr:""},logPath:a.logPath});return o.data??{}}throw n}}async function ey(e){let t=em.get(e);if(t){try{await eD(t.device,t.port,{command:"shutdown"})}catch{}try{await t.testPromise}catch{}ex(t.xctestrunPath),ex(t.jsonPath),em.delete(e)}}async function ev(e){await h("xcrun",["simctl","bootstatus",e,"-b"],{allowFailure:!0})}async function eI(e,t){let a=em.get(e.id);if(a)return a;await ev(e.id);let i=await eN(e.id,t),n=await eP(),r=process.env.AGENT_DEVICE_RUNNER_TIMEOUT??"300",{xctestrunPath:s,jsonPath:l}=await e_(i,{AGENT_DEVICE_RUNNER_PORT:String(n),AGENT_DEVICE_RUNNER_TIMEOUT:r},`session-${e.id}-${n}`),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=>{eA(e,t.logPath,t.traceLogPath,t.verbose)},onStderrChunk:e=>{eA(e,t.logPath,t.traceLogPath,t.verbose)},allowFailure:!0,env:{...process.env,AGENT_DEVICE_RUNNER_PORT:String(n),AGENT_DEVICE_RUNNER_TIMEOUT:r}}),u={device:e,deviceId:e.id,port:n,xctestrunPath:s,jsonPath:l,testPromise:c};return em.set(e.id,u),u}async function eN(e,t){let a,i=r.join(p.homedir(),".agent-device","ios-runner"),n=r.join(i,"derived");if((a=process.env.AGENT_DEVICE_IOS_CLEAN_DERIVED)&&["1","true","yes","on"].includes(a.toLowerCase()))try{d.rmSync(n,{recursive:!0,force:!0})}catch{}let s=eb(n);if(s)return s;let l=function(){let e=r.dirname(c(import.meta.url)),t=e;for(let e=0;e<6;e+=1){let e=r.join(t,"package.json");if(d.existsSync(e))return t;t=r.dirname(t)}return e}(),u=r.join(l,"ios-runner","AgentDeviceRunner","AgentDeviceRunner.xcodeproj");if(!d.existsSync(u))throw new f("COMMAND_FAILED","iOS runner project not found",{projectPath:u});try{await o("xcodebuild",["build-for-testing","-project",u,"-scheme","AgentDeviceRunner","-parallel-testing-enabled","NO","-maximum-concurrent-test-simulator-destinations","1","-destination",`platform=iOS Simulator,id=${e}`,"-derivedDataPath",n],{onStdoutChunk:e=>{eA(e,t.logPath,t.traceLogPath,t.verbose)},onStderrChunk:e=>{eA(e,t.logPath,t.traceLogPath,t.verbose)}})}catch(a){let e=a instanceof f?a:new f("COMMAND_FAILED",String(a));throw new f("COMMAND_FAILED","xcodebuild build-for-testing failed",{error:e.message,details:e.details,logPath:t.logPath})}let h=eb(n);if(!h)throw new f("COMMAND_FAILED","Failed to locate .xctestrun after build");return h}function eb(e){if(!d.existsSync(e))return null;let t=[],a=[e];for(;a.length>0;){let e=a.pop();for(let i of d.readdirSync(e,{withFileTypes:!0})){let n=r.join(e,i.name);if(i.isDirectory()){a.push(n);continue}if(i.isFile()&&i.name.endsWith(".xctestrun"))try{let e=d.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 eA(e,t,a,i){t&&d.appendFileSync(t,e),a&&d.appendFileSync(a,e),i&&process.stderr.write(e)}function eS(e){if(!(e instanceof f)||"COMMAND_FAILED"!==e.code)return!1;let t=`${e.message??""}`.toLowerCase();return!!(t.includes("runner did not accept connection")||t.includes("fetch failed")||t.includes("econnrefused")||t.includes("socket hang up"))}async function eD(e,t,a,i){i&&await eO(i,4e3);let n=Date.now(),r=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(a)})}catch(e){r=e,await new Promise(e=>setTimeout(e,100))}if("simulator"===e.kind){let i=await ek(e.id,t,a);return new Response(i.body,{status:i.status})}let o=i?function(e){try{if(!d.existsSync(e))return null;let t=d.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(a)})}catch(e){r=e}throw new f("COMMAND_FAILED","Runner did not accept connection",{port:t,fallbackPort:o,logPath:i,lastError:r?String(r):void 0})}async function ek(e,t,a){let i=JSON.stringify(a),n=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}),r=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:r}}async function eP(){return await new Promise((e,t)=>{let a=m.createServer();a.listen(0,"127.0.0.1",()=>{let i=a.address();a.close(),"object"==typeof i&&i?.port?e(i.port):t(new f("COMMAND_FAILED","Failed to allocate port"))}),a.on("error",t)})}async function eO(e,t){if(!d.existsSync(e))return;let a=Date.now(),i=0;for(;Date.now()-a<t;){if(!d.existsSync(e))return;let t=d.statSync(e);if(t.size>i){let a=d.openSync(e,"r"),n=Buffer.alloc(t.size-i);d.readSync(a,n,0,n.length,i),d.closeSync(a),i=t.size;let r=n.toString("utf8");if(r.includes("AGENT_DEVICE_RUNNER_LISTENER_READY")||r.includes("AGENT_DEVICE_RUNNER_PORT="))return}await new Promise(e=>setTimeout(e,100))}}async function e_(e,t,a){let i,n=r.dirname(e),o=a.replace(/[^a-zA-Z0-9._-]/g,"_"),s=r.join(n,`AgentDeviceRunner.env.${o}.json`),l=r.join(n,`AgentDeviceRunner.env.${o}.xctestrun`),c=await h("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{i=JSON.parse(c.stdout)}catch(t){throw new f("COMMAND_FAILED","Failed to parse xctestrun JSON",{xctestrunPath:e,error:String(t)})}let u=e=>{e.EnvironmentVariables={...e.EnvironmentVariables??{},...t},e.UITestEnvironmentVariables={...e.UITestEnvironmentVariables??{},...t},e.UITargetAppEnvironmentVariables={...e.UITargetAppEnvironmentVariables??{},...t},e.TestingEnvironmentVariables={...e.TestingEnvironmentVariables??{},...t}},p=i.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&&u(e)}for(let[e,t]of Object.entries(i))t&&"object"==typeof t&&t.TestBundlePath&&(u(t),i[e]=t);d.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 f("COMMAND_FAILED","Failed to write xctestrun plist",{tmpXctestrunPath:l,stderr:m.stderr});return{xctestrunPath:l,jsonPath:s}}function ex(e){try{d.existsSync(e)&&d.unlinkSync(e)}catch{}}async function eL(e,t={}){let a,i;if("ios"!==e.platform||"simulator"!==e.kind)throw new f("UNSUPPORTED_OPERATION","AX snapshot is only supported on iOS simulators");let n=await eE(),r=await N(async()=>{var e,a;let i,r,o,s=await h(n,[],{allowFailure:!0});if(t.traceLogPath&&(e=t.traceLogPath,i=((a=s).stdout??"").toString(),r=(a.stderr??"").toString(),o=`
2
+ [axsnapshot] exit=${a.exitCode} stdoutBytes=${i.length} stderrBytes=${r.length}
3
+ `,d.appendFileSync(e,o),(0!==a.exitCode||r.length>0)&&(r.length>0&&d.appendFileSync(e,`${r}
4
+ `),0!==a.exitCode&&i.length>0&&d.appendFileSync(e,`${i}
5
+ `))),0!==s.exitCode){let e,t,a=(s.stderr??"").toString(),i=(e=a.toLowerCase()).includes("accessibility permission")?" Enable Accessibility for your terminal in System Settings > Privacy & Security > Accessibility, or use --backend xctest (slower snapshots via XCTest).":e.includes("could not find ios app content")?" AX snapshot sometimes caches empty content. Try restarting the Simulator app.":"",n=!!((t=a.toLowerCase()).includes("could not find ios app content")||t.includes("timeout"));throw new f("COMMAND_FAILED","AX snapshot failed",{stderr:`${a}${i}`,stdout:s.stdout,retryable:n})}return s},{shouldRetry:e=>{var t;return(t=e)instanceof f&&"COMMAND_FAILED"===t.code&&t.details?.retryable===!0}});try{let e=JSON.parse(r.stdout);if(e&&"object"==typeof e&&"root"in e){if(!e.root)throw Error("AX snapshot missing root");a=e.root,i=e.windowFrame??void 0}else a=e}catch(e){throw new f("COMMAND_FAILED","Invalid AX snapshot JSON",{error:String(e)})}let o=a.frame??i,s=[],l=[],c=(e,t)=>{e.frame&&s.push(e.frame);let a=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 i of(l.push({...e,frame:a,children:void 0,depth:t}),e.children??[]))c(i,t+1)};return c(a,0),{nodes:(function(e,t,a){if(!t||0===a.length)return e;let i=1/0,n=1/0;for(let e of a)e.x<i&&(i=e.x),e.y<n&&(n=e.y);return i<=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})(l,o,s).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 eE(){let e=function(){let e=process.cwd();for(let t=0;t<6;t+=1){let t=r.join(e,"package.json");if(d.existsSync(t))return e;e=r.dirname(e)}return process.cwd()}(),t=r.join(e,"ios-runner","AXSnapshot"),a=process.env.AGENT_DEVICE_AX_BINARY;if(a&&d.existsSync(a))return a;let i=r.join(e,"dist","bin","axsnapshot");if(d.existsSync(i))return i;let n=r.join(t,".build","release","axsnapshot");if(d.existsSync(n))return n;let o=await h("swift",["build","-c","release"],{cwd:t,allowFailure:!0});if(0!==o.exitCode||!d.existsSync(n))throw new f("COMMAND_FAILED","Failed to build AX snapshot tool",{stderr:o.stderr,stdout:o.stdout});return n}async function eR(e){let t={platform:e.platform,deviceName:e.device,udid:e.udid,serial:e.serial};if("android"===t.platform){await J();let e=await y();return await g(e,t)}if("ios"===t.platform){let e=await Y();return await g(e,t)}let a=[];try{a.push(...await y())}catch{}try{a.push(...await Y())}catch{}return await g(a,t)}async function eC(e,t,a,i,n){let r=function(e){switch(e.platform){case"android":return{open:t=>_(e,t),openDevice:()=>x(e),close:t=>L(e,t),tap:(t,a)=>E(e,t,a),longPress:(t,a,i)=>T(e,t,a,i),focus:(t,a)=>B(e,t,a),type:t=>F(e,t),fill:(t,a,i)=>$(e,t,a,i),scroll:(t,a)=>U(e,t,a),scrollIntoView:t=>V(e,t),screenshot:t=>j(e,t)};case"ios":return{open:t=>Q(e,t),openDevice:()=>ee(e),close:t=>et(e,t),tap:(t,a)=>ea(e,t,a),longPress:(t,a,i)=>ei(e,t,a,i),focus:(t,a)=>en(e,t,a),type:t=>er(e,t),fill:(t,a,i)=>eo(e,t,a,i),scroll:(t,a)=>es(e,t,a),scrollIntoView:e=>el(e),screenshot:t=>ec(e,t)};default:throw new f("UNSUPPORTED_PLATFORM",`Unsupported platform: ${e.platform}`)}}(e);switch(t){case"open":{let e=a[0];if(!e)return await r.openDevice(),{app:null};return await r.open(e),{app:e}}case"close":{let e=a[0];if(!e)return{closed:"session"};return await r.close(e),{app:e}}case"press":{let[t,i]=a.map(Number);if(Number.isNaN(t)||Number.isNaN(i))throw new f("INVALID_ARGS","press requires x y");return"ios"===e.platform&&"simulator"===e.kind?await ew(e,{command:"tap",x:t,y:i,appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath,traceLogPath:n?.traceLogPath}):await r.tap(t,i),{x:t,y:i}}case"long-press":{let e=Number(a[0]),t=Number(a[1]),i=a[2]?Number(a[2]):void 0;if(Number.isNaN(e)||Number.isNaN(t))throw new f("INVALID_ARGS","long-press requires x y [durationMs]");return await r.longPress(e,t,i),{x:e,y:t,durationMs:i}}case"focus":{let[t,i]=a.map(Number);if(Number.isNaN(t)||Number.isNaN(i))throw new f("INVALID_ARGS","focus requires x y");return"ios"===e.platform&&"simulator"===e.kind?await ew(e,{command:"tap",x:t,y:i,appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath,traceLogPath:n?.traceLogPath}):await r.focus(t,i),{x:t,y:i}}case"type":{let t=a.join(" ");if(!t)throw new f("INVALID_ARGS","type requires text");return"ios"===e.platform&&"simulator"===e.kind?await ew(e,{command:"type",text:t,appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath,traceLogPath:n?.traceLogPath}):await r.type(t),{text:t}}case"fill":{let t=Number(a[0]),i=Number(a[1]),o=a.slice(2).join(" ");if(Number.isNaN(t)||Number.isNaN(i)||!o)throw new f("INVALID_ARGS","fill requires x y text");return"ios"===e.platform&&"simulator"===e.kind?(await ew(e,{command:"tap",x:t,y:i,appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath,traceLogPath:n?.traceLogPath}),await ew(e,{command:"type",text:o,appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath,traceLogPath:n?.traceLogPath})):await r.fill(t,i,o),{x:t,y:i,text:o}}case"scroll":{let t=a[0],i=a[1]?Number(a[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 a=function(e){switch(e){case"up":return"down";case"down":return"up";case"left":return"right";case"right":return"left"}}(t);await ew(e,{command:"swipe",direction:a,appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath,traceLogPath:n?.traceLogPath})}else await r.scroll(t,i);return{direction:t,amount:i}}case"scrollintoview":{let t=a.join(" ").trim();if(!t)throw new f("INVALID_ARGS","scrollintoview requires text");if("ios"===e.platform&&"simulator"===e.kind){for(let a=0;a<8;a+=1){let i=await ew(e,{command:"findText",text:t,appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath,traceLogPath:n?.traceLogPath});if(i?.found)return{text:t,attempts:a+1};await ew(e,{command:"swipe",direction:"up",appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath,traceLogPath:n?.traceLogPath}),await new Promise(e=>setTimeout(e,300))}throw new f("COMMAND_FAILED",`scrollintoview could not find text: ${t}`)}return await r.scrollIntoView(t),{text:t}}case"screenshot":{let e=i??`./screenshot-${Date.now()}.png`;return await r.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 ew(e,{command:"back",appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath,traceLogPath:n?.traceLogPath}),{action:"back"}}return await R(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 ew(e,{command:"home",appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath,traceLogPath:n?.traceLogPath}),{action:"home"}}return await C(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 ew(e,{command:"appSwitcher",appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath,traceLogPath:n?.traceLogPath}),{action:"app-switcher"}}return await M(e),{action:"app-switcher"};case"settings":{let[t,i,r]=a;if("ios"===e.platform)return await eu(e,t,i,r??n?.appBundleId),{setting:t,state:i};return await G(e,t,i),{setting:t,state:i}}case"snapshot":{let t=n?.snapshotBackend??"hybrid";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 eL(e,{traceLogPath:n?.traceLogPath})).nodes??[],truncated:!1,backend:"ax"};if("hybrid"===t){let t=(await eL(e,{traceLogPath:n?.traceLogPath})).nodes??[],a=function(e){let t=[];for(let a=0;a<e.length;a+=1){let i=e[a],n=i.depth??0;if((e[a+1]?.depth??-1)>n)continue;let r=eF(i.type);eM.has(r)&&t.push({index:a,depth:n,label:i.label,identifier:i.identifier,type:i.type})}return t}(t);if(0===a.length)return{nodes:t,truncated:!1,backend:"hybrid"};let i=await eT(e,t,a,{appBundleId:n?.appBundleId,interactiveOnly:n?.snapshotInteractiveOnly,compact:n?.snapshotCompact,depth:n?.snapshotDepth,raw:n?.snapshotRaw,verbose:n?.verbose,logPath:n?.logPath,traceLogPath:n?.traceLogPath});return{nodes:i.nodes,truncated:i.truncated,backend:"hybrid"}}let a=await ew(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,traceLogPath:n?.traceLogPath});return{nodes:a.nodes??[],truncated:a.truncated??!1,backend:"xctest"}}let a=await q(e,{interactiveOnly:n?.snapshotInteractiveOnly,compact:n?.snapshotCompact,depth:n?.snapshotDepth,scope:n?.snapshotScope,raw:n?.snapshotRaw});return{nodes:a.nodes??[],truncated:a.truncated??!1,backend:"android"}}default:throw new f("INVALID_ARGS",`Unknown command: ${t}`)}}let eM=new Set(["tabbar","toolbar","group"]);async function eT(e,t,a,i){let n=[...t],r=!1,o=0;for(let t of a){let a=function(e){for(let t of[e.label,e.identifier]){if(!t)continue;let e=t.trim();if(e)return e}return null}(t);if(!a)continue;let s=await ew(e,{command:"snapshot",appBundleId:i.appBundleId,interactiveOnly:i.interactiveOnly,compact:i.compact,depth:i.depth,scope:a,raw:i.raw},{verbose:i.verbose,logPath:i.logPath,traceLogPath:i.traceLogPath});s.truncated&&(r=!0);let l=(s.nodes??[]).filter(e=>{let t=eF(e.type);return"application"!==t&&"window"!==t});if(0===l.length)continue;let c=function(e,t){let a=1/0;for(let t of e){let e=t.depth??0;e<a&&(a=e)}return Number.isFinite(a)||(a=0),e.map(e=>({...e,depth:t+(e.depth??0)-a}))}(l,t.depth+1);n.splice(t.index+1+o,0,...c),o+=c.length}return{nodes:n=n.map((e,t)=>({...e,index:t})),truncated:r}}function eF(e){if(!e)return"";let t=e.replace(/XCUIElementType/gi,"").toLowerCase();return t.startsWith("ax")&&(t=t.replace(/^ax/,"")),t}function eB(e){return e.map((e,t)=>({...e,ref:`e${t+1}`}))}function e$(e){let t=e.trim();return t.startsWith("@")?t.slice(1)||null:t.startsWith("e")?t:null}function eU(e,t){return e.find(e=>e.ref===t)??null}function eV(e){return{x:Math.round(e.x+e.width/2),y:Math.round(e.y+e.height/2)}}function ej(e,t,a,i={}){let n=eq(a);if(!n)return null;let r=null;for(let a of e){if(i.requireRect&&!a.rect)continue;let e=function(e,t,a){switch(t){case"role":return function(e,t){let a=function(e){let t=e.trim();return t?((t=(t.split(".").pop()??t).replace(/XCUIElementType/gi,"").toLowerCase()).startsWith("ax")&&(t=t.replace(/^ax/,"")),t):""}(e??"");return a?a===t?2:+!!a.includes(t):0}(e.type,a);case"label":return eG(e.label,a);case"value":return eG(e.value,a);case"id":return eG(e.identifier,a);default:return Math.max(eG(e.label,a),eG(e.value,a),eG(e.identifier,a))}}(a,t,n);if(!(e<=0)&&(!r||e>r.score)&&(r={node:a,score:e},e>=2))break}return r?.node??null}function eG(e,t){let a=eq(e??"");return a?a===t?2:+!!a.includes(t):0}function eq(e){return e.trim().toLowerCase().replace(/\s+/g," ")}let eJ=new Map,eW=r.join(p.homedir(),".agent-device"),eX=r.join(eW,"daemon.json"),ez=r.join(eW,"daemon.log"),eH=r.join(eW,"sessions"),eY=function(){try{let e=function(){let e=r.dirname(c(import.meta.url)),t=e;for(let e=0;e<6;e+=1){let e=r.join(t,"package.json");if(d.existsSync(e))return t;t=r.dirname(t)}return e}();return JSON.parse(d.readFileSync(r.join(e,"package.json"),"utf8")).version??"0.0.0"}catch{return"0.0.0"}}(),eZ=a.randomBytes(24).toString("hex");function eK(e,t,a){return{appBundleId:t,verbose:e?.verbose,logPath:ez,traceLogPath:a,snapshotInteractiveOnly:e?.snapshotInteractiveOnly,snapshotCompact:e?.snapshotCompact,snapshotDepth:e?.snapshotDepth,snapshotScope:e?.snapshotScope,snapshotRaw:e?.snapshotRaw,snapshotBackend:e?.snapshotBackend}}async function eQ(e){if(e.token!==eZ)return{ok:!1,error:{code:"UNAUTHORIZED",message:"Invalid token"}};let t=e.command,a=e.session||"default";if("session_list"===t)return{ok:!0,data:{sessions:Array.from(eJ.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:Y}));t.push(...await e())}else{let{listAndroidDevices:e}=await Promise.resolve().then(()=>({listAndroidDevices:y})),{listIosDevices:a}=await Promise.resolve().then(()=>({listIosDevices:Y}));try{t.push(...await e())}catch{}try{t.push(...await a())}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=eJ.get(a),i=e.flags??{};if(!t&&!i.platform&&!i.device&&!i.udid&&!i.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 eR(i);if(await e9(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:t}=await Promise.resolve().then(()=>({listSimulatorApps:ep})),a=await t(n);return e.flags?.appsMetadata?{ok:!0,data:{apps:a}}:{ok:!0,data:{apps:a.map(e=>e.name&&e.name!==e.bundleId?`${e.name} (${e.bundleId})`:e.bundleId)}}}let{listAndroidApps:r,listAndroidAppsMetadata:o}=await Promise.resolve().then(()=>({listAndroidApps:D,listAndroidAppsMetadata:k}));return e.flags?.appsMetadata?{ok:!0,data:{apps:await o(n,e.flags?.appsFilter)}}:{ok:!0,data:{apps:await r(n,e.flags?.appsFilter)}}}if("appstate"===t){let t=eJ.get(a),i=e.flags??{},n=t?.device??await eR(i);if(await e9(n),"ios"===n.platform){if(t?.appBundleId)return{ok:!0,data:{platform:"ios",appBundleId:t.appBundleId,appName:t.appName??t.appBundleId,source:"session"}};let a=await e1(n,t?.trace?.outPath,e.flags);return{ok:!0,data:{platform:"ios",appName:a.appName,appBundleId:a.appBundleId,source:a.source}}}let{getAndroidAppState:r}=await Promise.resolve().then(()=>({getAndroidAppState:P})),o=await r(n);return{ok:!0,data:{platform:"android",package:o.package,activity:o.activity}}}if("open"===t){let i;if(eJ.has(a))return{ok:!1,error:{code:"INVALID_ARGS",message:"Session already active. Close it first or pass a new --session name."}};let n=await eR(e.flags??{});await e9(n);let r=Array.from(eJ.values()).find(e=>e.device.id===n.id);if(r)return{ok:!1,error:{code:"DEVICE_IN_USE",message:`Device is already in use by session "${r.name}".`,details:{session:r.name,deviceId:n.id,deviceName:n.name}}};let o=e.positionals?.[0];if("ios"===n.platform)try{let{resolveIosApp:t}=await Promise.resolve().then(()=>({resolveIosApp:K}));i=await t(n,e.positionals?.[0]??"")}catch{i=void 0}await eC(n,"open",e.positionals??[],e.flags?.out,{...eK(e.flags,i)});let s={name:a,device:n,createdAt:Date.now(),appBundleId:i,appName:o,actions:[]};return e0(s,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{session:a}}),eJ.set(a,s),{ok:!0,data:{session:a}}}if("replay"===t){let t=e.positionals?.[0];if(!t)return{ok:!1,error:{code:"INVALID_ARGS",message:"replay requires a path"}};try{let e=e8(t),i=JSON.parse(d.readFileSync(e,"utf8")),n=i.optimizedActions??i.actions??[];for(let e of n)e&&"replay"!==e.command&&await eQ({token:eZ,session:a,command:e.command,positionals:e.positionals??[],flags:e.flags??{}});return{ok:!0,data:{replayed:n.length,session:a}}}catch(t){let e=l(t);return{ok:!1,error:{code:e.code,message:e.message}}}}if("close"===t){let i=eJ.get(a);return i?(e.positionals&&e.positionals.length>0&&await eC(i.device,"close",e.positionals??[],e.flags?.out,{...eK(e.flags,i.appBundleId,i.trace?.outPath)}),"ios"===i.device.platform&&"simulator"===i.device.kind&&await ey(i.device.id),e0(i,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{session:a}}),e3(i),eJ.delete(a),{ok:!0,data:{session:a}}):{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session"}}}if("snapshot"===t){let i=eJ.get(a),n=i?.device??await eR(e.flags??{});i||await e9(n);let r=i?.appBundleId,o=e.flags?.snapshotScope;if(o&&o.trim().startsWith("@")){if(!i?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"Ref scope requires an existing snapshot in session."}};let e=e$(o.trim());if(!e)return{ok:!1,error:{code:"INVALID_ARGS",message:`Invalid ref scope: ${o}`}};let t=eU(i.snapshot.nodes,e),a=t?e7(t,i.snapshot.nodes):void 0;if(!a)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${o} not found or has no label`}};o=a}let s=await eC(n,"snapshot",[],e.flags?.out,{...eK({...e.flags,snapshotScope:o},r,i?.trace?.outPath)}),l=s?.nodes??[],c=eB(e.flags?.snapshotRaw?l:te(l)),u={nodes:c,truncated:s?.truncated,createdAt:Date.now(),backend:s?.backend},d={name:a,device:n,createdAt:i?.createdAt??Date.now(),appBundleId:i?.appBundleId??r,snapshot:u,actions:i?.actions??[],appName:i?.appName};return e0(d,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{nodes:c.length,truncated:s?.truncated??!1}}),eJ.set(a,d),{ok:!0,data:{nodes:c,truncated:s?.truncated??!1,appName:d.appBundleId?d.appName??d.appBundleId:void 0,appBundleId:d.appBundleId}}}if("wait"===t){let i=eJ.get(a),n=i?.device??await eR(e.flags??{});i||await e9(n);let r=e.positionals??[];if(0===r.length)return{ok:!1,error:{code:"INVALID_ARGS",message:"wait requires a duration or text"}};let o=e=>{if(!e)return null;let t=Number(e);return Number.isFinite(t)?t:null},s=o(r[0]);if(null!==s)return await new Promise(e=>setTimeout(e,s)),i&&e0(i,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{waitedMs:s}}),{ok:!0,data:{waitedMs:s}};let l="",c=null;if("text"===r[0])l=null!==(c=o(r[r.length-1]))?r.slice(1,-1).join(" "):r.slice(1).join(" ");else if(r[0].startsWith("@")){if(!i?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"Ref wait requires an existing snapshot in session."}};let e=e$(r[0]);if(!e)return{ok:!1,error:{code:"INVALID_ARGS",message:`Invalid ref: ${r[0]}`}};let t=eU(i.snapshot.nodes,e),a=t?e7(t,i.snapshot.nodes):void 0;if(!a)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${r[0]} not found or has no label`}};c=o(r[r.length-1]),l=a}else l=null!==(c=o(r[r.length-1]))?r.slice(0,-1).join(" "):r.join(" ");if(!(l=l.trim()))return{ok:!1,error:{code:"INVALID_ARGS",message:"wait requires text"}};let u=c??1e4,d=Date.now();for(;Date.now()-d<u;){if("ios"===n.platform&&"simulator"===n.kind){let a=await ew(n,{command:"findText",text:l,appBundleId:i?.appBundleId},{verbose:e.flags?.verbose,logPath:ez,traceLogPath:i?.trace?.outPath});if(a?.found)return i&&e0(i,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{text:l,waitedMs:Date.now()-d}}),{ok:!0,data:{text:l,waitedMs:Date.now()-d}}}else if("android"!==n.platform)return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"wait is not supported on this device"}};else if(e6(eB((await q(n,{scope:l})).nodes??[]),l))return i&&e0(i,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{text:l,waitedMs:Date.now()-d}}),{ok:!0,data:{text:l,waitedMs:Date.now()-d}};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 i=eJ.get(a),n=i?.device??await eR(e.flags??{});i||await e9(n);let r=(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"===r){let a=(e=>{if(!e)return null;let t=Number(e);return Number.isFinite(t)?t:null})(e.positionals?.[1])??1e4,r=Date.now();for(;Date.now()-r<a;){try{let a=await ew(n,{command:"alert",action:"get",appBundleId:i?.appBundleId},{verbose:e.flags?.verbose,logPath:ez,traceLogPath:i?.trace?.outPath});return i&&e0(i,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:a}),{ok:!0,data:a}}catch{}await new Promise(e=>setTimeout(e,300))}return{ok:!1,error:{code:"COMMAND_FAILED",message:"alert wait timed out"}}}let o=await ew(n,{command:"alert",action:"accept"===r||"dismiss"===r?r:"get",appBundleId:i?.appBundleId},{verbose:e.flags?.verbose,logPath:ez,traceLogPath:i?.trace?.outPath});return i&&e0(i,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:o}),{ok:!0,data:o}}if("record"===t){let i=(e.positionals?.[0]??"").toLowerCase();if(!["start","stop"].includes(i))return{ok:!1,error:{code:"INVALID_ARGS",message:"record requires start|stop"}};let n=eJ.get(a),o=n?.device??await eR(e.flags??{});n||await e9(o);let s=n??{name:a,device:o,createdAt:Date.now(),actions:[]};if("start"===i){if(s.recording)return{ok:!1,error:{code:"INVALID_ARGS",message:"recording already in progress"}};let i=e.positionals?.[1]??`./recording-${Date.now()}.mp4`,n=r.resolve(i),l=r.dirname(n);if(d.existsSync(l)||d.mkdirSync(l,{recursive:!0}),"ios"===o.platform){if("simulator"!==o.kind)return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"record is only supported on iOS simulators in v1"}};let{child:e,wait:t}=u("xcrun",["simctl","io",o.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:a}=u("adb",["-s",o.id,"shell","screenrecord",e],{allowFailure:!0});s.recording={platform:"android",outPath:n,remotePath:e,child:t,wait:a}}return eJ.set(a,s),e0(s,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{action:"start"}}),{ok:!0,data:{recording:"started",outPath:i}}}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 h("adb",["-s",o.id,"pull",l.remotePath,l.outPath],{allowFailure:!0}),await h("adb",["-s",o.id,"shell","rm","-f",l.remotePath],{allowFailure:!0})}catch{}return s.recording=void 0,e0(s,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{action:"stop",outPath:l.outPath}}),{ok:!0,data:{recording:"stopped",outPath:l.outPath}}}if("trace"===t){let i=(e.positionals?.[0]??"").toLowerCase();if(!["start","stop"].includes(i))return{ok:!1,error:{code:"INVALID_ARGS",message:"trace requires start|stop"}};let n=eJ.get(a);if(!n)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session"}};if("start"===i){let a,i;if(n.trace)return{ok:!1,error:{code:"INVALID_ARGS",message:"trace already in progress"}};let o=e8(e.positionals?.[1]??(a=n.name.replace(/[^a-zA-Z0-9._-]/g,"_"),i=new Date().toISOString().replace(/[:.]/g,"-"),r.join(eH,`${a}-${i}.trace.log`)));return d.mkdirSync(r.dirname(o),{recursive:!0}),d.appendFileSync(o,""),n.trace={outPath:o,startedAt:Date.now()},e0(n,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{action:"start",outPath:o}}),{ok:!0,data:{trace:"started",outPath:o}}}if(!n.trace)return{ok:!1,error:{code:"INVALID_ARGS",message:"no active trace"}};let o=n.trace.outPath;if(e.positionals?.[1]){let t=e8(e.positionals[1]);d.mkdirSync(r.dirname(t),{recursive:!0}),d.existsSync(o)?d.renameSync(o,t):d.appendFileSync(t,""),o=t}return n.trace=void 0,e0(n,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{action:"stop",outPath:o}}),{ok:!0,data:{trace:"stopped",outPath:o}}}if("settings"===t){let i=e.positionals?.[0],n=e.positionals?.[1];if(!i||!n)return{ok:!1,error:{code:"INVALID_ARGS",message:"settings requires <wifi|airplane|location> <on|off>"}};let r=eJ.get(a),o=r?.device??await eR(e.flags??{});r||await e9(o);let s=r?.appBundleId,l=await eC(o,"settings",[i,n,s??""],e.flags?.out,{...eK(e.flags,s,r?.trace?.outPath)});return r&&e0(r,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:l??{setting:i,state:n}}),{ok:!0,data:l??{setting:i,state:n}}}if("find"===t){let n=e.positionals??[];if(0===n.length)return{ok:!1,error:{code:"INVALID_ARGS",message:"find requires a locator or text"}};let{locator:r,query:o,action:s,value:l,timeoutMs:c}=function(e){let t="any",a=0;["text","label","value","role","id"].includes(e[0])&&(t=e[0],a=1);let i=e[a]??"",n=e.slice(a+1);if(0===n.length)return{locator:t,query:i,action:"click"};let r=n[0].toLowerCase();if("get"===r){let e=n[1]?.toLowerCase();if("text"===e)return{locator:t,query:i,action:"get_text"};if("attrs"===e)return{locator:t,query:i,action:"get_attrs"};throw new f("INVALID_ARGS","find get only supports text or attrs")}if("wait"===r)return{locator:t,query:i,action:"wait",timeoutMs:function(e){if(!e)return null;let t=Number(e);return Number.isFinite(t)?t:null}(n[1])??void 0};if("exists"===r)return{locator:t,query:i,action:"exists"};if("click"===r)return{locator:t,query:i,action:"click"};if("focus"===r)return{locator:t,query:i,action:"focus"};if("fill"===r)return{locator:t,query:i,action:"fill",value:n.slice(1).join(" ")};if("type"===r)return{locator:t,query:i,action:"type",value:n.slice(1).join(" ")};throw new f("INVALID_ARGS",`Unsupported find action: ${n[0]}`)}(n);if(!o)return{ok:!1,error:{code:"INVALID_ARGS",message:"find requires a value"}};let u=eJ.get(a);if(!u&&"exists"!==s&&"wait"!==s&&"get_text"!==s&&"get_attrs"!==s)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session. Run open first."}};let d=u?.device??await eR(e.flags??{});u||await e9(d);let p=u?.appBundleId,h="role"!==r?o:void 0,m="click"===s||"focus"===s||"fill"===s||"type"===s,w=0,g=null,y=async()=>{let t=Date.now();if(g&&t-w<750)return{nodes:g};let i=await eC(d,"snapshot",[],e.flags?.out,{...eK({...e.flags,snapshotScope:h,snapshotInteractiveOnly:m,snapshotCompact:m},p,u?.trace?.outPath)}),n=i?.nodes??[],r=eB(e.flags?.snapshotRaw?n:te(n));return w=t,g=r,u&&(u.snapshot={nodes:r,truncated:i?.truncated,createdAt:Date.now(),backend:i?.backend},eJ.set(a,u)),{nodes:r,truncated:i?.truncated,backend:i?.backend}};if("wait"===s){let a=c??1e4,i=Date.now();for(;Date.now()-i<a;){let{nodes:a}=await y();if(ej(a,r,o,{requireRect:!1}))return u&&e0(u,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{found:!0,waitedMs:Date.now()-i}}),{ok:!0,data:{found:!0,waitedMs:Date.now()-i}};await new Promise(e=>setTimeout(e,300))}return{ok:!1,error:{code:"COMMAND_FAILED",message:"find wait timed out"}}}let{nodes:v}=await y(),I=ej(v,r,o,{requireRect:m});if(!I)return{ok:!1,error:{code:"COMMAND_FAILED",message:"find did not match any element"}};let N=`@${I.ref}`,b={...e.flags??{},noRecord:!0};if("exists"===s)return u&&e0(u,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{found:!0}}),{ok:!0,data:{found:!0}};if("get_text"===s){var i;let a=[(i=I).label,i.value,i.identifier].map(e=>"string"==typeof e?e.trim():"").filter(e=>e.length>0)[0]??"";return u&&e0(u,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{ref:N,action:"get text",text:a}}),{ok:!0,data:{ref:N,text:a,node:I}}}if("get_attrs"===s)return u&&e0(u,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{ref:N,action:"get attrs"}}),{ok:!0,data:{ref:N,node:I}};if("click"===s){let i=await eQ({token:eZ,session:a,command:"click",positionals:[N],flags:b});return i.ok&&u&&e0(u,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{ref:N,action:"click"}}),i}if("fill"===s){if(!l)return{ok:!1,error:{code:"INVALID_ARGS",message:"find fill requires text"}};let i=await eQ({token:eZ,session:a,command:"fill",positionals:[N,l],flags:b});return i.ok&&u&&e0(u,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{ref:N,action:"fill"}}),i}if("focus"===s){let a=I.rect?eV(I.rect):null;if(!a)return{ok:!1,error:{code:"COMMAND_FAILED",message:"matched element has no bounds"}};let i=await eC(d,"focus",[String(a.x),String(a.y)],e.flags?.out,{...eK(e.flags,u?.appBundleId,u?.trace?.outPath)});return u&&e0(u,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{ref:N,action:"focus"}}),{ok:!0,data:i??{ref:N}}}if("type"===s){if(!l)return{ok:!1,error:{code:"INVALID_ARGS",message:"find type requires text"}};let a=I.rect?eV(I.rect):null;if(!a)return{ok:!1,error:{code:"COMMAND_FAILED",message:"matched element has no bounds"}};await eC(d,"focus",[String(a.x),String(a.y)],e.flags?.out,{...eK(e.flags,u?.appBundleId,u?.trace?.outPath)});let i=await eC(d,"type",[l],e.flags?.out,{...eK(e.flags,u?.appBundleId,u?.trace?.outPath)});return u&&e0(u,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{ref:N,action:"type"}}),{ok:!0,data:i??{ref:N}}}}if("click"===t){let i=eJ.get(a);if(!i?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"No snapshot in session. Run snapshot first."}};let n=e.positionals?.[0]??"",r=e$(n);if(!r)return{ok:!1,error:{code:"INVALID_ARGS",message:"click requires a ref like @e2"}};let o=eU(i.snapshot.nodes,r);if(!o?.rect&&e.positionals.length>1){let t=e.positionals.slice(1).join(" ").trim();t.length>0&&(o=e6(i.snapshot.nodes,t))}if(!o?.rect)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${n} not found or has no bounds`}};let s=e7(o,i.snapshot.nodes),l=o.label?.trim();if("ios"===i.device.platform&&"simulator"===i.device.kind&&l&&function(e,t){let a=t.trim().toLowerCase();if(!a)return!1;let i=0;for(let t of e)if((t.label??"").trim().toLowerCase()===a&&(i+=1)>1)return!1;return 1===i}(i.snapshot.nodes,l))return await ew(i.device,{command:"tap",text:l,appBundleId:i.appBundleId},{verbose:e.flags?.verbose,logPath:ez,traceLogPath:i?.trace?.outPath}),e0(i,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{ref:r,refLabel:l,mode:"text"}}),{ok:!0,data:{ref:r,mode:"text"}};let{x:c,y:u}=eV(o.rect);return await eC(i.device,"press",[String(c),String(u)],e.flags?.out,{...eK(e.flags,i.appBundleId,i.trace?.outPath)}),e0(i,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{ref:r,x:c,y:u,refLabel:s}}),{ok:!0,data:{ref:r,x:c,y:u}}}if("fill"===t){let i=eJ.get(a);if(e.positionals?.[0]?.startsWith("@")){if(!i?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"No snapshot in session. Run snapshot first."}};let a=e$(e.positionals[0]);if(!a)return{ok:!1,error:{code:"INVALID_ARGS",message:"fill requires a ref like @e2"}};let n=e.positionals.length>=3?e.positionals[1]:"",r=e.positionals.length>=3?e.positionals.slice(2).join(" "):e.positionals.slice(1).join(" ");if(!r)return{ok:!1,error:{code:"INVALID_ARGS",message:"fill requires text after ref"}};let o=eU(i.snapshot.nodes,a);if(!o?.rect&&n&&(o=e6(i.snapshot.nodes,n)),!o?.rect)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${e.positionals[0]} not found or has no bounds`}};let s=e7(o,i.snapshot.nodes),{x:l,y:c}=eV(o.rect),u=await eC(i.device,"fill",[String(l),String(c),r],e.flags?.out,{...eK(e.flags,i.appBundleId,i.trace?.outPath)});return e0(i,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:u??{ref:a,x:l,y:c,refLabel:s}}),{ok:!0,data:u??{ref:a,x:l,y:c}}}}if("get"===t){let i=e.positionals?.[0],n=e.positionals?.[1];if("text"!==i&&"attrs"!==i)return{ok:!1,error:{code:"INVALID_ARGS",message:"get only supports text or attrs"}};let r=eJ.get(a);if(!r?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"No snapshot in session. Run snapshot first."}};let o=e$(n??"");if(!o)return{ok:!1,error:{code:"INVALID_ARGS",message:"get text requires a ref like @e2"}};let s=eU(r.snapshot.nodes,o);if(!s&&e.positionals.length>2){let t=e.positionals.slice(2).join(" ").trim();t.length>0&&(s=e6(r.snapshot.nodes,t))}if(!s)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${n} not found`}};if("attrs"===i)return e0(r,{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 e0(r,{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}}}let n=eJ.get(a);if(!n)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session. Run open first."}};let o=await eC(n.device,t,e.positionals??[],e.flags?.out,{...eK(e.flags,n.appBundleId,n.trace?.outPath)});return e0(n,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:o??{}}),{ok:!0,data:o??{}}}function e0(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:a,udid:i,serial:n,out:r,verbose:o,snapshotInteractiveOnly:s,snapshotCompact:l,snapshotDepth:c,snapshotScope:u,snapshotRaw:d,snapshotBackend:p,appsMetadata:f,noRecord:h,recordJson:m}=e;return{platform:t,device:a,udid:i,serial:n,out:r,verbose:o,snapshotInteractiveOnly:s,snapshotCompact:l,snapshotDepth:c,snapshotScope:u,snapshotRaw:d,snapshotBackend:p,appsMetadata:f,noRecord:h,recordJson:m}}(t.flags),result:t.result})}async function e1(e,t,a){let i=e2(await eC(e,"snapshot",[],a?.out,{...eK({...a,snapshotDepth:1,snapshotCompact:!0,snapshotBackend:"ax"},void 0,t)}));if(i?.appName||i?.appBundleId)return{appName:i.appName??i.appBundleId??"unknown",appBundleId:i.appBundleId,source:"snapshot-ax"};let n=e2(await eC(e,"snapshot",[],a?.out,{...eK({...a,snapshotDepth:1,snapshotCompact:!0,snapshotBackend:"xctest"},void 0,t)}));return{appName:n?.appName??n?.appBundleId??"unknown",appBundleId:n?.appBundleId,source:"snapshot-xctest"}}function e2(e){let t=eB(e?.nodes??[]),a=t.find(e=>"application"===tt(e.type??""))??t[0];if(!a)return null;let i=a.label?.trim(),n=a.identifier?.trim();return i||n?{appName:i||void 0,appBundleId:n||void 0}:null}function e3(e){try{d.existsSync(eH)||d.mkdirSync(eH,{recursive:!0});let t=e.name.replace(/[^a-zA-Z0-9._-]/g,"_"),a=new Date(e.createdAt).toISOString().replace(/[:.]/g,"-"),i=r.join(eH,`${t}-${a}.ad`),n=r.join(eH,`${t}-${a}.json`),o={name:e.name,device:e.device,createdAt:e.createdAt,appBundleId:e.appBundleId,actions:e.actions,optimizedActions:function(e){let t=[];for(let a of e.actions)if("snapshot"!==a.command){if("click"===a.command||"fill"===a.command||"get"===a.command){let i=a.result?.refLabel;"string"==typeof i&&i.trim().length>0&&t.push({ts:a.ts,command:"snapshot",positionals:[],flags:{platform:e.device.platform,snapshotInteractiveOnly:!0,snapshotCompact:!0,snapshotScope:i.trim()},result:{scope:i.trim()}})}t.push(a)}return t}(e)},s=function(e,t){let a=[],i=e.device.name.replace(/"/g,'\\"'),n=e.device.kind?` kind=${e.device.kind}`:"";for(let r of(a.push(`context platform=${e.device.platform} device="${i}"${n} theme=unknown`),t))r.flags?.noRecord||a.push(function(e){let t=[e.command];if("click"===e.command){let a=e.positionals?.[0];if(a){t.push(e4(a));let i=e.result?.refLabel;return"string"==typeof i&&i.trim().length>0&&t.push(e4(i)),t.join(" ")}}if("fill"===e.command){let a=e.positionals?.[0];if(a&&a.startsWith("@")){t.push(e4(a));let i=e.result?.refLabel,n=e.positionals.slice(1).join(" ");return"string"==typeof i&&i.trim().length>0&&t.push(e4(i)),n&&t.push(e4(n)),t.join(" ")}}if("get"===e.command){let a=e.positionals?.[0],i=e.positionals?.[1];if(a&&i){t.push(e4(a)),t.push(e4(i));let n=e.result?.refLabel;return"string"==typeof n&&n.trim().length>0&&t.push(e4(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",e4(e.flags.snapshotScope)),e.flags?.snapshotRaw&&t.push("--raw"),e.flags?.snapshotBackend&&t.push("--backend",e.flags.snapshotBackend),t.join(" ");for(let a of e.positionals??[])t.push(e4(a));return t.join(" ")}(r));return`${a.join("\n")}
6
+ `}(e,o.optimizedActions);d.writeFileSync(i,s),e.actions.some(e=>e.flags?.recordJson)&&d.writeFileSync(n,JSON.stringify(o,null,2))}catch{}}function e8(e){return e.startsWith("~/")?r.join(p.homedir(),e.slice(2)):r.resolve(e)}function e4(e){let t=e.trim();return t.startsWith("@")||/^-?\d+(\.\d+)?$/.test(t)?t:JSON.stringify(t)}function e6(e,t){let a=t.toLowerCase();return e.find(e=>{let t=(e.label??"").toLowerCase(),i=(e.value??"").toLowerCase(),n=(e.identifier??"").toLowerCase();return t.includes(a)||i.includes(a)||n.includes(a)})??null}function e7(e,t){let a=[e.label,e.value,e.identifier].map(e=>"string"==typeof e?e.trim():"").find(e=>e&&e.length>0);return a&&e5(a)?a:function(e,t){if(!e.rect)return;let a=e.rect.y+e.rect.height/2,i=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||!e5(t))continue;let n=Math.abs(e.rect.y+e.rect.height/2-a);(!i||n<i.distance)&&(i={label:t,distance:n})}return i?.label}(e,t)??(a&&e5(a)?a:void 0)}function e5(e){let t=e.trim();return!(!t||/^(true|false)$/i.test(t)||/^\d+$/.test(t))}async function e9(e){if("ios"===e.platform&&"simulator"===e.kind){let{ensureBootedSimulator:t}=await Promise.resolve().then(()=>({ensureBootedSimulator:ef}));await t(e);return}if("android"===e.platform){let{waitForAndroidBoot:t}=await Promise.resolve().then(()=>({waitForAndroidBoot:I}));await t(e.id)}}function te(e){let t=[],a=[];for(let i of e){let e=i.depth??0;for(;t.length>0&&e<=t[t.length-1];)t.pop();let n=tt(i.type??""),r=[i.label,i.value,i.identifier].map(e=>"string"==typeof e?e.trim():"").find(e=>e&&e.length>0),o=!!r&&e5(r);if(("group"===n||"ioscontentgroup"===n)&&!o){t.push(e);continue}let s=Math.max(0,e-t.length);a.push({...i,depth:s})}return a}function tt(e){let t=e.replace(/XCUIElementType/gi,"").toLowerCase();return t.startsWith("ax")&&(t=t.replace(/^ax/,"")),t}(e=m.createServer(e=>{let t="";e.setEncoding("utf8"),e.on("data",async a=>{let i=(t+=a).indexOf("\n");for(;-1!==i;){let a,n=t.slice(0,i).trim();if(t=t.slice(i+1),0===n.length){i=t.indexOf("\n");continue}try{let e=JSON.parse(n);a=await eQ(e)}catch(t){let e=l(t);a={ok:!1,error:{code:e.code,message:e.message,details:e.details}}}e.write(`${JSON.stringify(a)}
7
+ `),i=t.indexOf("\n")}})})).listen(0,"127.0.0.1",()=>{let t=e.address();if("object"==typeof t&&t?.port){var a;a=t.port,d.existsSync(eW)||d.mkdirSync(eW,{recursive:!0}),d.writeFileSync(ez,""),d.writeFileSync(eX,JSON.stringify({port:a,token:eZ,pid:process.pid,version:eY},null,2),{mode:384}),process.stdout.write(`AGENT_DEVICE_DAEMON_PORT=${t.port}
8
+ `)}}),t=async()=>{for(let e of Array.from(eJ.values()))"ios"===e.device.platform&&"simulator"===e.device.kind&&await ey(e.device.id),e3(e);e.close(()=>{d.existsSync(eX)&&d.unlinkSync(eX),process.exit(0)})},process.on("SIGINT",()=>{t()}),process.on("SIGTERM",()=>{t()}),process.on("SIGHUP",()=>{t()}),process.on("uncaughtException",e=>{let a=e instanceof f?e:l(e);process.stderr.write(`Daemon error: ${a.message}
5
9
  `),t()});