monomind 1.10.39 → 1.10.41
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/package.json +1 -1
- package/packages/@monomind/cli/dist/src/browser/actions.js +114 -22
- package/packages/@monomind/cli/dist/src/browser/batch.d.ts +0 -2
- package/packages/@monomind/cli/dist/src/browser/batch.js +1 -10
- package/packages/@monomind/cli/dist/src/browser/browser.d.ts +1 -0
- package/packages/@monomind/cli/dist/src/browser/browser.js +51 -24
- package/packages/@monomind/cli/dist/src/browser/cdp.js +21 -4
- package/packages/@monomind/cli/dist/src/browser/console-log.d.ts +1 -0
- package/packages/@monomind/cli/dist/src/browser/console-log.js +19 -3
- package/packages/@monomind/cli/dist/src/browser/dialog.js +5 -2
- package/packages/@monomind/cli/dist/src/browser/find.js +28 -8
- package/packages/@monomind/cli/dist/src/browser/har.d.ts +1 -0
- package/packages/@monomind/cli/dist/src/browser/har.js +7 -5
- package/packages/@monomind/cli/dist/src/browser/network.d.ts +7 -4
- package/packages/@monomind/cli/dist/src/browser/network.js +60 -23
- package/packages/@monomind/cli/dist/src/browser/profiler.js +13 -2
- package/packages/@monomind/cli/dist/src/browser/screenshot.js +4 -2
- package/packages/@monomind/cli/dist/src/browser/session.js +49 -12
- package/packages/@monomind/cli/dist/src/browser/snapshot.js +26 -14
- package/packages/@monomind/cli/dist/src/browser/storage.d.ts +1 -0
- package/packages/@monomind/cli/dist/src/browser/storage.js +3 -0
- package/packages/@monomind/cli/dist/src/browser/tabs.d.ts +3 -3
- package/packages/@monomind/cli/dist/src/browser/tabs.js +13 -13
- package/packages/@monomind/cli/dist/src/browser/trace.js +10 -4
- package/packages/@monomind/cli/dist/src/browser/wait.js +24 -14
- package/packages/@monomind/cli/dist/src/commands/browse.js +300 -34
- package/packages/@monomind/cli/package.json +1 -1
|
@@ -6,20 +6,20 @@ export async function listTabs(port) {
|
|
|
6
6
|
export async function newTab(port, url = 'about:blank') {
|
|
7
7
|
return fetchNewTarget(port, url);
|
|
8
8
|
}
|
|
9
|
-
export async function closeTab(client,
|
|
10
|
-
await client.send('Target.closeTarget', { targetId }
|
|
9
|
+
export async function closeTab(client, _sessionId, targetId) {
|
|
10
|
+
await client.send('Target.closeTarget', { targetId });
|
|
11
11
|
}
|
|
12
|
-
export async function activateTab(client,
|
|
13
|
-
|
|
12
|
+
export async function activateTab(client, oldSessionId, targetId) {
|
|
13
|
+
if (oldSessionId) {
|
|
14
|
+
await client.send('Target.detachFromTarget', { sessionId: oldSessionId }).catch(() => { });
|
|
15
|
+
}
|
|
16
|
+
await client.send('Target.activateTarget', { targetId });
|
|
17
|
+
const result = await client.send('Target.attachToTarget', { targetId, flatten: true });
|
|
18
|
+
return result.sessionId;
|
|
14
19
|
}
|
|
15
|
-
export async function switchToFrame(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
frame ? frame.src || frame.name || null : null
|
|
20
|
-
`,
|
|
21
|
-
returnByValue: true,
|
|
22
|
-
}, sessionId);
|
|
23
|
-
return result.result?.value ?? null;
|
|
20
|
+
export async function switchToFrame(_client, _sessionId, _frameSelector) {
|
|
21
|
+
throw new Error('switchToFrame is not yet implemented. ' +
|
|
22
|
+
'For same-origin frames, CDP commands already apply to the whole page. ' +
|
|
23
|
+
'For cross-origin (OOPIF) frames, call Target.attachToTarget with the frame target ID.');
|
|
24
24
|
}
|
|
25
25
|
//# sourceMappingURL=tabs.js.map
|
|
@@ -50,8 +50,13 @@ export async function stopTrace(client, sessionId, outputPath) {
|
|
|
50
50
|
throw new Error('No active trace for this session');
|
|
51
51
|
try {
|
|
52
52
|
const [completePromise, cancelOnce] = client.onceWithOff('Tracing.tracingComplete', sessionId);
|
|
53
|
-
let
|
|
54
|
-
const timeoutPromise = new Promise((_, reject) =>
|
|
53
|
+
let timeoutHandle;
|
|
54
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
55
|
+
timeoutHandle = setTimeout(() => {
|
|
56
|
+
cancelOnce();
|
|
57
|
+
reject(new Error('Timeout waiting for Tracing.tracingComplete'));
|
|
58
|
+
}, 30_000);
|
|
59
|
+
});
|
|
55
60
|
try {
|
|
56
61
|
await client.send('Tracing.end', {}, sessionId);
|
|
57
62
|
await Promise.race([completePromise, timeoutPromise]);
|
|
@@ -60,8 +65,9 @@ export async function stopTrace(client, sessionId, outputPath) {
|
|
|
60
65
|
cancelOnce();
|
|
61
66
|
throw err;
|
|
62
67
|
}
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
finally {
|
|
69
|
+
clearTimeout(timeoutHandle);
|
|
70
|
+
}
|
|
65
71
|
}
|
|
66
72
|
finally {
|
|
67
73
|
state.offData();
|
|
@@ -32,7 +32,10 @@ async function waitForLoad(client, sessionId, condition, timeout) {
|
|
|
32
32
|
const event = condition === 'load' ? 'Page.loadEventFired' : 'Page.domContentEventFired';
|
|
33
33
|
const [eventPromise, cancelOnce] = client.onceWithOff(event, sessionId);
|
|
34
34
|
let timedOut = false;
|
|
35
|
-
|
|
35
|
+
let timeoutHandle;
|
|
36
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
37
|
+
timeoutHandle = setTimeout(() => { timedOut = true; resolve(); }, timeout);
|
|
38
|
+
});
|
|
36
39
|
try {
|
|
37
40
|
await Promise.race([eventPromise, timeoutPromise]);
|
|
38
41
|
if (timedOut)
|
|
@@ -40,11 +43,13 @@ async function waitForLoad(client, sessionId, condition, timeout) {
|
|
|
40
43
|
}
|
|
41
44
|
finally {
|
|
42
45
|
cancelOnce();
|
|
46
|
+
clearTimeout(timeoutHandle);
|
|
43
47
|
}
|
|
44
48
|
}
|
|
45
49
|
async function waitForNetworkIdle(client, sessionId, idleMs, timeout) {
|
|
46
50
|
return new Promise((resolve, reject) => {
|
|
47
51
|
let pending = 0;
|
|
52
|
+
const inflight = new Set();
|
|
48
53
|
let idleTimer = null;
|
|
49
54
|
const cleanup = () => {
|
|
50
55
|
if (idleTimer) {
|
|
@@ -55,6 +60,7 @@ async function waitForNetworkIdle(client, sessionId, idleMs, timeout) {
|
|
|
55
60
|
offReq();
|
|
56
61
|
offResp();
|
|
57
62
|
offFail();
|
|
63
|
+
offCache();
|
|
58
64
|
};
|
|
59
65
|
const killTimer = setTimeout(() => {
|
|
60
66
|
cleanup();
|
|
@@ -74,24 +80,28 @@ async function waitForNetworkIdle(client, sessionId, idleMs, timeout) {
|
|
|
74
80
|
}
|
|
75
81
|
}
|
|
76
82
|
};
|
|
77
|
-
const offReq = client.on('Network.requestWillBeSent', (
|
|
78
|
-
if (sid
|
|
83
|
+
const offReq = client.on('Network.requestWillBeSent', (params, sid) => {
|
|
84
|
+
if (sid !== sessionId)
|
|
85
|
+
return;
|
|
86
|
+
const id = params.requestId;
|
|
87
|
+
if (!inflight.has(id)) {
|
|
88
|
+
inflight.add(id);
|
|
79
89
|
pending++;
|
|
80
90
|
check();
|
|
81
91
|
}
|
|
82
92
|
});
|
|
83
|
-
const
|
|
84
|
-
if (sid
|
|
93
|
+
const decrement = (params, sid) => {
|
|
94
|
+
if (sid !== sessionId)
|
|
95
|
+
return;
|
|
96
|
+
const id = params.requestId;
|
|
97
|
+
if (inflight.delete(id)) {
|
|
85
98
|
pending = Math.max(0, pending - 1);
|
|
86
99
|
check();
|
|
87
100
|
}
|
|
88
|
-
}
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
check();
|
|
93
|
-
}
|
|
94
|
-
});
|
|
101
|
+
};
|
|
102
|
+
const offResp = client.on('Network.loadingFinished', decrement);
|
|
103
|
+
const offFail = client.on('Network.loadingFailed', decrement);
|
|
104
|
+
const offCache = client.on('Network.requestServedFromCache', decrement);
|
|
95
105
|
check();
|
|
96
106
|
});
|
|
97
107
|
}
|
|
@@ -107,7 +117,7 @@ async function waitForUrl(client, sessionId, pattern, deadline) {
|
|
|
107
117
|
}
|
|
108
118
|
async function waitForText(client, sessionId, text, deadline) {
|
|
109
119
|
while (Date.now() < deadline) {
|
|
110
|
-
const bodyText = await evaluateJs(client, sessionId, 'document.body
|
|
120
|
+
const bodyText = await evaluateJs(client, sessionId, 'document.body?.innerText ?? ""');
|
|
111
121
|
if (bodyText.includes(text))
|
|
112
122
|
return;
|
|
113
123
|
await sleep(POLL_INTERVAL);
|
|
@@ -128,7 +138,7 @@ function globToRegex(pattern) {
|
|
|
128
138
|
.replace(/[-[\]{}()+?.,\\^$|#\s]/g, '\\$&')
|
|
129
139
|
.replace(/\*\*/g, '.*')
|
|
130
140
|
.replace(/\*/g, '[^/]*');
|
|
131
|
-
return new RegExp(escaped);
|
|
141
|
+
return new RegExp(`^${escaped}$`);
|
|
132
142
|
}
|
|
133
143
|
function sleep(ms) {
|
|
134
144
|
return new Promise((r) => setTimeout(r, ms));
|
|
@@ -15,6 +15,13 @@ async function getBrowser() {
|
|
|
15
15
|
async function ensureConnected(port, targetId) {
|
|
16
16
|
const browser = await getBrowser();
|
|
17
17
|
if (!_client || !_client.isConnected()) {
|
|
18
|
+
if (_client && _sessionId) {
|
|
19
|
+
browser.teardownRouteInterception(_sessionId);
|
|
20
|
+
browser.stopRequestCapture(_sessionId);
|
|
21
|
+
browser.teardownDialogHandling(_sessionId);
|
|
22
|
+
browser.teardownConsoleCapture(_sessionId);
|
|
23
|
+
_client.close();
|
|
24
|
+
}
|
|
18
25
|
_port = await browser.launchBrowser({ port, headless: false });
|
|
19
26
|
const conn = await browser.connectToTarget(_port, targetId);
|
|
20
27
|
_client = conn.client;
|
|
@@ -45,12 +52,46 @@ const openCommand = {
|
|
|
45
52
|
throw new Error('URL required. Usage: monomind browse open <url>');
|
|
46
53
|
const port = ctx.flags.port ?? 9222;
|
|
47
54
|
const browser = await getBrowser();
|
|
55
|
+
if (_client) {
|
|
56
|
+
const prevSid = _sessionId;
|
|
57
|
+
const prevClient = _client;
|
|
58
|
+
if (browser.getHarStatus(prevSid).recording) {
|
|
59
|
+
try {
|
|
60
|
+
await browser.stopHarRecording(prevClient, prevSid);
|
|
61
|
+
}
|
|
62
|
+
catch { /* ignore */ }
|
|
63
|
+
}
|
|
64
|
+
if (browser.getTraceStatus(prevSid)) {
|
|
65
|
+
try {
|
|
66
|
+
await browser.stopTrace(prevClient, prevSid);
|
|
67
|
+
}
|
|
68
|
+
catch { /* ignore */ }
|
|
69
|
+
}
|
|
70
|
+
if (browser.isProfilingActive(prevSid)) {
|
|
71
|
+
try {
|
|
72
|
+
await browser.stopCpuProfile(prevClient, prevSid);
|
|
73
|
+
}
|
|
74
|
+
catch { /* ignore */ }
|
|
75
|
+
}
|
|
76
|
+
browser.teardownRouteInterception(prevSid);
|
|
77
|
+
browser.stopRequestCapture(prevSid);
|
|
78
|
+
browser.teardownDialogHandling(prevSid);
|
|
79
|
+
browser.teardownConsoleCapture(prevSid);
|
|
80
|
+
prevClient.close();
|
|
81
|
+
_client = null;
|
|
82
|
+
_sessionId = '';
|
|
83
|
+
_targetId = '';
|
|
84
|
+
_refs = new Map();
|
|
85
|
+
}
|
|
48
86
|
_port = await browser.launchBrowser({ port, headless: ctx.flags.headless });
|
|
49
87
|
const conn = await browser.connectToTarget(_port);
|
|
50
88
|
_client = conn.client;
|
|
51
89
|
_sessionId = conn.sessionId;
|
|
52
90
|
_targetId = conn.target.id;
|
|
53
91
|
_refs = new Map();
|
|
92
|
+
if (ctx.flags.state && ctx.flags.session) {
|
|
93
|
+
output.printWarning('Both --state and --session provided; --state takes precedence');
|
|
94
|
+
}
|
|
54
95
|
if (ctx.flags.state) {
|
|
55
96
|
await browser.loadStateFile(_client, _sessionId, ctx.flags.state);
|
|
56
97
|
}
|
|
@@ -198,7 +239,7 @@ const waitCommand = {
|
|
|
198
239
|
const interval = 200;
|
|
199
240
|
const deadline = Date.now() + timeout;
|
|
200
241
|
while (Date.now() < deadline) {
|
|
201
|
-
const text = await browser.evaluateJs(client, sessionId, 'document.body
|
|
242
|
+
const text = await browser.evaluateJs(client, sessionId, 'document.body?.innerText ?? ""');
|
|
202
243
|
if (!text.includes(target)) {
|
|
203
244
|
output.printSuccess('Text disappeared');
|
|
204
245
|
return { success: true };
|
|
@@ -283,7 +324,7 @@ const getCommand = {
|
|
|
283
324
|
value = result.result?.value ?? '';
|
|
284
325
|
}
|
|
285
326
|
else {
|
|
286
|
-
value = (await browser.evaluateJs(client, sessionId, 'document.body
|
|
327
|
+
value = (await browser.evaluateJs(client, sessionId, 'document.body?.innerText ?? ""'));
|
|
287
328
|
}
|
|
288
329
|
break;
|
|
289
330
|
}
|
|
@@ -410,21 +451,45 @@ const navigateCommand = {
|
|
|
410
451
|
description: 'Navigate browser history. Usage: monomind browse navigate back|forward|reload',
|
|
411
452
|
action: async (ctx) => {
|
|
412
453
|
const { client, sessionId } = await ensureConnected(_port);
|
|
454
|
+
const browser = await getBrowser();
|
|
413
455
|
const direction = ctx.args[0];
|
|
414
456
|
if (!direction)
|
|
415
457
|
throw new Error('Usage: monomind browse navigate back|forward|reload');
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
458
|
+
if (direction === 'back' || direction === 'forward') {
|
|
459
|
+
// Pre-register frame-start listener BEFORE JS navigation to avoid the race
|
|
460
|
+
// where history.back/forward() returns before the browser issues any requests
|
|
461
|
+
let offFrameStarted = () => { };
|
|
462
|
+
const frameStartedPromise = new Promise((resolve) => {
|
|
463
|
+
offFrameStarted = client.on('Page.frameStartedLoading', (_params, sid) => {
|
|
464
|
+
if (sid === sessionId) {
|
|
465
|
+
const off = offFrameStarted;
|
|
466
|
+
offFrameStarted = () => { };
|
|
467
|
+
off();
|
|
468
|
+
resolve();
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
try {
|
|
473
|
+
await client.send('Runtime.evaluate', {
|
|
474
|
+
expression: direction === 'back' ? 'history.back()' : 'history.forward()',
|
|
475
|
+
}, sessionId);
|
|
476
|
+
let fallbackHandle;
|
|
477
|
+
const fallbackPromise = new Promise((r) => { fallbackHandle = setTimeout(r, 2000); });
|
|
478
|
+
await Promise.race([frameStartedPromise, fallbackPromise]);
|
|
479
|
+
if (fallbackHandle !== undefined)
|
|
480
|
+
clearTimeout(fallbackHandle);
|
|
481
|
+
}
|
|
482
|
+
finally {
|
|
483
|
+
offFrameStarted();
|
|
484
|
+
}
|
|
485
|
+
await browser.waitForLoad(client, sessionId, 'networkidle');
|
|
486
|
+
}
|
|
487
|
+
else if (direction === 'reload') {
|
|
488
|
+
await client.send('Page.reload', {}, sessionId);
|
|
489
|
+
await browser.waitForLoad(client, sessionId, 'load');
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
throw new Error(`Unknown direction: ${direction}. Use: back|forward|reload`);
|
|
428
493
|
}
|
|
429
494
|
output.printSuccess(`Navigated: ${direction}`);
|
|
430
495
|
return { success: true };
|
|
@@ -471,7 +536,10 @@ const setCommand = {
|
|
|
471
536
|
break;
|
|
472
537
|
}
|
|
473
538
|
case 'offline': {
|
|
474
|
-
const
|
|
539
|
+
const offlineArg = ctx.args[1];
|
|
540
|
+
if (offlineArg === undefined)
|
|
541
|
+
throw new Error('Usage: set offline <true|false>');
|
|
542
|
+
const enabled = offlineArg === 'true';
|
|
475
543
|
await browser.setOfflineMode(client, sessionId, enabled);
|
|
476
544
|
output.printSuccess(`Offline mode: ${enabled}`);
|
|
477
545
|
break;
|
|
@@ -698,7 +766,33 @@ const closeCommand = {
|
|
|
698
766
|
description: 'Close the active browser session',
|
|
699
767
|
action: async (_ctx) => {
|
|
700
768
|
if (_client) {
|
|
701
|
-
|
|
769
|
+
const browser = await getBrowser();
|
|
770
|
+
const sid = _sessionId;
|
|
771
|
+
const client = _client;
|
|
772
|
+
// Tear down per-session Maps and listeners before closing
|
|
773
|
+
if (browser.getHarStatus(sid).recording) {
|
|
774
|
+
try {
|
|
775
|
+
await browser.stopHarRecording(client, sid);
|
|
776
|
+
}
|
|
777
|
+
catch { /* ignore */ }
|
|
778
|
+
}
|
|
779
|
+
if (browser.getTraceStatus(sid)) {
|
|
780
|
+
try {
|
|
781
|
+
await browser.stopTrace(client, sid);
|
|
782
|
+
}
|
|
783
|
+
catch { /* ignore */ }
|
|
784
|
+
}
|
|
785
|
+
if (browser.isProfilingActive(sid)) {
|
|
786
|
+
try {
|
|
787
|
+
await browser.stopCpuProfile(client, sid);
|
|
788
|
+
}
|
|
789
|
+
catch { /* ignore */ }
|
|
790
|
+
}
|
|
791
|
+
browser.teardownRouteInterception(sid);
|
|
792
|
+
browser.stopRequestCapture(sid);
|
|
793
|
+
browser.teardownDialogHandling(sid);
|
|
794
|
+
browser.teardownConsoleCapture(sid);
|
|
795
|
+
client.close();
|
|
702
796
|
_client = null;
|
|
703
797
|
_sessionId = '';
|
|
704
798
|
_targetId = '';
|
|
@@ -993,14 +1087,18 @@ const clipboardCommand = {
|
|
|
993
1087
|
output.printSuccess('Clipboard written');
|
|
994
1088
|
break;
|
|
995
1089
|
}
|
|
996
|
-
case 'copy':
|
|
997
|
-
|
|
1090
|
+
case 'copy': {
|
|
1091
|
+
const mod = process.platform === 'darwin' ? 4 : 2; // Meta/Cmd on macOS, Ctrl elsewhere
|
|
1092
|
+
await browser.pressKeyCombo(client, sessionId, 'c', mod);
|
|
998
1093
|
output.printSuccess('Copy sent');
|
|
999
1094
|
break;
|
|
1000
|
-
|
|
1001
|
-
|
|
1095
|
+
}
|
|
1096
|
+
case 'paste': {
|
|
1097
|
+
const mod = process.platform === 'darwin' ? 4 : 2;
|
|
1098
|
+
await browser.pressKeyCombo(client, sessionId, 'v', mod);
|
|
1002
1099
|
output.printSuccess('Paste sent');
|
|
1003
1100
|
break;
|
|
1101
|
+
}
|
|
1004
1102
|
default:
|
|
1005
1103
|
throw new Error('Usage: monomind browse clipboard read|write|copy|paste');
|
|
1006
1104
|
}
|
|
@@ -1087,13 +1185,74 @@ const tabCommand = {
|
|
|
1087
1185
|
const tabId = ctx.args[1];
|
|
1088
1186
|
if (!tabId)
|
|
1089
1187
|
throw new Error('Usage: monomind browse tab close <tabId>');
|
|
1090
|
-
|
|
1188
|
+
if (tabId === _targetId) {
|
|
1189
|
+
const sid = _sessionId;
|
|
1190
|
+
// Stop profiling before closing the tab so CDP commands still reach the live session
|
|
1191
|
+
if (browser.getHarStatus(sid).recording) {
|
|
1192
|
+
try {
|
|
1193
|
+
await browser.stopHarRecording(client, sid);
|
|
1194
|
+
}
|
|
1195
|
+
catch { /* ignore */ }
|
|
1196
|
+
}
|
|
1197
|
+
if (browser.getTraceStatus(sid)) {
|
|
1198
|
+
try {
|
|
1199
|
+
await browser.stopTrace(client, sid);
|
|
1200
|
+
}
|
|
1201
|
+
catch { /* ignore */ }
|
|
1202
|
+
}
|
|
1203
|
+
if (browser.isProfilingActive(sid)) {
|
|
1204
|
+
try {
|
|
1205
|
+
await browser.stopCpuProfile(client, sid);
|
|
1206
|
+
}
|
|
1207
|
+
catch { /* ignore */ }
|
|
1208
|
+
}
|
|
1209
|
+
browser.teardownRouteInterception(sid);
|
|
1210
|
+
browser.stopRequestCapture(sid);
|
|
1211
|
+
browser.teardownDialogHandling(sid);
|
|
1212
|
+
browser.teardownConsoleCapture(sid);
|
|
1213
|
+
await browser.closeTab(client, sessionId, tabId);
|
|
1214
|
+
client.close();
|
|
1215
|
+
_client = null;
|
|
1216
|
+
_sessionId = '';
|
|
1217
|
+
_targetId = '';
|
|
1218
|
+
_refs = new Map();
|
|
1219
|
+
}
|
|
1220
|
+
else {
|
|
1221
|
+
await browser.closeTab(client, sessionId, tabId);
|
|
1222
|
+
}
|
|
1091
1223
|
output.printSuccess(`Closed tab: ${tabId}`);
|
|
1092
1224
|
break;
|
|
1093
1225
|
}
|
|
1094
1226
|
default: {
|
|
1095
|
-
//
|
|
1096
|
-
await browser.activateTab(client, sessionId, action);
|
|
1227
|
+
// Attach to new tab FIRST — only tear down old session if that succeeds
|
|
1228
|
+
const newSid = await browser.activateTab(client, sessionId, action);
|
|
1229
|
+
const oldSid = _sessionId;
|
|
1230
|
+
if (browser.getHarStatus(oldSid).recording) {
|
|
1231
|
+
try {
|
|
1232
|
+
await browser.stopHarRecording(client, oldSid);
|
|
1233
|
+
}
|
|
1234
|
+
catch { /* ignore */ }
|
|
1235
|
+
}
|
|
1236
|
+
if (browser.getTraceStatus(oldSid)) {
|
|
1237
|
+
try {
|
|
1238
|
+
await browser.stopTrace(client, oldSid);
|
|
1239
|
+
}
|
|
1240
|
+
catch { /* ignore */ }
|
|
1241
|
+
}
|
|
1242
|
+
if (browser.isProfilingActive(oldSid)) {
|
|
1243
|
+
try {
|
|
1244
|
+
await browser.stopCpuProfile(client, oldSid);
|
|
1245
|
+
}
|
|
1246
|
+
catch { /* ignore */ }
|
|
1247
|
+
}
|
|
1248
|
+
await browser.disableInterception(client, oldSid).catch(() => { });
|
|
1249
|
+
browser.stopRequestCapture(oldSid);
|
|
1250
|
+
browser.teardownDialogHandling(oldSid);
|
|
1251
|
+
browser.teardownConsoleCapture(oldSid);
|
|
1252
|
+
_sessionId = newSid;
|
|
1253
|
+
_targetId = action;
|
|
1254
|
+
_refs = new Map();
|
|
1255
|
+
await browser.enableSessionDomains(client, _sessionId);
|
|
1097
1256
|
output.printSuccess(`Switched to tab: ${action}`);
|
|
1098
1257
|
}
|
|
1099
1258
|
}
|
|
@@ -1111,11 +1270,12 @@ const consoleLogCommand = {
|
|
|
1111
1270
|
action: async (ctx) => {
|
|
1112
1271
|
const browser = await getBrowser();
|
|
1113
1272
|
if (ctx.flags.clear) {
|
|
1114
|
-
browser.clearConsoleMessages();
|
|
1273
|
+
browser.clearConsoleMessages(_sessionId);
|
|
1115
1274
|
output.printSuccess('Console cleared');
|
|
1116
1275
|
return { success: true };
|
|
1117
1276
|
}
|
|
1118
|
-
const
|
|
1277
|
+
const allMsgs = browser.getConsoleMessages(_sessionId);
|
|
1278
|
+
const msgs = ctx.flags['errors-only'] ? allMsgs.filter((m) => m.type === 'error') : allMsgs;
|
|
1119
1279
|
if (ctx.flags.json) {
|
|
1120
1280
|
print(JSON.stringify(msgs));
|
|
1121
1281
|
}
|
|
@@ -1138,11 +1298,11 @@ const errorsCommand = {
|
|
|
1138
1298
|
action: async (ctx) => {
|
|
1139
1299
|
const browser = await getBrowser();
|
|
1140
1300
|
if (ctx.flags.clear) {
|
|
1141
|
-
browser.clearPageErrors();
|
|
1301
|
+
browser.clearPageErrors(_sessionId);
|
|
1142
1302
|
output.printSuccess('Errors cleared');
|
|
1143
1303
|
return { success: true };
|
|
1144
1304
|
}
|
|
1145
|
-
const errs = browser.getPageErrors();
|
|
1305
|
+
const errs = browser.getPageErrors(_sessionId);
|
|
1146
1306
|
if (ctx.flags.json) {
|
|
1147
1307
|
print(JSON.stringify(errs));
|
|
1148
1308
|
}
|
|
@@ -1192,6 +1352,8 @@ const storageCommand = {
|
|
|
1192
1352
|
if (key && ctx.flags.remove) {
|
|
1193
1353
|
if (isLocal)
|
|
1194
1354
|
await browser.removeLocalStorageKey(client, sessionId, key);
|
|
1355
|
+
else
|
|
1356
|
+
await browser.removeSessionStorageKey(client, sessionId, key);
|
|
1195
1357
|
output.printSuccess(`Removed ${key}`);
|
|
1196
1358
|
return { success: true };
|
|
1197
1359
|
}
|
|
@@ -1448,6 +1610,36 @@ const pushstateCommand = {
|
|
|
1448
1610
|
return { success: true };
|
|
1449
1611
|
},
|
|
1450
1612
|
};
|
|
1613
|
+
function tokenizeBatchCommand(input) {
|
|
1614
|
+
const tokens = [];
|
|
1615
|
+
let current = '';
|
|
1616
|
+
let inQuote = null;
|
|
1617
|
+
for (const ch of input.trim()) {
|
|
1618
|
+
if (inQuote) {
|
|
1619
|
+
if (ch === inQuote) {
|
|
1620
|
+
inQuote = null;
|
|
1621
|
+
}
|
|
1622
|
+
else {
|
|
1623
|
+
current += ch;
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
else if (ch === '"' || ch === "'") {
|
|
1627
|
+
inQuote = ch;
|
|
1628
|
+
}
|
|
1629
|
+
else if (/\s/.test(ch)) {
|
|
1630
|
+
if (current) {
|
|
1631
|
+
tokens.push(current);
|
|
1632
|
+
current = '';
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
else {
|
|
1636
|
+
current += ch;
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
if (current)
|
|
1640
|
+
tokens.push(current);
|
|
1641
|
+
return tokens;
|
|
1642
|
+
}
|
|
1451
1643
|
const batchCommand = {
|
|
1452
1644
|
name: 'batch',
|
|
1453
1645
|
description: 'Execute multiple commands. Usage: monomind browse batch "open url" "snapshot -i" "click @e1"',
|
|
@@ -1461,7 +1653,7 @@ const batchCommand = {
|
|
|
1461
1653
|
throw new Error('Usage: monomind browse batch "cmd1" "cmd2" ...');
|
|
1462
1654
|
const results = [];
|
|
1463
1655
|
for (const cmdStr of commands) {
|
|
1464
|
-
const parts = cmdStr
|
|
1656
|
+
const parts = tokenizeBatchCommand(cmdStr);
|
|
1465
1657
|
const subName = parts[0];
|
|
1466
1658
|
const subArgs = parts.slice(1);
|
|
1467
1659
|
const subCmd = browseCommand.subcommands?.find((s) => s.name === subName);
|
|
@@ -1474,28 +1666,71 @@ const batchCommand = {
|
|
|
1474
1666
|
}
|
|
1475
1667
|
try {
|
|
1476
1668
|
const parsedFlags = { _: [] };
|
|
1477
|
-
|
|
1669
|
+
const consumedIndices = new Set();
|
|
1670
|
+
// Parse --flags from subArgs, tracking which indices are flag names/values
|
|
1478
1671
|
for (let i = 0; i < subArgs.length; i++) {
|
|
1479
1672
|
if (subArgs[i].startsWith('--')) {
|
|
1673
|
+
consumedIndices.add(i);
|
|
1480
1674
|
const key = subArgs[i].slice(2);
|
|
1481
1675
|
const next = subArgs[i + 1];
|
|
1482
|
-
|
|
1483
|
-
|
|
1676
|
+
const optDef = subCmd.options?.find((o) => o.name === key);
|
|
1677
|
+
const isBooleanFlag = optDef?.type === 'boolean';
|
|
1678
|
+
if (next && (!next.startsWith('-') || /^-\d/.test(next)) && !isBooleanFlag) {
|
|
1679
|
+
// Non-boolean flags consume the next token as their value (allow negative numbers like -1)
|
|
1680
|
+
consumedIndices.add(i + 1);
|
|
1681
|
+
if (optDef?.type === 'number') {
|
|
1682
|
+
parsedFlags[key] = Number(next);
|
|
1683
|
+
}
|
|
1684
|
+
else {
|
|
1685
|
+
parsedFlags[key] = next;
|
|
1686
|
+
}
|
|
1687
|
+
i++;
|
|
1688
|
+
}
|
|
1689
|
+
else if (isBooleanFlag && (next === 'true' || next === 'false')) {
|
|
1690
|
+
// Explicit boolean value token
|
|
1691
|
+
consumedIndices.add(i + 1);
|
|
1692
|
+
parsedFlags[key] = next !== 'false';
|
|
1484
1693
|
i++;
|
|
1485
1694
|
}
|
|
1486
1695
|
else {
|
|
1487
1696
|
parsedFlags[key] = true;
|
|
1488
1697
|
}
|
|
1489
1698
|
}
|
|
1699
|
+
else if (subArgs[i].startsWith('-') && subArgs[i].length === 2 && /[a-zA-Z]/.test(subArgs[i][1])) {
|
|
1700
|
+
consumedIndices.add(i);
|
|
1701
|
+
const shortKey = subArgs[i][1];
|
|
1702
|
+
const optDef = subCmd.options?.find((o) => o.short === shortKey);
|
|
1703
|
+
if (optDef) {
|
|
1704
|
+
const key = optDef.name;
|
|
1705
|
+
const next = subArgs[i + 1];
|
|
1706
|
+
const isBooleanFlag = optDef.type === 'boolean';
|
|
1707
|
+
if (next && (!next.startsWith('-') || /^-\d/.test(next)) && !isBooleanFlag) {
|
|
1708
|
+
consumedIndices.add(i + 1);
|
|
1709
|
+
parsedFlags[key] = optDef.type === 'number' ? Number(next) : next;
|
|
1710
|
+
i++;
|
|
1711
|
+
}
|
|
1712
|
+
else if (isBooleanFlag && (next === 'true' || next === 'false')) {
|
|
1713
|
+
consumedIndices.add(i + 1);
|
|
1714
|
+
parsedFlags[key] = next !== 'false';
|
|
1715
|
+
i++;
|
|
1716
|
+
}
|
|
1717
|
+
else {
|
|
1718
|
+
parsedFlags[key] = true;
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1490
1722
|
}
|
|
1491
1723
|
const fakeCtx = {
|
|
1492
|
-
args: subArgs.filter((
|
|
1724
|
+
args: subArgs.filter((_, i) => !consumedIndices.has(i)),
|
|
1493
1725
|
flags: parsedFlags,
|
|
1494
1726
|
cwd: ctx.cwd,
|
|
1495
1727
|
interactive: false,
|
|
1496
1728
|
};
|
|
1497
|
-
await subCmd.action(fakeCtx);
|
|
1498
|
-
|
|
1729
|
+
const cmdResult = await subCmd.action(fakeCtx);
|
|
1730
|
+
const succeeded = cmdResult?.success !== false;
|
|
1731
|
+
results.push({ command: cmdStr, success: succeeded, error: succeeded ? undefined : 'Command returned failure' });
|
|
1732
|
+
if (!succeeded && ctx.flags.bail)
|
|
1733
|
+
break;
|
|
1499
1734
|
}
|
|
1500
1735
|
catch (e) {
|
|
1501
1736
|
const err = e instanceof Error ? e.message : String(e);
|
|
@@ -1548,6 +1783,37 @@ const connectCommand = {
|
|
|
1548
1783
|
action: async (ctx) => {
|
|
1549
1784
|
const port = ctx.flags.port ?? 9222;
|
|
1550
1785
|
const browser = await getBrowser();
|
|
1786
|
+
if (_client) {
|
|
1787
|
+
const prevSid = _sessionId;
|
|
1788
|
+
const prevClient = _client;
|
|
1789
|
+
if (browser.getHarStatus(prevSid).recording) {
|
|
1790
|
+
try {
|
|
1791
|
+
await browser.stopHarRecording(prevClient, prevSid);
|
|
1792
|
+
}
|
|
1793
|
+
catch { /* ignore */ }
|
|
1794
|
+
}
|
|
1795
|
+
if (browser.getTraceStatus(prevSid)) {
|
|
1796
|
+
try {
|
|
1797
|
+
await browser.stopTrace(prevClient, prevSid);
|
|
1798
|
+
}
|
|
1799
|
+
catch { /* ignore */ }
|
|
1800
|
+
}
|
|
1801
|
+
if (browser.isProfilingActive(prevSid)) {
|
|
1802
|
+
try {
|
|
1803
|
+
await browser.stopCpuProfile(prevClient, prevSid);
|
|
1804
|
+
}
|
|
1805
|
+
catch { /* ignore */ }
|
|
1806
|
+
}
|
|
1807
|
+
browser.teardownRouteInterception(prevSid);
|
|
1808
|
+
browser.stopRequestCapture(prevSid);
|
|
1809
|
+
browser.teardownDialogHandling(prevSid);
|
|
1810
|
+
browser.teardownConsoleCapture(prevSid);
|
|
1811
|
+
prevClient.close();
|
|
1812
|
+
_client = null;
|
|
1813
|
+
_sessionId = '';
|
|
1814
|
+
_targetId = '';
|
|
1815
|
+
_refs = new Map();
|
|
1816
|
+
}
|
|
1551
1817
|
const conn = await browser.connectToTarget(port, ctx.flags.target);
|
|
1552
1818
|
_client = conn.client;
|
|
1553
1819
|
_sessionId = conn.sessionId;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monoes/monomindcli",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.41",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Monomind CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
|
6
6
|
"main": "dist/src/index.js",
|