execbro 0.0.1-reserve → 1.10.13

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 (475) hide show
  1. package/LICENSE +32 -0
  2. package/README.md +496 -0
  3. package/build/__tests__/helpers/fake-cdp-server.d.ts +56 -0
  4. package/build/__tests__/helpers/fake-cdp-server.d.ts.map +1 -0
  5. package/build/__tests__/helpers/fake-cdp-server.js +120 -0
  6. package/build/__tests__/helpers/fake-cdp-server.js.map +1 -0
  7. package/build/__tests__/helpers/tap-test-helpers.d.ts +36 -0
  8. package/build/__tests__/helpers/tap-test-helpers.d.ts.map +1 -0
  9. package/build/__tests__/helpers/tap-test-helpers.js +130 -0
  10. package/build/__tests__/helpers/tap-test-helpers.js.map +1 -0
  11. package/build/__tests__/integration/auto-reconnect.test.d.ts +2 -0
  12. package/build/__tests__/integration/auto-reconnect.test.d.ts.map +1 -0
  13. package/build/__tests__/integration/auto-reconnect.test.js +118 -0
  14. package/build/__tests__/integration/auto-reconnect.test.js.map +1 -0
  15. package/build/__tests__/integration/connection-health.test.d.ts +2 -0
  16. package/build/__tests__/integration/connection-health.test.d.ts.map +1 -0
  17. package/build/__tests__/integration/connection-health.test.js +157 -0
  18. package/build/__tests__/integration/connection-health.test.js.map +1 -0
  19. package/build/__tests__/integration/execute-in-app.test.d.ts +2 -0
  20. package/build/__tests__/integration/execute-in-app.test.d.ts.map +1 -0
  21. package/build/__tests__/integration/execute-in-app.test.js +124 -0
  22. package/build/__tests__/integration/execute-in-app.test.js.map +1 -0
  23. package/build/__tests__/integration/per-call-timeout.test.d.ts +2 -0
  24. package/build/__tests__/integration/per-call-timeout.test.d.ts.map +1 -0
  25. package/build/__tests__/integration/per-call-timeout.test.js +103 -0
  26. package/build/__tests__/integration/per-call-timeout.test.js.map +1 -0
  27. package/build/__tests__/integration/tap-strategies.test.d.ts +2 -0
  28. package/build/__tests__/integration/tap-strategies.test.d.ts.map +1 -0
  29. package/build/__tests__/integration/tap-strategies.test.js +330 -0
  30. package/build/__tests__/integration/tap-strategies.test.js.map +1 -0
  31. package/build/__tests__/integration/tools.test.d.ts +2 -0
  32. package/build/__tests__/integration/tools.test.d.ts.map +1 -0
  33. package/build/__tests__/integration/tools.test.js +227 -0
  34. package/build/__tests__/integration/tools.test.js.map +1 -0
  35. package/build/__tests__/setup.d.ts +2 -0
  36. package/build/__tests__/setup.d.ts.map +1 -0
  37. package/build/__tests__/setup.js +15 -0
  38. package/build/__tests__/setup.js.map +1 -0
  39. package/build/__tests__/unit/androidEmulatorAvds.test.d.ts +2 -0
  40. package/build/__tests__/unit/androidEmulatorAvds.test.d.ts.map +1 -0
  41. package/build/__tests__/unit/androidEmulatorAvds.test.js +98 -0
  42. package/build/__tests__/unit/androidEmulatorAvds.test.js.map +1 -0
  43. package/build/__tests__/unit/buildToken.test.d.ts +2 -0
  44. package/build/__tests__/unit/buildToken.test.d.ts.map +1 -0
  45. package/build/__tests__/unit/buildToken.test.js +22 -0
  46. package/build/__tests__/unit/buildToken.test.js.map +1 -0
  47. package/build/__tests__/unit/bundle.test.d.ts +2 -0
  48. package/build/__tests__/unit/bundle.test.d.ts.map +1 -0
  49. package/build/__tests__/unit/bundle.test.js +53 -0
  50. package/build/__tests__/unit/bundle.test.js.map +1 -0
  51. package/build/__tests__/unit/burst.test.d.ts +2 -0
  52. package/build/__tests__/unit/burst.test.d.ts.map +1 -0
  53. package/build/__tests__/unit/burst.test.js +54 -0
  54. package/build/__tests__/unit/burst.test.js.map +1 -0
  55. package/build/__tests__/unit/connection-health.test.d.ts +2 -0
  56. package/build/__tests__/unit/connection-health.test.d.ts.map +1 -0
  57. package/build/__tests__/unit/connection-health.test.js +38 -0
  58. package/build/__tests__/unit/connection-health.test.js.map +1 -0
  59. package/build/__tests__/unit/debugGlobals.test.d.ts +2 -0
  60. package/build/__tests__/unit/debugGlobals.test.d.ts.map +1 -0
  61. package/build/__tests__/unit/debugGlobals.test.js +14 -0
  62. package/build/__tests__/unit/debugGlobals.test.js.map +1 -0
  63. package/build/__tests__/unit/deviceDiscovery.test.d.ts +2 -0
  64. package/build/__tests__/unit/deviceDiscovery.test.d.ts.map +1 -0
  65. package/build/__tests__/unit/deviceDiscovery.test.js +119 -0
  66. package/build/__tests__/unit/deviceDiscovery.test.js.map +1 -0
  67. package/build/__tests__/unit/deviceResolver.test.d.ts +2 -0
  68. package/build/__tests__/unit/deviceResolver.test.d.ts.map +1 -0
  69. package/build/__tests__/unit/deviceResolver.test.js +261 -0
  70. package/build/__tests__/unit/deviceResolver.test.js.map +1 -0
  71. package/build/__tests__/unit/escapeNonAscii.test.d.ts +2 -0
  72. package/build/__tests__/unit/escapeNonAscii.test.d.ts.map +1 -0
  73. package/build/__tests__/unit/escapeNonAscii.test.js +76 -0
  74. package/build/__tests__/unit/escapeNonAscii.test.js.map +1 -0
  75. package/build/__tests__/unit/exec.test.d.ts +2 -0
  76. package/build/__tests__/unit/exec.test.d.ts.map +1 -0
  77. package/build/__tests__/unit/exec.test.js +54 -0
  78. package/build/__tests__/unit/exec.test.js.map +1 -0
  79. package/build/__tests__/unit/executor.test.d.ts +2 -0
  80. package/build/__tests__/unit/executor.test.d.ts.map +1 -0
  81. package/build/__tests__/unit/executor.test.js +220 -0
  82. package/build/__tests__/unit/executor.test.js.map +1 -0
  83. package/build/__tests__/unit/failureArtifact.test.d.ts +2 -0
  84. package/build/__tests__/unit/failureArtifact.test.d.ts.map +1 -0
  85. package/build/__tests__/unit/failureArtifact.test.js +207 -0
  86. package/build/__tests__/unit/failureArtifact.test.js.map +1 -0
  87. package/build/__tests__/unit/fastRefreshRecorder.test.d.ts +2 -0
  88. package/build/__tests__/unit/fastRefreshRecorder.test.d.ts.map +1 -0
  89. package/build/__tests__/unit/fastRefreshRecorder.test.js +68 -0
  90. package/build/__tests__/unit/fastRefreshRecorder.test.js.map +1 -0
  91. package/build/__tests__/unit/fastRefreshTools.test.d.ts +2 -0
  92. package/build/__tests__/unit/fastRefreshTools.test.d.ts.map +1 -0
  93. package/build/__tests__/unit/fastRefreshTools.test.js +112 -0
  94. package/build/__tests__/unit/fastRefreshTools.test.js.map +1 -0
  95. package/build/__tests__/unit/fiberMatch.test.d.ts +2 -0
  96. package/build/__tests__/unit/fiberMatch.test.d.ts.map +1 -0
  97. package/build/__tests__/unit/fiberMatch.test.js +114 -0
  98. package/build/__tests__/unit/fiberMatch.test.js.map +1 -0
  99. package/build/__tests__/unit/fingerprint.test.d.ts +2 -0
  100. package/build/__tests__/unit/fingerprint.test.d.ts.map +1 -0
  101. package/build/__tests__/unit/fingerprint.test.js +99 -0
  102. package/build/__tests__/unit/fingerprint.test.js.map +1 -0
  103. package/build/__tests__/unit/focusedInput.test.d.ts +2 -0
  104. package/build/__tests__/unit/focusedInput.test.d.ts.map +1 -0
  105. package/build/__tests__/unit/focusedInput.test.js +58 -0
  106. package/build/__tests__/unit/focusedInput.test.js.map +1 -0
  107. package/build/__tests__/unit/focusedInputTools.test.d.ts +2 -0
  108. package/build/__tests__/unit/focusedInputTools.test.d.ts.map +1 -0
  109. package/build/__tests__/unit/focusedInputTools.test.js +79 -0
  110. package/build/__tests__/unit/focusedInputTools.test.js.map +1 -0
  111. package/build/__tests__/unit/iconSemantics.test.d.ts +2 -0
  112. package/build/__tests__/unit/iconSemantics.test.d.ts.map +1 -0
  113. package/build/__tests__/unit/iconSemantics.test.js +80 -0
  114. package/build/__tests__/unit/iconSemantics.test.js.map +1 -0
  115. package/build/__tests__/unit/imageBuffer.test.d.ts +2 -0
  116. package/build/__tests__/unit/imageBuffer.test.d.ts.map +1 -0
  117. package/build/__tests__/unit/imageBuffer.test.js +128 -0
  118. package/build/__tests__/unit/imageBuffer.test.js.map +1 -0
  119. package/build/__tests__/unit/logs.test.d.ts +2 -0
  120. package/build/__tests__/unit/logs.test.d.ts.map +1 -0
  121. package/build/__tests__/unit/logs.test.js +90 -0
  122. package/build/__tests__/unit/logs.test.js.map +1 -0
  123. package/build/__tests__/unit/measureComponent.test.d.ts +2 -0
  124. package/build/__tests__/unit/measureComponent.test.d.ts.map +1 -0
  125. package/build/__tests__/unit/measureComponent.test.js +66 -0
  126. package/build/__tests__/unit/measureComponent.test.js.map +1 -0
  127. package/build/__tests__/unit/measureComponentTools.test.d.ts +2 -0
  128. package/build/__tests__/unit/measureComponentTools.test.d.ts.map +1 -0
  129. package/build/__tests__/unit/measureComponentTools.test.js +94 -0
  130. package/build/__tests__/unit/measureComponentTools.test.js.map +1 -0
  131. package/build/__tests__/unit/metro.test.d.ts +2 -0
  132. package/build/__tests__/unit/metro.test.d.ts.map +1 -0
  133. package/build/__tests__/unit/metro.test.js +61 -0
  134. package/build/__tests__/unit/metro.test.js.map +1 -0
  135. package/build/__tests__/unit/network.test.d.ts +2 -0
  136. package/build/__tests__/unit/network.test.d.ts.map +1 -0
  137. package/build/__tests__/unit/network.test.js +102 -0
  138. package/build/__tests__/unit/network.test.js.map +1 -0
  139. package/build/__tests__/unit/networkInterceptor.test.d.ts +2 -0
  140. package/build/__tests__/unit/networkInterceptor.test.d.ts.map +1 -0
  141. package/build/__tests__/unit/networkInterceptor.test.js +104 -0
  142. package/build/__tests__/unit/networkInterceptor.test.js.map +1 -0
  143. package/build/__tests__/unit/probe.test.d.ts +2 -0
  144. package/build/__tests__/unit/probe.test.d.ts.map +1 -0
  145. package/build/__tests__/unit/probe.test.js +62 -0
  146. package/build/__tests__/unit/probe.test.js.map +1 -0
  147. package/build/__tests__/unit/rnGlobalsBootstrap.test.d.ts +2 -0
  148. package/build/__tests__/unit/rnGlobalsBootstrap.test.d.ts.map +1 -0
  149. package/build/__tests__/unit/rnGlobalsBootstrap.test.js +29 -0
  150. package/build/__tests__/unit/rnGlobalsBootstrap.test.js.map +1 -0
  151. package/build/__tests__/unit/screenState.test.d.ts +2 -0
  152. package/build/__tests__/unit/screenState.test.d.ts.map +1 -0
  153. package/build/__tests__/unit/screenState.test.js +384 -0
  154. package/build/__tests__/unit/screenState.test.js.map +1 -0
  155. package/build/__tests__/unit/screenshot-diff.test.d.ts +2 -0
  156. package/build/__tests__/unit/screenshot-diff.test.d.ts.map +1 -0
  157. package/build/__tests__/unit/screenshot-diff.test.js +97 -0
  158. package/build/__tests__/unit/screenshot-diff.test.js.map +1 -0
  159. package/build/__tests__/unit/tap-evidence.test.d.ts +2 -0
  160. package/build/__tests__/unit/tap-evidence.test.d.ts.map +1 -0
  161. package/build/__tests__/unit/tap-evidence.test.js +89 -0
  162. package/build/__tests__/unit/tap-evidence.test.js.map +1 -0
  163. package/build/__tests__/unit/tap.test.d.ts +2 -0
  164. package/build/__tests__/unit/tap.test.d.ts.map +1 -0
  165. package/build/__tests__/unit/tap.test.js +767 -0
  166. package/build/__tests__/unit/tap.test.js.map +1 -0
  167. package/build/__tests__/unit/timeoutClamp.test.d.ts +2 -0
  168. package/build/__tests__/unit/timeoutClamp.test.d.ts.map +1 -0
  169. package/build/__tests__/unit/timeoutClamp.test.js +17 -0
  170. package/build/__tests__/unit/timeoutClamp.test.js.map +1 -0
  171. package/build/__tests__/unit/toolDescriptions.test.d.ts +2 -0
  172. package/build/__tests__/unit/toolDescriptions.test.d.ts.map +1 -0
  173. package/build/__tests__/unit/toolDescriptions.test.js +145 -0
  174. package/build/__tests__/unit/toolDescriptions.test.js.map +1 -0
  175. package/build/__tests__/unit/transportClassify.test.d.ts +2 -0
  176. package/build/__tests__/unit/transportClassify.test.d.ts.map +1 -0
  177. package/build/__tests__/unit/transportClassify.test.js +60 -0
  178. package/build/__tests__/unit/transportClassify.test.js.map +1 -0
  179. package/build/__tests__/unit/verifyAction.test.d.ts +2 -0
  180. package/build/__tests__/unit/verifyAction.test.d.ts.map +1 -0
  181. package/build/__tests__/unit/verifyAction.test.js +26 -0
  182. package/build/__tests__/unit/verifyAction.test.js.map +1 -0
  183. package/build/core/android.d.ts +299 -0
  184. package/build/core/android.d.ts.map +1 -0
  185. package/build/core/android.js +1582 -0
  186. package/build/core/android.js.map +1 -0
  187. package/build/core/appDetection.d.ts +20 -0
  188. package/build/core/appDetection.d.ts.map +1 -0
  189. package/build/core/appDetection.js +167 -0
  190. package/build/core/appDetection.js.map +1 -0
  191. package/build/core/bundle.d.ts +49 -0
  192. package/build/core/bundle.d.ts.map +1 -0
  193. package/build/core/bundle.js +356 -0
  194. package/build/core/bundle.js.map +1 -0
  195. package/build/core/componentSearch.d.ts +41 -0
  196. package/build/core/componentSearch.d.ts.map +1 -0
  197. package/build/core/componentSearch.js +588 -0
  198. package/build/core/componentSearch.js.map +1 -0
  199. package/build/core/componentTree.d.ts +13 -0
  200. package/build/core/componentTree.d.ts.map +1 -0
  201. package/build/core/componentTree.js +275 -0
  202. package/build/core/componentTree.js.map +1 -0
  203. package/build/core/config.d.ts +9 -0
  204. package/build/core/config.d.ts.map +1 -0
  205. package/build/core/config.js +28 -0
  206. package/build/core/config.js.map +1 -0
  207. package/build/core/connection.d.ts +102 -0
  208. package/build/core/connection.d.ts.map +1 -0
  209. package/build/core/connection.js +1710 -0
  210. package/build/core/connection.js.map +1 -0
  211. package/build/core/connectionState.d.ts +122 -0
  212. package/build/core/connectionState.d.ts.map +1 -0
  213. package/build/core/connectionState.js +313 -0
  214. package/build/core/connectionState.js.map +1 -0
  215. package/build/core/debugGlobals.d.ts +6 -0
  216. package/build/core/debugGlobals.d.ts.map +1 -0
  217. package/build/core/debugGlobals.js +370 -0
  218. package/build/core/debugGlobals.js.map +1 -0
  219. package/build/core/deviceDiscovery.d.ts +52 -0
  220. package/build/core/deviceDiscovery.d.ts.map +1 -0
  221. package/build/core/deviceDiscovery.js +74 -0
  222. package/build/core/deviceDiscovery.js.map +1 -0
  223. package/build/core/deviceResolver.d.ts +44 -0
  224. package/build/core/deviceResolver.d.ts.map +1 -0
  225. package/build/core/deviceResolver.js +225 -0
  226. package/build/core/deviceResolver.js.map +1 -0
  227. package/build/core/errorScreenParser.d.ts +30 -0
  228. package/build/core/errorScreenParser.d.ts.map +1 -0
  229. package/build/core/errorScreenParser.js +198 -0
  230. package/build/core/errorScreenParser.js.map +1 -0
  231. package/build/core/errors.d.ts +14 -0
  232. package/build/core/errors.d.ts.map +1 -0
  233. package/build/core/errors.js +17 -0
  234. package/build/core/errors.js.map +1 -0
  235. package/build/core/escapeNonAscii.d.ts +18 -0
  236. package/build/core/escapeNonAscii.d.ts.map +1 -0
  237. package/build/core/escapeNonAscii.js +352 -0
  238. package/build/core/escapeNonAscii.js.map +1 -0
  239. package/build/core/exec.d.ts +22 -0
  240. package/build/core/exec.d.ts.map +1 -0
  241. package/build/core/exec.js +54 -0
  242. package/build/core/exec.js.map +1 -0
  243. package/build/core/executor.d.ts +9 -0
  244. package/build/core/executor.d.ts.map +1 -0
  245. package/build/core/executor.js +12 -0
  246. package/build/core/executor.js.map +1 -0
  247. package/build/core/failureArtifact.d.ts +120 -0
  248. package/build/core/failureArtifact.d.ts.map +1 -0
  249. package/build/core/failureArtifact.js +113 -0
  250. package/build/core/failureArtifact.js.map +1 -0
  251. package/build/core/fastRefreshRecorder.d.ts +23 -0
  252. package/build/core/fastRefreshRecorder.d.ts.map +1 -0
  253. package/build/core/fastRefreshRecorder.js +83 -0
  254. package/build/core/fastRefreshRecorder.js.map +1 -0
  255. package/build/core/fastRefreshTools.d.ts +19 -0
  256. package/build/core/fastRefreshTools.d.ts.map +1 -0
  257. package/build/core/fastRefreshTools.js +71 -0
  258. package/build/core/fastRefreshTools.js.map +1 -0
  259. package/build/core/feedback.d.ts +18 -0
  260. package/build/core/feedback.d.ts.map +1 -0
  261. package/build/core/feedback.js +73 -0
  262. package/build/core/feedback.js.map +1 -0
  263. package/build/core/fiberMatch.d.ts +25 -0
  264. package/build/core/fiberMatch.d.ts.map +1 -0
  265. package/build/core/fiberMatch.js +35 -0
  266. package/build/core/fiberMatch.js.map +1 -0
  267. package/build/core/fingerprint.d.ts +4 -0
  268. package/build/core/fingerprint.d.ts.map +1 -0
  269. package/build/core/fingerprint.js +47 -0
  270. package/build/core/fingerprint.js.map +1 -0
  271. package/build/core/focusedInput.d.ts +27 -0
  272. package/build/core/focusedInput.d.ts.map +1 -0
  273. package/build/core/focusedInput.js +110 -0
  274. package/build/core/focusedInput.js.map +1 -0
  275. package/build/core/focusedInputTools.d.ts +21 -0
  276. package/build/core/focusedInputTools.d.ts.map +1 -0
  277. package/build/core/focusedInputTools.js +51 -0
  278. package/build/core/focusedInputTools.js.map +1 -0
  279. package/build/core/format.d.ts +8 -0
  280. package/build/core/format.d.ts.map +1 -0
  281. package/build/core/format.js +34 -0
  282. package/build/core/format.js.map +1 -0
  283. package/build/core/guides.d.ts +21 -0
  284. package/build/core/guides.d.ts.map +1 -0
  285. package/build/core/guides.js +397 -0
  286. package/build/core/guides.js.map +1 -0
  287. package/build/core/iconSemantics.d.ts +18 -0
  288. package/build/core/iconSemantics.d.ts.map +1 -0
  289. package/build/core/iconSemantics.js +114 -0
  290. package/build/core/iconSemantics.js.map +1 -0
  291. package/build/core/imageBuffer.d.ts +37 -0
  292. package/build/core/imageBuffer.d.ts.map +1 -0
  293. package/build/core/imageBuffer.js +58 -0
  294. package/build/core/imageBuffer.js.map +1 -0
  295. package/build/core/index.d.ts +37 -0
  296. package/build/core/index.d.ts.map +1 -0
  297. package/build/core/index.js +66 -0
  298. package/build/core/index.js.map +1 -0
  299. package/build/core/inspector.d.ts +55 -0
  300. package/build/core/inspector.d.ts.map +1 -0
  301. package/build/core/inspector.js +603 -0
  302. package/build/core/inspector.js.map +1 -0
  303. package/build/core/ios.d.ts +263 -0
  304. package/build/core/ios.d.ts.map +1 -0
  305. package/build/core/ios.js +1705 -0
  306. package/build/core/ios.js.map +1 -0
  307. package/build/core/jsExecute.d.ts +62 -0
  308. package/build/core/jsExecute.d.ts.map +1 -0
  309. package/build/core/jsExecute.js +696 -0
  310. package/build/core/jsExecute.js.map +1 -0
  311. package/build/core/license.d.ts +48 -0
  312. package/build/core/license.d.ts.map +1 -0
  313. package/build/core/license.js +267 -0
  314. package/build/core/license.js.map +1 -0
  315. package/build/core/logbox.d.ts +58 -0
  316. package/build/core/logbox.d.ts.map +1 -0
  317. package/build/core/logbox.js +330 -0
  318. package/build/core/logbox.js.map +1 -0
  319. package/build/core/logs.d.ts +44 -0
  320. package/build/core/logs.d.ts.map +1 -0
  321. package/build/core/logs.js +149 -0
  322. package/build/core/logs.js.map +1 -0
  323. package/build/core/measureComponent.d.ts +19 -0
  324. package/build/core/measureComponent.d.ts.map +1 -0
  325. package/build/core/measureComponent.js +94 -0
  326. package/build/core/measureComponent.js.map +1 -0
  327. package/build/core/measureComponentTools.d.ts +6 -0
  328. package/build/core/measureComponentTools.d.ts.map +1 -0
  329. package/build/core/measureComponentTools.js +43 -0
  330. package/build/core/measureComponentTools.js.map +1 -0
  331. package/build/core/metro.d.ts +31 -0
  332. package/build/core/metro.d.ts.map +1 -0
  333. package/build/core/metro.js +124 -0
  334. package/build/core/metro.js.map +1 -0
  335. package/build/core/nativeOnlyHints.d.ts +14 -0
  336. package/build/core/nativeOnlyHints.d.ts.map +1 -0
  337. package/build/core/nativeOnlyHints.js +58 -0
  338. package/build/core/nativeOnlyHints.js.map +1 -0
  339. package/build/core/network.d.ts +43 -0
  340. package/build/core/network.d.ts.map +1 -0
  341. package/build/core/network.js +217 -0
  342. package/build/core/network.js.map +1 -0
  343. package/build/core/networkInterceptor.d.ts +30 -0
  344. package/build/core/networkInterceptor.d.ts.map +1 -0
  345. package/build/core/networkInterceptor.js +211 -0
  346. package/build/core/networkInterceptor.js.map +1 -0
  347. package/build/core/ocr.d.ts +87 -0
  348. package/build/core/ocr.d.ts.map +1 -0
  349. package/build/core/ocr.js +335 -0
  350. package/build/core/ocr.js.map +1 -0
  351. package/build/core/paths.d.ts +2 -0
  352. package/build/core/paths.d.ts.map +1 -0
  353. package/build/core/paths.js +28 -0
  354. package/build/core/paths.js.map +1 -0
  355. package/build/core/posthog.d.ts +5 -0
  356. package/build/core/posthog.d.ts.map +1 -0
  357. package/build/core/posthog.js +52 -0
  358. package/build/core/posthog.js.map +1 -0
  359. package/build/core/pressables.d.ts +46 -0
  360. package/build/core/pressables.d.ts.map +1 -0
  361. package/build/core/pressables.js +1719 -0
  362. package/build/core/pressables.js.map +1 -0
  363. package/build/core/probe.d.ts +12 -0
  364. package/build/core/probe.d.ts.map +1 -0
  365. package/build/core/probe.js +56 -0
  366. package/build/core/probe.js.map +1 -0
  367. package/build/core/redux.d.ts +22 -0
  368. package/build/core/redux.d.ts.map +1 -0
  369. package/build/core/redux.js +116 -0
  370. package/build/core/redux.js.map +1 -0
  371. package/build/core/register.d.ts +8 -0
  372. package/build/core/register.d.ts.map +1 -0
  373. package/build/core/register.js +202 -0
  374. package/build/core/register.js.map +1 -0
  375. package/build/core/rnGlobalsBootstrap.d.ts +16 -0
  376. package/build/core/rnGlobalsBootstrap.d.ts.map +1 -0
  377. package/build/core/rnGlobalsBootstrap.js +116 -0
  378. package/build/core/rnGlobalsBootstrap.js.map +1 -0
  379. package/build/core/screenLayout.d.ts +53 -0
  380. package/build/core/screenLayout.d.ts.map +1 -0
  381. package/build/core/screenLayout.js +878 -0
  382. package/build/core/screenLayout.js.map +1 -0
  383. package/build/core/screenState.d.ts +166 -0
  384. package/build/core/screenState.d.ts.map +1 -0
  385. package/build/core/screenState.js +1352 -0
  386. package/build/core/screenState.js.map +1 -0
  387. package/build/core/sdkBridge.d.ts +67 -0
  388. package/build/core/sdkBridge.d.ts.map +1 -0
  389. package/build/core/sdkBridge.js +148 -0
  390. package/build/core/sdkBridge.js.map +1 -0
  391. package/build/core/state.d.ts +37 -0
  392. package/build/core/state.d.ts.map +1 -0
  393. package/build/core/state.js +124 -0
  394. package/build/core/state.js.map +1 -0
  395. package/build/core/telemetry.d.ts +50 -0
  396. package/build/core/telemetry.d.ts.map +1 -0
  397. package/build/core/telemetry.js +653 -0
  398. package/build/core/telemetry.js.map +1 -0
  399. package/build/core/toolHelpers.d.ts +24 -0
  400. package/build/core/toolHelpers.d.ts.map +1 -0
  401. package/build/core/toolHelpers.js +118 -0
  402. package/build/core/toolHelpers.js.map +1 -0
  403. package/build/core/types.d.ts +162 -0
  404. package/build/core/types.d.ts.map +1 -0
  405. package/build/core/types.js +2 -0
  406. package/build/core/types.js.map +1 -0
  407. package/build/index.d.ts +4 -0
  408. package/build/index.d.ts.map +1 -0
  409. package/build/index.js +132 -0
  410. package/build/index.js.map +1 -0
  411. package/build/pro/screenshot-diff.d.ts +10 -0
  412. package/build/pro/screenshot-diff.d.ts.map +1 -0
  413. package/build/pro/screenshot-diff.js +38 -0
  414. package/build/pro/screenshot-diff.js.map +1 -0
  415. package/build/pro/tap.d.ts +291 -0
  416. package/build/pro/tap.d.ts.map +1 -0
  417. package/build/pro/tap.js +1839 -0
  418. package/build/pro/tap.js.map +1 -0
  419. package/build/pro/verifyAction.d.ts +50 -0
  420. package/build/pro/verifyAction.d.ts.map +1 -0
  421. package/build/pro/verifyAction.js +285 -0
  422. package/build/pro/verifyAction.js.map +1 -0
  423. package/build/tools/_deviceArg.d.ts +45 -0
  424. package/build/tools/_deviceArg.d.ts.map +1 -0
  425. package/build/tools/_deviceArg.js +58 -0
  426. package/build/tools/_deviceArg.js.map +1 -0
  427. package/build/tools/accountTools.d.ts +46 -0
  428. package/build/tools/accountTools.d.ts.map +1 -0
  429. package/build/tools/accountTools.js +223 -0
  430. package/build/tools/accountTools.js.map +1 -0
  431. package/build/tools/bundleTools.d.ts +3 -0
  432. package/build/tools/bundleTools.d.ts.map +1 -0
  433. package/build/tools/bundleTools.js +375 -0
  434. package/build/tools/bundleTools.js.map +1 -0
  435. package/build/tools/componentTools.d.ts +3 -0
  436. package/build/tools/componentTools.d.ts.map +1 -0
  437. package/build/tools/componentTools.js +746 -0
  438. package/build/tools/componentTools.js.map +1 -0
  439. package/build/tools/connectionTools.d.ts +3 -0
  440. package/build/tools/connectionTools.d.ts.map +1 -0
  441. package/build/tools/connectionTools.js +625 -0
  442. package/build/tools/connectionTools.js.map +1 -0
  443. package/build/tools/deviceTools.d.ts +3 -0
  444. package/build/tools/deviceTools.d.ts.map +1 -0
  445. package/build/tools/deviceTools.js +423 -0
  446. package/build/tools/deviceTools.js.map +1 -0
  447. package/build/tools/executionTools.d.ts +3 -0
  448. package/build/tools/executionTools.d.ts.map +1 -0
  449. package/build/tools/executionTools.js +243 -0
  450. package/build/tools/executionTools.js.map +1 -0
  451. package/build/tools/interactionTools.d.ts +3 -0
  452. package/build/tools/interactionTools.d.ts.map +1 -0
  453. package/build/tools/interactionTools.js +539 -0
  454. package/build/tools/interactionTools.js.map +1 -0
  455. package/build/tools/logTools.d.ts +3 -0
  456. package/build/tools/logTools.d.ts.map +1 -0
  457. package/build/tools/logTools.js +371 -0
  458. package/build/tools/logTools.js.map +1 -0
  459. package/build/tools/metaTools.d.ts +7 -0
  460. package/build/tools/metaTools.d.ts.map +1 -0
  461. package/build/tools/metaTools.js +186 -0
  462. package/build/tools/metaTools.js.map +1 -0
  463. package/build/tools/networkTools.d.ts +3 -0
  464. package/build/tools/networkTools.d.ts.map +1 -0
  465. package/build/tools/networkTools.js +478 -0
  466. package/build/tools/networkTools.js.map +1 -0
  467. package/build/tools/reduxTools.d.ts +3 -0
  468. package/build/tools/reduxTools.d.ts.map +1 -0
  469. package/build/tools/reduxTools.js +83 -0
  470. package/build/tools/reduxTools.js.map +1 -0
  471. package/build/tools/screenshotTools.d.ts +3 -0
  472. package/build/tools/screenshotTools.d.ts.map +1 -0
  473. package/build/tools/screenshotTools.js +703 -0
  474. package/build/tools/screenshotTools.js.map +1 -0
  475. package/package.json +63 -2
