🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

CDP 浏览器自动化 joansgwjbsu/dsh-plugin-cdp

DeepSeek Harness
joansgwjbsu15 天前有提交已提供中文文档扫描:含敏感能力

DeepSeek Harness 插件:用 CDP 驱动一个受控 Chrome 实例,附带「弹窗收集规则 +…

30.6/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/21 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add joansgwjbsu/dsh-plugin-cdp
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · browser
装得上吗
本站已在 dsh 0.2.1-rc.5 下真实安装成功
非静态推断
L5 · 运行时冒烟等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 15 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 69 处,展示前 66 条)
  • 读取本地文件
    lib/index.mjs:56
    for (const c of candidates) if (existsSync(c)) return c;
  • 发起网络请求
    lib/index.mjs:82
    const res = await fetch(`${this.base}/json/version`, { signal: AbortSignal.timeout(3000) });
  • 写入本地文件
    lib/index.mjs:90
    mkdirSync(this.profile, { recursive: true });
  • 执行外部命令来自配置/变量
    lib/index.mjs:101
    const child = spawn(this.chromePath, args, { detached: true, stdio: "ignore" });
  • 发起网络请求
    lib/index.mjs:116
    const res = await fetch(`${this.base}/json/list`);
  • 读取本地文件
    lib/index.mjs:123
    if (existsSync(this.statePath)) {
  • 读取本地文件
    lib/index.mjs:125
    id = JSON.parse(readFileSync(this.statePath, "utf8")).targetId;
  • 写入本地文件
    lib/index.mjs:142
    mkdirSync(dirname(this.statePath), { recursive: true });
  • 写入本地文件
    lib/index.mjs:143
    writeFileSync(this.statePath, JSON.stringify({ targetId }, null, 2));
  • 发起网络请求
    lib/index.mjs:147
    const res = await fetch(`${this.base}/json/new?${encodeURIComponent(url)}`, { method: "PUT" });
  • 读取本地文件
    lib/index.mjs:395
    for (const exe of browserCandidates(prefs.browser)) if (existsSync(exe)) return exe;
  • 写入本地文件
    lib/index.mjs:568
    mkdirSync(dirname(out), { recursive: true });
  • 写入本地文件
    lib/index.mjs:569
    writeFileSync(out, buf);
  • 读取本地文件
    lib/index.mjs:890
    const exeFromPref = browserCandidates(prefs.browser).find((p) => existsSync(p)) || "";
  • 读取本地文件
    lib/index.mjs:901
    exeExists: !!exePath && existsSync(exePath),
  • 读取本地文件
    lib/prefs.mjs:132
    if (!existsSync(path)) return { ...base, _exists: false };
  • 读取本地文件
    lib/prefs.mjs:134
    const raw = JSON.parse(readFileSync(path, "utf8"));
  • 写入本地文件
    lib/prefs.mjs:170
    mkdirSync(dirname(path), { recursive: true });
  • 写入本地文件
    lib/prefs.mjs:172
    writeFileSync(tmp, JSON.stringify(clean, null, 2), "utf8");
  • 写入本地文件
    lib/prefs.mjs:173
    renameSync(tmp, path);
  • 读取本地文件
    lib/supervisor.mjs:53
    return JSON.parse(readFileSync(path, "utf8"));
  • 写入本地文件
    lib/supervisor.mjs:60
    mkdirSync(dirname(path), { recursive: true });
  • 写入本地文件
    lib/supervisor.mjs:63
    writeFileSync(path, JSON.stringify(value, null, 2), "utf8");
  • 执行外部命令固定命令
    lib/supervisor.mjs:91
    const r = spawnSync("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
  • 读取本地文件
    lib/supervisor.mjs:196
    if (!existsSync(this.stateDir)) return 0;
  • 读取本地文件
    lib/supervisor.mjs:197
    return readdirSync(this.stateDir)
  • 写入本地文件
    lib/supervisor.mjs:212
    mkdirSync(this.stateDir, { recursive: true });
  • 执行外部命令来自配置/变量
    lib/supervisor.mjs:215
    const child = spawn(
  • 读取本地文件
    lib/supervisor.mjs:275
    if (!existsSync(this.stateDir)) return null;
  • 读取本地文件
    lib/supervisor.mjs:276
    const gens = readdirSync(this.stateDir)
  • 读取本地文件
    lib/match.mjs:323
    const st = statSync(path);
  • 读取本地文件
    lib/match.mjs:326
    let text = readFileSync(path, "utf8");
  • 读取本地文件
    lib/jobs.mjs:1498
    const [text, info] = await Promise.all([readFile(ledgerPath, "utf8"), stat(ledgerPath)]);
  • 读取本地文件
    lib/jobs.mjs:1498
    const [text, info] = await Promise.all([readFile(ledgerPath, "utf8"), stat(ledgerPath)]);
  • 读取本地文件
    lib/jobs.mjs:1521
    const names = (await readdir(logDir)).filter((name) => logPattern.test(name)).sort();
  • 读取本地文件
    lib/jobs.mjs:1524
    const [text, info] = await Promise.all([readFile(filePath, "utf8"), stat(filePath)]);
  • 读取本地文件
    lib/jobs.mjs:1524
    const [text, info] = await Promise.all([readFile(filePath, "utf8"), stat(filePath)]);
  • 读取本地文件
    lib/jobs.mjs:1546
    const names = (await readdir(logDir)).filter((name) => ndjsonPattern.test(name)).sort();
  • 读取本地文件
    lib/jobs.test.mjs:561
    const text = readFileSync(join(JW_DIR, "run-zp-B.log"), "utf8");
  • 读取本地文件
    lib/jobs.test.mjs:669
    const dirNames = readdirSync(JW_DIR);
  • 读取本地文件
    lib/logs.mjs:566
    text = readFileSync(item, "utf8");
  • 读取本地文件
    lib/logs.mjs:572
    else if (item.path) text = readFileSync(item.path, "utf8");
  • 写入本地文件
    lib/plugin.test.mjs:298
    writeFileSync(resumePath, RESUME_FIXTURE, "utf8");
  • 删除本地文件递归
    lib/plugin.test.mjs:316
    rmSync(dir, { recursive: true, force: true });
  • 删除本地文件递归
    lib/selftest.mjs:223
    rmSync(join(tmpPrefs, ".."), { recursive: true, force: true });
  • 读取本地文件
    lib/selftest.mjs:242
    const manifest = JSON.parse(readFileSync(pkgUrl, "utf8"));
  • 执行外部命令固定命令
    lib/supervisor.test.mjs:58
    spawnSync("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
  • 读取本地文件
    lib/supervisor.test.mjs:116
    const snapGenOf = () => JSON.parse(readFileSync(join(stateDir, "rules.json"), "utf8")).generation;
  • 读取本地文件
    lib/supervisor.test.mjs:169
    ok(existsSync(newStatusPath), `新一代状态文件存在 status.gen${swap.generation}.json`);
  • 读取本地文件
    lib/supervisor.test.mjs:170
    const newStat = JSON.parse(readFileSync(newStatusPath, "utf8"));
  • 读取本地文件
    lib/supervisor.test.mjs:174
    ok(existsSync(oldStatusPath), `老一代状态文件仍在 status.gen${gen1}.json(未被覆盖)`);
  • 读取本地文件
    lib/supervisor.test.mjs:175
    const oldStat = JSON.parse(readFileSync(oldStatusPath, "utf8"));
  • 写入本地文件
    lib/supervisor.test.mjs:188
    appendFileSync(donePath, JSON.stringify({ key: "old-task", generation: gen1 }) + "\n", "utf8");
  • 写入本地文件
    lib/supervisor.test.mjs:189
    appendFileSync(donePath, JSON.stringify({ key: "new-task", generation: swap.generation }) + "\n", "utf8");
  • 读取本地文件
    lib/supervisor.test.mjs:190
    const events = readFileSync(join(stateDir, "events.ndjson"), "utf8").trim().split("\n");
  • 删除本地文件递归
    lib/supervisor.test.mjs:227
    rmSync(dir, { recursive: true, force: true });
  • 写入本地文件
    lib/worker.mjs:58
    mkdirSync(dirname(file), { recursive: true });
  • 写入本地文件
    lib/worker.mjs:61
    appendFileSync(file, `${text}\n`, "utf8");
  • 读取本地文件
    lib/worker.mjs:80
    if (!snapshotPath || !existsSync(snapshotPath)) return null;
  • 读取本地文件
    lib/worker.mjs:82
    return JSON.parse(readFileSync(snapshotPath, "utf8"));
  • 读取本地文件
    lib/worker.mjs:95
    if (!existsSync(donePath)) return set;
  • 读取本地文件
    lib/worker.mjs:96
    for (const line of readFileSync(donePath, "utf8").split(/ ? /)) {
  • 写入本地文件
    lib/worker.mjs:193
    mkdirSync(stateDir, { recursive: true });
  • 写入本地文件
    lib/worker.mjs:199
    writeFileSync(resolve(stateDir, `status.gen${generation}.json`), JSON.stringify(status, null, 2), "utf8");
  • 执行外部命令来自配置/变量
    run-all-tests.mjs:31
    const r = spawnSync(process.execPath, ['--test', path], { encoding: 'utf8' });
  • 执行外部命令来自配置/变量
    run-all-tests.mjs:48
    const r = spawnSync(process.execPath, [join(libDir, 'selftest.mjs')], { encoding: 'utf8' });
扫描于 2026/11/3(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-app-boot@deepseek-ai/dsh-tools@deepseek-ai/schemastery
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

README

由 DeepSeek 最新模型翻译生成
dsh-plugin-cdp DeepSeek Harness 插件:用 CDP 驱动一个受控 Chrome 实例,附带「弹窗收集规则 + 文本可视化 + 无停机热替换」能力。 它解决什么问题 基于浏览器扩展的自动化通道驱动不了部分网站: | 问题 | 表现 | | 受控标签页被清空 | 目标站点会让被控制的标签页变空白 | | 必须借用用户标签页 | 借来的标签页同样会变空白 | | 成功弹窗不可信 | 页面报告了成功对话框,但操作并未真正生效 | 本插件因此自带一个受控 Chrome 实例、直接说 CDP 协议,并强制「可验证的操作流程」。 零运行时依赖 —— 只用 Node 内置的 fetch 与 WebSocket(要求 Node ≥ 22)。
展开完整文档(README 剩余 1 节)
dsh-plugin-cdp

DeepSeek Harness 插件:用 CDP 驱动一个受控 Chrome 实例,附带「弹窗收集规则 + 文本可视化 + 无停机热替换」能力。

它解决什么问题

基于浏览器扩展的自动化通道驱动不了部分网站:

| 问题 | 表现 |
| 受控标签页被清空 | 目标站点会让被控制的标签页变空白 |
| 必须借用用户标签页 | 借来的标签页同样会变空白 |
| 成功弹窗不可信 | 页面报告了成功对话框,但操作并未真正生效 |

本插件因此自带一个受控 Chrome 实例、直接说 CDP 协议,并强制「可验证的操作流程」。

零运行时依赖 —— 只用 Node 内置的 fetch 与 WebSocket(要求 Node ≥ 22)。

兼容性

| 项目 | 要求 |
| DeepSeek Harness | >=0.2.0-rc.2 <0.3.0-0(即 engines.dsh,由 DSH 的兼容性判定直接校验);已验证 0.2.1-rc.2 |
| @deepseek-ai/dsh-tools | ^0.1.5-rc.3 \|\| ^0.2.0-rc.1 \|\| ^0.2.1-rc.2(peer,由宿主提供) |
| @deepseek-ai/schemastery | ^3.18.2(peer,由宿主提供) |
| @deepseek-ai/cordis | ^4.0.2(peer,由宿主提供) |
| Node | ≥ 22(宿主自带 Node 24,无需另装) |

engines.dsh 是 DSH 真正会校验的字段。 DSH 的 evaluatePluginCompatibility() 在插件安装/启动时读取它;一旦声明,它单独决定兼容性(peerDependencies 不再参与判定)。
本插件声明 >=0.2.0-rc.2 <0.3.0-0:覆盖已验证的 0.2.x,并用 <0.3.0-0 挡住 0.3 的预发布版本(<0.3.0 在 includePrerelease 下会误放行 0.3.0-rc.1)。

注意:DSH 升级后,请确认配置档里的 @deepseek-ai 包也已拉齐到同版本。
应用更新不会自动刷新配置档的依赖树。详见 UPDATES.md。

各版本的封装产物、SHA256 与「该用哪个版本」见 RELEASES.md。

自查一条命令(在仓库目录执行;单元测试文件不随 npm 包分发):

node run-all-tests.mjs

安装

npm i dsh-plugin-cdp

或在 DSH 的插件配置里加入:

- id: cdp
name: "dsh-plugin-cdp"
config:
port: 9222

三个核心能力

1. 弹窗收集规则,插件本身不携带任何规则

首次使用时插件返回一份问卷,由 DSH 内置的 ask_user_question 弹窗询问,一屏收齐:

岗位方向 · 工作地点 · 薪资下限 · 投递平台 · 浏览器 · 排除规则

答案落盘后不再重复询问。

设计红线:所有规则字段默认为空,问卷的规则类问题不生成选项、不标 (Recommended)。一旦预置选项,就等于把某个人的规则当成新用户的默认答案。插件提供的是能力,不是内容。

2. 纯文本可视化,零依赖

不依赖浏览器、不依赖图表库,用 Unicode 字符画图:

── 薪资分布(月薪 · 按 salaryMin 分桶,n=80;日薪 1 单未计入) ─── <7k │████░░░░░░░░░░░░░│ 7 7-9k │██████████░░░░░░░│ 19 9-12k │█████████████████│ 34 12-15k │██████░░░░░░░░░░░│ 12 ≥20k │█░░░░░░░░░░░░░░░░│ 2 薪资未明 │█░░░░░░░░░░░░░░░░│ 2 ── 投递时间线 ────────────────────────── ▂▁▁▁█ (5 天,49 单) 峰值 44 单

含薪资直方图、地点条形图、平台占比、判定构成、投递火花线、等宽明细表。中文按双宽计算,表格列不会错位。

3. 运行器热替换:改规则不用停

采用 先起新的、再退旧的,而不是原地重启:

1. 用户改规则 → 写新快照,规则指纹变化
2. 起新一代进程,与老一代并存
3. 等新一代写出心跳,证明它真的活着
4. 老一代退位

为什么不是原地重启:那必须先杀掉老进程,于是总有一段「没有任何进程在跑」的真空期。

为什么重叠期安全:两代共享同一份 append-only 的完成记录。已完成的事两边都读得到(不会重复做),新增记录只是追加(不会互相覆盖)。重叠本身就是「不断档」的实现手段。

失败回滚:新一代若在就绪窗口内没写出心跳,保留老一代继续运行,绝不在新规则起不来时把任务停掉。

工具一览(15 个)

| 工具 | 作用 |
| cdp_status | 受控 Chrome 的状态、端口、标签页 |
| cdp_navigate | 导航并等待加载 |
| cdp_eval | 在标签页里执行 JS 并返回 JSON |
| cdp_read | 读取标签页 URL / 标题 / 可见文本 |
| cdp_screenshot | 截图到本地文件 |
| cdp_new_tab / cdp_use_tab | 新建 / 切换受控标签页 |
| cdp_apply_verified | 带校验的操作:等详情面板稳定且薪资与卡片一致才点击,且拒绝重复操作 |
| cdp_prefs | 读取 / 写入偏好(含弹窗问卷) |
| cdp_browser_info | 如实报告使用的浏览器与可执行文件是否存在 |
| cdp_jobs_report | 岗位 / 薪资 / 地点文本可视化报告 |
| cdp_jobs_data | 原始 JSON |
| cdp_runner_status | 运行器状态,含「规则改了但还没生效」提示 |
| cdp_runner_reload | 同步规则并热替换 |
| cdp_runner_stop | 停止运行器 |

cdp_apply_verified 为什么存在

一次「点错岗位」的操作长这样:点击列表卡片 → 右侧详情面板其实还没切换 → 此时点「申请」→ 申请到了上一个岗位。

这个工具强制三道闸门:

1. 等详情面板标题连续 3 次读数稳定
2. 面板薪资必须与卡片薪资逐字相同
3. 按钮文案若已是「继续沟通 / 已投递」则直接拒绝

任何一条不过就返回 ok: false 并说明原因,绝不「看起来成功」。

⚠️ 成功对话框不等于操作已记录。请始终到站点自己的记录页复核。

配置

- id: cdp
config:
chromePath: "" # 留空则自动探测 userDataDir: "你的独立浏览器目录" port: 9222 windowSize: "1500,950" defaultTimeoutMs: 120000 # 单条 CDP 命令超时 loadEventMs: 8000 # 等待页面加载事件的上限 settleMs: 600 # 导航后额外稳定等待 prefsPath: "偏好落盘路径" stateDir: "运行器状态目录"

注意:受控实例使用独立的用户目录,不复用你日常浏览器的配置,因此需要在该实例里单独登录一次目标站点。

开发与测试

node --test lib/jobs.test.mjs # 27 项
node --test lib/report.test.mjs # 28 项
node lib/plugin.test.mjs # 62 项
node lib/supervisor.test.mjs # 44 项
node lib/selftest.mjs # 插件加载自检

自检覆盖三类只有宿主才会拒绝的错误,本地跑 execute() 是测不出来的:

1. output.schema 漏声明返回字段
2. 返回值含 undefined(不是合法 JSON)
3. 输出未通过 validateJsonSchemaValue

已知限制

1. 输出字段必须与 output.schema 逐一对应 —— additionalProperties: false,多一个字段就会在宿主里被拒
2. 运行器默认是心跳模式 —— 未配置命令时不执行任何实际操作,这是有意为之:先证明链路通
3. 热替换依赖 append-only 状态 —— 若把共享状态改成「整份重写」,重叠期就会互相丢更新,那时「先起后杀」不再安全
4. 受控浏览器需要单独登录 —— 独立用户目录意味着不复用日常登录态

License

MIT

DeepSeek Harness 插件,提供 Chrome DevTools Protocol 自动化能力,自带自管理的 Chrome 实例,另外还有:

- 弹窗驱动规则收集 —— 插件不带任何规则;它通过 DSH 内置的 ask_user_question 问用户一次,然后存下答案。
- 零依赖文本仪表盘 —— Unicode 条形图、迷你走势图和等宽表格,用来展示岗位 / 薪资 / 地点数据(能识别 CJK 双宽字符,列永远不会错位)。
- 零停机热替换 —— 规则变化时,新一代 worker 先启动,证明自己活着之后,老一代才退休。两代共享 append-only 状态,所以重叠期是安全的,也不会出现没有进程在跑的真空期。新一代启动失败,就保留老一代。

共 15 个工具。零运行时依赖(Node ≥ 22 内置 fetch + WebSocket)。

⚠️ 成功对话框不能证明操作已记录。始终到站点自己的记录页复核。
预览分享卡片长什么样 ↓joansgwjbsu/dsh-plugin-cdp
分享 · DeepSeek Harness Hub
joansgwjbsu/dsh-plugin-cdp

DeepSeek Harness 插件:用 CDP 驱动一个受控 Chrome 实例,附带「弹窗收集规则 +…

这是你的仓库?认领条目

回到顶部