cdpilot 0.5.0 → 0.8.0

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.
Files changed (4) hide show
  1. package/README.md +147 -5
  2. package/bin/cdpilot.js +149 -0
  3. package/package.json +40 -9
  4. package/src/cdpilot.py +5767 -1600
package/README.md CHANGED
@@ -213,10 +213,34 @@ cdpilot throttle slow3g # Simulate slow 3G
213
213
  cdpilot throttle fast3g # Simulate fast 3G
214
214
  cdpilot throttle offline # Go offline
215
215
  cdpilot throttle off # Back to normal
216
- cdpilot proxy <url> # Set proxy
216
+ cdpilot proxy <url> # Set proxy (legacy single-URL form)
217
217
  cdpilot proxy off # Remove proxy
218
+
219
+ # v0.7.0: named pools (BrightData, IPRoyal, Anchor, etc.)
220
+ cdpilot proxy add brd "http://USER:PASS@brd.superproxy.io:22225" --geo us
221
+ cdpilot proxy add ipr "http://USER:PASS@geo.iproyal.com:12321" --sticky
222
+ cdpilot proxy use brd # Activate one pool
223
+ cdpilot proxy list # Show pools (credentials redacted)
224
+ cdpilot proxy show [<name>] # Active or named pool URL (redacted)
225
+ cdpilot proxy remove <name> # Drop a pool
226
+ ```
227
+
228
+ ### TLS Fingerprint (v0.8.0)
229
+
230
+ ```bash
231
+ cdpilot tls-check # Probe JA3/JA4/H2 via tls.peet.ws
232
+ cdpilot tls-check --service browserleaks # Alternate echo
233
+ cdpilot tls-check --json # Raw JSON
218
234
  ```
219
235
 
236
+ **Known limitation:** v0.8.0 ships the **probe** (`tls-check`) but no in-tree TLS fix.
237
+ There is no Chromium-based TLS-corrected browser that ships as a standalone binary
238
+ exposing `--remote-debugging-port`. Camoufox is Firefox+Juggler (no CDP);
239
+ Patchright / undetected-chromedriver / nodriver are Python/Playwright libraries,
240
+ not standalone browsers. cdpilot's CDP-only architecture is incompatible with all
241
+ of them without a protocol adapter. **Tracking:** v0.9 roadmap (TLS-MITM plugin
242
+ using curl-impersonate semantics, OR BoringSSL-patched Chromium fork).
243
+
220
244
  ### Request Interception
221
245
 
222
246
  ```bash
@@ -264,10 +288,22 @@ cdpilot session-close [id] # Close session
264
288
  ### Advanced
265
289
 
