@monoes/monomindcli 1.10.34 → 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.
- package/dist/src/browser/find.d.ts +1 -0
- package/dist/src/browser/find.d.ts.map +1 -1
- package/dist/src/browser/find.js +12 -0
- package/dist/src/browser/find.js.map +1 -1
- package/dist/src/browser/har.d.ts +26 -0
- package/dist/src/browser/har.d.ts.map +1 -0
- package/dist/src/browser/har.js +130 -0
- package/dist/src/browser/har.js.map +1 -0
- package/dist/src/browser/index.d.ts +5 -0
- package/dist/src/browser/index.d.ts.map +1 -1
- package/dist/src/browser/index.js +5 -0
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/browser/network.d.ts +15 -0
- package/dist/src/browser/network.d.ts.map +1 -1
- package/dist/src/browser/network.js +54 -0
- package/dist/src/browser/network.js.map +1 -1
- package/dist/src/browser/profiler.d.ts +10 -0
- package/dist/src/browser/profiler.d.ts.map +1 -0
- package/dist/src/browser/profiler.js +55 -0
- package/dist/src/browser/profiler.js.map +1 -0
- package/dist/src/browser/record.d.ts +21 -0
- package/dist/src/browser/record.d.ts.map +1 -0
- package/dist/src/browser/record.js +48 -0
- package/dist/src/browser/record.js.map +1 -0
- package/dist/src/browser/snapshot.d.ts.map +1 -1
- package/dist/src/browser/snapshot.js +23 -2
- package/dist/src/browser/snapshot.js.map +1 -1
- package/dist/src/browser/trace.d.ts +10 -0
- package/dist/src/browser/trace.d.ts.map +1 -0
- package/dist/src/browser/trace.js +72 -0
- package/dist/src/browser/trace.js.map +1 -0
- package/dist/src/browser/types.d.ts +1 -0
- package/dist/src/browser/types.d.ts.map +1 -1
- package/dist/src/browser/types.js.map +1 -1
- package/dist/src/browser/vitals.d.ts +15 -0
- package/dist/src/browser/vitals.d.ts.map +1 -0
- package/dist/src/browser/vitals.js +116 -0
- package/dist/src/browser/vitals.js.map +1 -0
- package/dist/src/commands/browse.d.ts.map +1 -1
- package/dist/src/commands/browse.js +415 -13
- package/dist/src/commands/browse.js.map +1 -1
- package/dist/src/ui/dashboard-v2.html +8 -8
- package/dist/tsconfig.tsbuildinfo +1 -1
- 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
|
|
335
|
-
|
|
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
|
},
|
|
@@ -1148,7 +1319,7 @@ const isCommand = {
|
|
|
1148
1319
|
};
|
|
1149
1320
|
const findCommand = {
|
|
1150
1321
|
name: 'find',
|
|
1151
|
-
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]',
|
|
1152
1323
|
options: [
|
|
1153
1324
|
{ name: 'name', type: 'string', description: 'Filter by accessible name' },
|
|
1154
1325
|
{ name: 'exact', type: 'boolean', description: 'Require exact match', default: false },
|
|
@@ -1162,7 +1333,7 @@ const findCommand = {
|
|
|
1162
1333
|
const value = ctx.args[1];
|
|
1163
1334
|
const action = ctx.args[2];
|
|
1164
1335
|
if (!locator || !value)
|
|
1165
|
-
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]');
|
|
1166
1337
|
const opts = {
|
|
1167
1338
|
name: ctx.flags.name,
|
|
1168
1339
|
exact: ctx.flags.exact,
|
|
@@ -1180,6 +1351,18 @@ const findCommand = {
|
|
|
1180
1351
|
case 'label':
|
|
1181
1352
|
ref = await browser.findByLabel(client, sessionId, _refs, value, opts);
|
|
1182
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
|
+
}
|
|
1183
1366
|
case 'testid': {
|
|
1184
1367
|
const sel = await browser.findByTestId(client, sessionId, value);
|
|
1185
1368
|
if (!sel) {
|
|
@@ -1190,7 +1373,7 @@ const findCommand = {
|
|
|
1190
1373
|
return { success: true, data: { selector: sel } };
|
|
1191
1374
|
}
|
|
1192
1375
|
default:
|
|
1193
|
-
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`);
|
|
1194
1377
|
}
|
|
1195
1378
|
if (!ref) {
|
|
1196
1379
|
output.printWarning(`No element found: ${locator}="${value}"`);
|
|
@@ -1361,6 +1544,218 @@ const removeinitscriptCommand = {
|
|
|
1361
1544
|
return { success: true };
|
|
1362
1545
|
},
|
|
1363
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
|
+
};
|
|
1364
1759
|
// ---------------------------------------------------------------------------
|
|
1365
1760
|
// Root browse command
|
|
1366
1761
|
// ---------------------------------------------------------------------------
|
|
@@ -1412,6 +1807,13 @@ const browseCommand = {
|
|
|
1412
1807
|
batchCommand,
|
|
1413
1808
|
addinitscriptCommand,
|
|
1414
1809
|
removeinitscriptCommand,
|
|
1810
|
+
connectCommand,
|
|
1811
|
+
recordCommand,
|
|
1812
|
+
traceCommand,
|
|
1813
|
+
profilerCommand,
|
|
1814
|
+
vitalsCommand,
|
|
1815
|
+
harCommand,
|
|
1816
|
+
resizeCommand,
|
|
1415
1817
|
closeCommand,
|
|
1416
1818
|
],
|
|
1417
1819
|
options: [
|