brave-mcp 1.10.1 → 1.12.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 (391) hide show
  1. package/README.md +69 -1
  2. package/{build → out/Default/gen}/src/BrowserManager.js +71 -50
  3. package/out/Default/gen/src/LogpointManager.js +307 -0
  4. package/{build → out/Default/gen}/src/McpContext.js +487 -109
  5. package/{build → out/Default/gen}/src/McpPage.js +477 -80
  6. package/{build → out/Default/gen}/src/McpResponse.js +257 -100
  7. package/{build → out/Default/gen}/src/SlimMcpResponse.js +5 -1
  8. package/{build → out/Default/gen}/src/TextSnapshot.js +40 -20
  9. package/out/Default/gen/src/ToolHandler.js +492 -0
  10. package/{build → out/Default/gen}/src/bin/brave-devtools-mcp-main.js +6 -4
  11. package/{build → out/Default/gen}/src/bin/brave-devtools-mcp.js +1 -1
  12. package/{build → out/Default/gen}/src/bin/brave-devtools.js +19 -12
  13. package/{build → out/Default/gen}/src/collectors/PageCollector.js +28 -12
  14. package/{build → out/Default/gen}/src/collectors/ServiceWorkerCollector.js +2 -2
  15. package/out/Default/gen/src/collectors/constants.js +7 -0
  16. package/{build → out/Default/gen}/src/config/ConfigLocator.js +5 -6
  17. package/out/Default/gen/src/config/ConfigParser.js +162 -0
  18. package/out/Default/gen/src/config/ConfigWatcher.js +94 -0
  19. package/out/Default/gen/src/config/ServerConfig.js +251 -0
  20. package/{build → out/Default/gen}/src/config/browser-options.js +13 -4
  21. package/{build → out/Default/gen}/src/config/category-options.js +9 -3
  22. package/out/Default/gen/src/config/cli-commands.js +186 -0
  23. package/{build → out/Default/gen}/src/config/cli-options.js +469 -58
  24. package/{build → out/Default/gen}/src/config/mcp-options.js +96 -48
  25. package/{build → out/Default/gen}/src/config/puppeteer-options.js +12 -13
  26. package/{build → out/Default/gen}/src/config/tool-options.js +4 -0
  27. package/{build → out/Default/gen}/src/daemon/client.js +38 -26
  28. package/{build → out/Default/gen}/src/daemon/daemon.js +11 -7
  29. package/{build → out/Default/gen}/src/daemon/utils.js +45 -2
  30. package/out/Default/gen/src/devtools/DevToolsCommentBridge.js +151 -0
  31. package/{build → out/Default/gen}/src/devtools/DevtoolsUtils.js +96 -22
  32. package/{build → out/Default/gen}/src/devtools/McpHostBindingAdapter.js +0 -1
  33. package/{build → out/Default/gen}/src/formatters/ConsoleFormatter.js +29 -2
  34. package/{build → out/Default/gen}/src/formatters/CssFormatter.js +3 -2
  35. package/{build → out/Default/gen}/src/formatters/HeapSnapshotFormatter.js +65 -64
  36. package/{build → out/Default/gen}/src/formatters/IssueFormatter.js +4 -3
  37. package/out/Default/gen/src/formatters/LogpointFormatter.js +62 -0
  38. package/{build → out/Default/gen}/src/formatters/NetworkFormatter.js +78 -24
  39. package/out/Default/gen/src/formatters/RemoteObjectFormatter.js +231 -0
  40. package/{build → out/Default/gen}/src/formatters/SnapshotFormatter.js +13 -3
  41. package/out/Default/gen/src/formatters/WebAuthnFormatter.js +56 -0
  42. package/out/Default/gen/src/index.js +434 -0
  43. package/{build → out/Default/gen}/src/processors/ChunkedTraceParser.js +11 -11
  44. package/out/Default/gen/src/processors/HeapSnapshotManager.js +569 -0
  45. package/{build → out/Default/gen}/src/processors/PerformanceTrace.js +9 -9
  46. package/{build → out/Default/gen}/src/telemetry/ClearcutLogger.js +1 -1
  47. package/{build → out/Default/gen}/src/telemetry/transformation.js +8 -12
  48. package/{build → out/Default/gen}/src/telemetry/watchdog/main.js +1 -1
  49. package/{build → out/Default/gen}/src/third_party/THIRD_PARTY_NOTICES +753 -717
  50. package/out/Default/gen/src/third_party/bundled-packages.json +11 -0
  51. package/out/Default/gen/src/third_party/cli.js +9239 -0
  52. package/out/Default/gen/src/third_party/devtools-formatter-worker.js +17825 -0
  53. package/out/Default/gen/src/third_party/devtools-heap-snapshot-worker.js +12575 -0
  54. package/{build → out/Default/gen}/src/third_party/index.js +67772 -56346
  55. package/out/Default/gen/src/third_party/issue-descriptions/genericGeolocationPromptWithoutUserGesture.md +5 -0
  56. package/out/Default/gen/src/third_party/lighthouse-devtools-mcp-bundle.js +60395 -0
  57. package/out/Default/gen/src/tools/ToolDefinition.js +180 -0
  58. package/{build → out/Default/gen}/src/tools/categories.js +2 -0
  59. package/{build → out/Default/gen}/src/tools/comments.js +55 -3
  60. package/{build → out/Default/gen}/src/tools/console.js +8 -6
  61. package/{build → out/Default/gen}/src/tools/css.js +1 -0
  62. package/{build → out/Default/gen}/src/tools/emulation.js +22 -8
  63. package/{build → out/Default/gen}/src/tools/extensions.js +5 -0
  64. package/{build → out/Default/gen}/src/tools/input.js +30 -13
  65. package/out/Default/gen/src/tools/lighthouse.js +273 -0
  66. package/out/Default/gen/src/tools/logpoints.js +51 -0
  67. package/{build → out/Default/gen}/src/tools/memory.js +138 -74
  68. package/{build → out/Default/gen}/src/tools/network.js +5 -4
  69. package/{build → out/Default/gen}/src/tools/pages.js +50 -8
  70. package/{build → out/Default/gen}/src/tools/performance.js +42 -8
  71. package/{build → out/Default/gen}/src/tools/pwa.js +7 -0
  72. package/out/Default/gen/src/tools/screencast.js +160 -0
  73. package/{build → out/Default/gen}/src/tools/screenshot.js +52 -21
  74. package/{build → out/Default/gen}/src/tools/script.js +39 -7
  75. package/{build → out/Default/gen}/src/tools/slim/tools.js +23 -12
  76. package/{build → out/Default/gen}/src/tools/snapshot.js +29 -5
  77. package/{build → out/Default/gen}/src/tools/thirdPartyDeveloper.js +9 -23
  78. package/out/Default/gen/src/tools/toolNames.js +10 -0
  79. package/{build → out/Default/gen}/src/tools/tools.js +4 -0
  80. package/out/Default/gen/src/tools/webauthn.js +51 -0
  81. package/{build → out/Default/gen}/src/tools/webmcp.js +11 -24
  82. package/out/Default/gen/src/types.js +21 -0
  83. package/out/Default/gen/src/utils/TaskRunner.js +136 -0
  84. package/{build → out/Default/gen}/src/utils/WaitForHelper.js +15 -2
  85. package/{build → out/Default/gen}/src/utils/bytes.js +2 -12
  86. package/{build → out/Default/gen}/src/utils/check-for-updates.js +2 -2
  87. package/out/Default/gen/src/utils/disclaimers.js +25 -0
  88. package/{build → out/Default/gen}/src/utils/errorHandling.js +6 -0
  89. package/{build → out/Default/gen}/src/utils/keyboard.js +15 -5
  90. package/out/Default/gen/src/utils/logger.js +131 -0
  91. package/out/Default/gen/src/utils/resourceType.js +27 -0
  92. package/out/Default/gen/src/utils/text.js +44 -0
  93. package/out/Default/gen/src/utils/timeout.js +14 -0
  94. package/{build → out/Default/gen}/src/utils/url.js +82 -10
  95. package/{build → out/Default/gen}/src/version.js +1 -1
  96. package/package.json +31 -23
  97. package/skills/a11y-debugging/SKILL.md +7 -12
  98. package/skills/brave-devtools/SKILL.md +1 -0
  99. package/skills/brave-devtools-cli/SKILL.md +15 -13
  100. package/skills/cookie-debugging/SKILL.md +8 -5
  101. package/skills/debug-optimize-lcp/SKILL.md +1 -1
  102. package/skills/troubleshooting/SKILL.md +4 -5
  103. package/build/src/ToolHandler.js +0 -374
  104. package/build/src/config/ConfigParser.js +0 -213
  105. package/build/src/config/cli-commands.js +0 -34
  106. package/build/src/devtools/DevToolsCommentBridge.js +0 -91
  107. package/build/src/index.js +0 -276
  108. package/build/src/processors/HeapSnapshotManager.js +0 -274
  109. package/build/src/third_party/bundled-packages.json +0 -11
  110. package/build/src/third_party/devtools-formatter-worker.js +0 -16545
  111. package/build/src/third_party/devtools-heap-snapshot-worker.js +0 -12067
  112. package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +0 -58435
  113. package/build/src/tools/ToolDefinition.js +0 -92
  114. package/build/src/tools/lighthouse.js +0 -149
  115. package/build/src/tools/screencast.js +0 -119
  116. package/build/src/types.js +0 -10
  117. package/build/src/utils/logger.js +0 -61
  118. package/build/src/utils/text.js +0 -15
  119. /package/{build → out/Default/gen}/src/McpWorker.js +0 -0
  120. /package/{build → out/Default/gen}/src/bin/check-latest-version.js +0 -0
  121. /package/{build → out/Default/gen}/src/daemon/types.js +0 -0
  122. /package/{build → out/Default/gen}/src/devtools/issueDescriptions.js +0 -0
  123. /package/{build → out/Default/gen}/src/formatters/CommentFormatter.js +0 -0
  124. /package/{build → out/Default/gen}/src/telemetry/WatchdogClient.js +0 -0
  125. /package/{build → out/Default/gen}/src/telemetry/errors.js +0 -0
  126. /package/{build → out/Default/gen}/src/telemetry/flagUtils.js +0 -0
  127. /package/{build → out/Default/gen}/src/telemetry/metricsRegistry.js +0 -0
  128. /package/{build → out/Default/gen}/src/telemetry/persistence.js +0 -0
  129. /package/{build → out/Default/gen}/src/telemetry/types.js +0 -0
  130. /package/{build → out/Default/gen}/src/telemetry/watchdog/ClearcutSender.js +0 -0
  131. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/CoepCoopSandboxedIframeCannotNavigateToCoopPage.md +0 -0
  132. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/CoepCorpNotSameOrigin.md +0 -0
  133. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/CoepCorpNotSameOriginAfterDefaultedToSameOriginByCoep.md +0 -0
  134. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/CoepCorpNotSameSite.md +0 -0
  135. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/CoepFrameResourceNeedsCoepHeader.md +0 -0
  136. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/CompatibilityModeQuirks.md +0 -0
  137. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/CookieAttributeValueExceedsMaxSize.md +0 -0
  138. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/LowTextContrast.md +0 -0
  139. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/SameSiteNoneInsecureErrorRead.md +0 -0
  140. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/SameSiteNoneInsecureErrorSet.md +0 -0
  141. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/SameSiteNoneInsecureWarnRead.md +0 -0
  142. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/SameSiteNoneInsecureWarnSet.md +0 -0
  143. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/SameSiteUnspecifiedLaxAllowUnsafeRead.md +0 -0
  144. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/SameSiteUnspecifiedLaxAllowUnsafeSet.md +0 -0
  145. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/bounceTrackingMitigations.md +0 -0
  146. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/clientHintMetaTagAllowListInvalidOrigin.md +0 -0
  147. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/clientHintMetaTagModifiedHTML.md +0 -0
  148. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/connectionAllowlistEmbeddingRequirementNotSatisfied.md +0 -0
  149. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/connectionAllowlistIFrameAttributeLoosensEmbeddingRequirement.md +0 -0
  150. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/connectionAllowlistInvalidAllowConnectionAllowlistFrom.md +0 -0
  151. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/connectionAllowlistInvalidAllowlistItemType.md +0 -0
  152. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/connectionAllowlistInvalidHeader.md +0 -0
  153. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/connectionAllowlistInvalidUrlPattern.md +0 -0
  154. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/connectionAllowlistItemNotInnerList.md +0 -0
  155. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/connectionAllowlistMoreThanOneList.md +0 -0
  156. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/connectionAllowlistReportingEndpointNotToken.md +0 -0
  157. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cookieCrossSiteRedirectDowngrade.md +0 -0
  158. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cookieExcludeDomainNonAscii.md +0 -0
  159. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cookieExcludePortMismatch.md +0 -0
  160. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cookieExcludeSchemeMismatch.md +0 -0
  161. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cookieExcludeThirdPartyPhaseoutRead.md +0 -0
  162. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cookieExcludeThirdPartyPhaseoutSet.md +0 -0
  163. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cookieWarnDomainNonAscii.md +0 -0
  164. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cookieWarnMetadataGrantRead.md +0 -0
  165. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cookieWarnMetadataGrantReadOptOut.md +0 -0
  166. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cookieWarnMetadataGrantSet.md +0 -0
  167. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cookieWarnMetadataGrantSetOptOut.md +0 -0
  168. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cookieWarnThirdPartyPhaseoutRead.md +0 -0
  169. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cookieWarnThirdPartyPhaseoutSet.md +0 -0
  170. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/corsAllowCredentialsRequired.md +0 -0
  171. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/corsDisabledScheme.md +0 -0
  172. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/corsDisallowedByMode.md +0 -0
  173. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/corsHeaderDisallowedByPreflightResponse.md +0 -0
  174. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/corsInvalidHeaderValues.md +0 -0
  175. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/corsLocalNetworkAccessPermissionDenied.md +0 -0
  176. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/corsMethodDisallowedByPreflightResponse.md +0 -0
  177. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/corsNoCorsRedirectModeNotFollow.md +0 -0
  178. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/corsOriginMismatch.md +0 -0
  179. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/corsPreflightResponseInvalid.md +0 -0
  180. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/corsRedirectContainsCredentials.md +0 -0
  181. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/corsWildcardOriginNotAllowed.md +0 -0
  182. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cspEvalViolation.md +0 -0
  183. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cspInlineViolation.md +0 -0
  184. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cspTrustedTypesPolicyViolation.md +0 -0
  185. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cspTrustedTypesSinkViolation.md +0 -0
  186. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/cspURLViolation.md +0 -0
  187. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/deprecation.md +0 -0
  188. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestAccountsEmptyList.md +0 -0
  189. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestAccountsHttpNotFound.md +0 -0
  190. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestAccountsInvalidContentType.md +0 -0
  191. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestAccountsInvalidResponse.md +0 -0
  192. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestAccountsNoResponse.md +0 -0
  193. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestDnsFetchFailed.md +0 -0
  194. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestDnsInvalidRecord.md +0 -0
  195. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownHttpNotFound.md +0 -0
  196. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownInvalidContentType.md +0 -0
  197. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownInvalidResponse.md +0 -0
  198. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownNoResponse.md +0 -0
  199. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestInvalidEmail.md +0 -0
  200. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestJwksHttpNotFound.md +0 -0
  201. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestJwksInvalidResponse.md +0 -0
  202. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestKeyBindingSigningFailed.md +0 -0
  203. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestRpOriginIsOpaque.md +0 -0
  204. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenHttpNotFound.md +0 -0
  205. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidContentType.md +0 -0
  206. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidResponse.md +0 -0
  207. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidSdJwt.md +0 -0
  208. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenMalformedSdJwt.md +0 -0
  209. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenNoResponse.md +0 -0
  210. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidAudience.md +0 -0
  211. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidIssuedAt.md +0 -0
  212. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidNonce.md +0 -0
  213. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidSdHash.md +0 -0
  214. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidTyp.md +0 -0
  215. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingAud.md +0 -0
  216. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingCnf.md +0 -0
  217. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingIat.md +0 -0
  218. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingNonce.md +0 -0
  219. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingSdHash.md +0 -0
  220. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbSignatureFailed.md +0 -0
  221. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidEmail.md +0 -0
  222. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidEmailVerified.md +0 -0
  223. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidHolderKey.md +0 -0
  224. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidIssuedAt.md +0 -0
  225. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidIssuer.md +0 -0
  226. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtJwksMissingKeys.md +0 -0
  227. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingCnf.md +0 -0
  228. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingEmail.md +0 -0
  229. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingIat.md +0 -0
  230. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingIss.md +0 -0
  231. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtSignatureFailed.md +0 -0
  232. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtUnsupportedHeaderAlg.md +0 -0
  233. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestUserLoggedOut.md +0 -0
  234. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestWellKnownAccountsEndpointCrossOrigin.md +0 -0
  235. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestWellKnownHttpNotFound.md +0 -0
  236. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestWellKnownInvalidContentType.md +0 -0
  237. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestWellKnownInvalidResponse.md +0 -0
  238. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestWellKnownIssuanceEndpointCrossOrigin.md +0 -0
  239. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestWellKnownListEmpty.md +0 -0
  240. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestWellKnownMissingAccountsEndpoint.md +0 -0
  241. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestWellKnownMissingIssuanceEndpoint.md +0 -0
  242. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestWellKnownNoResponse.md +0 -0
  243. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/emailVerificationRequestWellKnownUnsupportedSigningAlgorithm.md +0 -0
  244. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestAccountsBlockedByConnectionAllowlist.md +0 -0
  245. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestAccountsHttpNotFound.md +0 -0
  246. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestAccountsInvalidResponse.md +0 -0
  247. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestAccountsNoResponse.md +0 -0
  248. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestApprovalDeclined.md +0 -0
  249. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestCanceled.md +0 -0
  250. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestConfigBlockedByConnectionAllowlist.md +0 -0
  251. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestErrorFetchingSignin.md +0 -0
  252. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestErrorIdToken.md +0 -0
  253. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestIdTokenBlockedByConnectionAllowlist.md +0 -0
  254. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestIdTokenHttpNotFound.md +0 -0
  255. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestIdTokenInvalidRequest.md +0 -0
  256. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestIdTokenInvalidResponse.md +0 -0
  257. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestIdTokenNoResponse.md +0 -0
  258. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestInvalidSigninResponse.md +0 -0
  259. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestManifestHttpNotFound.md +0 -0
  260. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestManifestInvalidResponse.md +0 -0
  261. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestManifestNoResponse.md +0 -0
  262. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestTooManyRequests.md +0 -0
  263. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthRequestWellKnownBlockedByConnectionAllowlist.md +0 -0
  264. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthUserInfoRequestInvalidAccountsResponse.md +0 -0
  265. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthUserInfoRequestInvalidConfigOrWellKnown.md +0 -0
  266. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNoAccountSharingPermission.md +0 -0
  267. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNoApiPermission.md +0 -0
  268. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNoReturningUserFromFetchedAccounts.md +0 -0
  269. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotIframe.md +0 -0
  270. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotPotentiallyTrustworthy.md +0 -0
  271. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotSameOrigin.md +0 -0
  272. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotSignedInWithIdp.md +0 -0
  273. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/fetchingPartitionedBlobURL.md +0 -0
  274. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericBackUINavigationWouldSkipAd.md +0 -0
  275. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormAriaLabelledByToNonExistingIdError.md +0 -0
  276. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormAutocompleteAttributeEmptyError.md +0 -0
  277. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormDuplicateIdForInputError.md +0 -0
  278. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormEmptyIdAndNameAttributesForInputError.md +0 -0
  279. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormInputAssignedAutocompleteValueToIdOrNameAttributeError.md +0 -0
  280. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormInputHasWrongButWellIntendedAutocompleteValueError.md +0 -0
  281. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormInputWithNoLabelError.md +0 -0
  282. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormLabelForMatchesNonExistingIdError.md +0 -0
  283. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormLabelForNameError.md +0 -0
  284. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormLabelHasNeitherForNorNestedInputError.md +0 -0
  285. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormModelContextMissingToolDescription.md +0 -0
  286. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormModelContextMissingToolName.md +0 -0
  287. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormModelContextParameterMissingName.md +0 -0
  288. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormModelContextParameterMissingTitleAndDescription.md +0 -0
  289. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericFormModelContextRequiredParameterMissingName.md +0 -0
  290. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericNavigationEntryMarkedSkippable.md +0 -0
  291. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/genericResponseWasBlockedByORB.md +0 -0
  292. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/heavyAd.md +0 -0
  293. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/lazyLoadImageZeroSize.md +0 -0
  294. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/mixedContent.md +0 -0
  295. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/navigatingPartitionedBlobURL.md +0 -0
  296. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementActivationDisabled.md +0 -0
  297. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementActivationDisabledWithOccluder.md +0 -0
  298. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementActivationDisabledWithOccluderParent.md +0 -0
  299. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementCspFrameAncestorsMissing.md +0 -0
  300. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementFencedFrameDisallowed.md +0 -0
  301. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementFontSizeTooLarge.md +0 -0
  302. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementFontSizeTooSmall.md +0 -0
  303. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementGeolocationDeprecated.md +0 -0
  304. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementInsetBoxShadowUnsupported.md +0 -0
  305. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementInvalidDisplayStyle.md +0 -0
  306. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementInvalidSizeValue.md +0 -0
  307. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementInvalidType.md +0 -0
  308. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementInvalidTypeActivation.md +0 -0
  309. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementLowContrast.md +0 -0
  310. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementNonOpaqueColor.md +0 -0
  311. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementPaddingBottomUnsupported.md +0 -0
  312. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementPaddingRightUnsupported.md +0 -0
  313. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementPermissionsPolicyBlocked.md +0 -0
  314. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementRegistrationFailed.md +0 -0
  315. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementRequestInProgress.md +0 -0
  316. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementSecurityChecksFailed.md +0 -0
  317. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementTypeNotSupported.md +0 -0
  318. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/permissionElementUntrustedEvent.md +0 -0
  319. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/placeholderDescriptionForInvisibleIssues.md +0 -0
  320. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/propertyRuleInvalidNameIssue.md +0 -0
  321. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/propertyRuleIssue.md +0 -0
  322. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/selectElementAccessibilityDisallowedOptGroupChild.md +0 -0
  323. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/selectElementAccessibilityDisallowedSelectChild.md +0 -0
  324. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/selectElementAccessibilityInteractiveContentAttributesSelectDescendant.md +0 -0
  325. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/selectElementAccessibilityInteractiveContentLegendChild.md +0 -0
  326. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/selectElementAccessibilityInteractiveContentOptionChild.md +0 -0
  327. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/selectElementAccessibilityNonPhrasingContentOptionChild.md +0 -0
  328. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/selectivePermissionsIntervention.md +0 -0
  329. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedArrayBuffer.md +0 -0
  330. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryUseErrorCrossOriginNoCorsRequest.md +0 -0
  331. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryUseErrorDictionaryLoadFailure.md +0 -0
  332. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryUseErrorMatchingDictionaryNotUsed.md +0 -0
  333. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryUseErrorUnexpectedContentDictionaryHeader.md +0 -0
  334. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorCossOriginNoCorsRequest.md +0 -0
  335. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorDisallowedBySettings.md +0 -0
  336. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorExpiredResponse.md +0 -0
  337. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorFeatureDisabled.md +0 -0
  338. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorInsufficientResources.md +0 -0
  339. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorInvalidMatchField.md +0 -0
  340. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorInvalidStructuredHeader.md +0 -0
  341. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorInvalidTTLField.md +0 -0
  342. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNavigationRequest.md +0 -0
  343. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNoMatchField.md +0 -0
  344. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonIntegerTTLField.md +0 -0
  345. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonListMatchDestField.md +0 -0
  346. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonSecureContext.md +0 -0
  347. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonStringIdField.md +0 -0
  348. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonStringInMatchDestList.md +0 -0
  349. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonStringMatchField.md +0 -0
  350. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNonTokenTypeField.md +0 -0
  351. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorRequestAborted.md +0 -0
  352. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorShuttingDown.md +0 -0
  353. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorTooLongIdField.md +0 -0
  354. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sharedDictionaryWriteErrorUnsupportedType.md +0 -0
  355. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriInvalidSignatureHeader.md +0 -0
  356. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriInvalidSignatureInputHeader.md +0 -0
  357. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriMissingSignatureHeader.md +0 -0
  358. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriMissingSignatureInputHeader.md +0 -0
  359. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriSignatureHeaderValueIsIncorrectLength.md +0 -0
  360. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriSignatureHeaderValueIsNotByteSequence.md +0 -0
  361. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriSignatureHeaderValueIsParameterized.md +0 -0
  362. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidComponentName.md +0 -0
  363. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidComponentType.md +0 -0
  364. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidDerivedComponentParameter.md +0 -0
  365. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidHeaderComponentParameter.md +0 -0
  366. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidParameter.md +0 -0
  367. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriSignatureInputHeaderKeyIdLength.md +0 -0
  368. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriSignatureInputHeaderMissingLabel.md +0 -0
  369. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriSignatureInputHeaderMissingRequiredParameters.md +0 -0
  370. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriSignatureInputHeaderValueMissingComponents.md +0 -0
  371. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriSignatureInputHeaderValueNotInnerList.md +0 -0
  372. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriValidationFailedIntegrityMismatch.md +0 -0
  373. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriValidationFailedInvalidLength.md +0 -0
  374. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriValidationFailedSignatureExpired.md +0 -0
  375. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/sriValidationFailedSignatureMismatch.md +0 -0
  376. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/stylesheetLateImport.md +0 -0
  377. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/stylesheetRequestFailed.md +0 -0
  378. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/summaryElementAccessibilityInteractiveContentSummaryDescendant.md +0 -0
  379. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/unencodedDigestIncorrectDigestLength.md +0 -0
  380. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/unencodedDigestIncorrectDigestType.md +0 -0
  381. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/unencodedDigestMalformedDictionary.md +0 -0
  382. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/unencodedDigestUnknownAlgorithm.md +0 -0
  383. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/webInstallManifestMissingId.md +0 -0
  384. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/webInstallManifestMissingNameOrShortName.md +0 -0
  385. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/webInstallManifestParsingOrNetworkError.md +0 -0
  386. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/webInstallNoManifest.md +0 -0
  387. /package/{build → out/Default/gen}/src/third_party/issue-descriptions/webInstallStartUrlInvalid.md +0 -0
  388. /package/{build → out/Default/gen}/src/utils/files.js +0 -0
  389. /package/{build → out/Default/gen}/src/utils/id.js +0 -0
  390. /package/{build → out/Default/gen}/src/utils/pagination.js +0 -0
  391. /package/{build → out/Default/gen}/src/utils/polyfill.js +0 -0
