browser-debugger-cli 0.13.0 → 0.14.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 (112) hide show
  1. package/.claude/skills/bdg/SKILL.md +100 -186
  2. package/README.md +4 -4
  3. package/dist/commands/console.js +5 -1
  4. package/dist/commands/dom/a11y.d.ts +1 -1
  5. package/dist/commands/dom/a11y.js +20 -20
  6. package/dist/commands/dom/eval.d.ts +2 -1
  7. package/dist/commands/dom/eval.js +21 -3
  8. package/dist/commands/dom/formInteraction.js +1 -1
  9. package/dist/commands/dom/get.js +25 -7
  10. package/dist/commands/dom/index.js +7 -2
  11. package/dist/commands/dom/query.d.ts +2 -1
  12. package/dist/commands/dom/query.js +5 -3
  13. package/dist/commands/dom/screenshot.js +1 -0
  14. package/dist/commands/helpJson.js +1 -1
  15. package/dist/commands/network/list.js +46 -3
  16. package/dist/commands/optionBehaviors.d.ts +25 -2
  17. package/dist/commands/optionBehaviors.js +55 -42
  18. package/dist/commands/peek.js +3 -0
  19. package/dist/commands/shared/CommandRunner.js +13 -13
  20. package/dist/commands/shared/daemonErrorHandler.js +2 -2
  21. package/dist/commands/shared/dataFetcher.d.ts +4 -2
  22. package/dist/commands/shared/dataFetcher.js +11 -3
  23. package/dist/commands/shared/handleValidationError.js +3 -3
  24. package/dist/commands/shared/optionTypes.d.ts +14 -3
  25. package/dist/commands/shared/startHelpers.js +3 -3
  26. package/dist/connection/chromeIdentity.d.ts +8 -2
  27. package/dist/connection/chromeIdentity.js +85 -13
  28. package/dist/constants.d.ts +29 -1
  29. package/dist/constants.js +35 -1
  30. package/dist/daemon/SessionController.js +2 -0
  31. package/dist/daemon/session/Session.d.ts +2 -1
  32. package/dist/daemon/session/Session.js +10 -2
  33. package/dist/daemon/session/TelemetryStore.d.ts +7 -0
  34. package/dist/daemon/session/TelemetryStore.js +6 -0
  35. package/dist/daemon/session/commandRegistry.js +23 -5
  36. package/dist/daemon/session/matchedStylesReset.d.ts +26 -0
  37. package/dist/daemon/session/matchedStylesReset.js +46 -0
  38. package/dist/daemon/session/plugins.js +1 -0
  39. package/dist/daemon/session/triggeredRequests.d.ts +0 -5
  40. package/dist/daemon/session/triggeredRequests.js +13 -7
  41. package/dist/daemon.js +742 -460
  42. package/dist/errors/messages.d.ts +8 -0
  43. package/dist/errors/messages.js +10 -0
  44. package/dist/index.js +710 -518
  45. package/dist/ipc/protocol/commands.d.ts +4 -0
  46. package/dist/ipc/protocol/inspectTypes.d.ts +5 -2
  47. package/dist/ipc/session/types.d.ts +5 -1
  48. package/dist/program.d.ts +14 -0
  49. package/dist/program.js +53 -0
  50. package/dist/runtime/dom/elementGeometry.d.ts +23 -0
  51. package/dist/runtime/dom/elementGeometry.js +17 -15
  52. package/dist/runtime/dom/elementInfo.d.ts +6 -4
  53. package/dist/runtime/dom/elementInfo.js +7 -4
  54. package/dist/runtime/dom/frameScopedConnection.d.ts +7 -0
  55. package/dist/runtime/dom/frameScopedConnection.js +2 -2
  56. package/dist/runtime/dom/inspect.d.ts +17 -3
  57. package/dist/runtime/dom/inspect.js +40 -26
  58. package/dist/runtime/dom/inspectModel.d.ts +3 -3
  59. package/dist/runtime/dom/inspectRules.d.ts +29 -3
  60. package/dist/runtime/dom/inspectRules.js +205 -11
  61. package/dist/runtime/dom/layout.d.ts +0 -2
  62. package/dist/runtime/dom/layout.js +1 -2
  63. package/dist/runtime/dom/reactEventHelpers.d.ts +4 -1
  64. package/dist/runtime/dom/reactEventHelpers.js +9 -2
  65. package/dist/runtime/dom/targetNode.d.ts +10 -6
  66. package/dist/runtime/dom/targetNode.js +15 -8
  67. package/dist/telemetry/a11y.d.ts +15 -1
  68. package/dist/telemetry/a11y.js +83 -0
  69. package/dist/telemetry/har/builder.js +1 -1
  70. package/dist/telemetry/network.d.ts +13 -16
  71. package/dist/telemetry/network.js +30 -52
  72. package/dist/telemetry/networkRetention.d.ts +83 -0
  73. package/dist/telemetry/networkRetention.js +117 -0
  74. package/dist/types.d.ts +26 -0
  75. package/dist/ui/OutputBuilder.d.ts +10 -0
  76. package/dist/ui/OutputBuilder.js +12 -0
  77. package/dist/ui/formatters/a11y.d.ts +5 -7
  78. package/dist/ui/formatters/a11y.js +7 -61
  79. package/dist/ui/formatters/console/chronological.js +4 -4
  80. package/dist/ui/formatters/console/follow.d.ts +4 -2
  81. package/dist/ui/formatters/console/follow.js +6 -3
  82. package/dist/ui/formatters/console/json.d.ts +3 -6
  83. package/dist/ui/formatters/console/json.js +9 -13
  84. package/dist/ui/formatters/console/shared.d.ts +17 -2
  85. package/dist/ui/formatters/console/shared.js +17 -0
  86. package/dist/ui/formatters/console/summarize.d.ts +2 -2
  87. package/dist/ui/formatters/console/summarize.js +22 -7
  88. package/dist/ui/formatters/console.d.ts +1 -1
  89. package/dist/ui/formatters/console.js +1 -5
  90. package/dist/ui/formatters/details.js +1 -1
  91. package/dist/ui/formatters/dom.d.ts +13 -4
  92. package/dist/ui/formatters/dom.js +25 -7
  93. package/dist/ui/formatters/layout.js +2 -1
  94. package/dist/ui/formatters/longValues.d.ts +14 -0
  95. package/dist/ui/formatters/longValues.js +23 -0
  96. package/dist/ui/formatters/networkList.d.ts +8 -2
  97. package/dist/ui/formatters/networkList.js +11 -2
  98. package/dist/ui/formatters/preview.d.ts +4 -1
  99. package/dist/ui/formatters/preview.js +55 -13
  100. package/dist/ui/formatters/status.js +7 -0
  101. package/dist/ui/formatters/triggeredRequests.js +2 -1
  102. package/dist/ui/messages/chrome.d.ts +20 -1
  103. package/dist/ui/messages/chrome.js +29 -3
  104. package/dist/ui/messages/commands.d.ts +29 -8
  105. package/dist/ui/messages/commands.js +36 -8
  106. package/dist/ui/messages/networkMessages.d.ts +24 -0
  107. package/dist/ui/messages/networkMessages.js +45 -0
  108. package/dist/utils/http.d.ts +9 -2
  109. package/dist/utils/http.js +4 -3
  110. package/dist/utils/strings.d.ts +19 -0
  111. package/dist/utils/strings.js +16 -0
  112. package/package.json +2 -2
