| 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.token inside the config file selected by the PINCHTAB_CONFIG env var. If PINCHTAB_CONFIG is unset, the CLI defaults to ~/.pinchtab/config.json; the launchd daemon pins it to ~/Library/Application Support/pinchtab/config.json via its plist. These can be two different files — if their tokens drift you get 401 bad_token from the CLI while the daemon itself is fine. On this machine PINCHTAB_CONFIG is 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. Setting PINCHTAB_TOKEN in 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 config prints the exact file + token it's using. Compare .server.token across the candidate config files and curl each against the server:
pinchtab config
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 of PINCHTAB_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.cookie or pinchtab 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 in pinchtab eval scripts
- For bulk operations, use PinchTab's scheduler with
maxInflight to control concurrency
- Sites like LeetCode trigger bot detection with rapid automated requests
CLI Quick Reference
Server & Config
pinchtab health
pinchtab config
pinchtab config set <path> <val>
pinchtab instances
pinchtab profiles
Instance Management
curl -s -X POST http://localhost:9867/instances/start \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"profileId":"prof_xxx","mode":"headed"}'
curl -s -X POST http://localhost:9867/instances/<id>/stop \
-H "Authorization: Bearer $TOKEN"
jq -r .server.token "${PINCHTAB_CONFIG:-$HOME/.pinchtab/config.json}"
Shorthand Commands (default instance only)
pinchtab nav <url>
pinchtab snap
pinchtab snap --interactive
pinchtab snap --compact
pinchtab click <ref>
pinchtab click "css:#btn"
pinchtab click "find:login button"
pinchtab fill <ref> <text>
pinchtab type <ref> <text>
pinchtab press <key>
pinchtab text
pinchtab screenshot
pinchtab tab
pinchtab eval '<js>'
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",
"stealthLevel": "full"
},
"multiInstance": {
"strategy": "explicit",
"allocationPolicy": "fcfs"
},
"scheduler": {
"enabled": true,
"maxInflight": 5,
"maxPerAgentInflight": 2
}
}
server.port must be a JSON string, not a number. pinchtab's Go config parser (0.13.2) types server.port as string; a numeric value fails with cannot unmarshal number into ServerConfig.server.port of type string, the server never binds, and (in the homelab) the pod crashloops on its /health startup probe. In the cdk8s deploy (packages/homelab/src/cdk8s/src/resources/pinchtab/index.ts) emit it as String(PINCHTAB_PORT). A Greptile P2 once "fixed" this to a number (commit a831c840f) and broke the deployment — do not re-apply.
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
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"}'
pinchtab instance navigate <instanceId> https://example.com/login
pinchtab fill "css:#username" "myuser"
pinchtab fill "css:#password" "mypass"
Bulk operations with rate limiting
(async () => {
for (const item of items) {
await fetch(url, { method: "POST", body: JSON.stringify(item) });
await new Promise((r) => setTimeout(r, 2000));
}
window.__result = "done";
})();
Read async eval results
pinchtab eval '(async () => { window.__r = await fetch(...).then(r => r.text()); })()'
sleep 2
pinchtab eval 'window.__r'