browser-debugger-cli 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/.claude/skills/bdg/SKILL.md +4 -4
  2. package/README.md +1 -0
  3. package/dist/commands/cdp.d.ts +22 -1
  4. package/dist/commands/cdp.js +100 -43
  5. package/dist/commands/console.d.ts +12 -0
  6. package/dist/commands/console.js +62 -12
  7. package/dist/commands/dom/DomElementResolver.d.ts +3 -1
  8. package/dist/commands/dom/DomElementResolver.js +10 -3
  9. package/dist/commands/dom/a11y.js +3 -2
  10. package/dist/commands/dom/eval.d.ts +3 -2
  11. package/dist/commands/dom/eval.js +11 -5
  12. package/dist/commands/dom/form.js +10 -9
  13. package/dist/commands/dom/formInteraction.js +8 -7
  14. package/dist/commands/dom/get.js +8 -8
  15. package/dist/commands/dom/helpers/index.d.ts +1 -1
  16. package/dist/commands/dom/helpers/index.js +1 -1
  17. package/dist/commands/dom/helpers/query.d.ts +27 -3
  18. package/dist/commands/dom/helpers/query.js +152 -64
  19. package/dist/commands/dom/helpers/screenshot.js +13 -13
  20. package/dist/commands/dom/index.js +4 -2
  21. package/dist/commands/dom/query.d.ts +19 -2
  22. package/dist/commands/dom/query.js +37 -6
  23. package/dist/commands/dom/screenshot.js +2 -1
  24. package/dist/commands/dom/semanticUtils.d.ts +3 -2
  25. package/dist/commands/dom/semanticUtils.js +40 -9
  26. package/dist/commands/helpJson.d.ts +82 -19
  27. package/dist/commands/helpJson.js +111 -40
  28. package/dist/commands/helpTopic.d.ts +16 -1
  29. package/dist/commands/helpTopic.js +59 -1
  30. package/dist/commands/installSkill.d.ts +15 -5
  31. package/dist/commands/installSkill.js +86 -16
  32. package/dist/commands/network/list.js +22 -12
  33. package/dist/commands/optionBehaviors.js +33 -11
  34. package/dist/commands/shared/daemonErrorHandler.d.ts +5 -2
  35. package/dist/commands/shared/daemonErrorHandler.js +20 -9
  36. package/dist/commands/shared/dataFetcher.d.ts +12 -4
  37. package/dist/commands/shared/dataFetcher.js +12 -4
  38. package/dist/commands/shared/followMode.d.ts +9 -1
  39. package/dist/commands/shared/followMode.js +22 -4
  40. package/dist/commands/shared/optionTypes.d.ts +4 -1
  41. package/dist/commands/shared/outputFile.js +6 -1
  42. package/dist/commands/start.d.ts +7 -5
  43. package/dist/commands/start.js +65 -21
  44. package/dist/commands/stop.d.ts +11 -0
  45. package/dist/commands/stop.js +24 -1
  46. package/dist/commands.js +1 -1
  47. package/dist/connection/cdp.d.ts +7 -0
  48. package/dist/connection/cdp.js +9 -0
  49. package/dist/connection/launcher.js +3 -2
  50. package/dist/daemon/SessionController.js +6 -1
  51. package/dist/daemon/launcher.d.ts +3 -2
  52. package/dist/daemon/launcher.js +47 -3
  53. package/dist/daemon/session/Session.d.ts +4 -1
  54. package/dist/daemon/session/Session.js +33 -2
  55. package/dist/daemon/session/TelemetryStore.d.ts +8 -1
  56. package/dist/daemon/session/TelemetryStore.js +13 -1
  57. package/dist/daemon/session/commandRegistry.js +29 -13
  58. package/dist/daemon/session/interactions.d.ts +2 -1
  59. package/dist/daemon/session/interactions.js +13 -1
  60. package/dist/daemon/session/plugins.js +16 -2
  61. package/dist/daemon/session/teardown.js +1 -1
  62. package/dist/daemon.js +1622 -748
  63. package/dist/errors/messages.d.ts +54 -11
  64. package/dist/errors/messages.js +109 -22
  65. package/dist/index.js +13733 -8796
  66. package/dist/ipc/client.d.ts +18 -2
  67. package/dist/ipc/client.js +26 -5
  68. package/dist/ipc/protocol/auditTypes.d.ts +8 -2
  69. package/dist/ipc/protocol/commands.d.ts +12 -0
  70. package/dist/ipc/protocol/domTypes.d.ts +12 -0
  71. package/dist/ipc/protocol/inspectTypes.d.ts +2 -0
  72. package/dist/ipc/session/types.d.ts +2 -0
  73. package/dist/runtime/dom/actionEffects.d.ts +5 -1
  74. package/dist/runtime/dom/actionEffects.js +26 -14
  75. package/dist/runtime/dom/audit.js +3 -2
  76. package/dist/runtime/dom/auditModel.js +6 -1
  77. package/dist/runtime/dom/auditScripts.d.ts +9 -3
  78. package/dist/runtime/dom/auditScripts.js +41 -5
  79. package/dist/runtime/dom/elementGeometry.d.ts +10 -3
  80. package/dist/runtime/dom/elementGeometry.js +27 -4
  81. package/dist/runtime/dom/elementInfo.d.ts +74 -18
  82. package/dist/runtime/dom/elementInfo.js +187 -40
  83. package/dist/runtime/dom/evalHelpers.d.ts +12 -2
  84. package/dist/runtime/dom/evalHelpers.js +67 -7
  85. package/dist/runtime/dom/formDiscovery.d.ts +6 -2
  86. package/dist/runtime/dom/formDiscovery.js +20 -3
  87. package/dist/runtime/dom/formFillHelpers/fill.js +7 -11
  88. package/dist/runtime/dom/formFillHelpers/pressKey.js +2 -2
  89. package/dist/runtime/dom/formFillHelpers/shared.d.ts +16 -10
  90. package/dist/runtime/dom/formFillHelpers/shared.js +19 -52
  91. package/dist/runtime/dom/formSubmitHelpers.js +4 -3
  92. package/dist/runtime/dom/frameLayout.js +1 -0
  93. package/dist/runtime/dom/inspect.js +5 -6
  94. package/dist/runtime/dom/inspectAllStyles.js +1 -0
  95. package/dist/runtime/dom/inspectHints.d.ts +1 -1
  96. package/dist/runtime/dom/inspectModel.d.ts +2 -1
  97. package/dist/runtime/dom/inspectModel.js +7 -3
  98. package/dist/runtime/dom/inspectPaintModel.d.ts +2 -0
  99. package/dist/runtime/dom/inspectPaintModel.js +3 -1
  100. package/dist/runtime/dom/inspectScripts.d.ts +29 -2
  101. package/dist/runtime/dom/inspectScripts.js +49 -10
  102. package/dist/runtime/dom/layout.js +9 -7
  103. package/dist/runtime/dom/reactEventHelpers.d.ts +14 -4
  104. package/dist/runtime/dom/reactEventHelpers.js +63 -27
  105. package/dist/runtime/dom/targetNode.d.ts +18 -5
  106. package/dist/runtime/dom/targetNode.js +268 -8
  107. package/dist/runtime/dom/wait.js +2 -1
  108. package/dist/runtime/page/bdgWorld.d.ts +57 -0
  109. package/dist/runtime/page/bdgWorld.js +180 -0
  110. package/dist/runtime/page/replacedBuiltins.d.ts +28 -0
  111. package/dist/runtime/page/replacedBuiltins.js +136 -0
  112. package/dist/session/QueryCacheManager.d.ts +4 -1
  113. package/dist/session/QueryCacheManager.js +5 -2
  114. package/dist/session/chrome.d.ts +4 -1
  115. package/dist/session/chrome.js +7 -1
  116. package/dist/session/cleanup/staleSession.d.ts +21 -4
  117. package/dist/session/cleanup/staleSession.js +79 -9
  118. package/dist/session/cleanup/userCommands.d.ts +4 -1
  119. package/dist/session/cleanup/userCommands.js +10 -5
  120. package/dist/session/daemonSocket.d.ts +10 -0
  121. package/dist/session/daemonSocket.js +22 -0
  122. package/dist/session/lastSession.d.ts +6 -3
  123. package/dist/session/lastSession.js +11 -5
  124. package/dist/session/paths.d.ts +3 -1
  125. package/dist/session/paths.js +5 -5
  126. package/dist/session/portClaims.js +4 -3
  127. package/dist/session/sessionList.d.ts +13 -5
  128. package/dist/session/sessionList.js +31 -7
  129. package/dist/telemetry/a11y.js +2 -2
  130. package/dist/telemetry/console.d.ts +2 -1
  131. package/dist/telemetry/console.js +30 -21
  132. package/dist/telemetry/pageCrash.d.ts +26 -0
  133. package/dist/telemetry/pageCrash.js +53 -0
  134. package/dist/types.d.ts +16 -0
  135. package/dist/ui/formatters/audit.js +14 -5
  136. package/dist/ui/formatters/cdp.d.ts +138 -0
  137. package/dist/ui/formatters/cdp.js +131 -0
  138. package/dist/ui/formatters/console/chronological.js +3 -1
  139. package/dist/ui/formatters/console/follow.d.ts +2 -1
  140. package/dist/ui/formatters/console/follow.js +2 -2
  141. package/dist/ui/formatters/console/json.d.ts +2 -2
  142. package/dist/ui/formatters/console/json.js +11 -5
  143. package/dist/ui/formatters/console/shared.d.ts +30 -0
  144. package/dist/ui/formatters/console/shared.js +16 -0
  145. package/dist/ui/formatters/console/summarize.d.ts +9 -2
  146. package/dist/ui/formatters/console/summarize.js +40 -9
  147. package/dist/ui/formatters/console.d.ts +2 -1
  148. package/dist/ui/formatters/console.js +7 -5
  149. package/dist/ui/formatters/details.js +3 -1
  150. package/dist/ui/formatters/dom.d.ts +1 -1
  151. package/dist/ui/formatters/dom.js +5 -6
  152. package/dist/ui/formatters/helpFormatters.js +1 -1
  153. package/dist/ui/formatters/inspect.js +9 -3
  154. package/dist/ui/formatters/installSkill.d.ts +9 -1
  155. package/dist/ui/formatters/installSkill.js +32 -6
  156. package/dist/ui/formatters/layout.js +2 -1
  157. package/dist/ui/formatters/networkList.d.ts +1 -1
  158. package/dist/ui/formatters/networkList.js +1 -2
  159. package/dist/ui/formatters/preview.d.ts +2 -0
  160. package/dist/ui/formatters/preview.js +17 -7
  161. package/dist/ui/formatters/sessions.d.ts +2 -2
  162. package/dist/ui/formatters/sessions.js +9 -2
  163. package/dist/ui/logging/logger.d.ts +1 -1
  164. package/dist/ui/messages/commands.d.ts +124 -4
  165. package/dist/ui/messages/commands.js +162 -7
  166. package/dist/ui/messages/consoleMessages.d.ts +24 -0
  167. package/dist/ui/messages/consoleMessages.js +32 -0
  168. package/dist/ui/messages/preview.d.ts +6 -0
  169. package/dist/ui/messages/preview.js +9 -1
  170. package/dist/ui/messages/session.d.ts +13 -2
  171. package/dist/ui/messages/session.js +22 -3
  172. package/dist/utils/directories.d.ts +34 -0
  173. package/dist/utils/directories.js +88 -0
  174. package/dist/utils/display.d.ts +16 -0
  175. package/dist/utils/display.js +42 -0
  176. package/dist/utils/exitCodes.d.ts +1 -0
  177. package/dist/utils/exitCodes.js +6 -0
  178. package/dist/utils/process.d.ts +12 -0
  179. package/dist/utils/process.js +25 -0
  180. package/package.json +1 -1
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * `bdg help <command...>` topics and Commander usage-error hints.
3
3
  */
