browser-debugger-cli 0.8.0 → 0.9.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 (251) hide show
  1. package/README.md +4 -1
  2. package/dist/cdp/schema.d.ts +4 -1
  3. package/dist/cdp/schema.js +48 -7
  4. package/dist/commands/cdp.js +3 -2
  5. package/dist/commands/cleanup.d.ts +11 -0
  6. package/dist/commands/cleanup.js +161 -57
  7. package/dist/commands/console.d.ts +20 -1
  8. package/dist/commands/console.js +57 -17
  9. package/dist/commands/details.js +3 -2
  10. package/dist/commands/dom/DomElementResolver.d.ts +10 -3
  11. package/dist/commands/dom/DomElementResolver.js +35 -17
  12. package/dist/commands/dom/a11y.d.ts +10 -0
  13. package/dist/commands/dom/a11y.js +27 -5
  14. package/dist/commands/dom/eval.d.ts +3 -1
  15. package/dist/commands/dom/eval.js +29 -4
  16. package/dist/commands/dom/form.js +16 -62
  17. package/dist/commands/dom/formInteraction.js +152 -113
  18. package/dist/commands/dom/formSummary.d.ts +49 -0
  19. package/dist/commands/dom/formSummary.js +180 -0
  20. package/dist/commands/dom/frames.d.ts +2 -1
  21. package/dist/commands/dom/frames.js +17 -2
  22. package/dist/commands/dom/get.d.ts +6 -5
  23. package/dist/commands/dom/get.js +92 -82
  24. package/dist/commands/dom/helpers/index.d.ts +1 -1
  25. package/dist/commands/dom/helpers/index.js +1 -1
  26. package/dist/commands/dom/helpers/query.d.ts +44 -17
  27. package/dist/commands/dom/helpers/query.js +244 -97
  28. package/dist/commands/dom/helpers/runElementCommand.d.ts +10 -2
  29. package/dist/commands/dom/helpers/runElementCommand.js +97 -30
  30. package/dist/commands/dom/helpers/screenshot.d.ts +4 -1
  31. package/dist/commands/dom/helpers/screenshot.js +164 -49
  32. package/dist/commands/dom/index.d.ts +3 -1
  33. package/dist/commands/dom/index.js +16 -6
  34. package/dist/commands/dom/layout.d.ts +14 -0
  35. package/dist/commands/dom/layout.js +54 -0
  36. package/dist/commands/dom/listeners.d.ts +5 -1
  37. package/dist/commands/dom/listeners.js +13 -3
  38. package/dist/commands/dom/query.js +2 -3
  39. package/dist/commands/dom/screenshot.d.ts +12 -2
  40. package/dist/commands/dom/screenshot.js +27 -3
  41. package/dist/commands/dom/semanticUtils.d.ts +6 -13
  42. package/dist/commands/dom/semanticUtils.js +15 -19
  43. package/dist/commands/dom/wait.d.ts +13 -0
  44. package/dist/commands/dom/wait.js +83 -0
  45. package/dist/commands/helpJson.js +2 -2
  46. package/dist/commands/network/list.js +4 -11
  47. package/dist/commands/optionBehaviors.js +112 -21
  48. package/dist/commands/page.d.ts +2 -1
  49. package/dist/commands/page.js +41 -5
  50. package/dist/commands/peek.js +4 -11
  51. package/dist/commands/sessions.d.ts +8 -0
  52. package/dist/commands/sessions.js +19 -0
  53. package/dist/commands/shared/CommandRunner.js +4 -4
  54. package/dist/commands/shared/dataFetcher.js +2 -2
  55. package/dist/commands/shared/followMode.d.ts +21 -1
  56. package/dist/commands/shared/followMode.js +29 -2
  57. package/dist/commands/shared/handleValidationError.d.ts +2 -2
  58. package/dist/commands/shared/handleValidationError.js +12 -3
  59. package/dist/commands/shared/optionTypes.d.ts +40 -5
  60. package/dist/commands/shared/startHelpers.js +12 -3
  61. package/dist/commands/shared/validation.d.ts +3 -2
  62. package/dist/commands/shared/validation.js +4 -3
  63. package/dist/commands/start.d.ts +63 -0
  64. package/dist/commands/start.js +115 -15
  65. package/dist/commands/status.js +29 -7
  66. package/dist/commands/stop.js +7 -6
  67. package/dist/commands/tail.js +4 -11
  68. package/dist/commands/types.d.ts +2 -0
  69. package/dist/commands.js +2 -0
  70. package/dist/connection/chromeIdentity.d.ts +65 -0
  71. package/dist/connection/chromeIdentity.js +143 -0
  72. package/dist/connection/launcher/profilePreferences.d.ts +47 -0
  73. package/dist/connection/launcher/profilePreferences.js +151 -0
  74. package/dist/connection/launcher.d.ts +21 -2
  75. package/dist/connection/launcher.js +42 -16
  76. package/dist/connection/portReservation.d.ts +14 -4
  77. package/dist/connection/portReservation.js +21 -6
  78. package/dist/connection/startupExit.d.ts +8 -0
  79. package/dist/connection/startupExit.js +15 -6
  80. package/dist/constants.d.ts +6 -2
  81. package/dist/constants.js +9 -2
  82. package/dist/daemon/SessionController.js +23 -7
  83. package/dist/daemon/errors.d.ts +1 -1
  84. package/dist/daemon/errors.js +1 -1
  85. package/dist/daemon/launcher.d.ts +2 -1
  86. package/dist/daemon/launcher.js +5 -6
  87. package/dist/daemon/server/SocketServer.js +1 -2
  88. package/dist/daemon/session/Session.d.ts +13 -0
  89. package/dist/daemon/session/Session.js +57 -8
  90. package/dist/daemon/session/chromeConnection.d.ts +9 -0
  91. package/dist/daemon/session/chromeConnection.js +45 -8
  92. package/dist/daemon/session/commandRegistry.js +52 -62
  93. package/dist/daemon/session/interactions.d.ts +35 -9
  94. package/dist/daemon/session/interactions.js +36 -9
  95. package/dist/daemon/session/triggeredRequests.d.ts +67 -0
  96. package/dist/daemon/session/triggeredRequests.js +157 -0
  97. package/dist/daemon/session/types.d.ts +5 -1
  98. package/dist/daemon.js +5393 -1600
  99. package/dist/errors/messages.d.ts +387 -24
  100. package/dist/errors/messages.js +761 -67
  101. package/dist/index.js +3976 -1558
  102. package/dist/ipc/client.d.ts +12 -1
  103. package/dist/ipc/client.js +22 -3
  104. package/dist/ipc/protocol/commands.d.ts +89 -4
  105. package/dist/ipc/protocol/commands.js +2 -0
  106. package/dist/ipc/protocol/domTypes.d.ts +258 -7
  107. package/dist/ipc/session/lifecycle.d.ts +8 -1
  108. package/dist/ipc/session/queries.d.ts +5 -1
  109. package/dist/ipc/session/types.d.ts +5 -0
  110. package/dist/ipc/transport/index.d.ts +2 -1
  111. package/dist/ipc/transport/index.js +2 -2
  112. package/dist/runtime/dom/actionEffects.d.ts +106 -0
  113. package/dist/runtime/dom/actionEffects.js +256 -0
  114. package/dist/runtime/dom/actionEffectsScripts.d.ts +52 -0
  115. package/dist/runtime/dom/actionEffectsScripts.js +234 -0
  116. package/dist/runtime/dom/elementGeometry.d.ts +170 -0
  117. package/dist/runtime/dom/elementGeometry.js +553 -0
  118. package/dist/runtime/dom/elementInfo.d.ts +77 -0
  119. package/dist/runtime/dom/elementInfo.js +191 -0
  120. package/dist/runtime/dom/evalHelpers.d.ts +51 -6
  121. package/dist/runtime/dom/evalHelpers.js +136 -26
  122. package/dist/runtime/dom/eventListeners.d.ts +2 -1
  123. package/dist/runtime/dom/eventListeners.js +174 -47
  124. package/dist/runtime/dom/formDiscovery.d.ts +1 -1
  125. package/dist/runtime/dom/formDiscovery.js +116 -16
  126. package/dist/runtime/dom/formFillHelpers/fill.d.ts +10 -0
  127. package/dist/runtime/dom/formFillHelpers/fill.js +125 -10
  128. package/dist/runtime/dom/formFillHelpers/index.d.ts +2 -2
  129. package/dist/runtime/dom/formFillHelpers/index.js +2 -2
  130. package/dist/runtime/dom/formFillHelpers/pressKey.js +14 -3
  131. package/dist/runtime/dom/formFillHelpers/scroll.d.ts +3 -0
  132. package/dist/runtime/dom/formFillHelpers/scroll.js +60 -18
  133. package/dist/runtime/dom/formFillHelpers/shared.d.ts +17 -0
  134. package/dist/runtime/dom/formFillHelpers/shared.js +25 -1
  135. package/dist/runtime/dom/formFillHelpers/stability.d.ts +20 -6
  136. package/dist/runtime/dom/formFillHelpers/stability.js +50 -19
  137. package/dist/runtime/dom/formSubmitHelpers.d.ts +3 -0
  138. package/dist/runtime/dom/formSubmitHelpers.js +89 -15
  139. package/dist/runtime/dom/frameLayout.d.ts +60 -0
  140. package/dist/runtime/dom/frameLayout.js +140 -0
  141. package/dist/runtime/dom/frameOrigin.d.ts +50 -0
  142. package/dist/runtime/dom/frameOrigin.js +62 -0
  143. package/dist/runtime/dom/frameScopedConnection.d.ts +92 -0
  144. package/dist/runtime/dom/frameScopedConnection.js +252 -0
  145. package/dist/runtime/dom/frameSelection.d.ts +1 -1
  146. package/dist/runtime/dom/frameSelection.js +2 -2
  147. package/dist/runtime/dom/frames.d.ts +25 -2
  148. package/dist/runtime/dom/frames.js +202 -63
  149. package/dist/runtime/dom/layout.d.ts +67 -0
  150. package/dist/runtime/dom/layout.js +333 -0
  151. package/dist/runtime/dom/listenerPageScripts.d.ts +66 -0
  152. package/dist/runtime/dom/listenerPageScripts.js +279 -0
  153. package/dist/runtime/dom/listenerSummary.d.ts +132 -11
  154. package/dist/runtime/dom/listenerSummary.js +344 -22
  155. package/dist/runtime/dom/pageActivity.d.ts +41 -0
  156. package/dist/runtime/dom/pageActivity.js +123 -0
  157. package/dist/runtime/dom/reactEventHelpers.d.ts +58 -2
  158. package/dist/runtime/dom/reactEventHelpers.js +212 -41
  159. package/dist/runtime/dom/targetNode.d.ts +80 -27
  160. package/dist/runtime/dom/targetNode.js +249 -33
  161. package/dist/runtime/dom/wait.d.ts +25 -0
  162. package/dist/runtime/dom/wait.js +199 -0
  163. package/dist/runtime/dom/waitCondition.d.ts +71 -0
  164. package/dist/runtime/dom/waitCondition.js +75 -0
  165. package/dist/runtime/page/emulation.d.ts +51 -0
  166. package/dist/runtime/page/emulation.js +80 -0
  167. package/dist/runtime/page/loadingState.d.ts +36 -0
  168. package/dist/runtime/page/loadingState.js +86 -0
  169. package/dist/runtime/page/navigation.d.ts +46 -2
  170. package/dist/runtime/page/navigation.js +69 -33
  171. package/dist/session/QueryCacheManager.d.ts +11 -1
  172. package/dist/session/QueryCacheManager.js +25 -3
  173. package/dist/session/chromeOwners.d.ts +34 -0
  174. package/dist/session/chromeOwners.js +51 -0
  175. package/dist/session/cleanup/staleSession.d.ts +11 -1
  176. package/dist/session/cleanup/staleSession.js +17 -6
  177. package/dist/session/cleanup/userCommands.js +2 -4
  178. package/dist/session/metadata.d.ts +5 -1
  179. package/dist/session/metadata.js +2 -1
  180. package/dist/session/paths.d.ts +77 -3
  181. package/dist/session/paths.js +111 -5
  182. package/dist/session/port.d.ts +31 -7
  183. package/dist/session/port.js +50 -43
  184. package/dist/session/portClaims.d.ts +66 -0
  185. package/dist/session/portClaims.js +284 -0
  186. package/dist/session/sessionList.d.ts +58 -0
  187. package/dist/session/sessionList.js +199 -0
  188. package/dist/session/sessionName.d.ts +46 -0
  189. package/dist/session/sessionName.js +97 -0
  190. package/dist/telemetry/a11y.d.ts +8 -3
  191. package/dist/telemetry/a11y.js +92 -28
  192. package/dist/telemetry/requestKinds.d.ts +32 -0
  193. package/dist/telemetry/requestKinds.js +61 -0
  194. package/dist/telemetry/requestState.d.ts +31 -0
  195. package/dist/telemetry/requestState.js +38 -0
  196. package/dist/types.d.ts +80 -3
  197. package/dist/ui/formatters/a11y.js +3 -0
  198. package/dist/ui/formatters/console/chronological.d.ts +8 -0
  199. package/dist/ui/formatters/console/chronological.js +17 -4
  200. package/dist/ui/formatters/console/json.js +3 -4
  201. package/dist/ui/formatters/console/shared.d.ts +12 -0
  202. package/dist/ui/formatters/console.d.ts +2 -2
  203. package/dist/ui/formatters/console.js +1 -1
  204. package/dist/ui/formatters/details.js +2 -1
  205. package/dist/ui/formatters/dom.d.ts +26 -14
  206. package/dist/ui/formatters/dom.js +65 -52
  207. package/dist/ui/formatters/form.js +29 -18
  208. package/dist/ui/formatters/layout.d.ts +31 -0
  209. package/dist/ui/formatters/layout.js +53 -0
  210. package/dist/ui/formatters/listeners.d.ts +3 -2
  211. package/dist/ui/formatters/listeners.js +73 -9
  212. package/dist/ui/formatters/networkHeaders.js +13 -0
  213. package/dist/ui/formatters/preview.js +2 -1
  214. package/dist/ui/formatters/requestStatus.d.ts +1 -17
  215. package/dist/ui/formatters/requestStatus.js +2 -30
  216. package/dist/ui/formatters/sessions.d.ts +12 -0
  217. package/dist/ui/formatters/sessions.js +40 -0
  218. package/dist/ui/formatters/status.d.ts +21 -2
  219. package/dist/ui/formatters/status.js +47 -10
  220. package/dist/ui/formatters/triggeredRequests.d.ts +36 -0
  221. package/dist/ui/formatters/triggeredRequests.js +65 -0
  222. package/dist/ui/formatting.d.ts +10 -0
  223. package/dist/ui/formatting.js +28 -36
  224. package/dist/ui/messages/chrome.d.ts +9 -0
  225. package/dist/ui/messages/chrome.js +17 -5
  226. package/dist/ui/messages/commands.d.ts +388 -14
  227. package/dist/ui/messages/commands.js +664 -21
  228. package/dist/ui/messages/consoleMessages.d.ts +10 -0
  229. package/dist/ui/messages/consoleMessages.js +17 -0
  230. package/dist/ui/messages/hints.js +2 -1
  231. package/dist/ui/messages/preview.js +5 -4
  232. package/dist/ui/messages/session.d.ts +16 -21
  233. package/dist/ui/messages/session.js +28 -26
  234. package/dist/ui/messages/sessionCommand.d.ts +43 -0
  235. package/dist/ui/messages/sessionCommand.js +52 -0
  236. package/dist/utils/async.d.ts +8 -0
  237. package/dist/utils/async.js +19 -0
  238. package/dist/utils/http.d.ts +22 -1
  239. package/dist/utils/http.js +28 -9
  240. package/dist/utils/selectorFilters.d.ts +36 -8
  241. package/dist/utils/selectorFilters.js +267 -53
  242. package/dist/utils/shellDetection.d.ts +8 -2
  243. package/dist/utils/shellDetection.js +120 -33
  244. package/dist/utils/suggestions.d.ts +26 -0
  245. package/dist/utils/suggestions.js +73 -0
  246. package/dist/utils/taskMappings.js +10 -0
  247. package/dist/utils/url.d.ts +12 -2
  248. package/dist/utils/url.js +69 -7
  249. package/package.json +1 -1
  250. package/dist/ui/formatters/sessionFormatters.d.ts +0 -58
  251. package/dist/ui/formatters/sessionFormatters.js +0 -121
