Browser control for AI agents.
12MB Go binary. Zero config. Accessibility-first.
🦀 PINCH! PINCH!
Most agent browser tools (OpenClaw, Playwright MCP, Browser Use) are tightly coupled — they only work inside their own framework. If you switch agents or want to script something in bash, you're out of luck.
Pinchtab is a standalone HTTP server. Any agent, any language, even curl:
# Read a page — 800 tokens instead of 10,000
curl localhost:9867/text?tabId=X
# Click a button by ref from the last snapshot
curl -X POST localhost:9867/action -d '{"kind":"click","ref":"e5"}'| Pinchtab | OpenClaw Browser | |
|---|---|---|
| Tokens per page | ~800 (/text) / ~3,600 (interactive) |
~10,000+ (full snapshot) |
| Interface | HTTP — any agent, any language | Internal only |
| A11y snapshots | ✅ | ✅ |
| Element interaction | ✅ | ✅ |
| Stealth mode | ✅ | ❌ |
| Session persistence | ✅ | ❌ |
| Self-contained binary | ✅ 12MB | ❌ |
- 5-13x cheaper than screenshots or full snapshots for read-heavy tasks (real measurements)
- Plain HTTP API — not locked to any agent framework
- Self-contained — 12MB binary, launches its own Chrome, zero config
- Stealth mode — bypasses bot detection on major sites
- Persistent sessions — log in once, stays logged in across restarts
curl -fsSL https://pinchtab.com/install.sh | bashThen:
pinchtab # Start the server
# In another terminal:
pinchtab nav https://httpbin.org # Navigate
pinchtab snap # Get snapshotRequirements:
- Node.js 18+ (for npm install)
- macOS or Linux
Troubleshooting:
Command not found / PATH issue:
# npm -g may be in an unusual location
npm config get prefix # Find where global packages go
export PATH="$(npm config get prefix)/bin:$PATH"Permission denied (EACCES):
# Option 1: Use nvm (recommended, fresh Node install)
curl https://github.com/nvm-sh/nvm/raw/master/install.sh | bash
nvm install node
npm install -g pinchtab
# Option 2: User prefix (no sudo)
npm install -g --prefix ~/.local pinchtab
export PATH="$HOME/.local/bin:$PATH" # Add to ~/.bashrc or ~/.zshrc
# Option 3: Change npm prefix globally
mkdir ~/.npm-global && npm config set prefix '~/.npm-global'
export PATH=~/.npm-global/bin:$PATH # Add to ~/.bashrc or ~/.zshrcnpm install -g pinchtab
pinchtab --version# Install
npm install -g pinchtab
# One terminal:
pinchtab # Start server (keep running)
# Another terminal:
pinchtab quick https://example.com # Navigate + analyze page automaticallyThe quick command combines navigation and page analysis, showing you clickable elements and suggesting next actions.
docker run -d -p 9867:9867 --security-opt seccomp=unconfined pinchtab/pinchtab
curl http://localhost:9867/healthInstall Pinchtab and set it up for browser automation.
Your agent can clone, build, and configure Pinchtab using the OpenClaw skill. Just ask.
# Build
go build -o pinchtab ./cmd/pinchtab
# Headless mode (default) — no window, pure automation (best for token-efficient API flows)
./pinchtab
# Headed mode — visible window for operator-in-the-loop flows
BRIDGE_HEADLESS=false ./pinchtab| Mode | Command | Notes |
|---|---|---|
| Headless (default) | ./pinchtab |
Launches managed Chrome without UI |
| Headed | BRIDGE_HEADLESS=false ./pinchtab |
Launches managed Chrome with visible window |
| Dashboard / orchestrator | ./pinchtab dashboard |
Runs control plane only (profiles + instances), no browser in dashboard process |
| Remote CDP | CDP_URL=http://localhost:9222 ./pinchtab |
Connects to an existing Chrome instead of launching one |
Common runtime options:
# Custom port
BRIDGE_PORT=9870 ./pinchtab
# Custom profile directory
BRIDGE_PROFILE=/path/to/profile ./pinchtab
# Enable API auth
BRIDGE_TOKEN=your-secret-token ./pinchtabChrome runs invisibly in the background — no window, pure API. Best for servers, CI, Docker, and unattended automation. Token savings come from using /text and filtered snapshot formats (/snapshot?filter=interactive&format=compact) rather than vision/screenshot-heavy flows.
./pinchtabAll core API flows are validated in headless mode.
Instead of launching your own Chrome, connect to an existing instance:
# Start Chrome with debugging enabled (localhost only)
chrome --remote-debugging-port=9222 &
# Get the WebSocket URL
CDP_URL=$(curl http://localhost:9222/json/version | jq -r '.webSocketDebuggerUrl')
# Connect Pinchtab to it
export CDP_URL
./pinchtabUse cases:
- 🤝 Multi-agent resource sharing — All agents share one Chrome (save 1.3GB per agent)
- 🧪 Integration testing — Multiple test scripts use the same browser and session
- 🐳 Docker/containers — Chrome in one container, Pinchtab in another
See docs/cdp-url-shared-chrome.md for detailed setup and use cases.
Headed mode is for mixed human + agent workflows:
- Human signs in, solves captchas/2FA, validates page state
- Agent continues through HTTP API against the same profile
- Team can watch automation behavior in real time
See docs/headed-mode-guide.md for use cases of the headed mode.
You can run headed mode in two ways:
- Single local instance:
BRIDGE_HEADLESS=false ./pinchtab- Dashboard-managed profiles (recommended for headed ops):
./pinchtab dashboardOpen http://localhost:9867/dashboard and:
- create/import profiles
- launch headed instances per profile/port
- stop profiles gracefully
- open Live popup for a specific running profile
- inspect Info (status, tabs, feed summary, logs)
When a profile is launched, your agent targets that profile instance URL (for example http://localhost:9868), not the dashboard URL.
Helper command to resolve a running profile URL from dashboard state:
pinchtab connect <profile-name>
# -> http://localhost:<profile-port>Recommended human + agent flow:
# human operator
pinchtab dashboard
# create/import profile, then launch it from the dashboard
# agent process
PINCHTAB_BASE_URL="$(pinchtab connect <profile-name>)"
curl "$PINCHTAB_BASE_URL/health"Pinchtab keeps persistent profiles. Default single-instance profile: ~/.pinchtab/chrome-profile/.
Dashboard-managed profiles: ~/.pinchtab/profiles/<profile-name>/.
In headed mode, log into sites in the visible Chrome window once; cookies and local storage persist across restarts. In headless mode, either copy an existing profile or inject cookies via POST /cookies.
- 🌲 Accessibility-first — structured tree with stable refs (
e0,e1...) for click, type, read - 🎯 Smart filters —
?filter=interactivereturns only buttons/links/inputs (~75% fewer tokens) - 🖱️ Direct actions — click, type, fill, press, focus, hover, select, scroll by ref or CSS selector
- 🕵️ Stealth mode — patches
navigator.webdriver, spoofs UA, hides automation flags - 💾 Session persistence — cookies, auth, tabs survive restarts
- 🚫 Resource blocking — Skip downloads for faster, leaner browsing:
- Images:
BRIDGE_BLOCK_IMAGESor--block-images - Media:
BRIDGE_BLOCK_MEDIA(images + video + audio) - Ads/tracking:
BRIDGE_BLOCK_ADSor--block-ads(100+ domains)
- Images:
- 🎬 Animation disabling — freeze CSS animations/transitions for consistent snapshots (
BRIDGE_NO_ANIMATIONSor?noAnimations=true) - 📝 Text extraction — readability mode (strips nav/ads) or raw
innerText - 🔄 Smart diff —
?diff=truereturns only changes since last snapshot - 📄 Text format —
?format=textfor indented tree (~40-60% fewer tokens than JSON) - ⚡ JS evaluation — escape hatch for anything the API doesn't cover
- 📸 Screenshots — JPEG with quality control for visual verification
| Method | Endpoint | Description |
|---|---|---|
GET |
/health |
Connection status |
GET |
/tabs |
List open tabs |
GET |
/snapshot |
Accessibility tree (primary interface) |
GET |
/screenshot |
JPEG screenshot (opt-in) |
GET |
/pdf |
PDF export of current page |
GET |
/text |
Readable page text (readability or raw) |
POST |
/navigate |
Go to URL |
POST |
/action |
Click, type, fill, press, focus, hover, select, scroll |
POST |
/evaluate |
Execute JavaScript |
POST |
/tab |
Open/close tabs |
POST |
/tab/lock |
Lock tab for exclusive agent access |
POST |
/tab/unlock |
Release tab lock |
POST |
/upload |
Set files on <input type=file> elements |
GET |
/download |
Download URL using browser session |
| Param | Description |
|---|---|
tabId |
Target tab (default: first tab) |
filter=interactive |
Only buttons, links, inputs |
depth=N |
Max tree depth |
diff=true |
Return only added/changed/removed nodes since last snapshot |
format=text |
Indented plain text instead of JSON (~40-60% fewer tokens) |
format=compact |
One-line-per-node format (56-64% fewer tokens than JSON) |
selector=CSS |
Scope tree to a CSS selector subtree (e.g. ?selector=main) |
maxTokens=N |
Truncate output to ~N tokens |
noAnimations=true |
Disable CSS animations before capture |
output=file |
Save snapshot to disk instead of returning |
path=/custom/path |
Custom file path (with output=file) |
| Param | Description |
|---|---|
tabId |
Target tab (default: first tab) |
quality=N |
JPEG quality (default: 80) |
noAnimations=true |
Disable CSS animations before capture |
output=file |
Save screenshot to disk instead of returning |
| Param | Type | Default | Description |
|---|---|---|---|
tabId |
string | first tab | Target tab |
| Layout | |||
paperWidth |
float | 8.5 | Paper width in inches |
paperHeight |
float | 11.0 | Paper height in inches |
landscape |
bool | false | Landscape orientation |
marginTop |
float | 0.4 | Top margin in inches |
marginBottom |
float | 0.4 | Bottom margin in inches |
marginLeft |
float | 0.4 | Left margin in inches |
marginRight |
float | 0.4 | Right margin in inches |
| Content | |||
scale |
float | 1.0 | Print scale (0.1–2.0) |
pageRanges |
string | all | Pages to export (e.g., 1-3,5) |
preferCSSPageSize |
bool | false | Honor CSS @page size |
| Header/Footer | |||
displayHeaderFooter |
bool | false | Show header and footer |
headerTemplate |
string | — | HTML for header (supports <span class=date/title/url/pageNumber/totalPages>) |
footerTemplate |
string | — | HTML for footer (same format as header) |
| Accessibility | |||
generateTaggedPDF |
bool | false | Generate accessible/tagged PDF |
generateDocumentOutline |
bool | false | Embed document outline |
| Output | |||
output=file |
string | JSON | Save to disk instead of returning base64 |
path=/custom/path |
string | auto | Custom file path (with output=file) |
raw=true |
bool | false | Return raw PDF bytes instead of JSON |
| Param | Description |
|---|---|
tabId |
Target tab (default: first tab) |
mode=raw |
Raw innerText instead of readability extraction |
The same pinchtab binary doubles as a CLI client with helpful guidance for beginners.
# Terminal 1: Start server (keep running)
pinchtab
# Terminal 2: Use CLI commands
pinchtab quick https://example.com # NEW: Navigate + analyze in one commandIf you forget to start the server:
$ pinchtab snap
❌ Pinchtab server is not running on http://localhost:9867
To start the server:
pinchtab # Run in foreground (recommended for beginners)
pinchtab & # Run in background
pinchtab --port 9868 # Use different portAfter navigation, get helpful next steps:
$ pinchtab nav https://example.com
💡 Next steps:
pinchtab snap # See page structure
pinchtab screenshot # Capture visual
pinchtab click <ref> # Click an element
pinchtab pdf -o output.pdf # Save as PDF# Quick start
pinchtab quick <url> # Navigate + analyze page
# Navigation & interaction
pinchtab nav https://httpbin.org/html # Navigate
pinchtab snap -i -c # Snapshot (interactive, compact)
pinchtab click e5 # Click element by ref
pinchtab type e12 hello world # Type into element
pinchtab press Enter # Press key
# Content extraction
pinchtab text # Extract readable text
pinchtab ss -o page.jpg # Screenshot
pinchtab pdf -o page.pdf --landscape # Export PDF (many options available)
pinchtab eval "document.title" # Run JavaScript
# Tab management
pinchtab tabs # List tabs
pinchtab tabs new https://httpbin.org # Open new tab
pinchtab health # Check serverShort aliases: nav, snap, ss, eval, tab. Config via PINCHTAB_URL and PINCHTAB_TOKEN env vars. Pipe output with jq:
pinchtab text | jq .text
pinchtab snap -i -c | jq '.[] | select(.name | contains("Login"))'Full help: pinchtab help
| Variable | Default | Description |
|---|---|---|
BRIDGE_BIND |
127.0.0.1 |
Bind address — localhost only by default. Set to 0.0.0.0 for network access |
BRIDGE_PORT |
9867 |
HTTP server port |
BRIDGE_TOKEN |
(none) | Bearer token for auth (recommended when using BRIDGE_BIND=0.0.0.0) |
BRIDGE_HEADLESS |
true |
Run Chrome headless (no window) |
BRIDGE_PROFILE |
~/.pinchtab/chrome-profile |
Chrome profile directory |
BRIDGE_STATE_DIR |
~/.pinchtab |
State/session storage |
BRIDGE_NO_RESTORE |
false |
Skip restoring tabs from previous session |
BRIDGE_STEALTH |
light |
Stealth level: light (basic) or full (canvas/WebGL/font spoofing) |
BRIDGE_MAX_TABS |
20 |
Max open tabs (0 = unlimited) |
BRIDGE_BLOCK_IMAGES |
false |
Block image loading |
BRIDGE_BLOCK_MEDIA |
false |
Block all media (images + fonts + CSS + video) |
BRIDGE_BLOCK_ADS |
false |
Block ads and tracking domains (100+ patterns) |
BRIDGE_NO_ANIMATIONS |
false |
Disable CSS animations/transitions globally |
BRIDGE_TIMEZONE |
(none) | Force browser timezone (IANA tz, e.g. Europe/Rome) |
BRIDGE_CHROME_VERSION |
144.0.7559.133 |
Chrome version string used by fingerprint rotation profiles |
BRIDGE_USER_AGENT |
(none) | Custom User-Agent string; also overrides Sec-Ch-Ua client hints via CDP |
BRIDGE_TIMEOUT |
15 |
Action timeout (seconds) |
BRIDGE_NAV_TIMEOUT |
30 |
Navigation timeout (seconds) |
BRIDGE_CONFIG |
~/.pinchtab/config.json |
Path to config JSON file |
CHROME_BINARY |
(auto) | Path to Chrome/Chromium binary |
CHROME_FLAGS |
(none) | Extra Chrome flags (space-separated) |
CDP_URL |
(none) | Connect to existing Chrome instead of launching |
BRIDGE_NO_DASHBOARD |
false |
Disable embedded dashboard/orchestrator endpoints on instance processes |
| Variable | Default | Description |
|---|---|---|
PINCHTAB_AUTO_LAUNCH |
false |
Auto-launch a default profile instance at dashboard startup |
PINCHTAB_DEFAULT_PROFILE |
default |
Profile name used by auto-launch |
PINCHTAB_DEFAULT_PORT |
9867 |
Port used by auto-launch |
PINCHTAB_HEADED |
(unset) | If set, auto-launched instance is headed; unset means headless |
PINCHTAB_DASHBOARD_URL |
http://localhost:$BRIDGE_PORT |
CLI helper base URL for pinchtab connect |
Headed mode lets humans and agents share the same browser session. Human logs in, handles 2FA and CAPTCHAs. Agent takes over via HTTP API — same cookies, same session. Profiles persist across restarts, so you log in once and automate forever.
The dashboard exposes POST /start/{id} and POST /stop/{id} for easy profile lifecycle management — agents can spin up a profile, do their work, and shut it down with three API calls.
See docs/headed-mode-guide.md for the full walkthrough with real examples.
Need to distinguish Pinchtab's Chrome from your regular browser? Use CHROME_BINARY to point at a renamed Chrome copy, CHROME_FLAGS to tag instances in ps, or rely on the separate profile directory that's already built-in.
See docs/identifying-instances.md for the full guide with examples.
Pinchtab doesn't just run Chrome — it manages hardened, detection-resistant instances with pre-flight stealth injection, automatic lock file cleanup, retry logic, and per-tab context lifecycle management.
See docs/chrome-lifecycle.md for the full deep dive covering allocator strategy, launch flag hardening, instance orchestration, and tab management.
┌─────────────┐ HTTP :9867 ┌──────────────┐ ┌─────────┐
│ Any Agent │ ──────────────► │ Pinchtab │ ── CDP ──► │ Chrome │
│ (OpenClaw, │ snapshot, act, │ │ │ │
│ PicoClaw, │ navigate, eval │ stealth + │ │ your │
│ curl, │ │ sessions + │ │ tabs │
│ scripts) │ │ a11y tree │ │ │
└─────────────┘ └──────────────┘ └─────────┘
See docs/pinchtab-architecture.md
| Screenshots | Accessibility Tree | |
|---|---|---|
| Tokens | ~2,000/image | ~200-500/page |
| Speed | Render → encode → transfer | Instant structured data |
| Reliability | Vision model guesses coordinates | Deterministic refs |
| LLM requirement | Vision model required | Any text LLM works |
| Cost (10-step task) | ~$0.06 | ~$0.015 |
Playwright MCP, OpenClaw, and Browser Use all default to accessibility trees for the same reason.
Measured on a live search results page:
| Method | Size | ~Tokens |
|---|---|---|
| Full a11y snapshot | 42 KB | 10,500 |
Interactive-only (?filter=interactive) |
14 KB | 3,600 |
Text extraction (/text) |
3 KB | 800 |
| Screenshot (vision model) | — | ~2,000 |
For read-heavy tasks (monitoring feeds, scraping search results), /text at ~800 tokens per page is 5x cheaper than a full snapshot and 13x cheaper than the same page via screenshots.
Example: 50-page search monitoring task
| Approach | Tokens | Est. cost |
|---|---|---|
| Screenshots (vision) | ~100,000 | $0.30 |
| Full snapshots | ~525,000 | $0.16 |
Pinchtab /text |
~40,000 | $0.01 |
| Pinchtab interactive filter | ~180,000 | $0.05 |
Use /text when you only need content. Use ?filter=interactive when you need to act. Use the full snapshot when you need page structure.
| Dependency | What it does | License |
|---|---|---|
| chromedp | Chrome DevTools Protocol driver for Go | MIT |
| cdproto | Generated CDP types and commands | MIT |
| gobwas/ws | Low-level WebSocket (used by chromedp) | MIT |
| go-json-experiment/json | JSON v2 library (used by cdproto) | BSD-3-Clause |
Everything else is Go standard library.
- Go 1.25+ (build from source) or download a prebuilt binary
- Google Chrome or Chromium installed
# From source
go install github.com/pinchtab/pinchtab@latest
# Or clone and build
git clone https://github.com/pinchtab/pinchtab.git
cd pinchtab
go build -o pinchtab ./cmd/pinchtabgit clone https://github.com/pinchtab/pinchtab.git
cd pinchtab
go build -o pinchtab ./cmd/pinchtab
./pinchtab
# Run tests (38 tests)
go test ./...Pinchtab gives AI agents full control of a real browser with your real accounts.
When you log into sites through Pinchtab's Chrome window, those sessions — cookies, tokens, saved passwords — persist in ~/.pinchtab/chrome-profile/. Any agent with HTTP access to Pinchtab can then act as you: read your email, post on your behalf, make purchases, access sensitive data.
This is by design. That's what makes it useful. But it means:
- You are responsible for what agents do with your accounts. Pinchtab is a tool, not a guardrail. If you give an agent access to your bank and it does something stupid, that's on you.
- Set
BRIDGE_TOKEN— without it, anyone on your network can control your browser. In production, this is non-negotiable. - Treat
~/.pinchtab/as sensitive — it contains your Chrome profile with all saved sessions and cookies. Guard it like you'd guard your passwords. - Pinchtab binds to all interfaces by default — use a firewall or reverse proxy if you're on a shared network.
- If using
CDP_URL: Chrome's DevTools Protocol has no authentication. Never expose the CDP port to the network. Only use localhost or SSH tunnels. See docs/cdp-url-shared-chrome.md#security for details. - Start with low-risk accounts. Don't point an experimental agent at your primary email or bank account on day one. Test with throwaway accounts first.
- No data leaves your machine — all processing is local. But the agents you connect might send data wherever they want.
Think of Pinchtab like giving someone your unlocked laptop. Powerful if you trust them. Dangerous if you don't.

Luigi Agosti · Agent Manager
| Bosch | Mario |
| Debugging the simulation 🔮 | First principles · ships fast |
Pinchtab is built to work seamlessly with OpenClaw — the open-source personal AI assistant. Use Pinchtab as your agent's browser backend for faster, cheaper web automation.


