all repos — sz-chrome-remote-extension @ e88e8a3a7a2258c5e7152da192effd8c788ae677

pi-chrome, patched to drive a signed-in browser on another host through a broker, with an MCP server

pi-chrome 0.14.9: trusted-mode primer + auto-sync manifest version

- Primer teaches agents the trusted:true escape hatch for CSP-locked pages
  (github.com, banks, many SaaS) where chrome_evaluate/chrome_snapshot throw
  EvalError due to missing 'unsafe-eval'. Agents now self-escalate instead
  of giving up and asking the user.
- Corrects stale primer claim that chrome_evaluate works without 'unsafe-eval'.
- scripts/sync-manifest-version.js wired to npm version + prepublishOnly
  lifecycle hooks; kills the recurring browser-extension manifest version
  drift (cf. 0.14.4, 0.14.8).
Tianren Dong tianrendong.dtr@gmail.com
Wed, 13 May 2026 07:51:17 -0400
commit

e88e8a3a7a2258c5e7152da192effd8c788ae677

parent

2ebc7e8003d7b6c9d39074eb6d96831d56701117

A .gitignore

@@ -0,0 +1,1 @@

+.DS_Store
M CHANGELOG.md → CHANGELOG.md

@@ -2,6 +2,11 @@ # Changelog

All notable user-facing changes to `pi-chrome`. +## 0.14.9 + +- Primer (agent system prompt) now teaches the **trusted-mode escape hatch** explicitly. Previously the bridge would hit a CSP-locked page (github.com, banks, many SaaS apps), `chrome_evaluate`/`chrome_snapshot` would throw `EvalError: 'unsafe-eval' is not an allowed source of script`, and the agent would conclude *"bridge can't drive this page"* and ask the user for a fallback. New primer makes three things self-discoverable: (1) `trusted: true` on click/type/key/fill/hover/drag/scroll dispatches through chrome.debugger / CDP and bypasses page CSP entirely, (2) the recipe for strict-CSP pages is `chrome_screenshot` + trusted input at viewport coordinates, (3) when synthetic input produces no `pageMutated` or you see a CSP/eval error, **escalate to `trusted: true` yourself instead of asking the user**. Also corrects the old claim that `chrome_evaluate` works without `'unsafe-eval'` (it does not — Function constructor is gated by `script-src`). +- Add `scripts/sync-manifest-version.js` wired to npm's `version` + `prepublishOnly` lifecycle hooks. Bumping the package version with `npm version <bump>` now auto-syncs `extensions/chrome-profile-bridge/browser-extension/manifest.json` and stages it into the version commit — kills the recurring drift class (cf. 0.14.4, 0.14.8, this fix). + ## 0.14.8 - Repo moved to its own home: https://github.com/tianrendong/pi-chrome. No code changes; updated `repository`, `homepage`, and `bugs` URLs in `package.json`.
M extensions/chrome-profile-bridge/browser-extension/manifest.json → extensions/chrome-profile-bridge/browser-extension/manifest.json

@@ -1,10 +1,21 @@

