browser-debugger-cli 0.13.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 (159) hide show
  1. package/.claude/skills/bdg/SKILL.md +101 -187
  2. package/README.md +4 -4
  3. package/dist/commands/cdp.js +1 -0
  4. package/dist/commands/cleanup.js +3 -0
  5. package/dist/commands/console.js +5 -1
  6. package/dist/commands/dom/a11y.d.ts +1 -1
  7. package/dist/commands/dom/a11y.js +20 -20
  8. package/dist/commands/dom/eval.d.ts +3 -1
  9. package/dist/commands/dom/eval.js +8 -5
  10. package/dist/commands/dom/formInteraction.js +1 -1
  11. package/dist/commands/dom/get.js +25 -7
  12. package/dist/commands/dom/helpers/evalResult.d.ts +36 -0
  13. package/dist/commands/dom/helpers/evalResult.js +59 -0
  14. package/dist/commands/dom/index.js +7 -2
  15. package/dist/commands/dom/query.d.ts +2 -1
  16. package/dist/commands/dom/query.js +5 -3
  17. package/dist/commands/dom/screenshot.js +1 -0
  18. package/dist/commands/helpJson.d.ts +1 -1
  19. package/dist/commands/helpJson.js +4 -4
  20. package/dist/commands/helpTopic.js +10 -4
  21. package/dist/commands/network/har.js +18 -14
  22. package/dist/commands/network/list.js +46 -3
  23. package/dist/commands/optionBehaviors.d.ts +25 -2
  24. package/dist/commands/optionBehaviors.js +60 -42
  25. package/dist/commands/peek.js +3 -0
  26. package/dist/commands/shared/CommandRunner.js +13 -13
  27. package/dist/commands/shared/daemonErrorHandler.js +2 -2
  28. package/dist/commands/shared/dataFetcher.d.ts +4 -2
  29. package/dist/commands/shared/dataFetcher.js +11 -3
  30. package/dist/commands/shared/handleValidationError.js +3 -3
  31. package/dist/commands/shared/optionTypes.d.ts +15 -3
  32. package/dist/commands/shared/outputFile.d.ts +2 -1
  33. package/dist/commands/shared/outputFile.js +7 -4
  34. package/dist/commands/shared/startHelpers.js +3 -3
  35. package/dist/commands/status.js +3 -1
  36. package/dist/commands/stop.js +2 -1
  37. package/dist/connection/chromeIdentity.d.ts +8 -2
  38. package/dist/connection/chromeIdentity.js +85 -13
  39. package/dist/connection/launcher/flagsBuilder.d.ts +46 -0
  40. package/dist/connection/launcher/flagsBuilder.js +107 -23
  41. package/dist/connection/launcher.d.ts +1 -1
  42. package/dist/connection/launcher.js +1 -2
  43. package/dist/constants.d.ts +31 -5
  44. package/dist/constants.js +37 -5
  45. package/dist/daemon/SessionController.js +2 -0
  46. package/dist/daemon/launcher.d.ts +17 -3
  47. package/dist/daemon/launcher.js +37 -7
  48. package/dist/daemon/session/Session.d.ts +2 -1
  49. package/dist/daemon/session/Session.js +10 -2
  50. package/dist/daemon/session/TelemetryStore.d.ts +7 -0
  51. package/dist/daemon/session/TelemetryStore.js +6 -0
  52. package/dist/daemon/session/commandRegistry.js +25 -7
  53. package/dist/daemon/session/matchedStylesReset.d.ts +26 -0
  54. package/dist/daemon/session/matchedStylesReset.js +46 -0
  55. package/dist/daemon/session/plugins.js +1 -0
  56. package/dist/daemon/session/triggeredRequests.d.ts +0 -5
  57. package/dist/daemon/session/triggeredRequests.js +13 -7
  58. package/dist/daemon.js +8742 -8315
  59. package/dist/errors/messages.d.ts +31 -0
  60. package/dist/errors/messages.js +96 -6
  61. package/dist/index.js +1129 -548
  62. package/dist/ipc/client.d.ts +6 -1
  63. package/dist/ipc/client.js +11 -2
  64. package/dist/ipc/protocol/commands.d.ts +8 -0
  65. package/dist/ipc/protocol/inspectTypes.d.ts +5 -2
  66. package/dist/ipc/session/types.d.ts +5 -1
  67. package/dist/ipc/transport/index.d.ts +6 -0
  68. package/dist/ipc/transport/index.js +16 -1
  69. package/dist/program.d.ts +14 -0
  70. package/dist/program.js +53 -0
  71. package/dist/runtime/dom/elementGeometry.d.ts +23 -0
  72. package/dist/runtime/dom/elementGeometry.js +17 -15
  73. package/dist/runtime/dom/elementInfo.d.ts +13 -4
  74. package/dist/runtime/dom/elementInfo.js +15 -5
  75. package/dist/runtime/dom/evalHelpers.d.ts +24 -4
  76. package/dist/runtime/dom/evalHelpers.js +40 -12
  77. package/dist/runtime/dom/frameScopedConnection.d.ts +7 -0
  78. package/dist/runtime/dom/frameScopedConnection.js +2 -2
  79. package/dist/runtime/dom/frames.d.ts +2 -1
  80. package/dist/runtime/dom/frames.js +3 -1
  81. package/dist/runtime/dom/inspect.d.ts +17 -3
  82. package/dist/runtime/dom/inspect.js +40 -26
  83. package/dist/runtime/dom/inspectModel.d.ts +3 -3
  84. package/dist/runtime/dom/inspectRules.d.ts +29 -3
  85. package/dist/runtime/dom/inspectRules.js +205 -11
  86. package/dist/runtime/dom/layout.d.ts +0 -2
  87. package/dist/runtime/dom/layout.js +1 -2
  88. package/dist/runtime/dom/reactEventHelpers.d.ts +4 -1
  89. package/dist/runtime/dom/reactEventHelpers.js +9 -2
  90. package/dist/runtime/dom/targetNode.d.ts +10 -6
  91. package/dist/runtime/dom/targetNode.js +15 -8
  92. package/dist/runtime/page/emulation.js +6 -5
  93. package/dist/runtime/page/userAgent.d.ts +86 -2
  94. package/dist/runtime/page/userAgent.js +154 -33
  95. package/dist/session/paths.d.ts +38 -3
  96. package/dist/session/paths.js +154 -7
  97. package/dist/session/portClaims.d.ts +0 -8
  98. package/dist/session/portClaims.js +1 -22
  99. package/dist/session/sessionList.d.ts +5 -1
  100. package/dist/session/sessionList.js +5 -1
  101. package/dist/telemetry/a11y.d.ts +15 -1
  102. package/dist/telemetry/a11y.js +83 -0
  103. package/dist/telemetry/har/builder.d.ts +12 -1
  104. package/dist/telemetry/har/builder.js +11 -3
  105. package/dist/telemetry/har/sanitize.d.ts +24 -0
  106. package/dist/telemetry/har/sanitize.js +138 -0
  107. package/dist/telemetry/har/sanitizeBody.d.ts +38 -0
  108. package/dist/telemetry/har/sanitizeBody.js +168 -0
  109. package/dist/telemetry/network.d.ts +13 -16
  110. package/dist/telemetry/network.js +30 -52
  111. package/dist/telemetry/networkRetention.d.ts +83 -0
  112. package/dist/telemetry/networkRetention.js +117 -0
  113. package/dist/types.d.ts +26 -0
  114. package/dist/ui/OutputBuilder.d.ts +10 -0
  115. package/dist/ui/OutputBuilder.js +12 -0
  116. package/dist/ui/formatters/a11y.d.ts +5 -7
  117. package/dist/ui/formatters/a11y.js +7 -61
  118. package/dist/ui/formatters/console/chronological.js +4 -4
  119. package/dist/ui/formatters/console/follow.d.ts +4 -2
  120. package/dist/ui/formatters/console/follow.js +6 -3
  121. package/dist/ui/formatters/console/json.d.ts +3 -6
  122. package/dist/ui/formatters/console/json.js +9 -13
  123. package/dist/ui/formatters/console/shared.d.ts +17 -2
  124. package/dist/ui/formatters/console/shared.js +17 -0
  125. package/dist/ui/formatters/console/summarize.d.ts +2 -2
  126. package/dist/ui/formatters/console/summarize.js +22 -7
  127. package/dist/ui/formatters/console.d.ts +1 -1
  128. package/dist/ui/formatters/console.js +1 -5
  129. package/dist/ui/formatters/details.js +1 -1
  130. package/dist/ui/formatters/dom.d.ts +13 -4
  131. package/dist/ui/formatters/dom.js +25 -7
  132. package/dist/ui/formatters/layout.js +2 -1
  133. package/dist/ui/formatters/longValues.d.ts +14 -0
  134. package/dist/ui/formatters/longValues.js +23 -0
  135. package/dist/ui/formatters/networkList.d.ts +8 -2
  136. package/dist/ui/formatters/networkList.js +11 -2
  137. package/dist/ui/formatters/preview.d.ts +4 -1
  138. package/dist/ui/formatters/preview.js +55 -13
  139. package/dist/ui/formatters/sessions.d.ts +3 -2
  140. package/dist/ui/formatters/sessions.js +10 -3
  141. package/dist/ui/formatters/status.js +7 -0
  142. package/dist/ui/formatters/triggeredRequests.js +2 -1
  143. package/dist/ui/messages/chrome.d.ts +34 -7
  144. package/dist/ui/messages/chrome.js +81 -15
  145. package/dist/ui/messages/commands.d.ts +29 -8
  146. package/dist/ui/messages/commands.js +36 -8
  147. package/dist/ui/messages/networkMessages.d.ts +50 -0
  148. package/dist/ui/messages/networkMessages.js +66 -0
  149. package/dist/ui/messages/session.d.ts +8 -0
  150. package/dist/ui/messages/session.js +10 -0
  151. package/dist/utils/atomicFile.d.ts +2 -1
  152. package/dist/utils/atomicFile.js +5 -2
  153. package/dist/utils/directories.d.ts +41 -0
  154. package/dist/utils/directories.js +48 -0
  155. package/dist/utils/http.d.ts +9 -2
  156. package/dist/utils/http.js +4 -3
  157. package/dist/utils/strings.d.ts +19 -0
  158. package/dist/utils/strings.js +16 -0
  159. package/package.json +2 -2
