agent-device 0.2.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +41 -4
  2. package/dist/src/bin.js +26 -21
  3. package/dist/src/daemon.js +9 -8
  4. package/ios-runner/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/project.pbxproj +2 -0
  5. package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +15 -0
  6. package/package.json +3 -2
  7. package/skills/agent-device/SKILL.md +22 -6
  8. package/skills/agent-device/references/session-management.md +9 -0
  9. package/skills/agent-device/references/snapshot-refs.md +18 -5
  10. package/skills/agent-device/references/video-recording.md +2 -2
  11. package/src/cli.ts +6 -0
  12. package/src/core/__tests__/capabilities.test.ts +67 -0
  13. package/src/core/capabilities.ts +49 -0
  14. package/src/core/dispatch.ts +29 -118
  15. package/src/daemon/__tests__/is-predicates.test.ts +68 -0
  16. package/src/daemon/__tests__/selectors.test.ts +128 -0
  17. package/src/daemon/__tests__/session-routing.test.ts +108 -0
  18. package/src/daemon/__tests__/session-selector.test.ts +64 -0
  19. package/src/daemon/__tests__/session-store.test.ts +95 -0
  20. package/src/daemon/__tests__/snapshot-processing.test.ts +47 -0
  21. package/src/daemon/action-utils.ts +29 -0
  22. package/src/daemon/app-state.ts +66 -0
  23. package/src/daemon/context.ts +36 -0
  24. package/src/daemon/device-ready.ts +13 -0
  25. package/src/daemon/handlers/__tests__/find.test.ts +99 -0
  26. package/src/daemon/handlers/__tests__/replay-heal.test.ts +364 -0
  27. package/src/daemon/handlers/__tests__/snapshot.test.ts +128 -0
  28. package/src/daemon/handlers/find.ts +304 -0
  29. package/src/daemon/handlers/interaction.ts +510 -0
  30. package/src/daemon/handlers/parse-utils.ts +8 -0
  31. package/src/daemon/handlers/record-trace.ts +154 -0
  32. package/src/daemon/handlers/session.ts +732 -0
  33. package/src/daemon/handlers/snapshot.ts +396 -0
  34. package/src/daemon/is-predicates.ts +46 -0
  35. package/src/daemon/selectors.ts +423 -0
  36. package/src/daemon/session-routing.ts +22 -0
  37. package/src/daemon/session-selector.ts +39 -0
  38. package/src/daemon/session-store.ts +275 -0
  39. package/src/daemon/snapshot-processing.ts +127 -0
  40. package/src/daemon/types.ts +55 -0
  41. package/src/daemon.ts +66 -1592
  42. package/src/platforms/ios/index.ts +0 -62
  43. package/src/platforms/ios/runner-client.ts +2 -0
  44. package/src/utils/args.ts +19 -10
  45. package/src/utils/interactors.ts +102 -16
  46. package/src/utils/snapshot.ts +1 -0
