codeloop-mcp-server 0.1.87 → 0.1.91

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 (62) hide show
  1. package/dist/evidence/interaction_coverage.d.ts.map +1 -1
  2. package/dist/evidence/interaction_coverage.js +2 -0
  3. package/dist/evidence/interaction_coverage.js.map +1 -1
  4. package/dist/index.js +244 -55
  5. package/dist/index.js.map +1 -1
  6. package/dist/runners/active_target.d.ts +31 -0
  7. package/dist/runners/active_target.d.ts.map +1 -0
  8. package/dist/runners/active_target.js +65 -0
  9. package/dist/runners/active_target.js.map +1 -0
  10. package/dist/runners/app_launcher.d.ts +37 -1
  11. package/dist/runners/app_launcher.d.ts.map +1 -1
  12. package/dist/runners/app_launcher.js +114 -15
  13. package/dist/runners/app_launcher.js.map +1 -1
  14. package/dist/runners/app_logger.d.ts +1 -1
  15. package/dist/runners/app_logger.d.ts.map +1 -1
  16. package/dist/runners/app_logger.js +9 -8
  17. package/dist/runners/app_logger.js.map +1 -1
  18. package/dist/runners/device_probe.d.ts +18 -0
  19. package/dist/runners/device_probe.d.ts.map +1 -1
  20. package/dist/runners/device_probe.js +47 -20
  21. package/dist/runners/device_probe.js.map +1 -1
  22. package/dist/runners/interaction_engine.d.ts +6 -0
  23. package/dist/runners/interaction_engine.d.ts.map +1 -1
  24. package/dist/runners/interaction_engine.js +64 -21
  25. package/dist/runners/interaction_engine.js.map +1 -1
  26. package/dist/runners/ios_sim_input.d.ts +106 -0
  27. package/dist/runners/ios_sim_input.d.ts.map +1 -0
  28. package/dist/runners/ios_sim_input.js +453 -0
  29. package/dist/runners/ios_sim_input.js.map +1 -0
  30. package/dist/runners/maestro.d.ts +27 -2
  31. package/dist/runners/maestro.d.ts.map +1 -1
  32. package/dist/runners/maestro.js +57 -7
  33. package/dist/runners/maestro.js.map +1 -1
  34. package/dist/runners/maestro_generator.d.ts +1 -1
  35. package/dist/runners/maestro_generator.d.ts.map +1 -1
  36. package/dist/runners/maestro_generator.js +3 -2
  37. package/dist/runners/maestro_generator.js.map +1 -1
  38. package/dist/runners/screenshot.d.ts +6 -0
  39. package/dist/runners/screenshot.d.ts.map +1 -1
  40. package/dist/runners/screenshot.js +10 -9
  41. package/dist/runners/screenshot.js.map +1 -1
  42. package/dist/runners/video_recorder.d.ts +3 -1
  43. package/dist/runners/video_recorder.d.ts.map +1 -1
  44. package/dist/runners/video_recorder.js +19 -6
  45. package/dist/runners/video_recorder.js.map +1 -1
  46. package/dist/runners/window_manager.d.ts +55 -25
  47. package/dist/runners/window_manager.d.ts.map +1 -1
  48. package/dist/runners/window_manager.js +171 -71
  49. package/dist/runners/window_manager.js.map +1 -1
  50. package/dist/tools/discover_interactions.d.ts +22 -0
  51. package/dist/tools/discover_interactions.d.ts.map +1 -1
  52. package/dist/tools/discover_interactions.js +278 -5
  53. package/dist/tools/discover_interactions.js.map +1 -1
  54. package/dist/tools/discover_screens.d.ts +2 -0
  55. package/dist/tools/discover_screens.d.ts.map +1 -1
  56. package/dist/tools/discover_screens.js +139 -1
  57. package/dist/tools/discover_screens.js.map +1 -1
  58. package/dist/tools/run_journey.d.ts +7 -0
  59. package/dist/tools/run_journey.d.ts.map +1 -1
  60. package/dist/tools/run_journey.js +106 -17
  61. package/dist/tools/run_journey.js.map +1 -1
  62. package/package.json +2 -2
