browser-debugger-cli 0.11.0 → 0.13.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 (222) hide show
  1. package/.claude/skills/bdg/SKILL.md +4 -4
  2. package/README.md +143 -79
  3. package/dist/commands/cdp.d.ts +22 -1
  4. package/dist/commands/cdp.js +100 -43
  5. package/dist/commands/console.d.ts +12 -0
  6. package/dist/commands/console.js +62 -12
  7. package/dist/commands/css.d.ts +13 -0
  8. package/dist/commands/css.js +53 -0
  9. package/dist/commands/dom/DomElementResolver.d.ts +3 -1
  10. package/dist/commands/dom/DomElementResolver.js +10 -3
  11. package/dist/commands/dom/a11y.js +3 -2
  12. package/dist/commands/dom/audit.d.ts +14 -0
  13. package/dist/commands/dom/audit.js +87 -0
  14. package/dist/commands/dom/eval.d.ts +3 -2
  15. package/dist/commands/dom/eval.js +11 -5
  16. package/dist/commands/dom/form.js +10 -9
  17. package/dist/commands/dom/formInteraction.js +42 -11
  18. package/dist/commands/dom/get.js +8 -8
  19. package/dist/commands/dom/helpers/index.d.ts +1 -1
  20. package/dist/commands/dom/helpers/index.js +1 -1
  21. package/dist/commands/dom/helpers/keyAttributes.d.ts +3 -2
  22. package/dist/commands/dom/helpers/keyAttributes.js +6 -4
  23. package/dist/commands/dom/helpers/query.d.ts +27 -3
  24. package/dist/commands/dom/helpers/query.js +152 -64
  25. package/dist/commands/dom/helpers/screenshot.d.ts +1 -0
  26. package/dist/commands/dom/helpers/screenshot.js +169 -49
  27. package/dist/commands/dom/index.js +7 -2
  28. package/dist/commands/dom/query.d.ts +19 -2
  29. package/dist/commands/dom/query.js +37 -6
  30. package/dist/commands/dom/screenshot.js +12 -7
  31. package/dist/commands/dom/semanticUtils.d.ts +3 -2
  32. package/dist/commands/dom/semanticUtils.js +40 -9
  33. package/dist/commands/dom/wait.js +5 -3
  34. package/dist/commands/helpJson.d.ts +82 -19
  35. package/dist/commands/helpJson.js +112 -41
  36. package/dist/commands/helpTopic.d.ts +16 -1
  37. package/dist/commands/helpTopic.js +59 -1
  38. package/dist/commands/installSkill.d.ts +15 -5
  39. package/dist/commands/installSkill.js +86 -16
  40. package/dist/commands/network/list.js +22 -12
  41. package/dist/commands/optionBehaviors.js +53 -16
  42. package/dist/commands/page.js +7 -4
  43. package/dist/commands/peek.d.ts +7 -0
  44. package/dist/commands/peek.js +65 -23
  45. package/dist/commands/shared/daemonErrorHandler.d.ts +5 -2
  46. package/dist/commands/shared/daemonErrorHandler.js +20 -9
  47. package/dist/commands/shared/dataFetcher.d.ts +12 -4
  48. package/dist/commands/shared/dataFetcher.js +12 -4
  49. package/dist/commands/shared/followMode.d.ts +9 -1
  50. package/dist/commands/shared/followMode.js +22 -4
  51. package/dist/commands/shared/optionTypes.d.ts +9 -2
  52. package/dist/commands/shared/outputFile.js +6 -1
  53. package/dist/commands/start.d.ts +20 -5
  54. package/dist/commands/start.js +84 -23
  55. package/dist/commands/stop.d.ts +11 -0
  56. package/dist/commands/stop.js +24 -1
  57. package/dist/commands/tail.d.ts +7 -1
  58. package/dist/commands/tail.js +13 -62
  59. package/dist/commands.js +2 -0
  60. package/dist/connection/cdp.d.ts +7 -0
  61. package/dist/connection/cdp.js +9 -0
  62. package/dist/connection/launcher.js +3 -2
  63. package/dist/daemon/SessionController.js +6 -1
  64. package/dist/daemon/launcher.d.ts +3 -2
  65. package/dist/daemon/launcher.js +47 -3
  66. package/dist/daemon/session/Session.d.ts +4 -1
  67. package/dist/daemon/session/Session.js +33 -2
  68. package/dist/daemon/session/TelemetryStore.d.ts +8 -1
  69. package/dist/daemon/session/TelemetryStore.js +13 -1
  70. package/dist/daemon/session/commandRegistry.js +36 -14
  71. package/dist/daemon/session/interactions.d.ts +2 -1
  72. package/dist/daemon/session/interactions.js +13 -1
  73. package/dist/daemon/session/plugins.js +19 -53
  74. package/dist/daemon/session/teardown.js +1 -1
  75. package/dist/daemon.js +9234 -7222
  76. package/dist/errors/messages.d.ts +88 -15
  77. package/dist/errors/messages.js +177 -27
  78. package/dist/index.js +19322 -13961
  79. package/dist/ipc/client.d.ts +22 -2
  80. package/dist/ipc/client.js +34 -5
  81. package/dist/ipc/protocol/auditTypes.d.ts +135 -0
  82. package/dist/ipc/protocol/auditTypes.js +6 -0
  83. package/dist/ipc/protocol/commands.d.ts +35 -0
  84. package/dist/ipc/protocol/commands.js +2 -0
  85. package/dist/ipc/protocol/domTypes.d.ts +16 -0
  86. package/dist/ipc/protocol/inspectTypes.d.ts +73 -8
  87. package/dist/ipc/session/types.d.ts +2 -0
  88. package/dist/runtime/css/search.d.ts +39 -0
  89. package/dist/runtime/css/search.js +122 -0
  90. package/dist/runtime/dom/actionEffects.d.ts +9 -2
  91. package/dist/runtime/dom/actionEffects.js +30 -14
  92. package/dist/runtime/dom/audit.d.ts +19 -0
  93. package/dist/runtime/dom/audit.js +37 -0
  94. package/dist/runtime/dom/auditModel.d.ts +45 -0
  95. package/dist/runtime/dom/auditModel.js +220 -0
  96. package/dist/runtime/dom/auditScripts.d.ts +113 -0
  97. package/dist/runtime/dom/auditScripts.js +148 -0
  98. package/dist/runtime/dom/elementGeometry.d.ts +16 -3
  99. package/dist/runtime/dom/elementGeometry.js +49 -10
  100. package/dist/runtime/dom/elementInfo.d.ts +74 -17
  101. package/dist/runtime/dom/elementInfo.js +187 -34
  102. package/dist/runtime/dom/evalHelpers.d.ts +12 -2
  103. package/dist/runtime/dom/evalHelpers.js +67 -7
  104. package/dist/runtime/dom/formDiscovery.d.ts +6 -2
  105. package/dist/runtime/dom/formDiscovery.js +20 -3
  106. package/dist/runtime/dom/formFillHelpers/fill.js +8 -12
  107. package/dist/runtime/dom/formFillHelpers/pressKey.js +2 -2
  108. package/dist/runtime/dom/formFillHelpers/shared.d.ts +16 -10
  109. package/dist/runtime/dom/formFillHelpers/shared.js +19 -52
  110. package/dist/runtime/dom/formSubmitHelpers.js +4 -3
  111. package/dist/runtime/dom/frameLayout.js +1 -0
  112. package/dist/runtime/dom/inspect.d.ts +7 -0
  113. package/dist/runtime/dom/inspect.js +92 -28
  114. package/dist/runtime/dom/inspectAllStyles.d.ts +16 -4
  115. package/dist/runtime/dom/inspectAllStyles.js +90 -7
  116. package/dist/runtime/dom/inspectCascade.d.ts +19 -2
  117. package/dist/runtime/dom/inspectCascade.js +214 -44
  118. package/dist/runtime/dom/inspectCascadeModel.d.ts +8 -0
  119. package/dist/runtime/dom/inspectCascadeModel.js +108 -34
  120. package/dist/runtime/dom/inspectHints.d.ts +26 -3
  121. package/dist/runtime/dom/inspectHints.js +125 -9
  122. package/dist/runtime/dom/inspectModel.d.ts +5 -1
  123. package/dist/runtime/dom/inspectModel.js +37 -10
  124. package/dist/runtime/dom/inspectPaintModel.d.ts +50 -22
  125. package/dist/runtime/dom/inspectPaintModel.js +182 -68
  126. package/dist/runtime/dom/inspectRules.d.ts +19 -0
  127. package/dist/runtime/dom/inspectRules.js +21 -5
  128. package/dist/runtime/dom/inspectScripts.d.ts +112 -12
  129. package/dist/runtime/dom/inspectScripts.js +357 -32
  130. package/dist/runtime/dom/inspectTree.js +10 -2
  131. package/dist/runtime/dom/inspectWhyModel.d.ts +2 -1
  132. package/dist/runtime/dom/inspectWhyModel.js +52 -10
  133. package/dist/runtime/dom/layout.js +40 -16
  134. package/dist/runtime/dom/reactEventHelpers.d.ts +21 -4
  135. package/dist/runtime/dom/reactEventHelpers.js +90 -36
  136. package/dist/runtime/dom/targetNode.d.ts +18 -5
  137. package/dist/runtime/dom/targetNode.js +268 -8
  138. package/dist/runtime/dom/wait.js +2 -1
  139. package/dist/runtime/page/bdgWorld.d.ts +57 -0
  140. package/dist/runtime/page/bdgWorld.js +180 -0
  141. package/dist/runtime/page/emulation.d.ts +13 -4
  142. package/dist/runtime/page/emulation.js +69 -4
  143. package/dist/runtime/page/replacedBuiltins.d.ts +28 -0
  144. package/dist/runtime/page/replacedBuiltins.js +136 -0
  145. package/dist/runtime/page/userAgent.d.ts +17 -0
  146. package/dist/runtime/page/userAgent.js +57 -0
  147. package/dist/session/QueryCacheManager.d.ts +4 -1
  148. package/dist/session/QueryCacheManager.js +5 -2
  149. package/dist/session/chrome.d.ts +4 -1
  150. package/dist/session/chrome.js +7 -1
  151. package/dist/session/cleanup/staleSession.d.ts +21 -4
  152. package/dist/session/cleanup/staleSession.js +79 -9
  153. package/dist/session/cleanup/userCommands.d.ts +4 -1
  154. package/dist/session/cleanup/userCommands.js +10 -5
  155. package/dist/session/daemonSocket.d.ts +10 -0
  156. package/dist/session/daemonSocket.js +22 -0
  157. package/dist/session/lastSession.d.ts +6 -3
  158. package/dist/session/lastSession.js +11 -5
  159. package/dist/session/paths.d.ts +3 -1
  160. package/dist/session/paths.js +5 -5
  161. package/dist/session/portClaims.js +4 -3
  162. package/dist/session/sessionList.d.ts +13 -5
  163. package/dist/session/sessionList.js +31 -7
  164. package/dist/telemetry/a11y.js +2 -2
  165. package/dist/telemetry/console.d.ts +2 -1
  166. package/dist/telemetry/console.js +30 -21
  167. package/dist/telemetry/pageCrash.d.ts +26 -0
  168. package/dist/telemetry/pageCrash.js +53 -0
  169. package/dist/types.d.ts +20 -0
  170. package/dist/ui/formatters/audit.d.ts +19 -0
  171. package/dist/ui/formatters/audit.js +115 -0
  172. package/dist/ui/formatters/cdp.d.ts +138 -0
  173. package/dist/ui/formatters/cdp.js +131 -0
  174. package/dist/ui/formatters/console/chronological.js +3 -1
  175. package/dist/ui/formatters/console/follow.d.ts +2 -1
  176. package/dist/ui/formatters/console/follow.js +2 -2
  177. package/dist/ui/formatters/console/json.d.ts +2 -2
  178. package/dist/ui/formatters/console/json.js +11 -5
  179. package/dist/ui/formatters/console/shared.d.ts +30 -0
  180. package/dist/ui/formatters/console/shared.js +16 -0
  181. package/dist/ui/formatters/console/summarize.d.ts +9 -2
  182. package/dist/ui/formatters/console/summarize.js +40 -9
  183. package/dist/ui/formatters/console.d.ts +2 -1
  184. package/dist/ui/formatters/console.js +7 -5
  185. package/dist/ui/formatters/details.js +3 -1
  186. package/dist/ui/formatters/dom.d.ts +2 -2
  187. package/dist/ui/formatters/dom.js +10 -8
  188. package/dist/ui/formatters/helpFormatters.js +1 -1
  189. package/dist/ui/formatters/inspect.js +50 -17
  190. package/dist/ui/formatters/installSkill.d.ts +9 -1
  191. package/dist/ui/formatters/installSkill.js +32 -6
  192. package/dist/ui/formatters/layout.js +2 -1
  193. package/dist/ui/formatters/networkList.d.ts +1 -1
  194. package/dist/ui/formatters/networkList.js +1 -2
  195. package/dist/ui/formatters/preview.d.ts +2 -0
  196. package/dist/ui/formatters/preview.js +17 -7
  197. package/dist/ui/formatters/sessions.d.ts +2 -2
  198. package/dist/ui/formatters/sessions.js +9 -2
  199. package/dist/ui/formatters/status.js +1 -1
  200. package/dist/ui/logging/logger.d.ts +1 -1
  201. package/dist/ui/messages/commands.d.ts +168 -11
  202. package/dist/ui/messages/commands.js +245 -18
  203. package/dist/ui/messages/consoleMessages.d.ts +24 -0
  204. package/dist/ui/messages/consoleMessages.js +32 -0
  205. package/dist/ui/messages/preview.d.ts +12 -0
  206. package/dist/ui/messages/preview.js +18 -2
  207. package/dist/ui/messages/session.d.ts +13 -2
  208. package/dist/ui/messages/session.js +22 -3
  209. package/dist/utils/cssValues.js +36 -4
  210. package/dist/utils/decisionTrees.js +0 -5
  211. package/dist/utils/directories.d.ts +34 -0
  212. package/dist/utils/directories.js +88 -0
  213. package/dist/utils/display.d.ts +16 -0
  214. package/dist/utils/display.js +42 -0
  215. package/dist/utils/exitCodes.d.ts +1 -0
  216. package/dist/utils/exitCodes.js +6 -0
  217. package/dist/utils/process.d.ts +12 -0
  218. package/dist/utils/process.js +25 -0
  219. package/dist/utils/suggestions.d.ts +4 -2
  220. package/dist/utils/suggestions.js +7 -5
  221. package/dist/utils/taskMappings.js +1 -1
  222. package/package.json +3 -2