@@ -1,9 +1,10 @@
1
- let e,t;import a from"node:crypto";import{isCancel as i,select as n}from"@clack/prompts";import{node_path as r,runCmdStreaming as o,promises as s,asAppError as l,fileURLToPath as c,runCmdBackground as u,node_fs as d,node_os as p,errors_AppError as f,runCmd as h,node_net as m,whichCmd as w}from"./861.js";async function g(e,t){let a=e,r=e=>e.toLowerCase().replace(/_/g," ").replace(/\s+/g," ").trim();if(t.platform&&(a=a.filter(e=>e.platform===t.platform)),t.udid){let e=a.find(e=>e.id===t.udid&&"ios"===e.platform);if(!e)throw new f("DEVICE_NOT_FOUND",`No iOS device with UDID ${t.udid}`);return e}if(t.serial){let e=a.find(e=>e.id===t.serial&&"android"===e.platform);if(!e)throw new f("DEVICE_NOT_FOUND",`No Android device with serial ${t.serial}`);return e}if(t.deviceName){let e=r(t.deviceName),i=a.find(t=>r(t.name)===e);if(!i)throw new f("DEVICE_NOT_FOUND",`No device named ${t.deviceName}`);return i}if(1===a.length)return a[0];if(0===a.length)throw new f("DEVICE_NOT_FOUND","No devices found",{selector:t});let o=a.filter(e=>e.booted);if(1===o.length)return o[0];if(!process.env.CI&&process.stdin.isTTY&&process.stdout.isTTY){let e=await n({message:"Multiple devices available. Choose a device to continue:",options:(o.length>0?o:a).map(e=>({label:`${e.name} (${e.platform}${e.kind?`, ${e.kind}`:""}${e.booted?", booted":""})`,value:e.id}))});if(i(e))throw new f("INVALID_ARGS","Device selection cancelled");if(e){let t=a.find(t=>t.id===e);if(t)return t}}return o[0]??a[0]}async function y(){if(!await w("adb"))throw new f("TOOL_MISSING","adb not found in PATH");let e=(await h("adb",["devices","-l"])).stdout.split("\n").map(e=>e.trim()),t=[];for(let a of e){if(!a||a.startsWith("List of devices"))continue;let e=a.split(/\s+/),i=e[0];if("device"!==e[1])continue;let n=(e.find(e=>e.startsWith("model:"))??"").replace("model:","").replace(/_/g," ").trim()||i;if(i.startsWith("emulator-")){let e=await h("adb",["-s",i,"emu","avd","name"],{allowFailure:!0}),t=e.stdout.trim();0===e.exitCode&&t&&(n=t.replace(/_/g," "))}let r=await v(i);t.push({platform:"android",id:i,name:n,kind:i.startsWith("emulator-")?"emulator":"device",booted:r})}return t}async function v(e){try{let t=await h("adb",["-s",e,"shell","getprop","sys.boot_completed"],{allowFailure:!0});return"1"===t.stdout.trim()}catch{return!1}}async function I(e,t=6e4){let a=Date.now();for(;Date.now()-a<t;){if(await v(e))return;await new Promise(e=>setTimeout(e,1e3))}throw new f("COMMAND_FAILED","Android device did not finish booting in time",{serial:e,timeoutMs:t})}async function N(e,t={}){let a,i=t.attempts??3,n=t.baseDelayMs??200,r=t.maxDelayMs??2e3,o=t.jitter??.2;for(let s=1;s<=i;s+=1)try{return await e()}catch(l){if(a=l,s>=i||t.shouldRetry&&!t.shouldRetry(l,s))break;let e=function(e,t,a,i){let n=Math.min(t,e*2**(i-1));return Math.max(0,n+n*a*(2*Math.random()-1))}(n,r,o,s);await function(e){return new Promise(t=>setTimeout(t,e))}(e)}if(a)throw a;throw new f("COMMAND_FAILED","retry failed")}let 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 x(e,[["shell","dumpsys","window","windows"],["shell","dumpsys","window"]]);if(t)return t;let a=await x(e,[["shell","dumpsys","activity","activities"],["shell","dumpsys","activity"]]);return a||{}}async function x(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 O(e,t,a){e.booted||await I(e.id);let i=await S(e,t);if("intent"===i.type){if(a)throw new f("INVALID_ARGS","Activity override requires a package name, not an intent");await h("adb",b(e,["shell","am","start","-a",i.value]));return}if(a){let t=a.includes("/")?a:`${i.value}/${a.startsWith(".")?a:`.${a}`}`;await h("adb",b(e,["shell","am","start","-a","android.intent.action.MAIN","-c","android.intent.category.DEFAULT","-c","android.intent.category.LAUNCHER","-n",t]));return}await h("adb",b(e,["shell","am","start","-a","android.intent.action.MAIN","-c","android.intent.category.DEFAULT","-c","android.intent.category.LAUNCHER","-p",i.value]))}async function L(e){e.booted||await I(e.id)}async function _(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 M(e,t,a){await h("adb",b(e,["shell","input","tap",String(t),String(a)]))}async function C(e){await h("adb",b(e,["shell","input","keyevent","4"]))}async function R(e){await h("adb",b(e,["shell","input","keyevent","3"]))}async function E(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 $(e,t,a){await M(e,t,a)}async function B(e,t,a,i){await $(e,t,a);let n=null;for(let s of[{clearPadding:12,minClear:8,maxClear:48,chunkSize:4,delayMs:0},{clearPadding:24,minClear:16,maxClear:96,chunkSize:1,delayMs:15}]){var r,o;let l=(r=i.length+s.clearPadding,o=s.minClear,Math.max(o,Math.min(s.maxClear,r)));if(await K(e,l),await Y(e,i,s.chunkSize,s.delayMs),(n=await Z(e,t,a))===i)return}throw new f("COMMAND_FAILED","Android fill verification failed",{expected:i,actual:n??null})}async function U(e,t,a=.6){let{width:i,height:n}=await J(e),r=Math.floor(i*a),o=Math.floor(n*a),s=Math.floor(i/2),l=Math.floor(n/2),c=s,u=l,d=s,p=l;switch(t){case"up":u=l-Math.floor(o/2),p=l+Math.floor(o/2);break;case"down":u=l+Math.floor(o/2),p=l-Math.floor(o/2);break;case"left":c=s-Math.floor(r/2),d=s+Math.floor(r/2);break;case"right":c=s+Math.floor(r/2),d=s-Math.floor(r/2);break;default:throw new f("INVALID_ARGS",`Unknown direction: ${t}`)}await h("adb",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],r=/text="([^"]*)"/.exec(t),o=/content-desc="([^"]*)"/.exec(t),s=(r?.[1]??"").toLowerCase(),l=(o?.[1]??"").toLowerCase();if(s.includes(a)||l.includes(a)){let e=/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/.exec(t);if(e){let t=Number(e[1]),a=Number(e[2]);return{x:Math.floor((t+Number(e[3]))/2),y:Math.floor((a+Number(e[4]))/2)}}return{x:0,y:0}}n=i.exec(e)}return null}(a,t))return;await U(e,"down",.5)}throw new f("COMMAND_FAILED",`Could not find element containing "${t}" after scrolling`)}async function j(e,t){let a=await h("adb",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 r=et(t),o=ea(r.bounds),s=a[a.length-1],l={type:r.className,label:r.text||r.desc,value:r.text,identifier:r.resourceId,rect:o,enabled:r.enabled,hittable:r.clickable??r.focusable,depth:s.depth+1,parentIndex:void 0,children:[]};s.children.push(l),t.endsWith("/>")||a.push(l),n=i.exec(e)}return t}(e),n=[],r=!1,o=a.depth??1/0,s=a.scope?function(e,t){let a=t.toLowerCase(),i=[...e.children];for(;i.length>0;){let e=i.shift(),t=e.label?.toLowerCase()??"",n=e.value?.toLowerCase()??"",r=e.identifier?.toLowerCase()??"";if(t.includes(a)||n.includes(a)||r.includes(a))return e;i.push(...e.children)}return null}(i,a.scope):null,l=s?[s]:i.children,c=new Map,u=e=>{let t=c.get(e);if(void 0!==t)return t;for(let t of e.children)if(t.hittable||u(t))return c.set(e,!0),!0;return c.set(e,!1),!1},d=(e,t,i,s=!1,l=!1)=>{var c,p,f,h,m,w;let g,y,v,I,N,A,b,S;if(n.length>=800){r=!0;return}if(t>o)return;let D=!!a.raw||(c=e,p=a,f=s,h=u(e),m=l,y=ei(c.type),v=!!(c.label&&c.label.trim().length>0),I=!!(c.identifier&&c.identifier.trim().length>0),N=v&&!en(c.label??""),A=I&&!en(c.identifier??""),b=(g=(w=y).split(".").pop()??w).includes("layout")||"viewgroup"===g||"view"===g,S="imageview"===y||"imagebutton"===y,p.interactiveOnly?!!c.hittable||!!(N||A)&&!S&&(!b||!!m)&&(f||h||m):p.compact?N||A||!!c.hittable:!b&&!S||!!c.hittable||!!N||!!A&&!!h||h),k=i;D&&(k=n.length,n.push({index:k,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:i}));let P=s||!!e.hittable,x=l||function(e){if(!e)return!1;let t=ei(e);return t.includes("recyclerview")||t.includes("listview")||t.includes("gridview")}(e.type);for(let a of e.children)if(d(a,t+1,k,P,x),r)return};for(let e of l)if(d(e,0,void 0,!1,!1),r)break;return r?{nodes:n,truncated:r}:{nodes:n}}(await X(e),800,t)}async function W(){if(!await w("adb"))throw new f("TOOL_MISSING","adb not found in PATH")}async function J(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(e,t,a,i){let n=Math.max(1,Math.floor(a));for(let a=0;a<t.length;a+=n){let r=t.slice(a,a+n);await F(e,r),i>0&&a+n<t.length&&await ee(i)}}async function K(e,t){let a=Math.max(0,t);await h("adb",b(e,["shell","input","keyevent","KEYCODE_MOVE_END"]),{allowFailure:!0});for(let t=0;t<a;t+=24){let i=Math.min(24,a-t);await h("adb",b(e,["shell","input","keyevent",...Array(i).fill("KEYCODE_DEL")]),{allowFailure:!0})}}async function Z(e,t,a){let i,n=await X(e),r=/<node\b[^>]*>/g,o=null,s=null,l=null;for(;null!==(i=r.exec(n));){let e=et(i[0]),n=ea(e.bounds);if(!n)continue;let r=e.className??"",c=(e.text??"").replace(/&quot;/g,'"').replace(/&apos;/g,"'").replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&amp;/g,"&"),u=e.focused??!1;if(!c)continue;let d=Math.max(1,n.width*n.height),p=t>=n.x&&t<=n.x+n.width&&a>=n.y&&a<=n.y+n.height;if(u&&Q(r)){(!o||d<=o.area)&&(o={text:c,area:d});continue}if(p&&Q(r)){(!s||d<=s.area)&&(s={text:c,area:d});continue}p&&(!l||d<=l.area)&&(l={text:c,area:d})}return o?.text??s?.text??l?.text??null}function Q(e){let t=e.toLowerCase();return t.includes("edittext")||t.includes("textfield")}async function ee(e){await new Promise(t=>setTimeout(t,e))}function et(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"),focused:a("focused")}}function ea(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)}}function ei(e){return e?e.toLowerCase():""}function en(e){let t=e.trim();return!!t&&/^[\w.]+:id\/[\w.-]+$/i.test(t)}async function er(){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 eo={settings:"com.apple.Preferences"};async function es(e,t){let a=t.trim();if(a.includes("."))return a;let i=eo[a.toLowerCase()];if(i)return i;if("simulator"===e.kind){let i=(await eN(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 el(e,t){let a=await es(e,t);if("simulator"===e.kind){await eA(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 ec(e){"simulator"!==e.kind||"Booted"!==await eb(e.id)&&(await eA(e),await h("open",["-a","Simulator"],{allowFailure:!0}))}async function eu(e,t){let a=await es(e,t);if("simulator"===e.kind){await eA(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 ed(e,t,a){throw eI(e,"press"),new f("UNSUPPORTED_OPERATION","simctl io tap is not available; use the XCTest runner for input")}async function ep(e,t,a,i=800){throw eI(e,"long-press"),new f("UNSUPPORTED_OPERATION","long-press is not supported on iOS simulators without XCTest runner support")}async function ef(e,t,a){await ed(e,t,a)}async function eh(e,t){throw eI(e,"type"),new f("UNSUPPORTED_OPERATION","simctl io keyboard is not available; use the XCTest runner for input")}async function em(e,t,a,i){await ef(e,t,a),await eh(e,i)}async function ew(e,t,a=.6){throw eI(e,"scroll"),new f("UNSUPPORTED_OPERATION","simctl io swipe is not available; use the XCTest runner for input")}async function eg(e){throw new f("UNSUPPORTED_OPERATION",`scrollintoview is not supported on iOS without UI automation (${e})`)}async function ey(e,t){if("simulator"===e.kind){await eA(e),await h("xcrun",["simctl","io",e.id,"screenshot",t]);return}await h("xcrun",["devicectl","device","screenshot","--device",e.id,t])}async function ev(e,t,a,i){eI(e,"settings"),await eA(e);let n=t.toLowerCase(),r=function(e){let t=e.toLowerCase();if("on"===t||"true"===t||"1"===t)return!0;if("off"===t||"false"===t||"0"===t)return!1;throw new f("INVALID_ARGS",`Invalid setting state: ${e}`)}(a);switch(n){case"wifi":return void await h("xcrun",["simctl","status_bar",e.id,"override","--wifiMode",r?"active":"failed"]);case"airplane":r?await h("xcrun",["simctl","status_bar",e.id,"override","--dataNetwork","hide","--wifiMode","failed","--wifiBars","0","--cellularMode","failed","--cellularBars","0","--operatorName",""]):await h("xcrun",["simctl","status_bar",e.id,"clear"]);return;case"location":if(!i)throw new f("INVALID_ARGS","location setting requires an active app in session");await h("xcrun",["simctl","privacy",e.id,r?"grant":"revoke","location",i]);return;default:throw new f("INVALID_ARGS",`Unsupported setting: ${t}`)}}function eI(e,t){if("simulator"!==e.kind)throw new f("UNSUPPORTED_OPERATION",`${t} is only supported on iOS simulators in v1`)}async function eN(e){let t=(await 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 eA(e){"simulator"!==e.kind||"Booted"!==await eb(e.id)&&(await h("xcrun",["simctl","boot",e.id],{allowFailure:!0}),await h("xcrun",["simctl","bootstatus",e.id,"-b"],{allowFailure:!0}))}async function eb(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 eS=new Map;async function eD(e,t,a={}){var i;return"snapshot"===(i=t.command)||"findText"===i||"listTappables"===i||"alert"===i?N(()=>ek(e,t,a),{shouldRetry:eC}):ek(e,t,a)}async function ek(e,t,a={}){if("simulator"!==e.kind)throw new f("UNSUPPORTED_OPERATION","iOS runner only supports simulators in v1");try{let i=await eO(e,a),n=await eR(e,i.port,t,a.logPath),r=await n.text(),o={};try{o=JSON.parse(r)}catch{throw new f("COMMAND_FAILED","Invalid runner response",{text:r})}if(!o.ok)throw new f("COMMAND_FAILED",o.error?.message??"Runner error",{runner:o,xcodebuild:{exitCode:1,stdout:"",stderr:""},logPath:a.logPath});return o.data??{}}catch(n){let i=n instanceof f?n:new f("COMMAND_FAILED",String(n));if("COMMAND_FAILED"===i.code&&"string"==typeof i.message&&i.message.includes("Runner did not accept connection")){await eP(e.id);let i=await eO(e,a),n=await eR(e,i.port,t,a.logPath),r=await n.text(),o={};try{o=JSON.parse(r)}catch{throw new f("COMMAND_FAILED","Invalid runner response",{text:r})}if(!o.ok)throw new f("COMMAND_FAILED",o.error?.message??"Runner error",{runner:o,xcodebuild:{exitCode:1,stdout:"",stderr:""},logPath:a.logPath});return o.data??{}}throw n}}async function eP(e){let t=eS.get(e);if(t){try{await eR(t.device,t.port,{command:"shutdown"})}catch{}try{await t.testPromise}catch{}e$(t.xctestrunPath),e$(t.jsonPath),eS.delete(e)}}async function ex(e){await h("xcrun",["simctl","bootstatus",e,"-b"],{allowFailure:!0})}async function eO(e,t){let a=eS.get(e.id);if(a)return a;await ex(e.id);let i=await eL(e.id,t),n=await eT(),{xctestrunPath:r,jsonPath:s}=await eF(i,{AGENT_DEVICE_RUNNER_PORT:String(n)},`session-${e.id}-${n}`),l=o("xcodebuild",["test-without-building","-only-testing","AgentDeviceRunnerUITests/RunnerTests/testCommand","-parallel-testing-enabled","NO","-test-timeouts-enabled","NO","-maximum-concurrent-test-simulator-destinations","1","-xctestrun",r,"-destination",`platform=iOS Simulator,id=${e.id}`],{onStdoutChunk:e=>{eM(e,t.logPath,t.traceLogPath,t.verbose)},onStderrChunk:e=>{eM(e,t.logPath,t.traceLogPath,t.verbose)},allowFailure:!0,env:{...process.env,AGENT_DEVICE_RUNNER_PORT:String(n)}}),c={device:e,deviceId:e.id,port:n,xctestrunPath:r,jsonPath:s,testPromise:l};return eS.set(e.id,c),c}async function eL(e,t){let a,i=r.join(p.homedir(),".agent-device","ios-runner"),n=r.join(i,"derived");if((a=process.env.AGENT_DEVICE_IOS_CLEAN_DERIVED)&&["1","true","yes","on"].includes(a.toLowerCase()))try{d.rmSync(n,{recursive:!0,force:!0})}catch{}let s=e_(n);if(s)return s;let l=function(){let e=r.dirname(c(import.meta.url)),t=e;for(let e=0;e<6;e+=1){let e=r.join(t,"package.json");if(d.existsSync(e))return t;t=r.dirname(t)}return e}(),u=r.join(l,"ios-runner","AgentDeviceRunner","AgentDeviceRunner.xcodeproj");if(!d.existsSync(u))throw new f("COMMAND_FAILED","iOS runner project not found",{projectPath:u});try{await o("xcodebuild",["build-for-testing","-project",u,"-scheme","AgentDeviceRunner","-parallel-testing-enabled","NO","-maximum-concurrent-test-simulator-destinations","1","-destination",`platform=iOS Simulator,id=${e}`,"-derivedDataPath",n],{onStdoutChunk:e=>{eM(e,t.logPath,t.traceLogPath,t.verbose)},onStderrChunk:e=>{eM(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=e_(n);if(!h)throw new f("COMMAND_FAILED","Failed to locate .xctestrun after build");return h}function e_(e){if(!d.existsSync(e))return null;let t=[],a=[e];for(;a.length>0;){let e=a.pop();for(let i of d.readdirSync(e,{withFileTypes:!0})){let n=r.join(e,i.name);if(i.isDirectory()){a.push(n);continue}if(i.isFile()&&i.name.endsWith(".xctestrun"))try{let e=d.statSync(n);t.push({path:n,mtimeMs:e.mtimeMs})}catch{}}}return 0===t.length?null:(t.sort((e,t)=>t.mtimeMs-e.mtimeMs),t[0]?.path??null)}function eM(e,t,a,i){t&&d.appendFileSync(t,e),a&&d.appendFileSync(a,e),i&&process.stderr.write(e)}function eC(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 eR(e,t,a,i){let n=Date.now(),r=null;for(;Date.now()-n<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){r=e,await new Promise(e=>setTimeout(e,100))}if("simulator"===e.kind){let i=await eE(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:r?String(r):void 0})}async function eE(e,t,a){let i=JSON.stringify(a),n=await h("xcrun",["simctl","spawn",e,"/usr/bin/curl","-s","-X","POST","-H","Content-Type: application/json","--data",i,`http://127.0.0.1:${t}/command`],{allowFailure:!0}),r=n.stdout;if(0!==n.exitCode)throw new f("COMMAND_FAILED","Runner did not accept connection (simctl spawn)",{port:t,stdout:n.stdout,stderr:n.stderr,exitCode:n.exitCode});return{status:200,body:r}}async function eT(){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 eF(e,t,a){let i,n=r.dirname(e),o=a.replace(/[^a-zA-Z0-9._-]/g,"_"),s=r.join(n,`AgentDeviceRunner.env.${o}.json`),l=r.join(n,`AgentDeviceRunner.env.${o}.xctestrun`),c=await h("plutil",["-convert","json","-o","-",e],{allowFailure:!0});if(0!==c.exitCode||!c.stdout.trim())throw new f("COMMAND_FAILED","Failed to read xctestrun plist",{xctestrunPath:e,stderr:c.stderr});try{i=JSON.parse(c.stdout)}catch(t){throw new f("COMMAND_FAILED","Failed to parse xctestrun JSON",{xctestrunPath:e,error:String(t)})}let u=e=>{e.EnvironmentVariables={...e.EnvironmentVariables??{},...t},e.UITestEnvironmentVariables={...e.UITestEnvironmentVariables??{},...t},e.UITargetAppEnvironmentVariables={...e.UITargetAppEnvironmentVariables??{},...t},e.TestingEnvironmentVariables={...e.TestingEnvironmentVariables??{},...t}},p=i.TestConfigurations;if(Array.isArray(p))for(let e of p){if(!e||"object"!=typeof e)continue;let t=e.TestTargets;if(Array.isArray(t))for(let e of t)e&&"object"==typeof e&&u(e)}for(let[e,t]of Object.entries(i))t&&"object"==typeof t&&t.TestBundlePath&&(u(t),i[e]=t);d.writeFileSync(s,JSON.stringify(i,null,2));let m=await h("plutil",["-convert","xml1","-o",l,s],{allowFailure:!0});if(0!==m.exitCode)throw new f("COMMAND_FAILED","Failed to write xctestrun plist",{tmpXctestrunPath:l,stderr:m.stderr});return{xctestrunPath:l,jsonPath:s}}function e$(e){try{d.existsSync(e)&&d.unlinkSync(e)}catch{}}async function eB(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 eU(),r=await N(async()=>{var e,a;let i,r,o,s=await h(n,[],{allowFailure:!0});if(t.traceLogPath&&(e=t.traceLogPath,i=((a=s).stdout??"").toString(),r=(a.stderr??"").toString(),o=`
2
- [axsnapshot] exit=${a.exitCode} stdoutBytes=${i.length} stderrBytes=${r.length}
3
- `,d.appendFileSync(e,o),(0!==a.exitCode||r.length>0)&&(r.length>0&&d.appendFileSync(e,`${r}
4
- `),0!==a.exitCode&&i.length>0&&d.appendFileSync(e,`${i}
5
- `))),0!==s.exitCode){let e,t,a=(s.stderr??"").toString(),i=(e=a.toLowerCase()).includes("accessibility permission")?" Enable Accessibility for your terminal in System Settings > Privacy & Security > Accessibility, or use --backend xctest (slower snapshots via XCTest).":e.includes("could not find ios app content")?" AX snapshot sometimes caches empty content. Try restarting the Simulator app.":"",n=!!((t=a.toLowerCase()).includes("could not find ios app content")||t.includes("timeout"));throw new f("COMMAND_FAILED","AX snapshot failed",{stderr:`${a}${i}`,stdout:s.stdout,retryable:n})}return s},{shouldRetry:e=>{var t;return(t=e)instanceof f&&"COMMAND_FAILED"===t.code&&t.details?.retryable===!0}});try{let e=JSON.parse(r.stdout);if(e&&"object"==typeof e&&"root"in e){if(!e.root)throw Error("AX snapshot missing root");a=e.root,i=e.windowFrame??void 0}else a=e}catch(e){throw new f("COMMAND_FAILED","Invalid AX snapshot JSON",{error:String(e)})}let o=a.frame??i,s=[],l=[],c=(e,t)=>{e.frame&&s.push(e.frame);let a=e.frame&&o?{x:e.frame.x-o.x,y:e.frame.y-o.y,width:e.frame.width,height:e.frame.height}:e.frame;for(let i of(l.push({...e,frame:a,children:void 0,depth:t}),e.children??[]))c(i,t+1)};return c(a,0),{nodes:(function(e,t,a){if(!t||0===a.length)return e;let i=1/0,n=1/0;for(let e of a)e.x<i&&(i=e.x),e.y<n&&(n=e.y);return i<=5&&n<=5?e.map(e=>({...e,frame:e.frame?{x:e.frame.x+t.x,y:e.frame.y+t.y,width:e.frame.width,height:e.frame.height}:void 0})):e})(l,o,s).map((e,t)=>({index:t,type:e.subrole??e.role,label:e.label,value:e.value,identifier:e.identifier,rect:e.frame?{x:e.frame.x,y:e.frame.y,width:e.frame.width,height:e.frame.height}:void 0,depth:e.depth}))}}async function eU(){let e=function(){let e=r.dirname(c(import.meta.url));for(let t=0;t<6;t+=1){let t=r.join(e,"package.json");if(d.existsSync(t))return e;e=r.dirname(e)}return process.cwd()}(),t=r.join(e,"ios-runner","AXSnapshot"),a=process.env.AGENT_DEVICE_AX_BINARY;if(a&&d.existsSync(a))return a;let i=r.join(e,"dist","bin","axsnapshot");if(d.existsSync(i))return i;let n=r.join(t,".build","release","axsnapshot");if(d.existsSync(n))return n;let o=await h("swift",["build","-c","release"],{cwd:t,allowFailure:!0});if(0!==o.exitCode||!d.existsSync(n))throw new f("COMMAND_FAILED","Failed to build AX snapshot tool",{stderr:o.stderr,stdout:o.stdout});return n}async function eV(e){let t={platform:e.platform,deviceName:e.device,udid:e.udid,serial:e.serial};if("android"===t.platform){await W();let e=await y();return await g(e,t)}if("ios"===t.platform){let e=await er();return await g(e,t)}let a=[];try{a.push(...await y())}catch{}try{a.push(...await er())}catch{}return await g(a,t)}async function ej(e,t,a,i,n){let r=function(e){switch(e.platform){case"android":return{open:(t,a)=>O(e,t,a?.activity),openDevice:()=>L(e),close:t=>_(e,t),tap:(t,a)=>M(e,t,a),longPress:(t,a,i)=>T(e,t,a,i),focus:(t,a)=>$(e,t,a),type:t=>F(e,t),fill:(t,a,i)=>B(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=>el(e,t),openDevice:()=>ec(e),close:t=>eu(e,t),tap:(t,a)=>ed(e,t,a),longPress:(t,a,i)=>ep(e,t,a,i),focus:(t,a)=>ef(e,t,a),type:t=>eh(e,t),fill:(t,a,i)=>em(e,t,a,i),scroll:(t,a)=>ew(e,t,a),scrollIntoView:e=>eg(e),screenshot:t=>ey(e,t)};default:throw new f("UNSUPPORTED_PLATFORM",`Unsupported platform: ${e.platform}`)}}(e);switch(t){case"open":{let e=a[0];if(!e)return await r.openDevice(),{app:null};return await r.open(e,{activity:n?.activity}),{app:e}}case"close":{let e=a[0];if(!e)return{closed:"session"};return await r.close(e),{app:e}}case"press":{let[t,i]=a.map(Number);if(Number.isNaN(t)||Number.isNaN(i))throw new f("INVALID_ARGS","press requires x y");return"ios"===e.platform&&"simulator"===e.kind?await eD(e,{command:"tap",x:t,y:i,appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath,traceLogPath:n?.traceLogPath}):await r.tap(t,i),{x:t,y:i}}case"long-press":{let e=Number(a[0]),t=Number(a[1]),i=a[2]?Number(a[2]):void 0;if(Number.isNaN(e)||Number.isNaN(t))throw new f("INVALID_ARGS","long-press requires x y [durationMs]");return await r.longPress(e,t,i),{x:e,y:t,durationMs:i}}case"focus":{let[t,i]=a.map(Number);if(Number.isNaN(t)||Number.isNaN(i))throw new f("INVALID_ARGS","focus requires x y");return"ios"===e.platform&&"simulator"===e.kind?await eD(e,{command:"tap",x:t,y:i,appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath,traceLogPath:n?.traceLogPath}):await r.focus(t,i),{x:t,y:i}}case"type":{let t=a.join(" ");if(!t)throw new f("INVALID_ARGS","type requires text");return"ios"===e.platform&&"simulator"===e.kind?await eD(e,{command:"type",text:t,appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath,traceLogPath:n?.traceLogPath}):await r.type(t),{text:t}}case"fill":{let t=Number(a[0]),i=Number(a[1]),o=a.slice(2).join(" ");if(Number.isNaN(t)||Number.isNaN(i)||!o)throw new f("INVALID_ARGS","fill requires x y text");return"ios"===e.platform&&"simulator"===e.kind?(await eD(e,{command:"tap",x:t,y:i,appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath,traceLogPath:n?.traceLogPath}),await eD(e,{command:"type",text:o,clearFirst:!0,appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath,traceLogPath:n?.traceLogPath})):await r.fill(t,i,o),{x:t,y:i,text:o}}case"scroll":{let t=a[0],i=a[1]?Number(a[1]):void 0;if(!t)throw new f("INVALID_ARGS","scroll requires direction");if("ios"===e.platform&&"simulator"===e.kind){if(!["up","down","left","right"].includes(t))throw new f("INVALID_ARGS",`Unknown direction: ${t}`);let a=function(e){switch(e){case"up":return"down";case"down":return"up";case"left":return"right";case"right":return"left"}}(t);await eD(e,{command:"swipe",direction:a,appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath,traceLogPath:n?.traceLogPath})}else await r.scroll(t,i);return{direction:t,amount:i}}case"scrollintoview":{let t=a.join(" ").trim();if(!t)throw new f("INVALID_ARGS","scrollintoview requires text");if("ios"===e.platform&&"simulator"===e.kind){for(let a=0;a<8;a+=1){let i=await eD(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 eD(e,{command:"swipe",direction:"up",appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath,traceLogPath:n?.traceLogPath}),await new Promise(e=>setTimeout(e,300))}throw new f("COMMAND_FAILED",`scrollintoview could not find text: ${t}`)}return await r.scrollIntoView(t),{text:t}}case"pinch":{let t=Number(a[0]),i=a[1]?Number(a[1]):void 0,r=a[2]?Number(a[2]):void 0;if(Number.isNaN(t)||t<=0)throw new f("INVALID_ARGS","pinch requires scale > 0");if("ios"===e.platform&&"simulator"===e.kind)await eD(e,{command:"pinch",scale:t,x:i,y:r,appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath,traceLogPath:n?.traceLogPath});else throw new f("UNSUPPORTED_OPERATION","pinch is only supported on iOS simulators");return{scale:t,x:i,y:r}}case"screenshot":{let e=a[0]??i??`./screenshot-${Date.now()}.png`;return await r.screenshot(e),{path:e}}case"back":if("ios"===e.platform){if("simulator"!==e.kind)throw new f("UNSUPPORTED_OPERATION","back is only supported on iOS simulators in v1");return await eD(e,{command:"back",appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath,traceLogPath:n?.traceLogPath}),{action:"back"}}return await C(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 eD(e,{command:"home",appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath,traceLogPath:n?.traceLogPath}),{action:"home"}}return await R(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 eD(e,{command:"appSwitcher",appBundleId:n?.appBundleId},{verbose:n?.verbose,logPath:n?.logPath,traceLogPath:n?.traceLogPath}),{action:"app-switcher"}}return await E(e),{action:"app-switcher"};case"settings":{let[t,i,r]=a;if("ios"===e.platform)return await ev(e,t,i,r??n?.appBundleId),{setting:t,state:i};return await G(e,t,i),{setting:t,state:i}}case"snapshot":{let t=n?.snapshotBackend??"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 eB(e,{traceLogPath:n?.traceLogPath})).nodes??[],truncated:!1,backend:"ax"};let a=await eD(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 eB(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 eG(e){return e.map((e,t)=>({...e,ref:`e${t+1}`}))}function eq(e){let t=e.trim();return t.startsWith("@")?t.slice(1)||null:t.startsWith("e")?t:null}function eW(e,t){return e.find(e=>e.ref===t)??null}function eJ(e){return{x:Math.round(e.x+e.width/2),y:Math.round(e.y+e.height/2)}}function eX(e,t,a,i={}){let n=eH(a);if(!n)return null;let r=null;for(let a of e){if(i.requireRect&&!a.rect)continue;let e=function(e,t,a){switch(t){case"role":return function(e,t){let a=function(e){let t=e.trim();return t?((t=(t.split(".").pop()??t).replace(/XCUIElementType/gi,"").toLowerCase()).startsWith("ax")&&(t=t.replace(/^ax/,"")),t):""}(e??"");return a?a===t?2:+!!a.includes(t):0}(e.type,a);case"label":return ez(e.label,a);case"value":return ez(e.value,a);case"id":return ez(e.identifier,a);default:return Math.max(ez(e.label,a),ez(e.value,a),ez(e.identifier,a))}}(a,t,n);if(!(e<=0)&&(!r||e>r.score)&&(r={node:a,score:e},e>=2))break}return r?.node??null}function ez(e,t){let a=eH(e??"");return a?a===t?2:+!!a.includes(t):0}function eH(e){return e.trim().toLowerCase().replace(/\s+/g," ")}let eY=new Map,eK=r.join(p.homedir(),".agent-device"),eZ=r.join(eK,"daemon.json"),eQ=r.join(eK,"daemon.log"),e0=r.join(eK,"sessions"),e1=function(){try{let e=function(){let e=r.dirname(c(import.meta.url)),t=e;for(let e=0;e<6;e+=1){let e=r.join(t,"package.json");if(d.existsSync(e))return t;t=r.dirname(t)}return e}();return JSON.parse(d.readFileSync(r.join(e,"package.json"),"utf8")).version??"0.0.0"}catch{return"0.0.0"}}(),e2=a.randomBytes(24).toString("hex");function e3(e,t,a){return{appBundleId:t,activity:e?.activity,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 e4(e){var t,a,i;if(e.token!==e2)return{ok:!1,error:{code:"UNAUTHORIZED",message:"Invalid token"}};let n=e.command,o=e.session||"default";if("session_list"===n)return{ok:!0,data:{sessions:Array.from(eY.values()).map(e=>({name:e.name,platform:e.device.platform,device:e.device.name,id:e.device.id,createdAt:e.createdAt}))}};if("devices"===n)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:er}));t.push(...await e())}else{let{listAndroidDevices:e}=await Promise.resolve().then(()=>({listAndroidDevices:y})),{listIosDevices:a}=await Promise.resolve().then(()=>({listIosDevices:er}));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"===n){let t=eY.get(o),a=e.flags??{};if(!t&&!a.platform&&!a.device&&!a.udid&&!a.serial)return{ok:!1,error:{code:"INVALID_ARGS",message:"apps requires an active session or an explicit device selector (e.g. --platform ios)."}};let i=t?.device??await eV(a);if(await tn(i),"ios"===i.platform){if("simulator"!==i.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:eN})),a=await t(i);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(i,e.flags?.appsFilter)}}:{ok:!0,data:{apps:await n(i,e.flags?.appsFilter)}}}if("appstate"===n){let t=eY.get(o),a=e.flags??{},i=t?.device??await eV(a);if(await tn(i),"ios"===i.platform){if(t?.appBundleId)return{ok:!0,data:{platform:"ios",appBundleId:t.appBundleId,appName:t.appName??t.appBundleId,source:"session"}};let a=await e6(i,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(i);return{ok:!0,data:{platform:"android",package:r.package,activity:r.activity}}}if("open"===n){let t;if(eY.has(o)){let t,a=eY.get(o),i=e.positionals?.[0];if(!a||!i)return{ok:!1,error:{code:"INVALID_ARGS",message:"Session already active. Close it first or pass a new --session name."}};if("ios"===a.device.platform)try{let{resolveIosApp:e}=await Promise.resolve().then(()=>({resolveIosApp:es}));t=await e(a.device,i)}catch{t=void 0}await ej(a.device,"open",e.positionals??[],e.flags?.out,{...e3(e.flags,t)});let r={...a,appBundleId:t,appName:i,snapshot:void 0};return e8(r,{command:n,positionals:e.positionals??[],flags:e.flags??{},result:{session:o,appName:i,appBundleId:t}}),eY.set(o,r),{ok:!0,data:{session:o,appName:i,appBundleId:t}}}let a=await eV(e.flags??{});await tn(a);let i=Array.from(eY.values()).find(e=>e.device.id===a.id);if(i)return{ok:!1,error:{code:"DEVICE_IN_USE",message:`Device is already in use by session "${i.name}".`,details:{session:i.name,deviceId:a.id,deviceName:a.name}}};let r=e.positionals?.[0];if("ios"===a.platform)try{let{resolveIosApp:i}=await Promise.resolve().then(()=>({resolveIosApp:es}));t=await i(a,e.positionals?.[0]??"")}catch{t=void 0}await ej(a,"open",e.positionals??[],e.flags?.out,{...e3(e.flags,t)});let s={name:o,device:a,createdAt:Date.now(),appBundleId:t,appName:r,actions:[]};return e8(s,{command:n,positionals:e.positionals??[],flags:e.flags??{},result:{session:o}}),eY.set(o,s),{ok:!0,data:{session:o}}}if("replay"===n){let t=e.positionals?.[0];if(!t)return{ok:!1,error:{code:"INVALID_ARGS",message:"replay requires a path"}};try{let e=e9(t),a=JSON.parse(d.readFileSync(e,"utf8")),i=a.optimizedActions??a.actions??[];for(let e of i)e&&"replay"!==e.command&&await e4({token:e2,session:o,command:e.command,positionals:e.positionals??[],flags:e.flags??{}});return{ok:!0,data:{replayed:i.length,session:o}}}catch(t){let e=l(t);return{ok:!1,error:{code:e.code,message:e.message}}}}if("close"===n){let t=eY.get(o);return t?(e.positionals&&e.positionals.length>0&&await ej(t.device,"close",e.positionals??[],e.flags?.out,{...e3(e.flags,t.appBundleId,t.trace?.outPath)}),"ios"===t.device.platform&&"simulator"===t.device.kind&&await eP(t.device.id),e8(t,{command:n,positionals:e.positionals??[],flags:e.flags??{},result:{session:o}}),e7(t),eY.delete(o),{ok:!0,data:{session:o}}):{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session"}}}if("snapshot"===n){let t=eY.get(o),a=t?.device??await eV(e.flags??{});t||await tn(a);let i=t?.appBundleId,r=e.flags?.snapshotScope;if(r&&r.trim().startsWith("@")){if(!t?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"Ref scope requires an existing snapshot in session."}};let e=eq(r.trim());if(!e)return{ok:!1,error:{code:"INVALID_ARGS",message:`Invalid ref scope: ${r}`}};let a=eW(t.snapshot.nodes,e),i=a?ta(a,t.snapshot.nodes):void 0;if(!i)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${r} not found or has no label`}};r=i}let s=await ej(a,"snapshot",[],e.flags?.out,{...e3({...e.flags,snapshotScope:r},i,t?.trace?.outPath)}),l=s?.nodes??[],c=eG(e.flags?.snapshotRaw?l:tr(l)),u={nodes:c,truncated:s?.truncated,createdAt:Date.now(),backend:s?.backend},d={name:o,device:a,createdAt:t?.createdAt??Date.now(),appBundleId:t?.appBundleId??i,snapshot:u,actions:t?.actions??[],appName:t?.appName};return e8(d,{command:n,positionals:e.positionals??[],flags:e.flags??{},result:{nodes:c.length,truncated:s?.truncated??!1}}),eY.set(o,d),{ok:!0,data:{nodes:c,truncated:s?.truncated??!1,appName:d.appBundleId?d.appName??d.appBundleId:void 0,appBundleId:d.appBundleId}}}if("wait"===n){let t=eY.get(o),a=t?.device??await eV(e.flags??{});t||await tn(a);let i=e.positionals??[];if(0===i.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(i[0]);if(null!==s)return await new Promise(e=>setTimeout(e,s)),t&&e8(t,{command:n,positionals:e.positionals??[],flags:e.flags??{},result:{waitedMs:s}}),{ok:!0,data:{waitedMs:s}};let l="",c=null;if("text"===i[0])l=null!==(c=r(i[i.length-1]))?i.slice(1,-1).join(" "):i.slice(1).join(" ");else if(i[0].startsWith("@")){if(!t?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"Ref wait requires an existing snapshot in session."}};let e=eq(i[0]);if(!e)return{ok:!1,error:{code:"INVALID_ARGS",message:`Invalid ref: ${i[0]}`}};let a=eW(t.snapshot.nodes,e),n=a?ta(a,t.snapshot.nodes):void 0;if(!n)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${i[0]} not found or has no label`}};c=r(i[i.length-1]),l=n}else l=null!==(c=r(i[i.length-1]))?i.slice(0,-1).join(" "):i.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"===a.platform&&"simulator"===a.kind){let i=await eD(a,{command:"findText",text:l,appBundleId:t?.appBundleId},{verbose:e.flags?.verbose,logPath:eQ,traceLogPath:t?.trace?.outPath});if(i?.found)return t&&e8(t,{command:n,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"!==a.platform)return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"wait is not supported on this device"}};else if(tt(eG((await q(a,{scope:l})).nodes??[]),l))return t&&e8(t,{command:n,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"===n){let t=eY.get(o),a=t?.device??await eV(e.flags??{});t||await tn(a);let i=(e.positionals?.[0]??"get").toLowerCase();if("ios"!==a.platform||"simulator"!==a.kind)return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"alert is only supported on iOS simulators in v1"}};if("wait"===i){let i=(e=>{if(!e)return null;let t=Number(e);return Number.isFinite(t)?t:null})(e.positionals?.[1])??1e4,r=Date.now();for(;Date.now()-r<i;){try{let i=await eD(a,{command:"alert",action:"get",appBundleId:t?.appBundleId},{verbose:e.flags?.verbose,logPath:eQ,traceLogPath:t?.trace?.outPath});return t&&e8(t,{command:n,positionals:e.positionals??[],flags:e.flags??{},result:i}),{ok:!0,data:i}}catch{}await new Promise(e=>setTimeout(e,300))}return{ok:!1,error:{code:"COMMAND_FAILED",message:"alert wait timed out"}}}let r=await eD(a,{command:"alert",action:"accept"===i||"dismiss"===i?i:"get",appBundleId:t?.appBundleId},{verbose:e.flags?.verbose,logPath:eQ,traceLogPath:t?.trace?.outPath});return t&&e8(t,{command:n,positionals:e.positionals??[],flags:e.flags??{},result:r}),{ok:!0,data:r}}if("record"===n){let t=(e.positionals?.[0]??"").toLowerCase();if(!["start","stop"].includes(t))return{ok:!1,error:{code:"INVALID_ARGS",message:"record requires start|stop"}};let a=eY.get(o),i=a?.device??await eV(e.flags??{});a||await tn(i);let s=a??{name:o,device:i,createdAt:Date.now(),actions:[]};if("start"===t){if(s.recording)return{ok:!1,error:{code:"INVALID_ARGS",message:"recording already in progress"}};let t=e.positionals?.[1]??`./recording-${Date.now()}.mp4`,a=r.resolve(t),l=r.dirname(a);if(d.existsSync(l)||d.mkdirSync(l,{recursive:!0}),"ios"===i.platform){if("simulator"!==i.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",i.id,"recordVideo",a],{allowFailure:!0});s.recording={platform:"ios",outPath:a,child:e,wait:t}}else{let e=`/sdcard/agent-device-recording-${Date.now()}.mp4`,{child:t,wait:n}=u("adb",["-s",i.id,"shell","screenrecord",e],{allowFailure:!0});s.recording={platform:"android",outPath:a,remotePath:e,child:t,wait:n}}return eY.set(o,s),e8(s,{command:n,positionals:e.positionals??[],flags:e.flags??{},result:{action:"start"}}),{ok:!0,data:{recording:"started",outPath:t}}}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",i.id,"pull",l.remotePath,l.outPath],{allowFailure:!0}),await h("adb",["-s",i.id,"shell","rm","-f",l.remotePath],{allowFailure:!0})}catch{}return s.recording=void 0,e8(s,{command:n,positionals:e.positionals??[],flags:e.flags??{},result:{action:"stop",outPath:l.outPath}}),{ok:!0,data:{recording:"stopped",outPath:l.outPath}}}if("trace"===n){let t=(e.positionals?.[0]??"").toLowerCase();if(!["start","stop"].includes(t))return{ok:!1,error:{code:"INVALID_ARGS",message:"trace requires start|stop"}};let a=eY.get(o);if(!a)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session"}};if("start"===t){let t,i;if(a.trace)return{ok:!1,error:{code:"INVALID_ARGS",message:"trace already in progress"}};let o=e9(e.positionals?.[1]??(t=a.name.replace(/[^a-zA-Z0-9._-]/g,"_"),i=new Date().toISOString().replace(/[:.]/g,"-"),r.join(e0,`${t}-${i}.trace.log`)));return d.mkdirSync(r.dirname(o),{recursive:!0}),d.appendFileSync(o,""),a.trace={outPath:o,startedAt:Date.now()},e8(a,{command:n,positionals:e.positionals??[],flags:e.flags??{},result:{action:"start",outPath:o}}),{ok:!0,data:{trace:"started",outPath:o}}}if(!a.trace)return{ok:!1,error:{code:"INVALID_ARGS",message:"no active trace"}};let i=a.trace.outPath;if(e.positionals?.[1]){let t=e9(e.positionals[1]);d.mkdirSync(r.dirname(t),{recursive:!0}),d.existsSync(i)?d.renameSync(i,t):d.appendFileSync(t,""),i=t}return a.trace=void 0,e8(a,{command:n,positionals:e.positionals??[],flags:e.flags??{},result:{action:"stop",outPath:i}}),{ok:!0,data:{trace:"stopped",outPath:i}}}if("settings"===n){let t=e.positionals?.[0],a=e.positionals?.[1];if(!t||!a)return{ok:!1,error:{code:"INVALID_ARGS",message:"settings requires <wifi|airplane|location> <on|off>"}};let i=eY.get(o),r=i?.device??await eV(e.flags??{});i||await tn(r);let s=i?.appBundleId,l=await ej(r,"settings",[t,a,s??""],e.flags?.out,{...e3(e.flags,s,i?.trace?.outPath)});return i&&e8(i,{command:n,positionals:e.positionals??[],flags:e.flags??{},result:l??{setting:t,state:a}}),{ok:!0,data:l??{setting:t,state:a}}}if("find"===n){let a=e.positionals??[];if(0===a.length)return{ok:!1,error:{code:"INVALID_ARGS",message:"find requires a locator or text"}};let{locator:i,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 r=n[0].toLowerCase();if("get"===r){let e=n[1]?.toLowerCase();if("text"===e)return{locator:t,query:i,action:"get_text"};if("attrs"===e)return{locator:t,query:i,action:"get_attrs"};throw new f("INVALID_ARGS","find get only supports text or attrs")}if("wait"===r)return{locator:t,query:i,action:"wait",timeoutMs:function(e){if(!e)return null;let t=Number(e);return Number.isFinite(t)?t:null}(n[1])??void 0};if("exists"===r)return{locator:t,query:i,action:"exists"};if("click"===r)return{locator:t,query:i,action:"click"};if("focus"===r)return{locator:t,query:i,action:"focus"};if("fill"===r)return{locator:t,query:i,action:"fill",value:n.slice(1).join(" ")};if("type"===r)return{locator:t,query:i,action:"type",value:n.slice(1).join(" ")};throw new f("INVALID_ARGS",`Unsupported find action: ${n[0]}`)}(a);if(!r)return{ok:!1,error:{code:"INVALID_ARGS",message:"find requires a value"}};let u=eY.get(o);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 eV(e.flags??{});u||await tn(d);let p=u?.appBundleId,h="role"!==i?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 a=await ej(d,"snapshot",[],e.flags?.out,{...e3({...e.flags,snapshotScope:h,snapshotInteractiveOnly:m,snapshotCompact:m},p,u?.trace?.outPath)}),i=a?.nodes??[],n=eG(e.flags?.snapshotRaw?i:tr(i));return w=t,g=n,u&&(u.snapshot={nodes:n,truncated:a?.truncated,createdAt:Date.now(),backend:a?.backend},eY.set(o,u)),{nodes:n,truncated:a?.truncated,backend:a?.backend}};if("wait"===s){let t=c??1e4,a=Date.now();for(;Date.now()-a<t;){let{nodes:t}=await y();if(eX(t,i,r,{requireRect:!1}))return u&&e8(u,{command:n,positionals:e.positionals??[],flags:e.flags??{},result:{found:!0,waitedMs:Date.now()-a}}),{ok:!0,data:{found:!0,waitedMs:Date.now()-a}};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=eX(v,i,r,{requireRect:m});if(!I)return{ok:!1,error:{code:"COMMAND_FAILED",message:"find did not match any element"}};let N="click"===s||"focus"===s||"fill"===s||"type"===s?function(e,t){if(t.hittable)return t;let a=t,i=new Set;for(;void 0!==a.parentIndex&&!i.has(a.ref);){i.add(a.ref);let t=e[a.parentIndex];if(!t)break;if(t.hittable)return t;a=t}return null}(v,I)??I:I,A=`@${N.ref}`,b={...e.flags??{},noRecord:!0};if("exists"===s)return u&&e8(u,{command:n,positionals:e.positionals??[],flags:e.flags??{},result:{found:!0}}),{ok:!0,data:{found:!0}};if("get_text"===s){let a=[(t=I).label,t.value,t.identifier].map(e=>"string"==typeof e?e.trim():"").filter(e=>e.length>0)[0]??"";return u&&e8(u,{command:n,positionals:e.positionals??[],flags:e.flags??{},result:{ref:A,action:"get text",text:a}}),{ok:!0,data:{ref:A,text:a,node:I}}}if("get_attrs"===s)return u&&e8(u,{command:n,positionals:e.positionals??[],flags:e.flags??{},result:{ref:A,action:"get attrs"}}),{ok:!0,data:{ref:A,node:I}};if("click"===s){let t=await e4({token:e2,session:o,command:"click",positionals:[A],flags:b});return t.ok&&u&&e8(u,{command:n,positionals:e.positionals??[],flags:e.flags??{},result:{ref:A,action:"click"}}),t}if("fill"===s){if(!l)return{ok:!1,error:{code:"INVALID_ARGS",message:"find fill requires text"}};let t=await e4({token:e2,session:o,command:"fill",positionals:[A,l],flags:b});return t.ok&&u&&e8(u,{command:n,positionals:e.positionals??[],flags:e.flags??{},result:{ref:A,action:"fill"}}),t}if("focus"===s){let t=I.rect?eJ(I.rect):null;if(!t)return{ok:!1,error:{code:"COMMAND_FAILED",message:"matched element has no bounds"}};let a=await ej(d,"focus",[String(t.x),String(t.y)],e.flags?.out,{...e3(e.flags,u?.appBundleId,u?.trace?.outPath)});return u&&e8(u,{command:n,positionals:e.positionals??[],flags:e.flags??{},result:{ref:A,action:"focus"}}),{ok:!0,data:a??{ref:A}}}if("type"===s){if(!l)return{ok:!1,error:{code:"INVALID_ARGS",message:"find type requires text"}};let t=I.rect?eJ(I.rect):null;if(!t)return{ok:!1,error:{code:"COMMAND_FAILED",message:"matched element has no bounds"}};await ej(d,"focus",[String(t.x),String(t.y)],e.flags?.out,{...e3(e.flags,u?.appBundleId,u?.trace?.outPath)});let a=await ej(d,"type",[l],e.flags?.out,{...e3(e.flags,u?.appBundleId,u?.trace?.outPath)});return u&&e8(u,{command:n,positionals:e.positionals??[],flags:e.flags??{},result:{ref:A,action:"type"}}),{ok:!0,data:a??{ref:A}}}}if("click"===n){let t=eY.get(o);if(!t?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"No snapshot in session. Run snapshot first."}};let a=e.positionals?.[0]??"",i=eq(a);if(!i)return{ok:!1,error:{code:"INVALID_ARGS",message:"click requires a ref like @e2"}};let r=eW(t.snapshot.nodes,i);if(!r?.rect&&e.positionals.length>1){let a=e.positionals.slice(1).join(" ").trim();a.length>0&&(r=tt(t.snapshot.nodes,a))}if(!r?.rect)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${a} not found or has no bounds`}};let s=ta(r,t.snapshot.nodes),{x:l,y:c}=eJ(r.rect);return await ej(t.device,"press",[String(l),String(c)],e.flags?.out,{...e3(e.flags,t.appBundleId,t.trace?.outPath)}),e8(t,{command:n,positionals:e.positionals??[],flags:e.flags??{},result:{ref:i,x:l,y:c,refLabel:s}}),{ok:!0,data:{ref:i,x:l,y:c}}}if("fill"===n){let t=eY.get(o);if(e.positionals?.[0]?.startsWith("@")){let r;if(!t?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"No snapshot in session. Run snapshot first."}};let o=eq(e.positionals[0]);if(!o)return{ok:!1,error:{code:"INVALID_ARGS",message:"fill requires a ref like @e2"}};let s=e.positionals.length>=3?e.positionals[1]:"",l=e.positionals.length>=3?e.positionals.slice(2).join(" "):e.positionals.slice(1).join(" ");if(!l)return{ok:!1,error:{code:"INVALID_ARGS",message:"fill requires text after ref"}};let c=eW(t.snapshot.nodes,o);if(!c?.rect&&s&&(c=tt(t.snapshot.nodes,s)),!c?.rect)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${e.positionals[0]} not found or has no bounds`}};let u=c.type??"";if(u&&(a=u,i=t.device.platform,!(!(r=to(a))||("android"===i?r.includes("edittext")||r.includes("autocompletetextview"):r.includes("textfield")||r.includes("securetextfield")||r.includes("searchfield")||r.includes("textview")||r.includes("textarea")||"search"===r))))return{ok:!1,error:{code:"INVALID_ARGS",message:`fill requires a text input element, got "${u}" for ${e.positionals[0]}. Select a text input ref or use click/focus + type.`}};let d=ta(c,t.snapshot.nodes),{x:p,y:f}=eJ(c.rect),h=await ej(t.device,"fill",[String(p),String(f),l],e.flags?.out,{...e3(e.flags,t.appBundleId,t.trace?.outPath)});return e8(t,{command:n,positionals:e.positionals??[],flags:e.flags??{},result:h??{ref:o,x:p,y:f,refLabel:d}}),{ok:!0,data:h??{ref:o,x:p,y:f}}}}if("get"===n){let t=e.positionals?.[0],a=e.positionals?.[1];if("text"!==t&&"attrs"!==t)return{ok:!1,error:{code:"INVALID_ARGS",message:"get only supports text or attrs"}};let i=eY.get(o);if(!i?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"No snapshot in session. Run snapshot first."}};let r=eq(a??"");if(!r)return{ok:!1,error:{code:"INVALID_ARGS",message:"get text requires a ref like @e2"}};let s=eW(i.snapshot.nodes,r);if(!s&&e.positionals.length>2){let t=e.positionals.slice(2).join(" ").trim();t.length>0&&(s=tt(i.snapshot.nodes,t))}if(!s)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${a} not found`}};if("attrs"===t)return e8(i,{command:n,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 e8(i,{command:n,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 s=eY.get(o);if(!s)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session. Run open first."}};let c=await ej(s.device,n,e.positionals??[],e.flags?.out,{...e3(e.flags,s.appBundleId,s.trace?.outPath)});return e8(s,{command:n,positionals:e.positionals??[],flags:e.flags??{},result:c??{}}),{ok:!0,data:c??{}}}function e8(e,t){t.flags?.noRecord||e.actions.push({ts:Date.now(),command:t.command,positionals:t.positionals,flags:function(e){if(!e)return{};let{platform:t,device:a,udid:i,serial:n,out:r,verbose:o,snapshotInteractiveOnly:s,snapshotCompact:l,snapshotDepth:c,snapshotScope:u,snapshotRaw:d,snapshotBackend:p,appsMetadata:f,noRecord:h,recordJson:m}=e;return{platform:t,device:a,udid:i,serial:n,out:r,verbose:o,snapshotInteractiveOnly:s,snapshotCompact:l,snapshotDepth:c,snapshotScope:u,snapshotRaw:d,snapshotBackend:p,appsMetadata:f,noRecord:h,recordJson:m}}(t.flags),result:t.result})}async function e6(e,t,a){let i=e5(await ej(e,"snapshot",[],a?.out,{...e3({...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=e5(await ej(e,"snapshot",[],a?.out,{...e3({...a,snapshotDepth:1,snapshotCompact:!0,snapshotBackend:"xctest"},void 0,t)}));return{appName:n?.appName??n?.appBundleId??"unknown",appBundleId:n?.appBundleId,source:"snapshot-xctest"}}function e5(e){let t=eG(e?.nodes??[]),a=t.find(e=>"application"===to(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 e7(e){try{d.existsSync(e0)||d.mkdirSync(e0,{recursive:!0});let t=e.name.replace(/[^a-zA-Z0-9._-]/g,"_"),a=new Date(e.createdAt).toISOString().replace(/[:.]/g,"-"),i=r.join(e0,`${t}-${a}.ad`),n=function(e,t,a){let i=r.join(e0,`${t}-${a}.json`),n=[...e.actions].reverse().find(e=>e.flags?.recordJson&&"string"==typeof e.flags?.out&&e.flags.out.trim().length>0);if(!n||!n.flags?.out)return i;let o=n.flags.out.trim(),s=e9(o);if(o.endsWith("/")||o.endsWith("\\"))return r.join(s,`${t}-${a}.json`);try{if(d.existsSync(s)&&d.statSync(s).isDirectory())return r.join(s,`${t}-${a}.json`)}catch{return i}return s}(e,t,a),o={name:e.name,device:e.device,createdAt:e.createdAt,appBundleId:e.appBundleId,actions:e.actions,optimizedActions:function(e){let t=[];for(let a of e.actions)if("snapshot"!==a.command){if("click"===a.command||"fill"===a.command||"get"===a.command){let i=a.result?.refLabel;"string"==typeof i&&i.trim().length>0&&t.push({ts:a.ts,command:"snapshot",positionals:[],flags:{platform:e.device.platform,snapshotInteractiveOnly:!0,snapshotCompact:!0,snapshotScope:i.trim()},result:{scope:i.trim()}})}t.push(a)}return t}(e)},s=function(e,t){let a=[],i=e.device.name.replace(/"/g,'\\"'),n=e.device.kind?` kind=${e.device.kind}`:"";for(let r of(a.push(`context platform=${e.device.platform} device="${i}"${n} theme=unknown`),t))r.flags?.noRecord||a.push(function(e){let t=[e.command];if("click"===e.command){let a=e.positionals?.[0];if(a){t.push(te(a));let i=e.result?.refLabel;return"string"==typeof i&&i.trim().length>0&&t.push(te(i)),t.join(" ")}}if("fill"===e.command){let a=e.positionals?.[0];if(a&&a.startsWith("@")){t.push(te(a));let i=e.result?.refLabel,n=e.positionals.slice(1).join(" ");return"string"==typeof i&&i.trim().length>0&&t.push(te(i)),n&&t.push(te(n)),t.join(" ")}}if("get"===e.command){let a=e.positionals?.[0],i=e.positionals?.[1];if(a&&i){t.push(te(a)),t.push(te(i));let n=e.result?.refLabel;return"string"==typeof n&&n.trim().length>0&&t.push(te(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",te(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(te(a));return t.join(" ")}(r));return`${a.join("\n")}
6
- `}(e,o.optimizedActions);d.writeFileSync(i,s),e.actions.some(e=>e.flags?.recordJson)&&(d.mkdirSync(r.dirname(n),{recursive:!0}),d.writeFileSync(n,JSON.stringify(o,null,2)))}catch{}}function e9(e){return e.startsWith("~/")?r.join(p.homedir(),e.slice(2)):r.resolve(e)}function te(e){let t=e.trim();return t.startsWith("@")||/^-?\d+(\.\d+)?$/.test(t)?t:JSON.stringify(t)}function tt(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 ta(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&&ti(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||!ti(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&&ti(a)?a:void 0)}function ti(e){let t=e.trim();return!(!t||/^(true|false)$/i.test(t)||/^\d+$/.test(t))}async function tn(e){if("ios"===e.platform&&"simulator"===e.kind){let{ensureBootedSimulator:t}=await Promise.resolve().then(()=>({ensureBootedSimulator:eA}));await t(e);return}if("android"===e.platform){let{waitForAndroidBoot:t}=await Promise.resolve().then(()=>({waitForAndroidBoot:I}));await t(e.id)}}function tr(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=to(i.type??""),r=[i.label,i.value,i.identifier].map(e=>"string"==typeof e?e.trim():"").find(e=>e&&e.length>0),o=!!r&&ti(r);if(("group"===n||"ioscontentgroup"===n)&&!o){t.push(e);continue}let s=Math.max(0,e-t.length);a.push({...i,depth:s})}return a}function to(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 e4(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(eK)||d.mkdirSync(eK,{recursive:!0}),d.writeFileSync(eQ,""),d.writeFileSync(eZ,JSON.stringify({port:a,token:e2,pid:process.pid,version:e1},null,2),{mode:384}),process.stdout.write(`AGENT_DEVICE_DAEMON_PORT=${t.port}
8
- `)}}),t=async()=>{for(let e of Array.from(eY.values()))"ios"===e.device.platform&&"simulator"===e.device.kind&&await eP(e.device.id),e7(e);e.close(()=>{d.existsSync(eZ)&&d.unlinkSync(eZ),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}
1
+ let e,t;import i from"node:crypto";import{isCancel as r,select as a}from"@clack/prompts";import{node_path as n,runCmdStreaming as o,promises as s,asAppError as l,fileURLToPath as c,runCmdBackground as u,node_fs as d,node_os as f,errors_AppError as p,runCmd as m,node_net as h,whichCmd as w}from"./861.js";async function g(e,t){let i=e,n=e=>e.toLowerCase().replace(/_/g," ").replace(/\s+/g," ").trim();if(t.platform&&(i=i.filter(e=>e.platform===t.platform)),t.udid){let e=i.find(e=>e.id===t.udid&&"ios"===e.platform);if(!e)throw new p("DEVICE_NOT_FOUND",`No iOS device with UDID ${t.udid}`);return e}if(t.serial){let e=i.find(e=>e.id===t.serial&&"android"===e.platform);if(!e)throw new p("DEVICE_NOT_FOUND",`No Android device with serial ${t.serial}`);return e}if(t.deviceName){let e=n(t.deviceName),r=i.find(t=>n(t.name)===e);if(!r)throw new p("DEVICE_NOT_FOUND",`No device named ${t.deviceName}`);return r}if(1===i.length)return i[0];if(0===i.length)throw new p("DEVICE_NOT_FOUND","No devices found",{selector:t});let o=i.filter(e=>e.booted);if(1===o.length)return o[0];if(!process.env.CI&&process.stdin.isTTY&&process.stdout.isTTY){let e=await a({message:"Multiple devices available. Choose a device to continue:",options:(o.length>0?o:i).map(e=>({label:`${e.name} (${e.platform}${e.kind?`, ${e.kind}`:""}${e.booted?", booted":""})`,value:e.id}))});if(r(e))throw new p("INVALID_ARGS","Device selection cancelled");if(e){let t=i.find(t=>t.id===e);if(t)return t}}return o[0]??i[0]}async function v(){if(!await w("adb"))throw new p("TOOL_MISSING","adb not found in PATH");let e=(await m("adb",["devices","-l"])).stdout.split("\n").map(e=>e.trim()),t=[];for(let i of e){if(!i||i.startsWith("List of devices"))continue;let e=i.split(/\s+/),r=e[0];if("device"!==e[1])continue;let a=(e.find(e=>e.startsWith("model:"))??"").replace("model:","").replace(/_/g," ").trim()||r;if(r.startsWith("emulator-")){let e=await m("adb",["-s",r,"emu","avd","name"],{allowFailure:!0}),t=e.stdout.trim();0===e.exitCode&&t&&(a=t.replace(/_/g," "))}let n=await y(r);t.push({platform:"android",id:r,name:a,kind:r.startsWith("emulator-")?"emulator":"device",booted:n})}return t}async function y(e){try{let t=await m("adb",["-s",e,"shell","getprop","sys.boot_completed"],{allowFailure:!0});return"1"===t.stdout.trim()}catch{return!1}}async function A(e,t=6e4){let i=Date.now();for(;Date.now()-i<t;){if(await y(e))return;await new Promise(e=>setTimeout(e,1e3))}throw new p("COMMAND_FAILED","Android device did not finish booting in time",{serial:e,timeoutMs:t})}async function I(e,t={}){let i,r=t.attempts??3,a=t.baseDelayMs??200,n=t.maxDelayMs??2e3,o=t.jitter??.2;for(let s=1;s<=r;s+=1)try{return await e()}catch(l){if(i=l,s>=r||t.shouldRetry&&!t.shouldRetry(l,s))break;let e=function(e,t,i,r){let a=Math.min(t,e*2**(r-1));return Math.max(0,a+a*i*(2*Math.random()-1))}(a,n,o,s);await function(e){return new Promise(t=>setTimeout(t,e))}(e)}if(i)throw i;throw new p("COMMAND_FAILED","retry failed")}let b={settings:{type:"intent",value:"android.settings.SETTINGS"}};function N(e,t){return["-s",e.id,...t]}async function k(e,t){let i=t.trim();if(i.includes("."))return{type:"package",value:i};let r=b[i.toLowerCase()];if(r)return r;let a=(await m("adb",N(e,["shell","pm","list","packages"]))).stdout.split("\n").map(e=>e.replace("package:","").trim()).filter(Boolean).filter(e=>e.toLowerCase().includes(i.toLowerCase()));if(1===a.length)return{type:"package",value:a[0]};if(a.length>1)throw new p("INVALID_ARGS",`Multiple packages matched "${t}"`,{matches:a});throw new p("APP_NOT_INSTALLED",`No package found matching "${t}"`)}async function S(e,t="launchable"){if("launchable"===t){let t=await m("adb",N(e,["shell","cmd","package","query-activities","--brief","-a","android.intent.action.MAIN","-c","android.intent.category.LAUNCHER"]),{allowFailure:!0});if(0===t.exitCode&&t.stdout.trim().length>0){let e=new Set;for(let i of t.stdout.split("\n")){let t=i.trim();if(!t)continue;let r=t.split(/\s+/)[0],a=r.includes("/")?r.split("/")[0]:r;a&&e.add(a)}if(e.size>0)return Array.from(e)}}return(await m("adb",N(e,"user-installed"===t?["shell","pm","list","packages","-3"]:["shell","pm","list","packages"]))).stdout.split("\n").map(e=>e.replace("package:","").trim()).filter(Boolean)}async function D(e,t="launchable"){let i=await S(e,t),r=new Set("launchable"===t?i:await S(e,"launchable"));return i.map(e=>({package:e,launchable:r.has(e)}))}async function x(e){let t=await L(e,[["shell","dumpsys","window","windows"],["shell","dumpsys","window"]]);if(t)return t;let i=await L(e,[["shell","dumpsys","activity","activities"],["shell","dumpsys","activity"]]);return i||{}}async function L(e,t){for(let i 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 i=t.exec(e);if(i)return{package:i[1],activity:i[2]}}return null}((await m("adb",N(e,i),{allowFailure:!0})).stdout??"");if(t)return t}return null}async function _(e,t,i){e.booted||await A(e.id);let r=await k(e,t);if("intent"===r.type){if(i)throw new p("INVALID_ARGS","Activity override requires a package name, not an intent");await m("adb",N(e,["shell","am","start","-a",r.value]));return}if(i){let t=i.includes("/")?i:`${r.value}/${i.startsWith(".")?i:`.${i}`}`;await m("adb",N(e,["shell","am","start","-a","android.intent.action.MAIN","-c","android.intent.category.DEFAULT","-c","android.intent.category.LAUNCHER","-n",t]));return}await m("adb",N(e,["shell","am","start","-a","android.intent.action.MAIN","-c","android.intent.category.DEFAULT","-c","android.intent.category.LAUNCHER","-p",r.value]))}async function O(e){e.booted||await A(e.id)}async function P(e,t){if("settings"===t.trim().toLowerCase())return void await m("adb",N(e,["shell","am","force-stop","com.android.settings"]));let i=await k(e,t);if("intent"===i.type)throw new p("INVALID_ARGS","Close requires a package name, not an intent");await m("adb",N(e,["shell","am","force-stop",i.value]))}async function M(e,t,i){await m("adb",N(e,["shell","input","tap",String(t),String(i)]))}async function R(e){await m("adb",N(e,["shell","input","keyevent","4"]))}async function C(e){await m("adb",N(e,["shell","input","keyevent","3"]))}async function E(e){await m("adb",N(e,["shell","input","keyevent","187"]))}async function $(e,t,i,r=800){await m("adb",N(e,["shell","input","swipe",String(t),String(i),String(t),String(i),String(r)]))}async function F(e,t){let i=t.replace(/ /g,"%s");await m("adb",N(e,["shell","input","text",i]))}async function T(e,t,i){await M(e,t,i)}async function V(e,t,i,r){await T(e,t,i);let a=null;for(let s of[{clearPadding:12,minClear:8,maxClear:48,chunkSize:4,delayMs:0},{clearPadding:24,minClear:16,maxClear:96,chunkSize:1,delayMs:15}]){var n,o;let l=(n=r.length+s.clearPadding,o=s.minClear,Math.max(o,Math.min(s.maxClear,n)));if(await K(e,l),await Y(e,r,s.chunkSize,s.delayMs),(a=await Z(e,t,i))===r)return}throw new p("COMMAND_FAILED","Android fill verification failed",{expected:r,actual:a??null})}async function B(e,t,i=.6){let{width:r,height:a}=await J(e),n=Math.floor(r*i),o=Math.floor(a*i),s=Math.floor(r/2),l=Math.floor(a/2),c=s,u=l,d=s,f=l;switch(t){case"up":u=l-Math.floor(o/2),f=l+Math.floor(o/2);break;case"down":u=l+Math.floor(o/2),f=l-Math.floor(o/2);break;case"left":c=s-Math.floor(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 p("INVALID_ARGS",`Unknown direction: ${t}`)}await m("adb",N(e,["shell","input","swipe",String(c),String(u),String(d),String(f),"300"]))}async function j(e,t){for(let i=0;i<8;i+=1){let i="";try{i=await H(e)}catch(t){let e=t instanceof Error?t.message:String(t);throw new p("UNSUPPORTED_OPERATION",`uiautomator dump failed: ${e}`)}if(function(e,t){let i=t.toLowerCase(),r=/<node[^>]+>/g,a=r.exec(e);for(;a;){let t=a[0],n=/text="([^"]*)"/.exec(t),o=/content-desc="([^"]*)"/.exec(t),s=(n?.[1]??"").toLowerCase(),l=(o?.[1]??"").toLowerCase();if(s.includes(i)||l.includes(i)){let e=/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/.exec(t);if(e){let t=Number(e[1]),i=Number(e[2]);return{x:Math.floor((t+Number(e[3]))/2),y:Math.floor((i+Number(e[4]))/2)}}return{x:0,y:0}}a=r.exec(e)}return null}(i,t))return;await B(e,"down",.5)}throw new p("COMMAND_FAILED",`Could not find element containing "${t}" after scrolling`)}async function G(e,t){let i=await m("adb",N(e,["exec-out","screencap","-p"]),{binaryStdout:!0});if(!i.stdoutBuffer)throw new p("COMMAND_FAILED","Failed to capture screenshot");await s.writeFile(t,i.stdoutBuffer)}async function U(e,t,i){let r=t.toLowerCase(),a=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 p("INVALID_ARGS",`Invalid setting state: ${e}`)}(i);switch(r){case"wifi":return void await m("adb",N(e,["shell","svc","wifi",a?"enable":"disable"]));case"airplane":await m("adb",N(e,["shell","settings","put","global","airplane_mode_on",a?"1":"0"])),await m("adb",N(e,["shell","am","broadcast","-a","android.intent.action.AIRPLANE_MODE","--ez","state",a?"true":"false"]));return;case"location":return void await m("adb",N(e,["shell","settings","put","secure","location_mode",a?"3":"0"]));default:throw new p("INVALID_ARGS",`Unsupported setting: ${t}`)}}async function q(e,t={}){return function(e,t,i){let r=function(e){let t={type:null,label:null,value:null,identifier:null,depth:-1,children:[]},i=[t],r=/<node\b[^>]*>|<\/node>/g,a=r.exec(e);for(;a;){let t=a[0];if(t.startsWith("</node")){i.length>1&&i.pop(),a=r.exec(e);continue}let n=et(t),o=ei(n.bounds),s=i[i.length-1],l={type:n.className,label:n.text||n.desc,value:n.text,identifier:n.resourceId,rect:o,enabled:n.enabled,hittable:n.clickable??n.focusable,depth:s.depth+1,parentIndex:void 0,children:[]};s.children.push(l),t.endsWith("/>")||i.push(l),a=r.exec(e)}return t}(e),a=[],n=!1,o=i.depth??1/0,s=i.scope?function(e,t){let i=t.toLowerCase(),r=[...e.children];for(;r.length>0;){let e=r.shift(),t=e.label?.toLowerCase()??"",a=e.value?.toLowerCase()??"",n=e.identifier?.toLowerCase()??"";if(t.includes(i)||a.includes(i)||n.includes(i))return e;r.push(...e.children)}return null}(r,i.scope):null,l=s?[s]:r.children,c=new Map,u=e=>{let t=c.get(e);if(void 0!==t)return t;for(let t of e.children)if(t.hittable||u(t))return c.set(e,!0),!0;return c.set(e,!1),!1},d=(e,t,r,s=!1,l=!1)=>{var c,f,p,m,h,w;let g,v,y,A,I,b,N,k;if(a.length>=800){n=!0;return}if(t>o)return;let S=!!i.raw||(c=e,f=i,p=s,m=u(e),h=l,v=er(c.type),y=!!(c.label&&c.label.trim().length>0),A=!!(c.identifier&&c.identifier.trim().length>0),I=y&&!ea(c.label??""),b=A&&!ea(c.identifier??""),N=(g=(w=v).split(".").pop()??w).includes("layout")||"viewgroup"===g||"view"===g,k="imageview"===v||"imagebutton"===v,f.interactiveOnly?!!c.hittable||!!(I||b)&&!k&&(!N||!!h)&&(p||m||h):f.compact?I||b||!!c.hittable:!N&&!k||!!c.hittable||!!I||!!b&&!!m||m),D=r;S&&(D=a.length,a.push({index:D,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:r}));let x=s||!!e.hittable,L=l||function(e){if(!e)return!1;let t=er(e);return t.includes("recyclerview")||t.includes("listview")||t.includes("gridview")}(e.type);for(let i of e.children)if(d(i,t+1,D,x,L),n)return};for(let e of l)if(d(e,0,void 0,!1,!1),n)break;return n?{nodes:a,truncated:n}:{nodes:a}}(await H(e),800,t)}async function W(){if(!await w("adb"))throw new p("TOOL_MISSING","adb not found in PATH")}async function J(e){let t=(await m("adb",N(e,["shell","wm","size"]))).stdout.match(/Physical size:\s*(\d+)x(\d+)/);if(!t)throw new p("COMMAND_FAILED","Unable to read screen size");return{width:Number(t[1]),height:Number(t[2])}}async function H(e){return I(()=>z(e),{shouldRetry:X})}async function z(e){return await m("adb",N(e,["shell","uiautomator","dump","/sdcard/window_dump.xml"])),(await m("adb",N(e,["shell","cat","/sdcard/window_dump.xml"]))).stdout}function X(e){if(!(e instanceof p)||"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(e,t,i,r){let a=Math.max(1,Math.floor(i));for(let i=0;i<t.length;i+=a){let n=t.slice(i,i+a);await F(e,n),r>0&&i+a<t.length&&await ee(r)}}async function K(e,t){let i=Math.max(0,t);await m("adb",N(e,["shell","input","keyevent","KEYCODE_MOVE_END"]),{allowFailure:!0});for(let t=0;t<i;t+=24){let r=Math.min(24,i-t);await m("adb",N(e,["shell","input","keyevent",...Array(r).fill("KEYCODE_DEL")]),{allowFailure:!0})}}async function Z(e,t,i){let r,a=await H(e),n=/<node\b[^>]*>/g,o=null,s=null,l=null;for(;null!==(r=n.exec(a));){let e=et(r[0]),a=ei(e.bounds);if(!a)continue;let n=e.className??"",c=(e.text??"").replace(/&quot;/g,'"').replace(/&apos;/g,"'").replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&amp;/g,"&"),u=e.focused??!1;if(!c)continue;let d=Math.max(1,a.width*a.height),f=t>=a.x&&t<=a.x+a.width&&i>=a.y&&i<=a.y+a.height;if(u&&Q(n)){(!o||d<=o.area)&&(o={text:c,area:d});continue}if(f&&Q(n)){(!s||d<=s.area)&&(s={text:c,area:d});continue}f&&(!l||d<=l.area)&&(l={text:c,area:d})}return o?.text??s?.text??l?.text??null}function Q(e){let t=e.toLowerCase();return t.includes("edittext")||t.includes("textfield")}async function ee(e){await new Promise(t=>setTimeout(t,e))}function et(e){let t=t=>{let i=RegExp(`${t}="([^"]*)"`).exec(e);return i?i[1]:null},i=e=>{let i=t(e);if(null!==i)return"true"===i};return{text:t("text"),desc:t("content-desc"),resourceId:t("resource-id"),className:t("class"),bounds:t("bounds"),clickable:i("clickable"),enabled:i("enabled"),focusable:i("focusable"),focused:i("focused")}}function ei(e){if(!e)return;let t=/\[(\d+),(\d+)\]\[(\d+),(\d+)\]/.exec(e);if(!t)return;let i=Number(t[1]),r=Number(t[2]);return{x:i,y:r,width:Math.max(0,Number(t[3])-i),height:Math.max(0,Number(t[4])-r)}}function er(e){return e?e.toLowerCase():""}function ea(e){let t=e.trim();return!!t&&/^[\w.]+:id\/[\w.-]+$/i.test(t)}async function en(){if("darwin"!==process.platform)throw new p("UNSUPPORTED_PLATFORM","iOS tools are only available on macOS");if(!await w("xcrun"))throw new p("TOOL_MISSING","xcrun not found in PATH");let e=[],t=await m("xcrun",["simctl","list","devices","-j"]);try{let i=JSON.parse(t.stdout);for(let t of Object.values(i.devices))for(let i of t)i.isAvailable&&e.push({platform:"ios",id:i.udid,name:i.name,kind:"simulator",booted:"Booted"===i.state})}catch(e){throw new p("COMMAND_FAILED","Failed to parse simctl devices JSON",void 0,e)}if(await w("xcrun"))try{let t=await m("xcrun",["devicectl","list","devices","--json"]);for(let i of JSON.parse(t.stdout).devices??[])i.platform?.toLowerCase().includes("ios")&&e.push({platform:"ios",id:i.identifier,name:i.name,kind:"device",booted:!0})}catch{}return e}let eo={settings:"com.apple.Preferences"};async function es(e,t){let i=t.trim();if(i.includes("."))return i;let r=eo[i.toLowerCase()];if(r)return r;if("simulator"===e.kind){let r=(await ep(e)).filter(e=>e.name.toLowerCase()===i.toLowerCase());if(1===r.length)return r[0].bundleId;if(r.length>1)throw new p("INVALID_ARGS",`Multiple apps matched "${t}"`,{matches:r})}throw new p("APP_NOT_INSTALLED",`No app found matching "${t}"`)}async function el(e,t){let i=await es(e,t);if("simulator"===e.kind){await em(e),await m("open",["-a","Simulator"],{allowFailure:!0}),await m("xcrun",["simctl","launch",e.id,i]);return}await m("xcrun",["devicectl","device","process","launch","--device",e.id,i])}async function ec(e){"simulator"!==e.kind||"Booted"!==await eh(e.id)&&(await em(e),await m("open",["-a","Simulator"],{allowFailure:!0}))}async function eu(e,t){let i=await es(e,t);if("simulator"===e.kind){await em(e);let t=await m("xcrun",["simctl","terminate",e.id,i],{allowFailure:!0});if(0!==t.exitCode){if(t.stderr.toLowerCase().includes("found nothing to terminate"))return;throw new p("COMMAND_FAILED",`xcrun exited with code ${t.exitCode}`,{cmd:"xcrun",args:["simctl","terminate",e.id,i],stdout:t.stdout,stderr:t.stderr,exitCode:t.exitCode})}return}await m("xcrun",["devicectl","device","process","terminate","--device",e.id,i])}async function ed(e,t){if("simulator"===e.kind){await em(e),await m("xcrun",["simctl","io",e.id,"screenshot",t]);return}await m("xcrun",["devicectl","device","screenshot","--device",e.id,t])}async function ef(e,t,i,r){(function(e,t){if("simulator"!==e.kind)throw new p("UNSUPPORTED_OPERATION",`${t} is only supported on iOS simulators in v1`)})(e,"settings"),await em(e);let a=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 p("INVALID_ARGS",`Invalid setting state: ${e}`)}(i);switch(a){case"wifi":return void await m("xcrun",["simctl","status_bar",e.id,"override","--wifiMode",n?"active":"failed"]);case"airplane":n?await m("xcrun",["simctl","status_bar",e.id,"override","--dataNetwork","hide","--wifiMode","failed","--wifiBars","0","--cellularMode","failed","--cellularBars","0","--operatorName",""]):await m("xcrun",["simctl","status_bar",e.id,"clear"]);return;case"location":if(!r)throw new p("INVALID_ARGS","location setting requires an active app in session");await m("xcrun",["simctl","privacy",e.id,n?"grant":"revoke","location",r]);return;default:throw new p("INVALID_ARGS",`Unsupported setting: ${t}`)}}async function ep(e){let t=(await m("xcrun",["simctl","listapps",e.id],{allowFailure:!0})).stdout.trim();if(!t)return[];let i=null;if(t.startsWith("{"))try{i=JSON.parse(t)}catch{i=null}if(!i&&t.startsWith("{"))try{let e=await m("plutil",["-convert","json","-o","-","-"],{allowFailure:!0,stdin:t});0===e.exitCode&&e.stdout.trim().startsWith("{")&&(i=JSON.parse(e.stdout))}catch{i=null}return i?Object.entries(i).map(([e,t])=>({bundleId:e,name:t.CFBundleDisplayName??t.CFBundleName??e})):[]}async function em(e){"simulator"!==e.kind||"Booted"!==await eh(e.id)&&(await m("xcrun",["simctl","boot",e.id],{allowFailure:!0}),await m("xcrun",["simctl","bootstatus",e.id,"-b"],{allowFailure:!0}))}async function eh(e){let t=await m("xcrun",["simctl","list","devices","-j"],{allowFailure:!0});if(0!==t.exitCode)return null;try{let i=JSON.parse(t.stdout);for(let t of Object.values(i.devices??{})){let i=t.find(t=>t.udid===e);if(i)return i.state}}catch{}return null}let ew=new Map;async function eg(e,t,i={}){var r;return"snapshot"===(r=t.command)||"findText"===r||"listTappables"===r||"alert"===r?I(()=>ev(e,t,i),{shouldRetry:eS}):ev(e,t,i)}async function ev(e,t,i={}){if("simulator"!==e.kind)throw new p("UNSUPPORTED_OPERATION","iOS runner only supports simulators in v1");try{let r=await eI(e,i),a=await eD(e,r.port,t,i.logPath),n=await a.text(),o={};try{o=JSON.parse(n)}catch{throw new p("COMMAND_FAILED","Invalid runner response",{text:n})}if(!o.ok)throw new p("COMMAND_FAILED",o.error?.message??"Runner error",{runner:o,xcodebuild:{exitCode:1,stdout:"",stderr:""},logPath:i.logPath});return o.data??{}}catch(a){let r=a instanceof p?a:new p("COMMAND_FAILED",String(a));if("COMMAND_FAILED"===r.code&&"string"==typeof r.message&&r.message.includes("Runner did not accept connection")){await ey(e.id);let r=await eI(e,i),a=await eD(e,r.port,t,i.logPath),n=await a.text(),o={};try{o=JSON.parse(n)}catch{throw new p("COMMAND_FAILED","Invalid runner response",{text:n})}if(!o.ok)throw new p("COMMAND_FAILED",o.error?.message??"Runner error",{runner:o,xcodebuild:{exitCode:1,stdout:"",stderr:""},logPath:i.logPath});return o.data??{}}throw a}}async function ey(e){let t=ew.get(e);if(t){try{await eD(t.device,t.port,{command:"shutdown"})}catch{}try{await t.testPromise}catch{}eO(t.xctestrunPath),eO(t.jsonPath),ew.delete(e)}}async function eA(e){await m("xcrun",["simctl","bootstatus",e,"-b"],{allowFailure:!0})}async function eI(e,t){let i=ew.get(e.id);if(i)return i;await eA(e.id);let r=await eb(e.id,t),a=await eL(),{xctestrunPath:n,jsonPath:s}=await e_(r,{AGENT_DEVICE_RUNNER_PORT:String(a)},`session-${e.id}-${a}`),l=o("xcodebuild",["test-without-building","-only-testing","AgentDeviceRunnerUITests/RunnerTests/testCommand","-parallel-testing-enabled","NO","-test-timeouts-enabled","NO","-maximum-concurrent-test-simulator-destinations","1","-xctestrun",n,"-destination",`platform=iOS Simulator,id=${e.id}`],{onStdoutChunk:e=>{ek(e,t.logPath,t.traceLogPath,t.verbose)},onStderrChunk:e=>{ek(e,t.logPath,t.traceLogPath,t.verbose)},allowFailure:!0,env:{...process.env,AGENT_DEVICE_RUNNER_PORT:String(a)}}),c={device:e,deviceId:e.id,port:a,xctestrunPath:n,jsonPath:s,testPromise:l};return ew.set(e.id,c),c}async function eb(e,t){let i,r=n.join(f.homedir(),".agent-device","ios-runner"),a=n.join(r,"derived");if((i=process.env.AGENT_DEVICE_IOS_CLEAN_DERIVED)&&["1","true","yes","on"].includes(i.toLowerCase()))try{d.rmSync(a,{recursive:!0,force:!0})}catch{}let s=eN(a);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 p("COMMAND_FAILED","iOS runner project not found",{projectPath:u});try{await o("xcodebuild",["build-for-testing","-project",u,"-scheme","AgentDeviceRunner","-parallel-testing-enabled","NO","-maximum-concurrent-test-simulator-destinations","1","-destination",`platform=iOS Simulator,id=${e}`,"-derivedDataPath",a],{onStdoutChunk:e=>{ek(e,t.logPath,t.traceLogPath,t.verbose)},onStderrChunk:e=>{ek(e,t.logPath,t.traceLogPath,t.verbose)}})}catch(i){let e=i instanceof p?i:new p("COMMAND_FAILED",String(i));throw new p("COMMAND_FAILED","xcodebuild build-for-testing failed",{error:e.message,details:e.details,logPath:t.logPath})}let m=eN(a);if(!m)throw new p("COMMAND_FAILED","Failed to locate .xctestrun after build");return m}function eN(e){if(!d.existsSync(e))return null;let t=[],i=[e];for(;i.length>0;){let e=i.pop();for(let r of d.readdirSync(e,{withFileTypes:!0})){let a=n.join(e,r.name);if(r.isDirectory()){i.push(a);continue}if(r.isFile()&&r.name.endsWith(".xctestrun"))try{let e=d.statSync(a);t.push({path:a,mtimeMs:e.mtimeMs})}catch{}}}return 0===t.length?null:(t.sort((e,t)=>t.mtimeMs-e.mtimeMs),t[0]?.path??null)}function ek(e,t,i,r){t&&d.appendFileSync(t,e),i&&d.appendFileSync(i,e),r&&process.stderr.write(e)}function eS(e){if(!(e instanceof p)||"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,i,r){let a=Date.now(),n=null;for(;Date.now()-a<15e3;)try{return await fetch(`http://127.0.0.1:${t}/command`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)})}catch(e){n=e,await new Promise(e=>setTimeout(e,100))}if("simulator"===e.kind){let r=await ex(e.id,t,i);return new Response(r.body,{status:r.status})}throw new p("COMMAND_FAILED","Runner did not accept connection",{port:t,logPath:r,lastError:n?String(n):void 0})}async function ex(e,t,i){let r=JSON.stringify(i),a=await m("xcrun",["simctl","spawn",e,"/usr/bin/curl","-s","-X","POST","-H","Content-Type: application/json","--data",r,`http://127.0.0.1:${t}/command`],{allowFailure:!0}),n=a.stdout;if(0!==a.exitCode)throw new p("COMMAND_FAILED","Runner did not accept connection (simctl spawn)",{port:t,stdout:a.stdout,stderr:a.stderr,exitCode:a.exitCode});return{status:200,body:n}}async function eL(){return await new Promise((e,t)=>{let i=h.createServer();i.listen(0,"127.0.0.1",()=>{let r=i.address();i.close(),"object"==typeof r&&r?.port?e(r.port):t(new p("COMMAND_FAILED","Failed to allocate port"))}),i.on("error",t)})}async function e_(e,t,i){let r,a=n.dirname(e),o=i.replace(/[^a-zA-Z0-9._-]/g,"_"),s=n.join(a,`AgentDeviceRunner.env.${o}.json`),l=n.join(a,`AgentDeviceRunner.env.${o}.xctestrun`),c=await m("plutil",["-convert","json","-o","-",e],{allowFailure:!0});if(0!==c.exitCode||!c.stdout.trim())throw new p("COMMAND_FAILED","Failed to read xctestrun plist",{xctestrunPath:e,stderr:c.stderr});try{r=JSON.parse(c.stdout)}catch(t){throw new p("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}},f=r.TestConfigurations;if(Array.isArray(f))for(let e of f){if(!e||"object"!=typeof e)continue;let t=e.TestTargets;if(Array.isArray(t))for(let e of t)e&&"object"==typeof e&&u(e)}for(let[e,t]of Object.entries(r))t&&"object"==typeof t&&t.TestBundlePath&&(u(t),r[e]=t);d.writeFileSync(s,JSON.stringify(r,null,2));let h=await m("plutil",["-convert","xml1","-o",l,s],{allowFailure:!0});if(0!==h.exitCode)throw new p("COMMAND_FAILED","Failed to write xctestrun plist",{tmpXctestrunPath:l,stderr:h.stderr});return{xctestrunPath:l,jsonPath:s}}function eO(e){try{d.existsSync(e)&&d.unlinkSync(e)}catch{}}async function eP(e,t={}){let i,r;if("ios"!==e.platform||"simulator"!==e.kind)throw new p("UNSUPPORTED_OPERATION","AX snapshot is only supported on iOS simulators");let a=await eM(),n=await I(async()=>{var e,i;let r,n,o,s=await m(a,[],{allowFailure:!0});if(t.traceLogPath&&(e=t.traceLogPath,r=((i=s).stdout??"").toString(),n=(i.stderr??"").toString(),o=`
2
+ [axsnapshot] exit=${i.exitCode} stdoutBytes=${r.length} stderrBytes=${n.length}
3
+ `,d.appendFileSync(e,o),(0!==i.exitCode||n.length>0)&&(n.length>0&&d.appendFileSync(e,`${n}
4
+ `),0!==i.exitCode&&r.length>0&&d.appendFileSync(e,`${r}
5
+ `))),0!==s.exitCode){let e,t,i=(s.stderr??"").toString(),r=(e=i.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.":"",a=!!((t=i.toLowerCase()).includes("could not find ios app content")||t.includes("timeout"));throw new p("COMMAND_FAILED","AX snapshot failed",{stderr:`${i}${r}`,stdout:s.stdout,retryable:a})}return s},{shouldRetry:e=>{var t;return(t=e)instanceof p&&"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");i=e.root,r=e.windowFrame??void 0}else i=e}catch(e){throw new p("COMMAND_FAILED","Invalid AX snapshot JSON",{error:String(e)})}let o=i.frame??r,s=[],l=[],c=(e,t)=>{e.frame&&s.push(e.frame);let i=e.frame&&o?{x:e.frame.x-o.x,y:e.frame.y-o.y,width:e.frame.width,height:e.frame.height}:e.frame;for(let r of(l.push({...e,frame:i,children:void 0,depth:t}),e.children??[]))c(r,t+1)};return c(i,0),{nodes:(function(e,t,i){if(!t||0===i.length)return e;let r=1/0,a=1/0;for(let e of i)e.x<r&&(r=e.x),e.y<a&&(a=e.y);return r<=5&&a<=5?e.map(e=>({...e,frame:e.frame?{x:e.frame.x+t.x,y:e.frame.y+t.y,width:e.frame.width,height:e.frame.height}:void 0})):e})(l,o,s).map((e,t)=>({index:t,type:e.subrole??e.role,label:e.label,value:e.value,identifier:e.identifier,rect:e.frame?{x:e.frame.x,y:e.frame.y,width:e.frame.width,height:e.frame.height}:void 0,depth:e.depth}))}}async function eM(){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"),i=process.env.AGENT_DEVICE_AX_BINARY;if(i&&d.existsSync(i))return i;let r=n.join(e,"dist","bin","axsnapshot");if(d.existsSync(r))return r;let a=n.join(t,".build","release","axsnapshot");if(d.existsSync(a))return a;let o=await m("swift",["build","-c","release"],{cwd:t,allowFailure:!0});if(0!==o.exitCode||!d.existsSync(a))throw new p("COMMAND_FAILED","Failed to build AX snapshot tool",{stderr:o.stderr,stdout:o.stdout});return a}async function eR(e){let t={platform:e.platform,deviceName:e.device,udid:e.udid,serial:e.serial};if("android"===t.platform){await W();let e=await v();return await g(e,t)}if("ios"===t.platform){let e=await en();return await g(e,t)}let i=[];try{i.push(...await v())}catch{}try{i.push(...await en())}catch{}return await g(i,t)}async function eC(e,t,i,r,a){let o=function(e,t){switch(e.platform){case"android":return{open:(t,i)=>_(e,t,i?.activity),openDevice:()=>O(e),close:t=>P(e,t),tap:(t,i)=>M(e,t,i),longPress:(t,i,r)=>$(e,t,i,r),focus:(t,i)=>T(e,t,i),type:t=>F(e,t),fill:(t,i,r)=>V(e,t,i,r),scroll:(t,i)=>B(e,t,i),scrollIntoView:t=>j(e,t),screenshot:t=>G(e,t)};case"ios":var i,r;let a;return{open:t=>el(e,t),openDevice:()=>ec(e),close:t=>eu(e,t),screenshot:t=>ed(e,t),...(i=e,a={verbose:(r=t).verbose,logPath:r.logPath,traceLogPath:r.traceLogPath},{tap:async(e,t)=>{await eg(i,{command:"tap",x:e,y:t,appBundleId:r.appBundleId},a)},longPress:async(e,t,n)=>{await eg(i,{command:"longPress",x:e,y:t,durationMs:n,appBundleId:r.appBundleId},a)},focus:async(e,t)=>{await eg(i,{command:"tap",x:e,y:t,appBundleId:r.appBundleId},a)},type:async e=>{await eg(i,{command:"type",text:e,appBundleId:r.appBundleId},a)},fill:async(e,t,n)=>{await eg(i,{command:"tap",x:e,y:t,appBundleId:r.appBundleId},a),await eg(i,{command:"type",text:n,clearFirst:!0,appBundleId:r.appBundleId},a)},scroll:async(e,t)=>{if(!["up","down","left","right"].includes(e))throw new p("INVALID_ARGS",`Unknown direction: ${e}`);let n=function(e){switch(e){case"up":return"down";case"down":return"up";case"left":return"right";case"right":return"left"}}(e);await eg(i,{command:"swipe",direction:n,appBundleId:r.appBundleId},a)},scrollIntoView:async e=>{for(let t=0;t<8;t+=1){let n=await eg(i,{command:"findText",text:e,appBundleId:r.appBundleId},a);if(n?.found)return{attempts:t+1};await eg(i,{command:"swipe",direction:"up",appBundleId:r.appBundleId},a),await new Promise(e=>setTimeout(e,300))}throw new p("COMMAND_FAILED",`scrollintoview could not find text: ${e}`)}})};default:throw new p("UNSUPPORTED_PLATFORM",`Unsupported platform: ${e.platform}`)}}(e,{appBundleId:a?.appBundleId,verbose:a?.verbose,logPath:a?.logPath,traceLogPath:a?.traceLogPath});switch(t){case"open":{let e=i[0];if(!e)return await o.openDevice(),{app:null};return await o.open(e,{activity:a?.activity}),{app:e}}case"close":{let e=i[0];if(!e)return{closed:"session"};return await o.close(e),{app:e}}case"press":{let[e,t]=i.map(Number);if(Number.isNaN(e)||Number.isNaN(t))throw new p("INVALID_ARGS","press requires x y");return await o.tap(e,t),{x:e,y:t}}case"long-press":{let e=Number(i[0]),t=Number(i[1]),r=i[2]?Number(i[2]):void 0;if(Number.isNaN(e)||Number.isNaN(t))throw new p("INVALID_ARGS","long-press requires x y [durationMs]");return await o.longPress(e,t,r),{x:e,y:t,durationMs:r}}case"focus":{let[e,t]=i.map(Number);if(Number.isNaN(e)||Number.isNaN(t))throw new p("INVALID_ARGS","focus requires x y");return await o.focus(e,t),{x:e,y:t}}case"type":{let e=i.join(" ");if(!e)throw new p("INVALID_ARGS","type requires text");return await o.type(e),{text:e}}case"fill":{let e=Number(i[0]),t=Number(i[1]),r=i.slice(2).join(" ");if(Number.isNaN(e)||Number.isNaN(t)||!r)throw new p("INVALID_ARGS","fill requires x y text");return await o.fill(e,t,r),{x:e,y:t,text:r}}case"scroll":{let e=i[0],t=i[1]?Number(i[1]):void 0;if(!e)throw new p("INVALID_ARGS","scroll requires direction");return await o.scroll(e,t),{direction:e,amount:t}}case"scrollintoview":{let e=i.join(" ").trim();if(!e)throw new p("INVALID_ARGS","scrollintoview requires text");let t=await o.scrollIntoView(e);if(t?.attempts)return{text:e,attempts:t.attempts};return{text:e}}case"pinch":{let t=Number(i[0]),r=i[1]?Number(i[1]):void 0,n=i[2]?Number(i[2]):void 0;if(Number.isNaN(t)||t<=0)throw new p("INVALID_ARGS","pinch requires scale > 0");return await eg(e,{command:"pinch",scale:t,x:r,y:n,appBundleId:a?.appBundleId},{verbose:a?.verbose,logPath:a?.logPath,traceLogPath:a?.traceLogPath}),{scale:t,x:r,y:n}}case"screenshot":{let e=i[0]??r??`./screenshot-${Date.now()}.png`;return await s.mkdir(n.dirname(e),{recursive:!0}),await o.screenshot(e),{path:e}}case"back":if("ios"===e.platform)return await eg(e,{command:"back",appBundleId:a?.appBundleId},{verbose:a?.verbose,logPath:a?.logPath,traceLogPath:a?.traceLogPath}),{action:"back"};return await R(e),{action:"back"};case"home":if("ios"===e.platform)return await eg(e,{command:"home",appBundleId:a?.appBundleId},{verbose:a?.verbose,logPath:a?.logPath,traceLogPath:a?.traceLogPath}),{action:"home"};return await C(e),{action:"home"};case"app-switcher":if("ios"===e.platform)return await eg(e,{command:"appSwitcher",appBundleId:a?.appBundleId},{verbose:a?.verbose,logPath:a?.logPath,traceLogPath:a?.traceLogPath}),{action:"app-switcher"};return await E(e),{action:"app-switcher"};case"settings":{let[t,r,n]=i;if("ios"===e.platform)return await ef(e,t,r,n??a?.appBundleId),{setting:t,state:r};return await U(e,t,r),{setting:t,state:r}}case"snapshot":{let t=a?.snapshotBackend??"xctest";if("ios"===e.platform){if("ax"===t)return{nodes:(await eP(e,{traceLogPath:a?.traceLogPath})).nodes??[],truncated:!1,backend:"ax"};let i=await eg(e,{command:"snapshot",appBundleId:a?.appBundleId,interactiveOnly:a?.snapshotInteractiveOnly,compact:a?.snapshotCompact,depth:a?.snapshotDepth,scope:a?.snapshotScope,raw:a?.snapshotRaw},{verbose:a?.verbose,logPath:a?.logPath,traceLogPath:a?.traceLogPath}),r=i.nodes??[];if(0===r.length)try{return{nodes:(await eP(e,{traceLogPath:a?.traceLogPath})).nodes??[],truncated:!1,backend:"ax"}}catch{}return{nodes:r,truncated:i.truncated??!1,backend:"xctest"}}let i=await q(e,{interactiveOnly:a?.snapshotInteractiveOnly,compact:a?.snapshotCompact,depth:a?.snapshotDepth,scope:a?.snapshotScope,raw:a?.snapshotRaw});return{nodes:i.nodes??[],truncated:i.truncated??!1,backend:"android"}}default:throw new p("INVALID_ARGS",`Unknown command: ${t}`)}}let eE={alert:{ios:{simulator:!0},android:{}},pinch:{ios:{simulator:!0},android:{}},"app-switcher":{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},apps:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},back:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},click:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},close:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},fill:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},find:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},focus:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},get:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},is:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},home:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},"long-press":{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},open:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},press:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},record:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},screenshot:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},scroll:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},settings:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},snapshot:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},type:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}},wait:{ios:{simulator:!0},android:{emulator:!0,device:!0,unknown:!0}}};function e$(e,t){let i=eE[e];if(!i)return!0;let r=i[t.platform];return!!r&&!0===r[t.kind??"unknown"]}function eF(e){let t=e.result?.text;if("string"==typeof t&&t.trim().length>0)return t;let i=e.positionals??[];return 0===i.length?"":i[0].startsWith("@")?i.length>=3?i.slice(2).join(" ").trim():i.slice(1).join(" ").trim():!(i.length>=3)||Number.isNaN(Number(i[0]))||Number.isNaN(Number(i[1]))?i.slice(1).join(" ").trim():i.slice(2).join(" ").trim()}function eT(e){let t=new Set,i=[];for(let r of e)t.has(r)||(t.add(r),i.push(r));return i}function eV(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}class eB{get(e){return this.sessions.get(e)}has(e){return this.sessions.has(e)}set(e,t){this.sessions.set(e,t)}delete(e){return this.sessions.delete(e)}values(){return this.sessions.values()}toArray(){return Array.from(this.sessions.values())}recordAction(e,t){t.flags?.noRecord||(t.flags?.saveScript&&(e.recordSession=!0),e.actions.push({ts:Date.now(),command:t.command,positionals:t.positionals,flags:function(e){if(!e)return{};let{platform:t,device:i,udid:r,serial:a,out:n,verbose:o,snapshotInteractiveOnly:s,snapshotCompact:l,snapshotDepth:c,snapshotScope:u,snapshotRaw:d,snapshotBackend:f,appsMetadata:p,saveScript:m,noRecord:h}=e;return{platform:t,device:i,udid:r,serial:a,out:n,verbose:o,snapshotInteractiveOnly:s,snapshotCompact:l,snapshotDepth:c,snapshotScope:u,snapshotRaw:d,snapshotBackend:f,appsMetadata:p,saveScript:m,noRecord:h}}(t.flags),result:t.result}))}writeSessionLog(e){try{if(!e.recordSession)return;d.existsSync(this.sessionsDir)||d.mkdirSync(this.sessionsDir,{recursive:!0});let t=e.name.replace(/[^a-zA-Z0-9._-]/g,"_"),i=new Date(e.createdAt).toISOString().replace(/[:.]/g,"-"),r=n.join(this.sessionsDir,`${t}-${i}.ad`),a=function(e,t){let i=[],r=e.device.name.replace(/"/g,'\\"'),a=e.device.kind?` kind=${e.device.kind}`:"";for(let n of(i.push(`context platform=${e.device.platform} device="${r}"${a} theme=unknown`),t))n.flags?.noRecord||i.push(function(e){let t=[e.command];if("click"===e.command){let i=e.positionals?.[0];if(i){if(t.push(ej(i)),i.startsWith("@")){let i=e.result?.refLabel;"string"==typeof i&&i.trim().length>0&&t.push(ej(i))}return t.join(" ")}}if("fill"===e.command){let i=e.positionals?.[0];if(i&&i.startsWith("@")){t.push(ej(i));let r=e.result?.refLabel,a=e.positionals.slice(1).join(" ");return"string"==typeof r&&r.trim().length>0&&t.push(ej(r)),a&&t.push(ej(a)),t.join(" ")}}if("get"===e.command){let i=e.positionals?.[0],r=e.positionals?.[1];if(i&&r){if(t.push(ej(i)),t.push(ej(r)),r.startsWith("@")){let i=e.result?.refLabel;"string"==typeof i&&i.trim().length>0&&t.push(ej(i))}return 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",ej(e.flags.snapshotScope)),e.flags?.snapshotRaw&&t.push("--raw"),e.flags?.snapshotBackend&&t.push("--backend",e.flags.snapshotBackend),t.join(" ");for(let i of e.positionals??[])t.push(ej(i));return t.join(" ")}(n));return`${i.join("\n")}
6
+ `}(e,this.buildOptimizedActions(e));d.writeFileSync(r,a)}catch{}}defaultTracePath(e){let t=e.name.replace(/[^a-zA-Z0-9._-]/g,"_"),i=new Date().toISOString().replace(/[:.]/g,"-");return n.join(this.sessionsDir,`${t}-${i}.trace.log`)}static expandHome(e){return e.startsWith("~/")?n.join(f.homedir(),e.slice(2)):n.resolve(e)}buildOptimizedActions(e){let t=[];for(let i of e.actions){if("snapshot"===i.command)continue;let r=Array.isArray(i.result?.selectorChain)&&i.result?.selectorChain.every(e=>"string"==typeof e)?i.result.selectorChain:[];if(r.length>0&&("click"===i.command||"fill"===i.command||"get"===i.command)){let e=r.join(" || ");if("click"===i.command){t.push({...i,positionals:[e]});continue}if("fill"===i.command){let r=eF(i);if(r.length>0){t.push({...i,positionals:[e,r]});continue}}if("get"===i.command){let r=i.positionals?.[0];if("text"===r||"attrs"===r){t.push({...i,positionals:[r,e]});continue}}}if("click"===i.command||"fill"===i.command||"get"===i.command){let r=i.result?.refLabel;"string"==typeof r&&r.trim().length>0&&t.push({ts:i.ts,command:"snapshot",positionals:[],flags:{platform:e.device.platform,snapshotInteractiveOnly:!0,snapshotCompact:!0,snapshotScope:r.trim()},result:{scope:r.trim()}})}t.push(i)}return t}constructor(e){eV(this,"sessions",new Map),eV(this,"sessionsDir",void 0),this.sessionsDir=e}}function ej(e){let t=e.trim();return t.startsWith("@")||/^-?\d+(\.\d+)?$/.test(t)?t:JSON.stringify(t)}function eG(e,t,i,r){return{appBundleId:i,activity:t?.activity,verbose:t?.verbose,logPath:e,traceLogPath:r,snapshotInteractiveOnly:t?.snapshotInteractiveOnly,snapshotCompact:t?.snapshotCompact,snapshotDepth:t?.snapshotDepth,snapshotScope:t?.snapshotScope,snapshotRaw:t?.snapshotRaw,snapshotBackend:t?.snapshotBackend}}async function eU(e){if("ios"===e.platform&&"simulator"===e.kind){let{ensureBootedSimulator:t}=await Promise.resolve().then(()=>({ensureBootedSimulator:em}));await t(e);return}if("android"===e.platform){let{waitForAndroidBoot:t}=await Promise.resolve().then(()=>({waitForAndroidBoot:A}));await t(e.id)}}function eq(e){return e.map((e,t)=>({...e,ref:`e${t+1}`}))}function eW(e){let t=e.trim();return t.startsWith("@")?t.slice(1)||null:t.startsWith("e")?t:null}function eJ(e,t){return e.find(e=>e.ref===t)??null}function eH(e){return{x:Math.round(e.x+e.width/2),y:Math.round(e.y+e.height/2)}}function ez(e,t){let i=t.toLowerCase();return e.find(e=>{let t=(e.label??"").toLowerCase(),r=(e.value??"").toLowerCase(),a=(e.identifier??"").toLowerCase();return t.includes(i)||r.includes(i)||a.includes(i)})??null}function eX(e,t){let i=[e.label,e.value,e.identifier].map(e=>"string"==typeof e?e.trim():"").find(e=>e&&e.length>0);return i&&eY(i)?i:function(e,t){if(!e.rect)return;let i=e.rect.y+e.rect.height/2,r=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||!eY(t))continue;let a=Math.abs(e.rect.y+e.rect.height/2-i);(!r||a<r.distance)&&(r={label:t,distance:a})}return r?.label}(e,t)??(i&&eY(i)?i:void 0)}function eY(e){let t=e.trim();return!(!t||/^(true|false)$/i.test(t)||/^\d+$/.test(t))}function eK(e){let t=[],i=[];for(let r of e){let e=r.depth??0;for(;t.length>0&&e<=t[t.length-1];)t.pop();let a=eZ(r.type??""),n=[r.label,r.value,r.identifier].map(e=>"string"==typeof e?e.trim():"").find(e=>e&&e.length>0),o=!!n&&eY(n);if(("group"===a||"ioscontentgroup"===a)&&!o){t.push(e);continue}let s=Math.max(0,e-t.length);i.push({...r,depth:s})}return i}function eZ(e){let t=e.replace(/XCUIElementType/gi,"").toLowerCase();return t.startsWith("ax")&&(t=t.replace(/^ax/,"")),t}function eQ(e,t){let i=eZ(e);return!i||("android"===t?i.includes("edittext")||i.includes("autocompletetextview"):i.includes("textfield")||i.includes("securetextfield")||i.includes("searchfield")||i.includes("textview")||i.includes("textarea")||"search"===i)}function e0(e){return[e.label,e.value,e.identifier].map(e=>"string"==typeof e?e.trim():"").filter(e=>e.length>0)[0]??""}async function e1(e,t,i,r){let a=e2(await eC(e,"snapshot",[],r?.out,{...eG(t,{...r,snapshotDepth:1,snapshotCompact:!0,snapshotBackend:"ax"},void 0,i)}));if(a?.appName||a?.appBundleId)return{appName:a.appName??a.appBundleId??"unknown",appBundleId:a.appBundleId,source:"snapshot-ax"};let n=e2(await eC(e,"snapshot",[],r?.out,{...eG(t,{...r,snapshotDepth:1,snapshotCompact:!0,snapshotBackend:"xctest"},void 0,i)}));return{appName:n?.appName??n?.appBundleId??"unknown",appBundleId:n?.appBundleId,source:"snapshot-xctest"}}function e2(e){let t=eq(e?.nodes??[]),i=t.find(e=>"application"===eZ(e.type??""))??t[0];if(!i)return null;let r=i.label?.trim(),a=i.identifier?.trim();return r||a?{appName:r||void 0,appBundleId:a||void 0}:null}let e3=new Set(["id","role","text","label","value"]),e4=new Set(["visible","hidden","editable","selected","enabled","hittable"]),e8=new Set([...e3,...e4]);function e6(e){let t=e.trim();if(!t)throw new p("INVALID_ARGS","Selector expression cannot be empty");let i=function(e){let t=[],i="",r=null;for(let a=0;a<e.length;a+=1){let n=e[a];if(('"'===n||"'"===n)&&"\\"!==e[a-1]){r?r===n&&(r=null):r=n,i+=n;continue}if(!r&&"|"===n&&"|"===e[a+1]){let r=i.trim();if(!r)throw new p("INVALID_ARGS",`Invalid selector fallback expression: ${e}`);t.push(r),i="",a+=1;continue}i+=n}let a=i.trim();if(!a)throw new p("INVALID_ARGS",`Invalid selector fallback expression: ${e}`);return t.push(a),t}(t);if(0===i.length)throw new p("INVALID_ARGS","Selector expression cannot be empty");return{raw:t,selectors:i.map(e=>(function(e){let t=e.trim();if(!t)throw new p("INVALID_ARGS","Selector segment cannot be empty");let i=function(e){let t=[],i="",r=null;for(let a=0;a<e.length;a+=1){let n=e[a];if(('"'===n||"'"===n)&&"\\"!==e[a-1]){r?r===n&&(r=null):r=n,i+=n;continue}if(!r&&/\s/.test(n)){i.trim().length>0&&t.push(i.trim()),i="";continue}i+=n}if(r)throw new p("INVALID_ARGS",`Unclosed quote in selector: ${e}`);return i.trim().length>0&&t.push(i.trim()),t}(t);if(0===i.length)throw new p("INVALID_ARGS",`Invalid selector segment: ${e}`);return{raw:t,terms:i.map(ta)}})(e))}}function e5(e,t,i){let r=i.requireRect??!1,a=i.requireUnique??!0,n=[];for(let o=0;o<t.selectors.length;o+=1){let s=t.selectors[o],l=e.filter(e=>(!r||!!e.rect)&&tn(e,s,i.platform));if((n.push({selector:s.raw,matches:l.length}),0!==l.length)&&(!a||1===l.length))return{node:l[0],selector:s,selectorIndex:o,matches:l.length,diagnostics:n}}return null}function e7(e,t,i){let r=i.requireRect??!1,a=[];for(let n=0;n<t.selectors.length;n+=1){let o=t.selectors[n],s=e.filter(e=>(!r||!!e.rect)&&tn(e,o,i.platform));if(a.push({selector:o.raw,matches:s.length}),s.length>0)return{selectorIndex:n,selector:o,matches:s.length,diagnostics:a}}return null}function e9(e,t,i){let r=i.unique??!0;if(0===t.length)return`Selector did not match: ${e.raw}`;let a=t.map(e=>`${e.selector} -> ${e.matches}`).join(", ");return r?`Selector did not resolve uniquely (${a})`:`Selector did not match (${a})`}function te(e){if(0===e.length)return null;let t=0;for(;t<e.length&&function(e){let t=e.trim();if(!t)return!1;if("||"===t)return!0;let i=t.indexOf("=");if(-1!==i){let e=t.slice(0,i).trim().toLowerCase();return e8.has(e)}return e8.has(t.toLowerCase())}(e[t]);)t+=1;if(0===t)return null;let i=e.slice(0,t).join(" ").trim();return i?{selectorExpression:i,rest:e.slice(t)}:null}function tt(e){return!0===e.hittable||!!e.rect&&e.rect.width>0&&e.rect.height>0}function ti(e,t){return eQ(e.type??"",t)&&!1!==e.enabled}function tr(e,t,i={}){let r=[],a=eZ(e.type??""),n=tu(e.identifier),o=tu(e.label),s=tu(e.value),l=tu(e0(e)),c="fill"===i.action;n&&r.push(`id=${tc(n)}`),a&&o&&r.push(c?`role=${tc(a)} label=${tc(o)} editable=true`:`role=${tc(a)} label=${tc(o)}`),o&&r.push(c?`label=${tc(o)} editable=true`:`label=${tc(o)}`),s&&r.push(c?`value=${tc(s)} editable=true`:`value=${tc(s)}`),l&&l!==o&&l!==s&&r.push(c?`text=${tc(l)} editable=true`:`text=${tc(l)}`),a&&c&&!r.some(e=>e.includes("editable=true"))&&r.push(`role=${tc(a)} editable=true`);let u=eT(r);return 0===u.length&&a&&u.push(c?`role=${tc(a)} editable=true`:`role=${tc(a)}`),0===u.length&&tt(e)&&u.push("visible=true"),u}function ta(e){let t=e.trim();if(!t)throw new p("INVALID_ARGS","Empty selector term");let i=t.indexOf("=");if(-1===i){let i=t.toLowerCase();if(!e4.has(i))throw new p("INVALID_ARGS",`Invalid selector term "${e}", expected key=value`);return{key:i,value:!0}}let r=t.slice(0,i).trim().toLowerCase(),a=t.slice(i+1).trim();if(!e8.has(r))throw new p("INVALID_ARGS",`Unknown selector key: ${r}`);if(!a)throw new p("INVALID_ARGS",`Missing selector value for key: ${r}`);if(e4.has(r)){let e,t="true"===(e=to(a).toLowerCase())||"false"!==e&&null;if(null===t)throw new p("INVALID_ARGS",`Invalid boolean value for ${r}: ${a}`);return{key:r,value:t}}return{key:r,value:to(a)}}function tn(e,t,i){return t.terms.every(t=>(function(e,t,i){switch(t.key){case"id":return ts(e.identifier,String(t.value));case"role":var r,a;return r=e.type,a=String(t.value),function(e){return eZ(e)}(r??"")===function(e){return eZ(e)}(a);case"label":return ts(e.label,String(t.value));case"value":return ts(e.value,String(t.value));case"text":{let i=tl(String(t.value));return tl(e0(e))===i}case"visible":return tt(e)===!!t.value;case"hidden":return!tt(e)==!!t.value;case"editable":return ti(e,i)===!!t.value;case"selected":return!0===e.selected==!!t.value;case"enabled":return!1!==e.enabled==!!t.value;case"hittable":return!0===e.hittable==!!t.value;default:return!1}})(e,t,i))}function to(e){let t=e.trim();return t.startsWith('"')&&t.endsWith('"')||t.startsWith("'")&&t.endsWith("'")?t.slice(1,-1).replace(/\\(["'])/g,"$1"):t}function ts(e,t){return tl(e??"")===tl(t)}function tl(e){return e.trim().toLowerCase().replace(/\s+/g," ")}function tc(e){return JSON.stringify(e)}function tu(e){if(!e)return null;let t=e.trim();return t||null}async function td(e){let{req:t,sessionName:i,logPath:r,sessionStore:a,invoke:n,dispatch:o}=e,s=o??eC,c=t.command;if("session_list"===c)return{ok:!0,data:{sessions:a.toArray().map(e=>({name:e.name,platform:e.device.platform,device:e.device.name,id:e.device.id,createdAt:e.createdAt}))}};if("devices"===c)try{let e=[];if(t.flags?.platform==="android"){let{listAndroidDevices:t}=await Promise.resolve().then(()=>({listAndroidDevices:v}));e.push(...await t())}else if(t.flags?.platform==="ios"){let{listIosDevices:t}=await Promise.resolve().then(()=>({listIosDevices:en}));e.push(...await t())}else{let{listAndroidDevices:t}=await Promise.resolve().then(()=>({listAndroidDevices:v})),{listIosDevices:i}=await Promise.resolve().then(()=>({listIosDevices:en}));try{e.push(...await t())}catch{}try{e.push(...await i())}catch{}}return{ok:!0,data:{devices:e}}}catch(t){let e=l(t);return{ok:!1,error:{code:e.code,message:e.message,details:e.details}}}if("apps"===c){let e=a.get(i),r=t.flags??{};if(!e&&!r.platform&&!r.device&&!r.udid&&!r.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=e?.device??await eR(r);if(await eU(n),!e$("apps",n))return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"apps is not supported on this device"}};if("ios"===n.platform){let{listSimulatorApps:e}=await Promise.resolve().then(()=>({listSimulatorApps:ep})),i=await e(n);return t.flags?.appsMetadata?{ok:!0,data:{apps:i}}:{ok:!0,data:{apps:i.map(e=>e.name&&e.name!==e.bundleId?`${e.name} (${e.bundleId})`:e.bundleId)}}}let{listAndroidApps:o,listAndroidAppsMetadata:s}=await Promise.resolve().then(()=>({listAndroidApps:S,listAndroidAppsMetadata:D}));return t.flags?.appsMetadata?{ok:!0,data:{apps:await s(n,t.flags?.appsFilter)}}:{ok:!0,data:{apps:await o(n,t.flags?.appsFilter)}}}if("appstate"===c){let e=a.get(i),n=t.flags??{},o=e?.device??await eR(n);if(await eU(o),"ios"===o.platform){if(e?.appBundleId)return{ok:!0,data:{platform:"ios",appBundleId:e.appBundleId,appName:e.appName??e.appBundleId,source:"session"}};let i=await e1(o,r,e?.trace?.outPath,t.flags);return{ok:!0,data:{platform:"ios",appName:i.appName,appBundleId:i.appBundleId,source:i.source}}}let{getAndroidAppState:s}=await Promise.resolve().then(()=>({getAndroidAppState:x})),l=await s(o);return{ok:!0,data:{platform:"android",package:l.package,activity:l.activity}}}if("open"===c){let e;if(a.has(i)){let e,n=a.get(i),o=t.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:t}=await Promise.resolve().then(()=>({resolveIosApp:es}));e=await t(n.device,o)}catch{e=void 0}await s(n.device,"open",t.positionals??[],t.flags?.out,{...eG(r,t.flags,e)});let l={...n,appBundleId:e,appName:o,recordSession:n.recordSession||t.flags?.saveScript===!0,snapshot:void 0};return a.recordAction(l,{command:c,positionals:t.positionals??[],flags:t.flags??{},result:{session:i,appName:o,appBundleId:e}}),a.set(i,l),{ok:!0,data:{session:i,appName:o,appBundleId:e}}}let n=await eR(t.flags??{});await eU(n);let o=a.toArray().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 l=t.positionals?.[0];if("ios"===n.platform)try{let{resolveIosApp:i}=await Promise.resolve().then(()=>({resolveIosApp:es}));e=await i(n,t.positionals?.[0]??"")}catch{e=void 0}await s(n,"open",t.positionals??[],t.flags?.out,{...eG(r,t.flags,e)});let u={name:i,device:n,createdAt:Date.now(),appBundleId:e,appName:l,recordSession:t.flags?.saveScript===!0,actions:[]};return a.recordAction(u,{command:c,positionals:t.positionals??[],flags:t.flags??{},result:{session:i}}),a.set(i,u),{ok:!0,data:{session:i}}}if("replay"===c){let e=t.positionals?.[0];if(!e)return{ok:!1,error:{code:"INVALID_ARGS",message:"replay requires a path"}};try{let o=eB.expandHome(e),l=d.readFileSync(o,"utf8"),c=l.trimStart()[0];if("{"===c||"["===c)return{ok:!1,error:{code:"INVALID_ARGS",message:"replay accepts .ad script files. JSON replay payloads are no longer supported."}};let u=function(e){let t=[];for(let i of e.split(/\r?\n/)){let e=function(e){let t=e.trim();if(0===t.length||t.startsWith("#"))return null;let i=function(e){let t=[],i=0;for(;i<e.length;){for(;i<e.length&&/\s/.test(e[i]);)i+=1;if(i>=e.length)break;if('"'===e[i]){let r=i+1,a=!1;for(;r<e.length;){let t=e[r];if('"'===t&&!a)break;a="\\"===t&&!a,"\\"!==t&&(a=!1),r+=1}if(r>=e.length)throw new p("INVALID_ARGS",`Invalid replay script line: ${e}`);let n=e.slice(i,r+1);t.push(JSON.parse(n)),i=r+1;continue}let r=i;for(;r<e.length&&!/\s/.test(e[r]);)r+=1;t.push(e.slice(i,r)),i=r}return t}(t);if(0===i.length)return null;let[r,...a]=i;if("context"===r)return null;let n={ts:Date.now(),command:r,positionals:[],flags:{}};if("snapshot"===r){n.positionals=[];for(let e=0;e<a.length;e+=1){let t=a[e];if("-i"===t){n.flags.snapshotInteractiveOnly=!0;continue}if("-c"===t){n.flags.snapshotCompact=!0;continue}if("--raw"===t){n.flags.snapshotRaw=!0;continue}if(("-d"===t||"--depth"===t)&&e+1<a.length){let t=Number(a[e+1]);Number.isFinite(t)&&t>=0&&(n.flags.snapshotDepth=Math.floor(t)),e+=1;continue}if(("-s"===t||"--scope"===t)&&e+1<a.length){n.flags.snapshotScope=a[e+1],e+=1;continue}if("--backend"===t&&e+1<a.length){let t=a[e+1];("ax"===t||"xctest"===t)&&(n.flags.snapshotBackend=t),e+=1}}return n}if("click"===r){if(0===a.length)return n;let e=a[0];return e.startsWith("@")?(n.positionals=[e],a[1]&&(n.result={refLabel:a[1]})):n.positionals=[a.join(" ")],n}if("fill"===r){if(a.length<2)return n.positionals=a,n;let e=a[0];return e.startsWith("@")?(a.length>=3?(n.positionals=[e,a.slice(2).join(" ")],n.result={refLabel:a[1]}):n.positionals=[e,a[1]],n):(n.positionals=[e,a.slice(1).join(" ")],n)}if("get"===r){if(a.length<2)return n.positionals=a,n;let e=a[0],t=a[1];return t.startsWith("@")?(n.positionals=[e,t],a[2]&&(n.result={refLabel:a[2]})):n.positionals=[e,a.slice(1).join(" ")],n}return n.positionals=a,n}(i);e&&t.push(e)}return t}(l),f=t.flags?.replayUpdate===!0,m=0;for(let e=0;e<u.length;e+=1){let o=u[e];if(!o||"replay"===o.command)continue;let l=await n({token:t.token,session:i,command:o.command,positionals:o.positionals??[],flags:o.flags??{}});if(l.ok)continue;if(!f)return l;let c=await tf({action:o,sessionName:i,logPath:r,sessionStore:a,dispatch:s});if(!c||(u[e]=c,!(l=await n({token:t.token,session:i,command:c.command,positionals:c.positionals??[],flags:c.flags??{}})).ok))return l;m+=1}if(f&&m>0){let e=a.get(i);!function(e,t,i){let r=[];if(i){let e=i.device.name.replace(/"/g,'\\"'),t=i.device.kind?` kind=${i.device.kind}`:"";r.push(`context platform=${i.device.platform} device="${e}"${t} theme=unknown`)}for(let e of t)r.push(function(e){let t=[e.command];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",th(e.flags.snapshotScope)),e.flags?.snapshotRaw&&t.push("--raw"),e.flags?.snapshotBackend&&t.push("--backend",e.flags.snapshotBackend),t.join(" ");for(let i of e.positionals??[])t.push(th(i));return t.join(" ")}(e));let a=`${r.join("\n")}
7
+ `,n=`${e}.tmp-${process.pid}-${Date.now()}`;d.writeFileSync(n,a),d.renameSync(n,e)}(o,u,e)}return{ok:!0,data:{replayed:u.length,healed:m,session:i}}}catch(t){let e=l(t);return{ok:!1,error:{code:e.code,message:e.message}}}}if("close"===c){let e=a.get(i);return e?(t.positionals&&t.positionals.length>0&&await s(e.device,"close",t.positionals??[],t.flags?.out,{...eG(r,t.flags,e.appBundleId,e.trace?.outPath)}),"ios"===e.device.platform&&"simulator"===e.device.kind&&await ey(e.device.id),a.recordAction(e,{command:c,positionals:t.positionals??[],flags:t.flags??{},result:{session:i}}),t.flags?.saveScript&&(e.recordSession=!0),a.writeSessionLog(e),a.delete(i),{ok:!0,data:{session:i}}):{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session"}}}return null}async function tf(e){let{action:t,sessionName:i,logPath:r,sessionStore:a,dispatch:n}=e;if(!["click","fill","get","is","wait"].includes(t.command))return null;let o=a.get(i);if(!o)return null;let s="click"===t.command||"fill"===t.command,l=await tp(o,t,r,s,n,a);for(let e of function(e){let t=[],i=Array.isArray(e.result?.selectorChain)&&e.result?.selectorChain.every(e=>"string"==typeof e)?e.result.selectorChain:[];if(t.push(...i),"click"===e.command){let i=e.positionals?.[0]??"";i&&!i.startsWith("@")&&t.push(e.positionals.join(" "))}if("fill"===e.command){let i=e.positionals?.[0]??"";i&&!i.startsWith("@")&&Number.isNaN(Number(i))&&t.push(i)}if("get"===e.command){let i=e.positionals?.[1]??"";i&&!i.startsWith("@")&&t.push(e.positionals.slice(1).join(" "))}if("is"===e.command){let i=te(e.positionals.slice(1));i&&t.push(i.selectorExpression)}if("wait"===e.command){let{selectorExpression:i}=tm(e.positionals??[]);i&&t.push(i)}let r="string"==typeof e.result?.refLabel?e.result.refLabel.trim():"";if(r.length>0){let i=JSON.stringify(r);"fill"===e.command?(t.push(`id=${i} editable=true`),t.push(`label=${i} editable=true`),t.push(`text=${i} editable=true`),t.push(`value=${i} editable=true`)):(t.push(`id=${i}`),t.push(`label=${i}`),t.push(`text=${i}`),t.push(`value=${i}`))}return eT(t).filter(e=>e.trim().length>0)}(t)){let i=e6(e),r=e5(l.nodes,i,{platform:o.device.platform,requireRect:s,requireUnique:!0});if(!r)continue;let a=tr(r.node,o.device.platform,{action:"click"===t.command?"click":"fill"===t.command?"fill":"get"}).join(" || ");if("click"===t.command)return{...t,positionals:[a]};if("fill"===t.command){let e=eF(t);if(!e)continue;return{...t,positionals:[a,e]}}if("get"===t.command){let e=t.positionals?.[0];if("text"!==e&&"attrs"!==e)continue;return{...t,positionals:[e,a]}}if("is"===t.command){let e=t.positionals?.[0];if(!e)continue;let i=te(t.positionals.slice(1)),r=i?.rest.join(" ").trim()??"",n=[e,a];return"text"===e&&r.length>0&&n.push(r),{...t,positionals:n}}if("wait"===t.command){let{selectorTimeout:e}=tm(t.positionals??[]),i=[a];return e&&i.push(e),{...t,positionals:i}}}return null}async function tp(e,t,i,r,a,n){let o=await a(e.device,"snapshot",[],t.flags?.out,{...eG(i,{...t.flags??{},snapshotInteractiveOnly:r,snapshotCompact:r},e.appBundleId,e.trace?.outPath)}),s=o?.nodes??[],l={nodes:eq(t.flags?.snapshotRaw?s:eK(s)),truncated:o?.truncated,createdAt:Date.now(),backend:o?.backend};return e.snapshot=l,n.set(e.name,e),l}function tm(e){if(0===e.length)return{selectorExpression:null,selectorTimeout:null};let t=e[e.length-1],i=/^\d+$/.test(t??""),r=te(i?e.slice(0,-1):e.slice());return!r||r.rest.length>0?{selectorExpression:null,selectorTimeout:null}:{selectorExpression:r.selectorExpression,selectorTimeout:i?t:null}}function th(e){let t=e.trim();return t.startsWith("@")||/^-?\d+(\.\d+)?$/.test(t)?t:JSON.stringify(t)}function tw(e){if(!e)return null;let t=Number(e);return Number.isFinite(t)?t:null}async function tg(e){let{req:t,sessionName:i,logPath:r,sessionStore:a}=e,n=t.command;if("snapshot"===n){let{session:e,device:n}=await tv(a,i,t.flags);if(!e$("snapshot",n))return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"snapshot is only supported on iOS simulators in v1"}};let o=e?.appBundleId,s=t.flags?.snapshotScope;if(s&&s.trim().startsWith("@")){if(!e?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"Ref scope requires an existing snapshot in session."}};let t=eW(s.trim());if(!t)return{ok:!1,error:{code:"INVALID_ARGS",message:`Invalid ref scope: ${s}`}};let i=eJ(e.snapshot.nodes,t),r=i?eX(i,e.snapshot.nodes):void 0;if(!r)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${s} not found or has no label`}};s=r}let l=await eC(n,"snapshot",[],t.flags?.out,{...eG(r,{...t.flags,snapshotScope:s},o,e?.trace?.outPath)}),c=l?.nodes??[],u=eq(t.flags?.snapshotRaw?c:eK(c)),d={nodes:u,truncated:l?.truncated,createdAt:Date.now(),backend:l?.backend},f=e?{...e,snapshot:d}:{name:i,device:n,createdAt:Date.now(),appBundleId:o,snapshot:d,actions:[]};return ty(a,f,t,{nodes:u.length,truncated:l?.truncated??!1}),a.set(i,f),{ok:!0,data:{nodes:u,truncated:l?.truncated??!1,appName:f.appBundleId?f.appName??f.appBundleId:void 0,appBundleId:f.appBundleId}}}if("wait"===n){let e,n,{session:o,device:s}=await tv(a,i,t.flags),l=function(e){if(0===e.length)return null;let t=tw(e[0]);if(null!==t)return{kind:"sleep",durationMs:t};if("text"===e[0]){let t=tw(e[e.length-1]);return{kind:"text",text:(null!==t?e.slice(1,-1).join(" "):e.slice(1).join(" ")).trim(),timeoutMs:t}}if(e[0].startsWith("@")){let t=tw(e[e.length-1]);return{kind:"ref",rawRef:e[0],timeoutMs:t}}let i=tw(e[e.length-1]),r=te(null!==i?e.slice(0,-1):e.slice());if(r&&0===r.rest.length){let e=function(e){try{return e6(e)}catch{return null}}(r.selectorExpression);if(e)return{kind:"selector",selector:e,selectorExpression:r.selectorExpression,timeoutMs:i}}return{kind:"text",text:(null!==i?e.slice(0,-1).join(" "):e.join(" ")).trim(),timeoutMs:i}}(t.positionals??[]);if(!l)return{ok:!1,error:{code:"INVALID_ARGS",message:"wait requires a duration or text"}};if("sleep"===l.kind)return await new Promise(e=>setTimeout(e,l.durationMs)),ty(a,o,t,{waitedMs:l.durationMs}),{ok:!0,data:{waitedMs:l.durationMs}};if(!e$("wait",s))return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"wait is not supported on this device"}};if("selector"===l.kind){let e=l.timeoutMs??1e4,n=Date.now();for(;Date.now()-n<e;){let e=await eC(s,"snapshot",[],t.flags?.out,{...eG(r,{...t.flags,snapshotInteractiveOnly:!1,snapshotCompact:!1},o?.appBundleId,o?.trace?.outPath)}),c=e?.nodes??[],u=eq(t.flags?.snapshotRaw?c:eK(c));o&&(o.snapshot={nodes:u,truncated:e?.truncated,createdAt:Date.now(),backend:e?.backend},a.set(i,o));let d=e7(u,l.selector,{platform:s.platform});if(d)return ty(a,o,t,{selector:d.selector.raw,waitedMs:Date.now()-n}),{ok:!0,data:{selector:d.selector.raw,waitedMs:Date.now()-n}};await new Promise(e=>setTimeout(e,300))}return{ok:!1,error:{code:"COMMAND_FAILED",message:`wait timed out for selector: ${l.selectorExpression}`}}}if("ref"===l.kind){if(!o?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"Ref wait requires an existing snapshot in session."}};let t=eW(l.rawRef);if(!t)return{ok:!1,error:{code:"INVALID_ARGS",message:`Invalid ref: ${l.rawRef}`}};let i=eJ(o.snapshot.nodes,t),r=i?eX(i,o.snapshot.nodes):void 0;if(!r)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${l.rawRef} not found or has no label`}};e=r,n=l.timeoutMs}else e=l.text,n=l.timeoutMs;if(!e)return{ok:!1,error:{code:"INVALID_ARGS",message:"wait requires text"}};let c=n??1e4,u=Date.now();for(;Date.now()-u<c;){if("ios"===s.platform&&"simulator"===s.kind){let i=await eg(s,{command:"findText",text:e,appBundleId:o?.appBundleId},{verbose:t.flags?.verbose,logPath:r,traceLogPath:o?.trace?.outPath});if(i?.found)return ty(a,o,t,{text:e,waitedMs:Date.now()-u}),{ok:!0,data:{text:e,waitedMs:Date.now()-u}}}else if("android"===s.platform&&ez(eq((await q(s,{scope:e})).nodes??[]),e))return ty(a,o,t,{text:e,waitedMs:Date.now()-u}),{ok:!0,data:{text:e,waitedMs:Date.now()-u}};await new Promise(e=>setTimeout(e,300))}return{ok:!1,error:{code:"COMMAND_FAILED",message:`wait timed out for text: ${e}`}}}if("alert"===n){let{session:e,device:n}=await tv(a,i,t.flags),o=(t.positionals?.[0]??"get").toLowerCase();if(!e$("alert",n))return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"alert is only supported on iOS simulators in v1"}};if("wait"===o){let i=tw(t.positionals?.[1])??1e4,o=Date.now();for(;Date.now()-o<i;){try{let i=await eg(n,{command:"alert",action:"get",appBundleId:e?.appBundleId},{verbose:t.flags?.verbose,logPath:r,traceLogPath:e?.trace?.outPath});return ty(a,e,t,i),{ok:!0,data:i}}catch{}await new Promise(e=>setTimeout(e,300))}return{ok:!1,error:{code:"COMMAND_FAILED",message:"alert wait timed out"}}}let s=await eg(n,{command:"alert",action:"accept"===o||"dismiss"===o?o:"get",appBundleId:e?.appBundleId},{verbose:t.flags?.verbose,logPath:r,traceLogPath:e?.trace?.outPath});return ty(a,e,t,s),{ok:!0,data:s}}if("settings"===n){let e=t.positionals?.[0],n=t.positionals?.[1];if(!e||!n)return{ok:!1,error:{code:"INVALID_ARGS",message:"settings requires <wifi|airplane|location> <on|off>"}};let{session:o,device:s}=await tv(a,i,t.flags),l=o?.appBundleId,c=await eC(s,"settings",[e,n,l??""],t.flags?.out,{...eG(r,t.flags,l,o?.trace?.outPath)});return ty(a,o,t,c??{setting:e,state:n}),{ok:!0,data:c??{setting:e,state:n}}}return null}async function tv(e,t,i){let r=e.get(t),a=r?.device??await eR(i??{});return r||await eU(a),{session:r,device:a}}function ty(e,t,i,r){t&&e.recordAction(t,{command:i.command,positionals:i.positionals??[],flags:i.flags??{},result:r})}function tA(e,t,i,r={}){let a=tb(i);if(!a)return null;let n=null;for(let i of e){if(r.requireRect&&!i.rect)continue;let e=function(e,t,i){switch(t){case"role":return function(e,t){let i=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 i?i===t?2:+!!i.includes(t):0}(e.type,i);case"label":return tI(e.label,i);case"value":return tI(e.value,i);case"id":return tI(e.identifier,i);default:return Math.max(tI(e.label,i),tI(e.value,i),tI(e.identifier,i))}}(i,t,a);if(!(e<=0)&&(!n||e>n.score)&&(n={node:i,score:e},e>=2))break}return n?.node??null}function tI(e,t){let i=tb(e??"");return i?i===t?2:+!!i.includes(t):0}function tb(e){return e.trim().toLowerCase().replace(/\s+/g," ")}async function tN(e){let{req:t,sessionName:i,logPath:r,sessionStore:a,invoke:n}=e,o=t.command;if("find"!==o)return null;let s=t.positionals??[];if(0===s.length)return{ok:!1,error:{code:"INVALID_ARGS",message:"find requires a locator or text"}};let{locator:l,query:c,action:u,value:d,timeoutMs:f}=function(e){let t="any",i=0;["text","label","value","role","id"].includes(e[0])&&(t=e[0],i=1);let r=e[i]??"",a=e.slice(i+1);if(0===a.length)return{locator:t,query:r,action:"click"};let n=a[0].toLowerCase();if("get"===n){let e=a[1]?.toLowerCase();if("text"===e)return{locator:t,query:r,action:"get_text"};if("attrs"===e)return{locator:t,query:r,action:"get_attrs"};throw new p("INVALID_ARGS","find get only supports text or attrs")}if("wait"===n)return{locator:t,query:r,action:"wait",timeoutMs:tw(a[1])??void 0};if("exists"===n)return{locator:t,query:r,action:"exists"};if("click"===n)return{locator:t,query:r,action:"click"};if("focus"===n)return{locator:t,query:r,action:"focus"};if("fill"===n)return{locator:t,query:r,action:"fill",value:a.slice(1).join(" ")};if("type"===n)return{locator:t,query:r,action:"type",value:a.slice(1).join(" ")};throw new p("INVALID_ARGS",`Unsupported find action: ${a[0]}`)}(s);if(!c)return{ok:!1,error:{code:"INVALID_ARGS",message:"find requires a value"}};let m=a.get(i);if(!m&&"exists"!==u&&"wait"!==u&&"get_text"!==u&&"get_attrs"!==u)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session. Run open first."}};let h=m?.device??await eR(t.flags??{});m||await eU(h);let w=m?.appBundleId,g="role"!==l?c:void 0,v="click"===u||"focus"===u||"fill"===u||"type"===u,y=0,A=null,I=async()=>{let e=Date.now();if(A&&e-y<750)return{nodes:A};let n=await eC(h,"snapshot",[],t.flags?.out,{...eG(r,{...t.flags,snapshotScope:g,snapshotInteractiveOnly:v,snapshotCompact:v},w,m?.trace?.outPath)}),o=n?.nodes??[],s=eq(t.flags?.snapshotRaw?o:eK(o));return y=e,A=s,m&&(m.snapshot={nodes:s,truncated:n?.truncated,createdAt:Date.now(),backend:n?.backend},a.set(i,m)),{nodes:s,truncated:n?.truncated,backend:n?.backend}};if("wait"===u){let e=f??1e4,i=Date.now();for(;Date.now()-i<e;){let{nodes:e}=await I();if(tA(e,l,c,{requireRect:!1}))return m&&a.recordAction(m,{command:o,positionals:t.positionals??[],flags:t.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:b}=await I(),N=tA(b,l,c,{requireRect:v});if(!N)return{ok:!1,error:{code:"COMMAND_FAILED",message:"find did not match any element"}};let k="click"===u||"focus"===u||"fill"===u||"type"===u?function(e,t){if(t.hittable)return t;let i=t,r=new Set;for(;void 0!==i.parentIndex&&!r.has(i.ref);){r.add(i.ref);let t=e[i.parentIndex];if(!t)break;if(t.hittable)return t;i=t}return null}(b,N)??N:N,S=`@${k.ref}`,D={...t.flags??{},noRecord:!0};if("exists"===u)return m&&a.recordAction(m,{command:o,positionals:t.positionals??[],flags:t.flags??{},result:{found:!0}}),{ok:!0,data:{found:!0}};if("get_text"===u){let e=e0(N);return m&&a.recordAction(m,{command:o,positionals:t.positionals??[],flags:t.flags??{},result:{ref:S,action:"get text",text:e}}),{ok:!0,data:{ref:S,text:e,node:N}}}if("get_attrs"===u)return m&&a.recordAction(m,{command:o,positionals:t.positionals??[],flags:t.flags??{},result:{ref:S,action:"get attrs"}}),{ok:!0,data:{ref:S,node:N}};if("click"===u){let e=await n({token:t.token,session:i,command:"click",positionals:[S],flags:D});return e.ok&&m&&a.recordAction(m,{command:o,positionals:t.positionals??[],flags:t.flags??{},result:{ref:S,action:"click"}}),e}if("fill"===u){if(!d)return{ok:!1,error:{code:"INVALID_ARGS",message:"find fill requires text"}};let e=await n({token:t.token,session:i,command:"fill",positionals:[S,d],flags:D});return e.ok&&m&&a.recordAction(m,{command:o,positionals:t.positionals??[],flags:t.flags??{},result:{ref:S,action:"fill"}}),e}if("focus"===u){let e=N.rect?eH(N.rect):null;if(!e)return{ok:!1,error:{code:"COMMAND_FAILED",message:"matched element has no bounds"}};let i=await eC(h,"focus",[String(e.x),String(e.y)],t.flags?.out,{...eG(r,t.flags,m?.appBundleId,m?.trace?.outPath)});return m&&a.recordAction(m,{command:o,positionals:t.positionals??[],flags:t.flags??{},result:{ref:S,action:"focus"}}),{ok:!0,data:i??{ref:S}}}if("type"===u){if(!d)return{ok:!1,error:{code:"INVALID_ARGS",message:"find type requires text"}};let e=N.rect?eH(N.rect):null;if(!e)return{ok:!1,error:{code:"COMMAND_FAILED",message:"matched element has no bounds"}};await eC(h,"focus",[String(e.x),String(e.y)],t.flags?.out,{...eG(r,t.flags,m?.appBundleId,m?.trace?.outPath)});let i=await eC(h,"type",[d],t.flags?.out,{...eG(r,t.flags,m?.appBundleId,m?.trace?.outPath)});return m&&a.recordAction(m,{command:o,positionals:t.positionals??[],flags:t.flags??{},result:{ref:S,action:"type"}}),{ok:!0,data:i??{ref:S}}}return null}async function tk(e){let{req:t,sessionName:i,sessionStore:r}=e,a=t.command;if("record"===a){let e=(t.positionals?.[0]??"").toLowerCase();if(!["start","stop"].includes(e))return{ok:!1,error:{code:"INVALID_ARGS",message:"record requires start|stop"}};let o=r.get(i),s=o?.device??await eR(t.flags??{});o||await eU(s);let l=o??{name:i,device:s,createdAt:Date.now(),actions:[]};if("start"===e){if(l.recording)return{ok:!1,error:{code:"INVALID_ARGS",message:"recording already in progress"}};let e=t.positionals?.[1]??`./recording-${Date.now()}.mp4`,o=n.resolve(e),c=n.dirname(o);if(d.existsSync(c)||d.mkdirSync(c,{recursive:!0}),!e$("record",s))return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"record is only supported on iOS simulators in v1"}};if("ios"===s.platform){let{child:e,wait:t}=u("xcrun",["simctl","io",s.id,"recordVideo",o],{allowFailure:!0});l.recording={platform:"ios",outPath:o,child:e,wait:t}}else{let e=`/sdcard/agent-device-recording-${Date.now()}.mp4`,{child:t,wait:i}=u("adb",["-s",s.id,"shell","screenrecord",e],{allowFailure:!0});l.recording={platform:"android",outPath:o,remotePath:e,child:t,wait:i}}return r.set(i,l),r.recordAction(l,{command:a,positionals:t.positionals??[],flags:t.flags??{},result:{action:"start"}}),{ok:!0,data:{recording:"started",outPath:e}}}if(!l.recording)return{ok:!1,error:{code:"INVALID_ARGS",message:"no active recording"}};let c=l.recording;c.child.kill("SIGINT");try{await c.wait}catch{}if("android"===c.platform&&c.remotePath)try{await m("adb",["-s",s.id,"pull",c.remotePath,c.outPath],{allowFailure:!0}),await m("adb",["-s",s.id,"shell","rm","-f",c.remotePath],{allowFailure:!0})}catch{}return l.recording=void 0,r.recordAction(l,{command:a,positionals:t.positionals??[],flags:t.flags??{},result:{action:"stop",outPath:c.outPath}}),{ok:!0,data:{recording:"stopped",outPath:c.outPath}}}if("trace"===a){let e=(t.positionals?.[0]??"").toLowerCase();if(!["start","stop"].includes(e))return{ok:!1,error:{code:"INVALID_ARGS",message:"trace requires start|stop"}};let o=r.get(i);if(!o)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session"}};if("start"===e){if(o.trace)return{ok:!1,error:{code:"INVALID_ARGS",message:"trace already in progress"}};let e=t.positionals?.[1]??r.defaultTracePath(o),i=eB.expandHome(e);return d.mkdirSync(n.dirname(i),{recursive:!0}),d.appendFileSync(i,""),o.trace={outPath:i,startedAt:Date.now()},r.recordAction(o,{command:a,positionals:t.positionals??[],flags:t.flags??{},result:{action:"start",outPath:i}}),{ok:!0,data:{trace:"started",outPath:i}}}if(!o.trace)return{ok:!1,error:{code:"INVALID_ARGS",message:"no active trace"}};let s=o.trace.outPath;if(t.positionals?.[1]){let e=eB.expandHome(t.positionals[1]);d.mkdirSync(n.dirname(e),{recursive:!0}),d.existsSync(s)?d.renameSync(s,e):d.appendFileSync(e,""),s=e}return o.trace=void 0,r.recordAction(o,{command:a,positionals:t.positionals??[],flags:t.flags??{},result:{action:"stop",outPath:s}}),{ok:!0,data:{trace:"stopped",outPath:s}}}return null}async function tS(e){let{req:t,sessionName:i,sessionStore:r,contextFromFlags:a}=e,n=t.command;if("click"===n){let e=r.get(i);if(!e)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session. Run open first."}};let o=t.positionals?.[0]??"";if(o.startsWith("@")){if(!e.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"No snapshot in session. Run snapshot first."}};let i=eW(o);if(!i)return{ok:!1,error:{code:"INVALID_ARGS",message:"click requires a ref like @e2"}};let s=eJ(e.snapshot.nodes,i);if(!s?.rect&&t.positionals.length>1){let i=t.positionals.slice(1).join(" ").trim();i.length>0&&(s=ez(e.snapshot.nodes,i))}if(!s?.rect)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${o} not found or has no bounds`}};let l=eX(s,e.snapshot.nodes),c=tr(s,e.device.platform,{action:"click"}),{x:u,y:d}=eH(s.rect);return await eC(e.device,"press",[String(u),String(d)],t.flags?.out,{...a(t.flags,e.appBundleId,e.trace?.outPath)}),r.recordAction(e,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:{ref:i,x:u,y:d,refLabel:l,selectorChain:c}}),{ok:!0,data:{ref:i,x:u,y:d}}}let s=(t.positionals??[]).join(" ").trim();if(!s)return{ok:!1,error:{code:"INVALID_ARGS",message:"click requires @ref or selector expression"}};let l=e6(s),c=await tD(e,t.flags,r,a,{interactiveOnly:!0}),u=e5(c.nodes,l,{platform:e.device.platform,requireRect:!0,requireUnique:!0});if(!u||!u.node.rect)return{ok:!1,error:{code:"COMMAND_FAILED",message:e9(l,u?.diagnostics??[],{unique:!0})}};let{x:d,y:f}=eH(u.node.rect);await eC(e.device,"press",[String(d),String(f)],t.flags?.out,{...a(t.flags,e.appBundleId,e.trace?.outPath)});let p=tr(u.node,e.device.platform,{action:"click"}),m=eX(u.node,c.nodes);return r.recordAction(e,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:{x:d,y:f,selector:u.selector.raw,selectorChain:p,refLabel:m}}),{ok:!0,data:{selector:u.selector.raw,x:d,y:f}}}if("fill"===n){let e=r.get(i);if(t.positionals?.[0]?.startsWith("@")){if(!e?.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"No snapshot in session. Run snapshot first."}};let i=eW(t.positionals[0]);if(!i)return{ok:!1,error:{code:"INVALID_ARGS",message:"fill requires a ref like @e2"}};let o=t.positionals.length>=3?t.positionals[1]:"",s=t.positionals.length>=3?t.positionals.slice(2).join(" "):t.positionals.slice(1).join(" ");if(!s)return{ok:!1,error:{code:"INVALID_ARGS",message:"fill requires text after ref"}};let l=eJ(e.snapshot.nodes,i);if(!l?.rect&&o&&(l=ez(e.snapshot.nodes,o)),!l?.rect)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${t.positionals[0]} not found or has no bounds`}};let c=l.type??"",u=c&&!eQ(c,e.device.platform)?`fill target ${t.positionals[0]} resolved to "${c}", attempting fill anyway.`:void 0,d=eX(l,e.snapshot.nodes),f=tr(l,e.device.platform,{action:"fill"}),{x:p,y:m}=eH(l.rect),h={...await eC(e.device,"fill",[String(p),String(m),s],t.flags?.out,{...a(t.flags,e.appBundleId,e.trace?.outPath)})??{ref:i,x:p,y:m}};return u&&(h.warning=u),r.recordAction(e,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:{...h,refLabel:d,selectorChain:f}}),{ok:!0,data:h}}if(!e)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session. Run open first."}};let o=te(t.positionals??[]);if(o){if(0===o.rest.length)return{ok:!1,error:{code:"INVALID_ARGS",message:"fill requires text after selector"}};let i=o.rest.join(" ").trim();if(!i)return{ok:!1,error:{code:"INVALID_ARGS",message:"fill requires text after selector"}};let s=e6(o.selectorExpression),l=await tD(e,t.flags,r,a,{interactiveOnly:!0}),c=e5(l.nodes,s,{platform:e.device.platform,requireRect:!0,requireUnique:!0});if(!c||!c.node.rect)return{ok:!1,error:{code:"COMMAND_FAILED",message:e9(s,c?.diagnostics??[],{unique:!0})}};let u=c.node,d=u.type??"",f=d&&!eQ(d,e.device.platform)?`fill target ${c.selector.raw} resolved to "${d}", attempting fill anyway.`:void 0,{x:p,y:m}=eH(c.node.rect),h=await eC(e.device,"fill",[String(p),String(m),i],t.flags?.out,{...a(t.flags,e.appBundleId,e.trace?.outPath)}),w=tr(u,e.device.platform,{action:"fill"}),g={...h??{x:p,y:m,text:i},selector:c.selector.raw,selectorChain:w,refLabel:eX(u,l.nodes)};return f&&(g.warning=f),r.recordAction(e,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:g}),{ok:!0,data:g}}return{ok:!1,error:{code:"INVALID_ARGS",message:"fill requires x y text, @ref text, or selector text"}}}if("get"===n){let e=t.positionals?.[0];if("text"!==e&&"attrs"!==e)return{ok:!1,error:{code:"INVALID_ARGS",message:"get only supports text or attrs"}};let o=r.get(i);if(!o)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session. Run open first."}};let s=t.positionals?.[1]??"";if(s.startsWith("@")){if(!o.snapshot)return{ok:!1,error:{code:"INVALID_ARGS",message:"No snapshot in session. Run snapshot first."}};let i=eW(s??"");if(!i)return{ok:!1,error:{code:"INVALID_ARGS",message:"get text requires a ref like @e2"}};let a=eJ(o.snapshot.nodes,i);if(!a&&t.positionals.length>2){let e=t.positionals.slice(2).join(" ").trim();e.length>0&&(a=ez(o.snapshot.nodes,e))}if(!a)return{ok:!1,error:{code:"COMMAND_FAILED",message:`Ref ${s} not found`}};let l=tr(a,o.device.platform,{action:"get"});if("attrs"===e)return r.recordAction(o,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:{ref:i,selectorChain:l}}),{ok:!0,data:{ref:i,node:a}};let c=e0(a);return r.recordAction(o,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:{ref:i,text:c,refLabel:c||void 0,selectorChain:l}}),{ok:!0,data:{ref:i,text:c,node:a}}}let l=t.positionals.slice(1).join(" ").trim();if(!l)return{ok:!1,error:{code:"INVALID_ARGS",message:"get requires @ref or selector expression"}};let c=e6(l),u=e5((await tD(o,t.flags,r,a,{interactiveOnly:!1})).nodes,c,{platform:o.device.platform,requireRect:!1,requireUnique:!0});if(!u)return{ok:!1,error:{code:"COMMAND_FAILED",message:e9(c,[],{unique:!0})}};let d=u.node,f=tr(d,o.device.platform,{action:"get"});if("attrs"===e)return r.recordAction(o,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:{selector:u.selector.raw,selectorChain:f}}),{ok:!0,data:{selector:u.selector.raw,node:d}};let p=e0(d);return r.recordAction(o,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:{text:p,refLabel:p||void 0,selector:u.selector.raw,selectorChain:f}}),{ok:!0,data:{selector:u.selector.raw,text:p,node:d}}}if("is"===n){let e=(t.positionals?.[0]??"").toLowerCase();if(!["visible","hidden","exists","editable","selected","text"].includes(e))return{ok:!1,error:{code:"INVALID_ARGS",message:"is requires predicate: visible|hidden|exists|editable|selected|text"}};let o=r.get(i);if(!o)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session. Run open first."}};if(!e$("is",o.device))return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:"is is not supported on this device"}};let s=te(t.positionals.slice(1));if(!s)return{ok:!1,error:{code:"INVALID_ARGS",message:"is requires a selector expression"}};let l=s.rest.join(" ").trim();if("text"===e&&!l)return{ok:!1,error:{code:"INVALID_ARGS",message:"is text requires expected text value"}};if("text"!==e&&s.rest.length>0)return{ok:!1,error:{code:"INVALID_ARGS",message:`is ${e} does not accept trailing values`}};let c=e6(s.selectorExpression),u=await tD(o,t.flags,r,a,{interactiveOnly:!1});if("exists"===e){let i=e7(u.nodes,c,{platform:o.device.platform});return i?(r.recordAction(o,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:{predicate:e,selector:i.selector.raw,selectorChain:c.selectors.map(e=>e.raw),pass:!0,matches:i.matches}}),{ok:!0,data:{predicate:e,pass:!0,selector:i.selector.raw,matches:i.matches}}):{ok:!1,error:{code:"COMMAND_FAILED",message:e9(c,[],{unique:!1})}}}let d=e5(u.nodes,c,{platform:o.device.platform,requireUnique:!0});if(!d)return{ok:!1,error:{code:"COMMAND_FAILED",message:e9(c,[],{unique:!0})}};let f=function(e){let{predicate:t,node:i,expectedText:r,platform:a}=e,n=e0(i),o=!1;switch(t){case"visible":o=tt(i);break;case"hidden":o=!tt(i);break;case"editable":o=ti(i,a);break;case"selected":o=!0===i.selected;break;case"text":o=n===(r??"")}let s="text"===t?`expected="${r??""}" actual="${n}"`:`actual=${JSON.stringify({visible:tt(i),editable:ti(i,a),selected:!0===i.selected})}`;return{pass:o,actualText:n,details:s}}({predicate:e,node:d.node,expectedText:l,platform:o.device.platform});return f.pass?(r.recordAction(o,{command:n,positionals:t.positionals??[],flags:t.flags??{},result:{predicate:e,selector:d.selector.raw,selectorChain:c.selectors.map(e=>e.raw),pass:!0,text:"text"===e?f.actualText:void 0}}),{ok:!0,data:{predicate:e,pass:!0,selector:d.selector.raw}}):{ok:!1,error:{code:"COMMAND_FAILED",message:`is ${e} failed for selector ${d.selector.raw}: ${f.details}`}}}return null}async function tD(e,t,i,r,a){let n=await eC(e.device,"snapshot",[],t?.out,{...r({...t??{},snapshotInteractiveOnly:a.interactiveOnly,snapshotCompact:a.interactiveOnly},e.appBundleId,e.trace?.outPath)}),o=n?.nodes??[];return e.snapshot={nodes:eq(t?.snapshotRaw?o:eK(o)),truncated:n?.truncated,createdAt:Date.now(),backend:n?.backend},i.set(e.name,e),e.snapshot}let tx=n.join(f.homedir(),".agent-device"),tL=n.join(tx,"daemon.json"),t_=n.join(tx,"daemon.log"),tO=new eB(n.join(tx,"sessions")),tP=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"}}(),tM=i.randomBytes(24).toString("hex"),tR=new Set(["session_list","devices"]);function tC(e,t,i){return eG(t_,e,t,i)}async function tE(e){if(e.token!==tM)return{ok:!1,error:{code:"UNAUTHORIZED",message:"Invalid token"}};let t=e.command,i=function(e,t){var i;let r,a=e.session||"default";if(i=e,"string"==typeof(r=i.flags?.session)&&r.trim().length>0||"default"!==a||t.has(a))return a;let n=t.toArray();return 1===n.length?n[0].name:a}(e,tO),r=tO.get(i);r&&!tR.has(t)&&function(e,t){if(!t)return;let i=[],r=e.device;if(t.platform&&t.platform!==r.platform&&i.push(`--platform=${t.platform}`),t.udid&&("ios"!==r.platform||t.udid!==r.id)&&i.push(`--udid=${t.udid}`),t.serial&&("android"!==r.platform||t.serial!==r.id)&&i.push(`--serial=${t.serial}`),0!==i.length){var a;let t,r,n;throw new p("INVALID_ARGS",`Session "${e.name}" is bound to ${(t=(a=e).device.platform,r=a.device.name.trim(),n=a.device.id,`${t} device "${r}" (${n})`)} and cannot be used with ${i.join(", ")}. Use a different --session name or close this session first.`)}}(r,e.flags);let a=await td({req:e,sessionName:i,logPath:t_,sessionStore:tO,invoke:tE});if(a)return a;let n=await tg({req:e,sessionName:i,logPath:t_,sessionStore:tO});if(n)return n;let o=await tk({req:e,sessionName:i,sessionStore:tO});if(o)return o;let s=await tN({req:e,sessionName:i,logPath:t_,sessionStore:tO,invoke:tE});if(s)return s;let l=await tS({req:e,sessionName:i,sessionStore:tO,contextFromFlags:tC});if(l)return l;let c=tO.get(i);if(!c)return{ok:!1,error:{code:"SESSION_NOT_FOUND",message:"No active session. Run open first."}};if(!e$(t,c.device))return{ok:!1,error:{code:"UNSUPPORTED_OPERATION",message:`${t} is not supported on this device`}};let u=await eC(c.device,t,e.positionals??[],e.flags?.out,{...tC(e.flags,c.appBundleId,c.trace?.outPath)});return tO.recordAction(c,{command:t,positionals:e.positionals??[],flags:e.flags??{},result:u??{}}),{ok:!0,data:u??{}}}(e=h.createServer(e=>{let t="";e.setEncoding("utf8"),e.on("data",async i=>{let r=(t+=i).indexOf("\n");for(;-1!==r;){let i,a=t.slice(0,r).trim();if(t=t.slice(r+1),0===a.length){r=t.indexOf("\n");continue}try{let e=JSON.parse(a);i=await tE(e)}catch(t){let e=l(t);i={ok:!1,error:{code:e.code,message:e.message,details:e.details}}}e.write(`${JSON.stringify(i)}
8
+ `),r=t.indexOf("\n")}})})).listen(0,"127.0.0.1",()=>{let t=e.address();if("object"==typeof t&&t?.port){var i;i=t.port,d.existsSync(tx)||d.mkdirSync(tx,{recursive:!0}),d.writeFileSync(t_,""),d.writeFileSync(tL,JSON.stringify({port:i,token:tM,pid:process.pid,version:tP},null,2),{mode:384}),process.stdout.write(`AGENT_DEVICE_DAEMON_PORT=${t.port}
9
+ `)}}),t=async()=>{for(let e of tO.toArray())"ios"===e.device.platform&&"simulator"===e.device.kind&&await ey(e.device.id),tO.writeSessionLog(e);e.close(()=>{d.existsSync(tL)&&d.unlinkSync(tL),process.exit(0)})},process.on("SIGINT",()=>{t()}),process.on("SIGTERM",()=>{t()}),process.on("SIGHUP",()=>{t()}),process.on("uncaughtException",e=>{let i=e instanceof p?e:l(e);process.stderr.write(`Daemon error: ${i.message}
9
10
  `),t()});
@@ -336,6 +336,7 @@
336
336
  INFOPLIST_KEY_UILaunchScreen_Generation = YES;
337
337
  INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
338
338
  INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
339
+ IPHONEOS_DEPLOYMENT_TARGET = 15.6;
339
340
  LD_RUNPATH_SEARCH_PATHS = (
340
341
  "$(inherited)",
341
342
  "@executable_path/Frameworks",
@@ -368,6 +369,7 @@
368
369
  INFOPLIST_KEY_UILaunchScreen_Generation = YES;
369
370
  INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
370
371
  INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
372
+ IPHONEOS_DEPLOYMENT_TARGET = 15.6;
371
373
  LD_RUNPATH_SEARCH_PATHS = (
372
374
  "$(inherited)",
373
375
  "@executable_path/Frameworks",
@@ -232,6 +232,13 @@ final class RunnerTests: XCTestCase {
232
232
  return Response(ok: true, data: DataPayload(message: "tapped"))
233
233
  }
234
234
  return Response(ok: false, error: ErrorPayload(message: "tap requires text or x/y"))
235
+ case .longPress:
236
+ guard let x = command.x, let y = command.y else {
237
+ return Response(ok: false, error: ErrorPayload(message: "longPress requires x and y"))
238
+ }
239
+ let duration = (command.durationMs ?? 800) / 1000.0
240
+ longPressAt(app: activeApp, x: x, y: y, duration: duration)
241
+ return Response(ok: true, data: DataPayload(message: "long pressed"))
235
242
  case .type:
236
243
  guard let text = command.text else {
237
244
  return Response(ok: false, error: ErrorPayload(message: "type requires text"))
@@ -411,6 +418,12 @@ final class RunnerTests: XCTestCase {
411
418
  coordinate.tap()
412
419
  }
413
420
 
421
+ private func longPressAt(app: XCUIApplication, x: Double, y: Double, duration: TimeInterval) {
422
+ let origin = app.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 0))
423
+ let coordinate = origin.withOffset(CGVector(dx: x, dy: y))
424
+ coordinate.press(forDuration: duration)
425
+ }
426
+
414
427
  private func swipe(app: XCUIApplication, direction: SwipeDirection) {
415
428
  let target = app.windows.firstMatch.exists ? app.windows.firstMatch : app
416
429
  let start = target.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.2))
@@ -792,6 +805,7 @@ private func resolveRunnerPort() -> UInt16 {
792
805
 
793
806
  enum CommandType: String, Codable {
794
807
  case tap
808
+ case longPress
795
809
  case type
796
810
  case swipe
797
811
  case findText
@@ -820,6 +834,7 @@ struct Command: Codable {
820
834
  let action: String?
821
835
  let x: Double?
822
836
  let y: Double?
837
+ let durationMs: Double?
823
838
  let direction: SwipeDirection?
824
839
  let scale: Double?
825
840
  let interactiveOnly: Bool?
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-device",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Unified control plane for physical and virtual devices via an agent-driven CLI.",
5
5
  "license": "MIT",
6
6
  "author": "Callstack",
@@ -26,7 +26,8 @@
26
26
  "prepack": "pnpm build:node && pnpm build:axsnapshot",
27
27
  "typecheck": "tsc -p tsconfig.json",
28
28
  "test": "node --test",
29
- "test:smoke": "node --test test/smoke/*.test.ts",
29
+ "test:unit": "node --test src/core/__tests__/*.test.ts src/daemon/__tests__/*.test.ts src/daemon/handlers/__tests__/*.test.ts",
30
+ "test:smoke": "node --test test/integration/smoke-*.test.ts",
30
31
  "test:integration": "node --test test/integration/*.test.ts"
31
32
  },
32
33
  "files": [
@@ -5,6 +5,8 @@ description: Automates mobile and simulator interactions for iOS and Android dev
5
5
 
6
6
  # Mobile Automation with agent-device
7
7
 
8
+ For agent-driven exploration: use refs. For deterministic replay scripts: use selectors.
9
+
8
10
  ## Quick start
9
11
 
10
12
  ```bash
@@ -26,9 +28,9 @@ npx -y agent-device
26
28
  ## Core workflow
27
29
 
28
30
  1. Open app or just boot device: `open [app]`
29
- 2. Snapshot: `snapshot` to get full XCTest accessibility tree snapshot
31
+ 2. Snapshot: `snapshot` to get refs from accessibility tree
30
32
  3. Interact using refs (`click @ref`, `fill @ref "text"`)
31
- 4. Re-snapshot after navigation or UI changes
33
+ 4. Re-snapshot after navigation/UI changes
32
34
  5. Close session when done
33
35
 
34
36
  ## Commands
@@ -102,13 +104,15 @@ agent-device type "text" # Type into focused field without clearin
102
104
  agent-device press 300 500 # Tap by coordinates
103
105
  agent-device long-press 300 500 800 # Long press (where supported)
104
106
  agent-device scroll down 0.5
105
- agent-device pinch 2.0 # Zoom in 2x (iOS simulator)
106
- agent-device pinch 0.5 200 400 # Zoom out at coordinates (iOS simulator)
107
+ agent-device pinch 2.0 # Zoom in 2x (iOS simulator + Android)
108
+ agent-device pinch 0.5 200 400 # Zoom out at coordinates
107
109
  agent-device back
108
110
  agent-device home
109
111
  agent-device app-switcher
110
112
  agent-device wait 1000
111
113
  agent-device wait text "Settings"
114
+ agent-device is visible 'id="settings_anchor"' # selector assertions for deterministic checks
115
+ agent-device is text 'id="header_title"' "Settings"
112
116
  agent-device alert get
113
117
  ```
114
118
 
@@ -120,6 +124,16 @@ agent-device get attrs @e1
120
124
  agent-device screenshot out.png
121
125
  ```
122
126
 
127
+ ### Deterministic replay and updating
128
+
129
+ ```bash
130
+ agent-device open App --save-script # Save session script (.ad) on close
131
+ agent-device replay ./session.ad # Run deterministic replay from .ad script
132
+ agent-device replay -u ./session.ad # Update selector drift and rewrite .ad script in place
133
+ ```
134
+
135
+ `replay` reads `.ad` recordings.
136
+
123
137
  ### Trace logs (AX/XCTest)
124
138
 
125
139
  ```bash
@@ -141,8 +155,9 @@ agent-device apps --platform android --user-installed
141
155
 
142
156
  ## Best practices
143
157
 
144
- - Pinch (`pinch <scale> [x y]`) is supported on iOS simulators only; scale > 1 zooms in, < 1 zooms out.
145
- - Always snapshot right before interactions; refs invalidate on UI changes.
158
+ - Pinch (`pinch <scale> [x y]`) is supported on iOS simulators and Android; scale > 1 zooms in, < 1 zooms out. On Android, pinch uses multi-touch `sendevent` injection.
159
+ - Snapshot refs are the core mechanism for interactive agent flows.
160
+ - Use selectors for deterministic replay artifacts and assertions (e.g. in e2e test workflows).
146
161
  - Prefer `snapshot -i` to reduce output size.
147
162
  - On iOS, `xctest` is the default and does not require Accessibility permission.
148
163
  - If XCTest returns 0 nodes (foreground app changed), agent-device falls back to AX when available.
@@ -153,6 +168,7 @@ agent-device apps --platform android --user-installed
153
168
  - Use `fill` when you want clear-then-type semantics.
154
169
  - Use `type` when you want to append/enter text without clearing.
155
170
  - On Android, prefer `fill` for important fields; it verifies entered text and retries once when IME reorders characters.
171
+ - If using deterministic replay scripts, use `replay -u` during maintenance runs to update selector drift in replay scripts. Use plain `replay` in CI.
156
172
 
157
173
  ## References
158
174
 
@@ -14,9 +14,18 @@ Sessions isolate device context. A device can only be held by one session at a t
14
14
  - Name sessions semantically.
15
15
  - Close sessions when done.
16
16
  - Use separate sessions for parallel work.
17
+ - For deterministic replay scripts, prefer selector-based actions and assertions.
18
+ - Use `replay -u` to update selector drift during maintenance.
17
19
 
18
20
  ## Listing sessions
19
21
 
20
22
  ```bash
21
23
  agent-device session list
22
24
  ```
25
+
26
+ ## Replay within sessions
27
+
28
+ ```bash
29
+ agent-device replay ./session.ad --session auth
30
+ agent-device replay -u ./session.ad --session auth
31
+ ```