mercury-agent 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +452 -451
- package/container/Dockerfile +127 -127
- package/container/Dockerfile.base +109 -109
- package/container/Dockerfile.power +17 -17
- package/container/agent-package.json +8 -8
- package/container/build.sh +54 -54
- package/docs/ROADMAP.md +3 -4
- package/docs/archive/summarization/2026-07-02-recent-dev-summary.md +71 -0
- package/docs/auth/dashboard.md +28 -28
- package/docs/auth/overview.md +109 -109
- package/docs/auth/whatsapp.md +173 -173
- package/docs/authoring-profiles.md +174 -174
- package/docs/configuration.md +54 -54
- package/docs/container-lifecycle.md +349 -349
- package/docs/{bugs/bwrap-privileged-linux-docker.md → debug/major/2026-07-02-bwrap-privileged-linux-docker.md} +21 -1
- package/docs/{bugs/e2big-prompt-delivery-fix.md → debug/major/2026-07-02-e2big-prompt-delivery-fix.md} +27 -1
- package/docs/{bugs/registry-pull-no-local-fallback.md → debug/major/2026-07-02-registry-pull-no-local-fallback.md} +27 -1
- package/docs/debug/summarization/2026-07-02-common-bug-patterns.md +72 -0
- package/docs/deployment.md +199 -199
- package/docs/extensions.md +375 -375
- package/docs/graceful-shutdown.md +62 -62
- package/docs/kb-distillation.md +77 -77
- package/docs/media/overview.md +140 -140
- package/docs/media/whatsapp.md +171 -171
- package/docs/memory.md +137 -137
- package/docs/permissions.md +217 -217
- package/docs/pipeline.md +228 -228
- package/docs/prd-chat-memory.md +76 -76
- package/docs/prd-config-load.md +82 -82
- package/docs/rate-limiting.md +229 -229
- package/docs/runbooks/publish-checklist.md +9 -1
- package/docs/scheduler.md +288 -288
- package/docs/setup-discord.md +100 -100
- package/docs/setup-slack.md +119 -119
- package/docs/setup-whatsapp.md +94 -94
- package/docs/subagents.md +166 -166
- package/docs/web-search.md +62 -62
- package/examples/extensions/README.md +12 -12
- package/examples/extensions/charts/index.ts +13 -13
- package/examples/extensions/charts/skill/SKILL.md +98 -98
- package/examples/extensions/gws/README.md +52 -52
- package/examples/extensions/gws/skill/SKILL.md +57 -57
- package/examples/extensions/gws/skill/references/calendar.md +101 -101
- package/examples/extensions/gws/skill/references/docs.md +65 -65
- package/examples/extensions/gws/skill/references/drive.md +79 -79
- package/examples/extensions/gws/skill/references/gmail.md +85 -85
- package/examples/extensions/gws/skill/references/sheets.md +60 -60
- package/examples/extensions/napkin/skill/SKILL.md +728 -728
- package/examples/extensions/pdf/skill/LICENSE.txt +30 -30
- package/examples/extensions/pdf/skill/SKILL.md +314 -314
- package/examples/extensions/pdf/skill/forms.md +294 -294
- package/examples/extensions/pdf/skill/reference.md +611 -611
- package/examples/extensions/pdf/skill/scripts/check_bounding_boxes.py +65 -65
- package/examples/extensions/pdf/skill/scripts/check_fillable_fields.py +11 -11
- package/examples/extensions/pdf/skill/scripts/convert_pdf_to_images.py +33 -33
- package/examples/extensions/pdf/skill/scripts/create_validation_image.py +37 -37
- package/examples/extensions/pdf/skill/scripts/extract_form_field_info.py +122 -122
- package/examples/extensions/pdf/skill/scripts/extract_form_structure.py +115 -115
- package/examples/extensions/pdf/skill/scripts/fill_fillable_fields.py +98 -98
- package/examples/extensions/pdf/skill/scripts/fill_pdf_form_with_annotations.py +107 -107
- package/examples/extensions/permission-guard/index.ts +65 -65
- package/examples/extensions/pinchtab/skill/SKILL.md +224 -224
- package/examples/extensions/pinchtab/skill/TRUST.md +69 -69
- package/examples/extensions/pinchtab/skill/references/api.md +297 -297
- package/examples/extensions/pinchtab/skill/references/env.md +45 -45
- package/examples/extensions/pinchtab/skill/references/profiles.md +107 -107
- package/examples/extensions/tradestation/host/refresh.ts +102 -102
- package/examples/extensions/tradestation/index.ts +153 -153
- package/examples/extensions/tradestation/skill/SKILL.md +67 -67
- package/examples/extensions/voice-synth/index.ts +94 -94
- package/examples/extensions/voice-synth/skill/SKILL.md +38 -38
- package/examples/extensions/voice-transcribe/requirements.txt +8 -8
- package/examples/extensions/voice-transcribe/scripts/transcribe.py +179 -179
- package/examples/extensions/voice-transcribe/skill/SKILL.md +53 -53
- package/examples/extensions/yahoo-mail/cli/package.json +13 -13
- package/examples/extensions/yahoo-mail/skill/SKILL.md +78 -78
- package/package.json +106 -106
- package/resources/agents/explore.md +50 -50
- package/resources/agents/worker.md +24 -24
- package/resources/connection-env-vars.json +25 -25
- package/resources/pi-extensions/subagent/agents.ts +126 -126
- package/resources/pi-extensions/subagent/index.ts +964 -964
- package/resources/profiles/coding/AGENTS.md +43 -43
- package/resources/profiles/coding/mercury-profile.yaml +15 -15
- package/resources/profiles/general/AGENTS.md +31 -31
- package/resources/profiles/general/mercury-profile.yaml +15 -15
- package/resources/profiles/research/AGENTS.md +40 -40
- package/resources/profiles/research/mercury-profile.yaml +15 -15
- package/resources/skills/config/SKILL.md +25 -25
- package/resources/skills/context/SKILL.md +33 -33
- package/resources/skills/conversation-recap/SKILL.md +19 -19
- package/resources/skills/mutes/SKILL.md +31 -31
- package/resources/skills/permissions/SKILL.md +19 -19
- package/resources/skills/preferences/SKILL.md +31 -31
- package/resources/skills/recall/SKILL.md +24 -24
- package/resources/skills/roles/SKILL.md +18 -18
- package/resources/skills/spaces/SKILL.md +18 -18
- package/resources/skills/tasks/SKILL.md +45 -45
- package/resources/templates/AGENTS.md +157 -157
- package/resources/templates/env.template +38 -38
- package/resources/templates/mercury.example.yaml +99 -99
- package/src/agent/container-entry.ts +1 -1
- package/src/agent/container-runner.ts +1345 -1346
- package/src/cli/mercury.ts +39 -7
- package/src/cli/mrctl.ts +636 -636
- package/src/config-file.ts +540 -540
- package/src/config.ts +339 -339
- package/src/core/api.ts +125 -125
- package/src/core/caller-token.ts +101 -101
- package/src/core/permissions.ts +228 -228
- package/src/core/profiles.ts +271 -271
- package/src/core/routes/capability.ts +70 -70
- package/src/core/routes/index.ts +15 -15
- package/src/core/runtime.ts +1530 -1530
- package/src/dashboard/index.html +729 -729
- package/src/extensions/api.ts +273 -273
- package/src/extensions/loader.ts +286 -286
- package/src/extensions/types.ts +517 -517
- package/src/main.ts +605 -605
- package/docs/pending-updates/applicative-profiles.md +0 -15
- package/docs/pending-updates/caller-bound-capability-token.md +0 -15
- package/docs/pending-updates/dm-auto-space.md +0 -15
- /package/docs/archive/{2026-07-01-applicative-profiles.md → applicative-profiles/2026-07-01-applicative-profiles.md} +0 -0
- /package/docs/archive/{2026-07-01-caller-bound-capability-token.md → applicative-profiles/2026-07-01-caller-bound-capability-token.md} +0 -0
- /package/docs/archive/{2026-07-01-dm-auto-space.md → applicative-profiles/2026-07-01-dm-auto-space.md} +0 -0
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
# Pinchtab Environment Variables
|
|
2
|
-
|
|
3
|
-
## Core runtime
|
|
4
|
-
|
|
5
|
-
| Var | Default | Description |
|
|
6
|
-
|---|---|---|
|
|
7
|
-
| `BRIDGE_BIND` | `127.0.0.1` | Bind address. Set `0.0.0.0` for network access |
|
|
8
|
-
| `BRIDGE_PORT` | `9867` | HTTP port |
|
|
9
|
-
| `BRIDGE_HEADLESS` | `true` | Run Chrome headless |
|
|
10
|
-
| `BRIDGE_TOKEN` | (none) | Bearer auth token (recommended with `0.0.0.0`) |
|
|
11
|
-
| `BRIDGE_PROFILE` | `~/.pinchtab/chrome-profile` | Chrome profile dir |
|
|
12
|
-
| `BRIDGE_STATE_DIR` | `~/.pinchtab` | State/session storage |
|
|
13
|
-
| `BRIDGE_NO_RESTORE` | `false` | Skip tab restore on startup |
|
|
14
|
-
| `BRIDGE_STEALTH` | `light` | Stealth level: `light` or `full` |
|
|
15
|
-
| `BRIDGE_MAX_TABS` | `20` | Max open tabs (0 = unlimited) |
|
|
16
|
-
| `BRIDGE_BLOCK_IMAGES` | `false` | Block image loading |
|
|
17
|
-
| `BRIDGE_BLOCK_MEDIA` | `false` | Block all media (images + fonts + CSS + video) |
|
|
18
|
-
| `BRIDGE_NO_ANIMATIONS` | `false` | Disable CSS animations/transitions |
|
|
19
|
-
| `BRIDGE_TIMEZONE` | (none) | Force browser timezone (IANA tz) |
|
|
20
|
-
| `BRIDGE_CHROME_VERSION` | `144.0.7559.133` | Chrome version for fingerprint rotation |
|
|
21
|
-
| `BRIDGE_USER_AGENT` | (none) | Custom User-Agent string; also overrides Sec-Ch-Ua client hints via CDP |
|
|
22
|
-
| `CHROME_BINARY` | (auto) | Path to Chrome/Chromium binary |
|
|
23
|
-
| `CHROME_FLAGS` | (none) | Extra Chrome flags (space-separated) |
|
|
24
|
-
| `BRIDGE_CONFIG` | `~/.pinchtab/config.json` | Path to config JSON file |
|
|
25
|
-
| `BRIDGE_TIMEOUT` | `15` | Action timeout (seconds) |
|
|
26
|
-
| `BRIDGE_NAV_TIMEOUT` | `30` | Navigation timeout (seconds) |
|
|
27
|
-
| `CDP_URL` | (none) | Connect to existing Chrome DevTools |
|
|
28
|
-
| `BRIDGE_NO_DASHBOARD` | `false` | Disable dashboard endpoints on instance processes |
|
|
29
|
-
|
|
30
|
-
## CLI client
|
|
31
|
-
|
|
32
|
-
| Var | Default | Description |
|
|
33
|
-
|---|---|---|
|
|
34
|
-
| `PINCHTAB_URL` | `http://localhost:9867` | Pinchtab server URL for CLI commands |
|
|
35
|
-
| `PINCHTAB_TOKEN` | (none) | Auth token for CLI (sent as `Authorization: Bearer`) |
|
|
36
|
-
|
|
37
|
-
## Dashboard (`pinchtab`)
|
|
38
|
-
|
|
39
|
-
| Var | Default | Description |
|
|
40
|
-
|---|---|---|
|
|
41
|
-
| `PINCHTAB_AUTO_LAUNCH` | `false` | Auto-launch default profile at startup |
|
|
42
|
-
| `PINCHTAB_DEFAULT_PROFILE` | `default` | Profile name for auto-launch |
|
|
43
|
-
| `PINCHTAB_DEFAULT_PORT` | `9867` | Port for auto-launched profile |
|
|
44
|
-
| `PINCHTAB_HEADED` | (unset) | If set, auto-launched profile is headed |
|
|
45
|
-
| `PINCHTAB_DASHBOARD_URL` | `http://localhost:$BRIDGE_PORT` | Base URL for `pinchtab connect` |
|
|
1
|
+
# Pinchtab Environment Variables
|
|
2
|
+
|
|
3
|
+
## Core runtime
|
|
4
|
+
|
|
5
|
+
| Var | Default | Description |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| `BRIDGE_BIND` | `127.0.0.1` | Bind address. Set `0.0.0.0` for network access |
|
|
8
|
+
| `BRIDGE_PORT` | `9867` | HTTP port |
|
|
9
|
+
| `BRIDGE_HEADLESS` | `true` | Run Chrome headless |
|
|
10
|
+
| `BRIDGE_TOKEN` | (none) | Bearer auth token (recommended with `0.0.0.0`) |
|
|
11
|
+
| `BRIDGE_PROFILE` | `~/.pinchtab/chrome-profile` | Chrome profile dir |
|
|
12
|
+
| `BRIDGE_STATE_DIR` | `~/.pinchtab` | State/session storage |
|
|
13
|
+
| `BRIDGE_NO_RESTORE` | `false` | Skip tab restore on startup |
|
|
14
|
+
| `BRIDGE_STEALTH` | `light` | Stealth level: `light` or `full` |
|
|
15
|
+
| `BRIDGE_MAX_TABS` | `20` | Max open tabs (0 = unlimited) |
|
|
16
|
+
| `BRIDGE_BLOCK_IMAGES` | `false` | Block image loading |
|
|
17
|
+
| `BRIDGE_BLOCK_MEDIA` | `false` | Block all media (images + fonts + CSS + video) |
|
|
18
|
+
| `BRIDGE_NO_ANIMATIONS` | `false` | Disable CSS animations/transitions |
|
|
19
|
+
| `BRIDGE_TIMEZONE` | (none) | Force browser timezone (IANA tz) |
|
|
20
|
+
| `BRIDGE_CHROME_VERSION` | `144.0.7559.133` | Chrome version for fingerprint rotation |
|
|
21
|
+
| `BRIDGE_USER_AGENT` | (none) | Custom User-Agent string; also overrides Sec-Ch-Ua client hints via CDP |
|
|
22
|
+
| `CHROME_BINARY` | (auto) | Path to Chrome/Chromium binary |
|
|
23
|
+
| `CHROME_FLAGS` | (none) | Extra Chrome flags (space-separated) |
|
|
24
|
+
| `BRIDGE_CONFIG` | `~/.pinchtab/config.json` | Path to config JSON file |
|
|
25
|
+
| `BRIDGE_TIMEOUT` | `15` | Action timeout (seconds) |
|
|
26
|
+
| `BRIDGE_NAV_TIMEOUT` | `30` | Navigation timeout (seconds) |
|
|
27
|
+
| `CDP_URL` | (none) | Connect to existing Chrome DevTools |
|
|
28
|
+
| `BRIDGE_NO_DASHBOARD` | `false` | Disable dashboard endpoints on instance processes |
|
|
29
|
+
|
|
30
|
+
## CLI client
|
|
31
|
+
|
|
32
|
+
| Var | Default | Description |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| `PINCHTAB_URL` | `http://localhost:9867` | Pinchtab server URL for CLI commands |
|
|
35
|
+
| `PINCHTAB_TOKEN` | (none) | Auth token for CLI (sent as `Authorization: Bearer`) |
|
|
36
|
+
|
|
37
|
+
## Dashboard (`pinchtab`)
|
|
38
|
+
|
|
39
|
+
| Var | Default | Description |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| `PINCHTAB_AUTO_LAUNCH` | `false` | Auto-launch default profile at startup |
|
|
42
|
+
| `PINCHTAB_DEFAULT_PROFILE` | `default` | Profile name for auto-launch |
|
|
43
|
+
| `PINCHTAB_DEFAULT_PORT` | `9867` | Port for auto-launched profile |
|
|
44
|
+
| `PINCHTAB_HEADED` | (unset) | If set, auto-launched profile is headed |
|
|
45
|
+
| `PINCHTAB_DASHBOARD_URL` | `http://localhost:$BRIDGE_PORT` | Base URL for `pinchtab connect` |
|
|
@@ -1,107 +1,107 @@
|
|
|
1
|
-
# Profile Management
|
|
2
|
-
|
|
3
|
-
When running `pinchtab`, profiles are managed via the dashboard API on port 9867.
|
|
4
|
-
|
|
5
|
-
## List profiles
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
curl http://localhost:9867/profiles
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Returns array of profiles with `id`, `name`, `accountEmail`, `useWhen`, etc.
|
|
12
|
-
|
|
13
|
-
## Start a profile
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
# Auto-allocate port (recommended)
|
|
17
|
-
curl -X POST http://localhost:9867/profiles/<ID>/start
|
|
18
|
-
|
|
19
|
-
# With specific port and headless mode
|
|
20
|
-
curl -X POST http://localhost:9867/profiles/<ID>/start \
|
|
21
|
-
-H 'Content-Type: application/json' \
|
|
22
|
-
-d '{"port": "9868", "headless": true}'
|
|
23
|
-
|
|
24
|
-
# Short alias
|
|
25
|
-
curl -X POST http://localhost:9867/start/<ID>
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
Returns instance info including allocated `port`. Use that port for all subsequent API calls.
|
|
29
|
-
|
|
30
|
-
## Stop a profile
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
curl -X POST http://localhost:9867/profiles/<ID>/stop
|
|
34
|
-
|
|
35
|
-
# Short alias
|
|
36
|
-
curl -X POST http://localhost:9867/stop/<ID>
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
## Check instance status
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
# By profile ID (recommended)
|
|
43
|
-
curl http://localhost:9867/profiles/<ID>/instance
|
|
44
|
-
|
|
45
|
-
# By profile name
|
|
46
|
-
curl http://localhost:9867/profiles/My%20Profile/instance
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
## Launch by name
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
curl -X POST http://localhost:9867/instances/launch \
|
|
53
|
-
-H 'Content-Type: application/json' \
|
|
54
|
-
-d '{"name": "work", "port": "9868"}'
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
## CLI usage with profiles
|
|
58
|
-
|
|
59
|
-
The CLI doesn't have profile subcommands yet — use `curl` for profile management.
|
|
60
|
-
Once a profile instance is running, point the CLI at it:
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
# Get the instance port, then use CLI
|
|
64
|
-
PINCHTAB_URL=http://localhost:9868 pinchtab snap -i
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
## Typical agent flow
|
|
68
|
-
|
|
69
|
-
```bash
|
|
70
|
-
# 1. List profiles
|
|
71
|
-
PROFILES=$(curl -s http://localhost:9867/profiles)
|
|
72
|
-
|
|
73
|
-
# 2. Start profile (auto-allocates port)
|
|
74
|
-
INSTANCE=$(curl -s -X POST http://localhost:9867/profiles/$PROFILE_ID/start)
|
|
75
|
-
PORT=$(echo $INSTANCE | jq -r .port)
|
|
76
|
-
|
|
77
|
-
# 3. Use the instance
|
|
78
|
-
curl -X POST http://localhost:$PORT/navigate -H 'Content-Type: application/json' \
|
|
79
|
-
-d '{"url": "https://mail.google.com"}'
|
|
80
|
-
curl http://localhost:$PORT/snapshot?maxTokens=4000
|
|
81
|
-
|
|
82
|
-
# 4. Stop when done
|
|
83
|
-
curl -s -X POST http://localhost:9867/profiles/$PROFILE_ID/stop
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
## Profile IDs
|
|
87
|
-
|
|
88
|
-
Each profile gets a stable 12-char hex ID (SHA-256 of name, truncated) stored in `profile.json`. IDs are URL-safe and never change — use them instead of names in automation.
|
|
89
|
-
|
|
90
|
-
## Headed mode
|
|
91
|
-
|
|
92
|
-
Headed mode = real visible Chrome window managed by Pinchtab.
|
|
93
|
-
|
|
94
|
-
- Human can log in, pass 2FA/captcha, validate state
|
|
95
|
-
- Agent calls HTTP APIs against the same running instance
|
|
96
|
-
- Session state persists in profile directory (cookies/storage carry over)
|
|
97
|
-
|
|
98
|
-
Recommended human + agent flow:
|
|
99
|
-
|
|
100
|
-
```bash
|
|
101
|
-
# Human starts dashboard and sets up profile
|
|
102
|
-
pinchtab
|
|
103
|
-
|
|
104
|
-
# Agent resolves the profile endpoint
|
|
105
|
-
PINCHTAB_BASE_URL="$(pinchtab connect <profile-name>)"
|
|
106
|
-
curl "$PINCHTAB_BASE_URL/health"
|
|
107
|
-
```
|
|
1
|
+
# Profile Management
|
|
2
|
+
|
|
3
|
+
When running `pinchtab`, profiles are managed via the dashboard API on port 9867.
|
|
4
|
+
|
|
5
|
+
## List profiles
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
curl http://localhost:9867/profiles
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Returns array of profiles with `id`, `name`, `accountEmail`, `useWhen`, etc.
|
|
12
|
+
|
|
13
|
+
## Start a profile
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Auto-allocate port (recommended)
|
|
17
|
+
curl -X POST http://localhost:9867/profiles/<ID>/start
|
|
18
|
+
|
|
19
|
+
# With specific port and headless mode
|
|
20
|
+
curl -X POST http://localhost:9867/profiles/<ID>/start \
|
|
21
|
+
-H 'Content-Type: application/json' \
|
|
22
|
+
-d '{"port": "9868", "headless": true}'
|
|
23
|
+
|
|
24
|
+
# Short alias
|
|
25
|
+
curl -X POST http://localhost:9867/start/<ID>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Returns instance info including allocated `port`. Use that port for all subsequent API calls.
|
|
29
|
+
|
|
30
|
+
## Stop a profile
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
curl -X POST http://localhost:9867/profiles/<ID>/stop
|
|
34
|
+
|
|
35
|
+
# Short alias
|
|
36
|
+
curl -X POST http://localhost:9867/stop/<ID>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Check instance status
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# By profile ID (recommended)
|
|
43
|
+
curl http://localhost:9867/profiles/<ID>/instance
|
|
44
|
+
|
|
45
|
+
# By profile name
|
|
46
|
+
curl http://localhost:9867/profiles/My%20Profile/instance
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Launch by name
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
curl -X POST http://localhost:9867/instances/launch \
|
|
53
|
+
-H 'Content-Type: application/json' \
|
|
54
|
+
-d '{"name": "work", "port": "9868"}'
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## CLI usage with profiles
|
|
58
|
+
|
|
59
|
+
The CLI doesn't have profile subcommands yet — use `curl` for profile management.
|
|
60
|
+
Once a profile instance is running, point the CLI at it:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Get the instance port, then use CLI
|
|
64
|
+
PINCHTAB_URL=http://localhost:9868 pinchtab snap -i
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Typical agent flow
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# 1. List profiles
|
|
71
|
+
PROFILES=$(curl -s http://localhost:9867/profiles)
|
|
72
|
+
|
|
73
|
+
# 2. Start profile (auto-allocates port)
|
|
74
|
+
INSTANCE=$(curl -s -X POST http://localhost:9867/profiles/$PROFILE_ID/start)
|
|
75
|
+
PORT=$(echo $INSTANCE | jq -r .port)
|
|
76
|
+
|
|
77
|
+
# 3. Use the instance
|
|
78
|
+
curl -X POST http://localhost:$PORT/navigate -H 'Content-Type: application/json' \
|
|
79
|
+
-d '{"url": "https://mail.google.com"}'
|
|
80
|
+
curl http://localhost:$PORT/snapshot?maxTokens=4000
|
|
81
|
+
|
|
82
|
+
# 4. Stop when done
|
|
83
|
+
curl -s -X POST http://localhost:9867/profiles/$PROFILE_ID/stop
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Profile IDs
|
|
87
|
+
|
|
88
|
+
Each profile gets a stable 12-char hex ID (SHA-256 of name, truncated) stored in `profile.json`. IDs are URL-safe and never change — use them instead of names in automation.
|
|
89
|
+
|
|
90
|
+
## Headed mode
|
|
91
|
+
|
|
92
|
+
Headed mode = real visible Chrome window managed by Pinchtab.
|
|
93
|
+
|
|
94
|
+
- Human can log in, pass 2FA/captcha, validate state
|
|
95
|
+
- Agent calls HTTP APIs against the same running instance
|
|
96
|
+
- Session state persists in profile directory (cookies/storage carry over)
|
|
97
|
+
|
|
98
|
+
Recommended human + agent flow:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# Human starts dashboard and sets up profile
|
|
102
|
+
pinchtab
|
|
103
|
+
|
|
104
|
+
# Agent resolves the profile endpoint
|
|
105
|
+
PINCHTAB_BASE_URL="$(pinchtab connect <profile-name>)"
|
|
106
|
+
curl "$PINCHTAB_BASE_URL/health"
|
|
107
|
+
```
|
|
@@ -1,102 +1,102 @@
|
|
|
1
|
-
export const TRADESTATION_EXT = "tradestation" as const;
|
|
2
|
-
|
|
3
|
-
type Db = {
|
|
4
|
-
getExtState(ext: string, key: string): string | null;
|
|
5
|
-
setExtState(ext: string, key: string, value: string): void;
|
|
6
|
-
deleteExtState(ext: string, key: string): boolean;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
const TRADESTATION_TOKEN_URL = "https://signin.tradestation.com/oauth/token";
|
|
10
|
-
|
|
11
|
-
export async function runTradeStationTokenRefresh(
|
|
12
|
-
db: Db,
|
|
13
|
-
log: { info(msg: string): void; warn(msg: string): void; error(msg: string): void },
|
|
14
|
-
fetchImpl: typeof fetch = fetch,
|
|
15
|
-
): Promise<void> {
|
|
16
|
-
const clientId =
|
|
17
|
-
process.env.MERCURY_TS_CLIENT_ID?.trim() ||
|
|
18
|
-
process.env.TS_CLIENT_ID?.trim() ||
|
|
19
|
-
null;
|
|
20
|
-
if (!clientId) {
|
|
21
|
-
log.warn("[ts-refresh] no client_id — skipping");
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const clientSecret = process.env.MERCURY_TS_CLIENT_SECRET?.trim() || null;
|
|
26
|
-
|
|
27
|
-
let refreshToken =
|
|
28
|
-
db.getExtState(TRADESTATION_EXT, "refresh_token") ??
|
|
29
|
-
process.env.MERCURY_TRADESTATION_REFRESH_TOKEN?.trim() ??
|
|
30
|
-
process.env.TS_REFRESH_TOKEN?.trim() ??
|
|
31
|
-
null;
|
|
32
|
-
|
|
33
|
-
if (!refreshToken) {
|
|
34
|
-
log.warn("[ts-refresh] no refresh_token — skipping");
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const accessToken = db.getExtState(TRADESTATION_EXT, "access_token");
|
|
39
|
-
const expiryRaw = db.getExtState(TRADESTATION_EXT, "token_expiry_ms");
|
|
40
|
-
if (accessToken && expiryRaw) {
|
|
41
|
-
const expiry = Number(expiryRaw);
|
|
42
|
-
if (expiry > Date.now() + 60_000) {
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const bodyParams: Record<string, string> = {
|
|
48
|
-
grant_type: "refresh_token",
|
|
49
|
-
client_id: clientId,
|
|
50
|
-
refresh_token: refreshToken,
|
|
51
|
-
};
|
|
52
|
-
if (clientSecret) bodyParams.client_secret = clientSecret;
|
|
53
|
-
const body = new URLSearchParams(bodyParams);
|
|
54
|
-
|
|
55
|
-
let res: Response;
|
|
56
|
-
try {
|
|
57
|
-
res = await fetchImpl(TRADESTATION_TOKEN_URL, {
|
|
58
|
-
method: "POST",
|
|
59
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
60
|
-
body: body.toString(),
|
|
61
|
-
});
|
|
62
|
-
} catch (err) {
|
|
63
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
64
|
-
log.error(`[ts-refresh] fetch error: ${msg}`);
|
|
65
|
-
db.setExtState(TRADESTATION_EXT, "auth_error", "refresh_failed:network");
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (!res.ok) {
|
|
70
|
-
log.warn(`[ts-refresh] token refresh failed: ${res.status}`);
|
|
71
|
-
db.setExtState(TRADESTATION_EXT, "auth_error", `refresh_failed:${res.status}`);
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
let data: Record<string, unknown>;
|
|
76
|
-
try {
|
|
77
|
-
data = (await res.json()) as Record<string, unknown>;
|
|
78
|
-
} catch {
|
|
79
|
-
db.setExtState(TRADESTATION_EXT, "auth_error", "refresh_failed:invalid_json");
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const newAccess = typeof data.access_token === "string" ? data.access_token : null;
|
|
84
|
-
if (!newAccess) {
|
|
85
|
-
db.setExtState(TRADESTATION_EXT, "auth_error", "refresh_failed:no_access_token");
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
db.setExtState(TRADESTATION_EXT, "access_token", newAccess);
|
|
90
|
-
if (typeof data.refresh_token === "string" && data.refresh_token) {
|
|
91
|
-
db.setExtState(TRADESTATION_EXT, "refresh_token", data.refresh_token);
|
|
92
|
-
}
|
|
93
|
-
if (typeof data.expires_in === "number") {
|
|
94
|
-
db.setExtState(
|
|
95
|
-
TRADESTATION_EXT,
|
|
96
|
-
"token_expiry_ms",
|
|
97
|
-
String(Date.now() + data.expires_in * 1000),
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
db.deleteExtState(TRADESTATION_EXT, "auth_error");
|
|
101
|
-
log.info("[ts-refresh] tokens refreshed");
|
|
102
|
-
}
|
|
1
|
+
export const TRADESTATION_EXT = "tradestation" as const;
|
|
2
|
+
|
|
3
|
+
type Db = {
|
|
4
|
+
getExtState(ext: string, key: string): string | null;
|
|
5
|
+
setExtState(ext: string, key: string, value: string): void;
|
|
6
|
+
deleteExtState(ext: string, key: string): boolean;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const TRADESTATION_TOKEN_URL = "https://signin.tradestation.com/oauth/token";
|
|
10
|
+
|
|
11
|
+
export async function runTradeStationTokenRefresh(
|
|
12
|
+
db: Db,
|
|
13
|
+
log: { info(msg: string): void; warn(msg: string): void; error(msg: string): void },
|
|
14
|
+
fetchImpl: typeof fetch = fetch,
|
|
15
|
+
): Promise<void> {
|
|
16
|
+
const clientId =
|
|
17
|
+
process.env.MERCURY_TS_CLIENT_ID?.trim() ||
|
|
18
|
+
process.env.TS_CLIENT_ID?.trim() ||
|
|
19
|
+
null;
|
|
20
|
+
if (!clientId) {
|
|
21
|
+
log.warn("[ts-refresh] no client_id — skipping");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const clientSecret = process.env.MERCURY_TS_CLIENT_SECRET?.trim() || null;
|
|
26
|
+
|
|
27
|
+
let refreshToken =
|
|
28
|
+
db.getExtState(TRADESTATION_EXT, "refresh_token") ??
|
|
29
|
+
process.env.MERCURY_TRADESTATION_REFRESH_TOKEN?.trim() ??
|
|
30
|
+
process.env.TS_REFRESH_TOKEN?.trim() ??
|
|
31
|
+
null;
|
|
32
|
+
|
|
33
|
+
if (!refreshToken) {
|
|
34
|
+
log.warn("[ts-refresh] no refresh_token — skipping");
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const accessToken = db.getExtState(TRADESTATION_EXT, "access_token");
|
|
39
|
+
const expiryRaw = db.getExtState(TRADESTATION_EXT, "token_expiry_ms");
|
|
40
|
+
if (accessToken && expiryRaw) {
|
|
41
|
+
const expiry = Number(expiryRaw);
|
|
42
|
+
if (expiry > Date.now() + 60_000) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const bodyParams: Record<string, string> = {
|
|
48
|
+
grant_type: "refresh_token",
|
|
49
|
+
client_id: clientId,
|
|
50
|
+
refresh_token: refreshToken,
|
|
51
|
+
};
|
|
52
|
+
if (clientSecret) bodyParams.client_secret = clientSecret;
|
|
53
|
+
const body = new URLSearchParams(bodyParams);
|
|
54
|
+
|
|
55
|
+
let res: Response;
|
|
56
|
+
try {
|
|
57
|
+
res = await fetchImpl(TRADESTATION_TOKEN_URL, {
|
|
58
|
+
method: "POST",
|
|
59
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
60
|
+
body: body.toString(),
|
|
61
|
+
});
|
|
62
|
+
} catch (err) {
|
|
63
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
64
|
+
log.error(`[ts-refresh] fetch error: ${msg}`);
|
|
65
|
+
db.setExtState(TRADESTATION_EXT, "auth_error", "refresh_failed:network");
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!res.ok) {
|
|
70
|
+
log.warn(`[ts-refresh] token refresh failed: ${res.status}`);
|
|
71
|
+
db.setExtState(TRADESTATION_EXT, "auth_error", `refresh_failed:${res.status}`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let data: Record<string, unknown>;
|
|
76
|
+
try {
|
|
77
|
+
data = (await res.json()) as Record<string, unknown>;
|
|
78
|
+
} catch {
|
|
79
|
+
db.setExtState(TRADESTATION_EXT, "auth_error", "refresh_failed:invalid_json");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const newAccess = typeof data.access_token === "string" ? data.access_token : null;
|
|
84
|
+
if (!newAccess) {
|
|
85
|
+
db.setExtState(TRADESTATION_EXT, "auth_error", "refresh_failed:no_access_token");
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
db.setExtState(TRADESTATION_EXT, "access_token", newAccess);
|
|
90
|
+
if (typeof data.refresh_token === "string" && data.refresh_token) {
|
|
91
|
+
db.setExtState(TRADESTATION_EXT, "refresh_token", data.refresh_token);
|
|
92
|
+
}
|
|
93
|
+
if (typeof data.expires_in === "number") {
|
|
94
|
+
db.setExtState(
|
|
95
|
+
TRADESTATION_EXT,
|
|
96
|
+
"token_expiry_ms",
|
|
97
|
+
String(Date.now() + data.expires_in * 1000),
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
db.deleteExtState(TRADESTATION_EXT, "auth_error");
|
|
101
|
+
log.info("[ts-refresh] tokens refreshed");
|
|
102
|
+
}
|