browser-debugger-cli 0.15.0 → 0.16.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 (133) hide show
  1. package/.claude/skills/bdg/SKILL.md +2 -1
  2. package/dist/cdp/methodTarget.d.ts +92 -0
  3. package/dist/cdp/methodTarget.js +159 -0
  4. package/dist/cdp/protocol.d.ts +16 -1
  5. package/dist/cdp/protocol.js +21 -0
  6. package/dist/cdp/schema.d.ts +55 -1
  7. package/dist/cdp/schema.js +134 -25
  8. package/dist/cdp/types.d.ts +3 -1
  9. package/dist/commands/cdp.d.ts +38 -1
  10. package/dist/commands/cdp.js +200 -133
  11. package/dist/commands/cleanup.js +18 -4
  12. package/dist/commands/dom/formInteraction.js +8 -4
  13. package/dist/commands/dom/helpers/index.d.ts +4 -4
  14. package/dist/commands/dom/helpers/index.js +3 -3
  15. package/dist/commands/dom/helpers/query.d.ts +2 -2
  16. package/dist/commands/dom/helpers/query.js +2 -2
  17. package/dist/commands/dom/helpers/screenshot.d.ts +21 -26
  18. package/dist/commands/dom/helpers/screenshot.js +50 -668
  19. package/dist/commands/dom/screenshot.js +56 -36
  20. package/dist/commands/optionBehaviors.js +18 -8
  21. package/dist/commands/shared/CommandRunner.d.ts +5 -0
  22. package/dist/commands/shared/CommandRunner.js +18 -3
  23. package/dist/commands/shared/interrupt.d.ts +40 -0
  24. package/dist/commands/shared/interrupt.js +73 -0
  25. package/dist/commands/shared/optionTypes.d.ts +2 -0
  26. package/dist/commands/shared/startHelpers.d.ts +26 -3
  27. package/dist/commands/shared/startHelpers.js +145 -23
  28. package/dist/commands/types.d.ts +5 -0
  29. package/dist/connection/cdp.js +1 -16
  30. package/dist/connection/chromeIdentity.d.ts +24 -5
  31. package/dist/connection/chromeIdentity.js +53 -22
  32. package/dist/connection/launcher.d.ts +34 -1
  33. package/dist/connection/launcher.js +98 -10
  34. package/dist/connection/typed-cdp.d.ts +3 -2
  35. package/dist/constants.d.ts +1 -1
  36. package/dist/constants.js +1 -1
  37. package/dist/daemon/SessionController.d.ts +10 -5
  38. package/dist/daemon/SessionController.js +15 -8
  39. package/dist/daemon/ipcServer.js +1 -1
  40. package/dist/daemon/launcher.d.ts +5 -0
  41. package/dist/daemon/launcher.js +8 -1
  42. package/dist/daemon/session/Session.d.ts +5 -1
  43. package/dist/daemon/session/Session.js +9 -8
  44. package/dist/daemon/session/TelemetryStore.d.ts +5 -0
  45. package/dist/daemon/session/TelemetryStore.js +4 -0
  46. package/dist/daemon/session/captureGate.d.ts +59 -0
  47. package/dist/daemon/session/captureGate.js +96 -0
  48. package/dist/daemon/session/chromeConnection.d.ts +16 -1
  49. package/dist/daemon/session/chromeConnection.js +34 -4
  50. package/dist/daemon/session/collectors.d.ts +15 -0
  51. package/dist/daemon/session/collectors.js +39 -2
  52. package/dist/daemon/session/commandRegistry.d.ts +14 -1
  53. package/dist/daemon/session/commandRegistry.js +46 -11
  54. package/dist/daemon/session/downloads.d.ts +32 -0
  55. package/dist/daemon/session/downloads.js +96 -0
  56. package/dist/daemon/session/interactions.d.ts +3 -2
  57. package/dist/daemon/session/interactions.js +7 -2
  58. package/dist/daemon/session/plugins.js +6 -0
  59. package/dist/daemon.js +12843 -11482
  60. package/dist/errors/CommandError.d.ts +2 -0
  61. package/dist/errors/issues.d.ts +1 -1
  62. package/dist/errors/messages.d.ts +58 -0
  63. package/dist/errors/messages.js +112 -0
  64. package/dist/index.js +999 -1020
  65. package/dist/ipc/client.d.ts +14 -1
  66. package/dist/ipc/client.js +21 -4
  67. package/dist/ipc/protocol/commands.d.ts +32 -2
  68. package/dist/ipc/protocol/commands.js +1 -0
  69. package/dist/ipc/protocol/domTypes.d.ts +24 -1
  70. package/dist/ipc/session/queries.d.ts +3 -0
  71. package/dist/ipc/session/types.d.ts +5 -0
  72. package/dist/ipc/transport/IPCError.d.ts +9 -0
  73. package/dist/ipc/transport/IPCError.js +12 -0
  74. package/dist/ipc/transport/errors.d.ts +2 -1
  75. package/dist/ipc/transport/errors.js +4 -1
  76. package/dist/ipc/transport/index.d.ts +4 -2
  77. package/dist/ipc/transport/index.js +13 -3
  78. package/dist/runtime/dom/actionEffects.d.ts +48 -9
  79. package/dist/runtime/dom/actionEffects.js +269 -34
  80. package/dist/runtime/dom/actionEffectsScripts.d.ts +45 -0
  81. package/dist/runtime/dom/actionEffectsScripts.js +101 -2
  82. package/dist/runtime/dom/captureArea.d.ts +35 -0
  83. package/dist/runtime/dom/captureArea.js +203 -0
  84. package/dist/runtime/dom/elementInfo.d.ts +10 -8
  85. package/dist/runtime/dom/elementInfo.js +8 -6
  86. package/dist/runtime/dom/formDiscovery.d.ts +1 -1
  87. package/dist/runtime/page/bdgWorld.d.ts +9 -0
  88. package/dist/runtime/page/bdgWorld.js +11 -0
  89. package/dist/runtime/page/captureEmulation.d.ts +119 -0
  90. package/dist/runtime/page/captureEmulation.js +189 -0
  91. package/dist/runtime/page/captureScroll.d.ts +24 -0
  92. package/dist/runtime/page/captureScroll.js +124 -0
  93. package/dist/runtime/page/screenshot.d.ts +41 -0
  94. package/dist/runtime/page/screenshot.js +394 -0
  95. package/dist/session/paths.d.ts +14 -0
  96. package/dist/session/paths.js +25 -0
  97. package/dist/telemetry/downloads.d.ts +127 -0
  98. package/dist/telemetry/downloads.js +265 -0
  99. package/dist/telemetry/har/builder.js +22 -7
  100. package/dist/telemetry/har/sanitize.d.ts +7 -3
  101. package/dist/telemetry/har/sanitize.js +52 -6
  102. package/dist/telemetry/har/sanitizeBody.d.ts +47 -7
  103. package/dist/telemetry/har/sanitizeBody.js +429 -56
  104. package/dist/telemetry/har/types.d.ts +2 -0
  105. package/dist/telemetry/network.d.ts +4 -4
  106. package/dist/telemetry/network.js +38 -4
  107. package/dist/telemetry/networkRetention.d.ts +35 -14
  108. package/dist/telemetry/networkRetention.js +62 -26
  109. package/dist/types.d.ts +9 -14
  110. package/dist/ui/OutputBuilder.d.ts +3 -2
  111. package/dist/ui/OutputBuilder.js +4 -3
  112. package/dist/ui/formatters/cdp.d.ts +32 -9
  113. package/dist/ui/formatters/cdp.js +77 -6
  114. package/dist/ui/formatters/details.js +7 -15
  115. package/dist/ui/formatters/preview.d.ts +2 -0
  116. package/dist/ui/formatters/preview.js +7 -1
  117. package/dist/ui/formatters/status.js +6 -1
  118. package/dist/ui/formatting.d.ts +7 -0
  119. package/dist/ui/formatting.js +13 -0
  120. package/dist/ui/logging/logger.d.ts +1 -1
  121. package/dist/ui/messages/chrome.d.ts +13 -0
  122. package/dist/ui/messages/chrome.js +26 -0
  123. package/dist/ui/messages/commands.d.ts +71 -3
  124. package/dist/ui/messages/commands.js +98 -3
  125. package/dist/ui/messages/networkMessages.d.ts +24 -5
  126. package/dist/ui/messages/networkMessages.js +31 -8
  127. package/dist/utils/async.d.ts +3 -2
  128. package/dist/utils/async.js +16 -3
  129. package/dist/utils/http.d.ts +11 -4
  130. package/dist/utils/http.js +5 -3
  131. package/package.json +18 -4
  132. /package/dist/{commands/dom → runtime/page}/screenshotResize.d.ts +0 -0
  133. /package/dist/{commands/dom → runtime/page}/screenshotResize.js +0 -0
