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.
Files changed (27) hide show
  1. package/package.json +1 -1
  2. package/packages/@monomind/cli/dist/src/browser/actions.js +114 -22
  3. package/packages/@monomind/cli/dist/src/browser/batch.d.ts +0 -2
  4. package/packages/@monomind/cli/dist/src/browser/batch.js +1 -10
  5. package/packages/@monomind/cli/dist/src/browser/browser.d.ts +1 -0
  6. package/packages/@monomind/cli/dist/src/browser/browser.js +51 -24
  7. package/packages/@monomind/cli/dist/src/browser/cdp.js +21 -4
  8. package/packages/@monomind/cli/dist/src/browser/console-log.d.ts +1 -0
  9. package/packages/@monomind/cli/dist/src/browser/console-log.js +19 -3
  10. package/packages/@monomind/cli/dist/src/browser/dialog.js +5 -2
  11. package/packages/@monomind/cli/dist/src/browser/find.js +28 -8
  12. package/packages/@monomind/cli/dist/src/browser/har.d.ts +1 -0
  13. package/packages/@monomind/cli/dist/src/browser/har.js +7 -5
  14. package/packages/@monomind/cli/dist/src/browser/network.d.ts +7 -4
  15. package/packages/@monomind/cli/dist/src/browser/network.js +60 -23
  16. package/packages/@monomind/cli/dist/src/browser/profiler.js +13 -2
  17. package/packages/@monomind/cli/dist/src/browser/screenshot.js +4 -2
  18. package/packages/@monomind/cli/dist/src/browser/session.js +49 -12
  19. package/packages/@monomind/cli/dist/src/browser/snapshot.js +26 -14
  20. package/packages/@monomind/cli/dist/src/browser/storage.d.ts +1 -0
  21. package/packages/@monomind/cli/dist/src/browser/storage.js +3 -0
  22. package/packages/@monomind/cli/dist/src/browser/tabs.d.ts +3 -3
  23. package/packages/@monomind/cli/dist/src/browser/tabs.js +13 -13
  24. package/packages/@monomind/cli/dist/src/browser/trace.js +10 -4
  25. package/packages/@monomind/cli/dist/src/browser/wait.js +24 -14
  26. package/packages/@monomind/cli/dist/src/commands/browse.js +300 -34
  27. 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, sessionId, targetId) {
10
- await client.send('Target.closeTarget', { targetId }, sessionId);
9
+ export async function closeTab(client, _sessionId, targetId) {
10
+ await client.send('Target.closeTarget', { targetId });
11
11
  }
12
- export async function activateTab(client, sessionId, targetId) {
13
- await client.send('Target.activateTarget', { targetId }, sessionId);
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(client, sessionId, frameSelector) {
16
- const result = await client.send('Runtime.evaluate', {
17
- expression: `
18
- const frame = document.querySelector(${JSON.stringify(frameSelector)});
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 timedOut = false;
54
- const timeoutPromise = new Promise((_, reject) => setTimeout(() => { timedOut = true; cancelOnce(); reject(new Error('Timeout waiting for Tracing.tracingComplete')); }, 30_000));
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
- if (timedOut)
64
- throw new Error('Timeout waiting for Tracing.tracingComplete');
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
- const timeoutPromise = sleep(timeout).then(() => { timedOut = true; });
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', (_, sid) => {
78
- if (sid === sessionId) {
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 offResp = client.on('Network.loadingFinished', (_, sid) => {
84
- if (sid === sessionId) {
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 offFail = client.on('Network.loadingFailed', (_, sid) => {
90
- if (sid === sessionId) {
91
- pending = Math.max(0, pending - 1);
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.innerText');
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.innerText');
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.innerText'));
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
- switch (direction) {
417
- case 'back':
418
- await client.send('Runtime.evaluate', { expression: 'history.back()' }, sessionId);
419
- break;
420
- case 'forward':
421
- await client.send('Runtime.evaluate', { expression: 'history.forward()' }, sessionId);
422
- break;
423
- case 'reload':
424
- await client.send('Page.reload', {}, sessionId);
425
- break;
426
- default:
427
- throw new Error(`Unknown direction: ${direction}. Use: back|forward|reload`);
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 enabled = ctx.args[1] !== 'false';
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
- _client.close();
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
- await browser.pressKeyCombo(client, sessionId, 'c', 2); // Ctrl modifier
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
- case 'paste':
1001
- await browser.pressKeyCombo(client, sessionId, 'v', 2); // Ctrl modifier
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
- await browser.closeTab(client, sessionId, tabId);
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
- // Try to treat arg as tab ID to switch to
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 msgs = browser.getConsoleMessages();
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.trim().split(/\s+/);
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
- // Parse --flags from subArgs
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
- if (next && !next.startsWith('--')) {
1483
- parsedFlags[key] = next;
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((a) => !a.startsWith('--')),
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
- results.push({ command: cmdStr, success: true });
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.39",
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",