Turn any Chromium-based browser into an agentic browser — a Chrome extension + CLI system where AI agent harnesses can see the browser (form detection, tab/window/profile session tracking), act on it (fill forms, open tabs, create docs), and chat with you from a sidepanel.
What began as form-filling automation is now a general browser-agent platform: the Chrome extension is the first integration for the CLI, and the CLI is the pluggable brain (LLMs, agent harnesses, MCP tools) behind any browser client.
┌──────────────────────── Chromium browser ────────────────────────┐
│ extension: content script background service worker │
│ DOM/form snapshots ────────► WebSocket ⇄ CLI │
│ page ops (set/click/…) sidepanel chat (streamdown UI) │
│ offscreen doc: Pyodide (WASM) │
└──────────────────────────────┬───────────────────────────────────┘
│ ws://127.0.0.1:8765
┌──────────────────────────────▼─────────────── CLI (Python) ─────┐
│ integrations: websocket, stdio (+ plugin transports) │
│ form adapters: generic, Google Forms, Typeform (+ plugins) │
│ planner: rulebook answers + LLM-generated fill programs │
│ session tracker: windows / tabs / profiles, kept coherent │
│ event bus: tab.new / tab.open / tab.close / doc.open │
│ chat handler: harness turns → actions (browser ops, docs) │
│ harnesses: OpenCode, OpenClaw, Hermes (+ plugin harnesses) │
│ LLM providers: OpenRouter, OpenAI, Anthropic, offline │
│ MCP servers: custom Model Context Protocol tools (stdio RPC) │
└─────────────────────────────────────────────────────────────────┘
- Detect forms on any page (generic HTML; plug-n-play adapters for Google Forms, Typeform, and more via the plugin system)
- Snapshot labels, types, options, selectors, and existing values — pre-filled fields are never overwritten
- Answers resolved per field: rulebook (
answers.jsonc) → LLM → mock - The CLI writes a small Python program per form; the extension executes it in Pyodide (CPython compiled to WebAssembly) in an offscreen document and applies
page.set/click/select/submitagainst the live DOM
- React + Vercel streamdown chat UI (bundled, CSP-safe, no CDN)
- Talk to coding-agent harnesses that can drive the browser:
- OpenCode — local binary or HTTP server with live SSE streaming (thinking + text deltas as they're generated), async prompts (no HTTP timeouts on long turns), multi-turn memory, and automatic tool-permission approval
- OpenClaw — gateway
/tools/invokeAPI with bearer auth - Hermes — via the
hermesCLI - any other harness via the
browserstonks_harnessesplugin group
- Harnesses reply in markdown and can emit actions:
[[tab.new|https://…]],[[doc.create|markdown|notes.md]], … - The CLI applies actions: opens/closes real tabs, writes HTML/markdown/text docs to
media/<sessionId>/and opens them — optionally publishing to managed storage (Cloudinary default, S3, GCS) and opening the served URL - Live model thinking — the model's reasoning stream renders in the panel while it works (💭 thinking); the final message keeps a collapsible full-reasoning section
- Stop button — terminate any running turn instantly (aborts the harness-side run too, e.g. OpenCode session abort); hung responses can't freeze the UI (guaranteed
chat.completedsignal + hard turn timeout + client safety nets) - Plan / build modes — a toggle in the chat panel header:
📋 planis read-only (open/close/read/process/focus tabs),🔨 buildadds document create/modify/delete and page actions (tab.actionfill/click/select/submit,tab.editDOM editing,tab.submitform submission). Mode is per-message from the panel, never config. - Multi-window focus tracking — the CLI tracks which window is focused and which tab is active in the focused window; the harness prompt calls out the CURRENT TAB explicitly, and
tab.newdedupes (focuses an already-open tab instead of duplicating it) - Session history — every conversation is persisted under
cli/sessions/; a picker below the harness dropdown lists saved sessions (title + message count), restores transcripts, and continues where you left off - Session auto-restore — closing the sidepanel with a chat open and reopening it resumes that exact conversation (active session + harness remembered in localStorage; transcript reloaded from the CLI store)
- Floating panel button — a BrowserStonks logo button hugs the right edge of every page; one click opens the chat panel from any webpage (no toolbar needed), with a popup-window fallback when Chrome blocks the side-panel gesture
- Multi-round work loop — harnesses chain
tab.read/tab.processactions until the whole task is done (open → read → analyze → more reads → final report), without needing "Continue" prompts
tab.read— harness reads any open tab's HTML through your browser (login state, sessions, everything)tab.process— harness writes its ownprocess(HTML)Python (stdlib, 30s timeout) and gets the result back- Read/process failures are fed back so the model can adapt (open the tab, then retry)
Every action badge (tab.new ✓, tab.read ✕, doc.create ✓…) is interactive:
- Hover — a rich Google-search-style preview card: favicon, hostname, page title, Open Graph description/site name/image — judge how legit a referenced site is before visiting
- Click — jump to the exact tab that action opened (or reopen the URL if it's gone)
- Failed badges show their error inline; successful reads show chars read
- The extension reports a full session snapshot on connect and deltas on every tab/window change
- The CLI keeps a coherent model: profiles → windows → tabs (url, title, active) — queryable via
session.snapshot, fed to harness prompts - CLI-emitted events (
tab.new,tab.open,tab.close,doc.open) are applied by the extension in real time
Three plugin families, discovered from built-ins, drop-in .py files in plugins/, and package entry-points:
browserstonks_integrations— transports (websocket, stdio built-in)browserstonks_form_adapters— form-builder site adaptersbrowserstonks_harnesses— agent harnesses
OpenRouter, OpenAI (and any OpenAI-compatible base_url), Anthropic, or offline (rulebook only). Keys via .env with ${VAR} expansion in config.jsonc.
Per-harness auth block in config.jsonc: bearer, basic, apikey (custom header or query param), plus arbitrary extra headers — for any secure server-mode harness.
Expose the CLI beyond localhost safely:
- Issue tokens:
browserstonks tokens issue --label work-laptop(plaintext shown once; SHA-256 hash stored),revoke/listto manage - With
auth.required: true, every websocket connection must authenticate with a valid token as its first message — invalid/missing tokens are dropped at the gate - Token database: SQLite by default (
tokens.db);auth.databaseacceptspostgres://…/mongodb://…DSNs for shared DBs - Extension side: Secure auth checkbox in the popup reveals a token field; the background worker sends it on every connect/reconnect
doc.createalways writes locally tomedia/<sessionId>/; withstorage.enabled: trueit also uploads and thedoc.openaction opens the hosted URL- Providers: Cloudinary (default), Amazon S3, Google Cloud Storage — all via raw HTTP, no SDKs; credentials in
.env
Declare Model Context Protocol servers in config.jsonc (mcpServers); launched over stdio JSON-RPC, tools listed and callable by the planner.
config.jsonc— JSON-with-comments config;${VAR}env expansionconfig.schema.jsonc— schema of every allowed field;serveauto-syncs it with the packaged schema;doctorvalidates.env— loaded before integrations start (see.env.example)
Named subsystem loggers (ws, chat, harness, actions, plan, answers, mcp, plugins, auth, storage, sessions), one INFO line per pipeline step, DEBUG for payloads; --verbose/--debug, BROWSERSTONKS_LOG, or BS_VERBOSE/BS_DEBUG with start.sh.
The event bus records browser events when no browser is attached, so the same action pipeline can run headless and be asserted on later — the foundation for CI/CD automation without a display.
Secure internet mode — expose the CLI beyond localhost safely: issue connection tokens (browserstonks tokens issue --label my-laptop), enable "auth": { "required": true } in config.jsonc (token DB: SQLite by default; Postgres/Mongo DSN supported), and turn on Secure auth in the extension popup, pasting the token. Connections without a valid token are refused. Enable "storage": { "enabled": true, "provider": "cloudinary" } (with credentials in .env) to publish generated docs online — the doc.open action then opens the hosted URL.
One-command setup — installs the CLI, the agent harnesses (OpenCode, OpenClaw, Hermes), builds the extension sidepanel, auto-configures config.jsonc (detecting running servers and reading the OpenClaw token from ~/.openclaw), validates with doctor, and starts serving:
cd cli
./setup.sh # install + configure + start
./setup.sh --no-start # install + configure only
./setup.sh --skip-harnesses # CLI + extension only
Manual alternative:
cd cli
pip install -e .
browserstonks init # config.jsonc, config.schema.jsonc, .env, answers.jsonc
# put LLM_API_KEY in .env; configure harnesses in config.jsonc
./start.sh # or ./start.sh --daemon
Load chrome-extension/ as an unpacked extension (chrome://extensions → Developer mode → Load unpacked), open any form, click Fill this form, or open the sidepanel and chat with a harness.
browserstonks init # write config files (never overwrites)
browserstonks serve # start enabled integrations + MCP + harnesses
browserstonks plan # one-shot: DOM snapshot JSON -> fill program
browserstonks doctor # validate config/env/keys/harnesses/mcp
| message | direction | purpose |
|---|---|---|
{type:"plan", dom, requestId} |
client → CLI | form-fill plan (program + values) |
{type:"chat", harness, sessionId, message} |
client → CLI | one agent turn |
{type:"session.hello", profile, windows} |
client → CLI | full session snapshot |
{type:"session.tab.updated", ...} etc. |
client → CLI | session deltas |
{type:"harnesses.list"} |
client → CLI | available harnesses |
{type:"session.snapshot"} |
client → CLI | query tracked state |
{type:"chat.response", reply, actionsApplied, events} |
CLI → client | chat reply + applied actions + browser events |
{type:"browser.events", events} |
CLI → client | events to apply (tab.new, doc.open, …) |
See cli/INTEGRATIONS.md for the full protocol and plugin authoring
guide.
chrome-extension/ MV3 extension
manifest.json permissions, sidepanel, CSP (wasm-unsafe-eval)
background.js websocket client, event application, session reporting
content.js DOM/form snapshot, page ops
offscreen.js/.html Pyodide (bundled WASM) program execution
sidepanel-src/ React + streamdown chat source (esbuild → sidepanel.js)
popup.html/.js fill button, chat button, CLI URL setting
vendor/pyodide/ bundled Pyodide runtime
cli/ Python CLI
browserstonks/
cli.py init/serve/plan/doctor
config.py JSONC + .env loading, ${VAR} expansion, validation
config.schema.jsonc allowed config fields
plugins.py dynamic plugin registry (3 families)
integrations/ websocket + stdio transports, message dispatch
form_adapters.py generic / Google Forms / Typeform
planner.py fill-program generation (rulebook + LLM)
answers.py per-field answer resolution (async get_answer)
prompt.py shared harness system prompt (action vocabulary)
logging_util.py named loggers, levels, BROWSERSTONKS_LOG
session.py SessionTracker + EventBus
chat.py chat handler, work loop, action routing, docs
harnesses.py OpenCode (SSE) / OpenClaw / Hermes + auth + sessions
llm.py provider clients
mcp.py MCP server support
plugins/ drop-in plugin directory
setup.sh one-command install: CLI + harnesses + extension build
start.sh / stop.sh server lifecycle scripts (BS_VERBOSE/BS_DEBUG)
- Verified end-to-end against live services: the full plan/fill pipeline, chat → action → browser-event round-trips, and multi-round research turns (open tabs, read HTML through the user's browser, chained reads, doc creation)
- OpenCode server mode fully verified live: SSE thinking streams, multi-turn memory, tool-use turns with auto-approved permissions
- OpenClaw gateway verified at transport level; Hermes is CLI-driven (provider-limited).
- Client-packed chat history: the panel sends the full prior conversation with every chat message; the CLI injects it into the harness prompt only when the harness session is cold — restoring a saved session, restarting the CLI, or switching harnesses mid-chat all keep full context.
- Plan / build chat modes:
📋 plan(read-only: tabs only, model cleans up after itself) vs🔨 build(doc.create/modify/delete + page actions). Toggle lives in the chat panel header, persisted to localStorage, sent per-message; actions outside the mode are filtered and reported failed. - New build-mode actions:
tab.edit(DOM editing per CSS selector: html/text/attrs/remove),tab.submit(submits the form closest to the last edited/filled element),tab.action(fill/click/select/submit relay). - Multi-window focus tracking: windows carry
focusedstate, tab activation clears the global "current tab" marker, and the harness prompt calls out CURRENT TAB explicitly;tab.newdedupes to focus existing tabs. - Thinking interleaving fixed: streamed thinking/text now render in arrival order (ordered part stream) instead of all thinking mashed into one section.
- Local LLM providers: lmstudio / llamacpp / llmd / ollama with optional bearer auth; OpenCode harness picks model from
.env(OPENCODE_MODEL_PROVIDER/ID,<KIND>_MODEL_IDshorthand); model/provider errors surface fast in chat.
- Floating panel button: BrowserStonks-logo button on the right edge of every page; click opens the chat sidepanel from any webpage (popup-window fallback when Chrome 127+ blocks content-script
sidePanel.opengestures); drag behaviour deferred - Session auto-restore: the sidepanel reopens into the last active chat session — active session id + harness persisted in localStorage, transcript reloaded from the CLI session store; deleting the active session starts fresh
- Fix:
OPEN_CHAT_PANELhandler crashed on an undefinedsender(renamed listener parameter) — the floating button's click never opened the panel
- Connection-token auth:
browserstonks tokens issue/revoke/list; SHA-256-hashed tokens in SQLite (default) / Postgres / Mongo; websocket auth gate (auth.required) closes unauthenticated connections; extension Secure auth checkbox + token field in the popup - Managed storage:
doc.createuploads to Cloudinary (default) / S3 / GCS after the local write (raw HTTP, no SDKs);doc.openopens the hosted URL - Generated content moved to
media/<sessionId>/(per-session folders);cli/docs/unignored and reserved for developer documentation - Fix:
tokens issuestdout polluted by log lines (broke copy-pasted tokens); server now strips token whitespace
- Interactive action badges: hover for a rich link preview (favicon, title, Open Graph description/site/image — legit-check before visiting); click jumps to the exact tab that action opened; failed badges show errors inline
- Session history: transcripts persisted to
cli/sessions/; session picker below the harness dropdown (new / load / delete); restores messages, actions, and reasoning tab.read/tab.processbadges carry resolved URLs (success and failure) so every reference is previewable- Raised websocket frame limit to 50 MB; extension truncates oversized page HTML at 2 MiB (with truncation flag) — fixed
1009 message too bigdisconnects on large pages - Fix: empty reply on tool-using turns — completion now keyed on session busy/idle, not the first assistant finish event
chat.completedguarantee: exactly one completion signal per turn (ok/error/timeout/stopped) plus a hard turn timeout — the UI can never hang- Stop button: kills a running turn instantly, end to end (stop events through the chat handler, SSE wait loop, and OpenCode
/session/{id}/abort); chat turns run as concurrent tasks so stop/pings are read mid-turn - Multi-round work loop: harnesses chain reads until the task is complete (max 16 rounds); browser events flush mid-loop (tab.new takes effect before the next round); failures feed back so the model can retry
- OpenCode SSE streaming:
/eventsubscription streams thinking + text deltas live into the panel; async prompts eliminate HTTP timeouts; tool permissions auto-approved; prompt forbids bash/curl for browser actions - Structured logging across every CLI subsystem with
--verbose/--debugand env controls - MV3 keepalive (20s ping), protocol keepalive disabled, send serialization, graceful disconnects — long turns survive Chrome's worker suspension
- Connection-state notifications in the panel;
setup.shone-command installer; absolute doc paths; repo folder renamedCLI/tocli/
- Chrome extension: form snapshots, bundled Pyodide in an offscreen document (CSP-safe), on-demand content-script injection, fill programs with pre-filled-field protection
- CLI: plugin system (integrations / form adapters / harnesses), LLM providers, MCP servers, JSONC config with schema validation, websocket + stdio transports
- Sidepanel chat (React + streamdown), harness action vocabulary (
tab.*,doc.create), session tracking, event bus - Verified live against OpenCode (:4096), OpenClaw (:18789), Hermes (:9119)
