browsertrack 0.2.1 → 0.2.2

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 (52) hide show
  1. package/AGENTS.md +9 -6
  2. package/README.md +2 -0
  3. package/dist/{chunk-INXDWPJW.js → chunk-4HRLW6YF.js} +161 -106
  4. package/dist/chunk-4HRLW6YF.js.map +1 -0
  5. package/dist/{chunk-3HOXPTM2.js → chunk-AYSVE6NG.js} +808 -53
  6. package/dist/chunk-AYSVE6NG.js.map +1 -0
  7. package/dist/{chunk-6VA7GBAO.js → chunk-QRZ57ME3.js} +70 -2
  8. package/dist/chunk-QRZ57ME3.js.map +1 -0
  9. package/dist/{chunk-464D4U2U.js → chunk-TWEYRBDU.js} +279 -43
  10. package/dist/chunk-TWEYRBDU.js.map +1 -0
  11. package/dist/cli/index.js +1038 -545
  12. package/dist/cli/index.js.map +1 -1
  13. package/dist/client/index.cjs +184 -104
  14. package/dist/client/index.js +2 -2
  15. package/dist/client.iife.js +9 -9
  16. package/dist/core/index.d.ts +24 -1
  17. package/dist/core/index.js +9 -1
  18. package/dist/daemon/index.d.ts +2 -2
  19. package/dist/daemon/index.js +6 -8
  20. package/dist/index.d.ts +2 -2
  21. package/dist/index.js +14 -7
  22. package/dist/mcp/index.d.ts +1 -1
  23. package/dist/mcp/index.js +7 -4
  24. package/dist/{server-DiVmTrIR.d.ts → server-DjV7RWQM.d.ts} +9 -1
  25. package/docs/cli.md +4 -1
  26. package/docs/getting-started.md +60 -6
  27. package/docs/mcp-reference.md +42 -0
  28. package/package.json +1 -1
  29. package/packages/cli/src/index.ts +247 -151
  30. package/packages/client/src/interceptors/navigation.ts +38 -26
  31. package/packages/client/src/interceptors/network.ts +22 -17
  32. package/packages/client/src/notes/inspector.ts +81 -48
  33. package/packages/client/src/source/resolver.ts +9 -3
  34. package/packages/client/src/transport/websocket.ts +23 -18
  35. package/packages/core/src/index.ts +1 -0
  36. package/packages/core/src/safety.ts +86 -0
  37. package/packages/daemon/src/server/daemon.ts +7 -1
  38. package/packages/daemon/src/server/http.ts +125 -5
  39. package/packages/daemon/src/server/ws.ts +33 -29
  40. package/packages/daemon/src/storage/db.ts +57 -35
  41. package/packages/mcp/src/handlers.ts +114 -45
  42. package/packages/mcp/src/server.ts +202 -2
  43. package/test/core/safety.test.ts +106 -0
  44. package/test/daemon/storage.test.ts +36 -0
  45. package/test/e2e/daemon-mcp-e2e.test.ts +10 -0
  46. package/test/mcp/auto-start.test.ts +87 -0
  47. package/dist/chunk-3HOXPTM2.js.map +0 -1
  48. package/dist/chunk-464D4U2U.js.map +0 -1
  49. package/dist/chunk-6VA7GBAO.js.map +0 -1
  50. package/dist/chunk-7OCOQGDN.js +0 -635
  51. package/dist/chunk-7OCOQGDN.js.map +0 -1
  52. package/dist/chunk-INXDWPJW.js.map +0 -1
package/AGENTS.md CHANGED
@@ -99,10 +99,9 @@ When an error or unexpected behavior occurs in the frontend:
99
99
 
100
100
  If BrowserTrack is not yet active in your project:
101
101
 
102
- ### 1. Ensure Daemon is Running
103
- ```bash
104
- browsertrack start
105
- ```
102
+ ### 1. Zero-Config Daemon Auto-Boot (Singleton Daemon)
103
+ You do **not** need to manually run `browsertrack start`! When your IDE connects to BrowserTrack via MCP (`browsertrack mcp`), the background HTTP/WebSocket server is launched **automatically** on `http://127.0.0.1:7331` as a **singleton daemon**. Multiple windows, workspaces, or sub-agent sessions seamlessly share this single daemon without spawning duplicates or conflicting on ports.
104
+ *(You can still manually run `browsertrack start` or `browsertrack stop` if you want a standalone terminal process).*
106
105
 
