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.
- package/README.md +41 -4
- package/dist/src/bin.js +26 -21
- package/dist/src/daemon.js +9 -8
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/project.pbxproj +2 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +15 -0
- package/package.json +3 -2
- package/skills/agent-device/SKILL.md +22 -6
- package/skills/agent-device/references/session-management.md +9 -0
- package/skills/agent-device/references/snapshot-refs.md +18 -5
- package/skills/agent-device/references/video-recording.md +2 -2
- package/src/cli.ts +6 -0
- package/src/core/__tests__/capabilities.test.ts +67 -0
- package/src/core/capabilities.ts +49 -0
- package/src/core/dispatch.ts +29 -118
- package/src/daemon/__tests__/is-predicates.test.ts +68 -0
- package/src/daemon/__tests__/selectors.test.ts +128 -0
- package/src/daemon/__tests__/session-routing.test.ts +108 -0
- package/src/daemon/__tests__/session-selector.test.ts +64 -0
- package/src/daemon/__tests__/session-store.test.ts +95 -0
- package/src/daemon/__tests__/snapshot-processing.test.ts +47 -0
- package/src/daemon/action-utils.ts +29 -0
- package/src/daemon/app-state.ts +66 -0
- package/src/daemon/context.ts +36 -0
- package/src/daemon/device-ready.ts +13 -0
- package/src/daemon/handlers/__tests__/find.test.ts +99 -0
- package/src/daemon/handlers/__tests__/replay-heal.test.ts +364 -0
- package/src/daemon/handlers/__tests__/snapshot.test.ts +128 -0
- package/src/daemon/handlers/find.ts +304 -0
- package/src/daemon/handlers/interaction.ts +510 -0
- package/src/daemon/handlers/parse-utils.ts +8 -0
- package/src/daemon/handlers/record-trace.ts +154 -0
- package/src/daemon/handlers/session.ts +732 -0
- package/src/daemon/handlers/snapshot.ts +396 -0
- package/src/daemon/is-predicates.ts +46 -0
- package/src/daemon/selectors.ts +423 -0
- package/src/daemon/session-routing.ts +22 -0
- package/src/daemon/session-selector.ts +39 -0
- package/src/daemon/session-store.ts +275 -0
- package/src/daemon/snapshot-processing.ts +127 -0
- package/src/daemon/types.ts +55 -0
- package/src/daemon.ts +66 -1592
- package/src/platforms/ios/index.ts +0 -62
- package/src/platforms/ios/runner-client.ts +2 -0
- package/src/utils/args.ts +19 -10
- package/src/utils/interactors.ts +102 -16
- package/src/utils/snapshot.ts +1 -0
package/dist/src/daemon.js
CHANGED
|
@@ -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(/"/g,'"').replace(/'/g,"'").replace(/</g,"<").replace(/>/g,">").replace(/&/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=${
|
|
3
|
-
`,d.appendFileSync(e,o),(0!==
|
|
4
|
-
`),0!==
|
|
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
|
-
`)
|
|
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(/"/g,'"').replace(/'/g,"'").replace(/</g,"<").replace(/>/g,">").replace(/&/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.
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
145
|
-
-
|
|
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
|
+
```
|