266
290
  ```bash
267
- cdpilot cookies [domain] # List cookies (filter by domain)
268
- cdpilot cookies save <file> [<domain>]
269
- # Export cookies as JSON (optional domain filter)
270
- cdpilot cookies load <file> # Import cookies (replay CF clearance across runs)
291
+ cdpilot cookies [domain] # List cookies (filter by domain)
292
+ cdpilot cookies save <file> [<domain>] # Export cookies as JSON
293
+ cdpilot cookies load <file> # Import cookies (replay CF clearance)
294
+ cdpilot cookies save --host x.com # Save to per-host cache
295
+ cdpilot cookies load --host x.com # Load from per-host cache
296
+ cdpilot cookies list # Cached hosts + age + CF clearance flag
297
+ cdpilot cookies clear --host x.com # Remove one host
298
+ cdpilot cookies clear --all # Wipe entire cache
299
+ cdpilot cookies clear --older-than 7d # Remove stale entries
300
+ cdpilot cookies auto on # Toggle global auto-save/replay flag (v0.6.1: requires safe-list)
301
+ cdpilot cookies auto add <host> # Opt host into auto save/replay (v0.6.1)
302
+ cdpilot cookies auto remove <host> # Remove host from safe-list
303
+ cdpilot cookies auto list # Enable flag + current safe-list
304
+ cdpilot cookies cf-replay <url> # Inject cached CF clearance before nav
305
+ cdpilot wipe [--cookies|--storage|--tabs|--keep h1,h2]
306
+ # v0.6.2: per-task state hygiene (cross-task contamination)
271
307
  cdpilot storage # localStorage contents
272
308
  cdpilot upload <sel> <file> # Upload file to input
273
309
  cdpilot multi-eval <js> # Execute JS in all tabs
@@ -338,6 +374,41 @@ cdpilot adaptive clear # Drop the stealth host memory entirely
338
374
  > when it sees one — adds the host to a persistent list, retries once with
339
375
  > stealth on. Never auto-demotes. Conservative by design.
340
376
 
377
+ ### Captcha Solver Plugins (v0.6+)
378
+
379
+ Optional integration with 2captcha, anti-captcha, and capmonster. Per-solve
380
+ cost ~$0.001–0.003. API keys stored in `~/.cdpilot/captcha-providers.json`
381
+ (chmod 600) — **never committed to git**.
382
+
383
+ ```bash
384
+ # One-time setup
385
+ cdpilot captcha config --provider 2captcha --api-key YOUR_KEY
386
+ cdpilot captcha config --provider anticaptcha --api-key YOUR_KEY # fallback
387
+
388
+ # Enable auto-solve (adaptive layer auto-solves on detect)
389
+ cdpilot captcha auto on
390
+
391
+ # Manual solve (debug / scripting)
392
+ cdpilot captcha solve --type recaptcha-v2 --site-key SK --url https://example.com
393
+ # returns: {"token": "03AGdBq2...", "duration_ms": 12500, "cost": 0.003, "provider": "2captcha"}
394
+
395
+ cdpilot captcha solve --type hcaptcha --site-key SK --url URL
396
+ cdpilot captcha solve --type turnstile --site-key SK --url URL
397
+ cdpilot captcha solve --type funcaptcha --site-key SK --url URL
398
+
399
+ # Status & balance
400
+ cdpilot captcha status # {"configured": [...], "preferred": "2captcha", "auto_enabled": true}
401
+ cdpilot captcha balance # {"2captcha": 1.23, "anticaptcha": 0.50}
402
+ ```
403
+
404
+ Supported types: `recaptcha-v2`, `recaptcha-v3`, `hcaptcha`, `turnstile`, `funcaptcha`
405
+
406
+ Tokens are injected via `Runtime.evaluate` CDP — no browser-side libraries required.
407
+ When `captcha auto on` is set, the adaptive layer detects and solves automatically
408
+ after each navigation. Without auto-on, detection still works but solving is manual.
409
+
410
+ > **Expected bench improvement (v0.6):** reCaptcha 2/6 → 5+/6, hCaptcha 2/3 → 3/3
411
+
341
412
  Verified against public bot-detection panels:
342
413
  - **bot.sannysoft.com:** 24/24 PASS (WebDriver, Chrome obj, Plugins as PluginArray, WebGL, PHANTOM_*, HEADCHR_*, SELENIUM_DRIVER)
343
414
  - **bot.incolumitas.com** intoli: 6/6 OK — new-tests: 6/7 OK (one FAIL = pure CDP presence, cannot be JS-patched)
@@ -422,6 +493,54 @@ print(result.stdout)
422
493
 
423
494
  **No Puppeteer. No Playwright. No Selenium.** Just direct CDP communication.
424
495
 
496
+ ## Stealth Bench V1
497
+
498
+ `cdpilot` is benchmarked against a suite of 80 high-friction web tasks to measure success rates against modern anti-bot systems. Gemini 2.5 Flash drives `cdpilot` as the controller; success is defined as full task completion without interception.
499
+
500
+ ### v0.5.3 Results (current)
501
+
502
+ | Category | Success / Total | Rate |
503
+ | :--- | :--- | :--- |
504
+ | Custom Antibot | 5 / 5 | 100.0% |
505
+ | Temu Slider | 1 / 1 | 100.0% |
506
+ | hCaptcha | 2 / 3 | 67.0% |
507
+ | Cloudflare | 12 / 22 | 55.0% |
508
+ | DataDome | 5 / 13 | 38.0% |
509
+ | reCaptcha | 2 / 6 | 33.0% |
510
+ | Akamai | 1 / 6 | 17.0% |
511
+ | PerimeterX | 2 / 18 | 11.0% |
512
+ | GeeTest | 0 / 4 | 0.0% |
513
+ | Shape | 0 / 1 | 0.0% |
514
+ | Kasada | 0 / 1 | 0.0% |
515
+ | **Total** | **30 / 80** | **37.5%** |
516
+
517
+ ### Version history
518
+
519
+ | Version | Mode | Total | Rate |
520
+ | :--- | :--- | :--- | :--- |
521
+ | v0.5.0 | Baseline (stealth off / adaptive off) | 30 / 80 | 37.5% |
522
+ | v0.5.0 | Stealth only (stealth on / adaptive off) | 32 / 80 | 40.0% |
523
+ | v0.5.0 | Full (stealth on / adaptive on) | 26 / 80 | 32.5% |
524
+ | v0.5.1 | Full — regression fix | 29 / 80 | 36.25% |
525
+ | v0.5.2 | Full — entropy auto-hook | 28 / 80 | 35.0% |
526
+ | v0.5.3 | Full — entropy scope tightened | 30 / 80 | 37.5% |
527
+ | v0.6.0 | + captcha solver + cookies-auto (regression) | 15 / 80 | 18.75% |
528
+ | **v0.8.0** | **Full — cookies safe-host scoped + per-task wipe (no proxy, no TLS fork)** | **29 / 80** | **36.25%** |
529
+ | v0.7.0 (slot) | + named proxy pools | _depends on user proxy_ | — |
530
+ | v0.8.0 (slot) | + TLS-aware launcher (camoufox/undetected-chrome) | _depends on browser choice_ | — |
531
+
532
+ **What cdpilot does not do:** `cdpilot` is an avoidance engine, not a CAPTCHA solver. We prioritize structural stealth (JS fingerprinting, behavioral entropy) to prevent challenges from appearing. When a CAPTCHA blocks progress and cannot be bypassed, 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; v0.7+ (residential proxy) and v0.8+ (TLS fingerprint correction via camoufox) target these directly.
533
+
534
+ ## Recommended Configuration
535
+
536
+ Based on Stealth Bench V1 results:
537
+
538
+ - **Speed-first** (most use cases): `cdpilot launch` — default browser behavior, no patches
539
+ - **Stealth-on** (RECOMMENDED, best overall): `cdpilot launch && cdpilot stealth on`
540
+ - **Full adaptive** (specific captcha-heavy workflows): `cdpilot launch && cdpilot stealth on && cdpilot adaptive on`
541
+
542
+ The full adaptive layer is bench-neutral vs baseline (30/80 vs 30/80) for Stealth Bench V1's task mix. Stealth-only (32/80 = 40%) is still the best-performing single variant. For your specific workload, profile both and pick.
543
+
425
544
  ## Comparison
426
545
 
427
546
  | Feature | cdpilot | Puppeteer | Playwright | Selenium |
@@ -453,6 +572,29 @@ Future paid offerings:
453
572
 
454
573
  Found a vulnerability? Please email the maintainer directly instead of opening a public issue.
455
574
 
575
+ ## Cookie Persistence (v0.6+)
576
+
577
+ Per-host cookie cache with auto-replay before navigation. Particularly useful for
578
+ sites with expensive challenges (Cloudflare, DataDome) — once passed, clearance
579
+ cookies (`cf_clearance`, `__cf_bm`) are cached and replayed on next visit.
580
+
581
+ ```bash
582
+ # Enable auto-mode — cookies saved/replayed on every navigate
583
+ cdpilot cookies auto on
584
+
585
+ # Manual per-host workflow
586
+ cdpilot cookies save --host x.com # cache current session cookies
587
+ cdpilot cookies load --host x.com # inject before navigating
588
+ cdpilot cookies cf-replay https://x.com # explicit CF clearance injection
589
+
590
+ # Inspect and clean
591
+ cdpilot cookies list # all cached hosts + age + CF flag
592
+ cdpilot cookies clear --older-than 7d # prune stale cache
593
+ ```
594
+
595
+ Storage: `~/.cdpilot/cookies/<host>/cookies.json` (chmod 600, never committed to git).
596
+ Expired cookies are filtered automatically on load.
597
+
456
598
  ## Roadmap
457
599
 
458
600
  The only browser MCP with built-in test assertions. Here's what we've shipped and what's next:
package/bin/cdpilot.js CHANGED
@@ -374,8 +374,157 @@ function showHelp() {
374
374
  `);
