首页 / 全部技能 / 文档与知识 / excalidraw-use
文档与知识 · daymade/claude-code-skills

excalidraw-use

Place existing images onto an Excalidraw whiteboard, turn a slide deck into clean per-slide images first, and inspect what is inside a .excalidraw file. Use whenever someone mentions Excalidraw, a whiteboard or canvas, a .excalidraw file, 白板 or 画板 — especially "put these screenshots on my board", "add my old workshop images to the canvas", "space them out so I don't have to adjust spacing by hand", "turn these slides into images I can draw on", or "what's in this scene file". Also use when a talk or workshop is delivered by hand-drawing over screenshots on a shared canvas, or when someone needs a pick tray of many images to choose from. Not for generating a diagram from a text description — that is a different job.

L4 高权限 · 分级授权与审计V1 已固定执行命令联网读写文件Dual-pass Audit
Author daymadeGitHub daymade/claude-code-skills ↗Stars 1385License MIT(仓库根 LICENSE 文件;GitHub API spdx MIT;Copyright (c) 2025 daymade)Commit d5c4678cb5
管控等级描述 skill 的能力与建议配套的管控措施,不代表质量或安全认证,也不表示可用或不可用。是否使用由用户和管理者决定。不同 agent 宿主对权限的约束各不相同,请勿假定宿主一定会提供安全兜底。第三方 skill 版权归原作者,使用风险由使用者自行承担。

1实现原理 · 为什么它能做到

核心把戏是把「一堆图摆上白板」做成一个独立 scene 文件(pick tray 挑选托盘),而不是去改用户那块正在用的板子。整个 Job 1 就是把 N 张图等比缩放、居中排进网格、base64 内嵌进 files 映射。

excalidraw-use/scripts/build_scene.py
The scene is a *pick tray*: open it in a second Excalidraw tab, select what you want, and copy-paste it onto your real board. It is never written into the board file itself — see references/paste_workflow.md for why that matters.
这里在做什么:把「交付物」和「用户的现场文件」在物理上分开,从根上避免合并冲突式的事故;SKILL.md 用一整节『Never write into their live board file』来强化这条纪律。

图片尺寸不靠外部库,直接从文件头解析:PNG 读 IHDR 的 8 字节宽高,JPEG 走段扫描到 SOF 标记再取宽高。

excalidraw-use/scripts/build_scene.py
"""(width, height) in pixels for PNG or JPEG, read from the file's own header."""
实现细节:PNG 分支用 struct.unpack(">II", data[16:24]);JPEG 分支逐段跳过 0xFF 标记直到 0xC0-0xCF 系列。读不出就 raise ValueError,由 build() 转成 SystemExit 拒绝写文件——「宁可失败也不写坏文件」的取向贯穿全脚本。

每次写入后强制从磁盘 read-back 自证:元素数、fileId 与 base64 内容的 sha1 是否一致、绘制长宽比与原图是否一致、两两包围盒是否重叠,任一不符即 SystemExit。

excalidraw-use/scripts/build_scene.py
Deliberately re-reads from disk rather than checking the in-memory objects: the failure this catches is a bad write, and an in-memory assert cannot see one.
verify() 的 docstring 把理由讲得很清楚。SKILL.md 把这个输出行当作可用性信号:『Expected output — if you do not see the `verified:` line, the file is not usable』。

去重与排除都按内容哈希(sha256)而不是文件名,改过名的副本同样被识别。

excalidraw-use/scripts/build_scene.py
skip = {hashlib.sha256(p.read_bytes()).hexdigest() for p in exclude if p.exists()}
同一函数里还有 seen 字典做批内去重(digest in seen 即跳过并记录『skipped (identical content)』),SKILL.md 对应条目:『The check is by image content, not filename, so a renamed copy is still caught.』

--template-from 解决的是「官方 JSON schema 没写 image element 自己的字段」这个真空:从用户自己的板子里深拷贝一个真实 image element 来拿字段集。

excalidraw-use/scripts/build_scene.py
for el in scene.get("elements", []): if el.get("type") == "image" and not el.get("isDeleted"): return copy.deepcopy(el)
references/scene_file_format.md 用一个『Image elements — NOT documented』小节展开:『The published schema describes the `files` map but does not enumerate the image element's own fields.』,并给出方法论总结:『copy a working instance from the destination rather than reconstructing one from documentation that does not cover it.』

交付路线刻意选剪贴板而非 Open / 拖拽:后两者会整块替换用户当前场景。

excalidraw-use/references/paste_workflow.md
The clipboard carries elements *and* their embedded image payloads, so copy-paste merges instead of replacing:
SKILL.md 同一件事的措辞:『at excalidraw.com, both *Open* and drag-and-drop **replace the entire current scene**』。paste_workflow.md 还给出为什么磁盘文件不等于现场板子的两条独立理由:浏览器标签页不监听文件变更、以及导出的文件通常比浏览器状态旧。

Job 2 的截图之所以不丢内容,靠三件事:从源码正则抽 slide id 与 fragmentCount、每页全量 page.goto(而不是改 hash)、以及用 computed opacity 而不是 innerText 判断 fragment 是否真的显示出来。

