browser-pilot-cli 0.1.0 → 0.1.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/README.md +58 -53
- package/dist/cli.js +58 -10
- package/package.json +5 -2
- package/playwright.config.ts +36 -0
- package/test-results/.last-run.json +4 -0
package/README.md
CHANGED
|
@@ -1,89 +1,94 @@
|
|
|
1
1
|
# browser-pilot
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Give your AI agent control of your real browser — with your logins, cookies, and extensions intact. No extension needed.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
|
|
7
|
-
bp click 3 # click [3] — returns updated page
|
|
8
|
-
bp type 5 "hello" --submit # type + Enter — returns updated page
|
|
9
|
-
bp eval "document.title" # run any JavaScript
|
|
6
|
+
npm install -g browser-pilot-cli
|
|
10
7
|
```
|
|
11
8
|
|
|
9
|
+
## Agent Setup
|
|
10
|
+
|
|
11
|
+
### 1. Enable Chrome remote debugging (one-time)
|
|
12
|
+
|
|
13
|
+
Open `chrome://inspect/#remote-debugging` in Chrome (144+) and click Allow. No command-line flags, no restart needed.
|
|
14
|
+
|
|
15
|
+
> Chrome 136 disabled the old `--remote-debugging-port` flag for security. Chrome 144 introduced this new UI toggle as the replacement — browser-pilot uses this.
|
|
16
|
+
|
|
17
|
+
### 2. Install the plugin for your agent
|
|
18
|
+
|
|
19
|
+
**Claude Code:**
|
|
20
|
+
```
|
|
21
|
+
/plugin marketplace add https://github.com/relixiaobo/browser-pilot.git
|
|
22
|
+
/plugin install browser-pilot@browser-pilot-marketplace
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Codex CLI:**
|
|
26
|
+
```bash
|
|
27
|
+
npx skills add relixiaobo/browser-pilot
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**OpenClaw:**
|
|
31
|
+
```bash
|
|
32
|
+
cp -r plugin/skills/browser-pilot ~/.agents/skills/
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Cursor / VS Code Copilot:**
|
|
36
|
+
```bash
|
|
37
|
+
npx skills add relixiaobo/browser-pilot
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 3. Use it
|
|
41
|
+
|
|
42
|
+
Just tell your agent what you want to do:
|
|
43
|
+
|
|
44
|
+
- "Open GitHub and check my notifications"
|
|
45
|
+
- "Go to Hacker News and summarize the top 5 posts"
|
|
46
|
+
- "Fill out the form on this page"
|
|
47
|
+
|
|
48
|
+
The agent will use `bp` commands automatically. Your real login sessions are preserved — no need to re-authenticate.
|
|
49
|
+
|
|
50
|
+
## Why browser-pilot?
|
|
51
|
+
|
|
52
|
+
- **No extension required** — Uses Chrome 144's native remote debugging toggle, not the Extension Debugger API
|
|
53
|
+
- **Real login sessions** — Operates your actual browser profile. Cookies, extensions, logins all intact
|
|
54
|
+
- **CLI-native** — Any agent with bash access can use it. No MCP protocol, no SDK integration needed
|
|
55
|
+
- **Auto-snapshot** — Every action returns page state with numbered `[ref]` elements, so the agent always knows what's on screen
|
|
56
|
+
- **Lightweight** — 78KB npm package. No bundled Chromium (unlike Playwright's 400MB+)
|
|
57
|
+
|
|
12
58
|
## Comparison
|
|
13
59
|
|
|
14
60
|
| | browser-pilot | Playwright MCP | Chrome DevTools MCP | browser-use |
|
|
15
61
|
|---|---|---|---|---|
|
|
16
62
|
| **Interface** | CLI (bash) | MCP protocol | MCP protocol | Python SDK |
|
|
17
63
|
| **Login session reuse** | Yes | No | Depends | Yes |
|
|
64
|
+
| **Extension required** | No | No | No | No |
|
|
18
65
|
| **Element refs** | Numbered (accessibility tree) | Named refs (ARIA) | CSS selectors | Numbered (DOM) |
|
|
19
66
|
| **Auto-snapshot after action** | Yes | Yes | No | Yes |
|
|
20
67
|
| **Network interception** | Yes (block/mock/headers) | Yes | Yes | No |
|
|
21
|
-
| **Drag and drop** | No | Yes | Yes | No |
|
|
22
|
-
| **Mobile emulation** | No | Yes | Yes | Yes |
|
|
23
68
|
| **Multi-browser** | Chromium-only | Chromium + Firefox + WebKit | Chromium-only | Chromium-only |
|
|
24
|
-
| **Connection model** | Daemon (one-time Allow) | MCP server | MCP server | Python process |
|
|
25
69
|
| **Dialog auto-handling** | Yes | Yes | No | Yes |
|
|
26
70
|
| **JSON output** | Default | MCP structured | MCP structured | Python objects |
|
|
27
|
-
| **Visual indicator** | Pulsing glow | None | None | None |
|
|
28
71
|
| **File upload** | Auto-detect input | Yes | No | Yes |
|
|
29
|
-
| **Autonomous agent** | No (tool, not agent) | No (tool) | No (tool) | Yes (plans + executes) |
|
|
30
|
-
|
|
31
|
-
**browser-pilot is best when:**
|
|
32
|
-
- You need your existing login sessions (paywalled content, internal tools)
|
|
33
|
-
- Your LLM has bash access but no MCP support
|
|
34
|
-
- You want every action to return page state automatically
|
|
35
|
-
|
|
36
|
-
**Use Playwright MCP when:**
|
|
37
|
-
- You need multi-browser or mobile emulation
|
|
38
|
-
- Your LLM supports MCP natively
|
|
39
|
-
- You don't need existing login sessions
|
|
40
|
-
|
|
41
|
-
**Use browser-use when:**
|
|
42
|
-
- You want an autonomous agent that plans and executes multi-step tasks
|
|
43
|
-
- You're building in Python
|
|
44
|
-
|
|
45
|
-
## Quick Start
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
# Install
|
|
49
|
-
npm install -g browser-pilot
|
|
50
|
-
|
|
51
|
-
# Enable debugging in Chrome (one-time)
|
|
52
|
-
# Open chrome://inspect/#remote-debugging → toggle ON
|
|
53
|
-
|
|
54
|
-
# Connect (click Allow in Chrome's dialog)
|
|
55
|
-
bp connect
|
|
56
|
-
|
|
57
|
-
# Use
|
|
58
|
-
bp open https://github.com
|
|
59
|
-
bp click 3
|
|
60
|
-
bp type 5 "hello" --submit
|
|
61
|
-
bp eval "document.title"
|
|
62
|
-
bp screenshot page.png
|
|
63
|
-
bp disconnect
|
|
64
|
-
```
|
|
65
72
|
|
|
66
73
|
## How It Works
|
|
67
74
|
|
|
68
75
|
```
|
|
69
|
-
|
|
76
|
+
Agent (bash tool)
|
|
70
77
|
│ bp open / bp click / bp eval ...
|
|
71
78
|
▼
|
|
72
79
|
CLI Process ──── HTTP/Unix Socket ──── Daemon Process (persistent)
|
|
73
80
|
│
|
|
74
|
-
│ WebSocket (CDP
|
|
81
|
+
│ WebSocket (CDP)
|
|
75
82
|
▼
|
|
76
83
|
Chrome (your browser, your profile)
|
|
77
84
|
├── Your windows (untouched)
|
|
78
|
-
└── Pilot window (
|
|
85
|
+
└── Pilot window (agent operates here)
|
|
79
86
|
```
|
|
80
87
|
|
|
81
|
-
The daemon maintains a single CDP WebSocket connection.
|
|
88
|
+
The daemon maintains a single CDP WebSocket connection. A pulsing blue glow around the Pilot window indicates the agent is active.
|
|
82
89
|
|
|
83
90
|
## Commands
|
|
84
91
|
|
|
85
|
-
Run `bp --help` for full details including workflow, refs, and eval examples.
|
|
86
|
-
|
|
87
92
|
### Core Loop
|
|
88
93
|
|
|
89
94
|
| Command | Returns | Description |
|
|
@@ -222,9 +227,9 @@ bp net clear # clear captured request log
|
|
|
222
227
|
|
|
223
228
|
## Requirements
|
|
224
229
|
|
|
225
|
-
- Chrome
|
|
230
|
+
- Chrome 144+ / Edge / Brave (any Chromium-based browser)
|
|
226
231
|
- Node.js >= 18
|
|
227
|
-
-
|
|
232
|
+
- Remote debugging enabled (`chrome://inspect/#remote-debugging`)
|
|
228
233
|
|
|
229
234
|
## License
|
|
230
235
|
|
package/dist/cli.js
CHANGED
|
@@ -245,6 +245,14 @@ var SET_VALUE = `function(text, clear) {
|
|
|
245
245
|
this.dispatchEvent(new Event('input', {bubbles:true}));
|
|
246
246
|
this.dispatchEvent(new Event('change', {bubbles:true}));
|
|
247
247
|
}`;
|
|
248
|
+
var IS_CONTENTEDITABLE = `function() {
|
|
249
|
+
return this.isContentEditable && !(this instanceof HTMLInputElement) && !(this instanceof HTMLTextAreaElement);
|
|
250
|
+
}`;
|
|
251
|
+
var CONTENTEDITABLE_CLEAR = `function() {
|
|
252
|
+
this.focus();
|
|
253
|
+
document.execCommand('selectAll');
|
|
254
|
+
document.execCommand('delete');
|
|
255
|
+
}`;
|
|
248
256
|
var PAGE_INFO = `JSON.stringify({title:document.title,url:location.href})`;
|
|
249
257
|
var PAGE_DIMENSIONS = `JSON.stringify({
|
|
250
258
|
width: Math.max(document.documentElement.scrollWidth, document.documentElement.clientWidth),
|
|
@@ -431,6 +439,17 @@ var INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
|
|
|
431
439
|
"menuitemradio",
|
|
432
440
|
"tab"
|
|
433
441
|
]);
|
|
442
|
+
var ALLOW_EMPTY_NAME = /* @__PURE__ */ new Set([
|
|
443
|
+
"textbox",
|
|
444
|
+
"searchbox",
|
|
445
|
+
"combobox",
|
|
446
|
+
"listbox",
|
|
447
|
+
"checkbox",
|
|
448
|
+
"radio",
|
|
449
|
+
"spinbutton",
|
|
450
|
+
"slider",
|
|
451
|
+
"switch"
|
|
452
|
+
]);
|
|
434
453
|
function saveRefs(targetId, entries) {
|
|
435
454
|
writeFileSync2(REFS_FILE, JSON.stringify({ targetId, entries }), { mode: 384 });
|
|
436
455
|
}
|
|
@@ -466,16 +485,20 @@ async function takeSnapshot(transport, sessionId, targetId, limit = 50) {
|
|
|
466
485
|
if (!node) return;
|
|
467
486
|
if (!node.ignored) {
|
|
468
487
|
const role = node.role?.value;
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
488
|
+
const props = Object.fromEntries(
|
|
489
|
+
(node.properties || []).map((p) => [p.name, p.value?.value])
|
|
490
|
+
);
|
|
491
|
+
const isEditable = props.editable === "richtext";
|
|
492
|
+
const isInteractive = role && (INTERACTIVE_ROLES.has(role) || isEditable);
|
|
493
|
+
if (isInteractive && node.backendDOMNodeId !== void 0) {
|
|
473
494
|
const name = node.name?.value || "";
|
|
474
495
|
const value = node.value?.value;
|
|
475
|
-
|
|
496
|
+
const effectiveRole = isEditable && role === "generic" ? "textbox" : role;
|
|
497
|
+
const hasIdentity = name || value || ALLOW_EMPTY_NAME.has(effectiveRole) || isEditable;
|
|
498
|
+
if (!props.disabled && hasIdentity && refs.length < limit) {
|
|
476
499
|
const checked = props.checked === "true" || props.checked === true ? true : void 0;
|
|
477
|
-
refs.push({ backendNodeId: node.backendDOMNodeId, role, name });
|
|
478
|
-
elements.push({ ref: refs.length, backendNodeId: node.backendDOMNodeId, role, name, value, checked });
|
|
500
|
+
refs.push({ backendNodeId: node.backendDOMNodeId, role: effectiveRole, name });
|
|
501
|
+
elements.push({ ref: refs.length, backendNodeId: node.backendDOMNodeId, role: effectiveRole, name, value, checked });
|
|
479
502
|
}
|
|
480
503
|
}
|
|
481
504
|
}
|
|
@@ -747,11 +770,36 @@ program.command("type <ref> <text>").description("Type text into element and ret
|
|
|
747
770
|
await withPilot(async ({ transport, sessionId, state }) => {
|
|
748
771
|
const objectId = await resolveTarget(transport, sessionId, ref, state.activeTargetId);
|
|
749
772
|
try {
|
|
750
|
-
await transport.send("Runtime.callFunctionOn", {
|
|
773
|
+
const { result: ceResult } = await transport.send("Runtime.callFunctionOn", {
|
|
751
774
|
objectId,
|
|
752
|
-
functionDeclaration:
|
|
753
|
-
|
|
775
|
+
functionDeclaration: IS_CONTENTEDITABLE,
|
|
776
|
+
returnByValue: true
|
|
754
777
|
}, sessionId);
|
|
778
|
+
if (ceResult.value) {
|
|
779
|
+
if (opts.clear) {
|
|
780
|
+
await transport.send("Runtime.callFunctionOn", {
|
|
781
|
+
objectId,
|
|
782
|
+
functionDeclaration: CONTENTEDITABLE_CLEAR
|
|
783
|
+
}, sessionId);
|
|
784
|
+
} else {
|
|
785
|
+
await transport.send("Runtime.callFunctionOn", {
|
|
786
|
+
objectId,
|
|
787
|
+
functionDeclaration: `function() {
|
|
788
|
+
this.focus();
|
|
789
|
+
const sel = window.getSelection();
|
|
790
|
+
sel.selectAllChildren(this);
|
|
791
|
+
sel.collapseToEnd();
|
|
792
|
+
}`
|
|
793
|
+
}, sessionId);
|
|
794
|
+
}
|
|
795
|
+
await transport.send("Input.insertText", { text }, sessionId);
|
|
796
|
+
} else {
|
|
797
|
+
await transport.send("Runtime.callFunctionOn", {
|
|
798
|
+
objectId,
|
|
799
|
+
functionDeclaration: SET_VALUE,
|
|
800
|
+
arguments: [{ value: text }, { value: !!opts.clear }]
|
|
801
|
+
}, sessionId);
|
|
802
|
+
}
|
|
755
803
|
if (opts.submit) await dispatchKey(transport, sessionId, "Enter");
|
|
756
804
|
} finally {
|
|
757
805
|
await transport.send("Runtime.releaseObject", { objectId }, sessionId).catch(() => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "browser-pilot-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "CLI tool to control your browser via Chrome DevTools Protocol",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,13 +11,16 @@
|
|
|
11
11
|
"build": "tsup",
|
|
12
12
|
"dev": "tsup --watch",
|
|
13
13
|
"start": "node dist/cli.js",
|
|
14
|
-
"test": "
|
|
14
|
+
"test": "tsup && npx playwright test --project core --project compat --project network",
|
|
15
|
+
"test:integration": "tsup && npx playwright test --project integration",
|
|
16
|
+
"test:all": "tsup && npx playwright test"
|
|
15
17
|
},
|
|
16
18
|
"dependencies": {
|
|
17
19
|
"commander": "^13.1.0",
|
|
18
20
|
"ws": "^8.18.0"
|
|
19
21
|
},
|
|
20
22
|
"devDependencies": {
|
|
23
|
+
"@playwright/test": "^1.59.1",
|
|
21
24
|
"@types/node": "^22.0.0",
|
|
22
25
|
"@types/ws": "^8.5.0",
|
|
23
26
|
"tsup": "^8.0.0",
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { defineConfig } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
testDir: './tests',
|
|
5
|
+
timeout: 30_000,
|
|
6
|
+
retries: 0,
|
|
7
|
+
workers: 1, // bp uses a single browser session — must be serial
|
|
8
|
+
globalSetup: './tests/global-setup.ts',
|
|
9
|
+
globalTeardown: './tests/global-teardown.ts',
|
|
10
|
+
use: {
|
|
11
|
+
baseURL: 'http://127.0.0.1:18274',
|
|
12
|
+
},
|
|
13
|
+
webServer: {
|
|
14
|
+
command: 'node tests/server.mjs 18274',
|
|
15
|
+
port: 18274,
|
|
16
|
+
reuseExistingServer: true,
|
|
17
|
+
},
|
|
18
|
+
projects: [
|
|
19
|
+
{
|
|
20
|
+
name: 'core',
|
|
21
|
+
testMatch: /core\.spec/,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'compat',
|
|
25
|
+
testMatch: /fill|click|snapshot/,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'network',
|
|
29
|
+
testMatch: /network\.spec/,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'integration',
|
|
33
|
+
testMatch: /real-site\.spec/,
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
});
|