与 Agent 在画板上共改原型并生成前端页面
30.6/100
综合分
4
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/9/16 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
自动兼容检查有告警:未发布到 npm registry,仅可从源码安装。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add guchang/draw2code该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:需留意
是什么
dsh 原生插件 · browser
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 57 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
⚠︎ 实装验证未通过(dependency_conflict · 2026/11/2) ——可能是验证环境差异,装前建议到 GitHub 仓库确认最近更新与 issue。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查⚠ 装前注意未发布到 npm registry,仅可从源码安装
展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✗npm 包dsh-draw2code(未发布到 npm,仅可源码安装)
✓Node 引擎要求 >=22 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/13 09:39:52
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 346 处,展示前 126 条)
- 读取本地文件dist/index.js:564
canonical = await realpath(root); - 读取本地文件dist/index.js:618
raw = await readFile(this.activeBoardPath(gated.value), "utf8"); - 写入本地文件dist/index.js:640
await mkdir(this.dir(gated.value), { recursive: true }); - 写入本地文件dist/index.js:644
await writeFile(tmp, `${JSON.stringify({ name: named.value })} - 写入本地文件dist/index.js:646
await rename(tmp, path); - 读取本地文件dist/index.js:737
bytes = await readFile(join(dir, candidate)); - 写入本地文件dist/index.js:768
await writeFile(temp, compressed); - 写入本地文件dist/index.js:769
await rename(temp, target); - 读取本地文件dist/index.js:778
raw = decodeVersion(nextEntry, await readFile(join(dir, nextEntry)), this.limits.hardCapBytes * 4); - 读取本地文件dist/index.js:802
const info = await stat(scenePath); - 读取本地文件dist/index.js:804
raw = await readFile(scenePath, "utf8"); - 写入本地文件dist/index.js:822
await mkdir(dir, { recursive: true }); - 删除本地文件dist/index.js:862
await rm(join(dir, doomed.entry), { force: true }).catch(() => void 0); - 删除本地文件dist/index.js:1158
await rm(path); - 删除本地文件递归dist/index.js:1162
await rm(this.versionsDir(gated.value, named.value), { recursive: true, force: true }).catch(() => void 0); - 删除本地文件dist/index.js:1168
if (latest.ok && latest.value.name === named.value) await rm(activePath, { force: true }); - 发起网络请求dist/index.js:5826
const response = await fetch(url, { redirect: "error", signal: AbortSignal.timeout(3e3) }); - 发起网络请求dist/index.js:6696
const response = await fetch(`http://127.0.0.1:${descriptor.port}${path}`, { - 删除本地文件dist/index.js:6755
await rm2(options.descriptorPath, { force: true }); - 执行外部命令来自配置/变量dist/index.js:6763
const child = spawn(process.execPath, [options.entry], { - 删除本地文件dist/index.js:6773
await rm2(lockPath, { force: true }); - 发起网络请求dist/index.js:6830
const response = await fetch(`http://127.0.0.1:${descriptor.port}/rpc`, { - 发起网络请求dist/index.js:6839
const response = await fetch(`http://127.0.0.1:${descriptor.port}/register`, { - 发起网络请求dist/index.js:6851
const response = await fetch(`http://127.0.0.1:${descriptor.port}${path}`, { - 发起网络请求dist/index.js:6863
const response = await fetch(`http://127.0.0.1:${descriptor.port}/canvas-token`, { - 执行外部命令来自配置/变量dist/index.js:6892
execFile(launcher.command, launcher.args, (error2) => resolve3(error2 === null)); - 执行外部命令来自配置/变量dist/index.js:7037
execFile2(command, args, { encoding: "utf8" }, (error2, stdout, stderr) => { - 读取本地文件dist/runtime.js:555
canonical = await realpath(root); - 读取本地文件dist/runtime.js:609
raw = await readFile(this.activeBoardPath(gated.value), "utf8"); - 写入本地文件dist/runtime.js:631
await mkdir(this.dir(gated.value), { recursive: true }); - 写入本地文件dist/runtime.js:635
await writeFile(tmp, `${JSON.stringify({ name: named.value })} - 写入本地文件dist/runtime.js:637
await rename(tmp, path); - 读取本地文件dist/runtime.js:728
bytes = await readFile(join(dir, candidate)); - 写入本地文件dist/runtime.js:759
await writeFile(temp, compressed); - 写入本地文件dist/runtime.js:760
await rename(temp, target); - 读取本地文件dist/runtime.js:769
raw = decodeVersion(nextEntry, await readFile(join(dir, nextEntry)), this.limits.hardCapBytes * 4); - 读取本地文件dist/runtime.js:793
const info = await stat(scenePath); - 读取本地文件dist/runtime.js:795
raw = await readFile(scenePath, "utf8"); - 写入本地文件dist/runtime.js:813
await mkdir(dir, { recursive: true }); - 删除本地文件dist/runtime.js:853
await rm(join(dir, doomed.entry), { force: true }).catch(() => void 0); - 删除本地文件dist/runtime.js:1149
await rm(path); - 删除本地文件递归dist/runtime.js:1153
await rm(this.versionsDir(gated.value, named.value), { recursive: true, force: true }).catch(() => void 0); - 删除本地文件dist/runtime.js:1159
if (latest.ok && latest.value.name === named.value) await rm(activePath, { force: true }); - 发起网络请求dist/runtime.js:5825
const response = await fetch(url, { redirect: "error", signal: AbortSignal.timeout(3e3) }); - 读取本地文件dist/daemon-client.js:74
const info = await stat(path); - 读取本地文件dist/daemon-client.js:76
const value = JSON.parse(await readFile(path, "utf8")); - 发起网络请求dist/daemon-client.js:99
const response = await fetch(`http://127.0.0.1:${descriptor.port}${path}`, { - 读取本地文件dist/daemon-client.js:133
const info = await stat2(path); - 读取本地文件dist/daemon-client.js:136
owner = JSON.parse(await readFile2(path, "utf8")); - 写入本地文件dist/daemon-client.js:157
await mkdir2(dirname(options.descriptorPath), { recursive: true, mode: 448 }); - 删除本地文件dist/daemon-client.js:158
await rm(options.descriptorPath, { force: true }); - 执行外部命令来自配置/变量dist/daemon-client.js:166
const child = spawn(process.execPath, [options.entry], { - 删除本地文件dist/daemon-client.js:176
await rm(lockPath, { force: true }); - 删除本地文件dist/daemon-client.js:181
if (lock !== null) await rm(lockPath, { force: true }); - 发起网络请求dist/daemon-client.js:233
const response = await fetch(`http://127.0.0.1:${descriptor.port}/rpc`, { - 发起网络请求dist/daemon-client.js:242
const response = await fetch(`http://127.0.0.1:${descriptor.port}/register`, { - 发起网络请求dist/daemon-client.js:254
const response = await fetch(`http://127.0.0.1:${descriptor.port}${path}`, { - 发起网络请求dist/daemon-client.js:266
const response = await fetch(`http://127.0.0.1:${descriptor.port}/canvas-token`, { - 发起网络请求dist/daemon-client.js:280
const response = await fetch(`http://127.0.0.1:${gateway.port}/bootstrap-code`, { - 执行外部命令来自配置/变量dist/daemon-client.js:295
execFile(launcher.command, launcher.args, (error) => resolve(error === null)); - 读取本地文件dist/draw2code-daemon.js:4214
canonical = await realpath(root); - 读取本地文件dist/draw2code-daemon.js:4268
raw = await readFile(this.activeBoardPath(gated.value), "utf8"); - 写入本地文件dist/draw2code-daemon.js:4290
await mkdir(this.dir(gated.value), { recursive: true }); - 写入本地文件dist/draw2code-daemon.js:4294
await writeFile(tmp, `${JSON.stringify({ name: named.value })} - 写入本地文件dist/draw2code-daemon.js:4296
await rename(tmp, path); - 读取本地文件dist/draw2code-daemon.js:4387
bytes = await readFile(join(dir, candidate)); - 写入本地文件dist/draw2code-daemon.js:4418
await writeFile(temp, compressed); - 写入本地文件dist/draw2code-daemon.js:4419
await rename(temp, target); - 读取本地文件dist/draw2code-daemon.js:4428
raw = decodeVersion(nextEntry, await readFile(join(dir, nextEntry)), this.limits.hardCapBytes * 4); - 读取本地文件dist/draw2code-daemon.js:4452
const info = await stat(scenePath); - 读取本地文件dist/draw2code-daemon.js:4454
raw = await readFile(scenePath, "utf8"); - 写入本地文件dist/draw2code-daemon.js:4472
await mkdir(dir, { recursive: true }); - 删除本地文件dist/draw2code-daemon.js:4512
await rm(join(dir, doomed.entry), { force: true }).catch(() => void 0); - 删除本地文件dist/draw2code-daemon.js:4808
await rm(path); - 删除本地文件递归dist/draw2code-daemon.js:4812
await rm(this.versionsDir(gated.value, named.value), { recursive: true, force: true }).catch(() => void 0); - 删除本地文件dist/draw2code-daemon.js:4818
if (latest.ok && latest.value.name === named.value) await rm(activePath, { force: true }); - 执行外部命令来自配置/变量dist/draw2code-daemon.js:4920
execFile(command, args, { encoding: "utf8" }, (error2, stdout, stderr) => { - 发起网络请求dist/draw2code-daemon.js:11030
const outcome = await approval.request({ - 发起网络请求dist/draw2code-daemon.js:15672
const response = await fetch(url, { redirect: "error", signal: AbortSignal.timeout(3e3) }); - 删除本地文件dist/draw2code-daemon.js:16871
if (current.nonce === descriptor.nonce) await rm2(options.descriptorPath, { force: true }); - 发起网络请求dist/draw2code-gateway.js:9286
const outcome = await approval.request({ - 写入本地文件dist/draw2code-gateway.js:9590
await mkdir(dirname(path), { recursive: true, mode: 448 }); - 写入本地文件dist/draw2code-gateway.js:9592
await writeFile(tmp, `${JSON.stringify(descriptor)} - 写入本地文件dist/draw2code-gateway.js:9594
await rename(tmp, path); - 读取本地文件dist/draw2code-gateway.js:9599
const info = await stat(path); - 读取本地文件dist/draw2code-gateway.js:9601
const value = JSON.parse(await readFile(path, "utf8")); - 发起网络请求dist/draw2code-gateway.js:9621
const response = await fetch(`http://127.0.0.1:${descriptor.port}${path}`, { - 读取本地文件dist/draw2code-gateway.js:9642
const info = await stat2(path); - 读取本地文件dist/draw2code-gateway.js:9645
owner = JSON.parse(await readFile2(path, "utf8")); - 写入本地文件dist/draw2code-gateway.js:9666
await mkdir2(dirname2(options.descriptorPath), { recursive: true, mode: 448 }); - 删除本地文件dist/draw2code-gateway.js:9667
await rm(options.descriptorPath, { force: true }); - 执行外部命令来自配置/变量dist/draw2code-gateway.js:9675
const child = spawn(process.execPath, [options.entry], { - 删除本地文件dist/draw2code-gateway.js:9685
await rm(lockPath, { force: true }); - 删除本地文件dist/draw2code-gateway.js:9690
if (lock !== null) await rm(lockPath, { force: true }); - 读取本地文件dist/draw2code-gateway.js:9742
value = JSON.parse(await readFile3(this.path, "utf8")); - 读取本地文件dist/draw2code-gateway.js:9753
canonical = await realpath2(candidate.path); - 写入本地文件dist/draw2code-gateway.js:9779
await mkdir3(dirname3(this.path), { recursive: true, mode: 448 }); - 写入本地文件dist/draw2code-gateway.js:9781
await writeFile2(temporary, `${JSON.stringify({ version: 1, workspaces: rows }, null, 2)} - 发起网络请求dist/draw2code-gateway.js:9945
const workerFetch = async (worker, path, init = {}) => await fetch( - 发起网络请求dist/draw2code-gateway.js:9978
const response = await fetch(`http://127.0.0.1:${worker.port}/canvas-workspaces?root=${encodeURIComponent(session.root)}`, { - 发起网络请求dist/draw2code-gateway.js:10200
const switched = await fetch(`http://127.0.0.1:${worker.port}/canvas-workspace-token`, { - 发起网络请求dist/draw2code-gateway.js:10241
const upstream = await fetch(`http://127.0.0.1:${worker.port}${req.url ?? url2.pathname}`, { - 删除本地文件dist/draw2code-gateway.js:10346
if (current.nonce === descriptor.nonce) await rm2(options.descriptorPath, { force: true }); - 发起网络请求dist/draw2code-mcp.js:18820
return await this.request(r, resultSchema, requestOptions); - 发起网络请求dist/draw2code-mcp.js:18985
const result = await this.request(request, resultSchema, options); - 发起网络请求dist/draw2code-mcp.js:18997
const createResult = await this.request(request, CreateTaskResultSchema, options); - 发起网络请求dist/draw2code-mcp.js:19170
return this.request({ method: "tasks/get", params }, GetTaskResultSchema, options); - 发起网络请求dist/draw2code-mcp.js:19178
return this.request({ method: "tasks/result", params }, resultSchema, options); - 发起网络请求dist/draw2code-mcp.js:19186
return this.request({ method: "tasks/list", params }, ListTasksResultSchema, options); - 读取本地文件dist/draw2code-mcp.js:27085
const info = await stat(path); - 读取本地文件dist/draw2code-mcp.js:27087
const value = JSON.parse(await readFile(path, "utf8")); - 读取本地文件dist/draw2code-mcp.js:27144
const info = await stat2(path); - 读取本地文件dist/draw2code-mcp.js:27147
owner = JSON.parse(await readFile2(path, "utf8")); - 写入本地文件dist/draw2code-mcp.js:27168
await mkdir2(dirname(options.descriptorPath), { recursive: true, mode: 448 }); - 删除本地文件dist/draw2code-mcp.js:27169
await rm(options.descriptorPath, { force: true }); - 执行外部命令来自配置/变量dist/draw2code-mcp.js:27177
const child = spawn(process.execPath, [options.entry], { - 删除本地文件dist/draw2code-mcp.js:27187
await rm(lockPath, { force: true }); - 删除本地文件dist/draw2code-mcp.js:27192
if (lock !== null) await rm(lockPath, { force: true }); - 执行外部命令来自配置/变量dist/draw2code-mcp.js:27306
execFile(launcher.command, launcher.args, (error2) => resolve3(error2 === null)); - 读取本地文件dist/workspace-registry.js:19
value = JSON.parse(await readFile(this.path, "utf8")); - 读取本地文件dist/workspace-registry.js:30
canonical = await realpath(candidate.path); - 读取本地文件dist/workspace-registry.js:48
const canonical = await realpath(path); - 写入本地文件dist/workspace-registry.js:56
await mkdir(dirname(this.path), { recursive: true, mode: 448 }); - 写入本地文件dist/workspace-registry.js:58
await writeFile(temporary, `${JSON.stringify({ version: 1, workspaces: rows }, null, 2)} - 写入本地文件dist/workspace-registry.js:60
await rename(temporary, this.path); - 写入本地文件dist/workspace-registry.js:61
await chmod(this.path, 384);
扫描于 2026/11/2(1 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-client-runtime@deepseek-ai/dsh-host-webserver@deepseek-ai/dsh-llm@deepseek-ai/dsh-session@deepseek-ai/dsh-system-prompt@deepseek-ai/dsh-tools@deepseek-ai/dsh-workspace给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
功能亮点
- 人机协作原型设计
- 画板共改原型
- 生成前端页面
为 DeepSeek Harness 打造人机协作原型设计。
README
由 DeepSeek 最新模型翻译生成Draw2Code · 画码
面向 DSH、Codex 与其他 MCP Agent 的人机协作原型工具:先通过结构化提问把产品需求想清楚,再让 Agent 和用户在同一张 Excalidraw 画板上共同修改低保真原型,最后基于已经确认的画板生成并验收前端页面。
Draw2Code 使用一个共享 Core 和按需启动的本地 daemon。DSH 与 Codex 不互相依赖,但同时运行时会打开同一份工作区数据。
为什么需要 Draw2Code
Vibe Coding 很容易从一句模糊需求直接跳到代码,结果往往是页面范围没想清楚、用户手工删除的内容被 Agent 恢复,或者 Agent 声称已经画完但用户看不到结果。Draw2Code 把协作过程拆成四个边界明确的阶段:
1. Create:澄清产品
- 当用户提出一个新产品想法时,draw2code_create 先提取已经明确的事实,再由 Agent 围绕场景、差异化、核心闭环和关键风险提出产品专属问题。
- 每题先给产品判断,再提供有真实取舍的可点击选项;不会固定重复询问模块和页面,信息足够即停止,最多 10 题。可跳过单题、直接整理简报,也可在 ready 后只调整受影响的产品决策。
展开完整文档(README 剩余 1 节)
Draw2Code · 画码 面向 DSH、Codex 与其他 MCP Agent 的人机协作原型工具:先通过结构化提问把产品需求想清楚,再让 Agent 和用户在同一张 Excalidraw 画板上共同修改低保真原型,最后基于已经确认的画板生成并验收前端页面。 Draw2Code 使用一个共享 Core 和按需启动的本地 daemon。DSH 与 Codex 不互相依赖,但同时运行时会打开同一份工作区数据。 为什么需要 Draw2Code Vibe Coding 很容易从一句模糊需求直接跳到代码,结果往往是页面范围没想清楚、用户手工删除的内容被 Agent 恢复,或者 Agent 声称已经画完但用户看不到结果。Draw2Code 把协作过程拆成四个边界明确的阶段: 1. Create:澄清产品 - 当用户提出一个新产品想法时,draw2code_create 先提取已经明确的事实,再由 Agent 围绕场景、差异化、核心闭环和关键风险提出产品专属问题。 - 每题先给产品判断,再提供有真实取舍的可点击选项;不会固定重复询问模块和页面,信息足够即停止,最多 10 题。可跳过单题、直接整理简报,也可在 ready 后只调整受影响的产品决策。 - 工具把结构化 PrototypeBrief 确定性渲染成包含逐页结构、真实 mock 数据、交互关系和验收方式的完整项目简报;最后用一张页面范围确认卡明确列出将绘制的页面,用户确认前不会创建画板。 - 视觉品牌、颜色和正式前端实现仍留到生成阶段。 2. Open & Demonstrate:用户画给 Agent 看 - 用户可以直接访问固定本机入口 http://127.0.0.1:64775/;画码会从本机已登记工作区恢复,无需先从 Codex 建立连接。用户说“打开画码,我自己画一下”时,由独立的 $draw2code-open 快速入口只调用一次 draw2code_open,不加载 Create、Update、Generate、代表页复核或质量门禁;工具内部的短期定向连接只用于把当前任务的 workspace/board 精确绑定到当前标签页,随后回到可收藏的干净根地址。 - 用户说“我画好了”后,Agent 先用 draw2code_read 读取并复述页面、组件和交互关系,再按用户指令继续修改或生成。 - URL 就绪、daemon 启动和画布真正可见是三个不同状态;只有侧边栏实际显示后才报告“已经打开”。 3. Update:共同画原型 - draw2code_update action=write 把语义化低保真页面写入 Excalidraw,用户可以直接拖动、删除、改字或添加便签;action=review 使用写入返回的 reviewToken 记录可见复核,不改画板 revision,也不发布新的 reveal。 - Agent 更新前先读取当前画板索引;draw2code_read 默认只返回页面、关系、分层容量和当前 review/pending 状态,不把整板元素塞进上下文。需要内容时再按 pageIds、elementIds、区域或近期 revision 增量读取。已有 3 页以上画板的独立小改动不会被旧的首次代表页门禁拦截。 - Create 会逐页给出核心任务、首屏信息、主操作和语义组件蓝图;3 个及以上页面返回结构化 drawingPlan,强制先生成代表页,复核通过后才生成其余页面。若 Agent 仍误提前提交其余页面,Update 会返回 pendingUpdateId 暂存该批 ops,复核后以 action=commit_pending 直接提交,避免整批 JSON 被丢弃和重新生成。 - 单次 ops 默认限制为 500 项或 512 KiB,超出时在布局检查前返回 reduce_batch_size;这与完整画板容量分开治理。画板不再设置正常业务容量:默认 32 MiB 仅进入 large 提示,256 MiB 只作为异常输入保险丝,且可配置提高;元素异常保护同步提高到 50,000。磁盘缩进、内联资源、元素数与 gzip checkpoint / delta 历史分别报告。真正触发保险丝时返回 archive_or_split_board,不会误导 Agent 反复缩小同一批次。 4. Generate:生成并验收前端 - draw2code_generate 开始前先用普通对话询问是否有参考风格图片;随后读取最新画板,让用户多选页面范围,并结合参考图或产品语义智能推荐整体视觉方向。 - 原型不完整时先回画板修复;不会在 HTML 中偷偷补出未经确认的产品功能。 - 原型定义产品事实,前端使用 Grid/Flex、内容流和响应式约束重新排版,不复制 Excalidraw 绝对坐标。 - 页面生成后必须逐页截图,检查控制台、DOM、布局和核心流程;工具会读取 workspace 内的截图/DOM 快照、核对 SHA-256 与视口尺寸,并直接比较未选页面块哈希,证据门禁通过后才会报告完成。 主要能力 - DSH 右侧 dsh-better-sidebar 中的完整 Excalidraw 画布; - 多画板创建、切换、删除、历史版本和导出;独立画码可在本机已明确注册的工作区之间切换,各工作区的画板仍原位保存; - Agent 工具:draw2code_list、draw2code_read、draw2code_create、draw2code_update、draw2code_generate、draw2code_open; - 用户手工编辑与 Agent 更新之间的三方合并和冲突确认; - 成功更新后自动展开画码、激活目标画板,同一事件不会反复抢焦点; - 无 Frame 新页面模型:普通矩形页面外框、外部页面标题、自由组件和不被裁切的手绘跨页箭头;旧命名 Frame 画板继续兼容; - 原型质量门禁:文字高度、页面边界、底部导航、绑定文字、mock 数据、信息密度、视觉层级、点击区域和页面节奏; - 内置 153 个产品原型素材,安装后无需单独下载素材库; - Create 和 Generate 都支持可恢复的结构化流程,会话中断后不会重新询问已完成选择; - 画板、项目 brief、生成设置和前端产物都保存在用户自己的工作区中。 系统要求 - 已安装 DeepSeek Harness,且 dsh web 可以正常启动; - Node.js 22 或更高版本; - DSH 宿主使用 dsh-better-sidebar 0.12.3 或更高版本;Codex 可独立安装,不需要 DSH。 在 Codex 中使用首版通过本地 personal marketplace 安装,不提交公共 Plugin 目录。开发构建并把仓库的 Plugin 产物同步到 ~/plugins/draw2code 后执行: codex plugin add draw2code@personal 安装后新建 Codex 任务,使 Skills 与六个 MCP 工具进入新会话。用户不需要进入单独的 Plugin 页面或手输工具名:选择“打开 Draw2Code / 画码”快速入口,或直接说“打开画码”,只走单次 Open;“用 Draw2Code 帮我设计一个习惯追踪 App”“帮我画原型”等产品任务才进入综合工作流。普通“帮我做一个 App”不会自动进入 Draw2Code。 draw2code_open 在 MCP/Codex 中默认使用 presentation=handoff:MCP 连接初始化时会后台预热固定入口网关和按需启动的动态 worker。普通用户直接访问 http://127.0.0.1:64775/ 即可;网关会为 loopback 浏览器自动建立 HttpOnly、SameSite=Strict 的内部会话,从本机登记表恢复最近工作区,并在写请求中校验同源 CSRF token。用户无需理解或手工维护 session。Open 工具仍会返回只含短期 code 的定向连接 URL,以保证多个 Codex 任务可分别定位自己的 workspace/board;浏览器完成定位后立即回到干净根地址,最终地址不保留 workspace 路径、画板名、token 或 code。网关或内部 worker 重启后刷新固定地址即可自动恢复。工具不注册会生成打不开卡片的静态 openai/outputTemplate;只有显式选择 presentation=browser 时才尝试启动外部浏览器。后续更新通过 WebSocket 刷新,断线时继续使用 revision polling,不反复打开窗口。localhost 会永久重定向到规范的 127.0.0.1 地址;端口被其他程序占用时会明确报错,不会静默换端口。 Draw2Code 把画板注册到 dsh-better-sidebar 提供的右侧栏中。DSH 当前只会自动启用用户直接安装的 bundle,不会自动启用另一个插件的传递依赖,因此下面两条安装命令都必须执行。 从 GitHub 安装 仓库提交了经过测试的 dist/ 和 lib/ 运行产物,因此普通用户不需要在本地构建: 1. 安装右侧栏基础插件 dsh plugin --profile web add dsh-better-sidebar 2. 安装 Draw2Code 稳定版 dsh plugin --profile web add github:guchang/draw2code#v0.5.0 3. 重启 dsh web;如果已经在运行,请先停止旧进程再启动 dsh web 如果是第一次初始化 DSH web profile,pnpm 可能会先暂停 node-pty 的原生构建,并在 $DSH_HOME/profiles/web/pnpm-workspace.yaml(默认 ~/.dsh/profiles/web/pnpm-workspace.yaml)写入待确认项。按提示把它设为允许后重跑安装命令: allowBuilds: node-pty: true 这是 DSH 基础运行时的首次构建授权,不是 Draw2Code 额外执行的安装脚本。 刷新 DSH 页面后,在右侧栏的 + 菜单中选择“画码”。随后可以直接在对话中说: 我想创建一个新的习惯追踪 App。 Agent 会先调用 draw2code_create 澄清需求;确认 brief 后创建独立画板并开始绘制。 从源码安装(开发者) git clone https://github.com/guchang/draw2code.git cd draw2code npm ci npm test dsh plugin --profile web add dsh-better-sidebar dsh plugin --profile web add link:$(pwd) 修改源码后运行 npm run build,再重启 dsh web 并刷新页面。 工作区数据 Draw2Code 只在当前宿主注册的工作区内创建以下内容: draw2code/ ├── .excalidraw.json # 原型画板 ├── .active-board.json # 当前画板指针 ├── .projects/ # Create 项目 brief 与版本 ├── .generations/ # Generate 可恢复会话 └── .generate-settings/ # 项目级视觉方向 draw2code-pages/ └── /index.html # 生成并验收的前端 Demo 这些运行数据已加入 .gitignore,不会进入 Draw2Code 源码仓库。 独立画码会记住 Codex、DSH 或其他宿主明确注册过的工作区,并在画板菜单中显示当前工作区及其他已经存在画板的工作区,包括名称、完整路径和画板数量;插件缓存和没有画板的空 root 不进入切换菜单。切换工作区前会先落盘当前待保存内容,再换取目标工作区的新短期凭据;原凭据不能直接读取其他工作区。Draw2Code 不扫描整台电脑,也不会自动复制、合并或迁移不同工作区的画板。 协作与安全边界 - 文件访问受 HostContext workspace 门禁限制,root 经 realpath 后不能越过已注册工作区; - 固定入口网关与动态 worker 都只监听 loopback,descriptor 权限为 0600;固定入口为本机浏览器自动建立 HttpOnly、SameSite=Strict 会话,并以同源 CSRF token 保护写请求;Codex 定向连接码一次有效且短时过期,只用于任务/标签页隔离。网关代浏览器持有短期 workspace-scoped token,最终 URL 不暴露 root、board、token 或 code;独立画码切换工作区时仍必须经过已授权会话; - DSH /api/draw2code/* 是隐藏 token 的同源 daemon 代理; - draw2code_update 使用原子写入、revision 和回读验证,不直接修改未知文件; - 涉及用户手工修改的危险覆盖会返回确认状态,不会静默写入; - Draw2Code 不上传画板、brief 或生成页面到外部服务; - 画布不设日常业务上限;默认 50,000 元素与 256 MiB 规范内容只作为异常输入保险丝,32 MiB 仅提示按页面读取,均可通过环境变量提高;单次 Agent ops 另设 500 项 / 512 KiB 传输预算。 异常保险丝与传输预算可独立调整:DRAW2CODE_MAX_SCENE_BYTES、DRAW2CODE_SOFT_SCENE_BYTES、DRAW2CODE_MAX_ELEMENTS、DRAW2CODE_MAX_OPS_BYTES、DRAW2CODE_MAX_OPS、DRAW2CODE_MAX_VERSION_STORAGE_BYTES。它们不是套餐或业务配额;日常大画板不会因为磁盘缩进或历史快照增长而突然不可写。 架构 Codex Skill / DSH tools / future MCP clients │ Host Adapters │ stable loopback gateway · dynamic worker │ Draw2CodeRuntime.execute() │ Create state · Scene/Project store · CAS/merge · Generate gate │ existing workspace files (no migration) DSH Host 构建到 dist/index.js;Codex stdio MCP、固定入口网关与动态 worker 分别构建到 dist/draw2code-mcp.js、dist/draw2code-gateway.js、dist/draw2code-daemon.js;共享浏览器画板构建到 lib/canvas.html。Plugin 清单位于 .codex-plugin/plugin.json,跨宿主约束只有一份真值:workflow contract。 开发与验证 npm ci npm run typecheck npm test npm run build npm pack --dry-run - 产品行为说明:BDD.md - Generate 产品流程:GENERATE_PRODUCT_FLOW.md - Gherkin 契约:features/draw2code.feature License Draw2Code 代码采用 Apache License 2.0。内置 Excalidraw 素材继续遵循其上游 MIT License,作者与来源见 THIRD_PARTY_NOTICES.md。
预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
guchang/draw2code
与 Agent 在画板上共改原型并生成前端页面
相关插件 · 浏览器
智能体插件框架deepseek-ai/deepseek-harness
DeepSeek Harnessdsh是由 DeepSeek AI 开发的开源 agent harness智能体框架。
★ 270,422已汉化DSH
71.9
多设备 Agent 看板YaoApp/yao
✨ 所有 Agent 和工作区集中管理,覆盖你的每一台设备。任务看板追踪进度,桌面端、移动端、浏览器、API…
★ 8,147已汉化DSH
71.2
可扩展侧边栏工作台omdsh-dev/DSH-better-sidebar
DSH 的双工作台:原生右侧栏 + 自绘底部面板,并把 ctx.betterSidebar 服务开放给所有插件
★ 4,761已汉化DSH
71.1
浏览器自动化桥Tencent/BrowserSkill
你的 Agent。你的浏览器。连接,就能协作。
★ 9,967已汉化DSH
71
AI 小说创作工作台EthanYoQ/AI-Novel-Writer
AI 小说作家 / AI Novel Writer
★ 1,591已汉化DSH
70.7
智能体浏览器引擎platonai/Browser4
DeepSeek Harness 集成
★ 1,175已汉化DSH
70.4
这是你的仓库?认领条目