@crouton-kit/capture 1.4.21 → 1.4.22
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 +33 -7
- package/package.json +1 -1
- package/src/cdp/args.ts +10 -0
- package/src/cdp/commands/page/exec.ts +34 -2
- package/src/cdp/commands/tab/close.ts +3 -2
- package/src/interact.ts +17 -0
package/bin/capture
CHANGED
|
@@ -9413,7 +9413,12 @@ function validateCliInvocation(parsed) {
|
|
|
9413
9413
|
}
|
|
9414
9414
|
if (leaf === "launch" || leaf === "list") requireCount(values, 0, 0, `tab ${leaf}`);
|
|
9415
9415
|
else if (leaf === "quit") requireCount(values, 0, 1, "tab quit");
|
|
9416
|
-
else
|
|
9416
|
+
else if (leaf === "close" && parsed.target !== void 0) {
|
|
9417
|
+
requireCount(values, 0, 1, "tab close");
|
|
9418
|
+
if (values.length === 1 && values[0] !== parsed.target) {
|
|
9419
|
+
throw schemaInput("tab close", `<target>=${values[0]}, --target=${parsed.target}`, "one tab: the positional <target> or --target, not two different ids", "<target> or --target");
|
|
9420
|
+
}
|
|
9421
|
+
} else requireCount(values, 1, 1, `tab ${leaf}`);
|
|
9417
9422
|
if (leaf === "network" && values[0] !== "offline" && values[0] !== "online") {
|
|
9418
9423
|
throw schemaInput("tab network", values[0] ?? "(missing)", "offline or online", "<mode>");
|
|
9419
9424
|
}
|
|
@@ -10566,7 +10571,7 @@ function validateHarAppendBatch(value, source) {
|
|
|
10566
10571
|
return { entries, incompleteLifecycles };
|
|
10567
10572
|
}
|
|
10568
10573
|
function captureVersion() {
|
|
10569
|
-
const version = "1.4.
|
|
10574
|
+
const version = "1.4.22";
|
|
10570
10575
|
return typeof version === "string" && version.length > 0 ? version : "development";
|
|
10571
10576
|
}
|
|
10572
10577
|
function token() {
|
|
@@ -12668,7 +12673,7 @@ function wallTime(value) {
|
|
|
12668
12673
|
}
|
|
12669
12674
|
}
|
|
12670
12675
|
function captureVersion2() {
|
|
12671
|
-
const version = "1.4.
|
|
12676
|
+
const version = "1.4.22";
|
|
12672
12677
|
return typeof version === "string" && version.length > 0 ? version : "development";
|
|
12673
12678
|
}
|
|
12674
12679
|
function deepFreeze(value) {
|
|
@@ -17075,6 +17080,7 @@ async function dispatchResolvedClick(client, resolved, opts) {
|
|
|
17075
17080
|
}
|
|
17076
17081
|
if (hitTest.backendNodeId !== backendNodeId2 || opts.includeHitTestReceiver) hitTestReceiverBackendNodeId = hitTest.backendNodeId;
|
|
17077
17082
|
}
|
|
17083
|
+
await presentFrameBeforeInput(client);
|
|
17078
17084
|
const pressParams = { type: "mousePressed", x: dispatchX, y: dispatchY, button: "left", clickCount: 1 };
|
|
17079
17085
|
if (opts.mark !== void 0 && client.sendMarked) {
|
|
17080
17086
|
await client.sendMarked("Input.dispatchMouseEvent", pressParams, opts.mark);
|
|
@@ -17091,6 +17097,12 @@ async function dispatchResolvedClick(client, resolved, opts) {
|
|
|
17091
17097
|
...hitTestReceiverBackendNodeId === void 0 ? {} : { hitTestReceiverBackendNodeId }
|
|
17092
17098
|
};
|
|
17093
17099
|
}
|
|
17100
|
+
async function presentFrameBeforeInput(client) {
|
|
17101
|
+
await client.send("Page.captureScreenshot", {
|
|
17102
|
+
format: "png",
|
|
17103
|
+
clip: { x: 0, y: 0, width: 1, height: 1, scale: 1 }
|
|
17104
|
+
});
|
|
17105
|
+
}
|
|
17094
17106
|
async function typeText(client, text2) {
|
|
17095
17107
|
await client.send("Input.insertText", { text: text2 });
|
|
17096
17108
|
}
|
|
@@ -228715,6 +228727,19 @@ function spillWholeResult(payload, artifactDir) {
|
|
|
228715
228727
|
writePrivateFile(filePath, payload);
|
|
228716
228728
|
return filePath;
|
|
228717
228729
|
}
|
|
228730
|
+
function describePageException(details) {
|
|
228731
|
+
const exception = details.exception;
|
|
228732
|
+
if (!exception) return details.text ?? "Unknown error";
|
|
228733
|
+
if (exception.subtype === "error" && exception.description) return exception.description;
|
|
228734
|
+
if (exception.type === "undefined") return "undefined";
|
|
228735
|
+
if ("value" in exception) return JSON.stringify(exception.value);
|
|
228736
|
+
const properties = exception.preview?.properties;
|
|
228737
|
+
if (exception.type === "object" && properties) {
|
|
228738
|
+
const fields = properties.map((p) => `${p.name}: ${p.value ?? "?"}`).join(", ");
|
|
228739
|
+
return `${exception.className ?? "Object"} {${fields}${exception.preview?.overflow ? ", \u2026" : ""}}`;
|
|
228740
|
+
}
|
|
228741
|
+
return exception.description ?? details.text ?? "Unknown error";
|
|
228742
|
+
}
|
|
228718
228743
|
function isRequestTimeout(error4) {
|
|
228719
228744
|
return error4 instanceof Error && /CDP request timeout \(\d+ms\): Runtime\.evaluate|collector-host CDP call "Runtime\.evaluate" failed: CDP request timeout/.test(error4.message);
|
|
228720
228745
|
}
|
|
@@ -228860,7 +228885,7 @@ async function cmdPageExec(parsed, _args) {
|
|
|
228860
228885
|
);
|
|
228861
228886
|
}
|
|
228862
228887
|
if (evalResult.exceptionDetails) {
|
|
228863
|
-
const description = evalResult.exceptionDetails
|
|
228888
|
+
const description = describePageException(evalResult.exceptionDetails);
|
|
228864
228889
|
return emitExecError(
|
|
228865
228890
|
parsed,
|
|
228866
228891
|
"exec_exception",
|
|
@@ -230199,9 +230224,9 @@ async function cmdTabClose(parsed, _args) {
|
|
|
230199
230224
|
console.log(USAGE13);
|
|
230200
230225
|
return;
|
|
230201
230226
|
}
|
|
230202
|
-
const target = parsed.positional[0];
|
|
230227
|
+
const target = parsed.positional[0] ?? (parsed.targetSource === "flag" ? parsed.target : void 0);
|
|
230203
230228
|
if (!target) {
|
|
230204
|
-
throw invalidInput("received: no target; expected: capture tab close <target> [--port <port>].", "missing_argument");
|
|
230229
|
+
throw invalidInput("received: no target; expected: capture tab close <target> (or --target <id>) [--port <port>].", "missing_argument");
|
|
230205
230230
|
}
|
|
230206
230231
|
if (target.length < 8) {
|
|
230207
230232
|
throw invalidInput(`received: target ${target}; expected: a full target id or an unambiguous prefix of at least 8 characters.`, "target_prefix_too_short");
|
|
@@ -230267,6 +230292,7 @@ var init_close = __esm({
|
|
|
230267
230292
|
|
|
230268
230293
|
input:
|
|
230269
230294
|
<target> required \u2014 full tab target id or an unambiguous 8-character prefix
|
|
230295
|
+
--target <id> same as <target>; only an explicit flag counts, never the session's tab or CDP_TARGET
|
|
230270
230296
|
--port <port> CDP endpoint (default: the auto-discovered preferred endpoint)
|
|
230271
230297
|
|
|
230272
230298
|
output: <tab-closed port=\u2026 target=\u2026> \u2014 the closed tab's full target id and last observed URL.
|
|
@@ -267458,7 +267484,7 @@ I/O contract: flags and positionals on input; one rendered prose block on stdout
|
|
|
267458
267484
|
A CDP-enabled browser must be running; \`capture tab list\` is the probe, and \`capture tab launch\` starts one capture owns and reaps when nothing is (never hand-roll a detached browser \u2014 nothing reaps that). Capture never signals a browser it did not start, so attaching to your own with --port is unchanged. CDP_PORT / CDP_TARGET pin the browser + tab for target-selecting commands \u2014 precedence: explicit flag > active session > env. \`session start\` adopts only an explicit --target.`;
|
|
267459
267485
|
}
|
|
267460
267486
|
function printVersion() {
|
|
267461
|
-
const declared = "1.4.
|
|
267487
|
+
const declared = "1.4.22";
|
|
267462
267488
|
if (declared) {
|
|
267463
267489
|
console.log(declared);
|
|
267464
267490
|
return;
|
package/package.json
CHANGED
package/src/cdp/args.ts
CHANGED
|
@@ -272,6 +272,16 @@ export function validateCliInvocation(parsed: ParsedArgs): void {
|
|
|
272
272
|
// `quit` takes an optional port; every other tab leaf names exactly one.
|
|
273
273
|
if (leaf === 'launch' || leaf === 'list') requireCount(values, 0, 0, `tab ${leaf}`);
|
|
274
274
|
else if (leaf === 'quit') requireCount(values, 0, 1, 'tab quit');
|
|
275
|
+
// `tab close` names its tab either positionally or with an explicit
|
|
276
|
+
// --target flag (the form every follow_up line teaches). Only the flag
|
|
277
|
+
// counts here: session/CDP_TARGET defaults are hydrated later and must
|
|
278
|
+
// never choose which tab a destructive close removes.
|
|
279
|
+
else if (leaf === 'close' && parsed.target !== undefined) {
|
|
280
|
+
requireCount(values, 0, 1, 'tab close');
|
|
281
|
+
if (values.length === 1 && values[0] !== parsed.target) {
|
|
282
|
+
throw schemaInput('tab close', `<target>=${values[0]}, --target=${parsed.target}`, 'one tab: the positional <target> or --target, not two different ids', '<target> or --target');
|
|
283
|
+
}
|
|
284
|
+
}
|
|
275
285
|
else requireCount(values, 1, 1, `tab ${leaf}`);
|
|
276
286
|
if (leaf === 'network' && values[0] !== 'offline' && values[0] !== 'online') {
|
|
277
287
|
throw schemaInput('tab network', values[0] ?? '(missing)', 'offline or online', '<mode>');
|
|
@@ -107,7 +107,39 @@ function spillWholeResult(payload: string, artifactDir?: string): string {
|
|
|
107
107
|
|
|
108
108
|
interface EvalResult {
|
|
109
109
|
result?: { value?: unknown };
|
|
110
|
-
exceptionDetails?:
|
|
110
|
+
exceptionDetails?: ExceptionDetails;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
interface ExceptionDetails {
|
|
114
|
+
text?: string;
|
|
115
|
+
exception?: {
|
|
116
|
+
type?: string;
|
|
117
|
+
subtype?: string;
|
|
118
|
+
className?: string;
|
|
119
|
+
value?: unknown;
|
|
120
|
+
description?: string;
|
|
121
|
+
preview?: { properties?: Array<{ name: string; value?: string }>; overflow?: boolean };
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Describes what the page threw. An Error carries its message and stack in
|
|
127
|
+
* `description`; a thrown string, number, or plain object does not, so its
|
|
128
|
+
* value (or its preview's properties) is named instead of falling back to a
|
|
129
|
+
* bare "Object" or "Unknown error".
|
|
130
|
+
*/
|
|
131
|
+
function describePageException(details: ExceptionDetails): string {
|
|
132
|
+
const exception = details.exception;
|
|
133
|
+
if (!exception) return details.text ?? 'Unknown error';
|
|
134
|
+
if (exception.subtype === 'error' && exception.description) return exception.description;
|
|
135
|
+
if (exception.type === 'undefined') return 'undefined';
|
|
136
|
+
if ('value' in exception) return JSON.stringify(exception.value);
|
|
137
|
+
const properties = exception.preview?.properties;
|
|
138
|
+
if (exception.type === 'object' && properties) {
|
|
139
|
+
const fields = properties.map((p) => `${p.name}: ${p.value ?? '?'}`).join(', ');
|
|
140
|
+
return `${exception.className ?? 'Object'} {${fields}${exception.preview?.overflow ? ', …' : ''}}`;
|
|
141
|
+
}
|
|
142
|
+
return exception.description ?? details.text ?? 'Unknown error';
|
|
111
143
|
}
|
|
112
144
|
|
|
113
145
|
/** The structural client surface the focus/evaluation scope drives — the
|
|
@@ -316,7 +348,7 @@ export async function cmdPageExec(parsed: ParsedArgs, _args: string[]): Promise<
|
|
|
316
348
|
}
|
|
317
349
|
|
|
318
350
|
if (evalResult.exceptionDetails) {
|
|
319
|
-
const description = evalResult.exceptionDetails
|
|
351
|
+
const description = describePageException(evalResult.exceptionDetails);
|
|
320
352
|
return emitExecError(
|
|
321
353
|
parsed,
|
|
322
354
|
'exec_exception',
|
|
@@ -17,6 +17,7 @@ const USAGE = `capture tab close — close one explicitly identified background
|
|
|
17
17
|
|
|
18
18
|
input:
|
|
19
19
|
<target> required — full tab target id or an unambiguous 8-character prefix
|
|
20
|
+
--target <id> same as <target>; only an explicit flag counts, never the session's tab or CDP_TARGET
|
|
20
21
|
--port <port> CDP endpoint (default: the auto-discovered preferred endpoint)
|
|
21
22
|
|
|
22
23
|
output: <tab-closed port=… target=…> — the closed tab's full target id and last observed URL.
|
|
@@ -36,9 +37,9 @@ export async function cmdTabClose(parsed: ParsedArgs, _args: string[]): Promise<
|
|
|
36
37
|
return;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
const target = parsed.positional[0];
|
|
40
|
+
const target = parsed.positional[0] ?? (parsed.targetSource === 'flag' ? parsed.target : undefined);
|
|
40
41
|
if (!target) {
|
|
41
|
-
throw invalidInput('received: no target; expected: capture tab close <target> [--port <port>].', 'missing_argument');
|
|
42
|
+
throw invalidInput('received: no target; expected: capture tab close <target> (or --target <id>) [--port <port>].', 'missing_argument');
|
|
42
43
|
}
|
|
43
44
|
if (target.length < 8) {
|
|
44
45
|
throw invalidInput(`received: target ${target}; expected: a full target id or an unambiguous prefix of at least 8 characters.`, 'target_prefix_too_short');
|
package/src/interact.ts
CHANGED
|
@@ -434,6 +434,8 @@ async function dispatchResolvedClick(
|
|
|
434
434
|
if (hitTest.backendNodeId !== backendNodeId || opts.includeHitTestReceiver) hitTestReceiverBackendNodeId = hitTest.backendNodeId;
|
|
435
435
|
}
|
|
436
436
|
|
|
437
|
+
await presentFrameBeforeInput(client);
|
|
438
|
+
|
|
437
439
|
const pressParams = { type: 'mousePressed', x: dispatchX, y: dispatchY, button: 'left', clickCount: 1 };
|
|
438
440
|
if (opts.mark !== undefined && client.sendMarked) {
|
|
439
441
|
await client.sendMarked('Input.dispatchMouseEvent', pressParams, opts.mark);
|
|
@@ -452,6 +454,21 @@ async function dispatchResolvedClick(
|
|
|
452
454
|
};
|
|
453
455
|
}
|
|
454
456
|
|
|
457
|
+
/**
|
|
458
|
+
* Makes the renderer produce a frame before mouse input is dispatched. Arc does
|
|
459
|
+
* not paint a tab opened with `Target.createTarget({ background: true })` until
|
|
460
|
+
* something asks for a frame, and until it paints, `Input.dispatchMouseEvent`
|
|
461
|
+
* returns success while delivering no events to the page. A 1x1 clipped capture
|
|
462
|
+
* is the cheapest request that forces that frame. Chrome, which already paints
|
|
463
|
+
* background targets, only pays the capture.
|
|
464
|
+
*/
|
|
465
|
+
async function presentFrameBeforeInput(client: LiveClient): Promise<void> {
|
|
466
|
+
await client.send('Page.captureScreenshot', {
|
|
467
|
+
format: 'png',
|
|
468
|
+
clip: { x: 0, y: 0, width: 1, height: 1, scale: 1 },
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
455
472
|
/** Types text into the currently focused element via CDP `Input.insertText`. */
|
|
456
473
|
export async function typeText(client: LiveClient, text: string): Promise<void> {
|
|
457
474
|
await client.send('Input.insertText', { text });
|