@metamask/device-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (315) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +266 -0
  3. package/dist/backends/adb-backend.cjs +442 -0
  4. package/dist/backends/adb-backend.cjs.map +1 -0
  5. package/dist/backends/adb-backend.d.cts +35 -0
  6. package/dist/backends/adb-backend.d.cts.map +1 -0
  7. package/dist/backends/adb-backend.d.mts +35 -0
  8. package/dist/backends/adb-backend.d.mts.map +1 -0
  9. package/dist/backends/adb-backend.mjs +436 -0
  10. package/dist/backends/adb-backend.mjs.map +1 -0
  11. package/dist/backends/appium-backend.cjs +518 -0
  12. package/dist/backends/appium-backend.cjs.map +1 -0
  13. package/dist/backends/appium-backend.d.cts +36 -0
  14. package/dist/backends/appium-backend.d.cts.map +1 -0
  15. package/dist/backends/appium-backend.d.mts +36 -0
  16. package/dist/backends/appium-backend.d.mts.map +1 -0
  17. package/dist/backends/appium-backend.mjs +512 -0
  18. package/dist/backends/appium-backend.mjs.map +1 -0
  19. package/dist/backends/idb-backend.cjs +442 -0
  20. package/dist/backends/idb-backend.cjs.map +1 -0
  21. package/dist/backends/idb-backend.d.cts +35 -0
  22. package/dist/backends/idb-backend.d.cts.map +1 -0
  23. package/dist/backends/idb-backend.d.mts +35 -0
  24. package/dist/backends/idb-backend.d.mts.map +1 -0
  25. package/dist/backends/idb-backend.mjs +436 -0
  26. package/dist/backends/idb-backend.mjs.map +1 -0
  27. package/dist/backends/index.cjs +134 -0
  28. package/dist/backends/index.cjs.map +1 -0
  29. package/dist/backends/index.d.cts +16 -0
  30. package/dist/backends/index.d.cts.map +1 -0
  31. package/dist/backends/index.d.mts +16 -0
  32. package/dist/backends/index.d.mts.map +1 -0
  33. package/dist/backends/index.mjs +126 -0
  34. package/dist/backends/index.mjs.map +1 -0
  35. package/dist/backends/session-file.cjs +62 -0
  36. package/dist/backends/session-file.cjs.map +1 -0
  37. package/dist/backends/session-file.d.cts +24 -0
  38. package/dist/backends/session-file.d.cts.map +1 -0
  39. package/dist/backends/session-file.d.mts +24 -0
  40. package/dist/backends/session-file.d.mts.map +1 -0
  41. package/dist/backends/session-file.mjs +59 -0
  42. package/dist/backends/session-file.mjs.map +1 -0
  43. package/dist/backends/types.cjs +3 -0
  44. package/dist/backends/types.cjs.map +1 -0
  45. package/dist/backends/types.d.cts +93 -0
  46. package/dist/backends/types.d.cts.map +1 -0
  47. package/dist/backends/types.d.mts +93 -0
  48. package/dist/backends/types.d.mts.map +1 -0
  49. package/dist/backends/types.mjs +2 -0
  50. package/dist/backends/types.mjs.map +1 -0
  51. package/dist/backends/webdriver-client.cjs +167 -0
  52. package/dist/backends/webdriver-client.cjs.map +1 -0
  53. package/dist/backends/webdriver-client.d.cts +55 -0
  54. package/dist/backends/webdriver-client.d.cts.map +1 -0
  55. package/dist/backends/webdriver-client.d.mts +55 -0
  56. package/dist/backends/webdriver-client.d.mts.map +1 -0
  57. package/dist/backends/webdriver-client.mjs +162 -0
  58. package/dist/backends/webdriver-client.mjs.map +1 -0
  59. package/dist/cli/device-mcp.cjs +20 -0
  60. package/dist/cli/device-mcp.cjs.map +1 -0
  61. package/dist/cli/device-mcp.d.cts +3 -0
  62. package/dist/cli/device-mcp.d.cts.map +1 -0
  63. package/dist/cli/device-mcp.d.mts +3 -0
  64. package/dist/cli/device-mcp.d.mts.map +1 -0
  65. package/dist/cli/device-mcp.mjs +18 -0
  66. package/dist/cli/device-mcp.mjs.map +1 -0
  67. package/dist/index.cjs +9 -0
  68. package/dist/index.cjs.map +1 -0
  69. package/dist/index.d.cts +5 -0
  70. package/dist/index.d.cts.map +1 -0
  71. package/dist/index.d.mts +5 -0
  72. package/dist/index.d.mts.map +1 -0
  73. package/dist/index.mjs +3 -0
  74. package/dist/index.mjs.map +1 -0
  75. package/dist/server.cjs +43 -0
  76. package/dist/server.cjs.map +1 -0
  77. package/dist/server.d.cts +4 -0
  78. package/dist/server.d.cts.map +1 -0
  79. package/dist/server.d.mts +4 -0
  80. package/dist/server.d.mts.map +1 -0
  81. package/dist/server.mjs +40 -0
  82. package/dist/server.mjs.map +1 -0
  83. package/dist/tools/alert-text.cjs +25 -0
  84. package/dist/tools/alert-text.cjs.map +1 -0
  85. package/dist/tools/alert-text.d.cts +4 -0
  86. package/dist/tools/alert-text.d.cts.map +1 -0
  87. package/dist/tools/alert-text.d.mts +4 -0
  88. package/dist/tools/alert-text.d.mts.map +1 -0
  89. package/dist/tools/alert-text.mjs +22 -0
  90. package/dist/tools/alert-text.mjs.map +1 -0
  91. package/dist/tools/app-state.cjs +40 -0
  92. package/dist/tools/app-state.cjs.map +1 -0
  93. package/dist/tools/app-state.d.cts +4 -0
  94. package/dist/tools/app-state.d.cts.map +1 -0
  95. package/dist/tools/app-state.d.mts +4 -0
  96. package/dist/tools/app-state.d.mts.map +1 -0
  97. package/dist/tools/app-state.mjs +37 -0
  98. package/dist/tools/app-state.mjs.map +1 -0
  99. package/dist/tools/clipboard.cjs +42 -0
  100. package/dist/tools/clipboard.cjs.map +1 -0
  101. package/dist/tools/clipboard.d.cts +4 -0
  102. package/dist/tools/clipboard.d.cts.map +1 -0
  103. package/dist/tools/clipboard.d.mts +4 -0
  104. package/dist/tools/clipboard.d.mts.map +1 -0
  105. package/dist/tools/clipboard.mjs +39 -0
  106. package/dist/tools/clipboard.mjs.map +1 -0
  107. package/dist/tools/close-app.cjs +25 -0
  108. package/dist/tools/close-app.cjs.map +1 -0
  109. package/dist/tools/close-app.d.cts +4 -0
  110. package/dist/tools/close-app.d.cts.map +1 -0
  111. package/dist/tools/close-app.d.mts +4 -0
  112. package/dist/tools/close-app.d.mts.map +1 -0
  113. package/dist/tools/close-app.mjs +22 -0
  114. package/dist/tools/close-app.mjs.map +1 -0
  115. package/dist/tools/context.cjs +52 -0
  116. package/dist/tools/context.cjs.map +1 -0
  117. package/dist/tools/context.d.cts +4 -0
  118. package/dist/tools/context.d.cts.map +1 -0
  119. package/dist/tools/context.d.mts +4 -0
  120. package/dist/tools/context.d.mts.map +1 -0
  121. package/dist/tools/context.mjs +49 -0
  122. package/dist/tools/context.mjs.map +1 -0
  123. package/dist/tools/device-info.cjs +34 -0
  124. package/dist/tools/device-info.cjs.map +1 -0
  125. package/dist/tools/device-info.d.cts +4 -0
  126. package/dist/tools/device-info.d.cts.map +1 -0
  127. package/dist/tools/device-info.d.mts +4 -0
  128. package/dist/tools/device-info.d.mts.map +1 -0
  129. package/dist/tools/device-info.mjs +31 -0
  130. package/dist/tools/device-info.mjs.map +1 -0
  131. package/dist/tools/dismiss-alert.cjs +34 -0
  132. package/dist/tools/dismiss-alert.cjs.map +1 -0
  133. package/dist/tools/dismiss-alert.d.cts +4 -0
  134. package/dist/tools/dismiss-alert.d.cts.map +1 -0
  135. package/dist/tools/dismiss-alert.d.mts +4 -0
  136. package/dist/tools/dismiss-alert.d.mts.map +1 -0
  137. package/dist/tools/dismiss-alert.mjs +31 -0
  138. package/dist/tools/dismiss-alert.mjs.map +1 -0
  139. package/dist/tools/dismiss-keyboard.cjs +23 -0
  140. package/dist/tools/dismiss-keyboard.cjs.map +1 -0
  141. package/dist/tools/dismiss-keyboard.d.cts +4 -0
  142. package/dist/tools/dismiss-keyboard.d.cts.map +1 -0
  143. package/dist/tools/dismiss-keyboard.d.mts +4 -0
  144. package/dist/tools/dismiss-keyboard.d.mts.map +1 -0
  145. package/dist/tools/dismiss-keyboard.mjs +20 -0
  146. package/dist/tools/dismiss-keyboard.mjs.map +1 -0
  147. package/dist/tools/generate-locators.cjs +33 -0
  148. package/dist/tools/generate-locators.cjs.map +1 -0
  149. package/dist/tools/generate-locators.d.cts +4 -0
  150. package/dist/tools/generate-locators.d.cts.map +1 -0
  151. package/dist/tools/generate-locators.d.mts +4 -0
  152. package/dist/tools/generate-locators.d.mts.map +1 -0
  153. package/dist/tools/generate-locators.mjs +30 -0
  154. package/dist/tools/generate-locators.mjs.map +1 -0
  155. package/dist/tools/index.cjs +50 -0
  156. package/dist/tools/index.cjs.map +1 -0
  157. package/dist/tools/index.d.cts +24 -0
  158. package/dist/tools/index.d.cts.map +1 -0
  159. package/dist/tools/index.d.mts +24 -0
  160. package/dist/tools/index.d.mts.map +1 -0
  161. package/dist/tools/index.mjs +24 -0
  162. package/dist/tools/index.mjs.map +1 -0
  163. package/dist/tools/logs.cjs +48 -0
  164. package/dist/tools/logs.cjs.map +1 -0
  165. package/dist/tools/logs.d.cts +4 -0
  166. package/dist/tools/logs.d.cts.map +1 -0
  167. package/dist/tools/logs.d.mts +4 -0
  168. package/dist/tools/logs.d.mts.map +1 -0
  169. package/dist/tools/logs.mjs +45 -0
  170. package/dist/tools/logs.mjs.map +1 -0
  171. package/dist/tools/long-press.cjs +38 -0
  172. package/dist/tools/long-press.cjs.map +1 -0
  173. package/dist/tools/long-press.d.cts +4 -0
  174. package/dist/tools/long-press.d.cts.map +1 -0
  175. package/dist/tools/long-press.d.mts +4 -0
  176. package/dist/tools/long-press.d.mts.map +1 -0
  177. package/dist/tools/long-press.mjs +35 -0
  178. package/dist/tools/long-press.mjs.map +1 -0
  179. package/dist/tools/open-app.cjs +26 -0
  180. package/dist/tools/open-app.cjs.map +1 -0
  181. package/dist/tools/open-app.d.cts +4 -0
  182. package/dist/tools/open-app.d.cts.map +1 -0
  183. package/dist/tools/open-app.d.mts +4 -0
  184. package/dist/tools/open-app.d.mts.map +1 -0
  185. package/dist/tools/open-app.mjs +23 -0
  186. package/dist/tools/open-app.mjs.map +1 -0
  187. package/dist/tools/press-button.cjs +30 -0
  188. package/dist/tools/press-button.cjs.map +1 -0
  189. package/dist/tools/press-button.d.cts +4 -0
  190. package/dist/tools/press-button.d.cts.map +1 -0
  191. package/dist/tools/press-button.d.mts +4 -0
  192. package/dist/tools/press-button.d.mts.map +1 -0
  193. package/dist/tools/press-button.mjs +27 -0
  194. package/dist/tools/press-button.mjs.map +1 -0
  195. package/dist/tools/screen-recording.cjs +46 -0
  196. package/dist/tools/screen-recording.cjs.map +1 -0
  197. package/dist/tools/screen-recording.d.cts +4 -0
  198. package/dist/tools/screen-recording.d.cts.map +1 -0
  199. package/dist/tools/screen-recording.d.mts +4 -0
  200. package/dist/tools/screen-recording.d.mts.map +1 -0
  201. package/dist/tools/screen-recording.mjs +43 -0
  202. package/dist/tools/screen-recording.mjs.map +1 -0
  203. package/dist/tools/screenshot.cjs +42 -0
  204. package/dist/tools/screenshot.cjs.map +1 -0
  205. package/dist/tools/screenshot.d.cts +4 -0
  206. package/dist/tools/screenshot.d.cts.map +1 -0
  207. package/dist/tools/screenshot.d.mts +4 -0
  208. package/dist/tools/screenshot.d.mts.map +1 -0
  209. package/dist/tools/screenshot.mjs +39 -0
  210. package/dist/tools/screenshot.mjs.map +1 -0
  211. package/dist/tools/scroll-to-element.cjs +56 -0
  212. package/dist/tools/scroll-to-element.cjs.map +1 -0
  213. package/dist/tools/scroll-to-element.d.cts +4 -0
  214. package/dist/tools/scroll-to-element.d.cts.map +1 -0
  215. package/dist/tools/scroll-to-element.d.mts +4 -0
  216. package/dist/tools/scroll-to-element.d.mts.map +1 -0
  217. package/dist/tools/scroll-to-element.mjs +53 -0
  218. package/dist/tools/scroll-to-element.mjs.map +1 -0
  219. package/dist/tools/shared.cjs +11 -0
  220. package/dist/tools/shared.cjs.map +1 -0
  221. package/dist/tools/shared.d.cts +8 -0
  222. package/dist/tools/shared.d.cts.map +1 -0
  223. package/dist/tools/shared.d.mts +8 -0
  224. package/dist/tools/shared.d.mts.map +1 -0
  225. package/dist/tools/shared.mjs +8 -0
  226. package/dist/tools/shared.mjs.map +1 -0
  227. package/dist/tools/snapshot.cjs +37 -0
  228. package/dist/tools/snapshot.cjs.map +1 -0
  229. package/dist/tools/snapshot.d.cts +4 -0
  230. package/dist/tools/snapshot.d.cts.map +1 -0
  231. package/dist/tools/snapshot.d.mts +4 -0
  232. package/dist/tools/snapshot.d.mts.map +1 -0
  233. package/dist/tools/snapshot.mjs +34 -0
  234. package/dist/tools/snapshot.mjs.map +1 -0
  235. package/dist/tools/swipe.cjs +39 -0
  236. package/dist/tools/swipe.cjs.map +1 -0
  237. package/dist/tools/swipe.d.cts +4 -0
  238. package/dist/tools/swipe.d.cts.map +1 -0
  239. package/dist/tools/swipe.d.mts +4 -0
  240. package/dist/tools/swipe.d.mts.map +1 -0
  241. package/dist/tools/swipe.mjs +36 -0
  242. package/dist/tools/swipe.mjs.map +1 -0
  243. package/dist/tools/tap-coordinates.cjs +27 -0
  244. package/dist/tools/tap-coordinates.cjs.map +1 -0
  245. package/dist/tools/tap-coordinates.d.cts +4 -0
  246. package/dist/tools/tap-coordinates.d.cts.map +1 -0
  247. package/dist/tools/tap-coordinates.d.mts +4 -0
  248. package/dist/tools/tap-coordinates.d.mts.map +1 -0
  249. package/dist/tools/tap-coordinates.mjs +24 -0
  250. package/dist/tools/tap-coordinates.mjs.map +1 -0
  251. package/dist/tools/tap-element.cjs +46 -0
  252. package/dist/tools/tap-element.cjs.map +1 -0
  253. package/dist/tools/tap-element.d.cts +4 -0
  254. package/dist/tools/tap-element.d.cts.map +1 -0
  255. package/dist/tools/tap-element.d.mts +4 -0
  256. package/dist/tools/tap-element.d.mts.map +1 -0
  257. package/dist/tools/tap-element.mjs +43 -0
  258. package/dist/tools/tap-element.mjs.map +1 -0
  259. package/dist/tools/type-text.cjs +32 -0
  260. package/dist/tools/type-text.cjs.map +1 -0
  261. package/dist/tools/type-text.d.cts +4 -0
  262. package/dist/tools/type-text.d.cts.map +1 -0
  263. package/dist/tools/type-text.d.mts +4 -0
  264. package/dist/tools/type-text.d.mts.map +1 -0
  265. package/dist/tools/type-text.mjs +29 -0
  266. package/dist/tools/type-text.mjs.map +1 -0
  267. package/dist/tools/wait-for.cjs +51 -0
  268. package/dist/tools/wait-for.cjs.map +1 -0
  269. package/dist/tools/wait-for.d.cts +4 -0
  270. package/dist/tools/wait-for.d.cts.map +1 -0
  271. package/dist/tools/wait-for.d.mts +4 -0
  272. package/dist/tools/wait-for.d.mts.map +1 -0
  273. package/dist/tools/wait-for.mjs +48 -0
  274. package/dist/tools/wait-for.mjs.map +1 -0
  275. package/dist/tools/window-size.cjs +29 -0
  276. package/dist/tools/window-size.cjs.map +1 -0
  277. package/dist/tools/window-size.d.cts +4 -0
  278. package/dist/tools/window-size.d.cts.map +1 -0
  279. package/dist/tools/window-size.d.mts +4 -0
  280. package/dist/tools/window-size.d.mts.map +1 -0
  281. package/dist/tools/window-size.mjs +26 -0
  282. package/dist/tools/window-size.mjs.map +1 -0
  283. package/dist/utils/alert-labels.cjs +26 -0
  284. package/dist/utils/alert-labels.cjs.map +1 -0
  285. package/dist/utils/alert-labels.d.cts +3 -0
  286. package/dist/utils/alert-labels.d.cts.map +1 -0
  287. package/dist/utils/alert-labels.d.mts +3 -0
  288. package/dist/utils/alert-labels.d.mts.map +1 -0
  289. package/dist/utils/alert-labels.mjs +23 -0
  290. package/dist/utils/alert-labels.mjs.map +1 -0
  291. package/dist/utils/element.cjs +212 -0
  292. package/dist/utils/element.cjs.map +1 -0
  293. package/dist/utils/element.d.cts +24 -0
  294. package/dist/utils/element.d.cts.map +1 -0
  295. package/dist/utils/element.d.mts +24 -0
  296. package/dist/utils/element.d.mts.map +1 -0
  297. package/dist/utils/element.mjs +203 -0
  298. package/dist/utils/element.mjs.map +1 -0
  299. package/dist/utils/exec.cjs +57 -0
  300. package/dist/utils/exec.cjs.map +1 -0
  301. package/dist/utils/exec.d.cts +23 -0
  302. package/dist/utils/exec.d.cts.map +1 -0
  303. package/dist/utils/exec.d.mts +23 -0
  304. package/dist/utils/exec.d.mts.map +1 -0
  305. package/dist/utils/exec.mjs +52 -0
  306. package/dist/utils/exec.mjs.map +1 -0
  307. package/dist/utils/platform.cjs +93 -0
  308. package/dist/utils/platform.cjs.map +1 -0
  309. package/dist/utils/platform.d.cts +8 -0
  310. package/dist/utils/platform.d.cts.map +1 -0
  311. package/dist/utils/platform.d.mts +8 -0
  312. package/dist/utils/platform.d.mts.map +1 -0
  313. package/dist/utils/platform.mjs +90 -0
  314. package/dist/utils/platform.mjs.map +1 -0
  315. package/package.json +106 -0
