cdpilot 0.9.1 → 0.9.3
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 +183 -9
- package/bin/cdpilot.js +86 -7
- package/package.json +1 -1
- package/src/cdpilot.py +2835 -138
package/README.md
CHANGED
|
@@ -124,7 +124,7 @@ See [CHANGELOG.md](CHANGELOG.md) for the full list with rationale.
|
|
|
124
124
|
### Navigation & Content
|
|
125
125
|
|
|
126
126
|
```bash
|
|
127
|
-
cdpilot go <url> # Navigate to URL
|
|
127
|
+
cdpilot go <url> # Navigate to URL (alias: open <url>)
|
|
128
128
|
cdpilot content # Get page text content
|
|
129
129
|
cdpilot html # Get page HTML
|
|
130
130
|
cdpilot shot [file] # Take screenshot (PNG)
|
|
@@ -144,6 +144,76 @@ cdpilot scroll-to <selector> # Scroll element into view
|
|
|
144
144
|
cdpilot drag <from> <to> # Drag and drop
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
+
### Element targeting inside iframes
|
|
148
|
+
|
|
149
|
+
Card forms (Stripe/iyzico-style), embedded login widgets and the reCAPTCHA
|
|
150
|
+
checkbox live inside `<iframe>`s. Name the frame chain before the element
|
|
151
|
+
selector with `>>>`, or pass `--frame`:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# Payment-style page: the card fields are inside <iframe id="card" title="Secure card payment">
|
|
155
|
+
cdpilot fill "iframe#card >>> input[name=cardnumber]" "4242424242424242"
|
|
156
|
+
cdpilot type --frame "#card" "input[name=cvc]" "123"
|
|
157
|
+
cdpilot click "iframe[title='Secure card payment'] >>> button#pay-btn"
|
|
158
|
+
|
|
159
|
+
# Nested frames: outermost first
|
|
160
|
+
cdpilot click "iframe.checkout >>> iframe.card >>> button"
|
|
161
|
+
|
|
162
|
+
# --frame <selector|index|url-substring>, nestable with >>>
|
|
163
|
+
cdpilot click --frame 0 "button.submit" # first iframe (same order as `frame list`)
|
|
164
|
+
cdpilot fill --frame "url=js.stripe.com" "input[name=cardnumber]" "4242424242424242"
|
|
165
|
+
cdpilot frame list --frame "#card" # iframes inside that frame
|
|
166
|
+
cdpilot frame eval --frame "#card" "document.title" # runs in the frame's own page context
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
- Works for `click`, `fill`, `type`, `submit`, `hover`, `dblclick`, `rightclick`,
|
|
170
|
+
`smart-click`, `smart-fill`, `smart-select` and `frame list|eval|shadow`.
|
|
171
|
+
- A frame hop is a CSS selector, a 0-based index, a `name`/`id`, or
|
|
172
|
+
`url=<src substring>`. `--frame` also takes a bare `src` substring.
|
|
173
|
+
`>>>` inside quotes or `[...]` is literal. A selector that matches an
|
|
174
|
+
element *wrapping* an iframe (Stripe's `#card-element`) enters the iframe
|
|
175
|
+
inside it and says so on stderr:
|
|
176
|
+
`note: '#card-element' is not an iframe; using the iframe inside it`.
|
|
177
|
+
- 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
|
|
179
|
+
(`click "Next >>>"`), the whole string is used as written, exactly as
|
|
180
|
+
before. If that fails too, one stderr line explains:
|
|
181
|
+
`note: 'Home' matched no iframe; used the selector as written`.
|
|
182
|
+
`--frame` never falls back: a frame it cannot find is an error (exit 1).
|
|
183
|
+
- For `smart-click` / `smart-fill` / `smart-select` the part before the first
|
|
184
|
+
`>>>` is a frame only if it looks like a selector (it has `#`, `.` or `[`,
|
|
185
|
+
or starts with an `iframe`/`frame` tag) or is `url=…`; words stay text.
|
|
186
|
+
`smart-click "Main >>> Settings"` clicks the page's "Main >>> Settings"
|
|
187
|
+
link even when a `<main>` element holds an iframe. For a frame index or a
|
|
188
|
+
bare name use `--frame`.
|
|
189
|
+
- Same-origin **and cross-origin** frames work, including out-of-process
|
|
190
|
+
iframes (site isolation): cdpilot resolves each hop over CDP
|
|
191
|
+
(`DOM.describeNode` → frame id → the frame's execution context, or
|
|
192
|
+
`Target.attachToTarget` with a flat session for an out-of-process frame),
|
|
193
|
+
never through `contentDocument`, which the browser blocks cross-origin.
|
|
194
|
+
- Real mouse input (`hover`, `dblclick`, `rightclick`, `--entropy=on` clicks)
|
|
195
|
+
is dispatched at page coordinates: the frame's offset in the page is added
|
|
196
|
+
automatically, so the browser hit-tests into the right frame.
|
|
197
|
+
- `smart-click` / `smart-fill` / `smart-select` look in the page first and
|
|
198
|
+
compare texts with whitespace collapsed (` ` and line breaks count as
|
|
199
|
+
one space). An enabled page element whose text or label contains the whole
|
|
200
|
+
query is used. If the page's only such elements are disabled, the command
|
|
201
|
+
fails as before (`no enabled element matches …`) without looking in frames.
|
|
202
|
+
Otherwise the visible frames are searched breadth-first for a whole-query
|
|
203
|
+
match (at most 20 frames, and 2 s for all of the search's CDP calls) and the
|
|
204
|
+
match is reported on stderr:
|
|
205
|
+
`smart-click: matched inside frame iframe#card (https://…)`. The search only
|
|
206
|
+
looks (no click, no typing); the command then acts once, in the chosen
|
|
207
|
+
frame. With no frame match, the page's partial (word) match is used as
|
|
208
|
+
before. When the 2 s run out:
|
|
209
|
+
`smart-click: frame search stopped after 2s (3 of 7 frames)`; a frame that
|
|
210
|
+
had not answered by then has not been touched.
|
|
211
|
+
- `smart-fill` and `smart-select` search automatically only frames of the
|
|
212
|
+
page's own origin, so a typed value never lands in a third-party frame (an
|
|
213
|
+
ad, chat or payment widget) by accident; reach a cross-origin frame with
|
|
214
|
+
`>>>` or `--frame`. `smart-click` searches all visible frames.
|
|
215
|
+
- `frame list` without `--frame` prints the same list as before.
|
|
216
|
+
|
|
147
217
|
### Debugging
|
|
148
218
|
|
|
149
219
|
```bash
|
|
@@ -308,7 +378,8 @@ cdpilot storage # localStorage contents
|
|
|
308
378
|
cdpilot upload <sel> <file> # Upload file to input
|
|
309
379
|
cdpilot multi-eval <js> # Execute JS in all tabs
|
|
310
380
|
cdpilot headless [on|off] # Toggle headless mode
|
|
311
|
-
cdpilot frame list # List iframes
|
|
381
|
+
cdpilot frame list # List iframes (index = --frame <index>)
|
|
382
|
+
cdpilot frame eval --frame <f> <js> # Run JS inside an iframe (cross-origin too)
|
|
312
383
|
cdpilot dialog auto-accept # Auto-accept dialogs
|
|
313
384
|
cdpilot permission grant geo # Grant geolocation
|
|
314
385
|
```
|
|
@@ -537,17 +608,32 @@ cdpilot profile warm # age the profile for reCAPTCHA v3 score
|
|
|
537
608
|
which nudges reCAPTCHA v3's behavioral score upward over time. Slow by
|
|
538
609
|
design — run it ahead of a session, not inline.
|
|
539
610
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
611
|
+
Public bot-detection panels, measured 2026-09-27, **headless** Brave (Chrome 154)
|
|
612
|
+
([method and raw results](.claude/docs/stealth-panel-olcumu-2026-09-27.md)):
|
|
613
|
+
|
|
614
|
+
| `mode` | bot.sannysoft.com (31 rows) | incolumitas intoli | incolumitas fpscanner | incolumitas new-tests |
|
|
615
|
+
|---|---|---|---|---|
|
|
616
|
+
| `regular` (no patches, default) | 28 pass / 3 fail | 5/6 | 17 ok / 3 fail / 1 warn | all ok |
|
|
617
|
+
| `stealth` | 31 pass | 6/6 | 19 ok / 1 fail / 1 warn | all ok |
|
|
618
|
+
| `undetected` | 31 pass | 6/6 | 19 ok / 1 fail / 1 warn | all ok |
|
|
619
|
+
|
|
620
|
+
- The remaining fpscanner fail is `'webdriver' in navigator`, which is true in every modern
|
|
621
|
+
Chrome; faking it away would make cdpilot the odd one out.
|
|
622
|
+
- `regular`'s fails all come from the `HeadlessChrome` user agent. `stealth`/`undetected`
|
|
623
|
+
rewrite it for the page load that `go` starts; a later, separate command in the same page
|
|
624
|
+
can still read `HeadlessChrome`.
|
|
625
|
+
- new-tests: every key is ok; `connectionRTT` is reported as unknown in all tiers.
|
|
626
|
+
- nowsecure.nl and areyouheadless could not be measured (a fixed always-interactive
|
|
627
|
+
Turnstile test key; HTTP 502).
|
|
628
|
+
|
|
629
|
+
Earlier figures on this page (sannysoft 24/24, intoli 6/6) were measured on v0.4.x in April
|
|
630
|
+
2026; they are replaced by the table above.
|
|
545
631
|
|
|
546
632
|
### Reliability
|
|
547
633
|
|
|
548
634
|
```bash
|
|
549
635
|
cdpilot browser [name|auto] # workload-aware browser selection
|
|
550
|
-
cdpilot health # JSON: alive, port, tabs, browser, today's crashes
|
|
636
|
+
cdpilot health # JSON: alive, port, tabs, browser, today's crashes, idle close
|
|
551
637
|
```
|
|
552
638
|
|
|
553
639
|
`cdpilot health` is designed for shell watchdogs:
|
|
@@ -559,6 +645,89 @@ until cdpilot health >/dev/null; do cdpilot launch; sleep 2; done
|
|
|
559
645
|
Surfaces today's Brave crash count from `~/Library/Logs/DiagnosticReports/`
|
|
560
646
|
on macOS — spot degradation before your automation silently stalls.
|
|
561
647
|
|
|
648
|
+
**Auto-launch.** A page command (`go`, `content`, `click`, `shot`, …) that finds
|
|
649
|
+
the browser not running starts it the same way `cdpilot launch` does (same
|
|
650
|
+
profile, same headless/visible setting), then carries on, printing one line to
|
|
651
|
+
stderr: `cdpilot: browser was not running — launched it (CDPILOT_NO_AUTOLAUNCH=1 to disable)`.
|
|
652
|
+
Lifecycle, status and configuration commands (`launch`, `stop`, `close`,
|
|
653
|
+
`close-tab`, `stop-all`, `project-stop`, `session-close`, `status`, `health`,
|
|
654
|
+
`tabs`, `sessions`, `projects`, `headless`, `proxy`, `browser`, `extensions`,
|
|
655
|
+
`mcp`, `serve`, …) never launch. `CDPILOT_NO_AUTOLAUNCH=1` restores the old
|
|
656
|
+
"CDP connection error. Is the browser running?" error and exit code 1.
|
|
657
|
+
|
|
658
|
+
**Idle auto-close.** A browser that a page command auto-launched, or that the
|
|
659
|
+
MCP server launched (`browser_launch` included), closes itself after
|
|
660
|
+
**15 minutes** without a cdpilot command or a visible page change, so a
|
|
661
|
+
finished agent task does not leave it holding memory. An explicit CLI
|
|
662
|
+
`cdpilot launch` stays open (you may be browsing in it by hand) unless you ask:
|
|
663
|
+
`cdpilot launch --idle-close 30` or `CDPILOT_IDLE_CLOSE=30`. The env var also
|
|
664
|
+
sets the auto-launch delay (read when the browser starts; fractions allowed);
|
|
665
|
+
`0` turns idle close off. A browser you started yourself, or one cdpilot merely
|
|
666
|
+
attached to, is never closed.
|
|
667
|
+
|
|
668
|
+
What counts as use: any cdpilot command except the read-only checks (`status`,
|
|
669
|
+
`health`, `projects`, `version` — a `cdpilot health` watchdog loop does not keep
|
|
670
|
+
the browser alive), MCP tool calls, `serve` requests, a CDP client still
|
|
671
|
+
attached to a page (a long-running command, `watch`, Playwright via
|
|
672
|
+
`connectOverCDP`), and any change in the open pages' URLs or in the set of
|
|
673
|
+
tabs (someone navigating, a tab opened or closed). Title changes do not count,
|
|
674
|
+
so a page that rewrites its own title (a clock, an unread counter) cannot keep
|
|
675
|
+
the browser alive. `cdpilot status` and
|
|
676
|
+
`cdpilot health` show `idle close in 12m` or `idle close off`.
|
|
677
|
+
|
|
678
|
+
How: every command stamps `~/.cdpilot/projects/<id>/last-activity`; the launch
|
|
679
|
+
starts a small detached watcher (one per port, no console window on Windows)
|
|
680
|
+
that checks every ≤30 s, stops the browser like `cdpilot stop`, marks it
|
|
681
|
+
stopped in the registry and exits — it also exits as soon as the browser is
|
|
682
|
+
gone for any other reason. Browsers started by `serve --api` are managed by the
|
|
683
|
+
server and have no idle close.
|
|
684
|
+
|
|
685
|
+
**Timeouts.** Any command takes `--timeout <seconds>`, before or after the
|
|
686
|
+
command name, or a default from `CDPILOT_TIMEOUT` (the flag wins; `0`
|
|
687
|
+
disables). It bounds the whole command's wall-clock: on expiry cdpilot prints
|
|
688
|
+
`cdpilot: timed out after <N>s (<command>)` to stderr, kills the child processes
|
|
689
|
+
it started and exits with code **124** (like GNU `timeout`). A browser that was
|
|
690
|
+
already up and registered is left running.
|
|
691
|
+
|
|
692
|
+
```bash
|
|
693
|
+
cdpilot --timeout 10 click "#submit" # before the command...
|
|
694
|
+
cdpilot shot page.png --timeout 30 # ...or after it
|
|
695
|
+
CDPILOT_TIMEOUT=60 cdpilot run flow.cdp # default for every command (and each script line)
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
For `mcp` and `serve` the value is not applied to the long-running server
|
|
699
|
+
itself; it is passed on to every tool call / request it runs.
|
|
700
|
+
|
|
701
|
+
### Session log
|
|
702
|
+
|
|
703
|
+
Every command (and every MCP tool call) appends one JSON line to a local,
|
|
704
|
+
per-project log, so when a browser task is done there is a record of what was
|
|
705
|
+
done and found. Nothing leaves the machine.
|
|
706
|
+
|
|
707
|
+
```bash
|
|
708
|
+
cdpilot log # today's commands: time, exit, command, url, result
|
|
709
|
+
cdpilot log --md # Markdown report: pages visited, actions, errors, files produced
|
|
710
|
+
cdpilot log --json # raw lines (one JSON object per command)
|
|
711
|
+
cdpilot log --days 3 # include the last 3 days
|
|
712
|
+
cdpilot log --path # where the files are
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
Each line has `ts`, `cmd`, `args`, `exit`, `duration_ms`, the page `url` and
|
|
716
|
+
`title` after the command (when the command already had them), a ~200-char
|
|
717
|
+
`summary` of its output, the `error` line, and `files` it wrote (screenshots,
|
|
718
|
+
PDFs). Files live in `~/.cdpilot/projects/<project-id>/log/<YYYY-MM-DD>.jsonl`.
|
|
719
|
+
|
|
720
|
+
Redaction happens before anything is written: values given to `fill`, `type`,
|
|
721
|
+
`smart-fill`, `smart-select`, `assert-value` and `dialog prompt` become
|
|
722
|
+
`«redacted:N chars»`; so do values of password/token/key/secret/cookie/auth
|
|
723
|
+
flags and headers, token-shaped arguments, and URL query/fragment values whose
|
|
724
|
+
names contain token, key, secret, password, auth, code or session. `cookies`
|
|
725
|
+
and `storage` output is never logged; `eval` source is, with string literals
|
|
726
|
+
over 40 chars cut and secret-looking ones replaced. Logging is best effort: it
|
|
727
|
+
never changes a command's output or exit code, and a failed write costs one
|
|
728
|
+
stderr line. `CDPILOT_LOG=0` turns it off; `CDPILOT_LOG_DAYS` (default 14)
|
|
729
|
+
sets how many days are kept. The MCP server exposes it as `browser_log`.
|
|
730
|
+
|
|
562
731
|
### Scaling & Workstation Use
|
|
563
732
|
|
|
564
733
|
```bash
|
|
@@ -620,6 +789,11 @@ print(result.stdout)
|
|
|
620
789
|
| `BROWSER_SESSION` | Auto | Session identifier |
|
|
621
790
|
| `CDPILOT_MODE` | `regular` | Stealth tier override (`regular`/`stealth`/`undetected`) |
|
|
622
791
|
| `CDPILOT_OFFSCREEN` | `0` | Headed but render off-screen — no window steals focus |
|
|
792
|
+
| `CDPILOT_TIMEOUT` | unset | Default `--timeout` in seconds for every command (flag wins, `0` disables); expiry exits 124 |
|
|
793
|
+
| `CDPILOT_NO_AUTOLAUNCH` | `0` | `1` = page commands fail with the old "Is the browser running?" error instead of launching the browser |
|
|
794
|
+
| `CDPILOT_IDLE_CLOSE` | `15` | Minutes without a cdpilot command or page change before a browser cdpilot launched closes itself (fractions allowed, `0` = never; read at launch). Default applies to auto-launch and MCP launches; an explicit CLI `launch` is off unless this is set or `--idle-close <min>` is given |
|
|
795
|
+
| `CDPILOT_LOG` | `1` | `0` = do not write the session log (`cdpilot log`) |
|
|
796
|
+
| `CDPILOT_LOG_DAYS` | `14` | Days of session log to keep; older day files are deleted on the first write of a day (`0` keeps all) |
|
|
623
797
|
|
|
624
798
|
## How It Works
|
|
625
799
|
|
|
@@ -780,10 +954,10 @@ The only browser MCP with built-in test assertions. Here's what we've shipped an
|
|
|
780
954
|
- [x] **Data extraction** (`extract`) — structured DOM data in text, JSON, or list format
|
|
781
955
|
- [x] **Page observation** (`observe`) — list all interactive elements with available actions
|
|
782
956
|
- [x] **Script runner** (`run`) — execute `.cdp` script files with pass/fail reporting
|
|
957
|
+
- [x] **iframe targeting** — `"iframe#card >>> input"` / `--frame` for element commands, same-origin and cross-origin (out-of-process) frames; smart commands search frames automatically
|
|
783
958
|
|
|
784
959
|
### Coming Soon
|
|
785
960
|
|
|
786
|
-
- [ ] **iframe** support — interact with elements inside iframes (Shadow DOM traversal already shipped in smart commands)
|
|
787
961
|
- [ ] **Multi-instance pool** (`CDPILOT_POOL_SIZE`) — N independent browser processes with least-loaded dispatch
|
|
788
962
|
- [ ] **Session recording & replay** — record browser sessions and replay them deterministically
|
|
789
963
|
- [ ] **Stealth mode** *(Pro)* — human-like mouse/typing, anti-fingerprint, CAPTCHA solving
|
package/bin/cdpilot.js
CHANGED
|
@@ -242,7 +242,8 @@ function runStatus() {
|
|
|
242
242
|
console.log(` ✓ Connected`);
|
|
243
243
|
console.log(` Browser: ${info.Browser || 'Unknown'}`);
|
|
244
244
|
console.log(` Protocol: ${info['Protocol-Version'] || 'Unknown'}`);
|
|
245
|
-
console.log(` WebSocket: ${info.webSocketDebuggerUrl || 'N/A'}
|
|
245
|
+
console.log(` WebSocket: ${info.webSocketDebuggerUrl || 'N/A'}`);
|
|
246
|
+
console.log(` ${idleCloseLabel(port)}\n`);
|
|
246
247
|
} catch {
|
|
247
248
|
console.log(' ✓ CDP responding but version info unavailable\n');
|
|
248
249
|
}
|
|
@@ -262,6 +263,33 @@ function runStatus() {
|
|
|
262
263
|
}
|
|
263
264
|
}
|
|
264
265
|
|
|
266
|
+
// Idle auto-close countdown, from the files src/cdpilot.py keeps (see
|
|
267
|
+
// _idle_status there): CDPILOT_HOME/idle/<port>.json names the watcher and its
|
|
268
|
+
// minutes, CDPILOT_HOME/projects/<id>/last-activity the last command.
|
|
269
|
+
function idleCloseLabel(port) {
|
|
270
|
+
const home = process.env.CDPILOT_HOME || path.join(os.homedir(), '.cdpilot');
|
|
271
|
+
try {
|
|
272
|
+
const st = JSON.parse(fs.readFileSync(path.join(home, 'idle', `${port}.json`), 'utf-8'));
|
|
273
|
+
const minutes = Number(st.minutes) || 0;
|
|
274
|
+
if (minutes <= 0 || !st.pid) return 'idle close off';
|
|
275
|
+
try {
|
|
276
|
+
process.kill(st.pid, 0); // existence probe; safe on Windows in Node
|
|
277
|
+
} catch (e) {
|
|
278
|
+
if (e.code !== 'EPERM') return 'idle close off';
|
|
279
|
+
}
|
|
280
|
+
let last = Number(st.started) || Date.now() / 1000;
|
|
281
|
+
try {
|
|
282
|
+
const file = path.join(home, 'projects', String(st.project_id), 'last-activity');
|
|
283
|
+
const t = parseFloat(fs.readFileSync(file, 'utf-8'));
|
|
284
|
+
if (t > last) last = t;
|
|
285
|
+
} catch {}
|
|
286
|
+
const left = Math.max(0, Math.floor(minutes * 60 - (Date.now() / 1000 - last)));
|
|
287
|
+
return `idle close in ${Math.ceil(left / 60)}m`;
|
|
288
|
+
} catch {
|
|
289
|
+
return 'idle close off';
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
265
293
|
// ── Version ──
|
|
266
294
|
|
|
267
295
|
function showVersion() {
|
|
@@ -321,18 +349,29 @@ function showHelp() {
|
|
|
321
349
|
cdpilot v${VERSION} — Zero-dependency browser automation
|
|
322
350
|
|
|
323
351
|
USAGE
|
|
324
|
-
cdpilot <command> [args]
|
|
352
|
+
cdpilot [--timeout <s>] <command> [args]
|
|
353
|
+
|
|
354
|
+
GLOBAL OPTIONS
|
|
355
|
+
--timeout <s> Abort the command after <s> seconds (exit 124). Before or
|
|
356
|
+
after the command; env CDPILOT_TIMEOUT sets a default
|
|
357
|
+
(the flag wins, 0 disables).
|
|
358
|
+
Page commands start the browser if it is not running
|
|
359
|
+
(set CDPILOT_NO_AUTOLAUNCH=1 to get an error instead).
|
|
360
|
+
An auto-launched (or MCP-launched) browser closes after 15 min without a
|
|
361
|
+
cdpilot command or page change (CDPILOT_IDLE_CLOSE=<minutes>,
|
|
362
|
+
0 = never); an explicit launch stays open unless asked (below).
|
|
325
363
|
|
|
326
364
|
SETUP
|
|
327
365
|
setup Auto-detect browser, create isolated profile
|
|
328
|
-
launch
|
|
366
|
+
launch [--idle-close <min>] Start browser with CDP enabled (--idle-close or
|
|
367
|
+
CDPILOT_IDLE_CLOSE: close it after <min> idle minutes)
|
|
329
368
|
status Check browser connection
|
|
330
369
|
stop [--smart] Stop browser (--smart = close owned tabs, quit if empty)
|
|
331
370
|
close [--force|--keep] Smart close: close cdpilot's tabs; quit browser only
|
|
332
371
|
if no user tabs remain (--force quits anyway, --keep never quits)
|
|
333
372
|
|
|
334
373
|
NAVIGATION
|
|
335
|
-
go <url> Navigate to URL
|
|
374
|
+
go <url> Navigate to URL (alias: open)
|
|
336
375
|
content Get page text content
|
|
337
376
|
html Get page HTML
|
|
338
377
|
shot [file] Take screenshot
|
|
@@ -345,6 +384,14 @@ function showHelp() {
|
|
|
345
384
|
submit <form> Submit form
|
|
346
385
|
hover <sel> Hover element
|
|
347
386
|
keys <combo> Keyboard shortcut
|
|
387
|
+
Inside an iframe: click "iframe#card >>> input[name=cardnumber]"
|
|
388
|
+
(nest: "iframe.a >>> iframe.b >>> button") or
|
|
389
|
+
--frame <selector|index|url-substring>; same-origin and
|
|
390
|
+
cross-origin frames. With no match in the page,
|
|
391
|
+
smart-click searches all frames, smart-fill and
|
|
392
|
+
smart-select only frames of the page's origin.
|
|
393
|
+
frame list|eval [--frame <f>]
|
|
394
|
+
List iframes / run JS inside a frame
|
|
348
395
|
|
|
349
396
|
DEBUGGING
|
|
350
397
|
console [url] Capture console logs
|
|
@@ -423,6 +470,16 @@ function showHelp() {
|
|
|
423
470
|
AI AGENT
|
|
424
471
|
mcp Start MCP server (stdin/stdout JSON-RPC)
|
|
425
472
|
|
|
473
|
+
SESSION LOG (always on, local, redacted)
|
|
474
|
+
log Today's commands for this project: time, exit, command,
|
|
475
|
+
url, result
|
|
476
|
+
log --md Markdown report (pages visited, actions, errors, files
|
|
477
|
+
produced) to paste into an issue or PR
|
|
478
|
+
log --json Raw JSON lines · log --days N · log --path (log directory)
|
|
479
|
+
Typed values, secret-looking args and token/key/secret URL
|
|
480
|
+
params are redacted. CDPILOT_LOG=0 turns it off;
|
|
481
|
+
CDPILOT_LOG_DAYS (default 14) sets how many days are kept.
|
|
482
|
+
|
|
426
483
|
WATCH (continuous screencast for AI video understanding)
|
|
427
484
|
watch start <url> Begin JPEG screencast at N fps to a disk ring buffer
|
|
428
485
|
(default 10fps, 5min retention, 100MB cap). Background
|
|
@@ -590,8 +647,23 @@ if (process.argv.includes('--internal-test-runner')) {
|
|
|
590
647
|
return; // runAll() is async, this exits via process.exit
|
|
591
648
|
}
|
|
592
649
|
|
|
650
|
+
// Global flags may precede the command name (`cdpilot --timeout 10 click "#x"`).
|
|
651
|
+
// src/cdpilot.py parses and validates them (it is also run directly, without
|
|
652
|
+
// this launcher); here they only have to be skipped to find the command that
|
|
653
|
+
// the launcher answers itself (help / --version / setup / status). args are
|
|
654
|
+
// passed to Python unchanged.
|
|
655
|
+
function commandIndex(argv) {
|
|
656
|
+
let i = 0;
|
|
657
|
+
while (i < argv.length) {
|
|
658
|
+
if (argv[i] === '--timeout') { i += 2; continue; }
|
|
659
|
+
if (argv[i].startsWith('--timeout=')) { i += 1; continue; }
|
|
660
|
+
break;
|
|
661
|
+
}
|
|
662
|
+
return i;
|
|
663
|
+
}
|
|
664
|
+
|
|
593
665
|
const args = process.argv.slice(2);
|
|
594
|
-
const cmd = args[
|
|
666
|
+
const cmd = args[commandIndex(args)];
|
|
595
667
|
|
|
596
668
|
if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
597
669
|
showHelp();
|
|
@@ -650,7 +722,14 @@ if (cmd === 'status') {
|
|
|
650
722
|
env,
|
|
651
723
|
});
|
|
652
724
|
|
|
653
|
-
child.
|
|
654
|
-
|
|
725
|
+
// A child killed by a signal reports code === null. `code || 0` turned that
|
|
726
|
+
// into success, so an OOM-killed or SIGKILLed run looked fine to scripts
|
|
727
|
+
// and agents. Use the shell convention 128 + signal number instead.
|
|
728
|
+
child.on('close', (code, signal) => {
|
|
729
|
+
if (code === null && signal) {
|
|
730
|
+
console.error(`cdpilot: python was terminated by ${signal}`);
|
|
731
|
+
process.exit(128 + (os.constants.signals[signal] || 0));
|
|
732
|
+
}
|
|
733
|
+
process.exit(code ?? 0);
|
|
655
734
|
});
|
|
656
735
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cdpilot",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"mcpName": "io.github.mehmetnadir/cdpilot",
|
|
5
5
|
"description": "Zero-dependency browser automation CLI. Raw CDP, 70+ commands, friction ladder, 3-tier stealth, CAPTCHA + press-and-hold solvers, proxy pools, MCP server. No Puppeteer/Playwright/Selenium.",
|
|
6
6
|
"bin": {
|