bwb-browser 4.0.0 → 4.0.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/AGENTS.md +12 -0
- package/README.md +25 -21
- package/lib/browser.mjs +44 -2
- package/package.json +1 -1
- package/server.mjs +17 -4
package/AGENTS.md
CHANGED
|
@@ -226,6 +226,18 @@ BWB_CHROME_PATH=/path/to/chrome bwb
|
|
|
226
226
|
bwb --browser-path /path/to/chrome
|
|
227
227
|
```
|
|
228
228
|
|
|
229
|
+
### Attach Mode (drive the window you're looking at)
|
|
230
|
+
```bash
|
|
231
|
+
# Launch your browser with remote debugging first, e.g.:
|
|
232
|
+
# brave --remote-debugging-port=9222
|
|
233
|
+
BWB_ATTACH_PORT=9222 bwb
|
|
234
|
+
# or
|
|
235
|
+
bwb --attach-port 9222
|
|
236
|
+
```
|
|
237
|
+
Guest mode: no spawn, no kill, no journal restore. `browser_newTab` opens a
|
|
238
|
+
VISIBLE tab in your window. Auto-shed is disabled (those are your real tabs) —
|
|
239
|
+
pressure is reported, you close them. `browser_status` shows `attached: true`.
|
|
240
|
+
|
|
229
241
|
---
|
|
230
242
|
|
|
231
243
|
## License
|
package/README.md
CHANGED
|
@@ -90,7 +90,7 @@ Create tabs, close them, switch between them, save cookies, load them back. Like
|
|
|
90
90
|
|
|
91
91
|
Normalizes `navigator.webdriver`, plugins, languages, and user-agent for testing environments. Not "stealth mode" — just honest fingerprint normalization so your tests actually match real user conditions.
|
|
92
92
|
|
|
93
|
-
### 7. Element Screenshots
|
|
93
|
+
### 7. Element Screenshots
|
|
94
94
|
|
|
95
95
|
Capture just one element — a login form, a chart, a product card — not the whole page:
|
|
96
96
|
|
|
@@ -105,20 +105,24 @@ Every screenshot is saved to disk (Android: `/storage/emulated/0/Download/bwb-sc
|
|
|
105
105
|
|
|
106
106
|
---
|
|
107
107
|
|
|
108
|
-
## What's New in
|
|
108
|
+
## What's New in 4.0.0 — "Lightweight Like Air"
|
|
109
109
|
|
|
110
|
-
###
|
|
111
|
-
- **`
|
|
112
|
-
- **
|
|
110
|
+
### Static-first fetch ladder
|
|
111
|
+
- **`browser_goto` no longer spawns Chromium for plain pages** — fetch + extract in milliseconds (`mode: "static"`). JS pages escalate automatically (`mode: "browser"` + reason). Dead URLs error without spawning anything.
|
|
112
|
+
- **On-demand capabilities** — `browser_download` / `browser_export` ship as verbs, not weight. Missing backends prompt for consent install. Nothing heavy is ever bundled.
|
|
113
113
|
|
|
114
|
-
###
|
|
115
|
-
-
|
|
116
|
-
-
|
|
117
|
-
- **`killOrphanedChrome` safety** — graceful SIGTERM→SIGKILL, now scoped to bwb's own profile so it never kills another agent's browser
|
|
118
|
-
- **`browser_restart` hygiene** — watch listeners can't outlive the dying protocol
|
|
119
|
-
- **`browser_status` accuracy** — uses the real bound port, no more hardcoded 9222 poke
|
|
114
|
+
### Vigilance system
|
|
115
|
+
- **Every tool response carries a `[bwb resources]` footer** — MCP + Chromium MB, tabs, ok/watch/critical. `browser_watch` streams memory samples on its existing poll rhythm.
|
|
116
|
+
- **Thresholds act, then report** — critical pressure hibernates tabs, tears down at one tab, journals everything. The agent reads about the save, never discovers the OOM.
|
|
120
117
|
|
|
121
|
-
|
|
118
|
+
### Survival profile
|
|
119
|
+
- **`--lean` auto-enables on Termux** — capped renderers, silenced background services, 3-tab cap, 5-minute mayfly teardown, 256MB JS heap. `--nuclear` opts into `--single-process`.
|
|
120
|
+
- **Tab journal + lazy restore** — kills become resume points, not disasters. `bwb --setup` prints a survival guide.
|
|
121
|
+
|
|
122
|
+
### Breaking
|
|
123
|
+
- `browser_title` + `browser_url` folded into `browser_status.targets`. Still 26 tools — that's now a release gate.
|
|
124
|
+
|
|
125
|
+
*Full story in the [changelog](./CHANGELOG.md). Older releases documented there too.*
|
|
122
126
|
|
|
123
127
|
---
|
|
124
128
|
|
|
@@ -127,7 +131,7 @@ Every screenshot is saved to disk (Android: `/storage/emulated/0/Download/bwb-sc
|
|
|
127
131
|
```bash
|
|
128
132
|
npm install -g bwb-browser
|
|
129
133
|
bwb --version
|
|
130
|
-
# → bwb-browser
|
|
134
|
+
# → bwb-browser 4.0.0
|
|
131
135
|
```
|
|
132
136
|
|
|
133
137
|
Done. If you have Chrome/Chromium anywhere on your system, bwb finds it. No config files. No environment variables. Just works.
|
|
@@ -151,12 +155,10 @@ bwb
|
|
|
151
155
|
| **`browser_watch`** | 🔥 Live event capture — console, network, errors, navigation |
|
|
152
156
|
| **`browser_diagnose`** | 🔥 Full page health check — perf, errors, broken images, score |
|
|
153
157
|
| **`browser_fingerprint`** | 🔥 Realistic browser profile for testing |
|
|
154
|
-
| `browser_goto` | Navigate to
|
|
158
|
+
| `browser_goto` | Navigate — static-first, escalates to browser with reason |
|
|
155
159
|
| `browser_screenshot` | Take a screenshot — whole page, viewport, or a single element via `selector` |
|
|
156
160
|
| `browser_html` | Get page/selector HTML |
|
|
157
161
|
| `browser_text` | Get page/selector text |
|
|
158
|
-
| `browser_title` | Get page title |
|
|
159
|
-
| `browser_url` | Get current URL |
|
|
160
162
|
| `browser_back` | Go back in history |
|
|
161
163
|
| `browser_click` | Click an element (native CDP) |
|
|
162
164
|
| `browser_fill` | Fill an input field (native CDP) |
|
|
@@ -171,7 +173,9 @@ bwb
|
|
|
171
173
|
| `browser_saveCookies` | Save session to disk |
|
|
172
174
|
| `browser_loadCookies` | Load session from disk |
|
|
173
175
|
| `browser_listSessions` | List saved sessions |
|
|
174
|
-
| `
|
|
176
|
+
| `browser_download` | Download media (needs system yt-dlp, consent-gated) |
|
|
177
|
+
| `browser_export` | Export md/txt/html (pdf/docx/pptx need pip libs, consent-gated) |
|
|
178
|
+
| `browser_status` | Status + live resources + active profile |
|
|
175
179
|
| `browser_restart` | Restart the browser |
|
|
176
180
|
|
|
177
181
|
---
|
|
@@ -212,13 +216,13 @@ See [AGENTS.md](./AGENTS.md) for copy-paste configs for each one.
|
|
|
212
216
|
|
|
213
217
|
## The Backstory
|
|
214
218
|
|
|
215
|
-
|
|
219
|
+
Every browser automation tool assumes you have 400MB to spare and a desktop-class machine. That assumption excludes phones, cheap VPS boxes, Raspberry Pis, and CI runners — most of the world's computers.
|
|
216
220
|
|
|
217
|
-
|
|
221
|
+
bwb is engineered against the hardest constraint first: **a memory-pressured device where every megabyte is contested.** No bundled browser. No wrapper frameworks. Just raw CDP — the protocol Chrome speaks natively — plus a static-fetch ladder so Chromium only starts when JavaScript demands it. Mobile-first isn't a feature here. It's the design spec everything else has to survive.
|
|
218
222
|
|
|
219
|
-
The result is
|
|
223
|
+
The result is ~136KB of source that does what 400MB of dependencies do. Not better code — less code, held to budgets: 26 tools max, 60 kB tarball max, zero native modules. Constraints are features.
|
|
220
224
|
|
|
221
|
-
*— Krish Tiwari ([@krshforever](https://github.com/krshforever))
|
|
225
|
+
*— Krish Tiwari ([@krshforever](https://github.com/krshforever))*
|
|
222
226
|
|
|
223
227
|
---
|
|
224
228
|
|
package/lib/browser.mjs
CHANGED
|
@@ -35,8 +35,15 @@ export const cfg = {
|
|
|
35
35
|
nuclear: false, // --single-process: max RAM saving, min stability. Opt-in only.
|
|
36
36
|
idleMs: null, // null = auto (5min lean, off desktop); 0 = never teardown
|
|
37
37
|
tabMax: null, // null = auto (3 lean, unlimited desktop); 0 = unlimited
|
|
38
|
+
attachPort: 0, // 0 = off (spawn own browser). N = attach to existing
|
|
39
|
+
// browser's CDP port (e.g. 9222) — never spawns, never kills.
|
|
38
40
|
};
|
|
39
41
|
|
|
42
|
+
// True when connected to a foreign (user-owned) browser via --attach-port.
|
|
43
|
+
// Attached browsers are never spawned, killed, or journal-restored —
|
|
44
|
+
// bwb is a guest there. Tab hibernation still works but closes REAL tabs.
|
|
45
|
+
export let attached = false;
|
|
46
|
+
|
|
40
47
|
// ─── Environment ─────────────────────────────────────────────────────────────
|
|
41
48
|
|
|
42
49
|
export function isTermux() {
|
|
@@ -119,8 +126,11 @@ export function findBrowserPath(cliPath) {
|
|
|
119
126
|
"chromium-browser",
|
|
120
127
|
"chromium",
|
|
121
128
|
"google-chrome-stable",
|
|
129
|
+
"brave-browser",
|
|
130
|
+
"brave",
|
|
122
131
|
"/usr/bin/google-chrome",
|
|
123
132
|
"/usr/bin/chromium-browser",
|
|
133
|
+
"/usr/bin/brave-browser",
|
|
124
134
|
"/snap/bin/chromium",
|
|
125
135
|
],
|
|
126
136
|
darwin: [
|
|
@@ -179,6 +189,34 @@ export async function ensureBrowser() {
|
|
|
179
189
|
browserStartup = new Promise((res, rej) => { startResolve = res; startReject = rej; });
|
|
180
190
|
browserStartup.catch(() => { browserStartup = null; });
|
|
181
191
|
|
|
192
|
+
// ─── Attach mode: guest on someone else's browser ──────────────────────
|
|
193
|
+
// No spawn, no orphan-kill, no journal restore (never navigate a user's
|
|
194
|
+
// tabs on connect). ensureDefaultTab() registers whatever is already open.
|
|
195
|
+
if (cfg.attachPort) {
|
|
196
|
+
(async () => {
|
|
197
|
+
try {
|
|
198
|
+
const port = cfg.attachPort;
|
|
199
|
+
const cdp = await CDP({ port });
|
|
200
|
+
protocol = cdp;
|
|
201
|
+
browser = null;
|
|
202
|
+
attached = true;
|
|
203
|
+
actualCdpPort = port;
|
|
204
|
+
startResolve(cdp);
|
|
205
|
+
pokeActivity();
|
|
206
|
+
} catch (err) {
|
|
207
|
+
browserStartup = null;
|
|
208
|
+
browserExited = true;
|
|
209
|
+
attached = false;
|
|
210
|
+
startReject(new Error(
|
|
211
|
+
`Attach failed: no browser listening on CDP port ${cfg.attachPort} (${err.message}). ` +
|
|
212
|
+
`Launch one with --remote-debugging-port=${cfg.attachPort} first.`
|
|
213
|
+
));
|
|
214
|
+
}
|
|
215
|
+
})();
|
|
216
|
+
return browserStartup;
|
|
217
|
+
}
|
|
218
|
+
attached = false;
|
|
219
|
+
|
|
182
220
|
(async () => {
|
|
183
221
|
try {
|
|
184
222
|
const browserPath = assertBrowserExists(cfg.browserPath);
|
|
@@ -297,7 +335,8 @@ export async function stopBrowser(reason = "manual") {
|
|
|
297
335
|
try { await protocol.close(); } catch {}
|
|
298
336
|
protocol = null;
|
|
299
337
|
}
|
|
300
|
-
|
|
338
|
+
// Attached browsers are foreign — disconnect only, never kill.
|
|
339
|
+
if (browser && !attached) {
|
|
301
340
|
try { browser.kill("SIGTERM"); } catch {}
|
|
302
341
|
await new Promise(r => setTimeout(r, 1500));
|
|
303
342
|
try { browser.kill("SIGKILL"); } catch {}
|
|
@@ -306,6 +345,7 @@ export async function stopBrowser(reason = "manual") {
|
|
|
306
345
|
} catch {}
|
|
307
346
|
browserExited = true; // next ensureBrowser() respawns fresh + restores journal
|
|
308
347
|
actualCdpPort = null;
|
|
348
|
+
attached = false;
|
|
309
349
|
browserStartup = null;
|
|
310
350
|
cleaningUp = false;
|
|
311
351
|
try {
|
|
@@ -326,7 +366,9 @@ export async function restartBrowser() {
|
|
|
326
366
|
try { await protocol.close(); } catch {}
|
|
327
367
|
protocol = null;
|
|
328
368
|
}
|
|
329
|
-
|
|
369
|
+
// Attached browsers are foreign — disconnect only, never kill.
|
|
370
|
+
// Next ensureBrowser() re-attaches (no journal restore in attach mode).
|
|
371
|
+
if (browser && !attached) {
|
|
330
372
|
browser.kill("SIGTERM");
|
|
331
373
|
await new Promise(r => setTimeout(r, 2000));
|
|
332
374
|
try { browser.kill("SIGKILL"); } catch {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bwb-browser",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.1",
|
|
4
4
|
"description": "Browser Without Bloat — lightweight MCP server that gives AI agents browser superpowers. Static-first fetch ladder (Chromium only when JS demands it), lean profiles for Termux/1GB VPS, resource vigilance, tab journaling. Runs anywhere. Stays lean like air.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"bwb": "bin/bwb"
|
package/server.mjs
CHANGED
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
* (default: 5min lean, off desktop)
|
|
22
22
|
* --tab-max / BWB_TAB_MAX — Live-tab cap, oldest hibernated
|
|
23
23
|
* (default: 3 lean, unlimited desktop)
|
|
24
|
+
* --attach-port / BWB_ATTACH_PORT — Attach to an already-running
|
|
25
|
+
* browser's CDP port (e.g. 9222)
|
|
26
|
+
* instead of spawning. Guest mode:
|
|
27
|
+
* never spawns, kills, or restores.
|
|
24
28
|
*/
|
|
25
29
|
|
|
26
30
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -35,7 +39,7 @@ import { fileURLToPath } from "url";
|
|
|
35
39
|
|
|
36
40
|
import {
|
|
37
41
|
ensureBrowser, restartBrowser, stopBrowser, saveScreenshot,
|
|
38
|
-
cfg, browser, browserExited, actualCdpPort,
|
|
42
|
+
cfg, browser, browserExited, actualCdpPort, attached,
|
|
39
43
|
isTermux, pokeActivity, setIdleSuppressed,
|
|
40
44
|
} from "./lib/browser.mjs";
|
|
41
45
|
|
|
@@ -87,6 +91,7 @@ function parseArgs() {
|
|
|
87
91
|
case "--nuclear": cliCfg.nuclear = args[++i] !== "false"; break;
|
|
88
92
|
case "--idle": cliCfg.idleMs = parseInt(args[++i], 10); break;
|
|
89
93
|
case "--tab-max": cliCfg.tabMax = parseInt(args[++i], 10); break;
|
|
94
|
+
case "--attach-port": cliCfg.attachPort = parseInt(args[++i], 10); break;
|
|
90
95
|
case "--version": console.log(`bwb-browser ${BWB_VERSION}`); process.exit(0);
|
|
91
96
|
case "--help": printHelp(); process.exit(0);
|
|
92
97
|
}
|
|
@@ -117,6 +122,8 @@ OPTIONS:
|
|
|
117
122
|
--nuclear Add --single-process (max saving, min stability)
|
|
118
123
|
--idle <ms> Mayfly teardown after N ms idle (default: 5min lean)
|
|
119
124
|
--tab-max <n> Live-tab cap, oldest hibernated (default: 3 lean)
|
|
125
|
+
--attach-port <n> Attach to a running browser's CDP port (e.g. 9222).
|
|
126
|
+
Guest mode: no spawn, no kill, visible window.
|
|
120
127
|
--version Print version
|
|
121
128
|
--help Show this help
|
|
122
129
|
|
|
@@ -198,6 +205,7 @@ async function ensureDeps() {
|
|
|
198
205
|
|
|
199
206
|
Object.assign(cfg, parseArgs());
|
|
200
207
|
cfg.port = cfg.port || parseInt(process.env.BWB_CDP_PORT || "0", 10);
|
|
208
|
+
cfg.attachPort = cfg.attachPort || parseInt(process.env.BWB_ATTACH_PORT || "0", 10);
|
|
201
209
|
cfg.headless = cfg.headless !== undefined ? cfg.headless : (process.env.BWB_HEADLESS !== "false");
|
|
202
210
|
cfg.userDataDir = cfg.userDataDir || process.env.BWB_USER_DATA_DIR || join(homedir(), ".cache", "bwb-browser");
|
|
203
211
|
cfg.screenshotsDir = cfg.screenshotsDir || process.env.BWB_SCREENSHOTS_DIR || (() => {
|
|
@@ -733,9 +741,11 @@ const tools = {
|
|
|
733
741
|
schema: {},
|
|
734
742
|
handler: async () => {
|
|
735
743
|
const status = { connected: false, port: cfg.port, actualPort: null, running: false, pid: null, tabs: [] };
|
|
736
|
-
|
|
744
|
+
// Attach mode: no child process (browser === null) — liveness is the CDP link.
|
|
745
|
+
if ((browser && !browserExited) || (attached && !browserExited)) {
|
|
737
746
|
status.running = true;
|
|
738
|
-
status.pid = browser.pid;
|
|
747
|
+
status.pid = browser ? browser.pid : null;
|
|
748
|
+
if (attached) status.attached = true;
|
|
739
749
|
status.tabs = listTabs();
|
|
740
750
|
// actualCdpPort is the real bound port; cfg.port may be 0 (random).
|
|
741
751
|
// Never fall back to a hardcoded 9222 — that could be another tool's browser.
|
|
@@ -753,6 +763,7 @@ const tools = {
|
|
|
753
763
|
status.resources = sampleResources(browser?.pid, status.tabs.filter((t) => !t.hibernated).length);
|
|
754
764
|
status.resources.state = assess(status.resources);
|
|
755
765
|
status.profile = { lean: cfg.lean, nuclear: cfg.nuclear, idleMs: cfg.idleMs, tabMax: cfg.tabMax };
|
|
766
|
+
if (attached) status.profile.attached = actualCdpPort || cfg.attachPort;
|
|
756
767
|
} catch {}
|
|
757
768
|
return { content: [{ type: "text", text: JSON.stringify(status) }] };
|
|
758
769
|
},
|
|
@@ -788,7 +799,9 @@ for (const [name, tool] of Object.entries(tools)) {
|
|
|
788
799
|
const liveTabs = listTabs().filter((t) => !t.hibernated).length;
|
|
789
800
|
const sample = sampleResources(browser?.pid, liveTabs);
|
|
790
801
|
let note = "";
|
|
791
|
-
|
|
802
|
+
// Never auto-shed in attach mode: those are the user's REAL tabs.
|
|
803
|
+
// Report pressure in the footer; the human closes their own tabs.
|
|
804
|
+
if (!attached && assess(sample) === "critical" && browser && !browserExited) {
|
|
792
805
|
// Evidence first: keep the peak numbers that triggered the shed.
|
|
793
806
|
const peak = `${sample.mcpMb}+${sample.chromiumMb ?? "?"}MB`;
|
|
794
807
|
// Shed oldest non-active tabs first; teardown at one tab. Journal keeps all.
|