|
#!/usr/bin/env bash |
|
set -euo pipefail |
|
|
|
mode="copy" |
|
session="default" |
|
port="9222" |
|
detect_chrome() { |
|
if [[ -n "${CDB_CHROME:-}" ]]; then |
|
printf '%s\n' "$CDB_CHROME" |
|
return 0 |
|
fi |
|
if [[ -x "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" ]]; then |
|
printf '%s\n' "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" |
|
return 0 |
|
fi |
|
command -v google-chrome-stable 2>/dev/null && return 0 |
|
command -v google-chrome 2>/dev/null && return 0 |
|
command -v chromium 2>/dev/null && return 0 |
|
command -v chromium-browser 2>/dev/null && return 0 |
|
return 1 |
|
} |
|
|
|
detect_source_profile() { |
|
if [[ -n "${CDB_SOURCE_PROFILE:-}" ]]; then |
|
printf '%s\n' "$CDB_SOURCE_PROFILE" |
|
return 0 |
|
fi |
|
if [[ -d "$HOME/Library/Application Support/Google/Chrome" ]]; then |
|
printf '%s\n' "$HOME/Library/Application Support/Google/Chrome" |
|
return 0 |
|
fi |
|
if [[ -d "$HOME/.config/google-chrome" ]]; then |
|
printf '%s\n' "$HOME/.config/google-chrome" |
|
return 0 |
|
fi |
|
if [[ -d "$HOME/.config/chromium" ]]; then |
|
printf '%s\n' "$HOME/.config/chromium" |
|
return 0 |
|
fi |
|
return 1 |
|
} |
|
|
|
chrome="$(detect_chrome || true)" |
|
source_profile="$(detect_source_profile || true)" |
|
base="${CDB_BASE:-/tmp/cdb-$USER}" |
|
chrome_args=() |
|
cdb_args=() |
|
|
|
# Everything after -- is passed through to Chrome unchanged. |
|
while [[ $# -gt 0 ]]; do |
|
if [[ "$1" == "--" ]]; then |
|
shift |
|
chrome_args=("$@") |
|
break |
|
fi |
|
cdb_args+=("$1") |
|
shift |
|
done |
|
set -- "${cdb_args[@]}" |
|
|
|
usage() { |
|
cat <<'EOF' |
|
Chrome Debug Browser (cdb) |
|
|
|
Purpose / LLM notes: |
|
cdb is a small universal Chrome DevTools Protocol (CDP) launcher. |
|
It exists because modern Chrome refuses remote debugging on the default |
|
user-data directory. Running: |
|
|
|
open -a "Google Chrome" --args --remote-debugging-port=9222 |
|
|
|
may show the flag in `ps`, but often no listener appears on 127.0.0.1:9222. |
|
Chrome prints the real reason when launched directly: |
|
|
|
DevTools remote debugging requires a non-default data directory. |
|
|
|
This is official Chrome behavior since Chrome 136, not a local macOS bug: |
|
https://developer.chrome.com/blog/remote-debugging-port |
|
|
|
cdb solves this by copying the normal signed-in Chrome user-data directory to |
|
a non-default profile under /tmp and launching Chrome from that copy with CDP |
|
enabled. This keeps browser automation tools from mutating the live default |
|
Chrome profile while still preserving cookies/logins from the copied profile. |
|
|
|
What cdb is: |
|
- a generic Chrome/CDP shim, not tied to any one agent/tool |
|
- usable by any CDP client: Playwright, Puppeteer, browser-tools, MCP, custom scripts |
|
- a session/profile manager for temporary debug Chrome profiles |
|
|
|
What cdb is not: |
|
- not a Gmail/OpenAI/Oracle tool |
|
- not a browser automation client itself |
|
- not a permanent password/session vault unless CDB_BASE points outside /tmp |
|
- not the official Chrome DevTools MCP autoConnect flow |
|
|
|
Official/alternative approaches: |
|
- Chrome recommends a non-default --user-data-dir for classic CDP port usage. |
|
- Chrome recommends Chrome for Testing for automation compatibility. |
|
- Chrome >=144 also has a permission-based Chrome DevTools MCP autoConnect |
|
flow via chrome://inspect/#remote-debugging, but that is MCP-specific and |
|
not the same as exposing a generic http://127.0.0.1:9222/json/version CDP |
|
endpoint for arbitrary tools. |
|
- autoConnect docs: |
|
https://developer.chrome.com/blog/chrome-devtools-mcp-debug-your-browser-session |
|
|
|
Profile semantics: |
|
- Default source profile: auto-detected Chrome/Chromium user-data dir |
|
- Override source: CDB_SOURCE_PROFILE=/path/to/profile |
|
- Override browser: CDB_CHROME=/path/to/chrome |
|
- Default temp copy: /tmp/cdb-$USER/<session> |
|
- The copy is reused until /tmp cleanup/reboot or `cdb --fresh`. |
|
- Closing Chrome does not delete the copied profile. |
|
- Use `--fresh` after changing login/account in real Chrome. |
|
- Use `--empty` for a clean non-copied profile. |
|
- Use CDB_BASE=$HOME/.chrome-debug to persist sessions across reboots. |
|
|
|
Usage: |
|
cdb Start/reuse copied Chrome profile 'default' on 9222 |
|
cdb <session> Start/reuse copied Chrome profile session on 9222 |
|
cdb <session> <port> Start/reuse session on custom port |
|
cdb <session> <port> <url> Start/reuse session and open url |
|
cdb [args...] -- <chrome flags/args...> |
|
Pass everything after -- through to Chrome |
|
cdb --fresh [session] [port] [url] |
|
Delete session copy, copy current Chrome profile again, start |
|
cdb --empty [session] [port] [url] |
|
Start empty non-default profile, no copy |
|
cdb --kill [session] Kill Chrome processes using that session profile |
|
cdb --status [port] Check DevTools endpoint and print /json/version |
|
|
|
Default temp base: |
|
/tmp/cdb-$USER/<session> |
|
|
|
Persistent base across reboots: |
|
export CDB_BASE="$HOME/.chrome-debug" |
|
|
|
Environment overrides: |
|
CDB_CHROME=/path/to/chrome |
|
CDB_SOURCE_PROFILE=/path/to/Chrome/UserData |
|
CDB_BASE=/path/to/debug-profiles |
|
|
|
After start, connect any CDP client to: |
|
127.0.0.1:9222 |
|
|
|
Useful local links: |
|
http://127.0.0.1:9222/json/version # browser/version/webSocketDebuggerUrl |
|
http://127.0.0.1:9222/json/list # open tabs/targets |
|
|
|
Optional default URL: |
|
CDB_URL=https://example.com cdb |
|
|
|
Examples: |
|
cdb |
|
cdb pro |
|
cdb pro 9222 https://chatgpt.com |
|
cdb work 9222 -- --auto-open-devtools-for-tabs |
|
cdb test 9333 https://example.com -- --incognito |
|
cdb --fresh pro |
|
cdb --empty clean 9224 -- --disable-extensions |
|
cdb --kill pro |
|
cdb --status 9222 |
|
|
|
Reserved by cdb: |
|
--remote-debugging-port, --remote-debugging-address, --user-data-dir |
|
|
|
Common CDP client examples: |
|
# Generic endpoint: |
|
127.0.0.1:9222 |
|
|
|
# Playwright-style clients usually need the websocket URL from: |
|
http://127.0.0.1:9222/json/version |
|
|
|
# Tools with a host:port option usually use: |
|
127.0.0.1:9222 |
|
EOF |
|
} |
|
|
|
case "${1:-}" in |
|
-h|--help|help) |
|
usage |
|
exit 0 |
|
;; |
|
--fresh) |
|
mode="fresh"; shift |
|
;; |
|
--empty) |
|
mode="empty"; shift |
|
;; |
|
--kill) |
|
mode="kill"; shift |
|
;; |
|
--status) |
|
mode="status"; shift |
|
;; |
|
esac |
|
|
|
if [[ "$mode" == "status" ]]; then |
|
port="${1:-9222}" |
|
if curl -fsS --max-time 1 "http://127.0.0.1:$port/json/version"; then |
|
exit 0 |
|
fi |
|
echo "Kein Chrome DevTools endpoint auf 127.0.0.1:$port" >&2 |
|
exit 1 |
|
fi |
|
|
|
session="${1:-$session}" |
|
port="${2:-$port}" |
|
start_url="${3:-${CDB_URL:-}}" |
|
session="${session//[^A-Za-z0-9._-]/_}" |
|
profile="$base/$session" |
|
log="$profile/chrome.log" |
|
|
|
if [[ "$mode" == "kill" ]]; then |
|
pkill -f -- "--user-data-dir=$profile" 2>/dev/null || true |
|
echo "Chrome Debug Browser beendet: $session ($profile)" |
|
exit 0 |
|
fi |
|
|
|
mkdir -p "$base" |
|
chmod 700 "$base" 2>/dev/null || true |
|
|
|
if [[ "$mode" == "fresh" ]]; then |
|
echo "Loesche Session-Kopie: $profile" |
|
pkill -f -- "--user-data-dir=$profile" 2>/dev/null || true |
|
rm -rf "$profile" |
|
fi |
|
|
|
if [[ -z "$chrome" || ! -x "$chrome" ]]; then |
|
echo "Chrome executable not found. Set CDB_CHROME=/path/to/chrome" >&2 |
|
exit 1 |
|
fi |
|
|
|
if [[ "$mode" != "empty" && ! -f "$profile/Local State" ]]; then |
|
if [[ -z "$source_profile" || ! -d "$source_profile" ]]; then |
|
echo "Chrome source profile not found. Set CDB_SOURCE_PROFILE=/path/to/user-data-dir or use --empty" >&2 |
|
exit 1 |
|
fi |
|
echo "Kopiere Chrome-Profil nach: $profile" |
|
echo "Quelle: $source_profile" |
|
mkdir -p "$profile" |
|
rsync -a --delete \ |
|
--exclude='Singleton*' \ |
|
--exclude='Crashpad' \ |
|
--exclude='BrowserMetrics' \ |
|
--exclude='Safe Browsing*' \ |
|
--exclude='GrShaderCache' \ |
|
--exclude='ShaderCache' \ |
|
--exclude='*/Cache' \ |
|
--exclude='*/Code Cache' \ |
|
--exclude='*/GPUCache' \ |
|
--exclude='*/Service Worker/CacheStorage' \ |
|
--exclude='*/Service Worker/ScriptCache' \ |
|
"$source_profile/" "$profile/" |
|
else |
|
mkdir -p "$profile" |
|
fi |
|
chmod 700 "$profile" 2>/dev/null || true |
|
|
|
if curl -fsS --max-time 1 "http://127.0.0.1:$port/json/version" >/dev/null 2>&1; then |
|
echo "Chrome Debug Browser laeuft bereits: http://127.0.0.1:$port" |
|
echo "Profil: $profile" |
|
echo "CDP endpoint: 127.0.0.1:$port" |
|
echo "Version: http://127.0.0.1:$port/json/version" |
|
echo "Targets: http://127.0.0.1:$port/json/list" |
|
exit 0 |
|
fi |
|
|
|
echo "Starte Chrome Debug Browser" |
|
echo "Profil: $profile" |
|
echo "Port: $port" |
|
if [[ -n "$start_url" ]]; then |
|
echo "URL: $start_url" |
|
fi |
|
if [[ ${#chrome_args[@]} -gt 0 ]]; then |
|
echo "Chrome args: ${chrome_args[*]}" |
|
fi |
|
cmd=( |
|
"$chrome" |
|
--remote-debugging-port="$port" |
|
--remote-debugging-address=127.0.0.1 |
|
--user-data-dir="$profile" |
|
--no-first-run |
|
--no-default-browser-check |
|
) |
|
if [[ ${#chrome_args[@]} -gt 0 ]]; then |
|
cmd+=("${chrome_args[@]}") |
|
fi |
|
if [[ -n "$start_url" ]]; then |
|
cmd+=("$start_url") |
|
fi |
|
"${cmd[@]}" >"$log" 2>&1 & |
|
|
|
pid=$! |
|
echo "PID: $pid" |
|
echo "Log: $log" |
|
|
|
for _ in $(seq 1 50); do |
|
if curl -fsS --max-time 1 "http://127.0.0.1:$port/json/version" >/dev/null 2>&1; then |
|
echo "CDP bereit: http://127.0.0.1:$port" |
|
echo "CDP endpoint: 127.0.0.1:$port" |
|
echo "Version: http://127.0.0.1:$port/json/version" |
|
echo "Targets: http://127.0.0.1:$port/json/list" |
|
exit 0 |
|
fi |
|
sleep 0.2 |
|
done |
|
|
|
echo "WARNUNG: Chrome gestartet, aber CDP-Port antwortet noch nicht. Siehe Log: $log" >&2 |
|
exit 1 |