barebrowse 0.2.1 → 0.3.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 CHANGED
@@ -1,5 +1,58 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0
4
+
5
+ CLI session mode. Shell commands that output to disk — coding agents read files when needed instead of getting full snapshots in every tool response. ~4x more token-efficient than MCP for multi-step browsing flows.
6
+
7
+ ### New: CLI session commands
8
+ - `barebrowse open [url] [flags]` — spawn background daemon holding a `connect()` session
9
+ - `barebrowse close` / `status` — session lifecycle
10
+ - `barebrowse goto <url>` — navigate
11
+ - `barebrowse snapshot [--mode=act|read]` — ARIA snapshot → `.barebrowse/page-*.yml`
12
+ - `barebrowse screenshot [--format]` — screenshot → `.barebrowse/screenshot-*.png`
13
+ - `barebrowse click/type/fill/press/scroll/hover/select` — all interactions from connect() API
14
+ - Open flags: `--mode`, `--port`, `--no-cookies`, `--browser`, `--timeout`, `--prune-mode`, `--no-consent`
15
+
16
+ ### New: agent self-sufficiency
17
+ - `barebrowse eval <expression>` — run JS in page context via `Runtime.evaluate`
18
+ - `barebrowse console-logs [--level --clear]` — dump captured console logs → `.barebrowse/console-*.json`
19
+ - `barebrowse network-log [--failed]` — dump network requests → `.barebrowse/network-*.json`
20
+ - `barebrowse wait-idle [--timeout]` — wait for network idle
21
+
22
+ ### New: daemon architecture (`src/daemon.js` + `src/session-client.js`)
23
+ - Background HTTP server on random localhost port, holding a `connect()` session
24
+ - Spawned as detached child process, communicates via `session.json`
25
+ - Console capture via `Runtime.consoleAPICalled`
26
+ - Network capture via `Network.requestWillBeSent` / `responseReceived` / `loadingFailed`
27
+ - Graceful shutdown on `close` command or SIGTERM
28
+
29
+ ### New: SKILL.md for Claude Code
30
+ - `.claude/skills/barebrowse/SKILL.md` — skill definition + full CLI command reference
31
+ - `barebrowse install --skill` — copies SKILL.md to `~/.config/claude/skills/barebrowse/`
32
+
33
+ ### Fixed: MCP setup instructions
34
+ - README now has per-client instructions: Claude Code (`claude mcp add`), Claude Desktop/Cursor (`npx barebrowse install`), VS Code (`.vscode/mcp.json`)
35
+ - `install` command no longer writes `.mcp.json` for Claude Code — prints `claude mcp add` hint instead
36
+
37
+ ### Fixed: ARIA tree formatting (`src/aria.js`)
38
+ - Ignored nodes joined children with empty string instead of newline, causing sibling subtrees to concatenate on one line
39
+ - Fixed to `.filter(Boolean).join('\n')`
40
+
41
+ ### Changed
42
+ - `cli.js` — expanded from 3 commands to full dispatch table (20+ commands)
43
+ - `barebrowse.context.md` — added CLI as third integration path, updated MCP setup
44
+ - `README.md` — "Two ways" → "Three ways", added CLI section
45
+
46
+ ### Docs
47
+ - `docs/04-process/testing.md` — updated to 64 tests, added CLI test section
48
+ - `docs/00-context/system-state.md` — added daemon/session-client to module table, CLI to integrations
49
+ - `docs/03-logs/validation-log.md` — full CLI manual validation results
50
+
51
+ ### Tests
52
+ - 64 tests passing (was 54 in 0.2.x)
53
+ - New: `test/integration/cli.test.js` (10 tests) — full open → snapshot → goto → click → eval → console → network → close cycle
54
+ - All existing 54 tests unchanged and passing
55
+
3
56
  ## 0.2.1
4
57
 
5
58
  - README rewritten: no code blocks, full obstacle course table with mode column, two usage paths (MCP vs framework), mcprune credited, measured token savings, context.md as code reference
package/CLAUDE.md CHANGED
@@ -12,11 +12,13 @@
12
12
 
13
13
  ## Project Specifics
14
14
 
15
+ - **What:** Vanilla JS library — CDP-direct browsing for autonomous agents. URL in, pruned ARIA snapshot out.
15
16
  - **Language:** Vanilla JavaScript, ES modules, no build step
16
17
  - **Runtime:** Node.js >= 22 (built-in WebSocket, sqlite)
17
18
  - **Protocol:** CDP (Chrome DevTools Protocol) direct — no Playwright
