pi-chrome 0.15.39 → 0.15.41
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 +4 -0
- package/README.md +71 -213
- package/docs/ARCHITECTURE.md +74 -0
- package/extensions/chrome-profile-bridge/browser-extension/manifest.json +1 -1
- package/extensions/chrome-profile-bridge/browser-extension/service_worker.js +16 -6
- package/package.json +1 -1
- package/test-suite/unit/automation-target.test.mjs +8 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
All notable user-facing changes to `pi-chrome`.
|
|
4
4
|
|
|
5
|
+
## 0.15.40 — 2026-06-22
|
|
6
|
+
|
|
7
|
+
- **Automation targets reuse the session tab group.** When `chrome_navigate` / implicit page actions create a new pi-chrome automation tab, it is now created in this session's existing tab-group window when possible and joins that same group, avoiding duplicate same-title `Pi Session: ...` groups.
|
|
8
|
+
|
|
5
9
|
## 0.15.39 — 2026-06-07
|
|
6
10
|
|
|
7
11
|
- **Dedicated automation tab/window (no more hijacking your active tab).** When a chrome_* action runs without an explicit target, pi-chrome now opens and reuses a dedicated automation window it owns (falling back to a dedicated tab if a separate window can't be created) instead of navigating whatever tab you currently have open. Your existing tabs/windows are left untouched. Pass `targetId`/`urlIncludes`/`titleIncludes` to act on a specific existing tab.
|
package/README.md
CHANGED
|
@@ -1,301 +1,159 @@
|
|
|
1
1
|
# pi-chrome
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Let [Pi](https://pi.dev) use your existing signed-in Chrome profile after explicit authorization.
|
|
4
4
|
|
|
5
|
-
**MIT · 0 runtime deps · loopback-only bridge (`127.0.0.1:17318`) ·
|
|
5
|
+
**MIT · 0 runtime deps · loopback-only bridge (`127.0.0.1:17318`) · inspectable unpacked Chrome extension.** Review [`extensions/chrome-profile-bridge/browser-extension/`](./extensions/chrome-profile-bridge/browser-extension) before loading. Verify setup with `/chrome doctor`.
|
|
6
6
|
|
|
7
7
|
```text
|
|
8
|
-
You: "Find my open GitHub PR tab, summarize review state, and screenshot
|
|
8
|
+
You: "Find my open GitHub PR tab, summarize review state, and screenshot failing CI."
|
|
9
9
|
Agent: chrome_tab(list) → chrome_snapshot(uid:…) → chrome_screenshot(...)
|
|
10
10
|
✓ 3 reviewers, 1 change requested, CI red on iOS. Saved → .pi/chrome-screenshots/ci.png
|
|
11
11
|
You: [keeps coding — agent never asked you to log in]
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
`pi-chrome`
|
|
14
|
+
`pi-chrome` runs through a small Chrome extension inside the Chrome profile **you already use** — including sites where you're already signed in. Agents can inspect or control Chrome only after you run `/chrome authorize` in current Pi session.
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
|
-
##
|
|
19
|
-
|
|
20
|
-
To install pi-chrome, run the following command:
|
|
18
|
+
## Install
|
|
21
19
|
|
|
22
20
|
```bash
|
|
23
21
|
pi install npm:pi-chrome
|
|
24
22
|
```
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
Then in Pi, run the next command, which will:
|
|
28
|
-
|
|
29
|
-
1. Reveal the bundled browser-extension folder in Finder, and copy the folder path to your clipboard.
|
|
30
|
-
2. Pop open the chrome://extensions webpage in Chrome.
|
|
31
|
-
|
|
32
|
-
In the Chrome Extensions page it opened, **YOU WILL NEED TO**:
|
|
33
|
-
|
|
34
|
-
1. Turn on **developer mode** (top right).
|
|
35
|
-
2. Click the **load unpacked** button (top left).
|
|
36
|
-
3. Use **Cmd + Shift + G** (Mac) or **Ctrl + L** (Windows/Linux) to open the folder path field.
|
|
37
|
-
4. **Cmd + V** (Mac) or **Ctrl + V** (Windows/Linux) to paste the copied path and press Enter.
|
|
38
|
-
5. You're done with the chrome extensions page, and you can continue with the rest of the installation commands
|
|
24
|
+
In Pi:
|
|
39
25
|
|
|
40
26
|
```text
|
|
41
27
|
/chrome onboard
|
|
42
28
|
```
|
|
43
29
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
```text
|
|
47
|
-
/reload
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
Verify the chrome connection:
|
|
30
|
+
This opens `chrome://extensions` and copies bundled extension path. In Chrome Extensions:
|
|
52
31
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
32
|
+
1. Turn on **Developer mode**.
|
|
33
|
+
2. Click **Load unpacked**.
|
|
34
|
+
3. Open path field with **Cmd+Shift+G** on macOS or **Ctrl+L** on Windows/Linux.
|
|
35
|
+
4. Paste copied path.
|
|
36
|
+
5. Press Enter.
|
|
57
37
|
|
|
58
|
-
|
|
38
|
+
Reload Pi so installed package loads:
|
|
59
39
|
|
|
60
|
-
Lastly, authorize the current session by running:
|
|
61
40
|
```text
|
|
62
|
-
/
|
|
41
|
+
/reload
|
|
63
42
|
```
|
|
64
43
|
|
|
65
|
-
|
|
44
|
+
Check bridge:
|
|
66
45
|
|
|
67
46
|
```text
|
|
68
47
|
/chrome doctor
|
|
69
48
|
```
|
|
70
|
-
---
|
|
71
|
-
|
|
72
|
-
## Try this in 30 seconds after install
|
|
73
|
-
|
|
74
|
-
```text
|
|
75
|
-
Look at my GitHub notifications tab and triage which PRs
|
|
76
|
-
need my review today, sorted by staleness.
|
|
77
|
-
Don't click anything yet — just read and summarize.
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
You'll watch the agent jump to your GitHub tab and read the page — using **your** session, **your** filters, **your** orgs.
|
|
81
|
-
|
|
82
|
-
---
|
|
83
|
-
|
|
84
|
-
## Killer recipes (copy-paste into Pi)
|
|
85
|
-
|
|
86
|
-
Each recipe assumes the relevant tab is already open in the Chrome you control.
|
|
87
|
-
|
|
88
|
-
**PR triage**
|
|
89
|
-
|
|
90
|
-
```text
|
|
91
|
-
You: "Look at my GitHub notifications tab and summarize the PRs needing my review today, sorted by staleness."
|
|
92
|
-
Agent: chrome_tab(list) → chrome_snapshot(uid:el-notifications) → chrome_evaluate(...)
|
|
93
|
-
✓ 7 PRs waiting on you. 2 stale >3d (storage-rewrite, billing-v2).
|
|
94
|
-
1 just turned CI-green (api-keys-prune). Full sorted list below.
|
|
95
|
-
You: [pastes the list straight into Linear]
|
|
96
|
-
```
|
|
97
49
|
|
|
98
|
-
|
|
50
|
+
You should see:
|
|
99
51
|
|
|
100
52
|
```text
|
|
101
|
-
|
|
102
|
-
Agent: chrome_tab(activate, urlIncludes:"linear.app") → chrome_snapshot(uid:el-cycle) → chrome_evaluate(...)
|
|
103
|
-
✓ 5 in-progress, 2 blocked. Standup draft:
|
|
104
|
-
• Shipped: bridge auto-recover. • In flight: input reliability path.
|
|
105
|
-
• Blocked: vendor portal CSP (waiting on infra).
|
|
106
|
-
• Next: benchmark v2. • Risk: none today.
|
|
107
|
-
You: [drops it into #standup]
|
|
53
|
+
✓ Chrome is connected (...)
|
|
108
54
|
```
|
|
109
55
|
|
|
110
|
-
|
|
56
|
+
Authorize current session:
|
|
111
57
|
|
|
112
58
|
```text
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
→ chrome_click(uid:el-checkout) → chrome_list_network_requests() → chrome_screenshot(./repro/02-500.png)
|
|
116
|
-
✓ POST /api/checkout → 500. Response body saved → ./repro/checkout-500.json
|
|
117
|
-
3 screenshots in ./repro/. Trigger: missing tax_id when cart contains digital goods.
|
|
118
|
-
You: [files the ticket with the folder attached]
|
|
59
|
+
/chrome authorize
|
|
60
|
+
/chrome doctor
|
|
119
61
|
```
|
|
120
62
|
|
|
121
|
-
|
|
122
|
-
<summary><strong>More recipes</strong> (form auto-fill, admin cross-check, visual diff, auth-only data pull, network forensics, file upload)</summary>
|
|
123
|
-
|
|
124
|
-
**Form auto-fill (no submit)**
|
|
125
|
-
> Open the vendor portal, fill the new-vendor form from this JSON, stop before submit.
|
|
126
|
-
|
|
127
|
-
**Admin cross-check**
|
|
128
|
-
> Across my Stripe / Postmark / our admin tabs, find any user where state disagrees.
|
|
129
|
-
|
|
130
|
-
**Local dev visual diff**
|
|
131
|
-
> Snapshot `localhost:3000` and the staging URL of the same page; tell me what's visually different.
|
|
132
|
-
|
|
133
|
-
**Auth-only data pull**
|
|
134
|
-
> Open my analytics dashboard tab and pull today's KPIs from the page.
|
|
135
|
-
|
|
136
|
-
**Network forensics**
|
|
137
|
-
> Reproduce the checkout bug, find the failing API call, and dump its response body.
|
|
138
|
-
|
|
139
|
-
**File upload through React**
|
|
140
|
-
> Open the photo uploader, upload `./fixtures/sample.png`, confirm the preview renders.
|
|
141
|
-
|
|
142
|
-
</details>
|
|
63
|
+
Second doctor run should show all checks passing.
|
|
143
64
|
|
|
144
65
|
---
|
|
145
66
|
|
|
146
|
-
##
|
|
67
|
+
## Quick start
|
|
147
68
|
|
|
148
|
-
|
|
69
|
+
After install + authorize, try:
|
|
149
70
|
|
|
150
71
|
```text
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
72
|
+
Look at my GitHub notifications tab and triage which PRs
|
|
73
|
+
need my review today, sorted by staleness.
|
|
74
|
+
Don't click anything yet — just read and summarize.
|
|
154
75
|
```
|
|
155
76
|
|
|
156
|
-
|
|
77
|
+
More examples: [`docs/EXAMPLES.md`](./docs/EXAMPLES.md).
|
|
157
78
|
|
|
158
79
|
---
|
|
159
80
|
|
|
160
|
-
## What
|
|
161
|
-
|
|
162
|
-
**21 tools**, grouped by job. Every one runs against your already-open tabs.
|
|
163
|
-
|
|
164
|
-
| Category | Tools |
|
|
165
|
-
| --------------- | ---------------------------------------------------------------------------------------------- |
|
|
166
|
-
| **Tabs** | `chrome_tab` (list/new/activate/close/version), `chrome_launch` |
|
|
167
|
-
| **Inspect** | `chrome_snapshot` (concise observation: layout, actions, forms, page map, query matches, diff + stable uids), `chrome_find` (query → ranked uids), `chrome_inspect` (deep single-element context), `chrome_screenshot`, `chrome_evaluate` |
|
|
168
|
-
| **Navigate** | `chrome_navigate` (with optional `initScript` at `document_start`), `chrome_wait_for` |
|
|
169
|
-
| **Interact** | `chrome_click`, `chrome_type`, `chrome_fill`, `chrome_key`, `chrome_hover` |
|
|
170
|
-
| **Gesture** | `chrome_drag` (Chrome pointer drag), `chrome_scroll` (wheel + momentum), `chrome_tap` (touch) |
|
|
171
|
-
| **Files** | `chrome_upload_file` (Chrome file-input control; no native picker) |
|
|
172
|
-
| **Observe** | `chrome_list_console_messages`, `chrome_list_network_requests`, `chrome_get_network_request` (with response body) |
|
|
81
|
+
## What it can do
|
|
173
82
|
|
|
174
|
-
|
|
83
|
+
Pi gets browser tools for:
|
|
175
84
|
|
|
176
|
-
|
|
85
|
+
- **Tabs** — list, open, activate, close, launch Chrome.
|
|
86
|
+
- **Inspect** — page snapshots, element search/inspection, screenshots, JS evaluation.
|
|
87
|
+
- **Navigate/wait** — open URLs, wait for page state.
|
|
88
|
+
- **Interact** — click, type, fill, key, hover, drag, scroll, tap.
|
|
89
|
+
- **Files** — upload files through `<input type=file>` without native picker.
|
|
90
|
+
- **Observe** — console logs, network requests, response bodies.
|
|
177
91
|
|
|
178
|
-
|
|
92
|
+
Input tools return structured verification data. Pass `includeSnapshot: true` on click/type/fill/key calls to get fresh page state in same result.
|
|
179
93
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
- **Per session.** Each Pi session owns its *own* automation window, so concurrent sessions (which all share one companion extension) never fight over a tab.
|
|
183
|
-
- **Survives `/reload` and Chrome service-worker restarts.** Ownership is tracked by id and mirrored to `chrome.storage.session`, so the window is reused rather than orphaned.
|
|
184
|
-
- **Cleanup is safe.** The dedicated target is closed when you revoke Chrome control (`/chrome revoke`) and on real session end — never on `/reload`. Cleanup only ever closes the calling session's own automation window/tab; user tabs/windows and other sessions' targets are never closed. Cleanup is fire-and-forget so it never blocks `/quit`, `/reload`, or session end.
|
|
185
|
-
- **Management actions are guarded.** `chrome_tab` `activate`/`close`/`group`/`ungroup` with no explicit target act on the session's automation tab if it exists, otherwise they error instead of touching your active tab.
|
|
186
|
-
|
|
187
|
-
### Known limits vs. human Chrome use
|
|
188
|
-
|
|
189
|
-
pi-chrome is strongest on web-page workflows exposed through DOM, screenshots, tabs, and Chrome input. It is not a full human/OS substitute. Current limitations include native Chrome/OS surfaces (print/save dialogs, permission bubbles, password-manager prompts), cross-origin iframe DOM access, rich multitouch/pinch/stylus gestures, visual CAPTCHA/bot challenges, hardware-backed auth (passkeys/security keys/biometrics), and arbitrary OS app interaction. For strict-CSP pages, use screenshot + coordinate input when `chrome_snapshot`/`chrome_evaluate` are blocked.
|
|
94
|
+
Tool parameters and gotchas are documented inline in Pi.
|
|
190
95
|
|
|
191
96
|
---
|
|
192
97
|
|
|
193
|
-
##
|
|
194
|
-
|
|
195
|
-
`pi-chrome` drives interactive controls through Chrome's real input layer: clicks, typing, fill, keys, hover, drag, scroll, and touch. Under the hood it uses `chrome.debugger` / CDP, so input satisfies normal user-activation gates. Chrome may show the *"Pi Chrome Connector started debugging this browser"* banner while attached.
|
|
196
|
-
|
|
197
|
-
### Authorization
|
|
98
|
+
## Safety model
|
|
198
99
|
|
|
199
|
-
Chrome control is locked by default.
|
|
100
|
+
Chrome control is locked by default. Authorize per Pi session:
|
|
200
101
|
|
|
201
102
|
```text
|
|
202
|
-
/chrome authorize #
|
|
203
|
-
/chrome authorize 30m #
|
|
204
|
-
/chrome authorize
|
|
205
|
-
/chrome authorize indefinite # authorize until revoked or Pi exits
|
|
103
|
+
/chrome authorize # 15 minutes
|
|
104
|
+
/chrome authorize 30m # custom duration
|
|
105
|
+
/chrome authorize indefinite
|
|
206
106
|
/chrome revoke # lock again
|
|
207
|
-
/chrome status
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
This protects your signed-in Chrome profile from accidental agent use. The loopback bridge also rejects browser-origin command requests so arbitrary web pages cannot call into `127.0.0.1:17318` through CORS.
|
|
211
|
-
|
|
212
|
-
### Run in background / watch modes
|
|
213
|
-
|
|
214
|
-
By default, every `chrome_*` call runs in the **background** — Chrome won't pop up or steal focus. Switch to foreground when you want to **watch the agent work** (demos, debugging, first-time confidence).
|
|
215
|
-
|
|
216
|
-
```text
|
|
217
|
-
/chrome background # toggle for the whole session
|
|
218
|
-
/chrome background on # run in background (default)
|
|
219
|
-
/chrome background off # bring Chrome forward so you can watch
|
|
107
|
+
/chrome status
|
|
220
108
|
```
|
|
221
109
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
### Diagnostics
|
|
110
|
+
Safety properties:
|
|
225
111
|
|
|
226
|
-
-
|
|
227
|
-
-
|
|
228
|
-
-
|
|
229
|
-
-
|
|
112
|
+
- Extension runs in your real Chrome profile and has broad tab/scripting permissions. Install only from trusted package source.
|
|
113
|
+
- Pi side binds to `127.0.0.1:17318` only; no default network exposure.
|
|
114
|
+
- Bridge rejects browser-origin command requests, so ordinary web pages cannot drive it through CORS.
|
|
115
|
+
- Each Pi session gets its own automation target; user tabs/windows are not closed by cleanup.
|
|
116
|
+
- `/chrome revoke` closes only calling session's automation target.
|
|
230
117
|
|
|
231
|
-
|
|
118
|
+
Security details: [`SECURITY.md`](./SECURITY.md). Architecture details: [`docs/ARCHITECTURE.md`](./docs/ARCHITECTURE.md).
|
|
232
119
|
|
|
233
120
|
---
|
|
234
121
|
|
|
235
|
-
##
|
|
122
|
+
## Commands
|
|
236
123
|
|
|
237
124
|
```text
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
automatically Linear, Stripe, etc.)
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
Multiple Pi sessions (planner / worker / audit) can all drive the same Chrome at once. The first session opens the local bridge; later sessions detect it and pipe their commands through.
|
|
250
|
-
|
|
251
|
-
---
|
|
252
|
-
|
|
253
|
-
## Built-in benchmark suite
|
|
254
|
-
|
|
255
|
-
[`test-suite/`](./test-suite) is a benchmark for **any** browser-control agent (not just pi-chrome). It includes **42 primitive challenges** plus **4 hermetic BrowserGym-style long-horizon tasks**.
|
|
256
|
-
|
|
257
|
-
Scoring tracks expected outcomes per challenge rather than raw PASS count, so tools are judged against their declared browser-control capability. Unit challenges are split into gate buckets:
|
|
258
|
-
|
|
259
|
-
- `core` — expected release blockers for normal trusted-mode browser control.
|
|
260
|
-
- `conditional` — capability/environment gated (clipboard, touch, dialogs, native UI, etc.).
|
|
261
|
-
- `quality` — adversarial humanization/fingerprint signals; report trends, don't block general release by default.
|
|
262
|
-
|
|
263
|
-
Each challenge exposes `window.__verdict` / `window.__reason` / `window.__events` and a manifest entry with expected results per mode.
|
|
264
|
-
|
|
265
|
-
```bash
|
|
266
|
-
cd test-suite && python3 -m http.server 8765
|
|
267
|
-
# open http://127.0.0.1:8765/ in the Chrome window pi-chrome controls
|
|
125
|
+
/chrome onboard # guided setup
|
|
126
|
+
/chrome doctor # connectivity + version + eval checks
|
|
127
|
+
/chrome status # connection + auth + background state
|
|
128
|
+
/chrome authorize [duration]
|
|
129
|
+
/chrome revoke
|
|
130
|
+
/chrome background on # default: don't steal focus
|
|
131
|
+
/chrome background off # foreground/watch mode
|
|
132
|
+
/chrome background status
|
|
268
133
|
```
|
|
269
134
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
If you build a competing tool, please open a PR with your scores. We benchmark in public.
|
|
135
|
+
If loaded extension is older than installed `pi-chrome`, `/chrome doctor` tells you to reload it from `chrome://extensions`.
|
|
273
136
|
|
|
274
137
|
---
|
|
275
138
|
|
|
276
|
-
##
|
|
277
|
-
|
|
278
|
-
**Unpacked on purpose.** pi-chrome ships as an inspectable, MIT-licensed extension folder you load once with Developer Mode, so the local bridge and browser permissions are easy to audit and update without a Web Store release cycle. Every line is yours to read in [`extensions/chrome-profile-bridge/browser-extension/`](./extensions/chrome-profile-bridge/browser-extension). `/chrome doctor` reports the loaded extension version and warns when it drifts from your installed `pi-chrome`.
|
|
139
|
+
## Limits
|
|
279
140
|
|
|
280
|
-
|
|
141
|
+
`pi-chrome` works best on web-page workflows exposed through DOM, screenshots, tabs, network, console, and Chrome input. It is not full OS automation.
|
|
281
142
|
|
|
282
|
-
|
|
143
|
+
Current limits include native Chrome/OS surfaces, print/save dialogs, permission bubbles, password-manager prompts, cross-origin iframe DOM access, CAPTCHA/bot challenges, passkeys/security keys/biometrics, rich multitouch/pinch/stylus gestures, and arbitrary desktop apps.
|
|
283
144
|
|
|
284
|
-
|
|
145
|
+
For strict-CSP pages, use screenshots + coordinate input when snapshot/evaluate paths are blocked.
|
|
285
146
|
|
|
286
147
|
---
|
|
287
148
|
|
|
288
|
-
##
|
|
289
|
-
|
|
290
|
-
`pi-chrome` is actively shipped. Things on the near roadmap:
|
|
291
|
-
|
|
292
|
-
- More observability tools (DOM mutation streams, performance traces)
|
|
293
|
-
- First-class cross-origin iframe + Shadow-DOM uid stability across snapshots
|
|
294
|
-
- Native-browser surface coverage where extension APIs allow it (downloads, permissions, context menus)
|
|
295
|
-
- Web Push & service worker introspection
|
|
296
|
-
- Recorder mode that emits agent prompts from your own clicks
|
|
149
|
+
## Docs
|
|
297
150
|
|
|
298
|
-
|
|
151
|
+
- Examples: [`docs/EXAMPLES.md`](./docs/EXAMPLES.md)
|
|
152
|
+
- FAQ: [`docs/FAQ.md`](./docs/FAQ.md)
|
|
153
|
+
- Comparison: [`docs/COMPARISON.md`](./docs/COMPARISON.md)
|
|
154
|
+
- Security: [`SECURITY.md`](./SECURITY.md)
|
|
155
|
+
- Benchmark suite: [`test-suite/README.md`](./test-suite/README.md)
|
|
156
|
+
- Architecture: [`docs/ARCHITECTURE.md`](./docs/ARCHITECTURE.md)
|
|
299
157
|
|
|
300
158
|
---
|
|
301
159
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# pi-chrome architecture
|
|
2
|
+
|
|
3
|
+
`pi-chrome` connects Pi to your existing Chrome profile through a local-only bridge and an unpacked Chrome extension.
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
+----------------------+ +--------------------------+
|
|
7
|
+
| Pi agent (terminal) | -- 127.0.0.1:17318 ->| Chrome extension |
|
|
8
|
+
| chrome_* tools | | (your real profile) |
|
|
9
|
+
+-----------+----------+ +-------------+------------+
|
|
10
|
+
| same machine |
|
|
11
|
+
v v
|
|
12
|
+
Other Pi sessions Tabs you already have open
|
|
13
|
+
share same bridge (GitHub, Linear, Stripe, etc.)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Components
|
|
17
|
+
|
|
18
|
+
- **Pi extension** — exposes `chrome_*` tools and `/chrome` commands inside Pi.
|
|
19
|
+
- **Loopback bridge** — listens on `127.0.0.1:17318`; no external network bind by default.
|
|
20
|
+
- **Chrome companion extension** — loaded unpacked into your real Chrome profile.
|
|
21
|
+
- **Chrome debugger / CDP** — drives input, screenshots, network/console observation, and evaluation.
|
|
22
|
+
|
|
23
|
+
## Session model
|
|
24
|
+
|
|
25
|
+
Multiple Pi sessions can use same Chrome companion extension. First session opens local bridge; later sessions detect it and pipe commands through.
|
|
26
|
+
|
|
27
|
+
Each Pi session owns its own automation target:
|
|
28
|
+
|
|
29
|
+
- First chrome action without explicit target opens dedicated automation window.
|
|
30
|
+
- If separate window cannot be created, pi-chrome falls back to dedicated tab.
|
|
31
|
+
- Target survives `/reload` and Chrome service-worker restarts.
|
|
32
|
+
- Ownership is tracked by id and mirrored to `chrome.storage.session`.
|
|
33
|
+
- Cleanup closes only calling session's own target, never user tabs/windows or other sessions' targets.
|
|
34
|
+
|
|
35
|
+
To point pi-chrome at an existing tab, pass `targetId`, `urlIncludes`, or `titleIncludes`.
|
|
36
|
+
|
|
37
|
+
## Tab management guards
|
|
38
|
+
|
|
39
|
+
`chrome_tab` management actions are guarded:
|
|
40
|
+
|
|
41
|
+
- `activate`, `close`, `group`, and `ungroup` without explicit target act on session automation tab if it exists.
|
|
42
|
+
- If no automation tab exists, operation errors instead of touching your active tab.
|
|
43
|
+
|
|
44
|
+
## Background mode
|
|
45
|
+
|
|
46
|
+
By default, chrome calls run in background so Chrome does not steal focus.
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
/chrome background on # background mode
|
|
50
|
+
/chrome background off # foreground/watch mode
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Per-call `background: false` brings Chrome forward for that action. Per-call `background: true` forces background.
|
|
54
|
+
|
|
55
|
+
## Authorization
|
|
56
|
+
|
|
57
|
+
Bridge connection alone is not enough. Chrome control stays locked until current Pi session runs:
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
/chrome authorize
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Authorization expires after configured duration, on `/chrome revoke`, or when Pi exits.
|
|
64
|
+
|
|
65
|
+
## Unpacked extension choice
|
|
66
|
+
|
|
67
|
+
`pi-chrome` ships browser extension source as an unpacked folder on purpose:
|
|
68
|
+
|
|
69
|
+
- easy to inspect before loading
|
|
70
|
+
- no Web Store release delay
|
|
71
|
+
- MIT-licensed source in repo
|
|
72
|
+
- `/chrome doctor` can compare loaded extension version against installed package
|
|
73
|
+
|
|
74
|
+
Loaded extension has broad tab/scripting permissions inside profile where it is installed. Install only from trusted package source.
|
|
@@ -86,9 +86,19 @@ function isPiChromeOwnedTarget(tabId, sessionKey) {
|
|
|
86
86
|
return false;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
// Create a fresh
|
|
90
|
-
//
|
|
91
|
-
|
|
89
|
+
// Create a fresh automation target for `sessionKey`. If this session already has a tab group,
|
|
90
|
+
// create the tab inside that group's window so one Pi session keeps one Chrome tab group (Chrome
|
|
91
|
+
// groups cannot span windows). If no group exists yet, prefer an isolated window; fall back to a
|
|
92
|
+
// tab. When the tab is created in a pre-existing group window, leave windowId unset so cleanup only
|
|
93
|
+
// closes our tab, never that whole window.
|
|
94
|
+
async function createAutomationTarget(sessionKey, groupTitle) {
|
|
95
|
+
const existingGroup = groupTitle ? await findGroupRecordByTitle(groupTitle) : null;
|
|
96
|
+
if (existingGroup && typeof existingGroup.windowId === "number") {
|
|
97
|
+
const tab = await chrome.tabs.create({ url: "about:blank", active: false, windowId: existingGroup.windowId });
|
|
98
|
+
automationTargets.set(sessionKey, { windowId: undefined, tabId: typeof tab.id === "number" ? tab.id : undefined });
|
|
99
|
+
await persistAutomationTargets();
|
|
100
|
+
return tab;
|
|
101
|
+
}
|
|
92
102
|
if (chrome.windows && typeof chrome.windows.create === "function") {
|
|
93
103
|
try {
|
|
94
104
|
const win = await chrome.windows.create({ url: "about:blank", focused: false });
|
|
@@ -125,8 +135,8 @@ async function resolveOwnedAutomationTarget(sessionKey) {
|
|
|
125
135
|
|
|
126
136
|
// Return the session's dedicated automation target, creating it on first use (or after the user
|
|
127
137
|
// closed it). Used by page/navigation actions that need a live surface to drive.
|
|
128
|
-
async function getOrCreateAutomationTarget(sessionKey) {
|
|
129
|
-
return (await resolveOwnedAutomationTarget(sessionKey)) || createAutomationTarget(sessionKey);
|
|
138
|
+
async function getOrCreateAutomationTarget(sessionKey, groupTitle) {
|
|
139
|
+
return (await resolveOwnedAutomationTarget(sessionKey)) || createAutomationTarget(sessionKey, groupTitle);
|
|
130
140
|
}
|
|
131
141
|
|
|
132
142
|
// Close only the session's pi-chrome-owned window/tab, and only if it still exists. Never touches
|
|
@@ -1317,7 +1327,7 @@ async function getTabByParams(params, { createOwnedTarget = true } = {}) {
|
|
|
1317
1327
|
// existing tab pass targetId/urlIncludes/titleIncludes above.
|
|
1318
1328
|
const sessionKey = sessionKeyOf(params);
|
|
1319
1329
|
tab = createOwnedTarget
|
|
1320
|
-
? await getOrCreateAutomationTarget(sessionKey)
|
|
1330
|
+
? await getOrCreateAutomationTarget(sessionKey, params.sessionGroupTitle)
|
|
1321
1331
|
: await resolveOwnedAutomationTarget(sessionKey);
|
|
1322
1332
|
if (!tab) {
|
|
1323
1333
|
throw new Error(
|
package/package.json
CHANGED
|
@@ -232,6 +232,14 @@ async function run() {
|
|
|
232
232
|
const blankTitle = await w.dispatch("tab.new", { url: "https://pi.test/blank-title", groupTitle: "", group: false, sessionKey: SK });
|
|
233
233
|
ok(typeof blankTitle.tab.groupId === "number" && blankTitle.tab.groupId >= 0, "tab.new-group: groupTitle:'' still creates a grouped tab");
|
|
234
234
|
ok(blankTitle.group.title === "Pi", "tab.new-group: blank groupTitle falls back to a group instead of opting out");
|
|
235
|
+
|
|
236
|
+
const nav2 = await w.dispatch("page.navigate", {
|
|
237
|
+
url: "https://pi.test/new-automation-target", waitUntilLoad: false,
|
|
238
|
+
sessionKey: "session:beta", joinSessionGroup: true, sessionGroupTitle: groupTitle,
|
|
239
|
+
});
|
|
240
|
+
ok(state.groups.size === groupsBefore + 1, "automation-target-group: reused the existing session group, only blank-title Pi group was extra");
|
|
241
|
+
ok(nav2.groupId === groupId, "automation-target-group: new automation target joined the existing session group");
|
|
242
|
+
ok(nav2.windowId === nav.windowId, "automation-target-group: new automation target was created in the existing group's window");
|
|
235
243
|
}
|
|
236
244
|
|
|
237
245
|
// ===== tab.new never leaves an ungrouped tab behind when grouping fails. =====
|