pi-chrome 0.3.0 → 0.3.1

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
@@ -10,7 +10,7 @@ Multiple Pi sessions can use Chrome at the same time. The first Pi session start
10
10
 
11
11
  - **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.
12
12
  - **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.
13
- - **Background by default** — agents can inspect, navigate, click, type, and snapshot without bringing Chrome to the foreground or interrupting whatever you are doing. Pass `foreground: true` to a tool only when you specifically need Chrome focused.
13
+ - **Background by default** — agents can inspect, navigate, click, type, and snapshot without bringing Chrome to the foreground or interrupting whatever you are doing. Toggle for the whole session with `/chrome-foreground` (useful for demos, pair-driving, debugging) or pass `foreground: true` on a single tool call.
14
14
  - **Built-in setup and agent guidance** — `/chrome-onboard` walks users through installing the companion extension, `/chrome-status` checks connectivity, screenshots save to disk, and the prompt primer tells agents to inspect with `chrome_snapshot` before acting and avoid destructive actions unless explicitly requested.
15
15
 
16
16
  ## Install
@@ -315,6 +315,10 @@ const waitForValues = ["selector", "expression"] as const;
315
315
 
316
316
  export default function (pi: ExtensionAPI): void {
317
317
  const bridge = new ChromeProfileBridge(DEFAULT_HOST, DEFAULT_PORT);
318
+ let foregroundDefault = false;
319
+
320
+ const withForeground = <T extends Record<string, unknown>>(params: T): T =>
321
+ ({ ...params, foreground: ((params as { foreground?: boolean }).foreground) ?? foregroundDefault }) as T;
318
322
 
319
323
  pi.on("session_start", async (_event, ctx) => {
320
324
  await bridge.start();
@@ -337,7 +341,7 @@ export default function (pi: ExtensionAPI): void {
337
341
  <chrome-profile-bridge>
338
342
  Chrome control is available through the chrome_* tools via a companion Chrome extension installed in the user's normal Chrome profile.
339
343
  This is not CDP: it can use the user's existing Chrome windows and authenticated sessions after the user loads the companion browser extension.
340
- If chrome_* tools time out, ask the user to run /chrome-onboard, then load the bundled browser-extension folder in chrome://extensions. Prefer chrome_snapshot before clicking/typing. Avoid destructive actions unless explicitly requested. By default chrome_* tools run in the background and do not bring Chrome to the foreground; only pass foreground=true when you specifically need Chrome focused (for example, demoing a flow or capturing a screenshot the user should see).
344
+ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the bundled browser-extension folder in chrome://extensions. Prefer chrome_snapshot before clicking/typing. Avoid destructive actions unless explicitly requested. By default chrome_* tools run in the background and do not bring Chrome to the foreground. The user can flip this for the whole session via /chrome-foreground; you can also pass foreground=true on a single tool call when you specifically need Chrome focused (for example, demoing a flow or capturing a screenshot the user should see).
341
345
  </chrome-profile-bridge>`;
342
346
  return { systemPrompt: event.systemPrompt + primer };
343
347
  });
@@ -360,6 +364,32 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
360
364
  },
361
365
  });
362
366
 
367
+ pi.registerCommand("chrome-foreground", {
368
+ description:
369
+ "Toggle whether chrome_* tools bring Chrome to the foreground. Foreground ON: you can watch the agent work in your browser, useful for demos, pair-driving, and debugging — tradeoff: Chrome pops up and steals focus, interrupting whatever app you were using. Foreground OFF (default): chrome_* tools act silently in the background, so your editor/terminal keeps focus and your workflow is not interrupted. Pass `on` / `off` to set explicitly, or no argument to toggle.",
370
+ getArgumentCompletions: (prefix) => {
371
+ const items = [
372
+ { value: "on", label: "on", description: "Bring Chrome to the foreground for chrome_* actions" },
373
+ { value: "off", label: "off", description: "Run chrome_* actions silently in the background" },
374
+ ];
375
+ const lowered = prefix.toLowerCase();
376
+ const matches = items.filter((item) => item.value.startsWith(lowered));
377
+ return matches.length > 0 ? matches : null;
378
+ },
379
+ handler: async (args, ctx) => {
380
+ const arg = (args || "").trim().toLowerCase();
381
+ if (arg === "on" || arg === "true" || arg === "1") foregroundDefault = true;
382
+ else if (arg === "off" || arg === "false" || arg === "0") foregroundDefault = false;
383
+ else foregroundDefault = !foregroundDefault;
384
+ ctx.ui.notify(
385
+ foregroundDefault
386
+ ? "Chrome foreground ON. chrome_* tools will focus Chrome and activate the target tab. Useful for demos and debugging — but Chrome will pop up over whatever you're doing."
387
+ : "Chrome foreground OFF. chrome_* tools run silently in the background. Your current app keeps focus.",
388
+ "info",
389
+ );
390
+ },
391
+ });
392
+
363
393
  pi.registerCommand("chrome-onboard", {
364
394
  description: "Guide Chrome extension setup for the existing-profile bridge",
365
395
  handler: async (_args, ctx) => {
@@ -462,7 +492,11 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
462
492
  port: Type.Optional(Type.Number()),
463
493
  }),
464
494
  async execute(_id, params): Promise<ToolTextResult> {
465
- const snapshot = await bridge.send("page.snapshot", { ...params, maxElements: params.maxElements ?? MAX_ELEMENTS }, DEFAULT_TIMEOUT_MS);
495
+ const snapshot = await bridge.send(
496
+ "page.snapshot",
497
+ withForeground({ ...params, maxElements: params.maxElements ?? MAX_ELEMENTS }),
498
+ DEFAULT_TIMEOUT_MS,
499
+ );
466
500
  return { content: [{ type: "text", text: truncateText(safeJson(snapshot)) }], details: { snapshot } };
467
501
  },
468
502
  });
@@ -487,7 +521,7 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
487
521
  port: Type.Optional(Type.Number()),
488
522
  }),
489
523
  async execute(_id, params): Promise<ToolTextResult> {
490
- const result = await bridge.send("page.navigate", params, params.timeoutMs ?? 15_000);
524
+ const result = await bridge.send("page.navigate", withForeground(params), params.timeoutMs ?? 15_000);
491
525
  return { content: [{ type: "text", text: `Navigated to ${params.url}` }], details: { result: result as Json } };
492
526
  },
493
527
  });
@@ -512,7 +546,7 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
512
546
  port: Type.Optional(Type.Number()),
513
547
  }),
514
548
  async execute(_id, params): Promise<ToolTextResult> {
515
- const value = await bridge.send("page.evaluate", params, DEFAULT_TIMEOUT_MS);
549
+ const value = await bridge.send("page.evaluate", withForeground(params), DEFAULT_TIMEOUT_MS);
516
550
  return { content: [{ type: "text", text: truncateText(typeof value === "string" ? value : safeJson(value)) }], details: { value: value as Json } };
517
551
  },
518
552
  });
@@ -537,7 +571,7 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
537
571
  port: Type.Optional(Type.Number()),
538
572
  }),
539
573
  async execute(_id, params): Promise<ToolTextResult> {
540
- const result = await bridge.send("page.click", params, DEFAULT_TIMEOUT_MS);
574
+ const result = await bridge.send("page.click", withForeground(params), DEFAULT_TIMEOUT_MS);
541
575
  return { content: [{ type: "text", text: `Clicked ${params.selector ?? `${params.x},${params.y}`}` }], details: { result: result as Json } };
542
576
  },
543
577
  });
@@ -562,7 +596,7 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
562
596
  port: Type.Optional(Type.Number()),
563
597
  }),
564
598
  async execute(_id, params): Promise<ToolTextResult> {
565
- const result = await bridge.send("page.type", params, DEFAULT_TIMEOUT_MS);
599
+ const result = await bridge.send("page.type", withForeground(params), DEFAULT_TIMEOUT_MS);
566
600
  return { content: [{ type: "text", text: `Typed ${params.text.length} character(s)${params.selector ? ` into ${params.selector}` : ""}.` }], details: { result: result as Json } };
567
601
  },
568
602
  });
@@ -585,7 +619,7 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
585
619
  port: Type.Optional(Type.Number()),
586
620
  }),
587
621
  async execute(_id, params): Promise<ToolTextResult> {
588
- const result = await bridge.send("page.key", params, DEFAULT_TIMEOUT_MS);
622
+ const result = await bridge.send("page.key", withForeground(params), DEFAULT_TIMEOUT_MS);
589
623
  return { content: [{ type: "text", text: `Pressed ${params.key}.` }], details: { result: result as Json } };
590
624
  },
591
625
  });
@@ -637,7 +671,7 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
637
671
  const cwd = workspaceCwd(ctx);
638
672
  const defaultPath = join(cwd, ".pi", "chrome-screenshots", `${new Date().toISOString().replace(/[:.]/g, "-")}.${format}`);
639
673
  const outputPath = params.path ? resolve(cwd, params.path) : defaultPath;
640
- const result = (await bridge.send("page.screenshot", params, DEFAULT_TIMEOUT_MS)) as { dataUrl: string; tab?: unknown };
674
+ const result = (await bridge.send("page.screenshot", withForeground(params), DEFAULT_TIMEOUT_MS)) as { dataUrl: string; tab?: unknown };
641
675
  const base64 = result.dataUrl.replace(/^data:image\/(?:png|jpeg);base64,/, "");
642
676
  await mkdir(dirname(outputPath), { recursive: true });
643
677
  await writeFile(outputPath, Buffer.from(base64, "base64"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-chrome",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Control your existing authenticated Chrome profile from one or more Pi sessions with tabs, snapshots, clicks, typing, JS evaluation, waits, and screenshots. Background-by-default so Chrome stops popping up when agents act.",
5
5
  "keywords": [
6
6
  "pi-package",