name: pinchtab-helper description: | PinchTab browser automation - profiles, instances, multi-instance routing, tabs, actions, and anti-detection When user mentions PinchTab, browser automation, pinchtab commands, headed/headless browser, or web scraping with Chrome
PinchTab Browser Automation
Core Concepts
- Profile: stored browser state on disk (cookies, local storage, history, extensions). Persistent across restarts.
- Instance: a running Chrome process backed by a profile. One profile can have at most one active instance.
- Tab ID: opaque string returned by API. Never construct them.
- Shorthand routes (
pinchtab nav,pinchtab eval,pinchtab snap): proxy to the default/first instance. Do NOT use when targeting a non-default profile.
Authentication
The server requires a bearer token (Authorization: Bearer <token>) for all protected routes.
- Where the token lives: at
.server.tokeninside the config file selected by thePINCHTAB_CONFIGenv var. IfPINCHTAB_CONFIGis unset, the CLI defaults to~/.pinchtab/config.json; the launchd daemon pins it to~/Library/Application Support/pinchtab/config.jsonvia its plist. These can be two different files — if their tokens drift you get401 bad_tokenfrom the CLI while the daemon itself is fine. On this machinePINCHTAB_CONFIGis exported in fish config to the Library path so the CLI, daemon, and install script all share one config. - CLI auth: shorthand commands (
pinchtab nav,pinchtab health, …) read the token from that config file. SettingPINCHTAB_TOKENin the environment overrides the config-file token — handy for a one-off call, but if it's the only thing making the CLI work, you're masking a config split (fix the split instead). - Diagnose a 401:
pinchtab configprints the exact file + token it's using. Compare.server.tokenacross the candidate config files and curl each against the server:
pinchtab config # shows the config file + token currently in use
T=$(jq -r .server.token "$HOME/Library/Application Support/pinchtab/config.json")
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:9867/instances -H "Authorization: Bearer $T"
- REST/curl works with an explicit
Authorization: Bearer <token>header regardless ofPINCHTAB_CONFIG.
Critical: Multi-Instance Routing
Shorthand CLI commands route to the default instance. When working with a non-default profile:
- Get the instance ID:
pinchtab instances - Use instance-scoped CLI:
pinchtab instance navigate <instanceId> <url> - Or use REST API with instance-scoped routes (see API section below)
The always-on strategy auto-respawns the default instance — stopping it is futile. To prevent this, change strategy to explicit in config:
pinchtab config set multiInstance.strategy explicit
Authentication & CAPTCHAs
- HttpOnly cookies (session tokens) CANNOT be set via
document.cookieorpinchtab eval. They can only be set by actual browser login flows. - CAPTCHAs (Cloudflare Turnstile, reCAPTCHA) cannot be solved by headless browsers. At the first sign of a CAPTCHA, immediately start headed mode and ask the user to solve it.
- After user logs in via headed mode, cookies persist in the profile. Do NOT restart the instance — that may lose the session.
- For subsequent runs, start headless from the same profile to reuse persisted cookies.
Rate Limiting
PinchTab has no built-in rate limiting for target sites. When making multiple API calls:
- Add
await new Promise(r => setTimeout(r, 2000))between fetch calls inpinchtab evalscripts - For bulk operations, use PinchTab's scheduler with
maxInflightto control concurrency - Sites like LeetCode trigger bot detection with rapid automated requests
CLI Quick Reference
Server & Config
pinchtab health # Check server health
pinchtab config # Show config
pinchtab config set <path> <val> # Set config value
pinchtab instances # List running instances
pinchtab profiles # List profiles
Instance Management
# Start instance (prefer REST API for full control)
curl -s -X POST http://localhost:9867/instances/start \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"profileId":"prof_xxx","mode":"headed"}'
# Stop instance
curl -s -X POST http://localhost:9867/instances/<id>/stop \
-H "Authorization: Bearer $TOKEN"
# Get auth token (from the config file PINCHTAB_CONFIG selects; default below)
jq -r .server.token "${PINCHTAB_CONFIG:-$HOME/.pinchtab/config.json}"
Shorthand Commands (default instance only)
pinchtab nav <url> # Navigate
pinchtab snap # Accessibility snapshot
pinchtab snap --interactive # Interactive elements only
pinchtab snap --compact # Token-efficient format
pinchtab click <ref> # Click element (e.g. "e5")
pinchtab click "css:#btn" # Click by CSS selector
pinchtab click "find:login button" # Click by semantic search
pinchtab fill <ref> <text> # Fill input field
pinchtab type <ref> <text> # Type into element
pinchtab press <key> # Press key (Enter, Tab, Escape)
pinchtab text # Extract page text
pinchtab screenshot # Take screenshot
pinchtab tab # List tabs
pinchtab eval '<js>' # Execute JavaScript
Instance-Scoped Commands (target specific instance)
pinchtab instance navigate <instanceId> <url>
pinchtab instance logs <instanceId>
pinchtab instance stop <instanceId>
REST API Reference
Base URL: http://localhost:9867
Auth: Authorization: Bearer <token>
Profiles
| Method | Endpoint | Description |
|---|---|---|
| GET | /profiles |
List profiles |
| POST | /profiles |
Create profile |
| DELETE | /profiles/{id} |
Delete profile |
| POST | /profiles/{nameOrId}/start |
Start instance from profile |
| POST | /profiles/{nameOrId}/stop |
Stop profile's instance |
Instances
| Method | Endpoint | Description |
|---|---|---|
| GET | /instances |
List running instances |
| POST | /instances/start |
Start new instance |
| POST | /instances/{id}/stop |
Stop instance |
| POST | /instances/{id}/tabs/open |
Open tab in specific instance |
| GET | /instances/{id}/tabs |
List tabs in specific instance |
Tabs (cross-instance, by tab ID)
| Method | Endpoint | Description |
|---|---|---|
| POST | /tabs/{tabId}/navigate |
Navigate tab |
| GET | /tabs/{tabId}/snapshot |
Get accessibility snapshot |
| GET | /tabs/{tabId}/text |
Extract page text |
| GET | /tabs/{tabId}/cookies |
Get cookies (read-only) |
| POST | /tabs/{tabId}/action |
Execute action (click, type, etc.) |
| GET | /tabs/{tabId}/screenshot |
Capture screenshot |
| POST | /tabs/{tabId}/close |
Close tab |
Scheduler (if enabled)
| Method | Endpoint | Description |
|---|---|---|
| POST | /tasks |
Submit task |
| GET | /tasks |
List tasks |
| POST | /tasks/{id}/cancel |
Cancel task |
Config Reference
Config location: the file PINCHTAB_CONFIG points at — default ~/.pinchtab/config.json, but the daemon (and this machine's fish config) pin it to ~/Library/Application Support/pinchtab/config.json. See the Authentication section above.
Key settings:
{
"server": { "token": "..." },
"instanceDefaults": {
"mode": "headed", // or "headless"
"stealthLevel": "full" // "light", "medium", "full"
},
"multiInstance": {
"strategy": "explicit", // "simple", "explicit", "simple-autorestart"
"allocationPolicy": "fcfs" // "fcfs", "round_robin", "random"
},
"scheduler": {
"enabled": true,
"maxInflight": 5,
"maxPerAgentInflight": 2
}
}
Stealth Levels
- light: Minimal anti-detection
- medium: Enhanced measures
- full: Maximum anti-detection (recommended for sites with bot detection)
Common Patterns
Login to a site with CAPTCHA
# 1. Start headed instance on a persistent profile
TOKEN=$(jq -r .server.token "$HOME/Library/Application Support/pinchtab/config.json")
curl -s -X POST http://localhost:9867/instances/start \
-H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-d '{"profileId":"prof_xxx","mode":"headed"}'
# 2. Navigate to login page
pinchtab instance navigate <instanceId> https://example.com/login
# 3. Fill credentials
pinchtab fill "css:#username" "myuser"
pinchtab fill "css:#password" "mypass"
# 4. Ask user to solve CAPTCHA in the headed window
# 5. After login, cookies persist in profile for future headless use
Bulk operations with rate limiting
// In pinchtab eval - add delays between API calls
(async () => {
for (const item of items) {
await fetch(url, { method: "POST", body: JSON.stringify(item) });
await new Promise((r) => setTimeout(r, 2000)); // 2s delay
}
window.__result = "done";
})();
Read async eval results
# pinchtab eval returns {} for async results
# Store result in window.__result, then read it after a delay
pinchtab eval '(async () => { window.__r = await fetch(...).then(r => r.text()); })()'
sleep 2
pinchtab eval 'window.__r'