@veolab/discoverylab 1.2.1 → 1.3.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 (160) hide show
  1. package/.mcp.json +2 -2
  2. package/README.md +182 -0
  3. package/dist/{chunk-TAODYZ52.js → chunk-3QRQEDWR.js} +510 -213
  4. package/dist/{chunk-L4SA5F5W.js → chunk-4L76GPRC.js} +1162 -58
  5. package/dist/chunk-6EGBXRDK.js +30 -0
  6. package/dist/{chunk-I6YD3QFM.js → chunk-FIL7IWEL.js} +5 -3
  7. package/dist/{chunk-4KLG6DDE.js → chunk-FNUN7EPB.js} +6 -6
  8. package/dist/chunk-GAKEFJ5T.js +481 -0
  9. package/dist/chunk-LB3RNE3O.js +109 -0
  10. package/dist/chunk-N6JJ2RGV.js +2680 -0
  11. package/dist/{chunk-XUKWS2CE.js → chunk-VRM42PML.js} +3546 -926
  12. package/dist/{chunk-TJ3H23LL.js → chunk-VVIOB362.js} +3 -1
  13. package/dist/{chunk-W3WJGYR6.js → chunk-XFVDP332.js} +8 -2
  14. package/dist/{chunk-QJXXHOV7.js → chunk-XKX6NBHF.js} +5 -1
  15. package/dist/cli.js +405 -11
  16. package/dist/{db-ADBEBNH6.js → db-6WLEVKUV.js} +3 -1
  17. package/dist/esvp-GSISVXLC.js +52 -0
  18. package/dist/esvp-mobile-GC7MAGMI.js +20 -0
  19. package/dist/index.d.ts +123 -1
  20. package/dist/index.html +11689 -8690
  21. package/dist/index.js +67 -11
  22. package/dist/{ocr-UTWC7537.js → ocr-QDYNCSPE.js} +1 -1
  23. package/dist/{playwright-R7Y5HREH.js → playwright-VZ7PXDC5.js} +2 -2
  24. package/dist/runtime/esvp-host-runtime/darwin-arm64/esvp-host-runtime +0 -0
  25. package/dist/runtime/esvp-host-runtime/manifest.json +10 -0
  26. package/dist/{server-3FBHBA7L.js → server-6N3KIEGP.js} +2 -1
  27. package/dist/server-FO3UVUZU.js +22 -0
  28. package/dist/{setup-27CQAX6K.js → setup-2SQC5UHJ.js} +4 -3
  29. package/dist/{tools-L6PKKQPY.js → tools-OCRMOQ4U.js} +63 -8
  30. package/package.json +36 -5
  31. package/dist/chunk-22OCFYHG.js +0 -6283
  32. package/dist/chunk-24VARQVO.js +0 -7818
  33. package/dist/chunk-2OGZX6C4.js +0 -588
  34. package/dist/chunk-2WCNIFRO.js +0 -6191
  35. package/dist/chunk-43U6UYV7.js +0 -590
  36. package/dist/chunk-4H2E3K2G.js +0 -7638
  37. package/dist/chunk-4MS6YW2B.js +0 -6490
  38. package/dist/chunk-4NNTRJOI.js +0 -7791
  39. package/dist/chunk-5F76VWME.js +0 -6397
  40. package/dist/chunk-5NEFN42O.js +0 -7791
  41. package/dist/chunk-63MEQ6UH.js +0 -7673
  42. package/dist/chunk-6H3NXFX3.js +0 -6861
  43. package/dist/chunk-7IDQLLBW.js +0 -311
  44. package/dist/chunk-7NP64TGJ.js +0 -6822
  45. package/dist/chunk-AATLY4KT.js +0 -6505
  46. package/dist/chunk-C7QUR7XX.js +0 -6397
  47. package/dist/chunk-CGKCE6MC.js +0 -6279
  48. package/dist/chunk-D25V6IWE.js +0 -6487
  49. package/dist/chunk-EQOZSXAT.js +0 -6822
  50. package/dist/chunk-FPHD7HSQ.js +0 -6812
  51. package/dist/chunk-GGJJUCFK.js +0 -7160
  52. package/dist/chunk-GLHOY3NN.js +0 -7805
  53. package/dist/chunk-GML5MKQA.js +0 -6398
  54. package/dist/chunk-GOL6FUJL.js +0 -6045
  55. package/dist/chunk-GSWHWEYC.js +0 -1346
  56. package/dist/chunk-HDKEQOF5.js +0 -7788
  57. package/dist/chunk-HZGSWVVS.js +0 -7111
  58. package/dist/chunk-IGZ5TICZ.js +0 -334
  59. package/dist/chunk-IRKQG33A.js +0 -7054
  60. package/dist/chunk-JFTBF4JR.js +0 -6040
  61. package/dist/chunk-JVLVBPUJ.js +0 -6180
  62. package/dist/chunk-JY3KC67R.js +0 -6504
  63. package/dist/chunk-KUFBCBNJ.js +0 -6815
  64. package/dist/chunk-KV7KDJ43.js +0 -7639
  65. package/dist/chunk-L5IJZV5F.js +0 -6822
  66. package/dist/chunk-MFFPQLU4.js +0 -7102
  67. package/dist/chunk-MJS2YKNR.js +0 -6397
  68. package/dist/chunk-MN6LCZHZ.js +0 -1320
  69. package/dist/chunk-NBAUZ7X2.js +0 -1336
  70. package/dist/chunk-NDBW6ELQ.js +0 -7638
  71. package/dist/chunk-O2HBSDI2.js +0 -6175
  72. package/dist/chunk-OFFIUYMG.js +0 -6341
  73. package/dist/chunk-OVCQGF2J.js +0 -1321
  74. package/dist/chunk-P4S7ZY6G.js +0 -7638
  75. package/dist/chunk-PBHUHSC3.js +0 -6002
  76. package/dist/chunk-PC4LR4ZI.js +0 -6359
  77. package/dist/chunk-PMTGGZ7R.js +0 -6397
  78. package/dist/chunk-PTXSB3UV.js +0 -497
  79. package/dist/chunk-PYUCY3U6.js +0 -1340
  80. package/dist/chunk-RDZDSOAL.js +0 -7750
  81. package/dist/chunk-RLW2OI2L.js +0 -6383
  82. package/dist/chunk-RUGHHO4K.js +0 -6395
  83. package/dist/chunk-SIOQVM2E.js +0 -6819
  84. package/dist/chunk-SR67SRIT.js +0 -1336
  85. package/dist/chunk-SSRXIO2V.js +0 -6822
  86. package/dist/chunk-SWSEKFON.js +0 -6487
  87. package/dist/chunk-TBG76CYG.js +0 -6395
  88. package/dist/chunk-V3CBINLD.js +0 -6812
  89. package/dist/chunk-VPYSLEGM.js +0 -6710
  90. package/dist/chunk-VY3BLXBW.js +0 -329
  91. package/dist/chunk-WTFOGVJQ.js +0 -6365
  92. package/dist/chunk-X64SFUT5.js +0 -6099
  93. package/dist/chunk-XIBF5LBD.js +0 -6395
  94. package/dist/chunk-Y5VDMSYC.js +0 -6701
  95. package/dist/chunk-YUBL36H4.js +0 -6605
  96. package/dist/chunk-YWVXFVSW.js +0 -6456
  97. package/dist/chunk-ZXZACOLD.js +0 -6822
  98. package/dist/db-IWIL65EX.js +0 -33
  99. package/dist/gridCompositor-ENKLFPWR.js +0 -409
  100. package/dist/playwright-A3OGSDRG.js +0 -38
  101. package/dist/playwright-ATDC4NYW.js +0 -38
  102. package/dist/playwright-E6EUFIJG.js +0 -38
  103. package/dist/server-2DXLKLFM.js +0 -13
  104. package/dist/server-2ICEWJVK.js +0 -13
  105. package/dist/server-2MQV3FNY.js +0 -13
  106. package/dist/server-2NGD7GE3.js +0 -13
  107. package/dist/server-2VKO76UK.js +0 -14
  108. package/dist/server-3BK2VFU7.js +0 -13
  109. package/dist/server-4LDOB3NX.js +0 -13
  110. package/dist/server-4YI44KDR.js +0 -13
  111. package/dist/server-64XMXA5P.js +0 -13
  112. package/dist/server-6IPHVUYT.js +0 -14
  113. package/dist/server-73ORHMJN.js +0 -13
  114. package/dist/server-73P7M3QB.js +0 -14
  115. package/dist/server-BPVRW5LJ.js +0 -14
  116. package/dist/server-BW4RKZIX.js +0 -13
  117. package/dist/server-CFS5SM5K.js +0 -13
  118. package/dist/server-DX7VYHHM.js +0 -13
  119. package/dist/server-F3YPX6ET.js +0 -13
  120. package/dist/server-FUXTR33I.js +0 -13
  121. package/dist/server-G2SY3DOS.js +0 -13
  122. package/dist/server-G32U7VOQ.js +0 -13
  123. package/dist/server-IOOZK4NP.js +0 -14
  124. package/dist/server-J52LMTBT.js +0 -13
  125. package/dist/server-JG7UKFGK.js +0 -14
  126. package/dist/server-JSCHEBOD.js +0 -13
  127. package/dist/server-K6KC4ZOM.js +0 -13
  128. package/dist/server-KJVRGWFE.js +0 -13
  129. package/dist/server-LCPB2L4U.js +0 -13
  130. package/dist/server-M7LDYKAJ.js +0 -13
  131. package/dist/server-MKVK6ZQQ.js +0 -13
  132. package/dist/server-MU52LCXT.js +0 -13
  133. package/dist/server-NM5CKDUU.js +0 -13
  134. package/dist/server-NPZN3FWO.js +0 -14
  135. package/dist/server-O5FIAHSY.js +0 -14
  136. package/dist/server-OESJUEYC.js +0 -13
  137. package/dist/server-ONSKQO4W.js +0 -13
  138. package/dist/server-P27BZXBL.js +0 -14
  139. package/dist/server-Q4FBWQUA.js +0 -13
  140. package/dist/server-RNQ7VUAL.js +0 -13
  141. package/dist/server-S6B5WUBT.js +0 -14
  142. package/dist/server-SRYNSGSP.js +0 -14
  143. package/dist/server-SUN3W2YK.js +0 -13
  144. package/dist/server-UA62LHZB.js +0 -13
  145. package/dist/server-UJB44EW5.js +0 -13
  146. package/dist/server-X3TLP6DX.js +0 -14
  147. package/dist/server-YT2UGEZK.js +0 -13
  148. package/dist/server-ZBPQ33V6.js +0 -14
  149. package/dist/setup-AQX4JQVR.js +0 -17
  150. package/dist/setup-EQTU7FI6.js +0 -17
  151. package/dist/tools-2KPB37GK.js +0 -178
  152. package/dist/tools-3H6IOWXV.js +0 -178
  153. package/dist/tools-3KYHPDCJ.js +0 -178
  154. package/dist/tools-75BAPCUM.js +0 -177
  155. package/dist/tools-BUVCUCRL.js +0 -178
  156. package/dist/tools-HDNODRS6.js +0 -178
  157. package/dist/tools-HP5MNY3D.js +0 -177
  158. package/dist/tools-N5N2IO7V.js +0 -178
  159. package/dist/tools-NFJEZ2FF.js +0 -177
  160. package/dist/tools-TLCKABUW.js +0 -178
