browser-debugger-cli 0.14.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 (167) hide show
  1. package/.claude/skills/bdg/SKILL.md +3 -2
  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 +201 -133
  11. package/dist/commands/cleanup.js +21 -4
  12. package/dist/commands/dom/eval.d.ts +2 -1
  13. package/dist/commands/dom/eval.js +6 -21
  14. package/dist/commands/dom/formInteraction.js +8 -4
  15. package/dist/commands/dom/helpers/evalResult.d.ts +36 -0
  16. package/dist/commands/dom/helpers/evalResult.js +59 -0
  17. package/dist/commands/dom/helpers/index.d.ts +4 -4
  18. package/dist/commands/dom/helpers/index.js +3 -3
  19. package/dist/commands/dom/helpers/query.d.ts +2 -2
  20. package/dist/commands/dom/helpers/query.js +2 -2
  21. package/dist/commands/dom/helpers/screenshot.d.ts +21 -26
  22. package/dist/commands/dom/helpers/screenshot.js +50 -668
  23. package/dist/commands/dom/screenshot.js +56 -36
  24. package/dist/commands/helpJson.d.ts +1 -1
  25. package/dist/commands/helpJson.js +3 -3
  26. package/dist/commands/helpTopic.js +10 -4
  27. package/dist/commands/network/har.js +18 -14
  28. package/dist/commands/optionBehaviors.js +24 -9
  29. package/dist/commands/shared/CommandRunner.d.ts +5 -0
  30. package/dist/commands/shared/CommandRunner.js +18 -3
  31. package/dist/commands/shared/interrupt.d.ts +40 -0
  32. package/dist/commands/shared/interrupt.js +73 -0
  33. package/dist/commands/shared/optionTypes.d.ts +3 -0
  34. package/dist/commands/shared/outputFile.d.ts +2 -1
  35. package/dist/commands/shared/outputFile.js +7 -4
  36. package/dist/commands/shared/startHelpers.d.ts +26 -3
  37. package/dist/commands/shared/startHelpers.js +145 -23
  38. package/dist/commands/status.js +3 -1
  39. package/dist/commands/stop.js +2 -1
  40. package/dist/commands/types.d.ts +5 -0
  41. package/dist/connection/cdp.js +1 -16
  42. package/dist/connection/chromeIdentity.d.ts +24 -5
  43. package/dist/connection/chromeIdentity.js +53 -22
  44. package/dist/connection/launcher/flagsBuilder.d.ts +46 -0
  45. package/dist/connection/launcher/flagsBuilder.js +107 -23
  46. package/dist/connection/launcher.d.ts +35 -2
  47. package/dist/connection/launcher.js +99 -12
  48. package/dist/connection/typed-cdp.d.ts +3 -2
  49. package/dist/constants.d.ts +3 -5
  50. package/dist/constants.js +3 -5
  51. package/dist/daemon/SessionController.d.ts +10 -5
  52. package/dist/daemon/SessionController.js +15 -8
  53. package/dist/daemon/ipcServer.js +1 -1
  54. package/dist/daemon/launcher.d.ts +22 -3
  55. package/dist/daemon/launcher.js +45 -8
  56. package/dist/daemon/session/Session.d.ts +5 -1
  57. package/dist/daemon/session/Session.js +9 -8
  58. package/dist/daemon/session/TelemetryStore.d.ts +5 -0
  59. package/dist/daemon/session/TelemetryStore.js +4 -0
  60. package/dist/daemon/session/captureGate.d.ts +59 -0
  61. package/dist/daemon/session/captureGate.js +96 -0
  62. package/dist/daemon/session/chromeConnection.d.ts +16 -1
  63. package/dist/daemon/session/chromeConnection.js +34 -4
  64. package/dist/daemon/session/collectors.d.ts +15 -0
  65. package/dist/daemon/session/collectors.js +39 -2
  66. package/dist/daemon/session/commandRegistry.d.ts +14 -1
  67. package/dist/daemon/session/commandRegistry.js +48 -13
  68. package/dist/daemon/session/downloads.d.ts +32 -0
  69. package/dist/daemon/session/downloads.js +96 -0
  70. package/dist/daemon/session/interactions.d.ts +3 -2
  71. package/dist/daemon/session/interactions.js +7 -2
  72. package/dist/daemon/session/plugins.js +6 -0
  73. package/dist/daemon.js +18520 -17014
  74. package/dist/errors/CommandError.d.ts +2 -0
  75. package/dist/errors/issues.d.ts +1 -1
  76. package/dist/errors/messages.d.ts +81 -0
  77. package/dist/errors/messages.js +198 -6
  78. package/dist/index.js +1446 -1078
  79. package/dist/ipc/client.d.ts +20 -2
  80. package/dist/ipc/client.js +32 -6
  81. package/dist/ipc/protocol/commands.d.ts +36 -2
  82. package/dist/ipc/protocol/commands.js +1 -0
  83. package/dist/ipc/protocol/domTypes.d.ts +24 -1
  84. package/dist/ipc/session/queries.d.ts +3 -0
  85. package/dist/ipc/session/types.d.ts +5 -0
  86. package/dist/ipc/transport/IPCError.d.ts +9 -0
  87. package/dist/ipc/transport/IPCError.js +12 -0
  88. package/dist/ipc/transport/errors.d.ts +2 -1
  89. package/dist/ipc/transport/errors.js +4 -1
  90. package/dist/ipc/transport/index.d.ts +10 -2
  91. package/dist/ipc/transport/index.js +29 -4
  92. package/dist/runtime/dom/actionEffects.d.ts +48 -9
  93. package/dist/runtime/dom/actionEffects.js +269 -34
  94. package/dist/runtime/dom/actionEffectsScripts.d.ts +45 -0
  95. package/dist/runtime/dom/actionEffectsScripts.js +101 -2
  96. package/dist/runtime/dom/captureArea.d.ts +35 -0
  97. package/dist/runtime/dom/captureArea.js +203 -0
  98. package/dist/runtime/dom/elementInfo.d.ts +13 -4
  99. package/dist/runtime/dom/elementInfo.js +12 -3
  100. package/dist/runtime/dom/evalHelpers.d.ts +24 -4
  101. package/dist/runtime/dom/evalHelpers.js +40 -12
  102. package/dist/runtime/dom/formDiscovery.d.ts +1 -1
  103. package/dist/runtime/dom/frames.d.ts +2 -1
  104. package/dist/runtime/dom/frames.js +3 -1
  105. package/dist/runtime/page/bdgWorld.d.ts +9 -0
  106. package/dist/runtime/page/bdgWorld.js +11 -0
  107. package/dist/runtime/page/captureEmulation.d.ts +119 -0
  108. package/dist/runtime/page/captureEmulation.js +189 -0
  109. package/dist/runtime/page/captureScroll.d.ts +24 -0
  110. package/dist/runtime/page/captureScroll.js +124 -0
  111. package/dist/runtime/page/emulation.js +6 -5
  112. package/dist/runtime/page/screenshot.d.ts +41 -0
  113. package/dist/runtime/page/screenshot.js +394 -0
  114. package/dist/runtime/page/userAgent.d.ts +86 -2
  115. package/dist/runtime/page/userAgent.js +154 -33
  116. package/dist/session/paths.d.ts +52 -3
  117. package/dist/session/paths.js +179 -7
  118. package/dist/session/portClaims.d.ts +0 -8
  119. package/dist/session/portClaims.js +1 -22
  120. package/dist/session/sessionList.d.ts +5 -1
  121. package/dist/session/sessionList.js +5 -1
  122. package/dist/telemetry/downloads.d.ts +127 -0
  123. package/dist/telemetry/downloads.js +265 -0
  124. package/dist/telemetry/har/builder.d.ts +12 -1
  125. package/dist/telemetry/har/builder.js +32 -9
  126. package/dist/telemetry/har/sanitize.d.ts +28 -0
  127. package/dist/telemetry/har/sanitize.js +184 -0
  128. package/dist/telemetry/har/sanitizeBody.d.ts +78 -0
  129. package/dist/telemetry/har/sanitizeBody.js +541 -0
  130. package/dist/telemetry/har/types.d.ts +2 -0
  131. package/dist/telemetry/network.d.ts +4 -4
  132. package/dist/telemetry/network.js +38 -4
  133. package/dist/telemetry/networkRetention.d.ts +35 -14
  134. package/dist/telemetry/networkRetention.js +62 -26
  135. package/dist/types.d.ts +9 -14
  136. package/dist/ui/OutputBuilder.d.ts +3 -2
  137. package/dist/ui/OutputBuilder.js +4 -3
  138. package/dist/ui/formatters/cdp.d.ts +32 -9
  139. package/dist/ui/formatters/cdp.js +77 -6
  140. package/dist/ui/formatters/details.js +7 -15
  141. package/dist/ui/formatters/preview.d.ts +2 -0
  142. package/dist/ui/formatters/preview.js +7 -1
  143. package/dist/ui/formatters/sessions.d.ts +3 -2
  144. package/dist/ui/formatters/sessions.js +10 -3
  145. package/dist/ui/formatters/status.js +6 -1
  146. package/dist/ui/formatting.d.ts +7 -0
  147. package/dist/ui/formatting.js +13 -0
  148. package/dist/ui/logging/logger.d.ts +1 -1
  149. package/dist/ui/messages/chrome.d.ts +27 -6
  150. package/dist/ui/messages/chrome.js +78 -12
  151. package/dist/ui/messages/commands.d.ts +71 -3
  152. package/dist/ui/messages/commands.js +98 -3
  153. package/dist/ui/messages/networkMessages.d.ts +50 -5
  154. package/dist/ui/messages/networkMessages.js +50 -6
  155. package/dist/ui/messages/session.d.ts +8 -0
  156. package/dist/ui/messages/session.js +10 -0
  157. package/dist/utils/async.d.ts +3 -2
  158. package/dist/utils/async.js +16 -3
  159. package/dist/utils/atomicFile.d.ts +2 -1
  160. package/dist/utils/atomicFile.js +5 -2
  161. package/dist/utils/directories.d.ts +41 -0
  162. package/dist/utils/directories.js +48 -0
  163. package/dist/utils/http.d.ts +11 -4
  164. package/dist/utils/http.js +5 -3
  165. package/package.json +18 -4
  166. /package/dist/{commands/dom → runtime/page}/screenshotResize.d.ts +0 -0
  167. /package/dist/{commands/dom → runtime/page}/screenshotResize.js +0 -0