package/README.md CHANGED
@@ -58,12 +58,15 @@ bdg page navigate example.com/about
58
58
  bdg eval "document.title" # Run JavaScript in the page (--frame for iframes)
59
59
  bdg network list --preset errors # Network requests, console: bdg console
60
60
  bdg dom listeners "#save" # Which event listeners run for an element
61
+ bdg dom layout "#save" # Where it is, whether it is visible or covered
62
+ bdg dom wait "#result" --visible # Wait for an element instead of sleeping
63
+ bdg example.com --session agent2 --viewport 1280x800 # A second, independent session
61
64
  bdg stop # End session
62
65
  ```
63
66
 
64
67
  ## Current State
65
68
 
66
- **Raw CDP access is complete.** Every protocol method works now. High-level commands cover the common work: page navigation, DOM queries and interaction (click, fill, hover, keys, forms, shadow DOM and iframes), accessibility tree, screenshots, network requests and HAR export, console messages and event listeners. See the [CLI reference](docs/CLI_REFERENCE.md) for every command, and `bdg --help --json` for the machine-readable version.
69
+ **Raw CDP access is complete.** Every protocol method works now. High-level commands cover the common work: page navigation, DOM queries and interaction (click, fill, hover, keys, forms, shadow DOM and iframes), accessibility tree, screenshots, network requests and HAR export, console messages and event listeners. Actions report what they changed (navigation, new messages, requests, or no visible effect), and several named sessions can run side by side. See the [CLI reference](docs/CLI_REFERENCE.md) for every command, and `bdg --help --json` for the machine-readable version.
67
70
 
68
71
  ## Agent Discovery Pattern
69
72
 
@@ -154,7 +154,9 @@ export declare function getProtocolCounts(): {
154
154
  /**
155
155
  * Search methods by keyword (case-insensitive).
156
156
  *
157
- * Searches in method names and descriptions.
157
+ * Searches method names, extra keywords ({@link SEARCH_KEYWORDS}) and
158
+ * descriptions. Matches by name or keyword come first, then those found only
159
+ * in the description, each in protocol order.
158
160
  *
159
161
  * @param query - Search query
160
162
  * @returns Array of matching method schemas
@@ -163,6 +165,7 @@ export declare function getProtocolCounts(): {
163
165
  * ```typescript
