
Chrome Cdp
Drive Chrome through a lightweight CDP CLI and per-tab daemons when agents need DevTools-level browser control without Puppeteer.
Overview
chrome-cdp is an agent skill most often used in Ship (also Build agent-tooling) that exposes a lightweight Chrome DevTools Protocol CLI with per-tab daemons for browser debugging and automation.
Install
npx skills add https://github.com/pasky/chrome-cdp-skill --skill chrome-cdpWhat is this skill?
- Raw Chrome DevTools Protocol over WebSocket with no Puppeteer dependency
- Requires Node 22+ built-in WebSocket client
- Per-tab persistent daemons keep sessions warm; Allow debugging modal once per tab daemon
- Daemons auto-exit after 20 minutes idle
- Cross-platform runtime dir with Windows named pipes and Unix domain sockets
- 20-minute idle timeout before per-tab daemons exit
- 15s default command timeout and 30s navigation timeout
- Requires Node 22+ for built-in WebSocket
Adoption & trust: 1.7k installs on skills.sh; 3.1k GitHub stars; 2/3 security scanners passed (skills.sh audits).
What problem does it solve?
You need DevTools-level Chrome control from an agent or terminal but want to avoid Puppeteer and repeated debugging approval dialogs.
Who is it for?
Advanced solo devs and agent setups that already run Chrome with remote debugging and want a minimal CDP driver on Node 22+.
Skip if: Beginners who only need high-level screenshot MCPs, or environments where installing/debugging Chrome locally is blocked.
When should I use this skill?
When the agent or user needs lightweight Chrome DevTools Protocol control via the cdp CLI, persistent per-tab sessions, or Puppeteer-free browser automation from Node.
What do I get? / Deliverables
You run CDP commands through a persistent per-tab daemon with predictable timeouts and cached page targets for faster reruns.
- CDP command results from live targets
- Reusable per-tab daemon sessions for repeated page commands
Recommended Skills
Journey fit
Spans multiple journey phases - primary shelf plus alternate fits below.
Ship/testing is the primary shelf because CDP is used to observe pages, automate checks, and debug live UI behavior. Testing fits scripted navigation, snapshots, and validation; the skill is invoked for hands-on browser inspection rather than product planning.
Where it fits
Attach a daemon to an open tab and rerun CDP navigation commands after each CSS tweak.
Wire the CDP CLI into a custom agent toolchain that lists targets from cached pages.json.
Reproduce a customer-only UI fault by driving the same route under CDP with extended navigation timeout.
How it compares
Low-level CDP CLI skill—not a full expect-style verify gate or Playwright-backed QA workflow package.
Common Questions / FAQ
Who is chrome-cdp for?
Indie builders and agent operators comfortable with Chrome remote debugging who want direct protocol access from Node without Puppeteer.
When should I use chrome-cdp?
During Ship for live UI debugging and scripted checks; during Build agent-tooling when wiring custom browser automation; and in Operate when reproducing production-like page behavior locally.
Is chrome-cdp safe to install?
Use the Security Audits panel on this Prism page; CDP access can read page content and execute in logged-in sessions—treat it like privileged browser control.
SKILL.md
READMESKILL.md - Chrome Cdp
#!/usr/bin/env node // cdp - lightweight Chrome DevTools Protocol CLI // Uses raw CDP over WebSocket, no Puppeteer dependency. // Requires Node 22+ (built-in WebSocket). // // Per-tab persistent daemon: page commands go through a daemon that holds // the CDP session open. Chrome's "Allow debugging" modal fires once per // daemon (= once per tab). Daemons auto-exit after 20min idle. import { readFileSync, writeFileSync, unlinkSync, existsSync, mkdirSync } from 'fs'; import { homedir } from 'os'; import { resolve } from 'path'; import { spawn } from 'child_process'; import net from 'net'; const TIMEOUT = 15000; const NAVIGATION_TIMEOUT = 30000; const IDLE_TIMEOUT = 20 * 60 * 1000; const DAEMON_CONNECT_RETRIES = 20; const DAEMON_CONNECT_DELAY = 300; const MIN_TARGET_PREFIX_LEN = 8; const IS_WINDOWS = process.platform === 'win32'; if (!IS_WINDOWS) process.umask(0o077); const RUNTIME_DIR = IS_WINDOWS ? resolve(process.env.LOCALAPPDATA || resolve(homedir(), 'AppData', 'Local'), 'cdp') : process.env.XDG_RUNTIME_DIR ? resolve(process.env.XDG_RUNTIME_DIR, 'cdp') : resolve(homedir(), '.cache', 'cdp'); try { mkdirSync(RUNTIME_DIR, { recursive: true, mode: 0o700 }); } catch {} const PAGES_CACHE = resolve(RUNTIME_DIR, 'pages.json'); function sockPath(targetId) { return IS_WINDOWS ? `\\\\.\\pipe\\cdp-${targetId}` : resolve(RUNTIME_DIR, `cdp-${targetId}.sock`); } function getWsUrl() { const home = homedir(); // macOS: ~/Library/Application Support/<name>/DevToolsActivePort const macBrowsers = [ 'Google/Chrome', 'Google/Chrome Beta', 'Google/Chrome for Testing', 'Chromium', 'BraveSoftware/Brave-Browser', 'Microsoft Edge', ]; // Linux: ~/.config/<name>/DevToolsActivePort const linuxBrowsers = [ 'google-chrome', 'google-chrome-beta', 'chromium', 'vivaldi', 'vivaldi-snapshot', 'BraveSoftware/Brave-Browser', 'microsoft-edge', ]; // Linux Flatpak: ~/.var/app/<app-id>/config/<name>/DevToolsActivePort const flatpakBrowsers = [ ['org.chromium.Chromium', 'chromium'], ['com.google.Chrome', 'google-chrome'], ['com.brave.Browser', 'BraveSoftware/Brave-Browser'], ['com.microsoft.Edge', 'microsoft-edge'], ['com.vivaldi.Vivaldi', 'vivaldi'], ]; const candidates = [ process.env.CDP_PORT_FILE, ...macBrowsers.flatMap(b => [ resolve(home, 'Library/Application Support', b, 'DevToolsActivePort'), resolve(home, 'Library/Application Support', b, 'Default/DevToolsActivePort'), ]), ...linuxBrowsers.flatMap(b => [ resolve(home, '.config', b, 'DevToolsActivePort'), resolve(home, '.config', b, 'Default/DevToolsActivePort'), ]), ...flatpakBrowsers.flatMap(([appId, name]) => [ resolve(home, '.var/app', appId, 'config', name, 'DevToolsActivePort'), resolve(home, '.var/app', appId, 'config', name, 'Default/DevToolsActivePort'), ]), // Windows: %LOCALAPPDATA%/<name>/User Data/DevToolsActivePort ...(IS_WINDOWS ? ['Google/Chrome', 'BraveSoftware/Brave-Browser', 'Microsoft/Edge'].flatMap(b => { const base = process.env.LOCALAPPDATA || resolve(home, 'AppData/Local'); return [ resolve(base, b, 'User Data/DevToolsActivePort'), resolve(base, b, 'User Data/Default/DevToolsActivePort'), ]; }) : []), ].filter(Boolean); const portFile = candidates.find(p => existsSync(p)); if (!portFile) throw new Error('No DevToolsActivePort found. Enable remote debugging at chrome://inspect/#remote-debugging'); const lines = readFileSync(portFile, 'utf8').trim().split('\n'); if (lines.length < 2 || !lines[0] || !lines[1]) throw new Error(`Invalid DevToolsActivePort file: ${portFile}`); const host = process.env.CDP_HOST || '127.0.0.1'; return `ws://${host}:${lines[0]}${lines[1]}`; } const sleep = (ms) => new Promise(r => setTimeout(r, ms)); function resolvePrefix(prefix, candidates, noun = 'target', missingHint = '') { const upper = prefix.toUpperCase(); const matches = candidates.filte