@crouton-kit/capture 1.3.6 → 1.3.7
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/bin/capture +1384 -891
- package/package.json +1 -1
- package/src/capture.ts +20 -0
- package/src/cdp/args.ts +14 -0
- package/src/cdp/bridge/client.ts +49 -0
- package/src/cdp/bridge/protocol.ts +38 -0
- package/src/cdp/bridge/server.ts +179 -0
- package/src/cdp/bridge/spawn.ts +65 -0
- package/src/cdp/client.ts +7 -1
- package/src/cdp/commands/bridge-serve.ts +18 -0
- package/src/cdp/commands/cdp.ts +175 -0
- package/src/cdp/dispatch.ts +4 -0
- package/src/cdp/targets.ts +8 -1
- package/src/cdp/types.ts +5 -0
- package/src/cdp/usage.ts +7 -0
- package/src/session/commands.ts +64 -5
- package/src/session-context.ts +2 -0
package/src/cdp/usage.ts
CHANGED
|
@@ -21,6 +21,7 @@ Commands:
|
|
|
21
21
|
lib search "<q>" Fuzzy-search lib functions [--limit N]
|
|
22
22
|
lib show <name> Lib + function summaries
|
|
23
23
|
lib read <name> [fn…] Full input/output schemas (+ .ts source path)
|
|
24
|
+
cdp <Domain.method> Send a raw CDP command capture doesn't wrap [--browser] [--params json] [--wait-event name]
|
|
24
25
|
|
|
25
26
|
Options:
|
|
26
27
|
--port <port> Override CDP port
|
|
@@ -37,6 +38,10 @@ Options:
|
|
|
37
38
|
--filter-status <s> Filter HAR by status: code, prefix (4), or range (400-499)
|
|
38
39
|
--filter-method <m> Filter HAR by HTTP method (har read)
|
|
39
40
|
--limit <N> Return only first N matching HAR entries (har read)
|
|
41
|
+
--browser Target the browser-level connection (cdp)
|
|
42
|
+
--params '<json>' JSON params for the method (cdp)
|
|
43
|
+
--wait-event <name> Wait for a CDP event and return it (cdp)
|
|
44
|
+
--timeout <ms> Wait timeout, default 10000 (cdp)
|
|
40
45
|
--out <path> Output path (screenshot)
|
|
41
46
|
--viewport <preset> Viewport preset: desktop-wide|desktop|tablet|mobile (default: desktop)
|
|
42
47
|
--height <px> Override viewport height (screenshot)
|
|
@@ -67,4 +72,6 @@ Examples:
|
|
|
67
72
|
capture lib search "search emails"
|
|
68
73
|
capture lib read gmail searchEmails
|
|
69
74
|
capture exec "import {searchEmails} from 'libs/gmail'; const ctx = await getContext(); return await searchEmails({ ...ctx, query: 'invoice' })"
|
|
75
|
+
capture cdp Browser.grantPermissions --browser --params '{"origin":"http://localhost:3000","permissions":["notifications"]}'
|
|
76
|
+
capture cdp --help Raw CDP escape hatch (Browser.*, ServiceWorker.*, ...) + how session --hold keeps grants alive
|
|
70
77
|
`;
|
package/src/session/commands.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
clearActiveSession,
|
|
14
14
|
} from '../session-context.js';
|
|
15
15
|
import { expandEqualsFlags } from '../cdp/args.js';
|
|
16
|
+
import { startBridge, stopBridge } from '../cdp/bridge/spawn.js';
|
|
16
17
|
|
|
17
18
|
const CAPTURE_ROOT = path.join(os.tmpdir(), 'capture-sessions');
|
|
18
19
|
|
|
@@ -31,6 +32,9 @@ interface Session {
|
|
|
31
32
|
targetId: string | null;
|
|
32
33
|
stepCount: number;
|
|
33
34
|
logPids: LogPid[];
|
|
35
|
+
/** Set when started with --hold: one CDP browser connection held open for the session. */
|
|
36
|
+
bridgeSocket: string | null;
|
|
37
|
+
bridgePid: number | null;
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
interface BundleManifest {
|
|
@@ -90,7 +94,8 @@ function printSessionHelp(): void {
|
|
|
90
94
|
console.log(`capture session — manage capture sessions
|
|
91
95
|
|
|
92
96
|
Sub-commands:
|
|
93
|
-
start [--url <url>]
|
|
97
|
+
start [--url <url>] [--hold] Start a session (opens tab, records HAR, sets active context)
|
|
98
|
+
--hold keeps one CDP browser connection open for the session
|
|
94
99
|
stop <session-id> Finalize and bundle artifacts (screenshots, HAR, a11y, logs)
|
|
95
100
|
list List active and stopped sessions
|
|
96
101
|
view <id> [--filter section] View bundle manifest; section = screenshots|har|a11y|logs
|
|
@@ -110,6 +115,17 @@ Typical flow:
|
|
|
110
115
|
3. capture session stop <session-id>
|
|
111
116
|
4. capture session view <session-id>
|
|
112
117
|
|
|
118
|
+
--hold: keeps one CDP browser connection open for the session's lifetime, so
|
|
119
|
+
browser-level state (Browser.grantPermissions, ServiceWorker.enable, ...)
|
|
120
|
+
survives across separate commands instead of reverting the instant each
|
|
121
|
+
command's own connection closes. Use it together with \`capture cdp --browser\`:
|
|
122
|
+
|
|
123
|
+
capture session start --url http://localhost:3000 --hold
|
|
124
|
+
capture cdp Browser.grantPermissions --browser --params '{"origin":"http://localhost:3000","permissions":["notifications"]}'
|
|
125
|
+
capture cdp ServiceWorker.enable --browser --target <pageTabId>
|
|
126
|
+
capture cdp ServiceWorker.deliverPushMessage --browser --target <pageTabId> --params '{...}'
|
|
127
|
+
capture session stop <session-id> # also tears down the held connection
|
|
128
|
+
|
|
113
129
|
Related: capture log <path> [--name label] Tail a log into the active session
|
|
114
130
|
See also: capture --help Full command list`);
|
|
115
131
|
}
|
|
@@ -121,10 +137,13 @@ See also: capture --help Full command list`);
|
|
|
121
137
|
async function start(rawArgs: string[]): Promise<void> {
|
|
122
138
|
const args = expandEqualsFlags(rawArgs);
|
|
123
139
|
let url: string | null = null;
|
|
140
|
+
let hold = false;
|
|
124
141
|
|
|
125
142
|
for (let i = 0; i < args.length; i++) {
|
|
126
143
|
if (args[i] === '--url' && args[i + 1]) {
|
|
127
144
|
url = args[++i];
|
|
145
|
+
} else if (args[i] === '--hold') {
|
|
146
|
+
hold = true;
|
|
128
147
|
}
|
|
129
148
|
}
|
|
130
149
|
|
|
@@ -145,11 +164,15 @@ async function start(rawArgs: string[]): Promise<void> {
|
|
|
145
164
|
// Open tab if URL provided. Fail fast if the new target cannot attach.
|
|
146
165
|
let targetId: string | null = null;
|
|
147
166
|
let pageLoadTimedOut = false;
|
|
167
|
+
let cdpPort: number | null = null;
|
|
168
|
+
if (url || hold) {
|
|
169
|
+
const { detectCdpPort } = await import('../cdp.js');
|
|
170
|
+
cdpPort = await detectCdpPort();
|
|
171
|
+
}
|
|
148
172
|
if (url) {
|
|
149
173
|
try {
|
|
150
|
-
const {
|
|
151
|
-
const
|
|
152
|
-
const tab = await openTab(port, url);
|
|
174
|
+
const { openTab, CDPClient } = await import('../cdp.js');
|
|
175
|
+
const tab = await openTab(cdpPort!, url);
|
|
153
176
|
targetId = tab.id;
|
|
154
177
|
|
|
155
178
|
if (!targetId) {
|
|
@@ -188,6 +211,29 @@ async function start(rawArgs: string[]): Promise<void> {
|
|
|
188
211
|
}
|
|
189
212
|
}
|
|
190
213
|
|
|
214
|
+
// Hold a CDP browser connection open for the session's lifetime so
|
|
215
|
+
// browser-level state (permission grants, ServiceWorker enablement)
|
|
216
|
+
// survives across separate `capture` commands instead of reverting the
|
|
217
|
+
// instant each command's own connection closes.
|
|
218
|
+
let bridgeSocket: string | null = null;
|
|
219
|
+
let bridgePid: number | null = null;
|
|
220
|
+
if (hold) {
|
|
221
|
+
try {
|
|
222
|
+
const bridge = await startBridge(dir, cdpPort!);
|
|
223
|
+
bridgeSocket = bridge.socketPath;
|
|
224
|
+
bridgePid = bridge.pid;
|
|
225
|
+
} catch (err) {
|
|
226
|
+
if (harId) {
|
|
227
|
+
try {
|
|
228
|
+
deleteHarRecording(harId);
|
|
229
|
+
} catch {
|
|
230
|
+
/* best effort */
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
throw err;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
191
237
|
const session: Session = {
|
|
192
238
|
id,
|
|
193
239
|
dir,
|
|
@@ -197,6 +243,8 @@ async function start(rawArgs: string[]): Promise<void> {
|
|
|
197
243
|
targetId,
|
|
198
244
|
stepCount: 0,
|
|
199
245
|
logPids: [],
|
|
246
|
+
bridgeSocket,
|
|
247
|
+
bridgePid,
|
|
200
248
|
};
|
|
201
249
|
fs.writeFileSync(sessionMetaPath(id), JSON.stringify(session, null, 2));
|
|
202
250
|
|
|
@@ -207,6 +255,7 @@ async function start(rawArgs: string[]): Promise<void> {
|
|
|
207
255
|
harId,
|
|
208
256
|
targetId,
|
|
209
257
|
stepCount: 0,
|
|
258
|
+
bridgeSocket,
|
|
210
259
|
});
|
|
211
260
|
|
|
212
261
|
// Output for agent consumption
|
|
@@ -218,6 +267,7 @@ async function start(rawArgs: string[]): Promise<void> {
|
|
|
218
267
|
pageLoadTimedOut,
|
|
219
268
|
shotsDir: path.join(dir, 'shots'),
|
|
220
269
|
a11yDir: path.join(dir, 'a11y'),
|
|
270
|
+
held: hold,
|
|
221
271
|
};
|
|
222
272
|
console.log(JSON.stringify(result, null, 2));
|
|
223
273
|
|
|
@@ -229,6 +279,9 @@ async function start(rawArgs: string[]): Promise<void> {
|
|
|
229
279
|
console.error(`Page load timed out, but the session is attached to target ${targetId.slice(0, 8)}. Use \`capture exec --target ${targetId.slice(0, 8)}\` or \`capture list\` if you need to recover it.`);
|
|
230
280
|
}
|
|
231
281
|
}
|
|
282
|
+
if (hold) {
|
|
283
|
+
console.error(`CDP bridge held (pid ${bridgePid}) — browser-level state now survives across commands via: capture cdp <Method> --browser [--params '<json>']`);
|
|
284
|
+
}
|
|
232
285
|
console.error(`\nWhen done: capture session stop ${id}`);
|
|
233
286
|
}
|
|
234
287
|
|
|
@@ -309,6 +362,12 @@ async function stop(args: string[]): Promise<void> {
|
|
|
309
362
|
await new Promise((r) => setTimeout(r, 200));
|
|
310
363
|
}
|
|
311
364
|
|
|
365
|
+
// Tear down the held CDP bridge, if any — releases the browser-level
|
|
366
|
+
// connection (and any grants/target-enablement it was keeping alive).
|
|
367
|
+
if (session.bridgePid || session.bridgeSocket) {
|
|
368
|
+
stopBridge(session.bridgePid, session.bridgeSocket);
|
|
369
|
+
}
|
|
370
|
+
|
|
312
371
|
// Collect screenshots
|
|
313
372
|
const shotsDir = path.join(session.dir, 'shots');
|
|
314
373
|
const screenshots = fs.existsSync(shotsDir)
|
|
@@ -357,7 +416,7 @@ async function stop(args: string[]): Promise<void> {
|
|
|
357
416
|
|
|
358
417
|
// Collect anything else dropped in the session dir
|
|
359
418
|
const knownDirs = new Set(['shots', 'a11y', 'logs']);
|
|
360
|
-
const knownFiles = new Set(['.session.json', 'har.json', 'bundle.json']);
|
|
419
|
+
const knownFiles = new Set(['.session.json', 'har.json', 'bundle.json', 'bridge.sock']);
|
|
361
420
|
const other = fs.readdirSync(session.dir)
|
|
362
421
|
.filter((f) => !knownDirs.has(f) && !knownFiles.has(f))
|
|
363
422
|
.map((f) => ({ name: f, path: path.join(session.dir, f) }));
|
package/src/session-context.ts
CHANGED
|
@@ -19,6 +19,8 @@ export interface ActiveSessionState {
|
|
|
19
19
|
harId: string | null;
|
|
20
20
|
targetId: string | null;
|
|
21
21
|
stepCount: number;
|
|
22
|
+
/** Unix socket of the session's held CDP bridge (`session start --hold`), if any. */
|
|
23
|
+
bridgeSocket?: string | null;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
export function getActiveSession(): ActiveSessionState | null {
|