agent-device 0.19.3 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/README.md +61 -70
  2. package/android/ime-helper/dist/agent-device-android-ime-helper-0.20.0.apk +0 -0
  3. package/android/ime-helper/dist/agent-device-android-ime-helper-0.20.0.apk.sha256 +1 -0
  4. package/android/ime-helper/dist/agent-device-android-ime-helper-0.20.0.manifest.json +10 -0
  5. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.0.apk +0 -0
  6. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.0.apk.sha256 +1 -0
  7. package/{android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.19.3.manifest.json → android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.0.manifest.json} +6 -6
  8. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAXSnapshotBridge.h +2 -0
  9. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAXSnapshotBridge.m +5 -0
  10. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.h +5 -10
  11. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.m +74 -174
  12. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift +113 -19
  13. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+BlockingSystemModalResolution.swift +82 -0
  14. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +272 -118
  15. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift +2 -2
  16. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +134 -101
  17. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +56 -2
  18. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+LifecycleCacheTests.swift +73 -0
  19. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +44 -24
  20. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScrollGesture.swift +2 -2
  21. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SequenceExecution.swift +2 -107
  22. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift +126 -11
  23. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCapturePlan.swift +51 -8
  24. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotTraversalIdentityTests.swift +24 -0
  25. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedGesturePolicy.swift +1 -1
  26. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift +43 -35
  27. package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +14 -0
  28. package/dist/src/agent-browser-tool.js +1 -1
  29. package/dist/src/agent-device-client.js +3 -0
  30. package/dist/src/android-adb.d.ts +1 -1
  31. package/dist/src/android-adb.js +1 -1
  32. package/dist/src/android-input-method-overlays.js +1 -0
  33. package/dist/src/android-input-ownership.js +1 -6
  34. package/dist/src/android-system-chrome.js +1 -0
  35. package/dist/src/android.js +1 -1
  36. package/dist/src/app-lifecycle.js +5 -5
  37. package/dist/src/app-log-request-scope.js +1 -0
  38. package/dist/src/app-resolution.js +1 -0
  39. package/dist/src/apple-multitouch-support.js +1 -0
  40. package/dist/src/apps.js +5 -5
  41. package/dist/src/boot-diagnostics.js +2 -0
  42. package/dist/src/cli-config.js +1 -0
  43. package/dist/src/cli-help.js +61 -27
  44. package/dist/src/cli-runner.js +1 -1
  45. package/dist/src/cli.js +15 -11
  46. package/dist/src/command-schema.js +3 -3
  47. package/dist/src/config.js +1 -0
  48. package/dist/src/context.js +1 -0
  49. package/dist/src/contracts.d.ts +1 -1
  50. package/dist/src/daemon-shutdown-report.js +1 -0
  51. package/dist/src/debug-symbols.js +1 -1
  52. package/dist/src/device-input-state.js +1 -1
  53. package/dist/src/devices2.js +1 -1
  54. package/dist/src/find.js +1 -1
  55. package/dist/src/finders.js +1 -1
  56. package/dist/src/generic.js +1 -1
  57. package/dist/src/helper.js +1 -1
  58. package/dist/src/index.d.ts +412 -293
  59. package/dist/src/index.js +1 -1
  60. package/dist/src/input-actions.js +4 -3
  61. package/dist/src/install-artifact.js +1 -0
  62. package/dist/src/install-source.d.ts +5 -1
  63. package/dist/src/install-source.js +1 -1
  64. package/dist/src/install-source2.js +1 -1
  65. package/dist/src/interaction-snapshot.js +1 -1
  66. package/dist/src/interaction.d.ts +230 -0
  67. package/dist/src/interaction.js +1 -1
  68. package/dist/src/interactor.js +2 -2
  69. package/dist/src/internal/bin.js +1 -1
  70. package/dist/src/internal/daemon.js +42 -20
  71. package/dist/src/internal/png-worker.d.ts +22 -0
  72. package/dist/src/internal/png-worker.js +1 -1
  73. package/dist/src/launch-console.js +1 -0
  74. package/dist/src/lease-context.js +1 -0
  75. package/dist/src/lease.js +1 -1
  76. package/dist/src/linux.js +1 -1
  77. package/dist/src/logcat.js +5 -0
  78. package/dist/src/metro.d.ts +2 -2
  79. package/dist/src/notifications.js +1 -1
  80. package/dist/src/png-worker-contract.js +1 -1
  81. package/dist/src/process-lock.js +1 -0
  82. package/dist/src/react-native.js +1 -1
  83. package/dist/src/record-trace.js +1 -1
  84. package/dist/src/register-builtins.js +1 -0
  85. package/dist/src/registry.js +24 -24
  86. package/dist/src/reporting.js +8 -7
  87. package/dist/src/runner-client.js +1 -1
  88. package/dist/src/runner-disposal.js +2 -0
  89. package/dist/src/runner-lease.js +1 -0
  90. package/dist/src/runner-sequence.js +1 -1
  91. package/dist/src/runtime.js +3 -3
  92. package/dist/src/runtime2.js +1 -0
  93. package/dist/src/sdk-android-adb.d.ts +119 -33
  94. package/dist/src/sdk-android-adb.js +2 -2
  95. package/dist/src/sdk-batch-runner.d.ts +2 -1
  96. package/dist/src/sdk-batch-runner.js +2 -2
  97. package/dist/src/sdk-batch.js +2 -2
  98. package/dist/src/sdk-contracts.d.ts +105 -98
  99. package/dist/src/sdk-contracts.js +1 -1
  100. package/dist/src/sdk-device.js +1 -1
  101. package/dist/src/sdk-finders.d.ts +3 -3
  102. package/dist/src/sdk-finders.js +1 -1
  103. package/dist/src/sdk-io.d.ts +1 -1
  104. package/dist/src/sdk-remote-config.d.ts +9 -1
  105. package/dist/src/sdk-remote-config.js +1 -1
  106. package/dist/src/sdk-selectors.js +1 -1
  107. package/dist/src/selector-runtime.js +1 -1
  108. package/dist/src/selector-vocabulary.js +1 -0
  109. package/dist/src/selectors.d.ts +12 -3
  110. package/dist/src/selectors.js +1 -1
  111. package/dist/src/server.js +3 -3
  112. package/dist/src/session-target-evidence.js +1 -0
  113. package/dist/src/session.js +8 -10
  114. package/dist/src/simulator.js +1 -1
  115. package/dist/src/snapshot-diagnostics.js +5 -2
  116. package/dist/src/snapshot.js +1 -1
  117. package/dist/src/snapshot2.js +1 -2
  118. package/dist/src/tv-remote.js +13 -12
  119. package/dist/src/url.js +1 -1
  120. package/dist/src/web.js +1 -1
  121. package/package.json +28 -23
  122. package/server.json +2 -2
  123. package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.19.3.apk +0 -0
  124. package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.19.3.apk.sha256 +0 -1
  125. package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.19.3.manifest.json +0 -10
  126. package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.19.3.apk +0 -0
  127. package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.19.3.apk.sha256 +0 -1
  128. package/dist/src/apple-runner-options.js +0 -1
  129. package/dist/src/cli-output.js +0 -1
  130. package/dist/src/client.js +0 -3
  131. package/dist/src/handler-utils.js +0 -1
  132. package/dist/src/output-path.js +0 -1
  133. package/dist/src/points.js +0 -4
  134. package/dist/src/provider.js +0 -1
  135. package/dist/src/providers.js +0 -1
  136. package/dist/src/record-trace-recording.js +0 -23
  137. package/dist/src/recording-provider.js +0 -1
  138. package/dist/src/selectors2.js +0 -1
  139. package/dist/src/simctl.js +0 -1
  140. package/dist/src/ttl-memo.js +0 -1
  141. package/dist/src/xml.js +0 -2
  142. /package/{macos-helper → apple/macos-helper}/Package.swift +0 -0
  143. /package/{macos-helper → apple/macos-helper}/Sources/AgentDeviceMacOSHelper/AudioProbe.swift +0 -0
  144. /package/{macos-helper → apple/macos-helper}/Sources/AgentDeviceMacOSHelper/SnapshotTraversal.swift +0 -0
  145. /package/{macos-helper → apple/macos-helper}/Sources/AgentDeviceMacOSHelper/main.swift +0 -0
  146. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunner/AgentDeviceRunnerApp.m +0 -0
  147. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/project.pbxproj +0 -0
  148. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -0
  149. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/xcshareddata/xcschemes/AgentDeviceRunner.xcscheme +0 -0
  150. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/AgentDeviceRunnerUITests-Bridging-Header.h +0 -0
  151. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/AgentDeviceRunnerUITests.entitlements +0 -0
  152. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerObjCExceptionCatcher.h +0 -0
  153. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerObjCExceptionCatcher.m +0 -0
  154. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTapPointPolicy.swift +0 -0
  155. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+AXSnapshotFallback.swift +0 -0
  156. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Environment.swift +0 -0
  157. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Exceptions.swift +0 -0
  158. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+FlatSnapshotFiltering.swift +0 -0
  159. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Navigation.swift +0 -0
  160. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScreenRecorder.swift +0 -0
  161. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift +0 -0
  162. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Transport.swift +0 -0
  163. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TvRemote.swift +0 -0
  164. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests.xctestplan +0 -0
  165. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/RecordingScripts/recording-overlay.swift +0 -0
  166. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/RecordingScripts/recording-resize.swift +0 -0
  167. /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/RecordingScripts/recording-trim.swift +0 -0
  168. /package/{src/platforms/linux → linux}/atspi-dump.py +0 -0
