@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.
- package/CHANGELOG.md +33 -0
- package/LICENSE +21 -0
- package/README.md +254 -0
- package/dist/browser/browser-manager.d.ts +14 -0
- package/dist/browser/browser-manager.d.ts.map +1 -0
- package/dist/browser/browser-manager.js +72 -0
- package/dist/browser/browser-manager.js.map +1 -0
- package/dist/browser/cdp-session.d.ts +7 -0
- package/dist/browser/cdp-session.d.ts.map +1 -0
- package/dist/browser/cdp-session.js +35 -0
- package/dist/browser/cdp-session.js.map +1 -0
- package/dist/browser/page-manager.d.ts +30 -0
- package/dist/browser/page-manager.d.ts.map +1 -0
- package/dist/browser/page-manager.js +123 -0
- package/dist/browser/page-manager.js.map +1 -0
- package/dist/dev/auditor.d.ts +39 -0
- package/dist/dev/auditor.d.ts.map +1 -0
- package/dist/dev/auditor.js +474 -0
- package/dist/dev/auditor.js.map +1 -0
- package/dist/dev/dev-mode-state.d.ts +24 -0
- package/dist/dev/dev-mode-state.d.ts.map +1 -0
- package/dist/dev/dev-mode-state.js +93 -0
- package/dist/dev/dev-mode-state.js.map +1 -0
- package/dist/dev/file-watcher.d.ts +20 -0
- package/dist/dev/file-watcher.d.ts.map +1 -0
- package/dist/dev/file-watcher.js +78 -0
- package/dist/dev/file-watcher.js.map +1 -0
- package/dist/dev/static-server.d.ts +18 -0
- package/dist/dev/static-server.d.ts.map +1 -0
- package/dist/dev/static-server.js +73 -0
- package/dist/dev/static-server.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/renderer/accessibility-extractor.d.ts +19 -0
- package/dist/renderer/accessibility-extractor.d.ts.map +1 -0
- package/dist/renderer/accessibility-extractor.js +138 -0
- package/dist/renderer/accessibility-extractor.js.map +1 -0
- package/dist/renderer/content-extractor.d.ts +6 -0
- package/dist/renderer/content-extractor.d.ts.map +1 -0
- package/dist/renderer/content-extractor.js +150 -0
- package/dist/renderer/content-extractor.js.map +1 -0
- package/dist/renderer/dom-path.d.ts +4 -0
- package/dist/renderer/dom-path.d.ts.map +1 -0
- package/dist/renderer/dom-path.js +34 -0
- package/dist/renderer/dom-path.js.map +1 -0
- package/dist/renderer/element-id-generator.d.ts +19 -0
- package/dist/renderer/element-id-generator.d.ts.map +1 -0
- package/dist/renderer/element-id-generator.js +73 -0
- package/dist/renderer/element-id-generator.js.map +1 -0
- package/dist/renderer/interactive-extractor.d.ts +13 -0
- package/dist/renderer/interactive-extractor.d.ts.map +1 -0
- package/dist/renderer/interactive-extractor.js +161 -0
- package/dist/renderer/interactive-extractor.js.map +1 -0
- package/dist/renderer/layout-extractor.d.ts +8 -0
- package/dist/renderer/layout-extractor.d.ts.map +1 -0
- package/dist/renderer/layout-extractor.js +48 -0
- package/dist/renderer/layout-extractor.js.map +1 -0
- package/dist/renderer/renderer-pipeline.d.ts +26 -0
- package/dist/renderer/renderer-pipeline.d.ts.map +1 -0
- package/dist/renderer/renderer-pipeline.js +163 -0
- package/dist/renderer/renderer-pipeline.js.map +1 -0
- package/dist/server.d.ts +19 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +39 -0
- package/dist/server.js.map +1 -0
- package/dist/state/differ.d.ts +9 -0
- package/dist/state/differ.d.ts.map +1 -0
- package/dist/state/differ.js +295 -0
- package/dist/state/differ.js.map +1 -0
- package/dist/state/snapshot-store.d.ts +52 -0
- package/dist/state/snapshot-store.d.ts.map +1 -0
- package/dist/state/snapshot-store.js +98 -0
- package/dist/state/snapshot-store.js.map +1 -0
- package/dist/tools/dev-mode.d.ts +4 -0
- package/dist/tools/dev-mode.d.ts.map +1 -0
- package/dist/tools/dev-mode.js +160 -0
- package/dist/tools/dev-mode.js.map +1 -0
- package/dist/tools/evaluate.d.ts +10 -0
- package/dist/tools/evaluate.d.ts.map +1 -0
- package/dist/tools/evaluate.js +109 -0
- package/dist/tools/evaluate.js.map +1 -0
- package/dist/tools/interaction.d.ts +4 -0
- package/dist/tools/interaction.d.ts.map +1 -0
- package/dist/tools/interaction.js +680 -0
- package/dist/tools/interaction.js.map +1 -0
- package/dist/tools/navigation.d.ts +4 -0
- package/dist/tools/navigation.d.ts.map +1 -0
- package/dist/tools/navigation.js +136 -0
- package/dist/tools/navigation.js.map +1 -0
- package/dist/tools/observation.d.ts +4 -0
- package/dist/tools/observation.d.ts.map +1 -0
- package/dist/tools/observation.js +278 -0
- package/dist/tools/observation.js.map +1 -0
- package/dist/tools/session.d.ts +4 -0
- package/dist/tools/session.d.ts.map +1 -0
- package/dist/tools/session.js +372 -0
- package/dist/tools/session.js.map +1 -0
- package/dist/tools/tool-helpers.d.ts +89 -0
- package/dist/tools/tool-helpers.d.ts.map +1 -0
- package/dist/tools/tool-helpers.js +127 -0
- package/dist/tools/tool-helpers.js.map +1 -0
- package/dist/types/config.d.ts +7 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +7 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/element-id.d.ts +8 -0
- package/dist/types/element-id.d.ts.map +1 -0
- package/dist/types/element-id.js +19 -0
- package/dist/types/element-id.js.map +1 -0
- package/dist/types/errors.d.ts +22 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/errors.js +30 -0
- package/dist/types/errors.js.map +1 -0
- package/dist/types/page-representation.d.ts +84 -0
- package/dist/types/page-representation.d.ts.map +1 -0
- package/dist/types/page-representation.js +2 -0
- package/dist/types/page-representation.js.map +1 -0
- package/dist/types/snapshot.d.ts +22 -0
- package/dist/types/snapshot.d.ts.map +1 -0
- package/dist/types/snapshot.js +2 -0
- package/dist/types/snapshot.js.map +1 -0
- package/dist/utils/hash.d.ts +2 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/hash.js +6 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/logger.d.ts +9 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +31 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/wait.d.ts +21 -0
- package/dist/utils/wait.d.ts.map +1 -0
- package/dist/utils/wait.js +55 -0
- package/dist/utils/wait.js.map +1 -0
- 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 @@
|
|
|
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":"AAEA,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAgB5C,IAAI,gBAAgB,GAAG,CAAC,CAAC;AAEzB,SAAS,aAAa;IACpB,OAAO,OAAO,gBAAgB,EAAE,EAAE,CAAC;AACrC,CAAC;AAED,MAAM,OAAO,WAAW;IACd,KAAK,GAAG,IAAI,GAAG,EAAuB,CAAC;IACvC,WAAW,GAAkB,IAAI,CAAC;IAE1C,KAAK,CAAC,OAAO,CAAC,cAA8B,EAAE,GAAY;QACxD,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,OAAO,EAAE,CAAC;QAC5C,MAAM,KAAK,GAAG,aAAa,EAAE,CAAC;QAE9B,MAAM,WAAW,GAAgB;YAC/B,EAAE,EAAE,KAAK;YACT,IAAI;YACJ,aAAa,EAAE,EAAE;YACjB,aAAa,EAAE,EAAE;SAClB,CAAC;QAEF,yBAAyB;QACzB,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE;YACzB,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;YACzB,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;gBAC1C,WAAW,CAAC,aAAa,CAAC,IAAI,CAAC;oBAC7B,KAAK;oBACL,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE;iBACjB,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,yBAAyB;QACzB,IAAI,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,QAAQ,EAAE,EAAE;YAC/B,IAAI,QAAQ,CAAC,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;gBAC7B,WAAW,CAAC,aAAa,CAAC,IAAI,CAAC;oBAC7B,GAAG,EAAE,QAAQ,CAAC,GAAG,EAAE;oBACnB,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE;oBACzB,UAAU,EAAE,QAAQ,CAAC,UAAU,EAAE;iBAClC,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;QACnC,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;QAEzB,IAAI,GAAG,EAAE,CAAC;YACR,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;QAC9C,CAAC;QAED,MAAM,CAAC,IAAI,CAAC,cAAc,KAAK,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;QAC5C,OAAO,KAAK,CAAC;IACf,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,KAAa;QAC3B,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC1C,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,MAAM,IAAI,cAAc,CACtB,kBAAkB,CAAC,aAAa,EAChC,QAAQ,KAAK,aAAa,CAC3B,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;QACzB,MAAM,WAAW,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;QACtC,OAAO,WAAW,CAAC,IAAI,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,KAAa;QAC1B,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC1C,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,MAAM,IAAI,cAAc,CACtB,kBAAkB,CAAC,aAAa,EAChC,QAAQ,KAAK,aAAa,CAC3B,CAAC;QACJ,CAAC;QAED,MAAM,WAAW,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;QAC/B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAEzB,IAAI,IAAI,CAAC,WAAW,KAAK,KAAK,EAAE,CAAC;YAC/B,oCAAoC;YACpC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC;YAC3C,IAAI,CAAC,WAAW,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC;QAC7D,CAAC;QAED,MAAM,CAAC,IAAI,CAAC,cAAc,KAAK,EAAE,CAAC,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,MAAM,IAAI,GAAc,EAAE,CAAC;QAC3B,KAAK,MAAM,CAAC,EAAE,EAAE,WAAW,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC3C,IAAI,CAAC,IAAI,CAAC;gBACR,EAAE;gBACF,GAAG,EAAE,WAAW,CAAC,IAAI,CAAC,GAAG,EAAE;gBAC3B,KAAK,EAAE,MAAM,WAAW,CAAC,IAAI,CAAC,KAAK,EAAE;gBACrC,MAAM,EAAE,EAAE,KAAK,IAAI,CAAC,WAAW;aAChC,CAAC,CAAC;QACL,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,aAAa;QACX,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,MAAM,IAAI,cAAc,CACtB,kBAAkB,CAAC,aAAa,EAChC,kCAAkC,CACnC,CAAC;QACJ,CAAC;QAED,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACrD,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,MAAM,IAAI,cAAc,CACtB,kBAAkB,CAAC,aAAa,EAChC,sCAAsC,CACvC,CAAC;QACJ,CAAC;QAED,OAAO,WAAW,CAAC,IAAI,CAAC;IAC1B,CAAC;IAED,cAAc;QACZ,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,MAAM,IAAI,cAAc,CACtB,kBAAkB,CAAC,aAAa,EAChC,eAAe,CAChB,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAED,gBAAgB;QACd,IAAI,CAAC,IAAI,CAAC,WAAW;YAAE,OAAO,EAAE,CAAC;QACjC,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACrD,OAAO,WAAW,EAAE,aAAa,IAAI,EAAE,CAAC;IAC1C,CAAC;IAED,gBAAgB;QAKd,IAAI,CAAC,IAAI,CAAC,WAAW;YAAE,OAAO,EAAE,CAAC;QACjC,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACrD,OAAO,WAAW,EAAE,aAAa,IAAI,EAAE,CAAC;IAC1C,CAAC;IAED,WAAW;QACT,IAAI,CAAC,IAAI,CAAC,WAAW;YAAE,OAAO;QAC9B,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACrD,IAAI,WAAW,EAAE,CAAC;YAChB,WAAW,CAAC,aAAa,GAAG,EAAE,CAAC;YAC/B,WAAW,CAAC,aAAa,GAAG,EAAE,CAAC;QACjC,CAAC;IACH,CAAC;IAED,QAAQ;QACN,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC;IAC7B,CAAC;CACF"}
|
|
@@ -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"}
|