@monoes/monobrowse 1.0.9 → 1.0.12

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 (221) hide show
  1. package/README.md +1 -1
  2. package/dist/src/__tests__/adapters.test.js +5 -2
  3. package/dist/src/__tests__/adapters.test.js.map +1 -1
  4. package/dist/src/__tests__/batch-eval-parsing.test.js +4 -2
  5. package/dist/src/__tests__/batch-eval-parsing.test.js.map +1 -1
  6. package/dist/src/__tests__/box-model.test.js +9 -5
  7. package/dist/src/__tests__/box-model.test.js.map +1 -1
  8. package/dist/src/__tests__/browser-launch.test.d.ts +10 -0
  9. package/dist/src/__tests__/browser-launch.test.d.ts.map +1 -1
  10. package/dist/src/__tests__/browser-launch.test.js +17 -10
  11. package/dist/src/__tests__/browser-launch.test.js.map +1 -1
  12. package/dist/src/__tests__/cdp-client.test.js +9 -3
  13. package/dist/src/__tests__/cdp-client.test.js.map +1 -1
  14. package/dist/src/__tests__/close-browser.test.d.ts +13 -0
  15. package/dist/src/__tests__/close-browser.test.d.ts.map +1 -1
  16. package/dist/src/__tests__/close-browser.test.js +40 -26
  17. package/dist/src/__tests__/close-browser.test.js.map +1 -1
  18. package/dist/src/__tests__/console-log.test.js +17 -6
  19. package/dist/src/__tests__/console-log.test.js.map +1 -1
  20. package/dist/src/__tests__/find.test.js +26 -6
  21. package/dist/src/__tests__/find.test.js.map +1 -1
  22. package/dist/src/__tests__/frame-command.test.js +9 -3
  23. package/dist/src/__tests__/frame-command.test.js.map +1 -1
  24. package/dist/src/__tests__/open-url-navigate-error.test.js +1 -3
  25. package/dist/src/__tests__/open-url-navigate-error.test.js.map +1 -1
  26. package/dist/src/__tests__/output.test.js +1 -1
  27. package/dist/src/__tests__/output.test.js.map +1 -1
  28. package/dist/src/__tests__/reap-idle-browser.test.d.ts +15 -0
  29. package/dist/src/__tests__/reap-idle-browser.test.d.ts.map +1 -0
  30. package/dist/src/__tests__/reap-idle-browser.test.js +238 -0
  31. package/dist/src/__tests__/reap-idle-browser.test.js.map +1 -0
  32. package/dist/src/__tests__/ref-cache.test.js +18 -10
  33. package/dist/src/__tests__/ref-cache.test.js.map +1 -1
  34. package/dist/src/__tests__/session-state.test.d.ts +8 -0
  35. package/dist/src/__tests__/session-state.test.d.ts.map +1 -1
  36. package/dist/src/__tests__/session-state.test.js +10 -10
  37. package/dist/src/__tests__/session-state.test.js.map +1 -1
  38. package/dist/src/__tests__/snapshot.test.js +45 -15
  39. package/dist/src/__tests__/snapshot.test.js.map +1 -1
  40. package/dist/src/__tests__/storage-eval.test.js +14 -6
  41. package/dist/src/__tests__/storage-eval.test.js.map +1 -1
  42. package/dist/src/__tests__/tabs.test.js +25 -9
  43. package/dist/src/__tests__/tabs.test.js.map +1 -1
  44. package/dist/src/browser/action-builder/analyzer.d.ts +1 -1
  45. package/dist/src/browser/action-builder/analyzer.d.ts.map +1 -1
  46. package/dist/src/browser/action-builder/analyzer.js +25 -9
  47. package/dist/src/browser/action-builder/analyzer.js.map +1 -1
  48. package/dist/src/browser/action-builder/types.d.ts.map +1 -1
  49. package/dist/src/browser/actions.d.ts +1 -1
  50. package/dist/src/browser/actions.d.ts.map +1 -1
  51. package/dist/src/browser/actions.js +16 -6
  52. package/dist/src/browser/actions.js.map +1 -1
  53. package/dist/src/browser/adapters/gemini.d.ts.map +1 -1
  54. package/dist/src/browser/adapters/gemini.js.map +1 -1
  55. package/dist/src/browser/adapters/google.d.ts.map +1 -1
  56. package/dist/src/browser/adapters/google.js.map +1 -1
  57. package/dist/src/browser/adapters/index.d.ts.map +1 -1
  58. package/dist/src/browser/adapters/index.js +3 -3
  59. package/dist/src/browser/adapters/index.js.map +1 -1
  60. package/dist/src/browser/adapters/instagram.d.ts.map +1 -1
  61. package/dist/src/browser/adapters/instagram.js.map +1 -1
  62. package/dist/src/browser/adapters/linkedin.d.ts.map +1 -1
  63. package/dist/src/browser/adapters/linkedin.js.map +1 -1
  64. package/dist/src/browser/adapters/microsoft.d.ts.map +1 -1
  65. package/dist/src/browser/adapters/microsoft.js.map +1 -1
  66. package/dist/src/browser/adapters/x.d.ts.map +1 -1
  67. package/dist/src/browser/adapters/x.js.map +1 -1
  68. package/dist/src/browser/browser.d.ts +30 -0
  69. package/dist/src/browser/browser.d.ts.map +1 -1
  70. package/dist/src/browser/browser.js +122 -15
  71. package/dist/src/browser/browser.js.map +1 -1
  72. package/dist/src/browser/cdp.d.ts.map +1 -1
  73. package/dist/src/browser/cdp.js +21 -8
  74. package/dist/src/browser/cdp.js.map +1 -1
  75. package/dist/src/browser/console-log.d.ts.map +1 -1
  76. package/dist/src/browser/console-log.js +2 -2
  77. package/dist/src/browser/console-log.js.map +1 -1
  78. package/dist/src/browser/dashboard/server.d.ts.map +1 -1
  79. package/dist/src/browser/dashboard/server.js +29 -19
  80. package/dist/src/browser/dashboard/server.js.map +1 -1
  81. package/dist/src/browser/dashboard/ui.html +153 -165
  82. package/dist/src/browser/dialog.d.ts.map +1 -1
  83. package/dist/src/browser/dialog.js +3 -1
  84. package/dist/src/browser/dialog.js.map +1 -1
  85. package/dist/src/browser/emulation.d.ts.map +1 -1
  86. package/dist/src/browser/emulation.js +21 -6
  87. package/dist/src/browser/emulation.js.map +1 -1
  88. package/dist/src/browser/find.d.ts +4 -4
  89. package/dist/src/browser/find.d.ts.map +1 -1
  90. package/dist/src/browser/find.js +14 -6
  91. package/dist/src/browser/find.js.map +1 -1
  92. package/dist/src/browser/har.d.ts.map +1 -1
  93. package/dist/src/browser/har.js +14 -7
  94. package/dist/src/browser/har.js.map +1 -1
  95. package/dist/src/browser/index.d.ts +16 -16
  96. package/dist/src/browser/index.d.ts.map +1 -1
  97. package/dist/src/browser/index.js +16 -16
  98. package/dist/src/browser/index.js.map +1 -1
  99. package/dist/src/browser/network.d.ts.map +1 -1
  100. package/dist/src/browser/network.js +14 -4
  101. package/dist/src/browser/network.js.map +1 -1
  102. package/dist/src/browser/pdf.d.ts.map +1 -1
  103. package/dist/src/browser/pdf.js +3 -3
  104. package/dist/src/browser/pdf.js.map +1 -1
  105. package/dist/src/browser/profiler.d.ts.map +1 -1
  106. package/dist/src/browser/profiler.js +10 -5
  107. package/dist/src/browser/profiler.js.map +1 -1
  108. package/dist/src/browser/record.d.ts +1 -1
  109. package/dist/src/browser/record.d.ts.map +1 -1
  110. package/dist/src/browser/record.js +18 -7
  111. package/dist/src/browser/record.js.map +1 -1
  112. package/dist/src/browser/ref-cache.d.ts.map +1 -1
  113. package/dist/src/browser/ref-cache.js +9 -4
  114. package/dist/src/browser/ref-cache.js.map +1 -1
  115. package/dist/src/browser/screenshot.d.ts.map +1 -1
  116. package/dist/src/browser/screenshot.js +11 -7
  117. package/dist/src/browser/screenshot.js.map +1 -1
  118. package/dist/src/browser/session.d.ts +1 -1
  119. package/dist/src/browser/session.d.ts.map +1 -1
  120. package/dist/src/browser/session.js +27 -8
  121. package/dist/src/browser/session.js.map +1 -1
  122. package/dist/src/browser/snapshot.d.ts +1 -1
  123. package/dist/src/browser/snapshot.d.ts.map +1 -1
  124. package/dist/src/browser/snapshot.js +8 -4
  125. package/dist/src/browser/snapshot.js.map +1 -1
  126. package/dist/src/browser/storage.d.ts.map +1 -1
  127. package/dist/src/browser/storage.js.map +1 -1
  128. package/dist/src/browser/tabs.d.ts.map +1 -1
  129. package/dist/src/browser/tabs.js +9 -3
  130. package/dist/src/browser/tabs.js.map +1 -1
  131. package/dist/src/browser/trace.d.ts.map +1 -1
  132. package/dist/src/browser/trace.js +3 -3
  133. package/dist/src/browser/trace.js.map +1 -1
  134. package/dist/src/browser/types.d.ts.map +1 -1
  135. package/dist/src/browser/types.js +23 -4
  136. package/dist/src/browser/types.js.map +1 -1
  137. package/dist/src/browser/vitals.d.ts.map +1 -1
  138. package/dist/src/browser/vitals.js +1 -1
  139. package/dist/src/browser/vitals.js.map +1 -1
  140. package/dist/src/browser/wait.d.ts.map +1 -1
  141. package/dist/src/browser/wait.js +11 -5
  142. package/dist/src/browser/wait.js.map +1 -1
  143. package/dist/src/cli/action.d.ts.map +1 -1
  144. package/dist/src/cli/action.js +14 -11
  145. package/dist/src/cli/action.js.map +1 -1
  146. package/dist/src/cli/commands.d.ts.map +1 -1
  147. package/dist/src/cli/commands.js +453 -159
  148. package/dist/src/cli/commands.js.map +1 -1
  149. package/dist/src/cli/output.js +5 -5
  150. package/dist/src/cli/output.js.map +1 -1
  151. package/dist/src/cli/platform.d.ts.map +1 -1
  152. package/dist/src/cli/platform.js +15 -11
  153. package/dist/src/cli/platform.js.map +1 -1
  154. package/dist/src/cli/types.d.ts +1 -1
  155. package/dist/src/cli/types.d.ts.map +1 -1
  156. package/dist/src/cli.js +21 -9
  157. package/dist/src/cli.js.map +1 -1
  158. package/dist/src/index.d.ts +2 -2
  159. package/dist/src/index.d.ts.map +1 -1
  160. package/dist/src/index.js +2 -2
  161. package/dist/src/index.js.map +1 -1
  162. package/dist/tsconfig.tsbuildinfo +1 -1
  163. package/package.json +2 -2
  164. package/src/__tests__/adapters.test.ts +15 -8
  165. package/src/__tests__/batch-eval-parsing.test.ts +4 -2
  166. package/src/__tests__/box-model.test.ts +9 -5
  167. package/src/__tests__/browser-launch.test.ts +26 -15
  168. package/src/__tests__/cdp-client.test.ts +25 -9
  169. package/src/__tests__/close-browser.test.ts +49 -26
  170. package/src/__tests__/console-log.test.ts +36 -13
  171. package/src/__tests__/find.test.ts +58 -20
  172. package/src/__tests__/frame-command.test.ts +12 -6
  173. package/src/__tests__/open-url-navigate-error.test.ts +2 -4
  174. package/src/__tests__/output.test.ts +1 -1
  175. package/src/__tests__/reap-idle-browser.test.ts +276 -0
  176. package/src/__tests__/ref-cache.test.ts +25 -12
  177. package/src/__tests__/session-state.test.ts +40 -28
  178. package/src/__tests__/snapshot.test.ts +61 -22
  179. package/src/__tests__/storage-eval.test.ts +30 -16
  180. package/src/__tests__/tabs.test.ts +26 -10
  181. package/src/browser/action-builder/analyzer.ts +39 -15
  182. package/src/browser/action-builder/types.ts +45 -8
  183. package/src/browser/actions.ts +288 -123
  184. package/src/browser/adapters/gemini.ts +7 -3
  185. package/src/browser/adapters/google.ts +5 -3
  186. package/src/browser/adapters/index.ts +5 -4
  187. package/src/browser/adapters/instagram.ts +4 -2
  188. package/src/browser/adapters/linkedin.ts +4 -2
  189. package/src/browser/adapters/microsoft.ts +9 -5
  190. package/src/browser/adapters/x.ts +7 -3
  191. package/src/browser/browser.ts +188 -42
  192. package/src/browser/cdp.ts +43 -11
  193. package/src/browser/console-log.ts +24 -7
  194. package/src/browser/dashboard/server.ts +44 -25
  195. package/src/browser/dashboard/ui.html +153 -165
  196. package/src/browser/dialog.ts +25 -8
  197. package/src/browser/emulation.ts +86 -36
  198. package/src/browser/find.ts +110 -47
  199. package/src/browser/har.ts +58 -26
  200. package/src/browser/index.ts +16 -16
  201. package/src/browser/network.ts +147 -46
  202. package/src/browser/pdf.ts +4 -4
  203. package/src/browser/profiler.ts +23 -10
  204. package/src/browser/record.ts +38 -19
  205. package/src/browser/ref-cache.ts +38 -17
  206. package/src/browser/screenshot.ts +58 -32
  207. package/src/browser/session.ts +51 -18
  208. package/src/browser/snapshot.ts +34 -20
  209. package/src/browser/storage.ts +73 -17
  210. package/src/browser/tabs.ts +29 -17
  211. package/src/browser/trace.ts +14 -10
  212. package/src/browser/types.ts +23 -4
  213. package/src/browser/vitals.ts +33 -16
  214. package/src/browser/wait.ts +47 -21
  215. package/src/cli/action.ts +52 -21
  216. package/src/cli/commands.ts +1093 -385
  217. package/src/cli/output.ts +5 -5
  218. package/src/cli/platform.ts +44 -23
  219. package/src/cli/types.ts +1 -1
  220. package/src/cli.ts +34 -18
  221. package/src/index.ts +2 -2