@@ -1,38 +1,42 @@
1
1
  /**
2
2
  * What the network collector keeps of a long session: the newest finished
3
- * requests up to a cap, and the newest response bodies up to a total size.
3
+ * requests up to a cap, and the newest request and response bodies up to a
4
+ * total size.
4
5
  */
5
6
  import type { NetworkRequest } from '../types.js';
6
7
  /** Counts of what the session let go at its limits */
7
8
  export interface NetworkEvictions {
8
9
  /** Finished requests dropped at the request cap, oldest first */
9
10
  requestsDropped: number;
10
- /** Response bodies replaced by a placeholder at the total body budget, oldest first */
11
+ /** Request and response bodies replaced by a placeholder at the total body budget, oldest first */
11
12
  bodiesEvicted: number;
12
13
  }
13
14
  /** Limits of {@link RequestRetention} */
14
15
  export interface RetentionLimits {
15
16
  /** Finished requests kept at most */
16
17
  maxRequests: number;
17
- /** Total size of the stored response bodies (bytes) */
18
+ /** Total size of the stored request and response bodies (bytes) */
18
19
  maxTotalBodyBytes: number;
19
20
  }
20
21
  /**
21
22
  * Keeps finished requests, oldest first, dropping the oldest past the cap
22
23
  * (requests in flight are never in the list, so never dropped), and tracks
23
- * the size of their stored bodies, replacing the oldest bodies past the
24
- * budget with a placeholder that says why.
24
+ * the size of their stored request and response bodies under one budget,
25
+ * replacing the oldest bodies past it with a placeholder that says why.
25
26
  *
26
- * Stored bodies are kept in a Map, which iterates in insertion order: its
27
- * first entry is the oldest body, and a dropped request's body is removed in
28
- * O(1). Dropping from the front of the list is `Array.shift`, which V8 does
29
- * without copying for arrays of this size.
27
+ * A request body is counted when its request finishes, a response body when
28
+ * it is fetched. Stored bodies are kept in a Set, which iterates in insertion
29
+ * order: its first entry is the oldest body. Each part is also indexed by
30
+ * request, so a dropped request's bodies are removed in O(1). Dropping from
31
+ * the front of the list is `Array.shift`, which V8 does without copying for
32
+ * arrays of this size.
30
33
  */