4
+ import { commandPath } from './helpJson.js';
4
5
  import { CommandError } from '../errors/index.js';
5
- import { unknownHelpTopicError } from '../errors/messages.js';
6
+ import { missingSubcommandMessage, unknownHelpTopicError, usageHelpSuggestion, } from '../errors/messages.js';
6
7
  import { EXIT_CODES } from '../utils/exitCodes.js';
7
8
  import { findSimilar } from '../utils/suggestions.js';
8
9
  /** Commander's typo hint on its own line, e.g. "(Did you mean query?)" */
@@ -55,4 +56,61 @@ export function splitCommanderHint(text) {
55
56
  return { message };
56
57
  return { message: message.slice(0, hint.index).trim(), suggestion: `Did you mean: ${hint[1]}?` };
57
58
  }
59
+ /** The option named in Commander's "unknown option '--x'" message, without an `=value` */
60
+ const UNKNOWN_OPTION = /unknown option '([^'=]+)/;
61
+ /**
62
+ * The long option of a command closest to a mistyped one, hidden global
63
+ * options (`--session`, `--quiet`) included. Up to one edit per three letters
64
+ * of the name counts as a typo, so `--frob` does not suggest `--json`.
65
+ *
66
+ * @param flag - Option as typed, e.g. "--sesion"
67
+ * @param command - Command it was given to
68
+ * @returns Closest long option, if any is similar
69
+ */
70
+ function closestOption(flag, command) {
71
+ const longs = command.options.flatMap((option) => (option.long ? [option.long] : []));
72
+ const maxDistance = Math.ceil(flag.replace(/^-+/, '').length / 3);
73
+ return findSimilar(flag, longs, { maxDistance })[0];
74
+ }
75
+ /**
76
+ * The option as typed, for one Commander split: it reads `-josn` as `-j`
77
+ * (`--json`) followed by `-osn`, and reports `-osn` as unknown.
78
+ *
79
+ * @param flag - Option Commander reported, e.g. "-osn"
80
+ * @param argv - Process arguments
81
+ * @returns The argument it came from, e.g. "-josn", else the option itself
82
+ */
83
+ function typedOption(flag, argv) {
84
+ if (flag.startsWith('--'))
85
+ return flag;
86
+ const rest = flag.slice(1);
87
+ return (argv.find((arg) => /^-[^-]/.test(arg) && arg.length > flag.length && arg.endsWith(rest)) ?? flag);
88
+ }
89
+ /**
90
+ * Message and suggestion for a Commander usage error: a did-you-mean for an
91
+ * unknown option or command, otherwise a pointer to the command's `--help`.
92
+ * A word typed after a single dash (`-josn`) is matched against the long
93
+ * options.
94
+ *
95
+ * @param error - Commander error (not a help or version display)
96
+ * @param command - Command the error came from (see resolveCommand)
97
+ * @param argv - Process arguments (to name an option as typed)
98
+ * @returns Message and suggestion
99
+ */
100
+ export function usageErrorDetails(error, command, argv = process.argv) {
101
+ const help = usageHelpSuggestion(commandPath(command));
102
+ if (error.code === 'commander.help') {
103
+ return { message: missingSubcommandMessage(), suggestion: help };
104
+ }
105
+ const { message, suggestion } = splitCommanderHint(error.message);
106
+ const flag = error.code === 'commander.unknownOption' && UNKNOWN_OPTION.exec(message)?.[1];
107
+ if (!flag)
108
+ return { message, suggestion: suggestion ?? help };
109
+ const typed = typedOption(flag, argv);
110
+ const closest = closestOption(/^-[^-]../.test(typed) ? `-${typed}` : typed, command);
111
+ return {
112
+ message: message.replace(`'${flag}'`, `'${typed}'`),
113
+ suggestion: closest ? `Did you mean: ${closest}?` : help,
114
+ };
115
+ }
58
116
  //# sourceMappingURL=helpTopic.js.map
