English | δΈζ
A local-first browser rendering CLI for live preview, browser control, and DevTools capture. Write HTML/CSS/JS in a local directory and have it rendered in a real Chromium browser with full DevTools control. π¨
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.
- 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
Download the latest binary for your platform from GitHub Releases:
| Platform | Archive |
|---|---|
| Linux x86_64 | browsercli-v*-linux-x86_64.tar.gz |
| Linux ARM64 | browsercli-v*-linux-arm64.tar.gz |
| macOS Intel | browsercli-v*-macos-x86_64.tar.gz |
| macOS Apple Silicon | browsercli-v*-macos-arm64.tar.gz |
| Windows x86_64 | browsercli-v*-windows-x86_64.zip |
Extract the archive and place the binary in your $PATH.
brew tap justinhuangcode/tap
brew install browserclicargo install browsercli# Node.js
npm install @justinhuangcode/browsercli
# Python
pip install browserclicargo install --path .Requirements: Rust 1.75+ and a Chromium-based browser (Chrome, Chromium, Brave, or Edge). On Windows, Microsoft Edge works out of the box.
# Start with a project directory
browsercli start --dir ./my-site
# Or start with a temp directory
browsercli start
# Check status
browsercli status
# Navigate to a path
browsercli goto /
# Query the DOM
browsercli dom query "h1" --mode text
browsercli dom all "a" --mode outer_html
# Evaluate JavaScript
browsercli eval "document.title"
# Take a screenshot
browsercli screenshot --out page.png
# View console output (--clear drains the buffer)
browsercli console --level error
browsercli console --clear
# View network requests (--clear drains the buffer)
browsercli network --limit 20
browsercli network --clear
# Performance timing
browsercli perf
# Stop
browsercli stop| 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 |
| 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 |
| 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 |
browsercli dom query "selector" [--mode outer_html|text]
browsercli dom all "selector" [--mode outer_html|text] [--limit N]
browsercli dom attr "selector" "attribute-name"
browsercli dom click "selector"
browsercli dom type "selector" "text" [--clear]
browsercli dom wait "selector" [--state visible|hidden|present|gone] [--timeout 10s]Shorthand:
browsercli dom "#app" --mode textbrowsercli 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
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 |
browsercli start --template tailwind
browsercli start --template dashboard
browsercli start --template chart
browsercli start --template formAll templates are single-file HTML with CDN imports β zero build step, instant reload.
Plugins can provide additional templates. Templates are HTML/CSS/JS scaffolds that get copied to the serve directory at startup:
browsercli start --template my-custom-templatePlugins 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=$(cat)
echo '{"ok": true, "refreshed_at": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}'Call from client libraries:
// Node.js
const result = await ac.pluginRpc("/x/dashboard/refresh", { key: "value" });# Python
result = ac.plugin_rpc("/x/dashboard/refresh", {"key": "value"})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 |
browsercli plugin init my-plugin # Scaffold a new plugin
browsercli plugin list # List installed plugins
browsercli start --template name # Apply a plugin template at startupAll 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.
-
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.
Unix Socket (macOS/Linux)
+-----------+ or TCP localhost (Windows) +----------+
| CLI cmd | --------------> +--------------+ CDP/WS | Chromium |
| | <-------------- | Daemon | -------> | |
+-----------+ JSON RPC | | <------- +----------+
| HTTP Server |
| File Watch |
+--------------+
|
v
Local Files
A zero-dependency Node.js client (written in TypeScript, ships with type definitions) is included in clients/node/. Install it with:
cd clients/node && npm installimport { 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.
A zero-dependency Python client is included in clients/python/. Install it with:
pip install -e clients/pythonfrom browsercli import BrowserCLI
ac = BrowserCLI.connect() # reads ~/.browsercli/session.json
ac.goto("/")
title = ac.dom_query("h1", mode="text")
ac.screenshot(out="page.png")
ac.stop()
# Plugin support
plugins = ac.plugin_list()
result = ac.plugin_rpc("/x/my-plugin/action", {"key": "value"})See clients/python/README.md for the full API reference.
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:
browsercli start --dir /tmp/demo-site
# Node.js (build the client first)
cd clients/node && npm run build && cd ../..
node examples/01_write_reload_screenshot.mjs
# Python
python examples/01_write_reload_screenshot.py
browsercli stopsrc/
βββ 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)
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 |
- 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.
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.
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
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
See CHANGELOG.md for release history.
Inspired by steipete/canvas.