pi-browser-harness 0.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/CHANGELOG.md +19 -0
- package/LICENSE +21 -0
- package/README.md +212 -0
- package/assets/hero.png +0 -0
- package/package.json +68 -0
- package/skills/pi-browser-harness/SKILL.md +213 -0
- package/src/daemon.ts +767 -0
- package/src/index.ts +223 -0
- package/src/prompt.ts +106 -0
- package/src/protocol.ts +77 -0
- package/src/renderers.ts +21 -0
- package/src/setup.ts +97 -0
- package/src/state.ts +77 -0
- package/src/tools.ts +1358 -0
- package/tsconfig.json +18 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to pi-browser-harness will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [0.1.0] - 2026-05-02
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Initial release of pi-browser-harness.
|
|
9
|
+
- 20 browser control tools (`browser_navigate`, `browser_screenshot`, `browser_click`, `browser_type`, `browser_press_key`, `browser_scroll`, `browser_execute_js`, `browser_http_get`, `browser_new_tab`, `browser_open_urls`, `browser_switch_tab`, `browser_list_tabs`, `browser_current_tab`, `browser_page_info`, `browser_go_back`, `browser_go_forward`, `browser_reload`, `browser_wait`, `browser_wait_for_load`, `browser_handle_dialog`).
|
|
10
|
+
- Self-extending harness: `list_dynamic_tools`, `register_tool`, `remove_tool` — the agent can write new browser tools at runtime.
|
|
11
|
+
- Guided setup command (`/browser-setup`) with Chrome detection, automatic browser-harness installation via `uv` or `git clone`.
|
|
12
|
+
- `/browser-status` and `/browser-reload-daemon` commands for daemon health monitoring.
|
|
13
|
+
- `--browser-namespace` and `--browser-debug-clicks` CLI flags.
|
|
14
|
+
- Session persistence for tab history and daemon namespace across reloads and branch navigation.
|
|
15
|
+
- System prompt injection with browser usage guidance and common workflow patterns.
|
|
16
|
+
- Custom TUI renderers for screenshots and tab listings.
|
|
17
|
+
- Dialog detection and handling for JS `alert`/`confirm`/`prompt`/`beforeunload`.
|
|
18
|
+
- Parallel URL opening via `browser_open_urls` with live progress streaming.
|
|
19
|
+
- Output truncation with temp-file fallback for large JS evaluation and HTTP responses.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Pi Browser Harness Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# pi-browser-harness
|
|
2
|
+
|
|
3
|
+
Full browser control for pi agents. Navigate, click, type, screenshot, and extract data from your real Chrome — driven by your coding agent.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# 1. Install
|
|
11
|
+
pi install npm:pi-browser-harness
|
|
12
|
+
|
|
13
|
+
# Or install from GitHub:
|
|
14
|
+
# pi install git:github.com/amankumarsingh77/pi-browser-harness
|
|
15
|
+
|
|
16
|
+
# 2. Enable Chrome remote debugging
|
|
17
|
+
# Open chrome://inspect/#remote-debugging in Chrome,
|
|
18
|
+
# tick the "Discover network targets" checkbox, click Allow.
|
|
19
|
+
# No Chrome relaunch needed.
|
|
20
|
+
|
|
21
|
+
# 3. Connect pi to Chrome
|
|
22
|
+
/browser-setup
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
That's it. Try:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
> navigate to https://github.com and take a screenshot
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Requirements
|
|
32
|
+
|
|
33
|
+
- pi (latest)
|
|
34
|
+
- Node.js ≥ 22
|
|
35
|
+
- Chrome / Chromium / Edge (any recent version)
|
|
36
|
+
|
|
37
|
+
> If `chrome://inspect/#remote-debugging` isn't available, you can launch Chrome with `--remote-debugging-port=9222` instead. The harness will find it either way.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## What you can do
|
|
42
|
+
|
|
43
|
+
### Navigation & tabs
|
|
44
|
+
|
|
45
|
+
| Tool | What it does |
|
|
46
|
+
|------|--------------|
|
|
47
|
+
| `browser_navigate` | Go to a URL (reuses the attached tab) |
|
|
48
|
+
| `browser_new_tab` | Open a new tab, optionally navigate |
|
|
49
|
+
| `browser_open_urls` | Open many URLs in parallel tabs |
|
|
50
|
+
| `browser_go_back` / `browser_go_forward` | History navigation |
|
|
51
|
+
| `browser_reload` | Reload the current page |
|
|
52
|
+
| `browser_list_tabs` / `browser_current_tab` | Discover tabs |
|
|
53
|
+
| `browser_switch_tab` | Switch the active tab |
|
|
54
|
+
| `browser_page_info` | URL, title, viewport, scroll, dialog state |
|
|
55
|
+
|
|
56
|
+
### Interaction
|
|
57
|
+
|
|
58
|
+
| Tool | What it does |
|
|
59
|
+
|------|--------------|
|
|
60
|
+
| `browser_click` | Click at viewport coordinates (works through iframes / shadow DOM) |
|
|
61
|
+
| `browser_type` | Type into the focused element |
|
|
62
|
+
| `browser_press_key` | Send a key with optional modifiers |
|
|
63
|
+
| `browser_scroll` | Scroll by delta pixels |
|
|
64
|
+
| `browser_handle_dialog` | Accept or dismiss `alert` / `confirm` / `prompt` |
|
|
65
|
+
|
|
66
|
+
### Visual & data
|
|
67
|
+
|
|
68
|
+
| Tool | What it does |
|
|
69
|
+
|------|--------------|
|
|
70
|
+
| `browser_screenshot` | Capture the current page as PNG |
|
|
71
|
+
| `browser_execute_js` | Run JS in the page and return the result |
|
|
72
|
+
| `browser_http_get` | Direct HTTP GET — bypasses the browser, much faster for APIs |
|
|
73
|
+
| `browser_wait` / `browser_wait_for_load` | Sleep, or wait for `readyState === 'complete'` |
|
|
74
|
+
|
|
75
|
+
### Extending the harness at runtime
|
|
76
|
+
|
|
77
|
+
| Tool | What it does |
|
|
78
|
+
|------|--------------|
|
|
79
|
+
| `browser_run_script` | Execute a temporary script file with daemon access |
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Usage
|
|
84
|
+
|
|
85
|
+
The core pattern is **screenshot → act → screenshot → verify.** Don't assume an action worked — look.
|
|
86
|
+
|
|
87
|
+
### Navigate and screenshot
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
browser_navigate("https://github.com/browser-use/browser-harness")
|
|
91
|
+
browser_wait_for_load()
|
|
92
|
+
browser_screenshot()
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Fill a form
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
browser_screenshot() # see the form
|
|
99
|
+
browser_click(400, 200) # click the search input
|
|
100
|
+
browser_type("pi browser harness") # type
|
|
101
|
+
browser_press_key("Enter") # submit
|
|
102
|
+
browser_wait_for_load()
|
|
103
|
+
browser_screenshot() # verify
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Extract data
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
// Fast: hit the API directly, no browser
|
|
110
|
+
browser_http_get("https://api.github.com/repos/browser-use/browser-harness")
|
|
111
|
+
|
|
112
|
+
// DOM scraping for rendered content
|
|
113
|
+
browser_execute_js("document.querySelector('.price').innerText")
|
|
114
|
+
|
|
115
|
+
// Structured arrays
|
|
116
|
+
browser_execute_js(`
|
|
117
|
+
JSON.stringify(
|
|
118
|
+
Array.from(document.querySelectorAll('.result')).map(el => ({
|
|
119
|
+
title: el.querySelector('h3')?.textContent,
|
|
120
|
+
link: el.querySelector('a')?.href,
|
|
121
|
+
}))
|
|
122
|
+
)
|
|
123
|
+
`)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Open multiple results in parallel
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
browser_navigate("https://google.com/search?q=chrome+devtools+protocol")
|
|
130
|
+
browser_open_urls({ urls: [
|
|
131
|
+
"https://chromedevtools.github.io/devtools-protocol/",
|
|
132
|
+
"https://developer.chrome.com/docs/devtools",
|
|
133
|
+
]})
|
|
134
|
+
browser_list_tabs()
|
|
135
|
+
browser_switch_tab({ targetId: "..." })
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### SPAs
|
|
139
|
+
|
|
140
|
+
`browser_wait_for_load` only checks `readyState`. For React/Vue/etc., poll for the actual element you want:
|
|
141
|
+
|
|
142
|
+
```js
|
|
143
|
+
browser_execute_js("!!document.querySelector('.loaded-content')")
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Keyboard modifiers
|
|
147
|
+
|
|
148
|
+
`browser_press_key` takes a modifier bitfield: `Alt=1`, `Ctrl=2`, `Meta/Cmd=4`, `Shift=8`. Combine with `|`.
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
browser_press_key("c", { modifiers: 2 }) // Ctrl+C
|
|
152
|
+
browser_press_key("v", { modifiers: 4 }) // Cmd+V
|
|
153
|
+
browser_press_key("T", { modifiers: 10 }) // Ctrl+Shift+T
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Writing a temporary script
|
|
157
|
+
|
|
158
|
+
When the built-ins aren't enough, write a script to disk and run it with `browser_run_script`.
|
|
159
|
+
|
|
160
|
+
```js
|
|
161
|
+
// 1. Write the script to disk
|
|
162
|
+
write("/tmp/scrape-results.js", `
|
|
163
|
+
const data = await daemon.evaluateJS(\`
|
|
164
|
+
JSON.stringify(
|
|
165
|
+
Array.from(document.querySelectorAll('${params.selector}'))
|
|
166
|
+
.map(el => el.textContent.trim())
|
|
167
|
+
)
|
|
168
|
+
\`);
|
|
169
|
+
return {
|
|
170
|
+
content: [{ type: "text", text: data }],
|
|
171
|
+
details: { items: JSON.parse(data) },
|
|
172
|
+
};
|
|
173
|
+
`)
|
|
174
|
+
|
|
175
|
+
// 2. Execute it
|
|
176
|
+
browser_run_script("/tmp/scrape-results.js", { selector: ".result" })
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Scripts receive `params`, `daemon`, `require`, `signal`, `onUpdate`, `ctx`, plus the standard JS globals. The script is on disk — auditable and re-runnable.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Commands
|
|
184
|
+
|
|
185
|
+
| Command | Description |
|
|
186
|
+
|---------|-------------|
|
|
187
|
+
| `/browser-setup` | Connect pi to Chrome (run once) |
|
|
188
|
+
| `/browser-status` | Show daemon health and current page |
|
|
189
|
+
| `/browser-reload-daemon` | Restart the connection |
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Troubleshooting
|
|
194
|
+
|
|
195
|
+
| Symptom | Fix |
|
|
196
|
+
|---------|-----|
|
|
197
|
+
| `DevToolsActivePort not found` | Open `chrome://inspect/#remote-debugging`, tick the checkbox, click Allow, then retry. |
|
|
198
|
+
| Connection fails after Chrome restart | Run `/browser-reload-daemon`. |
|
|
199
|
+
| Page seems loaded but content is missing | SPA — `browser_wait_for_load` isn't enough. Poll with `browser_execute_js` for the element you need. |
|
|
200
|
+
| A JS dialog is blocking actions | `browser_page_info` will report the dialog. Use `browser_handle_dialog` to accept or dismiss. |
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Security
|
|
205
|
+
|
|
206
|
+
This extension drives your real Chrome. The agent can see open tabs, read page content, submit forms, and act inside authenticated sessions. `browser_run_script` evaluates JavaScript in the pi process with full `require` access — review any temporary scripts the agent writes to `/tmp/` before executing them.
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## License
|
|
211
|
+
|
|
212
|
+
MIT — see [LICENSE](LICENSE).
|
package/assets/hero.png
ADDED
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-browser-harness",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Browser control extension for pi — navigate, click, type, screenshot, and extract data from real Chrome via CDP",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pi-package",
|
|
7
|
+
"pi-extension",
|
|
8
|
+
"browser",
|
|
9
|
+
"cdp",
|
|
10
|
+
"chrome",
|
|
11
|
+
"web-automation",
|
|
12
|
+
"screenshot",
|
|
13
|
+
"scraping"
|
|
14
|
+
],
|
|
15
|
+
"author": "Pi Browser Harness Contributors",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/amankumarsingh77/pi-browser-harness.git"
|
|
20
|
+
},
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/amankumarsingh77/pi-browser-harness/issues"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/amankumarsingh77/pi-browser-harness#readme",
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=22.0.0"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"src/",
|
|
30
|
+
"skills/",
|
|
31
|
+
"assets/",
|
|
32
|
+
"tsconfig.json",
|
|
33
|
+
"LICENSE",
|
|
34
|
+
"README.md",
|
|
35
|
+
"CHANGELOG.md"
|
|
36
|
+
],
|
|
37
|
+
"pi": {
|
|
38
|
+
"extensions": [
|
|
39
|
+
"./src/index.ts"
|
|
40
|
+
],
|
|
41
|
+
"skills": [
|
|
42
|
+
"./skills"
|
|
43
|
+
],
|
|
44
|
+
"image": "https://raw.githubusercontent.com/amankumarsingh77/pi-browser-harness/main/assets/hero.png"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"@mariozechner/pi-coding-agent": "*",
|
|
48
|
+
"@mariozechner/pi-tui": "*",
|
|
49
|
+
"typebox": "*"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@mariozechner/pi-coding-agent": "*",
|
|
53
|
+
"@mariozechner/pi-tui": "*",
|
|
54
|
+
"@types/ws": "^8.18.1",
|
|
55
|
+
"typebox": "*",
|
|
56
|
+
"typescript": "^5.0.0"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"typecheck": "tsc --noEmit",
|
|
60
|
+
"prepublishOnly": "tsc --noEmit"
|
|
61
|
+
},
|
|
62
|
+
"publishConfig": {
|
|
63
|
+
"access": "public"
|
|
64
|
+
},
|
|
65
|
+
"dependencies": {
|
|
66
|
+
"ws": "^8.20.0"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pi-browser-harness
|
|
3
|
+
description: Direct browser control via CDP. Use when the user wants to automate, scrape, test, or interact with web pages. Connects to the user's already-running Chrome.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# pi-browser-harness
|
|
7
|
+
|
|
8
|
+
Direct browser control via CDP. Gives pi agents full control of a real Chrome browser.
|
|
9
|
+
Browser tools coexist with all standard pi tools (read, bash, edit, write, etc.).
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
Run `/browser-setup` to install browser-harness and connect to Chrome. Chrome must be running
|
|
14
|
+
with remote debugging enabled. Start Chrome with:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
--remote-debugging-port=9222
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Check status with `/browser-status`. Restart the daemon with `/browser-reload-daemon`.
|
|
21
|
+
|
|
22
|
+
## Core Workflow
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
browser_screenshot() → browser_click(x, y) → browser_screenshot()
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
1. **Screenshot first** — see what's on screen before any interaction
|
|
29
|
+
2. **Coordinate click** — read the target pixel from the screenshot, click at (x, y)
|
|
30
|
+
3. **Screenshot again** — verify the action worked
|
|
31
|
+
|
|
32
|
+
Compositor-level clicks (`Input.dispatchMouseEvent`) work through iframes, shadow DOM,
|
|
33
|
+
and cross-origin content. No selectors needed.
|
|
34
|
+
|
|
35
|
+
## Available Tools
|
|
36
|
+
|
|
37
|
+
### Navigation
|
|
38
|
+
- `browser_navigate` — navigate to a URL (creates new tab on first call, reuses current tab after)
|
|
39
|
+
- `browser_new_tab` — open a new tab, optionally navigate (use for first navigation)
|
|
40
|
+
- `browser_open_urls` — open multiple URLs in parallel tabs (use after search to open result links)
|
|
41
|
+
- `browser_go_back` / `browser_go_forward` / `browser_reload` — history navigation
|
|
42
|
+
- `browser_page_info` — get URL, title, viewport, scroll position, or dialog info
|
|
43
|
+
- `browser_list_tabs` / `browser_current_tab` / `browser_switch_tab` — tab management
|
|
44
|
+
|
|
45
|
+
### Interaction
|
|
46
|
+
- `browser_click` — click at viewport CSS-pixel coordinates
|
|
47
|
+
- `browser_type` — type text into focused element
|
|
48
|
+
- `browser_press_key` — press a key (Enter, Tab, Escape, arrows, etc.)
|
|
49
|
+
- `browser_scroll` — scroll the page at coordinates
|
|
50
|
+
|
|
51
|
+
### Visual
|
|
52
|
+
- `browser_screenshot` — capture PNG screenshot
|
|
53
|
+
|
|
54
|
+
### Data Extraction
|
|
55
|
+
- `browser_execute_js` — execute JavaScript and return result
|
|
56
|
+
- `browser_http_get` — direct HTTP GET outside browser (10-50x faster for APIs)
|
|
57
|
+
|
|
58
|
+
### Utility
|
|
59
|
+
- `browser_wait` — wait N seconds
|
|
60
|
+
- `browser_wait_for_load` — wait for document.readyState === 'complete'
|
|
61
|
+
- `browser_handle_dialog` — accept or dismiss JS dialogs (alert/confirm/prompt)
|
|
62
|
+
|
|
63
|
+
### Extending
|
|
64
|
+
- `browser_run_script` — execute a temporary script file with daemon access
|
|
65
|
+
|
|
66
|
+
## Pattern Reference
|
|
67
|
+
|
|
68
|
+
### Navigation
|
|
69
|
+
```
|
|
70
|
+
browser_new_tab("https://example.com") → browser_wait_for_load() → browser_screenshot()
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Form Filling
|
|
74
|
+
```
|
|
75
|
+
browser_screenshot() → find input coordinates
|
|
76
|
+
→ browser_click(x, y) → browser_type("text")
|
|
77
|
+
→ browser_press_key("Tab") → browser_screenshot()
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Data Extraction
|
|
81
|
+
```
|
|
82
|
+
browser_execute_js("document.querySelector('.price').innerText")
|
|
83
|
+
// or for APIs directly:
|
|
84
|
+
browser_http_get("https://api.example.com/data")
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Scrolling
|
|
88
|
+
```
|
|
89
|
+
browser_screenshot() → browser_scroll({ deltaY: -500 }) → browser_screenshot()
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Research (search engine + browser_open_urls)
|
|
93
|
+
```
|
|
94
|
+
browser_new_tab("https://google.com/search?q=query") → search engine results
|
|
95
|
+
browser_open_urls({urls: ["url1", "url2"]}) → open result pages in parallel tabs
|
|
96
|
+
browser_list_tabs() → see all open tabs with targetIds
|
|
97
|
+
browser_switch_tab({targetId: "..."}) → switch to a specific tab
|
|
98
|
+
browser_wait_for_load() → wait for the page to render
|
|
99
|
+
browser_screenshot() → visually inspect the page
|
|
100
|
+
browser_execute_js("document.querySelector('.content').innerText") → extract text
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Temporary Scripts
|
|
104
|
+
|
|
105
|
+
When the built-in tools aren't enough, write a temporary script to disk and execute it
|
|
106
|
+
with `browser_run_script`. Scripts run in the harness process with direct access to
|
|
107
|
+
the browser daemon and full Node.js APIs — no dynamic tool registration needed.
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
● agent: needs to scrape 47 paginated search result pages
|
|
111
|
+
│
|
|
112
|
+
● write("/tmp/scrape-pages.js", "...script using daemon.cdp() and daemon.evaluateJS()...")
|
|
113
|
+
│
|
|
114
|
+
● browser_run_script("/tmp/scrape-pages.js", { urls: [...] })
|
|
115
|
+
│
|
|
116
|
+
✓ script executes and returns results
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The script is written to disk, so it's auditable and re-runnable.
|
|
120
|
+
|
|
121
|
+
**When to use scripts:**
|
|
122
|
+
- You're about to do the same 3+ step sequence more than once
|
|
123
|
+
- You need a capability the built-in tools don't provide (e.g., bulk CSV export, structured extraction, pagination)
|
|
124
|
+
- You discover a domain-specific pattern that should be reusable
|
|
125
|
+
- The alternative is a fragile multi-turn loop of screenshots + clicks
|
|
126
|
+
|
|
127
|
+
**What NOT to do:**
|
|
128
|
+
- Don't write scripts for one-off actions — use the built-in browser_* tools directly.
|
|
129
|
+
- Don't try to call browser_* tools from within a script — sequence them as separate tool calls.
|
|
130
|
+
|
|
131
|
+
**Script bindings:**
|
|
132
|
+
- `params` — the arguments passed to browser_run_script
|
|
133
|
+
- `daemon` — browser daemon (daemon.cdp(), daemon.evaluateJS(), daemon.getPageInfo(), etc.)
|
|
134
|
+
- `require` — Node.js require() (use 'fs', 'path', 'crypto', etc.)
|
|
135
|
+
- `signal` — AbortSignal for cancellation
|
|
136
|
+
- `onUpdate` — progress callback: onUpdate({ content: [{ type: 'text', text: '...' }] })
|
|
137
|
+
- `ctx` — ExtensionContext (ctx.cwd, ctx.signal, etc.)
|
|
138
|
+
- `console`, `fetch`, `JSON`, `Buffer`, `setTimeout`, `clearTimeout`
|
|
139
|
+
|
|
140
|
+
**Example — a script that extracts structured data:**
|
|
141
|
+
```javascript
|
|
142
|
+
// /tmp/extract-products.js
|
|
143
|
+
const info = await daemon.getPageInfo();
|
|
144
|
+
if ("dialog" in info) throw new Error("Dialog is blocking: " + info.dialog.message);
|
|
145
|
+
const data = await daemon.evaluateJS(`
|
|
146
|
+
JSON.stringify(Array.from(document.querySelectorAll('${params.rowSelector}')).map(el => ({
|
|
147
|
+
title: el.querySelector('${params.titleSelector}')?.textContent?.trim(),
|
|
148
|
+
price: el.querySelector('${params.priceSelector}')?.textContent?.trim(),
|
|
149
|
+
})))
|
|
150
|
+
`);
|
|
151
|
+
return { content: [{ type: 'text', text: data }], details: { raw: JSON.parse(data) } };
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## What NOT To Do
|
|
155
|
+
|
|
156
|
+
- **Don't launch your own browser** — you're connected to the user's real Chrome
|
|
157
|
+
- **Don't type credentials** — if you hit an auth wall, stop and ask the user
|
|
158
|
+
- **Don't assume page state** — screenshot to confirm
|
|
159
|
+
- **Don't write pixel coordinates into documentation** — describe how to locate targets (selectors, text, aria-label)
|
|
160
|
+
- **Don't use browser_navigate for first navigation** — use browser_new_tab to explicitly preserve the user's active tab (browser_navigate auto-creates a new tab on first call, but explicit is clearer)
|
|
161
|
+
- **Don't ignore dialogs** — if browser_page_info returns a dialog, handle it first
|
|
162
|
+
|
|
163
|
+
## Dialogs
|
|
164
|
+
|
|
165
|
+
JS dialogs (alert, confirm, prompt, beforeunload) freeze the page's JS thread.
|
|
166
|
+
If browser_page_info returns a dialog, handle it with browser_handle_dialog
|
|
167
|
+
before any other browser action.
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
// Check page_info first — if it shows a dialog:
|
|
171
|
+
browser_handle_dialog({ action: "accept" }) // accept/confirm
|
|
172
|
+
browser_handle_dialog({ action: "dismiss" }) // dismiss/cancel
|
|
173
|
+
browser_handle_dialog({ action: "accept", promptText: "hello" }) // prompt with text
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## SPAs (React, Vue, etc.)
|
|
177
|
+
|
|
178
|
+
`browser_wait_for_load()` only checks `document.readyState === "complete"`.
|
|
179
|
+
Single-page apps often paint content after this. After wait_for_load, use
|
|
180
|
+
`browser_execute_js` to check for specific elements:
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
browser_execute_js("!!document.querySelector('.loaded-content')")
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Keyboard Modifiers
|
|
187
|
+
|
|
188
|
+
`browser_press_key` supports modifier bitfield:
|
|
189
|
+
- 1 = Alt, 2 = Ctrl, 4 = Meta/Cmd, 8 = Shift
|
|
190
|
+
- Combine: Ctrl+Shift = 2|8 = 10, Cmd+Shift = 4|8 = 12
|
|
191
|
+
|
|
192
|
+
## Troubleshooting
|
|
193
|
+
|
|
194
|
+
- **Daemon not starting**: Run `/browser-setup` to re-run the guided setup
|
|
195
|
+
- **"DevToolsActivePort not found"**: Open chrome://inspect/#remote-debugging, tick checkbox, click Allow
|
|
196
|
+
- **Stale session**: Run `/browser-reload-daemon` to restart the daemon
|
|
197
|
+
- **Dialog blocking**: Run browser_page_info to detect it, then browser_handle_dialog
|
|
198
|
+
- **Status check**: Run `/browser-status` to see daemon health and current page
|
|
199
|
+
|
|
200
|
+
## Architecture
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
pi agent → pi-browser-harness (TypeScript)
|
|
204
|
+
│ CDP WebSocket
|
|
205
|
+
▼
|
|
206
|
+
Chrome
|
|
207
|
+
|
|
208
|
+
Temporary scripts:
|
|
209
|
+
write("/tmp/script.js") → browser_run_script("/tmp/script.js")
|
|
210
|
+
│ │
|
|
211
|
+
▼ ▼
|
|
212
|
+
script on disk (auditable) executed in harness process with daemon access
|
|
213
|
+
```
|