@@ -15,18 +15,41 @@ function megabytes(bytes) {
15
15
  return `${Math.round(bytes / (1024 * 1024))} MB`;
16
16
  }
17
17
  /**
18
- * Why a stored response body was replaced by a placeholder (shown by
19
- * `bdg details network <id>` as `bodyNotCaptured`).
18
+ * Why a stored request or response body was replaced by a placeholder
19
+ * (shown by `bdg details network <id>` as `requestBodyNotCaptured` or
20
+ * `bodyNotCaptured`).
20
21
  *
21
22
  * @param budgetBytes - Total body budget of the session
22
- * @returns e.g. `evicted: total body budget (bdg keeps the newest 100 MB of response bodies)`
23
+ * @returns e.g. `evicted: total body budget (bdg keeps the newest 100 MB of request and response bodies)`
23
24
  */
24
25
  export function bodyEvictedReason(budgetBytes) {
25
- return `evicted: total body budget (bdg keeps the newest ${megabytes(budgetBytes)} of response bodies)`;
26
+ return `evicted: total body budget (bdg keeps the newest ${megabytes(budgetBytes)} of request and response bodies)`;
27
+ }
28
+ /**
29
+ * Why a response body is missing when Chrome no longer had it
30
+ * (`Network.getResponseBody` failed with "No resource with given identifier
31
+ * found" or "No data found for resource with given identifier"), shown by
32
+ * `bdg details network <id>` as `bodyNotCaptured` and in the HAR as the
33
+ * content comment.
34
+ *
35
+ * @returns Reason text
36
+ */
37
+ export function bodyGoneReason() {
38
+ return 'Chrome no longer had the body (its network buffer evicted it, or the request was cancelled)';
39
+ }
40
+ /**
41
+ * Why a response body is missing when `Network.getResponseBody` failed for
42
+ * another reason (a CDP timeout, the connection closing mid-fetch).
43
+ *
44
+ * @param errorMessage - The error Chrome or the connection gave
45
+ * @returns e.g. `Chrome did not return the body: CDP command timeout`
46
+ */
47
+ export function bodyFetchFailedReason(errorMessage) {
48
+ return `Chrome did not return the body: ${errorMessage}`;
26
49
  }
