pi-chrome 0.2.2 → 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(() => {
|
|
@@ -337,7 +337,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
337
337
|
<chrome-profile-bridge>
|
|
338
338
|
Chrome control is available through the chrome_* tools via a companion Chrome extension installed in the user's normal Chrome profile.
|
|
339
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.
|
|
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.
|
|
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).
|
|
341
341
|
</chrome-profile-bridge>`;
|
|
342
342
|
return { systemPrompt: event.systemPrompt + primer };
|
|
343
343
|
});
|
|
@@ -448,13 +448,16 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
|
|
|
448
448
|
name: "chrome_snapshot",
|
|
449
449
|
label: "Chrome Snapshot",
|
|
450
450
|
description:
|
|
451
|
-
"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.",
|
|
452
452
|
promptSnippet: "Inspect the current Chrome page and get CSS selectors for browser automation.",
|
|
453
453
|
parameters: Type.Object({
|
|
454
454
|
targetId: Type.Optional(Type.String()),
|
|
455
455
|
urlIncludes: Type.Optional(Type.String()),
|
|
456
456
|
titleIncludes: Type.Optional(Type.String()),
|
|
457
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
|
+
),
|
|
458
461
|
host: Type.Optional(Type.String()),
|
|
459
462
|
port: Type.Optional(Type.Number()),
|
|
460
463
|
}),
|
|
@@ -467,7 +470,8 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
|
|
|
467
470
|
pi.registerTool({
|
|
468
471
|
name: "chrome_navigate",
|
|
469
472
|
label: "Chrome Navigate",
|
|
470
|
-
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.",
|
|
471
475
|
promptSnippet: "Navigate a Chrome tab in the user's existing profile.",
|
|
472
476
|
parameters: Type.Object({
|
|
473
477
|
url: Type.String(),
|
|
@@ -476,6 +480,9 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
|
|
|
476
480
|
titleIncludes: Type.Optional(Type.String()),
|
|
477
481
|
waitUntilLoad: Type.Optional(Type.Boolean({ default: true })),
|
|
478
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
|
+
),
|
|
479
486
|
host: Type.Optional(Type.String()),
|
|
480
487
|
port: Type.Optional(Type.Number()),
|
|
481
488
|
}),
|
|
@@ -489,7 +496,7 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
|
|
|
489
496
|
name: "chrome_evaluate",
|
|
490
497
|
label: "Chrome Evaluate",
|
|
491
498
|
description:
|
|
492
|
-
"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.",
|
|
493
500
|
promptSnippet: "Evaluate JavaScript in the active Chrome tab through the companion extension.",
|
|
494
501
|
parameters: Type.Object({
|
|
495
502
|
expression: Type.String(),
|
|
@@ -498,6 +505,9 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
|
|
|
498
505
|
targetId: Type.Optional(Type.String()),
|
|
499
506
|
urlIncludes: Type.Optional(Type.String()),
|
|
500
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
|
+
),
|
|
501
511
|
host: Type.Optional(Type.String()),
|
|
502
512
|
port: Type.Optional(Type.Number()),
|
|
503
513
|
}),
|
|
@@ -510,7 +520,8 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
|
|
|
510
520
|
pi.registerTool({
|
|
511
521
|
name: "chrome_click",
|
|
512
522
|
label: "Chrome Click",
|
|
513
|
-
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.",
|
|
514
525
|
promptSnippet: "Click page elements in Chrome by selector or viewport coordinate.",
|
|
515
526
|
parameters: Type.Object({
|
|
516
527
|
selector: Type.Optional(Type.String({ description: "CSS selector to click. Prefer selectors from chrome_snapshot." })),
|
|
@@ -519,6 +530,9 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
|
|
|
519
530
|
targetId: Type.Optional(Type.String()),
|
|
520
531
|
urlIncludes: Type.Optional(Type.String()),
|
|
521
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
|
+
),
|
|
522
536
|
host: Type.Optional(Type.String()),
|
|
523
537
|
port: Type.Optional(Type.Number()),
|
|
524
538
|
}),
|
|
@@ -531,7 +545,8 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
|
|
|
531
545
|
pi.registerTool({
|
|
532
546
|
name: "chrome_type",
|
|
533
547
|
label: "Chrome Type",
|
|
534
|
-
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.",
|
|
535
550
|
promptSnippet: "Type text into Chrome, optionally focusing a selector first.",
|
|
536
551
|
parameters: Type.Object({
|
|
537
552
|
text: Type.String(),
|
|
@@ -540,6 +555,9 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
|
|
|
540
555
|
targetId: Type.Optional(Type.String()),
|
|
541
556
|
urlIncludes: Type.Optional(Type.String()),
|
|
542
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
|
+
),
|
|
543
561
|
host: Type.Optional(Type.String()),
|
|
544
562
|
port: Type.Optional(Type.Number()),
|
|
545
563
|
}),
|
|
@@ -552,13 +570,17 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
|
|
|
552
570
|
pi.registerTool({
|
|
553
571
|
name: "chrome_key",
|
|
554
572
|
label: "Chrome Key",
|
|
555
|
-
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.",
|
|
556
575
|
promptSnippet: "Press keys in Chrome through the companion extension.",
|
|
557
576
|
parameters: Type.Object({
|
|
558
577
|
key: Type.String(),
|
|
559
578
|
targetId: Type.Optional(Type.String()),
|
|
560
579
|
urlIncludes: Type.Optional(Type.String()),
|
|
561
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
|
+
),
|
|
562
584
|
host: Type.Optional(Type.String()),
|
|
563
585
|
port: Type.Optional(Type.Number()),
|
|
564
586
|
}),
|
|
@@ -593,7 +615,8 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
|
|
|
593
615
|
pi.registerTool({
|
|
594
616
|
name: "chrome_screenshot",
|
|
595
617
|
label: "Chrome Screenshot",
|
|
596
|
-
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.",
|
|
597
620
|
promptSnippet: "Capture Chrome screenshots and save them under .pi/chrome-screenshots by default.",
|
|
598
621
|
parameters: Type.Object({
|
|
599
622
|
path: Type.Optional(Type.String({ description: "Output path. Defaults to .pi/chrome-screenshots/<timestamp>.<format>." })),
|
|
@@ -603,6 +626,9 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
|
|
|
603
626
|
targetId: Type.Optional(Type.String()),
|
|
604
627
|
urlIncludes: Type.Optional(Type.String()),
|
|
605
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
|
+
),
|
|
606
632
|
host: Type.Optional(Type.String()),
|
|
607
633
|
port: Type.Optional(Type.Number()),
|
|
608
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",
|