@monoes/monomindcli 1.10.33 → 1.10.35

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 (56) hide show
  1. package/.claude/helpers/intelligence.cjs +3 -2
  2. package/.claude/helpers/session.cjs +2 -1
  3. package/dist/src/browser/browser.d.ts.map +1 -1
  4. package/dist/src/browser/browser.js +16 -12
  5. package/dist/src/browser/browser.js.map +1 -1
  6. package/dist/src/browser/cdp.d.ts +1 -1
  7. package/dist/src/browser/cdp.d.ts.map +1 -1
  8. package/dist/src/browser/cdp.js +4 -2
  9. package/dist/src/browser/cdp.js.map +1 -1
  10. package/dist/src/browser/find.d.ts +1 -0
  11. package/dist/src/browser/find.d.ts.map +1 -1
  12. package/dist/src/browser/find.js +12 -0
  13. package/dist/src/browser/find.js.map +1 -1
  14. package/dist/src/browser/har.d.ts +26 -0
  15. package/dist/src/browser/har.d.ts.map +1 -0
  16. package/dist/src/browser/har.js +130 -0
  17. package/dist/src/browser/har.js.map +1 -0
  18. package/dist/src/browser/index.d.ts +5 -0
  19. package/dist/src/browser/index.d.ts.map +1 -1
  20. package/dist/src/browser/index.js +5 -0
  21. package/dist/src/browser/index.js.map +1 -1
  22. package/dist/src/browser/network.d.ts +15 -0
  23. package/dist/src/browser/network.d.ts.map +1 -1
  24. package/dist/src/browser/network.js +54 -0
  25. package/dist/src/browser/network.js.map +1 -1
  26. package/dist/src/browser/profiler.d.ts +10 -0
  27. package/dist/src/browser/profiler.d.ts.map +1 -0
  28. package/dist/src/browser/profiler.js +55 -0
  29. package/dist/src/browser/profiler.js.map +1 -0
  30. package/dist/src/browser/record.d.ts +21 -0
  31. package/dist/src/browser/record.d.ts.map +1 -0
  32. package/dist/src/browser/record.js +48 -0
  33. package/dist/src/browser/record.js.map +1 -0
  34. package/dist/src/browser/snapshot.d.ts.map +1 -1
  35. package/dist/src/browser/snapshot.js +23 -2
  36. package/dist/src/browser/snapshot.js.map +1 -1
  37. package/dist/src/browser/trace.d.ts +10 -0
  38. package/dist/src/browser/trace.d.ts.map +1 -0
  39. package/dist/src/browser/trace.js +72 -0
  40. package/dist/src/browser/trace.js.map +1 -0
  41. package/dist/src/browser/types.d.ts +1 -0
  42. package/dist/src/browser/types.d.ts.map +1 -1
  43. package/dist/src/browser/types.js.map +1 -1
  44. package/dist/src/browser/vitals.d.ts +15 -0
  45. package/dist/src/browser/vitals.d.ts.map +1 -0
  46. package/dist/src/browser/vitals.js +116 -0
  47. package/dist/src/browser/vitals.js.map +1 -0
  48. package/dist/src/browser/wait.js +1 -1
  49. package/dist/src/browser/wait.js.map +1 -1
  50. package/dist/src/commands/browse.d.ts.map +1 -1
  51. package/dist/src/commands/browse.js +426 -23
  52. package/dist/src/commands/browse.js.map +1 -1
  53. package/dist/src/ui/.monomind/sessions/current.json +1 -1
  54. package/dist/src/ui/dashboard-v2.html +245 -40
  55. package/dist/tsconfig.tsbuildinfo +1 -1
  56. package/package.json +1 -1
@@ -71,6 +71,8 @@ const snapshotCommand = {
71
71
  { name: 'interactive', short: 'i', type: 'boolean', description: 'Interactive elements only (93% token reduction)', default: false },
72
72
  { name: 'compact', short: 'c', type: 'boolean', description: 'Compact output format', default: false },
73
73
  { name: 'json', type: 'boolean', description: 'Output as JSON', default: false },
74
+ { name: 'depth', short: 'd', type: 'number', description: 'Max depth of AX tree to show' },
75
+ { name: 'selector', short: 's', type: 'string', description: 'Scope snapshot to a CSS selector' },
74
76
  ],