31
34
  export declare class RequestRetention {
32
35
  private readonly requests;
33
36
  private readonly limits;
34
37
  private readonly evictions;
35
- private readonly bodySizes;
38
+ private readonly storedBodies;
39
+ private readonly bodiesByPart;
36
40
  private storedBodyBytes;
37
41
  /**
38
42
  * @param requests - Finished requests, oldest first (updated in place)
@@ -41,12 +45,19 @@ export declare class RequestRetention {
41
45
  */
42
46
  constructor(requests: NetworkRequest[], limits: RetentionLimits, evictions: NetworkEvictions);
43
47
  /**
44
- * Add a finished request, dropping the oldest one past the cap.
48
+ * Add a finished request, drop the oldest request past the cap, then count
49
+ * the new request's body against the budget.
45
50
  *
46
51
  * @param request - Finished request
47
52
  * @returns The dropped request, if one was
48
53
  */
49
54
  add(request: NetworkRequest): NetworkRequest | undefined;
55
+ /**
56
+ * Drop the oldest request when the list is over the cap, with its bodies.
57
+ *
58
+ * @returns The dropped request, if one was
59
+ */
60
+ private dropOldestPastCap;
50
61
  /**
51
62
  * Store a fetched response body on a kept request, then evict the oldest
52
63
  * bodies until the total fits the budget (a body larger than the whole
@@ -57,17 +68,27 @@ export declare class RequestRetention {
57
68
  * @param base64Encoded - Whether `body` is base64
58
69
  */
59
70
  storeBody(request: NetworkRequest, body: string, base64Encoded: boolean): void;
71
+ /**
72
+ * Count a stored body (replacing an earlier count of the same part), then
73
+ * evict the oldest bodies while the total is over the budget.
74
+ *
75
+ * @param request - Request the body belongs to
76
+ * @param part - Which of its bodies
77
+ * @param size - Body size (bytes)
78
+ */
79
+ private countBody;
60
80
  /** Evict the oldest stored bodies while their total is over the budget. */
61
81
  private enforceBodyBudget;
62
82
  /**
63
- * Stop counting a request's stored body.
83
+ * Stop counting one of a request's stored bodies.
64
84
  *
65
85
  * @param request - Request whose body is let go
86
+ * @param part - Which of its bodies
66
87
  */
67
88
  private forgetBody;
68
89
  }
69
90
  /**
70
- * Placeholder stored instead of a response body that was not fetched.
91
+ * Placeholder stored instead of a body that was not fetched or not kept.
71
92
  *
72
93
  * @param reason - Why the body was skipped
73
94
  * @returns Placeholder text shown by `bdg details`
@@ -76,7 +97,7 @@ export declare function skippedBodyPlaceholder(reason: string): string;
76
97
  /**
77
98
  * Extract the reason from a skipped-body placeholder.
78
99
  *
79
- * @param body - Stored response body
100
+ * @param body - Stored request or response body
80
101
  * @returns Reason if `body` is a placeholder, otherwise undefined
81
102
  */
82
103
  export declare function skippedBodyReason(body: string | undefined): string | undefined;
@@ -1,24 +1,31 @@
1
1
  /**
2
2
  * What the network collector keeps of a long session: the newest finished
3
- * requests up to a cap, and the newest response bodies up to a total size.
3
+ * requests up to a cap, and the newest request and response bodies up to a
4
+ * total size.
4
5
  */
5
6
  import { bodyEvictedReason } from '../ui/messages/networkMessages.js';
6
7
  /**
7
8
  * Keeps finished requests, oldest first, dropping the oldest past the cap
8
9
  * (requests in flight are never in the list, so never dropped), and tracks
9
- * the size of their stored bodies, replacing the oldest bodies past the
10
- * budget with a placeholder that says why.
10
+ * the size of their stored request and response bodies under one budget,
11
+ * replacing the oldest bodies past it with a placeholder that says why.
11
12
  *
12
- * Stored bodies are kept in a Map, which iterates in insertion order: its
13
- * first entry is the oldest body, and a dropped request's body is removed in
14
- * O(1). Dropping from the front of the list is `Array.shift`, which V8 does
15
- * without copying for arrays of this size.
13
+ * A request body is counted when its request finishes, a response body when
14
+ * it is fetched. Stored bodies are kept in a Set, which iterates in insertion
15
+ * order: its first entry is the oldest body. Each part is also indexed by
16
+ * request, so a dropped request's bodies are removed in O(1). Dropping from
17
+ * the front of the list is `Array.shift`, which V8 does without copying for
18
+ * arrays of this size.
16
19
  */