@@ -0,0 +1,453 @@
1
+ /**
2
+ * iOS Simulator input backend — REAL tap/type/swipe/key for the Simulator.
3
+ *
4
+ * Why this exists: `xcrun simctl io` only supports `enumerate / poll /
5
+ * recordVideo / screenshot`. The old `simctl io booted tap/type/swipe/
6
+ * sendkey` helpers in window_manager.ts invoked subcommands that have
7
+ * NEVER existed in any Xcode release, so every coordinate-driven iOS
8
+ * interaction failed unconditionally (E2E 11, WedCheese run).
9
+ *
10
+ * Strategy (in preference order):
11
+ * 1. `idb` (Facebook's iOS debug bridge) when on PATH — drives HID
12
+ * events directly into the simulator (`idb ui tap/swipe/text`),
13
+ * no window focus needed, coordinates are device POINTS.
14
+ * 2. CGEvent via the Simulator window (always available on macOS):
15
+ * bring Simulator frontmost → resolve the DEVICE CONTENT rect →
16
+ * a fresh device screenshot gives a self-calibrating map from
17
+ * screenshot pixels → host screen points → post real events.
18
+ * The content rect comes from the Accessibility tree (the device
19
+ * screen is an AXGroup sized exactly device-points — robust to
20
+ * bezels, zoom levels, and toolbar chrome); when AX is unavailable
21
+ * we fall back to window-bounds width-ratio calibration (default
22
+ * window mode only).
23
+ *
24
+ * Coordinate convention: callers pass coordinates in DEVICE PIXELS as
25
+ * seen on a `simctl io screenshot` PNG (that's what the agent's vision
26
+ * works from). Both backends translate internally.
27
+ */
28
+ import { join } from "path";
29
+ import { tmpdir, platform } from "os";
30
+ import { unlinkSync, existsSync } from "fs";
31
+ import { runCommand, checkToolAvailable } from "./base.js";
32
+ import { readPngDims } from "./png_dims.js";
33
+ import { getWindowBounds, bringAppToFront, clickAtPosition, dragDrop, longPressAtPosition, typeText, sendKeyByName, sendHotkey, } from "./window_manager.js";
34
+ const SIMULATOR_APP = "Simulator";
35
+ /** Calibration is re-used briefly so a tap+type sequence doesn't re-screenshot. */
36
+ const MAPPING_TTL_MS = 5_000;
37
+ /** Window chrome (title bar) sanity range, in screen points. */
38
+ const MAX_CHROME_PT = 120;
39
+ const SCREENSHOT_TIMEOUT_MS = 20_000;
40
+ const IDB_TIMEOUT_MS = 30_000;
41
+ // ── idb backend detection ───────────────────────────────────────────
42
+ let idbAvailableCache = null;
43
+ export async function isIdbAvailable() {
44
+ if (idbAvailableCache !== null)
45
+ return idbAvailableCache;
46
+ idbAvailableCache = await checkToolAvailable("idb");
47
+ return idbAvailableCache;
48
+ }
49
+ /** Test hook — reset memoized idb detection + calibration. */
50
+ export function resetIosInputCaches() {
51
+ idbAvailableCache = null;
52
+ mappingCache.clear();
53
+ }
54
+ const idbDimsCache = new Map();
55
+ /**
56
+ * idb expects coordinates in device POINTS; our callers supply device
57
+ * PIXELS (screenshot space). `idb describe` exposes both so we can
58
+ * scale. Cached per udid — device geometry never changes while booted.
59
+ */
60
+ async function idbScreenDims(udid) {
61
+ const cached = idbDimsCache.get(udid);
62
+ if (cached)
63
+ return cached;
64
+ const args = ["describe", "--json"];
65
+ if (udid && udid !== "booted")
66
+ args.push("--udid", udid);
67
+ const result = await runCommand("idb", args, process.cwd(), undefined, undefined, IDB_TIMEOUT_MS);
68
+ if (result.exit_code !== 0)
69
+ return null;
70
+ try {
71
+ const parsed = JSON.parse(result.stdout);
72
+ const sd = parsed.screen_dimensions;
73
+ if (!sd?.width || !sd.height || !sd.width_points || !sd.height_points)
74
+ return null;
75
+ const dims = {
76
+ widthPixels: sd.width,
77
+ heightPixels: sd.height,
78
+ widthPoints: sd.width_points,
79
+ heightPoints: sd.height_points,
80
+ };
81
+ idbDimsCache.set(udid, dims);
82
+ return dims;
83
+ }
84
+ catch {
85
+ return null;
86
+ }
87
+ }
88
+ function idbUdidArgs(udid) {
89
+ return udid && udid !== "booted" ? ["--udid", udid] : [];
90
+ }
91
+ /** Convert device-pixel coords → device points for idb. */
92
+ async function toIdbPoints(udid, x, y) {
93
+ const dims = await idbScreenDims(udid);
94
+ if (!dims)
95
+ return null;
96
+ const factor = dims.widthPixels > 0 ? dims.widthPoints / dims.widthPixels : 1;
97
+ return { x: Math.round(x * factor), y: Math.round(y * factor) };
98
+ }
99
+ // ── CGEvent backend: content-rect calibration ───────────────────────
100
+ const mappingCache = new Map();
101
+ /**
102
+ * The device screen inside the Simulator window is an AXGroup whose AX
103
+ * frame is exactly the content rect in global screen points — robust to
104
+ * device bezels, zoom level, and toolbar chrome (all of which break the
105
+ * naive "content spans the window width" assumption; live debugging
106
+ * showed Xcode 26's Simulator adds bezel margins by default).
107
+ *
108
+ * Returns every device-content group across Simulator windows so the
109
+ * caller can match the window title to the pinned device's name.
110
+ */
111
+ async function getSimulatorContentRects() {
112
+ const script = [
113
+ 'set out to ""',
114
+ 'tell application "System Events" to tell process "Simulator"',
115
+ ' repeat with w in windows',
116
+ ' try',
117
+ ' repeat with el in UI elements of w',
118
+ ' if role of el is "AXGroup" then',
119
+ ' set p to position of el',
120
+ ' set s to size of el',
121
+ ' set out to out & (name of w) & "|" & (item 1 of p) & "|" & (item 2 of p) & "|" & (item 1 of s) & "|" & (item 2 of s) & linefeed',
122
+ ' end if',
123
+ ' end repeat',
124
+ ' end try',
125
+ ' end repeat',
126
+ 'end tell',
127
+ 'return out',
128
+ ].join("\n");
129
+ const r = await runCommand("osascript", ["-e", script], process.cwd(), undefined, undefined, 15_000);
130
+ if (r.exit_code !== 0)
131
+ return [];
132
+ const rects = [];
133
+ for (const line of r.stdout.split("\n")) {
134
+ const parts = line.trim().split("|");
135
+ if (parts.length !== 5)
136
+ continue;
137
+ const [windowName, xs, ys, ws, hs] = parts;
138
+ const x = Number(xs), y = Number(ys), width = Number(ws), height = Number(hs);
139
+ if ([x, y, width, height].some((n) => !Number.isFinite(n)) || width < 50 || height < 50)
140
+ continue;
141
+ rects.push({ x, y, width, height, windowName });
142
+ }
143
+ return rects;
144
+ }
145
+ /** Booted-device name for a udid (so multi-sim setups pick the right window). */
146
+ async function simDeviceName(udid) {
147
+ if (!udid || udid === "booted")
148
+ return null;
149
+ const r = await runCommand("xcrun", ["simctl", "list", "devices", "booted"], process.cwd(), undefined, undefined, 15_000);
150
+ if (r.exit_code !== 0)
151
+ return null;
152
+ const m = r.stdout.split("\n").find((l) => l.includes(udid));
153
+ if (!m)
154
+ return null;
155
+ const name = m.trim().split(`(${udid}`)[0]?.trim();
156
+ return name && name.length > 0 ? name : null;
157
+ }
158
+ /**
159
+ * Self-calibrating screenshot-pixel → host-screen-point mapping.
160
+ *
161
+ * 1. Bring Simulator frontmost (CGEvents land on whatever is under
162
+ * the cursor — we need our window there, and key events need focus).
163
+ * 2. Resolve the device CONTENT rect via the AX tree (preferred), or
164
+ * fall back to `getWindowBounds("Simulator")` width-ratio math
165
+ * (default window mode, no bezels, title bar only).
166
+ * 3. `simctl io <udid> screenshot` + IHDR read → device pixel dims →
167
+ * scale = contentWidth / pngWidth.
168
+ *
169
+ * Returns null when no Simulator window can be resolved or the
170
+ * geometry is implausible — callers surface a directive instead of
171
+ * clicking blind.
172
+ */
173
+ export async function resolveSimMapping(udid = "booted", opts) {
174
+ // os.platform() (not process.platform) so unit tests can mock the host —
175
+ // CI runs this suite on Linux where the real value would null every mapping.
176
+ if (platform() !== "darwin")
177
+ return null;
178
+ const cacheKey = udid || "booted";
179
+ if (!opts?.forceRefresh) {
180
+ const cached = mappingCache.get(cacheKey);
181
+ if (cached && Date.now() - cached.capturedAt < MAPPING_TTL_MS)
182
+ return cached;
183
+ }
184
+ try {
185
+ await bringAppToFront(SIMULATOR_APP);
186
+ }
187
+ catch { /* best-effort */ }
188
+ // Give the window server a beat to finish the front-switch animation.
189
+ await new Promise((r) => setTimeout(r, 300));
190
+ const shotPath = join(tmpdir(), `codeloop_sim_cal_${Date.now()}.png`);
191
+ try {
192
+ const shot = await runCommand("xcrun", ["simctl", "io", cacheKey, "screenshot", shotPath], process.cwd(), undefined, undefined, SCREENSHOT_TIMEOUT_MS);
193
+ if (shot.exit_code !== 0 || !existsSync(shotPath))
194
+ return null;
195
+ const dims = readPngDims(shotPath);
196
+ if (!dims)
197
+ return null;
198
+ const aspect = dims.height / dims.width;
199
+ // Preferred: exact content rect from the AX tree.
200
+ const rects = await getSimulatorContentRects();
201
+ if (rects.length > 0) {
202
+ const wantedName = await simDeviceName(cacheKey);
203
+ const matching = wantedName ? rects.filter((r) => r.windowName.includes(wantedName)) : rects;
204
+ // Among candidates prefer the one whose aspect matches this device's
205
+ // screenshot (belt + suspenders when several sims are visible).
206
+ const pool = matching.length > 0 ? matching : rects;
207
+ const best = pool
208
+ .map((r) => ({ r, aspectErr: Math.abs(r.height / r.width - aspect) / aspect }))
209
+ .sort((a, b) => a.aspectErr - b.aspectErr)[0];
210
+ if (best && best.aspectErr < 0.05) {
211
+ const mapping = {
212
+ bounds: { x: best.r.x, y: best.r.y, width: best.r.width, height: best.r.height },
213
+ pngWidth: dims.width,
214
+ pngHeight: dims.height,
215
+ scale: best.r.width / dims.width,
216
+ chromeTop: 0,
217
+ method: "ax",
218
+ udid: cacheKey,
219
+ capturedAt: Date.now(),
220
+ };
221
+ mappingCache.set(cacheKey, mapping);
222
+ return mapping;
223
+ }
224
+ }
225
+ // Fallback: window bounds + width-ratio (default window mode only —
226
+ // content spans the full width with a title bar on top).
227
+ const bounds = await getWindowBounds(SIMULATOR_APP);
228
+ if (!bounds || bounds.width <= 0 || bounds.height <= 0)
229
+ return null;
230
+ const scale = bounds.width / dims.width;
231
+ const chromeTop = bounds.height - dims.height * scale;
232
+ if (!Number.isFinite(scale) || scale <= 0 || chromeTop < -2 || chromeTop > MAX_CHROME_PT) {
233
+ return null;
234
+ }
235
+ const mapping = {
236
+ bounds,
237
+ pngWidth: dims.width,
238
+ pngHeight: dims.height,
239
+ scale,
240
+ chromeTop: Math.max(0, chromeTop),
241
+ method: "window-ratio",
242
+ udid: cacheKey,
243
+ capturedAt: Date.now(),
244
+ };
245
+ mappingCache.set(cacheKey, mapping);
246
+ return mapping;
247
+ }
248
+ finally {
249
+ try {
250
+ unlinkSync(shotPath);
251
+ }
252
+ catch { /* ignore */ }
253
+ }
254
+ }
255
+ /** Map a device-pixel coordinate to a host-screen point. Exported for tests. */
256
+ export function mapToScreenPoint(mapping, x, y) {
257
+ return {
258
+ x: mapping.bounds.x + x * mapping.scale,
259
+ y: mapping.bounds.y + mapping.chromeTop + y * mapping.scale,
260
+ };
261
+ }
262
+ const MAPPING_FAIL_DETAIL = "could not calibrate the Simulator window (is the Simulator app open and visible, with Accessibility + Screen Recording permissions granted to the agent's terminal?). " +
263
+ "Try: open -a Simulator, ensure the device window is on screen, then retry. " +
264
+ "Installing idb (`brew install idb-companion && pipx install fb-idb`) enables window-independent input.";
265
+ // ── Public input actions ─────────────────────────────────────────────
266
+ export async function iosSimTap(x, y, udid = "booted") {
267
+ if (await isIdbAvailable()) {
268
+ const pt = await toIdbPoints(udid, x, y);
269
+ if (pt) {
270
+ const r = await runCommand("idb", ["ui", "tap", ...idbUdidArgs(udid), String(pt.x), String(pt.y)], process.cwd(), undefined, undefined, IDB_TIMEOUT_MS);
271
+ if (r.exit_code === 0) {
272
+ return { success: true, backend: "idb", detail: `idb tap (${pt.x},${pt.y}) pt` };
273
+ }
274
+ // fall through to CGEvent on idb failure
275
+ }
276
+ }
277
+ const mapping = await resolveSimMapping(udid);
278
+ if (!mapping)
279
+ return { success: false, backend: "none", detail: MAPPING_FAIL_DETAIL };
280
+ const pt = mapToScreenPoint(mapping, x, y);
281
+ const ok = await clickAtPosition(pt.x, pt.y);
282
+ return {
283
+ success: ok,
284
+ backend: "cgevent",
285
+ detail: ok
286
+ ? `cgevent tap px(${Math.round(x)},${Math.round(y)}) → screen(${Math.round(pt.x)},${Math.round(pt.y)})`
287
+ : "CGEvent click failed (Accessibility permission?)",
288
+ };
289
+ }
290
+ export async function iosSimLongPress(x, y, durationMs = 1000, udid = "booted") {
291
+ // idb has no long-press primitive; CGEvent press-and-hold works on the window.
292
+ const mapping = await resolveSimMapping(udid);
293
+ if (!mapping)
294
+ return { success: false, backend: "none", detail: MAPPING_FAIL_DETAIL };
295
+ const pt = mapToScreenPoint(mapping, x, y);
296
+ const ok = await longPressAtPosition(pt.x, pt.y, durationMs);
297
+ return {
298
+ success: ok,
299
+ backend: "cgevent",
300
+ detail: ok ? `cgevent long-press ${durationMs}ms at screen(${Math.round(pt.x)},${Math.round(pt.y)})` : "CGEvent long-press failed",
301
+ };
302
+ }
303
+ export async function iosSimSwipe(x1, y1, x2, y2, durationMs = 300, udid = "booted") {
304
+ if (await isIdbAvailable()) {
305
+ const a = await toIdbPoints(udid, x1, y1);
306
+ const b = await toIdbPoints(udid, x2, y2);
307
+ if (a && b) {
308
+ const r = await runCommand("idb", [
309
+ "ui", "swipe", ...idbUdidArgs(udid),
310
+ "--duration", String(Math.max(0.05, durationMs / 1000)),
311
+ String(a.x), String(a.y), String(b.x), String(b.y),
312
+ ], process.cwd(), undefined, undefined, IDB_TIMEOUT_MS);
313
+ if (r.exit_code === 0) {
314
+ return { success: true, backend: "idb", detail: `idb swipe (${a.x},${a.y})→(${b.x},${b.y}) pt` };
315
+ }
316
+ }
317
+ }
318
+ const mapping = await resolveSimMapping(udid);
319
+ if (!mapping)
320
+ return { success: false, backend: "none", detail: MAPPING_FAIL_DETAIL };
321
+ const a = mapToScreenPoint(mapping, x1, y1);
322
+ const b = mapToScreenPoint(mapping, x2, y2);
323
+ // CGEvent drag on the simulator content == touch swipe on the device.
324
+ const ok = await dragDrop(a.x, a.y, b.x, b.y, durationMs);
325
+ return {
326
+ success: ok,
327
+ backend: "cgevent",
328
+ detail: ok
329
+ ? `cgevent swipe screen(${Math.round(a.x)},${Math.round(a.y)})→(${Math.round(b.x)},${Math.round(b.y)})`
330
+ : "CGEvent drag failed (Accessibility permission?)",
331
+ };
332
+ }
333
+ export async function iosSimType(text, udid = "booted") {
334
+ if (text.length === 0)
335
+ return { success: true, backend: "none", detail: "empty text" };
336
+ if (await isIdbAvailable()) {
337
+ const r = await runCommand("idb", ["ui", "text", ...idbUdidArgs(udid), text], process.cwd(), undefined, undefined, IDB_TIMEOUT_MS);
338
+ if (r.exit_code === 0) {
339
+ return { success: true, backend: "idb", detail: `idb text (${text.length} chars)` };
340
+ }
341
+ }
342
+ // osascript keystroke goes to the frontmost app; Simulator forwards
343
+ // host keyboard input to the device when "Send Keyboard Input to
344
+ // Device" is enabled (the default).
345
+ try {
346
+ await bringAppToFront(SIMULATOR_APP);
347
+ }
348
+ catch { /* best-effort */ }
349
+ await new Promise((r) => setTimeout(r, 200));
350
+ const ok = await typeText(text);
351
+ return {
352
+ success: ok,
353
+ backend: "cgevent",
354
+ detail: ok ? `osascript keystroke (${text.length} chars)` : "osascript keystroke failed (Accessibility permission?)",
355
+ };
356
+ }
357
+ /**
358
+ * Send a named special key (return/tab/escape/arrows/…) to the simulator.
359
+ * Uses the same key-name normalisation as the desktop path.
360
+ */
361
+ export async function iosSimKey(keyName, udid = "booted") {
362
+ if (await isIdbAvailable()) {
363
+ // idb key takes an Apple HID keycode; map the common ones.
364
+ const HID = {
365
+ return: 40, enter: 40, tab: 43, space: 44, escape: 41, esc: 41,
366
+ backspace: 42, delete: 42, "forward-delete": 76,
367
+ up: 82, "up-arrow": 82, down: 81, "down-arrow": 81,
368
+ left: 80, "left-arrow": 80, right: 79, "right-arrow": 79,
369
+ home: 74, end: 77,
370
+ };
371
+ const code = HID[keyName.toLowerCase()];
372
+ if (code != null) {
373
+ const r = await runCommand("idb", ["ui", "key", ...idbUdidArgs(udid), String(code)], process.cwd(), undefined, undefined, IDB_TIMEOUT_MS);
374
+ if (r.exit_code === 0)
375
+ return { success: true, backend: "idb", detail: `idb key ${keyName}` };
376
+ }
377
+ }
378
+ try {
379
+ await bringAppToFront(SIMULATOR_APP);
380
+ }
381
+ catch { /* best-effort */ }
382
+ await new Promise((r) => setTimeout(r, 200));
383
+ const ok = await sendKeyByName(keyName);
384
+ return {
385
+ success: ok,
386
+ backend: "cgevent",
387
+ detail: ok ? `cgevent key ${keyName}` : `key "${keyName}" failed or unknown`,
388
+ };
389
+ }
390
+ /**
391
+ * Rotate the simulator via the Device menu shortcuts (Cmd+Left / Cmd+Right).
392
+ * There is no simctl rotation command, so this drives the Simulator app —
393
+ * P1.2 (0.1.91); previously rotate_device silently no-opped on iOS.
394
+ *
395
+ * `orientation` is a TARGET hint: we can't read the current orientation,
396
+ * so "landscape" rotates right once and "portrait" rotates left once —
397
+ * callers should verify with a screenshot (mapping cache is invalidated).
398
+ */
399
+ export async function iosSimRotate(orientation, udid = "booted") {
400
+ try {
401
+ await bringAppToFront(SIMULATOR_APP);
402
+ }
403
+ catch { /* best-effort */ }
404
+ await new Promise((r) => setTimeout(r, 300));
405
+ const combo = orientation === "landscape" ? "cmd+right" : "cmd+left";
406
+ const ok = await sendHotkey(combo);
407
+ // Window geometry changed — force recalibration on the next tap.
408
+ mappingCache.delete(udid || "booted");
409
+ return {
410
+ success: ok,
411
+ backend: "cgevent",
412
+ detail: ok
413
+ ? `${combo} sent to Simulator (target ${orientation}); rotation has no state read-back — verify with a screenshot`
414
+ : `${combo} failed — is the Simulator window frontmost and Accessibility permission granted?`,
415
+ };
416
+ }
417
+ /**
418
+ * Read the accessibility/UI tree off the simulator — only possible via
419
+ * idb (`describe-all`). Returns null when idb is missing so callers can
420
+ * keep the honest "use vision on a screenshot" directive.
421
+ */
422
+ export async function iosSimGetText(udid = "booted") {
423
+ if (!(await isIdbAvailable()))
424
+ return null;
425
+ const r = await runCommand("idb", ["ui", "describe-all", ...idbUdidArgs(udid)], process.cwd(), undefined, undefined, IDB_TIMEOUT_MS);
426
+ if (r.exit_code !== 0 || !r.stdout.trim())
427
+ return null;
428
+ // describe-all emits one JSON object per element; surface the visible
429
+ // labels/values as plain text for the agent.
430
+ const texts = [];
431
+ try {
432
+ const parsed = JSON.parse(r.stdout);
433
+ for (const el of parsed) {
434
+ const t = (el.AXLabel || el.AXValue || "").trim();
435
+ if (t && t !== "(null)")
436
+ texts.push(t);
437
+ }
438
+ }
439
+ catch {
440
+ // Some idb versions emit NDJSON.
441
+ for (const line of r.stdout.split("\n")) {
442
+ try {
443
+ const el = JSON.parse(line);
444
+ const t = (el.AXLabel || el.AXValue || "").trim();
445
+ if (t && t !== "(null)")
446
+ texts.push(t);
447
+ }
448
+ catch { /* skip non-JSON lines */ }
449
+ }
450
+ }
451
+ return texts.length > 0 ? texts.join("\n") : null;
452
+ }
453
+ //# sourceMappingURL=ios_sim_input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ios_sim_input.js","sourceRoot":"","sources":["../../src/runners/ios_sim_input.ts"],"names":[],"mappings":"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"}
@@ -8,8 +8,33 @@ export interface MaestroResult {
8
8
  logPath: string;
9
9
  }
