barebrowse 0.1.0 → 0.2.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/README.md CHANGED
@@ -1,69 +1,228 @@
1
1
  # barebrowse
2
2
 
3
- Vanilla JS library. CDP-direct. URL in, pruned ARIA snapshot out.
4
- No Playwright, no bundled browser, no build step.
3
+ **URL in, agent-ready snapshot out.** Zero dependencies. Uses your own browser.
5
4
 
6
- ## What It Does
5
+ barebrowse gives autonomous agents eyes and hands on the web. It launches your installed Chromium, navigates to any page, and returns a pruned ARIA snapshot — a compact, semantic representation of what's on screen. The agent reads the snapshot, picks an element by ref, acts, and reads the next snapshot. Observe, think, act.
7
6
 
8
- Gives autonomous agents authenticated access to the web through the user's own Chromium browser.
7
+ No Playwright. No bundled browser. No 200MB download. Just CDP over a WebSocket to whatever Chromium you already have.
9
8
 
10
- ```js
11
- import { browse, connect } from 'barebrowse';
9
+ ## The idea
12
10
 
13
- // One-shot: read a page
14
- const snapshot = await browse('https://any-page.com');
11
+ LLMs don't need DOM. They need to know what's on the page and what they can interact with. That's exactly what the browser's accessibility tree provides — every heading, button, link, input, and landmark, structured semantically.
12
+
13
+ But raw ARIA trees are noisy. A typical page produces 50-100KB of ARIA data. Most of it is decorative wrappers, hidden elements, and structural noise. barebrowse includes a 9-step pruning pipeline (ported from [mcprune](https://github.com/nickvdyck/mcprune)) that strips 47-95% of tokens while keeping every interactive element and meaningful label. A page that costs $0.15 in tokens raw costs $0.02-0.08 pruned.
14
+
15
+ The snapshot format uses `[ref=N]` markers on interactive elements. The agent says "click ref 8" and barebrowse scrolls the element into view, calculates coordinates, and dispatches real mouse events. No CSS selectors. No XPath. Just semantic refs from the ARIA tree.
16
+
17
+ ## Install
18
+
19
+ ```
20
+ npm install barebrowse
21
+ ```
22
+
23
+ Requires Node.js >= 22 and any installed Chromium-based browser (Chrome, Chromium, Brave, Edge, Vivaldi).
24
+
25
+ ## Quick start
26
+
27
+ ```javascript
28
+ import { browse } from 'barebrowse';
29
+
30
+ // One line. That's it.
31
+ const snapshot = await browse('https://news.ycombinator.com');
32
+ console.log(snapshot);
33
+ ```
34
+
35
+ Output (pruned, ~50% smaller than raw):
36
+ ```
37
+ - WebArea "Hacker News" [ref=1]
38
+ - link "Hacker News" [ref=4]
39
+ - link "new" [ref=7]
40
+ - link "past" [ref=9]
41
+ - link "comments" [ref=11]
42
+ ...
43
+ - link "Show HN: I built a thing" [ref=42]
44
+ - link "197 comments" [ref=45]
45
+ ```
46
+
47
+ ## Two ways to use it
48
+
49
+ ### 1. As a library (framework mode)
50
+
51
+ Import and call directly. You control the loop.
52
+
53
+ **One-shot** — read a page and get the snapshot:
54
+
55
+ ```javascript
56
+ import { browse } from 'barebrowse';
57
+
58
+ const snapshot = await browse('https://example.com', {
59
+ mode: 'headless', // 'headless' | 'headed' | 'hybrid'
60
+ cookies: true, // inject cookies from your browser
61
+ prune: true, // ARIA pruning (47-95% token reduction)
62
+ consent: true, // auto-dismiss cookie consent dialogs
63
+ });
64
+ ```
65
+
66
+ **Session** — navigate and interact across multiple pages:
67
+
68
+ ```javascript
69
+ import { connect } from 'barebrowse';
15
70
 
16
- // Session: navigate, interact, observe
17
71
  const page = await connect();
18
- await page.goto('https://any-page.com');
19
- console.log(await page.snapshot());
20
- await page.click('8'); // ref from snapshot
21
- await page.type('3', 'hello');
22
- await page.scroll(500);
72
+ await page.goto('https://duckduckgo.com');
73
+
74
+ let snap = await page.snapshot();
75
+ // Agent sees: combobox "Search" [ref=5]
76
+
77
+ await page.type('5', 'barebrowse github');
78
+ await page.press('Enter');
79
+ await page.waitForNavigation();
80
+
81
+ snap = await page.snapshot();
82
+ // Agent sees search results with clickable refs
83
+
84
+ await page.click('12'); // click first result
23
85
  await page.close();
