browsertrack 0.0.1 → 0.1.1

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 (110) hide show
  1. package/.github/workflows/docs.yml +54 -0
  2. package/AGENTS.md +133 -0
  3. package/README.md +143 -0
  4. package/dist/chunk-6VA7GBAO.js +246 -0
  5. package/dist/chunk-6VA7GBAO.js.map +1 -0
  6. package/dist/chunk-G2Y3CXCY.js +659 -0
  7. package/dist/chunk-G2Y3CXCY.js.map +1 -0
  8. package/dist/chunk-ILRYKMME.js +1228 -0
  9. package/dist/chunk-ILRYKMME.js.map +1 -0
  10. package/dist/chunk-SKCMT2DE.js +2641 -0
  11. package/dist/chunk-SKCMT2DE.js.map +1 -0
  12. package/dist/chunk-SPCIROIU.js +616 -0
  13. package/dist/chunk-SPCIROIU.js.map +1 -0
  14. package/dist/cli/index.js +2942 -0
  15. package/dist/cli/index.js.map +1 -0
  16. package/dist/client/index.cjs +2780 -0
  17. package/dist/client/index.d.ts +211 -0
  18. package/dist/client/index.js +20 -0
  19. package/dist/client/index.js.map +1 -0
  20. package/dist/client.iife.js +630 -0
  21. package/dist/core/index.d.ts +86 -0
  22. package/dist/core/index.js +37 -0
  23. package/dist/core/index.js.map +1 -0
  24. package/dist/daemon/index.d.ts +58 -0
  25. package/dist/daemon/index.js +32 -0
  26. package/dist/daemon/index.js.map +1 -0
  27. package/dist/engine-CeT9URuN.d.ts +161 -0
  28. package/dist/index.d.ts +11 -0
  29. package/dist/index.js +56 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/mcp/index.d.ts +11 -0
  32. package/dist/mcp/index.js +12 -0
  33. package/dist/mcp/index.js.map +1 -0
  34. package/dist/notes-CBvN91Wf.d.ts +260 -0
  35. package/dist/projects-CY8ungMt.d.ts +99 -0
  36. package/dist/server-Dd8NX2Mk.d.ts +55 -0
  37. package/docboot.config.js +16 -0
  38. package/docs/cli.md +51 -0
  39. package/docs/closed-loop-verification.md +57 -0
  40. package/docs/getting-started.md +122 -0
  41. package/docs/incidents-diagnostics.md +61 -0
  42. package/docs/index.md +46 -0
  43. package/docs/mcp-reference.md +128 -0
  44. package/docs/security-privacy.md +32 -0
  45. package/docs/visual-notes.md +67 -0
  46. package/examples/test-app/index.html +299 -0
  47. package/package.json +56 -7
  48. package/packages/cli/src/index.ts +413 -0
  49. package/packages/client/package.json +28 -0
  50. package/packages/client/src/breadcrumbs.ts +30 -0
  51. package/packages/client/src/client.ts +278 -0
  52. package/packages/client/src/commands/handler.ts +301 -0
  53. package/packages/client/src/config.ts +37 -0
  54. package/packages/client/src/index.ts +50 -0
  55. package/packages/client/src/interceptors/console.ts +68 -0
  56. package/packages/client/src/interceptors/interaction.ts +119 -0
  57. package/packages/client/src/interceptors/navigation.ts +93 -0
  58. package/packages/client/src/interceptors/network.ts +165 -0
  59. package/packages/client/src/interceptors/runtime.ts +65 -0
  60. package/packages/client/src/notes/inspector.ts +1553 -0
  61. package/packages/client/src/screenshot/browser-script-driver.ts +192 -0
  62. package/packages/client/src/screenshot/driver.ts +14 -0
  63. package/packages/client/src/transport/websocket.ts +194 -0
  64. package/packages/client/tsconfig.json +8 -0
  65. package/packages/core/package.json +26 -0
  66. package/packages/core/src/fingerprint.ts +120 -0
  67. package/packages/core/src/index.ts +9 -0
  68. package/packages/core/src/redaction.ts +174 -0
  69. package/packages/core/src/selector.ts +77 -0
  70. package/packages/core/src/types/commands.ts +101 -0
  71. package/packages/core/src/types/events.ts +108 -0
  72. package/packages/core/src/types/incidents.ts +54 -0
  73. package/packages/core/src/types/notes.ts +106 -0
  74. package/packages/core/src/types/probes.ts +44 -0
  75. package/packages/core/src/types/projects.ts +20 -0
  76. package/packages/core/tsconfig.json +8 -0
  77. package/packages/daemon/src/config.ts +29 -0
  78. package/packages/daemon/src/incidents/engine.ts +211 -0
  79. package/packages/daemon/src/index.ts +18 -0
  80. package/packages/daemon/src/notes/engine.ts +92 -0
  81. package/packages/daemon/src/notes/verification.ts +191 -0
  82. package/packages/daemon/src/server/daemon.ts +112 -0
  83. package/packages/daemon/src/server/http.ts +165 -0
  84. package/packages/daemon/src/server/ws.ts +246 -0
  85. package/packages/daemon/src/session/manager.ts +147 -0
  86. package/packages/daemon/src/storage/db.ts +742 -0
  87. package/packages/daemon/src/storage/screenshot-store.ts +49 -0
  88. package/packages/daemon/src/verification/engine.ts +264 -0
  89. package/packages/mcp/src/handlers.ts +398 -0
  90. package/packages/mcp/src/index.ts +3 -0
  91. package/packages/mcp/src/server.ts +86 -0
  92. package/packages/mcp/src/tools.ts +236 -0
  93. package/src/index.ts +4 -0
  94. package/test/client/interceptors.test.ts +130 -0
  95. package/test/core/fingerprint.test.ts +53 -0
  96. package/test/core/notes.test.ts +66 -0
  97. package/test/core/redaction.test.ts +60 -0
  98. package/test/daemon/incident-engine.test.ts +98 -0
  99. package/test/daemon/notes-storage.test.ts +130 -0
  100. package/test/daemon/notes-verification.test.ts +135 -0
  101. package/test/daemon/storage.test.ts +123 -0
  102. package/test/daemon/verification-engine.test.ts +88 -0
  103. package/test/e2e/daemon-mcp-e2e.test.ts +153 -0
  104. package/test/e2e/visual-notes-e2e.test.ts +205 -0
  105. package/test/mcp/handlers.test.ts +116 -0
  106. package/test/mcp/notes.test.ts +107 -0
  107. package/tsconfig.base.json +17 -0
  108. package/tsconfig.json +26 -0
  109. package/tsup.config.ts +54 -0
  110. package/vitest.config.ts +9 -0