@@ -1,9 +1,231 @@
1
1
  import {
2
- PROJECTS_DIR
3
- } from "./chunk-TJ3H23LL.js";
2
+ PROJECTS_DIR,
3
+ TEMPLATES_DIR
4
+ } from "./chunk-VVIOB362.js";
5
+
6
+ // src/core/templates/loader.ts
7
+ import { existsSync, readFileSync } from "fs";
8
+ import { join, dirname } from "path";
9
+ import { fileURLToPath } from "url";
10
+ var MANIFEST_FILE = "manifest.json";
11
+ var BUNDLE_DIR = "bundle";
12
+ var __dirname = dirname(fileURLToPath(import.meta.url));
13
+ var BUNDLED_TEMPLATES_DIR = join(__dirname, "..", "..", "templates");
14
+ var cachedManifest = null;
15
+ var cachedTemplatesDir = null;
16
+ var cachedAt = 0;
17
+ var CACHE_TTL = 3e4;
18
+ function resolveTemplatesDir() {
19
+ const localManifest = join(TEMPLATES_DIR, MANIFEST_FILE);
20
+ const localBundle = join(TEMPLATES_DIR, BUNDLE_DIR);
21
+ if (existsSync(localManifest) && existsSync(localBundle)) {
22
+ return TEMPLATES_DIR;
23
+ }
24
+ const bundledManifest = join(BUNDLED_TEMPLATES_DIR, MANIFEST_FILE);
25
+ const bundledBundle = join(BUNDLED_TEMPLATES_DIR, BUNDLE_DIR);
26
+ if (existsSync(bundledManifest) && existsSync(bundledBundle)) {
27
+ return BUNDLED_TEMPLATES_DIR;
28
+ }
29
+ return null;
30
+ }
31
+ function loadManifest() {
32
+ const now = Date.now();
33
+ if (cachedManifest && now - cachedAt < CACHE_TTL) {
34
+ return cachedManifest;
35
+ }
36
+ const dir = resolveTemplatesDir();
37
+ if (!dir) {
38
+ cachedManifest = null;
39
+ cachedTemplatesDir = null;
40
+ cachedAt = now;
41
+ return null;
42
+ }
43
+ try {
44
+ const raw = readFileSync(join(dir, MANIFEST_FILE), "utf-8");
45
+ const manifest = JSON.parse(raw);
46
+ cachedManifest = manifest;
47
+ cachedTemplatesDir = dir;
48
+ cachedAt = now;
49
+ return manifest;
50
+ } catch {
51
+ cachedManifest = null;
52
+ cachedTemplatesDir = null;
53
+ cachedAt = now;
54
+ return null;
55
+ }
56
+ }
57
+ function isTemplatesInstalled() {
58
+ return loadManifest() !== null;
59
+ }
60
+ function getAvailableTemplates() {
61
+ const manifest = loadManifest();
62
+ return manifest?.templates ?? [];
63
+ }
64
+ function getTemplate(id) {
65
+ const templates = getAvailableTemplates();
66
+ return templates.find((t) => t.id === id) ?? null;
67
+ }
68
+ function getBundlePath() {
69
+ loadManifest();
70
+ if (!cachedTemplatesDir) return null;
71
+ const bundlePath = join(cachedTemplatesDir, BUNDLE_DIR);
72
+ if (!existsSync(bundlePath)) return null;
73
+ return bundlePath;
74
+ }
75
+
76
+ // src/core/android/adb.ts
77
+ import { execSync } from "child_process";
78
+ import { existsSync as existsSync2 } from "fs";
79
+ import { homedir } from "os";
80
+ import { join as join2 } from "path";
81
+ var ADB_COMMAND_CACHE_SUCCESS_TTL_MS = 5 * 60 * 1e3;
82
+ var ADB_COMMAND_CACHE_FAILURE_TTL_MS = 5 * 1e3;
83
+ var adbCommandCache = null;
84
+ function quoteCommand(cmd) {
85
+ return cmd.includes(" ") ? `"${cmd}"` : cmd;
86
+ }
87
+ function shellQuoteArg(value) {
88
+ const str = String(value ?? "");
89
+ if (!str) return "''";
90
+ return `'${str.replace(/'/g, `'"'"'`)}'`;
91
+ }
92
+ function findAndroidSdkPath() {
93
+ const envPaths = [
94
+ process.env.ANDROID_HOME,
95
+ process.env.ANDROID_SDK_ROOT,
96
+ process.env.ANDROID_SDK
97
+ ].filter((value) => typeof value === "string" && value.trim().length > 0);
98
+ for (const envPath of envPaths) {
99
+ if (existsSync2(join2(envPath, "platform-tools", "adb"))) {
100
+ return envPath;
101
+ }
102
+ }
103
+ const home = homedir();
104
+ const commonPaths = [
105
+ join2(home, "Library", "Android", "sdk"),
106
+ join2(home, "Android", "Sdk"),
107
+ "/opt/android-sdk",
108
+ "/usr/local/android-sdk"
109
+ ];
110
+ for (const sdkPath of commonPaths) {
111
+ if (existsSync2(join2(sdkPath, "platform-tools", "adb"))) {
112
+ return sdkPath;
113
+ }
114
+ }
115
+ return null;
116
+ }
117
+ function getAdbPath() {
118
+ const sdkPath = findAndroidSdkPath();
119
+ if (!sdkPath) return null;
120
+ const adbPath = join2(sdkPath, "platform-tools", "adb");
121
+ return existsSync2(adbPath) ? adbPath : null;
122
+ }
123
+ function getEmulatorPath() {
124
+ const sdkPath = findAndroidSdkPath();
125
+ if (!sdkPath) return null;
126
+ const emulatorPath = join2(sdkPath, "emulator", "emulator");
127
+ return existsSync2(emulatorPath) ? emulatorPath : null;
128
+ }
129
+ function getAdbCommand(options) {
130
+ const forceRefresh = options?.forceRefresh === true;
131
+ const now = Date.now();
132
+ if (!forceRefresh && adbCommandCache) {
133
+ const ttlMs = adbCommandCache.value ? ADB_COMMAND_CACHE_SUCCESS_TTL_MS : ADB_COMMAND_CACHE_FAILURE_TTL_MS;
134
+ if (now - adbCommandCache.checkedAt < ttlMs) {
135
+ return adbCommandCache.value;
136
+ }
137
+ }
138
+ const candidates = [
139
+ getAdbPath(),
140
+ "/opt/homebrew/bin/adb",
141
+ "/usr/local/bin/adb",
142
+ "adb"
143
+ ].filter((value, index, arr) => Boolean(value) && arr.indexOf(value) === index);
144
+ for (const candidate of candidates) {
145
+ try {
146
+ if (candidate === "adb") {
147
+ execSync("which adb", { stdio: "pipe", timeout: 2e3 });
148
+ } else if (!existsSync2(candidate)) {
149
+ continue;
150
+ }
151
+ adbCommandCache = { value: candidate, checkedAt: Date.now() };
152
+ return candidate;
153
+ } catch {
154
+ }
155
+ }
156
+ adbCommandCache = { value: null, checkedAt: Date.now() };
157
+ return null;
158
+ }
159
+ function normalizeAndroidDeviceToken(value) {
160
+ return value.trim().replace(/^android:/i, "").replace(/\s+/g, "_").toLowerCase();
161
+ }
162
+ function listConnectedAndroidDevices(adbCommand = getAdbCommand()) {
163
+ if (!adbCommand) return [];
164
+ try {
165
+ const adbOutput = execSync(`${quoteCommand(adbCommand)} devices -l`, {
166
+ encoding: "utf8",
167
+ timeout: 4e3
168
+ });
169
+ const lines = adbOutput.split("\n").slice(1);
170
+ const devices = [];
171
+ for (const rawLine of lines) {
172
+ const line = rawLine.trim();
173
+ if (!line) continue;
174
+ const parts = line.split(/\s+/);
175
+ const serial = parts[0];
176
+ const state = parts[1];
177
+ if (!serial || !state) continue;
178
+ const modelMatch = line.match(/model:(\S+)/);
179
+ const deviceMatch = line.match(/device:(\S+)/);
180
+ const deviceInfo = {
181
+ serial,
182
+ state,
183
+ model: modelMatch?.[1],
184
+ device: deviceMatch?.[1]
185
+ };
186
+ if (serial.startsWith("emulator-") && state === "device") {
187
+ try {
188
+ const avdNameOutput = execSync(
189
+ `${quoteCommand(adbCommand)} -s ${shellQuoteArg(serial)} emu avd name`,
190
+ {
191
+ encoding: "utf8",
192
+ timeout: 1500
193
+ }
194
+ );
195
+ const avdName = avdNameOutput.split("\n").map((value) => value.trim()).find((value) => value && value !== "OK");
196
+ if (avdName) {
197
+ deviceInfo.avdName = avdName;
198
+ }
199
+ } catch {
200
+ }
201
+ }
202
+ devices.push(deviceInfo);
203
+ }
204
+ return devices;
205
+ } catch {
206
+ return [];
207
+ }
208
+ }
209
+ function resolveAndroidDeviceSerial(deviceId, adbCommand = getAdbCommand()) {
210
+ const connectedDevices = listConnectedAndroidDevices(adbCommand);
211
+ const onlineDevices = connectedDevices.filter((device) => device.state === "device");
212
+ if (onlineDevices.length === 0) return null;
213
+ const requested = typeof deviceId === "string" ? deviceId.trim() : "";
214
+ if (!requested) {
215
+ return onlineDevices[0]?.serial || null;
216
+ }
217
+ const requestedToken = normalizeAndroidDeviceToken(requested);
218
+ for (const device of onlineDevices) {
219
+ const candidates = [device.serial, device.avdName, device.model, device.device].filter((value) => Boolean(value)).map((value) => normalizeAndroidDeviceToken(value));
220
+ if (candidates.includes(requestedToken)) {
221
+ return device.serial;
222
+ }
223
+ }
224
+ return null;
225
+ }
4
226
 