{ "manifest_version": 3, "name": "Pi Chrome Connector", - "version": "0.14.7", + "version": "0.14.9", "description": "Lets Pi control tabs in Chrome via a local connector at 127.0.0.1.", - "permissions": ["tabs", "scripting", "storage", "activeTab", "alarms", "webNavigation", "debugger"], - "host_permissions": ["<all_urls>", "http://127.0.0.1:17318/*"], + "permissions": [ + "tabs", + "scripting", + "storage", + "activeTab", + "alarms", + "webNavigation", + "debugger" + ], + "host_permissions": [ + "<all_urls>", + "http://127.0.0.1:17318/*" + ], "background": { "service_worker": "service_worker.js" },
M extensions/chrome-profile-bridge/index.ts → extensions/chrome-profile-bridge/index.ts

@@ -436,9 +436,10 @@ <chrome-profile-bridge>

Chrome control is available through the chrome_* tools via a companion Chrome extension installed in the user's normal Chrome profile. Tools target the existing signed-in profile, no CDP, no throwaway profile. Capability model (important): -- All input is **synthetic DOM events** (\`isTrusted=false\`). Synthetic events drive React/Vue/Angular state fine, but they do NOT satisfy Chrome's user-activation gates: audio/video autoplay, clipboard write, file pickers, fullscreen, and Web Push prompts will NOT open from a chrome_click. -- \`chrome_evaluate\` runs in MAIN world via the Function constructor. It works on pages with strict CSP (\`script-src 'self'\` without \`'unsafe-eval'\`), and surfaces thrown exceptions. -- Tool results include \`pageMutated\`, \`defaultPrevented\`, \`elementVisible\`, \`occludedBy\`, and (for type/fill) \`valueMatches\`. If \`pageMutated\` is false after a click that should have changed something, the click likely didn't take effect — do NOT just retry; check the action result and snapshot for the cause. +- Default input path is **synthetic DOM events** (\`isTrusted=false\`). Synthetic events drive React/Vue/Angular state fine, but they do NOT satisfy Chrome's user-activation gates: audio/video autoplay, clipboard write, file pickers, fullscreen, and Web Push prompts will NOT open from a synthetic chrome_click. +- **Trusted escape hatch**: chrome_click / chrome_type / chrome_key / chrome_fill / chrome_hover / chrome_drag / chrome_scroll all accept \`trusted: true\`, which dispatches through chrome.debugger / CDP. Trusted events are browser-trusted (\`isTrusted=true\`) and **bypass page CSP entirely** because they're injected at the input layer, not via JS. Default mode is \`auto\`: synthetic first, silent CDP retry only when the click looks gated. If a synthetic click/type produced no \`pageMutated\` or you got a CSP/eval error from chrome_evaluate, escalate to \`trusted: true\` yourself — don't ask the user. +- \`chrome_evaluate\` and \`chrome_snapshot\` run in MAIN world via the **Function constructor**, which requires \`'unsafe-eval'\` in the page CSP. Pages with strict CSP (e.g. github.com, many bank/SaaS apps) will throw \`EvalError: ... 'unsafe-eval' is not an allowed source of script\` and chrome_snapshot will return empty. On those pages, drive the page with \`chrome_screenshot\` (extension API, not gated by CSP) + \`chrome_click\`/\`chrome_type\`/\`chrome_key\` with \`trusted: true\` and viewport coordinates. \`chrome_navigate\`, \`chrome_screenshot\`, \`chrome_tab\`, and trusted input all keep working under any CSP. +- Tool results include \`pageMutated\`, \`defaultPrevented\`, \`elementVisible\`, \`occludedBy\`, and (for type/fill) \`valueMatches\`. If \`pageMutated\` is false after a click that should have changed something, the click likely didn't take effect — do NOT just retry the same way; either escalate to \`trusted: true\` or check the snapshot for occlusion. Usage rules: 1. \`chrome_snapshot\` before clicking/typing; pass \`uid\` over \`selector\`.

@@ -446,7 +447,7 @@ 2. \`includeSnapshot=true\` on click/type/fill to verify in one round trip.

3. If \`chrome_evaluate\` returns null when you expected a value, the expression evaluated to null/undefined in the page; surface the value via \`JSON.stringify\` to confirm. 4. \`chrome_navigate\` supports an optional \`initScript\` that runs at document_start in MAIN world for the next navigation (good for seeding localStorage or stubbing Date.now). 5. By default chrome_* tools focus Chrome so the user can watch; pass \`background=true\` or run /chrome quiet to silence the whole session. -6. If you hit an autoplay/clipboard/file-picker gate, tell the user; this bridge cannot satisfy it. +6. If you hit an autoplay/clipboard/file-picker gate, tell the user; this bridge cannot satisfy it. (Generic clicks/typing/CSP gates are fine — escalate to \`trusted: true\`.) 7. Run /chrome doctor when in doubt about connectivity or capabilities. </chrome-profile-bridge>`; return { systemPrompt: event.systemPrompt + primer };
M package.json → package.json

@@ -1,6 +1,10 @@

{ "name": "pi-chrome", - "version": "0.14.8", + "version": "0.14.9", + "scripts": { + "version": "node scripts/sync-manifest-version.js", + "prepublishOnly": "node scripts/sync-manifest-version.js" + }, "description": "The de-facto browser automation toolkit for Pi agents. Drive your existing logged-in Chrome — no re-login, no throwaway profile, no CDP. 20+ tools (click, type, navigate, screenshot, network capture, file upload, drag, scroll, touch) + honest result envelopes + a built-in benchmark suite.", "keywords": [ "pi",
A scripts/sync-manifest-version.js

@@ -0,0 +1,37 @@

+#!/usr/bin/env node +// Sync browser-extension/manifest.json version from package.json. +// Run automatically by the `version` npm lifecycle hook (npm version <bump>), +// and pre-publish, so the Chrome extension version never drifts from the npm +// package version. +const { readFileSync, writeFileSync } = require("node:fs"); +const { join } = require("node:path"); + +const pkgPath = join(__dirname, "..", "package.json"); +const manifestPath = join( + __dirname, + "..", + "extensions", + "chrome-profile-bridge", + "browser-extension", + "manifest.json", +); + +const pkg = JSON.parse(readFileSync(pkgPath, "utf8")); +const manifest = JSON.parse(readFileSync(manifestPath, "utf8")); + +if (manifest.version === pkg.version) { + console.log(`manifest.json already at ${pkg.version}`); + process.exit(0); +} + +const prev = manifest.version; +manifest.version = pkg.version; +writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n"); +console.log(`manifest.json ${prev} -> ${pkg.version}`); + +// Stage so `npm version` includes manifest bump in the version commit. +try { + require("node:child_process").execSync(`git add "${manifestPath}"`, { stdio: "ignore" }); +} catch { + /* not in a git checkout - ok */ +}