@@ -6,11 +6,12 @@ import { runCommand } from './shared/CommandRunner.js';
6
6
  import { jsonOption } from './shared/commonOptions.js';
7
7
  import { noteFollowConnected } from './shared/daemonErrorHandler.js';
8
8
  import { fetchConsoleMessages, createErrorResult } from './shared/dataFetcher.js';
9
- import { followFetchFailure, setupFollowMode, } from './shared/followMode.js';
9
+ import { followFetchFailure, newPageCrashes, setupFollowMode, } from './shared/followMode.js';
10
10
  import { handleValidationError } from './shared/handleValidationError.js';
11
11
  import { consoleLevelOption, positiveIntRule } from './shared/validation.js';
12
12
  import { buildSuccessResponse } from '../ui/OutputBuilder.js';
13
13
  import { buildConsoleJsonOutput, formatConsole, formatConsoleFollowLines, LEVEL_MAP, lastMessages, } from '../ui/formatters/console.js';
14
+ import { pageCrashedNote } from '../ui/messages/commands.js';
14
15
  import { followingConsoleMessage, stoppedFollowingConsoleMessage, } from '../ui/messages/consoleMessages.js';
15
16
  const MIN_LAST = 0;
16
17
  const MAX_LAST = 10000;
@@ -37,9 +38,40 @@ export function listsMessages(options) {
37
38
  export function filterByCurrentNavigation(messages, currentNavigationId) {
38
39
  if (messages.length === 0)
39
40
  return messages;
40
- const navId = currentNavigationId ?? Math.max(...messages.map((m) => m.navigationId ?? 0));
41
+ const navId = shownNavigationId(messages, currentNavigationId);
41
42
  return messages.filter((m) => (m.navigationId ?? 0) === navId);
42
43
  }
44
+ /**
45
+ * Navigation id of the page whose messages are shown without `--history`.
46
+ *
47
+ * @param messages - All captured messages
48
+ * @param currentNavigationId - Navigation id of the current page, if known
49
+ * @returns That id, else the newest navigation id among the messages
50
+ */
51
+ function shownNavigationId(messages, currentNavigationId) {
52
+ return currentNavigationId ?? Math.max(...messages.map((m) => m.navigationId ?? 0));
53
+ }
54
+ /**
55
+ * Dropped messages that could have been in the view: all of them with
56
+ * `--history`; for the current page only while the oldest kept message is
57
+ * that page's (else every dropped one came from an earlier page).
58
+ *
59
+ * @param messages - All kept messages, oldest first
60
+ * @param dropped - Oldest messages the session dropped at its limit
61
+ * @param options - `--history`
62
+ * @param currentNavigationId - Navigation id of the current page, if known
63
+ * @returns Dropped count to warn about (0: none of the view's)
64
+ */
65
+ export function droppedInView(messages, dropped, options, currentNavigationId) {
66
+ if (dropped === 0 || options.history)
67
+ return dropped;
68
+ const oldest = messages[0];
69
+ if (!oldest)
70
+ return dropped;
71
+ return (oldest.navigationId ?? 0) === shownNavigationId(messages, currentNavigationId)
72
+ ? dropped
73
+ : 0;
74
+ }
43
75
  export function filterByLevel(messages, level) {
44
76
  return messages.filter((m) => LEVEL_MAP[m.type] === level);
45
77
  }
@@ -84,10 +116,15 @@ export function skippedMessages(all, listed) {
84
116
  * @param options - Command options
85
117
  * @param lastN - `--last` value
86
118
  * @param skipped - Messages the filters left out between the listed ones
119
+ * @param dropped - Oldest messages the session dropped at its limit
120
+ * @param pageCrashedAt - When the page crashed, while it is not loaded again
87
121
  * @returns Formatting options
88
122
  */
89
- function buildFormatOptions(options, lastN, skipped) {
123
+ function buildFormatOptions(options, lastN, skipped, dropped, pageCrashedAt) {
90
124
  return {
125
+ ...(options.last !== undefined && { groupLimit: lastN }),
126
+ ...(dropped && { dropped }),
127
+ ...(pageCrashedAt !== undefined && { pageCrashedAt }),
91
128
  json: options.json,
92
129
  list: listsMessages(options),
93
130
  follow: options.follow,
@@ -99,13 +136,15 @@ function buildFormatOptions(options, lastN, skipped) {
99
136
  }
100
137
  /**
101
138
  * Stream console messages: the last `lastN` at start, then each new message
102
- * once (like `tail -f`), with a separator when the page navigates.
139
+ * once (like `tail -f`), with a separator when the page navigates and a
140
+ * warning (JSON `pageCrashedAt`) once when the page crashes.
103
141
  *
104
142
  * @param options - Command options
105
143
  * @param lastN - Messages to show at start (0 = all)
106
144
  */
107
145
  async function runFollowMode(options, lastN) {
108
146
  const shown = new Set();
147
+ const newCrash = newPageCrashes();
109
148
  let navigationId;
110
149
  let started = false;
111
150
  const showConsole = async () => {
@@ -115,6 +154,7 @@ async function runFollowMode(options, lastN) {
115
154
  }
116
155
  noteFollowConnected();
117
156
  const { messages, currentNavigationId } = result.data;
157
+ const crashedAt = newCrash(result.data.pageCrashedAt);
118
158
  const matching = applyFilters(messages, options, currentNavigationId);
119
159
  const keys = messageKeys(matching);
120
160
  const fresh = matching.filter((_message, i) => !shown.has(keys[i]));
@@ -124,9 +164,13 @@ async function runFollowMode(options, lastN) {
124
164
  const navigated = started && navigationId !== currentNavigationId;
125
165
  navigationId = currentNavigationId;
126
166
  if (options.json) {
127
- if (!started || backlog.length > 0) {
128
- const data = buildConsoleJsonOutput(backlog, { list: true, last: 0 });
129
- console.log(JSON.stringify(buildSuccessResponse(data), null, 2));
167
+ if (!started || backlog.length > 0 || crashedAt !== undefined) {
168
+ const data = buildConsoleJsonOutput(backlog, {
169
+ list: true,
170
+ last: 0,
171
+ pageCrashedAt: crashedAt,
172
+ });
173
+ console.log(JSON.stringify(buildSuccessResponse(data)));
130
174
  }
131
175
  }
132
176
  else {
@@ -137,6 +181,8 @@ async function runFollowMode(options, lastN) {
137
181
  });
138
182
  if (text)
139
183
  console.log(text);
184
+ if (crashedAt !== undefined)
185
+ console.log(pageCrashedNote(crashedAt));
140
186
  }
141
187
  started = true;
142
188
  return undefined;
@@ -196,19 +242,23 @@ export function registerConsoleCommand(program) {
196
242
  if (!result.success) {
197
243
  return createErrorResult(result.error, result.exitCode, result.suggestion);
198
244
  }
199
- const { messages, currentNavigationId } = result.data;
245
+ const { messages, currentNavigationId, dropped, pageCrashedAt } = result.data;
200
246
  const filtered = applyFilters(messages, options, currentNavigationId);
201
247
  if (options.json) {
202
248
  return {
203
249
  success: true,
204
- data: buildConsoleJsonOutput(filtered, buildFormatOptions(options, lastN)),
250
+ data: buildConsoleJsonOutput(filtered, buildFormatOptions(options, lastN, undefined, dropped, pageCrashedAt)),
205
251
  };
206
252
  }
207
- return { success: true, data: { messages, filtered } };
253
+ const droppedShown = droppedInView(messages, dropped, options, currentNavigationId);
254
+ return {
255
+ success: true,
256
+ data: { messages, filtered, dropped: droppedShown, pageCrashedAt },
257
+ };
208
258
  }, options, (data) => {
209
- const { messages, filtered } = data;
259
+ const { messages, filtered, dropped, pageCrashedAt } = data;
210
260
  const skipped = skippedMessages(messages, lastMessages(filtered, lastN));
211
- return formatConsole(filtered, buildFormatOptions(options, lastN, skipped));
261
+ return formatConsole(filtered, buildFormatOptions(options, lastN, skipped, dropped, pageCrashedAt));
212
262
  });
213
263
  });
214
264
  }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `bdg css search <text>` - find text in the page's stylesheets, including
3
+ * cross-origin ones that page scripts cannot read: where a token is set, which
4
+ * rules use `oklch(`, a class or a custom property.
5
+ */
6
+ import type { Command } from 'commander';
7
+ /**
8
+ * Register the `css` command group.
9
+ *
10
+ * @param program - Root command
11
+ */
12
+ export declare function registerCssCommands(program: Command): void;
13
+ //# sourceMappingURL=css.d.ts.map
@@ -0,0 +1,53 @@
1
+ /**
2
+ * `bdg css search <text>` - find text in the page's stylesheets, including
3
+ * cross-origin ones that page scripts cannot read: where a token is set, which
4
+ * rules use `oklch(`, a class or a custom property.
5
+ */
6
+ import { runCommand } from './shared/CommandRunner.js';
7
+ import { jsonOption } from './shared/commonOptions.js';
8
+ import { integerOption } from './shared/validation.js';
9
+ import { cssSearch } from '../ipc/client.js';
10
+ import { formatCssSearch } from '../ui/formatters/audit.js';
11
+ import { CSS_SEARCH_HELP_EXAMPLES } from '../ui/messages/commands.js';
12
+ import { EXIT_CODES } from '../utils/exitCodes.js';
13
+ /**
14
+ * Register the `css` command group.
15
+ *
16
+ * @param program - Root command
17
+ */
18
+ export function registerCssCommands(program) {
19
+ const css = program.command('css').description("The page's stylesheets: search <text>");
20
+ css
21
+ .command('search')
22
+ .description('Find text in every stylesheet of the page (cross-origin ones too), with the rule and file:line')
23
+ .argument('<text>', 'Text to find (case-insensitive), e.g. oklch( or .btn-primary; put -- before one that starts with - (-- --brand)')
24
+ .option('--limit <n>', 'Matches listed (default: 20)', integerOption(1, 500))
25
+ .addOption(jsonOption())
26
+ .addHelpText('after', CSS_SEARCH_HELP_EXAMPLES)
27
+ .action(async (text, options) => {
28
+ await runCommand(() => search(text, options), options, formatCssSearch);
29
+ });
30
+ }
31
+ /**
32
+ * Ask the daemon to search the stylesheets.
33
+ *
34
+ * @param text - Text to find
35
+ * @param options - Limit
36
+ * @returns Command result
37
+ */
38
+ async function search(text, options) {
39
+ const response = await cssSearch({
40
+ query: text,
41
+ ...(options.limit !== undefined && { limit: options.limit }),
42
+ });
43
+ if (response.status === 'error' || !response.data) {
44
+ return {
45
+ success: false,
46
+ error: response.error ?? 'Failed to search the stylesheets',
47
+ exitCode: response.exitCode ?? EXIT_CODES.CDP_CONNECTION_FAILURE,
48
+ ...(response.suggestion && { errorContext: { suggestion: response.suggestion } }),
49
+ };
50
+ }
51
+ return { success: true, data: response.data };
52
+ }
53
+ //# sourceMappingURL=css.js.map
@@ -109,7 +109,9 @@ export declare class DomElementResolver {
109
109
  *
110
110
  * @param index - Zero-based index
111
111
  * @returns Cached node and the query's selector
112
- * @throws CommandError (83) without a session, (81) without a usable cache, (87) for an index outside the cached results
112
+ * @throws CommandError (83) without a session, (81) without a usable cache,
113
+ * (87) for an index outside the cached results or of a page document
114
+ * that has since been replaced (another page's elements reuse its node ids)
113
115
  */
114
116
  private lookup;
115
117
  }
@@ -18,6 +18,7 @@
18
18
  * // { success: true, selector: 'button.submit' }
19
19
  * ```
20
20
  */
21
+ import { pageDocumentId } from './helpers/query.js';
21
22
  import { noActiveSessionError } from '../shared/CommandRunner.js';
22
23
  import { CommandError } from '../../errors/index.js';
23
24
  import { cachedIndexOutOfRangeError, indexWithIndexOptionError, staleNodeError, } from '../../errors/messages.js';
@@ -136,7 +137,9 @@ export class DomElementResolver {
136
137
  *
137
138
  * @param index - Zero-based index
138
139
  * @returns Cached node and the query's selector
139
- * @throws CommandError (83) without a session, (81) without a usable cache, (87) for an index outside the cached results
140
+ * @throws CommandError (83) without a session, (81) without a usable cache,
141
+ * (87) for an index outside the cached results or of a page document
142
+ * that has since been replaced (another page's elements reuse its node ids)
140
143
  */
141
144
  async lookup(index) {
142
145
  const validation = await this.cacheManager.validate();
@@ -145,11 +148,15 @@ export class DomElementResolver {
145
148
  throw noActiveSessionError();
146
149
  throw new CommandError(validation.error ?? 'No cached query results found', validation.suggestion ? { suggestion: validation.suggestion } : {}, EXIT_CODES.INVALID_ARGUMENTS);
147
150
  }
148
- const { nodes, selector } = validation.cache;
151
+ const { nodes, selector, count, document } = validation.cache;
149
152
  const source = indexSourceOf(index, selector);
153
+ if (document !== undefined && (await pageDocumentId()) !== document) {
154
+ const err = staleNodeError(index, source);
155
+ throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.STALE_CACHE);
156
+ }
150
157
  const node = nodes.find((n) => n.index === index);
151
158
  if (!node) {
152
- const err = cachedIndexOutOfRangeError(source, nodes.length);
159
+ const err = cachedIndexOutOfRangeError(source, nodes.length, count);
153
160
  throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.STALE_CACHE);
154
161
  }
155
162
  return { node, selector, source };
@@ -9,7 +9,7 @@
9
9
  * Uses IPC/callCDP pattern for consistency with other DOM commands.
10
10
  */
11
11
  import { DomElementResolver } from './DomElementResolver.js';
12
- import { getDomContext, resolveBackendNodeIds } from './helpers/index.js';
12
+ import { getDomContext, pageDocumentId, resolveBackendNodeIds, } from './helpers/index.js';
13
13
  import { withSecretMasked } from './semanticUtils.js';
14
14
  import { runCommand, runJsonCommand } from '../shared/CommandRunner.js';
15
15
  import { jsonOption } from '../shared/commonOptions.js';
@@ -85,6 +85,7 @@ async function handleA11yQuery(pattern, options) {
85
85
  const err = invalidQueryPatternError(pattern);
86
86
  throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
87
87
  }
88
+ const document = await pageDocumentId();
88
89
  const tree = await collectA11yTree();
89
90
  const result = queryA11yTree(tree, queryPattern);
90
91
  if (result.count === 0) {
@@ -101,7 +102,7 @@ async function handleA11yQuery(pattern, options) {
101
102
  tag: node.role,
102
103
  ...(node.name && { preview: node.name }),
103
104
  })),
104
- });
105
+ }, document);
105
106
  return {
106
107
  success: true,
107
108
  data: limitMatches(indexed, options.limit ?? (options.json ? 0 : A11Y_QUERY_LIMIT)),
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `bdg dom audit [check...]` - page-wide checks without a screenshot or
3
+ * `dom eval`: text below a WCAG contrast level, what makes the page scroll
4
+ * sideways and cut-off text and scaled images, fixed and sticky layers, and
5
+ * running animations.
6
+ */
7
+ import type { Command } from 'commander';
8
+ /**
9
+ * Register `bdg dom audit`.
10
+ *
11
+ * @param dom - The `dom` command group
12
+ */
13
+ export declare function registerAuditCommand(dom: Command): void;
14
+ //# sourceMappingURL=audit.d.ts.map
@@ -0,0 +1,87 @@
1
+ /**
2
+ * `bdg dom audit [check...]` - page-wide checks without a screenshot or
3
+ * `dom eval`: text below a WCAG contrast level, what makes the page scroll
4
+ * sideways and cut-off text and scaled images, fixed and sticky layers, and
5
+ * running animations.
6
+ */
7
+ import { InvalidArgumentError } from 'commander';
8
+ import { runCommand } from '../shared/CommandRunner.js';
9
+ import { jsonOption } from '../shared/commonOptions.js';
10
+ import { integerOption } from '../shared/validation.js';
11
+ import { unknownAuditCheckMessage } from '../../errors/messages.js';
12
+ import { domAudit } from '../../ipc/client.js';
13
+ import { AUDIT_CHECKS } from '../../ipc/protocol/auditTypes.js';
14
+ import { formatAudit } from '../../ui/formatters/audit.js';
15
+ import { AUDIT_HELP_EXAMPLES } from '../../ui/messages/commands.js';
16
+ import { EXIT_CODES } from '../../utils/exitCodes.js';
17
+ import { findSimilar } from '../../utils/suggestions.js';
18
+ /**
19
+ * Register `bdg dom audit`.
20
+ *
21
+ * @param dom - The `dom` command group
22
+ */
23
+ export function registerAuditCommand(dom) {
24
+ dom
25
+ .command('audit')
26
+ .description('Page-wide checks: text below WCAG contrast, sideways scroll and cut-off text, scaled images, fixed/sticky layers, animations')
27
+ .argument('[checks...]', `Checks to run: ${AUDIT_CHECKS.join(', ')} (default: all)`, checkList)
28
+ .option('--level <level>', 'WCAG level text must reach: AA or AAA (default: AA)', levelOption)
29
+ .option('--limit <n>', 'Findings listed per check (default: 20)', integerOption(1, 500))
30
+ .addOption(jsonOption())
31
+ .addHelpText('after', AUDIT_HELP_EXAMPLES)
32
+ .action(async (checks, options) => {
33
+ await runCommand(() => audit(checks, options), options, formatAudit);
34
+ });
35
+ }
36
+ /**
37
+ * Parse the checks, rejecting unknown names with the closest one.
38
+ *
39
+ * @param value - One check name
40
+ * @param previous - Checks so far
41
+ * @returns Checks
42
+ * @throws InvalidArgumentError for an unknown name
43
+ */
44
+ function checkList(value, previous = []) {
45
+ const check = AUDIT_CHECKS.find((name) => name === value.trim().toLowerCase());
46
+ if (!check) {
47
+ throw new InvalidArgumentError(unknownAuditCheckMessage(value, findSimilar(value, [...AUDIT_CHECKS]), AUDIT_CHECKS));
48
+ }
49
+ return previous.includes(check) ? previous : [...previous, check];
50
+ }
51
+ /**
52
+ * Parse `--level`.
53
+ *
54
+ * @param value - Level
55
+ * @returns AA or AAA
56
+ * @throws InvalidArgumentError for another value
57
+ */
58
+ function levelOption(value) {
59
+ const level = value.trim().toUpperCase();
60
+ if (level === 'AA' || level === 'AAA')
61
+ return level;
62
+ throw new InvalidArgumentError('Use AA or AAA');
63
+ }
64
+ /**
65
+ * Ask the daemon to audit the page.
66
+ *
67
+ * @param checks - Checks given (all when none)
68
+ * @param options - Level and limit
69
+ * @returns Command result
70
+ */
71
+ async function audit(checks, options) {
72
+ const response = await domAudit({
73
+ checks: checks && checks.length > 0 ? checks : [...AUDIT_CHECKS],
74
+ ...(options.level && { level: options.level }),
75
+ ...(options.limit !== undefined && { limit: options.limit }),
76
+ });
77
+ if (response.status === 'error' || !response.data) {
78
+ return {
79
+ success: false,
80
+ error: response.error ?? 'Failed to audit the page',
81
+ exitCode: response.exitCode ?? EXIT_CODES.CDP_CONNECTION_FAILURE,
82
+ ...(response.suggestion && { errorContext: { suggestion: response.suggestion } }),
83
+ };
84
+ }
85
+ return { success: true, data: response.data };
86
+ }
87
+ //# sourceMappingURL=audit.js.map
@@ -8,8 +8,9 @@
8
8
  import type { DomEvalCommandOptions } from '../shared/optionTypes.js';
9
9
  /**
10
10
  * Handle `bdg dom eval <script> [--frame <frame>]`. With `--frame`, the
11
- * frame the script ran in is a `Frame:` line on stderr (JSON: `frame`), so
12
- * stdout stays the bare value for pipes.
11
+ * frame the script ran in is a `Frame:` line on stderr (JSON: `frame`), and
12
+ * a warning about how the result was copied goes there too (JSON:
13
+ * `warning`), so stdout stays the bare value for pipes.
13
14
  */
14
15
  export declare function handleDomEval(script: string, options: DomEvalCommandOptions): Promise<void>;
15
16
  //# sourceMappingURL=eval.d.ts.map
@@ -10,12 +10,13 @@ import { runCommand } from '../shared/CommandRunner.js';
10
10
  import { emptyScriptError, withLoadingHint } from '../../errors/messages.js';
11
11
  import { domEval } from '../../ipc/client.js';
12
12
  import { formatDomEval } from '../../ui/formatters/dom.js';
13
- import { evalFrameLine } from '../../ui/messages/commands.js';
13
+ import { evalFrameLine, warningMessage } from '../../ui/messages/commands.js';
14
14
  import { EXIT_CODES } from '../../utils/exitCodes.js';
15
15
  /**
16
16
  * Handle `bdg dom eval <script> [--frame <frame>]`. With `--frame`, the
17
- * frame the script ran in is a `Frame:` line on stderr (JSON: `frame`), so
18
- * stdout stays the bare value for pipes.
17
+ * frame the script ran in is a `Frame:` line on stderr (JSON: `frame`), and
18
+ * a warning about how the result was copied goes there too (JSON:
19
+ * `warning`), so stdout stays the bare value for pipes.
19
20
  */
20
21
  export async function handleDomEval(script, options) {
21
22
  await runCommand(async () => {
@@ -38,7 +39,11 @@ export async function handleDomEval(script, options) {
38
39
  ...(suggestion && { errorContext: { suggestion } }),
39
40
  };
40
41
  }
41
- const { value, type, subtype, frame } = response.data;
42
+ const { value, type, subtype, frame, warning } = response.data;
43
+ const hint = [
44
+ ...(frame !== undefined ? [evalFrameLine(frame)] : []),
45
+ ...(warning ? [warningMessage(warning)] : []),
46
+ ].join('\n');
42
47
  return {
43
48
  success: true,
44
49
  data: {
@@ -46,8 +51,9 @@ export async function handleDomEval(script, options) {
46
51
  type,
47
52
  ...(subtype && { subtype }),
48
53
  ...(frame !== undefined && { frame }),
54
+ ...(warning && { warning }),
49
55
  },
50
- ...(frame !== undefined && !options.json && { hint: evalFrameLine(frame) }),
56
+ ...(hint && !options.json && { hint }),
51
57
  };
52
58
  }, options, formatDomEval);
53
59
  }
@@ -5,11 +5,12 @@
5
5
  * validation state, and suggested commands for agent consumption.
6
6
  */
7
7
  import { calculateSummary, orderForms, primaryButtonIndex } from './formSummary.js';
8
- import { resolveBackendNodeIds } from './helpers/index.js';
8
+ import { pageDocumentId, resolveBackendNodeIds } from './helpers/index.js';
9
9
  import { runCommand } from '../shared/CommandRunner.js';
10
10
  import { jsonOption } from '../shared/commonOptions.js';
11
11
  import { noFormsFoundError, formInIframeError } from '../../errors/messages.js';
12
12
  import { domFormDiscover } from '../../ipc/client.js';
13
+ import { MASKED_VALUE } from '../../runtime/dom/elementInfo.js';
13
14
  import { FORM_DISCOVERY_CACHE_SELECTOR, QueryCacheManager } from '../../session/QueryCacheManager.js';
14
15
  import { formatFormDiscovery } from '../../ui/formatters/form.js';
15
16
  import { createLogger } from '../../ui/logging/index.js';
@@ -84,16 +85,14 @@ function buildFieldState(raw) {
84
85
  return 'empty';
85
86
  }
86
87
  /**
87
- * Build masked value for password fields.
88
+ * The masked value of a sensitive field (the page script never sends its
89
+ * real value).
88
90
  *
89
91
  * @param raw - Raw field data
90
92
  * @returns Masked value string
91
93
  */
92
94
  function buildMaskedValue(raw) {
93
- if (raw.inputType === 'password' && typeof raw.value === 'string' && raw.value.length > 0) {
94
- return '•'.repeat(Math.min(raw.value.length, 8));
95
- }
96
- return undefined;
95
+ return raw.value === MASKED_VALUE ? MASKED_VALUE : undefined;
97
96
  }
98
97
  /**
99
98
  * Build interaction warning for non-native fields.
@@ -244,8 +243,9 @@ function transformForm(raw) {
244
243
  * the selector later.
245
244
  *
246
245
  * @param forms - Discovered forms
246
+ * @param document - Identity of the page document they were found in, read before discovery
247
247
  */
248
- async function cacheFormElements(forms) {
248
+ async function cacheFormElements(forms, document) {
249
249
  const elements = forms.flatMap((form) => [...form.fields, ...form.buttons]);
250
250
  const backendNodeIds = await resolveBackendNodeIds(elements.map((el) => el.selector)).catch((error) => {
251
251
  log.debug(`Form fields not cached by node: ${getErrorMessage(error)}`);
@@ -259,7 +259,7 @@ async function cacheFormElements(forms) {
259
259
  nodeId: backendNodeIds[i] ?? 0,
260
260
  selector: el.selector,
261
261
  })),
262
- });
262
+ }, document);
263
263
  log.debug(`Cached ${elements.length} form elements`);
264
264
  }
265
265
  /**
@@ -269,6 +269,7 @@ async function cacheFormElements(forms) {
269
269
  */
270
270
  async function handleFormCommand(options) {
271
271
  await runCommand(async () => {
272
+ const document = await pageDocumentId();
272
273
  const response = await domFormDiscover();
273
274
  if (response.status === 'error' || !response.data) {
274
275
  return {
@@ -311,7 +312,7 @@ async function handleFormCommand(options) {
311
312
  const allForms = orderForms(rawData.forms).map(transformForm);
312
313
  const forms = options.all ? allForms : [allForms[0]];
313
314
  // Cache ALL forms so global indices work with bdg dom fill/click
314
- await cacheFormElements(allForms);
315
+ await cacheFormElements(allForms, document);
315
316
  const result = {
316
317
  formCount: rawData.forms.length,
317
318
  selectedForm: 0,
@@ -10,14 +10,15 @@ import { InvalidArgumentError } from 'commander';
10
10
  import { runElementCommand } from './helpers/runElementCommand.js';
11
11
  import { runCommand } from '../shared/CommandRunner.js';
12
12
  import { jsonOption, SELECTOR_OR_INDEX_ARGUMENT } from '../shared/commonOptions.js';
13
+ import { createErrorResult } from '../shared/dataFetcher.js';
13
14
  import { integerOption } from '../shared/validation.js';
14
15
  import { CommandError } from '../../errors/index.js';
15
- import { VIA_LABEL_SUFFIX, conflictingOptionsMessage, indexSourceText, internalError, scrollOptionsError, } from '../../errors/messages.js';
16
- import { domClick, domFill, domPressKey, domScroll, domSubmit } from '../../ipc/client.js';
16
+ import { VIA_LABEL_SUFFIX, conflictingOptionsError, hoverOffWithTargetError, missingArgumentError, indexSourceText, internalError, scrollOptionsError, } from '../../errors/messages.js';
17
+ import { callCDP, domClick, domFill, domPressKey, domScroll, domSubmit } from '../../ipc/client.js';
17
18
  import { findUnknownModifiers } from '../../runtime/dom/keyMapping.js';
18
19
  import { formatTriggeredRequestLines, formatTriggeredRequestsTitle, } from '../../ui/formatters/triggeredRequests.js';
19
20
  import { OutputFormatter } from '../../ui/formatting.js';
20
- import { CLICK_RESULT_WAIT_HELP, POINTER_ACTION_DONE, POINTER_ACTION_NOUN, actionStatusLine, dialogConsoleText, newMessageText, pageNavigationText, shownElementText, stillChangingNote, } from '../../ui/messages/commands.js';
21
+ import { CLICK_RESULT_WAIT_HELP, HOVER_OFF_DONE, HOVER_USAGE, POINTER_ACTION_DONE, POINTER_ACTION_NOUN, pointerScrollText, actionStatusLine, dialogConsoleText, moreMessagesText, newMessageText, pageNavigationText, shownElementText, stillChangingNote, } from '../../ui/messages/commands.js';
21
22
  import { sessionCommand } from '../../ui/messages/sessionCommand.js';
22
23
  import { EXIT_CODES } from '../../utils/exitCodes.js';
23
24
  /** Help of `--strict` on click and hover */
@@ -96,13 +97,18 @@ export function registerFormInteractionCommands(program) {
96
97
  });
97
98
  domCommand
98
99
  .command('hover')
99
- .description('Move the mouse over an element (shows hover menus and tooltips)')
100
- .argument('<selectorOrIndex>', SELECTOR_OR_INDEX_ARGUMENT)
100
+ .description('Move the mouse over an element (shows hover menus and tooltips); --off moves it off the page')
101
+ .argument('[selectorOrIndex]', SELECTOR_OR_INDEX_ARGUMENT)
101
102
  .option('--index <n>', 'Element index if selector matches multiple (0-based)', integerOption(0))
103
+ .option('--off', 'Move the mouse off the page instead (closes menus that open on hover)')
102
104
  .option('--strict', STRICT_OPTION_HELP)
103
105
  .option('--no-wait', 'Skip waiting for network stability after hovering')
104
106
  .addOption(jsonOption())
105
107
  .action(async (selectorOrIndex, options) => {
108
+ if (options.off || selectorOrIndex === undefined) {
109
+ await hoverOff(selectorOrIndex, options);
110
+ return;
111
+ }
106
112
  await runPointerCommand(selectorOrIndex, options, 'hover');
107
113
  });
108
114
  domCommand
@@ -260,13 +266,32 @@ function scrollOptionsProblem(selector, options) {
260
266
  * @param options - Command options
261
267
  * @param action - Pointer action
262
268
  */
269
+ /**
270
+ * `bdg dom hover --off`: move the mouse off the page, so `mouseleave` and
271
+ * `:hover` end and menus that open on hover close.
272
+ *
273
+ * @param selectorOrIndex - Must be absent with --off
274
+ * @param options - Command options
275
+ */
276
+ async function hoverOff(selectorOrIndex, options) {
277
+ await runCommand(async () => {
278
+ if (!options.off || selectorOrIndex !== undefined || options.index !== undefined) {
279
+ const err = options.off ? hoverOffWithTargetError() : missingArgumentError(HOVER_USAGE);
280
+ return {
281
+ success: false,
282
+ error: err.message,
283
+ exitCode: EXIT_CODES.INVALID_ARGUMENTS,
284
+ errorContext: { suggestion: err.suggestion },
285
+ };
286
+ }
287
+ await callCDP('Input.dispatchMouseEvent', { type: 'mouseMoved', x: -1, y: -1 });
288
+ return { success: true, data: { pointer: 'off' } };
289
+ }, options, () => HOVER_OFF_DONE);
290
+ }
263
291
  async function runPointerCommand(selectorOrIndex, options, action) {
292
+ const conflict = conflictingOptionsError('--double', '--right');
264
293
  await runCommand(() => options.double && options.right
265
- ? Promise.resolve({
266
- success: false,
267
- error: conflictingOptionsMessage('--double', '--right'),
268
- exitCode: EXIT_CODES.INVALID_ARGUMENTS,
269
- })
294
+ ? Promise.resolve(createErrorResult(conflict.message, EXIT_CODES.INVALID_ARGUMENTS, conflict.suggestion))
270
295
  : runElementCommand({
271
296
  selectorOrIndex,
272
297
  index: options.index,
@@ -316,7 +341,10 @@ function formatActionOutput(done, details, result, options = {}) {
316
341
  fmt.keyValueList(details, keyWidth);
317
342
  if (result.navigation)
318
343
  fmt.keyValue('Page', pageNavigationText(result.navigation), keyWidth);
319
- listRows(fmt, 'New text', (result.messages ?? []).map(newMessageText), keyWidth);
344
+ listRows(fmt, 'New text', [
345
+ ...(result.messages ?? []).map(newMessageText),
346
+ ...(result.moreMessages ? [moreMessagesText(result.moreMessages)] : []),
347
+ ], keyWidth);
320
348
  listRows(fmt, 'Shown', (result.shown ?? []).map(shownElementText), keyWidth);
321
349
  const omitted = result.triggeredRequestsOmitted;
322
350
  const requests = formatTriggeredRequestLines(result.triggeredRequests ?? [], omitted);
@@ -400,6 +428,9 @@ function formatClickOutput(result) {
400
428
  ...selectorRows(result),
401
429
  elementRow(result),
402
430
  ['Method', result.method === 'dom' ? 'DOM events' : 'mouse events'],
431
+ ...(result.scrolledBy
432
+ ? [['Scrolled', pointerScrollText(result.scrolledBy)]]
433
+ : []),
403
434
  ], result, { action: POINTER_ACTION_NOUN[result.action ?? 'click'] }).build();
404
435
  }
405
436
  /**