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.
Files changed (69) hide show
  1. package/.claude/skills/bdg/SKILL.md +1 -1
  2. package/dist/commands/cdp.js +1 -0
  3. package/dist/commands/cleanup.js +3 -0
  4. package/dist/commands/dom/eval.d.ts +2 -1
  5. package/dist/commands/dom/eval.js +6 -21
  6. package/dist/commands/dom/helpers/evalResult.d.ts +36 -0
  7. package/dist/commands/dom/helpers/evalResult.js +59 -0
  8. package/dist/commands/helpJson.d.ts +1 -1
  9. package/dist/commands/helpJson.js +3 -3
  10. package/dist/commands/helpTopic.js +10 -4
  11. package/dist/commands/network/har.js +18 -14
  12. package/dist/commands/optionBehaviors.js +8 -3
  13. package/dist/commands/shared/optionTypes.d.ts +1 -0
  14. package/dist/commands/shared/outputFile.d.ts +2 -1
  15. package/dist/commands/shared/outputFile.js +7 -4
  16. package/dist/commands/status.js +3 -1
  17. package/dist/commands/stop.js +2 -1
  18. package/dist/connection/launcher/flagsBuilder.d.ts +46 -0
  19. package/dist/connection/launcher/flagsBuilder.js +107 -23
  20. package/dist/connection/launcher.d.ts +1 -1
  21. package/dist/connection/launcher.js +1 -2
  22. package/dist/constants.d.ts +2 -4
  23. package/dist/constants.js +2 -4
  24. package/dist/daemon/launcher.d.ts +17 -3
  25. package/dist/daemon/launcher.js +37 -7
  26. package/dist/daemon/session/commandRegistry.js +2 -2
  27. package/dist/daemon.js +8107 -7962
  28. package/dist/errors/messages.d.ts +23 -0
  29. package/dist/errors/messages.js +86 -6
  30. package/dist/index.js +555 -166
  31. package/dist/ipc/client.d.ts +6 -1
  32. package/dist/ipc/client.js +11 -2
  33. package/dist/ipc/protocol/commands.d.ts +4 -0
  34. package/dist/ipc/transport/index.d.ts +6 -0
  35. package/dist/ipc/transport/index.js +16 -1
  36. package/dist/runtime/dom/elementInfo.d.ts +7 -0
  37. package/dist/runtime/dom/elementInfo.js +8 -1
  38. package/dist/runtime/dom/evalHelpers.d.ts +24 -4
  39. package/dist/runtime/dom/evalHelpers.js +40 -12
  40. package/dist/runtime/dom/frames.d.ts +2 -1
  41. package/dist/runtime/dom/frames.js +3 -1
  42. package/dist/runtime/page/emulation.js +6 -5
  43. package/dist/runtime/page/userAgent.d.ts +86 -2
  44. package/dist/runtime/page/userAgent.js +154 -33
  45. package/dist/session/paths.d.ts +38 -3
  46. package/dist/session/paths.js +154 -7
  47. package/dist/session/portClaims.d.ts +0 -8
  48. package/dist/session/portClaims.js +1 -22
  49. package/dist/session/sessionList.d.ts +5 -1
  50. package/dist/session/sessionList.js +5 -1
  51. package/dist/telemetry/har/builder.d.ts +12 -1
  52. package/dist/telemetry/har/builder.js +10 -2
  53. package/dist/telemetry/har/sanitize.d.ts +24 -0
  54. package/dist/telemetry/har/sanitize.js +138 -0
  55. package/dist/telemetry/har/sanitizeBody.d.ts +38 -0
  56. package/dist/telemetry/har/sanitizeBody.js +168 -0
  57. package/dist/ui/formatters/sessions.d.ts +3 -2
  58. package/dist/ui/formatters/sessions.js +10 -3
  59. package/dist/ui/messages/chrome.d.ts +14 -6
  60. package/dist/ui/messages/chrome.js +52 -12
  61. package/dist/ui/messages/networkMessages.d.ts +26 -0
  62. package/dist/ui/messages/networkMessages.js +21 -0
  63. package/dist/ui/messages/session.d.ts +8 -0
  64. package/dist/ui/messages/session.js +10 -0
  65. package/dist/utils/atomicFile.d.ts +2 -1
  66. package/dist/utils/atomicFile.js +5 -2
  67. package/dist/utils/directories.d.ts +41 -0
  68. package/dist/utils/directories.js +48 -0
  69. 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
@@ -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)')
@@ -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
- * (a string result in JSON with `truncatedFrom`) unless `--full`.
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
- * (a string result in JSON with `truncatedFrom`) unless `--full`.
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
- ...jsonResult(value, options),
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
- /** First line of the description */
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: first
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
- /** Commander's typo hint on its own line, e.g. "(Did you mean query?)" */
10
- const COMMANDER_HINT = /\n?\(Did you mean (.+)\?\)\s*$/;
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 { message: message.slice(0, hint.index).trim(), suggestion: `Did you mean: ${hint[1]}?` };
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
- const filterNote = data.filtered ? ' (filtered)' : '';
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 har = buildHAR(requests, {
107
- version: VERSION,
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
- return {
114
- success: true,
115
- data: {
116
- file,
117
- entries: har.log.entries.length,
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)"; in JSON a string result is cut to 20000 characters with truncatedFrom (the original length). Objects and arrays in JSON are not cut',
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
- else
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);
@@ -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);
@@ -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