agent-device 0.1.8 → 0.2.0

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
@@ -1,10 +1,10 @@
1
1
  # agent-device
2
2
 
3
- CLI to control iOS and Android devices for AI agents.
3
+ CLI to control iOS and Android devices for AI agents influenced by Vercel’s [agent-browser](https://github.com/vercel/agent-browser).
4
4
 
5
- This project mirrors the spirit of `agent-browser`, but targets iOS simulators/devices and Android emulators/devices.
5
+ The project is in early development and considered experimental. Pull requests are welcome!
6
6
 
7
- ## Current scope (v1)
7
+ ## Features
8
8
  - Platforms: iOS (simulator + limited device support) and Android (emulator + device).
9
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).
@@ -23,40 +23,67 @@ Or use it without installing:
23
23
  npx agent-device open SampleApp
24
24
  ```
25
25
 
26
- ## Usage
26
+ ## Quick Start
27
+
28
+ ```bash
29
+ agent-device open Contacts --platform ios
30
+ agent-device snapshot -i -c --platform ios
31
+ agent-device click @e5 --platform ios
32
+ agent-device fill @e6 "John" --platform ios
33
+ agent-device fill @e7 "Doe" --platform ios
34
+ agent-device click @e3 --platform ios
35
+ agent-device close
36
+ ```
37
+
38
+ ## CLI Usage
27
39
 
28
40
  ```bash
29
41
  agent-device <command> [args] [--json]
30
42
  ```
31
43
 
32
- Examples:
44
+ Basic flow:
33
45
 
34
46
  ```bash
35
47
  agent-device open SampleApp
36
48
  agent-device snapshot
37
- agent-device snapshot -s @e7
38
49
  agent-device click @e7
39
- agent-device wait text "Camera"
40
- agent-device alert wait 10000
41
- agent-device back
42
- agent-device type "hello"
43
- agent-device screenshot --out ./screenshot.png
50
+ agent-device fill @e8 "hello"
44
51
  agent-device close SampleApp
45
52
  ```
46
53
 
47
- Best practice: run `snapshot` immediately before interactions to avoid stale coordinates if the Simulator window moves or UI changes.
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.
54
+ Debug flow:
55
+
56
+ ```bash
57
+ agent-device trace start
58
+ agent-device snapshot --backend xctest -s "Sample App"
59
+ agent-device find label "Wi-Fi" click
60
+ agent-device trace stop ./trace.log
61
+ ```
49
62
 
50
63
  Coordinates:
51
64
  - All coordinate-based commands (`press`, `long-press`, `focus`, `fill`) use device coordinates with origin at top-left.
52
65
  - X increases to the right, Y increases downward.
53
66
 
54
- iOS snapshots:
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.
67
+ ## Command Index
68
+ - `open`, `close`, `home`, `back`, `app-switcher`
69
+ - `snapshot`, `find`, `get`
70
+ - `click`, `focus`, `type`, `fill`, `press`, `long-press`, `scroll`, `scrollintoview`
71
+ - `alert`, `wait`, `screenshot`
72
+ - `trace start`, `trace stop`
73
+ - `settings wifi|airplane|location on|off`
74
+ - `appstate`, `apps`, `devices`, `session list`
75
+
76
+ ## Backends (iOS snapshots)
77
+
78
+ | Backend | Speed | Accuracy | Requirements |
79
+ | --- | --- | --- | --- |
80
+ | `ax` | Fast | Medium | Accessibility permission for the terminal app |
81
+ | `xctest` | Fast | High | No Accessibility permission required |
82
+
83
+ Notes:
84
+ - Default backend is `xctest` on iOS.
85
+ - Scope snapshots with `-s "<label>"` or `-s @ref`.
86
+ - If XCTest returns 0 nodes (e.g., foreground app changed), agent-device falls back to AX when available.
60
87
 
61
88
  Flags:
62
89
  - `--platform ios|android`
@@ -67,21 +94,19 @@ Flags:
67
94
  - `--session <name>`
68
95
  - `--verbose` for daemon and runner logs
69
96
  - `--json` for structured output
70
- - `--backend ax|xctest|hybrid` (snapshot only; defaults to `hybrid` on iOS)
97
+ - `--backend ax|xctest` (snapshot only; defaults to `xctest` on iOS)
71
98
 
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.
99
+ ## Skills
100
+ Install the automation skills listed in [SKILL.md](skills/agent-device/SKILL.md).
75
101
 
76
102
  Sessions:
77
103
  - `open` starts a session. Without args boots/activates the target device/simulator without launching an app.
78
104
  - All interaction commands require an open session.
105
+ - If a session is already open, `open <app>` switches the active app and updates the session app bundle.
79
106
  - `close` stops the session and releases device resources. Pass an app to close it explicitly, or omit to just close the session.
80
107
  - Use `--session <name>` to manage multiple sessions.
81
108
  - Session logs are written to `~/.agent-device/sessions/<session>-<timestamp>.ad`.
82
109
 
83
- Snapshot defaults to the hybrid backend on iOS simulators. Use `--backend ax` for AX-only or `--backend xctest` for XCTest-only.
84
-
85
110
  Find (semantic):
86
111
  - `find <text> <action> [value]` finds by any text (label/value/identifier) using a scoped snapshot.
87
112
  - `find text|label|value|role|id <value> <action> [value]` for specific locators.
@@ -90,8 +115,8 @@ Find (semantic):
90
115
  Settings helpers (simulators):
91
116
  - `settings wifi on|off`
92
117
  - `settings airplane on|off`
93
- - `settings location on|off` (iOS uses perapp 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.
118
+ - `settings location on|off` (iOS uses per-app permission for the current session app)
119
+ Note: iOS wifi/airplane toggles status bar indicators, not actual network state. Airplane off clears status bar overrides.
95
120
 
96
121
  App state:
97
122
  - `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).
