pi-chrome 0.4.4 → 0.6.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,9 +11,9 @@ 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
- - **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`, or pass `foreground: true` on a single tool call.
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-background`, 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
- - **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.
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
 
18
18
  ## Install
19
19
 
@@ -53,38 +53,40 @@ Then in Chrome:
53
53
  4. Return to Pi and run:
54
54
 
55
55
  ```text
56
- /chrome-status
56
+ /chrome-doctor
57
57
  ```
58
58
 
59
- Expected messages:
59
+ Expected output:
60
60
 
61
61
  ```text
62
62
  Performing Chrome bridge health check
63
- Chrome profile bridge connected (ID: <chrome-extension-id>)
63
+ pi-chrome v<version>
64
+ • Local bridge: mode=server, url=http://127.0.0.1:17318
65
+ ✓ Companion Chrome extension responding (ID: <chrome-extension-id>, ext v<version>)
64
66
  ```
65
67
 
66
- ## Foreground control
68
+ ## Background mode
67
69
 
68
- By default, `chrome_*` tools act silently in the background your editor or terminal keeps focus and Chrome does not pop up. This lets agents work alongside you without interrupting whatever you are doing.
70
+ By default, `chrome_*` tools focus Chrome and activate the target tab so you can watch the agent work great for demos, pair-driving, debugging, and first-time confidence that things are happening.
69
71
 
70
- When you want to watch the agent (demos, pair-driving, debugging), turn foreground on for the whole Pi session:
72
+ When you want quiet (planner / audit / worker sessions running alongside your editor), turn background mode on for the whole Pi session:
71
73
 
72
74
  ```text
73
- /chrome-foreground # toggle
74
- /chrome-foreground on # explicit
75
- /chrome-foreground off # explicit
75
+ /chrome-background # toggle
76
+ /chrome-background on # explicit
77
+ /chrome-background off # explicit
76
78
  ```
77
79
 
78
- For a single tool call, the agent can pass `foreground: true` directly. The per-call value always wins over the session toggle.
80
+ For a single tool call, the agent can pass `background: true` directly. The per-call value always wins over the session toggle.
79
81
 
80
82
  ## Quick demo prompts
81
83
 
82
84
  After setup, try one of these in Pi:
83
85
 
84
- Background inspection (no Chrome interruption):
86
+ Silent inspection (no Chrome interruption):
85
87
 
86
88
  ```text
87
- Inspect my active GitHub tab in the background with chrome_snapshot and summarize the PR state without focusing Chrome.
89
+ Inspect my active GitHub tab with chrome_snapshot using background:true and summarize the PR state without focusing Chrome.
88
90
  ```
89
91
 
90
92
  Existing authenticated tab:
@@ -115,8 +117,7 @@ Screenshots save under `.pi/chrome-screenshots/` by default, which composes nice
115
117
 
116
118
  ## Diagnostics
117
119
 
118
- - `/chrome-status` — quick health check; reports the connected Chrome extension ID and version.
119
- - `/chrome-doctor` — deeper diagnosis with one-line fixes for common setup failures (extension not loaded, bridge owner stale after `pi update`, version mismatch between pi-chrome and the loaded Chrome extension).
120
+ - `/chrome-doctor` — single command that checks connectivity and reports the loaded Chrome extension ID + version, plus a one-line fix for common setup failures (extension not loaded, bridge owner stale after `pi update`, version mismatch between pi-chrome and the loaded Chrome extension).
120
121
 
121
122
  If the Chrome extension you have loaded is older than `pi-chrome` on disk, `/chrome-doctor` will tell you to reload it from `chrome://extensions`.
122
123
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Pi Existing Chrome Profile Bridge",
4
- "version": "0.4.4",
4
+ "version": "0.6.0",
5
5
  "description": "Lets Pi control tabs in this existing Chrome profile via a local bridge at 127.0.0.1.",
6
6
  "permissions": ["tabs", "scripting", "storage", "activeTab", "alarms"],
7
7
  "host_permissions": ["<all_urls>", "http://127.0.0.1:17318/*"],
@@ -46,7 +46,7 @@ type BridgeResult = {
46
46
  error?: string;
47
47
  };
48
48
 
49
- const PI_CHROME_VERSION = "0.4.4";
49
+ const PI_CHROME_VERSION = "0.6.0";
50
50
  const DEFAULT_HOST = process.env.PI_CHROME_BRIDGE_HOST ?? "127.0.0.1";
