agent-device 0.1.9 → 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
@@ -45,7 +45,7 @@ Basic flow:
45
45
 
46
46
  ```bash
47
47
  agent-device open SampleApp
48
- agent-device snapshot -i -c
48
+ agent-device snapshot
49
49
  agent-device click @e7
50
50
  agent-device fill @e8 "hello"
51
51
  agent-device close SampleApp
@@ -78,12 +78,12 @@ Coordinates:
78
78
  | Backend | Speed | Accuracy | Requirements |
79
79
  | --- | --- | --- | --- |
80
80
  | `ax` | Fast | Medium | Accessibility permission for the terminal app |
81
- | `xctest` | Slow | High | No Accessibility permission required |
81
+ | `xctest` | Fast | High | No Accessibility permission required |
82
82
 
83
83
  Notes:
84
84
  - Default backend is `xctest` on iOS.
85
85
  - Scope snapshots with `-s "<label>"` or `-s @ref`.
86
- - If XCTest returns 0 nodes (e.g., foreground app changed), agent-device will fall back to an AX snapshot when available.
86
+ - If XCTest returns 0 nodes (e.g., foreground app changed), agent-device falls back to AX when available.
87
87
 
88
88
  Flags:
89
89
  - `--platform ios|android`
@@ -1,9 +1,9 @@
1
- let e,t;import a from"node:crypto";import{isCancel as i,select as n}from"@clack/prompts";import{node_path as o,runCmdStreaming as r,promises as s,asAppError as l,fileURLToPath as c,runCmdBackground as 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,o=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=o(t.deviceName),i=a.find(t=>o(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 r=a.filter(e=>e.booted);if(1===r.length)return r[0];if(!process.env.CI&&process.stdin.isTTY&&process.stdout.isTTY){let e=await n({message:"Multiple devices available. Choose a device to continue:",options:(r.length>0?r: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 r[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 o=await v(i);t.push({platform:"android",id:i,name:n,kind:i.startsWith("emulator-")?"emulator":"device",booted:o})}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,o=t.maxDelayMs??2e3,r=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,o,r,s);await function(e){return new Promise(t=>setTimeout(t,e))}(e)}if(a)throw a;throw new f("COMMAND_FAILED","retry failed")}let A={settings:{type:"intent",value:"android.settings.SETTINGS"}};function b(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=A[a.toLowerCase()];if(i)return i;let n=(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(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",b(e,["shell","cmd","package","query-activities","--brief","-a","android.intent.action.MAIN","-c","android.intent.category.LAUNCHER"]),{allowFailure:!0});if(0===t.exitCode&&t.stdout.trim().length>0){let e=new Set;for(let 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",b(e,"user-installed"===t?["shell","pm","list","packages","-3"]:["shell","pm","list","packages"]))).stdout.split("\n").map(e=>e.replace("package:","").trim()).filter(Boolean)}async function 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",b(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",b(e,["shell","am","start","-a",a.value])):await h("adb",b(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",b(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",b(e,["shell","am","force-stop",a.value]))}async function E(e,t,a){await h("adb",b(e,["shell","input","tap",String(t),String(a)]))}async function R(e){await h("adb",b(e,["shell","input","keyevent","4"]))}async function C(e){await h("adb",b(e,["shell","input","keyevent","3"]))}async function M(e){await h("adb",b(e,["shell","input","keyevent","187"]))}async function T(e,t,a,i=800){await h("adb",b(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",b(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),o=Math.floor(i*a),r=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(r/2),p=l+Math.floor(r/2);break;case"down":u=l+Math.floor(r/2),p=l-Math.floor(r/2);break;case"left":c=s-Math.floor(o/2),d=s+Math.floor(o/2);break;case"right":c=s+Math.floor(o/2),d=s-Math.floor(o/2);break;default:throw new f("INVALID_ARGS",`Unknown direction: ${t}`)}await h("adb",b(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],o=/text="([^"]*)"/.exec(t),r=/content-desc="([^"]*)"/.exec(t),s=(o?.[1]??"").toLowerCase(),l=(r?.[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",b(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",b(e,["shell","svc","wifi",n?"enable":"disable"]));case"airplane":await h("adb",b(e,["shell","settings","put","global","airplane_mode_on",n?"1":"0"])),await h("adb",b(e,["shell","am","broadcast","-a","android.intent.action.AIRPLANE_MODE","--ez","state",n?"true":"false"]));return;case"location":return void await h("adb",b(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 o=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),r=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)}}(o.bounds),s=a[a.length-1],l={type:o.className,label:o.text||o.desc,value:o.text,identifier:o.resourceId,rect:r,enabled:o.enabled,hittable:o.clickable??o.focusable,depth:s.depth+1,parentIndex:void 0,children:[]};s.children.push(l),t.endsWith("/>")||a.push(l),n=i.exec(e)}return t}(e),n=[],o=!1,r=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()??"",o=e.identifier?.toLowerCase()??"";if(t.includes(a)||n.includes(a)||o.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){o=!0;return}if(!(t>r)){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),o)return}};for(let e of l)if(c(e,0),o)break;return o?{nodes:n,truncated:o}:{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",b(e,["shell","wm","size"]))).stdout.match(/Physical size:\s*(\d+)x(\d+)/);if(!t)throw new f("COMMAND_FAILED","Unable to read screen size");return{width:Number(t[1]),height:Number(t[2])}}async function X(e){return N(()=>z(e),{shouldRetry:H})}async function z(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}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 eo(e,t){throw ed(e,"type"),new f("UNSUPPORTED_OPERATION","simctl io keyboard is not available; use the XCTest runner for input")}async function er(e,t,a,i){await en(e,t,a),await eo(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(),o=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",o?"active":"failed"]);case"airplane":o?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,o?"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),o=await n.text(),r={};try{r=JSON.parse(o)}catch{throw new f("COMMAND_FAILED","Invalid runner response",{text:o})}if(!r.ok)throw new f("COMMAND_FAILED",r.error?.message??"Runner error",{runner:r,xcodebuild:{exitCode:1,stdout:"",stderr:""},logPath:a.logPath});return r.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),o=await n.text(),r={};try{r=JSON.parse(o)}catch{throw new f("COMMAND_FAILED","Invalid runner response",{text:o})}if(!r.ok)throw new f("COMMAND_FAILED",r.error?.message??"Runner error",{runner:r,xcodebuild:{exitCode:1,stdout:"",stderr:""},logPath:a.logPath});return r.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(),o=process.env.AGENT_DEVICE_RUNNER_TIMEOUT??"300",{xctestrunPath:s,jsonPath:l}=await e_(i,{AGENT_DEVICE_RUNNER_PORT:String(n),AGENT_DEVICE_RUNNER_TIMEOUT:o},`session-${e.id}-${n}`),c=r("xcodebuild",["test-without-building","-only-testing","AgentDeviceRunnerUITests/RunnerTests/testCommand","-parallel-testing-enabled","NO","-maximum-concurrent-test-simulator-destinations","1","-xctestrun",s,"-destination",`platform=iOS Simulator,id=${e.id}`],{onStdoutChunk:e=>{eb(e,t.logPath,t.traceLogPath,t.verbose)},onStderrChunk:e=>{eb(e,t.logPath,t.traceLogPath,t.verbose)},allowFailure:!0,env:{...process.env,AGENT_DEVICE_RUNNER_PORT:String(n),AGENT_DEVICE_RUNNER_TIMEOUT:o}}),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=o.join(p.homedir(),".agent-device","ios-runner"),n=o.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=eA(n);if(s)return s;let l=function(){let e=o.dirname(c(import.meta.url)),t=e;for(let e=0;e<6;e+=1){let e=o.join(t,"package.json");if(d.existsSync(e))return t;t=o.dirname(t)}return e}(),u=o.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 r("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=>{eb(e,t.logPath,t.traceLogPath,t.verbose)},onStderrChunk:e=>{eb(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=eA(n);if(!h)throw new f("COMMAND_FAILED","Failed to locate .xctestrun after build");return h}function eA(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=o.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 eb(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(),o=null;for(;Date.now()-n<8e3;)try{return await fetch(`http://127.0.0.1:${t}/command`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(a)})}catch(e){o=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 r=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(r&&r!==t)try{return await fetch(`http://127.0.0.1:${r}/command`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(a)})}catch(e){o=e}throw new f("COMMAND_FAILED","Runner did not accept connection",{port:t,fallbackPort:r,logPath:i,lastError:o?String(o):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}),o=n.stdout;if(0!==n.exitCode)throw new f("COMMAND_FAILED","Runner did not accept connection (simctl spawn)",{port:t,stdout:n.stdout,stderr:n.stderr,exitCode:n.exitCode});return{status:200,body:o}}async function 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 o=n.toString("utf8");if(o.includes("AGENT_DEVICE_RUNNER_LISTENER_READY")||o.includes("AGENT_DEVICE_RUNNER_PORT="))return}await new Promise(e=>setTimeout(e,100))}}async function e_(e,t,a){let i,n=o.dirname(e),r=a.replace(/[^a-zA-Z0-9._-]/g,"_"),s=o.join(n,`AgentDeviceRunner.env.${r}.json`),l=o.join(n,`AgentDeviceRunner.env.${r}.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(),o=await N(async()=>{var e,a;let i,o,r,s=await h(n,[],{allowFailure:!0});if(t.traceLogPath&&(e=t.traceLogPath,i=((a=s).stdout??"").toString(),o=(a.stderr??"").toString(),r=`
2
- [axsnapshot] exit=${a.exitCode} stdoutBytes=${i.length} stderrBytes=${o.length}
3
- `,d.appendFileSync(e,r),(0!==a.exitCode||o.length>0)&&(o.length>0&&d.appendFileSync(e,`${o}
1
+ let e,t;import a from"node:crypto";import{isCancel as i,select as o}from"@clack/prompts";import{node_path as n,runCmdStreaming as r,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,n=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=n(t.deviceName),i=a.find(t=>n(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 r=a.filter(e=>e.booted);if(1===r.length)return r[0];if(!process.env.CI&&process.stdin.isTTY&&process.stdout.isTTY){let e=await o({message:"Multiple devices available. Choose a device to continue:",options:(r.length>0?r: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 r[0]??a[0]}async function v(){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 o=(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&&(o=t.replace(/_/g," "))}let n=await y(i);t.push({platform:"android",id:i,name:o,kind:i.startsWith("emulator-")?"emulator":"device",booted:n})}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 I(e,t=6e4){let a=Date.now();for(;Date.now()-a<t;){if(await y(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,o=t.baseDelayMs??200,n=t.maxDelayMs??2e3,r=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 o=Math.min(t,e*2**(i-1));return Math.max(0,o+o*a*(2*Math.random()-1))}(o,n,r,s);await function(e){return new Promise(t=>setTimeout(t,e))}(e)}if(a)throw a;throw new f("COMMAND_FAILED","retry failed")}let A={settings:{type:"intent",value:"android.settings.SETTINGS"}};function b(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=A[a.toLowerCase()];if(i)return i;let o=(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(a.toLowerCase()));if(1===o.length)return{type:"package",value:o[0]};if(o.length>1)throw new f("INVALID_ARGS",`Multiple packages matched "${t}"`,{matches:o});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",b(e,["shell","cmd","package","query-activities","--brief","-a","android.intent.action.MAIN","-c","android.intent.category.LAUNCHER"]),{allowFailure:!0});if(0===t.exitCode&&t.stdout.trim().length>0){let e=new Set;for(let a of t.stdout.split("\n")){let t=a.trim();if(!t)continue;let i=t.split(/\s+/)[0],o=i.includes("/")?i.split("/")[0]:i;o&&e.add(o)}if(e.size>0)return Array.from(e)}}return(await h("adb",b(e,"user-installed"===t?["shell","pm","list","packages","-3"]:["shell","pm","list","packages"]))).stdout.split("\n").map(e=>e.replace("package:","").trim()).filter(Boolean)}async function 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",b(e,a),{allowFailure:!0})).stdout??"");if(t)return t}return null}async function x(e,t){e.booted||await I(e.id);let a=await S(e,t);"intent"===a.type?await h("adb",b(e,["shell","am","start","-a",a.value])):await h("adb",b(e,["shell","monkey","-p",a.value,"-c","android.intent.category.LAUNCHER","1"]))}async function _(e){e.booted||await I(e.id)}async function L(e,t){if("settings"===t.trim().toLowerCase())return void await h("adb",b(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",b(e,["shell","am","force-stop",a.value]))}async function E(e,t,a){await h("adb",b(e,["shell","input","tap",String(t),String(a)]))}async function R(e){await h("adb",b(e,["shell","input","keyevent","4"]))}async function C(e){await h("adb",b(e,["shell","input","keyevent","3"]))}async function M(e){await h("adb",b(e,["shell","input","keyevent","187"]))}async function T(e,t,a,i=800){await h("adb",b(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",b(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:o}=await W(e),n=Math.floor(i*a),r=Math.floor(o*a),s=Math.floor(i/2),l=Math.floor(o/2),c=s,u=l,d=s,p=l;switch(t){case"up":u=l-Math.floor(r/2),p=l+Math.floor(r/2);break;case"down":u=l+Math.floor(r/2),p=l-Math.floor(r/2);break;case"left":c=s-Math.floor(n/2),d=s+Math.floor(n/2);break;case"right":c=s+Math.floor(n/2),d=s-Math.floor(n/2);break;default:throw new f("INVALID_ARGS",`Unknown direction: ${t}`)}await h("adb",b(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,o=i.exec(e);for(;o;){let t=o[0],n=/text="([^"]*)"/.exec(t),r=/content-desc="([^"]*)"/.exec(t),s=(n?.[1]??"").toLowerCase(),l=(r?.[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}}o=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",b(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(),o=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",b(e,["shell","svc","wifi",o?"enable":"disable"]));case"airplane":await h("adb",b(e,["shell","settings","put","global","airplane_mode_on",o?"1":"0"])),await h("adb",b(e,["shell","am","broadcast","-a","android.intent.action.AIRPLANE_MODE","--ez","state",o?"true":"false"]));return;case"location":return void await h("adb",b(e,["shell","settings","put","secure","location_mode",o?"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,o=i.exec(e);for(;o;){let t=o[0];if(t.startsWith("</node")){a.length>1&&a.pop(),o=i.exec(e);continue}let n=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),r=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)}}(n.bounds),s=a[a.length-1],l={type:n.className,label:n.text||n.desc,value:n.text,identifier:n.resourceId,rect:r,enabled:n.enabled,hittable:n.clickable??n.focusable,depth:s.depth+1,parentIndex:void 0,children:[]};s.children.push(l),t.endsWith("/>")||a.push(l),o=i.exec(e)}return t}(e),o=[],n=!1,r=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()??"",o=e.value?.toLowerCase()??"",n=e.identifier?.toLowerCase()??"";if(t.includes(a)||o.includes(a)||n.includes(a))return e;i.push(...e.children)}return null}(i,a.scope):null,l=s?[s]:i.children,c=(e,t)=>{if(o.length>=800){n=!0;return}if(!(t>r)){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))&&o.push({index:o.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),n)return}};for(let e of l)if(c(e,0),n)break;return n?{nodes:o,truncated:n}:{nodes:o}}(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",b(e,["shell","wm","size"]))).stdout.match(/Physical size:\s*(\d+)x(\d+)/);if(!t)throw new f("COMMAND_FAILED","Unable to read screen size");return{width:Number(t[1]),height:Number(t[2])}}async function X(e){return N(()=>z(e),{shouldRetry:H})}async function z(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}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 Z(){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 Y={settings:"com.apple.Preferences"};async function K(e,t){let a=t.trim();if(a.includes("."))return a;let i=Y[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 eo(e,t,a){await ea(e,t,a)}async function en(e,t){throw ed(e,"type"),new f("UNSUPPORTED_OPERATION","simctl io keyboard is not available; use the XCTest runner for input")}async function er(e,t,a,i){await eo(e,t,a),await en(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 o=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(o){case"wifi":return void await h("xcrun",["simctl","status_bar",e.id,"override","--wifiMode",n?"active":"failed"]);case"airplane":n?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,n?"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),o=await eD(e,i.port,t,a.logPath),n=await o.text(),r={};try{r=JSON.parse(n)}catch{throw new f("COMMAND_FAILED","Invalid runner response",{text:n})}if(!r.ok)throw new f("COMMAND_FAILED",r.error?.message??"Runner error",{runner:r,xcodebuild:{exitCode:1,stdout:"",stderr:""},logPath:a.logPath});return r.data??{}}catch(o){let i=o instanceof f?o:new f("COMMAND_FAILED",String(o));if("COMMAND_FAILED"===i.code&&"string"==typeof i.message&&i.message.includes("Runner did not accept connection")){await ev(e.id);let i=await eI(e,a),o=await eD(e,i.port,t,a.logPath),n=await o.text(),r={};try{r=JSON.parse(n)}catch{throw new f("COMMAND_FAILED","Invalid runner response",{text:n})}if(!r.ok)throw new f("COMMAND_FAILED",r.error?.message??"Runner error",{runner:r,xcodebuild:{exitCode:1,stdout:"",stderr:""},logPath:a.logPath});return r.data??{}}throw o}}async function ev(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 ey(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 ey(e.id);let i=await eN(e.id,t),o=await eP(),n=process.env.AGENT_DEVICE_RUNNER_TIMEOUT??"300",{xctestrunPath:s,jsonPath:l}=await eO(i,{AGENT_DEVICE_RUNNER_PORT:String(o),AGENT_DEVICE_RUNNER_TIMEOUT:n},`session-${e.id}-${o}`),c=r("xcodebuild",["test-without-building","-only-testing","AgentDeviceRunnerUITests/RunnerTests/testCommand","-parallel-testing-enabled","NO","-maximum-concurrent-test-simulator-destinations","1","-xctestrun",s,"-destination",`platform=iOS Simulator,id=${e.id}`],{onStdoutChunk:e=>{eb(e,t.logPath,t.traceLogPath,t.verbose)},onStderrChunk:e=>{eb(e,t.logPath,t.traceLogPath,t.verbose)},allowFailure:!0,env:{...process.env,AGENT_DEVICE_RUNNER_PORT:String(o),AGENT_DEVICE_RUNNER_TIMEOUT:n}}),u={device:e,deviceId:e.id,port:o,xctestrunPath:s,jsonPath:l,testPromise:c};return em.set(e.id,u),u}async function eN(e,t){let a,i=n.join(p.homedir(),".agent-device","ios-runner"),o=n.join(i,"derived");if((a=process.env.AGENT_DEVICE_IOS_CLEAN_DERIVED)&&["1","true","yes","on"].includes(a.toLowerCase()))try{d.rmSync(o,{recursive:!0,force:!0})}catch{}let s=eA(o);if(s)return s;let l=function(){let e=n.dirname(c(import.meta.url)),t=e;for(let e=0;e<6;e+=1){let e=n.join(t,"package.json");if(d.existsSync(e))return t;t=n.dirname(t)}return e}(),u=n.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 r("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",o],{onStdoutChunk:e=>{eb(e,t.logPath,t.traceLogPath,t.verbose)},onStderrChunk:e=>{eb(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=eA(o);if(!h)throw new f("COMMAND_FAILED","Failed to locate .xctestrun after build");return h}function eA(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 o=n.join(e,i.name);if(i.isDirectory()){a.push(o);continue}if(i.isFile()&&i.name.endsWith(".xctestrun"))try{let e=d.statSync(o);t.push({path:o,mtimeMs:e.mtimeMs})}catch{}}}return 0===t.length?null:(t.sort((e,t)=>t.mtimeMs-e.mtimeMs),t[0]?.path??null)}function eb(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){let o=Date.now(),n=null;for(;Date.now()-o<15e3;)try{return await fetch(`http://127.0.0.1:${t}/command`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(a)})}catch(e){n=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})}throw new f("COMMAND_FAILED","Runner did not accept connection",{port:t,logPath:i,lastError:n?String(n):void 0})}async function ek(e,t,a){let i=JSON.stringify(a),o=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}),n=o.stdout;if(0!==o.exitCode)throw new f("COMMAND_FAILED","Runner did not accept connection (simctl spawn)",{port:t,stdout:o.stdout,stderr:o.stderr,exitCode:o.exitCode});return{status:200,body:n}}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,a){let i,o=n.dirname(e),r=a.replace(/[^a-zA-Z0-9._-]/g,"_"),s=n.join(o,`AgentDeviceRunner.env.${r}.json`),l=n.join(o,`AgentDeviceRunner.env.${r}.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 e_(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 o=await eL(),n=await N(async()=>{var e,a;let i,n,r,s=await h(o,[],{allowFailure:!0});if(t.traceLogPath&&(e=t.traceLogPath,i=((a=s).stdout??"").toString(),n=(a.stderr??"").toString(),r=`
2
+ [axsnapshot] exit=${a.exitCode} stdoutBytes=${i.length} stderrBytes=${n.length}
3
+ `,d.appendFileSync(e,r),(0!==a.exitCode||n.length>0)&&(n.length>0&&d.appendFileSync(e,`${n}
4
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(o.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 r=a.frame??i,s=[],l=[],c=(e,t)=>{e.frame&&s.push(e.frame);let a=e.frame&&r?{x:e.frame.x-r.x,y:e.frame.y-r.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,r,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=o.dirname(c(import.meta.url));for(let t=0;t<6;t+=1){let t=o.join(e,"package.json");if(d.existsSync(t))return e;e=o.dirname(e)}return process.cwd()}(),t=o.join(e,"ios-runner","AXSnapshot"),a=process.env.AGENT_DEVICE_AX_BINARY;if(a&&d.existsSync(a))return a;let i=o.join(e,"dist","bin","axsnapshot");if(d.existsSync(i))return i;let n=o.join(t,".build","release","axsnapshot");if(d.existsSync(n))return n;let r=await h("swift",["build","-c","release"],{cwd:t,allowFailure:!0});if(0!==r.exitCode||!d.existsSync(n))throw new f("COMMAND_FAILED","Failed to build AX snapshot tool",{stderr:r.stderr,stdout:r.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 o=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=>eo(e,t),fill:(t,a,i)=>er(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 o.openDevice(),{app:null};return await o.open(e),{app:e}}case"close":{let e=a[0];if(!e)return{closed:"session"};return await o.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 o.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 o.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 o.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 o.type(t),{text:t}}case"fill":{let t=Number(a[0]),i=Number(a[1]),r=a.slice(2).join(" ");if(Number.isNaN(t)||Number.isNaN(i)||!r)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:r,appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath,traceLogPath:n?.traceLogPath})):await o.fill(t,i,r),{x:t,y:i,text:r}}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 o.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 o.scrollIntoView(t),{text:t}}case"screenshot":{let e=i??`./screenshot-${Date.now()}.png`;return await o.screenshot(e),{path:e}}case"back":if("ios"===e.platform){if("simulator"!==e.kind)throw new f("UNSUPPORTED_OPERATION","back is only supported on iOS simulators in v1");return await 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,o]=a;if("ios"===e.platform)return await eu(e,t,i,o??n?.appBundleId),{setting:t,state:i};return await G(e,t,i),{setting:t,state:i}}case"snapshot":{let t=n?.snapshotBackend??"xctest";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"};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}),i=a.nodes??[];if(0===i.length)try{return{nodes:(await eL(e,{traceLogPath:n?.traceLogPath})).nodes??[],truncated:!1,backend:"ax"}}catch{}return{nodes:i,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}`)}}function eM(e){return e.map((e,t)=>({...e,ref:`e${t+1}`}))}function eT(e){let t=e.trim();return t.startsWith("@")?t.slice(1)||null:t.startsWith("e")?t:null}function eF(e,t){return e.find(e=>e.ref===t)??null}function eB(e){return{x:Math.round(e.x+e.width/2),y:Math.round(e.y+e.height/2)}}function e$(e,t,a,i={}){let n=eV(a);if(!n)return null;let o=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 eU(e.label,a);case"value":return eU(e.value,a);case"id":return eU(e.identifier,a);default:return Math.max(eU(e.label,a),eU(e.value,a),eU(e.identifier,a))}}(a,t,n);if(!(e<=0)&&(!o||e>o.score)&&(o={node:a,score:e},e>=2))break}return o?.node??null}function eU(e,t){let a=eV(e??"");return a?a===t?2:+!!a.includes(t):0}function eV(e){return e.trim().toLowerCase().replace(/\s+/g," ")}let ej=new Map,eG=o.join(p.homedir(),".agent-device"),eq=o.join(eG,"daemon.json"),eJ=o.join(eG,"daemon.log"),eW=o.join(eG,"sessions"),eX=function(){try{let e=function(){let e=o.dirname(c(import.meta.url)),t=e;for(let e=0;e<6;e+=1){let e=o.join(t,"package.json");if(d.existsSync(e))return t;t=o.dirname(t)}return e}();return JSON.parse(d.readFileSync(o.join(e,"package.json"),"utf8")).version??"0.0.0"}catch{return"0.0.0"}}(),ez=a.randomBytes(24).toString("hex");function eH(e,t,a){return{appBundleId:t,verbose:e?.verbose,logPath:eJ,traceLogPath:a,snapshotInteractiveOnly:e?.snapshotInteractiveOnly,snapshotCompact:e?.snapshotCompact,snapshotDepth:e?.snapshotDepth,snapshotScope:e?.snapshotScope,snapshotRaw:e?.snapshotRaw,snapshotBackend:e?.snapshotBackend}}async function eY(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 e6(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:o,listAndroidAppsMetadata:r}=await Promise.resolve().then(()=>({listAndroidApps:D,listAndroidAppsMetadata:k}));return e.flags?.appsMetadata?{ok:!0,data:{apps:await r(n,e.flags?.appsFilter)}}:{ok:!0,data:{apps:await o(n,e.flags?.appsFilter)}}}if("appstate"===t){let t=ej.get(a),i=e.flags??{},n=t?.device??await eR(i);if(await e6(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 eK(n,t?.trace?.outPath,e.flags);return{ok:!0,data:{platform:"ios",appName:a.appName,appBundleId:a.appBundleId,source:a.source}}}let{getAndroidAppState:o}=await Promise.resolve().then(()=>({getAndroidAppState:P})),r=await o(n);return{ok:!0,data:{platform:"android",package:r.package,activity:r.activity}}}if("open"===t){let i;if(ej.has(a)){let i,n=ej.get(a),o=e.positionals?.[0];if(!n||!o)return{ok:!1,error:{code:"INVALID_ARGS",message:"Session already active. Close it first or pass a new --session name."}};if("ios"===n.device.platform)try{let{resolveIosApp:e}=await Promise.resolve().then(()=>({resolveIosApp:K}));i=await e(n.device,o)}catch{i=void 0}await eC(n.device,"open",e.positionals??[],e.flags?.out,{...eH(e.flags,i)});let r={...n,appBundleId:i,appName:o,snapshot:void 0};return eZ(r,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{session:a,appName:o,appBundleId:i}}),ej.set(a,r),{ok:!0,data:{session:a,appName:o,appBundleId:i}}}let n=await eR(e.flags??{});await e6(n);let o=Array.from(ej.values()).find(e=>e.device.id===n.id);if(o)return{ok:!1,error:{code:"DEVICE_IN_USE",message:`Device is already in use by session "${o.name}".`,details:{session:o.name,deviceId:n.id,deviceName:n.name}}};let r=e.positionals?.[0];if("ios"===n.platform)try{let{resolveIosApp:t}=await Promise.resolve().then(()=>({resolveIosApp:K}));i=await t(n,e.positionals?.[0]??"")}catch{i=void 0}await eC(n,"open",e.positionals??[],e.flags?.out,{...eH(e.flags,i)});let s={name:a,device:n,createdAt:Date.now(),appBundleId:i,appName:r,actions:[]};return eZ(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=e1(t),i=JSON.parse(d.readFileSync(e,"utf8")),n=i.optimizedActions??i.actions??[];for(let e of n)e&&"replay"!==e.command&&await eY({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,{...eH(e.flags,i.appBundleId,i.trace?.outPath)}),"ios"===i.device.platform&&"simulator"===i.device.kind&&await ey(i.device.id),eZ(i,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{session:a}}),e0(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 e6(n);let o=i?.appBundleId,r=e.flags?.snapshotScope;if(r&&r.trim().startsWith("@")){if(!i?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"Ref scope requires an existing snapshot in session."}};let e=eT(r.trim());if(!e)return{ok:!1,error:{code:"INVALID_ARGS",message:`Invalid ref scope: ${r}`}};let t=eF(i.snapshot.nodes,e),a=t?e8(t,i.snapshot.nodes):void 0;if(!a)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${r} not found or has no label`}};r=a}let s=await eC(n,"snapshot",[],e.flags?.out,{...eH({...e.flags,snapshotScope:r},o,i?.trace?.outPath)}),l=s?.nodes??[],c=eM(e.flags?.snapshotRaw?l:e7(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??o,snapshot:u,actions:i?.actions??[],appName:i?.appName};return eZ(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 e6(n);let o=e.positionals??[];if(0===o.length)return{ok:!1,error:{code:"INVALID_ARGS",message:"wait requires a duration or text"}};let r=e=>{if(!e)return null;let t=Number(e);return Number.isFinite(t)?t:null},s=r(o[0]);if(null!==s)return await new Promise(e=>setTimeout(e,s)),i&&eZ(i,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{waitedMs:s}}),{ok:!0,data:{waitedMs:s}};let l="",c=null;if("text"===o[0])l=null!==(c=r(o[o.length-1]))?o.slice(1,-1).join(" "):o.slice(1).join(" ");else if(o[0].startsWith("@")){if(!i?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"Ref wait requires an existing snapshot in session."}};let e=eT(o[0]);if(!e)return{ok:!1,error:{code:"INVALID_ARGS",message:`Invalid ref: ${o[0]}`}};let t=eF(i.snapshot.nodes,e),a=t?e8(t,i.snapshot.nodes):void 0;if(!a)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${o[0]} not found or has no label`}};c=r(o[o.length-1]),l=a}else l=null!==(c=r(o[o.length-1]))?o.slice(0,-1).join(" "):o.join(" ");if(!(l=l.trim()))return{ok:!1,error:{code:"INVALID_ARGS",message:"wait requires text"}};let 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:eJ,traceLogPath:i?.trace?.outPath});if(a?.found)return i&&eZ(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(e3(eM((await q(n,{scope:l})).nodes??[]),l))return i&&eZ(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 e6(n);let o=(e.positionals?.[0]??"get").toLowerCase();if("ios"!==n.platform||"simulator"!==n.kind)return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"alert is only supported on iOS simulators in v1"}};if("wait"===o){let a=(e=>{if(!e)return null;let t=Number(e);return Number.isFinite(t)?t:null})(e.positionals?.[1])??1e4,o=Date.now();for(;Date.now()-o<a;){try{let a=await ew(n,{command:"alert",action:"get",appBundleId:i?.appBundleId},{verbose:e.flags?.verbose,logPath:eJ,traceLogPath:i?.trace?.outPath});return i&&eZ(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 r=await ew(n,{command:"alert",action:"accept"===o||"dismiss"===o?o:"get",appBundleId:i?.appBundleId},{verbose:e.flags?.verbose,logPath:eJ,traceLogPath:i?.trace?.outPath});return i&&eZ(i,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:r}),{ok:!0,data:r}}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),r=n?.device??await eR(e.flags??{});n||await e6(r);let s=n??{name:a,device:r,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=o.resolve(i),l=o.dirname(n);if(d.existsSync(l)||d.mkdirSync(l,{recursive:!0}),"ios"===r.platform){if("simulator"!==r.kind)return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"record is only supported on iOS simulators in v1"}};let{child:e,wait:t}=u("xcrun",["simctl","io",r.id,"recordVideo",n],{allowFailure:!0});s.recording={platform:"ios",outPath:n,child:e,wait:t}}else{let e=`/sdcard/agent-device-recording-${Date.now()}.mp4`,{child:t,wait:a}=u("adb",["-s",r.id,"shell","screenrecord",e],{allowFailure:!0});s.recording={platform:"android",outPath:n,remotePath:e,child:t,wait:a}}return ej.set(a,s),eZ(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",r.id,"pull",l.remotePath,l.outPath],{allowFailure:!0}),await h("adb",["-s",r.id,"shell","rm","-f",l.remotePath],{allowFailure:!0})}catch{}return s.recording=void 0,eZ(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 r=e1(e.positionals?.[1]??(a=n.name.replace(/[^a-zA-Z0-9._-]/g,"_"),i=new Date().toISOString().replace(/[:.]/g,"-"),o.join(eW,`${a}-${i}.trace.log`)));return d.mkdirSync(o.dirname(r),{recursive:!0}),d.appendFileSync(r,""),n.trace={outPath:r,startedAt:Date.now()},eZ(n,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{action:"start",outPath:r}}),{ok:!0,data:{trace:"started",outPath:r}}}if(!n.trace)return{ok:!1,error:{code:"INVALID_ARGS",message:"no active trace"}};let r=n.trace.outPath;if(e.positionals?.[1]){let t=e1(e.positionals[1]);d.mkdirSync(o.dirname(t),{recursive:!0}),d.existsSync(r)?d.renameSync(r,t):d.appendFileSync(t,""),r=t}return n.trace=void 0,eZ(n,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{action:"stop",outPath:r}}),{ok:!0,data:{trace:"stopped",outPath:r}}}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 o=ej.get(a),r=o?.device??await eR(e.flags??{});o||await e6(r);let s=o?.appBundleId,l=await eC(r,"settings",[i,n,s??""],e.flags?.out,{...eH(e.flags,s,o?.trace?.outPath)});return o&&eZ(o,{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:o,query:r,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 o=n[0].toLowerCase();if("get"===o){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"===o)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"===o)return{locator:t,query:i,action:"exists"};if("click"===o)return{locator:t,query:i,action:"click"};if("focus"===o)return{locator:t,query:i,action:"focus"};if("fill"===o)return{locator:t,query:i,action:"fill",value:n.slice(1).join(" ")};if("type"===o)return{locator:t,query:i,action:"type",value:n.slice(1).join(" ")};throw new f("INVALID_ARGS",`Unsupported find action: ${n[0]}`)}(n);if(!r)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 e6(d);let p=u?.appBundleId,h="role"!==o?r: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,{...eH({...e.flags,snapshotScope:h,snapshotInteractiveOnly:m,snapshotCompact:m},p,u?.trace?.outPath)}),n=i?.nodes??[],o=eM(e.flags?.snapshotRaw?n:e7(n));return w=t,g=o,u&&(u.snapshot={nodes:o,truncated:i?.truncated,createdAt:Date.now(),backend:i?.backend},ej.set(a,u)),{nodes:o,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(e$(a,o,r,{requireRect:!1}))return u&&eZ(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=e$(v,o,r,{requireRect:m});if(!I)return{ok:!1,error:{code:"COMMAND_FAILED",message:"find did not match any element"}};let N=`@${I.ref}`,A={...e.flags??{},noRecord:!0};if("exists"===s)return u&&eZ(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&&eZ(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&&eZ(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 eY({token:ez,session:a,command:"click",positionals:[N],flags:A});return i.ok&&u&&eZ(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 eY({token:ez,session:a,command:"fill",positionals:[N,l],flags:A});return i.ok&&u&&eZ(u,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{ref:N,action:"fill"}}),i}if("focus"===s){let a=I.rect?eB(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,{...eH(e.flags,u?.appBundleId,u?.trace?.outPath)});return u&&eZ(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?eB(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,{...eH(e.flags,u?.appBundleId,u?.trace?.outPath)});let i=await eC(d,"type",[l],e.flags?.out,{...eH(e.flags,u?.appBundleId,u?.trace?.outPath)});return u&&eZ(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]??"",o=eT(n);if(!o)return{ok:!1,error:{code:"INVALID_ARGS",message:"click requires a ref like @e2"}};let r=eF(i.snapshot.nodes,o);if(!r?.rect&&e.positionals.length>1){let t=e.positionals.slice(1).join(" ").trim();t.length>0&&(r=e3(i.snapshot.nodes,t))}if(!r?.rect)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${n} not found or has no bounds`}};let s=e8(r,i.snapshot.nodes),{x:l,y:c}=eB(r.rect);return await eC(i.device,"press",[String(l),String(c)],e.flags?.out,{...eH(e.flags,i.appBundleId,i.trace?.outPath)}),eZ(i,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{ref:o,x:l,y:c,refLabel:s}}),{ok:!0,data:{ref:o,x:l,y:c}}}if("fill"===t){let i=ej.get(a);if(e.positionals?.[0]?.startsWith("@")){let a;if(!i?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"No snapshot in session. Run snapshot first."}};let n=eT(e.positionals[0]);if(!n)return{ok:!1,error:{code:"INVALID_ARGS",message:"fill requires a ref like @e2"}};let o=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 s=eF(i.snapshot.nodes,n);if(!s?.rect&&o&&(s=e3(i.snapshot.nodes,o)),!s?.rect)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${e.positionals[0]} not found or has no bounds`}};let l=e8(s,i.snapshot.nodes),c=s.label?.trim();if("ios"===i.device.platform&&"simulator"===i.device.kind&&("textfield"===(a=e5(s.type??""))||"textview"===a||"searchfield"===a||"textarea"===a)){let a=s.rect?eB(s.rect):null;return a?(await eC(i.device,"focus",[String(a.x),String(a.y)],e.flags?.out,{...eH(e.flags,i.appBundleId,i.trace?.outPath)}),await ew(i.device,{command:"type",text:r,appBundleId:i.appBundleId},{verbose:e.flags?.verbose,logPath:eJ,traceLogPath:i?.trace?.outPath}),eZ(i,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{ref:n,refLabel:l??c,action:"fill",text:r}}),{ok:!0,data:{ref:n}}):{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${e.positionals[0]} not found or has no bounds`}}}let{x:u,y:d}=eB(s.rect),p=await eC(i.device,"fill",[String(u),String(d),r],e.flags?.out,{...eH(e.flags,i.appBundleId,i.trace?.outPath)});return eZ(i,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:p??{ref:n,x:u,y:d,refLabel:l}}),{ok:!0,data:p??{ref:n,x:u,y:d}}}}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 o=ej.get(a);if(!o?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"No snapshot in session. Run snapshot first."}};let r=eT(n??"");if(!r)return{ok:!1,error:{code:"INVALID_ARGS",message:"get text requires a ref like @e2"}};let s=eF(o.snapshot.nodes,r);if(!s&&e.positionals.length>2){let t=e.positionals.slice(2).join(" ").trim();t.length>0&&(s=e3(o.snapshot.nodes,t))}if(!s)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${n} not found`}};if("attrs"===i)return eZ(o,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{ref:r}}),{ok:!0,data:{ref:r,node:s}};let l=[s.label,s.value,s.identifier].map(e=>"string"==typeof e?e.trim():"").filter(e=>e.length>0)[0]??"";return eZ(o,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{ref:r,text:l,refLabel:l||void 0}}),{ok:!0,data:{ref:r,text:l,node:s}}}let n=ej.get(a);if(!n)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session. Run open first."}};let r=await eC(n.device,t,e.positionals??[],e.flags?.out,{...eH(e.flags,n.appBundleId,n.trace?.outPath)});return eZ(n,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:r??{}}),{ok:!0,data:r??{}}}function eZ(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:o,verbose:r,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:o,verbose:r,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 eK(e,t,a){let i=eQ(await eC(e,"snapshot",[],a?.out,{...eH({...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=eQ(await eC(e,"snapshot",[],a?.out,{...eH({...a,snapshotDepth:1,snapshotCompact:!0,snapshotBackend:"xctest"},void 0,t)}));return{appName:n?.appName??n?.appBundleId??"unknown",appBundleId:n?.appBundleId,source:"snapshot-xctest"}}function eQ(e){let t=eM(e?.nodes??[]),a=t.find(e=>"application"===e5(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 e0(e){try{d.existsSync(eW)||d.mkdirSync(eW,{recursive:!0});let t=e.name.replace(/[^a-zA-Z0-9._-]/g,"_"),a=new Date(e.createdAt).toISOString().replace(/[:.]/g,"-"),i=o.join(eW,`${t}-${a}.ad`),n=o.join(eW,`${t}-${a}.json`),r={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 o of(a.push(`context platform=${e.device.platform} device="${i}"${n} theme=unknown`),t))o.flags?.noRecord||a.push(function(e){let t=[e.command];if("click"===e.command){let a=e.positionals?.[0];if(a){t.push(e2(a));let i=e.result?.refLabel;return"string"==typeof i&&i.trim().length>0&&t.push(e2(i)),t.join(" ")}}if("fill"===e.command){let a=e.positionals?.[0];if(a&&a.startsWith("@")){t.push(e2(a));let i=e.result?.refLabel,n=e.positionals.slice(1).join(" ");return"string"==typeof i&&i.trim().length>0&&t.push(e2(i)),n&&t.push(e2(n)),t.join(" ")}}if("get"===e.command){let a=e.positionals?.[0],i=e.positionals?.[1];if(a&&i){t.push(e2(a)),t.push(e2(i));let n=e.result?.refLabel;return"string"==typeof n&&n.trim().length>0&&t.push(e2(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",e2(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(e2(a));return t.join(" ")}(o));return`${a.join("\n")}
6
- `}(e,r.optimizedActions);d.writeFileSync(i,s),e.actions.some(e=>e.flags?.recordJson)&&d.writeFileSync(n,JSON.stringify(r,null,2))}catch{}}function e1(e){return e.startsWith("~/")?o.join(p.homedir(),e.slice(2)):o.resolve(e)}function e2(e){let t=e.trim();return t.startsWith("@")||/^-?\d+(\.\d+)?$/.test(t)?t:JSON.stringify(t)}function e3(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 e8(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&&e4(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||!e4(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&&e4(a)?a:void 0)}function e4(e){let t=e.trim();return!(!t||/^(true|false)$/i.test(t)||/^\d+$/.test(t))}async function e6(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 e7(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=e5(i.type??""),o=[i.label,i.value,i.identifier].map(e=>"string"==typeof e?e.trim():"").find(e=>e&&e.length>0),r=!!o&&e4(o);if(("group"===n||"ioscontentgroup"===n)&&!r){t.push(e);continue}let s=Math.max(0,e-t.length);a.push({...i,depth:s})}return a}function e5(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 eY(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(eG)||d.mkdirSync(eG,{recursive:!0}),d.writeFileSync(eJ,""),d.writeFileSync(eq,JSON.stringify({port:a,token:ez,pid:process.pid,version:eX},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),e0(e);e.close(()=>{d.existsSync(eq)&&d.unlinkSync(eq),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
+ `))),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.":"",o=!!((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:o})}return s},{shouldRetry:e=>{var t;return(t=e)instanceof f&&"COMMAND_FAILED"===t.code&&t.details?.retryable===!0}});try{let e=JSON.parse(n.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 r=a.frame??i,s=[],l=[],c=(e,t)=>{e.frame&&s.push(e.frame);let a=e.frame&&r?{x:e.frame.x-r.x,y:e.frame.y-r.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,o=1/0;for(let e of a)e.x<i&&(i=e.x),e.y<o&&(o=e.y);return i<=5&&o<=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,r,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 eL(){let e=function(){let e=n.dirname(c(import.meta.url));for(let t=0;t<6;t+=1){let t=n.join(e,"package.json");if(d.existsSync(t))return e;e=n.dirname(e)}return process.cwd()}(),t=n.join(e,"ios-runner","AXSnapshot"),a=process.env.AGENT_DEVICE_AX_BINARY;if(a&&d.existsSync(a))return a;let i=n.join(e,"dist","bin","axsnapshot");if(d.existsSync(i))return i;let o=n.join(t,".build","release","axsnapshot");if(d.existsSync(o))return o;let r=await h("swift",["build","-c","release"],{cwd:t,allowFailure:!0});if(0!==r.exitCode||!d.existsSync(o))throw new f("COMMAND_FAILED","Failed to build AX snapshot tool",{stderr:r.stderr,stdout:r.stdout});return o}async function eE(e){let t={platform:e.platform,deviceName:e.device,udid:e.udid,serial:e.serial};if("android"===t.platform){await J();let e=await v();return await g(e,t)}if("ios"===t.platform){let e=await Z();return await g(e,t)}let a=[];try{a.push(...await v())}catch{}try{a.push(...await Z())}catch{}return await g(a,t)}async function eR(e,t,a,i,o){let n=function(e){switch(e.platform){case"android":return{open:t=>x(e,t),openDevice:()=>_(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)=>eo(e,t,a),type:t=>en(e,t),fill:(t,a,i)=>er(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 n.openDevice(),{app:null};return await n.open(e),{app:e}}case"close":{let e=a[0];if(!e)return{closed:"session"};return await n.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:o?.appBundleId},{verbose:o?.verbose,logPath:o?.logPath,traceLogPath:o?.traceLogPath}):await n.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 n.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:o?.appBundleId},{verbose:o?.verbose,logPath:o?.logPath,traceLogPath:o?.traceLogPath}):await n.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:o?.appBundleId},{verbose:o?.verbose,logPath:o?.logPath,traceLogPath:o?.traceLogPath}):await n.type(t),{text:t}}case"fill":{let t=Number(a[0]),i=Number(a[1]),r=a.slice(2).join(" ");if(Number.isNaN(t)||Number.isNaN(i)||!r)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:o?.appBundleId},{verbose:o?.verbose,logPath:o?.logPath,traceLogPath:o?.traceLogPath}),await ew(e,{command:"type",text:r,appBundleId:o?.appBundleId},{verbose:o?.verbose,logPath:o?.logPath,traceLogPath:o?.traceLogPath})):await n.fill(t,i,r),{x:t,y:i,text:r}}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:o?.appBundleId},{verbose:o?.verbose,logPath:o?.logPath,traceLogPath:o?.traceLogPath})}else await n.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:o?.appBundleId},{verbose:o?.verbose,logPath:o?.logPath,traceLogPath:o?.traceLogPath});if(i?.found)return{text:t,attempts:a+1};await ew(e,{command:"swipe",direction:"up",appBundleId:o?.appBundleId},{verbose:o?.verbose,logPath:o?.logPath,traceLogPath:o?.traceLogPath}),await new Promise(e=>setTimeout(e,300))}throw new f("COMMAND_FAILED",`scrollintoview could not find text: ${t}`)}return await n.scrollIntoView(t),{text:t}}case"screenshot":{let e=i??`./screenshot-${Date.now()}.png`;return await n.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:o?.appBundleId},{verbose:o?.verbose,logPath:o?.logPath,traceLogPath:o?.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:o?.appBundleId},{verbose:o?.verbose,logPath:o?.logPath,traceLogPath:o?.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:o?.appBundleId},{verbose:o?.verbose,logPath:o?.logPath,traceLogPath:o?.traceLogPath}),{action:"app-switcher"}}return await M(e),{action:"app-switcher"};case"settings":{let[t,i,n]=a;if("ios"===e.platform)return await eu(e,t,i,n??o?.appBundleId),{setting:t,state:i};return await G(e,t,i),{setting:t,state:i}}case"snapshot":{let t=o?.snapshotBackend??"xctest";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 e_(e,{traceLogPath:o?.traceLogPath})).nodes??[],truncated:!1,backend:"ax"};let a=await ew(e,{command:"snapshot",appBundleId:o?.appBundleId,interactiveOnly:o?.snapshotInteractiveOnly,compact:o?.snapshotCompact,depth:o?.snapshotDepth,scope:o?.snapshotScope,raw:o?.snapshotRaw},{verbose:o?.verbose,logPath:o?.logPath,traceLogPath:o?.traceLogPath}),i=a.nodes??[];if(0===i.length)try{return{nodes:(await e_(e,{traceLogPath:o?.traceLogPath})).nodes??[],truncated:!1,backend:"ax"}}catch{}return{nodes:i,truncated:a.truncated??!1,backend:"xctest"}}let a=await q(e,{interactiveOnly:o?.snapshotInteractiveOnly,compact:o?.snapshotCompact,depth:o?.snapshotDepth,scope:o?.snapshotScope,raw:o?.snapshotRaw});return{nodes:a.nodes??[],truncated:a.truncated??!1,backend:"android"}}default:throw new f("INVALID_ARGS",`Unknown command: ${t}`)}}function eC(e){return e.map((e,t)=>({...e,ref:`e${t+1}`}))}function eM(e){let t=e.trim();return t.startsWith("@")?t.slice(1)||null:t.startsWith("e")?t:null}function eT(e,t){return e.find(e=>e.ref===t)??null}function eF(e){return{x:Math.round(e.x+e.width/2),y:Math.round(e.y+e.height/2)}}function eB(e,t,a,i={}){let o=eU(a);if(!o)return null;let n=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 e$(e.label,a);case"value":return e$(e.value,a);case"id":return e$(e.identifier,a);default:return Math.max(e$(e.label,a),e$(e.value,a),e$(e.identifier,a))}}(a,t,o);if(!(e<=0)&&(!n||e>n.score)&&(n={node:a,score:e},e>=2))break}return n?.node??null}function e$(e,t){let a=eU(e??"");return a?a===t?2:+!!a.includes(t):0}function eU(e){return e.trim().toLowerCase().replace(/\s+/g," ")}let eV=new Map,ej=n.join(p.homedir(),".agent-device"),eG=n.join(ej,"daemon.json"),eq=n.join(ej,"daemon.log"),eJ=n.join(ej,"sessions"),eW=function(){try{let e=function(){let e=n.dirname(c(import.meta.url)),t=e;for(let e=0;e<6;e+=1){let e=n.join(t,"package.json");if(d.existsSync(e))return t;t=n.dirname(t)}return e}();return JSON.parse(d.readFileSync(n.join(e,"package.json"),"utf8")).version??"0.0.0"}catch{return"0.0.0"}}(),eX=a.randomBytes(24).toString("hex");function ez(e,t,a){return{appBundleId:t,verbose:e?.verbose,logPath:eq,traceLogPath:a,snapshotInteractiveOnly:e?.snapshotInteractiveOnly,snapshotCompact:e?.snapshotCompact,snapshotDepth:e?.snapshotDepth,snapshotScope:e?.snapshotScope,snapshotRaw:e?.snapshotRaw,snapshotBackend:e?.snapshotBackend}}async function eH(e){if(e.token!==eX)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(eV.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:v}));t.push(...await e())}else if(e.flags?.platform==="ios"){let{listIosDevices:e}=await Promise.resolve().then(()=>({listIosDevices:Z}));t.push(...await e())}else{let{listAndroidDevices:e}=await Promise.resolve().then(()=>({listAndroidDevices:v})),{listIosDevices:a}=await Promise.resolve().then(()=>({listIosDevices:Z}));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=eV.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 o=t?.device??await eE(i);if(await e4(o),"ios"===o.platform){if("simulator"!==o.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(o);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:n,listAndroidAppsMetadata:r}=await Promise.resolve().then(()=>({listAndroidApps:D,listAndroidAppsMetadata:k}));return e.flags?.appsMetadata?{ok:!0,data:{apps:await r(o,e.flags?.appsFilter)}}:{ok:!0,data:{apps:await n(o,e.flags?.appsFilter)}}}if("appstate"===t){let t=eV.get(a),i=e.flags??{},o=t?.device??await eE(i);if(await e4(o),"ios"===o.platform){if(t?.appBundleId)return{ok:!0,data:{platform:"ios",appBundleId:t.appBundleId,appName:t.appName??t.appBundleId,source:"session"}};let a=await eY(o,t?.trace?.outPath,e.flags);return{ok:!0,data:{platform:"ios",appName:a.appName,appBundleId:a.appBundleId,source:a.source}}}let{getAndroidAppState:n}=await Promise.resolve().then(()=>({getAndroidAppState:P})),r=await n(o);return{ok:!0,data:{platform:"android",package:r.package,activity:r.activity}}}if("open"===t){let i;if(eV.has(a)){let i,o=eV.get(a),n=e.positionals?.[0];if(!o||!n)return{ok:!1,error:{code:"INVALID_ARGS",message:"Session already active. Close it first or pass a new --session name."}};if("ios"===o.device.platform)try{let{resolveIosApp:e}=await Promise.resolve().then(()=>({resolveIosApp:K}));i=await e(o.device,n)}catch{i=void 0}await eR(o.device,"open",e.positionals??[],e.flags?.out,{...ez(e.flags,i)});let r={...o,appBundleId:i,appName:n,snapshot:void 0};return eZ(r,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{session:a,appName:n,appBundleId:i}}),eV.set(a,r),{ok:!0,data:{session:a,appName:n,appBundleId:i}}}let o=await eE(e.flags??{});await e4(o);let n=Array.from(eV.values()).find(e=>e.device.id===o.id);if(n)return{ok:!1,error:{code:"DEVICE_IN_USE",message:`Device is already in use by session "${n.name}".`,details:{session:n.name,deviceId:o.id,deviceName:o.name}}};let r=e.positionals?.[0];if("ios"===o.platform)try{let{resolveIosApp:t}=await Promise.resolve().then(()=>({resolveIosApp:K}));i=await t(o,e.positionals?.[0]??"")}catch{i=void 0}await eR(o,"open",e.positionals??[],e.flags?.out,{...ez(e.flags,i)});let s={name:a,device:o,createdAt:Date.now(),appBundleId:i,appName:r,actions:[]};return eZ(s,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{session:a}}),eV.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=e0(t),i=JSON.parse(d.readFileSync(e,"utf8")),o=i.optimizedActions??i.actions??[];for(let e of o)e&&"replay"!==e.command&&await eH({token:eX,session:a,command:e.command,positionals:e.positionals??[],flags:e.flags??{}});return{ok:!0,data:{replayed:o.length,session:a}}}catch(t){let e=l(t);return{ok:!1,error:{code:e.code,message:e.message}}}}if("close"===t){let i=eV.get(a);return i?(e.positionals&&e.positionals.length>0&&await eR(i.device,"close",e.positionals??[],e.flags?.out,{...ez(e.flags,i.appBundleId,i.trace?.outPath)}),"ios"===i.device.platform&&"simulator"===i.device.kind&&await ev(i.device.id),eZ(i,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{session:a}}),eQ(i),eV.delete(a),{ok:!0,data:{session:a}}):{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session"}}}if("snapshot"===t){let i=eV.get(a),o=i?.device??await eE(e.flags??{});i||await e4(o);let n=i?.appBundleId,r=e.flags?.snapshotScope;if(r&&r.trim().startsWith("@")){if(!i?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"Ref scope requires an existing snapshot in session."}};let e=eM(r.trim());if(!e)return{ok:!1,error:{code:"INVALID_ARGS",message:`Invalid ref scope: ${r}`}};let t=eT(i.snapshot.nodes,e),a=t?e3(t,i.snapshot.nodes):void 0;if(!a)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${r} not found or has no label`}};r=a}let s=await eR(o,"snapshot",[],e.flags?.out,{...ez({...e.flags,snapshotScope:r},n,i?.trace?.outPath)}),l=s?.nodes??[],c=eC(e.flags?.snapshotRaw?l:e6(l)),u={nodes:c,truncated:s?.truncated,createdAt:Date.now(),backend:s?.backend},d={name:a,device:o,createdAt:i?.createdAt??Date.now(),appBundleId:i?.appBundleId??n,snapshot:u,actions:i?.actions??[],appName:i?.appName};return eZ(d,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{nodes:c.length,truncated:s?.truncated??!1}}),eV.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=eV.get(a),o=i?.device??await eE(e.flags??{});i||await e4(o);let n=e.positionals??[];if(0===n.length)return{ok:!1,error:{code:"INVALID_ARGS",message:"wait requires a duration or text"}};let r=e=>{if(!e)return null;let t=Number(e);return Number.isFinite(t)?t:null},s=r(n[0]);if(null!==s)return await new Promise(e=>setTimeout(e,s)),i&&eZ(i,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{waitedMs:s}}),{ok:!0,data:{waitedMs:s}};let l="",c=null;if("text"===n[0])l=null!==(c=r(n[n.length-1]))?n.slice(1,-1).join(" "):n.slice(1).join(" ");else if(n[0].startsWith("@")){if(!i?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"Ref wait requires an existing snapshot in session."}};let e=eM(n[0]);if(!e)return{ok:!1,error:{code:"INVALID_ARGS",message:`Invalid ref: ${n[0]}`}};let t=eT(i.snapshot.nodes,e),a=t?e3(t,i.snapshot.nodes):void 0;if(!a)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${n[0]} not found or has no label`}};c=r(n[n.length-1]),l=a}else l=null!==(c=r(n[n.length-1]))?n.slice(0,-1).join(" "):n.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"===o.platform&&"simulator"===o.kind){let a=await ew(o,{command:"findText",text:l,appBundleId:i?.appBundleId},{verbose:e.flags?.verbose,logPath:eq,traceLogPath:i?.trace?.outPath});if(a?.found)return i&&eZ(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"!==o.platform)return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"wait is not supported on this device"}};else if(e2(eC((await q(o,{scope:l})).nodes??[]),l))return i&&eZ(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=eV.get(a),o=i?.device??await eE(e.flags??{});i||await e4(o);let n=(e.positionals?.[0]??"get").toLowerCase();if("ios"!==o.platform||"simulator"!==o.kind)return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"alert is only supported on iOS simulators in v1"}};if("wait"===n){let a=(e=>{if(!e)return null;let t=Number(e);return Number.isFinite(t)?t:null})(e.positionals?.[1])??1e4,n=Date.now();for(;Date.now()-n<a;){try{let a=await ew(o,{command:"alert",action:"get",appBundleId:i?.appBundleId},{verbose:e.flags?.verbose,logPath:eq,traceLogPath:i?.trace?.outPath});return i&&eZ(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 r=await ew(o,{command:"alert",action:"accept"===n||"dismiss"===n?n:"get",appBundleId:i?.appBundleId},{verbose:e.flags?.verbose,logPath:eq,traceLogPath:i?.trace?.outPath});return i&&eZ(i,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:r}),{ok:!0,data:r}}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 o=eV.get(a),r=o?.device??await eE(e.flags??{});o||await e4(r);let s=o??{name:a,device:r,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`,o=n.resolve(i),l=n.dirname(o);if(d.existsSync(l)||d.mkdirSync(l,{recursive:!0}),"ios"===r.platform){if("simulator"!==r.kind)return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"record is only supported on iOS simulators in v1"}};let{child:e,wait:t}=u("xcrun",["simctl","io",r.id,"recordVideo",o],{allowFailure:!0});s.recording={platform:"ios",outPath:o,child:e,wait:t}}else{let e=`/sdcard/agent-device-recording-${Date.now()}.mp4`,{child:t,wait:a}=u("adb",["-s",r.id,"shell","screenrecord",e],{allowFailure:!0});s.recording={platform:"android",outPath:o,remotePath:e,child:t,wait:a}}return eV.set(a,s),eZ(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",r.id,"pull",l.remotePath,l.outPath],{allowFailure:!0}),await h("adb",["-s",r.id,"shell","rm","-f",l.remotePath],{allowFailure:!0})}catch{}return s.recording=void 0,eZ(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 o=eV.get(a);if(!o)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session"}};if("start"===i){let a,i;if(o.trace)return{ok:!1,error:{code:"INVALID_ARGS",message:"trace already in progress"}};let r=e0(e.positionals?.[1]??(a=o.name.replace(/[^a-zA-Z0-9._-]/g,"_"),i=new Date().toISOString().replace(/[:.]/g,"-"),n.join(eJ,`${a}-${i}.trace.log`)));return d.mkdirSync(n.dirname(r),{recursive:!0}),d.appendFileSync(r,""),o.trace={outPath:r,startedAt:Date.now()},eZ(o,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{action:"start",outPath:r}}),{ok:!0,data:{trace:"started",outPath:r}}}if(!o.trace)return{ok:!1,error:{code:"INVALID_ARGS",message:"no active trace"}};let r=o.trace.outPath;if(e.positionals?.[1]){let t=e0(e.positionals[1]);d.mkdirSync(n.dirname(t),{recursive:!0}),d.existsSync(r)?d.renameSync(r,t):d.appendFileSync(t,""),r=t}return o.trace=void 0,eZ(o,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{action:"stop",outPath:r}}),{ok:!0,data:{trace:"stopped",outPath:r}}}if("settings"===t){let i=e.positionals?.[0],o=e.positionals?.[1];if(!i||!o)return{ok:!1,error:{code:"INVALID_ARGS",message:"settings requires <wifi|airplane|location> <on|off>"}};let n=eV.get(a),r=n?.device??await eE(e.flags??{});n||await e4(r);let s=n?.appBundleId,l=await eR(r,"settings",[i,o,s??""],e.flags?.out,{...ez(e.flags,s,n?.trace?.outPath)});return n&&eZ(n,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:l??{setting:i,state:o}}),{ok:!0,data:l??{setting:i,state:o}}}if("find"===t){let o=e.positionals??[];if(0===o.length)return{ok:!1,error:{code:"INVALID_ARGS",message:"find requires a locator or text"}};let{locator:n,query:r,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]??"",o=e.slice(a+1);if(0===o.length)return{locator:t,query:i,action:"click"};let n=o[0].toLowerCase();if("get"===n){let e=o[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"===n)return{locator:t,query:i,action:"wait",timeoutMs:function(e){if(!e)return null;let t=Number(e);return Number.isFinite(t)?t:null}(o[1])??void 0};if("exists"===n)return{locator:t,query:i,action:"exists"};if("click"===n)return{locator:t,query:i,action:"click"};if("focus"===n)return{locator:t,query:i,action:"focus"};if("fill"===n)return{locator:t,query:i,action:"fill",value:o.slice(1).join(" ")};if("type"===n)return{locator:t,query:i,action:"type",value:o.slice(1).join(" ")};throw new f("INVALID_ARGS",`Unsupported find action: ${o[0]}`)}(o);if(!r)return{ok:!1,error:{code:"INVALID_ARGS",message:"find requires a value"}};let u=eV.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 eE(e.flags??{});u||await e4(d);let p=u?.appBundleId,h="role"!==n?r:void 0,m="click"===s||"focus"===s||"fill"===s||"type"===s,w=0,g=null,v=async()=>{let t=Date.now();if(g&&t-w<750)return{nodes:g};let i=await eR(d,"snapshot",[],e.flags?.out,{...ez({...e.flags,snapshotScope:h,snapshotInteractiveOnly:m,snapshotCompact:m},p,u?.trace?.outPath)}),o=i?.nodes??[],n=eC(e.flags?.snapshotRaw?o:e6(o));return w=t,g=n,u&&(u.snapshot={nodes:n,truncated:i?.truncated,createdAt:Date.now(),backend:i?.backend},eV.set(a,u)),{nodes:n,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 v();if(eB(a,n,r,{requireRect:!1}))return u&&eZ(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:y}=await v(),I=eB(y,n,r,{requireRect:m});if(!I)return{ok:!1,error:{code:"COMMAND_FAILED",message:"find did not match any element"}};let N=`@${I.ref}`,A={...e.flags??{},noRecord:!0};if("exists"===s)return u&&eZ(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&&eZ(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&&eZ(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 eH({token:eX,session:a,command:"click",positionals:[N],flags:A});return i.ok&&u&&eZ(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 eH({token:eX,session:a,command:"fill",positionals:[N,l],flags:A});return i.ok&&u&&eZ(u,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{ref:N,action:"fill"}}),i}if("focus"===s){let a=I.rect?eF(I.rect):null;if(!a)return{ok:!1,error:{code:"COMMAND_FAILED",message:"matched element has no bounds"}};let i=await eR(d,"focus",[String(a.x),String(a.y)],e.flags?.out,{...ez(e.flags,u?.appBundleId,u?.trace?.outPath)});return u&&eZ(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?eF(I.rect):null;if(!a)return{ok:!1,error:{code:"COMMAND_FAILED",message:"matched element has no bounds"}};await eR(d,"focus",[String(a.x),String(a.y)],e.flags?.out,{...ez(e.flags,u?.appBundleId,u?.trace?.outPath)});let i=await eR(d,"type",[l],e.flags?.out,{...ez(e.flags,u?.appBundleId,u?.trace?.outPath)});return u&&eZ(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=eV.get(a);if(!i?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"No snapshot in session. Run snapshot first."}};let o=e.positionals?.[0]??"",n=eM(o);if(!n)return{ok:!1,error:{code:"INVALID_ARGS",message:"click requires a ref like @e2"}};let r=eT(i.snapshot.nodes,n);if(!r?.rect&&e.positionals.length>1){let t=e.positionals.slice(1).join(" ").trim();t.length>0&&(r=e2(i.snapshot.nodes,t))}if(!r?.rect)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${o} not found or has no bounds`}};let s=e3(r,i.snapshot.nodes),{x:l,y:c}=eF(r.rect);return await eR(i.device,"press",[String(l),String(c)],e.flags?.out,{...ez(e.flags,i.appBundleId,i.trace?.outPath)}),eZ(i,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{ref:n,x:l,y:c,refLabel:s}}),{ok:!0,data:{ref:n,x:l,y:c}}}if("fill"===t){let i=eV.get(a);if(e.positionals?.[0]?.startsWith("@")){let a;if(!i?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"No snapshot in session. Run snapshot first."}};let o=eM(e.positionals[0]);if(!o)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 s=eT(i.snapshot.nodes,o);if(!s?.rect&&n&&(s=e2(i.snapshot.nodes,n)),!s?.rect)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${e.positionals[0]} not found or has no bounds`}};let l=e3(s,i.snapshot.nodes),c=s.label?.trim();if("ios"===i.device.platform&&"simulator"===i.device.kind&&("textfield"===(a=e7(s.type??""))||"textview"===a||"searchfield"===a||"textarea"===a)){let a=s.rect?eF(s.rect):null;return a?(await eR(i.device,"focus",[String(a.x),String(a.y)],e.flags?.out,{...ez(e.flags,i.appBundleId,i.trace?.outPath)}),await ew(i.device,{command:"type",text:r,appBundleId:i.appBundleId},{verbose:e.flags?.verbose,logPath:eq,traceLogPath:i?.trace?.outPath}),eZ(i,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{ref:o,refLabel:l??c,action:"fill",text:r}}),{ok:!0,data:{ref:o}}):{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${e.positionals[0]} not found or has no bounds`}}}let{x:u,y:d}=eF(s.rect),p=await eR(i.device,"fill",[String(u),String(d),r],e.flags?.out,{...ez(e.flags,i.appBundleId,i.trace?.outPath)});return eZ(i,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:p??{ref:o,x:u,y:d,refLabel:l}}),{ok:!0,data:p??{ref:o,x:u,y:d}}}}if("get"===t){let i=e.positionals?.[0],o=e.positionals?.[1];if("text"!==i&&"attrs"!==i)return{ok:!1,error:{code:"INVALID_ARGS",message:"get only supports text or attrs"}};let n=eV.get(a);if(!n?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"No snapshot in session. Run snapshot first."}};let r=eM(o??"");if(!r)return{ok:!1,error:{code:"INVALID_ARGS",message:"get text requires a ref like @e2"}};let s=eT(n.snapshot.nodes,r);if(!s&&e.positionals.length>2){let t=e.positionals.slice(2).join(" ").trim();t.length>0&&(s=e2(n.snapshot.nodes,t))}if(!s)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${o} not found`}};if("attrs"===i)return eZ(n,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{ref:r}}),{ok:!0,data:{ref:r,node:s}};let l=[s.label,s.value,s.identifier].map(e=>"string"==typeof e?e.trim():"").filter(e=>e.length>0)[0]??"";return eZ(n,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:{ref:r,text:l,refLabel:l||void 0}}),{ok:!0,data:{ref:r,text:l,node:s}}}let o=eV.get(a);if(!o)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session. Run open first."}};let r=await eR(o.device,t,e.positionals??[],e.flags?.out,{...ez(e.flags,o.appBundleId,o.trace?.outPath)});return eZ(o,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:r??{}}),{ok:!0,data:r??{}}}function eZ(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:o,out:n,verbose:r,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:o,out:n,verbose:r,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 eY(e,t,a){let i=eK(await eR(e,"snapshot",[],a?.out,{...ez({...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 o=eK(await eR(e,"snapshot",[],a?.out,{...ez({...a,snapshotDepth:1,snapshotCompact:!0,snapshotBackend:"xctest"},void 0,t)}));return{appName:o?.appName??o?.appBundleId??"unknown",appBundleId:o?.appBundleId,source:"snapshot-xctest"}}function eK(e){let t=eC(e?.nodes??[]),a=t.find(e=>"application"===e7(e.type??""))??t[0];if(!a)return null;let i=a.label?.trim(),o=a.identifier?.trim();return i||o?{appName:i||void 0,appBundleId:o||void 0}:null}function eQ(e){try{d.existsSync(eJ)||d.mkdirSync(eJ,{recursive:!0});let t=e.name.replace(/[^a-zA-Z0-9._-]/g,"_"),a=new Date(e.createdAt).toISOString().replace(/[:.]/g,"-"),i=n.join(eJ,`${t}-${a}.ad`),o=n.join(eJ,`${t}-${a}.json`),r={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,'\\"'),o=e.device.kind?` kind=${e.device.kind}`:"";for(let n of(a.push(`context platform=${e.device.platform} device="${i}"${o} theme=unknown`),t))n.flags?.noRecord||a.push(function(e){let t=[e.command];if("click"===e.command){let a=e.positionals?.[0];if(a){t.push(e1(a));let i=e.result?.refLabel;return"string"==typeof i&&i.trim().length>0&&t.push(e1(i)),t.join(" ")}}if("fill"===e.command){let a=e.positionals?.[0];if(a&&a.startsWith("@")){t.push(e1(a));let i=e.result?.refLabel,o=e.positionals.slice(1).join(" ");return"string"==typeof i&&i.trim().length>0&&t.push(e1(i)),o&&t.push(e1(o)),t.join(" ")}}if("get"===e.command){let a=e.positionals?.[0],i=e.positionals?.[1];if(a&&i){t.push(e1(a)),t.push(e1(i));let o=e.result?.refLabel;return"string"==typeof o&&o.trim().length>0&&t.push(e1(o)),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",e1(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(e1(a));return t.join(" ")}(n));return`${a.join("\n")}
6
+ `}(e,r.optimizedActions);d.writeFileSync(i,s),e.actions.some(e=>e.flags?.recordJson)&&d.writeFileSync(o,JSON.stringify(r,null,2))}catch{}}function e0(e){return e.startsWith("~/")?n.join(p.homedir(),e.slice(2)):n.resolve(e)}function e1(e){let t=e.trim();return t.startsWith("@")||/^-?\d+(\.\d+)?$/.test(t)?t:JSON.stringify(t)}function e2(e,t){let a=t.toLowerCase();return e.find(e=>{let t=(e.label??"").toLowerCase(),i=(e.value??"").toLowerCase(),o=(e.identifier??"").toLowerCase();return t.includes(a)||i.includes(a)||o.includes(a)})??null}function e3(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&&e8(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||!e8(t))continue;let o=Math.abs(e.rect.y+e.rect.height/2-a);(!i||o<i.distance)&&(i={label:t,distance:o})}return i?.label}(e,t)??(a&&e8(a)?a:void 0)}function e8(e){let t=e.trim();return!(!t||/^(true|false)$/i.test(t)||/^\d+$/.test(t))}async function e4(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 e6(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 o=e7(i.type??""),n=[i.label,i.value,i.identifier].map(e=>"string"==typeof e?e.trim():"").find(e=>e&&e.length>0),r=!!n&&e8(n);if(("group"===o||"ioscontentgroup"===o)&&!r){t.push(e);continue}let s=Math.max(0,e-t.length);a.push({...i,depth:s})}return a}function e7(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,o=t.slice(0,i).trim();if(t=t.slice(i+1),0===o.length){i=t.indexOf("\n");continue}try{let e=JSON.parse(o);a=await eH(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(ej)||d.mkdirSync(ej,{recursive:!0}),d.writeFileSync(eq,""),d.writeFileSync(eG,JSON.stringify({port:a,token:eX,pid:process.pid,version:eW},null,2),{mode:384}),process.stdout.write(`AGENT_DEVICE_DAEMON_PORT=${t.port}
8
+ `)}}),t=async()=>{for(let e of Array.from(eV.values()))"ios"===e.device.platform&&"simulator"===e.device.kind&&await ev(e.device.id),eQ(e);e.close(()=>{d.existsSync(eG)&&d.unlinkSync(eG),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}
9
9
  `),t()});
@@ -65,6 +65,7 @@ final class RunnerTests: XCTestCase {
65
65
  }
66
66
  case .failed(let error):
67
67
  NSLog("AGENT_DEVICE_RUNNER_LISTENER_FAILED=%@", String(describing: error))
68
+ self?.doneExpectation?.fulfill()
68
69
  default:
69
70
  break
70
71
  }
@@ -80,7 +81,9 @@ final class RunnerTests: XCTestCase {
80
81
  return
81
82
  }
82
83
  NSLog("AGENT_DEVICE_RUNNER_WAITING")
83
- let result = XCTWaiter.wait(for: [expectation], timeout: resolveRunnerTimeout())
84
+ let timeout = resolveRunnerTimeout()
85
+ let effectiveTimeout = timeout > 0 ? timeout : 24 * 60 * 60
86
+ let result = XCTWaiter.wait(for: [expectation], timeout: effectiveTimeout)
84
87
  NSLog("AGENT_DEVICE_RUNNER_WAIT_RESULT=%@", String(describing: result))
85
88
  if result != .completed {
86
89
  XCTFail("runner wait ended with \(result)")
@@ -471,11 +474,14 @@ final class RunnerTests: XCTestCase {
471
474
  break
472
475
  }
473
476
  if let limit = options.depth, depth > limit { continue }
474
- if !isVisibleInViewport(snapshot.frame, viewport) { continue }
475
477
 
476
478
  let label = aggregatedLabel(for: snapshot) ?? snapshot.label.trimmingCharacters(in: .whitespacesAndNewlines)
477
479
  let identifier = snapshot.identifier.trimmingCharacters(in: .whitespacesAndNewlines)
478
480
  let valueText = snapshotValueText(snapshot)
481
+ let hasContent = !label.isEmpty || !identifier.isEmpty || (valueText != nil)
482
+ if !isVisibleInViewport(snapshot.frame, viewport) && !hasContent {
483
+ continue
484
+ }
479
485
 
480
486
  let include = shouldInclude(
481
487
  snapshot: snapshot,
@@ -638,7 +644,7 @@ final class RunnerTests: XCTestCase {
638
644
  }
639
645
 
640
646
  private func aggregatedLabel(for snapshot: XCUIElementSnapshot, depth: Int = 0) -> String? {
641
- if depth > 2 { return nil }
647
+ if depth > 4 { return nil }
642
648
  let text = snapshot.label.trimmingCharacters(in: .whitespacesAndNewlines)
643
649
  if !text.isEmpty { return text }
644
650
  if let valueText = snapshotValueText(snapshot) { return valueText }
@@ -698,7 +704,7 @@ private func resolveRunnerTimeout() -> TimeInterval {
698
704
  let parsed = Double(env) {
699
705
  return parsed
700
706
  }
701
- return 300
707
+ return 0
702
708
  }
703
709
 
704
710
  enum CommandType: String, Codable {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-device",
3
- "version": "0.1.9",
3
+ "version": "0.2.0",
4
4
  "description": "Unified control plane for physical and virtual devices via an agent-driven CLI.",
5
5
  "license": "MIT",
6
6
  "author": "Callstack",
@@ -39,21 +39,17 @@ agent-device session list # List active sessions
39
39
  ### Snapshot (page analysis)
40
40
 
41
41
  ```bash
42
- agent-device snapshot # Full accessibility tree
43
- agent-device snapshot -i # Interactive elements only (recommended)
44
- agent-device snapshot -c # Compact output
45
- agent-device snapshot -d 3 # Limit depth
42
+ agent-device snapshot # Full XCTest accessibility tree snapshot
43
+ agent-device snapshot -i # Interactive elements only (recommended)
44
+ agent-device snapshot -c # Compact output
45
+ agent-device snapshot -d 3 # Limit depth
46
46
  agent-device snapshot -s "Camera" # Scope to label/identifier
47
- agent-device snapshot --raw # Raw node output
48
- agent-device snapshot --backend xctest # Default: full XCTest snapshot (most complete)
49
- agent-device snapshot --backend ax # macOS Accessibility tree (fast, needs permissions)
50
- agent-device snapshot --backend xctest # XCTest snapshot (slow, no permissions)
47
+ agent-device snapshot --raw # Raw node output
48
+ agent-device snapshot --backend xctest # XCTest snapshot (fast, complete, no permissions)
49
+ agent-device snapshot --backend ax # macOS Accessibility tree (fast, needs permissions, less fidelity, optional)
51
50
  ```
52
51
 
53
- Hybrid will automatically fill empty containers (e.g. `group`, `tab bar`) by scoping XCTest to the container label.
54
- It is recommended because AX is fast but can miss UI details, while XCTest is slower but more complete.
55
- If you want explicit control or AX is unavailable, use `--backend xctest`.
56
- Use `--backend ax` when you need faster snapshots and can tolerate missing details.
52
+ XCTest is the default: fast and complete and does not require permissions. Use it in most cases and only fall back to AX when something breaks.
57
53
 
58
54
  ### Find (semantic)
59
55
 
@@ -46,4 +46,4 @@ agent-device snapshot -i -s @e3 --platform ios
46
46
 
47
47
  - Ref not found: re-snapshot.
48
48
  - AX returns Simulator window: restart Simulator and re-run.
49
- - AX empty: verify Accessibility permission or use `--backend xctest` (XCTest is slower but more complete).
49
+ - AX empty: verify Accessibility permission or use `--backend xctest` (XCTest is more complete).
@@ -359,12 +359,9 @@ async function waitForRunner(
359
359
  command: RunnerCommand,
360
360
  logPath?: string,
361
361
  ): Promise<Response> {
362
- if (logPath) {
363
- await waitForRunnerReady(logPath, 4000);
364
- }
365
362
  const start = Date.now();
366
363
  let lastError: unknown = null;
367
- while (Date.now() - start < 8000) {
364
+ while (Date.now() - start < 15000) {
368
365
  try {
369
366
  const response = await fetch(`http://127.0.0.1:${port}/command`, {
370
367
  method: 'POST',
@@ -381,23 +378,9 @@ async function waitForRunner(
381
378
  const simResponse = await postCommandViaSimulator(device.id, port, command);
382
379
  return new Response(simResponse.body, { status: simResponse.status });
383
380
  }
384
- const fallbackPort = logPath ? extractPortFromLog(logPath) : null;
385
- if (fallbackPort && fallbackPort !== port) {
386
- try {
387
- const response = await fetch(`http://127.0.0.1:${fallbackPort}/command`, {
388
- method: 'POST',
389
- headers: { 'Content-Type': 'application/json' },
390
- body: JSON.stringify(command),
391
- });
392
- return response;
393
- } catch (err) {
394
- lastError = err;
395
- }
396
- }
397
381
 
398
382
  throw new AppError('COMMAND_FAILED', 'Runner did not accept connection', {
399
383
  port,
400
- fallbackPort,
401
384
  logPath,
402
385
  lastError: lastError ? String(lastError) : undefined,
403
386
  });
@@ -455,43 +438,6 @@ async function getFreePort(): Promise<number> {
455
438
  });
456
439
  }
457
440
 
458
- async function waitForRunnerReady(logPath: string, timeoutMs: number): Promise<void> {
459
- if (!fs.existsSync(logPath)) return;
460
- const start = Date.now();
461
- let offset = 0;
462
- while (Date.now() - start < timeoutMs) {
463
- if (!fs.existsSync(logPath)) return;
464
- const stats = fs.statSync(logPath);
465
- if (stats.size > offset) {
466
- const fd = fs.openSync(logPath, 'r');
467
- const buffer = Buffer.alloc(stats.size - offset);
468
- fs.readSync(fd, buffer, 0, buffer.length, offset);
469
- fs.closeSync(fd);
470
- offset = stats.size;
471
- const text = buffer.toString('utf8');
472
- if (
473
- text.includes('AGENT_DEVICE_RUNNER_LISTENER_READY') ||
474
- text.includes('AGENT_DEVICE_RUNNER_PORT=')
475
- ) {
476
- return;
477
- }
478
- }
479
- await new Promise((resolve) => setTimeout(resolve, 100));
480
- }
481
- }
482
-
483
- function extractPortFromLog(logPath: string): number | null {
484
- try {
485
- if (!fs.existsSync(logPath)) return null;
486
- const text = fs.readFileSync(logPath, 'utf8');
487
- const match = text.match(/AGENT_DEVICE_RUNNER_PORT=(\d+)/);
488
- if (match) return Number(match[1]);
489
- } catch {
490
- return null;
491
- }
492
- return null;
493
- }
494
-
495
441
  async function prepareXctestrunWithEnv(
496
442
  xctestrunPath: string,
497
443
  envVars: Record<string, string>,