cdpilot 0.4.0 → 0.4.1
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/package.json +1 -1
- package/src/cdpilot.py +125 -38
package/package.json
CHANGED
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.
|
|
17
|
+
__version__ = "0.4.1"
|
|
18
18
|
|
|
19
19
|
import asyncio
|
|
20
20
|
import json
|
|
@@ -1503,6 +1503,15 @@ def cmd_launch():
|
|
|
1503
1503
|
proj_label = f' [{PROJECT_ID}]' if PROJECT_ID else ''
|
|
1504
1504
|
print(f'Launching browser (isolated session, port {CDP_PORT}){proj_label}...')
|
|
1505
1505
|
|
|
1506
|
+
# ─── Stability fixes (2026-04-21) ───
|
|
1507
|
+
# Brave on Apple Silicon crashed with SIGTRAP/EXC_BREAKPOINT deterministically
|
|
1508
|
+
# at ~7 min uptime on ThreadPoolForegroundWorker. Two changes:
|
|
1509
|
+
# 1) `--disable-gpu-compositing` removed — conflicts with Metal accel on
|
|
1510
|
+
# M-series. Browser falls back to GPU-accel rendering which is the
|
|
1511
|
+
# Chromium default and matches what websites expect.
|
|
1512
|
+
# 2) `--disable-features=...` added — catches Brave-specific background
|
|
1513
|
+
# tasks that individual `--disable-brave-*` flags don't fully kill
|
|
1514
|
+
# (Rewards ad scanner, Sync heartbeat, News fetcher).
|
|
1506
1515
|
chrome_args = [
|
|
1507
1516
|
CHROME_BIN,
|
|
1508
1517
|
f'--remote-debugging-port={CDP_PORT}',
|
|
@@ -1521,6 +1530,14 @@ def cmd_launch():
|
|
|
1521
1530
|
'--disable-tor',
|
|
1522
1531
|
'--disable-ipfs',
|
|
1523
1532
|
'--disable-brave-extension',
|
|
1533
|
+
# Feature-level disables — catch background workers the
|
|
1534
|
+
# single-purpose flags above miss. Comma-joined string = one arg.
|
|
1535
|
+
'--disable-features=' + ','.join([
|
|
1536
|
+
'BraveRewards', 'BraveAds', 'BraveSync', 'BraveNewsToday',
|
|
1537
|
+
'BraveVPN', 'BraveWalletBubble', 'SpeedReader', 'Tor',
|
|
1538
|
+
'IPFSCompanion', 'Translate', 'OptimizationGuideModelDownloading',
|
|
1539
|
+
'InterestFeedContentSuggestions', 'CalculateNativeWinOcclusion',
|
|
1540
|
+
]),
|
|
1524
1541
|
# ─── Chromium performance flags ───
|
|
1525
1542
|
'--disable-background-networking',
|
|
1526
1543
|
'--disable-background-timer-throttling',
|
|
@@ -1542,7 +1559,9 @@ def cmd_launch():
|
|
|
1542
1559
|
'--safebrowsing-disable-auto-update',
|
|
1543
1560
|
'--password-store=basic',
|
|
1544
1561
|
# ─── GPU / rendering ───
|
|
1545
|
-
|
|
1562
|
+
# --disable-gpu-compositing REMOVED: caused EXC_BREAKPOINT on Apple
|
|
1563
|
+
# Silicon Brave 147+ after ~7min. Let the browser use its default
|
|
1564
|
+
# (Metal-accelerated) compositor.
|
|
1546
1565
|
'--disable-smooth-scrolling',
|
|
1547
1566
|
'--new-window', 'about:blank',
|
|
1548
1567
|
]
|
|
@@ -2225,52 +2244,71 @@ def cmd_session_close(session_id=None):
|
|
|
2225
2244
|
|
|
2226
2245
|
|
|
2227
2246
|
def cmd_extensions():
|
|
2228
|
-
"""List installed extensions.
|
|
2229
|
-
ext_dir = os.path.join(PROFILE_DIR, "Default", "Extensions")
|
|
2230
|
-
if not os.path.isdir(ext_dir):
|
|
2231
|
-
print("No extensions installed.")
|
|
2232
|
-
return
|
|
2247
|
+
"""List installed extensions (packed via Chrome Web Store + dev mode unpacked).
|
|
2233
2248
|
|
|
2234
|
-
|
|
2249
|
+
Two sources are checked independently — neither absence should hide the other.
|
|
2250
|
+
Previously an early-return on missing Default/Extensions/ silently swallowed
|
|
2251
|
+
dev-mode entries written by `ext-install`.
|
|
2252
|
+
"""
|
|
2253
|
+
# 1) Packed (CRX) extensions live under Default/Extensions and are described
|
|
2254
|
+
# in Default/Preferences. Only present after the user installs from the
|
|
2255
|
+
# Chrome Web Store; ext-install does NOT write here.
|
|
2256
|
+
ext_dir = os.path.join(PROFILE_DIR, "Default", "Extensions")
|
|
2257
|
+
packed_ids = []
|
|
2235
2258
|
ext_names = {}
|
|
2236
|
-
if os.path.
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2259
|
+
if os.path.isdir(ext_dir):
|
|
2260
|
+
prefs_path = os.path.join(PROFILE_DIR, "Default", "Preferences")
|
|
2261
|
+
if os.path.exists(prefs_path):
|
|
2262
|
+
try:
|
|
2263
|
+
with open(prefs_path) as f:
|
|
2264
|
+
prefs = json.load(f)
|
|
2265
|
+
settings = prefs.get("extensions", {}).get("settings", {})
|
|
2266
|
+
for ext_id, info in settings.items():
|
|
2267
|
+
manifest = info.get("manifest", {})
|
|
2268
|
+
ext_names[ext_id] = {
|
|
2269
|
+
"name": manifest.get("name", ext_id),
|
|
2270
|
+
"version": manifest.get("version", "?"),
|
|
2271
|
+
"enabled": info.get("state", 1) == 1,
|
|
2272
|
+
}
|
|
2273
|
+
except Exception:
|
|
2274
|
+
pass
|
|
2275
|
+
packed_ids = [d for d in os.listdir(ext_dir) if not d.startswith(".")]
|
|
2276
|
+
|
|
2277
|
+
# 2) Dev-mode (unpacked) extensions registered by `ext-install` —
|
|
2278
|
+
# loaded into the browser via --load-extension on each launch.
|
|
2279
|
+
dev_exts = get_dev_extensions()
|
|
2251
2280
|
|
|
2252
|
-
|
|
2253
|
-
if not ext_ids:
|
|
2281
|
+
if not packed_ids and not dev_exts:
|
|
2254
2282
|
print("No extensions installed.")
|
|
2255
2283
|
return
|
|
2256
2284
|
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2285
|
+
if packed_ids:
|
|
2286
|
+
for ext_id in sorted(packed_ids):
|
|
2287
|
+
info = ext_names.get(ext_id, {})
|
|
2288
|
+
name = info.get("name", ext_id)
|
|
2289
|
+
version = info.get("version", "?")
|
|
2290
|
+
enabled = info.get("enabled", True)
|
|
2291
|
+
status = "✅" if enabled else "⏸️"
|
|
2292
|
+
print(f" {status} {name} (v{version})")
|
|
2293
|
+
print(f" ID: {ext_id}")
|
|
2294
|
+
print(f"\n{len(packed_ids)} packed extension{'s' if len(packed_ids) != 1 else ''}")
|
|
2267
2295
|
|
|
2268
|
-
dev_exts = get_dev_extensions()
|
|
2269
2296
|
if dev_exts:
|
|
2270
|
-
|
|
2297
|
+
if packed_ids:
|
|
2298
|
+
print()
|
|
2299
|
+
print(f"Dev Mode Extensions ({len(dev_exts)}):")
|
|
2271
2300
|
for i, path in enumerate(dev_exts):
|
|
2272
2301
|
exists = '✅' if os.path.isdir(path) else '❌ (directory not found)'
|
|
2273
|
-
|
|
2302
|
+
# Try to read manifest for friendlier output
|
|
2303
|
+
label = os.path.basename(path.rstrip('/'))
|
|
2304
|
+
try:
|
|
2305
|
+
with open(os.path.join(path, 'manifest.json')) as f:
|
|
2306
|
+
mf = json.load(f)
|
|
2307
|
+
label = f"{mf.get('name', label)} (v{mf.get('version', '?')})"
|
|
2308
|
+
except Exception:
|
|
2309
|
+
pass
|
|
2310
|
+
print(f" {exists} [{i}] {label}")
|
|
2311
|
+
print(f" {path}")
|
|
2274
2312
|
|
|
2275
2313
|
|
|
2276
2314
|
def cmd_ext_install(source):
|
|
@@ -2589,6 +2627,54 @@ def cmd_headless(state=None):
|
|
|
2589
2627
|
print('Restart browser (stop → launch).')
|
|
2590
2628
|
|
|
2591
2629
|
|
|
2630
|
+
def cmd_health():
|
|
2631
|
+
"""Print JSON health summary of the cdpilot browser session.
|
|
2632
|
+
|
|
2633
|
+
Output keys:
|
|
2634
|
+
alive — bool, CDP /json/version reachable
|
|
2635
|
+
port — int, current CDP port
|
|
2636
|
+
project_id — str|null, project identifier (multi-instance)
|
|
2637
|
+
tabs — int, count of page targets (when alive)
|
|
2638
|
+
browser — str|null, version string from /json/version
|
|
2639
|
+
crashes_today — int, Brave crash dump count from macOS today
|
|
2640
|
+
stealth — bool, current stealth config
|
|
2641
|
+
uptime_warning — str|null, hint when browser is alive but very old
|
|
2642
|
+
|
|
2643
|
+
Exit codes: 0 = alive, 2 = down. Designed for shell watchdog loops:
|
|
2644
|
+
`until cdpilot health >/dev/null; do cdpilot launch; sleep 2; done`
|
|
2645
|
+
"""
|
|
2646
|
+
import datetime as _dt
|
|
2647
|
+
import glob as _glob
|
|
2648
|
+
|
|
2649
|
+
info = {
|
|
2650
|
+
'alive': False,
|
|
2651
|
+
'port': CDP_PORT,
|
|
2652
|
+
'project_id': PROJECT_ID,
|
|
2653
|
+
'tabs': 0,
|
|
2654
|
+
'browser': None,
|
|
2655
|
+
'crashes_today': 0,
|
|
2656
|
+
'stealth': get_stealth_config(),
|
|
2657
|
+
'uptime_warning': None,
|
|
2658
|
+
}
|
|
2659
|
+
ver = cdp_get('/json/version')
|
|
2660
|
+
if ver:
|
|
2661
|
+
info['alive'] = True
|
|
2662
|
+
info['browser'] = ver.get('Browser') or ver.get('browser') or ''
|
|
2663
|
+
targets = cdp_get('/json') or []
|
|
2664
|
+
info['tabs'] = sum(1 for t in targets if t.get('type') == 'page')
|
|
2665
|
+
|
|
2666
|
+
# Today's crash count from macOS DiagnosticReports (Brave only).
|
|
2667
|
+
if platform.system() == 'Darwin':
|
|
2668
|
+
today = _dt.date.today().strftime('%Y-%m-%d')
|
|
2669
|
+
pattern = os.path.expanduser(f'~/Library/Logs/DiagnosticReports/Brave Browser-{today}-*.ips')
|
|
2670
|
+
info['crashes_today'] = len(_glob.glob(pattern))
|
|
2671
|
+
if info['crashes_today'] >= 3:
|
|
2672
|
+
info['uptime_warning'] = f"{info['crashes_today']} Brave crashes today — consider `cdpilot stop` then relaunch"
|
|
2673
|
+
|
|
2674
|
+
print(json.dumps(info, ensure_ascii=False))
|
|
2675
|
+
sys.exit(0 if info['alive'] else 2)
|
|
2676
|
+
|
|
2677
|
+
|
|
2592
2678
|
def cmd_stealth(state=None):
|
|
2593
2679
|
"""Toggle stealth fingerprint patches.
|
|
2594
2680
|
|
|
@@ -4756,6 +4842,7 @@ if __name__ == "__main__":
|
|
|
4756
4842
|
'proxy': lambda: cmd_proxy(args[0] if args else None),
|
|
4757
4843
|
'headless': lambda: cmd_headless(args[0] if args else None),
|
|
4758
4844
|
'stealth': lambda: cmd_stealth(args[0] if args else None),
|
|
4845
|
+
'health': cmd_health,
|
|
4759
4846
|
'session': cmd_session,
|
|
4760
4847
|
'sessions': cmd_sessions,
|
|
4761
4848
|
'session-close': lambda: cmd_session_close(args[0] if args else None),
|