@@ -1,16 +1,26 @@
1
1
  import type { Command } from 'commander';
2
+ import { CommandError } from '../errors/index.js';
2
3
  import type { InstalledSkill, SkillTarget } from '../types.js';
4
+ /** What installing the skill did, and why it failed for a target, if it did */
5
+ export interface SkillInstallResult {
6
+ /** Targets the skill was installed for (or left unchanged) */
7
+ skills: InstalledSkill[];
8
+ /** Error (82) for the targets that could not be written */
9
+ failure?: CommandError;
10
+ }
3
11
  /**
4
- * Copy the bdg skill into each target's skill directory, overwriting an
5
- * older copy.
12
+ * Copy the bdg skill into each target's skill directory. A copy that differs
13
+ * (an older version, or one the user edited) is kept as `SKILL.md.bak`
14
+ * before it is overwritten. A target that cannot be written does not stop
15
+ * the others.
6
16
  *
7
17
  * @param targets - Agents to install for
8
18
  * @param home - Home directory the skill roots are relative to
9
19
  * @param source - SKILL.md to copy
10
- * @returns One entry per target, in the given order
11
- * @throws CommandError when the source is missing (83) or a write fails (82)
20
+ * @returns The targets written, in the given order, and the failure if any
21
+ * @throws CommandError when the source is missing (83)
12
22
  */
13
- export declare function installSkill(targets: SkillTarget[], home?: string, source?: string): InstalledSkill[];
23
+ export declare function installSkill(targets: SkillTarget[], home?: string, source?: string): SkillInstallResult;
14
24
  /**
15
25
  * Register the install-skill command.
16
26
  *
@@ -1,14 +1,17 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
1
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'fs';
2
2
  import { homedir } from 'os';
3
3
  import { dirname, join } from 'path';
4
4
  import { runCommand } from './shared/CommandRunner.js';
5
5
  import { jsonOption } from './shared/commonOptions.js';
6
6
  import { CommandError } from '../errors/index.js';
7
7
  import { skillSourceMissingError, skillWriteFailedError } from '../errors/messages.js';
8
- import { formatInstalledSkills } from '../ui/formatters/installSkill.js';
8
+ import { formatInstalledSkills, formatSkillTargets } from '../ui/formatters/installSkill.js';
9
+ import { createLogger } from '../ui/logging/index.js';
9
10
  import { getErrorMessage } from '../utils/errors.js';
10
11
  import { EXIT_CODES } from '../utils/exitCodes.js';
12
+ import { safeRemoveFile } from '../utils/file.js';
11
13
  import { PACKAGE_ROOT } from '../utils/packageRoot.js';
14
+ const log = createLogger('bdg');
12
15
  /** The skill shipped with the package (also used by agents working in this repo). */
13
16
  const SKILL_SOURCE_PATH = join(PACKAGE_ROOT, '.claude', 'skills', 'bdg', 'SKILL.md');
14
17
  /** Skill roots, relative to the home directory, of the agents the skill is installed for. */
@@ -17,14 +20,16 @@ const SKILL_ROOTS = {
17
20
  agents: join('.agents', 'skills'),
18
21
  };
19
22
  /**
20
- * Copy the bdg skill into each target's skill directory, overwriting an
21
- * older copy.
23
+ * Copy the bdg skill into each target's skill directory. A copy that differs
24
+ * (an older version, or one the user edited) is kept as `SKILL.md.bak`
25
+ * before it is overwritten. A target that cannot be written does not stop
26
+ * the others.
22
27
  *
23
28
  * @param targets - Agents to install for
24
29
  * @param home - Home directory the skill roots are relative to
25
30
  * @param source - SKILL.md to copy
26
- * @returns One entry per target, in the given order
27
- * @throws CommandError when the source is missing (83) or a write fails (82)
31
+ * @returns The targets written, in the given order, and the failure if any
32
+ * @throws CommandError when the source is missing (83)
28
33
  */
