browser-debugger-cli 0.9.0 → 0.11.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 (139) hide show
  1. package/.claude/skills/bdg/SKILL.md +268 -0
  2. package/README.md +15 -1
  3. package/dist/commands/dom/a11y.js +2 -1
  4. package/dist/commands/dom/formInteraction.js +56 -25
  5. package/dist/commands/dom/helpers/keyAttributes.d.ts +20 -0
  6. package/dist/commands/dom/helpers/keyAttributes.js +54 -0
  7. package/dist/commands/dom/helpers/query.d.ts +1 -1
  8. package/dist/commands/dom/helpers/query.js +66 -19
  9. package/dist/commands/dom/helpers/runElementCommand.js +4 -3
  10. package/dist/commands/dom/helpers/screenshot.js +85 -12
  11. package/dist/commands/dom/index.d.ts +1 -0
  12. package/dist/commands/dom/index.js +8 -3
  13. package/dist/commands/dom/inspect.d.ts +15 -0
  14. package/dist/commands/dom/inspect.js +82 -0
  15. package/dist/commands/dom/layout.js +2 -2
  16. package/dist/commands/dom/listeners.js +2 -2
  17. package/dist/commands/dom/semanticUtils.d.ts +14 -1
  18. package/dist/commands/dom/semanticUtils.js +44 -3
  19. package/dist/commands/installSkill.d.ts +20 -0
  20. package/dist/commands/installSkill.js +87 -0
  21. package/dist/commands/network/list.js +13 -2
  22. package/dist/commands/optionBehaviors.js +48 -6
  23. package/dist/commands/page.d.ts +1 -1
  24. package/dist/commands/page.js +62 -3
  25. package/dist/commands/shared/commonOptions.d.ts +4 -0
  26. package/dist/commands/shared/commonOptions.js +9 -0
  27. package/dist/commands/shared/optionTypes.d.ts +21 -0
  28. package/dist/commands/shared/startHelpers.d.ts +66 -0
  29. package/dist/commands/shared/startHelpers.js +91 -10
  30. package/dist/commands/shared/validation.d.ts +11 -0
  31. package/dist/commands/shared/validation.js +16 -0
  32. package/dist/commands.js +3 -0
  33. package/dist/daemon/launcher.d.ts +8 -1
  34. package/dist/daemon/launcher.js +3 -1
  35. package/dist/daemon/session/Session.d.ts +7 -0
  36. package/dist/daemon/session/Session.js +23 -1
  37. package/dist/daemon/session/commandRegistry.d.ts +14 -1
  38. package/dist/daemon/session/commandRegistry.js +65 -9
  39. package/dist/daemon/session/interactions.d.ts +18 -5
  40. package/dist/daemon/session/interactions.js +22 -12
  41. package/dist/daemon.js +3565 -329
  42. package/dist/errors/messages.d.ts +85 -0
  43. package/dist/errors/messages.js +128 -1
  44. package/dist/index.js +2151 -960
  45. package/dist/ipc/client.d.ts +9 -0
  46. package/dist/ipc/client.js +13 -0
  47. package/dist/ipc/protocol/commands.d.ts +56 -1
  48. package/dist/ipc/protocol/commands.js +2 -0
  49. package/dist/ipc/protocol/domTypes.d.ts +35 -2
  50. package/dist/ipc/protocol/inspectTypes.d.ts +388 -0
  51. package/dist/ipc/protocol/inspectTypes.js +10 -0
  52. package/dist/runtime/dom/actionEffects.d.ts +94 -15
  53. package/dist/runtime/dom/actionEffects.js +173 -27
  54. package/dist/runtime/dom/actionEffectsScripts.d.ts +52 -14
  55. package/dist/runtime/dom/actionEffectsScripts.js +224 -32
  56. package/dist/runtime/dom/elementInfo.d.ts +26 -0
  57. package/dist/runtime/dom/elementInfo.js +65 -0
  58. package/dist/runtime/dom/eventListeners.js +14 -4
  59. package/dist/runtime/dom/formFillHelpers/fill.d.ts +3 -4
  60. package/dist/runtime/dom/formFillHelpers/fill.js +77 -28
  61. package/dist/runtime/dom/frameSelection.d.ts +11 -0
  62. package/dist/runtime/dom/frameSelection.js +20 -1
  63. package/dist/runtime/dom/frames.d.ts +38 -5
  64. package/dist/runtime/dom/frames.js +136 -21
  65. package/dist/runtime/dom/inspect.d.ts +28 -0
  66. package/dist/runtime/dom/inspect.js +557 -0
  67. package/dist/runtime/dom/inspectAllStyles.d.ts +62 -0
  68. package/dist/runtime/dom/inspectAllStyles.js +385 -0
  69. package/dist/runtime/dom/inspectCascade.d.ts +94 -0
  70. package/dist/runtime/dom/inspectCascade.js +371 -0
  71. package/dist/runtime/dom/inspectCascadeModel.d.ts +39 -0
  72. package/dist/runtime/dom/inspectCascadeModel.js +232 -0
  73. package/dist/runtime/dom/inspectHints.d.ts +62 -0
  74. package/dist/runtime/dom/inspectHints.js +305 -0
  75. package/dist/runtime/dom/inspectLayoutModel.d.ts +87 -0
  76. package/dist/runtime/dom/inspectLayoutModel.js +346 -0
  77. package/dist/runtime/dom/inspectModel.d.ts +74 -0
  78. package/dist/runtime/dom/inspectModel.js +184 -0
  79. package/dist/runtime/dom/inspectPaintModel.d.ts +157 -0
  80. package/dist/runtime/dom/inspectPaintModel.js +461 -0
  81. package/dist/runtime/dom/inspectRules.d.ts +37 -0
  82. package/dist/runtime/dom/inspectRules.js +101 -0
  83. package/dist/runtime/dom/inspectScripts.d.ts +132 -0
  84. package/dist/runtime/dom/inspectScripts.js +263 -0
  85. package/dist/runtime/dom/inspectTree.d.ts +40 -0
  86. package/dist/runtime/dom/inspectTree.js +134 -0
  87. package/dist/runtime/dom/inspectVariables.d.ts +33 -0
  88. package/dist/runtime/dom/inspectVariables.js +94 -0
  89. package/dist/runtime/dom/inspectWhyModel.d.ts +20 -0
  90. package/dist/runtime/dom/inspectWhyModel.js +134 -0
  91. package/dist/runtime/dom/layout.d.ts +5 -1
  92. package/dist/runtime/dom/layout.js +10 -3
  93. package/dist/runtime/dom/listenerPageScripts.d.ts +11 -5
  94. package/dist/runtime/dom/listenerPageScripts.js +95 -9
  95. package/dist/runtime/dom/listenerSummary.d.ts +4 -0
  96. package/dist/runtime/dom/listenerSummary.js +26 -9
  97. package/dist/runtime/dom/reactEventHelpers.d.ts +5 -0
  98. package/dist/runtime/dom/reactEventHelpers.js +12 -4
  99. package/dist/runtime/page/emulation.d.ts +20 -0
  100. package/dist/runtime/page/emulation.js +37 -0
  101. package/dist/telemetry/a11y.d.ts +10 -0
  102. package/dist/telemetry/a11y.js +78 -1
  103. package/dist/telemetry/console.d.ts +1 -0
  104. package/dist/telemetry/console.js +100 -5
  105. package/dist/telemetry/network.js +3 -1
  106. package/dist/types.d.ts +40 -0
  107. package/dist/ui/formatters/details.d.ts +8 -0
  108. package/dist/ui/formatters/details.js +59 -3
  109. package/dist/ui/formatters/dom.d.ts +2 -1
  110. package/dist/ui/formatters/dom.js +25 -9
  111. package/dist/ui/formatters/inspect.d.ts +39 -0
  112. package/dist/ui/formatters/inspect.js +596 -0
  113. package/dist/ui/formatters/installSkill.d.ts +11 -0
  114. package/dist/ui/formatters/installSkill.js +31 -0
  115. package/dist/ui/formatters/keyAttributes.d.ts +19 -0
  116. package/dist/ui/formatters/keyAttributes.js +84 -0
  117. package/dist/ui/formatters/layout.js +2 -2
  118. package/dist/ui/formatters/networkHeaders.d.ts +13 -0
  119. package/dist/ui/formatters/networkHeaders.js +23 -3
  120. package/dist/ui/formatters/networkList.d.ts +29 -1
  121. package/dist/ui/formatters/networkList.js +86 -20
  122. package/dist/ui/formatters/status.js +1 -1
  123. package/dist/ui/formatting.d.ts +9 -0
  124. package/dist/ui/formatting.js +6 -3
  125. package/dist/ui/messages/commands.d.ts +123 -7
  126. package/dist/ui/messages/commands.js +181 -10
  127. package/dist/ui/messages/networkMessages.d.ts +14 -0
  128. package/dist/ui/messages/networkMessages.js +18 -0
  129. package/dist/ui/messages/session.d.ts +14 -0
  130. package/dist/ui/messages/session.js +20 -0
  131. package/dist/utils/async.d.ts +9 -0
  132. package/dist/utils/async.js +17 -0
  133. package/dist/utils/color.d.ts +84 -0
  134. package/dist/utils/color.js +376 -0
  135. package/dist/utils/cssValues.d.ts +109 -0
  136. package/dist/utils/cssValues.js +236 -0
  137. package/dist/utils/selectorFilters.d.ts +12 -0
  138. package/dist/utils/selectorFilters.js +29 -0
  139. package/package.json +2 -1