@@ -58,7 +58,7 @@ export function registerFormInteractionCommands(program) {
58
58
  .command('fill')
59
59
  .description('Fill a form field with a value (React-compatible, waits for stability)')
60
60
  .argument('<selectorOrIndex>', SELECTOR_OR_INDEX_ARGUMENT)
61
- .argument('<value>', 'Value to fill (file inputs: paths separated by commas, "" clears)')
61
+ .argument('<value>', 'Value to fill (file inputs: local file paths, separated by commas, that are uploaded to the page; "" clears)')
62
62
  .option('--index <n>', 'Element index if selector matches multiple (0-based)', integerOption(0))
63
63
  .option('--no-blur', 'Do not blur after filling (keeps focus on element)')
64
64
  .option('--no-wait', 'Skip waiting for network stability after fill')
@@ -9,12 +9,14 @@ import { DomElementResolver } from './DomElementResolver.js';
9
9
  import { getDOMElements, getDomContext, selectMatch, } from './helpers/index.js';
10
10
  import { formatSemanticNodeWithContext, resolveNodeWithFallback, } from './semanticUtils.js';
11
11
  import { runCommand } from '../shared/CommandRunner.js';
12
+ import { MAX_VALUE_LENGTH } from '../../constants.js';
12
13
  import { CommandError } from '../../errors/index.js';
13
14
  import { conflictingOptionsError, indexWithIndexOptionError, nodeIdNotFoundError, optionRequiresError, } from '../../errors/messages.js';
14
15
  import { resolveA11yNode } from '../../telemetry/a11y.js';
15
16
  import { formatDomGet } from '../../ui/formatters/dom.js';
16
17
  import { EXIT_CODES } from '../../utils/exitCodes.js';
17
18
  import { filterDefined } from '../../utils/objects.js';
19
+ import { capLength } from '../../utils/strings.js';
18
20
  /** What `bdg dom get` reads without a selector */
19
21
  export const DOM_GET_DEFAULT_SELECTOR = 'body';
20
22
  /**
@@ -58,7 +60,7 @@ async function semanticElement(backendNodeId, full) {
58
60
  * Raw view: the element(s) with attributes and outer HTML.
59
61
  *
60
62
  * @param selectorOrIndex - CSS selector or cached index
61
- * @param options - Command options (`--all`, `--index`)
63
+ * @param options - Command options (`--all`, `--index`, `--full`)
62
64
  */
63
65
  async function handleRawGet(selectorOrIndex, options) {
64
66
  await runCommand(async () => {
@@ -70,8 +72,27 @@ async function handleRawGet(selectorOrIndex, options) {
70
72
  all: options.all,
71
73
  nth: matchIndex(options),
72
74
  });
73
- return { success: true, data: await getDOMElements(getOptions) };
74
- }, options, formatDomGet);
75
+ return { success: true, data: rawGetData(await getDOMElements(getOptions), options) };
76
+ }, options, (data) => formatDomGet(data, { full: options.full }));
77
+ }
78
+ /**
79
+ * Raw elements for the output: in JSON each outer HTML is cut to
80
+ * {@link MAX_VALUE_LENGTH} characters with `truncatedFrom`, unless `--full`
81
+ * (human output is cut when formatted).
82
+ *
83
+ * @param result - Elements read
84
+ * @param options - `--json`, `--full`
85
+ * @returns Elements to output
86
+ */
87
+ function rawGetData(result, options) {
88
+ if (!options.json || options.full)
89
+ return result;
90
+ return {
91
+ nodes: result.nodes.map((node) => {
92
+ const { text, truncatedFrom } = capLength(node.outerHTML ?? '', MAX_VALUE_LENGTH);
93
+ return truncatedFrom === undefined ? node : { ...node, outerHTML: text, truncatedFrom };
94
+ }),
95
+ };
75
96
  }