375
375
  }
376
376
 
377
+ // ── Internal Test Runner ──
378
+
379
+ function runInternalTestRunner(testFile, traceDir, traceMode, grepPattern) {
380
+ if (traceDir) {
381
+ fs.mkdirSync(path.join(traceDir, 'screenshots'), { recursive: true });
382
+ fs.mkdirSync(path.join(traceDir, 'a11y'), { recursive: true });
383
+ }
384
+
385
+ const metaPath = traceDir ? path.join(traceDir, 'meta.json') : null;
386
+ const stepsPath = traceDir && traceMode !== 'off' ? path.join(traceDir, 'steps.jsonl') : null;
387
+
388
+ const meta = { name: path.basename(testFile), started_at: new Date().toISOString(), status: 'running', tests: [] };
389
+ if (metaPath) fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2));
390
+
391
+ const testQueue = [];
392
+ global.test = (name, fn) => testQueue.push({ name, fn });
393
+
394
+ try {
395
+ require(path.resolve(testFile));
396
+ } catch (err) {
397
+ const out = { passed: 0, failed: 1, skipped: 0, tests: [{ name: testFile, status: 'failed', duration_ms: 0, error: err.message }] };
398
+ process.stdout.write(JSON.stringify(out) + '\n');
399
+ process.exit(1);
400
+ }
401
+
402
+ const results = { passed: 0, failed: 0, skipped: 0, tests: [] };
403
+ const cdpPort = process.env.CDP_PORT || '9222';
404
+ const SCRIPT = path.join(__dirname, '..', 'src', 'cdpilot.py');
405
+ const python = 'python3';
406
+ let stepIdx = 0;
407
+
408
+ const makeT = () => {
409
+ const runCmd = (cmd, ...cargs) => {
410
+ const padded = String(stepIdx).padStart(3, '0');
411
+ const step = { action: cmd + ' ' + cargs.join(' '), ts_ms: Date.now(), duration_ms: 0, error: null };
412
+ const t0 = Date.now();
413
+ try {
414
+ const quoted = cargs.map(a => JSON.stringify(String(a))).join(' ');
415
+ execSync(`${python} ${SCRIPT} ${cmd} ${quoted}`, {
416
+ stdio: 'pipe',
417
+ env: { ...process.env, CDP_PORT: cdpPort },
418
+ timeout: 30000,
419
+ });
420
+ step.duration_ms = Date.now() - t0;
421
+ if (stepsPath) fs.appendFileSync(stepsPath, JSON.stringify(step) + '\n');
422
+ // Screenshot after each step (best-effort — no browser = skipped)
423
+ if (traceDir && traceMode !== 'off') {
424
+ try {
425
+ const shotPath = path.join(traceDir, 'screenshots', `step-${padded}.png`);
426
+ execSync(`${python} ${SCRIPT} shot ${shotPath}`, { stdio: 'pipe', env: { ...process.env, CDP_PORT: cdpPort }, timeout: 10000 });
427
+ } catch (_) { /* no browser is OK in unit-style tests */ }
428
+ }
429
+ stepIdx++;
430
+ } catch (err) {
431
+ step.duration_ms = Date.now() - t0;
432
+ step.error = err.stderr ? err.stderr.toString().trim() : err.message;
433
+ if (stepsPath) fs.appendFileSync(stepsPath, JSON.stringify(step) + '\n');
434
+ stepIdx++;
435
+ throw new Error(`${cmd} failed: ${step.error}`);
436
+ }
437
+ };
438
+
439
+ const t = {
440
+ goto: (url) => runCmd('go', url),
441
+ click: (sel) => runCmd('click', sel),
442
+ fill: (sel, val) => runCmd('fill', sel, val),
443
+ type: (sel, val) => runCmd('type', sel, val),
444
+ hover: (sel) => runCmd('hover', sel),
445
+ screenshot: (p) => runCmd('shot', p),
446
+ eval: (js) => {
447
+ try {
448
+ const out = execSync(`${python} ${SCRIPT} eval ${JSON.stringify(js)}`, { stdio: 'pipe', env: { ...process.env, CDP_PORT: cdpPort }, timeout: 10000 });
449
+ return out.toString().trim();
450
+ } catch (e) { throw new Error('eval failed: ' + e.message); }
451
+ },
452
+ a11y: () => runCmd('a11y-snapshot'),
453
+ };
454
+
455
+ t.expect = (textOrSel) => runCmd('assert', textOrSel);
456
+ t.expect.url = (expected) => runCmd('assert-url', expected);
457
+ t.expect.visible = (sel) => runCmd('assert-visible', sel);
458
+ t.expect.hidden = (sel) => runCmd('assert-hidden', sel);
459
+
460
+ return t;
461
+ };
462
+
463
+ // Run tests sequentially (parallel is managed at the Python level across files)
464
+ const runAll = async () => {
465
+ for (const tst of testQueue) {
466
+ if (grepPattern && !tst.name.match(new RegExp(grepPattern, 'i'))) {
467
+ results.skipped++;
468
+ continue;
469
+ }
470
+ const t0 = Date.now();
471
+ const rec = { name: tst.name, status: 'passed', duration_ms: 0, error: null };
472
+ try {
473
+ await tst.fn(makeT());
474
+ rec.status = 'passed';
475
+ results.passed++;
476
+ } catch (err) {
477
+ rec.status = 'failed';
478
+ rec.error = err.message;
479
+ results.failed++;
480
+ }
481
+ rec.duration_ms = Date.now() - t0;
482
+ results.tests.push(rec);
483
+ if (metaPath) {
484
+ meta.tests.push(rec);
485
+ meta.status = 'running';
486
+ fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2));
487
+ }
488
+ }
489
+
490
+ // Finalize meta
491
+ if (metaPath) {
492
+ meta.status = results.failed > 0 ? 'failed' : 'passed';
493
+ meta.passed = results.passed;
494
+ meta.failed = results.failed;
495
+ meta.skipped = results.skipped;
496
+ meta.ended_at = new Date().toISOString();
497
+ fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2));
498
+ }
499
+
500
+ process.stdout.write(JSON.stringify(results) + '\n');
501
+ process.exit(results.failed > 0 ? 1 : 0);
502
+ };
503
+
504
+ runAll().catch(err => {
505
+ process.stderr.write('Test runner error: ' + err.message + '\n');
506
+ process.exit(1);
507
+ });
508
+ }
509
+
377
510
  // ── Main ──