@@ -5,7 +5,9 @@
5
5
  * their surrounding DOM context and to fall back gracefully when only one
6
6
  * of the two data sources is available.
7
7
  */
8
+ import { MASKED_VALUE } from '../../runtime/dom/elementInfo.js';
8
9
  import { synthesizeA11yNode } from '../../telemetry/roleInference.js';
10
+ import { keyAttributeItems } from '../../ui/formatters/keyAttributes.js';
9
11
  import { joinLines } from '../../ui/formatting.js';
10
12
  import { elementTextLine, emptyElementLine } from '../../ui/messages/commands.js';
11
13
  function capitalize(str) {
@@ -35,6 +37,27 @@ function buildContextText(node, domContext) {
35
37
  }
36
38
  return '';
37
39
  }
40
+ /**
41
+ * Key attributes of the element (src, href, a field's type and name, ...),
42
+ * leaving out what the role line already shows: values equal to the
43
+ * accessible name (an image's alt, a field's placeholder) and the value
44
+ * when the accessibility node has one.
45
+ *
46
+ * @param node - Accessibility node
47
+ * @param domContext - DOM context with the key attributes
48
+ * @returns ` src="…/logo.png"`-style text, or empty
49
+ */
50
+ function buildKeyAttributesText(node, domContext) {
51
+ if (!domContext?.attributes)
52
+ return '';
53
+ const shown = Object.entries(domContext.attributes)
54
+ .filter(([, value]) => Boolean(node.name) && value === node.name)
55
+ .map(([name]) => name);
56
+ if (node.value !== undefined && node.value !== '')
57
+ shown.push('value');
58
+ const items = keyAttributeItems(domContext.tag, domContext.attributes, new Set(shown));
59
+ return items.length > 0 ? ` ${items.join(' ')}` : '';
60
+ }
38
61
  function buildPropertiesText(node) {
39
62
  const props = [];
40
63
  if (node.value !== undefined && node.value !== '')
@@ -59,7 +82,9 @@ function buildPropertiesText(node) {
59
82
  /**
60
83
  * Format a semantic node together with DOM context for human-readable output.
61
84
  *
62
- * The role line is followed by up to 500 characters of the element's text
85
+ * The role line names the element's key attributes (an image's file name, a
86
+ * link's href, a field's type and name), like `dom query` does, and is
87
+ * followed by up to 500 characters of the element's text
63
88
  * (all of it with `dom get --full`) when it is longer than the one-line
64
89
  * preview, or, for an element without text or name, what it holds.
65
90
  *
@@ -70,9 +95,10 @@ export function formatSemanticNodeWithContext(data) {
70
95
  const { node, domContext } = data;
71
96
  const roleText = buildRoleText(node);
72
97
  const contextText = buildContextText(node, domContext);
98
+ const keysText = buildKeyAttributesText(node, domContext);
73
99
  const propsText = buildPropertiesText(node);
74
100
  const inferredText = node.inferred ? ' (inferred from DOM)' : '';
75
- const line = `${roleText}${contextText}${propsText}${inferredText}`;
101
+ const line = `${roleText}${contextText}${keysText}${propsText}${inferredText}`;
76
102
  if (domContext?.text)
77
103
  return joinLines(line, elementTextLine(domContext.text));
78
104
  if (domContext?.childCount !== undefined && !node.name) {
@@ -90,9 +116,24 @@ export function formatSemanticNodeWithContext(data) {
90
116
  */
91
117
  export function resolveNodeWithFallback(a11yNode, domContext, nodeId) {
92
118
  if (a11yNode)
93
- return a11yNode;
119
+ return withSecretMasked(a11yNode, domContext);
94
120
  if (domContext && nodeId)
95
121
  return synthesizeA11yNode(domContext, nodeId);
96
122
  return null;
97
123
  }
124
+ /**
125
+ * The accessibility node with its value masked when the element holds a
126
+ * secret (`domContext.sensitive`): Chrome reports a password field's value
127
+ * as one bullet per character, and a field switched to text by a "show
128
+ * password" button in clear.
129
+ *
130
+ * @param node - Accessibility node
131
+ * @param domContext - DOM context of the same element
132
+ * @returns The node, with {@link MASKED_VALUE} as its value for a secret
133
+ */
134
+ export function withSecretMasked(node, domContext) {
135
+ if (!domContext?.sensitive || !node.value)
136
+ return node;
137
+ return { ...node, value: MASKED_VALUE };
138
+ }
98
139
  //# sourceMappingURL=semanticUtils.js.map
@@ -0,0 +1,20 @@
1
+ import type { Command } from 'commander';
2
+ import type { InstalledSkill, SkillTarget } from '../types.js';
3
+ /**
4
+ * Copy the bdg skill into each target's skill directory, overwriting an
5
+ * older copy.
6
+ *
7
+ * @param targets - Agents to install for
8
+ * @param home - Home directory the skill roots are relative to
9
+ * @param source - SKILL.md to copy
10
+ * @returns One entry per target, in the given order
11
+ * @throws CommandError when the source is missing (83) or a write fails (82)
12
+ */
13
+ export declare function installSkill(targets: SkillTarget[], home?: string, source?: string): InstalledSkill[];
14
+ /**
15
+ * Register the install-skill command.
16
+ *
17
+ * @param program - Commander.js Command instance to register commands on
18
+ */
19
+ export declare function registerInstallSkillCommand(program: Command): void;
20
+ //# sourceMappingURL=installSkill.d.ts.map
@@ -0,0 +1,87 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
2
+ import { homedir } from 'os';
3
+ import { dirname, join } from 'path';
4
+ import { runCommand } from './shared/CommandRunner.js';
5
+ import { jsonOption } from './shared/commonOptions.js';
6
+ import { CommandError } from '../errors/index.js';
7
+ import { skillSourceMissingError, skillWriteFailedError } from '../errors/messages.js';
8
+ import { formatInstalledSkills } from '../ui/formatters/installSkill.js';
9
+ import { getErrorMessage } from '../utils/errors.js';
10
+ import { EXIT_CODES } from '../utils/exitCodes.js';
11
+ import { PACKAGE_ROOT } from '../utils/packageRoot.js';
12
+ /** The skill shipped with the package (also used by agents working in this repo). */
13
+ const SKILL_SOURCE_PATH = join(PACKAGE_ROOT, '.claude', 'skills', 'bdg', 'SKILL.md');
14
+ /** Skill roots, relative to the home directory, of the agents the skill is installed for. */
15
+ const SKILL_ROOTS = {
16
+ claude: join('.claude', 'skills'),
17
+ agents: join('.agents', 'skills'),
18
+ };
19
+ /**
20
+ * Copy the bdg skill into each target's skill directory, overwriting an
21
+ * older copy.
22
+ *
23
+ * @param targets - Agents to install for
24
+ * @param home - Home directory the skill roots are relative to
25
+ * @param source - SKILL.md to copy
26
+ * @returns One entry per target, in the given order
27
+ * @throws CommandError when the source is missing (83) or a write fails (82)
28
+ */
29
+ export function installSkill(targets, home = homedir(), source = SKILL_SOURCE_PATH) {
30
+ if (!existsSync(source)) {
31
+ const err = skillSourceMissingError(source);
32
+ throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.RESOURCE_NOT_FOUND);
33
+ }
34
+ const content = readFileSync(source, 'utf-8');
35
+ return targets.map((target) => writeSkill(target, join(home, SKILL_ROOTS[target], 'bdg', 'SKILL.md'), content));
36
+ }
37
+ /**
38
+ * Write the skill to one path unless it already holds the same content.
39
+ *
40
+ * @param target - Agent the path belongs to
41
+ * @param path - Destination SKILL.md
42
+ * @param content - Skill text
43
+ * @returns What happened to the file
44
+ * @throws CommandError (82) when the directory or file cannot be written
45
+ */
46
+ function writeSkill(target, path, content) {
47
+ const existing = existsSync(path) ? readFileSync(path, 'utf-8') : undefined;
48
+ if (existing === content) {
49
+ return { target, path, status: 'unchanged' };
50
+ }
51
+ try {
52
+ mkdirSync(dirname(path), { recursive: true });
53
+ writeFileSync(path, content);
54
+ }
55
+ catch (caught) {
56
+ const err = skillWriteFailedError(path, getErrorMessage(caught));
57
+ throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.PERMISSION_DENIED);
58
+ }
59
+ return { target, path, status: existing === undefined ? 'installed' : 'updated' };
60
+ }
61
+ /**
62
+ * Targets picked by the flags; no flag means every agent.
63
+ *
64
+ * @param options - Parsed command options
65
+ * @returns Targets to install for
66
+ */
67
+ function selectedTargets(options) {
68
+ const picked = Object.keys(SKILL_ROOTS).filter((target) => options[target]);
69
+ return picked.length > 0 ? picked : Object.keys(SKILL_ROOTS);
70
+ }
71
+ /**
72
+ * Register the install-skill command.
73
+ *
74
+ * @param program - Commander.js Command instance to register commands on
75
+ */
76
+ export function registerInstallSkillCommand(program) {
77
+ program
78
+ .command('install-skill')
79
+ .description('Install the bdg agent skill for Claude Code (~/.claude/skills) and agents reading ~/.agents/skills (Codex, Gemini CLI, ...); re-run after upgrading bdg')
80
+ .option('--claude', 'Only ~/.claude/skills (Claude Code)')
81
+ .option('--agents', 'Only ~/.agents/skills (Codex, Gemini CLI and other agents)')
82
+ .addOption(jsonOption())
83
+ .action(async (options) => {
84
+ await runCommand((opts) => Promise.resolve({ success: true, data: { skills: installSkill(selectedTargets(opts)) } }), options, formatInstalledSkills);
85
+ });
86
+ }
87
+ //# sourceMappingURL=installSkill.js.map
@@ -13,7 +13,7 @@ import { applyFilters, getFilterHelpText, validateFilterString } from '../../tel
13
13
  import { resolvePreset, FILTER_PRESETS } from '../../telemetry/filterPresets.js';