18
19
  - **Browser:** Any installed Chromium-based browser (chromium, chrome, brave, edge)
19
- - **Key files:** `src/index.js` (API), `src/cdp.js` (CDP client), `src/chromium.js` (browser launch), `src/aria.js` (ARIA formatting)
20
- - **Docs:** `docs/prd.md` (decisions + rationale), `docs/poc-plan.md` (phases + DoD)
20
+ - **Modules:** 11 files in `src/`, ~2,400 lines, zero required deps
21
+ - **Tests:** 54 passing run with `node --test test/unit/*.test.js test/integration/*.test.js`
22
+ - **Docs:** `docs/README.md` (navigation guide to all documentation)
21
23
 
22
24
  For full development and testing standards, see `.claude/memory/AGENT_RULES.md`.
package/README.md CHANGED
@@ -18,7 +18,7 @@
18
18
 
19
19
  barebrowse is agentic browsing stripped to the bone. It gives your AI agent eyes and hands on the web -- navigate any page, see what's there, click buttons, fill forms, scroll, and move on. It uses your installed Chromium browser (Chrome, Brave, Edge -- whatever you have), reuses your existing login sessions, and handles all the friction automatically: cookie consent walls, permission prompts, bot detection, GDPR dialogs.
20
20
 
21
- Instead of dumping raw DOM or taking screenshots, barebrowse returns a **pruned ARIA snapshot** -- a compact semantic view of what's on the page and what the agent can interact with. Buttons, links, inputs, headings -- labeled with `[ref=N]` markers the agent uses to act. The pruning pipeline is ported from [mcprune](https://github.com/nickvdyck/mcprune) and cuts 40-90% of tokens compared to raw page output. Every token your agent reads is meaningful.
21
+ Instead of dumping raw DOM or taking screenshots, barebrowse returns a **pruned ARIA snapshot** -- a compact semantic view of what's on the page and what the agent can interact with. Buttons, links, inputs, headings -- labeled with `[ref=N]` markers the agent uses to act. The pruning pipeline is ported from [mcprune](https://github.com/hamr0/mcprune) and cuts 40-90% of tokens compared to raw page output. Every token your agent reads is meaningful.
22
22
 
23
23
  No Playwright. No bundled browser. No 200MB download. No broken dependencies. Zero deps. Just CDP over a WebSocket to whatever Chromium you already have.
24
24
 
@@ -30,18 +30,65 @@ npm install barebrowse
30
30
 
31
31
  Requires Node.js >= 22 and any installed Chromium-based browser.
32
32
 
33
- ## Two ways to use it
33
+ ## Three ways to use it
34
34
 
35
- ### 1. MCP server -- for Claude Desktop, Cursor, Claude Code
35
+ ### 1. CLI session -- for coding agents and quick testing
36
36
 
37
+ ```bash
38
+ barebrowse open https://example.com # Start session + navigate
39
+ barebrowse snapshot # ARIA snapshot → .barebrowse/page-*.yml
40
+ barebrowse click 8 # Click element
41
+ barebrowse close # End session
37
42
  ```
38
- npm install -g barebrowse
43
+
44
+ Outputs go to `.barebrowse/` as files -- agents read them with their file tools, no token waste in tool responses. Install the skill for Claude Code:
45
+
46
+ ```bash
47
+ barebrowse install --skill
48
+ # or: claude mcp add barebrowse -- npx barebrowse mcp
49
+ ```
50
+
51
+ Full command reference: [.claude/skills/barebrowse/SKILL.md](.claude/skills/barebrowse/SKILL.md)
52
+
53
+ ### 2. MCP server -- for Claude Desktop, Cursor, and other MCP clients
54
+
55
+ **Claude Code:**
56
+ ```bash
57
+ claude mcp add barebrowse -- npx barebrowse mcp
58
+ ```
59
+
60
+ **Claude Desktop / Cursor:**
61
+ ```bash
39
62
  npx barebrowse install
40
63
  ```
41
64
 
42
- That's it. `install` auto-detects your MCP client and writes the config. No manual JSON editing. Restart your client and you have 7 browsing tools: `browse`, `goto`, `snapshot`, `click`, `type`, `press`, `scroll`.
65
+ Or manually add to your config (`claude_desktop_config.json`, `.cursor/mcp.json`):
66
+ ```json
67
+ {
68
+ "mcpServers": {
69
+ "barebrowse": {
70
+ "command": "npx",
71
+ "args": ["barebrowse", "mcp"]
72
+ }
73
+ }
74
+ }
75
+ ```
76
+
77
+ **VS Code (`.vscode/mcp.json`):**
78
+ ```json
79
+ {
80
+ "servers": {
81
+ "barebrowse": {
82
+ "command": "npx",
83
+ "args": ["barebrowse", "mcp"]
84
+ }
85
+ }
86
+ }
87
+ ```
88
+
89
+ 7 tools: `browse`, `goto`, `snapshot`, `click`, `type`, `press`, `scroll`.
43
90
 