@@ -2,8 +2,8 @@
2
2
  * Browse Command — Native browser automation via Chrome DevTools Protocol
3
3
  * Provides ref-based element model and token-efficient accessibility snapshots
4
4
  */
5
- import { output } from './output.js';
6
5
  import { createActionCommand } from './action.js';
6
+ import { output } from './output.js';
7
7
  import { createPlatformCommand } from './platform.js';
8
8
  // Runtime state (single session per CLI process)
9
9
  const DEFAULT_PORT = 9222;
@@ -50,7 +50,9 @@ function ensureSignalCleanupHandlers() {
50
50
  try {
51
51
  process.kill(pid, 'SIGKILL');
52
52
  }
53
- catch { /* already exited */ }
53
+ catch {
54
+ /* already exited */
55
+ }
54
56
  }
55
57
  }
56
58
  }
@@ -78,7 +80,9 @@ async function resolveDefaultPort(browser) {
78
80
  // user's debug port and silently swap which browser commands act on —
79
81
  // fail loudly instead.
80
82
  try {
81
- await fetch(`http://127.0.0.1:${persisted.port}/json/version`, { signal: AbortSignal.timeout(1500) });
83
+ await fetch(`http://127.0.0.1:${persisted.port}/json/version`, {
84
+ signal: AbortSignal.timeout(1500),
85
+ });
82
86
  }
83
87
  catch {
84
88
  await browser.clearActivePort();
@@ -89,7 +93,7 @@ async function resolveDefaultPort(browser) {
89
93
  }
90
94
  async function ensureConnected(port, targetId) {
91
95
  const browser = await getBrowser();
92
- if (!_client || !_client.isConnected()) {
96
+ if (!_client?.isConnected()) {
93
97
  if (_client && _sessionId) {
94
98
  browser.teardownRouteInterception(_sessionId);
95
99
  browser.stopRequestCapture(_sessionId);
@@ -146,20 +150,28 @@ async function detectAttentionNeeded(client, sessionId, url) {
146
150
  if (ATTENTION_URL_RE.test(url))
147
151
  return 'login';
148
152
  try {
149
- const hasPassword = await client.send('Runtime.evaluate', {
153
+ const hasPassword = await client
154
+ .send('Runtime.evaluate', {
150
155
  expression: '!!document.querySelector("input[type=password]")',
151
156
  returnByValue: true,
152
- }, sessionId).then(r => r.result?.value === true).catch(() => false);
157
+ }, sessionId)
158
+ .then((r) => r.result?.value === true)
159
+ .catch(() => false);
153
160
  if (hasPassword)
154
161
  return 'login';
155
- const hasCaptcha = await client.send('Runtime.evaluate', {
162
+ const hasCaptcha = await client
163
+ .send('Runtime.evaluate', {
156
164
  expression: '!!(document.querySelector("iframe[src*=recaptcha]") || document.querySelector("iframe[src*=hcaptcha]") || document.querySelector(".g-recaptcha") || document.querySelector(".h-captcha") || document.querySelector("[data-sitekey]"))',
157
165
  returnByValue: true,
158
- }, sessionId).then(r => r.result?.value === true).catch(() => false);
166
+ }, sessionId)
167
+ .then((r) => r.result?.value === true)
168
+ .catch(() => false);
159
169
  if (hasCaptcha)
160
170
  return 'captcha';
161
171
  }
162
- catch { /* ignore CDP errors */ }
172
+ catch {
173
+ /* ignore CDP errors */
174
+ }
163
175
  return null;
164
176
  }
165
177
  function waitForEnter() {
@@ -167,12 +179,16 @@ function waitForEnter() {
167
179
  // Non-interactive context (MCP server, piped input, CI) — cannot safely read stdin.
168
180
  // Auto-continue after a short grace period so automation isn't blocked.
169
181
  output.printWarning('Non-interactive mode: auto-continuing in 30s. Switch to headed manually if needed.');
170
- return new Promise(resolve => setTimeout(resolve, 30_000));
182
+ return new Promise((resolve) => setTimeout(resolve, 30_000));
171
183
  }
172
- return new Promise(resolve => {
184
+ return new Promise((resolve) => {
173
185
  process.stdin.resume();
174
186
  process.stdin.setEncoding('utf8');
175
- const onData = () => { process.stdin.pause(); process.stdin.off('data', onData); resolve(); };
187
+ const onData = () => {
188
+ process.stdin.pause();
189
+ process.stdin.off('data', onData);
190
+ resolve();
191
+ };
176
192
  process.stdin.once('data', onData);
177
193
  });
178
194
  }
@@ -184,7 +200,9 @@ async function switchToHeaded(url, port) {
184
200
  try {
185
201
  savedCookies = await browser.getCookies(_client, _sessionId);
186
202
  }
187
- catch { /* ignore */ }
203
+ catch {
204
+ /* ignore */
205
+ }
188
206
  browser.teardownRouteInterception(_sessionId);
189
207
  browser.stopRequestCapture(_sessionId);
190
208
  browser.teardownDialogHandling(_sessionId);
@@ -219,14 +237,18 @@ async function switchToHeaded(url, port) {
219
237
  try {
220
238
  await browser.setCookies(_client, _sessionId, savedCookies);
221
239
  }
222
- catch { /* ignore */ }
240
+ catch {
241
+ /* ignore */
242
+ }
223
243
  }
224
244
  await browser.openUrl(_client, _sessionId, url);
225
245
  output.printInfo('Browser window opened. Complete the required action (login / CAPTCHA), then press Enter to continue in headless mode...');
226
246
  await waitForEnter();
227
247
  // Capture post-auth cookies
228
248
  const authCookies = await browser.getCookies(_client, _sessionId).catch(() => []);
229
- const authLocalStorage = await browser.getLocalStorage(_client, _sessionId).catch(() => ({}));
249
+ const authLocalStorage = await browser
250
+ .getLocalStorage(_client, _sessionId)
251
+ .catch(() => ({}));
230
252
  // Close headed session — actually terminate the underlying Chrome process
231
253
  // (Browser.close CDP command, PID-kill fallback), not just our CDP client
232
254
  // connection, so the visible authenticated window doesn't linger forever.
@@ -251,13 +273,17 @@ async function switchToHeaded(url, port) {
251
273
  try {
252
274
  await browser.setCookies(_client, _sessionId, authCookies);
253
275
  }
254
- catch { /* ignore */ }
276
+ catch {
277
+ /* ignore */
278
+ }
255
279
  }
256
280
  if (authLocalStorage && Object.keys(authLocalStorage).length) {
257
281
  try {
258
282
  await browser.setLocalStorage(_client, _sessionId, authLocalStorage);
259
283
  }
260
- catch { /* ignore */ }
284
+ catch {
285
+ /* ignore */
286
+ }
261
287
  }
262
288
  }
263
289
  /**
@@ -272,7 +298,7 @@ function imageFormat(value, allowed, fallback) {
272
298
  return allowed.includes(value) ? value : fallback;
273
299
  }
274
300
  function print(msg) {
275
- process.stdout.write(msg + '\n');
301
+ process.stdout.write(`${msg}\n`);
276
302
  }
277
303
  // ---------------------------------------------------------------------------
278
304
  // Sub-commands
@@ -282,7 +308,12 @@ const openCommand = {
282
308
  description: 'Open a URL in the browser. Usage: monomind browse open <url>',
283
309
  options: [
284
310
  { name: 'port', short: 'p', type: 'number', description: 'CDP port', default: 9222 },
285
- { name: 'headed', type: 'boolean', description: 'Force visible browser window', default: false },
311
+ {
312
+ name: 'headed',
313
+ type: 'boolean',
314
+ description: 'Force visible browser window',
315
+ default: false,
316
+ },
286
317
  { name: 'session', short: 's', type: 'string', description: 'Session name to restore' },
287
318
  { name: 'state', type: 'string', description: 'State file to load' },
288
319
  ],
@@ -300,19 +331,25 @@ const openCommand = {
300
331
  try {
301
332
  await browser.stopHarRecording(prevClient, prevSid);
302
333
  }
303
- catch { /* ignore */ }
334
+ catch {
335
+ /* ignore */
336
+ }
304
337
  }
305
338
  if (browser.getTraceStatus(prevSid)) {
306
339
  try {
307
340
  await browser.stopTrace(prevClient, prevSid);
308
341
  }
309
- catch { /* ignore */ }
342
+ catch {
343
+ /* ignore */
344
+ }
310
345
  }
311
346
  if (browser.isProfilingActive(prevSid)) {
312
347
  try {
313
348
  await browser.stopCpuProfile(prevClient, prevSid);
314
349
  }
315
- catch { /* ignore */ }
350
+ catch {
351
+ /* ignore */
352
+ }
316
353
  }
317
354
  browser.teardownRouteInterception(prevSid);
318
355
  browser.stopRequestCapture(prevSid);
@@ -352,7 +389,10 @@ const openCommand = {
352
389
  // launched it) instead of overwriting with undefined.
353
390
  const existing = await browser.loadActivePortInfo();
354
391
  const preserved = existing && existing.port === _port ? existing : undefined;
355
- await browser.saveActivePort(_port, { pid: preserved?.pid, userDataDir: preserved?.userDataDir });
392
+ await browser.saveActivePort(_port, {
393
+ pid: preserved?.pid,
394
+ userDataDir: preserved?.userDataDir,
395
+ });
356
396
  }
357
397
  ensureSignalCleanupHandlers();
358
398
  const conn = await browser.connectToTarget(_port);
@@ -395,15 +435,49 @@ const snapshotCommand = {
395
435
  name: 'snapshot',
396
436
  description: 'Capture accessibility snapshot with ref-based element handles (@e1, @e2, ...)',
397
437
  options: [
398
- { name: 'interactive', short: 'i', type: 'boolean', description: 'Interactive elements only (93% token reduction)', default: false },
399
- { name: 'compact', short: 'c', type: 'boolean', description: 'Compact output format', default: false },
438
+ {
439
+ name: 'interactive',
440
+ short: 'i',
441
+ type: 'boolean',
442
+ description: 'Interactive elements only (93% token reduction)',
443
+ default: false,
444
+ },
445
+ {
446
+ name: 'compact',
447
+ short: 'c',
448
+ type: 'boolean',
449
+ description: 'Compact output format',
450
+ default: false,
451
+ },
400
452
  { name: 'json', type: 'boolean', description: 'Output as JSON', default: false },
401
453
  { name: 'depth', short: 'd', type: 'number', description: 'Max depth of AX tree to show' },
402
- { name: 'selector', short: 's', type: 'string', description: 'Scope snapshot to a CSS selector' },
403
- { name: 'save', type: 'string', description: 'Save snapshot text to file (baseline for --diff)' },
404
- { name: 'diff', type: 'string', description: 'Compare current snapshot against a saved baseline file' },
405
- { name: 'content-boundaries', type: 'boolean', description: 'Wrap output in sentinel markers to prevent page-content injection attacks', default: false },
406
- { name: 'max-output', type: 'number', description: 'Truncate output to N characters (prevents context window blowout on large pages)' },
454
+ {
455
+ name: 'selector',
456
+ short: 's',
457
+ type: 'string',
458
+ description: 'Scope snapshot to a CSS selector',
459
+ },
460
+ {
461
+ name: 'save',
462
+ type: 'string',
463
+ description: 'Save snapshot text to file (baseline for --diff)',
464
+ },
465
+ {
466
+ name: 'diff',
467
+ type: 'string',
468
+ description: 'Compare current snapshot against a saved baseline file',
469
+ },
470
+ {
471
+ name: 'content-boundaries',
472
+ type: 'boolean',
473
+ description: 'Wrap output in sentinel markers to prevent page-content injection attacks',
474
+ default: false,
475
+ },
476
+ {
477
+ name: 'max-output',
478
+ type: 'number',
479
+ description: 'Truncate output to N characters (prevents context window blowout on large pages)',
480
+ },
407
481
  ],
408
482
  action: async (ctx) => {
409
483
  const { client, sessionId } = await ensureConnected(_port);
@@ -427,8 +501,8 @@ const snapshotCommand = {
427
501
  };
428
502
  // --save: write snapshot text to baseline file
429
503
  if (ctx.flags.save) {
430
- const { writeFile, mkdir } = await import('fs/promises');
431
- const { dirname } = await import('path');
504
+ const { writeFile, mkdir } = await import('node:fs/promises');
505
+ const { dirname } = await import('node:path');
432
506
  const savePath = ctx.flags.save;
433
507
  await mkdir(dirname(savePath), { recursive: true }).catch(() => { });
434
508
  await writeFile(savePath, result.text, 'utf8');
@@ -437,7 +511,7 @@ const snapshotCommand = {
437
511
  }
438
512
  // --diff: compare against baseline file
439
513
  if (ctx.flags.diff) {
440
- const { readFile } = await import('fs/promises');
514
+ const { readFile } = await import('node:fs/promises');
441
515
  const baselinePath = ctx.flags.diff;
442
516
  let baseline;
443
517
  try {
@@ -459,7 +533,13 @@ const snapshotCommand = {
459
533
  removed.push(l);
460
534
  const changed = added.length > 0 || removed.length > 0;
461
535
  if (ctx.flags.json) {
462
- print(JSON.stringify({ changed, additions: added.length, removals: removed.length, added, removed }));
536
+ print(JSON.stringify({
537
+ changed,
538
+ additions: added.length,
539
+ removals: removed.length,
540
+ added,
541
+ removed,
542
+ }));
463
543
  }
464
544
  else {
465
545
  if (!changed) {
@@ -473,11 +553,19 @@ const snapshotCommand = {
473
553
  print(`\x1b[31m- ${l}\x1b[0m`);
474
554
  }
475
555
  }
476
- return { success: true, data: { changed, additions: added.length, removals: removed.length } };
556
+ return {
557
+ success: true,
558
+ data: { changed, additions: added.length, removals: removed.length },
559
+ };
477
560
  }
478
561
  if (ctx.flags.json) {
479
562
  const refsObj = Object.fromEntries([...result.refs.entries()].map(([k, v]) => [k, v]));
480
- print(JSON.stringify({ url: result.url, title: result.title, refs: refsObj, snapshot: result.text }));
563
+ print(JSON.stringify({
564
+ url: result.url,
565
+ title: result.title,
566
+ refs: refsObj,
567
+ snapshot: result.text,
568
+ }));
481
569
  }
482
570
  else {
483
571
  print(`[${result.title}] ${result.url}\n`);
@@ -555,11 +643,19 @@ const waitCommand = {
555
643
  { name: 'text', type: 'string', description: 'Wait for text to appear in page' },
556
644
  { name: 'not-text', type: 'string', description: 'Wait for text to disappear from page' },
557
645
  { name: 'selector', type: 'string', description: 'Wait for CSS selector to appear' },
558
- { name: 'load', type: 'string', description: 'Wait for load event: load|networkidle|domcontentloaded' },
646
+ {
647
+ name: 'load',
648
+ type: 'string',
649
+ description: 'Wait for load event: load|networkidle|domcontentloaded',
650
+ },
559
651
  { name: 'fn', type: 'string', description: 'Wait until JS expression returns truthy' },
560
652
  { name: 'ms', type: 'number', description: 'Wait N milliseconds' },
561
653
  { name: 'timeout', short: 't', type: 'number', description: 'Timeout in ms', default: 30000 },
562
- { name: 'download', type: 'string', description: 'Wait for a file download to complete and save to path (monitors Browser.downloadProgress events)' },
654
+ {
655
+ name: 'download',
656
+ type: 'string',
657
+ description: 'Wait for a file download to complete and save to path (monitors Browser.downloadProgress events)',
658
+ },
563
659
  ],
564
660
  action: async (ctx) => {
565
661
  const { client, sessionId } = await ensureConnected(_port);
@@ -574,7 +670,9 @@ const waitCommand = {
574
670
  if (ctx.flags.fn) {
575
671
  const expr = ctx.flags.fn;
576
672
  const rawTimeout = ctx.flags.timeout ?? 30000;
577
- const timeout = Number.isFinite(rawTimeout) ? Math.max(100, Math.min(rawTimeout, 300_000)) : 30000; // cap at 5min
673
+ const timeout = Number.isFinite(rawTimeout)
674
+ ? Math.max(100, Math.min(rawTimeout, 300_000))
675
+ : 30000; // cap at 5min
578
676
  const interval = 200;
579
677
  const deadline = Date.now() + timeout;
580
678
  while (Date.now() < deadline) {
@@ -593,7 +691,7 @@ const waitCommand = {
593
691
  const interval = 200;
594
692
  const deadline = Date.now() + timeout;
595
693
  while (Date.now() < deadline) {
596
- const text = await browser.evaluateJs(client, sessionId, 'document.body?.innerText ?? ""');
694
+ const text = (await browser.evaluateJs(client, sessionId, 'document.body?.innerText ?? ""'));
597
695
  if (!text.includes(target)) {
598
696
  output.printSuccess('Text disappeared');
599
697
  return { success: true };
@@ -604,26 +702,39 @@ const waitCommand = {
604
702
  }
605
703
  if (ctx.flags.download) {
606
704
  const savePath = ctx.flags.download;
607
- const { mkdir } = await import('fs/promises');
608
- const { dirname, join } = await import('path');
609
- const { tmpdir } = await import('os');
705
+ const { mkdir } = await import('node:fs/promises');
706
+ const { dirname, join } = await import('node:path');
707
+ const { tmpdir } = await import('node:os');
610
708
  const downloadDir = join(tmpdir(), `mm-dl-wait-${Date.now()}`);
611
709
  await mkdir(downloadDir, { recursive: true });
612
- await client.send('Browser.setDownloadBehavior', {
613
- behavior: 'allow', downloadPath: downloadDir, eventsEnabled: true,
614
- }, undefined).catch(() => client.send('Page.setDownloadBehavior', { behavior: 'allow', downloadPath: downloadDir }, sessionId).catch(() => { }));
710
+ await client
711
+ .send('Browser.setDownloadBehavior', {
712
+ behavior: 'allow',
713
+ downloadPath: downloadDir,
714
+ eventsEnabled: true,
715
+ }, undefined)
716
+ .catch(() => client
717
+ .send('Page.setDownloadBehavior', { behavior: 'allow', downloadPath: downloadDir }, sessionId)
718
+ .catch(() => { }));
615
719
  const MAX_DOWNLOAD_TIMEOUT = 5 * 60 * 1000; // I6: cap at 5 minutes
616
720
  const rawTimeout = Math.min(ctx.flags.timeout ?? 30000, MAX_DOWNLOAD_TIMEOUT);
617
721
  const finalPath = await new Promise((resolve, reject) => {
618
722
  let guid = '';
619
723
  let settled = false;
620
724
  // C2: capture off() functions to avoid listener leaks
621
- const offBegin = client.on('Browser.downloadWillBegin', (params) => { guid = params.guid; });
725
+ const offBegin = client.on('Browser.downloadWillBegin', (params) => {
726
+ guid = params.guid;
727
+ });
622
728
  let offProgress;
623
729
  // cleanup defined before setTimeout so the timeout callback can call it
624
730
  let tid;
625
731
  let pollTid;
626
- const cleanup = () => { clearTimeout(tid); clearInterval(pollTid); offBegin?.(); offProgress?.(); };
732
+ const cleanup = () => {
733
+ clearTimeout(tid);
734
+ clearInterval(pollTid);
735
+ offBegin?.();
736
+ offProgress?.();
737
+ };
627
738
  const finish = (path) => {
628
739
  if (settled)
629
740
  return;
@@ -641,7 +752,7 @@ const waitCommand = {
641
752
  tid = setTimeout(() => fail(new Error('Download timed out')), rawTimeout);
642
753
  offProgress = client.on('Browser.downloadProgress', async (params) => {
643
754
  if (params.guid === guid && params.state === 'completed') {
644
- const { readdir, rename, rmdir } = await import('fs/promises');
755
+ const { readdir, rename, rmdir } = await import('node:fs/promises');
645
756
  const files = await readdir(downloadDir);
646
757
  if (files.length > 0) {
647
758
  const src = join(downloadDir, files[0]);
@@ -671,7 +782,7 @@ const waitCommand = {
671
782
  if (settled || guid)
672
783
  return; // a real CDP event has taken over
673
784
  try {
674
- const { stat } = await import('fs/promises');
785
+ const { stat } = await import('node:fs/promises');
675
786
  const st = await stat(savePath);
676
787
  if (st.isFile() && st.size > 0 && st.size === lastSize) {
677
788
  stableReads++;
@@ -709,8 +820,18 @@ const screenshotCommand = {
709
820
  { name: 'full', type: 'boolean', description: 'Full page screenshot', default: false },
710
821
  { name: 'format', type: 'string', description: 'Format: png|jpeg|webp', default: 'png' },
711
822
  { name: 'quality', type: 'number', description: 'Quality 0-100 for jpeg/webp', default: 80 },
712
- { name: 'annotate', type: 'boolean', description: 'Overlay numbered labels keyed to @eN refs from last snapshot (viewport-only; do not combine with --full)', default: false },
713
- { name: 'hide-scrollbars', type: 'boolean', description: 'Hide native scrollbars via CSS injection before capture', default: false },
823
+ {
824
+ name: 'annotate',
825
+ type: 'boolean',
826
+ description: 'Overlay numbered labels keyed to @eN refs from last snapshot (viewport-only; do not combine with --full)',
827
+ default: false,
828
+ },
829
+ {
830
+ name: 'hide-scrollbars',
831
+ type: 'boolean',
832
+ description: 'Hide native scrollbars via CSS injection before capture',
833
+ default: false,
834
+ },
714
835
  { name: 'json', type: 'boolean', description: 'Output JSON with path', default: false },
715
836
  ],
716
837
  action: async (ctx) => {
@@ -718,10 +839,12 @@ const screenshotCommand = {
718
839
  const browser = await getBrowser();
719
840
  const hideScrollbars = ctx.flags['hide-scrollbars'];
720
841
  if (hideScrollbars) {
721
- await client.send('Runtime.evaluate', {
842
+ await client
843
+ .send('Runtime.evaluate', {
722
844
  expression: `(function(){var s=document.getElementById('__mm_noscroll__');if(s)return;var el=document.createElement('style');el.id='__mm_noscroll__';el.textContent='*::-webkit-scrollbar{display:none!important}*{scrollbar-width:none!important;-ms-overflow-style:none!important}';document.head.appendChild(el);})()`,
723
845
  returnByValue: false,
724
- }, sessionId).catch(() => { });
846
+ }, sessionId)
847
+ .catch(() => { });
725
848
  }
726
849
  const annotate = ctx.flags.annotate;
727
850
  // eslint-disable-next-line prefer-const
@@ -738,10 +861,12 @@ const screenshotCommand = {
738
861
  }
739
862
  finally {
740
863
  if (hideScrollbars) {
741
- await client.send('Runtime.evaluate', {
864
+ await client
865
+ .send('Runtime.evaluate', {
742
866
  expression: `(function(){var s=document.getElementById('__mm_noscroll__');if(s)s.remove();})()`,
743
867
  returnByValue: false,
744
- }, sessionId).catch(() => { });
868
+ }, sessionId)
869
+ .catch(() => { });
745
870
  }
746
871
  }
747
872
  if (ctx.flags.json) {
@@ -756,9 +881,7 @@ const screenshotCommand = {
756
881
  const getCommand = {
757
882
  name: 'get',
758
883
  description: 'Get page info. Usage: monomind browse get url|title|text|html|value|attr|count|box|styles [@ref] [attrName]',
759
- options: [
760
- { name: 'json', type: 'boolean', description: 'Output as JSON', default: false },
761
- ],
884
+ options: [{ name: 'json', type: 'boolean', description: 'Output as JSON', default: false }],
762
885
  action: async (ctx) => {
763
886
  const { client, sessionId } = await ensureConnected(_port);
764
887
  const browser = await getBrowser();
@@ -811,7 +934,8 @@ const getCommand = {
811
934
  throw new Error('Element not in DOM');
812
935
  const r = await client.send('Runtime.callFunctionOn', {
813
936
  functionDeclaration: 'function() { return this.value ?? null; }',
814
- objectId, returnByValue: true,
937
+ objectId,
938
+ returnByValue: true,
815
939
  }, sessionId);
816
940
  value = r.result?.value ?? null;
817
941
  break;
@@ -830,7 +954,8 @@ const getCommand = {
830
954
  throw new Error('Element not in DOM');
831
955
  const r = await client.send('Runtime.callFunctionOn', {
832
956
  functionDeclaration: `function() { return this.getAttribute(${JSON.stringify(attrName)}); }`,
833
- objectId, returnByValue: true,
957
+ objectId,
958
+ returnByValue: true,
834
959
  }, sessionId);
835
960
  value = r.result?.value ?? null;
836
961
  break;
@@ -867,7 +992,8 @@ const getCommand = {
867
992
  throw new Error('Element not in DOM');
868
993
  const r = await client.send('Runtime.callFunctionOn', {
869
994
  functionDeclaration: 'function() { const s = window.getComputedStyle(this); return JSON.stringify(Object.fromEntries([...s].map(k => [k, s.getPropertyValue(k)]))); }',
870
- objectId, returnByValue: true,
995
+ objectId,
996
+ returnByValue: true,
871
997
  }, sessionId);
872
998
  try {
873
999
  value = JSON.parse(r.result?.value ?? '{}');
@@ -895,7 +1021,12 @@ const scrollCommand = {
895
1021
  options: [
896
1022
  { name: 'amount', short: 'a', type: 'number', description: 'Pixels to scroll', default: 300 },
897
1023
  { name: 'ref', type: 'string', description: 'Element ref to scroll within' },
898
- { name: 'selector', short: 's', type: 'string', description: 'CSS selector of element to scroll within' },
1024
+ {
1025
+ name: 'selector',
1026
+ short: 's',
1027
+ type: 'string',
1028
+ description: 'CSS selector of element to scroll within',
1029
+ },
899
1030
  ],
900
1031
  action: async (ctx) => {
901
1032
  const { client, sessionId } = await ensureConnected(_port);
@@ -905,12 +1036,14 @@ const scrollCommand = {
905
1036
  throw new Error('Usage: monomind browse scroll up|down|left|right [amount]');
906
1037
  // Support positional amount: scroll down 300
907
1038
  const positionalAmount = ctx.args[1] !== undefined ? parseInt(ctx.args[1], 10) : undefined;
908
- const amount = (positionalAmount && Number.isFinite(positionalAmount)) ? positionalAmount : ctx.flags.amount ?? 300;
1039
+ const amount = positionalAmount && Number.isFinite(positionalAmount)
1040
+ ? positionalAmount
1041
+ : (ctx.flags.amount ?? 300);
909
1042
  if (ctx.flags.selector) {
910
1043
  const sel = ctx.flags.selector;
911
1044
  const dx = direction === 'right' ? amount : direction === 'left' ? -amount : 0;
912
1045
  const dy = direction === 'down' ? amount : direction === 'up' ? -amount : 0;
913
- const posJson = await browser.evaluateJs(client, sessionId, `(function(){var el=document.querySelector(${JSON.stringify(sel)});if(!el)return null;var r=el.getBoundingClientRect();return JSON.stringify({x:r.left+r.width/2,y:r.top+r.height/2});})()`);
1046
+ const posJson = (await browser.evaluateJs(client, sessionId, `(function(){var el=document.querySelector(${JSON.stringify(sel)});if(!el)return null;var r=el.getBoundingClientRect();return JSON.stringify({x:r.left+r.width/2,y:r.top+r.height/2});})()`));
914
1047
  if (!posJson)
915
1048
  throw new Error(`Selector not found: ${sel}`);
916
1049
  const pos = JSON.parse(posJson);
@@ -958,7 +1091,9 @@ const navigateCommand = {
958
1091
  expression: direction === 'back' ? 'history.back()' : 'history.forward()',
959
1092
  }, sessionId);
960
1093
  let fallbackHandle;
961
- const fallbackPromise = new Promise((r) => { fallbackHandle = setTimeout(r, 2000); });
1094
+ const fallbackPromise = new Promise((r) => {
1095
+ fallbackHandle = setTimeout(r, 2000);
1096
+ });
962
1097
  await Promise.race([frameStartedPromise, fallbackPromise]);
963
1098
  if (fallbackHandle !== undefined)
964
1099
  clearTimeout(fallbackHandle);
@@ -996,10 +1131,13 @@ const setCommand = {
996
1131
  const width = parseInt(ctx.args[1], 10);
997
1132
  const height = parseInt(ctx.args[2], 10);
998
1133
  const dpr = parseFloat(ctx.args[3]) || undefined;
999
- if (isNaN(width) || isNaN(height))
1134
+ if (Number.isNaN(width) || Number.isNaN(height))
1000
1135
  throw new Error('Usage: set viewport <width> <height> [dpr]');
1001
1136
  await client.send('Emulation.setDeviceMetricsOverride', {
1002
- width, height, deviceScaleFactor: dpr ?? 1, mobile: false,
1137
+ width,
1138
+ height,
1139
+ deviceScaleFactor: dpr ?? 1,
1140
+ mobile: false,
1003
1141
  }, sessionId);
1004
1142
  output.printSuccess(`Viewport set to ${width}x${height}${dpr ? ` @${dpr}x` : ''}`);
1005
1143
  break;
@@ -1016,7 +1154,7 @@ const setCommand = {
1016
1154
  const lat = parseFloat(ctx.args[1]);
1017
1155
  const lon = parseFloat(ctx.args[2]);
1018
1156
  const acc = parseFloat(ctx.args[3]) || 100;
1019
- if (isNaN(lat) || isNaN(lon))
1157
+ if (Number.isNaN(lat) || Number.isNaN(lon))
1020
1158
  throw new Error('Usage: set geo <latitude> <longitude> [accuracy]');
1021
1159
  await browser.setGeolocation(client, sessionId, lat, lon, acc);
1022
1160
  output.printSuccess(`Geolocation set: ${lat}, ${lon}`);
@@ -1066,7 +1204,11 @@ const stateCommand = {
1066
1204
  name: 'state',
1067
1205
  description: 'Manage browser session state. Usage: monomind browse state save|load|list|rename|clean [name]',
1068
1206
  options: [
1069
- { name: 'older-than', type: 'number', description: 'For state clean: remove sessions older than N days' },
1207
+ {
1208
+ name: 'older-than',
1209
+ type: 'number',
1210
+ description: 'For state clean: remove sessions older than N days',
1211
+ },
1070
1212
  ],
1071
1213
  action: async (ctx) => {
1072
1214
  const browser = await getBrowser();
@@ -1123,7 +1265,13 @@ const stateCommand = {
1123
1265
  const title = await browser.getCurrentTitle(c, sid);
1124
1266
  const cookies = await browser.getCookies(c, sid);
1125
1267
  const ls = await browser.getAllLocalStorage(c, sid);
1126
- const info = { url, title, cookies: cookies.length, localStorage: Object.keys(ls).length, refs: _refs.size };
1268
+ const info = {
1269
+ url,
1270
+ title,
1271
+ cookies: cookies.length,
1272
+ localStorage: Object.keys(ls).length,
1273
+ refs: _refs.size,
1274
+ };
1127
1275
  print(JSON.stringify(info, null, 2));
1128
1276
  return { success: true, data: info };
1129
1277
  }
@@ -1146,14 +1294,14 @@ const stateCommand = {
1146
1294
  if (!sessions.includes(oldName))
1147
1295
  throw new Error(`Session not found: ${oldName}`);
1148
1296
  // W1: validate newName to prevent path traversal
1149
- const { basename: basenameFn } = await import('path');
1297
+ const { basename: basenameFn } = await import('node:path');
1150
1298
  const safeName = basenameFn(newName);
1151
1299
  if (safeName !== newName || safeName.startsWith('.') || safeName.includes('/')) {
1152
1300
  throw new Error('Invalid session name — must not contain path separators or start with "."');
1153
1301
  }
1154
- const { unlink: unlinkRename, readFile, writeFile, mkdir: mkdirRename } = await import('fs/promises');
1155
- const { join: joinR } = await import('path');
1156
- const { homedir } = await import('os');
1302
+ const { unlink: unlinkRename, readFile, writeFile, mkdir: mkdirRename, } = await import('node:fs/promises');
1303
+ const { join: joinR } = await import('node:path');
1304
+ const { homedir } = await import('node:os');
1157
1305
  const sessionDir = joinR(homedir(), '.monomind', 'browser-sessions');
1158
1306
  const oldPath = joinR(sessionDir, `${oldName}.json`);
1159
1307
  const newPath = joinR(sessionDir, `${newName}.json`);
@@ -1167,9 +1315,9 @@ const stateCommand = {
1167
1315
  }
1168
1316
  case 'clean': {
1169
1317
  const days = ctx.flags['older-than'] ?? 7;
1170
- const { unlink, stat } = await import('fs/promises');
1171
- const { join: joinC } = await import('path');
1172
- const { homedir: homedirC } = await import('os');
1318
+ const { unlink, stat } = await import('node:fs/promises');
1319
+ const { join: joinC } = await import('node:path');
1320
+ const { homedir: homedirC } = await import('node:os');
1173
1321
  const sessionDir = joinC(homedirC(), '.monomind', 'browser-sessions');
1174
1322
  const sessions = await browser.listSessions();
1175
1323
  const cutoff = Date.now() - days * 86400 * 1000;
@@ -1200,10 +1348,26 @@ const networkCommand = {
1200
1348
  { name: 'status', type: 'number', description: 'HTTP status for fulfill', default: 200 },
1201
1349
  { name: 'headers', type: 'string', description: 'JSON headers object' },
1202
1350
  { name: 'json', type: 'boolean', description: 'Output as JSON', default: false },
1203
- { name: 'filter', type: 'string', description: 'Filter requests by URL substring (for network requests)' },
1204
- { name: 'method', type: 'string', description: 'Filter by HTTP method, e.g. GET, POST (for network requests)' },
1205
- { name: 'status-code', type: 'number', description: 'Filter by HTTP status code (for network requests)' },
1206
- { name: 'type', type: 'string', description: 'Filter by resource type: xhr|fetch|document|script|stylesheet|image (for network requests)' },
1351
+ {
1352
+ name: 'filter',
1353
+ type: 'string',
1354
+ description: 'Filter requests by URL substring (for network requests)',
1355
+ },
1356
+ {
1357
+ name: 'method',
1358
+ type: 'string',
1359
+ description: 'Filter by HTTP method, e.g. GET, POST (for network requests)',
1360
+ },
1361
+ {
1362
+ name: 'status-code',
1363
+ type: 'number',
1364
+ description: 'Filter by HTTP status code (for network requests)',
1365
+ },
1366
+ {
1367
+ name: 'type',
1368
+ type: 'string',
1369
+ description: 'Filter by resource type: xhr|fetch|document|script|stylesheet|image (for network requests)',
1370
+ },
1207
1371
  ],
1208
1372
  action: async (ctx) => {
1209
1373
  const { client, sessionId } = await ensureConnected(_port);
@@ -1216,15 +1380,19 @@ const networkCommand = {
1216
1380
  const pattern = ctx.flags.pattern;
1217
1381
  if (!pattern)
1218
1382
  throw new Error('--pattern required for network route');
1219
- const routes = [{
1383
+ const routes = [
1384
+ {
1220
1385
  pattern,
1221
1386
  action: ctx.flags.abort ? 'abort' : ctx.flags.fulfill ? 'fulfill' : 'continue',
1222
- response: ctx.flags.fulfill ? {
1223
- status: ctx.flags.status,
1224
- body: ctx.flags.fulfill,
1225
- headers: ctx.flags.headers ? JSON.parse(ctx.flags.headers) : {},
1226
- } : undefined,
1227
- }];
1387
+ response: ctx.flags.fulfill
1388
+ ? {
1389
+ status: ctx.flags.status,
1390
+ body: ctx.flags.fulfill,
1391
+ headers: ctx.flags.headers ? JSON.parse(ctx.flags.headers) : {},
1392
+ }
1393
+ : undefined,
1394
+ },
1395
+ ];
1228
1396
  await browser.setupRoutes(client, sessionId, routes);
1229
1397
  output.printSuccess(`Network route set: ${pattern}`);
1230
1398
  break;
@@ -1275,7 +1443,8 @@ const networkCommand = {
1275
1443
  if (filterStatus)
1276
1444
  reqs = reqs.filter((r) => r.status === filterStatus);
1277
1445
  if (filterType)
1278
- reqs = reqs.filter((r) => r.resourceType === filterType || r.type === filterType);
1446
+ reqs = reqs.filter((r) => r.resourceType === filterType ||
1447
+ r.type === filterType);
1279
1448
  if (ctx.flags.json)
1280
1449
  print(JSON.stringify({ data: reqs }));
1281
1450
  else {
@@ -1293,7 +1462,8 @@ const networkCommand = {
1293
1462
  if (!reqId)
1294
1463
  throw new Error('Usage: monomind browse network request <requestId>');
1295
1464
  const reqs = browser.getCapturedRequests(sessionId);
1296
- const req = reqs.find((r) => r.requestId === reqId || r.id === reqId);
1465
+ const req = reqs.find((r) => r.requestId === reqId ||
1466
+ r.id === reqId);
1297
1467
  if (!req) {
1298
1468
  output.printWarning(`Request not found: ${reqId}`);
1299
1469
  return { success: false };
@@ -1319,16 +1489,29 @@ const DEFAULT_EVAL_MAX_OUTPUT = 50_000;
1319
1489
  function truncateForOutput(text, maxOutput) {
1320
1490
  if (!(maxOutput > 0) || text.length <= maxOutput)
1321
1491
  return text;
1322
- return text.slice(0, maxOutput) + `\n[... truncated at ${maxOutput} chars]`;
1492
+ return `${text.slice(0, maxOutput)}\n[... truncated at ${maxOutput} chars]`;
1323
1493
  }
1324
1494
  const evalCommand = {
1325
1495
  name: 'eval',
1326
1496
  description: 'Evaluate JavaScript in page context. Usage: monomind browse eval "document.title"',
1327
1497
  options: [
1328
1498
  { name: 'json', type: 'boolean', description: 'Output as JSON', default: false },
1329
- { name: 'stdin', type: 'boolean', description: 'Read JS expression from stdin (heredoc-friendly for multiline scripts)', default: false },
1330
- { name: 'max-output', type: 'number', description: `Truncate printed output to N characters (default ${DEFAULT_EVAL_MAX_OUTPUT}; 0 disables truncation)` },
1331
- { name: 'timeout', type: 'number', description: 'Max ms to wait for evaluation to settle (default 30000)' },
1499
+ {
1500
+ name: 'stdin',
1501
+ type: 'boolean',
1502
+ description: 'Read JS expression from stdin (heredoc-friendly for multiline scripts)',
1503
+ default: false,
1504
+ },
1505
+ {
1506
+ name: 'max-output',
1507
+ type: 'number',
1508
+ description: `Truncate printed output to N characters (default ${DEFAULT_EVAL_MAX_OUTPUT}; 0 disables truncation)`,
1509
+ },
1510
+ {
1511
+ name: 'timeout',
1512
+ type: 'number',
1513
+ description: 'Max ms to wait for evaluation to settle (default 30000)',
1514
+ },
1332
1515
  ],
1333
1516
  action: async (ctx) => {
1334
1517
  const { client, sessionId } = await ensureConnected(_port);
@@ -1367,19 +1550,25 @@ const closeCommand = {
1367
1550
  try {
1368
1551
  await browser.stopHarRecording(client, sid);
1369
1552
  }
1370
- catch { /* ignore */ }
1553
+ catch {
1554
+ /* ignore */
1555
+ }
1371
1556
  }
1372
1557
  if (browser.getTraceStatus(sid)) {
1373
1558
  try {
1374
1559
  await browser.stopTrace(client, sid);
1375
1560
  }
1376
- catch { /* ignore */ }
1561
+ catch {
1562
+ /* ignore */
1563
+ }
1377
1564
  }
1378
1565
  if (browser.isProfilingActive(sid)) {
1379
1566
  try {
1380
1567
  await browser.stopCpuProfile(client, sid);
1381
1568
  }
1382
- catch { /* ignore */ }
1569
+ catch {
1570
+ /* ignore */
1571
+ }
1383
1572
  }
1384
1573
  browser.teardownRouteInterception(sid);
1385
1574
  browser.stopRequestCapture(sid);
@@ -1418,7 +1607,9 @@ const closeCommand = {
1418
1607
  try {
1419
1608
  conn.client.close();
1420
1609
  }
1421
- catch { /* already gone */ }
1610
+ catch {
1611
+ /* already gone */
1612
+ }
1422
1613
  }
1423
1614
  // closeBrowser has no PID fallback in a fresh process (launchedPids
1424
1615
  // is per-process) — re-probe so we report what actually happened.
@@ -1428,8 +1619,10 @@ const closeCommand = {
1428
1619
  const probeDeadline = Date.now() + 3000;
1429
1620
  while (stillUp && Date.now() < probeDeadline) {
1430
1621
  try {
1431
- await fetch(`http://127.0.0.1:${persisted.port}/json/version`, { signal: AbortSignal.timeout(800) });
1432
- await new Promise(r => setTimeout(r, 300));
1622
+ await fetch(`http://127.0.0.1:${persisted.port}/json/version`, {
1623
+ signal: AbortSignal.timeout(800),
1624
+ });
1625
+ await new Promise((r) => setTimeout(r, 300));
1433
1626
  }
1434
1627
  catch {
1435
1628
  stillUp = false;
@@ -1733,7 +1926,7 @@ const tapCommand = {
1733
1926
  y = Math.round(box.y);
1734
1927
  }
1735
1928
  else {
1736
- const posJson = await browser.evaluateJs(client, sessionId, `(function(){var el=document.querySelector(${JSON.stringify(arg)});if(!el)return null;var r=el.getBoundingClientRect();return JSON.stringify({x:r.left+r.width/2,y:r.top+r.height/2});})()`);
1929
+ const posJson = (await browser.evaluateJs(client, sessionId, `(function(){var el=document.querySelector(${JSON.stringify(arg)});if(!el)return null;var r=el.getBoundingClientRect();return JSON.stringify({x:r.left+r.width/2,y:r.top+r.height/2});})()`));
1737
1930
  if (!posJson)
1738
1931
  throw new Error(`Selector not found: ${arg}`);
1739
1932
  const pos = JSON.parse(posJson);
@@ -1750,9 +1943,25 @@ const swipeCommand = {
1750
1943
  name: 'swipe',
1751
1944
  description: 'Swipe gesture (mobile). Usage: monomind browse swipe up|down|left|right [distance] [--x N] [--y N]',
1752
1945
  options: [
1753
- { name: 'x', type: 'number', description: 'Start X coordinate (default: center)', default: 200 },
1754
- { name: 'y', type: 'number', description: 'Start Y coordinate (default: center)', default: 400 },
1755
- { name: 'distance', short: 'd', type: 'number', description: 'Swipe distance in pixels', default: 300 },
1946
+ {
1947
+ name: 'x',
1948
+ type: 'number',
1949
+ description: 'Start X coordinate (default: center)',
1950
+ default: 200,
1951
+ },
1952
+ {
1953
+ name: 'y',
1954
+ type: 'number',
1955
+ description: 'Start Y coordinate (default: center)',
1956
+ default: 400,
1957
+ },
1958
+ {
1959
+ name: 'distance',
1960
+ short: 'd',
1961
+ type: 'number',
1962
+ description: 'Swipe distance in pixels',
1963
+ default: 300,
1964
+ },
1756
1965
  ],
1757
1966
  action: async (ctx) => {
1758
1967
  const { client, sessionId } = await ensureConnected(_port);
@@ -1763,14 +1972,16 @@ const swipeCommand = {
1763
1972
  const startX = ctx.flags.x ?? 200;
1764
1973
  const startY = ctx.flags.y ?? 400;
1765
1974
  const positionalDistance = ctx.args[1] !== undefined ? parseInt(ctx.args[1], 10) : undefined;
1766
- const distance = (positionalDistance && Number.isFinite(positionalDistance)) ? positionalDistance : ctx.flags.distance ?? 300;
1975
+ const distance = positionalDistance && Number.isFinite(positionalDistance)
1976
+ ? positionalDistance
1977
+ : (ctx.flags.distance ?? 300);
1767
1978
  const dx = direction === 'right' ? distance : direction === 'left' ? -distance : 0;
1768
1979
  const dy = direction === 'down' ? distance : direction === 'up' ? -distance : 0;
1769
1980
  await client.send('Input.dispatchTouchEvent', { type: 'touchStart', touchPoints: [{ x: startX, y: startY }] }, sessionId);
1770
1981
  const steps = 10;
1771
1982
  for (let i = 1; i <= steps; i++) {
1772
- const x = Math.round(startX + dx * i / steps);
1773
- const y = Math.round(startY + dy * i / steps);
1983
+ const x = Math.round(startX + (dx * i) / steps);
1984
+ const y = Math.round(startY + (dy * i) / steps);
1774
1985
  await client.send('Input.dispatchTouchEvent', { type: 'touchMove', touchPoints: [{ x, y }] }, sessionId);
1775
1986
  await new Promise((r) => setTimeout(r, 16));
1776
1987
  }
@@ -1835,33 +2046,43 @@ const downloadCommand = {
1835
2046
  name: 'download',
1836
2047
  description: 'Click an element and capture the triggered file download. Usage: monomind browse download @e1 ./output.pdf',
1837
2048
  options: [
1838
- { name: 'timeout', short: 't', type: 'number', description: 'Max wait for download in ms', default: 30000 },
2049
+ {
2050
+ name: 'timeout',
2051
+ short: 't',
2052
+ type: 'number',
2053
+ description: 'Max wait for download in ms',
2054
+ default: 30000,
2055
+ },
1839
2056
  { name: 'json', type: 'boolean', description: 'Output result as JSON', default: false },
1840
2057
  ],
1841
2058
  action: async (ctx) => {
1842
2059
  const { client, sessionId } = await ensureConnected(_port);
1843
- const browser = await getBrowser();
2060
+ const _browser = await getBrowser();
1844
2061
  const refOrSel = ctx.args[0];
1845
2062
  const savePath = ctx.args[1];
1846
2063
  if (!refOrSel || !savePath)
1847
2064
  throw new Error('Usage: monomind browse download @e1|selector <save-path>');
1848
- const { mkdir } = await import('fs/promises');
1849
- const { dirname } = await import('path');
1850
- const { tmpdir } = await import('os');
1851
- const { join } = await import('path');
2065
+ const { mkdir } = await import('node:fs/promises');
2066
+ const { dirname } = await import('node:path');
2067
+ const { tmpdir } = await import('node:os');
2068
+ const { join } = await import('node:path');
1852
2069
  const downloadDir = join(tmpdir(), `mm-download-${Date.now()}`);
1853
2070
  await mkdir(downloadDir, { recursive: true });
1854
2071
  // Enable Page.downloadWillBegin / Page.downloadProgress events
1855
- await client.send('Browser.setDownloadBehavior', {
2072
+ await client
2073
+ .send('Browser.setDownloadBehavior', {
1856
2074
  behavior: 'allow',
1857
2075
  downloadPath: downloadDir,
1858
2076
  eventsEnabled: true,
1859
- }, undefined).catch(() => {
2077
+ }, undefined)
2078
+ .catch(() => {
1860
2079
  // Fallback: older Chrome API (session-scoped)
1861
- return client.send('Page.setDownloadBehavior', {
2080
+ return client
2081
+ .send('Page.setDownloadBehavior', {
1862
2082
  behavior: 'allow',
1863
2083
  downloadPath: downloadDir,
1864
- }, sessionId).catch(() => { });
2084
+ }, sessionId)
2085
+ .catch(() => { });
1865
2086
  });
1866
2087
  // Track when download completes
1867
2088
  const downloadPromise = new Promise((resolve, reject) => {
@@ -1873,13 +2094,20 @@ const downloadCommand = {
1873
2094
  let offProgress;
1874
2095
  // cleanup defined before setTimeout so the timeout callback can call it
1875
2096
  let timeout;
1876
- const cleanup = () => { clearTimeout(timeout); offBegin?.(); offProgress?.(); };
1877
- timeout = setTimeout(() => { cleanup(); reject(new Error('Download timed out')); }, ctx.flags.timeout);
2097
+ const cleanup = () => {
2098
+ clearTimeout(timeout);
2099
+ offBegin?.();
2100
+ offProgress?.();
2101
+ };
2102
+ timeout = setTimeout(() => {
2103
+ cleanup();
2104
+ reject(new Error('Download timed out'));
2105
+ }, ctx.flags.timeout);
1878
2106
  offProgress = client.on('Browser.downloadProgress', async (params) => {
1879
2107
  if (params.guid === guid && params.state === 'completed') {
1880
2108
  cleanup();
1881
2109
  // Find the downloaded file in downloadDir
1882
- const { readdir, rename, rmdir } = await import('fs/promises');
2110
+ const { readdir, rename, rmdir } = await import('node:fs/promises');
1883
2111
  const files = await readdir(downloadDir);
1884
2112
  if (files.length > 0) {
1885
2113
  const src = join(downloadDir, files[0]);
@@ -2071,9 +2299,7 @@ const frameCommand = {
2071
2299
  const tabCommand = {
2072
2300
  name: 'tab',
2073
2301
  description: 'Tab management. Usage: monomind browse tab list|new|close [url]',
2074
- options: [
2075
- { name: 'label', type: 'string', description: 'Label for new tab' },
2076
- ],
2302
+ options: [{ name: 'label', type: 'string', description: 'Label for new tab' }],
2077
2303
  action: async (ctx) => {
2078
2304
  const { client, sessionId } = await ensureConnected(_port);
2079
2305
  const browser = await getBrowser();
@@ -2102,19 +2328,25 @@ const tabCommand = {
2102
2328
  try {
2103
2329
  await browser.stopHarRecording(client, sid);
2104
2330
  }
2105
- catch { /* ignore */ }
2331
+ catch {
2332
+ /* ignore */
2333
+ }
2106
2334
  }
2107
2335
  if (browser.getTraceStatus(sid)) {
2108
2336
  try {
2109
2337
  await browser.stopTrace(client, sid);
2110
2338
  }
2111
- catch { /* ignore */ }
2339
+ catch {
2340
+ /* ignore */
2341
+ }
2112
2342
  }
2113
2343
  if (browser.isProfilingActive(sid)) {
2114
2344
  try {
2115
2345
  await browser.stopCpuProfile(client, sid);
2116
2346
  }
2117
- catch { /* ignore */ }
2347
+ catch {
2348
+ /* ignore */
2349
+ }
2118
2350
  }
2119
2351
  browser.teardownRouteInterception(sid);
2120
2352
  browser.stopRequestCapture(sid);
@@ -2142,19 +2374,25 @@ const tabCommand = {
2142
2374
  try {
2143
2375
  await browser.stopHarRecording(client, oldSid);
2144
2376
  }
2145
- catch { /* ignore */ }
2377
+ catch {
2378
+ /* ignore */
2379
+ }
2146
2380
  }
2147
2381
  if (browser.getTraceStatus(oldSid)) {
2148
2382
  try {
2149
2383
  await browser.stopTrace(client, oldSid);
2150
2384
  }
2151
- catch { /* ignore */ }
2385
+ catch {
2386
+ /* ignore */
2387
+ }
2152
2388
  }
2153
2389
  if (browser.isProfilingActive(oldSid)) {
2154
2390
  try {
2155
2391
  await browser.stopCpuProfile(client, oldSid);
2156
2392
  }
2157
- catch { /* ignore */ }
2393
+ catch {
2394
+ /* ignore */
2395
+ }
2158
2396
  }
2159
2397
  await browser.disableInterception(client, oldSid).catch(() => { });
2160
2398
  browser.stopRequestCapture(oldSid);
@@ -2192,19 +2430,25 @@ const windowCommand = {
2192
2430
  try {
2193
2431
  await browser.stopHarRecording(client, oldSid);
2194
2432
  }
2195
- catch { /* ignore */ }
2433
+ catch {
2434
+ /* ignore */
2435
+ }
2196
2436
  }
2197
2437
  if (browser.getTraceStatus(oldSid)) {
2198
2438
  try {
2199
2439
  await browser.stopTrace(client, oldSid);
2200
2440
  }
2201
- catch { /* ignore */ }
2441
+ catch {
2442
+ /* ignore */
2443
+ }
2202
2444
  }
2203
2445
  if (browser.isProfilingActive(oldSid)) {
2204
2446
  try {
2205
2447
  await browser.stopCpuProfile(client, oldSid);
2206
2448
  }
2207
- catch { /* ignore */ }
2449
+ catch {
2450
+ /* ignore */
2451
+ }
2208
2452
  }
2209
2453
  browser.teardownRouteInterception(oldSid);
2210
2454
  browser.stopRequestCapture(oldSid);
@@ -2366,11 +2610,13 @@ const cookiesCommand = {
2366
2610
  if (!name || value === undefined) {
2367
2611
  throw new Error('Usage: monomind browse cookies set <name> <value> [--domain <d>]');
2368
2612
  }
2369
- await browser.setCookies(client, sessionId, [{
2613
+ await browser.setCookies(client, sessionId, [
2614
+ {
2370
2615
  name,
2371
2616
  value,
2372
2617
  domain: ctx.flags.domain,
2373
- }]);
2618
+ },
2619
+ ]);
2374
2620
  output.printSuccess(`Cookie set: ${name}`);
2375
2621
  break;
2376
2622
  }
@@ -2406,9 +2652,7 @@ const pdfCommand = {
2406
2652
  const isCommand = {
2407
2653
  name: 'is',
2408
2654
  description: 'Check element state. Usage: monomind browse is visible|enabled|checked @e1',
2409
- options: [
2410
- { name: 'json', type: 'boolean', description: 'Output as JSON', default: false },
2411
- ],
2655
+ options: [{ name: 'json', type: 'boolean', description: 'Output as JSON', default: false }],
2412
2656
  action: async (ctx) => {
2413
2657
  const { client, sessionId } = await ensureConnected(_port);
2414
2658
  const browser = await getBrowser();
@@ -2429,7 +2673,8 @@ const isCommand = {
2429
2673
  case 'checked':
2430
2674
  result = await browser.isChecked(client, sessionId, ref);
2431
2675
  break;
2432
- default: throw new Error(`Unknown check: ${check}. Use: visible|enabled|checked`);
2676
+ default:
2677
+ throw new Error(`Unknown check: ${check}. Use: visible|enabled|checked`);
2433
2678
  }
2434
2679
  if (ctx.flags.json) {
2435
2680
  print(JSON.stringify({ data: { [check]: result } }));
@@ -2467,7 +2712,7 @@ const findCommand = {
2467
2712
  if (locator === 'alttext') {
2468
2713
  // I2: use JS attribute comparison to avoid broken CSS selectors for values with spaces/quotes
2469
2714
  const valJson = JSON.stringify(value);
2470
- const found = await browser.evaluateJs(client, sessionId, `(function(v){var el=document.querySelector('img[alt]')||null;var all=document.querySelectorAll('[alt]');for(var i=0;i<all.length;i++){if(all[i].getAttribute('alt')===v){all[i].setAttribute('data-mm-located','true');return true;}}return false;})(${valJson})`);
2715
+ const found = (await browser.evaluateJs(client, sessionId, `(function(v){var el=document.querySelector('img[alt]')||null;var all=document.querySelectorAll('[alt]');for(var i=0;i<all.length;i++){if(all[i].getAttribute('alt')===v){all[i].setAttribute('data-mm-located','true');return true;}}return false;})(${valJson})`));
2471
2716
  if (!found) {
2472
2717
  output.printWarning(`alttext not found: ${value}`);
2473
2718
  return { success: false };
@@ -2479,7 +2724,7 @@ const findCommand = {
2479
2724
  if (locator === 'title') {
2480
2725
  // I2: use JS attribute comparison to avoid broken CSS selectors for values with spaces/quotes
2481
2726
  const valJson = JSON.stringify(value);
2482
- const found = await browser.evaluateJs(client, sessionId, `(function(v){var all=document.querySelectorAll('[title]');for(var i=0;i<all.length;i++){if(all[i].getAttribute('title')===v){all[i].setAttribute('data-mm-located','true');return true;}}return false;})(${valJson})`);
2727
+ const found = (await browser.evaluateJs(client, sessionId, `(function(v){var all=document.querySelectorAll('[title]');for(var i=0;i<all.length;i++){if(all[i].getAttribute('title')===v){all[i].setAttribute('data-mm-located','true');return true;}}return false;})(${valJson})`));
2483
2728
  if (!found) {
2484
2729
  output.printWarning(`title not found: ${value}`);
2485
2730
  return { success: false };
@@ -2585,7 +2830,13 @@ const diffCommand = {
2585
2830
  name: 'diff',
2586
2831
  description: 'Compare two URLs or snapshots. Usage: monomind browse diff url <url1> <url2> [--interactive] [--json]',
2587
2832
  options: [
2588
- { name: 'interactive', short: 'i', type: 'boolean', description: 'Snapshot interactive elements only', default: false },
2833
+ {
2834
+ name: 'interactive',
2835
+ short: 'i',
2836
+ type: 'boolean',
2837
+ description: 'Snapshot interactive elements only',
2838
+ default: false,
2839
+ },
2589
2840
  { name: 'json', type: 'boolean', description: 'Output as JSON', default: false },
2590
2841
  ],
2591
2842
  action: async (ctx) => {
@@ -2600,11 +2851,15 @@ const diffCommand = {
2600
2851
  // Capture snapshot at url1
2601
2852
  await browser.openUrl(client, sessionId, url1);
2602
2853
  await browser.waitFor(client, sessionId, { load: 'load', timeout: 15000 });
2603
- const snap1 = await browser.captureSnapshot(client, sessionId, { interactiveOnly: ctx.flags.interactive });
2854
+ const snap1 = await browser.captureSnapshot(client, sessionId, {
2855
+ interactiveOnly: ctx.flags.interactive,
2856
+ });
2604
2857
  // Capture snapshot at url2
2605
2858
  await browser.openUrl(client, sessionId, url2);
2606
2859
  await browser.waitFor(client, sessionId, { load: 'load', timeout: 15000 });
2607
- const snap2 = await browser.captureSnapshot(client, sessionId, { interactiveOnly: ctx.flags.interactive });
2860
+ const snap2 = await browser.captureSnapshot(client, sessionId, {
2861
+ interactiveOnly: ctx.flags.interactive,
2862
+ });
2608
2863
  _refs = snap2.refs;
2609
2864
  await browser.saveRefCache(_targetId, snap2.url, _refs);
2610
2865
  // Text diff
@@ -2616,7 +2871,15 @@ const diffCommand = {
2616
2871
  const onlyIn2 = lines2.filter((l) => !set1.has(l));
2617
2872
  const changed = onlyIn1.length > 0 || onlyIn2.length > 0;
2618
2873
  if (ctx.flags.json) {
2619
- print(JSON.stringify({ changed, url1, url2, onlyIn1, onlyIn2, additions: onlyIn2.length, removals: onlyIn1.length }));
2874
+ print(JSON.stringify({
2875
+ changed,
2876
+ url1,
2877
+ url2,
2878
+ onlyIn1,
2879
+ onlyIn2,
2880
+ additions: onlyIn2.length,
2881
+ removals: onlyIn1.length,
2882
+ }));
2620
2883
  }
2621
2884
  else {
2622
2885
  if (!changed) {
@@ -2630,7 +2893,10 @@ const diffCommand = {
2630
2893
  print(`\x1b[32m+ ${l}\x1b[0m`);
2631
2894
  }
2632
2895
  }
2633
- return { success: true, data: { changed, url1, url2, additions: onlyIn2.length, removals: onlyIn1.length } };
2896
+ return {
2897
+ success: true,
2898
+ data: { changed, url1, url2, additions: onlyIn2.length, removals: onlyIn1.length },
2899
+ };
2634
2900
  }
2635
2901
  throw new Error('Usage: monomind browse diff url <url1> <url2>');
2636
2902
  },
@@ -2744,7 +3010,6 @@ export function parseBatchCommandLine(cmdStr) {
2744
3010
  flags[boolFlag[1]] = true;
2745
3011
  rest = rest.slice(boolFlag[0].length);
2746
3012
  consumedAnother = true;
2747
- continue;
2748
3013
  }
2749
3014
  }
2750
3015
  return { subName: 'eval', subArgs: [rest], flags };
@@ -2803,7 +3068,9 @@ const batchCommand = {
2803
3068
  parsedFlags[key] = true;
2804
3069
  }
2805
3070
  }
2806
- else if (subArgs[i].startsWith('-') && subArgs[i].length === 2 && /[a-zA-Z]/.test(subArgs[i][1])) {
3071
+ else if (subArgs[i].startsWith('-') &&
3072
+ subArgs[i].length === 2 &&
3073
+ /[a-zA-Z]/.test(subArgs[i][1])) {
2807
3074
  consumedIndices.add(i);
2808
3075
  const shortKey = subArgs[i][1];
2809
3076
  const optDef = subCmd.options?.find((o) => o.short === shortKey);
@@ -2835,7 +3102,11 @@ const batchCommand = {
2835
3102
  };
2836
3103
  const cmdResult = await subCmd.action(fakeCtx);
2837
3104
  const succeeded = cmdResult?.success !== false;
2838
- results.push({ command: cmdStr, success: succeeded, error: succeeded ? undefined : 'Command returned failure' });
3105
+ results.push({
3106
+ command: cmdStr,
3107
+ success: succeeded,
3108
+ error: succeeded ? undefined : 'Command returned failure',
3109
+ });
2839
3110
  if (!succeeded && ctx.flags.bail)
2840
3111
  break;
2841
3112
  }
@@ -2886,7 +3157,12 @@ const connectCommand = {
2886
3157
  options: [
2887
3158
  { name: 'port', short: 'p', type: 'number', description: 'CDP port', default: 9222 },
2888
3159
  { name: 'target', type: 'string', description: 'Target ID to attach to' },
2889
- { name: 'auto-connect', type: 'boolean', description: 'Auto-discover running Chrome on ports 9222 and 9229', default: false },
3160
+ {
3161
+ name: 'auto-connect',
3162
+ type: 'boolean',
3163
+ description: 'Auto-discover running Chrome on ports 9222 and 9229',
3164
+ default: false,
3165
+ },
2890
3166
  ],
2891
3167
  action: async (ctx) => {
2892
3168
  let port = ctx.flags.port ?? 9222;
@@ -2902,7 +3178,9 @@ const connectCommand = {
2902
3178
  break;
2903
3179
  }
2904
3180
  }
2905
- catch { /* port not open */ }
3181
+ catch {
3182
+ /* port not open */
3183
+ }
2906
3184
  }
2907
3185
  if (!found)
2908
3186
  throw new Error('No running Chrome instance found. Launch Chrome with --remote-debugging-port or use --port.');
@@ -2915,19 +3193,25 @@ const connectCommand = {
2915
3193
  try {
2916
3194
  await browser.stopHarRecording(prevClient, prevSid);
2917
3195
  }
2918
- catch { /* ignore */ }
3196
+ catch {
3197
+ /* ignore */
3198
+ }
2919
3199
  }
2920
3200
  if (browser.getTraceStatus(prevSid)) {
2921
3201
  try {
2922
3202
  await browser.stopTrace(prevClient, prevSid);
2923
3203
  }
2924
- catch { /* ignore */ }
3204
+ catch {
3205
+ /* ignore */
3206
+ }
2925
3207
  }
2926
3208
  if (browser.isProfilingActive(prevSid)) {
2927
3209
  try {
2928
3210
  await browser.stopCpuProfile(prevClient, prevSid);
2929
3211
  }
2930
- catch { /* ignore */ }
3212
+ catch {
3213
+ /* ignore */
3214
+ }
2931
3215
  }
2932
3216
  browser.teardownRouteInterception(prevSid);
2933
3217
  browser.stopRequestCapture(prevSid);
@@ -3030,7 +3314,9 @@ const traceCommand = {
3030
3314
  throw new Error('Usage: monomind browse trace start|stop [path]');
3031
3315
  switch (action) {
3032
3316
  case 'start':
3033
- await browser.startTrace(client, sessionId, { screenshots: ctx.flags.screenshots });
3317
+ await browser.startTrace(client, sessionId, {
3318
+ screenshots: ctx.flags.screenshots,
3319
+ });
3034
3320
  output.printSuccess('Trace started');
3035
3321
  return { success: true };
3036
3322
  case 'stop': {
@@ -3064,7 +3350,9 @@ const profilerCommand = {
3064
3350
  throw new Error('Usage: monomind browse profiler start|stop|heap [path]');
3065
3351
  switch (action) {
3066
3352
  case 'start':
3067
- await browser.startCpuProfile(client, sessionId, { samplingInterval: ctx.flags.interval });
3353
+ await browser.startCpuProfile(client, sessionId, {
3354
+ samplingInterval: ctx.flags.interval,
3355
+ });
3068
3356
  output.printSuccess('CPU profiler started');
3069
3357
  return { success: true };
3070
3358
  case 'stop': {
@@ -3155,7 +3443,7 @@ const resizeCommand = {
3155
3443
  const browser = await getBrowser();
3156
3444
  const width = parseInt(ctx.args[0], 10);
3157
3445
  const height = parseInt(ctx.args[1], 10);
3158
- if (isNaN(width) || isNaN(height))
3446
+ if (Number.isNaN(width) || Number.isNaN(height))
3159
3447
  throw new Error('Usage: monomind browse resize <width> <height>');
3160
3448
  await browser.setViewport(client, sessionId, width, height);
3161
3449
  output.printSuccess(`Resized to ${width}x${height}`);
@@ -3303,7 +3591,10 @@ const browseCommand = {
3303
3591
  ],
3304
3592
  examples: [
3305
3593
  { command: 'monomind browse open https://example.com', description: 'Open a URL' },
3306
- { command: 'monomind browse snapshot -i', description: 'Interactive-only snapshot (93% token reduction)' },
3594
+ {
3595
+ command: 'monomind browse snapshot -i',
3596
+ description: 'Interactive-only snapshot (93% token reduction)',
3597
+ },
3307
3598
  { command: 'monomind browse click @e3', description: 'Click element by ref' },
3308
3599
  { command: 'monomind browse fill @e1 "user@example.com"', description: 'Fill an input' },
3309
3600
  { command: 'monomind browse press Enter', description: 'Press Enter key' },
@@ -3317,7 +3608,10 @@ const browseCommand = {
3317
3608
  { command: 'monomind browse state save my-session', description: 'Save session state' },
3318
3609
  { command: 'monomind browse navigate back', description: 'Navigate back' },
3319
3610
  { command: 'monomind browse eval "document.title"', description: 'Evaluate JavaScript' },
3320
- { command: 'monomind browse network route --pattern "https://api.*" --abort', description: 'Abort API calls' },
3611
+ {
3612
+ command: 'monomind browse network route --pattern "https://api.*" --abort',
3613
+ description: 'Abort API calls',
3614
+ },
3321
3615
  { command: 'monomind browse close', description: 'Close browser session' },
3322
3616
  ],
3323
3617
  action: async (_ctx) => {