Awesome-claude-skills pinchtab
git clone https://github.com/Anna-Pinewood/awesome-claude-skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/Anna-Pinewood/awesome-claude-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/pinchtab" ~/.claude/skills/anna-pinewood-awesome-claude-skills-pinchtab && rm -rf "$T"
skills/pinchtab/SKILL.mdPinchtab
Fast, lightweight browser control for AI agents via HTTP + accessibility tree.
Setup
Start Pinchtab in one of these modes:
# Headless (default) — no UI, pure automation (lowest token cost when using /text and filtered snapshots) pinchtab & # Headed — visible Chrome for human + agent workflows BRIDGE_HEADLESS=false pinchtab & # Dashboard/orchestrator — profile manager + launcher, no browser in dashboard process pinchtab dashboard &
Default port:
9867. Override with BRIDGE_PORT=9868.
Auth: set BRIDGE_TOKEN=<secret> and pass Authorization: Bearer <secret>.
Base URL for all examples:
http://localhost:9867
Token savings come from the API shape (
/text, /snapshot?filter=interactive&format=compact), not from headless vs headed alone.
Headed mode definition
Headed mode means a real visible Chrome window managed by Pinchtab.
- Human can open profile(s), log in, pass 2FA/captcha, and validate page state
- Agent then calls Pinchtab HTTP APIs against that same running profile instance
- Session state persists in the profile directory, so follow-up runs reuse cookies/storage
In dashboard workflows, the dashboard process itself does not launch Chrome; it launches profile instances that run Chrome (headed or headless).
To resolve a running profile endpoint from dashboard state:
pinchtab connect <profile-name>
Recommended human + agent flow:
# human pinchtab dashboard # setup profile + launch profile instance # agent PINCHTAB_BASE_URL="$(pinchtab connect <profile-name>)" curl "$PINCHTAB_BASE_URL/health"
Profile Management (Dashboard Mode)
When running
pinchtab dashboard, profiles are managed via the dashboard API on port 9867.
List profiles
curl http://localhost:9867/profiles
Returns array of profiles with
id, name, accountEmail, useWhen, etc.
Start a profile by ID
# Auto-allocate port (recommended) curl -X POST http://localhost:9867/profiles/278be873adeb/start # With specific port and headless mode curl -X POST http://localhost:9867/profiles/278be873adeb/start \ -H 'Content-Type: application/json' \ -d '{"port": "9868", "headless": true}' # Short alias (same behavior) curl -X POST http://localhost:9867/start/278be873adeb
Returns the instance info including the allocated
port. Use that port for all subsequent API calls (navigate, snapshot, action, etc.).
Stop a profile by ID
curl -X POST http://localhost:9867/profiles/278be873adeb/stop # Short alias curl -X POST http://localhost:9867/stop/278be873adeb
Check profile instance status
# By profile ID (recommended) curl http://localhost:9867/profiles/278be873adeb/instance # By profile name (also works) curl http://localhost:9867/profiles/Pinchtab%20org/instance
Launch by name (dashboard style)
curl -X POST http://localhost:9867/instances/launch \ -H 'Content-Type: application/json' \ -d '{"name": "work", "port": "9868"}'
Typical agent flow with profiles
# 1. List profiles to find the right one PROFILES=$(curl -s http://localhost:9867/profiles) # Pick the profile ID you need (12-char hex, e.g. "278be873adeb") # 2. Start the profile (auto-allocates port) INSTANCE=$(curl -s -X POST http://localhost:9867/profiles/$PROFILE_ID/start) PORT=$(echo $INSTANCE | jq -r .port) # 3. Use the instance (all API calls go to the instance port) curl -X POST http://localhost:$PORT/navigate -H 'Content-Type: application/json' \ -d '{"url": "https://mail.google.com"}' curl http://localhost:$PORT/snapshot?maxTokens=4000 # 4. Check instance status curl http://localhost:9867/profiles/$PROFILE_ID/instance # 5. Stop when done curl -s -X POST http://localhost:9867/profiles/$PROFILE_ID/stop
Profile IDs
Each profile gets a stable 12-char hex ID (SHA-256 of name, truncated) stored in
profile.json. The ID is generated at creation time and never changes. Use IDs instead of names in automation — they're URL-safe and stable.
Core Workflow
The typical agent loop:
- Navigate to a URL
- Snapshot the accessibility tree (get refs)
- Act on refs (click, type, press)
- Snapshot again to see results
Refs (e.g.
e0, e5, e12) are cached per tab after each snapshot — no need to re-snapshot before every action unless the page changed significantly.
API Reference
Navigate
curl -X POST http://localhost:9867/navigate \ -H 'Content-Type: application/json' \ -d '{"url": "https://example.com"}' # With options: custom timeout, block images, open in new tab curl -X POST http://localhost:9867/navigate \ -H 'Content-Type: application/json' \ -d '{"url": "https://example.com", "timeout": 60, "blockImages": true, "newTab": true}'
Snapshot (accessibility tree)
# Full tree curl http://localhost:9867/snapshot # Interactive elements only (buttons, links, inputs) — much smaller curl "http://localhost:9867/snapshot?filter=interactive" # Limit depth curl "http://localhost:9867/snapshot?depth=5" # Smart diff — only changes since last snapshot (massive token savings) curl "http://localhost:9867/snapshot?diff=true" # Text format — indented tree, ~40-60% fewer tokens than JSON curl "http://localhost:9867/snapshot?format=text" # Compact format — one-line-per-node, 56-64% fewer tokens than JSON (recommended) curl "http://localhost:9867/snapshot?format=compact" # YAML format curl "http://localhost:9867/snapshot?format=yaml" # Scope to CSS selector (e.g. main content only) curl "http://localhost:9867/snapshot?selector=main" # Truncate to ~N tokens curl "http://localhost:9867/snapshot?maxTokens=2000" # Combine for maximum efficiency curl "http://localhost:9867/snapshot?format=compact&selector=main&maxTokens=2000&filter=interactive" # Disable animations before capture curl "http://localhost:9867/snapshot?noAnimations=true" # Write to file curl "http://localhost:9867/snapshot?output=file&path=/tmp/snapshot.json"
Returns flat JSON array of nodes with
ref, role, name, depth, value, nodeId.
Token optimization: Use
?format=compact for best token efficiency. Add ?filter=interactive for action-oriented tasks (~75% fewer nodes). Use ?selector=main to scope to relevant content. Use ?maxTokens=2000 to cap output. Use ?diff=true on multi-step workflows to see only changes. Combine all params freely.
Act on elements
# Click by ref curl -X POST http://localhost:9867/action \ -H 'Content-Type: application/json' \ -d '{"kind": "click", "ref": "e5"}' # Type into focused element (click first, then type) curl -X POST http://localhost:9867/action \ -H 'Content-Type: application/json' \ -d '{"kind": "click", "ref": "e12"}' curl -X POST http://localhost:9867/action \ -H 'Content-Type: application/json' \ -d '{"kind": "type", "ref": "e12", "text": "hello world"}' # Press a key curl -X POST http://localhost:9867/action \ -H 'Content-Type: application/json' \ -d '{"kind": "press", "key": "Enter"}' # Focus an element curl -X POST http://localhost:9867/action \ -H 'Content-Type: application/json' \ -d '{"kind": "focus", "ref": "e3"}' # Fill (set value directly, no keystrokes) curl -X POST http://localhost:9867/action \ -H 'Content-Type: application/json' \ -d '{"kind": "fill", "selector": "#email", "text": "user@example.com"}' # Hover (trigger dropdowns/tooltips) curl -X POST http://localhost:9867/action \ -H 'Content-Type: application/json' \ -d '{"kind": "hover", "ref": "e8"}' # Select dropdown option (by value or visible text) curl -X POST http://localhost:9867/action \ -H 'Content-Type: application/json' \ -d '{"kind": "select", "ref": "e10", "value": "option2"}' # Scroll to element curl -X POST http://localhost:9867/action \ -H 'Content-Type: application/json' \ -d '{"kind": "scroll", "ref": "e20"}' # Scroll by pixels (infinite scroll pages) curl -X POST http://localhost:9867/action \ -H 'Content-Type: application/json' \ -d '{"kind": "scroll", "scrollY": 800}' # Click and wait for navigation (link clicks) curl -X POST http://localhost:9867/action \ -H 'Content-Type: application/json' \ -d '{"kind": "click", "ref": "e5", "waitNav": true}'
Extract text
# Readability mode (default) — strips nav/footer/ads, keeps article/main content curl http://localhost:9867/text # Raw innerText (old behavior) curl "http://localhost:9867/text?mode=raw"
Returns
{url, title, text}. Cheapest option (~1K tokens for most pages).
Download files
# Download using browser session (preserves cookies, auth, stealth) # Returns base64 JSON by default curl "http://localhost:9867/download?url=https://site.com/report.pdf" # Raw bytes (pipe to file) curl "http://localhost:9867/download?url=https://site.com/image.jpg&raw=true" -o image.jpg # Save directly to disk curl "http://localhost:9867/download?url=https://site.com/export.csv&output=file&path=/tmp/export.csv"
Key use case: downloading files from authenticated sites — the browser's cookies and stealth settings are used automatically. No need to extract cookies and use curl separately.
Screenshot
# Raw JPEG bytes curl "http://localhost:9867/screenshot?raw=true" -o screenshot.jpg # With quality setting (default 80) curl "http://localhost:9867/screenshot?raw=true&quality=50" -o screenshot.jpg
Evaluate JavaScript
curl -X POST http://localhost:9867/evaluate \ -H 'Content-Type: application/json' \ -d '{"expression": "document.title"}'
Tab management
# List tabs curl http://localhost:9867/tabs # Open new tab curl -X POST http://localhost:9867/tab \ -H 'Content-Type: application/json' \ -d '{"action": "new", "url": "https://example.com"}' # Close tab curl -X POST http://localhost:9867/tab \ -H 'Content-Type: application/json' \ -d '{"action": "close", "tabId": "TARGET_ID"}'
Multi-tab: pass
?tabId=TARGET_ID to snapshot/screenshot/text, or "tabId" in POST body.
Tab locking (multi-agent)
# Lock a tab (default 30s timeout, max 5min) curl -X POST http://localhost:9867/tab/lock \ -H 'Content-Type: application/json' \ -d '{"tabId": "TARGET_ID", "owner": "agent-1", "timeoutSec": 60}' # Unlock curl -X POST http://localhost:9867/tab/unlock \ -H 'Content-Type: application/json' \ -d '{"tabId": "TARGET_ID", "owner": "agent-1"}'
Locked tabs show
owner and lockedUntil in /tabs. Returns 409 on conflict.
Batch actions
# Execute multiple actions in sequence curl -X POST http://localhost:9867/actions \ -H 'Content-Type: application/json' \ -d '{"actions":[{"kind":"click","ref":"e3"},{"kind":"type","ref":"e3","text":"hello"},{"kind":"press","key":"Enter"}]}' # Stop on first error (default: false, continues through all) curl -X POST http://localhost:9867/actions \ -H 'Content-Type: application/json' \ -d '{"tabId":"TARGET_ID","actions":[...],"stopOnError":true}'
Cookies
# Get cookies for current page curl http://localhost:9867/cookies # Set cookies curl -X POST http://localhost:9867/cookies \ -H 'Content-Type: application/json' \ -d '{"url":"https://example.com","cookies":[{"name":"session","value":"abc123"}]}'
Stealth
# Check stealth status and score curl http://localhost:9867/stealth/status # Rotate browser fingerprint curl -X POST http://localhost:9867/fingerprint/rotate \ -H 'Content-Type: application/json' \ -d '{"os":"windows"}' # os: "windows", "mac", or omit for random
Health check
curl http://localhost:9867/health
Token Cost Guide
| Method | Typical tokens | When to use |
|---|---|---|
| ~800 | Reading page content |
| ~3,600 | Finding buttons/links to click |
| varies | Multi-step workflows (only changes) |
| ~56-64% less | One-line-per-node, best token efficiency |
| ~40-60% less | Indented tree, cheaper than JSON |
| ~10,500 | Full page understanding |
| ~2K (vision) | Visual verification |
Strategy: Start with
/snapshot?filter=interactive. Use ?diff=true on subsequent snapshots in multi-step tasks. Use /text when you only need the readable content. Use ?format=text to cut token costs further. Use full /snapshot only for complete page understanding.
Environment Variables
Core runtime
| Var | Default | Description |
|---|---|---|
| | Bind address — localhost only by default. Set for network access |
| | HTTP port |
| | Run Chrome headless |
| (none) | Bearer auth token (recommended when using ) |
| | Chrome profile dir |
| | State/session storage |
| | Skip tab restore on startup |
| | Stealth level: or |
| | Max open tabs (0 = unlimited) |
| | Block image loading |
| | Block all media (images + fonts + CSS + video) |
| | Disable CSS animations/transitions |
| (none) | Force browser timezone (IANA tz) |
| | Chrome version string used by fingerprint rotation |
| (auto) | Path to Chrome/Chromium binary |
| (none) | Extra Chrome flags (space-separated) |
| | Path to config JSON file |
| | Action timeout (seconds) |
| | Navigation timeout (seconds) |
| (none) | Connect to existing Chrome DevTools |
| | Disable dashboard/orchestrator endpoints on instance processes |
Dashboard mode (pinchtab dashboard
)
pinchtab dashboard| Var | Default | Description |
|---|---|---|
| | Auto-launch a default profile at dashboard startup |
| | Profile name for auto-launch |
| | Port for auto-launched profile |
| (unset) | If set, auto-launched profile is headed; unset means headless |
| | CLI helper base URL for |
Troubleshooting
Health check hangs
After a force-kill or unclean shutdown, Pinchtab may start but stop responding on the HTTP port. Symptoms:
curl http://localhost:9867/health hangs indefinitely, but pgrep pinchtab shows the process is alive.
Fix:
# 1. Kill pinchtab and all its Chrome children pkill -f pinchtab; pkill -f "chrome-profile"; sleep 2 # 2. Restart with BRIDGE_NO_RESTORE=true to skip restoring stale tabs BRIDGE_HEADLESS=false BRIDGE_NO_RESTORE=true pinchtab > /tmp/pinchtab.log 2>&1 & # 3. Wait and verify (always use --max-time on health checks!) sleep 5 && curl -s --max-time 5 http://localhost:9867/health
Always use
on --max-time 5
calls — without a timeout, curl will hang forever if Pinchtab is stuck, blocking the agent./health
General restart procedure
# Check if running pgrep -x pinchtab || echo "NOT_RUNNING" # Clean kill pkill -f pinchtab; pkill -f "chrome-profile"; sleep 2 # Start fresh BRIDGE_HEADLESS=false BRIDGE_NO_RESTORE=true pinchtab > /tmp/pinchtab.log 2>&1 & sleep 5 && curl -s --max-time 5 http://localhost:9867/health
Known Websites & Recipes
Pre-mapped workflows for specific sites — use these instead of exploratory snapshots to save tokens.
See
for all recipes:websites_and_actions/
- Ktalk — Download transcription
- Mattermost — Save thread to file
- 2GIS self-hosted OWA — Extract calendar events
Cookie Transfer
When a site blocks login in Pinchtab (e.g. invisible hCaptcha detecting CDP), transfer cookies from the user's regular Chrome. See
for the full recipe.cookie_transfer.md
Tips
- Always use
on ALL curl calls to Pinchtab — not just--max-time
. Navigation, snapshots, text extraction, and actions can all hang if the page is slow, stuck, or Pinchtab is unresponsive. Recommended timeouts:/health
for--max-time 5
,/health
for--max-time 10
,/text
,/snapshot
,/action
for--max-time 30
. Without this, a hanging curl blocks the agent indefinitely./navigate - Always pass
explicitly when working with multiple tabs — active tab tracking can be unreliabletabId - Refs are stable between snapshot and actions — no need to re-snapshot before clicking
- After navigation or major page changes, take a new snapshot to get fresh refs
- Use
by default, fall back to full snapshot when neededfilter=interactive - Pinchtab persists sessions — tabs survive restarts (disable with
)BRIDGE_NO_RESTORE=true - Chrome profile is persistent — cookies/logins carry over between runs
- Chrome uses its native User-Agent by default —
only affects fingerprint rotationBRIDGE_CHROME_VERSION - Use
orBRIDGE_BLOCK_IMAGES=true
on navigate for read-heavy tasks — reduces bandwidth and memory"blockImages": true