76
97
  /**
77
98
  * Semantic view of the target.
@@ -105,9 +126,6 @@ function getOptionsConflict(selectorOrIndex, options) {
105
126
  if (options.nodeId !== undefined && selectorOrIndex !== undefined) {
106
127
  return conflictingOptionsError('--node-id', 'a selector or index');
107
128
  }
108
- if (options.full && (options.raw || options.nodeId !== undefined)) {
109
- return conflictingOptionsError('--full', options.raw ? '--raw' : '--node-id');
110
- }
111
129
  if (options.index !== undefined && options.nth !== undefined) {
112
130
  return conflictingOptionsError('--index', '--nth');
113
131
  }
@@ -134,7 +152,7 @@ export async function handleDomGet(selectorOrIndex, options) {
134
152
  }
135
153
  const { nodeId } = options;
136
154
  if (nodeId !== undefined) {
137
- await runCommand(async () => ({ success: true, data: await getDOMElements({ nodeId }) }), options, formatDomGet);
155
+ await runCommand(async () => ({ success: true, data: rawGetData(await getDOMElements({ nodeId }), options) }), options, (data) => formatDomGet(data, { full: options.full }));
138
156
  return;
139
157
  }
140
158
  const target = selectorOrIndex ?? DOM_GET_DEFAULT_SELECTOR;
@@ -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
@@ -31,6 +31,9 @@ import { handleDomScreenshot } from './screenshot.js';
31
31
  import { registerWaitCommand } from './wait.js';
32
32
  import { SELECTOR_OR_INDEX_ARGUMENT, SELECTOR_SCOPE_HELP, } from '../shared/commonOptions.js';
33
33
  import { integerOption, screenshotFormatOption } from '../shared/validation.js';
34
+ import { MAX_VALUE_LENGTH, QUERY_JSON_LIST_LIMIT } from '../../constants.js';
35
+ /** Help of `--full` on `dom eval` and the `eval` shortcut */
36
+ const EVAL_FULL_HELP = `Print the whole value (default: the first ${MAX_VALUE_LENGTH} characters; a string result in JSON too)`;
34
37
  /**
35
38
  * Register DOM telemetry commands on the root Commander program.
36
39
  */