44
- ### 2. Framework -- for agentic automation
91
+ ### 3. Library -- for agentic automation
45
92
 
46
93
  Import barebrowse in your agent code. One-shot reads, interactive sessions, full observe-think-act loops. Works with any LLM orchestration library. Ships with a ready-made adapter for [bareagent](https://www.npmjs.com/package/bare-agent) (9 tools, auto-snapshot after every action).
47
94
 
@@ -80,7 +127,7 @@ This is the obstacle course your agent doesn't have to think about:
80
127
 
81
128
  ## What the agent sees
82
129
 
83
- Raw ARIA output from a page is noisy -- decorative wrappers, hidden elements, structural junk. The pruning pipeline (ported from [mcprune](https://github.com/nickvdyck/mcprune)) strips it down to what matters.
130
+ Raw ARIA output from a page is noisy -- decorative wrappers, hidden elements, structural junk. The pruning pipeline (ported from [mcprune](https://github.com/hamr0/mcprune)) strips it down to what matters.
84
131
 
85
132
  | Page | Raw | Pruned | Reduction |
86
133
  |------|-----|--------|-----------|
@@ -1,7 +1,7 @@
1
1
  # barebrowse -- Integration Guide
2
2
 
3
3
  > For AI assistants and developers wiring barebrowse into a project.
4
- > v0.1.0 | Node.js >= 22 | 0 required deps | MIT
4
+ > v0.3.0 | Node.js >= 22 | 0 required deps | MIT
5
5
 
6
6
  ## What this is
7
7
 
@@ -13,9 +13,10 @@ No Playwright. No bundled browser. No build step. Vanilla JS, ES modules.
13
13
  npm install barebrowse
14
14
  ```
15
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
16
+ Three integration paths:
17
+ 1. **Library:** `import { browse, connect } from 'barebrowse'` -- one-shot or interactive session
18
+ 2. **MCP server:** `barebrowse mcp` -- JSON-RPC over stdio for Claude Desktop, Cursor, etc.
19
+ 3. **CLI session:** `barebrowse open` / `click` / `snapshot` / `close` -- shell commands, outputs to disk
19
20
 
20
21
  ## Which mode do I need?
21
22
 
@@ -174,15 +175,33 @@ try {
174
175
 
175
176
  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
 
177
- ## MCP wrapper
178
+ ## CLI session mode
178
179
 
179
- barebrowse ships an MCP server for direct use with Claude Desktop, Cursor, or any MCP client.
180
+ For coding agents (Claude Code, Copilot, Cursor) and quick interactive testing. Commands output files to `.barebrowse/` -- agents read them with file tools, avoiding token waste in tool responses.
180
181
 
181
182
  ```bash
182
- npm install barebrowse # or npm install -g barebrowse
183
+ barebrowse open https://example.com # Start daemon + navigate
184
+ barebrowse snapshot # → .barebrowse/page-<timestamp>.yml
185
+ barebrowse click 8 # Click element ref=8
186
+ barebrowse type 12 hello world # Type into element ref=12
187
+ barebrowse screenshot # → .barebrowse/screenshot-<timestamp>.png
188
+ barebrowse console-logs # → .barebrowse/console-<timestamp>.json
189
+ barebrowse close # Kill daemon + browser
183
190
  ```
184
191
 
185
- Add to your MCP client config (`.mcp.json`, `claude_desktop_config.json`, etc.):
192
+ Session lifecycle: `open` spawns a background daemon holding a `connect()` session. Subsequent commands POST to the daemon over HTTP (localhost). `close` shuts everything down.
193
+
194
+ Full command reference: `.claude/skills/barebrowse/SKILL.md`
195
+
196
+ ## MCP wrapper
197
+
198
+ barebrowse ships an MCP server for direct use with Claude Desktop, Cursor, or any MCP client.
199
+
200
+ **Claude Code:** `claude mcp add barebrowse -- npx barebrowse mcp`
201
+
202
+ **Claude Desktop / Cursor:** `npx barebrowse install` (auto-detects and writes config)
203
+
204
+ **Manual config** (`claude_desktop_config.json`, `.cursor/mcp.json`):
186
205
  ```json
187
206
  {
188
207
  "mcpServers": {
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
+ }