27
50
  /**
28
51
  * Note that the session dropped its oldest requests or evicted its oldest
29
- * response bodies at its limits.
52
+ * request and response bodies at its limits.
30
53
  *
31
54
  * @param counts - Requests dropped and bodies evicted
32
55
  * @returns e.g. `⚠ 2000 older network requests were dropped: bdg keeps the newest 10000`;
@@ -35,7 +58,7 @@ export function bodyEvictedReason(budgetBytes) {
35
58
  export function networkEvictedNote(counts) {
36
59
  const { requestsDropped, bodiesEvicted } = counts;
37
60
  const requests = pluralize(requestsDropped, 'older network request');
38
- const bodies = pluralize(bodiesEvicted, 'older response body', 'older response bodies');
61
+ const bodies = pluralize(bodiesEvicted, 'older request/response body', 'older request/response bodies');
39
62
  const budget = megabytes(MAX_TOTAL_BODY_BYTES);
40
63
  if (requestsDropped > 0 && bodiesEvicted > 0) {
41
64
  return `⚠ ${requests} dropped, ${bodies} evicted: bdg keeps the newest ${MAX_NETWORK_REQUESTS} requests and ${budget} of bodies`;
@@ -82,4 +105,25 @@ export function headerRepeatedNote(count) {
82
105
  export function localProxyNote() {
83
106
  return '(loopback; likely a local proxy)';
84
107
  }
108
+ /**
109
+ * HAR log comment of a sanitized export.
110
+ *
111
+ * @returns Comment naming what was redacted and the flag that keeps it
112
+ */
113
+ export function harSanitizedComment() {
114
+ return 'Sanitized by bdg: values of auth, cookie, API key, token and session headers, cookies, credential query parameters in URLs, password/token/secret fields of JSON and form request and response bodies and WebSocket messages (also truncated JSON, JSON encoded in strings, socket.io, SockJS, server-sent events, NDJSON, and base64 bodies and binary messages that are UTF-8 text), and any JWT (also inside longer strings and form values) are [redacted] in place (by name, so some harmless values are too); everything else stays byte for byte. Not sanitized: other binary data, text that is not JSON or a form, and non-JSON syntax (single quotes, unquoted keys, JSONP, bare values with spaces); a body that could not be sanitized is [redacted] whole. Export with --include-sensitive to keep everything';
115
+ }
116
+ /**
117
+ * Success message of `bdg network har`.
118
+ *
119
+ * @param result - Export result
120
+ * @returns e.g. `✓ Exported 4 requests to /tmp/out.har` and a line on sanitization
121
+ */
122
+ export function harExportedMessage(result) {
123
+ const filterNote = result.filtered ? ' (filtered)' : '';
124
+ const note = result.sanitized
125
+ ? 'Credentials sanitized (auth/cookie/API key/token headers, cookies, URL tokens, password and token fields of bodies and WebSocket messages are [redacted]); --include-sensitive keeps them'
126
+ : '⚠ Includes credentials (--include-sensitive): share this file with care';
127
+ return `✓ Exported ${result.entries} requests${filterNote} to ${result.file}\n ${note}`;
128
+ }
85
129
  //# sourceMappingURL=networkMessages.js.map