@@ -99,9 +124,8 @@ App state:
99
124
 
100
125
  ## Debug
101
126
 
102
- - Start trace capture before a flaky sequence:
103
- - `agent-device trace start`
104
- - `agent-device trace stop ./trace.log`
127
+ - `agent-device trace start`
128
+ - `agent-device trace stop ./trace.log`
105
129
  - The trace log includes AX snapshot stderr and XCTest runner logs for the session.
106
130
  - Built-in retries cover transient runner connection failures, AX snapshot hiccups, and Android UI dumps.
107
131
  - For snapshot issues, compare `--backend ax` vs `--backend xctest` and scope with `-s "<label>"`.
Binary file
package/dist/src/bin.js CHANGED
@@ -1,24 +1,23 @@
1
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?`
2
+ `)}function u(e){let t=e.details?`
3
3
  ${JSON.stringify(e.details,null,2)}`:"";process.stderr.write(`Error (${e.code}): ${e.message}${t}
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]
4
+ `)}let d=e.join(a.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=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 y(t))return t;t&&(t.version!==r||!await y(t))&&o.existsSync(p)&&o.unlinkSync(p),await g();let n=Date.now();for(;Date.now()-n<5e3;){let e=w();if(e&&await y(e))return e;await new Promise(e=>setTimeout(e,100))}throw new l("COMMAND_FAILED","Failed to start daemon",{infoPath:p,hint:"Run pnpm build, or delete ~/.agent-device/daemon.json if stale."})}function w(){if(!o.existsSync(p))return null;try{let e=JSON.parse(o.readFileSync(p,"utf8"));if(!e.port||!e.token)return null;return e}catch{return null}}async function y(e){return new Promise(t=>{let r=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)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|hybrid]
12
+ snapshot [-i] [-c] [-d <depth>] [-s <scope>] [--raw] [--backend ax|xctest]
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|hybrid hybrid: default; AX snapshot with XCTest fill for empty containers
19
+ --backend ax|xctest xctest: default; XCTest snapshot (slower, no permissions)
20
20
  ax: macOS Accessibility tree (fast, needs permissions)
21
- xctest: XCTest snapshot (slower, no permissions)
22
21
  devices List available devices
23
22
  apps [--user-installed|--all|--metadata] List installed apps (Android launchable by default, iOS simulator)
24
23
  appstate Show foreground app/activity
@@ -66,14 +65,14 @@ Flags:
66
65
  --user-installed Apps: list user-installed packages (Android only)
67
66
  --all Apps: list all packages (Android only)
68
67
 
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")}
68
+ `),process.exit(+!n.flags.help));let{command:s,positionals:i,flags:d}=n,p=d.session??process.env.AGENT_DEVICE_SESSION??"default",f=d.verbose&&!d.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);d.json?c({success:!0,data:t.data??{}}):process.stdout.write(`${JSON.stringify(t.data??{},null,2)}
69
+ `),f&&f();return}let e=await m({session:p,command:s,positionals:i,flags:d});if(e.ok){if(d.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
70
  `:"";if(!Array.isArray(r)||0===r.length)return`${l}${i}
72
71
  `;if(t.raw){let e=r.map(e=>JSON.stringify(e));return`${l}${i}
73
72
  ${e.join("\n")}
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}
73
+ `}let c=[],u=[];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(", "),d=l?` [${l}]`:"",p=r?` "${r}"`:"";if(s){u.push(`${a}${i} [${n}]${d}`.trimEnd());continue}u.push(`${a}${i} [${n}]${p}${d}`.trimEnd())}return`${l}${i}
74
+ ${u.join("\n")}
75
+ `}(e.data??{},{raw:d.snapshotRaw})),f&&f();return}if("get"===s){let t=i[0];if("text"===t){let t=e.data?.text??"";process.stdout.write(`${t}
77
76
  `),f&&f();return}if("attrs"===t){let t=e.data?.node??{};process.stdout.write(`${JSON.stringify(t,null,2)}
78
77
  `),f&&f();return}}if("find"===s){let t=e.data;if("string"==typeof t?.text){process.stdout.write(`${t.text}
79
78
  `),f&&f();return}if("boolean"==typeof t?.found){process.stdout.write(`Found: ${t.found}
@@ -86,7 +85,7 @@ ${d.join("\n")}
86
85
  `),s&&process.stdout.write(`Source: ${s}
87
86
  `),f&&f();return}if("android"===e){process.stdout.write(`Foreground app: ${o??"unknown"}
88
87
  `),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(`
88
+ `),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(d.json)c({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(`
90
89
  [daemon log]
91
90
  ${n}
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));
91
+ `)}}catch{}f&&f(),process.exit(1)}}n(process.argv[1]??"").href===import.meta.url&&$(process.argv.slice(2)).catch(e=>{u(r(e)),process.exit(1)}),$(process.argv.slice(2));