@@ -50,7 +53,7 @@ export function registerDomCommands(program) {
50
53
  .command('query')
51
54
  .description('Find elements by CSS selector')
52
55
  .argument('<selector>', 'CSS selector (e.g., ".error", "#app", "button")')
53
- .option('--limit <n>', `Matches to list (default: ${QUERY_LIST_LIMIT}, ${QUERY_CACHE_LIMIT} with --json; 0 = all); the first ${QUERY_CACHE_LIMIT} (or more with a higher limit) are indexed`, integerOption(0))
56
+ .option('--limit <n>', `Matches to list (default: ${QUERY_LIST_LIMIT}, ${QUERY_JSON_LIST_LIMIT} with --json; 0 = all); the first ${QUERY_CACHE_LIMIT} (or more with a higher limit) are indexed`, integerOption(0))
54
57
  .option('-j, --json', 'Output as JSON')
55
58
  .addHelpText('after', SELECTOR_SCOPE_HELP)
56
59
  .action(async (selector, options) => {
@@ -61,6 +64,7 @@ export function registerDomCommands(program) {
61
64
  .description('Evaluate JavaScript expression in the page context')
62
65
  .argument('<script>', 'JavaScript to execute (e.g., "document.title", "window.location.href")')
63
66
  .option('--frame <frame>', 'Evaluate in an iframe, cross-origin ones included: index (from dom frames; 87 when stale), name/id attribute, or part of the name, id or URL')
67
+ .option('--full', EVAL_FULL_HELP)
64
68
  .option('-j, --json', 'Output as JSON')
65
69
  .action(async (script, options) => {
66
70
  await handleDomEval(script, options);
@@ -70,6 +74,7 @@ export function registerDomCommands(program) {
70
74
  .description('Shortcut for: bdg dom eval')
71
75
  .argument('<script>', 'JavaScript to execute')
72
76
  .option('--frame <frame>', 'Evaluate in an iframe (see dom frames)')
77
+ .option('--full', EVAL_FULL_HELP)
73
78
  .option('-j, --json', 'Output as JSON')
74
79
  .action(async (script, options) => {
75
80
  await handleDomEval(script, options);
@@ -86,7 +91,7 @@ export function registerDomCommands(program) {
86
91
  .description('Get semantic accessibility structure (default) or raw HTML (--raw)')
87
92
  .argument('[selectorOrIndex]', `${SELECTOR_OR_INDEX_ARGUMENT} (e.g. ".error", "#app", 0); default: ${DOM_GET_DEFAULT_SELECTOR}`)
88
93
  .option('--raw', 'Output raw HTML with all filtering options')
89
- .option('--full', 'Show all of the element text (default: the first 500 characters)')
94
+ .option('--full', `Show all of the element text (default: the first 500 characters); with --raw, all of the HTML (default: the first ${MAX_VALUE_LENGTH} characters)`)
90
95
  .option('--all', 'Get all matches (only with --raw)')
91
96
  .option('--index <n>', 'Element index if selector matches multiple (0-based)', integerOption(0))
92
97
  .addOption(new Option('--nth <n>', 'Alias of --index').argParser(integerOption(0)).hideHelp())
@@ -10,7 +10,8 @@ export declare const QUERY_LIST_LIMIT = 50;
10
10
  *
11
11
  * Runs the selector, caches the described matches so later commands can
12
12
  * reference elements by index, and lists the first `--limit` of them (50,
13
- * or {@link QUERY_CACHE_LIMIT} with `--json`; 0 = all) with the total count.
13
+ * or {@link QUERY_JSON_LIST_LIMIT} with `--json`; 0 = all) with the total
14
+ * count. The first {@link QUERY_CACHE_LIMIT} are indexed whatever is listed.
14
15
  * No match exits 83, like `dom get` and `dom a11y`, and clears the cache so
15
16
  * indices of an earlier query are not used by mistake.
16
17
  */
@@ -2,8 +2,9 @@
2
2
  * `bdg dom query` — find elements by CSS selector and populate the query cache.
3
3
  */
4
4
  import { noMatchesError, pageDocumentId, queryDOMElements } from './helpers/index.js';
5
- import { QUERY_CACHE_LIMIT, VIEWPORT_HINT_LIMIT } from './helpers/query.js';
5
+ import { VIEWPORT_HINT_LIMIT } from './helpers/query.js';
6
6
  import { runCommand } from '../shared/CommandRunner.js';
7
+ import { QUERY_JSON_LIST_LIMIT } from '../../constants.js';
7
8
  import { QueryCacheManager } from '../../session/QueryCacheManager.js';
8
9
  import { formatDomQuery } from '../../ui/formatters/dom.js';
9
10
  import { EXIT_CODES } from '../../utils/exitCodes.js';
@@ -14,12 +15,13 @@ export const QUERY_LIST_LIMIT = 50;
14
15
  *
15
16
  * Runs the selector, caches the described matches so later commands can
16
17
  * reference elements by index, and lists the first `--limit` of them (50,
17
- * or {@link QUERY_CACHE_LIMIT} with `--json`; 0 = all) with the total count.
18
+ * or {@link QUERY_JSON_LIST_LIMIT} with `--json`; 0 = all) with the total
19
+ * count. The first {@link QUERY_CACHE_LIMIT} are indexed whatever is listed.
18
20
  * No match exits 83, like `dom get` and `dom a11y`, and clears the cache so
19
21
  * indices of an earlier query are not used by mistake.
20
22
  */
21
23
  export async function handleDomQuery(selector, options) {
22
- const limit = options.limit ?? (options.json ? QUERY_CACHE_LIMIT : QUERY_LIST_LIMIT);
24
+ const limit = options.limit ?? (options.json ? QUERY_JSON_LIST_LIMIT : QUERY_LIST_LIMIT);
23
25
  await runCommand(async () => {
24
26
  const document = await pageDocumentId();
25
27
  const result = await queryDOMElements(selector, limit);
@@ -189,6 +189,7 @@ async function handleSequenceCapture(outputDir, options) {
189
189
  /**
190
190
  * End a capture sequence on an error (e.g. the element disappeared): print
191
191
  * it (one JSON line with `--json`, like the frames) and exit with its code.
192
+ * It stays compact on a terminal too, since it ends the NDJSON frame stream.
192
193
  *
193
194
  * @param error - Capture error
194
195
  * @param captured - Frames captured before it
@@ -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;
@@ -36,7 +36,7 @@ function convertOption(option, commandName) {
36
36
  if (option.argChoices) {
37
37
  metadata.choices = option.argChoices;
38
38
  }
39
- const behavior = getOptionBehavior(commandName, option.flags);
39
+ const behavior = getOptionBehavior(commandName, option);
40
40
  if (behavior) {
41
41
  metadata.behavior = behavior;
42
42
  }
@@ -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
  }
@@ -112,13 +112,51 @@ function buildFormatOptions(options, result, lastLimit) {
112
112
  totalCount: result.totalCount,
113
113
  filteredCount: result.filteredCount,
114
114
  ...(result.pageStart && { pageStart: result.pageStart }),
115
+ evictions: {
116
+ requestsDropped: result.dropped ?? 0,
117
+ bodiesEvicted: result.bodiesEvicted ?? 0,
118
+ },
119
+ };
120
+ }
121
+ /**
122
+ * Watch the session's dropped/evicted counts across follow polls, so the
123
+ * stream notes them once per kind instead of on every poll.
124
+ *
125
+ * @returns Function giving the counts when requests or bodies were let go
126
+ * for the first time since the stream started, otherwise undefined
127
+ */
128
+ function newEvictionKinds() {
129
+ let requestsNoted = false;
130
+ let bodiesNoted = false;
131
+ return (counts) => {
132
+ const newRequests = !requestsNoted && counts.requestsDropped > 0;
133
+ const newBodies = !bodiesNoted && counts.bodiesEvicted > 0;
134
+ requestsNoted ||= newRequests;
135
+ bodiesNoted ||= newBodies;
136
+ return newRequests || newBodies ? counts : undefined;
137
+ };
138
+ }
139
+ /**
140
+ * JSON fields of the dropped/evicted counts (the non-zero ones).
141
+ *
142
+ * @param counts - Counts to report, if any
143
+ * @returns `dropped` and `bodiesEvicted` when non-zero
144
+ */
145
+ function evictionFields(counts) {
146
+ if (!counts)
147
+ return {};
148
+ return {
149
+ ...(counts.requestsDropped > 0 && { dropped: counts.requestsDropped }),
150
+ ...(counts.bodiesEvicted > 0 && { bodiesEvicted: counts.bodiesEvicted }),
115
151
  };
116
152
  }
117
153
  /**
118
154
  * Stream network requests: the last `lastN` finished ones at start, then
119
155
  * each request once, when it has finished loading or failed (a request whose
120
156
  * headers arrived but whose body is still loading waits), like `tail -f`,
121
- * with a warning (JSON `pageCrashedAt`) once when the page crashes.
157
+ * with a warning (JSON `pageCrashedAt`) once when the page crashes, and a
158
+ * note (JSON `dropped`, `bodiesEvicted`) the first time the session drops
159
+ * requests or evicts bodies at its limits.
122
160
  *
123
161
  * @param options - Command options
124
162
  * @param resourceTypes - Validated resource types
@@ -127,6 +165,7 @@ function buildFormatOptions(options, result, lastLimit) {
127
165
  async function runFollowMode(options, resourceTypes, lastN) {
128
166
  const shown = new Set();
129
167
  const newCrash = newPageCrashes();
168
+ const newEviction = newEvictionKinds();
130
169
  let started = false;
131
170
  const showNetwork = async () => {
132
171
  const result = await fetchNetworkRequests(filtersNeedHeaders(options));
@@ -136,6 +175,7 @@ async function runFollowMode(options, resourceTypes, lastN) {
136
175
  noteFollowConnected();
137
176
  const { requests } = result.data;
138
177
  const crashedAt = newCrash(result.data.pageCrashedAt);
178
+ const evictions = newEviction(result.data.evictions);
139
179
  const finished = filterRequests(requests, options, resourceTypes).filter((request) => request.duration !== undefined && !shown.has(request.requestId));
140
180
  const present = new Set(requests.map((request) => request.requestId));
141
181
  for (const id of shown)
@@ -144,12 +184,13 @@ async function runFollowMode(options, resourceTypes, lastN) {
144
184
  finished.forEach((request) => shown.add(request.requestId));
145
185
  const fresh = started || lastN === 0 ? finished : finished.slice(-lastN);
146
186
  if (options.json) {
147
- if (!started || fresh.length > 0 || crashedAt !== undefined) {
187
+ if (!started || fresh.length > 0 || crashedAt !== undefined || evictions) {
148
188
  const data = {
149
189
  requests: fresh,
150
190
  totalCount: requests.length,
151
191
  filteredCount: fresh.length,
152
192
  ...(crashedAt !== undefined && { pageCrashedAt: crashedAt }),
193
+ ...evictionFields(evictions),
153
194
  };
154
195
  console.log(JSON.stringify(buildSuccessResponse(data)));
155
196
  }
@@ -160,6 +201,7 @@ async function runFollowMode(options, resourceTypes, lastN) {
160
201
  header: !started,
161
202
  verbose: options.verbose ?? false,
162
203
  ...(pageStart && { pageStart }),
204
+ ...(evictions && { evictions }),
163
205
  });
164
206
  if (text)
165
207
  console.log(text);
@@ -227,7 +269,7 @@ export function registerListCommand(networkCmd) {
227
269
  }
228
270
  return createErrorResult(result.error, result.exitCode, result.suggestion);
229
271
  }
230
- const { requests, pageCrashedAt } = result.data;
272
+ const { requests, pageCrashedAt, evictions } = result.data;
231
273
  const filtered = filterRequests(requests, options, resourceTypes);
232
274
  const pageStart = pageStartOf(requests);
233
275
  return {
@@ -238,6 +280,7 @@ export function registerListCommand(networkCmd) {
238
280
  filteredCount: filtered.length,
239
281
  ...(pageStart && { pageStart }),
240
282
  ...(pageCrashedAt !== undefined && { pageCrashedAt }),
283
+ ...evictionFields(evictions),
241
284
  },
242
285
  };
243
286
  }, options, (data) => withPageCrashedNote(formatNetworkList(data.requests, buildFormatOptions(options, data, lastN)), data.pageCrashedAt));
@@ -6,13 +6,36 @@
6
6
  *
7
7
  * @see docs/principles/SELF_DOCUMENTING_SYSTEMS.md
8
8
  */
9
+ import type { Option } from 'commander';
9
10
  import type { OptionBehavior } from './helpJson.js';
11
+ /**
12
+ * Registry key format: last command name, colon, long flag (the short flag
13
+ * when there is no long one), e.g. "screenshot:--no-resize", "bdg:--headless"
14
+ */
15
+ type BehaviorKey = string;
16
+ /**
17
+ * Build behavior registry key from command and option: the command's own
18
+ * name (`bdg` for the root) and the option's long flag, or its short flag
19
+ * when it has no long one.
20
+ *
21
+ * @param commandName - Command name (e.g., "screenshot")
22
+ * @param option - Commander option
23
+ * @returns Registry key
24
+ */
25
+ export declare function behaviorKey(commandName: string, option: Option): BehaviorKey;
26
+ /**
27
+ * Every key in the behavior registry.
28
+ *
29
+ * @returns Registry keys
30
+ */
31
+ export declare function listBehaviorKeys(): BehaviorKey[];
10
32
  /**
11
33
  * Look up behavioral metadata for an option.
12
34
  *
13
35
  * @param commandName - Name of the command containing the option
14
- * @param flags - Option flags string from Commander
36
+ * @param option - Commander option
15
37
  * @returns Behavioral metadata if registered, undefined otherwise
16
38
  */
17
- export declare function getOptionBehavior(commandName: string, flags: string): OptionBehavior | undefined;
39
+ export declare function getOptionBehavior(commandName: string, option: Option): OptionBehavior | undefined;
40
+ export {};
18
41
  //# sourceMappingURL=optionBehaviors.d.ts.map