24
86
  ```
25
87
 
26
- ## Features
88
+ ### 2. As an MCP server
27
89
 
28
- - **CDP direct** — no Playwright, no 200MB download, uses your installed Chromium
29
- - **ARIA snapshots** — semantic, token-efficient output for LLMs
30
- - **Built-in pruning** — 47-95% token reduction via 9-step pipeline
31
- - **Cookie extraction** — authenticated browsing from your existing sessions (Chromium + Firefox)
32
- - **Interactions** — click, type, scroll via CDP Input domain
33
- - **Three modes** — headless (default), headed (connect to running browser), hybrid (planned)
34
-
35
- ## Architecture
90
+ For Claude Desktop, Cursor, Windsurf, or any MCP client.
36
91
 
92
+ ```bash
93
+ npm install -g barebrowse
94
+ npx barebrowse install
37
95
  ```
38
- URL → chromium.js (find/launch browser)
39
- cdp.js (WebSocket CDP client)
40
- → auth.js (extract cookies → inject via CDP)
41
- Page.navigate
42
- → aria.js (Accessibility.getFullAXTree → nested tree)
43
- → prune.js (9-step role-based pruning)
44
- interact.js (click/type/scroll via Input domain)
45
- agent-ready snapshot
96
+
97
+ That's it. `install` auto-detects Claude Desktop, Cursor, and Claude Code, and writes the MCP config for you. No manual JSON editing.
98
+
99
+ If you prefer manual setup, add this to your MCP config:
100
+ ```json
101
+ {
102
+ "mcpServers": {
103
+ "barebrowse": {
104
+ "command": "npx",
105
+ "args": ["barebrowse", "mcp"]
106
+ }
107
+ }
108
+ }
46
109
  ```
47
110
 
48
- Seven modules, ~1,400 lines, zero required dependencies.
111
+ This exposes 7 tools: `browse`, `goto`, `snapshot`, `click`, `type`, `press`, `scroll`. The LLM calls `goto` to navigate, `snapshot` to observe, and action tools to interact. Action tools return `'ok'` -- the LLM calls `snapshot` explicitly to see what changed.
112
+
113
+ **Same package, two entry points.** `npm install barebrowse` gives you both the library (`import { browse } from 'barebrowse'`) and the MCP server (`npx barebrowse mcp`). Pick whichever fits your setup.
114
+
115
+ ## Three modes
116
+
117
+ | Mode | Flag | What happens | Use for |
118
+ |------|------|-------------|---------|
119
+ | **Headless** | `mode: 'headless'` (default) | Launches a fresh Chromium, no UI | Scraping, reading, fast automation |
120
+ | **Headed** | `mode: 'headed'` | Connects to your running browser via CDP port | Bot-detected sites, visual debugging |
121
+ | **Hybrid** | `mode: 'hybrid'` | Tries headless first, falls back to headed if bot-blocked | General-purpose agent browsing |
122
+
123
+ Headed mode requires your browser running with `--remote-debugging-port=9222`.
124
+
125
+ ## What it handles automatically
126
+
127
+ You don't need to write code for any of this:
128
+
129
+ - **Cookie consent dialogs** — ARIA scan + jsClick across 7 languages (EN, NL, DE, FR, ES, IT, PT). Tested on 16+ sites.
130
+ - **Permission prompts** — notifications, geolocation, camera, mic all auto-denied via CDP
131
+ - **Login walls** — cookies extracted from your Firefox or Chromium profile, injected via CDP
132
+ - **Off-screen elements** — scrolled into view before every click
133
+ - **Bot detection** — stealth patches in headless (navigator.webdriver, plugins, chrome object)
134
+ - **Profile locking** — unique temp dir per headless instance
135
+ - **ARIA noise** — 9-step pruning pipeline strips decorative wrappers, hidden nodes, structural noise
136
+
137
+ ## connect() API reference
138
+
139
+ | Method | Description |
140
+ |--------|-------------|
141
+ | `goto(url)` | Navigate + wait for load + dismiss consent |
142
+ | `snapshot()` | Pruned ARIA tree with `[ref=N]` markers |
143
+ | `click(ref)` | Scroll into view + mouse click at element center |
144
+ | `type(ref, text, opts?)` | Focus + insert text. `{ clear: true }` replaces existing. |
145
+ | `press(key)` | Special key: Enter, Tab, Escape, Backspace, Delete, arrows, Space |
146
+ | `scroll(deltaY)` | Mouse wheel. Positive = down, negative = up. |
147
+ | `hover(ref)` | Move mouse to element center |
148
+ | `select(ref, value)` | Set `<select>` value or click custom dropdown option |
149
+ | `screenshot(opts?)` | Returns base64 PNG/JPEG/WebP |
150
+ | `waitForNavigation()` | Wait for page load (SPA-aware) |
151
+ | `waitForNetworkIdle()` | Wait until no pending requests for 500ms |
152
+ | `injectCookies(url)` | Extract + inject cookies from your browser |
153
+ | `cdp` | Raw CDP session escape hatch |
154
+ | `close()` | Clean up everything |
155
+
156
+ ## bareagent integration
157
+
158
+ barebrowse ships a tool adapter for [bareagent](https://github.com/nickvdyck/bareagent):
159
+
160
+ ```javascript
161
+ import { Loop } from 'bare-agent';
162
+ import { Anthropic } from 'bare-agent/providers';
163
+ import { createBrowseTools } from 'barebrowse/bareagent';
164
+
165
+ const provider = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
166
+ const loop = new Loop({ provider });
167
+
168
+ const { tools, close } = createBrowseTools();
169
+ try {
170
+ const result = await loop.run(
171
+ [{ role: 'user', content: 'Find the top story on Hacker News' }],
172
+ tools
173
+ );
174
+ console.log(result.text);
175
+ } finally {
176
+ await close();
177
+ }
178
+ ```
49
179
 
50
- ## Requirements
180
+ `createBrowseTools(opts)` returns 9 tools: browse, goto, snapshot, click, type, press, scroll, select, screenshot. Action tools auto-return a fresh snapshot after each action so the LLM always sees the result.
51
181
 
52
- - Node.js >= 22
53
- - Any installed Chromium-based browser (Chrome, Chromium, Brave, Edge, Vivaldi, Arc, Opera)
182
+ You can pass any `connect()` options to `createBrowseTools()` — mode, port, cookies, consent.
54
183
 
55
- ## Ecosystem
184
+ ## How it works
56
185
 
57
186
  ```