@@ -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;
@@ -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
@@ -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
  }
@@ -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
@@ -65,8 +65,9 @@ const OPTION_BEHAVIORS = {
65
65
  },
66
66
  'get:--full': {
67
67
  default: 'Semantic output shows the element text up to 500 characters (whitespace collapsed; close buttons such as "×" and aria-hidden icons left out)',
68
- whenEnabled: 'Shows all of the element text; cannot be combined with --raw or --node-id',
69
- tokenImpact: 'A page-sized container can add thousands of tokens; target the element you need',
68
+ whenEnabled: 'Shows all of the element text; with --raw (or --node-id) prints the whole outer HTML instead of its first 20000 characters, and JSON outerHTML is whole too (else cut to 20000 with truncatedFrom, the original length)',
69
+ automaticBehavior: 'Without --full, --raw output cuts each element\'s HTML at 20000 characters and ends it with "… N more chars (use --full)"',
70
+ tokenImpact: 'A page-sized container can add thousands of tokens; dom get body --raw --full on Wikipedia is about 3.4 MB. Target the element you need',
70
71
  },
71
72
  'get:--all': {
72
73
  default: 'Returns first matching element only',
@@ -78,30 +79,42 @@ const OPTION_BEHAVIORS = {
78
79
  automaticBehavior: 'Past the last match exits 81; a numeric index argument (a cached query index) past the indexed matches, or from an earlier page, exits 87 (re-run dom query)',
79
80
  },
80
81
  'query:--limit': {
81
- default: 'dom query and dom a11y query list the first 50 matches and say how many more there are; --json returns 1000 (dom query) or all of them (dom a11y query)',
82
+ default: 'dom query and dom a11y query list the first 50 matches and say how many more there are; --json lists the first 100 with count (all matches) and omitted (the rest)',
82
83
  whenEnabled: 'Lists that many matches (0 = all), in human and JSON output; count is always the total, JSON omitted the rest',
83
84
  automaticBehavior: 'Matches are cached for index-based access (bdg dom click 55 works even when 50 are listed): all of them for dom a11y query, the first 1000 (or --limit, if higher) for dom query, which describes only those, so a page with 50000 matches answers in under a second; an element the page and frame trees both report is listed once. Indices work with click, fill, hover, pressKey, scroll, submit, layout, get and listeners, also for elements of a cross-origin iframe of the same site (a consent dialog), whose scripts then run in that frame',
84
- tokenImpact: 'About one line per match; a page can have hundreds of links',
85
+ tokenImpact: 'About one line per match (piped JSON about 160 bytes per dom query match, 230 per a11y match); a page can have thousands of links: on Wikipedia "United States" --limit 0 --json is 1.0 MB (dom query a) and 1.3 MB (dom a11y query role:link), the default 16 KB and 23 KB',
86
+ },
87
+ 'tree:--limit': {
88
+ default: 'dom a11y tree lists the first 50 meaningful nodes depth-first, in human and JSON output; JSON count is the whole tree = nodes listed + omitted (cut by --limit/--depth) + skipped (never listed)',
89
+ whenEnabled: 'Lists that many nodes; 0 = all listed nodes (text boxes and empty wrappers are always skipped)',
90
+ automaticBehavior: 'Ignored nodes, text boxes, blank text, text repeating its parent name and nameless layout wrappers (generic, none, presentation, layout tables) are never listed (JSON skipped counts them); their children move up a level. For the raw tree with every node use bdg cdp Accessibility.getFullAXTree --json. JSON nodes carry depth (0 = root) instead of childIds; nodes outside the root (frame content) follow the root tree',
91
+ tokenImpact: 'About 140 bytes per piped JSON node (7 KB by default); the whole tree of a long page is megabytes (Wikipedia "United States": 51k nodes, 20k listed, 2.6 MB with --limit 0 --json), so prefer --depth or dom a11y query "role:<role>"',
92
+ },
93
+ 'tree:--depth': {
94
+ default: 'dom a11y tree lists every level (up to --limit nodes)',
95
+ whenEnabled: 'Lists nodes down to that level (0 = root only); deeper nodes are counted in omitted',
96
+ tokenImpact: 'An outline of a page (landmarks, headings) in a few levels',
85
97
  },
86
98
  'eval:--frame': {
87
99
  default: "Evaluates in the page's main frame",
88
100
  whenEnabled: "Evaluates in one iframe's main world (its own globals), including cross-origin (out-of-process) iframes; output gains a frame field (its URL)",
89
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.',
90
102
  },
91
- 'console:-H': {
92
- default: 'Shows messages from current page load only (most recent navigation)',
93
- whenEnabled: 'Shows messages from ALL page loads during the session',
94
- automaticBehavior: 'Page navigations create new "navigation contexts" - default filters to latest context',
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',
107
+ },
108
+ 'console:--full': {
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)',
110
+ whenEnabled: 'Message texts are printed whole, in human and JSON output',
111
+ tokenImpact: 'A page that logs a large payload or throws a long error can add megabytes; bdg details console <n> shows one message whole',
95
112
  },