17
20
  export class RequestRetention {
18
21
  requests;
19
22
  limits;
20
23
  evictions;
21
- bodySizes = new Map();
24
+ storedBodies = new Set();
25
+ bodiesByPart = {
26
+ requestBody: new Map(),
27
+ responseBody: new Map(),
28
+ };
22
29
  storedBodyBytes = 0;
23
30
  /**
24
31
  * @param requests - Finished requests, oldest first (updated in place)
@@ -31,20 +38,34 @@ export class RequestRetention {
31
38
  this.evictions = evictions;
32
39
  }
33
40
  /**
34
- * Add a finished request, dropping the oldest one past the cap.
41
+ * Add a finished request, drop the oldest request past the cap, then count
42
+ * the new request's body against the budget.
35
43
  *
36
44
  * @param request - Finished request
37
45
  * @returns The dropped request, if one was
38
46
  */
39
47
  add(request) {
40
48
  this.requests.push(request);
49
+ const dropped = this.dropOldestPastCap();
50
+ if (request.requestBody) {
51
+ this.countBody(request, 'requestBody', Buffer.byteLength(request.requestBody));
52
+ }
53
+ return dropped;
54
+ }
55
+ /**
56
+ * Drop the oldest request when the list is over the cap, with its bodies.
57
+ *
58
+ * @returns The dropped request, if one was
59
+ */
60
+ dropOldestPastCap() {
41
61
  if (this.requests.length <= this.limits.maxRequests)
42
62
  return undefined;
43
63
  const dropped = this.requests.shift();
44
64
  if (!dropped)
45
65
  return undefined;
46
66
  this.evictions.requestsDropped++;
47
- this.forgetBody(dropped);
67
+ this.forgetBody(dropped, 'requestBody');
68
+ this.forgetBody(dropped, 'responseBody');
48
69
  return dropped;
49
70
  }
50
71
  /**
@@ -57,47 +78,62 @@ export class RequestRetention {
57
78
  * @param base64Encoded - Whether `body` is base64
58
79
  */
59
80
  storeBody(request, body, base64Encoded) {
60
- this.forgetBody(request);
61
81
  request.responseBody = body;
62
82
  if (base64Encoded)
63
83
  request.responseBodyBase64 = true;
64
84
  if (body) {
65
85
  request.decodedBodyLength = Buffer.byteLength(body, base64Encoded ? 'base64' : 'utf-8');
66
86
  }
67
- const size = Buffer.byteLength(body);
68
- this.bodySizes.set(request, size);
87
+ this.countBody(request, 'responseBody', Buffer.byteLength(body));
88
+ }
89
+ /**
90
+ * Count a stored body (replacing an earlier count of the same part), then
91
+ * evict the oldest bodies while the total is over the budget.
92
+ *
93
+ * @param request - Request the body belongs to
94
+ * @param part - Which of its bodies
95
+ * @param size - Body size (bytes)
96
+ */
97
+ countBody(request, part, size) {
98
+ this.forgetBody(request, part);
99
+ const stored = { request, part, size };
100
+ this.storedBodies.add(stored);
101
+ this.bodiesByPart[part].set(request, stored);
69
102
  this.storedBodyBytes += size;
70
103
  this.enforceBodyBudget();
71
104
  }
72
105
  /** Evict the oldest stored bodies while their total is over the budget. */
73
106
  enforceBodyBudget() {
74
107
  while (this.storedBodyBytes > this.limits.maxTotalBodyBytes) {
75
- const oldest = this.bodySizes.keys().next();
108
+ const oldest = this.storedBodies.values().next();
76
109
  if (oldest.done)
77
110
  return;
78
- const request = oldest.value;
79
- this.forgetBody(request);
80
- request.responseBody = skippedBodyPlaceholder(bodyEvictedReason(this.limits.maxTotalBodyBytes));
81
- delete request.responseBodyBase64;
111
+ const { request, part } = oldest.value;
112
+ this.forgetBody(request, part);
113
+ request[part] = skippedBodyPlaceholder(bodyEvictedReason(this.limits.maxTotalBodyBytes));
114
+ if (part === 'responseBody')
115
+ delete request.responseBodyBase64;
82
116
  this.evictions.bodiesEvicted++;
83
117
  }
84
118
  }
85
119
  /**
86
- * Stop counting a request's stored body.
120
+ * Stop counting one of a request's stored bodies.
87
121
  *
88
122
  * @param request - Request whose body is let go
123
+ * @param part - Which of its bodies
89
124
  */
90
- forgetBody(request) {
91
- const size = this.bodySizes.get(request);
92
- if (size === undefined)
125
+ forgetBody(request, part) {
126
+ const stored = this.bodiesByPart[part].get(request);
127
+ if (!stored)
93
128
  return;
94
- this.bodySizes.delete(request);
95
- this.storedBodyBytes -= size;
129
+ this.bodiesByPart[part].delete(request);
130
+ this.storedBodies.delete(stored);
131
+ this.storedBodyBytes -= stored.size;
96
132
  }
97
133
  }
98
134
  const SKIPPED_BODY_PATTERN = /^\[SKIPPED: (.*)\]$/s;
99
135
  /**
100
- * Placeholder stored instead of a response body that was not fetched.
136
+ * Placeholder stored instead of a body that was not fetched or not kept.
101
137
  *
102
138
  * @param reason - Why the body was skipped
103
139
  * @returns Placeholder text shown by `bdg details`
@@ -108,7 +144,7 @@ export function skippedBodyPlaceholder(reason) {
108
144
  /**
109
145
  * Extract the reason from a skipped-body placeholder.
110
146
  *
111
- * @param body - Stored response body
147
+ * @param body - Stored request or response body
112
148
  * @returns Reason if `body` is a placeholder, otherwise undefined
113
149
  */
114
150
  export function skippedBodyReason(body) {
package/dist/types.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { Protocol } from './connection/typed-cdp.js';
2
+ import type { DownloadInfo } from './ipc/protocol/domTypes.js';
2
3
  /**
3
4
  * Standard response envelope for all bdg command JSON output.
4
5
  *
@@ -40,6 +41,8 @@ export interface BdgResponse<T = unknown> {
40
41
  exitCode?: number;
41
42
  /** Actionable suggestion for error recovery */
42
43
  suggestion?: string;
44
+ /** How the command ran despite something unusual (e.g. a CDP method bdg's protocol lacks) */
45
+ warning?: string;
43
46
  }
44
47
  /**
45
48
  * Re-export connection types for backward compatibility.
@@ -150,6 +153,8 @@ export interface NetworkRequest {
150
153
  fromCache?: boolean;
151
154
  /** Why the response body was not captured (`details`; `responseBody` is then absent) */
152
155
  bodyNotCaptured?: string;
156
+ /** Why the request body was not kept (`details`; `requestBody` is then absent) */
157
+ requestBodyNotCaptured?: string;
153
158
  /** Messages and lifecycle of a WebSocket connection (`resourceType` is `WebSocket`) */
154
159
  webSocket?: {
155
160
  frames: WebSocketFrame[];
@@ -230,6 +235,8 @@ export interface BdgOutput {
230
235
  currentNavigationId?: number;
231
236
  /** When the page's renderer crashed (epoch ms), while it is not loaded again */
232
237
  pageCrashedAt?: number;
238
+ /** Downloads that began during the session, oldest first (live previews) */
239
+ downloads?: DownloadInfo[];
233
240
  /** Counts of all captured items matching the request (e.g. `peek --type`), when `data` holds only the most recent ones */
234
241
  totals?: {
235
242
  network: number;
@@ -528,8 +535,8 @@ export interface ScreenshotResult {
528
535
  /** `--padding` given: CSS px of page added around the captured area */
529
536
  padding?: number;
530
537
  };
531
- /** Capture mode used */
532
- captureMode?: 'full_page' | 'viewport';
538
+ /** Capture mode used: the whole page, the viewport, or one element */
539
+ captureMode?: 'full_page' | 'viewport' | 'element';
533
540
  /** Whether the image was auto-resized to fit token budget */
534
541
  resized?: boolean;
535
542
  /** Original width before resize (pixels) */
@@ -560,18 +567,6 @@ export interface DomGetOptions {
560
567
  all?: boolean;
561
568
  nth?: number;
562
569
  }
563
- /**
564
- * Options for screenshot operation.
565
- */
566
- export interface ScreenshotOptions {
567
- format?: 'png' | 'jpeg';
568
- quality?: number;
569
- fullPage?: boolean;
570
- /** Disable auto-resize to 1568px max edge */
571
- noResize?: boolean;
572
- /** Scroll element into view before capture */
573
- scroll?: string;
574
- }
575
570
  /**
576
571
  * Field validation state from HTML5 or custom validation.
577
572
  */
@@ -6,9 +6,10 @@ import type { BdgResponse } from '../types.js';
6
6
  * Build a success response envelope.
7
7
  *
8
8
  * @param data - Response payload
9
- * @returns `{ version, success: true, data }`
9
+ * @param warning - Warning about how the command ran, if any
10
+ * @returns `{ version, success: true, data }`, plus `warning` when given
10
11
  */
11
- export declare function buildSuccessResponse<T>(data: T): BdgResponse<T>;
12
+ export declare function buildSuccessResponse<T>(data: T, warning?: string): BdgResponse<T>;
12
13
  /**
13
14
  * Serialize a `--json` response envelope for stdout.
14
15
  *
@@ -6,10 +6,11 @@ import { VERSION } from '../utils/version.js';
6
6
  * Build a success response envelope.
7
7
  *
8
8
  * @param data - Response payload
9
- * @returns `{ version, success: true, data }`
9
+ * @param warning - Warning about how the command ran, if any
10
+ * @returns `{ version, success: true, data }`, plus `warning` when given
10
11
  */
11
- export function buildSuccessResponse(data) {
12
- return { version: VERSION, success: true, data };
12
+ export function buildSuccessResponse(data, warning) {
13
+ return { version: VERSION, success: true, data, ...(warning && { warning }) };
13
14
  }
14
15
  /**
15
16
  * Serialize a `--json` response envelope for stdout.
@@ -67,6 +67,15 @@ interface CdpField {
67
67
  description?: string | undefined;
68
68
  items?: string | undefined;
69
69
  }
70
+ /** A parameter (or type property) in `--describe`: `enum` also holds a `$ref` type's values */
71
+ interface CdpParameter extends CdpField {
72
+ required: boolean;
73
+ enum?: string[] | undefined;
74
+ ref?: string | undefined;
75
+ refType?: string | undefined;
76
+ experimental?: boolean | undefined;
77
+ deprecated?: boolean | undefined;
78
+ }
70
79
  /** `bdg cdp <Domain.method> --describe` result */
71
80
  export interface CdpMethodDescription extends ProtocolEntry {
72
81
  type: 'method';
@@ -74,19 +83,31 @@ export interface CdpMethodDescription extends ProtocolEntry {
74
83
  domain: string;
75
84
  method: string;
76
85
  note?: string | undefined;
77
- parameters: (CdpField & {
78
- required: boolean;
79
- enum?: string[] | undefined;
80
- deprecated?: boolean | undefined;
81
- })[];
86
+ parameters: CdpParameter[];
82
87
  returns: (CdpField & {
83
88
  optional: boolean;
84
89
  })[];
90
+ redirect?: {
91
+ method: string;
92
+ resolved: boolean;
93
+ parameters: CdpParameter[];
94
+ } | undefined;
85
95
  example?: {
86
96
  command: string;
87
97
  params?: Record<string, unknown> | undefined;
88
98
  } | undefined;
89
99
  }
100
+ /** `bdg cdp <Domain.Type> --describe` result */
101
+ export interface CdpTypeDescription extends ProtocolEntry {
102
+ type: 'type';
103
+ name: string;
104
+ domain: string;
105
+ id: string;
106
+ baseType: string;
107
+ enum?: string[] | undefined;
108
+ items?: string | undefined;
109
+ properties?: CdpParameter[] | undefined;
110
+ }
90
111
  /** `bdg cdp <Domain.method>` result */
91
112
  export interface CdpExecuteData {
92
113
  method: string;
@@ -114,12 +135,14 @@ export declare function formatCdpDomains(data: CdpDomainListData): string;
114
135
  */
115
136
  export declare function formatCdpDomainMethods(data: CdpDomainMethodsData): string;
116
137
  /**
117
- * Format `bdg cdp <Domain.method> --describe` or `bdg cdp <Domain> --describe`.
138
+ * Format `bdg cdp <Domain.method> --describe`, `bdg cdp <Domain> --describe`
139
+ * or `bdg cdp <Domain.Type> --describe`.
118
140
  *
119
- * @param data - Method or domain description
120
- * @returns Description, parameters (`?` = optional), returns, note and example
141
+ * @param data - Method, domain or type description
142
+ * @returns Description, parameters (`?` = optional, `$ref` enums inline),
143
+ * the redirect target's parameters, returns, note and example
121
144
  */
122
- export declare function formatCdpDescription(data: CdpMethodDescription | CdpDomainDescription): string;
145
+ export declare function formatCdpDescription(data: CdpMethodDescription | CdpDomainDescription | CdpTypeDescription): string;
123
146
  /**
124
147
  * Whether a CDP method returned nothing (null, undefined or an empty object).
125
148
  *
@@ -3,6 +3,7 @@
3
3
  * lists, method schemas and method results (`--json` prints the data as is).
4
4
  */
5
5
  import { joinLines, pluralize } from '../formatting.js';
6
+ import { cdpRedirectTitle, cdpUnresolvedRedirectLine } from '../messages/commands.js';
6
7
  /**
7
8
  * First sentence (or line) of a protocol description.
8
9
  *
@@ -43,6 +44,24 @@ function columns(rows) {
43
44
  function typeText(field) {
44
45
  return field.items ? `${field.type}<${field.items}>` : field.type;
45
46
  }
47
+ /** Enum values listed in text before the rest are counted */
48
+ const MAX_ENUM_VALUES = 8;
49
+ /**
50
+ * What a field's type stands for, in parentheses: enum values (the first
51
+ * {@link MAX_ENUM_VALUES}), or the base type of a referenced non-object type.
52
+ *
53
+ * @param field - Field with its inline or referenced enum and referenced base type
54
+ * @returns e.g. " (Strict|Lax|None)", " (number)", or an empty string
55
+ */
56
+ function expansionText(field) {
57
+ const values = field.enum;
58
+ if (!values || values.length === 0) {
59
+ return field.refType && field.refType !== 'object' ? ` (${field.refType})` : '';
60
+ }
61
+ const shown = values.slice(0, MAX_ENUM_VALUES);
62
+ const more = values.length - shown.length;
63
+ return ` (${[...shown, ...(more > 0 ? [`… ${more} more`] : [])].join('|')})`;
64
+ }
46
65
  /**
47
66
  * Format `bdg cdp --search <query>`.
48
67
  *
@@ -89,22 +108,74 @@ function fieldSection(title, fields) {
89
108
  return [
90
109
  `${title}:`,
91
110
  ...columns(fields.map((f) => [
92
- `${f.name}${f.optional ? '?' : ''}: ${typeText(f)}`,
93
- (f.description ?? '').replace(/\s*\n\s*/g, ' '),
111
+ `${f.name}${f.optional ? '?' : ''}: ${typeText(f)}${expansionText(f)}`,
112
+ `${(f.description ?? '').replace(/\s*\n\s*/g, ' ')}${tags(f)}`,
94
113
  ])),
95
114
  ];
96
115
  }
97
116
  /**
98
- * Format `bdg cdp <Domain.method> --describe` or `bdg cdp <Domain> --describe`.
117
+ * Parameters as fields with `?` for optional ones.
118
+ *
119
+ * @param parameters - Parameters or type properties
120
+ * @returns Fields for {@link fieldSection}
121
+ */
122
+ function parameterFields(parameters) {
123
+ return parameters.map((p) => ({ ...p, optional: !p.required }));
124
+ }
125
+ /**
126
+ * How to describe the first object type a parameter refers to (enums and
127
+ * other types are already shown inline).
128
+ *
129
+ * @param parameters - Parameters, the redirect target's included
130
+ * @returns Hint line, or undefined when no parameter refers to such a type
131
+ */
132
+ function typeHint(parameters) {
133
+ const ref = parameters.find((p) => p.refType === 'object')?.ref;
134
+ return ref && `Describe a type: bdg cdp ${ref} --describe`;
135
+ }
136
+ /**
137
+ * Lines naming the method a redirected one runs, with its parameters, or
138
+ * saying the protocol lacks it.
139
+ *
140
+ * @param redirect - Redirect target, if any
141
+ * @returns Title and parameter lines, or nothing without a redirect
142
+ */
143
+ function redirectSection(redirect) {
144
+ if (!redirect)
145
+ return [];
146
+ if (!redirect.resolved)
147
+ return [cdpUnresolvedRedirectLine(redirect.method)];
148
+ const title = cdpRedirectTitle(redirect.method);
149
+ return redirect.parameters.length === 0
150
+ ? [title]
151
+ : fieldSection(`${title}, with these parameters`, parameterFields(redirect.parameters));
152
+ }
153
+ /**
154
+ * Format `bdg cdp <Domain.Type> --describe`.
155
+ *
156
+ * @param data - Type description
157
+ * @returns Base type, description and values or properties
158
+ */
159
+ function formatCdpType(data) {
160
+ const properties = data.properties ?? [];
161
+ return joinLines(`${data.name}: ${typeText({ name: data.id, type: data.baseType, items: data.items })}${tags(data)}`, data.description, data.enum && `Values: ${data.enum.join(', ')}`, ...fieldSection('Properties', parameterFields(properties)), typeHint(properties));
162
+ }
163
+ /**
164
+ * Format `bdg cdp <Domain.method> --describe`, `bdg cdp <Domain> --describe`
165
+ * or `bdg cdp <Domain.Type> --describe`.
99
166
  *
100
- * @param data - Method or domain description
101
- * @returns Description, parameters (`?` = optional), returns, note and example
167
+ * @param data - Method, domain or type description
168
+ * @returns Description, parameters (`?` = optional, `$ref` enums inline),
169
+ * the redirect target's parameters, returns, note and example
102
170
  */
103
171
  export function formatCdpDescription(data) {
172
+ if (data.type === 'type')
173
+ return formatCdpType(data);
104
174
  if (data.type === 'domain') {
105
175
  return joinLines(`${data.domain}: ${pluralize(data.commands, 'method')}, ${pluralize(data.events, 'event')}${tags(data)}`, data.description, data.note, data.nextStep);
106
176
  }
107
- return joinLines(`${data.name}${tags(data)}`, data.description, ...fieldSection('Parameters', data.parameters.map((p) => ({ ...p, optional: !p.required }))), ...fieldSection('Returns', data.returns), data.note && `Note: ${data.note}`, data.example && `Example: ${data.example.command}`);
177
+ const redirected = data.redirect?.parameters ?? [];
178
+ return joinLines(`${data.name}${tags(data)}`, data.description, ...fieldSection('Parameters', parameterFields(data.parameters)), ...redirectSection(data.redirect), ...fieldSection('Returns', data.returns), data.note && `Note: ${data.note}`, typeHint([...data.parameters, ...redirected]), data.example && `Example: ${data.example.command}`);
108
179
  }
109
180
  /**
110
181
  * Whether a CDP method returned nothing (null, undefined or an empty object).
@@ -2,7 +2,7 @@ import { skippedBodyReason } from '../../telemetry/networkRetention.js';
2
2
  import { formatFramePosition, formatTimestamp } from './console/shared.js';
3
3
  import { headerValueLines } from './networkHeaders.js';
4
4
  import { formatRequestStatus } from './requestStatus.js';
5
- import { OutputFormatter } from '../formatting.js';
5
+ import { OutputFormatter, formatBytes } from '../formatting.js';
6
6
  import { localProxyNote } from '../messages/networkMessages.js';
7
7
  import { sessionCommand } from '../messages/sessionCommand.js';
8
8
  import { truncateByLength } from '../../utils/strings.js';
@@ -85,19 +85,6 @@ function addWebSocketMessages(fmt, webSocket) {
85
85
  }
86
86
  /** Characters of a text body shown in human output (`--json` has all of it) */
87
87
  const BODY_PREVIEW_LENGTH = 20000;
88
- /**
89
- * Format a byte count for humans.
90
- *
91
- * @param bytes - Byte count
92
- * @returns e.g. "512 B", "12.3 KB"
93
- */
94
- function formatBytes(bytes) {
95
- if (bytes < 1024)
96
- return `${bytes} B`;
97
- return bytes < 1024 * 1024
98
- ? `${(bytes / 1024).toFixed(1)} KB`
99
- : `${(bytes / 1024 / 1024).toFixed(1)} MB`;
100
- }
101
88
  /**
102
89
  * Whether an IP address is a loopback address (`127.0.0.0/8`, `::1`).
103
90
  *
@@ -229,7 +216,12 @@ export function formatNetworkDetails(request) {
229
216
  fmt.blank();
230
217
  if (request.requestHeaders)
231
218
  addHeaders(fmt, 'Request Headers:', request.requestHeaders);
232
- if (request.requestBody) {
219
+ if (request.requestBodyNotCaptured) {
220
+ fmt.text('Request Body:').separator('━', 70);
221
+ fmt.text(`(not captured: ${request.requestBodyNotCaptured})`);
222
+ fmt.blank();
223
+ }
224
+ else if (request.requestBody) {
233
225
  fmt.text('Request Body:').separator('━', 70);
234
226
  fmt.text(request.requestBody);
235
227
  fmt.blank();
@@ -57,6 +57,8 @@ export interface PreviewJsonData {
57
57
  totals?: BdgOutput['totals'];
58
58
  /** When the page's renderer crashed (epoch ms), while it is not loaded again */
59
59
  pageCrashedAt?: number;
60
+ /** Downloads that began during the session, oldest first */
61
+ downloads?: BdgOutput['downloads'];
60
62
  network?: BdgOutput['data']['network'];
61
63
  console?: BdgOutput['data']['console'];
62
64
  }
