bwb-browser 3.0.0 → 3.1.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/AGENTS.md +25 -13
- package/lib/setup.mjs +324 -0
- package/package.json +1 -1
- package/server.mjs +7 -0
package/AGENTS.md
CHANGED
|
@@ -14,21 +14,33 @@ Built on Termux/Android. Runs everywhere. Weighs nothing. **Browser automation f
|
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
+
## 🔥 Zero-Config Install (5 seconds)
|
|
18
|
+
|
|
19
|
+
Don't copy-paste configs. Don't hunt for the right path. Just:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g bwb-browser
|
|
23
|
+
bwb --setup
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
That's it. `bwb --setup` auto-detects every AI agent on your machine (OpenCode, Antigravity, Claude Code, Hermes, Cline, Continue, Codex CLI), writes the correct MCP config for each, detects Chrome/Chromium, and prints a summary. Close and reopen your agent — tools are ready.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
17
30
|
## Compatible Agents
|
|
18
31
|
|
|
19
|
-
| Agent |
|
|
20
|
-
|
|
21
|
-
| **Claude Code** (Anthropic) |
|
|
22
|
-
| **OpenCode** |
|
|
23
|
-
| **Antigravity CLI** |
|
|
24
|
-
| **Cline** (VS Code) |
|
|
25
|
-
| **Continue.dev** |
|
|
26
|
-
| **
|
|
27
|
-
| **
|
|
28
|
-
| **
|
|
29
|
-
| **
|
|
30
|
-
| **Cursor** |
|
|
31
|
-
| **Any MCP-compatible agent** | MCP stdio transport | ✅ |
|
|
32
|
+
| Agent | Auto-Config via `bwb --setup` | Manual Config |
|
|
33
|
+
|-------|------------------------------|---------------|
|
|
34
|
+
| **Claude Code** (Anthropic) | ✅ `~/.claude/settings.json` | MCP config |
|
|
35
|
+
| **OpenCode** | ✅ `~/.config/opencode/opencode.json` | MCP config |
|
|
36
|
+
| **Antigravity CLI** | ✅ `~/.gemini/config/mcp_config.json` | MCP config |
|
|
37
|
+
| **Cline** (VS Code) | ✅ `~/.cline/mcp.json` | MCP config |
|
|
38
|
+
| **Continue.dev** | ✅ `~/.continue/config.json` | MCP config |
|
|
39
|
+
| **Codex CLI** | ✅ `~/.codex/mcp.json` | MCP config |
|
|
40
|
+
| **Hermes** | ✅ `~/.hermes/mcp.json` | MCP config |
|
|
41
|
+
| **Aider** | ❌ (custom tool) | Custom integration |
|
|
42
|
+
| **Cody** (Sourcegraph) | ❌ | MCP config |
|
|
43
|
+
| **Cursor** | ❌ (per-project) | `.cursor/mcp.json` |
|
|
32
44
|
|
|
33
45
|
---
|
|
34
46
|
|
package/lib/setup.mjs
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* bwb-browser --setup
|
|
4
|
+
* One-command self-configuration for any AI CLI agent.
|
|
5
|
+
*
|
|
6
|
+
* Detects installed agents, writes MCP config entries, verifies Chrome.
|
|
7
|
+
* Run: bwb --setup
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import { fileURLToPath } from 'url';
|
|
13
|
+
import { execSync } from 'child_process';
|
|
14
|
+
|
|
15
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const HOME = process.env.HOME || '/root';
|
|
17
|
+
const SERVER_PATH = path.resolve(__dirname, '..', 'server.mjs');
|
|
18
|
+
|
|
19
|
+
// ─── Detect Chrome/Chromium ───────────────────────────────────────
|
|
20
|
+
function detectChrome() {
|
|
21
|
+
if (process.env.BWB_CHROME_PATH) {
|
|
22
|
+
const p = process.env.BWB_CHROME_PATH;
|
|
23
|
+
if (fs.existsSync(p)) return { path: p, source: 'BWB_CHROME_PATH env' };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const candidates = [
|
|
27
|
+
'chromium-browser', 'chromium', 'google-chrome',
|
|
28
|
+
'google-chrome-stable', 'chrome', 'brave-browser',
|
|
29
|
+
'/usr/bin/chromium-browser', '/usr/bin/chromium',
|
|
30
|
+
'/usr/bin/google-chrome', '/snap/bin/chromium',
|
|
31
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
32
|
+
'/Applications/Brave Browser.app/Contents/MacOS/Brave Browser',
|
|
33
|
+
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
|
34
|
+
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
for (const bin of candidates) {
|
|
38
|
+
try {
|
|
39
|
+
const out = execSync(`which "${bin}" 2>/dev/null || command -v "${bin}" 2>/dev/null || echo ""`, {
|
|
40
|
+
encoding: 'utf8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe']
|
|
41
|
+
}).trim();
|
|
42
|
+
if (out) return { path: out, source: 'PATH' };
|
|
43
|
+
} catch { /* not found */ }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ─── Detect AI CLI tools ──────────────────────────────────────────
|
|
50
|
+
const AGENT_CONFIGS = [
|
|
51
|
+
{
|
|
52
|
+
name: 'OpenCode',
|
|
53
|
+
file: path.join(HOME, '.config/opencode/opencode.json'),
|
|
54
|
+
detect: (cfg) => !!(cfg.mcp?.bwb || cfg.mcpServers?.bwb),
|
|
55
|
+
add(cfg) {
|
|
56
|
+
const entry = {
|
|
57
|
+
type: 'local',
|
|
58
|
+
command: ['node', SERVER_PATH]
|
|
59
|
+
};
|
|
60
|
+
if (cfg.mcp && typeof cfg.mcp === 'object') cfg.mcp.bwb = entry;
|
|
61
|
+
else if (cfg.mcpServers && typeof cfg.mcpServers === 'object') cfg.mcpServers.bwb = entry;
|
|
62
|
+
else cfg.mcp = { bwb: entry };
|
|
63
|
+
return cfg;
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'Antigravity (CLI)',
|
|
68
|
+
file: path.join(HOME, '.gemini/config/mcp_config.json'),
|
|
69
|
+
detect: (cfg) => !!(cfg.mcpServers?.bwb),
|
|
70
|
+
add(cfg) {
|
|
71
|
+
if (!cfg.mcpServers) cfg.mcpServers = {};
|
|
72
|
+
cfg.mcpServers.bwb = {
|
|
73
|
+
command: 'node',
|
|
74
|
+
args: [SERVER_PATH]
|
|
75
|
+
};
|
|
76
|
+
return cfg;
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'Antigravity (CLI - secondary)',
|
|
81
|
+
file: path.join(HOME, '.gemini/antigravity-cli/mcp_config.json'),
|
|
82
|
+
detect: (cfg) => !!(cfg.mcpServers?.bwb),
|
|
83
|
+
add(cfg) {
|
|
84
|
+
if (!cfg.mcpServers) cfg.mcpServers = {};
|
|
85
|
+
cfg.mcpServers.bwb = {
|
|
86
|
+
command: 'node',
|
|
87
|
+
args: [SERVER_PATH]
|
|
88
|
+
};
|
|
89
|
+
return cfg;
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: 'Claude Code',
|
|
94
|
+
file: path.join(HOME, '.claude/settings.json'),
|
|
95
|
+
detect: (cfg) => !!(cfg.mcpServers?.bwb),
|
|
96
|
+
add(cfg) {
|
|
97
|
+
if (!cfg.mcpServers) cfg.mcpServers = {};
|
|
98
|
+
cfg.mcpServers.bwb = {
|
|
99
|
+
command: 'node',
|
|
100
|
+
args: [SERVER_PATH]
|
|
101
|
+
};
|
|
102
|
+
return cfg;
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: 'Hermes',
|
|
107
|
+
file: path.join(HOME, '.hermes/mcp.json'),
|
|
108
|
+
detect: (cfg) => !!(cfg.mcpServers?.bwb),
|
|
109
|
+
add(cfg) {
|
|
110
|
+
if (!cfg.mcpServers) cfg.mcpServers = {};
|
|
111
|
+
cfg.mcpServers.bwb = {
|
|
112
|
+
command: 'node',
|
|
113
|
+
args: [SERVER_PATH]
|
|
114
|
+
};
|
|
115
|
+
return cfg;
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: 'Cline (VS Code)',
|
|
120
|
+
file: path.join(HOME, '.cline/mcp.json'),
|
|
121
|
+
detect: (cfg) => !!(cfg.mcpServers?.bwb),
|
|
122
|
+
add(cfg) {
|
|
123
|
+
if (!cfg.mcpServers) cfg.mcpServers = {};
|
|
124
|
+
cfg.mcpServers.bwb = {
|
|
125
|
+
command: 'node',
|
|
126
|
+
args: [SERVER_PATH]
|
|
127
|
+
};
|
|
128
|
+
return cfg;
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: 'Continue.dev',
|
|
133
|
+
file: path.join(HOME, '.continue/config.json'),
|
|
134
|
+
detect: (cfg) => {
|
|
135
|
+
const servers = cfg.experimental?.mcpServers || cfg.mcpServers;
|
|
136
|
+
return !!(servers?.bwb);
|
|
137
|
+
},
|
|
138
|
+
add(cfg) {
|
|
139
|
+
if (!cfg.experimental) cfg.experimental = {};
|
|
140
|
+
if (!cfg.experimental.mcpServers) cfg.experimental.mcpServers = {};
|
|
141
|
+
cfg.experimental.mcpServers.bwb = {
|
|
142
|
+
command: 'node',
|
|
143
|
+
args: [SERVER_PATH]
|
|
144
|
+
};
|
|
145
|
+
return cfg;
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: 'Codex CLI',
|
|
150
|
+
file: path.join(HOME, '.codex/mcp.json'),
|
|
151
|
+
detect: (cfg) => !!(cfg.mcpServers?.bwb),
|
|
152
|
+
add(cfg) {
|
|
153
|
+
if (!cfg.mcpServers) cfg.mcpServers = {};
|
|
154
|
+
cfg.mcpServers.bwb = {
|
|
155
|
+
command: 'node',
|
|
156
|
+
args: [SERVER_PATH]
|
|
157
|
+
};
|
|
158
|
+
return cfg;
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
// ─── Core logic ────────────────────────────────────────────────────
|
|
164
|
+
function backup(file) {
|
|
165
|
+
const bak = file + '.bak';
|
|
166
|
+
try {
|
|
167
|
+
fs.copyFileSync(file, bak);
|
|
168
|
+
return bak;
|
|
169
|
+
} catch { return null; }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function writeConfigSafely(file, data) {
|
|
173
|
+
const tmp = file + '.tmp';
|
|
174
|
+
try {
|
|
175
|
+
fs.writeFileSync(tmp, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
176
|
+
fs.renameSync(tmp, file);
|
|
177
|
+
return true;
|
|
178
|
+
} catch { return false; }
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function configureAgent(agent) {
|
|
182
|
+
const { name, file, detect, add } = agent;
|
|
183
|
+
const fileExists = fs.existsSync(file);
|
|
184
|
+
|
|
185
|
+
if (!fileExists) return { name, status: 'skipped', reason: 'not installed' };
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const raw = fs.readFileSync(file, 'utf8');
|
|
189
|
+
let config;
|
|
190
|
+
try { config = JSON.parse(raw); }
|
|
191
|
+
catch { return { name, status: 'error', reason: 'invalid JSON' }; }
|
|
192
|
+
|
|
193
|
+
if (detect(config)) {
|
|
194
|
+
return { name, status: 'already configured', file };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const bak = backup(file);
|
|
198
|
+
config = add(config);
|
|
199
|
+
if (!writeConfigSafely(file, config)) {
|
|
200
|
+
return { name, status: 'error', reason: 'write failed' };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return { name, status: 'configured', file, backup: bak };
|
|
204
|
+
} catch (err) {
|
|
205
|
+
return { name, status: 'error', reason: err.message };
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ─── Permissions check ─────────────────────────────────────────────
|
|
210
|
+
function checkPermissions() {
|
|
211
|
+
// On Termux, the HOME may be accessible. Check if we can read/write config dirs.
|
|
212
|
+
const checkPaths = [
|
|
213
|
+
path.join(HOME, '.config/opencode'),
|
|
214
|
+
path.join(HOME, '.gemini/config'),
|
|
215
|
+
path.join(HOME, '.claude'),
|
|
216
|
+
];
|
|
217
|
+
const issues = [];
|
|
218
|
+
for (const p of checkPaths) {
|
|
219
|
+
if (fs.existsSync(p)) {
|
|
220
|
+
try { fs.accessSync(p, fs.constants.R_OK | fs.constants.W_OK); }
|
|
221
|
+
catch { issues.push(`${p}: no write permission`); }
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return issues;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ─── ────────────────────────────────────────────────────────────────
|
|
228
|
+
export function runSetup() {
|
|
229
|
+
console.log('\n ╔══════════════════════════════════════════════╗');
|
|
230
|
+
console.log(' ║ bwb-browser — Auto Setup ║');
|
|
231
|
+
console.log(' ╚══════════════════════════════════════════════╝\n');
|
|
232
|
+
|
|
233
|
+
// 1. Self-check
|
|
234
|
+
if (!fs.existsSync(SERVER_PATH)) {
|
|
235
|
+
console.error(` ❌ server.mjs not found at:\n ${SERVER_PATH}`);
|
|
236
|
+
console.error(' Is bwb-browser installed correctly?\n');
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
console.log(` 📦 bwb-browser: ${SERVER_PATH}`);
|
|
240
|
+
console.log(` v${getVersion()}\n`);
|
|
241
|
+
|
|
242
|
+
// 2. Check permissions
|
|
243
|
+
const permIssues = checkPermissions();
|
|
244
|
+
if (permIssues.length > 0) {
|
|
245
|
+
console.log(' ⚠️ Permission notes:');
|
|
246
|
+
for (const i of permIssues) console.log(` • ${i}`);
|
|
247
|
+
console.log();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// 3. Detect Chrome
|
|
251
|
+
const chrome = detectChrome();
|
|
252
|
+
if (chrome) {
|
|
253
|
+
console.log(` ✅ Chrome/Chromium detected:`);
|
|
254
|
+
console.log(` ${chrome.path} (${chrome.source})`);
|
|
255
|
+
} else {
|
|
256
|
+
console.log(` ⚠️ Chrome/Chromium not detected.`);
|
|
257
|
+
console.log(` Install it: pkg install chromium (Termux)`);
|
|
258
|
+
console.log(` or set BWB_CHROME_PATH env var.\n`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// 4. Configure all agents
|
|
262
|
+
console.log(' 🔧 Configuring agents...\n');
|
|
263
|
+
const results = AGENT_CONFIGS.map(configureAgent);
|
|
264
|
+
|
|
265
|
+
const configured = results.filter(r => r.status === 'configured');
|
|
266
|
+
const alreadyDone = results.filter(r => r.status === 'already configured');
|
|
267
|
+
const skipped = results.filter(r => r.status === 'skipped');
|
|
268
|
+
const errors = results.filter(r => r.status === 'error');
|
|
269
|
+
|
|
270
|
+
for (const r of results) {
|
|
271
|
+
switch (r.status) {
|
|
272
|
+
case 'configured':
|
|
273
|
+
console.log(` ✅ ${r.name}: configured`);
|
|
274
|
+
if (r.backup) console.log(` backup: ${r.backup}`);
|
|
275
|
+
break;
|
|
276
|
+
case 'already configured':
|
|
277
|
+
console.log(` ✅ ${r.name}: already set up`);
|
|
278
|
+
break;
|
|
279
|
+
case 'skipped':
|
|
280
|
+
break; // silent
|
|
281
|
+
case 'error':
|
|
282
|
+
console.log(` ❌ ${r.name}: ${r.reason}`);
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// 5. Summary
|
|
288
|
+
console.log('\n ─── Summary ───────────────────────────────────────');
|
|
289
|
+
console.log(` ✅ Agents configured: ${configured.length}`);
|
|
290
|
+
console.log(` ✅ Already set up: ${alreadyDone.length}`);
|
|
291
|
+
console.log(` ⏭️ Not installed: ${skipped.length}`);
|
|
292
|
+
if (errors.length) console.log(` ❌ Errors: ${errors.length}`);
|
|
293
|
+
console.log();
|
|
294
|
+
|
|
295
|
+
if (configured.length > 0) {
|
|
296
|
+
console.log(' 🔄 RESTART REQUIRED: Close and reopen your AI agent');
|
|
297
|
+
console.log(' for the new MCP tools to take effect.\n');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (!chrome) {
|
|
301
|
+
console.log(' ⚠️ Chrome not found — install it first:');
|
|
302
|
+
console.log(' Termux: pkg install chromium');
|
|
303
|
+
console.log(' macOS: brew install --cask google-chrome');
|
|
304
|
+
console.log(' Linux: apt install chromium-browser\n');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
console.log(' 🚀 Ready to go! Try: browser_status\n');
|
|
308
|
+
return { configured: configured.length, alreadyDone: alreadyDone.length, errors: errors.length };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function getVersion() {
|
|
312
|
+
try {
|
|
313
|
+
const pkg = path.resolve(__dirname, '..', 'package.json');
|
|
314
|
+
const json = JSON.parse(fs.readFileSync(pkg, 'utf8'));
|
|
315
|
+
return json.version || 'unknown';
|
|
316
|
+
} catch {
|
|
317
|
+
return 'unknown';
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// ─── Direct execution ──────────────────────────────────────────────
|
|
322
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
323
|
+
runSetup();
|
|
324
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bwb-browser",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Browser Without Bloat — 76KB MCP server that gives AI agents browser superpowers. Natural language interaction, live event watching, persistent sessions, multi-tab. Runs on Termux/Android, desktop, and CI. Zero heavy dependencies.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"bwb": "bin/bwb"
|
package/server.mjs
CHANGED
|
@@ -28,6 +28,13 @@ import {
|
|
|
28
28
|
cfg, browser, browserExited, actualCdpPort,
|
|
29
29
|
} from "./lib/browser.mjs";
|
|
30
30
|
|
|
31
|
+
// ─── --setup mode ──────────────────────────────────────────────────
|
|
32
|
+
if (process.argv.includes('--setup')) {
|
|
33
|
+
const { runSetup } = await import('./lib/setup.mjs');
|
|
34
|
+
runSetup();
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
31
38
|
import {
|
|
32
39
|
gotoUrl, clickElement, fillElement, waitForSelector,
|
|
33
40
|
} from "./lib/helpers.mjs";
|