barebrowse 0.2.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli.js CHANGED
@@ -2,28 +2,192 @@
2
2
  /**
3
3
  * cli.js -- barebrowse CLI entry point.
4
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
5
+ * Session commands:
6
+ * barebrowse open [url] [flags] Open browser session (daemon)
7
+ * barebrowse close Close session + kill daemon
8
+ * barebrowse status Check if session is running
9
+ *
10
+ * Navigation:
11
+ * barebrowse goto <url> Navigate to URL
12
+ * barebrowse snapshot [--mode] Get pruned ARIA snapshot → file
13
+ * barebrowse screenshot [--format] Take screenshot → file
14
+ *
15
+ * Interaction:
16
+ * barebrowse click <ref> Click element by ref
17
+ * barebrowse type <ref> <text> Type text into element
18
+ * barebrowse fill <ref> <text> Clear + type (replace content)
19
+ * barebrowse press <key> Press special key
20
+ * barebrowse scroll <deltaY> Scroll page
21
+ * barebrowse hover <ref> Hover over element
22
+ * barebrowse select <ref> <value> Select dropdown value
23
+ *
24
+ * Self-sufficiency:
25
+ * barebrowse eval <expression> Evaluate JS in page
26
+ * barebrowse wait-idle [--timeout] Wait for network idle
27
+ * barebrowse console-logs Dump console logs → file
28
+ * barebrowse network-log Dump network log → file
29
+ *
30
+ * Legacy / tools:
31
+ * barebrowse browse <url> [mode] One-shot browse (stdout)
32
+ * barebrowse mcp Start MCP server (stdio)
33
+ * barebrowse install [--skill] Auto-configure MCP or install skill
9
34
  */
10
35
 
11
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
12
- import { join } from 'node:path';
36
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync } from 'node:fs';
37
+ import { join, resolve } from 'node:path';
13
38
  import { homedir, platform } from 'node:os';
39
+ import { fileURLToPath } from 'node:url';
14
40
 
15
- const cmd = process.argv[2];
41
+ const args = process.argv.slice(2);
42
+ const cmd = args[0];
16
43
 