excalidraw-use/scripts/shoot_deck.mjs
// Fragments that never revealed stay in the DOM at opacity 0, so any check // based on text content reports them as present while the picture is blank. // Measure what actually renders.
对应的实测判据是 parseFloat(getComputedStyle(e).opacity) < 0.9,未揭示的会被记进 unrevealed 并打印 WARNING(--strict 时改为非零退出)。references/deck_screenshot_pitfalls.md 第 2 节把这个反直觉点单列:『document.body.innerText // shows all four years — looks correct』而截图只有一年。

两种 fragment 推进模式被判定为互斥,脚本层直接禁止混用:--advance 只接受 pdf 或 keys。

excalidraw-use/scripts/shoot_deck.mjs
console.error(`--advance must be "pdf" or "keys", got ${ADVANCE}`)
原因写在文件头注释与 SKILL.md:『Never both — under `?pdf=1` the fragment index is already at maximum, so extra key presses move to the *next* slide and you silently capture the wrong content.』

Job 3 的 inspect_scene.py 是只读体检:类型计数、内嵌 payload 体积、fileId 与内容 sha1 是否仍然一致、以及已占用 extent。extent 是给用户「往哪滚再粘贴」用的。

excalidraw-use/scripts/inspect_scene.py
# Excalidraw keys files by a content hash; when it holds, a # mismatch means the payload was swapped without rekeying.
report 里同时检测反向问题(『file {fid} is embedded but no live element uses it』)与长宽比失真(abs(el['width']/el['height'] - px/py) > 1e-3)。whole file 只读不写,异常被 except Exception 兜住只为报告不崩。

split_scene.py 先整除再按实际分片数命名(避免出现「1-of-4 却没有 4-of-4」),并且每个分片只携带自己引用到的 files,否则分片体积毫无收益。

excalidraw-use/scripts/split_scene.py
"files": {e["fileId"]: files[e["fileId"]] for e in part},
上下文注释写着『Carry only the files this part actually references, or every chunk would be as large as the original and the split would buy nothing.』;分片后同样跑一遍 read-back 校验(元素数、fileId 匹配、长宽比、两两不重叠),并用 total != len(elements) 兜住「分片丢元素」。

2核心能力

01把大批已有图片摆成规整网格的白板托盘(可指定列数/格边长/间距)
02按内容哈希排除已在板上的图(改名副本也认得出)
03从真实板子抽取 image element 字段集(补齐官方 schema 没写的部分)
04把 Vite/React 幻灯片逐页截成干净 PNG(隐藏讲者专用装饰、展开分步动画)
05只读体检 .excalidraw:元素构成、内嵌体积、坏引用、内容哈希是否还对得上、占用范围
06大场景分片(解决剪贴板在几十 MB 时卡死或静默失败)
07给出「文件安全、渲染未验证」的诚实交付说明与粘贴操作序列

3外部依赖

KindDependency / Endpoint
packagepuppeteer(Job 2 无头浏览器,第三方 npm 包,需可被解析,skill 不负责安装)
clinode(执行 shoot_deck.mjs)
clinpx vite build(把 deck 从源码构建到 /tmp/deck-dist)
clipython3 / python3 -m http.server(本地静态服务器)
networkhttp://127.0.0.1:8080(无头浏览器取页地址;注意 http.server 默认监听 0.0.0.0)
networkhttps://excalidraw.com(不是调用点:只作为生成 scene 的 source 字段字符串,以及文档里指向站点的手工操作说明)
networkhttps://docs.excalidraw.com/docs/codebase/json-schema(参考文献里引用的官方文档链接,非运行时代码)

4管控建议与判定依据

建议配套管控措施
面向 Agent
分级或逐次授权,建议在沙箱运行
面向管理者
审批流程、可回滚、全程审计
判定依据(文件路径与关键语句摘要)
  • scripts/: 执行本地脚本或命令: excalidraw-use/scripts/build_scene.py(python3,纯标准库)
  • 网络端点: 声明外部网络访问: http://127.0.0.1:8080(本地静态服务,供无头浏览器加载 /tmp/deck-dist;python3 -m http.server
  • 本地工作目录: 文件读写与工件输出: <out>.excalidraw(build_scene.py 生成,含 base64 内嵌图片;会先 mkdir 父目录)
审计说明: 。按统一分档:① 三个主脚本(build/split/inspect)是纯标准库 Python,只读本地图片与 .excalidraw、只写用户指定的输出文件,零网络、零凭证(全目录 token 扫描 subprocess/os.environ/API_KEY/TOKEN/SECRET/.env/cookie/keychain 均零命中);② 唯一的「执行外部代码」是 Job 2 的 puppeteer 无头浏览器,跑的是用户自己的 deck 页面且不回落 Node——不构成任意代码执行面;③ 无凭证读取、无密钥、无埋点、无第三方镜像,域名级只出现 excalidraw.com(还只是写进 scene 的 source 字符串)与 docs.excalidraw.com(文档链接)。使其不是绿色的两点:Job 2 依赖第三方 npm 包 puppeteer(skill 不自带、要求环境里已可解析),以及 SKILL.md 指定的 python3 -m http.server 8080 默认绑定 0.0.0.0,会把 /tmp/deck-dist 目录列表与内容暴露给同网段——这是文档层面的操作建议,不是脚本行为,但值得留意。

