DeepSeek Harness Web GUI 的网页预览与元素批注插件,让 AI 根据可视化反馈直接修改前端源码。
43/100
综合分
20
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/9/17 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
自动兼容检查未通过:缺少 main/exports/bin 入口声明;仓库 package.json 标记 private,未发布到 npm,需从源码安装。以下命令仍可尝试,但可能失败。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add CanglongCl/dsh-web-review该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:需留意
是什么
dsh 原生插件 · ui
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 59 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
⚠︎ 实装验证未通过(unknown · 2026/10/21) ——可能是验证环境差异,装前建议到 GitHub 仓库确认最近更新与 issue。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查需源码安装缺少 main/exports/bin 入口声明;仓库 package.json 标记 private,未发布到 npm,需从源码安装
展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✓npm 包dsh-web-review @ 1.1.2
✓Node 引擎未声明 engines.node
✓dsh CLI 依赖未声明 dsh 版本约束
✗入口文件缺少入口声明
源码安装时若 pnpm 拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑。
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/30 15:48:16
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件读取剪贴板或密钥环境变量发起外部网络请求
查看扫描证据(共 154 处,展示前 120 条)
- 执行外部命令来自配置/变量eval/capture/capture.ts:70
const child = spawn(process.execPath, ['--import', 'tsx', join(REPO_ROOT, 'eval', 'fixtures', 'serve.ts'), baselineDir(task.fixture), String(port)], { - 发起网络请求eval/capture/capture.ts:78
const response = await fetch(url, { signal: AbortSignal.timeout(3000) }) - 执行外部命令来自配置/变量eval/capture/capture.ts:89
const child = spawn(process.execPath, [viteBin, '--port', String(port), '--strictPort'], { - 发起网络请求eval/capture/capture.ts:96
await waitFor(async () => (await fetch(url)).ok, 30_000, `${task.id} vite server`) - 删除本地文件递归eval/capture/capture.ts:97
return { url, stop: () => { child.kill('SIGTERM'); rmSync(copy, { recursive: true, force: true }) } } - 写入本地文件eval/capture/capture.ts:110
writeFileSync(join(dshHome, 'settings.yaml'), [ - 读取本地文件eval/capture/capture.ts:117
readFileSync(join(REPO_ROOT, 'packages', 'dsh-web-review', 'entry-name.json'), 'utf8'), - 写入本地文件eval/capture/capture.ts:120
writeFileSync(overlayPath, [ - 读取密钥环境变量eval/capture/capture.ts:149
if (process.env.DEEPSEEK_API_KEY !== undefined) break - 执行外部命令来自配置/变量eval/capture/capture.ts:159
const web = spawn(launch.command, launch.args, { cwd: REPO_ROOT, stdio: ['ignore', 'pipe', 'pipe'], env: launch.env }) - 删除本地文件递归eval/capture/capture.ts:167
rmSync(dshHome, { recursive: true, force: true }) - 发起网络请求eval/capture/capture.ts:170
await waitFor(async () => (await fetch(webUrl)).ok, 90_000, 'web GUI ready') - 写入本地文件eval/capture/capture.ts:179
mkdirSync(join(root, name), { recursive: true }) - 发起网络请求eval/capture/capture.ts:272
const body = route.request().postData() - 写入本地文件eval/capture/capture.ts:393
mkdirSync(dir, { recursive: true }) - 写入本地文件eval/capture/capture.ts:394
writeFileSync(frozenPath(task.id, round, 'snapshot.json'), JSON.stringify(snapshot, null, 2)) - 写入本地文件eval/capture/capture.ts:395
writeFileSync(frozenPath(task.id, round, 'meta.json'), JSON.stringify({ - 读取本地文件eval/capture/capture.ts:470
if (!existsSync(frozenPath(task.id, roundNumber, 'snapshot.json'))) { - 读取本地文件eval/capture/capture.ts:475
const frozen = JSON.parse(readFileSync(frozenPath(task.id, roundNumber, 'snapshot.json'), 'utf8')) as FrozenSnapshot - 读取本地文件eval/capture/capture.ts:480
const meta = JSON.parse(readFileSync(metaPath, 'utf8')) as CaptureMeta - 读取本地文件eval/fixtures/serve.ts:38
const body = await readFile(file) - 发起网络请求eval/grader.ts:40
const response = await fetch(url) - 执行外部命令来自配置/变量eval/grader.ts:59
const child = spawn(process.execPath, ['--import', 'tsx', SERVE_SCRIPT, dir, String(port)], { - 执行外部命令来自配置/变量eval/grader.ts:70
const child = spawn(process.execPath, [viteBin, '--port', String(port), '--strictPort'], { - 读取本地文件eval/grader.ts:96
if (task.fixtureKind !== 'static' && !existsSync(join(temp, 'node_modules'))) { - 读取本地文件eval/grader.ts:98
if (existsSync(modules)) { - 读取本地文件eval/grader.ts:104
const patch = readFileSync(join(base, task.golden.patchFile), 'utf8') - 执行外部命令固定命令eval/grader.ts:105
execFileSync('git', ['apply', '-p1'], { cwd: temp, input: patch, encoding: 'utf8' }) - 读取本地文件eval/grader.ts:539
if (!existsSync(path)) return { ok: false, expected: `${assertion.file} contains ${assertion.contains.join(' | ')}`, measured: 'file missing' } - 读取本地文件eval/grader.ts:540
const content = readFileSync(path, 'utf8') - 读取本地文件eval/grader.ts:552
for (const entry of readdirSync(dir)) { - 写入本地文件eval/grader.ts:611
mkdirSync(evidenceDir, { recursive: true }) - 删除本地文件递归eval/grader.ts:640
rmSync(variantDir, { recursive: true, force: true }) - 读取本地文件eval/identity.ts:25
return digest(`${readFileSync(GRADER_SOURCE, 'utf8')}\n${JSON.stringify(task.grader)}`) - 读取本地文件eval/identity.ts:29
return digest(EXECUTION_SOURCES.map(url => readFileSync(fileURLToPath(url), 'utf8')).join('\n')) - 读取本地文件eval/process-stats.ts:64
const lines = readFileSync(sessionPath, 'utf8').split('\n').filter(line => line.trim() !== '') - 写入本地文件eval/process-stats.ts:237
writeFileSync(tracePath, trace.join('\n')) - 写入本地文件eval/process-stats.ts:259
writeFileSync(join(outDir, 'process.json'), JSON.stringify(stats, null, 2)) - 读取本地文件eval/regrade.ts:14
if (!existsSync(RESULTS_FILE)) throw new Error(`no results at ${RESULTS_FILE}`) - 读取本地文件eval/regrade.ts:21
for (const line of readFileSync(RESULTS_FILE, 'utf8').split('\n')) { - 读取本地文件eval/regrade.ts:35
if (!existsSync(workspace)) continue - 写入本地文件eval/regrade.ts:37
mkdirSync(join(record.runDir, 'evidence'), { recursive: true }) - 读取本地文件eval/regrade.ts:38
const process = existsSync(session) - 写入本地文件eval/regrade.ts:41
if (process !== undefined) writeFileSync(join(record.runDir, 'process.json'), JSON.stringify(process, null, 2)) - 写入本地文件eval/regrade.ts:49
writeFileSync(join(record.runDir, 'grader.json'), JSON.stringify(grader, null, 2)) - 写入本地文件eval/regrade.ts:64
appendFileSync(RESULTS_FILE, `${JSON.stringify(updated)}\n`) - 读取本地文件eval/report.ts:64
if (record.runDir !== '' && existsSync(join(record.runDir, 'session.jsonl'))) { - 读取本地文件eval/report.ts:68
if (record.runDir !== '' && existsSync(join(record.runDir, 'trace.md'))) detail.traceHref = linkTo('trace.md') - 读取本地文件eval/report.ts:76
if (existsSync(path)) { - 读取本地文件eval/report.ts:77
const text = readFileSync(path, 'utf8') - 读取本地文件eval/report.ts:86
if (!existsSync(resultsFile)) { - 读取本地文件eval/report.ts:92
const parsed = readFileSync(resultsFile, 'utf8').split('\n') - 写入本地文件eval/report.ts:579
mkdirSync(outDir, { recursive: true }) - 写入本地文件eval/report.ts:581
writeFileSync(out, html) - 写入本地文件eval/report.ts:610
mkdirSync(archiveDir, { recursive: true }) - 写入本地文件eval/report.ts:611
writeFileSync(join(archiveDir, 'report.html'), html) - 写入本地文件eval/report.ts:631
writeFileSync(join(archiveDir, 'summary.json'), JSON.stringify({ - 执行外部命令固定命令eval/runner/batch.ts:112
execFileSync('pnpm', ['--filter', '@dsh-web-review-dev/eval-runner', 'build'], { cwd: REPO_ROOT, stdio: 'inherit' }) - 执行外部命令固定命令eval/runner/batch.ts:114
const currentRepoCommit = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: REPO_ROOT, encoding: 'utf8' }).trim() - 执行外部命令固定命令eval/runner/batch.ts:116
? execFileSync('git', ['rev-parse', 'HEAD'], { cwd: harnessRoot, encoding: 'utf8' }).trim() - 读取本地文件eval/runner/batch.ts:118
const manifest = JSON.parse(readFileSync(join(dirname(dirname(dshCli)), 'package.json'), 'utf8')) as { name?: string; version?: string } - 读取本地文件eval/runner/batch.ts:125
if (!flags.force && existsSync(RESULTS_FILE)) { - 读取本地文件eval/runner/batch.ts:126
for (const line of readFileSync(RESULTS_FILE, 'utf8').split('\n')) { - 写入本地文件eval/runner/batch.ts:141
mkdirSync(ARTIFACTS_ROOT, { recursive: true }) - 写入本地文件eval/runner/batch.ts:142
mkdirSync(RESULTS_PATH, { recursive: true }) - 写入本地文件eval/runner/batch.ts:169
appendFileSync(RESULTS_FILE, `${JSON.stringify(record)}\n`) - 写入本地文件eval/runner/batch.ts:208
appendFileSync(RESULTS_FILE, `${JSON.stringify(record)}\n`) - 写入本地文件eval/runner/batch.ts:217
writeFileSync(join(RESULTS_PATH, 'run-summary.json'), JSON.stringify({ - 写入本地文件eval/runner/run-one.ts:50
mkdirSync(join(liveRoot, 'runtime', 'dependencies'), { recursive: true }) - 删除本地文件eval/runner/run-one.ts:52
unlinkSync(join(workspaceDir, 'node_modules')) - 写入本地文件eval/runner/run-one.ts:74
mkdirSync(runDir, { recursive: true }) - 写入本地文件eval/runner/run-one.ts:75
mkdirSync(dshHome, { recursive: true }) - 读取本地文件eval/runner/run-one.ts:78
if (liveRoot === undefined && !existsSync(workspaceDir)) stageWorkspace(task, workspaceDir) - 写入本地文件eval/runner/run-one.ts:88
mkdirSync(runtimeRunner, { recursive: true }) - 写入本地文件eval/runner/run-one.ts:89
mkdirSync(join(runtimeRepo, 'packages', 'dsh-web-review'), { recursive: true }) - 写入本地文件eval/runner/run-one.ts:121
writeFileSync(join(runDir, 'stdout.txt'), launch.stdout) - 删除本地文件递归eval/runner/run-one.ts:168
rmSync(liveRoot, { recursive: true, force: true }) - 读取本地文件eval/runner/runner.ts:66
for (const entry of readdirSync(current).sort()) { - 读取本地文件eval/runner/runner.ts:68
const stat = statSync(path) - 读取本地文件eval/runner/runner.ts:72
hash.update(readFileSync(path)) - 写入本地文件eval/runner/runner.ts:83
mkdirSync(workspaceDir, { recursive: true }) - 读取本地文件eval/runner/runner.ts:90
if (!existsSync(sharedModules)) { - 读取密钥环境变量eval/runner/runner.ts:123
if (process.env.DEEPSEEK_API_KEY !== undefined) return { stagedCredentials: false } - 读取密钥环境变量eval/runner/runner.ts:127
if (process.env.DEEPSEEK_API_KEY !== undefined) return { stagedCredentials: false } - 读取本地文件eval/runner/runner.ts:133
if (existsSync(defaultCredentials)) { - 写入本地文件eval/runner/runner.ts:135
copyFileSync(defaultCredentials, staged) - 写入本地文件eval/runner/runner.ts:136
chmodSync(staged, 0o600) - 写入本地文件eval/runner/runner.ts:188
writeFileSync(path, overlay) - 执行外部命令来自配置/变量eval/runner/runner.ts:225
const child = spawn(process.execPath, [ - 读取本地文件eval/runner/runner.ts:255
if (!existsSync(current)) return - 执行外部命令固定命令eval/runner/runner.ts:336
return execFileSync('git', ['rev-parse', 'HEAD'], { cwd: REPO_ROOT, encoding: 'utf8' }).trim() - 执行外部命令固定命令eval/runner/runner.ts:340
return execFileSync('git', ['rev-parse', 'HEAD'], { cwd: harnessRoot, encoding: 'utf8' }).trim() - 删除本地文件递归eval/runner/runner.ts:352
rmSync(runDir, { recursive: true, force: true }) - 读取本地文件eval/runner-plugin/src/index.ts:120
content: skillBody(readFileSync(join(directory, 'SKILL.md'), 'utf8')), - 读取本地文件eval/runner-plugin/src/index.ts:173
if (!existsSync(join(round.snapshotDir, file))) { - 执行外部命令来自配置/变量eval/smoke.ts:61
const result = spawnSync( - 读取本地文件eval/snapshot-stage.ts:61
if (!existsSync(frozenPagePath(task.id, roundNumber, suffix))) { - 写入本地文件eval/snapshot-stage.ts:73
mkdirSync(dir, { recursive: true, mode: 0o700 }) - 写入本地文件eval/snapshot-stage.ts:76
copyFileSync(htmlPath, join(dir, 'page.html')) - 写入本地文件eval/snapshot-stage.ts:77
copyFileSync(pngPath, join(dir, 'page.png')) - 读取本地文件eval/snapshot-stage.ts:78
const html = readFileSync(htmlPath, 'utf8') - 读取本地文件eval/snapshot-stage.ts:79
const png = readFileSync(pngPath) - 写入本地文件eval/snapshot-stage.ts:80
writeFileSync( - 删除本地文件递归eval/snapshot-stage.ts:90
for (const dir of dirs) rmSync(dir, { recursive: true, force: true }) - 读取本地文件eval/tasks/frozen.ts:24
if (!existsSync(snapshotPath) || !existsSync(metaPath)) { - 读取本地文件eval/tasks/frozen.ts:30
snapshot: JSON.parse(readFileSync(snapshotPath, 'utf8')) as FrozenSnapshot, - 读取本地文件eval/tasks/frozen.ts:31
captureMeta: JSON.parse(readFileSync(metaPath, 'utf8')) as CaptureMeta, - 读取本地文件eval/tasks/register.ts:37
const files = readdirSync(TASKS_DIR) - 读取本地文件eval/view-session.ts:73
if (!existsSync(path)) throw new Error(`eval session path does not exist: ${path}`) - 读取本地文件eval/view-session.ts:74
const log = statSync(path).isDirectory() ? join(path, 'session.jsonl') : path - 读取本地文件eval/view-session.ts:75
if (!existsSync(log)) throw new Error(`eval run has no session.jsonl: ${path}`) - 读取本地文件eval/view-session.ts:80
const firstLine = readFileSync(log, 'utf8').split('\n', 1)[0] - 写入本地文件eval/view-session.ts:135
mkdirSync(dirname(stagedLog), { recursive: true }) - 写入本地文件eval/view-session.ts:136
copyFileSync(log, stagedLog) - 写入本地文件eval/view-session.ts:138
writeFileSync(overlay, [ - 执行外部命令来自配置/变量eval/view-session.ts:152
const child = spawn(isJavaScriptEntry ? process.execPath : cli, [ - 读取本地文件eval/view-session.ts:156
cwd: header.cwd !== undefined && existsSync(header.cwd) ? header.cwd : dirname(log), - 删除本地文件递归eval/view-session.ts:164
rmSync(temporary, { recursive: true, force: true }) - 读取本地文件packages/dsh-web-review/src/index.ts:99
return await readFile(candidate, 'utf8') - 读取本地文件packages/dsh-web-review/src/skill-provider.ts:97
content: skillBody(await readFile(skillFile(name), 'utf8')),
扫描于 2026/11/3(0 天前) · 范围 全包扫描 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
界面预览
3 张 · 点击查看大图功能亮点
- 网页预览
- 元素批注
- AI修改前端源码
README
由 DeepSeek 最新模型翻译生成dsh-web-review
⭐ 如果这个项目对你有帮助,欢迎点个 Star 支持一下!你的支持是我持续维护和改进的动力。
在内置浏览器中,像使用设计工具一样选择页面元素、填写修改意见,并临时调整文本、颜色、字体、尺寸、间距、边框与效果。确认发送后,智能体会结合页面批注修改当前工作区中的源码。
如果你用过 v0、Codex 等 Coding Agent 应用的内置浏览器,你应该对此会很熟悉。
安装
安装并启动:
dsh plugin --profile web add @canglongcl/dsh-web-review
dsh web
使用方法
1. 告诉启动要评审的前端页面,点击AI返回的地址页面。
也可以切换到 DSH 的「网页预览」Tab,输入页面的绝对 HTTP(S) URL。
2. 点击批注按钮,再点击页面中的目标元素。
3. 填写修改意见;如需视觉调整,展开「调整」并修改属性。
4. 点击批注工具栏中的发送按钮,发送后会自动切回「对话」Tab;或在 DSH 的输入框中填写更多提示词,然后点击 DSH 发送按钮,注释会随着你的提示词一同发送。
5. 智能体修改源码后,刷新预览进行验收;不满意可以继续下一轮批注。
主要功能
网页预览
- 在 DSH 内打开智能体提供的链接页面
元素批注
- 悬停高亮并点选页面元素。
- 为多个目标添加批注。
展开完整文档(README 剩余 1 节)
dsh-web-review ⭐ 如果这个项目对你有帮助,欢迎点个 Star 支持一下!你的支持是我持续维护和改进的动力。 在内置浏览器中,像使用设计工具一样选择页面元素、填写修改意见,并临时调整文本、颜色、字体、尺寸、间距、边框与效果。确认发送后,智能体会结合页面批注修改当前工作区中的源码。 如果你用过 v0、Codex 等 Coding Agent 应用的内置浏览器,你应该对此会很熟悉。 安装 安装并启动: dsh plugin --profile web add @canglongcl/dsh-web-review dsh web 使用方法 1. 告诉启动要评审的前端页面,点击AI返回的地址页面。 也可以切换到 DSH 的「网页预览」Tab,输入页面的绝对 HTTP(S) URL。 2. 点击批注按钮,再点击页面中的目标元素。 3. 填写修改意见;如需视觉调整,展开「调整」并修改属性。 4. 点击批注工具栏中的发送按钮,发送后会自动切回「对话」Tab;或在 DSH 的输入框中填写更多提示词,然后点击 DSH 发送按钮,注释会随着你的提示词一同发送。 5. 智能体修改源码后,刷新预览进行验收;不满意可以继续下一轮批注。 主要功能 网页预览 - 在 DSH 内打开智能体提供的链接页面 元素批注 - 悬停高亮并点选页面元素。 - 为多个目标添加批注。 - 自动附带选择器、文本、可访问名称和源码线索,帮助智能体找到对应实现。 实时视觉调整 - 修改文本、颜色、字体、字号、行高、尺寸和透明度。 - 调整间距、布局、边框、圆角和效果。 - 所有修改即时预览。 AI 协作 - 批注会作为独立上下文随你的提示词注入。 - 对话中的页面批注沿用 DSH 原生折叠行:收起时显示页面与批注数,展开后只展示目标、修改意图、前后值和可用源码线索。 - 智能体根据批注修改当前工作区源码,页面中的临时调整不会直接写入工程。 dsh-better-sidebar集成 安装 dsh-better-sidebar 后,网页预览会自动在侧边栏中打开。你也可以在侧边栏新建 tab 菜单中打开网页预览以启用。 UI 优化 Skills 插件内置了 Jakub Krehel 的设计 Skills: - better-ui - better-typography - better-layout - better-writing - better-accessibility - better-colors - better-interface - interface-review 你可以通过斜杠命令调用 skill,也可以在批注编辑器中选择,让智能体在本轮修改中参考相应规则。 插件能力评测 项目包含一套面向真实使用流程的评测,用于验证智能体在收到插件生成的页面批注后,能否正确定位源码并完成前端修改。 评测覆盖: - 文案、样式、布局和响应式修改。 - 多元素与多项关联修改。 - React、Vue 和静态页面。 - 源码锚点可用及缺失时的定位。 - 语义化与无障碍要求。 - 多轮批注和修改范围判断。 - Token 使用量、执行步骤和运行耗时。 评测设计、运行方式和结果解释见 Eval suite。 参与开发 开发环境、架构说明与验证流程见 CONTRIBUTING.md。 版本变更记录见 CHANGELOG.md。
预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
CanglongCl/dsh-web-review
DeepSeek Harness Web GUI 的网页预览与元素批注插件,让 AI 根据可视化反馈直接修改前端源码。
相关插件 · UI / 主题
破甲增强版JeffKeeping/dsh-purge-plus
dsh-purge 的非官方分支fork:适配 DeepSeek Harness 0.2.1-rc.2、破甲内核…
★ 387已汉化DeepSeek 客户端
71.9
无代码业务系统平台nocobase/nocobase
NocoBase 是什么
★ 24,804已汉化DSH
71.8
1Panel 服务器面板1Panel-dev/1Panel
全球 250 万+ 自托管用户共同喜爱。
★ 37,344已汉化DSH
71.5
终端编码代理esengine/DeepSeek-Reasonix
· 指南 · ACP · 扩展开发 · 规格 · 官方网站 · Discord · QQ
★ 36,045已汉化DSH
71.2
工作状态行动态ccch1mneyyy/working-activity
作者:chimney@ccch1mneyyy。社区项目,非 DeepSeek 官方项目。
★ 664已汉化DSH
71.2
多模型配置管家farion1231/cc-switch
跨平台桌面一体化助手,支持 Claude Code、Codex、OpenCode、DeepSeek Harness 等工具,唯一官网 ccswitch.io
★ 151,202已汉化DSH
71.1