browser-pilot-cli 0.1.0 → 0.1.2
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 +102 -58
- package/dist/cli.js +125 -10
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -1,89 +1,95 @@
|
|
|
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
|
+
- **Rich editor support** — Works with contenteditable editors (Draft.js, ProseMirror, Quill, Slate) and Shadow DOM elements out of the box
|
|
58
|
+
|
|
12
59
|
## Comparison
|
|
13
60
|
|
|
14
61
|
| | browser-pilot | Playwright MCP | Chrome DevTools MCP | browser-use |
|
|
15
62
|
|---|---|---|---|---|
|
|
16
63
|
| **Interface** | CLI (bash) | MCP protocol | MCP protocol | Python SDK |
|
|
17
64
|
| **Login session reuse** | Yes | No | Depends | Yes |
|
|
65
|
+
| **Extension required** | No | No | No | No |
|
|
18
66
|
| **Element refs** | Numbered (accessibility tree) | Named refs (ARIA) | CSS selectors | Numbered (DOM) |
|
|
19
67
|
| **Auto-snapshot after action** | Yes | Yes | No | Yes |
|
|
20
68
|
| **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
69
|
| **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
70
|
| **Dialog auto-handling** | Yes | Yes | No | Yes |
|
|
26
71
|
| **JSON output** | Default | MCP structured | MCP structured | Python objects |
|
|
27
|
-
| **Visual indicator** | Pulsing glow | None | None | None |
|
|
28
72
|
| **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
73
|
|
|
66
74
|
## How It Works
|
|
67
75
|
|
|
68
76
|
```
|
|
69
|
-
|
|
77
|
+
Agent (bash tool)
|
|
70
78
|
│ bp open / bp click / bp eval ...
|
|
71
79
|
▼
|
|
72
80
|
CLI Process ──── HTTP/Unix Socket ──── Daemon Process (persistent)
|
|
73
81
|
│
|
|
74
|
-
│ WebSocket (CDP
|
|
82
|
+
│ WebSocket (CDP)
|
|
75
83
|
▼
|
|
76
84
|
Chrome (your browser, your profile)
|
|
77
85
|
├── Your windows (untouched)
|
|
78
|
-
└── Pilot window (
|
|
86
|
+
└── Pilot window (agent operates here)
|
|
79
87
|
```
|
|
80
88
|
|
|
81
|
-
The daemon maintains a single CDP WebSocket connection.
|
|
89
|
+
The daemon maintains a single CDP WebSocket connection. A pulsing blue glow around the Pilot window indicates the agent is active.
|
|
82
90
|
|
|
83
91
|
## Commands
|
|
84
92
|
|
|
85
|
-
Run `bp --help` for full details including workflow, refs, and eval examples.
|
|
86
|
-
|
|
87
93
|
### Core Loop
|
|
88
94
|
|
|
89
95
|
| Command | Returns | Description |
|
|
@@ -122,7 +128,7 @@ Popup windows (target="_blank", window.open) are auto-detected. Run `bp tabs` to
|
|
|
122
128
|
| `bp net` | List recent requests (`--url`, `--method`, `--status`, `--type`) |
|
|
123
129
|
| `bp net show <id>` | Full request/response details (`--save <file>`) |
|
|
124
130
|
| `bp net block <pattern>` | Block requests matching URL pattern |
|
|
125
|
-
| `bp net mock <pattern>` | Mock responses (`--body`, `--file
|
|
131
|
+
| `bp net mock <pattern>` | Mock responses (`--body`, `--file`) |
|
|
126
132
|
| `bp net headers <pattern> <header...>` | Add/override request headers |
|
|
127
133
|
| `bp net rules` | List active interception rules |
|
|
128
134
|
| `bp net remove [id]` | Remove rule(s) (`--all`) |
|
|
@@ -144,13 +150,18 @@ Action commands return a snapshot of interactive elements, each with a `[ref]` n
|
|
|
144
150
|
|
|
145
151
|
```
|
|
146
152
|
[1] link "Home"
|
|
147
|
-
[2] textbox "Search"
|
|
148
|
-
[3]
|
|
153
|
+
[2] textbox "Search" ← <input>, <textarea>, or contenteditable
|
|
154
|
+
[3] textbox "" ← unnamed input (still interactive)
|
|
155
|
+
[4] combobox "" ← <select> dropdown
|
|
156
|
+
[5] spinbutton "Quantity" ← <input type="number">
|
|
157
|
+
[6] button "Submit"
|
|
158
|
+
[7] checkbox "Agree" checked
|
|
159
|
+
[8] slider "Volume" ← <input type="range">
|
|
149
160
|
```
|
|
150
161
|
|
|
151
162
|
Use the number in subsequent commands: `bp click 1`, `bp type 2 "hello"`.
|
|
152
163
|
|
|
153
|
-
Refs are scoped to the current page — they refresh automatically after every action.
|
|
164
|
+
Refs are scoped to the current page — they refresh automatically after every action. Elements inside Shadow DOM are included automatically.
|
|
154
165
|
|
|
155
166
|
## Output
|
|
156
167
|
|
|
@@ -192,6 +203,22 @@ bp click 5 # click "Search by image"
|
|
|
192
203
|
bp upload ~/Downloads/photo.jpg # auto-finds file input, triggers upload
|
|
193
204
|
```
|
|
194
205
|
|
|
206
|
+
## Rich Text Editors & Shadow DOM
|
|
207
|
+
|
|
208
|
+
`bp type` works with contenteditable-based editors (Draft.js, ProseMirror, Quill, Slate, Lexical). They appear as `textbox` in snapshots:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
bp type 3 "new content" --clear # replace content in a rich text editor
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Shadow DOM elements are traversed automatically — no special commands needed. Elements inside open shadow roots (even deeply nested) appear in snapshots and can be clicked/typed normally.
|
|
215
|
+
|
|
216
|
+
For `<select>` dropdowns (shown as `combobox`), use `bp eval`:
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
bp eval 'document.querySelector("select").value = "opt2"; document.querySelector("select").dispatchEvent(new Event("change",{bubbles:true}))'
|
|
220
|
+
```
|
|
221
|
+
|
|
195
222
|
## Network Interception
|
|
196
223
|
|
|
197
224
|
Monitor, block, and mock HTTP requests:
|
|
@@ -208,7 +235,7 @@ bp net block "*ads*"
|
|
|
208
235
|
|
|
209
236
|
# Mock API responses
|
|
210
237
|
bp net mock "*api/data*" --body '{"ok":true}'
|
|
211
|
-
bp net mock "*api/users*" --file mock.json
|
|
238
|
+
bp net mock "*api/users*" --file mock.json
|
|
212
239
|
|
|
213
240
|
# Override request headers
|
|
214
241
|
bp net headers "*api*" "Authorization:Bearer test123"
|
|
@@ -220,11 +247,28 @@ bp net remove --all # clear all rules
|
|
|
220
247
|
bp net clear # clear captured request log
|
|
221
248
|
```
|
|
222
249
|
|
|
250
|
+
## Testing
|
|
251
|
+
|
|
252
|
+
121 tests across 4 Playwright projects, adapted from Playwright/Puppeteer test fixtures and real-world sites:
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
npm test # core + compat + network (105 tests, ~2min)
|
|
256
|
+
npm run test:integration # real-site tests against the-internet.herokuapp.com
|
|
257
|
+
npm run test:all # all 121 tests
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
| Project | Tests | Coverage |
|
|
261
|
+
|---------|-------|----------|
|
|
262
|
+
| **core** | 41 | lifecycle, nav, click, type, press, eval, screenshot, pdf, cookies, frames, upload, auth, tabs, dialogs |
|
|
263
|
+
| **compat** | 46 | contenteditable (5 variants), Shadow DOM (3 levels), input types, scrollable, overlay, select |
|
|
264
|
+
| **network** | 19 | request monitoring, block, mock, headers, rule management |
|
|
265
|
+
| **integration** | 15 | checkboxes, dropdown, key presses, dynamic controls, Shadow DOM, nested frames, TinyMCE, large DOM |
|
|
266
|
+
|
|
223
267
|
## Requirements
|
|
224
268
|
|
|
225
|
-
- Chrome
|
|
269
|
+
- Chrome 144+ / Edge / Brave (any Chromium-based browser)
|
|
226
270
|
- Node.js >= 18
|
|
227
|
-
-
|
|
271
|
+
- Remote debugging enabled (`chrome://inspect/#remote-debugging`)
|
|
228
272
|
|
|
229
273
|
## License
|
|
230
274
|
|
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(() => {
|
|
@@ -760,6 +808,73 @@ program.command("type <ref> <text>").description("Type text into element and ret
|
|
|
760
808
|
emitSnapshot(await snap(transport, sessionId, state.activeTargetId, limit));
|
|
761
809
|
});
|
|
762
810
|
}));
|
|
811
|
+
async function typeViaKeyboard(t, sid, text) {
|
|
812
|
+
for (const char of text) {
|
|
813
|
+
if (char === "\n") {
|
|
814
|
+
await dispatchKey(t, sid, "Enter");
|
|
815
|
+
} else if (char === " ") {
|
|
816
|
+
await dispatchKey(t, sid, "Tab");
|
|
817
|
+
} else if (char.charCodeAt(0) >= 32 && char.charCodeAt(0) <= 126) {
|
|
818
|
+
const code = char === " " ? "Space" : `Key${char.toUpperCase()}`;
|
|
819
|
+
const keyCode = char.toUpperCase().charCodeAt(0);
|
|
820
|
+
await t.send("Input.dispatchKeyEvent", { type: "keyDown", key: char, code, windowsVirtualKeyCode: keyCode, text: char }, sid);
|
|
821
|
+
await t.send("Input.dispatchKeyEvent", { type: "keyUp", key: char, code, windowsVirtualKeyCode: keyCode }, sid);
|
|
822
|
+
} else {
|
|
823
|
+
await t.send("Input.insertText", { text: char }, sid);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
program.command("keyboard <text>").description("Type text via keyboard events (for canvas editors like Google Docs)").option("-c, --clear", "select all + delete before typing").option("-s, --submit", "press Enter after typing").option("-d, --delay <ms>", "delay between keystrokes in ms").option("--click <selector>", "click element by CSS selector first to focus it").option("-l, --limit <n>", "max elements in snapshot", "50").addHelpText("after", `
|
|
828
|
+
Unlike 'type', this does not target a specific element. It sends real
|
|
829
|
+
keyboard events to whatever is currently focused \u2014 works with canvas-based
|
|
830
|
+
editors (Google Docs, Google Sheets, Figma) that don't expose DOM inputs.
|
|
831
|
+
|
|
832
|
+
Use --click to focus an element before typing (sends a real CDP mouse click).
|
|
833
|
+
|
|
834
|
+
Examples:
|
|
835
|
+
bp keyboard "hello world"
|
|
836
|
+
bp keyboard "new content" --clear
|
|
837
|
+
bp keyboard "search query" --submit
|
|
838
|
+
bp keyboard "Hello Docs!" --click ".kix-appview-editor"
|
|
839
|
+
bp keyboard "slow typing" --delay 50`).action(action(async (text, opts) => {
|
|
840
|
+
const limit = parseLimit(opts.limit);
|
|
841
|
+
await withPilot(async ({ transport, sessionId, state }) => {
|
|
842
|
+
if (opts.click) {
|
|
843
|
+
const { result } = await transport.send("Runtime.evaluate", {
|
|
844
|
+
expression: `JSON.stringify((function(){var el=document.querySelector(${JSON.stringify(opts.click)});if(!el)return null;el.scrollIntoView({block:'center'});var r=el.getBoundingClientRect();return{x:r.x+r.width/2,y:r.y+r.height/2}})())`,
|
|
845
|
+
returnByValue: true
|
|
846
|
+
}, sessionId);
|
|
847
|
+
const coords = result.value ? JSON.parse(result.value) : null;
|
|
848
|
+
if (!coords) throw new Error(`Element not found: ${opts.click}`);
|
|
849
|
+
await dispatchClick(transport, sessionId, coords.x, coords.y);
|
|
850
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
851
|
+
}
|
|
852
|
+
if (opts.clear) {
|
|
853
|
+
await dispatchKey(transport, sessionId, "Meta+a");
|
|
854
|
+
await dispatchKey(transport, sessionId, "Delete");
|
|
855
|
+
}
|
|
856
|
+
if (opts.delay) {
|
|
857
|
+
const delay = parseInt(opts.delay, 10);
|
|
858
|
+
for (const char of text) {
|
|
859
|
+
if (char === "\n") {
|
|
860
|
+
await dispatchKey(transport, sessionId, "Enter");
|
|
861
|
+
} else if (char.charCodeAt(0) >= 32 && char.charCodeAt(0) <= 126) {
|
|
862
|
+
const code = char === " " ? "Space" : `Key${char.toUpperCase()}`;
|
|
863
|
+
const keyCode = char.toUpperCase().charCodeAt(0);
|
|
864
|
+
await transport.send("Input.dispatchKeyEvent", { type: "keyDown", key: char, code, windowsVirtualKeyCode: keyCode, text: char }, sessionId);
|
|
865
|
+
await transport.send("Input.dispatchKeyEvent", { type: "keyUp", key: char, code, windowsVirtualKeyCode: keyCode }, sessionId);
|
|
866
|
+
} else {
|
|
867
|
+
await transport.send("Input.insertText", { text: char }, sessionId);
|
|
868
|
+
}
|
|
869
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
870
|
+
}
|
|
871
|
+
} else {
|
|
872
|
+
await typeViaKeyboard(transport, sessionId, text);
|
|
873
|
+
}
|
|
874
|
+
if (opts.submit) await dispatchKey(transport, sessionId, "Enter");
|
|
875
|
+
emitSnapshot(await snap(transport, sessionId, state.activeTargetId, limit));
|
|
876
|
+
});
|
|
877
|
+
}));
|
|
763
878
|
program.command("press <key>").description("Press key combo (e.g. Enter, Escape, Control+a) and return snapshot").option("-l, --limit <n>", "max elements in snapshot", "50").addHelpText("after", "\nKeys: Enter, Escape, Tab, Space, Backspace, Delete,\n ArrowUp, ArrowDown, ArrowLeft, ArrowRight,\n Home, End, PageUp, PageDown\nModifiers: Control (Ctrl), Shift, Alt, Meta (Cmd)\n\nExamples:\n bp press Enter\n bp press Escape\n bp press Control+a\n bp press Meta+c").action(action(async (key, opts) => {
|
|
764
879
|
const limit = parseLimit(opts.limit);
|
|
765
880
|
await withPilot(async ({ transport, sessionId, state }) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "browser-pilot-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
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",
|