dsh-tool-excalidraw
v0.2.0
Published
Model-facing Excalidraw tools for DeepSeek Harness: create whiteboards, add shapes/text/arrows/lines, update or remove elements, and export SVG or JSON.
Maintainers
Readme
dsh-tool-excalidraw
让 DeepSeek Harness 的 agent 通过工具调用创建和编辑 Excalidraw 白板:
- 新建
.excalidraw白板文件(Excalidraw v2 JSON,可直接在 excalidraw.com 打开继续编辑) - 添加图形元素:矩形 / 椭圆 / 菱形 / 文本 / 箭头 / 直线 / 手绘笔迹
- 按 id 更新或删除元素(移动、缩放、改文字、改颜色与手绘样式)
- 导出为独立 SVG(roughjs 手绘渲染,浏览器可直接查看)或 JSON
渲染使用 roughjs —— 与 Excalidraw 相同的手绘渲染引擎,在纯 Node 中运行(jsdom 提供 SVG DOM),无需浏览器。
工具一览
| 工具 | 作用 |
|---|---|
| excalidraw_create | 新建白板文件(可带初始元素与画布尺寸) |
| excalidraw_add_elements | 追加元素,返回新元素的 id |
| excalidraw_update_elements | 按 id 更新元素(移动 / 缩放 / 改文字 / 改颜色 / isDeleted: true 删除) |
| excalidraw_group | 按名称组合元素(同组元素在编辑器中联动,成员自动重排为连续) |
| excalidraw_organize | 智能层级组织:按几何包含自动建立父子/多层组合,检测同层重叠,可选自动避让 |
| excalidraw_get | 列出元素(includeFull: true 时返回完整记录) |
| excalidraw_export | 导出 SVG(手绘渲染)或 JSON |
元素参数
{
"type": "rectangle", // rectangle | ellipse | diamond | text | arrow | line | freedraw
"x": 10, "y": 20, // 左上角坐标(像素)
"width": 200, "height": 100,// 尺寸(rectangle/ellipse/diamond 必填;text 传 width 时自动换行到框内)
"text": "Hello\nWorld", // text 类型必填,\n 换行
"points": [[0, 0], [120, 40]], // line/arrow/freedraw 的相对顶点
"strokeColor": "#1971c2", // 描边颜色
"backgroundColor": "#a5d8ff",// 填充颜色(transparent 不填充)
"fillStyle": "solid", // solid | hachure | cross-hatch | zigzag | dotted
"strokeStyle": "solid", // solid | dashed | dotted
"strokeWidth": 1,
"roughness": 1, // 手绘粗糙度 0–2
"fontSize": 20, // text 字号
"group": "row-1" // 可选:分组名,同名元素在编辑器里一起移动(如框与其内文字)
}分组与防重叠
- 组合:给视觉关联的元素(一个框和它的文字、标题和它的框)传同一个
group名即可; 插件为每个名字生成确定性的组 id,之后add同名元素会自动并入同一组。 对已有元素用excalidraw_group { file, groups: [{ name, elements: [ids] }] }事后组合, 工具会自动把同组成员重排为连续(Excalidraw 要求)。 - 智能层级组织:
excalidraw_organize { file, group?, resolveOverlaps?, minGap? }从几何关系自动推断结构——- 层级组合(
group: true,默认):构建包含树,每个容器(矩形/椭圆/菱形)自动成为一组,包住 其内部文字与嵌套框;嵌套框的组再包进外层框的组(groupIds从内到外),形成父子孙多层, 每层的同层元素各自成组。移动外层框,整棵树跟随;移动内层框,只有它和它的内容跟随。 - 同层重叠检测:始终返回同一层元素(兄弟/顶层)之间互相覆盖的清单(
overlapPairs); 父子包含不算重叠。文本元素因宽度估算偏大而超出容器也会被发现(文本的width现在始终取 实际估算宽度,仅用于自动换行,不再撑大包围盒)。 - 自动避让(
resolveOverlaps: true):把重叠的兄弟元素沿最小重叠轴推开,留minGap(默认 20px) 间距,移动元素时其整棵子树(内部文字/嵌套框)一起平移。
- 层级组合(
- 防重叠:文本元素传
width(所在容器的内宽)会自动按宽度换行,不再溢出/遮挡; 宽度估算按字符分档(CJK 全宽、拉丁/数字/空格各自近似),比等宽估算更接近真实渲染。
安装
插件是一个标准 DSH 组合包(bundle),用 dsh plugin 装进你的 profile:
dsh plugin --profile web add ./excalidraw-plugindsh plugin 会把本目录链接进 profile、安装 roughjs / jsdom 依赖,并把插件行追加进 dsh.profile.bundles。之后重启 dsh(GUI 需刷新页面),agent 即可调用 excalidraw_* 工具。
从 GitHub 安装
插件是 TypeScript 实现,从 GitHub 安装时 pnpm 会运行 prepare 脚本构建 lib/ 产物,因此首次安装需要为构建授权(pnpm ≥10 默认拒绝运行 git 依赖的 prepare 脚本):
dsh plugin --profile web add github:<你的用户名>/dsh-tool-excalidraw
# 首次 add 会失败并打印所需包键,把它加进 profile 的 pnpm-workspace.yaml:
# allowBuilds:
# dsh-tool-excalidraw: true
# 然后重新 add也可以手动接入:把构建产物(
lib/)放到任何可 import 的位置,并在 profile 的cordis.patch.yml里insert一行{ id: excalidraw, name: 'dsh-tool-excalidraw' }(与deepseek-harness-zh-cn相同的方式)。
@deepseek-ai/dsh-tools是 optional peer:pnpm 会自动安装(registry 的 0.1.0-rc.x), 或复用 DSH host 的(Node 模块解析向上找到~/.dsh/profiles/node_modules),版本兼容>=0.1.0-rc.5 <0.2.0。
使用示例
- 创建:
excalidraw_create { file: "/tmp/arch.excalidraw", elements: [...] } - 追加:
excalidraw_add_elements { file: "/tmp/arch.excalidraw", elements: [{ type: "arrow", x: 210, y: 60, points: [[0,0],[90,10]] }] } - 查看:
excalidraw_get { file: "/tmp/arch.excalidraw" } - 导出:
excalidraw_export { file: "/tmp/arch.excalidraw", format: "svg" }→ 得到/tmp/arch.svg
examples/ 目录有一个完整的示例白板(excalidraw-demo.excalidraw + 渲染好的 excalidraw-demo.svg / .png)。
开发
项目由 pnpm 管理,源码为 TypeScript(src/),构建产物输出到 lib/:
pnpm install # 安装依赖(含 typescript、@types/*;optional peer 的 dsh-tools 会自动装上)
pnpm build # tsc 编译 src/ → lib/(js + d.ts)
pnpm test # 构建 + 两套测试test/smoke.mjs—— 数据层 + SVG 渲染冒烟测试(无需 dsh 运行时)test/tools.spec.mjs—— 工具层测试(5 个工具全生命周期 + schema 校验),import 构建产物lib/
工具层测试直接 import 构建产物(
lib/),pnpm test已包含构建步骤。 roughjs 的类型由src/types/roughjs.d.ts提供(其自带声明在 NodeNext 下与 CJS 包不匹配)。
限制
- 导出格式为 SVG(浏览器可直接查看);PNG 导出可用
sharp/rsvg等工具二次转换 - 文本宽度按字符估算(无 DOM 测量),与 Excalidraw 的精确排版可能有细微差异
- 不处理图片(image)与嵌入文件元素