10
10
  export declare function isMaestroInstalled(): Promise<boolean>;
11
+ /**
12
+ * Per-OS Maestro install directive. The old curl-only line dead-ended
13
+ * Windows hosts (no bash); Windows gets the PowerShell installer.
14
+ */
15
+ export declare function maestroInstallDirective(): string;
16
+ /** Per-OS Java install directive (Maestro is a JVM CLI; no JVM = no Maestro). */
17
+ export declare function javaInstallDirective(): string;
18
+ export interface MaestroPreflight {
19
+ ok: boolean;
20
+ /** Actionable per-OS directive when not ok. */
21
+ directive?: string;
22
+ }
23
+ /**
24
+ * Preflight the Maestro toolchain BEFORE driving: the `maestro` launcher is a
25
+ * shell script around a JVM CLI, so a missing/broken Java produces a cryptic
26
+ * mid-flow failure unless caught here. `java -version` exits 0 and prints the
27
+ * version to stderr on every JDK.
28
+ */
29
+ export declare function maestroPreflight(): Promise<MaestroPreflight>;
30
+ /**
31
+ * Env for Maestro runs: the FIRST run on an iOS simulator installs +
32
+ * boots the XCTest driver, which routinely exceeds Maestro's default
33
+ * 15s driver-startup timeout — raise it so first runs don't flake.
34
+ */
35
+ export declare function maestroRunEnv(): Record<string, string>;
11
36
  export declare function findMaestroFlows(cwd: string): string[];
