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,1582 @@
1
+ import { existsSync } from "fs";
2
+ import path from "path";
3
+ import os from "os";
4
+ import sharp from "sharp";
5
+ import { notifyDriverMissing } from "./logbox.js";
6
+ import { execAsync } from "./exec.js";
7
+ // XML parsing for uiautomator dump
8
+ import { XMLParser } from "fast-xml-parser";
9
+ // ADB command timeout in milliseconds
10
+ const ADB_TIMEOUT = 30000;
11
+ // Module-level cache for ADB availability — checked once per session.
12
+ // `null` = unchecked, boolean = decided. Reset only via `resetAdbAvailabilityCache()` (tests).
13
+ let adbAvailableCache = null;
14
+ let adbPathFixApplied = false;
15
+ /**
16
+ * Standard locations where the Android SDK platform-tools may live when adb is
17
+ * installed but not on the spawned process's PATH (very common on macOS where
18
+ * users add platform-tools only to ~/.zshrc, which GUI-spawned processes don't read).
19
+ */
20
+ function candidateAdbDirs() {
21
+ const dirs = [];
22
+ const env = process.env;
23
+ if (env.ANDROID_HOME)
24
+ dirs.push(path.join(env.ANDROID_HOME, "platform-tools"));
25
+ if (env.ANDROID_SDK_ROOT)
26
+ dirs.push(path.join(env.ANDROID_SDK_ROOT, "platform-tools"));
27
+ const home = os.homedir();
28
+ if (process.platform === "darwin") {
29
+ dirs.push(path.join(home, "Library", "Android", "sdk", "platform-tools"));
30
+ }
31
+ else if (process.platform === "win32") {
32
+ if (env.LOCALAPPDATA)
33
+ dirs.push(path.join(env.LOCALAPPDATA, "Android", "Sdk", "platform-tools"));
34
+ }
35
+ else {
36
+ dirs.push(path.join(home, "Android", "Sdk", "platform-tools"));
37
+ }
38
+ return dirs;
39
+ }
40
+ function adbBinaryName() {
41
+ return process.platform === "win32" ? "adb.exe" : "adb";
42
+ }
43
+ /**
44
+ * Check if ADB is reachable. Caches the result for the lifetime of the process.
45
+ *
46
+ * Resolution order:
47
+ * 1. `adb version` against the inherited PATH.
48
+ * 2. Standard SDK install paths (ANDROID_HOME, ~/Library/Android/sdk/..., etc).
49
+ * If found, prepend the directory to process.env.PATH so subsequent bare
50
+ * `adb` invocations work without any call-site changes.
51
+ *
52
+ * The recovery path covers the common case of macOS users with Android Studio
53
+ * installed but adb missing from PATH because ~/.zshrc isn't sourced by GUI-spawned
54
+ * processes (Claude Code, VS Code MCP, etc).
55
+ */
56
+ export async function isAdbAvailable() {
57
+ if (adbAvailableCache !== null)
58
+ return adbAvailableCache;
59
+ try {
60
+ await execAsync("adb version", { timeout: 5000 });
61
+ adbAvailableCache = true;
62
+ return true;
63
+ }
64
+ catch {
65
+ // Fall through to path discovery below.
66
+ }
67
+ if (!adbPathFixApplied) {
68
+ for (const dir of candidateAdbDirs()) {
69
+ const candidate = path.join(dir, adbBinaryName());
70
+ if (!existsSync(candidate))
71
+ continue;
72
+ const sep = process.platform === "win32" ? ";" : ":";
73
+ process.env.PATH = `${dir}${sep}${process.env.PATH ?? ""}`;
74
+ adbPathFixApplied = true;
75
+ try {
76
+ await execAsync("adb version", { timeout: 5000 });
77
+ adbAvailableCache = true;
78
+ return true;
79
+ }
80
+ catch {
81
+ // Found the binary but it didn't run — try next candidate.
82
+ continue;
83
+ }
84
+ }
85
+ }
86
+ adbAvailableCache = false;
87
+ return false;
88
+ }
89
+ /**
90
+ * Test-only: clear the ADB availability cache so the next call re-probes.
91
+ */
92
+ export function resetAdbAvailabilityCache() {
93
+ adbAvailableCache = null;
94
+ adbPathFixApplied = false;
95
+ }
96
+ const ADB_MISSING_ERROR = "ADB is not installed or not in PATH. To fix:\n1. Install Android SDK Platform Tools: https://developer.android.com/tools/releases/platform-tools\n2. Add to PATH: export PATH=$PATH:~/Library/Android/sdk/platform-tools\n3. Verify: run 'adb devices' in terminal";
97
+ /**
98
+ * Check ADB availability and push a LogBox notification if missing.
99
+ * Returns an AdbResult error if ADB is unavailable, or null if ready.
100
+ */
101
+ async function requireAdb() {
102
+ if (await isAdbAvailable())
103
+ return null;
104
+ notifyDriverMissing("android");
105
+ return { success: false, error: ADB_MISSING_ERROR };
106
+ }
107
+ /**
108
+ * List connected Android devices
109
+ */
110
+ export async function listAndroidDevices() {
111
+ try {
112
+ const adbMissing = await requireAdb();
113
+ if (adbMissing)
114
+ return adbMissing;
115
+ const { stdout } = await execAsync("adb devices -l", { timeout: ADB_TIMEOUT });
116
+ const lines = stdout.trim().split("\n");
117
+ // Skip the "List of devices attached" header
118
+ const deviceLines = lines.slice(1).filter((line) => line.trim().length > 0);
119
+ if (deviceLines.length === 0) {
120
+ return {
121
+ success: true,
122
+ result: "No Android devices connected."
123
+ };
124
+ }
125
+ const devices = deviceLines.map((line) => {
126
+ const parts = line.trim().split(/\s+/);
127
+ const id = parts[0];
128
+ const status = parts[1];
129
+ const device = { id, status };
130
+ // Parse additional info like product:xxx model:xxx device:xxx transport_id:xxx
131
+ for (let i = 2; i < parts.length; i++) {
132
+ const [key, value] = parts[i].split(":");
133
+ if (key === "product")
134
+ device.product = value;
135
+ else if (key === "model")
136
+ device.model = value;
137
+ else if (key === "device")
138
+ device.device = value;
139
+ else if (key === "transport_id")
140
+ device.transportId = value;
141
+ }
142
+ return device;
143
+ });
144
+ const formatted = devices
145
+ .map((d) => {
146
+ let info = `${d.id} (${d.status})`;
147
+ if (d.model)
148
+ info += ` - ${d.model.replace(/_/g, " ")}`;
149
+ if (d.product)
150
+ info += ` [${d.product}]`;
151
+ return info;
152
+ })
153
+ .join("\n");
154
+ return {
155
+ success: true,
156
+ result: `Connected Android devices:\n${formatted}`,
157
+ devices
158
+ };
159
+ }
160
+ catch (error) {
161
+ return {
162
+ success: false,
163
+ error: `Failed to list devices: ${error instanceof Error ? error.message : String(error)}`
164
+ };
165
+ }
166
+ }
167
+ /**
168
+ * Get the first connected Android device ID
169
+ */
170
+ export async function getDefaultAndroidDevice() {
171
+ try {
172
+ const { stdout } = await execAsync("adb devices", { timeout: ADB_TIMEOUT });
173
+ const lines = stdout.trim().split("\n");
174
+ const deviceLines = lines.slice(1).filter((line) => line.trim().length > 0);
175
+ for (const line of deviceLines) {
176
+ const [id, status] = line.trim().split(/\s+/);
177
+ if (status === "device") {
178
+ return id;
179
+ }
180
+ }
181
+ return null;
182
+ }
183
+ catch {
184
+ return null;
185
+ }
186
+ }
187
+ /**
188
+ * Locate the `emulator` binary. Returns the command string to invoke it
189
+ * (either bare "emulator" if it's on PATH, or a quoted absolute path).
190
+ * Returns null when the binary cannot be located. Mirrors `isAdbAvailable`'s
191
+ * PATH → ANDROID_HOME → common-install-paths discovery order.
192
+ */
193
+ async function findEmulatorBinary() {
194
+ try {
195
+ await execAsync("emulator -help", { timeout: 3000 });
196
+ return "emulator";
197
+ }
198
+ catch {
199
+ // not on PATH — try standard install locations
200
+ }
201
+ const env = process.env;
202
+ const candidates = [];
203
+ if (env.ANDROID_HOME)
204
+ candidates.push(path.join(env.ANDROID_HOME, "emulator", "emulator"));
205
+ if (env.ANDROID_SDK_ROOT)
206
+ candidates.push(path.join(env.ANDROID_SDK_ROOT, "emulator", "emulator"));
207
+ candidates.push(path.join(os.homedir(), "Library", "Android", "sdk", "emulator", "emulator"));
208
+ for (const candidate of candidates) {
209
+ try {
210
+ await execAsync(`"${candidate}" -help`, { timeout: 3000 });
211
+ return `"${candidate}"`;
212
+ }
213
+ catch {
214
+ // try next
215
+ }
216
+ }
217
+ return null;
218
+ }
219
+ /**
220
+ * List all Android Virtual Devices defined on the host machine, regardless of
221
+ * whether they are currently running.
222
+ *
223
+ * Returns `[]` when adb is missing, the emulator binary cannot be located, or
224
+ * the listing fails. Callers should treat that as "no inventory available"
225
+ * rather than an error so the surrounding tool can still surface other
226
+ * platforms.
227
+ */
228
+ export async function getAndroidEmulatorAvds() {
229
+ const adbMissing = await requireAdb();
230
+ if (adbMissing)
231
+ return [];
232
+ const emulatorBin = await findEmulatorBinary();
233
+ if (!emulatorBin)
234
+ return [];
235
+ try {
236
+ const { stdout } = await execAsync(`${emulatorBin} -list-avds`, { timeout: ADB_TIMEOUT });
237
+ return stdout
238
+ .split("\n")
239
+ .map((line) => line.trim())
240
+ .filter((line) => line.length > 0 && !line.includes("Storing crashdata"));
241
+ }
242
+ catch {
243
+ return [];
244
+ }
245
+ }
246
+ /**
247
+ * Map a running emulator's AVD name to its adb serial (e.g. "emulator-5554").
248
+ *
249
+ * Strategy: list adb devices, filter to `emulator-NNNN` serials, then call
250
+ * `adb -s <serial> emu avd name` per serial until one matches the requested
251
+ * AVD name. Returns `null` if no running emulator matches or if adb is
252
+ * unavailable. This is the slow path for the resolver — callers should prefer
253
+ * the cached registry entry first.
254
+ */
255
+ export async function getAdbIdForAvd(avdName) {
256
+ const adbMissing = await requireAdb();
257
+ if (adbMissing)
258
+ return null;
259
+ try {
260
+ const { stdout: devicesOut } = await execAsync("adb devices", { timeout: ADB_TIMEOUT });
261
+ const lines = devicesOut.trim().split("\n").slice(1); // skip "List of devices attached" header
262
+ const emulatorSerials = lines
263
+ .map((line) => line.trim().split(/\s+/)[0])
264
+ .filter((id) => /^emulator-\d+$/.test(id));
265
+ const target = avdName.trim();
266
+ for (const serial of emulatorSerials) {
267
+ try {
268
+ const { stdout } = await execAsync(`adb -s ${serial} emu avd name`, {
269
+ timeout: ADB_TIMEOUT
270
+ });
271
+ const reported = stdout.split("\n")[0]?.trim() ?? "";
272
+ if (reported === target)
273
+ return serial;
274
+ }
275
+ catch {
276
+ // skip this serial, try next
277
+ }
278
+ }
279
+ return null;
280
+ }
281
+ catch {
282
+ return null;
283
+ }
284
+ }
285
+ /**
286
+ * Build device selector for ADB command
287
+ */
288
+ function buildDeviceArg(deviceId) {
289
+ return deviceId ? `-s ${deviceId}` : "";
290
+ }
291
+ /**
292
+ * Take a screenshot from an Android device
293
+ */
294
+ export async function androidScreenshot(outputPath, deviceId, signal) {
295
+ try {
296
+ const adbMissing = await requireAdb();
297
+ if (adbMissing)
298
+ return adbMissing;
299
+ const device = deviceId || (await getDefaultAndroidDevice());
300
+ if (!device) {
301
+ return {
302
+ success: false,
303
+ error: "No Android device connected. Connect a device or start an emulator."
304
+ };
305
+ }
306
+ const deviceArg = buildDeviceArg(device);
307
+ // Generate output path if not provided
308
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
309
+ const finalOutputPath = outputPath || path.join(os.tmpdir(), `android-screenshot-${timestamp}.png`);
310
+ // Capture screenshot on device
311
+ const remotePath = "/sdcard/screenshot-temp.png";
312
+ await execAsync(`adb ${deviceArg} shell screencap -p ${remotePath}`, {
313
+ timeout: ADB_TIMEOUT,
314
+ signal
315
+ });
316
+ // Pull screenshot to local machine
317
+ await execAsync(`adb ${deviceArg} pull ${remotePath} "${finalOutputPath}"`, {
318
+ timeout: ADB_TIMEOUT,
319
+ signal
320
+ });
321
+ // Clean up remote file
322
+ await execAsync(`adb ${deviceArg} shell rm ${remotePath}`, {
323
+ timeout: ADB_TIMEOUT,
324
+ signal
325
+ }).catch(() => {
326
+ // Ignore cleanup errors
327
+ });
328
+ // Resize image if needed (API limit: 2000px max for multi-image requests)
329
+ // Return scale factor so AI can convert image coords to device coords
330
+ const MAX_DIMENSION = 2000;
331
+ const image = sharp(finalOutputPath);
332
+ const metadata = await image.metadata();
333
+ const originalWidth = metadata.width || 0;
334
+ const originalHeight = metadata.height || 0;
335
+ let imageData;
336
+ let scaleFactor = 1;
337
+ if (originalWidth > MAX_DIMENSION || originalHeight > MAX_DIMENSION) {
338
+ // Calculate scale to fit within MAX_DIMENSION
339
+ scaleFactor = Math.max(originalWidth, originalHeight) / MAX_DIMENSION;
340
+ imageData = await image
341
+ .resize(MAX_DIMENSION, MAX_DIMENSION, {
342
+ fit: "inside",
343
+ withoutEnlargement: true
344
+ })
345
+ .jpeg({ quality: 85 })
346
+ .toBuffer();
347
+ }
348
+ else {
349
+ imageData = await image
350
+ .jpeg({ quality: 85 })
351
+ .toBuffer();
352
+ }
353
+ return {
354
+ success: true,
355
+ result: finalOutputPath,
356
+ data: imageData,
357
+ scaleFactor,
358
+ originalWidth,
359
+ originalHeight
360
+ };
361
+ }
362
+ catch (error) {
363
+ return {
364
+ success: false,
365
+ error: `Failed to capture screenshot: ${error instanceof Error ? error.message : String(error)}`
366
+ };
367
+ }
368
+ }
369
+ /**
370
+ * Install an APK on an Android device
371
+ */
372
+ export async function androidInstallApp(apkPath, deviceId, options) {
373
+ try {
374
+ const adbMissing = await requireAdb();
375
+ if (adbMissing)
376
+ return adbMissing;
377
+ // Verify APK exists
378
+ if (!existsSync(apkPath)) {
379
+ return {
380
+ success: false,
381
+ error: `APK file not found: ${apkPath}`
382
+ };
383
+ }
384
+ const device = deviceId || (await getDefaultAndroidDevice());
385
+ if (!device) {
386
+ return {
387
+ success: false,
388
+ error: "No Android device connected. Connect a device or start an emulator."
389
+ };
390
+ }
391
+ const deviceArg = buildDeviceArg(device);
392
+ // Build install flags
393
+ const flags = [];
394
+ if (options?.replace)
395
+ flags.push("-r");
396
+ if (options?.grantPermissions)
397
+ flags.push("-g");
398
+ const flagsStr = flags.length > 0 ? flags.join(" ") + " " : "";
399
+ const { stdout, stderr } = await execAsync(`adb ${deviceArg} install ${flagsStr}"${apkPath}"`, { timeout: 120000 } // 2 minute timeout for install
400
+ );
401
+ const output = stdout + stderr;
402
+ if (output.includes("Success")) {
403
+ return {
404
+ success: true,
405
+ result: `Successfully installed ${path.basename(apkPath)}`
406
+ };
407
+ }
408
+ else {
409
+ return {
410
+ success: false,
411
+ error: output.trim() || "Installation failed with unknown error"
412
+ };
413
+ }
414
+ }
415
+ catch (error) {
416
+ return {
417
+ success: false,
418
+ error: `Failed to install app: ${error instanceof Error ? error.message : String(error)}`
419
+ };
420
+ }
421
+ }
422
+ /**
423
+ * Launch an app on an Android device
424
+ */
425
+ export async function androidLaunchApp(packageName, activityName, deviceId) {
426
+ try {
427
+ const adbMissing = await requireAdb();
428
+ if (adbMissing)
429
+ return adbMissing;
430
+ const device = deviceId || (await getDefaultAndroidDevice());
431
+ if (!device) {
432
+ return {
433
+ success: false,
434
+ error: "No Android device connected. Connect a device or start an emulator."
435
+ };
436
+ }
437
+ const deviceArg = buildDeviceArg(device);
438
+ let command;
439
+ if (activityName) {
440
+ // Launch specific activity
441
+ command = `adb ${deviceArg} shell am start -n ${packageName}/${activityName}`;
442
+ }
443
+ else {
444
+ // Launch main/launcher activity
445
+ command = `adb ${deviceArg} shell monkey -p ${packageName} -c android.intent.category.LAUNCHER 1`;
446
+ }
447
+ const { stdout, stderr } = await execAsync(command, { timeout: ADB_TIMEOUT });
448
+ const output = stdout + stderr;
449
+ // Check for errors
450
+ if (output.includes("Error") || output.includes("Exception")) {
451
+ return {
452
+ success: false,
453
+ error: output.trim()
454
+ };
455
+ }
456
+ return {
457
+ success: true,
458
+ result: `Launched ${packageName}${activityName ? `/${activityName}` : ""}`
459
+ };
460
+ }
461
+ catch (error) {
462
+ return {
463
+ success: false,
464
+ error: `Failed to launch app: ${error instanceof Error ? error.message : String(error)}`
465
+ };
466
+ }
467
+ }
468
+ /**
469
+ * Get list of installed packages on the device
470
+ */
471
+ export async function androidListPackages(deviceId, filter) {
472
+ try {
473
+ const adbMissing = await requireAdb();
474
+ if (adbMissing)
475
+ return adbMissing;
476
+ const device = deviceId || (await getDefaultAndroidDevice());
477
+ if (!device) {
478
+ return {
479
+ success: false,
480
+ error: "No Android device connected. Connect a device or start an emulator."
481
+ };
482
+ }
483
+ const deviceArg = buildDeviceArg(device);
484
+ const { stdout } = await execAsync(`adb ${deviceArg} shell pm list packages`, {
485
+ timeout: ADB_TIMEOUT
486
+ });
487
+ let packages = stdout
488
+ .trim()
489
+ .split("\n")
490
+ .map((line) => line.replace("package:", "").trim())
491
+ .filter((pkg) => pkg.length > 0);
492
+ if (filter) {
493
+ const filterLower = filter.toLowerCase();
494
+ packages = packages.filter((pkg) => pkg.toLowerCase().includes(filterLower));
495
+ }
496
+ if (packages.length === 0) {
497
+ return {
498
+ success: true,
499
+ result: filter ? `No packages found matching "${filter}"` : "No packages found"
500
+ };
501
+ }
502
+ return {
503
+ success: true,
504
+ result: `Installed packages${filter ? ` matching "${filter}"` : ""}:\n${packages.join("\n")}`
505
+ };
506
+ }
507
+ catch (error) {
508
+ return {
509
+ success: false,
510
+ error: `Failed to list packages: ${error instanceof Error ? error.message : String(error)}`
511
+ };
512
+ }
513
+ }
514
+ // ============================================================================
515
+ // UI Input Functions (Phase 2)
516
+ // ============================================================================
517
+ /**
518
+ * Common key event codes for Android
519
+ */
520
+ export const ANDROID_KEY_EVENTS = {
521
+ HOME: 3,
522
+ BACK: 4,
523
+ CALL: 5,
524
+ END_CALL: 6,
525
+ VOLUME_UP: 24,
526
+ VOLUME_DOWN: 25,
527
+ POWER: 26,
528
+ CAMERA: 27,
529
+ CLEAR: 28,
530
+ TAB: 61,
531
+ ENTER: 66,
532
+ DEL: 67,
533
+ MENU: 82,
534
+ SEARCH: 84,
535
+ MEDIA_PLAY_PAUSE: 85,
536
+ MEDIA_STOP: 86,
537
+ MEDIA_NEXT: 87,
538
+ MEDIA_PREVIOUS: 88,
539
+ MOVE_HOME: 122,
540
+ MOVE_END: 123,
541
+ APP_SWITCH: 187,
542
+ ESCAPE: 111
543
+ };
544
+ /**
545
+ * Tap at coordinates on an Android device
546
+ */
547
+ export async function androidTap(x, y, deviceId) {
548
+ try {
549
+ const adbMissing = await requireAdb();
550
+ if (adbMissing)
551
+ return adbMissing;
552
+ const device = deviceId || (await getDefaultAndroidDevice());
553
+ if (!device) {
554
+ return {
555
+ success: false,
556
+ error: "No Android device connected. Connect a device or start an emulator."
557
+ };
558
+ }
559
+ const deviceArg = buildDeviceArg(device);
560
+ await execAsync(`adb ${deviceArg} shell input tap ${Math.round(x)} ${Math.round(y)}`, {
561
+ timeout: ADB_TIMEOUT
562
+ });
563
+ return {
564
+ success: true,
565
+ result: `Tapped at (${Math.round(x)}, ${Math.round(y)})`
566
+ };
567
+ }
568
+ catch (error) {
569
+ return {
570
+ success: false,
571
+ error: `Failed to tap: ${error instanceof Error ? error.message : String(error)}`
572
+ };
573
+ }
574
+ }
575
+ /**
576
+ * Long press at coordinates on an Android device
577
+ */
578
+ export async function androidLongPress(x, y, durationMs = 1000, deviceId) {
579
+ try {
580
+ const adbMissing = await requireAdb();
581
+ if (adbMissing)
582
+ return adbMissing;
583
+ const device = deviceId || (await getDefaultAndroidDevice());
584
+ if (!device) {
585
+ return {
586
+ success: false,
587
+ error: "No Android device connected. Connect a device or start an emulator."
588
+ };
589
+ }
590
+ const deviceArg = buildDeviceArg(device);
591
+ // Long press is implemented as a swipe from the same point to the same point
592
+ const xRounded = Math.round(x);
593
+ const yRounded = Math.round(y);
594
+ await execAsync(`adb ${deviceArg} shell input swipe ${xRounded} ${yRounded} ${xRounded} ${yRounded} ${durationMs}`, { timeout: ADB_TIMEOUT + durationMs });
595
+ return {
596
+ success: true,
597
+ result: `Long pressed at (${xRounded}, ${yRounded}) for ${durationMs}ms`
598
+ };
599
+ }
600
+ catch (error) {
601
+ return {
602
+ success: false,
603
+ error: `Failed to long press: ${error instanceof Error ? error.message : String(error)}`
604
+ };
605
+ }
606
+ }
607
+ /**
608
+ * Swipe on an Android device
609
+ */
610
+ export async function androidSwipe(startX, startY, endX, endY, durationMs = 300, deviceId) {
611
+ try {
612
+ const adbMissing = await requireAdb();
613
+ if (adbMissing)
614
+ return adbMissing;
615
+ const device = deviceId || (await getDefaultAndroidDevice());
616
+ if (!device) {
617
+ return {
618
+ success: false,
619
+ error: "No Android device connected. Connect a device or start an emulator."
620
+ };
621
+ }
622
+ const deviceArg = buildDeviceArg(device);
623
+ const x1 = Math.round(startX);
624
+ const y1 = Math.round(startY);
625
+ const x2 = Math.round(endX);
626
+ const y2 = Math.round(endY);
627
+ await execAsync(`adb ${deviceArg} shell input swipe ${x1} ${y1} ${x2} ${y2} ${durationMs}`, { timeout: ADB_TIMEOUT + durationMs });
628
+ return {
629
+ success: true,
630
+ result: `Swiped from (${x1}, ${y1}) to (${x2}, ${y2}) in ${durationMs}ms`
631
+ };
632
+ }
633
+ catch (error) {
634
+ return {
635
+ success: false,
636
+ error: `Failed to swipe: ${error instanceof Error ? error.message : String(error)}`
637
+ };
638
+ }
639
+ }
640
+ /**
641
+ * Input text on an Android device
642
+ *
643
+ * ADB input text has limitations with special characters.
644
+ * This function handles escaping properly for URLs, emails, and special strings.
645
+ */
646
+ export async function androidInputText(text, deviceId) {
647
+ try {
648
+ const adbMissing = await requireAdb();
649
+ if (adbMissing)
650
+ return adbMissing;
651
+ const device = deviceId || (await getDefaultAndroidDevice());
652
+ if (!device) {
653
+ return {
654
+ success: false,
655
+ error: "No Android device connected. Connect a device or start an emulator."
656
+ };
657
+ }
658
+ const deviceArg = buildDeviceArg(device);
659
+ // For complex strings with special characters, type character by character
660
+ // using key events for reliability
661
+ const hasComplexChars = /[/:?=&#@%+]/.test(text);
662
+ if (hasComplexChars) {
663
+ // Use character-by-character input for strings with special chars
664
+ // This is slower but more reliable for URLs, emails, etc.
665
+ for (const char of text) {
666
+ let keyCmd;
667
+ // Map special characters to their escaped form or use direct input
668
+ switch (char) {
669
+ case " ":
670
+ keyCmd = `adb ${deviceArg} shell input text "%s"`;
671
+ break;
672
+ case "'":
673
+ // Single quote needs special handling
674
+ keyCmd = `adb ${deviceArg} shell input text "\\'"`;
675
+ break;
676
+ case '"':
677
+ keyCmd = `adb ${deviceArg} shell input text '\\"'`;
678
+ break;
679
+ case "\\":
680
+ keyCmd = `adb ${deviceArg} shell input text "\\\\"`;
681
+ break;
682
+ case "&":
683
+ keyCmd = `adb ${deviceArg} shell input text "\\&"`;
684
+ break;
685
+ case "|":
686
+ keyCmd = `adb ${deviceArg} shell input text "\\|"`;
687
+ break;
688
+ case ";":
689
+ keyCmd = `adb ${deviceArg} shell input text "\\;"`;
690
+ break;
691
+ case "<":
692
+ keyCmd = `adb ${deviceArg} shell input text "\\<"`;
693
+ break;
694
+ case ">":
695
+ keyCmd = `adb ${deviceArg} shell input text "\\>"`;
696
+ break;
697
+ case "(":
698
+ keyCmd = `adb ${deviceArg} shell input text "\\("`;
699
+ break;
700
+ case ")":
701
+ keyCmd = `adb ${deviceArg} shell input text "\\)"`;
702
+ break;
703
+ case "$":
704
+ keyCmd = `adb ${deviceArg} shell input text "\\$"`;
705
+ break;
706
+ case "`":
707
+ keyCmd = `adb ${deviceArg} shell input text "\\\`"`;
708
+ break;
709
+ case "#":
710
+ // # is a shell comment character — single quotes don't prevent interpretation
711
+ // on some Android shell versions, so use backslash escaping in double quotes
712
+ keyCmd = `adb ${deviceArg} shell input text "\\#"`;
713
+ break;
714
+ default:
715
+ // For most characters, wrap in single quotes to prevent shell interpretation
716
+ // Single quotes preserve literal meaning of all characters except single quote itself
717
+ keyCmd = `adb ${deviceArg} shell input text '${char}'`;
718
+ }
719
+ await execAsync(keyCmd, { timeout: 5000 });
720
+ }
721
+ return {
722
+ success: true,
723
+ result: `Typed: "${text}"`
724
+ };
725
+ }
726
+ // For simple alphanumeric strings, use the faster bulk input
727
+ // Escape basic special characters
728
+ const escapedText = text
729
+ .replace(/\\/g, "\\\\")
730
+ .replace(/"/g, '\\"')
731
+ .replace(/'/g, "\\'")
732
+ .replace(/`/g, "\\`")
733
+ .replace(/\$/g, "\\$")
734
+ .replace(/ /g, "%s");
735
+ await execAsync(`adb ${deviceArg} shell input text "${escapedText}"`, {
736
+ timeout: ADB_TIMEOUT
737
+ });
738
+ return {
739
+ success: true,
740
+ result: `Typed: "${text}"`
741
+ };
742
+ }
743
+ catch (error) {
744
+ return {
745
+ success: false,
746
+ error: `Failed to input text: ${error instanceof Error ? error.message : String(error)}`
747
+ };
748
+ }
749
+ }
750
+ /**
751
+ * Send a key event to an Android device
752
+ */
753
+ export async function androidKeyEvent(keyCode, deviceId) {
754
+ try {
755
+ const adbMissing = await requireAdb();
756
+ if (adbMissing)
757
+ return adbMissing;
758
+ const device = deviceId || (await getDefaultAndroidDevice());
759
+ if (!device) {
760
+ return {
761
+ success: false,
762
+ error: "No Android device connected. Connect a device or start an emulator."
763
+ };
764
+ }
765
+ const deviceArg = buildDeviceArg(device);
766
+ // Resolve key code from name if needed
767
+ const resolvedKeyCode = typeof keyCode === "string" ? ANDROID_KEY_EVENTS[keyCode] : keyCode;
768
+ if (resolvedKeyCode === undefined) {
769
+ return {
770
+ success: false,
771
+ error: `Invalid key code: ${keyCode}`
772
+ };
773
+ }
774
+ await execAsync(`adb ${deviceArg} shell input keyevent ${resolvedKeyCode}`, {
775
+ timeout: ADB_TIMEOUT
776
+ });
777
+ // Get key name for display
778
+ const keyName = typeof keyCode === "string"
779
+ ? keyCode
780
+ : Object.entries(ANDROID_KEY_EVENTS).find(([_, v]) => v === keyCode)?.[0] ||
781
+ `keycode ${keyCode}`;
782
+ return {
783
+ success: true,
784
+ result: `Sent key event: ${keyName}`
785
+ };
786
+ }
787
+ catch (error) {
788
+ return {
789
+ success: false,
790
+ error: `Failed to send key event: ${error instanceof Error ? error.message : String(error)}`
791
+ };
792
+ }
793
+ }
794
+ /**
795
+ * Parse bounds string like "[0,0][1080,1920]" to AndroidUIElement bounds
796
+ */
797
+ function parseBoundsForUIElement(boundsStr) {
798
+ const match = boundsStr.match(/\[(\d+),(\d+)\]\[(\d+),(\d+)\]/);
799
+ if (!match)
800
+ return null;
801
+ const left = parseInt(match[1], 10);
802
+ const top = parseInt(match[2], 10);
803
+ const right = parseInt(match[3], 10);
804
+ const bottom = parseInt(match[4], 10);
805
+ return {
806
+ left,
807
+ top,
808
+ right,
809
+ bottom,
810
+ width: right - left,
811
+ height: bottom - top
812
+ };
813
+ }
814
+ /**
815
+ * Parse uiautomator XML dump into element array
816
+ */
817
+ function parseUIAutomatorXML(xml) {
818
+ const elements = [];
819
+ // Match all node elements with their attributes
820
+ const nodeRegex = /<node\s+([^>]+)\/?>|<node\s+([^>]+)>/g;
821
+ let match;
822
+ while ((match = nodeRegex.exec(xml)) !== null) {
823
+ const attrStr = match[1] || match[2];
824
+ if (!attrStr)
825
+ continue;
826
+ // Extract attributes
827
+ const getAttr = (name) => {
828
+ const attrMatch = attrStr.match(new RegExp(`${name}="([^"]*)"`));
829
+ return attrMatch ? attrMatch[1] : "";
830
+ };
831
+ const boundsStr = getAttr("bounds");
832
+ const bounds = parseBoundsForUIElement(boundsStr);
833
+ if (!bounds)
834
+ continue;
835
+ const element = {
836
+ text: getAttr("text"),
837
+ contentDesc: getAttr("content-desc"),
838
+ resourceId: getAttr("resource-id"),
839
+ className: getAttr("class"),
840
+ bounds,
841
+ center: {
842
+ x: Math.round((bounds.left + bounds.right) / 2),
843
+ y: Math.round((bounds.top + bounds.bottom) / 2)
844
+ },
845
+ clickable: getAttr("clickable") === "true",
846
+ enabled: getAttr("enabled") === "true",
847
+ focused: getAttr("focused") === "true",
848
+ scrollable: getAttr("scrollable") === "true",
849
+ selected: getAttr("selected") === "true"
850
+ };
851
+ elements.push(element);
852
+ }
853
+ return elements;
854
+ }
855
+ /**
856
+ * Match element against find options
857
+ */
858
+ /**
859
+ * Check if an element's center is within the screen viewport.
860
+ * Filters out off-screen elements in scroll views, pagers, etc.
861
+ */
862
+ function isAndroidElementInViewport(element, screenWidth, screenHeight) {
863
+ const { center } = element;
864
+ return center.x >= 0 && center.x <= screenWidth && center.y >= 0 && center.y <= screenHeight;
865
+ }
866
+ /**
867
+ * Extract screen dimensions from the uiautomator XML.
868
+ * The root hierarchy node's bounds represent the screen size.
869
+ */
870
+ function extractAndroidScreenSize(rawXml) {
871
+ // Match the first node's bounds (root element = screen)
872
+ const match = rawXml.match(/<(?:hierarchy|node)[^>]*bounds="\[0,0\]\[(\d+),(\d+)\]"/);
873
+ if (match) {
874
+ return { width: parseInt(match[1], 10), height: parseInt(match[2], 10) };
875
+ }
876
+ return null;
877
+ }
878
+ function matchesElement(element, options) {
879
+ if (options.text !== undefined) {
880
+ if (element.text !== options.text)
881
+ return false;
882
+ }
883
+ if (options.textContains !== undefined) {
884
+ if (!element.text.toLowerCase().includes(options.textContains.toLowerCase()))
885
+ return false;
886
+ }
887
+ if (options.contentDesc !== undefined) {
888
+ if (element.contentDesc !== options.contentDesc)
889
+ return false;
890
+ }
891
+ if (options.contentDescContains !== undefined) {
892
+ if (!element.contentDesc.toLowerCase().includes(options.contentDescContains.toLowerCase()))
893
+ return false;
894
+ }
895
+ if (options.resourceId !== undefined) {
896
+ // Support both full "com.app:id/button" and short "button" forms
897
+ const shortId = element.resourceId.split("/").pop() || "";
898
+ if (element.resourceId !== options.resourceId && shortId !== options.resourceId)
899
+ return false;
900
+ }
901
+ return true;
902
+ }
903
+ /**
904
+ * Get UI accessibility tree from Android device using uiautomator
905
+ */
906
+ export async function androidGetUITree(deviceId, signal) {
907
+ try {
908
+ const adbMissing = await requireAdb();
909
+ if (adbMissing)
910
+ return adbMissing;
911
+ const device = deviceId || (await getDefaultAndroidDevice());
912
+ if (!device) {
913
+ return {
914
+ success: false,
915
+ error: "No Android device connected. Connect a device or start an emulator."
916
+ };
917
+ }
918
+ const deviceArg = buildDeviceArg(device);
919
+ // Dump UI hierarchy to device. The `signal` is passed so a strategy-level
920
+ // timeout in tap.ts can SIGKILL the host adb process — the device-side
921
+ // uiautomator dump is independent and finishes silently, but we stop
922
+ // waiting on the ADB channel so the next strategy can use it.
923
+ const remotePath = "/sdcard/ui_dump.xml";
924
+ await execAsync(`adb ${deviceArg} shell uiautomator dump ${remotePath}`, {
925
+ timeout: ADB_TIMEOUT,
926
+ signal
927
+ });
928
+ // Read the XML content
929
+ const { stdout } = await execAsync(`adb ${deviceArg} shell cat ${remotePath}`, {
930
+ timeout: ADB_TIMEOUT,
931
+ signal
932
+ });
933
+ // Clean up remote file
934
+ await execAsync(`adb ${deviceArg} shell rm ${remotePath}`, {
935
+ timeout: ADB_TIMEOUT,
936
+ signal
937
+ }).catch(() => { });
938
+ const elements = parseUIAutomatorXML(stdout);
939
+ return {
940
+ success: true,
941
+ elements,
942
+ rawXml: stdout
943
+ };
944
+ }
945
+ catch (error) {
946
+ return {
947
+ success: false,
948
+ error: `Failed to get UI tree: ${error instanceof Error ? error.message : String(error)}`
949
+ };
950
+ }
951
+ }
952
+ /**
953
+ * Find element(s) in the UI tree matching the given criteria
954
+ */
955
+ export async function androidFindElement(options, deviceId, signal) {
956
+ try {
957
+ // Validate that at least one search criteria is provided
958
+ if (!options.text && !options.textContains && !options.contentDesc &&
959
+ !options.contentDescContains && !options.resourceId) {
960
+ return {
961
+ success: false,
962
+ found: false,
963
+ error: "At least one search criteria (text, textContains, contentDesc, contentDescContains, or resourceId) must be provided"
964
+ };
965
+ }
966
+ const treeResult = await androidGetUITree(deviceId, signal);
967
+ if (!treeResult.success || !treeResult.elements) {
968
+ return {
969
+ success: false,
970
+ found: false,
971
+ error: treeResult.error
972
+ };
973
+ }
974
+ // Filter to viewport-visible elements first, then match criteria.
975
+ // This prevents selecting off-screen duplicates in scroll views / pagers.
976
+ const screenSize = treeResult.rawXml ? extractAndroidScreenSize(treeResult.rawXml) : null;
977
+ const visibleElements = screenSize
978
+ ? treeResult.elements.filter(el => isAndroidElementInViewport(el, screenSize.width, screenSize.height))
979
+ : treeResult.elements;
980
+ let matches = visibleElements.filter(el => matchesElement(el, options));
981
+ // If no visible matches, fall back to all elements (element may be partially visible
982
+ // or the screen size extraction failed)
983
+ if (matches.length === 0) {
984
+ matches = treeResult.elements.filter(el => matchesElement(el, options));
985
+ }
986
+ if (matches.length === 0) {
987
+ return {
988
+ success: true,
989
+ found: false,
990
+ matchCount: 0
991
+ };
992
+ }
993
+ // Select the element at the specified index (default 0)
994
+ const index = options.index ?? 0;
995
+ const selectedElement = matches[index];
996
+ if (!selectedElement) {
997
+ return {
998
+ success: true,
999
+ found: false,
1000
+ matchCount: matches.length,
1001
+ error: `Index ${index} out of bounds. Found ${matches.length} matching element(s).`
1002
+ };
1003
+ }
1004
+ return {
1005
+ success: true,
1006
+ found: true,
1007
+ element: selectedElement,
1008
+ allMatches: matches,
1009
+ matchCount: matches.length
1010
+ };
1011
+ }
1012
+ catch (error) {
1013
+ return {
1014
+ success: false,
1015
+ found: false,
1016
+ error: `Failed to find element: ${error instanceof Error ? error.message : String(error)}`
1017
+ };
1018
+ }
1019
+ }
1020
+ /**
1021
+ * Wait for element to appear on screen with polling
1022
+ */
1023
+ export async function androidWaitForElement(options, deviceId) {
1024
+ const timeoutMs = options.timeoutMs ?? 10000;
1025
+ const pollIntervalMs = options.pollIntervalMs ?? 500;
1026
+ const startTime = Date.now();
1027
+ // Validate that at least one search criteria is provided
1028
+ if (!options.text && !options.textContains && !options.contentDesc &&
1029
+ !options.contentDescContains && !options.resourceId) {
1030
+ return {
1031
+ success: false,
1032
+ found: false,
1033
+ timedOut: false,
1034
+ error: "At least one search criteria (text, textContains, contentDesc, contentDescContains, or resourceId) must be provided"
1035
+ };
1036
+ }
1037
+ while (Date.now() - startTime < timeoutMs) {
1038
+ const result = await androidFindElement(options, deviceId);
1039
+ if (result.found && result.element) {
1040
+ return {
1041
+ ...result,
1042
+ elapsedMs: Date.now() - startTime,
1043
+ timedOut: false
1044
+ };
1045
+ }
1046
+ // If there was an error (not just "not found"), return it
1047
+ if (!result.success) {
1048
+ return {
1049
+ ...result,
1050
+ elapsedMs: Date.now() - startTime,
1051
+ timedOut: false
1052
+ };
1053
+ }
1054
+ // Wait before next poll
1055
+ await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
1056
+ }
1057
+ return {
1058
+ success: true,
1059
+ found: false,
1060
+ elapsedMs: Date.now() - startTime,
1061
+ timedOut: true,
1062
+ error: `Timed out after ${timeoutMs}ms waiting for element`
1063
+ };
1064
+ }
1065
+ /**
1066
+ * Get device screen size
1067
+ */
1068
+ export async function androidGetScreenSize(deviceId) {
1069
+ try {
1070
+ const adbMissing = await requireAdb();
1071
+ if (adbMissing)
1072
+ return adbMissing;
1073
+ const device = deviceId || (await getDefaultAndroidDevice());
1074
+ if (!device) {
1075
+ return {
1076
+ success: false,
1077
+ error: "No Android device connected. Connect a device or start an emulator."
1078
+ };
1079
+ }
1080
+ const deviceArg = buildDeviceArg(device);
1081
+ const { stdout } = await execAsync(`adb ${deviceArg} shell wm size`, {
1082
+ timeout: ADB_TIMEOUT
1083
+ });
1084
+ // Parse output like "Physical size: 1080x1920"
1085
+ const match = stdout.match(/(\d+)x(\d+)/);
1086
+ if (match) {
1087
+ return {
1088
+ success: true,
1089
+ width: parseInt(match[1], 10),
1090
+ height: parseInt(match[2], 10)
1091
+ };
1092
+ }
1093
+ return {
1094
+ success: false,
1095
+ error: `Could not parse screen size from: ${stdout.trim()}`
1096
+ };
1097
+ }
1098
+ catch (error) {
1099
+ return {
1100
+ success: false,
1101
+ error: `Failed to get screen size: ${error instanceof Error ? error.message : String(error)}`
1102
+ };
1103
+ }
1104
+ }
1105
+ /**
1106
+ * Get device display density (dpi)
1107
+ */
1108
+ export async function androidGetDensity(deviceId) {
1109
+ try {
1110
+ const adbMissing = await requireAdb();
1111
+ if (adbMissing)
1112
+ return adbMissing;
1113
+ const device = deviceId || (await getDefaultAndroidDevice());
1114
+ if (!device) {
1115
+ return {
1116
+ success: false,
1117
+ error: "No Android device connected. Connect a device or start an emulator."
1118
+ };
1119
+ }
1120
+ const deviceArg = buildDeviceArg(device);
1121
+ const { stdout } = await execAsync(`adb ${deviceArg} shell wm density`, {
1122
+ timeout: ADB_TIMEOUT
1123
+ });
1124
+ // Parse output like "Physical density: 440" or "Override density: 440"
1125
+ const match = stdout.match(/density:\s*(\d+)/i);
1126
+ if (match) {
1127
+ return {
1128
+ success: true,
1129
+ density: parseInt(match[1], 10)
1130
+ };
1131
+ }
1132
+ return {
1133
+ success: false,
1134
+ error: `Could not parse density from: ${stdout.trim()}`
1135
+ };
1136
+ }
1137
+ catch (error) {
1138
+ return {
1139
+ success: false,
1140
+ error: `Failed to get density: ${error instanceof Error ? error.message : String(error)}`
1141
+ };
1142
+ }
1143
+ }
1144
+ /**
1145
+ * Get status bar height in pixels
1146
+ * Android status bar is typically 24dp, but can vary by device/OS version
1147
+ */
1148
+ export async function androidGetStatusBarHeight(deviceId) {
1149
+ try {
1150
+ // Get density first
1151
+ const densityResult = await androidGetDensity(deviceId);
1152
+ if (!densityResult.success || !densityResult.density) {
1153
+ // Fallback to common estimate
1154
+ return {
1155
+ success: true,
1156
+ heightPixels: 63, // Common for 420dpi devices (24dp * 2.625)
1157
+ heightDp: 24
1158
+ };
1159
+ }
1160
+ const density = densityResult.density;
1161
+ const densityScale = density / 160; // Android baseline is 160dpi
1162
+ // Try to get actual status bar height from resources
1163
+ const deviceArg = buildDeviceArg(deviceId);
1164
+ try {
1165
+ const { stdout } = await execAsync(`adb ${deviceArg} shell "dumpsys window | grep -E 'statusBars|mStatusBarLayer|InsetsSource.*statusBars'"`, { timeout: ADB_TIMEOUT });
1166
+ // Try to parse status bar height from dumpsys output
1167
+ // Look for patterns like "statusBars frame=[0,0][1080,63]"
1168
+ const frameMatch = stdout.match(/statusBars.*frame=\[[\d,]+\]\[(\d+),(\d+)\]/);
1169
+ if (frameMatch) {
1170
+ const heightPixels = parseInt(frameMatch[2], 10);
1171
+ return {
1172
+ success: true,
1173
+ heightPixels,
1174
+ heightDp: Math.round(heightPixels / densityScale)
1175
+ };
1176
+ }
1177
+ }
1178
+ catch {
1179
+ // Fallback to standard calculation
1180
+ }
1181
+ // Standard status bar height is 24dp on most devices
1182
+ const statusBarDp = 24;
1183
+ const statusBarPixels = Math.round(statusBarDp * densityScale);
1184
+ return {
1185
+ success: true,
1186
+ heightPixels: statusBarPixels,
1187
+ heightDp: statusBarDp
1188
+ };
1189
+ }
1190
+ catch (error) {
1191
+ return {
1192
+ success: false,
1193
+ error: `Failed to get status bar height: ${error instanceof Error ? error.message : String(error)}`
1194
+ };
1195
+ }
1196
+ }
1197
+ /**
1198
+ * Simplify Android class name for display
1199
+ * android.widget.Button -> Button
1200
+ * android.widget.TextView -> TextView
1201
+ */
1202
+ function simplifyClassName(className) {
1203
+ if (!className)
1204
+ return "Unknown";
1205
+ const parts = className.split(".");
1206
+ return parts[parts.length - 1];
1207
+ }
1208
+ /**
1209
+ * Parse bounds string "[left,top][right,bottom]" to object
1210
+ */
1211
+ function parseBounds(boundsStr) {
1212
+ const match = boundsStr?.match(/\[(\d+),(\d+)\]\[(\d+),(\d+)\]/);
1213
+ if (!match)
1214
+ return null;
1215
+ return {
1216
+ left: parseInt(match[1], 10),
1217
+ top: parseInt(match[2], 10),
1218
+ right: parseInt(match[3], 10),
1219
+ bottom: parseInt(match[4], 10)
1220
+ };
1221
+ }
1222
+ /**
1223
+ * Parse a single node from uiautomator XML
1224
+ */
1225
+ function parseUiNode(node) {
1226
+ const attrs = node["@_bounds"]
1227
+ ? node
1228
+ : node.node
1229
+ ? (Array.isArray(node.node) ? node.node[0] : node.node)
1230
+ : null;
1231
+ if (!attrs)
1232
+ return null;
1233
+ const boundsStr = attrs["@_bounds"];
1234
+ const bounds = parseBounds(boundsStr);
1235
+ if (!bounds)
1236
+ return null;
1237
+ const width = bounds.right - bounds.left;
1238
+ const height = bounds.bottom - bounds.top;
1239
+ const centerX = Math.round(bounds.left + width / 2);
1240
+ const centerY = Math.round(bounds.top + height / 2);
1241
+ const element = {
1242
+ class: simplifyClassName(attrs["@_class"] || ""),
1243
+ bounds,
1244
+ frame: {
1245
+ x: bounds.left,
1246
+ y: bounds.top,
1247
+ width,
1248
+ height
1249
+ },
1250
+ tap: {
1251
+ x: centerX,
1252
+ y: centerY
1253
+ },
1254
+ children: []
1255
+ };
1256
+ // Add optional attributes
1257
+ if (attrs["@_text"])
1258
+ element.text = attrs["@_text"];
1259
+ if (attrs["@_content-desc"])
1260
+ element.contentDesc = attrs["@_content-desc"];
1261
+ if (attrs["@_resource-id"])
1262
+ element.resourceId = attrs["@_resource-id"];
1263
+ if (attrs["@_checkable"] === "true")
1264
+ element.checkable = true;
1265
+ if (attrs["@_checked"] === "true")
1266
+ element.checked = true;
1267
+ if (attrs["@_clickable"] === "true")
1268
+ element.clickable = true;
1269
+ if (attrs["@_enabled"] === "true")
1270
+ element.enabled = true;
1271
+ if (attrs["@_focusable"] === "true")
1272
+ element.focusable = true;
1273
+ if (attrs["@_focused"] === "true")
1274
+ element.focused = true;
1275
+ if (attrs["@_scrollable"] === "true")
1276
+ element.scrollable = true;
1277
+ if (attrs["@_selected"] === "true")
1278
+ element.selected = true;
1279
+ return element;
1280
+ }
1281
+ /**
1282
+ * Recursively parse UI hierarchy from XML node
1283
+ */
1284
+ function parseHierarchy(node) {
1285
+ const results = [];
1286
+ // Handle the node itself
1287
+ if (node["@_bounds"]) {
1288
+ const element = parseUiNode(node);
1289
+ if (element) {
1290
+ // Parse children
1291
+ if (node.node) {
1292
+ const children = Array.isArray(node.node) ? node.node : [node.node];
1293
+ for (const child of children) {
1294
+ element.children.push(...parseHierarchy(child));
1295
+ }
1296
+ }
1297
+ results.push(element);
1298
+ }
1299
+ }
1300
+ else if (node.node) {
1301
+ // This is a container without bounds (like hierarchy root)
1302
+ const children = Array.isArray(node.node) ? node.node : [node.node];
1303
+ for (const child of children) {
1304
+ results.push(...parseHierarchy(child));
1305
+ }
1306
+ }
1307
+ return results;
1308
+ }
1309
+ /**
1310
+ * Format accessibility tree for display (similar to iOS format)
1311
+ */
1312
+ function formatAndroidAccessibilityTree(elements, indent = 0) {
1313
+ const lines = [];
1314
+ const prefix = " ".repeat(indent);
1315
+ for (const element of elements) {
1316
+ const parts = [];
1317
+ // [ClassName] "text" or "content-desc"
1318
+ parts.push(`[${element.class}]`);
1319
+ // Add label (text or content-desc)
1320
+ const label = element.text || element.contentDesc;
1321
+ if (label) {
1322
+ parts.push(`"${label}"`);
1323
+ }
1324
+ // Add frame and tap coordinates
1325
+ const f = element.frame;
1326
+ parts.push(`frame=(${f.x}, ${f.y}, ${f.width}x${f.height}) tap=(${element.tap.x}, ${element.tap.y})`);
1327
+ lines.push(`${prefix}${parts.join(" ")}`);
1328
+ // Recurse into children
1329
+ if (element.children.length > 0) {
1330
+ lines.push(formatAndroidAccessibilityTree(element.children, indent + 1));
1331
+ }
1332
+ }
1333
+ return lines.join("\n");
1334
+ }
1335
+ /**
1336
+ * Flatten element tree to array for searching
1337
+ */
1338
+ function flattenElements(elements) {
1339
+ const result = [];
1340
+ for (const element of elements) {
1341
+ result.push(element);
1342
+ if (element.children.length > 0) {
1343
+ result.push(...flattenElements(element.children));
1344
+ }
1345
+ }
1346
+ return result;
1347
+ }
1348
+ /**
1349
+ * Get the UI hierarchy from the connected Android device using uiautomator dump
1350
+ */
1351
+ export async function androidDescribeAll(deviceId) {
1352
+ try {
1353
+ const adbMissing = await requireAdb();
1354
+ if (adbMissing)
1355
+ return adbMissing;
1356
+ const device = deviceId || (await getDefaultAndroidDevice());
1357
+ if (!device) {
1358
+ return {
1359
+ success: false,
1360
+ error: "No Android device connected. Connect a device or start an emulator."
1361
+ };
1362
+ }
1363
+ const deviceArg = buildDeviceArg(device);
1364
+ // Use file-based approach (most reliable across devices)
1365
+ // /dev/tty doesn't work on most emulators/devices
1366
+ const remotePath = "/sdcard/ui_dump.xml";
1367
+ await execAsync(`adb ${deviceArg} shell uiautomator dump ${remotePath}`, {
1368
+ timeout: ADB_TIMEOUT
1369
+ });
1370
+ const { stdout } = await execAsync(`adb ${deviceArg} shell cat ${remotePath}`, {
1371
+ timeout: ADB_TIMEOUT,
1372
+ maxBuffer: 10 * 1024 * 1024
1373
+ });
1374
+ const xmlContent = stdout.trim();
1375
+ // Clean up
1376
+ await execAsync(`adb ${deviceArg} shell rm ${remotePath}`, {
1377
+ timeout: 5000
1378
+ }).catch(() => { });
1379
+ if (!xmlContent || !xmlContent.includes("<hierarchy")) {
1380
+ return {
1381
+ success: false,
1382
+ error: "Failed to get UI hierarchy. Make sure the device screen is unlocked and the app is in foreground."
1383
+ };
1384
+ }
1385
+ // Parse XML
1386
+ const parser = new XMLParser({
1387
+ ignoreAttributes: false,
1388
+ attributeNamePrefix: "@_"
1389
+ });
1390
+ const parsed = parser.parse(xmlContent);
1391
+ if (!parsed.hierarchy) {
1392
+ return {
1393
+ success: false,
1394
+ error: "Invalid UI hierarchy XML structure"
1395
+ };
1396
+ }
1397
+ const elements = parseHierarchy(parsed.hierarchy);
1398
+ const formatted = formatAndroidAccessibilityTree(elements);
1399
+ return {
1400
+ success: true,
1401
+ elements,
1402
+ formatted
1403
+ };
1404
+ }
1405
+ catch (error) {
1406
+ return {
1407
+ success: false,
1408
+ error: `Failed to get UI hierarchy: ${error instanceof Error ? error.message : String(error)}`
1409
+ };
1410
+ }
1411
+ }
1412
+ /**
1413
+ * Get accessibility info for the UI element at specific coordinates
1414
+ */
1415
+ export async function androidDescribePoint(x, y, deviceId) {
1416
+ try {
1417
+ // First get the full hierarchy
1418
+ const result = await androidDescribeAll(deviceId);
1419
+ if (!result.success || !result.elements) {
1420
+ return result;
1421
+ }
1422
+ // Flatten and find elements containing the point
1423
+ const allElements = flattenElements(result.elements);
1424
+ // Find all elements whose bounds contain the point
1425
+ const matchingElements = allElements.filter((el) => {
1426
+ const b = el.bounds;
1427
+ return x >= b.left && x <= b.right && y >= b.top && y <= b.bottom;
1428
+ });
1429
+ if (matchingElements.length === 0) {
1430
+ return {
1431
+ success: true,
1432
+ formatted: `No element found at (${x}, ${y})`
1433
+ };
1434
+ }
1435
+ // Return the deepest (smallest) element that contains the point
1436
+ // Sort by area (smallest first) to get the most specific element
1437
+ matchingElements.sort((a, b) => {
1438
+ const areaA = a.frame.width * a.frame.height;
1439
+ const areaB = b.frame.width * b.frame.height;
1440
+ return areaA - areaB;
1441
+ });
1442
+ const element = matchingElements[0];
1443
+ // Format detailed output
1444
+ const lines = [];
1445
+ const label = element.text || element.contentDesc;
1446
+ lines.push(`[${element.class}]${label ? ` "${label}"` : ""} frame=(${element.frame.x}, ${element.frame.y}, ${element.frame.width}x${element.frame.height}) tap=(${element.tap.x}, ${element.tap.y})`);
1447
+ if (element.resourceId) {
1448
+ lines.push(` resource-id: ${element.resourceId}`);
1449
+ }
1450
+ if (element.contentDesc && element.text) {
1451
+ // Show content-desc separately if we showed text as label
1452
+ lines.push(` content-desc: ${element.contentDesc}`);
1453
+ }
1454
+ if (element.text && element.contentDesc) {
1455
+ // Show text separately if we showed content-desc as label
1456
+ lines.push(` text: ${element.text}`);
1457
+ }
1458
+ // Show state flags
1459
+ const flags = [];
1460
+ if (element.clickable)
1461
+ flags.push("clickable");
1462
+ if (element.enabled)
1463
+ flags.push("enabled");
1464
+ if (element.focusable)
1465
+ flags.push("focusable");
1466
+ if (element.focused)
1467
+ flags.push("focused");
1468
+ if (element.scrollable)
1469
+ flags.push("scrollable");
1470
+ if (element.selected)
1471
+ flags.push("selected");
1472
+ if (element.checked)
1473
+ flags.push("checked");
1474
+ if (flags.length > 0) {
1475
+ lines.push(` state: ${flags.join(", ")}`);
1476
+ }
1477
+ return {
1478
+ success: true,
1479
+ elements: [element],
1480
+ formatted: lines.join("\n")
1481
+ };
1482
+ }
1483
+ catch (error) {
1484
+ return {
1485
+ success: false,
1486
+ error: `Failed to describe point: ${error instanceof Error ? error.message : String(error)}`
1487
+ };
1488
+ }
1489
+ }
1490
+ /**
1491
+ * Tap an element by its text, content-description, or resource-id
1492
+ */
1493
+ export async function androidTapElement(options) {
1494
+ try {
1495
+ const { text, textContains, contentDesc, contentDescContains, resourceId, index = 0, deviceId } = options;
1496
+ // Validate that at least one search criterion is provided
1497
+ if (!text && !textContains && !contentDesc && !contentDescContains && !resourceId) {
1498
+ return {
1499
+ success: false,
1500
+ error: "At least one of text, textContains, contentDesc, contentDescContains, or resourceId must be provided"
1501
+ };
1502
+ }
1503
+ // Get the UI hierarchy
1504
+ const result = await androidDescribeAll(deviceId);
1505
+ if (!result.success || !result.elements) {
1506
+ return {
1507
+ success: false,
1508
+ error: result.error || "Failed to get UI hierarchy"
1509
+ };
1510
+ }
1511
+ // Flatten and search
1512
+ const allElements = flattenElements(result.elements);
1513
+ // Filter elements based on search criteria
1514
+ const matchingElements = allElements.filter((el) => {
1515
+ if (text && el.text !== text)
1516
+ return false;
1517
+ if (textContains && (!el.text || !el.text.toLowerCase().includes(textContains.toLowerCase())))
1518
+ return false;
1519
+ if (contentDesc && el.contentDesc !== contentDesc)
1520
+ return false;
1521
+ if (contentDescContains && (!el.contentDesc || !el.contentDesc.toLowerCase().includes(contentDescContains.toLowerCase())))
1522
+ return false;
1523
+ if (resourceId) {
1524
+ // Support both full resource-id and short form
1525
+ if (!el.resourceId)
1526
+ return false;
1527
+ if (el.resourceId !== resourceId && !el.resourceId.endsWith(`:id/${resourceId}`))
1528
+ return false;
1529
+ }
1530
+ return true;
1531
+ });
1532
+ if (matchingElements.length === 0) {
1533
+ const criteria = [];
1534
+ if (text)
1535
+ criteria.push(`text="${text}"`);
1536
+ if (textContains)
1537
+ criteria.push(`textContains="${textContains}"`);
1538
+ if (contentDesc)
1539
+ criteria.push(`contentDesc="${contentDesc}"`);
1540
+ if (contentDescContains)
1541
+ criteria.push(`contentDescContains="${contentDescContains}"`);
1542
+ if (resourceId)
1543
+ criteria.push(`resourceId="${resourceId}"`);
1544
+ return {
1545
+ success: false,
1546
+ error: `Element not found: ${criteria.join(", ")}`
1547
+ };
1548
+ }
1549
+ if (index >= matchingElements.length) {
1550
+ return {
1551
+ success: false,
1552
+ error: `Index ${index} out of range. Found ${matchingElements.length} matching element(s).`
1553
+ };
1554
+ }
1555
+ const element = matchingElements[index];
1556
+ const label = element.text || element.contentDesc || element.resourceId || element.class;
1557
+ // Log if multiple matches
1558
+ let resultMessage;
1559
+ if (matchingElements.length > 1) {
1560
+ resultMessage = `Found ${matchingElements.length} elements, tapping "${label}" (index ${index}) at (${element.tap.x}, ${element.tap.y})`;
1561
+ }
1562
+ else {
1563
+ resultMessage = `Tapped "${label}" at (${element.tap.x}, ${element.tap.y})`;
1564
+ }
1565
+ // Perform the tap
1566
+ const tapResult = await androidTap(element.tap.x, element.tap.y, deviceId);
1567
+ if (!tapResult.success) {
1568
+ return tapResult;
1569
+ }
1570
+ return {
1571
+ success: true,
1572
+ result: resultMessage
1573
+ };
1574
+ }
1575
+ catch (error) {
1576
+ return {
1577
+ success: false,
1578
+ error: `Failed to tap element: ${error instanceof Error ? error.message : String(error)}`
1579
+ };
1580
+ }
1581
+ }
1582
+ //# sourceMappingURL=android.js.map