pi-chrome 0.13.0 → 0.14.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Pi Chrome Connector",
4
- "version": "0.13.0",
4
+ "version": "0.14.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/*"],
@@ -1,5 +1,7 @@
1
1
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import { getSettingsListTheme } from "@earendil-works/pi-coding-agent";
2
3
  import { StringEnum } from "@earendil-works/pi-ai";
4
+ import { Container, type SettingItem, SettingsList, Text } from "@earendil-works/pi-tui";
3
5
  import { Type } from "typebox";
4
6
  import { existsSync, readFileSync, statSync } from "node:fs";
5
7
  import { mkdir, writeFile } from "node:fs/promises";
@@ -679,6 +681,81 @@ Usage rules:
679
681
  ctx.ui.notify(await statusSummary(), "info");
680
682
  };
681
683
 
684
+ // Interactive dialog: each row is a setting whose value cycles with Space/Enter. Enter on
685
+ // the last value also saves; Esc / 'q' closes. The description below changes with the
686
+ // current value so users always see what the active setting means.
687
+ const openSettingsDialog = async (ctx: ExtensionContext): Promise<void> => {
688
+ // Read current click mode (might fail if extension permission missing).
689
+ let clicksMode: string = "auto";
690
+ let permissionGranted = false;
691
+ try {
692
+ const t = (await bridge.send("trusted.status", {}, 5_000)) as { mode?: string; permissionGranted?: boolean };
693
+ clicksMode = t.mode ?? "auto";
694
+ permissionGranted = !!t.permissionGranted;
695
+ } catch {}
696
+
697
+ const clicksItem: SettingItem = {
698
+ id: "clicks",
699
+ label: "Click realism",
700
+ currentValue: clicksMode,
701
+ values: ["auto", "on", "off"],
702
+ description: permissionGranted
703
+ ? (CLICKS_DESC[clicksMode] ?? "")
704
+ : "Real-looking clicks unavailable: reload the Chrome extension in chrome://extensions and accept the new permission prompt.",
705
+ };
706
+ const quietItem: SettingItem = {
707
+ id: "quiet",
708
+ label: "Quiet mode",
709
+ currentValue: backgroundDefault ? "on" : "off",
710
+ values: ["on", "off"],
711
+ description: QUIET_DESC[backgroundDefault ? "on" : "off"] ?? "",
712
+ };
713
+ const items: SettingItem[] = [clicksItem, quietItem];
714
+
715
+ await ctx.ui.custom<void>((_tui, theme, _kb, done) => {
716
+ const container = new Container();
717
+ container.addChild(new Text(theme.fg("accent", theme.bold("pi-chrome settings")), 1, 1));
718
+ container.addChild(new Text(theme.fg("muted", "\u2191\u2193 navigate · space/enter cycle · esc close"), 1, 0));
719
+
720
+ let list: SettingsList;
721
+ list = new SettingsList(
722
+ items,
723
+ Math.min(items.length + 2, 8),
724
+ getSettingsListTheme(),
725
+ (id, newValue) => {
726
+ if (id === "clicks") {
727
+ if (!permissionGranted) {
728
+ ctx.ui.notify("Click mode locked: reload the Chrome extension first.", "warning");
729
+ // Revert by snapping back to the previous value.
730
+ list.updateValue("clicks", clicksItem.currentValue);
731
+ return;
732
+ }
733
+ // Mutate description so the help text matches the new value.
734
+ clicksItem.currentValue = newValue;
735
+ clicksItem.description = CLICKS_DESC[newValue] ?? "";
736
+ list.invalidate();
737
+ void bridge.send("trusted.mode", { mode: newValue }, 5_000).catch((err) => {
738
+ ctx.ui.notify(`Couldn't switch click mode: ${(err as Error).message}`, "warning");
739
+ });
740
+ } else if (id === "quiet") {
741
+ backgroundDefault = newValue === "on";
742
+ quietItem.currentValue = newValue;
743
+ quietItem.description = QUIET_DESC[newValue] ?? "";
744
+ list.invalidate();
745
+ }
746
+ },
747
+ () => done(undefined),
748
+ );
749
+ container.addChild(list);
750
+
751
+ return {
752
+ render: (w) => container.render(w),
753
+ invalidate: () => container.invalidate(),
754
+ handleInput: (data: string) => list.handleInput(data),
755
+ };
756
+ });
757
+ };
758
+
682
759
  pi.registerCommand("chrome", {
683
760
  description:
684
761
  "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.",
@@ -727,32 +804,7 @@ Usage rules:
727
804
  handler: async (args, ctx) => {
728
805
  const tokens = (args || "").trim().split(/\s+/).filter(Boolean);
729
806
  if (tokens.length === 0) {
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] ?? ""}`,
749
- ]);
750
- if (!picked) return;
751
- if (picked.startsWith("status")) return statusHandler(ctx);
752
- if (picked.startsWith("doctor")) return doctorHandler(ctx);
753
- if (picked.startsWith("onboard")) return onboardHandler(ctx);
754
- if (picked.startsWith("clicks")) return trustedHandler(ctx, ""); // cycles
755
- if (picked.startsWith("quiet")) return backgroundHandler(ctx, ""); // toggles
807
+ await openSettingsDialog(ctx);
756
808
  return;
757
809
  }
758
810
  const [head, ...rest] = tokens;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-chrome",
3
- "version": "0.13.0",
3
+ "version": "0.14.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",