164
166
  * const cookies = searchMethods('cookie');
165
167
  * // Returns: Network.getCookies, Network.setCookie, Network.deleteCookies, etc.
168
+ * searchMethods('viewport')[0]?.name; // 'Emulation.setDeviceMetricsOverride'
166
169
  * ```
167
170
  */
168
171
  export declare function searchMethods(query: string): MethodSchema[];
@@ -233,10 +233,45 @@ export function getProtocolCounts() {
233
233
  methodsIn: (domain) => summaries.find((d) => d.name === domain)?.commandCount ?? 0,
234
234
  };
235
235
  }
236
+ /**
237
+ * Words agents search for that a method's name and description lack, e.g.
238
+ * "viewport" for `Emulation.setDeviceMetricsOverride`.
239
+ */
240
+ const SEARCH_KEYWORDS = {
241
+ 'Emulation.setDeviceMetricsOverride': [
242
+ 'viewport',
243
+ 'window size',
244
+ 'screen size',
245
+ 'resize',
246
+ 'responsive',
247
+ 'mobile',
248
+ ],
249
+ 'Emulation.setEmulatedMedia': [
250
+ 'color scheme',
251
+ 'dark mode',
252
+ 'light mode',
253
+ 'prefers-color-scheme',
254
+ 'reduced motion',
255
+ 'print',
256
+ ],
257
+ 'Browser.setWindowBounds': ['window size', 'resize'],
258
+ };
259
+ /**
260
+ * Lower-case text without spaces, hyphens and underscores, so "user agent"
261
+ * finds `setUserAgentOverride`.
262
+ *
263
+ * @param text - Text
264
+ * @returns Normalized text
265
+ */
266
+ function searchable(text) {
267
+ return text.toLowerCase().replace(/[\s_-]+/g, '');
268
+ }
236
269
  /**
237
270
  * Search methods by keyword (case-insensitive).
238
271
  *
239
- * Searches in method names and descriptions.
272
+ * Searches method names, extra keywords ({@link SEARCH_KEYWORDS}) and
273
+ * descriptions. Matches by name or keyword come first, then those found only
274
+ * in the description, each in protocol order.
240
275
  *
241
276
  * @param query - Search query
242
277
  * @returns Array of matching method schemas
@@ -245,23 +280,29 @@ export function getProtocolCounts() {
245
280
  * ```typescript
