pi-chrome 0.2.1 → 0.3.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/README.md
CHANGED
|
@@ -10,6 +10,7 @@ Multiple Pi sessions can use Chrome at the same time. The first Pi session start
|
|
|
10
10
|
|
|
11
11
|
- **Uses your existing Chrome profile** — works with the Chrome windows/tabs you are already using, including logged-in GitHub, admin dashboards, local apps, and internal tools.
|
|
12
12
|
- **Full browser automation toolkit for Pi** — list/create/activate/close tabs, snapshot pages with usable CSS selectors, navigate, evaluate JavaScript, click, type, press keys, wait for page state, and capture screenshots.
|
|
13
|
+
- **Background by default** — agents can inspect, navigate, click, type, and snapshot without bringing Chrome to the foreground or interrupting whatever you are doing. Pass `foreground: true` to a tool only when you specifically need Chrome focused.
|
|
13
14
|
- **Built-in setup and agent guidance** — `/chrome-onboard` walks users through installing the companion extension, `/chrome-status` checks connectivity, screenshots save to disk, and the prompt primer tells agents to inspect with `chrome_snapshot` before acting and avoid destructive actions unless explicitly requested.
|
|
14
15
|
|
|
15
16
|
## Install
|
|
@@ -111,20 +111,35 @@ async function dispatch(action, params) {
|
|
|
111
111
|
return executeInTab(params, waitForPage, [params.kind, params.value, params.timeoutMs || 10000, params.intervalMs || 250]);
|
|
112
112
|
case "page.navigate": {
|
|
113
113
|
const tab = await getTabByParams(params);
|
|
114
|
+
if (params.foreground) await bringToFront(tab);
|
|
114
115
|
const wait = params.waitUntilLoad !== false ? waitForTabComplete(tab.id, params.timeoutMs || 15000) : Promise.resolve(undefined);
|
|
115
|
-
const updated = await chrome.tabs.update(tab.id, { url: params.url
|
|
116
|
+
const updated = await chrome.tabs.update(tab.id, { url: params.url });
|
|
116
117
|
await wait;
|
|
117
118
|
return formatTab(await chrome.tabs.get(updated.id));
|
|
118
119
|
}
|
|
119
120
|
case "page.screenshot": {
|
|
120
121
|
const tab = await getTabByParams(params);
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
122
|
+
if (params.foreground) await bringToFront(tab);
|
|
123
|
+
// captureVisibleTab requires the target tab to be the active tab in its window. Activate it
|
|
124
|
+
// without focusing the window so other apps don't get pushed behind Chrome, and restore the
|
|
125
|
+
// previous active tab afterwards to minimize disruption.
|
|
126
|
+
let previousActiveId;
|
|
127
|
+
if (!tab.active) {
|
|
128
|
+
const activeBefore = await chrome.tabs.query({ active: true, windowId: tab.windowId });
|
|
129
|
+
previousActiveId = activeBefore[0]?.id;
|
|
130
|
+
await chrome.tabs.update(tab.id, { active: true });
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
const dataUrl = await chrome.tabs.captureVisibleTab(tab.windowId, {
|
|
134
|
+
format: params.format || "png",
|
|
135
|
+
quality: params.format === "jpeg" ? params.quality : undefined,
|
|
136
|
+
});
|
|
137
|
+
return { dataUrl, tab: formatTab(tab) };
|
|
138
|
+
} finally {
|
|
139
|
+
if (previousActiveId !== undefined && previousActiveId !== tab.id) {
|
|
140
|
+
await chrome.tabs.update(previousActiveId, { active: true }).catch(() => undefined);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
128
143
|
}
|
|
129
144
|
default:
|
|
130
145
|
throw new Error(`Unknown action: ${action}`);
|
|
@@ -168,8 +183,7 @@ async function getTabByParams(params) {
|
|
|
168
183
|
|
|
169
184
|
async function executeInTab(params, func, args) {
|
|
170
185
|
const tab = await getTabByParams(params);
|
|
171
|
-
|
|
172
|
-
await chrome.tabs.update(tab.id, { active: true });
|
|
186
|
+
if (params.foreground) await bringToFront(tab);
|
|
173
187
|
const results = await chrome.scripting.executeScript({
|
|
174
188
|
target: { tabId: tab.id },
|
|
175
189
|
world: "MAIN",
|
|
@@ -179,6 +193,11 @@ async function executeInTab(params, func, args) {
|
|
|
179
193
|
return results?.[0]?.result;
|
|
180
194
|
}
|
|
181
195
|
|
|
196
|
+
async function bringToFront(tab) {
|
|
197
|
+
await chrome.windows.update(tab.windowId, { focused: true });
|
|
198
|
+
await chrome.tabs.update(tab.id, { active: true });
|
|
199
|
+
}
|
|
200
|
+
|
|
182
201
|
function waitForTabComplete(tabId, timeoutMs) {
|
|
183
202
|
return new Promise((resolve, reject) => {
|
|
184
203
|
const timer = setTimeout(() => {
|
|
@@ -318,9 +318,12 @@ export default function (pi: ExtensionAPI): void {
|
|
|
318
318
|
|
|
319
319
|
pi.on("session_start", async (_event, ctx) => {
|
|
320
320
|
await bridge.start();
|
|
321
|
+
const status = bridge.status();
|
|
321
322
|
ctx.ui.setStatus("chrome", `Chrome bridge :${DEFAULT_PORT}`);
|
|
322
323
|
ctx.ui.notify(
|
|
323
|
-
|
|
324
|
+
status.mode === "client"
|
|
325
|
+
? `Chrome profile bridge connected to shared bridge at ${bridge.url}.`
|
|
326
|
+
: `Chrome profile bridge listening at ${bridge.url}. Run /chrome-onboard to load the bundled browser extension in your normal Chrome profile.`,
|
|
324
327
|
"info",
|
|
325
328
|
);
|
|
326
329
|
});
|
|
@@ -334,7 +337,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
334
337
|
<chrome-profile-bridge>
|
|
335
338
|
Chrome control is available through the chrome_* tools via a companion Chrome extension installed in the user's normal Chrome profile.
|
|
336
339
|
This is not CDP: it can use the user's existing Chrome windows and authenticated sessions after the user loads the companion browser extension.
|
|
337
|
-
If chrome_* tools time out, ask the user to run /chrome-onboard, then load the bundled browser-extension folder in chrome://extensions. Prefer chrome_snapshot before clicking/typing. Avoid destructive actions unless explicitly requested.
|
|
340
|
+
If chrome_* tools time out, ask the user to run /chrome-onboard, then load the bundled browser-extension folder in chrome://extensions. Prefer chrome_snapshot before clicking/typing. Avoid destructive actions unless explicitly requested. By default chrome_* tools run in the background and do not bring Chrome to the foreground; only pass foreground=true when you specifically need Chrome focused (for example, demoing a flow or capturing a screenshot the user should see).
|
|
338
341
|
</chrome-profile-bridge>`;
|
|
339
342
|
return { systemPrompt: event.systemPrompt + primer };
|
|
340
343
|
});
|
|
@@ -445,13 +448,16 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
|
|
|
445
448
|
name: "chrome_snapshot",
|
|
446
449
|
label: "Chrome Snapshot",
|
|
447
450
|
description:
|
|
448
|
-
"Inspect a page in the user's existing Chrome profile: title, URL, visible body text, viewport, and clickable/focusable elements with CSS selectors.",
|
|
451
|
+
"Inspect a page in the user's existing Chrome profile: title, URL, visible body text, viewport, and clickable/focusable elements with CSS selectors. Runs in the background by default; pass foreground=true to bring Chrome to the front first.",
|
|
449
452
|
promptSnippet: "Inspect the current Chrome page and get CSS selectors for browser automation.",
|
|
450
453
|
parameters: Type.Object({
|
|
451
454
|
targetId: Type.Optional(Type.String()),
|
|
452
455
|
urlIncludes: Type.Optional(Type.String()),
|
|
453
456
|
titleIncludes: Type.Optional(Type.String()),
|
|
454
457
|
maxElements: Type.Optional(Type.Number({ default: MAX_ELEMENTS })),
|
|
458
|
+
foreground: Type.Optional(
|
|
459
|
+
Type.Boolean({ description: "If true, focus the Chrome window and activate the tab before inspecting. Default false to avoid interrupting the user." }),
|
|
460
|
+
),
|
|
455
461
|
host: Type.Optional(Type.String()),
|
|
456
462
|
port: Type.Optional(Type.Number()),
|
|
457
463
|
}),
|
|
@@ -464,7 +470,8 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
|
|
|
464
470
|
pi.registerTool({
|
|
465
471
|
name: "chrome_navigate",
|
|
466
472
|
label: "Chrome Navigate",
|
|
467
|
-
description:
|
|
473
|
+
description:
|
|
474
|
+
"Navigate an existing Chrome tab to a URL via the companion extension. Navigates in the background by default and does not change the user's currently active tab; pass foreground=true to also focus Chrome and activate the tab. Optionally waits for load completion.",
|
|
468
475
|
promptSnippet: "Navigate a Chrome tab in the user's existing profile.",
|
|
469
476
|
parameters: Type.Object({
|
|
470
477
|
url: Type.String(),
|
|
@@ -473,6 +480,9 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
|
|
|
473
480
|
titleIncludes: Type.Optional(Type.String()),
|
|
474
481
|
waitUntilLoad: Type.Optional(Type.Boolean({ default: true })),
|
|
475
482
|
timeoutMs: Type.Optional(Type.Number({ default: 15_000 })),
|
|
483
|
+
foreground: Type.Optional(
|
|
484
|
+
Type.Boolean({ description: "If true, focus the Chrome window and activate the tab before navigating. Default false." }),
|
|
485
|
+
),
|
|
476
486
|
host: Type.Optional(Type.String()),
|
|
477
487
|
port: Type.Optional(Type.Number()),
|
|
478
488
|
}),
|
|
@@ -486,7 +496,7 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
|
|
|
486
496
|
name: "chrome_evaluate",
|
|
487
497
|
label: "Chrome Evaluate",
|
|
488
498
|
description:
|
|
489
|
-
"Evaluate JavaScript in an existing Chrome tab through the companion extension. Runs in the page context and returns JSON-serializable values when possible.",
|
|
499
|
+
"Evaluate JavaScript in an existing Chrome tab through the companion extension. Runs in the page context (background) and returns JSON-serializable values when possible. Pass foreground=true to also focus Chrome and activate the tab.",
|
|
490
500
|
promptSnippet: "Evaluate JavaScript in the active Chrome tab through the companion extension.",
|
|
491
501
|
parameters: Type.Object({
|
|
492
502
|
expression: Type.String(),
|
|
@@ -495,6 +505,9 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
|
|
|
495
505
|
targetId: Type.Optional(Type.String()),
|
|
496
506
|
urlIncludes: Type.Optional(Type.String()),
|
|
497
507
|
titleIncludes: Type.Optional(Type.String()),
|
|
508
|
+
foreground: Type.Optional(
|
|
509
|
+
Type.Boolean({ description: "If true, focus the Chrome window and activate the tab before evaluating. Default false." }),
|
|
510
|
+
),
|
|
498
511
|
host: Type.Optional(Type.String()),
|
|
499
512
|
port: Type.Optional(Type.Number()),
|
|
500
513
|
}),
|
|
@@ -507,7 +520,8 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
|
|
|
507
520
|
pi.registerTool({
|
|
508
521
|
name: "chrome_click",
|
|
509
522
|
label: "Chrome Click",
|
|
510
|
-
description:
|
|
523
|
+
description:
|
|
524
|
+
"Click a CSS selector or viewport coordinate in an existing Chrome tab through the companion extension. The click is dispatched as a synthetic DOM event in the background by default; pass foreground=true to focus Chrome and activate the tab first.",
|
|
511
525
|
promptSnippet: "Click page elements in Chrome by selector or viewport coordinate.",
|
|
512
526
|
parameters: Type.Object({
|
|
513
527
|
selector: Type.Optional(Type.String({ description: "CSS selector to click. Prefer selectors from chrome_snapshot." })),
|
|
@@ -516,6 +530,9 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
|
|
|
516
530
|
targetId: Type.Optional(Type.String()),
|
|
517
531
|
urlIncludes: Type.Optional(Type.String()),
|
|
518
532
|
titleIncludes: Type.Optional(Type.String()),
|
|
533
|
+
foreground: Type.Optional(
|
|
534
|
+
Type.Boolean({ description: "If true, focus the Chrome window and activate the tab before clicking. Default false." }),
|
|
535
|
+
),
|
|
519
536
|
host: Type.Optional(Type.String()),
|
|
520
537
|
port: Type.Optional(Type.Number()),
|
|
521
538
|
}),
|
|
@@ -528,7 +545,8 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
|
|
|
528
545
|
pi.registerTool({
|
|
529
546
|
name: "chrome_type",
|
|
530
547
|
label: "Chrome Type",
|
|
531
|
-
description:
|
|
548
|
+
description:
|
|
549
|
+
"Focus an optional CSS selector, then type text into an existing Chrome tab through the companion extension. Runs in the background by default; pass foreground=true to focus Chrome and activate the tab first.",
|
|
532
550
|
promptSnippet: "Type text into Chrome, optionally focusing a selector first.",
|
|
533
551
|
parameters: Type.Object({
|
|
534
552
|
text: Type.String(),
|
|
@@ -537,6 +555,9 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
|
|
|
537
555
|
targetId: Type.Optional(Type.String()),
|
|
538
556
|
urlIncludes: Type.Optional(Type.String()),
|
|
539
557
|
titleIncludes: Type.Optional(Type.String()),
|
|
558
|
+
foreground: Type.Optional(
|
|
559
|
+
Type.Boolean({ description: "If true, focus the Chrome window and activate the tab before typing. Default false." }),
|
|
560
|
+
),
|
|
540
561
|
host: Type.Optional(Type.String()),
|
|
541
562
|
port: Type.Optional(Type.Number()),
|
|
542
563
|
}),
|
|
@@ -549,13 +570,17 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
|
|
|
549
570
|
pi.registerTool({
|
|
550
571
|
name: "chrome_key",
|
|
551
572
|
label: "Chrome Key",
|
|
552
|
-
description:
|
|
573
|
+
description:
|
|
574
|
+
"Send a keyboard key to an existing Chrome tab (Enter, Escape, Tab, Backspace, Delete, ArrowUp/Down/Left/Right, or one character). Runs in the background by default; pass foreground=true to focus Chrome and activate the tab first.",
|
|
553
575
|
promptSnippet: "Press keys in Chrome through the companion extension.",
|
|
554
576
|
parameters: Type.Object({
|
|
555
577
|
key: Type.String(),
|
|
556
578
|
targetId: Type.Optional(Type.String()),
|
|
557
579
|
urlIncludes: Type.Optional(Type.String()),
|
|
558
580
|
titleIncludes: Type.Optional(Type.String()),
|
|
581
|
+
foreground: Type.Optional(
|
|
582
|
+
Type.Boolean({ description: "If true, focus the Chrome window and activate the tab before sending the key. Default false." }),
|
|
583
|
+
),
|
|
559
584
|
host: Type.Optional(Type.String()),
|
|
560
585
|
port: Type.Optional(Type.Number()),
|
|
561
586
|
}),
|
|
@@ -590,7 +615,8 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
|
|
|
590
615
|
pi.registerTool({
|
|
591
616
|
name: "chrome_screenshot",
|
|
592
617
|
label: "Chrome Screenshot",
|
|
593
|
-
description:
|
|
618
|
+
description:
|
|
619
|
+
"Capture a screenshot of an existing Chrome tab via the companion extension and save it to disk. Chrome's extension screenshot API requires the target tab to be the active tab in its window, so this momentarily activates it (without focusing the window) and restores the previous active tab. Pass foreground=true to also bring the Chrome window to the front.",
|
|
594
620
|
promptSnippet: "Capture Chrome screenshots and save them under .pi/chrome-screenshots by default.",
|
|
595
621
|
parameters: Type.Object({
|
|
596
622
|
path: Type.Optional(Type.String({ description: "Output path. Defaults to .pi/chrome-screenshots/<timestamp>.<format>." })),
|
|
@@ -600,6 +626,9 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
|
|
|
600
626
|
targetId: Type.Optional(Type.String()),
|
|
601
627
|
urlIncludes: Type.Optional(Type.String()),
|
|
602
628
|
titleIncludes: Type.Optional(Type.String()),
|
|
629
|
+
foreground: Type.Optional(
|
|
630
|
+
Type.Boolean({ description: "If true, focus the Chrome window and activate the tab before capturing. Default false." }),
|
|
631
|
+
),
|
|
603
632
|
host: Type.Optional(Type.String()),
|
|
604
633
|
port: Type.Optional(Type.Number()),
|
|
605
634
|
}),
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-chrome",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Control your existing authenticated Chrome profile from one or more Pi sessions with tabs, snapshots, clicks, typing, JS evaluation, waits, and screenshots.",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Control your existing authenticated Chrome profile from one or more Pi sessions with tabs, snapshots, clicks, typing, JS evaluation, waits, and screenshots. Background-by-default so Chrome stops popping up when agents act.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
7
7
|
"pi-extension",
|