cdpilot 0.4.2 → 0.4.4

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 (3) hide show
  1. package/README.md +65 -12
  2. package/package.json +1 -1
  3. package/src/cdpilot.py +54 -12
package/README.md CHANGED
@@ -37,22 +37,34 @@ AI agents and developers need browser control that **just works**:
37
37
  - **AI control warning** — Red toast notification appears when you hover during active automation
38
38
  - **Privacy-first** — Everything runs locally. No data leaves your machine
39
39
 
40
- ### Why Brave?
40
+ ### Browser Selection (Workload-Aware Auto-Pick)
41
41
 
42
- cdpilot uses [Brave Browser](https://brave.com) as its engine. Here's why:
42
+ cdpilot picks the right browser for what you're doing. `auto` (default) is a
43
+ two-axis policy — extension workload × platform stability:
43
44
 
44
- | Feature | Brave | Chrome | Why it matters |
45
- |---------|-------|--------|---------------|
46
- | **Built-in ad blocker** | Shields (native) | Extension needed | Pages load faster, less noise in DOM |
47
- | **Tracker blocking** | Default on | Manual config | Cleaner network logs for debugging |
48
- | **Fingerprint protection** | Native | None | Better privacy for automated sessions |
49
- | **Chromium-based** | Full CDP support | Full CDP support | Same DevTools Protocol, same power |
50
- | **Open source** | Yes (MPL 2.0) | Chromium yes, Chrome no | Transparent, auditable |
51
- | **Resource usage** | Lower memory | Higher memory | Better for running alongside your work |
45
+ | Your workload | Auto-pick order |
46
+ |---|---|
47
+ | Has extensions registered (`ext-install`) | vivaldi → brave → edge → chromium → chrome |
48
+ | No extensions (pure automation) | chrome → vivaldi → edge → chromium → brave |
52
49
 
53
- **TL;DR:** Brave = Chrome's power + built-in privacy + less bloat. Perfect for automation.
50
+ Override anytime:
54
51
 
55
- > cdpilot also works with Chrome and Chromium as fallback. Brave is recommended, not required.
52
+ ```bash
53
+ cdpilot browser # show current pick + reason
54
+ cdpilot browser vivaldi # pin to Vivaldi
55
+ cdpilot browser auto # restore smart default
56
+ ```
57
+
58
+ **Why the split?**
59
+ - **Chrome 147+ silently drops `--load-extension`** for unpacked extensions
60
+ (no error, no warning). Verified — `chrome://extensions` shows 0 items.
61
+ - **Vivaldi, Brave, Edge, Chromium** honor `--load-extension` (tested).
62
+ - On **macOS 26 (Tahoe)** Brave 1.89 crashes deterministically at ~7min
63
+ uptime (SIGTRAP in ThreadPoolForegroundWorker). cdpilot detects the OS
64
+ and demotes Brave automatically until a fixed Brave release ships.
65
+
66
+ Each browser gets its own isolated profile (`~/.cdpilot/.../profile-vivaldi`
67
+ etc.) so switching never causes prefs corruption.
56
68
 
57
69
  ## Installation
58
70
 
@@ -187,6 +199,47 @@ cdpilot dialog auto-accept # Auto-accept dialogs
187
199
  cdpilot permission grant geo # Grant geolocation
188
200
  ```
189
201
 
202
+ ### Stealth & CAPTCHA
203
+
204
+ Zero-dependency anti-fingerprint layer — patches `navigator.webdriver`,
205
+ `chrome.runtime`, plugins (proper `PluginArray` inheritance), WebGL
206
+ vendor/renderer, permissions, hardware concurrency, and the `Worker`
207
+ constructor. Injected via `Page.addScriptToEvaluateOnNewDocument` before
208
+ any page script runs. Disabled by default; opt-in.
209
+
210
+ ```bash
211
+ cdpilot stealth on # enable fingerprint patches (opt-in)
212
+ cdpilot stealth off # disable (default)
213
+ cdpilot stealth status # show which patches are applied
214
+
215
+ cdpilot captcha-check # JSON detection of Turnstile/hCaptcha/reCAPTCHA/
216
+ # DataDome/PerimeterX/Arkose/GeeTest. Exit 0/3
217
+ cdpilot captcha-wait [sec] # block until user solves (interactive)
218
+ # or poll with JSON stream (non-interactive)
219
+ ```
220
+
221
+ Verified against public bot-detection panels:
222
+ - **bot.sannysoft.com:** 24/24 PASS (WebDriver, Chrome obj, Plugins as PluginArray, WebGL, PHANTOM_*, HEADCHR_*, SELENIUM_DRIVER)
223
+ - **bot.incolumitas.com** intoli: 6/6 OK — new-tests: 6/7 OK (one FAIL = pure CDP presence, cannot be JS-patched)
224
+ - **nowsecure.nl** (Cloudflare full challenge): passed
225
+ - **arh.antoinevastel.com/areyouheadless:** "You are not Chrome headless"
226
+
227
+ ### Reliability
228
+
229
+ ```bash
230
+ cdpilot browser [name|auto] # workload-aware browser selection
231
+ cdpilot health # JSON: alive, port, tabs, browser, today's crashes
232
+ ```
233
+
234
+ `cdpilot health` is designed for shell watchdogs:
235
+
236
+ ```bash
237
+ until cdpilot health >/dev/null; do cdpilot launch; sleep 2; done
238
+ ```
239
+
240
+ Surfaces today's Brave crash count from `~/Library/Logs/DiagnosticReports/`
241
+ on macOS — spot degradation before your automation silently stalls.
242
+
190
243
  ## Use with AI Agents
191
244
 
192
245
  cdpilot is designed to be called by AI agents as a tool:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdpilot",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "Zero-dependency browser automation from your terminal. One command, full control.",
5
5
  "bin": {
6
6
  "cdpilot": "./bin/cdpilot.js",
package/src/cdpilot.py CHANGED
@@ -14,7 +14,7 @@ Environment:
14
14
  CDPILOT_PROFILE Isolated browser profile directory
15
15
  """
16
16
 
17
- __version__ = "0.4.2"
17
+ __version__ = "0.4.4"
18
18
 
19
19
  import asyncio
20
20
  import json
@@ -1286,15 +1286,41 @@ def get_browser_preference():
1286
1286
 
1287
1287
 
1288
1288
  def _auto_browser_priority():
1289
- """Default detection order. macOS 26 (Tahoe) demotes Brave because the
1290
- current Brave 1.89 build crashes deterministically at ~7min uptime
1291
- (SIGTRAP in ThreadPoolForegroundWorker, observed across 9+ dumps).
1292
- Vivaldi takes priority because it's Chromium-based AND honors
1293
- --load-extension (Chrome silently drops unpacked extensions in 147+).
1289
+ """Workload-aware browser priority.
1290
+
1291
+ Two axes:
1292
+ 1. Extension workload — `cdpilot ext-install` populates dev-extensions.json.
1293
+ If non-empty, the user is doing extension work, so prioritise browsers
1294
+ that honor --load-extension for unpacked extensions:
1295
+ Vivaldi ✅, Brave ✅, Edge ✅, Chrome ❌ (silently drops, 147+),
1296
+ Chromium ✅ (but rarely installed).
1297
+ If the registry is empty, prefer Chrome — most stable, fastest startup,
1298
+ no idiosyncratic background workers.
1299
+ 2. Platform stability — macOS 26 (Tahoe) demotes Brave because the
1300
+ current Brave 1.89 build crashes deterministically at ~7min uptime
1301
+ (SIGTRAP in ThreadPoolForegroundWorker, observed across 9+ dumps).
1302
+
1303
+ Returns (priority_list, reason_string) — reason explains *why* this order
1304
+ was picked, surfaced by `cdpilot browser status`.
1294
1305
  """
1295
- if _macos_major() and _macos_major() >= 26:
1296
- return ['vivaldi', 'brave', 'chrome', 'chromium', 'edge']
1297
- return ['brave', 'chrome', 'vivaldi', 'chromium', 'edge']
1306
+ has_extensions = bool(get_dev_extensions())
1307
+ on_tahoe = _macos_major() and _macos_major() >= 26
1308
+
1309
+ if has_extensions:
1310
+ if on_tahoe:
1311
+ order = ['vivaldi', 'brave', 'edge', 'chromium', 'chrome']
1312
+ reason = f"extension mode (registry has dev extensions) + macOS {_macos_major()} (Brave demoted)"
1313
+ else:
1314
+ order = ['brave', 'vivaldi', 'edge', 'chromium', 'chrome']
1315
+ reason = "extension mode (registry has dev extensions)"
1316
+ else:
1317
+ if on_tahoe:
1318
+ order = ['chrome', 'vivaldi', 'edge', 'chromium', 'brave']
1319
+ reason = f"stability mode (no dev extensions) + macOS {_macos_major()} (Brave demoted)"
1320
+ else:
1321
+ order = ['chrome', 'brave', 'vivaldi', 'edge', 'chromium']
1322
+ reason = "stability mode (no dev extensions)"
1323
+ return order, reason
1298
1324
 
1299
1325
 
1300
1326
  def _find_browser():
@@ -1323,7 +1349,8 @@ def _find_browser():
1323
1349
  sys.stderr.write(f"⚠️ Configured browser '{pref}' not found, falling back to auto-detect.\n")
1324
1350
 
1325
1351
  # 3) Auto-detection in priority order
1326
- for name in _auto_browser_priority():
1352
+ order, _reason = _auto_browser_priority()
1353
+ for name in order:
1327
1354
  resolved = _resolve_browser_name(name)
1328
1355
  if resolved:
1329
1356
  return resolved
@@ -2428,6 +2455,18 @@ def cmd_ext_install(source):
2428
2455
 
2429
2456
  print(f'✅ Dev extension registered: {name} (v{version})')
2430
2457
  print(f' Path: {abs_source}')
2458
+
2459
+ # Warn if the active browser silently drops --load-extension.
2460
+ # Chrome 147+ does this without any console message, easily wasting
2461
+ # hours of "why isn't my extension loading?" debugging.
2462
+ active = os.environ.get('CHROME_BIN') or _find_browser() or ''
2463
+ active_lower = os.path.basename(active).lower()
2464
+ if 'google chrome' in active_lower or active_lower == 'chrome' or 'google-chrome' in active_lower:
2465
+ sys.stderr.write(
2466
+ "⚠️ Active browser is Chrome — Chrome 147+ silently ignores --load-extension\n"
2467
+ " for unpacked extensions. Switch with: cdpilot browser vivaldi\n"
2468
+ )
2469
+
2431
2470
  print(' Restarting browser...')
2432
2471
  cmd_stop()
2433
2472
  import time as _time
@@ -2729,13 +2768,16 @@ def cmd_browser(name=None):
2729
2768
  resolved = _resolve_browser_name(pref) if pref != 'auto' else None
2730
2769
  auto_resolved = _find_browser()
2731
2770
  installed = [n for n in BROWSER_BINARIES if _resolve_browser_name(n)]
2771
+ order, reason = _auto_browser_priority()
2772
+ ext_count = len(get_dev_extensions())
2732
2773
  print(f"Preference: {pref}")
2733
2774
  if pref != 'auto':
2734
2775
  print(f" resolved: {resolved or '(not found — falling back to auto)'}")
2735
2776
  print(f"Auto-pick: {auto_resolved or '(none)'}")
2777
+ print(f" reason: {reason}")
2778
+ print(f" order: {' > '.join(order)}")
2736
2779
  print(f"Installed: {', '.join(installed) or '(none detected)'}")
2737
- if _macos_major() and _macos_major() >= 26:
2738
- print(f"Note: macOS {_macos_major()} demotes Brave in auto-pick (Brave 1.89 crashes ~7min).")
2780
+ print(f"Dev exts: {ext_count} registered (run `cdpilot extensions` to list)")
2739
2781
  return
2740
2782
 
2741
2783
  name = name.lower().strip()