barebrowse 0.7.1 → 0.9.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/CHANGELOG.md +249 -0
- package/LICENSE +202 -21
- package/NOTICE +8 -0
- package/README.md +39 -10
- package/barebrowse.context.md +45 -18
- package/cli.js +114 -3
- package/mcp-server.js +276 -70
- package/package.json +2 -2
- package/src/bareagent.js +43 -4
- package/src/chromium.js +115 -5
- package/src/consent.js +3 -8
- package/src/daemon.js +13 -0
- package/src/index.js +440 -135
- package/src/network-idle.js +62 -0
- package/src/prune.js +2 -1
- package/src/stealth.js +87 -6
package/barebrowse.context.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# barebrowse -- Integration Guide
|
|
2
2
|
|
|
3
3
|
> For AI assistants and developers wiring barebrowse into a project.
|
|
4
|
-
> v0.
|
|
4
|
+
> v0.9.1 | Node.js >= 22 | 0 required deps | Apache-2.0
|
|
5
5
|
|
|
6
6
|
## What this is
|
|
7
7
|
|
|
8
|
-
barebrowse is a CDP-direct browsing library for autonomous agents (~
|
|
8
|
+
barebrowse is a CDP-direct browsing library for autonomous agents (~3,600 lines in `src/` across 14 modules). URL in, pruned ARIA snapshot out. It launches the user's installed Chromium browser (or attaches to one already running), navigates, handles consent/permissions/cookies, walks iframes, captures downloads, and returns a token-efficient ARIA tree with `[ref=N]` markers for interaction.
|
|
9
9
|
|
|
10
10
|
No Playwright. No bundled browser. No build step. Vanilla JS, ES modules.
|
|
11
11
|
|
|
@@ -25,6 +25,9 @@ Three integration paths:
|
|
|
25
25
|
| `headless` (default) | Launches a fresh Chromium, no UI | Scraping, reading, fast automation |
|
|
26
26
|
| `headed` | Auto-launches a visible Chromium window | Bot-detected sites, debugging, visual tasks |
|
|
27
27
|
| `hybrid` | Tries headless first, headed fallback per-navigation (switches back to headless next time) | General-purpose agent browsing |
|
|
28
|
+
| `connect({ port })` (attach) | Attaches to a Chromium *you* started with `--remote-debugging-port=N` — your real logged-in profile, no clone | When you need the user's real session (auth cookies, localStorage, IndexedDB). `close()` only kills the tab we opened, not the browser. |
|
|
29
|
+
|
|
30
|
+
Attach mode skips three things vs. spawn modes: stealth patches (would persist via `addScriptToEvaluateOnNewDocument`), `Browser.setPermission` calls (browser-wide — would leak deny-states into the user's other tabs), and `Browser.setDownloadBehavior` (don't override the user's download preference). Stealth is unnecessary anyway because the user's real browser doesn't look headless.
|
|
28
31
|
|
|
29
32
|
## Minimal usage: one-shot browse
|
|
30
33
|
|
|
@@ -55,6 +58,7 @@ const snapshot = await browse('https://example.com', {
|
|
|
55
58
|
| `goto(url, timeout?)` | url: string, timeout: number (default 30000) | void | Navigate + wait for load + dismiss consent |
|
|
56
59
|
| `goBack()` | -- | void | Navigate back in browser history |
|
|
57
60
|
| `goForward()` | -- | void | Navigate forward in browser history |
|
|
61
|
+
| `reload(opts?)` | { ignoreCache?: boolean, timeout?: number } | void | Reload the current page. Clears refMap (refs from pre-reload reject). |
|
|
58
62
|
| `snapshot(pruneOpts?)` | false or { mode: 'act'\|'read' } | string | ARIA tree with `[ref=N]` markers. Pass `false` for raw. |
|
|
59
63
|
| `click(ref)` | ref: string | void | Scroll into view + mouse press+release at center |
|
|
60
64
|
| `type(ref, text, opts?)` | ref: string, text: string, opts: { clear?, keyEvents? } | void | Focus + insert text. `clear: true` replaces existing. |
|
|
@@ -73,16 +77,20 @@ const snapshot = await browse('https://example.com', {
|
|
|
73
77
|
| `waitForNetworkIdle(opts?)` | { timeout?: number, idle?: number } | void | Wait until no pending requests for `idle` ms (default 500) |
|
|
74
78
|
| `saveState(filePath)` | filePath: string | void | Export cookies + localStorage to JSON file |
|
|
75
79
|
| `injectCookies(url, opts?)` | url: string, { browser?: string } | void | Extract cookies from user's browser and inject via CDP |
|
|
76
|
-
| `botBlocked` | -- | boolean | True if last `goto()` hit a bot challenge (
|
|
80
|
+
| `botBlocked` | -- | boolean | True if last `goto()` hit a bot challenge. Heuristic tightened in v0.9.0 (H9): Cloudflare-strong phrases fire alone; generic phrases ("access denied"/"unknown error") only fire on near-empty pages. Resets on each navigation. |
|
|
77
81
|
| `dialogLog` | -- | Array<{type, message, timestamp}> | Auto-dismissed JS dialog history |
|
|
78
|
-
| `
|
|
82
|
+
| `onDialog(handler)` | handler: ({type, message, defaultPrompt}) => {accept, promptText} \| undefined, or null to remove | void | Override the auto-accept default. Handler receives the dialog params; return `{accept: false}` to cancel, `{accept: true, promptText: 'x'}` to supply prompt input. Pass `null` to restore defaults. |
|
|
83
|
+
| `downloads` | -- | Array<{guid, url, suggestedFilename, savedPath, state, totalBytes, receivedBytes}> | Live array of every `Content-Disposition: attachment` download captured during this session. `state`: `inProgress` → `completed` \| `canceled`. |
|
|
84
|
+
| `cdp` | -- | object | Raw CDP session (getter — survives hybrid fallback and switchTab) for escape hatch: `page.cdp.send(method, params)` |
|
|
79
85
|
| `createTab()` | -- | tab handle | New tab in same browser. Returns `{ goto, botBlocked, injectCookies, waitForNetworkIdle, cdp, close }`. Tab close doesn't affect session. |
|
|
80
86
|
| `close()` | -- | void | Close page, disconnect CDP, kill browser (if headless) |
|
|
81
87
|
|
|
82
88
|
**connect() options** (in addition to mode/port/consent):
|
|
89
|
+
- `port: 9222` — Attach to a Chromium already running with `--remote-debugging-port=N` instead of spawning one. The browser keeps running on `close()`. Stealth + permission denial + download capture are skipped to avoid mutating the user's running browser.
|
|
83
90
|
- `proxy: 'http://...'` — HTTP/SOCKS proxy for browser
|
|
84
91
|
- `viewport: '1280x720'` — Set viewport dimensions
|
|
85
92
|
- `storageState: 'file.json'` — Load cookies/localStorage from saved state
|
|
93
|
+
- `downloadPath: '/abs/dir'` — Where downloads land. Default: per-session `mkdtemp` under `/tmp/barebrowse-dl-*` that gets removed on `close()`. Caller-supplied paths are not cleaned up — caller owns the lifecycle.
|
|
86
94
|
|
|
87
95
|
## Snapshot format
|
|
88
96
|
|
|
@@ -152,8 +160,10 @@ barebrowse can inject cookies from the user's real browser sessions, bypassing l
|
|
|
152
160
|
| Off-screen elements | `DOM.scrollIntoViewIfNeeded` before every click, JS `.click()` fallback for no-layout elements | Both |
|
|
153
161
|
| Form submission | `press('Enter')` triggers onsubmit | Both |
|
|
154
162
|
| SPA navigation | `waitForNavigation()` uses loadEventFired + frameNavigated | Both |
|
|
155
|
-
| Bot detection |
|
|
156
|
-
| `
|
|
163
|
+
| Bot detection | v0.9.0 (H9): Cloudflare-strong phrases ("Just a moment", "Attention Required", "verify you are human") fire alone; generic phrases ("access denied", "unknown error") only fire on near-empty pages — no more false-positive headed-launches on legitimate 4xx/5xx pages. `botBlocked` flag set after every `goto()`. Hybrid fallback switches to headed. Snapshot shows `[BOT CHALLENGE DETECTED]` warning. | Hybrid |
|
|
164
|
+
| Stealth (headless tells) | v0.9.0 (H4): `Network.setUserAgentOverride` strips "HeadlessChrome" from UA in HTTP headers AND `navigator.userAgent`; JS patches for webdriver, plugins, languages, full `chrome.runtime` enum shape, `Notification` constructor + `permission: 'default'`, `hardwareConcurrency: 8`, `deviceMemory: 8`, WebGL `UNMASKED_VENDOR_WEBGL`/`UNMASKED_RENDERER_WEBGL` spoofed to Intel | Headless |
|
|
165
|
+
| iframe / OOPIF content (Stripe, reCAPTCHA, embedded forms) | v0.9.0 (H2): `Target.setAutoAttach({flatten:true})` registers a CDP session per iframe; `ariaTree()` walks `Page.getFrameTree`, fetches each frame's AX tree on the right session, splices children under iframe placeholders via `DOM.getFrameOwner`. Refs route via `{session, backendNodeId}` so clicks dispatch in the iframe's Input domain. `--site-per-process` launch flag forces every iframe — including same-origin — into OOPIF so coords work. | Both |
|
|
166
|
+
| Downloads | v0.9.0 (H7): `Browser.setDownloadBehavior({behavior:'allowAndName', downloadPath, eventsEnabled:true})` + listeners populate `page.downloads`. Files land at `savedPath` (under `--download-path` if supplied, else per-session `/tmp/barebrowse-dl-*`). | Headless + Headed (skipped in attach mode) |
|
|
157
167
|
| Profile locking | Unique temp dir per headless instance | Headless |
|
|
158
168
|
| Shared memory crash (Linux) | `--disable-dev-shm-usage` flag prevents `/dev/shm` exhaustion | Headless |
|
|
159
169
|
| ARIA noise | 9-step pruning: wrapper collapse, noise removal, landmark promotion | Both |
|
|
@@ -184,10 +194,12 @@ try {
|
|
|
184
194
|
```
|
|
185
195
|
|
|
186
196
|
`createBrowseTools(opts)` returns:
|
|
187
|
-
- `tools` -- array of bareagent-compatible tool objects
|
|
197
|
+
- `tools` -- array of bareagent-compatible tool objects: `browse`, `goto`, `snapshot`, `click`, `type`, `press`, `scroll`, `select`, `hover`, `back`, `forward`, `reload` (v0.9.0), `drag`, `upload`, `tabs`, `switchTab`, `pdf`, `screenshot`, `wait_for` (v0.9.0), `downloads` (v0.9.0), plus `assess` if wearehere installed
|
|
188
198
|
- `close()` -- cleanup function, call when done
|
|
189
199
|
|
|
190
|
-
Action tools (click, type, press, scroll, hover, goto, back, forward, drag, upload, select, switchTab) auto-return a fresh snapshot so the LLM always sees the result. 300ms settle delay after actions for DOM updates.
|
|
200
|
+
Action tools (click, type, press, scroll, hover, goto, back, forward, reload, drag, upload, select, switchTab, wait_for) auto-return a fresh snapshot so the LLM always sees the result. 300ms settle delay after actions for DOM updates.
|
|
201
|
+
|
|
202
|
+
`onDialog` is intentionally not exposed as a tool — it's a callback shape that doesn't fit a request/response tool loop. If your bareagent flow needs to override a confirm/prompt, drop to `import { connect }` directly and pass the page through.
|
|
191
203
|
|
|
192
204
|
## CLI session mode
|
|
193
205
|
|
|
@@ -199,6 +211,8 @@ barebrowse snapshot # → .barebrowse/page-<timestamp>.yml
|
|
|
199
211
|
barebrowse click 8 # Click element ref=8
|
|
200
212
|
barebrowse type 12 hello world # Type into element ref=12
|
|
201
213
|
barebrowse back # Go back in history
|
|
214
|
+
barebrowse reload [--no-cache] # v0.9.0 — reload current page (bypass cache optional)
|
|
215
|
+
barebrowse downloads # v0.9.0 — JSON array of captured downloads (savedPath, state...)
|
|
202
216
|
barebrowse upload 7 /path/to/file.pdf # Upload file to file input
|
|
203
217
|
barebrowse pdf # → .barebrowse/page-<timestamp>.pdf
|
|
204
218
|
barebrowse wait-for --text="Success" # Wait for content to appear
|
|
@@ -207,7 +221,7 @@ barebrowse save-state # → .barebrowse/state-<timestamp>.json
|
|
|
207
221
|
barebrowse close # Kill daemon + browser
|
|
208
222
|
```
|
|
209
223
|
|
|
210
|
-
**Open flags:** `--mode=headless|headed|hybrid`, `--proxy=URL`, `--viewport=WxH`, `--storage-state=FILE`, `--no-cookies`, `--browser=firefox|chromium`, `--timeout=N`
|
|
224
|
+
**Open flags:** `--mode=headless|headed|hybrid`, `--port=N` (attach to running browser), `--proxy=URL`, `--viewport=WxH`, `--storage-state=FILE`, `--download-path=DIR` (v0.9.0), `--no-cookies`, `--browser=firefox|chromium`, `--timeout=N`
|
|
211
225
|
|
|
212
226
|
Session lifecycle: `open` spawns a background daemon holding a `connect()` session. Subsequent commands POST to the daemon over HTTP (localhost). `close` shuts everything down. JS dialogs (alert/confirm/prompt) are auto-dismissed and logged.
|
|
213
227
|
|
|
@@ -219,7 +233,9 @@ barebrowse ships an MCP server for direct use with Claude Desktop, Cursor, or an
|
|
|
219
233
|
|
|
220
234
|
**Claude Code:** `claude mcp add barebrowse -- npx barebrowse mcp`
|
|
221
235
|
|
|
222
|
-
**Claude Desktop / Cursor:** `npx barebrowse install` (auto-detects and writes config)
|
|
236
|
+
**Claude Desktop / Cursor:** `npx barebrowse install` (auto-detects and writes config; pass `--force` to overwrite an existing entry pointing at a different endpoint)
|
|
237
|
+
|
|
238
|
+
**Diagnose scope conflicts:** `npx barebrowse doctor` scans every known MCP config location (Claude Code user/project/local, Claude Desktop, Cursor, VS Code) and prints which `barebrowse` entries are registered + where they point. Flags `CONFLICT` when two scopes point at different paths — OAuth tokens are stored per endpoint, so a split silently breaks auth. The MCP server itself also writes a one-line banner to stderr at startup (`barebrowse mcp v<X.Y.Z> | serving from <abs path> | pid <N>`) so a stuck agent is diagnosable from the MCP client log.
|
|
223
239
|
|
|
224
240
|
**Manual config** (`claude_desktop_config.json`, `.cursor/mcp.json`):
|
|
225
241
|
```json
|
|
@@ -233,15 +249,17 @@ barebrowse ships an MCP server for direct use with Claude Desktop, Cursor, or an
|
|
|
233
249
|
}
|
|
234
250
|
```
|
|
235
251
|
|
|
236
|
-
|
|
252
|
+
18 core tools as of v0.9.0: `browse` (one-shot), `goto`, `snapshot`, `click`, `type`, `press`, `scroll`, `hover`, `select`, `back`, `forward`, `reload`, `drag`, `upload`, `pdf`, `screenshot`, `wait_for`, `tabs`. Plus `assess` (privacy scan) if `wearehere` is installed (`npm install wearehere`). Plus the **opt-in `eval` tool** gated by `BAREBROWSE_MCP_EVAL=1` (default OFF) — `Runtime.evaluate` in the user's authenticated session can read cookies/localStorage and hit any same-origin endpoint, so opt-in only.
|
|
237
253
|
|
|
238
254
|
Action tools return `'ok'` -- the agent calls `snapshot` explicitly to observe. This avoids double-token output since MCP tool calls are cheap to chain.
|
|
239
255
|
|
|
240
|
-
`browse` and `snapshot` accept a `maxChars` param (default 30000). If the snapshot exceeds the limit, it's saved to `.barebrowse/page-<timestamp>.yml` and a short message with the file path is returned instead.
|
|
256
|
+
`browse` and `snapshot` accept a `maxChars` param (default 30000). If the snapshot exceeds the limit, it's saved to `.barebrowse/page-<timestamp>.yml` and a short message with the file path is returned instead. `screenshot` always saves to `.barebrowse/screenshot-<timestamp>.{png,jpeg,webp}` and returns the file path (raw base64 in a JSON-RPC response would blow `maxChars`). `tabs` returns the JSON array, or with `switchTo: N` it switches and returns `'ok'`.
|
|
257
|
+
|
|
258
|
+
`browse` and `snapshot` also accept `pruneMode: 'act'|'read'`. `act` (the default) keeps interactive elements and short labels — best for clicking/filling. `read` keeps paragraphs, headings, and long text — best for articles, docs, and content extraction. Same surface on the bareagent adapter. If act mode collapses a content-heavy page (raw > 5 KB → pruned < 500 chars AND < 5% of raw), the result includes a `hint: act mode dropped most of the page — retry with pruneMode='read' …` line between the stats and the tree so the caller knows to re-snapshot in read mode instead of bailing to a separate HTTP fetch.
|
|
241
259
|
|
|
242
260
|
Session runs in hybrid mode (headless with automatic headed fallback on bot detection). `goto` injects cookies from the user's browser before navigation for authenticated access.
|
|
243
261
|
|
|
244
|
-
Session tools share a singleton page, lazy-created on first use. All session tools have auto-retry on transient failures (browser crash, WebSocket close, navigation timeout)
|
|
262
|
+
Session tools share a singleton page, lazy-created on first use. All session tools have auto-retry on transient failures (browser crash, WebSocket close, navigation timeout) on a per-tool deadline (v0.9.0 H5): `goto`/`reload`/`wait_for` 60s, `back`/`forward` 30s, interactive ops (`click`/`type`/`press`/`scroll`/`hover`/`select`/`drag`/`snapshot`/`eval`) 15s, `tabs` 5s, heavy I/O (`pdf`/`screenshot`/`upload`) 45s — replaces the prior blanket 30s. Session resets between attempts. Idempotent tools retry once; mutating tools (`click`/`type`/`upload`/etc.) `{ retry: false }` so partial first attempts don't replay on a fresh page. Scroll accepts `direction: "up"/"down"` in addition to numeric `deltaY`. Click falls back to JS `.click()` when elements have no layout. `browse` has a 60s timeout (no retry — stateless). Assess tries headless first; if bot-blocked, retries headed. Browser OOM/crash auto-recovers (session resets, server stays alive).
|
|
245
263
|
|
|
246
264
|
## Architecture
|
|
247
265
|
|
|
@@ -261,17 +279,18 @@ URL -> chromium.js (find/launch browser, permission flags)
|
|
|
261
279
|
|
|
262
280
|
| Module | Lines | Purpose |
|
|
263
281
|
|---|---|---|
|
|
264
|
-
| `src/index.js` | ~
|
|
282
|
+
| `src/index.js` | ~940 | Public API: `browse()`, `connect()`, attach mode, iframe frame-tree walking, downloads, onDialog, isChallengePage |
|
|
265
283
|
| `src/cdp.js` | 148 | WebSocket CDP client, flattened sessions |
|
|
266
|
-
| `src/chromium.js` |
|
|
284
|
+
| `src/chromium.js` | ~160 | Find/launch Chromium browsers, `attach({port})`, `cleanupBrowser`, permission-suppressing flags, `--site-per-process` |
|
|
267
285
|
| `src/aria.js` | 69 | Format ARIA tree as text |
|
|
268
286
|
| `src/auth.js` | 279 | Cookie extraction (Chromium AES + keyring, Firefox), CDP injection |
|
|
269
287
|
| `src/prune.js` | 472 | ARIA pruning pipeline (ported from mcprune) |
|
|
270
288
|
| `src/interact.js` | ~170 | Click, type, press, scroll, hover, select |
|
|
271
289
|
| `src/consent.js` | 200 | Auto-dismiss cookie consent dialogs across languages |
|
|
272
|
-
| `src/stealth.js` | ~
|
|
273
|
-
| `src/
|
|
274
|
-
| `
|
|
290
|
+
| `src/stealth.js` | ~110 | UA override + JS patches (webdriver, WebGL, hardware, Notification, chrome.runtime) |
|
|
291
|
+
| `src/network-idle.js` | ~50 | Set-based network-idle wait (extracted in v0.8.0, F9) |
|
|
292
|
+
| `src/bareagent.js` | ~330 | Tool adapter for bareagent Loop (21 tools) |
|
|
293
|
+
| `mcp-server.js` | ~660 | MCP server (JSON-RPC over stdio, `runStdio()`, `TIMEOUTS`/`TOOLS` exports, opt-in eval, assess session reuse + concurrency) |
|
|
275
294
|
|
|
276
295
|
## Privacy assessment (optional)
|
|
277
296
|
|
|
@@ -323,6 +342,14 @@ Useful for agent threshold decisions: "skip sites above score 40", "warn if term
|
|
|
323
342
|
|
|
324
343
|
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.
|
|
325
344
|
|
|
345
|
+
11. **`--site-per-process` is on by default (v0.9.0).** Required for iframe support — without it, same-origin iframes stay in the parent process and `Input.dispatchMouseEvent` coords don't match `DOM.getBoxModel` coords for iframe-internal elements. Memory cost: +50-150MB per cross-origin frame. Real Chrome does this for cross-origin by default; we just extend it to all iframes. If you attach via `connect({port})`, the user's browser is whatever they launched it as — for iframe interaction reliability, start it with `--site-per-process` too.
|
|
346
|
+
|
|
347
|
+
12. **Attach mode (`connect({port})`) skips three things on purpose.** No stealth (would inject persistent JS via `addScriptToEvaluateOnNewDocument`), no `Browser.setPermission` (browser-wide — would leak deny-states into the user's other tabs), no `Browser.setDownloadBehavior` (don't override the user's download preference). The trade-off: `page.downloads` is always empty in attach mode. If you need download capture in an attached session, start the browser with `--remote-debugging-port=N` *and* configure download preferences in the browser UI first.
|
|
348
|
+
|
|
349
|
+
13. **Refs are globally flat across frames.** v0.9.0 (H2) assigns refs from a shared counter across the merged frame tree, so a `[ref=42]` from an iframe and a `[ref=43]` from the parent come from one address space. The visible `[ref=N]` format is unchanged. refMap stores `{session, backendNodeId}` so `click(ref)` automatically dispatches in the right frame's session.
|
|
350
|
+
|
|
351
|
+
14. **`eval` MCP tool is opt-in.** Set `BAREBROWSE_MCP_EVAL=1` to register it. Default off because `Runtime.evaluate` in an authenticated session can read cookies/localStorage, post on the user's behalf, hit any same-origin endpoint. CLI/connect()/daemon all keep `eval` because the developer is the caller; MCP gates it because the agent acts with less judgment.
|
|
352
|
+
|
|
326
353
|
## Constraints
|
|
327
354
|
|
|
328
355
|
- **Node >= 22** -- built-in WebSocket, built-in SQLite
|
package/cli.js
CHANGED
|
@@ -17,9 +17,15 @@ const cmd = args[0];
|
|
|
17
17
|
if (args.includes('--daemon-internal')) {
|
|
18
18
|
await runDaemonInternal();
|
|
19
19
|
} else if (cmd === 'mcp') {
|
|
20
|
-
|
|
20
|
+
// Explicitly start the JSON-RPC loop — relying on the previous "isMain
|
|
21
|
+
// auto-start" guard inside mcp-server.js would silently hang here because
|
|
22
|
+
// process.argv[1] is cli.js, not mcp-server.js.
|
|
23
|
+
const { runStdio } = await import('./mcp-server.js');
|
|
24
|
+
runStdio();
|
|
21
25
|
} else if (cmd === 'install') {
|
|
22
26
|
install();
|
|
27
|
+
} else if (cmd === 'doctor') {
|
|
28
|
+
doctor();
|
|
23
29
|
} else if (cmd === 'browse' && args[1]) {
|
|
24
30
|
await oneShot();
|
|
25
31
|
} else if (cmd === 'open') {
|
|
@@ -60,6 +66,10 @@ if (args.includes('--daemon-internal')) {
|
|
|
60
66
|
await cmdProxy('back');
|
|
61
67
|
} else if (cmd === 'forward') {
|
|
62
68
|
await cmdProxy('forward');
|
|
69
|
+
} else if (cmd === 'reload') {
|
|
70
|
+
await cmdProxy('reload', { ignoreCache: hasFlag('--no-cache') });
|
|
71
|
+
} else if (cmd === 'downloads') {
|
|
72
|
+
await cmdProxy('downloads');
|
|
63
73
|
} else if (cmd === 'drag' && args[1] && args[2]) {
|
|
64
74
|
await cmdProxy('drag', { fromRef: args[1], toRef: args[2] });
|
|
65
75
|
} else if (cmd === 'upload' && args[1] && args[2]) {
|
|
@@ -106,6 +116,7 @@ async function cmdOpen() {
|
|
|
106
116
|
proxy: parseFlag('--proxy'),
|
|
107
117
|
viewport: parseFlag('--viewport'),
|
|
108
118
|
storageState: parseFlag('--storage-state'),
|
|
119
|
+
downloadPath: parseFlag('--download-path'),
|
|
109
120
|
};
|
|
110
121
|
|
|
111
122
|
try {
|
|
@@ -206,6 +217,7 @@ async function runDaemonInternal() {
|
|
|
206
217
|
proxy: parseFlag('--proxy'),
|
|
207
218
|
viewport: parseFlag('--viewport'),
|
|
208
219
|
storageState: parseFlag('--storage-state'),
|
|
220
|
+
downloadPath: parseFlag('--download-path'),
|
|
209
221
|
};
|
|
210
222
|
const outputDir = parseFlag('--output-dir') || resolve('.barebrowse');
|
|
211
223
|
const url = parseFlag('--url');
|
|
@@ -257,8 +269,33 @@ function install() {
|
|
|
257
269
|
if (!config.mcpServers) config.mcpServers = {};
|
|
258
270
|
|
|
259
271
|
if (config.mcpServers.barebrowse) {
|
|
260
|
-
|
|
261
|
-
|
|
272
|
+
// Detect a stale entry pointing at a different location/command —
|
|
273
|
+
// common when a contributor has both a global install (`npx`) and
|
|
274
|
+
// a worktree-local entry (`node /abs/path/mcp-server.js`). OAuth
|
|
275
|
+
// tokens are stored per endpoint, so leaving the stale one means
|
|
276
|
+
// auth from one path silently won't carry over to the other.
|
|
277
|
+
const existing = config.mcpServers.barebrowse;
|
|
278
|
+
const sameEndpoint =
|
|
279
|
+
existing.command === mcpEntry.command &&
|
|
280
|
+
JSON.stringify(existing.args || []) === JSON.stringify(mcpEntry.args);
|
|
281
|
+
if (!sameEndpoint) {
|
|
282
|
+
if (hasFlag('--force')) {
|
|
283
|
+
config.mcpServers.barebrowse = mcpEntry;
|
|
284
|
+
writeFileSync(target.path, JSON.stringify(config, null, 2) + '\n');
|
|
285
|
+
console.log(` ${target.name}: REPLACED stale entry`);
|
|
286
|
+
console.log(` was: ${existing.command} ${(existing.args || []).join(' ')}`);
|
|
287
|
+
console.log(` now: ${mcpEntry.command} ${mcpEntry.args.join(' ')}`);
|
|
288
|
+
installed++;
|
|
289
|
+
} else {
|
|
290
|
+
console.log(` ${target.name}: CONFLICT — different endpoint already registered`);
|
|
291
|
+
console.log(` existing: ${existing.command} ${(existing.args || []).join(' ')}`);
|
|
292
|
+
console.log(` new: ${mcpEntry.command} ${mcpEntry.args.join(' ')}`);
|
|
293
|
+
console.log(` Pass --force to overwrite, or edit ${target.path} by hand.`);
|
|
294
|
+
}
|
|
295
|
+
} else {
|
|
296
|
+
console.log(` ${target.name}: already configured`);
|
|
297
|
+
installed++;
|
|
298
|
+
}
|
|
262
299
|
continue;
|
|
263
300
|
}
|
|
264
301
|
|
|
@@ -339,6 +376,74 @@ function readJsonOrEmpty(path) {
|
|
|
339
376
|
}
|
|
340
377
|
}
|
|
341
378
|
|
|
379
|
+
/**
|
|
380
|
+
* Scan every known MCP config location for a `barebrowse` entry and print
|
|
381
|
+
* what's there. Built for the Claude Code "Conflicting scopes" warning,
|
|
382
|
+
* which is generated when the same MCP server name resolves to different
|
|
383
|
+
* absolute endpoints across scopes — OAuth tokens are stored per-endpoint
|
|
384
|
+
* so a split silently breaks auth.
|
|
385
|
+
*/
|
|
386
|
+
function doctor() {
|
|
387
|
+
const home = homedir();
|
|
388
|
+
const cwd = process.cwd();
|
|
389
|
+
const os = platform();
|
|
390
|
+
|
|
391
|
+
// (label, file path, key) — `key` is the top-level config key that holds
|
|
392
|
+
// the servers map. Claude Code / Desktop / Cursor use `mcpServers`; VS
|
|
393
|
+
// Code's .vscode/mcp.json uses `servers`.
|
|
394
|
+
const locations = [
|
|
395
|
+
['Claude Code (user)', join(home, '.claude.json'), 'mcpServers'],
|
|
396
|
+
['Claude Code (project)', join(cwd, '.mcp.json'), 'mcpServers'],
|
|
397
|
+
['Claude Code (local)', join(cwd, '.claude.json'), 'mcpServers'],
|
|
398
|
+
['VS Code (project)', join(cwd, '.vscode', 'mcp.json'), 'servers'],
|
|
399
|
+
['Cursor (user)', join(home, '.cursor', 'mcp.json'), 'mcpServers'],
|
|
400
|
+
];
|
|
401
|
+
// Claude Desktop varies by OS
|
|
402
|
+
if (os === 'darwin') {
|
|
403
|
+
locations.push(['Claude Desktop', join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'), 'mcpServers']);
|
|
404
|
+
} else if (os === 'linux') {
|
|
405
|
+
locations.push(['Claude Desktop', join(home, '.config', 'Claude', 'claude_desktop_config.json'), 'mcpServers']);
|
|
406
|
+
} else if (os === 'win32') {
|
|
407
|
+
locations.push(['Claude Desktop', join(home, 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json'), 'mcpServers']);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
console.log('barebrowse doctor — scanning known MCP config locations:\n');
|
|
411
|
+
const findings = [];
|
|
412
|
+
for (const [label, path, key] of locations) {
|
|
413
|
+
if (!existsSync(path)) {
|
|
414
|
+
console.log(` - ${label.padEnd(22)} ${path} (not present)`);
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
const cfg = readJsonOrEmpty(path);
|
|
418
|
+
const entry = cfg[key]?.barebrowse;
|
|
419
|
+
if (!entry) {
|
|
420
|
+
console.log(` - ${label.padEnd(22)} ${path} (no barebrowse entry)`);
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
const sig = `${entry.command || '?'} ${(entry.args || []).join(' ')}`;
|
|
424
|
+
console.log(` ✓ ${label.padEnd(22)} ${path}`);
|
|
425
|
+
console.log(` endpoint: ${sig}`);
|
|
426
|
+
findings.push({ label, path, sig });
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (findings.length <= 1) {
|
|
430
|
+
console.log(`\n${findings.length} registration${findings.length === 1 ? '' : 's'} found. No scope conflict.`);
|
|
431
|
+
} else {
|
|
432
|
+
const unique = new Set(findings.map((f) => f.sig));
|
|
433
|
+
if (unique.size === 1) {
|
|
434
|
+
console.log(`\n${findings.length} registrations found, all pointing at the same endpoint. No conflict.`);
|
|
435
|
+
} else {
|
|
436
|
+
console.log(`\n⚠ CONFLICT: ${findings.length} registrations across ${unique.size} different endpoints.`);
|
|
437
|
+
console.log(` Claude Code stores OAuth tokens per endpoint — authenticating in one scope`);
|
|
438
|
+
console.log(` will not carry over to the other. Recommended fix: keep one, remove the rest.\n`);
|
|
439
|
+
console.log(` Claude Code: claude mcp remove barebrowse -s user (or -s project / -s local)`);
|
|
440
|
+
console.log(` Other clients: edit the JSON file shown above and delete the barebrowse key.\n`);
|
|
441
|
+
console.log(` Tip: run \`barebrowse mcp\` directly to see the startup banner —`);
|
|
442
|
+
console.log(` the absolute serving path it prints to stderr is the one currently in use.`);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
342
447
|
|
|
343
448
|
// --- Usage ---
|
|
344
449
|
|
|
@@ -361,11 +466,13 @@ Session:
|
|
|
361
466
|
--proxy=URL HTTP/SOCKS proxy server
|
|
362
467
|
--viewport=WxH Viewport size (e.g. 1280x720)
|
|
363
468
|
--storage-state=FILE Load cookies/localStorage from JSON file
|
|
469
|
+
--download-path=DIR Directory for downloaded files (default: per-session temp dir)
|
|
364
470
|
|
|
365
471
|
Navigation:
|
|
366
472
|
barebrowse goto <url> Navigate to URL
|
|
367
473
|
barebrowse back Go back in history
|
|
368
474
|
barebrowse forward Go forward in history
|
|
475
|
+
barebrowse reload [--no-cache] Reload current page
|
|
369
476
|
barebrowse snapshot [--mode=M] ARIA snapshot -> .barebrowse/page-*.yml
|
|
370
477
|
barebrowse screenshot [--format] Screenshot -> .barebrowse/screenshot-*.png
|
|
371
478
|
barebrowse pdf [--landscape] PDF export -> .barebrowse/page-*.pdf
|
|
@@ -395,6 +502,7 @@ Debugging:
|
|
|
395
502
|
barebrowse console-logs Console logs -> .barebrowse/console-*.json
|
|
396
503
|
barebrowse network-log Network log -> .barebrowse/network-*.json
|
|
397
504
|
barebrowse dialog-log JS dialog log -> .barebrowse/dialogs-*.json
|
|
505
|
+
barebrowse downloads List Content-Disposition downloads + savedPath (JSON)
|
|
398
506
|
barebrowse save-state Cookies + localStorage -> .barebrowse/state-*.json
|
|
399
507
|
|
|
400
508
|
One-shot:
|
|
@@ -402,6 +510,9 @@ One-shot:
|
|
|
402
510
|
|
|
403
511
|
MCP:
|
|
404
512
|
barebrowse mcp Start MCP server (JSON-RPC over stdio)
|
|
513
|
+
barebrowse install [--force] Add barebrowse to detected MCP clients (--force replaces stale entries)
|
|
514
|
+
barebrowse install --skill Install Claude Code skill file
|
|
515
|
+
barebrowse doctor Scan MCP config locations for barebrowse entries + flag scope conflicts
|
|
405
516
|
barebrowse install Auto-configure MCP for Claude Desktop / Cursor
|
|
406
517
|
barebrowse install --skill Install SKILL.md for Claude Code
|
|
407
518
|
|