pi-chrome 0.15.6 → 0.15.8

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 CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  All notable user-facing changes to `pi-chrome`.
4
4
 
5
+ ## 0.15.8 — 2026-05-14
6
+
7
+ - **Simpler `/chrome` submenus.** Authorize menu now offers 15 minutes, 30 minutes, indefinite, and custom minutes only. Background menu now offers only foreground/background. Esc from a submenu returns to the main `/chrome` menu.
8
+
9
+ ## 0.15.7 — 2026-05-14
10
+
11
+ - **Grouped `/chrome` menu.** Bare `/chrome` now opens a status dashboard with grouped actions: authorize, lock, status, doctor, background/watch mode, and onboard. Authorize/background open submenus instead of showing one flat command list.
12
+
5
13
  ## 0.15.6 — 2026-05-14
6
14
 
7
15
  - **Bare `/chrome` is now a command menu.** Running `/chrome` shows interactive options for every `/chrome ...` command, including authorize/revoke/status/doctor/onboard/background variants.
package/README.md CHANGED
@@ -191,9 +191,10 @@ Each tool is documented inline in Pi — agents see the parameters and gotchas (
191
191
  Chrome control is locked by default. Before any agent can use `chrome_*` tools, explicitly authorize the current Pi session:
192
192
 
193
193
  ```text
194
- /chrome authorize # authorize until this Pi session exits
195
- /chrome authorize once # allow one Chrome command
196
- /chrome authorize 15m # allow for 15 minutes
194
+ /chrome authorize # default: authorize for 15 minutes
195
+ /chrome authorize 30m # authorize for 30 minutes
196
+ /chrome authorize 45 # custom minutes
197
+ /chrome authorize indefinite # authorize until revoked or Pi exits
197
198
  /chrome revoke # lock again
198
199
  /chrome status # shows connection + auth + background
199
200
  ```
@@ -216,7 +217,7 @@ Per-call `background: true` wins over the session setting.
216
217
 
217
218
  - `/chrome doctor` — single command: connectivity, extension version, bridge owner, version drift, MAIN-world helper injection, `chrome_evaluate("1+1") === 2`, fingerprint flags.
218
219
  - `/chrome onboard` — guided first-time setup.
219
- - `/chrome authorize status` — current Chrome-control authorization state.
220
+ - `/chrome status` — current connection, authorization, and background state.
220
221
  - `/chrome background status` — current watch/background setting.
221
222
 
222
223
  If the loaded Chrome extension is older than `pi-chrome` on disk, `/chrome doctor` tells you to reload it from `chrome://extensions`.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Pi Chrome Connector",
4
- "version": "0.15.6",
4
+ "version": "0.15.8",
5
5
  "description": "Lets Pi control tabs in Chrome via a local connector at 127.0.0.1.",
6
6
  "permissions": [
7
7
  "tabs",
@@ -434,26 +434,21 @@ export default function (pi: ExtensionAPI): void {
434
434
 
435
435
  const bridge = new ChromeProfileBridge(DEFAULT_HOST, DEFAULT_PORT);
436
436
  let backgroundDefault = false;
437
- let chromeAuthorizedUntil: number | "session" | undefined;
438
- let chromeAuthorizedCalls: number | undefined;
437
+ let chromeAuthorizedUntil: number | "indefinite" | undefined;
439
438
 
440
439
  const authSummary = (): string => {
441
- if (chromeAuthorizedUntil === "session") return "authorized for this session";
440
+ if (chromeAuthorizedUntil === "indefinite") return "authorized indefinitely";
442
441
  if (typeof chromeAuthorizedUntil === "number") {
443
442
  const remainingMs = chromeAuthorizedUntil - Date.now();
444
- if (remainingMs > 0) {
445
- const minutes = Math.ceil(remainingMs / 60_000);
446
- return `authorized for ${chromeAuthorizedCalls === 1 ? "one Chrome command" : `~${minutes}m`}`;
447
- }
443
+ if (remainingMs > 0) return `authorized for ~${Math.ceil(remainingMs / 60_000)}m`;
448
444
  }
449
445
  return "locked";
450
446
  };
451
447
 
452
448
  const chromeControlAuthorized = (): boolean => {
453
- if (chromeAuthorizedUntil === "session") return true;
449
+ if (chromeAuthorizedUntil === "indefinite") return true;
454
450
  if (typeof chromeAuthorizedUntil === "number" && chromeAuthorizedUntil > Date.now()) return true;
455
451
  chromeAuthorizedUntil = undefined;
456
- chromeAuthorizedCalls = undefined;
457
452
  return false;
458
453
  };
459
454
 
@@ -461,13 +456,6 @@ export default function (pi: ExtensionAPI): void {
461
456
  if (!chromeControlAuthorized()) {
462
457
  throw new Error("Chrome control locked. Ask the user to run /chrome authorize before using chrome_* tools.");
463
458
  }
464
- if (chromeAuthorizedCalls !== undefined) {
465
- chromeAuthorizedCalls -= 1;
466
- if (chromeAuthorizedCalls <= 0) {
467
- chromeAuthorizedUntil = undefined;
468
- chromeAuthorizedCalls = undefined;
469
- }
470
- }
471
459
  };
472
460
 
473
461
  const authorizedBridgeSend = (action: string, params: Record<string, unknown>, timeoutMs = DEFAULT_TIMEOUT_MS): Promise<unknown> => {
@@ -620,39 +608,38 @@ Usage rules:
620
608
  ctx.ui.notify(`Run in background → ${nextLabel}. ${BACKGROUND_DESC[nextLabel]}`, "info");
621
609
  };
622
610
 
623
- const authorizeHandler = async (ctx: ExtensionContext, args: string) => {
624
- const arg = (args || "").trim().toLowerCase() || "session";
625
- if (arg === "status") {
626
- ctx.ui.notify(`Chrome control auth: ${authSummary()}.`, "info");
627
- return;
628
- }
629
- const options: Record<string, { label: string; until: number | "session"; calls?: number }> = {
630
- once: { label: "one Chrome command", until: Date.now() + 10 * 60_000, calls: 1 },
631
- "15m": { label: "15 minutes", until: Date.now() + 15 * 60_000 },
632
- "1h": { label: "1 hour", until: Date.now() + 60 * 60_000 },
633
- session: { label: "this Pi session", until: "session" },
634
- };
635
- const grant = options[arg];
636
- if (!grant) {
637
- ctx.ui.notify("Unknown authorize duration. Pick one of: once | 15m | 1h | session | status.", "warning");
638
- return;
639
- }
611
+ const authorizeFor = async (ctx: ExtensionContext, label: string, until: number | "indefinite") => {
640
612
  const ok = await ctx.ui.confirm(
641
613
  "Authorize pi-chrome control?",
642
- `This Pi session will be allowed to inspect and control your existing Chrome profile for ${grant.label}.\n\nChrome actions use your signed-in browser state and real input. Only approve if you trust the current agent/task.`,
614
+ `This Pi session will be allowed to inspect and control your existing Chrome profile for ${label}.\n\nChrome actions use your signed-in browser state and real input. Only approve if you trust the current agent/task.`,
643
615
  );
644
616
  if (!ok) {
645
617
  ctx.ui.notify("Chrome control remains locked.", "info");
646
618
  return;
647
619
  }
648
- chromeAuthorizedUntil = grant.until;
649
- chromeAuthorizedCalls = grant.calls;
650
- ctx.ui.notify(`Chrome control authorized for ${grant.label}.`, "info");
620
+ chromeAuthorizedUntil = until;
621
+ ctx.ui.notify(`Chrome control authorized for ${label}.`, "info");
622
+ };
623
+
624
+ const parseAuthorizeArg = (arg: string): { label: string; until: number | "indefinite" } | undefined => {
625
+ const normalized = arg.trim().toLowerCase() || "15m";
626
+ if (normalized === "indefinite" || normalized === "forever") return { label: "indefinitely", until: "indefinite" };
627
+ const minutes = normalized.endsWith("m") ? Number(normalized.slice(0, -1)) : Number(normalized);
628
+ if (!Number.isFinite(minutes) || minutes <= 0) return undefined;
629
+ return { label: `${minutes} minutes`, until: Date.now() + minutes * 60_000 };
630
+ };
631
+
632
+ const authorizeHandler = async (ctx: ExtensionContext, args: string) => {
633
+ const grant = parseAuthorizeArg(args);
634
+ if (!grant) {
635
+ ctx.ui.notify("Unknown authorize duration. Use minutes (15m, 30m, 45) or indefinite.", "warning");
636
+ return;
637
+ }
638
+ return authorizeFor(ctx, grant.label, grant.until);
651
639
  };
652
640
 
653
641
  const revokeHandler = (ctx: ExtensionContext) => {
654
642
  chromeAuthorizedUntil = undefined;
655
- chromeAuthorizedCalls = undefined;
656
643
  ctx.ui.notify("Chrome control locked. Run /chrome authorize to allow chrome_* tools again.", "info");
657
644
  };
658
645
 
@@ -700,45 +687,65 @@ Usage rules:
700
687
  ctx.ui.notify(await statusSummary(), "info");
701
688
  };
702
689
 
703
- const openCommandMenu = async (ctx: ExtensionContext): Promise<void> => {
704
- const choice = await ctx.ui.select("pi-chrome", [
705
- "/chrome authorize",
706
- "/chrome authorize once",
707
- "/chrome authorize 15m",
708
- "/chrome authorize 1h",
709
- "/chrome authorize status",
710
- "/chrome revoke",
711
- "/chrome status",
712
- "/chrome doctor",
713
- "/chrome onboard",
714
- "/chrome background",
715
- "/chrome background toggle",
716
- "/chrome background on",
717
- "/chrome background off",
718
- "/chrome background status",
690
+ const openAuthorizeMenu = async (ctx: ExtensionContext): Promise<void> => {
691
+ while (true) {
692
+ const choice = await ctx.ui.select("Authorize Chrome control", [
693
+ "15 minutes",
694
+ "30 minutes",
695
+ "Indefinite",
696
+ "Custom minutes",
697
+ ]);
698
+ if (!choice) return;
699
+ switch (choice) {
700
+ case "15 minutes": return authorizeHandler(ctx, "15m");
701
+ case "30 minutes": return authorizeHandler(ctx, "30m");
702
+ case "Indefinite": return authorizeHandler(ctx, "indefinite");
703
+ case "Custom minutes": {
704
+ const value = await ctx.ui.input("Authorize for how many minutes?", "45");
705
+ if (!value) continue;
706
+ return authorizeHandler(ctx, value);
707
+ }
708
+ }
709
+ }
710
+ };
711
+
712
+ const openBackgroundMenu = async (ctx: ExtensionContext): Promise<void> => {
713
+ const choice = await ctx.ui.select("Background / watch mode", [
714
+ "Use Chrome in background",
715
+ "Use Chrome in foreground",
719
716
  ]);
720
717
  if (!choice) return;
721
718
  switch (choice) {
722
- case "/chrome authorize": return authorizeHandler(ctx, "session");
723
- case "/chrome authorize once": return authorizeHandler(ctx, "once");
724
- case "/chrome authorize 15m": return authorizeHandler(ctx, "15m");
725
- case "/chrome authorize 1h": return authorizeHandler(ctx, "1h");
726
- case "/chrome authorize status": return authorizeHandler(ctx, "status");
727
- case "/chrome revoke": return revokeHandler(ctx);
728
- case "/chrome status": return statusHandler(ctx);
729
- case "/chrome doctor": return doctorHandler(ctx);
730
- case "/chrome onboard": return onboardHandler(ctx);
731
- case "/chrome background": return backgroundHandler(ctx, "");
732
- case "/chrome background toggle": return backgroundHandler(ctx, "toggle");
733
- case "/chrome background on": return backgroundHandler(ctx, "on");
734
- case "/chrome background off": return backgroundHandler(ctx, "off");
735
- case "/chrome background status": return backgroundHandler(ctx, "status");
719
+ case "Use Chrome in background": return backgroundHandler(ctx, "on");
720
+ case "Use Chrome in foreground": return backgroundHandler(ctx, "off");
721
+ }
722
+ };
723
+
724
+ const openCommandMenu = async (ctx: ExtensionContext): Promise<void> => {
725
+ while (true) {
726
+ const choice = await ctx.ui.select(`pi-chrome\n${await statusSummary()}`, [
727
+ "Authorize Chrome control…",
728
+ "Lock Chrome control",
729
+ "Connection status",
730
+ "Doctor / troubleshoot",
731
+ "Background / watch mode…",
732
+ "Install / onboard extension",
733
+ ]);
734
+ if (!choice) return;
735
+ switch (choice) {
736
+ case "Authorize Chrome control…": await openAuthorizeMenu(ctx); continue;
737
+ case "Lock Chrome control": return revokeHandler(ctx);
738
+ case "Connection status": return statusHandler(ctx);
739
+ case "Doctor / troubleshoot": return doctorHandler(ctx);
740
+ case "Background / watch mode…": await openBackgroundMenu(ctx); continue;
741
+ case "Install / onboard extension": return onboardHandler(ctx);
742
+ }
736
743
  }
737
744
  };
738
745
 
739
746
  pi.registerCommand("chrome", {
740
747
  description:
741
- "All pi-chrome controls in one place.\n /chrome authorize [once|15m|1h|session|status] — allow this Pi session to use chrome_* tools.\n /chrome revoke — lock Chrome control.\n /chrome status — one-line snapshot of connection, auth, and background setting.\n /chrome doctor — full health check.\n /chrome onboard — install the Chrome companion extension.\n /chrome background [on|off|status|toggle] — whether pi-chrome runs without focusing Chrome.\nRun with no arguments for an interactive picker that shows current state.",
748
+ "All pi-chrome controls in one place.\n /chrome authorize [15m|30m|<minutes>|indefinite] — allow this Pi session to use chrome_* tools.\n /chrome revoke — lock Chrome control.\n /chrome status — one-line snapshot of connection, auth, and background setting.\n /chrome doctor — full health check.\n /chrome onboard — install the Chrome companion extension.\n /chrome background [on|off|status|toggle] — whether pi-chrome runs without focusing Chrome.\nRun with no arguments for an interactive picker that shows current state.",
742
749
  getArgumentCompletions: (prefix) => {
743
750
  const raw = prefix;
744
751
  const trimmedRight = raw.replace(/\s+$/, "");
@@ -764,11 +771,9 @@ Usage rules:
764
771
  ];
765
772
  } else if (path[0] === "authorize" && path.length === 1) {
766
773
  candidates = [
767
- { fullValue: "authorize once", label: "once", description: "Authorize one Chrome command." },
768
774
  { fullValue: "authorize 15m", label: "15m", description: "Authorize Chrome control for 15 minutes." },
769
- { fullValue: "authorize 1h", label: "1h", description: "Authorize Chrome control for 1 hour." },
770
- { fullValue: "authorize session", label: "session", description: "Authorize Chrome control until this Pi session exits." },
771
- { fullValue: "authorize status", label: "status", description: "Show Chrome control authorization state." },
775
+ { fullValue: "authorize 30m", label: "30m", description: "Authorize Chrome control for 30 minutes." },
776
+ { fullValue: "authorize indefinite", label: "indefinite", description: "Authorize Chrome control until revoked or Pi exits." },
772
777
  ];
773
778
  } else if (path[0] === "background" && path.length === 1) {
774
779
  candidates = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-chrome",
3
- "version": "0.15.6",
3
+ "version": "0.15.8",
4
4
  "scripts": {
5
5
  "version": "node scripts/sync-manifest-version.js",
6
6
  "prepublishOnly": "node scripts/sync-manifest-version.js"