@ticktockbent/charlotte 0.1.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 (136) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/LICENSE +21 -0
  3. package/README.md +254 -0
  4. package/dist/browser/browser-manager.d.ts +14 -0
  5. package/dist/browser/browser-manager.d.ts.map +1 -0
  6. package/dist/browser/browser-manager.js +72 -0
  7. package/dist/browser/browser-manager.js.map +1 -0
  8. package/dist/browser/cdp-session.d.ts +7 -0
  9. package/dist/browser/cdp-session.d.ts.map +1 -0
  10. package/dist/browser/cdp-session.js +35 -0
  11. package/dist/browser/cdp-session.js.map +1 -0
  12. package/dist/browser/page-manager.d.ts +30 -0
  13. package/dist/browser/page-manager.d.ts.map +1 -0
  14. package/dist/browser/page-manager.js +123 -0
  15. package/dist/browser/page-manager.js.map +1 -0
  16. package/dist/dev/auditor.d.ts +39 -0
  17. package/dist/dev/auditor.d.ts.map +1 -0
  18. package/dist/dev/auditor.js +474 -0
  19. package/dist/dev/auditor.js.map +1 -0
  20. package/dist/dev/dev-mode-state.d.ts +24 -0
  21. package/dist/dev/dev-mode-state.d.ts.map +1 -0
  22. package/dist/dev/dev-mode-state.js +93 -0
  23. package/dist/dev/dev-mode-state.js.map +1 -0
  24. package/dist/dev/file-watcher.d.ts +20 -0
  25. package/dist/dev/file-watcher.d.ts.map +1 -0
  26. package/dist/dev/file-watcher.js +78 -0
  27. package/dist/dev/file-watcher.js.map +1 -0
  28. package/dist/dev/static-server.d.ts +18 -0
  29. package/dist/dev/static-server.d.ts.map +1 -0
  30. package/dist/dev/static-server.js +73 -0
  31. package/dist/dev/static-server.js.map +1 -0
  32. package/dist/index.d.ts +3 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +60 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/renderer/accessibility-extractor.d.ts +19 -0
  37. package/dist/renderer/accessibility-extractor.d.ts.map +1 -0
  38. package/dist/renderer/accessibility-extractor.js +138 -0
  39. package/dist/renderer/accessibility-extractor.js.map +1 -0
  40. package/dist/renderer/content-extractor.d.ts +6 -0
  41. package/dist/renderer/content-extractor.d.ts.map +1 -0
  42. package/dist/renderer/content-extractor.js +150 -0
  43. package/dist/renderer/content-extractor.js.map +1 -0
  44. package/dist/renderer/dom-path.d.ts +4 -0
  45. package/dist/renderer/dom-path.d.ts.map +1 -0
  46. package/dist/renderer/dom-path.js +34 -0
  47. package/dist/renderer/dom-path.js.map +1 -0
  48. package/dist/renderer/element-id-generator.d.ts +19 -0
  49. package/dist/renderer/element-id-generator.d.ts.map +1 -0
  50. package/dist/renderer/element-id-generator.js +73 -0
  51. package/dist/renderer/element-id-generator.js.map +1 -0
  52. package/dist/renderer/interactive-extractor.d.ts +13 -0
  53. package/dist/renderer/interactive-extractor.d.ts.map +1 -0
  54. package/dist/renderer/interactive-extractor.js +161 -0
  55. package/dist/renderer/interactive-extractor.js.map +1 -0
  56. package/dist/renderer/layout-extractor.d.ts +8 -0
  57. package/dist/renderer/layout-extractor.d.ts.map +1 -0
  58. package/dist/renderer/layout-extractor.js +48 -0
  59. package/dist/renderer/layout-extractor.js.map +1 -0
  60. package/dist/renderer/renderer-pipeline.d.ts +26 -0
  61. package/dist/renderer/renderer-pipeline.d.ts.map +1 -0
  62. package/dist/renderer/renderer-pipeline.js +163 -0
  63. package/dist/renderer/renderer-pipeline.js.map +1 -0
  64. package/dist/server.d.ts +19 -0
  65. package/dist/server.d.ts.map +1 -0
  66. package/dist/server.js +39 -0
  67. package/dist/server.js.map +1 -0
  68. package/dist/state/differ.d.ts +9 -0
  69. package/dist/state/differ.d.ts.map +1 -0
  70. package/dist/state/differ.js +295 -0
  71. package/dist/state/differ.js.map +1 -0
  72. package/dist/state/snapshot-store.d.ts +52 -0
  73. package/dist/state/snapshot-store.d.ts.map +1 -0
  74. package/dist/state/snapshot-store.js +98 -0
  75. package/dist/state/snapshot-store.js.map +1 -0
  76. package/dist/tools/dev-mode.d.ts +4 -0
  77. package/dist/tools/dev-mode.d.ts.map +1 -0
  78. package/dist/tools/dev-mode.js +160 -0
  79. package/dist/tools/dev-mode.js.map +1 -0
  80. package/dist/tools/evaluate.d.ts +10 -0
  81. package/dist/tools/evaluate.d.ts.map +1 -0
  82. package/dist/tools/evaluate.js +109 -0
  83. package/dist/tools/evaluate.js.map +1 -0
  84. package/dist/tools/interaction.d.ts +4 -0
  85. package/dist/tools/interaction.d.ts.map +1 -0
  86. package/dist/tools/interaction.js +680 -0
  87. package/dist/tools/interaction.js.map +1 -0
  88. package/dist/tools/navigation.d.ts +4 -0
  89. package/dist/tools/navigation.d.ts.map +1 -0
  90. package/dist/tools/navigation.js +136 -0
  91. package/dist/tools/navigation.js.map +1 -0
  92. package/dist/tools/observation.d.ts +4 -0
  93. package/dist/tools/observation.d.ts.map +1 -0
  94. package/dist/tools/observation.js +278 -0
  95. package/dist/tools/observation.js.map +1 -0
  96. package/dist/tools/session.d.ts +4 -0
  97. package/dist/tools/session.d.ts.map +1 -0
  98. package/dist/tools/session.js +372 -0
  99. package/dist/tools/session.js.map +1 -0
  100. package/dist/tools/tool-helpers.d.ts +89 -0
  101. package/dist/tools/tool-helpers.d.ts.map +1 -0
  102. package/dist/tools/tool-helpers.js +127 -0
  103. package/dist/tools/tool-helpers.js.map +1 -0
  104. package/dist/types/config.d.ts +7 -0
  105. package/dist/types/config.d.ts.map +1 -0
  106. package/dist/types/config.js +7 -0
  107. package/dist/types/config.js.map +1 -0
  108. package/dist/types/element-id.d.ts +8 -0
  109. package/dist/types/element-id.d.ts.map +1 -0
  110. package/dist/types/element-id.js +19 -0
  111. package/dist/types/element-id.js.map +1 -0
  112. package/dist/types/errors.d.ts +22 -0
  113. package/dist/types/errors.d.ts.map +1 -0
  114. package/dist/types/errors.js +30 -0
  115. package/dist/types/errors.js.map +1 -0
  116. package/dist/types/page-representation.d.ts +84 -0
  117. package/dist/types/page-representation.d.ts.map +1 -0
  118. package/dist/types/page-representation.js +2 -0
  119. package/dist/types/page-representation.js.map +1 -0
  120. package/dist/types/snapshot.d.ts +22 -0
  121. package/dist/types/snapshot.d.ts.map +1 -0
  122. package/dist/types/snapshot.js +2 -0
  123. package/dist/types/snapshot.js.map +1 -0
  124. package/dist/utils/hash.d.ts +2 -0
  125. package/dist/utils/hash.d.ts.map +1 -0
  126. package/dist/utils/hash.js +6 -0
  127. package/dist/utils/hash.js.map +1 -0
  128. package/dist/utils/logger.d.ts +9 -0
  129. package/dist/utils/logger.d.ts.map +1 -0
  130. package/dist/utils/logger.js +31 -0
  131. package/dist/utils/logger.js.map +1 -0
  132. package/dist/utils/wait.d.ts +21 -0
  133. package/dist/utils/wait.d.ts.map +1 -0
  134. package/dist/utils/wait.js +55 -0
  135. package/dist/utils/wait.js.map +1 -0
  136. package/package.json +67 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,33 @@
