@monoes/monobrowse 1.0.2 → 1.0.4
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/__tests__/batch-eval-parsing.test.d.ts +2 -0
- package/dist/src/__tests__/batch-eval-parsing.test.d.ts.map +1 -0
- package/dist/src/__tests__/batch-eval-parsing.test.js +47 -0
- package/dist/src/__tests__/batch-eval-parsing.test.js.map +1 -0
- package/dist/src/__tests__/output.test.d.ts +2 -0
- package/dist/src/__tests__/output.test.d.ts.map +1 -0
- package/dist/src/__tests__/output.test.js +54 -0
- package/dist/src/__tests__/output.test.js.map +1 -0
- package/dist/src/__tests__/ref-cache.test.d.ts +2 -0
- package/dist/src/__tests__/ref-cache.test.d.ts.map +1 -0
- package/dist/src/__tests__/ref-cache.test.js +145 -0
- package/dist/src/__tests__/ref-cache.test.js.map +1 -0
- package/dist/src/browser/action-builder/analyzer.d.ts +0 -0
- package/dist/src/browser/action-builder/analyzer.d.ts.map +0 -0
- package/dist/src/browser/action-builder/analyzer.js +0 -0
- package/dist/src/browser/action-builder/analyzer.js.map +1 -1
- package/dist/src/browser/action-builder/types.d.ts +0 -0
- package/dist/src/browser/action-builder/types.d.ts.map +0 -0
- package/dist/src/browser/action-builder/types.js +0 -0
- package/dist/src/browser/action-builder/types.js.map +0 -0
- package/dist/src/browser/actions.d.ts +1 -1
- package/dist/src/browser/actions.d.ts.map +1 -1
- package/dist/src/browser/actions.js +19 -2
- package/dist/src/browser/actions.js.map +1 -1
- package/dist/src/browser/adapters/gemini.d.ts +0 -0
- package/dist/src/browser/adapters/gemini.d.ts.map +0 -0
- package/dist/src/browser/adapters/gemini.js +0 -0
- package/dist/src/browser/adapters/gemini.js.map +0 -0
- package/dist/src/browser/adapters/google.d.ts +0 -0
- package/dist/src/browser/adapters/google.d.ts.map +0 -0
- package/dist/src/browser/adapters/google.js +0 -0
- package/dist/src/browser/adapters/google.js.map +0 -0
- package/dist/src/browser/adapters/index.d.ts +0 -0
- package/dist/src/browser/adapters/index.d.ts.map +0 -0
- package/dist/src/browser/adapters/index.js +0 -0
- package/dist/src/browser/adapters/index.js.map +0 -0
- package/dist/src/browser/adapters/instagram.d.ts +0 -0
- package/dist/src/browser/adapters/instagram.d.ts.map +0 -0
- package/dist/src/browser/adapters/instagram.js +0 -0
- package/dist/src/browser/adapters/instagram.js.map +0 -0
- package/dist/src/browser/adapters/linkedin.d.ts +0 -0
- package/dist/src/browser/adapters/linkedin.d.ts.map +0 -0
- package/dist/src/browser/adapters/linkedin.js +0 -0
- package/dist/src/browser/adapters/linkedin.js.map +0 -0
- package/dist/src/browser/adapters/microsoft.d.ts +0 -0
- package/dist/src/browser/adapters/microsoft.d.ts.map +0 -0
- package/dist/src/browser/adapters/microsoft.js +0 -0
- package/dist/src/browser/adapters/microsoft.js.map +0 -0
- package/dist/src/browser/adapters/x.d.ts +0 -0
- package/dist/src/browser/adapters/x.d.ts.map +0 -0
- package/dist/src/browser/adapters/x.js +0 -0
- package/dist/src/browser/adapters/x.js.map +0 -0
- package/dist/src/browser/batch.d.ts +0 -0
- package/dist/src/browser/batch.d.ts.map +0 -0
- package/dist/src/browser/batch.js +0 -0
- package/dist/src/browser/batch.js.map +0 -0
- package/dist/src/browser/browser.d.ts +10 -0
- package/dist/src/browser/browser.d.ts.map +1 -1
- package/dist/src/browser/browser.js +111 -3
- package/dist/src/browser/browser.js.map +1 -1
- package/dist/src/browser/cdp.d.ts +0 -0
- package/dist/src/browser/cdp.d.ts.map +1 -1
- package/dist/src/browser/cdp.js +1 -1
- package/dist/src/browser/cdp.js.map +1 -1
- package/dist/src/browser/console-log.d.ts +0 -0
- package/dist/src/browser/console-log.d.ts.map +0 -0
- package/dist/src/browser/console-log.js +0 -0
- package/dist/src/browser/console-log.js.map +0 -0
- package/dist/src/browser/dashboard/api-types.d.ts +0 -0
- package/dist/src/browser/dashboard/api-types.d.ts.map +0 -0
- package/dist/src/browser/dashboard/api-types.js +0 -0
- package/dist/src/browser/dashboard/api-types.js.map +0 -0
- package/dist/src/browser/dashboard/server.d.ts +0 -0
- package/dist/src/browser/dashboard/server.d.ts.map +1 -1
- package/dist/src/browser/dashboard/server.js +37 -2
- package/dist/src/browser/dashboard/server.js.map +1 -1
- package/dist/src/browser/dashboard/ui.html +1 -1
- package/dist/src/browser/dialog.d.ts +0 -0
- package/dist/src/browser/dialog.d.ts.map +0 -0
- package/dist/src/browser/dialog.js +0 -0
- package/dist/src/browser/dialog.js.map +0 -0
- package/dist/src/browser/emulation.d.ts +0 -0
- package/dist/src/browser/emulation.d.ts.map +0 -0
- package/dist/src/browser/emulation.js +0 -0
- package/dist/src/browser/emulation.js.map +0 -0
- package/dist/src/browser/find.d.ts +0 -0
- package/dist/src/browser/find.d.ts.map +0 -0
- package/dist/src/browser/find.js +0 -0
- package/dist/src/browser/find.js.map +1 -1
- package/dist/src/browser/har.d.ts +0 -0
- package/dist/src/browser/har.d.ts.map +0 -0
- package/dist/src/browser/har.js +0 -0
- package/dist/src/browser/har.js.map +0 -0
- package/dist/src/browser/index.d.ts +1 -0
- package/dist/src/browser/index.d.ts.map +1 -1
- package/dist/src/browser/index.js +1 -0
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/browser/network.d.ts +7 -0
- package/dist/src/browser/network.d.ts.map +1 -1
- package/dist/src/browser/network.js +10 -0
- package/dist/src/browser/network.js.map +1 -1
- package/dist/src/browser/pdf.d.ts +0 -0
- package/dist/src/browser/pdf.d.ts.map +0 -0
- package/dist/src/browser/pdf.js +0 -0
- package/dist/src/browser/pdf.js.map +1 -1
- package/dist/src/browser/profiler.d.ts +0 -0
- package/dist/src/browser/profiler.d.ts.map +0 -0
- package/dist/src/browser/profiler.js +0 -0
- package/dist/src/browser/profiler.js.map +1 -1
- package/dist/src/browser/record.d.ts +3 -0
- package/dist/src/browser/record.d.ts.map +1 -1
- package/dist/src/browser/record.js +37 -5
- package/dist/src/browser/record.js.map +1 -1
- package/dist/src/browser/ref-cache.d.ts +41 -0
- package/dist/src/browser/ref-cache.d.ts.map +1 -0
- package/dist/src/browser/ref-cache.js +120 -0
- package/dist/src/browser/ref-cache.js.map +1 -0
- package/dist/src/browser/screenshot.d.ts +0 -0
- package/dist/src/browser/screenshot.d.ts.map +0 -0
- package/dist/src/browser/screenshot.js +0 -0
- package/dist/src/browser/screenshot.js.map +1 -1
- package/dist/src/browser/session.d.ts +0 -0
- package/dist/src/browser/session.d.ts.map +1 -1
- package/dist/src/browser/session.js +12 -7
- package/dist/src/browser/session.js.map +1 -1
- package/dist/src/browser/snapshot.d.ts +0 -0
- package/dist/src/browser/snapshot.d.ts.map +0 -0
- package/dist/src/browser/snapshot.js +0 -0
- package/dist/src/browser/snapshot.js.map +1 -1
- package/dist/src/browser/storage.d.ts +0 -0
- package/dist/src/browser/storage.d.ts.map +0 -0
- package/dist/src/browser/storage.js +0 -0
- package/dist/src/browser/storage.js.map +0 -0
- package/dist/src/browser/tabs.d.ts +1 -1
- package/dist/src/browser/tabs.d.ts.map +1 -1
- package/dist/src/browser/tabs.js +20 -4
- package/dist/src/browser/tabs.js.map +1 -1
- package/dist/src/browser/trace.d.ts +0 -0
- package/dist/src/browser/trace.d.ts.map +0 -0
- package/dist/src/browser/trace.js +0 -0
- package/dist/src/browser/trace.js.map +1 -1
- package/dist/src/browser/types.d.ts +0 -0
- package/dist/src/browser/types.d.ts.map +0 -0
- package/dist/src/browser/types.js +0 -0
- package/dist/src/browser/types.js.map +0 -0
- package/dist/src/browser/vitals.d.ts +0 -0
- package/dist/src/browser/vitals.d.ts.map +0 -0
- package/dist/src/browser/vitals.js +0 -0
- package/dist/src/browser/vitals.js.map +0 -0
- package/dist/src/browser/wait.d.ts +0 -0
- package/dist/src/browser/wait.d.ts.map +0 -0
- package/dist/src/browser/wait.js +0 -0
- package/dist/src/browser/wait.js.map +0 -0
- package/dist/src/cli/action.d.ts +0 -0
- package/dist/src/cli/action.d.ts.map +1 -1
- package/dist/src/cli/action.js +87 -2
- package/dist/src/cli/action.js.map +1 -1
- package/dist/src/cli/commands.d.ts +22 -0
- package/dist/src/cli/commands.d.ts.map +1 -1
- package/dist/src/cli/commands.js +276 -28
- package/dist/src/cli/commands.js.map +1 -1
- package/dist/src/cli/output.d.ts +0 -0
- package/dist/src/cli/output.d.ts.map +1 -1
- package/dist/src/cli/output.js +0 -0
- package/dist/src/cli/output.js.map +0 -0
- package/dist/src/cli/platform.d.ts +0 -0
- package/dist/src/cli/platform.d.ts.map +0 -0
- package/dist/src/cli/platform.js +0 -0
- package/dist/src/cli/platform.js.map +0 -0
- package/dist/src/cli/types.d.ts +0 -0
- package/dist/src/cli/types.d.ts.map +0 -0
- package/dist/src/cli/types.js +0 -0
- package/dist/src/cli/types.js.map +0 -0
- package/dist/src/cli.d.ts +0 -0
- package/dist/src/cli.d.ts.map +0 -0
- package/dist/src/cli.js.map +1 -1
- package/dist/src/index.d.ts +0 -0
- package/dist/src/index.d.ts.map +0 -0
- package/dist/src/index.js +0 -0
- package/dist/src/index.js.map +0 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/__tests__/batch-eval-parsing.test.ts +52 -0
- package/src/__tests__/output.test.ts +62 -0
- package/src/__tests__/ref-cache.test.ts +175 -0
- package/src/browser/action-builder/analyzer.ts +0 -0
- package/src/browser/action-builder/types.ts +0 -0
- package/src/browser/actions.ts +22 -2
- package/src/browser/adapters/gemini.ts +0 -0
- package/src/browser/adapters/google.ts +0 -0
- package/src/browser/adapters/index.ts +0 -0
- package/src/browser/adapters/instagram.ts +0 -0
- package/src/browser/adapters/linkedin.ts +0 -0
- package/src/browser/adapters/microsoft.ts +0 -0
- package/src/browser/adapters/x.ts +0 -0
- package/src/browser/batch.ts +0 -0
- package/src/browser/browser.ts +115 -2
- package/src/browser/cdp.ts +1 -1
- package/src/browser/console-log.ts +0 -0
- package/src/browser/dashboard/api-types.ts +0 -0
- package/src/browser/dashboard/server.ts +37 -2
- package/src/browser/dashboard/ui.html +1 -1
- package/src/browser/dialog.ts +0 -0
- package/src/browser/emulation.ts +0 -0
- package/src/browser/find.ts +0 -0
- package/src/browser/har.ts +0 -0
- package/src/browser/index.ts +1 -0
- package/src/browser/network.ts +11 -0
- package/src/browser/pdf.ts +0 -0
- package/src/browser/profiler.ts +0 -0
- package/src/browser/record.ts +43 -6
- package/src/browser/ref-cache.ts +141 -0
- package/src/browser/screenshot.ts +0 -0
- package/src/browser/session.ts +13 -7
- package/src/browser/snapshot.ts +0 -0
- package/src/browser/storage.ts +0 -0
- package/src/browser/tabs.ts +36 -7
- package/src/browser/trace.ts +0 -0
- package/src/browser/types.ts +0 -0
- package/src/browser/vitals.ts +0 -0
- package/src/browser/wait.ts +0 -0
- package/src/cli/action.ts +85 -3
- package/src/cli/commands.ts +272 -28
- package/src/cli/output.ts +0 -0
- package/src/cli/platform.ts +0 -0
- package/src/cli/types.ts +0 -0
- package/src/cli.ts +0 -0
- package/src/index.ts +0 -0
- package/dist/package.json +0 -71
- package/dist/src/browser/playbook/browser-handlers.d.ts +0 -3
- package/dist/src/browser/playbook/browser-handlers.d.ts.map +0 -1
- package/dist/src/browser/playbook/browser-handlers.js +0 -377
- package/dist/src/browser/playbook/browser-handlers.js.map +0 -1
- package/dist/src/browser/playbook/builtin-handlers.d.ts +0 -3
- package/dist/src/browser/playbook/builtin-handlers.d.ts.map +0 -1
- package/dist/src/browser/playbook/builtin-handlers.js +0 -364
- package/dist/src/browser/playbook/builtin-handlers.js.map +0 -1
- package/dist/src/browser/playbook/index.d.ts +0 -3
- package/dist/src/browser/playbook/index.d.ts.map +0 -1
- package/dist/src/browser/playbook/index.js +0 -3
- package/dist/src/browser/playbook/index.js.map +0 -1
- package/dist/src/browser/playbook/store.d.ts +0 -8
- package/dist/src/browser/playbook/store.d.ts.map +0 -1
- package/dist/src/browser/playbook/store.js +0 -87
- package/dist/src/browser/playbook/store.js.map +0 -1
- package/dist/src/browser/workflow/builtin-handlers.d.ts +0 -3
- package/dist/src/browser/workflow/builtin-handlers.d.ts.map +0 -1
- package/dist/src/browser/workflow/builtin-handlers.js +0 -343
- package/dist/src/browser/workflow/builtin-handlers.js.map +0 -1
- package/dist/src/browser/workflow/engine.d.ts +0 -10
- package/dist/src/browser/workflow/engine.d.ts.map +0 -1
- package/dist/src/browser/workflow/engine.js +0 -174
- package/dist/src/browser/workflow/engine.js.map +0 -1
- package/dist/src/browser/workflow/expression.d.ts +0 -4
- package/dist/src/browser/workflow/expression.d.ts.map +0 -1
- package/dist/src/browser/workflow/expression.js +0 -89
- package/dist/src/browser/workflow/expression.js.map +0 -1
- package/dist/src/browser/workflow/index.d.ts +0 -6
- package/dist/src/browser/workflow/index.d.ts.map +0 -1
- package/dist/src/browser/workflow/index.js +0 -6
- package/dist/src/browser/workflow/index.js.map +0 -1
- package/dist/src/browser/workflow/store.d.ts +0 -8
- package/dist/src/browser/workflow/store.d.ts.map +0 -1
- package/dist/src/browser/workflow/store.js +0 -87
- package/dist/src/browser/workflow/store.js.map +0 -1
- package/dist/src/browser/workflow/types.d.ts +0 -62
- package/dist/src/browser/workflow/types.d.ts.map +0 -1
- package/dist/src/browser/workflow/types.js +0 -2
- package/dist/src/browser/workflow/types.js.map +0 -1
- package/dist/src/cli/playbook.d.ts +0 -3
- package/dist/src/cli/playbook.d.ts.map +0 -1
- package/dist/src/cli/playbook.js +0 -265
- package/dist/src/cli/playbook.js.map +0 -1
- package/dist/src/cli/workflow.d.ts +0 -2
- package/dist/src/cli/workflow.d.ts.map +0 -1
- package/dist/src/cli/workflow.js +0 -3
- package/dist/src/cli/workflow.js.map +0 -1
package/dist/src/cli/commands.js
CHANGED
|
@@ -6,14 +6,41 @@ import { output } from './output.js';
|
|
|
6
6
|
import { createActionCommand } from './action.js';
|
|
7
7
|
import { createPlatformCommand } from './platform.js';
|
|
8
8
|
// Runtime state (single session per CLI process)
|
|
9
|
+
const DEFAULT_PORT = 9222;
|
|
9
10
|
let _client = null;
|
|
10
11
|
let _sessionId = '';
|
|
11
12
|
let _targetId = '';
|
|
12
|
-
let _port =
|
|
13
|
+
let _port = DEFAULT_PORT;
|
|
13
14
|
let _refs = new Map();
|
|
14
15
|
async function getBrowser() {
|
|
15
16
|
return import('../index.js');
|
|
16
17
|
}
|
|
18
|
+
// Each CLI invocation is a fresh process, so this module's `_port` variable
|
|
19
|
+
// is always freshly initialized to DEFAULT_PORT — a `--port 9333` passed to
|
|
20
|
+
// an earlier `open` command has no effect on this process's own default.
|
|
21
|
+
// Resolve the persisted "active port" (written by `open --port N`, read via
|
|
22
|
+
// ref-cache.ts's saveActivePort/loadActivePort) so subsequent commands
|
|
23
|
+
// attach to the browser the user actually opened instead of silently
|
|
24
|
+
// launching/attaching to a second, unrelated Chrome instance on 9222.
|
|
25
|
+
async function resolveDefaultPort(browser) {
|
|
26
|
+
const persisted = await browser.loadActivePortInfo();
|
|
27
|
+
if (!persisted)
|
|
28
|
+
return DEFAULT_PORT;
|
|
29
|
+
if (!persisted.launched) {
|
|
30
|
+
// connect-origin port: the browser belongs to someone else. If it died,
|
|
31
|
+
// relaunching our own headless Chrome on that port would squat the
|
|
32
|
+
// user's debug port and silently swap which browser commands act on —
|
|
33
|
+
// fail loudly instead.
|
|
34
|
+
try {
|
|
35
|
+
await fetch(`http://127.0.0.1:${persisted.port}/json/version`, { signal: AbortSignal.timeout(1500) });
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
await browser.clearActivePort();
|
|
39
|
+
throw new Error(`Connected browser on port ${persisted.port} is gone — re-run \`connect\` (or \`open\` to launch a fresh one).`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return persisted.port;
|
|
43
|
+
}
|
|
17
44
|
async function ensureConnected(port, targetId) {
|
|
18
45
|
const browser = await getBrowser();
|
|
19
46
|
if (!_client || !_client.isConnected()) {
|
|
@@ -24,15 +51,45 @@ async function ensureConnected(port, targetId) {
|
|
|
24
51
|
browser.teardownConsoleCapture(_sessionId);
|
|
25
52
|
_client.close();
|
|
26
53
|
}
|
|
27
|
-
|
|
54
|
+
const effectivePort = port === DEFAULT_PORT ? await resolveDefaultPort(browser) : port;
|
|
55
|
+
_port = await browser.launchBrowser({ port: effectivePort, headless: true });
|
|
28
56
|
const conn = await browser.connectToTarget(_port, targetId);
|
|
29
57
|
_client = conn.client;
|
|
30
58
|
_sessionId = conn.sessionId;
|
|
31
59
|
_targetId = conn.target.id;
|
|
32
60
|
_refs = new Map();
|
|
61
|
+
await hydrateRefsFromCache(browser, _targetId, conn.target.url);
|
|
33
62
|
}
|
|
34
63
|
return { client: _client, sessionId: _sessionId, targetId: _targetId };
|
|
35
64
|
}
|
|
65
|
+
// Each CLI invocation is a fresh process, so the in-memory _refs Map built by
|
|
66
|
+
// a prior `snapshot` command is gone by the time a later `find`/`click`/etc.
|
|
67
|
+
// command runs. Rehydrate it from the on-disk ref cache (written by
|
|
68
|
+
// captureSnapshot call sites below) so refs resolved by a previous process
|
|
69
|
+
// remain usable. Falls back silently to an empty Map if no cache exists.
|
|
70
|
+
//
|
|
71
|
+
// The cached `url` field (captured at snapshot time) is compared against the
|
|
72
|
+
// browser's CURRENT url (`currentUrl`, from the just-fetched target info).
|
|
73
|
+
// A stale `backendDOMNodeId` can still successfully resolve via
|
|
74
|
+
// DOM.getBoxModel even after a same-tab SPA navigation or DOM mutation
|
|
75
|
+
// changed what's actually at those coordinates — so a mismatch here means
|
|
76
|
+
// EVERY ref in the cache is potentially pointing at the wrong element. We
|
|
77
|
+
// hard-invalidate (skip hydration entirely, so any @eN lookup fails loudly
|
|
78
|
+
// via resolveRef's "not found" error) rather than the weaker 30s time-based
|
|
79
|
+
// staleness check below, which only warns.
|
|
80
|
+
async function hydrateRefsFromCache(browser, targetId, currentUrl) {
|
|
81
|
+
const cached = await browser.loadRefCache(targetId);
|
|
82
|
+
if (!cached)
|
|
83
|
+
return;
|
|
84
|
+
if (currentUrl && cached.url && currentUrl !== cached.url) {
|
|
85
|
+
output.printError(`Stale references — page has navigated (cache: ${cached.url} → current: ${currentUrl}). Re-run snapshot before using @eN refs.`);
|
|
86
|
+
return; // leave _refs empty — do not attempt to resolve refs against a different page
|
|
87
|
+
}
|
|
88
|
+
_refs = cached.refs;
|
|
89
|
+
if (cached.stale) {
|
|
90
|
+
output.printWarning(`AX ref cache is ${Math.round(cached.ageMs / 1000)}s old — page may have changed since the last snapshot; re-run snapshot if refs don't resolve as expected`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
36
93
|
// URL patterns that signal an unambiguous login/auth wall.
|
|
37
94
|
// Excludes 'auth', 'oauth', 'sso', 'saml' — their callback/ACS/token paths
|
|
38
95
|
// (/auth/callback, /sso/callback, /saml/acs) are completion endpoints, not walls.
|
|
@@ -122,11 +179,14 @@ async function switchToHeaded(url, port) {
|
|
|
122
179
|
// Capture post-auth cookies
|
|
123
180
|
const authCookies = await browser.getCookies(_client, _sessionId).catch(() => []);
|
|
124
181
|
const authLocalStorage = await browser.getLocalStorage(_client, _sessionId).catch(() => ({}));
|
|
125
|
-
// Close headed session
|
|
182
|
+
// Close headed session — actually terminate the underlying Chrome process
|
|
183
|
+
// (Browser.close CDP command, PID-kill fallback), not just our CDP client
|
|
184
|
+
// connection, so the visible authenticated window doesn't linger forever.
|
|
126
185
|
browser.teardownRouteInterception(_sessionId);
|
|
127
186
|
browser.stopRequestCapture(_sessionId);
|
|
128
187
|
browser.teardownDialogHandling(_sessionId);
|
|
129
188
|
browser.teardownConsoleCapture(_sessionId);
|
|
189
|
+
await browser.closeBrowser(_client, headedPort);
|
|
130
190
|
_client.close();
|
|
131
191
|
_client = null;
|
|
132
192
|
_sessionId = '';
|
|
@@ -151,6 +211,17 @@ async function switchToHeaded(url, port) {
|
|
|
151
211
|
catch { /* ignore */ }
|
|
152
212
|
}
|
|
153
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* Sanitize an image-format flag. The host CLI (monomind) defines a GLOBAL
|
|
216
|
+
* --format flag for output shaping (text|json|table, default 'text') that
|
|
217
|
+
* leaks into subcommand flags — so ctx.flags.format arrives as 'text' even
|
|
218
|
+
* when the user never passed it, and Chrome rejects it with
|
|
219
|
+
* "CDP error -32602: Invalid image format". Only trust values that are real
|
|
220
|
+
* image formats; otherwise fall back to the subcommand's own default.
|
|
221
|
+
*/
|
|
222
|
+
function imageFormat(value, allowed, fallback) {
|
|
223
|
+
return allowed.includes(value) ? value : fallback;
|
|
224
|
+
}
|
|
154
225
|
function print(msg) {
|
|
155
226
|
process.stdout.write(msg + '\n');
|
|
156
227
|
}
|
|
@@ -205,11 +276,18 @@ const openCommand = {
|
|
|
205
276
|
_refs = new Map();
|
|
206
277
|
}
|
|
207
278
|
_port = await browser.launchBrowser({ port, headless: !forceHeaded });
|
|
279
|
+
// Persist the active port so subsequent CLI invocations (each a fresh
|
|
280
|
+
// process) default to attaching here instead of hardcoded 9222.
|
|
281
|
+
await browser.saveActivePort(_port);
|
|
208
282
|
const conn = await browser.connectToTarget(_port);
|
|
209
283
|
_client = conn.client;
|
|
210
284
|
_sessionId = conn.sessionId;
|
|
211
285
|
_targetId = conn.target.id;
|
|
212
286
|
_refs = new Map();
|
|
287
|
+
// A snapshot taken before this navigation is no longer valid for the new
|
|
288
|
+
// page — drop the persisted ref cache so a stale process's weak
|
|
289
|
+
// time-based check can't resurrect it before the next explicit snapshot.
|
|
290
|
+
await browser.clearRefCache();
|
|
213
291
|
if (ctx.flags.state && ctx.flags.session) {
|
|
214
292
|
output.printWarning('Both --state and --session provided; --state takes precedence');
|
|
215
293
|
}
|
|
@@ -261,12 +339,10 @@ const snapshotCommand = {
|
|
|
261
339
|
selector: ctx.flags.selector,
|
|
262
340
|
});
|
|
263
341
|
_refs = result.refs;
|
|
342
|
+
await browser.saveRefCache(_targetId, result.url, _refs);
|
|
264
343
|
const applyOutputLimits = (text) => {
|
|
265
|
-
let out = text;
|
|
266
344
|
const maxOutput = ctx.flags['max-output'];
|
|
267
|
-
|
|
268
|
-
out = out.slice(0, maxOutput) + `\n[... truncated at ${maxOutput} chars]`;
|
|
269
|
-
}
|
|
345
|
+
let out = maxOutput ? truncateForOutput(text, maxOutput) : text;
|
|
270
346
|
if (ctx.flags['content-boundaries']) {
|
|
271
347
|
const nonce = Math.random().toString(36).slice(2, 10);
|
|
272
348
|
out = `MONOMIND_PAGE_CONTENT nonce=${nonce} origin=${result.url}\n${out}\nEND_MONOMIND_PAGE_CONTENT nonce=${nonce}`;
|
|
@@ -464,16 +540,31 @@ const waitCommand = {
|
|
|
464
540
|
const rawTimeout = Math.min(ctx.flags.timeout ?? 30000, MAX_DOWNLOAD_TIMEOUT);
|
|
465
541
|
const finalPath = await new Promise((resolve, reject) => {
|
|
466
542
|
let guid = '';
|
|
543
|
+
let settled = false;
|
|
467
544
|
// C2: capture off() functions to avoid listener leaks
|
|
468
545
|
const offBegin = client.on('Browser.downloadWillBegin', (params) => { guid = params.guid; });
|
|
469
546
|
let offProgress;
|
|
470
547
|
// cleanup defined before setTimeout so the timeout callback can call it
|
|
471
548
|
let tid;
|
|
472
|
-
|
|
473
|
-
|
|
549
|
+
let pollTid;
|
|
550
|
+
const cleanup = () => { clearTimeout(tid); clearInterval(pollTid); offBegin?.(); offProgress?.(); };
|
|
551
|
+
const finish = (path) => {
|
|
552
|
+
if (settled)
|
|
553
|
+
return;
|
|
554
|
+
settled = true;
|
|
555
|
+
cleanup();
|
|
556
|
+
resolve(path);
|
|
557
|
+
};
|
|
558
|
+
const fail = (err) => {
|
|
559
|
+
if (settled)
|
|
560
|
+
return;
|
|
561
|
+
settled = true;
|
|
562
|
+
cleanup();
|
|
563
|
+
reject(err);
|
|
564
|
+
};
|
|
565
|
+
tid = setTimeout(() => fail(new Error('Download timed out')), rawTimeout);
|
|
474
566
|
offProgress = client.on('Browser.downloadProgress', async (params) => {
|
|
475
567
|
if (params.guid === guid && params.state === 'completed') {
|
|
476
|
-
cleanup();
|
|
477
568
|
const { readdir, rename, rmdir } = await import('fs/promises');
|
|
478
569
|
const files = await readdir(downloadDir);
|
|
479
570
|
if (files.length > 0) {
|
|
@@ -481,18 +572,45 @@ const waitCommand = {
|
|
|
481
572
|
await mkdir(dirname(savePath), { recursive: true });
|
|
482
573
|
await rename(src, savePath);
|
|
483
574
|
await rmdir(downloadDir).catch(() => { }); // I1: cleanup temp dir
|
|
484
|
-
|
|
575
|
+
finish(savePath);
|
|
485
576
|
}
|
|
486
577
|
else {
|
|
487
578
|
await rmdir(downloadDir).catch(() => { }); // I1: cleanup temp dir
|
|
488
|
-
|
|
579
|
+
fail(new Error('Download completed but no file found'));
|
|
489
580
|
}
|
|
490
581
|
}
|
|
491
582
|
else if (params.guid === guid && params.state === 'canceled') {
|
|
492
|
-
|
|
493
|
-
reject(new Error('Download was canceled'));
|
|
583
|
+
fail(new Error('Download was canceled'));
|
|
494
584
|
}
|
|
495
585
|
});
|
|
586
|
+
// Fallback for the empty-guid race: this process's listener attaches
|
|
587
|
+
// AFTER a separate `click` process may have already started (and even
|
|
588
|
+
// finished) the download — so downloadWillBegin/downloadProgress for it
|
|
589
|
+
// were never observed here. While guid is still empty, poll the expected
|
|
590
|
+
// output path directly: if it already exists with a stable, non-zero
|
|
591
|
+
// size across two consecutive checks, treat the download as complete.
|
|
592
|
+
let lastSize = -1;
|
|
593
|
+
let stableReads = 0;
|
|
594
|
+
pollTid = setInterval(async () => {
|
|
595
|
+
if (settled || guid)
|
|
596
|
+
return; // a real CDP event has taken over
|
|
597
|
+
try {
|
|
598
|
+
const { stat } = await import('fs/promises');
|
|
599
|
+
const st = await stat(savePath);
|
|
600
|
+
if (st.isFile() && st.size > 0 && st.size === lastSize) {
|
|
601
|
+
stableReads++;
|
|
602
|
+
if (stableReads >= 2)
|
|
603
|
+
finish(savePath);
|
|
604
|
+
}
|
|
605
|
+
else {
|
|
606
|
+
stableReads = 0;
|
|
607
|
+
}
|
|
608
|
+
lastSize = st.size;
|
|
609
|
+
}
|
|
610
|
+
catch {
|
|
611
|
+
// savePath not present yet — keep polling until timeout
|
|
612
|
+
}
|
|
613
|
+
}, 300);
|
|
496
614
|
});
|
|
497
615
|
output.printSuccess(`Download saved: ${finalPath}`);
|
|
498
616
|
return { success: true, data: { path: finalPath } };
|
|
@@ -536,7 +654,7 @@ const screenshotCommand = {
|
|
|
536
654
|
result = await browser.captureScreenshot(client, sessionId, {
|
|
537
655
|
path: ctx.args[0],
|
|
538
656
|
fullPage: ctx.flags.full,
|
|
539
|
-
format: ctx.flags.format,
|
|
657
|
+
format: imageFormat(ctx.flags.format, ['png', 'jpeg', 'webp'], 'png'),
|
|
540
658
|
quality: ctx.flags.quality,
|
|
541
659
|
annotate,
|
|
542
660
|
refs: annotate ? _refs : undefined,
|
|
@@ -780,6 +898,9 @@ const navigateCommand = {
|
|
|
780
898
|
else {
|
|
781
899
|
throw new Error(`Unknown direction: ${direction}. Use: back|forward|reload`);
|
|
782
900
|
}
|
|
901
|
+
// Refs captured before this navigation may now point at different content.
|
|
902
|
+
_refs = new Map();
|
|
903
|
+
await browser.clearRefCache();
|
|
783
904
|
output.printSuccess(`Navigated: ${direction}`);
|
|
784
905
|
return { success: true };
|
|
785
906
|
},
|
|
@@ -935,6 +1056,7 @@ const stateCommand = {
|
|
|
935
1056
|
await browser.clearLocalStorage(c, sid);
|
|
936
1057
|
await browser.clearSessionStorage(c, sid);
|
|
937
1058
|
_refs = new Map();
|
|
1059
|
+
await browser.clearRefCache();
|
|
938
1060
|
output.printSuccess('Browser state cleared (cookies, localStorage, sessionStorage, refs)');
|
|
939
1061
|
return { success: true };
|
|
940
1062
|
}
|
|
@@ -1111,12 +1233,25 @@ const networkCommand = {
|
|
|
1111
1233
|
return { success: true };
|
|
1112
1234
|
},
|
|
1113
1235
|
};
|
|
1236
|
+
// Default output cap for eval when --max-output isn't passed explicitly —
|
|
1237
|
+
// print(String(result)) had NO limit at all (unlike snapshot's --max-output),
|
|
1238
|
+
// so e.g. `eval "document.documentElement.outerHTML"` could dump megabytes
|
|
1239
|
+
// straight into an agent's context. An explicit --max-output still overrides
|
|
1240
|
+
// this default.
|
|
1241
|
+
const DEFAULT_EVAL_MAX_OUTPUT = 50_000;
|
|
1242
|
+
function truncateForOutput(text, maxOutput) {
|
|
1243
|
+
if (!(maxOutput > 0) || text.length <= maxOutput)
|
|
1244
|
+
return text;
|
|
1245
|
+
return text.slice(0, maxOutput) + `\n[... truncated at ${maxOutput} chars]`;
|
|
1246
|
+
}
|
|
1114
1247
|
const evalCommand = {
|
|
1115
1248
|
name: 'eval',
|
|
1116
1249
|
description: 'Evaluate JavaScript in page context. Usage: monomind browse eval "document.title"',
|
|
1117
1250
|
options: [
|
|
1118
1251
|
{ name: 'json', type: 'boolean', description: 'Output as JSON', default: false },
|
|
1119
1252
|
{ name: 'stdin', type: 'boolean', description: 'Read JS expression from stdin (heredoc-friendly for multiline scripts)', default: false },
|
|
1253
|
+
{ name: 'max-output', type: 'number', description: `Truncate printed output to N characters (default ${DEFAULT_EVAL_MAX_OUTPUT}; 0 disables truncation)` },
|
|
1254
|
+
{ name: 'timeout', type: 'number', description: 'Max ms to wait for evaluation to settle (default 30000)' },
|
|
1120
1255
|
],
|
|
1121
1256
|
action: async (ctx) => {
|
|
1122
1257
|
const { client, sessionId } = await ensureConnected(_port);
|
|
@@ -1130,12 +1265,14 @@ const evalCommand = {
|
|
|
1130
1265
|
}
|
|
1131
1266
|
if (!expr)
|
|
1132
1267
|
throw new Error('Usage: monomind browse eval "<expression>" (or pipe with --stdin)');
|
|
1133
|
-
const
|
|
1268
|
+
const timeoutMs = ctx.flags.timeout;
|
|
1269
|
+
const result = await browser.evaluateJs(client, sessionId, expr, timeoutMs);
|
|
1270
|
+
const maxOutput = ctx.flags['max-output'] ?? DEFAULT_EVAL_MAX_OUTPUT;
|
|
1134
1271
|
if (ctx.flags.json) {
|
|
1135
|
-
print(JSON.stringify({ data: result }));
|
|
1272
|
+
print(truncateForOutput(JSON.stringify({ data: result }), maxOutput));
|
|
1136
1273
|
}
|
|
1137
1274
|
else {
|
|
1138
|
-
print(String(result ?? ''));
|
|
1275
|
+
print(truncateForOutput(String(result ?? ''), maxOutput));
|
|
1139
1276
|
}
|
|
1140
1277
|
return { success: true, data: { result } };
|
|
1141
1278
|
},
|
|
@@ -1176,10 +1313,66 @@ const closeCommand = {
|
|
|
1176
1313
|
_sessionId = '';
|
|
1177
1314
|
_targetId = '';
|
|
1178
1315
|
_refs = new Map();
|
|
1316
|
+
// Session is gone — forget the persisted port and stale refs so later
|
|
1317
|
+
// invocations don't chase a dead endpoint (or the wrong elements).
|
|
1318
|
+
await browser.clearActivePort();
|
|
1319
|
+
await browser.clearRefCache();
|
|
1179
1320
|
output.printSuccess('Browser session closed');
|
|
1180
1321
|
}
|
|
1181
1322
|
else {
|
|
1182
|
-
|
|
1323
|
+
// Each CLI invocation is a fresh process, so `close` almost always
|
|
1324
|
+
// lands here. The persisted port file is the real session handle:
|
|
1325
|
+
// if monobrowse LAUNCHED that browser (open), gracefully Browser.close
|
|
1326
|
+
// it — otherwise every open→close cycle leaks a headless Chrome. If we
|
|
1327
|
+
// merely ATTACHED to it (connect, launched:false), never kill it: it's
|
|
1328
|
+
// the user's own browser. Either way, forget the port and refs.
|
|
1329
|
+
const browser = await getBrowser();
|
|
1330
|
+
const persisted = await browser.loadActivePortInfo();
|
|
1331
|
+
if (persisted?.launched) {
|
|
1332
|
+
try {
|
|
1333
|
+
const conn = await browser.connectToTarget(persisted.port);
|
|
1334
|
+
try {
|
|
1335
|
+
await browser.closeBrowser(conn.client, persisted.port);
|
|
1336
|
+
}
|
|
1337
|
+
finally {
|
|
1338
|
+
// Always drop our websocket — a hung Browser.close must not keep
|
|
1339
|
+
// this CLI process's event loop alive.
|
|
1340
|
+
try {
|
|
1341
|
+
conn.client.close();
|
|
1342
|
+
}
|
|
1343
|
+
catch { /* already gone */ }
|
|
1344
|
+
}
|
|
1345
|
+
// closeBrowser has no PID fallback in a fresh process (launchedPids
|
|
1346
|
+
// is per-process) — re-probe so we report what actually happened.
|
|
1347
|
+
// Poll briefly: Browser.close is acknowledged before the process
|
|
1348
|
+
// actually exits, so a single immediate probe false-alarms.
|
|
1349
|
+
let stillUp = true;
|
|
1350
|
+
const probeDeadline = Date.now() + 3000;
|
|
1351
|
+
while (stillUp && Date.now() < probeDeadline) {
|
|
1352
|
+
try {
|
|
1353
|
+
await fetch(`http://127.0.0.1:${persisted.port}/json/version`, { signal: AbortSignal.timeout(800) });
|
|
1354
|
+
await new Promise(r => setTimeout(r, 300));
|
|
1355
|
+
}
|
|
1356
|
+
catch {
|
|
1357
|
+
stillUp = false;
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
if (stillUp)
|
|
1361
|
+
output.printWarning(`Browser on port ${persisted.port} did not exit — kill it manually if needed`);
|
|
1362
|
+
else
|
|
1363
|
+
output.printSuccess(`Closed browser on port ${persisted.port}`);
|
|
1364
|
+
}
|
|
1365
|
+
catch {
|
|
1366
|
+
output.printInfo(`No browser answering on port ${persisted.port} — nothing to close`);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
else {
|
|
1370
|
+
output.printInfo(persisted
|
|
1371
|
+
? `Detached from browser on port ${persisted.port} (attached via connect — left running)`
|
|
1372
|
+
: 'No active browser session');
|
|
1373
|
+
}
|
|
1374
|
+
await browser.clearActivePort();
|
|
1375
|
+
await browser.clearRefCache();
|
|
1183
1376
|
}
|
|
1184
1377
|
return { success: true };
|
|
1185
1378
|
},
|
|
@@ -2318,6 +2511,7 @@ const diffCommand = {
|
|
|
2318
2511
|
await browser.waitFor(client, sessionId, { load: 'load', timeout: 15000 });
|
|
2319
2512
|
const snap2 = await browser.captureSnapshot(client, sessionId, { interactiveOnly: ctx.flags.interactive });
|
|
2320
2513
|
_refs = snap2.refs;
|
|
2514
|
+
await browser.saveRefCache(_targetId, snap2.url, _refs);
|
|
2321
2515
|
// Text diff
|
|
2322
2516
|
const lines1 = snap1.text.split('\n');
|
|
2323
2517
|
const lines2 = snap2.text.split('\n');
|
|
@@ -2356,6 +2550,10 @@ const pushstateCommand = {
|
|
|
2356
2550
|
if (!url)
|
|
2357
2551
|
throw new Error('Usage: monomind browse pushstate <url>');
|
|
2358
2552
|
await browser.pushState(client, sessionId, url);
|
|
2553
|
+
// SPA navigation changes what's on the page without a full page load —
|
|
2554
|
+
// refs captured before this pushState call may now resolve to different content.
|
|
2555
|
+
_refs = new Map();
|
|
2556
|
+
await browser.clearRefCache();
|
|
2359
2557
|
output.printSuccess(`pushState: ${url}`);
|
|
2360
2558
|
return { success: true };
|
|
2361
2559
|
},
|
|
@@ -2390,6 +2588,52 @@ function tokenizeBatchCommand(input) {
|
|
|
2390
2588
|
tokens.push(current);
|
|
2391
2589
|
return tokens;
|
|
2392
2590
|
}
|
|
2591
|
+
/**
|
|
2592
|
+
* Splits one line of a `batch` command string into a subcommand name, its
|
|
2593
|
+
* args, and any recognized leading flags.
|
|
2594
|
+
*
|
|
2595
|
+
* `eval` is special-cased: its sole argument is a raw JS expression, which
|
|
2596
|
+
* legitimately contains its own string-literal quotes (e.g.
|
|
2597
|
+
* `eval document.querySelector('a')`). tokenizeBatchCommand's shell-style
|
|
2598
|
+
* word-splitting treats `'...'`/`"..."` as grouping delimiters and discards
|
|
2599
|
+
* them — correct for a value like `fill @e1 "some text"`, but for `eval` it
|
|
2600
|
+
* silently deletes the expression's own quotes, turning a string literal
|
|
2601
|
+
* into a bare (undefined) identifier reference and producing a
|
|
2602
|
+
* ReferenceError instead of evaluating the intended expression. For `eval`,
|
|
2603
|
+
* only its own known --flags are consumed from the front; everything after
|
|
2604
|
+
* them is taken verbatim as one argument, untouched by tokenization.
|
|
2605
|
+
*
|
|
2606
|
+
* Exported for direct unit testing — not part of the CLI's public API.
|
|
2607
|
+
*/
|
|
2608
|
+
export function parseBatchCommandLine(cmdStr) {
|
|
2609
|
+
const trimmed = cmdStr.trim();
|
|
2610
|
+
const evalMatch = trimmed.match(/^eval\b\s*/);
|
|
2611
|
+
if (!evalMatch) {
|
|
2612
|
+
const parts = tokenizeBatchCommand(trimmed);
|
|
2613
|
+
return { subName: parts[0], subArgs: parts.slice(1), flags: {} };
|
|
2614
|
+
}
|
|
2615
|
+
let rest = trimmed.slice(evalMatch[0].length);
|
|
2616
|
+
const flags = {};
|
|
2617
|
+
let consumedAnother = true;
|
|
2618
|
+
while (consumedAnother) {
|
|
2619
|
+
consumedAnother = false;
|
|
2620
|
+
const withValue = rest.match(/^--(max-output|timeout)\s+(-?\d+)\s*/);
|
|
2621
|
+
if (withValue) {
|
|
2622
|
+
flags[withValue[1]] = Number(withValue[2]);
|
|
2623
|
+
rest = rest.slice(withValue[0].length);
|
|
2624
|
+
consumedAnother = true;
|
|
2625
|
+
continue;
|
|
2626
|
+
}
|
|
2627
|
+
const boolFlag = rest.match(/^--(json|stdin)\b\s*/);
|
|
2628
|
+
if (boolFlag) {
|
|
2629
|
+
flags[boolFlag[1]] = true;
|
|
2630
|
+
rest = rest.slice(boolFlag[0].length);
|
|
2631
|
+
consumedAnother = true;
|
|
2632
|
+
continue;
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
return { subName: 'eval', subArgs: [rest], flags };
|
|
2636
|
+
}
|
|
2393
2637
|
const batchCommand = {
|
|
2394
2638
|
name: 'batch',
|
|
2395
2639
|
description: 'Execute multiple commands. Usage: monomind browse batch "open url" "snapshot -i" "click @e1"',
|
|
@@ -2403,9 +2647,7 @@ const batchCommand = {
|
|
|
2403
2647
|
throw new Error('Usage: monomind browse batch "cmd1" "cmd2" ...');
|
|
2404
2648
|
const results = [];
|
|
2405
2649
|
for (const cmdStr of commands) {
|
|
2406
|
-
const
|
|
2407
|
-
const subName = parts[0];
|
|
2408
|
-
const subArgs = parts.slice(1);
|
|
2650
|
+
const { subName, subArgs, flags: preParsedFlags } = parseBatchCommandLine(cmdStr);
|
|
2409
2651
|
const subCmd = browseCommand.subcommands?.find((s) => s.name === subName);
|
|
2410
2652
|
if (!subCmd?.action) {
|
|
2411
2653
|
const err = `Unknown command: ${subName}`;
|
|
@@ -2415,7 +2657,7 @@ const batchCommand = {
|
|
|
2415
2657
|
continue;
|
|
2416
2658
|
}
|
|
2417
2659
|
try {
|
|
2418
|
-
const parsedFlags = { _: [] };
|
|
2660
|
+
const parsedFlags = { _: [], ...preParsedFlags };
|
|
2419
2661
|
const consumedIndices = new Set();
|
|
2420
2662
|
// Parse --flags from subArgs, tracking which indices are flag names/values
|
|
2421
2663
|
for (let i = 0; i < subArgs.length; i++) {
|
|
@@ -2525,7 +2767,7 @@ const removeinitscriptCommand = {
|
|
|
2525
2767
|
};
|
|
2526
2768
|
const connectCommand = {
|
|
2527
2769
|
name: 'connect',
|
|
2528
|
-
description: 'Connect to existing Chrome instance. Usage: monomind browse connect [--port 9222] [--target <id>] [--auto-connect]',
|
|
2770
|
+
description: 'Connect to existing Chrome instance; later commands reuse this session (note: `open` without --port still launches on its own default). Usage: monomind browse connect [--port 9222] [--target <id>] [--auto-connect]',
|
|
2529
2771
|
options: [
|
|
2530
2772
|
{ name: 'port', short: 'p', type: 'number', description: 'CDP port', default: 9222 },
|
|
2531
2773
|
{ name: 'target', type: 'string', description: 'Target ID to attach to' },
|
|
@@ -2588,6 +2830,12 @@ const connectCommand = {
|
|
|
2588
2830
|
_targetId = conn.target.id;
|
|
2589
2831
|
_port = port;
|
|
2590
2832
|
_refs = new Map();
|
|
2833
|
+
// Persist the port like `open` does — without this, the NEXT CLI process
|
|
2834
|
+
// (each command is a fresh process) resolves the hardcoded default and
|
|
2835
|
+
// tries to launch its own Chrome on 9222 instead of reusing this session.
|
|
2836
|
+
// launched:false marks this browser as someone else's — close must never
|
|
2837
|
+
// kill it, and a dead endpoint must not be silently relaunched.
|
|
2838
|
+
await browser.saveActivePort(port, { launched: false });
|
|
2591
2839
|
const url = await browser.getCurrentUrl(_client, _sessionId);
|
|
2592
2840
|
const title = await browser.getCurrentTitle(_client, _sessionId);
|
|
2593
2841
|
output.printSuccess(`Connected: ${title} (${url})`);
|
|
@@ -2611,7 +2859,7 @@ const recordCommand = {
|
|
|
2611
2859
|
switch (action) {
|
|
2612
2860
|
case 'start':
|
|
2613
2861
|
await browser.startRecording(client, sessionId, {
|
|
2614
|
-
format: ctx.flags.format,
|
|
2862
|
+
format: imageFormat(ctx.flags.format, ['jpeg', 'png'], 'jpeg'),
|
|
2615
2863
|
quality: ctx.flags.quality,
|
|
2616
2864
|
});
|
|
2617
2865
|
output.printSuccess('Recording started');
|
|
@@ -2627,12 +2875,12 @@ const recordCommand = {
|
|
|
2627
2875
|
case 'restart': {
|
|
2628
2876
|
const prevStatus = browser.getRecordingStatus(sessionId);
|
|
2629
2877
|
let prevPath;
|
|
2630
|
-
if (prevStatus.recording) {
|
|
2878
|
+
if (prevStatus.recording || prevStatus.autoStopped) {
|
|
2631
2879
|
prevPath = await browser.stopRecording(client, sessionId, ctx.args[1]);
|
|
2632
2880
|
output.printInfo(`Previous recording saved: ${prevPath}`);
|
|
2633
2881
|
}
|
|
2634
2882
|
await browser.startRecording(client, sessionId, {
|
|
2635
|
-
format: ctx.flags.format,
|
|
2883
|
+
format: imageFormat(ctx.flags.format, ['jpeg', 'png'], 'jpeg'),
|
|
2636
2884
|
quality: ctx.flags.quality,
|
|
2637
2885
|
});
|
|
2638
2886
|
output.printSuccess('Recording restarted');
|
|
@@ -2643,7 +2891,7 @@ const recordCommand = {
|
|
|
2643
2891
|
if (ctx.flags.json)
|
|
2644
2892
|
print(JSON.stringify({ data: status }));
|
|
2645
2893
|
else
|
|
2646
|
-
print(`Recording: ${status.recording} | Frames: ${status.frames}`);
|
|
2894
|
+
print(`Recording: ${status.recording} | Frames: ${status.frames}${status.autoStopped ? ' (auto-stopped: buffer limit reached — run "record stop" to save)' : ''}`);
|
|
2647
2895
|
return { success: true, data: status };
|
|
2648
2896
|
}
|
|
2649
2897
|
default:
|