29
34
  export function installSkill(targets, home = homedir(), source = SKILL_SOURCE_PATH) {
30
35
  if (!existsSync(source)) {
@@ -32,31 +37,76 @@ export function installSkill(targets, home = homedir(), source = SKILL_SOURCE_PA
32
37
  throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.RESOURCE_NOT_FOUND);
33
38
  }
34
39
  const content = readFileSync(source, 'utf-8');
35
- return targets.map((target) => writeSkill(target, join(home, SKILL_ROOTS[target], 'bdg', 'SKILL.md'), content));
40
+ const skills = [];
41
+ const failures = [];
42
+ for (const target of targets) {
43
+ const result = writeSkill(target, join(home, SKILL_ROOTS[target], 'bdg', 'SKILL.md'), content);
44
+ if ('error' in result)
45
+ failures.push(result);
46
+ else
47
+ skills.push(result);
48
+ }
49
+ if (failures.length === 0)
50
+ return { skills };
51
+ return { skills, failure: skillFailureError(failures, targets) };
52
+ }
53
+ /**
54
+ * The error for targets the skill could not be written for. When only one of
55
+ * several targets failed, it suggests installing for the other only. Each
56
+ * distinct suggestion is kept, so every failure cause gets its fix.
57
+ *
58
+ * @param failures - Targets that failed (at least one)
59
+ * @param targets - Targets picked
60
+ * @returns Command error (82)
61
+ */
62
+ function skillFailureError(failures, targets) {
63
+ const other = failures.length === 1 ? targets.find((t) => t !== failures[0]?.target) : undefined;
64
+ const errors = failures.map(({ path, error }) => {
65
+ const code = error.code;
66
+ return skillWriteFailedError(path, getErrorMessage(error), code === 'ENOTDIR' || code === 'EEXIST', other && `--${other}`);
67
+ });
68
+ return new CommandError(errors.map((err) => err.message).join('\n'), { suggestion: [...new Set(errors.map((err) => err.suggestion))].join('\n') }, EXIT_CODES.PERMISSION_DENIED);
36
69
  }
37
70
  /**
38
- * Write the skill to one path unless it already holds the same content.
71
+ * Write the skill to one path unless it already holds the same content; a
72
+ * different copy is first kept next to it as `SKILL.md.bak` (replacing an
73
+ * earlier backup), so edits to it are not lost. The new text is written to a
74
+ * temporary file first, so a failed write leaves both the copy and the
75
+ * backup as they were; the backup gets the default file mode, whatever the
76
+ * copy's was.
39
77
  *
40
78
  * @param target - Agent the path belongs to
41
79
  * @param path - Destination SKILL.md
42
80
  * @param content - Skill text
43
- * @returns What happened to the file
44
- * @throws CommandError (82) when the directory or file cannot be written
81
+ * @returns What happened to the file, with the backup path when one was made,
82
+ * or the file that could not be written and why
45
83
  */
46
84
  function writeSkill(target, path, content) {
47
85
  const existing = existsSync(path) ? readFileSync(path, 'utf-8') : undefined;
48
86
  if (existing === content) {
49
87
  return { target, path, status: 'unchanged' };
50
88
  }
89
+ const backup = existing === undefined ? undefined : `${path}.bak`;
90
+ const temporary = `${path}.tmp`;
91
+ let writing = path;
51
92
  try {
52
93
  mkdirSync(dirname(path), { recursive: true });
53
- writeFileSync(path, content);
94
+ writeFileSync(temporary, content);
95
+ if (backup !== undefined) {
96
+ writing = backup;
97
+ rmSync(backup, { force: true });
98
+ writeFileSync(backup, existing ?? '');
99
+ }
100
+ writing = path;
101
+ renameSync(temporary, path);
54
102
  }
55
- catch (caught) {
56
- const err = skillWriteFailedError(path, getErrorMessage(caught));
57
- throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.PERMISSION_DENIED);
103
+ catch (error) {
104
+ safeRemoveFile(temporary, 'temporary skill copy', log);
105
+ return { target, path: writing, error };
58
106
  }
59
- return { target, path, status: existing === undefined ? 'installed' : 'updated' };
107
+ if (backup === undefined)
108
+ return { target, path, status: 'installed' };
109
+ return { target, path, status: 'updated', backup };
60
110
  }
61
111
  /**
62
112
  * Targets picked by the flags; no flag means every agent.
@@ -68,6 +118,26 @@ function selectedTargets(options) {
68
118
  const picked = Object.keys(SKILL_ROOTS).filter((target) => options[target]);
69
119
  return picked.length > 0 ? picked : Object.keys(SKILL_ROOTS);
70
120
  }
121
+ /**
122
+ * Install the skill for the picked targets. When a target fails, the error
123
+ * still lists the targets that were written (and their backups): JSON
124
+ * `skills`, or the same lines as a successful install.
125
+ *
126
+ * @param options - Parsed command options
127
+ * @returns Command result
128
+ */
129
+ function installSkillResult(options) {
130
+ const { skills, failure } = installSkill(selectedTargets(options));
131
+ if (!failure)
132
+ return { success: true, data: { skills } };
133
+ const written = skills.length === 0 ? {} : options.json ? { skills } : { written: formatSkillTargets(skills) };
134
+ return {
135
+ success: false,
136
+ error: failure.message,
137
+ exitCode: failure.exitCode,
138
+ errorContext: { ...written, ...failure.metadata },
139
+ };
140
+ }
71
141
  /**
72
142
  * Register the install-skill command.
73
143
  *
@@ -81,7 +151,7 @@ export function registerInstallSkillCommand(program) {
81
151
  .option('--agents', 'Only ~/.agents/skills (Codex, Gemini CLI and other agents)')
82
152
  .addOption(jsonOption())
83
153
  .action(async (options) => {
84
- await runCommand((opts) => Promise.resolve({ success: true, data: { skills: installSkill(selectedTargets(opts)) } }), options, formatInstalledSkills);
154
+ await runCommand((opts) => Promise.resolve(installSkillResult(opts)), options, formatInstalledSkills);
85
155
  });
86
156
  }
87
157
  //# sourceMappingURL=installSkill.js.map
@@ -6,7 +6,7 @@ import { runCommand } from '../shared/CommandRunner.js';
6
6
  import { jsonOption } from '../shared/commonOptions.js';
7
7
  import { noteFollowConnected } from '../shared/daemonErrorHandler.js';
8
8
  import { fetchNetworkRequests, createErrorResult } from '../shared/dataFetcher.js';
9
- import { followFetchFailure, setupFollowMode, } from '../shared/followMode.js';
9
+ import { followFetchFailure, newPageCrashes, setupFollowMode, } from '../shared/followMode.js';
10
10
  import { handleValidationError } from '../shared/handleValidationError.js';
11
11
  import { positiveIntRule, resourceTypeRule } from '../shared/validation.js';
12
12
  import { applyFilters, getFilterHelpText, validateFilterString } from '../../telemetry/filterDsl.js';
@@ -14,6 +14,7 @@ import { resolvePreset, FILTER_PRESETS } from '../../telemetry/filterPresets.js'
14
14
  import { filterByResourceType } from '../../telemetry/filters.js';
15
15
  import { buildSuccessResponse } from '../../ui/OutputBuilder.js';
16
16
  import { formatNetworkFollowRows, formatNetworkList, pageStartOf, } from '../../ui/formatters/networkList.js';
17
+ import { pageCrashedNote, withPageCrashedNote } from '../../ui/messages/commands.js';
17
18
  import { followingNetworkMessage, stoppedFollowingNetworkMessage, } from '../../ui/messages/networkMessages.js';
18
19
  import { EXIT_CODES } from '../../utils/exitCodes.js';
19
20
  import { validateFilterOption } from './shared.js';
@@ -116,7 +117,8 @@ function buildFormatOptions(options, result, lastLimit) {
116
117
  /**
117
118
  * Stream network requests: the last `lastN` finished ones at start, then
118
119
  * each request once, when it has finished loading or failed (a request whose
119
- * headers arrived but whose body is still loading waits), like `tail -f`.
120
+ * headers arrived but whose body is still loading waits), like `tail -f`,
121
+ * with a warning (JSON `pageCrashedAt`) once when the page crashes.
120
122
  *
121
123
  * @param options - Command options
122
124
  * @param resourceTypes - Validated resource types
@@ -124,6 +126,7 @@ function buildFormatOptions(options, result, lastLimit) {
124
126
  */
