Browser automation via CLI — for humans and agents
Project description
webctl
Browser automation for AI agents and humans, built on the command line.
webctl start
webctl navigate "https://google.com"
webctl type 'role=combobox name~="Search"' "best restaurants nearby" --submit
webctl snapshot --interactive-only --limit 20
webctl stop --daemon
Why CLI Instead of MCP?
MCP browser tools have a fundamental problem: the server controls what enters your context. With Playwright MCP, every response includes the full accessibility tree plus console messages (default: "info" level). After a few page queries, your context is full.
CLI flips this around: you control what enters context.
# Filter before context
webctl snapshot --interactive-only --limit 30 # Only buttons, links, inputs
webctl snapshot --within "role=main" # Skip nav, footer, ads
# Pipe through Unix tools
webctl snapshot | grep -i "submit" # Find specific elements
webctl --format jsonl snapshot | jq '.data.role' # Extract with jq
webctl snapshot | head -50 # Truncate output
Beyond filtering, CLI gives you:
| Capability | CLI | MCP |
|---|---|---|
| Filter output | Built-in flags + grep/jq/head | Server decides |
| Debug | Run same command as agent | Opaque |
| Cache | webctl snapshot > cache.txt |
Every call hits server |
| Script | Save to .sh, version control | Ephemeral |
| Timeout | timeout 30 webctl ... |
Internal only |
| Parallelize | parallel, xargs, & |
Server-dependent |
| Human takeover | Same commands | Different interface |
Quick Start
pip install webctl # Requires Python 3.11+
webctl setup # Downloads Chromium (~150MB)
Verify it works:
webctl start
webctl navigate "https://example.com"
webctl snapshot --interactive-only
webctl stop --daemon
Install from source
git clone https://github.com/cosinusalpha/webctl
cd webctl
uv sync && uv run webctl setup
Linux system dependencies
playwright install-deps chromium
# Or manually: sudo apt-get install libnss3 libatk1.0-0 libatk-bridge2.0-0 ...
Core Concepts
Sessions
Browser stays open across commands. Cookies persist to disk.
webctl start # Visible browser
webctl start --mode unattended # Headless
webctl -s work start # Named profile (separate cookies)
webctl stop --daemon # Shutdown everything
Element Queries
Semantic targeting based on ARIA roles - stable across CSS refactors:
role=button # Any button
role=button name="Submit" # Exact match
role=button name~="Submit" # Contains (preferred)
role=textbox name~="Email" # Input field
role=link name~="Sign in" # Link
Output Control
webctl snapshot # Human-readable
webctl --quiet navigate "..." # Suppress events
webctl --result-only --format jsonl navigate "..." # Pure JSON, final result only
Commands
Navigation
webctl navigate "https://..." # Go to URL
webctl back # History back
webctl forward # History forward
webctl reload # Refresh
Observation
webctl snapshot # Full a11y tree
webctl snapshot --interactive-only # Buttons, links, inputs only
webctl snapshot --limit 30 # Cap output
webctl snapshot --within "role=main" # Scope to container
webctl snapshot --roles "button,link" # Filter by role
webctl query "role=button name~=Submit" # Debug query, get suggestions
webctl screenshot --path shot.png # Screenshot
Interaction
webctl click 'role=button name~="Submit"'
webctl type 'role=textbox name~="Email"' "user@example.com"
webctl type 'role=textbox name~="Search"' "query" --submit # Type + Enter
webctl select 'role=combobox name~="Country"' --label "Germany"
webctl check 'role=checkbox name~="Remember"'
webctl press Enter
webctl scroll down
webctl upload 'role=button name~="Upload"' --file ./doc.pdf
Wait Conditions
webctl wait network-idle
webctl wait 'exists:role=button name~="Continue"'
webctl wait 'visible:role=dialog'
webctl wait 'hidden:role=progressbar'
webctl wait 'url-contains:"/dashboard"'
Session Management
webctl status # Current state (includes console error counts)
webctl save # Persist cookies now
webctl sessions # List profiles
webctl pages # List tabs
webctl focus p2 # Switch tab
webctl close-page p1 # Close tab
Console Logs
webctl console # Get last 100 logs
webctl console --count # Just counts by level (LLM-friendly)
webctl console --level error # Filter to errors only
webctl console --follow # Stream new logs continuously
webctl console -n 50 -l warn # Last 50 warnings
Setup & Config
webctl setup # Install browser
webctl doctor # Diagnose installation
webctl init # Add to agent configs (CLAUDE.md, etc.)
webctl config show # Show settings
webctl config set idle_timeout 1800
Agent Integration
Tell your AI agent to use webctl. The easiest way:
webctl init # Creates CLAUDE.md, GEMINI.md, etc.
webctl init --agents claude # Only specific agents
Or manually add to your agent's config:
For web browsing, use webctl CLI. Run `webctl agent-prompt` for instructions.
For AI Agents
This section is designed to be read by AI agents directly.
webctl Quick Reference
Control a browser via CLI. Start with webctl start, end with webctl stop --daemon.
Commands:
webctl start # Open browser
webctl navigate "URL" # Go to URL
webctl snapshot --interactive-only # See clickable elements
webctl click 'role=button name~="Text"' # Click element
webctl type 'role=textbox name~="Field"' "text" # Type
webctl type 'role=textbox name~="Field"' "text" --submit # Type + Enter
webctl select 'role=combobox' --label "Option" # Dropdown
webctl wait 'exists:role=button name~="..."' # Wait for element
webctl stop --daemon # Close browser
Query syntax:
role=button- By ARIA role (button, link, textbox, combobox, checkbox)name~="partial"- Partial match (preferred, more robust)name="exact"- Exact match
Example - Login:
webctl start
webctl navigate "https://site.com/login"
webctl type 'role=textbox name~="Email"' "user@example.com"
webctl type 'role=textbox name~="Password"' "secret" --submit
webctl wait 'url-contains:"/dashboard"'
Tips:
- Use
--interactive-onlyto reduce output (only buttons, links, inputs) - Use
name~=for partial matching (handles minor text changes) - Use
webctl query "..."if element not found - shows suggestions - Use
--quietto suppress event output - Sessions persist cookies - login once, stay logged in
- Check
webctl statusfor console error counts before investigating - Use
webctl console --countfor log summary,--level errorfor details
Architecture
┌─────────────┐ TCP/IPC ┌─────────────┐
│ CLI │ ◄──────────────► │ Daemon │
│ (webctl) │ JSON-RPC │ (browser) │
└─────────────┘ └─────────────┘
│ │
▼ ▼
Agent/User Chromium + Playwright
- CLI: Stateless, sends commands to daemon
- Daemon: Manages browser, auto-starts on first command
- Profiles:
~/.local/share/webctl/profiles/ - Config:
~/.config/webctl/config.json
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file webctl-0.1.2.tar.gz.
File metadata
- Download URL: webctl-0.1.2.tar.gz
- Upload date:
- Size: 101.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
559eb49ae1c9672b22df2c1d6e53080ca6ecdf714849bb68e250047afd0eddc8
|
|
| MD5 |
5a658820caf2c990f7251a538a936024
|
|
| BLAKE2b-256 |
4adde7792036bfe1bf0128808eafd35c57b70c6c0132e892ce5526d075d6d0c5
|
Provenance
The following attestation bundles were made for webctl-0.1.2.tar.gz:
Publisher:
publish.yml on cosinusalpha/webctl
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
webctl-0.1.2.tar.gz -
Subject digest:
559eb49ae1c9672b22df2c1d6e53080ca6ecdf714849bb68e250047afd0eddc8 - Sigstore transparency entry: 819687294
- Sigstore integration time:
-
Permalink:
cosinusalpha/webctl@353b4811b1d9884311f51d9c418cee7aacc9bef4 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/cosinusalpha
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@353b4811b1d9884311f51d9c418cee7aacc9bef4 -
Trigger Event:
push
-
Statement type:
File details
Details for the file webctl-0.1.2-py3-none-any.whl.
File metadata
- Download URL: webctl-0.1.2-py3-none-any.whl
- Upload date:
- Size: 76.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6558433a278f867cd744848e49c17f9b0ed5c4353be13233c324580c9a375f46
|
|
| MD5 |
33c6a43f688fd4b6a719ce5e6c859b35
|
|
| BLAKE2b-256 |
7833f06307e2db7317cbf1d79341b7c970536cf645789ab6e81f90ca09d94c61
|
Provenance
The following attestation bundles were made for webctl-0.1.2-py3-none-any.whl:
Publisher:
publish.yml on cosinusalpha/webctl
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
webctl-0.1.2-py3-none-any.whl -
Subject digest:
6558433a278f867cd744848e49c17f9b0ed5c4353be13233c324580c9a375f46 - Sigstore transparency entry: 819687319
- Sigstore integration time:
-
Permalink:
cosinusalpha/webctl@353b4811b1d9884311f51d9c418cee7aacc9bef4 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/cosinusalpha
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@353b4811b1d9884311f51d9c418cee7aacc9bef4 -
Trigger Event:
push
-
Statement type: