barebrowse 0.11.0 → 0.12.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 +36 -0
- package/README.md +2 -0
- package/barebrowse.context.md +6 -1
- package/package.json +31 -5
- package/src/auth.js +7 -4
- package/src/bareagent.js +16 -5
- package/src/cdp.js +4 -2
- package/src/chromium.js +7 -2
- package/src/daemon.js +8 -2
- package/src/index.js +20 -1
- package/src/network-idle.js +4 -1
- package/src/prune.js +1 -1
- package/src/session-client.js +1 -1
- package/src/wearehere.d.ts +6 -0
- package/types/aria.d.ts +17 -0
- package/types/auth.d.ts +35 -0
- package/types/bareagent.d.ts +25 -0
- package/types/blocklist.d.ts +21 -0
- package/types/cdp.d.ts +16 -0
- package/types/chromium.d.ts +58 -0
- package/types/consent.d.ts +9 -0
- package/types/daemon.d.ts +10 -0
- package/types/index.d.ts +138 -0
- package/types/interact.d.ts +79 -0
- package/types/network-idle.d.ts +19 -0
- package/types/prune.d.ts +13 -0
- package/types/session-client.d.ts +19 -0
- package/types/stealth.d.ts +14 -0
- package/types/url-guard.d.ts +26 -0
- package/.github/workflows/publish.yml +0 -26
- package/commands/barebrowse/SKILL.md +0 -137
- package/commands/barebrowse.md +0 -136
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
## [0.12.0] - 2026-05-29
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **Shipped TypeScript types, generated from JSDoc.** The package now ships
|
|
9
|
+
`.d.ts` declarations so adopters get autocomplete and type errors out of the
|
|
10
|
+
box — no `@types/barebrowse`. The `.js` we author is still the `.js` that
|
|
11
|
+
ships; there is **no build step for runtime code**. Types are generated by
|
|
12
|
+
`tsc` (`checkJs` + `strictNullChecks`), emitted to a git-ignored `types/`, and
|
|
13
|
+
built into the tarball at publish via `prepublishOnly`. Because they are
|
|
14
|
+
generated-and-never-committed, the JSDoc, the `.d.ts`, and CI cannot drift.
|
|
15
|
+
`exports` now carries a `types` condition on every subpath.
|
|
16
|
+
- **`ci.yml` (push/PR gate):** `npm ci → typecheck → build:types → test`. A
|
|
17
|
+
JSDoc/code mismatch is now a type error that blocks merge. No lint step — `tsc`
|
|
18
|
+
covers the bug class that matters for a vanilla-ESM lib.
|
|
19
|
+
- Dev-only tooling: `typescript` + `@types/node` (devDependencies; never
|
|
20
|
+
shipped), `tsconfig.json`, and `typecheck` / `build:types` / `prepublishOnly`
|
|
21
|
+
scripts.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- **`publish.yml` is now manual-only (`workflow_dispatch`) — npm OIDC trusted publishing with provenance, idempotent, and verifies the registry end-state.**
|
|
25
|
+
- **Packaging now uses a `files` allowlist** (`src/`, generated `types/`,
|
|
26
|
+
`cli.js`, `mcp-server.js`, and the doc set) instead of the old `.npmignore`
|
|
27
|
+
denylist, which was removed. Repo-only files (`test/`, `docs/`, `CLAUDE.md`)
|
|
28
|
+
are excluded from the tarball.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
- **`auth.js`: cookie databases are now opened with `readOnly: true`.** The
|
|
32
|
+
previous `readonly` (lowercase) key is silently ignored by `node:sqlite`;
|
|
33
|
+
surfaced by the new `tsc` typecheck. Read-only was already enforced via the
|
|
34
|
+
`?immutable=1` connection URI, so observable behavior is unchanged — this
|
|
35
|
+
honors the intended option. Added minimal, behavior-preserving null/type
|
|
36
|
+
guards in a few spots (`server.address()`, SQLite row values) flagged by
|
|
37
|
+
`strictNullChecks`.
|
|
38
|
+
|
|
3
39
|
## 0.11.0
|
|
4
40
|
|
|
5
41
|
### Security hardening — audit findings fixed, safe-by-default
|
package/README.md
CHANGED
|
@@ -35,6 +35,8 @@ npm install barebrowse
|
|
|
35
35
|
|
|
36
36
|
Requires Node.js >= 22 and any installed Chromium-based browser.
|
|
37
37
|
|
|
38
|
+
Ships with TypeScript types (generated from JSDoc) — autocomplete and type-checking work out of the box, no `@types/barebrowse` needed. The library is vanilla JS with no build step.
|
|
39
|
+
|
|
38
40
|
## Three ways to use it
|
|
39
41
|
|
|
40
42
|
### 1. CLI session -- for coding agents and quick testing
|
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.
|
|
4
|
+
> v0.12.0 | Node.js >= 22 | 0 required deps | Apache-2.0
|
|
5
5
|
|
|
6
6
|
## What this is
|
|
7
7
|
|
|
@@ -13,6 +13,11 @@ No Playwright. No bundled browser. No build step. Vanilla JS, ES modules.
|
|
|
13
13
|
npm install barebrowse
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
**TypeScript:** ships with `.d.ts` types generated from the source JSDoc, so
|
|
17
|
+
autocomplete and type-checking work out of the box — no `@types/barebrowse`
|
|
18
|
+
needed. The library itself is vanilla JS with no build step; the types are a
|
|
19
|
+
publish-time artifact.
|
|
20
|
+
|
|
16
21
|
Three integration paths:
|
|
17
22
|
1. **Library:** `import { browse, connect } from 'barebrowse'` -- one-shot or interactive session
|
|
18
23
|
2. **MCP server:** `barebrowse mcp` -- JSON-RPC over stdio for Claude Desktop, Cursor, etc.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "barebrowse",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Authenticated web browsing for autonomous agents via CDP. URL in, pruned ARIA snapshot out.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -10,18 +10,40 @@
|
|
|
10
10
|
"bugs": "https://github.com/hamr0/barebrowse/issues",
|
|
11
11
|
"type": "module",
|
|
12
12
|
"main": "src/index.js",
|
|
13
|
+
"types": "./types/index.d.ts",
|
|
13
14
|
"exports": {
|
|
14
|
-
".":
|
|
15
|
-
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./types/index.d.ts",
|
|
17
|
+
"default": "./src/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./bareagent": {
|
|
20
|
+
"types": "./types/bareagent.d.ts",
|
|
21
|
+
"default": "./src/bareagent.js"
|
|
22
|
+
}
|
|
16
23
|
},
|
|
17
24
|
"bin": {
|
|
18
25
|
"barebrowse": "./cli.js"
|
|
19
26
|
},
|
|
27
|
+
"files": [
|
|
28
|
+
"src/",
|
|
29
|
+
"types/",
|
|
30
|
+
"cli.js",
|
|
31
|
+
"mcp-server.js",
|
|
32
|
+
"barebrowse.context.md",
|
|
33
|
+
"README.md",
|
|
34
|
+
"CHANGELOG.md",
|
|
35
|
+
"NOTICE"
|
|
36
|
+
],
|
|
20
37
|
"engines": {
|
|
21
38
|
"node": ">=22"
|
|
22
39
|
},
|
|
23
40
|
"scripts": {
|
|
24
|
-
"test": "node --test test/unit/*.test.js test/integration/*.test.js"
|
|
41
|
+
"test": "node --test test/unit/*.test.js test/integration/*.test.js",
|
|
42
|
+
"test:unit": "node --test test/unit/*.test.js",
|
|
43
|
+
"test:integration": "node --test test/integration/*.test.js",
|
|
44
|
+
"typecheck": "tsc --noEmit",
|
|
45
|
+
"build:types": "tsc",
|
|
46
|
+
"prepublishOnly": "npm run build:types"
|
|
25
47
|
},
|
|
26
48
|
"keywords": [
|
|
27
49
|
"browser",
|
|
@@ -37,5 +59,9 @@
|
|
|
37
59
|
"optionalDependencies": {
|
|
38
60
|
"wearehere": "1.0.0"
|
|
39
61
|
},
|
|
40
|
-
"license": "Apache-2.0"
|
|
62
|
+
"license": "Apache-2.0",
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@types/node": "^25.9.1",
|
|
65
|
+
"typescript": "^6.0.3"
|
|
66
|
+
}
|
|
41
67
|
}
|
package/src/auth.js
CHANGED
|
@@ -126,7 +126,7 @@ function extractChromiumCookies(dbPath, domain) {
|
|
|
126
126
|
const aesKey = deriveKey(password);
|
|
127
127
|
|
|
128
128
|
// immutable=1 bypasses WAL lock on live databases
|
|
129
|
-
const db = new DatabaseSync(`file://${dbPath}?immutable=1`, {
|
|
129
|
+
const db = new DatabaseSync(`file://${dbPath}?immutable=1`, { readOnly: true });
|
|
130
130
|
|
|
131
131
|
let sql = `SELECT host_key, name, value, encrypted_value, path,
|
|
132
132
|
CAST(expires_utc AS TEXT) AS expires_utc, is_secure, is_httponly, samesite
|
|
@@ -144,7 +144,8 @@ function extractChromiumCookies(dbPath, domain) {
|
|
|
144
144
|
const SAMESITE = { 0: 'None', 1: 'Lax', 2: 'Strict' };
|
|
145
145
|
|
|
146
146
|
return rows.map((row) => {
|
|
147
|
-
const
|
|
147
|
+
const rawEnc = row.encrypted_value;
|
|
148
|
+
const enc = rawEnc instanceof Uint8Array ? Buffer.from(rawEnc) : Buffer.alloc(0);
|
|
148
149
|
let value;
|
|
149
150
|
try {
|
|
150
151
|
value = enc.length > 0 ? decryptCookie(enc, aesKey) : row.value;
|
|
@@ -154,7 +155,9 @@ function extractChromiumCookies(dbPath, domain) {
|
|
|
154
155
|
|
|
155
156
|
// Chrome timestamp: microseconds since 1601-01-01
|
|
156
157
|
const CHROME_EPOCH = 11644473600000000n;
|
|
157
|
-
const expiresUtc = row.expires_utc
|
|
158
|
+
const expiresUtc = typeof row.expires_utc === 'string' || typeof row.expires_utc === 'number'
|
|
159
|
+
? BigInt(row.expires_utc)
|
|
160
|
+
: 0n;
|
|
158
161
|
const expires = expiresUtc > 0n
|
|
159
162
|
? Number((expiresUtc - CHROME_EPOCH) / 1000000n)
|
|
160
163
|
: -1;
|
|
@@ -179,7 +182,7 @@ function extractChromiumCookies(dbPath, domain) {
|
|
|
179
182
|
* @returns {Array<object>} Cookies in CDP Network.setCookie format
|
|
180
183
|
*/
|
|
181
184
|
function extractFirefoxCookies(dbPath, domain) {
|
|
182
|
-
const db = new DatabaseSync(`file://${dbPath}?immutable=1`, {
|
|
185
|
+
const db = new DatabaseSync(`file://${dbPath}?immutable=1`, { readOnly: true });
|
|
183
186
|
|
|
184
187
|
let sql = `SELECT host, name, value, path, expiry, isSecure, isHttpOnly, sameSite
|
|
185
188
|
FROM moz_cookies`;
|
package/src/bareagent.js
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
* 300ms settle delay after actions for DOM updates.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
/// <reference path="./wearehere.d.ts" />
|
|
15
|
+
|
|
14
16
|
import { browse, connect } from './index.js';
|
|
15
17
|
|
|
16
18
|
// Optional: privacy assessment via wearehere
|
|
@@ -22,6 +24,14 @@ try {
|
|
|
22
24
|
const SETTLE_MS = 300;
|
|
23
25
|
const settle = () => new Promise((r) => setTimeout(r, SETTLE_MS));
|
|
24
26
|
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {object} BrowseTool
|
|
29
|
+
* @property {string} name
|
|
30
|
+
* @property {string} description
|
|
31
|
+
* @property {object} parameters - JSON-schema-shaped parameter spec
|
|
32
|
+
* @property {(args?: any) => Promise<any>} execute
|
|
33
|
+
*/
|
|
34
|
+
|
|
25
35
|
/**
|
|
26
36
|
* Create bareagent-compatible browse tools.
|
|
27
37
|
* @param {object} [opts] - Options passed to connect() for session tools
|
|
@@ -42,6 +52,7 @@ export function createBrowseTools(opts = {}) {
|
|
|
42
52
|
return await page.snapshot();
|
|
43
53
|
}
|
|
44
54
|
|
|
55
|
+
/** @type {BrowseTool[]} */
|
|
45
56
|
const tools = [
|
|
46
57
|
{
|
|
47
58
|
name: 'browse',
|
|
@@ -77,7 +88,7 @@ export function createBrowseTools(opts = {}) {
|
|
|
77
88
|
pruneMode: { type: 'string', enum: ['act', 'read'], description: '"act" (default) for interactive elements only; "read" for paragraphs and long text (articles/docs).' },
|
|
78
89
|
},
|
|
79
90
|
},
|
|
80
|
-
execute: async ({ pruneMode } = {}) => {
|
|
91
|
+
execute: async (/** @type {{ pruneMode?: string }} */ { pruneMode } = {}) => {
|
|
81
92
|
const page = await getPage();
|
|
82
93
|
return await page.snapshot(pruneMode ? { mode: pruneMode } : undefined);
|
|
83
94
|
},
|
|
@@ -231,7 +242,7 @@ export function createBrowseTools(opts = {}) {
|
|
|
231
242
|
landscape: { type: 'boolean', description: 'Landscape orientation (default: false)' },
|
|
232
243
|
},
|
|
233
244
|
},
|
|
234
|
-
execute: async ({ landscape } = {}) => {
|
|
245
|
+
execute: async (/** @type {{ landscape?: boolean }} */ { landscape } = {}) => {
|
|
235
246
|
const page = await getPage();
|
|
236
247
|
return await page.pdf({ landscape });
|
|
237
248
|
},
|
|
@@ -245,7 +256,7 @@ export function createBrowseTools(opts = {}) {
|
|
|
245
256
|
format: { type: 'string', enum: ['png', 'jpeg', 'webp'], description: 'Image format (default: png)' },
|
|
246
257
|
},
|
|
247
258
|
},
|
|
248
|
-
execute: async ({ format } = {}) => {
|
|
259
|
+
execute: async (/** @type {{ format?: string }} */ { format } = {}) => {
|
|
249
260
|
const page = await getPage();
|
|
250
261
|
return await page.screenshot({ format });
|
|
251
262
|
},
|
|
@@ -259,7 +270,7 @@ export function createBrowseTools(opts = {}) {
|
|
|
259
270
|
ignoreCache: { type: 'boolean', description: 'Bypass HTTP cache (hard reload). Default: false.' },
|
|
260
271
|
},
|
|
261
272
|
},
|
|
262
|
-
execute: async ({ ignoreCache } = {}) => actionAndSnapshot((page) => page.reload({ ignoreCache })),
|
|
273
|
+
execute: async (/** @type {{ ignoreCache?: boolean }} */ { ignoreCache } = {}) => actionAndSnapshot((page) => page.reload({ ignoreCache })),
|
|
263
274
|
},
|
|
264
275
|
{
|
|
265
276
|
name: 'wait_for',
|
|
@@ -272,7 +283,7 @@ export function createBrowseTools(opts = {}) {
|
|
|
272
283
|
timeout: { type: 'number', description: 'Timeout in ms (default: 30000)' },
|
|
273
284
|
},
|
|
274
285
|
},
|
|
275
|
-
execute: async ({ text, selector, timeout } = {}) => actionAndSnapshot((page) => page.waitFor({ text, selector, timeout })),
|
|
286
|
+
execute: async (/** @type {{ text?: string, selector?: string, timeout?: number }} */ { text, selector, timeout } = {}) => actionAndSnapshot((page) => page.waitFor({ text, selector, timeout })),
|
|
276
287
|
},
|
|
277
288
|
{
|
|
278
289
|
name: 'downloads',
|
package/src/cdp.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
/**
|
|
13
13
|
* Create a CDP client connected to the given WebSocket URL.
|
|
14
14
|
* @param {string} wsUrl - WebSocket URL (ws://127.0.0.1:PORT/devtools/...)
|
|
15
|
-
* @returns {Promise<
|
|
15
|
+
* @returns {Promise<object>} CDP client ({ send, on, once, session, close })
|
|
16
16
|
*/
|
|
17
17
|
export async function createCDP(wsUrl) {
|
|
18
18
|
const ws = new WebSocket(wsUrl);
|
|
@@ -20,7 +20,8 @@ export async function createCDP(wsUrl) {
|
|
|
20
20
|
const pending = new Map(); // id → { resolve, reject }
|
|
21
21
|
const listeners = new Map(); // "method" or "sessionId:method" → Set<callback>
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
/** @type {Promise<void>} */
|
|
24
|
+
const connected = new Promise((resolve, reject) => {
|
|
24
25
|
const timeout = setTimeout(() => reject(new Error('CDP connection timeout (5s)')), 5000);
|
|
25
26
|
ws.onopen = () => { clearTimeout(timeout); resolve(); };
|
|
26
27
|
ws.onerror = (e) => {
|
|
@@ -28,6 +29,7 @@ export async function createCDP(wsUrl) {
|
|
|
28
29
|
reject(new Error(`CDP WebSocket connection failed: ${e.message || 'unknown error'}`));
|
|
29
30
|
};
|
|
30
31
|
});
|
|
32
|
+
await connected;
|
|
31
33
|
|
|
32
34
|
ws.onmessage = (event) => {
|
|
33
35
|
const msg = JSON.parse(typeof event.data === 'string' ? event.data : event.data.toString());
|
package/src/chromium.js
CHANGED
|
@@ -112,7 +112,8 @@ export function findBrowser() {
|
|
|
112
112
|
* @param {number} [opts.port=0] - CDP port (0 = random available port)
|
|
113
113
|
* @param {string} [opts.userDataDir] - Browser profile directory
|
|
114
114
|
* @param {boolean} [opts.headed=false] - Launch in headed mode (with visible window)
|
|
115
|
-
* @
|
|
115
|
+
* @param {string} [opts.proxy] - Proxy server (e.g. 'http://host:port')
|
|
116
|
+
* @returns {Promise<{wsUrl: string, process: import('node:child_process').ChildProcess, port: number}>}
|
|
116
117
|
*/
|
|
117
118
|
export async function launch(opts = {}) {
|
|
118
119
|
const binary = opts.binary || findBrowser();
|
|
@@ -235,6 +236,7 @@ export async function cleanupBrowser(browser) {
|
|
|
235
236
|
if (!browser) return;
|
|
236
237
|
activeBrowsers.delete(browser);
|
|
237
238
|
if (browser.process && !browser.process.killed && browser.process.exitCode === null) {
|
|
239
|
+
/** @type {Promise<void>} */
|
|
238
240
|
const exited = new Promise((resolve) => {
|
|
239
241
|
const timer = setTimeout(resolve, 2000);
|
|
240
242
|
browser.process.once('exit', () => { clearTimeout(timer); resolve(); });
|
|
@@ -282,7 +284,10 @@ export async function cleanupBrowser(browser) {
|
|
|
282
284
|
export async function getDebugUrl(port) {
|
|
283
285
|
const res = await fetch(`http://127.0.0.1:${port}/json/version`);
|
|
284
286
|
if (!res.ok) throw new Error(`Cannot reach browser debug port at ${port}: ${res.status}`);
|
|
285
|
-
const data = await res.json();
|
|
287
|
+
const data = /** @type {{ webSocketDebuggerUrl?: string }} */ (await res.json());
|
|
288
|
+
if (!data.webSocketDebuggerUrl) {
|
|
289
|
+
throw new Error(`Browser debug port at ${port} returned no webSocketDebuggerUrl`);
|
|
290
|
+
}
|
|
286
291
|
return data.webSocketDebuggerUrl;
|
|
287
292
|
}
|
|
288
293
|
|
package/src/daemon.js
CHANGED
|
@@ -416,11 +416,17 @@ export async function runDaemon(opts, outputDir, initialUrl) {
|
|
|
416
416
|
}
|
|
417
417
|
});
|
|
418
418
|
|
|
419
|
-
|
|
419
|
+
/** @type {Promise<void>} */
|
|
420
|
+
const listening = new Promise((resolve) => {
|
|
420
421
|
server.listen(0, '127.0.0.1', () => resolve());
|
|
421
422
|
});
|
|
423
|
+
await listening;
|
|
422
424
|
|
|
423
|
-
const
|
|
425
|
+
const address = server.address();
|
|
426
|
+
if (!address || typeof address === 'string') {
|
|
427
|
+
throw new Error('Daemon server failed to bind to a TCP port');
|
|
428
|
+
}
|
|
429
|
+
const port = address.port;
|
|
424
430
|
|
|
425
431
|
// Write session.json so parent/clients can find us. Owner-only: it carries
|
|
426
432
|
// the auth token that gates /command.
|
package/src/index.js
CHANGED
|
@@ -37,6 +37,17 @@ import { chmodSync } from 'node:fs';
|
|
|
37
37
|
* See src/blocklist.js for the default set. Set false to disable.
|
|
38
38
|
* @param {string[]} [opts.blockUrls] - Extra URL glob patterns to block,
|
|
39
39
|
* merged with the default unless blockAds:false.
|
|
40
|
+
* @param {boolean} [opts.allowLocalUrls=false] - Permit navigation to local-
|
|
41
|
+
* resource schemes (file:, view-source:, chrome:, …). Blocked by default.
|
|
42
|
+
* @param {boolean} [opts.blockPrivateNetwork=false] - Reject navigation to
|
|
43
|
+
* loopback / RFC-1918 / link-local / cloud-metadata hosts (SSRF guard).
|
|
44
|
+
* @param {string} [opts.proxy] - Proxy server (e.g. 'http://host:port').
|
|
45
|
+
* @param {string} [opts.binary] - Path to browser binary (auto-detected if omitted).
|
|
46
|
+
* @param {string} [opts.userDataDir] - Browser profile directory.
|
|
47
|
+
* @param {{width: number, height: number}} [opts.viewport] - Viewport dimensions.
|
|
48
|
+
* @param {string} [opts.browser] - Source browser for cookie extraction.
|
|
49
|
+
* @param {boolean} [opts.consent=true] - Auto-dismiss cookie consent dialogs.
|
|
50
|
+
* @param {'act'|'browse'|'navigate'|'full'|'read'} [opts.pruneMode='act'] - Pruning mode.
|
|
40
51
|
* @returns {Promise<string>} ARIA snapshot text
|
|
41
52
|
*/
|
|
42
53
|
export async function browse(url, opts = {}) {
|
|
@@ -169,6 +180,14 @@ export async function browse(url, opts = {}) {
|
|
|
169
180
|
* @param {string} [opts.uploadDir] - When set, upload() rejects any file that
|
|
170
181
|
* does not resolve (symlinks included) inside this directory. Sandboxes the
|
|
171
182
|
* agent's file-upload capability. Default: no restriction.
|
|
183
|
+
* @param {string} [opts.proxy] - Proxy server (e.g. 'http://host:port').
|
|
184
|
+
* @param {string} [opts.binary] - Path to browser binary (auto-detected if omitted).
|
|
185
|
+
* @param {string} [opts.userDataDir] - Browser profile directory.
|
|
186
|
+
* @param {{width: number, height: number}} [opts.viewport] - Viewport dimensions.
|
|
187
|
+
* @param {boolean} [opts.consent=true] - Auto-dismiss cookie consent dialogs.
|
|
188
|
+
* @param {string} [opts.storageState] - Path to a storage-state JSON file
|
|
189
|
+
* (cookies + localStorage) to load before navigation.
|
|
190
|
+
* @param {'act'|'browse'|'navigate'|'full'|'read'} [opts.pruneMode='act'] - Pruning mode.
|
|
172
191
|
* @returns {Promise<object>} Page handle with goto, snapshot, close
|
|
173
192
|
*/
|
|
174
193
|
export async function connect(opts = {}) {
|
|
@@ -189,7 +208,7 @@ export async function connect(opts = {}) {
|
|
|
189
208
|
// Reuse the user's running browser — do not launch, do not own the
|
|
190
209
|
// profile. cleanupBrowser() is a no-op on this shape (process: null,
|
|
191
210
|
// ownedProfileDir: null), which is the whole point.
|
|
192
|
-
browser = await attach({ port: opts.port });
|
|
211
|
+
browser = await attach({ port: opts.port ?? 0 });
|
|
193
212
|
cdp = await createCDP(browser.wsUrl);
|
|
194
213
|
} else if (mode === 'headed') {
|
|
195
214
|
browser = await launch({ ...launchOpts, headed: true });
|
package/src/network-idle.js
CHANGED
|
@@ -12,12 +12,14 @@
|
|
|
12
12
|
* @param {object} [opts]
|
|
13
13
|
* @param {number} [opts.timeout=30000] - Max wait time before reject
|
|
14
14
|
* @param {number} [opts.idle=500] - Required idle duration before resolve
|
|
15
|
+
* @returns {Promise<void>}
|
|
15
16
|
*/
|
|
16
17
|
export function waitForNetworkIdle(session, opts = {}) {
|
|
17
18
|
const timeout = opts.timeout || 30000;
|
|
18
19
|
const idle = opts.idle || 500;
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
/** @type {Promise<void>} */
|
|
22
|
+
const settled = new Promise((resolve, reject) => {
|
|
21
23
|
const pending = new Set();
|
|
22
24
|
let timer = null;
|
|
23
25
|
const unsubs = [];
|
|
@@ -59,4 +61,5 @@ export function waitForNetworkIdle(session, opts = {}) {
|
|
|
59
61
|
// Start check immediately (might already be idle)
|
|
60
62
|
check();
|
|
61
63
|
});
|
|
64
|
+
return settled;
|
|
62
65
|
}
|
package/src/prune.js
CHANGED
|
@@ -60,7 +60,7 @@ const SKIP_ROLES = new Set([
|
|
|
60
60
|
*
|
|
61
61
|
* @param {object} tree - Root node from buildTree() (CDP format)
|
|
62
62
|
* @param {object} [options]
|
|
63
|
-
* @param {'act'|'browse'|'navigate'|'full'} [options.mode='act'] - Pruning mode
|
|
63
|
+
* @param {'act'|'browse'|'navigate'|'full'|'read'} [options.mode='act'] - Pruning mode ('read' is an alias for 'browse')
|
|
64
64
|
* @param {string} [options.context=''] - Search context for relevance filtering
|
|
65
65
|
* @returns {object|null} Pruned tree
|
|
66
66
|
*/
|
package/src/session-client.js
CHANGED
|
@@ -13,7 +13,7 @@ const SESSION_FILE = 'session.json';
|
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Read session.json from the output directory.
|
|
16
|
-
* @returns {{ port: number, pid: number, startedAt: string } | null}
|
|
16
|
+
* @returns {{ port: number, pid: number, token?: string, startedAt: string } | null}
|
|
17
17
|
*/
|
|
18
18
|
export function readSession(outputDir) {
|
|
19
19
|
const sessionPath = join(resolve(outputDir), SESSION_FILE);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Ambient shim for the optional 'wearehere' dependency.
|
|
2
|
+
// It is dynamically imported and may not be installed; this declaration
|
|
3
|
+
// satisfies the typechecker without pulling in a hard dependency.
|
|
4
|
+
declare module 'wearehere' {
|
|
5
|
+
export function assess(...args: any[]): Promise<any>;
|
|
6
|
+
}
|
package/types/aria.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* aria.js — Format ARIA accessibility tree nodes for agent consumption.
|
|
3
|
+
*
|
|
4
|
+
* Takes a nested tree (built from CDP's Accessibility.getFullAXTree)
|
|
5
|
+
* and formats it as readable YAML-like text, similar to Playwright's ariaSnapshot.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Format a nested ARIA tree as readable text output.
|
|
9
|
+
*
|
|
10
|
+
* Output format (one node per line, indented):
|
|
11
|
+
* - role "name" [props] [ref=nodeId]
|
|
12
|
+
*
|
|
13
|
+
* @param {object} node - Tree node { role, name, properties, children, ignored, nodeId }
|
|
14
|
+
* @param {number} [depth=0] - Current indentation depth
|
|
15
|
+
* @returns {string} Formatted ARIA tree text
|
|
16
|
+
*/
|
|
17
|
+
export function formatTree(node: object, depth?: number): string;
|
package/types/auth.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract cookies from the user's browser, auto-detecting which browser to use.
|
|
3
|
+
* @param {object} [opts]
|
|
4
|
+
* @param {string} [opts.browser] - 'chromium', 'chrome', 'brave', 'edge', 'firefox', or 'auto'
|
|
5
|
+
* @param {string} [opts.domain] - Filter by domain
|
|
6
|
+
* @returns {Array<object>} Cookies in CDP-compatible format
|
|
7
|
+
*/
|
|
8
|
+
export function extractCookies(opts?: {
|
|
9
|
+
browser?: string | undefined;
|
|
10
|
+
domain?: string | undefined;
|
|
11
|
+
}): Array<object>;
|
|
12
|
+
/**
|
|
13
|
+
* Inject cookies into a CDP session via Network.setCookie.
|
|
14
|
+
* @param {object} session - CDP session handle (from cdp.session())
|
|
15
|
+
* @param {Array<object>} cookies - Cookies from extractCookies()
|
|
16
|
+
*/
|
|
17
|
+
export function injectCookies(session: object, cookies: Array<object>): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* RFC 6265 domain-match: does `host` belong to a cookie declared for
|
|
20
|
+
* `cookieDomain`? Leading dot on the cookie domain is ignored (host-only
|
|
21
|
+
* vs domain cookies are matched the same here, intentionally — we want
|
|
22
|
+
* parent-domain cookies like .google.com to apply to mail.google.com).
|
|
23
|
+
* @param {string} host - target hostname (e.g. 'mail.google.com')
|
|
24
|
+
* @param {string} cookieDomain - cookie's host_key (e.g. '.google.com')
|
|
25
|
+
* @returns {boolean}
|
|
26
|
+
*/
|
|
27
|
+
export function cookieDomainMatch(host: string, cookieDomain: string): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Extract cookies for a URL and inject them into a CDP session.
|
|
30
|
+
* Convenience function combining extractCookies + injectCookies.
|
|
31
|
+
* @param {object} session - CDP session handle
|
|
32
|
+
* @param {string} url - URL to extract cookies for
|
|
33
|
+
* @param {object} [opts] - Options passed to extractCookies
|
|
34
|
+
*/
|
|
35
|
+
export function authenticate(session: object, url: string, opts?: object): Promise<number>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {object} BrowseTool
|
|
3
|
+
* @property {string} name
|
|
4
|
+
* @property {string} description
|
|
5
|
+
* @property {object} parameters - JSON-schema-shaped parameter spec
|
|
6
|
+
* @property {(args?: any) => Promise<any>} execute
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Create bareagent-compatible browse tools.
|
|
10
|
+
* @param {object} [opts] - Options passed to connect() for session tools
|
|
11
|
+
* @returns {{ tools: Array, close: () => Promise<void> }}
|
|
12
|
+
*/
|
|
13
|
+
export function createBrowseTools(opts?: object): {
|
|
14
|
+
tools: any[];
|
|
15
|
+
close: () => Promise<void>;
|
|
16
|
+
};
|
|
17
|
+
export type BrowseTool = {
|
|
18
|
+
name: string;
|
|
19
|
+
description: string;
|
|
20
|
+
/**
|
|
21
|
+
* - JSON-schema-shaped parameter spec
|
|
22
|
+
*/
|
|
23
|
+
parameters: object;
|
|
24
|
+
execute: (args?: any) => Promise<any>;
|
|
25
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* blocklist.js — Ad/tracker URL patterns for CDP Network.setBlockedURLs.
|
|
3
|
+
*
|
|
4
|
+
* Curated by real-world frequency, not pulled wholesale from Peter Lowe /
|
|
5
|
+
* EasyList. CDP does linear pattern matching per request, so 3,000-entry
|
|
6
|
+
* lists add ~150ms cumulative cost on a typical page for ~5% extra coverage
|
|
7
|
+
* (long-tail regional networks the agent rarely encounters). The set below
|
|
8
|
+
* is ~120 patterns covering the trackers that actually show up in agent
|
|
9
|
+
* traffic: Google/FB/Amazon/MS/Adobe ad+analytics, the major SaaS analytics
|
|
10
|
+
* stacks (Segment/Amplitude/Mixpanel/HubSpot/Hotjar/FullStory/Heap/Mouseflow),
|
|
11
|
+
* session-replay (LogRocket/Crazy Egg/Optimizely/VWO), content-recommendation
|
|
12
|
+
* (Taboola/Outbrain/Criteo), and the consumer-pixel cluster (LinkedIn/Twitter/
|
|
13
|
+
* TikTok/Snap/Pinterest/Reddit).
|
|
14
|
+
*
|
|
15
|
+
* Patterns are CDP-format globs: '*' matches any character run.
|
|
16
|
+
*
|
|
17
|
+
* To extend at runtime, pass connect({ blockUrls: [...] }) — your patterns
|
|
18
|
+
* are merged with this default. To turn the default off entirely, pass
|
|
19
|
+
* { blockAds: false }.
|
|
20
|
+
*/
|
|
21
|
+
export const DEFAULT_BLOCKLIST: string[];
|
package/types/cdp.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cdp.js — Minimal Chrome DevTools Protocol client over WebSocket.
|
|
3
|
+
*
|
|
4
|
+
* Sends JSON-RPC commands, receives responses and events.
|
|
5
|
+
* Uses Node 22's built-in WebSocket (no external deps).
|
|
6
|
+
*
|
|
7
|
+
* Supports flattened sessions: when a sessionId is provided,
|
|
8
|
+
* it's sent at the top level of the message (not inside params).
|
|
9
|
+
* Events from sessions are also dispatched by sessionId.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Create a CDP client connected to the given WebSocket URL.
|
|
13
|
+
* @param {string} wsUrl - WebSocket URL (ws://127.0.0.1:PORT/devtools/...)
|
|
14
|
+
* @returns {Promise<object>} CDP client ({ send, on, once, session, close })
|
|
15
|
+
*/
|
|
16
|
+
export function createCDP(wsUrl: string): Promise<object>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find the first available Chromium binary on the system.
|
|
3
|
+
* @returns {string} Path to the binary
|
|
4
|
+
* @throws {Error} If no Chromium browser is found
|
|
5
|
+
*/
|
|
6
|
+
export function findBrowser(): string;
|
|
7
|
+
/**
|
|
8
|
+
* Launch a Chromium instance with CDP enabled.
|
|
9
|
+
* @param {object} [opts]
|
|
10
|
+
* @param {string} [opts.binary] - Path to browser binary (auto-detected if omitted)
|
|
11
|
+
* @param {number} [opts.port=0] - CDP port (0 = random available port)
|
|
12
|
+
* @param {string} [opts.userDataDir] - Browser profile directory
|
|
13
|
+
* @param {boolean} [opts.headed=false] - Launch in headed mode (with visible window)
|
|
14
|
+
* @param {string} [opts.proxy] - Proxy server (e.g. 'http://host:port')
|
|
15
|
+
* @returns {Promise<{wsUrl: string, process: import('node:child_process').ChildProcess, port: number}>}
|
|
16
|
+
*/
|
|
17
|
+
export function launch(opts?: {
|
|
18
|
+
binary?: string | undefined;
|
|
19
|
+
port?: number | undefined;
|
|
20
|
+
userDataDir?: string | undefined;
|
|
21
|
+
headed?: boolean | undefined;
|
|
22
|
+
proxy?: string | undefined;
|
|
23
|
+
}): Promise<{
|
|
24
|
+
wsUrl: string;
|
|
25
|
+
process: import("node:child_process").ChildProcess;
|
|
26
|
+
port: number;
|
|
27
|
+
}>;
|
|
28
|
+
/**
|
|
29
|
+
* Kill a launched browser and remove its temp profile dir (if we created one).
|
|
30
|
+
* Waits up to 2s for the process to actually exit before unlinking the dir —
|
|
31
|
+
* Chromium can still hold files briefly after SIGTERM, which races rmSync.
|
|
32
|
+
* Safe to call on partially-failed launches or already-dead processes.
|
|
33
|
+
* @returns {Promise<void>}
|
|
34
|
+
*/
|
|
35
|
+
export function cleanupBrowser(browser: any): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Get the CDP WebSocket URL for a browser already running with --remote-debugging-port.
|
|
38
|
+
* @param {number} port - The debug port
|
|
39
|
+
* @returns {Promise<string>} WebSocket URL
|
|
40
|
+
*/
|
|
41
|
+
export function getDebugUrl(port: number): Promise<string>;
|
|
42
|
+
/**
|
|
43
|
+
* Attach to a Chromium already running with --remote-debugging-port=<port>.
|
|
44
|
+
* Returns the same shape as launch() but with process: null and
|
|
45
|
+
* ownedProfileDir: null — cleanupBrowser() becomes a no-op so we never
|
|
46
|
+
* kill a browser we did not start or remove a profile we do not own.
|
|
47
|
+
* @param {object} opts
|
|
48
|
+
* @param {number} opts.port - The debug port the running browser is listening on
|
|
49
|
+
* @returns {Promise<{wsUrl: string, process: null, port: number, ownedProfileDir: null}>}
|
|
50
|
+
*/
|
|
51
|
+
export function attach({ port }: {
|
|
52
|
+
port: number;
|
|
53
|
+
}): Promise<{
|
|
54
|
+
wsUrl: string;
|
|
55
|
+
process: null;
|
|
56
|
+
port: number;
|
|
57
|
+
ownedProfileDir: null;
|
|
58
|
+
}>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Try to dismiss a cookie consent dialog on the current page.
|
|
3
|
+
* Inspects the ARIA tree for dialog elements with consent-related content,
|
|
4
|
+
* then clicks the "accept" button.
|
|
5
|
+
*
|
|
6
|
+
* @param {object} session - Session-scoped CDP handle
|
|
7
|
+
* @returns {Promise<boolean>} true if a consent dialog was dismissed
|
|
8
|
+
*/
|
|
9
|
+
export function dismissConsent(session: object): Promise<boolean>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spawn a detached child process that runs the daemon.
|
|
3
|
+
* Parent polls for session.json, then exits.
|
|
4
|
+
*/
|
|
5
|
+
export function startDaemon(opts: any, outputDir: any, initialUrl: any): Promise<any>;
|
|
6
|
+
/**
|
|
7
|
+
* Run the daemon HTTP server. Called by cli.js --daemon-internal.
|
|
8
|
+
* Holds a connect() session and serves commands over HTTP.
|
|
9
|
+
*/
|
|
10
|
+
export function runDaemon(opts: any, outputDir: any, initialUrl: any): Promise<void>;
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browse a URL and return an ARIA snapshot.
|
|
3
|
+
* This is the primary API — URL in, agent-ready snapshot out.
|
|
4
|
+
*
|
|
5
|
+
* @param {string} url - The URL to browse
|
|
6
|
+
* @param {object} [opts]
|
|
7
|
+
* @param {'headless'|'headed'|'hybrid'} [opts.mode='headless'] - Browser mode
|
|
8
|
+
* @param {boolean} [opts.cookies=true] - Inject user's cookies (Phase 2)
|
|
9
|
+
* @param {boolean} [opts.prune=true] - Apply ARIA pruning (Phase 2)
|
|
10
|
+
* @param {number} [opts.timeout=30000] - Navigation timeout in ms
|
|
11
|
+
* @param {boolean} [opts.blockAds=true] - Block ~120 common ad/tracker
|
|
12
|
+
* URL patterns via CDP. Shrinks ARIA snapshots and speeds page loads.
|
|
13
|
+
* See src/blocklist.js for the default set. Set false to disable.
|
|
14
|
+
* @param {string[]} [opts.blockUrls] - Extra URL glob patterns to block,
|
|
15
|
+
* merged with the default unless blockAds:false.
|
|
16
|
+
* @param {boolean} [opts.allowLocalUrls=false] - Permit navigation to local-
|
|
17
|
+
* resource schemes (file:, view-source:, chrome:, …). Blocked by default.
|
|
18
|
+
* @param {boolean} [opts.blockPrivateNetwork=false] - Reject navigation to
|
|
19
|
+
* loopback / RFC-1918 / link-local / cloud-metadata hosts (SSRF guard).
|
|
20
|
+
* @param {string} [opts.proxy] - Proxy server (e.g. 'http://host:port').
|
|
21
|
+
* @param {string} [opts.binary] - Path to browser binary (auto-detected if omitted).
|
|
22
|
+
* @param {string} [opts.userDataDir] - Browser profile directory.
|
|
23
|
+
* @param {{width: number, height: number}} [opts.viewport] - Viewport dimensions.
|
|
24
|
+
* @param {string} [opts.browser] - Source browser for cookie extraction.
|
|
25
|
+
* @param {boolean} [opts.consent=true] - Auto-dismiss cookie consent dialogs.
|
|
26
|
+
* @param {'act'|'browse'|'navigate'|'full'|'read'} [opts.pruneMode='act'] - Pruning mode.
|
|
27
|
+
* @returns {Promise<string>} ARIA snapshot text
|
|
28
|
+
*/
|
|
29
|
+
export function browse(url: string, opts?: {
|
|
30
|
+
mode?: "headless" | "headed" | "hybrid" | undefined;
|
|
31
|
+
cookies?: boolean | undefined;
|
|
32
|
+
prune?: boolean | undefined;
|
|
33
|
+
timeout?: number | undefined;
|
|
34
|
+
blockAds?: boolean | undefined;
|
|
35
|
+
blockUrls?: string[] | undefined;
|
|
36
|
+
allowLocalUrls?: boolean | undefined;
|
|
37
|
+
blockPrivateNetwork?: boolean | undefined;
|
|
38
|
+
proxy?: string | undefined;
|
|
39
|
+
binary?: string | undefined;
|
|
40
|
+
userDataDir?: string | undefined;
|
|
41
|
+
viewport?: {
|
|
42
|
+
width: number;
|
|
43
|
+
height: number;
|
|
44
|
+
} | undefined;
|
|
45
|
+
browser?: string | undefined;
|
|
46
|
+
consent?: boolean | undefined;
|
|
47
|
+
pruneMode?: "act" | "browse" | "navigate" | "full" | "read" | undefined;
|
|
48
|
+
}): Promise<string>;
|
|
49
|
+
/**
|
|
50
|
+
* Connect to a browser for a long-lived interactive session.
|
|
51
|
+
*
|
|
52
|
+
* @param {object} [opts]
|
|
53
|
+
* @param {'headless'|'headed'|'hybrid'} [opts.mode='headless'] - Browser mode
|
|
54
|
+
* @param {number} [opts.port] - Attach to an already-running Chromium at this
|
|
55
|
+
* CDP port instead of launching a new one. The browser keeps running on
|
|
56
|
+
* close(); only the tab we created is torn down. Use this to drive a
|
|
57
|
+
* user's logged-in session (start Chromium with --remote-debugging-port=N).
|
|
58
|
+
* @param {string} [opts.downloadPath] - Directory to save downloaded files.
|
|
59
|
+
* Default: a per-session subdirectory under the OS temp dir. Downloads
|
|
60
|
+
* land here as <guid>; check `page.downloads` for { url, suggestedFilename,
|
|
61
|
+
* savedPath, state, totalBytes, receivedBytes } per file.
|
|
62
|
+
* @param {boolean} [opts.blockAds] - Block ~120 common ad/tracker URL
|
|
63
|
+
* patterns via CDP. Defaults to true for launched browsers, false in
|
|
64
|
+
* attach mode (would affect any tab attached to the user's running
|
|
65
|
+
* session). Setting blockAds:true explicitly in attach mode honors the
|
|
66
|
+
* request — blocking applies to whichever tab the session is currently
|
|
67
|
+
* attached to and follows the session across switchTab() until close.
|
|
68
|
+
* @param {string[]} [opts.blockUrls] - Extra URL glob patterns to block,
|
|
69
|
+
* merged with the default unless blockAds is false.
|
|
70
|
+
* @param {boolean} [opts.allowLocalUrls=false] - Permit navigation to local-
|
|
71
|
+
* resource schemes (file:, view-source:, chrome:, …). Blocked by default
|
|
72
|
+
* because a prompt-injected agent could use them to read local files.
|
|
73
|
+
* @param {boolean} [opts.blockPrivateNetwork=false] - Reject navigation to
|
|
74
|
+
* loopback / RFC-1918 / link-local / cloud-metadata hosts (SSRF guard).
|
|
75
|
+
* Off by default so localhost dev-server browsing keeps working.
|
|
76
|
+
* @param {string} [opts.uploadDir] - When set, upload() rejects any file that
|
|
77
|
+
* does not resolve (symlinks included) inside this directory. Sandboxes the
|
|
78
|
+
* agent's file-upload capability. Default: no restriction.
|
|
79
|
+
* @param {string} [opts.proxy] - Proxy server (e.g. 'http://host:port').
|
|
80
|
+
* @param {string} [opts.binary] - Path to browser binary (auto-detected if omitted).
|
|
81
|
+
* @param {string} [opts.userDataDir] - Browser profile directory.
|
|
82
|
+
* @param {{width: number, height: number}} [opts.viewport] - Viewport dimensions.
|
|
83
|
+
* @param {boolean} [opts.consent=true] - Auto-dismiss cookie consent dialogs.
|
|
84
|
+
* @param {string} [opts.storageState] - Path to a storage-state JSON file
|
|
85
|
+
* (cookies + localStorage) to load before navigation.
|
|
86
|
+
* @param {'act'|'browse'|'navigate'|'full'|'read'} [opts.pruneMode='act'] - Pruning mode.
|
|
87
|
+
* @returns {Promise<object>} Page handle with goto, snapshot, close
|
|
88
|
+
*/
|
|
89
|
+
export function connect(opts?: {
|
|
90
|
+
mode?: "headless" | "headed" | "hybrid" | undefined;
|
|
91
|
+
port?: number | undefined;
|
|
92
|
+
downloadPath?: string | undefined;
|
|
93
|
+
blockAds?: boolean | undefined;
|
|
94
|
+
blockUrls?: string[] | undefined;
|
|
95
|
+
allowLocalUrls?: boolean | undefined;
|
|
96
|
+
blockPrivateNetwork?: boolean | undefined;
|
|
97
|
+
uploadDir?: string | undefined;
|
|
98
|
+
proxy?: string | undefined;
|
|
99
|
+
binary?: string | undefined;
|
|
100
|
+
userDataDir?: string | undefined;
|
|
101
|
+
viewport?: {
|
|
102
|
+
width: number;
|
|
103
|
+
height: number;
|
|
104
|
+
} | undefined;
|
|
105
|
+
consent?: boolean | undefined;
|
|
106
|
+
storageState?: string | undefined;
|
|
107
|
+
pruneMode?: "act" | "browse" | "navigate" | "full" | "read" | undefined;
|
|
108
|
+
}): Promise<object>;
|
|
109
|
+
/**
|
|
110
|
+
* Apply Network.setBlockedURLs for ad/tracker blocking on a session.
|
|
111
|
+
* Default list is on; pass blockAds:false to skip, blockUrls:[] to extend.
|
|
112
|
+
* On failure (legacy Chrome lacking the method) warns once and continues —
|
|
113
|
+
* blocking is an enhancement, not a hard requirement.
|
|
114
|
+
*
|
|
115
|
+
* Exported for unit testing of the warn-once behavior; not part of the public
|
|
116
|
+
* API surface.
|
|
117
|
+
*/
|
|
118
|
+
export function applyBlocklist(session: any, pageOpts: any): Promise<void>;
|
|
119
|
+
/** Test-only: reset the warn-once flag. Not part of the public API. */
|
|
120
|
+
export function _resetBlocklistWarning(): void;
|
|
121
|
+
/**
|
|
122
|
+
* Detect if a page is a bot-challenge page (Cloudflare, hCaptcha, etc.).
|
|
123
|
+
*
|
|
124
|
+
* Pre-H9 this was over-aggressive: `nodeCount < 50` alone fired on any
|
|
125
|
+
* legitimate small page (404s, simple landings, error pages), and generic
|
|
126
|
+
* phrases like "access denied" / "unknown error" / "permission denied"
|
|
127
|
+
* triggered on real HTTP 4xx/5xx pages, kicking hybrid mode into a costly
|
|
128
|
+
* headed fallback for nothing.
|
|
129
|
+
*
|
|
130
|
+
* H9 split: STRONG_PHRASES are essentially-unambiguous challenge UI and
|
|
131
|
+
* fire regardless of page size; WEAK_PHRASES only fire when the page is
|
|
132
|
+
* ALSO tiny (so a legitimate-looking error page with "access denied" in
|
|
133
|
+
* its body doesn't trip the fallback).
|
|
134
|
+
*
|
|
135
|
+
* @param {object} tree - Nested ARIA tree (from buildTree)
|
|
136
|
+
* @param {number} [nodeCount] - Raw CDP node count (from Accessibility.getFullAXTree)
|
|
137
|
+
*/
|
|
138
|
+
export function isChallengePage(tree: object, nodeCount?: number): boolean;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Click an element by its backendDOMNodeId.
|
|
3
|
+
* Scrolls into view, resolves coordinates, then dispatches mousePressed + mouseReleased.
|
|
4
|
+
*
|
|
5
|
+
* @param {object} session - Session-scoped CDP handle
|
|
6
|
+
* @param {number} backendNodeId - Backend DOM node ID
|
|
7
|
+
*/
|
|
8
|
+
export function click(session: object, backendNodeId: number): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Type text into an element by its backendDOMNodeId.
|
|
11
|
+
* Default: DOM.focus + Input.insertText (fast, no key events).
|
|
12
|
+
* With { keyEvents: true }: dispatches keyDown/keyUp per character (triggers handlers).
|
|
13
|
+
* With { clear: true }: selects all existing text and deletes it before typing.
|
|
14
|
+
*
|
|
15
|
+
* @param {object} session - Session-scoped CDP handle
|
|
16
|
+
* @param {number} backendNodeId - Backend DOM node ID
|
|
17
|
+
* @param {string} text - Text to type
|
|
18
|
+
* @param {object} [opts]
|
|
19
|
+
* @param {boolean} [opts.keyEvents=false] - Use char-by-char key events
|
|
20
|
+
* @param {boolean} [opts.clear=false] - Clear existing content before typing
|
|
21
|
+
*/
|
|
22
|
+
export function type(session: object, backendNodeId: number, text: string, opts?: {
|
|
23
|
+
keyEvents?: boolean | undefined;
|
|
24
|
+
clear?: boolean | undefined;
|
|
25
|
+
}): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Press a special key (Enter, Tab, Escape, etc.).
|
|
28
|
+
* Dispatches keyDown + keyUp for the named key.
|
|
29
|
+
*
|
|
30
|
+
* @param {object} session - Session-scoped CDP handle
|
|
31
|
+
* @param {string} key - Key name (e.g. 'Enter', 'Tab', 'Escape', 'ArrowDown')
|
|
32
|
+
*/
|
|
33
|
+
export function press(session: object, key: string): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Scroll the page via mouseWheel event.
|
|
36
|
+
* Dispatches at viewport center by default, or at given coordinates.
|
|
37
|
+
*
|
|
38
|
+
* @param {object} session - Session-scoped CDP handle
|
|
39
|
+
* @param {number} deltaY - Pixels to scroll (positive = down, negative = up)
|
|
40
|
+
* @param {number} [x=400] - X coordinate for scroll event
|
|
41
|
+
* @param {number} [y=300] - Y coordinate for scroll event
|
|
42
|
+
*/
|
|
43
|
+
export function scroll(session: object, deltaY: number, x?: number, y?: number): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Hover over an element by its backendDOMNodeId.
|
|
46
|
+
* Scrolls into view, then dispatches mouseMoved at center.
|
|
47
|
+
*
|
|
48
|
+
* @param {object} session - Session-scoped CDP handle
|
|
49
|
+
* @param {number} backendNodeId - Backend DOM node ID
|
|
50
|
+
*/
|
|
51
|
+
export function hover(session: object, backendNodeId: number): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Select a value in a <select> element or custom dropdown.
|
|
54
|
+
*
|
|
55
|
+
* Strategy 1: Native <select> — set .value + dispatch 'change' event.
|
|
56
|
+
* Strategy 2: Custom dropdown — click to open, find matching option, click it.
|
|
57
|
+
*
|
|
58
|
+
* @param {object} session - Session-scoped CDP handle
|
|
59
|
+
* @param {number} backendNodeId - Backend DOM node ID of the select/combobox
|
|
60
|
+
* @param {string} value - Value or visible text to select
|
|
61
|
+
*/
|
|
62
|
+
export function select(session: object, backendNodeId: number, value: string): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Drag one element to another.
|
|
65
|
+
* Scrolls source into view, mouse down, move to target center, mouse up.
|
|
66
|
+
*
|
|
67
|
+
* @param {object} session - Session-scoped CDP handle
|
|
68
|
+
* @param {number} fromNodeId - Source element backendDOMNodeId
|
|
69
|
+
* @param {number} toNodeId - Target element backendDOMNodeId
|
|
70
|
+
*/
|
|
71
|
+
export function drag(session: object, fromNodeId: number, toNodeId: number): Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* Upload files to a file input element.
|
|
74
|
+
*
|
|
75
|
+
* @param {object} session - Session-scoped CDP handle
|
|
76
|
+
* @param {number} backendNodeId - Backend DOM node ID of the file input
|
|
77
|
+
* @param {string[]} files - Absolute paths to files to upload
|
|
78
|
+
*/
|
|
79
|
+
export function upload(session: object, backendNodeId: number, files: string[]): Promise<void>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* network-idle.js — wait until the page's network has been idle for N ms.
|
|
3
|
+
*
|
|
4
|
+
* Tracks in-flight requests by requestId in a Set, so an orphan
|
|
5
|
+
* loadingFinished/Failed (event for a request whose requestWillBeSent
|
|
6
|
+
* arrived before our listener attached) is a harmless no-op instead of
|
|
7
|
+
* driving a counter negative and resolving prematurely.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* @param {object} session - CDP session-scoped handle with .on() returning unsub
|
|
11
|
+
* @param {object} [opts]
|
|
12
|
+
* @param {number} [opts.timeout=30000] - Max wait time before reject
|
|
13
|
+
* @param {number} [opts.idle=500] - Required idle duration before resolve
|
|
14
|
+
* @returns {Promise<void>}
|
|
15
|
+
*/
|
|
16
|
+
export function waitForNetworkIdle(session: object, opts?: {
|
|
17
|
+
timeout?: number | undefined;
|
|
18
|
+
idle?: number | undefined;
|
|
19
|
+
}): Promise<void>;
|
package/types/prune.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prune an ARIA tree for agent consumption.
|
|
3
|
+
*
|
|
4
|
+
* @param {object} tree - Root node from buildTree() (CDP format)
|
|
5
|
+
* @param {object} [options]
|
|
6
|
+
* @param {'act'|'browse'|'navigate'|'full'|'read'} [options.mode='act'] - Pruning mode ('read' is an alias for 'browse')
|
|
7
|
+
* @param {string} [options.context=''] - Search context for relevance filtering
|
|
8
|
+
* @returns {object|null} Pruned tree
|
|
9
|
+
*/
|
|
10
|
+
export function prune(tree: object, options?: {
|
|
11
|
+
mode?: "act" | "browse" | "navigate" | "full" | "read" | undefined;
|
|
12
|
+
context?: string | undefined;
|
|
13
|
+
}): object | null;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read session.json from the output directory.
|
|
3
|
+
* @returns {{ port: number, pid: number, token?: string, startedAt: string } | null}
|
|
4
|
+
*/
|
|
5
|
+
export function readSession(outputDir: any): {
|
|
6
|
+
port: number;
|
|
7
|
+
pid: number;
|
|
8
|
+
token?: string;
|
|
9
|
+
startedAt: string;
|
|
10
|
+
} | null;
|
|
11
|
+
/**
|
|
12
|
+
* Check if the daemon is alive by hitting GET /status.
|
|
13
|
+
*/
|
|
14
|
+
export function isAlive(outputDir: any): Promise<boolean>;
|
|
15
|
+
/**
|
|
16
|
+
* Send a command to the running daemon.
|
|
17
|
+
* @returns {Promise<object>} The daemon's response
|
|
18
|
+
*/
|
|
19
|
+
export function sendCommand(command: any, args: any, outputDir: any): Promise<object>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Apply stealth patches to a CDP session.
|
|
3
|
+
* Must be called before any navigation.
|
|
4
|
+
*
|
|
5
|
+
* Splits into two layers:
|
|
6
|
+
* 1. Network.setUserAgentOverride strips "HeadlessChrome" from the UA
|
|
7
|
+
* that ships in HTTP request headers AND that navigator.userAgent
|
|
8
|
+
* reports — `--headless=new` leaves "HeadlessChrome" in there.
|
|
9
|
+
* 2. Page.addScriptToEvaluateOnNewDocument injects the JS-level patches
|
|
10
|
+
* before any page script runs.
|
|
11
|
+
*
|
|
12
|
+
* @param {object} session - Session-scoped CDP handle
|
|
13
|
+
*/
|
|
14
|
+
export function applyStealth(session: object): Promise<void>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Throw if `url` is unsafe to navigate to under the given policy.
|
|
3
|
+
* @param {string} url
|
|
4
|
+
* @param {object} [opts]
|
|
5
|
+
* @param {boolean} [opts.allowLocalUrls=false] - permit file:/chrome:/etc.
|
|
6
|
+
* @param {boolean} [opts.blockPrivateNetwork=false] - reject loopback/RFC-1918/metadata.
|
|
7
|
+
*/
|
|
8
|
+
export function assertNavigable(url: string, opts?: {
|
|
9
|
+
allowLocalUrls?: boolean | undefined;
|
|
10
|
+
blockPrivateNetwork?: boolean | undefined;
|
|
11
|
+
}): void;
|
|
12
|
+
/**
|
|
13
|
+
* Throw if any file in `files` resolves outside `uploadDir`. Both the base
|
|
14
|
+
* dir and each file are resolved through realpath, so symlinks (in either the
|
|
15
|
+
* base path — e.g. macOS /tmp → /private/tmp — or the file) can't be used to
|
|
16
|
+
* escape the sandbox or to false-reject a legitimate file.
|
|
17
|
+
* No-op when `uploadDir` is falsy (no restriction configured).
|
|
18
|
+
* @param {string|string[]} files
|
|
19
|
+
* @param {string|null} uploadDir
|
|
20
|
+
*/
|
|
21
|
+
export function assertUploadAllowed(files: string | string[], uploadDir: string | null): void;
|
|
22
|
+
/**
|
|
23
|
+
* @param {string} host - hostname (no brackets for IPv6)
|
|
24
|
+
* @returns {boolean} true if it names a private/loopback/link-local/internal host
|
|
25
|
+
*/
|
|
26
|
+
export function isPrivateHost(host: string): boolean;
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
name: Publish to npm
|
|
2
|
-
|
|
3
|
-
# Trusted publishing via OIDC — no NPM_TOKEN needed.
|
|
4
|
-
# Configure the trusted publisher at npmjs.com first (see repo notes).
|
|
5
|
-
on:
|
|
6
|
-
workflow_dispatch: # manual "Run workflow" button
|
|
7
|
-
release:
|
|
8
|
-
types: [published] # also publishes when you cut a GitHub release
|
|
9
|
-
|
|
10
|
-
permissions:
|
|
11
|
-
contents: read
|
|
12
|
-
id-token: write # required: lets npm mint OIDC credentials
|
|
13
|
-
|
|
14
|
-
jobs:
|
|
15
|
-
publish:
|
|
16
|
-
runs-on: ubuntu-latest
|
|
17
|
-
steps:
|
|
18
|
-
- uses: actions/checkout@v4
|
|
19
|
-
- uses: actions/setup-node@v4
|
|
20
|
-
with:
|
|
21
|
-
node-version: 22
|
|
22
|
-
registry-url: 'https://registry.npmjs.org'
|
|
23
|
-
- name: Upgrade npm (trusted publishing needs >= 11.5.1)
|
|
24
|
-
run: npm install -g npm@latest
|
|
25
|
-
- name: Publish
|
|
26
|
-
run: npm publish
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: barebrowse
|
|
3
|
-
description: Browser automation using the user's real browser with real cookies. Handles consent walls, login sessions, and bot detection automatically.
|
|
4
|
-
allowed-tools: Bash(barebrowse:*)
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# barebrowse CLI — Browser Automation for Agents
|
|
8
|
-
|
|
9
|
-
Browse any URL using the user's real browser with real cookies. Returns pruned ARIA snapshots (40-90% smaller than raw) with `[ref=N]` markers for interaction. Handles cookie consent, login sessions, JS dialogs, and bot detection automatically.
|
|
10
|
-
|
|
11
|
-
## Quick Start
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
barebrowse open https://example.com # Start session + navigate
|
|
15
|
-
barebrowse snapshot # Get ARIA snapshot → .barebrowse/page-*.yml
|
|
16
|
-
barebrowse click 8 # Click element with ref=8
|
|
17
|
-
barebrowse snapshot # See result
|
|
18
|
-
barebrowse close # End session
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
All output files go to `.barebrowse/` in the current directory. Read them with the Read tool when needed.
|
|
22
|
-
|
|
23
|
-
## Commands
|
|
24
|
-
|
|
25
|
-
### Session Lifecycle
|
|
26
|
-
|
|
27
|
-
| Command | Description |
|
|
28
|
-
|---------|-------------|
|
|
29
|
-
| `barebrowse open [url] [flags]` | Start browser session. Optionally navigate to URL. |
|
|
30
|
-
| `barebrowse close` | Close session and kill browser. |
|
|
31
|
-
| `barebrowse status` | Check if session is running. |
|
|
32
|
-
|
|
33
|
-
**Open flags:**
|
|
34
|
-
- `--mode=headless|headed|hybrid` — Browser mode (default: headless)
|
|
35
|
-
- `--no-cookies` — Skip cookie injection
|
|
36
|
-
- `--browser=firefox|chromium` — Cookie source
|
|
37
|
-
- `--prune-mode=act|read` — Default pruning mode
|
|
38
|
-
- `--timeout=N` — Navigation timeout in ms
|
|
39
|
-
- `--proxy=URL` — HTTP/SOCKS proxy server
|
|
40
|
-
- `--viewport=WxH` — Viewport size (e.g. 1280x720)
|
|
41
|
-
- `--storage-state=FILE` — Load cookies/localStorage from JSON file
|
|
42
|
-
- `--block-private-network` — SSRF guard: refuse loopback / RFC-1918 / link-local / cloud-metadata hosts (v0.11.0)
|
|
43
|
-
- `--upload-dir=DIR` — Sandbox uploads to DIR; reject files outside it (v0.11.0)
|
|
44
|
-
|
|
45
|
-
> Security (v0.11.0): `file:`/`chrome:`/etc. navigation is blocked by default, and the daemon requires a per-session token (handled transparently by the CLI). Snapshots and saved state are written owner-only (`0600`).
|
|
46
|
-
|
|
47
|
-
### Navigation
|
|
48
|
-
|
|
49
|
-
| Command | Output |
|
|
50
|
-
|---------|--------|
|
|
51
|
-
| `barebrowse goto <url>` | Navigates, waits for load, dismisses consent. Prints "ok". |
|
|
52
|
-
| `barebrowse back` | Go back in browser history. |
|
|
53
|
-
| `barebrowse forward` | Go forward in browser history. |
|
|
54
|
-
| `barebrowse snapshot` | ARIA snapshot → `.barebrowse/page-<timestamp>.yml` |
|
|
55
|
-
| `barebrowse snapshot --mode=read` | Read mode: keeps all text (for content extraction) |
|
|
56
|
-
| `barebrowse screenshot` | Screenshot → `.barebrowse/screenshot-<timestamp>.png` |
|
|
57
|
-
| `barebrowse pdf [--landscape]` | PDF export → `.barebrowse/page-<timestamp>.pdf` |
|
|
58
|
-
|
|
59
|
-
### Interaction
|
|
60
|
-
|
|
61
|
-
| Command | Description |
|
|
62
|
-
|---------|-------------|
|
|
63
|
-
| `barebrowse click <ref>` | Click element (scrolls into view first) |
|
|
64
|
-
| `barebrowse type <ref> <text>` | Type text into element |
|
|
65
|
-
| `barebrowse fill <ref> <text>` | Clear existing content + type new text |
|
|
66
|
-
| `barebrowse press <key>` | Press key: Enter, Tab, Escape, Backspace, Delete, arrows, Space |
|
|
67
|
-
| `barebrowse scroll <deltaY>` | Scroll page (positive=down, negative=up) |
|
|
68
|
-
| `barebrowse hover <ref>` | Hover over element (triggers tooltips) |
|
|
69
|
-
| `barebrowse select <ref> <value>` | Select dropdown option |
|
|
70
|
-
| `barebrowse drag <fromRef> <toRef>` | Drag element to another element |
|
|
71
|
-
| `barebrowse upload <ref> <files..>` | Upload file(s) to a file input element |
|
|
72
|
-
|
|
73
|
-
### Tabs
|
|
74
|
-
|
|
75
|
-
| Command | Description |
|
|
76
|
-
|---------|-------------|
|
|
77
|
-
| `barebrowse tabs` | List open tabs (index, url, title) |
|
|
78
|
-
| `barebrowse tab <index>` | Switch to tab by index |
|
|
79
|
-
|
|
80
|
-
### Debugging
|
|
81
|
-
|
|
82
|
-
| Command | Output |
|
|
83
|
-
|---------|--------|
|
|
84
|
-
| `barebrowse eval <expression>` | Evaluate JS in page, print result |
|
|
85
|
-
| `barebrowse wait-idle` | Wait for network idle (no requests for 500ms) |
|
|
86
|
-
| `barebrowse wait-for [opts]` | Wait for content to appear on page |
|
|
87
|
-
| `barebrowse console-logs` | Console logs → `.barebrowse/console-<timestamp>.json` |
|
|
88
|
-
| `barebrowse network-log` | Network log → `.barebrowse/network-<timestamp>.json` |
|
|
89
|
-
| `barebrowse network-log --failed` | Only failed/4xx/5xx requests |
|
|
90
|
-
| `barebrowse dialog-log` | JS dialog log → `.barebrowse/dialogs-<timestamp>.json` |
|
|
91
|
-
| `barebrowse save-state` | Cookies + localStorage → `.barebrowse/state-<timestamp>.json` |
|
|
92
|
-
|
|
93
|
-
**wait-for flags:**
|
|
94
|
-
- `--text=STRING` — Wait for text to appear in page body
|
|
95
|
-
- `--selector=CSS` — Wait for CSS selector to match
|
|
96
|
-
- `--timeout=N` — Max wait time in ms (default: 30000)
|
|
97
|
-
|
|
98
|
-
## Snapshot Format
|
|
99
|
-
|
|
100
|
-
The snapshot is a YAML-like ARIA tree. Each line is one node:
|
|
101
|
-
|
|
102
|
-
```
|
|
103
|
-
# https://example.com/
|
|
104
|
-
# 379 chars → 45 chars (88% pruned)
|
|
105
|
-
- heading "Example Domain" [level=1] [ref=3]
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
- `[ref=N]` — Use this number with click, type, fill, hover, select, drag, upload
|
|
109
|
-
- Refs change on every snapshot — always take a fresh snapshot before interacting
|
|
110
|
-
- **act mode** (default): interactive elements + labels — for clicking, typing, navigating
|
|
111
|
-
- **read mode**: all text content — for reading articles, extracting data
|
|
112
|
-
|
|
113
|
-
## Workflow Pattern
|
|
114
|
-
|
|
115
|
-
1. `barebrowse open <url>` — start session
|
|
116
|
-
2. `barebrowse snapshot` — observe page (read the .yml file)
|
|
117
|
-
3. Decide action based on snapshot content
|
|
118
|
-
4. `barebrowse click/type/fill/press/scroll/drag/upload <ref>` — act
|
|
119
|
-
5. `barebrowse snapshot` — observe result (refs are now different!)
|
|
120
|
-
6. Repeat 3-5 until goal achieved
|
|
121
|
-
7. `barebrowse close` — clean up
|
|
122
|
-
|
|
123
|
-
## Tips
|
|
124
|
-
|
|
125
|
-
- **Always snapshot before interacting** — refs are ephemeral and change every time
|
|
126
|
-
- **Use `fill` instead of `type`** when replacing existing text in input fields
|
|
127
|
-
- **Use `--mode=read`** for snapshot when you need to extract article content or data
|
|
128
|
-
- **Use `back`/`forward`** to navigate browser history instead of re-entering URLs
|
|
129
|
-
- **Use `upload`** for file inputs — pass absolute paths to the files
|
|
130
|
-
- **Use `wait-for`** when content loads asynchronously — more reliable than `wait-idle`
|
|
131
|
-
- **Check `dialog-log`** if JS alerts/confirms were auto-dismissed during your session
|
|
132
|
-
- **Use `save-state`** to persist cookies/localStorage for later sessions via `--storage-state`
|
|
133
|
-
- **Check `console-logs`** when page behavior seems wrong — JS errors show up there
|
|
134
|
-
- **Check `network-log --failed`** to debug missing content or broken API calls
|
|
135
|
-
- **Use `eval`** as an escape hatch when ARIA tree doesn't show what you need
|
|
136
|
-
- **One session per project** — `.barebrowse/` is project-scoped
|
|
137
|
-
- For bot-detected sites, use `--mode=headed` (requires browser with `--remote-debugging-port=9222`)
|
package/commands/barebrowse.md
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: barebrowse
|
|
3
|
-
description: Browser automation using the user's real browser with real cookies. Handles consent walls, login sessions, and bot detection automatically.
|
|
4
|
-
allowed-tools: Bash(barebrowse:*)
|
|
5
|
-
---
|
|
6
|
-
# barebrowse CLI — Browser Automation for Agents
|
|
7
|
-
|
|
8
|
-
Browse any URL using the user's real browser with real cookies. Returns pruned ARIA snapshots (40-90% smaller than raw) with `[ref=N]` markers for interaction. Handles cookie consent, login sessions, JS dialogs, and bot detection automatically.
|
|
9
|
-
|
|
10
|
-
## Quick Start
|
|
11
|
-
|
|
12
|
-
```bash
|
|
13
|
-
barebrowse open https://example.com # Start session + navigate
|
|
14
|
-
barebrowse snapshot # Get ARIA snapshot → .barebrowse/page-*.yml
|
|
15
|
-
barebrowse click 8 # Click element with ref=8
|
|
16
|
-
barebrowse snapshot # See result
|
|
17
|
-
barebrowse close # End session
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
All output files go to `.barebrowse/` in the current directory. Read them with the Read tool when needed.
|
|
21
|
-
|
|
22
|
-
## Commands
|
|
23
|
-
|
|
24
|
-
### Session Lifecycle
|
|
25
|
-
|
|
26
|
-
| Command | Description |
|
|
27
|
-
|---------|-------------|
|
|
28
|
-
| `barebrowse open [url] [flags]` | Start browser session. Optionally navigate to URL. |
|
|
29
|
-
| `barebrowse close` | Close session and kill browser. |
|
|
30
|
-
| `barebrowse status` | Check if session is running. |
|
|
31
|
-
|
|
32
|
-
**Open flags:**
|
|
33
|
-
- `--mode=headless|headed|hybrid` — Browser mode (default: headless)
|
|
34
|
-
- `--no-cookies` — Skip cookie injection
|
|
35
|
-
- `--browser=firefox|chromium` — Cookie source
|
|
36
|
-
- `--prune-mode=act|read` — Default pruning mode
|
|
37
|
-
- `--timeout=N` — Navigation timeout in ms
|
|
38
|
-
- `--proxy=URL` — HTTP/SOCKS proxy server
|
|
39
|
-
- `--viewport=WxH` — Viewport size (e.g. 1280x720)
|
|
40
|
-
- `--storage-state=FILE` — Load cookies/localStorage from JSON file
|
|
41
|
-
- `--block-private-network` — SSRF guard: refuse loopback / RFC-1918 / link-local / cloud-metadata hosts (v0.11.0)
|
|
42
|
-
- `--upload-dir=DIR` — Sandbox uploads to DIR; reject files outside it (v0.11.0)
|
|
43
|
-
|
|
44
|
-
> Security (v0.11.0): `file:`/`chrome:`/etc. navigation is blocked by default, and the daemon requires a per-session token (handled transparently by the CLI). Snapshots and saved state are written owner-only (`0600`).
|
|
45
|
-
|
|
46
|
-
### Navigation
|
|
47
|
-
|
|
48
|
-
| Command | Output |
|
|
49
|
-
|---------|--------|
|
|
50
|
-
| `barebrowse goto <url>` | Navigates, waits for load, dismisses consent. Prints "ok". |
|
|
51
|
-
| `barebrowse back` | Go back in browser history. |
|
|
52
|
-
| `barebrowse forward` | Go forward in browser history. |
|
|
53
|
-
| `barebrowse snapshot` | ARIA snapshot → `.barebrowse/page-<timestamp>.yml` |
|
|
54
|
-
| `barebrowse snapshot --mode=read` | Read mode: keeps all text (for content extraction) |
|
|
55
|
-
| `barebrowse screenshot` | Screenshot → `.barebrowse/screenshot-<timestamp>.png` |
|
|
56
|
-
| `barebrowse pdf [--landscape]` | PDF export → `.barebrowse/page-<timestamp>.pdf` |
|
|
57
|
-
|
|
58
|
-
### Interaction
|
|
59
|
-
|
|
60
|
-
| Command | Description |
|
|
61
|
-
|---------|-------------|
|
|
62
|
-
| `barebrowse click <ref>` | Click element (scrolls into view first) |
|
|
63
|
-
| `barebrowse type <ref> <text>` | Type text into element |
|
|
64
|
-
| `barebrowse fill <ref> <text>` | Clear existing content + type new text |
|
|
65
|
-
| `barebrowse press <key>` | Press key: Enter, Tab, Escape, Backspace, Delete, arrows, Space |
|
|
66
|
-
| `barebrowse scroll <deltaY>` | Scroll page (positive=down, negative=up) |
|
|
67
|
-
| `barebrowse hover <ref>` | Hover over element (triggers tooltips) |
|
|
68
|
-
| `barebrowse select <ref> <value>` | Select dropdown option |
|
|
69
|
-
| `barebrowse drag <fromRef> <toRef>` | Drag element to another element |
|
|
70
|
-
| `barebrowse upload <ref> <files..>` | Upload file(s) to a file input element |
|
|
71
|
-
|
|
72
|
-
### Tabs
|
|
73
|
-
|
|
74
|
-
| Command | Description |
|
|
75
|
-
|---------|-------------|
|
|
76
|
-
| `barebrowse tabs` | List open tabs (index, url, title) |
|
|
77
|
-
| `barebrowse tab <index>` | Switch to tab by index |
|
|
78
|
-
|
|
79
|
-
### Debugging
|
|
80
|
-
|
|
81
|
-
| Command | Output |
|
|
82
|
-
|---------|--------|
|
|
83
|
-
| `barebrowse eval <expression>` | Evaluate JS in page, print result |
|
|
84
|
-
| `barebrowse wait-idle` | Wait for network idle (no requests for 500ms) |
|
|
85
|
-
| `barebrowse wait-for [opts]` | Wait for content to appear on page |
|
|
86
|
-
| `barebrowse console-logs` | Console logs → `.barebrowse/console-<timestamp>.json` |
|
|
87
|
-
| `barebrowse network-log` | Network log → `.barebrowse/network-<timestamp>.json` |
|
|
88
|
-
| `barebrowse network-log --failed` | Only failed/4xx/5xx requests |
|
|
89
|
-
| `barebrowse dialog-log` | JS dialog log → `.barebrowse/dialogs-<timestamp>.json` |
|
|
90
|
-
| `barebrowse save-state` | Cookies + localStorage → `.barebrowse/state-<timestamp>.json` |
|
|
91
|
-
|
|
92
|
-
**wait-for flags:**
|
|
93
|
-
- `--text=STRING` — Wait for text to appear in page body
|
|
94
|
-
- `--selector=CSS` — Wait for CSS selector to match
|
|
95
|
-
- `--timeout=N` — Max wait time in ms (default: 30000)
|
|
96
|
-
|
|
97
|
-
## Snapshot Format
|
|
98
|
-
|
|
99
|
-
The snapshot is a YAML-like ARIA tree. Each line is one node:
|
|
100
|
-
|
|
101
|
-
```
|
|
102
|
-
# https://example.com/
|
|
103
|
-
# 379 chars → 45 chars (88% pruned)
|
|
104
|
-
- heading "Example Domain" [level=1] [ref=3]
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
- `[ref=N]` — Use this number with click, type, fill, hover, select, drag, upload
|
|
108
|
-
- Refs change on every snapshot — always take a fresh snapshot before interacting
|
|
109
|
-
- **act mode** (default): interactive elements + labels — for clicking, typing, navigating
|
|
110
|
-
- **read mode**: all text content — for reading articles, extracting data
|
|
111
|
-
|
|
112
|
-
## Workflow Pattern
|
|
113
|
-
|
|
114
|
-
1. `barebrowse open <url>` — start session
|
|
115
|
-
2. `barebrowse snapshot` — observe page (read the .yml file)
|
|
116
|
-
3. Decide action based on snapshot content
|
|
117
|
-
4. `barebrowse click/type/fill/press/scroll/drag/upload <ref>` — act
|
|
118
|
-
5. `barebrowse snapshot` — observe result (refs are now different!)
|
|
119
|
-
6. Repeat 3-5 until goal achieved
|
|
120
|
-
7. `barebrowse close` — clean up
|
|
121
|
-
|
|
122
|
-
## Tips
|
|
123
|
-
|
|
124
|
-
- **Always snapshot before interacting** — refs are ephemeral and change every time
|
|
125
|
-
- **Use `fill` instead of `type`** when replacing existing text in input fields
|
|
126
|
-
- **Use `--mode=read`** for snapshot when you need to extract article content or data
|
|
127
|
-
- **Use `back`/`forward`** to navigate browser history instead of re-entering URLs
|
|
128
|
-
- **Use `upload`** for file inputs — pass absolute paths to the files
|
|
129
|
-
- **Use `wait-for`** when content loads asynchronously — more reliable than `wait-idle`
|
|
130
|
-
- **Check `dialog-log`** if JS alerts/confirms were auto-dismissed during your session
|
|
131
|
-
- **Use `save-state`** to persist cookies/localStorage for later sessions via `--storage-state`
|
|
132
|
-
- **Check `console-logs`** when page behavior seems wrong — JS errors show up there
|
|
133
|
-
- **Check `network-log --failed`** to debug missing content or broken API calls
|
|
134
|
-
- **Use `eval`** as an escape hatch when ARIA tree doesn't show what you need
|
|
135
|
-
- **One session per project** — `.barebrowse/` is project-scoped
|
|
136
|
-
- For bot-detected sites, use `--mode=headed` (requires browser with `--remote-debugging-port=9222`)
|