cdpilot 0.9.3 → 0.9.5
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 +451 -22
- package/bin/cdpilot.js +160 -15
- package/package.json +3 -2
- package/src/cdpilot.py +6885 -511
package/README.md
CHANGED
|
@@ -28,14 +28,14 @@ No config files. No boilerplate. Just `npx` and go.
|
|
|
28
28
|
AI agents and developers need browser control that **just works**:
|
|
29
29
|
|
|
30
30
|
- **Zero config** — `npx cdpilot launch` starts an isolated browser session
|
|
31
|
-
- **Zero
|
|
31
|
+
- **Zero npm dependencies** — No Puppeteer, no Playwright, no Selenium. Pure CDP over HTTP/WebSocket; the Python side needs one package, `websockets` (the setup wizard installs it)
|
|
32
32
|
- **70+ commands** — Navigate, click, type, screenshot, network, console, accessibility, video understanding, progressive anti-bot resilience, and more
|
|
33
33
|
- **AI-agent friendly** — Designed for Claude, GPT, Gemini, and any LLM tool-use workflow
|
|
34
34
|
- **Isolated sessions** — Your personal browser stays untouched. cdpilot runs in its own profile
|
|
35
35
|
- **Visual feedback** — Green glow overlay, cursor visualization, click ripples, and keystroke display keep you informed during automation
|
|
36
36
|
- **Multi-project isolation** — Each project gets its own browser instance and port automatically, no conflicts
|
|
37
37
|
- **AI control warning** — Red toast notification appears when you hover during active automation
|
|
38
|
-
- **Privacy-first** — Everything runs locally
|
|
38
|
+
- **Privacy-first** — Everything runs locally; nothing leaves your machine unless you opt in (CAPTCHA solver keys, `tls-check`, proxies)
|
|
39
39
|
|
|
40
40
|
### Browser Selection (Workload-Aware Auto-Pick)
|
|
41
41
|
|
|
@@ -44,7 +44,7 @@ two-axis policy — extension workload × platform stability:
|
|
|
44
44
|
|
|
45
45
|
| Your workload | Auto-pick order |
|
|
46
46
|
|---|---|
|
|
47
|
-
| Has extensions registered (`ext-install`) | vivaldi → brave → edge → chromium → chrome |
|
|
47
|
+
| Has extensions registered (`ext-install`) | vivaldi → brave → edge → chromium → chrome (→ Chrome for Testing, if installed) |
|
|
48
48
|
| No extensions (pure automation) | chrome → vivaldi → edge → chromium → brave |
|
|
49
49
|
|
|
50
50
|
Override anytime:
|
|
@@ -52,13 +52,16 @@ Override anytime:
|
|
|
52
52
|
```bash
|
|
53
53
|
cdpilot browser # show current pick + reason
|
|
54
54
|
cdpilot browser vivaldi # pin to Vivaldi
|
|
55
|
+
cdpilot browser chrome-for-testing # pin to Chrome for Testing (after installing it, below)
|
|
55
56
|
cdpilot browser auto # restore smart default
|
|
56
57
|
```
|
|
57
58
|
|
|
58
59
|
**Why the split?**
|
|
59
|
-
- **Chrome
|
|
60
|
-
(no error, no warning)
|
|
61
|
-
-
|
|
60
|
+
- **Chrome 137+ silently drops `--load-extension`** for unpacked extensions
|
|
61
|
+
(no error, no warning) — Google removed it from branded Chrome builds
|
|
62
|
+
([PSA](https://groups.google.com/a/chromium.org/g/chromium-extensions/c/1-g8EFx2BBY/m/S0ET5wPjCAAJ)).
|
|
63
|
+
Verified — `chrome://extensions` shows 0 items.
|
|
64
|
+
- **Vivaldi, Brave, Edge, Chromium and Chrome for Testing** honor `--load-extension` (tested).
|
|
62
65
|
- On **macOS 26 (Tahoe)** Brave 1.89 crashes deterministically at ~7min
|
|
63
66
|
uptime (SIGTRAP in ThreadPoolForegroundWorker). cdpilot detects the OS
|
|
64
67
|
and demotes Brave automatically until a fixed Brave release ships.
|
|
@@ -66,6 +69,43 @@ cdpilot browser auto # restore smart default
|
|
|
66
69
|
Each browser gets its own isolated profile (`~/.cdpilot/.../profile-vivaldi`
|
|
67
70
|
etc.) so switching never causes prefs corruption.
|
|
68
71
|
|
|
72
|
+
### Extension development: Chrome for Testing
|
|
73
|
+
|
|
74
|
+
If you develop an extension against Chrome itself, install
|
|
75
|
+
[Chrome for Testing](https://developer.chrome.com/blog/chrome-for-testing) (CfT) —
|
|
76
|
+
the Chrome team's versioned, non-auto-updating Chrome build for automation, which
|
|
77
|
+
still loads unpacked extensions:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
cdpilot browser install chrome-for-testing # latest Stable
|
|
81
|
+
cdpilot browser install chrome-for-testing --channel beta # stable | beta | dev | canary
|
|
82
|
+
cdpilot browser install chrome-for-testing --version 154 # a milestone, or a full version (154.0.8037.57)
|
|
83
|
+
cdpilot ext-install ./my-extension/ # register your unpacked extension
|
|
84
|
+
cdpilot browser status # shows the CfT version and path
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
- **Download only on that command.** It is a one-time ~150-190 MB zip (mac-arm64 at
|
|
88
|
+
154.0.8037.57: 191,429,663 bytes); cdpilot never fetches it by itself. The build comes
|
|
89
|
+
from the official [CfT JSON endpoints](https://googlechromelabs.github.io/chrome-for-testing/)
|
|
90
|
+
(`last-known-good-versions-with-downloads.json`, per-version and per-milestone files).
|
|
91
|
+
Those list URLs only, no hash, so the download is checked against the server's
|
|
92
|
+
`Content-Length` and its `x-goog-hash` md5; the zip's SHA-256 is recorded.
|
|
93
|
+
- It is extracted to `$CDPILOT_HOME/browsers/chrome-for-testing/<version>/` (on macOS the
|
|
94
|
+
quarantine attribute is cleared on that directory only) and recorded in `installed.json`.
|
|
95
|
+
- **Used automatically for extension work:** while dev extensions are registered, a
|
|
96
|
+
branded Chrome pick (`cdpilot browser chrome`, or `auto` with Chrome the only
|
|
97
|
+
extension-capable choice) becomes Chrome for Testing, with one stderr line saying so.
|
|
98
|
+
Without CfT installed, `ext-install` on Chrome prints the install command and keeps
|
|
99
|
+
Vivaldi/Brave as the alternative. `cdpilot browser chrome-for-testing` selects it
|
|
100
|
+
always. An explicit `CHROME_BIN` is never replaced.
|
|
101
|
+
- It runs with cdpilot's usual per-project isolated profile (`profile-cft`).
|
|
102
|
+
- **It is for extension development and testing — not a stealth browser.** cdpilot
|
|
103
|
+
makes no anti-bot claims for it; use your regular browser setup for that.
|
|
104
|
+
- **Linux (Ubuntu 23.10+):** AppArmor blocks unprivileged user namespaces, and CfT
|
|
105
|
+
(unlike the distro's Chrome package) has no profile allowing them, so it exits at
|
|
106
|
+
start. cdpilot prints this hint; allow them with
|
|
107
|
+
`sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0`.
|
|
108
|
+
|
|
69
109
|
## Installation
|
|
70
110
|
|
|
71
111
|
```bash
|
|
@@ -86,6 +126,46 @@ npx cdpilot launch # Start browser with CDP enabled
|
|
|
86
126
|
npx cdpilot status # Check connection
|
|
87
127
|
```
|
|
88
128
|
|
|
129
|
+
### Claude Code plugin marketplace
|
|
130
|
+
|
|
131
|
+
Install cdpilot's MCP server and its skill straight into Claude Code, no
|
|
132
|
+
`.mcp.json` editing required:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
/plugin marketplace add mehmetnadir/cdpilot
|
|
136
|
+
/plugin install cdpilot@cdpilot
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
This registers the `cdpilot` MCP server (`npx cdpilot mcp`) and a short skill
|
|
140
|
+
that teaches Claude Code when and how to reach for cdpilot's commands. See
|
|
141
|
+
[`.claude-plugin/marketplace.json`](.claude-plugin/marketplace.json) and
|
|
142
|
+
[`plugins/cdpilot/`](plugins/cdpilot/).
|
|
143
|
+
|
|
144
|
+
### Smaller MCP tool list
|
|
145
|
+
|
|
146
|
+
`cdpilot mcp --tools core` (or `CDPILOT_MCP_TOOLS=core`; the flag wins) lists 10 core
|
|
147
|
+
tools (navigate, click, fill, content, screenshot, eval, wait_for, assert, tabs, connect)
|
|
148
|
+
plus `browser_tools`. The agent calls `browser_tools` with `list` to see the other
|
|
149
|
+
tools, or `enable` to add some (the server sends `tools/list_changed`). `--tools a,b,c`
|
|
150
|
+
picks tools by name; `all` (default) is unchanged. Schema size of `tools/list`
|
|
151
|
+
(tiktoken `o200k_base`): 45 tools = 5,769 tokens, `core` = 11 tools = 1,342 tokens.
|
|
152
|
+
|
|
153
|
+
### MCP Bundle (.mcpb)
|
|
154
|
+
|
|
155
|
+
For MCP clients that support one-click local server installation (Claude
|
|
156
|
+
Desktop and others), download the `.mcpb` file attached to the
|
|
157
|
+
[latest release](https://github.com/mehmetnadir/cdpilot/releases/latest) and
|
|
158
|
+
open it, or build it yourself:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
npm run build:mcpb # writes cdpilot.mcpb at the repo root
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The bundle still needs the same runtime on the machine that runs it: Node.js
|
|
165
|
+
18+, Python 3.10+, and Brave/Chrome/Chromium installed. See
|
|
166
|
+
[`manifest.json`](manifest.json) for the bundle's MCP Bundle manifest
|
|
167
|
+
([spec](https://github.com/modelcontextprotocol/mcpb)).
|
|
168
|
+
|
|
89
169
|
### Upgrading from 0.4.x → 0.5.0 — read this first
|
|
90
170
|
|
|
91
171
|
**One breaking change**, the rest is additive.
|
|
@@ -144,6 +224,8 @@ cdpilot scroll-to <selector> # Scroll element into view
|
|
|
144
224
|
cdpilot drag <from> <to> # Drag and drop
|
|
145
225
|
```
|
|
146
226
|
|
|
227
|
+
A real mouse click (inside a frame, or `--entropy=on`) whose target moves or gets covered while the button is held is not clicked again by script: `click` prints `Pressed (released elsewhere, not clicked): …` and ends with **exit 3** (pressed, release missed the target, not clicked; `1` stays "error"). A target the page replaced or navigated away from on mousedown prints `Pressed (the page replaced or left it, not clicked): …` and exits 0. MCP (`browser_click`, `browser_smart_click`) returns `isError: false` with a first line `{"clicked": false, "reason": "moved"|"gone"|"unknown"}`. `batch` and `run` finish every step, then exit 1 if a step failed, else 3 if a click was not clicked, else 0.
|
|
228
|
+
|
|
147
229
|
### Element targeting inside iframes
|
|
148
230
|
|
|
149
231
|
Card forms (Stripe/iyzico-style), embedded login widgets and the reCAPTCHA
|
|
@@ -175,11 +257,19 @@ cdpilot frame eval --frame "#card" "document.title" # runs in the frame's
|
|
|
175
257
|
inside it and says so on stderr:
|
|
176
258
|
`note: '#card-element' is not an iframe; using the iframe inside it`.
|
|
177
259
|
- Selectors and texts that already contain `>>>` keep working: when the part
|
|
178
|
-
before the first `>>>` finds no iframe in the page, or a segment is empty
|
|
260
|
+
before the first `>>>` finds no iframe or shadow host in the page, or a segment is empty
|
|
179
261
|
(`click "Next >>>"`), the whole string is used as written, exactly as
|
|
180
262
|
before. If that fails too, one stderr line explains:
|
|
181
|
-
`note: 'Home' matched no iframe; used the selector as written`.
|
|
263
|
+
`note: 'Home' matched no iframe or shadow host; used the selector as written`.
|
|
182
264
|
`--frame` never falls back: a frame it cannot find is an error (exit 1).
|
|
265
|
+
- **Shadow DOM support**: `>>>` hops also pierce open shadow roots (`host.shadowRoot.querySelector(next)`).
|
|
266
|
+
Chains mix freely with iframes: `cdpilot click "iframe#pay >>> my-card >>> button.inner"` or
|
|
267
|
+
`cdpilot click "my-app >>> my-panel >>> input"`. A closed shadow root is unreachable by design
|
|
268
|
+
and fails with `'X' has a closed shadow root; cdpilot cannot reach inside it`.
|
|
269
|
+
When a plain CSS selector matches nothing at document level, cdpilot automatically falls back
|
|
270
|
+
to a depth-first search across all open shadow roots, printing one stderr note:
|
|
271
|
+
`note: '<sel>' found inside the shadow root of <host tag#id>`. Explicit `>>>` hops do not print this note.
|
|
272
|
+
`wait` and `wait-for` poll shadow roots every 200 ms alongside the document MutationObserver.
|
|
183
273
|
- For `smart-click` / `smart-fill` / `smart-select` the part before the first
|
|
184
274
|
`>>>` is a frame only if it looks like a selector (it has `#`, `.` or `[`,
|
|
185
275
|
or starts with an `iframe`/`frame` tag) or is `url=…`; words stay text.
|
|
@@ -191,9 +281,17 @@ cdpilot frame eval --frame "#card" "document.title" # runs in the frame's
|
|
|
191
281
|
(`DOM.describeNode` → frame id → the frame's execution context, or
|
|
192
282
|
`Target.attachToTarget` with a flat session for an out-of-process frame),
|
|
193
283
|
never through `contentDocument`, which the browser blocks cross-origin.
|
|
194
|
-
-
|
|
195
|
-
|
|
196
|
-
|
|
284
|
+
- Inside a frame, `click` and `smart-click` are real mouse input
|
|
285
|
+
(`isTrusted: true`), like `hover`, `dblclick`, `rightclick` and
|
|
286
|
+
`--entropy=on` clicks; an element with no box falls back to a script click.
|
|
287
|
+
Mouse input is dispatched at page coordinates: each frame's offset and scale
|
|
288
|
+
(`transform: scale()`, `zoom`) in the page is applied automatically, so the
|
|
289
|
+
browser hit-tests into the right frame. Before pressing, cdpilot checks the
|
|
290
|
+
point (`elementFromPoint` in each document on the way); if something covers
|
|
291
|
+
the target, such as a cookie banner, it clicks the target by script instead
|
|
292
|
+
and says so on one stderr line (`note: … is covered by div#cookie at the
|
|
293
|
+
click point; used a script click`). cdpilot's own input blocker (`show on`,
|
|
294
|
+
MCP sessions) lets only cdpilot's click through, for the click itself.
|
|
197
295
|
- `smart-click` / `smart-fill` / `smart-select` look in the page first and
|
|
198
296
|
compare texts with whitespace collapsed (` ` and line breaks count as
|
|
199
297
|
one space). An enabled page element whose text or label contains the whole
|
|
@@ -217,14 +315,22 @@ cdpilot frame eval --frame "#card" "document.title" # runs in the frame's
|
|
|
217
315
|
### Debugging
|
|
218
316
|
|
|
219
317
|
```bash
|
|
220
|
-
cdpilot console [url]
|
|
221
|
-
cdpilot network [url]
|
|
222
|
-
cdpilot
|
|
223
|
-
cdpilot
|
|
224
|
-
cdpilot
|
|
225
|
-
cdpilot eval
|
|
318
|
+
cdpilot console [url] # Capture console logs
|
|
319
|
+
cdpilot network [url] # Monitor network requests
|
|
320
|
+
cdpilot network <url> --har trace.har # Export navigation traffic as HAR 1.2 JSON (--har - for stdout)
|
|
321
|
+
cdpilot debug [url] # Full diagnostic (console+network+perf+shot)
|
|
322
|
+
cdpilot perf # Performance metrics
|
|
323
|
+
cdpilot eval <js> # Execute JavaScript
|
|
324
|
+
cdpilot eval-batch <json> # Run N JS expressions in 1 roundtrip (5-30x faster)
|
|
226
325
|
```
|
|
227
326
|
|
|
327
|
+
#### Network monitoring & HAR export
|
|
328
|
+
|
|
329
|
+
`cdpilot network <url> --har <file>` exports all HTTP requests of a navigation as standard HAR 1.2 JSON (`--har -` writes to stdout).
|
|
330
|
+
- **Included:** URLs, methods, headers, status codes, query strings, and CDP timing breakdowns (DNS, connect, SSL, send, wait, receive).
|
|
331
|
+
- **Omitted:** Response bodies are not fetched or stored (avoids memory bloat and round-trip overhead); content.size reflects transfer length; bodySize is -1.
|
|
332
|
+
- **Masking:** Sensitive header values matching security patterns (`Cookie`, `Set-Cookie`, `Authorization`, `X-Api-Key`, token, secret) are replaced with `"«masked»"`. URLs are masked the same way as the session log: userinfo passwords and query/fragment parameters named like secrets (`token`, `key`, `code`, `session`, …) in `request.url`, `queryString`, `redirectURL` and URL-bearing header values (`Location`, `Referer`, `Link`); a `data:` URL keeps only its media type.
|
|
333
|
+
|
|
228
334
|
```bash
|
|
229
335
|
# Example: read 4 DOM values in a single CDP roundtrip instead of 4
|
|
230
336
|
cdpilot eval-batch '["document.title","location.href","document.links.length","document.images.length"]'
|
|
@@ -462,6 +568,8 @@ At launch, cdpilot also passes `--disable-blink-features=AutomationControlled`,
|
|
|
462
568
|
which closes the Blink runtime flag that Cloudflare and DataDome probe to detect
|
|
463
569
|
an automated browser.
|
|
464
570
|
|
|
571
|
+
Real mouse clicks (frames, `--entropy=on`, `click @ref`, `dblclick`, `rightclick`) hold the button 40-120 ms like a person's press; `CDPILOT_PRESS_MS=min-max` changes the range (`0-0` = instant).
|
|
572
|
+
|
|
465
573
|
#### Three-tier stealth mode
|
|
466
574
|
|
|
467
575
|
`cdpilot mode` is the recommended entry point — one switch that sets how much
|
|
@@ -505,6 +613,145 @@ cdpilot adaptive clear # Drop the stealth host memory entirely
|
|
|
505
613
|
> when it sees one — adds the host to a persistent list, retries once with
|
|
506
614
|
> stealth on. Never auto-demotes. Conservative by design.
|
|
507
615
|
|
|
616
|
+
### Web Bot Auth (signed agent)
|
|
617
|
+
|
|
618
|
+
The opposite of stealth. Stealth tries to make the browser look like a person;
|
|
619
|
+
Web Bot Auth says "this is an automated agent, and here is proof of which one".
|
|
620
|
+
Every request the browser makes carries an Ed25519
|
|
621
|
+
[HTTP Message Signature (RFC 9421)](https://www.rfc-editor.org/rfc/rfc9421)
|
|
622
|
+
that a site, or Cloudflare in front of it, checks against a key directory you
|
|
623
|
+
publish on your own domain. The format follows
|
|
624
|
+
[draft-meunier-web-bot-auth-architecture](https://datatracker.ietf.org/doc/draft-meunier-web-bot-auth-architecture/)
|
|
625
|
+
and [draft-meunier-http-message-signatures-directory](https://datatracker.ietf.org/doc/draft-meunier-http-message-signatures-directory/),
|
|
626
|
+
and reproduces the Ed25519 test vectors of Cloudflare's reference
|
|
627
|
+
implementation ([cloudflare/web-bot-auth](https://github.com/cloudflare/web-bot-auth)).
|
|
628
|
+
|
|
629
|
+
**Optional dependency.** Signing needs the `cryptography` package, for the Python cdpilot runs
|
|
630
|
+
on (`CDPILOT_PYTHON` if you set it):
|
|
631
|
+
|
|
632
|
+
```bash
|
|
633
|
+
pip install cryptography
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
Without it, the `bot-auth` commands and `launch --bot-auth` print that hint and exit 2. Every
|
|
637
|
+
other command works as before.
|
|
638
|
+
|
|
639
|
+
**1. Generate the key** (once):
|
|
640
|
+
|
|
641
|
+
```bash
|
|
642
|
+
cdpilot bot-auth init --agent-url https://your-domain.com
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
This writes an Ed25519 private key to `CDPILOT_HOME/bot-auth/ed25519.key` (default
|
|
646
|
+
`~/.cdpilot`), created with mode `0600`. cdpilot never prints or logs the key, and it warns
|
|
647
|
+
when the file is readable by others. The agent URL must be an `https://` origin (no path). A
|
|
648
|
+
second `init` refuses to replace the key unless you pass `--force`, because the new key would no
|
|
649
|
+
longer match the directory you published.
|
|
650
|
+
|
|
651
|
+
**2. Host the directory on your own domain, signed.** `cdpilot bot-auth directory` prints the
|
|
652
|
+
public key as a JWKS, where `kid` is the key's RFC 7638 JWK thumbprint. Cloudflare also requires
|
|
653
|
+
the directory *response* to be signed, so `--headers` prints the headers to serve with it:
|
|
654
|
+
|
|
655
|
+
```bash
|
|
656
|
+
cdpilot bot-auth directory --headers # headers, a blank line, then the JSON body
|
|
657
|
+
cdpilot bot-auth directory --headers --json # the same as {"authority", "headers", "body"}
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
```
|
|
661
|
+
Content-Type: application/http-message-signatures-directory+json
|
|
662
|
+
Signature-Input: sig1=("@authority";req);created=…;keyid="<thumbprint>";alg="ed25519";expires=…;nonce="…";tag="http-message-signatures-directory"
|
|
663
|
+
Signature: sig1=:<base64 Ed25519 signature>:
|
|
664
|
+
|
|
665
|
+
{ "keys": [ … ] }
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
Serve that body at `https://your-domain.com/.well-known/http-message-signatures-directory` with
|
|
669
|
+
those three headers. The signature covers `("@authority";req)`, the host the directory is fetched
|
|
670
|
+
from: by default the agent URL's host, or `--authority <host>`. There is one signature per key in
|
|
671
|
+
the directory. It is valid for 24 hours (`--ttl <seconds>`), so a static host needs it refreshed
|
|
672
|
+
daily, from cron or an edge function that runs the same signing. A cron example that rewrites the
|
|
673
|
+
headers file and the body your web server serves:
|
|
674
|
+
|
|
675
|
+
```bash
|
|
676
|
+
# m h dom mon dow — every day at 03:17, 48 h validity so one missed run does not break it
|
|
677
|
+
17 3 * * * cdpilot bot-auth directory --headers --ttl 172800 --json > /var/www/wba/directory.json.tmp \
|
|
678
|
+
&& mv /var/www/wba/directory.json.tmp /var/www/wba/directory.json
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
Your server (or an edge function) reads `headers` and `body` from that JSON and serves them at
|
|
682
|
+
the well-known path. `--content-digest` also covers the body (`Content-Digest`, RFC 9530), as
|
|
683
|
+
in Cloudflare's reference vector; then the body must be served byte for byte as printed.
|
|
684
|
+
|
|
685
|
+
**3. Launch a signing browser:**
|
|
686
|
+
|
|
687
|
+
```bash
|
|
688
|
+
cdpilot launch --bot-auth # or: CDPILOT_BOT_AUTH=1 cdpilot launch
|
|
689
|
+
cdpilot status # ... bot-auth: on (keyid <thumbprint>)
|
|
690
|
+
cdpilot go https://example.com # this request is signed, and so is everything after it
|
|
691
|
+
cdpilot stop # stops the browser and the signer
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
`launch --bot-auth` starts a small detached signer next to the browser. It uses the same
|
|
695
|
+
self-fork as the idle-close watcher and lives exactly as long as the browser. It holds one CDP
|
|
696
|
+
connection and auto-attaches to every page, popup, new tab, out-of-process iframe and worker. It
|
|
697
|
+
signs each request at the Fetch "Request" stage, including every redirect hop. So requests the page makes on its own are
|
|
698
|
+
signed as well: timers, `fetch()` after your cdpilot command has returned, a tab the page opens.
|
|
699
|
+
`data:`, `blob:` and `chrome-extension:` requests never leave the browser, so they pass through
|
|
700
|
+
unchanged. If signing a request fails, that request goes out unsigned, one line is written to
|
|
701
|
+
`CDPILOT_HOME/bot-auth/signers/<port>.log` (host only), and the page is never held up. Running
|
|
702
|
+
`launch --bot-auth` against a browser that is already up adds the signer to it.
|
|
703
|
+
|
|
704
|
+
Each request gets three headers:
|
|
705
|
+
|
|
706
|
+
| Header | Value |
|
|
707
|
+
|--------|-------|
|
|
708
|
+
| `Signature-Agent` | `"https://your-domain.com"` |
|
|
709
|
+
| `Signature-Input` | `sig1=("@authority" "signature-agent");created=…;keyid="<thumbprint>";alg="ed25519";expires=…;nonce="…";tag="web-bot-auth"` |
|
|
710
|
+
| `Signature` | `sig1=:<base64 Ed25519 signature>:` |
|
|
711
|
+
|
|
712
|
+
`expires` is `created` + 5 minutes. The `nonce` is 64 random bytes, base64-encoded, and new for
|
|
713
|
+
every request.
|
|
714
|
+
|
|
715
|
+
This is the **legacy** `Signature-Agent` form (draft-05 A.2.3), and it is the default because it
|
|
716
|
+
is the form Cloudflare's verifier accepts: Cloudflare rejects the dictionary form of later drafts.
|
|
717
|
+
To send the dictionary form instead (`Signature-Agent: sig1="https://your-domain.com"`, covered as
|
|
718
|
+
`"signature-agent";key="sig1"`), run `cdpilot bot-auth format dict` (`bot-auth format legacy`
|
|
719
|
+
switches back, `bot-auth format` shows it, `bot-auth init --agent-format dict` sets it at
|
|
720
|
+
creation). It applies from the next `launch --bot-auth`.
|
|
721
|
+
|
|
722
|
+
**What is not signed.** WebSocket handshakes are not signed: the browser does not pass them
|
|
723
|
+
through CDP's `Fetch` domain, so a `wss://` connection the page opens goes out without the
|
|
724
|
+
headers. Service-worker-internal cache hits and `data:`/`blob:` URLs never reach a server.
|
|
725
|
+
|
|
726
|
+
**If the signer dies or hangs.** While it runs, the signer holds every request of the browser at
|
|
727
|
+
the Fetch stage until it has added the headers. If it is killed, requests go out unsigned; `cdpilot
|
|
728
|
+
status` and `cdpilot go` then print one stderr line (signer not running, requests go out unsigned,
|
|
729
|
+
run `cdpilot launch --bot-auth` again) until you do, or `stop`. If it is frozen (a
|
|
730
|
+
stopped process, a debugger), requests hang: `cdpilot stop` and `launch --bot-auth` again.
|
|
731
|
+
cdpilot trusts its signer state only for the process whose command line is that port's signer and
|
|
732
|
+
only for the browser it attached to, so a stale state file whose pid was reused by another program
|
|
733
|
+
is dropped, never signalled.
|
|
734
|
+
|
|
735
|
+
**Idle close** still works while the signer runs: the signer is attached to every page, so instead
|
|
736
|
+
of "a page is attached" cdpilot counts the CDP clients other than the signer (by socket owner:
|
|
737
|
+
`netstat` with an `lsof` fallback on macOS, `netstat` on Windows, `ss` on Linux), and a `watch` daemon or a Playwright session keeps
|
|
738
|
+
the browser open. On Linux, `ss -tnp` shows only your own processes' sockets, so a client running
|
|
739
|
+
as another user is not counted and idle close may close the browser under it.
|
|
740
|
+
|
|
741
|
+
**Stealth conflict.** Signing says "I am an agent", and stealth says "I am not". If you pass
|
|
742
|
+
`--bot-auth` together with `--stealth`/`--undetected`, or while `cdpilot mode` is `stealth` or
|
|
743
|
+
`undetected`, cdpilot prints one warning line and applies bot-auth. While the signer runs, no
|
|
744
|
+
stealth script or user-agent override is injected, and adaptive escalation does not retry at a
|
|
745
|
+
stealth tier.
|
|
746
|
+
|
|
747
|
+
**Register with Cloudflare.** Once the directory is live, submit it through the Bot Submission
|
|
748
|
+
Form in the Cloudflare dashboard as a *signed agent* (an agent acting for its users) or a
|
|
749
|
+
*verified bot*; see Cloudflare's
|
|
750
|
+
[signed agents](https://blog.cloudflare.com/signed-agents/) and
|
|
751
|
+
[verified bots with cryptography](https://blog.cloudflare.com/verified-bots-with-cryptography/)
|
|
752
|
+
posts. Sites behind Cloudflare then see your requests as coming from your agent. Sites that
|
|
753
|
+
don't check Web Bot Auth ignore the headers.
|
|
754
|
+
|
|
508
755
|
### Friction Ladder (progressive anti-bot detection)
|
|
509
756
|
|
|
510
757
|
Real sites don't just throw a CAPTCHA — they stack defenses incrementally.
|
|
@@ -633,6 +880,7 @@ Earlier figures on this page (sannysoft 24/24, intoli 6/6) were measured on v0.4
|
|
|
633
880
|
|
|
634
881
|
```bash
|
|
635
882
|
cdpilot browser [name|auto] # workload-aware browser selection
|
|
883
|
+
cdpilot browser install chrome-for-testing # for extension development (see above)
|
|
636
884
|
cdpilot health # JSON: alive, port, tabs, browser, today's crashes, idle close
|
|
637
885
|
```
|
|
638
886
|
|
|
@@ -728,6 +976,187 @@ never changes a command's output or exit code, and a failed write costs one
|
|
|
728
976
|
stderr line. `CDPILOT_LOG=0` turns it off; `CDPILOT_LOG_DAYS` (default 14)
|
|
729
977
|
sets how many days are kept. The MCP server exposes it as `browser_log`.
|
|
730
978
|
|
|
979
|
+
### Record & replay
|
|
980
|
+
|
|
981
|
+
Record sequences of CLI commands into a portable flow JSON file and replay them with variable substitution:
|
|
982
|
+
|
|
983
|
+
```bash
|
|
984
|
+
cdpilot record start [--name <n>]
|
|
985
|
+
cdpilot record status
|
|
986
|
+
cdpilot record cancel
|
|
987
|
+
cdpilot go https://example.com
|
|
988
|
+
cdpilot click "#login"
|
|
989
|
+
cdpilot fill "#email" test@test.com
|
|
990
|
+
cdpilot record stop [--save login-flow.json]
|
|
991
|
+
cdpilot replay login-flow.json [--var k=v ...] [--delay real|none|<ms>] [--dry-run] [--continue]
|
|
992
|
+
```
|
|
993
|
+
|
|
994
|
+
Commands that run while recording is active are appended to `$CDPILOT_HOME/record/active.json`; secrets are replaced by `{{name}}` placeholders before a step is written to active.json. `CDPILOT_LOG=0` does not turn recording off, and steps run by `replay` are not recorded. Failed steps (`exit != 0`) are dropped at `record stop`. A value becomes a placeholder `{{name}}` cataloged under `vars` if the selector/label is secret-shaped (`#password`, `input[type=password]`, API key), or the value matches a token/bearer pattern or secret URL parameter. Other typed values (emails, search terms, anything the rules do not recognise) are stored verbatim, so review a flow before sharing it; `--save` files are created with mode 0600. In `replay`, `--delay real` reproduces recorded inter-step timing (capped at 10 s per gap), while `--delay none` or a fixed millisecond delay skips waiting. Variables are supplied via `--var k=v` or `CDPILOT_VAR_<NAME>`; missing variables halt replay before any step runs.
|
|
995
|
+
|
|
996
|
+
### Use your own browser (human-in-the-loop)
|
|
997
|
+
|
|
998
|
+
An agent hits a CAPTCHA or a login wall it cannot get past. A person solves it,
|
|
999
|
+
and the agent carries on in the same browser.
|
|
1000
|
+
|
|
1001
|
+
**The simplest way needs no `connect`.** cdpilot's own browser is a normal,
|
|
1002
|
+
visible window unless you launch it headless (`cdpilot headless on` /
|
|
1003
|
+
`CHROME_HEADLESS=1`). The person solves the CAPTCHA or logs in right there, and
|
|
1004
|
+
the agent continues:
|
|
1005
|
+
|
|
1006
|
+
```bash
|
|
1007
|
+
cdpilot go https://example.com/dashboard # agent: "captcha detected" / login wall
|
|
1008
|
+
cdpilot captcha-wait 300 # agent waits while the person solves it
|
|
1009
|
+
cdpilot go https://example.com/dashboard # agent continues in the same window
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
That window uses cdpilot's own profile, so the login stays for later runs.
|
|
1013
|
+
|
|
1014
|
+
**`connect` is for a browser you start yourself**, with a debugging port and
|
|
1015
|
+
your own profile directory (log into it once; it keeps its cookies):
|
|
1016
|
+
|
|
1017
|
+
```bash
|
|
1018
|
+
# macOS
|
|
1019
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
|
|
1020
|
+
--remote-debugging-port=9450 --user-data-dir="$HOME/cdpilot-chrome"
|
|
1021
|
+
# Linux
|
|
1022
|
+
google-chrome --remote-debugging-port=9450 --user-data-dir="$HOME/cdpilot-chrome"
|
|
1023
|
+
# Windows
|
|
1024
|
+
"%ProgramFiles%\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9450 --user-data-dir="%USERPROFILE%\cdpilot-chrome"
|
|
1025
|
+
|
|
1026
|
+
cdpilot connect 9450 # or: cdpilot connect ws://127.0.0.1:9450/devtools/browser/<id>
|
|
1027
|
+
cdpilot connect --auto # or: find it via a DevToolsActivePort file
|
|
1028
|
+
cdpilot go https://example.com/dashboard
|
|
1029
|
+
cdpilot disconnect # forget it; the browser keeps running
|
|
1030
|
+
```
|
|
1031
|
+
|
|
1032
|
+
Since Chrome 136, `--remote-debugging-port` is ignored on the default profile,
|
|
1033
|
+
so the separate `--user-data-dir` is required. `connect --auto` reads the
|
|
1034
|
+
DevToolsActivePort file of each known profile directory (`~/cdpilot-chrome`
|
|
1035
|
+
from the command above, Chrome stable/beta/dev/canary, Chromium, Brave,
|
|
1036
|
+
Vivaldi, Edge), skips files left over from a browser that is gone or was
|
|
1037
|
+
replaced on that port (the browser id in the file must match), and connects to
|
|
1038
|
+
the first live one. It asks over HTTP only; a WebSocket probe (which Chrome's
|
|
1039
|
+
chrome://inspect mode may answer with a permission prompt) is sent only when no
|
|
1040
|
+
live browser was found.
|
|
1041
|
+
|
|
1042
|
+
**Not supported yet: Chrome's `chrome://inspect/#remote-debugging` toggle**
|
|
1043
|
+
(Chrome 144+, your everyday logged-in profile). In that mode Chrome answers
|
|
1044
|
+
only on its WebSocket (`/json/version` and `/json` return 404) and may ask you
|
|
1045
|
+
to allow every new debugging connection, while cdpilot lists tabs over `/json`
|
|
1046
|
+
and opens a new connection per command. `connect --auto` recognises the mode
|
|
1047
|
+
and exits with code 2 and the start command above instead of half-working.
|
|
1048
|
+
|
|
1049
|
+
**What cdpilot does with a connected browser:**
|
|
1050
|
+
|
|
1051
|
+
- Page commands (`go`, `click`, `fill`, `shot`, …) run in a tab cdpilot opens
|
|
1052
|
+
for itself in a new window that does not take the focus
|
|
1053
|
+
(`Target.createTarget` with `newWindow` + `background`, so your window and
|
|
1054
|
+
tab keep it) on the first page command, and keep using that tab. `close`,
|
|
1055
|
+
`session-close` and the idle session cleanup close only such tabs, the ones
|
|
1056
|
+
cdpilot opened in this browser run, never the last one. Your own tabs are
|
|
1057
|
+
never navigated or typed into; only a command that names a tab touches it
|
|
1058
|
+
(`switch-tab`, `close-tab <index|id>`, `CDPILOT_TARGET`, and `multi-eval`,
|
|
1059
|
+
which runs its script in every open tab).
|
|
1060
|
+
- It never closes or kills the browser. `close`, `close --force`, `stop`,
|
|
1061
|
+
`stop --smart [--force]`, `project-stop`, `stop-all`, `session-close` and MCP
|
|
1062
|
+
`browser_close` close at most cdpilot's own tabs (above) and print
|
|
1063
|
+
"connected browser left running; run `cdpilot disconnect` to forget it".
|
|
1064
|
+
The idle auto-close never touches it,
|
|
1065
|
+
`tabs --reap` closes nothing, `wipe`, `permission`, `cookies load` and `cf-replay` are refused (add `--allow-external` to load/replay cookies anyway),
|
|
1066
|
+
`context close` only destroys a context `context create` made, and `launch` /
|
|
1067
|
+
MCP `browser_launch` start nothing.
|
|
1068
|
+
- `close-tab` is an explicit command and **can close your tabs**: the active
|
|
1069
|
+
(cdpilot) tab with no argument, or any tab you name by index or id. It never
|
|
1070
|
+
closes the last tab, which would quit the browser on Windows and Linux.
|
|
1071
|
+
- It injects nothing into it: no stealth patch or user-agent override (whatever
|
|
1072
|
+
`cdpilot mode` / `stealth` says), no glow, no input blocker, no dev-extension
|
|
1073
|
+
scripts, no auto-cookie restore.
|
|
1074
|
+
- The registration stays until you run `cdpilot disconnect`. If the browser is
|
|
1075
|
+
closed, page commands exit 1 with "your connected browser is gone; run
|
|
1076
|
+
`cdpilot connect` again or `cdpilot disconnect`", and cdpilot never launches a
|
|
1077
|
+
browser in its place.
|
|
1078
|
+
- `connect` refuses when this project already has a browser cdpilot launched
|
|
1079
|
+
("stop it first (`cdpilot stop`) or use another project"), and only
|
|
1080
|
+
`127.0.0.1` / `localhost` is accepted (anything else exits 2). `disconnect`
|
|
1081
|
+
also stops this project's `watch` daemon.
|
|
1082
|
+
- cdpilot treats a browser as its own only with proof: the browser id it
|
|
1083
|
+
recorded at `launch` (or, for older entries, a live recorded pid that holds
|
|
1084
|
+
the debug port). A busy port is not proof, so a leftover registry entry never
|
|
1085
|
+
makes `stop` / `close` touch your own Chrome on port 9222.
|
|
1086
|
+
|
|
1087
|
+
**Risks:**
|
|
1088
|
+
|
|
1089
|
+
- Every cdpilot command, and any agent that can run cdpilot, can read and act
|
|
1090
|
+
with the cookies and sessions of that profile (mail, banking, admin panels).
|
|
1091
|
+
Use a profile that is logged into only what the task needs.
|
|
1092
|
+
- The debugging port is open to every local program for as long as that browser
|
|
1093
|
+
runs, not just to cdpilot. Close the browser when you are done.
|
|
1094
|
+
- Sites can tell automation more easily: cdpilot applies no stealth to a
|
|
1095
|
+
connected browser.
|
|
1096
|
+
- cdpilot's tab sits next to yours in the same window, and an agent may
|
|
1097
|
+
still act in one of your tabs when it names it (`switch-tab`,
|
|
1098
|
+
`close-tab <n>`, `multi-eval`).
|
|
1099
|
+
|
|
1100
|
+
### WebMCP tools (opt-in)
|
|
1101
|
+
|
|
1102
|
+
[WebMCP](https://webmachinelearning.github.io/webmcp/) lets a page register
|
|
1103
|
+
"tools" on `document.modelContext`: imperatively with `registerTool()`, or
|
|
1104
|
+
declaratively with `<form toolname tooldescription>` (inputs described by
|
|
1105
|
+
`toolparamdescription`). cdpilot lists and calls them through the browser's own
|
|
1106
|
+
API, `getTools()` and `executeTool()`. In Chrome the API is behind
|
|
1107
|
+
`chrome://flags/#enable-webmcp-testing` (Chromium 146+), so start the browser
|
|
1108
|
+
with it:
|
|
1109
|
+
|
|
1110
|
+
```bash
|
|
1111
|
+
cdpilot launch --webmcp # saved for this project: later launches (auto-launch too) keep it
|
|
1112
|
+
cdpilot go https://example.com/shop
|
|
1113
|
+
cdpilot tools list # name, title, description, annotations, input schema
|
|
1114
|
+
cdpilot tools list --json
|
|
1115
|
+
cdpilot tools call add_to_cart '{"sku":"A1","qty":2}'
|
|
1116
|
+
cdpilot tools call add_to_cart --arg sku=A1 --arg qty=2
|
|
1117
|
+
cdpilot launch --no-webmcp # turn it off again (applies at the next start)
|
|
1118
|
+
```
|
|
1119
|
+
|
|
1120
|
+
- `launch --webmcp` adds `--enable-features=WebMCP` to the browser's command
|
|
1121
|
+
line and saves the mode next to the stealth mode (`webmcp.json` in the
|
|
1122
|
+
project profile); `CDPILOT_WEBMCP=1|0` overrides it. `cdpilot status` shows a
|
|
1123
|
+
WebMCP line while the mode is on.
|
|
1124
|
+
Chrome reads feature flags only at startup: a browser that is already running
|
|
1125
|
+
keeps its flags until `cdpilot stop`.
|
|
1126
|
+
- `tools list` shows the tools of the page and of its same-origin iframes
|
|
1127
|
+
(with the frame URL; every readable frame is asked for its own tools, so a
|
|
1128
|
+
frame tool missing from the top document's `getTools()` is still listed and
|
|
1129
|
+
runs in its frame), marks form tools and whether the form submits itself
|
|
1130
|
+
(`toolautosubmit`), and shows `title` and the `readOnlyHint` /
|
|
1131
|
+
`consequentialHint` / `untrustedContentHint` annotations. Nothing is injected
|
|
1132
|
+
into the page: the list is the browser's registry at that moment, so it
|
|
1133
|
+
follows reloads and navigations. Both commands run in cdpilot's own isolated
|
|
1134
|
+
world (no `Runtime.enable`), so a page script that wraps `getTools()` /
|
|
1135
|
+
`executeTool()` cannot add tools, change results or see the calls; only if
|
|
1136
|
+
that world cannot see `document.modelContext` does cdpilot use the page's
|
|
1137
|
+
main world, and it says so on stderr. With no tools it says why (WebMCP not
|
|
1138
|
+
enabled in the running browser, not a secure context, browser too old,
|
|
1139
|
+
document not origin-keyed, or the page registered none) and exits 0.
|
|
1140
|
+
- `tools call` checks the arguments against the tool's `inputSchema` first
|
|
1141
|
+
(`required`, `type` — `true` is not an integer —, `enum`, `const`, nested
|
|
1142
|
+
`properties` and `items`; other keywords are left to the page) and exits 1
|
|
1143
|
+
on a mismatch, an unknown tool or a tool error. It passes an `AbortSignal`
|
|
1144
|
+
and aborts it when `--timeout` (default 20 s) runs out, then exits 124
|
|
1145
|
+
with `timed out after <N>s (--timeout); its execution was aborted`. The
|
|
1146
|
+
signal fires at the `--timeout` deadline; the watchdog waits 3 s more so
|
|
1147
|
+
that report is printed.
|
|
1148
|
+
A form tool without `toolautosubmit` waits for a person to submit the form.
|
|
1149
|
+
When several frames register the same name, `--frame <url-part>` picks one
|
|
1150
|
+
(an exact frame URL wins); without it the top document's tool is used, and
|
|
1151
|
+
if there is none the call stops and lists the frames.
|
|
1152
|
+
- MCP: `browser_site_tools` and `browser_site_tool_call` are listed only while
|
|
1153
|
+
the project's WebMCP mode is on (or `CDPILOT_WEBMCP=1` for the server).
|
|
1154
|
+
|
|
1155
|
+
> **Security**: `tools call` runs the page's own code, and tool names,
|
|
1156
|
+
> descriptions and results are page content: treat them as untrusted.
|
|
1157
|
+
> Arguments and results are written to the session log with secret-named
|
|
1158
|
+
> fields (password, token, key, secret, auth, ...) redacted at any depth.
|
|
1159
|
+
|
|
731
1160
|
### Scaling & Workstation Use
|
|
732
1161
|
|
|
733
1162
|
```bash
|
|
@@ -845,9 +1274,9 @@ print(result.stdout)
|
|
|
845
1274
|
| v0.6.0 | + captcha solver + cookies-auto (regression) | 15 / 80 | 18.75% |
|
|
846
1275
|
| **v0.8.0** | **Full — cookies safe-host scoped + per-task wipe (no proxy, no TLS fork)** | **29 / 80** | **36.25%** |
|
|
847
1276
|
| v0.7.0 (slot) | + named proxy pools | _depends on user proxy_ | — |
|
|
848
|
-
| v0.8.0 (slot) |
|
|
1277
|
+
| v0.8.0 (slot) | TLS-aware launcher — withdrawn: camoufox / undetected-chrome do not speak CDP (CHANGELOG 0.8.0) | — | — |
|
|
849
1278
|
|
|
850
|
-
**What cdpilot does not do:** `cdpilot` is an avoidance engine,
|
|
1279
|
+
**What cdpilot does not do:** `cdpilot` is an avoidance engine first; the solver plugins above (`cdpilot captcha`, bring your own key, off by default) are a fallback, not the strategy. We prioritize structural stealth (JS fingerprinting, behavioral entropy) to prevent challenges from appearing. When a CAPTCHA blocks progress and cannot be avoided, the task fails — that is the honest definition of our success rate. PerimeterX (2/18), GeeTest (0/4) and Akamai (1/6) are known weaknesses; named proxy pools (v0.7) help where the block is IP-based, while TLS-level fingerprinting cannot be fixed from inside a CDP-driven browser and remains open.
|
|
851
1280
|
|
|
852
1281
|
## Recommended Configuration
|
|
853
1282
|
|
|
@@ -886,7 +1315,7 @@ Future paid offerings:
|
|
|
886
1315
|
- **No arbitrary file access** — MCP screenshot filenames are sanitized and restricted to the screenshots directory. Path traversal is blocked.
|
|
887
1316
|
- **Safe CSS selectors** — All selectors passed to `querySelector` are JSON-escaped to prevent injection.
|
|
888
1317
|
- **No network exposure** — CDP listens on `127.0.0.1` only. Remote connections are not possible by default.
|
|
889
|
-
- **
|
|
1318
|
+
- **One dependency** — zero npm packages; the Python side needs only `websockets`. A one-package supply chain is small enough to audit.
|
|
890
1319
|
|
|
891
1320
|
Found a vulnerability? Please email the maintainer directly instead of opening a public issue.
|
|
892
1321
|
|
|
@@ -924,13 +1353,13 @@ The only browser MCP with built-in test assertions. Here's what we've shipped an
|
|
|
924
1353
|
- [x] **10 built-in test assertions** — assert, assert-url, assert-title, assert-count, assert-value, assert-attr, assert-visible/hidden, wait-for, check (batch), screenshot-diff
|
|
925
1354
|
- [x] **`test` runner + `trace` viewer** — `cdpilot test [--watch]` runs `*.cdpt.js` files; `cdpilot trace open <run>` opens a time-travel trace viewer for a run
|
|
926
1355
|
- [x] **Accessibility tree snapshot** (`a11y-snapshot`) — structured text with @ref handles the agent can act on directly, no vision model needed; 1.4–42× smaller than the page's raw HTML on the four pages we measured (2026-09-27). Not always cheaper than a screenshot: link-heavy pages produce more text than a small screenshot costs
|
|
927
|
-
- [x] **Token-efficient screenshots** — element-level crop
|
|
1356
|
+
- [x] **Token-efficient screenshots** — element-level crop, JPEG quality control, format selection
|
|
928
1357
|
- [x] **Vision fallback** (`describe`) — a11y + screenshot + text in one call
|
|
929
1358
|
- [x] **Annotated screenshots** — @N badge overlays on interactive elements
|
|
930
1359
|
- [x] **Auto-wait** — MutationObserver-based, 5s automatic element waiting
|
|
931
1360
|
- [x] **`wait-for-text`** — adaptive text-based waiting (subtree + characterData) for streaming AI responses, async toasts, and selector-less synchronization
|
|
932
1361
|
- [x] **`eval-batch`** — run N JS expressions in 1 CDP roundtrip (5-30x speedup vs sequential `eval`)
|
|
933
|
-
- [x] **`block`** — request blocking via `Network.setBlockedURLs` with built-in presets (images/fonts/ads/media)
|
|
1362
|
+
- [x] **`block`** — request blocking via `Network.setBlockedURLs` with built-in presets (images/fonts/ads/media); fewer bytes and faster loads on opt-in (measure on your own pages)
|
|
934
1363
|
- [x] **`dismiss`** — heuristic auto-click for LLM chat sign-up walls (EN+TR pattern library, destructive-action guards)
|
|
935
1364
|
- [x] **`adaptive`** — auto-escalate to stealth on CAPTCHA-protected hosts, persistent per-host memory ("run fast, climb walls")
|
|
936
1365
|
- [x] **`cookies save/load`** — export/import cookies as JSON (replay CF/DataDome clearance across runs)
|