@@ -88,6 +88,14 @@ export declare function endedSessionText(label: string, end: {
88
88
  reason: string;
89
89
  endedAt: number;
90
90
  }): string;
91
+ /**
92
+ * A session whose directory is not safe to use, for `bdg sessions`.
93
+ *
94
+ * @param label - Session name as listed
95
+ * @param why - Untrusted directory and why
96
+ * @returns One line
97
+ */
98
+ export declare function untrustedSessionText(label: string, why: string): string;
91
99
  /**
92
100
  * Note after a failed start or a stop whose daemon had not exited when bdg stopped waiting.
93
101
  *
@@ -101,6 +101,16 @@ export function lastSessionEndText(end) {
101
101
  export function endedSessionText(label, end) {
102
102
  return `${label} ended ${sessionEndText(end)}`;
103
103
  }
104
+ /**
105
+ * A session whose directory is not safe to use, for `bdg sessions`.
106
+ *
107
+ * @param label - Session name as listed
108
+ * @param why - Untrusted directory and why
109
+ * @returns One line
110
+ */
111
+ export function untrustedSessionText(label, why) {
112
+ return `${label}: ${why}`;
113
+ }
104
114
  /**
105
115
  * When and why a session ended without `bdg stop`.
106
116
  *
@@ -2,11 +2,12 @@
2
2
  * Async utilities for common patterns.
3
3
  */
