browser-debugger-cli 0.14.0 → 0.15.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 +1 -1
- package/dist/commands/cdp.js +1 -0
- package/dist/commands/cleanup.js +3 -0
- package/dist/commands/dom/eval.d.ts +2 -1
- package/dist/commands/dom/eval.js +6 -21
- package/dist/commands/dom/helpers/evalResult.d.ts +36 -0
- package/dist/commands/dom/helpers/evalResult.js +59 -0
- 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 +8 -3
- package/dist/commands/shared/optionTypes.d.ts +1 -0
- package/dist/commands/shared/outputFile.d.ts +2 -1
- package/dist/commands/shared/outputFile.js +7 -4
- package/dist/commands/status.js +3 -1
- package/dist/commands/stop.js +2 -1
- package/dist/connection/launcher/flagsBuilder.d.ts +46 -0
- package/dist/connection/launcher/flagsBuilder.js +107 -23
- package/dist/connection/launcher.d.ts +1 -1
- package/dist/connection/launcher.js +1 -2
- package/dist/constants.d.ts +2 -4
- package/dist/constants.js +2 -4
- package/dist/daemon/launcher.d.ts +17 -3
- package/dist/daemon/launcher.js +37 -7
- package/dist/daemon/session/commandRegistry.js +2 -2
- package/dist/daemon.js +8107 -7962
- package/dist/errors/messages.d.ts +23 -0
- package/dist/errors/messages.js +86 -6
- package/dist/index.js +555 -166
- package/dist/ipc/client.d.ts +6 -1
- package/dist/ipc/client.js +11 -2
- package/dist/ipc/protocol/commands.d.ts +4 -0
- package/dist/ipc/transport/index.d.ts +6 -0
- package/dist/ipc/transport/index.js +16 -1
- package/dist/runtime/dom/elementInfo.d.ts +7 -0
- package/dist/runtime/dom/elementInfo.js +8 -1
- package/dist/runtime/dom/evalHelpers.d.ts +24 -4
- package/dist/runtime/dom/evalHelpers.js +40 -12
- package/dist/runtime/dom/frames.d.ts +2 -1
- package/dist/runtime/dom/frames.js +3 -1
- package/dist/runtime/page/emulation.js +6 -5
- package/dist/runtime/page/userAgent.d.ts +86 -2
- package/dist/runtime/page/userAgent.js +154 -33
- package/dist/session/paths.d.ts +38 -3
- package/dist/session/paths.js +154 -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/har/builder.d.ts +12 -1
- package/dist/telemetry/har/builder.js +10 -2
- package/dist/telemetry/har/sanitize.d.ts +24 -0
- package/dist/telemetry/har/sanitize.js +138 -0
- package/dist/telemetry/har/sanitizeBody.d.ts +38 -0
- package/dist/telemetry/har/sanitizeBody.js +168 -0
- package/dist/ui/formatters/sessions.d.ts +3 -2
- package/dist/ui/formatters/sessions.js +10 -3
- package/dist/ui/messages/chrome.d.ts +14 -6
- package/dist/ui/messages/chrome.js +52 -12
- package/dist/ui/messages/networkMessages.d.ts +26 -0
- package/dist/ui/messages/networkMessages.js +21 -0
- package/dist/ui/messages/session.d.ts +8 -0
- package/dist/ui/messages/session.js +10 -0
- 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/package.json +1 -1
|
@@ -126,7 +126,7 @@ bdg details network <id> # Headers, timing, body of one req
|
|
|
126
126
|
bdg network getCookies
|
|
127
127
|
bdg console --level error # Errors on the current page
|
|
128
128
|
bdg console --follow # Streams (blocks; agents re-run bdg console instead)
|
|
129
|
-
bdg network har /tmp/session.har # Export HAR 1.2
|
|
129
|
+
bdg network har /tmp/session.har # Export HAR 1.2 (credentials redacted; --include-sensitive keeps them)
|
|
130
130
|
```
|
|
131
131
|
|
|
132
132
|
## Raw CDP
|
package/dist/commands/cdp.js
CHANGED
|
@@ -80,6 +80,7 @@ function getMethodHint(methodName, result) {
|
|
|
80
80
|
export function registerCdpCommand(program) {
|
|
81
81
|
program
|
|
82
82
|
.command('cdp')
|
|
83
|
+
.summary('CDP protocol introspection and execution')
|
|
83
84
|
.description('CDP protocol introspection and execution\n' +
|
|
84
85
|
' Discovery: --list, --search, --describe\n' +
|
|
85
86
|
' Execution: case-insensitive (network.getcookies works)')
|
package/dist/commands/cleanup.js
CHANGED
|
@@ -136,6 +136,9 @@ async function cleanupBlocker(opts) {
|
|
|
136
136
|
}
|
|
137
137
|
/**
|
|
138
138
|
* Clean up the selected session (and delete its directory with `--purge`).
|
|
139
|
+
* It does not run the session directory trust check (`secureSessionDir`): it
|
|
140
|
+
* sends no command, only probes the socket and signals PIDs verified by
|
|
141
|
+
* their command line, and must still clean up an untrusted directory.
|
|
139
142
|
*
|
|
140
143
|
* @param opts - Cleanup options
|
|
141
144
|
* @returns Command result
|
|
@@ -11,7 +11,8 @@ import type { DomEvalCommandOptions } from '../shared/optionTypes.js';
|
|
|
11
11
|
* frame the script ran in is a `Frame:` line on stderr (JSON: `frame`), and
|
|
12
12
|
* a warning about how the result was copied goes there too (JSON:
|
|
13
13
|
* `warning`), so stdout stays the bare value for pipes. Long values are cut
|
|
14
|
-
*
|
|
14
|
+
* unless `--full`: human output when formatted, JSON results by
|
|
15
|
+
* {@link boundEvalResult}.
|
|
15
16
|
*/
|
|
16
17
|
export declare function handleDomEval(script: string, options: DomEvalCommandOptions): Promise<void>;
|
|
17
18
|
//# sourceMappingURL=eval.d.ts.map
|
|
@@ -5,21 +5,21 @@
|
|
|
5
5
|
* CLI-side handler. Actual evaluation happens in the daemon via the
|
|
6
6
|
* `dom_eval` IPC command so the session's persistent CDP connection is reused.
|
|
7
7
|
*/
|
|
8
|
+
import { boundEvalResult } from './helpers/evalResult.js';
|
|
8
9
|
import { documentReadyState } from './helpers/query.js';
|
|
9
10
|
import { runCommand } from '../shared/CommandRunner.js';
|
|
10
|
-
import { MAX_VALUE_LENGTH } from '../../constants.js';
|
|
11
11
|
import { emptyScriptError, withLoadingHint } from '../../errors/messages.js';
|
|
12
12
|
import { domEval } from '../../ipc/client.js';
|
|
13
13
|
import { formatDomEval } from '../../ui/formatters/dom.js';
|
|
14
14
|
import { evalFrameLine, warningMessage } from '../../ui/messages/commands.js';
|
|
15
15
|
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
16
|
-
import { capLength } from '../../utils/strings.js';
|
|
17
16
|
/**
|
|
18
17
|
* Handle `bdg dom eval <script> [--frame <frame>]`. With `--frame`, the
|
|
19
18
|
* frame the script ran in is a `Frame:` line on stderr (JSON: `frame`), and
|
|
20
19
|
* a warning about how the result was copied goes there too (JSON:
|
|
21
20
|
* `warning`), so stdout stays the bare value for pipes. Long values are cut
|
|
22
|
-
*
|
|
21
|
+
* unless `--full`: human output when formatted, JSON results by
|
|
22
|
+
* {@link boundEvalResult}.
|
|
23
23
|
*/
|
|
24
24
|
export async function handleDomEval(script, options) {
|
|
25
25
|
await runCommand(async () => {
|
|
@@ -32,7 +32,7 @@ export async function handleDomEval(script, options) {
|
|
|
32
32
|
errorContext: { suggestion: err.suggestion },
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
|
-
const response = await domEval(script, options.frame);
|
|
35
|
+
const response = await domEval(script, options.frame, options.full);
|
|
36
36
|
if (response.status === 'error' || !response.data) {
|
|
37
37
|
const suggestion = await frameErrorSuggestion(response, options.frame);
|
|
38
38
|
return {
|
|
@@ -42,7 +42,7 @@ export async function handleDomEval(script, options) {
|
|
|
42
42
|
...(suggestion && { errorContext: { suggestion } }),
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
|
-
const { value, type, subtype, frame, warning } = response.data;
|
|
45
|
+
const { value, type, subtype, length, frame, warning } = response.data;
|
|
46
46
|
const hint = [
|
|
47
47
|
...(frame !== undefined ? [evalFrameLine(frame)] : []),
|
|
48
48
|
...(warning ? [warningMessage(warning)] : []),
|
|
@@ -50,7 +50,7 @@ export async function handleDomEval(script, options) {
|
|
|
50
50
|
return {
|
|
51
51
|
success: true,
|
|
52
52
|
data: {
|
|
53
|
-
...
|
|
53
|
+
...(options.json && !options.full ? boundEvalResult(value, length) : { result: value }),
|
|
54
54
|
type,
|
|
55
55
|
...(subtype && { subtype }),
|
|
56
56
|
...(frame !== undefined && { frame }),
|
|
@@ -60,21 +60,6 @@ export async function handleDomEval(script, options) {
|
|
|
60
60
|
};
|
|
61
61
|
}, options, (data) => formatDomEval(data, { full: options.full }));
|
|
62
62
|
}
|
|
63
|
-
/**
|
|
64
|
-
* The result field of the output: a string result in JSON cut to
|
|
65
|
-
* {@link MAX_VALUE_LENGTH} characters with `truncatedFrom`, unless `--full`
|
|
66
|
-
* (human output is cut when formatted).
|
|
67
|
-
*
|
|
68
|
-
* @param value - Evaluated value
|
|
69
|
-
* @param options - `--json`, `--full`
|
|
70
|
-
* @returns `result`, and `truncatedFrom` when cut
|
|
71
|
-
*/
|
|
72
|
-
function jsonResult(value, options) {
|
|
73
|
-
if (!options.json || options.full || typeof value !== 'string')
|
|
74
|
-
return { result: value };
|
|
75
|
-
const { text, truncatedFrom } = capLength(value, MAX_VALUE_LENGTH);
|
|
76
|
-
return { result: text, ...(truncatedFrom !== undefined && { truncatedFrom }) };
|
|
77
|
-
}
|
|
78
63
|
/**
|
|
79
64
|
* Suggestion of a failed eval; a frame not found (83) while the page is
|
|
80
65
|
* still loading says so (its iframes may not exist yet).
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounds of a `dom eval --json` result (#478): one eval must not put
|
|
3
|
+
* megabytes into an agent's context.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The `result` of `dom eval --json`, and what was left out of it. A string
|
|
7
|
+
* `result` with `truncatedFrom` while `type` is `object` is the start of
|
|
8
|
+
* the value's JSON text, not a string the script returned.
|
|
9
|
+
*/
|
|
10
|
+
export interface BoundedEvalResult {
|
|
11
|
+
/** The value, its first elements, or the start of its (JSON) text */
|
|
12
|
+
result: unknown;
|
|
13
|
+
/** Elements of an array result in the page, set only when it was bounded */
|
|
14
|
+
count?: number;
|
|
15
|
+
/** Elements left out of a listed array result */
|
|
16
|
+
omitted?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Length of the string, or of the JSON text of the copied object or array
|
|
19
|
+
* (which holds at most 1000 entries per list or object), `result` was cut from
|
|
20
|
+
*/
|
|
21
|
+
truncatedFrom?: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Bound an eval result for JSON output: a string is cut to
|
|
25
|
+
* {@link MAX_VALUE_LENGTH} characters, an array keeps its first
|
|
26
|
+
* {@link EVAL_JSON_ARRAY_LIMIT} elements (with `count` and `omitted`), and
|
|
27
|
+
* an object or array whose JSON is still longer than the cap becomes the
|
|
28
|
+
* start of its JSON text (with `truncatedFrom`), so the output stays valid
|
|
29
|
+
* JSON. Small values are returned as they are.
|
|
30
|
+
*
|
|
31
|
+
* @param value - Evaluated value, as copied from the page
|
|
32
|
+
* @param length - Elements of an array result in the page (its copy may hold fewer)
|
|
33
|
+
* @returns `result`, with what was left out when bounded
|
|
34
|
+
*/
|
|
35
|
+
export declare function boundEvalResult(value: unknown, length?: number): BoundedEvalResult;
|
|
36
|
+
//# sourceMappingURL=evalResult.d.ts.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounds of a `dom eval --json` result (#478): one eval must not put
|
|
3
|
+
* megabytes into an agent's context.
|
|
4
|
+
*/
|
|
5
|
+
import { EVAL_JSON_ARRAY_LIMIT, MAX_VALUE_LENGTH } from '../../../constants.js';
|
|
6
|
+
import { capLength } from '../../../utils/strings.js';
|
|
7
|
+
/**
|
|
8
|
+
* Bound an eval result for JSON output: a string is cut to
|
|
9
|
+
* {@link MAX_VALUE_LENGTH} characters, an array keeps its first
|
|
10
|
+
* {@link EVAL_JSON_ARRAY_LIMIT} elements (with `count` and `omitted`), and
|
|
11
|
+
* an object or array whose JSON is still longer than the cap becomes the
|
|
12
|
+
* start of its JSON text (with `truncatedFrom`), so the output stays valid
|
|
13
|
+
* JSON. Small values are returned as they are.
|
|
14
|
+
*
|
|
15
|
+
* @param value - Evaluated value, as copied from the page
|
|
16
|
+
* @param length - Elements of an array result in the page (its copy may hold fewer)
|
|
17
|
+
* @returns `result`, with what was left out when bounded
|
|
18
|
+
*/
|
|
19
|
+
export function boundEvalResult(value, length) {
|
|
20
|
+
if (typeof value === 'string') {
|
|
21
|
+
const { text, truncatedFrom } = capLength(value, MAX_VALUE_LENGTH);
|
|
22
|
+
return { result: text, ...(truncatedFrom !== undefined && { truncatedFrom }) };
|
|
23
|
+
}
|
|
24
|
+
if (Array.isArray(value))
|
|
25
|
+
return boundArray(value, length ?? value.length);
|
|
26
|
+
if (value === null || typeof value !== 'object')
|
|
27
|
+
return { result: value };
|
|
28
|
+
const json = JSON.stringify(value);
|
|
29
|
+
return json.length > MAX_VALUE_LENGTH ? jsonStart(json) : { result: value };
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Bound an array result: its first {@link EVAL_JSON_ARRAY_LIMIT} elements,
|
|
33
|
+
* or the start of its JSON text when even those are over the cap.
|
|
34
|
+
*
|
|
35
|
+
* @param value - Array result
|
|
36
|
+
* @param count - Elements of the array in the page
|
|
37
|
+
* @returns `result` with `count`, and `omitted` or `truncatedFrom`
|
|
38
|
+
*/
|
|
39
|
+
function boundArray(value, count) {
|
|
40
|
+
const listed = value.slice(0, EVAL_JSON_ARRAY_LIMIT);
|
|
41
|
+
const listedJson = JSON.stringify(listed);
|
|
42
|
+
if (listedJson.length > MAX_VALUE_LENGTH) {
|
|
43
|
+
const json = listed.length === value.length ? listedJson : JSON.stringify(value);
|
|
44
|
+
return { ...jsonStart(json), count };
|
|
45
|
+
}
|
|
46
|
+
return listed.length < count
|
|
47
|
+
? { result: listed, count, omitted: count - listed.length }
|
|
48
|
+
: { result: value };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The first {@link MAX_VALUE_LENGTH} characters of a JSON text.
|
|
52
|
+
*
|
|
53
|
+
* @param json - JSON text of an object or array over the cap
|
|
54
|
+
* @returns Its start, and the length of all of it
|
|
55
|
+
*/
|
|
56
|
+
function jsonStart(json) {
|
|
57
|
+
return { result: capLength(json, MAX_VALUE_LENGTH).text, truncatedFrom: json.length };
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=evalResult.js.map
|
|
@@ -93,7 +93,7 @@ export interface CompactCommand {
|
|
|
93
93
|
name: string;
|
|
94
94
|
/** Command aliases (only when it has some) */
|
|
95
95
|
aliases?: readonly string[];
|
|
96
|
-
/**
|
|
96
|
+
/** Summary if set, else the first line of the description */
|
|
97
97
|
description: string;
|
|
98
98
|
/** Arguments as in usage, e.g. "<selector> [index]" (only when it takes some) */
|
|
99
99
|
arguments?: string;
|
|
@@ -114,8 +114,8 @@ function argumentTerm(argument) {
|
|
|
114
114
|
return argument.required ? `<${name}>` : `[${name}]`;
|
|
115
115
|
}
|
|
116
116
|
/**
|
|
117
|
-
* Recursively converts a Commander Command to its compact summary:
|
|
118
|
-
* description line, arguments, and visible options with their descriptions.
|
|
117
|
+
* Recursively converts a Commander Command to its compact summary: summary
|
|
118
|
+
* (or first description line), arguments, and visible options with their descriptions.
|
|
119
119
|
* Empty fields are left out.
|
|
120
120
|
*
|
|
121
121
|
* @param command - Commander command instance
|
|
@@ -128,7 +128,7 @@ function convertCompactCommand(command) {
|
|
|
128
128
|
return {
|
|
129
129
|
name: command.name(),
|
|
130
130
|
...(aliases.length > 0 && { aliases }),
|
|
131
|
-
description: command.description().split('\n')[0] ?? '',
|
|
131
|
+
description: command.summary() || (command.description().split('\n')[0] ?? ''),
|
|
132
132
|
...(args && { arguments: args }),
|
|
133
133
|
...(options.length > 0 && {
|
|
134
134
|
options: Object.fromEntries(options.map((option) => [option.flags, option.description])),
|
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { commandPath } from './helpJson.js';
|
|
5
5
|
import { CommandError } from '../errors/index.js';
|
|
6
|
-
import { missingSubcommandMessage, unknownHelpTopicError, usageHelpSuggestion, } from '../errors/messages.js';
|
|
6
|
+
import { didYouMeanSuggestion, missingSubcommandMessage, unknownHelpTopicError, usageHelpSuggestion, } from '../errors/messages.js';
|
|
7
7
|
import { EXIT_CODES } from '../utils/exitCodes.js';
|
|
8
8
|
import { findSimilar } from '../utils/suggestions.js';
|
|
9
|
-
/**
|
|
10
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Commander's typo hint on its own line: "(Did you mean query?)" or, for
|
|
11
|
+
* several equally close candidates, "(Did you mean one of form, frames?)"
|
|
12
|
+
*/
|
|
13
|
+
const COMMANDER_HINT = /\n?\(Did you mean (?:one of )?(.+)\?\)\s*$/;
|
|
11
14
|
/**
|
|
12
15
|
* The command path of `bdg help <path...>`: the words before the first option.
|
|
13
16
|
*
|
|
@@ -54,7 +57,10 @@ export function splitCommanderHint(text) {
|
|
|
54
57
|
const hint = COMMANDER_HINT.exec(message);
|
|
55
58
|
if (!hint)
|
|
56
59
|
return { message };
|
|
57
|
-
return {
|
|
60
|
+
return {
|
|
61
|
+
message: message.slice(0, hint.index).trim(),
|
|
62
|
+
suggestion: didYouMeanSuggestion((hint[1] ?? '').split(', ')),
|
|
63
|
+
};
|
|
58
64
|
}
|
|
59
65
|
/** The option named in Commander's "unknown option '--x'" message, without an `=value` */
|
|
60
66
|
const UNKNOWN_OPTION = /unknown option '([^'=]+)/;
|
|
@@ -15,6 +15,7 @@ import { getSessionFilePath } from '../../session/paths.js';
|
|
|
15
15
|
import { applyFilters, parseFilterString } from '../../telemetry/filterDsl.js';
|
|
16
16
|
import { buildHAR } from '../../telemetry/har/builder.js';
|
|
17
17
|
import { createLogger } from '../../ui/logging/index.js';
|
|
18
|
+
import { harExportedMessage } from '../../ui/messages/networkMessages.js';
|
|
18
19
|
import { getErrorMessage } from '../../utils/errors.js';
|
|
19
20
|
import { VERSION } from '../../utils/version.js';
|
|
20
21
|
import { getNetworkRequests, validateFilterOption } from './shared.js';
|
|
@@ -67,15 +68,20 @@ async function getChromeVersion() {
|
|
|
67
68
|
function formatHARExport(data) {
|
|
68
69
|
if ('log' in data)
|
|
69
70
|
return JSON.stringify(data, null, 2);
|
|
70
|
-
|
|
71
|
-
return `✓ Exported ${data.entries} requests${filterNote} to ${data.file}`;
|
|
71
|
+
return harExportedMessage(data);
|
|
72
72
|
}
|
|
73
|
+
/** HAR files may hold credentials (--include-sensitive): readable by their owner only */
|
|
74
|
+
const HAR_FILE_MODE = 0o600;
|
|
73
75
|
/** Output path meaning "write the HAR to stdout" */
|
|
74
76
|
const STDOUT_PATH = '-';
|
|
75
77
|
/**
|
|
76
78
|
* Option for filter DSL.
|
|
77
79
|
*/
|
|
78
80
|
const filterDslOption = new Option('--filter <dsl>', 'Filter requests using DevTools DSL (e.g., "status-code:>=400")');
|
|
81
|
+
/**
|
|
82
|
+
* Option to keep credentials in the export.
|
|
83
|
+
*/
|
|
84
|
+
const includeSensitiveOption = new Option('--include-sensitive', 'Keep credentials (auth/cookie/API key headers, cookie values, URL tokens, password and token body fields); redacted by default');
|
|
79
85
|
/**
|
|
80
86
|
* Register HAR export command.
|
|
81
87
|
*
|
|
@@ -88,6 +94,7 @@ export function registerHarCommand(networkCmd) {
|
|
|
88
94
|
.description('Export network data as HAR 1.2 format')
|
|
89
95
|
.addOption(jsonOption())
|
|
90
96
|
.addOption(filterDslOption)
|
|
97
|
+
.addOption(includeSensitiveOption)
|
|
91
98
|
.action(async (outputFile, options) => {
|
|
92
99
|
await runCommand(async () => {
|
|
93
100
|
let requests = await getNetworkRequests();
|
|
@@ -103,21 +110,18 @@ export function registerHarCommand(networkCmd) {
|
|
|
103
110
|
if (outputPath !== STDOUT_PATH)
|
|
104
111
|
assertFilePath(outputPath, '.har');
|
|
105
112
|
const chromeVersion = await getChromeVersion();
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
...(chromeVersion && { chromeVersion }),
|
|
109
|
-
});
|
|
113
|
+
const includeSensitive = options.includeSensitive === true;
|
|
114
|
+
const har = buildHAR(requests, { version: VERSION, ...(chromeVersion && { chromeVersion }) }, { includeSensitive });
|
|
110
115
|
if (outputPath === STDOUT_PATH)
|
|
111
116
|
return { success: true, data: har };
|
|
112
|
-
const file = await writeOutputFile(outputPath, JSON.stringify(har, null, 2), '.har');
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
filtered,
|
|
119
|
-
},
|
|
117
|
+
const file = await writeOutputFile(outputPath, JSON.stringify(har, null, 2), '.har', HAR_FILE_MODE);
|
|
118
|
+
const data = {
|
|
119
|
+
file,
|
|
120
|
+
entries: har.log.entries.length,
|
|
121
|
+
filtered,
|
|
122
|
+
sanitized: !includeSensitive,
|
|
120
123
|
};
|
|
124
|
+
return { success: true, data };
|
|
121
125
|
}, options, formatHARExport);
|
|
122
126
|
});
|
|
123
127
|
}
|
|
@@ -101,9 +101,9 @@ const OPTION_BEHAVIORS = {
|
|
|
101
101
|
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.',
|
|
102
102
|
},
|
|
103
103
|
'eval:--full': {
|
|
104
|
-
default: 'Human output prints the first 20000 characters of the value followed by "… N more chars (use --full)"
|
|
105
|
-
whenEnabled: 'Prints the whole value, byte for byte',
|
|
106
|
-
tokenImpact: 'dom eval document.documentElement.outerHTML on Wikipedia is about 3.6 MB with --full; select what you need in the expression instead',
|
|
104
|
+
default: 'Human output prints the first 20000 characters of the value followed by "… 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',
|
|
105
|
+
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)',
|
|
106
|
+
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',
|
|
107
107
|
},
|
|
108
108
|
'console:--full': {
|
|
109
109
|
default: 'Message texts are cut: human output (summary, --list, --follow) at 200 characters followed by "… N more chars (use --full)"; JSON text at 10000 characters with truncatedFrom (the original length)',
|
|
@@ -330,6 +330,11 @@ const OPTION_BEHAVIORS = {
|
|
|
330
330
|
whenEnabled: 'Streams requests as they finish',
|
|
331
331
|
automaticBehavior: FOLLOW_BEHAVIOR,
|
|
332
332
|
},
|
|
333
|
+
'har:--include-sensitive': {
|
|
334
|
+
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 fields of JSON (primitives at any depth under such a name), form-urlencoded (also sniffed when the Content-Type says otherwise) and multipart request bodies. A JSON body too deep to walk becomes "[redacted]" whole. Header, cookie and parameter names, cookie attributes, headersSize and bodySize stay; log.comment and JSON sanitized: true say so',
|
|
335
|
+
whenEnabled: 'Writes every captured value (JSON sanitized: false); human output warns that the file holds credentials',
|
|
336
|
+
automaticBehavior: 'Matching is by name, so it over-redacts: harmless values under credential-looking names (tokenCount: 5, sessionLength) are replaced too, and credentials under other names are kept. Unlike Chrome DevTools, which drops these headers and empties cookies, names are kept so the HAR still shows a request was authenticated. Response bodies and WebSocket messages are not redacted. HAR files are written readable by their owner only (0600). network headers and network getCookies always show real values',
|
|
337
|
+
},
|
|
333
338
|
'peek:--verbose': {
|
|
334
339
|
default: 'Compact output (truncated URLs, no resource types)',
|
|
335
340
|
whenEnabled: 'Verbose output with full URLs and resource types',
|
|
@@ -300,6 +300,7 @@ export type NetworkCookiesCommandOptions = BaseOptions & {
|
|
|
300
300
|
/** Options for network HAR command */
|
|
301
301
|
export type NetworkHarCommandOptions = BaseOptions & {
|
|
302
302
|
outputFile?: string;
|
|
303
|
+
includeSensitive?: boolean;
|
|
303
304
|
};
|
|
304
305
|
/** Options for network headers command */
|
|
305
306
|
export type NetworkHeadersCommandOptions = BaseOptions & {
|
|
@@ -26,8 +26,9 @@ export declare function assertFilePath(filePath: string, extension?: string): vo
|
|
|
26
26
|
* @param filePath - Path the user gave
|
|
27
27
|
* @param data - File contents
|
|
28
28
|
* @param extension - Extension of the file kind, for examples in errors
|
|
29
|
+
* @param mode - File permissions of a text file (default: 0666 less the umask)
|
|
29
30
|
* @returns Absolute path written
|
|
30
31
|
* @throws CommandError naming the path when it cannot be written
|
|
31
32
|
*/
|
|
32
|
-
export declare function writeOutputFile(filePath: string, data: string | Buffer, extension?: string): Promise<string>;
|
|
33
|
+
export declare function writeOutputFile(filePath: string, data: string | Buffer, extension?: string, mode?: number): Promise<string>;
|
|
33
34
|
//# sourceMappingURL=outputFile.d.ts.map
|
|
@@ -62,18 +62,21 @@ export function assertFilePath(filePath, extension) {
|
|
|
62
62
|
* @param filePath - Path the user gave
|
|
63
63
|
* @param data - File contents
|
|
64
64
|
* @param extension - Extension of the file kind, for examples in errors
|
|
65
|
+
* @param mode - File permissions of a text file (default: 0666 less the umask)
|
|
65
66
|
* @returns Absolute path written
|
|
66
67
|
* @throws CommandError naming the path when it cannot be written
|
|
67
68
|
*/
|
|
68
|
-
export async function writeOutputFile(filePath, data, extension) {
|
|
69
|
+
export async function writeOutputFile(filePath, data, extension, mode) {
|
|
69
70
|
assertFilePath(filePath, extension);
|
|
70
71
|
const absolutePath = path.resolve(filePath);
|
|
71
72
|
try {
|
|
72
73
|
makeDirectory(path.dirname(absolutePath));
|
|
73
|
-
if (typeof data === 'string')
|
|
74
|
-
await AtomicFileWriter.writeAsync(absolutePath, data);
|
|
75
|
-
|
|
74
|
+
if (typeof data === 'string') {
|
|
75
|
+
await AtomicFileWriter.writeAsync(absolutePath, data, mode === undefined ? {} : { mode });
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
76
78
|
await AtomicFileWriter.writeBufferAsync(absolutePath, data);
|
|
79
|
+
}
|
|
77
80
|
}
|
|
78
81
|
catch (error) {
|
|
79
82
|
throw outputPathError(filePath, error, extension);
|
package/dist/commands/status.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { runCommand } from './shared/CommandRunner.js';
|
|
2
|
-
import { isDaemonConnectionError } from '../errors/index.js';
|
|
2
|
+
import { CommandError, isDaemonConnectionError } from '../errors/index.js';
|
|
3
3
|
import { invalidResponseError, sessionNotRespondingError } from '../errors/messages.js';
|
|
4
4
|
import { getStatus } from '../ipc/client.js';
|
|
5
5
|
import { IPCTimeoutError } from '../ipc/transport/IPCError.js';
|
|
@@ -134,6 +134,8 @@ export function registerStatusCommand(program) {
|
|
|
134
134
|
return { success: true, data: withSessionName(jsonOutput) };
|
|
135
135
|
}
|
|
136
136
|
catch (error) {
|
|
137
|
+
if (error instanceof CommandError)
|
|
138
|
+
throw error;
|
|
137
139
|
const errorMessage = getErrorMessage(error);
|
|
138
140
|
if (error instanceof IPCTimeoutError) {
|
|
139
141
|
const err = sessionNotRespondingError(error.timeoutMs / 1000);
|
package/dist/commands/stop.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { runCommand } from './shared/CommandRunner.js';
|
|
2
2
|
import { jsonOption } from './shared/commonOptions.js';
|
|
3
|
+
import { CommandError } from '../errors/index.js';
|
|
3
4
|
import { stopSession } from '../ipc/client.js';
|
|
4
5
|
import { IPCErrorCode } from '../ipc/index.js';
|
|
5
6
|
import { IPCTimeoutError } from '../ipc/transport/index.js';
|
|
@@ -97,7 +98,7 @@ export function registerStopCommand(program) {
|
|
|
97
98
|
}
|
|
98
99
|
}
|
|
99
100
|
catch (error) {
|
|
100
|
-
if (error instanceof IPCTimeoutError)
|
|
101
|
+
if (error instanceof IPCTimeoutError || error instanceof CommandError)
|
|
101
102
|
throw error;
|
|
102
103
|
const errorMessage = getErrorMessage(error);
|
|
103
104
|
if (isDaemonNotRunningError(errorMessage)) {
|
|
@@ -56,5 +56,51 @@ export declare function needsNoSandbox(): boolean;
|
|
|
56
56
|
* @returns True if running in Docker, false otherwise
|
|
57
57
|
*/
|
|
58
58
|
export declare function isDocker(): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Remove `HEADLESS` from this process's environment.
|
|
61
|
+
*
|
|
62
|
+
* chrome-launcher adds a bare `--headless` whenever the launching process has
|
|
63
|
+
* a non-empty `HEADLESS` variable (even `0` or `false`), whatever its
|
|
64
|
+
* `envVars` option says, which would make `--no-headless` launch headless.
|
|
65
|
+
* The daemon calls this before it launches Chrome.
|
|
66
|
+
*
|
|
67
|
+
* @param env - Environment to clean (defaults to `process.env`)
|
|
68
|
+
*/
|
|
69
|
+
export declare function dropLauncherHeadlessEnv(env?: NodeJS.ProcessEnv): void;
|
|
70
|
+
/**
|
|
71
|
+
* Build Chrome flags array from launch options.
|
|
72
|
+
*
|
|
73
|
+
* Uses chrome-launcher default flags as base (unless ignoreDefaultFlags is true)
|
|
74
|
+
* and layers bdg-specific overrides on top. Headless mode uses the new headless
|
|
75
|
+
* implementation for better compatibility.
|
|
76
|
+
*
|
|
77
|
+
* When running in Docker, automatically adds GPU-disabling flags to work around
|
|
78
|
+
* graphics limitations in containerized environments.
|
|
79
|
+
*
|
|
80
|
+
* Custom flags are passed via the chromeFlags option. The BDG_CHROME_FLAGS env var
|
|
81
|
+
* is parsed by the CLI and merged into chromeFlags before reaching this function.
|
|
82
|
+
* Feature lists from all sources end up in one `--disable-features` (and one
|
|
83
|
+
* `--enable-features`) flag, and each flag appears once.
|
|
84
|
+
*
|
|
85
|
+
* @param options - Launch options containing flag preferences
|
|
86
|
+
* @returns Array of Chrome command-line flags
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* // Standard flags
|
|
91
|
+
* const flags = buildChromeFlags({ port: 9222 });
|
|
92
|
+
*
|
|
93
|
+
* // Headless with custom flags
|
|
94
|
+
* const flags = buildChromeFlags({
|
|
95
|
+
* port: 9222,
|
|
96
|
+
* headless: true,
|
|
97
|
+
* chromeFlags: ['--window-size=1920,1080']
|
|
98
|
+
* });
|
|
99
|
+
*
|
|
100
|
+
* // Docker environment (auto-detects)
|
|
101
|
+
* const flags = buildChromeFlags({ port: 9222 });
|
|
102
|
+
* // Includes --disable-gpu, --no-sandbox if in Docker
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
59
105
|
export declare function buildChromeFlags(options: FlagsBuilderOptions): string[];
|
|
60
106
|
//# sourceMappingURL=flagsBuilder.d.ts.map
|