125
127
  async function runFollowMode(options, resourceTypes, lastN) {
126
128
  const shown = new Set();
129
+ const newCrash = newPageCrashes();
127
130
  let started = false;
128
131
  const showNetwork = async () => {
129
132
  const result = await fetchNetworkRequests(filtersNeedHeaders(options));
@@ -131,25 +134,28 @@ async function runFollowMode(options, resourceTypes, lastN) {
131
134
  return followFetchFailure(result, { json: options.json, retryIntervalMs: FOLLOW_INTERVAL });
132
135
  }
133
136
  noteFollowConnected();
134
- const finished = filterRequests(result.data, options, resourceTypes).filter((request) => request.duration !== undefined && !shown.has(request.requestId));
135
- const present = new Set(result.data.map((request) => request.requestId));
137
+ const { requests } = result.data;
138
+ const crashedAt = newCrash(result.data.pageCrashedAt);
139
+ const finished = filterRequests(requests, options, resourceTypes).filter((request) => request.duration !== undefined && !shown.has(request.requestId));
140
+ const present = new Set(requests.map((request) => request.requestId));
136
141
  for (const id of shown)
137
142
  if (!present.has(id))
138
143
  shown.delete(id);
139
144
  finished.forEach((request) => shown.add(request.requestId));
140
145
  const fresh = started || lastN === 0 ? finished : finished.slice(-lastN);
141
146
  if (options.json) {
142
- if (!started || fresh.length > 0) {
147
+ if (!started || fresh.length > 0 || crashedAt !== undefined) {
143
148
  const data = {
144
149
  requests: fresh,
145
- totalCount: result.data.length,
150
+ totalCount: requests.length,
146
151
  filteredCount: fresh.length,
152
+ ...(crashedAt !== undefined && { pageCrashedAt: crashedAt }),
147
153
  };
148
- console.log(JSON.stringify(buildSuccessResponse(data), null, 2));
154
+ console.log(JSON.stringify(buildSuccessResponse(data)));
149
155
  }
150
156
  }
151
157
  else {
152
- const pageStart = pageStartOf(result.data);
158
+ const pageStart = pageStartOf(requests);
153
159
  const text = formatNetworkFollowRows(fresh, {
154
160
  header: !started,
155
161
  verbose: options.verbose ?? false,
@@ -157,6 +163,8 @@ async function runFollowMode(options, resourceTypes, lastN) {
157
163
  });
158
164
  if (text)
159
165
  console.log(text);
166
+ if (crashedAt !== undefined)
167
+ console.log(pageCrashedNote(crashedAt));
160
168
  }
161
169
  started = true;
162
170
  return undefined;
@@ -219,18 +227,20 @@ export function registerListCommand(networkCmd) {
219
227
  }
220
228
  return createErrorResult(result.error, result.exitCode, result.suggestion);
221
229
  }
222
- const filtered = filterRequests(result.data, options, resourceTypes);
223
- const pageStart = pageStartOf(result.data);
230
+ const { requests, pageCrashedAt } = result.data;
231
+ const filtered = filterRequests(requests, options, resourceTypes);
232
+ const pageStart = pageStartOf(requests);
224
233
  return {
225
234
  success: true,
226
235
  data: {
227
236
  requests: lastN === 0 ? filtered : filtered.slice(-lastN),
228
- totalCount: result.data.length,
237
+ totalCount: requests.length,
229
238
  filteredCount: filtered.length,
230
239
  ...(pageStart && { pageStart }),
240
+ ...(pageCrashedAt !== undefined && { pageCrashedAt }),
231
241
  },
232
242
  };
233
- }, options, (data) => formatNetworkList(data.requests, buildFormatOptions(options, data, lastN)));
243
+ }, options, (data) => withPageCrashedNote(formatNetworkList(data.requests, buildFormatOptions(options, data, lastN)), data.pageCrashedAt));
234
244
  });
235
245
  }
236
246
  //# sourceMappingURL=list.js.map
@@ -17,6 +17,8 @@ const STILL_CHANGING_BEHAVIOR = 'When the page was still changing as the action
17
17
  const SHOWN_BEHAVIOR = 'Elements the action showed are listed (Shown: <element> "<text>"; JSON shown [{ text, element }], at most 3, outermost first): elements with visible text added inside the target\'s form, search box, dialog or combobox (else its grandparent, or its parent when that is the body), and popups and messages added anywhere (tooltip, menu, listbox, dialog, alert, status roles, popover, aria-live, message-like classes); widgets elsewhere on the page and re-rendered elements whose text was there before do not count';