246
281
  * const cookies = searchMethods('cookie');
247
282
  * // Returns: Network.getCookies, Network.setCookie, Network.deleteCookies, etc.
283
+ * searchMethods('viewport')[0]?.name; // 'Emulation.setDeviceMetricsOverride'
248
284
  * ```
249
285
  */
250
286
  export function searchMethods(query) {
251
287
  const protocol = loadProtocol();
252
- const results = [];
288
+ const named = [];
289
+ const described = [];
253
290
  const lowerQuery = query.toLowerCase();
291
+ const needle = searchable(query);
254
292
  protocol.domains.forEach((domain) => {
255
293
  if (!domain.commands)
256
294
  return;
257
295
  domain.commands.forEach((command) => {
258
- const nameMatch = command.name.toLowerCase().includes(lowerQuery);
296
+ const keywords = SEARCH_KEYWORDS[`${domain.domain}.${command.name}`] ?? [];
297
+ const nameMatch = searchable(command.name).includes(needle) ||
298
+ keywords.some((keyword) => searchable(keyword).includes(needle));
259
299
  const descMatch = command.description?.toLowerCase().includes(lowerQuery) ?? false;
260
- if (nameMatch || descMatch) {
261
- results.push(buildMethodSchema(domain.domain, command));
262
- }
300
+ if (nameMatch)
301
+ named.push(buildMethodSchema(domain.domain, command));
302
+ else if (descMatch)
303
+ described.push(buildMethodSchema(domain.domain, command));
263
304
  });
264
305
  });
265
- return results;
306
+ return [...named, ...described];
266
307
  }
267
308
  //# sourceMappingURL=schema.js.map
@@ -6,6 +6,7 @@ import { CommandError } from '../errors/index.js';
6
6
  import { callCDP } from '../ipc/client.js';
7
7
  import { validateIPCResponse } from '../ipc/index.js';
8
8
  import { formatHint } from '../ui/messages/hints.js';
9
+ import { sessionCommand } from '../ui/messages/sessionCommand.js';
9
10
  import { getErrorMessage } from '../utils/errors.js';
10
11
  import { EXIT_CODES } from '../utils/exitCodes.js';
11
12
  import { findSimilar } from '../utils/suggestions.js';
@@ -166,7 +167,7 @@ function domainSuggestion(domainName) {
166
167
  */
167
168
  function blockedAlternative(methodName) {
168
169
  const blocked = BLOCKED_CDP_METHODS[methodName];
169
- return blocked && `${blocked.alternative} (raw ${methodName} is blocked)`;
170
+ return blocked && `${sessionCommand(blocked.alternative)} (raw ${methodName} is blocked)`;
170
171
  }
171
172
  /**
172
173
  * Handle search mode: Find methods by keyword.
@@ -403,7 +404,7 @@ async function handleExecuteMethod(methodName, paramsJson) {
403
404
  success: false,
404
405
  error: `${normalized} is blocked via raw CDP: ${blocked.reason}`,
405
406
  exitCode: EXIT_CODES.INVALID_ARGUMENTS,
406
- errorContext: { suggestion: `Use: ${blocked.alternative}` },
407
+ errorContext: { suggestion: `Use: ${sessionCommand(blocked.alternative)}` },
407
408
  };
408
409
  }
409
410
  if (!normalized) {
@@ -1,4 +1,15 @@
1
1
  import type { Command } from 'commander';
2
+ import { type ErrorWithSuggestion } from '../errors/messages.js';
3
+ /**
4
+ * Why the session directory must not be deleted after cleanup: its daemon
5
+ * still runs, cleanup reported problems, or its Chrome has not exited.
6
+ *
7
+ * @param chromePid - The session's Chrome as found before cleanup, or null
8
+ * @param warnings - Warnings from cleanup
9
+ * @param exitWaitMs - How long to wait for that Chrome to exit
10
+ * @returns Error and suggestion, or null when the directory may be deleted
11
+ */
12
+ export declare function purgeBlocker(chromePid: number | null, warnings: string[], exitWaitMs?: number): Promise<ErrorWithSuggestion | null>;
2
13
  /**
3
14
  * Register cleanup command
4
15
  *
@@ -1,14 +1,17 @@
1
1
  import * as fs from 'fs';
2
2
  import { runCommand } from './shared/CommandRunner.js';
3
3
  import { jsonOption } from './shared/commonOptions.js';
4
- import { sessionDirIsFileError } from '../errors/messages.js';
4
+ import { purgeNeedsNamedSessionError, purgeRefusedError, sessionDirIsFileError, } from '../errors/messages.js';
5
+ import { isSessionChrome } from '../session/cleanup/staleSession.js';
5
6
  import { performSessionCleanup } from '../session/cleanup/userCommands.js';
6
7
  import { isDaemonAlive } from '../session/daemonSocket.js';
7
- import { getSessionDir } from '../session/paths.js';
8
- import { readDaemonPid } from '../session/pid.js';
8
+ import { getSessionDir, getSessionFilePath, getSessionName } from '../session/paths.js';
9
+ import { readDaemonPid, readPidFromFile } from '../session/pid.js';
9
10
  import { joinLines } from '../ui/formatting.js';
10
- import { sessionFilesCleanedMessage, sessionOutputRemovedMessage, sessionDirectoryCleanMessage, noSessionFilesMessage, sessionStillActiveError, warningMessage, } from '../ui/messages/commands.js';
11
+ import { sessionFilesCleanedMessage, sessionOutputRemovedMessage, sessionDirectoryCleanMessage, sessionDirectoryPurgedMessage, noSessionFilesMessage, sessionStillActiveError, sessionStillActiveSuggestion, warningMessage, } from '../ui/messages/commands.js';
12
+ import { delay } from '../utils/async.js';
11
13
  import { EXIT_CODES } from '../utils/exitCodes.js';
14
+ import { isProcessAlive } from '../utils/process.js';
12
15
  /**
13
16
  * Format cleanup result for human-readable output.
14
17
  *
@@ -18,6 +21,158 @@ function formatCleanup(data) {
18
21
  const { cleaned } = data;
19
22
  return joinLines(cleaned.session && sessionFilesCleanedMessage(), cleaned.output && sessionOutputRemovedMessage(), ...(data.warnings ?? []).map((warning) => warningMessage(warning)), '', data.message);
20
23
  }
24
+ /**
25
+ * Delete the selected named session's directory (Chrome profile, logs, port).
26
+ *
27
+ * @returns The deleted directory, or undefined if there was none
28
+ */
29
+ function purgeSessionDir() {
30
+ const dir = getSessionDir();
31
+ if (!fs.existsSync(dir))
32
+ return undefined;
33
+ fs.rmSync(dir, { recursive: true, force: true, maxRetries: 3 });
34
+ return dir;
35
+ }
36
+ /** How long `--purge` waits for a killed Chrome to exit */
37
+ const PURGE_CHROME_EXIT_WAIT_MS = 5000;
38
+ /**
39
+ * The running Chrome bdg launched for the selected session (from chrome.pid,
40
+ * verified by its marker flag).
41
+ *
42
+ * @returns Chrome PID, or null
43
+ */
44
+ function liveSessionChromePid() {
45
+ const pid = readPidFromFile(getSessionFilePath('CHROME_PID'));
46
+ if (pid === null || !isProcessAlive(pid))
47
+ return null;
48
+ return isSessionChrome(pid, getSessionDir()) ? pid : null;
49
+ }
50
+ /**
51
+ * Wait until a process has exited.
52
+ *
53
+ * @param pid - Process ID
54
+ * @param timeoutMs - Longest wait
55
+ * @returns True if it exited
56
+ */
57
+ async function waitForExit(pid, timeoutMs) {
58
+ const deadline = Date.now() + timeoutMs;
59
+ while (isProcessAlive(pid) && Date.now() < deadline)
60
+ await delay(50);
61
+ return !isProcessAlive(pid);
62
+ }
63
+ /**
64
+ * Why the session directory must not be deleted after cleanup: its daemon
65
+ * still runs, cleanup reported problems, or its Chrome has not exited.
66
+ *
67
+ * @param chromePid - The session's Chrome as found before cleanup, or null
68
+ * @param warnings - Warnings from cleanup
69
+ * @param exitWaitMs - How long to wait for that Chrome to exit
70
+ * @returns Error and suggestion, or null when the directory may be deleted
71
+ */
72
+ export async function purgeBlocker(chromePid, warnings, exitWaitMs = PURGE_CHROME_EXIT_WAIT_MS) {
73
+ const dir = getSessionDir();
74
+ if (await isDaemonAlive())
75
+ return purgeRefusedError(dir, 'its daemon is still running');
76
+ if (warnings.length > 0)
77
+ return purgeRefusedError(dir, warnings.join('; '));
78
+ if (chromePid !== null && !(await waitForExit(chromePid, exitWaitMs))) {
79
+ return purgeRefusedError(dir, `its Chrome (PID ${chromePid}) is still running`);
80
+ }
81
+ return null;
82
+ }
83
+ /**
84
+ * Delete the session directory unless {@link purgeBlocker} objects.
85
+ *
86
+ * @param chromePid - The session's Chrome as found before cleanup, or null
87
+ * @param warnings - Warnings from cleanup
88
+ * @returns The deleted directory (undefined if there was none), or the refusal
89
+ */
90
+ async function purge(chromePid, warnings) {
91
+ const refusal = await purgeBlocker(chromePid, warnings);
92
+ if (refusal)
93
+ return { refusal };
94
+ const purged = purgeSessionDir();
95
+ return purged === undefined ? {} : { purged };
96
+ }
97
+ /**
98
+ * Why cleanup cannot run: the session directory is a file, `--purge` lacks a
99
+ * named session, or the session is still running (without `--force`).
100
+ *
101
+ * @param opts - Cleanup options
102
+ * @returns Error result, or null when cleanup may run
103
+ */
104
+ async function cleanupBlocker(opts) {
105
+ const dir = getSessionDir();
106
+ if (fs.existsSync(dir) && !fs.statSync(dir).isDirectory()) {
107
+ const err = sessionDirIsFileError(dir);
108
+ return {
109
+ success: false,
110
+ error: err.message,
111
+ exitCode: EXIT_CODES.SESSION_FILE_ERROR,
112
+ errorContext: { suggestion: err.suggestion },
113
+ };
114
+ }
115
+ if (opts.purge && getSessionName() === null) {
116
+ const err = purgeNeedsNamedSessionError();
117
+ return {
118
+ success: false,
119
+ error: err.message,
120
+ exitCode: EXIT_CODES.INVALID_ARGUMENTS,
121
+ errorContext: { suggestion: err.suggestion },
122
+ };
123
+ }
124
+ if (!opts.force && !opts.aggressive && (await isDaemonAlive())) {
125
+ return {
126
+ success: false,
127
+ error: sessionStillActiveError(readDaemonPid() ?? 0),
128
+ exitCode: EXIT_CODES.RESOURCE_BUSY,
129
+ errorContext: {
130
+ suggestion: sessionStillActiveSuggestion(getSessionName()),
131
+ warning: 'Force cleanup kills the running daemon and its Chrome',
132
+ },
133
+ };
134
+ }
135
+ return null;
136
+ }
137
+ /**
138
+ * Clean up the selected session (and delete its directory with `--purge`).
139
+ *
140
+ * @param opts - Cleanup options
141
+ * @returns Command result
142
+ */
143
+ async function cleanupSession(opts) {
144
+ const blocker = await cleanupBlocker(opts);
145
+ if (blocker)
146
+ return blocker;
147
+ const chromePid = opts.purge ? liveSessionChromePid() : null;
148
+ const { cleaned, warnings } = await performSessionCleanup({
149
+ force: Boolean(opts.force) || Boolean(opts.aggressive),
150
+ removeOutput: opts.removeOutput,
151
+ });
152
+ const { purged, refusal } = opts.purge ? await purge(chromePid, warnings) : {};
153
+ if (refusal) {
154
+ return {
155
+ success: false,
156
+ error: refusal.message,
157
+ exitCode: EXIT_CODES.RESOURCE_CONFLICT,
158
+ errorContext: { suggestion: refusal.suggestion },
159
+ };
160
+ }
161
+ const didCleanup = Object.values(cleaned).some(Boolean) || purged !== undefined;
162
+ return {
163
+ success: true,
164
+ data: {
165
+ cleaned,
166
+ ...(purged !== undefined && { purged }),
167
+ message: !didCleanup
168
+ ? noSessionFilesMessage()
169
+ : purged !== undefined
170
+ ? sessionDirectoryPurgedMessage(purged)
171
+ : sessionDirectoryCleanMessage(),
172
+ ...(warnings.length > 0 && { warnings }),
173
+ },
174
+ };
175
+ }
21
176
  /**
22
177
  * Register cleanup command
23
178
  *
@@ -30,61 +185,10 @@ export function registerCleanupCommand(program) {
30
185
  .option('-f, --force', 'Kill a running (possibly hung) session, then clean up', false)
31
186
  .option('--remove-output', 'Also remove session.json output file', false)
32
187
  .option('--aggressive', 'Alias for --force (kept for compatibility)', false)
188
+ .option('--purge', "Also delete a named session's directory (Chrome profile, logs, port); needs --session", false)
33
189
  .addOption(jsonOption())
34
190
  .action(async (options) => {
35
- await runCommand(async (opts) => {
36
- const dir = getSessionDir();
37
- if (fs.existsSync(dir) && !fs.statSync(dir).isDirectory()) {
38
- const err = sessionDirIsFileError(dir);
39
- return {
40
- success: false,
41
- error: err.message,
42
- exitCode: EXIT_CODES.SESSION_FILE_ERROR,
43
- errorContext: { suggestion: err.suggestion },
44
- };
45
- }
46
- if (!opts.force && !opts.aggressive && (await isDaemonAlive())) {
47
- return {
48
- success: false,
49
- error: sessionStillActiveError(readDaemonPid() ?? 0),
50
- exitCode: EXIT_CODES.RESOURCE_BUSY,
51
- errorContext: {
52
- suggestion: 'Stop gracefully: bdg stop\nForce cleanup: bdg cleanup --force',
53
- warning: 'Force cleanup kills the running daemon and its Chrome',
54
- },
55
- };
56
- }
57
- const cleanupResult = await performSessionCleanup({
58
- force: Boolean(opts.force) || Boolean(opts.aggressive),
59
- removeOutput: opts.removeOutput,
60
- });
61
- const didCleanup = cleanupResult.cleaned.session ||
62
- cleanupResult.cleaned.chrome ||
63
- cleanupResult.cleaned.daemons ||
64
- cleanupResult.cleaned.output;
65
- if (!didCleanup) {
66
- return {
67
- success: true,
68
- data: {
69
- cleaned: { session: false, output: false, chrome: false, daemons: false },
70
- message: noSessionFilesMessage(),
71
- },
72
- };
73
- }
74
- return {
75
- success: true,
76
- data: {
77
- cleaned: {
78
- session: cleanupResult.cleaned.session,
79
- output: cleanupResult.cleaned.output,
80
- chrome: cleanupResult.cleaned.chrome,
81
- daemons: cleanupResult.cleaned.daemons,
82
- },
83
- message: sessionDirectoryCleanMessage(),
84
- ...(cleanupResult.warnings.length > 0 && { warnings: cleanupResult.warnings }),
85
- },
86
- };
87
- }, options, formatCleanup);
191
+ await runCommand(cleanupSession, options, formatCleanup);
88
192
  });
89
193
  }
90
194
  //# sourceMappingURL=cleanup.js.map
@@ -2,8 +2,17 @@
2
2
  * Console command for inspecting and filtering console messages.
3
3
  */
4
4
  import { type Command } from 'commander';
5
+ import type { ConsoleCommandOptions } from './shared/optionTypes.js';
5
6
  import type { ConsoleMessage } from '../types.js';
6
- import { type ConsoleLevel } from '../ui/formatters/console.js';
7
+ import { type ConsoleLevel, type ConsoleSkipped } from '../ui/formatters/console.js';
8
+ /**
9
+ * Whether to list the messages instead of summarising them: with `--list`,
10
+ * and when `--last` asks for a number of them.
11
+ *
12
+ * @param options - Command options
13
+ * @returns True to list
14
+ */
15
+ export declare function listsMessages(options: Pick<ConsoleCommandOptions, 'list' | 'last'>): boolean;
7
16
  /**
8
17
  * Keep the messages of the page currently loaded.
9
18
  *
@@ -14,6 +23,16 @@ import { type ConsoleLevel } from '../ui/formatters/console.js';
14
23
  */
15
24
  export declare function filterByCurrentNavigation(messages: ConsoleMessage[], currentNavigationId?: number): ConsoleMessage[];
16
25
  export declare function filterByLevel(messages: ConsoleMessage[], level: ConsoleLevel): ConsoleMessage[];
26
+ /**
27
+ * Messages the filters left out between the first and the last listed
28
+ * message (their session indices skip them), by why: logged by another page
29
+ * load, or of another level.
30
+ *
31
+ * @param all - All messages of the session
32
+ * @param listed - Messages listed
33
+ * @returns Counts per reason
34
+ */
35
+ export declare function skippedMessages(all: ConsoleMessage[], listed: ConsoleMessage[]): ConsoleSkipped;
17
36
  /**
18
37
  * Identity of each message across polls (its index can shift when an earlier
19
38
  * message is inserted late). Identical messages logged in the same
@@ -4,18 +4,28 @@
4
4
  import { Option } from 'commander';
5
5
  import { runCommand } from './shared/CommandRunner.js';
6
6
  import { jsonOption } from './shared/commonOptions.js';
7
- import { handleDaemonConnectionError, noteFollowConnected, } from './shared/daemonErrorHandler.js';
7
+ import { noteFollowConnected } from './shared/daemonErrorHandler.js';
8
8
  import { fetchConsoleMessages, createErrorResult } from './shared/dataFetcher.js';
9
- import { setupFollowMode } from './shared/followMode.js';
9
+ import { followFetchFailure, 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
- import { buildConsoleJsonOutput, formatConsole, formatConsoleFollowLines, LEVEL_MAP, } from '../ui/formatters/console.js';
13
+ import { buildConsoleJsonOutput, formatConsole, formatConsoleFollowLines, LEVEL_MAP, lastMessages, } from '../ui/formatters/console.js';
14
14
  import { followingConsoleMessage, stoppedFollowingConsoleMessage, } from '../ui/messages/consoleMessages.js';
15
15
  const MIN_LAST = 0;
16
16
  const MAX_LAST = 10000;
17
17
  const DEFAULT_LAST = 100;
18
- const consoleLastOption = new Option('--last <n>', 'Show last N console messages (0 = all)').default(String(DEFAULT_LAST));
18
+ const consoleLastOption = new Option('--last <n>', `List the last N console messages (0 = all; default: ${DEFAULT_LAST} with --list or --follow)`);
19
+ /**
20
+ * Whether to list the messages instead of summarising them: with `--list`,
21
+ * and when `--last` asks for a number of them.
22
+ *
23
+ * @param options - Command options
24
+ * @returns True to list
25
+ */
26
+ export function listsMessages(options) {
27
+ return options.list === true || options.last !== undefined;
28
+ }
19
29
  /**
20
30
  * Keep the messages of the page currently loaded.
21
31
  *
@@ -41,14 +51,50 @@ function applyFilters(messages, options, currentNavigationId) {
41
51
  filtered = filterByLevel(filtered, options.level);
42
52
  return filtered;
43
53
  }
44
- function buildFormatOptions(options, lastN) {
54
+ /**
55
+ * Messages the filters left out between the first and the last listed
56
+ * message (their session indices skip them), by why: logged by another page
57
+ * load, or of another level.
58
+ *
59
+ * @param all - All messages of the session
60
+ * @param listed - Messages listed
61
+ * @returns Counts per reason
62
+ */
63
+ export function skippedMessages(all, listed) {
64
+ const shown = new Set(listed.map((message) => message.index));
65
+ const indices = listed.map((message) => message.index).filter((index) => index !== undefined);
66
+ const skipped = { otherPages: 0, otherLevels: 0 };
67
+ if (indices.length < 2)
68
+ return skipped;
69
+ const [first, last] = [Math.min(...indices), Math.max(...indices)];
70
+ const pages = new Set(listed.map((message) => message.navigationId));
71
+ for (const { index, navigationId } of all) {
72
+ if (index === undefined || index < first || index > last || shown.has(index))
73
+ continue;
74
+ if (pages.has(navigationId))
75
+ skipped.otherLevels++;
76
+ else
77
+ skipped.otherPages++;
78
+ }
79
+ return skipped;
80
+ }
81
+ /**
82
+ * Formatting options from the command options.
83
+ *
84
+ * @param options - Command options
85
+ * @param lastN - `--last` value
86
+ * @param skipped - Messages the filters left out between the listed ones
87
+ * @returns Formatting options
88
+ */
89
+ function buildFormatOptions(options, lastN, skipped) {
45
90
  return {
46
91
  json: options.json,
47
- list: options.list,
92
+ list: listsMessages(options),
48
93
  follow: options.follow,
49
94
  last: lastN,
50
95
  history: options.history,
51
96
  level: options.level,
97
+ skipped,
52
98
  };
53
99
  }
54
100
  /**
@@ -65,15 +111,7 @@ async function runFollowMode(options, lastN) {
65
111
  const showConsole = async () => {
66
112
  const result = await fetchConsoleMessages();
67
113
  if (!result.success) {
68
- const errorResult = handleDaemonConnectionError(result.error, {
69
- json: options.json,
70
- follow: true,
71
- retryIntervalMs: 1000,
72
- exitCode: result.exitCode,
73
- });
74
- if (errorResult.shouldExit)
75
- process.exit(errorResult.exitCode);
76
- return;
114
+ return followFetchFailure(result, { json: options.json, retryIntervalMs: 1000 });
77
115
  }
78
116
  noteFollowConnected();
79
117
  const { messages, currentNavigationId } = result.data;
@@ -101,6 +139,7 @@ async function runFollowMode(options, lastN) {
101
139
  console.log(text);
102
140
  }
103
141
  started = true;
142
+ return undefined;
104
143
  };
105
144
  await setupFollowMode(showConsole, {
106
145
  startMessage: followingConsoleMessage,
@@ -167,8 +206,9 @@ export function registerConsoleCommand(program) {
167
206
  }
168
207
  return { success: true, data: { messages, filtered } };
169
208
  }, options, (data) => {
170
- const { filtered } = data;
171
- return formatConsole(filtered, buildFormatOptions(options, lastN));
209
+ const { messages, filtered } = data;
210
+ const skipped = skippedMessages(messages, lastMessages(filtered, lastN));
211
+ return formatConsole(filtered, buildFormatOptions(options, lastN, skipped));
172
212
  });
173
213
  });
174
214
  }
@@ -3,6 +3,7 @@ import { jsonOption } from './shared/commonOptions.js';
3
3
  import { getDetails } from '../ipc/client.js';
4
4
  import { validateIPCResponse } from '../ipc/index.js';
5
5
  import { formatNetworkDetails, formatConsoleDetails } from '../ui/formatters/details.js';
6
+ import { sessionCommand } from '../ui/messages/sessionCommand.js';
6
7
  import { EXIT_CODES } from '../utils/exitCodes.js';
7
8
  import { validateDetailsItem } from '../utils/typeGuards.js';
8
9
  /**
@@ -65,8 +66,8 @@ export function registerDetailsCommand(program) {
65
66
  exitCode: EXIT_CODES.RESOURCE_NOT_FOUND,
66
67
  errorContext: {
67
68
  suggestion: opts.type === 'network'
68
- ? 'Use bdg network list to see available request IDs'
69
- : 'Use bdg peek --console to see available console message indices',
69
+ ? `Use ${sessionCommand('bdg network list')} to see available request IDs`
70
+ : `Use ${sessionCommand('bdg peek --console')} to see available console message indices`,
70
71
  },
71
72
  };
72
73
  }
@@ -11,14 +11,15 @@
11
11
  * ```typescript