@@ -0,0 +1,1705 @@
1
+ import { execFile } from "child_process";
2
+ import { promisify } from "util";
3
+ import { existsSync } from "fs";
4
+ import path from "path";
5
+ import os from "os";
6
+ import sharp from "sharp";
7
+ import { getActiveSimulatorUdid } from "./state.js";
8
+ import { notifyDriverMissing } from "./logbox.js";
9
+ import { execAsync } from "./exec.js";
10
+ const execFileAsync = promisify(execFile);
11
+ // simctl command timeout in milliseconds
12
+ const SIMCTL_TIMEOUT = 30000;
13
+ // IDB command timeout in milliseconds
14
+ const IDB_TIMEOUT = 30000;
15
+ // AXe command timeout in milliseconds
16
+ const AXE_TIMEOUT = 30000;
17
+ // Valid button types for IDB ui button command
18
+ export const IOS_BUTTON_TYPES = [
19
+ "HOME",
20
+ "LOCK",
21
+ "SIDE_BUTTON",
22
+ "SIRI",
23
+ "APPLE_PAY",
24
+ ];
25
+ // Track connected IDB simulators to avoid redundant connect calls
26
+ const connectedIdbSimulators = new Set();
27
+ /**
28
+ * Get the IDB executable path
29
+ * Supports IDB_PATH environment variable for custom installations
30
+ */
31
+ function getIdbPath() {
32
+ return process.env.IDB_PATH || "idb";
33
+ }
34
+ /**
35
+ * Get the active iOS UI driver.
36
+ * Defaults to AXe; set IOS_DRIVER=idb in MCP server env to use IDB instead.
37
+ */
38
+ function getIosDriver() {
39
+ const driver = process.env.IOS_DRIVER?.toLowerCase();
40
+ if (driver === "idb")
41
+ return "idb";
42
+ return "axe";
43
+ }
44
+ /**
45
+ * Get the AXe executable path.
46
+ * Supports AXE_PATH environment variable for custom installations.
47
+ */
48
+ function getAxePath() {
49
+ return process.env.AXE_PATH || "axe";
50
+ }
51
+ /**
52
+ * Run AXe command with execFile (no shell) for proper argument handling.
53
+ */
54
+ async function runAxe(...args) {
55
+ const axePath = getAxePath();
56
+ const { stdout, stderr } = await execFileAsync(axePath, args, {
57
+ timeout: AXE_TIMEOUT,
58
+ });
59
+ return {
60
+ stdout: stdout.trim(),
61
+ stderr: stderr.trim(),
62
+ };
63
+ }
64
+ /**
65
+ * Run IDB command with execFile (no shell) for proper argument handling
66
+ * This matches the original ios-simulator-mcp implementation
67
+ */
68
+ async function runIdb(...args) {
69
+ const idbPath = getIdbPath();
70
+ const { stdout, stderr } = await execFileAsync(idbPath, args, {
71
+ timeout: IDB_TIMEOUT,
72
+ });
73
+ return {
74
+ stdout: stdout.trim(),
75
+ stderr: stderr.trim(),
76
+ };
77
+ }
78
+ /**
79
+ * Check if IDB is available
80
+ */
81
+ export async function isIdbAvailable() {
82
+ try {
83
+ await runIdb("--help");
84
+ return true;
85
+ }
86
+ catch {
87
+ return false;
88
+ }
89
+ }
90
+ /**
91
+ * Check if AXe CLI is available
92
+ */
93
+ export async function isAxeAvailable() {
94
+ try {
95
+ await runAxe("--version");
96
+ return true;
97
+ }
98
+ catch {
99
+ return false;
100
+ }
101
+ }
102
+ /**
103
+ * Check if the active iOS UI driver is available.
104
+ * Used by tap.ts for early fail-fast checks.
105
+ */
106
+ export async function isUiDriverAvailable() {
107
+ const driver = getIosDriver();
108
+ return driver === "axe" ? isAxeAvailable() : isIdbAvailable();
109
+ }
110
+ /**
111
+ * Get install instructions for the active iOS UI driver.
112
+ */
113
+ export function getUiDriverInstallHint() {
114
+ const driver = getIosDriver();
115
+ if (driver === "axe") {
116
+ return "AXe is not installed. Install with: brew install cameroncooke/axe/axe\nAXe is the recommended iOS UI driver. See: https://github.com/cameroncooke/AXe";
117
+ }
118
+ return "IDB is not installed. Install with: brew install idb-companion\nTip: Consider switching to AXe (recommended) — brew install cameroncooke/axe/axe and set IOS_DRIVER=axe";
119
+ }
120
+ /**
121
+ * Map IDB button names (uppercase/underscores) to AXe button names (lowercase/hyphens).
122
+ */
123
+ function toAxeButtonName(idbButton) {
124
+ const map = {
125
+ HOME: "home",
126
+ LOCK: "lock",
127
+ SIDE_BUTTON: "side-button",
128
+ SIRI: "siri",
129
+ APPLE_PAY: "apple-pay",
130
+ };
131
+ return map[idbButton];
132
+ }
133
+ /**
134
+ * Ensure IDB is connected to the specified simulator
135
+ * IDB requires `idb connect <UDID>` before any UI commands work
136
+ */
137
+ async function ensureIdbConnected(udid) {
138
+ // Skip if already connected in this session
139
+ if (connectedIdbSimulators.has(udid)) {
140
+ return { success: true };
141
+ }
142
+ try {
143
+ await runIdb("connect", udid);
144
+ connectedIdbSimulators.add(udid);
145
+ return { success: true };
146
+ }
147
+ catch (error) {
148
+ const errorMessage = error instanceof Error ? error.message : String(error);
149
+ // "Already connected" is not an error
150
+ if (errorMessage.includes("already connected") ||
151
+ errorMessage.includes("Connected")) {
152
+ connectedIdbSimulators.add(udid);
153
+ return { success: true };
154
+ }
155
+ return {
156
+ success: false,
157
+ error: `Failed to connect IDB to simulator: ${errorMessage}`,
158
+ };
159
+ }
160
+ }
161
+ /**
162
+ * Shared pre-flight check for all UI driver functions:
163
+ * 1. Check driver availability
164
+ * 2. Resolve target simulator UDID
165
+ * 3. Ensure IDB connection (IDB only)
166
+ */
167
+ async function ensureUiDriverReady(udid) {
168
+ const driver = getIosDriver();
169
+ if (driver === "axe") {
170
+ if (!(await isAxeAvailable())) {
171
+ notifyDriverMissing("ios");
172
+ return {
173
+ ready: false,
174
+ error: { success: false, error: getUiDriverInstallHint() },
175
+ };
176
+ }
177
+ }
178
+ else {
179
+ if (!(await isIdbAvailable())) {
180
+ notifyDriverMissing("ios");
181
+ return {
182
+ ready: false,
183
+ error: { success: false, error: getUiDriverInstallHint() },
184
+ };
185
+ }
186
+ }
187
+ const targetUdid = udid || (await getActiveOrBootedSimulatorUdid());
188
+ if (!targetUdid) {
189
+ return {
190
+ ready: false,
191
+ error: {
192
+ success: false,
193
+ error: await buildNoSimulatorError(),
194
+ },
195
+ };
196
+ }
197
+ if (driver === "idb") {
198
+ const connectResult = await ensureIdbConnected(targetUdid);
199
+ if (!connectResult.success) {
200
+ return {
201
+ ready: false,
202
+ error: { success: false, error: connectResult.error },
203
+ };
204
+ }
205
+ }
206
+ return { ready: true, driver, targetUdid };
207
+ }
208
+ // Shared message for callers that hit a missing-Xcode preflight. Includes
209
+ // the actual install command so the agent (or user) can act without guessing.
210
+ export const XCODE_MISSING_ERROR = "Xcode command line tools are not installed or not selected. " +
211
+ "Run `xcode-select --install` for the CLT, or install full Xcode from the App Store " +
212
+ "and run `sudo xcode-select -s /Applications/Xcode.app/Contents/Developer`. " +
213
+ "simctl ships with full Xcode (the standalone CLT does not include it).";
214
+ // Memoized result — Xcode CLT presence does not change mid-session in
215
+ // practice, and `xcrun simctl help` adds 50–200 ms per ios_* call without
216
+ // caching. Re-resolve once per process; restart the MCP server after install.
217
+ let simctlAvailableCache = null;
218
+ /**
219
+ * Check if simctl is available (cached for the lifetime of the process).
220
+ */
221
+ export async function isSimctlAvailable() {
222
+ if (simctlAvailableCache)
223
+ return simctlAvailableCache;
224
+ simctlAvailableCache = (async () => {
225
+ try {
226
+ await execAsync("xcrun simctl help", { timeout: 5000 });
227
+ return true;
228
+ }
229
+ catch {
230
+ return false;
231
+ }
232
+ })();
233
+ return simctlAvailableCache;
234
+ }
235
+ /**
236
+ * Reset the cached simctl availability check. Test-only.
237
+ */
238
+ export function resetSimctlAvailableCache() {
239
+ simctlAvailableCache = null;
240
+ }
241
+ /**
242
+ * List iOS simulators
243
+ */
244
+ export async function listIOSSimulators(onlyBooted = false) {
245
+ try {
246
+ const simctlAvailable = await isSimctlAvailable();
247
+ if (!simctlAvailable) {
248
+ return {
249
+ success: false,
250
+ error: XCODE_MISSING_ERROR,
251
+ };
252
+ }
253
+ const { stdout } = await execAsync("xcrun simctl list devices -j", {
254
+ timeout: SIMCTL_TIMEOUT,
255
+ });
256
+ const data = JSON.parse(stdout);
257
+ const simulators = [];
258
+ // Parse devices from each runtime
259
+ for (const [runtime, devices] of Object.entries(data.devices)) {
260
+ if (!Array.isArray(devices))
261
+ continue;
262
+ for (const device of devices) {
263
+ if (!device.isAvailable)
264
+ continue;
265
+ if (onlyBooted && device.state !== "Booted")
266
+ continue;
267
+ // Extract iOS version from runtime string
268
+ const runtimeMatch = runtime.match(/iOS[- ](\d+[.-]\d+)/i);
269
+ const runtimeVersion = runtimeMatch
270
+ ? `iOS ${runtimeMatch[1].replace("-", ".")}`
271
+ : runtime;
272
+ simulators.push({
273
+ udid: device.udid,
274
+ name: device.name,
275
+ state: device.state,
276
+ runtime: runtimeVersion,
277
+ deviceType: device.deviceTypeIdentifier,
278
+ isAvailable: device.isAvailable,
279
+ });
280
+ }
281
+ }
282
+ if (simulators.length === 0) {
283
+ return {
284
+ success: true,
285
+ result: onlyBooted
286
+ ? "No booted iOS simulators. Start a simulator first."
287
+ : "No available iOS simulators found.",
288
+ simulators: [],
289
+ };
290
+ }
291
+ // Sort: Booted first, then by name
292
+ simulators.sort((a, b) => {
293
+ if (a.state === "Booted" && b.state !== "Booted")
294
+ return -1;
295
+ if (a.state !== "Booted" && b.state === "Booted")
296
+ return 1;
297
+ return a.name.localeCompare(b.name);
298
+ });
299
+ const formatted = simulators
300
+ .map((s) => {
301
+ const status = s.state === "Booted" ? "🟢 Booted" : "⚪ Shutdown";
302
+ return `${s.name} (${s.runtime}) - ${status}\n UDID: ${s.udid}`;
303
+ })
304
+ .join("\n\n");
305
+ return {
306
+ success: true,
307
+ result: `iOS Simulators:\n\n${formatted}`,
308
+ simulators,
309
+ };
310
+ }
311
+ catch (error) {
312
+ return {
313
+ success: false,
314
+ error: `Failed to list simulators: ${error instanceof Error ? error.message : String(error)}`,
315
+ };
316
+ }
317
+ }
318
+ /**
319
+ * Build the actionable "no booted simulator" error message. Lists up to 6
320
+ * recently-available shutdown simulators by name+UDID and points the agent
321
+ * at `ios_boot_simulator`, so it can recover instead of giving up.
322
+ */
323
+ export async function buildNoSimulatorError() {
324
+ const base = "No iOS simulator is currently running. " +
325
+ "Boot one with `ios_boot_simulator` (pass udid or name), " +
326
+ "or run `xcrun simctl boot <udid>` directly.";
327
+ try {
328
+ const { stdout } = await execAsync("xcrun simctl list devices -j", {
329
+ timeout: SIMCTL_TIMEOUT,
330
+ });
331
+ const data = JSON.parse(stdout);
332
+ const shutdown = [];
333
+ for (const list of Object.values(data.devices)) {
334
+ if (!Array.isArray(list))
335
+ continue;
336
+ for (const d of list) {
337
+ if (d.isAvailable === false)
338
+ continue;
339
+ if (d.state === "Shutdown")
340
+ shutdown.push({ name: d.name, udid: d.udid });
341
+ }
342
+ }
343
+ if (shutdown.length === 0)
344
+ return base;
345
+ const top = shutdown.slice(0, 6).map((d) => ` - ${d.name} (${d.udid})`).join("\n");
346
+ const more = shutdown.length > 6 ? `\n ...and ${shutdown.length - 6} more (run list_ios_simulators)` : "";
347
+ return `${base}\n\nAvailable simulators (shutdown):\n${top}${more}`;
348
+ }
349
+ catch {
350
+ return base;
351
+ }
352
+ }
353
+ /**
354
+ * Get the booted simulator UDID
355
+ */
356
+ export async function getBootedSimulatorUdid() {
357
+ try {
358
+ const { stdout } = await execAsync("xcrun simctl list devices booted -j", {
359
+ timeout: SIMCTL_TIMEOUT,
360
+ });
361
+ const data = JSON.parse(stdout);
362
+ for (const devices of Object.values(data.devices)) {
363
+ if (!Array.isArray(devices))
364
+ continue;
365
+ for (const device of devices) {
366
+ if (device.state === "Booted") {
367
+ return device.udid;
368
+ }
369
+ }
370
+ }
371
+ return null;
372
+ }
373
+ catch {
374
+ return null;
375
+ }
376
+ }
377
+ /**
378
+ * Find a booted simulator's UDID by its device name
379
+ * Matches Metro's deviceName against simulator names from simctl
380
+ */
381
+ export async function findSimulatorByName(deviceName) {
382
+ try {
383
+ const { stdout } = await execAsync("xcrun simctl list devices booted -j", {
384
+ timeout: SIMCTL_TIMEOUT,
385
+ });
386
+ const data = JSON.parse(stdout);
387
+ const normalizedDeviceName = deviceName.toLowerCase().trim();
388
+ for (const devices of Object.values(data.devices)) {
389
+ if (!Array.isArray(devices))
390
+ continue;
391
+ for (const device of devices) {
392
+ if (device.state !== "Booted")
393
+ continue;
394
+ const normalizedSimName = device.name.toLowerCase().trim();
395
+ // Exact match
396
+ if (normalizedSimName === normalizedDeviceName) {
397
+ return device.udid;
398
+ }
399
+ // Partial match (deviceName contains simulator name or vice versa)
400
+ if (normalizedSimName.includes(normalizedDeviceName) ||
401
+ normalizedDeviceName.includes(normalizedSimName)) {
402
+ return device.udid;
403
+ }
404
+ }
405
+ }
406
+ return null;
407
+ }
408
+ catch {
409
+ return null;
410
+ }
411
+ }
412
+ /**
413
+ * Get the active simulator UDID (Metro-connected) or fall back to first booted simulator
414
+ * This enables automatic device scoping based on Metro connection
415
+ */
416
+ export async function getActiveOrBootedSimulatorUdid() {
417
+ // First, check if there's an active Metro-connected simulator
418
+ const activeUdid = getActiveSimulatorUdid();
419
+ if (activeUdid) {
420
+ return activeUdid;
421
+ }
422
+ // Fall back to first booted simulator
423
+ return getBootedSimulatorUdid();
424
+ }
425
+ /**
426
+ * Build device selector for simctl command
427
+ */
428
+ function buildDeviceArg(udid) {
429
+ return udid || "booted";
430
+ }
431
+ /**
432
+ * Take a screenshot from an iOS simulator
433
+ */
434
+ export async function iosScreenshot(outputPath, udid) {
435
+ try {
436
+ const simctlAvailable = await isSimctlAvailable();
437
+ if (!simctlAvailable) {
438
+ return {
439
+ success: false,
440
+ error: XCODE_MISSING_ERROR,
441
+ };
442
+ }
443
+ // Resolve target UDID (prefer Metro-connected simulator)
444
+ const targetUdid = udid || (await getActiveOrBootedSimulatorUdid());
445
+ if (!targetUdid) {
446
+ return {
447
+ success: false,
448
+ error: await buildNoSimulatorError(),
449
+ };
450
+ }
451
+ // Generate output path if not provided
452
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
453
+ const finalOutputPath = outputPath || path.join(os.tmpdir(), `ios-screenshot-${timestamp}.png`);
454
+ await execAsync(`xcrun simctl io ${targetUdid} screenshot "${finalOutputPath}"`, {
455
+ timeout: SIMCTL_TIMEOUT,
456
+ });
457
+ // Resize image if needed (API limit: 2000px max for multi-image requests)
458
+ // Return scale factor so AI can convert image coords to device coords
459
+ const MAX_DIMENSION = 2000;
460
+ const image = sharp(finalOutputPath);
461
+ const metadata = await image.metadata();
462
+ const originalWidth = metadata.width || 0;
463
+ const originalHeight = metadata.height || 0;
464
+ let imageData;
465
+ let scaleFactor = 1;
466
+ if (originalWidth > MAX_DIMENSION || originalHeight > MAX_DIMENSION) {
467
+ // Calculate scale to fit within MAX_DIMENSION
468
+ scaleFactor = Math.max(originalWidth, originalHeight) / MAX_DIMENSION;
469
+ imageData = await image
470
+ .resize(MAX_DIMENSION, MAX_DIMENSION, {
471
+ fit: "inside",
472
+ withoutEnlargement: true,
473
+ })
474
+ .jpeg({ quality: 85 })
475
+ .toBuffer();
476
+ }
477
+ else {
478
+ imageData = await image.jpeg({ quality: 85 }).toBuffer();
479
+ }
480
+ return {
481
+ success: true,
482
+ result: finalOutputPath,
483
+ data: imageData,
484
+ scaleFactor,
485
+ originalWidth,
486
+ originalHeight,
487
+ };
488
+ }
489
+ catch (error) {
490
+ return {
491
+ success: false,
492
+ error: `Failed to capture screenshot: ${error instanceof Error ? error.message : String(error)}`,
493
+ };
494
+ }
495
+ }
496
+ /**
497
+ * Install an app on an iOS simulator
498
+ */
499
+ export async function iosInstallApp(appPath, udid) {
500
+ try {
501
+ const simctlAvailable = await isSimctlAvailable();
502
+ if (!simctlAvailable) {
503
+ return {
504
+ success: false,
505
+ error: XCODE_MISSING_ERROR,
506
+ };
507
+ }
508
+ // Verify app exists
509
+ if (!existsSync(appPath)) {
510
+ return {
511
+ success: false,
512
+ error: `App bundle not found: ${appPath}`,
513
+ };
514
+ }
515
+ // Resolve target UDID (prefer Metro-connected simulator)
516
+ const targetUdid = udid || (await getActiveOrBootedSimulatorUdid());
517
+ if (!targetUdid) {
518
+ return {
519
+ success: false,
520
+ error: await buildNoSimulatorError(),
521
+ };
522
+ }
523
+ await execAsync(`xcrun simctl install ${targetUdid} "${appPath}"`, {
524
+ timeout: 120000, // 2 minute timeout for install
525
+ });
526
+ return {
527
+ success: true,
528
+ result: `Successfully installed ${path.basename(appPath)}`,
529
+ };
530
+ }
531
+ catch (error) {
532
+ return {
533
+ success: false,
534
+ error: `Failed to install app: ${error instanceof Error ? error.message : String(error)}`,
535
+ };
536
+ }
537
+ }
538
+ /**
539
+ * Launch an app on an iOS simulator
540
+ */
541
+ export async function iosLaunchApp(bundleId, udid) {
542
+ try {
543
+ const simctlAvailable = await isSimctlAvailable();
544
+ if (!simctlAvailable) {
545
+ return {
546
+ success: false,
547
+ error: XCODE_MISSING_ERROR,
548
+ };
549
+ }
550
+ // Resolve target UDID (prefer Metro-connected simulator)
551
+ const targetUdid = udid || (await getActiveOrBootedSimulatorUdid());
552
+ if (!targetUdid) {
553
+ return {
554
+ success: false,
555
+ error: await buildNoSimulatorError(),
556
+ };
557
+ }
558
+ await execAsync(`xcrun simctl launch ${targetUdid} ${bundleId}`, {
559
+ timeout: SIMCTL_TIMEOUT,
560
+ });
561
+ return {
562
+ success: true,
563
+ result: `Launched ${bundleId}`,
564
+ };
565
+ }
566
+ catch (error) {
567
+ const msg = error instanceof Error ? error.message : String(error);
568
+ let hint = "";
569
+ if (msg.includes("FBSOpenApplicationErrorDomain")) {
570
+ hint =
571
+ "\n\nCommon causes:\n- App is not installed on this simulator (install it first with ios_install_app)\n- Bundle ID is incorrect (check with: xcrun simctl listapps booted)";
572
+ }
573
+ return {
574
+ success: false,
575
+ error: `Failed to launch app: ${msg}${hint}`,
576
+ };
577
+ }
578
+ }
579
+ /**
580
+ * Open a URL in the iOS simulator
581
+ */
582
+ export async function iosOpenUrl(url, udid) {
583
+ try {
584
+ const simctlAvailable = await isSimctlAvailable();
585
+ if (!simctlAvailable) {
586
+ return {
587
+ success: false,
588
+ error: XCODE_MISSING_ERROR,
589
+ };
590
+ }
591
+ // Resolve target UDID (prefer Metro-connected simulator)
592
+ const targetUdid = udid || (await getActiveOrBootedSimulatorUdid());
593
+ if (!targetUdid) {
594
+ return {
595
+ success: false,
596
+ error: await buildNoSimulatorError(),
597
+ };
598
+ }
599
+ await execAsync(`xcrun simctl openurl ${targetUdid} "${url}"`, {
600
+ timeout: SIMCTL_TIMEOUT,
601
+ });
602
+ return {
603
+ success: true,
604
+ result: `Opened URL: ${url}`,
605
+ };
606
+ }
607
+ catch (error) {
608
+ const msg = error instanceof Error ? error.message : String(error);
609
+ let hint = "";
610
+ if (msg.includes("LSApplicationWorkspaceError") ||
611
+ msg.includes("OpenApplicationErrorDomain")) {
612
+ hint =
613
+ "\n\nCommon causes:\n- Custom URL scheme not registered in the app's Info.plist\n- App that handles this URL scheme is not installed\n- For Expo apps, use the exp:// URL format";
614
+ }
615
+ return {
616
+ success: false,
617
+ error: `Failed to open URL: ${msg}${hint}`,
618
+ };
619
+ }
620
+ }
621
+ /**
622
+ * Terminate an app on an iOS simulator
623
+ */
624
+ export async function iosTerminateApp(bundleId, udid) {
625
+ try {
626
+ const simctlAvailable = await isSimctlAvailable();
627
+ if (!simctlAvailable) {
628
+ return {
629
+ success: false,
630
+ error: XCODE_MISSING_ERROR,
631
+ };
632
+ }
633
+ // Resolve target UDID (prefer Metro-connected simulator)
634
+ const targetUdid = udid || (await getActiveOrBootedSimulatorUdid());
635
+ if (!targetUdid) {
636
+ return {
637
+ success: false,
638
+ error: await buildNoSimulatorError(),
639
+ };
640
+ }
641
+ await execAsync(`xcrun simctl terminate ${targetUdid} ${bundleId}`, {
642
+ timeout: SIMCTL_TIMEOUT,
643
+ });
644
+ return {
645
+ success: true,
646
+ result: `Terminated ${bundleId}`,
647
+ };
648
+ }
649
+ catch (error) {
650
+ return {
651
+ success: false,
652
+ error: `Failed to terminate app: ${error instanceof Error ? error.message : String(error)}`,
653
+ };
654
+ }
655
+ }
656
+ /**
657
+ * Boot an iOS simulator
658
+ */
659
+ export async function iosBootSimulator(udid) {
660
+ try {
661
+ const simctlAvailable = await isSimctlAvailable();
662
+ if (!simctlAvailable) {
663
+ return {
664
+ success: false,
665
+ error: XCODE_MISSING_ERROR,
666
+ };
667
+ }
668
+ await execAsync(`xcrun simctl boot ${udid}`, {
669
+ timeout: 60000, // 1 minute timeout for boot
670
+ });
671
+ // Open Simulator app
672
+ await execAsync("open -a Simulator", { timeout: 10000 }).catch(() => {
673
+ // Ignore if Simulator app doesn't open
674
+ });
675
+ return {
676
+ success: true,
677
+ result: `Simulator ${udid} is now booting`,
678
+ };
679
+ }
680
+ catch (error) {
681
+ const errorMessage = error instanceof Error ? error.message : String(error);
682
+ // Already booted is not an error
683
+ if (errorMessage.includes("Unable to boot device in current state: Booted")) {
684
+ return {
685
+ success: true,
686
+ result: "Simulator is already booted",
687
+ };
688
+ }
689
+ return {
690
+ success: false,
691
+ error: `Failed to boot simulator: ${errorMessage}`,
692
+ };
693
+ }
694
+ }
695
+ // ============================================================================
696
+ // iOS UI Interaction Tools
697
+ // These tools require an iOS UI driver to be installed:
698
+ // Recommended: AXe — brew install cameroncooke/axe/axe
699
+ // Alternative: IDB — brew install idb-companion
700
+ // AXe is the default driver. Set IOS_DRIVER=idb to fall back to IDB.
701
+ // ============================================================================
702
+ /**
703
+ * Tap at coordinates on an iOS simulator using IDB
704
+ */
705
+ export async function iosTap(x, y, options) {
706
+ try {
707
+ const preflight = await ensureUiDriverReady(options?.udid);
708
+ if (!preflight.ready)
709
+ return preflight.error;
710
+ const { driver, targetUdid } = preflight;
711
+ const xRounded = Math.round(x);
712
+ const yRounded = Math.round(y);
713
+ if (driver === "axe") {
714
+ if (options?.duration !== undefined) {
715
+ // Long press: use axe touch with delay (convert ms to seconds)
716
+ const delaySecs = String(options.duration / 1000);
717
+ await runAxe("touch", "-x", String(xRounded), "-y", String(yRounded), "--down", "--up", "--delay", delaySecs, "--udid", targetUdid);
718
+ }
719
+ else {
720
+ await runAxe("tap", "-x", String(xRounded), "-y", String(yRounded), "--udid", targetUdid);
721
+ }
722
+ }
723
+ else {
724
+ const args = ["ui", "tap", "--udid", targetUdid];
725
+ if (options?.duration !== undefined) {
726
+ args.push("--duration", String(options.duration));
727
+ }
728
+ args.push("--json", "--", String(xRounded), String(yRounded));
729
+ const { stderr } = await runIdb(...args);
730
+ if (stderr)
731
+ throw new Error(stderr);
732
+ }
733
+ return {
734
+ success: true,
735
+ result: `Tapped at (${xRounded}, ${yRounded})`,
736
+ };
737
+ }
738
+ catch (error) {
739
+ return {
740
+ success: false,
741
+ error: `Failed to tap: ${error instanceof Error ? error.message : String(error)}`,
742
+ };
743
+ }
744
+ }
745
+ /**
746
+ * Swipe gesture on an iOS simulator
747
+ */
748
+ export async function iosSwipe(startX, startY, endX, endY, options) {
749
+ try {
750
+ const preflight = await ensureUiDriverReady(options?.udid);
751
+ if (!preflight.ready)
752
+ return preflight.error;
753
+ const { driver, targetUdid } = preflight;
754
+ const x1 = Math.round(startX);
755
+ const y1 = Math.round(startY);
756
+ const x2 = Math.round(endX);
757
+ const y2 = Math.round(endY);
758
+ if (driver === "axe") {
759
+ const args = [
760
+ "swipe",
761
+ "--start-x",
762
+ String(x1),
763
+ "--start-y",
764
+ String(y1),
765
+ "--end-x",
766
+ String(x2),
767
+ "--end-y",
768
+ String(y2),
769
+ ];
770
+ if (options?.duration !== undefined) {
771
+ // Both axe and idb expect duration in seconds (matches the MCP tool schema).
772
+ args.push("--duration", String(options.duration));
773
+ }
774
+ if (options?.delta !== undefined) {
775
+ args.push("--delta", String(options.delta));
776
+ }
777
+ args.push("--udid", targetUdid);
778
+ await runAxe(...args);
779
+ }
780
+ else {
781
+ const args = ["ui", "swipe", "--udid", targetUdid];
782
+ if (options?.duration !== undefined) {
783
+ args.push("--duration", String(options.duration));
784
+ }
785
+ if (options?.delta !== undefined) {
786
+ args.push("--delta", String(options.delta));
787
+ }
788
+ args.push("--json", "--", String(x1), String(y1), String(x2), String(y2));
789
+ const { stderr } = await runIdb(...args);
790
+ if (stderr)
791
+ throw new Error(stderr);
792
+ }
793
+ return {
794
+ success: true,
795
+ result: `Swiped from (${x1}, ${y1}) to (${x2}, ${y2})`,
796
+ };
797
+ }
798
+ catch (error) {
799
+ return {
800
+ success: false,
801
+ error: `Failed to swipe: ${error instanceof Error ? error.message : String(error)}`,
802
+ };
803
+ }
804
+ }
805
+ /**
806
+ * Input text into the active field on an iOS simulator
807
+ */
808
+ export async function iosInputText(text, udid) {
809
+ try {
810
+ const preflight = await ensureUiDriverReady(udid);
811
+ if (!preflight.ready)
812
+ return preflight.error;
813
+ const { driver, targetUdid } = preflight;
814
+ if (driver === "axe") {
815
+ await runAxe("type", text, "--udid", targetUdid);
816
+ }
817
+ else {
818
+ const { stderr } = await runIdb("ui", "text", "--udid", targetUdid, text);
819
+ if (stderr)
820
+ throw new Error(stderr);
821
+ }
822
+ return {
823
+ success: true,
824
+ result: `Typed text: "${text}"`,
825
+ };
826
+ }
827
+ catch (error) {
828
+ return {
829
+ success: false,
830
+ error: `Failed to input text: ${error instanceof Error ? error.message : String(error)}`,
831
+ };
832
+ }
833
+ }
834
+ /**
835
+ * Press a hardware button on an iOS simulator
836
+ */
837
+ export async function iosButton(button, options) {
838
+ try {
839
+ // Validate button type
840
+ if (!IOS_BUTTON_TYPES.includes(button)) {
841
+ return {
842
+ success: false,
843
+ error: `Invalid button type: ${button}. Valid options: ${IOS_BUTTON_TYPES.join(", ")}`,
844
+ };
845
+ }
846
+ const preflight = await ensureUiDriverReady(options?.udid);
847
+ if (!preflight.ready)
848
+ return preflight.error;
849
+ const { driver, targetUdid } = preflight;
850
+ if (driver === "axe") {
851
+ const args = ["button", toAxeButtonName(button)];
852
+ if (options?.duration !== undefined) {
853
+ args.push("--duration", String(options.duration));
854
+ }
855
+ args.push("--udid", targetUdid);
856
+ await runAxe(...args);
857
+ }
858
+ else {
859
+ const args = ["ui", "button", "--udid", targetUdid];
860
+ if (options?.duration !== undefined) {
861
+ args.push("--duration", String(options.duration));
862
+ }
863
+ args.push(button);
864
+ const { stderr } = await runIdb(...args);
865
+ if (stderr)
866
+ throw new Error(stderr);
867
+ }
868
+ return {
869
+ success: true,
870
+ result: `Pressed ${button} button`,
871
+ };
872
+ }
873
+ catch (error) {
874
+ return {
875
+ success: false,
876
+ error: `Failed to press button: ${error instanceof Error ? error.message : String(error)}`,
877
+ };
878
+ }
879
+ }
880
+ /**
881
+ * Send a key event to an iOS simulator
882
+ */
883
+ export async function iosKeyEvent(keycode, options) {
884
+ try {
885
+ const preflight = await ensureUiDriverReady(options?.udid);
886
+ if (!preflight.ready)
887
+ return preflight.error;
888
+ const { driver, targetUdid } = preflight;
889
+ if (driver === "axe") {
890
+ const args = ["key", String(keycode)];
891
+ if (options?.duration !== undefined) {
892
+ args.push("--duration", String(options.duration));
893
+ }
894
+ args.push("--udid", targetUdid);
895
+ await runAxe(...args);
896
+ }
897
+ else {
898
+ const args = ["ui", "key", "--udid", targetUdid];
899
+ if (options?.duration !== undefined) {
900
+ args.push("--duration", String(options.duration));
901
+ }
902
+ args.push(String(keycode));
903
+ const { stderr } = await runIdb(...args);
904
+ if (stderr)
905
+ throw new Error(stderr);
906
+ }
907
+ return {
908
+ success: true,
909
+ result: `Sent key event: ${keycode}`,
910
+ };
911
+ }
912
+ catch (error) {
913
+ return {
914
+ success: false,
915
+ error: `Failed to send key event: ${error instanceof Error ? error.message : String(error)}`,
916
+ };
917
+ }
918
+ }
919
+ /**
920
+ * Send a sequence of key events to an iOS simulator
921
+ */
922
+ export async function iosKeySequence(keycodes, udid) {
923
+ try {
924
+ if (!keycodes || keycodes.length === 0) {
925
+ return {
926
+ success: false,
927
+ error: "At least one keycode is required",
928
+ };
929
+ }
930
+ const preflight = await ensureUiDriverReady(udid);
931
+ if (!preflight.ready)
932
+ return preflight.error;
933
+ const { driver, targetUdid } = preflight;
934
+ if (driver === "axe") {
935
+ await runAxe("key-sequence", "--keycodes", keycodes.join(","), "--udid", targetUdid);
936
+ }
937
+ else {
938
+ const args = [
939
+ "ui",
940
+ "key-sequence",
941
+ "--udid",
942
+ targetUdid,
943
+ ...keycodes.map(String),
944
+ ];
945
+ const { stderr } = await runIdb(...args);
946
+ if (stderr)
947
+ throw new Error(stderr);
948
+ }
949
+ return {
950
+ success: true,
951
+ result: `Sent key sequence: ${keycodes.join(", ")}`,
952
+ };
953
+ }
954
+ catch (error) {
955
+ return {
956
+ success: false,
957
+ error: `Failed to send key sequence: ${error instanceof Error ? error.message : String(error)}`,
958
+ };
959
+ }
960
+ }
961
+ /**
962
+ * Format accessibility tree for human-readable output
963
+ */
964
+ function formatAccessibilityTree(elements, depth = 0) {
965
+ const lines = [];
966
+ const indent = " ".repeat(depth);
967
+ for (const element of elements) {
968
+ const parts = [];
969
+ if (element.type)
970
+ parts.push(`[${element.type}]`);
971
+ if (element.AXLabel)
972
+ parts.push(`"${element.AXLabel}"`);
973
+ if (element.AXValue)
974
+ parts.push(`value="${element.AXValue}"`);
975
+ if (element.frame) {
976
+ const f = element.frame;
977
+ const centerX = Math.round(f.x + f.width / 2);
978
+ const centerY = Math.round(f.y + f.height / 2);
979
+ parts.push(`frame=(${f.x}, ${f.y}, ${f.width}x${f.height}) tap=(${centerX}, ${centerY})`);
980
+ }
981
+ if (parts.length > 0) {
982
+ lines.push(`${indent}${parts.join(" ")}`);
983
+ }
984
+ if (element.children && element.children.length > 0) {
985
+ lines.push(formatAccessibilityTree(element.children, depth + 1));
986
+ }
987
+ }
988
+ return lines.join("\n");
989
+ }
990
+ /**
991
+ * Probe the iOS root accessibility element for the top safe-area inset in points.
992
+ * React-native-screens modal/sheet presentations cause measureInWindow to return y
993
+ * relative to the content origin (below the inset) instead of the window. Knowing
994
+ * the inset lets us detect and correct the shifted coordinate space.
995
+ *
996
+ * Cached briefly so a single tap flow doesn't incur multiple describe-ui calls.
997
+ */
998
+ const SAFE_AREA_CACHE_TTL_MS = 5000;
999
+ const safeAreaTopCache = new Map();
1000
+ export async function getIOSSafeAreaTop(udid) {
1001
+ const cacheKey = udid || "__default__";
1002
+ const cached = safeAreaTopCache.get(cacheKey);
1003
+ if (cached && cached.expires > Date.now())
1004
+ return cached.value;
1005
+ let value = 0;
1006
+ try {
1007
+ const preflight = await ensureUiDriverReady(udid);
1008
+ if (preflight.ready) {
1009
+ const { driver, targetUdid } = preflight;
1010
+ let stdout;
1011
+ if (driver === "axe") {
1012
+ stdout = (await runAxe("describe-ui", "--udid", targetUdid)).stdout;
1013
+ }
1014
+ else {
1015
+ const r = await runIdb("ui", "describe-all", "--udid", targetUdid, "--json", "--nested");
1016
+ stdout = r.stdout;
1017
+ }
1018
+ const data = JSON.parse(stdout);
1019
+ const root = Array.isArray(data) ? data[0] : data;
1020
+ const y = root?.frame?.y;
1021
+ if (typeof y === "number" && y > 0)
1022
+ value = Math.round(y);
1023
+ }
1024
+ }
1025
+ catch {
1026
+ // Fall through: value stays 0 (no correction)
1027
+ }
1028
+ safeAreaTopCache.set(cacheKey, { value, expires: Date.now() + SAFE_AREA_CACHE_TTL_MS });
1029
+ return value;
1030
+ }
1031
+ /**
1032
+ * Get accessibility info for the entire screen
1033
+ */
1034
+ export async function iosDescribeAll(udid) {
1035
+ try {
1036
+ const preflight = await ensureUiDriverReady(udid);
1037
+ if (!preflight.ready)
1038
+ return preflight.error;
1039
+ const { driver, targetUdid } = preflight;
1040
+ let stdout;
1041
+ if (driver === "axe") {
1042
+ const result = await runAxe("describe-ui", "--udid", targetUdid);
1043
+ stdout = result.stdout;
1044
+ }
1045
+ else {
1046
+ const result = await runIdb("ui", "describe-all", "--udid", targetUdid, "--json", "--nested");
1047
+ if (result.stderr)
1048
+ throw new Error(result.stderr);
1049
+ stdout = result.stdout;
1050
+ }
1051
+ // Parse JSON response
1052
+ const elements = JSON.parse(stdout);
1053
+ // Format for human-readable output
1054
+ const formatted = formatAccessibilityTree(elements);
1055
+ const header = 'Note: All coordinates are in iOS points. Use tap(text="...") for tapping — do not pass these coordinates to tap(x, y) directly.\n\n';
1056
+ return {
1057
+ success: true,
1058
+ result: header + (formatted || "No accessibility elements found"),
1059
+ elements,
1060
+ };
1061
+ }
1062
+ catch (error) {
1063
+ return {
1064
+ success: false,
1065
+ error: `Failed to describe screen: ${error instanceof Error ? error.message : String(error)}`,
1066
+ };
1067
+ }
1068
+ }
1069
+ /**
1070
+ * I1 (2026-05-16): when a native iOS system sheet (auth prompt, permission request,
1071
+ * Safari sign-in sheet) is on screen, the RN app's fiber tree still describes the
1072
+ * underlying screen — but those pressables aren't interactable until the sheet is
1073
+ * dismissed. Detect the overlay so the screenshot output can warn the agent and
1074
+ * point it at tap(x, y, native=true) for the overlay's actual buttons.
1075
+ *
1076
+ * Heuristic: scan the top three levels of the AXe accessibility tree for an
1077
+ * element whose type matches an iOS sheet/alert role, OR whose AXLabel matches a
1078
+ * well-known system-overlay phrase. False positives are rare because the patterns
1079
+ * are specific to system-presented sheets — RN content rarely uses these labels.
1080
+ */
1081
+ const IOS_SYSTEM_OVERLAY_TYPES = /^(Alert|ActionSheet|Sheet|Dialog|SystemDialog)$/i;
1082
+ const IOS_SYSTEM_OVERLAY_LABEL_PATTERNS = /Wants to Use|Sign in to|Sign In to|Allow .* to (use|access)|Open in Safari|Continue with|Use Face ID|Use Touch ID|Use Passcode/i;
1083
+ /**
1084
+ * Telltale of an RN accessibilityLabel rollup vs a system-presented sheet:
1085
+ * a rollup concatenates multiple child labels with commas / newlines (3+ separators),
1086
+ * while a system sheet's label is a single sentence. Without this guard the label
1087
+ * patterns above false-positive on screens like gifted.co's login wrapper whose
1088
+ * rollup happens to contain "Continue with" amongst many other phrases.
1089
+ */
1090
+ function looksLikeRNLabelRollup(label) {
1091
+ const commas = (label.match(/,/g) ?? []).length;
1092
+ const newlines = (label.match(/\n/g) ?? []).length;
1093
+ return commas + newlines >= 3;
1094
+ }
1095
+ export async function detectIOSSystemOverlay(udid) {
1096
+ try {
1097
+ const desc = await iosDescribeAll(udid);
1098
+ if (!desc.success || !desc.elements)
1099
+ return null;
1100
+ const found = (function walk(elements, depth) {
1101
+ if (depth > 2)
1102
+ return null;
1103
+ for (const el of elements) {
1104
+ if (el.type && IOS_SYSTEM_OVERLAY_TYPES.test(el.type)) {
1105
+ return { type: el.type, label: el.AXLabel ?? "" };
1106
+ }
1107
+ if (el.AXLabel &&
1108
+ IOS_SYSTEM_OVERLAY_LABEL_PATTERNS.test(el.AXLabel) &&
1109
+ !looksLikeRNLabelRollup(el.AXLabel)) {
1110
+ return { type: el.type ?? "Unknown", label: el.AXLabel };
1111
+ }
1112
+ if (el.children && el.children.length > 0) {
1113
+ const nested = walk(el.children, depth + 1);
1114
+ if (nested)
1115
+ return nested;
1116
+ }
1117
+ }
1118
+ return null;
1119
+ })(desc.elements, 0);
1120
+ if (!found)
1121
+ return { detected: false };
1122
+ return { detected: true, description: found.label || found.type };
1123
+ }
1124
+ catch {
1125
+ return null;
1126
+ }
1127
+ }
1128
+ export function formatIOSSystemOverlayWarning(detection) {
1129
+ if (!detection.detected)
1130
+ return "";
1131
+ const desc = detection.description?.slice(0, 120) ?? "system sheet";
1132
+ return (`\n\n⚠️ Native iOS overlay detected: "${desc}". The pressables list above reflects the ` +
1133
+ `React Native screen UNDERNEATH and may be stale. For overlay buttons, take the coordinates ` +
1134
+ `from the screenshot and call tap(x, y, native=true, platform="ios").`);
1135
+ }
1136
+ /**
1137
+ * Get accessibility info at a specific point
1138
+ */
1139
+ export async function iosDescribePoint(x, y, udid) {
1140
+ try {
1141
+ const preflight = await ensureUiDriverReady(udid);
1142
+ if (!preflight.ready)
1143
+ return preflight.error;
1144
+ const { driver, targetUdid } = preflight;
1145
+ const xRounded = Math.round(x);
1146
+ const yRounded = Math.round(y);
1147
+ let stdout;
1148
+ if (driver === "axe") {
1149
+ const result = await runAxe("describe-ui", "--point", `${xRounded},${yRounded}`, "--udid", targetUdid);
1150
+ stdout = result.stdout;
1151
+ }
1152
+ else {
1153
+ const result = await runIdb("ui", "describe-point", "--udid", targetUdid, "--json", "--", String(xRounded), String(yRounded));
1154
+ if (result.stderr)
1155
+ throw new Error(result.stderr);
1156
+ stdout = result.stdout;
1157
+ }
1158
+ // Parse JSON response - may be single element or array
1159
+ let element;
1160
+ try {
1161
+ const parsed = JSON.parse(stdout);
1162
+ element = Array.isArray(parsed) ? parsed[0] : parsed;
1163
+ }
1164
+ catch {
1165
+ return {
1166
+ success: true,
1167
+ result: `No accessibility element found at (${xRounded}, ${yRounded})`,
1168
+ elements: [],
1169
+ };
1170
+ }
1171
+ // Format for human-readable output
1172
+ const parts = [];
1173
+ if (element.type)
1174
+ parts.push(`Type: ${element.type}`);
1175
+ if (element.AXLabel)
1176
+ parts.push(`Label: "${element.AXLabel}"`);
1177
+ if (element.AXValue)
1178
+ parts.push(`Value: "${element.AXValue}"`);
1179
+ if (element.frame) {
1180
+ const f = element.frame;
1181
+ const centerX = Math.round(f.x + f.width / 2);
1182
+ const centerY = Math.round(f.y + f.height / 2);
1183
+ parts.push(`Frame: (${f.x}, ${f.y}) ${f.width}x${f.height}`);
1184
+ parts.push(`Tap: (${centerX}, ${centerY})`);
1185
+ }
1186
+ return {
1187
+ success: true,
1188
+ result: parts.length > 0
1189
+ ? `Element at (${xRounded}, ${yRounded}):\n${parts.join("\n")}`
1190
+ : `No accessibility element found at (${xRounded}, ${yRounded})`,
1191
+ elements: element ? [element] : [],
1192
+ };
1193
+ }
1194
+ catch (error) {
1195
+ return {
1196
+ success: false,
1197
+ error: `Failed to describe point: ${error instanceof Error ? error.message : String(error)}`,
1198
+ };
1199
+ }
1200
+ }
1201
+ /**
1202
+ * Helper to flatten nested accessibility elements
1203
+ */
1204
+ function flattenElements(elements) {
1205
+ const result = [];
1206
+ for (const el of elements) {
1207
+ result.push(el);
1208
+ if (el.children && el.children.length > 0) {
1209
+ result.push(...flattenElements(el.children));
1210
+ }
1211
+ }
1212
+ return result;
1213
+ }
1214
+ /**
1215
+ * Tap an element by its accessibility label using IDB
1216
+ * This simplifies the workflow: no need to manually find coordinates
1217
+ */
1218
+ export async function iosTapElement(options) {
1219
+ try {
1220
+ const { label, labelContains, index = 0, duration, udid } = options;
1221
+ if (!label && !labelContains) {
1222
+ return {
1223
+ success: false,
1224
+ error: "Either 'label' (exact match) or 'labelContains' (partial match) is required",
1225
+ };
1226
+ }
1227
+ // Get all accessibility elements
1228
+ const describeResult = await iosDescribeAll(udid);
1229
+ if (!describeResult.success || !describeResult.elements) {
1230
+ return {
1231
+ success: false,
1232
+ error: describeResult.error || "Failed to get accessibility elements",
1233
+ };
1234
+ }
1235
+ // Flatten the tree and find matching elements
1236
+ const allElements = flattenElements(describeResult.elements);
1237
+ const matches = allElements.filter((el) => {
1238
+ if (!el.AXLabel)
1239
+ return false;
1240
+ if (label)
1241
+ return el.AXLabel === label;
1242
+ if (labelContains)
1243
+ return el.AXLabel.toLowerCase().includes(labelContains.toLowerCase());
1244
+ return false;
1245
+ });
1246
+ if (matches.length === 0) {
1247
+ const searchTerm = label
1248
+ ? `label="${label}"`
1249
+ : `labelContains="${labelContains}"`;
1250
+ return {
1251
+ success: false,
1252
+ error: `No element found with ${searchTerm}`,
1253
+ };
1254
+ }
1255
+ // Select element by index (default 0 = first match)
1256
+ if (index >= matches.length) {
1257
+ return {
1258
+ success: false,
1259
+ error: `Index ${index} out of range. Found ${matches.length} matching element(s).`,
1260
+ };
1261
+ }
1262
+ const element = matches[index];
1263
+ // Check if element has frame coordinates
1264
+ if (!element.frame) {
1265
+ return {
1266
+ success: false,
1267
+ error: `Element "${element.AXLabel}" has no frame coordinates`,
1268
+ };
1269
+ }
1270
+ // Calculate center
1271
+ const centerX = Math.round(element.frame.x + element.frame.width / 2);
1272
+ const centerY = Math.round(element.frame.y + element.frame.height / 2);
1273
+ // Tap at center
1274
+ const tapResult = await iosTap(centerX, centerY, { duration, udid });
1275
+ if (tapResult.success) {
1276
+ return {
1277
+ success: true,
1278
+ result: `Tapped "${element.AXLabel}" at (${centerX}, ${centerY})`,
1279
+ };
1280
+ }
1281
+ return tapResult;
1282
+ }
1283
+ catch (error) {
1284
+ return {
1285
+ success: false,
1286
+ error: `Failed to tap element: ${error instanceof Error ? error.message : String(error)}`,
1287
+ };
1288
+ }
1289
+ }
1290
+ /**
1291
+ * Parse IDB accessibility output into simplified element array
1292
+ */
1293
+ function parseIdbAccessibilityForFindElement(output) {
1294
+ const elements = [];
1295
+ try {
1296
+ const data = JSON.parse(output);
1297
+ const extractElements = (node) => {
1298
+ const frame = node.frame;
1299
+ if (frame) {
1300
+ const element = {
1301
+ label: node.AXLabel || node.label || "",
1302
+ value: node.AXValue || node.value || "",
1303
+ type: node.type || node.AXType || "",
1304
+ identifier: node.AXIdentifier ||
1305
+ node.identifier ||
1306
+ node.AXUniqueId ||
1307
+ "",
1308
+ frame: {
1309
+ x: frame.x || 0,
1310
+ y: frame.y || 0,
1311
+ width: frame.width || 0,
1312
+ height: frame.height || 0,
1313
+ },
1314
+ center: {
1315
+ x: Math.round((frame.x || 0) + (frame.width || 0) / 2),
1316
+ y: Math.round((frame.y || 0) + (frame.height || 0) / 2),
1317
+ },
1318
+ enabled: node.enabled !== false,
1319
+ traits: node.traits || [],
1320
+ };
1321
+ if (element.label ||
1322
+ element.value ||
1323
+ element.type ||
1324
+ element.identifier) {
1325
+ elements.push(element);
1326
+ }
1327
+ }
1328
+ const children = node.children;
1329
+ if (children && Array.isArray(children)) {
1330
+ for (const child of children) {
1331
+ extractElements(child);
1332
+ }
1333
+ }
1334
+ };
1335
+ if (Array.isArray(data)) {
1336
+ for (const item of data) {
1337
+ extractElements(item);
1338
+ }
1339
+ }
1340
+ else {
1341
+ extractElements(data);
1342
+ }
1343
+ }
1344
+ catch {
1345
+ // If JSON parsing fails, return empty array
1346
+ }
1347
+ return elements;
1348
+ }
1349
+ /**
1350
+ * Check if an element's center is within the screen viewport.
1351
+ * Filters out off-screen elements in scroll views, horizontal pagers, etc.
1352
+ */
1353
+ function isIOSElementInViewport(element, screenWidth, screenHeight) {
1354
+ const { center } = element;
1355
+ return (center.x >= 0 &&
1356
+ center.x <= screenWidth &&
1357
+ center.y >= 0 &&
1358
+ center.y <= screenHeight);
1359
+ }
1360
+ /**
1361
+ * Extract screen dimensions from the parsed UI tree.
1362
+ * The root Application element's frame represents the screen size.
1363
+ */
1364
+ function extractIOSScreenSize(rawOutput) {
1365
+ try {
1366
+ const data = JSON.parse(rawOutput);
1367
+ const root = Array.isArray(data) ? data[0] : data;
1368
+ const frame = root?.frame;
1369
+ if (frame?.width && frame?.height) {
1370
+ return { width: frame.width, height: frame.height };
1371
+ }
1372
+ }
1373
+ catch {
1374
+ // Fall through
1375
+ }
1376
+ return null;
1377
+ }
1378
+ /**
1379
+ * Match iOS element against find options
1380
+ */
1381
+ function matchesIOSFindElement(element, options) {
1382
+ if (options.label !== undefined) {
1383
+ if (element.label !== options.label)
1384
+ return false;
1385
+ }
1386
+ if (options.labelContains !== undefined) {
1387
+ if (!element.label.toLowerCase().includes(options.labelContains.toLowerCase()))
1388
+ return false;
1389
+ }
1390
+ if (options.value !== undefined) {
1391
+ if (element.value !== options.value)
1392
+ return false;
1393
+ }
1394
+ if (options.valueContains !== undefined) {
1395
+ if (!element.value.toLowerCase().includes(options.valueContains.toLowerCase()))
1396
+ return false;
1397
+ }
1398
+ if (options.type !== undefined) {
1399
+ if (!element.type.toLowerCase().includes(options.type.toLowerCase()))
1400
+ return false;
1401
+ }
1402
+ if (options.identifier !== undefined) {
1403
+ if (element.identifier !== options.identifier)
1404
+ return false;
1405
+ }
1406
+ if (options.identifierContains !== undefined) {
1407
+ if (!element.identifier
1408
+ .toLowerCase()
1409
+ .includes(options.identifierContains.toLowerCase()))
1410
+ return false;
1411
+ }
1412
+ return true;
1413
+ }
1414
+ /**
1415
+ * Get UI accessibility tree from iOS simulator using IDB (for find_element)
1416
+ */
1417
+ export async function iosGetUITree(udid) {
1418
+ try {
1419
+ const preflight = await ensureUiDriverReady(udid);
1420
+ if (!preflight.ready)
1421
+ return preflight.error;
1422
+ const { driver, targetUdid } = preflight;
1423
+ let stdout;
1424
+ if (driver === "axe") {
1425
+ const result = await runAxe("describe-ui", "--udid", targetUdid);
1426
+ stdout = result.stdout;
1427
+ }
1428
+ else {
1429
+ const result = await runIdb("ui", "describe-all", "--udid", targetUdid);
1430
+ stdout = result.stdout;
1431
+ }
1432
+ const elements = parseIdbAccessibilityForFindElement(stdout);
1433
+ return {
1434
+ success: true,
1435
+ elements,
1436
+ rawOutput: stdout,
1437
+ };
1438
+ }
1439
+ catch (error) {
1440
+ return {
1441
+ success: false,
1442
+ error: `Failed to get UI tree: ${error instanceof Error ? error.message : String(error)}`,
1443
+ };
1444
+ }
1445
+ }
1446
+ /**
1447
+ * Bug 3 (2026-05-16): RN rolls up child Text labels into a parent View's
1448
+ * accessibilityLabel when the parent is marked `accessible`. Result: a screen-root
1449
+ * View whose label is a 200+ char concatenation of every visible text. `labelContains`
1450
+ * matches it, frame is the whole screen, centroid happens to land on whatever button
1451
+ * sits near the middle — and the tap silently lands on the wrong target.
1452
+ *
1453
+ * Heuristic filter: an element is an aggregator wrapper when EITHER
1454
+ * (a) its label is much longer than the query AND contains concatenation hints
1455
+ * (newlines, commas, semicolons — RN rollup typically joins with `, ` or `\n`)
1456
+ * (b) its frame covers >50% of the viewport (a real button never does this)
1457
+ *
1458
+ * Both heuristics together catch the gifted.co LoginScreen wrapper case the doc cites.
1459
+ */
1460
+ function isAggregatorWrapper(element, query, screenSize) {
1461
+ const labelLen = element.label?.length ?? 0;
1462
+ const queryLen = query.length;
1463
+ const isLongConcatenatedLabel = queryLen > 0 &&
1464
+ labelLen > queryLen * 5 &&
1465
+ labelLen > 60 &&
1466
+ /[,;\n]/.test(element.label ?? "");
1467
+ let isFullScreenFrame = false;
1468
+ if (screenSize && element.frame) {
1469
+ const elArea = (element.frame.width ?? 0) * (element.frame.height ?? 0);
1470
+ const screenArea = screenSize.width * screenSize.height;
1471
+ if (screenArea > 0) {
1472
+ isFullScreenFrame = elArea > screenArea * 0.5;
1473
+ }
1474
+ }
1475
+ return isLongConcatenatedLabel || isFullScreenFrame;
1476
+ }
1477
+ /**
1478
+ * Find element(s) in the iOS UI tree matching the given criteria
1479
+ */
1480
+ export async function iosFindElement(options, udid) {
1481
+ try {
1482
+ if (!options.label &&
1483
+ !options.labelContains &&
1484
+ !options.value &&
1485
+ !options.valueContains &&
1486
+ !options.type &&
1487
+ !options.identifier &&
1488
+ !options.identifierContains) {
1489
+ return {
1490
+ success: false,
1491
+ found: false,
1492
+ error: "At least one search criteria (label, labelContains, value, valueContains, identifier, identifierContains, or type) must be provided",
1493
+ };
1494
+ }
1495
+ const treeResult = await iosGetUITree(udid);
1496
+ if (!treeResult.success || !treeResult.elements) {
1497
+ return {
1498
+ success: false,
1499
+ found: false,
1500
+ error: treeResult.error,
1501
+ };
1502
+ }
1503
+ // Filter to viewport-visible elements first, then match criteria.
1504
+ // This prevents selecting off-screen duplicates in scroll views / pagers.
1505
+ const screenSize = treeResult.rawOutput
1506
+ ? extractIOSScreenSize(treeResult.rawOutput)
1507
+ : null;
1508
+ const visibleElements = screenSize
1509
+ ? treeResult.elements.filter((el) => isIOSElementInViewport(el, screenSize.width, screenSize.height))
1510
+ : treeResult.elements;
1511
+ let matches = visibleElements.filter((el) => matchesIOSFindElement(el, options));
1512
+ // If no visible matches, fall back to all elements (element may be partially visible
1513
+ // or the screen size extraction failed)
1514
+ if (matches.length === 0) {
1515
+ matches = treeResult.elements.filter((el) => matchesIOSFindElement(el, options));
1516
+ }
1517
+ // Bug 3 (2026-05-16): when matching by partial string (labelContains / identifierContains),
1518
+ // drop aggregator-wrapper elements that match only because their label rolls up every
1519
+ // visible child's text. Without this filter, tapping such a wrapper's centroid
1520
+ // silently lands on the wrong on-screen element.
1521
+ //
1522
+ // When ALL matches are aggregator wrappers (the common case — RN often rolls labels
1523
+ // up to a single accessible wrapper, leaving no individual element with a matching
1524
+ // label), refuse with a clear error pointing the agent at component= or coordinates.
1525
+ // This is safer than silently tapping the wrapper centroid (which lands wherever the
1526
+ // wrapper's geometric center happens to be — usually NOT on the requested button).
1527
+ if (options.labelContains !== undefined && matches.length > 0) {
1528
+ const filtered = matches.filter((el) => !isAggregatorWrapper(el, options.labelContains, screenSize));
1529
+ if (filtered.length > 0) {
1530
+ matches = filtered;
1531
+ }
1532
+ else {
1533
+ return {
1534
+ success: true,
1535
+ found: false,
1536
+ matchCount: 0,
1537
+ error: `Only matches for labelContains="${options.labelContains}" were aggregator wrappers ` +
1538
+ `(RN accessibilityLabel rollup of a parent View covering the whole screen). ` +
1539
+ `Tapping the wrapper would land on the wrong element. ` +
1540
+ `Try component="ComponentName" instead, or pass coordinates (x, y) from a screenshot.`,
1541
+ };
1542
+ }
1543
+ }
1544
+ if (options.identifierContains !== undefined && matches.length > 0) {
1545
+ const filtered = matches.filter((el) => !isAggregatorWrapper(el, options.identifierContains, screenSize));
1546
+ if (filtered.length > 0) {
1547
+ matches = filtered;
1548
+ }
1549
+ else {
1550
+ return {
1551
+ success: true,
1552
+ found: false,
1553
+ matchCount: 0,
1554
+ error: `Only matches for identifierContains="${options.identifierContains}" were aggregator wrappers. ` +
1555
+ `Try component="ComponentName" or pass coordinates (x, y) from a screenshot.`,
1556
+ };
1557
+ }
1558
+ }
1559
+ if (matches.length === 0) {
1560
+ return {
1561
+ success: true,
1562
+ found: false,
1563
+ matchCount: 0,
1564
+ };
1565
+ }
1566
+ const index = options.index ?? 0;
1567
+ const selectedElement = matches[index];
1568
+ if (!selectedElement) {
1569
+ return {
1570
+ success: true,
1571
+ found: false,
1572
+ matchCount: matches.length,
1573
+ error: `Index ${index} out of bounds. Found ${matches.length} matching element(s).`,
1574
+ };
1575
+ }
1576
+ return {
1577
+ success: true,
1578
+ found: true,
1579
+ element: selectedElement,
1580
+ allMatches: matches,
1581
+ matchCount: matches.length,
1582
+ };
1583
+ }
1584
+ catch (error) {
1585
+ return {
1586
+ success: false,
1587
+ found: false,
1588
+ error: `Failed to find element: ${error instanceof Error ? error.message : String(error)}`,
1589
+ };
1590
+ }
1591
+ }
1592
+ // DPR cache: UDID → device pixel ratio (never changes during session)
1593
+ const dprCache = new Map();
1594
+ /**
1595
+ * Pure calculation: DPR = screenshotPixelWidth / screenPointWidth.
1596
+ * Exported for unit testing.
1597
+ */
1598
+ export function calculateDPR(screenshotPixelWidth, screenPointWidth) {
1599
+ return Math.round(screenshotPixelWidth / screenPointWidth);
1600
+ }
1601
+ /**
1602
+ * Query the actual device pixel ratio for an iOS simulator.
1603
+ * Uses screenshot pixel width vs accessibility root frame width.
1604
+ * Result is cached per UDID for the session.
1605
+ */
1606
+ export async function getDevicePixelRatio(udid) {
1607
+ const cacheKey = udid || "__default__";
1608
+ const cached = dprCache.get(cacheKey);
1609
+ if (cached)
1610
+ return cached;
1611
+ const resolvedUdid = udid || (await getActiveOrBootedSimulatorUdid());
1612
+ if (!resolvedUdid) {
1613
+ throw new Error(await buildNoSimulatorError());
1614
+ }
1615
+ // Take screenshot to get pixel dimensions
1616
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
1617
+ const screenshotPath = path.join(os.tmpdir(), `ios-dpr-screenshot-${timestamp}.png`);
1618
+ await execAsync(`xcrun simctl io ${resolvedUdid} screenshot "${screenshotPath}"`, {
1619
+ timeout: SIMCTL_TIMEOUT,
1620
+ });
1621
+ const metadata = await sharp(screenshotPath).metadata();
1622
+ const pixelWidth = metadata.width;
1623
+ const pixelHeight = metadata.height;
1624
+ // Try accessibility tree for exact DPR, fall back to inference
1625
+ const driver = getIosDriver();
1626
+ const driverOk = driver === "axe" ? await isAxeAvailable() : await isIdbAvailable();
1627
+ if (driverOk) {
1628
+ try {
1629
+ let stdout;
1630
+ if (driver === "axe") {
1631
+ const result = await runAxe("describe-ui", "--udid", resolvedUdid);
1632
+ stdout = result.stdout;
1633
+ }
1634
+ else {
1635
+ await ensureIdbConnected(resolvedUdid);
1636
+ const result = await runIdb("ui", "describe-all", "--udid", resolvedUdid, "--json", "--nested");
1637
+ stdout = result.stdout;
1638
+ }
1639
+ const screenSize = extractIOSScreenSize(stdout);
1640
+ if (screenSize) {
1641
+ const dpr = calculateDPR(pixelWidth, screenSize.width);
1642
+ dprCache.set(cacheKey, dpr);
1643
+ return dpr;
1644
+ }
1645
+ }
1646
+ catch {
1647
+ // driver failed — fall through to inference
1648
+ }
1649
+ }
1650
+ // Fallback: infer DPR from screenshot pixel dimensions
1651
+ const { inferIOSDevicePixelRatio } = await import("./ocr.js");
1652
+ const dpr = inferIOSDevicePixelRatio(pixelWidth, pixelHeight);
1653
+ dprCache.set(cacheKey, dpr);
1654
+ return dpr;
1655
+ }
1656
+ /** Clear DPR cache (for testing or when devices change) */
1657
+ export function clearDPRCache() {
1658
+ dprCache.clear();
1659
+ }
1660
+ /**
1661
+ * Wait for element to appear on iOS screen with polling
1662
+ */
1663
+ export async function iosWaitForElement(options, udid) {
1664
+ const timeoutMs = options.timeoutMs ?? 10000;
1665
+ const pollIntervalMs = options.pollIntervalMs ?? 500;
1666
+ const startTime = Date.now();
1667
+ if (!options.label &&
1668
+ !options.labelContains &&
1669
+ !options.value &&
1670
+ !options.valueContains &&
1671
+ !options.type) {
1672
+ return {
1673
+ success: false,
1674
+ found: false,
1675
+ timedOut: false,
1676
+ error: "At least one search criteria (label, labelContains, value, valueContains, or type) must be provided",
1677
+ };
1678
+ }
1679
+ while (Date.now() - startTime < timeoutMs) {
1680
+ const result = await iosFindElement(options, udid);
1681
+ if (result.found && result.element) {
1682
+ return {
1683
+ ...result,
1684
+ elapsedMs: Date.now() - startTime,
1685
+ timedOut: false,
1686
+ };
1687
+ }
1688
+ if (!result.success) {
1689
+ return {
1690
+ ...result,
1691
+ elapsedMs: Date.now() - startTime,
1692
+ timedOut: false,
1693
+ };
1694
+ }
1695
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
1696
+ }
1697
+ return {
1698
+ success: true,
1699
+ found: false,
1700
+ elapsedMs: Date.now() - startTime,
1701
+ timedOut: true,
1702
+ error: `Timed out after ${timeoutMs}ms waiting for element`,
1703
+ };
1704
+ }
1705
+ //# sourceMappingURL=ios.js.map