pi-browser-harness 0.3.2 → 0.5.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 CHANGED
@@ -2,6 +2,51 @@
2
2
 
3
3
  All notable changes to pi-browser-harness will be documented in this file.
4
4
 
5
+ ## 0.5.0 — 2026-05-07
6
+
7
+ ### Added
8
+
9
+ - **`browser_console`** — new tool. Reads JS errors and console messages from the active tab via two CDP sources merged into one buffer: `Runtime.consoleAPICalled` (page `console.*` calls and uncaught exceptions) and `Log.entryAdded` (browser-level entries — CSP violations, mixed content, deprecations, network errors). Filters: `levels` (log/info/warn/error/debug), `textPattern` (substring; wrap in slashes for regex), `sinceMs`, `limit` (default 50, cap 500). Each record carries a monotonic `seq`; the response includes `nextCursor` so callers can pass `sinceSeq` to see only what's new since the previous drain — the cursor pattern that makes "what did this action cause?" answerable in one call. Buffer is page-scoped (cleared on tab switch) and bounded at 500 records; `bufferOverflowed` flag reports drops since the last drain. Stack traces (top 3 frames) are preserved for error/warn records. Per-arg cap of 2 KB prevents a single `console.log(hugeBlob)` from blowing the buffer.
10
+ - **`Log` CDP domain enabled** alongside Page/DOM/Runtime/Network/Accessibility on every attach. (`Runtime` was already enabled, so `Runtime.consoleAPICalled` was reachable; this adds the missing domain for browser-level entries.)
11
+ - **`browser_console` ships with a custom `renderResult`** following the established pattern — collapsed: header counts (`3 errors · 2 warnings · 47 logs`) + last 5 rows. Expanded: full list with stack traces inline as code fences. Appends `keyHint("app.tools.expand", ...)` so the binding label adapts to user remaps.
12
+
13
+ ### Internal
14
+
15
+ - New pure module `src/cdp/console-buffer.ts` mirrors the `network-buffer.ts` pattern (insertion-ordered Map, FIFO eviction at capacity 500, overflow flag reset per drain). Wired into the existing single-consumer event loop in `src/cdp/session.ts`.
16
+
17
+ ### Known follow-ups
18
+
19
+ - No tests added in this release. Verification is manual against real Chrome.
20
+
21
+ ## 0.4.0 — 2026-05-06
22
+
23
+ ### Added
24
+
25
+ - **`browser_snapshot`** — new tool. Returns the structured CDP accessibility tree (roles, names, states, hierarchy) for the current page. For every interactive element (button, link, textbox, checkbox, etc.) the outline includes click coordinates as `@(x,y)`, fetched via `DOM.getBoxModel` per node in parallel under a 1.5s aggregate budget. Pass these straight to `browser_click` — no `browser_screenshot` round-trip needed. `format:"json"` returns the slim structure with `box: {x,y,width,height,cx,cy}` per node. Optional `includeScreenshot:true` attaches a JPEG (q=80) when visual confirmation is also wanted.
26
+ - **`browser_network_requests`** — new tool, replacing the deprecated `browser_get_network_log` placeholder. Lists requests captured on the current tab since attach with filters: `urlPattern` (substring; wrap in slashes for regex), `methodFilter`, `statusFilter`, `resourceTypes`, `sinceMs`, `limit` (default 50, cap 500). `includeResponseBodies:true` fetches `Network.getResponseBody` per matched record under a 5s aggregate budget with a 50 KB per-body cap. Buffer is page-scoped (cleared on tab switch) and bounded at 500 records; `bufferOverflowed` flag in the result reports drops since the last drain.
27
+ - **Tab ownership / harness-window isolation.** New `OwnershipRegistry` tracks which page targets this session opened. The harness now creates a dedicated Chrome window on first attach (`newWindow:true`) instead of grabbing the user's foreground tab; subsequent `browser_new_tab` calls open inside that window via `openerId`. `browser_list_tabs` defaults to `scope:"owned"`; pass `scope:"all"` to see the user's other tabs read-only. `browser_switch_tab` and the new **`browser_close_tab`** refuse non-owned tabs with a clear remediation hint. Ownership is persisted across session reloads via `BrowserState`. The session also subscribes `Target.setDiscoverTargets` and reaps `targetDestroyed` events so the registry stays in sync.
28
+ - **`Accessibility` CDP domain enabled** alongside Page/DOM/Runtime/Network on every attach.
29
+ - **`Ctrl+O` (`app.tools.expand`) expand/collapse on tool output.** Three tools now ship custom `renderResult`:
30
+ - `browser_snapshot` — collapsed: 4-line summary (node count, URL, landmarks/buttons/inputs, screenshot status). Expanded: full indented outline + inline screenshot when `includeScreenshot:true`.
31
+ - `browser_network_requests` — collapsed: header + first 5 rows. Expanded: full markdown table + per-request body sections when bodies were requested.
32
+ - `browser_execute_js` — collapsed: size + first 120-char preview. Expanded: pretty-printed JSON if value parses, otherwise raw value, in a code fence.
33
+ All three append a `keyHint("app.tools.expand", ...)` so the binding label adapts to user remaps.
34
+
35
+ ### Changed
36
+
37
+ - **Tool prompts pivoted from screenshot-first to snapshot-first.** `browser_screenshot`'s description now explicitly says "NOT a default exploration tool". `browser_snapshot` is documented as the default for understanding pages; `browser_execute_js` as the default for surgical reads. `browser_click` guidance no longer instructs the agent to screenshot for coordinates — it points at `browser_snapshot`'s `@(x,y)` hints. `browser_open_urls` post-step flipped from screenshot to snapshot.
38
+ - **`SKILL.md` rewritten** (~342 lines → ~53 lines). Frontmatter description carries the tool-hierarchy hint (always in context per pi's progressive-disclosure model). Body keeps only what isn't already in tool prompts: the decision tree, the connection rules (real Chrome, no creds, dialog-first), and the `browser_run_script` daemon bindings (the only tool whose API can't be inferred from its prompt). Pattern reference, parallelization details, troubleshooting, and tool enumeration removed as duplication.
39
+ - **CDP `Network.*` events** are now consumed by an in-process aggregator (`src/cdp/network-buffer.ts`) wired into the existing single-consumer event loop in `src/cdp/session.ts`. Pure module, ring-buffered, non-destructive drain.
40
+
41
+ ### Removed
42
+
43
+ - **`browser_get_network_log`** — was a deprecated placeholder in v0.3 that returned a "use PerformanceObserver" message because the CDP event stream had no public drain API. Replaced by `browser_network_requests`.
44
+
45
+ ### Known follow-ups
46
+
47
+ - Live network streaming (`browser_network_monitor` real-time during a click) — deferred; reuses the same buffer plumbing once the post-hoc form is proven.
48
+ - No tests added in this release. Verification is manual against real Chrome.
49
+
5
50
  ## 0.3.2 — 2026-05-05
6
51
 
7
52
  ### Added
package/README.md CHANGED
@@ -2,7 +2,29 @@
2
2
 
3
3
  ![pi-browser-harness](https://raw.githubusercontent.com/amankumarsingh77/pi-browser-harness/main/assets/hero.png)
4
4
 
5
- Full browser control for pi agents. Navigate, click, type, screenshot, and extract data from your real Chrome — all driven through natural language via CDP.
5
+ Full browser control for pi agents in **your real Chrome** — your sessions, your cookies, your tabs. Drives navigation, structured page reads, network capture, clicks, typing, screenshots, and arbitrary scripts via CDP.
6
+
7
+ ---
8
+
9
+ ## Why pi-browser-harness?
10
+
11
+ | Capability | pi-browser-harness | Playwright MCP | Stagehand | Puppeteer MCP |
12
+ |---|:---:|:---:|:---:|:---:|
13
+ | **Drives your real Chrome** (logged-in sessions preserved) | ✅ | ❌ launches its own browser | ❌ | ❌ |
14
+ | **Coordinate clicks** that work through iframes, shadow DOM, cross-origin | ✅ | ❌ selector-based | ❌ selector-based | ❌ selector-based |
15
+ | **Inline TUI screenshot rendering** (Kitty/iTerm2/Ghostty/WezTerm) | ✅ | ❌ | ❌ | ❌ |
16
+ | **Accessibility-tree snapshot with click coords `@(x,y)` per element** | ✅ | ✅ tree only, no coords | partial | ❌ |
17
+ | **Network request capture** with filters + body capture | ✅ | ✅ post-hoc list | ❌ | ❌ |
18
+ | **Parallel tool execution** with automatic mutation serialization | ✅ | ❌ | ❌ | ❌ |
19
+ | **Temporary scripts** with daemon + full Node.js | ✅ | ❌ | ❌ | ❌ |
20
+ | **Direct HTTP GET** outside the browser (10–50× faster for APIs) | ✅ | ❌ | ❌ | ❌ |
21
+ | **Tab ownership isolation** — never touches the user's other tabs | ✅ | N/A | N/A | N/A |
22
+ | **Pi-native** — no MCP/JSON-RPC overhead, no extra LLM API keys | ✅ | ❌ MCP roundtrip | ❌ external LLM | ❌ MCP roundtrip |
23
+ | **TypeScript strict mode**, zero `any`, all CDP casts documented | ✅ | unknown | unknown | unknown |
24
+ | Ctrl+O expand/collapse on tool output | ✅ | ❌ | ❌ | ❌ |
25
+ | Compositor-level dispatch (works on every site, no flakey waits) | ✅ | ❌ | ❌ | ❌ |
26
+
27
+ If you live in pi and you want an agent driving the same Chrome you're already signed into, this is the only one that fits.
6
28
 
7
29
  ---
8
30
 
@@ -29,84 +51,144 @@ pi install npm:pi-browser-harness
29
51
 
30
52
  ---
31
53
 
54
+ ## Tool hierarchy — read this first
55
+
56
+ ```
57
+ What do you need to know?
58
+
59
+ ├─ Page structure / what's clickable / labels?
60
+ │ → browser_snapshot (DEFAULT — AX tree with @(x,y) per interactive element)
61
+ │
62
+ ├─ A specific element's value / attribute / coords?
63
+ │ → browser_execute_js (e.g. el.innerText, el.getBoundingClientRect())
64
+ │
65
+ ├─ Network behavior on the current page?
66
+ │ → browser_network_requests
67
+ │
68
+ └─ Visual rendering (layout / colors / chart drew correctly)?
69
+ → browser_screenshot (LAST RESORT — pixels only)
70
+ ```
71
+
72
+ Pass `@(x,y)` from `browser_snapshot` straight to `browser_click`. **No screenshot round-trip needed** to find click targets — the snapshot already has them.
73
+
74
+ ---
75
+
32
76
  ## Tools
33
77
 
78
+ ### Page inspection (use these by default)
79
+
34
80
  | Tool | Purpose |
35
81
  |------|---------|
36
- | `browser_screenshot` | Capture a PNG screenshot of the current page |
37
- | `browser_click` | Click at viewport coordinates |
38
- | `browser_type` | Type text into the focused element |
39
- | `browser_press_key` | Press a key (Enter, Tab, Escape, arrows, etc.) with optional modifiers |
40
- | `browser_scroll` | Scroll the page by delta pixels |
41
- | `browser_navigate` | Navigate to a URL |
42
- | `browser_new_tab` | Open a new tab, optionally navigate |
82
+ | `browser_snapshot` | **Default for page inspection.** Returns the CDP accessibility tree with click coords `@(x,y)` for every interactive element. Optional `includeScreenshot:true`. |
83
+ | `browser_execute_js` | Surgical DOM reads — element text, attributes, `getBoundingClientRect()`. Cheapest, most precise. |
84
+ | `browser_network_requests` | List recent network requests on the current tab. Filter by url/method/status/type/recency; optional response-body capture. |
85
+ | `browser_page_info` | URL, title, viewport, scroll position, or pending dialog. |
86
+ | `browser_http_get` | Direct HTTP GET outside the browser — 10-50× faster for APIs. |
87
+
88
+ ### Visual (last resort)
89
+
90
+ | Tool | Purpose |
91
+ |------|---------|
92
+ | `browser_screenshot` | Capture PNG/JPEG. Use only when you need to verify visual rendering. |
93
+
94
+ ### Navigation
95
+
96
+ | Tool | Purpose |
97
+ |------|---------|
98
+ | `browser_navigate` / `browser_new_tab` | Navigate or open a tab |
43
99
  | `browser_open_urls` | Open multiple URLs in parallel tabs |
44
100
  | `browser_go_back` / `browser_go_forward` / `browser_reload` | History navigation |
45
- | `browser_page_info` | Get URL, title, viewport, scroll position, or dialog state |
46
- | `browser_list_tabs` / `browser_current_tab` / `browser_switch_tab` | Tab discovery and switching |
47
- | `browser_execute_js` | Run JavaScript in the page and return the result |
48
- | `browser_http_get` | Direct HTTP GET outside the browser — much faster for APIs |
101
+ | `browser_list_tabs` / `browser_current_tab` / `browser_switch_tab` / `browser_close_tab` | Tab management (only tabs this session opened) |
102
+
103
+ ### Interaction
104
+
105
+ | Tool | Purpose |
106
+ |------|---------|
107
+ | `browser_click` | Click at viewport coordinates (use `@(x,y)` from `browser_snapshot`) |
108
+ | `browser_type` | Type text into the focused element |
109
+ | `browser_press_key` | Press a key with optional modifiers |
110
+ | `browser_scroll` | Scroll the page by delta pixels |
111
+
112
+ ### Utility
113
+
114
+ | Tool | Purpose |
115
+ |------|---------|
49
116
  | `browser_wait` / `browser_wait_for_load` | Sleep, or wait for `readyState === 'complete'` |
50
117
  | `browser_handle_dialog` | Accept or dismiss `alert` / `confirm` / `prompt` |
51
118
  | `browser_run_script` | Execute a temporary script with daemon and Node.js access |
52
119
 
53
- ---
120
+ Three tools (`browser_snapshot`, `browser_network_requests`, `browser_execute_js`) ship custom TUI rendering with **Ctrl+O** (`app.tools.expand`) to toggle between compact and full output.
54
121
 
55
- ## Core Patterns
122
+ ---
56
123
 
57
- The golden rule: **screenshot → act → screenshot → verify.** Never assume — always look.
124
+ ## Core patterns
58
125
 
59
- ### Navigation
126
+ ### Page inspection
60
127
 
61
128
  ```
62
- browser_new_tab({ url: "https://example.com" })
63
- browser_wait_for_load()
64
- browser_screenshot()
129
+ browser_snapshot()
130
+ # → AX outline with @(x,y) per button/link/input
65
131
  ```
66
132
 
67
- ### Form filling
133
+ ### Form filling (no screenshots)
68
134
 
69
135
  ```
70
- browser_screenshot() # see the form
71
- browser_click({ x: 400, y: 200 }) # click the search input
72
- browser_type({ text: "query" }) # type
73
- browser_press_key({ key: "Enter" }) # submit
136
+ browser_snapshot() # find input @(x,y) and labels
137
+ browser_click({ x, y }) # click using snapshot's @(x,y)
138
+ browser_type({ text: "query" })
139
+ browser_press_key({ key: "Enter" })
74
140
  browser_wait_for_load()
75
- browser_screenshot() # verify
141
+ browser_snapshot() # verify next state
76
142
  ```
77
143
 
78
144
  ### Data extraction
79
145
 
80
- ```
146
+ ```js
147
+ // One value
81
148
  browser_execute_js({ expression: "document.querySelector('.price').innerText" })
82
149
 
150
+ // Direct API call outside the browser
83
151
  browser_http_get({ url: "https://api.github.com/repos/amankumarsingh77/pi-browser-harness" })
84
152
 
85
153
  // Structured arrays
86
- browser_execute_js({ expression: `Array.from(document.querySelectorAll('.result')).map(el => ({
87
- title: el.querySelector('h3')?.textContent,
88
- link: el.querySelector('a')?.href,
89
- }))` })
154
+ browser_execute_js({ expression: `JSON.stringify(
155
+ Array.from(document.querySelectorAll('.result')).map(el => ({
156
+ title: el.querySelector('h3')?.textContent,
157
+ link: el.querySelector('a')?.href,
158
+ }))
159
+ )` })
160
+ ```
161
+
162
+ ### Network debugging
163
+
164
+ ```
165
+ browser_navigate({ url: "https://app.example.com/feed" })
166
+ browser_wait_for_load()
167
+ browser_network_requests({
168
+ urlPattern: "/api/",
169
+ statusFilter: { min: 400 },
170
+ includeResponseBodies: true
171
+ })
90
172
  ```
91
173
 
92
174
  ### Research workflow
93
175
 
94
176
  ```
95
177
  browser_navigate({ url: "https://google.com/search?q=..." })
96
- // read search results
97
178
  browser_open_urls({ urls: ["url1", "url2", "url3"] })
98
179
  browser_list_tabs()
99
180
  browser_switch_tab({ targetId: "..." })
100
- browser_screenshot()
181
+ browser_wait_for_load()
182
+ browser_snapshot()
101
183
  browser_execute_js({ expression: "document.querySelector('.content').innerText" })
102
184
  ```
103
185
 
104
- ### Scrolling
186
+ ### Visual verification (only when pixels matter)
105
187
 
106
188
  ```
107
- browser_screenshot()
108
- browser_scroll({ deltaY: -500 })
109
- browser_screenshot()
189
+ browser_click({ x, y }) # got coords from browser_snapshot
190
+ browser_snapshot() # confirm the form transitioned
191
+ browser_screenshot() # ONLY if you need to verify a chart/modal/CSS rendered correctly
110
192
  ```
111
193
 
112
194
  ### Keyboard modifiers
@@ -118,55 +200,65 @@ browser_screenshot()
118
200
  | Meta / Cmd | 4 |
119
201
  | Shift | 8 |
120
202
 
121
- Combine with bitwise OR:
122
203
  ```
123
204
  browser_press_key({ key: "c", modifiers: 2 }) // Ctrl+C
124
205
  browser_press_key({ key: "v", modifiers: 4 }) // Cmd+V
125
206
  browser_press_key({ key: "T", modifiers: 10 }) // Ctrl+Shift+T
126
207
  ```
127
208
 
128
- ### SPAs (React, Vue, etc.)
209
+ ### Dialogs
129
210
 
130
- `browser_wait_for_load` only checks `readyState`. Poll for elements after:
211
+ JS dialogs freeze the page. Check `browser_page_info` first — if it reports a dialog, handle it before anything else:
131
212
 
132
213
  ```
133
- browser_execute_js({ expression: "!!document.querySelector('.loaded-content')" })
214
+ browser_handle_dialog({ action: "accept" }) // confirm
215
+ browser_handle_dialog({ action: "dismiss" }) // cancel
216
+ browser_handle_dialog({ action: "accept", promptText: "hello" }) // prompt
134
217
  ```
135
218
 
136
- ### Dialogs
219
+ ---
220
+
221
+ ## Parallel execution
137
222
 
138
- JS dialogs (`alert` / `confirm` / `prompt`) freeze the page. Check `browser_page_info` first — if it reports a dialog, handle it before anything else:
223
+ Observation tools run in parallel by default. Mutation tools (`click`, `type`, `scroll`, `navigate`, `switch_tab`, …) are automatically serialized through a shared mutex — emit them in the same turn and the harness FIFO-queues them.
139
224
 
140
225
  ```
141
- browser_handle_dialog({ action: "accept" }) // confirm
142
- browser_handle_dialog({ action: "dismiss" }) // cancel
143
- browser_handle_dialog({ action: "accept", promptText: "hello" }) // prompt
226
+ # All three run concurrently
227
+ browser_snapshot()
228
+ browser_network_requests({ sinceMs: 5000 })
229
+ browser_http_get({ url: "..." })
144
230
  ```
145
231
 
146
232
  ---
147
233
 
148
- ## Temporary Scripts
234
+ ## Tab ownership
149
235
 
150
- When built-in tools aren't enough, write a script to disk and run it:
236
+ The harness never touches tabs you didn't open through it. On first attach it spawns a dedicated Chrome window; subsequent `browser_new_tab` calls open inside that window. `browser_list_tabs` defaults to `scope:"owned"` (pass `scope:"all"` to see read-only listings of your other tabs); `browser_switch_tab` and `browser_close_tab` refuse non-owned tabs.
237
+
238
+ ---
239
+
240
+ ## Temporary scripts
241
+
242
+ When the built-in tools aren't enough, write a script to disk and run it. Scripts get a `daemon` binding for direct CDP access — much faster than chaining tool calls.
151
243
 
152
244
  ```js
153
- // 1. Write the script
154
245
  write("/tmp/scrape-pages.js", `
155
246
  const results = [];
156
247
  for (const url of params.urls) {
157
- await daemon.cdp("Page.navigate", { url });
248
+ await daemon.session().call("Page.navigate", { url });
158
249
  await new Promise(r => setTimeout(r, 2000));
159
- const title = await daemon.evaluateJS("document.title");
250
+ const title = await daemon.evaluateJs("document.title");
160
251
  results.push({ url, title });
161
252
  }
162
253
  return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
163
254
  `)
164
255
 
165
- // 2. Execute it
166
256
  browser_run_script({ path: "/tmp/scrape-pages.js", params: { urls: [...] } })
167
257
  ```
168
258
 
169
- **Script bindings:** `params`, `daemon`, `require`, `signal`, `onUpdate`, `ctx`, `console`, `fetch`, `JSON`, `Buffer`, `setTimeout`, `clearTimeout`.
259
+ **Bindings:** `params`, `daemon`, `require`, `signal`, `onUpdate`, `ctx`, `console`, `fetch`, `JSON`, `Buffer`, `setTimeout`, `clearTimeout`.
260
+
261
+ `daemon` exposes: `evaluateJs`, `pageInfo`, `listTabs`, `switchTab`, `newTab`, `current`, and `session(targetId?)` for raw CDP via `session.call` / `session.callOnTarget` / `session.callBrowser` / `session.takeDialog`.
170
262
 
171
263
  Scripts are written to disk — auditable and re-runnable.
172
264
 
@@ -186,8 +278,9 @@ Scripts are written to disk — auditable and re-runnable.
186
278
 
187
279
  - Don't launch your own browser — you're connected to the user's real Chrome
188
280
  - Don't type credentials — if you hit an auth wall, stop and ask
189
- - Don't assume a page action worked — screenshot to verify
190
- - Don't write pixel coordinates into documentation — describe selectors and text instead
281
+ - Don't screenshot to understand the page — `browser_snapshot` is the default
282
+ - Don't screenshot to find click coordinates — `browser_snapshot`'s `@(x,y)` is exact
283
+ - Don't screenshot to read a value — `browser_execute_js` is one round-trip
191
284
  - Don't ignore dialogs — check `browser_page_info` first
192
285
 
193
286
  ---
@@ -211,9 +304,10 @@ Temporary scripts run inside the harness process with full daemon and Node.js ac
211
304
  |---------|-----|
212
305
  | `DevToolsActivePort not found` | Open `chrome://inspect/#remote-debugging`, tick the checkbox, click Allow |
213
306
  | Connection fails after Chrome restart | Run `/browser-reload-daemon` |
214
- | Page seems loaded but content is missing | SPA — poll with `browser_execute_js` for the element you need |
215
- | A JS dialog is blocking actions | `browser_page_info` will report it — use `browser_handle_dialog` |
307
+ | Page seems loaded but content is missing | SPA — call `browser_snapshot` again, or `browser_execute_js` for a specific element |
308
+ | JS dialog is blocking actions | `browser_page_info` will report it — use `browser_handle_dialog` |
216
309
  | Daemon not starting | Run `/browser-setup` to re-run guided setup |
310
+ | Snapshot didn't return `@(x,y)` for a target | Element wasn't recognized as interactive. Fall back to `browser_execute_js` with `getBoundingClientRect()` |
217
311
 
218
312
  ---
219
313
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-browser-harness",
3
- "version": "0.3.2",
3
+ "version": "0.5.0",
4
4
  "description": "Browser control extension for pi — navigate, click, type, screenshot, and extract data from real Chrome via CDP",
5
5
  "keywords": [
6
6
  "pi-package",