@@ -0,0 +1,212 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findElement = findElement;
4
+ exports.matchesQuery = matchesQuery;
5
+ exports.describeElement = describeElement;
6
+ exports.computeSwipeEnd = computeSwipeEnd;
7
+ exports.generateLocators = generateLocators;
8
+ exports.formatLocators = formatLocators;
9
+ exports.formatHierarchy = formatHierarchy;
10
+ function findElement(elements, query) {
11
+ for (const el of elements) {
12
+ if (matchesQuery(el, query)) {
13
+ return el;
14
+ }
15
+ if (el.children) {
16
+ const found = findElement(el.children, query);
17
+ if (found) {
18
+ return found;
19
+ }
20
+ }
21
+ }
22
+ return null;
23
+ }
24
+ function matchesQuery(element, query) {
25
+ if (query.identifier && !fuzzyMatch(element.identifier, query.identifier)) {
26
+ return false;
27
+ }
28
+ if (query.label && !fuzzyMatch(element.label, query.label)) {
29
+ return false;
30
+ }
31
+ if (query.text &&
32
+ !fuzzyMatch(element.value, query.text) &&
33
+ !fuzzyMatch(element.label, query.text)) {
34
+ return false;
35
+ }
36
+ if (query.type && !fuzzyMatch(element.type, query.type)) {
37
+ return false;
38
+ }
39
+ const hasAtLeastOneFilter = query.identifier ?? query.label ?? query.text ?? query.type;
40
+ return Boolean(hasAtLeastOneFilter);
41
+ }
42
+ function fuzzyMatch(actual, expected) {
43
+ if (!actual) {
44
+ return false;
45
+ }
46
+ if (actual === expected) {
47
+ return true;
48
+ }
49
+ if (actual.toLowerCase() === expected.toLowerCase()) {
50
+ return true;
51
+ }
52
+ return actual.toLowerCase().includes(expected.toLowerCase());
53
+ }
54
+ function describeElement(element) {
55
+ const parts = [element.type];
56
+ if (element.label) {
57
+ parts.push(`label="${element.label}"`);
58
+ }
59
+ if (element.identifier) {
60
+ parts.push(`id="${element.identifier}"`);
61
+ }
62
+ if (element.value) {
63
+ parts.push(`value="${element.value}"`);
64
+ }
65
+ return parts.join(' ');
66
+ }
67
+ function computeSwipeEnd(startX, startY, direction, distance) {
68
+ switch (direction) {
69
+ case 'up':
70
+ return [startX, startY - distance];
71
+ case 'down':
72
+ return [startX, startY + distance];
73
+ case 'left':
74
+ return [startX - distance, startY];
75
+ case 'right':
76
+ return [startX + distance, startY];
77
+ default:
78
+ return [startX, startY];
79
+ }
80
+ }
81
+ const INTERACTIVE_TYPES = new Set([
82
+ 'Button',
83
+ 'TextField',
84
+ 'SecureTextField',
85
+ 'Switch',
86
+ 'Slider',
87
+ 'Picker',
88
+ 'Link',
89
+ 'SearchField',
90
+ 'TextArea',
91
+ 'Tab',
92
+ 'MenuItem',
93
+ 'Cell',
94
+ 'android.widget.Button',
95
+ 'android.widget.EditText',
96
+ 'android.widget.CheckBox',
97
+ 'android.widget.RadioButton',
98
+ 'android.widget.Switch',
99
+ 'android.widget.ToggleButton',
100
+ 'android.widget.Spinner',
101
+ 'android.widget.SeekBar',
102
+ 'android.widget.ImageButton',
103
+ ]);
104
+ function isInteractive(element) {
105
+ if (INTERACTIVE_TYPES.has(element.type)) {
106
+ return true;
107
+ }
108
+ for (const t of INTERACTIVE_TYPES) {
109
+ if (element.type.includes(t)) {
110
+ return true;
111
+ }
112
+ }
113
+ return element.enabled && Boolean(element.identifier);
114
+ }
115
+ function buildSuggestions(element) {
116
+ const suggestions = [];
117
+ if (element.identifier) {
118
+ suggestions.push({
119
+ strategy: 'identifier',
120
+ value: element.identifier,
121
+ confidence: 'high',
122
+ });
123
+ }
124
+ if (element.label) {
125
+ suggestions.push({
126
+ strategy: 'label',
127
+ value: element.label,
128
+ confidence: element.identifier ? 'medium' : 'high',
129
+ });
130
+ }
131
+ if (element.value && element.value !== element.label) {
132
+ suggestions.push({
133
+ strategy: 'text',
134
+ value: element.value,
135
+ confidence: 'low',
136
+ });
137
+ }
138
+ if (suggestions.length === 0) {
139
+ suggestions.push({
140
+ strategy: 'type',
141
+ value: element.type,
142
+ confidence: 'low',
143
+ });
144
+ }
145
+ return suggestions;
146
+ }
147
+ function generateLocators(elements) {
148
+ const results = [];
149
+ collectLocators(elements, results);
150
+ return results;
151
+ }
152
+ function collectLocators(elements, results) {
153
+ for (const el of elements) {
154
+ if (isInteractive(el)) {
155
+ const suggestions = buildSuggestions(el);
156
+ results.push({
157
+ description: describeElement(el),
158
+ frame: el.frame,
159
+ suggestions,
160
+ });
161
+ }
162
+ if (el.children) {
163
+ collectLocators(el.children, results);
164
+ }
165
+ }
166
+ }
167
+ function formatLocators(locators) {
168
+ if (locators.length === 0) {
169
+ return 'No interactive elements found on screen.';
170
+ }
171
+ const confidenceIcon = {
172
+ high: '***',
173
+ medium: '**',
174
+ low: '*',
175
+ };
176
+ return locators
177
+ .map((loc, i) => {
178
+ const header = `${i + 1}. ${loc.description} (${loc.frame.x},${loc.frame.y} ${loc.frame.width}x${loc.frame.height})`;
179
+ const strategies = loc.suggestions
180
+ .map((s) => ` ${confidenceIcon[s.confidence]} { ${s.strategy}: "${s.value}" }`)
181
+ .join('\n');
182
+ return `${header}\n${strategies}`;
183
+ })
184
+ .join('\n\n');
185
+ }
186
+ function formatHierarchy(elements, depth) {
187
+ const indent = ' '.repeat(depth);
188
+ return elements
189
+ .map((el) => {
190
+ const parts = [`${indent}[${el.type}]`];
191
+ if (el.label) {
192
+ parts.push(`label="${el.label}"`);
193
+ }
194
+ if (el.identifier) {
195
+ parts.push(`id="${el.identifier}"`);
196
+ }
197
+ if (el.value) {
198
+ parts.push(`value="${el.value}"`);
199
+ }
200
+ parts.push(`(${el.frame.x},${el.frame.y} ${el.frame.width}x${el.frame.height})`);
201
+ if (!el.enabled) {
202
+ parts.push('DISABLED');
203
+ }
204
+ let result = parts.join(' ');
205
+ if (el.children?.length) {
206
+ result += `\n${formatHierarchy(el.children, depth + 1)}`;
207
+ }
208
+ return result;
209
+ })
210
+ .join('\n');
211
+ }
212
+ //# sourceMappingURL=element.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"element.cjs","sourceRoot":"","sources":["../../src/utils/element.ts"],"names":[],"mappings":"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","sourcesContent":["import type { UIElement, ElementQuery } from '../backends/types.js';\n\nexport function findElement(\n elements: UIElement[],\n query: ElementQuery,\n): UIElement | null {\n for (const el of elements) {\n if (matchesQuery(el, query)) {\n return el;\n }\n if (el.children) {\n const found = findElement(el.children, query);\n if (found) {\n return found;\n }\n }\n }\n return null;\n}\n\nexport function matchesQuery(element: UIElement, query: ElementQuery): boolean {\n if (query.identifier && !fuzzyMatch(element.identifier, query.identifier)) {\n return false;\n }\n if (query.label && !fuzzyMatch(element.label, query.label)) {\n return false;\n }\n if (\n query.text &&\n !fuzzyMatch(element.value, query.text) &&\n !fuzzyMatch(element.label, query.text)\n ) {\n return false;\n }\n if (query.type && !fuzzyMatch(element.type, query.type)) {\n return false;\n }\n\n const hasAtLeastOneFilter =\n query.identifier ?? query.label ?? query.text ?? query.type;\n return Boolean(hasAtLeastOneFilter);\n}\n\nfunction fuzzyMatch(actual: string | undefined, expected: string): boolean {\n if (!actual) {\n return false;\n }\n if (actual === expected) {\n return true;\n }\n if (actual.toLowerCase() === expected.toLowerCase()) {\n return true;\n }\n return actual.toLowerCase().includes(expected.toLowerCase());\n}\n\nexport function describeElement(element: UIElement): string {\n const parts: string[] = [element.type];\n if (element.label) {\n parts.push(`label=\"${element.label}\"`);\n }\n if (element.identifier) {\n parts.push(`id=\"${element.identifier}\"`);\n }\n if (element.value) {\n parts.push(`value=\"${element.value}\"`);\n }\n return parts.join(' ');\n}\n\nexport function computeSwipeEnd(\n startX: number,\n startY: number,\n direction: 'up' | 'down' | 'left' | 'right',\n distance: number,\n): [number, number] {\n switch (direction) {\n case 'up':\n return [startX, startY - distance];\n case 'down':\n return [startX, startY + distance];\n case 'left':\n return [startX - distance, startY];\n case 'right':\n return [startX + distance, startY];\n default:\n return [startX, startY];\n }\n}\n\nexport type LocatorSuggestion = {\n strategy: 'identifier' | 'label' | 'text' | 'type';\n value: string;\n confidence: 'high' | 'medium' | 'low';\n};\n\nexport type ElementLocator = {\n description: string;\n frame: { x: number; y: number; width: number; height: number };\n suggestions: LocatorSuggestion[];\n};\n\nconst INTERACTIVE_TYPES = new Set([\n 'Button',\n 'TextField',\n 'SecureTextField',\n 'Switch',\n 'Slider',\n 'Picker',\n 'Link',\n 'SearchField',\n 'TextArea',\n 'Tab',\n 'MenuItem',\n 'Cell',\n 'android.widget.Button',\n 'android.widget.EditText',\n 'android.widget.CheckBox',\n 'android.widget.RadioButton',\n 'android.widget.Switch',\n 'android.widget.ToggleButton',\n 'android.widget.Spinner',\n 'android.widget.SeekBar',\n 'android.widget.ImageButton',\n]);\n\nfunction isInteractive(element: UIElement): boolean {\n if (INTERACTIVE_TYPES.has(element.type)) {\n return true;\n }\n for (const t of INTERACTIVE_TYPES) {\n if (element.type.includes(t)) {\n return true;\n }\n }\n return element.enabled && Boolean(element.identifier);\n}\n\nfunction buildSuggestions(element: UIElement): LocatorSuggestion[] {\n const suggestions: LocatorSuggestion[] = [];\n\n if (element.identifier) {\n suggestions.push({\n strategy: 'identifier',\n value: element.identifier,\n confidence: 'high',\n });\n }\n\n if (element.label) {\n suggestions.push({\n strategy: 'label',\n value: element.label,\n confidence: element.identifier ? 'medium' : 'high',\n });\n }\n\n if (element.value && element.value !== element.label) {\n suggestions.push({\n strategy: 'text',\n value: element.value,\n confidence: 'low',\n });\n }\n\n if (suggestions.length === 0) {\n suggestions.push({\n strategy: 'type',\n value: element.type,\n confidence: 'low',\n });\n }\n\n return suggestions;\n}\n\nexport function generateLocators(elements: UIElement[]): ElementLocator[] {\n const results: ElementLocator[] = [];\n collectLocators(elements, results);\n return results;\n}\n\nfunction collectLocators(\n elements: UIElement[],\n results: ElementLocator[],\n): void {\n for (const el of elements) {\n if (isInteractive(el)) {\n const suggestions = buildSuggestions(el);\n results.push({\n description: describeElement(el),\n frame: el.frame,\n suggestions,\n });\n }\n if (el.children) {\n collectLocators(el.children, results);\n }\n }\n}\n\nexport function formatLocators(locators: ElementLocator[]): string {\n if (locators.length === 0) {\n return 'No interactive elements found on screen.';\n }\n\n const confidenceIcon: Record<string, string> = {\n high: '***',\n medium: '**',\n low: '*',\n };\n\n return locators\n .map((loc, i) => {\n const header = `${i + 1}. ${loc.description} (${loc.frame.x},${loc.frame.y} ${loc.frame.width}x${loc.frame.height})`;\n const strategies = loc.suggestions\n .map(\n (s) =>\n ` ${confidenceIcon[s.confidence]} { ${s.strategy}: \"${s.value}\" }`,\n )\n .join('\\n');\n return `${header}\\n${strategies}`;\n })\n .join('\\n\\n');\n}\n\nexport function formatHierarchy(elements: UIElement[], depth: number): string {\n const indent = ' '.repeat(depth);\n return elements\n .map((el) => {\n const parts = [`${indent}[${el.type}]`];\n if (el.label) {\n parts.push(`label=\"${el.label}\"`);\n }\n if (el.identifier) {\n parts.push(`id=\"${el.identifier}\"`);\n }\n if (el.value) {\n parts.push(`value=\"${el.value}\"`);\n }\n parts.push(\n `(${el.frame.x},${el.frame.y} ${el.frame.width}x${el.frame.height})`,\n );\n if (!el.enabled) {\n parts.push('DISABLED');\n }\n\n let result = parts.join(' ');\n if (el.children?.length) {\n result += `\\n${formatHierarchy(el.children, depth + 1)}`;\n }\n return result;\n })\n .join('\\n');\n}\n"]}
@@ -0,0 +1,24 @@
1
+ import type { UIElement, ElementQuery } from "../backends/types.cjs";
2
+ export declare function findElement(elements: UIElement[], query: ElementQuery): UIElement | null;
3
+ export declare function matchesQuery(element: UIElement, query: ElementQuery): boolean;
4
+ export declare function describeElement(element: UIElement): string;
5
+ export declare function computeSwipeEnd(startX: number, startY: number, direction: 'up' | 'down' | 'left' | 'right', distance: number): [number, number];
6
+ export type LocatorSuggestion = {
7
+ strategy: 'identifier' | 'label' | 'text' | 'type';
8
+ value: string;
9
+ confidence: 'high' | 'medium' | 'low';
10
+ };
11
+ export type ElementLocator = {
12
+ description: string;
13
+ frame: {
14
+ x: number;
15
+ y: number;
16
+ width: number;
17
+ height: number;
18
+ };
19
+ suggestions: LocatorSuggestion[];
20
+ };
21
+ export declare function generateLocators(elements: UIElement[]): ElementLocator[];
22
+ export declare function formatLocators(locators: ElementLocator[]): string;
23
+ export declare function formatHierarchy(elements: UIElement[], depth: number): string;
24
+ //# sourceMappingURL=element.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"element.d.cts","sourceRoot":"","sources":["../../src/utils/element.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,8BAA6B;AAEpE,wBAAgB,WAAW,CACzB,QAAQ,EAAE,SAAS,EAAE,EACrB,KAAK,EAAE,YAAY,GAClB,SAAS,GAAG,IAAI,CAalB;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAqB7E;AAeD,wBAAgB,eAAe,CAAC,OAAO,EAAE,SAAS,GAAG,MAAM,CAY1D;AAED,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAC3C,QAAQ,EAAE,MAAM,GACf,CAAC,MAAM,EAAE,MAAM,CAAC,CAalB;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,YAAY,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/D,WAAW,EAAE,iBAAiB,EAAE,CAAC;CAClC,CAAC;AA4EF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,SAAS,EAAE,GAAG,cAAc,EAAE,CAIxE;AAqBD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,MAAM,CAuBjE;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CA4B5E"}
@@ -0,0 +1,24 @@
1
+ import type { UIElement, ElementQuery } from "../backends/types.mjs";
2
+ export declare function findElement(elements: UIElement[], query: ElementQuery): UIElement | null;
3
+ export declare function matchesQuery(element: UIElement, query: ElementQuery): boolean;
4
+ export declare function describeElement(element: UIElement): string;
5
+ export declare function computeSwipeEnd(startX: number, startY: number, direction: 'up' | 'down' | 'left' | 'right', distance: number): [number, number];
6
+ export type LocatorSuggestion = {
7
+ strategy: 'identifier' | 'label' | 'text' | 'type';
8
+ value: string;
9
+ confidence: 'high' | 'medium' | 'low';
10
+ };
11
+ export type ElementLocator = {
12
+ description: string;
13
+ frame: {
14
+ x: number;
15
+ y: number;
16
+ width: number;
17
+ height: number;
18
+ };
19
+ suggestions: LocatorSuggestion[];
20
+ };
21
+ export declare function generateLocators(elements: UIElement[]): ElementLocator[];
22
+ export declare function formatLocators(locators: ElementLocator[]): string;
23
+ export declare function formatHierarchy(elements: UIElement[], depth: number): string;
24
+ //# sourceMappingURL=element.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"element.d.mts","sourceRoot":"","sources":["../../src/utils/element.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,8BAA6B;AAEpE,wBAAgB,WAAW,CACzB,QAAQ,EAAE,SAAS,EAAE,EACrB,KAAK,EAAE,YAAY,GAClB,SAAS,GAAG,IAAI,CAalB;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAqB7E;AAeD,wBAAgB,eAAe,CAAC,OAAO,EAAE,SAAS,GAAG,MAAM,CAY1D;AAED,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAC3C,QAAQ,EAAE,MAAM,GACf,CAAC,MAAM,EAAE,MAAM,CAAC,CAalB;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,YAAY,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/D,WAAW,EAAE,iBAAiB,EAAE,CAAC;CAClC,CAAC;AA4EF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,SAAS,EAAE,GAAG,cAAc,EAAE,CAIxE;AAqBD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,MAAM,CAuBjE;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CA4B5E"}
@@ -0,0 +1,203 @@
1
+ export function findElement(elements, query) {
2
+ for (const el of elements) {
3
+ if (matchesQuery(el, query)) {
4
+ return el;
5
+ }
6
+ if (el.children) {
7
+ const found = findElement(el.children, query);
8
+ if (found) {
9
+ return found;
10
+ }
11
+ }
12
+ }
13
+ return null;
14
+ }
15
+ export function matchesQuery(element, query) {
16
+ if (query.identifier && !fuzzyMatch(element.identifier, query.identifier)) {
17
+ return false;
18
+ }
19
+ if (query.label && !fuzzyMatch(element.label, query.label)) {
20
+ return false;
21
+ }
22
+ if (query.text &&
23
+ !fuzzyMatch(element.value, query.text) &&
24
+ !fuzzyMatch(element.label, query.text)) {
25
+ return false;
26
+ }
27
+ if (query.type && !fuzzyMatch(element.type, query.type)) {
28
+ return false;
29
+ }
30
+ const hasAtLeastOneFilter = query.identifier ?? query.label ?? query.text ?? query.type;
31
+ return Boolean(hasAtLeastOneFilter);
32
+ }
33
+ function fuzzyMatch(actual, expected) {
34
+ if (!actual) {
35
+ return false;
36
+ }
37
+ if (actual === expected) {
38
+ return true;
39
+ }
40
+ if (actual.toLowerCase() === expected.toLowerCase()) {
41
+ return true;
42
+ }
43
+ return actual.toLowerCase().includes(expected.toLowerCase());
44
+ }
45
+ export function describeElement(element) {
46
+ const parts = [element.type];
47
+ if (element.label) {
48
+ parts.push(`label="${element.label}"`);
49
+ }
50
+ if (element.identifier) {
51
+ parts.push(`id="${element.identifier}"`);
52
+ }
53
+ if (element.value) {
54
+ parts.push(`value="${element.value}"`);
55
+ }
56
+ return parts.join(' ');
57
+ }
58
+ export function computeSwipeEnd(startX, startY, direction, distance) {
59
+ switch (direction) {
60
+ case 'up':
61
+ return [startX, startY - distance];
62
+ case 'down':
63
+ return [startX, startY + distance];
64
+ case 'left':
65
+ return [startX - distance, startY];
66
+ case 'right':
67
+ return [startX + distance, startY];
68
+ default:
69
+ return [startX, startY];
70
+ }
71
+ }
72
+ const INTERACTIVE_TYPES = new Set([
73
+ 'Button',
74
+ 'TextField',
75
+ 'SecureTextField',
76
+ 'Switch',
77
+ 'Slider',
78
+ 'Picker',
79
+ 'Link',
80
+ 'SearchField',
81
+ 'TextArea',
82
+ 'Tab',
83
+ 'MenuItem',
84
+ 'Cell',
85
+ 'android.widget.Button',
86
+ 'android.widget.EditText',
87
+ 'android.widget.CheckBox',
88
+ 'android.widget.RadioButton',
89
+ 'android.widget.Switch',
90
+ 'android.widget.ToggleButton',
91
+ 'android.widget.Spinner',
92
+ 'android.widget.SeekBar',
93
+ 'android.widget.ImageButton',
94
+ ]);
95
+ function isInteractive(element) {
96
+ if (INTERACTIVE_TYPES.has(element.type)) {
97
+ return true;
98
+ }
99
+ for (const t of INTERACTIVE_TYPES) {
100
+ if (element.type.includes(t)) {
101
+ return true;
102
+ }
103
+ }
104
+ return element.enabled && Boolean(element.identifier);
105
+ }
106
+ function buildSuggestions(element) {
107
+ const suggestions = [];
108
+ if (element.identifier) {
109
+ suggestions.push({
110
+ strategy: 'identifier',
111
+ value: element.identifier,
112
+ confidence: 'high',
113
+ });
114
+ }
115
+ if (element.label) {
116
+ suggestions.push({
117
+ strategy: 'label',
118
+ value: element.label,
119
+ confidence: element.identifier ? 'medium' : 'high',
120
+ });
121
+ }
122
+ if (element.value && element.value !== element.label) {
123
+ suggestions.push({
124
+ strategy: 'text',
125
+ value: element.value,
126
+ confidence: 'low',
127
+ });
128
+ }
129
+ if (suggestions.length === 0) {
130
+ suggestions.push({
131
+ strategy: 'type',
132
+ value: element.type,
133
+ confidence: 'low',
134
+ });
135
+ }
136
+ return suggestions;
137
+ }
138
+ export function generateLocators(elements) {
139
+ const results = [];
140
+ collectLocators(elements, results);
141
+ return results;
142
+ }
143
+ function collectLocators(elements, results) {
144
+ for (const el of elements) {
145
+ if (isInteractive(el)) {
146
+ const suggestions = buildSuggestions(el);
147
+ results.push({
148
+ description: describeElement(el),
149
+ frame: el.frame,
150
+ suggestions,
151
+ });
152
+ }
153
+ if (el.children) {
154
+ collectLocators(el.children, results);
155
+ }
156
+ }
157
+ }
158
+ export function formatLocators(locators) {
159
+ if (locators.length === 0) {
160
+ return 'No interactive elements found on screen.';
161
+ }
162
+ const confidenceIcon = {
163
+ high: '***',
164
+ medium: '**',
165
+ low: '*',
166
+ };
167
+ return locators
168
+ .map((loc, i) => {
169
+ const header = `${i + 1}. ${loc.description} (${loc.frame.x},${loc.frame.y} ${loc.frame.width}x${loc.frame.height})`;
170
+ const strategies = loc.suggestions
171
+ .map((s) => ` ${confidenceIcon[s.confidence]} { ${s.strategy}: "${s.value}" }`)
172
+ .join('\n');
173
+ return `${header}\n${strategies}`;
174
+ })
175
+ .join('\n\n');
176
+ }
177
+ export function formatHierarchy(elements, depth) {
178
+ const indent = ' '.repeat(depth);
179
+ return elements
180
+ .map((el) => {
181
+ const parts = [`${indent}[${el.type}]`];
182
+ if (el.label) {
183
+ parts.push(`label="${el.label}"`);
184
+ }
185
+ if (el.identifier) {
186
+ parts.push(`id="${el.identifier}"`);
187
+ }
188
+ if (el.value) {
189
+ parts.push(`value="${el.value}"`);
190
+ }
191
+ parts.push(`(${el.frame.x},${el.frame.y} ${el.frame.width}x${el.frame.height})`);
192
+ if (!el.enabled) {
193
+ parts.push('DISABLED');
194
+ }
195
+ let result = parts.join(' ');
196
+ if (el.children?.length) {
197
+ result += `\n${formatHierarchy(el.children, depth + 1)}`;
198
+ }
199
+ return result;
200
+ })
201
+ .join('\n');
202
+ }
203
+ //# sourceMappingURL=element.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"element.mjs","sourceRoot":"","sources":["../../src/utils/element.ts"],"names":[],"mappings":"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","sourcesContent":["import type { UIElement, ElementQuery } from '../backends/types.js';\n\nexport function findElement(\n elements: UIElement[],\n query: ElementQuery,\n): UIElement | null {\n for (const el of elements) {\n if (matchesQuery(el, query)) {\n return el;\n }\n if (el.children) {\n const found = findElement(el.children, query);\n if (found) {\n return found;\n }\n }\n }\n return null;\n}\n\nexport function matchesQuery(element: UIElement, query: ElementQuery): boolean {\n if (query.identifier && !fuzzyMatch(element.identifier, query.identifier)) {\n return false;\n }\n if (query.label && !fuzzyMatch(element.label, query.label)) {\n return false;\n }\n if (\n query.text &&\n !fuzzyMatch(element.value, query.text) &&\n !fuzzyMatch(element.label, query.text)\n ) {\n return false;\n }\n if (query.type && !fuzzyMatch(element.type, query.type)) {\n return false;\n }\n\n const hasAtLeastOneFilter =\n query.identifier ?? query.label ?? query.text ?? query.type;\n return Boolean(hasAtLeastOneFilter);\n}\n\nfunction fuzzyMatch(actual: string | undefined, expected: string): boolean {\n if (!actual) {\n return false;\n }\n if (actual === expected) {\n return true;\n }\n if (actual.toLowerCase() === expected.toLowerCase()) {\n return true;\n }\n return actual.toLowerCase().includes(expected.toLowerCase());\n}\n\nexport function describeElement(element: UIElement): string {\n const parts: string[] = [element.type];\n if (element.label) {\n parts.push(`label=\"${element.label}\"`);\n }\n if (element.identifier) {\n parts.push(`id=\"${element.identifier}\"`);\n }\n if (element.value) {\n parts.push(`value=\"${element.value}\"`);\n }\n return parts.join(' ');\n}\n\nexport function computeSwipeEnd(\n startX: number,\n startY: number,\n direction: 'up' | 'down' | 'left' | 'right',\n distance: number,\n): [number, number] {\n switch (direction) {\n case 'up':\n return [startX, startY - distance];\n case 'down':\n return [startX, startY + distance];\n case 'left':\n return [startX - distance, startY];\n case 'right':\n return [startX + distance, startY];\n default:\n return [startX, startY];\n }\n}\n\nexport type LocatorSuggestion = {\n strategy: 'identifier' | 'label' | 'text' | 'type';\n value: string;\n confidence: 'high' | 'medium' | 'low';\n};\n\nexport type ElementLocator = {\n description: string;\n frame: { x: number; y: number; width: number; height: number };\n suggestions: LocatorSuggestion[];\n};\n\nconst INTERACTIVE_TYPES = new Set([\n 'Button',\n 'TextField',\n 'SecureTextField',\n 'Switch',\n 'Slider',\n 'Picker',\n 'Link',\n 'SearchField',\n 'TextArea',\n 'Tab',\n 'MenuItem',\n 'Cell',\n 'android.widget.Button',\n 'android.widget.EditText',\n 'android.widget.CheckBox',\n 'android.widget.RadioButton',\n 'android.widget.Switch',\n 'android.widget.ToggleButton',\n 'android.widget.Spinner',\n 'android.widget.SeekBar',\n 'android.widget.ImageButton',\n]);\n\nfunction isInteractive(element: UIElement): boolean {\n if (INTERACTIVE_TYPES.has(element.type)) {\n return true;\n }\n for (const t of INTERACTIVE_TYPES) {\n if (element.type.includes(t)) {\n return true;\n }\n }\n return element.enabled && Boolean(element.identifier);\n}\n\nfunction buildSuggestions(element: UIElement): LocatorSuggestion[] {\n const suggestions: LocatorSuggestion[] = [];\n\n if (element.identifier) {\n suggestions.push({\n strategy: 'identifier',\n value: element.identifier,\n confidence: 'high',\n });\n }\n\n if (element.label) {\n suggestions.push({\n strategy: 'label',\n value: element.label,\n confidence: element.identifier ? 'medium' : 'high',\n });\n }\n\n if (element.value && element.value !== element.label) {\n suggestions.push({\n strategy: 'text',\n value: element.value,\n confidence: 'low',\n });\n }\n\n if (suggestions.length === 0) {\n suggestions.push({\n strategy: 'type',\n value: element.type,\n confidence: 'low',\n });\n }\n\n return suggestions;\n}\n\nexport function generateLocators(elements: UIElement[]): ElementLocator[] {\n const results: ElementLocator[] = [];\n collectLocators(elements, results);\n return results;\n}\n\nfunction collectLocators(\n elements: UIElement[],\n results: ElementLocator[],\n): void {\n for (const el of elements) {\n if (isInteractive(el)) {\n const suggestions = buildSuggestions(el);\n results.push({\n description: describeElement(el),\n frame: el.frame,\n suggestions,\n });\n }\n if (el.children) {\n collectLocators(el.children, results);\n }\n }\n}\n\nexport function formatLocators(locators: ElementLocator[]): string {\n if (locators.length === 0) {\n return 'No interactive elements found on screen.';\n }\n\n const confidenceIcon: Record<string, string> = {\n high: '***',\n medium: '**',\n low: '*',\n };\n\n return locators\n .map((loc, i) => {\n const header = `${i + 1}. ${loc.description} (${loc.frame.x},${loc.frame.y} ${loc.frame.width}x${loc.frame.height})`;\n const strategies = loc.suggestions\n .map(\n (s) =>\n ` ${confidenceIcon[s.confidence]} { ${s.strategy}: \"${s.value}\" }`,\n )\n .join('\\n');\n return `${header}\\n${strategies}`;\n })\n .join('\\n\\n');\n}\n\nexport function formatHierarchy(elements: UIElement[], depth: number): string {\n const indent = ' '.repeat(depth);\n return elements\n .map((el) => {\n const parts = [`${indent}[${el.type}]`];\n if (el.label) {\n parts.push(`label=\"${el.label}\"`);\n }\n if (el.identifier) {\n parts.push(`id=\"${el.identifier}\"`);\n }\n if (el.value) {\n parts.push(`value=\"${el.value}\"`);\n }\n parts.push(\n `(${el.frame.x},${el.frame.y} ${el.frame.width}x${el.frame.height})`,\n );\n if (!el.enabled) {\n parts.push('DISABLED');\n }\n\n let result = parts.join(' ');\n if (el.children?.length) {\n result += `\\n${formatHierarchy(el.children, depth + 1)}`;\n }\n return result;\n })\n .join('\\n');\n}\n"]}
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.exec = exec;
4
+ exports.execStrict = execStrict;
5
+ exports.isCommandAvailable = isCommandAvailable;
6
+ const node_child_process_1 = require("node:child_process");
7
+ /**
8
+ * Execute a command and return stdout/stderr/exitCode.
9
+ * Rejects only on spawn failure — non-zero exit codes are returned, not thrown.
10
+ *
11
+ * @param command - The command to execute.
12
+ * @param args - Arguments to pass to the command.
13
+ * @param options - Execution options including timeoutMs.
14
+ * @returns The stdout, stderr, and exit code.
15
+ */
16
+ async function exec(command, args, options) {
17
+ const { timeoutMs = 30000, ...execOptions } = options ?? {};
18
+ return new Promise((resolve, reject) => {
19
+ const child = (0, node_child_process_1.execFile)(command, args, {
20
+ ...execOptions,
21
+ timeout: timeoutMs,
22
+ maxBuffer: 10 * 1024 * 1024, // 10MB — UI hierarchies can be large
23
+ }, (error, stdout, stderr) => {
24
+ if (error && !('code' in error)) {
25
+ // Spawn failure (command not found, ENOENT, etc.)
26
+ reject(new Error(error.message));
27
+ return;
28
+ }
29
+ resolve({
30
+ stdout: stdout?.toString() ?? '',
31
+ stderr: stderr?.toString() ?? '',
32
+ exitCode: error?.code
33
+ ? 1
34
+ : (child.exitCode ?? 0),
35
+ });
36
+ });
37
+ });
38
+ }
39
+ async function execStrict(command, args, options) {
40
+ const result = await exec(command, args, options);
41
+ if (result.exitCode !== 0) {
42
+ throw new Error(`Command failed: ${command} ${args.join(' ')}\n` +
43
+ `Exit code: ${result.exitCode}\n` +
44
+ `stderr: ${result.stderr}`);
45
+ }
46
+ return result.stdout;
47
+ }
48
+ async function isCommandAvailable(command) {
49
+ try {
50
+ await exec('which', [command], { timeoutMs: 5000 });
51
+ return true;
52
+ }
53
+ catch {
54
+ return false;
55
+ }
56
+ }
57
+ //# sourceMappingURL=exec.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exec.cjs","sourceRoot":"","sources":["../../src/utils/exec.ts"],"names":[],"mappings":";;AAiBA,oBAgCC;AAED,gCAcC;AAED,gDAOC;AA1ED,2DAA+D;AAQ/D;;;;;;;;GAQG;AACI,KAAK,UAAU,IAAI,CACxB,OAAe,EACf,IAAc,EACd,OAAkD;IAElD,MAAM,EAAE,SAAS,GAAG,KAAM,EAAE,GAAG,WAAW,EAAE,GAAG,OAAO,IAAI,EAAE,CAAC;IAE7D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,KAAK,GAAG,IAAA,6BAAQ,EACpB,OAAO,EACP,IAAI,EACJ;YACE,GAAG,WAAW;YACd,OAAO,EAAE,SAAS;YAClB,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,qCAAqC;SACnE,EACD,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACxB,IAAI,KAAK,IAAI,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,EAAE,CAAC;gBAChC,kDAAkD;gBAClD,MAAM,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;gBACjC,OAAO;YACT,CAAC;YACD,OAAO,CAAC;gBACN,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;gBAChC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;gBAChC,QAAQ,EAAG,KAA+B,EAAE,IAAI;oBAC9C,CAAC,CAAC,CAAC;oBACH,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,IAAI,CAAC,CAAC;aAC1B,CAAC,CAAC;QACL,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAEM,KAAK,UAAU,UAAU,CAC9B,OAAe,EACf,IAAc,EACd,OAAkD;IAElD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IAClD,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CACb,mBAAmB,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI;YAC9C,cAAc,MAAM,CAAC,QAAQ,IAAI;YACjC,WAAW,MAAM,CAAC,MAAM,EAAE,CAC7B,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC;AACvB,CAAC;AAEM,KAAK,UAAU,kBAAkB,CAAC,OAAe;IACtD,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAK,EAAE,CAAC,CAAC;QACrD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC","sourcesContent":["import { execFile, ExecFileOptions } from 'node:child_process';\n\nexport type ExecResult = {\n stdout: string;\n stderr: string;\n exitCode: number;\n};\n\n/**\n * Execute a command and return stdout/stderr/exitCode.\n * Rejects only on spawn failure — non-zero exit codes are returned, not thrown.\n *\n * @param command - The command to execute.\n * @param args - Arguments to pass to the command.\n * @param options - Execution options including timeoutMs.\n * @returns The stdout, stderr, and exit code.\n */\nexport async function exec(\n command: string,\n args: string[],\n options?: ExecFileOptions & { timeoutMs?: number },\n): Promise<ExecResult> {\n const { timeoutMs = 30_000, ...execOptions } = options ?? {};\n\n return new Promise((resolve, reject) => {\n const child = execFile(\n command,\n args,\n {\n ...execOptions,\n timeout: timeoutMs,\n maxBuffer: 10 * 1024 * 1024, // 10MB — UI hierarchies can be large\n },\n (error, stdout, stderr) => {\n if (error && !('code' in error)) {\n // Spawn failure (command not found, ENOENT, etc.)\n reject(new Error(error.message));\n return;\n }\n resolve({\n stdout: stdout?.toString() ?? '',\n stderr: stderr?.toString() ?? '',\n exitCode: (error as NodeJS.ErrnoException)?.code\n ? 1\n : (child.exitCode ?? 0),\n });\n },\n );\n });\n}\n\nexport async function execStrict(\n command: string,\n args: string[],\n options?: ExecFileOptions & { timeoutMs?: number },\n): Promise<string> {\n const result = await exec(command, args, options);\n if (result.exitCode !== 0) {\n throw new Error(\n `Command failed: ${command} ${args.join(' ')}\\n` +\n `Exit code: ${result.exitCode}\\n` +\n `stderr: ${result.stderr}`,\n );\n }\n return result.stdout;\n}\n\nexport async function isCommandAvailable(command: string): Promise<boolean> {\n try {\n await exec('which', [command], { timeoutMs: 5_000 });\n return true;\n } catch {\n return false;\n }\n}\n"]}
@@ -0,0 +1,23 @@
1
+ import { ExecFileOptions } from "node:child_process";
2
+ export type ExecResult = {
3
+ stdout: string;
4
+ stderr: string;
5
+ exitCode: number;
6
+ };
7
+ /**
8
+ * Execute a command and return stdout/stderr/exitCode.
9
+ * Rejects only on spawn failure — non-zero exit codes are returned, not thrown.
10
+ *
11
+ * @param command - The command to execute.
12
+ * @param args - Arguments to pass to the command.
13
+ * @param options - Execution options including timeoutMs.
14
+ * @returns The stdout, stderr, and exit code.
15
+ */
16
+ export declare function exec(command: string, args: string[], options?: ExecFileOptions & {
17
+ timeoutMs?: number;
18
+ }): Promise<ExecResult>;
19
+ export declare function execStrict(command: string, args: string[], options?: ExecFileOptions & {
20
+ timeoutMs?: number;
21
+ }): Promise<string>;
22
+ export declare function isCommandAvailable(command: string): Promise<boolean>;
23
+ //# sourceMappingURL=exec.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exec.d.cts","sourceRoot":"","sources":["../../src/utils/exec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,eAAe,EAAE,2BAA2B;AAE/D,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAsB,IAAI,CACxB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,CAAC,EAAE,eAAe,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GACjD,OAAO,CAAC,UAAU,CAAC,CA4BrB;AAED,wBAAsB,UAAU,CAC9B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,CAAC,EAAE,eAAe,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GACjD,OAAO,CAAC,MAAM,CAAC,CAUjB;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO1E"}
@@ -0,0 +1,23 @@
1
+ import { ExecFileOptions } from "node:child_process";
2
+ export type ExecResult = {
3
+ stdout: string;
4
+ stderr: string;
5
+ exitCode: number;
6
+ };
7
+ /**
8
+ * Execute a command and return stdout/stderr/exitCode.
9
+ * Rejects only on spawn failure — non-zero exit codes are returned, not thrown.
10
+ *
11
+ * @param command - The command to execute.
12
+ * @param args - Arguments to pass to the command.
13
+ * @param options - Execution options including timeoutMs.
14
+ * @returns The stdout, stderr, and exit code.
15
+ */
16
+ export declare function exec(command: string, args: string[], options?: ExecFileOptions & {
17
+ timeoutMs?: number;
18
+ }): Promise<ExecResult>;
19
+ export declare function execStrict(command: string, args: string[], options?: ExecFileOptions & {
20
+ timeoutMs?: number;
21
+ }): Promise<string>;
22
+ export declare function isCommandAvailable(command: string): Promise<boolean>;
23
+ //# sourceMappingURL=exec.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exec.d.mts","sourceRoot":"","sources":["../../src/utils/exec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,eAAe,EAAE,2BAA2B;AAE/D,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAsB,IAAI,CACxB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,CAAC,EAAE,eAAe,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GACjD,OAAO,CAAC,UAAU,CAAC,CA4BrB;AAED,wBAAsB,UAAU,CAC9B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,CAAC,EAAE,eAAe,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GACjD,OAAO,CAAC,MAAM,CAAC,CAUjB;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO1E"}