pi-chrome 0.12.1 → 0.13.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
|
@@ -11,7 +11,7 @@ Multiple Pi sessions can use Chrome at the same time. The first Pi session start
|
|
|
11
11
|
## Why try it?
|
|
12
12
|
|
|
13
13
|
- **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.
|
|
14
|
-
- **Watch your authenticated Chrome work** — by default, `chrome_*` tool calls focus Chrome and activate the target tab so you can see the agent inspect, navigate, click, and type in real time. Switch to silent/background mode for the whole session with `/chrome
|
|
14
|
+
- **Watch your authenticated Chrome work** — by default, `chrome_*` tool calls focus Chrome and activate the target tab so you can see the agent inspect, navigate, click, and type in real time. Switch to silent/background mode for the whole session with `/chrome quiet`, or pass `background: true` on a single tool call when you want quiet.
|
|
15
15
|
- **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.
|
|
16
16
|
- **Built-in setup and agent guidance** — `/chrome onboard` walks users through installing the companion extension, `/chrome doctor` checks connectivity and version drift, screenshots save to disk, and the prompt primer tells agents to inspect with `chrome_snapshot` before acting and avoid destructive actions unless explicitly requested.
|
|
17
17
|
|
|
@@ -72,13 +72,13 @@ pi-chrome can drive Chrome two ways:
|
|
|
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
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.
|
|
74
74
|
|
|
75
|
-
Pick a mode with `/chrome
|
|
75
|
+
Pick a mode with `/chrome clicks`:
|
|
76
76
|
|
|
77
77
|
```text
|
|
78
|
-
/chrome
|
|
79
|
-
/chrome
|
|
80
|
-
/chrome
|
|
81
|
-
/chrome
|
|
78
|
+
/chrome clicks auto # default; quiet by default, real-looking only when needed
|
|
79
|
+
/chrome clicks off # always quiet, no banner ever
|
|
80
|
+
/chrome clicks on # always real-looking, banner stays up the whole session
|
|
81
|
+
/chrome clicks status # show the current mode
|
|
82
82
|
```
|
|
83
83
|
|
|
84
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.
|
|
@@ -92,9 +92,9 @@ By default, `chrome_*` tools focus Chrome and activate the target tab so you can
|
|
|
92
92
|
When you want quiet (planner / audit / worker sessions running alongside your editor), turn background mode on for the whole Pi session:
|
|
93
93
|
|
|
94
94
|
```text
|
|
95
|
-
/chrome
|
|
96
|
-
/chrome
|
|
97
|
-
/chrome
|
|
95
|
+
/chrome quiet # toggle
|
|
96
|
+
/chrome quiet on # explicit
|
|
97
|
+
/chrome quiet off # explicit
|
|
98
98
|
```
|
|
99
99
|
|
|
100
100
|
For a single tool call, the agent can pass `background: true` directly. The per-call value always wins over the session toggle.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "Pi Chrome Connector",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.13.0",
|
|
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/*"],
|
|
@@ -440,7 +440,7 @@ Usage rules:
|
|
|
440
440
|
2. \`includeSnapshot=true\` on click/type/fill to verify in one round trip.
|
|
441
441
|
3. If \`chrome_evaluate\` returns null when you expected a value, the expression evaluated to null/undefined in the page; surface the value via \`JSON.stringify\` to confirm.
|
|
442
442
|
4. \`chrome_navigate\` supports an optional \`initScript\` that runs at document_start in MAIN world for the next navigation (good for seeding localStorage or stubbing Date.now).
|
|
443
|
-
5. By default chrome_* tools focus Chrome so the user can watch; pass \`background=true\` or run /chrome
|
|
443
|
+
5. By default chrome_* tools focus Chrome so the user can watch; pass \`background=true\` or run /chrome quiet to silence the whole session.
|
|
444
444
|
6. If you hit an autoplay/clipboard/file-picker gate, tell the user; this bridge cannot satisfy it.
|
|
445
445
|
7. Run /chrome doctor when in doubt about connectivity or capabilities.
|
|
446
446
|
</chrome-profile-bridge>`;
|
|
@@ -523,7 +523,7 @@ Usage rules:
|
|
|
523
523
|
? " 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."
|
|
524
524
|
: status.mode === "on"
|
|
525
525
|
? " Every click and keystroke uses a real-looking event. The Chrome banner stays up on every tab pi-chrome touches."
|
|
526
|
-
: " All clicks are quiet, no banner. Some sites (sign-ins, copy buttons, file pickers, paywalls) may silently ignore them.
|
|
526
|
+
: " All clicks are quiet, no banner. Some sites (sign-ins, copy buttons, file pickers, paywalls) may silently ignore them. Run /chrome clicks if a site isn’t responding.";
|
|
527
527
|
const label = status.mode === "auto" ? "auto (smart upgrade)" : status.mode === "on" ? "on (always real-looking)" : "off (always quiet)";
|
|
528
528
|
lines.push(`✓ Click mode: ${label}${banner}.${note}`);
|
|
529
529
|
} else {
|
|
@@ -537,113 +537,98 @@ Usage rules:
|
|
|
537
537
|
ctx.ui.notify(lines.join("\n"), "info");
|
|
538
538
|
};
|
|
539
539
|
|
|
540
|
+
// Click realism handler. With no args, cycles auto → on → off → auto. Explicit args jump
|
|
541
|
+
// directly. 'status' prints the current mode without changing it.
|
|
542
|
+
const CLICKS_CYCLE = ["auto", "on", "off"] as const;
|
|
543
|
+
const CLICKS_DESC: Record<string, string> = {
|
|
544
|
+
auto: "Quiet by default; pi-chrome retries once with a real-looking click if a site rejects the quiet one. The Chrome banner appears only when that retry happens.",
|
|
545
|
+
off: "All clicks are quiet, no banner. Some sites (sign-ins, copy buttons, file pickers, paywalls) may silently ignore these clicks.",
|
|
546
|
+
on: "Every click and keystroke looks real to websites. Chrome shows a 'Pi Chrome Connector started debugging this browser' banner on every tab pi-chrome touches.",
|
|
547
|
+
};
|
|
548
|
+
const CLICKS_LABEL: Record<string, string> = {
|
|
549
|
+
auto: "auto (smart upgrade)",
|
|
550
|
+
off: "off (always quiet)",
|
|
551
|
+
on: "on (always real-looking)",
|
|
552
|
+
};
|
|
553
|
+
|
|
540
554
|
const trustedHandler = async (ctx: ExtensionContext, args: string) => {
|
|
541
|
-
|
|
555
|
+
const rawArg = (args || "").trim().toLowerCase();
|
|
542
556
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
if (!status) return;
|
|
557
|
+
let status: { mode: string; attachedTabs: number[]; permissionGranted: boolean } | undefined;
|
|
558
|
+
try {
|
|
559
|
+
status = (await bridge.send("trusted.status", {}, 5_000)) as typeof status;
|
|
560
|
+
} catch (error) {
|
|
561
|
+
ctx.ui.notify(`Couldn't check current click mode: ${(error as Error).message}`, "warning");
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
if (!status) return;
|
|
552
565
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
566
|
+
if (!status.permissionGranted) {
|
|
567
|
+
ctx.ui.notify(
|
|
568
|
+
"pi-chrome can't drive real-looking clicks yet — the companion extension is missing a permission. Open chrome://extensions, click reload on 'Pi Chrome Connector', and accept the new permission prompt that appears.",
|
|
569
|
+
"warning",
|
|
570
|
+
);
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
560
573
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
off: "off (always quiet)",
|
|
564
|
-
on: "on (always real-looking)",
|
|
565
|
-
};
|
|
566
|
-
const friendly = (m: string) => MODE_NAMES[m] ?? m;
|
|
567
|
-
const attached = status.attachedTabs?.length ? ` — banner currently up on ${status.attachedTabs.length} tab(s)` : "";
|
|
568
|
-
const current = status.mode;
|
|
574
|
+
const current = status.mode;
|
|
575
|
+
const attached = status.attachedTabs?.length ? ` (banner up on ${status.attachedTabs.length} tab(s))` : "";
|
|
569
576
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
}
|
|
575
|
-
if (!target) {
|
|
576
|
-
// Interactive picker. Plain-English descriptions; no jargon.
|
|
577
|
-
const options = [
|
|
578
|
-
`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.`,
|
|
579
|
-
`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.`,
|
|
580
|
-
`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.`,
|
|
581
|
-
`status — just show me which mode is on right now.`,
|
|
582
|
-
];
|
|
583
|
-
const picked = await ctx.ui.select(
|
|
584
|
-
`How realistic should pi-chrome's clicks be? (current: ${friendly(current)}${attached})`,
|
|
585
|
-
options,
|
|
586
|
-
);
|
|
587
|
-
if (!picked) return; // cancelled
|
|
588
|
-
if (picked.startsWith("on")) target = "on";
|
|
589
|
-
else if (picked.startsWith("off")) target = "off";
|
|
590
|
-
else if (picked.startsWith("auto")) target = "auto";
|
|
591
|
-
else if (picked.startsWith("status")) {
|
|
592
|
-
ctx.ui.notify(`Current mode: ${friendly(current)}${attached}`, "info");
|
|
593
|
-
return;
|
|
594
|
-
}
|
|
595
|
-
}
|
|
577
|
+
if (rawArg === "status") {
|
|
578
|
+
ctx.ui.notify(`Click mode is ${CLICKS_LABEL[current] ?? current}${attached}. ${CLICKS_DESC[current] ?? ""}`, "info");
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
596
581
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
582
|
+
// No argument = cycle to the next mode.
|
|
583
|
+
let target = rawArg;
|
|
584
|
+
if (!target) {
|
|
585
|
+
const idx = CLICKS_CYCLE.indexOf(current as typeof CLICKS_CYCLE[number]);
|
|
586
|
+
target = CLICKS_CYCLE[(idx + 1 + CLICKS_CYCLE.length) % CLICKS_CYCLE.length];
|
|
587
|
+
}
|
|
601
588
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
589
|
+
if (!["on", "off", "auto"].includes(target)) {
|
|
590
|
+
ctx.ui.notify(`Unknown click mode '${rawArg}'. Pick one of: auto | off | on | status.`, "warning");
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
606
593
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
"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.",
|
|
612
|
-
);
|
|
613
|
-
if (!ok) {
|
|
614
|
-
ctx.ui.notify("Mode unchanged.", "info");
|
|
615
|
-
return;
|
|
616
|
-
}
|
|
617
|
-
}
|
|
594
|
+
if (target === current) {
|
|
595
|
+
ctx.ui.notify(`Click mode is already ${CLICKS_LABEL[current] ?? current}.`, "info");
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
618
598
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
"info",
|
|
625
|
-
);
|
|
626
|
-
} else if (result.mode === "off") {
|
|
627
|
-
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");
|
|
628
|
-
} else {
|
|
629
|
-
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");
|
|
630
|
-
}
|
|
631
|
-
} catch (error) {
|
|
632
|
-
ctx.ui.notify(`Couldn't switch mode: ${(error as Error).message}`, "warning");
|
|
599
|
+
try {
|
|
600
|
+
await bridge.send("trusted.mode", { mode: target }, 5_000);
|
|
601
|
+
ctx.ui.notify(`Click mode → ${CLICKS_LABEL[target] ?? target}. ${CLICKS_DESC[target] ?? ""}`, "info");
|
|
602
|
+
} catch (error) {
|
|
603
|
+
ctx.ui.notify(`Couldn't switch click mode: ${(error as Error).message}`, "warning");
|
|
633
604
|
}
|
|
634
605
|
};
|
|
635
606
|
|
|
607
|
+
// Quiet (Chrome focus) handler. No args = toggle. Explicit on/off/status.
|
|
608
|
+
const QUIET_DESC: Record<string, string> = {
|
|
609
|
+
on: "pi-chrome works in the background; Chrome won't pop up or steal focus.",
|
|
610
|
+
off: "Chrome pops to the front and switches tabs so you can watch what pi-chrome is doing.",
|
|
611
|
+
};
|
|
612
|
+
|
|
636
613
|
const backgroundHandler = async (ctx: ExtensionContext, args: string) => {
|
|
637
614
|
const arg = (args || "").trim().toLowerCase();
|
|
615
|
+
const currentLabel = backgroundDefault ? "on" : "off";
|
|
616
|
+
|
|
617
|
+
if (arg === "status") {
|
|
618
|
+
ctx.ui.notify(`Quiet mode is ${currentLabel}. ${QUIET_DESC[currentLabel]}`, "info");
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
|
|
638
622
|
if (arg === "on" || arg === "true" || arg === "1") backgroundDefault = true;
|
|
639
623
|
else if (arg === "off" || arg === "false" || arg === "0") backgroundDefault = false;
|
|
640
|
-
else backgroundDefault = !backgroundDefault;
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
624
|
+
else if (arg === "toggle" || arg === "") backgroundDefault = !backgroundDefault;
|
|
625
|
+
else {
|
|
626
|
+
ctx.ui.notify(`Unknown quiet mode '${arg}'. Pick one of: on | off | toggle | status.`, "warning");
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const nextLabel = backgroundDefault ? "on" : "off";
|
|
631
|
+
ctx.ui.notify(`Quiet mode → ${nextLabel}. ${QUIET_DESC[nextLabel]}`, "info");
|
|
647
632
|
};
|
|
648
633
|
|
|
649
634
|
const onboardHandler = async (ctx: ExtensionContext) => {
|
|
@@ -667,90 +652,131 @@ Usage rules:
|
|
|
667
652
|
);
|
|
668
653
|
};
|
|
669
654
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
}
|
|
684
|
-
const [head, ...sub] = rest;
|
|
685
|
-
const subArgs = sub.join(" ");
|
|
686
|
-
switch (head) {
|
|
687
|
-
case "background": return backgroundHandler(ctx, subArgs);
|
|
688
|
-
case "trusted": return trustedHandler(ctx, subArgs);
|
|
689
|
-
default:
|
|
690
|
-
ctx.ui.notify(`Unknown setting '${head}'. Try: /chrome settings background | trusted.`, "warning");
|
|
655
|
+
// One-line snapshot of pi-chrome's current state. Used as a header in the bare-/chrome
|
|
656
|
+
// picker and as the body of /chrome status.
|
|
657
|
+
const statusSummary = async (): Promise<string> => {
|
|
658
|
+
const parts: string[] = [];
|
|
659
|
+
try {
|
|
660
|
+
const version = (await bridge.send("tab.version", {}, 5_000)) as { extensionVersion?: string };
|
|
661
|
+
if (version.extensionVersion && version.extensionVersion !== PI_CHROME_VERSION) {
|
|
662
|
+
parts.push(`⚠ Chrome extension v${version.extensionVersion} (pi-chrome v${PI_CHROME_VERSION}, reload extension)`);
|
|
663
|
+
} else {
|
|
664
|
+
parts.push(`✓ Chrome connected`);
|
|
665
|
+
}
|
|
666
|
+
} catch {
|
|
667
|
+
parts.push(`✗ Chrome not responding`);
|
|
691
668
|
}
|
|
669
|
+
try {
|
|
670
|
+
const t = (await bridge.send("trusted.status", {}, 3_000)) as { mode?: string; attachedTabs?: number[] };
|
|
671
|
+
const banner = t.attachedTabs?.length ? `, banner on ${t.attachedTabs.length} tab(s)` : "";
|
|
672
|
+
parts.push(`clicks: ${t.mode ?? "?"}${banner}`);
|
|
673
|
+
} catch {}
|
|
674
|
+
parts.push(`quiet: ${backgroundDefault ? "on" : "off"}`);
|
|
675
|
+
return parts.join(" · ");
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
const statusHandler = async (ctx: ExtensionContext) => {
|
|
679
|
+
ctx.ui.notify(await statusSummary(), "info");
|
|
692
680
|
};
|
|
693
681
|
|
|
694
682
|
pi.registerCommand("chrome", {
|
|
695
683
|
description:
|
|
696
|
-
"All pi-chrome controls in one place
|
|
684
|
+
"All pi-chrome controls in one place.\n /chrome status — one-line snapshot of connection + current modes.\n /chrome doctor — full health check.\n /chrome onboard — install the Chrome companion extension.\n /chrome clicks [auto|off|on|status] — how realistic should pi-chrome's clicks be.\n /chrome quiet [on|off|status|toggle] — whether Chrome pops to the front when pi-chrome acts.\nRun with no arguments for an interactive picker that shows current state.",
|
|
697
685
|
getArgumentCompletions: (prefix) => {
|
|
698
|
-
const
|
|
699
|
-
const
|
|
700
|
-
const
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
];
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
else if (path[0] === "
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
686
|
+
const raw = prefix;
|
|
687
|
+
const trimmedRight = raw.replace(/\s+$/, "");
|
|
688
|
+
const tokens = trimmedRight ? trimmedRight.split(/\s+/) : [];
|
|
689
|
+
const endsWithSpace = raw.length > 0 && raw !== trimmedRight;
|
|
690
|
+
// Path = completed tokens; partial = the token currently being typed (or "" if cursor sits right after a space).
|
|
691
|
+
const partial = endsWithSpace ? "" : (tokens.pop() ?? "");
|
|
692
|
+
const path = tokens.map((t) => t.toLowerCase());
|
|
693
|
+
const partialLower = partial.toLowerCase();
|
|
694
|
+
|
|
695
|
+
// Build candidate set with FULL argument-text values so pi-tui's apply-completion
|
|
696
|
+
// (which replaces the entire argument) lands correctly even for nested paths.
|
|
697
|
+
type Item = { fullValue: string; label: string; description: string };
|
|
698
|
+
let candidates: Item[] = [];
|
|
699
|
+
if (path.length === 0) {
|
|
700
|
+
candidates = [
|
|
701
|
+
{ fullValue: "status", label: "status", description: "One-line summary: connection + click mode + quiet mode." },
|
|
702
|
+
{ fullValue: "doctor", label: "doctor", description: "Full health check. Tells you if Chrome is connected and what's wrong if it isn't." },
|
|
703
|
+
{ fullValue: "onboard", label: "onboard", description: "Install the Chrome companion extension (first-time setup)." },
|
|
704
|
+
{ fullValue: "clicks", label: "clicks", description: "How realistic should pi-chrome's clicks be? auto / off / on." },
|
|
705
|
+
{ fullValue: "quiet", label: "quiet", description: "Should Chrome pop to the front when pi-chrome acts, or work silently?" },
|
|
706
|
+
];
|
|
707
|
+
} else if (path[0] === "clicks" && path.length === 1) {
|
|
708
|
+
candidates = [
|
|
709
|
+
{ fullValue: "clicks auto", label: "auto", description: "Default. Quiet clicks; upgrade to real-looking ones only when a site rejects them." },
|
|
710
|
+
{ fullValue: "clicks off", label: "off", description: "Always quiet. No banner. Some sites won't accept the clicks." },
|
|
711
|
+
{ fullValue: "clicks on", label: "on", description: "Always real-looking. Chrome shows a banner. Best for stubborn sites." },
|
|
712
|
+
{ fullValue: "clicks status", label: "status", description: "Show the current click mode." },
|
|
713
|
+
];
|
|
714
|
+
} else if (path[0] === "quiet" && path.length === 1) {
|
|
715
|
+
candidates = [
|
|
716
|
+
{ fullValue: "quiet on", label: "on", description: "Work silently. Chrome stays in the background. Your editor keeps focus." },
|
|
717
|
+
{ fullValue: "quiet off", label: "off", description: "Bring Chrome to the front so you can watch (default)." },
|
|
718
|
+
{ fullValue: "quiet toggle", label: "toggle", description: "Flip whichever way it's currently set." },
|
|
719
|
+
{ fullValue: "quiet status", label: "status", description: "Show the current setting." },
|
|
720
|
+
];
|
|
721
|
+
}
|
|
722
|
+
if (candidates.length === 0) return null;
|
|
723
|
+
const filtered = candidates.filter((c) => c.label.toLowerCase().startsWith(partialLower));
|
|
724
|
+
if (filtered.length === 0) return null;
|
|
725
|
+
return filtered.map((c) => ({ value: c.fullValue, label: c.label, description: c.description }));
|
|
732
726
|
},
|
|
733
727
|
handler: async (args, ctx) => {
|
|
734
728
|
const tokens = (args || "").trim().split(/\s+/).filter(Boolean);
|
|
735
729
|
if (tokens.length === 0) {
|
|
736
|
-
const
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
730
|
+
const header = await statusSummary();
|
|
731
|
+
// Compute next-cycle targets so the picker labels describe the toggle action.
|
|
732
|
+
let clicksNow = "?";
|
|
733
|
+
try {
|
|
734
|
+
const t = (await bridge.send("trusted.status", {}, 3_000)) as { mode?: string };
|
|
735
|
+
clicksNow = t.mode ?? "?";
|
|
736
|
+
} catch {}
|
|
737
|
+
const clicksNext = (() => {
|
|
738
|
+
const idx = CLICKS_CYCLE.indexOf(clicksNow as typeof CLICKS_CYCLE[number]);
|
|
739
|
+
return idx >= 0 ? CLICKS_CYCLE[(idx + 1) % CLICKS_CYCLE.length] : "auto";
|
|
740
|
+
})();
|
|
741
|
+
const quietNow = backgroundDefault ? "on" : "off";
|
|
742
|
+
const quietNext = backgroundDefault ? "off" : "on";
|
|
743
|
+
const picked = await ctx.ui.select(`pi-chrome — ${header}`, [
|
|
744
|
+
`status — print the line above (so you can copy it).`,
|
|
745
|
+
`doctor — full health check; explains anything broken.`,
|
|
746
|
+
`onboard — install the Chrome companion extension (first-time setup).`,
|
|
747
|
+
`clicks: ${clicksNow} → switch to ${clicksNext} — ${CLICKS_DESC[clicksNext] ?? ""}`,
|
|
748
|
+
`quiet: ${quietNow} → switch to ${quietNext} — ${QUIET_DESC[quietNext] ?? ""}`,
|
|
740
749
|
]);
|
|
741
750
|
if (!picked) return;
|
|
751
|
+
if (picked.startsWith("status")) return statusHandler(ctx);
|
|
742
752
|
if (picked.startsWith("doctor")) return doctorHandler(ctx);
|
|
743
753
|
if (picked.startsWith("onboard")) return onboardHandler(ctx);
|
|
744
|
-
if (picked.startsWith("
|
|
754
|
+
if (picked.startsWith("clicks")) return trustedHandler(ctx, ""); // cycles
|
|
755
|
+
if (picked.startsWith("quiet")) return backgroundHandler(ctx, ""); // toggles
|
|
745
756
|
return;
|
|
746
757
|
}
|
|
747
758
|
const [head, ...rest] = tokens;
|
|
759
|
+
const subArgs = rest.join(" ");
|
|
748
760
|
switch (head) {
|
|
761
|
+
case "status": return statusHandler(ctx);
|
|
749
762
|
case "doctor": return doctorHandler(ctx);
|
|
750
763
|
case "onboard": return onboardHandler(ctx);
|
|
751
|
-
case "
|
|
764
|
+
case "clicks":
|
|
765
|
+
case "trusted": // legacy alias
|
|
766
|
+
return trustedHandler(ctx, subArgs);
|
|
767
|
+
case "quiet":
|
|
768
|
+
case "background": // legacy alias
|
|
769
|
+
return backgroundHandler(ctx, subArgs);
|
|
770
|
+
case "settings": {
|
|
771
|
+
// Legacy nested form: /chrome settings background ... or /chrome settings trusted ...
|
|
772
|
+
const [setting, ...settingArgs] = rest;
|
|
773
|
+
if (setting === "background") return backgroundHandler(ctx, settingArgs.join(" "));
|
|
774
|
+
if (setting === "trusted") return trustedHandler(ctx, settingArgs.join(" "));
|
|
775
|
+
ctx.ui.notify(`'/chrome settings' was removed. Use /chrome clicks or /chrome quiet directly.`, "warning");
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
752
778
|
default:
|
|
753
|
-
ctx.ui.notify(`Unknown subcommand '${head}'. Try: /chrome doctor | onboard |
|
|
779
|
+
ctx.ui.notify(`Unknown subcommand '${head}'. Try: /chrome status | doctor | onboard | clicks | quiet.`, "warning");
|
|
754
780
|
}
|
|
755
781
|
},
|
|
756
782
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-chrome",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
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",
|