pi-chrome 0.11.3 → 0.11.5
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
|
@@ -65,24 +65,25 @@ pi-chrome v<version>
|
|
|
65
65
|
✓ Companion Chrome extension responding (ID: <chrome-extension-id>, ext v<version>)
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
##
|
|
68
|
+
## Click modes
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
pi-chrome can drive Chrome two ways:
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
- **Quiet clicks** — fast and unobtrusive. They work on most sites, but some pages (sign-in flows, copy-to-clipboard buttons, file pickers, autoplay videos, fullscreen, paywalls) ignore them because they don't look like a real human action.
|
|
73
|
+
- **Real-looking clicks** — indistinguishable from a person clicking. They unlock the cases above, but Chrome shows a *"Pi Chrome Connector started debugging this browser"* banner at the top of every tab pi-chrome touches while it's working.
|
|
73
74
|
|
|
74
|
-
|
|
75
|
+
Pick a mode with `/chrome-trusted`:
|
|
75
76
|
|
|
76
77
|
```text
|
|
77
|
-
/chrome-trusted
|
|
78
|
-
/chrome-trusted off #
|
|
79
|
-
/chrome-trusted
|
|
80
|
-
/chrome-trusted status # show current mode
|
|
78
|
+
/chrome-trusted auto # default; quiet by default, real-looking only when needed
|
|
79
|
+
/chrome-trusted off # always quiet, no banner ever
|
|
80
|
+
/chrome-trusted on # always real-looking, banner stays up the whole session
|
|
81
|
+
/chrome-trusted status # show the current mode
|
|
81
82
|
```
|
|
82
83
|
|
|
83
|
-
For a
|
|
84
|
+
For a one-off call, pass `trusted: true` (or `false`) on `chrome_click`, `chrome_type`, `chrome_fill`, `chrome_key`, `chrome_hover`, `chrome_drag`, or `chrome_scroll`. The per-call value wins over the global mode.
|
|
84
85
|
|
|
85
|
-
First time you
|
|
86
|
+
First time you update pi-chrome to a version that supports real-looking clicks, Chrome will ask you to re-approve the extension. Open `chrome://extensions` and accept the new permission once.
|
|
86
87
|
|
|
87
88
|
## Background mode
|
|
88
89
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "Pi Chrome Connector",
|
|
4
|
-
"version": "0.11.
|
|
4
|
+
"version": "0.11.5",
|
|
5
5
|
"description": "Lets Pi control tabs in Chrome via a local connector at 127.0.0.1.",
|
|
6
6
|
"permissions": ["tabs", "scripting", "storage", "activeTab", "alarms", "webNavigation", "debugger"],
|
|
7
7
|
"host_permissions": ["<all_urls>", "http://127.0.0.1:17318/*"],
|
|
@@ -415,8 +415,8 @@ export default function (pi: ExtensionAPI): void {
|
|
|
415
415
|
ctx.ui.setStatus("chrome", `Chrome bridge :${DEFAULT_PORT}`);
|
|
416
416
|
ctx.ui.notify(
|
|
417
417
|
status.mode === "client"
|
|
418
|
-
? `
|
|
419
|
-
: `
|
|
418
|
+
? `pi-chrome connected (sharing the Chrome connection an earlier pi session opened).`
|
|
419
|
+
: `pi-chrome is ready and waiting for the Chrome companion to connect. Run /chrome-onboard to install it.`,
|
|
420
420
|
"info",
|
|
421
421
|
);
|
|
422
422
|
});
|
|
@@ -449,12 +449,13 @@ Usage rules:
|
|
|
449
449
|
|
|
450
450
|
pi.registerCommand("chrome-doctor", {
|
|
451
451
|
description:
|
|
452
|
-
"
|
|
452
|
+
"Run a quick health check on pi-chrome. Shows whether Chrome is connected, whether the companion extension is up to date, which click mode is active, and how to fix anything that's wrong.",
|
|
453
453
|
handler: async (_args, ctx) => {
|
|
454
|
-
ctx.ui.notify("
|
|
454
|
+
ctx.ui.notify("Checking pi-chrome…", "info");
|
|
455
455
|
const lines: string[] = [`pi-chrome v${PI_CHROME_VERSION}`];
|
|
456
456
|
const status = bridge.status();
|
|
457
|
-
|
|
457
|
+
const roleLabel = status.mode === "client" ? "sharing another pi session's connection" : "running the Chrome connection for this machine";
|
|
458
|
+
lines.push(`• This pi session is ${roleLabel}.`);
|
|
458
459
|
let extensionAlive = false;
|
|
459
460
|
let versionMismatch = false;
|
|
460
461
|
try {
|
|
@@ -469,44 +470,44 @@ Usage rules:
|
|
|
469
470
|
if (version.extensionVersion && version.extensionVersion !== PI_CHROME_VERSION) {
|
|
470
471
|
versionMismatch = true;
|
|
471
472
|
lines.push(
|
|
472
|
-
`✗
|
|
473
|
-
`
|
|
474
|
-
` Fix: open chrome://extensions and click
|
|
475
|
-
` (
|
|
473
|
+
`✗ The Chrome companion extension is on an old version (${version.extensionVersion}); this pi-chrome is ${PI_CHROME_VERSION}.`,
|
|
474
|
+
` Every Chrome action will run with the old code until you reload the extension.`,
|
|
475
|
+
` Fix: open chrome://extensions and click the refresh icon on 'Pi Chrome Connector'.`,
|
|
476
|
+
` (After this one-time fix, future updates reload automatically.)`,
|
|
476
477
|
);
|
|
477
478
|
} else {
|
|
478
|
-
lines.push(`✓
|
|
479
|
+
lines.push(`✓ Chrome is connected (companion extension v${version.extensionVersion ?? "?"}, responded in ${latencyMs}ms).`);
|
|
479
480
|
}
|
|
480
481
|
} catch (error) {
|
|
481
482
|
const message = (error as Error).message;
|
|
482
|
-
lines.push(`✗
|
|
483
|
+
lines.push(`✗ Chrome isn't responding: ${message}`);
|
|
483
484
|
if (message.includes("older pi-chrome without multi-session")) {
|
|
484
|
-
lines.push(" Fix: restart the
|
|
485
|
+
lines.push(" Fix: quit and restart the pi session that first opened the Chrome connection (it was on an older pi-chrome).");
|
|
485
486
|
} else {
|
|
486
|
-
lines.push(" Fix: run /chrome-onboard
|
|
487
|
+
lines.push(" Fix: run /chrome-onboard to install the Chrome companion extension, then keep that Chrome window open.");
|
|
487
488
|
}
|
|
488
489
|
}
|
|
489
490
|
|
|
490
491
|
if (extensionAlive && !versionMismatch) {
|
|
491
|
-
//
|
|
492
|
+
// Sanity-check that pi-chrome can actually run code in the active tab.
|
|
492
493
|
try {
|
|
493
494
|
const value = await bridge.send("page.evaluate", { expression: "1+1", awaitPromise: true, foreground: false }, 10_000);
|
|
494
|
-
if (value === 2) lines.push(`✓
|
|
495
|
-
else lines.push(`⚠
|
|
495
|
+
if (value === 2) lines.push(`✓ pi-chrome can run code in the active Chrome tab.`);
|
|
496
|
+
else lines.push(`⚠ pi-chrome ran code in the active tab but got an unexpected result (${JSON.stringify(value)}). The current tab may be locked-down (a Chrome internal page or a strict site).`);
|
|
496
497
|
} catch (error) {
|
|
497
|
-
lines.push(`✗
|
|
498
|
+
lines.push(`✗ pi-chrome can't run code in the active tab: ${(error as Error).message}`);
|
|
498
499
|
}
|
|
499
500
|
|
|
500
|
-
//
|
|
501
|
+
// Surface obvious site-side automation flags so the user knows why a site might block pi.
|
|
501
502
|
try {
|
|
502
503
|
const probe = (await bridge.send("page.probe", { foreground: false }, 10_000)) as Record<string, unknown>;
|
|
503
|
-
if (probe && probe.arithmetic === 2) lines.push(`✓
|
|
504
|
-
if (probe && probe.webdriver) lines.push(`⚠
|
|
504
|
+
if (probe && probe.arithmetic === 2) lines.push(`✓ The active tab is ${hostnameOf(String(probe.location))} and accepts pi-chrome's commands.`);
|
|
505
|
+
if (probe && probe.webdriver) lines.push(`⚠ Your Chrome is reporting itself as automated to websites. Some sites use this signal to block sign-ins or bot checks.`);
|
|
505
506
|
} catch (error) {
|
|
506
|
-
lines.push(`⚠
|
|
507
|
+
lines.push(`⚠ Couldn't inspect the active tab: ${(error as Error).message}`);
|
|
507
508
|
}
|
|
508
509
|
} else if (versionMismatch) {
|
|
509
|
-
lines.push(`… Skipped
|
|
510
|
+
lines.push(`… Skipped the remaining checks until you reload the Chrome extension.`);
|
|
510
511
|
}
|
|
511
512
|
|
|
512
513
|
// Real-input mode probe (plain English for the user).
|
|
@@ -518,13 +519,13 @@ Usage rules:
|
|
|
518
519
|
permissionGranted?: boolean;
|
|
519
520
|
};
|
|
520
521
|
if (status.permissionGranted) {
|
|
521
|
-
const banner = status.attachedTabs && status.attachedTabs.length ? ` (
|
|
522
|
+
const banner = status.attachedTabs && status.attachedTabs.length ? ` (‘Pi Chrome Connector started debugging this browser’ banner up on ${status.attachedTabs.length} tab(s))` : "";
|
|
522
523
|
const note =
|
|
523
524
|
status.mode === "auto"
|
|
524
|
-
? " Clicks/keys are quiet by default; if a site rejects a quiet click, pi-chrome retries it once with a real-looking click.
|
|
525
|
+
? " Clicks/keys are quiet by default; if a site rejects a quiet click, pi-chrome retries it once with a real-looking click. The Chrome banner shows only when that retry happens."
|
|
525
526
|
: status.mode === "on"
|
|
526
|
-
? " Every click and keystroke uses a real-looking event.
|
|
527
|
-
: " All clicks are quiet, no
|
|
527
|
+
? " Every click and keystroke uses a real-looking event. The Chrome banner stays up on every tab pi-chrome touches."
|
|
528
|
+
: " All clicks are quiet, no banner. Some sites (sign-ins, copy buttons, file pickers, paywalls) may silently ignore them. Switch to /chrome-trusted auto if a site isn’t responding.";
|
|
528
529
|
const label = status.mode === "auto" ? "auto (smart upgrade)" : status.mode === "on" ? "on (always real-looking)" : "off (always quiet)";
|
|
529
530
|
lines.push(`✓ Click mode: ${label}${banner}.${note}`);
|
|
530
531
|
} else {
|
|
@@ -541,7 +542,7 @@ Usage rules:
|
|
|
541
542
|
|
|
542
543
|
pi.registerCommand("chrome-trusted", {
|
|
543
544
|
description:
|
|
544
|
-
"Choose how realistically pi-chrome should drive Chrome. Real-looking clicks/keys unlock things like copy-to-clipboard buttons, file pickers, and sign-in pages, but show a
|
|
545
|
+
"Choose how realistically pi-chrome should drive Chrome. Real-looking clicks/keys unlock things like copy-to-clipboard buttons, file pickers, and sign-in pages, but show a banner at the top of every Chrome window saying it's being driven by pi-chrome. Three modes:\n auto (default) — quiet by default; auto-upgrade when a site blocks the quiet click.\n off — always quiet, no banner; some sites won't accept these clicks.\n on — always real-looking, banner stays up the whole session.\n status — show the current mode.",
|
|
545
546
|
getArgumentCompletions: (prefix) => {
|
|
546
547
|
const items = [
|
|
547
548
|
{ value: "auto", label: "auto", description: "Default. Quiet clicks; upgrade to real ones only when a site rejects them." },
|
|
@@ -580,7 +581,7 @@ Usage rules:
|
|
|
580
581
|
on: "on (always real-looking)",
|
|
581
582
|
};
|
|
582
583
|
const friendly = (m: string) => MODE_NAMES[m] ?? m;
|
|
583
|
-
const attached = status.attachedTabs?.length ? ` —
|
|
584
|
+
const attached = status.attachedTabs?.length ? ` — banner currently up on ${status.attachedTabs.length} tab(s)` : "";
|
|
584
585
|
const current = status.mode;
|
|
585
586
|
|
|
586
587
|
let target = rawArg;
|
|
@@ -592,8 +593,8 @@ Usage rules:
|
|
|
592
593
|
// Interactive picker. Plain-English descriptions; no jargon.
|
|
593
594
|
const options = [
|
|
594
595
|
`auto${current === "auto" ? " (current)" : ""} — quiet by default; if a site rejects a quiet click, retry it once with a real-looking click. Yellow banner appears only when needed. Recommended for everyday use.`,
|
|
595
|
-
`off${current === "off" ? " (current)" : ""} — always quiet. No
|
|
596
|
-
`on${current === "on" ? " (current)" : ""} — every click and keystroke uses a real-looking event.
|
|
596
|
+
`off${current === "off" ? " (current)" : ""} — always quiet. No banner, ever. Fast and unobtrusive, but some sites (sign-in pages, copy-to-clipboard buttons, file pickers, paywalls) will silently ignore the click.`,
|
|
597
|
+
`on${current === "on" ? " (current)" : ""} — every click and keystroke uses a real-looking event. A banner stays at the top of every Chrome window for the whole session, saying ‘Pi Chrome Connector started debugging this browser’. Best when working a stubborn site for a long stretch.`,
|
|
597
598
|
`status — just show me which mode is on right now.`,
|
|
598
599
|
];
|
|
599
600
|
const picked = await ctx.ui.select(
|
|
@@ -624,7 +625,7 @@ Usage rules:
|
|
|
624
625
|
if (target === "on" && current !== "on") {
|
|
625
626
|
const ok = await ctx.ui.confirm(
|
|
626
627
|
"Always use real-looking clicks?",
|
|
627
|
-
"Every click and keystroke pi-chrome sends will now look like a real human action to websites. This unlocks copy-to-clipboard buttons, sign-in pages, file pickers, fullscreen, autoplay, and most bot-protected sites.\n\nThe tradeoff: Chrome will pin a
|
|
628
|
+
"Every click and keystroke pi-chrome sends will now look like a real human action to websites. This unlocks copy-to-clipboard buttons, sign-in pages, file pickers, fullscreen, autoplay, and most bot-protected sites.\n\nThe tradeoff: Chrome will pin a banner at the top of every Chrome window saying ‘Pi Chrome Connector started debugging this browser’. The banner stays visible for the rest of your pi session (or until you switch back to auto/off). Clicking ‘Cancel’ on the banner interrupts pi-chrome.",
|
|
628
629
|
);
|
|
629
630
|
if (!ok) {
|
|
630
631
|
ctx.ui.notify("Mode unchanged.", "info");
|
|
@@ -636,13 +637,13 @@ Usage rules:
|
|
|
636
637
|
const result = (await bridge.send("trusted.mode", { mode: target }, 5_000)) as { mode: string };
|
|
637
638
|
if (result.mode === "on") {
|
|
638
639
|
ctx.ui.notify(
|
|
639
|
-
"On. Every click and keystroke now looks real to websites.
|
|
640
|
+
"On. Every click and keystroke now looks real to websites. A banner saying ‘Pi Chrome Connector started debugging this browser’ will appear on every tab pi-chrome touches.",
|
|
640
641
|
"info",
|
|
641
642
|
);
|
|
642
643
|
} else if (result.mode === "off") {
|
|
643
|
-
|
|
644
|
+
ctx.ui.notify("Off. All clicks are quiet now, no banner. Note: some sites (sign-ins, copy buttons, file pickers, paywalls) may silently ignore these clicks.", "info");
|
|
644
645
|
} else {
|
|
645
|
-
ctx.ui.notify("Auto. Clicks stay quiet by default; pi-chrome will only switch to real-looking clicks when a site rejects a quiet one. The
|
|
646
|
+
ctx.ui.notify("Auto. Clicks stay quiet by default; pi-chrome will only switch to real-looking clicks when a site rejects a quiet one. The Chrome banner will appear only when that retry happens.", "info");
|
|
646
647
|
}
|
|
647
648
|
} catch (error) {
|
|
648
649
|
ctx.ui.notify(`Couldn't switch mode: ${(error as Error).message}`, "warning");
|
|
@@ -652,11 +653,11 @@ Usage rules:
|
|
|
652
653
|
|
|
653
654
|
pi.registerCommand("chrome-background", {
|
|
654
655
|
description:
|
|
655
|
-
"
|
|
656
|
+
"Choose whether Chrome jumps to the front when pi-chrome acts. ON: pi-chrome works silently and Chrome stays in the background — your editor or terminal keeps focus. OFF (default): Chrome pops up and switches to the right tab so you can watch what pi-chrome is doing. Pass `on` / `off`, or run with no argument to flip it.",
|
|
656
657
|
getArgumentCompletions: (prefix) => {
|
|
657
658
|
const items = [
|
|
658
|
-
{ value: "on", label: "on", description: "
|
|
659
|
-
{ value: "off", label: "off", description: "Bring Chrome to the
|
|
659
|
+
{ value: "on", label: "on", description: "Work silently. Chrome stays in the background. Your editor keeps focus." },
|
|
660
|
+
{ value: "off", label: "off", description: "Bring Chrome to the front so you can watch (default)." },
|
|
660
661
|
];
|
|
661
662
|
const lowered = prefix.toLowerCase();
|
|
662
663
|
const matches = items.filter((item) => item.value.startsWith(lowered));
|
|
@@ -669,23 +670,23 @@ Usage rules:
|
|
|
669
670
|
else backgroundDefault = !backgroundDefault;
|
|
670
671
|
ctx.ui.notify(
|
|
671
672
|
backgroundDefault
|
|
672
|
-
? "
|
|
673
|
-
: "
|
|
673
|
+
? "Quiet mode on. pi-chrome will work in the background; Chrome won't steal focus."
|
|
674
|
+
: "Watch mode on. Chrome will pop to the front and switch tabs so you can see what pi-chrome is doing.",
|
|
674
675
|
"info",
|
|
675
676
|
);
|
|
676
677
|
},
|
|
677
678
|
});
|
|
678
679
|
|
|
679
680
|
pi.registerCommand("chrome-onboard", {
|
|
680
|
-
description: "
|
|
681
|
+
description: "Walk through installing the Chrome companion extension that pi-chrome needs to control your browser.",
|
|
681
682
|
handler: async (_args, ctx) => {
|
|
682
683
|
const extensionPath = browserExtensionPath();
|
|
683
684
|
const proceed = await ctx.ui.confirm(
|
|
684
|
-
"Chrome
|
|
685
|
-
`This
|
|
685
|
+
"Install the pi-chrome Chrome extension?",
|
|
686
|
+
`This opens Chrome's extensions page and reveals the folder pi-chrome needs you to load.\n\nWhen the windows open, in Chrome:\n 1. Turn on 'Developer mode' (top-right toggle).\n 2. Click 'Load unpacked' and choose the folder that just opened in Finder, or paste this path:\n ${extensionPath}\n\nPress Enter to continue, or Esc to cancel.`,
|
|
686
687
|
);
|
|
687
688
|
if (!proceed) {
|
|
688
|
-
ctx.ui.notify("
|
|
689
|
+
ctx.ui.notify("Cancelled. You can run /chrome-onboard again whenever you're ready.", "info");
|
|
689
690
|
return;
|
|
690
691
|
}
|
|
691
692
|
if (process.platform === "darwin") {
|
|
@@ -694,7 +695,7 @@ Usage rules:
|
|
|
694
695
|
await pi.exec("sh", ["-lc", `printf %s ${JSON.stringify(extensionPath)} | pbcopy`], { cwd: workspaceCwd(ctx), timeout: 5_000 }).catch(() => undefined);
|
|
695
696
|
}
|
|
696
697
|
ctx.ui.notify(
|
|
697
|
-
"Chrome
|
|
698
|
+
"Chrome and Finder should be open. The extension folder path is on your clipboard. After you click 'Load unpacked' and pick it, run /chrome-doctor to confirm everything is connected.",
|
|
698
699
|
"info",
|
|
699
700
|
);
|
|
700
701
|
},
|
|
@@ -849,7 +850,7 @@ Usage rules:
|
|
|
849
850
|
name: "chrome_click",
|
|
850
851
|
label: "Chrome Click",
|
|
851
852
|
description:
|
|
852
|
-
"Click a snapshot uid, CSS selector, or viewport coordinate. Default 'auto' mode runs synthetic DOM events first and silently retries with trusted CDP only when the click looks gated (no page change + affordance label matches play/copy/share/sign-in/etc, or a recent NotAllowedError). The
|
|
853
|
+
"Click a snapshot uid, CSS selector, or viewport coordinate. Default 'auto' mode runs synthetic DOM events first and silently retries with trusted CDP only when the click looks gated (no page change + affordance label matches play/copy/share/sign-in/etc, or a recent NotAllowedError). The 'started debugging' banner appears only when the retry actually happens. Pass trusted=true to force CDP for this call (banner appears immediately). Pass trusted=false to skip retry. Pass includeSnapshot=true to return a fresh snapshot after the click.",
|
|
853
854
|
promptSnippet: "Click page elements in Chrome by snapshot uid, selector, or viewport coordinate.",
|
|
854
855
|
parameters: Type.Object({
|
|
855
856
|
uid: Type.Optional(Type.String({ description: "Stable element uid from chrome_snapshot. Prefer uid over selector after taking a snapshot." })),
|
|
@@ -1176,7 +1177,7 @@ Usage rules:
|
|
|
1176
1177
|
name: "chrome_tap",
|
|
1177
1178
|
label: "Chrome Tap (Touch)",
|
|
1178
1179
|
description:
|
|
1179
|
-
"Dispatch a real browser-trusted touchstart/touchend tap via chrome.debugger (CDP Input.dispatchTouchEvent). Use for sites that gate on TouchEvent rather than MouseEvent (mobile-first PWAs, swipe carousels). Always uses the trusted CDP path — the
|
|
1180
|
+
"Dispatch a real browser-trusted touchstart/touchend tap via chrome.debugger (CDP Input.dispatchTouchEvent). Use for sites that gate on TouchEvent rather than MouseEvent (mobile-first PWAs, swipe carousels). Always uses the trusted CDP path — the 'started debugging' banner appears.",
|
|
1180
1181
|
promptSnippet: "Tap (real touch) a Chrome element by snapshot uid, selector, or coordinate.",
|
|
1181
1182
|
parameters: Type.Object({
|
|
1182
1183
|
uid: Type.Optional(Type.String()),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-chrome",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.5",
|
|
4
4
|
"description": "Drive your existing logged-in Chrome from Pi — no re-login, no throwaway profile, watch the agent work in real time (or toggle quiet background mode).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|