
Browser Cdp
- 10.3k installs
- 5k repo stars
- Updated August 4, 2026
- worldwonderer/oh-story-claudecode
How do you safely start Chrome CDP on port 9222 and automate pages with agent-browser while reusing login sessions?
About
This skill controls Google Chrome through the Chrome DevTools Protocol using the agent-browser CLI and a setup script on port 9222. It prioritizes a detect-only probe that reports CDP_STATUS ready or needs-setup before mutating the user environment. When Chrome is already running, the agent must ask for consent because setup can kill regular Chrome and lose unsaved tabs. Ready sessions connect with agent-browser --cdp 9222 for open, wait, eval, snapshot, click, and type operations. Complex JavaScript uses base64 eval or stdin heredoc to avoid shell escaping errors. Auth tokens can be read from localStorage or cookies. The workflow supports resetting the debug profile when login state expires and documents exit codes for consent, errors, and success. Use it when browser automation must reuse an existing logged-in Chrome session rather than starting cold.
- Detect-only setup probe avoids killing Chrome when CDP is already ready
- Requires user consent before setup when CHROME_RUNNING=yes
- agent-browser --cdp 9222 supports open, eval, snapshot, click, and type
- Complex JS uses eval -b base64 or eval --stdin to avoid shell escaping
- Exit code 3 signals NEEDS_CONSENT when Chrome is running without --yes
Browser Cdp by the numbers
- 10,294 all-time installs (skills.sh)
- +665 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #50 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Security screen: CRITICAL risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/worldwonderer/oh-story-claudecode --skill browser-cdpAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 10.3k |
|---|---|
| repo stars | ★ 5k |
| Security audit | 0 / 3 scanners passed |
| Last updated | August 4, 2026 |
| Repository | worldwonderer/oh-story-claudecode ↗ |
What it does
Control Chrome via CDP with agent-browser to reuse login sessions for navigation, JavaScript eval, snapshots, and auth token extraction.
Who is it for?
Agents scraping logged-in web apps, extracting auth tokens, and reusing Chrome sessions for multi-step browser workflows.
Skip if: Headless scraping without Chrome installed, server-side APIs that do not need a browser, or greenfield login flows without session reuse.
When should I use this skill?
User needs browser automation, CDP control, session reuse, token extraction, or agent-browser commands against an existing Chrome profile.
What you get
Chrome CDP is probed safely, user consent is obtained when needed, and agent-browser commands extract pages, tokens, and interactive snapshots reliably.
- CDP-ready Chrome debug session
- structured CDP_STATUS detect output
By the numbers
- Documents 4 exit codes for setup-cdp-chrome.js
- Supports 5 CLI flags including --detect-only and --reset
Files
Browser CDP 操作工具
通过 CDP 协议控制 Chrome,复用已有登录态,执行浏览器自动化操作。
前置条件
- macOS / Linux / Windows(实验性),已安装 Google Chrome
- Node.js 12+
agent-browser已安装:npm install -g agent-browser
⚠️ 首次启动会 kill 用户的常规 Chrome。 在启动前必须征求用户同意(见下方"启动流程"),否则用户可能丢失未保存的标签页/草稿。
---
启动流程(skill-mode 强制步骤)
第一步:探测当前状态(无副作用)
node {SKILL_DIR}/scripts/setup-cdp-chrome.js 9222 --detect-only输出形如:
CDP_STATUS=ready # 已就绪,可直接复用
CDP_URL=http://127.0.0.1:9222/json/version
BROWSER=Chrome/148.0.7778.168或:
CDP_STATUS=needs-setup
CHROME_RUNNING=yes # 用户有 Chrome 在跑,启动会杀掉
CHROME_PID_COUNT=3第二步:根据探测结果分支
CDP_STATUS=ready→ 直接使用agent-browser --cdp 9222 ...,不要运行 setup。CDP_STATUS=needs-setup且CHROME_RUNNING=no→ 安全启动:
node {SKILL_DIR}/scripts/setup-cdp-chrome.js 9222 --yesCDP_STATUS=needs-setup且CHROME_RUNNING=yes→ 先用 AskUserQuestion 工具向用户确认:告知会杀掉 N 个 Chrome 进程、可能丢失未保存工作;用户同意后再带--yes启动;用户拒绝则放弃这次自动化。
为什么不能直接 `--yes`: 脚本在非 TTY(即 skill 模式 / Bash 工具)下,如果检测到 Chrome 在跑而没有 --yes,会以退出码 3 报 NEEDS_CONSENT: ... 并中止,不会静默杀进程。这是有意的兜底——但 skill 流程仍应先问用户,而不是看到 3 就盲传 --yes。
---
启动脚本选项
| 选项 | 说明 |
|---|---|
--detect-only | 只探测,不修改任何状态(skill 用) |
--yes | 已征得同意,跳过交互提示 |
--reset | 启动前清空 ~/chrome-debug-profile(登录失效时用) |
--profile <name> | 使用非 Default 的 Chrome profile(如 "Profile 1") |
--dry-run | 打印将执行的步骤,不执行 |
退出码:0 成功 / 1 通用错误 / 2 用户拒绝(TTY)/ 3 需同意但缺 --yes。
---
常用操作
打开页面并等待加载
agent-browser --cdp 9222 open "<URL>"
agent-browser --cdp 9222 wait 3000提取页面文本
agent-browser --cdp 9222 eval 'document.body.innerText.substring(0, 8000)'提取 Auth Token
agent-browser --cdp 9222 eval 'localStorage.getItem("token") || document.cookie'复杂 JS(含引号 / $ / 反引号)
shell 转义容易出错,用以下两种方式之一:
# 1) base64 包裹
agent-browser --cdp 9222 eval -b "$(echo -n "document.querySelectorAll('a').length" | base64)"
# 2) heredoc + --stdin
cat <<'EOF' | agent-browser --cdp 9222 eval --stdin
const links = document.querySelectorAll('a');
links.length;
EOF页面交互(snapshot 拿元素引用)
agent-browser --cdp 9222 snapshot -i # 仅交互元素
agent-browser --cdp 9222 click "<CSS or @e1>"
agent-browser --cdp 9222 type "<sel>" "<text>"---
停止 / 清理
- 关掉 debug Chrome 窗口即可(或
pkill -9 -x 'Google Chrome'/taskkill /F /IM chrome.exe)。 - 登录态失效:
node {SKILL_DIR}/scripts/setup-cdp-chrome.js 9222 --reset --yes(注意--yes同样需要先问用户)。
---
常见问题
| 问题 | 解决方案 |
|---|---|
NEEDS_CONSENT + 退出码 3 | 用 AskUserQuestion 询问用户是否允许杀掉 Chrome,同意后加 --yes 重跑 |
| CDP 端口未监听 | --detect-only 再确认;端口被占用则换端口 |
| 页面跳转到登录页 | snapshot -i 找登录按钮并操作 |
eval 返回 null | 检查 localStorage key 名;含引号的 JS 用 eval -b 或 --stdin |
| 登录态过期 | setup-cdp-chrome.js 9222 --reset --yes 重新拷贝 |
| 有多个 Chrome profile | --profile "Profile 1" 指定 |
| Chrome 不会启动(30s 超时) | 试 --reset;检查端口冲突;查看 ~/chrome-debug-profile/ 是否损坏 |
#!/usr/bin/env node
// setup-cdp-chrome.js
// 准备带有 CDP(Chrome DevTools Protocol)调试功能的 Chrome 环境(跨平台)。
// 通过此脚本,agent-browser 可以复用用户的 Chrome 登录态。
//
// 用法:
// node setup-cdp-chrome.js [port] [options]
//
// Options:
// --detect-only 只探测当前状态(结构化输出),不做任何修改
// --yes 确认杀死现有 Chrome,跳过交互提示
// --reset 清空 ~/chrome-debug-profile 后重新复制
// --profile <name> 使用指定 Chrome profile(默认: Default)
// --dry-run 打印将执行的操作,不实际执行
//
// 退出码:
// 0 成功 / detect-only 完成
// 1 通用错误(环境缺失、超时等)
// 2 用户拒绝(TTY 模式下回答 N)
// 3 需要同意但当前为非 TTY 且未传 --yes
//
// detect-only 结构化输出(stdout,每行 KEY=value):
// CDP_STATUS=ready|needs-setup
// CDP_URL=... (仅当 ready)
// BROWSER=... (仅当 ready)
// CHROME_RUNNING=yes|no
// CHROME_PID_COUNT=N (仅当 CHROME_RUNNING=yes)
"use strict";
const { execSync, spawn } = require("child_process");
const fs = require("fs");
const http = require("http");
const os = require("os");
const path = require("path");
const readline = require("readline");
// ---------------------------------------------------------------------------
// 参数解析
// ---------------------------------------------------------------------------
function parseArgs(argv) {
const flags = { dryRun: false, yes: false, detectOnly: false, reset: false };
let profile = "Default";
let port = null;
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
switch (a) {
case "--dry-run": flags.dryRun = true; break;
case "--yes": case "-y": flags.yes = true; break;
case "--detect-only": flags.detectOnly = true; break;
case "--reset": flags.reset = true; break;
case "--profile":
profile = argv[++i];
if (!profile) {
console.error("❌ --profile 需要一个参数(例如: --profile \"Profile 1\")");
process.exit(1);
}
break;
default:
if (/^\d+$/.test(a)) {
port = parseInt(a, 10);
} else if (a.startsWith("--")) {
console.error(`⚠️ 未知参数: ${a}`);
} else {
console.error(`⚠️ 忽略参数: ${a}`);
}
}
}
if (port === null) port = 9222;
if (!Number.isInteger(port) || port < 1 || port > 65535) {
console.error(`❌ 端口非法: ${port}。必须是 1-65535 的整数。`);
process.exit(1);
}
return { flags, profile, port };
}
const ARGS = parseArgs(process.argv.slice(2));
const CDP_PORT = ARGS.port;
const PLATFORM = os.platform();
// ---------------------------------------------------------------------------
// 平台配置映射
// ---------------------------------------------------------------------------
const PLATFORM_CONFIG = {
darwin: {
chromePaths: [
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
],
profileDir: path.join(
os.homedir(),
"Library",
"Application Support",
"Google",
"Chrome"
),
findChrome() {
for (const p of this.chromePaths) if (fs.existsSync(p)) return p;
return null;
},
listChromePids() {
try {
const out = execSync("pgrep -x 'Google Chrome'", { encoding: "utf-8" }).trim();
return out.split("\n").map(Number).filter((n) => n > 0);
} catch { return []; }
},
killChrome() {
try { execSync("pkill -9 -x 'Google Chrome'", { stdio: "ignore" }); } catch {}
},
},
win32: {
chromePaths: [
path.join(process.env["PROGRAMFILES(X86)"] || "", "Google", "Chrome", "Application", "chrome.exe"),
path.join(process.env.PROGRAMFILES || "", "Google", "Chrome", "Application", "chrome.exe"),
path.join(process.env.LOCALAPPDATA || "", "Google", "Chrome", "Application", "chrome.exe"),
],
profileDir: path.join(
process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"),
"Google", "Chrome", "User Data"
),
findChrome() {
for (const p of this.chromePaths) if (p && fs.existsSync(p)) return p;
return null;
},
listChromePids() {
try {
const out = execSync('tasklist /FI "IMAGENAME eq chrome.exe" /NH /FO CSV', { encoding: "utf-8" }).trim();
return out.split("\n").map((line) => {
const m = line.match(/"chrome.exe","(\d+)"/i);
return m ? parseInt(m[1], 10) : 0;
}).filter((n) => n > 0);
} catch { return []; }
},
killChrome() {
try { execSync("taskkill /F /IM chrome.exe", { stdio: "ignore" }); } catch {}
},
},
linux: {
chromePaths: [
"/usr/bin/google-chrome-stable",
"/usr/bin/google-chrome",
"/opt/google/chrome/google-chrome",
],
profileDir: path.join(os.homedir(), ".config", "google-chrome"),
findChrome() {
for (const p of this.chromePaths) if (fs.existsSync(p)) return p;
return null;
},
listChromePids() {
// 覆盖常见的 Chrome 进程命名
const patterns = ["google-chrome-stable", "google-chrome", "chrome"];
const pids = new Set();
for (const pat of patterns) {
try {
const out = execSync(`pgrep -x ${pat}`, { encoding: "utf-8" }).trim();
out.split("\n").map(Number).filter((n) => n > 0).forEach((n) => pids.add(n));
} catch {}
}
return [...pids];
},
killChrome() {
for (const pat of ["google-chrome-stable", "google-chrome", "chrome"]) {
try { execSync(`pkill -9 -x ${pat}`, { stdio: "ignore" }); } catch {}
}
},
},
};
// ---------------------------------------------------------------------------
// 工具函数
// ---------------------------------------------------------------------------
function log(msg) { console.log(msg); }
function warn(msg) { console.warn("⚠️ " + msg); }
function ok(msg) { console.log("✅ " + msg); }
function err(msg) { console.error("❌ " + msg); }
function getConfig() {
const config = PLATFORM_CONFIG[PLATFORM];
if (!config) {
err(`不支持的平台: ${PLATFORM}。支持 darwin/win32/linux。`);
process.exit(1);
}
return config;
}
/** 同步等待 ms 毫秒(不依赖 setTimeout / 系统 sleep) */
function sleepSync(ms) {
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
}
/** HTTP GET 检查 CDP 端点。拒绝 4xx/5xx;自动 drain 防止 keep-alive 残留连接。 */
function httpGet(url) {
return new Promise((resolve, reject) => {
const req = http.get(url, { timeout: 3000 }, (res) => {
let body = "";
res.on("data", (chunk) => (body += chunk));
res.on("end", () => {
if (res.statusCode >= 400) {
reject(new Error(`HTTP ${res.statusCode}`));
} else {
resolve(body);
}
});
});
req.on("error", reject);
req.on("timeout", () => {
req.destroy();
reject(new Error("timeout"));
});
});
}
async function probeCDP(port) {
try {
const version = await httpGet(`http://127.0.0.1:${port}/json/version`);
return version;
} catch {
return null;
}
}
/** 复制文件(吞掉 ENOENT;其他错误打印一次警告供用户排查) */
function copyFileSafe(src, dest) {
try {
fs.copyFileSync(src, dest);
return true;
} catch (e) {
if (e.code !== "ENOENT") {
warn(`复制失败: ${src} -> ${dest} (${e.code || e.message})`);
}
return false;
}
}
/** 递归复制目录 */
function copyDirRecursive(src, dest) {
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
const entries = fs.readdirSync(src, { withFileTypes: true });
for (const entry of entries) {
const srcPath = path.join(src, entry.name);
const destPath = path.join(dest, entry.name);
if (entry.isDirectory()) {
copyDirRecursive(srcPath, destPath);
} else if (entry.isFile()) {
copyFileSafe(srcPath, destPath);
}
}
}
/** 递归删除目录(兼容老版本 Node 的 fs.rmSync 缺失场景) */
function rmDirSafe(dir) {
if (!fs.existsSync(dir)) return;
if (fs.rmSync) {
fs.rmSync(dir, { recursive: true, force: true });
} else {
fs.rmdirSync(dir, { recursive: true });
}
}
/**
* 刷新登录态相关文件(在 debugProfile 已存在的"增量"路径上使用)。
* 同时尝试老路径(Default/Cookies)和新路径(Default/Network/Cookies),
* 包含各类 -journal / -wal / -shm 旁路文件,以及 Google 账号登录数据。
*/
function refreshAuthFiles(srcDefault, destDefault) {
const targets = [
"Cookies", "Cookies-journal",
"Login Data", "Login Data-journal",
"Login Data For Account", "Login Data For Account-journal",
"Web Data", "Web Data-journal",
path.join("Network", "Cookies"),
path.join("Network", "Cookies-journal"),
];
let copied = 0;
for (const rel of targets) {
const src = path.join(srcDefault, rel);
if (!fs.existsSync(src)) continue;
const dest = path.join(destDefault, rel);
fs.mkdirSync(path.dirname(dest), { recursive: true });
if (copyFileSafe(src, dest)) copied++;
}
return copied;
}
/** 清理 Chrome singleton 锁,避免上次崩溃后下次启动失败 */
function clearSingletonLocks(profileDir) {
const names = ["SingletonLock", "SingletonCookie", "SingletonSocket"];
for (const n of names) {
try { fs.unlinkSync(path.join(profileDir, n)); } catch {}
}
}
/** 等待 Chrome PID 列表为空 */
function waitForChromeExit(config, maxMs = 8000, stepMs = 500) {
const start = Date.now();
while (Date.now() - start < maxMs) {
if (config.listChromePids().length === 0) return true;
sleepSync(stepMs);
}
return false;
}
/** TTY 交互式问询 */
function promptYesNo(question) {
return new Promise((resolve) => {
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
rl.question(question, (answer) => {
rl.close();
resolve(/^y(es)?$/i.test((answer || "").trim()));
});
});
}
// ---------------------------------------------------------------------------
// detect-only 模式
// ---------------------------------------------------------------------------
async function runDetectOnly(config) {
const version = await probeCDP(CDP_PORT);
if (version) {
log("CDP_STATUS=ready");
log(`CDP_URL=http://127.0.0.1:${CDP_PORT}/json/version`);
// 尝试从 JSON 提取浏览器版本(容错)
try {
const obj = JSON.parse(version);
if (obj.Browser) log(`BROWSER=${obj.Browser}`);
} catch {}
process.exit(0);
}
log("CDP_STATUS=needs-setup");
const pids = config.listChromePids();
if (pids.length > 0) {
log("CHROME_RUNNING=yes");
log(`CHROME_PID_COUNT=${pids.length}`);
} else {
log("CHROME_RUNNING=no");
}
process.exit(0);
}
// ---------------------------------------------------------------------------
// 同意流程:返回 true 继续,false 用户拒绝
// ---------------------------------------------------------------------------
async function ensureConsentToKill(pids) {
if (pids.length === 0) return true;
if (ARGS.flags.yes) return true;
// 非 TTY:拒绝静默杀进程,给调用方(Claude / 上层脚本)一个明确信号
if (!process.stdin.isTTY) {
err(`NEEDS_CONSENT: ${pids.length} running Chrome process(es) will be killed.`);
err(`Pass --yes to confirm (after asking the user), or stop Chrome manually first.`);
process.exit(3);
}
// TTY:交互问询
warn(`检测到 ${pids.length} 个正在运行的 Chrome 进程。`);
warn("继续将杀死它们,你在常规 Chrome 中未保存的工作可能丢失。");
return promptYesNo("继续?[y/N] ");
}
// ---------------------------------------------------------------------------
// 主流程
// ---------------------------------------------------------------------------
async function main() {
const config = getConfig();
const debugProfile = path.join(os.homedir(), "chrome-debug-profile");
// 1) 检测 Chrome 可执行路径(detect-only 也需要 profileDir)
const chromePath = config.findChrome();
// detect-only:不修改任何状态
if (ARGS.flags.detectOnly) {
if (!chromePath) {
log("CDP_STATUS=needs-setup");
log("CHROME_INSTALLED=no");
process.exit(0);
}
return runDetectOnly(config);
}
log("=== CDP Chrome 环境准备 ===");
log(`平台: ${PLATFORM} | CDP 端口: ${CDP_PORT} | profile: ${ARGS.profile}`);
if (!chromePath) {
err("未找到 Google Chrome。请确保已安装。");
err(`搜索路径: ${JSON.stringify(config.chromePaths, null, 2)}`);
process.exit(1);
}
log(`Chrome 路径: ${chromePath}`);
// 2) dry-run:先于任何副作用(包括"复用现有 CDP")打印计划,让用户能看到真要执行时的步骤
const defaultProfile = path.join(config.profileDir, ARGS.profile);
const hasProfile = fs.existsSync(defaultProfile);
if (ARGS.flags.dryRun) {
const cdpAlive = !!(await probeCDP(CDP_PORT));
log(`Chrome profile: ${defaultProfile} (${hasProfile ? "存在" : "不存在"})`);
log(`CDP 端口 ${CDP_PORT}: ${cdpAlive ? "已就绪(实际运行时会直接复用)" : "未监听"}`);
const runningPids = config.listChromePids();
log(`检测到 ${runningPids.length} 个 Chrome 进程`);
log("\n--- dry-run 模式:只打印操作,不执行 ---");
if (cdpAlive) {
log("0. CDP 已就绪,实际运行会直接复用并退出 0(以下步骤仅供参考)");
}
if (ARGS.flags.reset) log(`1. 删除 ${debugProfile}`);
if (runningPids.length > 0) {
log(`2. ${ARGS.flags.yes ? "(已同意)" : "请求同意后 "}杀死 ${runningPids.length} 个 Chrome 进程`);
} else {
log("2. 无 Chrome 进程,无需杀死");
}
if (hasProfile) {
log(`3. 复制 profile: ${defaultProfile} -> ${debugProfile}/Default`);
} else {
log("3. ⚠️ 无用户 profile,将以空 profile 启动");
}
log("4. 清理 SingletonLock / SingletonCookie / SingletonSocket");
log(`5. 启动 Chrome(含 --remote-allow-origins=*, --no-first-run 等)`);
log(`6. 验证 http://127.0.0.1:${CDP_PORT}/json/version`);
ok("dry-run 完成。");
process.exit(0);
}
// 3) 若 CDP 已就绪 → 复用,直接退出
const existing = await probeCDP(CDP_PORT);
if (existing) {
ok("CDP 已就绪,复用现有 Chrome。");
log(existing.split("\n").slice(0, 5).join("\n"));
process.exit(0);
}
if (!hasProfile) {
err(`未找到 Chrome profile: ${defaultProfile}`);
err("请确保已安装 Google Chrome 并至少使用过一次,或用 --profile <name> 指定其他 profile。");
process.exit(1);
}
// 4) 同意流程:如有 Chrome 进程要杀,先征得同意
const runningPids = config.listChromePids();
const consented = await ensureConsentToKill(runningPids);
if (!consented) {
err("用户拒绝,已中止。");
process.exit(2);
}
// 5) 杀死现有 Chrome 进程,等待退出
if (runningPids.length > 0) {
log(`正在停止 ${runningPids.length} 个 Chrome 进程...`);
config.killChrome();
if (!waitForChromeExit(config, 6000)) {
warn("首轮 kill 后仍有 Chrome 进程,再试一次...");
config.killChrome();
waitForChromeExit(config, 4000);
}
const remain = config.listChromePids();
if (remain.length > 0) {
warn(`仍有 ${remain.length} 个 Chrome 进程未退出,继续尝试启动(可能失败)`);
} else {
ok("Chrome 已退出。");
}
}
// 6) --reset:清空 debug profile
if (ARGS.flags.reset) {
log(`正在删除 debug profile: ${debugProfile}`);
rmDirSafe(debugProfile);
}
// 7) 复制 / 刷新 profile(此时 Chrome 已关闭,SQLite 一致)
const debugDefault = path.join(debugProfile, "Default");
if (!fs.existsSync(debugDefault)) {
log("正在复制 Chrome profile 到 debug 目录...");
fs.mkdirSync(debugProfile, { recursive: true });
try { fs.chmodSync(debugProfile, 0o700); } catch {}
copyDirRecursive(defaultProfile, debugDefault);
ok(`Profile 已复制到: ${debugProfile}`);
} else {
log("debug profile 已存在,刷新登录态相关文件...");
try { fs.chmodSync(debugProfile, 0o700); } catch {}
const n = refreshAuthFiles(defaultProfile, debugDefault);
ok(`已刷新 ${n} 个登录态文件`);
}
// 8) 清理 singleton 锁
clearSingletonLocks(debugProfile);
// 9) 以 CDP 模式启动 Chrome
log(`正在以 CDP 模式启动 Chrome(端口 ${CDP_PORT})...`);
const chromeArgs = [
`--remote-debugging-port=${CDP_PORT}`,
`--user-data-dir=${debugProfile}`,
"--remote-allow-origins=*",
"--no-first-run",
"--no-default-browser-check",
"--disable-features=ChromeWhatsNewUI",
];
const child = spawn(chromePath, chromeArgs, { detached: true, stdio: "ignore" });
const childPid = child.pid;
child.unref();
// 10) 等待启动并验证
log("等待 Chrome 启动...");
for (let i = 1; i <= 15; i++) {
sleepSync(2000);
const version = await probeCDP(CDP_PORT);
if (version) {
ok(`Chrome 已成功以 CDP 模式启动(端口 ${CDP_PORT})`);
log(version.split("\n").slice(0, 5).join("\n"));
process.exit(0);
}
log(` 尝试 ${i}/15...`);
}
// 11) 失败清理:杀死刚才启动的孤儿 Chrome
err("30 秒内未能启动 Chrome CDP 环境。");
err("正在清理刚启动的 Chrome 进程...");
if (childPid) {
try { process.kill(childPid); } catch {}
}
config.killChrome();
err("可能原因:");
err(" - Chrome 不支持 --remote-debugging-port");
err(` - 端口 ${CDP_PORT} 已被其他进程占用`);
err(" - debug profile 目录已损坏(试试 --reset)");
process.exit(1);
}
main().catch((e) => {
err(`启动失败: ${e.message}`);
process.exit(1);
});
Related skills
FAQ
What does browser-cdp setup script do?
browser-cdp runs setup-cdp-chrome.js to launch Chrome with CDP debugging on a configurable port, copying the user profile so agent-browser inherits logins, cookies, and extensions.
How do you check CDP status without changing Chrome?
browser-cdp supports setup-cdp-chrome.js --detect-only, which outputs structured KEY=value lines like CDP_STATUS=ready without modifying the running Chrome environment.
Is Browser Cdp safe to install?
skills.sh reports 0 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.