75
77
  action: async (ctx) => {
76
78
  const { client, sessionId } = await ensureConnected(_port);
@@ -78,6 +80,8 @@ const snapshotCommand = {
78
80
  const result = await browser.captureSnapshot(client, sessionId, {
79
81
  interactiveOnly: ctx.flags.interactive,
80
82
  compact: ctx.flags.compact,
83
+ maxDepth: ctx.flags.depth,
84
+ selector: ctx.flags.selector,
81
85
  });
82
86
  _refs = result.refs;
83
87
  if (ctx.flags.json) {
@@ -158,8 +162,10 @@ const waitCommand = {
158
162
  options: [
159
163
  { name: 'url', type: 'string', description: 'Wait for URL matching glob pattern' },
160
164
  { name: 'text', type: 'string', description: 'Wait for text to appear in page' },
165
+ { name: 'not-text', type: 'string', description: 'Wait for text to disappear from page' },
161
166
  { name: 'selector', type: 'string', description: 'Wait for CSS selector to appear' },
162
167
  { name: 'load', type: 'string', description: 'Wait for load event: load|networkidle|domcontentloaded' },
168
+ { name: 'fn', type: 'string', description: 'Wait until JS expression returns truthy' },
163
169
  { name: 'ms', type: 'number', description: 'Wait N milliseconds' },
164
170
  { name: 'timeout', short: 't', type: 'number', description: 'Timeout in ms', default: 30000 },
165
171
  ],
@@ -171,6 +177,36 @@ const waitCommand = {
171
177
  output.printSuccess(`Waited ${ctx.flags.ms}ms`);
172
178
  return { success: true };
173
179
  }
180
+ if (ctx.flags.fn) {
181
+ const expr = ctx.flags.fn;
182
+ const timeout = ctx.flags.timeout ?? 30000;
183
+ const interval = 200;
184
+ const deadline = Date.now() + timeout;
185
+ while (Date.now() < deadline) {
186
+ const result = await browser.evaluateJs(client, sessionId, expr);
187
+ if (result) {
188
+ output.printSuccess('Wait function returned truthy');
189
+ return { success: true };
190
+ }
191
+ await new Promise((r) => setTimeout(r, interval));
192
+ }
193
+ throw new Error(`Timeout waiting for --fn: ${expr}`);
194
+ }
195
+ if (ctx.flags['not-text']) {
196
+ const target = ctx.flags['not-text'];
197
+ const timeout = ctx.flags.timeout ?? 30000;
198
+ const interval = 200;
199
+ const deadline = Date.now() + timeout;
200
+ while (Date.now() < deadline) {
201
+ const text = await browser.evaluateJs(client, sessionId, 'document.body.innerText');
202
+ if (!text.includes(target)) {
203
+ output.printSuccess('Text disappeared');
204
+ return { success: true };
205
+ }
206
+ await new Promise((r) => setTimeout(r, interval));
207
+ }
208
+ throw new Error(`Timeout waiting for text to disappear: "${target}"`);
209
+ }
174
210
  await browser.waitFor(client, sessionId, {
175
211
  url: ctx.flags.url,
176
212
  text: ctx.flags.text,
@@ -211,7 +247,7 @@ const screenshotCommand = {
211
247
  };
212
248
  const getCommand = {
213
249
  name: 'get',
214
- description: 'Get page info. Usage: monomind browse get url|title|text|html [@ref]',
250
+ description: 'Get page info. Usage: monomind browse get url|title|text|html|value|attr|count|box|styles [@ref] [attrName]',
215
251
  options: [
216
252
  { name: 'json', type: 'boolean', description: 'Output as JSON', default: false },
217
253
  ],
@@ -220,7 +256,7 @@ const getCommand = {
220
256
  const browser = await getBrowser();
221
257
  const what = ctx.args[0];
222
258
  if (!what)
223
- throw new Error('Usage: monomind browse get url|title|text|html');
259
+ throw new Error('Usage: monomind browse get url|title|text|html|value|attr|count|box|styles');
224
260
  let value;
225
261
  switch (what) {
226
262
  case 'url':
@@ -254,14 +290,92 @@ const getCommand = {
254
290
  case 'html':
255
291
  value = (await browser.evaluateJs(client, sessionId, 'document.documentElement.outerHTML'));
256
292
  break;
293
+ case 'value': {
294
+ const refArg = ctx.args[1];
295
+ if (!refArg)
296
+ throw new Error('Usage: monomind browse get value @ref');
297
+ const refKey = refArg.startsWith('@') ? refArg.slice(1) : refArg;
298
+ const ref = _refs.get(refKey);
299
+ if (!ref)
300
+ throw new Error(`Ref @${refKey} not found`);
301
+ const objectId = await browser.getObjectIdForRef(client, sessionId, ref);
302
+ if (!objectId)
303
+ throw new Error('Element not in DOM');
304
+ const r = await client.send('Runtime.callFunctionOn', {
305
+ functionDeclaration: 'function() { return this.value ?? null; }',
306
+ objectId, returnByValue: true,
307
+ }, sessionId);
308
+ value = r.result?.value ?? null;
309
+ break;
310
+ }
311
+ case 'attr': {
312
+ const refArg = ctx.args[1];
313
+ const attrName = ctx.args[2];
314
+ if (!refArg || !attrName)
315
+ throw new Error('Usage: monomind browse get attr @ref <attrName>');
316
+ const refKey = refArg.startsWith('@') ? refArg.slice(1) : refArg;
317
+ const ref = _refs.get(refKey);
318
+ if (!ref)
319
+ throw new Error(`Ref @${refKey} not found`);
320
+ const objectId = await browser.getObjectIdForRef(client, sessionId, ref);
321
+ if (!objectId)
322
+ throw new Error('Element not in DOM');
323
+ const r = await client.send('Runtime.callFunctionOn', {
324
+ functionDeclaration: `function() { return this.getAttribute(${JSON.stringify(attrName)}); }`,
325
+ objectId, returnByValue: true,
326
+ }, sessionId);
327
+ value = r.result?.value ?? null;
328
+ break;
329
+ }
330
+ case 'count': {
331
+ const selector = ctx.args[1];
332
+ if (!selector)
333
+ throw new Error('Usage: monomind browse get count <cssSelector>');
334
+ value = await browser.evaluateJs(client, sessionId, `document.querySelectorAll(${JSON.stringify(selector)}).length`);
335
+ break;
336
+ }
337
+ case 'box': {
338
+ const refArg = ctx.args[1];
339
+ if (!refArg)
340
+ throw new Error('Usage: monomind browse get box @ref');
341
+ const refKey = refArg.startsWith('@') ? refArg.slice(1) : refArg;
342
+ const ref = _refs.get(refKey);
343
+ if (!ref)
344
+ throw new Error(`Ref @${refKey} not found`);
345
+ value = await browser.getElementBox(client, sessionId, ref);
346
+ break;
347
+ }
348
+ case 'styles': {
349
+ const refArg = ctx.args[1];
350
+ if (!refArg)
351
+ throw new Error('Usage: monomind browse get styles @ref');
352
+ const refKey = refArg.startsWith('@') ? refArg.slice(1) : refArg;
353
+ const ref = _refs.get(refKey);
354
+ if (!ref)
355
+ throw new Error(`Ref @${refKey} not found`);
356
+ const objectId = await browser.getObjectIdForRef(client, sessionId, ref);
357
+ if (!objectId)
358
+ throw new Error('Element not in DOM');
359
+ const r = await client.send('Runtime.callFunctionOn', {
360
+ functionDeclaration: 'function() { const s = window.getComputedStyle(this); return JSON.stringify(Object.fromEntries([...s].map(k => [k, s.getPropertyValue(k)]))); }',
361
+ objectId, returnByValue: true,
362
+ }, sessionId);
363
+ try {
364
+ value = JSON.parse(r.result?.value ?? '{}');
365
+ }
366
+ catch {
367
+ value = {};
368
+ }
369
+ break;
370
+ }
257
371
  default:
258
- throw new Error(`Unknown: ${what}. Use: url|title|text|html`);
372
+ throw new Error(`Unknown: ${what}. Use: url|title|text|html|value|attr|count|box|styles`);
259
373
  }
260
374
  if (ctx.flags.json) {
261
375
  print(JSON.stringify({ data: { [what]: value } }));
262
376
  }
263
377
  else {
264
- print(value);
378
+ print(typeof value === 'object' ? JSON.stringify(value, null, 2) : String(value ?? ''));
265
379
  }
266
380
  return { success: true, data: { [what]: value } };
267
381
  },
@@ -329,10 +443,13 @@ const setCommand = {
329
443
  case 'viewport': {
330
444
  const width = parseInt(ctx.args[1], 10);
331
445
  const height = parseInt(ctx.args[2], 10);
446
+ const dpr = parseFloat(ctx.args[3]) || undefined;
332
447
  if (isNaN(width) || isNaN(height))
333
- throw new Error('Usage: set viewport <width> <height>');
334
- await browser.setViewport(client, sessionId, width, height);
335
- output.printSuccess(`Viewport set to ${width}x${height}`);
448
+ throw new Error('Usage: set viewport <width> <height> [dpr]');
449
+ await client.send('Emulation.setDeviceMetricsOverride', {
450
+ width, height, deviceScaleFactor: dpr ?? 1, mobile: false,
451
+ }, sessionId);
452
+ output.printSuccess(`Viewport set to ${width}x${height}${dpr ? ` @${dpr}x` : ''}`);
336
453
  break;
337
454
  }
338
455
  case 'device': {
@@ -442,8 +559,27 @@ const stateCommand = {
442
559
  output.printSuccess(`State loaded from ${target}`);
443
560
  return { success: true };
444
561
  }
562
+ case 'show': {
563
+ const { client: c, sessionId: sid } = await ensureConnected(_port);
564
+ const url = await browser.getCurrentUrl(c, sid);
565
+ const title = await browser.getCurrentTitle(c, sid);
566
+ const cookies = await browser.getCookies(c, sid);
567
+ const ls = await browser.getAllLocalStorage(c, sid);
568
+ const info = { url, title, cookies: cookies.length, localStorage: Object.keys(ls).length, refs: _refs.size };
569
+ print(JSON.stringify(info, null, 2));
570
+ return { success: true, data: info };
571
+ }
572
+ case 'clear': {
573
+ const { client: c, sessionId: sid } = await ensureConnected(_port);
574
+ await browser.clearCookies(c, sid);
575
+ await browser.clearLocalStorage(c, sid);
576
+ await browser.clearSessionStorage(c, sid);
577
+ _refs = new Map();
578
+ output.printSuccess('Browser state cleared (cookies, localStorage, sessionStorage, refs)');
579
+ return { success: true };
580
+ }
445
581
  default:
446
- throw new Error(`Unknown action: ${action}. Use: save|load|list`);
582
+ throw new Error(`Unknown action: ${action}. Use: save|load|list|show|clear`);
447
583
  }
448
584
  },
449
585
  };
@@ -456,13 +592,14 @@ const networkCommand = {
456
592
  { name: 'fulfill', type: 'string', description: 'JSON response body' },
457
593
  { name: 'status', type: 'number', description: 'HTTP status for fulfill', default: 200 },
458
594
  { name: 'headers', type: 'string', description: 'JSON headers object' },
595
+ { name: 'json', type: 'boolean', description: 'Output as JSON', default: false },
459
596
  ],
460
597
  action: async (ctx) => {
461
598
  const { client, sessionId } = await ensureConnected(_port);
462
599
  const browser = await getBrowser();
463
600
  const action = ctx.args[0];
464
601
  if (!action)
465
- throw new Error('Usage: monomind browse network route|cookies|headers');
602
+ throw new Error('Usage: monomind browse network route|unroute|cookies|headers|requests');
466
603
  switch (action) {
467
604
  case 'route': {
468
605
  const pattern = ctx.flags.pattern;
@@ -481,6 +618,10 @@ const networkCommand = {
481
618
  output.printSuccess(`Network route set: ${pattern}`);
482
619
  break;
483
620
  }
621
+ case 'unroute':
622
+ await browser.disableInterception(client, sessionId);
623
+ output.printSuccess('Network interception disabled');
624
+ break;
484
625
  case 'cookies': {
485
626
  const cookies = await browser.getCookies(client, sessionId);
486
627
  print(JSON.stringify(cookies, null, 2));
@@ -494,8 +635,38 @@ const networkCommand = {
494
635
  output.printSuccess('Extra headers set');
495
636
  break;
496
637
  }
638
+ case 'capture': {
639
+ const subAction = ctx.args[1] ?? 'start';
640
+ if (subAction === 'start') {
641
+ browser.startRequestCapture(client, sessionId);
642
+ output.printSuccess('Request capture started');
643
+ }
644
+ else if (subAction === 'stop') {
645
+ browser.stopRequestCapture(sessionId);
646
+ output.printSuccess('Request capture stopped');
647
+ }
648
+ else if (subAction === 'clear') {
649
+ browser.clearCapturedRequests(sessionId);
650
+ output.printSuccess('Captured requests cleared');
651
+ }
652
+ break;
653
+ }
654
+ case 'requests': {
655
+ const reqs = browser.getCapturedRequests(sessionId);
656
+ if (ctx.flags.json)
657
+ print(JSON.stringify({ data: reqs }));
658
+ else {
659
+ if (reqs.length === 0) {
660
+ output.printInfo('No captured requests. Run: network capture start');
661
+ }
662
+ else
663
+ for (const r of reqs)
664
+ print(` ${r.method ?? 'GET'} ${r.status ?? '-'} ${r.url}`);
665
+ }
666
+ return { success: true, data: { requests: reqs } };
667
+ }
497
668
  default:
498
- throw new Error(`Unknown: ${action}. Use: route|cookies|headers`);
669
+ throw new Error(`Unknown: ${action}. Use: route|unroute|cookies|headers|capture|requests`);
499
670
  }
500
671
  return { success: true };
501
672
  },
@@ -1066,17 +1237,18 @@ const cookiesCommand = {
1066
1237
  return { success: true, data: { cookies } };
1067
1238
  }
1068
1239
  case 'set': {
1069
- if (ctx.flags.name && ctx.flags.value) {
1070
- await browser.setCookies(client, sessionId, [{
1071
- name: ctx.flags.name,
1072
- value: ctx.flags.value,
1073
- domain: ctx.flags.domain,
1074
- }]);
1075
- output.printSuccess(`Cookie set: ${ctx.flags.name}`);
1076
- }
1077
- else {
1078
- throw new Error('Usage: monomind browse cookies set --name <n> --value <v>');
1240
+ // Support both: cookies set --name n --value v AND cookies set <name> <value>
1241
+ const name = ctx.flags.name ?? ctx.args[1];
1242
+ const value = ctx.flags.value ?? ctx.args[2];
1243
+ if (!name || value === undefined) {
1244
+ throw new Error('Usage: monomind browse cookies set <name> <value> [--domain <d>]');
1079
1245
  }
1246
+ await browser.setCookies(client, sessionId, [{
1247
+ name,
1248
+ value,
1249
+ domain: ctx.flags.domain,
1250
+ }]);
1251
+ output.printSuccess(`Cookie set: ${name}`);
1080
1252
  break;
1081
1253
  }
1082
1254
  case 'clear':
@@ -1147,7 +1319,7 @@ const isCommand = {
1147
1319
  };
1148
1320
  const findCommand = {
1149
1321
  name: 'find',
1150
- description: 'Find elements by semantic locators. Usage: monomind browse find role|text|label|testid <value> [action]',
1322
+ description: 'Find elements by semantic locators. Usage: monomind browse find role|text|label|placeholder|testid|selector <value> [action]',
1151
1323
  options: [
1152
1324
  { name: 'name', type: 'string', description: 'Filter by accessible name' },
1153
1325
  { name: 'exact', type: 'boolean', description: 'Require exact match', default: false },
@@ -1161,7 +1333,7 @@ const findCommand = {
1161
1333
  const value = ctx.args[1];
1162
1334
  const action = ctx.args[2];
1163
1335
  if (!locator || !value)
1164
- throw new Error('Usage: monomind browse find role|text|label|testid <value> [action]');
1336
+ throw new Error('Usage: monomind browse find role|text|label|placeholder|testid|selector <value> [action]');
1165
1337
  const opts = {
1166
1338
  name: ctx.flags.name,
1167
1339
  exact: ctx.flags.exact,
@@ -1179,6 +1351,18 @@ const findCommand = {
1179
1351
  case 'label':
1180
1352
  ref = await browser.findByLabel(client, sessionId, _refs, value, opts);
1181
1353
  break;
1354
+ case 'placeholder':
1355
+ ref = await browser.findByPlaceholder(client, sessionId, _refs, value, opts);
1356
+ break;
1357
+ case 'selector': {
1358
+ const html = await browser.findBySelector(client, sessionId, value, opts);
1359
+ if (!html) {
1360
+ output.printWarning(`selector not found: ${value}`);
1361
+ return { success: false };
1362
+ }
1363
+ output.printSuccess(`Found: ${value}`);
1364
+ return { success: true, data: { html } };
1365
+ }
1182
1366
  case 'testid': {
1183
1367
  const sel = await browser.findByTestId(client, sessionId, value);
1184
1368
  if (!sel) {
@@ -1189,7 +1373,7 @@ const findCommand = {
1189
1373
  return { success: true, data: { selector: sel } };
1190
1374
  }
1191
1375
  default:
1192
- throw new Error(`Unknown locator: ${locator}. Use: role|text|label|testid`);
1376
+ throw new Error(`Unknown locator: ${locator}. Use: role|text|label|placeholder|testid|selector`);
1193
1377
  }
1194
1378
  if (!ref) {
1195
1379
  output.printWarning(`No element found: ${locator}="${value}"`);
@@ -1360,6 +1544,218 @@ const removeinitscriptCommand = {
1360
1544
  return { success: true };
1361
1545
  },
1362
1546
  };
1547
+ const connectCommand = {
1548
+ name: 'connect',
1549
+ description: 'Connect to existing Chrome instance. Usage: monomind browse connect [--port 9222] [--target <id>]',
1550
+ options: [
1551
+ { name: 'port', short: 'p', type: 'number', description: 'CDP port', default: 9222 },
1552
+ { name: 'target', type: 'string', description: 'Target ID to attach to' },
1553
+ ],
1554
+ action: async (ctx) => {
1555
+ const port = ctx.flags.port ?? 9222;
1556
+ const browser = await getBrowser();
1557
+ const conn = await browser.connectToTarget(port, ctx.flags.target);
1558
+ _client = conn.client;
1559
+ _sessionId = conn.sessionId;
1560
+ _targetId = conn.target.id;
1561
+ _port = port;
1562
+ _refs = new Map();
1563
+ const url = await browser.getCurrentUrl(_client, _sessionId);
1564
+ const title = await browser.getCurrentTitle(_client, _sessionId);
1565
+ output.printSuccess(`Connected: ${title} (${url})`);
1566
+ return { success: true, data: { targetId: _targetId, url, title } };
1567
+ },
1568
+ };
1569
+ const recordCommand = {
1570
+ name: 'record',
1571
+ description: 'Screen recording. Usage: monomind browse record start|stop|status [path]',
1572
+ options: [
1573
+ { name: 'format', type: 'string', description: 'jpeg|png', default: 'jpeg' },
1574
+ { name: 'quality', type: 'number', description: 'Quality 0-100', default: 80 },
1575
+ { name: 'json', type: 'boolean', description: 'Output as JSON', default: false },
1576
+ ],
1577
+ action: async (ctx) => {
1578
+ const { client, sessionId } = await ensureConnected(_port);
1579
+ const browser = await getBrowser();
1580
+ const action = ctx.args[0];
1581
+ if (!action)
1582
+ throw new Error('Usage: monomind browse record start|stop|status');
1583
+ switch (action) {
1584
+ case 'start':
1585
+ await browser.startRecording(client, sessionId, {
1586
+ format: ctx.flags.format,
1587
+ quality: ctx.flags.quality,
1588
+ });
1589
+ output.printSuccess('Recording started');
1590
+ return { success: true };
1591
+ case 'stop': {
1592
+ const path = await browser.stopRecording(client, sessionId, ctx.args[1]);
1593
+ if (ctx.flags.json)
1594
+ print(JSON.stringify({ data: { path } }));
1595
+ else
1596
+ output.printSuccess(`Recording saved: ${path}`);
1597
+ return { success: true, data: { path } };
1598
+ }
1599
+ case 'status': {
1600
+ const status = browser.getRecordingStatus(sessionId);
1601
+ if (ctx.flags.json)
1602
+ print(JSON.stringify({ data: status }));
1603
+ else
1604
+ print(`Recording: ${status.recording} | Frames: ${status.frames}`);
1605
+ return { success: true, data: status };
1606
+ }
1607
+ default:
1608
+ throw new Error('Usage: monomind browse record start|stop|status [path]');
1609
+ }
1610
+ },
1611
+ };
1612
+ const traceCommand = {
1613
+ name: 'trace',
1614
+ description: 'CDP performance trace. Usage: monomind browse trace start|stop [path]',
1615
+ options: [
1616
+ { name: 'screenshots', type: 'boolean', description: 'Include screenshots', default: false },
1617
+ { name: 'json', type: 'boolean', description: 'Output as JSON', default: false },
1618
+ ],
1619
+ action: async (ctx) => {
1620
+ const { client, sessionId } = await ensureConnected(_port);
1621
+ const browser = await getBrowser();
1622
+ const action = ctx.args[0];
1623
+ if (!action)
1624
+ throw new Error('Usage: monomind browse trace start|stop [path]');
1625
+ switch (action) {
1626
+ case 'start':
1627
+ await browser.startTrace(client, sessionId, { screenshots: ctx.flags.screenshots });
1628
+ output.printSuccess('Trace started');
1629
+ return { success: true };
1630
+ case 'stop': {
1631
+ const path = await browser.stopTrace(client, sessionId, ctx.args[1]);
1632
+ if (ctx.flags.json)
1633
+ print(JSON.stringify({ data: { path } }));
1634
+ else
1635
+ output.printSuccess(`Trace saved: ${path}`);
1636
+ return { success: true, data: { path } };
1637
+ }
1638
+ case 'status':
1639
+ print(browser.getTraceStatus(sessionId) ? 'Tracing active' : 'Not tracing');
1640
+ return { success: true };
1641
+ default:
1642
+ throw new Error('Usage: monomind browse trace start|stop|status [path]');
1643
+ }
1644
+ },
1645
+ };
1646
+ const profilerCommand = {
1647
+ name: 'profiler',
1648
+ description: 'CPU profiler. Usage: monomind browse profiler start|stop|heap [path]',
1649
+ options: [
1650
+ { name: 'interval', type: 'number', description: 'Sampling interval µs', default: 1000 },
1651
+ { name: 'json', type: 'boolean', description: 'Output as JSON', default: false },
1652
+ ],
1653
+ action: async (ctx) => {
1654
+ const { client, sessionId } = await ensureConnected(_port);
1655
+ const browser = await getBrowser();
1656
+ const action = ctx.args[0];
1657
+ if (!action)
1658
+ throw new Error('Usage: monomind browse profiler start|stop|heap [path]');
1659
+ switch (action) {
1660
+ case 'start':
1661
+ await browser.startCpuProfile(client, sessionId, { samplingInterval: ctx.flags.interval });
1662
+ output.printSuccess('CPU profiler started');
1663
+ return { success: true };
1664
+ case 'stop': {
1665
+ const path = await browser.stopCpuProfile(client, sessionId, ctx.args[1]);
1666
+ if (ctx.flags.json)
1667
+ print(JSON.stringify({ data: { path } }));
1668
+ else
1669
+ output.printSuccess(`Profile saved: ${path}`);
1670
+ return { success: true, data: { path } };
1671
+ }
1672
+ case 'heap': {
1673
+ const path = await browser.startHeapSnapshot(client, sessionId, ctx.args[1]);
1674
+ if (ctx.flags.json)
1675
+ print(JSON.stringify({ data: { path } }));
1676
+ else
1677
+ output.printSuccess(`Heap snapshot saved: ${path}`);
1678
+ return { success: true, data: { path } };
1679
+ }
1680
+ default:
1681
+ throw new Error('Usage: monomind browse profiler start|stop|heap [path]');
1682
+ }
1683
+ },
1684
+ };
1685
+ const vitalsCommand = {
1686
+ name: 'vitals',
1687
+ description: 'Collect Core Web Vitals. Usage: monomind browse vitals [--wait 2000]',
1688
+ options: [
1689
+ { name: 'wait', type: 'number', description: 'Wait ms for observers', default: 2000 },
1690
+ { name: 'json', type: 'boolean', description: 'Output as JSON', default: false },
1691
+ ],
1692
+ action: async (ctx) => {
1693
+ const { client, sessionId } = await ensureConnected(_port);
1694
+ const browser = await getBrowser();
1695
+ const vitals = await browser.collectVitals(client, sessionId, ctx.flags.wait);
1696
+ if (ctx.flags.json) {
1697
+ print(JSON.stringify({ data: vitals }));
1698
+ }
1699
+ else {
1700
+ print(browser.formatVitals(vitals));
1701
+ }
1702
+ return { success: true, data: vitals };
1703
+ },
1704
+ };
1705
+ const harCommand = {
1706
+ name: 'har',
1707
+ description: 'HAR network recording. Usage: monomind browse har start|stop|status [path]',
1708
+ options: [
1709
+ { name: 'bodies', type: 'boolean', description: 'Capture response bodies', default: false },
1710
+ { name: 'json', type: 'boolean', description: 'Output as JSON', default: false },
1711
+ ],
1712
+ action: async (ctx) => {
1713
+ const { client, sessionId } = await ensureConnected(_port);
1714
+ const browser = await getBrowser();
1715
+ const action = ctx.args[0];
1716
+ if (!action)
1717
+ throw new Error('Usage: monomind browse har start|stop|status [path]');
1718
+ switch (action) {
1719
+ case 'start':
1720
+ await browser.startHarRecording(client, sessionId);
1721
+ output.printSuccess('HAR recording started');
1722
+ return { success: true };
1723
+ case 'stop': {
1724
+ const path = await browser.stopHarRecording(client, sessionId, ctx.args[1], ctx.flags.bodies);
1725
+ if (ctx.flags.json)
1726
+ print(JSON.stringify({ data: { path } }));
1727
+ else
1728
+ output.printSuccess(`HAR saved: ${path}`);
1729
+ return { success: true, data: { path } };
1730
+ }
1731
+ case 'status': {
1732
+ const status = browser.getHarStatus(sessionId);
1733
+ if (ctx.flags.json)
1734
+ print(JSON.stringify({ data: status }));
1735
+ else
1736
+ print(`Recording: ${status.recording} | Requests: ${status.requestCount}`);
1737
+ return { success: true, data: status };
1738
+ }
1739
+ default:
1740
+ throw new Error('Usage: monomind browse har start|stop|status [path]');
1741
+ }
1742
+ },
1743
+ };
1744
+ const resizeCommand = {
1745
+ name: 'resize',
1746
+ description: 'Resize browser window. Usage: monomind browse resize <width> <height>',
1747
+ action: async (ctx) => {
1748
+ const { client, sessionId } = await ensureConnected(_port);
1749
+ const browser = await getBrowser();
1750
+ const width = parseInt(ctx.args[0], 10);
1751
+ const height = parseInt(ctx.args[1], 10);
1752
+ if (isNaN(width) || isNaN(height))
1753
+ throw new Error('Usage: monomind browse resize <width> <height>');
1754
+ await browser.setViewport(client, sessionId, width, height);
1755
+ output.printSuccess(`Resized to ${width}x${height}`);
1756
+ return { success: true, data: { width, height } };
1757
+ },
1758
+ };
1363
1759
  // ---------------------------------------------------------------------------
1364
1760
  // Root browse command
1365
1761
  // ---------------------------------------------------------------------------
@@ -1411,6 +1807,13 @@ const browseCommand = {
1411
1807
  batchCommand,
1412
1808
  addinitscriptCommand,
1413
1809
  removeinitscriptCommand,
1810
+ connectCommand,
1811
+ recordCommand,
1812
+ traceCommand,
1813
+ profilerCommand,
1814
+ vitalsCommand,
1815
+ harCommand,
1816
+ resizeCommand,
1414
1817
  closeCommand,
1415
1818
  ],
1416
1819
  options: [