browsercli
English | 中文
A browser visual workspace for AI agents. Write HTML/CSS/JS in a local directory and have it rendered in a real Chromium browser with full DevTools control — all from the command line.
Why browsercli?
AI agents that generate HTML/CSS/JS need to see what they built. They need to render the page in a real browser, take a screenshot, read the DOM, check for console errors, and iterate — all without a human clicking around.
Existing tools don't fit this workflow:
| browsercli | Playwright | Puppeteer | live-server | |
|---|---|---|---|---|
| Designed for AI agents | Yes | No (test framework) | No (library) | No (dev tool) |
| Persistent daemon | Yes — start once, control anytime |
No — new browser per script | No — new browser per script | No daemon |
| Local file serving + auto-reload | Built-in (250ms) | No | No | Yes, but no automation |
| CLI + client libraries | CLI + Python + Node.js | Python/Node.js/Java/.NET | Node.js only | None |
| DOM / screenshot / console / network | All via CLI or SDK | Via code only | Via code only | None |
| Plugin system | Templates + RPC + hooks | No | No | No |
| Setup complexity | browsercli start |
Install + write test script | Install + write script | npx live-server |
The typical AI agent workflow with browsercli:
Agent writes HTML/CSS/JS to disk
↓
browsercli auto-reloads the browser (250ms)
↓
Agent runs: browsercli screenshot --out page.png
↓
Agent inspects the screenshot / queries DOM / checks console
↓
Agent iterates on the code
No test framework boilerplate. No browser lifecycle management. Just a persistent browser that reflects your files and responds to commands.
Features
- Daemon architecture —
browsercli startlaunches a background process; CLI commands talk to it via Unix socket RPC (macOS/Linux) or TCP localhost (Windows) - Static file server — Serves a local directory over HTTP with automatic
index.htmlresolution - Auto-reload — File watcher with 250ms debounce triggers browser reload on save
- App mode — Opens a chromeless (
--app) browser window by default - Stealth mode — Best-effort automation detection reduction (webdriver flag removal)
- Full DOM control — Query, query-all, attr, click, type, wait via CSS selectors
- JavaScript evaluation — Execute arbitrary JS in the controlled tab
- Screenshot capture — Full page or element-specific (PNG)
- Console capture — View browser console output (log, warn, error, info) with level filtering and
--clearto drain the buffer - Network logging — Inspect HTTP requests/responses with method, status, resource type, MIME type, size, and duration; supports
--clear - Performance metrics — Navigation Timing Level 2 (with legacy fallback) for DOMContentLoaded and Load timing
- JSON output — Pass
--jsonfor machine-readable output on every command - Plugin system — Extend browsercli with custom page templates, RPC endpoints, and lifecycle hooks via script-based plugins with JSON manifests
- Cross-platform — macOS (app bundles), Linux, and Windows Chrome/Chromium/Edge auto-detection
Installation
Pre-built binaries (recommended)
Download the latest binary for your platform from GitHub Releases:
| Platform | Archive |
|---|---|
| Linux x86_64 | browsercli-v*-x86_64-unknown-linux-gnu.tar.gz |
| Linux ARM64 | browsercli-v*-aarch64-unknown-linux-gnu.tar.gz |
| macOS Intel | browsercli-v*-x86_64-apple-darwin.tar.gz |
| macOS Apple Silicon | browsercli-v*-aarch64-apple-darwin.tar.gz |
| Windows x86_64 | browsercli-v*-x86_64-pc-windows-msvc.zip |
Extract the archive and place the binary in your $PATH.
Homebrew (macOS / Linux)
Via Cargo (crates.io)
Client libraries
# Node.js
# Python
From source
Requirements: Rust 1.75+ and a Chromium-based browser (Chrome, Chromium, Brave, or Edge). On Windows, Microsoft Edge works out of the box.
Quick Start
# Start with a project directory
# Or start with a temp directory
# Check status
# Navigate to a path
# Query the DOM
# Evaluate JavaScript
# Take a screenshot
# View console output (--clear drains the buffer)
# View network requests (--clear drains the buffer)
# Performance timing
# Stop
Commands
| Command | Description |
|---|---|
start |
Launch daemon in background |
serve |
Run in foreground (no daemon) |
status |
Show current session status |
stop |
Stop the daemon |
focus |
Bring browser window to front (macOS) |
devtools |
Print DevTools WebSocket URL |
goto <path> |
Navigate to a path or URL |
eval <expr> |
Evaluate JavaScript |
reload |
Reload the browser tab |
dom |
DOM utilities: query, all, attr, click, type, wait |
screenshot |
Capture page or element screenshot |
console |
View browser console entries |
network |
View network request log |
perf |
Show page performance metrics |
plugin list |
List installed plugins |
plugin init <name> |
Scaffold a new plugin |
Start Flags
| Flag | Default | Description |
|---|---|---|
--dir <path> |
temp dir | Directory to serve |
--port <n> |
0 (random) | HTTP port |
--devtools-port <n> |
0 (random) | Chrome DevTools port |
--headless |
false | Run browser headless |
--no-app |
false | Disable chromeless window |
--no-stealth |
false | Disable stealth mode |
--window-size <w,h> |
1280,720 | Browser window size |
--browser-bin <path> |
auto-detect | Chromium/Chrome binary path |
--restart |
false | Restart if already running |
--template <name> |
(none) | Apply a plugin template at startup |
Console & Network Flags
| Flag | Applies To | Description |
|---|---|---|
--level <level> |
console |
Filter by level: log, warn, error, info |
--limit <n> |
console, network |
Limit number of returned entries |
--clear |
console, network |
Drain the buffer after reading |
DOM Subcommands
Shorthand:
Plugin System
browsercli has a built-in plugin system with three extension points: page templates, custom RPC endpoints, and lifecycle hooks. Plugins are plain directories with a plugin.json manifest and executable scripts -- no compilation, WASM, or dynamic libraries required.
~/.browsercli/plugins/my-plugin/
├── plugin.json # Manifest (required)
├── templates/
│ └── dashboard/ # HTML/CSS/JS scaffold
│ ├── index.html
│ ├── style.css
│ └── app.js
├── handlers/
│ └── refresh.sh # Custom RPC endpoint script
└── hooks/
└── on_start.sh # Lifecycle hook script
Built-in Templates
browsercli ships with 4 built-in templates that work out of the box — no plugins needed:
| Template | Stack | Use Case |
|---|---|---|
tailwind |
Tailwind CSS v4 CDN | General-purpose responsive UI |
dashboard |
Tailwind CSS v4 + DaisyUI v5 | Admin panels, monitoring dashboards |
chart |
Tailwind CSS v4 + Chart.js v4 | Data visualization (bar, line, doughnut, radar) |
form |
Tailwind CSS v4 + Alpine.js v3 | Interactive forms with client-side validation |
All templates are single-file HTML with CDN imports — zero build step, instant reload.
1. Page Templates (Plugins)
Plugins can provide additional templates. Templates are HTML/CSS/JS scaffolds that get copied to the serve directory at startup:
2. Custom RPC Endpoints
Plugins can expose HTTP endpoints under the /x/ namespace. Handler scripts receive JSON on stdin and write JSON to stdout:
# handlers/refresh.sh
#!/bin/sh
INPUT=
Call from client libraries:
// Node.js
const result = await ac.pluginRpc("/x/dashboard/refresh", { key: "value" });
# Python
=
3. Lifecycle Hooks
Fire-and-forget scripts triggered by daemon events:
| Event | Trigger | Extra Context |
|---|---|---|
on_daemon_start |
Daemon is ready | -- |
on_daemon_stop |
Daemon shutting down | -- |
on_file_change |
File changed in serve dir | $BROWSERCLI_FILE_PATH |
on_navigate |
Browser navigated | $BROWSERCLI_URL |
on_console |
Console message | JSON on stdin |
on_network |
Network request | JSON on stdin |
Plugin CLI
All scripts receive environment variables: BROWSERCLI_TOKEN, BROWSERCLI_HTTP_PORT, BROWSERCLI_DIR, BROWSERCLI_BASE_URL, BROWSERCLI_STATE_DIR, BROWSERCLI_PLUGIN_NAME.
See PLUGINS.md for the full development guide, manifest schema, security model, and cross-platform notes. A complete example plugin is included.
How It Works
-
browsercli startspawns a daemon process that:- Starts an HTTP static file server on a random port
- Launches a Chromium browser via CDP (Chrome DevTools Protocol)
- Opens a Unix socket (macOS/Linux) or TCP localhost (Windows) RPC server for CLI communication
- Watches the served directory for file changes
- Writes session state to
~/.browsercli/session.json(macOS/Linux) or%LOCALAPPDATA%\browsercli\session.json(Windows)
-
Subsequent CLI commands (
goto,eval,dom, etc.) connect to the RPC endpoint and send JSON requests to the daemon. -
The daemon translates RPC requests into CDP commands over WebSocket.
Architecture
Unix Socket (macOS/Linux)
+-----------+ or TCP localhost (Windows) +----------+
| CLI cmd | --------------> +--------------+ CDP/WS | Chromium |
| | <-------------- | Daemon | -------> | |
+-----------+ JSON RPC | | <------- +----------+
| HTTP Server |
| File Watch |
+--------------+
|
v
Local Files
Client Libraries
Node.js
A zero-dependency Node.js client (written in TypeScript, ships with type definitions) is included in clients/node/. Install it with:
&&
import { BrowserCLI } from "@justinhuangcode/browsercli";
const ac = BrowserCLI.connect(); // reads ~/.browsercli/session.json
await ac.goto("/");
const title = await ac.domQuery("h1", "text");
await ac.screenshot("", "page.png");
await ac.stop();
// Plugin support
const plugins = await ac.pluginList();
const result = await ac.pluginRpc("/x/my-plugin/action", { key: "value" });
See clients/node/README.md for the full API reference.
Python
A zero-dependency Python client is included in clients/python/. Install it with:
= # reads ~/.browsercli/session.json
=
# Plugin support
=
=
See clients/python/README.md for the full API reference.
Examples
End-to-end examples are provided in examples/ for both Node.js and Python:
| Script | Description |
|---|---|
01_write_reload_screenshot.mjs |
Agent writes HTML, auto-reload picks it up, takes a screenshot |
02_form_fill_and_submit.mjs |
Fills a form, clicks submit, inspects network log, exports results |
03_debug_report.mjs |
Collects console, network, and perf data into a JSON debug report |
01_write_reload_screenshot.py |
Same as above, Python version |
02_form_fill_and_submit.py |
Same as above, Python version |
03_debug_report.py |
Same as above, Python version |
Run any example after starting the daemon:
# Node.js (build the client first)
&& &&
# Python
Project Structure
src/
├── main.rs # CLI entry point and command dispatch
├── cli/mod.rs # Command-line argument definitions (clap)
├── daemon/
│ ├── mod.rs # Daemon module exports
│ └── server.rs # Daemon process, RPC handler, session management
├── browser/
│ ├── mod.rs # Browser module exports
│ ├── controller.rs # CDP communication, browser lifecycle
│ ├── devtools.rs # DevTools HTTP API client
│ └── find.rs # Chromium/Chrome binary auto-detection
├── web/
│ ├── mod.rs # Web module exports
│ ├── server.rs # Static file handler with index resolution
│ └── welcome.rs # Welcome page HTML template
├── rpc/
│ ├── mod.rs # RPC module exports
│ ├── types.rs # Request/response type definitions
│ └── client.rs # RPC client (Unix socket / TCP)
├── plugins/
│ ├── mod.rs # Plugin manifest types, validation, discovery
│ ├── registry.rs # Central plugin registry with O(1) lookups
│ ├── executor.rs # Script execution engine
│ ├── hooks.rs # Lifecycle hook dispatch
│ └── templates.rs # Template copy logic
└── watch/
└── mod.rs # File system watcher with debounce
clients/node/ # Node.js client library (TypeScript, zero dependencies)
clients/python/ # Python client library (zero dependencies)
examples/ # End-to-end example scripts
examples/plugins/ # Example plugins (dashboard)
tests/
├── cli_integration.rs # CLI integration tests
└── e2e_integration.rs # Full lifecycle E2E test (requires Chromium)
Security & Threat Model
browsercli is designed for single-user, local-only use on development machines. The following controls are in place:
| Layer | Control | Detail |
|---|---|---|
| HTTP server | Localhost-only binding | Binds to 127.0.0.1; never exposed to the network |
| RPC transport | Unix socket (macOS/Linux) or TCP localhost (Windows) + Bearer token | Socket at ~/.browsercli/sock with 0600 permissions (Unix); TCP 127.0.0.1 bound to a random port (Windows); every request requires a random token |
| Session file | Owner-only permissions | ~/.browsercli/session.json (Unix) or %LOCALAPPDATA%\browsercli\session.json (Windows) is created with mode 0600 (Unix) so other users cannot read the token |
| Static files | Path traversal protection | Requested paths are canonicalized and checked against the serve root |
| Browser | User data isolation | Each session uses a dedicated --user-data-dir in a temp directory |
Not recommended for
- Multi-user / shared machines — Other local users with root or same-UID access can read the session token and issue RPC commands. If you run on a shared server, restrict access to
~/.browsercli/via OS-level permissions or containers. - Serving untrusted content — The HTTP server is intended for local files authored by you or your agent. Do not point
--dirat untrusted directories. - Production workloads — browsercli is a development/testing tool. It does not implement TLS, rate limiting, or audit logging.
Stealth mode
By default, browsercli removes the navigator.webdriver flag and applies minor automation-fingerprint mitigations so that local pages behave as they would in a normal browser (e.g., some front-end frameworks alter behavior when they detect headless/automated Chrome).
| Flag | Behavior |
|---|---|
| (default) | Stealth patches applied — navigator.webdriver returns false |
--no-stealth |
All stealth patches disabled — browser reports as automated |
Scope: Stealth mode is strictly for local development and testing where automation detection interferes with page behavior. It is not designed for bypassing security controls on external websites.
Troubleshooting
See TROUBLESHOOTING.md for common issues and solutions, including:
- Browser not found — install instructions per platform
- Port conflicts
- Headless mode on servers / CI
- Permission errors
- Template not found
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
Changelog
See CHANGELOG.md for release history.
Acknowledgments
Inspired by steipete/canvas.