107
106
  ### 2. Connect the Browser Client
108
107
  - **Via HTML script tag:**
@@ -116,17 +115,21 @@ browsertrack start
116
115
 
117
116
  ### 3. MCP Configuration
118
117
  Ensure BrowserTrack MCP is configured in your editor:
118
+
119
119
  ```json
120
120
  {
121
121
  "mcpServers": {
122
122
  "browsertrack": {
123
- "command": "browsertrack",
124
- "args": ["mcp"]
123
+ "command": "npx",
124
+ "args": ["-y", "browsertrack@latest", "mcp"]
125
125
  }
126
126
  }
127
127
  }
128
128
  ```
129
129
 
130
+ > **Note on PATH errors (`executable file not found in $PATH`):**
131
+ > If your IDE cannot find `npx` or `browsertrack` in `$PATH` (common in GUI apps on macOS using NVM or Homebrew), provide the absolute path to `npx` (e.g. `~/.nvm/versions/node/v20.x.x/bin/npx`) along with `PATH` in the `env` object.
132
+
130
133
  ---
131
134
 
132
135
  ## 💡 Best Practices for AI Agents
package/README.md CHANGED
@@ -17,6 +17,8 @@ browsertrack start
17
17
 
18
18
  This starts the local diagnostics server at `http://127.0.0.1:7331` (WebSocket at `ws://127.0.0.1:7331`).
19
19
 
20
+ > **⚡ Zero-Config Auto-Start**: If you configure BrowserTrack via MCP in your AI editor (Cursor, Antigravity, Claude Code), you do **not** need to manually run `browsertrack start`! The MCP server automatically launches and manages a **single background daemon (singleton)** on port `7331`. Multiple windows or sessions share this single daemon without duplicate processes or port conflicts.
21
+
20
22
  ### 2. Connect Your Web Project
21
23
 
22
24
  #### Option A: Zero-install script tag (Plain HTML / Vite / Next.js)
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  getSemanticSelector,
3
3
  redactUrl,
4
+ safeJsonStringify,
4
5
  truncate
5
- } from "./chunk-6VA7GBAO.js";
6
+ } from "./chunk-QRZ57ME3.js";
6
7
 
7
8
  // packages/client/src/breadcrumbs.ts
8
9
  var BreadcrumbBuffer = class {
@@ -78,7 +79,9 @@ function resolveReactComponent(el) {
78
79
  }
79
80
  }
80
81
  let curr = hostFiber;