package/README.md CHANGED
@@ -62,7 +62,7 @@ When the agent connects, Brave asks for permission. Click **Allow**.
62
62
 
63
63
  Use [Chrome DevTools MCP](https://github.com/ChromeDevTools/chrome-devtools-mcp) when your target browser is Chrome. Use `brave-mcp` when your target browser is Brave and you want Brave-native discovery, profiles, channels, naming, and privacy defaults without giving up upstream features.
64
64
 
65
- ## [Tool reference](./docs/tool-reference.md) | [Changelog](./CHANGELOG.md) | [Contributing](./CONTRIBUTING.md) | [Troubleshooting](./docs/troubleshooting.md) | [Design principles](./docs/design-principles.md)
65
+ ## [Tool reference](./docs/tool-reference.md) | [CLI](./docs/cli.md) | [Skills](#skills) | [Changelog](./CHANGELOG.md) | [Contributing](./CONTRIBUTING.md) | [Troubleshooting](./docs/troubleshooting.md) | [Design principles](./docs/design-principles.md)
66
66
 
67
67
  ## Key features
68
68
 
@@ -163,6 +163,70 @@ Brave asks you to allow the connection. After you click **Allow**, your MCP clie
163
163
  > [!NOTE]
164
164
  > The MCP server connects to Brave only when the MCP client first uses a tool that needs a browser, not when the client starts. Without `--autoConnect`, that is when the server launches its own Brave.
165
165
 
166
+ ## CLI
167
+
168
+ The package also includes the `brave-devtools` CLI. Use it to control the browser from your terminal without MCP:
169
+
170
+ ```sh
171
+ npm i brave-mcp@latest -g
172
+ brave-devtools navigate_page 1 --url "https://brave.com"
173
+ brave-devtools take_screenshot 1 --filePath screenshot.png
174
+ brave-devtools stop
175
+ ```
176
+
177
+ See the [CLI guide][cli] for all commands and options.
178
+
179
+ ## Skills
180
+
181
+ The package includes [skills](./skills) that teach your agent how to use Brave DevTools:
182
+
183
+ | Skill | Use it to |
184
+ | --------------------------------------------------------- | ---------------------------------------------- |
185
+ | [`brave-devtools`](./skills/brave-devtools) | Debug and automate pages with the MCP tools. |
186
+ | [`brave-devtools-cli`](./skills/brave-devtools-cli) | Run the CLI and write shell scripts with it. |
187
+ | [`a11y-debugging`](./skills/a11y-debugging) | Audit and fix accessibility issues. |
188
+ | [`cookie-debugging`](./skills/cookie-debugging) | Debug cookies, sessions, and authentication. |
189
+ | [`debug-optimize-lcp`](./skills/debug-optimize-lcp) | Debug and improve Largest Contentful Paint. |
190
+ | [`memory-leak-debugging`](./skills/memory-leak-debugging) | Find and fix memory leaks. |
191
+ | [`troubleshooting`](./skills/troubleshooting) | Fix connection problems between MCP and Brave. |
192
+
193
+ ### Install skills as a plugin
194
+
195
+ A plugin installs the MCP server and all skills, including the CLI skill:
196
+
197
+ - **Claude Code:** Run `/plugin marketplace add triuzzi/brave-devtools-mcp`, then `/plugin install brave-devtools-mcp@brave-devtools-plugins`.
198
+ - **Copilot / VS Code:** Run **Chat: Install Plugin From Source** and enter `triuzzi/brave-devtools-mcp`.
199
+ - **Gemini CLI:** Run `gemini extensions install --auto-update https://github.com/triuzzi/brave-devtools-mcp`.
200
+
201
+ See the [Client Configurations Guide][client-configurations-guide] for more information.
202
+
203
+ ### Install skills manually
204
+
205
+ If your agent does not support plugins, copy the skill folders into the skills directory of your agent. The npm package contains the skills:
206
+
207
+ ```sh
208
+ npm i brave-mcp@latest -g
209
+ # Example for Claude Code. Use the skills directory of your agent.
210
+ mkdir -p ~/.claude/skills
211
+ cp -R "$(npm root -g)/brave-mcp/skills/brave-devtools-cli" ~/.claude/skills/
212
+ ```
213
+
214
+ > [!IMPORTANT]
215
+ > The `brave-devtools-cli` skill requires the CLI. Install the package globally before you use the skill.
216
+
217
+ ### Keep skills up to date
218
+
219
+ Skills describe the tools of a specific version. Update the skills each time you update the MCP server or the CLI. Outdated skills can tell your agent to use tools or parameters that changed.
220
+
221
+ - **Plugins:** Use the update command of the plugin manager of your agent. The Gemini CLI command above enables automatic updates with `--auto-update`.
222
+ - **Manual installs:** Update the package, then copy the skill folders again:
223
+
224
+ ```sh
225
+ npm i brave-mcp@latest -g
226
+ mkdir -p ~/.claude/skills
227
+ cp -R "$(npm root -g)/brave-mcp/skills/brave-devtools-cli" ~/.claude/skills/
228
+ ```
229
+
166
230
  ## Tools
167
231
 
168
232
  If you run into any issues, checkout our [troubleshooting guide][troubleshooting].
@@ -172,6 +236,8 @@ See the full [Tool reference][tool-reference] for a complete list of all support
172
236
 
173
237
  Find the complete list of server parameters (e.g., `--headless`, `--isolated`, `--slim`) and how to configure WebSocket connections in the [Configuration Guide][configuration-guide].
174
238
 
239
+ For basic browser tasks only, the `--slim` flag exposes a reduced set of tools. See the [Slim tool reference](./docs/slim-tool-reference.md).
240
+
175
241
  ## Advanced usage
176
242
 
177
243
  For advanced features such as handling concurrent sessions, persistent user data directories, connecting to a running Brave instance instead of starting a new one, or debugging on Android, see our [Advanced Usage Guide][advanced-usage-guide].
@@ -187,6 +253,8 @@ If you are developing agentic tooling and want to provide an integrated browser
187
253
  For a reference implementation, see the [Gemini CLI browser agent documentation](https://geminicli.com/docs/core/subagents/#browser-agent).
188
254
 
189
255
  [advanced-usage-guide]: ./docs/advanced-usage.md
256
+ [cli]: ./docs/cli.md
257
+ [skills]: #skills
190
258
  [client-configurations-guide]: ./docs/client-configurations.md
191
259
  [configuration-guide]: ./docs/configuration.md
192
260
  [tool-reference]: ./docs/tool-reference.md
@@ -60,20 +60,28 @@ import { execSync } from 'node:child_process';
60
60
  import fs from 'node:fs';
61
61
  import os from 'node:os';
62
62
  import path from 'node:path';
63
- import { Mutex, puppeteer } from './third_party/index.js';
64
- import { logger, puppeteerLogger } from './utils/logger.js';
63
+ import { Mutex, puppeteer, } from './third_party/index.js';
64
+ import { logger, puppeteerLogger, redactConnectOptionsForLogging, } from './utils/logger.js';
65
65
  import { isAllowedUrl } from './utils/url.js';
66
+ // Node runs a timer whose delay exceeds 2^31-1 ms after 1 ms instead, which
67
+ // would make every CDP call and tool call time out at once.
68
+ const MAXIMUM_TIMER_DELAY_MILLISECONDS = 2_147_483_647;
69
+ export function parseProtocolTimeoutMilliseconds(rawValue) {
70
+ const timeoutMilliseconds = Number(rawValue ?? '600000');
71
+ if (!Number.isSafeInteger(timeoutMilliseconds) ||
72
+ timeoutMilliseconds <= 0 ||
73
+ timeoutMilliseconds > MAXIMUM_TIMER_DELAY_MILLISECONDS) {
74
+ throw new Error(`BRAVE_DEVTOOLS_PROTOCOL_TIMEOUT_MS must be a positive integer no greater than ${MAXIMUM_TIMER_DELAY_MILLISECONDS}.`);
75
+ }
76
+ return timeoutMilliseconds;
77
+ }
66
78
  // Heavy pages (e.g. Studio module dev bundles >100MB) cannot ack
67
79
  // `Network.enable` and other auto-attached domain calls within
68
80
  // puppeteer's default 180s. Once that fires, the CDP connection is
69
81
  // marked dead and every subsequent call throws — only daemon restart
70
82
  // recovers. Bumping the ceiling to 10min covers realistic loads;
71
83
  // override via env for power users.
72
- export const PROTOCOL_TIMEOUT_MILLISECONDS = Number(process.env.BRAVE_DEVTOOLS_PROTOCOL_TIMEOUT_MS ?? '600000');
73
- if (!Number.isSafeInteger(PROTOCOL_TIMEOUT_MILLISECONDS) ||
74
- PROTOCOL_TIMEOUT_MILLISECONDS <= 0) {
75
- throw new Error('BRAVE_DEVTOOLS_PROTOCOL_TIMEOUT_MS must be a positive integer.');
76
- }
84
+ export const PROTOCOL_TIMEOUT_MILLISECONDS = parseProtocolTimeoutMilliseconds(process.env.BRAVE_DEVTOOLS_PROTOCOL_TIMEOUT_MS);
77
85
  export function resolveBraveExecutablePath(channel = 'release') {
78
86
  const environmentPath = process.env['BRAVE_PATH'];
79
87
  if (environmentPath) {
@@ -169,11 +177,11 @@ export class BrowserManager {
169
177
  #initPromise;
170
178
  #mutex = new Mutex();
171
179
  #closingCount = 0;
172
- #serverArgs;
180
+ #config;
173
181
  #options;
174
182
  #browserAttempt = new BrowserAttempt();
175
- constructor(serverArgs, options = {}) {
176
- this.#serverArgs = serverArgs;
183
+ constructor(config, options = {}) {
184
+ this.#config = config;
177
185
  this.#options = options;
178
186
  }
179
187
  static makeTargetFilter(enableExtensions = false) {
@@ -255,7 +263,7 @@ export class BrowserManager {
255
263
  // a call silently adopt the freshly-rotated token as its own baseline
256
264
  // once the lock frees up, defeating the abandonment check entirely.
257
265
  const attempt = this.#browserAttempt;
258
- const _guard = __addDisposableResource(env_1, await this.#mutex.acquire(), false);
266
+ const guard = __addDisposableResource(env_1, await this.#mutex.acquire(), false);
259
267
  if (this.#closingCount > 0) {
260
268
  throw new Error('Browser was closed while initializing.');
261
269
  }
@@ -300,38 +308,40 @@ export class BrowserManager {
300
308
  * McpContext initialization built on it — if ensureBrowser() already
301
309
  * resolved and it's that later step hanging (e.g. a dead CDP transport),
302
310
  * the token rotation alone does nothing, since #browser is already
303
- * cached. Safe to forget unconditionally here: the tool mutex serializes
304
- * every call, so there's no concurrent caller to disrupt.
311
+ * cached. Safe to forget unconditionally here: a tool call that already
312
+ * received a Browser keeps using it, so forgetting the cached value only
313
+ * makes the next ensureBrowser() reconnect.
305
314
  */
306
315
  abandonPendingAttempt() {
307
316
  this.#browserAttempt = new BrowserAttempt();
308
317
  this.#initPromise = undefined;
309
- if (this.#browser) {
310
- this.forget(this.#browser);
311
- }
318
+ this.#forgetCurrentBrowser();
312
319
  }
313
320
  async #initBrowser() {
314
- if (this.#serverArgs.browserUrl ||
315
- this.#serverArgs.wsEndpoint ||
316
- this.#serverArgs.autoConnect) {
321
+ if (this.#config.resolved.browserUrl ||
322
+ this.#config.resolved.wsEndpoint ||
323
+ this.#config.resolved.autoConnect) {
317
324
  return await this.#connect();
318
325
  }
319
326
  return await this.#launch();
320
327
  }
321
328
  async #launch() {
322
- const { channel, headless, isolated = false, categoryExtensions: enableExtensions, viaCli, viewport, acceptInsecureCerts, experimentalDevtools: devtools = false, blockedUrlPattern: blocklist, allowedUrlPattern: allowlist, ignoreDefaultBraveArg, proxyServer, } = this.#serverArgs;
329
+ const { channel, headless, isolated = false, categoryExtensions: enableExtensions, viaCli, viewport, acceptInsecureCerts, experimentalDevtools: devtools = false, blockedUrlPattern: blocklist, allowedUrlPattern: allowlist, ignoreDefaultBraveArg, proxyServer, } = this.#config.resolved;
323
330
  const profileDirName = channel && channel !== 'release'
324
331
  ? `brave-profile-${channel}`
325
332
  : 'brave-profile';
326
- const executablePath = this.#serverArgs.executablePath ?? resolveBraveExecutablePath(channel);
327
- let userDataDir = this.#serverArgs.userDataDir;
333
+ const executablePath = this.#config.resolved.executablePath ??
334
+ resolveBraveExecutablePath(channel);
335
+ let userDataDir = this.#config.resolved.userDataDir;
328
336
  if (!isolated && !userDataDir) {
329
337
  userDataDir = path.join(os.homedir(), '.cache', viaCli ? 'brave-devtools-mcp-cli' : 'brave-devtools-mcp', profileDirName);
330
338
  await fs.promises.mkdir(userDataDir, {
331
339
  recursive: true,
332
340
  });
333
341
  }
334
- const args = [...(this.#serverArgs.braveArg ?? [])];
342
+ const args = [
343
+ ...(this.#config.resolved.braveArg ?? []),
344
+ ];
335
345
  if (proxyServer) {
336
346
  args.push(`--proxy-server=${proxyServer}`);
337
347
  }
@@ -364,6 +374,12 @@ export class BrowserManager {
364
374
  blocklist,
365
375
  allowlist,
366
376
  logger: puppeteerLogger,
377
+ // The entry points own signal handling and call close(), which
378
+ // closes the browser gracefully. Puppeteer's handlers would SIGKILL
379
+ // Chrome synchronously on the same signal and win the race.
380
+ handleSIGINT: false,
381
+ handleSIGTERM: false,
382
+ handleSIGHUP: false,
367
383
  });
368
384
  if (this.#options.logFile) {
369
385
  // FIXME: we are probably subscribing too late to catch startup logs. We
@@ -405,24 +421,31 @@ export class BrowserManager {
405
421
  }
406
422
  }
407
423
  async #connect() {
408
- const { browserUrl: browserURL, wsEndpoint, wsHeaders, autoConnect: isAutoConnect, userDataDir, blockedUrlPattern: blocklist, allowedUrlPattern: allowlist, } = this.#serverArgs;
424
+ const { browserUrl: browserURL, wsEndpoint, wsHeaders, autoConnect: isAutoConnect, userDataDir, blockedUrlPattern: blocklist, allowedUrlPattern: allowlist, categoryExtensions: enableExtensions, } = this.#config.resolved;
409
425
  // Important: only pass channel, if autoConnect is true.
410
- const channel = isAutoConnect ? this.#serverArgs.channel : undefined;
426
+ const channel = isAutoConnect ? this.#config.resolved.channel : undefined;
411
427
  const connectOptions = {
412
- targetFilter: _a.makeTargetFilter(),
428
+ targetFilter: _a.makeTargetFilter(enableExtensions),
413
429
  defaultViewport: null,
414
430
  handleDevToolsAsPage: true,
415
431
  protocolTimeout: PROTOCOL_TIMEOUT_MILLISECONDS,
416
432
  blocklist,
417
433
  allowlist,
418
434
  logger: puppeteerLogger,
435
+ // Detects a transport that died without a close frame (e.g. an adb
436
+ // port-forward torn down mid-call). Puppeteer pings the socket
437
+ // periodically and terminates it on a missed pong, which rejects the
438
+ // in-flight CDP calls and fires `disconnected` so #evictIfCurrent()
439
+ // drops the stale handle. Without this, such a call would hang until
440
+ // protocolTimeout inside the context queue.
441
+ wsOptions: {
442
+ keepAlive: true,
443
+ headers: wsHeaders,
444
+ },
419
445
  };
420
446
  let autoConnect = false;
421
447
  if (wsEndpoint) {
422
448
  connectOptions.browserWSEndpoint = wsEndpoint;
423
- if (wsHeaders) {
424
- connectOptions.headers = wsHeaders;
425
- }
426
449
  }
427
450
  else if (browserURL) {
428
451
  connectOptions.browserURL = browserURL;
@@ -462,7 +485,7 @@ export class BrowserManager {
462
485
  else {
463
486
  throw new Error('Either browserURL, wsEndpoint, channel or userDataDir must be provided');
464
487
  }
465
- logger?.('Connecting Puppeteer to ', JSON.stringify(connectOptions));
488
+ logger?.('Connecting Puppeteer to ', JSON.stringify(redactConnectOptionsForLogging(connectOptions)));
466
489
  try {
467
490
  const connected = await puppeteer.connect(connectOptions);
468
491
  logger?.('Connected Puppeteer');
@@ -478,32 +501,31 @@ export class BrowserManager {
478
501
  }
479
502
  }
480
503
  /**
481
- * Clears the cached browser handle if it still matches `candidate`, so the
482
- * next ensureBrowser() call establishes a fresh connection instead of
483
- * reusing a handle that looks connected but is actually dead (e.g. its CDP
484
- * transport died without ever emitting a `close`/`disconnected` event — as
485
- * happens when an adb port-forward is torn down mid-call rather than
486
- * closed cleanly).
504
+ * Drops the cached browser handle so the next ensureBrowser() call
505
+ * establishes a fresh connection. Only called from abandonPendingAttempt()
506
+ * when a getContext()-level timeout fires after ensureBrowser() already
507
+ * resolved, which means the handle looks connected but its CDP
508
+ * initialization is hanging.
487
509
  *
488
- * Also actively tears `candidate` down in the background: closes it if it
489
- * was launched (so the Brave subprocess doesn't leak), or disconnects if
490
- * it was only connected to (so the transport and its listeners don't
491
- * leak). This also settles any CDP call still pending against it —
492
- * Puppeteer's connection disposal synchronously rejects in-flight
493
- * callbacks — instead of leaving a hung call to leak forever.
510
+ * Also tears the browser down in the background: closes it if it was
511
+ * launched (so the Brave subprocess doesn't leak), or disconnects if it
512
+ * was only connected to (so the transport and its listeners don't leak).
513
+ * Puppeteer's connection disposal rejects any in-flight CDP call, so a hung
514
+ * call settles instead of leaking forever.
494
515
  */
495
- forget(candidate) {
496
- if (this.#browser !== candidate) {
516
+ #forgetCurrentBrowser() {
517
+ const browser = this.#browser;
518
+ if (!browser) {
497
519
  return;
498
520
  }
499
521
  const mode = this.#browserMode;
500
522
  this.#browser = undefined;
501
523
  this.#browserMode = undefined;
502
524
  if (mode === 'launched') {
503
- void this.#closeLaunchedBrowser(candidate);
525
+ void this.#closeLaunchedBrowser(browser);
504
526
  }
505
527
  else {
506
- void candidate.disconnect().catch(err => {
528
+ void browser.disconnect().catch(err => {
507
529
  logger?.('Failed to disconnect forgotten browser', err);
508
530
  });
509
531
  }
@@ -514,9 +536,8 @@ export class BrowserManager {
514
536
  * call reconnects proactively rather than only discovering the staleness
515
537
  * lazily on the next call's `!this.#browser?.connected` check. No teardown
516
538
  * here — the browser is already disconnected/closed, that's why this
517
- * fired — so this shares forget()'s identity guard without its cleanup,
518
- * making a `disconnected` that fires late on an already-superseded browser
519
- * a safe no-op.
539
+ * fired. The identity guard makes a `disconnected` that fires late on an
540
+ * already-superseded browser a safe no-op.
520
541
  */
521
542
  #evictIfCurrent(candidate) {
522
543
  if (this.#browser !== candidate) {
@@ -571,7 +592,7 @@ export class BrowserManager {
571
592
  try {
572
593
  this.#initPromise = undefined;
573
594
  this.#closingCount++;
574
- const _guard = __addDisposableResource(env_2, await this.#mutex.acquire(), false);
595
+ const guard = __addDisposableResource(env_2, await this.#mutex.acquire(), false);
575
596
  try {
576
597
  await this.#closeBrowser();
577
598
  }
@@ -0,0 +1,307 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import { Script } from 'node:vm';
7
+ import { waitForSourceMaps, } from './devtools/DevtoolsUtils.js';
8
+ import { DevTools } from './third_party/index.js';
9
+ /**
10
+ * Thrown by {@link LogpointManager.setLogpoints} when some locations could
11
+ * not be bound. The other logpoints were applied; `logpoints` holds the
12
+ * resulting state.
13
+ */
14
+ export class LogpointError extends Error {
15
+ logpoints;
16
+ constructor(message, logpoints) {
17
+ super(message);
18
+ this.name = 'LogpointError';
19
+ this.logpoints = logpoints;
20
+ }
21
+ }
22
+ export const LOGPOINT_FORMAT = '<url>:<lineNumber>[:<columnNumber>] <expression>';
23
+ /**
24
+ * Parses a logpoint given as `<url>:<lineNumber>[:<columnNumber>] <expression>`.
25
+ * The url may itself contain colons (for example a port), so the location is
26
+ * the shortest prefix that ends in one or two numbers followed by whitespace.
27
+ */
28
+ export function parseLogpoint(text) {
29
+ const match = /^(\S+?):(\d+)(?::(\d+))?\s+(\S.*)$/s.exec(text.trim());
30
+ if (!match) {
31
+ throw new Error(`Invalid logpoint "${text}". Expected the format ${LOGPOINT_FORMAT}, for example "src/app.ts:42 'total', cart.total".`);
32
+ }
33
+ const [, url, lineNumber, columnNumber, expression] = match;
34
+ const logpoint = {
35
+ url,
36
+ lineNumber: Number(lineNumber),
37
+ expression,
38
+ };
39
+ if (columnNumber !== undefined) {
40
+ logpoint.columnNumber = Number(columnNumber);
41
+ }
42
+ if (logpoint.lineNumber < 1 || logpoint.columnNumber === 0) {
43
+ throw new Error(`Invalid logpoint "${text}": line and column numbers are 1-based.`);
44
+ }
45
+ return logpoint;
46
+ }
47
+ /**
48
+ * Checks that the expression parses as `console.log()` arguments. V8 is used
49
+ * for parsing both here and in the browser, so an expression that is rejected
50
+ * here would also be rejected as a breakpoint condition. The script is only
51
+ * compiled, never run.
52
+ */
53
+ export function validateExpression(expression) {
54
+ try {
55
+ new Script(`console.log(${expression})`);
56
+ }
57
+ catch (error) {
58
+ throw new Error(`The logpoint expression \`${expression}\` does not compile: ${error instanceof Error ? error.message : String(error)}. Provide the expression like arguments to console.log(...).`);
59
+ }
60
+ }
61
+ function toUserCondition(expression) {
62
+ return expression;
63
+ }
64
+ function isFromSourceMap(uiSourceCode) {
65
+ return uiSourceCode.contentType().isFromSourceMap();
66
+ }
67
+ function locationKey(url, lineNumber, columnNumber) {
68
+ return `${url}:${lineNumber}${columnNumber === undefined ? '' : `:${columnNumber}`}`;
69
+ }
70
+ function baseName(url) {
71
+ return url.slice(url.lastIndexOf('/') + 1).toLowerCase();
72
+ }
73
+ /**
74
+ * How long to wait for the source maps of the page to load before an original
75
+ * source URL is reported as unknown. Loads still pending then continue in the
76
+ * background, so a retry finds the source.
77
+ */
78
+ const SOURCE_MAP_LOAD_TIMEOUT_MS = 5_000;
79
+ /**
80
+ * Manages logpoints for a single page on top of the `BreakpointManager` of
81
+ * the page's DevTools universe.
82
+ *
83
+ * A logpoint is a DevTools breakpoint with `isLogpoint` set: its condition
84
+ * logs the expression and never pauses. The breakpoint manager resolves the
85
+ * location through source maps, substitutes minified variable names in the
86
+ * expression and keeps the breakpoint bound across reloads and rebuilt
87
+ * bundles. This class only maps the tool API (URLs and 1-based positions)
88
+ * onto workspace source codes.
89
+ */
90
+ export class LogpointManager {
91
+ #universe;
92
+ #target;
93
+ /**
94
+ * The breakpoints that are logpoints of this page. The breakpoint manager
95
+ * keeps one `Breakpoint` per normalized location, so two requested
96
+ * locations can share one entry.
97
+ */
98
+ #records = new Set();
99
+ constructor(devTools) {
100
+ this.#universe = devTools.universe;
101
+ this.#target = devTools.target;
102
+ }
103
+ get #debuggerModel() {
104
+ const model = this.#target.model(DevTools.DebuggerModel);
105
+ if (!model) {
106
+ throw new Error('The DevTools debugger model is not available.');
107
+ }
108
+ return model;
109
+ }
110
+ /**
111
+ * Makes the given logpoints the complete set of logpoints on the page:
112
+ * logpoints not in the list are removed, new ones are added and the
113
+ * expression of an existing logpoint at the same location is replaced.
114
+ * Expressions and locations are validated before anything is changed.
115
+ * Throws a {@link LogpointError} when a location has no executable code;
116
+ * the other logpoints are applied regardless.
117
+ */
118
+ async setLogpoints(desired) {
119
+ for (const options of desired) {
120
+ validateExpression(options.expression);
121
+ }
122
+ const resolved = new Map();
123
+ for (const options of desired) {
124
+ const uiSourceCode = await this.#findUISourceCode(options.url);
125
+ const key = locationKey(uiSourceCode.url(), options.lineNumber, options.columnNumber);
126
+ // The last entry for a location wins, like setting it twice would.
127
+ resolved.set(key, { options, uiSourceCode, key });
128
+ }
129
+ const kept = new Set();
130
+ const unbound = [];
131
+ for (const { options, uiSourceCode, key } of resolved.values()) {
132
+ const breakpoint = await this.#universe.breakpointManager.setBreakpoint(uiSourceCode,
133
+ // The tool API is 1-based like the DevTools UI; the model is 0-based.
134
+ options.lineNumber - 1, options.columnNumber === undefined
135
+ ? undefined
136
+ : options.columnNumber - 1, toUserCondition(options.expression),
137
+ /* enabled */ true,
138
+ /* isLogpoint */ true, "USER_ACTION" /* DevTools.BreakpointManager.BreakpointOrigin.USER_ACTION */);
139
+ if (!breakpoint) {
140
+ unbound.push(key);
141
+ continue;
142
+ }
143
+ // Wait for the breakpoint to be set in the backend so that the result
144
+ // reports whether the location resolved.
145
+ await breakpoint.updateBreakpoint();
146
+ if (!this.#records.has(breakpoint) && !breakpoint.bound()) {
147
+ await breakpoint.remove(/* keepInStorage */ false);
148
+ unbound.push(key);
149
+ continue;
150
+ }
151
+ this.#records.add(breakpoint);
152
+ kept.add(breakpoint);
153
+ }
154
+ for (const breakpoint of [...this.#records]) {
155
+ if (!kept.has(breakpoint)) {
156
+ this.#records.delete(breakpoint);
157
+ await breakpoint.remove(/* keepInStorage */ false);
158
+ }
159
+ }
160
+ this.#setLazySourceMapLoading(this.#records.size === 0);
161
+ const logpoints = this.#currentLogpoints();
162
+ if (unbound.length) {
163
+ throw new LogpointError(`No executable code at ${unbound.join(', ')} (for example a comment, type annotation or empty line, or a line not mapped to the generated script). Pick lines containing executable code. The other logpoints were applied.`, logpoints);
164
+ }
165
+ return logpoints;
166
+ }
167
+ /**
168
+ * Releases the records. The breakpoints are not removed from the backend:
169
+ * the universe is being disposed and its session detaches, which removes
170
+ * them along with the page.
171
+ */
172
+ dispose() {
173
+ this.#records.clear();
174
+ }
175
+ #currentLogpoints() {
176
+ return [...this.#records].map(breakpoint => this.#snapshot(breakpoint));
177
+ }
178
+ /**
179
+ * Reads the current state of a breakpoint, so that the result reflects
180
+ * the normalized location and the re-binding done by the breakpoint
181
+ * manager after reloads. The breakpoint manager may have moved a
182
+ * generated location to its original source, so whether the logpoint is
183
+ * source-mapped follows from the breakpoint, not from the request.
184
+ */
185
+ #snapshot(breakpoint) {
186
+ const sourceMapped = [...breakpoint.getUiSourceCodes()].some(isFromSourceMap);
187
+ const modelBreakpoint = breakpoint.modelBreakpoint(this.#debuggerModel);
188
+ const generatedLocations = [];
189
+ if (breakpoint.bound()) {
190
+ for (const position of modelBreakpoint?.currentState ?? []) {
191
+ generatedLocations.push({
192
+ url: position.url,
193
+ lineNumber: position.lineNumber + 1,
194
+ columnNumber: (position.columnNumber ?? 0) + 1,
195
+ });
196
+ }
197
+ }
198
+ const columnNumber = breakpoint.columnNumber();
199
+ return {
200
+ url: breakpoint.url(),
201
+ lineNumber: breakpoint.lineNumber() + 1,
202
+ columnNumber: columnNumber === undefined ? undefined : columnNumber + 1,
203
+ expression: breakpoint.condition(),
204
+ sourceMapped,
205
+ generatedLocations,
206
+ };
207
+ }
208
+ /**
209
+ * Source maps load lazily by default, so after a reload the original
210
+ * sources of a rebuilt bundle would not re-appear in the workspace and a
211
+ * logpoint on an original source file could not be re-bound to the new
212
+ * generated location. While logpoints exist, source maps are loaded
213
+ * eagerly instead, like DevTools does with the Sources panel open.
214
+ */
215
+ #setLazySourceMapLoading(lazy) {
216
+ this.#universe.settings
217
+ .resolve(DevTools.SourceMapManager.lazyLoadingSettingDescriptor)
218
+ .set(lazy);
219
+ }
220
+ /**
221
+ * Finds the workspace source code for a script or original source URL,
222
+ * loading source maps if needed. Throws a descriptive error, including
223
+ * sources with the same file name, when nothing matches.
224
+ */
225
+ async #findUISourceCode(url) {
226
+ // Source maps are loaded lazily in the DevTools universe and original
227
+ // sources only appear in the workspace once their source map has loaded.
228
+ let match = this.#matchUISourceCode(url);
229
+ let timedOut = false;
230
+ if (match && !isFromSourceMap(match)) {
231
+ // The script may be a transpiled file served at the URL of its original
232
+ // source (see #matchUISourceCode); its source map decides.
233
+ const matchedUrl = match.url();
234
+ const scripts = this.#debuggerModel
235
+ .scripts()
236
+ .filter(script => script.sourceURL === matchedUrl);
237
+ if (scripts.some(script => script.sourceMapURL)) {
238
+ const signal = AbortSignal.timeout(SOURCE_MAP_LOAD_TIMEOUT_MS);
239
+ await waitForSourceMaps(this.#debuggerModel, scripts, signal);
240
+ match = this.#matchUISourceCode(url);
241
+ if (signal.aborted && (!match || !isFromSourceMap(match))) {
242
+ // Setting the logpoint on the served script would use its line
243
+ // numbers instead of those of the original source.
244
+ throw new Error(`The source map of ${matchedUrl} was still loading after ${SOURCE_MAP_LOAD_TIMEOUT_MS / 1000} seconds; retry.`);
245
+ }
246
+ }
247
+ }
248
+ else if (!match) {
249
+ const signal = AbortSignal.timeout(SOURCE_MAP_LOAD_TIMEOUT_MS);
250
+ await waitForSourceMaps(this.#debuggerModel, this.#debuggerModel.scripts(), signal);
251
+ timedOut = signal.aborted;
252
+ match = this.#matchUISourceCode(url);
253
+ }
254
+ if (match) {
255
+ return match;
256
+ }
257
+ const name = baseName(url);
258
+ const similar = [...this.#pageSourceCodes()]
259
+ .map(uiSourceCode => uiSourceCode.url())
260
+ .filter(candidate => baseName(candidate) === name)
261
+ .slice(0, 5);
262
+ throw new Error(`No parsed script or original source file matches "${url}". The url must match the URL of a script as served to the browser or an original source file from a source map, exactly or as a path suffix.${timedOut ? ` Some source maps were still loading after ${SOURCE_MAP_LOAD_TIMEOUT_MS / 1000} seconds; retry if the file comes from a source map.` : ''}${similar.length ? ` Sources with the same file name: ${similar.join(', ')}.` : ''}`);
263
+ }
264
+ /** The scripts and source-map sources of the page in the workspace. */
265
+ *#pageSourceCodes() {
266
+ for (const uiSourceCode of this.#universe.workspace.uiSourceCodes()) {
267
+ const projectType = uiSourceCode.project().type();
268
+ // Content scripts of extensions are left out: they are not part of the
269
+ // page under development and would make path-suffix matches ambiguous.
270
+ if (projectType === DevTools.Workspace.Workspace.projectTypes.Network) {
271
+ yield uiSourceCode;
272
+ }
273
+ }
274
+ }
275
+ /**
276
+ * Finds the workspace source code for a script or original source URL.
277
+ * An exact URL match wins; otherwise the URL is matched as a path suffix so
278
+ * that users can pass "src/app.ts" without knowing the bundler's URL
279
+ * scheme. An ambiguous suffix match is reported as an error.
280
+ */
281
+ #matchUISourceCode(url) {
282
+ const candidates = new Map();
283
+ for (const uiSourceCode of this.#pageSourceCodes()) {
284
+ const candidateUrl = uiSourceCode.url();
285
+ if (candidateUrl !== url && !candidateUrl.endsWith('/' + url)) {
286
+ continue;
287
+ }
288
+ // Dev servers like Vite serve a transpiled file at the URL of its
289
+ // original source, with a source map pointing back at that same URL.
290
+ // The original source then has the line numbers of the file on disk,
291
+ // so it wins over the script served at the same URL.
292
+ const existing = candidates.get(candidateUrl);
293
+ if (!existing || isFromSourceMap(uiSourceCode)) {
294
+ candidates.set(candidateUrl, uiSourceCode);
295
+ }
296
+ }
297
+ const exact = candidates.get(url);
298
+ if (exact) {
299
+ return exact;
300
+ }
301
+ if (candidates.size > 1) {
302
+ throw new Error(`"${url}" matches multiple sources: ${[...candidates.keys()].join(', ')}. Specify a longer path.`);
303
+ }
304
+ return candidates.values().next().value ?? null;
305
+ }
306
+ }
307
+ //# sourceMappingURL=LogpointManager.js.map