browser-debugger-cli 0.13.0 → 0.15.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 (159) hide show
  1. package/.claude/skills/bdg/SKILL.md +101 -187
  2. package/README.md +4 -4
  3. package/dist/commands/cdp.js +1 -0
  4. package/dist/commands/cleanup.js +3 -0
  5. package/dist/commands/console.js +5 -1
  6. package/dist/commands/dom/a11y.d.ts +1 -1
  7. package/dist/commands/dom/a11y.js +20 -20
  8. package/dist/commands/dom/eval.d.ts +3 -1
  9. package/dist/commands/dom/eval.js +8 -5
  10. package/dist/commands/dom/formInteraction.js +1 -1
  11. package/dist/commands/dom/get.js +25 -7
  12. package/dist/commands/dom/helpers/evalResult.d.ts +36 -0
  13. package/dist/commands/dom/helpers/evalResult.js +59 -0
  14. package/dist/commands/dom/index.js +7 -2
  15. package/dist/commands/dom/query.d.ts +2 -1
  16. package/dist/commands/dom/query.js +5 -3
  17. package/dist/commands/dom/screenshot.js +1 -0
  18. package/dist/commands/helpJson.d.ts +1 -1
  19. package/dist/commands/helpJson.js +4 -4
  20. package/dist/commands/helpTopic.js +10 -4
  21. package/dist/commands/network/har.js +18 -14
  22. package/dist/commands/network/list.js +46 -3
  23. package/dist/commands/optionBehaviors.d.ts +25 -2
  24. package/dist/commands/optionBehaviors.js +60 -42
  25. package/dist/commands/peek.js +3 -0
  26. package/dist/commands/shared/CommandRunner.js +13 -13
  27. package/dist/commands/shared/daemonErrorHandler.js +2 -2
  28. package/dist/commands/shared/dataFetcher.d.ts +4 -2
  29. package/dist/commands/shared/dataFetcher.js +11 -3
  30. package/dist/commands/shared/handleValidationError.js +3 -3
  31. package/dist/commands/shared/optionTypes.d.ts +15 -3
  32. package/dist/commands/shared/outputFile.d.ts +2 -1
  33. package/dist/commands/shared/outputFile.js +7 -4
  34. package/dist/commands/shared/startHelpers.js +3 -3
  35. package/dist/commands/status.js +3 -1
  36. package/dist/commands/stop.js +2 -1
  37. package/dist/connection/chromeIdentity.d.ts +8 -2
  38. package/dist/connection/chromeIdentity.js +85 -13
  39. package/dist/connection/launcher/flagsBuilder.d.ts +46 -0
  40. package/dist/connection/launcher/flagsBuilder.js +107 -23
  41. package/dist/connection/launcher.d.ts +1 -1
  42. package/dist/connection/launcher.js +1 -2
  43. package/dist/constants.d.ts +31 -5
  44. package/dist/constants.js +37 -5
  45. package/dist/daemon/SessionController.js +2 -0
  46. package/dist/daemon/launcher.d.ts +17 -3
  47. package/dist/daemon/launcher.js +37 -7
  48. package/dist/daemon/session/Session.d.ts +2 -1
  49. package/dist/daemon/session/Session.js +10 -2
  50. package/dist/daemon/session/TelemetryStore.d.ts +7 -0
  51. package/dist/daemon/session/TelemetryStore.js +6 -0
  52. package/dist/daemon/session/commandRegistry.js +25 -7
  53. package/dist/daemon/session/matchedStylesReset.d.ts +26 -0
  54. package/dist/daemon/session/matchedStylesReset.js +46 -0
  55. package/dist/daemon/session/plugins.js +1 -0
  56. package/dist/daemon/session/triggeredRequests.d.ts +0 -5
  57. package/dist/daemon/session/triggeredRequests.js +13 -7
  58. package/dist/daemon.js +8742 -8315
  59. package/dist/errors/messages.d.ts +31 -0
  60. package/dist/errors/messages.js +96 -6
  61. package/dist/index.js +1129 -548
  62. package/dist/ipc/client.d.ts +6 -1
  63. package/dist/ipc/client.js +11 -2
  64. package/dist/ipc/protocol/commands.d.ts +8 -0
  65. package/dist/ipc/protocol/inspectTypes.d.ts +5 -2
  66. package/dist/ipc/session/types.d.ts +5 -1
  67. package/dist/ipc/transport/index.d.ts +6 -0
  68. package/dist/ipc/transport/index.js +16 -1
  69. package/dist/program.d.ts +14 -0
  70. package/dist/program.js +53 -0
  71. package/dist/runtime/dom/elementGeometry.d.ts +23 -0
  72. package/dist/runtime/dom/elementGeometry.js +17 -15
  73. package/dist/runtime/dom/elementInfo.d.ts +13 -4
  74. package/dist/runtime/dom/elementInfo.js +15 -5
  75. package/dist/runtime/dom/evalHelpers.d.ts +24 -4
  76. package/dist/runtime/dom/evalHelpers.js +40 -12
  77. package/dist/runtime/dom/frameScopedConnection.d.ts +7 -0
  78. package/dist/runtime/dom/frameScopedConnection.js +2 -2
  79. package/dist/runtime/dom/frames.d.ts +2 -1
  80. package/dist/runtime/dom/frames.js +3 -1
  81. package/dist/runtime/dom/inspect.d.ts +17 -3
  82. package/dist/runtime/dom/inspect.js +40 -26
  83. package/dist/runtime/dom/inspectModel.d.ts +3 -3
  84. package/dist/runtime/dom/inspectRules.d.ts +29 -3
  85. package/dist/runtime/dom/inspectRules.js +205 -11
  86. package/dist/runtime/dom/layout.d.ts +0 -2
  87. package/dist/runtime/dom/layout.js +1 -2
  88. package/dist/runtime/dom/reactEventHelpers.d.ts +4 -1
  89. package/dist/runtime/dom/reactEventHelpers.js +9 -2
  90. package/dist/runtime/dom/targetNode.d.ts +10 -6
  91. package/dist/runtime/dom/targetNode.js +15 -8
  92. package/dist/runtime/page/emulation.js +6 -5
  93. package/dist/runtime/page/userAgent.d.ts +86 -2
  94. package/dist/runtime/page/userAgent.js +154 -33
  95. package/dist/session/paths.d.ts +38 -3
  96. package/dist/session/paths.js +154 -7
  97. package/dist/session/portClaims.d.ts +0 -8
  98. package/dist/session/portClaims.js +1 -22
  99. package/dist/session/sessionList.d.ts +5 -1
  100. package/dist/session/sessionList.js +5 -1
  101. package/dist/telemetry/a11y.d.ts +15 -1
  102. package/dist/telemetry/a11y.js +83 -0
  103. package/dist/telemetry/har/builder.d.ts +12 -1
  104. package/dist/telemetry/har/builder.js +11 -3
  105. package/dist/telemetry/har/sanitize.d.ts +24 -0
  106. package/dist/telemetry/har/sanitize.js +138 -0
  107. package/dist/telemetry/har/sanitizeBody.d.ts +38 -0
  108. package/dist/telemetry/har/sanitizeBody.js +168 -0
  109. package/dist/telemetry/network.d.ts +13 -16
  110. package/dist/telemetry/network.js +30 -52
  111. package/dist/telemetry/networkRetention.d.ts +83 -0
  112. package/dist/telemetry/networkRetention.js +117 -0
  113. package/dist/types.d.ts +26 -0
  114. package/dist/ui/OutputBuilder.d.ts +10 -0
  115. package/dist/ui/OutputBuilder.js +12 -0
  116. package/dist/ui/formatters/a11y.d.ts +5 -7
  117. package/dist/ui/formatters/a11y.js +7 -61
  118. package/dist/ui/formatters/console/chronological.js +4 -4
  119. package/dist/ui/formatters/console/follow.d.ts +4 -2
  120. package/dist/ui/formatters/console/follow.js +6 -3
  121. package/dist/ui/formatters/console/json.d.ts +3 -6
  122. package/dist/ui/formatters/console/json.js +9 -13
  123. package/dist/ui/formatters/console/shared.d.ts +17 -2
  124. package/dist/ui/formatters/console/shared.js +17 -0
  125. package/dist/ui/formatters/console/summarize.d.ts +2 -2
  126. package/dist/ui/formatters/console/summarize.js +22 -7
  127. package/dist/ui/formatters/console.d.ts +1 -1
  128. package/dist/ui/formatters/console.js +1 -5
  129. package/dist/ui/formatters/details.js +1 -1
  130. package/dist/ui/formatters/dom.d.ts +13 -4
  131. package/dist/ui/formatters/dom.js +25 -7
  132. package/dist/ui/formatters/layout.js +2 -1
  133. package/dist/ui/formatters/longValues.d.ts +14 -0
  134. package/dist/ui/formatters/longValues.js +23 -0
  135. package/dist/ui/formatters/networkList.d.ts +8 -2
  136. package/dist/ui/formatters/networkList.js +11 -2
  137. package/dist/ui/formatters/preview.d.ts +4 -1
  138. package/dist/ui/formatters/preview.js +55 -13
  139. package/dist/ui/formatters/sessions.d.ts +3 -2
  140. package/dist/ui/formatters/sessions.js +10 -3
  141. package/dist/ui/formatters/status.js +7 -0
  142. package/dist/ui/formatters/triggeredRequests.js +2 -1
  143. package/dist/ui/messages/chrome.d.ts +34 -7
  144. package/dist/ui/messages/chrome.js +81 -15
  145. package/dist/ui/messages/commands.d.ts +29 -8
  146. package/dist/ui/messages/commands.js +36 -8
  147. package/dist/ui/messages/networkMessages.d.ts +50 -0
  148. package/dist/ui/messages/networkMessages.js +66 -0
  149. package/dist/ui/messages/session.d.ts +8 -0
  150. package/dist/ui/messages/session.js +10 -0
  151. package/dist/utils/atomicFile.d.ts +2 -1
  152. package/dist/utils/atomicFile.js +5 -2
  153. package/dist/utils/directories.d.ts +41 -0
  154. package/dist/utils/directories.js +48 -0
  155. package/dist/utils/http.d.ts +9 -2
  156. package/dist/utils/http.js +4 -3
  157. package/dist/utils/strings.d.ts +19 -0
  158. package/dist/utils/strings.js +16 -0
  159. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1287,14 +1287,14 @@ var require_is_docker = __commonJS({
1287
1287
  var require_is_wsl = __commonJS({
1288
1288
  "node_modules/is-wsl/index.js"(exports, module) {
1289
1289
  "use strict";
1290
- var os4 = __require("os");
1290
+ var os5 = __require("os");
1291
1291
  var fs19 = __require("fs");
1292
1292
  var isDocker = require_is_docker();
1293
1293
  var isWsl3 = () => {
1294
1294
  if (process.platform !== "linux") {
1295
1295
  return false;
1296
1296
  }
1297
- if (os4.release().toLowerCase().includes("microsoft")) {
1297
+ if (os5.release().toLowerCase().includes("microsoft")) {
1298
1298
  if (isDocker()) {
1299
1299
  return false;
1300
1300
  }
@@ -1327,16 +1327,25 @@ function resourceTypeFromName(name) {
1327
1327
  ([type, abbreviation]) => type.toLowerCase() === wanted || abbreviation.toLowerCase() === wanted
1328
1328
  )?.[0];
1329
1329
  }
1330
- var HTTP_LOCALHOST, MAX_CONSOLE_MESSAGES, MAX_RESPONSE_SIZE, CHROME_NETWORK_BUFFER_TOTAL, CHROME_NETWORK_BUFFER_PER_RESOURCE, CHROME_POST_DATA_LIMIT, MAX_JSONL_BUFFER_SIZE, PORT_OPTION_DESCRIPTION, RESOURCE_TYPE_ABBREVIATIONS, MIME_TYPE_RULES;
1330
+ var HTTP_LOCALHOST, MAX_NETWORK_REQUESTS, MAX_CONSOLE_MESSAGES, MAX_RESPONSE_SIZE, MAX_TOTAL_BODY_BYTES, MAX_VALUE_LENGTH, MAX_CONSOLE_TEXT_LENGTH, MAX_CONSOLE_JSON_TEXT_LENGTH, CHROME_NETWORK_BUFFER_TOTAL, CHROME_NETWORK_BUFFER_PER_RESOURCE, CHROME_POST_DATA_LIMIT, QUERY_JSON_LIST_LIMIT, EVAL_JSON_ARRAY_LIMIT, LAYOUT_ELEMENT_LIMIT, MAX_TRIGGERED_REQUESTS, MAX_JSONL_BUFFER_SIZE, PORT_OPTION_DESCRIPTION, RESOURCE_TYPE_ABBREVIATIONS, MIME_TYPE_RULES;
1331
1331
  var init_constants = __esm({
1332
1332
  "src/constants.ts"() {
1333
1333
  "use strict";
1334
1334
  HTTP_LOCALHOST = "127.0.0.1";
1335
+ MAX_NETWORK_REQUESTS = 1e4;
1335
1336
  MAX_CONSOLE_MESSAGES = 1e4;
1336
1337
  MAX_RESPONSE_SIZE = 5 * 1024 * 1024;
1338
+ MAX_TOTAL_BODY_BYTES = 100 * 1024 * 1024;
1339
+ MAX_VALUE_LENGTH = 2e4;
1340
+ MAX_CONSOLE_TEXT_LENGTH = 200;
1341
+ MAX_CONSOLE_JSON_TEXT_LENGTH = 1e4;
1337
1342
  CHROME_NETWORK_BUFFER_TOTAL = 50 * 1024 * 1024;
1338
1343
  CHROME_NETWORK_BUFFER_PER_RESOURCE = 10 * 1024 * 1024;
1339
1344
  CHROME_POST_DATA_LIMIT = 1 * 1024 * 1024;
1345
+ QUERY_JSON_LIST_LIMIT = 100;
1346
+ EVAL_JSON_ARRAY_LIMIT = 100;
1347
+ LAYOUT_ELEMENT_LIMIT = 100;
1348
+ MAX_TRIGGERED_REQUESTS = 50;
1340
1349
  MAX_JSONL_BUFFER_SIZE = 256 * 1024 * 1024;
1341
1350
  PORT_OPTION_DESCRIPTION = "Chrome debugging port";
1342
1351
  RESOURCE_TYPE_ABBREVIATIONS = {
@@ -1481,11 +1490,30 @@ function makeDirectory(dir, mode) {
1481
1490
  }
1482
1491
  fs2.mkdirSync(dir, { recursive: true, ...mode !== void 0 && { mode } });
1483
1492
  }
1484
- var PSEUDO_FILESYSTEM;
1493
+ function dirTrustProblem(dir, options = {}) {
1494
+ const stat = fs2.lstatSync(dir);
1495
+ const problem = (reason, kind) => ({ reason, kind });
1496
+ if (stat.isSymbolicLink()) return problem("it is a symbolic link", "symlink");
1497
+ if (!stat.isDirectory()) return problem("not a directory", "not-directory");
1498
+ const uid = process.getuid?.();
1499
+ if (uid !== void 0 && stat.uid !== uid) return problem(`owned by uid ${stat.uid}`, "owner");
1500
+ if (process.platform === "win32") return null;
1501
+ const writeBits = options.allowGroupWrite ? OTHER_WRITE : GROUP_OTHER_WRITE;
1502
+ if ((stat.mode & writeBits) === 0) return null;
1503
+ const mode = (stat.mode & 4095).toString(8);
1504
+ if ((stat.mode & STICKY) !== 0 && (stat.mode & OTHER_WRITE) !== 0) {
1505
+ return problem(`a shared sticky directory (mode ${mode})`, "shared");
1506
+ }
1507
+ return problem(`writable by others (mode ${mode})`, "writable");
1508
+ }
1509
+ var PSEUDO_FILESYSTEM, GROUP_OTHER_WRITE, OTHER_WRITE, STICKY;
1485
1510
  var init_directories = __esm({
1486
1511
  "src/utils/directories.ts"() {
1487
1512
  "use strict";
1488
1513
  PSEUDO_FILESYSTEM = /^\/(proc|sys)(\/|$)/;
1514
+ GROUP_OTHER_WRITE = 18;
1515
+ OTHER_WRITE = 2;
1516
+ STICKY = 512;
1489
1517
  }
1490
1518
  });
1491
1519
 
@@ -1494,12 +1522,16 @@ import * as fs3 from "fs";
1494
1522
  import * as os2 from "os";
1495
1523
  import * as path3 from "path";
1496
1524
  function getSessionBaseDir() {
1497
- const override = process.env[SESSION_DIR_OVERRIDE_ENV];
1498
- if (override && override.trim().length > 0) {
1525
+ const override = sessionDirOverride();
1526
+ if (override !== null) {
1499
1527
  return path3.isAbsolute(override) ? override : path3.resolve(override);
1500
1528
  }
1501
1529
  return path3.join(os2.homedir(), ".bdg");
1502
1530
  }
1531
+ function sessionDirOverride() {
1532
+ const override = process.env[SESSION_DIR_OVERRIDE_ENV];
1533
+ return override && override.trim().length > 0 ? override : null;
1534
+ }
1503
1535
  function getSessionName() {
1504
1536
  const name = process.env[SESSION_NAME_ENV]?.trim();
1505
1537
  if (!name) return null;
@@ -1535,14 +1567,81 @@ function getDaemonSocketPath() {
1535
1567
  return getSessionFilePath("DAEMON_SOCKET");
1536
1568
  }
1537
1569
  function ensureSessionDir() {
1538
- makeDirectory(getSessionDir());
1570
+ makeDirectory(getSessionDir(), PRIVATE_DIR_MODE);
1571
+ }
1572
+ function sessionDirChain(dir) {
1573
+ const base = getSessionBaseDir();
1574
+ const relative2 = path3.relative(base, dir);
1575
+ if (relative2 === ".." || relative2.startsWith(`..${path3.sep}`) || path3.isAbsolute(relative2)) {
1576
+ return [{ dir, bdgOwned: false, isBase: false }];
1577
+ }
1578
+ const chain = [{ dir: base, bdgOwned: sessionDirOverride() === null, isBase: true }];
1579
+ let current = base;
1580
+ for (const part of relative2.split(path3.sep).filter(Boolean)) {
1581
+ current = path3.join(current, part);
1582
+ chain.push({ dir: current, bdgOwned: true, isBase: false });
1583
+ }
1584
+ return chain;
1585
+ }
1586
+ function tightenDir(dir) {
1587
+ if (process.platform === "win32") return;
1588
+ let fd;
1589
+ try {
1590
+ const { O_RDONLY, O_DIRECTORY, O_NOFOLLOW } = fs3.constants;
1591
+ fd = fs3.openSync(dir, O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
1592
+ const stat = fs3.fstatSync(fd);
1593
+ if (stat.uid !== process.getuid?.() || (stat.mode & GROUP_OTHER_ACCESS) === 0) return;
1594
+ fs3.fchmodSync(fd, PRIVATE_DIR_MODE);
1595
+ log.debug(`Session directory ${dir} restricted to mode 700`);
1596
+ } catch (error) {
1597
+ log.debug(`Session directory ${dir} not restricted: ${getErrorMessage(error)}`);
1598
+ } finally {
1599
+ if (fd !== void 0) fs3.closeSync(fd);
1600
+ }
1601
+ }
1602
+ function chainDirProblem(entry) {
1603
+ const problem = dirTrustProblem(entry.dir, SESSION_DIR_TRUST);
1604
+ if (problem?.kind !== "symlink" || !entry.isBase) return problem;
1605
+ const target = fs3.realpathSync(entry.dir);
1606
+ const targetProblem = dirTrustProblem(target, SESSION_DIR_TRUST);
1607
+ return targetProblem && {
1608
+ ...targetProblem,
1609
+ reason: `it links to ${target}, which is ${targetProblem.reason}`
1610
+ };
1539
1611
  }
1540
- var log, SESSION_FILES, SESSION_DIR_OVERRIDE_ENV, SESSION_NAME_ENV, NAMED_SESSIONS_DIR, MAX_SOCKET_PATH_BYTES, MAX_DAEMON_SOCKET_PATH_BYTES, SESSION_STATE_FILES;
1612
+ function acceptTrustedDir(entry) {
1613
+ if (entry.bdgOwned) {
1614
+ tightenDir(entry.dir);
1615
+ return;
1616
+ }
1617
+ const mode = fs3.statSync(entry.dir, { throwIfNoEntry: false })?.mode ?? 0;
1618
+ if ((mode & GROUP_OTHER_ACCESS) === 0) return;
1619
+ log.debug(
1620
+ `Session directory ${entry.dir} is open to group or others; left as is (chosen with ${SESSION_DIR_OVERRIDE_ENV})`
1621
+ );
1622
+ }
1623
+ function secureSessionDir(dir = getSessionDir()) {
1624
+ for (const entry of sessionDirChain(dir)) {
1625
+ let problem;
1626
+ try {
1627
+ problem = chainDirProblem(entry);
1628
+ } catch (error) {
1629
+ const code = error.code;
1630
+ if (code === "ENOENT" || code === "ENOTDIR") continue;
1631
+ problem = { reason: getErrorMessage(error), kind: "not-directory" };
1632
+ }
1633
+ if (problem !== null) return { dir: entry.dir, ...problem, bdgOwned: entry.bdgOwned };
1634
+ acceptTrustedDir(entry);
1635
+ }
1636
+ return null;
1637
+ }
1638
+ var log, SESSION_FILES, SESSION_DIR_OVERRIDE_ENV, SESSION_NAME_ENV, NAMED_SESSIONS_DIR, MAX_SOCKET_PATH_BYTES, MAX_DAEMON_SOCKET_PATH_BYTES, SESSION_STATE_FILES, PRIVATE_DIR_MODE, GROUP_OTHER_ACCESS, SESSION_DIR_TRUST;
1541
1639
  var init_paths = __esm({
1542
1640
  "src/session/paths.ts"() {
1543
1641
  "use strict";
1544
1642
  init_logging();
1545
1643
  init_directories();
1644
+ init_errors();
1546
1645
  log = createLogger("session");
1547
1646
  SESSION_FILES = {
1548
1647
  OUTPUT: "session.json",
@@ -1564,6 +1663,9 @@ var init_paths = __esm({
1564
1663
  "CHROME_PID",
1565
1664
  "METADATA"
1566
1665
  ];
1666
+ PRIVATE_DIR_MODE = 448;
1667
+ GROUP_OTHER_ACCESS = 63;
1668
+ SESSION_DIR_TRUST = { allowGroupWrite: true };
1567
1669
  }
1568
1670
  });
1569
1671
 
@@ -1645,14 +1747,17 @@ var init_atomicFile = __esm({
1645
1747
  *
1646
1748
  * @param filePath - Target file path
1647
1749
  * @param data - Data to write
1648
- * @param options - Write options
1750
+ * @param options - Write options (`mode`: permissions of the new file, less the umask)
1649
1751
  * @returns Promise that resolves when write completes
1650
1752
  * @throws Error if write operation fails
1651
1753
  */
1652
1754
  static async writeAsync(filePath, data, options = {}) {
1653
1755
  const tmpPath = this.getTempPath(filePath);
1654
1756
  try {
1655
- await fs4.promises.writeFile(tmpPath, data, { encoding: options.encoding ?? "utf-8" });
1757
+ await fs4.promises.writeFile(tmpPath, data, {
1758
+ encoding: options.encoding ?? "utf-8",
1759
+ ...options.mode !== void 0 && { mode: options.mode }
1760
+ });
1656
1761
  await fs4.promises.rename(tmpPath, filePath);
1657
1762
  } catch (error) {
1658
1763
  try {
@@ -2163,6 +2268,12 @@ function truncateByLength(text, maxLength = DEFAULT_MAX_LENGTH) {
2163
2268
  }
2164
2269
  return text.slice(0, maxLength - 1) + "\u2026";
2165
2270
  }
2271
+ function capLength(text, maxLength) {
2272
+ if (text.length <= maxLength) return { text };
2273
+ const lastKept = text.charCodeAt(maxLength - 1);
2274
+ const end = lastKept >= 55296 && lastKept <= 56319 ? maxLength - 1 : maxLength;
2275
+ return { text: text.slice(0, end), truncatedFrom: text.length };
2276
+ }
2166
2277
  var DEFAULT_MAX_LENGTH;
2167
2278
  var init_strings = __esm({
2168
2279
  "src/utils/strings.ts"() {
@@ -2178,8 +2289,8 @@ function chromeClosedMessage(pid) {
2178
2289
  function orphanedDaemonsCleanedMessage(count) {
2179
2290
  return `Cleaned up ${count} orphaned daemon process${count === 1 ? "" : "es"}`;
2180
2291
  }
2181
- function skillBackupMessage(path7) {
2182
- return `previous copy kept in ${path7}`;
2292
+ function skillBackupMessage(path8) {
2293
+ return `previous copy kept in ${path8}`;
2183
2294
  }
2184
2295
  function fieldLabelList(labels) {
2185
2296
  const shown = labels.slice(0, 5).map((label) => label.replace(/\s*:\s*$/, "")).join(", ");
@@ -2228,8 +2339,7 @@ function newMessageText(message) {
2228
2339
  return `"${message.text}" (${message.element})`;
2229
2340
  }
2230
2341
  function moreMatchesNote(hidden, jsonLimit) {
2231
- const where = jsonLimit === void 0 ? "use --json for all" : `--json lists the first ${jsonLimit}`;
2232
- return `... and ${hidden} more (${where})`;
2342
+ return `... and ${hidden} more (--json lists up to ${jsonLimit})`;
2233
2343
  }
2234
2344
  function queryMoreMatchesNote(omitted, indexed) {
2235
2345
  const indices = indexed === void 0 ? "" : `; indices 0-${indexed - 1} work with other commands`;
@@ -2238,6 +2348,12 @@ function queryMoreMatchesNote(omitted, indexed) {
2238
2348
  function queryViewportCheckedNote(checked) {
2239
2349
  return `Visibility is checked for the first ${checked} matches only; ${sessionCommand("bdg dom layout <index>")} checks any of them`;
2240
2350
  }
2351
+ function a11yTreeShownNote(listed) {
2352
+ return `Showing the first ${listed} nodes (text boxes and repeated text left out)`;
2353
+ }
2354
+ function a11yTreeMoreNote(omitted) {
2355
+ return `${omitted} more: --limit 0 lists all, --depth <n> limits the levels, or search with bdg dom a11y query "role:<role>"`;
2356
+ }
2241
2357
  function a11yMoreMatchesNote(omitted) {
2242
2358
  return `... and ${omitted} more (--limit 0 lists all; their indices work too)`;
2243
2359
  }
@@ -2344,6 +2460,8 @@ function coverText(cover, transparent) {
2344
2460
  return transparent ? `under transparent ${cover} (clicks land on it)` : `covered by ${cover}`;
2345
2461
  }
2346
2462
  function inspectCascadeNote(reason) {
2463
+ if (reason === "skipped")
2464
+ return "hints skipped: this page's stylesheets are slow to read (--rules waits 5 s)";
2347
2465
  return reason === "timeout" ? "CSS rules not read: the page's stylesheets took too long (hints wait 1 s; --rules and --why 5 s)" : "CSS rules not read: Chrome could not report the rules matching this element";
2348
2466
  }
2349
2467
  function inspectPseudoOfNote(pseudo) {
@@ -2574,6 +2692,9 @@ function inspectMidTransitionNote() {
2574
2692
  function helpJsonDetailsNote() {
2575
2693
  return "Option behaviors, defaults, choices and examples: bdg <command> --help --json (e.g. bdg dom query --help --json). Everything at once: bdg --help --json --full";
2576
2694
  }
2695
+ function moreCharsNote(count) {
2696
+ return `\u2026 ${count} more chars (use --full)`;
2697
+ }
2577
2698
  var REQUIRED_FIELD_EMPTY_REASON, NO_VISIBLE_EFFECT, HOVER_OFF_DONE, HOVER_USAGE, LAYOUT_REASONS, SCROLL_LOCKED_PREFIX, VIEWPORT_POSITION_HINTS, INSPECT_OUTPUT_LEGEND, COLLAPSED_LISTENERS_HEADING, NO_LISTENERS_HINT, NOTE_TYPES_SHOWN, POINTER_ACTION_DONE, POINTER_ACTION_NOUN, PAGE_ACTION_DONE, PAGE_INFO_DESCRIPTION, PAGE_EMULATE_DESCRIPTION, PAGE_ACTION_DESCRIPTIONS, CLICK_RESULT_WAIT_HELP, WAIT_HELP_EXAMPLES, FRAME_URL_MAX_LENGTH, AUDIT_HELP_EXAMPLES, CSS_SEARCH_HELP_EXAMPLES;
2578
2699
  var init_commands = __esm({
2579
2700
  "src/ui/messages/commands.ts"() {
@@ -3096,6 +3217,7 @@ var init_shellDetection = __esm({
3096
3217
  });
3097
3218
 
3098
3219
  // src/errors/messages.ts
3220
+ import * as os3 from "os";
3099
3221
  import * as path4 from "path";
3100
3222
  function sessionAlreadyRunningError(pid, duration, targetUrl) {
3101
3223
  return joinLines(
@@ -3267,8 +3389,13 @@ function unknownHelpTopicError(topic, closest) {
3267
3389
  suggestion: closest ? `Did you mean: bdg help ${closest}?` : `Run "bdg ${parent ? `${parent} ` : ""}--help" for commands`
3268
3390
  };
3269
3391
  }
3270
- function usageHelpSuggestion(path7) {
3271
- return `Run "${path7} --help" for usage`;
3392
+ function didYouMeanSuggestion(candidates) {
3393
+ const last = candidates.at(-1) ?? "";
3394
+ const rest = candidates.slice(0, -1);
3395
+ return `Did you mean: ${rest.length > 0 ? `${rest.join(", ")} or ${last}` : last}?`;
3396
+ }
3397
+ function usageHelpSuggestion(path8) {
3398
+ return `Run "${path8} --help" for usage`;
3272
3399
  }
3273
3400
  function missingSubcommandMessage() {
3274
3401
  return "Missing subcommand";
@@ -3325,7 +3452,7 @@ function invalidSessionNameError(name, maxLength) {
3325
3452
  function sessionNameSocketTooLongError(name, socketPath, max) {
3326
3453
  return {
3327
3454
  message: `Session name "${name}" makes the daemon socket path too long (${Buffer.byteLength(socketPath)} bytes, at most ${max}): ${socketPath}`,
3328
- suggestion: "Use a shorter session name, or a shorter BDG_SESSION_DIR (e.g. /tmp/bdg)"
3455
+ suggestion: `Use a shorter session name, or a shorter BDG_SESSION_DIR (e.g. ${privateSessionDirExample()})`
3329
3456
  };
3330
3457
  }
3331
3458
  function purgeNeedsNamedSessionError() {
@@ -3340,19 +3467,52 @@ function purgeRefusedError(dir, reason) {
3340
3467
  suggestion: `End the session first (${sessionCommand("bdg cleanup --force")}), then retry: ${sessionCommand("bdg cleanup --purge")}`
3341
3468
  };
3342
3469
  }
3470
+ function privateSessionDirExample() {
3471
+ const runtimeDir = process.env["XDG_RUNTIME_DIR"]?.trim();
3472
+ if (runtimeDir) return path4.join(runtimeDir, "bdg");
3473
+ const uid = process.getuid?.();
3474
+ return path4.join(os3.tmpdir(), uid === void 0 ? "bdg" : `bdg-${uid}`);
3475
+ }
3476
+ function sessionDirSuggestion() {
3477
+ return `Set BDG_SESSION_DIR to a short directory only you can write to, e.g. BDG_SESSION_DIR=${privateSessionDirExample()}`;
3478
+ }
3343
3479
  function sessionDirIsFileError(dir) {
3344
- return { message: `Session directory ${dir} is a file`, suggestion: SESSION_DIR_SUGGESTION };
3480
+ return { message: `Session directory ${dir} is a file`, suggestion: sessionDirSuggestion() };
3345
3481
  }
3346
3482
  function sessionDirNotWritableError(dir, reason) {
3347
3483
  return {
3348
3484
  message: `Session directory ${dir} is not writable (${reason})`,
3349
- suggestion: SESSION_DIR_SUGGESTION
3485
+ suggestion: sessionDirSuggestion()
3486
+ };
3487
+ }
3488
+ function untrustedSessionDirError(untrusted) {
3489
+ return {
3490
+ message: `Session directory ${untrusted.dir} is not safe to use: ${untrusted.reason}`,
3491
+ suggestion: untrustedSessionDirSuggestion(untrusted)
3492
+ };
3493
+ }
3494
+ function untrustedSessionDirSuggestion(untrusted) {
3495
+ const { dir, kind, bdgOwned } = untrusted;
3496
+ const example = `BDG_SESSION_DIR=${privateSessionDirExample()}`;
3497
+ if (kind === "symlink") {
3498
+ return `Remove the link (rm ${dir}) or point BDG_SESSION_DIR at the real directory`;
3499
+ }
3500
+ if (kind === "shared") {
3501
+ return `${dir} is shared by all users; use a subdirectory only you can write to, e.g. ${example}`;
3502
+ }
3503
+ if (bdgOwned) return `Run chmod 700 ${dir} (or remove it if it is not yours), then retry`;
3504
+ return `Use a directory you own that others cannot write to, e.g. ${example} (or chmod 700 ${dir} if it is yours)`;
3505
+ }
3506
+ function daemonLogNotOpenedError(logPath, code) {
3507
+ return {
3508
+ message: `Cannot open the daemon log ${logPath} (${code === "ELOOP" ? "it is a symbolic link" : code})`,
3509
+ suggestion: `Remove ${logPath} if you did not create it, then retry`
3350
3510
  };
3351
3511
  }
3352
3512
  function socketPathTooLongError(socketPath, max) {
3353
3513
  return {
3354
3514
  message: `Session directory path is too long for the daemon socket (${Buffer.byteLength(socketPath)} bytes, at most ${max}): ${socketPath}`,
3355
- suggestion: SESSION_DIR_SUGGESTION
3515
+ suggestion: sessionDirSuggestion()
3356
3516
  };
3357
3517
  }
3358
3518
  function externalChromeUnreachableError(endpoint, secure) {
@@ -3691,10 +3851,10 @@ function skillSourceMissingError(source) {
3691
3851
  suggestion: "Reinstall bdg: npm install -g browser-debugger-cli"
3692
3852
  };
3693
3853
  }
3694
- function skillWriteFailedError(path7, reason, pathIsFile, otherTargetFlag) {
3854
+ function skillWriteFailedError(path8, reason, pathIsFile, otherTargetFlag) {
3695
3855
  const fix = pathIsFile ? "A part of that path is a file, not a directory: move it away" : "Check the permissions of that directory";
3696
3856
  return {
3697
- message: `Could not write ${path7}: ${reason}`,
3857
+ message: `Could not write ${path8}: ${reason}`,
3698
3858
  suggestion: otherTargetFlag ? `${fix}, or install for the other agent only (${otherTargetFlag})` : fix
3699
3859
  };
3700
3860
  }
@@ -3707,7 +3867,7 @@ function intervalWithoutFollowError() {
3707
3867
  function unknownAuditCheckMessage(value, similar, checks) {
3708
3868
  return `Unknown check "${value}"${similar.length > 0 ? `; did you mean ${similar[0]}?` : ""} (checks: ${checks.join(", ")})`;
3709
3869
  }
3710
- var LAUNCHED_CHROME_DESCRIPTION, A11Y_NAME_QUERY_EXAMPLE, SCOPED_FILTER_EXAMPLES, PLAYWRIGHT_ONLY_SYNTAX, MALFORMED_SELECTOR_DETAILS, SESSION_DIR_SUGGESTION, SCROLL_PROBLEMS, VIA_LABEL_SUFFIX, NAME_QUERY_PLACEHOLDER, LABEL_WITHOUT_CONTROL, REDECLARED_PATTERN;
3870
+ var LAUNCHED_CHROME_DESCRIPTION, A11Y_NAME_QUERY_EXAMPLE, SCOPED_FILTER_EXAMPLES, PLAYWRIGHT_ONLY_SYNTAX, MALFORMED_SELECTOR_DETAILS, SCROLL_PROBLEMS, VIA_LABEL_SUFFIX, NAME_QUERY_PLACEHOLDER, LABEL_WITHOUT_CONTROL, REDECLARED_PATTERN;
3711
3871
  var init_messages = __esm({
3712
3872
  "src/errors/messages.ts"() {
3713
3873
  "use strict";
@@ -3728,7 +3888,6 @@ var init_messages = __esm({
3728
3888
  "empty-has": () => ":has() has an empty selector",
3729
3889
  "scope-in-has": () => ":has() with filters is already relative to the element; write :has(> a:visible) instead of :has(:scope > a:visible)"
3730
3890
  };
3731
- SESSION_DIR_SUGGESTION = "Set BDG_SESSION_DIR to a short, writable directory, e.g. BDG_SESSION_DIR=/tmp/bdg";
3732
3891
  SCROLL_PROBLEMS = {
3733
3892
  "index-without-selector": [
3734
3893
  "--index requires a selector",
@@ -4051,7 +4210,17 @@ var init_validation2 = __esm({
4051
4210
  });
4052
4211
 
4053
4212
  // src/ipc/transport/index.ts
4213
+ import * as path5 from "path";
4054
4214
  async function sendRequest(request, requestName, expectedType, timeoutMs = getIPCRequestTimeout(), socketPath = getDaemonSocketPath()) {
4215
+ const untrusted = secureSessionDir(path5.dirname(socketPath));
4216
+ if (untrusted) {
4217
+ const err = untrustedSessionDirError(untrusted);
4218
+ throw new CommandError(
4219
+ err.message,
4220
+ { suggestion: err.suggestion },
4221
+ EXIT_CODES.SESSION_FILE_ERROR
4222
+ );
4223
+ }
4055
4224
  return new Promise((resolve4, reject2) => {
4056
4225
  const buffer = new JSONLBuffer();
4057
4226
  let resolved = false;
@@ -4110,8 +4279,11 @@ var init_transport = __esm({
4110
4279
  "src/ipc/transport/index.ts"() {
4111
4280
  "use strict";
4112
4281
  init_constants();
4282
+ init_errors3();
4283
+ init_messages();
4113
4284
  init_paths();
4114
4285
  init_logging();
4286
+ init_exitCodes();
4115
4287
  init_errors4();
4116
4288
  init_jsonl();
4117
4289
  init_socket();
@@ -4287,8 +4459,12 @@ async function callCDP(method, params, options = {}) {
4287
4459
  function callBdgScript(method, params) {
4288
4460
  return callCDP(method, params, { isolated: true });
4289
4461
  }
4290
- async function domEval(script, frame) {
4291
- return sendCommand("dom_eval", { script, ...frame !== void 0 && { frame } });
4462
+ async function domEval(script, frame, full) {
4463
+ return sendCommand("dom_eval", {
4464
+ script,
4465
+ ...frame !== void 0 && { frame },
4466
+ ...full && { full }
4467
+ });
4292
4468
  }
4293
4469
  async function domFrames() {
4294
4470
  return sendCommand("dom_frames", {});
@@ -8085,7 +8261,7 @@ function textPreview2(text, length = PREVIEW_LENGTH) {
8085
8261
  const characters = Array.from(collapsed);
8086
8262
  return characters.length > length ? characters.slice(0, length).join("") + "..." : collapsed;
8087
8263
  }
8088
- var CLASS_FRAGMENT, LABEL_CLASSES_JS, PREVIEW_LENGTH, ELEMENT_TEXT_LENGTH, WITHOUT_DECORATIONS_JS, COMPOSED_JS, FLAT_TEXT_JS, ELEMENT_TEXT_JS, MASKED_VALUE, SENSITIVE_FIELD_JS, ELEMENT_STATE_JS, ELEMENT_DESCRIPTION_JS, SIBLING_POSITION_JS, ELEMENT_IDENTITY_JS, ELEMENT_CONTEXT_JS;
8264
+ var CLASS_FRAGMENT, LABEL_CLASSES_JS, PREVIEW_LENGTH, ELEMENT_TEXT_LENGTH, WITHOUT_DECORATIONS_JS, COMPOSED_JS, FLAT_TEXT_JS, ELEMENT_TEXT_JS, MASKED_VALUE, SENSITIVE_NAME_SOURCE, SENSITIVE_FIELD_JS, ELEMENT_STATE_JS, ELEMENT_DESCRIPTION_JS, SIBLING_POSITION_JS, ELEMENT_IDENTITY_JS, ELEMENT_CONTEXT_JS;
8089
8265
  var init_elementInfo = __esm({
8090
8266
  "src/runtime/dom/elementInfo.ts"() {
8091
8267
  "use strict";
@@ -8126,7 +8302,7 @@ var init_elementInfo = __esm({
8126
8302
  while (walker.nextNode()) if (composes(walker.currentNode)) return true;
8127
8303
  return false;
8128
8304
  }`;
8129
- FLAT_TEXT_JS = `(el, limit) => {
8305
+ FLAT_TEXT_JS = `(el, limit, fields) => {
8130
8306
  const composed = ${COMPOSED_JS};
8131
8307
  const view = el.ownerDocument.defaultView;
8132
8308
  let text = '';
@@ -8136,7 +8312,8 @@ var init_elementInfo = __esm({
8136
8312
  if (text.length >= limit) return;
8137
8313
  if (node.nodeType === 3 && visible) text += node.data.replace(/\\s+/g, ' ');
8138
8314
  if (node.nodeType !== 1) return;
8139
- if (/^(input|textarea|select)$/.test(node.localName) || node.isContentEditable) return;
8315
+ if (/^(input|textarea)$/.test(node.localName)) return;
8316
+ if (!fields && (node.localName === 'select' || node.isContentEditable)) return;
8140
8317
  const style = view.getComputedStyle(node);
8141
8318
  if (node.checkVisibility && !node.checkVisibility() && style.display !== 'contents') return;
8142
8319
  if (node.localName === 'br') text += '\\n';
@@ -8175,12 +8352,13 @@ var init_elementInfo = __esm({
8175
8352
  return withoutDecorations(el, start);
8176
8353
  }`;
8177
8354
  MASKED_VALUE = "\u2022\u2022\u2022\u2022";
8355
+ SENSITIVE_NAME_SOURCE = "passw|passwd|pwd|passcode|(^|[^a-z])otp([^a-z]|$)|cvv|cvc";
8178
8356
  SENSITIVE_FIELD_JS = `(el) => {
8179
8357
  const autocomplete = el.getAttribute('autocomplete') || '';
8180
8358
  if (/(^|\\s)(cc-[a-z-]+|one-time-code|current-password|new-password)(\\s|$)/i.test(autocomplete)) return true;
8181
8359
  if (el.type === 'password' || /^password$/i.test(el.getAttribute('type') || '')) return true;
8182
8360
  const names = [el.getAttribute('name'), el.id, autocomplete].join(' ');
8183
- if (/passw|passwd|pwd|passcode|(^|[^a-z])otp([^a-z]|$)|cvv|cvc/i.test(names)) return true;
8361
+ if (/${SENSITIVE_NAME_SOURCE}/i.test(names)) return true;
8184
8362
  try {
8185
8363
  const security = el.ownerDocument.defaultView.getComputedStyle(el).getPropertyValue('-webkit-text-security');
8186
8364
  return Boolean(security) && security !== 'none';
@@ -8544,7 +8722,7 @@ var init_elementGeometry = __esm({
8544
8722
  /transform|filter|perspective/.test(style.willChange) || /paint|layout|strict|content/.test(style.contain)`;
8545
8723
  FIXED_TO_VIEWPORT_JS = `(n) => {
8546
8724
  const holdsFixed = ${HOLDS_FIXED_JS};
8547
- const parentOf = (node) => node.parentElement || (node.parentNode && node.parentNode.host) || null;
8725
+ const parentOf = ${FLAT_PARENT_JS};
8548
8726
  for (let p = parentOf(n); p && p !== n.ownerDocument.documentElement; p = parentOf(p)) {
8549
8727
  if (holdsFixed(p.ownerDocument.defaultView.getComputedStyle(p))) return false;
8550
8728
  }
@@ -8557,7 +8735,7 @@ var init_elementGeometry = __esm({
8557
8735
  const fixedToViewport = ${FIXED_TO_VIEWPORT_JS};
8558
8736
  const reasons = ${JSON.stringify(LAYOUT_REASONS)};
8559
8737
  const styleOf = (n) => n.ownerDocument.defaultView.getComputedStyle(n);
8560
- const parentOf = (n) => n.parentElement || (n.parentNode && n.parentNode.host) || null;
8738
+ const parentOf = ${FLAT_PARENT_JS};
8561
8739
  const doc = node.ownerDocument;
8562
8740
  const rootStyle = styleOf(doc.documentElement);
8563
8741
  const bodyClips = rootStyle.overflowX !== 'visible' || rootStyle.overflowY !== 'visible';
@@ -8771,13 +8949,13 @@ var init_frameScopedConnection = __esm({
8771
8949
  });
8772
8950
 
8773
8951
  // src/runtime/page/bdgWorld.ts
8774
- var log23;
8952
+ var log21;
8775
8953
  var init_bdgWorld = __esm({
8776
8954
  "src/runtime/page/bdgWorld.ts"() {
8777
8955
  "use strict";
8778
8956
  init_logging();
8779
8957
  init_errors();
8780
- log23 = createLogger("cdp");
8958
+ log21 = createLogger("cdp");
8781
8959
  }
8782
8960
  });
8783
8961
 
@@ -8965,10 +9143,10 @@ function findHasFilters(text, selector) {
8965
9143
  }
8966
9144
  function parseHasArgument(argument, selector) {
8967
9145
  if (!FILTER_NAME_HINT.test(argument)) return null;
8968
- const selectors = splitSelectorList(argument).map((relative) => {
8969
- if (relative.trim() === "") reject(malformedSelectorError(selector, "empty-has"));
8970
- if (SCOPE_PSEUDO.test(relative)) reject(malformedSelectorError(selector, "scope-in-has"));
8971
- const { leading, steps } = parseChain(relative, selector);
9146
+ const selectors = splitSelectorList(argument).map((relative2) => {
9147
+ if (relative2.trim() === "") reject(malformedSelectorError(selector, "empty-has"));
9148
+ if (SCOPE_PSEUDO.test(relative2)) reject(malformedSelectorError(selector, "scope-in-has"));
9149
+ const { leading, steps } = parseChain(relative2, selector);
8972
9150
  if (leading && leading !== ">") reject(siblingInHasError(selector, leading));
8973
9151
  const [first, ...rest] = steps;
8974
9152
  return first ? [{ ...first, combinator: leading || " " }, ...rest] : [];
@@ -9055,12 +9233,13 @@ function selectorArgsJS(selector) {
9055
9233
  }
9056
9234
  return `${JSON.stringify(selector)}, ${JSON.stringify(parseSelectorFilters(selector))}`;
9057
9235
  }
9058
- var BOUND_TARGET_SELECTOR, log24, FILTER_MATCHING_JS, DEEP_QUERY_JS, FIND_ELEMENTS_JS, UNSEARCHED_CONTENT_JS, NAME_SCAN_LIMIT;
9236
+ var BOUND_TARGET_SELECTOR, log22, FILTER_MATCHING_JS, DEEP_QUERY_JS, FIND_ELEMENTS_JS, UNSEARCHED_CONTENT_JS, NAME_SCAN_LIMIT;
9059
9237
  var init_targetNode = __esm({
9060
9238
  "src/runtime/dom/targetNode.ts"() {
9061
9239
  "use strict";
9062
9240
  init_errors3();
9063
9241
  init_messages();
9242
+ init_elementInfo();
9064
9243
  init_shared();
9065
9244
  init_frameScopedConnection();
9066
9245
  init_bdgWorld();
@@ -9071,16 +9250,17 @@ var init_targetNode = __esm({
9071
9250
  init_exitCodes();
9072
9251
  init_selectorFilters();
9073
9252
  BOUND_TARGET_SELECTOR = "__bdg_bound_target__";
9074
- log24 = createLogger("dom");
9253
+ log22 = createLogger("dom");
9075
9254
  FILTER_MATCHING_JS = `(shadowRoots) => {
9255
+ const composed = ${COMPOSED_JS};
9256
+ const flatText = ${FLAT_TEXT_JS};
9076
9257
  const skipped = /^(script|style|noscript|template)$/;
9077
9258
  const hiddenText = (el) => {
9078
- const walker = el.ownerDocument.createTreeWalker(el, 5, {
9079
- acceptNode: (node) => (node.nodeType === 1 && skipped.test(node.localName) ? 2 : 1)
9080
- });
9259
+ const nodes = el.localName === 'slot' ? el.assignedNodes({ flatten: true }) : (el.shadowRoot || el).childNodes;
9081
9260
  let text = '';
9082
- for (let node = walker.nextNode(); node; node = walker.nextNode()) {
9261
+ for (const node of nodes) {
9083
9262
  if (node.nodeType === 3) text += node.data;
9263
+ else if (node.nodeType === 1 && !skipped.test(node.localName)) text += hiddenText(node);
9084
9264
  }
9085
9265
  return text;
9086
9266
  };
@@ -9088,7 +9268,8 @@ var init_targetNode = __esm({
9088
9268
  if (el.localName === 'input' && /^(submit|button|reset)$/i.test(el.type)) return el.value;
9089
9269
  const rendered = typeof el.innerText === 'string' &&
9090
9270
  (typeof el.checkVisibility !== 'function' || el.checkVisibility({ visibilityProperty: true }));
9091
- return (rendered ? el.innerText : hiddenText(el)).replace(/\\s+/g, ' ').trim();
9271
+ const text = !rendered ? hiddenText(el) : composed(el) ? flatText(el, Infinity, true) : el.innerText;
9272
+ return text.replace(/\\s+/g, ' ').trim();
9092
9273
  };
9093
9274
  const passes = (el, filter) => {
9094
9275
  if (filter.kind === 'visible') {
@@ -9345,7 +9526,7 @@ async function noMatchContext(selector) {
9345
9526
  ...typeof value.shadowHost === "number" && compounds[value.shadowHost] && { shadowHost: compounds[value.shadowHost] }
9346
9527
  };
9347
9528
  } catch (error) {
9348
- log25.debug(`Could not read the page after no match: ${getErrorMessage(error)}`);
9529
+ log23.debug(`Could not read the page after no match: ${getErrorMessage(error)}`);
9349
9530
  return {};
9350
9531
  }
9351
9532
  }
@@ -9358,7 +9539,7 @@ async function documentReadyState() {
9358
9539
  const { result } = evaluated.data?.result ?? {};
9359
9540
  return typeof result?.value === "string" ? result.value : void 0;
9360
9541
  } catch (error) {
9361
- log25.debug(`Could not read document.readyState: ${getErrorMessage(error)}`);
9542
+ log23.debug(`Could not read document.readyState: ${getErrorMessage(error)}`);
9362
9543
  return void 0;
9363
9544
  }
9364
9545
  }
@@ -9464,7 +9645,7 @@ async function queryDOMElements(selector, limit = 0) {
9464
9645
  selector,
9465
9646
  (arrayObjectId) => elementsWithDetails(arrayObjectId, described)
9466
9647
  );
9467
- if (total > 20) log25.debug(`Queried ${total} elements with selector: ${selector}`);
9648
+ if (total > 20) log23.debug(`Queried ${total} elements with selector: ${selector}`);
9468
9649
  return { selector, count: total, nodes: elements.map(queryNode) };
9469
9650
  }
9470
9651
  function queryNode(element, index) {
@@ -9521,7 +9702,7 @@ async function getDomContext(ref, options = {}) {
9521
9702
  await callCDP("DOM.enable", {});
9522
9703
  const desc = await describeNode(ref);
9523
9704
  if (!desc) {
9524
- log25.debug(`No DOM context for ${JSON.stringify(ref)}`);
9705
+ log23.debug(`No DOM context for ${JSON.stringify(ref)}`);
9525
9706
  return null;
9526
9707
  }
9527
9708
  const full = options.full === true;
@@ -9681,11 +9862,11 @@ async function pageDocumentId() {
9681
9862
  return typeof result?.value === "string" ? result.value : void 0;
9682
9863
  } catch (error) {
9683
9864
  if (error instanceof CommandError) throw error;
9684
- log25.debug(`Page document not identified: ${getErrorMessage(error)}`);
9865
+ log23.debug(`Page document not identified: ${getErrorMessage(error)}`);
9685
9866
  return void 0;
9686
9867
  }
9687
9868
  }
9688
- var log25, CDP_CONCURRENCY_LIMIT, queryCount, MAX_HOST_COMPOUNDS, VIEWPORT_HINT_LIMIT, QUERY_CACHE_LIMIT, ELEMENT_DETAILS_FUNCTION, SLICE_FUNCTION, CHILDREN_LISTED, UNSHOWN_CHILD;
9869
+ var log23, CDP_CONCURRENCY_LIMIT, queryCount, MAX_HOST_COMPOUNDS, VIEWPORT_HINT_LIMIT, QUERY_CACHE_LIMIT, ELEMENT_DETAILS_FUNCTION, SLICE_FUNCTION, CHILDREN_LISTED, UNSHOWN_CHILD;
9689
9870
  var init_query = __esm({
9690
9871
  "src/commands/dom/helpers/query.ts"() {
9691
9872
  "use strict";
@@ -9703,7 +9884,7 @@ var init_query = __esm({
9703
9884
  init_exitCodes();
9704
9885
  init_selectorFilters();
9705
9886
  init_suggestions();
9706
- log25 = createLogger("dom");
9887
+ log23 = createLogger("dom");
9707
9888
  CDP_CONCURRENCY_LIMIT = 10;
9708
9889
  queryCount = 0;
9709
9890
  MAX_HOST_COMPOUNDS = 4;
@@ -9733,7 +9914,7 @@ var init_query = __esm({
9733
9914
 
9734
9915
  // src/commands/shared/outputFile.ts
9735
9916
  import * as fs15 from "fs";
9736
- import * as path5 from "path";
9917
+ import * as path6 from "path";
9737
9918
  function outputPathError(filePath, error, extension2) {
9738
9919
  const code = error.code ?? "";
9739
9920
  const problem = PATH_PROBLEMS[code];
@@ -9762,13 +9943,16 @@ function assertFilePath(filePath, extension2) {
9762
9943
  );
9763
9944
  }
9764
9945
  }
9765
- async function writeOutputFile(filePath, data, extension2) {
9946
+ async function writeOutputFile(filePath, data, extension2, mode) {
9766
9947
  assertFilePath(filePath, extension2);
9767
- const absolutePath = path5.resolve(filePath);
9948
+ const absolutePath = path6.resolve(filePath);
9768
9949
  try {
9769
- makeDirectory(path5.dirname(absolutePath));
9770
- if (typeof data === "string") await AtomicFileWriter.writeAsync(absolutePath, data);
9771
- else await AtomicFileWriter.writeBufferAsync(absolutePath, data);
9950
+ makeDirectory(path6.dirname(absolutePath));
9951
+ if (typeof data === "string") {
9952
+ await AtomicFileWriter.writeAsync(absolutePath, data, mode === void 0 ? {} : { mode });
9953
+ } else {
9954
+ await AtomicFileWriter.writeBufferAsync(absolutePath, data);
9955
+ }
9772
9956
  } catch (error) {
9773
9957
  throw outputPathError(filePath, error, extension2);
9774
9958
  }
@@ -9802,10 +9986,13 @@ var init_outputFile = __esm({
9802
9986
  });
9803
9987
 
9804
9988
  // src/runtime/page/userAgent.ts
9989
+ var log24;
9805
9990
  var init_userAgent = __esm({
9806
9991
  "src/runtime/page/userAgent.ts"() {
9807
9992
  "use strict";
9993
+ init_logging();
9808
9994
  init_errors();
9995
+ log24 = createLogger("session");
9809
9996
  }
9810
9997
  });
9811
9998
 
@@ -9819,7 +10006,7 @@ function viewportOverride(viewport, deviceScaleFactor = 0) {
9819
10006
  mobile
9820
10007
  };
9821
10008
  }
9822
- var log26, MOBILE_PIXEL_RATIO;
10009
+ var log25, MOBILE_PIXEL_RATIO;
9823
10010
  var init_emulation = __esm({
9824
10011
  "src/runtime/page/emulation.ts"() {
9825
10012
  "use strict";
@@ -9827,7 +10014,7 @@ var init_emulation = __esm({
9827
10014
  init_userAgent();
9828
10015
  init_logging();
9829
10016
  init_errors();
9830
- log26 = createLogger("session");
10017
+ log25 = createLogger("session");
9831
10018
  MOBILE_PIXEL_RATIO = 3;
9832
10019
  }
9833
10020
  });
@@ -9892,12 +10079,12 @@ async function waitForPostScrollStability() {
9892
10079
  const networkIdle = value?.networkIdle ?? 0;
9893
10080
  const domIdle = value?.domIdle ?? 0;
9894
10081
  if (networkIdle >= POST_SCROLL_NETWORK_IDLE_MS && domIdle >= POST_SCROLL_DOM_STABLE_MS) {
9895
- log27.debug(`Post-scroll stable: network ${networkIdle}ms, DOM ${domIdle}ms`);
10082
+ log26.debug(`Post-scroll stable: network ${networkIdle}ms, DOM ${domIdle}ms`);
9896
10083
  return;
9897
10084
  }
9898
10085
  await new Promise((resolve4) => setTimeout(resolve4, STABILITY_CHECK_INTERVAL_MS));
9899
10086
  }
9900
- log27.debug("Post-scroll stability timeout, proceeding anyway");
10087
+ log26.debug("Post-scroll stability timeout, proceeding anyway");
9901
10088
  } finally {
9902
10089
  await callBdgScript("Runtime.evaluate", {
9903
10090
  expression: `
@@ -10196,7 +10383,7 @@ async function paintedArea(ref, bounds) {
10196
10383
  height: bounds.height + top + bottom
10197
10384
  };
10198
10385
  } catch (error) {
10199
- log27.debug(`Could not measure overflowing content: ${String(error)}`);
10386
+ log26.debug(`Could not measure overflowing content: ${String(error)}`);
10200
10387
  return bounds;
10201
10388
  } finally {
10202
10389
  await callCDP("Runtime.releaseObjectGroup", { objectGroup }).catch(() => void 0);
@@ -10293,7 +10480,7 @@ function roundBounds(bounds) {
10293
10480
  height: Math.round(bounds.height)
10294
10481
  };
10295
10482
  }
10296
- var log27, POST_SCROLL_NETWORK_IDLE_MS, POST_SCROLL_DOM_STABLE_MS, POST_SCROLL_MAX_WAIT_MS, STABILITY_CHECK_INTERVAL_MS, OVERFLOW_SCAN_LIMIT, CONTENT_OVERFLOW_JS, OVERFLOW_SLACK;
10483
+ var log26, POST_SCROLL_NETWORK_IDLE_MS, POST_SCROLL_DOM_STABLE_MS, POST_SCROLL_MAX_WAIT_MS, STABILITY_CHECK_INTERVAL_MS, OVERFLOW_SCAN_LIMIT, CONTENT_OVERFLOW_JS, OVERFLOW_SLACK;
10297
10484
  var init_screenshot = __esm({
10298
10485
  "src/commands/dom/helpers/screenshot.ts"() {
10299
10486
  "use strict";
@@ -10308,7 +10495,7 @@ var init_screenshot = __esm({
10308
10495
  init_metadata();
10309
10496
  init_logging();
10310
10497
  init_exitCodes();
10311
- log27 = createLogger("dom");
10498
+ log26 = createLogger("dom");
10312
10499
  POST_SCROLL_NETWORK_IDLE_MS = 150;
10313
10500
  POST_SCROLL_DOM_STABLE_MS = 200;
10314
10501
  POST_SCROLL_MAX_WAIT_MS = 2e3;
@@ -13509,9 +13696,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
13509
13696
  * @param {string} [path]
13510
13697
  * @return {(string|null|Command)}
13511
13698
  */
13512
- executableDir(path7) {
13513
- if (path7 === void 0) return this._executableDir;
13514
- this._executableDir = path7;
13699
+ executableDir(path8) {
13700
+ if (path8 === void 0) return this._executableDir;
13701
+ this._executableDir = path8;
13515
13702
  return this;
13516
13703
  }
13517
13704
  /**
@@ -13818,8 +14005,9 @@ var OPTION_BEHAVIORS = {
13818
14005
  },
13819
14006
  "get:--full": {
13820
14007
  default: 'Semantic output shows the element text up to 500 characters (whitespace collapsed; close buttons such as "\xD7" and aria-hidden icons left out)',
13821
- whenEnabled: "Shows all of the element text; cannot be combined with --raw or --node-id",
13822
- tokenImpact: "A page-sized container can add thousands of tokens; target the element you need"
14008
+ whenEnabled: "Shows all of the element text; with --raw (or --node-id) prints the whole outer HTML instead of its first 20000 characters, and JSON outerHTML is whole too (else cut to 20000 with truncatedFrom, the original length)",
14009
+ automaticBehavior: `Without --full, --raw output cuts each element's HTML at 20000 characters and ends it with "\u2026 N more chars (use --full)"`,
14010
+ tokenImpact: "A page-sized container can add thousands of tokens; dom get body --raw --full on Wikipedia is about 3.4 MB. Target the element you need"
13823
14011
  },
13824
14012
  "get:--all": {
13825
14013
  default: "Returns first matching element only",
@@ -13831,30 +14019,42 @@ var OPTION_BEHAVIORS = {
13831
14019
  automaticBehavior: "Past the last match exits 81; a numeric index argument (a cached query index) past the indexed matches, or from an earlier page, exits 87 (re-run dom query)"
13832
14020
  },
13833
14021
  "query:--limit": {
13834
- default: "dom query and dom a11y query list the first 50 matches and say how many more there are; --json returns 1000 (dom query) or all of them (dom a11y query)",
14022
+ default: "dom query and dom a11y query list the first 50 matches and say how many more there are; --json lists the first 100 with count (all matches) and omitted (the rest)",
13835
14023
  whenEnabled: "Lists that many matches (0 = all), in human and JSON output; count is always the total, JSON omitted the rest",
13836
14024
  automaticBehavior: "Matches are cached for index-based access (bdg dom click 55 works even when 50 are listed): all of them for dom a11y query, the first 1000 (or --limit, if higher) for dom query, which describes only those, so a page with 50000 matches answers in under a second; an element the page and frame trees both report is listed once. Indices work with click, fill, hover, pressKey, scroll, submit, layout, get and listeners, also for elements of a cross-origin iframe of the same site (a consent dialog), whose scripts then run in that frame",
13837
- tokenImpact: "About one line per match; a page can have hundreds of links"
14025
+ tokenImpact: 'About one line per match (piped JSON about 160 bytes per dom query match, 230 per a11y match); a page can have thousands of links: on Wikipedia "United States" --limit 0 --json is 1.0 MB (dom query a) and 1.3 MB (dom a11y query role:link), the default 16 KB and 23 KB'
14026
+ },
14027
+ "tree:--limit": {
14028
+ default: "dom a11y tree lists the first 50 meaningful nodes depth-first, in human and JSON output; JSON count is the whole tree = nodes listed + omitted (cut by --limit/--depth) + skipped (never listed)",
14029
+ whenEnabled: "Lists that many nodes; 0 = all listed nodes (text boxes and empty wrappers are always skipped)",
14030
+ automaticBehavior: "Ignored nodes, text boxes, blank text, text repeating its parent name and nameless layout wrappers (generic, none, presentation, layout tables) are never listed (JSON skipped counts them); their children move up a level. For the raw tree with every node use bdg cdp Accessibility.getFullAXTree --json. JSON nodes carry depth (0 = root) instead of childIds; nodes outside the root (frame content) follow the root tree",
14031
+ tokenImpact: 'About 140 bytes per piped JSON node (7 KB by default); the whole tree of a long page is megabytes (Wikipedia "United States": 51k nodes, 20k listed, 2.6 MB with --limit 0 --json), so prefer --depth or dom a11y query "role:<role>"'
14032
+ },
14033
+ "tree:--depth": {
14034
+ default: "dom a11y tree lists every level (up to --limit nodes)",
14035
+ whenEnabled: "Lists nodes down to that level (0 = root only); deeper nodes are counted in omitted",
14036
+ tokenImpact: "An outline of a page (landmarks, headings) in a few levels"
13838
14037
  },
13839
14038
  "eval:--frame": {
13840
14039
  default: "Evaluates in the page's main frame",
13841
14040
  whenEnabled: "Evaluates in one iframe's main world (its own globals), including cross-origin (out-of-process) iframes; output gains a frame field (its URL)",
13842
14041
  automaticBehavior: "The value is matched as: a 0-based index (bdg dom frames order: document order of the <iframe> elements, nested ones depth-first, main page not counted), else an exact name/id attribute, else a case-insensitive part of the name, id or URL. Several matches fail with 81 listing them; none fails with 83 listing all frames. Frames are looked up on every call (a reloaded iframe is found again). An index that names another frame than in the last bdg dom frames listing (iframes added, removed or moved, or the page navigated) fails with 87 STALE_CACHE: re-run bdg dom frames or pick the frame by name."
13843
14042
  },
13844
- "console:-H": {
13845
- default: "Shows messages from current page load only (most recent navigation)",
13846
- whenEnabled: "Shows messages from ALL page loads during the session",
13847
- automaticBehavior: 'Page navigations create new "navigation contexts" - default filters to latest context'
14043
+ "eval:--full": {
14044
+ default: 'Human output prints the first 20000 characters of the value followed by "\u2026 N more chars (use --full)". In JSON a string result is cut to 20000 characters with truncatedFrom (the original length); an array result lists its first 100 elements with count (all of them) and omitted; an object or array whose JSON is still over 20000 characters becomes the first 20000 characters of its JSON text (a string; arrays keep count) with truncatedFrom (the length of the JSON text of the copy, which holds at most 1000 entries per list or object): a string result with truncatedFrom while type is object is such a JSON-text start',
14045
+ whenEnabled: "Prints the whole value, byte for byte; objects and arrays are copied with every entry (without --full at most 1000 per list or object)",
14046
+ tokenImpact: 'dom eval document.documentElement.outerHTML on Wikipedia is about 3.6 MB with --full; on a page with 20000 elements, [...document.querySelectorAll("*")].map(e => e.outerHTML) --json is 3 MB with --full and 23 KB without; select what you need in the expression instead'
14047
+ },
14048
+ "console:--full": {
14049
+ default: 'Message texts are cut: human output (summary, --list, --follow) at 200 characters followed by "\u2026 N more chars (use --full)"; JSON text at 10000 characters with truncatedFrom (the original length)',
14050
+ whenEnabled: "Message texts are printed whole, in human and JSON output",
14051
+ tokenImpact: "A page that logs a large payload or throws a long error can add megabytes; bdg details console <n> shows one message whole"
13848
14052
  },
13849
14053
  "console:--history": {
13850
14054
  default: "Shows messages from current page load only (most recent navigation)",
13851
14055
  whenEnabled: "Shows messages from ALL page loads during the session",
13852
14056
  automaticBehavior: 'Page navigations create new "navigation contexts" - default filters to latest context'
13853
14057
  },
13854
- "console:-l": {
13855
- default: "Smart summary with errors deduplicated and warnings grouped: the newest 50 distinct errors and warnings, with a note for the earlier ones. The session keeps the newest 10000 messages; dropped ones are counted (dropped in JSON)",
13856
- whenEnabled: "Lists all messages chronologically without deduplication"
13857
- },
13858
14058
  "console:--list": {
13859
14059
  default: "Smart summary with errors deduplicated and warnings grouped: the newest 50 distinct errors and warnings, with a note for the earlier ones. The session keeps the newest 10000 messages; dropped ones are counted (dropped in JSON)",
13860
14060
  whenEnabled: "Lists all messages chronologically without deduplication"
@@ -14006,6 +14206,7 @@ var OPTION_BEHAVIORS = {
14006
14206
  },
14007
14207
  "inspect:--no-hints": {
14008
14208
  default: "Hints at the element's own author declarations that have no effect (flex/grid properties without flex or grid, item properties without a flex or grid parent, offsets on static elements, sizes on inline ones, var() of an unset custom property, form controls in the browser's font), within a 1 s budget; hints none when nothing was found",
14209
+ automaticBehavior: "After a hint read times out, later inspects on the same page skip the hints without waiting (hints skipped: this page's stylesheets are slow to read) until a read is fast again, a stylesheet changes or it navigates; --rules and --why still wait up to 5 s. Matched rules that took over 300 ms are reused for up to 5 s, until a command that may change the page or a stylesheet or DOM change",
14009
14210
  whenEnabled: "Skips the hints and does not read the matched rules"
14010
14211
  },
14011
14212
  "scroll:--down": {
@@ -14046,56 +14247,57 @@ var OPTION_BEHAVIORS = {
14046
14247
  whenEnabled: "Quick scan: field names, types, and required status only",
14047
14248
  tokenImpact: "Reduces output ~50% for initial discovery"
14048
14249
  },
14250
+ "peek:--full": {
14251
+ default: 'Console message texts are cut: human output at 200 characters (compact output also at 2 lines) followed by "\u2026 N more chars (use --full)"; JSON text at 10000 characters with truncatedFrom (the original length)',
14252
+ whenEnabled: "Console message texts are printed whole, in human and JSON output",
14253
+ tokenImpact: "A page that logs a large payload can add megabytes per peek"
14254
+ },
14049
14255
  "peek:--type": {
14050
14256
  whenEnabled: "Filters network requests by CDP resource type. Case-insensitive, comma-separated. Valid: Document, Stylesheet, Image, Media, Font, Script, XHR, Fetch, WebSocket, etc."
14051
14257
  },
14052
- "peek:-f": {
14053
- default: "Shows snapshot of current data",
14054
- whenEnabled: "Continuous monitoring (like tail -f): refreshes every second, or every --interval ms (100-60000). Replaces the deprecated bdg tail",
14055
- automaticBehavior: FOLLOW_BEHAVIOR
14056
- },
14057
14258
  "peek:--follow": {
14058
14259
  default: "Shows snapshot of current data",
14059
14260
  whenEnabled: "Continuous monitoring (like tail -f): refreshes every second, or every --interval ms (100-60000). Replaces the deprecated bdg tail",
14060
14261
  automaticBehavior: FOLLOW_BEHAVIOR
14061
14262
  },
14062
- "console:-f": {
14263
+ "console:--follow": {
14063
14264
  default: "Prints the messages logged so far and exits",
14064
14265
  whenEnabled: "Streams new messages as they come (the last --last at start)",
14065
14266
  automaticBehavior: FOLLOW_BEHAVIOR
14066
14267
  },
14067
- "list:-f": {
14268
+ "list:--follow": {
14068
14269
  default: "Lists the requests captured so far and exits",
14069
14270
  whenEnabled: "Streams requests as they finish",
14070
14271
  automaticBehavior: FOLLOW_BEHAVIOR
14071
14272
  },
14072
- "peek:-v": {
14273
+ "har:--include-sensitive": {
14274
+ default: 'The HAR is sanitized: values become "[redacted]" for Authorization, Proxy-Authorization, Authentication, Cookie and Set-Cookie headers, X-*key/token/secret/auth headers and headers with an api-key/apikey/token/secret/jwt/subscription-key/session(-id) segment (www-authenticate is kept); every cookie value; query and fragment parameters named like credentials (plus code, sig, key) in the request URL, queryString, redirectURL and Location/Referer headers ("%5Bredacted%5D" in URLs); and password/token/secret/key/session/signature fields of JSON (primitives at any depth under such a name), form-urlencoded (also sniffed when the Content-Type says otherwise) and multipart request bodies. A JSON body too deep to walk becomes "[redacted]" whole. Header, cookie and parameter names, cookie attributes, headersSize and bodySize stay; log.comment and JSON sanitized: true say so',
14275
+ whenEnabled: "Writes every captured value (JSON sanitized: false); human output warns that the file holds credentials",
14276
+ automaticBehavior: "Matching is by name, so it over-redacts: harmless values under credential-looking names (tokenCount: 5, sessionLength) are replaced too, and credentials under other names are kept. Unlike Chrome DevTools, which drops these headers and empties cookies, names are kept so the HAR still shows a request was authenticated. Response bodies and WebSocket messages are not redacted. HAR files are written readable by their owner only (0600). network headers and network getCookies always show real values"
14277
+ },
14278
+ "peek:--verbose": {
14073
14279
  default: "Compact output (truncated URLs, no resource types)",
14074
14280
  whenEnabled: "Verbose output with full URLs and resource types"
14075
14281
  },
14076
- "start:--headless": {
14282
+ "bdg:--headless": {
14077
14283
  default: "A window when there is a display: on macOS unless over SSH (SSH_CONNECTION, SSH_TTY) or CI is set; on Linux when DISPLAY or WAYLAND_DISPLAY is set. Servers, containers and CI run headless",
14078
14284
  whenEnabled: "Chrome runs without a window (pass it when running unattended on a Mac)"
14079
14285
  },
14080
- "start:--no-headless": {
14286
+ "bdg:--no-headless": {
14081
14287
  default: "A window when there is a display: on macOS unless over SSH (SSH_CONNECTION, SSH_TTY) or CI is set; on Linux when DISPLAY or WAYLAND_DISPLAY is set",
14082
14288
  whenEnabled: "Chrome shows its window even without a detected display (it fails without one)"
14083
14289
  },
14084
- "start:--all": {
14290
+ "bdg:--all": {
14085
14291
  default: "Tracking/analytics requests and console noise are filtered; bodies of binary responses (images, fonts) are not captured",
14086
14292
  whenEnabled: "Everything is captured, including binary response bodies (base64, flagged by responseBodyBase64, within --max-body-size)",
14087
14293
  tokenImpact: "details network --json and HAR exports can grow considerably on media-heavy pages"
14088
14294
  },
14089
- "peek:--verbose": {
14090
- default: "Compact output (truncated URLs, no resource types)",
14091
- whenEnabled: "Verbose output with full URLs and resource types"
14092
- },
14093
14295
  "bdg:--session": {
14094
14296
  default: "The default session in ~/.bdg (or $BDG_SESSION_DIR); BDG_SESSION=<name> selects a named session like the flag",
14095
14297
  whenEnabled: "Uses the named session in ~/.bdg/sessions/<name>/ (or $BDG_SESSION_DIR/sessions/<name>/) with its own daemon, Chrome, profile and port; every command (status, stop, cleanup, ...) acts on that session only",
14096
14298
  automaticBehavior: 'Accepted before or after any subcommand; --session wins over BDG_SESSION. Names are case-insensitive (lower-cased: ALPHA is alpha). Without --port a named session takes the first free port above 9222 not claimed by another running session, and keeps it in port.txt. Names: 1-40 letters, digits, "-" or "_", starting with a letter or digit (exit 81 otherwise, also when the socket path would be too long). Hints and suggestions in its output carry --session <name>'
14097
14299
  },
14098
- "cleanup:-f": {
14300
+ "cleanup:--force": {
14099
14301
  default: "Refuses to run while a session is active; removes files left by a crashed session",
14100
14302
  whenEnabled: "Kills the running daemon and its Chrome first (use when a session is stuck)"
14101
14303
  },
@@ -14130,23 +14332,16 @@ var OPTION_BEHAVIORS = {
14130
14332
  default: "Chrome launched by bdg is always closed on stop; an attached Chrome (--chrome-ws-url) is left running",
14131
14333
  whenEnabled: "No additional effect; kept for compatibility"
14132
14334
  },
14133
- "status:-v": {
14134
- default: "Basic session status (daemon running, session active, URL)",
14135
- whenEnabled: "Includes Chrome diagnostics and CDP connection details"
14136
- },
14137
14335
  "status:--verbose": {
14138
14336
  default: "Basic session status (daemon running, session active, URL)",
14139
14337
  whenEnabled: "Includes Chrome diagnostics and CDP connection details"
14140
14338
  }
14141
14339
  };
14142
- function buildKey(commandName, flags) {
14143
- const firstFlag = flags.split(",")[0] ?? flags;
14144
- const flagName = firstFlag.trim().split(" ")[0] ?? firstFlag.trim();
14145
- return `${commandName}:${flagName}`;
14340
+ function behaviorKey(commandName, option) {
14341
+ return `${commandName}:${option.long ?? option.short ?? option.flags}`;
14146
14342
  }
14147
- function getOptionBehavior(commandName, flags) {
14148
- const key = buildKey(commandName, flags);
14149
- return OPTION_BEHAVIORS[key];
14343
+ function getOptionBehavior(commandName, option) {
14344
+ return OPTION_BEHAVIORS[behaviorKey(commandName, option)];
14150
14345
  }
14151
14346
 
14152
14347
  // node_modules/chrome-launcher/dist/chrome-finder.js
@@ -14815,9 +15010,9 @@ async function waitUntil(condition, timeoutMs, pollMs = 25) {
14815
15010
  // src/utils/file.ts
14816
15011
  init_logging();
14817
15012
  import * as fs9 from "fs";
14818
- function safeRemoveFile(path7, context, log36) {
15013
+ function safeRemoveFile(path8, context, log36) {
14819
15014
  try {
14820
- fs9.rmSync(path7, { force: true });
15015
+ fs9.rmSync(path8, { force: true });
14821
15016
  return true;
14822
15017
  } catch (error) {
14823
15018
  logDebugError(log36, `remove ${context}`, error);
@@ -15258,7 +15453,7 @@ function convertOption(option, commandName) {
15258
15453
  if (option.argChoices) {
15259
15454
  metadata.choices = option.argChoices;
15260
15455
  }
15261
- const behavior = getOptionBehavior(commandName, option.flags);
15456
+ const behavior = getOptionBehavior(commandName, option);
15262
15457
  if (behavior) {
15263
15458
  metadata.behavior = behavior;
15264
15459
  }
@@ -15311,7 +15506,7 @@ function convertCompactCommand(command) {
15311
15506
  return {
15312
15507
  name: command.name(),
15313
15508
  ...aliases.length > 0 && { aliases },
15314
- description: command.description().split("\n")[0] ?? "",
15509
+ description: command.summary() || (command.description().split("\n")[0] ?? ""),
15315
15510
  ...args && { arguments: args },
15316
15511
  ...options.length > 0 && {
15317
15512
  options: Object.fromEntries(options.map((option) => [option.flags, option.description]))
@@ -15408,14 +15603,14 @@ init_errors3();
15408
15603
  init_messages();
15409
15604
  init_exitCodes();
15410
15605
  init_suggestions();
15411
- var COMMANDER_HINT = /\n?\(Did you mean (.+)\?\)\s*$/;
15606
+ var COMMANDER_HINT = /\n?\(Did you mean (?:one of )?(.+)\?\)\s*$/;
15412
15607
  function helpTopicPath(args) {
15413
15608
  const firstOption = args.findIndex((arg) => arg.startsWith("-"));
15414
15609
  return firstOption === -1 ? args : args.slice(0, firstOption);
15415
15610
  }
15416
15611
  function assertKnownHelpTopic(program2, topic) {
15417
15612
  let command = program2;
15418
- const path7 = [];
15613
+ const path8 = [];
15419
15614
  for (const word of topic) {
15420
15615
  if (command.commands.length === 0 || word === "help") return;
15421
15616
  const sub = command.commands.find((c) => c.name() === word || c.aliases().includes(word));
@@ -15425,8 +15620,8 @@ function assertKnownHelpTopic(program2, topic) {
15425
15620
  command.commands.map((c) => c.name())
15426
15621
  );
15427
15622
  const err = unknownHelpTopicError(
15428
- [...path7, word].join(" "),
15429
- closest && [...path7, closest].join(" ")
15623
+ [...path8, word].join(" "),
15624
+ closest && [...path8, closest].join(" ")
15430
15625
  );
15431
15626
  throw new CommandError(
15432
15627
  err.message,
@@ -15434,7 +15629,7 @@ function assertKnownHelpTopic(program2, topic) {
15434
15629
  EXIT_CODES.INVALID_ARGUMENTS
15435
15630
  );
15436
15631
  }
15437
- path7.push(sub.name());
15632
+ path8.push(sub.name());
15438
15633
  command = sub;
15439
15634
  }
15440
15635
  }
@@ -15442,7 +15637,10 @@ function splitCommanderHint(text) {
15442
15637
  const message = text.replace(/^error:\s*/i, "");
15443
15638
  const hint = COMMANDER_HINT.exec(message);
15444
15639
  if (!hint) return { message };
15445
- return { message: message.slice(0, hint.index).trim(), suggestion: `Did you mean: ${hint[1]}?` };
15640
+ return {
15641
+ message: message.slice(0, hint.index).trim(),
15642
+ suggestion: didYouMeanSuggestion((hint[1] ?? "").split(", "))
15643
+ };
15446
15644
  }
15447
15645
  var UNKNOWN_OPTION = /unknown option '([^'=]+)/;
15448
15646
  function closestOption(flag, command) {
@@ -15473,7 +15671,7 @@ function usageErrorDetails(error, command, argv = process.argv) {
15473
15671
 
15474
15672
  // src/commands/start.ts
15475
15673
  import fs11 from "node:fs";
15476
- import os3 from "node:os";
15674
+ import os4 from "node:os";
15477
15675
 
15478
15676
  // src/commands/shared/commonOptions.ts
15479
15677
  function jsonOption() {
@@ -15498,14 +15696,14 @@ init_messages();
15498
15696
 
15499
15697
  // src/utils/version.ts
15500
15698
  import { readFileSync as readFileSync6 } from "fs";
15501
- import { join as join5 } from "path";
15699
+ import { join as join6 } from "path";
15502
15700
  var cachedVersion = "";
15503
15701
  function getVersion() {
15504
15702
  if (cachedVersion) {
15505
15703
  return cachedVersion;
15506
15704
  }
15507
15705
  try {
15508
- const pkgPath = join5(PACKAGE_ROOT, "package.json");
15706
+ const pkgPath = join6(PACKAGE_ROOT, "package.json");
15509
15707
  const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
15510
15708
  cachedVersion = pkg.version ?? "0.0.0";
15511
15709
  } catch {
@@ -15519,6 +15717,9 @@ var VERSION = getVersion();
15519
15717
  function buildSuccessResponse(data) {
15520
15718
  return { version: VERSION, success: true, data };
15521
15719
  }
15720
+ function stringifyEnvelope(envelope) {
15721
+ return process.stdout.isTTY ? JSON.stringify(envelope, null, 2) : JSON.stringify(envelope);
15722
+ }
15522
15723
  var OutputBuilder = class {
15523
15724
  /**
15524
15725
  * Build an error response envelope.
@@ -15557,9 +15758,7 @@ function handleValidationError(error, json) {
15557
15758
  if (error.metadata.suggestion) {
15558
15759
  errorOptions.suggestion = error.metadata.suggestion;
15559
15760
  }
15560
- console.log(
15561
- JSON.stringify(OutputBuilder.buildJsonError(error.message, errorOptions), null, 2)
15562
- );
15761
+ console.log(stringifyEnvelope(OutputBuilder.buildJsonError(error.message, errorOptions)));
15563
15762
  } else {
15564
15763
  console.error(genericError(error.message));
15565
15764
  if (error.metadata.suggestion) console.error(escapeControlChars(error.metadata.suggestion));
@@ -15571,7 +15770,7 @@ function handleValidationError(error, json) {
15571
15770
  const envelope = OutputBuilder.buildJsonError(message, {
15572
15771
  exitCode: EXIT_CODES.INVALID_ARGUMENTS
15573
15772
  });
15574
- console.log(JSON.stringify(envelope, null, 2));
15773
+ console.log(stringifyEnvelope(envelope));
15575
15774
  } else {
15576
15775
  console.error(genericError(message));
15577
15776
  }
@@ -15597,20 +15796,18 @@ function noActiveSessionError() {
15597
15796
  async function runJsonCommand(fn) {
15598
15797
  try {
15599
15798
  const data = await fn();
15600
- console.log(JSON.stringify(buildSuccessResponse(data), null, 2));
15799
+ console.log(stringifyEnvelope(buildSuccessResponse(data)));
15601
15800
  process.exit(EXIT_CODES.SUCCESS);
15602
15801
  } catch (caught) {
15603
15802
  const error = isDaemonConnectionError(caught) ? noActiveSessionError() : caught instanceof IPCTimeoutError ? timeoutError(caught) : caught;
15604
15803
  const exitCode = getErrorExitCode(error, EXIT_CODES.UNHANDLED_EXCEPTION);
15605
15804
  const suggestion = error instanceof CommandError && typeof error.metadata["suggestion"] === "string" ? error.metadata["suggestion"] : void 0;
15606
15805
  console.log(
15607
- JSON.stringify(
15806
+ stringifyEnvelope(
15608
15807
  OutputBuilder.buildJsonError(getErrorMessage(error), {
15609
15808
  exitCode,
15610
15809
  ...suggestion && { suggestion }
15611
- }),
15612
- null,
15613
- 2
15810
+ })
15614
15811
  )
15615
15812
  );
15616
15813
  process.exit(exitCode);
@@ -15636,13 +15833,11 @@ async function runCommand(handler, options, formatter) {
15636
15833
  const exitCode = result.exitCode ?? EXIT_CODES.UNHANDLED_EXCEPTION;
15637
15834
  if (options.json) {
15638
15835
  console.log(
15639
- JSON.stringify(
15836
+ stringifyEnvelope(
15640
15837
  OutputBuilder.buildJsonError(result.error ?? "Unknown error", {
15641
15838
  ...result.errorContext,
15642
15839
  exitCode
15643
- }),
15644
- null,
15645
- 2
15840
+ })
15646
15841
  )
15647
15842
  );
15648
15843
  } else {
@@ -15663,12 +15858,12 @@ async function runCommand(handler, options, formatter) {
15663
15858
  console.error(escapeControlChars(result.hint));
15664
15859
  }
15665
15860
  if (options.json) {
15666
- console.log(JSON.stringify(buildSuccessResponse(result.data), null, 2));
15861
+ console.log(stringifyEnvelope(buildSuccessResponse(result.data)));
15667
15862
  } else if (formatter) {
15668
15863
  const formattedOutput = formatter(result.data);
15669
15864
  console.log(escapeControlChars(formattedOutput));
15670
15865
  } else {
15671
- console.log(JSON.stringify(buildSuccessResponse(result.data), null, 2));
15866
+ console.log(stringifyEnvelope(buildSuccessResponse(result.data)));
15672
15867
  }
15673
15868
  process.exit(EXIT_CODES.SUCCESS);
15674
15869
  } catch (caught) {
@@ -15676,13 +15871,11 @@ async function runCommand(handler, options, formatter) {
15676
15871
  if (error instanceof CommandError) {
15677
15872
  if (options.json) {
15678
15873
  console.log(
15679
- JSON.stringify(
15874
+ stringifyEnvelope(
15680
15875
  OutputBuilder.buildJsonError(error.message, {
15681
15876
  ...error.metadata,
15682
15877
  exitCode: error.exitCode
15683
- }),
15684
- null,
15685
- 2
15878
+ })
15686
15879
  )
15687
15880
  );
15688
15881
  } else {
@@ -15697,13 +15890,11 @@ async function runCommand(handler, options, formatter) {
15697
15890
  if (isDaemonConnectionError(error)) {
15698
15891
  if (options.json) {
15699
15892
  console.log(
15700
- JSON.stringify(
15893
+ stringifyEnvelope(
15701
15894
  OutputBuilder.buildJsonError(noActiveSessionMessage(), {
15702
15895
  suggestion: startSessionSuggestion(),
15703
15896
  exitCode: EXIT_CODES.RESOURCE_NOT_FOUND
15704
- }),
15705
- null,
15706
- 2
15897
+ })
15707
15898
  )
15708
15899
  );
15709
15900
  } else {
@@ -15713,9 +15904,7 @@ async function runCommand(handler, options, formatter) {
15713
15904
  }
15714
15905
  const exitCode = getErrorExitCode(error, EXIT_CODES.UNHANDLED_EXCEPTION);
15715
15906
  if (options.json) {
15716
- console.log(
15717
- JSON.stringify(OutputBuilder.buildJsonError(errorMessage, { exitCode }), null, 2)
15718
- );
15907
+ console.log(stringifyEnvelope(OutputBuilder.buildJsonError(errorMessage, { exitCode })));
15719
15908
  } else {
15720
15909
  console.error(genericError(errorMessage));
15721
15910
  }
@@ -15770,6 +15959,9 @@ function lastSessionEndText(end) {
15770
15959
  function endedSessionText(label, end) {
15771
15960
  return `${label} ended ${sessionEndText(end)}`;
15772
15961
  }
15962
+ function untrustedSessionText(label, why) {
15963
+ return `${label}: ${why}`;
15964
+ }
15773
15965
  function sessionEndText(end) {
15774
15966
  const why = {
15775
15967
  crash: "Chrome crashed or was closed",
@@ -15827,7 +16019,7 @@ var SessionDirError = class extends DaemonError {
15827
16019
  // src/daemon/launcher.ts
15828
16020
  import { spawn } from "child_process";
15829
16021
  import fs10 from "fs";
15830
- import { join as join6 } from "path";
16022
+ import { join as join7 } from "path";
15831
16023
  init_messages();
15832
16024
  init_paths();
15833
16025
  init_logging();
@@ -15838,6 +16030,7 @@ var log8 = createLogger("launcher");
15838
16030
  var DAEMON_READY_TIMEOUT_MS = 5e3;
15839
16031
  var DAEMON_READY_POLL_MS = 20;
15840
16032
  async function launchDaemon() {
16033
+ assertUsableSessionDir();
15841
16034
  if (await isDaemonAlive()) {
15842
16035
  log8.debug("Daemon already running");
15843
16036
  return void 0;
@@ -15848,11 +16041,10 @@ async function launchDaemon() {
15848
16041
  "DAEMON_SCRIPT_NOT_FOUND"
15849
16042
  );
15850
16043
  }
15851
- assertUsableSessionDir();
15852
16044
  await stopUnreachableDaemon();
15853
- const logPath = join6(getSessionDir(), "daemon.log");
16045
+ const logPath = join7(getSessionDir(), "daemon.log");
15854
16046
  rotateLog(logPath);
15855
- const logFd = fs10.openSync(logPath, "a");
16047
+ const logFd = openDaemonLog(logPath);
15856
16048
  log8.debug(`Starting daemon: ${DAEMON_SCRIPT_PATH}`);
15857
16049
  const daemon = spawn(process.execPath, [DAEMON_SCRIPT_PATH], {
15858
16050
  detached: true,
@@ -15908,6 +16100,8 @@ function assertUsableSessionDir() {
15908
16100
  problem.denied ? EXIT_CODES.PERMISSION_DENIED : EXIT_CODES.SESSION_FILE_ERROR
15909
16101
  );
15910
16102
  }
16103
+ const untrusted = secureSessionDir(dir);
16104
+ if (untrusted) fail(untrustedSessionDirError(untrusted));
15911
16105
  try {
15912
16106
  ensureSessionDir();
15913
16107
  fs10.accessSync(dir, fs10.constants.W_OK);
@@ -15920,6 +16114,18 @@ function assertUsableSessionDir() {
15920
16114
  );
15921
16115
  }
15922
16116
  }
16117
+ var DAEMON_LOG_FLAGS = fs10.constants.O_WRONLY | fs10.constants.O_APPEND | fs10.constants.O_CREAT | (fs10.constants.O_NOFOLLOW ?? 0);
16118
+ function openDaemonLog(logPath) {
16119
+ try {
16120
+ return fs10.openSync(logPath, DAEMON_LOG_FLAGS, 384);
16121
+ } catch (error) {
16122
+ const err = daemonLogNotOpenedError(
16123
+ logPath,
16124
+ error.code ?? getErrorMessage(error)
16125
+ );
16126
+ throw new SessionDirError(err.message, err.suggestion);
16127
+ }
16128
+ }
15923
16129
  var MAX_LOG_BYTES = 5 * 1024 * 1024;
15924
16130
  function rotateLog(logPath) {
15925
16131
  try {
@@ -15939,7 +16145,7 @@ async function waitForDaemonReady(hasExited) {
15939
16145
  }
15940
16146
  if (hasExited() && !await isDaemonAlive()) {
15941
16147
  throw new DaemonStartupError(
15942
- `Daemon exited during startup. See ${join6(getSessionDir(), "daemon.log")}`,
16148
+ `Daemon exited during startup. See ${join7(getSessionDir(), "daemon.log")}`,
15943
16149
  "DAEMON_EXITED"
15944
16150
  );
15945
16151
  }
@@ -16262,7 +16468,7 @@ function reportStartOutcome(outcome, options) {
16262
16468
  exitCode: outcome.exitCode,
16263
16469
  ...outcome.details
16264
16470
  });
16265
- console.log(JSON.stringify(envelope, null, 2));
16471
+ console.log(stringifyEnvelope(envelope));
16266
16472
  } else {
16267
16473
  console.error(escapeControlChars(outcome.human));
16268
16474
  }
@@ -16283,7 +16489,7 @@ function reportStartOutcome(outcome, options) {
16283
16489
  daemonPid: data.daemonPid,
16284
16490
  ...autoStopAt && { autoStopAt: autoStopAt.toISOString() }
16285
16491
  };
16286
- console.log(JSON.stringify(buildSuccessResponse(result), null, 2));
16492
+ console.log(stringifyEnvelope(buildSuccessResponse(result)));
16287
16493
  } else {
16288
16494
  const page = {
16289
16495
  url: data.targetUrl,
@@ -16524,9 +16730,10 @@ init_errors();
16524
16730
  var CDP_HTTP_TIMEOUT_MS = 5e3;
16525
16731
  async function probeDevToolsEndpoint(port, logger, options) {
16526
16732
  const url = `${options?.secure ? "https" : "http"}://${options?.host ?? HTTP_LOCALHOST}:${port}/json/version`;
16733
+ const timeoutMs = options?.timeoutMs ?? CDP_HTTP_TIMEOUT_MS;
16527
16734
  let response;
16528
16735
  try {
16529
- response = await fetch(url, { signal: AbortSignal.timeout(CDP_HTTP_TIMEOUT_MS) });
16736
+ response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
16530
16737
  } catch (error) {
16531
16738
  logger?.debug(`Chrome version request failed: ${getErrorMessage(error)} (${url})`);
16532
16739
  return { kind: "unreachable" };
@@ -16545,7 +16752,7 @@ async function probeDevToolsEndpoint(port, logger, options) {
16545
16752
  init_suggestions();
16546
16753
  init_url();
16547
16754
  function expandHome(value) {
16548
- return value.startsWith("~/") ? value.replace(/^~/, os3.homedir()) : value;
16755
+ return value.startsWith("~/") ? value.replace(/^~/, os4.homedir()) : value;
16549
16756
  }
16550
16757
  function extractUserDataDirFromFlags(flags) {
16551
16758
  const rest = [];
@@ -16837,6 +17044,10 @@ function assertValidChromeWsUrl(url) {
16837
17044
  );
16838
17045
  }
16839
17046
 
17047
+ // src/index.ts
17048
+ init_errors3();
17049
+ init_messages();
17050
+
16840
17051
  // src/commands/cdp.ts
16841
17052
  init_protocol();
16842
17053
  init_schema();
@@ -17217,7 +17428,7 @@ function getMethodHint(methodName, result) {
17217
17428
  return void 0;
17218
17429
  }
17219
17430
  function registerCdpCommand(program2) {
17220
- program2.command("cdp").description(
17431
+ program2.command("cdp").summary("CDP protocol introspection and execution").description(
17221
17432
  "CDP protocol introspection and execution\n Discovery: --list, --search, --describe\n Execution: case-insensitive (network.getcookies works)"
17222
17433
  ).argument("[method]", "CDP method name (e.g., Network.getCookies, network.getcookies)").addOption(new Option("--params <json>", "Method parameters as JSON")).addOption(
17223
17434
  new Option("--list", "List all domains or methods in a domain").conflicts([
@@ -17855,7 +18066,7 @@ function handleDaemonConnectionError(error, options) {
17855
18066
  exitCode,
17856
18067
  ...suggestion && { suggestion }
17857
18068
  });
17858
- console.log(follow ? JSON.stringify(envelope) : JSON.stringify(envelope, null, 2));
18069
+ console.log(follow ? JSON.stringify(envelope) : stringifyEnvelope(envelope));
17859
18070
  } else {
17860
18071
  console.error(genericError(message));
17861
18072
  }
@@ -17941,9 +18152,17 @@ async function fetchPreviewData(query = {}) {
17941
18152
  async function fetchNetworkRequests(withHeaders = false) {
17942
18153
  const result = await fetchPreviewData({ lastN: 0, only: "network", withHeaders });
17943
18154
  if (!result.success) return result;
18155
+ const { totals, pageCrashedAt } = result.data.output;
17944
18156
  return {
17945
18157
  success: true,
17946
- data: { requests: result.data.network, pageCrashedAt: result.data.output.pageCrashedAt }
18158
+ data: {
18159
+ requests: result.data.network,
18160
+ pageCrashedAt,
18161
+ evictions: {
18162
+ requestsDropped: totals?.networkDropped ?? 0,
18163
+ bodiesEvicted: totals?.networkBodiesEvicted ?? 0
18164
+ }
18165
+ }
17947
18166
  };
17948
18167
  }
17949
18168
  async function fetchConsoleMessages() {
@@ -18010,9 +18229,24 @@ async function setupFollowMode(refreshFn, options) {
18010
18229
  process.on("SIGTERM", () => stop2(EXIT_CODES.TERMINATED));
18011
18230
  }
18012
18231
 
18232
+ // src/commands/console.ts
18233
+ init_constants();
18234
+
18013
18235
  // src/ui/formatters/console.ts
18014
18236
  init_commands();
18015
18237
 
18238
+ // src/ui/formatters/console/chronological.ts
18239
+ init_constants();
18240
+
18241
+ // src/ui/formatters/longValues.ts
18242
+ init_commands();
18243
+ init_strings();
18244
+ function capForDisplay(text, maxLength, full) {
18245
+ if (full) return text;
18246
+ const capped = capLength(text, maxLength);
18247
+ return capped.truncatedFrom === void 0 ? text : `${capped.text}${moreCharsNote(capped.truncatedFrom - capped.text.length)}`;
18248
+ }
18249
+
18016
18250
  // src/ui/formatters/console/chronological.ts
18017
18251
  init_formatting();
18018
18252
 
@@ -18040,10 +18274,9 @@ function consoleMoreGroupsNote(more, level) {
18040
18274
  return `(+${more} earlier distinct ${more === 1 ? level : `${level}s`}; ${sessionCommand(`bdg console --level ${level} --last 0`)} lists every one)`;
18041
18275
  }
18042
18276
 
18043
- // src/ui/formatters/console/chronological.ts
18044
- init_strings();
18045
-
18046
18277
  // src/ui/formatters/console/shared.ts
18278
+ init_constants();
18279
+ init_strings();
18047
18280
  var DEFAULT_GROUP_LIMIT = 50;
18048
18281
  function newestGroups(groups, limit) {
18049
18282
  const keep = limit ?? DEFAULT_GROUP_LIMIT;
@@ -18170,9 +18403,13 @@ function formatSectionHeader(label, unique, total) {
18170
18403
  function formatFramePosition(frame) {
18171
18404
  return frame.lineNumber < 0 ? frame.url : `${frame.url}:${frame.lineNumber + 1}:${frame.columnNumber + 1}`;
18172
18405
  }
18406
+ function capMessageText(message, full) {
18407
+ if (full) return message;
18408
+ const { text, truncatedFrom } = capLength(message.text, MAX_CONSOLE_JSON_TEXT_LENGTH);
18409
+ return truncatedFrom === void 0 ? message : { ...message, text, truncatedFrom };
18410
+ }
18173
18411
 
18174
18412
  // src/ui/formatters/console/chronological.ts
18175
- var MAX_LIST_TEXT_LENGTH = 200;
18176
18413
  function formatConsoleChronological(messages, options) {
18177
18414
  const fmt = new OutputFormatter();
18178
18415
  const displayMessages = lastMessages(messages, options.last);
@@ -18203,8 +18440,8 @@ function formatConsoleChronological(messages, options) {
18203
18440
  }
18204
18441
  lastNavigationId = msg.navigationId;
18205
18442
  }
18206
- const truncatedText = truncateByLength(msg.text, MAX_LIST_TEXT_LENGTH);
18207
- fmt.text(`${index} ${level} ${time} ${truncatedText}`);
18443
+ const text = capForDisplay(msg.text, MAX_CONSOLE_TEXT_LENGTH, options.full);
18444
+ fmt.text(`${index} ${level} ${time} ${text}`);
18208
18445
  const source = formatSourceLocation(msg.stackTrace);
18209
18446
  if (source) {
18210
18447
  fmt.text(`${sourceIndent}\u2192 ${source}`);
@@ -18221,56 +18458,20 @@ function lastMessages(messages, last) {
18221
18458
  return last && last > 0 ? messages.slice(-last) : messages;
18222
18459
  }
18223
18460
 
18224
- // src/ui/formatters/console/json.ts
18225
- function toJsonError(dedup, includeStackTrace) {
18226
- const source = dedup.message.stackTrace?.[0];
18227
- return {
18228
- count: dedup.count,
18229
- level: dedup.message.type,
18230
- text: dedup.message.text,
18231
- ...dedup.message.index !== void 0 && { index: dedup.message.index },
18232
- ...source && {
18233
- source: {
18234
- url: source.url,
18235
- ...source.lineNumber >= 0 && {
18236
- line: source.lineNumber + 1,
18237
- column: source.columnNumber + 1
18238
- }
18239
- }
18240
- },
18241
- ...includeStackTrace && dedup.message.stackTrace && { stackTrace: dedup.message.stackTrace }
18242
- };
18243
- }
18244
- function buildConsoleJsonOutput(messages, options) {
18245
- const { grouped, summary } = analyzeMessages(messages);
18246
- const errors = newestGroups(grouped.errors, options.groupLimit);
18247
- const warnings = newestGroups(grouped.warnings, options.groupLimit);
18248
- const output = {
18249
- summary,
18250
- errors: errors.shown.map((d) => toJsonError(d, true)),
18251
- warnings: warnings.shown.map((d) => toJsonError(d, false)),
18252
- ...errors.more > 0 && { moreErrors: errors.more },
18253
- ...warnings.more > 0 && { moreWarnings: warnings.more },
18254
- ...options.dropped && { dropped: options.dropped },
18255
- ...options.pageCrashedAt !== void 0 && { pageCrashedAt: options.pageCrashedAt }
18256
- };
18257
- if (options.list) output.messages = lastMessages(messages, options.last);
18258
- return output;
18259
- }
18260
- function formatConsoleJson(messages, options) {
18261
- return JSON.stringify(buildConsoleJsonOutput(messages, options), null, 2);
18262
- }
18263
-
18264
18461
  // src/ui/formatters/console/summarize.ts
18462
+ init_constants();
18265
18463
  init_formatting();
18266
- function renderErrorSection(fmt, errors, total, limit) {
18464
+ function messageText(message, full) {
18465
+ return capForDisplay(message.text, MAX_CONSOLE_TEXT_LENGTH, full);
18466
+ }
18467
+ function renderErrorSection(fmt, errors, total, limit, full) {
18267
18468
  if (errors.length === 0) return;
18268
18469
  const { shown, more } = newestGroups(errors, limit);
18269
18470
  fmt.text(formatSectionHeader("Errors", errors.length, total));
18270
18471
  fmt.separator("\u2500", 30);
18271
18472
  if (more > 0) fmt.text(consoleMoreGroupsNote(more, "error")).blank();
18272
18473
  for (const { message, count } of shown) {
18273
- fmt.text(`${formatCountPrefix(count)}${message.text}`);
18474
+ fmt.text(`${formatCountPrefix(count)}${messageText(message, full)}`);
18274
18475
  const source = formatSourceLocation(message.stackTrace);
18275
18476
  if (source) {
18276
18477
  fmt.text(` \u2192 ${source}`);
@@ -18278,14 +18479,14 @@ function renderErrorSection(fmt, errors, total, limit) {
18278
18479
  fmt.blank();
18279
18480
  }
18280
18481
  }
18281
- function renderWarningSection(fmt, warnings, total, limit) {
18482
+ function renderWarningSection(fmt, warnings, total, limit, full) {
18282
18483
  if (warnings.length === 0) return;
18283
18484
  const { shown, more } = newestGroups(warnings, limit);
18284
18485
  fmt.text(formatSectionHeader("Warnings", warnings.length, total));
18285
18486
  fmt.separator("\u2500", 30);
18286
18487
  if (more > 0) fmt.text(consoleMoreGroupsNote(more, "warning"));
18287
18488
  for (const { message, count } of shown) {
18288
- fmt.text(`\u2022 ${formatCountPrefix(count)}${message.text}`);
18489
+ fmt.text(`\u2022 ${formatCountPrefix(count)}${messageText(message, full)}`);
18289
18490
  const source = formatSourceLocation(message.stackTrace);
18290
18491
  if (source) fmt.text(` \u2192 ${source}`);
18291
18492
  }
@@ -18309,8 +18510,9 @@ function formatConsoleSummary(messages, options = {}) {
18309
18510
  fmt.separator("\u2501", 60);
18310
18511
  if (options.dropped) fmt.text(consoleDroppedNote(options.dropped));
18311
18512
  fmt.blank();
18312
- renderErrorSection(fmt, grouped.errors, summary.errors.total, options.groupLimit);
18313
- renderWarningSection(fmt, grouped.warnings, summary.warnings.total, options.groupLimit);
18513
+ const { groupLimit, full } = options;
18514
+ renderErrorSection(fmt, grouped.errors, summary.errors.total, groupLimit, full);
18515
+ renderWarningSection(fmt, grouped.warnings, summary.warnings.total, groupLimit, full);
18314
18516
  if (grouped.errors.length === 0 && grouped.warnings.length === 0) {
18315
18517
  fmt.text("No errors or warnings found");
18316
18518
  fmt.blank();
@@ -18320,6 +18522,7 @@ function formatConsoleSummary(messages, options = {}) {
18320
18522
  }
18321
18523
 
18322
18524
  // src/ui/formatters/console/follow.ts
18525
+ init_constants();
18323
18526
  init_formatting();
18324
18527
  function formatConsoleFollowLines(messages, options = {}) {
18325
18528
  const fmt = new OutputFormatter();
@@ -18333,18 +18536,56 @@ function formatConsoleFollowLines(messages, options = {}) {
18333
18536
  for (const msg of messages) {
18334
18537
  const time = formatTimestamp(msg.timestamp);
18335
18538
  const level = msg.type.padEnd(7);
18336
- fmt.text(`${time} ${level} ${msg.text}`);
18539
+ fmt.text(`${time} ${level} ${capForDisplay(msg.text, MAX_CONSOLE_TEXT_LENGTH, options.full)}`);
18337
18540
  const source = formatSourceLocation(msg.stackTrace);
18338
18541
  if (source) fmt.text(` \u2192 ${source}`);
18339
18542
  }
18340
18543
  return fmt.build();
18341
18544
  }
18342
18545
 
18546
+ // src/ui/formatters/console/json.ts
18547
+ function toJsonError(dedup, includeStackTrace) {
18548
+ const source = dedup.message.stackTrace?.[0];
18549
+ return {
18550
+ count: dedup.count,
18551
+ level: dedup.message.type,
18552
+ text: dedup.message.text,
18553
+ ...dedup.message.index !== void 0 && { index: dedup.message.index },
18554
+ ...source && {
18555
+ source: {
18556
+ url: source.url,
18557
+ ...source.lineNumber >= 0 && {
18558
+ line: source.lineNumber + 1,
18559
+ column: source.columnNumber + 1
18560
+ }
18561
+ }
18562
+ },
18563
+ ...includeStackTrace && dedup.message.stackTrace && { stackTrace: dedup.message.stackTrace }
18564
+ };
18565
+ }
18566
+ function buildConsoleJsonOutput(messages, options) {
18567
+ const { grouped, summary } = analyzeMessages(messages);
18568
+ const errors = newestGroups(grouped.errors, options.groupLimit);
18569
+ const warnings = newestGroups(grouped.warnings, options.groupLimit);
18570
+ const output = {
18571
+ summary,
18572
+ errors: errors.shown.map((d) => capMessageText(toJsonError(d, true), options.full)),
18573
+ warnings: warnings.shown.map((d) => capMessageText(toJsonError(d, false), options.full)),
18574
+ ...errors.more > 0 && { moreErrors: errors.more },
18575
+ ...warnings.more > 0 && { moreWarnings: warnings.more },
18576
+ ...options.dropped && { dropped: options.dropped },
18577
+ ...options.pageCrashedAt !== void 0 && { pageCrashedAt: options.pageCrashedAt }
18578
+ };
18579
+ if (options.list) {
18580
+ output.messages = lastMessages(messages, options.last).map(
18581
+ (message) => capMessageText(message, options.full)
18582
+ );
18583
+ }
18584
+ return output;
18585
+ }
18586
+
18343
18587
  // src/ui/formatters/console.ts
18344
18588
  function formatConsole(messages, options) {
18345
- if (options.json) {
18346
- return formatConsoleJson(messages, options);
18347
- }
18348
18589
  const body = options.list || options.level ? formatConsoleChronological(messages, options) : formatConsoleSummary(messages, options);
18349
18590
  return withPageCrashedNote(body, options.pageCrashedAt);
18350
18591
  }
@@ -18402,12 +18643,12 @@ function buildFormatOptions(options, lastN, skipped, dropped, pageCrashedAt) {
18402
18643
  ...options.last !== void 0 && { groupLimit: lastN },
18403
18644
  ...dropped && { dropped },
18404
18645
  ...pageCrashedAt !== void 0 && { pageCrashedAt },
18405
- json: options.json,
18406
18646
  list: listsMessages(options),
18407
18647
  follow: options.follow,
18408
18648
  last: lastN,
18409
18649
  history: options.history,
18410
18650
  level: options.level,
18651
+ full: options.full,
18411
18652
  skipped
18412
18653
  };
18413
18654
  }
@@ -18437,13 +18678,15 @@ async function runFollowMode(options, lastN) {
18437
18678
  const data = buildConsoleJsonOutput(backlog, {
18438
18679
  list: true,
18439
18680
  last: 0,
18440
- pageCrashedAt: crashedAt
18681
+ pageCrashedAt: crashedAt,
18682
+ full: options.full
18441
18683
  });
18442
18684
  console.log(JSON.stringify(buildSuccessResponse(data)));
18443
18685
  }
18444
18686
  } else {
18445
18687
  const text = formatConsoleFollowLines(backlog, {
18446
18688
  header: !started,
18689
+ full: options.full,
18447
18690
  ...navigated && currentNavigationId !== void 0 && { navigationId: currentNavigationId }
18448
18691
  });
18449
18692
  if (text) console.log(text);
@@ -18478,7 +18721,12 @@ function registerConsoleCommand(program2) {
18478
18721
  "--level <level>",
18479
18722
  "Filter by message level: error, warning, info (includes log), debug; any case"
18480
18723
  ).argParser(consoleLevelOption)
18481
- ).addOption(consoleLastOption).addOption(jsonOption()).action(async (options) => {
18724
+ ).addOption(consoleLastOption).addOption(
18725
+ new Option(
18726
+ "--full",
18727
+ `Print message texts whole (default: the first ${MAX_CONSOLE_TEXT_LENGTH} characters, ${MAX_CONSOLE_JSON_TEXT_LENGTH} in JSON)`
18728
+ ).default(false)
18729
+ ).addOption(jsonOption()).action(async (options) => {
18482
18730
  let lastN;
18483
18731
  try {
18484
18732
  lastN = positiveIntRule({
@@ -18644,144 +18892,66 @@ async function search(text, options) {
18644
18892
  // src/commands/details.ts
18645
18893
  init_client();
18646
18894
 
18647
- // src/telemetry/network.ts
18895
+ // src/ui/messages/networkMessages.ts
18648
18896
  init_constants();
18897
+ init_formatting();
18898
+ function megabytes(bytes) {
18899
+ return `${Math.round(bytes / (1024 * 1024))} MB`;
18900
+ }
18901
+ function networkEvictedNote(counts) {
18902
+ const { requestsDropped, bodiesEvicted } = counts;
18903
+ const requests = pluralize(requestsDropped, "older network request");
18904
+ const bodies = pluralize(bodiesEvicted, "older response body", "older response bodies");
18905
+ const budget = megabytes(MAX_TOTAL_BODY_BYTES);
18906
+ if (requestsDropped > 0 && bodiesEvicted > 0) {
18907
+ return `\u26A0 ${requests} dropped, ${bodies} evicted: bdg keeps the newest ${MAX_NETWORK_REQUESTS} requests and ${budget} of bodies`;
18908
+ }
18909
+ if (requestsDropped > 0) {
18910
+ return `\u26A0 ${requests} ${requestsDropped === 1 ? "was" : "were"} dropped: bdg keeps the newest ${MAX_NETWORK_REQUESTS}`;
18911
+ }
18912
+ if (bodiesEvicted > 0) {
18913
+ return `\u26A0 ${bodies} ${bodiesEvicted === 1 ? "was" : "were"} evicted: bdg keeps the newest ${budget} of bodies`;
18914
+ }
18915
+ return void 0;
18916
+ }
18917
+ function followingNetworkMessage() {
18918
+ return "Following network requests... (Ctrl+C to stop)";
18919
+ }
18920
+ function stoppedFollowingNetworkMessage() {
18921
+ return "\nStopped following network requests.";
18922
+ }
18923
+ function headerRepeatedNote(count) {
18924
+ return `(sent ${count} times)`;
18925
+ }
18926
+ function localProxyNote() {
18927
+ return "(loopback; likely a local proxy)";
18928
+ }
18929
+ function harSanitizedComment() {
18930
+ return "Sanitized by bdg: values of auth, cookie, API key, token and session headers, cookies, credential query parameters in URLs, and password/token fields of request bodies are [redacted] (by name, so some harmless values are too); response bodies and WebSocket messages are not sanitized. Export with --include-sensitive to keep everything";
18931
+ }
18932
+ function harExportedMessage(result) {
18933
+ const filterNote = result.filtered ? " (filtered)" : "";
18934
+ const note = result.sanitized ? "Credentials sanitized (auth/cookie/API key/token headers, cookies, URL tokens, password and token body fields are [redacted]); --include-sensitive keeps them" : "\u26A0 Includes credentials (--include-sensitive): share this file with care";
18935
+ return `\u2713 Exported ${result.entries} requests${filterNote} to ${result.file}
18936
+ ${note}`;
18937
+ }
18649
18938
 
18650
- // src/telemetry/attachedTargets.ts
18651
- init_logging();
18652
- init_errors();
18653
- var log21 = createLogger("targets");
18939
+ // src/telemetry/networkRetention.ts
18940
+ var SKIPPED_BODY_PATTERN = /^\[SKIPPED: (.*)\]$/s;
18941
+ function skippedBodyReason(body) {
18942
+ return body === void 0 ? void 0 : SKIPPED_BODY_PATTERN.exec(body)?.[1];
18943
+ }
18654
18944
 
18655
- // src/telemetry/network.ts
18656
- init_logging();
18657
- init_errors();
18658
-
18659
- // src/telemetry/filters.ts
18660
- init_url();
18661
- var DEFAULT_EXCLUDED_DOMAINS = [
18662
- "analytics.google.com",
18663
- "googletagmanager.com",
18664
- "googleadservices.com",
18665
- "doubleclick.net",
18666
- "google-analytics.com",
18667
- "clarity.ms",
18668
- "bat.bing.com",
18669
- "facebook.com",
18670
- "connect.facebook.net",
18671
- "tiktok.com",
18672
- "linkedin.com",
18673
- "twitter.com",
18674
- "snapchat.com",
18675
- "mixpanel.com",
18676
- "segment.com",
18677
- "segment.io",
18678
- "amplitude.com",
18679
- "heap.io",
18680
- "fullstory.com",
18681
- "hotjar.com",
18682
- "logrocket.com",
18683
- "smartlook.com",
18684
- "exactag.com",
18685
- "criteo.com",
18686
- "adroll.com",
18687
- "outbrain.com",
18688
- "taboola.com",
18689
- "confirmit.com",
18690
- "newrelic.com",
18691
- "datadoghq.com",
18692
- "sentry.io",
18693
- "googlesyndication.com",
18694
- "googletagservices.com",
18695
- "adservice.google.com",
18696
- "amazon-adsystem.com",
18697
- "adnxs.com",
18698
- "rubiconproject.com",
18699
- "pubmatic.com",
18700
- "casalemedia.com",
18701
- "indexww.com",
18702
- "openx.net",
18703
- "3lift.com",
18704
- "teads.tv",
18705
- "bidswitch.net",
18706
- "sharethrough.com",
18707
- "adsafeprotected.com",
18708
- "moatads.com",
18709
- "doubleverify.com",
18710
- "scorecardresearch.com",
18711
- "imrworldwide.com",
18712
- "chartbeat.com",
18713
- "chartbeat.net",
18714
- "permutive.com",
18715
- "permutive.app",
18716
- "optimizely.com",
18717
- "quantserve.com",
18718
- "quantcount.com",
18719
- "krxd.net",
18720
- "demdex.net",
18721
- "omtrdc.net",
18722
- "everesttech.net",
18723
- "xiti.com",
18724
- "ati-host.net"
18725
- ];
18726
- var EXCLUDED_DOMAINS_SET = new Set(DEFAULT_EXCLUDED_DOMAINS.map((d) => d.toLowerCase()));
18727
- var DEFAULT_SKIP_BODY_MIME_TYPES = [
18728
- "text/css",
18729
- "image/svg+xml",
18730
- "image/png",
18731
- "image/jpeg",
18732
- "image/gif",
18733
- "image/webp",
18734
- "image/avif",
18735
- "image/bmp",
18736
- "image/tiff",
18737
- "font/woff",
18738
- "font/woff2",
18739
- "font/ttf",
18740
- "font/eot",
18741
- "font/otf",
18742
- "application/font-woff",
18743
- "application/font-woff2",
18744
- "video/mp4",
18745
- "video/webm",
18746
- "audio/mpeg",
18747
- "audio/wav"
18748
- ];
18749
- var SKIP_BODY_MIME_TYPES_SET = new Set(DEFAULT_SKIP_BODY_MIME_TYPES.map((m) => m.toLowerCase()));
18750
- function matchesWildcard(str, pattern) {
18751
- const regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
18752
- const regex = new RegExp(`^${regexPattern}$`, "i");
18753
- return regex.test(str);
18754
- }
18755
- function filterByResourceType(requests, types) {
18756
- if (types.length === 0) {
18757
- return requests;
18758
- }
18759
- return requests.filter((req) => {
18760
- if (!req.resourceType) {
18761
- return false;
18762
- }
18763
- return types.includes(req.resourceType);
18764
- });
18765
- }
18766
-
18767
- // src/telemetry/network.ts
18768
- var log22 = createLogger("network");
18769
- var SKIPPED_BODY_PATTERN = /^\[SKIPPED: (.*)\]$/s;
18770
- function skippedBodyReason(body) {
18771
- return body === void 0 ? void 0 : SKIPPED_BODY_PATTERN.exec(body)?.[1];
18772
- }
18773
- var MAX_FRAME_PAYLOAD_SIZE = 100 * 1024;
18774
-
18775
- // src/telemetry/requestState.ts
18776
- function getRequestState(request) {
18777
- if (request.status === void 0) return "pending";
18778
- return request.status === 0 ? "failed" : "complete";
18779
- }
18780
- function failureReason(request) {
18781
- if (!request.errorText) return void 0;
18782
- return getRequestState(request) === "complete" && request.errorText === BODY_ABORTED ? void 0 : request.errorText;
18783
- }
18784
- var BODY_ABORTED = "net::ERR_ABORTED";
18945
+ // src/telemetry/requestState.ts
18946
+ function getRequestState(request) {
18947
+ if (request.status === void 0) return "pending";
18948
+ return request.status === 0 ? "failed" : "complete";
18949
+ }
18950
+ function failureReason(request) {
18951
+ if (!request.errorText) return void 0;
18952
+ return getRequestState(request) === "complete" && request.errorText === BODY_ABORTED ? void 0 : request.errorText;
18953
+ }
18954
+ var BODY_ABORTED = "net::ERR_ABORTED";
18785
18955
 
18786
18956
  // src/ui/formatters/requestStatus.ts
18787
18957
  function formatRequestStatus(request) {
@@ -18793,22 +18963,6 @@ function formatRequestStatus(request) {
18793
18963
 
18794
18964
  // src/ui/formatters/networkHeaders.ts
18795
18965
  init_formatting();
18796
-
18797
- // src/ui/messages/networkMessages.ts
18798
- function followingNetworkMessage() {
18799
- return "Following network requests... (Ctrl+C to stop)";
18800
- }
18801
- function stoppedFollowingNetworkMessage() {
18802
- return "\nStopped following network requests.";
18803
- }
18804
- function headerRepeatedNote(count) {
18805
- return `(sent ${count} times)`;
18806
- }
18807
- function localProxyNote() {
18808
- return "(loopback; likely a local proxy)";
18809
- }
18810
-
18811
- // src/ui/formatters/networkHeaders.ts
18812
18966
  function formatNetworkHeaders(data) {
18813
18967
  const fmt = new OutputFormatter();
18814
18968
  fmt.text("Network Request Headers").separator("\u2501", 60).blank();
@@ -19435,6 +19589,9 @@ function findUnknownModifiers(modifiers) {
19435
19589
  return modifierNames(modifiers).filter((name) => !(name in MODIFIER_NAMES));
19436
19590
  }
19437
19591
 
19592
+ // src/ui/formatters/triggeredRequests.ts
19593
+ init_constants();
19594
+
19438
19595
  // src/telemetry/requestKinds.ts
19439
19596
  var ACTIVITY_RESOURCE_TYPES = /* @__PURE__ */ new Set([
19440
19597
  "Document",
@@ -19489,7 +19646,10 @@ function formatTriggeredRequestLines(requests, omitted = 0) {
19489
19646
  const assets = requests.filter((request) => !isNotableRequest(request));
19490
19647
  const lines = notable.slice(0, MAX_TRIGGERED_REQUESTS_SHOWN).map(formatTriggeredRequest);
19491
19648
  const hidden = notable.length - lines.length + omitted;
19492
- if (hidden > 0) lines.push(omitted > 0 ? moreRequestsNote(hidden) : moreMatchesNote(hidden));
19649
+ if (hidden > 0)
19650
+ lines.push(
19651
+ omitted > 0 ? moreRequestsNote(hidden) : moreMatchesNote(hidden, MAX_TRIGGERED_REQUESTS)
19652
+ );
19493
19653
  if (assets.length > 0) lines.push(assetRequestsNote(assets.length, assetTypeNames(assets)));
19494
19654
  return lines;
19495
19655
  }
@@ -19513,7 +19673,10 @@ function registerFormInteractionCommands(program2) {
19513
19673
  const err = internalError("DOM command group not found");
19514
19674
  throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.SOFTWARE_ERROR);
19515
19675
  }
19516
- domCommand.command("fill").description("Fill a form field with a value (React-compatible, waits for stability)").argument("<selectorOrIndex>", SELECTOR_OR_INDEX_ARGUMENT).argument("<value>", 'Value to fill (file inputs: paths separated by commas, "" clears)').option("--index <n>", "Element index if selector matches multiple (0-based)", integerOption(0)).option("--no-blur", "Do not blur after filling (keeps focus on element)").option("--no-wait", "Skip waiting for network stability after fill").addOption(jsonOption()).action(async (selectorOrIndex, value, options) => {
19676
+ domCommand.command("fill").description("Fill a form field with a value (React-compatible, waits for stability)").argument("<selectorOrIndex>", SELECTOR_OR_INDEX_ARGUMENT).argument(
19677
+ "<value>",
19678
+ 'Value to fill (file inputs: local file paths, separated by commas, that are uploaded to the page; "" clears)'
19679
+ ).option("--index <n>", "Element index if selector matches multiple (0-based)", integerOption(0)).option("--no-blur", "Do not blur after filling (keeps focus on element)").option("--no-wait", "Skip waiting for network stability after fill").addOption(jsonOption()).action(async (selectorOrIndex, value, options) => {
19517
19680
  await runCommand(
19518
19681
  () => runElementCommand({
19519
19682
  selectorOrIndex,
@@ -19939,12 +20102,12 @@ function synthesizeA11yNode(domContext, nodeId) {
19939
20102
  // src/ui/formatters/keyAttributes.ts
19940
20103
  init_formatting();
19941
20104
  init_url();
19942
- var MAX_VALUE_LENGTH = 40;
20105
+ var MAX_VALUE_LENGTH2 = 40;
19943
20106
  function fileName(src) {
19944
20107
  if (/^(data|blob):/i.test(src)) return src;
19945
- const path7 = src.split(/[?#]/)[0] ?? src;
19946
- const name = path7.substring(path7.lastIndexOf("/") + 1);
19947
- return name && name !== path7 ? `\u2026/${name}` : src;
20108
+ const path8 = src.split(/[?#]/)[0] ?? src;
20109
+ const name = path8.substring(path8.lastIndexOf("/") + 1);
20110
+ return name && name !== path8 ? `\u2026/${name}` : src;
19948
20111
  }
19949
20112
  function hostOf(src) {
19950
20113
  const parsed = /^https?:/i.test(src) ? safeParseUrl(src) : null;
@@ -19953,13 +20116,13 @@ function hostOf(src) {
19953
20116
  return `//${parsed.host}${more}`;
19954
20117
  }
19955
20118
  function shortUrl(url) {
19956
- return /^https?:/i.test(url) ? `//${truncateUrl(url, MAX_VALUE_LENGTH - 2)}` : url;
20119
+ return /^https?:/i.test(url) ? `//${truncateUrl(url, MAX_VALUE_LENGTH2 - 2)}` : url;
19957
20120
  }
19958
20121
  function shortValue(tag, name, value) {
19959
20122
  let short = value;
19960
20123
  if (name === "src") short = tag === "iframe" ? hostOf(value) : fileName(value);
19961
20124
  else if (name === "href" || name === "action") short = shortUrl(value);
19962
- return cutMiddle(escapeControlChars(short.replace(/\s+/g, " ").trim()), MAX_VALUE_LENGTH);
20125
+ return cutMiddle(escapeControlChars(short.replace(/\s+/g, " ").trim()), MAX_VALUE_LENGTH2);
19963
20126
  }
19964
20127
  function keyAttributeItems(tag, attributes, leaveOut = /* @__PURE__ */ new Set()) {
19965
20128
  return Object.entries(attributes ?? {}).flatMap(([name, value]) => {
@@ -20059,6 +20222,7 @@ function withSecretMasked(node, domContext) {
20059
20222
  }
20060
20223
 
20061
20224
  // src/commands/dom/a11y.ts
20225
+ init_constants();
20062
20226
  init_errors3();
20063
20227
  init_messages();
20064
20228
 
@@ -20072,7 +20236,7 @@ init_elementInfo();
20072
20236
  init_frameScopedConnection();
20073
20237
  init_logging();
20074
20238
  init_errors();
20075
- var log28 = createLogger("dom");
20239
+ var log27 = createLogger("dom");
20076
20240
  function childFrameIds(tree) {
20077
20241
  return (tree?.childFrames ?? []).flatMap((child) => [child.frame.id, ...childFrameIds(child)]);
20078
20242
  }
@@ -20109,6 +20273,51 @@ function buildTreeFromRawNodes(rawNodes) {
20109
20273
  const root = nodes.get(rootRaw.nodeId);
20110
20274
  return { root, nodes, count: nodes.size };
20111
20275
  }
20276
+ var LAYOUT_ROLES = /* @__PURE__ */ new Set([
20277
+ "generic",
20278
+ "none",
20279
+ "presentation",
20280
+ "LayoutTable",
20281
+ "LayoutTableRow",
20282
+ "LayoutTableCell"
20283
+ ]);
20284
+ function isTreeNoise(node, parentName) {
20285
+ return node.role === "InlineTextBox" || node.role === "StaticText" && (node.name === parentName || !node.name?.trim()) || LAYOUT_ROLES.has(node.role) && !node.name;
20286
+ }
20287
+ function listA11yTree(tree, limit, maxDepth) {
20288
+ const nodes = [];
20289
+ const visited = /* @__PURE__ */ new Set();
20290
+ let omitted = 0;
20291
+ let skipped = 0;
20292
+ const visit = (node, depth, parentName) => {
20293
+ if (visited.has(node.nodeId)) return;
20294
+ visited.add(node.nodeId);
20295
+ const listable = !isTreeNoise(node, parentName);
20296
+ if (listable) {
20297
+ const fits = (limit === 0 || nodes.length < limit) && (maxDepth ?? depth) >= depth;
20298
+ if (fits) nodes.push(listedNode(node, depth));
20299
+ else omitted++;
20300
+ } else {
20301
+ skipped++;
20302
+ }
20303
+ for (const childId of node.childIds ?? []) {
20304
+ const child = tree.nodes.get(childId);
20305
+ if (child) visit(child, listable ? depth + 1 : depth, node.name ?? parentName);
20306
+ }
20307
+ };
20308
+ visit(tree.root, 0, void 0);
20309
+ for (const node of tree.nodes.values()) visit(node, 0, void 0);
20310
+ return {
20311
+ nodes,
20312
+ count: tree.count,
20313
+ ...omitted > 0 && { omitted },
20314
+ ...skipped > 0 && { skipped }
20315
+ };
20316
+ }
20317
+ function listedNode(node, depth) {
20318
+ const { childIds: _childIds, ...rest } = node;
20319
+ return { ...rest, depth };
20320
+ }
20112
20321
  function visibleChildIds(rawNode, rawById, seen = /* @__PURE__ */ new Set()) {
20113
20322
  return (rawNode.childIds ?? []).flatMap((childId) => {
20114
20323
  const child = rawById.get(childId);
@@ -20398,49 +20607,16 @@ var IGNORED_REASONS = {
20398
20607
  // src/ui/formatters/a11y.ts
20399
20608
  init_formatting();
20400
20609
  init_commands();
20401
- var MAX_TREE_NODES_DISPLAY = 50;
20402
20610
  var SEPARATOR_WIDTH = 50;
20403
20611
  function formatA11yTree(tree) {
20404
20612
  const fmt = new OutputFormatter();
20405
20613
  fmt.text(`Accessibility Tree (${tree.count} nodes)`).separator("\u2500", SEPARATOR_WIDTH).blank();
20406
- const { lines, truncated } = treeLines(tree);
20407
- lines.forEach((line) => fmt.text(line));
20408
- if (truncated) {
20409
- fmt.blank().text(
20410
- `Showing the first ${MAX_TREE_NODES_DISPLAY} nodes (text boxes and repeated text left out)`
20411
- ).text('Use --json flag for complete output, or bdg dom a11y query "role:<role>" to search');
20614
+ tree.nodes.forEach((node) => fmt.text(" ".repeat(node.depth) + formatA11yNodeOneLine(node)));
20615
+ if (tree.omitted) {
20616
+ fmt.blank().text(a11yTreeShownNote(tree.nodes.length)).text(a11yTreeMoreNote(tree.omitted));
20412
20617
  }
20413
20618
  return fmt.build();
20414
20619
  }
20415
- var LAYOUT_ROLES = /* @__PURE__ */ new Set([
20416
- "generic",
20417
- "none",
20418
- "presentation",
20419
- "LayoutTable",
20420
- "LayoutTableRow",
20421
- "LayoutTableCell"
20422
- ]);
20423
- function treeLines(tree) {
20424
- const lines = [];
20425
- const visited = /* @__PURE__ */ new Set();
20426
- let truncated = false;
20427
- const visit = (node, depth, parentName) => {
20428
- if (visited.has(node.nodeId)) return;
20429
- visited.add(node.nodeId);
20430
- if (lines.length >= MAX_TREE_NODES_DISPLAY) {
20431
- truncated = true;
20432
- return;
20433
- }
20434
- const skip = node.role === "InlineTextBox" || node.role === "StaticText" && (node.name === parentName || !node.name?.trim()) || LAYOUT_ROLES.has(node.role) && !node.name;
20435
- if (!skip) lines.push(" ".repeat(depth) + formatA11yNodeOneLine(node));
20436
- for (const childId of node.childIds ?? []) {
20437
- const child = tree.nodes.get(childId);
20438
- if (child) visit(child, skip ? depth : depth + 1, node.name ?? parentName);
20439
- }
20440
- };
20441
- visit(tree.root, 0, void 0);
20442
- return { lines, truncated };
20443
- }
20444
20620
  var FILLABLE_ROLES = /* @__PURE__ */ new Set([
20445
20621
  "textbox",
20446
20622
  "searchbox",
@@ -20571,19 +20747,14 @@ function truncate(str, maxLen) {
20571
20747
  // src/commands/dom/a11y.ts
20572
20748
  init_exitCodes();
20573
20749
  var A11Y_QUERY_LIMIT = 50;
20750
+ var A11Y_TREE_LIMIT = 50;
20574
20751
  async function handleA11yTree(options) {
20752
+ const listTree = async () => listA11yTree(await collectA11yTree(), options.limit ?? A11Y_TREE_LIMIT, options.depth);
20575
20753
  if (options.json) {
20576
- await runJsonCommand(async () => {
20577
- const tree = await collectA11yTree();
20578
- return {
20579
- root: tree.root,
20580
- nodes: Array.from(tree.nodes.values()),
20581
- count: tree.count
20582
- };
20583
- });
20754
+ await runJsonCommand(listTree);
20584
20755
  }
20585
20756
  await runCommand(
20586
- async () => ({ success: true, data: await collectA11yTree() }),
20757
+ async () => ({ success: true, data: await listTree() }),
20587
20758
  options,
20588
20759
  formatA11yTree
20589
20760
  );
@@ -20632,7 +20803,10 @@ async function handleA11yQuery(pattern, options) {
20632
20803
  );
20633
20804
  return {
20634
20805
  success: true,
20635
- data: limitMatches(indexed, options.limit ?? (options.json ? 0 : A11Y_QUERY_LIMIT))
20806
+ data: limitMatches(
20807
+ indexed,
20808
+ options.limit ?? (options.json ? QUERY_JSON_LIST_LIMIT : A11Y_QUERY_LIMIT)
20809
+ )
20636
20810
  };
20637
20811
  },
20638
20812
  options,
@@ -20704,7 +20878,17 @@ function registerA11yCommands(domCmd) {
20704
20878
  await handleA11yQuery(`name:${quoteQueryValue(search2)}`, options);
20705
20879
  }
20706
20880
  });
20707
- a11y.command("tree").description("Dump full accessibility tree (filters ignored nodes)").addOption(jsonOption()).action(async (options) => {
20881
+ a11y.command("tree").description(
20882
+ "List the accessibility tree depth-first (ignored nodes, text boxes, repeated text and nameless wrappers left out)"
20883
+ ).option(
20884
+ "--limit <n>",
20885
+ `Nodes to list (default: ${A11Y_TREE_LIMIT}, also with --json; 0 = all listed nodes (text boxes and empty wrappers are always skipped))`,
20886
+ integerOption(0)
20887
+ ).option(
20888
+ "--depth <n>",
20889
+ "Levels below the root to list (0 = root only; default: all)",
20890
+ integerOption(0)
20891
+ ).addOption(jsonOption()).action(async (options) => {
20708
20892
  await handleA11yTree(options);
20709
20893
  });
20710
20894
  a11y.command("query").description('Query elements by accessibility properties (e.g., "role:button", "name:Submit")').argument(
@@ -20712,7 +20896,7 @@ function registerA11yCommands(domCmd) {
20712
20896
  "Fields role, name, description as key:value or key=value, separated by spaces or commas; * is a wildcard. A name or description runs to the next field or the end, so it may contain spaces and colons; quote the whole pattern (e.g. 'role=button name=Sign in', 'name=E-mail address:')"
20713
20897
  ).option(
20714
20898
  "--limit <n>",
20715
- `Matches to list (default: ${A11Y_QUERY_LIMIT}, all with --json; 0 = all); all are indexed`,
20899
+ `Matches to list (default: ${A11Y_QUERY_LIMIT}, ${QUERY_JSON_LIST_LIMIT} with --json; 0 = all); all are indexed`,
20716
20900
  integerOption(0)
20717
20901
  ).addOption(jsonOption()).action(async (pattern, options) => {
20718
20902
  await handleA11yQuery(pattern, options);
@@ -20774,12 +20958,39 @@ async function audit(checks, options) {
20774
20958
  return { success: true, data: response.data };
20775
20959
  }
20776
20960
 
20961
+ // src/commands/dom/helpers/evalResult.ts
20962
+ init_constants();
20963
+ init_strings();
20964
+ function boundEvalResult(value, length) {
20965
+ if (typeof value === "string") {
20966
+ const { text, truncatedFrom } = capLength(value, MAX_VALUE_LENGTH);
20967
+ return { result: text, ...truncatedFrom !== void 0 && { truncatedFrom } };
20968
+ }
20969
+ if (Array.isArray(value)) return boundArray(value, length ?? value.length);
20970
+ if (value === null || typeof value !== "object") return { result: value };
20971
+ const json = JSON.stringify(value);
20972
+ return json.length > MAX_VALUE_LENGTH ? jsonStart(json) : { result: value };
20973
+ }
20974
+ function boundArray(value, count) {
20975
+ const listed = value.slice(0, EVAL_JSON_ARRAY_LIMIT);
20976
+ const listedJson = JSON.stringify(listed);
20977
+ if (listedJson.length > MAX_VALUE_LENGTH) {
20978
+ const json = listed.length === value.length ? listedJson : JSON.stringify(value);
20979
+ return { ...jsonStart(json), count };
20980
+ }
20981
+ return listed.length < count ? { result: listed, count, omitted: count - listed.length } : { result: value };
20982
+ }
20983
+ function jsonStart(json) {
20984
+ return { result: capLength(json, MAX_VALUE_LENGTH).text, truncatedFrom: json.length };
20985
+ }
20986
+
20777
20987
  // src/commands/dom/eval.ts
20778
20988
  init_query();
20779
20989
  init_messages();
20780
20990
  init_client();
20781
20991
 
20782
20992
  // src/ui/formatters/dom.ts
20993
+ init_constants();
20783
20994
  init_formatting();
20784
20995
  init_commands();
20785
20996
  function formatDomQuery(data) {
@@ -20808,18 +21019,22 @@ function queryTagAttributes(node) {
20808
21019
  ...node.classes?.length ? [`class="${node.classes.join(" ")}"`] : []
20809
21020
  ];
20810
21021
  }
20811
- function formatDomGet(data) {
21022
+ function formatDomGet(data, options = {}) {
20812
21023
  const { nodes } = data;
21024
+ const html = (node) => capForDisplay(node.outerHTML ?? "", MAX_VALUE_LENGTH, options.full);
20813
21025
  if (nodes.length === 1) {
20814
- return nodes[0].outerHTML ?? "";
21026
+ return html(nodes[0]);
20815
21027
  }
20816
21028
  const fmt = new OutputFormatter();
20817
21029
  nodes.forEach((node, i) => {
20818
- fmt.text(`[${i}] ${node.outerHTML}`);
21030
+ fmt.text(`[${i}] ${html(node)}`);
20819
21031
  });
20820
21032
  return fmt.build();
20821
21033
  }
20822
- function formatDomEval(data) {
21034
+ function formatDomEval(data, options = {}) {
21035
+ return capForDisplay(evalResultText(data), MAX_VALUE_LENGTH, options.full);
21036
+ }
21037
+ function evalResultText(data) {
20823
21038
  if (data.type === "undefined") return "undefined";
20824
21039
  const { result } = data;
20825
21040
  if (typeof result === "string" && (data.type !== "string" || !looksLikeOtherValue(result))) {
@@ -20876,7 +21091,7 @@ async function handleDomEval(script, options) {
20876
21091
  errorContext: { suggestion: err.suggestion }
20877
21092
  };
20878
21093
  }
20879
- const response = await domEval(script, options.frame);
21094
+ const response = await domEval(script, options.frame, options.full);
20880
21095
  if (response.status === "error" || !response.data) {
20881
21096
  const suggestion = await frameErrorSuggestion(response, options.frame);
20882
21097
  return {
@@ -20886,7 +21101,7 @@ async function handleDomEval(script, options) {
20886
21101
  ...suggestion && { errorContext: { suggestion } }
20887
21102
  };
20888
21103
  }
20889
- const { value, type, subtype, frame, warning } = response.data;
21104
+ const { value, type, subtype, length, frame, warning } = response.data;
20890
21105
  const hint = [
20891
21106
  ...frame !== void 0 ? [evalFrameLine(frame)] : [],
20892
21107
  ...warning ? [warningMessage(warning)] : []
@@ -20894,7 +21109,7 @@ async function handleDomEval(script, options) {
20894
21109
  return {
20895
21110
  success: true,
20896
21111
  data: {
20897
- result: value,
21112
+ ...options.json && !options.full ? boundEvalResult(value, length) : { result: value },
20898
21113
  type,
20899
21114
  ...subtype && { subtype },
20900
21115
  ...frame !== void 0 && { frame },
@@ -20904,7 +21119,7 @@ async function handleDomEval(script, options) {
20904
21119
  };
20905
21120
  },
20906
21121
  options,
20907
- formatDomEval
21122
+ (data) => formatDomEval(data, { full: options.full })
20908
21123
  );
20909
21124
  }
20910
21125
  async function frameErrorSuggestion(response, frame) {
@@ -21224,7 +21439,7 @@ function formatFormDiscovery(result) {
21224
21439
  init_logging();
21225
21440
  init_errors();
21226
21441
  init_exitCodes();
21227
- var log29 = createLogger("dom");
21442
+ var log28 = createLogger("dom");
21228
21443
  function buildValidation(raw) {
21229
21444
  const hasNativeError = !raw.isValid && !raw.valueMissing && raw.validationMessage;
21230
21445
  const hasAriaError = raw.ariaInvalid;
@@ -21381,7 +21596,7 @@ async function cacheFormElements(forms, document2) {
21381
21596
  const elements = forms.flatMap((form) => [...form.fields, ...form.buttons]);
21382
21597
  const backendNodeIds = await resolveBackendNodeIds(elements.map((el) => el.selector)).catch(
21383
21598
  (error) => {
21384
- log29.debug(`Form fields not cached by node: ${getErrorMessage(error)}`);
21599
+ log28.debug(`Form fields not cached by node: ${getErrorMessage(error)}`);
21385
21600
  return [];
21386
21601
  }
21387
21602
  );
@@ -21397,7 +21612,7 @@ async function cacheFormElements(forms, document2) {
21397
21612
  },
21398
21613
  document2
21399
21614
  );
21400
- log29.debug(`Cached ${elements.length} form elements`);
21615
+ log28.debug(`Cached ${elements.length} form elements`);
21401
21616
  }
21402
21617
  async function handleFormCommand(options) {
21403
21618
  await runCommand(
@@ -21504,9 +21719,11 @@ async function withReadyState(frames) {
21504
21719
 
21505
21720
  // src/commands/dom/get.ts
21506
21721
  init_helpers();
21722
+ init_constants();
21507
21723
  init_errors3();
21508
21724
  init_messages();
21509
21725
  init_exitCodes();
21726
+ init_strings();
21510
21727
  var DOM_GET_DEFAULT_SELECTOR = "body";
21511
21728
  async function targetNodeId(selectorOrIndex, index) {
21512
21729
  const resolver = DomElementResolver.getInstance();
@@ -21541,12 +21758,21 @@ async function handleRawGet(selectorOrIndex, options) {
21541
21758
  all: options.all,
21542
21759
  nth: matchIndex(options)
21543
21760
  });
21544
- return { success: true, data: await getDOMElements(getOptions) };
21761
+ return { success: true, data: rawGetData(await getDOMElements(getOptions), options) };
21545
21762
  },
21546
21763
  options,
21547
- formatDomGet
21764
+ (data) => formatDomGet(data, { full: options.full })
21548
21765
  );
21549
21766
  }
21767
+ function rawGetData(result, options) {
21768
+ if (!options.json || options.full) return result;
21769
+ return {
21770
+ nodes: result.nodes.map((node) => {
21771
+ const { text, truncatedFrom } = capLength(node.outerHTML ?? "", MAX_VALUE_LENGTH);
21772
+ return truncatedFrom === void 0 ? node : { ...node, outerHTML: text, truncatedFrom };
21773
+ })
21774
+ };
21775
+ }
21550
21776
  async function handleSemanticGet(selectorOrIndex, options) {
21551
21777
  await runCommand(
21552
21778
  async () => {
@@ -21564,9 +21790,6 @@ function getOptionsConflict(selectorOrIndex, options) {
21564
21790
  if (options.nodeId !== void 0 && selectorOrIndex !== void 0) {
21565
21791
  return conflictingOptionsError("--node-id", "a selector or index");
21566
21792
  }
21567
- if (options.full && (options.raw || options.nodeId !== void 0)) {
21568
- return conflictingOptionsError("--full", options.raw ? "--raw" : "--node-id");
21569
- }
21570
21793
  if (options.index !== void 0 && options.nth !== void 0) {
21571
21794
  return conflictingOptionsError("--index", "--nth");
21572
21795
  }
@@ -21588,9 +21811,9 @@ async function handleDomGet(selectorOrIndex, options) {
21588
21811
  const { nodeId } = options;
21589
21812
  if (nodeId !== void 0) {
21590
21813
  await runCommand(
21591
- async () => ({ success: true, data: await getDOMElements({ nodeId }) }),
21814
+ async () => ({ success: true, data: rawGetData(await getDOMElements({ nodeId }), options) }),
21592
21815
  options,
21593
- formatDomGet
21816
+ (data) => formatDomGet(data, { full: options.full })
21594
21817
  );
21595
21818
  return;
21596
21819
  }
@@ -21876,11 +22099,11 @@ function treeRow(node) {
21876
22099
  node.childCount !== void 0 && `(${node.childCount})`
21877
22100
  ].filter(Boolean).join(" ");
21878
22101
  }
21879
- function treeLines2(nodes, hidden, depth) {
22102
+ function treeLines(nodes, hidden, depth) {
21880
22103
  const indent = " ".repeat(depth);
21881
22104
  const lines = nodes.flatMap((node) => [
21882
22105
  `${indent}${treeRow(node)}`,
21883
- ...treeLines2(node.children ?? [], node.hiddenChildren, depth + 1)
22106
+ ...treeLines(node.children ?? [], node.hiddenChildren, depth + 1)
21884
22107
  ]);
21885
22108
  return hidden ? [...lines, `${indent}(+${hidden} not rendered)`] : lines;
21886
22109
  }
@@ -21956,7 +22179,7 @@ function treeBlock(data) {
21956
22179
  if (!data.children && !data.hiddenChildren) return [];
21957
22180
  return [
21958
22181
  "tree",
21959
- ...treeLines2(data.children ?? [], data.hiddenChildren, 1),
22182
+ ...treeLines(data.children ?? [], data.hiddenChildren, 1),
21960
22183
  ...data.moreRows ? [` \u2026 +${data.moreRows} more`] : []
21961
22184
  ];
21962
22185
  }
@@ -22077,6 +22300,7 @@ async function inspectTarget(selectorOrIndex, options) {
22077
22300
  init_client();
22078
22301
 
22079
22302
  // src/ui/formatters/layout.ts
22303
+ init_constants();
22080
22304
  init_messages();
22081
22305
  init_formatting();
22082
22306
  init_commands();
@@ -22099,7 +22323,7 @@ function formatLayout(data) {
22099
22323
  const more = data.elements.length - shown.length + (data.omitted ?? 0);
22100
22324
  return new OutputFormatter().text(pageLayoutLine(data.page)).text(
22101
22325
  data.indexSource ? indexLayoutHeadline(indexSourceText(data.indexSource)) : layoutHeadline(data.count, data.elements.length + (data.omitted ?? 0), data.selector)
22102
- ).list(shown.map((element) => layoutLine2(element, data.page.viewport))).list(more > 0 ? [moreMatchesNote(more, data.omitted ? data.elements.length : void 0)] : []).build();
22326
+ ).list(shown.map((element) => layoutLine2(element, data.page.viewport))).list(more > 0 ? [moreMatchesNote(more, LAYOUT_ELEMENT_LIMIT)] : []).build();
22103
22327
  }
22104
22328
 
22105
22329
  // src/commands/dom/layout.ts
@@ -22326,10 +22550,11 @@ async function listElementListeners(selectorOrIndex, options) {
22326
22550
  // src/commands/dom/query.ts
22327
22551
  init_helpers();
22328
22552
  init_query();
22553
+ init_constants();
22329
22554
  init_exitCodes();
22330
22555
  var QUERY_LIST_LIMIT = 50;
22331
22556
  async function handleDomQuery(selector, options) {
22332
- const limit = options.limit ?? (options.json ? QUERY_CACHE_LIMIT : QUERY_LIST_LIMIT);
22557
+ const limit = options.limit ?? (options.json ? QUERY_JSON_LIST_LIMIT : QUERY_LIST_LIMIT);
22333
22558
  await runCommand(
22334
22559
  async () => {
22335
22560
  const document2 = await pageDocumentId();
@@ -22373,7 +22598,7 @@ init_logging();
22373
22598
  init_directories();
22374
22599
  init_errors();
22375
22600
  init_exitCodes();
22376
- var log30 = createLogger("dom");
22601
+ var log29 = createLogger("dom");
22377
22602
  var EXTENSION_FORMATS = {
22378
22603
  ".png": "png",
22379
22604
  ".jpg": "jpeg",
@@ -22499,8 +22724,8 @@ async function captureSequenceFrame(outputPath, options) {
22499
22724
  }
22500
22725
  async function handleSequenceCapture(outputDir, options) {
22501
22726
  const fs19 = await import("fs");
22502
- const path7 = await import("path");
22503
- const absoluteDir = path7.resolve(outputDir);
22727
+ const path8 = await import("path");
22728
+ const absoluteDir = path8.resolve(outputDir);
22504
22729
  ensureDirectory(absoluteDir, fs19);
22505
22730
  const intervalRule = positiveIntRule({ name: "--interval", min: 100, max: 6e4, default: 1e3 });
22506
22731
  const limitRule = positiveIntRule({ name: "--limit", min: 1, max: 1e4, required: false });
@@ -22516,7 +22741,7 @@ async function handleSequenceCapture(outputDir, options) {
22516
22741
  while (!stopping) {
22517
22742
  frameCount++;
22518
22743
  const filename = formatFrameFilename(frameCount, format);
22519
- const outputPath = path7.join(absoluteDir, filename);
22744
+ const outputPath = path8.join(absoluteDir, filename);
22520
22745
  try {
22521
22746
  await captureSequenceFrame(outputPath, options);
22522
22747
  } catch (error) {
@@ -22525,7 +22750,7 @@ async function handleSequenceCapture(outputDir, options) {
22525
22750
  if (options.json) {
22526
22751
  console.log(JSON.stringify(buildSuccessResponse({ frame: frameCount, path: outputPath })));
22527
22752
  } else {
22528
- log30.info(`Frame ${frameCount}: ${filename}`);
22753
+ log29.info(`Frame ${frameCount}: ${filename}`);
22529
22754
  }
22530
22755
  if (limit > 0 && frameCount >= limit) break;
22531
22756
  await delay2(interval);
@@ -22647,6 +22872,8 @@ function formatWait(data) {
22647
22872
  }
22648
22873
 
22649
22874
  // src/commands/dom/index.ts
22875
+ init_constants();
22876
+ var EVAL_FULL_HELP = `Print the whole value (default: the first ${MAX_VALUE_LENGTH} characters; a string result in JSON too)`;
22650
22877
  function registerDomCommands(program2) {
22651
22878
  const dom = program2.command("dom").description("DOM inspection and manipulation").enablePositionalOptions();
22652
22879
  registerA11yCommands(dom);
@@ -22658,7 +22885,7 @@ function registerDomCommands(program2) {
22658
22885
  registerWaitCommand(dom);
22659
22886
  dom.command("query").description("Find elements by CSS selector").argument("<selector>", 'CSS selector (e.g., ".error", "#app", "button")').option(
22660
22887
  "--limit <n>",
22661
- `Matches to list (default: ${QUERY_LIST_LIMIT}, ${QUERY_CACHE_LIMIT} with --json; 0 = all); the first ${QUERY_CACHE_LIMIT} (or more with a higher limit) are indexed`,
22888
+ `Matches to list (default: ${QUERY_LIST_LIMIT}, ${QUERY_JSON_LIST_LIMIT} with --json; 0 = all); the first ${QUERY_CACHE_LIMIT} (or more with a higher limit) are indexed`,
22662
22889
  integerOption(0)
22663
22890
  ).option("-j, --json", "Output as JSON").addHelpText("after", SELECTOR_SCOPE_HELP).action(async (selector, options) => {
22664
22891
  await handleDomQuery(selector, options);
@@ -22666,10 +22893,10 @@ function registerDomCommands(program2) {
22666
22893
  dom.command("eval").description("Evaluate JavaScript expression in the page context").argument("<script>", 'JavaScript to execute (e.g., "document.title", "window.location.href")').option(
22667
22894
  "--frame <frame>",
22668
22895
  "Evaluate in an iframe, cross-origin ones included: index (from dom frames; 87 when stale), name/id attribute, or part of the name, id or URL"
22669
- ).option("-j, --json", "Output as JSON").action(async (script, options) => {
22896
+ ).option("--full", EVAL_FULL_HELP).option("-j, --json", "Output as JSON").action(async (script, options) => {
22670
22897
  await handleDomEval(script, options);
22671
22898
  });
22672
- program2.command("eval", { hidden: true }).description("Shortcut for: bdg dom eval").argument("<script>", "JavaScript to execute").option("--frame <frame>", "Evaluate in an iframe (see dom frames)").option("-j, --json", "Output as JSON").action(async (script, options) => {
22899
+ program2.command("eval", { hidden: true }).description("Shortcut for: bdg dom eval").argument("<script>", "JavaScript to execute").option("--frame <frame>", "Evaluate in an iframe (see dom frames)").option("--full", EVAL_FULL_HELP).option("-j, --json", "Output as JSON").action(async (script, options) => {
22673
22900
  await handleDomEval(script, options);
22674
22901
  });
22675
22902
  dom.command("frames").description(
@@ -22680,7 +22907,10 @@ function registerDomCommands(program2) {
22680
22907
  dom.command("get").description("Get semantic accessibility structure (default) or raw HTML (--raw)").argument(
22681
22908
  "[selectorOrIndex]",
22682
22909
  `${SELECTOR_OR_INDEX_ARGUMENT} (e.g. ".error", "#app", 0); default: ${DOM_GET_DEFAULT_SELECTOR}`
22683
- ).option("--raw", "Output raw HTML with all filtering options").option("--full", "Show all of the element text (default: the first 500 characters)").option("--all", "Get all matches (only with --raw)").option("--index <n>", "Element index if selector matches multiple (0-based)", integerOption(0)).addOption(new Option("--nth <n>", "Alias of --index").argParser(integerOption(0)).hideHelp()).option(
22910
+ ).option("--raw", "Output raw HTML with all filtering options").option(
22911
+ "--full",
22912
+ `Show all of the element text (default: the first 500 characters); with --raw, all of the HTML (default: the first ${MAX_VALUE_LENGTH} characters)`
22913
+ ).option("--all", "Get all matches (only with --raw)").option("--index <n>", "Element index if selector matches multiple (0-based)", integerOption(0)).addOption(new Option("--nth <n>", "Alias of --index").argParser(integerOption(0)).hideHelp()).option(
22684
22914
  "--node-id <id>",
22685
22915
  "Get the element with this node id (from dom query/get --raw or a11y describe; implies --raw)",
22686
22916
  integerOption(1)
@@ -22703,8 +22933,8 @@ function registerDomCommands(program2) {
22703
22933
  "Image format: png or jpeg/jpg (default: from the file extension, else png)",
22704
22934
  screenshotFormatOption
22705
22935
  ).option("--quality <number>", "JPEG quality 0-100 (default: 90)", integerOption(0, 100)).option("--no-full-page", "Capture viewport only (default: full page)").option("--no-resize", "Disable auto-resize (full resolution)").option("--scroll <selector>", "Scroll element into view before capture").option("-f, --follow", "Continuous capture mode to directory").option("--interval <ms>", "Capture interval for --follow (default: 1000)").option("--limit <count>", "Max frames for --follow").option("-j, --json", "Output as JSON").action(
22706
- async (path7, target, options) => {
22707
- await handleDomScreenshot(path7, target, options);
22936
+ async (path8, target, options) => {
22937
+ await handleDomScreenshot(path8, target, options);
22708
22938
  }
22709
22939
  );
22710
22940
  }
@@ -22712,7 +22942,7 @@ function registerDomCommands(program2) {
22712
22942
  // src/commands/installSkill.ts
22713
22943
  import { existsSync as existsSync11, mkdirSync as mkdirSync2, readFileSync as readFileSync8, renameSync as renameSync2, rmSync as rmSync6, writeFileSync as writeFileSync2 } from "fs";
22714
22944
  import { homedir as homedir3 } from "os";
22715
- import { dirname as dirname6, join as join7 } from "path";
22945
+ import { dirname as dirname7, join as join8 } from "path";
22716
22946
  init_errors3();
22717
22947
  init_messages();
22718
22948
 
@@ -22721,9 +22951,9 @@ init_formatting();
22721
22951
  init_commands();
22722
22952
  import { homedir as homedir2 } from "os";
22723
22953
  var PATH_INDENT = " ".repeat(2 + 6 + 2 + 9 + 2);
22724
- function tildePath(path7) {
22954
+ function tildePath(path8) {
22725
22955
  const home = homedir2();
22726
- return path7.startsWith(`${home}/`) ? `~${path7.slice(home.length)}` : path7;
22956
+ return path8.startsWith(`${home}/`) ? `~${path8.slice(home.length)}` : path8;
22727
22957
  }
22728
22958
  function addSkillTargets(fmt, skills) {
22729
22959
  fmt.text("bdg skill:");
@@ -22747,11 +22977,11 @@ function formatInstalledSkills(data) {
22747
22977
  init_logging();
22748
22978
  init_errors();
22749
22979
  init_exitCodes();
22750
- var log31 = createLogger("bdg");
22751
- var SKILL_SOURCE_PATH = join7(PACKAGE_ROOT, ".claude", "skills", "bdg", "SKILL.md");
22980
+ var log30 = createLogger("bdg");
22981
+ var SKILL_SOURCE_PATH = join8(PACKAGE_ROOT, ".claude", "skills", "bdg", "SKILL.md");
22752
22982
  var SKILL_ROOTS = {
22753
- claude: join7(".claude", "skills"),
22754
- agents: join7(".agents", "skills")
22983
+ claude: join8(".claude", "skills"),
22984
+ agents: join8(".agents", "skills")
22755
22985
  };
22756
22986
  function installSkill(targets, home = homedir3(), source = SKILL_SOURCE_PATH) {
22757
22987
  if (!existsSync11(source)) {
@@ -22766,7 +22996,7 @@ function installSkill(targets, home = homedir3(), source = SKILL_SOURCE_PATH) {
22766
22996
  const skills = [];
22767
22997
  const failures = [];
22768
22998
  for (const target of targets) {
22769
- const result = writeSkill(target, join7(home, SKILL_ROOTS[target], "bdg", "SKILL.md"), content);
22999
+ const result = writeSkill(target, join8(home, SKILL_ROOTS[target], "bdg", "SKILL.md"), content);
22770
23000
  if ("error" in result) failures.push(result);
22771
23001
  else skills.push(result);
22772
23002
  }
@@ -22775,10 +23005,10 @@ function installSkill(targets, home = homedir3(), source = SKILL_SOURCE_PATH) {
22775
23005
  }
22776
23006
  function skillFailureError(failures, targets) {
22777
23007
  const other = failures.length === 1 ? targets.find((t) => t !== failures[0]?.target) : void 0;
22778
- const errors = failures.map(({ path: path7, error }) => {
23008
+ const errors = failures.map(({ path: path8, error }) => {
22779
23009
  const code = error.code;
22780
23010
  return skillWriteFailedError(
22781
- path7,
23011
+ path8,
22782
23012
  getErrorMessage(error),
22783
23013
  code === "ENOTDIR" || code === "EEXIST",
22784
23014
  other && `--${other}`
@@ -22790,30 +23020,30 @@ function skillFailureError(failures, targets) {
22790
23020
  EXIT_CODES.PERMISSION_DENIED
22791
23021
  );
22792
23022
  }
22793
- function writeSkill(target, path7, content) {
22794
- const existing = existsSync11(path7) ? readFileSync8(path7, "utf-8") : void 0;
23023
+ function writeSkill(target, path8, content) {
23024
+ const existing = existsSync11(path8) ? readFileSync8(path8, "utf-8") : void 0;
22795
23025
  if (existing === content) {
22796
- return { target, path: path7, status: "unchanged" };
23026
+ return { target, path: path8, status: "unchanged" };
22797
23027
  }
22798
- const backup = existing === void 0 ? void 0 : `${path7}.bak`;
22799
- const temporary = `${path7}.tmp`;
22800
- let writing = path7;
23028
+ const backup = existing === void 0 ? void 0 : `${path8}.bak`;
23029
+ const temporary = `${path8}.tmp`;
23030
+ let writing = path8;
22801
23031
  try {
22802
- mkdirSync2(dirname6(path7), { recursive: true });
23032
+ mkdirSync2(dirname7(path8), { recursive: true });
22803
23033
  writeFileSync2(temporary, content);
22804
23034
  if (backup !== void 0) {
22805
23035
  writing = backup;
22806
23036
  rmSync6(backup, { force: true });
22807
23037
  writeFileSync2(backup, existing ?? "");
22808
23038
  }
22809
- writing = path7;
22810
- renameSync2(temporary, path7);
23039
+ writing = path8;
23040
+ renameSync2(temporary, path8);
22811
23041
  } catch (error) {
22812
- safeRemoveFile(temporary, "temporary skill copy", log31);
23042
+ safeRemoveFile(temporary, "temporary skill copy", log30);
22813
23043
  return { target, path: writing, error };
22814
23044
  }
22815
- if (backup === void 0) return { target, path: path7, status: "installed" };
22816
- return { target, path: path7, status: "updated", backup };
23045
+ if (backup === void 0) return { target, path: path8, status: "installed" };
23046
+ return { target, path: path8, status: "updated", backup };
22817
23047
  }
22818
23048
  function selectedTargets(options) {
22819
23049
  const picked = Object.keys(SKILL_ROOTS).filter((target) => options[target]);
@@ -22844,17 +23074,127 @@ function registerInstallSkillCommand(program2) {
22844
23074
 
22845
23075
  // src/commands/network/har.ts
22846
23076
  import * as fs16 from "fs";
22847
- import * as path6 from "path";
23077
+ import * as path7 from "path";
22848
23078
  init_outputFile();
22849
23079
  init_client();
22850
23080
  init_paths();
22851
23081
 
22852
23082
  // src/telemetry/filterDsl.ts
22853
23083
  init_constants();
23084
+
23085
+ // src/telemetry/filters.ts
23086
+ init_url();
23087
+ var DEFAULT_EXCLUDED_DOMAINS = [
23088
+ "analytics.google.com",
23089
+ "googletagmanager.com",
23090
+ "googleadservices.com",
23091
+ "doubleclick.net",
23092
+ "google-analytics.com",
23093
+ "clarity.ms",
23094
+ "bat.bing.com",
23095
+ "facebook.com",
23096
+ "connect.facebook.net",
23097
+ "tiktok.com",
23098
+ "linkedin.com",
23099
+ "twitter.com",
23100
+ "snapchat.com",
23101
+ "mixpanel.com",
23102
+ "segment.com",
23103
+ "segment.io",
23104
+ "amplitude.com",
23105
+ "heap.io",
23106
+ "fullstory.com",
23107
+ "hotjar.com",
23108
+ "logrocket.com",
23109
+ "smartlook.com",
23110
+ "exactag.com",
23111
+ "criteo.com",
23112
+ "adroll.com",
23113
+ "outbrain.com",
23114
+ "taboola.com",
23115
+ "confirmit.com",
23116
+ "newrelic.com",
23117
+ "datadoghq.com",
23118
+ "sentry.io",
23119
+ "googlesyndication.com",
23120
+ "googletagservices.com",
23121
+ "adservice.google.com",
23122
+ "amazon-adsystem.com",
23123
+ "adnxs.com",
23124
+ "rubiconproject.com",
23125
+ "pubmatic.com",
23126
+ "casalemedia.com",
23127
+ "indexww.com",
23128
+ "openx.net",
23129
+ "3lift.com",
23130
+ "teads.tv",
23131
+ "bidswitch.net",
23132
+ "sharethrough.com",
23133
+ "adsafeprotected.com",
23134
+ "moatads.com",
23135
+ "doubleverify.com",
23136
+ "scorecardresearch.com",
23137
+ "imrworldwide.com",
23138
+ "chartbeat.com",
23139
+ "chartbeat.net",
23140
+ "permutive.com",
23141
+ "permutive.app",
23142
+ "optimizely.com",
23143
+ "quantserve.com",
23144
+ "quantcount.com",
23145
+ "krxd.net",
23146
+ "demdex.net",
23147
+ "omtrdc.net",
23148
+ "everesttech.net",
23149
+ "xiti.com",
23150
+ "ati-host.net"
23151
+ ];
23152
+ var EXCLUDED_DOMAINS_SET = new Set(DEFAULT_EXCLUDED_DOMAINS.map((d) => d.toLowerCase()));
23153
+ var DEFAULT_SKIP_BODY_MIME_TYPES = [
23154
+ "text/css",
23155
+ "image/svg+xml",
23156
+ "image/png",
23157
+ "image/jpeg",
23158
+ "image/gif",
23159
+ "image/webp",
23160
+ "image/avif",
23161
+ "image/bmp",
23162
+ "image/tiff",
23163
+ "font/woff",
23164
+ "font/woff2",
23165
+ "font/ttf",
23166
+ "font/eot",
23167
+ "font/otf",
23168
+ "application/font-woff",
23169
+ "application/font-woff2",
23170
+ "video/mp4",
23171
+ "video/webm",
23172
+ "audio/mpeg",
23173
+ "audio/wav"
23174
+ ];
23175
+ var SKIP_BODY_MIME_TYPES_SET = new Set(DEFAULT_SKIP_BODY_MIME_TYPES.map((m) => m.toLowerCase()));
23176
+ function matchesWildcard(str, pattern) {
23177
+ const regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
23178
+ const regex = new RegExp(`^${regexPattern}$`, "i");
23179
+ return regex.test(str);
23180
+ }
23181
+ function filterByResourceType(requests, types) {
23182
+ if (types.length === 0) {
23183
+ return requests;
23184
+ }
23185
+ return requests.filter((req) => {
23186
+ if (!req.resourceType) {
23187
+ return false;
23188
+ }
23189
+ return types.includes(req.resourceType);
23190
+ });
23191
+ }
23192
+
23193
+ // src/telemetry/filterDsl.ts
22854
23194
  init_logging();
22855
23195
  init_suggestions();
22856
23196
  init_url();
22857
- var log32 = createLogger("network");
23197
+ var log31 = createLogger("network");
22858
23198
  var SIZE_UNITS = {
22859
23199
  b: 1,
22860
23200
  kb: 1024,
@@ -22964,7 +23304,7 @@ function validateSizeValue(value) {
22964
23304
  parseSize(value);
22965
23305
  return null;
22966
23306
  } catch (error) {
22967
- log32.debug(
23307
+ log31.debug(
22968
23308
  `Size parse failed for "${value}": ${error instanceof Error ? error.message : String(error)}`
22969
23309
  );
22970
23310
  return createError(
@@ -23150,11 +23490,162 @@ Multiple filters:
23150
23490
 
23151
23491
  // src/telemetry/har/builder.ts
23152
23492
  import { createRequire as createRequire2 } from "node:module";
23493
+
23494
+ // src/telemetry/har/sanitizeBody.ts
23495
+ init_elementInfo();
23496
+ init_logging();
23497
+ init_errors();
23498
+ var log32 = createLogger("network");
23499
+ var REDACTED = "[redacted]";
23500
+ var SENSITIVE_FIELD = new RegExp(
23501
+ `${SENSITIVE_NAME_SOURCE}|token|secret|api[-_]?key|authoriz|credential|jwt|private[-_]?key|access[-_]?key|session|signature`,
23502
+ "i"
23503
+ );
23504
+ var FORM_BODY = /^[^\s=&{["]+=[^\s&]*(?:&[^\s=&]+=[^\s&]*)*$/;
23505
+ function isSensitiveField2(name) {
23506
+ return SENSITIVE_FIELD.test(name);
23507
+ }
23508
+ function redactPairs(text, isSensitive, replacement) {
23509
+ return text.split("&").map((pair) => {
23510
+ const eq = pair.indexOf("=");
23511
+ if (eq === -1 || !isSensitive(decodeName(pair.slice(0, eq)))) return pair;
23512
+ return `${pair.slice(0, eq)}=${replacement}`;
23513
+ }).join("&");
23514
+ }
23515
+ function redactRequestBody(text, mimeType) {
23516
+ if (/multipart\/form-data/i.test(mimeType)) return redactMultipart(text, mimeType);
23517
+ if (/x-www-form-urlencoded/i.test(mimeType) || FORM_BODY.test(text)) {
23518
+ return redactPairs(text, isSensitiveField2, REDACTED);
23519
+ }
23520
+ return redactJsonBody(text);
23521
+ }
23522
+ function decodeName(name) {
23523
+ try {
23524
+ return decodeURIComponent(name.replace(/\+/g, " "));
23525
+ } catch (error) {
23526
+ log32.debug(`Field name not decodable: ${getErrorMessage(error)}`);
23527
+ return name;
23528
+ }
23529
+ }
23530
+ function redactMultipart(text, mimeType) {
23531
+ const match = /boundary=(?:"([^"]+)"|([^;\s]+))/i.exec(mimeType);
23532
+ const boundary = match?.[1] ?? match?.[2];
23533
+ if (!boundary) return text;
23534
+ const delimiter = `--${boundary}`;
23535
+ return text.split(delimiter).map(redactPart).join(delimiter);
23536
+ }
23537
+ function redactPart(part) {
23538
+ const headerEnd = part.indexOf("\r\n\r\n");
23539
+ if (headerEnd === -1) return part;
23540
+ const name = /;\s*name="([^"]*)"/i.exec(part.slice(0, headerEnd))?.[1];
23541
+ if (name === void 0 || !isSensitiveField2(name)) return part;
23542
+ const valueEnd = part.endsWith("\r\n") ? part.length - 2 : part.length;
23543
+ return `${part.slice(0, headerEnd + 4)}${REDACTED}${part.slice(valueEnd)}`;
23544
+ }
23545
+ function redactJsonBody(text) {
23546
+ if (!/^\s*[[{]/.test(text)) return text;
23547
+ let parsed;
23548
+ try {
23549
+ parsed = JSON.parse(text);
23550
+ } catch (error) {
23551
+ log32.debug(`Request body not JSON: ${getErrorMessage(error)}`);
23552
+ return error instanceof SyntaxError ? text : REDACTED;
23553
+ }
23554
+ try {
23555
+ const hits = { count: 0 };
23556
+ const redacted = redactJson(parsed, false, hits);
23557
+ return hits.count > 0 ? JSON.stringify(redacted) : text;
23558
+ } catch (error) {
23559
+ log32.debug(`Request body redacted whole: ${getErrorMessage(error)}`);
23560
+ return REDACTED;
23561
+ }
23562
+ }
23563
+ function redactJson(value, sensitive, hits) {
23564
+ if (Array.isArray(value)) return value.map((item) => redactJson(item, sensitive, hits));
23565
+ if (value !== null && typeof value === "object") {
23566
+ return Object.fromEntries(
23567
+ Object.entries(value).map(([key, field]) => [
23568
+ key,
23569
+ redactJson(field, sensitive || isSensitiveField2(key), hits)
23570
+ ])
23571
+ );
23572
+ }
23573
+ if (!sensitive || !["string", "number", "boolean"].includes(typeof value)) return value;
23574
+ hits.count++;
23575
+ return REDACTED;
23576
+ }
23577
+
23578
+ // src/telemetry/har/sanitize.ts
23579
+ var URL_REDACTED = encodeURIComponent(REDACTED);
23580
+ var SENSITIVE_HEADERS = /* @__PURE__ */ new Set([
23581
+ "authorization",
23582
+ "proxy-authorization",
23583
+ "authentication",
23584
+ "cookie",
23585
+ "set-cookie"
23586
+ ]);
23587
+ var SENSITIVE_CUSTOM_HEADER = /^x-.*(key|token|secret|auth)/i;
23588
+ var SENSITIVE_HEADER_SEGMENT = /(^|-)(api-?key|apikey|token|secret|jwt|subscription-key|session(-?id)?)(-|$)/i;
23589
+ var URL_HEADERS = /* @__PURE__ */ new Set(["location", "referer"]);
23590
+ var SENSITIVE_URL_PARAM = /^(code|sig|key)$/i;
23591
+ function sanitizeEntry(entry) {
23592
+ const { request, response } = entry;
23593
+ const postData = request.postData;
23594
+ return {
23595
+ ...entry,
23596
+ request: {
23597
+ ...request,
23598
+ url: redactUrl(request.url),
23599
+ cookies: request.cookies.map(redactCookie),
23600
+ headers: request.headers.map(redactHeader),
23601
+ queryString: request.queryString.map(redactQueryParam),
23602
+ ...postData?.text !== void 0 && {
23603
+ postData: { ...postData, text: redactRequestBody(postData.text, postData.mimeType) }
23604
+ }
23605
+ },
23606
+ response: {
23607
+ ...response,
23608
+ cookies: response.cookies.map(redactCookie),
23609
+ headers: response.headers.map(redactHeader),
23610
+ redirectURL: redactUrl(response.redirectURL)
23611
+ }
23612
+ };
23613
+ }
23614
+ function isSensitiveHeader(name) {
23615
+ return SENSITIVE_HEADERS.has(name.toLowerCase()) || SENSITIVE_CUSTOM_HEADER.test(name) || SENSITIVE_HEADER_SEGMENT.test(name);
23616
+ }
23617
+ function redactHeader(header) {
23618
+ if (isSensitiveHeader(header.name)) return { ...header, value: REDACTED };
23619
+ if (!URL_HEADERS.has(header.name.toLowerCase())) return header;
23620
+ const value = redactUrl(header.value);
23621
+ return value === header.value ? header : { ...header, value };
23622
+ }
23623
+ function redactCookie(cookie) {
23624
+ return { ...cookie, value: REDACTED };
23625
+ }
23626
+ function isSensitiveParam(name) {
23627
+ return isSensitiveField2(name) || SENSITIVE_URL_PARAM.test(name);
23628
+ }
23629
+ function redactQueryParam(param) {
23630
+ return isSensitiveParam(param.name) ? { ...param, value: REDACTED } : param;
23631
+ }
23632
+ function redactUrl(url) {
23633
+ const hash = url.indexOf("#");
23634
+ const beforeHash = hash === -1 ? url : url.slice(0, hash);
23635
+ const fragment = hash === -1 ? "" : `#${redactPairs(url.slice(hash + 1), isSensitiveParam, URL_REDACTED)}`;
23636
+ const question = beforeHash.indexOf("?");
23637
+ if (question === -1) return beforeHash + fragment;
23638
+ const query = redactPairs(beforeHash.slice(question + 1), isSensitiveParam, URL_REDACTED);
23639
+ return `${beforeHash.slice(0, question)}?${query}${fragment}`;
23640
+ }
23641
+
23642
+ // src/telemetry/har/builder.ts
23153
23643
  var requireBuiltin = createRequire2(import.meta.url);
23154
23644
  var UNKNOWN_TIMING = -1;
23155
23645
  var DEFAULT_HTTP_VERSION = "HTTP/1.1";
23156
- function buildHAR(requests, metadata) {
23157
- const entries = [...requests].sort((a, b) => a.timestamp - b.timestamp).map(buildEntry);
23646
+ function buildHAR(requests, metadata, options = {}) {
23647
+ const built = [...requests].sort((a, b) => a.timestamp - b.timestamp).map(buildEntry);
23648
+ const entries = options.includeSensitive ? built : built.map(sanitizeEntry);
23158
23649
  const log36 = {
23159
23650
  version: "1.2",
23160
23651
  creator: {
@@ -23162,7 +23653,8 @@ function buildHAR(requests, metadata) {
23162
23653
  version: metadata.version,
23163
23654
  comment: "Browser Debugger CLI - https://github.com/szymdzum/browser-debugger-cli"
23164
23655
  },
23165
- entries
23656
+ entries,
23657
+ ...!options.includeSensitive && { comment: harSanitizedComment() }
23166
23658
  };
23167
23659
  if (metadata.chromeVersion) {
23168
23660
  log36.browser = {
@@ -23381,8 +23873,8 @@ function extractRedirectURL(req) {
23381
23873
  }
23382
23874
  function estimateRequestHeadersSize(method, url, headers) {
23383
23875
  const parsedUrl = new URL(url);
23384
- const path7 = parsedUrl.pathname + parsedUrl.search;
23385
- const requestLine = `${method} ${path7} ${DEFAULT_HTTP_VERSION}\r
23876
+ const path8 = parsedUrl.pathname + parsedUrl.search;
23877
+ const requestLine = `${method} ${path8} ${DEFAULT_HTTP_VERSION}\r
23386
23878
  `;
23387
23879
  const headersString = headers ? Object.entries(headers).map(([name, value]) => `${name}: ${value}\r
23388
23880
  `).join("") : "";
@@ -23505,6 +23997,11 @@ function formatSessionStatus(metadata, pid, activity, pageState, verbose = false
23505
23997
  if (activity.lastNetworkRequestAt) {
23506
23998
  fmt.keyValue(" Last Request", formatTimeAgo(activity.lastNetworkRequestAt), 18);
23507
23999
  }
24000
+ const evictedNote = networkEvictedNote({
24001
+ requestsDropped: activity.networkRequestsDropped ?? 0,
24002
+ bodiesEvicted: activity.networkBodiesEvicted ?? 0
24003
+ });
24004
+ if (evictedNote) fmt.text(` ${evictedNote}`);
23508
24005
  fmt.keyValue("Console Messages", `${activity.consoleMessagesCaptured} captured`, 18);
23509
24006
  if (activity.lastConsoleMessageAt) {
23510
24007
  fmt.keyValue(" Last Message", formatTimeAgo(activity.lastConsoleMessageAt), 18);
@@ -23614,6 +24111,7 @@ init_constants();
23614
24111
  init_formatting();
23615
24112
  init_commands();
23616
24113
  init_sessionCommand();
24114
+ init_strings();
23617
24115
  function inferResourceTypeFromMime(mimeType) {
23618
24116
  if (!mimeType) return void 0;
23619
24117
  return MIME_TYPE_RULES.find((rule) => rule.match.test(mimeType))?.type;
@@ -23645,17 +24143,25 @@ function buildPreviewJsonData(output, options) {
23645
24143
  ...output.totals && { totals: output.totals },
23646
24144
  ...output.pageCrashedAt !== void 0 && { pageCrashedAt: output.pageCrashedAt },
23647
24145
  ...pick("network") && output.data.network && { network: last(output.data.network) },
23648
- ...pick("console") && output.data.console && { console: last(output.data.console) }
24146
+ ...pick("console") && output.data.console && {
24147
+ console: last(output.data.console)?.map((message) => capMessageText(message, options.full))
24148
+ }
23649
24149
  };
23650
24150
  }
23651
24151
  function formatPreviewAsJson(output, options) {
23652
24152
  const envelope = buildSuccessResponse(buildPreviewJsonData(output, options));
23653
- return options.follow ? JSON.stringify(envelope) : JSON.stringify(envelope, null, 2);
24153
+ return options.follow ? JSON.stringify(envelope) : stringifyEnvelope(envelope);
23654
24154
  }
23655
24155
  function formatPreviewHumanReadable(output, options) {
23656
24156
  const body = options.verbose ? formatPreviewVerbose(output, options) : formatPreviewCompact(output, options);
23657
24157
  return withPageCrashedNote(body, output.pageCrashedAt);
23658
24158
  }
24159
+ function compactConsoleText(text, full) {
24160
+ if (full) return text;
24161
+ const capped = capLength(text, MAX_CONSOLE_TEXT_LENGTH);
24162
+ const shown = truncateText(capped.text, 2);
24163
+ return capped.truncatedFrom === void 0 ? shown : `${shown}${moreCharsNote(capped.truncatedFrom - capped.text.length)}`;
24164
+ }
23659
24165
  function formatPreviewCompact(output, options) {
23660
24166
  const fmt = new OutputFormatter();
23661
24167
  fmt.text(
@@ -23675,6 +24181,8 @@ function formatPreviewCompact(output, options) {
23675
24181
  const totalCount = output.totals?.network ?? output.data.network.length;
23676
24182
  const limitHint = formatLimitHint(showingCount, totalCount);
23677
24183
  fmt.text(`NETWORK (${showingCount}/${totalCount})${limitHint}:`);
24184
+ const evictedNote = previewEvictedNote(output);
24185
+ if (evictedNote) fmt.text(` ${evictedNote}`);
23678
24186
  if (requests.length === 0) {
23679
24187
  if (options.filteredTypes && options.filteredTypes.length > 0 && options.unfilteredNetworkCount && options.unfilteredNetworkCount > 0) {
23680
24188
  const typesStr = options.filteredTypes.join(", ");
@@ -23711,8 +24219,7 @@ function formatPreviewCompact(output, options) {
23711
24219
  } else {
23712
24220
  const consoleLines = messages.map((msg) => {
23713
24221
  const prefix = msg.type.toUpperCase().padEnd(5);
23714
- const text = truncateText(msg.text, 2);
23715
- return `${prefix} ${text}`;
24222
+ return `${prefix} ${compactConsoleText(msg.text, options.full)}`;
23716
24223
  });
23717
24224
  fmt.list(consoleLines, 2);
23718
24225
  }
@@ -23724,6 +24231,12 @@ function formatPreviewCompact(output, options) {
23724
24231
  }
23725
24232
  return fmt.build();
23726
24233
  }
24234
+ function previewEvictedNote(output) {
24235
+ return networkEvictedNote({
24236
+ requestsDropped: output.totals?.networkDropped ?? 0,
24237
+ bodiesEvicted: output.totals?.networkBodiesEvicted ?? 0
24238
+ });
24239
+ }
23727
24240
  function formatPreviewVerbose(output, options) {
23728
24241
  const fmt = new OutputFormatter();
23729
24242
  fmt.text(PREVIEW_HEADERS.LIVE_PREVIEW).separator("\u2501", 50);
@@ -23747,6 +24260,8 @@ function formatPreviewVerbose(output, options) {
23747
24260
  const requests = lastCount === 0 ? output.data.network : output.data.network.slice(-lastCount);
23748
24261
  const title = lastCount === 0 ? `Network Requests (all ${requests.length})` : `Network Requests (last ${requests.length} of ${output.totals?.network ?? output.data.network.length})`;
23749
24262
  fmt.text(title).separator("\u2501", 50);
24263
+ const evictedNote = previewEvictedNote(output);
24264
+ if (evictedNote) fmt.text(evictedNote);
23750
24265
  if (requests.length === 0) {
23751
24266
  if (options.filteredTypes && options.filteredTypes.length > 0 && options.unfilteredNetworkCount && options.unfilteredNetworkCount > 0) {
23752
24267
  const typesStr = options.filteredTypes.join(", ");
@@ -23791,7 +24306,9 @@ function formatPreviewVerbose(output, options) {
23791
24306
  } else {
23792
24307
  messages.forEach((msg) => {
23793
24308
  const icon = msg.type === "error" ? "ERR" : msg.type === "warning" ? "WARN" : "INFO";
23794
- fmt.text(`${icon} [${msg.type}] ${msg.text}`);
24309
+ fmt.text(
24310
+ `${icon} [${msg.type}] ${capForDisplay(msg.text, MAX_CONSOLE_TEXT_LENGTH, options.full)}`
24311
+ );
23795
24312
  });
23796
24313
  }
23797
24314
  fmt.blank();
@@ -23930,10 +24447,10 @@ function generateHARFilename() {
23930
24447
  const minutes = String(now.getMinutes()).padStart(2, "0");
23931
24448
  const seconds = String(now.getSeconds()).padStart(2, "0");
23932
24449
  const base = `capture-${year}-${month}-${day}-${hours}${minutes}${seconds}`;
23933
- const sessionDir = path6.dirname(getSessionFilePath("OUTPUT"));
23934
- let candidate = path6.join(sessionDir, `${base}.har`);
24450
+ const sessionDir = path7.dirname(getSessionFilePath("OUTPUT"));
24451
+ let candidate = path7.join(sessionDir, `${base}.har`);
23935
24452
  for (let n = 2; fs16.existsSync(candidate); n++) {
23936
- candidate = path6.join(sessionDir, `${base}-${n}.har`);
24453
+ candidate = path7.join(sessionDir, `${base}-${n}.har`);
23937
24454
  }
23938
24455
  return candidate;
23939
24456
  }
@@ -23949,16 +24466,20 @@ async function getChromeVersion() {
23949
24466
  }
23950
24467
  function formatHARExport(data) {
23951
24468
  if ("log" in data) return JSON.stringify(data, null, 2);
23952
- const filterNote = data.filtered ? " (filtered)" : "";
23953
- return `\u2713 Exported ${data.entries} requests${filterNote} to ${data.file}`;
24469
+ return harExportedMessage(data);
23954
24470
  }
24471
+ var HAR_FILE_MODE = 384;
23955
24472
  var STDOUT_PATH = "-";
23956
24473
  var filterDslOption = new Option(
23957
24474
  "--filter <dsl>",
23958
24475
  'Filter requests using DevTools DSL (e.g., "status-code:>=400")'
23959
24476
  );
24477
+ var includeSensitiveOption = new Option(
24478
+ "--include-sensitive",
24479
+ "Keep credentials (auth/cookie/API key headers, cookie values, URL tokens, password and token body fields); redacted by default"
24480
+ );
23960
24481
  function registerHarCommand(networkCmd) {
23961
- networkCmd.command("har [output-file]").addHelpText("after", "\nUse - as the output file to write the HAR to stdout.").description("Export network data as HAR 1.2 format").addOption(jsonOption()).addOption(filterDslOption).action(async (outputFile, options) => {
24482
+ networkCmd.command("har [output-file]").addHelpText("after", "\nUse - as the output file to write the HAR to stdout.").description("Export network data as HAR 1.2 format").addOption(jsonOption()).addOption(filterDslOption).addOption(includeSensitiveOption).action(async (outputFile, options) => {
23962
24483
  await runCommand(
23963
24484
  async () => {
23964
24485
  let requests = await getNetworkRequests();
@@ -23973,20 +24494,26 @@ function registerHarCommand(networkCmd) {
23973
24494
  const outputPath = outputFile ?? generateHARFilename();
23974
24495
  if (outputPath !== STDOUT_PATH) assertFilePath(outputPath, ".har");
23975
24496
  const chromeVersion = await getChromeVersion();
23976
- const har = buildHAR(requests, {
23977
- version: VERSION,
23978
- ...chromeVersion && { chromeVersion }
23979
- });
24497
+ const includeSensitive = options.includeSensitive === true;
24498
+ const har = buildHAR(
24499
+ requests,
24500
+ { version: VERSION, ...chromeVersion && { chromeVersion } },
24501
+ { includeSensitive }
24502
+ );
23980
24503
  if (outputPath === STDOUT_PATH) return { success: true, data: har };
23981
- const file = await writeOutputFile(outputPath, JSON.stringify(har, null, 2), ".har");
23982
- return {
23983
- success: true,
23984
- data: {
23985
- file,
23986
- entries: har.log.entries.length,
23987
- filtered
23988
- }
24504
+ const file = await writeOutputFile(
24505
+ outputPath,
24506
+ JSON.stringify(har, null, 2),
24507
+ ".har",
24508
+ HAR_FILE_MODE
24509
+ );
24510
+ const data = {
24511
+ file,
24512
+ entries: har.log.entries.length,
24513
+ filtered,
24514
+ sanitized: !includeSensitive
23989
24515
  };
24516
+ return { success: true, data };
23990
24517
  },
23991
24518
  options,
23992
24519
  formatHARExport
@@ -24153,6 +24680,8 @@ function formatNetworkListHuman(requests, options) {
24153
24680
  const header = buildHeader(requests.length, options.filteredCount ?? totalCount, totalCount);
24154
24681
  fmt.text(header);
24155
24682
  fmt.separator("\u2500", SEPARATOR_WIDTH2);
24683
+ const evictedNote = options.evictions && networkEvictedNote(options.evictions);
24684
+ if (evictedNote) fmt.text(evictedNote);
24156
24685
  if (requests.length === 0) {
24157
24686
  fmt.text("No matching requests found.");
24158
24687
  return fmt.build();
@@ -24169,6 +24698,8 @@ var FOLLOW_ID_WIDTH = 14;
24169
24698
  function formatNetworkFollowRows(requests, options = {}) {
24170
24699
  const fmt = new OutputFormatter();
24171
24700
  const widths = columnWidths(requests, FOLLOW_ID_WIDTH);
24701
+ const evictedNote = options.evictions && networkEvictedNote(options.evictions);
24702
+ if (evictedNote) fmt.text(evictedNote);
24172
24703
  if (options.header) {
24173
24704
  fmt.text(formatColumnHeader(widths));
24174
24705
  fmt.separator("\u2500", SEPARATOR_WIDTH2);
@@ -24236,12 +24767,35 @@ function buildFormatOptions2(options, result, lastLimit) {
24236
24767
  last: lastLimit,
24237
24768
  totalCount: result.totalCount,
24238
24769
  filteredCount: result.filteredCount,
24239
- ...result.pageStart && { pageStart: result.pageStart }
24770
+ ...result.pageStart && { pageStart: result.pageStart },
24771
+ evictions: {
24772
+ requestsDropped: result.dropped ?? 0,
24773
+ bodiesEvicted: result.bodiesEvicted ?? 0
24774
+ }
24775
+ };
24776
+ }
24777
+ function newEvictionKinds() {
24778
+ let requestsNoted = false;
24779
+ let bodiesNoted = false;
24780
+ return (counts) => {
24781
+ const newRequests = !requestsNoted && counts.requestsDropped > 0;
24782
+ const newBodies = !bodiesNoted && counts.bodiesEvicted > 0;
24783
+ requestsNoted ||= newRequests;
24784
+ bodiesNoted ||= newBodies;
24785
+ return newRequests || newBodies ? counts : void 0;
24786
+ };
24787
+ }
24788
+ function evictionFields(counts) {
24789
+ if (!counts) return {};
24790
+ return {
24791
+ ...counts.requestsDropped > 0 && { dropped: counts.requestsDropped },
24792
+ ...counts.bodiesEvicted > 0 && { bodiesEvicted: counts.bodiesEvicted }
24240
24793
  };
24241
24794
  }
24242
24795
  async function runFollowMode2(options, resourceTypes, lastN) {
24243
24796
  const shown = /* @__PURE__ */ new Set();
24244
24797
  const newCrash = newPageCrashes();
24798
+ const newEviction = newEvictionKinds();
24245
24799
  let started = false;
24246
24800
  const showNetwork = async () => {
24247
24801
  const result = await fetchNetworkRequests(filtersNeedHeaders(options));
@@ -24251,6 +24805,7 @@ async function runFollowMode2(options, resourceTypes, lastN) {
24251
24805
  noteFollowConnected();
24252
24806
  const { requests } = result.data;
24253
24807
  const crashedAt = newCrash(result.data.pageCrashedAt);
24808
+ const evictions = newEviction(result.data.evictions);
24254
24809
  const finished = filterRequests(requests, options, resourceTypes).filter(
24255
24810
  (request) => request.duration !== void 0 && !shown.has(request.requestId)
24256
24811
  );
@@ -24259,12 +24814,13 @@ async function runFollowMode2(options, resourceTypes, lastN) {
24259
24814
  finished.forEach((request) => shown.add(request.requestId));
24260
24815
  const fresh = started || lastN === 0 ? finished : finished.slice(-lastN);
24261
24816
  if (options.json) {
24262
- if (!started || fresh.length > 0 || crashedAt !== void 0) {
24817
+ if (!started || fresh.length > 0 || crashedAt !== void 0 || evictions) {
24263
24818
  const data = {
24264
24819
  requests: fresh,
24265
24820
  totalCount: requests.length,
24266
24821
  filteredCount: fresh.length,
24267
- ...crashedAt !== void 0 && { pageCrashedAt: crashedAt }
24822
+ ...crashedAt !== void 0 && { pageCrashedAt: crashedAt },
24823
+ ...evictionFields(evictions)
24268
24824
  };
24269
24825
  console.log(JSON.stringify(buildSuccessResponse(data)));
24270
24826
  }
@@ -24273,7 +24829,8 @@ async function runFollowMode2(options, resourceTypes, lastN) {
24273
24829
  const text = formatNetworkFollowRows(fresh, {
24274
24830
  header: !started,
24275
24831
  verbose: options.verbose ?? false,
24276
- ...pageStart && { pageStart }
24832
+ ...pageStart && { pageStart },
24833
+ ...evictions && { evictions }
24277
24834
  });
24278
24835
  if (text) console.log(text);
24279
24836
  if (crashedAt !== void 0) console.log(pageCrashedNote(crashedAt));
@@ -24349,7 +24906,7 @@ ${formatPresetHelp()}`
24349
24906
  }
24350
24907
  return createErrorResult(result.error, result.exitCode, result.suggestion);
24351
24908
  }
24352
- const { requests, pageCrashedAt } = result.data;
24909
+ const { requests, pageCrashedAt, evictions } = result.data;
24353
24910
  const filtered = filterRequests(requests, options, resourceTypes);
24354
24911
  const pageStart = pageStartOf(requests);
24355
24912
  return {
@@ -24359,7 +24916,8 @@ ${formatPresetHelp()}`
24359
24916
  totalCount: requests.length,
24360
24917
  filteredCount: filtered.length,
24361
24918
  ...pageStart && { pageStart },
24362
- ...pageCrashedAt !== void 0 && { pageCrashedAt }
24919
+ ...pageCrashedAt !== void 0 && { pageCrashedAt },
24920
+ ...evictionFields(evictions)
24363
24921
  }
24364
24922
  };
24365
24923
  },
@@ -24548,6 +25106,7 @@ function registerPageCommands(program2) {
24548
25106
  }
24549
25107
 
24550
25108
  // src/commands/peek.ts
25109
+ init_constants();
24551
25110
  init_errors3();
24552
25111
  init_messages();
24553
25112
  init_exitCodes();
@@ -24645,7 +25204,8 @@ function previewDisplayOptions(options, lastN) {
24645
25204
  console: options.console,
24646
25205
  last: lastN,
24647
25206
  verbose: options.verbose,
24648
- follow: options.follow
25207
+ follow: options.follow,
25208
+ full: options.full
24649
25209
  };
24650
25210
  }
24651
25211
  async function followPreview(options) {
@@ -24658,6 +25218,10 @@ function registerPeekCommand(program2) {
24658
25218
  program2.command("peek").description("Preview collected data without stopping the session").addOption(jsonOption()).option("-v, --verbose", "Use verbose output with full URLs and formatting", false).option("-n, --network", "Show only network requests", false).option("-c, --console", "Show only console messages", false).option("-f, --follow", "Watch for updates (like tail -f)", false).option("--interval <ms>", "Refresh interval of --follow in ms, 100-60000 (default: 1000)").option("--last <count>", "Show last N items, 0 for all", "10").option(
24659
25219
  "--type <types>",
24660
25220
  "Filter network requests by resource type (comma-separated: Document,XHR,Fetch,etc.)"
25221
+ ).option(
25222
+ "--full",
25223
+ `Print console message texts whole (default: the first ${MAX_CONSOLE_TEXT_LENGTH} characters, ${MAX_CONSOLE_JSON_TEXT_LENGTH} in JSON)`,
25224
+ false
24661
25225
  ).action(async (options) => {
24662
25226
  showBothSectionsWhenBothRequested(options);
24663
25227
  if (options.network && !options.json) {
@@ -24785,6 +25349,10 @@ async function describeSession(entry) {
24785
25349
  const socketPath = sessionFilePathIn(dir, "DAEMON_SOCKET");
24786
25350
  const probe = await probeDaemonSocket(socketPath);
24787
25351
  if (probe !== "alive") return describeWithoutSocket(entry, probe);
25352
+ const untrusted = secureSessionDir(dir);
25353
+ if (untrusted) {
25354
+ return { name, state: "untrusted", untrusted: `${untrusted.dir}: ${untrusted.reason}` };
25355
+ }
24788
25356
  try {
24789
25357
  const response = await getStatus(socketPath);
24790
25358
  if (response.status === "ok" && response.data) return toRunningSession(name, response.data);
@@ -24900,6 +25468,15 @@ function formatSessionList(data) {
24900
25468
  if (ended.length > 0) {
24901
25469
  fmt.blank().section("Ended without bdg stop:", ended);
24902
25470
  }
25471
+ const untrusted = data.sessions.flatMap(
25472
+ ({ name, untrusted: why }) => why ? [untrustedSessionText(name ?? DEFAULT_SESSION_LABEL, why)] : []
25473
+ );
25474
+ if (untrusted.length > 0) {
25475
+ fmt.blank().section(
25476
+ "Directory not safe to use (not asked; bdg status --session <name> says how to fix it):",
25477
+ untrusted
25478
+ );
25479
+ }
24903
25480
  const cleanups = data.sessions.flatMap((session) => session.cleanup ? [session.cleanup] : []);
24904
25481
  if (cleanups.length > 0) {
24905
25482
  fmt.hints("Crashed or stale sessions; clean up with:", cleanups);
@@ -25017,6 +25594,7 @@ function registerStatusCommand(program2) {
25017
25594
  }
25018
25595
  return { success: true, data: withSessionName(jsonOutput) };
25019
25596
  } catch (error) {
25597
+ if (error instanceof CommandError) throw error;
25020
25598
  const errorMessage = getErrorMessage(error);
25021
25599
  if (error instanceof IPCTimeoutError) {
25022
25600
  const err = sessionNotRespondingError(error.timeoutMs / 1e3);
@@ -25065,6 +25643,7 @@ function registerStatusCommand(program2) {
25065
25643
  }
25066
25644
 
25067
25645
  // src/commands/stop.ts
25646
+ init_errors3();
25068
25647
  init_client();
25069
25648
  init_transport();
25070
25649
  init_formatting();
@@ -25135,7 +25714,7 @@ function registerStopCommand(program2) {
25135
25714
  };
25136
25715
  }
25137
25716
  } catch (error) {
25138
- if (error instanceof IPCTimeoutError) throw error;
25717
+ if (error instanceof IPCTimeoutError || error instanceof CommandError) throw error;
25139
25718
  const errorMessage = getErrorMessage(error);
25140
25719
  if (isDaemonNotRunningError(errorMessage)) {
25141
25720
  return {
@@ -25206,18 +25785,38 @@ var commandRegistry = [
25206
25785
  registerInstallSkillCommand
25207
25786
  ];
25208
25787
 
25788
+ // src/program.ts
25789
+ var CLI_NAME = "bdg";
25790
+ var CLI_DESCRIPTION = "Browser telemetry via Chrome DevTools Protocol";
25791
+ var SESSION_OPTION_FLAGS = "--session <name>";
25792
+ var SESSION_OPTION_DESCRIPTION = "Use a named session (own daemon, Chrome and port) instead of the default one; env: BDG_SESSION";
25793
+ function addGlobalOptions(command) {
25794
+ for (const sub of command.commands) {
25795
+ if (!sub.options.some((option) => option.long === "--debug")) {
25796
+ sub.addOption(new Option("--debug", "Enable debug logging").hideHelp());
25797
+ }
25798
+ if (!sub.options.some((option) => option.long === "--quiet")) {
25799
+ sub.addOption(new Option("-q, --quiet", "Hide tips and hints").hideHelp());
25800
+ }
25801
+ if (!sub.options.some((option) => option.long === "--session")) {
25802
+ sub.addOption(new Option(SESSION_OPTION_FLAGS, SESSION_OPTION_DESCRIPTION).hideHelp());
25803
+ }
25804
+ addGlobalOptions(sub);
25805
+ }
25806
+ }
25807
+ function buildProgram(output = {}) {
25808
+ const program2 = new Command().name(CLI_NAME).description(CLI_DESCRIPTION).version(VERSION).option("--debug", "Enable debug logging (verbose output)").option(SESSION_OPTION_FLAGS, SESSION_OPTION_DESCRIPTION).enablePositionalOptions().exitOverride().configureOutput(output);
25809
+ commandRegistry.forEach((register) => register(program2));
25810
+ addGlobalOptions(program2);
25811
+ return program2;
25812
+ }
25813
+
25209
25814
  // src/index.ts
25210
- init_errors3();
25211
- init_messages();
25212
25815
  init_formatting();
25213
25816
  init_logging();
25214
25817
  init_errors();
25215
25818
  init_exitCodes();
25216
- var CLI_NAME = "bdg";
25217
- var CLI_DESCRIPTION = "Browser telemetry via Chrome DevTools Protocol";
25218
- var SESSION_OPTION_FLAGS = "--session <name>";
25219
25819
  var FULL_HELP_FLAG = "--full";
25220
- var SESSION_OPTION_DESCRIPTION = "Use a named session (own daemon, Chrome and port) instead of the default one; env: BDG_SESSION";
25221
25820
  function flagArgs() {
25222
25821
  const args = process.argv.slice(2);
25223
25822
  const end = args.indexOf("--");
@@ -25258,14 +25857,12 @@ async function main() {
25258
25857
  configureStdio();
25259
25858
  const helpTopic = rewriteHelpCommand();
25260
25859
  const jsonMode = isJsonMode();
25261
- const program2 = new Command().name(CLI_NAME).description(CLI_DESCRIPTION).version(VERSION).option("--debug", "Enable debug logging (verbose output)").option(SESSION_OPTION_FLAGS, SESSION_OPTION_DESCRIPTION).enablePositionalOptions().exitOverride().configureOutput({
25860
+ const program2 = buildProgram({
25262
25861
  writeOut: (text) => {
25263
25862
  if (!jsonMode) process.stdout.write(text);
25264
25863
  },
25265
25864
  outputError: () => void 0
25266
25865
  });
25267
- commandRegistry.forEach((register) => register(program2));
25268
- addGlobalOptions(program2);
25269
25866
  program2.hook("preAction", (_root, actionCommand) => applyGlobalOptions(program2, actionCommand));
25270
25867
  assertKnownHelpTopic(program2, helpTopic);
25271
25868
  assertNotMistypedCommand(program2, process.argv);
@@ -25288,20 +25885,6 @@ function helpJson(program2) {
25288
25885
  if (command !== program2) return generateCommandHelp(program2, command);
25289
25886
  return flagArgs().includes(FULL_HELP_FLAG) ? generateMachineReadableHelp(program2) : generateCompactHelp(program2);
25290
25887
  }
25291
- function addGlobalOptions(command) {
25292
- for (const sub of command.commands) {
25293
- if (!sub.options.some((option) => option.long === "--debug")) {
25294
- sub.addOption(new Option("--debug", "Enable debug logging").hideHelp());
25295
- }
25296
- if (!sub.options.some((option) => option.long === "--quiet")) {
25297
- sub.addOption(new Option("-q, --quiet", "Hide tips and hints").hideHelp());
25298
- }
25299
- if (!sub.options.some((option) => option.long === "--session")) {
25300
- sub.addOption(new Option(SESSION_OPTION_FLAGS, SESSION_OPTION_DESCRIPTION).hideHelp());
25301
- }
25302
- addGlobalOptions(sub);
25303
- }
25304
- }
25305
25888
  function sessionFromArgv(argv) {
25306
25889
  let value;
25307
25890
  argv.forEach((arg, i) => {
@@ -25329,7 +25912,7 @@ function applyGlobalOptions(program2, actionCommand) {
25329
25912
  }
25330
25913
  function handleUsageError(error, command, jsonMode) {
25331
25914
  if (error.code === "commander.version") {
25332
- if (jsonMode) console.log(JSON.stringify(buildSuccessResponse({ version: VERSION }), null, 2));
25915
+ if (jsonMode) console.log(stringifyEnvelope(buildSuccessResponse({ version: VERSION })));
25333
25916
  process.exit(EXIT_CODES.SUCCESS);
25334
25917
  }
25335
25918
  if (error.code === "commander.helpDisplayed" || error.code === "commander.help" && error.exitCode === 0) {
@@ -25338,9 +25921,7 @@ function handleUsageError(error, command, jsonMode) {
25338
25921
  const exitCode = EXIT_CODES.INVALID_ARGUMENTS;
25339
25922
  const { message, suggestion } = usageErrorDetails(error, command);
25340
25923
  if (jsonMode) {
25341
- console.log(
25342
- JSON.stringify(OutputBuilder.buildJsonError(message, { exitCode, suggestion }), null, 2)
25343
- );
25924
+ console.log(stringifyEnvelope(OutputBuilder.buildJsonError(message, { exitCode, suggestion })));
25344
25925
  } else if (error.code !== "commander.help") {
25345
25926
  console.error(genericError(message, suggestion));
25346
25927
  }
@@ -25352,7 +25933,7 @@ function handleFatalError(error) {
25352
25933
  const metadata = error instanceof CommandError ? error.metadata : {};
25353
25934
  if (isJsonMode()) {
25354
25935
  console.log(
25355
- JSON.stringify(OutputBuilder.buildJsonError(message, { ...metadata, exitCode }), null, 2)
25936
+ stringifyEnvelope(OutputBuilder.buildJsonError(message, { ...metadata, exitCode }))
25356
25937
  );
25357
25938
  } else {
25358
25939
  console.error(genericError(message));