browser-automation-cli 0.2.1__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -26,3 +26,5 @@ RELEASE.md
26
26
  # Local state
27
27
  *.log
28
28
  *.tmp
29
+ scratch/agentbench/results/calls.log
30
+ scratch/agentbench/results/current.json
@@ -0,0 +1,168 @@
1
+ # Agent Integration Guide
2
+
3
+ > **Give [SKILL.md](./SKILL.md) to your coding agent harness as a skill file. It contains ready-to-use workflows and decision guides for this tool.**
4
+
5
+ ## What This Tool Does
6
+
7
+ Browser CLI provides authenticated browser automation via a CLI:
8
+ - **`browser-daemon`** — background process owning a headless Chromium and persistent sessions, reachable over a Unix socket
9
+ - **`browser`** — CLI client (~40 ms per call) that sends commands to the daemon, or runs standalone captures
10
+
11
+ Any coding agent can use it via subprocess calls. No SDK required.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ uv tool install browser-automation-cli
17
+ browser install
18
+ ```
19
+
20
+ If `browser` or `browser-daemon` is not found: `export PATH="$HOME/.local/bin:$PATH"`
21
+
22
+ ## Quick Start
23
+
24
+ ```bash
25
+ browser-daemon & # 1. start once; headless, nothing opens
26
+ browser create # 2. prints an 8-char session id, e.g. abc12345
27
+ browser abc12345 navigate https://github.com/login
28
+ browser abc12345 snapshot # 3. see what is on the page
29
+ browser abc12345 type --label "Username or email address" octocat
30
+ browser abc12345 click --text "Sign in" -s # -s: return a fresh snapshot with the result
31
+ ```
32
+
33
+ If a site needs the **user** to log in: `browser abc12345 show` (a window opens), ask the user to log in, then `browser abc12345 hide`. Never ask for credentials.
34
+
35
+ ## Session Model
36
+
37
+ | Property | Detail |
38
+ |----------|--------|
39
+ | **ID** | 8-char hex (`a1b2c3d4`) |
40
+ | **Scope** | One session = one isolated browser profile (cookies, storage). One session can visit many sites |
41
+ | **Persistence** | Sessions survive daemon restarts (state saved under `~/.browser-daemon/sessions/`). `delete` forgets them |
42
+ | **Idle** | Hidden sessions are frozen (scripts paused) after 10 s idle and hibernated after 10 min; both are transparent — just send the next command |
43
+ | **Visibility** | Headless by default. `create --show`, `show`, `hide` move a session between a window and headless, keeping auth |
44
+ | **Viewport** | 1280x800 desktop; `navigator.webdriver` hidden; UA matches the real Chromium version |
45
+
46
+ ## Command Reference
47
+
48
+ ### Standalone (no daemon)
49
+
50
+ ```bash
51
+ browser capture <url> [-f] [-o path] # headless JPEG screenshot (viewport; -f full page)
52
+ browser install # Chromium runtime
53
+ browser cleanup # kill Chromium processes launched by this tool
54
+ ```
55
+
56
+ ### Sessions
57
+
58
+ ```bash
59
+ browser create [--show] # new session (id on stdout)
60
+ browser list [--table] # JSON: [{session_id, url, title, state, visible}]
61
+ browser <id> show | hide # window <-> headless
62
+ browser <id> delete
63
+ browser shutdown
64
+ ```
65
+
66
+ ### Page commands
67
+
68
+ Every command prints JSON and exits 1 on failure; `snapshot` prints text. Add `-s`/`--snapshot` to any action to append a fresh snapshot.
69
+
70
+ | Command | Notes |
71
+ |---------|-------|
72
+ | `navigate <url> [--wait load\|domcontentloaded\|networkidle]` | Returns when the page is usable (`load`). A slow `networkidle` is reported as `settled: false`, not an error |
73
+ | `snapshot [scope-selector] [--all] [--max N] [--json]` | Visible interactive elements and headings, one per line |
74
+ | `click <target> [--double]` | |
75
+ | `type <target> <text> [--sequential] [--submit]` | `--sequential` for autocomplete/combobox inputs; `--submit` presses Enter after |
76
+ | `press <key> [target]` | `Enter`, `Tab`, `Escape`, `Control+a` |
77
+ | `hover <target>` | |
78
+ | `select <target> <value-or-label>` | |
79
+ | `scroll [up\|down] [px]` / `scroll <target>` | |
80
+ | `text [selector]` | Readable text — use for extraction instead of `snapshot --all` |
81
+ | `wait [--text T \| --selector S] [--gone] [--timeout ms]` | |
82
+ | `screenshot [target] [-o path] [-f] [-q 70]` | JPEG under `~/.browser-daemon/shots/` (mode 600) |
83
+ | `eval <js-expression>` | |
84
+ | `console [--clear]` | Buffered console messages |
85
+ | `back` / `forward` | |
86
+ | `batch` | JSON lines on stdin, one round-trip, stops at first failure |
87
+
88
+ ### Targets
89
+
90
+ | Form | Example |
91
+ |------|---------|
92
+ | ref from snapshot (**preferred**) | `click @e12` |
93
+ | visible text | `click --text "Create"` or `click text=Create` |
94
+ | ARIA role + name | `click --role button --name Create` or `click "role=button[name=Create]"` |
95
+ | form label / placeholder | `type --label "Email" me@x.com`, `type --placeholder Search foo` |
96
+ | CSS / Playwright selector | `click "#submit"`, `click "form >> text=Save"` |
97
+
98
+ Ambiguous CSS selectors are **refused** (`strict mode violation`) rather than clicking the first match — use a ref or text.
99
+
100
+ ## Reading a snapshot
101
+
102
+ ```
103
+ url: https://dash.cloudflare.com/login
104
+ title: Cloudflare Dashboard | Manage Your Account
105
+ scroll: 0/1400 (viewport 1280x800; [below]/[above] = outside viewport)
106
+ @e2 link "Sign up" href="/sign-up"
107
+ h1 "Sign in to Cloudflare"
108
+ @e7 textbox "Email"
109
+ @e8 textbox "Password" type="password"
110
+ @e10 checkbox "Save email and login method on this device" [checked=false]
111
+ @e11 button "Sign in" [disabled]
112
+ @e30 button "Create" [below]
113
+ ```
114
+
115
+ - `@eN` refs stay valid until the page navigates or the element is removed; a stale ref returns `ref @eN is unknown or stale` — run `snapshot` again.
116
+ - `[below]`/`[above]` elements exist but are outside the viewport; clicking them scrolls automatically.
117
+ - Hidden elements (`display:none`, `aria-hidden`, zero size) are omitted. `--all` adds paragraphs/list items; `--json` adds `box` (x, y, w, h) and a unique CSS `selector`.
118
+ - Large pages: scope with a selector (`snapshot "#main"`) or `--max`.
119
+
120
+ ## Agent Workflow
121
+
122
+ 1. `browser list` — reuse an existing session if one fits.
123
+ 2. `browser create` if needed (`--show` only when the user must log in).
124
+ 3. `navigate <url> -s` — one call gives you the page and its snapshot.
125
+ 4. Act with refs/text: `type @e7 user@x.com`, `click --text "Sign in" -s`.
126
+ 5. Verify from the returned snapshot or `text`; take a `screenshot` only when layout matters.
127
+ 6. Chain known steps with `batch` to save round-trips:
128
+ ```bash
129
+ printf '%s\n' '{"cmd":"type @e7 me@x.com"}' '{"cmd":"type @e8 secret --submit"}' '{"cmd":"snapshot"}' | browser abc12345 batch
130
+ ```
131
+
132
+ ## Output Parsing
133
+
134
+ ```json
135
+ {"success": true, "url": "https://...", "title": "..."}
136
+ {"success": false, "error": "strict mode violation: locator(\"button.flex\") resolved to 3 elements"}
137
+ ```
138
+
139
+ `snapshot --json` elements: `{ref, role, name, pos, box, href?, value?, placeholder?, checked?, options?, expanded?, selected?, disabled?, required?}`.
140
+
141
+ ### Calling from code
142
+
143
+ ```python
144
+ import subprocess, json
145
+ r = subprocess.run(["browser", "abc12345", "click", "--text", "Create", "-s"], capture_output=True, text=True)
146
+ snapshot_text = r.stdout # success: snapshot text; failure: JSON with "error", exit code 1
147
+ ```
148
+
149
+ ## Troubleshooting
150
+
151
+ | Symptom | Fix |
152
+ |---------|-----|
153
+ | `Daemon not running` | Start `browser-daemon` |
154
+ | `Session not found` | `browser list` |
155
+ | `ref @eN is unknown or stale` | `snapshot` again |
156
+ | `strict mode violation` | Use `@ref`, `--text`, or a tighter selector |
157
+ | `Timeout … waiting for locator` | Element not visible/enabled — `snapshot` to check state, `wait --text …` |
158
+ | Site shows a login page | `browser <id> show`, ask the user to log in, `hide` |
159
+ | Browser doesn't launch | `browser install` |
160
+
161
+ ## Key Rules for Agents
162
+
163
+ 1. **Never request credentials** — the user logs in manually in a shown window.
164
+ 2. **Check `success`** (or the exit code) before proceeding.
165
+ 3. **Prefer refs and text targets** over guessed CSS selectors.
166
+ 4. **Use `-s` and `batch`** to cut round-trips; use `text` for extraction.
167
+ 5. **Reuse sessions**; delete them when no longer needed.
168
+ 6. **Leave the daemon running** between actions.
@@ -0,0 +1,249 @@
1
+ Metadata-Version: 2.5
2
+ Name: browser-automation-cli
3
+ Version: 0.3.0
4
+ Summary: Browser automation daemon + CLI for coding agents. Persistent sessions, no MCP, no extensions.
5
+ Project-URL: Homepage, https://github.com/jshan9078/browser-automation-cli
6
+ Project-URL: Repository, https://github.com/jshan9078/browser-automation-cli
7
+ Author: Browser CLI Maintainers
8
+ License: MIT License
9
+
10
+ Copyright (c) 2026 Browser CLI Maintainers
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
29
+ License-File: LICENSE.txt
30
+ Keywords: agent,automation,browser,cli,playwright
31
+ Requires-Python: >=3.9
32
+ Requires-Dist: playwright>=1.40.0
33
+ Description-Content-Type: text/markdown
34
+
35
+ # Browser Automation CLI
36
+
37
+ > **If you are an LLM, see** **[AGENTS.md](https://github.com/jshan9078/browser-automation-cli/blob/main/AGENTS.md)** **for quick setup and usage instructions.**
38
+
39
+ A lightweight, self-hosted browser automation tool with a background daemon and CLI client. Enables authenticated web automation, screenshots, compact page snapshots, and page interactions via simple CLI commands. Share the [`SKILL.md`](https://github.com/jshan9078/browser-automation-cli/blob/main/SKILL.md) file with your coding agent harness for seamless integration.
40
+
41
+ ## Why This Exists
42
+
43
+ Coding agents need to interact with authenticated web apps. Existing solutions all have tradeoffs:
44
+
45
+ * **Chrome DevTools MCP** — requires Node.js, per-agent MCP server configuration, Google telemetry by default, and complex setup for each coding agent
46
+ * **BrowserMCP and similar tools** — require installing Chrome extensions, tie into specific ecosystems, and use MCP which bloats the agent's context window with tool definitions and protocol overhead
47
+ * **Playwright/Puppeteer scripts** — require writing code for every interaction, no persistent auth state
48
+ * **AI browser frameworks** — heavy, opinionated, and framework-locked
49
+
50
+ Browser CLI solves this with a persistent daemon that any agent can call via subprocess. No extensions, no MCP config, no SDKs, no ecosystem lock-in. Sessions persist across agent calls (and daemon restarts) so you only log in once.
51
+
52
+ ## Install
53
+
54
+ ```bash
55
+ uv tool install browser-automation-cli
56
+ browser install
57
+ ```
58
+
59
+ If commands are not found after install, add `~/.local/bin` to your PATH:
60
+
61
+ ```bash
62
+ export PATH="$HOME/.local/bin:$PATH"
63
+ ```
64
+
65
+ ## Quick Start
66
+
67
+ ### 1. Start the daemon
68
+
69
+ ```bash
70
+ browser-daemon
71
+ ```
72
+
73
+ Nothing visible opens: the daemon runs Chromium headless. Keep this terminal running (or background it).
74
+
75
+ ### 2. Create a session
76
+
77
+ ```bash
78
+ browser create # headless session
79
+ browser create --show # opens a window so you can log in; `browser <id> hide` afterwards
80
+ ```
81
+
82
+ A session is an isolated browser profile (cookies, storage). Log into any sites you need while it is shown; the agent can drive it hidden afterwards. Sessions survive daemon restarts.
83
+
84
+ ### 3. Run browser actions
85
+
86
+ ```bash
87
+ browser <id> navigate https://github.com
88
+ browser <id> snapshot # interactive elements, one line each, with @refs
89
+ browser <id> click @e7 # click by ref from the snapshot
90
+ browser <id> click --text "Sign in" # or by visible text / role / label
91
+ browser <id> type --label "Username" octocat
92
+ browser <id> screenshot # JPEG saved under ~/.browser-daemon/shots/
93
+ ```
94
+
95
+ A snapshot looks like this (Cloudflare login, 245 tokens):
96
+
97
+ ```
98
+ url: https://dash.cloudflare.com/login
99
+ title: Cloudflare Dashboard | Manage Your Account
100
+ @e2 link "Sign up" href="/sign-up"
101
+ h1 "Sign in to Cloudflare"
102
+ @e3 button "Continue with Google"
103
+ @e7 textbox "Email"
104
+ @e8 textbox "Password" type="password"
105
+ @e10 checkbox "Save email and login method on this device"
106
+ @e11 button "Sign in" [disabled]
107
+ ```
108
+
109
+ ### 4. Manage sessions
110
+
111
+ ```bash
112
+ browser list # JSON (--table for humans)
113
+ browser <id> show | hide # move between a visible window and headless
114
+ browser <id> delete
115
+ browser shutdown # stop the daemon; sessions are saved and restored next start
116
+ ```
117
+
118
+ ***
119
+
120
+ ## Commands Reference
121
+
122
+ ### Standalone (no daemon)
123
+
124
+ ```bash
125
+ browser capture <url> [-f] [-o <path>] # headless viewport screenshot (-f = full page)
126
+ browser install # download Chromium (~170 MB)
127
+ browser cleanup # kill Chromium processes launched from Playwright's cache
128
+ ```
129
+
130
+ ### Sessions
131
+
132
+ | Command | Description |
133
+ | :-- | :-- |
134
+ | `browser create [--show]` | New session; `--show` opens a window (for manual login) |
135
+ | `browser list [--table]` | Sessions with `state` (active / frozen / hibernated) and `visible` |
136
+ | `browser <id> show` / `hide` | Move the session to a visible window / back to headless (auth kept) |
137
+ | `browser <id> delete` | Close session and forget its cookies |
138
+ | `browser shutdown` | Stop the daemon gracefully |
139
+ | `browser --version` / `browser update` | Show version; upgrade to the latest PyPI release. The daemon checks PyPI once a day and the CLI prints a one-line hint on stderr when a newer version exists (`BROWSER_NO_UPDATE_CHECK=1` disables) |
140
+
141
+ ### Page commands
142
+
143
+ All print JSON (snapshot prints text). Add `-s` / `--snapshot` to any action to get a fresh snapshot in the same call.
144
+
145
+ | Command | Description |
146
+ | :-- | :-- |
147
+ | `navigate <url> [--wait load\|domcontentloaded\|networkidle]` | Returns as soon as the page is usable; never fails on a slow `networkidle` |
148
+ | `snapshot [scope] [--all] [--max N] [--json]` | Visible interactive elements + headings. `--all` adds text blocks, `--json` gives boxes and unique selectors |
149
+ | `click <target> [--double]` | |
150
+ | `type <target> <text> [--sequential] [--submit]` | `fill()` by default; `--sequential` sends key events (autocomplete); `--submit` presses Enter |
151
+ | `press <key> [target]` | `Enter`, `Tab`, `Control+a`, … |
152
+ | `hover <target>` | |
153
+ | `select <target> <value-or-label>` | |
154
+ | `scroll [up\|down] [px]` / `scroll <target>` | |
155
+ | `text [selector]` | Readable text of the page or an element (cheap extraction) |
156
+ | `wait [--text T \| --selector S] [--gone] [--timeout ms]` | |
157
+ | `screenshot [target] [-o path] [-f] [-q 70]` | JPEG; element screenshots via any target |
158
+ | `eval <js>` | Evaluate an expression in the page |
159
+ | `console [--clear]` | Buffered console messages |
160
+ | `back` / `forward` | |
161
+ | `batch` | JSON lines on stdin, run in one round-trip, stop at first failure |
162
+
163
+ **Targets:** `@e12` (ref from snapshot — preferred) · CSS selector · `text=Create` · `role=button[name=Create]` · `label=Email` · `placeholder=Search` · or flags `--text / --role [--name] / --label / --placeholder`. Ambiguous CSS selectors are refused (strict mode) instead of clicking the first match.
164
+
165
+ ***
166
+
167
+ ## Architecture
168
+
169
+ * **Daemon** (`browser-daemon`): Unix socket server (`~/.browser-daemon/socket`, mode 600) owning a headless Chromium, plus a headed one that exists only while some session is `show`n.
170
+ * **CLI** (`browser`): ~40 ms per call, no Playwright import on the daemon path.
171
+ * **Sessions**: one isolated browser context each. Hidden sessions are **frozen** after 10 s idle (script execution paused, ~3% CPU on animated dashboards; callbacks that fire while frozen are dropped, so `BROWSER_FREEZE_AFTER=0` disables it) and **hibernated** to `~/.browser-daemon/sessions/<id>.json` (cookies + storage + URL) after 10 min idle or on shutdown; they are rehydrated transparently on the next command. Tune with `BROWSER_FREEZE_AFTER` / `BROWSER_HIBERNATE_AFTER` (seconds).
172
+ * **Resource profile** (M4, Cloudflare dashboard parked in a session): 2% CPU / 1.1 GB vs 264% CPU / 2.1 GB for v0.2. See [OPTIMIZATION.md](OPTIMIZATION.md) for the measurements.
173
+
174
+ ## Anti-Detection
175
+
176
+ * `navigator.webdriver` hidden via `add_init_script`
177
+ * Desktop Chrome user agent derived from the actual Chromium version
178
+ * 1280x800 viewport (desktop layouts; same size Anthropic/OpenAI computer-use tooling targets)
179
+
180
+ ## Output Format
181
+
182
+ Action responses:
183
+
184
+ ```json
185
+ {"success": true, "url": "https://github.com", "title": "GitHub"}
186
+ ```
187
+
188
+ Errors (exit code 1):
189
+
190
+ ```json
191
+ {"success": false, "error": "ref @e9 is unknown or stale (page changed); run snapshot again"}
192
+ ```
193
+
194
+ `snapshot --json`:
195
+
196
+ ```json
197
+ {"success": true, "url": "...", "title": "...", "scrollY": 0, "viewportHeight": 800, "viewportWidth": 1280, "documentHeight": 2400,
198
+ "elements": [{"ref": "e3", "role": "button", "name": "Create", "pos": "", "box": [912, 640, 88, 36]}]}
199
+ ```
200
+
201
+ ## Using with Coding Agents
202
+
203
+ Share [`SKILL.md`](SKILL.md) with your coding agent harness; see [AGENTS.md](AGENTS.md) for the integration guide.
204
+
205
+ ## Rust implementation (preview)
206
+
207
+ `rust/` contains a Rust client and daemon with the same CLI and socket protocol — no Python, Playwright or
208
+ Node at runtime (Chromium from `browser install` or `BROWSER_CHROME_PATH` is still required). Per-call
209
+ overhead 40 ms → 2 ms, daemon RSS −90 MB, frame- and shadow-DOM-aware snapshots. See OPTIMIZATION.md §7.
210
+
211
+ ```bash
212
+ cd rust && cargo build --release
213
+ ./target/release/browser-daemon & # drop-in for the Python daemon
214
+ ./target/release/browser list
215
+ ```
216
+
217
+ ## Development
218
+
219
+ ```bash
220
+ uv sync
221
+ .venv/bin/python -m unittest -v tests/test_cli.py # end-to-end tests against scratch/bench/site
222
+ .venv/bin/python scratch/bench/run.py <label> # benchmark (latency, tokens, idle CPU/RSS)
223
+ .venv/bin/python scratch/bench/compare.py baseline <label>
224
+ ```
225
+
226
+ ## Troubleshooting
227
+
228
+ | Symptom | Fix |
229
+ | :-- | :-- |
230
+ | `Command not found: browser` | `export PATH="$HOME/.local/bin:$PATH"` |
231
+ | `Daemon not running` | `browser-daemon` |
232
+ | Browser doesn't launch | `browser install` |
233
+ | `Session not found` | `browser list` |
234
+ | `ref @eN is unknown or stale` | Page changed; run `snapshot` again |
235
+ | `strict mode violation` | Selector matched several elements; use an `@ref`, `--text`, or a tighter selector |
236
+ | Stale Chromium processes | `browser cleanup` |
237
+
238
+ ### Installing the Rust binaries (preview, macOS arm64 build only for now)
239
+
240
+ ```bash
241
+ curl -fsSL https://raw.githubusercontent.com/jshan9078/browser-automation-cli/main/rust/install.sh | sh
242
+ ```
243
+
244
+ Installs `browser` and `browser-daemon` into `~/.local/bin` (set `BROWSER_CLI_BIN` to change). Other platforms: `cd rust && cargo build --release`.
245
+
246
+ Wheel (same PyPI project name, so download stats carry over): `cd rust && uvx maturin build --release` →
247
+ `uv tool install rust/target/wheels/browser_automation_cli-*.whl`. Publishing the Rust build to PyPI is
248
+ planned for 0.4.0 once Linux/Windows wheels are built in CI; until then the PyPI package is the Python
249
+ implementation and the Rust wheel is attached to the GitHub release.
@@ -0,0 +1,215 @@
1
+ # Browser Automation CLI
2
+
3
+ > **If you are an LLM, see** **[AGENTS.md](https://github.com/jshan9078/browser-automation-cli/blob/main/AGENTS.md)** **for quick setup and usage instructions.**
4
+
5
+ A lightweight, self-hosted browser automation tool with a background daemon and CLI client. Enables authenticated web automation, screenshots, compact page snapshots, and page interactions via simple CLI commands. Share the [`SKILL.md`](https://github.com/jshan9078/browser-automation-cli/blob/main/SKILL.md) file with your coding agent harness for seamless integration.
6
+
7
+ ## Why This Exists
8
+
9
+ Coding agents need to interact with authenticated web apps. Existing solutions all have tradeoffs:
10
+
11
+ * **Chrome DevTools MCP** — requires Node.js, per-agent MCP server configuration, Google telemetry by default, and complex setup for each coding agent
12
+ * **BrowserMCP and similar tools** — require installing Chrome extensions, tie into specific ecosystems, and use MCP which bloats the agent's context window with tool definitions and protocol overhead
13
+ * **Playwright/Puppeteer scripts** — require writing code for every interaction, no persistent auth state
14
+ * **AI browser frameworks** — heavy, opinionated, and framework-locked
15
+
16
+ Browser CLI solves this with a persistent daemon that any agent can call via subprocess. No extensions, no MCP config, no SDKs, no ecosystem lock-in. Sessions persist across agent calls (and daemon restarts) so you only log in once.
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ uv tool install browser-automation-cli
22
+ browser install
23
+ ```
24
+
25
+ If commands are not found after install, add `~/.local/bin` to your PATH:
26
+
27
+ ```bash
28
+ export PATH="$HOME/.local/bin:$PATH"
29
+ ```
30
+
31
+ ## Quick Start
32
+
33
+ ### 1. Start the daemon
34
+
35
+ ```bash
36
+ browser-daemon
37
+ ```
38
+
39
+ Nothing visible opens: the daemon runs Chromium headless. Keep this terminal running (or background it).
40
+
41
+ ### 2. Create a session
42
+
43
+ ```bash
44
+ browser create # headless session
45
+ browser create --show # opens a window so you can log in; `browser <id> hide` afterwards
46
+ ```
47
+
48
+ A session is an isolated browser profile (cookies, storage). Log into any sites you need while it is shown; the agent can drive it hidden afterwards. Sessions survive daemon restarts.
49
+
50
+ ### 3. Run browser actions
51
+
52
+ ```bash
53
+ browser <id> navigate https://github.com
54
+ browser <id> snapshot # interactive elements, one line each, with @refs
55
+ browser <id> click @e7 # click by ref from the snapshot
56
+ browser <id> click --text "Sign in" # or by visible text / role / label
57
+ browser <id> type --label "Username" octocat
58
+ browser <id> screenshot # JPEG saved under ~/.browser-daemon/shots/
59
+ ```
60
+
61
+ A snapshot looks like this (Cloudflare login, 245 tokens):
62
+
63
+ ```
64
+ url: https://dash.cloudflare.com/login
65
+ title: Cloudflare Dashboard | Manage Your Account
66
+ @e2 link "Sign up" href="/sign-up"
67
+ h1 "Sign in to Cloudflare"
68
+ @e3 button "Continue with Google"
69
+ @e7 textbox "Email"
70
+ @e8 textbox "Password" type="password"
71
+ @e10 checkbox "Save email and login method on this device"
72
+ @e11 button "Sign in" [disabled]
73
+ ```
74
+
75
+ ### 4. Manage sessions
76
+
77
+ ```bash
78
+ browser list # JSON (--table for humans)
79
+ browser <id> show | hide # move between a visible window and headless
80
+ browser <id> delete
81
+ browser shutdown # stop the daemon; sessions are saved and restored next start
82
+ ```
83
+
84
+ ***
85
+
86
+ ## Commands Reference
87
+
88
+ ### Standalone (no daemon)
89
+
90
+ ```bash
91
+ browser capture <url> [-f] [-o <path>] # headless viewport screenshot (-f = full page)
92
+ browser install # download Chromium (~170 MB)
93
+ browser cleanup # kill Chromium processes launched from Playwright's cache
94
+ ```
95
+
96
+ ### Sessions
97
+
98
+ | Command | Description |
99
+ | :-- | :-- |
100
+ | `browser create [--show]` | New session; `--show` opens a window (for manual login) |
101
+ | `browser list [--table]` | Sessions with `state` (active / frozen / hibernated) and `visible` |
102
+ | `browser <id> show` / `hide` | Move the session to a visible window / back to headless (auth kept) |
103
+ | `browser <id> delete` | Close session and forget its cookies |
104
+ | `browser shutdown` | Stop the daemon gracefully |
105
+ | `browser --version` / `browser update` | Show version; upgrade to the latest PyPI release. The daemon checks PyPI once a day and the CLI prints a one-line hint on stderr when a newer version exists (`BROWSER_NO_UPDATE_CHECK=1` disables) |
106
+
107
+ ### Page commands
108
+
109
+ All print JSON (snapshot prints text). Add `-s` / `--snapshot` to any action to get a fresh snapshot in the same call.
110
+
111
+ | Command | Description |
112
+ | :-- | :-- |
113
+ | `navigate <url> [--wait load\|domcontentloaded\|networkidle]` | Returns as soon as the page is usable; never fails on a slow `networkidle` |
114
+ | `snapshot [scope] [--all] [--max N] [--json]` | Visible interactive elements + headings. `--all` adds text blocks, `--json` gives boxes and unique selectors |
115
+ | `click <target> [--double]` | |
116
+ | `type <target> <text> [--sequential] [--submit]` | `fill()` by default; `--sequential` sends key events (autocomplete); `--submit` presses Enter |
117
+ | `press <key> [target]` | `Enter`, `Tab`, `Control+a`, … |
118
+ | `hover <target>` | |
119
+ | `select <target> <value-or-label>` | |
120
+ | `scroll [up\|down] [px]` / `scroll <target>` | |
121
+ | `text [selector]` | Readable text of the page or an element (cheap extraction) |
122
+ | `wait [--text T \| --selector S] [--gone] [--timeout ms]` | |
123
+ | `screenshot [target] [-o path] [-f] [-q 70]` | JPEG; element screenshots via any target |
124
+ | `eval <js>` | Evaluate an expression in the page |
125
+ | `console [--clear]` | Buffered console messages |
126
+ | `back` / `forward` | |
127
+ | `batch` | JSON lines on stdin, run in one round-trip, stop at first failure |
128
+
129
+ **Targets:** `@e12` (ref from snapshot — preferred) · CSS selector · `text=Create` · `role=button[name=Create]` · `label=Email` · `placeholder=Search` · or flags `--text / --role [--name] / --label / --placeholder`. Ambiguous CSS selectors are refused (strict mode) instead of clicking the first match.
130
+
131
+ ***
132
+
133
+ ## Architecture
134
+
135
+ * **Daemon** (`browser-daemon`): Unix socket server (`~/.browser-daemon/socket`, mode 600) owning a headless Chromium, plus a headed one that exists only while some session is `show`n.
136
+ * **CLI** (`browser`): ~40 ms per call, no Playwright import on the daemon path.
137
+ * **Sessions**: one isolated browser context each. Hidden sessions are **frozen** after 10 s idle (script execution paused, ~3% CPU on animated dashboards; callbacks that fire while frozen are dropped, so `BROWSER_FREEZE_AFTER=0` disables it) and **hibernated** to `~/.browser-daemon/sessions/<id>.json` (cookies + storage + URL) after 10 min idle or on shutdown; they are rehydrated transparently on the next command. Tune with `BROWSER_FREEZE_AFTER` / `BROWSER_HIBERNATE_AFTER` (seconds).
138
+ * **Resource profile** (M4, Cloudflare dashboard parked in a session): 2% CPU / 1.1 GB vs 264% CPU / 2.1 GB for v0.2. See [OPTIMIZATION.md](OPTIMIZATION.md) for the measurements.
139
+
140
+ ## Anti-Detection
141
+
142
+ * `navigator.webdriver` hidden via `add_init_script`
143
+ * Desktop Chrome user agent derived from the actual Chromium version
144
+ * 1280x800 viewport (desktop layouts; same size Anthropic/OpenAI computer-use tooling targets)
145
+
146
+ ## Output Format
147
+
148
+ Action responses:
149
+
150
+ ```json
151
+ {"success": true, "url": "https://github.com", "title": "GitHub"}
152
+ ```
153
+
154
+ Errors (exit code 1):
155
+
156
+ ```json
157
+ {"success": false, "error": "ref @e9 is unknown or stale (page changed); run snapshot again"}
158
+ ```
159
+
160
+ `snapshot --json`:
161
+
162
+ ```json
163
+ {"success": true, "url": "...", "title": "...", "scrollY": 0, "viewportHeight": 800, "viewportWidth": 1280, "documentHeight": 2400,
164
+ "elements": [{"ref": "e3", "role": "button", "name": "Create", "pos": "", "box": [912, 640, 88, 36]}]}
165
+ ```
166
+
167
+ ## Using with Coding Agents
168
+
169
+ Share [`SKILL.md`](SKILL.md) with your coding agent harness; see [AGENTS.md](AGENTS.md) for the integration guide.
170
+
171
+ ## Rust implementation (preview)
172
+
173
+ `rust/` contains a Rust client and daemon with the same CLI and socket protocol — no Python, Playwright or
174
+ Node at runtime (Chromium from `browser install` or `BROWSER_CHROME_PATH` is still required). Per-call
175
+ overhead 40 ms → 2 ms, daemon RSS −90 MB, frame- and shadow-DOM-aware snapshots. See OPTIMIZATION.md §7.
176
+
177
+ ```bash
178
+ cd rust && cargo build --release
179
+ ./target/release/browser-daemon & # drop-in for the Python daemon
180
+ ./target/release/browser list
181
+ ```
182
+
183
+ ## Development
184
+
185
+ ```bash
186
+ uv sync
187
+ .venv/bin/python -m unittest -v tests/test_cli.py # end-to-end tests against scratch/bench/site
188
+ .venv/bin/python scratch/bench/run.py <label> # benchmark (latency, tokens, idle CPU/RSS)
189
+ .venv/bin/python scratch/bench/compare.py baseline <label>
190
+ ```
191
+
192
+ ## Troubleshooting
193
+
194
+ | Symptom | Fix |
195
+ | :-- | :-- |
196
+ | `Command not found: browser` | `export PATH="$HOME/.local/bin:$PATH"` |
197
+ | `Daemon not running` | `browser-daemon` |
198
+ | Browser doesn't launch | `browser install` |
199
+ | `Session not found` | `browser list` |
200
+ | `ref @eN is unknown or stale` | Page changed; run `snapshot` again |
201
+ | `strict mode violation` | Selector matched several elements; use an `@ref`, `--text`, or a tighter selector |
202
+ | Stale Chromium processes | `browser cleanup` |
203
+
204
+ ### Installing the Rust binaries (preview, macOS arm64 build only for now)
205
+
206
+ ```bash
207
+ curl -fsSL https://raw.githubusercontent.com/jshan9078/browser-automation-cli/main/rust/install.sh | sh
208
+ ```
209
+
210
+ Installs `browser` and `browser-daemon` into `~/.local/bin` (set `BROWSER_CLI_BIN` to change). Other platforms: `cd rust && cargo build --release`.
211
+
212
+ Wheel (same PyPI project name, so download stats carry over): `cd rust && uvx maturin build --release` →
213
+ `uv tool install rust/target/wheels/browser_automation_cli-*.whl`. Publishing the Rust build to PyPI is
214
+ planned for 0.4.0 once Linux/Windows wheels are built in CI; until then the PyPI package is the Python
215
+ implementation and the Rust wheel is attached to the GitHub release.