pi-chrome 0.15.7 → 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,10 @@
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
+
5
9
  ## 0.15.7 — 2026-05-14
6
10
 
7
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.
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.7",
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
 
@@ -701,62 +688,64 @@ Usage rules:
701
688
  };
702
689
 
703
690
  const openAuthorizeMenu = async (ctx: ExtensionContext): Promise<void> => {
704
- const choice = await ctx.ui.select("Authorize Chrome control", [
705
- "This Pi session",
706
- "15 minutes",
707
- "1 hour",
708
- "One Chrome command",
709
- "Auth status",
710
- ]);
711
- if (!choice) return;
712
- switch (choice) {
713
- case "This Pi session": return authorizeHandler(ctx, "session");
714
- case "15 minutes": return authorizeHandler(ctx, "15m");
715
- case "1 hour": return authorizeHandler(ctx, "1h");
716
- case "One Chrome command": return authorizeHandler(ctx, "once");
717
- case "Auth status": return authorizeHandler(ctx, "status");
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
+ }
718
709
  }
719
710
  };
720
711
 
721
712
  const openBackgroundMenu = async (ctx: ExtensionContext): Promise<void> => {
722
713
  const choice = await ctx.ui.select("Background / watch mode", [
723
- "Toggle background mode",
724
- "Run in background",
725
- "Bring Chrome forward",
726
- "Background status",
714
+ "Use Chrome in background",
715
+ "Use Chrome in foreground",
727
716
  ]);
728
717
  if (!choice) return;
729
718
  switch (choice) {
730
- case "Toggle background mode": return backgroundHandler(ctx, "toggle");
731
- case "Run in background": return backgroundHandler(ctx, "on");
732
- case "Bring Chrome forward": return backgroundHandler(ctx, "off");
733
- case "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");
734
721
  }
735
722
  };
736
723
 
737
724
  const openCommandMenu = async (ctx: ExtensionContext): Promise<void> => {
738
- const choice = await ctx.ui.select(`pi-chrome\n${await statusSummary()}`, [
739
- "Authorize Chrome control…",
740
- "Lock Chrome control",
741
- "Connection status",
742
- "Doctor / troubleshoot",
743
- "Background / watch mode…",
744
- "Install / onboard extension",
745
- ]);
746
- if (!choice) return;
747
- switch (choice) {
748
- case "Authorize Chrome control…": return openAuthorizeMenu(ctx);
749
- case "Lock Chrome control": return revokeHandler(ctx);
750
- case "Connection status": return statusHandler(ctx);
751
- case "Doctor / troubleshoot": return doctorHandler(ctx);
752
- case "Background / watch mode…": return openBackgroundMenu(ctx);
753
- case "Install / onboard extension": return onboardHandler(ctx);
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
+ }
754
743
  }
755
744
  };
756
745
 
757
746
  pi.registerCommand("chrome", {
758
747
  description:
759
- "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.",
760
749
  getArgumentCompletions: (prefix) => {
761
750
  const raw = prefix;
762
751
  const trimmedRight = raw.replace(/\s+$/, "");
@@ -782,11 +771,9 @@ Usage rules:
782
771
  ];
783
772
  } else if (path[0] === "authorize" && path.length === 1) {
784
773
  candidates = [
785
- { fullValue: "authorize once", label: "once", description: "Authorize one Chrome command." },
786
774
  { fullValue: "authorize 15m", label: "15m", description: "Authorize Chrome control for 15 minutes." },
787
- { fullValue: "authorize 1h", label: "1h", description: "Authorize Chrome control for 1 hour." },
788
- { fullValue: "authorize session", label: "session", description: "Authorize Chrome control until this Pi session exits." },
789
- { 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." },
790
777
  ];
791
778
  } else if (path[0] === "background" && path.length === 1) {
792
779
  candidates = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-chrome",
3
- "version": "0.15.7",
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"