12
- export declare function runMaestroTests(cwd: string, logDir: string): Promise<MaestroResult>;
37
+ export declare function runMaestroTests(cwd: string, logDir: string, device?: string): Promise<MaestroResult>;
13
38
  /**
14
39
  * 0.1.51 H9 — Adapt `runMaestroTests` to the `RunnerResult` shape used
15
40
  * by every other verify runner so it can be pushed into the `results`
@@ -21,5 +46,5 @@ export declare function runMaestroTests(cwd: string, logDir: string): Promise<Ma
21
46
  * `passed` flows count toward `passed`, `failed` flows toward `failed`,
22
47
  * and `skipped` is always 0 (Maestro doesn't surface a skipped count).
23
48
  */
24
- export declare function runMaestroFlows(cwd: string, logPath: string): Promise<RunnerResult>;
49
+ export declare function runMaestroFlows(cwd: string, logPath: string, device?: string): Promise<RunnerResult>;
25
50
  //# sourceMappingURL=maestro.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"maestro.d.ts","sourceRoot":"","sources":["../../src/runners/maestro.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAK9C,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;CACjB;AAID,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,OAAO,CAAC,CAG3D;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAQtD;AAuGD,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAmCzF;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,CAAC,CAgCvB"}
1
+ {"version":3,"file":"maestro.d.ts","sourceRoot":"","sources":["../../src/runners/maestro.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAM9C,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;CACjB;AAID,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,OAAO,CAAC,CAG3D;AAID;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAWhD;AAED,iFAAiF;AACjF,wBAAgB,oBAAoB,IAAI,MAAM,CAK7C;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,OAAO,CAAC;IACZ,+CAA+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,gBAAgB,CAAC,CASlE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAEtD;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAQtD;AAuGD,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAqC1G;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,CAAC,CA8BvB"}
@@ -2,11 +2,58 @@ import { runCommand, makeSkippedResult } from "./base.js";
2
2
  import { MAESTRO_TIMEOUT_MS } from "./test_watchdog.js";
3
3
  import { existsSync, mkdirSync, readdirSync } from "fs";
4
4
  import { join } from "path";
5
+ import { platform } from "os";
5
6
  const MAESTRO_FLOW_REL_DIRS = ["tests/maestro", ".maestro", "maestro"];
6
7
  export async function isMaestroInstalled() {
7
8
  const result = await runCommand("maestro", ["--version"], process.cwd());
8
9
  return result.exit_code === 0;
9
10
  }
11
+ // ── P0.4 (0.1.90): Maestro as a DEPENDABLE primary engine ───────────
12
+ /**
13
+ * Per-OS Maestro install directive. The old curl-only line dead-ended
14
+ * Windows hosts (no bash); Windows gets the PowerShell installer.
15
+ */
16
+ export function maestroInstallDirective() {
17
+ if (platform() === "win32") {
18
+ return ("Install Maestro (PowerShell): `iwr -UseBasicParsing https://get.maestro.mobile.dev/install.ps1 | iex` " +
19
+ "(or download from https://maestro.mobile.dev). Maestro is a JVM CLI — it also needs Java 17+ on PATH.");
20
+ }
21
+ return ("Install Maestro: `curl -Ls https://get.maestro.mobile.dev | bash`. " +
22
+ "Maestro is a JVM CLI — it also needs Java 17+ on PATH.");
23
+ }
24
+ /** Per-OS Java install directive (Maestro is a JVM CLI; no JVM = no Maestro). */
25
+ export function javaInstallDirective() {
26
+ const os = platform();
27
+ if (os === "darwin")
28
+ return "Install Java 17+ for Maestro: `brew install --cask temurin` (then re-open the terminal).";
29
+ if (os === "win32")
30
+ return "Install Java 17+ for Maestro: `winget install EclipseAdoptium.Temurin.21.JDK` (then re-open the terminal).";
31
+ return "Install Java 17+ for Maestro: `sudo apt install openjdk-21-jre` (or your distro's OpenJDK 17+ package).";
32
+ }
33
+ /**
34
+ * Preflight the Maestro toolchain BEFORE driving: the `maestro` launcher is a
35
+ * shell script around a JVM CLI, so a missing/broken Java produces a cryptic
36
+ * mid-flow failure unless caught here. `java -version` exits 0 and prints the
37
+ * version to stderr on every JDK.
38
+ */
39
+ export async function maestroPreflight() {
40
+ if (!(await isMaestroInstalled())) {
41
+ return { ok: false, directive: maestroInstallDirective() };
42
+ }
43
+ const java = await runCommand("java", ["-version"], process.cwd(), undefined, undefined, 20_000);
44
+ if (java.exit_code !== 0) {
45
+ return { ok: false, directive: `Maestro is installed but Java is not runnable (\`java -version\` failed). ${javaInstallDirective()}` };
46
+ }
47
+ return { ok: true };
48
+ }
49
+ /**
50
+ * Env for Maestro runs: the FIRST run on an iOS simulator installs +
51
+ * boots the XCTest driver, which routinely exceeds Maestro's default
52
+ * 15s driver-startup timeout — raise it so first runs don't flake.
53
+ */
54
+ export function maestroRunEnv() {
55
+ return { MAESTRO_DRIVER_STARTUP_TIMEOUT: "120000" };
56
+ }
10
57
  export function findMaestroFlows(cwd) {
11
58
  const flows = [];
12
59
  for (const rel of MAESTRO_FLOW_REL_DIRS) {
@@ -104,7 +151,7 @@ function collectScreenshotPaths(root) {
104
151
  walk(root);
105
152
  return paths.sort();
106
153
  }
107
- export async function runMaestroTests(cwd, logDir) {
154
+ export async function runMaestroTests(cwd, logDir, device) {
108
155
  if (!(await isMaestroInstalled())) {
109
156
  return emptyMaestroResult();
110
157
  }
@@ -116,10 +163,12 @@ export async function runMaestroTests(cwd, logDir) {
116
163
  const testOutputDir = join(logDir, "maestro-output");
117
164
  mkdirSync(logDir, { recursive: true });
118
165
  mkdirSync(testOutputDir, { recursive: true });
119
- const args = ["test", "--test-output-dir", testOutputDir, ...flows];
166
+ // `--udid` (root-level flag, alias --device) pins the run when several
167
+ // devices are connected — without it Maestro picks its own.
168
+ const args = [...(device ? ["--udid", device] : []), "test", "--test-output-dir", testOutputDir, ...flows];
120
169
  // Cap Maestro: flows drive a real device/emulator and can stall indefinitely
121
170
  // waiting for one to boot or for an element that never appears.
122
- const result = await runCommand("maestro", args, cwd, logPath, undefined, MAESTRO_TIMEOUT_MS);
171
+ const result = await runCommand("maestro", args, cwd, logPath, maestroRunEnv(), MAESTRO_TIMEOUT_MS);
123
172
  const combined = result.stdout + result.stderr;
124
173
  const parsed = parseMaestroSummary(combined, flows.length, result.exit_code);
125
174
  const screenshotPaths = collectScreenshotPaths(testOutputDir);
@@ -144,17 +193,18 @@ export async function runMaestroTests(cwd, logDir) {
144
193
  * `passed` flows count toward `passed`, `failed` flows toward `failed`,
145
194
  * and `skipped` is always 0 (Maestro doesn't surface a skipped count).
146
195
  */
147
- export async function runMaestroFlows(cwd, logPath) {
196
+ export async function runMaestroFlows(cwd, logPath, device) {
148
197
  const flows = findMaestroFlows(cwd);
149
198
  if (flows.length === 0) {
150
199
  return makeSkippedResult("maestro_flows", "No Maestro flows found (looked under .maestro/, tests/maestro/, maestro/)");
151
200
  }
152
- if (!(await isMaestroInstalled())) {
153
- return makeSkippedResult("maestro_flows", "maestro CLI not found on PATH. Install via `curl -Ls 'https://get.maestro.mobile.dev' | bash` (POSIX/macOS) and retry.");
201
+ const preflight = await maestroPreflight();
202
+ if (!preflight.ok) {
203
+ return makeSkippedResult("maestro_flows", preflight.directive ?? "Maestro toolchain unavailable.");
154
204
  }
155
205
  const start = Date.now();
156
206
  const logDir = join(logPath, "..");
157
- const result = await runMaestroTests(cwd, logDir);
207
+ const result = await runMaestroTests(cwd, logDir, device);
158
208
  const duration_ms = Date.now() - start;
159
209
  return {
160
210
  runner_name: "maestro_flows",