51
51
  const DEFAULT_PORT = Number(process.env.PI_CHROME_BRIDGE_PORT ?? "17318");
52
52
  const DEFAULT_TIMEOUT_MS = 30_000;
@@ -339,10 +339,21 @@ const waitForValues = ["selector", "expression"] as const;
339
339
 
340
340
  export default function (pi: ExtensionAPI): void {
341
341
  const bridge = new ChromeProfileBridge(DEFAULT_HOST, DEFAULT_PORT);
342
- let foregroundDefault = false;
343
-
344
- const withForeground = <T extends Record<string, unknown>>(params: T): T =>
345
- ({ ...params, foreground: ((params as { foreground?: boolean }).foreground) ?? foregroundDefault }) as T;
342
+ let backgroundDefault = false;
343
+
344
+ // Translate the public `background` parameter (default false = visible/foreground) into the
345
+ // service worker's wire-level `foreground` flag, accepting legacy `foreground` as a fallback.
346
+ const withBackground = <T extends Record<string, unknown>>(params: T): T => {
347
+ const typed = params as { background?: boolean; foreground?: boolean };
348
+ const explicit =
349
+ typed.background !== undefined
350
+ ? typed.background
351
+ : typed.foreground !== undefined
352
+ ? !typed.foreground
353
+ : undefined;
354
+ const background = explicit ?? backgroundDefault;
355
+ return { ...params, foreground: !background } as T;
356
+ };
346
357
 
347
358
  pi.on("session_start", async (_event, ctx) => {
348
359
  await bridge.start();
@@ -365,36 +376,21 @@ export default function (pi: ExtensionAPI): void {
365
376
  <chrome-profile-bridge>
366
377
  Chrome control is available through the chrome_* tools via a companion Chrome extension installed in the user's normal Chrome profile.
367
378
  This is not CDP: it can use the user's existing Chrome windows and authenticated sessions after the user loads the companion browser extension.
368
- 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).
379
+ 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 focus Chrome and activate the target tab so the user can watch the agent work. The user can switch to silent/background mode for the whole session via /chrome-background; you can also pass background=true on a single tool call when the user explicitly wants the action to be silent (for example, scraping while they keep working in another app).
369
380
  </chrome-profile-bridge>`;
370
381
  return { systemPrompt: event.systemPrompt + primer };
371
382
  });
372
383
 
373
- pi.registerCommand("chrome-status", {
374
- description: "Run an explicit health check against the existing-profile Chrome companion extension",
375
- handler: async (_args, ctx) => {
376
- ctx.ui.notify("Performing Chrome bridge health check", "info");
377
- try {
378
- const version = (await bridge.send("tab.version", {}, 35_000)) as { extensionId?: string; extensionVersion?: string };
379
- const suffix = [version.extensionId ? `ID: ${version.extensionId}` : null, version.extensionVersion ? `ext v${version.extensionVersion}` : null]
380
- .filter(Boolean)
381
- .join(", ");
382
- ctx.ui.notify(suffix ? `Chrome profile bridge connected (${suffix})` : "Chrome profile bridge connected", "info");
383
- } catch (error) {
384
- ctx.ui.notify(`Chrome bridge health check failed: ${(error as Error).message}`, "warning");
385
- }
386
- },
387
- });
388
-
389
384
  pi.registerCommand("chrome-doctor", {
390
385
  description:
391
- "Diagnose Chrome bridge setup. Checks the local bridge, the companion Chrome extension, and reports a one-line fix for common failures.",
386
+ "Check Chrome bridge connectivity and diagnose setup. Reports the local bridge, companion Chrome extension status (ID + version), and a one-line fix for common failures (extension not loaded, stale service worker, version drift).",
392
387
  handler: async (_args, ctx) => {
388
+ ctx.ui.notify("Performing Chrome bridge health check", "info");
393
389
  const lines: string[] = [`pi-chrome v${PI_CHROME_VERSION}`];
394
390
  const status = bridge.status();
395
391
  lines.push(`• Local bridge: mode=${status.mode}, url=${status.url}`);
396
392
  try {
397
- const version = (await bridge.send("tab.version", {}, 8_000)) as {
393
+ const version = (await bridge.send("tab.version", {}, 35_000)) as {
398
394
  extensionId?: string;
399
395
  extensionVersion?: string;
400
396
  };
@@ -419,13 +415,13 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
419
415
  },
420
416
  });
421
417
 
422
- pi.registerCommand("chrome-foreground", {
418
+ pi.registerCommand("chrome-background", {
423
419
  description:
424
- "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.",
420
+ "Toggle silent/background mode for chrome_* tools. Background ON: chrome_* tools act silently your editor/terminal keeps focus, Chrome does not pop up, your workflow is not interrupted. Background OFF (default): Chrome focuses and activates the target tab so you can watch the agent work, useful for demos, pair-driving, and debugging tradeoff: Chrome pops up and steals focus. Pass `on` / `off` to set explicitly, or no argument to toggle.",
425
421
  getArgumentCompletions: (prefix) => {
426
422
  const items = [
427
- { value: "on", label: "on", description: "Bring Chrome to the foreground for chrome_* actions" },
428
- { value: "off", label: "off", description: "Run chrome_* actions silently in the background" },
423
+ { value: "on", label: "on", description: "Run chrome_* actions silently without focusing Chrome" },
424
+ { value: "off", label: "off", description: "Bring Chrome to the foreground for chrome_* actions (default)" },
429
425
  ];
430
426
  const lowered = prefix.toLowerCase();
431
427
  const matches = items.filter((item) => item.value.startsWith(lowered));
@@ -433,13 +429,13 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
433
429
  },
434
430
  handler: async (args, ctx) => {
435
431
  const arg = (args || "").trim().toLowerCase();
436
- if (arg === "on" || arg === "true" || arg === "1") foregroundDefault = true;
437
- else if (arg === "off" || arg === "false" || arg === "0") foregroundDefault = false;
438
- else foregroundDefault = !foregroundDefault;
432
+ if (arg === "on" || arg === "true" || arg === "1") backgroundDefault = true;
433
+ else if (arg === "off" || arg === "false" || arg === "0") backgroundDefault = false;
434
+ else backgroundDefault = !backgroundDefault;
439
435
  ctx.ui.notify(
440
- foregroundDefault
441
- ? "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."
442
- : "Chrome foreground OFF. chrome_* tools run silently in the background. Your current app keeps focus.",
436
+ backgroundDefault
437
+ ? "Chrome background mode ON. chrome_* tools will run silently. Your current app keeps focus."
438
+ : "Chrome background mode OFF. chrome_* tools will focus Chrome and activate the target tab so you can watch the agent work.",
443
439
  "info",
444
440
  );
445
441
  },
@@ -463,7 +459,7 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
463
459
  await pi.exec("sh", ["-lc", `printf %s ${JSON.stringify(extensionPath)} | pbcopy`], { cwd: workspaceCwd(ctx), timeout: 5_000 }).catch(() => undefined);
464
460
  }
465
461
  ctx.ui.notify(
466
- "Chrome bridge setup opened. The extension path has been copied to your clipboard. After loading it, run /chrome-status.",
462
+ "Chrome bridge setup opened. The extension path has been copied to your clipboard. After loading it, run /chrome-doctor.",
467
463
  "info",
468
464
  );
469
465
  },
@@ -533,15 +529,15 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
533
529
  name: "chrome_snapshot",
534
530
  label: "Chrome Snapshot",
535
531
  description:
536
- "Inspect a page in the user's existing Chrome profile: title, URL, visible body text, viewport, and clickable/focusable elements with CSS selectors. Runs in the background by default; pass foreground=true to bring Chrome to the front first.",
532
+ "Inspect a page in the user's existing Chrome profile: title, URL, visible body text, viewport, and clickable/focusable elements with CSS selectors. Brings Chrome to the foreground by default so the user can watch; pass background=true to inspect silently.",
537
533
  promptSnippet: "Inspect the current Chrome page and get CSS selectors for browser automation.",
538
534
  parameters: Type.Object({
539
535
  targetId: Type.Optional(Type.String()),
540
536
  urlIncludes: Type.Optional(Type.String()),
541
537
  titleIncludes: Type.Optional(Type.String()),
542
538
  maxElements: Type.Optional(Type.Number({ default: MAX_ELEMENTS })),
543
- foreground: Type.Optional(
544
- Type.Boolean({ description: "If true, focus the Chrome window and activate the tab before inspecting. Default false to avoid interrupting the user." }),
539
+ background: Type.Optional(
540
+ Type.Boolean({ description: "If true, run silently in the background without focusing Chrome. Default false (Chrome focuses + tab activates so the user can watch)." }),
545
541
  ),
546
542
  host: Type.Optional(Type.String()),
547
543
  port: Type.Optional(Type.Number()),
@@ -549,7 +545,7 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
549
545
  async execute(_id, params): Promise<ToolTextResult> {
550
546
  const snapshot = await bridge.send(
551
547
  "page.snapshot",
552
- withForeground({ ...params, maxElements: params.maxElements ?? MAX_ELEMENTS }),
548
+ withBackground({ ...params, maxElements: params.maxElements ?? MAX_ELEMENTS }),
553
549
  DEFAULT_TIMEOUT_MS,
554
550
  );
555
551
  return { content: [{ type: "text", text: truncateText(safeJson(snapshot)) }], details: { snapshot } };
@@ -560,7 +556,7 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
560
556
  name: "chrome_navigate",
561
557
  label: "Chrome Navigate",
562
558
  description:
563
- "Navigate an existing Chrome tab to a URL via the companion extension. Navigates in the background by default and does not change the user's currently active tab; pass foreground=true to also focus Chrome and activate the tab. Optionally waits for load completion.",
559
+ "Navigate an existing Chrome tab to a URL via the companion extension. By default focuses Chrome and activates the tab so the user can watch; pass background=true to navigate silently. Optionally waits for load completion.",
564
560
  promptSnippet: "Navigate a Chrome tab in the user's existing profile.",
565
561
  parameters: Type.Object({
566
562
  url: Type.String(),
@@ -569,14 +565,14 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
569
565
  titleIncludes: Type.Optional(Type.String()),
570
566
  waitUntilLoad: Type.Optional(Type.Boolean({ default: true })),
571
567
  timeoutMs: Type.Optional(Type.Number({ default: 15_000 })),
572
- foreground: Type.Optional(
573
- Type.Boolean({ description: "If true, focus the Chrome window and activate the tab before navigating. Default false." }),
568
+ background: Type.Optional(
569
+ Type.Boolean({ description: "If true, navigate silently without focusing Chrome. Default false." }),
574
570
  ),
575
571
  host: Type.Optional(Type.String()),
576
572
  port: Type.Optional(Type.Number()),
577
573
  }),
578
574
  async execute(_id, params): Promise<ToolTextResult> {
579
- const result = await bridge.send("page.navigate", withForeground(params), params.timeoutMs ?? 15_000);
575
+ const result = await bridge.send("page.navigate", withBackground(params), params.timeoutMs ?? 15_000);
580
576
  return { content: [{ type: "text", text: `Navigated to ${params.url}` }], details: { result: result as Json } };
581
577
  },
582
578
  });
@@ -585,7 +581,7 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
585
581
  name: "chrome_evaluate",
586
582
  label: "Chrome Evaluate",
587
583
  description:
588
- "Evaluate JavaScript in an existing Chrome tab through the companion extension. Runs in the page context (background) and returns JSON-serializable values when possible. Pass foreground=true to also focus Chrome and activate the tab.",
584
+ "Evaluate JavaScript in an existing Chrome tab through the companion extension. Runs in the page context and returns JSON-serializable values when possible. By default focuses Chrome and activates the tab; pass background=true to evaluate silently.",
589
585
  promptSnippet: "Evaluate JavaScript in the active Chrome tab through the companion extension.",
590
586
  parameters: Type.Object({
591
587
  expression: Type.String(),
@@ -594,14 +590,14 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
594
590
  targetId: Type.Optional(Type.String()),
595
591
  urlIncludes: Type.Optional(Type.String()),
596
592
  titleIncludes: Type.Optional(Type.String()),
597
- foreground: Type.Optional(
598
- Type.Boolean({ description: "If true, focus the Chrome window and activate the tab before evaluating. Default false." }),
593
+ background: Type.Optional(
594
+ Type.Boolean({ description: "If true, evaluate silently without focusing Chrome. Default false." }),
599
595
  ),
600
596
  host: Type.Optional(Type.String()),
601
597
  port: Type.Optional(Type.Number()),
602
598
  }),
603
599
  async execute(_id, params): Promise<ToolTextResult> {
604
- const value = await bridge.send("page.evaluate", withForeground(params), DEFAULT_TIMEOUT_MS);
600
+ const value = await bridge.send("page.evaluate", withBackground(params), DEFAULT_TIMEOUT_MS);
605
601
  return { content: [{ type: "text", text: truncateText(typeof value === "string" ? value : safeJson(value)) }], details: { value: value as Json } };
606
602
  },
607
603
  });
@@ -610,7 +606,7 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
610
606
  name: "chrome_click",
611
607
  label: "Chrome Click",
612
608
  description:
613
- "Click a CSS selector or viewport coordinate in an existing Chrome tab through the companion extension. The click is dispatched as a synthetic DOM event in the background by default; pass foreground=true to focus Chrome and activate the tab first.",
609
+ "Click a CSS selector or viewport coordinate in an existing Chrome tab through the companion extension. The click is dispatched as a synthetic DOM event; by default Chrome is focused so the user can watch, pass background=true to click silently.",
614
610
  promptSnippet: "Click page elements in Chrome by selector or viewport coordinate.",
615
611
  parameters: Type.Object({
616
612
  selector: Type.Optional(Type.String({ description: "CSS selector to click. Prefer selectors from chrome_snapshot." })),
@@ -619,14 +615,14 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
619
615
  targetId: Type.Optional(Type.String()),
620
616
  urlIncludes: Type.Optional(Type.String()),
621
617
  titleIncludes: Type.Optional(Type.String()),
622
- foreground: Type.Optional(
623
- Type.Boolean({ description: "If true, focus the Chrome window and activate the tab before clicking. Default false." }),
618
+ background: Type.Optional(
619
+ Type.Boolean({ description: "If true, click silently without focusing Chrome. Default false." }),
624
620
  ),
625
621
  host: Type.Optional(Type.String()),
626
622
  port: Type.Optional(Type.Number()),
627
623
  }),
628
624
  async execute(_id, params): Promise<ToolTextResult> {
629
- const result = await bridge.send("page.click", withForeground(params), DEFAULT_TIMEOUT_MS);
625
+ const result = await bridge.send("page.click", withBackground(params), DEFAULT_TIMEOUT_MS);
630
626
  return { content: [{ type: "text", text: `Clicked ${params.selector ?? `${params.x},${params.y}`}` }], details: { result: result as Json } };
631
627
  },
632
628
  });
@@ -635,7 +631,7 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
635
631
  name: "chrome_type",
636
632
  label: "Chrome Type",
637
633
  description:
638
- "Focus an optional CSS selector, then type text into an existing Chrome tab through the companion extension. Runs in the background by default; pass foreground=true to focus Chrome and activate the tab first.",
634
+ "Focus an optional CSS selector, then type text into an existing Chrome tab through the companion extension. By default focuses Chrome and activates the tab so the user can watch; pass background=true to type silently.",
639
635
  promptSnippet: "Type text into Chrome, optionally focusing a selector first.",
640
636
  parameters: Type.Object({
641
637
  text: Type.String(),
@@ -644,14 +640,14 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
644
640
  targetId: Type.Optional(Type.String()),
645
641
  urlIncludes: Type.Optional(Type.String()),
646
642
  titleIncludes: Type.Optional(Type.String()),
647
- foreground: Type.Optional(
648
- Type.Boolean({ description: "If true, focus the Chrome window and activate the tab before typing. Default false." }),
643
+ background: Type.Optional(
644
+ Type.Boolean({ description: "If true, type silently without focusing Chrome. Default false." }),
649
645
  ),
650
646
  host: Type.Optional(Type.String()),
651
647
  port: Type.Optional(Type.Number()),
652
648
  }),
653
649
  async execute(_id, params): Promise<ToolTextResult> {
654
- const result = await bridge.send("page.type", withForeground(params), DEFAULT_TIMEOUT_MS);
650
+ const result = await bridge.send("page.type", withBackground(params), DEFAULT_TIMEOUT_MS);
655
651
  return { content: [{ type: "text", text: `Typed ${params.text.length} character(s)${params.selector ? ` into ${params.selector}` : ""}.` }], details: { result: result as Json } };
656
652
  },
657
653
  });
@@ -660,21 +656,21 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
660
656
  name: "chrome_key",
661
657
  label: "Chrome Key",
662
658
  description:
663
- "Send a keyboard key to an existing Chrome tab (Enter, Escape, Tab, Backspace, Delete, ArrowUp/Down/Left/Right, or one character). Runs in the background by default; pass foreground=true to focus Chrome and activate the tab first.",
659
+ "Send a keyboard key to an existing Chrome tab (Enter, Escape, Tab, Backspace, Delete, ArrowUp/Down/Left/Right, or one character). By default focuses Chrome and activates the tab so the user can watch; pass background=true to send the key silently.",
664
660
  promptSnippet: "Press keys in Chrome through the companion extension.",
665
661
  parameters: Type.Object({
666
662
  key: Type.String(),
667
663
  targetId: Type.Optional(Type.String()),
668
664
  urlIncludes: Type.Optional(Type.String()),
669
665
  titleIncludes: Type.Optional(Type.String()),
670
- foreground: Type.Optional(
671
- Type.Boolean({ description: "If true, focus the Chrome window and activate the tab before sending the key. Default false." }),
666
+ background: Type.Optional(
667
+ Type.Boolean({ description: "If true, send the key silently without focusing Chrome. Default false." }),
672
668
  ),
673
669
  host: Type.Optional(Type.String()),
674
670
  port: Type.Optional(Type.Number()),
675
671
  }),
676
672
  async execute(_id, params): Promise<ToolTextResult> {
677
- const result = await bridge.send("page.key", withForeground(params), DEFAULT_TIMEOUT_MS);
673
+ const result = await bridge.send("page.key", withBackground(params), DEFAULT_TIMEOUT_MS);
678
674
  return { content: [{ type: "text", text: `Pressed ${params.key}.` }], details: { result: result as Json } };
679
675
  },
680
676
  });
@@ -705,7 +701,7 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
705
701
  name: "chrome_screenshot",
706
702
  label: "Chrome Screenshot",
707
703
  description:
708
- "Capture a screenshot of an existing Chrome tab via the companion extension and save it to disk. Chrome's extension screenshot API requires the target tab to be the active tab in its window, so this momentarily activates it (without focusing the window) and restores the previous active tab. Pass foreground=true to also bring the Chrome window to the front.",
704
+ "Capture a screenshot of an existing Chrome tab via the companion extension and save it to disk. Chrome's extension screenshot API requires the target tab to be the active tab in its window. By default Chrome is focused and the tab activates so the user can watch; pass background=true to capture silently (the tab is briefly activated within its window for the capture, then the previous active tab is restored).",
709
705
  promptSnippet: "Capture Chrome screenshots and save them under .pi/chrome-screenshots by default.",
710
706
  parameters: Type.Object({
711
707
  path: Type.Optional(Type.String({ description: "Output path. Defaults to .pi/chrome-screenshots/<timestamp>.<format>." })),
@@ -715,8 +711,8 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
715
711
  targetId: Type.Optional(Type.String()),
716
712
  urlIncludes: Type.Optional(Type.String()),
717
713
  titleIncludes: Type.Optional(Type.String()),
718
- foreground: Type.Optional(
719
- Type.Boolean({ description: "If true, focus the Chrome window and activate the tab before capturing. Default false." }),
714
+ background: Type.Optional(
715
+ Type.Boolean({ description: "If true, capture silently without focusing the Chrome window (the target tab is briefly activated within its window for the capture, then restored). Default false." }),
720
716
  ),
721
717
  host: Type.Optional(Type.String()),
722
718
  port: Type.Optional(Type.Number()),
@@ -726,7 +722,7 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
726
722
  const cwd = workspaceCwd(ctx);
727
723
  const defaultPath = join(cwd, ".pi", "chrome-screenshots", `${new Date().toISOString().replace(/[:.]/g, "-")}.${format}`);
728
724
  const outputPath = params.path ? resolve(cwd, params.path) : defaultPath;
729
- const result = (await bridge.send("page.screenshot", withForeground(params), DEFAULT_TIMEOUT_MS)) as { dataUrl: string; tab?: unknown };
725
+ const result = (await bridge.send("page.screenshot", withBackground(params), DEFAULT_TIMEOUT_MS)) as { dataUrl: string; tab?: unknown };
730
726
  const base64 = result.dataUrl.replace(/^data:image\/(?:png|jpeg);base64,/, "");
731
727
  await mkdir(dirname(outputPath), { recursive: true });
732
728
  await writeFile(outputPath, Buffer.from(base64, "base64"));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-chrome",
3
- "version": "0.4.4",
4
- "description": "Drive your existing logged-in Chrome from Pi \u2014 no re-login, no throwaway profile, background by default.",
3
+ "version": "0.6.0",
4
+ "description": "Drive your existing logged-in Chrome from Pi \u2014 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",
7
7
  "pi-extension",