5第二遍独立确认

  • [ok] external_deps 每条的调用点是否真实存在 — puppeteer(shoot_deck.mjs 第 1 行 import)、node(shebang)、npx vite build 与 python3 -m http.server(SKILL.md Job 2 命令行)、127.0.0.1:8080(--url 实参)全部实读命中,非推测。
  • [discrepancy] excalidraw.com 是否被当作网络端点误报 — 〔第二遍标注:第一遍易误报,已修正〕build_scene.py 与 split_scene.py 里的 https://excalidraw.com 只是写入 scene 的 "source" 字段值,脚本从不请求该域名;README/文档里的链接同理。已在 external_deps 中显式标注『不是调用点』,避免把字符串常量当成外发。
  • [discrepancy] exec( 命中是否意味着命令执行 — 〔第二遍标注:扫描器误报,已澄清〕shoot_deck.mjs 的 const frag = /fragmentCount:\s*(\d+)/.exec(m[2]) 是 JavaScript RegExp.prototype.exec,属正则匹配,不执行进程。全目录无 child_process / spawn / execSync / subprocess。
  • [ok] 『不写用户现场板子』是否只是口号 — 代码层:build_scene.py 只写 --out 指定路径,从不读入并回写用户的板子文件;文档层:SKILL.md 有独立小节『## Never write into their live board file』并给出两条独立理由(标签页不监听文件、磁盘文件通常旧于浏览器状态)。
  • [ok] 写入后校验是否真做(不是文档承诺) — build_scene.py 的 verify() 在 build() 末尾被调用且从磁盘重读;split_scene.py 对每个分片做同样校验。两道都是可执行代码,失败即 SystemExit。
  • [unlocatable] python3 -m http.server 绑定范围 — 〔第二遍标注:文档未声明,属解释器默认行为〕SKILL.md 只写 python3 -m http.server 8080,未写 --bind 127.0.0.1;http.server 的默认绑定是 0.0.0.0。这是 CPython 的既定行为而非本 skill 的代码,故标注为「文档未声明」并在 security.reason 中提示,不写成 skill 缺陷。
  • [ok] Job 2 的 --advance 互斥是否代码强制 — shoot_deck.mjs 中 if (!['pdf','keys'].includes(ADVANCE)) 报错退出,且 query 只在 ADVANCE === 'pdf' 时拼 ?pdf=1;SKILL.md 有对应警告段,代码与文档一致。
  • [ok] 元数据(license/stars/last_push) — 取自同 repo 同 commit 的池内既有值(MIT、1385★、2026-09-09T12:33:29Z);本次未联网复核 gh。

6结论

  • 零 Python 第三方依赖 + 写入即自证,产物不会「悄悄坏掉」。
  • 把「交付」这一步当作主要风险来设计(剪贴板而非 Open),是为数不多把用户现场数据安全写进流程的 skill。
  • 对官方 schema 的空白有清醒认知,并给出可执行的替代方案(--template-from)而非猜测字段。
  • 截图路径把「看起来对其实错」的失败模式逐条堵死(opacity 判据、全量 goto、advance 互斥、按键徽标等待、双路隐藏装饰)。
  • 诚实标注能力边界:结构可验、渲染不可验,并要求把这句话讲给用户。
  • 适合: 适合这样的场景:手上有一批截图/照片要成批摆到现有白板上(手工拖到崩溃),或者要把一套 Vite/React 幻灯片先变成干净的单页图再当素材用,或者需要在不打开浏览器的情况下先摸清某个 .excalidraw 里到底有什么、有没有坏引用。也适合把它当作「Excalidraw 文件格式的实测笔记」来读——references/scene_file_format.md 里那份「官方文档没写的部分」清单对自研工具有直接价值。
    不适合: 不适合「从一句话画一张架构图/流程图」——SKILL.md 明确把这个任务推给别的工具,本 skill 不做生成;不适合需要 agent 独立完成到底、无法让用户手工粘贴的场景;不适合非 Vite/React 或 slide 注册表形状不同的 deck(shoot_deck.mjs 的正则只认 { id: '...', fragmentCount: n } 这种条目,找不到就直接退出);不适合期望 agent 打开 excalidraw.com 帮你看一眼效果的场景——那恰好会顶掉用户当前的板子。
    Install Agent-Ready
    ① Verified Mirror 2026-09-15
    Option A · Direct DownloadDownload excalidraw-use.tar.gz
    sha256: 0c4ae44eb4c051d3…
    Option B · Copy Guide for Agent
    install.json
    agent 读 JSON 指南后会自动从本站下载安装,无需更多说明。
    ② Upstream GitHub
    GitHub Repo ↗
    Pinned to commit d5c4678cb5.