pi-chrome 0.11.1 → 0.11.3

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.11.1",
4
+ "version": "0.11.3",
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/*"],
@@ -169,7 +169,7 @@ async function cdpMoveTo(tabId, x, y) {
169
169
  const entry = attachedTabs.get(tabId);
170
170
  const startX = entry?.pointer?.x ?? Math.max(20, Math.min(400, x - 200));
171
171
  const startY = entry?.pointer?.y ?? Math.max(20, Math.min(400, y - 200));
172
- const n = Math.max(10, Math.min(36, Math.round(Math.hypot(x - startX, y - startY) / 20)));
172
+ const n = Math.max(18, Math.min(42, Math.round(Math.hypot(x - startX, y - startY) / 18)));
173
173
  for (let i = 1; i <= n; i++) {
174
174
  const t = i / n;
175
175
  const ease = t * t * (3 - 2 * t);
@@ -367,19 +367,20 @@ async function trustedScroll(params) {
367
367
  const x = resolved.rect ? resolved.rect.left + Math.min(resolved.rect.width, 800) / 2 : resolved.x;
368
368
  const y = resolved.rect ? resolved.rect.top + Math.min(resolved.rect.height, 600) / 2 : resolved.y;
369
369
  const totalY = params.deltaY || 0, totalX = params.deltaX || 0;
370
- // Cap per-step delta so IntersectionObserver / scroll-driven animations get gradient samples.
371
- // Real wheel notches ~50-120px; we aim ~40-90.
372
- const MAX_STEP = 80;
373
- const minStepsByDelta = Math.ceil(Math.max(Math.abs(totalY), Math.abs(totalX)) / MAX_STEP);
374
- const n = Math.max(6, Math.min(60, params.steps || Math.max(minStepsByDelta, 12)));
375
- // Momentum shape: ramp-up, plateau, decay. Each weight peaked mid-sequence then tapers.
370
+ // Per-event delta cap so IntersectionObserver / scroll-driven animations get gradient samples.
371
+ // A trackpad inertia tick often delivers ~10-30px per frame; using ~25px keeps small-target
372
+ // visibility transitions detectable while not making large scrolls take forever.
373
+ const MAX_STEP = 25;
374
+ const peak = Math.max(Math.abs(totalY), Math.abs(totalX));
375
+ // Front-loaded weights peak at ~1. average, so choose n so peak event stays under MAX_STEP.
376
+ const minN = Math.ceil(peak * 1.5 / MAX_STEP);
377
+ const n = Math.max(6, Math.min(200, params.steps || Math.max(minN, 12)));
378
+ // Front-loaded but smooth weights: w[i] = 1 + 0.5 * (1 - i/(n-1)) so the first event has
379
+ // weight 1.5, the last has 1.0, average ~1.25; redistribution stays predictable.
376
380
  const w = [];
377
381
  for (let i = 0; i < n; i++) {
378
382
  const t = i / Math.max(1, n - 1);
379
- // bell-ish curve biased earlier so motion starts strong then decays (like a trackpad flick).
380
- const base = Math.sin(Math.min(1, t * 1.4) * Math.PI);
381
- const decay = Math.pow(1 - t * 0.6, 2);
382
- w.push(0.2 + base * 0.8 * decay);
383
+ w.push(1 + 0.5 * (1 - t));
383
384
  }
384
385
  const sumW = w.reduce((a, b) => a + b, 0);
385
386
  for (let i = 0; i < n; i++) {
@@ -387,8 +388,8 @@ async function trustedScroll(params) {
387
388
  await cdp(tab.id, "Input.dispatchMouseEvent", {
388
389
  type: "mouseWheel", x, y, deltaX: dx, deltaY: dy, pointerType: "mouse",
389
390
  });
390
- // Sleep ~one frame+ so IntersectionObserver / rAF samples can run between events.
391
- await sleep(rng(22, 52));
391
+ // Sleep one+ frame so IntersectionObserver / rAF samples can run between events.
392
+ await sleep(rng(22, 48));
392
393
  }
393
394
  return { trusted: true, deltaX: totalX, deltaY: totalY, steps: n };
394
395
  }
@@ -509,7 +509,7 @@ Usage rules:
509
509
  lines.push(`… Skipped MAIN-world capability checks because the loaded extension is stale.`);
510
510
  }
511
511
 
512
- // Trusted-input (chrome.debugger) probe.
512
+ // Real-input mode probe (plain English for the user).
513
513
  if (extensionAlive && !versionMismatch) {
514
514
  try {
515
515
  const status = (await bridge.send("trusted.status", {}, 5_000)) as {
@@ -518,19 +518,20 @@ Usage rules:
518
518
  permissionGranted?: boolean;
519
519
  };
520
520
  if (status.permissionGranted) {
521
- const attached = status.attachedTabs && status.attachedTabs.length ? `; attached to tab ${status.attachedTabs.join(",")}` : "";
521
+ const banner = status.attachedTabs && status.attachedTabs.length ? ` (yellow banner up on ${status.attachedTabs.length} tab(s))` : "";
522
522
  const note =
523
523
  status.mode === "auto"
524
- ? " smart-retry enabled: synthetic input runs first; if a click/type produced no page change AND the target looks gated, the call is automatically re-run with trusted CDP (yellow debugger banner appears only for that retry)."
524
+ ? " Clicks/keys are quiet by default; if a site rejects a quiet click, pi-chrome retries it once with a real-looking click. Yellow 'pi-chrome is controlling Chrome' banner shows only when that retry happens."
525
525
  : status.mode === "on"
526
- ? " every chrome_* call goes through CDP; the yellow debugger banner is visible while attached."
527
- : " synthetic events only; pass trusted=true on chrome_click/type/etc, or switch to auto/on with /chrome-trusted, when isTrusted or user-activation gates matter.";
528
- lines.push(`✓ Trusted-input mode available via chrome.debugger (current: ${status.mode ?? "off"}${attached}).${note}`);
526
+ ? " Every click and keystroke uses a real-looking event. Yellow 'pi-chrome is controlling Chrome' banner stays up on every tab pi-chrome touches."
527
+ : " All clicks are quiet, no yellow banner. Some sites (sign-ins, copy buttons, file pickers, paywalls) may silently ignore them. Switch to /chrome-trusted auto if a site isn't responding.";
528
+ const label = status.mode === "auto" ? "auto (smart upgrade)" : status.mode === "on" ? "on (always real-looking)" : "off (always quiet)";
529
+ lines.push(`✓ Click mode: ${label}${banner}.${note}`);
529
530
  } else {
530
- lines.push(`⚠ chrome.debugger API unavailable. The extension is missing the "debugger" permission reload the extension in chrome://extensions and accept the new permission prompt.`);
531
+ lines.push(`⚠ Can't send real-looking clicks yet — the companion extension is missing a permission. Open chrome://extensions, click reload on 'Pi Chrome Connector', and accept the new permission prompt.`);
531
532
  }
532
533
  } catch (error) {
533
- lines.push(`⚠ trusted.status probe failed: ${(error as Error).message}`);
534
+ lines.push(`⚠ Couldn't check click mode: ${(error as Error).message}`);
534
535
  }
535
536
  }
536
537
 
@@ -540,13 +541,13 @@ Usage rules:
540
541
 
541
542
  pi.registerCommand("chrome-trusted", {
542
543
  description:
543
- "Toggle trusted-input mode for chrome_* tools. ON: all clicks/types/etc go through chrome.debugger (CDP) so events are browser-trusted (isTrusted=true) and satisfy user-activation gates (clipboard, fullscreen, autoplay, file picker). Tradeoff: Chrome pins a yellow 'started debugging this browser' banner to the top of any tab in use. OFF (default): synthetic DOM events. AUTO: attach on demand only when a per-call trusted=true is passed. STATUS: print current mode and attached tabs.",
544
+ "Choose how realistically pi-chrome should drive Chrome. Real-looking clicks/keys unlock things like copy-to-clipboard buttons, file pickers, and sign-in pages, but show a yellow 'pi-chrome is controlling Chrome' banner. Three modes:\n auto (default) quiet by default; auto-upgrade when a site blocks the quiet click.\n off always quiet, no banner; some sites won't accept these clicks.\n on always real-looking, banner stays up the whole session.\n status show the current mode.",
544
545
  getArgumentCompletions: (prefix) => {
545
546
  const items = [
546
- { value: "on", label: "on", description: "All chrome_* tools dispatch via CDP. Yellow debugger banner appears." },
547
- { value: "off", label: "off", description: "Synthetic events only (default)." },
548
- { value: "auto", label: "auto", description: "Use CDP only when a tool passes trusted=true." },
549
- { value: "status", label: "status", description: "Show current trusted mode and any attached tabs." },
547
+ { value: "auto", label: "auto", description: "Default. Quiet clicks; upgrade to real ones only when a site rejects them." },
548
+ { value: "off", label: "off", description: "Always quiet. No banner. Some sites won't accept the clicks." },
549
+ { value: "on", label: "on", description: "Always real-looking clicks. Yellow banner stays up. Best for stubborn sites." },
550
+ { value: "status", label: "status", description: "Show the current mode." },
550
551
  ];
551
552
  const lowered = prefix.toLowerCase();
552
553
  const matches = items.filter((item) => item.value.startsWith(lowered));
@@ -560,37 +561,43 @@ Usage rules:
560
561
  try {
561
562
  status = (await bridge.send("trusted.status", {}, 5_000)) as typeof status;
562
563
  } catch (error) {
563
- ctx.ui.notify(`Failed to read trusted mode: ${(error as Error).message}`, "warning");
564
+ ctx.ui.notify(`Couldn't check current mode: ${(error as Error).message}`, "warning");
564
565
  return;
565
566
  }
566
567
  if (!status) return;
567
568
 
568
569
  if (!status.permissionGranted) {
569
570
  ctx.ui.notify(
570
- "chrome.debugger API unavailable — the extension is missing the 'debugger' permission. Open chrome://extensions, reload 'Pi Chrome Connector', and accept the new permission prompt.",
571
+ "pi-chrome can't drive real-looking clicks yet — the companion extension is missing a permission. Open chrome://extensions, click reload on 'Pi Chrome Connector', and accept the new permission prompt that appears.",
571
572
  "warning",
572
573
  );
573
574
  return;
574
575
  }
575
576
 
576
- const attached = status.attachedTabs?.length ? ` — currently attached to tab ${status.attachedTabs.join(",")}` : "";
577
+ const MODE_NAMES: Record<string, string> = {
578
+ auto: "auto (smart upgrade)",
579
+ off: "off (always quiet)",
580
+ on: "on (always real-looking)",
581
+ };
582
+ const friendly = (m: string) => MODE_NAMES[m] ?? m;
583
+ const attached = status.attachedTabs?.length ? ` — yellow banner currently up on ${status.attachedTabs.length} tab(s)` : "";
577
584
  const current = status.mode;
578
585
 
579
586
  let target = rawArg;
580
587
  if (target === "status") {
581
- ctx.ui.notify(`Trusted-input mode: ${current}${attached}`, "info");
588
+ ctx.ui.notify(`Current mode: ${friendly(current)}${attached}`, "info");
582
589
  return;
583
590
  }
584
591
  if (!target) {
585
- // Interactive picker. Show current mode + tradeoffs in each label.
592
+ // Interactive picker. Plain-English descriptions; no jargon.
586
593
  const options = [
587
- `auto${current === "auto" ? " (current)" : ""} — default; synthetic first, retry with CDP only when a call looks gated`,
588
- `off${current === "off" ? " (current)" : ""} — synthetic DOM events only; never auto-retry`,
589
- `on${current === "on" ? " (current)" : ""} — every chrome_* call goes through CDP (yellow debugger banner permanently visible)`,
590
- `status — print current mode and any attached tabs\u2026`,
594
+ `auto${current === "auto" ? " (current)" : ""} — quiet by default; if a site rejects a quiet click, retry it once with a real-looking click. Yellow banner appears only when needed. Recommended for everyday use.`,
595
+ `off${current === "off" ? " (current)" : ""} — always quiet. No yellow banner, ever. Fast and unobtrusive, but some sites (sign-in pages, copy-to-clipboard buttons, file pickers, paywalls) will silently ignore the click.`,
596
+ `on${current === "on" ? " (current)" : ""} — every click and keystroke uses a real-looking event. Yellow 'pi-chrome is controlling Chrome' banner stays at the top of every Chrome window the whole session. Best when working a stubborn site for a long stretch.`,
597
+ `status — just show me which mode is on right now.`,
591
598
  ];
592
599
  const picked = await ctx.ui.select(
593
- `Trusted-input mode (current: ${current}${attached})`,
600
+ `How realistic should pi-chrome's clicks be? (current: ${friendly(current)}${attached})`,
594
601
  options,
595
602
  );
596
603
  if (!picked) return; // cancelled
@@ -598,29 +605,29 @@ Usage rules:
598
605
  else if (picked.startsWith("off")) target = "off";
599
606
  else if (picked.startsWith("auto")) target = "auto";
600
607
  else if (picked.startsWith("status")) {
601
- ctx.ui.notify(`Trusted-input mode: ${current}${attached}`, "info");
608
+ ctx.ui.notify(`Current mode: ${friendly(current)}${attached}`, "info");
602
609
  return;
603
610
  }
604
611
  }
605
612
 
606
613
  if (!["on", "off", "auto"].includes(target)) {
607
- ctx.ui.notify(`Unknown argument '${rawArg}'. Use: on | off | auto | status, or run /chrome-trusted with no args for a picker.`, "warning");
614
+ ctx.ui.notify(`Unknown choice '${rawArg}'. Pick one of: auto | off | on | status, or just run /chrome-trusted with no argument.`, "warning");
608
615
  return;
609
616
  }
610
617
 
611
618
  if (target === current) {
612
- ctx.ui.notify(`Trusted-input mode already ${current}.`, "info");
619
+ ctx.ui.notify(`Already in ${friendly(current)} mode.`, "info");
613
620
  return;
614
621
  }
615
622
 
616
- // Extra confirmation only on first-time "on" (warn about banner).
617
- if (target === "on" && current === "off") {
623
+ // Extra confirmation only on first-time "on" (warn about persistent banner).
624
+ if (target === "on" && current !== "on") {
618
625
  const ok = await ctx.ui.confirm(
619
- "Turn on trusted-input mode?",
620
- "All chrome_* tools will dispatch through chrome.debugger (CDP). Events will arrive as isTrusted=true and satisfy user-activation gates (clipboard, fullscreen, autoplay, file picker).\n\nChrome will pin a yellow 'Pi Chrome Connector started debugging this browser' banner to the top of any debugged tab while attached. Clicking 'Cancel' on that banner detaches the debugger.",
626
+ "Always use real-looking clicks?",
627
+ "Every click and keystroke pi-chrome sends will now look like a real human action to websites. This unlocks copy-to-clipboard buttons, sign-in pages, file pickers, fullscreen, autoplay, and most bot-protected sites.\n\nThe tradeoff: Chrome will pin a yellow bar at the top of every Chrome window saying 'pi-chrome is controlling Chrome'. The bar stays visible for the rest of your pi session (or until you switch back to auto/off). Clicking 'Cancel' on the bar interrupts pi-chrome.",
621
628
  );
622
629
  if (!ok) {
623
- ctx.ui.notify("Trusted-input mode unchanged.", "info");
630
+ ctx.ui.notify("Mode unchanged.", "info");
624
631
  return;
625
632
  }
626
633
  }
@@ -629,16 +636,16 @@ Usage rules:
629
636
  const result = (await bridge.send("trusted.mode", { mode: target }, 5_000)) as { mode: string };
630
637
  if (result.mode === "on") {
631
638
  ctx.ui.notify(
632
- "Trusted-input mode ON. chrome_* tools now dispatch through chrome.debugger. The yellow debugger banner will appear when Chrome is next driven.",
639
+ "On. Every click and keystroke now looks real to websites. The yellow 'pi-chrome is controlling Chrome' banner will appear on every tab pi-chrome touches.",
633
640
  "info",
634
641
  );
635
642
  } else if (result.mode === "off") {
636
- ctx.ui.notify("Trusted-input mode OFF. Synthetic events only. Any attached debugger sessions detached.", "info");
643
+ ctx.ui.notify("Off. All clicks are quiet now, no yellow banner. Note: some sites (sign-ins, copy buttons, file pickers, paywalls) may silently ignore these clicks.", "info");
637
644
  } else {
638
- ctx.ui.notify("Trusted-input mode AUTO. CDP attaches only when a tool passes trusted=true.", "info");
645
+ ctx.ui.notify("Auto. Clicks stay quiet by default; pi-chrome will only switch to real-looking clicks when a site rejects a quiet one. The yellow banner will appear only when that retry happens.", "info");
639
646
  }
640
647
  } catch (error) {
641
- ctx.ui.notify(`Failed to set trusted mode: ${(error as Error).message}`, "warning");
648
+ ctx.ui.notify(`Couldn't switch mode: ${(error as Error).message}`, "warning");
642
649
  }
643
650
  },
644
651
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-chrome",
3
- "version": "0.11.1",
3
+ "version": "0.11.3",
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",