58
- bareagent = the brain (orchestration, LLM loop, memory, retries)
59
- barebrowse = the eyes + hands (browse, read, interact with the web)
187
+ URL -> chromium.js find/launch browser, permission-suppressing flags
188
+ -> cdp.js WebSocket CDP client, flattened sessions
189
+ -> stealth.js navigator.webdriver patches (headless only)
190
+ -> Browser.setPermission suppress all prompts
191
+ -> auth.js extract cookies from Firefox/Chromium -> inject via CDP
192
+ -> Page.navigate go to URL, wait for load
193
+ -> consent.js detect + dismiss cookie consent dialogs
194
+ -> aria.js Accessibility.getFullAXTree -> nested tree
195
+ -> prune.js 9-step pruning: wrappers, noise, landmarks
196
+ -> interact.js click/type/scroll/hover/select via CDP Input domain
197
+ -> snapshot agent-ready ARIA text with [ref=N] markers
60
198
  ```
61
199
 
62
- barebrowse is a library. bareagent imports it as a capability.
200
+ 11 modules, 2,400 lines, zero dependencies.
201
+
202
+ ## Token savings
203
+
204
+ Real-world measurements on the pruning pipeline:
205
+
206
+ | Page | Raw ARIA | Pruned (act) | Reduction | Est. cost saved |
207
+ |------|----------|-------------|-----------|----------------|
208
+ | Hacker News | 52K chars | 27K chars | 47% | ~$0.04/call |
209
+ | Wikipedia article | 180K chars | 12K chars | 93% | ~$0.25/call |
210
+ | Amazon product | 95K chars | 8K chars | 92% | ~$0.13/call |
211
+ | Google results | 45K chars | 5K chars | 89% | ~$0.06/call |
63
212
 
64
- ## Status
213
+ Two pruning modes:
214
+ - **act** (default) — keeps interactive elements + visible labels. For clicking, typing, navigating.
215
+ - **read** — keeps all text content. For reading articles, extracting information.
216
+
217
+ ## Context file
218
+
219
+ `barebrowse.context.md` in the repo root is an LLM-consumable integration guide. Feed it to an AI assistant that needs to wire barebrowse into a project — it covers the full API, snapshot format, interaction loop, auth options, and gotchas.
220
+
221
+ ## Requirements
65
222
 
66
- Early development. API may change.
223
+ - Node.js >= 22 (built-in WebSocket, built-in SQLite)
224
+ - Any Chromium-based browser installed (Chrome, Chromium, Brave, Edge, Vivaldi)
225
+ - Linux tested (Fedora/KDE). macOS/Windows cookie paths exist but untested.
67
226
 
68
227
  ## License
69
228
 
@@ -0,0 +1,261 @@
1
+ # barebrowse -- Integration Guide
2
+
3
+ > For AI assistants and developers wiring barebrowse into a project.
4
+ > v0.1.0 | Node.js >= 22 | 0 required deps | MIT
5
+
6
+ ## What this is
7
+
8
+ barebrowse is a CDP-direct browsing library for autonomous agents (~1,800 lines). URL in, pruned ARIA snapshot out. It launches the user's installed Chromium browser, navigates, handles consent/permissions/cookies, and returns a token-efficient ARIA tree with `[ref=N]` markers for interaction.
9
+
10
+ No Playwright. No bundled browser. No build step. Vanilla JS, ES modules.
11
+
12
+ ```
13
+ npm install barebrowse
14
+ ```
15
+
16
+ Two entry points:
17
+ - `import { browse } from 'barebrowse'` -- one-shot: URL in, snapshot out
18
+ - `import { connect } from 'barebrowse'` -- session: navigate, interact, observe
19
+
20
+ ## Which mode do I need?
21
+
22
+ | Mode | What it does | When to use |
23
+ |---|---|---|
24
+ | `headless` (default) | Launches a fresh Chromium, no UI | Scraping, reading, fast automation |
25
+ | `headed` | Connects to user's running browser on CDP port | Bot-detected sites, debugging, visual tasks |
26
+ | `hybrid` | Tries headless first, falls back to headed if blocked | General-purpose agent browsing |
27
+
28
+ Headed mode requires the browser to be launched with `--remote-debugging-port=9222`.
29
+
30
+ ## Minimal usage: one-shot browse
31
+
32
+ ```javascript
33
+ import { browse } from 'barebrowse';
34
+
35
+ // Defaults: headless, cookies injected, pruned, consent dismissed
36
+ const snapshot = await browse('https://example.com');
37
+
38
+ // All options
39
+ const snapshot = await browse('https://example.com', {
40
+ mode: 'headless', // 'headless' | 'headed' | 'hybrid'
41
+ cookies: true, // inject user's browser cookies
42
+ browser: 'firefox', // cookie source: 'firefox' | 'chromium' (auto-detected)
43
+ prune: true, // apply ARIA pruning (47-95% token reduction)
44
+ pruneMode: 'act', // 'act' (interactive elements) | 'read' (all content)
45
+ consent: true, // auto-dismiss cookie consent dialogs
46
+ timeout: 30000, // navigation timeout in ms
47
+ port: 9222, // CDP port for headed/hybrid mode
48
+ });
49
+ ```
50
+
51
+ ## connect() API
52
+
53
+ `connect(opts)` returns a page handle for interactive sessions. Same opts as `browse()` for mode/port.
54
+
55
+ | Method | Args | Returns | Notes |
56
+ |---|---|---|---|
57
+ | `goto(url, timeout?)` | url: string, timeout: number (default 30000) | void | Navigate + wait for load + dismiss consent |
58
+ | `snapshot(pruneOpts?)` | false or { mode: 'act'\|'read' } | string | ARIA tree with `[ref=N]` markers. Pass `false` for raw. |
59
+ | `click(ref)` | ref: string | void | Scroll into view + mouse press+release at center |
60
+ | `type(ref, text, opts?)` | ref: string, text: string, opts: { clear?, keyEvents? } | void | Focus + insert text. `clear: true` replaces existing. |
61
+ | `press(key)` | key: string | void | Special key: Enter, Tab, Escape, Backspace, Delete, arrows, Home, End, PageUp, PageDown, Space |
62
+ | `scroll(deltaY)` | deltaY: number | void | Mouse wheel. Positive = down, negative = up. |
63
+ | `hover(ref)` | ref: string | void | Move mouse to element center |
64
+ | `select(ref, value)` | ref: string, value: string | void | Set `<select>` value or click custom dropdown option |
65
+ | `screenshot(opts?)` | { format?: 'png'\|'jpeg'\|'webp', quality?: number } | string (base64) | Page screenshot |
66
+ | `waitForNavigation(timeout?)` | timeout: number (default 30000) | void | Wait for page load or frame navigation |
67
+ | `waitForNetworkIdle(opts?)` | { timeout?: number, idle?: number } | void | Wait until no pending requests for `idle` ms (default 500) |
68
+ | `injectCookies(url, opts?)` | url: string, { browser?: string } | void | Extract cookies from user's browser and inject via CDP |
69
+ | `cdp` | -- | object | Raw CDP session for escape hatch: `page.cdp.send(method, params)` |
70
+ | `close()` | -- | void | Close page, disconnect CDP, kill browser (if headless) |
71
+
72
+ ## Snapshot format
73
+
74
+ The snapshot is a YAML-like ARIA tree. Each line is one node:
75
+
76
+ ```
77
+ - WebArea "Example Domain" [ref=1]
78
+ - heading "Example Domain" [level=1] [ref=3]
79
+ - paragraph [ref=5]
80
+ - StaticText "This domain is for use in illustrative examples." [ref=6]
81
+ - link "More information..." [ref=8]
82
+ ```
83
+
84
+ Key rules:
85
+ - `[ref=N]` markers appear on interactive and named elements
86
+ - Refs are **ephemeral** -- they change on every `snapshot()` call
87
+ - Always call `snapshot()` to get fresh refs before interacting
88
+ - `click(ref)` / `type(ref, text)` / `hover(ref)` / `select(ref, value)` use these ref strings
89
+ - Pruning removes noise (~47-95% token reduction) while keeping all interactive elements
90
+
91
+ ## Interaction loop: observe, think, act
92
+
93
+ ```javascript
94
+ import { connect } from 'barebrowse';
95
+
96
+ const page = await connect();
97
+ await page.goto('https://example.com');
98
+
99
+ // 1. Observe
100
+ let snap = await page.snapshot();
101
+
102
+ // 2. Think (LLM decides what to do based on snapshot)
103
+ // 3. Act
104
+ await page.click('8'); // click the "More information..." link
105
+ await page.waitForNavigation();
106
+
107
+ // 4. Observe again (refs are now different)
108
+ snap = await page.snapshot();
109
+
110
+ // ... repeat until goal is achieved
111
+
112
+ await page.close();
113
+ ```
114
+
115
+ ## Auth / cookie options
116
+
117
+ barebrowse can inject cookies from the user's real browser sessions, bypassing login walls.
118
+
119
+ | Source | How | Notes |
120
+ |---|---|---|
121
+ | Firefox (default) | SQLite `cookies.sqlite`, plaintext | Works on Linux. Auto-detected default profile. |
122
+ | Chromium | SQLite `Cookies` + AES decryption via keyring | Linux: KWallet or GNOME Keyring. Profile must not be locked. |
123
+ | Manual | `page.injectCookies(url, { browser: 'firefox' })` | Explicit injection on connect() sessions |
124
+ | Disabled | `{ cookies: false }` | No cookie injection |
125
+
126
+ `browse()` auto-injects cookies before navigation. `connect()` exposes `injectCookies()` for manual control.
127
+
128
+ ## Obstacle course -- what barebrowse handles automatically
129
+
130
+ | Obstacle | How | Mode |
131
+ |---|---|---|
132
+ | Cookie consent (GDPR) | ARIA scan + jsClick accept button, 7 languages | Both |
133
+ | Consent behind iframes | JS `.click()` via DOM.resolveNode bypasses overlays | Both |
134
+ | Permission prompts | Launch flags + CDP Browser.setPermission auto-deny | Both |
135
+ | Media autoplay blocked | `--autoplay-policy=no-user-gesture-required` | Both |
136
+ | Login walls | Cookie extraction from Firefox/Chromium + CDP injection | Both |
137
+ | Pre-filled form inputs | `type({ clear: true })` selects all + deletes first | Both |
138
+ | Off-screen elements | `DOM.scrollIntoViewIfNeeded` before every click | Both |
139
+ | Form submission | `press('Enter')` triggers onsubmit | Both |
140
+ | SPA navigation | `waitForNavigation()` uses loadEventFired + frameNavigated | Both |
141
+ | Bot detection | Headed mode with real cookies bypasses most checks | Headed |
142
+ | `navigator.webdriver` | Stealth patches in headless (webdriver, plugins, chrome obj) | Headless |
143
+ | Profile locking | Unique temp dir per headless instance | Headless |
144
+ | ARIA noise | 9-step pruning: wrapper collapse, noise removal, landmark promotion | Both |
145
+
146
+ ## bareagent wiring
147
+
148
+ barebrowse provides a tool adapter for bareagent's Loop:
149
+
150
+ ```javascript
151
+ import { Loop } from 'bare-agent';
152
+ import { Anthropic } from 'bare-agent/providers';
153
+ import { createBrowseTools } from 'barebrowse/src/bareagent.js';
154
+
155
+ const provider = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
156
+ const loop = new Loop({ provider });
157
+
158
+ const { tools, close } = createBrowseTools();
159
+
160
+ try {
161
+ const result = await loop.run(
162
+ [{ role: 'user', content: 'Search for "barebrowse" on DuckDuckGo and tell me the first result' }],
163
+ tools
164
+ );
165
+ console.log(result.text);
166
+ } finally {
167
+ await close();
168
+ }
169
+ ```
170
+
171
+ `createBrowseTools(opts)` returns:
172
+ - `tools` -- array of bareagent-compatible tool objects (browse, goto, snapshot, click, type, press, scroll, select, screenshot)
173
+ - `close()` -- cleanup function, call when done
174
+
175
+ Action tools (click, type, press, scroll, goto) auto-return a fresh snapshot so the LLM always sees the result. 300ms settle delay after actions for DOM updates.
176
+
177
+ ## MCP wrapper
178
+
179
+ barebrowse ships an MCP server for direct use with Claude Desktop, Cursor, or any MCP client.
180
+
181
+ ```bash
182
+ npm install barebrowse # or npm install -g barebrowse
183
+ ```
184
+
185
+ Add to your MCP client config (`.mcp.json`, `claude_desktop_config.json`, etc.):
186
+ ```json
187
+ {
188
+ "mcpServers": {
189
+ "barebrowse": {
190
+ "command": "npx",
191
+ "args": ["barebrowse", "mcp"]
192
+ }
193
+ }
194
+ }
195
+ ```
196
+
197
+ 7 tools exposed: `browse` (one-shot), `goto`, `snapshot`, `click`, `type`, `press`, `scroll`.
198
+
199
+ Action tools return `'ok'` -- the agent calls `snapshot` explicitly to observe. This avoids double-token output since MCP tool calls are cheap to chain.
200
+
201
+ Session tools (goto, snapshot, click, type, press, scroll) share a singleton page, lazy-created on first use.
202
+
203
+ ## Architecture
204
+
205
+ ```
206
+ URL -> chromium.js (find/launch browser, permission flags)
207
+ -> cdp.js (WebSocket CDP client)
208
+ -> stealth.js (navigator.webdriver patches, headless only)
209
+ -> Browser.setPermission (suppress prompts)
210
+ -> auth.js (extract cookies -> inject via CDP)
211
+ -> Page.navigate
212
+ -> consent.js (detect + dismiss cookie dialogs)
213
+ -> aria.js (Accessibility.getFullAXTree -> nested tree)
214
+ -> prune.js (9-step role-based pruning)
215
+ -> interact.js (click/type/scroll/hover/select via Input domain)
216
+ -> agent-ready snapshot
217
+ ```
218
+
219
+ | Module | Lines | Purpose |
220
+ |---|---|---|
221
+ | `src/index.js` | ~370 | Public API: `browse()`, `connect()`, screenshot, network idle, hybrid |
222
+ | `src/cdp.js` | 148 | WebSocket CDP client, flattened sessions |
223
+ | `src/chromium.js` | 148 | Find/launch Chromium browsers, permission-suppressing flags |
224
+ | `src/aria.js` | 69 | Format ARIA tree as text |
225
+ | `src/auth.js` | 279 | Cookie extraction (Chromium AES + keyring, Firefox), CDP injection |
226
+ | `src/prune.js` | 472 | ARIA pruning pipeline (ported from mcprune) |
227
+ | `src/interact.js` | ~170 | Click, type, press, scroll, hover, select |
228
+ | `src/consent.js` | 200 | Auto-dismiss cookie consent dialogs across languages |
229
+ | `src/stealth.js` | ~40 | Navigator patches for headless anti-detection |
230
+ | `src/bareagent.js` | ~120 | Tool adapter for bareagent Loop |
231
+ | `mcp-server.js` | ~170 | MCP server (JSON-RPC over stdio) |
232
+
233
+ ## Gotchas
234
+
235
+ 1. **Refs are ephemeral.** Every `snapshot()` call generates new refs. Always snapshot before interacting. Never cache refs across snapshots.
236
+
237
+ 2. **SPA navigation has no loadEventFired.** For single-page apps (React, YouTube, GitHub), use `waitForNetworkIdle()` or a timed wait after click instead of `waitForNavigation()`.
238
+
239
+ 3. **Pruning modes matter.** `act` mode (default) keeps interactive elements + visible labels. `read` mode keeps all text content. Use `read` for content extraction, `act` for form filling and navigation.
240
+
241
+ 4. **Headed mode requires manual browser launch.** Start your browser with `--remote-debugging-port=9222`. barebrowse connects to it -- it does not launch it.
242
+
243
+ 5. **Cookie extraction needs unlocked profile.** Chromium cookies are AES-encrypted with a keyring key. If Chromium is running, the profile may be locked. Firefox cookies are plaintext and always accessible.
244
+
245
+ 6. **Hybrid mode kills and relaunches.** If headless is bot-blocked, hybrid mode kills the headless browser and connects to headed on port 9222. The headed browser must already be running.
246
+
247
+ 7. **One page per connect().** Each `connect()` call creates one page. For multiple tabs, call `connect()` multiple times.
248
+
249
+ 8. **Consent dismiss is best-effort.** It handles 16+ tested sites across 7 languages but novel consent implementations may need manual handling. Disable with `{ consent: false }`.
250
+
251
+ 9. **Screenshot returns base64.** Write to file with `fs.writeFileSync('shot.png', Buffer.from(base64, 'base64'))` or pass directly to a vision model.
252
+
253
+ 10. **Chromium-only.** CDP protocol limits us to Chrome, Chromium, Edge, Brave, Vivaldi (~80% desktop share). Firefox support via WebDriver BiDi is not yet implemented.
254
+
255
+ ## Constraints
256
+
257
+ - **Node >= 22** -- built-in WebSocket, built-in SQLite
258
+ - **Chromium-only** -- CDP protocol
259
+ - **Linux first** -- tested on Fedora/KDE, macOS/Windows cookie paths exist but untested
260
+ - **Not a server** -- library that agents import. Wrap as MCP (included) or HTTP if needed.
261
+ - **Zero required deps** -- everything uses Node stdlib
package/cli.js ADDED
@@ -0,0 +1,156 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * cli.js -- barebrowse CLI entry point.
4
+ *
5
+ * Usage:
6
+ * npx barebrowse mcp Start the MCP server (JSON-RPC over stdio)
7
+ * npx barebrowse install Auto-configure MCP in Claude Desktop / Cursor / Claude Code
8
+ * npx barebrowse browse <url> One-shot browse, print snapshot to stdout
9
+ */
10
+
11
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
12
+ import { join } from 'node:path';
13
+ import { homedir, platform } from 'node:os';
14
+
15
+ const cmd = process.argv[2];
16
+
17
+ if (cmd === 'mcp') {
18
+ await import('./mcp-server.js');
19
+
20
+ } else if (cmd === 'install') {
21
+ install();
22
+
23
+ } else if (cmd === 'browse' && process.argv[3]) {
24
+ const { browse } = await import('./src/index.js');
25
+ const url = process.argv[3];
26
+ const mode = process.argv[4] || 'headless';
27
+ try {
28
+ const snapshot = await browse(url, { mode });
29
+ process.stdout.write(snapshot + '\n');
30
+ process.exit(0);
31
+ } catch (err) {
32
+ process.stderr.write(`Error: ${err.message}\n`);
33
+ process.exit(1);
34
+ }
35
+
36
+ } else {
37
+ process.stdout.write(`barebrowse -- CDP-direct browsing for autonomous agents
38
+
39
+ Usage:
40
+ barebrowse mcp Start MCP server (JSON-RPC over stdio)
41
+ barebrowse install Auto-configure MCP for Claude Desktop / Cursor / Claude Code
42
+ barebrowse browse <url> One-shot browse, print ARIA snapshot
43
+
44
+ As a library:
45
+ import { browse, connect } from 'barebrowse';
46
+
47
+ As bareagent tools:
48
+ import { createBrowseTools } from 'barebrowse/bareagent';
49
+
50
+ More: see README.md or barebrowse.context.md
51
+ `);
52
+ }
53
+
54
+ // --- MCP auto-installer ---
55
+
56
+ function install() {
57
+ const mcpEntry = {
58
+ command: 'npx',
59
+ args: ['barebrowse', 'mcp'],
60
+ };
61
+
62
+ const targets = detectTargets();
63
+
64
+ if (targets.length === 0) {
65
+ console.log('No MCP clients detected. You can manually add this to your MCP config:\n');
66
+ console.log(JSON.stringify({ mcpServers: { barebrowse: mcpEntry } }, null, 2));
67
+ console.log('\nSupported clients: Claude Desktop, Cursor, Claude Code');
68
+ return;
69
+ }
70
+
71
+ let installed = 0;
72
+
73
+ for (const target of targets) {
74
+ try {
75
+ const config = readJsonOrEmpty(target.path);
76
+ if (!config.mcpServers) config.mcpServers = {};
77
+
78
+ if (config.mcpServers.barebrowse) {
79
+ console.log(` ${target.name}: already configured`);
80
+ installed++;
81
+ continue;
82
+ }
83
+
84
+ config.mcpServers.barebrowse = mcpEntry;
85
+
86
+ // Ensure parent dir exists
87
+ const dir = join(target.path, '..');
88
+ mkdirSync(dir, { recursive: true });
89
+
90
+ writeFileSync(target.path, JSON.stringify(config, null, 2) + '\n');
91
+ console.log(` ${target.name}: installed -> ${target.path}`);
92
+ installed++;
93
+ } catch (err) {
94
+ console.log(` ${target.name}: failed (${err.message})`);
95
+ }
96
+ }
97
+
98
+ if (installed > 0) {
99
+ console.log(`\nDone. Restart your MCP client to pick up the new server.`);
100
+ console.log('Tools available: browse, goto, snapshot, click, type, press, scroll');
101
+ }
102
+ }
103
+
104
+ function detectTargets() {
105
+ const home = homedir();
106
+ const os = platform();
107
+ const targets = [];
108
+
109
+ // Claude Desktop
110
+ let claudeDesktop;
111
+ if (os === 'darwin') {
112
+ claudeDesktop = join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
113
+ } else if (os === 'linux') {
114
+ claudeDesktop = join(home, '.config', 'Claude', 'claude_desktop_config.json');
115
+ } else if (os === 'win32') {
116
+ claudeDesktop = join(home, 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json');
117
+ }
118
+ if (claudeDesktop) {
119
+ // Check if Claude Desktop dir exists (even if config doesn't yet)
120
+ const dir = join(claudeDesktop, '..');
121
+ if (existsSync(dir)) {
122
+ targets.push({ name: 'Claude Desktop', path: claudeDesktop });
123
+ }
124
+ }
125
+
126
+ // Cursor
127
+ let cursorDir;
128
+ if (os === 'darwin') {
129
+ cursorDir = join(home, '.cursor');
130
+ } else if (os === 'linux') {
131
+ cursorDir = join(home, '.cursor');
132
+ } else if (os === 'win32') {
133
+ cursorDir = join(home, '.cursor');
134
+ }
135
+ if (cursorDir && existsSync(cursorDir)) {
136
+ targets.push({ name: 'Cursor', path: join(cursorDir, 'mcp.json') });
137
+ }
138
+
139
+ // Claude Code (project-level .mcp.json in cwd)
140
+ const cwd = process.cwd();
141
+ const claudeCodePath = join(cwd, '.mcp.json');
142
+ // Only suggest if we're in a project directory (has package.json or .git)
143
+ if (existsSync(join(cwd, 'package.json')) || existsSync(join(cwd, '.git'))) {
144
+ targets.push({ name: 'Claude Code (this project)', path: claudeCodePath });
145
+ }
146
+
147
+ return targets;
148
+ }
149
+
150
+ function readJsonOrEmpty(path) {
151
+ try {
152
+ return JSON.parse(readFileSync(path, 'utf8'));
153
+ } catch {
154
+ return {};
155
+ }
156
+ }