@@ -0,0 +1,54 @@
1
+ # Generated by Docboot
2
+ name: Deploy Docs
3
+
4
+ on:
5
+ push:
6
+ branches:
7
+ - main
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: read
12
+ pages: write
13
+ id-token: write
14
+
15
+ concurrency:
16
+ group: pages
17
+ cancel-in-progress: true
18
+
19
+ jobs:
20
+ build:
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - name: Checkout repository
24
+ uses: actions/checkout@v4
25
+ with:
26
+ fetch-depth: 0
27
+
28
+ - name: Setup Node.js
29
+ uses: actions/setup-node@v4
30
+ with:
31
+ node-version: '22'
32
+ cache: 'npm'
33
+
34
+ - name: Install dependencies
35
+ run: npm ci
36
+
37
+ - name: Build documentation
38
+ run: npx -y docboot build
39
+
40
+ - name: Upload Pages artifact
41
+ uses: actions/upload-pages-artifact@v3
42
+ with:
43
+ path: dist-docs
44
+
45
+ deploy:
46
+ needs: build
47
+ runs-on: ubuntu-latest
48
+ environment:
49
+ name: github-pages
50
+ url: ${{ steps.deployment.outputs.page_url }}
51
+ steps:
52
+ - name: Deploy to GitHub Pages
53
+ id: deployment
54
+ uses: actions/deploy-pages@v4
package/AGENTS.md ADDED
@@ -0,0 +1,133 @@
1
+ # BrowserTrack — AI Agent Guidelines & MCP Instructions
2
+
3
+ This repository is integrated with **BrowserTrack**, a local browser diagnostics daemon and Model Context Protocol (MCP) bridge. As an AI coding agent, you have direct access to live browser runtime errors, console logs, network failures, user breadcrumbs, visual notes, screenshots, and automated closed-loop verification tools.
4
+
5
+ ---
6
+
7
+ ## 🎯 When to Use BrowserTrack Tools
8
+
9
+ Always check BrowserTrack when:
10
+ 1. **The user reports a bug, crash, or visual issue in the browser.**
11
+ 2. **The user leaves visual annotations or design feedback** on elements/pages (Alt+Click notes).
12
+ 3. **You modify frontend code** (React, Vue, HTML, CSS, JS) and need to verify whether runtime errors or visual regressions occurred.
13
+ 4. **You need real-time page state, live DOM hierarchy, or screenshots** without asking the user to manually copy-paste logs or send screenshots.
14
+
15
+ ---
16
+
17
+ ## 🧭 Core Agent Workflows
18
+
19
+ ### 1. Visual Notes & UI Fixes Workflow
20
+
21
+ When the user asks to fix a UI issue, layout bug, or style note:
22
+
23
+ ```mermaid
24
+ graph TD
25
+ A[list_notes] --> B[get_note noteId]
26
+ B --> C[Analyze DOM Context, Viewport & Screenshot]
27
+ C --> D[Modify CSS / Component Code]
28
+ D --> E[verify_note noteId]
29
+ E --> F{Resolved?}
30
+ F -->|Yes| G[resolve_note noteId]
31
+ F -->|No| D
32
+ ```
33
+
34
+ 1. **Find open notes:** Call `list_notes({ status: "OPEN" })`.
35
+ 2. **Inspect note context:** Call `get_note({ noteId: "note_xxx" })`.
36
+ - Review the target selector (`target.selector`), viewport dimensions, bounding box (`boundingRect`), and screenshot path.
37
+ - Check if layout overflow (`overflow-x`, min-width, flex wrapping) is causing the issue.
38
+ 3. **Apply the fix:** Edit the corresponding CSS, style rules, or component markup.
39
+ 4. **Verify the fix:** Call `verify_note({ noteId: "note_xxx" })`.
40
+ - This automatically reloads the browser, checks element visibility, tests viewport overflow, and captures an after-fix screenshot.
41
+ 5. **Close the note:** If verified, call `resolve_note({ noteId: "note_xxx" })`.
42
+
43
+ ---
44
+
45
+ ### 2. Runtime Error & Crash Diagnostics Workflow
46
+
47
+ When an error or unexpected behavior occurs in the frontend:
48
+
49
+ 1. **List open incidents:** Call `list_incidents({ status: "OPEN" })`.
50
+ 2. **Retrieve incident details:** Call `get_incident({ incidentId: "inc_xxx" })`.
51
+ - Inspect stack traces, exact source line/column, last user interactions, and failing network calls.
52
+ 3. **Inspect interaction timeline (if needed):** Call `get_breadcrumbs({ incidentId: "inc_xxx" })` to see what clicks, inputs, or navigations led to the failure.
53
+ 4. **Inspect failed network requests (if needed):** Call `get_network_failures()` to examine 4xx/5xx status codes, request URLs, and response details.
54
+ 5. **Apply code fix:** Edit the source file causing the bug.
55
+ 6. **Trigger closed-loop verification:** Call `verify_incident({ incidentId: "inc_xxx" })`.
56
+ - Optionally supply `expect` probes (e.g. `[{ type: "no_incident" }, { type: "element_visible", selector: ".my-component" }]`).
57
+
58
+ ---
59
+
60
+ ## 🛠️ Complete MCP Tool Reference
61
+
62
+ ### 📋 Incidents & Diagnostics
63
+ | MCP Tool | Purpose | Key Arguments |
64
+ | :--- | :--- | :--- |
65
+ | `list_incidents` | List grouped browser runtime errors and unhandled rejections | `status`, `severity`, `projectId`, `limit` |
66
+ | `get_incident` | Retrieve stack trace, breadcrumbs, screenshot, and context for an incident | `incidentId` *(required)* |
67
+ | `get_console` | Fetch recent console errors, warnings, and logs | `sessionId`, `limit` |
68
+ | `get_network_failures` | Fetch recent 4xx/5xx HTTP failures and aborted requests | `sessionId`, `limit` |
69
+ | `get_breadcrumbs` | Fetch chronological user interaction and navigation history | `incidentId`, `sessionId`, `limit` |
70
+ | `get_page_state` | Query current URL, document title, readyState, and active element | `sessionId` |
71
+ | `capture_element` | Capture an on-demand screenshot of a selector or full viewport | `selector`, `sessionId` |
72
+
73
+ ### 📝 Visual Notes & Layout Inspector
74
+ | MCP Tool | Purpose | Key Arguments |
75
+ | :--- | :--- | :--- |
76
+ | `list_notes` | List visual annotations left on elements/regions | `status`, `projectId`, `limit` |
77
+ | `get_note` | Get full note details (DOM context, selector, bounding rect, screenshot) | `noteId` *(required)* |
78
+ | `capture_note_context` | Inspect live DOM bounding box, styles, and overflow on an element | `selector` *(required)*, `sessionId` |
79
+ | `verify_note` | Run automated layout verification and before/after comparison | `noteId` *(required)*, `observationWindowMs` |
80
+ | `get_note_verification`| Retrieve latest layout verification verdict and screenshots | `noteId` *(required)* |
81
+ | `resolve_note` | Mark a visual note as RESOLVED | `noteId` *(required)* |
82
+ | `reopen_note` | Reopen a previously resolved visual note | `noteId` *(required)* |
83
+
84
+ ### 🎛️ Session & Project Management
85
+ | MCP Tool | Purpose | Key Arguments |
86
+ | :--- | :--- | :--- |
87
+ | `list_projects` | List all registered projects and mapped paths | *(none)* |
88
+ | `list_sessions` | List active browser tabs connected via WebSocket | `projectId`, `activeOnly` |
89
+ | `verify_incident` | Verify bug fix by reloading browser and evaluating probes | `incidentId` *(required)*, `expect`, `route` |
90
+ | `get_verification` | Retrieve verification verdict (`VERIFIED`, `FAILED`, `INCONCLUSIVE`) | `incidentId` *(required)* |
91
+
92
+ ---
93
+
94
+ ## ⚡ Quick Setup for Projects
95
+
96
+ If BrowserTrack is not yet active in your project:
97
+
98
+ ### 1. Ensure Daemon is Running
99
+ ```bash
100
+ browsertrack start
101
+ ```
102
+
103
+ ### 2. Connect the Browser Client
104
+ - **Via HTML script tag:**
105
+ ```html
106
+ <script src="http://127.0.0.1:7331/client.js"></script>
107
+ ```
108
+ - **Or via NPM package:**
109
+ ```typescript
110
+ import 'browsertrack/client';
111
+ ```
112
+
113
+ ### 3. MCP Configuration
114
+ Ensure BrowserTrack MCP is configured in your editor:
115
+ ```json
116
+ {
117
+ "mcpServers": {
118
+ "browsertrack": {
119
+ "command": "browsertrack",
120
+ "args": ["mcp"]
121
+ }
122
+ }
123
+ }
124
+ ```
125
+
126
+ ---
127
+
128
+ ## 💡 Best Practices for AI Agents
129
+
130
+ - **Don't guess error causes:** Always check `get_incident` or `get_breadcrumbs` before modifying code.
131
+ - **Verify after fixing:** Call `verify_incident` or `verify_note` after code edits to confirm the fix works in the real browser.
132
+ - **Respect privacy redaction:** Passwords, auth tokens, and sensitive query parameters are redacted by BrowserTrack by design.
133
+ - **Check responsive viewports:** When resolving layout notes, observe the `viewport` dimensions reported in `get_note`.
package/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # BrowserTrack 🔍
2
+
3
+ > **Local browser diagnostics shared with coding agents through MCP.**
4
+
5
+ BrowserTrack is a lightweight, framework-agnostic local development daemon and Model Context Protocol (MCP) bridge. It automatically collects browser-side runtime errors, unhandled rejections, console logs, network failures, user breadcrumbs, and DOM snapshots—making them instantly accessible to AI coding assistants (Antigravity, Cursor, Claude Code) for rapid debugging and closed-loop verification.
6
+
7
+ ---
8
+
9
+ ## ⚡ Quick Start
10
+
11
+ ### 1. Start the Local Daemon
12
+
13
+ ```bash
14
+ npm install -g browsertrack
15
+ browsertrack start
16
+ ```
17
+
18
+ This starts the local diagnostics server at `http://127.0.0.1:7331` (WebSocket at `ws://127.0.0.1:7331`).
19
+
20
+ ### 2. Connect Your Web Project
21
+
22
+ #### Option A: Zero-install script tag (Plain HTML / Vite / Next.js)
23
+
24
+ ```html
25
+ <script src="http://127.0.0.1:7331/client.js"></script>
26
+ ```
27
+
28
+ #### Option B: NPM package (React / Vue / Svelte / Astro)
29
+
30
+ ```bash
31
+ npm install -D browsertrack
32
+ ```
33
+
34
+ In your main application entry point (e.g. `main.ts` or `index.tsx`):
35
+
36
+ ```typescript
37
+ import 'browsertrack/client';
38
+ ```
39
+
40
+ ---
41
+
42
+ ## 🤖 Coding Agent (MCP) Setup
43
+
44
+ Add BrowserTrack to your MCP client configuration:
45
+
46
+ ### Cursor (`~/.cursor/mcp.json`) & Claude Code
47
+ ```json
48
+ {
49
+ "mcpServers": {
50
+ "browsertrack": {
51
+ "command": "npx",
52
+ "args": ["browsertrack", "mcp"]
53
+ }
54
+ }
55
+ }
56
+ ```
57
+
58
+ ### Antigravity (`~/.gemini/antigravity-ide/mcp_config.json`)
59
+ ```json
60
+ {
61
+ "mcpServers": {
62
+ "browsertrack": {
63
+ "command": "browsertrack",
64
+ "args": ["mcp"]
65
+ }
66
+ }
67
+ }
68
+ ```
69
+
70
+ ---
71
+
72
+ ## 🛠️ MCP Tools Exposed to AI Agents
73
+
74
+ | MCP Tool | Description |
75
+ | :--- | :--- |
76
+ | `list_projects` | Lists registered local development projects and mapped filesystem paths. |
77
+ | `list_sessions` | Lists active browser tabs connected over WebSocket. |
78
+ | `list_incidents` | Lists grouped error incidents with occurrences, severity, and status. |
79
+ | `get_incident` | Retrieves concise, high-signal context (stack trace, breadcrumbs, network failures, last interacted element, error screenshot). |
80
+ | `get_console` | Retrieves recent console errors/warnings. |
81
+ | `get_network_failures` | Retrieves recent 4xx/5xx HTTP failures and aborted requests. |
82
+ | `get_breadcrumbs` | Returns user interaction timeline leading up to an error. |
83
+ | `get_page_state` | Queries live DOM/URL state from the active browser tab. |
84
+ | `capture_element` | Captures an on-demand screenshot of a specific element or full page. |
85
+ | `verify_incident` | Runs closed-loop verification: reloads browser, checks if error reoccurs, executes probes, captures after-screenshot, and returns verdict (`VERIFIED`, `FAILED`, `INCONCLUSIVE`). |
86
+ | `get_verification` | Returns latest verification result and before/after screenshot artifacts. |
87
+
88
+ ---
89
+
90
+ ## 💻 CLI Commands
91
+
92
+ ```bash
93
+ browsertrack start # Start local daemon
94
+ browsertrack start --verbose # Verbose logging mode
95
+ browsertrack stop # Stop background daemon
96
+ browsertrack status # Check status & connected sessions
97
+
98
+ browsertrack projects # List tracked projects
99
+ browsertrack project add myapp --origin http://localhost:5173 --path ~/projects/myapp
100
+
101
+ browsertrack errors # List open runtime errors
102
+ browsertrack errors --project myapp
103
+
104
+ browsertrack clear # Clear stored logs & incidents
105
+ browsertrack mcp # Run MCP server over stdio
106
+ ```
107
+
108
+ ---
109
+
110
+ ## 🛡️ Security & Privacy
111
+
112
+ - **Localhost Only**: Daemon strictly binds to `127.0.0.1`.
113
+ - **Sensitive Key Redaction**: Powered by `@visulima/redact` — `Authorization`, `Cookie`, `Set-Cookie`, passwords, API tokens, bearer tokens, JWTs, AWS credentials, credit cards, SSNs, and secret query parameters are automatically sanitized.
114
+ - **No Form Values**: Form inputs (`<input>`, `<textarea>`) are never inspected or collected.
115
+ - **Zero Request/Response Body**: Payload bodies are disabled by default.
116
+
117
+ ---
118
+
119
+ ## 🏗️ Architecture
120
+
121
+ ```text
122
+ Browser Client (Vanilla / React / Vue / Vite)
123
+ ↕ WebSocket (ws://127.0.0.1:7331)
124
+ Local Daemon (SQLite Persistence + Verification Engine)
125
+ ↕ MCP (stdio)
126
+ Coding Agents (Antigravity / Cursor / Claude Code)
127
+ ```
128
+
129
+ ---
130
+
131
+ ## 🧪 Testing
132
+
133
+ Run the automated test suite:
134
+
135
+ ```bash
136
+ npm test
137
+ ```
138
+
139
+ Start the interactive fixture app for manual and end-to-end verification:
140
+
141
+ ```bash
142
+ npx serve examples/test-app
143
+ ```
@@ -0,0 +1,246 @@
1
+ // packages/core/src/fingerprint.ts
2
+ function normalizeErrorMessage(message) {
3
+ if (!message) return "unknown_error";
4
+ return message.trim().replace(/0x[0-9a-fA-F]+/g, "0x<HEX>").replace(/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g, "<UUID>").replace(/\?[tv]=[\w.-]+/g, "").replace(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z?/g, "<TIMESTAMP>").replace(/#\d+/g, "#<ID>").replace(/\s+/g, " ");
5
+ }
6
+ function normalizeSourceFile(filename) {
7
+ if (!filename) return "unknown_source";
8
+ let cleaned = filename.trim();
9
+ try {
10
+ if (cleaned.startsWith("http://") || cleaned.startsWith("https://")) {
11
+ const url = new URL(cleaned);
12
+ cleaned = url.pathname;
13
+ }
14
+ } catch {
15
+ cleaned = cleaned.replace(/^https?:\/\/[^/]+/, "");
16
+ }
17
+ cleaned = cleaned.split("?")[0].split("#")[0];
18
+ cleaned = cleaned.replace(/\\/g, "/");
19
+ return cleaned || "unknown_source";
20
+ }
21
+ function extractSourceFromStack(stack) {
22
+ if (!stack) return null;
23
+ const lines = stack.split("\n");
24
+ for (const line of lines) {
25
+ const match = line.match(/(?:at\s+(?:.*?\s+\()?)?(https?:\/\/[^\s)]+|file:\/\/[^\s)]+|\/[^\s)]+):(\d+):(\d+)\)?/);
26
+ if (match) {
27
+ return {
28
+ file: normalizeSourceFile(match[1]),
29
+ line: parseInt(match[2], 10),
30
+ column: parseInt(match[3], 10)
31
+ };
32
+ }
33
+ }
34
+ return null;
35
+ }
36
+ function djb2Hash(str) {
37
+ let hash = 5381;
38
+ for (let i = 0; i < str.length; i++) {
39
+ hash = hash * 33 ^ str.charCodeAt(i);
40
+ }
41
+ return (hash >>> 0).toString(16).padStart(8, "0");
42
+ }
43
+ function computeFingerprint(input) {
44
+ const normType = (input.type || "Error").trim().toLowerCase();
45
+ const normMsg = normalizeErrorMessage(input.message);
46
+ let sourceFile = normalizeSourceFile(input.sourceFile);
47
+ let line = input.line || 0;
48
+ if ((sourceFile === "unknown_source" || line === 0) && input.stack) {
49
+ const extracted = extractSourceFromStack(input.stack);
50
+ if (extracted) {
51
+ sourceFile = extracted.file;
52
+ line = extracted.line;
53
+ }
54
+ }
55
+ const rawKey = `${normType}::${normMsg}::${sourceFile}::${line}`;
56
+ const hash = djb2Hash(rawKey);
57
+ return `fp_${hash}`;
58
+ }
59
+
60
+ // packages/core/src/redaction.ts
61
+ import { redact as visulimaRedact, standardRules } from "@visulima/redact";
62
+ var REDACTED_PLACEHOLDER = "[REDACTED]";
63
+ var SENSITIVE_KEY_PATTERNS = [
64
+ /^authorization$/i,
65
+ /^cookie$/i,
66
+ /^set-cookie$/i,
67
+ /password/i,
68
+ /token/i,
69
+ /secret/i,
70
+ /api[-_]?key/i,
71
+ /access[-_]?token/i,
72
+ /refresh[-_]?token/i,
73
+ /credentials/i,
74
+ /private[-_]?key/i,
75
+ /ssn/i,
76
+ /credit[-_]?card/i,
77
+ /cvv/i
78
+ ];
79
+ var SENSITIVE_QUERY_PARAMS = [
80
+ "token",
81
+ "auth",
82
+ "key",
83
+ "apikey",
84
+ "api_key",
85
+ "secret",
86
+ "password",
87
+ "access_token",
88
+ "refresh_token",
89
+ "code",
90
+ "signature"
91
+ ];
92
+ var BROWSER_SECURITY_RULES = [
93
+ { deep: true, key: "password", replacement: REDACTED_PLACEHOLDER },
94
+ { deep: true, key: "secret", replacement: REDACTED_PLACEHOLDER },
95
+ { deep: true, key: "token", replacement: REDACTED_PLACEHOLDER },
96
+ { deep: true, key: "authorization", replacement: REDACTED_PLACEHOLDER },
97
+ { deep: true, key: "cookie", replacement: REDACTED_PLACEHOLDER },
98
+ { deep: true, key: "set-cookie", replacement: REDACTED_PLACEHOLDER },
99
+ { deep: true, key: "apikey", replacement: REDACTED_PLACEHOLDER },
100
+ { deep: true, key: "api_key", replacement: REDACTED_PLACEHOLDER },
101
+ { deep: true, key: "creditcard", pattern: /(?:\d[ -]*?){13,16}/, replacement: REDACTED_PLACEHOLDER },
102
+ { deep: true, key: "cvv", replacement: REDACTED_PLACEHOLDER },
103
+ { deep: true, key: "ssn", pattern: /\b\d{3}-\d{2}-\d{4}\b/, replacement: REDACTED_PLACEHOLDER },
104
+ { deep: true, key: "awsid", pattern: /\bAKIA[0-9A-Z]{16}\b/, replacement: REDACTED_PLACEHOLDER },
105
+ { deep: true, key: "awskey", pattern: /\b[0-9a-zA-Z/+]{40}\b/, replacement: REDACTED_PLACEHOLDER },
106
+ { deep: true, key: "jwt", pattern: /\beyJ[0-9a-zA-Z_\-]*\.[0-9a-zA-Z_\-]*\.[0-9a-zA-Z_\-]*\b/, replacement: REDACTED_PLACEHOLDER },
107
+ { deep: true, key: "slack_token", pattern: /\bxox[baprs]-[0-9a-zA-Z]{10,48}\b/, replacement: REDACTED_PLACEHOLDER }
108
+ ];
109
+ function isSensitiveKey(key) {
110
+ if (!key) return false;
111
+ const cleaned = key.replace(/[-_]/g, "");
112
+ return SENSITIVE_KEY_PATTERNS.some((pattern) => pattern.test(key) || pattern.test(cleaned));
113
+ }
114
+ function redactUrl(rawUrl) {
115
+ if (!rawUrl) return rawUrl;
116
+ try {
117
+ const isRelative = !rawUrl.startsWith("http://") && !rawUrl.startsWith("https://") && !rawUrl.startsWith("ws://") && !rawUrl.startsWith("wss://");
118
+ const base = "http://localhost";
119
+ const parsed = new URL(rawUrl, base);
120
+ let changed = false;
121
+ for (const param of SENSITIVE_QUERY_PARAMS) {
122
+ if (parsed.searchParams.has(param)) {
123
+ parsed.searchParams.set(param, REDACTED_PLACEHOLDER);
124
+ changed = true;
125
+ }
126
+ }
127
+ for (const key of Array.from(parsed.searchParams.keys())) {
128
+ if (isSensitiveKey(key)) {
129
+ parsed.searchParams.set(key, REDACTED_PLACEHOLDER);
130
+ changed = true;
131
+ }
132
+ }
133
+ if (!changed) return rawUrl;
134
+ let result = isRelative ? parsed.pathname + parsed.search + parsed.hash : parsed.toString();
135
+ result = result.replace(/%5BREDACTED%5D/g, REDACTED_PLACEHOLDER);
136
+ return result;
137
+ } catch {
138
+ let safe = rawUrl;
139
+ for (const param of SENSITIVE_QUERY_PARAMS) {
140
+ const reg = new RegExp(`([?&]${param}=)[^&#]+`, "gi");
141
+ safe = safe.replace(reg, `$1${REDACTED_PLACEHOLDER}`);
142
+ }
143
+ return safe;
144
+ }
145
+ }
146
+ function redactHeaders(headers) {
147
+ if (!headers || typeof headers !== "object") return {};
148
+ const safe = {};
149
+ for (const [key, value] of Object.entries(headers)) {
150
+ if (isSensitiveKey(key)) {
151
+ safe[key] = REDACTED_PLACEHOLDER;
152
+ } else {
153
+ safe[key] = value;
154
+ }
155
+ }
156
+ return safe;
157
+ }
158
+ function redactSensitiveData(data, maxDepth = 6, currentDepth = 0) {
159
+ if (data === null || data === void 0) return data;
160
+ if (typeof data !== "object") return data;
161
+ if (currentDepth > maxDepth) return "[DEPTH_EXCEEDED]";
162
+ if (Array.isArray(data)) {
163
+ return data.map((item) => redactSensitiveData(item, maxDepth, currentDepth + 1));
164
+ }
165
+ const result = {};
166
+ for (const [key, value] of Object.entries(data)) {
167
+ if (typeof value === "object" && value !== null) {
168
+ result[key] = redactSensitiveData(value, maxDepth, currentDepth + 1);
169
+ } else if (isSensitiveKey(key)) {
170
+ result[key] = REDACTED_PLACEHOLDER;
171
+ } else if (typeof value === "string") {
172
+ if (value.startsWith("http://") || value.startsWith("https://") || value.includes("?")) {
173
+ result[key] = redactUrl(value);
174
+ } else {
175
+ try {
176
+ result[key] = visulimaRedact(value, BROWSER_SECURITY_RULES);
177
+ } catch {
178
+ result[key] = value;
179
+ }
180
+ }
181
+ } else {
182
+ result[key] = value;
183
+ }
184
+ }
185
+ return result;
186
+ }
187
+
188
+ // packages/core/src/selector.ts
189
+ function getSemanticSelector(element, options = {}) {
190
+ if (!element || !element.tagName) return "unknown";
191
+ const maxClasses = options.maxClasses ?? 2;
192
+ const testId = element.getAttribute("data-testid") || element.getAttribute("data-test") || element.getAttribute("data-cy") || element.getAttribute("data-qa");
193
+ if (testId) {
194
+ return `[data-testid="${testId}"]`;
195
+ }
196
+ const id = element.id;
197
+ if (id && !/^[0-9]+$/.test(id) && !/[0-9a-f]{8}-[0-9a-f]{4}/i.test(id)) {
198
+ return `#${id}`;
199
+ }
200
+ const ariaLabel = element.getAttribute("aria-label");
201
+ if (ariaLabel && ariaLabel.length < 30) {
202
+ return `${element.tagName.toLowerCase()}[aria-label="${ariaLabel}"]`;
203
+ }
204
+ const nameAttr = element.getAttribute("name");
205
+ if (nameAttr) {
206
+ return `${element.tagName.toLowerCase()}[name="${nameAttr}"]`;
207
+ }
208
+ const tag = element.tagName.toLowerCase();
209
+ const classList = Array.from(element.classList || []).filter((c) => !c.startsWith("css-") && !c.startsWith("_") && !/^[a-z0-9]{5,}$/i.test(c)).slice(0, maxClasses);
210
+ if (classList.length > 0) {
211
+ const classStr = classList.map((c) => `.${c}`).join("");
212
+ return `${tag}${classStr}`;
213
+ }
214
+ if (element.parentElement && element.parentElement !== document.body && element.parentElement !== document.documentElement) {
215
+ const parentSelector = getSemanticSelector(element.parentElement, { maxClasses: 1 });
216
+ if (parentSelector && parentSelector !== "unknown" && !parentSelector.includes(">")) {
217
+ return `${parentSelector} > ${tag}`;
218
+ }
219
+ }
220
+ return tag;
221
+ }
222
+ function truncate(str, maxLength = 200) {
223
+ if (!str) return "";
224
+ if (str.length <= maxLength) return str;
225
+ return str.slice(0, maxLength) + "...";
226
+ }
227
+
228
+ export {
229
+ normalizeErrorMessage,
230
+ normalizeSourceFile,
231
+ extractSourceFromStack,
232
+ computeFingerprint,
233
+ visulimaRedact,
234
+ standardRules,
235
+ REDACTED_PLACEHOLDER,
236
+ SENSITIVE_KEY_PATTERNS,
237
+ SENSITIVE_QUERY_PARAMS,
238
+ BROWSER_SECURITY_RULES,
239
+ isSensitiveKey,
240
+ redactUrl,
241
+ redactHeaders,
242
+ redactSensitiveData,
243
+ getSemanticSelector,
244
+ truncate
245
+ };
246
+ //# sourceMappingURL=chunk-6VA7GBAO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../packages/core/src/fingerprint.ts","../packages/core/src/redaction.ts","../packages/core/src/selector.ts"],"sourcesContent":["/**\n * Error fingerprinting and normalization utilities\n */\n\nexport interface FingerprintInput {\n type: string;\n message: string;\n sourceFile?: string;\n line?: number;\n column?: number;\n stack?: string;\n}\n\n/**\n * Normalizes error messages by stripping dynamic content like UUIDs, timestamps,\n * hex addresses, and file timestamps.\n */\nexport function normalizeErrorMessage(message: string): string {\n if (!message) return 'unknown_error';\n\n return message\n // Normalize string representation\n .trim()\n // Replace hex addresses (e.g. 0x7ffee4b2a8)\n .replace(/0x[0-9a-fA-F]+/g, '0x<HEX>')\n // Replace UUIDs\n .replace(/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g, '<UUID>')\n // Replace timestamp query params like ?t=1719283748 or ?v=1.2.3\n .replace(/\\?[tv]=[\\w.-]+/g, '')\n // Replace ISO timestamps\n .replace(/\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?Z?/g, '<TIMESTAMP>')\n // Replace numbers in quotes/brackets if likely IDs (e.g., user \"12345\")\n .replace(/#\\d+/g, '#<ID>')\n // Normalize whitespaces\n .replace(/\\s+/g, ' ');\n}\n\n/**\n * Normalizes source filenames by removing origin and cache-busting query strings.\n */\nexport function normalizeSourceFile(filename?: string): string {\n if (!filename) return 'unknown_source';\n\n let cleaned = filename.trim();\n // Strip protocol and origin if present (e.g. http://localhost:5173/src/App.tsx?t=123)\n try {\n if (cleaned.startsWith('http://') || cleaned.startsWith('https://')) {\n const url = new URL(cleaned);\n cleaned = url.pathname;\n }\n } catch {\n // fallback string manipulation\n cleaned = cleaned.replace(/^https?:\\/\\/[^/]+/, '');\n }\n\n // Remove query parameters\n cleaned = cleaned.split('?')[0].split('#')[0];\n\n // Normalize windows path separators\n cleaned = cleaned.replace(/\\\\/g, '/');\n\n return cleaned || 'unknown_source';\n}\n\n/**\n * Extracts source location from stack if not explicitly provided\n */\nexport function extractSourceFromStack(stack?: string): { file: string; line: number; column?: number } | null {\n if (!stack) return null;\n\n const lines = stack.split('\\n');\n for (const line of lines) {\n // Match standard stack line: at functionName (http://localhost:5173/src/main.tsx:15:3) or at http://...:15:3\n const match = line.match(/(?:at\\s+(?:.*?\\s+\\()?)?(https?:\\/\\/[^\\s)]+|file:\\/\\/[^\\s)]+|\\/[^\\s)]+):(\\d+):(\\d+)\\)?/);\n if (match) {\n return {\n file: normalizeSourceFile(match[1]),\n line: parseInt(match[2], 10),\n column: parseInt(match[3], 10),\n };\n }\n }\n return null;\n}\n\n/**\n * Fast synchronous djb2-based hash for universal client/server runtime\n */\nfunction djb2Hash(str: string): string {\n let hash = 5381;\n for (let i = 0; i < str.length; i++) {\n hash = (hash * 33) ^ str.charCodeAt(i);\n }\n // Convert to unsigned 32-bit hex string\n return (hash >>> 0).toString(16).padStart(8, '0');\n}\n\n/**\n * Computes a deterministic fingerprint for an error\n */\nexport function computeFingerprint(input: FingerprintInput): string {\n const normType = (input.type || 'Error').trim().toLowerCase();\n const normMsg = normalizeErrorMessage(input.message);\n\n let sourceFile = normalizeSourceFile(input.sourceFile);\n let line = input.line || 0;\n\n if ((sourceFile === 'unknown_source' || line === 0) && input.stack) {\n const extracted = extractSourceFromStack(input.stack);\n if (extracted) {\n sourceFile = extracted.file;\n line = extracted.line;\n }\n }\n\n const rawKey = `${normType}::${normMsg}::${sourceFile}::${line}`;\n const hash = djb2Hash(rawKey);\n\n return `fp_${hash}`;\n}\n","import { redact as visulimaRedact, standardRules, type Rules } from '@visulima/redact';\n\n/**\n * Security and Redaction Utilities powered by @visulima/redact\n */\n\nexport const REDACTED_PLACEHOLDER = '[REDACTED]';\n\nexport const SENSITIVE_KEY_PATTERNS = [\n /^authorization$/i,\n /^cookie$/i,\n /^set-cookie$/i,\n /password/i,\n /token/i,\n /secret/i,\n /api[-_]?key/i,\n /access[-_]?token/i,\n /refresh[-_]?token/i,\n /credentials/i,\n /private[-_]?key/i,\n /ssn/i,\n /credit[-_]?card/i,\n /cvv/i,\n];\n\nexport const SENSITIVE_QUERY_PARAMS = [\n 'token',\n 'auth',\n 'key',\n 'apikey',\n 'api_key',\n 'secret',\n 'password',\n 'access_token',\n 'refresh_token',\n 'code',\n 'signature',\n];\n\n/**\n * Developer security rules for @visulima/redact\n */\nexport const BROWSER_SECURITY_RULES: Rules = [\n { deep: true, key: 'password', replacement: REDACTED_PLACEHOLDER },\n { deep: true, key: 'secret', replacement: REDACTED_PLACEHOLDER },\n { deep: true, key: 'token', replacement: REDACTED_PLACEHOLDER },\n { deep: true, key: 'authorization', replacement: REDACTED_PLACEHOLDER },\n { deep: true, key: 'cookie', replacement: REDACTED_PLACEHOLDER },\n { deep: true, key: 'set-cookie', replacement: REDACTED_PLACEHOLDER },\n { deep: true, key: 'apikey', replacement: REDACTED_PLACEHOLDER },\n { deep: true, key: 'api_key', replacement: REDACTED_PLACEHOLDER },\n { deep: true, key: 'creditcard', pattern: /(?:\\d[ -]*?){13,16}/, replacement: REDACTED_PLACEHOLDER },\n { deep: true, key: 'cvv', replacement: REDACTED_PLACEHOLDER },\n { deep: true, key: 'ssn', pattern: /\\b\\d{3}-\\d{2}-\\d{4}\\b/, replacement: REDACTED_PLACEHOLDER },\n { deep: true, key: 'awsid', pattern: /\\bAKIA[0-9A-Z]{16}\\b/, replacement: REDACTED_PLACEHOLDER },\n { deep: true, key: 'awskey', pattern: /\\b[0-9a-zA-Z/+]{40}\\b/, replacement: REDACTED_PLACEHOLDER },\n { deep: true, key: 'jwt', pattern: /\\beyJ[0-9a-zA-Z_\\-]*\\.[0-9a-zA-Z_\\-]*\\.[0-9a-zA-Z_\\-]*\\b/, replacement: REDACTED_PLACEHOLDER },\n { deep: true, key: 'slack_token', pattern: /\\bxox[baprs]-[0-9a-zA-Z]{10,48}\\b/, replacement: REDACTED_PLACEHOLDER },\n];\n\nexport { visulimaRedact, standardRules };\nexport type { Rules };\n\n/**\n * Checks if a property name matches any sensitive pattern\n */\nexport function isSensitiveKey(key: string): boolean {\n if (!key) return false;\n const cleaned = key.replace(/[-_]/g, '');\n return SENSITIVE_KEY_PATTERNS.some((pattern) => pattern.test(key) || pattern.test(cleaned));\n}\n\n/**\n * Redacts sensitive query parameters from a URL string\n */\nexport function redactUrl(rawUrl: string): string {\n if (!rawUrl) return rawUrl;\n\n try {\n const isRelative =\n !rawUrl.startsWith('http://') &&\n !rawUrl.startsWith('https://') &&\n !rawUrl.startsWith('ws://') &&\n !rawUrl.startsWith('wss://');\n const base = 'http://localhost';\n const parsed = new URL(rawUrl, base);\n\n let changed = false;\n for (const param of SENSITIVE_QUERY_PARAMS) {\n if (parsed.searchParams.has(param)) {\n parsed.searchParams.set(param, REDACTED_PLACEHOLDER);\n changed = true;\n }\n }\n\n // Also check all query params against isSensitiveKey\n for (const key of Array.from(parsed.searchParams.keys())) {\n if (isSensitiveKey(key)) {\n parsed.searchParams.set(key, REDACTED_PLACEHOLDER);\n changed = true;\n }\n }\n\n if (!changed) return rawUrl;\n\n let result = isRelative ? parsed.pathname + parsed.search + parsed.hash : parsed.toString();\n // Normalize URL-encoded brackets %5BREDACTED%5D back to [REDACTED]\n result = result.replace(/%5BREDACTED%5D/g, REDACTED_PLACEHOLDER);\n return result;\n } catch {\n // If URL parsing fails, perform regex replace\n let safe = rawUrl;\n for (const param of SENSITIVE_QUERY_PARAMS) {\n const reg = new RegExp(`([?&]${param}=)[^&#]+`, 'gi');\n safe = safe.replace(reg, `$1${REDACTED_PLACEHOLDER}`);\n }\n return safe;\n }\n}\n\n/**\n * Redacts sensitive HTTP headers\n */\nexport function redactHeaders(headers: Record<string, string>): Record<string, string> {\n if (!headers || typeof headers !== 'object') return {};\n\n const safe: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (isSensitiveKey(key)) {\n safe[key] = REDACTED_PLACEHOLDER;\n } else {\n safe[key] = value;\n }\n }\n return safe;\n}\n\n/**\n * Deeply redacts sensitive keys and payload data from an object or array,\n * leveraging @visulima/redact rules combined with BrowserTrack URL/header sanitizers.\n */\nexport function redactSensitiveData<T = any>(data: T, maxDepth = 6, currentDepth = 0): T {\n if (data === null || data === undefined) return data;\n if (typeof data !== 'object') return data;\n if (currentDepth > maxDepth) return '[DEPTH_EXCEEDED]' as any;\n\n if (Array.isArray(data)) {\n return data.map((item) => redactSensitiveData(item, maxDepth, currentDepth + 1)) as any;\n }\n\n const result: Record<string, any> = {};\n for (const [key, value] of Object.entries(data as Record<string, any>)) {\n if (typeof value === 'object' && value !== null) {\n result[key] = redactSensitiveData(value, maxDepth, currentDepth + 1);\n } else if (isSensitiveKey(key)) {\n result[key] = REDACTED_PLACEHOLDER;\n } else if (typeof value === 'string') {\n // Check if value is a URL containing secrets\n if (value.startsWith('http://') || value.startsWith('https://') || value.includes('?')) {\n result[key] = redactUrl(value);\n } else {\n try {\n result[key] = visulimaRedact(value, BROWSER_SECURITY_RULES);\n } catch {\n result[key] = value;\n }\n }\n } else {\n result[key] = value;\n }\n }\n\n return result as T;\n}\n","/**\n * Semantic element selector generation and inspection\n */\n\nexport interface SelectorOptions {\n maxClasses?: number;\n maxOuterHTMLLength?: number;\n maxInnerTextLength?: number;\n}\n\n/**\n * Builds a clean, human-readable semantic selector for a DOM element.\n * Prioritizes data-testid/data-test, id, semantic tag + specific class, or concise parent path.\n */\nexport function getSemanticSelector(element: HTMLElement | Element, options: SelectorOptions = {}): string {\n if (!element || !element.tagName) return 'unknown';\n\n const maxClasses = options.maxClasses ?? 2;\n\n // 1. Check for standard test attributes\n const testId =\n element.getAttribute('data-testid') ||\n element.getAttribute('data-test') ||\n element.getAttribute('data-cy') ||\n element.getAttribute('data-qa');\n\n if (testId) {\n return `[data-testid=\"${testId}\"]`;\n }\n\n // 2. Check for meaningful ID (skip dynamic UUID/numeric IDs)\n const id = element.id;\n if (id && !/^[0-9]+$/.test(id) && !/[0-9a-f]{8}-[0-9a-f]{4}/i.test(id)) {\n return `#${id}`;\n }\n\n // 3. Check for aria-label or name on buttons/inputs\n const ariaLabel = element.getAttribute('aria-label');\n if (ariaLabel && ariaLabel.length < 30) {\n return `${element.tagName.toLowerCase()}[aria-label=\"${ariaLabel}\"]`;\n }\n\n const nameAttr = element.getAttribute('name');\n if (nameAttr) {\n return `${element.tagName.toLowerCase()}[name=\"${nameAttr}\"]`;\n }\n\n // 4. Tag + curated classes\n const tag = element.tagName.toLowerCase();\n const classList = Array.from(element.classList || [])\n .filter((c) => !c.startsWith('css-') && !c.startsWith('_') && !/^[a-z0-9]{5,}$/i.test(c)) // filter hashed utility classes\n .slice(0, maxClasses);\n\n if (classList.length > 0) {\n const classStr = classList.map((c) => `.${c}`).join('');\n return `${tag}${classStr}`;\n }\n\n // 5. If generic element (div, span, p) without attributes, try parent context\n if (element.parentElement && element.parentElement !== document.body && element.parentElement !== document.documentElement) {\n const parentSelector = getSemanticSelector(element.parentElement, { maxClasses: 1 });\n if (parentSelector && parentSelector !== 'unknown' && !parentSelector.includes('>')) {\n return `${parentSelector} > ${tag}`;\n }\n }\n\n return tag;\n}\n\n/**\n * Truncates string to specified max length with ellipsis\n */\nexport function truncate(str: string | undefined | null, maxLength = 200): string {\n if (!str) return '';\n if (str.length <= maxLength) return str;\n return str.slice(0, maxLength) + '...';\n}\n"],"mappings":";AAiBO,SAAS,sBAAsB,SAAyB;AAC7D,MAAI,CAAC,QAAS,QAAO;AAErB,SAAO,QAEJ,KAAK,EAEL,QAAQ,mBAAmB,SAAS,EAEpC,QAAQ,gFAAgF,QAAQ,EAEhG,QAAQ,mBAAmB,EAAE,EAE7B,QAAQ,kDAAkD,aAAa,EAEvE,QAAQ,SAAS,OAAO,EAExB,QAAQ,QAAQ,GAAG;AACxB;AAKO,SAAS,oBAAoB,UAA2B;AAC7D,MAAI,CAAC,SAAU,QAAO;AAEtB,MAAI,UAAU,SAAS,KAAK;AAE5B,MAAI;AACF,QAAI,QAAQ,WAAW,SAAS,KAAK,QAAQ,WAAW,UAAU,GAAG;AACnE,YAAM,MAAM,IAAI,IAAI,OAAO;AAC3B,gBAAU,IAAI;AAAA,IAChB;AAAA,EACF,QAAQ;AAEN,cAAU,QAAQ,QAAQ,qBAAqB,EAAE;AAAA,EACnD;AAGA,YAAU,QAAQ,MAAM,GAAG,EAAE,CAAC,EAAE,MAAM,GAAG,EAAE,CAAC;AAG5C,YAAU,QAAQ,QAAQ,OAAO,GAAG;AAEpC,SAAO,WAAW;AACpB;AAKO,SAAS,uBAAuB,OAAwE;AAC7G,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,QAAQ,MAAM,MAAM,IAAI;AAC9B,aAAW,QAAQ,OAAO;AAExB,UAAM,QAAQ,KAAK,MAAM,uFAAuF;AAChH,QAAI,OAAO;AACT,aAAO;AAAA,QACL,MAAM,oBAAoB,MAAM,CAAC,CAAC;AAAA,QAClC,MAAM,SAAS,MAAM,CAAC,GAAG,EAAE;AAAA,QAC3B,QAAQ,SAAS,MAAM,CAAC,GAAG,EAAE;AAAA,MAC/B;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAKA,SAAS,SAAS,KAAqB;AACrC,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,WAAQ,OAAO,KAAM,IAAI,WAAW,CAAC;AAAA,EACvC;AAEA,UAAQ,SAAS,GAAG,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAClD;AAKO,SAAS,mBAAmB,OAAiC;AAClE,QAAM,YAAY,MAAM,QAAQ,SAAS,KAAK,EAAE,YAAY;AAC5D,QAAM,UAAU,sBAAsB,MAAM,OAAO;AAEnD,MAAI,aAAa,oBAAoB,MAAM,UAAU;AACrD,MAAI,OAAO,MAAM,QAAQ;AAEzB,OAAK,eAAe,oBAAoB,SAAS,MAAM,MAAM,OAAO;AAClE,UAAM,YAAY,uBAAuB,MAAM,KAAK;AACpD,QAAI,WAAW;AACb,mBAAa,UAAU;AACvB,aAAO,UAAU;AAAA,IACnB;AAAA,EACF;AAEA,QAAM,SAAS,GAAG,QAAQ,KAAK,OAAO,KAAK,UAAU,KAAK,IAAI;AAC9D,QAAM,OAAO,SAAS,MAAM;AAE5B,SAAO,MAAM,IAAI;AACnB;;;ACvHA,SAAS,UAAU,gBAAgB,qBAAiC;AAM7D,IAAM,uBAAuB;AAE7B,IAAM,yBAAyB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,yBAAyB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAKO,IAAM,yBAAgC;AAAA,EAC3C,EAAE,MAAM,MAAM,KAAK,YAAY,aAAa,qBAAqB;AAAA,EACjE,EAAE,MAAM,MAAM,KAAK,UAAU,aAAa,qBAAqB;AAAA,EAC/D,EAAE,MAAM,MAAM,KAAK,SAAS,aAAa,qBAAqB;AAAA,EAC9D,EAAE,MAAM,MAAM,KAAK,iBAAiB,aAAa,qBAAqB;AAAA,EACtE,EAAE,MAAM,MAAM,KAAK,UAAU,aAAa,qBAAqB;AAAA,EAC/D,EAAE,MAAM,MAAM,KAAK,cAAc,aAAa,qBAAqB;AAAA,EACnE,EAAE,MAAM,MAAM,KAAK,UAAU,aAAa,qBAAqB;AAAA,EAC/D,EAAE,MAAM,MAAM,KAAK,WAAW,aAAa,qBAAqB;AAAA,EAChE,EAAE,MAAM,MAAM,KAAK,cAAc,SAAS,uBAAuB,aAAa,qBAAqB;AAAA,EACnG,EAAE,MAAM,MAAM,KAAK,OAAO,aAAa,qBAAqB;AAAA,EAC5D,EAAE,MAAM,MAAM,KAAK,OAAO,SAAS,yBAAyB,aAAa,qBAAqB;AAAA,EAC9F,EAAE,MAAM,MAAM,KAAK,SAAS,SAAS,wBAAwB,aAAa,qBAAqB;AAAA,EAC/F,EAAE,MAAM,MAAM,KAAK,UAAU,SAAS,yBAAyB,aAAa,qBAAqB;AAAA,EACjG,EAAE,MAAM,MAAM,KAAK,OAAO,SAAS,4DAA4D,aAAa,qBAAqB;AAAA,EACjI,EAAE,MAAM,MAAM,KAAK,eAAe,SAAS,qCAAqC,aAAa,qBAAqB;AACpH;AAQO,SAAS,eAAe,KAAsB;AACnD,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,UAAU,IAAI,QAAQ,SAAS,EAAE;AACvC,SAAO,uBAAuB,KAAK,CAAC,YAAY,QAAQ,KAAK,GAAG,KAAK,QAAQ,KAAK,OAAO,CAAC;AAC5F;AAKO,SAAS,UAAU,QAAwB;AAChD,MAAI,CAAC,OAAQ,QAAO;AAEpB,MAAI;AACF,UAAM,aACJ,CAAC,OAAO,WAAW,SAAS,KAC5B,CAAC,OAAO,WAAW,UAAU,KAC7B,CAAC,OAAO,WAAW,OAAO,KAC1B,CAAC,OAAO,WAAW,QAAQ;AAC7B,UAAM,OAAO;AACb,UAAM,SAAS,IAAI,IAAI,QAAQ,IAAI;AAEnC,QAAI,UAAU;AACd,eAAW,SAAS,wBAAwB;AAC1C,UAAI,OAAO,aAAa,IAAI,KAAK,GAAG;AAClC,eAAO,aAAa,IAAI,OAAO,oBAAoB;AACnD,kBAAU;AAAA,MACZ;AAAA,IACF;AAGA,eAAW,OAAO,MAAM,KAAK,OAAO,aAAa,KAAK,CAAC,GAAG;AACxD,UAAI,eAAe,GAAG,GAAG;AACvB,eAAO,aAAa,IAAI,KAAK,oBAAoB;AACjD,kBAAU;AAAA,MACZ;AAAA,IACF;AAEA,QAAI,CAAC,QAAS,QAAO;AAErB,QAAI,SAAS,aAAa,OAAO,WAAW,OAAO,SAAS,OAAO,OAAO,OAAO,SAAS;AAE1F,aAAS,OAAO,QAAQ,mBAAmB,oBAAoB;AAC/D,WAAO;AAAA,EACT,QAAQ;AAEN,QAAI,OAAO;AACX,eAAW,SAAS,wBAAwB;AAC1C,YAAM,MAAM,IAAI,OAAO,QAAQ,KAAK,YAAY,IAAI;AACpD,aAAO,KAAK,QAAQ,KAAK,KAAK,oBAAoB,EAAE;AAAA,IACtD;AACA,WAAO;AAAA,EACT;AACF;AAKO,SAAS,cAAc,SAAyD;AACrF,MAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO,CAAC;AAErD,QAAM,OAA+B,CAAC;AACtC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AAClD,QAAI,eAAe,GAAG,GAAG;AACvB,WAAK,GAAG,IAAI;AAAA,IACd,OAAO;AACL,WAAK,GAAG,IAAI;AAAA,IACd;AAAA,EACF;AACA,SAAO;AACT;AAMO,SAAS,oBAA6B,MAAS,WAAW,GAAG,eAAe,GAAM;AACvF,MAAI,SAAS,QAAQ,SAAS,OAAW,QAAO;AAChD,MAAI,OAAO,SAAS,SAAU,QAAO;AACrC,MAAI,eAAe,SAAU,QAAO;AAEpC,MAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,WAAO,KAAK,IAAI,CAAC,SAAS,oBAAoB,MAAM,UAAU,eAAe,CAAC,CAAC;AAAA,EACjF;AAEA,QAAM,SAA8B,CAAC;AACrC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAA2B,GAAG;AACtE,QAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,aAAO,GAAG,IAAI,oBAAoB,OAAO,UAAU,eAAe,CAAC;AAAA,IACrE,WAAW,eAAe,GAAG,GAAG;AAC9B,aAAO,GAAG,IAAI;AAAA,IAChB,WAAW,OAAO,UAAU,UAAU;AAEpC,UAAI,MAAM,WAAW,SAAS,KAAK,MAAM,WAAW,UAAU,KAAK,MAAM,SAAS,GAAG,GAAG;AACtF,eAAO,GAAG,IAAI,UAAU,KAAK;AAAA,MAC/B,OAAO;AACL,YAAI;AACF,iBAAO,GAAG,IAAI,eAAe,OAAO,sBAAsB;AAAA,QAC5D,QAAQ;AACN,iBAAO,GAAG,IAAI;AAAA,QAChB;AAAA,MACF;AAAA,IACF,OAAO;AACL,aAAO,GAAG,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,SAAO;AACT;;;AC/JO,SAAS,oBAAoB,SAAgC,UAA2B,CAAC,GAAW;AACzG,MAAI,CAAC,WAAW,CAAC,QAAQ,QAAS,QAAO;AAEzC,QAAM,aAAa,QAAQ,cAAc;AAGzC,QAAM,SACJ,QAAQ,aAAa,aAAa,KAClC,QAAQ,aAAa,WAAW,KAChC,QAAQ,aAAa,SAAS,KAC9B,QAAQ,aAAa,SAAS;AAEhC,MAAI,QAAQ;AACV,WAAO,iBAAiB,MAAM;AAAA,EAChC;AAGA,QAAM,KAAK,QAAQ;AACnB,MAAI,MAAM,CAAC,WAAW,KAAK,EAAE,KAAK,CAAC,2BAA2B,KAAK,EAAE,GAAG;AACtE,WAAO,IAAI,EAAE;AAAA,EACf;AAGA,QAAM,YAAY,QAAQ,aAAa,YAAY;AACnD,MAAI,aAAa,UAAU,SAAS,IAAI;AACtC,WAAO,GAAG,QAAQ,QAAQ,YAAY,CAAC,gBAAgB,SAAS;AAAA,EAClE;AAEA,QAAM,WAAW,QAAQ,aAAa,MAAM;AAC5C,MAAI,UAAU;AACZ,WAAO,GAAG,QAAQ,QAAQ,YAAY,CAAC,UAAU,QAAQ;AAAA,EAC3D;AAGA,QAAM,MAAM,QAAQ,QAAQ,YAAY;AACxC,QAAM,YAAY,MAAM,KAAK,QAAQ,aAAa,CAAC,CAAC,EACjD,OAAO,CAAC,MAAM,CAAC,EAAE,WAAW,MAAM,KAAK,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC,kBAAkB,KAAK,CAAC,CAAC,EACvF,MAAM,GAAG,UAAU;AAEtB,MAAI,UAAU,SAAS,GAAG;AACxB,UAAM,WAAW,UAAU,IAAI,CAAC,MAAM,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE;AACtD,WAAO,GAAG,GAAG,GAAG,QAAQ;AAAA,EAC1B;AAGA,MAAI,QAAQ,iBAAiB,QAAQ,kBAAkB,SAAS,QAAQ,QAAQ,kBAAkB,SAAS,iBAAiB;AAC1H,UAAM,iBAAiB,oBAAoB,QAAQ,eAAe,EAAE,YAAY,EAAE,CAAC;AACnF,QAAI,kBAAkB,mBAAmB,aAAa,CAAC,eAAe,SAAS,GAAG,GAAG;AACnF,aAAO,GAAG,cAAc,MAAM,GAAG;AAAA,IACnC;AAAA,EACF;AAEA,SAAO;AACT;AAKO,SAAS,SAAS,KAAgC,YAAY,KAAa;AAChF,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI,IAAI,UAAU,UAAW,QAAO;AACpC,SAAO,IAAI,MAAM,GAAG,SAAS,IAAI;AACnC;","names":[]}