14
14
  import { filterByResourceType } from '../../telemetry/filters.js';
15
15
  import { buildSuccessResponse } from '../../ui/OutputBuilder.js';
16
- import { formatNetworkFollowRows, formatNetworkList, } from '../../ui/formatters/networkList.js';
16
+ import { formatNetworkFollowRows, formatNetworkList, pageStartOf, } from '../../ui/formatters/networkList.js';
17
17
  import { followingNetworkMessage, stoppedFollowingNetworkMessage, } from '../../ui/messages/networkMessages.js';
18
18
  import { EXIT_CODES } from '../../utils/exitCodes.js';
19
19
  import { validateFilterOption } from './shared.js';
@@ -110,6 +110,7 @@ function buildFormatOptions(options, result, lastLimit) {
110
110
  last: lastLimit,
111
111
  totalCount: result.totalCount,
112
112
  filteredCount: result.filteredCount,
113
+ ...(result.pageStart && { pageStart: result.pageStart }),
113
114
  };
114
115
  }
115
116
  /**
@@ -148,9 +149,11 @@ async function runFollowMode(options, resourceTypes, lastN) {
148
149
  }
149
150
  }
150
151
  else {
152
+ const pageStart = pageStartOf(result.data);
151
153
  const text = formatNetworkFollowRows(fresh, {
152
154
  header: !started,
153
155
  verbose: options.verbose ?? false,
156
+ ...(pageStart && { pageStart }),
154
157
  });
155
158
  if (text)
156
159
  console.log(text);
@@ -164,6 +167,12 @@ async function runFollowMode(options, resourceTypes, lastN) {
164
167
  intervalMs: FOLLOW_INTERVAL,
165
168
  });
166
169
  }
170
+ /** What the less obvious columns of the list mean */
171
+ const COLUMNS_HELP = `Columns:
172
+ START When the request started, from the start of the current page (its document
173
+ request): +1.2s. Requests of earlier pages are negative. --json has the
174
+ absolute time (timestamp, epoch ms) and data.pageStart.
175
+ TIME How long it took (to its last byte or failure); - while pending`;
167
176
  function formatPresetHelp() {
168
177
  return Object.entries(FILTER_PRESETS)
169
178
  .map(([name, preset]) => ` ${name.padEnd(12)} ${preset.description}`)
@@ -180,7 +189,7 @@ export function registerListCommand(networkCmd) {
180
189
  .addOption(networkLastOption)
181
190
  .addOption(new Option('-f, --follow', 'Stream network requests in real-time').default(false))
182
191
  .addOption(new Option('-v, --verbose', 'Show full URLs and additional details').default(false))
183
- .addHelpText('after', `\n${getFilterHelpText()}\n\nPresets:\n${formatPresetHelp()}`)
192
+ .addHelpText('after', `\n${COLUMNS_HELP}\n\n${getFilterHelpText()}\n\nPresets:\n${formatPresetHelp()}`)
184
193
  .action(async (options) => {
185
194
  let resourceTypes;
186
195
  let lastN;
@@ -211,12 +220,14 @@ export function registerListCommand(networkCmd) {
211
220
  return createErrorResult(result.error, result.exitCode, result.suggestion);
212
221
  }
213
222
  const filtered = filterRequests(result.data, options, resourceTypes);
223
+ const pageStart = pageStartOf(result.data);
214
224
  return {
215
225
  success: true,
216
226
  data: {
217
227
  requests: lastN === 0 ? filtered : filtered.slice(-lastN),
218
228
  totalCount: result.data.length,
219
229
  filteredCount: filtered.length,
230
+ ...(pageStart && { pageStart }),
220
231
  },
221
232
  };
222
233
  }, options, (data) => formatNetworkList(data.requests, buildFormatOptions(options, data, lastN)));
@@ -11,6 +11,10 @@ import { MAX_EDGE_PX, PIXELS_PER_TOKEN, TALL_PAGE_THRESHOLD, } from './dom/scree
11
11
  const TRIGGERED_REQUESTS_BEHAVIOR = 'Requests (and WebSocket connections) that start after the action begins are returned as triggeredRequests (method, url, status, durationMs; pending when still running at return, loading when the response arrived but its body is still streaming; with resourceType; human output lists documents, XHR/fetch and WebSockets first (up to 10) and counts static assets on one line; absent when network telemetry is off). Attribution is by time: requests a page timer or poller starts meanwhile are listed too, whether or not the action caused them';
12
12
  /** What every DOM action reports about the page besides its requests */
13
13
  const ACTION_EFFECTS_BEHAVIOR = 'The result also says what changed on the page: a navigation (Page: navigated to <url> (status), or URL changed to <url> (same document); JSON navigation { url, sameDocument, status }), and messages that appeared or changed in alert/status/aria-live elements or flash/error/toast-like classes (New text: "…" (element); JSON messages [{ text, element }], at most 3; after a navigation every message on the new page counts; texts of only digits and time units, such as clocks and counters, are left out, but other text that changes on its own, such as a rotating banner, can show up). Both are absent when nothing changed. Cost: one page script sent before the action without waiting for it and one read after it, a few ms; when the page does not answer (a pending navigation) bdg waits at most 200 ms for the snapshot and 250 ms per read, and the navigation is still reported from CDP events';
14
+ /** What click and pressKey report when the page was still changing as they returned */
15
+ const STILL_CHANGING_BEHAVIOR = 'When the page was still changing as the action returned, the status line says (page still changing), a note below it says what was pending and suggests bdg dom wait <selector>, and JSON has settled: false with pending { requests (document, fetch/XHR and script requests still running), navigation (a new page still loading), loading (a loading indicator that appeared, e.g. "div#loading"), domChanging (DOM changes kept coming in bursts over a second look 250 ms later; a single render, ticking text and style animations do not count), busy (the page did not answer within 250 ms: a long script) }; absent when the page looked settled (exit code stays 0). A result a timer renders later, with no DOM change, request or loading indicator before it, is not detected. Cost: nothing extra, except 250 ms plus one read when the DOM looked busy. Not checked with --no-wait';
16
+ /** What hover and pressKey report about elements they showed */
17
+ const SHOWN_BEHAVIOR = 'Elements the action showed are listed (Shown: <element> "<text>"; JSON shown [{ text, element }], at most 3, outermost first): elements with visible text added inside the target\'s form, search box, dialog or combobox (else its grandparent, or its parent when that is the body), and popups and messages added anywhere (tooltip, menu, listbox, dialog, alert, status roles, popover, aria-live, message-like classes); widgets elsewhere on the page and re-rendered elements whose text was there before do not count';
14
18
  /** What `--no-wait` does to a DOM action's triggered requests */
15
19
  const NO_WAIT_TRIGGERED_REQUESTS = 'Returns immediately without waiting for network; triggeredRequests lists only requests bdg saw start before returning (often none yet; check bdg network list later)';
16
20
  /**
@@ -76,7 +80,7 @@ const OPTION_BEHAVIORS = {
76
80
  'eval:--frame': {
77
81
  default: "Evaluates in the page's main frame",
78
82
  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)",
79
- automaticBehavior: 'The value is matched as: a 0-based index (bdg dom frames order, 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).',
83
+ 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.',
80
84
  },
81
85
  'console:-H': {
82
86
  default: 'Shows messages from current page load only (most recent navigation)',
@@ -118,7 +122,7 @@ const OPTION_BEHAVIORS = {
118
122
  'click:--no-wait': {
119
123
  default: 'Waits for network stability after click (150ms idle, up to 2s)',
120
124
  whenDisabled: NO_WAIT_TRIGGERED_REQUESTS,
121
- automaticBehavior: `Network wait helps ensure AJAX requests triggered by click complete. ${TRIGGERED_REQUESTS_BEHAVIOR}. The click itself uses real mouse events in the visible part of the element (method "mouse"); if the element is covered or has no size it falls back to DOM events (method "dom", with a warning). Results the page shows later without requests (timers, spinners) are not waited for: use bdg dom wait <selector> --visible. ${ACTION_EFFECTS_BEHAVIOR}. A click with no DOM change, no request and no navigation (checked again 300 ms later, which adds 300 ms plus at most 250 ms for the read) is reported as ⚠ Element Clicked (no visible effect observed: no DOM change, requests or navigation within 300 ms) and effect: "none" in JSON (exit code stays 0); not claimed with --no-wait, for hover or right-click, after a copy or cut, or when the click hit a form control, label, media, iframe, popover button, a mailto:/tel:/javascript: or other non-http link, a link to another window or a custom element with a closed shadow root, or moved focus to an element that is not a button or link. Effects outside the DOM (CSS :hover/:focus-within styles, canvas, clipboard without a copy event) are not seen`,
125
+ automaticBehavior: `Network wait helps ensure AJAX requests triggered by click complete. ${TRIGGERED_REQUESTS_BEHAVIOR}. The click itself uses real mouse events in the visible part of the element (method "mouse"); if the element is covered or has no size it falls back to DOM events (method "dom", with a warning; --strict refuses instead). Results the page shows later (timers, spinners, slow renders) are not waited for but reported as pending work: use bdg dom wait <selector> --visible. ${ACTION_EFFECTS_BEHAVIOR}. ${STILL_CHANGING_BEHAVIOR}. A click with no DOM change, no request and no navigation (checked again 300 ms later, which adds 300 ms plus at most 250 ms for the read) is reported as ⚠ Element Clicked (no visible effect observed: no DOM change, requests or navigation within 300 ms) and effect: "none" in JSON (exit code stays 0); not claimed with --no-wait, for hover or right-click, after a copy or cut, or when the click hit a form control, label, media, iframe, popover button, a mailto:/tel:/javascript: or other non-http link, a link to another window or a custom element with a closed shadow root, or moved focus to an element that is not a button or link. Effects outside the DOM (CSS :hover/:focus-within styles, canvas, clipboard without a copy event) are not seen`,
122
126
  tokenImpact: 'A click that navigates lists the whole page load in JSON triggeredRequests',
123
127
  },
124
128
  'click:--double': {
@@ -129,10 +133,19 @@ const OPTION_BEHAVIORS = {
129
133
  default: 'Left click',
130
134
  whenEnabled: 'Right-click: the page gets contextmenu (custom context menus open); cannot be combined with --double',
131
135
  },
136
+ 'click:--strict': {
137
+ default: 'A covered, hidden or zero-size element is clicked with DOM events (method "dom", with a warning, exit 0)',
138
+ whenEnabled: 'Refuses with exit 90 (RESOURCE_CONFLICT: the page state blocks the request) when a real mouse cannot reach the element, naming what covers it and suggesting bdg dom layout <selector>; also when the mouse press never reached the element (it is released, no further presses for --double)',
139
+ automaticBehavior: 'Applies to --double and --right too. Nothing is dispatched when the element is unreachable, so the page is unchanged',
140
+ },
132
141
  'hover:--no-wait': {
133
142
  default: 'Waits for network stability after moving the mouse (menus may load content)',
134
143
  whenDisabled: NO_WAIT_TRIGGERED_REQUESTS,
135
- automaticBehavior: `The mouse stays over the element afterwards, so hover menus stay open until the next mouse action. ${TRIGGERED_REQUESTS_BEHAVIOR}. ${ACTION_EFFECTS_BEHAVIOR}`,
144
+ automaticBehavior: `The mouse stays over the element afterwards, so hover menus stay open until the next mouse action. ${TRIGGERED_REQUESTS_BEHAVIOR}. ${ACTION_EFFECTS_BEHAVIOR}. ${SHOWN_BEHAVIOR}; for a hover also elements around it (its parent's subtree) and tooltips, menus, listboxes, dialogs and popovers anywhere that were hidden before, so captions shown by CSS :hover count (hidden elements noted by identity right before the mouse moves: up to 1500, within 8 ms). A hover never claims "no visible effect" and does not check whether the page was still changing`,
145
+ },
146
+ 'hover:--strict': {
147
+ default: 'A covered, hidden or zero-size element gets synthetic mouseover/mouseenter events (method "dom", with a warning)',
148
+ whenEnabled: 'Refuses with exit 90 when a real mouse cannot reach the element, naming what covers it and suggesting bdg dom layout <selector>',
136
149
  },
137
150
  'navigate:--no-wait': {
138
151
  default: 'Waits until the new page has loaded and the network and DOM are idle (up to 15 s)',
@@ -142,7 +155,7 @@ const OPTION_BEHAVIORS = {
142
155
  'pressKey:--no-wait': {
143
156
  default: 'Waits for network stability after key press (150ms idle, up to 2s)',
144
157
  whenDisabled: NO_WAIT_TRIGGERED_REQUESTS,
145
- automaticBehavior: `${TRIGGERED_REQUESTS_BEHAVIOR}. ${ACTION_EFFECTS_BEHAVIOR}`,
158
+ automaticBehavior: `${TRIGGERED_REQUESTS_BEHAVIOR}. ${ACTION_EFFECTS_BEHAVIOR}. ${SHOWN_BEHAVIOR}, such as the item Enter added to a list. ${STILL_CHANGING_BEHAVIOR}. A key press never claims "no visible effect"`,
146
159
  },
147
160
  'pressKey:--times': {
148
161
  default: 'Presses key once',
@@ -187,7 +200,7 @@ const OPTION_BEHAVIORS = {
187
200
  'listeners:--type': {
188
201
  default: 'Lists listeners of every event type on the element, its ancestors (through open shadow roots), its document and window',
189
202
  whenEnabled: 'Lists only these event types (comma-separated or repeated, case-sensitive like addEventListener); when none match, typeSuggestions names close types (Click, onclick → click)',
190
- automaticBehavior: "Listeners are grouped by type, the element's own handlers first (types handled on the element before delegated ones; nearest first, no-ops last); JSON line/column numbers are 0-based (human output shows them 1-based like DevTools). React's on… props that run for the element (its own and its React parents', through portals; onFocus/onBlur as focusin/focusout; parents' props for non-bubbling events left out) are listed with source and location (framework: \"React\", reactProp: \"onClick\"; at most 50 of the requested types, reactHandlersSkipped counts the rest). jQuery handlers are shown instead of jQuery's dispatcher (framework: \"jQuery\", delegateSelector for delegates the element matches; a dispatcher with no handler for the element is omitted; at most 50 are resolved, jqueryHandlersSkipped counts the rest). Empty handlers (React's onclick placeholder) are marked noop and do not count as the element's own handler. The Debugger domain is not enabled",
203
+ automaticBehavior: 'Listeners are grouped by type, the element\'s own handlers first (types handled on the element before delegated ones; nearest first, no-ops last); JSON line/column numbers are 0-based (human output shows them 1-based like DevTools). React\'s on… props that run for the element (its own and its React parents\', through portals and from a nested root into the outer root; onFocus/onBlur as focusin/focusout; parents\' props for non-bubbling events left out) are listed with source and location (framework: "React", reactProp: "onClick"; at most 50 of the requested types, reactHandlersSkipped counts the rest). jQuery handlers are shown instead of jQuery\'s dispatcher (framework: "jQuery", delegateSelector for delegates the element matches; a dispatcher with no handler for the element is omitted; at most 50 are resolved, jqueryHandlersSkipped counts the rest). Preact\'s event proxy is replaced by the handler Preact runs (framework: "Preact"). Empty handlers (React\'s onclick placeholder) are marked noop and do not count as the element\'s own handler. The Debugger domain is not enabled',
191
204
  tokenImpact: 'Framework roots are already collapsed; --type keeps the output short on pages with many listeners',
192
205
  },
193
206
  'listeners:--all': {
@@ -201,6 +214,35 @@ const OPTION_BEHAVIORS = {
201
214
  automaticBehavior: 'Coordinates are CSS px: bounds relative to the top-level page (iframe offsets and page scroll included), viewport relative to the visible area. Iframes and overflow containers (scroll lists, overflow: hidden) clip what counts as visible (clippedBy names the one cutting it off). scrollBy brings the whole element into view and is limited to how far the page can scroll: for an element out of view it centres it (aligns its start when it is larger than the viewport; human output says "to centre it"), for a partly visible one it is the smallest scroll that shows all of it (the part cut off at the top or bottom; the start of one larger than the viewport; "partly visible (87%); scroll up 5px to see all of it"). Elements a page script moves on scroll (floating menus) may move again after it; fixed and sticky elements (page scroll does not move them, or only until they stick) and ones beyond that range get offScreenReason instead, which says "page scrolling is locked (…)" when the page cannot scroll because body/html is position: fixed or overflow: hidden, so in-flow content is not called fixed; a visible dialog (dialog[open], [aria-modal=true], [role=dialog|alertdialog]) is named as the likely cause ("likely by dialog div#consent"). page.viewport is the layout viewport without scrollbars, as dom scroll reports it; page.colorScheme is the prefers-color-scheme media feature the page sees (not the theme it renders). Content in a closed <details> or under content-visibility: hidden is hidden. coveredBy is the topmost element at the center of the largest visible box (none for pointer-events: none, nor for an element of the same click target: an overlay inside the link, button or label the element is in, a link to the same URL, or the textless absolutely positioned overlay link spanning the card that holds plain content); inert elements are flagged, not hidden',
202
215
  tokenImpact: 'About one line per element; a cheap alternative to screenshots for "where is it?"',
203
216
  },
217
+ 'inspect:--index': {
218
+ default: 'Inspects the first rendered match (the first when none is rendered) and notes how many matched; a numeric argument inspects that cached element (from dom query, dom form or dom a11y query)',
219
+ whenEnabled: 'Inspects the nth match (0-based); out of range exits 81',
220
+ automaticBehavior: 'Answers "what does it look like" without a screenshot, grouped like Figma Dev Mode: header (element, text, size and page position, [flex]/[grid], [not rendered]/[hidden]/[offscreen]/[covered by …], prefers-color-scheme), box (margin, padding, border widths, box-sizing, overflow, scroll size), layout (display, position, flex/grid container and item settings), parent (its display and layout, distances to its content edges, gaps to the neighbouring siblings), text (first font family → the font Chrome rendered, (webfont) or local; weight size/line-height; color; WCAG contrast against the composited background; only for elements with text), fill, border (sides, radius, outline), fx (shadow, transform, filter, opacity, blend), state (cursor, pointer-events, user-select, appearance), pseudo (::before/::after with content, ::placeholder) and a child tree (depth 2, 20 rows, identical siblings grouped). Values that change nothing (0, none, transparent, normal) are left out; colors are hex (lab/oklch from Tailwind converted), lengths px without the unit, rounded to 0.1. Secrets are never shown. JSON uses Figma-aligned names (rect, box, layout.sizing hug/fill/fixed, text, fills, strokes, radius, effects, children). Also by default: hints, the element\'s own declarations that have no effect (justify-content on a block, width on an inline element, top on a static one, var() of an unset custom property) with the reason, the fix and the rule\'s file:line',
221
+ tokenImpact: 'About 60–80 tokens for the styles and 30–70 more for the child tree (--tree 0 drops it), against about 1,500 for a screenshot or 3,000+ for raw computed styles',
222
+ },
223
+ 'inspect:--all': {
224
+ default: 'Shows the curated groups (the properties that define the look)',
225
+ whenEnabled: 'Lists every computed property that differs from the default of the same element type, longhands collapsed into shorthands, noise (logical duplicates, currentColor echoes, custom properties) dropped',
226
+ tokenImpact: 'About 80 tokens instead of 60–80',
227
+ },
228
+ 'inspect:--props': {
229
+ default: 'Shows the curated groups',
230
+ whenEnabled: 'Shows only the named properties (custom properties like --brand included, "(not set)" when no rule sets one; --* lists every custom property the element has, --bs-btn-* those with a prefix), each computed and normalized; an unknown name exits 81 with a suggestion',
231
+ },
232
+ 'inspect:--rules': {
233
+ default: 'Shows the values, not where they come from',
234
+ whenEnabled: "Adds a rules group: for each shown property the page's CSS sets, the value as written (with the computed value when it uses var()), selector, file:line (column for minified files), @media/@container condition, cascade layer, how many ancestors up it is inherited from, and the rules it beats. Sides one declaration sets are one row; browser defaults are left out. With --props, only those properties",
235
+ automaticBehavior: 'The cascade is computed by bdg from CSS.getMatchedStylesForNode (origin, !important, style attribute, layers, specificity and order); reading it waits up to 5 s, then the output notes the cascade was not read',
236
+ tokenImpact: 'About 15–25 tokens per row, 5–20 rows',
237
+ },
238
+ 'inspect:--why': {
239
+ default: 'Not shown',
240
+ whenEnabled: "Adds why <property> = computed value, then every declaration of it on the element, highest precedence first: ✓ the winner (or the inherited ancestor's), ✗ the ones it beats, browser defaults included. Each rule shows its selector specificity [ids,classes,types]. var() values are shown substituted (or invalid: --x not set), with where the winner's custom properties are set, followed up to :root. Logical names map to physical ones (margin-inline-start → margin-left); a shorthand (padding, border) gives one answer when one declaration sets all its sides, else one per side",
241
+ },
242
+ 'inspect:--no-hints': {
243
+ 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",
244
+ whenEnabled: 'Skips the hints and does not read the matched rules',
245
+ },
204
246
  'scroll:--down': {
205
247
  whenEnabled: 'Scrolls page down by specified pixel amount',
206
248
  },
@@ -282,7 +324,7 @@ const OPTION_BEHAVIORS = {
282
324
  'bdg:--viewport': {
283
325
  default: 'A launched Chrome opens a 1920x1080 window (the viewport is smaller by the scrollbar, and in a visible window by the browser UI); an attached Chrome keeps its window',
284
326
  whenEnabled: 'The page gets exactly that viewport (CSS px, e.g. 1280x800) for the whole session, through navigations and reloads (Emulation.setDeviceMetricsOverride at the display pixel ratio); a launched Chrome also opens its window at that size, so tabs the page opens get it too',
285
- automaticBehavior: 'Works with --chrome-ws-url: the override belongs to the session, and Chrome drops it when the session ends, so the attached browser gets its own size back. bdg status shows the resulting layout viewport without the scrollbar (Viewport: 1265×800 (--viewport 1280x800)). Invalid sizes (not WxH, a side outside 1-10000) exit 81',
327
+ automaticBehavior: 'Works with --chrome-ws-url: the override belongs to the session, and Chrome drops it when the session ends, so the attached browser gets its own size back. bdg status shows the resulting layout viewport without the scrollbar (Viewport: 1265×800 (emulated 1280x800)). Invalid sizes (not WxH, a side outside 1-10000) exit 81',
286
328
  },
287
329
  'bdg:--color-scheme': {
288
330
  default: 'The page sees the system setting for prefers-color-scheme (headless Chrome follows the OS, so a dark OS renders dark pages); bdg status and dom layout show which one',
@@ -2,7 +2,7 @@
2
2
  * `bdg page navigate|reload|back|forward` — move the session's page;
3
3
  * `bdg page info` — where it is.
4
4
  */
5
- import type { Command } from 'commander';
5
+ import { type Command } from 'commander';
6
6
  /**
7
7
  * Register the `page` command group.
8
8
  *
@@ -2,12 +2,15 @@
2
2
  * `bdg page navigate|reload|back|forward` — move the session's page;
3
3
  * `bdg page info` — where it is.
4
4
  */
5
+ import { Option } from 'commander';
5
6
  import { noActiveSessionError, runCommand } from './shared/CommandRunner.js';
6
7
  import { jsonOption } from './shared/commonOptions.js';
8
+ import { parseColorScheme, parseViewport } from './start.js';
9
+ import { CommandError } from '../errors/index.js';
7
10
  import { javascriptNavigationError } from '../errors/messages.js';
8
- import { getStatus, pageNavigate } from '../ipc/client.js';
11
+ import { getStatus, pageEmulate, pageNavigate } from '../ipc/client.js';
9
12
  import { OutputFormatter } from '../ui/formatting.js';
10
- import { PAGE_ACTION_DESCRIPTIONS, PAGE_ACTION_DONE, PAGE_INFO_DESCRIPTION, pageLoadingWarning, } from '../ui/messages/commands.js';
13
+ import { PAGE_ACTION_DESCRIPTIONS, PAGE_ACTION_DONE, PAGE_EMULATE_DESCRIPTION, PAGE_INFO_DESCRIPTION, pageEmulateNothingError, pageEmulationLines, pageLoadingWarning, } from '../ui/messages/commands.js';
11
14
  import { EXIT_CODES } from '../utils/exitCodes.js';
12
15
  import { validateUrl } from '../utils/url.js';
13
16
  /**
@@ -114,6 +117,49 @@ async function showPageInfo(options) {
114
117
  ], 8)
115
118
  .build());
116
119
  }
120
+ /**
121
+ * The emulation request from the options.
122
+ *
123
+ * @param options - Command options
124
+ * @returns Request
125
+ * @throws CommandError (81) for nothing to change or an invalid value
126
+ */
127
+ function emulationRequest(options) {
128
+ if (options.reset)
129
+ return { reset: true };
130
+ if (options.viewport === undefined && options.colorScheme === undefined) {
131
+ const err = pageEmulateNothingError();
132
+ throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
133
+ }
134
+ return {
135
+ ...(options.viewport !== undefined && { viewport: parseViewport(options.viewport) }),
136
+ ...(options.colorScheme !== undefined && {
137
+ colorScheme: parseColorScheme(options.colorScheme),
138
+ }),
139
+ };
140
+ }
141
+ /**
142
+ * `bdg page emulate`: change the viewport or color scheme mid-session.
143
+ *
144
+ * @param options - Command options
145
+ */
146
+ async function emulate(options) {
147
+ await runCommand(async () => {
148
+ const response = await pageEmulate(emulationRequest(options));
149
+ if (response.status === 'error' || !response.data) {
150
+ return {
151
+ success: false,
152
+ error: response.error ?? 'Failed to change the emulation',
153
+ exitCode: response.exitCode ?? EXIT_CODES.SOFTWARE_ERROR,
154
+ ...(response.suggestion && { errorContext: { suggestion: response.suggestion } }),
155
+ };
156
+ }
157
+ return { success: true, data: response.data };
158
+ }, options, (result) => new OutputFormatter()
159
+ .text('✓ Page emulation changed')
160
+ .keyValueList(pageEmulationLines(result), 10)
161
+ .build());
162
+ }
117
163
  /**
118
164
  * Register the `page` command group.
119
165
  *
@@ -122,7 +168,7 @@ async function showPageInfo(options) {
122
168
  export function registerPageCommands(program) {
123
169
  const page = program
124
170
  .command('page')
125
- .description('The session page: info (URL and title), navigate <url>, reload, back, forward');
171
+ .description('The session page: info (URL and title), navigate <url>, reload, back, forward, emulate (viewport, color scheme)');
126
172
  page
127
173
  .command('info')
128
174
  .description(PAGE_INFO_DESCRIPTION)
@@ -139,6 +185,19 @@ export function registerPageCommands(program) {
139
185
  .argument('<url>', 'URL to load')).action(async (url, options) => {
140
186
  await runPageAction('navigate', options, url);
141
187
  });
188
+ page
189
+ .command('emulate')
190
+ .description(PAGE_EMULATE_DESCRIPTION)
191
+ .option('--viewport <WxH>', 'Viewport size in CSS px, e.g. 900x700')
192
+ .option('--color-scheme <scheme>', 'Emulate prefers-color-scheme: light or dark')
193
+ .addOption(new Option('--reset', 'Back to the browser window size and the system setting').conflicts([
194
+ 'viewport',
195
+ 'colorScheme',
196
+ ]))
197
+ .addOption(jsonOption())
198
+ .action(async (options) => {
199
+ await emulate(options);
200
+ });
142
201
  for (const action of ['reload', 'back', 'forward']) {
143
202
  withCommon(page.command(action).description(PAGE_ACTION_DESCRIPTIONS[action])).action(async (options) => {
144
203
  await runPageAction(action, options);
@@ -30,4 +30,8 @@ export declare function showBothSectionsWhenBothRequested(options: {
30
30
  network?: boolean;
31
31
  console?: boolean;
32
32
  }): void;
33
+ /** Help of a `<selectorOrIndex>` argument */
34
+ export declare const SELECTOR_OR_INDEX_ARGUMENT = "CSS selector (searches open shadow roots and same-origin iframes) or numeric index from query results (0-based)";
35
+ /** Help text after `dom query`'s options: what selectors search and what they cannot */
36
+ export declare const SELECTOR_SCOPE_HELP = "\nSelectors search the page, open shadow roots and same-origin iframes (nested ones\nincluded). They cannot reach into closed shadow roots or cross-origin iframes:\nuse bdg dom eval --frame <frame> for those (see bdg dom frames).";
33
37
  //# sourceMappingURL=commonOptions.d.ts.map
@@ -34,4 +34,13 @@ export function showBothSectionsWhenBothRequested(options) {
34
34
  options.console = false;
35
35
  }
36
36
  }
37
+ /** Where selectors search, for the help of commands that take one */
38
+ const SELECTOR_SCOPE = 'searches open shadow roots and same-origin iframes';
39
+ /** Help of a `<selectorOrIndex>` argument */
40
+ export const SELECTOR_OR_INDEX_ARGUMENT = `CSS selector (${SELECTOR_SCOPE}) or numeric index from query results (0-based)`;
41
+ /** Help text after `dom query`'s options: what selectors search and what they cannot */
42
+ export const SELECTOR_SCOPE_HELP = `
43
+ Selectors search the page, open shadow roots and same-origin iframes (nested ones
44
+ included). They cannot reach into closed shadow roots or cross-origin iframes:
45
+ use bdg dom eval --frame <frame> for those (see bdg dom frames).`;
37
46
  //# sourceMappingURL=commonOptions.js.map
@@ -183,6 +183,8 @@ export interface ClickCommandOptions extends BaseOptions, IndexOptions {
183
183
  double?: boolean;
184
184
  /** Right-click */
185
185
  right?: boolean;
186
+ /** Refuse instead of falling back to DOM events when the mouse can't reach the element */
187
+ strict?: boolean;
186
188
  }
187
189
  /**
188
190
  * Options for submit command.
@@ -236,6 +238,25 @@ export interface ListenersCommandOptions extends BaseOptions, IndexOptions {
236
238
  * Options for `dom layout`.
237
239
  */
238
240
  export type LayoutCommandOptions = BaseOptions & IndexOptions;
241
+ /**
242
+ * Options for `dom inspect`.
243
+ */
244
+ export interface InspectCommandOptions extends BaseOptions, IndexOptions {
245
+ /** Child tree depth (0 for none) */
246
+ tree?: number;
247
+ /** Child tree rows at most */
248
+ treeLimit?: number;
249
+ /** Every non-default property instead of the groups */
250
+ all?: boolean;
251
+ /** Only these properties */
252
+ props?: string[];
253
+ /** Which declaration sets each shown property */
254
+ rules?: boolean;
255
+ /** Every declaration of this property */
256
+ why?: string;
257
+ /** `--no-hints` sets false */
258
+ hints?: boolean;
259
+ }
239
260
  /** Options for `bdg dom wait` */
240
261
  export interface WaitCommandOptions extends BaseOptions {
241
262
  /** Text a match must contain */
@@ -5,7 +5,48 @@
5
5
  * Handles IPC communication with daemon to start browser sessions.
6
6
  */
7
7
  import type { SessionStartOptions } from './optionTypes.js';
8
+ import { type SpawnedDaemon } from '../../daemon/launcher.js';
9
+ import { startSession as sendStartSessionRequest } from '../../ipc/client.js';
10
+ import { IPCErrorCode, type StartSessionResponseData } from '../../ipc/index.js';
8
11
  import type { TelemetryType } from '../../types.js';
12
+ /**
13
+ * Outcome of a start attempt, printed by {@link reportStartOutcome}.
14
+ */
15
+ export type StartOutcome = {
16
+ ok: true;
17
+ data: StartSessionResponseData;
18
+ } | {
19
+ ok: false;
20
+ /** Message for `--json` (no "Error:" prefix) */
21
+ error: string;
22
+ /** Full human-readable message */
23
+ human: string;
24
+ exitCode: number;
25
+ /** Daemon's error code (internal: not printed) */
26
+ errorCode?: IPCErrorCode | undefined;
27
+ /** Extra fields of the JSON error envelope */
28
+ details?: Record<string, unknown>;
29
+ /** The daemon went away mid-request (e.g. the previous session was ending) */
30
+ retryable?: boolean;
31
+ /**
32
+ * The daemon reported the failure or dropped the connection, so it is
33
+ * exiting (a daemon this start spawned is then waited for)
34
+ */
35
+ daemonExiting?: boolean;
36
+ /** The daemon this attempt spawned, when it is exiting */
37
+ spawned?: SpawnedDaemon;
38
+ };
39
+ /** How long a failed start waits for the daemons it spawned to exit */
40
+ export declare const SPAWNED_DAEMON_EXIT_WAIT_MS = 3000;
41
+ /** What a start uses to reach the daemon (replaced in tests) */
42
+ export interface StartDeps {
43
+ /** Spawns the daemon if none runs ({@link launchDaemon}) */
44
+ launch: () => Promise<SpawnedDaemon | undefined>;
45
+ /** Sends `start_session_request` */
46
+ send: typeof sendStartSessionRequest;
47
+ /** How long a failed start waits for the daemons it spawned to exit */
48
+ exitWaitMs: number;
49
+ }
9
50
  /**
10
51
  * Start a session via the daemon and report the result.
11
52
  *
@@ -17,4 +58,29 @@ import type { TelemetryType } from '../../types.js';
17
58
  * @param telemetry - Array of telemetry types to enable
18
59
  */
19
60
  export declare function startSessionViaDaemon(url: string, options: SessionStartOptions, telemetry: TelemetryType[]): Promise<never>;
61
+ /**
62
+ * Start a session, retrying while the previous session shuts down. After a
63
+ * failure the daemon reported (or a dropped connection), it waits for every
64
+ * daemon the attempts spawned to exit ({@link afterSpawnedDaemonExit}).
65
+ *
66
+ * @param url - Target URL
67
+ * @param options - Session options
68
+ * @param telemetry - Telemetry types
69
+ * @param deps - How to reach the daemon (tests replace it)
70
+ * @returns Start outcome, ready to report
71
+ */
72
+ export declare function attemptStart(url: string, options: SessionStartOptions, telemetry: TelemetryType[], deps?: StartDeps): Promise<StartOutcome>;
73
+ /**
74
+ * Let the daemons a failed start spawned finish exiting before the error is
75
+ * reported: a daemon removes its session files on the way out, and a command
76
+ * run right after (`bdg sessions`, another start) would otherwise still see
77
+ * the session as starting. The wait is bounded; a daemon still running after
78
+ * it is reported (`details.daemonStillRunning`, `daemonPid`, a suggestion).
79
+ *
80
+ * @param outcome - Start outcome
81
+ * @param spawned - Exiting daemons the attempts spawned
82
+ * @param waitMs - Milliseconds to wait at most for all of them
83
+ * @returns The failure (without internal fields), with a hint when a daemon did not exit in time
84
+ */
85
+ export declare function afterSpawnedDaemonExit(outcome: StartOutcome, spawned: SpawnedDaemon[], waitMs: number): Promise<StartOutcome>;
20
86
  //# sourceMappingURL=startHelpers.d.ts.map