browser-debugger-cli 0.14.0 → 0.16.0
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/.claude/skills/bdg/SKILL.md +3 -2
- package/dist/cdp/methodTarget.d.ts +92 -0
- package/dist/cdp/methodTarget.js +159 -0
- package/dist/cdp/protocol.d.ts +16 -1
- package/dist/cdp/protocol.js +21 -0
- package/dist/cdp/schema.d.ts +55 -1
- package/dist/cdp/schema.js +134 -25
- package/dist/cdp/types.d.ts +3 -1
- package/dist/commands/cdp.d.ts +38 -1
- package/dist/commands/cdp.js +201 -133
- package/dist/commands/cleanup.js +21 -4
- package/dist/commands/dom/eval.d.ts +2 -1
- package/dist/commands/dom/eval.js +6 -21
- package/dist/commands/dom/formInteraction.js +8 -4
- package/dist/commands/dom/helpers/evalResult.d.ts +36 -0
- package/dist/commands/dom/helpers/evalResult.js +59 -0
- package/dist/commands/dom/helpers/index.d.ts +4 -4
- package/dist/commands/dom/helpers/index.js +3 -3
- package/dist/commands/dom/helpers/query.d.ts +2 -2
- package/dist/commands/dom/helpers/query.js +2 -2
- package/dist/commands/dom/helpers/screenshot.d.ts +21 -26
- package/dist/commands/dom/helpers/screenshot.js +50 -668
- package/dist/commands/dom/screenshot.js +56 -36
- package/dist/commands/helpJson.d.ts +1 -1
- package/dist/commands/helpJson.js +3 -3
- package/dist/commands/helpTopic.js +10 -4
- package/dist/commands/network/har.js +18 -14
- package/dist/commands/optionBehaviors.js +24 -9
- package/dist/commands/shared/CommandRunner.d.ts +5 -0
- package/dist/commands/shared/CommandRunner.js +18 -3
- package/dist/commands/shared/interrupt.d.ts +40 -0
- package/dist/commands/shared/interrupt.js +73 -0
- package/dist/commands/shared/optionTypes.d.ts +3 -0
- package/dist/commands/shared/outputFile.d.ts +2 -1
- package/dist/commands/shared/outputFile.js +7 -4
- package/dist/commands/shared/startHelpers.d.ts +26 -3
- package/dist/commands/shared/startHelpers.js +145 -23
- package/dist/commands/status.js +3 -1
- package/dist/commands/stop.js +2 -1
- package/dist/commands/types.d.ts +5 -0
- package/dist/connection/cdp.js +1 -16
- package/dist/connection/chromeIdentity.d.ts +24 -5
- package/dist/connection/chromeIdentity.js +53 -22
- package/dist/connection/launcher/flagsBuilder.d.ts +46 -0
- package/dist/connection/launcher/flagsBuilder.js +107 -23
- package/dist/connection/launcher.d.ts +35 -2
- package/dist/connection/launcher.js +99 -12
- package/dist/connection/typed-cdp.d.ts +3 -2
- package/dist/constants.d.ts +3 -5
- package/dist/constants.js +3 -5
- package/dist/daemon/SessionController.d.ts +10 -5
- package/dist/daemon/SessionController.js +15 -8
- package/dist/daemon/ipcServer.js +1 -1
- package/dist/daemon/launcher.d.ts +22 -3
- package/dist/daemon/launcher.js +45 -8
- package/dist/daemon/session/Session.d.ts +5 -1
- package/dist/daemon/session/Session.js +9 -8
- package/dist/daemon/session/TelemetryStore.d.ts +5 -0
- package/dist/daemon/session/TelemetryStore.js +4 -0
- package/dist/daemon/session/captureGate.d.ts +59 -0
- package/dist/daemon/session/captureGate.js +96 -0
- package/dist/daemon/session/chromeConnection.d.ts +16 -1
- package/dist/daemon/session/chromeConnection.js +34 -4
- package/dist/daemon/session/collectors.d.ts +15 -0
- package/dist/daemon/session/collectors.js +39 -2
- package/dist/daemon/session/commandRegistry.d.ts +14 -1
- package/dist/daemon/session/commandRegistry.js +48 -13
- package/dist/daemon/session/downloads.d.ts +32 -0
- package/dist/daemon/session/downloads.js +96 -0
- package/dist/daemon/session/interactions.d.ts +3 -2
- package/dist/daemon/session/interactions.js +7 -2
- package/dist/daemon/session/plugins.js +6 -0
- package/dist/daemon.js +18520 -17014
- package/dist/errors/CommandError.d.ts +2 -0
- package/dist/errors/issues.d.ts +1 -1
- package/dist/errors/messages.d.ts +81 -0
- package/dist/errors/messages.js +198 -6
- package/dist/index.js +1446 -1078
- package/dist/ipc/client.d.ts +20 -2
- package/dist/ipc/client.js +32 -6
- package/dist/ipc/protocol/commands.d.ts +36 -2
- package/dist/ipc/protocol/commands.js +1 -0
- package/dist/ipc/protocol/domTypes.d.ts +24 -1
- package/dist/ipc/session/queries.d.ts +3 -0
- package/dist/ipc/session/types.d.ts +5 -0
- package/dist/ipc/transport/IPCError.d.ts +9 -0
- package/dist/ipc/transport/IPCError.js +12 -0
- package/dist/ipc/transport/errors.d.ts +2 -1
- package/dist/ipc/transport/errors.js +4 -1
- package/dist/ipc/transport/index.d.ts +10 -2
- package/dist/ipc/transport/index.js +29 -4
- package/dist/runtime/dom/actionEffects.d.ts +48 -9
- package/dist/runtime/dom/actionEffects.js +269 -34
- package/dist/runtime/dom/actionEffectsScripts.d.ts +45 -0
- package/dist/runtime/dom/actionEffectsScripts.js +101 -2
- package/dist/runtime/dom/captureArea.d.ts +35 -0
- package/dist/runtime/dom/captureArea.js +203 -0
- package/dist/runtime/dom/elementInfo.d.ts +13 -4
- package/dist/runtime/dom/elementInfo.js +12 -3
- package/dist/runtime/dom/evalHelpers.d.ts +24 -4
- package/dist/runtime/dom/evalHelpers.js +40 -12
- package/dist/runtime/dom/formDiscovery.d.ts +1 -1
- package/dist/runtime/dom/frames.d.ts +2 -1
- package/dist/runtime/dom/frames.js +3 -1
- package/dist/runtime/page/bdgWorld.d.ts +9 -0
- package/dist/runtime/page/bdgWorld.js +11 -0
- package/dist/runtime/page/captureEmulation.d.ts +119 -0
- package/dist/runtime/page/captureEmulation.js +189 -0
- package/dist/runtime/page/captureScroll.d.ts +24 -0
- package/dist/runtime/page/captureScroll.js +124 -0
- package/dist/runtime/page/emulation.js +6 -5
- package/dist/runtime/page/screenshot.d.ts +41 -0
- package/dist/runtime/page/screenshot.js +394 -0
- package/dist/runtime/page/userAgent.d.ts +86 -2
- package/dist/runtime/page/userAgent.js +154 -33
- package/dist/session/paths.d.ts +52 -3
- package/dist/session/paths.js +179 -7
- package/dist/session/portClaims.d.ts +0 -8
- package/dist/session/portClaims.js +1 -22
- package/dist/session/sessionList.d.ts +5 -1
- package/dist/session/sessionList.js +5 -1
- package/dist/telemetry/downloads.d.ts +127 -0
- package/dist/telemetry/downloads.js +265 -0
- package/dist/telemetry/har/builder.d.ts +12 -1
- package/dist/telemetry/har/builder.js +32 -9
- package/dist/telemetry/har/sanitize.d.ts +28 -0
- package/dist/telemetry/har/sanitize.js +184 -0
- package/dist/telemetry/har/sanitizeBody.d.ts +78 -0
- package/dist/telemetry/har/sanitizeBody.js +541 -0
- package/dist/telemetry/har/types.d.ts +2 -0
- package/dist/telemetry/network.d.ts +4 -4
- package/dist/telemetry/network.js +38 -4
- package/dist/telemetry/networkRetention.d.ts +35 -14
- package/dist/telemetry/networkRetention.js +62 -26
- package/dist/types.d.ts +9 -14
- package/dist/ui/OutputBuilder.d.ts +3 -2
- package/dist/ui/OutputBuilder.js +4 -3
- package/dist/ui/formatters/cdp.d.ts +32 -9
- package/dist/ui/formatters/cdp.js +77 -6
- package/dist/ui/formatters/details.js +7 -15
- package/dist/ui/formatters/preview.d.ts +2 -0
- package/dist/ui/formatters/preview.js +7 -1
- package/dist/ui/formatters/sessions.d.ts +3 -2
- package/dist/ui/formatters/sessions.js +10 -3
- package/dist/ui/formatters/status.js +6 -1
- package/dist/ui/formatting.d.ts +7 -0
- package/dist/ui/formatting.js +13 -0
- package/dist/ui/logging/logger.d.ts +1 -1
- package/dist/ui/messages/chrome.d.ts +27 -6
- package/dist/ui/messages/chrome.js +78 -12
- package/dist/ui/messages/commands.d.ts +71 -3
- package/dist/ui/messages/commands.js +98 -3
- package/dist/ui/messages/networkMessages.d.ts +50 -5
- package/dist/ui/messages/networkMessages.js +50 -6
- package/dist/ui/messages/session.d.ts +8 -0
- package/dist/ui/messages/session.js +10 -0
- package/dist/utils/async.d.ts +3 -2
- package/dist/utils/async.js +16 -3
- package/dist/utils/atomicFile.d.ts +2 -1
- package/dist/utils/atomicFile.js +5 -2
- package/dist/utils/directories.d.ts +41 -0
- package/dist/utils/directories.js +48 -0
- package/dist/utils/http.d.ts +11 -4
- package/dist/utils/http.js +5 -3
- package/package.json +18 -4
- /package/dist/{commands/dom → runtime/page}/screenshotResize.d.ts +0 -0
- /package/dist/{commands/dom → runtime/page}/screenshotResize.js +0 -0
package/dist/index.js
CHANGED
|
@@ -54,6 +54,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
54
54
|
import { readFileSync } from "fs";
|
|
55
55
|
import { createRequire } from "module";
|
|
56
56
|
import { dirname, join } from "path";
|
|
57
|
+
function getBundledProtocolVersion() {
|
|
58
|
+
const packageJson = JSON.parse(
|
|
59
|
+
readFileSync(require2.resolve("devtools-protocol/package.json"), "utf-8")
|
|
60
|
+
);
|
|
61
|
+
return packageJson.version;
|
|
62
|
+
}
|
|
57
63
|
function loadProtocol() {
|
|
58
64
|
if (cachedProtocol) {
|
|
59
65
|
return cachedProtocol;
|
|
@@ -83,6 +89,10 @@ function findCommand(domainName, commandName) {
|
|
|
83
89
|
const normalized = commandName.toLowerCase();
|
|
84
90
|
return domain.commands.find((c) => c.name.toLowerCase() === normalized);
|
|
85
91
|
}
|
|
92
|
+
function findType(domainName, typeName) {
|
|
93
|
+
const normalized = typeName.toLowerCase();
|
|
94
|
+
return findDomain(domainName)?.types?.find((t) => t.id.toLowerCase() === normalized);
|
|
95
|
+
}
|
|
86
96
|
function normalizeMethod(input) {
|
|
87
97
|
const [domainName, commandName] = input.split(".");
|
|
88
98
|
if (!domainName || !commandName) {
|
|
@@ -115,6 +125,7 @@ __export(schema_exports, {
|
|
|
115
125
|
getDomainSummary: () => getDomainSummary,
|
|
116
126
|
getMethodSchema: () => getMethodSchema,
|
|
117
127
|
getProtocolCounts: () => getProtocolCounts,
|
|
128
|
+
getTypeSchema: () => getTypeSchema,
|
|
118
129
|
searchMethods: () => searchMethods
|
|
119
130
|
});
|
|
120
131
|
function getMethodSchema(domainName, methodName) {
|
|
@@ -129,47 +140,86 @@ function getMethodSchema(domainName, methodName) {
|
|
|
129
140
|
return buildMethodSchema(domain.domain, command);
|
|
130
141
|
}
|
|
131
142
|
function buildMethodSchema(domainName, command) {
|
|
132
|
-
const parameters = command.parameters?.map(paramToSchema) ?? [];
|
|
143
|
+
const parameters = command.parameters?.map((p) => paramToSchema(domainName, p)) ?? [];
|
|
133
144
|
const returns = command.returns?.map(returnToSchema) ?? [];
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
};
|
|
137
|
-
if (parameters.length > 0) {
|
|
138
|
-
const exampleParams = {};
|
|
139
|
-
parameters.forEach((p) => {
|
|
140
|
-
if (!p.required) return;
|
|
141
|
-
exampleParams[p.name] = getExampleValue(p);
|
|
142
|
-
});
|
|
143
|
-
if (Object.keys(exampleParams).length > 0) {
|
|
144
|
-
example.params = exampleParams;
|
|
145
|
-
example.command += ` --params '${JSON.stringify(exampleParams)}'`;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
145
|
+
const redirect = buildRedirect(command);
|
|
146
|
+
const exampleSource = parameters.length > 0 ? parameters : redirect?.parameters ?? [];
|
|
148
147
|
const schema = {
|
|
149
148
|
name: `${domainName}.${command.name}`,
|
|
150
149
|
domain: domainName,
|
|
151
150
|
method: command.name,
|
|
152
151
|
parameters,
|
|
153
152
|
returns,
|
|
154
|
-
example
|
|
153
|
+
example: buildExample(`${domainName}.${command.name}`, exampleSource)
|
|
155
154
|
};
|
|
155
|
+
if (redirect) schema.redirect = redirect;
|
|
156
156
|
if (command.description) schema.description = command.description;
|
|
157
157
|
if (command.experimental) schema.experimental = command.experimental;
|
|
158
158
|
if (command.deprecated) schema.deprecated = command.deprecated;
|
|
159
159
|
return schema;
|
|
160
160
|
}
|
|
161
|
-
function
|
|
161
|
+
function buildRedirect(command) {
|
|
162
|
+
if (!command.redirect) return void 0;
|
|
163
|
+
const target = findCommand(command.redirect, command.name);
|
|
164
|
+
return {
|
|
165
|
+
method: `${command.redirect}.${command.name}`,
|
|
166
|
+
resolved: target !== void 0,
|
|
167
|
+
parameters: target?.parameters?.map((p) => paramToSchema(command.redirect ?? "", p)) ?? []
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function buildExample(methodName, parameters) {
|
|
171
|
+
const example = { command: `bdg cdp ${methodName}` };
|
|
172
|
+
const required = parameters.filter((p) => p.required);
|
|
173
|
+
if (required.length === 0) return example;
|
|
174
|
+
const exampleParams = Object.fromEntries(required.map((p) => [p.name, getExampleValue(p)]));
|
|
175
|
+
example.params = exampleParams;
|
|
176
|
+
example.command += ` --params '${JSON.stringify(exampleParams)}'`;
|
|
177
|
+
return example;
|
|
178
|
+
}
|
|
179
|
+
function lookupRef(domainName, ref) {
|
|
180
|
+
const [refDomain, id] = ref.includes(".") ? ref.split(".") : [domainName, ref];
|
|
181
|
+
const type = refDomain && id ? findType(refDomain, id) : void 0;
|
|
182
|
+
return type && { name: `${refDomain}.${type.id}`, type };
|
|
183
|
+
}
|
|
184
|
+
function paramToSchema(domainName, param) {
|
|
162
185
|
const schema = {
|
|
163
186
|
name: param.name,
|
|
164
187
|
type: resolveType(param),
|
|
165
188
|
required: !param.optional
|
|
166
189
|
};
|
|
190
|
+
const referenced = param.$ref ? lookupRef(domainName, param.$ref) : void 0;
|
|
191
|
+
const values = param.enum ?? referenced?.type.enum;
|
|
167
192
|
if (param.description) schema.description = param.description;
|
|
168
193
|
if (param.deprecated) schema.deprecated = param.deprecated;
|
|
169
|
-
if (param.
|
|
194
|
+
if (param.experimental) schema.experimental = param.experimental;
|
|
195
|
+
if (values) schema.enum = values;
|
|
196
|
+
if (referenced) {
|
|
197
|
+
schema.ref = referenced.name;
|
|
198
|
+
schema.refType = referenced.type.type;
|
|
199
|
+
}
|
|
170
200
|
if (param.items) schema.items = resolveType(param.items);
|
|
171
201
|
return schema;
|
|
172
202
|
}
|
|
203
|
+
function getTypeSchema(domainName, typeName) {
|
|
204
|
+
const domain = findDomain(domainName);
|
|
205
|
+
const type = domain && findType(domain.domain, typeName);
|
|
206
|
+
if (!domain || !type) return void 0;
|
|
207
|
+
const schema = {
|
|
208
|
+
name: `${domain.domain}.${type.id}`,
|
|
209
|
+
domain: domain.domain,
|
|
210
|
+
id: type.id,
|
|
211
|
+
baseType: type.type
|
|
212
|
+
};
|
|
213
|
+
if (type.description) schema.description = type.description;
|
|
214
|
+
if (type.experimental) schema.experimental = type.experimental;
|
|
215
|
+
if (type.deprecated) schema.deprecated = type.deprecated;
|
|
216
|
+
if (type.enum) schema.enum = type.enum;
|
|
217
|
+
if (type.items) schema.items = resolveType(type.items);
|
|
218
|
+
if (type.properties) {
|
|
219
|
+
schema.properties = type.properties.map((p) => paramToSchema(domain.domain, p));
|
|
220
|
+
}
|
|
221
|
+
return schema;
|
|
222
|
+
}
|
|
173
223
|
function returnToSchema(ret) {
|
|
174
224
|
const schema = {
|
|
175
225
|
name: ret.name,
|
|
@@ -190,12 +240,16 @@ function getExampleValue(param) {
|
|
|
190
240
|
if (param.enum && param.enum.length > 0) {
|
|
191
241
|
return param.enum[0];
|
|
192
242
|
}
|
|
193
|
-
|
|
243
|
+
const type = param.refType ?? param.type;
|
|
244
|
+
const named = Object.hasOwn(EXAMPLE_VALUES, param.name) ? EXAMPLE_VALUES[param.name] : void 0;
|
|
245
|
+
const namedType = typeof named === "number" ? ["integer", "number"] : ["string"];
|
|
246
|
+
if (named !== void 0 && namedType.includes(type)) return named;
|
|
247
|
+
switch (type) {
|
|
194
248
|
case "string":
|
|
195
249
|
return "example";
|
|
196
250
|
case "integer":
|
|
197
251
|
case "number":
|
|
198
|
-
return
|
|
252
|
+
return 1;
|
|
199
253
|
case "boolean":
|
|
200
254
|
return true;
|
|
201
255
|
case "array":
|
|
@@ -265,11 +319,22 @@ function searchMethods(query) {
|
|
|
265
319
|
});
|
|
266
320
|
return [...named, ...described];
|
|
267
321
|
}
|
|
268
|
-
var SEARCH_KEYWORDS;
|
|
322
|
+
var EXAMPLE_VALUES, SEARCH_KEYWORDS;
|
|
269
323
|
var init_schema = __esm({
|
|
270
324
|
"src/cdp/schema.ts"() {
|
|
271
325
|
"use strict";
|
|
272
326
|
init_protocol();
|
|
327
|
+
EXAMPLE_VALUES = {
|
|
328
|
+
width: 1280,
|
|
329
|
+
height: 800,
|
|
330
|
+
x: 100,
|
|
331
|
+
y: 100,
|
|
332
|
+
deviceScaleFactor: 1,
|
|
333
|
+
scale: 1,
|
|
334
|
+
timeout: 5e3,
|
|
335
|
+
responseCode: 200,
|
|
336
|
+
url: "https://example.com"
|
|
337
|
+
};
|
|
273
338
|
SEARCH_KEYWORDS = {
|
|
274
339
|
"Emulation.setDeviceMetricsOverride": [
|
|
275
340
|
"viewport",
|
|
@@ -292,38 +357,6 @@ var init_schema = __esm({
|
|
|
292
357
|
}
|
|
293
358
|
});
|
|
294
359
|
|
|
295
|
-
// src/commands/dom/screenshotResize.ts
|
|
296
|
-
function calculateImageTokens(width, height) {
|
|
297
|
-
return Math.ceil(width * height / PIXELS_PER_TOKEN);
|
|
298
|
-
}
|
|
299
|
-
function calculateResizeScale(width, height) {
|
|
300
|
-
const longestEdge = Math.max(width, height);
|
|
301
|
-
if (longestEdge <= MAX_EDGE_PX) {
|
|
302
|
-
return 1;
|
|
303
|
-
}
|
|
304
|
-
return MAX_EDGE_PX / longestEdge;
|
|
305
|
-
}
|
|
306
|
-
function isTallPage(width, height) {
|
|
307
|
-
if (width === 0) return false;
|
|
308
|
-
return height / width > TALL_PAGE_THRESHOLD;
|
|
309
|
-
}
|
|
310
|
-
function shouldResize(width, height, noResize) {
|
|
311
|
-
if (noResize) {
|
|
312
|
-
return false;
|
|
313
|
-
}
|
|
314
|
-
const longestEdge = Math.max(width, height);
|
|
315
|
-
return longestEdge > MAX_EDGE_PX;
|
|
316
|
-
}
|
|
317
|
-
var MAX_EDGE_PX, PIXELS_PER_TOKEN, TALL_PAGE_THRESHOLD;
|
|
318
|
-
var init_screenshotResize = __esm({
|
|
319
|
-
"src/commands/dom/screenshotResize.ts"() {
|
|
320
|
-
"use strict";
|
|
321
|
-
MAX_EDGE_PX = 1568;
|
|
322
|
-
PIXELS_PER_TOKEN = 750;
|
|
323
|
-
TALL_PAGE_THRESHOLD = 3;
|
|
324
|
-
}
|
|
325
|
-
});
|
|
326
|
-
|
|
327
360
|
// node_modules/escape-string-regexp/index.js
|
|
328
361
|
var require_escape_string_regexp = __commonJS({
|
|
329
362
|
"node_modules/escape-string-regexp/index.js"(exports, module) {
|
|
@@ -960,7 +993,7 @@ var require_node = __commonJS({
|
|
|
960
993
|
var tty2 = __require("tty");
|
|
961
994
|
var util = __require("util");
|
|
962
995
|
exports.init = init;
|
|
963
|
-
exports.log =
|
|
996
|
+
exports.log = log33;
|
|
964
997
|
exports.formatArgs = formatArgs;
|
|
965
998
|
exports.save = save;
|
|
966
999
|
exports.load = load;
|
|
@@ -1095,7 +1128,7 @@ var require_node = __commonJS({
|
|
|
1095
1128
|
}
|
|
1096
1129
|
return (/* @__PURE__ */ new Date()).toISOString() + " ";
|
|
1097
1130
|
}
|
|
1098
|
-
function
|
|
1131
|
+
function log33(...args) {
|
|
1099
1132
|
return process.stderr.write(util.formatWithOptions(exports.inspectOpts, ...args) + "\n");
|
|
1100
1133
|
}
|
|
1101
1134
|
function save(namespaces) {
|
|
@@ -1287,14 +1320,14 @@ var require_is_docker = __commonJS({
|
|
|
1287
1320
|
var require_is_wsl = __commonJS({
|
|
1288
1321
|
"node_modules/is-wsl/index.js"(exports, module) {
|
|
1289
1322
|
"use strict";
|
|
1290
|
-
var
|
|
1323
|
+
var os5 = __require("os");
|
|
1291
1324
|
var fs19 = __require("fs");
|
|
1292
1325
|
var isDocker = require_is_docker();
|
|
1293
1326
|
var isWsl3 = () => {
|
|
1294
1327
|
if (process.platform !== "linux") {
|
|
1295
1328
|
return false;
|
|
1296
1329
|
}
|
|
1297
|
-
if (
|
|
1330
|
+
if (os5.release().toLowerCase().includes("microsoft")) {
|
|
1298
1331
|
if (isDocker()) {
|
|
1299
1332
|
return false;
|
|
1300
1333
|
}
|
|
@@ -1327,7 +1360,7 @@ function resourceTypeFromName(name) {
|
|
|
1327
1360
|
([type, abbreviation]) => type.toLowerCase() === wanted || abbreviation.toLowerCase() === wanted
|
|
1328
1361
|
)?.[0];
|
|
1329
1362
|
}
|
|
1330
|
-
var HTTP_LOCALHOST, MAX_NETWORK_REQUESTS, MAX_CONSOLE_MESSAGES, MAX_RESPONSE_SIZE, MAX_TOTAL_BODY_BYTES, MAX_VALUE_LENGTH, MAX_CONSOLE_TEXT_LENGTH, MAX_CONSOLE_JSON_TEXT_LENGTH, CHROME_NETWORK_BUFFER_TOTAL, CHROME_NETWORK_BUFFER_PER_RESOURCE, CHROME_POST_DATA_LIMIT, QUERY_JSON_LIST_LIMIT, LAYOUT_ELEMENT_LIMIT, MAX_TRIGGERED_REQUESTS, MAX_JSONL_BUFFER_SIZE, PORT_OPTION_DESCRIPTION, RESOURCE_TYPE_ABBREVIATIONS, MIME_TYPE_RULES;
|
|
1363
|
+
var HTTP_LOCALHOST, MAX_NETWORK_REQUESTS, MAX_CONSOLE_MESSAGES, MAX_RESPONSE_SIZE, MAX_TOTAL_BODY_BYTES, MAX_VALUE_LENGTH, MAX_CONSOLE_TEXT_LENGTH, MAX_CONSOLE_JSON_TEXT_LENGTH, CHROME_NETWORK_BUFFER_TOTAL, CHROME_NETWORK_BUFFER_PER_RESOURCE, CHROME_POST_DATA_LIMIT, QUERY_JSON_LIST_LIMIT, EVAL_JSON_ARRAY_LIMIT, LAYOUT_ELEMENT_LIMIT, MAX_TRIGGERED_REQUESTS, MAX_JSONL_BUFFER_SIZE, PORT_OPTION_DESCRIPTION, RESOURCE_TYPE_ABBREVIATIONS, MIME_TYPE_RULES;
|
|
1331
1364
|
var init_constants = __esm({
|
|
1332
1365
|
"src/constants.ts"() {
|
|
1333
1366
|
"use strict";
|
|
@@ -1343,6 +1376,7 @@ var init_constants = __esm({
|
|
|
1343
1376
|
CHROME_NETWORK_BUFFER_PER_RESOURCE = 10 * 1024 * 1024;
|
|
1344
1377
|
CHROME_POST_DATA_LIMIT = 1 * 1024 * 1024;
|
|
1345
1378
|
QUERY_JSON_LIST_LIMIT = 100;
|
|
1379
|
+
EVAL_JSON_ARRAY_LIMIT = 100;
|
|
1346
1380
|
LAYOUT_ELEMENT_LIMIT = 100;
|
|
1347
1381
|
MAX_TRIGGERED_REQUESTS = 50;
|
|
1348
1382
|
MAX_JSONL_BUFFER_SIZE = 256 * 1024 * 1024;
|
|
@@ -1421,8 +1455,8 @@ function createLogger(context) {
|
|
|
1421
1455
|
logger.debug = (message) => logMessage(message, "debug");
|
|
1422
1456
|
return logger;
|
|
1423
1457
|
}
|
|
1424
|
-
function logDebugError(
|
|
1425
|
-
|
|
1458
|
+
function logDebugError(log33, context, error) {
|
|
1459
|
+
log33.debug(`Failed to ${context}: ${getErrorMessage(error)}`);
|
|
1426
1460
|
}
|
|
1427
1461
|
var debugEnabled;
|
|
1428
1462
|
var init_logger = __esm({
|
|
@@ -1489,11 +1523,30 @@ function makeDirectory(dir, mode) {
|
|
|
1489
1523
|
}
|
|
1490
1524
|
fs2.mkdirSync(dir, { recursive: true, ...mode !== void 0 && { mode } });
|
|
1491
1525
|
}
|
|
1492
|
-
|
|
1526
|
+
function dirTrustProblem(dir, options = {}) {
|
|
1527
|
+
const stat = fs2.lstatSync(dir);
|
|
1528
|
+
const problem = (reason, kind) => ({ reason, kind });
|
|
1529
|
+
if (stat.isSymbolicLink()) return problem("it is a symbolic link", "symlink");
|
|
1530
|
+
if (!stat.isDirectory()) return problem("not a directory", "not-directory");
|
|
1531
|
+
const uid = process.getuid?.();
|
|
1532
|
+
if (uid !== void 0 && stat.uid !== uid) return problem(`owned by uid ${stat.uid}`, "owner");
|
|
1533
|
+
if (process.platform === "win32") return null;
|
|
1534
|
+
const writeBits = options.allowGroupWrite ? OTHER_WRITE : GROUP_OTHER_WRITE;
|
|
1535
|
+
if ((stat.mode & writeBits) === 0) return null;
|
|
1536
|
+
const mode = (stat.mode & 4095).toString(8);
|
|
1537
|
+
if ((stat.mode & STICKY) !== 0 && (stat.mode & OTHER_WRITE) !== 0) {
|
|
1538
|
+
return problem(`a shared sticky directory (mode ${mode})`, "shared");
|
|
1539
|
+
}
|
|
1540
|
+
return problem(`writable by others (mode ${mode})`, "writable");
|
|
1541
|
+
}
|
|
1542
|
+
var PSEUDO_FILESYSTEM, GROUP_OTHER_WRITE, OTHER_WRITE, STICKY;
|
|
1493
1543
|
var init_directories = __esm({
|
|
1494
1544
|
"src/utils/directories.ts"() {
|
|
1495
1545
|
"use strict";
|
|
1496
1546
|
PSEUDO_FILESYSTEM = /^\/(proc|sys)(\/|$)/;
|
|
1547
|
+
GROUP_OTHER_WRITE = 18;
|
|
1548
|
+
OTHER_WRITE = 2;
|
|
1549
|
+
STICKY = 512;
|
|
1497
1550
|
}
|
|
1498
1551
|
});
|
|
1499
1552
|
|
|
@@ -1502,12 +1555,16 @@ import * as fs3 from "fs";
|
|
|
1502
1555
|
import * as os2 from "os";
|
|
1503
1556
|
import * as path3 from "path";
|
|
1504
1557
|
function getSessionBaseDir() {
|
|
1505
|
-
const override =
|
|
1506
|
-
if (override
|
|
1558
|
+
const override = sessionDirOverride();
|
|
1559
|
+
if (override !== null) {
|
|
1507
1560
|
return path3.isAbsolute(override) ? override : path3.resolve(override);
|
|
1508
1561
|
}
|
|
1509
1562
|
return path3.join(os2.homedir(), ".bdg");
|
|
1510
1563
|
}
|
|
1564
|
+
function sessionDirOverride() {
|
|
1565
|
+
const override = process.env[SESSION_DIR_OVERRIDE_ENV];
|
|
1566
|
+
return override && override.trim().length > 0 ? override : null;
|
|
1567
|
+
}
|
|
1511
1568
|
function getSessionName() {
|
|
1512
1569
|
const name = process.env[SESSION_NAME_ENV]?.trim();
|
|
1513
1570
|
if (!name) return null;
|
|
@@ -1543,14 +1600,84 @@ function getDaemonSocketPath() {
|
|
|
1543
1600
|
return getSessionFilePath("DAEMON_SOCKET");
|
|
1544
1601
|
}
|
|
1545
1602
|
function ensureSessionDir() {
|
|
1546
|
-
makeDirectory(getSessionDir());
|
|
1603
|
+
makeDirectory(getSessionDir(), PRIVATE_DIR_MODE);
|
|
1547
1604
|
}
|
|
1548
|
-
|
|
1605
|
+
function getSessionDownloadsDir() {
|
|
1606
|
+
return path3.join(getSessionDir(), DOWNLOADS_DIR);
|
|
1607
|
+
}
|
|
1608
|
+
function sessionDirChain(dir) {
|
|
1609
|
+
const base = getSessionBaseDir();
|
|
1610
|
+
const relative2 = path3.relative(base, dir);
|
|
1611
|
+
if (relative2 === ".." || relative2.startsWith(`..${path3.sep}`) || path3.isAbsolute(relative2)) {
|
|
1612
|
+
return [{ dir, bdgOwned: false, isBase: false }];
|
|
1613
|
+
}
|
|
1614
|
+
const chain = [{ dir: base, bdgOwned: sessionDirOverride() === null, isBase: true }];
|
|
1615
|
+
let current = base;
|
|
1616
|
+
for (const part of relative2.split(path3.sep).filter(Boolean)) {
|
|
1617
|
+
current = path3.join(current, part);
|
|
1618
|
+
chain.push({ dir: current, bdgOwned: true, isBase: false });
|
|
1619
|
+
}
|
|
1620
|
+
return chain;
|
|
1621
|
+
}
|
|
1622
|
+
function tightenDir(dir) {
|
|
1623
|
+
if (process.platform === "win32") return;
|
|
1624
|
+
let fd;
|
|
1625
|
+
try {
|
|
1626
|
+
const { O_RDONLY, O_DIRECTORY, O_NOFOLLOW } = fs3.constants;
|
|
1627
|
+
fd = fs3.openSync(dir, O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
|
|
1628
|
+
const stat = fs3.fstatSync(fd);
|
|
1629
|
+
if (stat.uid !== process.getuid?.() || (stat.mode & GROUP_OTHER_ACCESS) === 0) return;
|
|
1630
|
+
fs3.fchmodSync(fd, PRIVATE_DIR_MODE);
|
|
1631
|
+
log.debug(`Session directory ${dir} restricted to mode 700`);
|
|
1632
|
+
} catch (error) {
|
|
1633
|
+
log.debug(`Session directory ${dir} not restricted: ${getErrorMessage(error)}`);
|
|
1634
|
+
} finally {
|
|
1635
|
+
if (fd !== void 0) fs3.closeSync(fd);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
function chainDirProblem(entry) {
|
|
1639
|
+
const problem = dirTrustProblem(entry.dir, SESSION_DIR_TRUST);
|
|
1640
|
+
if (problem?.kind !== "symlink" || !entry.isBase) return problem;
|
|
1641
|
+
const target = fs3.realpathSync(entry.dir);
|
|
1642
|
+
const targetProblem = dirTrustProblem(target, SESSION_DIR_TRUST);
|
|
1643
|
+
return targetProblem && {
|
|
1644
|
+
...targetProblem,
|
|
1645
|
+
reason: `it links to ${target}, which is ${targetProblem.reason}`
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
function acceptTrustedDir(entry) {
|
|
1649
|
+
if (entry.bdgOwned) {
|
|
1650
|
+
tightenDir(entry.dir);
|
|
1651
|
+
return;
|
|
1652
|
+
}
|
|
1653
|
+
const mode = fs3.statSync(entry.dir, { throwIfNoEntry: false })?.mode ?? 0;
|
|
1654
|
+
if ((mode & GROUP_OTHER_ACCESS) === 0) return;
|
|
1655
|
+
log.debug(
|
|
1656
|
+
`Session directory ${entry.dir} is open to group or others; left as is (chosen with ${SESSION_DIR_OVERRIDE_ENV})`
|
|
1657
|
+
);
|
|
1658
|
+
}
|
|
1659
|
+
function secureSessionDir(dir = getSessionDir()) {
|
|
1660
|
+
for (const entry of sessionDirChain(dir)) {
|
|
1661
|
+
let problem;
|
|
1662
|
+
try {
|
|
1663
|
+
problem = chainDirProblem(entry);
|
|
1664
|
+
} catch (error) {
|
|
1665
|
+
const code = error.code;
|
|
1666
|
+
if (code === "ENOENT" || code === "ENOTDIR") continue;
|
|
1667
|
+
problem = { reason: getErrorMessage(error), kind: "not-directory" };
|
|
1668
|
+
}
|
|
1669
|
+
if (problem !== null) return { dir: entry.dir, ...problem, bdgOwned: entry.bdgOwned };
|
|
1670
|
+
acceptTrustedDir(entry);
|
|
1671
|
+
}
|
|
1672
|
+
return null;
|
|
1673
|
+
}
|
|
1674
|
+
var log, SESSION_FILES, SESSION_DIR_OVERRIDE_ENV, SESSION_NAME_ENV, NAMED_SESSIONS_DIR, MAX_SOCKET_PATH_BYTES, MAX_DAEMON_SOCKET_PATH_BYTES, SESSION_STATE_FILES, PRIVATE_DIR_MODE, GROUP_OTHER_ACCESS, DOWNLOADS_DIR, SESSION_DIR_TRUST;
|
|
1549
1675
|
var init_paths = __esm({
|
|
1550
1676
|
"src/session/paths.ts"() {
|
|
1551
1677
|
"use strict";
|
|
1552
1678
|
init_logging();
|
|
1553
1679
|
init_directories();
|
|
1680
|
+
init_errors();
|
|
1554
1681
|
log = createLogger("session");
|
|
1555
1682
|
SESSION_FILES = {
|
|
1556
1683
|
OUTPUT: "session.json",
|
|
@@ -1572,6 +1699,10 @@ var init_paths = __esm({
|
|
|
1572
1699
|
"CHROME_PID",
|
|
1573
1700
|
"METADATA"
|
|
1574
1701
|
];
|
|
1702
|
+
PRIVATE_DIR_MODE = 448;
|
|
1703
|
+
GROUP_OTHER_ACCESS = 63;
|
|
1704
|
+
DOWNLOADS_DIR = "downloads";
|
|
1705
|
+
SESSION_DIR_TRUST = { allowGroupWrite: true };
|
|
1575
1706
|
}
|
|
1576
1707
|
});
|
|
1577
1708
|
|
|
@@ -1653,14 +1784,17 @@ var init_atomicFile = __esm({
|
|
|
1653
1784
|
*
|
|
1654
1785
|
* @param filePath - Target file path
|
|
1655
1786
|
* @param data - Data to write
|
|
1656
|
-
* @param options - Write options
|
|
1787
|
+
* @param options - Write options (`mode`: permissions of the new file, less the umask)
|
|
1657
1788
|
* @returns Promise that resolves when write completes
|
|
1658
1789
|
* @throws Error if write operation fails
|
|
1659
1790
|
*/
|
|
1660
1791
|
static async writeAsync(filePath, data, options = {}) {
|
|
1661
1792
|
const tmpPath = this.getTempPath(filePath);
|
|
1662
1793
|
try {
|
|
1663
|
-
await fs4.promises.writeFile(tmpPath, data, {
|
|
1794
|
+
await fs4.promises.writeFile(tmpPath, data, {
|
|
1795
|
+
encoding: options.encoding ?? "utf-8",
|
|
1796
|
+
...options.mode !== void 0 && { mode: options.mode }
|
|
1797
|
+
});
|
|
1664
1798
|
await fs4.promises.rename(tmpPath, filePath);
|
|
1665
1799
|
} catch (error) {
|
|
1666
1800
|
try {
|
|
@@ -1703,48 +1837,6 @@ var init_atomicFile = __esm({
|
|
|
1703
1837
|
}
|
|
1704
1838
|
});
|
|
1705
1839
|
|
|
1706
|
-
// src/session/metadata.ts
|
|
1707
|
-
import * as fs8 from "fs";
|
|
1708
|
-
function handleCorruptedMetadata(metaPath, error, options) {
|
|
1709
|
-
if (options?.warnOnCorruption) {
|
|
1710
|
-
log6.info(`Session metadata corrupted (cannot read details): ${getErrorMessage(error)}`);
|
|
1711
|
-
log6.info(`Troubleshooting: Run "${sessionCommand("bdg cleanup")}" to remove corrupted files`);
|
|
1712
|
-
}
|
|
1713
|
-
if (options?.selfHealOnCorruption) {
|
|
1714
|
-
try {
|
|
1715
|
-
fs8.rmSync(metaPath, { force: true });
|
|
1716
|
-
log6.info("Removed corrupted session metadata file");
|
|
1717
|
-
} catch (deleteError) {
|
|
1718
|
-
log6.debug(`Failed to remove corrupted metadata: ${getErrorMessage(deleteError)}`);
|
|
1719
|
-
}
|
|
1720
|
-
}
|
|
1721
|
-
}
|
|
1722
|
-
function readSessionMetadata(options) {
|
|
1723
|
-
const metaPath = getSessionFilePath("METADATA");
|
|
1724
|
-
if (!fs8.existsSync(metaPath)) {
|
|
1725
|
-
return null;
|
|
1726
|
-
}
|
|
1727
|
-
try {
|
|
1728
|
-
const content = fs8.readFileSync(metaPath, "utf-8");
|
|
1729
|
-
return JSON.parse(content);
|
|
1730
|
-
} catch (error) {
|
|
1731
|
-
handleCorruptedMetadata(metaPath, error, options);
|
|
1732
|
-
return null;
|
|
1733
|
-
}
|
|
1734
|
-
}
|
|
1735
|
-
var log6;
|
|
1736
|
-
var init_metadata = __esm({
|
|
1737
|
-
"src/session/metadata.ts"() {
|
|
1738
|
-
"use strict";
|
|
1739
|
-
init_logging();
|
|
1740
|
-
init_sessionCommand();
|
|
1741
|
-
init_atomicFile();
|
|
1742
|
-
init_errors();
|
|
1743
|
-
init_paths();
|
|
1744
|
-
log6 = createLogger("session");
|
|
1745
|
-
}
|
|
1746
|
-
});
|
|
1747
|
-
|
|
1748
1840
|
// src/utils/validation.ts
|
|
1749
1841
|
function valid() {
|
|
1750
1842
|
return { valid: true };
|
|
@@ -2041,6 +2133,10 @@ function formatDuration(ms) {
|
|
|
2041
2133
|
}
|
|
2042
2134
|
return `${ms}ms`;
|
|
2043
2135
|
}
|
|
2136
|
+
function formatBytes(bytes) {
|
|
2137
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
2138
|
+
return bytes < 1024 * 1024 ? `${(bytes / 1024).toFixed(1)} KB` : `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
2139
|
+
}
|
|
2044
2140
|
var hintsHidden, OutputFormatter, MIN_URL_END_SHOWN, WEB_PROTOCOLS;
|
|
2045
2141
|
var init_formatting = __esm({
|
|
2046
2142
|
"src/ui/formatting.ts"() {
|
|
@@ -2192,13 +2288,22 @@ function chromeClosedMessage(pid) {
|
|
|
2192
2288
|
function orphanedDaemonsCleanedMessage(count) {
|
|
2193
2289
|
return `Cleaned up ${count} orphaned daemon process${count === 1 ? "" : "es"}`;
|
|
2194
2290
|
}
|
|
2195
|
-
function skillBackupMessage(
|
|
2196
|
-
return `previous copy kept in ${
|
|
2291
|
+
function skillBackupMessage(path8) {
|
|
2292
|
+
return `previous copy kept in ${path8}`;
|
|
2197
2293
|
}
|
|
2198
2294
|
function fieldLabelList(labels) {
|
|
2199
2295
|
const shown = labels.slice(0, 5).map((label) => label.replace(/\s*:\s*$/, "")).join(", ");
|
|
2200
2296
|
return labels.length > 5 ? `${shown} and ${labels.length - 5} more` : shown;
|
|
2201
2297
|
}
|
|
2298
|
+
function cdpUnlistedMethodWarning(method, protocolVersion) {
|
|
2299
|
+
return `${method} is not in the bundled protocol (devtools-protocol ${protocolVersion}); sending it to Chrome as is`;
|
|
2300
|
+
}
|
|
2301
|
+
function cdpUnresolvedRedirectLine(method) {
|
|
2302
|
+
return `Redirect target ${method} is not in the protocol (unresolved redirect)`;
|
|
2303
|
+
}
|
|
2304
|
+
function cdpRedirectTitle(method) {
|
|
2305
|
+
return `Implemented by ${method} (redirect)`;
|
|
2306
|
+
}
|
|
2202
2307
|
function requiredFieldsEmptyMessage(labels) {
|
|
2203
2308
|
const fields = labels.length === 1 ? "field" : "fields";
|
|
2204
2309
|
return `${labels.length} required ${fields} empty: ${fieldLabelList(labels)}`;
|
|
@@ -2235,6 +2340,19 @@ function pageNavigationText(navigation) {
|
|
|
2235
2340
|
const status = navigation.status === void 0 ? "" : ` (${navigation.status})`;
|
|
2236
2341
|
return `navigated to ${navigation.url}${status}`;
|
|
2237
2342
|
}
|
|
2343
|
+
function downloadText(download) {
|
|
2344
|
+
const where = download.path === void 0 ? "" : ` \u2192 ${download.path}`;
|
|
2345
|
+
const running = download.state === "inProgress";
|
|
2346
|
+
const bytes = download.bytes === void 0 || running && download.bytes === 0 ? "" : `, ${formatBytes(download.bytes)}${running ? " so far" : ""}`;
|
|
2347
|
+
const reason = download.reason === void 0 ? "" : `: ${download.reason}`;
|
|
2348
|
+
return `Download: ${download.suggestedFilename}${where} (${download.state}${bytes}${reason})`;
|
|
2349
|
+
}
|
|
2350
|
+
function downloadsSummary(downloads) {
|
|
2351
|
+
const last = downloads[downloads.length - 1];
|
|
2352
|
+
if (!last) return "0";
|
|
2353
|
+
const where = last.path === void 0 ? "" : ` \u2192 ${last.path}`;
|
|
2354
|
+
return `${downloads.length} (last: ${last.suggestedFilename}${where}, ${last.state})`;
|
|
2355
|
+
}
|
|
2238
2356
|
function moreMessagesText(count) {
|
|
2239
2357
|
return `(+${count} more)`;
|
|
2240
2358
|
}
|
|
@@ -2535,6 +2653,9 @@ function sessionFilesCleanedMessage() {
|
|
|
2535
2653
|
function sessionOutputRemovedMessage() {
|
|
2536
2654
|
return "Session output file removed";
|
|
2537
2655
|
}
|
|
2656
|
+
function downloadsKeptMessage(dir, files) {
|
|
2657
|
+
return `Downloads kept: ${pluralize(files, "file")} in ${dir} (delete them yourself when done)`;
|
|
2658
|
+
}
|
|
2538
2659
|
function sessionDirectoryCleanMessage() {
|
|
2539
2660
|
return "Session directory is now clean";
|
|
2540
2661
|
}
|
|
@@ -2598,7 +2719,7 @@ function helpJsonDetailsNote() {
|
|
|
2598
2719
|
function moreCharsNote(count) {
|
|
2599
2720
|
return `\u2026 ${count} more chars (use --full)`;
|
|
2600
2721
|
}
|
|
2601
|
-
var REQUIRED_FIELD_EMPTY_REASON, NO_VISIBLE_EFFECT, HOVER_OFF_DONE, HOVER_USAGE, LAYOUT_REASONS, SCROLL_LOCKED_PREFIX, VIEWPORT_POSITION_HINTS, INSPECT_OUTPUT_LEGEND, COLLAPSED_LISTENERS_HEADING, NO_LISTENERS_HINT, NOTE_TYPES_SHOWN, POINTER_ACTION_DONE, POINTER_ACTION_NOUN, PAGE_ACTION_DONE, PAGE_INFO_DESCRIPTION, PAGE_EMULATE_DESCRIPTION, PAGE_ACTION_DESCRIPTIONS, CLICK_RESULT_WAIT_HELP, WAIT_HELP_EXAMPLES, FRAME_URL_MAX_LENGTH, AUDIT_HELP_EXAMPLES, CSS_SEARCH_HELP_EXAMPLES;
|
|
2722
|
+
var REQUIRED_FIELD_EMPTY_REASON, CDP_EXECUTION_HELP, NO_VISIBLE_EFFECT, HOVER_OFF_DONE, HOVER_USAGE, LAYOUT_REASONS, SCROLL_LOCKED_PREFIX, VIEWPORT_POSITION_HINTS, INSPECT_OUTPUT_LEGEND, COLLAPSED_LISTENERS_HEADING, NO_LISTENERS_HINT, NOTE_TYPES_SHOWN, POINTER_ACTION_DONE, POINTER_ACTION_NOUN, PAGE_ACTION_DONE, PAGE_INFO_DESCRIPTION, PAGE_EMULATE_DESCRIPTION, PAGE_ACTION_DESCRIPTIONS, CLICK_RESULT_WAIT_HELP, WAIT_HELP_EXAMPLES, FRAME_URL_MAX_LENGTH, AUDIT_HELP_EXAMPLES, CSS_SEARCH_HELP_EXAMPLES;
|
|
2602
2723
|
var init_commands = __esm({
|
|
2603
2724
|
"src/ui/messages/commands.ts"() {
|
|
2604
2725
|
"use strict";
|
|
@@ -2607,6 +2728,7 @@ var init_commands = __esm({
|
|
|
2607
2728
|
init_sessionCommand();
|
|
2608
2729
|
init_strings();
|
|
2609
2730
|
REQUIRED_FIELD_EMPTY_REASON = "Required field is empty";
|
|
2731
|
+
CDP_EXECUTION_HELP = " Execution: bundled methods are case-insensitive (network.getcookies works)\n Other methods are sent as typed (case-sensitive)";
|
|
2610
2732
|
NO_VISIBLE_EFFECT = "no visible effect observed: no DOM change, requests or navigation within 300 ms";
|
|
2611
2733
|
HOVER_OFF_DONE = "\u2713 Mouse moved off the page (hover styles and menus that close on mouseleave are gone)";
|
|
2612
2734
|
HOVER_USAGE = "bdg dom hover <selector|index>, or bdg dom hover --off to move the mouse away";
|
|
@@ -2911,7 +3033,7 @@ var init_CommandError = __esm({
|
|
|
2911
3033
|
});
|
|
2912
3034
|
|
|
2913
3035
|
// src/ipc/transport/IPCError.ts
|
|
2914
|
-
var IPCError, IPCConnectionError, IPCTimeoutError, IPCParseError, IPCEarlyCloseError;
|
|
3036
|
+
var IPCError, IPCConnectionError, IPCTimeoutError, IPCParseError, IPCEarlyCloseError, IPCCancelledError;
|
|
2915
3037
|
var init_IPCError = __esm({
|
|
2916
3038
|
"src/ipc/transport/IPCError.ts"() {
|
|
2917
3039
|
"use strict";
|
|
@@ -2969,6 +3091,14 @@ var init_IPCError = __esm({
|
|
|
2969
3091
|
this.requestName = requestName;
|
|
2970
3092
|
}
|
|
2971
3093
|
};
|
|
3094
|
+
IPCCancelledError = class extends IPCError {
|
|
3095
|
+
name = "IPCCancelledError";
|
|
3096
|
+
requestName;
|
|
3097
|
+
constructor(requestName) {
|
|
3098
|
+
super(`${requestName} request cancelled`, EXIT_CODES.SOFTWARE_ERROR);
|
|
3099
|
+
this.requestName = requestName;
|
|
3100
|
+
}
|
|
3101
|
+
};
|
|
2972
3102
|
}
|
|
2973
3103
|
});
|
|
2974
3104
|
|
|
@@ -3120,6 +3250,7 @@ var init_shellDetection = __esm({
|
|
|
3120
3250
|
});
|
|
3121
3251
|
|
|
3122
3252
|
// src/errors/messages.ts
|
|
3253
|
+
import * as os3 from "os";
|
|
3123
3254
|
import * as path4 from "path";
|
|
3124
3255
|
function sessionAlreadyRunningError(pid, duration, targetUrl) {
|
|
3125
3256
|
return joinLines(
|
|
@@ -3170,6 +3301,12 @@ function commandTimedOutError(seconds) {
|
|
|
3170
3301
|
suggestion: `Check the session with: ${sessionCommand("bdg status")}; if the page stays frozen, end it with: ${sessionCommand("bdg cleanup --force")}`
|
|
3171
3302
|
};
|
|
3172
3303
|
}
|
|
3304
|
+
function screenshotInterruptedError(signal) {
|
|
3305
|
+
return {
|
|
3306
|
+
message: `Screenshot cancelled (${signal === "SIGINT" ? "interrupted" : "terminated"})`,
|
|
3307
|
+
suggestion: "No file was written; run it again to capture (the next command sees the page's own emulation)"
|
|
3308
|
+
};
|
|
3309
|
+
}
|
|
3173
3310
|
function sessionUnavailableSuggestion(exitCode) {
|
|
3174
3311
|
return exitCode === 85 ? `Wait until "${sessionCommand("bdg <url>")}" returns, then retry` : startSessionSuggestion();
|
|
3175
3312
|
}
|
|
@@ -3291,8 +3428,13 @@ function unknownHelpTopicError(topic, closest) {
|
|
|
3291
3428
|
suggestion: closest ? `Did you mean: bdg help ${closest}?` : `Run "bdg ${parent ? `${parent} ` : ""}--help" for commands`
|
|
3292
3429
|
};
|
|
3293
3430
|
}
|
|
3294
|
-
function
|
|
3295
|
-
|
|
3431
|
+
function didYouMeanSuggestion(candidates) {
|
|
3432
|
+
const last = candidates.at(-1) ?? "";
|
|
3433
|
+
const rest = candidates.slice(0, -1);
|
|
3434
|
+
return `Did you mean: ${rest.length > 0 ? `${rest.join(", ")} or ${last}` : last}?`;
|
|
3435
|
+
}
|
|
3436
|
+
function usageHelpSuggestion(path8) {
|
|
3437
|
+
return `Run "${path8} --help" for usage`;
|
|
3296
3438
|
}
|
|
3297
3439
|
function missingSubcommandMessage() {
|
|
3298
3440
|
return "Missing subcommand";
|
|
@@ -3349,7 +3491,7 @@ function invalidSessionNameError(name, maxLength) {
|
|
|
3349
3491
|
function sessionNameSocketTooLongError(name, socketPath, max) {
|
|
3350
3492
|
return {
|
|
3351
3493
|
message: `Session name "${name}" makes the daemon socket path too long (${Buffer.byteLength(socketPath)} bytes, at most ${max}): ${socketPath}`,
|
|
3352
|
-
suggestion:
|
|
3494
|
+
suggestion: `Use a shorter session name, or a shorter BDG_SESSION_DIR (e.g. ${privateSessionDirExample()})`
|
|
3353
3495
|
};
|
|
3354
3496
|
}
|
|
3355
3497
|
function purgeNeedsNamedSessionError() {
|
|
@@ -3364,19 +3506,52 @@ function purgeRefusedError(dir, reason) {
|
|
|
3364
3506
|
suggestion: `End the session first (${sessionCommand("bdg cleanup --force")}), then retry: ${sessionCommand("bdg cleanup --purge")}`
|
|
3365
3507
|
};
|
|
3366
3508
|
}
|
|
3509
|
+
function privateSessionDirExample() {
|
|
3510
|
+
const runtimeDir = process.env["XDG_RUNTIME_DIR"]?.trim();
|
|
3511
|
+
if (runtimeDir) return path4.join(runtimeDir, "bdg");
|
|
3512
|
+
const uid = process.getuid?.();
|
|
3513
|
+
return path4.join(os3.tmpdir(), uid === void 0 ? "bdg" : `bdg-${uid}`);
|
|
3514
|
+
}
|
|
3515
|
+
function sessionDirSuggestion() {
|
|
3516
|
+
return `Set BDG_SESSION_DIR to a short directory only you can write to, e.g. BDG_SESSION_DIR=${privateSessionDirExample()}`;
|
|
3517
|
+
}
|
|
3367
3518
|
function sessionDirIsFileError(dir) {
|
|
3368
|
-
return { message: `Session directory ${dir} is a file`, suggestion:
|
|
3519
|
+
return { message: `Session directory ${dir} is a file`, suggestion: sessionDirSuggestion() };
|
|
3369
3520
|
}
|
|
3370
3521
|
function sessionDirNotWritableError(dir, reason) {
|
|
3371
3522
|
return {
|
|
3372
3523
|
message: `Session directory ${dir} is not writable (${reason})`,
|
|
3373
|
-
suggestion:
|
|
3524
|
+
suggestion: sessionDirSuggestion()
|
|
3525
|
+
};
|
|
3526
|
+
}
|
|
3527
|
+
function untrustedSessionDirError(untrusted) {
|
|
3528
|
+
return {
|
|
3529
|
+
message: `Session directory ${untrusted.dir} is not safe to use: ${untrusted.reason}`,
|
|
3530
|
+
suggestion: untrustedSessionDirSuggestion(untrusted)
|
|
3531
|
+
};
|
|
3532
|
+
}
|
|
3533
|
+
function untrustedSessionDirSuggestion(untrusted) {
|
|
3534
|
+
const { dir, kind, bdgOwned } = untrusted;
|
|
3535
|
+
const example = `BDG_SESSION_DIR=${privateSessionDirExample()}`;
|
|
3536
|
+
if (kind === "symlink") {
|
|
3537
|
+
return `Remove the link (rm ${dir}) or point BDG_SESSION_DIR at the real directory`;
|
|
3538
|
+
}
|
|
3539
|
+
if (kind === "shared") {
|
|
3540
|
+
return `${dir} is shared by all users; use a subdirectory only you can write to, e.g. ${example}`;
|
|
3541
|
+
}
|
|
3542
|
+
if (bdgOwned) return `Run chmod 700 ${dir} (or remove it if it is not yours), then retry`;
|
|
3543
|
+
return `Use a directory you own that others cannot write to, e.g. ${example} (or chmod 700 ${dir} if it is yours)`;
|
|
3544
|
+
}
|
|
3545
|
+
function daemonLogNotOpenedError(logPath, code) {
|
|
3546
|
+
return {
|
|
3547
|
+
message: `Cannot open the daemon log ${logPath} (${code === "ELOOP" ? "it is a symbolic link" : code})`,
|
|
3548
|
+
suggestion: `Remove ${logPath} if you did not create it, then retry`
|
|
3374
3549
|
};
|
|
3375
3550
|
}
|
|
3376
3551
|
function socketPathTooLongError(socketPath, max) {
|
|
3377
3552
|
return {
|
|
3378
3553
|
message: `Session directory path is too long for the daemon socket (${Buffer.byteLength(socketPath)} bytes, at most ${max}): ${socketPath}`,
|
|
3379
|
-
suggestion:
|
|
3554
|
+
suggestion: sessionDirSuggestion()
|
|
3380
3555
|
};
|
|
3381
3556
|
}
|
|
3382
3557
|
function externalChromeUnreachableError(endpoint, secure) {
|
|
@@ -3575,18 +3750,6 @@ function waitTargetRequiredError() {
|
|
|
3575
3750
|
suggestion: `e.g. ${sessionCommand("bdg dom wait '#result' --visible")}, ${sessionCommand("bdg dom wait body --text 'Welcome'")} or ${sessionCommand("bdg dom wait --load")}`
|
|
3576
3751
|
};
|
|
3577
3752
|
}
|
|
3578
|
-
function elementNotVisibleError() {
|
|
3579
|
-
return {
|
|
3580
|
-
message: "Element is not rendered (e.g. display: none), so it has no area to capture",
|
|
3581
|
-
suggestion: "Make it visible first, or capture a visible ancestor"
|
|
3582
|
-
};
|
|
3583
|
-
}
|
|
3584
|
-
function elementZeroDimensionsError() {
|
|
3585
|
-
return {
|
|
3586
|
-
message: "Element has zero width or height, so there is nothing to capture",
|
|
3587
|
-
suggestion: "It may be collapsed or empty; capture a visible ancestor instead"
|
|
3588
|
-
};
|
|
3589
|
-
}
|
|
3590
3753
|
function hoverOffWithTargetError() {
|
|
3591
3754
|
return {
|
|
3592
3755
|
message: "--off moves the mouse off the page; it takes no element",
|
|
@@ -3709,16 +3872,44 @@ function formInIframeError(iframeUrl, crossOrigin) {
|
|
|
3709
3872
|
suggestion: crossOrigin ? "Cross-origin iframes cannot be read or controlled; open the iframe URL directly: bdg <iframe url>" : 'dom form lists forms of the main document only; its fields are reachable directly: bdg dom query "input, select, textarea", then bdg dom fill <index> <value>'
|
|
3710
3873
|
};
|
|
3711
3874
|
}
|
|
3875
|
+
function cdpTypeNotMethodError(name) {
|
|
3876
|
+
return {
|
|
3877
|
+
message: `${name} is a protocol type, not a method`,
|
|
3878
|
+
suggestion: `Use: bdg cdp ${name} --describe (to see its values or properties)`
|
|
3879
|
+
};
|
|
3880
|
+
}
|
|
3881
|
+
function cdpMethodNotFoundError(input, similar, listHint) {
|
|
3882
|
+
const didYouMean = similar.length > 0 ? ["", "Did you mean:", ...similar.map((name) => ` - ${name}`)] : [];
|
|
3883
|
+
return {
|
|
3884
|
+
message: `Method '${input}' not found`,
|
|
3885
|
+
suggestion: [listHint, ...didYouMean].join("\n")
|
|
3886
|
+
};
|
|
3887
|
+
}
|
|
3888
|
+
function cdpMethodTypoError(input, similar, listHint) {
|
|
3889
|
+
const err = cdpMethodNotFoundError(input, similar, listHint);
|
|
3890
|
+
return {
|
|
3891
|
+
message: err.message,
|
|
3892
|
+
suggestion: `${err.suggestion}
|
|
3893
|
+
|
|
3894
|
+
To send it as typed (a method newer than bdg's protocol): ${sessionCommand(`bdg cdp ${input} --send-anyway`)}`
|
|
3895
|
+
};
|
|
3896
|
+
}
|
|
3897
|
+
function cdpMethodNotInBundledProtocolError(method, protocolVersion) {
|
|
3898
|
+
return {
|
|
3899
|
+
message: `Method '${method}' is not in the bundled protocol (devtools-protocol ${protocolVersion})`,
|
|
3900
|
+
suggestion: `bdg cdp ${method} sends it to Chrome as is, which may still have it. Methods bdg knows: bdg cdp --search <keyword>`
|
|
3901
|
+
};
|
|
3902
|
+
}
|
|
3712
3903
|
function skillSourceMissingError(source) {
|
|
3713
3904
|
return {
|
|
3714
3905
|
message: `The bdg skill is missing from the installed package (${source})`,
|
|
3715
3906
|
suggestion: "Reinstall bdg: npm install -g browser-debugger-cli"
|
|
3716
3907
|
};
|
|
3717
3908
|
}
|
|
3718
|
-
function skillWriteFailedError(
|
|
3909
|
+
function skillWriteFailedError(path8, reason, pathIsFile, otherTargetFlag) {
|
|
3719
3910
|
const fix = pathIsFile ? "A part of that path is a file, not a directory: move it away" : "Check the permissions of that directory";
|
|
3720
3911
|
return {
|
|
3721
|
-
message: `Could not write ${
|
|
3912
|
+
message: `Could not write ${path8}: ${reason}`,
|
|
3722
3913
|
suggestion: otherTargetFlag ? `${fix}, or install for the other agent only (${otherTargetFlag})` : fix
|
|
3723
3914
|
};
|
|
3724
3915
|
}
|
|
@@ -3731,7 +3922,7 @@ function intervalWithoutFollowError() {
|
|
|
3731
3922
|
function unknownAuditCheckMessage(value, similar, checks) {
|
|
3732
3923
|
return `Unknown check "${value}"${similar.length > 0 ? `; did you mean ${similar[0]}?` : ""} (checks: ${checks.join(", ")})`;
|
|
3733
3924
|
}
|
|
3734
|
-
var LAUNCHED_CHROME_DESCRIPTION, A11Y_NAME_QUERY_EXAMPLE, SCOPED_FILTER_EXAMPLES, PLAYWRIGHT_ONLY_SYNTAX, MALFORMED_SELECTOR_DETAILS,
|
|
3925
|
+
var LAUNCHED_CHROME_DESCRIPTION, A11Y_NAME_QUERY_EXAMPLE, SCOPED_FILTER_EXAMPLES, PLAYWRIGHT_ONLY_SYNTAX, MALFORMED_SELECTOR_DETAILS, SCROLL_PROBLEMS, VIA_LABEL_SUFFIX, NAME_QUERY_PLACEHOLDER, LABEL_WITHOUT_CONTROL, REDECLARED_PATTERN;
|
|
3735
3926
|
var init_messages = __esm({
|
|
3736
3927
|
"src/errors/messages.ts"() {
|
|
3737
3928
|
"use strict";
|
|
@@ -3752,7 +3943,6 @@ var init_messages = __esm({
|
|
|
3752
3943
|
"empty-has": () => ":has() has an empty selector",
|
|
3753
3944
|
"scope-in-has": () => ":has() with filters is already relative to the element; write :has(> a:visible) instead of :has(:scope > a:visible)"
|
|
3754
3945
|
};
|
|
3755
|
-
SESSION_DIR_SUGGESTION = "Set BDG_SESSION_DIR to a short, writable directory, e.g. BDG_SESSION_DIR=/tmp/bdg";
|
|
3756
3946
|
SCROLL_PROBLEMS = {
|
|
3757
3947
|
"index-without-selector": [
|
|
3758
3948
|
"--index requires a selector",
|
|
@@ -3886,6 +4076,47 @@ var init_suggestions = __esm({
|
|
|
3886
4076
|
}
|
|
3887
4077
|
});
|
|
3888
4078
|
|
|
4079
|
+
// src/commands/shared/interrupt.ts
|
|
4080
|
+
function interruptExitCode(signal) {
|
|
4081
|
+
return signal === "SIGINT" ? EXIT_CODES.INTERRUPTED : EXIT_CODES.TERMINATED;
|
|
4082
|
+
}
|
|
4083
|
+
function interruptSignal(interrupt) {
|
|
4084
|
+
return interrupt.reason === "SIGTERM" ? "SIGTERM" : "SIGINT";
|
|
4085
|
+
}
|
|
4086
|
+
function abortOnInterrupt() {
|
|
4087
|
+
const interrupt = new AbortController();
|
|
4088
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
4089
|
+
process.on(signal, () => {
|
|
4090
|
+
if (interrupt.signal.aborted) process.exit(interruptExitCode(signal));
|
|
4091
|
+
interrupt.abort(signal);
|
|
4092
|
+
});
|
|
4093
|
+
}
|
|
4094
|
+
return interrupt.signal;
|
|
4095
|
+
}
|
|
4096
|
+
async function unlessInterrupted(work, interrupt, interruptedError) {
|
|
4097
|
+
const fail = () => interruptedError(interruptSignal(interrupt));
|
|
4098
|
+
if (interrupt.aborted) throw fail();
|
|
4099
|
+
let onAbort = () => void 0;
|
|
4100
|
+
const interrupted = new Promise((_, reject2) => {
|
|
4101
|
+
onAbort = () => reject2(fail());
|
|
4102
|
+
interrupt.addEventListener("abort", onAbort, { once: true });
|
|
4103
|
+
});
|
|
4104
|
+
try {
|
|
4105
|
+
return await Promise.race([work, interrupted]);
|
|
4106
|
+
} catch (error) {
|
|
4107
|
+
if (interrupt.aborted) throw fail();
|
|
4108
|
+
throw error;
|
|
4109
|
+
} finally {
|
|
4110
|
+
interrupt.removeEventListener("abort", onAbort);
|
|
4111
|
+
}
|
|
4112
|
+
}
|
|
4113
|
+
var init_interrupt = __esm({
|
|
4114
|
+
"src/commands/shared/interrupt.ts"() {
|
|
4115
|
+
"use strict";
|
|
4116
|
+
init_exitCodes();
|
|
4117
|
+
}
|
|
4118
|
+
});
|
|
4119
|
+
|
|
3889
4120
|
// src/ipc/transport/errors.ts
|
|
3890
4121
|
function formatConnectionError(requestName, socketPath, error) {
|
|
3891
4122
|
const code = error.code;
|
|
@@ -3907,6 +4138,9 @@ function formatTimeoutError(requestName, timeoutMs) {
|
|
|
3907
4138
|
function formatEarlyCloseError(requestName) {
|
|
3908
4139
|
return new IPCEarlyCloseError(requestName);
|
|
3909
4140
|
}
|
|
4141
|
+
function formatCancelledError(requestName) {
|
|
4142
|
+
return new IPCCancelledError(requestName);
|
|
4143
|
+
}
|
|
3910
4144
|
var init_errors4 = __esm({
|
|
3911
4145
|
"src/ipc/transport/errors.ts"() {
|
|
3912
4146
|
"use strict";
|
|
@@ -4075,13 +4309,30 @@ var init_validation2 = __esm({
|
|
|
4075
4309
|
});
|
|
4076
4310
|
|
|
4077
4311
|
// src/ipc/transport/index.ts
|
|
4078
|
-
|
|
4312
|
+
import * as path5 from "path";
|
|
4313
|
+
async function sendRequest(request, requestName, expectedType, timeoutMs = getIPCRequestTimeout(), socketPath = getDaemonSocketPath(), signal) {
|
|
4314
|
+
const untrusted = secureSessionDir(path5.dirname(socketPath));
|
|
4315
|
+
if (untrusted) {
|
|
4316
|
+
const err = untrustedSessionDirError(untrusted);
|
|
4317
|
+
throw new CommandError(
|
|
4318
|
+
err.message,
|
|
4319
|
+
{ suggestion: err.suggestion },
|
|
4320
|
+
EXIT_CODES.SESSION_FILE_ERROR
|
|
4321
|
+
);
|
|
4322
|
+
}
|
|
4079
4323
|
return new Promise((resolve4, reject2) => {
|
|
4324
|
+
if (signal?.aborted) {
|
|
4325
|
+
reject2(formatCancelledError(requestName));
|
|
4326
|
+
return;
|
|
4327
|
+
}
|
|
4080
4328
|
const buffer = new JSONLBuffer();
|
|
4081
4329
|
let resolved = false;
|
|
4330
|
+
let onAbort = () => {
|
|
4331
|
+
};
|
|
4082
4332
|
const resolveOnce = (cleanup2, error, response) => {
|
|
4083
4333
|
if (resolved) return;
|
|
4084
4334
|
resolved = true;
|
|
4335
|
+
signal?.removeEventListener("abort", onAbort);
|
|
4085
4336
|
cleanup2();
|
|
4086
4337
|
if (error) {
|
|
4087
4338
|
reject2(error);
|
|
@@ -4127,6 +4378,8 @@ async function sendRequest(request, requestName, expectedType, timeoutMs = getIP
|
|
|
4127
4378
|
}
|
|
4128
4379
|
}
|
|
4129
4380
|
);
|
|
4381
|
+
onAbort = () => resolveOnce(cleanup, formatCancelledError(requestName));
|
|
4382
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
4130
4383
|
});
|
|
4131
4384
|
}
|
|
4132
4385
|
var log10;
|
|
@@ -4134,8 +4387,11 @@ var init_transport = __esm({
|
|
|
4134
4387
|
"src/ipc/transport/index.ts"() {
|
|
4135
4388
|
"use strict";
|
|
4136
4389
|
init_constants();
|
|
4390
|
+
init_errors3();
|
|
4391
|
+
init_messages();
|
|
4137
4392
|
init_paths();
|
|
4138
4393
|
init_logging();
|
|
4394
|
+
init_exitCodes();
|
|
4139
4395
|
init_errors4();
|
|
4140
4396
|
init_jsonl();
|
|
4141
4397
|
init_socket();
|
|
@@ -4231,7 +4487,7 @@ async function getHARData() {
|
|
|
4231
4487
|
await assertResponsive();
|
|
4232
4488
|
return sendRequest(request, "HAR data", "har_data_response");
|
|
4233
4489
|
}
|
|
4234
|
-
async function startSession(url, options) {
|
|
4490
|
+
async function startSession(url, options, signal) {
|
|
4235
4491
|
const request = withSession({
|
|
4236
4492
|
type: "start_session_request",
|
|
4237
4493
|
url,
|
|
@@ -4253,7 +4509,10 @@ async function startSession(url, options) {
|
|
|
4253
4509
|
return sendRequest(
|
|
4254
4510
|
request,
|
|
4255
4511
|
"start session",
|
|
4256
|
-
"start_session_response"
|
|
4512
|
+
"start_session_response",
|
|
4513
|
+
void 0,
|
|
4514
|
+
void 0,
|
|
4515
|
+
signal
|
|
4257
4516
|
);
|
|
4258
4517
|
}
|
|
4259
4518
|
async function stopSession() {
|
|
@@ -4265,7 +4524,7 @@ async function stopSession() {
|
|
|
4265
4524
|
"stop_session_response"
|
|
4266
4525
|
);
|
|
4267
4526
|
}
|
|
4268
|
-
async function sendCommand(commandName, params, timeoutMs) {
|
|
4527
|
+
async function sendCommand(commandName, params, timeoutMs, signal) {
|
|
4269
4528
|
const request = {
|
|
4270
4529
|
...params,
|
|
4271
4530
|
type: `${commandName}_request`,
|
|
@@ -4276,7 +4535,9 @@ async function sendCommand(commandName, params, timeoutMs) {
|
|
|
4276
4535
|
request,
|
|
4277
4536
|
commandName,
|
|
4278
4537
|
`${commandName}_response`,
|
|
4279
|
-
timeoutMs
|
|
4538
|
+
timeoutMs,
|
|
4539
|
+
void 0,
|
|
4540
|
+
signal
|
|
4280
4541
|
);
|
|
4281
4542
|
}
|
|
4282
4543
|
async function getDetails(type, id) {
|
|
@@ -4311,8 +4572,12 @@ async function callCDP(method, params, options = {}) {
|
|
|
4311
4572
|
function callBdgScript(method, params) {
|
|
4312
4573
|
return callCDP(method, params, { isolated: true });
|
|
4313
4574
|
}
|
|
4314
|
-
async function domEval(script, frame) {
|
|
4315
|
-
return sendCommand("dom_eval", {
|
|
4575
|
+
async function domEval(script, frame, full) {
|
|
4576
|
+
return sendCommand("dom_eval", {
|
|
4577
|
+
script,
|
|
4578
|
+
...frame !== void 0 && { frame },
|
|
4579
|
+
...full && { full }
|
|
4580
|
+
});
|
|
4316
4581
|
}
|
|
4317
4582
|
async function domFrames() {
|
|
4318
4583
|
return sendCommand("dom_frames", {});
|
|
@@ -4356,6 +4621,9 @@ async function cssSearch(params) {
|
|
|
4356
4621
|
async function domInspect(params) {
|
|
4357
4622
|
return sendCommand("dom_inspect", params);
|
|
4358
4623
|
}
|
|
4624
|
+
async function domScreenshot(params, signal) {
|
|
4625
|
+
return sendCommand("dom_screenshot", params, void 0, signal);
|
|
4626
|
+
}
|
|
4359
4627
|
async function domWait(params) {
|
|
4360
4628
|
return sendCommand(
|
|
4361
4629
|
"dom_wait",
|
|
@@ -4376,36 +4644,6 @@ var init_client = __esm({
|
|
|
4376
4644
|
}
|
|
4377
4645
|
});
|
|
4378
4646
|
|
|
4379
|
-
// src/connection/errors.ts
|
|
4380
|
-
var ConnectionError, CDPConnectionError;
|
|
4381
|
-
var init_errors5 = __esm({
|
|
4382
|
-
"src/connection/errors.ts"() {
|
|
4383
|
-
"use strict";
|
|
4384
|
-
init_exitCodes();
|
|
4385
|
-
ConnectionError = class extends Error {
|
|
4386
|
-
issue;
|
|
4387
|
-
constructor(message, options) {
|
|
4388
|
-
super(message);
|
|
4389
|
-
this.name = this.constructor.name;
|
|
4390
|
-
if (options instanceof Error) {
|
|
4391
|
-
this.cause = options;
|
|
4392
|
-
return;
|
|
4393
|
-
}
|
|
4394
|
-
if (options?.cause) {
|
|
4395
|
-
this.cause = options.cause;
|
|
4396
|
-
}
|
|
4397
|
-
if (options?.issue) {
|
|
4398
|
-
this.issue = options.issue;
|
|
4399
|
-
}
|
|
4400
|
-
}
|
|
4401
|
-
};
|
|
4402
|
-
CDPConnectionError = class extends ConnectionError {
|
|
4403
|
-
code = "CDP_CONNECTION_ERROR";
|
|
4404
|
-
exitCode = EXIT_CODES.CDP_CONNECTION_FAILURE;
|
|
4405
|
-
};
|
|
4406
|
-
}
|
|
4407
|
-
});
|
|
4408
|
-
|
|
4409
4647
|
// node_modules/ws/lib/constants.js
|
|
4410
4648
|
var require_constants = __commonJS({
|
|
4411
4649
|
"node_modules/ws/lib/constants.js"(exports, module) {
|
|
@@ -8109,7 +8347,7 @@ function textPreview2(text, length = PREVIEW_LENGTH) {
|
|
|
8109
8347
|
const characters = Array.from(collapsed);
|
|
8110
8348
|
return characters.length > length ? characters.slice(0, length).join("") + "..." : collapsed;
|
|
8111
8349
|
}
|
|
8112
|
-
var CLASS_FRAGMENT, LABEL_CLASSES_JS, PREVIEW_LENGTH, ELEMENT_TEXT_LENGTH, WITHOUT_DECORATIONS_JS, COMPOSED_JS, FLAT_TEXT_JS, ELEMENT_TEXT_JS, MASKED_VALUE, SENSITIVE_FIELD_JS, ELEMENT_STATE_JS, ELEMENT_DESCRIPTION_JS, SIBLING_POSITION_JS, ELEMENT_IDENTITY_JS, ELEMENT_CONTEXT_JS;
|
|
8350
|
+
var CLASS_FRAGMENT, LABEL_CLASSES_JS, PREVIEW_LENGTH, ELEMENT_TEXT_LENGTH, WITHOUT_DECORATIONS_JS, COMPOSED_JS, FLAT_TEXT_JS, ELEMENT_TEXT_JS, MASKED_VALUE, SENSITIVE_NAME_SOURCE, SENSITIVE_FIELD_JS, ELEMENT_STATE_JS, ELEMENT_DESCRIPTION_JS, SIBLING_POSITION_JS, ELEMENT_IDENTITY_JS, ELEMENT_CONTEXT_JS;
|
|
8113
8351
|
var init_elementInfo = __esm({
|
|
8114
8352
|
"src/runtime/dom/elementInfo.ts"() {
|
|
8115
8353
|
"use strict";
|
|
@@ -8200,12 +8438,13 @@ var init_elementInfo = __esm({
|
|
|
8200
8438
|
return withoutDecorations(el, start);
|
|
8201
8439
|
}`;
|
|
8202
8440
|
MASKED_VALUE = "\u2022\u2022\u2022\u2022";
|
|
8441
|
+
SENSITIVE_NAME_SOURCE = "passw|passwd|pwd|passcode|passphrase|(^|[^a-z])(otp|pin|ssn)([^a-z]|$)|cvv|cvc|card[-_]?(num(ber)?|no)([^a-z]|$)";
|
|
8203
8442
|
SENSITIVE_FIELD_JS = `(el) => {
|
|
8204
8443
|
const autocomplete = el.getAttribute('autocomplete') || '';
|
|
8205
8444
|
if (/(^|\\s)(cc-[a-z-]+|one-time-code|current-password|new-password)(\\s|$)/i.test(autocomplete)) return true;
|
|
8206
8445
|
if (el.type === 'password' || /^password$/i.test(el.getAttribute('type') || '')) return true;
|
|
8207
8446
|
const names = [el.getAttribute('name'), el.id, autocomplete].join(' ');
|
|
8208
|
-
if (/
|
|
8447
|
+
if (/${SENSITIVE_NAME_SOURCE}/i.test(names)) return true;
|
|
8209
8448
|
try {
|
|
8210
8449
|
const security = el.ownerDocument.defaultView.getComputedStyle(el).getPropertyValue('-webkit-text-security');
|
|
8211
8450
|
return Boolean(security) && security !== 'none';
|
|
@@ -8990,10 +9229,10 @@ function findHasFilters(text, selector) {
|
|
|
8990
9229
|
}
|
|
8991
9230
|
function parseHasArgument(argument, selector) {
|
|
8992
9231
|
if (!FILTER_NAME_HINT.test(argument)) return null;
|
|
8993
|
-
const selectors = splitSelectorList(argument).map((
|
|
8994
|
-
if (
|
|
8995
|
-
if (SCOPE_PSEUDO.test(
|
|
8996
|
-
const { leading, steps } = parseChain(
|
|
9232
|
+
const selectors = splitSelectorList(argument).map((relative2) => {
|
|
9233
|
+
if (relative2.trim() === "") reject(malformedSelectorError(selector, "empty-has"));
|
|
9234
|
+
if (SCOPE_PSEUDO.test(relative2)) reject(malformedSelectorError(selector, "scope-in-has"));
|
|
9235
|
+
const { leading, steps } = parseChain(relative2, selector);
|
|
8997
9236
|
if (leading && leading !== ">") reject(siblingInHasError(selector, leading));
|
|
8998
9237
|
const [first, ...rest] = steps;
|
|
8999
9238
|
return first ? [{ ...first, combinator: leading || " " }, ...rest] : [];
|
|
@@ -9673,7 +9912,7 @@ async function resolveSelector(selector) {
|
|
|
9673
9912
|
EXIT_CODES.RESOURCE_NOT_FOUND
|
|
9674
9913
|
);
|
|
9675
9914
|
}
|
|
9676
|
-
return
|
|
9915
|
+
return backendNodeId;
|
|
9677
9916
|
}
|
|
9678
9917
|
async function resolveBackendNodeIds(selectors) {
|
|
9679
9918
|
return mapConcurrently(selectors, async (selector) => (await selectAll(selector))[0]);
|
|
@@ -9761,7 +10000,7 @@ var init_query = __esm({
|
|
|
9761
10000
|
|
|
9762
10001
|
// src/commands/shared/outputFile.ts
|
|
9763
10002
|
import * as fs15 from "fs";
|
|
9764
|
-
import * as
|
|
10003
|
+
import * as path6 from "path";
|
|
9765
10004
|
function outputPathError(filePath, error, extension2) {
|
|
9766
10005
|
const code = error.code ?? "";
|
|
9767
10006
|
const problem = PATH_PROBLEMS[code];
|
|
@@ -9790,13 +10029,16 @@ function assertFilePath(filePath, extension2) {
|
|
|
9790
10029
|
);
|
|
9791
10030
|
}
|
|
9792
10031
|
}
|
|
9793
|
-
async function writeOutputFile(filePath, data, extension2) {
|
|
10032
|
+
async function writeOutputFile(filePath, data, extension2, mode) {
|
|
9794
10033
|
assertFilePath(filePath, extension2);
|
|
9795
|
-
const absolutePath =
|
|
10034
|
+
const absolutePath = path6.resolve(filePath);
|
|
9796
10035
|
try {
|
|
9797
|
-
makeDirectory(
|
|
9798
|
-
if (typeof data === "string")
|
|
9799
|
-
|
|
10036
|
+
makeDirectory(path6.dirname(absolutePath));
|
|
10037
|
+
if (typeof data === "string") {
|
|
10038
|
+
await AtomicFileWriter.writeAsync(absolutePath, data, mode === void 0 ? {} : { mode });
|
|
10039
|
+
} else {
|
|
10040
|
+
await AtomicFileWriter.writeBufferAsync(absolutePath, data);
|
|
10041
|
+
}
|
|
9800
10042
|
} catch (error) {
|
|
9801
10043
|
throw outputPathError(filePath, error, extension2);
|
|
9802
10044
|
}
|
|
@@ -9829,575 +10071,45 @@ var init_outputFile = __esm({
|
|
|
9829
10071
|
}
|
|
9830
10072
|
});
|
|
9831
10073
|
|
|
9832
|
-
// src/runtime/page/userAgent.ts
|
|
9833
|
-
var init_userAgent = __esm({
|
|
9834
|
-
"src/runtime/page/userAgent.ts"() {
|
|
9835
|
-
"use strict";
|
|
9836
|
-
init_errors();
|
|
9837
|
-
}
|
|
9838
|
-
});
|
|
9839
|
-
|
|
9840
|
-
// src/runtime/page/emulation.ts
|
|
9841
|
-
function viewportOverride(viewport, deviceScaleFactor = 0) {
|
|
9842
|
-
const mobile = viewport.mobile === true;
|
|
9843
|
-
return {
|
|
9844
|
-
width: viewport.width,
|
|
9845
|
-
height: viewport.height,
|
|
9846
|
-
deviceScaleFactor: deviceScaleFactor || (mobile ? MOBILE_PIXEL_RATIO : 0),
|
|
9847
|
-
mobile
|
|
9848
|
-
};
|
|
9849
|
-
}
|
|
9850
|
-
var log24, MOBILE_PIXEL_RATIO;
|
|
9851
|
-
var init_emulation = __esm({
|
|
9852
|
-
"src/runtime/page/emulation.ts"() {
|
|
9853
|
-
"use strict";
|
|
9854
|
-
init_elementGeometry();
|
|
9855
|
-
init_userAgent();
|
|
9856
|
-
init_logging();
|
|
9857
|
-
init_errors();
|
|
9858
|
-
log24 = createLogger("session");
|
|
9859
|
-
MOBILE_PIXEL_RATIO = 3;
|
|
9860
|
-
}
|
|
9861
|
-
});
|
|
9862
|
-
|
|
9863
10074
|
// src/commands/dom/helpers/screenshot.ts
|
|
9864
|
-
|
|
9865
|
-
const
|
|
9866
|
-
|
|
9867
|
-
|
|
9868
|
-
|
|
9869
|
-
|
|
9870
|
-
|
|
9871
|
-
|
|
9872
|
-
|
|
9873
|
-
|
|
9874
|
-
|
|
9875
|
-
const state = window.__bdg_scrollStability;
|
|
9876
|
-
|
|
9877
|
-
if (window.PerformanceObserver) {
|
|
9878
|
-
const perfObserver = new PerformanceObserver((list) => {
|
|
9879
|
-
for (const entry of list.getEntries()) {
|
|
9880
|
-
if (entry.entryType === 'resource') {
|
|
9881
|
-
state.lastNetworkActivity = Date.now();
|
|
9882
|
-
}
|
|
9883
|
-
}
|
|
9884
|
-
});
|
|
9885
|
-
try {
|
|
9886
|
-
perfObserver.observe({ entryTypes: ['resource'] });
|
|
9887
|
-
state.perfObserver = perfObserver;
|
|
9888
|
-
} catch (e) {}
|
|
9889
|
-
}
|
|
9890
|
-
|
|
9891
|
-
const mutationObserver = new MutationObserver(() => {
|
|
9892
|
-
state.lastDomMutation = Date.now();
|
|
9893
|
-
});
|
|
9894
|
-
mutationObserver.observe(document.body || document.documentElement, {
|
|
9895
|
-
childList: true,
|
|
9896
|
-
subtree: true,
|
|
9897
|
-
attributes: true
|
|
9898
|
-
});
|
|
9899
|
-
state.mutationObserver = mutationObserver;
|
|
9900
|
-
})()
|
|
9901
|
-
`,
|
|
9902
|
-
returnByValue: true
|
|
9903
|
-
});
|
|
9904
|
-
try {
|
|
9905
|
-
while (Date.now() < deadline) {
|
|
9906
|
-
const checkResult = await callBdgScript("Runtime.evaluate", {
|
|
9907
|
-
expression: `
|
|
9908
|
-
(() => {
|
|
9909
|
-
const state = window.__bdg_scrollStability;
|
|
9910
|
-
if (!state) return { networkIdle: 999, domIdle: 999 };
|
|
9911
|
-
return {
|
|
9912
|
-
networkIdle: Date.now() - state.lastNetworkActivity,
|
|
9913
|
-
domIdle: Date.now() - state.lastDomMutation
|
|
9914
|
-
};
|
|
9915
|
-
})()
|
|
9916
|
-
`,
|
|
9917
|
-
returnByValue: true
|
|
9918
|
-
});
|
|
9919
|
-
const value = checkResult.data?.result?.result?.value;
|
|
9920
|
-
const networkIdle = value?.networkIdle ?? 0;
|
|
9921
|
-
const domIdle = value?.domIdle ?? 0;
|
|
9922
|
-
if (networkIdle >= POST_SCROLL_NETWORK_IDLE_MS && domIdle >= POST_SCROLL_DOM_STABLE_MS) {
|
|
9923
|
-
log25.debug(`Post-scroll stable: network ${networkIdle}ms, DOM ${domIdle}ms`);
|
|
9924
|
-
return;
|
|
9925
|
-
}
|
|
9926
|
-
await new Promise((resolve4) => setTimeout(resolve4, STABILITY_CHECK_INTERVAL_MS));
|
|
9927
|
-
}
|
|
9928
|
-
log25.debug("Post-scroll stability timeout, proceeding anyway");
|
|
9929
|
-
} finally {
|
|
9930
|
-
await callBdgScript("Runtime.evaluate", {
|
|
9931
|
-
expression: `
|
|
9932
|
-
(() => {
|
|
9933
|
-
const state = window.__bdg_scrollStability;
|
|
9934
|
-
if (state) {
|
|
9935
|
-
state.perfObserver?.disconnect();
|
|
9936
|
-
state.mutationObserver?.disconnect();
|
|
9937
|
-
delete window.__bdg_scrollStability;
|
|
9938
|
-
}
|
|
9939
|
-
})()
|
|
9940
|
-
`,
|
|
9941
|
-
returnByValue: true
|
|
9942
|
-
});
|
|
9943
|
-
}
|
|
9944
|
-
}
|
|
9945
|
-
async function scrollToElement(selector) {
|
|
9946
|
-
const result = await callBdgScript("Runtime.evaluate", {
|
|
9947
|
-
expression: `
|
|
9948
|
-
(() => {
|
|
9949
|
-
const el = (${DEEP_QUERY_JS})(${selectorArgsJS(selector)})[0];
|
|
9950
|
-
if (!el) return { found: false };
|
|
9951
|
-
const originalX = window.scrollX;
|
|
9952
|
-
const originalY = window.scrollY;
|
|
9953
|
-
el.scrollIntoView({ block: 'center', behavior: 'instant' });
|
|
9954
|
-
return { found: true, originalX, originalY };
|
|
9955
|
-
})()
|
|
9956
|
-
`,
|
|
9957
|
-
returnByValue: true
|
|
9958
|
-
});
|
|
9959
|
-
const value = result.data?.result?.result?.value;
|
|
9960
|
-
if (!value?.found) {
|
|
9961
|
-
const err = noNodesFoundError(selector);
|
|
9962
|
-
throw new CommandError(
|
|
9963
|
-
err.message,
|
|
9964
|
-
{ suggestion: err.suggestion },
|
|
9965
|
-
EXIT_CODES.RESOURCE_NOT_FOUND
|
|
9966
|
-
);
|
|
9967
|
-
}
|
|
9968
|
-
await waitForPostScrollStability();
|
|
9969
|
-
return { x: value.originalX ?? 0, y: value.originalY ?? 0 };
|
|
9970
|
-
}
|
|
9971
|
-
async function restoreScrollPosition(position) {
|
|
9972
|
-
await callBdgScript("Runtime.evaluate", {
|
|
9973
|
-
expression: `window.scrollTo(${position.x}, ${position.y})`,
|
|
9974
|
-
returnByValue: true
|
|
9975
|
-
});
|
|
9976
|
-
}
|
|
9977
|
-
async function windowSize(viewport) {
|
|
9978
|
-
const response = await callBdgScript("Runtime.evaluate", {
|
|
9979
|
-
expression: "[window.innerWidth, window.innerHeight]",
|
|
9980
|
-
returnByValue: true
|
|
9981
|
-
});
|
|
9982
|
-
const value = response.data?.result?.result?.value;
|
|
9983
|
-
const [width, height] = Array.isArray(value) ? value : [];
|
|
9984
|
-
return {
|
|
9985
|
-
width: Math.round(width ?? viewport.clientWidth),
|
|
9986
|
-
height: Math.round(height ?? viewport.clientHeight)
|
|
9987
|
-
};
|
|
9988
|
-
}
|
|
9989
|
-
async function useUnitPixelRatio(devicePixelRatio, viewport) {
|
|
9990
|
-
if (devicePixelRatio === 1) return () => Promise.resolve();
|
|
9991
|
-
const sessionViewport = readSessionMetadata()?.viewport;
|
|
9992
|
-
const size = sessionViewport ?? await windowSize(viewport);
|
|
9993
|
-
await callCDP("Emulation.setDeviceMetricsOverride", viewportOverride(size, 1));
|
|
9994
|
-
return restoreSessionMetrics;
|
|
9995
|
-
}
|
|
9996
|
-
async function restoreSessionMetrics() {
|
|
9997
|
-
const sessionViewport = readSessionMetadata()?.viewport;
|
|
9998
|
-
if (!sessionViewport) {
|
|
9999
|
-
await callCDP("Emulation.clearDeviceMetricsOverride", {});
|
|
10000
|
-
return;
|
|
10001
|
-
}
|
|
10002
|
-
await callCDP("Emulation.setDeviceMetricsOverride", viewportOverride(sessionViewport));
|
|
10003
|
-
if (sessionViewport.mobile) {
|
|
10004
|
-
await callCDP("Emulation.setTouchEmulationEnabled", { enabled: true, maxTouchPoints: 5 });
|
|
10005
|
-
}
|
|
10075
|
+
function screenshotInterrupted(signal) {
|
|
10076
|
+
const err = screenshotInterruptedError(signal);
|
|
10077
|
+
return new CommandError(err.message, { suggestion: err.suggestion }, interruptExitCode(signal));
|
|
10078
|
+
}
|
|
10079
|
+
async function requestCapture(request, interrupt) {
|
|
10080
|
+
if (!interrupt) return domScreenshot(request);
|
|
10081
|
+
const capture = domScreenshot(request, interrupt);
|
|
10082
|
+
const response = await unlessInterrupted(capture, interrupt, screenshotInterrupted);
|
|
10083
|
+
if (interrupt.aborted) throw screenshotInterrupted(interruptSignal(interrupt));
|
|
10084
|
+
return response;
|
|
10006
10085
|
}
|
|
10007
|
-
async function
|
|
10008
|
-
const response = await
|
|
10009
|
-
|
|
10010
|
-
|
|
10011
|
-
|
|
10012
|
-
|
|
10013
|
-
err.message,
|
|
10014
|
-
{ suggestion: err.suggestion },
|
|
10015
|
-
EXIT_CODES.INVALID_ARGUMENTS
|
|
10086
|
+
async function captureScreenshot(outputPath, request, interrupt) {
|
|
10087
|
+
const response = await requestCapture(request, interrupt);
|
|
10088
|
+
if (response.status === "error" || !response.data) {
|
|
10089
|
+
const fallback = operationFailedError(
|
|
10090
|
+
"take the screenshot",
|
|
10091
|
+
`Check the session: ${sessionCommand("bdg status")}`
|
|
10016
10092
|
);
|
|
10017
|
-
|
|
10018
|
-
const border = boxModel.model.border;
|
|
10019
|
-
const x = border[0] ?? 0;
|
|
10020
|
-
const y = border[1] ?? 0;
|
|
10021
|
-
const width = (border[2] ?? 0) - x;
|
|
10022
|
-
const height = (border[5] ?? 0) - y;
|
|
10023
|
-
if (width <= 0 || height <= 0) {
|
|
10024
|
-
const err = elementZeroDimensionsError();
|
|
10093
|
+
const suggestion = response.error === void 0 ? fallback.suggestion : response.suggestion;
|
|
10025
10094
|
throw new CommandError(
|
|
10026
|
-
|
|
10027
|
-
{ suggestion:
|
|
10028
|
-
EXIT_CODES.
|
|
10095
|
+
response.error ?? fallback.message,
|
|
10096
|
+
suggestion ? { suggestion } : {},
|
|
10097
|
+
response.exitCode ?? EXIT_CODES.CDP_CONNECTION_FAILURE
|
|
10029
10098
|
);
|
|
10030
10099
|
}
|
|
10031
|
-
|
|
10032
|
-
}
|
|
10033
|
-
async function visibleAreaOrigin() {
|
|
10034
|
-
const response = await callCDP("Page.getLayoutMetrics", {});
|
|
10035
|
-
const metrics = response.data?.result;
|
|
10036
|
-
const viewport = metrics?.cssVisualViewport;
|
|
10037
|
-
return { x: viewport?.pageX ?? 0, y: viewport?.pageY ?? 0 };
|
|
10100
|
+
const path8 = await writeOutputFile(outputPath, Buffer.from(response.data.image, "base64"));
|
|
10101
|
+
return { path: path8, ...response.data.screenshot };
|
|
10038
10102
|
}
|
|
10039
|
-
async function capturePageScreenshot(outputPath, options = {}) {
|
|
10040
|
-
const format = options.format ?? "png";
|
|
10041
|
-
const quality = format === "jpeg" ? options.quality ?? 90 : void 0;
|
|
10042
|
-
const requestedFullPage = options.fullPage ?? true;
|
|
10043
|
-
const noResize = options.noResize ?? false;
|
|
10044
|
-
let originalScrollPosition;
|
|
10045
|
-
if (options.scroll) {
|
|
10046
|
-
originalScrollPosition = await scrollToElement(options.scroll);
|
|
10047
|
-
}
|
|
10048
|
-
const dprResponse = await callBdgScript("Runtime.evaluate", {
|
|
10049
|
-
expression: "window.devicePixelRatio",
|
|
10050
|
-
returnByValue: true
|
|
10051
|
-
});
|
|
10052
|
-
const devicePixelRatio = dprResponse.data?.result?.result?.value ?? 1;
|
|
10053
|
-
const metricsResponse = await callCDP("Page.getLayoutMetrics", {});
|
|
10054
|
-
const metricsResult = metricsResponse.data?.result;
|
|
10055
|
-
const contentSize = metricsResult?.contentSize ?? { width: 0, height: 0 };
|
|
10056
|
-
const viewport = metricsResult?.visualViewport ?? { clientWidth: 0, clientHeight: 0 };
|
|
10057
|
-
const pageIsTooTall = !noResize && requestedFullPage && isTallPage(contentSize.width, contentSize.height);
|
|
10058
|
-
const useScroll = options.scroll !== void 0;
|
|
10059
|
-
const effectiveFullPage = useScroll ? false : pageIsTooTall ? false : requestedFullPage;
|
|
10060
|
-
const captureWidth = effectiveFullPage ? contentSize.width : viewport.clientWidth;
|
|
10061
|
-
const captureHeight = effectiveFullPage ? contentSize.height : viewport.clientHeight;
|
|
10062
|
-
const resized = shouldResize(captureWidth, captureHeight, noResize);
|
|
10063
|
-
const scale = resized ? calculateResizeScale(captureWidth, captureHeight) : 1;
|
|
10064
|
-
const finalWidth = Math.round(captureWidth * scale);
|
|
10065
|
-
const finalHeight = Math.round(captureHeight * scale);
|
|
10066
|
-
const restoreMetrics = await useUnitPixelRatio(devicePixelRatio, viewport);
|
|
10067
|
-
if (devicePixelRatio !== 1) {
|
|
10068
|
-
if (options.scroll) {
|
|
10069
|
-
await callBdgScript("Runtime.evaluate", {
|
|
10070
|
-
expression: `(${DEEP_QUERY_JS})(${selectorArgsJS(options.scroll)})[0]?.scrollIntoView({ block: 'center', behavior: 'instant' })`,
|
|
10071
|
-
returnByValue: true
|
|
10072
|
-
});
|
|
10073
|
-
}
|
|
10074
|
-
}
|
|
10075
|
-
const clipOrigin = effectiveFullPage ? { x: 0, y: 0 } : await visibleAreaOrigin();
|
|
10076
|
-
let screenshotResult;
|
|
10077
|
-
try {
|
|
10078
|
-
const screenshotResponse = await callCDP("Page.captureScreenshot", {
|
|
10079
|
-
format,
|
|
10080
|
-
...quality !== void 0 && { quality },
|
|
10081
|
-
captureBeyondViewport: effectiveFullPage,
|
|
10082
|
-
clip: {
|
|
10083
|
-
x: clipOrigin.x,
|
|
10084
|
-
y: clipOrigin.y,
|
|
10085
|
-
width: captureWidth,
|
|
10086
|
-
height: captureHeight,
|
|
10087
|
-
scale
|
|
10088
|
-
}
|
|
10089
|
-
});
|
|
10090
|
-
screenshotResult = screenshotResponse.data?.result;
|
|
10091
|
-
} finally {
|
|
10092
|
-
await restoreMetrics();
|
|
10093
|
-
}
|
|
10094
|
-
if (!screenshotResult?.data) {
|
|
10095
|
-
throw new CDPConnectionError("No screenshot data returned", new Error("Empty response"));
|
|
10096
|
-
}
|
|
10097
|
-
const buffer = Buffer.from(screenshotResult.data, "base64");
|
|
10098
|
-
const absolutePath = await writeOutputFile(outputPath, buffer);
|
|
10099
|
-
const result = {
|
|
10100
|
-
path: absolutePath,
|
|
10101
|
-
format,
|
|
10102
|
-
width: finalWidth,
|
|
10103
|
-
height: finalHeight,
|
|
10104
|
-
size: buffer.length,
|
|
10105
|
-
fullPage: effectiveFullPage,
|
|
10106
|
-
captureMode: effectiveFullPage ? "full_page" : "viewport",
|
|
10107
|
-
finalTokens: calculateImageTokens(finalWidth, finalHeight)
|
|
10108
|
-
};
|
|
10109
|
-
if (quality !== void 0) {
|
|
10110
|
-
result.quality = quality;
|
|
10111
|
-
}
|
|
10112
|
-
if (!effectiveFullPage) {
|
|
10113
|
-
result.viewport = {
|
|
10114
|
-
width: viewport.clientWidth,
|
|
10115
|
-
height: viewport.clientHeight
|
|
10116
|
-
};
|
|
10117
|
-
}
|
|
10118
|
-
if (resized) {
|
|
10119
|
-
result.resized = true;
|
|
10120
|
-
result.originalWidth = captureWidth;
|
|
10121
|
-
result.originalHeight = captureHeight;
|
|
10122
|
-
result.originalTokens = calculateImageTokens(captureWidth, captureHeight);
|
|
10123
|
-
}
|
|
10124
|
-
if (pageIsTooTall && !useScroll) {
|
|
10125
|
-
const aspectRatio = Math.round(contentSize.height / contentSize.width * 10) / 10;
|
|
10126
|
-
result.fullPageSkipped = {
|
|
10127
|
-
reason: "page_too_tall",
|
|
10128
|
-
originalHeight: contentSize.height,
|
|
10129
|
-
aspectRatio
|
|
10130
|
-
};
|
|
10131
|
-
result.warning = `Full page capture skipped: page too tall (${aspectRatio}:1 aspect ratio). Only viewport captured.`;
|
|
10132
|
-
}
|
|
10133
|
-
if (useScroll && options.scroll) {
|
|
10134
|
-
result.scrolledTo = options.scroll;
|
|
10135
|
-
}
|
|
10136
|
-
if (originalScrollPosition) {
|
|
10137
|
-
await restoreScrollPosition(originalScrollPosition);
|
|
10138
|
-
}
|
|
10139
|
-
return result;
|
|
10140
|
-
}
|
|
10141
|
-
async function visibleViewport() {
|
|
10142
|
-
const metrics = (await callCDP("Page.getLayoutMetrics", {})).data?.result;
|
|
10143
|
-
const view = metrics?.cssVisualViewport ?? metrics?.visualViewport;
|
|
10144
|
-
return { width: view?.clientWidth ?? 0, height: view?.clientHeight ?? 0 };
|
|
10145
|
-
}
|
|
10146
|
-
function insideView(area, view) {
|
|
10147
|
-
return area.x >= 0 && area.y >= 0 && area.x + area.width <= view.width && area.y + area.height <= view.height;
|
|
10148
|
-
}
|
|
10149
|
-
async function measureInView(ref, padding) {
|
|
10150
|
-
const view = await visibleViewport();
|
|
10151
|
-
let box = await getElementBounds(ref);
|
|
10152
|
-
let bounds = await captureArea(ref, box, padding);
|
|
10153
|
-
const fits = bounds.width <= view.width && bounds.height <= view.height;
|
|
10154
|
-
if (!fits) {
|
|
10155
|
-
await keepLayoutWithoutScrollbars(view);
|
|
10156
|
-
box = await getElementBounds(ref);
|
|
10157
|
-
return { box, bounds: await captureArea(ref, box, padding), inView: false };
|
|
10158
|
-
}
|
|
10159
|
-
if (insideView(bounds, view)) return { box, bounds, inView: true };
|
|
10160
|
-
const scrolledFrom = await scrollPosition();
|
|
10161
|
-
const dx = bounds.x + bounds.width / 2 - view.width / 2;
|
|
10162
|
-
const dy = bounds.y + bounds.height / 2 - view.height / 2;
|
|
10163
|
-
await callBdgScript("Runtime.evaluate", { expression: `window.scrollBy(${dx}, ${dy})` });
|
|
10164
|
-
try {
|
|
10165
|
-
box = await getElementBounds(ref);
|
|
10166
|
-
bounds = await captureArea(ref, box, padding);
|
|
10167
|
-
} catch (error) {
|
|
10168
|
-
await restoreScrollPosition(scrolledFrom);
|
|
10169
|
-
throw error;
|
|
10170
|
-
}
|
|
10171
|
-
return { box, bounds, inView: insideView(bounds, view), scrolledFrom };
|
|
10172
|
-
}
|
|
10173
|
-
async function keepLayoutWithoutScrollbars(view) {
|
|
10174
|
-
const phone = readSessionMetadata()?.viewport?.mobile;
|
|
10175
|
-
await callCDP("Emulation.setScrollbarsHidden", { hidden: true });
|
|
10176
|
-
await callCDP(
|
|
10177
|
-
"Emulation.setDeviceMetricsOverride",
|
|
10178
|
-
viewportOverride({ ...view, ...phone && { mobile: true } }, 1)
|
|
10179
|
-
);
|
|
10180
|
-
}
|
|
10181
|
-
async function restoreViewport() {
|
|
10182
|
-
await callCDP("Emulation.setScrollbarsHidden", { hidden: false });
|
|
10183
|
-
await restoreSessionMetrics();
|
|
10184
|
-
}
|
|
10185
|
-
async function scrollPosition() {
|
|
10186
|
-
const response = await callBdgScript("Runtime.evaluate", {
|
|
10187
|
-
expression: "[window.scrollX, window.scrollY]",
|
|
10188
|
-
returnByValue: true
|
|
10189
|
-
});
|
|
10190
|
-
const value = response.data?.result?.result?.value;
|
|
10191
|
-
const [x, y] = Array.isArray(value) ? value : [];
|
|
10192
|
-
return { x: x ?? 0, y: y ?? 0 };
|
|
10193
|
-
}
|
|
10194
|
-
async function captureArea(ref, bounds, padding) {
|
|
10195
|
-
const area = await paintedArea(ref, bounds);
|
|
10196
|
-
return padding > 0 ? {
|
|
10197
|
-
x: area.x - padding,
|
|
10198
|
-
y: area.y - padding,
|
|
10199
|
-
width: area.width + 2 * padding,
|
|
10200
|
-
height: area.height + 2 * padding
|
|
10201
|
-
} : area;
|
|
10202
|
-
}
|
|
10203
|
-
async function paintedArea(ref, bounds) {
|
|
10204
|
-
const objectGroup = `bdg-shot-${process.pid}`;
|
|
10205
|
-
try {
|
|
10206
|
-
const resolved = await callBdgScript("DOM.resolveNode", { ...ref, objectGroup });
|
|
10207
|
-
const objectId = resolved.data?.result?.object.objectId;
|
|
10208
|
-
if (!objectId) return bounds;
|
|
10209
|
-
const response = await callCDP("Runtime.callFunctionOn", {
|
|
10210
|
-
objectId,
|
|
10211
|
-
functionDeclaration: CONTENT_OVERFLOW_JS,
|
|
10212
|
-
returnByValue: true
|
|
10213
|
-
});
|
|
10214
|
-
const overflow = response.data?.result?.result?.value;
|
|
10215
|
-
if (!overflow) return bounds;
|
|
10216
|
-
const [left, top, right, bottom] = ["left", "top", "right", "bottom"].map(
|
|
10217
|
-
(side) => Math.max(0, overflow[side] ?? 0)
|
|
10218
|
-
);
|
|
10219
|
-
if (Math.max(left, top, right, bottom) <= OVERFLOW_SLACK) return bounds;
|
|
10220
|
-
return {
|
|
10221
|
-
x: bounds.x - left,
|
|
10222
|
-
y: bounds.y - top,
|
|
10223
|
-
width: bounds.width + left + right,
|
|
10224
|
-
height: bounds.height + top + bottom
|
|
10225
|
-
};
|
|
10226
|
-
} catch (error) {
|
|
10227
|
-
log25.debug(`Could not measure overflowing content: ${String(error)}`);
|
|
10228
|
-
return bounds;
|
|
10229
|
-
} finally {
|
|
10230
|
-
await callCDP("Runtime.releaseObjectGroup", { objectGroup }).catch(() => void 0);
|
|
10231
|
-
}
|
|
10232
|
-
}
|
|
10233
|
-
async function captureElementScreenshot(outputPath, ref, options = {}) {
|
|
10234
|
-
const format = options.format ?? "png";
|
|
10235
|
-
const quality = format === "jpeg" ? options.quality ?? 90 : void 0;
|
|
10236
|
-
const noResize = options.noResize ?? false;
|
|
10237
|
-
const dprResponse = await callBdgScript("Runtime.evaluate", {
|
|
10238
|
-
expression: "window.devicePixelRatio",
|
|
10239
|
-
returnByValue: true
|
|
10240
|
-
});
|
|
10241
|
-
const devicePixelRatio = dprResponse.data?.result?.result?.value ?? 1;
|
|
10242
|
-
const before = (await callCDP("Page.getLayoutMetrics", {})).data?.result;
|
|
10243
|
-
const restoreMetrics = await useUnitPixelRatio(
|
|
10244
|
-
devicePixelRatio,
|
|
10245
|
-
before?.visualViewport ?? { clientWidth: 800, clientHeight: 600 }
|
|
10246
|
-
);
|
|
10247
|
-
const restore = async (wide, scrolledFrom2) => {
|
|
10248
|
-
await (wide ? restoreViewport() : restoreMetrics());
|
|
10249
|
-
if (scrolledFrom2) await restoreScrollPosition(scrolledFrom2);
|
|
10250
|
-
};
|
|
10251
|
-
let measured;
|
|
10252
|
-
try {
|
|
10253
|
-
measured = await measureInView(ref, options.padding ?? 0);
|
|
10254
|
-
} catch (error) {
|
|
10255
|
-
await restoreViewport();
|
|
10256
|
-
throw error;
|
|
10257
|
-
}
|
|
10258
|
-
const { box, bounds, inView, scrolledFrom } = measured;
|
|
10259
|
-
const originalWidth = bounds.width;
|
|
10260
|
-
const originalHeight = bounds.height;
|
|
10261
|
-
const resized = shouldResize(originalWidth, originalHeight, noResize);
|
|
10262
|
-
const scale = resized ? calculateResizeScale(originalWidth, originalHeight) : 1;
|
|
10263
|
-
const finalWidth = Math.round(originalWidth * scale);
|
|
10264
|
-
const finalHeight = Math.round(originalHeight * scale);
|
|
10265
|
-
const metricsResponse = await callCDP("Page.getLayoutMetrics", {});
|
|
10266
|
-
const metricsResult = metricsResponse.data?.result;
|
|
10267
|
-
const scroll = metricsResult?.cssLayoutViewport ?? { pageX: 0, pageY: 0 };
|
|
10268
|
-
const onPage = (area) => ({
|
|
10269
|
-
...area,
|
|
10270
|
-
x: area.x + scroll.pageX,
|
|
10271
|
-
y: area.y + scroll.pageY
|
|
10272
|
-
});
|
|
10273
|
-
const clip = onPage(bounds);
|
|
10274
|
-
let screenshotResult;
|
|
10275
|
-
try {
|
|
10276
|
-
const screenshotResponse = await callCDP("Page.captureScreenshot", {
|
|
10277
|
-
format,
|
|
10278
|
-
...quality !== void 0 && { quality },
|
|
10279
|
-
clip: { ...clip, scale },
|
|
10280
|
-
captureBeyondViewport: !inView
|
|
10281
|
-
});
|
|
10282
|
-
screenshotResult = screenshotResponse.data?.result;
|
|
10283
|
-
} finally {
|
|
10284
|
-
await restore(!inView, scrolledFrom);
|
|
10285
|
-
}
|
|
10286
|
-
if (!screenshotResult?.data) {
|
|
10287
|
-
throw new CDPConnectionError("No screenshot data returned", new Error("Empty response"));
|
|
10288
|
-
}
|
|
10289
|
-
const buffer = Buffer.from(screenshotResult.data, "base64");
|
|
10290
|
-
const absolutePath = await writeOutputFile(outputPath, buffer);
|
|
10291
|
-
const result = {
|
|
10292
|
-
path: absolutePath,
|
|
10293
|
-
format,
|
|
10294
|
-
width: finalWidth,
|
|
10295
|
-
height: finalHeight,
|
|
10296
|
-
size: buffer.length,
|
|
10297
|
-
fullPage: false,
|
|
10298
|
-
finalTokens: calculateImageTokens(finalWidth, finalHeight),
|
|
10299
|
-
element: {
|
|
10300
|
-
bounds: roundBounds(onPage(box)),
|
|
10301
|
-
...bounds !== box && { captured: roundBounds(clip) },
|
|
10302
|
-
...options.padding && { padding: options.padding }
|
|
10303
|
-
}
|
|
10304
|
-
};
|
|
10305
|
-
if (quality !== void 0) {
|
|
10306
|
-
result.quality = quality;
|
|
10307
|
-
}
|
|
10308
|
-
if (resized) {
|
|
10309
|
-
result.resized = true;
|
|
10310
|
-
result.originalWidth = originalWidth;
|
|
10311
|
-
result.originalHeight = originalHeight;
|
|
10312
|
-
result.originalTokens = calculateImageTokens(originalWidth, originalHeight);
|
|
10313
|
-
}
|
|
10314
|
-
return result;
|
|
10315
|
-
}
|
|
10316
|
-
function roundBounds(bounds) {
|
|
10317
|
-
return {
|
|
10318
|
-
x: Math.round(bounds.x),
|
|
10319
|
-
y: Math.round(bounds.y),
|
|
10320
|
-
width: Math.round(bounds.width),
|
|
10321
|
-
height: Math.round(bounds.height)
|
|
10322
|
-
};
|
|
10323
|
-
}
|
|
10324
|
-
var log25, POST_SCROLL_NETWORK_IDLE_MS, POST_SCROLL_DOM_STABLE_MS, POST_SCROLL_MAX_WAIT_MS, STABILITY_CHECK_INTERVAL_MS, OVERFLOW_SCAN_LIMIT, CONTENT_OVERFLOW_JS, OVERFLOW_SLACK;
|
|
10325
10103
|
var init_screenshot = __esm({
|
|
10326
10104
|
"src/commands/dom/helpers/screenshot.ts"() {
|
|
10327
10105
|
"use strict";
|
|
10328
|
-
|
|
10106
|
+
init_interrupt();
|
|
10329
10107
|
init_outputFile();
|
|
10330
|
-
init_errors5();
|
|
10331
10108
|
init_errors3();
|
|
10332
10109
|
init_messages();
|
|
10333
10110
|
init_client();
|
|
10334
|
-
|
|
10335
|
-
init_emulation();
|
|
10336
|
-
init_metadata();
|
|
10337
|
-
init_logging();
|
|
10111
|
+
init_sessionCommand();
|
|
10338
10112
|
init_exitCodes();
|
|
10339
|
-
log25 = createLogger("dom");
|
|
10340
|
-
POST_SCROLL_NETWORK_IDLE_MS = 150;
|
|
10341
|
-
POST_SCROLL_DOM_STABLE_MS = 200;
|
|
10342
|
-
POST_SCROLL_MAX_WAIT_MS = 2e3;
|
|
10343
|
-
STABILITY_CHECK_INTERVAL_MS = 50;
|
|
10344
|
-
OVERFLOW_SCAN_LIMIT = 2e3;
|
|
10345
|
-
CONTENT_OVERFLOW_JS = `function () {
|
|
10346
|
-
const view = this.ownerDocument.defaultView;
|
|
10347
|
-
const scroller = this.ownerDocument.scrollingElement || this.ownerDocument.documentElement;
|
|
10348
|
-
const own = this.getBoundingClientRect();
|
|
10349
|
-
const reach = { left: own.left, top: own.top, right: own.right, bottom: own.bottom };
|
|
10350
|
-
const page = { left: -view.scrollX, top: -view.scrollY, right: scroller.scrollWidth - view.scrollX, bottom: scroller.scrollHeight - view.scrollY };
|
|
10351
|
-
const clips = (style) => style.overflowX !== 'visible' || style.overflowY !== 'visible';
|
|
10352
|
-
let budget = ${OVERFLOW_SCAN_LIMIT};
|
|
10353
|
-
const walk = (el) => {
|
|
10354
|
-
for (const child of el.children) {
|
|
10355
|
-
if (--budget < 0) return;
|
|
10356
|
-
const style = view.getComputedStyle(child);
|
|
10357
|
-
if (style.display === 'none' || style.position === 'fixed') continue;
|
|
10358
|
-
const r = child.getBoundingClientRect();
|
|
10359
|
-
if (r.width > 0 && r.height > 0 && style.visibility === 'visible') {
|
|
10360
|
-
reach.left = Math.min(reach.left, Math.max(r.left, page.left));
|
|
10361
|
-
reach.top = Math.min(reach.top, Math.max(r.top, page.top));
|
|
10362
|
-
reach.right = Math.max(reach.right, Math.min(r.right, page.right));
|
|
10363
|
-
reach.bottom = Math.max(reach.bottom, Math.min(r.bottom, page.bottom));
|
|
10364
|
-
}
|
|
10365
|
-
if (!clips(style)) walk(child);
|
|
10366
|
-
}
|
|
10367
|
-
};
|
|
10368
|
-
const ownStyle = view.getComputedStyle(this);
|
|
10369
|
-
if (!clips(ownStyle)) {
|
|
10370
|
-
walk(this);
|
|
10371
|
-
const scaleX = this.offsetWidth ? own.width / this.offsetWidth : 1;
|
|
10372
|
-
const scaleY = this.offsetHeight ? own.height / this.offsetHeight : 1;
|
|
10373
|
-
const wider = Math.max(0, this.scrollWidth - this.clientWidth) * scaleX;
|
|
10374
|
-
const taller = Math.max(0, this.scrollHeight - this.clientHeight) * scaleY;
|
|
10375
|
-
if (ownStyle.direction === 'rtl') reach.left = Math.min(reach.left, own.left - wider);
|
|
10376
|
-
else reach.right = Math.max(reach.right, own.right + wider);
|
|
10377
|
-
reach.bottom = Math.max(reach.bottom, own.bottom + taller);
|
|
10378
|
-
}
|
|
10379
|
-
const ink = { left: 0, top: 0, right: 0, bottom: 0 };
|
|
10380
|
-
const grow = (side, amount) => { ink[side] = Math.max(ink[side], amount); };
|
|
10381
|
-
for (const layer of ownStyle.boxShadow === 'none' ? [] : ownStyle.boxShadow.split(/,(?![^(]*\\))/)) {
|
|
10382
|
-
if (/\\binset\\b/.test(layer)) continue;
|
|
10383
|
-
const [x = 0, y = 0, blur = 0, spread = 0] = (layer.replace(/(rgba?|hsla?|color|oklch|lab|lch)\\([^)]*\\)/g, '').match(/-?[\\d.]+px/g) || []).map(parseFloat);
|
|
10384
|
-
grow('left', blur + spread - x);
|
|
10385
|
-
grow('right', blur + spread + x);
|
|
10386
|
-
grow('top', blur + spread - y);
|
|
10387
|
-
grow('bottom', blur + spread + y);
|
|
10388
|
-
}
|
|
10389
|
-
if (ownStyle.outlineStyle !== 'none') {
|
|
10390
|
-
const outline = parseFloat(ownStyle.outlineWidth) + parseFloat(ownStyle.outlineOffset);
|
|
10391
|
-
['left', 'top', 'right', 'bottom'].forEach((side) => grow(side, outline));
|
|
10392
|
-
}
|
|
10393
|
-
return {
|
|
10394
|
-
left: Math.max(own.left - reach.left, ink.left),
|
|
10395
|
-
top: Math.max(own.top - reach.top, ink.top),
|
|
10396
|
-
right: Math.max(reach.right - own.right, ink.right),
|
|
10397
|
-
bottom: Math.max(reach.bottom - own.bottom, ink.bottom)
|
|
10398
|
-
};
|
|
10399
|
-
}`;
|
|
10400
|
-
OVERFLOW_SLACK = 1;
|
|
10401
10113
|
}
|
|
10402
10114
|
});
|
|
10403
10115
|
|
|
@@ -10405,17 +10117,15 @@ var init_screenshot = __esm({
|
|
|
10405
10117
|
var helpers_exports = {};
|
|
10406
10118
|
__export(helpers_exports, {
|
|
10407
10119
|
assertNodeAttached: () => assertNodeAttached,
|
|
10408
|
-
|
|
10409
|
-
capturePageScreenshot: () => capturePageScreenshot,
|
|
10410
|
-
documentReadyState: () => documentReadyState,
|
|
10120
|
+
captureScreenshot: () => captureScreenshot,
|
|
10411
10121
|
getDOMElements: () => getDOMElements,
|
|
10412
10122
|
getDomContext: () => getDomContext,
|
|
10413
|
-
getElementBounds: () => getElementBounds,
|
|
10414
10123
|
noMatchesError: () => noMatchesError,
|
|
10415
10124
|
pageDocumentId: () => pageDocumentId,
|
|
10416
10125
|
queryDOMElements: () => queryDOMElements,
|
|
10417
10126
|
resolveBackendNodeIds: () => resolveBackendNodeIds,
|
|
10418
10127
|
resolveSelector: () => resolveSelector,
|
|
10128
|
+
screenshotInterrupted: () => screenshotInterrupted,
|
|
10419
10129
|
selectMatch: () => selectMatch
|
|
10420
10130
|
});
|
|
10421
10131
|
var init_helpers = __esm({
|
|
@@ -13537,9 +13247,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
13537
13247
|
* @param {string} [path]
|
|
13538
13248
|
* @return {(string|null|Command)}
|
|
13539
13249
|
*/
|
|
13540
|
-
executableDir(
|
|
13541
|
-
if (
|
|
13542
|
-
this._executableDir =
|
|
13250
|
+
executableDir(path8) {
|
|
13251
|
+
if (path8 === void 0) return this._executableDir;
|
|
13252
|
+
this._executableDir = path8;
|
|
13543
13253
|
return this;
|
|
13544
13254
|
}
|
|
13545
13255
|
/**
|
|
@@ -13797,11 +13507,15 @@ var program = new Command();
|
|
|
13797
13507
|
// src/commands/helpJson.ts
|
|
13798
13508
|
init_schema();
|
|
13799
13509
|
|
|
13510
|
+
// src/runtime/page/screenshotResize.ts
|
|
13511
|
+
var MAX_EDGE_PX = 1568;
|
|
13512
|
+
var PIXELS_PER_TOKEN = 750;
|
|
13513
|
+
var TALL_PAGE_THRESHOLD = 3;
|
|
13514
|
+
|
|
13800
13515
|
// src/commands/optionBehaviors.ts
|
|
13801
|
-
init_screenshotResize();
|
|
13802
13516
|
var TRIGGERED_REQUESTS_BEHAVIOR = "Requests (and WebSocket connections) that start after the action begins are returned as triggeredRequests (method, url, status, durationMs; pending when still running at return, loading when the response arrived but its body is still streaming; with resourceType; human output lists documents, XHR/fetch and WebSockets first (up to 10) and counts static assets on one line; absent when network telemetry is off). Attribution is by time: requests a page timer or poller starts meanwhile are listed too, whether or not the action caused them";
|
|
13803
|
-
var ACTION_EFFECTS_BEHAVIOR = 'The result also says what changed on the page: a navigation (Page: navigated to <url> (status), or URL changed to <url> (same document); JSON navigation { url, sameDocument, status }), and messages that appeared or changed in alert/status/aria-live elements or flash/error/toast-like classes (New text: "\u2026" (element); JSON messages [{ text, element }], at most 3, with "(+N more)" and moreMessages for the rest; after a navigation every message on the new page counts; texts of only digits and time units, such as clocks and counters, are left out, but other text that changes on its own, such as a rotating banner, can show up). Both are absent when nothing changed. Cost: one page script sent before the action without waiting for it and one read after it, a few ms; when the page does not answer (a pending navigation) bdg waits at most 200 ms for the snapshot and 250 ms per read, and the navigation is still reported from CDP events';
|
|
13804
|
-
var STILL_CHANGING_BEHAVIOR = 'When the page was still changing as the action returned, the status line says (page still changing), a note below it says what was pending and suggests bdg dom wait <selector>, and JSON has settled: false with pending { requests (document, fetch/XHR and script requests still running), navigation (a new page still loading), loading (a loading indicator that appeared, e.g. "div#loading"), domChanging (DOM changes
|
|
13517
|
+
var ACTION_EFFECTS_BEHAVIOR = 'The result also says what changed on the page: a navigation (Page: navigated to <url> (status), or URL changed to <url> (same document); JSON navigation { url, sameDocument, status }), and messages that appeared or changed in alert/status/aria-live elements or flash/error/toast-like classes (New text: "\u2026" (element); JSON messages [{ text, element }], at most 3, with "(+N more)" and moreMessages for the rest; after a navigation every message on the new page counts; texts of only digits and time units, such as clocks and counters, are left out, but other text that changes on its own, such as a rotating banner, can show up). Both are absent when nothing changed. Downloads that began meanwhile are listed (Download: <name> \u2192 <path> (<state>, <size>); JSON downloads [{ url, suggestedFilename, path, state: inProgress|completed|canceled, bytes }]); a Chrome bdg launched saves them into <session dir>/downloads (never ~/Downloads), named as suggested with (1), (2)\u2026 when taken, and one still running when the action returns is inProgress and appears at its path once complete; downloads of tabs the page opens (target=_blank, window.open) count too; when that directory cannot be created they are refused (canceled, with reason) instead of going to ~/Downloads, and if Chrome refuses the download behavior bdg status warns that downloads are not redirected; ones that begin after the action returned are listed by bdg status and bdg peek (Downloads: N (last: \u2026)); files stay after stop and cleanup; with --chrome-ws-url (the exception: not a browser bdg owns) downloads go to the download folder of that Chrome (usually ~/Downloads), only downloads of the session page are tracked (not those of tabs or popups it opens), and bdg sets behavior default with events, which replaces one another CDP client set and which Chrome drops when the session connection closes. Cost: one page script sent before the action without waiting for it and one read after it, a few ms; when the page does not answer (a pending navigation) bdg waits at most 200 ms for the snapshot and 250 ms per read, and the navigation is still reported from CDP events';
|
|
13518
|
+
var STILL_CHANGING_BEHAVIOR = 'When the page was still changing as the action returned, the status line says (page still changing), a note below it says what was pending and suggests bdg dom wait <selector>, and JSON has settled: false with pending { requests (document, fetch/XHR and script requests still running), navigation (a new page still loading), loading (a loading indicator that appeared, e.g. "div#loading"), domChanging (DOM changes came in bursts and, at a second look 250 ms later, again, with no quiet gap over 150 ms; time the page could not run its tasks, a long task or timers a starved renderer runs late, is not quiet, and a single burst more than 150 ms old with at most 75 ms of quiet time since also gets the second look (only that 250 ms; domChanging still needs a fresh change at it); a single render, ticking text and style animations do not count, and changes more than about 150 ms apart while the page could run look settled), busy (the page did not answer within 250 ms and ran a long task since the action began, or did not answer a check within 250 ms more: a long script; a page that answered late without a long task, a starved renderer, gets its answer waited for instead; a renderer descheduled in the middle of a task can still record a long task and is then busy; the check runs at most once per action, adding at most 250 ms, so an endless script is reported busy about 500 ms after the action) }; absent when the page looked settled (exit code stays 0). A result a timer renders later, with no DOM change, request or loading indicator before it, is not detected. Cost: nothing extra, except 250 ms plus one read when the DOM looked busy. Not checked with --no-wait';
|
|
13805
13519
|
var SHOWN_BEHAVIOR = `Elements the action showed are listed (Shown: <element> "<text>"; JSON shown [{ text, element }], at most 3, outermost first): elements with visible text added inside the target's form, search box, dialog or combobox (else its grandparent, or its parent when that is the body), and popups and messages added anywhere (tooltip, menu, listbox, dialog, alert, status roles, popover, aria-live, message-like classes); widgets elsewhere on the page and re-rendered elements whose text was there before do not count`;
|
|
13806
13520
|
var NO_WAIT_TRIGGERED_REQUESTS = "Returns immediately without waiting for network; triggeredRequests lists only requests bdg saw start before returning (often none yet; check bdg network list later)";
|
|
13807
13521
|
var FOLLOW_BEHAVIOR = "Stops with exit 83 when the session it follows ends, 130 on Ctrl-C, 143 on SIGTERM; with --json prints one compact object per line (NDJSON). Other failures (a busy page, a timeout) are retried: reported once in text, as one error line per refresh in JSON";
|
|
@@ -13882,9 +13596,9 @@ var OPTION_BEHAVIORS = {
|
|
|
13882
13596
|
automaticBehavior: "The value is matched as: a 0-based index (bdg dom frames order: document order of the <iframe> elements, nested ones depth-first, main page not counted), else an exact name/id attribute, else a case-insensitive part of the name, id or URL. Several matches fail with 81 listing them; none fails with 83 listing all frames. Frames are looked up on every call (a reloaded iframe is found again). An index that names another frame than in the last bdg dom frames listing (iframes added, removed or moved, or the page navigated) fails with 87 STALE_CACHE: re-run bdg dom frames or pick the frame by name."
|
|
13883
13597
|
},
|
|
13884
13598
|
"eval:--full": {
|
|
13885
|
-
default: 'Human output prints the first 20000 characters of the value followed by "\u2026 N more chars (use --full)"
|
|
13886
|
-
whenEnabled: "Prints the whole value, byte for byte",
|
|
13887
|
-
tokenImpact:
|
|
13599
|
+
default: 'Human output prints the first 20000 characters of the value followed by "\u2026 N more chars (use --full)". In JSON a string result is cut to 20000 characters with truncatedFrom (the original length); an array result lists its first 100 elements with count (all of them) and omitted; an object or array whose JSON is still over 20000 characters becomes the first 20000 characters of its JSON text (a string; arrays keep count) with truncatedFrom (the length of the JSON text of the copy, which holds at most 1000 entries per list or object): a string result with truncatedFrom while type is object is such a JSON-text start',
|
|
13600
|
+
whenEnabled: "Prints the whole value, byte for byte; objects and arrays are copied with every entry (without --full at most 1000 per list or object)",
|
|
13601
|
+
tokenImpact: 'dom eval document.documentElement.outerHTML on Wikipedia is about 3.6 MB with --full; on a page with 20000 elements, [...document.querySelectorAll("*")].map(e => e.outerHTML) --json is 3 MB with --full and 23 KB without; select what you need in the expression instead'
|
|
13888
13602
|
},
|
|
13889
13603
|
"console:--full": {
|
|
13890
13604
|
default: 'Message texts are cut: human output (summary, --list, --follow) at 200 characters followed by "\u2026 N more chars (use --full)"; JSON text at 10000 characters with truncatedFrom (the original length)',
|
|
@@ -14111,6 +13825,11 @@ var OPTION_BEHAVIORS = {
|
|
|
14111
13825
|
whenEnabled: "Streams requests as they finish",
|
|
14112
13826
|
automaticBehavior: FOLLOW_BEHAVIOR
|
|
14113
13827
|
},
|
|
13828
|
+
"har:--include-sensitive": {
|
|
13829
|
+
default: `The HAR is sanitized: values become "[redacted]" for Authorization, Proxy-Authorization, Authentication, Cookie and Set-Cookie headers, X-*key/token/secret/auth headers and headers with an api-key/apikey/token/secret/jwt/subscription-key/session(-id) segment (www-authenticate is kept); every cookie value; query and fragment parameters named like credentials (plus code, sig, key) in the request URL, queryString, redirectURL and Location/Referer headers ("%5Bredacted%5D" in URLs); and password/token/secret/key/session/signature/bearer/cookie/csrf/refresh/auth/sid/pin/ssn/card-number fields of JSON (primitives at any depth under such a name), form-urlencoded (also sniffed when the Content-Type says otherwise, user[password] names too) and multipart request and response bodies (an access_token/refresh_token/id_token login response too) and of WebSocket messages, also in truncated JSON, JSON encoded in string values (3 levels), socket.io and SockJS packets, server-sent events, NDJSON, base64 bodies with no, a generic, JSON, form or event-stream type and binary WebSocket messages that are UTF-8 text; any whole JWT (eyJ\u2026) in a JSON string, form or URL value, multipart part or text body (only the JWT is replaced). camelCase names count as words (userPin). JSON is edited in place, not re-serialized: everything but the replaced values (64-bit numbers, formatting, duplicate keys, a BOM or )]}' prefix) stays byte for byte. Header, cookie and parameter names, cookie attributes, headersSize, bodySize and content.size stay; log.comment and JSON sanitized: true say so`,
|
|
13830
|
+
whenEnabled: "Writes every captured value (JSON sanitized: false); human output warns that the file holds credentials",
|
|
13831
|
+
automaticBehavior: 'Matching is by name, so it over-redacts: harmless values under credential-looking names (tokenCount: 5, sessionLength, refreshInterval, cookieConsent) are replaced too, and credentials under other names are kept. Only JSON syntax is understood: single-quoted strings, unquoted keys, JSONP, STOMP passcode: header lines and bare values with spaces ({"token":abc def}, a non-JWT token after Bearer in text) are not (fully) redacted. A body the sanitizer fails on is replaced whole by [redacted]. Unlike Chrome DevTools, which drops these headers and empties cookies, names are kept so the HAR still shows a request was authenticated. Kept as captured: other binary (base64) bodies, binary WebSocket messages that are not UTF-8, and text that is not JSON or a form (apart from JWTs); a WebSocket message cut at 100 KB has _truncatedFrom. HAR files are written readable by their owner only (0600). network headers and network getCookies always show real values'
|
|
13832
|
+
},
|
|
14114
13833
|
"peek:--verbose": {
|
|
14115
13834
|
default: "Compact output (truncated URLs, no resource types)",
|
|
14116
13835
|
whenEnabled: "Verbose output with full URLs and resource types"
|
|
@@ -14141,8 +13860,8 @@ var OPTION_BEHAVIORS = {
|
|
|
14141
13860
|
whenEnabled: "Alias for --force, kept for compatibility"
|
|
14142
13861
|
},
|
|
14143
13862
|
"cleanup:--purge": {
|
|
14144
|
-
default: "A named session's directory (Chrome profile, ~60 MB; logs; port.txt) is kept for its next start",
|
|
14145
|
-
whenEnabled: "After cleaning up, deletes the directory of the session named by --session (exit 81 without --session); a running session is refused unless --force is given, and the directory is kept (exit 90) if the daemon still answers, cleanup reported a problem, or its Chrome has not exited"
|
|
13863
|
+
default: "A named session's directory (Chrome profile, ~60 MB; logs; port.txt; downloads/) is kept for its next start",
|
|
13864
|
+
whenEnabled: "After cleaning up, deletes the directory of the session named by --session, downloaded files included (exit 81 without --session); a running session is refused unless --force is given, and the directory is kept (exit 90) if the daemon still answers, cleanup reported a problem, or its Chrome has not exited"
|
|
14146
13865
|
},
|
|
14147
13866
|
"bdg:--viewport": {
|
|
14148
13867
|
default: "A launched Chrome opens a 1920x1080 window (the viewport is smaller by the scrollbar, and in a visible window by the browser UI); an attached Chrome keeps its window",
|
|
@@ -14162,12 +13881,22 @@ var OPTION_BEHAVIORS = {
|
|
|
14162
13881
|
"bdg:--chrome-ws-url": {
|
|
14163
13882
|
default: "bdg launches its own Chrome (closed on stop)",
|
|
14164
13883
|
whenEnabled: "Attaches to a running Chrome instead; it keeps running after stop. --port, -u and --[no-]headless cannot be combined with it (exit 81)",
|
|
14165
|
-
automaticBehavior: "A port (9222), host:port or http://host:port is turned into the browser WebSocket URL via /json/version; a browser URL uses the first open tab. Refused with exit 90 when another running bdg session launched that Chrome or drives that tab (sessions of this BDG_SESSION_DIR, and of others that claimed a port)"
|
|
13884
|
+
automaticBehavior: "A port (9222), host:port or http://host:port is turned into the browser WebSocket URL via /json/version; a browser URL uses the first open tab. Refused with exit 90 when another running bdg session launched that Chrome or drives that tab (sessions of this BDG_SESSION_DIR, and of others that claimed a port). Downloads are not redirected: they go to the download folder of that Chrome (usually ~/Downloads), and only downloads of the session page are reported (not those of tabs or popups it opens)"
|
|
14166
13885
|
},
|
|
14167
13886
|
"stop:--kill-chrome": {
|
|
14168
13887
|
default: "Chrome launched by bdg is always closed on stop; an attached Chrome (--chrome-ws-url) is left running",
|
|
14169
13888
|
whenEnabled: "No additional effect; kept for compatibility"
|
|
14170
13889
|
},
|
|
13890
|
+
"cdp:--send-anyway": {
|
|
13891
|
+
default: "A Domain.method 1 edit from a bundled method or domain (2 for names over 5 letters, case ignored) is taken for a typo: exit 81 with Did you mean",
|
|
13892
|
+
whenEnabled: "Sends such a name to Chrome as typed (for a method newer than the bundled protocol, e.g. next to a bundled sibling like getWindowBounds/setWindowBounds), with the not-in-bundled-protocol warning",
|
|
13893
|
+
automaticBehavior: "Blocked methods (Page.captureScreenshot, Page.close, Browser.close), type names and names that are not Domain.method are still refused; a well-formed name far from every bundled one is sent without the flag"
|
|
13894
|
+
},
|
|
13895
|
+
"cdp:--describe": {
|
|
13896
|
+
default: "Without --describe, a Domain.method is called (one missing from the bundled protocol is sent as typed, with a warning: only bundled methods are matched case-insensitively)",
|
|
13897
|
+
whenEnabled: "Describes a domain, a method (parameters with ? for optional, returns, example) or a protocol type (Domain.Type: enum values or object properties)",
|
|
13898
|
+
automaticBehavior: "Parameters referring to an enum type list its values inline (JSON enum, ref, refType); a redirected method (DOM.highlightNode) also shows the method implementing it and its parameters, which Chrome checks (JSON redirect, resolved: true); a redirect to a method the protocol lacks (Page.deleteCookie \u2192 Network.deleteCookie) is shown as unresolved (resolved: false). Example values work as typed: width 1280, height 800, x/y 100, deviceScaleFactor/scale 1, timeout 5000, url https://example.com, other numbers 1 (never 0, which often means off)"
|
|
13899
|
+
},
|
|
14171
13900
|
"status:--verbose": {
|
|
14172
13901
|
default: "Basic session status (daemon running, session active, URL)",
|
|
14173
13902
|
whenEnabled: "Includes Chrome diagnostics and CDP connection details"
|
|
@@ -14231,25 +13960,25 @@ var loggingBufferColumns = 25;
|
|
|
14231
13960
|
var level_;
|
|
14232
13961
|
var Log = class _Log {
|
|
14233
13962
|
static _logToStdErr(title, argsArray) {
|
|
14234
|
-
const
|
|
14235
|
-
|
|
13963
|
+
const log33 = _Log.loggerfn(title);
|
|
13964
|
+
log33(...argsArray);
|
|
14236
13965
|
}
|
|
14237
13966
|
/**
|
|
14238
13967
|
* @param {string} title
|
|
14239
13968
|
*/
|
|
14240
13969
|
static loggerfn(title) {
|
|
14241
13970
|
title = `LH:${title}`;
|
|
14242
|
-
let
|
|
14243
|
-
if (!
|
|
14244
|
-
|
|
14245
|
-
loggersByTitle[title] =
|
|
13971
|
+
let log33 = loggersByTitle[title];
|
|
13972
|
+
if (!log33) {
|
|
13973
|
+
log33 = (0, import_debug.default)(title);
|
|
13974
|
+
loggersByTitle[title] = log33;
|
|
14246
13975
|
if (title.endsWith("error")) {
|
|
14247
|
-
|
|
13976
|
+
log33.color = colors.red;
|
|
14248
13977
|
} else if (title.endsWith("warn")) {
|
|
14249
|
-
|
|
13978
|
+
log33.color = colors.yellow;
|
|
14250
13979
|
}
|
|
14251
13980
|
}
|
|
14252
|
-
return
|
|
13981
|
+
return log33;
|
|
14253
13982
|
}
|
|
14254
13983
|
/**
|
|
14255
13984
|
* @param {string} level
|
|
@@ -14825,14 +14554,61 @@ function isDaemonSocketGone(socketPath = getSessionFilePath("DAEMON_SOCKET"), ti
|
|
|
14825
14554
|
});
|
|
14826
14555
|
}
|
|
14827
14556
|
|
|
14557
|
+
// src/session/metadata.ts
|
|
14558
|
+
init_logging();
|
|
14559
|
+
init_sessionCommand();
|
|
14560
|
+
init_atomicFile();
|
|
14561
|
+
init_errors();
|
|
14562
|
+
init_paths();
|
|
14563
|
+
import * as fs8 from "fs";
|
|
14564
|
+
var log6 = createLogger("session");
|
|
14565
|
+
function handleCorruptedMetadata(metaPath, error, options) {
|
|
14566
|
+
if (options?.warnOnCorruption) {
|
|
14567
|
+
log6.info(`Session metadata corrupted (cannot read details): ${getErrorMessage(error)}`);
|
|
14568
|
+
log6.info(`Troubleshooting: Run "${sessionCommand("bdg cleanup")}" to remove corrupted files`);
|
|
14569
|
+
}
|
|
14570
|
+
if (options?.selfHealOnCorruption) {
|
|
14571
|
+
try {
|
|
14572
|
+
fs8.rmSync(metaPath, { force: true });
|
|
14573
|
+
log6.info("Removed corrupted session metadata file");
|
|
14574
|
+
} catch (deleteError) {
|
|
14575
|
+
log6.debug(`Failed to remove corrupted metadata: ${getErrorMessage(deleteError)}`);
|
|
14576
|
+
}
|
|
14577
|
+
}
|
|
14578
|
+
}
|
|
14579
|
+
function readSessionMetadata(options) {
|
|
14580
|
+
const metaPath = getSessionFilePath("METADATA");
|
|
14581
|
+
if (!fs8.existsSync(metaPath)) {
|
|
14582
|
+
return null;
|
|
14583
|
+
}
|
|
14584
|
+
try {
|
|
14585
|
+
const content = fs8.readFileSync(metaPath, "utf-8");
|
|
14586
|
+
return JSON.parse(content);
|
|
14587
|
+
} catch (error) {
|
|
14588
|
+
handleCorruptedMetadata(metaPath, error, options);
|
|
14589
|
+
return null;
|
|
14590
|
+
}
|
|
14591
|
+
}
|
|
14592
|
+
|
|
14828
14593
|
// src/session/cleanup/staleSession.ts
|
|
14829
|
-
init_metadata();
|
|
14830
14594
|
init_paths();
|
|
14831
14595
|
init_logging();
|
|
14832
14596
|
|
|
14833
14597
|
// src/utils/async.ts
|
|
14834
|
-
function delay2(ms) {
|
|
14835
|
-
return new Promise((resolve4) =>
|
|
14598
|
+
function delay2(ms, signal) {
|
|
14599
|
+
return new Promise((resolve4) => {
|
|
14600
|
+
if (signal?.aborted) {
|
|
14601
|
+
resolve4();
|
|
14602
|
+
return;
|
|
14603
|
+
}
|
|
14604
|
+
const done = () => {
|
|
14605
|
+
clearTimeout(timer);
|
|
14606
|
+
signal?.removeEventListener("abort", done);
|
|
14607
|
+
resolve4();
|
|
14608
|
+
};
|
|
14609
|
+
const timer = setTimeout(done, ms);
|
|
14610
|
+
signal?.addEventListener("abort", done, { once: true });
|
|
14611
|
+
});
|
|
14836
14612
|
}
|
|
14837
14613
|
async function waitUntil(condition, timeoutMs, pollMs = 25) {
|
|
14838
14614
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -14846,12 +14622,12 @@ async function waitUntil(condition, timeoutMs, pollMs = 25) {
|
|
|
14846
14622
|
// src/utils/file.ts
|
|
14847
14623
|
init_logging();
|
|
14848
14624
|
import * as fs9 from "fs";
|
|
14849
|
-
function safeRemoveFile(
|
|
14625
|
+
function safeRemoveFile(path8, context, log33) {
|
|
14850
14626
|
try {
|
|
14851
|
-
fs9.rmSync(
|
|
14627
|
+
fs9.rmSync(path8, { force: true });
|
|
14852
14628
|
return true;
|
|
14853
14629
|
} catch (error) {
|
|
14854
|
-
logDebugError(
|
|
14630
|
+
logDebugError(log33, `remove ${context}`, error);
|
|
14855
14631
|
return false;
|
|
14856
14632
|
}
|
|
14857
14633
|
}
|
|
@@ -15342,7 +15118,7 @@ function convertCompactCommand(command) {
|
|
|
15342
15118
|
return {
|
|
15343
15119
|
name: command.name(),
|
|
15344
15120
|
...aliases.length > 0 && { aliases },
|
|
15345
|
-
description: command.description().split("\n")[0] ?? "",
|
|
15121
|
+
description: command.summary() || (command.description().split("\n")[0] ?? ""),
|
|
15346
15122
|
...args && { arguments: args },
|
|
15347
15123
|
...options.length > 0 && {
|
|
15348
15124
|
options: Object.fromEntries(options.map((option) => [option.flags, option.description]))
|
|
@@ -15439,14 +15215,14 @@ init_errors3();
|
|
|
15439
15215
|
init_messages();
|
|
15440
15216
|
init_exitCodes();
|
|
15441
15217
|
init_suggestions();
|
|
15442
|
-
var COMMANDER_HINT = /\n?\(Did you mean (.+)\?\)\s*$/;
|
|
15218
|
+
var COMMANDER_HINT = /\n?\(Did you mean (?:one of )?(.+)\?\)\s*$/;
|
|
15443
15219
|
function helpTopicPath(args) {
|
|
15444
15220
|
const firstOption = args.findIndex((arg) => arg.startsWith("-"));
|
|
15445
15221
|
return firstOption === -1 ? args : args.slice(0, firstOption);
|
|
15446
15222
|
}
|
|
15447
15223
|
function assertKnownHelpTopic(program2, topic) {
|
|
15448
15224
|
let command = program2;
|
|
15449
|
-
const
|
|
15225
|
+
const path8 = [];
|
|
15450
15226
|
for (const word of topic) {
|
|
15451
15227
|
if (command.commands.length === 0 || word === "help") return;
|
|
15452
15228
|
const sub = command.commands.find((c) => c.name() === word || c.aliases().includes(word));
|
|
@@ -15456,8 +15232,8 @@ function assertKnownHelpTopic(program2, topic) {
|
|
|
15456
15232
|
command.commands.map((c) => c.name())
|
|
15457
15233
|
);
|
|
15458
15234
|
const err = unknownHelpTopicError(
|
|
15459
|
-
[...
|
|
15460
|
-
closest && [...
|
|
15235
|
+
[...path8, word].join(" "),
|
|
15236
|
+
closest && [...path8, closest].join(" ")
|
|
15461
15237
|
);
|
|
15462
15238
|
throw new CommandError(
|
|
15463
15239
|
err.message,
|
|
@@ -15465,7 +15241,7 @@ function assertKnownHelpTopic(program2, topic) {
|
|
|
15465
15241
|
EXIT_CODES.INVALID_ARGUMENTS
|
|
15466
15242
|
);
|
|
15467
15243
|
}
|
|
15468
|
-
|
|
15244
|
+
path8.push(sub.name());
|
|
15469
15245
|
command = sub;
|
|
15470
15246
|
}
|
|
15471
15247
|
}
|
|
@@ -15473,7 +15249,10 @@ function splitCommanderHint(text) {
|
|
|
15473
15249
|
const message = text.replace(/^error:\s*/i, "");
|
|
15474
15250
|
const hint = COMMANDER_HINT.exec(message);
|
|
15475
15251
|
if (!hint) return { message };
|
|
15476
|
-
return {
|
|
15252
|
+
return {
|
|
15253
|
+
message: message.slice(0, hint.index).trim(),
|
|
15254
|
+
suggestion: didYouMeanSuggestion((hint[1] ?? "").split(", "))
|
|
15255
|
+
};
|
|
15477
15256
|
}
|
|
15478
15257
|
var UNKNOWN_OPTION = /unknown option '([^'=]+)/;
|
|
15479
15258
|
function closestOption(flag, command) {
|
|
@@ -15504,7 +15283,7 @@ function usageErrorDetails(error, command, argv = process.argv) {
|
|
|
15504
15283
|
|
|
15505
15284
|
// src/commands/start.ts
|
|
15506
15285
|
import fs11 from "node:fs";
|
|
15507
|
-
import
|
|
15286
|
+
import os4 from "node:os";
|
|
15508
15287
|
|
|
15509
15288
|
// src/commands/shared/commonOptions.ts
|
|
15510
15289
|
function jsonOption() {
|
|
@@ -15529,14 +15308,14 @@ init_messages();
|
|
|
15529
15308
|
|
|
15530
15309
|
// src/utils/version.ts
|
|
15531
15310
|
import { readFileSync as readFileSync6 } from "fs";
|
|
15532
|
-
import { join as
|
|
15311
|
+
import { join as join6 } from "path";
|
|
15533
15312
|
var cachedVersion = "";
|
|
15534
15313
|
function getVersion() {
|
|
15535
15314
|
if (cachedVersion) {
|
|
15536
15315
|
return cachedVersion;
|
|
15537
15316
|
}
|
|
15538
15317
|
try {
|
|
15539
|
-
const pkgPath =
|
|
15318
|
+
const pkgPath = join6(PACKAGE_ROOT, "package.json");
|
|
15540
15319
|
const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
|
|
15541
15320
|
cachedVersion = pkg.version ?? "0.0.0";
|
|
15542
15321
|
} catch {
|
|
@@ -15547,8 +15326,8 @@ function getVersion() {
|
|
|
15547
15326
|
var VERSION = getVersion();
|
|
15548
15327
|
|
|
15549
15328
|
// src/ui/OutputBuilder.ts
|
|
15550
|
-
function buildSuccessResponse(data) {
|
|
15551
|
-
return { version: VERSION, success: true, data };
|
|
15329
|
+
function buildSuccessResponse(data, warning) {
|
|
15330
|
+
return { version: VERSION, success: true, data, ...warning && { warning } };
|
|
15552
15331
|
}
|
|
15553
15332
|
function stringifyEnvelope(envelope) {
|
|
15554
15333
|
return process.stdout.isTTY ? JSON.stringify(envelope, null, 2) : JSON.stringify(envelope);
|
|
@@ -15659,6 +15438,9 @@ function sessionEndedError() {
|
|
|
15659
15438
|
EXIT_CODES.RESOURCE_NOT_FOUND
|
|
15660
15439
|
);
|
|
15661
15440
|
}
|
|
15441
|
+
function printWarning(warning) {
|
|
15442
|
+
if (warning) console.error(escapeControlChars(`Warning: ${warning}`));
|
|
15443
|
+
}
|
|
15662
15444
|
async function runCommand(handler, options, formatter) {
|
|
15663
15445
|
try {
|
|
15664
15446
|
const result = await handler(options);
|
|
@@ -15669,6 +15451,7 @@ async function runCommand(handler, options, formatter) {
|
|
|
15669
15451
|
stringifyEnvelope(
|
|
15670
15452
|
OutputBuilder.buildJsonError(result.error ?? "Unknown error", {
|
|
15671
15453
|
...result.errorContext,
|
|
15454
|
+
...result.warning && { warning: result.warning },
|
|
15672
15455
|
exitCode
|
|
15673
15456
|
})
|
|
15674
15457
|
)
|
|
@@ -15684,14 +15467,16 @@ async function runCommand(handler, options, formatter) {
|
|
|
15684
15467
|
}
|
|
15685
15468
|
}
|
|
15686
15469
|
}
|
|
15470
|
+
printWarning(result.warning);
|
|
15687
15471
|
}
|
|
15688
15472
|
process.exit(exitCode);
|
|
15689
15473
|
}
|
|
15474
|
+
if (!options.json) printWarning(result.warning);
|
|
15690
15475
|
if (result.hint && !options.quiet) {
|
|
15691
15476
|
console.error(escapeControlChars(result.hint));
|
|
15692
15477
|
}
|
|
15693
15478
|
if (options.json) {
|
|
15694
|
-
console.log(stringifyEnvelope(buildSuccessResponse(result.data)));
|
|
15479
|
+
console.log(stringifyEnvelope(buildSuccessResponse(result.data, result.warning)));
|
|
15695
15480
|
} else if (formatter) {
|
|
15696
15481
|
const formattedOutput = formatter(result.data);
|
|
15697
15482
|
console.log(escapeControlChars(formattedOutput));
|
|
@@ -15712,10 +15497,14 @@ async function runCommand(handler, options, formatter) {
|
|
|
15712
15497
|
)
|
|
15713
15498
|
);
|
|
15714
15499
|
} else {
|
|
15500
|
+
const { warning, ...metadata } = error.metadata;
|
|
15715
15501
|
console.error(genericError(error.message));
|
|
15716
|
-
for (const value of Object.values(
|
|
15717
|
-
console.error(
|
|
15502
|
+
for (const value of Object.values(metadata)) {
|
|
15503
|
+
console.error(
|
|
15504
|
+
escapeControlChars(typeof value === "string" ? value : JSON.stringify(value))
|
|
15505
|
+
);
|
|
15718
15506
|
}
|
|
15507
|
+
printWarning(warning);
|
|
15719
15508
|
}
|
|
15720
15509
|
process.exit(error.exitCode);
|
|
15721
15510
|
}
|
|
@@ -15745,6 +15534,9 @@ async function runCommand(handler, options, formatter) {
|
|
|
15745
15534
|
}
|
|
15746
15535
|
}
|
|
15747
15536
|
|
|
15537
|
+
// src/commands/shared/startHelpers.ts
|
|
15538
|
+
init_interrupt();
|
|
15539
|
+
|
|
15748
15540
|
// src/ui/messages/session.ts
|
|
15749
15541
|
init_formatting();
|
|
15750
15542
|
init_commands();
|
|
@@ -15792,6 +15584,9 @@ function lastSessionEndText(end) {
|
|
|
15792
15584
|
function endedSessionText(label, end) {
|
|
15793
15585
|
return `${label} ended ${sessionEndText(end)}`;
|
|
15794
15586
|
}
|
|
15587
|
+
function untrustedSessionText(label, why) {
|
|
15588
|
+
return `${label}: ${why}`;
|
|
15589
|
+
}
|
|
15795
15590
|
function sessionEndText(end) {
|
|
15796
15591
|
const why = {
|
|
15797
15592
|
crash: "Chrome crashed or was closed",
|
|
@@ -15849,7 +15644,7 @@ var SessionDirError = class extends DaemonError {
|
|
|
15849
15644
|
// src/daemon/launcher.ts
|
|
15850
15645
|
import { spawn } from "child_process";
|
|
15851
15646
|
import fs10 from "fs";
|
|
15852
|
-
import { join as
|
|
15647
|
+
import { join as join7 } from "path";
|
|
15853
15648
|
init_messages();
|
|
15854
15649
|
init_paths();
|
|
15855
15650
|
init_logging();
|
|
@@ -15860,6 +15655,7 @@ var log8 = createLogger("launcher");
|
|
|
15860
15655
|
var DAEMON_READY_TIMEOUT_MS = 5e3;
|
|
15861
15656
|
var DAEMON_READY_POLL_MS = 20;
|
|
15862
15657
|
async function launchDaemon() {
|
|
15658
|
+
assertUsableSessionDir();
|
|
15863
15659
|
if (await isDaemonAlive()) {
|
|
15864
15660
|
log8.debug("Daemon already running");
|
|
15865
15661
|
return void 0;
|
|
@@ -15870,11 +15666,10 @@ async function launchDaemon() {
|
|
|
15870
15666
|
"DAEMON_SCRIPT_NOT_FOUND"
|
|
15871
15667
|
);
|
|
15872
15668
|
}
|
|
15873
|
-
assertUsableSessionDir();
|
|
15874
15669
|
await stopUnreachableDaemon();
|
|
15875
|
-
const logPath =
|
|
15670
|
+
const logPath = join7(getSessionDir(), "daemon.log");
|
|
15876
15671
|
rotateLog(logPath);
|
|
15877
|
-
const logFd =
|
|
15672
|
+
const logFd = openDaemonLog(logPath);
|
|
15878
15673
|
log8.debug(`Starting daemon: ${DAEMON_SCRIPT_PATH}`);
|
|
15879
15674
|
const daemon = spawn(process.execPath, [DAEMON_SCRIPT_PATH], {
|
|
15880
15675
|
detached: true,
|
|
@@ -15887,7 +15682,13 @@ async function launchDaemon() {
|
|
|
15887
15682
|
});
|
|
15888
15683
|
daemon.unref();
|
|
15889
15684
|
await waitForDaemonReady(() => exited);
|
|
15890
|
-
return {
|
|
15685
|
+
return {
|
|
15686
|
+
pid: daemon.pid,
|
|
15687
|
+
hasExited: () => exited,
|
|
15688
|
+
stop: () => {
|
|
15689
|
+
if (!exited) daemon.kill("SIGTERM");
|
|
15690
|
+
}
|
|
15691
|
+
};
|
|
15891
15692
|
}
|
|
15892
15693
|
var UNREACHABLE_DAEMON_EXIT_MS = 8e3;
|
|
15893
15694
|
async function stopUnreachableDaemon() {
|
|
@@ -15930,6 +15731,8 @@ function assertUsableSessionDir() {
|
|
|
15930
15731
|
problem.denied ? EXIT_CODES.PERMISSION_DENIED : EXIT_CODES.SESSION_FILE_ERROR
|
|
15931
15732
|
);
|
|
15932
15733
|
}
|
|
15734
|
+
const untrusted = secureSessionDir(dir);
|
|
15735
|
+
if (untrusted) fail(untrustedSessionDirError(untrusted));
|
|
15933
15736
|
try {
|
|
15934
15737
|
ensureSessionDir();
|
|
15935
15738
|
fs10.accessSync(dir, fs10.constants.W_OK);
|
|
@@ -15942,6 +15745,18 @@ function assertUsableSessionDir() {
|
|
|
15942
15745
|
);
|
|
15943
15746
|
}
|
|
15944
15747
|
}
|
|
15748
|
+
var DAEMON_LOG_FLAGS = fs10.constants.O_WRONLY | fs10.constants.O_APPEND | fs10.constants.O_CREAT | (fs10.constants.O_NOFOLLOW ?? 0);
|
|
15749
|
+
function openDaemonLog(logPath) {
|
|
15750
|
+
try {
|
|
15751
|
+
return fs10.openSync(logPath, DAEMON_LOG_FLAGS, 384);
|
|
15752
|
+
} catch (error) {
|
|
15753
|
+
const err = daemonLogNotOpenedError(
|
|
15754
|
+
logPath,
|
|
15755
|
+
error.code ?? getErrorMessage(error)
|
|
15756
|
+
);
|
|
15757
|
+
throw new SessionDirError(err.message, err.suggestion);
|
|
15758
|
+
}
|
|
15759
|
+
}
|
|
15945
15760
|
var MAX_LOG_BYTES = 5 * 1024 * 1024;
|
|
15946
15761
|
function rotateLog(logPath) {
|
|
15947
15762
|
try {
|
|
@@ -15961,7 +15776,7 @@ async function waitForDaemonReady(hasExited) {
|
|
|
15961
15776
|
}
|
|
15962
15777
|
if (hasExited() && !await isDaemonAlive()) {
|
|
15963
15778
|
throw new DaemonStartupError(
|
|
15964
|
-
`Daemon exited during startup. See ${
|
|
15779
|
+
`Daemon exited during startup. See ${join7(getSessionDir(), "daemon.log")}`,
|
|
15965
15780
|
"DAEMON_EXITED"
|
|
15966
15781
|
);
|
|
15967
15782
|
}
|
|
@@ -16006,6 +15821,7 @@ var COMMANDS = {
|
|
|
16006
15821
|
dom_audit: defineCommand(),
|
|
16007
15822
|
css_search: defineCommand(),
|
|
16008
15823
|
dom_inspect: defineCommand(),
|
|
15824
|
+
dom_screenshot: defineCommand(),
|
|
16009
15825
|
dom_wait: defineCommand()
|
|
16010
15826
|
};
|
|
16011
15827
|
|
|
@@ -16088,27 +15904,58 @@ var SPAWNED_DAEMON_EXIT_WAIT_MS = 3e3;
|
|
|
16088
15904
|
var DEFAULT_START_DEPS = {
|
|
16089
15905
|
launch: launchDaemon,
|
|
16090
15906
|
send: startSession,
|
|
15907
|
+
stop: stopSession,
|
|
16091
15908
|
exitWaitMs: SPAWNED_DAEMON_EXIT_WAIT_MS
|
|
16092
15909
|
};
|
|
16093
15910
|
async function startSessionViaDaemon(url, options, telemetry) {
|
|
16094
|
-
|
|
16095
|
-
|
|
16096
|
-
|
|
15911
|
+
const interrupt = new AbortController();
|
|
15912
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
15913
|
+
process.on(signal, () => {
|
|
15914
|
+
if (interrupt.signal.aborted) reportStartOutcome(interruptedOutcome(signal), options);
|
|
15915
|
+
interrupt.abort(signal);
|
|
15916
|
+
});
|
|
15917
|
+
}
|
|
15918
|
+
reportStartOutcome(
|
|
15919
|
+
await attemptStart(url, options, telemetry, DEFAULT_START_DEPS, interrupt.signal),
|
|
15920
|
+
options
|
|
15921
|
+
);
|
|
16097
15922
|
}
|
|
16098
|
-
async function attemptStart(url, options, telemetry, deps = DEFAULT_START_DEPS) {
|
|
15923
|
+
async function attemptStart(url, options, telemetry, deps = DEFAULT_START_DEPS, interrupt) {
|
|
16099
15924
|
const spawned = [];
|
|
16100
15925
|
const attempt = async () => {
|
|
16101
|
-
const outcome2 = await requestSession(url, options, telemetry, deps);
|
|
15926
|
+
const outcome2 = await requestSession(url, options, telemetry, deps, interrupt);
|
|
16102
15927
|
if (!outcome2.ok && outcome2.spawned) spawned.push(outcome2.spawned);
|
|
16103
15928
|
return outcome2;
|
|
16104
15929
|
};
|
|
16105
15930
|
let outcome = await attempt();
|
|
16106
15931
|
const deadline = Date.now() + SHUTDOWN_WAIT_MS;
|
|
16107
15932
|
while (isShuttingDown(outcome) && Date.now() < deadline) {
|
|
16108
|
-
await delay2(SHUTDOWN_POLL_MS);
|
|
15933
|
+
await delay2(SHUTDOWN_POLL_MS, interrupt);
|
|
16109
15934
|
outcome = await attempt();
|
|
16110
15935
|
}
|
|
16111
|
-
return
|
|
15936
|
+
return settleOutcome(outcome, spawned, deps, interrupt);
|
|
15937
|
+
}
|
|
15938
|
+
async function settleOutcome(outcome, spawned, deps, interrupt) {
|
|
15939
|
+
if (outcome.ok) {
|
|
15940
|
+
const { spawned: daemon, ...started } = outcome;
|
|
15941
|
+
return interrupt?.aborted ? cancelStartedSession(started.data, daemon, deps, interrupt) : started;
|
|
15942
|
+
}
|
|
15943
|
+
const failure = await afterSpawnedDaemonExit(outcome, spawned, deps.exitWaitMs);
|
|
15944
|
+
return interrupt?.aborted ? withInterruptExitCode(failure, interrupt) : failure;
|
|
15945
|
+
}
|
|
15946
|
+
function cancelStartedSession(data, spawned, deps, interrupt) {
|
|
15947
|
+
log11.debug("Interrupted as the session started; stopping it");
|
|
15948
|
+
deps.stop().catch((error) => {
|
|
15949
|
+
log11.debug(`Stopping the interrupted session failed: ${getErrorMessage(error)}`);
|
|
15950
|
+
});
|
|
15951
|
+
const daemon = spawned ?? {
|
|
15952
|
+
pid: data.daemonPid,
|
|
15953
|
+
hasExited: () => !isProcessAlive(data.daemonPid)
|
|
15954
|
+
};
|
|
15955
|
+
return afterSpawnedDaemonExit(cancelledOutcome(interrupt), [daemon], deps.exitWaitMs);
|
|
15956
|
+
}
|
|
15957
|
+
function withInterruptExitCode(outcome, interrupt) {
|
|
15958
|
+
return outcome.ok ? outcome : { ...outcome, exitCode: interruptExitCode(interruptSignal(interrupt)) };
|
|
16112
15959
|
}
|
|
16113
15960
|
async function afterSpawnedDaemonExit(outcome, spawned, waitMs) {
|
|
16114
15961
|
if (outcome.ok) return outcome;
|
|
@@ -16136,15 +15983,18 @@ async function afterSpawnedDaemonExit(outcome, spawned, waitMs) {
|
|
|
16136
15983
|
}
|
|
16137
15984
|
function interruptedOutcome(signal) {
|
|
16138
15985
|
const message = `Start cancelled (${signal === "SIGINT" ? "interrupted" : "terminated"})`;
|
|
16139
|
-
|
|
16140
|
-
|
|
15986
|
+
return { ok: false, error: message, human: message, exitCode: interruptExitCode(signal) };
|
|
15987
|
+
}
|
|
15988
|
+
function cancelledOutcome(interrupt) {
|
|
15989
|
+
return interruptedOutcome(interruptSignal(interrupt));
|
|
16141
15990
|
}
|
|
16142
15991
|
var SHUTDOWN_WAIT_MS = 15e3;
|
|
16143
15992
|
var SHUTDOWN_POLL_MS = 200;
|
|
16144
15993
|
function isShuttingDown(outcome) {
|
|
16145
15994
|
return !outcome.ok && (outcome.retryable === true || outcome.errorCode === "SESSION_SHUTTING_DOWN" /* SESSION_SHUTTING_DOWN */);
|
|
16146
15995
|
}
|
|
16147
|
-
async function requestSession(url, options, telemetry, deps) {
|
|
15996
|
+
async function requestSession(url, options, telemetry, deps, interrupt) {
|
|
15997
|
+
if (interrupt?.aborted) return cancelledOutcome(interrupt);
|
|
16148
15998
|
let spawned;
|
|
16149
15999
|
try {
|
|
16150
16000
|
spawned = await deps.launch();
|
|
@@ -16162,10 +16012,16 @@ async function requestSession(url, options, telemetry, deps) {
|
|
|
16162
16012
|
const exitCode = error instanceof DaemonError ? error.exitCode : EXIT_CODES.SOFTWARE_ERROR;
|
|
16163
16013
|
return { ok: false, error: message, human: genericError(message), exitCode };
|
|
16164
16014
|
}
|
|
16165
|
-
|
|
16166
|
-
|
|
16015
|
+
if (interrupt?.aborted) return cancelBeforeRequest(spawned, interrupt);
|
|
16016
|
+
const outcome = await sendStart(url, options, telemetry, deps.send, interrupt);
|
|
16017
|
+
return spawned && (outcome.ok || outcome.daemonExiting) ? { ...outcome, spawned } : outcome;
|
|
16018
|
+
}
|
|
16019
|
+
function cancelBeforeRequest(spawned, interrupt) {
|
|
16020
|
+
if (!spawned) return cancelledOutcome(interrupt);
|
|
16021
|
+
spawned.stop();
|
|
16022
|
+
return { ...cancelledOutcome(interrupt), daemonExiting: true, spawned };
|
|
16167
16023
|
}
|
|
16168
|
-
async function sendStart(url, options, telemetry, send) {
|
|
16024
|
+
async function sendStart(url, options, telemetry, send, interrupt) {
|
|
16169
16025
|
try {
|
|
16170
16026
|
log11.debug("Connecting to daemon...");
|
|
16171
16027
|
const response = await send(
|
|
@@ -16182,7 +16038,8 @@ async function sendStart(url, options, telemetry, send) {
|
|
|
16182
16038
|
chromeFlags: options.chromeFlags,
|
|
16183
16039
|
viewport: options.viewport,
|
|
16184
16040
|
colorScheme: options.colorScheme
|
|
16185
|
-
})
|
|
16041
|
+
}),
|
|
16042
|
+
interrupt
|
|
16186
16043
|
);
|
|
16187
16044
|
if (response.status === "error") {
|
|
16188
16045
|
return { ...describeStartFailure(response, options), daemonExiting: true };
|
|
@@ -16198,6 +16055,7 @@ async function sendStart(url, options, telemetry, send) {
|
|
|
16198
16055
|
}
|
|
16199
16056
|
return { ok: true, data: response.data };
|
|
16200
16057
|
} catch (error) {
|
|
16058
|
+
if (interrupt?.aborted) return { ...cancelledOutcome(interrupt), daemonExiting: true };
|
|
16201
16059
|
if (error instanceof IPCTimeoutError) {
|
|
16202
16060
|
const timeout = timeoutError(error);
|
|
16203
16061
|
return {
|
|
@@ -16547,9 +16405,11 @@ var CDP_HTTP_TIMEOUT_MS = 5e3;
|
|
|
16547
16405
|
async function probeDevToolsEndpoint(port, logger, options) {
|
|
16548
16406
|
const url = `${options?.secure ? "https" : "http"}://${options?.host ?? HTTP_LOCALHOST}:${port}/json/version`;
|
|
16549
16407
|
const timeoutMs = options?.timeoutMs ?? CDP_HTTP_TIMEOUT_MS;
|
|
16408
|
+
const timeout = AbortSignal.timeout(timeoutMs);
|
|
16409
|
+
const signal = options?.signal ? AbortSignal.any([timeout, options.signal]) : timeout;
|
|
16550
16410
|
let response;
|
|
16551
16411
|
try {
|
|
16552
|
-
response = await fetch(url, { signal
|
|
16412
|
+
response = await fetch(url, { signal });
|
|
16553
16413
|
} catch (error) {
|
|
16554
16414
|
logger?.debug(`Chrome version request failed: ${getErrorMessage(error)} (${url})`);
|
|
16555
16415
|
return { kind: "unreachable" };
|
|
@@ -16568,7 +16428,7 @@ async function probeDevToolsEndpoint(port, logger, options) {
|
|
|
16568
16428
|
init_suggestions();
|
|
16569
16429
|
init_url();
|
|
16570
16430
|
function expandHome(value) {
|
|
16571
|
-
return value.startsWith("~/") ? value.replace(/^~/,
|
|
16431
|
+
return value.startsWith("~/") ? value.replace(/^~/, os4.homedir()) : value;
|
|
16572
16432
|
}
|
|
16573
16433
|
function extractUserDataDirFromFlags(flags) {
|
|
16574
16434
|
const rest = [];
|
|
@@ -16864,6 +16724,50 @@ function assertValidChromeWsUrl(url) {
|
|
|
16864
16724
|
init_errors3();
|
|
16865
16725
|
init_messages();
|
|
16866
16726
|
|
|
16727
|
+
// src/cdp/methodTarget.ts
|
|
16728
|
+
init_protocol();
|
|
16729
|
+
init_levenshtein();
|
|
16730
|
+
init_suggestions();
|
|
16731
|
+
var METHOD_NAME = /^[A-Za-z][A-Za-z0-9]*\.[A-Za-z][A-Za-z0-9]*$/;
|
|
16732
|
+
function typoLimit(name) {
|
|
16733
|
+
return name.length <= 5 ? 1 : 2;
|
|
16734
|
+
}
|
|
16735
|
+
function closeNames(input, candidates) {
|
|
16736
|
+
const lower = input.toLowerCase();
|
|
16737
|
+
return candidates.map((name) => ({ name, distance: levenshteinDistance(lower, name.toLowerCase()) })).filter(({ distance }) => distance > 0 && distance <= typoLimit(input)).sort((a, b) => a.distance - b.distance).map(({ name }) => name);
|
|
16738
|
+
}
|
|
16739
|
+
function closeMethods(domainName, methodName) {
|
|
16740
|
+
const commands = findDomain(domainName)?.commands ?? [];
|
|
16741
|
+
const exact = commands.find((c) => c.name.toLowerCase() === methodName.toLowerCase());
|
|
16742
|
+
const names = exact ? [exact.name] : closeNames(
|
|
16743
|
+
methodName,
|
|
16744
|
+
commands.map((c) => c.name)
|
|
16745
|
+
);
|
|
16746
|
+
return names.map((name) => `${domainName}.${name}`);
|
|
16747
|
+
}
|
|
16748
|
+
function resolveUnknownDomain(domainName, methodName) {
|
|
16749
|
+
const domains = closeNames(
|
|
16750
|
+
domainName,
|
|
16751
|
+
loadProtocol().domains.map((d) => d.domain)
|
|
16752
|
+
);
|
|
16753
|
+
const method = `${domainName}.${methodName}`;
|
|
16754
|
+
if (domains.length === 0) return { kind: "unlisted", method };
|
|
16755
|
+
return { kind: "typo", method, suggestions: domains.flatMap((d) => closeMethods(d, methodName)) };
|
|
16756
|
+
}
|
|
16757
|
+
function resolveMethodTarget(input) {
|
|
16758
|
+
if (!METHOD_NAME.test(input)) return { kind: "malformed" };
|
|
16759
|
+
const known = normalizeMethod(input);
|
|
16760
|
+
if (known) return { kind: "known", method: known };
|
|
16761
|
+
const [domainName = "", methodName = ""] = input.split(".");
|
|
16762
|
+
const domain = findDomain(domainName);
|
|
16763
|
+
if (!domain) return resolveUnknownDomain(domainName, methodName);
|
|
16764
|
+
const type = findType(domain.domain, methodName);
|
|
16765
|
+
if (type) return { kind: "type", name: `${domain.domain}.${type.id}` };
|
|
16766
|
+
const method = `${domain.domain}.${methodName}`;
|
|
16767
|
+
const suggestions = closeMethods(domain.domain, methodName);
|
|
16768
|
+
return suggestions.length > 0 ? { kind: "typo", method, suggestions } : { kind: "unlisted", method };
|
|
16769
|
+
}
|
|
16770
|
+
|
|
16867
16771
|
// src/commands/cdp.ts
|
|
16868
16772
|
init_protocol();
|
|
16869
16773
|
init_schema();
|
|
@@ -16871,8 +16775,10 @@ init_errors3();
|
|
|
16871
16775
|
init_messages();
|
|
16872
16776
|
init_client();
|
|
16873
16777
|
|
|
16778
|
+
// src/connection/errors.ts
|
|
16779
|
+
init_exitCodes();
|
|
16780
|
+
|
|
16874
16781
|
// src/runtime/dom/evalHelpers.ts
|
|
16875
|
-
init_errors5();
|
|
16876
16782
|
init_errors3();
|
|
16877
16783
|
init_messages();
|
|
16878
16784
|
|
|
@@ -16906,11 +16812,7 @@ var WEBSOCKET_CONFIG = {
|
|
|
16906
16812
|
noPongClosure: 1001
|
|
16907
16813
|
};
|
|
16908
16814
|
|
|
16909
|
-
// src/connection/cdp.ts
|
|
16910
|
-
init_errors5();
|
|
16911
|
-
|
|
16912
16815
|
// src/daemon/session/cdpSetup.ts
|
|
16913
|
-
init_errors5();
|
|
16914
16816
|
init_constants();
|
|
16915
16817
|
init_errors3();
|
|
16916
16818
|
init_messages();
|
|
@@ -17104,6 +17006,7 @@ function describeException(details) {
|
|
|
17104
17006
|
|
|
17105
17007
|
// src/ui/formatters/cdp.ts
|
|
17106
17008
|
init_formatting();
|
|
17009
|
+
init_commands();
|
|
17107
17010
|
function firstSentence(description) {
|
|
17108
17011
|
const [line = ""] = (description ?? "").split("\n");
|
|
17109
17012
|
const end = line.indexOf(". ");
|
|
@@ -17122,6 +17025,16 @@ function columns(rows) {
|
|
|
17122
17025
|
function typeText(field) {
|
|
17123
17026
|
return field.items ? `${field.type}<${field.items}>` : field.type;
|
|
17124
17027
|
}
|
|
17028
|
+
var MAX_ENUM_VALUES = 8;
|
|
17029
|
+
function expansionText(field) {
|
|
17030
|
+
const values = field.enum;
|
|
17031
|
+
if (!values || values.length === 0) {
|
|
17032
|
+
return field.refType && field.refType !== "object" ? ` (${field.refType})` : "";
|
|
17033
|
+
}
|
|
17034
|
+
const shown = values.slice(0, MAX_ENUM_VALUES);
|
|
17035
|
+
const more = values.length - shown.length;
|
|
17036
|
+
return ` (${[...shown, ...more > 0 ? [`\u2026 ${more} more`] : []].join("|")})`;
|
|
17037
|
+
}
|
|
17125
17038
|
function formatCdpSearch(data) {
|
|
17126
17039
|
if (data.count === 0) {
|
|
17127
17040
|
return joinLines(`No CDP method matches "${data.query}"`, "List the domains: bdg cdp --list");
|
|
@@ -17158,13 +17071,37 @@ function fieldSection(title, fields) {
|
|
|
17158
17071
|
`${title}:`,
|
|
17159
17072
|
...columns(
|
|
17160
17073
|
fields.map((f) => [
|
|
17161
|
-
`${f.name}${f.optional ? "?" : ""}: ${typeText(f)}`,
|
|
17162
|
-
(f.description ?? "").replace(/\s*\n\s*/g, " ")
|
|
17074
|
+
`${f.name}${f.optional ? "?" : ""}: ${typeText(f)}${expansionText(f)}`,
|
|
17075
|
+
`${(f.description ?? "").replace(/\s*\n\s*/g, " ")}${tags(f)}`
|
|
17163
17076
|
])
|
|
17164
17077
|
)
|
|
17165
17078
|
];
|
|
17166
17079
|
}
|
|
17080
|
+
function parameterFields(parameters) {
|
|
17081
|
+
return parameters.map((p) => ({ ...p, optional: !p.required }));
|
|
17082
|
+
}
|
|
17083
|
+
function typeHint(parameters) {
|
|
17084
|
+
const ref = parameters.find((p) => p.refType === "object")?.ref;
|
|
17085
|
+
return ref && `Describe a type: bdg cdp ${ref} --describe`;
|
|
17086
|
+
}
|
|
17087
|
+
function redirectSection(redirect) {
|
|
17088
|
+
if (!redirect) return [];
|
|
17089
|
+
if (!redirect.resolved) return [cdpUnresolvedRedirectLine(redirect.method)];
|
|
17090
|
+
const title = cdpRedirectTitle(redirect.method);
|
|
17091
|
+
return redirect.parameters.length === 0 ? [title] : fieldSection(`${title}, with these parameters`, parameterFields(redirect.parameters));
|
|
17092
|
+
}
|
|
17093
|
+
function formatCdpType(data) {
|
|
17094
|
+
const properties = data.properties ?? [];
|
|
17095
|
+
return joinLines(
|
|
17096
|
+
`${data.name}: ${typeText({ name: data.id, type: data.baseType, items: data.items })}${tags(data)}`,
|
|
17097
|
+
data.description,
|
|
17098
|
+
data.enum && `Values: ${data.enum.join(", ")}`,
|
|
17099
|
+
...fieldSection("Properties", parameterFields(properties)),
|
|
17100
|
+
typeHint(properties)
|
|
17101
|
+
);
|
|
17102
|
+
}
|
|
17167
17103
|
function formatCdpDescription(data) {
|
|
17104
|
+
if (data.type === "type") return formatCdpType(data);
|
|
17168
17105
|
if (data.type === "domain") {
|
|
17169
17106
|
return joinLines(
|
|
17170
17107
|
`${data.domain}: ${pluralize(data.commands, "method")}, ${pluralize(data.events, "event")}${tags(data)}`,
|
|
@@ -17173,15 +17110,15 @@ function formatCdpDescription(data) {
|
|
|
17173
17110
|
data.nextStep
|
|
17174
17111
|
);
|
|
17175
17112
|
}
|
|
17113
|
+
const redirected = data.redirect?.parameters ?? [];
|
|
17176
17114
|
return joinLines(
|
|
17177
17115
|
`${data.name}${tags(data)}`,
|
|
17178
17116
|
data.description,
|
|
17179
|
-
...fieldSection(
|
|
17180
|
-
|
|
17181
|
-
data.parameters.map((p) => ({ ...p, optional: !p.required }))
|
|
17182
|
-
),
|
|
17117
|
+
...fieldSection("Parameters", parameterFields(data.parameters)),
|
|
17118
|
+
...redirectSection(data.redirect),
|
|
17183
17119
|
...fieldSection("Returns", data.returns),
|
|
17184
17120
|
data.note && `Note: ${data.note}`,
|
|
17121
|
+
typeHint([...data.parameters, ...redirected]),
|
|
17185
17122
|
data.example && `Example: ${data.example.command}`
|
|
17186
17123
|
);
|
|
17187
17124
|
}
|
|
@@ -17192,6 +17129,9 @@ function formatCdpResult(data) {
|
|
|
17192
17129
|
return isEmptyCdpResult(data.result) ? `${data.method}: done (no result data)` : JSON.stringify(data.result, null, 2);
|
|
17193
17130
|
}
|
|
17194
17131
|
|
|
17132
|
+
// src/commands/cdp.ts
|
|
17133
|
+
init_commands();
|
|
17134
|
+
|
|
17195
17135
|
// src/ui/messages/hints.ts
|
|
17196
17136
|
init_formatting();
|
|
17197
17137
|
init_sessionCommand();
|
|
@@ -17244,14 +17184,19 @@ function getMethodHint(methodName, result) {
|
|
|
17244
17184
|
return void 0;
|
|
17245
17185
|
}
|
|
17246
17186
|
function registerCdpCommand(program2) {
|
|
17247
|
-
program2.command("cdp").description(
|
|
17248
|
-
"CDP protocol introspection and execution\n Discovery: --list, --search, --describe\n
|
|
17187
|
+
program2.command("cdp").summary("CDP protocol introspection and execution").description(
|
|
17188
|
+
"CDP protocol introspection and execution\n Discovery: --list, --search, --describe\n" + CDP_EXECUTION_HELP
|
|
17249
17189
|
).argument("[method]", "CDP method name (e.g., Network.getCookies, network.getcookies)").addOption(new Option("--params <json>", "Method parameters as JSON")).addOption(
|
|
17250
17190
|
new Option("--list", "List all domains or methods in a domain").conflicts([
|
|
17251
17191
|
"describe",
|
|
17252
17192
|
"params"
|
|
17253
17193
|
])
|
|
17254
17194
|
).addOption(new Option("--describe", "Show method signature and parameters").conflicts("params")).addOption(
|
|
17195
|
+
new Option(
|
|
17196
|
+
"--send-anyway",
|
|
17197
|
+
"Send a Domain.method that looks like a typo of a bundled one as typed"
|
|
17198
|
+
).conflicts(["list", "describe", "search"])
|
|
17199
|
+
).addOption(
|
|
17255
17200
|
new Option("--search <query>", "Search methods by keyword").conflicts([
|
|
17256
17201
|
"list",
|
|
17257
17202
|
"describe",
|
|
@@ -17275,11 +17220,7 @@ async function runCdpCommand(method, options) {
|
|
|
17275
17220
|
return runCommand(async () => handleDescribeMethod(method), options, formatCdpDescription);
|
|
17276
17221
|
}
|
|
17277
17222
|
if (method) {
|
|
17278
|
-
return runCommand(
|
|
17279
|
-
async () => handleExecuteMethod(method, options.params),
|
|
17280
|
-
options,
|
|
17281
|
-
formatCdpResult
|
|
17282
|
-
);
|
|
17223
|
+
return runCommand(async () => handleExecuteMethod(method, options), options, formatCdpResult);
|
|
17283
17224
|
}
|
|
17284
17225
|
return runCommand(async () => {
|
|
17285
17226
|
const err = missingArgumentError(CDP_USAGE);
|
|
@@ -17433,94 +17374,97 @@ function handleListDomainMethods(domainName) {
|
|
|
17433
17374
|
}
|
|
17434
17375
|
};
|
|
17435
17376
|
}
|
|
17436
|
-
function handleDescribeMethod(
|
|
17437
|
-
const [domainName,
|
|
17438
|
-
if (!
|
|
17439
|
-
|
|
17440
|
-
|
|
17441
|
-
|
|
17442
|
-
|
|
17443
|
-
|
|
17444
|
-
|
|
17445
|
-
|
|
17446
|
-
|
|
17447
|
-
|
|
17448
|
-
|
|
17449
|
-
|
|
17450
|
-
|
|
17451
|
-
|
|
17452
|
-
|
|
17453
|
-
|
|
17454
|
-
|
|
17455
|
-
|
|
17456
|
-
|
|
17457
|
-
|
|
17458
|
-
|
|
17459
|
-
|
|
17460
|
-
|
|
17461
|
-
|
|
17462
|
-
|
|
17463
|
-
|
|
17464
|
-
commands: summary.commandCount,
|
|
17465
|
-
events: summary.eventCount,
|
|
17466
|
-
experimental: summary.experimental,
|
|
17467
|
-
deprecated: summary.deprecated,
|
|
17468
|
-
note: domainNote,
|
|
17469
|
-
nextStep: `Use: bdg cdp ${summary.name} --list (to see all methods)`
|
|
17470
|
-
}
|
|
17471
|
-
};
|
|
17472
|
-
}
|
|
17473
|
-
const schema = getMethodSchema(domainName, method);
|
|
17474
|
-
if (!schema) {
|
|
17475
|
-
const similar = findSimilarMethods(methodName, domainName);
|
|
17476
|
-
const suggestions = [`Use: bdg cdp ${domainName} --list (to see all ${domainName} methods)`];
|
|
17477
|
-
if (similar.length > 0) {
|
|
17478
|
-
suggestions.push("");
|
|
17479
|
-
suggestions.push("Did you mean:");
|
|
17480
|
-
similar.forEach((name) => suggestions.push(` - ${name}`));
|
|
17481
|
-
}
|
|
17377
|
+
function handleDescribeMethod(name) {
|
|
17378
|
+
const [domainName = "", member] = name.includes(".") ? name.split(".") : [name, void 0];
|
|
17379
|
+
if (!member) return describeDomain(name);
|
|
17380
|
+
const schema = getMethodSchema(domainName, member);
|
|
17381
|
+
if (schema) return { success: true, data: describeMethod(schema) };
|
|
17382
|
+
const type = getTypeSchema(domainName, member);
|
|
17383
|
+
if (type) return { success: true, data: { type: "type", ...type } };
|
|
17384
|
+
const target = resolveMethodTarget(name);
|
|
17385
|
+
const err = target.kind === "unlisted" ? cdpMethodNotInBundledProtocolError(target.method, getBundledProtocolVersion()) : cdpMethodNotFoundError(
|
|
17386
|
+
name,
|
|
17387
|
+
findSimilarMethods(name, domainName),
|
|
17388
|
+
`Use: bdg cdp ${domainName} --list (to see all ${domainName} methods)`
|
|
17389
|
+
);
|
|
17390
|
+
return {
|
|
17391
|
+
success: false,
|
|
17392
|
+
error: err.message,
|
|
17393
|
+
exitCode: EXIT_CODES.INVALID_ARGUMENTS,
|
|
17394
|
+
errorContext: { suggestion: err.suggestion }
|
|
17395
|
+
};
|
|
17396
|
+
}
|
|
17397
|
+
function describeDomain(domainName) {
|
|
17398
|
+
const summary = getDomainSummary(domainName);
|
|
17399
|
+
if (!summary) {
|
|
17400
|
+
const err = cdpMethodNotFoundError(
|
|
17401
|
+
domainName,
|
|
17402
|
+
findSimilarMethods(domainName),
|
|
17403
|
+
"Use: bdg cdp --list (to see all domains)"
|
|
17404
|
+
);
|
|
17482
17405
|
return {
|
|
17483
17406
|
success: false,
|
|
17484
|
-
error: `
|
|
17407
|
+
error: `Domain or method '${domainName}' not found`,
|
|
17485
17408
|
exitCode: EXIT_CODES.INVALID_ARGUMENTS,
|
|
17486
|
-
errorContext: {
|
|
17487
|
-
suggestion: suggestions.join("\n")
|
|
17488
|
-
}
|
|
17409
|
+
errorContext: { suggestion: err.suggestion }
|
|
17489
17410
|
};
|
|
17490
17411
|
}
|
|
17491
|
-
const methodNote = METHOD_NOTES[schema.name] ?? DOMAIN_NOTES[schema.domain];
|
|
17492
|
-
const alternative = blockedAlternative(schema.name);
|
|
17493
17412
|
return {
|
|
17494
17413
|
success: true,
|
|
17495
17414
|
data: {
|
|
17496
|
-
type: "
|
|
17497
|
-
|
|
17498
|
-
|
|
17499
|
-
|
|
17500
|
-
|
|
17501
|
-
experimental:
|
|
17502
|
-
deprecated:
|
|
17503
|
-
note:
|
|
17504
|
-
|
|
17505
|
-
name: p.name,
|
|
17506
|
-
type: p.type,
|
|
17507
|
-
required: p.required,
|
|
17508
|
-
description: p.description,
|
|
17509
|
-
enum: p.enum,
|
|
17510
|
-
items: p.items,
|
|
17511
|
-
deprecated: p.deprecated
|
|
17512
|
-
})),
|
|
17513
|
-
returns: schema.returns.map((r) => ({
|
|
17514
|
-
name: r.name,
|
|
17515
|
-
type: r.type,
|
|
17516
|
-
optional: r.optional,
|
|
17517
|
-
description: r.description,
|
|
17518
|
-
items: r.items
|
|
17519
|
-
})),
|
|
17520
|
-
example: alternative ? { command: alternative } : schema.example
|
|
17415
|
+
type: "domain",
|
|
17416
|
+
domain: summary.name,
|
|
17417
|
+
description: summary.description,
|
|
17418
|
+
commands: summary.commandCount,
|
|
17419
|
+
events: summary.eventCount,
|
|
17420
|
+
experimental: summary.experimental,
|
|
17421
|
+
deprecated: summary.deprecated,
|
|
17422
|
+
note: DOMAIN_NOTES[summary.name],
|
|
17423
|
+
nextStep: `Use: bdg cdp ${summary.name} --list (to see all methods)`
|
|
17521
17424
|
}
|
|
17522
17425
|
};
|
|
17523
17426
|
}
|
|
17427
|
+
function describeParameter(p) {
|
|
17428
|
+
return {
|
|
17429
|
+
name: p.name,
|
|
17430
|
+
type: p.type,
|
|
17431
|
+
required: p.required,
|
|
17432
|
+
description: p.description,
|
|
17433
|
+
enum: p.enum,
|
|
17434
|
+
ref: p.ref,
|
|
17435
|
+
refType: p.refType,
|
|
17436
|
+
items: p.items,
|
|
17437
|
+
experimental: p.experimental,
|
|
17438
|
+
deprecated: p.deprecated
|
|
17439
|
+
};
|
|
17440
|
+
}
|
|
17441
|
+
function describeMethod(schema) {
|
|
17442
|
+
const alternative = blockedAlternative(schema.name);
|
|
17443
|
+
return {
|
|
17444
|
+
type: "method",
|
|
17445
|
+
name: schema.name,
|
|
17446
|
+
domain: schema.domain,
|
|
17447
|
+
method: schema.method,
|
|
17448
|
+
description: schema.description,
|
|
17449
|
+
experimental: schema.experimental,
|
|
17450
|
+
deprecated: schema.deprecated,
|
|
17451
|
+
note: METHOD_NOTES[schema.name] ?? DOMAIN_NOTES[schema.domain],
|
|
17452
|
+
parameters: schema.parameters.map(describeParameter),
|
|
17453
|
+
returns: schema.returns.map((r) => ({
|
|
17454
|
+
name: r.name,
|
|
17455
|
+
type: r.type,
|
|
17456
|
+
optional: r.optional,
|
|
17457
|
+
description: r.description,
|
|
17458
|
+
items: r.items
|
|
17459
|
+
})),
|
|
17460
|
+
redirect: schema.redirect && {
|
|
17461
|
+
method: schema.redirect.method,
|
|
17462
|
+
resolved: schema.redirect.resolved,
|
|
17463
|
+
parameters: schema.redirect.parameters.map(describeParameter)
|
|
17464
|
+
},
|
|
17465
|
+
example: alternative ? { command: alternative } : schema.example
|
|
17466
|
+
};
|
|
17467
|
+
}
|
|
17524
17468
|
var BLOCKED_CDP_METHODS = {
|
|
17525
17469
|
"Page.captureScreenshot": {
|
|
17526
17470
|
alternative: "bdg dom screenshot [path]",
|
|
@@ -17535,38 +17479,53 @@ var BLOCKED_CDP_METHODS = {
|
|
|
17535
17479
|
reason: "Closes Chrome under the session, which then ends without saving its state"
|
|
17536
17480
|
}
|
|
17537
17481
|
};
|
|
17538
|
-
|
|
17539
|
-
const
|
|
17540
|
-
if (
|
|
17541
|
-
const blocked = BLOCKED_CDP_METHODS[
|
|
17542
|
-
|
|
17543
|
-
|
|
17544
|
-
|
|
17545
|
-
|
|
17546
|
-
|
|
17547
|
-
|
|
17548
|
-
}
|
|
17549
|
-
if (!normalized) {
|
|
17550
|
-
const similar = findSimilarMethods(methodName);
|
|
17551
|
-
const suggestions = ["Use: bdg cdp --search <keyword> (to search for methods)"];
|
|
17552
|
-
if (similar.length > 0) {
|
|
17553
|
-
suggestions.push("");
|
|
17554
|
-
suggestions.push("Did you mean:");
|
|
17555
|
-
similar.forEach((name) => suggestions.push(` - ${name}`));
|
|
17482
|
+
function methodToSend(methodName, options = {}) {
|
|
17483
|
+
const target = resolveMethodTarget(methodName);
|
|
17484
|
+
if (target.kind === "known") {
|
|
17485
|
+
const blocked = BLOCKED_CDP_METHODS[target.method];
|
|
17486
|
+
if (blocked) {
|
|
17487
|
+
throw new CommandError(
|
|
17488
|
+
`${target.method} is blocked via raw CDP: ${blocked.reason}`,
|
|
17489
|
+
{ suggestion: `Use: ${sessionCommand(blocked.alternative)}` },
|
|
17490
|
+
EXIT_CODES.INVALID_ARGUMENTS
|
|
17491
|
+
);
|
|
17556
17492
|
}
|
|
17493
|
+
return { method: target.method };
|
|
17494
|
+
}
|
|
17495
|
+
if (target.kind === "unlisted" || target.kind === "typo" && options.sendAnyway) {
|
|
17557
17496
|
return {
|
|
17558
|
-
|
|
17559
|
-
|
|
17560
|
-
exitCode: EXIT_CODES.INVALID_ARGUMENTS,
|
|
17561
|
-
errorContext: {
|
|
17562
|
-
suggestion: suggestions.join("\n")
|
|
17563
|
-
}
|
|
17497
|
+
method: target.method,
|
|
17498
|
+
warning: cdpUnlistedMethodWarning(target.method, getBundledProtocolVersion())
|
|
17564
17499
|
};
|
|
17565
17500
|
}
|
|
17501
|
+
const err = target.kind === "type" ? cdpTypeNotMethodError(target.name) : unknownMethodError(methodName, target);
|
|
17502
|
+
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
|
|
17503
|
+
}
|
|
17504
|
+
function unknownMethodError(methodName, target) {
|
|
17505
|
+
if (target.kind === "malformed") {
|
|
17506
|
+
return cdpMethodNotFoundError(
|
|
17507
|
+
methodName,
|
|
17508
|
+
findSimilarMethods(methodName),
|
|
17509
|
+
"Use: bdg cdp --search <keyword> (to search for methods)"
|
|
17510
|
+
);
|
|
17511
|
+
}
|
|
17512
|
+
const [domainName = ""] = methodName.split(".");
|
|
17513
|
+
return target.suggestions.length > 0 ? cdpMethodTypoError(
|
|
17514
|
+
methodName,
|
|
17515
|
+
target.suggestions.slice(0, 3),
|
|
17516
|
+
"Use: bdg cdp --search <keyword> (to search for methods)"
|
|
17517
|
+
) : cdpMethodTypoError(methodName, [], domainSuggestion(domainName));
|
|
17518
|
+
}
|
|
17519
|
+
function withWarning(error, warning) {
|
|
17520
|
+
if (!warning || !(error instanceof CommandError)) return error;
|
|
17521
|
+
return new CommandError(error.message, { ...error.metadata, warning }, error.exitCode);
|
|
17522
|
+
}
|
|
17523
|
+
async function handleExecuteMethod(methodName, options) {
|
|
17524
|
+
const { method: normalized, warning } = methodToSend(methodName, options);
|
|
17566
17525
|
let params;
|
|
17567
|
-
if (
|
|
17526
|
+
if (options.params) {
|
|
17568
17527
|
try {
|
|
17569
|
-
params = JSON.parse(
|
|
17528
|
+
params = JSON.parse(options.params);
|
|
17570
17529
|
} catch (error) {
|
|
17571
17530
|
return {
|
|
17572
17531
|
success: false,
|
|
@@ -17574,31 +17533,31 @@ async function handleExecuteMethod(methodName, paramsJson) {
|
|
|
17574
17533
|
exitCode: EXIT_CODES.INVALID_ARGUMENTS,
|
|
17575
17534
|
errorContext: {
|
|
17576
17535
|
suggestion: `Use: bdg cdp ${normalized} --describe (to see parameter schema)`
|
|
17577
|
-
}
|
|
17536
|
+
},
|
|
17537
|
+
...warning && { warning }
|
|
17578
17538
|
};
|
|
17579
17539
|
}
|
|
17580
17540
|
}
|
|
17581
17541
|
const response = await callCDP(normalized, params);
|
|
17582
|
-
|
|
17583
|
-
|
|
17542
|
+
try {
|
|
17543
|
+
validateIPCResponse(response);
|
|
17544
|
+
} catch (error) {
|
|
17545
|
+
throw withWarning(error, warning);
|
|
17546
|
+
}
|
|
17547
|
+
const ipcHint = response.data?.hint && formatHint(response.data.hint);
|
|
17548
|
+
return cdpCallResult(normalized, response.data?.result, warning, ipcHint);
|
|
17549
|
+
}
|
|
17550
|
+
function cdpCallResult(method, cdpResult, warning, ipcHint) {
|
|
17551
|
+
const withCallWarning = warning ? { warning } : {};
|
|
17584
17552
|
const exception = pageExceptionResult(cdpResult);
|
|
17585
|
-
if (exception) return exception;
|
|
17586
|
-
const
|
|
17553
|
+
if (exception) return { ...exception, ...withCallWarning };
|
|
17554
|
+
const hints = [ipcHint, getMethodHint(method, cdpResult)].filter(Boolean);
|
|
17555
|
+
return {
|
|
17587
17556
|
success: true,
|
|
17588
|
-
data: {
|
|
17589
|
-
|
|
17590
|
-
|
|
17591
|
-
}
|
|
17557
|
+
data: { method, result: cdpResult },
|
|
17558
|
+
...withCallWarning,
|
|
17559
|
+
...hints.length > 0 && { hint: hints.join("\n") }
|
|
17592
17560
|
};
|
|
17593
|
-
if (response.data?.hint) {
|
|
17594
|
-
result.hint = formatHint(response.data.hint);
|
|
17595
|
-
}
|
|
17596
|
-
const methodHint = getMethodHint(normalized, cdpResult);
|
|
17597
|
-
if (methodHint) {
|
|
17598
|
-
result.hint = result.hint ? `${result.hint}
|
|
17599
|
-
${methodHint}` : methodHint;
|
|
17600
|
-
}
|
|
17601
|
-
return result;
|
|
17602
17561
|
}
|
|
17603
17562
|
|
|
17604
17563
|
// src/commands/cleanup.ts
|
|
@@ -17701,6 +17660,7 @@ function formatCleanup(data) {
|
|
|
17701
17660
|
return joinLines(
|
|
17702
17661
|
cleaned.session && sessionFilesCleanedMessage(),
|
|
17703
17662
|
cleaned.output && sessionOutputRemovedMessage(),
|
|
17663
|
+
data.downloadsKept && downloadsKeptMessage(data.downloadsKept.dir, data.downloadsKept.files),
|
|
17704
17664
|
...(data.warnings ?? []).map((warning) => warningMessage(warning)),
|
|
17705
17665
|
"",
|
|
17706
17666
|
data.message
|
|
@@ -17789,18 +17749,26 @@ async function cleanupSession(opts) {
|
|
|
17789
17749
|
};
|
|
17790
17750
|
}
|
|
17791
17751
|
const didCleanup = Object.values(cleaned).some(Boolean) || purged !== void 0;
|
|
17752
|
+
const downloadsKept = purged === void 0 ? keptDownloads() : void 0;
|
|
17792
17753
|
return {
|
|
17793
17754
|
success: true,
|
|
17794
17755
|
data: {
|
|
17795
17756
|
cleaned,
|
|
17796
17757
|
...purged !== void 0 && { purged },
|
|
17758
|
+
...downloadsKept && { downloadsKept },
|
|
17797
17759
|
message: !didCleanup ? noSessionFilesMessage() : purged !== void 0 ? sessionDirectoryPurgedMessage(purged) : sessionDirectoryCleanMessage(),
|
|
17798
17760
|
...warnings.length > 0 && { warnings }
|
|
17799
17761
|
}
|
|
17800
17762
|
};
|
|
17801
17763
|
}
|
|
17764
|
+
function keptDownloads() {
|
|
17765
|
+
const dir = getSessionDownloadsDir();
|
|
17766
|
+
if (!fs14.existsSync(dir) || !fs14.statSync(dir).isDirectory()) return void 0;
|
|
17767
|
+
const files = fs14.readdirSync(dir).length;
|
|
17768
|
+
return files > 0 ? { dir, files } : void 0;
|
|
17769
|
+
}
|
|
17802
17770
|
function registerCleanupCommand(program2) {
|
|
17803
|
-
program2.command("cleanup").description("Clean up stale session files").option("-f, --force", "Kill a running (possibly hung) session, then clean up", false).option("--remove-output", "Also remove session.json output file", false).option("--aggressive", "Alias for --force (kept for compatibility)", false).option(
|
|
17771
|
+
program2.command("cleanup").description("Clean up stale session files (downloaded files are kept)").option("-f, --force", "Kill a running (possibly hung) session, then clean up", false).option("--remove-output", "Also remove session.json output file", false).option("--aggressive", "Alias for --force (kept for compatibility)", false).option(
|
|
17804
17772
|
"--purge",
|
|
17805
17773
|
"Also delete a named session's directory (Chrome profile, logs, port); needs --session",
|
|
17806
17774
|
false
|
|
@@ -18717,7 +18685,11 @@ function megabytes(bytes) {
|
|
|
18717
18685
|
function networkEvictedNote(counts) {
|
|
18718
18686
|
const { requestsDropped, bodiesEvicted } = counts;
|
|
18719
18687
|
const requests = pluralize(requestsDropped, "older network request");
|
|
18720
|
-
const bodies = pluralize(
|
|
18688
|
+
const bodies = pluralize(
|
|
18689
|
+
bodiesEvicted,
|
|
18690
|
+
"older request/response body",
|
|
18691
|
+
"older request/response bodies"
|
|
18692
|
+
);
|
|
18721
18693
|
const budget = megabytes(MAX_TOTAL_BODY_BYTES);
|
|
18722
18694
|
if (requestsDropped > 0 && bodiesEvicted > 0) {
|
|
18723
18695
|
return `\u26A0 ${requests} dropped, ${bodies} evicted: bdg keeps the newest ${MAX_NETWORK_REQUESTS} requests and ${budget} of bodies`;
|
|
@@ -18742,6 +18714,15 @@ function headerRepeatedNote(count) {
|
|
|
18742
18714
|
function localProxyNote() {
|
|
18743
18715
|
return "(loopback; likely a local proxy)";
|
|
18744
18716
|
}
|
|
18717
|
+
function harSanitizedComment() {
|
|
18718
|
+
return "Sanitized by bdg: values of auth, cookie, API key, token and session headers, cookies, credential query parameters in URLs, password/token/secret fields of JSON and form request and response bodies and WebSocket messages (also truncated JSON, JSON encoded in strings, socket.io, SockJS, server-sent events, NDJSON, and base64 bodies and binary messages that are UTF-8 text), and any JWT (also inside longer strings and form values) are [redacted] in place (by name, so some harmless values are too); everything else stays byte for byte. Not sanitized: other binary data, text that is not JSON or a form, and non-JSON syntax (single quotes, unquoted keys, JSONP, bare values with spaces); a body that could not be sanitized is [redacted] whole. Export with --include-sensitive to keep everything";
|
|
18719
|
+
}
|
|
18720
|
+
function harExportedMessage(result) {
|
|
18721
|
+
const filterNote = result.filtered ? " (filtered)" : "";
|
|
18722
|
+
const note = result.sanitized ? "Credentials sanitized (auth/cookie/API key/token headers, cookies, URL tokens, password and token fields of bodies and WebSocket messages are [redacted]); --include-sensitive keeps them" : "\u26A0 Includes credentials (--include-sensitive): share this file with care";
|
|
18723
|
+
return `\u2713 Exported ${result.entries} requests${filterNote} to ${result.file}
|
|
18724
|
+
${note}`;
|
|
18725
|
+
}
|
|
18745
18726
|
|
|
18746
18727
|
// src/telemetry/networkRetention.ts
|
|
18747
18728
|
var SKIPPED_BODY_PATTERN = /^\[SKIPPED: (.*)\]$/s;
|
|
@@ -18854,10 +18835,6 @@ function addWebSocketMessages(fmt, webSocket) {
|
|
|
18854
18835
|
webSocket.frames.forEach((frame) => fmt.text(formatWebSocketMessage(frame)));
|
|
18855
18836
|
}
|
|
18856
18837
|
var BODY_PREVIEW_LENGTH = 2e4;
|
|
18857
|
-
function formatBytes(bytes) {
|
|
18858
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
18859
|
-
return bytes < 1024 * 1024 ? `${(bytes / 1024).toFixed(1)} KB` : `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
18860
|
-
}
|
|
18861
18838
|
function isLoopback(ip) {
|
|
18862
18839
|
const bare = ip.replace(/^\[|\]$/g, "").toLowerCase();
|
|
18863
18840
|
return /^(::ffff:)?127\./.test(bare) || bare === "::1";
|
|
@@ -18933,7 +18910,11 @@ function formatNetworkDetails(request) {
|
|
|
18933
18910
|
fmt.keyValueList(requestSummaryRows(request));
|
|
18934
18911
|
fmt.blank();
|
|
18935
18912
|
if (request.requestHeaders) addHeaders(fmt, "Request Headers:", request.requestHeaders);
|
|
18936
|
-
if (request.
|
|
18913
|
+
if (request.requestBodyNotCaptured) {
|
|
18914
|
+
fmt.text("Request Body:").separator("\u2501", 70);
|
|
18915
|
+
fmt.text(`(not captured: ${request.requestBodyNotCaptured})`);
|
|
18916
|
+
fmt.blank();
|
|
18917
|
+
} else if (request.requestBody) {
|
|
18937
18918
|
fmt.text("Request Body:").separator("\u2501", 70);
|
|
18938
18919
|
fmt.text(request.requestBody);
|
|
18939
18920
|
fmt.blank();
|
|
@@ -19711,6 +19692,8 @@ function formatActionOutput(done, details, result, options = {}) {
|
|
|
19711
19692
|
if (requests.length > 0) {
|
|
19712
19693
|
fmt.blank().section(formatTriggeredRequestsTitle(result.triggeredRequests ?? [], omitted), requests);
|
|
19713
19694
|
}
|
|
19695
|
+
if (result.downloads?.length) fmt.blank();
|
|
19696
|
+
for (const download of result.downloads ?? []) fmt.text(downloadText(download));
|
|
19714
19697
|
for (const dialog of result.dialogs ?? []) {
|
|
19715
19698
|
fmt.blank();
|
|
19716
19699
|
fmt.text(`Dialog: ${dialogConsoleText(dialog)}`);
|
|
@@ -19912,9 +19895,9 @@ init_url();
|
|
|
19912
19895
|
var MAX_VALUE_LENGTH2 = 40;
|
|
19913
19896
|
function fileName(src) {
|
|
19914
19897
|
if (/^(data|blob):/i.test(src)) return src;
|
|
19915
|
-
const
|
|
19916
|
-
const name =
|
|
19917
|
-
return name && name !==
|
|
19898
|
+
const path8 = src.split(/[?#]/)[0] ?? src;
|
|
19899
|
+
const name = path8.substring(path8.lastIndexOf("/") + 1);
|
|
19900
|
+
return name && name !== path8 ? `\u2026/${name}` : src;
|
|
19918
19901
|
}
|
|
19919
19902
|
function hostOf(src) {
|
|
19920
19903
|
const parsed = /^https?:/i.test(src) ? safeParseUrl(src) : null;
|
|
@@ -20043,7 +20026,7 @@ init_elementInfo();
|
|
|
20043
20026
|
init_frameScopedConnection();
|
|
20044
20027
|
init_logging();
|
|
20045
20028
|
init_errors();
|
|
20046
|
-
var
|
|
20029
|
+
var log24 = createLogger("dom");
|
|
20047
20030
|
function childFrameIds(tree) {
|
|
20048
20031
|
return (tree?.childFrames ?? []).flatMap((child) => [child.frame.id, ...childFrameIds(child)]);
|
|
20049
20032
|
}
|
|
@@ -20765,9 +20748,34 @@ async function audit(checks, options) {
|
|
|
20765
20748
|
return { success: true, data: response.data };
|
|
20766
20749
|
}
|
|
20767
20750
|
|
|
20751
|
+
// src/commands/dom/helpers/evalResult.ts
|
|
20752
|
+
init_constants();
|
|
20753
|
+
init_strings();
|
|
20754
|
+
function boundEvalResult(value, length) {
|
|
20755
|
+
if (typeof value === "string") {
|
|
20756
|
+
const { text, truncatedFrom } = capLength(value, MAX_VALUE_LENGTH);
|
|
20757
|
+
return { result: text, ...truncatedFrom !== void 0 && { truncatedFrom } };
|
|
20758
|
+
}
|
|
20759
|
+
if (Array.isArray(value)) return boundArray(value, length ?? value.length);
|
|
20760
|
+
if (value === null || typeof value !== "object") return { result: value };
|
|
20761
|
+
const json = JSON.stringify(value);
|
|
20762
|
+
return json.length > MAX_VALUE_LENGTH ? jsonStart(json) : { result: value };
|
|
20763
|
+
}
|
|
20764
|
+
function boundArray(value, count) {
|
|
20765
|
+
const listed = value.slice(0, EVAL_JSON_ARRAY_LIMIT);
|
|
20766
|
+
const listedJson = JSON.stringify(listed);
|
|
20767
|
+
if (listedJson.length > MAX_VALUE_LENGTH) {
|
|
20768
|
+
const json = listed.length === value.length ? listedJson : JSON.stringify(value);
|
|
20769
|
+
return { ...jsonStart(json), count };
|
|
20770
|
+
}
|
|
20771
|
+
return listed.length < count ? { result: listed, count, omitted: count - listed.length } : { result: value };
|
|
20772
|
+
}
|
|
20773
|
+
function jsonStart(json) {
|
|
20774
|
+
return { result: capLength(json, MAX_VALUE_LENGTH).text, truncatedFrom: json.length };
|
|
20775
|
+
}
|
|
20776
|
+
|
|
20768
20777
|
// src/commands/dom/eval.ts
|
|
20769
20778
|
init_query();
|
|
20770
|
-
init_constants();
|
|
20771
20779
|
init_messages();
|
|
20772
20780
|
init_client();
|
|
20773
20781
|
|
|
@@ -20861,7 +20869,6 @@ function formatDomScreenshot(data) {
|
|
|
20861
20869
|
// src/commands/dom/eval.ts
|
|
20862
20870
|
init_commands();
|
|
20863
20871
|
init_exitCodes();
|
|
20864
|
-
init_strings();
|
|
20865
20872
|
async function handleDomEval(script, options) {
|
|
20866
20873
|
await runCommand(
|
|
20867
20874
|
async () => {
|
|
@@ -20874,7 +20881,7 @@ async function handleDomEval(script, options) {
|
|
|
20874
20881
|
errorContext: { suggestion: err.suggestion }
|
|
20875
20882
|
};
|
|
20876
20883
|
}
|
|
20877
|
-
const response = await domEval(script, options.frame);
|
|
20884
|
+
const response = await domEval(script, options.frame, options.full);
|
|
20878
20885
|
if (response.status === "error" || !response.data) {
|
|
20879
20886
|
const suggestion = await frameErrorSuggestion(response, options.frame);
|
|
20880
20887
|
return {
|
|
@@ -20884,7 +20891,7 @@ async function handleDomEval(script, options) {
|
|
|
20884
20891
|
...suggestion && { errorContext: { suggestion } }
|
|
20885
20892
|
};
|
|
20886
20893
|
}
|
|
20887
|
-
const { value, type, subtype, frame, warning } = response.data;
|
|
20894
|
+
const { value, type, subtype, length, frame, warning } = response.data;
|
|
20888
20895
|
const hint = [
|
|
20889
20896
|
...frame !== void 0 ? [evalFrameLine(frame)] : [],
|
|
20890
20897
|
...warning ? [warningMessage(warning)] : []
|
|
@@ -20892,7 +20899,7 @@ async function handleDomEval(script, options) {
|
|
|
20892
20899
|
return {
|
|
20893
20900
|
success: true,
|
|
20894
20901
|
data: {
|
|
20895
|
-
...
|
|
20902
|
+
...options.json && !options.full ? boundEvalResult(value, length) : { result: value },
|
|
20896
20903
|
type,
|
|
20897
20904
|
...subtype && { subtype },
|
|
20898
20905
|
...frame !== void 0 && { frame },
|
|
@@ -20905,11 +20912,6 @@ async function handleDomEval(script, options) {
|
|
|
20905
20912
|
(data) => formatDomEval(data, { full: options.full })
|
|
20906
20913
|
);
|
|
20907
20914
|
}
|
|
20908
|
-
function jsonResult(value, options) {
|
|
20909
|
-
if (!options.json || options.full || typeof value !== "string") return { result: value };
|
|
20910
|
-
const { text, truncatedFrom } = capLength(value, MAX_VALUE_LENGTH);
|
|
20911
|
-
return { result: text, ...truncatedFrom !== void 0 && { truncatedFrom } };
|
|
20912
|
-
}
|
|
20913
20915
|
async function frameErrorSuggestion(response, frame) {
|
|
20914
20916
|
if (frame === void 0 || response.exitCode !== EXIT_CODES.RESOURCE_NOT_FOUND) {
|
|
20915
20917
|
return response.suggestion;
|
|
@@ -21227,7 +21229,7 @@ function formatFormDiscovery(result) {
|
|
|
21227
21229
|
init_logging();
|
|
21228
21230
|
init_errors();
|
|
21229
21231
|
init_exitCodes();
|
|
21230
|
-
var
|
|
21232
|
+
var log25 = createLogger("dom");
|
|
21231
21233
|
function buildValidation(raw) {
|
|
21232
21234
|
const hasNativeError = !raw.isValid && !raw.valueMissing && raw.validationMessage;
|
|
21233
21235
|
const hasAriaError = raw.ariaInvalid;
|
|
@@ -21384,7 +21386,7 @@ async function cacheFormElements(forms, document2) {
|
|
|
21384
21386
|
const elements = forms.flatMap((form) => [...form.fields, ...form.buttons]);
|
|
21385
21387
|
const backendNodeIds = await resolveBackendNodeIds(elements.map((el) => el.selector)).catch(
|
|
21386
21388
|
(error) => {
|
|
21387
|
-
|
|
21389
|
+
log25.debug(`Form fields not cached by node: ${getErrorMessage(error)}`);
|
|
21388
21390
|
return [];
|
|
21389
21391
|
}
|
|
21390
21392
|
);
|
|
@@ -21400,7 +21402,7 @@ async function cacheFormElements(forms, document2) {
|
|
|
21400
21402
|
},
|
|
21401
21403
|
document2
|
|
21402
21404
|
);
|
|
21403
|
-
|
|
21405
|
+
log25.debug(`Cached ${elements.length} form elements`);
|
|
21404
21406
|
}
|
|
21405
21407
|
async function handleFormCommand(options) {
|
|
21406
21408
|
await runCommand(
|
|
@@ -22378,6 +22380,7 @@ function listedMatches(result, limit) {
|
|
|
22378
22380
|
// src/commands/dom/screenshot.ts
|
|
22379
22381
|
import { extname as extname2 } from "path";
|
|
22380
22382
|
init_helpers();
|
|
22383
|
+
init_interrupt();
|
|
22381
22384
|
init_outputFile();
|
|
22382
22385
|
init_errors3();
|
|
22383
22386
|
init_messages();
|
|
@@ -22386,7 +22389,7 @@ init_logging();
|
|
|
22386
22389
|
init_directories();
|
|
22387
22390
|
init_errors();
|
|
22388
22391
|
init_exitCodes();
|
|
22389
|
-
var
|
|
22392
|
+
var log26 = createLogger("dom");
|
|
22390
22393
|
var EXTENSION_FORMATS = {
|
|
22391
22394
|
".png": "png",
|
|
22392
22395
|
".jpg": "jpeg",
|
|
@@ -22407,34 +22410,27 @@ function resolveImageFormat(outputPath, requested) {
|
|
|
22407
22410
|
}
|
|
22408
22411
|
return requested ?? fromExtension ?? "png";
|
|
22409
22412
|
}
|
|
22410
|
-
function
|
|
22411
|
-
|
|
22412
|
-
format: options.format,
|
|
22413
|
-
quality: options.quality,
|
|
22414
|
-
|
|
22415
|
-
|
|
22416
|
-
|
|
22417
|
-
|
|
22418
|
-
}
|
|
22419
|
-
|
|
22420
|
-
return filterDefined({
|
|
22421
|
-
format: options.format,
|
|
22422
|
-
quality: options.quality,
|
|
22423
|
-
noResize: options.resize === false,
|
|
22424
|
-
padding: options.padding
|
|
22425
|
-
});
|
|
22413
|
+
function buildScreenshotRequest(options, backendNodeId) {
|
|
22414
|
+
const shared = {
|
|
22415
|
+
format: options.format ?? "png",
|
|
22416
|
+
...filterDefined({ quality: options.quality }),
|
|
22417
|
+
...options.resize === false && { noResize: true }
|
|
22418
|
+
};
|
|
22419
|
+
if (backendNodeId !== void 0) {
|
|
22420
|
+
return { ...shared, backendNodeId, ...filterDefined({ padding: options.padding }) };
|
|
22421
|
+
}
|
|
22422
|
+
return { ...shared, ...filterDefined({ fullPage: options.fullPage, scroll: options.scroll }) };
|
|
22426
22423
|
}
|
|
22427
22424
|
function hasElementTarget(options) {
|
|
22428
22425
|
return options.selector !== void 0 || options.index !== void 0;
|
|
22429
22426
|
}
|
|
22430
22427
|
async function resolveElementNodeId(options) {
|
|
22431
22428
|
if (options.selector !== void 0 && options.index !== void 0) {
|
|
22432
|
-
return
|
|
22429
|
+
return selectMatch(options.selector, options.index);
|
|
22433
22430
|
}
|
|
22434
22431
|
if (options.index !== void 0) {
|
|
22435
|
-
const
|
|
22436
|
-
|
|
22437
|
-
return { backendNodeId: node.nodeId };
|
|
22432
|
+
const node = await DomElementResolver.getInstance().getNodeIdForIndex(options.index);
|
|
22433
|
+
return node.nodeId;
|
|
22438
22434
|
}
|
|
22439
22435
|
if (options.selector !== void 0) {
|
|
22440
22436
|
return resolveSelector(options.selector);
|
|
@@ -22477,10 +22473,11 @@ function formatFrameFilename(frameNumber, format) {
|
|
|
22477
22473
|
return `${String(frameNumber).padStart(3, "0")}.${format}`;
|
|
22478
22474
|
}
|
|
22479
22475
|
async function handlePageScreenshot(outputPath, options) {
|
|
22476
|
+
const interrupt = abortOnInterrupt();
|
|
22480
22477
|
await runCommand(
|
|
22481
22478
|
async () => {
|
|
22482
|
-
const
|
|
22483
|
-
const result = await
|
|
22479
|
+
const request = buildScreenshotRequest(options);
|
|
22480
|
+
const result = await captureScreenshot(outputPath, request, interrupt);
|
|
22484
22481
|
return { success: true, data: result };
|
|
22485
22482
|
},
|
|
22486
22483
|
options,
|
|
@@ -22488,12 +22485,14 @@ async function handlePageScreenshot(outputPath, options) {
|
|
|
22488
22485
|
);
|
|
22489
22486
|
}
|
|
22490
22487
|
async function handleElementScreenshot(outputPath, options) {
|
|
22488
|
+
const interrupt = abortOnInterrupt();
|
|
22491
22489
|
await runCommand(
|
|
22492
22490
|
async () => {
|
|
22493
|
-
const
|
|
22494
|
-
const
|
|
22495
|
-
const
|
|
22496
|
-
const
|
|
22491
|
+
const lookup = resolveElementNodeId(options);
|
|
22492
|
+
const backendNodeId = await unlessInterrupted(lookup, interrupt, screenshotInterrupted);
|
|
22493
|
+
const request = buildScreenshotRequest(options, backendNodeId);
|
|
22494
|
+
const shot = await captureScreenshot(outputPath, request, interrupt);
|
|
22495
|
+
const elementResult = addElementInfo(shot, options);
|
|
22497
22496
|
return { success: true, data: elementResult };
|
|
22498
22497
|
},
|
|
22499
22498
|
options,
|
|
@@ -22501,19 +22500,13 @@ async function handleElementScreenshot(outputPath, options) {
|
|
|
22501
22500
|
);
|
|
22502
22501
|
}
|
|
22503
22502
|
async function captureSequenceFrame(outputPath, options) {
|
|
22504
|
-
|
|
22505
|
-
|
|
22506
|
-
const elementOptions = buildElementScreenshotOptions(options);
|
|
22507
|
-
await captureElementScreenshot(outputPath, nodeRef, elementOptions);
|
|
22508
|
-
} else {
|
|
22509
|
-
const pageOptions = buildPageScreenshotOptions(options);
|
|
22510
|
-
await capturePageScreenshot(outputPath, pageOptions);
|
|
22511
|
-
}
|
|
22503
|
+
const backendNodeId = hasElementTarget(options) ? await resolveElementNodeId(options) : void 0;
|
|
22504
|
+
await captureScreenshot(outputPath, buildScreenshotRequest(options, backendNodeId));
|
|
22512
22505
|
}
|
|
22513
22506
|
async function handleSequenceCapture(outputDir, options) {
|
|
22514
22507
|
const fs19 = await import("fs");
|
|
22515
|
-
const
|
|
22516
|
-
const absoluteDir =
|
|
22508
|
+
const path8 = await import("path");
|
|
22509
|
+
const absoluteDir = path8.resolve(outputDir);
|
|
22517
22510
|
ensureDirectory(absoluteDir, fs19);
|
|
22518
22511
|
const intervalRule = positiveIntRule({ name: "--interval", min: 100, max: 6e4, default: 1e3 });
|
|
22519
22512
|
const limitRule = positiveIntRule({ name: "--limit", min: 1, max: 1e4, required: false });
|
|
@@ -22529,7 +22522,7 @@ async function handleSequenceCapture(outputDir, options) {
|
|
|
22529
22522
|
while (!stopping) {
|
|
22530
22523
|
frameCount++;
|
|
22531
22524
|
const filename = formatFrameFilename(frameCount, format);
|
|
22532
|
-
const outputPath =
|
|
22525
|
+
const outputPath = path8.join(absoluteDir, filename);
|
|
22533
22526
|
try {
|
|
22534
22527
|
await captureSequenceFrame(outputPath, options);
|
|
22535
22528
|
} catch (error) {
|
|
@@ -22538,7 +22531,7 @@ async function handleSequenceCapture(outputDir, options) {
|
|
|
22538
22531
|
if (options.json) {
|
|
22539
22532
|
console.log(JSON.stringify(buildSuccessResponse({ frame: frameCount, path: outputPath })));
|
|
22540
22533
|
} else {
|
|
22541
|
-
|
|
22534
|
+
log26.info(`Frame ${frameCount}: ${filename}`);
|
|
22542
22535
|
}
|
|
22543
22536
|
if (limit > 0 && frameCount >= limit) break;
|
|
22544
22537
|
await delay2(interval);
|
|
@@ -22721,8 +22714,8 @@ function registerDomCommands(program2) {
|
|
|
22721
22714
|
"Image format: png or jpeg/jpg (default: from the file extension, else png)",
|
|
22722
22715
|
screenshotFormatOption
|
|
22723
22716
|
).option("--quality <number>", "JPEG quality 0-100 (default: 90)", integerOption(0, 100)).option("--no-full-page", "Capture viewport only (default: full page)").option("--no-resize", "Disable auto-resize (full resolution)").option("--scroll <selector>", "Scroll element into view before capture").option("-f, --follow", "Continuous capture mode to directory").option("--interval <ms>", "Capture interval for --follow (default: 1000)").option("--limit <count>", "Max frames for --follow").option("-j, --json", "Output as JSON").action(
|
|
22724
|
-
async (
|
|
22725
|
-
await handleDomScreenshot(
|
|
22717
|
+
async (path8, target, options) => {
|
|
22718
|
+
await handleDomScreenshot(path8, target, options);
|
|
22726
22719
|
}
|
|
22727
22720
|
);
|
|
22728
22721
|
}
|
|
@@ -22730,7 +22723,7 @@ function registerDomCommands(program2) {
|
|
|
22730
22723
|
// src/commands/installSkill.ts
|
|
22731
22724
|
import { existsSync as existsSync11, mkdirSync as mkdirSync2, readFileSync as readFileSync8, renameSync as renameSync2, rmSync as rmSync6, writeFileSync as writeFileSync2 } from "fs";
|
|
22732
22725
|
import { homedir as homedir3 } from "os";
|
|
22733
|
-
import { dirname as
|
|
22726
|
+
import { dirname as dirname7, join as join8 } from "path";
|
|
22734
22727
|
init_errors3();
|
|
22735
22728
|
init_messages();
|
|
22736
22729
|
|
|
@@ -22739,9 +22732,9 @@ init_formatting();
|
|
|
22739
22732
|
init_commands();
|
|
22740
22733
|
import { homedir as homedir2 } from "os";
|
|
22741
22734
|
var PATH_INDENT = " ".repeat(2 + 6 + 2 + 9 + 2);
|
|
22742
|
-
function tildePath(
|
|
22735
|
+
function tildePath(path8) {
|
|
22743
22736
|
const home = homedir2();
|
|
22744
|
-
return
|
|
22737
|
+
return path8.startsWith(`${home}/`) ? `~${path8.slice(home.length)}` : path8;
|
|
22745
22738
|
}
|
|
22746
22739
|
function addSkillTargets(fmt, skills) {
|
|
22747
22740
|
fmt.text("bdg skill:");
|
|
@@ -22765,11 +22758,11 @@ function formatInstalledSkills(data) {
|
|
|
22765
22758
|
init_logging();
|
|
22766
22759
|
init_errors();
|
|
22767
22760
|
init_exitCodes();
|
|
22768
|
-
var
|
|
22769
|
-
var SKILL_SOURCE_PATH =
|
|
22761
|
+
var log27 = createLogger("bdg");
|
|
22762
|
+
var SKILL_SOURCE_PATH = join8(PACKAGE_ROOT, ".claude", "skills", "bdg", "SKILL.md");
|
|
22770
22763
|
var SKILL_ROOTS = {
|
|
22771
|
-
claude:
|
|
22772
|
-
agents:
|
|
22764
|
+
claude: join8(".claude", "skills"),
|
|
22765
|
+
agents: join8(".agents", "skills")
|
|
22773
22766
|
};
|
|
22774
22767
|
function installSkill(targets, home = homedir3(), source = SKILL_SOURCE_PATH) {
|
|
22775
22768
|
if (!existsSync11(source)) {
|
|
@@ -22784,7 +22777,7 @@ function installSkill(targets, home = homedir3(), source = SKILL_SOURCE_PATH) {
|
|
|
22784
22777
|
const skills = [];
|
|
22785
22778
|
const failures = [];
|
|
22786
22779
|
for (const target of targets) {
|
|
22787
|
-
const result = writeSkill(target,
|
|
22780
|
+
const result = writeSkill(target, join8(home, SKILL_ROOTS[target], "bdg", "SKILL.md"), content);
|
|
22788
22781
|
if ("error" in result) failures.push(result);
|
|
22789
22782
|
else skills.push(result);
|
|
22790
22783
|
}
|
|
@@ -22793,10 +22786,10 @@ function installSkill(targets, home = homedir3(), source = SKILL_SOURCE_PATH) {
|
|
|
22793
22786
|
}
|
|
22794
22787
|
function skillFailureError(failures, targets) {
|
|
22795
22788
|
const other = failures.length === 1 ? targets.find((t) => t !== failures[0]?.target) : void 0;
|
|
22796
|
-
const errors = failures.map(({ path:
|
|
22789
|
+
const errors = failures.map(({ path: path8, error }) => {
|
|
22797
22790
|
const code = error.code;
|
|
22798
22791
|
return skillWriteFailedError(
|
|
22799
|
-
|
|
22792
|
+
path8,
|
|
22800
22793
|
getErrorMessage(error),
|
|
22801
22794
|
code === "ENOTDIR" || code === "EEXIST",
|
|
22802
22795
|
other && `--${other}`
|
|
@@ -22808,30 +22801,30 @@ function skillFailureError(failures, targets) {
|
|
|
22808
22801
|
EXIT_CODES.PERMISSION_DENIED
|
|
22809
22802
|
);
|
|
22810
22803
|
}
|
|
22811
|
-
function writeSkill(target,
|
|
22812
|
-
const existing = existsSync11(
|
|
22804
|
+
function writeSkill(target, path8, content) {
|
|
22805
|
+
const existing = existsSync11(path8) ? readFileSync8(path8, "utf-8") : void 0;
|
|
22813
22806
|
if (existing === content) {
|
|
22814
|
-
return { target, path:
|
|
22807
|
+
return { target, path: path8, status: "unchanged" };
|
|
22815
22808
|
}
|
|
22816
|
-
const backup = existing === void 0 ? void 0 : `${
|
|
22817
|
-
const temporary = `${
|
|
22818
|
-
let writing =
|
|
22809
|
+
const backup = existing === void 0 ? void 0 : `${path8}.bak`;
|
|
22810
|
+
const temporary = `${path8}.tmp`;
|
|
22811
|
+
let writing = path8;
|
|
22819
22812
|
try {
|
|
22820
|
-
mkdirSync2(
|
|
22813
|
+
mkdirSync2(dirname7(path8), { recursive: true });
|
|
22821
22814
|
writeFileSync2(temporary, content);
|
|
22822
22815
|
if (backup !== void 0) {
|
|
22823
22816
|
writing = backup;
|
|
22824
22817
|
rmSync6(backup, { force: true });
|
|
22825
22818
|
writeFileSync2(backup, existing ?? "");
|
|
22826
22819
|
}
|
|
22827
|
-
writing =
|
|
22828
|
-
renameSync2(temporary,
|
|
22820
|
+
writing = path8;
|
|
22821
|
+
renameSync2(temporary, path8);
|
|
22829
22822
|
} catch (error) {
|
|
22830
|
-
safeRemoveFile(temporary, "temporary skill copy",
|
|
22823
|
+
safeRemoveFile(temporary, "temporary skill copy", log27);
|
|
22831
22824
|
return { target, path: writing, error };
|
|
22832
22825
|
}
|
|
22833
|
-
if (backup === void 0) return { target, path:
|
|
22834
|
-
return { target, path:
|
|
22826
|
+
if (backup === void 0) return { target, path: path8, status: "installed" };
|
|
22827
|
+
return { target, path: path8, status: "updated", backup };
|
|
22835
22828
|
}
|
|
22836
22829
|
function selectedTargets(options) {
|
|
22837
22830
|
const picked = Object.keys(SKILL_ROOTS).filter((target) => options[target]);
|
|
@@ -22862,7 +22855,7 @@ function registerInstallSkillCommand(program2) {
|
|
|
22862
22855
|
|
|
22863
22856
|
// src/commands/network/har.ts
|
|
22864
22857
|
import * as fs16 from "fs";
|
|
22865
|
-
import * as
|
|
22858
|
+
import * as path7 from "path";
|
|
22866
22859
|
init_outputFile();
|
|
22867
22860
|
init_client();
|
|
22868
22861
|
init_paths();
|
|
@@ -22982,7 +22975,7 @@ function filterByResourceType(requests, types) {
|
|
|
22982
22975
|
init_logging();
|
|
22983
22976
|
init_suggestions();
|
|
22984
22977
|
init_url();
|
|
22985
|
-
var
|
|
22978
|
+
var log28 = createLogger("network");
|
|
22986
22979
|
var SIZE_UNITS = {
|
|
22987
22980
|
b: 1,
|
|
22988
22981
|
kb: 1024,
|
|
@@ -23092,7 +23085,7 @@ function validateSizeValue(value) {
|
|
|
23092
23085
|
parseSize(value);
|
|
23093
23086
|
return null;
|
|
23094
23087
|
} catch (error) {
|
|
23095
|
-
|
|
23088
|
+
log28.debug(
|
|
23096
23089
|
`Size parse failed for "${value}": ${error instanceof Error ? error.message : String(error)}`
|
|
23097
23090
|
);
|
|
23098
23091
|
return createError(
|
|
@@ -23278,27 +23271,359 @@ Multiple filters:
|
|
|
23278
23271
|
|
|
23279
23272
|
// src/telemetry/har/builder.ts
|
|
23280
23273
|
import { createRequire as createRequire2 } from "node:module";
|
|
23274
|
+
|
|
23275
|
+
// src/telemetry/har/sanitizeBody.ts
|
|
23276
|
+
init_elementInfo();
|
|
23277
|
+
init_logging();
|
|
23278
|
+
init_errors();
|
|
23279
|
+
var log29 = createLogger("network");
|
|
23280
|
+
var REDACTED = "[redacted]";
|
|
23281
|
+
var SENSITIVE_FIELD = new RegExp(
|
|
23282
|
+
`${SENSITIVE_NAME_SOURCE}|token|secret|api[-_]?key|authoriz|credential|jwt|private[-_]?key|access[-_]?key|session|signature|bearer|cookie|csrf|xsrf|refresh|oauth|(^|[^a-z])(auth|sid)([^a-z]|$)|auth[-_]?code|code[-_]?verifier`,
|
|
23283
|
+
"i"
|
|
23284
|
+
);
|
|
23285
|
+
var FORM_BODY = /^(?!\[)[^\s=&{"]+=[^\s&]*(?:&[^\s=&]+=[^\s&]*)*$/;
|
|
23286
|
+
var JSON_LIKE_START = /^\uFEFF?(?:\)\]\}',?)?\s*(?:(?:\d+:)?(?:\d*-?(?:\/[^,]*,)?|[ac])[[{]|(?:data|event|id|retry):)/;
|
|
23287
|
+
var JSON_MIME = /json|event-stream/i;
|
|
23288
|
+
var DECODABLE_MIME = /^(?:application\/octet-stream|binary\/octet-stream|application\/x-www-form-urlencoded|text\/event-stream)?$|json/i;
|
|
23289
|
+
var BARE = /[^\s,:[\]{}"]+/y;
|
|
23290
|
+
var DOT = 46;
|
|
23291
|
+
var MAX_ENCODED_DEPTH = 3;
|
|
23292
|
+
var REDACTED_STRING = JSON.stringify(REDACTED);
|
|
23293
|
+
var UTF8 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
23294
|
+
function isSensitiveField2(name) {
|
|
23295
|
+
return SENSITIVE_FIELD.test(name.replace(/([a-z])([A-Z])/g, "$1_$2"));
|
|
23296
|
+
}
|
|
23297
|
+
function redactOrReplaceWhole(text, redact, whole = REDACTED) {
|
|
23298
|
+
try {
|
|
23299
|
+
return redact(text);
|
|
23300
|
+
} catch (error) {
|
|
23301
|
+
log29.debug(`Body replaced whole, sanitizing failed: ${getErrorMessage(error)}`);
|
|
23302
|
+
return whole;
|
|
23303
|
+
}
|
|
23304
|
+
}
|
|
23305
|
+
function redactJwts(text, replacement) {
|
|
23306
|
+
if (!text.includes("eyJ")) return text;
|
|
23307
|
+
const parts = [];
|
|
23308
|
+
let copied = 0;
|
|
23309
|
+
let at = text.indexOf("eyJ");
|
|
23310
|
+
while (at !== -1) {
|
|
23311
|
+
const end = isJwtBoundary(text, at - 1) ? jwtEnd(text, at) : void 0;
|
|
23312
|
+
if (end !== void 0 && isJwtBoundary(text, end)) {
|
|
23313
|
+
parts.push(text.slice(copied, at), replacement);
|
|
23314
|
+
copied = end;
|
|
23315
|
+
}
|
|
23316
|
+
at = text.indexOf("eyJ", Math.max(at + 3, copied));
|
|
23317
|
+
}
|
|
23318
|
+
if (parts.length === 0) return text;
|
|
23319
|
+
parts.push(text.slice(copied));
|
|
23320
|
+
return parts.join("");
|
|
23321
|
+
}
|
|
23322
|
+
function redactPairs(text, isSensitive, replacement) {
|
|
23323
|
+
return text.split("&").map((pair) => {
|
|
23324
|
+
const eq = pair.indexOf("=");
|
|
23325
|
+
if (eq === -1) return pair;
|
|
23326
|
+
if (isSensitive(decodeName(pair.slice(0, eq)))) return `${pair.slice(0, eq)}=${replacement}`;
|
|
23327
|
+
const value = pair.slice(eq + 1);
|
|
23328
|
+
const redacted = redactJwts(value, replacement);
|
|
23329
|
+
return redacted === value ? pair : `${pair.slice(0, eq + 1)}${redacted}`;
|
|
23330
|
+
}).join("&");
|
|
23331
|
+
}
|
|
23332
|
+
function redactBody(text, mimeType) {
|
|
23333
|
+
if (/multipart\/form-data/i.test(mimeType)) return redactMultipart(text, mimeType);
|
|
23334
|
+
if (/x-www-form-urlencoded/i.test(mimeType)) return redactPairs(text, isSensitiveField2, REDACTED);
|
|
23335
|
+
if (JSON_LIKE_START.test(text)) return redactJsonText(text, 0);
|
|
23336
|
+
if (FORM_BODY.test(text)) return redactPairs(text, isSensitiveField2, REDACTED);
|
|
23337
|
+
if (JSON_MIME.test(mimeType)) return redactJsonText(text, 0);
|
|
23338
|
+
return redactJwts(text, REDACTED);
|
|
23339
|
+
}
|
|
23340
|
+
function redactBase64Body(base64, mimeType) {
|
|
23341
|
+
if (!DECODABLE_MIME.test(mimeType.split(";")[0]?.trim() ?? "")) return base64;
|
|
23342
|
+
let decoded;
|
|
23343
|
+
try {
|
|
23344
|
+
decoded = UTF8.decode(Buffer.from(base64, "base64"));
|
|
23345
|
+
} catch (error) {
|
|
23346
|
+
log29.debug(`Base64 body is not UTF-8: ${getErrorMessage(error)}`);
|
|
23347
|
+
return base64;
|
|
23348
|
+
}
|
|
23349
|
+
const redacted = redactBody(decoded, mimeType);
|
|
23350
|
+
return redacted === decoded ? base64 : Buffer.from(redacted, "utf8").toString("base64");
|
|
23351
|
+
}
|
|
23352
|
+
function decodeName(name) {
|
|
23353
|
+
try {
|
|
23354
|
+
return decodeURIComponent(name.replace(/\+/g, " "));
|
|
23355
|
+
} catch (error) {
|
|
23356
|
+
log29.debug(`Field name not decodable: ${getErrorMessage(error)}`);
|
|
23357
|
+
return name;
|
|
23358
|
+
}
|
|
23359
|
+
}
|
|
23360
|
+
function redactMultipart(text, mimeType) {
|
|
23361
|
+
const match = /boundary=(?:"([^"]+)"|([^;\s]+))/i.exec(mimeType);
|
|
23362
|
+
const boundary = match?.[1] ?? match?.[2];
|
|
23363
|
+
if (!boundary) return text;
|
|
23364
|
+
const delimiter = `--${boundary}`;
|
|
23365
|
+
return text.split(delimiter).map(redactPart).join(delimiter);
|
|
23366
|
+
}
|
|
23367
|
+
function redactPart(part) {
|
|
23368
|
+
const headerEnd = part.indexOf("\r\n\r\n");
|
|
23369
|
+
if (headerEnd === -1) return part;
|
|
23370
|
+
const name = /;\s*name="([^"]*)"/i.exec(part.slice(0, headerEnd))?.[1];
|
|
23371
|
+
if (name === void 0 || !isSensitiveField2(name)) return redactJwts(part, REDACTED);
|
|
23372
|
+
const valueEnd = part.endsWith("\r\n") ? part.length - 2 : part.length;
|
|
23373
|
+
return `${part.slice(0, headerEnd + 4)}${REDACTED}${part.slice(valueEnd)}`;
|
|
23374
|
+
}
|
|
23375
|
+
function redactJsonText(text, depth) {
|
|
23376
|
+
const scan2 = {
|
|
23377
|
+
text,
|
|
23378
|
+
parts: [],
|
|
23379
|
+
copied: 0,
|
|
23380
|
+
containers: [false],
|
|
23381
|
+
key: void 0,
|
|
23382
|
+
lineEnd: -1,
|
|
23383
|
+
depth
|
|
23384
|
+
};
|
|
23385
|
+
let index = 0;
|
|
23386
|
+
while (index < text.length) index = scanToken(scan2, index);
|
|
23387
|
+
if (scan2.parts.length === 0) return text;
|
|
23388
|
+
scan2.parts.push(text.slice(scan2.copied));
|
|
23389
|
+
return scan2.parts.join("");
|
|
23390
|
+
}
|
|
23391
|
+
function scanToken(scan2, index) {
|
|
23392
|
+
const char = scan2.text[index];
|
|
23393
|
+
if (char === '"') return scanString(scan2, index);
|
|
23394
|
+
if (char === "{" || char === "[") {
|
|
23395
|
+
scan2.containers.push(takeValue(scan2));
|
|
23396
|
+
} else if (char === "}" || char === "]") {
|
|
23397
|
+
if (scan2.containers.length > 1) scan2.containers.pop();
|
|
23398
|
+
scan2.key = void 0;
|
|
23399
|
+
} else if (char === ",") {
|
|
23400
|
+
scan2.key = void 0;
|
|
23401
|
+
} else {
|
|
23402
|
+
return scanBare(scan2, index);
|
|
23403
|
+
}
|
|
23404
|
+
return index + 1;
|
|
23405
|
+
}
|
|
23406
|
+
function scanString(scan2, start) {
|
|
23407
|
+
const end = stringEnd(scan2, start);
|
|
23408
|
+
if (isFollowedByColon(scan2.text, end)) {
|
|
23409
|
+
scan2.key = isSensitiveField2(keyName(scan2.text, start, end));
|
|
23410
|
+
} else if (takeValue(scan2)) {
|
|
23411
|
+
replaceValue(scan2, start, end, REDACTED_STRING);
|
|
23412
|
+
} else {
|
|
23413
|
+
const encoded = redactEncodedJson(scan2, start, end);
|
|
23414
|
+
if (encoded === void 0) redactStringJwts(scan2, start, end);
|
|
23415
|
+
else replaceValue(scan2, start, end, encoded);
|
|
23416
|
+
}
|
|
23417
|
+
return end;
|
|
23418
|
+
}
|
|
23419
|
+
function redactStringJwts(scan2, start, end) {
|
|
23420
|
+
const contentEnd = end - 1 > start && scan2.text[end - 1] === '"' ? end - 1 : end;
|
|
23421
|
+
const content = scan2.text.slice(start + 1, contentEnd);
|
|
23422
|
+
const redacted = redactJwts(content, REDACTED);
|
|
23423
|
+
if (redacted !== content) replaceValue(scan2, start + 1, contentEnd, redacted);
|
|
23424
|
+
}
|
|
23425
|
+
function scanBare(scan2, index) {
|
|
23426
|
+
BARE.lastIndex = index;
|
|
23427
|
+
const bare = BARE.exec(scan2.text)?.[0];
|
|
23428
|
+
if (bare === void 0) return index + 1;
|
|
23429
|
+
const end = index + bare.length;
|
|
23430
|
+
const isLiteral = /^[-\d]/.test(bare) || bare === "true" || bare === "false" || bare === "null";
|
|
23431
|
+
if (!isLiteral && scan2.key !== true) return end;
|
|
23432
|
+
if (takeValue(scan2) && bare !== "null") replaceValue(scan2, index, end, REDACTED_STRING);
|
|
23433
|
+
return end;
|
|
23434
|
+
}
|
|
23435
|
+
function takeValue(scan2) {
|
|
23436
|
+
const sensitive = scan2.key === true || scan2.containers[scan2.containers.length - 1] === true;
|
|
23437
|
+
scan2.key = void 0;
|
|
23438
|
+
return sensitive;
|
|
23439
|
+
}
|
|
23440
|
+
function replaceValue(scan2, start, end, replacement) {
|
|
23441
|
+
scan2.parts.push(scan2.text.slice(scan2.copied, start), replacement);
|
|
23442
|
+
scan2.copied = end;
|
|
23443
|
+
}
|
|
23444
|
+
function isJwtBoundary(text, index) {
|
|
23445
|
+
if (index < 0 || index >= text.length) return true;
|
|
23446
|
+
const code = text.charCodeAt(index);
|
|
23447
|
+
return !isBase64UrlCode(code) && code !== DOT;
|
|
23448
|
+
}
|
|
23449
|
+
function jwtEnd(text, start) {
|
|
23450
|
+
const first = segmentEnd(text, start + 3);
|
|
23451
|
+
if (first - start < 8 || text[first] !== ".") return void 0;
|
|
23452
|
+
const second = segmentEnd(text, first + 1);
|
|
23453
|
+
if (second - first - 1 < 5 || text[second] !== ".") return void 0;
|
|
23454
|
+
return segmentEnd(text, second + 1);
|
|
23455
|
+
}
|
|
23456
|
+
function segmentEnd(text, from) {
|
|
23457
|
+
let index = from;
|
|
23458
|
+
while (index < text.length && isBase64UrlCode(text.charCodeAt(index))) index++;
|
|
23459
|
+
return index;
|
|
23460
|
+
}
|
|
23461
|
+
function isBase64UrlCode(code) {
|
|
23462
|
+
return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122 || code === 95 || code === 45;
|
|
23463
|
+
}
|
|
23464
|
+
function redactEncodedJson(scan2, start, end) {
|
|
23465
|
+
if (scan2.depth >= MAX_ENCODED_DEPTH || end - start < 2 || scan2.text[end - 1] !== '"') {
|
|
23466
|
+
return void 0;
|
|
23467
|
+
}
|
|
23468
|
+
if (!/[[{]/.test(scan2.text.slice(start + 1, end - 1))) return void 0;
|
|
23469
|
+
let decoded;
|
|
23470
|
+
try {
|
|
23471
|
+
decoded = JSON.parse(scan2.text.slice(start, end));
|
|
23472
|
+
} catch (error) {
|
|
23473
|
+
log29.debug(`String value not decodable: ${getErrorMessage(error)}`);
|
|
23474
|
+
return void 0;
|
|
23475
|
+
}
|
|
23476
|
+
if (!JSON_LIKE_START.test(decoded)) return void 0;
|
|
23477
|
+
const redacted = redactJsonText(decoded, scan2.depth + 1);
|
|
23478
|
+
return redacted === decoded ? void 0 : JSON.stringify(redacted);
|
|
23479
|
+
}
|
|
23480
|
+
function stringEnd(scan2, start) {
|
|
23481
|
+
const { text } = scan2;
|
|
23482
|
+
let from = start + 1;
|
|
23483
|
+
for (; ; ) {
|
|
23484
|
+
const quote = text.indexOf('"', from);
|
|
23485
|
+
const lineEnd = nextLineEnd(scan2, from);
|
|
23486
|
+
if (quote === -1 || lineEnd < quote) {
|
|
23487
|
+
return lineEnd > from && text[lineEnd - 1] === "\r" ? lineEnd - 1 : lineEnd;
|
|
23488
|
+
}
|
|
23489
|
+
let backslashes = 0;
|
|
23490
|
+
while (text[quote - 1 - backslashes] === "\\" && quote - 1 - backslashes > start) {
|
|
23491
|
+
backslashes++;
|
|
23492
|
+
}
|
|
23493
|
+
if (backslashes % 2 === 0) return quote + 1;
|
|
23494
|
+
from = quote + 1;
|
|
23495
|
+
}
|
|
23496
|
+
}
|
|
23497
|
+
function nextLineEnd(scan2, from) {
|
|
23498
|
+
if (scan2.lineEnd < from) {
|
|
23499
|
+
const lineEnd = scan2.text.indexOf("\n", from);
|
|
23500
|
+
scan2.lineEnd = lineEnd === -1 ? scan2.text.length : lineEnd;
|
|
23501
|
+
}
|
|
23502
|
+
return scan2.lineEnd;
|
|
23503
|
+
}
|
|
23504
|
+
function isFollowedByColon(text, index) {
|
|
23505
|
+
let next = index;
|
|
23506
|
+
while (next < text.length && " \n\r".includes(text.charAt(next))) next++;
|
|
23507
|
+
return text[next] === ":";
|
|
23508
|
+
}
|
|
23509
|
+
function keyName(text, start, end) {
|
|
23510
|
+
const raw = text.slice(start + 1, end - 1);
|
|
23511
|
+
if (!raw.includes("\\")) return raw;
|
|
23512
|
+
try {
|
|
23513
|
+
return JSON.parse(text.slice(start, end));
|
|
23514
|
+
} catch (error) {
|
|
23515
|
+
log29.debug(`Key not decodable: ${getErrorMessage(error)}`);
|
|
23516
|
+
return raw;
|
|
23517
|
+
}
|
|
23518
|
+
}
|
|
23519
|
+
|
|
23520
|
+
// src/telemetry/har/sanitize.ts
|
|
23521
|
+
var URL_REDACTED = encodeURIComponent(REDACTED);
|
|
23522
|
+
var SENSITIVE_HEADERS = /* @__PURE__ */ new Set([
|
|
23523
|
+
"authorization",
|
|
23524
|
+
"proxy-authorization",
|
|
23525
|
+
"authentication",
|
|
23526
|
+
"cookie",
|
|
23527
|
+
"set-cookie"
|
|
23528
|
+
]);
|
|
23529
|
+
var SENSITIVE_CUSTOM_HEADER = /^x-.*(key|token|secret|auth)/i;
|
|
23530
|
+
var SENSITIVE_HEADER_SEGMENT = /(^|-)(api-?key|apikey|token|secret|jwt|subscription-key|session(-?id)?)(-|$)/i;
|
|
23531
|
+
var URL_HEADERS = /* @__PURE__ */ new Set(["location", "referer"]);
|
|
23532
|
+
var SENSITIVE_URL_PARAM = /^(code|sig|key)$/i;
|
|
23533
|
+
var BASE64_REDACTED = Buffer.from(REDACTED).toString("base64");
|
|
23534
|
+
var TEXT_OPCODE = 1;
|
|
23535
|
+
var BINARY_OPCODE2 = 2;
|
|
23536
|
+
function sanitizeEntry(entry) {
|
|
23537
|
+
const { request, response, _webSocketMessages: messages } = entry;
|
|
23538
|
+
const postData = request.postData;
|
|
23539
|
+
return {
|
|
23540
|
+
...entry,
|
|
23541
|
+
request: {
|
|
23542
|
+
...request,
|
|
23543
|
+
url: redactUrl(request.url),
|
|
23544
|
+
cookies: request.cookies.map(redactCookie),
|
|
23545
|
+
headers: request.headers.map(redactHeader),
|
|
23546
|
+
queryString: request.queryString.map(redactQueryParam),
|
|
23547
|
+
...postData?.text !== void 0 && {
|
|
23548
|
+
postData: {
|
|
23549
|
+
...postData,
|
|
23550
|
+
text: redactOrReplaceWhole(postData.text, (text) => redactBody(text, postData.mimeType))
|
|
23551
|
+
}
|
|
23552
|
+
}
|
|
23553
|
+
},
|
|
23554
|
+
response: {
|
|
23555
|
+
...response,
|
|
23556
|
+
cookies: response.cookies.map(redactCookie),
|
|
23557
|
+
headers: response.headers.map(redactHeader),
|
|
23558
|
+
redirectURL: redactUrl(response.redirectURL),
|
|
23559
|
+
content: redactContent(response.content)
|
|
23560
|
+
},
|
|
23561
|
+
...messages && { _webSocketMessages: messages.map(redactWebSocketMessage) }
|
|
23562
|
+
};
|
|
23563
|
+
}
|
|
23564
|
+
function redactContent(content) {
|
|
23565
|
+
const { text, mimeType } = content;
|
|
23566
|
+
if (text === void 0) return content;
|
|
23567
|
+
const redacted = content.encoding === "base64" ? redactOrReplaceWhole(text, (body) => redactBase64Body(body, mimeType), BASE64_REDACTED) : redactOrReplaceWhole(text, (body) => redactBody(body, mimeType));
|
|
23568
|
+
return redacted === text ? content : { ...content, text: redacted };
|
|
23569
|
+
}
|
|
23570
|
+
function redactWebSocketMessage(message) {
|
|
23571
|
+
if (message.opcode !== TEXT_OPCODE && message.opcode !== BINARY_OPCODE2) return message;
|
|
23572
|
+
const data = message.opcode === TEXT_OPCODE ? redactOrReplaceWhole(message.data, (text) => redactBody(text, "")) : redactOrReplaceWhole(message.data, (text) => redactBase64Body(text, ""), BASE64_REDACTED);
|
|
23573
|
+
return data === message.data ? message : { ...message, data };
|
|
23574
|
+
}
|
|
23575
|
+
function isSensitiveHeader(name) {
|
|
23576
|
+
return SENSITIVE_HEADERS.has(name.toLowerCase()) || SENSITIVE_CUSTOM_HEADER.test(name) || SENSITIVE_HEADER_SEGMENT.test(name);
|
|
23577
|
+
}
|
|
23578
|
+
function redactHeader(header) {
|
|
23579
|
+
if (isSensitiveHeader(header.name)) return { ...header, value: REDACTED };
|
|
23580
|
+
if (!URL_HEADERS.has(header.name.toLowerCase())) return header;
|
|
23581
|
+
const value = redactUrl(header.value);
|
|
23582
|
+
return value === header.value ? header : { ...header, value };
|
|
23583
|
+
}
|
|
23584
|
+
function redactCookie(cookie) {
|
|
23585
|
+
return { ...cookie, value: REDACTED };
|
|
23586
|
+
}
|
|
23587
|
+
function isSensitiveParam(name) {
|
|
23588
|
+
return isSensitiveField2(name) || SENSITIVE_URL_PARAM.test(name);
|
|
23589
|
+
}
|
|
23590
|
+
function redactQueryParam(param) {
|
|
23591
|
+
return isSensitiveParam(param.name) ? { ...param, value: REDACTED } : param;
|
|
23592
|
+
}
|
|
23593
|
+
function redactUrl(url) {
|
|
23594
|
+
const hash = url.indexOf("#");
|
|
23595
|
+
const beforeHash = hash === -1 ? url : url.slice(0, hash);
|
|
23596
|
+
const fragment = hash === -1 ? "" : `#${redactPairs(url.slice(hash + 1), isSensitiveParam, URL_REDACTED)}`;
|
|
23597
|
+
const question = beforeHash.indexOf("?");
|
|
23598
|
+
if (question === -1) return beforeHash + fragment;
|
|
23599
|
+
const query = redactPairs(beforeHash.slice(question + 1), isSensitiveParam, URL_REDACTED);
|
|
23600
|
+
return `${beforeHash.slice(0, question)}?${query}${fragment}`;
|
|
23601
|
+
}
|
|
23602
|
+
|
|
23603
|
+
// src/telemetry/har/builder.ts
|
|
23281
23604
|
var requireBuiltin = createRequire2(import.meta.url);
|
|
23282
23605
|
var UNKNOWN_TIMING = -1;
|
|
23283
23606
|
var DEFAULT_HTTP_VERSION = "HTTP/1.1";
|
|
23284
|
-
function buildHAR(requests, metadata) {
|
|
23285
|
-
const
|
|
23286
|
-
const
|
|
23607
|
+
function buildHAR(requests, metadata, options = {}) {
|
|
23608
|
+
const built = [...requests].sort((a, b) => a.timestamp - b.timestamp).map(buildEntry);
|
|
23609
|
+
const entries = options.includeSensitive ? built : built.map(sanitizeEntry);
|
|
23610
|
+
const log33 = {
|
|
23287
23611
|
version: "1.2",
|
|
23288
23612
|
creator: {
|
|
23289
23613
|
name: "bdg",
|
|
23290
23614
|
version: metadata.version,
|
|
23291
23615
|
comment: "Browser Debugger CLI - https://github.com/szymdzum/browser-debugger-cli"
|
|
23292
23616
|
},
|
|
23293
|
-
entries
|
|
23617
|
+
entries,
|
|
23618
|
+
...!options.includeSensitive && { comment: harSanitizedComment() }
|
|
23294
23619
|
};
|
|
23295
23620
|
if (metadata.chromeVersion) {
|
|
23296
|
-
|
|
23621
|
+
log33.browser = {
|
|
23297
23622
|
name: "Chrome",
|
|
23298
23623
|
version: metadata.chromeVersion
|
|
23299
23624
|
};
|
|
23300
23625
|
}
|
|
23301
|
-
return { log:
|
|
23626
|
+
return { log: log33 };
|
|
23302
23627
|
}
|
|
23303
23628
|
function buildEntry(req) {
|
|
23304
23629
|
const timings = buildTimings(req);
|
|
@@ -23329,7 +23654,8 @@ function buildWebSocketMessage(frame) {
|
|
|
23329
23654
|
type: frame.direction === "sent" ? "send" : "receive",
|
|
23330
23655
|
time: frame.timestamp / 1e3,
|
|
23331
23656
|
opcode: frame.opcode,
|
|
23332
|
-
data: frame.payloadData
|
|
23657
|
+
data: frame.payloadData,
|
|
23658
|
+
...frame.truncatedFrom !== void 0 && { _truncatedFrom: frame.truncatedFrom }
|
|
23333
23659
|
};
|
|
23334
23660
|
}
|
|
23335
23661
|
function buildRequest(req) {
|
|
@@ -23342,7 +23668,7 @@ function buildRequest(req) {
|
|
|
23342
23668
|
headers: convertHeaders(req.requestHeaders),
|
|
23343
23669
|
queryString: extractQueryParams(url),
|
|
23344
23670
|
headersSize: estimateRequestHeadersSize(req.method, req.url, req.requestHeaders),
|
|
23345
|
-
bodySize:
|
|
23671
|
+
bodySize: requestBodySize(req)
|
|
23346
23672
|
};
|
|
23347
23673
|
const postData = buildPostData(req);
|
|
23348
23674
|
if (postData) {
|
|
@@ -23390,13 +23716,17 @@ function contentLength(req) {
|
|
|
23390
23716
|
const bytes = value === void 0 ? NaN : Number(value);
|
|
23391
23717
|
return Number.isInteger(bytes) && bytes >= 0 ? bytes : void 0;
|
|
23392
23718
|
}
|
|
23719
|
+
function requestBodySize(req) {
|
|
23720
|
+
if (!req.requestBody) return 0;
|
|
23721
|
+
if (skippedBodyReason(req.requestBody) !== void 0) return -1;
|
|
23722
|
+
return Buffer.byteLength(req.requestBody, "utf-8");
|
|
23723
|
+
}
|
|
23393
23724
|
function buildPostData(req) {
|
|
23394
23725
|
if (!req.requestBody) return void 0;
|
|
23395
|
-
const
|
|
23396
|
-
|
|
23397
|
-
|
|
23398
|
-
|
|
23399
|
-
};
|
|
23726
|
+
const mimeType = getHeader(req.requestHeaders, "content-type") ?? "text/plain";
|
|
23727
|
+
const skipped = skippedBodyReason(req.requestBody);
|
|
23728
|
+
if (skipped !== void 0) return { mimeType, comment: `Body not captured: ${skipped}` };
|
|
23729
|
+
return { mimeType, text: req.requestBody };
|
|
23400
23730
|
}
|
|
23401
23731
|
function span(start, end) {
|
|
23402
23732
|
if (start === void 0 || end === void 0 || start < 0 || end < 0 || end < start) {
|
|
@@ -23509,8 +23839,8 @@ function extractRedirectURL(req) {
|
|
|
23509
23839
|
}
|
|
23510
23840
|
function estimateRequestHeadersSize(method, url, headers) {
|
|
23511
23841
|
const parsedUrl = new URL(url);
|
|
23512
|
-
const
|
|
23513
|
-
const requestLine = `${method} ${
|
|
23842
|
+
const path8 = parsedUrl.pathname + parsedUrl.search;
|
|
23843
|
+
const requestLine = `${method} ${path8} ${DEFAULT_HTTP_VERSION}\r
|
|
23514
23844
|
`;
|
|
23515
23845
|
const headersString = headers ? Object.entries(headers).map(([name, value]) => `${name}: ${value}\r
|
|
23516
23846
|
`).join("") : "";
|
|
@@ -23642,6 +23972,13 @@ function formatSessionStatus(metadata, pid, activity, pageState, verbose = false
|
|
|
23642
23972
|
if (activity.lastConsoleMessageAt) {
|
|
23643
23973
|
fmt.keyValue(" Last Message", formatTimeAgo(activity.lastConsoleMessageAt), 18);
|
|
23644
23974
|
}
|
|
23975
|
+
const downloadRows = [
|
|
23976
|
+
...activity.downloads?.length ? [downloadsSummary(activity.downloads)] : [],
|
|
23977
|
+
...activity.downloadsWarning ? [`\u26A0 ${activity.downloadsWarning}`] : []
|
|
23978
|
+
];
|
|
23979
|
+
downloadRows.forEach(
|
|
23980
|
+
(row, index) => index === 0 ? fmt.keyValue("Downloads", row, 18) : fmt.text(`${" ".repeat(18)}${row}`)
|
|
23981
|
+
);
|
|
23645
23982
|
}
|
|
23646
23983
|
fmt.blank().text("Collectors").separator("\u2501", 50);
|
|
23647
23984
|
const activeTelemetry = metadata.activeTelemetry ?? ["network", "console", "dom"];
|
|
@@ -23778,6 +24115,7 @@ function buildPreviewJsonData(output, options) {
|
|
|
23778
24115
|
...output.partial !== void 0 && { partial: output.partial },
|
|
23779
24116
|
...output.totals && { totals: output.totals },
|
|
23780
24117
|
...output.pageCrashedAt !== void 0 && { pageCrashedAt: output.pageCrashedAt },
|
|
24118
|
+
...output.downloads && { downloads: output.downloads },
|
|
23781
24119
|
...pick("network") && output.data.network && { network: last(output.data.network) },
|
|
23782
24120
|
...pick("console") && output.data.console && {
|
|
23783
24121
|
console: last(output.data.console)?.map((message) => capMessageText(message, options.full))
|
|
@@ -23862,6 +24200,8 @@ function formatPreviewCompact(output, options) {
|
|
|
23862
24200
|
fmt.blank();
|
|
23863
24201
|
}
|
|
23864
24202
|
}
|
|
24203
|
+
if (output.downloads?.length)
|
|
24204
|
+
fmt.text(`Downloads: ${downloadsSummary(output.downloads)}`).blank();
|
|
23865
24205
|
if (!options.follow) {
|
|
23866
24206
|
fmt.tip(compactTipsMessage());
|
|
23867
24207
|
}
|
|
@@ -23950,6 +24290,9 @@ function formatPreviewVerbose(output, options) {
|
|
|
23950
24290
|
fmt.blank();
|
|
23951
24291
|
}
|
|
23952
24292
|
}
|
|
24293
|
+
if (output.downloads?.length) {
|
|
24294
|
+
fmt.keyValue("Downloads", downloadsSummary(output.downloads), 18).blank();
|
|
24295
|
+
}
|
|
23953
24296
|
if (!options.follow) {
|
|
23954
24297
|
fmt.tip(verboseCommandsMessage());
|
|
23955
24298
|
}
|
|
@@ -24073,7 +24416,7 @@ function registerDocumentCommand(networkCmd) {
|
|
|
24073
24416
|
}
|
|
24074
24417
|
|
|
24075
24418
|
// src/commands/network/har.ts
|
|
24076
|
-
var
|
|
24419
|
+
var log30 = createLogger("network");
|
|
24077
24420
|
function generateHARFilename() {
|
|
24078
24421
|
const now = /* @__PURE__ */ new Date();
|
|
24079
24422
|
const year = now.getFullYear();
|
|
@@ -24083,10 +24426,10 @@ function generateHARFilename() {
|
|
|
24083
24426
|
const minutes = String(now.getMinutes()).padStart(2, "0");
|
|
24084
24427
|
const seconds = String(now.getSeconds()).padStart(2, "0");
|
|
24085
24428
|
const base = `capture-${year}-${month}-${day}-${hours}${minutes}${seconds}`;
|
|
24086
|
-
const sessionDir =
|
|
24087
|
-
let candidate =
|
|
24429
|
+
const sessionDir = path7.dirname(getSessionFilePath("OUTPUT"));
|
|
24430
|
+
let candidate = path7.join(sessionDir, `${base}.har`);
|
|
24088
24431
|
for (let n = 2; fs16.existsSync(candidate); n++) {
|
|
24089
|
-
candidate =
|
|
24432
|
+
candidate = path7.join(sessionDir, `${base}-${n}.har`);
|
|
24090
24433
|
}
|
|
24091
24434
|
return candidate;
|
|
24092
24435
|
}
|
|
@@ -24096,22 +24439,26 @@ async function getChromeVersion() {
|
|
|
24096
24439
|
const product = response.data?.result?.product;
|
|
24097
24440
|
return product?.split("/")[1];
|
|
24098
24441
|
} catch (error) {
|
|
24099
|
-
|
|
24442
|
+
log30.debug(`Could not read Chrome version: ${getErrorMessage(error)}`);
|
|
24100
24443
|
return void 0;
|
|
24101
24444
|
}
|
|
24102
24445
|
}
|
|
24103
24446
|
function formatHARExport(data) {
|
|
24104
24447
|
if ("log" in data) return JSON.stringify(data, null, 2);
|
|
24105
|
-
|
|
24106
|
-
return `\u2713 Exported ${data.entries} requests${filterNote} to ${data.file}`;
|
|
24448
|
+
return harExportedMessage(data);
|
|
24107
24449
|
}
|
|
24450
|
+
var HAR_FILE_MODE = 384;
|
|
24108
24451
|
var STDOUT_PATH = "-";
|
|
24109
24452
|
var filterDslOption = new Option(
|
|
24110
24453
|
"--filter <dsl>",
|
|
24111
24454
|
'Filter requests using DevTools DSL (e.g., "status-code:>=400")'
|
|
24112
24455
|
);
|
|
24456
|
+
var includeSensitiveOption = new Option(
|
|
24457
|
+
"--include-sensitive",
|
|
24458
|
+
"Keep credentials (auth/cookie/API key headers, cookie values, URL tokens, password and token body fields); redacted by default"
|
|
24459
|
+
);
|
|
24113
24460
|
function registerHarCommand(networkCmd) {
|
|
24114
|
-
networkCmd.command("har [output-file]").addHelpText("after", "\nUse - as the output file to write the HAR to stdout.").description("Export network data as HAR 1.2 format").addOption(jsonOption()).addOption(filterDslOption).action(async (outputFile, options) => {
|
|
24461
|
+
networkCmd.command("har [output-file]").addHelpText("after", "\nUse - as the output file to write the HAR to stdout.").description("Export network data as HAR 1.2 format").addOption(jsonOption()).addOption(filterDslOption).addOption(includeSensitiveOption).action(async (outputFile, options) => {
|
|
24115
24462
|
await runCommand(
|
|
24116
24463
|
async () => {
|
|
24117
24464
|
let requests = await getNetworkRequests();
|
|
@@ -24126,20 +24473,26 @@ function registerHarCommand(networkCmd) {
|
|
|
24126
24473
|
const outputPath = outputFile ?? generateHARFilename();
|
|
24127
24474
|
if (outputPath !== STDOUT_PATH) assertFilePath(outputPath, ".har");
|
|
24128
24475
|
const chromeVersion = await getChromeVersion();
|
|
24129
|
-
const
|
|
24130
|
-
|
|
24131
|
-
|
|
24132
|
-
|
|
24476
|
+
const includeSensitive = options.includeSensitive === true;
|
|
24477
|
+
const har = buildHAR(
|
|
24478
|
+
requests,
|
|
24479
|
+
{ version: VERSION, ...chromeVersion && { chromeVersion } },
|
|
24480
|
+
{ includeSensitive }
|
|
24481
|
+
);
|
|
24133
24482
|
if (outputPath === STDOUT_PATH) return { success: true, data: har };
|
|
24134
|
-
const file = await writeOutputFile(
|
|
24135
|
-
|
|
24136
|
-
|
|
24137
|
-
|
|
24138
|
-
|
|
24139
|
-
|
|
24140
|
-
|
|
24141
|
-
|
|
24483
|
+
const file = await writeOutputFile(
|
|
24484
|
+
outputPath,
|
|
24485
|
+
JSON.stringify(har, null, 2),
|
|
24486
|
+
".har",
|
|
24487
|
+
HAR_FILE_MODE
|
|
24488
|
+
);
|
|
24489
|
+
const data = {
|
|
24490
|
+
file,
|
|
24491
|
+
entries: har.log.entries.length,
|
|
24492
|
+
filtered,
|
|
24493
|
+
sanitized: !includeSensitive
|
|
24142
24494
|
};
|
|
24495
|
+
return { success: true, data };
|
|
24143
24496
|
},
|
|
24144
24497
|
options,
|
|
24145
24498
|
formatHARExport
|
|
@@ -24898,13 +25251,13 @@ import * as fs17 from "fs";
|
|
|
24898
25251
|
init_paths();
|
|
24899
25252
|
init_logging();
|
|
24900
25253
|
init_directories();
|
|
24901
|
-
var
|
|
25254
|
+
var log31 = createLogger("session");
|
|
24902
25255
|
function readPortFile(portPath) {
|
|
24903
25256
|
try {
|
|
24904
25257
|
const port = parseInt(fs17.readFileSync(portPath, "utf-8").trim(), 10);
|
|
24905
25258
|
return Number.isInteger(port) && port >= 1 && port <= 65535 ? port : null;
|
|
24906
25259
|
} catch (error) {
|
|
24907
|
-
logDebugError(
|
|
25260
|
+
logDebugError(log31, `read ${portPath}`, error);
|
|
24908
25261
|
return null;
|
|
24909
25262
|
}
|
|
24910
25263
|
}
|
|
@@ -24956,7 +25309,7 @@ function selectSession(optionValue) {
|
|
|
24956
25309
|
// src/session/sessionList.ts
|
|
24957
25310
|
init_logging();
|
|
24958
25311
|
init_sessionCommand();
|
|
24959
|
-
var
|
|
25312
|
+
var log32 = createLogger("session");
|
|
24960
25313
|
function toRunningSession(name, data) {
|
|
24961
25314
|
const meta = data.sessionMetadata;
|
|
24962
25315
|
const state = meta ? "active" : data.ending ? "ending" : "starting";
|
|
@@ -24975,11 +25328,15 @@ async function describeSession(entry) {
|
|
|
24975
25328
|
const socketPath = sessionFilePathIn(dir, "DAEMON_SOCKET");
|
|
24976
25329
|
const probe = await probeDaemonSocket(socketPath);
|
|
24977
25330
|
if (probe !== "alive") return describeWithoutSocket(entry, probe);
|
|
25331
|
+
const untrusted = secureSessionDir(dir);
|
|
25332
|
+
if (untrusted) {
|
|
25333
|
+
return { name, state: "untrusted", untrusted: `${untrusted.dir}: ${untrusted.reason}` };
|
|
25334
|
+
}
|
|
24978
25335
|
try {
|
|
24979
25336
|
const response = await getStatus(socketPath);
|
|
24980
25337
|
if (response.status === "ok" && response.data) return toRunningSession(name, response.data);
|
|
24981
25338
|
} catch (error) {
|
|
24982
|
-
logDebugError(
|
|
25339
|
+
logDebugError(log32, `get status of ${dir}`, error);
|
|
24983
25340
|
}
|
|
24984
25341
|
return { name, state: "unresponsive" };
|
|
24985
25342
|
}
|
|
@@ -25018,7 +25375,7 @@ function leftoverPort(dir) {
|
|
|
25018
25375
|
const meta = JSON.parse(fs18.readFileSync(sessionFilePathIn(dir, "METADATA"), "utf8"));
|
|
25019
25376
|
if (typeof meta.port === "number") return meta.port;
|
|
25020
25377
|
} catch (error) {
|
|
25021
|
-
logDebugError(
|
|
25378
|
+
logDebugError(log32, `read the metadata in ${dir}`, error);
|
|
25022
25379
|
}
|
|
25023
25380
|
return readPortFile(sessionFilePathIn(dir, "PORT"));
|
|
25024
25381
|
}
|
|
@@ -25031,7 +25388,7 @@ function isSameDir(a, b) {
|
|
|
25031
25388
|
const second = fs18.statSync(b);
|
|
25032
25389
|
return first.ino === second.ino && first.dev === second.dev;
|
|
25033
25390
|
} catch (error) {
|
|
25034
|
-
logDebugError(
|
|
25391
|
+
logDebugError(log32, `compare ${a} with ${b}`, error);
|
|
25035
25392
|
return false;
|
|
25036
25393
|
}
|
|
25037
25394
|
}
|
|
@@ -25090,6 +25447,15 @@ function formatSessionList(data) {
|
|
|
25090
25447
|
if (ended.length > 0) {
|
|
25091
25448
|
fmt.blank().section("Ended without bdg stop:", ended);
|
|
25092
25449
|
}
|
|
25450
|
+
const untrusted = data.sessions.flatMap(
|
|
25451
|
+
({ name, untrusted: why }) => why ? [untrustedSessionText(name ?? DEFAULT_SESSION_LABEL, why)] : []
|
|
25452
|
+
);
|
|
25453
|
+
if (untrusted.length > 0) {
|
|
25454
|
+
fmt.blank().section(
|
|
25455
|
+
"Directory not safe to use (not asked; bdg status --session <name> says how to fix it):",
|
|
25456
|
+
untrusted
|
|
25457
|
+
);
|
|
25458
|
+
}
|
|
25093
25459
|
const cleanups = data.sessions.flatMap((session) => session.cleanup ? [session.cleanup] : []);
|
|
25094
25460
|
if (cleanups.length > 0) {
|
|
25095
25461
|
fmt.hints("Crashed or stale sessions; clean up with:", cleanups);
|
|
@@ -25207,6 +25573,7 @@ function registerStatusCommand(program2) {
|
|
|
25207
25573
|
}
|
|
25208
25574
|
return { success: true, data: withSessionName(jsonOutput) };
|
|
25209
25575
|
} catch (error) {
|
|
25576
|
+
if (error instanceof CommandError) throw error;
|
|
25210
25577
|
const errorMessage = getErrorMessage(error);
|
|
25211
25578
|
if (error instanceof IPCTimeoutError) {
|
|
25212
25579
|
const err = sessionNotRespondingError(error.timeoutMs / 1e3);
|
|
@@ -25255,6 +25622,7 @@ function registerStatusCommand(program2) {
|
|
|
25255
25622
|
}
|
|
25256
25623
|
|
|
25257
25624
|
// src/commands/stop.ts
|
|
25625
|
+
init_errors3();
|
|
25258
25626
|
init_client();
|
|
25259
25627
|
init_transport();
|
|
25260
25628
|
init_formatting();
|
|
@@ -25325,7 +25693,7 @@ function registerStopCommand(program2) {
|
|
|
25325
25693
|
};
|
|
25326
25694
|
}
|
|
25327
25695
|
} catch (error) {
|
|
25328
|
-
if (error instanceof IPCTimeoutError) throw error;
|
|
25696
|
+
if (error instanceof IPCTimeoutError || error instanceof CommandError) throw error;
|
|
25329
25697
|
const errorMessage = getErrorMessage(error);
|
|
25330
25698
|
if (isDaemonNotRunningError(errorMessage)) {
|
|
25331
25699
|
return {
|