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.
- {browser_automation_cli-0.2.1 → browser_automation_cli-0.3.0}/.gitignore +2 -0
- browser_automation_cli-0.3.0/AGENTS.md +168 -0
- browser_automation_cli-0.3.0/PKG-INFO +249 -0
- browser_automation_cli-0.3.0/README.md +215 -0
- browser_automation_cli-0.3.0/SKILL.md +127 -0
- browser_automation_cli-0.3.0/cli/main.py +371 -0
- browser_automation_cli-0.3.0/daemon/browser.py +511 -0
- browser_automation_cli-0.3.0/daemon/server.py +209 -0
- browser_automation_cli-0.3.0/daemon/session.py +269 -0
- browser_automation_cli-0.3.0/daemon/update.py +85 -0
- {browser_automation_cli-0.2.1 → browser_automation_cli-0.3.0}/pyproject.toml +4 -1
- browser_automation_cli-0.2.1/AGENTS.md +0 -232
- browser_automation_cli-0.2.1/Makefile +0 -29
- browser_automation_cli-0.2.1/PKG-INFO +0 -259
- browser_automation_cli-0.2.1/README.md +0 -225
- browser_automation_cli-0.2.1/SKILL.md +0 -276
- browser_automation_cli-0.2.1/cli/main.py +0 -356
- browser_automation_cli-0.2.1/daemon/browser.py +0 -236
- browser_automation_cli-0.2.1/daemon/server.py +0 -152
- browser_automation_cli-0.2.1/daemon/session.py +0 -91
- browser_automation_cli-0.2.1/setup.sh +0 -33
- browser_automation_cli-0.2.1/uv.lock +0 -120
- {browser_automation_cli-0.2.1 → browser_automation_cli-0.3.0}/LICENSE.txt +0 -0
|
@@ -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.
|