barebrowse 0.9.0 → 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 +36 -0
- package/README.md +2 -0
- package/barebrowse.context.md +3 -1
- package/mcp-server.js +4 -2
- package/package.json +1 -1
- package/src/bareagent.js +10 -4
- package/src/index.js +11 -3
- package/src/prune.js +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.1
|
|
4
|
+
|
|
5
|
+
### Pruning — `pruneMode` reaches MCP / bareagent and `read` finally works
|
|
6
|
+
|
|
7
|
+
- **`mode: 'read'` is now a real alias for `mode: 'browse'`** in `prune()`.
|
|
8
|
+
Previously, the CLI (`barebrowse snapshot --mode=read`) and the SKILL.md
|
|
9
|
+
advertised a `read` mode that did not exist — `MODE_REGIONS[mode] ||
|
|
10
|
+
MODE_REGIONS.act` silently fell back to act-mode pruning. Articles, docs,
|
|
11
|
+
and blog posts therefore came back gutted no matter which mode the agent
|
|
12
|
+
asked for, which is why Claude tended to give up and fall back to
|
|
13
|
+
WebFetch. One-line alias at the top of `prune()` fixes it; `act|browse|
|
|
14
|
+
navigate|full` still behave unchanged.
|
|
15
|
+
- **MCP `browse` and `snapshot` tools gained a `pruneMode: 'act'|'read'`
|
|
16
|
+
parameter** (mcp-server.js). Before this, the MCP surface had no way to
|
|
17
|
+
ask for any mode other than `act` — `browse`'s `mode` param was browser
|
|
18
|
+
mode (headless/headed/hybrid), and `snapshot` accepted only `maxChars`.
|
|
19
|
+
Tool descriptions now tell the caller when to pick `read` (content-heavy
|
|
20
|
+
pages: articles, docs, blogs).
|
|
21
|
+
- **bareagent `browse` and `snapshot` tools gained the same `pruneMode`
|
|
22
|
+
parameter** (`src/bareagent.js`) with identical semantics. The `browse`
|
|
23
|
+
handler preserves any caller-supplied default `opts.pruneMode` when the
|
|
24
|
+
tool is called without an arg (`pruneMode ? { ...opts, pruneMode } : opts`).
|
|
25
|
+
- **Auto-hint when act-mode looks suspect.** When `page.snapshot()` or
|
|
26
|
+
`browse()` is called in act mode against a substantial page (raw > 5 KB)
|
|
27
|
+
and the pruned output collapses to under 500 chars AND under 5% of raw,
|
|
28
|
+
the result includes a one-line `hint: act mode dropped most of the page
|
|
29
|
+
— retry with pruneMode='read' …` directly between the stats line and the
|
|
30
|
+
tree. Thresholds are deliberately conservative: an e-commerce or
|
|
31
|
+
search-results page (many interactive elements kept) won't trigger it;
|
|
32
|
+
a paragraph-heavy article will.
|
|
33
|
+
- **Regression test:** `test/unit/prune.test.js` — "aliases mode='read' to
|
|
34
|
+
browse mode" pins the alias contract by asserting `prune(tree, {mode:
|
|
35
|
+
'read'})` deep-equals `prune(tree, {mode: 'browse'})` and that paragraphs
|
|
36
|
+
survive (the act-mode-style stripping that previously masqueraded as
|
|
37
|
+
read-mode is gone).
|
|
38
|
+
|
|
3
39
|
## 0.9.0
|
|
4
40
|
|
|
5
41
|
Phase B — every H1–H9 from `docs/02-features/fix-plan.md` shipped one
|
package/README.md
CHANGED
|
@@ -94,6 +94,8 @@ Or manually add to your config (`claude_desktop_config.json`, `.cursor/mcp.json`
|
|
|
94
94
|
|
|
95
95
|
18 tools: `browse`, `goto`, `snapshot`, `click`, `type`, `press`, `scroll`, `hover`, `select`, `back`, `forward`, `reload`, `drag`, `upload`, `pdf`, `screenshot`, `wait_for`, `tabs`. Plus `assess` (privacy scan) if [wearehere](https://github.com/hamr0/wearehere) is installed. Plus opt-in `eval` (`BAREBROWSE_MCP_EVAL=1`) — runs JS in the authenticated session, off by default because it can read cookies/localStorage. Session runs in hybrid mode with automatic cookie injection. Per-tool timeouts (goto/reload/wait_for 60s, back/forward 30s, interactive ops 15s, pdf/screenshot/upload 45s) with auto-retry on transient failures (idempotent only — mutating tools fail loudly to avoid double-submits).
|
|
96
96
|
|
|
97
|
+
`browse` and `snapshot` accept `pruneMode: 'act'|'read'` (v0.9.1). `act` (default) keeps interactive elements — best for clicking/filling. `read` keeps paragraphs, headings, and long text — best for articles, docs, and content extraction. If act-mode collapses a content-heavy page near-totally, the snapshot includes a `hint: …` line suggesting `pruneMode='read'` so the agent doesn't bail to a separate HTTP fetch.
|
|
98
|
+
|
|
97
99
|
Troubleshooting MCP setup: `npx barebrowse doctor` scans every known config location and flags scope conflicts. `npx barebrowse install --force` overwrites an existing entry pointing at a different endpoint.
|
|
98
100
|
|
|
99
101
|
### 3. Library -- for agentic automation
|
package/barebrowse.context.md
CHANGED
|
@@ -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.9.
|
|
4
|
+
> v0.9.1 | Node.js >= 22 | 0 required deps | Apache-2.0
|
|
5
5
|
|
|
6
6
|
## What this is
|
|
7
7
|
|
|
@@ -255,6 +255,8 @@ Action tools return `'ok'` -- the agent calls `snapshot` explicitly to observe.
|
|
|
255
255
|
|
|
256
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
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.
|
|
259
|
+
|
|
258
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.
|
|
259
261
|
|
|
260
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).
|
package/mcp-server.js
CHANGED
|
@@ -150,6 +150,7 @@ export const TOOLS = [
|
|
|
150
150
|
properties: {
|
|
151
151
|
url: { type: 'string', description: 'URL to browse' },
|
|
152
152
|
mode: { type: 'string', enum: ['headless', 'headed', 'hybrid'], description: 'Browser mode (default: headless)' },
|
|
153
|
+
pruneMode: { type: 'string', enum: ['act', 'read'], description: 'Pruning mode. "act" (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. If the page is content-heavy and act-mode returns mostly empty, retry with "read".' },
|
|
153
154
|
maxChars: { type: 'number', description: 'Max chars to return inline. Larger snapshots are saved to .barebrowse/ and a file path is returned instead. Default: 30000.' },
|
|
154
155
|
},
|
|
155
156
|
required: ['url'],
|
|
@@ -172,6 +173,7 @@ export const TOOLS = [
|
|
|
172
173
|
inputSchema: {
|
|
173
174
|
type: 'object',
|
|
174
175
|
properties: {
|
|
176
|
+
pruneMode: { type: 'string', enum: ['act', 'read'], description: 'Pruning mode. "act" (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. If a previous snapshot looked empty on a content-heavy page, retry with "read".' },
|
|
175
177
|
maxChars: { type: 'number', description: 'Max chars to return inline. Larger snapshots are saved to .barebrowse/ and a file path is returned instead. Default: 30000.' },
|
|
176
178
|
},
|
|
177
179
|
},
|
|
@@ -374,7 +376,7 @@ async function handleToolCall(name, args) {
|
|
|
374
376
|
case 'browse': {
|
|
375
377
|
let timer;
|
|
376
378
|
const text = await Promise.race([
|
|
377
|
-
browse(args.url, { mode: args.mode }),
|
|
379
|
+
browse(args.url, { mode: args.mode, pruneMode: args.pruneMode }),
|
|
378
380
|
new Promise((_, rej) => { timer = setTimeout(() => rej(new Error('browse timed out after 60s')), 60000); }),
|
|
379
381
|
]);
|
|
380
382
|
clearTimeout(timer);
|
|
@@ -393,7 +395,7 @@ async function handleToolCall(name, args) {
|
|
|
393
395
|
}, TIMEOUTS.goto);
|
|
394
396
|
case 'snapshot': return withRetry(async () => {
|
|
395
397
|
const page = await getPage();
|
|
396
|
-
const text = await page.snapshot();
|
|
398
|
+
const text = await page.snapshot(args.pruneMode ? { mode: args.pruneMode } : undefined);
|
|
397
399
|
const limit = args.maxChars ?? MAX_CHARS_DEFAULT;
|
|
398
400
|
if (text.length > limit) {
|
|
399
401
|
const file = saveSnapshot(text);
|
package/package.json
CHANGED
package/src/bareagent.js
CHANGED
|
@@ -50,10 +50,11 @@ export function createBrowseTools(opts = {}) {
|
|
|
50
50
|
type: 'object',
|
|
51
51
|
properties: {
|
|
52
52
|
url: { type: 'string', description: 'URL to browse' },
|
|
53
|
+
pruneMode: { type: 'string', enum: ['act', 'read'], description: '"act" (default) for interactive elements only; "read" for paragraphs and long text (articles/docs).' },
|
|
53
54
|
},
|
|
54
55
|
required: ['url'],
|
|
55
56
|
},
|
|
56
|
-
execute: async ({ url }) => await browse(url, opts),
|
|
57
|
+
execute: async ({ url, pruneMode }) => await browse(url, pruneMode ? { ...opts, pruneMode } : opts),
|
|
57
58
|
},
|
|
58
59
|
{
|
|
59
60
|
name: 'goto',
|
|
@@ -70,10 +71,15 @@ export function createBrowseTools(opts = {}) {
|
|
|
70
71
|
{
|
|
71
72
|
name: 'snapshot',
|
|
72
73
|
description: 'Get the current ARIA snapshot. Returns a YAML-like tree with [ref=N] markers on interactive elements.',
|
|
73
|
-
parameters: {
|
|
74
|
-
|
|
74
|
+
parameters: {
|
|
75
|
+
type: 'object',
|
|
76
|
+
properties: {
|
|
77
|
+
pruneMode: { type: 'string', enum: ['act', 'read'], description: '"act" (default) for interactive elements only; "read" for paragraphs and long text (articles/docs).' },
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
execute: async ({ pruneMode } = {}) => {
|
|
75
81
|
const page = await getPage();
|
|
76
|
-
return await page.snapshot();
|
|
82
|
+
return await page.snapshot(pruneMode ? { mode: pruneMode } : undefined);
|
|
77
83
|
},
|
|
78
84
|
},
|
|
79
85
|
{
|
package/src/index.js
CHANGED
|
@@ -110,7 +110,11 @@ export async function browse(url, opts = {}) {
|
|
|
110
110
|
snapshot = raw;
|
|
111
111
|
}
|
|
112
112
|
const stats = `url: ${url}\n${raw.length.toLocaleString()} chars → ${snapshot.length.toLocaleString()} chars (${Math.round((1 - snapshot.length / raw.length) * 100)}% pruned)`;
|
|
113
|
-
|
|
113
|
+
const actMode = !opts.pruneMode || opts.pruneMode === 'act';
|
|
114
|
+
const hint = (actMode && raw.length > 5000 && snapshot.length < 500 && snapshot.length < raw.length * 0.05)
|
|
115
|
+
? `hint: act mode dropped most of the page — retry with pruneMode='read' for paragraphs and long text\n`
|
|
116
|
+
: '';
|
|
117
|
+
snapshot = stats + '\n' + hint + snapshot;
|
|
114
118
|
|
|
115
119
|
// Step 7: Clean up
|
|
116
120
|
await cdp.send('Target.closeTarget', { targetId: page.targetId });
|
|
@@ -382,10 +386,14 @@ export async function connect(opts = {}) {
|
|
|
382
386
|
const pageUrl = entries[currentIndex]?.url || '';
|
|
383
387
|
const warn = botBlocked ? '[BOT CHALLENGE DETECTED — page content may be incomplete or blocked]\n' : '';
|
|
384
388
|
if (pruneOpts === false) return `url: ${pageUrl}\n` + warn + raw;
|
|
385
|
-
const
|
|
389
|
+
const mode = pruneOpts?.mode || 'act';
|
|
390
|
+
const pruned = pruneTree(result.tree, { mode });
|
|
386
391
|
const out = formatTree(pruned);
|
|
387
392
|
const stats = `url: ${pageUrl}\n${raw.length.toLocaleString()} chars → ${out.length.toLocaleString()} chars (${Math.round((1 - out.length / raw.length) * 100)}% pruned)`;
|
|
388
|
-
|
|
393
|
+
const hint = (mode === 'act' && raw.length > 5000 && out.length < 500 && out.length < raw.length * 0.05)
|
|
394
|
+
? `hint: act mode dropped most of the page — retry with pruneMode='read' for paragraphs and long text\n`
|
|
395
|
+
: '';
|
|
396
|
+
return stats + '\n' + hint + warn + out;
|
|
389
397
|
},
|
|
390
398
|
|
|
391
399
|
async click(ref) {
|
package/src/prune.js
CHANGED
|
@@ -65,7 +65,8 @@ const SKIP_ROLES = new Set([
|
|
|
65
65
|
* @returns {object|null} Pruned tree
|
|
66
66
|
*/
|
|
67
67
|
export function prune(tree, options = {}) {
|
|
68
|
-
|
|
68
|
+
let { mode = 'act', context = '' } = options;
|
|
69
|
+
if (mode === 'read') mode = 'browse';
|
|
69
70
|
const allowedRegions = MODE_REGIONS[mode] || MODE_REGIONS.act;
|
|
70
71
|
const isBrowse = mode === 'browse';
|
|
71
72
|
const keywords = context
|