Skip to content

Architecture: Browy Chromium extension (frontend)

The Browy extension is a Chromium MV3 extension that hosts three UI surfaces (side panel, DevTools panel, options page) and talks to the native host over a single native-messaging port.

extension/
├── manifest.json ← MV3 manifest, permissions, background SW
├── background.js ← service worker, port owner, CDP bridge
├── sidepanel.html / .js ← chat UI (default surface)
├── marked.min.js ← Markdown renderer for the side panel
├── devtools.html / .js ← registers the DevTools panel
├── panel.html / .js / .css ← the actual DevTools CLI surface
├── options.html / .js ← Settings (model, tools, install help)
└── icons/ ← toolbar + store assets

A single chrome.runtime.connectNative('com.browy.host') port lives on the service worker. It’s the one and only channel to the host. Each UI surface opens its own chrome.runtime.connect port to the service worker (sidepanel-<random>, devtools-panel-<random> or options). The service worker forwards their messages to the host and routes each reply to the surface that owns its sessionId. Host messages without a session id, such as models.current and auth.status, go to every surface.

Side panel: `sidepanel.html`

The default chat UI: streamed replies with their tool steps, a past-chats overlay that loads transcripts from the host, Markdown export and a settings button that opens the options page. Most users live here.

DevTools panel: `panel.html`

The terminal-style REPL. Registered by devtools.js via chrome.devtools.panels.create('Browy', …). Shares the agent backend with the side panel and is bound to the inspected target. See the DevTools CLI page.

Options: `options.html`

Model picker, tool toggles, sign-in status, and install help when the host isn’t reachable. Opens via Chrome’s standard extension options entry or Browy’s toolbar menu.

Tools run in the host. When one needs the page, the host sends a cdp.send message, and the SW:

  1. Picks the tab: the tabId in the message if the host pinned one, else the tab bound to the session (a DevTools panel’s inspected tab), else the active tab in the side panel’s own window (sent as windowId with session.start), and the active tab in the last-focused window only when the panel didn’t say.
  2. Calls chrome.debugger.attach({tabId}, '1.3') if it isn’t already attached.
  3. Runs the command with chrome.debugger.sendCommand (Runtime.evaluate for an inspect_page snapshot, Input.dispatchMouseEvent for click_index, Network.enable, etc.) and leaves shaping the result to the host.
  4. Posts {type: 'tool.result', sessionId, callId, ok, result} back over the port.

tab.new, tab.activate, tab.close, tab.list and tab.getActive go through chrome.tabs the same way, and events from attached tabs flow back to the host as cdp.event.

The Yellow Banner of Doom (Chrome’s “Browy started debugging…” notice) is Chrome’s mandatory disclosure for any extension using the DevTools Protocol. We don’t try to suppress it. See Permissions → debugger.

The extension keeps its own state in chrome.storage.local:

  • Current chat: browy.sessionId for the side panel and browy.dt.sessionId.<tabId> for each inspected tab’s DevTools panel. The id is also the chat’s Copilot session id.
  • Chat display cache: browy.chat.<id> (rendered HTML), browy.chatMeta.<id> → {id, title, updated, preview} and browy.chatIndex (ids, newest first)
  • Settings: settings.tools (per-tool toggles)
  • UI flags: browy.firstSendDone (hides the starter prompts after your first message) and browy.hostUpdateSnoozeUntil (snoozes the backend-update banner)

The transcripts themselves live in the Copilot CLI’s session store under ~/.copilot/, and the host saves your model choice in ~/.browy/data/prefs.json. chrome.storage.local is stored in this browser profile and is cleared when the extension is removed.

The manifest declares no content scripts. Code that has to run in the page, such as the inspect_page snapshot, evaluate_js and query_dom, reaches the tab from the host as CDP Runtime.evaluate calls. The snapshot script walks open shadow roots and same-origin iframes, and tags each interactive element with data-browy-id so the *_index tools can find it again.

The extension ships as-is, with no bundler step. extension/ is a plain Chromium extension folder. The installer copies it to Browy-Extension in your Downloads folder, so you can sideload it with chrome://extensions → Load unpacked. npm run pack:extension zips the same folder for the Chrome Web Store, whose listing covers Chrome, Edge and Brave.