81
- while (curr) {
82
+ let depth = 0;
83
+ while (curr && depth < 50) {
84
+ depth++;
82
85
  const type = curr.type;
83
86
  const name = getReactComponentName(type);
84
87
  if (name && !name.startsWith("html:") && name !== "Fragment") {
@@ -133,7 +136,9 @@ function resolveVueComponent(el) {
133
136
  const sourceFile = type.__file;
134
137
  const hierarchy = [];
135
138
  let curr = vueParent;
136
- while (curr) {
139
+ let depth = 0;
140
+ while (curr && depth < 50) {
141
+ depth++;
137
142
  const cType = curr.type || {};
138
143
  const cName = cType.name || cType.__name || cType.displayName;
139
144
  if (cName && !hierarchy.includes(cName)) {
@@ -170,7 +175,9 @@ function resolveVueComponent(el) {
170
175
  function resolveSvelteComponent(el) {
171
176
  try {
172
177
  let curr = el;
173
- while (curr) {
178
+ let depth = 0;
179
+ while (curr && depth < 50) {
180
+ depth++;
174
181
  const meta = curr.__svelte_meta;
175
182
  if (meta && meta.loc) {
176
183
  return {
@@ -709,12 +716,16 @@ function setupConsoleInterceptors(onConsole) {
709
716
  function setupNavigationInterceptors(onNavigation) {
710
717
  if (typeof window === "undefined" || typeof history === "undefined") return () => {
711
718
  };
712
- let currentUrl = redactUrl(window.location.href);
713
- onNavigation({
714
- to: currentUrl,
715
- type: "initial",
716
- timestamp: Date.now()
717
- });
719
+ let currentUrl = "";
720
+ try {
721
+ currentUrl = redactUrl(window.location.href);
722
+ onNavigation({
723
+ to: currentUrl,
724
+ type: "initial",
725
+ timestamp: Date.now()
726
+ });
727
+ } catch {
728
+ }
718
729
  const originalPushState = history.pushState;
719
730
  const originalReplaceState = history.replaceState;
720
731
  history.pushState = function(data, unused, url) {
@@ -750,26 +761,32 @@ function setupNavigationInterceptors(onNavigation) {
750
761
  return result;
751
762
  };
752
763
  const onPopState = () => {
753
- const from = currentUrl;
754
- const to = redactUrl(window.location.href);
755
- currentUrl = to;
756
- onNavigation({
757
- from,
758
- to,
759
- type: "popstate",
760
- timestamp: Date.now()
761
- });
764
+ try {
765
+ const from = currentUrl;
766
+ const to = redactUrl(window.location.href);
767
+ currentUrl = to;
768
+ onNavigation({
769
+ from,
770
+ to,
771
+ type: "popstate",
772
+ timestamp: Date.now()
773
+ });
774
+ } catch {
775
+ }
762
776
  };
763
777
  const onHashChange = () => {
764
- const from = currentUrl;
765
- const to = redactUrl(window.location.href);
766
- currentUrl = to;
767
- onNavigation({
768
- from,
769
- to,
770
- type: "hashchange",
771
- timestamp: Date.now()
772
- });
778
+ try {
779
+ const from = currentUrl;
780
+ const to = redactUrl(window.location.href);
781
+ currentUrl = to;
782
+ onNavigation({
783
+ from,
784
+ to,
785
+ type: "hashchange",
786
+ timestamp: Date.now()
787
+ });
788
+ } catch {
789
+ }
773
790
  };
774
791
  window.addEventListener("popstate", onPopState);
775
792
  window.addEventListener("hashchange", onHashChange);
@@ -806,31 +823,38 @@ function setupNetworkInterceptors(onNetwork) {
806
823
  urlStr = "unknown_url";
807
824
  }
808
825
  const safeUrl = redactUrl(urlStr);
826
+ let response;
809
827
  try {
810
- const response = await originalFetch.apply(window, [input, init2]);
811
- const durationMs = Date.now() - startTime;
812
- onNetwork({
813
- url: safeUrl,
814
- method,
815
- status: response.status,
816
- statusText: response.statusText,
817
- durationMs,
818
- timestamp: startTime
819
- });
820
- return response;
828
+ response = await originalFetch.apply(window, [input, init2]);
821
829
  } catch (err) {
822
- const durationMs = Date.now() - startTime;
830
+ const durationMs2 = Date.now() - startTime;
823
831
  const isAbort = err?.name === "AbortError";
832
+ try {
833
+ onNetwork({
834
+ url: safeUrl,
835
+ method,
836
+ durationMs: durationMs2,
837
+ error: err?.message || "Network request failed",
838
+ aborted: isAbort,
839
+ timestamp: startTime
840
+ });
841
+ } catch {
842
+ }
843
+ throw err;
844
+ }
845
+ const durationMs = Date.now() - startTime;
846
+ try {
824
847
  onNetwork({
825
848
  url: safeUrl,
826
849
  method,
850
+ status: response.status,
851
+ statusText: response.statusText,
827
852
  durationMs,
828
- error: err?.message || "Network request failed",
829
- aborted: isAbort,
830
853
  timestamp: startTime
831
854
  });
832
- throw err;
855
+ } catch {
833
856
  }
857
+ return response;
834
858
  };
835
859
  cleanups.push(() => {
836
860
  window.fetch = originalFetch;
@@ -1216,15 +1240,18 @@ var WebSocketTransport = class {
1216
1240
  }
1217
1241
  }
1218
1242
  send(payload) {
1219
- const raw = JSON.stringify(payload);
1220
- if (this.isConnected()) {
1221
- try {
1222
- this.ws.send(raw);
1223
- } catch {
1243
+ try {
1244
+ const raw = safeJsonStringify(payload);
1245
+ if (this.isConnected()) {
1246
+ try {
1247
+ this.ws.send(raw);
1248
+ } catch {
1249
+ this.enqueue(raw);
1250
+ }
1251
+ } else {
1224
1252
  this.enqueue(raw);
1225
1253
  }
1226
- } else {
1227
- this.enqueue(raw);
1254
+ } catch {
1228
1255
  }
1229
1256
  }
1230
1257
  enqueue(raw) {
@@ -1249,17 +1276,17 @@ var WebSocketTransport = class {
1249
1276
  }
1250
1277
  sendHello() {
1251
1278
  if (typeof window === "undefined") return;
1252
- const hello = {
1253
- type: "hello",
1254
- origin: window.location.origin,
1255
- url: window.location.href,
1256
- title: document.title,
1257
- userAgent: navigator.userAgent,
1258
- timestamp: Date.now(),
1259
- projectId: this.projectId
1260
- };
1261
1279
  try {
1262
- this.ws.send(JSON.stringify(hello));
1280
+ const hello = {
1281
+ type: "hello",
1282
+ origin: window.location.origin,
1283
+ url: window.location.href,
1284
+ title: document.title,
1285
+ userAgent: navigator.userAgent,
1286
+ timestamp: Date.now(),
1287
+ projectId: this.projectId
1288
+ };
1289
+ this.ws.send(safeJsonStringify(hello));
1263
1290
  } catch {
1264
1291
  }
1265
1292
  }
@@ -2247,9 +2274,15 @@ var NoteInspector = class {
2247
2274
  this.markersContainer.innerHTML = "";
2248
2275
  if (!this.showMarkers || this.isHidden) return;
2249
2276
  const currentPath = window.location.pathname;
2250
- const activeNotes = this.savedNotes.filter(
2251
- (n) => n.status === "OPEN" && (n.route === currentPath || !n.route || n.route === "/" || window.location.href.includes(n.route))
2252
- );
2277
+ const activeNotes = this.savedNotes.filter((n) => {
2278
+ if (n.status !== "OPEN") return false;
2279
+ if (!n.route) return true;
2280
+ if (n.route === currentPath) return true;
2281
+ if (n.route.includes("#") || n.route.includes("?")) {
2282
+ return window.location.href.includes(n.route);
2283
+ }
2284
+ return false;
2285
+ });
2253
2286
  const pageNotes = [];
2254
2287
  activeNotes.forEach((note, index) => {
2255
2288
  if (note.type === "page") {
@@ -2474,65 +2507,83 @@ var NoteInspector = class {
2474
2507
  }
2475
2508
  setupListeners() {
2476
2509
  const onMouseMove = (e) => {
2477
- if (this.isHidden || this.modalOverlay || this.cardOverlay || this.isDraggingRegion || this.activeMode === "region") return;
2478
- const path = e.composedPath ? e.composedPath() : [];
2479
- if (this.container && path.includes(this.container)) {
2480
- this.hideHighlight();
2481
- return;
2482
- }
2483
- if (e.altKey || this.activeMode === "element") {
2484
- const target = document.elementFromPoint(e.clientX, e.clientY);
2485
- if (target && target !== this.container && !this.container?.contains(target)) {
2486
- this.hoveredElement = target;
2487
- this.updateHighlight(target);
2510
+ try {
2511
+ if (this.isHidden || this.modalOverlay || this.cardOverlay || this.isDraggingRegion || this.activeMode === "region") return;
2512
+ const path = e.composedPath ? e.composedPath() : [];
2513
+ if (this.container && path.includes(this.container)) {
2514
+ this.hideHighlight();
2488
2515
  return;
2489
2516
  }
2490
- }
2491
- if (!e.altKey && this.activeMode !== "element") {
2492
- this.hideHighlight();
2517
+ if (e.altKey || this.activeMode === "element") {
2518
+ const target = document.elementFromPoint(e.clientX, e.clientY);
2519
+ if (target && target !== this.container && !this.container?.contains(target)) {
2520
+ this.hoveredElement = target;
2521
+ this.updateHighlight(target);
2522
+ return;
2523
+ }
2524
+ }
2525
+ if (!e.altKey && this.activeMode !== "element") {
2526
+ this.hideHighlight();
2527
+ }
2528
+ } catch {
2493
2529
  }
2494
2530
  };
2495
2531
  const onClick = (e) => {
2496
- if (this.isHidden || this.modalOverlay || this.cardOverlay) return;
2497
- const path = e.composedPath ? e.composedPath() : [];
2498
- if (this.container && path.includes(this.container)) {
2499
- return;
2500
- }
2501
- if (this.activeMode === "region") return;
2502
- if (e.altKey || this.activeMode === "element") {
2503
- e.preventDefault();
2504
- e.stopPropagation();
2505
- const target = this.hoveredElement || document.elementFromPoint(e.clientX, e.clientY);
2506
- if (target && target !== this.container && !this.container?.contains(target)) {
2507
- this.selectedElement = target;
2508
- this.openNoteEditor(target, "element");
2509
- if (this.activeMode === "element" && !this.activeScenario) {
2510
- this.setMode("idle");
2532
+ try {
2533
+ if (this.isHidden || this.modalOverlay || this.cardOverlay) return;
2534
+ const path = e.composedPath ? e.composedPath() : [];
2535
+ if (this.container && path.includes(this.container)) {
2536
+ return;
2537
+ }
2538
+ if (this.activeMode === "region") return;
2539
+ if (e.altKey || this.activeMode === "element") {
2540
+ e.preventDefault();
2541
+ e.stopPropagation();
2542
+ const target = this.hoveredElement || document.elementFromPoint(e.clientX, e.clientY);
2543
+ if (target && target !== this.container && !this.container?.contains(target)) {
2544
+ this.selectedElement = target;
2545
+ this.openNoteEditor(target, "element");
2546
+ if (this.activeMode === "element" && !this.activeScenario) {
2547
+ this.setMode("idle");
2548
+ }
2511
2549
  }
2512
2550
  }
2551
+ } catch {
2513
2552
  }
2514
2553
  };
2515
2554
  const onKeyDown = (e) => {
2516
- if (e.key === "Escape") {
2517
- if (this.cardOverlay && this.cardOverlay.parentElement && this.shadowRoot) {
2518
- this.shadowRoot.removeChild(this.cardOverlay);
2519
- this.cardOverlay = null;
2520
- } else if (this.activeMode === "region" || this.activeMode === "element") {
2521
- if (!this.activeScenario) {
2522
- this.setMode("idle");
2555
+ try {
2556
+ if (e.key === "Escape") {
2557
+ if (this.cardOverlay && this.cardOverlay.parentElement && this.shadowRoot) {
2558
+ this.shadowRoot.removeChild(this.cardOverlay);
2559
+ this.cardOverlay = null;
2560
+ } else if (this.activeMode === "region" || this.activeMode === "element") {
2561
+ if (!this.activeScenario) {
2562
+ this.setMode("idle");
2563
+ }
2523
2564
  }
2524
2565
  }
2566
+ } catch {
2525
2567
  }
2526
2568
  };
2527
2569
  const onKeyUp = (e) => {
2528
- if (e.key === "Alt" && !this.modalOverlay && !this.cardOverlay && this.activeMode !== "element") {
2529
- this.hideHighlight();
2570
+ try {
2571
+ if (e.key === "Alt" && !this.modalOverlay && !this.cardOverlay && this.activeMode !== "element") {
2572
+ this.hideHighlight();
2573
+ }
2574
+ } catch {
2530
2575
  }
2531
2576
  };
2532
2577
  const onScrollOrResize = () => {
2533
- requestAnimationFrame(() => {
2534
- this.updateMarkerPositions();
2535
- });
2578
+ try {
2579
+ requestAnimationFrame(() => {
2580
+ try {
2581
+ this.updateMarkerPositions();
2582
+ } catch {
2583
+ }
2584
+ });
2585
+ } catch {
2586
+ }
2536
2587
  };
2537
2588
  window.addEventListener("mousemove", onMouseMove, { capture: true, passive: true });
2538
2589
  window.addEventListener("click", onClick, { capture: true });
@@ -2994,7 +3045,11 @@ var NoteInspector = class {
2994
3045
  region.width,
2995
3046
  region.height
2996
3047
  );
2997
- resolve(canvas.toDataURL("image/png"));
3048
+ try {
3049
+ resolve(canvas.toDataURL("image/png"));
3050
+ } catch {
3051
+ resolve(dataUrl);
3052
+ }
2998
3053
  };
2999
3054
  img.onerror = () => resolve(dataUrl);
3000
3055
  img.src = dataUrl;
@@ -3284,4 +3339,4 @@ export {
3284
3339
  init,
3285
3340
  getClient
3286
3341
  };
3287
- //# sourceMappingURL=chunk-INXDWPJW.js.map
3342
+ //# sourceMappingURL=chunk-4HRLW6YF.js.map