🎁 Get the FREE AI Skills Starter GuideSubscribe →
BytesAgainBytesAgain
🦀 ClawHub

Remote Chrome CDP

by @noestelar

Control Chrome/Chromium via CDP (Chrome DevTools Protocol) — open tabs, navigate URLs, take screenshots, execute JS. Supports local and remote machines via S...

Versionv1.0.0
Downloads564
Stars1
TERMINAL
clawhub install chrome-cdp-remote

📖 About This Skill


name: chrome-cdp-remote description: Control Chrome/Chromium via CDP (Chrome DevTools Protocol) — open tabs, navigate URLs, take screenshots, execute JS. Supports local and remote machines via SSH tunnel. tags: [browser, automation, cdp, chrome, screenshots, devtools, remote]

Chrome CDP Remote Control

Use when: automating browser tasks programmatically — navigate pages, capture screenshots, run searches, execute JavaScript — without a full Playwright/Puppeteer dependency.


Setup: Launch Chrome with CDP enabled

Linux

google-chrome \
  --remote-debugging-port=9222 \
  --remote-allow-origins=* \
  --user-data-dir=~/.config/chrome-cdp

macOS

/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
  --remote-debugging-port=9222 \
  --remote-allow-origins=* \
  --user-data-dir="$HOME/.config/chrome-cdp"

Note: Use a dedicated --user-data-dir (e.g. ~/.config/chrome-cdp) — Chrome 126+ blocks CDP on the default profile by design. Log in with your Google account once to enable sync.

Note: On macOS, omit --user-data-dir only if you don't need sync — it gives you your real profile with extensions and custom shortcuts.

Verify CDP is running

curl http://localhost:9222/json/version
Should return browser version and a webSocketDebuggerUrl.

Kill and relaunch

pkill -f 'chrome.*remote-debugging-port=9222'
sleep 2

relaunch with flags above


Setup: Remote machine via SSH tunnel (recommended)

Forward the remote machine's CDP port to localhost:

ssh -L 9223:localhost:9222 user@remote-host -N &
Then use PORT = 9223 in your scripts. Works great over Tailscale.


macOS: Raycast Script Command

Save as ~/.config/raycast/scripts/chrome-cdp.sh and chmod +x:

#!/bin/bash

@raycast.schemaVersion 1

@raycast.title Chrome CDP

@raycast.mode silent

@raycast.icon 🌐

@raycast.description Launch Chrome with CDP enabled

pkill -f "Google Chrome.*remote-debugging-port" 2>/dev/null sleep 1

/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \ --remote-debugging-port=9222 \ --remote-allow-origins=* \ --user-data-dir="$HOME/.config/chrome-cdp" &

echo "Chrome CDP started on port 9222"


Python: WebSocket control pattern

Install dependency (once):

pip install websocket-client

import json, urllib.request, websocket, time, base64

PORT = 9222 # use 9223 if connecting via SSH tunnel

def send_recv(ws, method, params, msg_id): """Send a CDP command and wait for its response, draining intermediate events.""" ws.send(json.dumps({"id": msg_id, "method": method, "params": params})) for _ in range(30): msg = json.loads(ws.recv()) if msg.get("id") == msg_id: return msg return None

Open a new tab

req = urllib.request.Request(f"http://localhost:{PORT}/json/new", method="PUT") tab = json.loads(urllib.request.urlopen(req).read())

Connect via WebSocket — origin header is required

ws = websocket.WebSocket() ws.connect(tab["webSocketDebuggerUrl"], origin=f"http://localhost:{PORT}") ws.settimeout(15)

msg_id = 1 send_recv(ws, "Page.enable", {}, msg_id); msg_id += 1

Navigate to a URL

send_recv(ws, "Page.navigate", {"url": "https://example.com"}, msg_id); msg_id += 1 time.sleep(5) # wait for page load

Take a screenshot

Use fromSurface + scale=2 on Retina/HiDPI displays (e.g. MacBook Pro)

result = send_recv(ws, "Page.captureScreenshot", { "format": "jpeg", "quality": 70, "fromSurface": True, "scale": 2 # remove on non-Retina displays }, msg_id); msg_id += 1

img = base64.b64decode(result["result"]["data"]) with open("/tmp/cdp_screenshot.jpg", "wb") as f: f.write(img)

ws.close() print("Screenshot saved.")


Custom search engine shortcuts (bangs)

If Chrome is launched with a real user profile, custom search engine shortcuts are available.

Configure in Chrome → Settings → Search engine → Manage search engines.

Example — add Perplexity as !ppx:

  • Shortcut: !ppx
  • URL: https://www.perplexity.ai/search?q=%s
  • Trigger via address bar: type !ppx then Tab, then your query.

    When controlling via CDP from a different machine, use the full URL directly:

    https://www.perplexity.ai/search?q=YOUR+QUERY
    https://www.google.com/search?q=YOUR+QUERY
    


    CDP patterns

  • Never connect to ws://localhost:9222/devtools/browser directly — always fetch http://localhost:9222/json/list and use webSocketDebuggerUrl from the target tab
  • Runtime.enable and Page.enable must be called before any Runtime.evaluate or Page.navigate
  • Network.responseReceived doesn't include body — call Network.getResponseBody with requestId after response completes
  • Block URLs before navigate: call Network.setBlockedURLs before Page.navigate, not after

  • Pitfalls

  • --remote-allow-origins=* is required — omitting it causes 403 Forbidden on WebSocket handshake
  • Chrome 126+ blocks CDP on the default profile — always use a dedicated --user-data-dir
  • Always filter ws.recv() by message ID — CDP sends intermediate events between your command and its response
  • SSH tunnel holds the port after Chrome dies — kill the tunnel before relaunching (kill %1 or pkill ssh)
  • macOS: omit --user-data-dir only if you want your real profile with bangs/extensions (and don't mind Chrome not starting if already open)
  • Chromium-based alternatives (Helium, Brave, etc.) support CDP but may behave inconsistently — Chrome is most reliable