18
18
  /** What `--no-wait` does to a DOM action's triggered requests */
19
19
  const NO_WAIT_TRIGGERED_REQUESTS = 'Returns immediately without waiting for network; triggeredRequests lists only requests bdg saw start before returning (often none yet; check bdg network list later)';
20
+ /** How every follow mode (peek, console, network list) runs and ends */
21
+ const FOLLOW_BEHAVIOR = 'Stops with exit 83 when the session it follows ends, 130 on Ctrl-C, 143 on SIGTERM; with --json prints one compact object per line (NDJSON). Other failures (a busy page, a timeout) are retried: reported once in text, as one error line per refresh in JSON';
20
22
  /**
21
23
  * Behavioral metadata registry.
22
24
  *
@@ -73,12 +75,12 @@ const OPTION_BEHAVIORS = {
73
75
  'get:--index': {
74
76
  default: 'Returns the first matching element (body without a selector)',
75
77
  whenEnabled: 'Returns that match of the selector (0-based), in semantic and --raw output; --nth is an alias',
76
- automaticBehavior: 'Out of range exits 81; with a numeric index argument (a cached query index) it exits 81',
78
+ 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)',
77
79
  },
78
80
  'query:--limit': {
79
- default: 'dom a11y query lists the first 50 matches and says how many more there are; --json returns all of them',
81
+ 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)',
80
82
  whenEnabled: 'Lists that many matches (0 = all), in human and JSON output; count is always the total, JSON omitted the rest',
81
- automaticBehavior: 'All matches are cached for index-based access (bdg dom click 55 works even when 50 are listed); 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',
83
+ 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',
82
84
  tokenImpact: 'About one line per match; a page can have hundreds of links',
83
85
  },
84
86
  'eval:--frame': {
@@ -97,16 +99,16 @@ const OPTION_BEHAVIORS = {
97
99
  automaticBehavior: 'Page navigations create new "navigation contexts" - default filters to latest context',
98
100
  },
99
101
  'console:-l': {
100
- default: 'Smart summary with errors deduplicated and warnings grouped',
102
+ 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)',
101
103
  whenEnabled: 'Lists all messages chronologically without deduplication',
102
104
  },
103
105
  'console:--list': {
104
- default: 'Smart summary with errors deduplicated and warnings grouped',
106
+ 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)',
105
107
  whenEnabled: 'Lists all messages chronologically without deduplication',
106
108
  },
107
109
  'console:--last': {
108
110
  default: 'Smart summary (without --list); a list shows the last 100 messages',
109
- whenEnabled: 'Lists the last N messages (0 = all) chronologically, also without --list; JSON gets messages',
111
+ whenEnabled: 'Lists the last N messages (0 = all) chronologically, also without --list; JSON gets messages and N distinct errors and warnings (0 = all; default 50)',
110
112
  automaticBehavior: 'The [n] shown are positions in the session message list (what bdg details console <n> takes); when the page or level filter left messages out between the listed ones, a note says how many and why',
111
113
  },
112
114
  'console:--level': {
@@ -126,7 +128,7 @@ const OPTION_BEHAVIORS = {
126
128
  'click:--no-wait': {
127
129
  default: 'Waits for network stability after click (150ms idle, up to 2s)',
128
130
  whenDisabled: NO_WAIT_TRIGGERED_REQUESTS,
129
- automaticBehavior: `Network wait helps ensure AJAX requests triggered by click complete. ${TRIGGERED_REQUESTS_BEHAVIOR}. The click itself uses real mouse events in the visible part of the element (method "mouse"); if the element is covered or has no size it falls back to DOM events (method "dom", with a warning; --strict refuses instead). Results the page shows later (timers, spinners, slow renders) are not waited for but reported as pending work: use bdg dom wait <selector> --visible. ${ACTION_EFFECTS_BEHAVIOR}. ${STILL_CHANGING_BEHAVIOR}. A click with no DOM change, no request and no navigation (checked again 300 ms later, which adds 300 ms plus at most 250 ms for the read) is reported as ⚠ Element Clicked (no visible effect observed: no DOM change, requests or navigation within 300 ms) and effect: "none" in JSON (exit code stays 0); not claimed with --no-wait, for hover or right-click, after a copy or cut, or when the click hit a form control, label, media, iframe, popover button, a mailto:/tel:/javascript: or other non-http link, a link to another window or a custom element with a closed shadow root, or moved focus to an element that is not a button or link. Effects outside the DOM (CSS :hover/:focus-within styles, canvas, clipboard without a copy event) are not seen`,
131
+ automaticBehavior: `Network wait helps ensure AJAX requests triggered by click complete. ${TRIGGERED_REQUESTS_BEHAVIOR}. The click itself uses real mouse events in the visible part of the element (method "mouse"); if the element is covered or has no size it falls back to DOM events (method "dom", with a warning; --strict refuses instead). Results the page shows later (timers, spinners, slow renders) are not waited for but reported as pending work: use bdg dom wait <selector> --visible. ${ACTION_EFFECTS_BEHAVIOR}. ${STILL_CHANGING_BEHAVIOR}. A click with no DOM change, no request, no navigation and no console message (checked again 300 ms later, which adds 300 ms plus at most 250 ms for the read) is reported as ⚠ Element Clicked (no visible effect observed: no DOM change, requests or navigation within 300 ms) and effect: "none" in JSON (exit code stays 0); not claimed with --no-wait, for hover or right-click, after a copy or cut, or when the click hit a form control, label, media, iframe, popover button, a mailto:/tel:/javascript: or other non-http link, a link to another window or a custom element with a closed shadow root, or moved focus to an element that is not a button or link. Effects outside the DOM (CSS :hover/:focus-within styles, canvas, clipboard without a copy event) are not seen`,
130
132
  tokenImpact: 'A click that navigates lists the whole page load in JSON triggeredRequests',
131
133
  },
132
134
  'click:--double': {
@@ -145,7 +147,7 @@ const OPTION_BEHAVIORS = {
145
147
  'hover:--no-wait': {
146
148
  default: 'Waits for network stability after moving the mouse (menus may load content)',
147
149
  whenDisabled: NO_WAIT_TRIGGERED_REQUESTS,
148
- automaticBehavior: `The mouse stays over the element afterwards, so hover menus stay open until the next mouse action. ${TRIGGERED_REQUESTS_BEHAVIOR}. ${ACTION_EFFECTS_BEHAVIOR}. ${SHOWN_BEHAVIOR}; for a hover also elements around it (its parent's subtree) and tooltips, menus, listboxes, dialogs and popovers anywhere that were hidden before, so captions shown by CSS :hover count (hidden elements noted by identity right before the mouse moves: up to 1500, within 8 ms). A hover never claims "no visible effect" and does not check whether the page was still changing`,
150
+ automaticBehavior: `The element is scrolled into view first; when the page moved, Scrolled (JSON scrolledBy) says how far. The mouse stays over the element afterwards, so hover menus stay open until the next mouse action. ${TRIGGERED_REQUESTS_BEHAVIOR}. ${ACTION_EFFECTS_BEHAVIOR}. ${SHOWN_BEHAVIOR}; for a hover also elements around it (its parent's subtree) and tooltips, menus, listboxes, dialogs and popovers anywhere that were hidden before, so captions shown by CSS :hover count (hidden elements noted by identity right before the mouse moves: up to 1500, within 8 ms). A hover never claims "no visible effect" and does not check whether the page was still changing`,
149
151
  },
150
152
  'hover:--strict': {
151
153
  default: 'A covered, hidden or zero-size element gets synthetic mouseover/mouseenter events (method "dom", with a warning)',
@@ -215,7 +217,7 @@ const OPTION_BEHAVIORS = {
215
217
  'audit:--level': {
216
218
  default: 'Text must reach WCAG AA: 4.5, or 3 for large text (24px, or 18.66px bold); every text-drawing element is checked, composited like dom inspect',
217
219
  whenEnabled: '--level AAA asks 7, or 4.5 for large text',
218
- automaticBehavior: 'One walk over the rendered elements (open shadow roots included, at most 20000; capped says when it stopped). Findings are sorted weakest first; --limit (default 20) lists that many per check and the rest are counted. Overflow leaves out content inside horizontal scrollers and visually-hidden 1px text; identical findings are grouped (×N)',
220
+ automaticBehavior: 'One walk over the rendered elements (open shadow roots included, at most 20000; capped says when it stopped). Findings are sorted weakest first; --limit (default 20) lists that many per check and the rest are counted. Overflow leaves out content inside horizontal scrollers and visually-hidden 1px text; identical findings are grouped (×N). Contrast is approximate (approximate: …) when something is painted behind or on top of text in view (hit-tested), or for text out of view whose ancestors paint nothing below body (only its ancestors were checked). Canvas animations cannot be listed; visible canvas elements are counted (canvases)',
219
221
  tokenImpact: 'About one line per finding; --limit bounds it',
220
222
  },
221
223
  'layout:--index': {
@@ -228,12 +230,12 @@ const OPTION_BEHAVIORS = {
228
230
  default: 'Inspects the first rendered match (the first when none is rendered) and notes how many matched; a numeric argument inspects that cached element (from dom query, dom form or dom a11y query)',
229
231
  whenEnabled: 'Inspects the nth match (0-based); out of range exits 81',
230
232
  automaticBehavior: 'Answers "what does it look like" without a screenshot, grouped like Figma Dev Mode: header (element, text, size and page position, [flex]/[grid], [not rendered]/[hidden]/[offscreen]/[covered by …], prefers-color-scheme), box (margin, padding, border widths, box-sizing, overflow, scroll size), layout (display, position, flex/grid container and item settings), parent (its display and layout, distances to its content edges, gaps to the neighbouring siblings), text (first font family → the font Chrome rendered, (webfont) or local; weight size/line-height; color; WCAG contrast against the composited background; only for elements with text), fill, border (sides, radius, outline), fx (shadow, transform, filter, opacity, blend), state (cursor, pointer-events, user-select, appearance), pseudo (::before/::after with content, ::placeholder) and a child tree (depth 2, 20 rows, identical siblings grouped). Values that change nothing (0, none, transparent, normal) are left out; colors are hex (lab/oklch from Tailwind converted), lengths px without the unit, rounded to 0.1. Secrets are never shown. JSON uses Figma-aligned names (rect, box, layout.sizing hug/fill/fixed, text, fills, strokes, radius, effects, children). Also by default: hints, the element\'s own declarations that have no effect (justify-content on a block, width on an inline element, top on a static one, var() of an unset custom property) with the reason, the fix and the rule\'s file:line',
231
- tokenImpact: 'About 60–80 tokens for the styles and 30–70 more for the child tree (--tree 0 drops it), against about 1,500 for a screenshot or 3,000+ for raw computed styles',
233
+ tokenImpact: 'About 80–130 tokens for a button: 80–100 for the styles, up to 50 per hint (--no-hints drops them), and 30–70 more for a child tree (--tree 0 drops it), against about 1,500 for a screenshot or 3,000+ for raw computed styles',
232
234
  },
233
235
  'inspect:--all': {
234
236
  default: 'Shows the curated groups (the properties that define the look)',
235
237
  whenEnabled: 'Lists every computed property that differs from the default of the same element type, longhands collapsed into shorthands, noise (logical duplicates, currentColor echoes, custom properties) dropped',
236
- tokenImpact: 'About 80 tokens instead of 60–80',
238
+ tokenImpact: 'About as many tokens as the curated groups (about 90 for a button)',
237
239
  },
238
240
  'inspect:--props': {
239
241
  default: 'Shows the curated groups',
@@ -297,15 +299,35 @@ const OPTION_BEHAVIORS = {
297
299
  'peek:-f': {
298
300
  default: 'Shows snapshot of current data',
299
301
  whenEnabled: 'Continuous monitoring (like tail -f): refreshes every second, or every --interval ms (100-60000). Replaces the deprecated bdg tail',
302
+ automaticBehavior: FOLLOW_BEHAVIOR,
300
303
  },
301
304
  'peek:--follow': {
302
305
  default: 'Shows snapshot of current data',
303
306
  whenEnabled: 'Continuous monitoring (like tail -f): refreshes every second, or every --interval ms (100-60000). Replaces the deprecated bdg tail',
307
+ automaticBehavior: FOLLOW_BEHAVIOR,
308
+ },
309
+ 'console:-f': {
310
+ default: 'Prints the messages logged so far and exits',
311
+ whenEnabled: 'Streams new messages as they come (the last --last at start)',
312
+ automaticBehavior: FOLLOW_BEHAVIOR,
313
+ },
314
+ 'list:-f': {
315
+ default: 'Lists the requests captured so far and exits',
316
+ whenEnabled: 'Streams requests as they finish',
317
+ automaticBehavior: FOLLOW_BEHAVIOR,
304
318
  },
305
319
  'peek:-v': {
306
320
  default: 'Compact output (truncated URLs, no resource types)',
307
321
  whenEnabled: 'Verbose output with full URLs and resource types',
308
322
  },
323
+ 'start:--headless': {
324
+ 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',
325
+ whenEnabled: 'Chrome runs without a window (pass it when running unattended on a Mac)',
326
+ },
327
+ 'start:--no-headless': {
328
+ 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',
329
+ whenEnabled: 'Chrome shows its window even without a detected display (it fails without one)',
330
+ },
309
331
  'start:--all': {
310
332
  default: 'Tracking/analytics requests and console noise are filtered; bodies of binary responses (images, fonts) are not captured',
311
333
  whenEnabled: 'Everything is captured, including binary response bodies (base64, flagged by responseBodyBase64, within --max-body-size)',
@@ -28,8 +28,11 @@ export declare function noteFollowConnected(): void;
28
28
  * Handle daemon connection errors with consistent formatting and behavior.
29
29
  *
30
30
  * Outside follow mode the command exits. In follow mode, a session that never
31
- * answered exits too (there is nothing to follow, exit 83); a session that
32
- * goes away is reported once and retried until a new one starts.
31
+ * answered exits too (there is nothing to follow, exit 83), and so does one
32
+ * that ends while followed (no session any more, exit 83), so a follower
33
+ * running in the background finds out. Other failures (a busy page, a
34
+ * timeout) are retried: reported once in text, and on every failed refresh
35
+ * in JSON, one object per line.
33
36
  *
34
37
  * @param error - Error message to display
35
38
  * @param options - Error handling options
@@ -4,7 +4,7 @@
4
4
  import { sessionUnavailableSuggestion } from '../../errors/messages.js';
5
5
  import { genericError } from '../../errors/messages.js';
6
6
  import { OutputBuilder } from '../../ui/OutputBuilder.js';
7
- import { connectionLostRetryMessage, connectionLostStopHintMessage, } from '../../ui/messages/preview.js';
7
+ import { connectionLostRetryMessage, connectionLostStopHintMessage, followedSessionEndedMessage, } from '../../ui/messages/preview.js';
8
8
  import { EXIT_CODES } from '../../utils/exitCodes.js';
9
9
  /** Follow-mode state: whether a session ever answered, and whether its loss was reported */