5
227
  // src/core/testing/maestro.ts
6
- import { exec, execSync, spawn } from "child_process";
228
+ import { exec, execSync as execSync2, spawn } from "child_process";
7
229
  import { promisify } from "util";
8
230
  import * as fs from "fs";
9
231
  import * as path from "path";
@@ -18,14 +240,21 @@ var maestroCommandCache = null;
18
240
  var maestroCommandResolveInFlight = null;
19
241
  var maestroDeviceListCache = null;
20
242
  var maestroDeviceListInFlight = null;
21
- function quoteCommand(cmd) {
243
+ function quoteCommand2(cmd) {
22
244
  return cmd.includes(" ") ? `"${cmd}"` : cmd;
23
245
  }
24
- function shellQuoteArg(value) {
246
+ function shellQuoteArg2(value) {
25
247
  const str = String(value ?? "");
26
248
  if (!str) return "''";
27
249
  return `'${str.replace(/'/g, `'"'"'`)}'`;
28
250
  }
251
+ function getAdbCommandOrThrow() {
252
+ const adbCommand = getAdbCommand();
253
+ if (!adbCommand) {
254
+ throw new Error("ADB not found. Set ANDROID_HOME/ANDROID_SDK_ROOT or add adb to PATH.");
255
+ }
256
+ return adbCommand;
257
+ }
29
258
  function getMaestroCommandCandidates() {
30
259
  const candidates = [];
31
260
  const maestroHomePath = path.join(os.homedir(), ".maestro", "bin", "maestro");
@@ -59,7 +288,7 @@ async function resolveMaestroCommand(options) {
59
288
  maestroCommandResolveInFlight = (async () => {
60
289
  for (const candidate of getMaestroCommandCandidates()) {
61
290
  try {
62
- const cmd = quoteCommand(candidate);
291
+ const cmd = quoteCommand2(candidate);
63
292
  await execAsync(`${cmd} --version`);
64
293
  maestroCommandCache = { value: candidate, checkedAt: Date.now() };
65
294
  return candidate;
@@ -114,7 +343,7 @@ async function getMaestroVersion() {
114
343
  try {
115
344
  const command = await resolveMaestroCommand();
116
345
  if (!command) return null;
117
- const cmd = quoteCommand(command);
346
+ const cmd = quoteCommand2(command);
118
347
  const { stdout } = await execAsync(`${cmd} --version`);
119
348
  return stdout.trim();
120
349
  } catch {
@@ -152,19 +381,22 @@ async function listMaestroDevices(options) {
152
381
  } catch {
153
382
  }
154
383
  try {
155
- const { stdout: androidOutput } = await execAsync("adb devices -l");
156
- const lines = androidOutput.split("\n").slice(1);
157
- for (const line of lines) {
158
- const match = line.match(/^(\S+)\s+device\s+(.*)$/);
159
- if (match) {
160
- const [, id, info] = match;
161
- const modelMatch = info.match(/model:(\S+)/);
162
- devices.push({
163
- id,
164
- name: modelMatch ? modelMatch[1] : id,
165
- platform: "android",
166
- status: "connected"
167
- });
384
+ const adbCommand = getAdbCommand();
385
+ if (adbCommand) {
386
+ const { stdout: androidOutput } = await execAsync(`${quoteCommand2(adbCommand)} devices -l`);
387
+ const lines = androidOutput.split("\n").slice(1);
388
+ for (const line of lines) {
389
+ const match = line.match(/^(\S+)\s+device\s+(.*)$/);
390
+ if (match) {
391
+ const [, id, info] = match;
392
+ const modelMatch = info.match(/model:(\S+)/);
393
+ devices.push({
394
+ id,
395
+ name: modelMatch ? modelMatch[1] : id,
396
+ platform: "android",
397
+ status: "connected"
398
+ });
399
+ }
168
400
  }
169
401
  }
170
402
  } catch {
@@ -320,7 +552,7 @@ async function runMaestroTest(options) {
320
552
  await fs.promises.mkdir(outputDir, { recursive: true });
321
553
  const startTime = Date.now();
322
554
  const args = ["test", flowPath];
323
- const maestroCmd = quoteCommand(command);
555
+ const maestroCmd = quoteCommand2(command);
324
556
  if (device) {
325
557
  args.push("--device", device);
326
558
  }
@@ -330,7 +562,7 @@ async function runMaestroTest(options) {
330
562
  args.push("--format", "junit");
331
563
  args.push("--output", path.join(outputDir, "report.xml"));
332
564
  try {
333
- const shellArgs = args.map(shellQuoteArg).join(" ");
565
+ const shellArgs = args.map(shellQuoteArg2).join(" ");
334
566
  const { stdout, stderr } = await execAsync(`${maestroCmd} ${shellArgs}`, {
335
567
  timeout,
336
568
  env: { ...process.env, ...env }
@@ -387,7 +619,8 @@ async function runMaestroWithCapture(options, onProgress) {
387
619
  if (targetDevice?.platform === "ios") {
388
620
  recordingProcess = spawn("xcrun", ["simctl", "io", device, "recordVideo", videoPath]);
389
621
  } else if (targetDevice?.platform === "android") {
390
- recordingProcess = spawn("adb", ["-s", device, "shell", "screenrecord", "/sdcard/recording.mp4"]);
622
+ const adbCommand = getAdbCommandOrThrow();
623
+ recordingProcess = spawn(adbCommand, ["-s", device, "shell", "screenrecord", "/sdcard/recording.mp4"]);
391
624
  }
392
625
  }
393
626
  onProgress?.("Running Maestro test...");
@@ -399,8 +632,9 @@ async function runMaestroWithCapture(options, onProgress) {
399
632
  const devices = await listMaestroDevices();
400
633
  const targetDevice = devices.find((d) => d.id === device || d.name === device);
401
634
  if (targetDevice?.platform === "android") {
402
- await execAsync(`adb -s ${device} pull /sdcard/recording.mp4 "${videoPath}"`);
403
- await execAsync(`adb -s ${device} shell rm /sdcard/recording.mp4`);
635
+ const adbCommand = getAdbCommandOrThrow();
636
+ await execAsync(`${quoteCommand2(adbCommand)} -s ${shellQuoteArg2(device)} pull /sdcard/recording.mp4 "${videoPath}"`);
637
+ await execAsync(`${quoteCommand2(adbCommand)} -s ${shellQuoteArg2(device)} shell rm /sdcard/recording.mp4`);
404
638
  }
405
639
  }
406
640
  return {
@@ -499,6 +733,212 @@ function createNavigationTestFlow(appId, tabs) {
499
733
  steps
500
734
  };
501
735
  }
736
+ function parseMaestroActionsFromYaml(yamlContent) {
737
+ const actions = [];
738
+ const lines = yamlContent.split("\n");
739
+ let currentAction = null;
740
+ let actionIndex = 0;
741
+ const nextId = () => `action_${String(++actionIndex).padStart(3, "0")}`;
742
+ const stripInlineComment = (value) => {
743
+ let inSingle = false;
744
+ let inDouble = false;
745
+ let escaped = false;
746
+ for (let index = 0; index < value.length; index += 1) {
747
+ const char = value[index];
748
+ if (escaped) {
749
+ escaped = false;
750
+ continue;
751
+ }
752
+ if (char === "\\") {
753
+ escaped = true;
754
+ continue;
755
+ }
756
+ if (char === '"' && !inSingle) {
757
+ inDouble = !inDouble;
758
+ continue;
759
+ }
760
+ if (char === "'" && !inDouble) {
761
+ inSingle = !inSingle;
762
+ continue;
763
+ }
764
+ if (char === "#" && !inSingle && !inDouble) {
765
+ return value.slice(0, index).trimEnd();
766
+ }
767
+ }
768
+ return value;
769
+ };
770
+ const cleanValue = (value) => stripInlineComment(value).trim().replace(/^["']|["']$/g, "");
771
+ const pushAction = (action) => {
772
+ actions.push({
773
+ id: action.id || nextId(),
774
+ type: action.type || "tap",
775
+ timestamp: action.timestamp || Date.now(),
776
+ description: action.description || action.type || "Action",
777
+ x: action.x,
778
+ y: action.y,
779
+ endX: action.endX,
780
+ endY: action.endY,
781
+ text: action.text,
782
+ direction: action.direction,
783
+ seconds: action.seconds,
784
+ appId: action.appId,
785
+ duration: action.duration,
786
+ screenshotPath: action.screenshotPath
787
+ });
788
+ };
789
+ for (const line of lines) {
790
+ const trimmed = line.trim();
791
+ if (trimmed.startsWith("#") || trimmed === "" || trimmed === "---") {
792
+ continue;
793
+ }
794
+ if (trimmed.startsWith("appId:")) {
795
+ continue;
796
+ }
797
+ if (trimmed.startsWith("- tapOn:")) {
798
+ const inlineValue = cleanValue(trimmed.replace("- tapOn:", ""));
799
+ if (inlineValue) {
800
+ pushAction({
801
+ type: "tap",
802
+ description: `Tap on "${inlineValue}"`,
803
+ text: inlineValue
804
+ });
805
+ continue;
806
+ }
807
+ currentAction = { type: "tap", description: "Tap" };
808
+ } else if (trimmed.startsWith("- tap:")) {
809
+ currentAction = { type: "tap", description: "Tap" };
810
+ } else if (trimmed.startsWith("- swipe:")) {
811
+ currentAction = { type: "swipe", description: "Swipe" };
812
+ } else if (trimmed.startsWith("- scroll:")) {
813
+ currentAction = { type: "scroll", description: "Scroll" };
814
+ } else if (trimmed.startsWith("- scrollUntilVisible:")) {
815
+ currentAction = { type: "scroll", description: "Scroll" };
816
+ } else if (trimmed.startsWith("- inputText:")) {
817
+ const text = cleanValue(trimmed.replace("- inputText:", ""));
818
+ pushAction({
819
+ type: "input",
820
+ description: `Input: ${text.slice(0, 20)}${text.length > 20 ? "..." : ""}`,
821
+ text
822
+ });
823
+ continue;
824
+ } else if (trimmed.startsWith("- launchApp:")) {
825
+ const appId = cleanValue(trimmed.replace("- launchApp:", ""));
826
+ pushAction({
827
+ type: "launch",
828
+ description: `Launch: ${appId}`,
829
+ text: appId,
830
+ appId
831
+ });
832
+ continue;
833
+ } else if (trimmed === "- launchApp") {
834
+ pushAction({
835
+ type: "launch",
836
+ description: "Launch app"
837
+ });
838
+ continue;
839
+ } else if (trimmed.startsWith("- assertVisible:")) {
840
+ const inlineValue = cleanValue(trimmed.replace("- assertVisible:", ""));
841
+ if (inlineValue) {
842
+ pushAction({
843
+ type: "assert",
844
+ description: `Assert visible "${inlineValue}"`,
845
+ text: inlineValue
846
+ });
847
+ continue;
848
+ }
849
+ currentAction = { type: "assert", description: "Assert visible" };
850
+ } else if (trimmed.startsWith("- extendedWaitUntil:")) {
851
+ currentAction = { type: "wait", description: "Wait" };
852
+ } else if (trimmed.startsWith("- pressKey:")) {
853
+ const key = cleanValue(trimmed.replace("- pressKey:", "")).toLowerCase();
854
+ if (key === "back") {
855
+ pushAction({ type: "back", description: "Back button" });
856
+ } else if (key === "home") {
857
+ pushAction({ type: "home", description: "Home button" });
858
+ } else {
859
+ pushAction({
860
+ type: "pressKey",
861
+ description: `Press: ${key}`,
862
+ text: key
863
+ });
864
+ }
865
+ continue;
866
+ } else if (trimmed.startsWith("- back")) {
867
+ pushAction({ type: "back", description: "Back button" });
868
+ continue;
869
+ }
870
+ if (currentAction && trimmed.startsWith("point:")) {
871
+ const point = cleanValue(trimmed.replace("point:", ""));
872
+ const [x, y] = point.split(",").map((n) => parseInt(n.trim()));
873
+ if (!isNaN(x) && !isNaN(y)) {
874
+ currentAction.x = x;
875
+ currentAction.y = y;
876
+ currentAction.description = `Tap at (${x}, ${y})`;
877
+ pushAction(currentAction);
878
+ currentAction = null;
879
+ continue;
880
+ }
881
+ }
882
+ if (currentAction && trimmed.startsWith("text:")) {
883
+ const text = cleanValue(trimmed.replace("text:", ""));
884
+ currentAction.text = text;
885
+ if (currentAction.type === "assert") {
886
+ currentAction.description = `Assert visible "${text}"`;
887
+ } else {
888
+ currentAction.description = `Tap on "${text}"`;
889
+ }
890
+ pushAction(currentAction);
891
+ currentAction = null;
892
+ continue;
893
+ }
894
+ if (currentAction && trimmed.startsWith("id:")) {
895
+ const id = cleanValue(trimmed.replace("id:", ""));
896
+ currentAction.text = id;
897
+ currentAction.description = `Tap on id: ${id}`;
898
+ pushAction(currentAction);
899
+ currentAction = null;
900
+ continue;
901
+ }
902
+ if (currentAction && (currentAction.type === "swipe" || currentAction.type === "scroll") && trimmed.startsWith("direction:")) {
903
+ const direction = cleanValue(trimmed.replace("direction:", "")).toLowerCase();
904
+ currentAction.direction = direction;
905
+ currentAction.description = currentAction.type === "swipe" ? `Swipe ${direction}` : `Scroll ${direction}`;
906
+ pushAction(currentAction);
907
+ currentAction = null;
908
+ continue;
909
+ }
910
+ if (currentAction && currentAction.type === "wait" && trimmed.startsWith("timeout:")) {
911
+ const timeoutMs = parseInt(cleanValue(trimmed.replace("timeout:", "")), 10);
912
+ const seconds = Number.isFinite(timeoutMs) && timeoutMs > 0 ? Math.max(1, Math.round(timeoutMs / 1e3)) : 3;
913
+ currentAction.seconds = seconds;
914
+ currentAction.description = `Wait ${seconds}s`;
915
+ pushAction(currentAction);
916
+ currentAction = null;
917
+ continue;
918
+ }
919
+ if (currentAction && currentAction.type === "swipe") {
920
+ if (trimmed.startsWith("start:")) {
921
+ const point = cleanValue(trimmed.replace("start:", ""));
922
+ const [x, y] = point.split(",").map((n) => parseInt(n.trim()));
923
+ if (!isNaN(x) && !isNaN(y)) {
924
+ currentAction.x = x;
925
+ currentAction.y = y;
926
+ }
927
+ } else if (trimmed.startsWith("end:")) {
928
+ const point = cleanValue(trimmed.replace("end:", ""));
929
+ const [x, y] = point.split(",").map((n) => parseInt(n.trim()));
930
+ if (!isNaN(x) && !isNaN(y)) {
931
+ currentAction.endX = x;
932
+ currentAction.endY = y;
933
+ currentAction.description = `Swipe from (${currentAction.x}, ${currentAction.y}) to (${x}, ${y})`;
934
+ pushAction(currentAction);
935
+ currentAction = null;
936
+ }
937
+ }
938
+ }
939
+ }
940
+ return actions;
941
+ }
502
942
  var MaestroRecorder = class extends EventEmitter {
503
943
  session = null;
504
944
  adbProcess = null;
@@ -527,7 +967,7 @@ var MaestroRecorder = class extends EventEmitter {
527
967
  */
528
968
  isMaestroInPath() {
529
969
  try {
530
- execSync("which maestro", { encoding: "utf-8", timeout: 2e3 });
970
+ execSync2("which maestro", { encoding: "utf-8", timeout: 2e3 });
531
971
  return true;
532
972
  } catch {
533
973
  return false;
@@ -535,7 +975,7 @@ var MaestroRecorder = class extends EventEmitter {
535
975
  }
536
976
  canRunMaestro(cmd) {
537
977
  try {
538
- execSync(`"${cmd}" --version`, { encoding: "utf-8", timeout: 3e3 });
978
+ execSync2(`"${cmd}" --version`, { encoding: "utf-8", timeout: 3e3 });
539
979
  return true;
540
980
  } catch {
541
981
  return false;
@@ -652,6 +1092,7 @@ var MaestroRecorder = class extends EventEmitter {
652
1092
  };
653
1093
  this.actionCounter = 0;
654
1094
  try {
1095
+ const adbCommand = platform === "android" ? getAdbCommandOrThrow() : null;
655
1096
  const flowPath = path.join(baseDir, "test.yaml");
656
1097
  this.session.flowPath = flowPath;
657
1098
  const videoPath = path.join(baseDir, "recording.mp4");
@@ -729,7 +1170,7 @@ var MaestroRecorder = class extends EventEmitter {
729
1170
  this.videoProcess = spawn("xcrun", ["simctl", "io", deviceId, "recordVideo", videoPath]);
730
1171
  } else {
731
1172
  const tempVideoPath = "/sdcard/maestro-recording.mp4";
732
- this.videoProcess = spawn("adb", ["-s", deviceId, "shell", "screenrecord", "--time-limit", "180", tempVideoPath]);
1173
+ this.videoProcess = spawn(adbCommand, ["-s", deviceId, "shell", "screenrecord", "--time-limit", "180", tempVideoPath]);
733
1174
  }
734
1175
  } else {
735
1176
  if (useNativeRecord) {
@@ -737,7 +1178,7 @@ var MaestroRecorder = class extends EventEmitter {
737
1178
  }
738
1179
  if (platform === "android") {
739
1180
  const tempVideoPath = "/sdcard/maestro-recording.mp4";
740
- this.videoProcess = spawn("adb", ["-s", deviceId, "shell", "screenrecord", "--time-limit", "180", tempVideoPath]);
1181
+ this.videoProcess = spawn(adbCommand, ["-s", deviceId, "shell", "screenrecord", "--time-limit", "180", tempVideoPath]);
741
1182
  this.startLegacyCapture(deviceId, platform);
742
1183
  } else {
743
1184
  this.videoProcess = spawn("xcrun", ["simctl", "io", deviceId, "recordVideo", videoPath]);
@@ -765,12 +1206,17 @@ var MaestroRecorder = class extends EventEmitter {
765
1206
  * Capture Android touch events via ADB
766
1207
  */
767
1208
  startAndroidEventCapture(deviceId) {
768
- exec(`adb -s ${deviceId} shell wm size`, (err, stdout) => {
1209
+ const adbCommand = getAdbCommand();
1210
+ if (!adbCommand) {
1211
+ console.error("[MaestroRecorder] ADB not found. Android event capture is unavailable.");
1212
+ return;
1213
+ }
1214
+ exec(`${quoteCommand2(adbCommand)} -s ${shellQuoteArg2(deviceId)} shell wm size`, (err, stdout) => {
769
1215
  if (err) return;
770
1216
  const match = stdout.match(/(\d+)x(\d+)/);
771
1217
  const screenWidth = match ? parseInt(match[1]) : 1080;
772
1218
  const screenHeight = match ? parseInt(match[2]) : 1920;
773
- this.adbProcess = spawn("adb", ["-s", deviceId, "shell", "getevent", "-lt"]);
1219
+ this.adbProcess = spawn(adbCommand, ["-s", deviceId, "shell", "getevent", "-lt"]);
774
1220
  let touchStartTime = 0;
775
1221
  let touchStartX = 0;
776
1222
  let touchStartY = 0;
@@ -910,7 +1356,8 @@ var MaestroRecorder = class extends EventEmitter {
910
1356
  const screenshotPath = path.join(this.session.screenshotsDir, screenshotName);
911
1357
  try {
912
1358
  if (this.session.platform === "android") {
913
- await execAsync(`adb -s ${this.session.deviceId} exec-out screencap -p > "${screenshotPath}"`);
1359
+ const adbCommand = getAdbCommandOrThrow();
1360
+ await execAsync(`${quoteCommand2(adbCommand)} -s ${shellQuoteArg2(this.session.deviceId)} exec-out screencap -p > "${screenshotPath}"`);
914
1361
  } else {
915
1362
  await execAsync(`xcrun simctl io ${this.session.deviceId} screenshot "${screenshotPath}"`);
916
1363
  }
@@ -1010,8 +1457,9 @@ var MaestroRecorder = class extends EventEmitter {
1010
1457
  await new Promise((resolve) => setTimeout(resolve, 2e3));
1011
1458
  if (this.session.platform === "android" && this.session.videoPath) {
1012
1459
  try {
1013
- await execAsync(`adb -s ${this.session.deviceId} pull /sdcard/maestro-recording.mp4 "${this.session.videoPath}"`);
1014
- await execAsync(`adb -s ${this.session.deviceId} shell rm /sdcard/maestro-recording.mp4`);
1460
+ const adbCommand = getAdbCommandOrThrow();
1461
+ await execAsync(`${quoteCommand2(adbCommand)} -s ${shellQuoteArg2(this.session.deviceId)} pull /sdcard/maestro-recording.mp4 "${this.session.videoPath}"`);
1462
+ await execAsync(`${quoteCommand2(adbCommand)} -s ${shellQuoteArg2(this.session.deviceId)} shell rm /sdcard/maestro-recording.mp4`);
1015
1463
  } catch (e) {
1016
1464
  console.error("Failed to pull video:", e);
1017
1465
  }
@@ -1027,6 +1475,18 @@ var MaestroRecorder = class extends EventEmitter {
1027
1475
  console.log("[MaestroRecorder] Generated flow YAML from recorded actions");
1028
1476
  }
1029
1477
  }
1478
+ if (this.session.actions.length === 0 && this.session.flowPath && fs.existsSync(this.session.flowPath)) {
1479
+ try {
1480
+ const fallbackYaml = await fs.promises.readFile(this.session.flowPath, "utf-8");
1481
+ const parsedActions = parseMaestroActionsFromYaml(fallbackYaml);
1482
+ if (parsedActions.length > 0) {
1483
+ this.session.actions = parsedActions;
1484
+ console.log(`[MaestroRecorder] Rehydrated ${parsedActions.length} actions from YAML for manual capture`);
1485
+ }
1486
+ } catch (error) {
1487
+ console.error("[MaestroRecorder] Failed to rehydrate actions from YAML:", error);
1488
+ }
1489
+ }
1030
1490
  const metadataPath = path.join(this.session.screenshotsDir, "..", "session.json");
1031
1491
  await fs.promises.writeFile(metadataPath, JSON.stringify(this.session, null, 2));
1032
1492
  this.clearPendingSession();
@@ -1040,186 +1500,13 @@ var MaestroRecorder = class extends EventEmitter {
1040
1500
  * Parse actions from Maestro YAML content
1041
1501
  */
1042
1502
  parseActionsFromYaml(yamlContent) {
1043
- const actions = [];
1044
- const lines = yamlContent.split("\n");
1045
- let currentAction = null;
1046
- let actionIndex = 0;
1047
- const nextId = () => `action_${String(++actionIndex).padStart(3, "0")}`;
1048
- const cleanValue = (value) => value.trim().replace(/^["']|["']$/g, "");
1049
- const pushAction = (action) => {
1050
- actions.push({
1051
- id: action.id || nextId(),
1052
- type: action.type || "tap",
1053
- timestamp: action.timestamp || Date.now(),
1054
- description: action.description || action.type || "Action",
1055
- x: action.x,
1056
- y: action.y,
1057
- endX: action.endX,
1058
- endY: action.endY,
1059
- text: action.text,
1060
- direction: action.direction,
1061
- seconds: action.seconds,
1062
- appId: action.appId,
1063
- duration: action.duration,
1064
- screenshotPath: action.screenshotPath
1065
- });
1066
- };
1067
- for (const line of lines) {
1068
- const trimmed = line.trim();
1069
- if (trimmed.startsWith("#") || trimmed === "" || trimmed === "---") {
1070
- continue;
1071
- }
1072
- if (trimmed.startsWith("appId:")) {
1073
- const parsedAppId = cleanValue(trimmed.replace("appId:", ""));
1074
- if (this.session && parsedAppId && !this.session.appId) {
1075
- this.session.appId = parsedAppId;
1076
- }
1077
- continue;
1078
- }
1079
- if (trimmed.startsWith("- tapOn:")) {
1080
- const inlineValue = cleanValue(trimmed.replace("- tapOn:", ""));
1081
- if (inlineValue) {
1082
- pushAction({
1083
- type: "tap",
1084
- description: `Tap on "${inlineValue}"`,
1085
- text: inlineValue
1086
- });
1087
- continue;
1088
- }
1089
- currentAction = { type: "tap", description: "Tap" };
1090
- } else if (trimmed.startsWith("- tap:")) {
1091
- currentAction = { type: "tap", description: "Tap" };
1092
- } else if (trimmed.startsWith("- swipe:")) {
1093
- currentAction = { type: "swipe", description: "Swipe" };
1094
- } else if (trimmed.startsWith("- scroll:")) {
1095
- currentAction = { type: "scroll", description: "Scroll" };
1096
- } else if (trimmed.startsWith("- scrollUntilVisible:")) {
1097
- currentAction = { type: "scroll", description: "Scroll" };
1098
- } else if (trimmed.startsWith("- inputText:")) {
1099
- const text = cleanValue(trimmed.replace("- inputText:", ""));
1100
- pushAction({
1101
- type: "input",
1102
- description: `Input: ${text.slice(0, 20)}${text.length > 20 ? "..." : ""}`,
1103
- text
1104
- });
1105
- continue;
1106
- } else if (trimmed.startsWith("- launchApp:")) {
1107
- const appId = cleanValue(trimmed.replace("- launchApp:", ""));
1108
- pushAction({
1109
- type: "launch",
1110
- description: `Launch: ${appId}`,
1111
- text: appId,
1112
- appId
1113
- });
1114
- continue;
1115
- } else if (trimmed === "- launchApp") {
1116
- pushAction({
1117
- type: "launch",
1118
- description: "Launch app"
1119
- });
1120
- continue;
1121
- } else if (trimmed.startsWith("- assertVisible:")) {
1122
- const inlineValue = cleanValue(trimmed.replace("- assertVisible:", ""));
1123
- if (inlineValue) {
1124
- pushAction({
1125
- type: "assert",
1126
- description: `Assert visible "${inlineValue}"`,
1127
- text: inlineValue
1128
- });
1129
- continue;
1130
- }
1131
- currentAction = { type: "assert", description: "Assert visible" };
1132
- } else if (trimmed.startsWith("- extendedWaitUntil:")) {
1133
- currentAction = { type: "wait", description: "Wait" };
1134
- } else if (trimmed.startsWith("- pressKey:")) {
1135
- const key = cleanValue(trimmed.replace("- pressKey:", "")).toLowerCase();
1136
- if (key === "back") {
1137
- pushAction({ type: "back", description: "Back button" });
1138
- } else if (key === "home") {
1139
- pushAction({ type: "home", description: "Home button" });
1140
- } else {
1141
- pushAction({
1142
- type: "pressKey",
1143
- description: `Press: ${key}`,
1144
- text: key
1145
- });
1146
- }
1147
- continue;
1148
- } else if (trimmed.startsWith("- back")) {
1149
- pushAction({ type: "back", description: "Back button" });
1150
- continue;
1151
- }
1152
- if (currentAction && trimmed.startsWith("point:")) {
1153
- const point = cleanValue(trimmed.replace("point:", ""));
1154
- const [x, y] = point.split(",").map((n) => parseInt(n.trim()));
1155
- if (!isNaN(x) && !isNaN(y)) {
1156
- currentAction.x = x;
1157
- currentAction.y = y;
1158
- currentAction.description = `Tap at (${x}, ${y})`;
1159
- pushAction(currentAction);
1160
- currentAction = null;
1161
- continue;
1162
- }
1163
- }
1164
- if (currentAction && trimmed.startsWith("text:")) {
1165
- const text = cleanValue(trimmed.replace("text:", ""));
1166
- currentAction.text = text;
1167
- if (currentAction.type === "assert") {
1168
- currentAction.description = `Assert visible "${text}"`;
1169
- } else {
1170
- currentAction.description = `Tap on "${text}"`;
1171
- }
1172
- pushAction(currentAction);
1173
- currentAction = null;
1174
- continue;
1175
- }
1176
- if (currentAction && trimmed.startsWith("id:")) {
1177
- const id = cleanValue(trimmed.replace("id:", ""));
1178
- currentAction.text = id;
1179
- currentAction.description = `Tap on id: ${id}`;
1180
- pushAction(currentAction);
1181
- currentAction = null;
1182
- continue;
1183
- }
1184
- if (currentAction && (currentAction.type === "swipe" || currentAction.type === "scroll") && trimmed.startsWith("direction:")) {
1185
- const direction = cleanValue(trimmed.replace("direction:", "")).toLowerCase();
1186
- currentAction.direction = direction;
1187
- currentAction.description = currentAction.type === "swipe" ? `Swipe ${direction}` : `Scroll ${direction}`;
1188
- pushAction(currentAction);
1189
- currentAction = null;
1190
- continue;
1191
- }
1192
- if (currentAction && currentAction.type === "wait" && trimmed.startsWith("timeout:")) {
1193
- const timeoutMs = parseInt(cleanValue(trimmed.replace("timeout:", "")), 10);
1194
- const seconds = Number.isFinite(timeoutMs) && timeoutMs > 0 ? Math.max(1, Math.round(timeoutMs / 1e3)) : 3;
1195
- currentAction.seconds = seconds;
1196
- currentAction.description = `Wait ${seconds}s`;
1197
- pushAction(currentAction);
1198
- currentAction = null;
1199
- continue;
1200
- }
1201
- if (currentAction && currentAction.type === "swipe") {
1202
- if (trimmed.startsWith("start:")) {
1203
- const point = cleanValue(trimmed.replace("start:", ""));
1204
- const [x, y] = point.split(",").map((n) => parseInt(n.trim()));
1205
- if (!isNaN(x) && !isNaN(y)) {
1206
- currentAction.x = x;
1207
- currentAction.y = y;
1208
- }
1209
- } else if (trimmed.startsWith("end:")) {
1210
- const point = cleanValue(trimmed.replace("end:", ""));
1211
- const [x, y] = point.split(",").map((n) => parseInt(n.trim()));
1212
- if (!isNaN(x) && !isNaN(y)) {
1213
- currentAction.endX = x;
1214
- currentAction.endY = y;
1215
- currentAction.description = `Swipe from (${currentAction.x}, ${currentAction.y}) to (${x}, ${y})`;
1216
- pushAction(currentAction);
1217
- currentAction = null;
1218
- }
1219
- }
1220
- }
1503
+ const parsedActions = parseMaestroActionsFromYaml(yamlContent);
1504
+ const appIdMatch = yamlContent.match(/^\s*appId:\s*([^\n#]+)/m);
1505
+ const parsedAppId = appIdMatch?.[1]?.trim().replace(/^["']|["']$/g, "") || "";
1506
+ if (this.session && parsedAppId && !this.session.appId) {
1507
+ this.session.appId = parsedAppId;
1221
1508
  }
1222
- return actions;
1509
+ return parsedActions;
1223
1510
  }
1224
1511
  mergeParsedActionsWithRecorded(parsedActions, recordedActions) {
1225
1512
  if (parsedActions.length === 0) return recordedActions;
@@ -1376,6 +1663,15 @@ function getMaestroRecorder() {
1376
1663
  }
1377
1664
 
1378
1665
  export {
1666
+ isTemplatesInstalled,
1667
+ getAvailableTemplates,
1668
+ getTemplate,
1669
+ getBundlePath,
1670
+ findAndroidSdkPath,
1671
+ getEmulatorPath,
1672
+ getAdbCommand,
1673
+ listConnectedAndroidDevices,
1674
+ resolveAndroidDeviceSerial,
1379
1675
  isMaestroInstalled,
1380
1676
  isIdbInstalled,
1381
1677
  tapViaIdb,
@@ -1389,5 +1685,6 @@ export {
1389
1685
  createLoginFlow,
1390
1686
  createOnboardingFlow,
1391
1687
  createNavigationTestFlow,
1688
+ parseMaestroActionsFromYaml,
1392
1689
  getMaestroRecorder
1393
1690
  };