@@ -4,7 +4,7 @@ import { capMessageText, formatTimestamp } from './console/shared.js';
4
4
  import { capForDisplay } from './longValues.js';
5
5
  import { failureReason, formatRequestStatus, getRequestState, } from './requestStatus.js';
6
6
  import { OutputFormatter, truncateUrl, truncateText } from '../formatting.js';
7
- import { moreCharsNote, withPageCrashedNote } from '../messages/commands.js';
7
+ import { downloadsSummary, moreCharsNote, withPageCrashedNote } from '../messages/commands.js';
8
8
  import { consoleDroppedNote } from '../messages/consoleMessages.js';
9
9
  import { networkEvictedNote } from '../messages/networkMessages.js';
10
10
  import { PREVIEW_EMPTY_STATES, PREVIEW_HEADERS, compactTipsMessage, verboseCommandsMessage, } from '../messages/preview.js';
@@ -76,6 +76,7 @@ export function buildPreviewJsonData(output, options) {
76
76
  ...(output.partial !== undefined && { partial: output.partial }),
77
77
  ...(output.totals && { totals: output.totals }),
78
78
  ...(output.pageCrashedAt !== undefined && { pageCrashedAt: output.pageCrashedAt }),
79
+ ...(output.downloads && { downloads: output.downloads }),
79
80
  ...(pick('network') && output.data.network && { network: last(output.data.network) }),
80
81
  ...(pick('console') &&
81
82
  output.data.console && {
@@ -194,6 +195,8 @@ function formatPreviewCompact(output, options) {
194
195
  fmt.blank();
195
196
  }
196
197
  }
198
+ if (output.downloads?.length)
199
+ fmt.text(`Downloads: ${downloadsSummary(output.downloads)}`).blank();
197
200
  if (!options.follow) {
198
201
  fmt.tip(compactTipsMessage());
199
202
  }
@@ -296,6 +299,9 @@ function formatPreviewVerbose(output, options) {
296
299
  fmt.blank();
297
300
  }
298
301
  }
302
+ if (output.downloads?.length) {
303
+ fmt.keyValue('Downloads', downloadsSummary(output.downloads), 18).blank();
304
+ }
299
305
  if (!options.follow) {
300
306
  fmt.tip(verboseCommandsMessage());
301
307
  }
@@ -1,7 +1,7 @@
1
1
  import { describeRunningChrome } from '../../session/chrome.js';
2
2
  import { calculateDuration, formatTimeAgo } from '../../session/statusData.js';
3
3
  import { OutputFormatter } from '../formatting.js';
4
- import { colorSchemeLabel, sessionActiveLine } from '../messages/commands.js';
4
+ import { colorSchemeLabel, downloadsSummary, sessionActiveLine } from '../messages/commands.js';
5
5
  import { networkEvictedNote } from '../messages/networkMessages.js';
6
6
  import { lastSessionEndText } from '../messages/session.js';
7
7
  import { noActiveSessionMessage, sessionCommand } from '../messages/sessionCommand.js';
@@ -62,6 +62,11 @@ export function formatSessionStatus(metadata, pid, activity, pageState, verbose
62
62
  if (activity.lastConsoleMessageAt) {
63
63
  fmt.keyValue(' Last Message', formatTimeAgo(activity.lastConsoleMessageAt), 18);
64
64
  }
65
+ const downloadRows = [
66
+ ...(activity.downloads?.length ? [downloadsSummary(activity.downloads)] : []),
67
+ ...(activity.downloadsWarning ? [`⚠ ${activity.downloadsWarning}`] : []),
68
+ ];
69
+ downloadRows.forEach((row, index) => index === 0 ? fmt.keyValue('Downloads', row, 18) : fmt.text(`${' '.repeat(18)}${row}`));
65
70
  }
66
71
  fmt.blank().text('Collectors').separator('━', 50);
67
72
  const activeTelemetry = metadata.activeTelemetry ?? ['network', 'console', 'dom'];