10
10
  const followState = { connected: false, lossReported: false };
@@ -20,8 +20,11 @@ export function noteFollowConnected() {
20
20
  * Handle daemon connection errors with consistent formatting and behavior.
21
21
  *
22
22
  * Outside follow mode the command exits. In follow mode, a session that never
23
- * answered exits too (there is nothing to follow, exit 83); a session that
24
- * goes away is reported once and retried until a new one starts.
23
+ * answered exits too (there is nothing to follow, exit 83), and so does one
24
+ * that ends while followed (no session any more, exit 83), so a follower
25
+ * running in the background finds out. Other failures (a busy page, a
26
+ * timeout) are retried: reported once in text, and on every failed refresh
27
+ * in JSON, one object per line.
25
28
  *
26
29
  * @param error - Error message to display
27
30
  * @param options - Error handling options
@@ -29,22 +32,30 @@ export function noteFollowConnected() {
29
32
  */
30
33
  export function handleDaemonConnectionError(error, options) {
31
34
  const { json = false, follow = false, retryIntervalMs = 1000, exitCode = EXIT_CODES.RESOURCE_NOT_FOUND, } = options;
32
- const exits = !follow || !followState.connected;
33
- if (exits || !followState.lossReported) {
35
+ const sessionGone = exitCode === EXIT_CODES.RESOURCE_NOT_FOUND;
36
+ const exits = !follow || !followState.connected || sessionGone;
37
+ const message = follow && followState.connected && sessionGone ? followedSessionEndedMessage() : error;
38
+ if (exits || json || !followState.lossReported) {
34
39
  if (json) {
35
40
  const suggestion = exits ? sessionUnavailableSuggestion(exitCode) : undefined;
36
- console.log(JSON.stringify(OutputBuilder.buildJsonError(error, { exitCode, ...(suggestion && { suggestion }) }), null, 2));
41
+ const envelope = OutputBuilder.buildJsonError(message, {
42
+ exitCode,
43
+ ...(suggestion && { suggestion }),
44
+ });
45
+ console.log(follow ? JSON.stringify(envelope) : JSON.stringify(envelope, null, 2));
37
46
  }
38
47
  else {
39
- console.error(genericError(error));
48
+ console.error(genericError(message));
40
49
  }
41
50
  }
42
51
  if (exits)
43
52
  return { shouldExit: true, exitCode };
44
53
  if (!followState.lossReported) {
45
54
  const retryMessage = retryIntervalMs >= 1000 ? `${retryIntervalMs / 1000}s` : `${retryIntervalMs}ms`;
46
- console.error(connectionLostRetryMessage(new Date().toISOString(), retryMessage));
47
- console.error(connectionLostStopHintMessage());
55
+ if (!json) {
56
+ console.error(connectionLostRetryMessage(new Date().toISOString(), retryMessage));
57
+ console.error(connectionLostStopHintMessage());
58
+ }
48
59
  followState.lossReported = true;
49
60
  }
50
61
  return { shouldExit: false };
@@ -47,18 +47,26 @@ export declare function fetchPreviewData(query?: PreviewQuery): Promise<FetchRes
47
47
  * Fetch all captured network requests from daemon.
48
48
  *
49
49
  * @param withHeaders - Include request/response headers (needed by header filters)
50
- * @returns Requests or a fetch error
50
+ * @returns Requests, and when the page crashed (while it is not loaded
51
+ * again), or a fetch error
51
52
  */
52
- export declare function fetchNetworkRequests(withHeaders?: boolean): Promise<FetchResult<NetworkRequest[]>>;
53
+ export declare function fetchNetworkRequests(withHeaders?: boolean): Promise<FetchResult<{
54
+ requests: NetworkRequest[];
55
+ pageCrashedAt: number | undefined;
56
+ }>>;
53
57
  /**
54
58
  * Fetch all console messages from daemon.
55
59
  *
56
- * @returns Messages (with their session-wide index) and the navigation id of
57
- * the page currently loaded
60
+ * @returns Messages (with their session-wide index), the navigation id of
61
+ * the page currently loaded, how many of the oldest messages the session
62
+ * dropped at its limit and when the page crashed (while it is not loaded
63
+ * again)
58
64
  */
59
65
  export declare function fetchConsoleMessages(): Promise<FetchResult<{
60
66
  messages: ConsoleMessage[];
61
67
  currentNavigationId: number | undefined;
68
+ dropped: number;
69
+ pageCrashedAt: number | undefined;
62
70
  }>>;
63
71
  interface ErrorResult {
64
72
  success: false;