378
511
 
512
+ // Internal test runner mode — intercept before normal CLI dispatch
513
+ if (process.argv.includes('--internal-test-runner')) {
514
+ const idx = process.argv.indexOf('--internal-test-runner');
515
+ const testFile = process.argv[idx + 1];
516
+ const traceDirArg = process.argv.find(a => a.startsWith('--trace-dir='));
517
+ const traceArg = process.argv.find(a => a.startsWith('--trace='));
518
+ const grepArg = process.argv.find(a => a.startsWith('--grep='));
519
+ runInternalTestRunner(
520
+ testFile,
521
+ traceDirArg ? traceDirArg.split('=').slice(1).join('=') : null,
522
+ traceArg ? traceArg.split('=')[1] : 'default',
523
+ grepArg ? grepArg.split('=').slice(1).join('=') : null,
524
+ );
525
+ return; // runAll() is async, this exits via process.exit
526
+ }
527
+
379
528
  const args = process.argv.slice(2);
380
529
  const cmd = args[0];
381
530
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cdpilot",
3
- "version": "0.5.0",
4
- "description": "Zero-dependency browser automation from your terminal. One command, full control.",
3
+ "version": "0.8.0",
4
+ "description": "Zero-dependency browser automation CLI. Raw CDP, 60+ commands, stealth + adaptive escalation, CAPTCHA solvers, proxy pools, TLS probe, MCP server. No Puppeteer/Playwright/Selenium.",
5
5
  "bin": {
6
6
  "cdpilot": "./bin/cdpilot.js",
7
7
  "bctl": "./bin/cdpilot.js"
@@ -11,12 +11,38 @@
11
11
  "start": "node bin/cdpilot.js"
12
12
  },
