🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

browser-pilot-cli

Package Overview
Dependencies
Maintainers
1
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

browser-pilot-cli - npm Package Compare versions

Comparing version
0.1.0
to
0.1.1
+36
playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30_000,
retries: 0,
workers: 1, // bp uses a single browser session — must be serial
globalSetup: './tests/global-setup.ts',
globalTeardown: './tests/global-teardown.ts',
use: {
baseURL: 'http://127.0.0.1:18274',
},
webServer: {
command: 'node tests/server.mjs 18274',
port: 18274,
reuseExistingServer: true,
},
projects: [
{
name: 'core',
testMatch: /core\.spec/,
},
{
name: 'compat',
testMatch: /fill|click|snapshot/,
},
{
name: 'network',
testMatch: /network\.spec/,
},
{
name: 'integration',
testMatch: /real-site\.spec/,
},
],
});
{
"status": "passed",
"failedTests": []
}
+58
-10

@@ -248,2 +248,10 @@ #!/usr/bin/env node

}`;
var IS_CONTENTEDITABLE = `function() {
return this.isContentEditable && !(this instanceof HTMLInputElement) && !(this instanceof HTMLTextAreaElement);
}`;
var CONTENTEDITABLE_CLEAR = `function() {
this.focus();
document.execCommand('selectAll');
document.execCommand('delete');
}`;
var PAGE_INFO = `JSON.stringify({title:document.title,url:location.href})`;

@@ -435,2 +443,13 @@ var PAGE_DIMENSIONS = `JSON.stringify({

]);
var ALLOW_EMPTY_NAME = /* @__PURE__ */ new Set([
"textbox",
"searchbox",
"combobox",
"listbox",
"checkbox",
"radio",
"spinbutton",
"slider",
"switch"
]);
function saveRefs(targetId, entries) {

@@ -471,12 +490,16 @@ writeFileSync2(REFS_FILE, JSON.stringify({ targetId, entries }), { mode: 384 });

const role = node.role?.value;
if (role && INTERACTIVE_ROLES.has(role) && node.backendDOMNodeId !== void 0) {
const props = Object.fromEntries(
(node.properties || []).map((p) => [p.name, p.value?.value])
);
const props = Object.fromEntries(
(node.properties || []).map((p) => [p.name, p.value?.value])
);
const isEditable = props.editable === "richtext";
const isInteractive = role && (INTERACTIVE_ROLES.has(role) || isEditable);
if (isInteractive && node.backendDOMNodeId !== void 0) {
const name = node.name?.value || "";
const value = node.value?.value;
if (!props.disabled && (name || value) && refs.length < limit) {
const effectiveRole = isEditable && role === "generic" ? "textbox" : role;
const hasIdentity = name || value || ALLOW_EMPTY_NAME.has(effectiveRole) || isEditable;
if (!props.disabled && hasIdentity && refs.length < limit) {
const checked = props.checked === "true" || props.checked === true ? true : void 0;
refs.push({ backendNodeId: node.backendDOMNodeId, role, name });
elements.push({ ref: refs.length, backendNodeId: node.backendDOMNodeId, role, name, value, checked });
refs.push({ backendNodeId: node.backendDOMNodeId, role: effectiveRole, name });
elements.push({ ref: refs.length, backendNodeId: node.backendDOMNodeId, role: effectiveRole, name, value, checked });
}

@@ -753,7 +776,32 @@ }

try {
await transport.send("Runtime.callFunctionOn", {
const { result: ceResult } = await transport.send("Runtime.callFunctionOn", {
objectId,
functionDeclaration: SET_VALUE,
arguments: [{ value: text }, { value: !!opts.clear }]
functionDeclaration: IS_CONTENTEDITABLE,
returnByValue: true
}, sessionId);
if (ceResult.value) {
if (opts.clear) {
await transport.send("Runtime.callFunctionOn", {
objectId,
functionDeclaration: CONTENTEDITABLE_CLEAR
}, sessionId);
} else {
await transport.send("Runtime.callFunctionOn", {
objectId,
functionDeclaration: `function() {
this.focus();
const sel = window.getSelection();
sel.selectAllChildren(this);
sel.collapseToEnd();
}`
}, sessionId);
}
await transport.send("Input.insertText", { text }, sessionId);
} else {
await transport.send("Runtime.callFunctionOn", {
objectId,
functionDeclaration: SET_VALUE,
arguments: [{ value: text }, { value: !!opts.clear }]
}, sessionId);
}
if (opts.submit) await dispatchKey(transport, sessionId, "Enter");

@@ -760,0 +808,0 @@ } finally {

+5
-2
{
"name": "browser-pilot-cli",
"version": "0.1.0",
"version": "0.1.1",
"description": "CLI tool to control your browser via Chrome DevTools Protocol",

@@ -14,3 +14,5 @@ "type": "module",

"start": "node dist/cli.js",
"test": "bash tests/run.sh"
"test": "tsup && npx playwright test --project core --project compat --project network",
"test:integration": "tsup && npx playwright test --project integration",
"test:all": "tsup && npx playwright test"
},

@@ -22,2 +24,3 @@ "dependencies": {

"devDependencies": {
"@playwright/test": "^1.59.1",
"@types/node": "^22.0.0",

@@ -24,0 +27,0 @@ "@types/ws": "^8.5.0",

+58
-53
# browser-pilot
CLI tool that gives LLM agents control of your real browser — with your logins, cookies, and extensions intact.
Give your AI agent control of your real browser — with your logins, cookies, and extensions intact. No extension needed.
```bash
bp open https://github.com # navigate — returns interactive elements
bp click 3 # click [3] — returns updated page
bp type 5 "hello" --submit # type + Enter — returns updated page
bp eval "document.title" # run any JavaScript
npm install -g browser-pilot-cli
```
## Agent Setup
### 1. Enable Chrome remote debugging (one-time)
Open `chrome://inspect/#remote-debugging` in Chrome (144+) and click Allow. No command-line flags, no restart needed.
> 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.
### 2. Install the plugin for your agent
**Claude Code:**
```
/plugin marketplace add https://github.com/relixiaobo/browser-pilot.git
/plugin install browser-pilot@browser-pilot-marketplace
```
**Codex CLI:**
```bash
npx skills add relixiaobo/browser-pilot
```
**OpenClaw:**
```bash
cp -r plugin/skills/browser-pilot ~/.agents/skills/
```
**Cursor / VS Code Copilot:**
```bash
npx skills add relixiaobo/browser-pilot
```
### 3. Use it
Just tell your agent what you want to do:
- "Open GitHub and check my notifications"
- "Go to Hacker News and summarize the top 5 posts"
- "Fill out the form on this page"
The agent will use `bp` commands automatically. Your real login sessions are preserved — no need to re-authenticate.
## Why browser-pilot?
- **No extension required** — Uses Chrome 144's native remote debugging toggle, not the Extension Debugger API
- **Real login sessions** — Operates your actual browser profile. Cookies, extensions, logins all intact
- **CLI-native** — Any agent with bash access can use it. No MCP protocol, no SDK integration needed
- **Auto-snapshot** — Every action returns page state with numbered `[ref]` elements, so the agent always knows what's on screen
- **Lightweight** — 78KB npm package. No bundled Chromium (unlike Playwright's 400MB+)
## Comparison

@@ -18,54 +64,15 @@

| **Login session reuse** | Yes | No | Depends | Yes |
| **Extension required** | No | No | No | No |
| **Element refs** | Numbered (accessibility tree) | Named refs (ARIA) | CSS selectors | Numbered (DOM) |
| **Auto-snapshot after action** | Yes | Yes | No | Yes |
| **Network interception** | Yes (block/mock/headers) | Yes | Yes | No |
| **Drag and drop** | No | Yes | Yes | No |
| **Mobile emulation** | No | Yes | Yes | Yes |
| **Multi-browser** | Chromium-only | Chromium + Firefox + WebKit | Chromium-only | Chromium-only |
| **Connection model** | Daemon (one-time Allow) | MCP server | MCP server | Python process |
| **Dialog auto-handling** | Yes | Yes | No | Yes |
| **JSON output** | Default | MCP structured | MCP structured | Python objects |
| **Visual indicator** | Pulsing glow | None | None | None |
| **File upload** | Auto-detect input | Yes | No | Yes |
| **Autonomous agent** | No (tool, not agent) | No (tool) | No (tool) | Yes (plans + executes) |
**browser-pilot is best when:**
- You need your existing login sessions (paywalled content, internal tools)
- Your LLM has bash access but no MCP support
- You want every action to return page state automatically
**Use Playwright MCP when:**
- You need multi-browser or mobile emulation
- Your LLM supports MCP natively
- You don't need existing login sessions
**Use browser-use when:**
- You want an autonomous agent that plans and executes multi-step tasks
- You're building in Python
## Quick Start
```bash
# Install
npm install -g browser-pilot
# Enable debugging in Chrome (one-time)
# Open chrome://inspect/#remote-debugging → toggle ON
# Connect (click Allow in Chrome's dialog)
bp connect
# Use
bp open https://github.com
bp click 3
bp type 5 "hello" --submit
bp eval "document.title"
bp screenshot page.png
bp disconnect
```
## How It Works
```
LLM (bash tool)
Agent (bash tool)
│ bp open / bp click / bp eval ...

@@ -75,15 +82,13 @@

│ WebSocket (CDP, one-time Allow)
│ WebSocket (CDP)
Chrome (your browser, your profile)
├── Your windows (untouched)
└── Pilot window (bp operates here)
└── Pilot window (agent operates here)
```
The daemon maintains a single CDP WebSocket connection. Chrome's "Allow" dialog appears once per session. A pulsing blue glow around the Pilot window indicates the agent is active.
The daemon maintains a single CDP WebSocket connection. A pulsing blue glow around the Pilot window indicates the agent is active.
## Commands
Run `bp --help` for full details including workflow, refs, and eval examples.
### Core Loop

@@ -227,5 +232,5 @@

- Chrome / Chromium / Edge / Brave (any Chromium-based browser)
- Chrome 144+ / Edge / Brave (any Chromium-based browser)
- Node.js >= 18
- Chrome remote debugging enabled (`chrome://inspect/#remote-debugging`)
- Remote debugging enabled (`chrome://inspect/#remote-debugging`)

@@ -232,0 +237,0 @@ ## License