1
+ # Changelog
2
+
3
+ All notable changes to Charlotte will be documented in this file.
4
+
5
+ ## [0.1.0] - 2026-02-13
6
+
7
+ Initial release. All six implementation phases complete.
8
+
9
+ ### Tools
10
+
11
+ **Navigation**: `navigate`, `back`, `forward`, `reload`
12
+
13
+ **Observation**: `observe` (minimal/summary/full detail levels, CSS selector scoping, computed styles), `find` (text, role, type, spatial near/within filters), `screenshot` (PNG/JPEG/WebP, element or full page), `diff` (structural comparison against snapshots)
14
+
15
+ **Interaction**: `click` (left/right/double), `type` (with clear_first, press_enter), `select`, `toggle`, `submit`, `scroll` (page/container, directional), `hover`, `key` (with modifiers), `wait_for` (element state, text, selector, JS expression)
16
+
17
+ **Session**: `tabs`, `tab_open`, `tab_switch`, `tab_close`, `set_cookies`, `set_headers`, `viewport` (mobile/tablet/desktop presets), `network` (3G/4G/offline throttling, URL blocking), `configure` (snapshot depth, auto-snapshot mode)
18
+
19
+ **Development**: `dev_serve` (static server + file watching with auto-reload), `dev_inject` (CSS/JS injection with delta), `dev_audit` (a11y, performance, SEO, contrast, broken links)
20
+
21
+ **Utilities**: `evaluate` (JS execution with timeout and promise awaiting)
22
+
23
+ ### Architecture
24
+
25
+ - Renderer pipeline: accessibility tree + layout geometry + interactive element extraction
26
+ - Hash-based element IDs stable across re-renders
27
+ - Snapshot store with ring buffer and structural diffing
28
+ - Chromium crash recovery with automatic relaunch
29
+ - Dev mode reload events surfaced through existing tool responses (zero integration overhead)
30
+
31
+ ### Testing
32
+
33
+ 222 tests across 19 test files (unit + integration).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ticktockbent
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,254 @@
1
+ # Charlotte
2
+
3
+ **The Web, Readable.**
4
+
5
+ Charlotte is an MCP server that renders web pages into structured, agent-readable representations using headless Chromium. It exposes the browser's semantic understanding — accessibility tree, layout geometry, interactive elements — to AI agents via [Model Context Protocol](https://modelcontextprotocol.io/) tools, enabling navigation, observation, and interaction without vision models or brittle selectors.
6
+
7
+ ## How It Works
8
+
9
+ Charlotte maintains a persistent headless Chromium session and acts as a translation layer between the visual web and the agent's text-native reasoning. Every page is decomposed into a structured representation:
10
+
11
+ ```
12
+ ┌─────────────┐ MCP Protocol ┌──────────────────┐
13
+ │ AI Agent │ <───────────────────> │ Charlotte │
14
+ └─────────────┘ │ │
15
+ │ ┌────────────┐ │
16
+ │ │ Renderer │ │
17
+ │ │ Pipeline │ │
18
+ │ └─────┬──────┘ │
19
+ │ │ │
20
+ │ ┌─────▼──────┐ │
21
+ │ │ Headless │ │
22
+ │ │ Chromium │ │
23
+ │ └────────────┘ │
24
+ └──────────────────┘
25
+ ```
26
+
27
+ Agents receive landmarks, headings, interactive elements with typed metadata, bounding boxes, form structures, and content summaries — all derived from what the browser already knows about every page.
28
+
29
+ ## Features
30
+
31
+ **Navigation** — `navigate`, `back`, `forward`, `reload`
32
+
33
+ **Observation** — `observe` (3 detail levels), `find` (spatial + semantic search), `screenshot`, `diff` (structural comparison against snapshots)
34
+
35
+ **Interaction** — `click`, `type`, `select`, `toggle`, `submit`, `scroll`, `hover`, `key`, `wait_for` (async condition polling)
36
+
37
+ **Session Management** — `tabs`, `tab_open`, `tab_switch`, `tab_close`, `viewport` (device presets), `network` (throttling, URL blocking), `set_cookies`, `set_headers`, `configure`
38
+
39
+ **Development Mode** — `dev_serve` (static server + file watching with auto-reload), `dev_inject` (CSS/JS injection), `dev_audit` (a11y, performance, SEO, contrast, broken links)
40
+
41
+ **Utilities** — `evaluate` (arbitrary JS execution in page context)
42
+
43
+ ## Quick Start
44
+
45
+ ### Prerequisites
46
+
47
+ - Node.js >= 22
48
+ - npm
49
+
50
+ ### Installation
51
+
52
+ ```bash
53
+ git clone https://github.com/ticktockbent/charlotte.git
54
+ cd charlotte
55
+ npm install
56
+ ```
57
+
58
+ ### Build
59
+
60
+ ```bash
61
+ npm run build
62
+ ```
63
+
64
+ ### Run
65
+
66
+ Charlotte communicates over stdio using the MCP protocol:
67
+
68
+ ```bash
69
+ npm start
70
+ ```
71
+
72
+ ### MCP Client Configuration
73
+
74
+ Add Charlotte to your MCP client configuration. For Claude Code, create `.mcp.json` in your project root:
75
+
76
+ ```json
77
+ {
78
+ "mcpServers": {
79
+ "charlotte": {
80
+ "type": "stdio",
81
+ "command": "node",
82
+ "args": ["/path/to/charlotte/dist/index.js"],
83
+ "env": {}
84
+ }
85
+ }
86
+ }
87
+ ```
88
+
89
+ For Claude Desktop, add to `claude_desktop_config.json`:
90
+
91
+ ```json
92
+ {
93
+ "mcpServers": {
94
+ "charlotte": {
95
+ "command": "node",
96
+ "args": ["/path/to/charlotte/dist/index.js"]
97
+ }
98
+ }
99
+ }
100
+ ```
101
+
102
+ See [docs/mcp-setup.md](docs/mcp-setup.md) for the full setup guide, including development mode, generic MCP clients, verification steps, and troubleshooting.
103
+
104
+ ## Usage Examples
105
+
106
+ Once connected, an agent can use Charlotte's tools:
107
+
108
+ ### Browse a website
109
+
110
+ ```
111
+ navigate({ url: "https://example.com" })
112
+ observe({ detail: "summary" })
113
+ find({ type: "link", text: "About" })
114
+ click({ element_id: "lnk-a3f1" })
115
+ ```
116
+
117
+ ### Fill out a form
118
+
119
+ ```
120
+ observe({ detail: "minimal" })
121
+ type({ element_id: "inp-c7e2", text: "hello@example.com" })
122
+ select({ element_id: "sel-e8a3", value: "option-2" })
123
+ submit({ form_id: "frm-b1d4" })
124
+ ```
125
+
126
+ ### Local development feedback loop
127
+
128
+ ```
129
+ dev_serve({ path: "./my-site", watch: true })
130
+ observe({ detail: "full" })
131
+ dev_audit({ checks: ["a11y", "contrast"] })
132
+ dev_inject({ css: "body { font-size: 18px; }" })
133
+ ```
134
+
135
+ ## Page Representation
136
+
137
+ Charlotte returns structured representations optimized for token efficiency:
138
+
139
+ ```json
140
+ {
141
+ "url": "https://example.com/dashboard",
142
+ "title": "Dashboard",
143
+ "viewport": { "width": 1280, "height": 720 },
144
+ "snapshot_id": 1,
145
+ "structure": {
146
+ "landmarks": [
147
+ { "role": "banner", "label": "Site header", "bounds": { "x": 0, "y": 0, "w": 1280, "h": 64 } },
148
+ { "role": "main", "label": "Content", "bounds": { "x": 240, "y": 64, "w": 1040, "h": 656 } }
149
+ ],
150
+ "headings": [
151
+ { "level": 1, "text": "Dashboard", "id": "h-1" }
152
+ ],
153
+ "content_summary": "main: 2 headings, 5 links, 1 form"
154
+ },
155
+ "interactive": [
156
+ {
157
+ "id": "btn-a3f1",
158
+ "type": "button",
159
+ "label": "Create Project",
160
+ "bounds": { "x": 960, "y": 80, "w": 160, "h": 40 },
161
+ "state": { "enabled": true, "visible": true }
162
+ }
163
+ ],
164
+ "forms": [],
165
+ "alerts": [],
166
+ "errors": { "console": [], "network": [] }
167
+ }
168
+ ```
169
+
170
+ Detail levels control verbosity:
171
+ - **`minimal`** (~200-500 tokens) — Landmarks + interactive elements only
172
+ - **`summary`** (~500-1500 tokens) — Adds content summaries, forms, errors
173
+ - **`full`** (variable) — Includes all visible text content
174
+
175
+ ## Element IDs
176
+
177
+ Element IDs are stable across minor DOM mutations. They're generated by hashing a composite key of element type, ARIA role, accessible name, and DOM path signature:
178
+
179
+ ```
180
+ btn-a3f1 (button) inp-c7e2 (text input)
181
+ lnk-d4b9 (link) sel-e8a3 (select)
182
+ chk-f1a2 (checkbox) frm-b1d4 (form)
183
+ ```
184
+
185
+ IDs survive unrelated DOM changes and element reordering within the same container.
186
+
187
+ ## Development
188
+
189
+ ```bash
190
+ # Run in watch mode
191
+ npm run dev
192
+
193
+ # Run all tests
194
+ npm test
195
+
196
+ # Run only unit tests
197
+ npm run test:unit
198
+
199
+ # Run only integration tests
200
+ npm run test:integration
201
+
202
+ # Type check
203
+ npx tsc --noEmit
204
+ ```
205
+
206
+ ### Project Structure
207
+
208
+ ```
209
+ src/
210
+ browser/ # Puppeteer lifecycle, tab management, CDP sessions
211
+ renderer/ # Accessibility tree extraction, layout, content, element IDs
212
+ state/ # Snapshot store, structural differ
213
+ tools/ # MCP tool definitions (navigation, observation, interaction, session, dev-mode)
214
+ dev/ # Static server, file watcher, auditor
215
+ types/ # TypeScript interfaces
216
+ utils/ # Logger, hash, wait utilities
217
+ tests/
218
+ unit/ # Fast tests with mocks
219
+ integration/ # Full Puppeteer tests against fixture HTML
220
+ fixtures/pages/ # Test HTML files
221
+ ```
222
+
223
+ ### Architecture
224
+
225
+ The **Renderer Pipeline** is the core — it calls extractors in order and assembles a `PageRepresentation`:
226
+
227
+ 1. Accessibility tree extraction (CDP `Accessibility.getFullAXTree`)
228
+ 2. Layout extraction (CDP `DOM.getBoxModel`)
229
+ 3. Landmark, heading, interactive element, and content extraction
230
+ 4. Element ID generation (hash-based, stable across re-renders)
231
+
232
+ All tools go through `renderActivePage()` which handles snapshots, reload events, and response formatting.
233
+
234
+ ## Sandbox
235
+
236
+ Charlotte includes a test website in `tests/sandbox/` that exercises all 30 tools without touching the public internet. Serve it locally with:
237
+
238
+ ```
239
+ dev_serve({ path: "tests/sandbox" })
240
+ ```
241
+
242
+ Four pages cover navigation, forms, interactive elements, delayed content, scroll containers, and more. See [docs/sandbox.md](docs/sandbox.md) for the full page reference and a tool-by-tool exercise checklist.
243
+
244
+ ## Full Specification
245
+
246
+ See [docs/CHARLOTTE_SPEC.md](docs/CHARLOTTE_SPEC.md) for the complete specification including all tool parameters, the page representation format, element identity strategy, and architecture details.
247
+
248
+ ## License
249
+
250
+ [MIT](LICENSE)
251
+
252
+ ## Contributing
253
+
254
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
@@ -0,0 +1,14 @@
1
+ import { type Browser, type Page, type LaunchOptions } from "puppeteer";
2
+ export declare class BrowserManager {
3
+ private browser;
4
+ private launchOptions;
5
+ private launching;
6
+ launch(options?: LaunchOptions): Promise<void>;
7
+ private doLaunch;
8
+ ensureConnected(): Promise<void>;
9
+ close(): Promise<void>;
10
+ getBrowser(): Browser;
11
+ newPage(): Promise<Page>;
12
+ isConnected(): boolean;
13
+ }
14
+ //# sourceMappingURL=browser-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-manager.d.ts","sourceRoot":"","sources":["../../src/browser/browser-manager.ts"],"names":[],"mappings":"AAAA,OAAkB,EAAE,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAInF,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,SAAS,CAA8B;IAEzC,MAAM,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;YAetC,QAAQ;IAchB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAoBhC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ5B,UAAU,IAAI,OAAO;IAUf,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAM9B,WAAW,IAAI,OAAO;CAGvB"}
@@ -0,0 +1,72 @@
1
+ import puppeteer from "puppeteer";
2
+ import { logger } from "../utils/logger.js";
3
+ import { CharlotteError, CharlotteErrorCode } from "../types/errors.js";
4
+ export class BrowserManager {
5
+ browser = null;
6
+ launchOptions = {};
7
+ launching = null;
8
+ async launch(options) {
9
+ this.launchOptions = {
10
+ headless: true,
11
+ args: [
12
+ "--no-sandbox",
13
+ "--disable-setuid-sandbox",
14
+ "--disable-gpu",
15
+ "--disable-dev-shm-usage",
16
+ ],
17
+ ...options,
18
+ };
19
+ await this.doLaunch();
20
+ }
21
+ async doLaunch() {
22
+ logger.info("Launching Chromium");
23
+ this.browser = await puppeteer.launch(this.launchOptions);
24
+ this.browser.on("disconnected", () => {
25
+ logger.warn("Chromium disconnected unexpectedly");
26
+ this.browser = null;
27
+ });
28
+ logger.info("Chromium launched", {
29
+ pid: this.browser.process()?.pid,
30
+ });
31
+ }
32
+ async ensureConnected() {
33
+ if (this.browser && this.browser.connected) {
34
+ return;
35
+ }
36
+ // Prevent concurrent relaunch attempts
37
+ if (this.launching) {
38
+ await this.launching;
39
+ return;
40
+ }
41
+ logger.info("Browser not connected, relaunching");
42
+ this.launching = this.doLaunch();
43
+ try {
44
+ await this.launching;
45
+ }
46
+ finally {
47
+ this.launching = null;
48
+ }
49
+ }
50
+ async close() {
51
+ if (this.browser) {
52
+ logger.info("Closing Chromium");
53
+ await this.browser.close();
54
+ this.browser = null;
55
+ }
56
+ }
57
+ getBrowser() {
58
+ if (!this.browser || !this.browser.connected) {
59
+ throw new CharlotteError(CharlotteErrorCode.SESSION_ERROR, "Browser is not connected. Call ensureConnected() first.");
60
+ }
61
+ return this.browser;
62
+ }
63
+ async newPage() {
64
+ await this.ensureConnected();
65
+ const browser = this.getBrowser();
66
+ return browser.newPage();
67
+ }
68
+ isConnected() {
69
+ return this.browser !== null && this.browser.connected;
70
+ }
71
+ }
72
+ //# sourceMappingURL=browser-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-manager.js","sourceRoot":"","sources":["../../src/browser/browser-manager.ts"],"names":[],"mappings":"AAAA,OAAO,SAA0D,MAAM,WAAW,CAAC;AACnF,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExE,MAAM,OAAO,cAAc;IACjB,OAAO,GAAmB,IAAI,CAAC;IAC/B,aAAa,GAAkB,EAAE,CAAC;IAClC,SAAS,GAAyB,IAAI,CAAC;IAE/C,KAAK,CAAC,MAAM,CAAC,OAAuB;QAClC,IAAI,CAAC,aAAa,GAAG;YACnB,QAAQ,EAAE,IAAI;YACd,IAAI,EAAE;gBACJ,cAAc;gBACd,0BAA0B;gBAC1B,eAAe;gBACf,yBAAyB;aAC1B;YACD,GAAG,OAAO;SACX,CAAC;QAEF,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;IACxB,CAAC;IAEO,KAAK,CAAC,QAAQ;QACpB,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAClC,IAAI,CAAC,OAAO,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAE1D,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,cAAc,EAAE,GAAG,EAAE;YACnC,MAAM,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;YAClD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACtB,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,IAAI,CAAC,mBAAmB,EAAE;YAC/B,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,GAAG;SACjC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;YAC3C,OAAO;QACT,CAAC;QAED,uCAAuC;QACvC,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,MAAM,IAAI,CAAC,SAAS,CAAC;YACrB,OAAO;QACT,CAAC;QAED,MAAM,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;QAClD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QACjC,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,SAAS,CAAC;QACvB,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACxB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;YAChC,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YAC3B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACtB,CAAC;IACH,CAAC;IAED,UAAU;QACR,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;YAC7C,MAAM,IAAI,cAAc,CACtB,kBAAkB,CAAC,aAAa,EAChC,yDAAyD,CAC1D,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,OAAO;QACX,MAAM,IAAI,CAAC,eAAe,EAAE,CAAC;QAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QAClC,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC3B,CAAC;IAED,WAAW;QACT,OAAO,IAAI,CAAC,OAAO,KAAK,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC;IACzD,CAAC;CACF"}
@@ -0,0 +1,7 @@
1
+ import type { Page, CDPSession } from "puppeteer";
2
+ export declare class CDPSessionManager {
3
+ private sessions;
4
+ getSession(page: Page): Promise<CDPSession>;
5
+ private enableDomains;
6
+ }
7
+ //# sourceMappingURL=cdp-session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cdp-session.d.ts","sourceRoot":"","sources":["../../src/browser/cdp-session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAWlD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAA4C;IAEtD,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC;YAanC,aAAa;CAW5B"}
@@ -0,0 +1,35 @@
1
+ import { logger } from "../utils/logger.js";
2
+ const REQUIRED_DOMAINS = [
3
+ "Accessibility",
4
+ "DOM",
5
+ "CSS",
6
+ "Page",
7
+ "Network",
8
+ ];
9
+ export class CDPSessionManager {
10
+ sessions = new WeakMap();
11
+ async getSession(page) {
12
+ const existing = this.sessions.get(page);
13
+ if (existing) {
14
+ return existing;
15
+ }
16
+ logger.debug("Creating new CDP session");
17
+ const session = await page.createCDPSession();
18
+ await this.enableDomains(session);
19
+ this.sessions.set(page, session);
20
+ return session;
21
+ }
22
+ async enableDomains(session) {
23
+ for (const domain of REQUIRED_DOMAINS) {
24
+ try {
25
+ await session.send(`${domain}.enable`);
26
+ logger.debug(`Enabled CDP domain: ${domain}`);
27
+ }
28
+ catch (error) {
29
+ // Some domains may not need explicit enabling
30
+ logger.debug(`Could not enable CDP domain: ${domain}`, error);
31
+ }
32
+ }
33
+ }
34
+ }
35
+ //# sourceMappingURL=cdp-session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cdp-session.js","sourceRoot":"","sources":["../../src/browser/cdp-session.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C,MAAM,gBAAgB,GAAG;IACvB,eAAe;IACf,KAAK;IACL,KAAK;IACL,MAAM;IACN,SAAS;CACD,CAAC;AAEX,MAAM,OAAO,iBAAiB;IACpB,QAAQ,GAA8B,IAAI,OAAO,EAAE,CAAC;IAE5D,KAAK,CAAC,UAAU,CAAC,IAAU;QACzB,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,MAAM,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;QACzC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC9C,MAAM,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAClC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACjC,OAAO,OAAO,CAAC;IACjB,CAAC;IAEO,KAAK,CAAC,aAAa,CAAC,OAAmB;QAC7C,KAAK,MAAM,MAAM,IAAI,gBAAgB,EAAE,CAAC;YACtC,IAAI,CAAC;gBACH,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,SAAgB,CAAC,CAAC;gBAC9C,MAAM,CAAC,KAAK,CAAC,uBAAuB,MAAM,EAAE,CAAC,CAAC;YAChD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,8CAA8C;gBAC9C,MAAM,CAAC,KAAK,CAAC,gCAAgC,MAAM,EAAE,EAAE,KAAK,CAAC,CAAC;YAChE,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,30 @@
1
+ import type { Page } from "puppeteer";
2
+ import type { BrowserManager } from "./browser-manager.js";
3
+ export interface TabInfo {
4
+ id: string;
5
+ url: string;
6
+ title: string;
7
+ active: boolean;
8
+ }
9
+ export declare class PageManager {
10
+ private pages;
11
+ private activeTabId;
12
+ openTab(browserManager: BrowserManager, url?: string): Promise<string>;
13
+ switchTab(tabId: string): Promise<Page>;
14
+ closeTab(tabId: string): Promise<void>;
15
+ listTabs(): Promise<TabInfo[]>;
16
+ getActivePage(): Page;
17
+ getActiveTabId(): string;
18
+ getConsoleErrors(): Array<{
19
+ level: string;
20
+ text: string;
21
+ }>;
22
+ getNetworkErrors(): Array<{
23
+ url: string;
24
+ status: number;
25
+ statusText: string;
26
+ }>;
27
+ clearErrors(): void;
28
+ hasPages(): boolean;
29
+ }
30
+ //# sourceMappingURL=page-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-manager.d.ts","sourceRoot":"","sources":["../../src/browser/page-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAI3D,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;CACjB;AAeD,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,CAAkC;IAC/C,OAAO,CAAC,WAAW,CAAuB;IAEpC,OAAO,CAAC,cAAc,EAAE,cAAc,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA4CtE,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAcvC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBtC,QAAQ,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAapC,aAAa,IAAI,IAAI;IAmBrB,cAAc,IAAI,MAAM;IAUxB,gBAAgB,IAAI,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAM1D,gBAAgB,IAAI,KAAK,CAAC;QACxB,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IAMF,WAAW,IAAI,IAAI;IASnB,QAAQ,IAAI,OAAO;CAGpB"}
@@ -0,0 +1,123 @@
1
+ import { CharlotteError, CharlotteErrorCode } from "../types/errors.js";
2
+ import { logger } from "../utils/logger.js";
3
+ let nextTabIdCounter = 1;
4
+ function generateTabId() {
5
+ return `tab-${nextTabIdCounter++}`;
6
+ }
7
+ export class PageManager {
8
+ pages = new Map();
9
+ activeTabId = null;
10
+ async openTab(browserManager, url) {
11
+ const page = await browserManager.newPage();
12
+ const tabId = generateTabId();
13
+ const managedPage = {
14
+ id: tabId,
15
+ page,
16
+ consoleErrors: [],
17
+ networkErrors: [],
18
+ };
19
+ // Collect console errors
20
+ page.on("console", (msg) => {
21
+ const level = msg.type();
22
+ if (level === "error" || level === "warn") {
23
+ managedPage.consoleErrors.push({
24
+ level,
25
+ text: msg.text(),
26
+ });
27
+ }
28
+ });
29
+ // Collect network errors
30
+ page.on("response", (response) => {
31
+ if (response.status() >= 400) {
32
+ managedPage.networkErrors.push({
33
+ url: response.url(),
34
+ status: response.status(),
35
+ statusText: response.statusText(),
36
+ });
37
+ }
38
+ });
39
+ this.pages.set(tabId, managedPage);
40
+ this.activeTabId = tabId;
41
+ if (url) {
42
+ await page.goto(url, { waitUntil: "load" });
43
+ }
44
+ logger.info(`Opened tab ${tabId}`, { url });
45
+ return tabId;
46
+ }
47
+ async switchTab(tabId) {
48
+ const managedPage = this.pages.get(tabId);
49
+ if (!managedPage) {
50
+ throw new CharlotteError(CharlotteErrorCode.SESSION_ERROR, `Tab '${tabId}' not found`);
51
+ }
52
+ this.activeTabId = tabId;
53
+ await managedPage.page.bringToFront();
54
+ return managedPage.page;
55
+ }
56
+ async closeTab(tabId) {
57
+ const managedPage = this.pages.get(tabId);
58
+ if (!managedPage) {
59
+ throw new CharlotteError(CharlotteErrorCode.SESSION_ERROR, `Tab '${tabId}' not found`);
60
+ }
61
+ await managedPage.page.close();
62
+ this.pages.delete(tabId);
63
+ if (this.activeTabId === tabId) {
64
+ // Switch to the first remaining tab
65
+ const remaining = this.pages.keys().next();
66
+ this.activeTabId = remaining.done ? null : remaining.value;
67
+ }
68
+ logger.info(`Closed tab ${tabId}`);
69
+ }
70
+ async listTabs() {
71
+ const tabs = [];
72
+ for (const [id, managedPage] of this.pages) {
73
+ tabs.push({
74
+ id,
75
+ url: managedPage.page.url(),
76
+ title: await managedPage.page.title(),
77
+ active: id === this.activeTabId,
78
+ });
79
+ }
80
+ return tabs;
81
+ }
82
+ getActivePage() {
83
+ if (!this.activeTabId) {
84
+ throw new CharlotteError(CharlotteErrorCode.SESSION_ERROR, "No active tab. Open a tab first.");
85
+ }
86
+ const managedPage = this.pages.get(this.activeTabId);
87
+ if (!managedPage) {
88
+ throw new CharlotteError(CharlotteErrorCode.SESSION_ERROR, "Active tab not found. This is a bug.");
89
+ }
90
+ return managedPage.page;
91
+ }
92
+ getActiveTabId() {
93
+ if (!this.activeTabId) {
94
+ throw new CharlotteError(CharlotteErrorCode.SESSION_ERROR, "No active tab");
95
+ }
96
+ return this.activeTabId;
97
+ }
98
+ getConsoleErrors() {
99
+ if (!this.activeTabId)
100
+ return [];
101
+ const managedPage = this.pages.get(this.activeTabId);
102
+ return managedPage?.consoleErrors ?? [];
103
+ }
104
+ getNetworkErrors() {
105
+ if (!this.activeTabId)
106
+ return [];
107
+ const managedPage = this.pages.get(this.activeTabId);
108
+ return managedPage?.networkErrors ?? [];
109
+ }
110
+ clearErrors() {
111
+ if (!this.activeTabId)
112
+ return;
113
+ const managedPage = this.pages.get(this.activeTabId);
114
+ if (managedPage) {
115
+ managedPage.consoleErrors = [];
116
+ managedPage.networkErrors = [];
117
+ }
118
+ }
119
+ hasPages() {
120
+ return this.pages.size > 0;
121
+ }
122
+ }
123
+ //# sourceMappingURL=page-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-manager.js","sourceRoot":"","sources":["../../src/browser/page-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import type { Page, CDPSession } from "puppeteer";
2
+ export type AuditCategory = "a11y" | "performance" | "seo" | "contrast" | "links";
3
+ export interface AuditFinding {
4
+ category: AuditCategory;
5
+ severity: "error" | "warning" | "info";
6
+ message: string;
7
+ element?: string;
8
+ recommendation: string;
9
+ }
10
+ export interface AuditResult {
11
+ categories_checked: AuditCategory[];
12
+ findings: AuditFinding[];
13
+ summary: string;
14
+ }
15
+ /**
16
+ * Compute relative luminance of a color per WCAG 2.1.
17
+ * Input: r, g, b in 0-255 range.
18
+ */
19
+ export declare function relativeLuminance(r: number, g: number, b: number): number;
20
+ /**
21
+ * Compute WCAG 2.1 contrast ratio between two colors.
22
+ * Returns a ratio >= 1.
23
+ */
24
+ export declare function contrastRatio(foregroundRgb: [number, number, number], backgroundRgb: [number, number, number]): number;
25
+ /**
26
+ * Parse a CSS color string like "rgb(255, 0, 0)" or "rgba(255, 0, 0, 1)" to [r, g, b].
27
+ * Returns null if unparseable.
28
+ */
29
+ export declare function parseRgbColor(colorString: string): [number, number, number] | null;
30
+ export declare class Auditor {
31
+ audit(page: Page, session: CDPSession, categories?: AuditCategory[]): Promise<AuditResult>;
32
+ private runCategoryAudit;
33
+ private auditAccessibility;
34
+ private auditPerformance;
35
+ private auditSeo;
36
+ private auditContrast;
37
+ private auditLinks;
38
+ }
39
+ //# sourceMappingURL=auditor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auditor.d.ts","sourceRoot":"","sources":["../../src/dev/auditor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAGlD,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,aAAa,GACb,KAAK,GACL,UAAU,GACV,OAAO,CAAC;AAEZ,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,aAAa,CAAC;IACxB,QAAQ,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,kBAAkB,EAAE,aAAa,EAAE,CAAC;IACpC,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;CACjB;AAUD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAOzE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EACvC,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GACtC,MAAM,CAMR;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,WAAW,EAAE,MAAM,GAClB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAYjC;AAmBD,qBAAa,OAAO;IACZ,KAAK,CACT,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,UAAU,EACnB,UAAU,CAAC,EAAE,aAAa,EAAE,GAC3B,OAAO,CAAC,WAAW,CAAC;YAiCT,gBAAgB;YAmBhB,kBAAkB;YAkGlB,gBAAgB;YA4EhB,QAAQ;YA2GR,aAAa;YAsGb,UAAU;CA+EzB"}