pi-chrome 0.4.3 → 0.5.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
@@ -13,7 +13,7 @@ Multiple Pi sessions can use Chrome at the same time. The first Pi session start
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
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.
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,14 +53,16 @@ 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
68
  ## Foreground control
@@ -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.3",
4
+ "version": "0.5.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.3";
49
+ const PI_CHROME_VERSION = "0.5.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;
@@ -274,7 +274,24 @@ class ChromeProfileBridge {
274
274
  if (request.method === "GET" && url.pathname === "/next") {
275
275
  this.lastSeenAt = Date.now();
276
276
  this.clientName = url.searchParams.get("name") ?? undefined;
277
- const command = this.queue.shift() ?? (await this.waitForCommand(25_000));
277
+ let aborted = false;
278
+ let activeWaiter: ((command: BridgeCommand | undefined) => void) | undefined;
279
+ request.once("close", () => {
280
+ aborted = true;
281
+ if (activeWaiter) this.waiters = this.waiters.filter((entry) => entry !== activeWaiter);
282
+ });
283
+ let command = this.queue.shift();
284
+ if (!command) {
285
+ command = await this.waitForCommand(25_000, (waiter) => {
286
+ activeWaiter = waiter;
287
+ });
288
+ }
289
+ if (aborted) {
290
+ // Long-poll connection died before we could deliver. Requeue any command we pulled
291
+ // so the next live /next picks it up instead of dropping it on the floor.
292
+ if (command) this.queue.unshift(command);
293
+ return;
294
+ }
278
295
  sendJson(response, 200, command ? { type: "command", command } : { type: "none" });
279
296
  return;
280
297
  }
@@ -296,16 +313,22 @@ class ChromeProfileBridge {
296
313
  sendJson(response, 404, { error: "not found" });
297
314
  }
298
315
 
299
- private waitForCommand(timeoutMs: number): Promise<BridgeCommand | undefined> {
316
+ private waitForCommand(
317
+ timeoutMs: number,
318
+ registerWaiter?: (waiter: (command: BridgeCommand | undefined) => void) => void,
319
+ ): Promise<BridgeCommand | undefined> {
300
320
  return new Promise((resolveWait) => {
301
- const timer = setTimeout(() => {
302
- this.waiters = this.waiters.filter((waiter) => waiter !== resolveWait);
303
- resolveWait(undefined);
304
- }, timeoutMs);
305
- this.waiters.push((command) => {
321
+ let settled = false;
322
+ const waiter = (command: BridgeCommand | undefined) => {
323
+ if (settled) return;
324
+ settled = true;
306
325
  clearTimeout(timer);
326
+ this.waiters = this.waiters.filter((entry) => entry !== waiter);
307
327
  resolveWait(command);
308
- });
328
+ };
329
+ const timer = setTimeout(() => waiter(undefined), timeoutMs);
330
+ this.waiters.push(waiter);
331
+ registerWaiter?.(waiter);
309
332
  });
310
333
  }
311
334
  }
@@ -347,31 +370,16 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
347
370
  return { systemPrompt: event.systemPrompt + primer };
348
371
  });
349
372
 
350
- pi.registerCommand("chrome-status", {
351
- description: "Run an explicit health check against the existing-profile Chrome companion extension",
352
- handler: async (_args, ctx) => {
353
- ctx.ui.notify("Performing Chrome bridge health check", "info");
354
- try {
355
- const version = (await bridge.send("tab.version", {}, 35_000)) as { extensionId?: string; extensionVersion?: string };
356
- const suffix = [version.extensionId ? `ID: ${version.extensionId}` : null, version.extensionVersion ? `ext v${version.extensionVersion}` : null]
357
- .filter(Boolean)
358
- .join(", ");
359
- ctx.ui.notify(suffix ? `Chrome profile bridge connected (${suffix})` : "Chrome profile bridge connected", "info");
360
- } catch (error) {
361
- ctx.ui.notify(`Chrome bridge health check failed: ${(error as Error).message}`, "warning");
362
- }
363
- },
364
- });
365
-
366
373
  pi.registerCommand("chrome-doctor", {
367
374
  description:
368
- "Diagnose Chrome bridge setup. Checks the local bridge, the companion Chrome extension, and reports a one-line fix for common failures.",
375
+ "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).",
369
376
  handler: async (_args, ctx) => {
377
+ ctx.ui.notify("Performing Chrome bridge health check", "info");
370
378
  const lines: string[] = [`pi-chrome v${PI_CHROME_VERSION}`];
371
379
  const status = bridge.status();
372
380
  lines.push(`• Local bridge: mode=${status.mode}, url=${status.url}`);
373
381
  try {
374
- const version = (await bridge.send("tab.version", {}, 8_000)) as {
382
+ const version = (await bridge.send("tab.version", {}, 35_000)) as {
375
383
  extensionId?: string;
376
384
  extensionVersion?: string;
377
385
  };
@@ -440,7 +448,7 @@ If chrome_* tools time out, ask the user to run /chrome-onboard, then load the b
440
448
  await pi.exec("sh", ["-lc", `printf %s ${JSON.stringify(extensionPath)} | pbcopy`], { cwd: workspaceCwd(ctx), timeout: 5_000 }).catch(() => undefined);
441
449
  }
442
450
  ctx.ui.notify(
443
- "Chrome bridge setup opened. The extension path has been copied to your clipboard. After loading it, run /chrome-status.",
451
+ "Chrome bridge setup opened. The extension path has been copied to your clipboard. After loading it, run /chrome-doctor.",
444
452
  "info",
445
453
  );
446
454
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-chrome",
3
- "version": "0.4.3",
3
+ "version": "0.5.0",
4
4
  "description": "Drive your existing logged-in Chrome from Pi \u2014 no re-login, no throwaway profile, background by default.",
5
5
  "keywords": [
6
6
  "pi-package",