96
113
  'console:--history': {
97
114
  default: 'Shows messages from current page load only (most recent navigation)',
98
115
  whenEnabled: 'Shows messages from ALL page loads during the session',
99
116
  automaticBehavior: 'Page navigations create new "navigation contexts" - default filters to latest context',
100
117
  },
101
- 'console:-l': {
102
- default: 'Smart summary with errors deduplicated and warnings grouped: the newest 50 distinct errors and warnings, with a note for the earlier ones. The session keeps the newest 10000 messages; dropped ones are counted (dropped in JSON)',
103
- whenEnabled: 'Lists all messages chronologically without deduplication',
104
- },
105
118
  'console:--list': {
106
119
  default: 'Smart summary with errors deduplicated and warnings grouped: the newest 50 distinct errors and warnings, with a note for the earlier ones. The session keeps the newest 10000 messages; dropped ones are counted (dropped in JSON)',
107
120
  whenEnabled: 'Lists all messages chronologically without deduplication',
@@ -253,6 +266,7 @@ const OPTION_BEHAVIORS = {
253
266
  },
254
267
  'inspect:--no-hints': {
255
268
  default: "Hints at the element's own author declarations that have no effect (flex/grid properties without flex or grid, item properties without a flex or grid parent, offsets on static elements, sizes on inline ones, var() of an unset custom property, form controls in the browser's font), within a 1 s budget; hints none when nothing was found",
269
+ automaticBehavior: "After a hint read times out, later inspects on the same page skip the hints without waiting (hints skipped: this page's stylesheets are slow to read) until a read is fast again, a stylesheet changes or it navigates; --rules and --why still wait up to 5 s. Matched rules that took over 300 ms are reused for up to 5 s, until a command that may change the page or a stylesheet or DOM change",
256
270
  whenEnabled: 'Skips the hints and does not read the matched rules',
257
271
  },
258
272
  'scroll:--down': {
@@ -293,56 +307,52 @@ const OPTION_BEHAVIORS = {
293
307
  whenEnabled: 'Quick scan: field names, types, and required status only',
294
308
  tokenImpact: 'Reduces output ~50% for initial discovery',
295
309
  },
310
+ 'peek:--full': {
311
+ default: 'Console message texts are cut: human output at 200 characters (compact output also at 2 lines) followed by "… N more chars (use --full)"; JSON text at 10000 characters with truncatedFrom (the original length)',
312
+ whenEnabled: 'Console message texts are printed whole, in human and JSON output',
313
+ tokenImpact: 'A page that logs a large payload can add megabytes per peek',
314
+ },
296
315
  'peek:--type': {
297
316
  whenEnabled: 'Filters network requests by CDP resource type. Case-insensitive, comma-separated. Valid: Document, Stylesheet, Image, Media, Font, Script, XHR, Fetch, WebSocket, etc.',
298
317
  },
299
- 'peek:-f': {
300
- default: 'Shows snapshot of current data',
301
- whenEnabled: 'Continuous monitoring (like tail -f): refreshes every second, or every --interval ms (100-60000). Replaces the deprecated bdg tail',
302
- automaticBehavior: FOLLOW_BEHAVIOR,
303
- },
304
318
  'peek:--follow': {
305
319
  default: 'Shows snapshot of current data',
306
320
  whenEnabled: 'Continuous monitoring (like tail -f): refreshes every second, or every --interval ms (100-60000). Replaces the deprecated bdg tail',
307
321
  automaticBehavior: FOLLOW_BEHAVIOR,
308
322
  },
309
- 'console:-f': {
323
+ 'console:--follow': {
310
324
  default: 'Prints the messages logged so far and exits',
311
325
  whenEnabled: 'Streams new messages as they come (the last --last at start)',
312
326
  automaticBehavior: FOLLOW_BEHAVIOR,
313
327
  },
314
- 'list:-f': {
328
+ 'list:--follow': {
315
329
  default: 'Lists the requests captured so far and exits',
316
330
  whenEnabled: 'Streams requests as they finish',
317
331
  automaticBehavior: FOLLOW_BEHAVIOR,
318
332
  },
319
- 'peek:-v': {
333
+ 'peek:--verbose': {
320
334
  default: 'Compact output (truncated URLs, no resource types)',
321
335
  whenEnabled: 'Verbose output with full URLs and resource types',
322
336
  },
323
- 'start:--headless': {
337
+ 'bdg:--headless': {
324
338
  default: 'A window when there is a display: on macOS unless over SSH (SSH_CONNECTION, SSH_TTY) or CI is set; on Linux when DISPLAY or WAYLAND_DISPLAY is set. Servers, containers and CI run headless',
325
339
  whenEnabled: 'Chrome runs without a window (pass it when running unattended on a Mac)',
326
340
  },
327
- 'start:--no-headless': {
341
+ 'bdg:--no-headless': {
328
342
  default: 'A window when there is a display: on macOS unless over SSH (SSH_CONNECTION, SSH_TTY) or CI is set; on Linux when DISPLAY or WAYLAND_DISPLAY is set',
329
343
  whenEnabled: 'Chrome shows its window even without a detected display (it fails without one)',
330
344
  },
331
- 'start:--all': {
345
+ 'bdg:--all': {
332
346
  default: 'Tracking/analytics requests and console noise are filtered; bodies of binary responses (images, fonts) are not captured',
333
347
  whenEnabled: 'Everything is captured, including binary response bodies (base64, flagged by responseBodyBase64, within --max-body-size)',
334
348
  tokenImpact: 'details network --json and HAR exports can grow considerably on media-heavy pages',
335
349
  },
336
- 'peek:--verbose': {
337
- default: 'Compact output (truncated URLs, no resource types)',
338
- whenEnabled: 'Verbose output with full URLs and resource types',
339
- },
340
350
  'bdg:--session': {
341
351
  default: 'The default session in ~/.bdg (or $BDG_SESSION_DIR); BDG_SESSION=<name> selects a named session like the flag',
342
352
  whenEnabled: 'Uses the named session in ~/.bdg/sessions/<name>/ (or $BDG_SESSION_DIR/sessions/<name>/) with its own daemon, Chrome, profile and port; every command (status, stop, cleanup, ...) acts on that session only',
343
353
  automaticBehavior: 'Accepted before or after any subcommand; --session wins over BDG_SESSION. Names are case-insensitive (lower-cased: ALPHA is alpha). Without --port a named session takes the first free port above 9222 not claimed by another running session, and keeps it in port.txt. Names: 1-40 letters, digits, "-" or "_", starting with a letter or digit (exit 81 otherwise, also when the socket path would be too long). Hints and suggestions in its output carry --session <name>',
344
354
  },
345
- 'cleanup:-f': {
355
+ 'cleanup:--force': {
346
356
  default: 'Refuses to run while a session is active; removes files left by a crashed session',
347
357
  whenEnabled: 'Kills the running daemon and its Chrome first (use when a session is stuck)',
348
358
  },
@@ -377,36 +387,39 @@ const OPTION_BEHAVIORS = {
377
387
  default: 'Chrome launched by bdg is always closed on stop; an attached Chrome (--chrome-ws-url) is left running',
378
388
  whenEnabled: 'No additional effect; kept for compatibility',
379
389
  },
380
- 'status:-v': {
381
- default: 'Basic session status (daemon running, session active, URL)',
382
- whenEnabled: 'Includes Chrome diagnostics and CDP connection details',
383
- },
384
390
  'status:--verbose': {
385
391
  default: 'Basic session status (daemon running, session active, URL)',
386
392
  whenEnabled: 'Includes Chrome diagnostics and CDP connection details',
387
393
  },
388
394
  };
389
395
  /**
390
- * Build behavior registry key from command and flag.
396
+ * Build behavior registry key from command and option: the command's own
397
+ * name (`bdg` for the root) and the option's long flag, or its short flag
398
+ * when it has no long one.
391
399
  *
392
400
  * @param commandName - Command name (e.g., "screenshot")
393
- * @param flags - Option flags string (e.g., "--no-resize")
401
+ * @param option - Commander option
394
402
  * @returns Registry key
395
403
  */
396
- function buildKey(commandName, flags) {
397
- const firstFlag = flags.split(',')[0] ?? flags;
398
- const flagName = firstFlag.trim().split(' ')[0] ?? firstFlag.trim();
399
- return `${commandName}:${flagName}`;
404
+ export function behaviorKey(commandName, option) {
405
+ return `${commandName}:${option.long ?? option.short ?? option.flags}`;
406
+ }
407
+ /**
408
+ * Every key in the behavior registry.
409
+ *
410
+ * @returns Registry keys
411
+ */
412
+ export function listBehaviorKeys() {
413
+ return Object.keys(OPTION_BEHAVIORS);
400
414
  }
401
415
  /**
402
416
  * Look up behavioral metadata for an option.
403
417
  *
404
418
  * @param commandName - Name of the command containing the option
405
- * @param flags - Option flags string from Commander
419
+ * @param option - Commander option
406
420
  * @returns Behavioral metadata if registered, undefined otherwise
407
421
  */
408
- export function getOptionBehavior(commandName, flags) {
409
- const key = buildKey(commandName, flags);
410
- return OPTION_BEHAVIORS[key];
422
+ export function getOptionBehavior(commandName, option) {
423
+ return OPTION_BEHAVIORS[behaviorKey(commandName, option)];
411
424
  }
412
425
  //# sourceMappingURL=optionBehaviors.js.map
@@ -8,6 +8,7 @@ import { fetchPreviewOutput, createErrorResult, } from './shared/dataFetcher.js'
8
8
  import { followFetchFailure, setupFollowMode, } from './shared/followMode.js';
9
9
  import { handleValidationError } from './shared/handleValidationError.js';
10
10
  import { MAX_LAST_ITEMS, positiveIntRule, resourceTypeRule } from './shared/validation.js';
11
+ import { MAX_CONSOLE_JSON_TEXT_LENGTH, MAX_CONSOLE_TEXT_LENGTH } from '../constants.js';
11
12
  import { CommandError } from '../errors/index.js';
12
13
  import { intervalWithoutFollowError } from '../errors/messages.js';
13
14
  import { filterByResourceType } from '../telemetry/filters.js';
@@ -137,6 +138,7 @@ function previewDisplayOptions(options, lastN) {
137
138
  last: lastN,
138
139
  verbose: options.verbose,
139
140
  follow: options.follow,
141
+ full: options.full,
140
142
  };
141
143
  }
142
144
  /**
@@ -162,6 +164,7 @@ export function registerPeekCommand(program) {
162
164
  .option('--interval <ms>', 'Refresh interval of --follow in ms, 100-60000 (default: 1000)')
163
165
  .option('--last <count>', 'Show last N items, 0 for all', '10')
164
166
  .option('--type <types>', 'Filter network requests by resource type (comma-separated: Document,XHR,Fetch,etc.)')
167
+ .option('--full', `Print console message texts whole (default: the first ${MAX_CONSOLE_TEXT_LENGTH} characters, ${MAX_CONSOLE_JSON_TEXT_LENGTH} in JSON)`, false)
165
168
  .action(async (options) => {
166
169
  showBothSectionsWhenBothRequested(options);
167
170
  if (options.network && !options.json) {
@@ -2,7 +2,7 @@ import { getQuickIPCRequestTimeout } from '../../constants.js';
2
2
  import { CommandError, isDaemonConnectionError } from '../../errors/index.js';
3
3
  import { daemonNotRunningError, unknownError, genericError, commandTimedOutError, sessionNotRespondingError, sessionEndedDuringCommandError, } from '../../errors/messages.js';
4
4
  import { IPCEarlyCloseError, IPCTimeoutError } from '../../ipc/transport/IPCError.js';
5
- import { OutputBuilder, buildSuccessResponse } from '../../ui/OutputBuilder.js';
5
+ import { OutputBuilder, buildSuccessResponse, stringifyEnvelope } from '../../ui/OutputBuilder.js';
6
6
  import { escapeControlChars } from '../../ui/formatting.js';
7
7
  import { noActiveSessionMessage, startSessionSuggestion } from '../../ui/messages/sessionCommand.js';
8
8
  import { getErrorExitCode, getErrorMessage } from '../../utils/errors.js';
@@ -36,7 +36,7 @@ export function noActiveSessionError() {
36
36
  export async function runJsonCommand(fn) {
37
37
  try {
38
38
  const data = await fn();
39
- console.log(JSON.stringify(buildSuccessResponse(data), null, 2));
39
+ console.log(stringifyEnvelope(buildSuccessResponse(data)));
40
40
  process.exit(EXIT_CODES.SUCCESS);
41
41
  }
42
42
  catch (caught) {
@@ -49,10 +49,10 @@ export async function runJsonCommand(fn) {
49
49
  const suggestion = error instanceof CommandError && typeof error.metadata['suggestion'] === 'string'
50
50
  ? error.metadata['suggestion']
51
51
  : undefined;
52
- console.log(JSON.stringify(OutputBuilder.buildJsonError(getErrorMessage(error), {
52
+ console.log(stringifyEnvelope(OutputBuilder.buildJsonError(getErrorMessage(error), {
53
53
  exitCode,
54
54
  ...(suggestion && { suggestion }),
55
- }), null, 2));
55
+ })));
56
56
  process.exit(exitCode);
57
57
  }
58
58
  }
@@ -112,10 +112,10 @@ export async function runCommand(handler, options, formatter) {
112
112
  if (!result.success) {
113
113
  const exitCode = result.exitCode ?? EXIT_CODES.UNHANDLED_EXCEPTION;
114
114
  if (options.json) {
115
- console.log(JSON.stringify(OutputBuilder.buildJsonError(result.error ?? 'Unknown error', {
115
+ console.log(stringifyEnvelope(OutputBuilder.buildJsonError(result.error ?? 'Unknown error', {
116
116
  ...result.errorContext,
117
117
  exitCode,
118
- }), null, 2));
118
+ })));
119
119
  }
120
120
  else {
121
121
  console.error(result.error ? genericError(result.error) : unknownError());
@@ -133,14 +133,14 @@ export async function runCommand(handler, options, formatter) {
133
133
  console.error(escapeControlChars(result.hint));
134
134
  }
135
135
  if (options.json) {
136
- console.log(JSON.stringify(buildSuccessResponse(result.data), null, 2));
136
+ console.log(stringifyEnvelope(buildSuccessResponse(result.data)));
137
137
  }
138
138
  else if (formatter) {
139
139
  const formattedOutput = formatter(result.data);
140
140
  console.log(escapeControlChars(formattedOutput));
141
141
  }
142
142
  else {
143
- console.log(JSON.stringify(buildSuccessResponse(result.data), null, 2));
143
+ console.log(stringifyEnvelope(buildSuccessResponse(result.data)));
144
144
  }
145
145
  process.exit(EXIT_CODES.SUCCESS);
146
146
  }
@@ -152,10 +152,10 @@ export async function runCommand(handler, options, formatter) {
152
152
  : caught;
153
153
  if (error instanceof CommandError) {
154
154
  if (options.json) {
155
- console.log(JSON.stringify(OutputBuilder.buildJsonError(error.message, {
155
+ console.log(stringifyEnvelope(OutputBuilder.buildJsonError(error.message, {
156
156
  ...error.metadata,
157
157
  exitCode: error.exitCode,
158
- }), null, 2));
158
+ })));
159
159
  }
160
160
  else {
161
161
  console.error(genericError(error.message));
@@ -168,10 +168,10 @@ export async function runCommand(handler, options, formatter) {
168
168
  const errorMessage = getErrorMessage(error);
169
169
  if (isDaemonConnectionError(error)) {
170
170
  if (options.json) {
171
- console.log(JSON.stringify(OutputBuilder.buildJsonError(noActiveSessionMessage(), {
171
+ console.log(stringifyEnvelope(OutputBuilder.buildJsonError(noActiveSessionMessage(), {
172
172
  suggestion: startSessionSuggestion(),
173
173
  exitCode: EXIT_CODES.RESOURCE_NOT_FOUND,
174
- }), null, 2));
174
+ })));
175
175
  }
176
176
  else {
177
177
  console.error(daemonNotRunningError());
@@ -180,7 +180,7 @@ export async function runCommand(handler, options, formatter) {
180
180
  }
181
181
  const exitCode = getErrorExitCode(error, EXIT_CODES.UNHANDLED_EXCEPTION);
182
182
  if (options.json) {
183
- console.log(JSON.stringify(OutputBuilder.buildJsonError(errorMessage, { exitCode }), null, 2));
183
+ console.log(stringifyEnvelope(OutputBuilder.buildJsonError(errorMessage, { exitCode })));
184
184
  }
185
185
  else {
186
186
  console.error(genericError(errorMessage));