17
- if (cmd === 'mcp') {
44
+ // Hidden internal flag: --daemon-internal
45
+ if (args.includes('--daemon-internal')) {
46
+ await runDaemonInternal();
47
+ } else if (cmd === 'mcp') {
18
48
  await import('./mcp-server.js');
19
-
20
49
  } else if (cmd === 'install') {
21
50
  install();
51
+ } else if (cmd === 'browse' && args[1]) {
52
+ await oneShot();
53
+ } else if (cmd === 'open') {
54
+ await cmdOpen();
55
+ } else if (cmd === 'close') {
56
+ await cmdProxy('close');
57
+ } else if (cmd === 'status') {
58
+ await cmdStatus();
59
+ } else if (cmd === 'goto' && args[1]) {
60
+ await cmdProxy('goto', { url: args[1], timeout: parseFlag('--timeout') });
61
+ } else if (cmd === 'snapshot') {
62
+ await cmdProxy('snapshot', { mode: parseFlag('--mode') });
63
+ } else if (cmd === 'screenshot') {
64
+ await cmdProxy('screenshot', { format: parseFlag('--format') });
65
+ } else if (cmd === 'click' && args[1]) {
66
+ await cmdProxy('click', { ref: args[1] });
67
+ } else if (cmd === 'type' && args[1] && args[2]) {
68
+ await cmdProxy('type', { ref: args[1], text: args.slice(2).filter(a => !a.startsWith('--')).join(' '), clear: hasFlag('--clear') });
69
+ } else if (cmd === 'fill' && args[1] && args[2]) {
70
+ await cmdProxy('fill', { ref: args[1], text: args.slice(2).filter(a => !a.startsWith('--')).join(' ') });
71
+ } else if (cmd === 'press' && args[1]) {
72
+ await cmdProxy('press', { key: args[1] });
73
+ } else if (cmd === 'scroll' && args[1]) {
74
+ await cmdProxy('scroll', { deltaY: Number(args[1]) });
75
+ } else if (cmd === 'hover' && args[1]) {
76
+ await cmdProxy('hover', { ref: args[1] });
77
+ } else if (cmd === 'select' && args[1] && args[2]) {
78
+ await cmdProxy('select', { ref: args[1], value: args[2] });
79
+ } else if (cmd === 'eval' && args[1]) {
80
+ await cmdProxy('eval', { expression: args.slice(1).join(' ') });
81
+ } else if (cmd === 'wait-idle') {
82
+ await cmdProxy('wait-idle', { timeout: parseFlag('--timeout') });
83
+ } else if (cmd === 'console-logs') {
84
+ await cmdProxy('console-logs', { level: parseFlag('--level'), clear: hasFlag('--clear') });
85
+ } else if (cmd === 'network-log') {
86
+ await cmdProxy('network-log', { failed: hasFlag('--failed') });
87
+ } else {
88
+ printUsage();
89
+ }
90
+
91
+
92
+ // --- Command implementations ---
93
+
94
+ async function cmdOpen() {
95
+ const { startDaemon } = await import('./src/daemon.js');
96
+ const { isAlive } = await import('./src/session-client.js');
97
+ const outputDir = resolve('.barebrowse');
98
+
99
+ // Check for existing session
100
+ if (await isAlive(outputDir)) {
101
+ process.stdout.write('Session already running. Use `barebrowse close` first.\n');
102
+ process.exit(1);
103
+ }
104
+
105
+ const url = args[1] && !args[1].startsWith('--') ? args[1] : undefined;
106
+ const opts = {
107
+ mode: parseFlag('--mode') || 'headless',
108
+ port: parseFlag('--port'),
109
+ cookies: !hasFlag('--no-cookies'),
110
+ browser: parseFlag('--browser'),
111
+ timeout: parseFlag('--timeout'),
112
+ pruneMode: parseFlag('--prune-mode') || 'act',
113
+ consent: !hasFlag('--no-consent'),
114
+ };
115
+
116
+ try {
117
+ const session = await startDaemon(opts, outputDir, url);
118
+ process.stdout.write(`Session started (pid ${session.pid}, port ${session.port})\n`);
119
+ if (url) process.stdout.write(`Navigated to ${url}\n`);
120
+ process.stdout.write(`Output dir: ${outputDir}\n`);
121
+ } catch (err) {
122
+ process.stderr.write(`Error: ${err.message}\n`);
123
+ process.exit(1);
124
+ }
125
+ }
22
126
 
23
- } else if (cmd === 'browse' && process.argv[3]) {
127
+ async function cmdStatus() {
128
+ const { readSession, isAlive } = await import('./src/session-client.js');
129
+ const outputDir = resolve('.barebrowse');
130
+ const session = readSession(outputDir);
131
+
132
+ if (!session) {
133
+ process.stdout.write('No session found.\n');
134
+ process.exit(1);
135
+ }
136
+
137
+ const alive = await isAlive(outputDir);
138
+ if (alive) {
139
+ process.stdout.write(`Session running (pid ${session.pid}, port ${session.port}, started ${session.startedAt})\n`);
140
+ } else {
141
+ process.stdout.write(`Session stale (pid ${session.pid} not responding). Run \`barebrowse close\` to clean up.\n`);
142
+ process.exit(1);
143
+ }
144
+ }
145
+
146
+ async function cmdProxy(command, cmdArgs) {
147
+ const { sendCommand, readSession } = await import('./src/session-client.js');
148
+ const { unlinkSync } = await import('node:fs');
149
+ const outputDir = resolve('.barebrowse');
150
+
151
+ try {
152
+ const result = await sendCommand(command, cmdArgs, outputDir);
153
+
154
+ if (!result.ok) {
155
+ process.stderr.write(`Error: ${result.error}\n`);
156
+ process.exit(1);
157
+ }
158
+
159
+ // Print result
160
+ if (result.file && result.count !== undefined) {
161
+ process.stdout.write(`${result.file} (${result.count} entries)\n`);
162
+ } else if (result.file) {
163
+ process.stdout.write(`${result.file}\n`);
164
+ } else if (result.value !== undefined) {
165
+ process.stdout.write(JSON.stringify(result.value) + '\n');
166
+ } else if (command === 'close') {
167
+ // Clean up session.json in case daemon didn't
168
+ const sessionPath = join(outputDir, 'session.json');
169
+ try { unlinkSync(sessionPath); } catch { /* already gone */ }
170
+ process.stdout.write('Session closed.\n');
171
+ } else {
172
+ process.stdout.write('ok\n');
173
+ }
174
+ } catch (err) {
175
+ if (command === 'close') {
176
+ // Daemon may have exited before responding — that's fine
177
+ const sessionPath = join(outputDir, 'session.json');
178
+ try { unlinkSync(sessionPath); } catch { /* already gone */ }
179
+ process.stdout.write('Session closed.\n');
180
+ } else {
181
+ process.stderr.write(`Error: ${err.message}\n`);
182
+ process.exit(1);
183
+ }
184
+ }
185
+ }
186
+
187
+ async function oneShot() {
24
188
  const { browse } = await import('./src/index.js');
25
- const url = process.argv[3];
26
- const mode = process.argv[4] || 'headless';
189
+ const url = args[1];
190
+ const mode = args[2] || 'headless';
27
191
  try {
28
192
  const snapshot = await browse(url, { mode });
29
193
  process.stdout.write(snapshot + '\n');
@@ -32,28 +196,50 @@ if (cmd === 'mcp') {
32
196
  process.stderr.write(`Error: ${err.message}\n`);
33
197
  process.exit(1);
34
198
  }
199
+ }
35
200
 
36
- } else {
37
- process.stdout.write(`barebrowse -- CDP-direct browsing for autonomous agents
201
+ async function runDaemonInternal() {
202
+ const { runDaemon } = await import('./src/daemon.js');
203
+ const opts = {
204
+ mode: parseFlag('--mode') || 'headless',
205
+ port: parseFlag('--port'),
206
+ cookies: !hasFlag('--no-cookies'),
207
+ browser: parseFlag('--browser'),
208
+ timeout: parseFlag('--timeout'),
209
+ pruneMode: parseFlag('--prune-mode') || 'act',
210
+ consent: !hasFlag('--no-consent'),
211
+ };
212
+ const outputDir = parseFlag('--output-dir') || resolve('.barebrowse');
213
+ const url = parseFlag('--url');
214
+ await runDaemon(opts, outputDir, url || undefined);
215
+ }
38
216
 
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
217
 
44
- As a library:
45
- import { browse, connect } from 'barebrowse';
218
+ // --- Flag parsing helpers ---
46
219
 
47
- As bareagent tools:
48
- import { createBrowseTools } from 'barebrowse/bareagent';
220
+ function parseFlag(name) {
221
+ // --name=value or --name value
222
+ for (let i = 0; i < args.length; i++) {
223
+ if (args[i].startsWith(name + '=')) return args[i].slice(name.length + 1);
224
+ if (args[i] === name && args[i + 1] && !args[i + 1].startsWith('--')) return args[i + 1];
225
+ }
226
+ return undefined;
227
+ }
49
228
 
50
- More: see README.md or barebrowse.context.md
51
- `);
229
+ function hasFlag(name) {
230
+ return args.includes(name);
52
231
  }
53
232
 
233
+
54
234
  // --- MCP auto-installer ---
55
235
 
56
236
  function install() {
237
+ // Handle --skill flag
238
+ if (hasFlag('--skill')) {
239
+ installSkill();
240
+ return;
241
+ }
242
+
57
243
  const mcpEntry = {
58
244
  command: 'npx',
59
245
  args: ['barebrowse', 'mcp'],
@@ -62,10 +248,7 @@ function install() {
62
248
  const targets = detectTargets();
63
249
 
64
250
  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;
251
+ console.log('No MCP clients detected.\n');
69
252
  }
70
253
 
71
254
  let installed = 0;
@@ -83,7 +266,6 @@ function install() {
83
266
 
84
267
  config.mcpServers.barebrowse = mcpEntry;
85
268
 
86
- // Ensure parent dir exists
87
269
  const dir = join(target.path, '..');
88
270
  mkdirSync(dir, { recursive: true });
89
271
 
@@ -97,8 +279,28 @@ function install() {
97
279
 
98
280
  if (installed > 0) {
99
281
  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
282
  }
283
+
284
+ // Always print Claude Code hint (it uses `claude mcp add`, not JSON config)
285
+ console.log(`\nClaude Code: run this instead of install:`);
286
+ console.log(` claude mcp add barebrowse -- npx barebrowse mcp\n`);
287
+ }
288
+
289
+ function installSkill() {
290
+ const thisDir = fileURLToPath(new URL('.', import.meta.url));
291
+ const src = join(thisDir, '.claude', 'skills', 'barebrowse', 'SKILL.md');
292
+
293
+ if (!existsSync(src)) {
294
+ console.error('SKILL.md not found in package. Reinstall barebrowse.');
295
+ process.exit(1);
296
+ }
297
+
298
+ const dest = join(homedir(), '.config', 'claude', 'skills', 'barebrowse', 'SKILL.md');
299
+ const destDir = join(dest, '..');
300
+ mkdirSync(destDir, { recursive: true });
301
+ copyFileSync(src, dest);
302
+ console.log(`Skill installed: ${dest}`);
303
+ console.log('Claude Code will now see barebrowse as an available skill.');
102
304
  }
103
305
 
104
306
  function detectTargets() {
@@ -116,7 +318,6 @@ function detectTargets() {
116
318
  claudeDesktop = join(home, 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json');
117
319
  }
118
320
  if (claudeDesktop) {
119
- // Check if Claude Desktop dir exists (even if config doesn't yet)
120
321
  const dir = join(claudeDesktop, '..');
121
322
  if (existsSync(dir)) {
122
323
  targets.push({ name: 'Claude Desktop', path: claudeDesktop });
@@ -124,26 +325,11 @@ function detectTargets() {
124
325
  }
125
326
 
126
327
  // 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)) {
328
+ const cursorDir = join(home, '.cursor');
329
+ if (existsSync(cursorDir)) {
136
330
  targets.push({ name: 'Cursor', path: join(cursorDir, 'mcp.json') });
137
331
  }
138
332
 
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
333
  return targets;
148
334
  }
149
335
 
@@ -154,3 +340,58 @@ function readJsonOrEmpty(path) {
154
340
  return {};
155
341
  }
156
342
  }
343
+
344
+
345
+ // --- Usage ---
346
+
347
+ function printUsage() {
348
+ process.stdout.write(`barebrowse -- CDP-direct browsing for autonomous agents
349
+
350
+ Session:
351
+ barebrowse open [url] [flags] Open browser session
352
+ barebrowse close Close session
353
+ barebrowse status Check session status
354
+
355
+ Open flags:
356
+ --mode=headless|headed|hybrid Browser mode (default: headless)
357
+ --port=N CDP port for headed mode
358
+ --no-cookies Skip cookie injection
359
+ --browser=firefox|chromium Cookie source browser
360
+ --timeout=N Navigation timeout in ms
361
+ --prune-mode=act|read Default pruning mode
362
+ --no-consent Skip consent dismissal
363
+
364
+ Navigation:
365
+ barebrowse goto <url> Navigate to URL
366
+ barebrowse snapshot [--mode=M] ARIA snapshot -> .barebrowse/page-*.yml
367
+ barebrowse screenshot [--format] Screenshot -> .barebrowse/screenshot-*.png
368
+
369
+ Interaction:
370
+ barebrowse click <ref> Click element
371
+ barebrowse type <ref> <text> Type text (--clear to replace)
372
+ barebrowse fill <ref> <text> Clear + type
373
+ barebrowse press <key> Press key (Enter, Tab, Escape, ...)
374
+ barebrowse scroll <deltaY> Scroll (positive=down)
375
+ barebrowse hover <ref> Hover element
376
+ barebrowse select <ref> <value> Select dropdown value
377
+
378
+ Debugging:
379
+ barebrowse eval <expression> Run JS in page context
380
+ barebrowse wait-idle [--timeout] Wait for network idle
381
+ barebrowse console-logs Console logs -> .barebrowse/console-*.json
382
+ barebrowse network-log Network log -> .barebrowse/network-*.json
383
+
384
+ One-shot:
385
+ barebrowse browse <url> [mode] Browse + print snapshot to stdout
386
+
387
+ MCP:
388
+ barebrowse mcp Start MCP server (JSON-RPC over stdio)
389
+ barebrowse install Auto-configure MCP for Claude Desktop / Cursor
390
+ barebrowse install --skill Install SKILL.md for Claude Code
391
+
392
+ As a library:
393
+ import { browse, connect } from 'barebrowse';
394
+
395
+ More: see README.md or barebrowse.context.md
396
+ `);
397
+ }
@@ -0,0 +1,38 @@
1
+ # barebrowse -- Assumptions & Constraints
2
+
3
+ ## Hard constraints
4
+
5
+ | Constraint | Detail |
6
+ |-----------|--------|
7
+ | **Chromium-only** | CDP protocol. Covers Chrome, Chromium, Edge, Brave, Vivaldi, Arc, Opera (~80% desktop share). Firefox later via WebDriver BiDi. |
8
+ | **Node >= 22** | Built-in WebSocket (`globalThis.WebSocket`), built-in SQLite (`node:sqlite`). No polyfills. |
9
+ | **Linux first** | Tested on Fedora/KDE/Wayland. macOS/Windows cookie extraction paths exist in auth.js but are untested. |
10
+ | **Zero required deps** | Everything uses Node stdlib. Vanilla JS, ES modules, no build step. |
11
+ | **Not a server** | Library that agents import. MCP wrapper included, HTTP wrapper is DIY. |
12
+
13
+ ## Assumptions
14
+
15
+ - **User has Chromium installed.** At least one of: chromium-browser, google-chrome, brave-browser, microsoft-edge. `chromium.js` searches common paths.
16
+ - **Cookie extraction needs unlocked profile.** Chromium cookies are AES-encrypted with a keyring key (KWallet on KDE, GNOME Keyring on GNOME). Firefox cookies are plaintext SQLite and always accessible.
17
+ - **Headed mode requires manual browser launch.** User must start their browser with `--remote-debugging-port=9222`. barebrowse connects to it -- does not launch it.
18
+ - **Hybrid fallback needs a running headed browser.** If headless is bot-blocked, hybrid kills headless and connects to headed on port 9222. That browser must already be running.
19
+ - **Cookies expire.** Cookie injection works for existing sessions, not new logins. For sites requiring fresh auth, headed mode with user interaction is the fallback.
20
+ - **One page per connect().** Each `connect()` call creates one page. For multiple tabs, call `connect()` multiple times.
21
+
22
+ ## Known limitations
23
+
24
+ | Limitation | Impact | Workaround |
25
+ |-----------|--------|------------|
26
+ | No Firefox/WebKit support | ~20% of desktop users can't use native browser | Use Chromium as the automation target, Firefox as cookie source |
27
+ | No file upload | Can't interact with file inputs | Not yet implemented (`Input.setFiles` via CDP) |
28
+ | No drag and drop | Can't use drag-based UIs | Not yet implemented |
29
+ | No cross-origin iframes | Content inside iframes invisible to ARIA tree | Frame tree traversal via CDP (medium effort) |
30
+ | No CAPTCHAs | Cannot solve challenge pages | Headed mode lets user solve manually |
31
+ | Canvas/WebGL opaque | No ARIA representation | Needs screenshot + vision model |
32
+ | macOS/Windows untested | Cookie paths exist but may not work | Linux-only for now |
33
+
34
+ ## Risks
35
+
36
+ - **CDP is not a stable API.** Chrome team can change it across versions. Mitigation: we use well-established domains (Accessibility, Input, Page, Network, DOM) that rarely break.
37
+ - **Cookie consent patterns evolve.** New consent frameworks may not be detected by `consent.js`. Mitigation: best-effort, opt-out with `{ consent: false }`.
38
+ - **Stealth patches are an arms race.** Bot detection evolves. Mitigation: headed mode with real browser profile is the ultimate fallback.
@@ -163,7 +163,7 @@ Every action returns a **pruned ARIA snapshot** -- the agent's view of the page
163
163
 
164
164
  ### Module table
165
165
 
166
- Eleven modules, 2,396 lines, zero required dependencies.
166
+ Thirteen modules, zero required dependencies.
167
167
 
168
168
  | Module | Lines | Purpose |
169
169
  |---|---|---|
@@ -177,6 +177,8 @@ Eleven modules, 2,396 lines, zero required dependencies.
177
177
  | `src/consent.js` | 210 | Auto-dismiss cookie consent dialogs, 7 languages |
178
178
  | `src/stealth.js` | 51 | Navigator patches for headless anti-detection |
179
179
  | `src/bareagent.js` | 161 | Tool adapter for bareagent Loop |
180
+ | `src/daemon.js` | ~230 | Background HTTP server holding connect() session for CLI mode |
181
+ | `src/session-client.js` | ~60 | HTTP client to daemon (sendCommand, readSession, isAlive) |
180
182
  | `mcp-server.js` | 216 | MCP server (JSON-RPC 2.0 over stdio) |
181
183
 
182
184
  ---
@@ -254,11 +256,12 @@ Anti-detection for headless mode via `Page.addScriptToEvaluateOnNewDocument` (ru
254
256
  - `Permissions.prototype.query` -> notifications return 'prompt'
255
257
  - Applied automatically in headless mode
256
258
 
257
- ### Tests -- 47+ passing
259
+ ### Tests -- 64 passing
258
260
  - 16 unit tests (pruning logic)
259
261
  - 7 unit tests (cookie extraction -- 2 skip when Chromium profile locked)
260
262
  - 5 unit tests (CDP client + browser launch)
261
263
  - 11 integration tests (end-to-end browse pipeline)
264
+ - 10 integration tests (CLI session lifecycle: open/snapshot/goto/click/eval/console/network/close)
262
265
  - 15 integration tests (real-world interactions: data: URL fixture + live sites)
263
266
 
264
267
  ---
@@ -302,6 +305,26 @@ Raw JSON-RPC 2.0 over stdio. Zero SDK dependencies. `npm install barebrowse` the
302
305
  Action tools return `'ok'` -- agent calls `snapshot` explicitly (MCP tool calls are cheap to chain).
303
306
  Session tools share a singleton page, lazy-created on first use.
304
307
 
308
+ ### CLI session -- for coding agents + human devs
309
+
310
+ Shell commands that output to disk. Coding agents (Claude Code, Copilot, Cursor) read output files with their file tools -- no tokens wasted in tool responses.
311
+
312
+ ```bash
313
+ barebrowse open https://example.com # Start daemon + navigate
314
+ barebrowse snapshot # → .barebrowse/page-*.yml
315
+ barebrowse click 8 # Click element
316
+ barebrowse console-logs # → .barebrowse/console-*.json
317
+ barebrowse close # Kill daemon + browser
318
+ ```
319
+
320
+ Architecture: `open` spawns a detached child process running an HTTP server on a random localhost port. Session state stored in `.barebrowse/session.json`. Subsequent commands POST to the daemon. `close` sends shutdown, daemon calls `page.close()` + `process.exit(0)`.
321
+
322
+ Full commands: open, close, status, goto, snapshot, screenshot, click, type, fill, press, scroll, hover, select, eval, wait-idle, console-logs, network-log.
323
+
324
+ Self-sufficiency features (console/network capture, eval) let agents debug without guessing -- they see JS errors and failed requests directly.
325
+
326
+ SKILL.md (`.claude/skills/barebrowse/SKILL.md`) teaches Claude Code the CLI commands. Install with `barebrowse install --skill`.
327
+
305
328
  ---
306
329
 
307
330
  ## Ecosystem
@@ -339,10 +362,12 @@ barebrowse/
339
362
  │ ├── interact.js # Click, type, press, scroll, hover, select
340
363
  │ ├── consent.js # Auto-dismiss cookie consent dialogs
341
364
  │ ├── stealth.js # Navigator patches for headless anti-detection
342
- └── bareagent.js # Tool adapter for bareagent Loop
365
+ ├── bareagent.js # Tool adapter for bareagent Loop
366
+ │ ├── daemon.js # Background HTTP server for CLI session
367
+ │ └── session-client.js # HTTP client to daemon
343
368
  ├── test/
344
369
  │ ├── unit/ # prune, auth, cdp tests
345
- │ └── integration/ # browse + interact tests (real sites)
370
+ │ └── integration/ # browse, interact, cli tests
346
371
  ├── examples/
347
372
  │ ├── headed-demo.js # Interactive demo: Wikipedia → DuckDuckGo
348
373
  │ └── yt-demo.js # YouTube demo: Firefox cookies → search → play video
@@ -352,7 +377,7 @@ barebrowse/
352
377
  │ ├── blueprint.md # This file
353
378
  │ └── testing.md # Test guide: pyramid, all 54 tests, CI strategy
354
379
  ├── mcp-server.js # MCP server (JSON-RPC 2.0 over stdio)
355
- ├── cli.js # CLI entry: `npx barebrowse mcp` or `npx barebrowse browse <url>`
380
+ ├── cli.js # CLI entry: session commands, MCP, browse, install
356
381
  ├── .mcp.json # MCP server config for Claude Desktop / Cursor
357
382
  ├── barebrowse.context.md # LLM-consumable integration guide
358
383
  ├── package.json
@@ -0,0 +1,52 @@
1
+ # barebrowse -- Vision
2
+
3
+ ## What it is
4
+
5
+ A standalone vanilla JavaScript library that gives autonomous agents authenticated access to the web through the user's own Chromium browser. One package, one import, three modes.
6
+
7
+ ```js
8
+ import { browse } from 'barebrowse';
9
+ const snapshot = await browse('https://any-page.com');
10
+ ```
11
+
12
+ barebrowse handles: finding the browser, connecting via CDP, injecting cookies, navigating, extracting the ARIA accessibility tree, and pruning it down to what an agent actually needs. The output is a clean, token-efficient snapshot of any web page -- authenticated as the real user.
13
+
14
+ ## What it is NOT
15
+
16
+ - **Not a framework.** No plugin system, no config files, no lifecycle hooks.
17
+ - **Not Playwright.** No bundled browser, no cross-engine abstraction, no 200MB download.
18
+ - **Not an agent.** No LLM, no planning, no orchestration -- that's bareagent's job.
19
+ - **Not a scraper.** It browses as the user, not as a bot harvesting data.
20
+
21
+ ## The core insight
22
+
23
+ The user already has a browser. It's already logged in. It already passes Cloudflare. Instead of fighting the web with headless stealth tricks, **use what's already there**.
24
+
25
+ CDP (Chrome DevTools Protocol) lets us connect to any Chromium-based browser -- the same one the user browses with daily. We get their cookies, their sessions, their anti-detection posture, for free.
26
+
27
+ ## The problem it solves
28
+
29
+ Every AI agent that needs to read or interact with the web hits the same walls:
30
+
31
+ 1. **Cloudflare / bot detection** -- headless browsers get blocked
32
+ 2. **Authentication** -- sites require login, OAuth, session cookies
33
+ 3. **Token bloat** -- raw DOM is 100K+ tokens; agents need ~5K
34
+ 4. **Two consumers, same need** -- research agents (read pages) and personal assistants (click/type) both need an authenticated browser, but existing tools force you to choose one path
35
+
36
+ ## The bare- ecosystem
37
+
38
+ ```
39
+ bareagent = the brain (orchestration, LLM loop, memory, retries)
40
+ barebrowse = the eyes + hands (browse, read, interact with the web)
41
+ ```
42
+
43
+ barebrowse is a library. bareagent imports it as a capability. barebrowse doesn't know about bareagent. bareagent doesn't know about CDP. Clean boundary. Each ships and tests independently.
44
+
45
+ ## Success criteria
46
+
47
+ 1. `browse(url)` returns a pruned ARIA snapshot of any page, authenticated as the user
48
+ 2. Zero heavy dependencies -- no Playwright, no Puppeteer, no bundled browser
49
+ 3. Works with any installed Chromium-based browser
50
+ 4. Headless for research, headed for interaction, hybrid for autonomous agents
51
+ 5. Plugs into bareagent as plain tool functions
52
+ 6. An agent using barebrowse + bareagent can autonomously research the web and act on pages