package/dist/src/apps.js CHANGED
@@ -1,5 +1,5 @@
1
- import{t as e}from"./rolldown-runtime.js";import{_ as t}from"./sdk-contracts.js";import{h as n,j as r,p as i,v as a}from"./sdk-android-adb.js";import{Ct as o,Et as s}from"./tv-remote.js";import{c,s as l}from"./sdk-device.js";import{i as u}from"./runtime.js";import{n as ee,t as te}from"./install-source2.js";import{D as d,E as f,O as ne,S as p,k as re,w as m,x as ie}from"./android-input-ownership.js";import{d as h,i as ae}from"./runner-client.js";import{a as g,i as _,r as oe,s as se}from"./tool-provider.js";import{a as ce,n as le}from"./helper.js";import{n as v,r as ue}from"./simctl.js";import{$ as de,$t as fe,G as y,J as pe,K as me,Q as he,U as ge,V as b,Y as x,bt as _e,en as ve,et as ye,tt as be,yt as xe}from"./internal/daemon.js";import{a as Se,i as Ce,n as S,r as C,t as w}from"./setting-state.js";import{i as T,n as we,t as E}from"./simulator.js";import{promises as D}from"node:fs";import O from"node:os";import k from"node:path";import{fileURLToPath as Te}from"node:url";function A(e){if(!(e instanceof t))return{reason:e instanceof Error?e.message:String(e)};let n=e.details??{},r=Array.isArray(n.args)?n.args.filter(e=>typeof e==`string`).join(` `):void 0;return{errorCode:e.code,reason:e.message,timeoutMs:typeof n.timeoutMs==`number`?n.timeoutMs:void 0,exitCode:typeof n.exitCode==`number`?n.exitCode:void 0,stderr:typeof n.stderr==`string`&&n.stderr.trim()?n.stderr:void 0,stdout:typeof n.stdout==`string`&&n.stdout.trim()?n.stdout:void 0,commandArgs:r}}const Ee=[`--time`,`9:41`,`--dataNetwork`,`wifi`,`--wifiMode`,`active`,`--wifiBars`,`3`,`--batteryState`,`charged`,`--batteryLevel`,`100`],De={0:`hide`,1:`wifi`,6:`3g`,7:`4g`,8:`lte`,9:`lte-a`,10:`lte+`,11:`5g`,12:`5g+`,13:`5g-uwb`,14:`5g-uc`},Oe={1:`searching`,2:`failed`,3:`active`},ke={0:`notSupported`,1:`searching`,2:`failed`,3:`active`};function j(e,t,n){return ue(e,t,n)}const M=new Set;async function Ae(e){let t=null,n=!1;if(Me(e))n=!0;else try{t=await Ne(e),n=!0}catch(t){R(e,`snapshot_failed`,t)}try{(!n||t)&&await I(e),await L(e,Ee)}catch(t){R(e,`prepare_failed`,t)}return async()=>{await je(e,n?t:null)}}async function je(e,t){if(await I(e),!t){P(e);return}await L(e,Fe(t)),N(e)}function N(e){M.delete(F(e))}function P(e){M.add(F(e))}function Me(e){return M.has(F(e))}function F(e){return`${e.platform}:${e.kind}:${e.id}`}async function Ne(e){return Pe(n(await j(e,[`status_bar`,e.id,`list`],{allowFailure:!0}),`Failed to read simulator status bar overrides`).stdout)}async function I(e){await j(e,[`status_bar`,e.id,`clear`])}async function L(e,t){t.length!==0&&await j(e,[`status_bar`,e.id,`override`,...t])}function Pe(e){let n={},r=e.split(`
2
- `).map(e=>e.trim()).filter(e=>e.length>0&&e!==`Current Status Bar Overrides:`&&!/^=+$/.test(e));for(let e of r){let r=/^DataNetworkType:\s+(\d+)$/.exec(e);if(r){let e=Number(r[1]),i=De[e];if(!i)throw new t(`COMMAND_FAILED`,`Unsupported simulator data network type: ${e}`);n.dataNetwork=i;continue}let i=/^WiFi Mode:\s+(\d+),\s+WiFi Bars:\s+(\d+)$/.exec(e);if(i){let e=Oe[Number(i[1])];e&&(n.wifiMode=e),n.wifiBars=i[2];continue}let a=/^Cell Mode:\s+(\d+),\s+Cell Bars:\s+(\d+)$/.exec(e);if(a){let e=Number(a[1]),r=ke[e];if(!r)throw new t(`COMMAND_FAILED`,`Unsupported simulator cellular mode: ${e}`);n.cellularMode=r,n.cellularBars=a[2];continue}let o=/^Operator Name:\s*(.*)$/.exec(e);if(o){n.operatorName=o[1]??``;continue}}return Object.keys(n).length===0?null:n}function Fe(e){let t=[];return e.dataNetwork&&t.push(`--dataNetwork`,e.dataNetwork),e.wifiMode&&t.push(`--wifiMode`,e.wifiMode),e.wifiBars!==void 0&&(e.dataNetwork===`wifi`||e.wifiMode)&&t.push(`--wifiBars`,e.wifiBars),e.cellularMode&&t.push(`--cellularMode`,e.cellularMode),e.cellularBars!==void 0&&(e.cellularMode||Ie(e.dataNetwork)||e.operatorName!==void 0)&&t.push(`--cellularBars`,e.cellularBars),e.operatorName!==void 0&&t.push(`--operatorName`,e.operatorName),t}function Ie(e){return!!(e&&e!==`hide`&&e!==`wifi`)}function R(e,t,n){r({level:`warn`,phase:`ios_screenshot_status_bar_${t}`,data:{platform:e.platform,deviceKind:e.kind,deviceId:e.id,...A(n)}})}function z(e,t,n){return ue(e,t,n)}const Le={ensureBooted:E,prepareStatusBarForScreenshot:Ae,captureWithRetry:Be,normalizeDensity:Xe,captureWithRunner:V,shouldFallbackToRunner:Je},B=new Map;async function Re(e,t,n={}){if(c(e)){await V(e,t,n.appBundleId,n.fullscreen,n.runnerOptions);return}if(e.kind===`simulator`){await ze(e,t,n);return}try{await y([`device`,`screenshot`,`--device`,e.id,t],{action:`capture iOS screenshot`,deviceId:e.id});return}catch(t){if(!qe(t))throw t;We(e,`devicectl_screenshot`,t)}await V(e,t,n.appBundleId,n.fullscreen,n.runnerOptions)}async function ze(e,n,r={}){if(e.kind!==`simulator`)throw new t(`UNSUPPORTED_OPERATION`,`Simulator screenshot fallback flow supports only iOS simulators`);let i={...Le,...r.deps??{}};r.skipIosSimulatorBootCheck||await i.ensureBooted(e);let a=async()=>{};if(r.normalizeStatusBar===!0)try{a=await i.prepareStatusBarForScreenshot(e)}catch(t){Ge(e,`prepare_failed`,t)}try{try{await i.captureWithRetry(e,n),await i.normalizeDensity(e,n,r.pixelDensity);return}catch(t){let a=t;if(r.skipIosSimulatorBootCheck&&Ye(t)){await i.ensureBooted(e);try{await i.captureWithRetry(e,n),await i.normalizeDensity(e,n,r.pixelDensity);return}catch(e){a=e}}if(!i.shouldFallbackToRunner(a))throw a;We(e,`simctl_screenshot`,a)}await i.captureWithRunner(e,n,r.appBundleId,r.fullscreen,r.runnerOptions),await i.normalizeDensity(e,n,r.pixelDensity)}finally{await a().catch(t=>Ge(e,`restore_failed`,t))}}async function Be(e,t){let n=p.fromTimeoutMs(ye);await m(async({deadline:n})=>{await z(e,[`io`,e.id,`screenshot`,t],{timeoutMs:Math.max(1e3,n?.remainingMs()??2e4)})},{maxAttempts:5,baseDelayMs:he,maxDelayMs:de,jitter:.2,shouldRetry:e=>Je(e)},{deadline:n,phase:`ios_simulator_screenshot`})}async function V(e,n,r,i,a){let o=(await ae(e,{command:`screenshot`,appBundleId:r,fullscreen:i},a)).message;if(!o)throw new t(`COMMAND_FAILED`,`Failed to capture iOS screenshot: runner returned no file path`);if(c(e)){await D.copyFile(o,n);return}if(e.kind===`simulator`){await He(e,o,n);return}await Ve(e,o,n)}async function Ve(e,n,r){let i=p.fromTimeoutMs(x),a={exitCode:1,stdout:``,stderr:``};for(let t of h)if(a=await g([`devicectl`,`device`,`copy`,`from`,`--device`,e.id,`--source`,n,`--destination`,r,`--domain-type`,`appDataContainer`,`--domain-identifier`,t],{allowFailure:!0,timeoutMs:Ue(i,x,`runner screenshot copy`)}),a.exitCode===0)return;throw new t(`COMMAND_FAILED`,`Failed to capture iOS screenshot: ${a.stderr.trim()||a.stdout.trim()||`devicectl exited with code ${a.exitCode}`}`)}async function He(e,n,r){let i=p.fromTimeoutMs(x),a=`Unable to locate runner container for simulator screenshot`;for(let t of h){let o=await z(e,[`get_app_container`,e.id,t,`data`],{allowFailure:!0,timeoutMs:Ue(i,x,`runner screenshot container lookup`)});if(o.exitCode!==0){let e=o.stderr.trim();e&&(a=e);continue}let s=o.stdout.trim();if(!s){a=`simctl get_app_container returned empty output`;continue}let c=Ke(s,n);for(let e of c)try{await D.copyFile(e,r);return}catch(e){a=e instanceof Error?e.message:String(e)}}throw new t(`COMMAND_FAILED`,`Failed to capture iOS screenshot: ${a}`)}function Ue(e,n,r){let i=e.remainingMs();if(i>0)return i;throw new t(`COMMAND_FAILED`,`iOS ${r} timed out after ${n}ms`,{timeoutMs:n,step:r})}function We(e,t,n){let i=A(n);r({level:`warn`,phase:`ios_screenshot_fallback`,data:{platform:e.platform,deviceKind:e.kind,deviceId:e.id,from:t,to:`runner`,...i}})}function Ge(e,t,n){r({level:`warn`,phase:`ios_screenshot_status_bar_${t}`,data:{platform:e.platform,deviceKind:e.kind,deviceId:e.id,...A(n)}})}function Ke(e,t){let n=k.resolve(e),r=t.trim();if(!r)return[];let i=[],a=new Set,o=e=>{let t=k.normalize(e);a.has(t)||(a.add(t),i.push(t))},s=r.replace(/^\/+/,``),c=s.replace(/\\/g,`/`);if(s&&o(k.join(n,s)),k.isAbsolute(r)&&o(k.normalize(r)),c.startsWith(`tmp/`))o(k.join(n,c));else{let e=c.lastIndexOf(`/tmp/`);if(e>=0){let t=c.slice(e+1);o(k.join(n,t))}}let l=k.basename(r);return l&&o(k.join(n,`tmp`,l)),i}function qe(e){if(!(e instanceof t)||e.code!==`COMMAND_FAILED`)return!1;let n=H(e);return n.includes(`unknown option '--device'`)||n.includes(`unknown subcommand`)&&n.includes(`screenshot`)||n.includes(`unrecognized subcommand`)&&n.includes(`screenshot`)}function Je(e){if(!(e instanceof t)||e.code!==`COMMAND_FAILED`)return!1;let n=H(e);return n.includes(`timeout waiting for screen surfaces`)||n.includes(`nsposixerrordomain`)&&n.includes(`code=60`)&&n.includes(`screenshot`)||n.includes(`timed out`)&&n.includes(`screenshot`)}function Ye(e){if(!(e instanceof t)||e.code!==`COMMAND_FAILED`)return!1;let n=H(e);return n.includes(`not booted`)||n.includes(`current state: shutdown`)||n.includes(`current state is shutdown`)||n.includes(`current state=shutdown`)||n.includes(`state: shutdown`)||n.includes(`state=shutdown`)}function H(e){let t=e.details??{},n=typeof t.stdout==`string`?t.stdout:``,r=typeof t.stderr==`string`?t.stderr:``,i=Array.isArray(t.args)?t.args.filter(e=>typeof e==`string`).join(` `):``;return`${e.message}\n${n}\n${r}\n${i}`.toLowerCase()}async function Xe(e,t,n){let r=await Ze(e),i=xe(await _e(t),r,n);i&&await u(t,i.width,i.height)}async function Ze(e){let n=B.get(e.id);if(n!==void 0)return n;let r=await z(e,[`getenv`,e.id,`SIMULATOR_MAINSCREEN_SCALE`],{timeoutMs:5e3}),i=Number(r.stdout.trim());if(!Number.isFinite(i)||i<=0)throw new t(`COMMAND_FAILED`,`Failed to read iOS simulator screenshot scale from SIMULATOR_MAINSCREEN_SCALE`);return B.set(e.id,i),i}const Qe={settings:`com.apple.systempreferences`},$e=/^[a-z0-9-]+(?:\.[a-z0-9-]+)+$/,et={platform:`macos`,deviceId:`host`,variant:`all`},U=f();function tt(e){return $e.test(e)}async function W(e){let n=e.trim(),r=Qe[n.toLowerCase()];if(r)return r;if(tt(n))return n;let i=U.get(et,n);if(i)return i;let a=(await ct(`all`)).filter(e=>e.name.toLowerCase()===n.toLowerCase()),o=a[0];if(o!==void 0&&a.length===1)return U.set(et,n,o.bundleId);throw a.length>1?new t(`INVALID_ARGS`,`Multiple apps matched "${e}"`,{matches:a}):new t(`APP_NOT_INSTALLED`,`No app found matching "${e}"`)}async function nt(e,n,r){let i=r?.url?.trim();if(i){if(!d(i))throw new t(`INVALID_ARGS`,`open <app> <url> requires a valid URL target`);let e=r?.appBundleId??await W(n);await G().openBundle(e,i);return}let a=n.trim();if(d(a)){await G().openTarget(a);return}let o=r?.appBundleId??await W(a);await G().openBundle(o)}async function rt(e,n){let r=await W(n),i=await le(r);if(!(!i.running||i.terminated||i.forceTerminated))throw new t(`COMMAND_FAILED`,`Failed to close macOS app ${n}`,{bundleId:r,running:i.running,terminated:i.terminated,forceTerminated:i.forceTerminated})}async function it(){return await G().readClipboard()}async function at(e){await G().writeClipboard(e)}async function ot(){return await G().readDarkMode()}async function st(e){let t=S(e),n=t===`toggle`?!await ot():t===`dark`;await G().setDarkMode(n)}async function ct(e){return await G().listApps(e)}function G(){let e=oe().macosHost;if(!e)throw new t(`UNSUPPORTED_OPERATION`,`macOS host provider is not available`);return e}function K(e,t){return v(e,t)}function q(e,t,n){return g(K(e,t),n)}function lt(e){return e.includes(`not installed`)||e.includes(`not found`)||e.includes(`no such file`)}const ut={settings:`com.apple.Preferences`},J=f();function dt(e){return{platform:`ios`,deviceId:e.id,variant:e.kind}}async function Y(e,t){return await J.invalidateWhile(dt(e),t)}async function X(e,n){if(c(e))return await W(n);let r=n.trim();if(r.includes(`.`))return r;let i=ut[r.toLowerCase()];if(i)return i;let a=dt(e),o=J.get(a,r);if(o)return o;let s=(e.kind===`simulator`?await ht(e):await ge(e,`all`)).filter(e=>e.name.toLowerCase()===r.toLowerCase()),l=s[0];if(l!==void 0&&s.length===1)return J.set(a,r,l.bundleId);throw s.length>1?new t(`INVALID_ARGS`,`Multiple apps matched "${n}"`,{matches:s}):new t(`APP_NOT_INSTALLED`,`No app found matching "${n}"`)}async function ft(e,t){if(!l(e)||e.kind!==`simulator`)return;let n=pt(t);if(!n)return;let r=await gt(e),i=[];for(let e of r)e.bundleId.startsWith(`com.callstack.agentdevice.runner`)||e.path&&(await vt(k.join(e.path,`Info.plist`))).has(n)&&i.push(e);let a=i.filter(e=>e.applicationType===`User`);if(a.length===1)return a[0]?.bundleId;if(!(a.length>1))return i.length===1?i[0]?.bundleId:void 0}function pt(e){return/^([A-Za-z][A-Za-z0-9+.-]*):/.exec(e.trim())?.[1]?.toLowerCase()}async function mt(e,t){return c(e)?await ct(t):e.kind===`simulator`?se(await ht(e),t):await ge(e,t)}async function ht(e){return(await gt(e)).map(e=>({bundleId:e.bundleId,name:e.name}))}async function gt(e){let t=(await q(e,[`listapps`,e.id],{allowFailure:!0})).stdout.trim();if(!t)return[];let n=null;if(t.startsWith(`{`))try{n=JSON.parse(t)}catch{n=null}if(!n&&t.startsWith(`{`))try{let e=await _(`plutil`,[`-convert`,`json`,`-o`,`-`,`-`],{allowFailure:!0,stdin:t});e.exitCode===0&&e.stdout.trim().startsWith(`{`)&&(n=JSON.parse(e.stdout))}catch{n=null}return n?Object.entries(n).map(([e,t])=>{let n=_t(t);return{bundleId:e,name:t.CFBundleDisplayName??t.CFBundleName??e,...n?{path:n}:{},...t.ApplicationType?{applicationType:t.ApplicationType}:{}}}):[]}function _t(e){if(e.Path)return e.Path;if(e.Bundle)try{return Te(e.Bundle)}catch{return}}async function vt(e){let t=await _(`plutil`,[`-convert`,`json`,`-o`,`-`,e],{allowFailure:!0});if(t.exitCode!==0)return new Set;try{let e=JSON.parse(t.stdout),n=new Set;for(let t of e.CFBundleURLTypes??[])if(Array.isArray(t.CFBundleURLSchemes))for(let e of t.CFBundleURLSchemes)typeof e==`string`&&e.trim()&&n.add(e.trim().toLowerCase());return n}catch{return new Set}}function yt(e){if(!(e instanceof t)||e.code!==`COMMAND_FAILED`)return!1;let n=e.details??{};if(n.exitCode!==4)return!1;let r=String(n.stderr??``).toLowerCase();return r.includes(`fbsopenapplicationserviceerrordomain`)&&r.includes(`the request to open`)}async function bt(e,t){let n=await g(v(e,[`get_app_container`,e.id,t]),{allowFailure:!0});if(n.exitCode!==0)return{installed:!1};let r=n.stdout.trim();if(!r)return{installed:!1};let i=await _(`plutil`,[`-extract`,`CFBundleExecutable`,`raw`,`-o`,`-`,`${r}/Info.plist`],{allowFailure:!0});if(i.exitCode!==0||!i.stdout.trim())return{installed:!0};let a=await _(`otool`,[`-l`,`${r}/${i.stdout.trim()}`],{allowFailure:!0});if(a.exitCode!==0)return{installed:!0};let o=a.stdout.toLowerCase();return{installed:!0,simulatorCompatible:o.includes(`iossimulator`)||o.includes(`platform 7`)}}function xt(e){return e.installed?e.simulatorCompatible===!1?`ARCH_MISMATCH`:`PERSISTENT_LAUNCH_FAIL`:`APP_NOT_INSTALLED`}function St(e){switch(e){case`ARCH_MISMATCH`:return`The app binary was not built for the simulator platform. Rebuild with a simulator destination or use a physical device.`;case`APP_NOT_INSTALLED`:return`The app bundle is not installed on this simulator. Run install before open.`;case`PERSISTENT_LAUNCH_FAIL`:return`The simulator repeatedly refused to launch the app. Inspect crash logs in Console.app or ~/Library/Logs/DiagnosticReports/ and consider reinstalling the app.`;default:return`The simulator failed to launch the app. Retry with --debug and inspect diagnostics log for details.`}}const Z=25e3;async function Ct(e,n,r){let i=r?.launchConsole?.trim(),a=r?.launchArgs;if(i&&(!l(e)||e.kind!==`simulator`))throw new t(`UNSUPPORTED_OPERATION`,ve);if(c(e)){if(a&&a.length>0)throw new t(`UNSUPPORTED_OPERATION`,`--launch-args is not supported on macOS; launch arguments are currently iOS-only.`);await nt(e,n,r);return}let o=r?.url?.trim();if(o){if(i)throw new t(`INVALID_ARGS`,fe);if(!d(o))throw new t(`INVALID_ARGS`,`open <app> <url> requires a valid URL target`);if(e.kind===`simulator`){(a||ne(o))&&await Dt(e,r?.appBundleId??await X(e,n),{...a?{launchArgs:a}:{}}),await wt(e,o,void 0);return}let s=re(r?.appBundleId??await X(e,n),o);if(!s)throw new t(`INVALID_ARGS`,`Deep link open on iOS devices requires an active app bundle ID. Open the app first, then open the URL.`);await Q(e,s,{payloadUrl:o,launchArgs:a});return}let s=n.trim();if(d(s)){if(i)throw new t(`INVALID_ARGS`,fe);if(e.kind===`simulator`){await wt(e,s,a);return}let n=re(r?.appBundleId,s);if(!n)throw new t(`INVALID_ARGS`,`Deep link open on iOS devices requires an active app bundle ID. Open the app first, then open the URL.`);await Q(e,n,{payloadUrl:s,launchArgs:a});return}let u=r?.appBundleId??await X(e,n);if(e.kind===`simulator`){await Dt(e,u,{...i?{launchConsole:i}:{},...a?{launchArgs:a}:{},...r?.terminateRunningApp?{terminateRunningApp:!0}:{}});return}await Q(e,u,{launchArgs:a})}async function wt(e,n,r){if(r&&r.length>0)throw new t(`INVALID_ARGS`,`--launch-args is not supported with iOS simulator URL opens (simctl openurl ignores launch args). Launch the app first with --launch-args, then issue the URL open in a separate call.`);await E(e),await q(e,[`openurl`,e.id,n])}async function Tt(e){c(e)||e.kind===`simulator`&&await we(e)!==`Booted`&&await E(e)}async function Et(e,n){if(c(e)){await rt(e,n);return}let r=await X(e,n);if(e.kind===`simulator`){await E(e);let n=K(e,[`terminate`,e.id,r]),a=await g(n,{allowFailure:!0,timeoutMs:be});if(a.exitCode!==0){if(a.stderr.toLowerCase().includes(`found nothing to terminate`))return;throw new t(`COMMAND_FAILED`,`xcrun exited with code ${a.exitCode}`,i(a,{cmd:`xcrun`,args:n}))}return}await y([`device`,`process`,`terminate`,`--device`,e.id,r],{action:`terminate iOS app`,deviceId:e.id})}async function Dt(e,n,r){await E(e);let a=0,o=p.fromTimeoutMs(me);try{await m(async({deadline:a})=>{if(a?.isExpired())throw new t(`COMMAND_FAILED`,`App launch deadline exceeded`,{timeoutMs:me});let o=K(e,Ot(e.id,n,r)),s=r?.launchConsole?await kt(o,r.launchConsole):await g(o,{allowFailure:!0});if(s.exitCode!==0)throw new t(`COMMAND_FAILED`,`xcrun exited with code ${s.exitCode}`,i(s,{cmd:`xcrun`,args:o}))},{maxAttempts:10,baseDelayMs:1e3,maxDelayMs:5e3,jitter:.2,shouldRetry(e){return yt(e)?(a+=1,a<3):!1}},{deadline:o})}catch(t){if(yt(t)){let r=t,i=xt(await bt(e,n));r.details={...r.details,hint:St(i)}}throw t}}function Ot(e,t,n){let r=[`launch`];return n?.launchConsole&&r.push(`--console-pty`),n?.terminateRunningApp&&r.push(`--terminate-running-process`),r.push(e,t),n?.launchArgs&&n.launchArgs.length>0&&r.push(...n.launchArgs),r}async function kt(e,n){await D.mkdir(k.dirname(n),{recursive:!0});try{let t=await g(e,{allowFailure:!0,timeoutMs:Z});return await At(n,t.stdout,t.stderr),t}catch(e){let i=(e instanceof t?e:void 0)?.details;if(i?.timeoutMs===Z){let e=typeof i.stdout==`string`?i.stdout:``,t=typeof i.stderr==`string`?i.stderr:``;return await At(n,e,t),r({level:`warn`,phase:`ios_simulator_launch_console_capture_timeout`,data:{timeoutMs:Z,logPath:n,stdoutBytes:Buffer.byteLength(e),stderrBytes:Buffer.byteLength(t)}}),{stdout:e,stderr:t,exitCode:0}}throw e}}async function At(e,t,n){await D.writeFile(e,jt(t,n),`utf8`)}function jt(e,t){return!e||!t||e.endsWith(`
3
- `)||e.endsWith(`\r`)?`${e}${t}`:`${e}\n${t}`}async function Q(e,t,n){let r=[`device`,`process`,`launch`,`--device`,e.id,t];n?.payloadUrl&&r.push(`--payload-url`,n.payloadUrl),n?.launchArgs&&n.launchArgs.length>0&&r.push(`--`,...n.launchArgs),await y(r,{action:`launch iOS app`,deviceId:e.id})}var Mt=e({prepareIosInstallArtifact:()=>Nt});async function Nt(e,n){if(e.kind===`url`&&!te(e.url))throw new t(`INVALID_ARGS`,`iOS install_from_source URL sources are only supported for trusted artifact services such as GitHub Actions and EAS. Use a path source for other hosts.`);let r=await ee({source:e,isInstallablePath:(e,t)=>t.isDirectory()&&e.toLowerCase().endsWith(`.app`)||t.isFile()&&e.toLowerCase().endsWith(`.ipa`),installableLabel:`iOS installable (.app or .ipa)`,allowArchiveExtraction:e.kind!==`url`||te(e.url),signal:n?.signal}),i;try{i=await Ft(r.installablePath,n);let e=i,t=await Pt(e.installPath);return{archivePath:r.archivePath??(r.installablePath.toLowerCase().endsWith(`.ipa`)?r.installablePath:void 0),installablePath:e.installPath,bundleId:t.bundleId,appName:t.appName,cleanup:async()=>{await e.cleanup(),await r.cleanup()}}}catch(e){try{await i?.cleanup()}finally{await r.cleanup()}throw e}}async function Pt(e){let t=k.join(e,`Info.plist`),[n,r,i]=await Promise.all([b(t,`CFBundleIdentifier`),b(t,`CFBundleDisplayName`),b(t,`CFBundleName`)]);return{bundleId:n,appName:r??i}}async function Ft(e,n){if(!e.toLowerCase().endsWith(`.ipa`))return{installPath:e,cleanup:async()=>{}};let r=await D.mkdtemp(k.join(O.tmpdir(),`agent-device-ios-ipa-`)),i=async()=>{await D.rm(r,{recursive:!0,force:!0})};try{await a(`unzip`,[`-q`,e,`-d`,r]);let o=k.join(r,`Payload`),s=(await D.readdir(o,{withFileTypes:!0}).catch(()=>{throw new t(`INVALID_ARGS`,`Invalid IPA: missing Payload directory`)})).filter(e=>e.isDirectory()&&e.name.toLowerCase().endsWith(`.app`)).map(e=>({installPath:k.join(o,e.name),bundleName:e.name.replace(/\.app$/i,``)})),c=s[0];if(c!==void 0&&s.length===1)return{installPath:c.installPath,cleanup:i};if(s.length===0)throw new t(`INVALID_ARGS`,`Invalid IPA: expected at least one .app under Payload, found 0`);await It(s);let l=n?.appIdentifierHint?.trim();if(l){let e=Lt(s,l);if(e)return{installPath:e.installPath,cleanup:i};throw new t(`INVALID_ARGS`,`Invalid IPA: found ${s.length} .app bundles under Payload and none matched "${l}". Available bundles: ${s.map(Rt).join(`, `)}`)}throw new t(`INVALID_ARGS`,`Invalid IPA: found ${s.length} .app bundles under Payload. Pass an app identifier or bundle name matching one of: ${s.map(Rt).join(`, `)}`)}catch(e){throw await i(),e}}async function It(e){await Promise.all(e.map(async e=>{if(e.bundleId&&e.appName)return;let t=await Pt(e.installPath);e.bundleId=e.bundleId??t.bundleId,e.appName=e.appName??t.appName}))}function Lt(e,n){let r=n.toLowerCase(),i=e.filter(e=>e.bundleName.toLowerCase()===r);if(i.length===1)return i[0];if(i.length>1)throw new t(`INVALID_ARGS`,`Invalid IPA: multiple app bundles matched "${n}" by name. Use a bundle id hint instead.`);if(n.includes(`.`)){let t=e.filter(e=>e.bundleId?.toLowerCase()===r);if(t.length===1)return t[0]}}function Rt(e){let t=e.bundleId??e.appName;return t?`${e.bundleName}.app (${t})`:`${e.bundleName}.app`}async function zt(e,n){return await Y(e,async()=>{let r=await X(e,n);if(e.kind!==`simulator`)return await y([`device`,`uninstall`,`app`,`--device`,e.id,r],{action:`uninstall iOS app ${r}`,deviceId:e.id},{tolerateOutput:(e,t)=>lt(`${e}\n${t}`.toLowerCase())}),{bundleId:r};await E(e);let a=await q(e,[`uninstall`,e.id,r],{allowFailure:!0});if(a.exitCode!==0&&!lt(`${a.stdout}\n${a.stderr}`.toLowerCase()))throw new t(`COMMAND_FAILED`,`simctl uninstall failed for ${r}`,i(a));return{bundleId:r}})}async function Bt(e,t,n){let r=await Nt({kind:`path`,path:t},n);try{return await Ht(e,r.installablePath),{archivePath:r.archivePath,installablePath:r.installablePath,bundleId:r.bundleId,appName:r.appName,launchTarget:r.bundleId}}finally{await r.cleanup()}}async function Vt(e,t,n){return await Y(e,async()=>{let{bundleId:r}=await zt(e,t);return await Bt(e,n,{appIdentifierHint:t}),{bundleId:r}})}async function Ht(e,t){await Y(e,async()=>{if(e.kind!==`simulator`){await y([`device`,`install`,`app`,`--device`,e.id,t],{action:`install iOS app`,deviceId:e.id},{timeoutMs:pe});return}await E(e),await q(e,[`install`,e.id,t])})}async function Ut(e){return c(e)?await it():(T(e,`clipboard`),await E(e),n(await q(e,[`pbpaste`,e.id],{allowFailure:!0}),`Failed to read iOS simulator clipboard`).stdout.replace(/\r\n/g,`
4
- `).replace(/\n$/,``))}async function Wt(e,t){if(c(e)){await at(t);return}T(e,`clipboard`),await E(e),n(await q(e,[`pbcopy`,e.id],{allowFailure:!0,stdin:t}),`Failed to write iOS simulator clipboard`)}async function Gt(e,t,n){T(e,`push`),await E(e);let r=await D.mkdtemp(k.join(O.tmpdir(),`agent-device-ios-push-`)),i=k.join(r,`payload.apns`);try{await D.writeFile(i,`${JSON.stringify(n)}\n`,`utf8`),await q(e,[`push`,e.id,t,i])}finally{await D.rm(r,{recursive:!0,force:!0})}}let $=null,Kt;async function qt(e,n,r,i,a){if(c(e)){let e=n.toLowerCase();if(e===`appearance`){await st(r);return}if(e===`permission`){let e=C(r);if(e===`deny`)throw new t(`INVALID_ARGS`,s(`permission`));return await ce(e,Yt(a?.permissionTarget))}throw new t(`INVALID_ARGS`,s(n))}T(e,`settings`),await E(e);let l=n.toLowerCase();switch(l){case`clear-app-state`:{if(r.toLowerCase()!==`clear`)throw new t(`INVALID_ARGS`,`settings clear-app-state only supports clear.`);if(!i)throw new t(`INVALID_ARGS`,`settings clear-app-state requires an app id or an active app session.`);let n=await Jt(e,i);return{bundleId:n.bundleId,containerPath:n.containerPath,cleared:!0}}case`wifi`:{let t=w(r)?`active`:`failed`;await q(e,[`status_bar`,e.id,`override`,`--wifiMode`,t]),N(e);return}case`airplane`:w(r)?(await q(e,[`status_bar`,e.id,`override`,`--dataNetwork`,`hide`,`--wifiMode`,`failed`,`--wifiBars`,`0`,`--cellularMode`,`failed`,`--cellularBars`,`0`,`--operatorName`,``]),N(e)):(await q(e,[`status_bar`,e.id,`clear`]),P(e));return;case`location`:{if(r.toLowerCase()===`set`){let{latitude:t,longitude:n}=o(a);return await q(e,[`location`,e.id,`set`,`${t},${n}`]),{latitude:t,longitude:n}}let n=w(r);if(!i)throw new t(`INVALID_ARGS`,`location setting requires an active app in session`);let s=n?`grant`:`revoke`;await q(e,[`privacy`,e.id,s,`location`,i]);return}case`faceid`:case`touchid`:{let t=l,n=Qt[t];await sn(e,on(r,t),{settingName:t,label:n.label,modalityAliases:n.modalityAliases});return}case`appearance`:{let t=await Xt(e,r);await q(e,[`ui`,e.id,`appearance`,t]);return}case`permission`:if(!i)throw new t(`INVALID_ARGS`,`permission setting requires an active app in session`);await en(e,$t(C(r)),an(a?.permissionTarget,a?.permissionMode),i);return;default:throw new t(`INVALID_ARGS`,`Unsupported setting: ${n}`)}}async function Jt(e,r){if(!l(e)||e.kind!==`simulator`)throw new t(`UNSUPPORTED_OPERATION`,`Clearing app state is currently supported only on iOS simulators.`);let i=await X(e,r);await E(e),await Et(e,i);let a=n(await q(e,[`get_app_container`,e.id,i,`data`],{allowFailure:!0}),`simctl get_app_container failed for ${i}`).stdout.trim();if(!a)throw new t(`COMMAND_FAILED`,`simctl get_app_container returned an empty data container path for ${i}`);let o=await D.readdir(a);return await Promise.all(o.map(e=>D.rm(k.join(a,e),{recursive:!0,force:!0}))),{bundleId:i,containerPath:a}}function Yt(e){let n=e?.trim().toLowerCase();if(n===`accessibility`||n===`screen-recording`||n===`input-monitoring`)return n;throw new t(`INVALID_ARGS`,`Unsupported macOS permission target. Use accessibility|screen-recording|input-monitoring.`)}async function Xt(e,r){let i=S(r);if(i!==`toggle`)return i;let a=n(await q(e,[`ui`,e.id,`appearance`],{allowFailure:!0}),`Failed to read current iOS appearance`),o=Zt(a.stdout,a.stderr);if(!o)throw new t(`COMMAND_FAILED`,`Unable to determine current iOS appearance for toggle`,{stdout:a.stdout,stderr:a.stderr});return o===`dark`?`light`:`dark`}function Zt(e,t){let n=/\b(light|dark|unsupported|unknown)\b/i.exec(`${e}\n${t}`);if(!n)return null;let r=n[1]?.toLowerCase();return r===`dark`?`dark`:r===`light`?`light`:null}const Qt={faceid:{label:`Face ID`,modalityAliases:[`face`]},touchid:{label:`Touch ID`,modalityAliases:[`finger`,`touch`]}};function $t(e){return e===`deny`?`revoke`:e}async function en(e,n,r,i){let a=await nn(e);if(!a.has(r))throw new t(`UNSUPPORTED_OPERATION`,`iOS simctl privacy does not support service "${r}" on this runtime.`,{deviceId:e.id,appBundleId:i,hint:`Supported services: ${Array.from(a).sort().join(`, `)}`});let o=[`privacy`,e.id,n,r,i],s=r===`notifications`;if(!(n===`reset`&&s))try{await q(e,o);return}catch(n){throw s&&tn(n)?new t(`UNSUPPORTED_OPERATION`,`iOS simulator does not support setting notifications permission via simctl privacy on this runtime.`,{deviceId:e.id,appBundleId:i,hint:`Use reset notifications for reprompt behavior, or toggle notifications manually in Settings.`}):n}try{await q(e,o);return}catch(e){if(!tn(e))throw e}try{await q(e,[`privacy`,e.id,`reset`,`all`,i])}catch(n){throw new t(`COMMAND_FAILED`,`iOS simulator blocked direct notifications reset. Fallback reset-all also failed.`,{deviceId:e.id,appBundleId:i,hint:`Use reinstall to force a fresh notifications prompt, or reset simulator content and settings.`},n instanceof Error?n:void 0)}}function tn(e){if(!(e instanceof t)||e.code!==`COMMAND_FAILED`)return!1;let n=String(e.details?.stderr??``).toLowerCase();return(n.includes(`failed to grant access`)||n.includes(`failed to revoke access`)||n.includes(`failed to reset access`))&&n.includes(`operation not permitted`)}async function nn(e){let n=ie(e.simulatorSetPath),r=`${process.env.PATH??``}::${n??``}`;if($&&Kt===r)return $;let i=await q(e,[`privacy`,`help`],{allowFailure:!0}),a=rn(`${i.stdout}\n${i.stderr}`);if(a.size===0)throw new t(`COMMAND_FAILED`,`Unable to determine supported simctl privacy services`,{stdout:i.stdout,stderr:i.stderr,exitCode:i.exitCode,hint:"Run `xcrun simctl privacy help` manually to verify available services for this runtime."});return $=a,Kt=r,a}function rn(e){let t=new Set,n=!1;for(let r of e.split(`
5
- `)){let e=r.trim();if(!e)continue;if(e===`service`){n=!0;continue}if(!n)continue;if(e.startsWith(`bundle identifier`))break;let i=/^([a-z-]+)\s+-\s+/.exec(e)?.[1];i!==void 0&&t.add(i)}return t}function an(e,n){let r=Ce(e);if(r!==`photos`&&n?.trim())throw new t(`INVALID_ARGS`,`Permission mode is only supported for photos. Received: ${n}.`);if(r===`camera`)return`camera`;if(r===`microphone`)return`microphone`;if(r===`contacts`)return`contacts`;if(r===`contacts-limited`)return`contacts-limited`;if(r===`notifications`)return`notifications`;if(r===`calendar`)return`calendar`;if(r===`location`)return`location`;if(r===`location-always`)return`location-always`;if(r===`media-library`)return`media-library`;if(r===`motion`)return`motion`;if(r===`reminders`)return`reminders`;if(r===`siri`)return`siri`;if(r===`photos`){let e=n?.trim().toLowerCase();if(!e||e===`full`)return`photos`;if(e===`limited`)return`photos-add`;throw new t(`INVALID_ARGS`,`Invalid photos mode: ${n}. Use full|limited.`)}throw new t(`INVALID_ARGS`,`Unsupported permission target: ${e}. Use camera|microphone|photos|contacts|contacts-limited|notifications|calendar|location|location-always|media-library|motion|reminders|siri.`)}function on(e,n){let r=e.trim().toLowerCase();if(r===`match`)return`match`;if(r===`nonmatch`)return`nonmatch`;if(r===`enroll`)return`enroll`;if(r===`unenroll`)return`unenroll`;throw new t(`INVALID_ARGS`,`Invalid ${n} state: ${e}. Use match|nonmatch|enroll|unenroll.`)}async function sn(e,n,r){let i=cn(e.id,n,r.modalityAliases),a=[];for(let t of i){let n=K(e,t),r=await g(n,{allowFailure:!0});if(r.exitCode===0)return;a.push({args:n,stderr:r.stderr,stdout:r.stdout,exitCode:r.exitCode})}let o=Se(a);throw a.length>0&&a.every(e=>ln(e.stdout,e.stderr))?new t(`UNSUPPORTED_OPERATION`,`${r.label} simulation is not supported on this simulator runtime.`,{deviceId:e.id,action:n,setting:r.settingName,attempts:o}):new t(`COMMAND_FAILED`,`Failed to simulate ${r.settingName}.`,{deviceId:e.id,action:n,setting:r.settingName,attempts:o})}function cn(e,t,n){let r=n.length>0?n:[`face`];switch(t){case`match`:return r.flatMap(t=>[[`biometric`,e,`match`,t],[`biometric`,`match`,e,t]]);case`nonmatch`:return r.flatMap(t=>[[`biometric`,e,`nonmatch`,t],[`biometric`,e,`nomatch`,t],[`biometric`,`nonmatch`,e,t],[`biometric`,`nomatch`,e,t]]);case`enroll`:return[[`biometric`,e,`enroll`,`yes`],[`biometric`,e,`enroll`,`1`],[`biometric`,`enroll`,e,`yes`],[`biometric`,`enroll`,e,`1`]];case`unenroll`:return[[`biometric`,e,`enroll`,`no`],[`biometric`,e,`enroll`,`0`],[`biometric`,`enroll`,e,`no`],[`biometric`,`enroll`,e,`0`]]}}function ln(e,t){let n=`${e}\n${t}`.toLowerCase();return n.includes(`unrecognized subcommand`)||n.includes(`unknown subcommand`)||n.includes(`not supported`)||n.includes(`unavailable`)||n.includes(`biometric`)&&n.includes(`invalid`)}var un=e({installIosApp:()=>Bt,installIosInstallablePath:()=>Ht,listIosApps:()=>mt,pushIosNotification:()=>Gt,reinstallIosApp:()=>Vt,resolveIosApp:()=>X,resolveIosSimulatorDeepLinkBundleId:()=>ft});export{Mt as a,Tt as c,Wt as i,mt as l,qt as n,Et as o,Ut as r,Ct as s,un as t,Re as u};
1
+ import{t as e}from"./rolldown-runtime.js";import{_ as t}from"./sdk-contracts.js";import{h as n,j as r,p as i}from"./sdk-android-adb.js";import{Wt as a,qt as o}from"./tv-remote.js";import{c as s,s as c}from"./sdk-device.js";import{s as l}from"./runtime.js";import{d as u,f as ee,p as d}from"./logcat.js";import{l as f,o as te,s as p}from"./boot-diagnostics.js";import{M as ne,N as m,w as h}from"./runner-disposal.js";import{n as re,t as g}from"./launch-console.js";import{a as _,i as v}from"./tool-provider.js";import{a as ie}from"./helper.js";import{a as ae}from"./runner-client.js";import{a as y,c as oe,d as se,i as ce,l as le,t as b,u as ue}from"./config.js";import{Et as de,Tt as fe,ot as x,st as pe}from"./internal/daemon.js";import{a as me,i as he,n as ge,r as S,t as C}from"./setting-state.js";import{i as w,n as _e,t as T}from"./simulator.js";import{a as ve,c as E,d as ye,f as be,i as D,l as xe,n as O,o as k,p as Se,r as Ce,s as A,u as we}from"./app-resolution.js";import{n as Te}from"./install-artifact.js";import{promises as j}from"node:fs";import Ee from"node:os";import M from"node:path";function N(e){if(!(e instanceof t))return{reason:e instanceof Error?e.message:String(e)};let n=e.details??{},r=Array.isArray(n.args)?n.args.filter(e=>typeof e==`string`).join(` `):void 0;return{errorCode:e.code,reason:e.message,timeoutMs:typeof n.timeoutMs==`number`?n.timeoutMs:void 0,exitCode:typeof n.exitCode==`number`?n.exitCode:void 0,stderr:typeof n.stderr==`string`&&n.stderr.trim()?n.stderr:void 0,stdout:typeof n.stdout==`string`&&n.stdout.trim()?n.stdout:void 0,commandArgs:r}}const De=[`--time`,`9:41`,`--dataNetwork`,`wifi`,`--wifiMode`,`active`,`--wifiBars`,`3`,`--batteryState`,`charged`,`--batteryLevel`,`100`],Oe={0:`hide`,1:`wifi`,6:`3g`,7:`4g`,8:`lte`,9:`lte-a`,10:`lte+`,11:`5g`,12:`5g+`,13:`5g-uwb`,14:`5g-uc`},ke={1:`searching`,2:`failed`,3:`active`},Ae={0:`notSupported`,1:`searching`,2:`failed`,3:`active`};function P(e,t,n){return m(e,t,n)}const F=new Set;async function je(e){let t=null,n=!1;if(Ne(e))n=!0;else try{t=await Pe(e),n=!0}catch(t){V(e,`snapshot_failed`,t)}try{(!n||t)&&await z(e),await B(e,De)}catch(t){V(e,`prepare_failed`,t)}return async()=>{await Me(e,n?t:null)}}async function Me(e,t){if(await z(e),!t){L(e);return}await B(e,Ie(t)),I(e)}function I(e){F.delete(R(e))}function L(e){F.add(R(e))}function Ne(e){return F.has(R(e))}function R(e){return`${e.platform}:${e.kind}:${e.id}`}async function Pe(e){return Fe(n(await P(e,[`status_bar`,e.id,`list`],{allowFailure:!0}),`Failed to read simulator status bar overrides`).stdout)}async function z(e){await P(e,[`status_bar`,e.id,`clear`])}async function B(e,t){t.length!==0&&await P(e,[`status_bar`,e.id,`override`,...t])}function Fe(e){let n={},r=e.split(`
2
+ `).map(e=>e.trim()).filter(e=>e.length>0&&e!==`Current Status Bar Overrides:`&&!/^=+$/.test(e));for(let e of r){let r=/^DataNetworkType:\s+(\d+)$/.exec(e);if(r){let e=Number(r[1]),i=Oe[e];if(!i)throw new t(`COMMAND_FAILED`,`Unsupported simulator data network type: ${e}`);n.dataNetwork=i;continue}let i=/^WiFi Mode:\s+(\d+),\s+WiFi Bars:\s+(\d+)$/.exec(e);if(i){let e=ke[Number(i[1])];e&&(n.wifiMode=e),n.wifiBars=i[2];continue}let a=/^Cell Mode:\s+(\d+),\s+Cell Bars:\s+(\d+)$/.exec(e);if(a){let e=Number(a[1]),r=Ae[e];if(!r)throw new t(`COMMAND_FAILED`,`Unsupported simulator cellular mode: ${e}`);n.cellularMode=r,n.cellularBars=a[2];continue}let o=/^Operator Name:\s*(.*)$/.exec(e);if(o){n.operatorName=o[1]??``;continue}}return Object.keys(n).length===0?null:n}function Ie(e){let t=[];return e.dataNetwork&&t.push(`--dataNetwork`,e.dataNetwork),e.wifiMode&&t.push(`--wifiMode`,e.wifiMode),e.wifiBars!==void 0&&(e.dataNetwork===`wifi`||e.wifiMode)&&t.push(`--wifiBars`,e.wifiBars),e.cellularMode&&t.push(`--cellularMode`,e.cellularMode),e.cellularBars!==void 0&&(e.cellularMode||Le(e.dataNetwork)||e.operatorName!==void 0)&&t.push(`--cellularBars`,e.cellularBars),e.operatorName!==void 0&&t.push(`--operatorName`,e.operatorName),t}function Le(e){return!!(e&&e!==`hide`&&e!==`wifi`)}function V(e,t,n){r({level:`warn`,phase:`ios_screenshot_status_bar_${t}`,data:{platform:e.platform,deviceKind:e.kind,deviceId:e.id,...N(n)}})}function H(e,t,n){return m(e,t,n)}const Re={ensureBooted:T,prepareStatusBarForScreenshot:je,captureWithRetry:Ve,normalizeDensity:Xe,captureWithRunner:G,shouldFallbackToRunner:Je},U=new Map,W=new Map;async function ze(e,t,n={}){if(s(e)){await G(e,t,n.appBundleId,n.fullscreen,n.runnerOptions);return}if(e.kind===`simulator`){await Be(e,t,n);return}try{await x([`device`,`screenshot`,`--device`,e.id,t],{action:`capture iOS screenshot`,deviceId:e.id});return}catch(t){if(!qe(t))throw t;We(e,`devicectl_screenshot`,t)}await G(e,t,n.appBundleId,n.fullscreen,n.runnerOptions)}async function Be(e,n,r={}){if(e.kind!==`simulator`)throw new t(`UNSUPPORTED_OPERATION`,`Simulator screenshot fallback flow supports only iOS simulators`);let i={...Re,...r.deps??{}};r.skipIosSimulatorBootCheck||await i.ensureBooted(e);let a=async()=>{};if(r.normalizeStatusBar===!0)try{a=await i.prepareStatusBarForScreenshot(e)}catch(t){Ge(e,`prepare_failed`,t)}try{try{await i.captureWithRetry(e,n),await i.normalizeDensity(e,n,r.pixelDensity);return}catch(t){let a=t;if(r.skipIosSimulatorBootCheck&&Ye(t)){await i.ensureBooted(e);try{await i.captureWithRetry(e,n),await i.normalizeDensity(e,n,r.pixelDensity);return}catch(e){a=e}}if(!i.shouldFallbackToRunner(a))throw a;We(e,`simctl_screenshot`,a)}await i.captureWithRunner(e,n,r.appBundleId,r.fullscreen,r.runnerOptions),await i.normalizeDensity(e,n,r.pixelDensity)}finally{await a().catch(t=>Ge(e,`restore_failed`,t))}}async function Ve(e,t){let n=p.fromTimeoutMs(ue);await f(async({deadline:n})=>{await H(e,[`io`,e.id,`screenshot`,t],{timeoutMs:Math.max(1e3,n?.remainingMs()??2e4)})},{maxAttempts:5,baseDelayMs:oe,maxDelayMs:le,jitter:.2,shouldRetry:e=>Je(e)},{deadline:n,phase:`ios_simulator_screenshot`})}async function G(e,n,r,i,a){let o=(await ae(e,{command:`screenshot`,appBundleId:r,fullscreen:i},a)).message;if(!o)throw new t(`COMMAND_FAILED`,`Failed to capture iOS screenshot: runner returned no file path`);if(s(e)){await j.copyFile(o,n);return}if(e.kind===`simulator`){await Ue(e,o,n);return}await He(e,o,n)}async function He(e,n,r){let i=p.fromTimeoutMs(y),a={exitCode:1,stdout:``,stderr:``};for(let t of h)if(a=await _([`devicectl`,`device`,`copy`,`from`,`--device`,e.id,`--source`,n,`--destination`,r,`--domain-type`,`appDataContainer`,`--domain-identifier`,t],{allowFailure:!0,timeoutMs:q(i,y,`runner screenshot copy`)}),a.exitCode===0)return;throw new t(`COMMAND_FAILED`,`Failed to capture iOS screenshot: ${a.stderr.trim()||a.stdout.trim()||`devicectl exited with code ${a.exitCode}`}`)}async function Ue(e,n,r){let i=p.fromTimeoutMs(y),a=`Unable to locate runner container for simulator screenshot`,o=W.get(e.id);if(o){let t=await K(o,n,r);if(t.copied)return;a=t.error,W.delete(e.id)}for(let t of h){let o=await H(e,[`get_app_container`,e.id,t,`data`],{allowFailure:!0,timeoutMs:q(i,y,`runner screenshot container lookup`)});if(o.exitCode!==0){let e=o.stderr.trim();e&&(a=e);continue}let s=o.stdout.trim();if(!s){a=`simctl get_app_container returned empty output`;continue}let c=await K(s,n,r);if(c.copied){W.set(e.id,s);return}a=c.error}throw new t(`COMMAND_FAILED`,`Failed to capture iOS screenshot: ${a}`)}async function K(e,t,n){let r=`Runner screenshot was not found in the simulator container`;for(let i of Ke(e,t))try{return await j.copyFile(i,n),{copied:!0}}catch(e){r=e instanceof Error?e.message:String(e)}return{copied:!1,error:r}}function q(e,n,r){let i=e.remainingMs();if(i>0)return i;throw new t(`COMMAND_FAILED`,`iOS ${r} timed out after ${n}ms`,{timeoutMs:n,step:r})}function We(e,t,n){let i=N(n);r({level:`warn`,phase:`ios_screenshot_fallback`,data:{platform:e.platform,deviceKind:e.kind,deviceId:e.id,from:t,to:`runner`,...i}})}function Ge(e,t,n){r({level:`warn`,phase:`ios_screenshot_status_bar_${t}`,data:{platform:e.platform,deviceKind:e.kind,deviceId:e.id,...N(n)}})}function Ke(e,t){let n=M.resolve(e),r=t.trim();if(!r)return[];let i=[],a=new Set,o=e=>{let t=M.normalize(e);a.has(t)||(a.add(t),i.push(t))},s=r.replace(/^\/+/,``),c=s.replace(/\\/g,`/`);if(s&&o(M.join(n,s)),M.isAbsolute(r)&&o(M.normalize(r)),c.startsWith(`tmp/`))o(M.join(n,c));else{let e=c.lastIndexOf(`/tmp/`);if(e>=0){let t=c.slice(e+1);o(M.join(n,t))}}let l=M.basename(r);return l&&o(M.join(n,`tmp`,l)),i}function qe(e){if(!(e instanceof t)||e.code!==`COMMAND_FAILED`)return!1;let n=J(e);return n.includes(`unknown option '--device'`)||n.includes(`unknown subcommand`)&&n.includes(`screenshot`)||n.includes(`unrecognized subcommand`)&&n.includes(`screenshot`)}function Je(e){if(!(e instanceof t)||e.code!==`COMMAND_FAILED`)return!1;let n=J(e);return n.includes(`timeout waiting for screen surfaces`)||n.includes(`nsposixerrordomain`)&&n.includes(`code=60`)&&n.includes(`screenshot`)||n.includes(`timed out`)&&n.includes(`screenshot`)}function Ye(e){if(!(e instanceof t)||e.code!==`COMMAND_FAILED`)return!1;let n=J(e);return n.includes(`not booted`)||n.includes(`current state: shutdown`)||n.includes(`current state is shutdown`)||n.includes(`current state=shutdown`)||n.includes(`state: shutdown`)||n.includes(`state=shutdown`)}function J(e){let t=e.details??{},n=typeof t.stdout==`string`?t.stdout:``,r=typeof t.stderr==`string`?t.stderr:``,i=Array.isArray(t.args)?t.args.filter(e=>typeof e==`string`).join(` `):``;return`${e.message}\n${n}\n${r}\n${i}`.toLowerCase()}async function Xe(e,t,n){let r=await Ze(e),i=fe(await de(t),r,n);i&&await l(t,i.width,i.height)}async function Ze(e){let n=U.get(e.id);if(n!==void 0)return n;let r=await H(e,[`getenv`,e.id,`SIMULATOR_MAINSCREEN_SCALE`],{timeoutMs:5e3}),i=Number(r.stdout.trim());if(!Number.isFinite(i)||i<=0)throw new t(`COMMAND_FAILED`,`Failed to read iOS simulator screenshot scale from SIMULATOR_MAINSCREEN_SCALE`);return U.set(e.id,i),i}function Qe(e){if(!(e instanceof t)||e.code!==`COMMAND_FAILED`)return!1;let n=e.details??{};if(n.exitCode!==4)return!1;let r=String(n.stderr??``).toLowerCase();return r.includes(`fbsopenapplicationserviceerrordomain`)&&r.includes(`the request to open`)}async function $e(e,t){let n=await _(ne(e,[`get_app_container`,e.id,t]),{allowFailure:!0});if(n.exitCode!==0)return{installed:!1};let r=n.stdout.trim();if(!r)return{installed:!1};let i=await v(`plutil`,[`-extract`,`CFBundleExecutable`,`raw`,`-o`,`-`,`${r}/Info.plist`],{allowFailure:!0});if(i.exitCode!==0||!i.stdout.trim())return{installed:!0};let a=await v(`otool`,[`-l`,`${r}/${i.stdout.trim()}`],{allowFailure:!0});if(a.exitCode!==0)return{installed:!0};let o=a.stdout.toLowerCase();return{installed:!0,simulatorCompatible:o.includes(`iossimulator`)||o.includes(`platform 7`)}}function et(e){return e.installed?e.simulatorCompatible===!1?`ARCH_MISMATCH`:`PERSISTENT_LAUNCH_FAIL`:`APP_NOT_INSTALLED`}function tt(e){switch(e){case`ARCH_MISMATCH`:return`The app binary was not built for the simulator platform. Rebuild with a simulator destination or use a physical device.`;case`APP_NOT_INSTALLED`:return`The app bundle is not installed on this simulator. Run install before open.`;case`PERSISTENT_LAUNCH_FAIL`:return`The simulator repeatedly refused to launch the app. Inspect crash logs in Console.app or ~/Library/Logs/DiagnosticReports/ and consider reinstalling the app.`;default:return`The simulator failed to launch the app. Retry with --debug and inspect diagnostics log for details.`}}const Y=25e3;async function nt(e,n,r){let i=r?.launchConsole?.trim(),a=r?.launchArgs;if(i&&(!c(e)||e.kind!==`simulator`))throw new t(`UNSUPPORTED_OPERATION`,re);if(s(e)){if(a&&a.length>0)throw new t(`UNSUPPORTED_OPERATION`,`--launch-args is not supported on macOS; launch arguments are currently iOS-only.`);await we(e,n,r);return}let o=r?.url?.trim();if(o){if(i)throw new t(`INVALID_ARGS`,g);if(!u(o))throw new t(`INVALID_ARGS`,`open <app> <url> requires a valid URL target`);if(e.kind===`simulator`){(a||ee(o))&&await ot(e,r?.appBundleId??await D(e,n),{...a?{launchArgs:a}:{}}),await rt(e,o,void 0);return}let s=d(r?.appBundleId??await D(e,n),o);if(!s)throw new t(`INVALID_ARGS`,`Deep link open on iOS devices requires an active app bundle ID. Open the app first, then open the URL.`);await X(e,s,{payloadUrl:o,launchArgs:a});return}let l=n.trim();if(u(l)){if(i)throw new t(`INVALID_ARGS`,g);if(e.kind===`simulator`){await rt(e,l,a);return}let n=d(r?.appBundleId,l);if(!n)throw new t(`INVALID_ARGS`,`Deep link open on iOS devices requires an active app bundle ID. Open the app first, then open the URL.`);await X(e,n,{payloadUrl:l,launchArgs:a});return}let f=r?.appBundleId??await D(e,n);if(e.kind===`simulator`){await ot(e,f,{...i?{launchConsole:i}:{},...a?{launchArgs:a}:{},...r?.terminateRunningApp?{terminateRunningApp:!0}:{}});return}await X(e,f,{launchArgs:a})}async function rt(e,n,r){if(r&&r.length>0)throw new t(`INVALID_ARGS`,`--launch-args is not supported with iOS simulator URL opens (simctl openurl ignores launch args). Launch the app first with --launch-args, then issue the URL open in a separate call.`);await T(e),await A(e,[`openurl`,e.id,n])}async function it(e){s(e)||e.kind===`simulator`&&await _e(e)!==`Booted`&&await T(e)}async function at(e,n){if(s(e)){await xe(e,n);return}let r=await D(e,n);if(e.kind===`simulator`){await T(e);let n=E(e,[`terminate`,e.id,r]),a=await _(n,{allowFailure:!0,timeoutMs:se});if(a.exitCode!==0){if(a.stderr.toLowerCase().includes(`found nothing to terminate`))return;throw new t(`COMMAND_FAILED`,`xcrun exited with code ${a.exitCode}`,i(a,{cmd:`xcrun`,args:n}))}return}await pe(e,r)}async function ot(e,n,r){await T(e);let a=0,o=p.fromTimeoutMs(b);try{await f(async({deadline:a})=>{if(a?.isExpired())throw new t(`COMMAND_FAILED`,`App launch deadline exceeded`,{timeoutMs:b});let o=E(e,st(e.id,n,r)),s=r?.launchConsole?await ct(o,r.launchConsole):await _(o,{allowFailure:!0});if(s.exitCode!==0)throw new t(`COMMAND_FAILED`,`xcrun exited with code ${s.exitCode}`,i(s,{cmd:`xcrun`,args:o}))},{maxAttempts:10,baseDelayMs:1e3,maxDelayMs:5e3,jitter:.2,shouldRetry(e){return Qe(e)?(a+=1,a<3):!1}},{deadline:o})}catch(t){if(Qe(t)){let r=t,i=et(await $e(e,n));r.details={...r.details,hint:tt(i)}}throw t}}function st(e,t,n){let r=[`launch`];return n?.launchConsole&&r.push(`--console-pty`),n?.terminateRunningApp&&r.push(`--terminate-running-process`),r.push(e,t),n?.launchArgs&&n.launchArgs.length>0&&r.push(...n.launchArgs),r}async function ct(e,n){await j.mkdir(M.dirname(n),{recursive:!0});try{let t=await _(e,{allowFailure:!0,timeoutMs:Y});return await lt(n,t.stdout,t.stderr),t}catch(e){let i=(e instanceof t?e:void 0)?.details;if(i?.timeoutMs===Y){let e=typeof i.stdout==`string`?i.stdout:``,t=typeof i.stderr==`string`?i.stderr:``;return await lt(n,e,t),r({level:`warn`,phase:`ios_simulator_launch_console_capture_timeout`,data:{timeoutMs:Y,logPath:n,stdoutBytes:Buffer.byteLength(e),stderrBytes:Buffer.byteLength(t)}}),{stdout:e,stderr:t,exitCode:0}}throw e}}async function lt(e,t,n){await j.writeFile(e,ut(t,n),`utf8`)}function ut(e,t){return!e||!t||e.endsWith(`
3
+ `)||e.endsWith(`\r`)?`${e}${t}`:`${e}\n${t}`}async function X(e,t,n){let r=[`device`,`process`,`launch`,`--device`,e.id,t];n?.payloadUrl&&r.push(`--payload-url`,n.payloadUrl),n?.launchArgs&&n.launchArgs.length>0&&r.push(`--`,...n.launchArgs),await x(r,{action:`launch iOS app`,deviceId:e.id})}async function dt(e,n){return await O(e,async()=>{let r=await D(e,n);if(e.kind!==`simulator`)return await x([`device`,`uninstall`,`app`,`--device`,e.id,r],{action:`uninstall iOS app ${r}`,deviceId:e.id},{tolerateOutput:(e,t)=>k(`${e}\n${t}`.toLowerCase())}),{bundleId:r};await T(e);let a=await A(e,[`uninstall`,e.id,r],{allowFailure:!0});if(a.exitCode!==0&&!k(`${a.stdout}\n${a.stderr}`.toLowerCase()))throw new t(`COMMAND_FAILED`,`simctl uninstall failed for ${r}`,i(a));return{bundleId:r}})}async function ft(e,t,n){let r=await Te({kind:`path`,path:t},n);try{return await Z(e,r.installablePath),{archivePath:r.archivePath,installablePath:r.installablePath,bundleId:r.bundleId,appName:r.appName,launchTarget:r.bundleId}}finally{await r.cleanup()}}async function pt(e,t,n){return await O(e,async()=>{let{bundleId:r}=await dt(e,t);return await ft(e,n,{appIdentifierHint:t}),{bundleId:r}})}async function Z(e,t){await O(e,async()=>{if(e.kind!==`simulator`){await x([`device`,`install`,`app`,`--device`,e.id,t],{action:`install iOS app`,deviceId:e.id},{timeoutMs:ce});return}await T(e),await A(e,[`install`,e.id,t])})}async function mt(e){return s(e)?await ye():(w(e,`clipboard`),await T(e),n(await A(e,[`pbpaste`,e.id],{allowFailure:!0}),`Failed to read iOS simulator clipboard`).stdout.replace(/\r\n/g,`
4
+ `).replace(/\n$/,``))}async function ht(e,t){if(s(e)){await Se(t);return}w(e,`clipboard`),await T(e),n(await A(e,[`pbcopy`,e.id],{allowFailure:!0,stdin:t}),`Failed to write iOS simulator clipboard`)}async function gt(e,t,n){w(e,`push`),await T(e);let r=await j.mkdtemp(M.join(Ee.tmpdir(),`agent-device-ios-push-`)),i=M.join(r,`payload.apns`);try{await j.writeFile(i,`${JSON.stringify(n)}\n`,`utf8`),await A(e,[`push`,e.id,t,i])}finally{await j.rm(r,{recursive:!0,force:!0})}}let Q=null,_t;async function vt(e,n,r,i,c){if(s(e)){let e=n.toLowerCase();if(e===`appearance`){await be(r);return}if(e===`permission`){let e=S(r);if(e===`deny`)throw new t(`INVALID_ARGS`,o(`permission`));return await ie(e,bt(c?.permissionTarget))}throw new t(`INVALID_ARGS`,o(n))}w(e,`settings`),await T(e);let l=n.toLowerCase();switch(l){case`clear-app-state`:{if(r.toLowerCase()!==`clear`)throw new t(`INVALID_ARGS`,`settings clear-app-state only supports clear.`);if(!i)throw new t(`INVALID_ARGS`,`settings clear-app-state requires an app id or an active app session.`);let n=await yt(e,i);return{bundleId:n.bundleId,containerPath:n.containerPath,cleared:!0}}case`wifi`:{let t=C(r)?`active`:`failed`;await A(e,[`status_bar`,e.id,`override`,`--wifiMode`,t]),I(e);return}case`airplane`:C(r)?(await A(e,[`status_bar`,e.id,`override`,`--dataNetwork`,`hide`,`--wifiMode`,`failed`,`--wifiBars`,`0`,`--cellularMode`,`failed`,`--cellularBars`,`0`,`--operatorName`,``]),I(e)):(await A(e,[`status_bar`,e.id,`clear`]),L(e));return;case`location`:{if(r.toLowerCase()===`set`){let{latitude:t,longitude:n}=a(c);return await A(e,[`location`,e.id,`set`,`${t},${n}`]),{latitude:t,longitude:n}}let n=C(r);if(!i)throw new t(`INVALID_ARGS`,`location setting requires an active app in session`);let o=n?`grant`:`revoke`;await A(e,[`privacy`,e.id,o,`location`,i]);return}case`faceid`:case`touchid`:{let t=l,n=Ct[t];await At(e,kt(r,t),{settingName:t,label:n.label,modalityAliases:n.modalityAliases});return}case`appearance`:{let t=await xt(e,r);await A(e,[`ui`,e.id,`appearance`,t]);return}case`permission`:if(!i)throw new t(`INVALID_ARGS`,`permission setting requires an active app in session`);await Tt(e,wt(S(r)),Ot(c?.permissionTarget,c?.permissionMode),i);return;default:throw new t(`INVALID_ARGS`,`Unsupported setting: ${n}`)}}async function yt(e,r){if(!c(e)||e.kind!==`simulator`)throw new t(`UNSUPPORTED_OPERATION`,`Clearing app state is currently supported only on iOS simulators.`);let i=await D(e,r);await T(e),await at(e,i);let a=n(await A(e,[`get_app_container`,e.id,i,`data`],{allowFailure:!0}),`simctl get_app_container failed for ${i}`).stdout.trim();if(!a)throw new t(`COMMAND_FAILED`,`simctl get_app_container returned an empty data container path for ${i}`);let o=await j.readdir(a);return await Promise.all(o.map(e=>j.rm(M.join(a,e),{recursive:!0,force:!0}))),{bundleId:i,containerPath:a}}function bt(e){let n=e?.trim().toLowerCase();if(n===`accessibility`||n===`screen-recording`||n===`input-monitoring`)return n;throw new t(`INVALID_ARGS`,`Unsupported macOS permission target. Use accessibility|screen-recording|input-monitoring.`)}async function xt(e,r){let i=ge(r);if(i!==`toggle`)return i;let a=n(await A(e,[`ui`,e.id,`appearance`],{allowFailure:!0}),`Failed to read current iOS appearance`),o=St(a.stdout,a.stderr);if(!o)throw new t(`COMMAND_FAILED`,`Unable to determine current iOS appearance for toggle`,{stdout:a.stdout,stderr:a.stderr});return o===`dark`?`light`:`dark`}function St(e,t){let n=/\b(light|dark|unsupported|unknown)\b/i.exec(`${e}\n${t}`);if(!n)return null;let r=n[1]?.toLowerCase();return r===`dark`?`dark`:r===`light`?`light`:null}const Ct={faceid:{label:`Face ID`,modalityAliases:[`face`]},touchid:{label:`Touch ID`,modalityAliases:[`finger`,`touch`]}};function wt(e){return e===`deny`?`revoke`:e}async function Tt(e,n,r,i){let a=await Et(e);if(!a.has(r))throw new t(`UNSUPPORTED_OPERATION`,`iOS simctl privacy does not support service "${r}" on this runtime.`,{deviceId:e.id,appBundleId:i,hint:`Supported services: ${Array.from(a).sort().join(`, `)}`});let o=[`privacy`,e.id,n,r,i],s=r===`notifications`;if(!(n===`reset`&&s))try{await A(e,o);return}catch(n){throw s&&$(n)?new t(`UNSUPPORTED_OPERATION`,`iOS simulator does not support setting notifications permission via simctl privacy on this runtime.`,{deviceId:e.id,appBundleId:i,hint:`Use reset notifications for reprompt behavior, or toggle notifications manually in Settings.`}):n}try{await A(e,o);return}catch(e){if(!$(e))throw e}try{await A(e,[`privacy`,e.id,`reset`,`all`,i])}catch(n){throw new t(`COMMAND_FAILED`,`iOS simulator blocked direct notifications reset. Fallback reset-all also failed.`,{deviceId:e.id,appBundleId:i,hint:`Use reinstall to force a fresh notifications prompt, or reset simulator content and settings.`},n instanceof Error?n:void 0)}}function $(e){if(!(e instanceof t)||e.code!==`COMMAND_FAILED`)return!1;let n=String(e.details?.stderr??``).toLowerCase();return(n.includes(`failed to grant access`)||n.includes(`failed to revoke access`)||n.includes(`failed to reset access`))&&n.includes(`operation not permitted`)}async function Et(e){let n=te(e.simulatorSetPath),r=`${process.env.PATH??``}::${n??``}`;if(Q&&_t===r)return Q;let i=await A(e,[`privacy`,`help`],{allowFailure:!0}),a=Dt(`${i.stdout}\n${i.stderr}`);if(a.size===0)throw new t(`COMMAND_FAILED`,`Unable to determine supported simctl privacy services`,{stdout:i.stdout,stderr:i.stderr,exitCode:i.exitCode,hint:"Run `xcrun simctl privacy help` manually to verify available services for this runtime."});return Q=a,_t=r,a}function Dt(e){let t=new Set,n=!1;for(let r of e.split(`
5
+ `)){let e=r.trim();if(!e)continue;if(e===`service`){n=!0;continue}if(!n)continue;if(e.startsWith(`bundle identifier`))break;let i=/^([a-z-]+)\s+-\s+/.exec(e)?.[1];i!==void 0&&t.add(i)}return t}function Ot(e,n){let r=he(e);if(r!==`photos`&&n?.trim())throw new t(`INVALID_ARGS`,`Permission mode is only supported for photos. Received: ${n}.`);if(r===`camera`)return`camera`;if(r===`microphone`)return`microphone`;if(r===`contacts`)return`contacts`;if(r===`contacts-limited`)return`contacts-limited`;if(r===`notifications`)return`notifications`;if(r===`calendar`)return`calendar`;if(r===`location`)return`location`;if(r===`location-always`)return`location-always`;if(r===`media-library`)return`media-library`;if(r===`motion`)return`motion`;if(r===`reminders`)return`reminders`;if(r===`siri`)return`siri`;if(r===`photos`){let e=n?.trim().toLowerCase();if(!e||e===`full`)return`photos`;if(e===`limited`)return`photos-add`;throw new t(`INVALID_ARGS`,`Invalid photos mode: ${n}. Use full|limited.`)}throw new t(`INVALID_ARGS`,`Unsupported permission target: ${e}. Use camera|microphone|photos|contacts|contacts-limited|notifications|calendar|location|location-always|media-library|motion|reminders|siri.`)}function kt(e,n){let r=e.trim().toLowerCase();if(r===`match`)return`match`;if(r===`nonmatch`)return`nonmatch`;if(r===`enroll`)return`enroll`;if(r===`unenroll`)return`unenroll`;throw new t(`INVALID_ARGS`,`Invalid ${n} state: ${e}. Use match|nonmatch|enroll|unenroll.`)}async function At(e,n,r){let i=jt(e.id,n,r.modalityAliases),a=[];for(let t of i){let n=E(e,t),r=await _(n,{allowFailure:!0});if(r.exitCode===0)return;a.push({args:n,stderr:r.stderr,stdout:r.stdout,exitCode:r.exitCode})}let o=me(a);throw a.length>0&&a.every(e=>Mt(e.stdout,e.stderr))?new t(`UNSUPPORTED_OPERATION`,`${r.label} simulation is not supported on this simulator runtime.`,{deviceId:e.id,action:n,setting:r.settingName,attempts:o}):new t(`COMMAND_FAILED`,`Failed to simulate ${r.settingName}.`,{deviceId:e.id,action:n,setting:r.settingName,attempts:o})}function jt(e,t,n){let r=n.length>0?n:[`face`];switch(t){case`match`:return r.flatMap(t=>[[`biometric`,e,`match`,t],[`biometric`,`match`,e,t]]);case`nonmatch`:return r.flatMap(t=>[[`biometric`,e,`nonmatch`,t],[`biometric`,e,`nomatch`,t],[`biometric`,`nonmatch`,e,t],[`biometric`,`nomatch`,e,t]]);case`enroll`:return[[`biometric`,e,`enroll`,`yes`],[`biometric`,e,`enroll`,`1`],[`biometric`,`enroll`,e,`yes`],[`biometric`,`enroll`,e,`1`]];case`unenroll`:return[[`biometric`,e,`enroll`,`no`],[`biometric`,e,`enroll`,`0`],[`biometric`,`enroll`,e,`no`],[`biometric`,`enroll`,e,`0`]]}}function Mt(e,t){let n=`${e}\n${t}`.toLowerCase();return n.includes(`unrecognized subcommand`)||n.includes(`unknown subcommand`)||n.includes(`not supported`)||n.includes(`unavailable`)||n.includes(`biometric`)&&n.includes(`invalid`)}var Nt=e({installIosApp:()=>ft,installIosInstallablePath:()=>Z,listIosApps:()=>Ce,pushIosNotification:()=>gt,reinstallIosApp:()=>pt,resolveIosApp:()=>D,resolveIosSimulatorDeepLinkBundleId:()=>ve});export{at as a,G as c,ht as i,ze as l,vt as n,nt as o,mt as r,it as s,Nt as t};
@@ -0,0 +1,2 @@
1
+ import{_ as e,v as t}from"./sdk-contracts.js";import{j as n}from"./sdk-android-adb.js";function r(e){return[`1`,`true`,`yes`,`on`].includes((e??``).trim().toLowerCase())}const i={attempts:3,baseDelayMs:200,maxDelayMs:2e3,jitter:.2};var a=class e{startedAtMs;expiresAtMs;constructor(e,t){this.startedAtMs=e,this.expiresAtMs=e+Math.max(0,t)}static fromTimeoutMs(t,n=Date.now()){return new e(n,t)}remainingMs(e=Date.now()){return Math.max(0,this.expiresAtMs-e)}elapsedMs(e=Date.now()){return Math.max(0,e-this.startedAtMs)}isExpired(e=Date.now()){return this.remainingMs(e)<=0}};async function o(t,n={},r={}){let a={maxAttempts:n.maxAttempts??i.attempts,baseDelayMs:n.baseDelayMs??i.baseDelayMs,maxDelayMs:n.maxDelayMs??i.maxDelayMs,jitter:n.jitter??i.jitter,shouldRetry:n.shouldRetry},o;for(let n=1;n<=a.maxAttempts;n+=1){if(r.signal?.aborted)throw new e(`COMMAND_FAILED`,`request canceled`,{reason:`request_canceled`});if(r.deadline?.isExpired()&&n>1)break;try{let e=await t({attempt:n,maxAttempts:a.maxAttempts,deadline:r.deadline});return r.onEvent?.({phase:r.phase,event:`succeeded`,attempt:n,maxAttempts:a.maxAttempts,elapsedMs:r.deadline?.elapsedMs(),remainingMs:r.deadline?.remainingMs()}),u({phase:r.phase,event:`succeeded`,attempt:n,maxAttempts:a.maxAttempts,elapsedMs:r.deadline?.elapsedMs(),remainingMs:r.deadline?.remainingMs()}),e}catch(e){o=e;let t=r.classifyReason?.(e),i={phase:r.phase,event:`attempt_failed`,attempt:n,maxAttempts:a.maxAttempts,elapsedMs:r.deadline?.elapsedMs(),remainingMs:r.deadline?.remainingMs(),reason:t};if(r.onEvent?.(i),u(i),n>=a.maxAttempts||a.shouldRetry&&!a.shouldRetry(e,n))break;let s=c(a.baseDelayMs,a.maxDelayMs,a.jitter,n),d=r.deadline?Math.min(s,r.deadline.remainingMs()):s;if(d<=0)break;let f={phase:r.phase,event:`retry_scheduled`,attempt:n,maxAttempts:a.maxAttempts,delayMs:d,elapsedMs:r.deadline?.elapsedMs(),remainingMs:r.deadline?.remainingMs(),reason:t};r.onEvent?.(f),u(f),await l(d,r.signal)}}let s={phase:r.phase,event:`exhausted`,attempt:a.maxAttempts,maxAttempts:a.maxAttempts,elapsedMs:r.deadline?.elapsedMs(),remainingMs:r.deadline?.remainingMs(),reason:r.classifyReason?.(o)};throw r.onEvent?.(s),u(s),o||new e(`COMMAND_FAILED`,`retry failed`)}async function s(e,t={}){return o(()=>e(),{maxAttempts:t.attempts,baseDelayMs:t.baseDelayMs,maxDelayMs:t.maxDelayMs,jitter:t.jitter,shouldRetry:t.shouldRetry})}function c(e,t,n,r){let i=Math.min(t,e*2**(r-1)),a=i*n;return Math.max(0,i+(Math.random()*2-1)*a)}function l(e,t){return new Promise(n=>{if(t?.aborted){n();return}let r=!1,i=()=>{r||(r=!0,t&&t.removeEventListener(`abort`,o),n())},a=setTimeout(i,e);function o(){clearTimeout(a),i()}t&&t.addEventListener(`abort`,o,{once:!0})})}function u(e){n({level:e.event===`attempt_failed`||e.event===`exhausted`?`warn`:`debug`,phase:`retry`,data:{...e}})}function d(e){return e?.trim()||void 0}function f(e){return d(e)}function p(e){return new Set(e.split(/[\s,]+/).map(e=>e.trim()).filter(Boolean))}function m(e,t=process.env){let n=d(e)??d(t.AGENT_DEVICE_ANDROID_DEVICE_ALLOWLIST);if(n)return p(n)}const h=new Set([`IOS_BOOT_TIMEOUT`,`IOS_RUNNER_CONNECT_TIMEOUT`,`IOS_TOOL_MISSING`,`ANDROID_BOOT_TIMEOUT`,`ADB_TRANSPORT_UNAVAILABLE`,`CI_RESOURCE_STARVATION_SUSPECTED`]);function g(e){return h.has(e.toUpperCase())}function _(e){let n=e.error?t(e.error):null,r=e.context?.platform,i=e.context?.phase;if(n?.code===`TOOL_MISSING`)return r===`android`?`ADB_TRANSPORT_UNAVAILABLE`:`IOS_TOOL_MISSING`;let a=n?.details??{},o=typeof a.message==`string`?a.message:void 0,s=typeof a.stdout==`string`?a.stdout:void 0,c=typeof a.stderr==`string`?a.stderr:void 0,l=a.boot&&typeof a.boot==`object`?a.boot:null,u=a.bootstatus&&typeof a.bootstatus==`object`?a.bootstatus:null,d=[e.message,n?.message,e.stdout,e.stderr,o,s,c,typeof l?.stdout==`string`?l.stdout:void 0,typeof l?.stderr==`string`?l.stderr:void 0,typeof u?.stdout==`string`?u.stdout:void 0,typeof u?.stderr==`string`?u.stderr:void 0].filter(Boolean).join(`
2
+ `).toLowerCase();return r===`ios`&&(d.includes(`runner did not accept connection`)||i===`connect`&&(d.includes(`timed out`)||d.includes(`timeout`)||d.includes(`econnrefused`)||d.includes(`connection refused`)||d.includes(`fetch failed`)||d.includes(`socket hang up`)))?`IOS_RUNNER_CONNECT_TIMEOUT`:r===`ios`&&i===`boot`&&(d.includes(`timed out`)||d.includes(`timeout`))?`IOS_BOOT_TIMEOUT`:r===`android`&&i===`boot`&&(d.includes(`timed out`)||d.includes(`timeout`))?`ANDROID_BOOT_TIMEOUT`:d.includes(`resource temporarily unavailable`)||d.includes(`killed: 9`)||d.includes(`cannot allocate memory`)||d.includes(`system is low on memory`)?`CI_RESOURCE_STARVATION_SUSPECTED`:r===`android`&&(d.includes(`device not found`)||d.includes(`no devices`)||d.includes(`device offline`)||d.includes(`offline`)||d.includes(`unauthorized`)||d.includes(`not authorized`)||d.includes(`unable to locate device`)||d.includes(`invalid device`))?`ADB_TRANSPORT_UNAVAILABLE`:n?.code===`COMMAND_FAILED`||d.length>0?`BOOT_COMMAND_FAILED`:`UNKNOWN`}function v(e){switch(e){case`IOS_BOOT_TIMEOUT`:return`Retry simulator boot and inspect simctl bootstatus logs; in CI reduce parallel jobs or use a larger runner.`;case`IOS_RUNNER_CONNECT_TIMEOUT`:return`Retry runner startup, inspect xcodebuild logs, and verify simulator responsiveness before command execution.`;case`ANDROID_BOOT_TIMEOUT`:return`Retry emulator startup and verify sys.boot_completed reaches 1; consider increasing startup budget in CI.`;case`ADB_TRANSPORT_UNAVAILABLE`:return`Check adb server/device transport (adb devices -l), restart adb, and ensure the target device is online and authorized.`;case`CI_RESOURCE_STARVATION_SUSPECTED`:return`CI machine may be resource constrained; reduce parallel jobs or use a larger runner.`;case`IOS_TOOL_MISSING`:return`Xcode command-line tools are missing or not in PATH; run xcode-select --install and verify xcrun works.`;case`BOOT_COMMAND_FAILED`:return`Inspect command stderr/stdout for the failing boot phase and retry after environment validation.`;default:return`Retry once and inspect verbose logs for the failing phase.`}}export{m as a,r as c,p as i,o as l,_ as n,f as o,g as r,a as s,v as t,s as u};
@@ -0,0 +1 @@
1
+ import{_ as e}from"./sdk-contracts.js";import{E as t,O as n}from"./sdk-android-adb.js";import{ar as r}from"./tv-remote.js";import{n as i,t as a}from"./path-resolution.js";import{f as o,t as s,u as c,v as l}from"./registry.js";import{n as u}from"./command-schema.js";import d from"node:fs";import f from"node:path";function p(e,t){for(let[n,r]of Object.entries(t))r!==void 0&&(e[n]=r);return e}const m=new Set([`config`,`remoteConfig`,`help`,`version`,`batchSteps`,`githubActionsArtifact`]),h=new Set([`appsFilter`,`iosSimulatorDeviceSet`,`sessionLocked`,`sessionLockConflicts`]),g=S(),_=new Map(g.map(e=>[e.key,e]));function v(e){return _.get(e)}function y(e){return g.filter(t=>t.config.enabled&&t.supportsCommand(e))}function b(e,t){return v(e)?.supportsCommand(t)??!1}function x(e,t,r,i){return n(w(e),t,r,i)}function S(){let e=new Map;for(let t of l()){let n=e.get(t.key);n?n.push(t):e.set(t.key,[t])}let n=new Map;for(let e of o)n.set(e,new Set([`*`]));for(let e of r()){let t=u(e);for(let r of[...t.allowedFlags??[],...t.supportedFlags??[]]){let t=n.get(r);t&&t.has(`*`)||(t?t.add(e):n.set(r,new Set([e])))}}return[...e.entries()].map(([e,r])=>({key:e,flagDefinitions:r,config:{enabled:!m.has(e),key:e},env:{names:h.has(e)?[]:[t(e)]},supportsCommand(t){let r=n.get(e);return r?r.has(`*`)?!0:t?r.has(t):!1:!1}})).sort((e,t)=>e.key.localeCompare(t.key))}function C(e){let t=e.flagDefinitions[0];if(!t)throw Error(`Missing flag definition for option ${e.key}`);return t}function w(e){let t=e.flagDefinitions.find(e=>e.setValue===void 0);if(t)return t;let n=C(e);if(n.type===`enum`){let t=n.enumValues??e.flagDefinitions.map(e=>e.setValue).filter(e=>e!==void 0);return{...n,setValue:void 0,enumValues:t}}return n}const T=s();function E(e){return T[e.name]?.({input:e.input??{},result:e.result})}function D(e){let t=e.env??process.env;return p(p({},j(O(e.cwd,e.cliFlags.config,t))),P(t,e.command))}function O(e,t,n){let r=t??n.AGENT_DEVICE_CONFIG;return r?[{path:A(r,e,n),required:!0}]:[{path:k(n),required:!1},{path:f.resolve(e,`agent-device.json`),required:!1}]}function k(e){return f.join(a(`~`,{env:e}),`.agent-device`,`config.json`)}function A(e,t,n){return i(e,{cwd:t,env:n})}function j(e){let t={};for(let n of e)p(t,M(n.path,n.required));return t}function M(t,n){if(!d.existsSync(t)){if(n)throw new e(`INVALID_ARGS`,`Config file not found: ${t}`);return{}}let r;try{r=d.readFileSync(t,`utf8`)}catch(n){throw new e(`INVALID_ARGS`,`Failed to read config file: ${t}`,{cause:n instanceof Error?n.message:String(n)})}let i;try{i=JSON.parse(r)}catch(n){throw new e(`INVALID_ARGS`,`Invalid JSON in config file: ${t}`,{cause:n instanceof Error?n.message:String(n)})}if(!i||typeof i!=`object`||Array.isArray(i))throw new e(`INVALID_ARGS`,`Config file must contain a JSON object: ${t}`);return N(i,`config file ${t}`)}function N(t,n){let r={};for(let[i,a]of Object.entries(t)){if(i===`installSource`){r.installSource=c(a,n);continue}let t=i,o=v(t);if(!o)throw new e(`INVALID_ARGS`,`Unknown config key "${i}" in ${n}.`);if(!o.config.enabled)throw new e(`INVALID_ARGS`,`Unsupported config key "${i}" in ${n}.`);r[t]=x(o,a,n,i)}return r}function P(e,t){let n={};for(let r of y(t)){if(r.key===`installSource`)continue;let t=r.env.names.map(t=>({name:t,value:e[t]})).find(e=>typeof e.value==`string`&&e.value.trim().length>0);t&&(n[r.key]=x(r,t.value,`environment variable ${t.name}`,t.name))}return n}export{p as i,E as n,b as r,D as t};
@@ -1,4 +1,5 @@
1
- import{Qn as e}from"./tv-remote.js";import{h as t,v as n}from"./registry.js";import{n as r,r as i}from"./command-schema.js";function a(e){let t=e.endsWith(`?`),n=t?e.slice(0,-1):e;return t?`[${n}]`:`<${n}>`}function o(e){return n().filter(t=>t.key===e)}function s(e,t){if(t.usageOverride)return t.usageOverride;let n=(t.positionalArgs??[]).map(a),r=(t.allowedFlags??[]).flatMap(e=>o(e).map(e=>e.usageLabel??e.names[0])).map(e=>`[${e}]`);return[e,...n,...r].join(` `)}const c=[{label:`agent-device help manual-qa`,description:`Follow a manual test script with exact interactions and verification`},{label:`agent-device help dogfood`,description:`Explore an app and report issues with evidence`},{label:`agent-device help validate`,description:`Validate code changes, perf, visuals, logs, and cleanup`},{label:`agent-device help workflow`,description:`Full app automation reference for commands, refs, selectors, and waits`},{label:`agent-device help debugging`,description:`Use when logs, network, audio, perf memory, traces, alerts, or diagnostics matter`},{label:`agent-device help tv`,description:`Use when navigating Android TV or tvOS focus-first surfaces`},{label:`agent-device help react-native`,description:`Use when the target app is React Native, Expo, or a dev client`},{label:`agent-device help react-devtools`,description:`Use when inspecting components, props/state/hooks, renders, or profiles`},{label:`agent-device help cdp`,description:`Use when investigating JS heap growth, heap snapshots, or retainers`},{label:`agent-device help physical-device`,description:`Use when using a connected phone/tablet or iOS signing setup`},{label:`agent-device help remote`,description:`Use when working through cloud config, tenants, leases, or local tunnels`},{label:`agent-device help web`,description:`Use when automating a browser through agent-device sessions`},{label:`agent-device help macos`,description:`Use when targeting desktop, frontmost app, or menu bar surfaces`}],l=[`Write full command lines starting with agent-device; do not output pseudo commands, helper prose, pipes, grep, jq, or hidden stderr.`,`Default app loop: agent-device open <app> -> agent-device snapshot -i -> agent-device press/fill/click <target> --settle -> continue from that settled diff -> agent-device close.`,`Use --settle on every mutating app action that supports it: press, click, fill, longpress.`,`The settled diff is the next snapshot. Do not run wait stable or snapshot after settled:true unless the diff lacks the next target/evidence.`,`If --settle prints not settled, follow its hint before the next ref-based action.`,`Use refs or selectors as targets: @e12, label="Search", role=button label="Follow".`,`Pick the help mode below when the task is manual QA, dogfooding, engineering validation, or debugging.`],u=[`Planning output contract: when asked to plan commands, output command lines only: no prose, numbering, Markdown fences, pipes, or shell helpers.`,`If you did not use --settle, verify a mutation with diff snapshot (or diff snapshot -i), not a full snapshot: it prints only the added/removed/changed lines since the last snapshot in this session.`,`Network-backed or debounced results may arrive after the --settle quiet window; follow the settled action with wait text "Expected result" or wait <selector> instead of polling full snapshots.`,`Pin a raw CLI ref to the response that minted it with ~s<n> (n = refsGeneration): press @e12~s4; plain refs stay valid input.`,`Plain snapshot reads state; snapshot -i refreshes current interactive refs only.`,`Default snapshot text is an agent-facing, token-efficient view for planning and targeting actions.`,`Read-only visible/state question: use snapshot/get/is/find; use snapshot -i only when refs are needed.`,`Truncated text/input preview: expand first with snapshot -s @e12, not get text.`,`React Native apps: read help react-native.`,`Text fields: use fill <target> <text> --settle to replace a field value. Use type <text> only to append after focusing a field with press.`,`Clearing text: do not use fill <target> ""; use a visible clear/reset control or report that clearing is unsupported.`,`Implicit default sessions are scoped to the current worktree; if a prompt names a Session, include --session <name> on every command in that flow.`,`Run mutating commands serially within one session; parallelize only read-only commands or separate sessions/devices.`,`After mutation: refs are stale. If the next target is known, use its selector directly; otherwise refresh with snapshot -i, scoped with -s when a stable container is known. Use press/click for taps.`,`macOS context menus use click <ref> --button secondary, then snapshot -i. Longpress is for mobile hold gestures, not macOS secondary-click menus.`,`Remote lifecycle: use connect, then open, commands, close, and disconnect. Read help remote for proxy, cloud, and device-cloud provider flows.`,`TV/D-pad targets: read help tv. Web browser sessions: read help web.`,`Debug evidence: Session state contains request diagnostics and runner.log; use logs clear --restart/mark/path, trace, and network dump --include headers for app evidence.`,`Routine QA loop with concrete command shapes: agent-device help manual-qa. Full operating guide: agent-device help workflow. Exploratory QA: agent-device help dogfood.`],d=[`Default config files: ~/.agent-device/config.json, ./agent-device.json`,`Use --config <path> or AGENT_DEVICE_CONFIG to load one explicit config file.`],f=[{label:`AGENT_DEVICE_SESSION`,description:`Explicit session name`},{label:`AGENT_DEVICE_PLATFORM`,description:`Default platform binding`},{label:`AGENT_DEVICE_SESSION_LOCK`,description:`Bound-session conflict mode`},{label:`AGENT_DEVICE_DAEMON_BASE_URL`,description:`Connect to remote daemon`},{label:`AGENT_DEVICE_DAEMON_AUTH_TOKEN`,description:`Remote daemon service/API token`},{label:`AGENT_DEVICE_CLOUD_BASE_URL`,description:`Bridge/control-plane API origin for cloud auth and /api-keys`}],p=[`agent-device open Settings --platform ios`,`agent-device open https://example.com --platform web`,`agent-device snapshot -i`,`agent-device fill @e3 "test@example.com"`,`agent-device replay ./session.ad`,`agent-device test ./suite --platform android`],m={"manual-qa":{summary:`Follow manual test scripts with exact interactions and verification`,body:`agent-device help manual-qa
1
+ import{ar as e}from"./tv-remote.js";import{f as t,v as n}from"./registry.js";import{n as r,r as i}from"./command-schema.js";const a=[`Flows: launchApp; runFlow file/inline with platform, visibility, and limited boolean conditions; onFlowStart/onFlowComplete; repeat.times and retry.`,`Interactions: tapOn, doubleTapOn, longPressOn, inputText, eraseText, openLink, hideKeyboard, basic pressKey, and back; targets support index, childOf, label, points, and optional.`,`Assertions and navigation: assertVisible, assertNotVisible, extendedWaitUntil, scroll, scrollUntilVisible, absolute/percentage/target swipe, takeScreenshot, waitForAnimationToEnd, and stopApp.`,`Scripts: ordered runScript file/env scripts with http.post, json, and output variables.`],o=[`Runtime: iOS and Android only; launchApp.clearState supports Android and iOS simulators, launch arguments are Apple-only, and standalone device utility/state commands are unsupported.`,`Expressions: when.true supports boolean literals and maestro.platform comparisons; repeat.while, evalScript, and broader JavaScript expressions are unsupported.`,`Environment: flow env is the default, AD_VAR_* overrides it, and CLI -e KEY=VALUE wins over both.`,`Trust: runScript executes trusted scripts, may make http.post network requests, and is not a security sandbox; output keys cannot contain a dot.`,`Errors and tracking: unsupported commands and fields fail with source context when available; open a focused issue only when implementation work is planned.`];function s(){return[`Supported subset:`,...a.map(e=>` - ${e}`),``,`Boundaries:`,...o.map(e=>` - ${e}`)].join(`
2
+ `)}function c(e){let t=e.endsWith(`?`),n=t?e.slice(0,-1):e;return t?`[${n}]`:`<${n}>`}function l(e){return n().filter(t=>t.key===e)}function u(e,t){if(t.usageOverride)return t.usageOverride;let n=(t.positionalArgs??[]).map(c),r=(t.allowedFlags??[]).flatMap(e=>l(e).map(e=>e.usageLabel??e.names[0])).map(e=>`[${e}]`);return[e,...n,...r].join(` `)}const d=[{label:`agent-device help manual-qa`,description:`Follow a manual test script with exact interactions and verification`},{label:`agent-device help dogfood`,description:`Explore an app and report issues with evidence`},{label:`agent-device help validate`,description:`Validate code changes, perf, visuals, logs, and cleanup`},{label:`agent-device help workflow`,description:`Full app automation reference for commands, refs, selectors, and waits`},{label:`agent-device help debugging`,description:`Use when logs, network, audio, perf memory, traces, alerts, or diagnostics matter`},{label:`agent-device help tv`,description:`Use when navigating Android TV or tvOS focus-first surfaces`},{label:`agent-device help react-native`,description:`Use when the target app is React Native, Expo, or a dev client`},{label:`agent-device help react-devtools`,description:`Use when inspecting components, props/state/hooks, renders, or profiles`},{label:`agent-device help cdp`,description:`Use when investigating JS heap growth, heap snapshots, or retainers`},{label:`agent-device help physical-device`,description:`Use when using a connected phone/tablet or iOS signing setup`},{label:`agent-device help remote`,description:`Use when working through cloud config, tenants, leases, or local tunnels`},{label:`agent-device help web`,description:`Use when automating a browser through agent-device sessions`},{label:`agent-device help macos`,description:`Use when targeting desktop, frontmost app, or menu bar surfaces`}],f=[`Write full command lines starting with agent-device; do not output pseudo commands, helper prose, pipes, grep, jq, or hidden stderr.`,`Default app loop: agent-device open <app> -> agent-device snapshot -i -> agent-device press/fill/click <target> --settle -> continue from that settled diff -> agent-device close.`,`Use --settle on every mutating app action that supports it: press, click, fill, longpress.`,`The settled diff is the next snapshot. Do not run wait stable or snapshot after settled:true unless the diff lacks the next target/evidence.`,`If --settle prints not settled, follow its hint before the next ref-based action.`,`Use refs or selectors as targets: @e12, label="Search", role=button label="Follow".`,`Pick the help mode below when the task is manual QA, dogfooding, engineering validation, or debugging.`],p=[`Planning output contract: when asked to plan commands, output command lines only: no prose, numbering, Markdown fences, pipes, or shell helpers.`,`If you did not use --settle, verify a mutation with diff snapshot (or diff snapshot -i), not a full snapshot: it prints only the added/removed/changed lines since the last snapshot in this session.`,`Network-backed or debounced results may arrive after the --settle quiet window; follow the settled action with wait text "Expected result" or wait <selector> instead of polling full snapshots.`,`Pin a raw CLI ref to the response that minted it with ~s<n> (n = refsGeneration): press @e12~s4. On iOS, stale refs are rejected for mutations; refresh refs or use a stable selector.`,`Plain snapshot reads state; snapshot -i refreshes current interactive refs only.`,`Default snapshot text is an agent-facing, token-efficient view for planning and targeting actions.`,`Read-only visible/state question: use snapshot/get/is/find; use snapshot -i only when refs are needed.`,`Truncated text/input preview: expand first with snapshot -s @e12, not get text.`,`React Native apps: read help react-native.`,`Text fields: use fill <target> <text> --settle to replace a field value. Use type <text> only to append after focusing a field with press.`,`Clearing text: do not use fill <target> ""; use a visible clear/reset control or report that clearing is unsupported.`,`Implicit default sessions are scoped to the current worktree; if a prompt names a Session, include --session <name> on every command in that flow.`,`Run mutating commands serially within one session; parallelize only read-only commands or separate sessions/devices.`,`After mutation: refs are stale. If the next target is known, use its selector directly; otherwise refresh with snapshot -i, scoped with -s when a stable container is known. Use press/click for taps.`,`macOS context menus use click <ref> --button secondary, then snapshot -i. Longpress is for mobile hold gestures, not macOS secondary-click menus.`,`Remote lifecycle: use connect, then open, commands, close, and disconnect. Read help remote for proxy, cloud, and device-cloud provider flows.`,`TV/D-pad targets: read help tv. Web browser sessions: read help web.`,`Debug evidence: Session state contains request diagnostics and runner.log; use logs clear --restart/mark/path, trace, and network dump --include headers for app evidence.`,`Routine QA loop with concrete command shapes: agent-device help manual-qa. Full operating guide: agent-device help workflow. Exploratory QA: agent-device help dogfood.`],m=[`Default config files: ~/.agent-device/config.json, ./agent-device.json`,`Use --config <path> or AGENT_DEVICE_CONFIG to load one explicit config file.`],h=[{label:`AGENT_DEVICE_SESSION`,description:`Explicit session name`},{label:`AGENT_DEVICE_PLATFORM`,description:`Default platform binding`},{label:`AGENT_DEVICE_SESSION_LOCK`,description:`Bound-session conflict mode`},{label:`AGENT_DEVICE_DAEMON_BASE_URL`,description:`Connect to remote daemon`},{label:`AGENT_DEVICE_DAEMON_AUTH_TOKEN`,description:`Remote daemon service/API token`},{label:`AGENT_DEVICE_CLOUD_BASE_URL`,description:`Bridge/control-plane API origin for cloud auth and /api-keys`}],g=[`agent-device open Settings --platform ios`,`agent-device open https://example.com --platform web`,`agent-device snapshot -i`,`agent-device fill @e3 "test@example.com"`,`agent-device replay ./session.ad`,`agent-device test ./suite --platform android`],_={"manual-qa":{summary:`Follow manual test scripts with exact interactions and verification`,body:`agent-device help manual-qa
2
3
 
3
4
  Use this when asked to follow a manual QA script, test case, checklist, acceptance flow, or user-provided instructions.
4
5
 
@@ -36,7 +37,14 @@ Recovery:
36
37
  Network/typeahead result missing: wait text "Expected result" or wait <selector>.
37
38
  Keyboard visible over the next target: the on-screen keyboard usually does not block presses, so press the target directly instead of dismissing. If the press fails or reports no visible effect, scroll the target into view or use keyboard enter when submission is wanted.
38
39
  Sparse or recovered accessibility snapshot: use screenshot as visual truth, leave the bad screen if needed, then retry snapshot -i.
39
- Non-hittable success hint: verify with the settled diff or snapshot; retarget by a better ref/selector if the UI did not change.`},workflow:{summary:`Normal agent-device bootstrap, exploration, and validation loop`,body:`agent-device help workflow
40
+ Non-hittable success hint: verify with the settled diff or snapshot; retarget by a better ref/selector if the UI did not change.`},maestro:{summary:`Supported Maestro YAML commands, grammar, and runtime boundaries`,body:`agent-device help maestro
41
+
42
+ Run Maestro compatibility flows with replay <flow.yaml> --maestro or test <path> --maestro. Bind an iOS or Android target with --platform or an existing session.
43
+
44
+ ${s()}
45
+
46
+ Unsupported syntax fails loudly rather than being skipped. Architecture, performance tradeoffs, and declared conformance divergences: https://github.com/callstack/agent-device/blob/main/docs/adr/0015-direct-maestro-engine.md
47
+ Focused compatibility request: https://github.com/callstack/agent-device/issues/new`},workflow:{summary:`Normal agent-device bootstrap, exploration, and validation loop`,body:`agent-device help workflow
40
48
 
41
49
  Version-matched operating guide for normal agent-device work.
42
50
 
@@ -54,7 +62,7 @@ Command shape:
54
62
  Snapshot refs look like @e12. After snapshot -i, use the exact @eN ref from that output.
55
63
  If the exact ref is not known yet, first output snapshot -i, then use a concrete example shape like press @e12 in the next command; do not write @<ref>, @ref, @Label_Name, or @eN placeholders.
56
64
  Close means agent-device close. App-owned back means back; system back means back --system.
57
- Taps are press or click; tap is an alias for press. On Android TV and tvOS, read help tv and use tv-remote press up|down|left|right|select to move D-pad/remote focus before activating controls; use tv-remote longpress <button> for a held remote button. Gestures use swipe, longpress, or gesture <pan|fling|swipe|pinch|rotate|transform>. Use gesture swipe left|right for reliable in-page horizontal swipes, and gesture swipe right-edge for left-edge navigation/back gestures. Android swipe, pinch, rotate, and transform use provider-native touch injection when available, then the bundled touch helper. iOS simulator transform uses private XCTest synthesis for a continuous two-finger pan/scale/rotation path; otherwise it reports UNSUPPORTED_OPERATION.
65
+ Taps are press or click; tap is an alias for press. On Android TV and tvOS, read help tv and use tv-remote press up|down|left|right|select to move D-pad/remote focus before activating controls; use tv-remote longpress <button> for a held remote button. Gestures use swipe, longpress, or gesture <pan|fling|swipe|pinch|rotate|transform>. Use gesture swipe left|right for reliable in-page horizontal swipes, and gesture swipe right-edge for left-edge navigation/back gestures. gesture pan is one finger by default; add --pointer-count 2 for a parallel two-finger pan. Android swipe and multi-touch gestures use provider-native touch injection when available, then the bundled touch helper. iOS simulator multi-touch uses private XCTest synthesis for a continuous two-pointer path; otherwise it reports UNSUPPORTED_OPERATION.
58
66
 
59
67
  Bootstrap:
60
68
  agent-device devices --platform ios
@@ -73,6 +81,15 @@ Bootstrap:
73
81
  CI may cache ~/.agent-device/apple-runner/derived with an exact key that includes the agent-device package and Xcode version. Avoid broad restore-key fallbacks; prepare ios-runner already recovers bad restored runner artifacts and one retryable non-connecting runner launch. Runner build/start output is written to the session's runner.log; daemon.log is for daemon lifecycle/startup issues.
74
82
  Do not open artifact paths or invent package ids. If apps lookup misses the target and no URL/artifact is provided, ask or stop.
75
83
 
84
+ Reusable open-to-destination scripts:
85
+ Arm recording on the first open, perform the full journey, verify the ready destination with a selective selector-targeted wait, then publish without closing:
86
+ agent-device open com.example.app --relaunch --save-script=screen-x.ad
87
+ agent-device press 'id="continue"' --settle
88
+ agent-device wait 'role="heading" label="Screen X"'
89
+ agent-device session save-script
90
+ session save-script [path] [--force] publishes the sole recorded open through the destination guard, omits close, and leaves the session active. A duration wait, wait stable, or wait @ref is not a destination guard. A second successful open aborts publication; start a fresh session to author again.
91
+ Recorded fill/type inputs are written literally to the .ad file. Do not record passwords, tokens, or other secrets; use pre-authenticated test state or non-secret fixture credentials until parameterized input authoring is available.
92
+
76
93
  Snapshots and refs:
77
94
  snapshot reads visible state. snapshot -i gets current interactive refs only; it is the fast path when the next step is an interaction.
78
95
  Default snapshot text is an agent-facing, token-efficient view for planning and targeting actions; use --raw or --json only when you need the full provider tree.
@@ -84,7 +101,7 @@ Snapshots and refs:
84
101
  For press/fill/click/longpress, prefer --settle and continue from its settled diff when it exposes the next target or evidence. Refresh with snapshot -i only when you did not settle, settle printed not settled, or the settle output lacks what you need.
85
102
  Anti-pattern: snapshot -i followed by snapshot -i | grep ..., or adding 2>/dev/null | jq ... before reading the raw command output.
86
103
  Refs from the first snapshot remain valid until you press, click, fill, type, scroll, go back, wait for async UI, or otherwise change app state.
87
- Pinned refs (@e12~s4, generation from refsGeneration or settle.refsGeneration) get exact staleness warnings instead of the coarse tree-changed one; plain refs stay valid input.
104
+ Pinned refs (@e12~s4, generation from refsGeneration or settle.refsGeneration) identify their source tree. On iOS, stale refs are rejected for press/fill/click/longpress before dispatch; refresh with snapshot -i or use a stable selector. Read-only commands report a staleness warning, and other platforms retain their existing warning behavior.
88
105
  After a mutation, prefer a known selector/label directly (for example press 'label="Send"') because interaction commands refresh interactive state internally. A settled diff with no added refs (for example a modal dismiss) also lists an "unchanged interactive" tail of still-present refs, so check that before falling back. If you need to discover a new control not shown by settle or its tail, use snapshot -i, or snapshot -i -s "Composer" when a stable container label/id can scope the refresh.
89
106
  If typing/fill opened the keyboard or changed layout and the next target has no stable selector, run snapshot -i, use the fresh ref, then verify with wait/find or diff snapshot -i.
90
107
  For a targeted query, use find/get/is. If you truly need the full tree again, pass --force-full.
@@ -102,7 +119,7 @@ Selectors:
102
119
  agent-device press 'id="submit-order"'
103
120
  agent-device is visible 'label="Online"'
104
121
  agent-device get text 'id="quantity-value"'
105
- Ambiguous selector disambiguation: a selector on an interactive command (press/click/fill/focus/longpress/scroll/swipe/pinch) that matches multiple elements does not fail by default. It auto-resolves deepest node first (largest depth in the tree), then smallest on-screen area; only an exact tie on both depth and area fails with "Selector did not resolve uniquely". replay and replay-heal apply the same depth-then-area policy for touch/fill/get-text, so recorded flows and live commands pick the same candidate. This exists because short/reused labels (tab + header + button with the same text, or a duplicated list-row label) are common in real apps; add id="..." or a longer/more specific text to force a different match instead of assuming ambiguous selectors always fail.
122
+ Ambiguous selector disambiguation: a selector on an interactive command (press/click/fill/focus/longpress/scroll/swipe/pinch) that matches multiple elements does not fail by default. It auto-resolves deepest node first (largest depth in the tree), then smallest on-screen area; only an exact tie on both depth and area fails with "Selector did not resolve uniquely". replay's suggestion re-resolution (in a divergence report) applies the same depth-then-area policy for touch/fill/get-text, so recorded flows and live commands pick the same candidate. This exists because short/reused labels (tab + header + button with the same text, or a duplicated list-row label) are common in real apps; add id="..." or a longer/more specific text to force a different match instead of assuming ambiguous selectors always fail.
106
123
  Selector match can still land on a non-interactive node (for example an off-screen map annotation that exact-matches text= while the real control has a longer label). Success responses for press/fill/click/ref targets carry targetHittable: false and a hint when the resolved element reports hittable: false, since the tap may have had no visible effect; treat that as a signal to verify with a snapshot or re-target by @ref/longer text, not as a command failure.
107
124
 
108
125
  Text entry:
@@ -113,11 +130,13 @@ Text entry:
113
130
  agent-device type "Handle with care" --delay-ms 80
114
131
  Empty replacement is not a supported clear-field command: do not plan fill <target> "" or fill <target> ''. Prefer a visible clear/reset control; if the app exposes none, report the tool gap instead of inventing a clear command.
115
132
  Debounced field with no result selector: agent-device wait 1000. Keyboard read-only: keyboard status/get. The on-screen keyboard usually does not block agent-device interactions; press the next target directly instead of dismissing. If that press fails or reports no visible effect, scroll the target into view or use keyboard enter when submission is wanted.
116
- Only dismiss the keyboard when hiding it is the actual goal. To hide the keyboard, use keyboard dismiss. It taps safe controls like Done when available and verifies the keyboard closed. If it reports UNSUPPORTED_OPERATION, press a visible app control such as Done only when that is the intended fallback, or press the next target directly instead of retrying dismiss.
133
+ Only dismiss the keyboard when hiding it is the actual goal. To hide the keyboard, use keyboard dismiss. It taps safe controls like Done when available and verifies the keyboard closed.
134
+ On iOS, if it returns UNSUPPORTED_OPERATION, there is no generic app-agnostic blur fallback: do not assume a static text or heading is safe to press, because it can belong to a tappable parent. Use an app-provided dismiss control only when its action is explicitly intended; otherwise report that keyboard dismissal is unavailable.
135
+ On Android, keyboard dismiss first avoids navigation. If it returns UNSUPPORTED_OPERATION because the current IME needs back navigation, use back only when normal back behavior is acceptable; otherwise report that keyboard dismissal is unavailable.
117
136
  Use plain fill/type first for ordinary login and form fields. If an iOS debounced or search-as-you-type field actually drops characters, or must receive incremental updates, retry with --delay-ms before trying clipboard paste; --delay-ms intentionally paces character entry.
118
137
  iOS Allow Paste prompt cannot be exercised under XCUITest. To test paste-driven app behavior, prefill first with agent-device clipboard write "some text"; test the system prompt manually.
119
138
  Android Gboard handwriting/stylus UI can capture text in an IME-owned input instead of the app field. If fill reports that input was captured by the keyboard/IME, use the diagnostic targetInput/actualInput details, inspect keyboard status/get if needed, and switch or disable handwriting outside the command plan before retrying. Do not keep retrying fill/type against the same field while the IME owns focus.
120
- Android text entry is owned by agent-device: provider-native text injection when available, then chunk-safe ASCII shell input. Do not switch to raw adb, clipboard, or paste as an agent fallback. If non-ASCII is unsupported in the current backend, report the tool/device gap.
139
+ Android text entry is owned by agent-device: provider-native text injection when available, else the bundled test IME helper (emulators activate it automatically; real devices need open --test-ime), else chunk-safe ASCII shell input. Do not switch to raw adb, clipboard, or paste as an agent fallback. If non-ASCII text still fails, report the tool/device gap.
121
140
 
122
141
  Session ordering:
123
142
  Stateful commands within one session must run serially. Do not run open/press/fill/type/scroll/back/alert/replay/batch/close commands in parallel against the same session.
@@ -141,20 +160,21 @@ Read-only and waits:
141
160
  Ambiguous find: add --first or --last. If info is not visible/exposed, report that gap instead of typing/searching/navigating to reveal it.
142
161
 
143
162
  Navigation and gestures:
144
- Use scroll for lists; swipe for coordinate gestures/carousels; gesture pan for deliberate drags; gesture fling for fast directional throws.
163
+ Use scroll for lists; swipe for quick coordinate gestures/carousels; gesture pan for deliberate timed drags; gesture fling for fast directional throws.
145
164
  For fast macOS desktop list traversal, prefer fixed pixel wheel steps and batch them when no snapshot is needed between passes:
146
165
  agent-device scroll down --pixels 200 --duration-ms 50 --platform macos
147
166
  agent-device batch --steps '[{"command":"scroll","input":{"direction":"down","pixels":200,"durationMs":50}},{"command":"scroll","input":{"direction":"down","pixels":200,"durationMs":50}}]' --platform macos
148
167
  For raw coordinate gestures, run snapshot -i first and choose a point near the center of the intended app-owned target. Avoid screen edges, tab bars, navigation bars, and home indicators because those areas can trigger system or app navigation instead of the gesture under test.
149
168
  If app-owned back is ambiguous or has just misrouted, prefer a visible nav/back button ref, tab-bar ref, or deep link over repeated back/system back.
150
169
  App-owned action sheets, menus, and camera/scan screens are normal UI. After opening one, run snapshot -i or wait for the option, press by label/ref, handle visible permission sheets through UI or platform-supported native alerts, then wait for a concrete result before returning to chat/form state.
151
- Keep count/pause/pattern on one swipe; flags are --count, --pause-ms, --pattern ping-pong.
170
+ Keep count/pause/pattern on one swipe; flags are --count, --pause-ms, --pattern ping-pong. Count is capped at 200, pause at 10000ms, and the combined swipe/pause schedule at 60000ms.
152
171
  For repeated iOS gesture smoke checks, use press <x> <y> --count <n> --jitter-px <n> for tap series and swipe <x1> <y1> <x2> <y2> --count <n> for drag series.
153
172
  longpress accepts coordinates, @refs, or selectors. Prefer @ref/selector from snapshot -i; use coordinates only as a fallback when accessibility refs miss the exact target. Duration and gesture scale/center are positional:
154
173
  agent-device longpress 300 500 800
155
174
  agent-device longpress @e12 800
156
175
  agent-device swipe 320 500 40 500 --count 8 --pause-ms 30 --pattern ping-pong
157
176
  agent-device gesture pan 200 420 0 -80 500
177
+ agent-device gesture pan 200 420 80 -40 700 --pointer-count 2
158
178
  agent-device gesture fling right 200 420 180
159
179
  agent-device gesture pinch 0.5 200 400
160
180
  agent-device gesture rotate 35 200 420
@@ -165,7 +185,9 @@ Navigation and gestures:
165
185
  agent-device wait text "pan changed yes" 3000 --platform android
166
186
  agent-device wait text "pinch changed yes" 3000 --platform android
167
187
  agent-device wait text "rotate changed yes" 3000 --platform android
168
- If Android needs exact app-state values, prefer isolated gesture pan, gesture pinch, or gesture rotate commands over one combined transform.
188
+ If Android needs exact app-state values, prefer isolated gesture pan --pointer-count 2, gesture pinch, or gesture rotate commands over one combined transform.
189
+ Gesture planning prefers the active-app frame. A backend without a gesture viewport resolver falls back to the visible snapshot union, which can be less accurate near edges.
190
+ tvOS coordinate pan and fling preserve only the dominant direction as a remote swipe; authored endpoints and duration are not preserved.
169
191
  macOS context menus are secondary clicks, not long presses:
170
192
  agent-device click @e66 --button secondary --platform macos
171
193
  agent-device snapshot -i --platform macos
@@ -181,7 +203,7 @@ Validation and evidence:
181
203
  agent-device screenshot
182
204
  agent-device press 124 817
183
205
  agent-device snapshot -i
184
- Startup/CPU/memory/frame first pass: perf metrics --json (bare perf and metrics are aliases). Focused frame/jank health: perf frames --json. Memory-only sample: perf memory sample --json returns compact JSON with bounded top offenders. Heap/memgraph artifact escalation: perf memory snapshot --out heap.artifact; use --kind android-hprof on Android or --kind memgraph on supported Apple simulator/macOS app sessions. Android native profiling: perf cpu profile start|stop|report --kind simpleperf --out <path>; Android native traces: perf trace start|stop --kind perfetto --out <path>. Artifact collectors return compact state/path/size metadata only; raw heap/profile/trace files stay on disk. Treat native perf output as the agent evidence: for example, a Perfetto stop can return state=stopped, outPath=/tmp/app.perfetto-trace, sizeBytes=5392410, and method=adb-shell-perfetto while the 5.3 MB raw trace stays in the artifact. This is better than raw dumps for agents because it is stable, bounded, and keeps large artifacts out of context. heapprofd is deferred until Perfetto plumbing is available. Replay maintenance: replay -u ./flow.ad.
206
+ Startup/CPU/memory/frame first pass: perf metrics --json (bare perf and metrics are aliases). Focused frame/jank health: perf frames --json. Memory-only sample: perf memory sample --json returns compact JSON with bounded top offenders. Heap/memgraph artifact escalation: perf memory snapshot --out heap.artifact; use --kind android-hprof on Android or --kind memgraph on supported Apple simulator/macOS app sessions. Android native profiling: perf cpu profile start|stop|report --kind simpleperf --out <path>; Android native traces: perf trace start|stop --kind perfetto --out <path>. Artifact collectors return compact state/path/size metadata only; raw heap/profile/trace files stay on disk. Treat native perf output as the agent evidence: for example, a Perfetto stop can return state=stopped, outPath=/tmp/app.perfetto-trace, sizeBytes=5392410, and method=adb-shell-perfetto while the 5.3 MB raw trace stays in the artifact. This is better than raw dumps for agents because it is stable, bounded, and keeps large artifacts out of context. heapprofd is deferred until Perfetto plumbing is available. Replay divergence and resume: a failing replay/test step returns REPLAY_DIVERGENCE with a bounded report (screen digest, ranked selector suggestions, resume). Repair app state, then resume with replay --from <n> --plan-digest <sha256> (both from the report's resume field) to continue from the failed step without re-running earlier ones; resume never re-executes skipped steps, so app state is the caller's responsibility, and it is rejected with INVALID_ARGS when the plan digest is stale, --from is out of range, or the skipped range/target touches runtime control flow. The plan digest binds the script, its includes, the effective --platform/--target, and per-action runtime/identity. Native .ad interpolation is late-bound after planning, so changing only its values keeps the digest; Maestro environment substitution occurs during compatibility parsing and can change action inputs, includes, or control expansion, so it can change the digest. --from is replay-only; test rejects it. --update/-u no longer rewrites the script (ADR 0012) — it is a no-op kept for compatibility; every divergence already carries the same ranked suggestions. Agent-supervised repair (heal-by-doing): arm replay <file>.ad --save-script[=<out>] before step 1 (armed once; --from continuations do not need it again). Every divergence carries a repairHint: record-and-heal means press the correct control via a blessed @ref from the divergence's screen.refs, recorded (no --no-record), then continue with replay --from <n+1> --plan-digest <sha256>; state-repair means the script is correct but app state is not, so fix state with --no-record actions, then replay --from <n> --plan-digest <sha256> to re-run the unchanged step; caution means something already matches the recorded selector, so a blind re-press may repeat the mistake; manual means no safe automated repair could be proven. While armed, read-only inspection YOU run to locate the repair target (snapshot -i, get attrs, find, is) is excluded from the healed script by default — no --no-record needed; the script's own authored get/is/find steps are unaffected and stay in the heal. If the step you are repairing is itself a read, pass --record on that one command so it lands in the heal (--record is accepted only on snapshot/get/is and a read-only find; it is mutually exclusive with --no-record). Ending the repair with close --save-script[=<out>] writes only the steps recorded since the arming replay as <out>, defaulting to the <file> sibling <stem>.healed.ad — review its diff before promoting it over the original.
185
207
  Recording: record start/stop. The default scope is app and expects an active session created by open <app>; this keeps app proof videos tied to the intended app session. Use record start --scope device/system to explicitly request whole-screen capture where the selected backend supports it, such as recordings that intentionally span multiple apps, home screen, settings, or app transitions. Use --max-size to cap the longest edge and --quality medium|high to choose output quality across Android and Apple targets. By default, stop burns touch overlays into the video; use record start --hide-touches for the fastest raw recording. Android record start publishes a durable device manifest. Android adb screenrecord has a 180s platform limit, so longer Android recordings are returned as multiple MP4 chunks while the daemon stays alive; after daemon restart, record stop recovers only manifest-owned chunks and warns when gesture overlays are unavailable. For gesture-heavy iOS simulator proof videos, prefer --hide-touches because overlay timing depends on a stable runner session while gestures are executing. Tracing: trace start ./trace.log, trace stop ./trace.log. Paths are positional.
186
208
  Stable known flow: batch ./steps.json, not workflow batch.
187
209
  Inline batch JSON example:
@@ -243,7 +265,7 @@ React Native dev loop:
243
265
 
244
266
  Guarantees:
245
267
  Statements of fact for agents to reason from without probing them via trial commands. Each is backed by source in the agent-device repo; behavior changes land with an updated statement here.
246
- Selector ambiguity: a selector on an interactive command that matches multiple elements does not fail by default. Resolution auto-disambiguates deepest node first, then smallest on-screen area; only an exact tie on both fails with "Selector did not resolve uniquely (...)". replay and replay-heal apply the same depth-then-area policy, so recorded and live commands pick the same candidate.
268
+ Selector ambiguity: a selector on an interactive command that matches multiple elements does not fail by default. Resolution auto-disambiguates deepest node first, then smallest on-screen area; only an exact tie on both fails with "Selector did not resolve uniquely (...)". replay's suggestion re-resolution applies the same depth-then-area policy, so recorded and live commands pick the same candidate.
247
269
  Hittability: iOS AX hittable:false on a resolved node does not block resolution or fail the command; non-hittable resolution is allowed by design because iOS AX hittable flags are unreliable on deep React Native trees. press/fill/click success responses carry targetHittable: false plus a hint when the resolved ref or selector target reports hittable: false, so treat that as a signal to verify with a snapshot or re-target, not as a failure.
248
270
  Open: on iOS, open <app> without --relaunch dispatches a plain simctl launch, which is idempotent-foreground for an already-running app (it brings the process forward; it does not restart it). open --relaunch restarts the app; on iOS simulators (not real devices or macOS) this collapses to one simctl launch --terminate-running-process call instead of a separate terminate-then-launch, so relaunch is a single step there.
249
271
  Close and runner retention: close keeps a healthy iOS simulator XCTest runner warm by default so the next open on that device skips the runner build, unless --shutdown was requested, the session was recording, the session held a device lease, or the device used a scoped (non-default) simulator set — any of those tear the runner down on close. A retained runner auto-stops after an idle window (default 5 minutes) to release the device's runner lease for other daemons; set AGENT_DEVICE_IOS_RUNNER_IDLE_STOP_MS to override the window, or 0 to disable idle stop and retain until daemon exit.
@@ -632,9 +654,10 @@ Providers:
632
654
  Direct proxy: agent-device connect proxy --daemon-base-url <proxy-agent-device-url> stores the shared proxy profile and client identity.
633
655
  BrowserStack: agent-device connect browserstack stores a local provider profile and creates the App Automate session on first open.
634
656
  AWS Device Farm: agent-device connect aws-device-farm stores a local provider profile and creates the remote access session on first open.
657
+ Limrun: agent-device connect limrun stores a local provider profile and creates a direct iOS or Android instance on first open.
635
658
 
636
659
  Device cloud interfaces:
637
- CLI is the canonical bootstrap path: connect browserstack/aws-device-farm, then use normal open/snapshot/click/close/artifacts/disconnect commands.
660
+ CLI is the canonical bootstrap path: connect limrun/browserstack/aws-device-farm, then use normal open/snapshot/click/close/artifacts/disconnect commands.
638
661
  JavaScript can skip persisted connect state by passing leaseProvider plus provider fields to createAgentDeviceClient or per-command options.
639
662
  MCP exposes operational tools such as open, snapshot, click, close, and artifacts. It does not expose connect/disconnect; run CLI connect first in the same state dir before relying on MCP tools.
640
663
 
@@ -675,6 +698,16 @@ AWS Device Farm hosted-device flow:
675
698
  agent-device artifacts --json
676
699
  agent-device disconnect
677
700
 
701
+ Limrun direct-device flow:
702
+ LIMRUN_API_KEY=...
703
+ agent-device connect limrun --platform android
704
+
705
+ Limrun creates remote iOS simulators and Android emulators only. Do not pass local device selectors such as --udid, --serial, or --device.
706
+ agent-device open com.example.app
707
+ agent-device snapshot -i
708
+ agent-device close
709
+ agent-device disconnect
710
+
678
711
  Local profile flow:
679
712
  agent-device connect --remote-config ./remote-config.json
680
713
  agent-device open com.example.app
@@ -691,9 +724,10 @@ Rules:
691
724
  Use connect without --remote-config when the cloud control plane owns the connection profile.
692
725
  Prefer connect --remote-config over --daemon-base-url, --tenant, --run-id, and --lease-id when using a local profile.
693
726
  Use agent-device proxy for direct tunnel access to a Mac you control. Expose the printed proxy URL through cloudflared/ngrok, then run agent-device connect proxy with the tunnel URL and printed token before normal commands.
694
- Use BrowserStack and AWS Device Farm through local provider profiles; they do not accept a remote agent-device daemon URL.
695
- Device cloud credentials must be available before the command starts. BrowserStack uses BROWSERSTACK_USERNAME and BROWSERSTACK_ACCESS_KEY. AWS Device Farm uses the AWS CLI credential chain, including CI-provided AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY/AWS_SESSION_TOKEN, AWS profiles, or web identity role variables.
696
- Prefer short-lived AWS role credentials in CI. Generated connection profiles store app/device selectors and ARNs, not BrowserStack access keys or AWS credentials.
727
+ Use Limrun, BrowserStack, and AWS Device Farm through local provider profiles; they do not accept a remote agent-device daemon URL.
728
+ Device cloud credentials must be available before the command starts. Limrun uses LIMRUN_API_KEY. BrowserStack uses BROWSERSTACK_USERNAME and BROWSERSTACK_ACCESS_KEY. AWS Device Farm uses the AWS CLI credential chain, including CI-provided AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY/AWS_SESSION_TOKEN, AWS profiles, or web identity role variables.
729
+ Prefer short-lived AWS role credentials in CI. Generated connection profiles store app/device selectors and ARNs, not Limrun API keys, BrowserStack access keys, or AWS credentials.
730
+ Limrun Android supports direct ADB port reverse for local Metro. Limrun iOS requires a public Metro/React DevTools URL because it cannot reach local host ports directly.
697
731
  After closing a device cloud session, run agent-device artifacts --json to retrieve provider video/log/dashboard URLs when the provider has made them available.
698
732
  connect proxy stores the connection profile and client identity. Device leases are acquired on open and expire after five minutes without commands.
699
733
  Multiple agents can share one proxy when each uses connect proxy, open, commands, close, and disconnect.
@@ -868,13 +902,13 @@ Evidence:
868
902
 
869
903
  Report:
870
904
  Summarize what changed, exact validation commands, pass/fail observations, artifact paths, and residual risk.
871
- If live validation is blocked, state the blocker, device/session, and exact next command needed.`}};function h(e,t,n){let r=n.endsWith(`?`),i=r?n.slice(0,-1):n,a=/^[a-z-]+(?:\|[a-z-]+)+$/i.test(i),o=a||t.usageOverride!==void 0&&t.usageOverride.startsWith(`${e} ${i}`);return r?a?`[${i}]`:o?i:`[${i}]`:o?i:`<${i}>`}function g(e,t){return t.listUsageOverride?t.listUsageOverride:[e,...(t.positionalArgs??[]).map(n=>h(e,t,n))].join(` `)}function _(){let n=C(e().map(e=>{let t=r(e);return{name:e,schema:t,usage:g(e,t)}})),i=b(`Global Flags:`,y(t)),a=S(`Agent Starting Point:`,l),o=S(`Agent Quickstart:`,u);return`agent-device <command> [args] [--json]
905
+ If live validation is blocked, state the blocker, device/session, and exact next command needed.`}};function v(e,t,n){let r=n.endsWith(`?`),i=r?n.slice(0,-1):n,a=/^[a-z-]+(?:\|[a-z-]+)+$/i.test(i),o=a||t.usageOverride!==void 0&&t.usageOverride.startsWith(`${e} ${i}`);return r?a?`[${i}]`:o?i:`[${i}]`:o?i:`<${i}>`}function y(e,t){return t.listUsageOverride?t.listUsageOverride:[e,...(t.positionalArgs??[]).map(n=>v(e,t,n))].join(` `)}function b(){let n=E(e().map(e=>{let t=r(e);return{name:e,schema:t,usage:y(e,t)}})),i=C(`Global Flags:`,S(t)),a=T(`Agent Starting Point:`,f),o=T(`Agent Quickstart:`,p);return`agent-device <command> [args] [--json]
872
906
 
873
907
  CLI to automate supported app, device, desktop, and web targets for AI agents.
874
908
 
875
909
  ${a}
876
910
 
877
- ${x(`Agent Workflows:`,c)}
911
+ ${w(`Agent Workflows:`,d)}
878
912
 
879
913
  ${n}
880
914
 
@@ -882,26 +916,26 @@ ${i}
882
916
 
883
917
  ${o}
884
918
 
885
- ${S(`Configuration:`,d)}
919
+ ${T(`Configuration:`,m)}
886
920
 
887
- ${x(`Environment:`,f)}
921
+ ${w(`Environment:`,h)}
888
922
 
889
- ${S(`Examples:`,p)}
890
- `}function v(){return _()}function y(e){return n().filter(t=>e.has(t.key)&&t.usageLabel!==void 0&&t.usageDescription!==void 0)}function b(e,t){return x(e,t.map(e=>({label:e.usageLabel??``,description:e.usageDescription??``})))}function x(e,t){if(t.length===0)return`${e}\n (none)`;let n=Math.max(...t.map(e=>Math.min(e.label.length,26)))+2,r=[e];for(let e of t){let t=e.label.length<=26?e.label.padEnd(n):`${e.label} `;r.push(` ${t}${e.description}`)}return r.join(`
891
- `)}function S(e,t){return t.length===0?`${e}\n (none)`:[e,...t.map(e=>` ${e}`)].join(`
892
- `)}function C(e){return x(`Commands:`,e.map(e=>({label:e.usage,description:e.schema.summary??e.schema.helpDescription})))}function w(e){let t=T(e);if(t)return t;let n=i(e);if(!n)return null;let r=s(e,n),a=y(new Set(n.allowedFlags??[])),o=[];a.length>0&&o.push(b(`Command flags:`,a));let c=o.length>0?`\n\n${o.join(`
923
+ ${T(`Examples:`,g)}
924
+ `}function x(){return b()}function S(e){return n().filter(t=>e.has(t.key)&&t.usageLabel!==void 0&&t.usageDescription!==void 0)}function C(e,t){return w(e,t.map(e=>({label:e.usageLabel??``,description:e.usageDescription??``})))}function w(e,t){if(t.length===0)return`${e}\n (none)`;let n=Math.max(...t.map(e=>Math.min(e.label.length,26)))+2,r=[e];for(let e of t){let t=e.label.length<=26?e.label.padEnd(n):`${e.label} `;r.push(` ${t}${e.description}`)}return r.join(`
925
+ `)}function T(e,t){return t.length===0?`${e}\n (none)`:[e,...t.map(e=>` ${e}`)].join(`
926
+ `)}function E(e){return w(`Commands:`,e.map(e=>({label:e.usage,description:e.schema.summary??e.schema.helpDescription})))}function D(e){let t=O(e);if(t)return t;let n=i(e);if(!n)return null;let r=u(e,n),a=S(new Set(n.allowedFlags??[])),o=[];a.length>0&&o.push(C(`Command flags:`,a));let s=o.length>0?`\n\n${o.join(`
893
927
 
894
928
  `)}`:``;return`agent-device ${r}
895
929
 
896
930
  ${n.helpDescription}
897
931
 
898
932
  Usage:
899
- agent-device ${r}${c}
900
- `}function T(e){let t=m[e];return t?`${t.body}
933
+ agent-device ${r}${s}
934
+ `}function O(e){let t=_[e];return t?`${t.body}
901
935
 
902
936
  Related:
903
937
  agent-device help command list and global flags
904
938
  agent-device help <command> command-specific flags
905
939
  agent-device help manual-qa routine QA loop with concrete command shapes
906
940
  agent-device help workflow full app automation reference
907
- `:null}export{w as buildCommandUsageText,v as buildUsageText};
941
+ `:null}export{D as buildCommandUsageText,x as buildUsageText};
@@ -1,2 +1,2 @@
1
- import{Wn as e}from"./tv-remote.js";import{n as t}from"./registry.js";import{t as n}from"./cli-output.js";import{n as r}from"./command-surface.js";function i(t,n,r){if(t.json){e({success:!0,data:n});return}let i=r?.();i&&a(i)}function a(e){process.stdout.write(e.endsWith(`
1
+ import{Rn as e}from"./tv-remote.js";import{n as t}from"./registry.js";import{n}from"./cli-config.js";import{n as r}from"./command-surface.js";function i(t,n,r){if(t.json){e({success:!0,data:n});return}let i=r?.();i&&a(i)}function a(e){process.stdout.write(e.endsWith(`
2
2
  `)?e:`${e}\n`)}const o=t();function s(e,t,n){let r=o[e];if(!r)throw Error(`Missing CLI reader for command: ${e}`);return r(t,n)}async function c(e){return(await l(e)).result}async function l(e){let t=s(e.command,e.positionals,e.flags),i=await r(e.client,e.command,t);return{result:i,cliOutput:n({name:e.command,input:t,result:i})}}export{i,l as n,s as r,c as t};