cdpilot 0.9.4 → 0.9.6
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 +188 -17
- package/bin/cdpilot.js +376 -18
- package/package.json +1 -1
- package/src/cdpilot.py +3659 -294
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
|
|
|
@@ -141,6 +141,34 @@ that teaches Claude Code when and how to reach for cdpilot's commands. See
|
|
|
141
141
|
[`.claude-plugin/marketplace.json`](.claude-plugin/marketplace.json) and
|
|
142
142
|
[`plugins/cdpilot/`](plugins/cdpilot/).
|
|
143
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; the four
|
|
152
|
+
`browser_agent_*` tools add ~11% of schema (49 tools ≈ 6,400 tokens, scaled from the measurement).
|
|
153
|
+
|
|
154
|
+
### Agent mode over MCP
|
|
155
|
+
|
|
156
|
+
`browser_agent_observe` (`diff`), `browser_agent_act` (`ref`/`url`, `action`, `text`), `browser_agent_flow_save`
|
|
157
|
+
(`name`, `last`) and `browser_agent_run` (`flow`, `vars`, `dry_run`) wrap the agent commands below, so a model
|
|
158
|
+
on MCP works from numbered refs, gets diffs, and replays a learned flow with no page read at all. They are
|
|
159
|
+
outside the `core` profile; `browser_tools enable` adds them. A drift or a missing variable comes back as
|
|
160
|
+
`isError` with the command's JSON (`status:"drift"` carries the page's observation).
|
|
161
|
+
|
|
162
|
+
Set `CDPILOT_DAEMON=1` in the MCP server's environment and every tool call runs in the server process
|
|
163
|
+
instead of a fresh Python process: ~10 ms before the CDP work instead of ~400 ms (`browser_eval` /
|
|
164
|
+
`browser_tabs` / `browser_content` medians 11 / 7 / 9 ms against 412 / 390 / 416 ms, M1 Max under load).
|
|
165
|
+
Same rules as the warm process below — same output, exit codes and session log; lifecycle tools
|
|
166
|
+
(`browser_launch`, `browser_connect`) still run fresh; a call past 30 s is cancelled.
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
{ "mcpServers": { "cdpilot": { "command": "npx", "args": ["cdpilot", "mcp"], "env": { "CDPILOT_DAEMON": "1" } } } }
|
|
170
|
+
```
|
|
171
|
+
|
|
144
172
|
### MCP Bundle (.mcpb)
|
|
145
173
|
|
|
146
174
|
For MCP clients that support one-click local server installation (Claude
|
|
@@ -248,11 +276,19 @@ cdpilot frame eval --frame "#card" "document.title" # runs in the frame's
|
|
|
248
276
|
inside it and says so on stderr:
|
|
249
277
|
`note: '#card-element' is not an iframe; using the iframe inside it`.
|
|
250
278
|
- Selectors and texts that already contain `>>>` keep working: when the part
|
|
251
|
-
before the first `>>>` finds no iframe in the page, or a segment is empty
|
|
279
|
+
before the first `>>>` finds no iframe or shadow host in the page, or a segment is empty
|
|
252
280
|
(`click "Next >>>"`), the whole string is used as written, exactly as
|
|
253
281
|
before. If that fails too, one stderr line explains:
|
|
254
|
-
`note: 'Home' matched no iframe; used the selector as written`.
|
|
282
|
+
`note: 'Home' matched no iframe or shadow host; used the selector as written`.
|
|
255
283
|
`--frame` never falls back: a frame it cannot find is an error (exit 1).
|
|
284
|
+
- **Shadow DOM support**: `>>>` hops also pierce open shadow roots (`host.shadowRoot.querySelector(next)`).
|
|
285
|
+
Chains mix freely with iframes: `cdpilot click "iframe#pay >>> my-card >>> button.inner"` or
|
|
286
|
+
`cdpilot click "my-app >>> my-panel >>> input"`. A closed shadow root is unreachable by design
|
|
287
|
+
and fails with `'X' has a closed shadow root; cdpilot cannot reach inside it`.
|
|
288
|
+
When a plain CSS selector matches nothing at document level, cdpilot automatically falls back
|
|
289
|
+
to a depth-first search across all open shadow roots, printing one stderr note:
|
|
290
|
+
`note: '<sel>' found inside the shadow root of <host tag#id>`. Explicit `>>>` hops do not print this note.
|
|
291
|
+
`wait` and `wait-for` poll shadow roots every 200 ms alongside the document MutationObserver.
|
|
256
292
|
- For `smart-click` / `smart-fill` / `smart-select` the part before the first
|
|
257
293
|
`>>>` is a frame only if it looks like a selector (it has `#`, `.` or `[`,
|
|
258
294
|
or starts with an `iframe`/`frame` tag) or is `url=…`; words stay text.
|
|
@@ -298,14 +334,22 @@ cdpilot frame eval --frame "#card" "document.title" # runs in the frame's
|
|
|
298
334
|
### Debugging
|
|
299
335
|
|
|
300
336
|
```bash
|
|
301
|
-
cdpilot console [url]
|
|
302
|
-
cdpilot network [url]
|
|
303
|
-
cdpilot
|
|
304
|
-
cdpilot
|
|
305
|
-
cdpilot
|
|
306
|
-
cdpilot eval
|
|
337
|
+
cdpilot console [url] # Capture console logs
|
|
338
|
+
cdpilot network [url] # Monitor network requests
|
|
339
|
+
cdpilot network <url> --har trace.har # Export navigation traffic as HAR 1.2 JSON (--har - for stdout)
|
|
340
|
+
cdpilot debug [url] # Full diagnostic (console+network+perf+shot)
|
|
341
|
+
cdpilot perf # Performance metrics
|
|
342
|
+
cdpilot eval <js> # Execute JavaScript
|
|
343
|
+
cdpilot eval-batch <json> # Run N JS expressions in 1 roundtrip (5-30x faster)
|
|
307
344
|
```
|
|
308
345
|
|
|
346
|
+
#### Network monitoring & HAR export
|
|
347
|
+
|
|
348
|
+
`cdpilot network <url> --har <file>` exports all HTTP requests of a navigation as standard HAR 1.2 JSON (`--har -` writes to stdout).
|
|
349
|
+
- **Included:** URLs, methods, headers, status codes, query strings, and CDP timing breakdowns (DNS, connect, SSL, send, wait, receive).
|
|
350
|
+
- **Omitted:** Response bodies are not fetched or stored (avoids memory bloat and round-trip overhead); content.size reflects transfer length; bodySize is -1.
|
|
351
|
+
- **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.
|
|
352
|
+
|
|
309
353
|
```bash
|
|
310
354
|
# Example: read 4 DOM values in a single CDP roundtrip instead of 4
|
|
311
355
|
cdpilot eval-batch '["document.title","location.href","document.links.length","document.images.length"]'
|
|
@@ -428,6 +472,70 @@ cdpilot a11y summary # Quick summary
|
|
|
428
472
|
cdpilot a11y find <role> # Find elements by ARIA role
|
|
429
473
|
```
|
|
430
474
|
|
|
475
|
+
### Agent mode: numbered refs and diffs (token budget)
|
|
476
|
+
|
|
477
|
+
```bash
|
|
478
|
+
cdpilot agent observe # {url, title, actions:[{ref:"@1", role, name, value?}], text, token_estimate}
|
|
479
|
+
cdpilot agent act --ref @3 --action click
|
|
480
|
+
cdpilot agent act --ref @4 --action fill --text "cdpilot"
|
|
481
|
+
cdpilot agent act --url https://example.com/ # navigates silently, prints the diff
|
|
482
|
+
cdpilot agent observe --diff # only what changed since the last observe/act
|
|
483
|
+
cdpilot agent stats | agent reset
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
`observe` turns the accessibility tree into a numbered list of things an agent can act on (buttons, links,
|
|
487
|
+
fields with their current value, unique text anchors) plus a short text summary; no screenshot, no HTML.
|
|
488
|
+
Every `act` and `observe --diff` returns only the change: `changed.added` (full descriptors, so the agent can act
|
|
489
|
+
on a new element without another observe), `changed.removed` (refs), `changed.value_changed`, and `text` only
|
|
490
|
+
when it changed (`text_changed`). Refs are stable while the element exists (keyed by the DOM node, not by
|
|
491
|
+
position); a navigation starts a new set. `token_estimate` is `len(json)/4`; `saved_vs_full` compares the diff
|
|
492
|
+
with a full observe of the same page. State lives in `~/.cdpilot/projects/<id>/agent-state.json`.
|
|
493
|
+
|
|
494
|
+
#### Learned flows: drive a site once, replay it with no model
|
|
495
|
+
|
|
496
|
+
```bash
|
|
497
|
+
cdpilot agent observe && cdpilot agent act --ref @3 --action click && cdpilot agent act --ref @4 --action fill --text "cdpilot"
|
|
498
|
+
cdpilot agent flow save inbox-triage # the acts since the last navigation → ~/.cdpilot/projects/<id>/flows/inbox_triage.json
|
|
499
|
+
cdpilot agent run inbox-triage # replays by anchors; prints one JSON: {status:"ok", steps, url, title, ms, tokens_avoided}
|
|
500
|
+
cdpilot agent run login --var password=… # a typed secret was saved as {{password}}; --var supplies it, or CDPILOT_VAR_PASSWORD
|
|
501
|
+
# with CDPILOT_VAR_HOSTS=example.com (the host the value was recorded on)
|
|
502
|
+
cdpilot agent flow list | show <name> | delete <name>
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
Every `agent act` leaves a trail entry addressed by an *anchor*, the element's accessibility role, name and
|
|
506
|
+
position among equals (`{"role":"button","name":"Archive","nth":0}`), never a `@ref` or a DOM node id, which
|
|
507
|
+
belong to one session. `agent flow save` writes the trail as a flow; `agent run` finds each anchor on the live
|
|
508
|
+
page (exact name, then case-insensitive, then a longer name containing it when that is unambiguous) and acts,
|
|
509
|
+
with no model in the loop. The result's `tokens_avoided` is what a model would have read observing the page
|
|
510
|
+
before each step. When a site has a flow, `agent observe` lists it under `known_flows`.
|
|
511
|
+
|
|
512
|
+
**Drift stops, never guesses.** If a step's anchor is gone, or the step lands on another host than it did
|
|
513
|
+
when recorded, `agent run` exits 4 and prints that page's full observation (`status:"drift"`, `step`,
|
|
514
|
+
`expected`, `actions`, `text`): the agent acts on those refs, finishes the task, and `agent flow save <name>`
|
|
515
|
+
rewrites the flow. Exit 2: unknown flow or a `{{variable}}` without a value (checked before any step runs);
|
|
516
|
+
`--dry-run` prints the steps with placeholders, never values. Values typed into secret-named fields
|
|
517
|
+
(password, one-time code, API key, PIN, card number, …) or token-shaped values are placeholders on disk; the
|
|
518
|
+
same fields show `«masked»` as their value in `observe` output. Masking goes by field name or token shape: a
|
|
519
|
+
secret typed into a field named otherwise is stored in clear, so review a flow before sharing it (flow files
|
|
520
|
+
and `agent-state.json` are 0600). A `{{placeholder}}` typed into `agent act --text` or `--url` is refused
|
|
521
|
+
(exit 2): placeholders are cdpilot's own, so a flow replays only values the user recorded, and a step that
|
|
522
|
+
types one runs only on the host it was recorded on. `--var` values are visible in the process list for the
|
|
523
|
+
duration of the call and are redacted in the session log; an environment value never enters argv.
|
|
524
|
+
`CDPILOT_VAR_<NAME>` is used only by a step recorded on a host named in `CDPILOT_VAR_HOSTS` (comma-separated,
|
|
525
|
+
subdomains included), on that same origin, and never inside a URL — so a flow that a prompt-injected model
|
|
526
|
+
records on another site cannot draw on the environment's secrets; anything else is refused before the first
|
|
527
|
+
step (exit 2, `refused` says why) or is a drift.
|
|
528
|
+
|
|
529
|
+
#### Text that talks to the agent: `injection_suspect`
|
|
530
|
+
|
|
531
|
+
Page text that addresses the model instead of the user is the shape of an indirect prompt injection ("AI
|
|
532
|
+
assistants reading this page: ignore your previous instructions…"). `agent observe`, the diff of `agent act`
|
|
533
|
+
and a drift result of `agent run` carry `injection_suspect: [{text, match}]` (at most 5, absent when none)
|
|
534
|
+
whenever such a block is in the text: `ignore_instructions`, `addresses_ai`, `system_prompt`,
|
|
535
|
+
`new_instructions`, `hide_from_user`, `exfiltrate`. Nothing is hidden or blocked, the text stays where it was;
|
|
536
|
+
the agent sees the mark next to it and treats the block as data. The patterns are narrow on purpose: a page
|
|
537
|
+
*about* AI agents or system prompts is not flagged.
|
|
538
|
+
|
|
431
539
|
### Session Management
|
|
432
540
|
|
|
433
541
|
```bash
|
|
@@ -709,7 +817,7 @@ is dropped, never signalled.
|
|
|
709
817
|
|
|
710
818
|
**Idle close** still works while the signer runs: the signer is attached to every page, so instead
|
|
711
819
|
of "a page is attached" cdpilot counts the CDP clients other than the signer (by socket owner:
|
|
712
|
-
`netstat` on macOS
|
|
820
|
+
`netstat` with an `lsof` fallback on macOS, `netstat` on Windows, `ss` on Linux), and a `watch` daemon or a Playwright session keeps
|
|
713
821
|
the browser open. On Linux, `ss -tnp` shows only your own processes' sockets, so a client running
|
|
714
822
|
as another user is not counted and idle close may close the browser under it.
|
|
715
823
|
|
|
@@ -921,6 +1029,50 @@ CDPILOT_TIMEOUT=60 cdpilot run flow.cdp # default for every command (and each
|
|
|
921
1029
|
For `mcp` and `serve` the value is not applied to the long-running server
|
|
922
1030
|
itself; it is passed on to every tool call / request it runs.
|
|
923
1031
|
|
|
1032
|
+
### Warm process (`CDPILOT_DAEMON=1`)
|
|
1033
|
+
|
|
1034
|
+
Every cdpilot call is a fresh process: node starts (~0.1 s), Python imports the
|
|
1035
|
+
core (~0.15 s) and a CDP session is set up (~0.5 s) before the command itself
|
|
1036
|
+
runs — a browser paints a page in 20 ms, `cdpilot go` took about a second
|
|
1037
|
+
(measurements: `.claude/docs/perf-taban-2026-11-08.md`). With
|
|
1038
|
+
`CDPILOT_DAEMON=1` the launcher keeps one warm Python process per project and
|
|
1039
|
+
environment and hands page commands to it over a Unix socket; the process keeps
|
|
1040
|
+
the module and its event loop between calls, so the second and later calls skip
|
|
1041
|
+
the start-up cost.
|
|
1042
|
+
|
|
1043
|
+
```bash
|
|
1044
|
+
export CDPILOT_DAEMON=1
|
|
1045
|
+
cdpilot go https://example.com # first call: fresh process, starts the daemon
|
|
1046
|
+
cdpilot content # served warm
|
|
1047
|
+
cdpilot daemon status # pid, requests served, uptime, socket
|
|
1048
|
+
cdpilot daemon stop # stop every running daemon (they also leave after 30 min idle)
|
|
1049
|
+
```
|
|
1050
|
+
|
|
1051
|
+
Output, exit codes, the session log and recordings are the same either way:
|
|
1052
|
+
each request runs as its own process would (the caller's argv, cwd,
|
|
1053
|
+
environment and stdin; module state reset between requests; exit-time hooks
|
|
1054
|
+
run at request end; the CDP connections a command opened are closed at request
|
|
1055
|
+
end, as a process exit closed them, so no viewport, throttle or event state
|
|
1056
|
+
leaks into the next command). Closing the client (Ctrl+C) cancels the running
|
|
1057
|
+
command. The launcher runs a fresh process instead whenever the daemon is
|
|
1058
|
+
missing, busy with another call, of another version (that one is asked to
|
|
1059
|
+
leave), set up for a port or profile the registry no longer names, or the
|
|
1060
|
+
command is not a page command: `launch`, `stop`, `connect`, `mcp`, `serve`,
|
|
1061
|
+
`watch`, `replay`, `run`, `captcha-wait` and the other lifecycle commands, and
|
|
1062
|
+
anything with `--timeout`. Nothing is ever executed twice. POSIX only for now;
|
|
1063
|
+
the socket lives in `$CDPILOT_HOME/daemon/` (or `$XDG_RUNTIME_DIR` / a private
|
|
1064
|
+
temp directory when that path is too long for a Unix socket), 0600, and is used
|
|
1065
|
+
only when it and its directory belong to you. `CDPILOT_DAEMON_IDLE=<seconds>`
|
|
1066
|
+
sets the idle exit (default 1800). Opt-in in this release; the default may
|
|
1067
|
+
change once two weeks of use show no difference in error rates.
|
|
1068
|
+
|
|
1069
|
+
The MCP server (`cdpilot mcp`) reads the same variable: with `CDPILOT_DAEMON=1`
|
|
1070
|
+
in its environment it runs each tool call in the server process the same way (the
|
|
1071
|
+
same executor, no socket involved) instead of starting a Python process per
|
|
1072
|
+
call — ~400 ms → ~10 ms before the CDP work. Lifecycle tools and a project
|
|
1073
|
+
configuration that moved since the server started still run fresh; a call past
|
|
1074
|
+
30 s is cancelled at its next async step. See "Agent mode over MCP" above.
|
|
1075
|
+
|
|
924
1076
|
### Session log
|
|
925
1077
|
|
|
926
1078
|
Every command (and every MCP tool call) appends one JSON line to a local,
|
|
@@ -951,6 +1103,23 @@ never changes a command's output or exit code, and a failed write costs one
|
|
|
951
1103
|
stderr line. `CDPILOT_LOG=0` turns it off; `CDPILOT_LOG_DAYS` (default 14)
|
|
952
1104
|
sets how many days are kept. The MCP server exposes it as `browser_log`.
|
|
953
1105
|
|
|
1106
|
+
### Record & replay
|
|
1107
|
+
|
|
1108
|
+
Record sequences of CLI commands into a portable flow JSON file and replay them with variable substitution:
|
|
1109
|
+
|
|
1110
|
+
```bash
|
|
1111
|
+
cdpilot record start [--name <n>]
|
|
1112
|
+
cdpilot record status
|
|
1113
|
+
cdpilot record cancel
|
|
1114
|
+
cdpilot go https://example.com
|
|
1115
|
+
cdpilot click "#login"
|
|
1116
|
+
cdpilot fill "#email" test@test.com
|
|
1117
|
+
cdpilot record stop [--save login-flow.json]
|
|
1118
|
+
cdpilot replay login-flow.json [--var k=v ...] [--delay real|none|<ms>] [--dry-run] [--continue]
|
|
1119
|
+
```
|
|
1120
|
+
|
|
1121
|
+
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.
|
|
1122
|
+
|
|
954
1123
|
### Use your own browser (human-in-the-loop)
|
|
955
1124
|
|
|
956
1125
|
An agent hits a CAPTCHA or a login wall it cannot get past. A person solves it,
|
|
@@ -1082,7 +1251,9 @@ cdpilot launch --no-webmcp # turn it off again (applies at the next start)
|
|
|
1082
1251
|
Chrome reads feature flags only at startup: a browser that is already running
|
|
1083
1252
|
keeps its flags until `cdpilot stop`.
|
|
1084
1253
|
- `tools list` shows the tools of the page and of its same-origin iframes
|
|
1085
|
-
(with the frame URL
|
|
1254
|
+
(with the frame URL; every readable frame is asked for its own tools, so a
|
|
1255
|
+
frame tool missing from the top document's `getTools()` is still listed and
|
|
1256
|
+
runs in its frame), marks form tools and whether the form submits itself
|
|
1086
1257
|
(`toolautosubmit`), and shows `title` and the `readOnlyHint` /
|
|
1087
1258
|
`consequentialHint` / `untrustedContentHint` annotations. Nothing is injected
|
|
1088
1259
|
into the page: the list is the browser's registry at that moment, so it
|
|
@@ -1230,9 +1401,9 @@ print(result.stdout)
|
|
|
1230
1401
|
| v0.6.0 | + captcha solver + cookies-auto (regression) | 15 / 80 | 18.75% |
|
|
1231
1402
|
| **v0.8.0** | **Full — cookies safe-host scoped + per-task wipe (no proxy, no TLS fork)** | **29 / 80** | **36.25%** |
|
|
1232
1403
|
| v0.7.0 (slot) | + named proxy pools | _depends on user proxy_ | — |
|
|
1233
|
-
| v0.8.0 (slot) |
|
|
1404
|
+
| v0.8.0 (slot) | TLS-aware launcher — withdrawn: camoufox / undetected-chrome do not speak CDP (CHANGELOG 0.8.0) | — | — |
|
|
1234
1405
|
|
|
1235
|
-
**What cdpilot does not do:** `cdpilot` is an avoidance engine,
|
|
1406
|
+
**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.
|
|
1236
1407
|
|
|
1237
1408
|
## Recommended Configuration
|
|
1238
1409
|
|
|
@@ -1271,7 +1442,7 @@ Future paid offerings:
|
|
|
1271
1442
|
- **No arbitrary file access** — MCP screenshot filenames are sanitized and restricted to the screenshots directory. Path traversal is blocked.
|
|
1272
1443
|
- **Safe CSS selectors** — All selectors passed to `querySelector` are JSON-escaped to prevent injection.
|
|
1273
1444
|
- **No network exposure** — CDP listens on `127.0.0.1` only. Remote connections are not possible by default.
|
|
1274
|
-
- **
|
|
1445
|
+
- **One dependency** — zero npm packages; the Python side needs only `websockets`. A one-package supply chain is small enough to audit.
|
|
1275
1446
|
|
|
1276
1447
|
Found a vulnerability? Please email the maintainer directly instead of opening a public issue.
|
|
1277
1448
|
|
|
@@ -1309,13 +1480,13 @@ The only browser MCP with built-in test assertions. Here's what we've shipped an
|
|
|
1309
1480
|
- [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
|
|
1310
1481
|
- [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
|
|
1311
1482
|
- [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
|
|
1312
|
-
- [x] **Token-efficient screenshots** — element-level crop
|
|
1483
|
+
- [x] **Token-efficient screenshots** — element-level crop, JPEG quality control, format selection
|
|
1313
1484
|
- [x] **Vision fallback** (`describe`) — a11y + screenshot + text in one call
|
|
1314
1485
|
- [x] **Annotated screenshots** — @N badge overlays on interactive elements
|
|
1315
1486
|
- [x] **Auto-wait** — MutationObserver-based, 5s automatic element waiting
|
|
1316
1487
|
- [x] **`wait-for-text`** — adaptive text-based waiting (subtree + characterData) for streaming AI responses, async toasts, and selector-less synchronization
|
|
1317
1488
|
- [x] **`eval-batch`** — run N JS expressions in 1 CDP roundtrip (5-30x speedup vs sequential `eval`)
|
|
1318
|
-
- [x] **`block`** — request blocking via `Network.setBlockedURLs` with built-in presets (images/fonts/ads/media)
|
|
1489
|
+
- [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)
|
|
1319
1490
|
- [x] **`dismiss`** — heuristic auto-click for LLM chat sign-up walls (EN+TR pattern library, destructive-action guards)
|
|
1320
1491
|
- [x] **`adaptive`** — auto-escalate to stealth on CAPTCHA-protected hosts, persistent per-host memory ("run fast, climb walls")
|
|
1321
1492
|
- [x] **`cookies save/load`** — export/import cookies as JSON (replay CF/DataDome clearance across runs)
|