4
4
  /**
5
- * Delay execution for a specified duration.
5
+ * Delay execution for a specified duration, ending early when `signal` aborts.
6
6
  *
7
7
  * @param ms - Milliseconds to delay
8
+ * @param signal - Optional abort signal (the delay resolves, not rejects, on abort)
8
9
  */
9
- export declare function delay(ms: number): Promise<void>;
10
+ export declare function delay(ms: number, signal?: AbortSignal): Promise<void>;
10
11
  /**
11
12
  * Wait for a promise, giving up after a time; the timer is cleared either way.
12
13
  *
@@ -2,12 +2,25 @@
2
2
  * Async utilities for common patterns.
3
3
  */
4
4
  /**
5
- * Delay execution for a specified duration.
5
+ * Delay execution for a specified duration, ending early when `signal` aborts.
6
6
  *
7
7
  * @param ms - Milliseconds to delay
8
+ * @param signal - Optional abort signal (the delay resolves, not rejects, on abort)
8
9
  */
9
- export function delay(ms) {
10
- return new Promise((resolve) => setTimeout(resolve, ms));
10
+ export function delay(ms, signal) {
11
+ return new Promise((resolve) => {
12
+ if (signal?.aborted) {
13
+ resolve();
14
+ return;
15
+ }
16
+ const done = () => {
17
+ clearTimeout(timer);
18
+ signal?.removeEventListener('abort', done);
19
+ resolve();
20
+ };
21
+ const timer = setTimeout(done, ms);
22
+ signal?.addEventListener('abort', done, { once: true });
23
+ });
11
24
  }
12
25
  /**
13
26
  * Wait for a promise, giving up after a time; the timer is cleared either way.
@@ -38,12 +38,13 @@ export declare class AtomicFileWriter {
38
38
  *
39
39
  * @param filePath - Target file path
40
40
  * @param data - Data to write
41
- * @param options - Write options
41
+ * @param options - Write options (`mode`: permissions of the new file, less the umask)
42
42
  * @returns Promise that resolves when write completes
43
43
  * @throws Error if write operation fails
44
44
  */
45
45
  static writeAsync(filePath: string, data: string, options?: {
46
46
  encoding?: BufferEncoding;
47
+ mode?: number;
47
48
  }): Promise<void>;
48
49
  /**
49
50
  * Write binary data (Buffer) to a file atomically (asynchronous).
@@ -59,14 +59,17 @@ export class AtomicFileWriter {
59
59
  *
60
60
  * @param filePath - Target file path
61
61
  * @param data - Data to write
62
- * @param options - Write options
62
+ * @param options - Write options (`mode`: permissions of the new file, less the umask)
63
63
  * @returns Promise that resolves when write completes
64
64
  * @throws Error if write operation fails
65
65
  */
66
66
  static async writeAsync(filePath, data, options = {}) {
67
67
  const tmpPath = this.getTempPath(filePath);
68
68
  try {
69
- await fs.promises.writeFile(tmpPath, data, { encoding: options.encoding ?? 'utf-8' });
69
+ await fs.promises.writeFile(tmpPath, data, {
70
+ encoding: options.encoding ?? 'utf-8',
71
+ ...(options.mode !== undefined && { mode: options.mode }),
72
+ });
70
73
  await fs.promises.rename(tmpPath, filePath);
71
74
  }
72
75
  catch (error) {
@@ -31,4 +31,45 @@ export declare function directoryProblem(dir: string): DirectoryProblem | null;
31
31
  * file on the path), `EACCES` (not writable), or the `mkdir` error
32
32
  */
33
33
  export declare function makeDirectory(dir: string, mode?: number): void;
34
+ /**
35
+ * Kind of untrusted directory: a symlink, not a directory, another user's,
36
+ * a shared sticky directory such as `/tmp` (others may create entries in
37
+ * it), or writable by others
38
+ */
39
+ export type DirTrustKind = 'symlink' | 'not-directory' | 'owner' | 'shared' | 'writable';
40
+ /** Why a directory cannot be trusted */
41
+ export interface DirTrustProblem {
42
+ /** e.g. `owned by uid 1001`, `writable by others (mode 777)` */
43
+ reason: string;
44
+ kind: DirTrustKind;
45
+ }
46
+ /** Options of {@link dirTrustProblem} */
47
+ export interface DirTrustOptions {
48
+ /**
49
+ * Accept a directory its group may write to (only others' write access is
50
+ * refused): under umask 002, common with per-user groups, directories are
51
+ * created 0775 and the group holds only the user
52
+ */
53
+ allowGroupWrite?: boolean;
54
+ }
55
+ /**
56
+ * Whether a path is a directory the current user can trust: a real directory
57
+ * (not a symlink), owned by the user, not writable by group or others (by
58
+ * others only, with `allowGroupWrite`).
59
+ *
60
+ * @param dir - Existing path
61
+ * @param options - What else to accept
62
+ * @returns Why it cannot be trusted, or null if it can
63
+ * @throws Error from `lstat` (e.g. `ENOENT` for a missing path)
64
+ */
65
+ export declare function dirTrustProblem(dir: string, options?: DirTrustOptions): DirTrustProblem | null;
66
+ /**
67
+ * Strict form of {@link dirTrustProblem}: not a symlink, owned by the user,
68
+ * not writable by group or others.
69
+ *
70
+ * @param dir - Existing path
71
+ * @returns Why it cannot be trusted, or null if it can
72
+ * @throws Error from `lstat` (e.g. `ENOENT` for a missing path)
73
+ */
74
+ export declare function untrustedDirReason(dir: string): string | null;
34
75
  //# sourceMappingURL=directories.d.ts.map
@@ -85,4 +85,52 @@ export function makeDirectory(dir, mode) {
85
85
  }
86
86
  fs.mkdirSync(dir, { recursive: true, ...(mode !== undefined && { mode }) });
87
87
  }
88
+ /** Permission bits that let group or others write */
89
+ const GROUP_OTHER_WRITE = 0o022;
90
+ /** Permission bit that lets others (not the group) write */
91
+ const OTHER_WRITE = 0o002;
92
+ /** Sticky bit: a shared directory like `/tmp` where only owners remove their entries */
93
+ const STICKY = 0o1000;
94
+ /**
95
+ * Whether a path is a directory the current user can trust: a real directory
96
+ * (not a symlink), owned by the user, not writable by group or others (by
97
+ * others only, with `allowGroupWrite`).
98
+ *
99
+ * @param dir - Existing path
100
+ * @param options - What else to accept
101
+ * @returns Why it cannot be trusted, or null if it can
102
+ * @throws Error from `lstat` (e.g. `ENOENT` for a missing path)
103
+ */
104
+ export function dirTrustProblem(dir, options = {}) {
105
+ const stat = fs.lstatSync(dir);
106
+ const problem = (reason, kind) => ({ reason, kind });
107
+ if (stat.isSymbolicLink())
108
+ return problem('it is a symbolic link', 'symlink');
109
+ if (!stat.isDirectory())
110
+ return problem('not a directory', 'not-directory');
111
+ const uid = process.getuid?.();
112
+ if (uid !== undefined && stat.uid !== uid)
113
+ return problem(`owned by uid ${stat.uid}`, 'owner');
114
+ if (process.platform === 'win32')
115
+ return null;
116
+ const writeBits = options.allowGroupWrite ? OTHER_WRITE : GROUP_OTHER_WRITE;
117
+ if ((stat.mode & writeBits) === 0)
118
+ return null;
119
+ const mode = (stat.mode & 0o7777).toString(8);
120
+ if ((stat.mode & STICKY) !== 0 && (stat.mode & OTHER_WRITE) !== 0) {
121
+ return problem(`a shared sticky directory (mode ${mode})`, 'shared');
122
+ }
123
+ return problem(`writable by others (mode ${mode})`, 'writable');
124
+ }
125
+ /**
126
+ * Strict form of {@link dirTrustProblem}: not a symlink, owned by the user,
127
+ * not writable by group or others.
128
+ *
129
+ * @param dir - Existing path
130
+ * @returns Why it cannot be trusted, or null if it can
131
+ * @throws Error from `lstat` (e.g. `ENOENT` for a missing path)
132
+ */
133
+ export function untrustedDirReason(dir) {
134
+ return dirTrustProblem(dir)?.reason ?? null;
135
+ }
88
136
  //# sourceMappingURL=directories.js.map
@@ -37,6 +37,13 @@ export interface FetchCDPTargetsOptions {
37
37
  * - Network errors are logged to help diagnose Chrome connectivity issues
38
38
  */
39
39
  export declare function fetchCDPTargets(port?: number, logger?: Logger, options?: FetchCDPTargetsOptions): Promise<CDPTarget[]>;
40
+ /**
41
+ * Options for a DevTools endpoint probe.
42
+ */
43
+ export interface DevToolsProbeOptions extends FetchCDPTargetsOptions {
44
+ /** Ends a pending request early (the probe then reports `unreachable`) */
45
+ signal?: AbortSignal | undefined;
46
+ }
40
47
  /**
41
48
  * What answers on a DevTools HTTP endpoint: a Chrome (with its browser-level
42
49
  * WebSocket URL), an HTTP server that is not DevTools, or nothing.
@@ -54,19 +61,19 @@ export type DevToolsProbe = {
54
61
  *
55
62
  * @param port - Chrome debugging port
56
63
  * @param logger - Optional logger for debug output
57
- * @param options - Host, HTTPS and request timeout
64
+ * @param options - Host, HTTPS, request timeout and abort signal
58
65
  * @returns What answered
59
66
  */
60
- export declare function probeDevToolsEndpoint(port: number, logger?: Logger, options?: FetchCDPTargetsOptions): Promise<DevToolsProbe>;
67
+ export declare function probeDevToolsEndpoint(port: number, logger?: Logger, options?: DevToolsProbeOptions): Promise<DevToolsProbe>;
61
68
  /**
62
69
  * The browser-level DevTools WebSocket URL of a Chrome (`/json/version`).
63
70
  *
64
71
  * @param port - Chrome debugging port
65
72
  * @param logger - Optional logger for debug output
66
- * @param options - Host and HTTPS
73
+ * @param options - Host, HTTPS and abort signal
67
74
  * @returns The URL, or null if no Chrome answered
68
75
  */
69
- export declare function fetchBrowserWsUrl(port: number, logger?: Logger, options?: Pick<FetchCDPTargetsOptions, 'host' | 'secure'>): Promise<string | null>;
76
+ export declare function fetchBrowserWsUrl(port: number, logger?: Logger, options?: Pick<DevToolsProbeOptions, 'host' | 'secure' | 'signal'>): Promise<string | null>;
70
77
  /**
71
78
  * Fetch specific CDP target by ID from Chrome's HTTP API.
72
79
  *
@@ -67,15 +67,17 @@ export async function fetchCDPTargets(port = DEFAULT_CDP_PORT, logger, options)
67
67
  *
68
68
  * @param port - Chrome debugging port
69
69
  * @param logger - Optional logger for debug output
70
- * @param options - Host, HTTPS and request timeout
70
+ * @param options - Host, HTTPS, request timeout and abort signal
71
71
  * @returns What answered
72
72
  */
73
73
  export async function probeDevToolsEndpoint(port, logger, options) {
74
74
  const url = `${options?.secure ? 'https' : 'http'}://${options?.host ?? HTTP_LOCALHOST}:${port}/json/version`;
75
75
  const timeoutMs = options?.timeoutMs ?? CDP_HTTP_TIMEOUT_MS;
76
+ const timeout = AbortSignal.timeout(timeoutMs);
77
+ const signal = options?.signal ? AbortSignal.any([timeout, options.signal]) : timeout;
76
78
  let response;
77
79
  try {
78
- response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
80
+ response = await fetch(url, { signal });
79
81
  }
80
82
  catch (error) {
81
83
  logger?.debug(`Chrome version request failed: ${getErrorMessage(error)} (${url})`);
@@ -97,7 +99,7 @@ export async function probeDevToolsEndpoint(port, logger, options) {
97
99
  *
98
100
  * @param port - Chrome debugging port
99
101
  * @param logger - Optional logger for debug output
100
- * @param options - Host and HTTPS
102
+ * @param options - Host, HTTPS and abort signal
101
103
  * @returns The URL, or null if no Chrome answered
102
104
  */
103
105
  export async function fetchBrowserWsUrl(port, logger, options) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "browser-debugger-cli",
3
- "version": "0.14.0",
4
- "description": "DevTools telemetry in your terminal. For humans and agents. Direct WebSocket to Chrome's debugging port.",
3
+ "version": "0.16.0",
4
+ "description": "Let Claude Code and other coding agents drive and debug Chrome from the shell. DOM, network, console and raw CDP as short commands, no screenshots and no MCP needed.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "bdg": "dist/index.js"
@@ -37,10 +37,24 @@
37
37
  "keywords": [
38
38
  "cdp",
39
39
  "chrome-devtools-protocol",
40
+ "chrome",
41
+ "devtools",
42
+ "debugging",
40
43
  "browser",
41
- "telemetry",
44
+ "browser-automation",
45
+ "headless-chrome",
42
46
  "cli",
43
- "devtools"
47
+ "claude",
48
+ "claude-code",
49
+ "coding-agent",
50
+ "ai-agent",
51
+ "agent-skill",
52
+ "codex",
53
+ "css",
54
+ "figma",
55
+ "telemetry",
56
+ "puppeteer-alternative",
57
+ "playwright-alternative"
44
58
  ],
45
59
  "license": "MIT",
46
60
  "dependencies": {