13
13
  "keywords": [
14
- "browser-automation", "cdp", "chrome-devtools-protocol", "cli",
15
- "mcp", "ai-agent", "web-scraping", "zero-dependency",
16
- "headless-browser", "puppeteer-alternative", "playwright-alternative",
17
- "screenshot", "pdf", "automation", "devtools", "browser",
18
- "model-context-protocol", "claude", "brave", "testing",
19
- "web-testing", "accessibility", "a11y"
14
+ "browser-automation",
15
+ "cdp",
16
+ "chrome-devtools-protocol",
17
+ "cli",
18
+ "mcp",
19
+ "ai-agent",
20
+ "web-scraping",
21
+ "zero-dependency",
22
+ "headless-browser",
23
+ "puppeteer-alternative",
24
+ "playwright-alternative",
25
+ "screenshot",
26
+ "pdf",
27
+ "automation",
28
+ "devtools",
29
+ "browser",
30
+ "model-context-protocol",
31
+ "claude",
32
+ "brave",
33
+ "testing",
34
+ "web-testing",
35
+ "accessibility",
36
+ "a11y",
37
+ "stealth",
38
+ "captcha-solver",
39
+ "anti-bot",
40
+ "cloudflare-bypass",
41
+ "datadome",
42
+ "adaptive-escalation",
43
+ "proxy-rotation",
44
+ "tls-fingerprint",
45
+ "cookie-management"
20
46
  ],
21
47
  "author": "Mehmet Nadir",
22
48
  "license": "MIT",
@@ -32,5 +58,10 @@
32
58
  "src/cdpilot.py",
33
59
  "README.md",
34
60
  "LICENSE"
35
- ]
61
+ ],
62
+ "devDependencies": {
63
+ "gray-matter": "^4.0.3",
64
+ "remark": "^15.0.1",
65
+ "remark-html": "^16.0.1"
66
+ }
36
67
  }