12
12
  * const resolver = DomElementResolver.getInstance();
13
13
  *
14
- * const target = await resolver.resolve('0');
14
+ * const target = await resolver.resolve('0', undefined, 'click');
15
15
  * // { success: true, selector: '.cached-selector', backendNodeId: 42 }
16
16
  *
17
- * const target = await resolver.resolve('button.submit');
17
+ * const target = await resolver.resolve('button.submit', undefined, 'click');
18
18
  * // { success: true, selector: 'button.submit' }
19
19
  * ```
20
20
  */
21
21
  import { QueryCacheManager } from '../../session/QueryCacheManager.js';
22
+ import type { IndexSource } from '../../types.js';
22
23
  /**
23
24
  * Successful result of resolving a selector or index argument.
24
25
  */
@@ -31,6 +32,10 @@ export interface ElementTargetSuccess {
31
32
  index?: number | undefined;
32
33
  /** Exact element from the query cache (index arguments only) */
33
34
  backendNodeId?: number | undefined;
35
+ /** The list the index refers to (index arguments only) */
36
+ source?: IndexSource | undefined;
37
+ /** What the cached element was when listed, e.g. `h3 "Welcome"` (index arguments only) */
38
+ preview?: string | undefined;
34
39
  }
35
40
  /**
36
41
  * Failed result of resolving a selector or index argument.
@@ -76,9 +81,10 @@ export declare class DomElementResolver {
76
81
  *
77
82
  * @param selectorOrIndex - CSS selector or numeric index from query results
78
83
  * @param explicitIndex - Optional explicit --index flag value (0-based, selectors only)
84
+ * @param command - `bdg dom` subcommand being run, e.g. "click" (for suggestions)
79
85
  * @returns Resolution result
80
86
  */
81
- resolve(selectorOrIndex: string, explicitIndex?: number): Promise<ElementTargetResult>;
87
+ resolve(selectorOrIndex: string, explicitIndex: number | undefined, command: string): Promise<ElementTargetResult>;
82
88
  /**
83
89
  * Get the backend node id for a cached index, checking the element still exists.
84
90
  *
@@ -89,6 +95,7 @@ export declare class DomElementResolver {
89
95
  */
90
96
  getNodeIdForIndex(index: number): Promise<{
91
97
  nodeId: number;
98
+ source: IndexSource;
92
99
  }>;
93
100
  /**
94
101
  * Check if the argument is a numeric index.