在 Notion 区块与 Markdown 之间双向互转
30/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/10/3 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add yuehancn/dsh-tool-notion该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:待复验
是什么
dsh 原生插件 · tool
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 30 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
读写/删除本地文件
查看扫描证据(共 7 处,展示前 7 条)
- 读取本地文件lib/index.js:981
const raw = await readFile(resolve(join(config.workDir, args.path)), "utf8"); - 写入本地文件lib/index.js:1007
await mkdir(dirname(target), { recursive: true }); - 写入本地文件lib/index.js:1008
await writeFile(target, result.markdown, "utf8"); - 读取本地文件lib/index.js:1069
source = await readFile(resolve(join(config.workDir, args.path)), "utf8"); - 写入本地文件lib/index.js:1091
await mkdir(dirname(target), { recursive: true }); - 写入本地文件lib/index.js:1092
await writeFile(target, `${JSON.stringify({ object: "list", results: blocks }, null, 2)}\n`, "utf8"); - 读取本地文件lib/index.js:1159
const raw = await readFile(resolve(join(config.workDir, args.path)), "utf8");
扫描于 2026/10/31(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/schemastery@deepseek-ai/cordis@deepseek-ai/dsh-tools给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
README
由 DeepSeek 最新模型翻译生成dsh-tool-notion
Notion 区块与 Markdown 的双向转换插件,给 DeepSeek Harness 用。
没有 API 客户端,没有外部依赖。 你从 Notion 导出(或从 API 拿到)一份区块数组,它转成 Markdown;你有一份 Markdown,它转成可以直接回写的区块数组。中间不做网络请求 —— 所以它能离线跑、能进测试、也不会因为一个 token 过期就整个失败。
notion_to_md blocks → Markdown
notion_to_blocks Markdown → blocks
notion_validate 两边都收,报出结构问题 notion_status 先说清楚哪些块类型它认得
为什么要单独做这个
看起来是「数组 ↔ 字符串」,写起来才知道反了。
Notion 的段落是一组富文本 run,Markdown 的段落是一个字符串。
展开完整文档(README 剩余 1 节)
dsh-tool-notion
Notion 区块与 Markdown 的双向转换插件,给 DeepSeek Harness 用。
没有 API 客户端,没有外部依赖。 你从 Notion 导出(或从 API 拿到)一份区块数组,它转成 Markdown;你有一份 Markdown,它转成可以直接回写的区块数组。中间不做网络请求 —— 所以它能离线跑、能进测试、也不会因为一个 token 过期就整个失败。
notion_to_md blocks → Markdown
notion_to_blocks Markdown → blocks
notion_validate 两边都收,报出结构问题 notion_status 先说清楚哪些块类型它认得
为什么要单独做这个
看起来是「数组 ↔ 字符串」,写起来才知道反了。
Notion 的段落是一组富文本 run,Markdown 的段落是一个字符串。
Notion: [ {content:"Hello ",annotations:{}}, {content:"world",annotations:{bold:true}} ]
Markdown: "Hello world"
一个段落能有任意多个 run,每个 run 带自己的 bold / italic / strikethrough / underline / code,还能各带各的链接。要变成一行文本,就得在每一个注解边界切开插标记;要从一行文本变回去,就得把相邻的、注解相同的 run 合并才能得到 Notion 原本想要的形状。
这两个方向不是互逆操作。 一个是切分,一个是合并。这就是本插件里代码量的主要来源,也是测试里绝大多数断言在盯的地方。
安装
cordis.patch.yml
plugins:
dsh-tool-notion:
workDir: "."
outputDir: "notion-output"
dsh plugin add dsh-tool-notion
工具
notion_status
在转换之前先问它。它返回:
- toMarkdown —— 能转成 Markdown 的块类型列表和它们的标记
- toBlocks —— 能转回区块的 Markdown 构造
- annotations —— 认识哪五个注解键
- callouts —— 五种 callout 标签 ↔ emoji 的对应表
- directories —— workDir / outputDir 的绝对路径
先看这个,就能把「不支持的块类型」变成一条已知边界,而不是转换到一半发现丢了东西。
notion_to_md
收一份区块数组,产出 Notion 风格的 Markdown。
// 参数(三选一) { blocks: [ … ] } // 区块数组本身 { blocksJson: "[…]" } // 原始 API 响应(字符串),内部 JSON.parse { results: { … } } // 带 results 的 API 包装对象,会自动认出来
为什么要有 blocksJson: defineTool 会在 execute 跑之前就校验参数类型,所以 blocks 这个参数只能收数组。而 Notion API 返回的是一个带 results 的对象。把对象塞进 blocks 会在你还没机会处理之前就被框架拒掉(invalid arguments: "blocks" must be an array)。所以留了一个字符串入口,让原始响应能原样递进来。
输出会在遇到无法表示的结构时显式报告,而不是默默吞掉。
notion_to_blocks
收 Markdown,产出区块数组。
{ markdown: "# Title\n\nSome bold text." }
{ path: "notes/export.md" } // 相对路径,按 workDir 解析
产出的数组可以直接喂给 Notion 的 append 接口。
notion_validate
不转换,只检查。收 blocks / blocksJson,报出:
- 区块数组的结构错误(缺 type、payload 形状不对)
- 不认识的块类型
- 空 run、缺 content 的 run
- callout 标签无法回读的情况
要判断的是「这份数据能不能安全往返」,不是「这份数据合不合法」。 所以它盯的是会静默丢信息的地方。
五个注解,和有顺序的标记
annotations 有五个键:bold italic strikethrough underline code。
写标记的时候顺序是定死的,因为顺序变了意思就变了:
| 场景 | 写法 | 为什么 |
| 粗体 + 斜体 | x | 不是 __x__,粗体在外、斜体在内 |
| 行内代码 | 最内层 | 反引号会压制其他所有标记,所以代码必须包在最里面 |
| 删除线 | x | |
INLINE_MARKERS 表是按从长到短扫的( 先于 ,___ 先于 _),否则 x* 会被解析成三个独立的 。
反斜杠奇偶性决定标记是不是活的 —— 这是行内扫描里最容易写错的一条:
"a \ b c" → 7 个字符:反斜杠转义了 ,标记没生效 "a \\ b c" → 4 个字符:反斜杠自己被转义,标记是活的
同一个形状,一个反斜杠之差,结果完全不同。测试里两种情况都钉了。
反引号要加宽围栏:如果内容里本身有反引号,外面的围栏就得比里面长。行内代码和围栏代码块都是这个规则。
Callout 的标签 ↔ emoji 必须是双射
这是开发过程中测试自己抓出来的一个真 bug,值得单独说。
Markdown 写 callout 用的是语义标签:
[!WARNING]
这会删库。
Notion 存 callout 用的是图标 emoji。所以中间要做一次映射。第一版写了单向映射:解析时把 WARNING 存进 icon,回写时又把 emoji 写进 icon,再读回来时期待看到标签 —— 结果每一个 [!WARNING] 都变成了 [!NOTE]。
一个警告被静默降级成了提示。这种 bug 不会报错、不会崩,只会在某天有人因为没看到警告而删了库。
修法是把它做成真正的双射:
const CALLOUT_EMOJI = { NOTE: "📘", TIP: "💡", IMPORTANT: "❗", WARNING: "⚠️", CAUTION: "🛑" };
const EMOJI_CALLOUT = Object.fromEntries(
Object.entries(CALLOUT_EMOJI).map(([label, emoji]) => [emoji, label])
);
function calloutLabel(payload) { // 读:emoji → 标签 const icon = payload?.icon; const emoji = typeof icon === "string" ? icon : icon?.emoji;
if (emoji === undefined) return "NOTE";
return EMOJI_CALLOUT[emoji] ?? "NOTE"; // 不认识的 emoji 退回 NOTE,不是丢 callout }
五个标签全部在测试里钉了往返。另外还钉了两种输入形状:icon 是裸字符串、icon 是 { type:"emoji", emoji } 对象 —— Notion 两种都会返回。
通用的教训:凡是「语义值 ↔ 表示值」的映射,都要有反向表和反向断言。单向映射静静吃掉信息的方式是无声的。
parseNotionId 要认带 slug 的 URL
Notion 分享链接把 id 粘在人类可读的 slug 后面:
https://www.notion.so/My-Page-1f2e3d4c5b6a7c8d9e0f1a2b3c4d5e6f
^^^^^^^ 这一段不是 hex
所以不能拿最后一段去匹配 ^[0-9a-f]{32}$ —— 会返回 null。正确做法是先剥掉 query 和 hash,再在整串里找 32 位 hex(或者带连字符的标准 UUID 形式):
const withoutQuery = text.split(/[?#]/u)[0];
const match = /([0-9a-f]{32}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/iu
.exec(withoutQuery);
一个被测试逼出来的框架规则
defineTool 的 JSON schema 里,每一个嵌套的 { type: "object" } 都必须显式声明 additionalProperties —— 固定形状写 false,自由映射写 true。不写就抛:
JsonSchemaError: … additionalProperties must be explicitly true or false
这个坑在本插件里踩了三次:嵌套的 directories 对象、blocks 数组的 items、以及 byType 这种自由映射。三次都是同一个错误,只是位置不同。数组的 items 也算嵌套对象,也要声明。
测试
node _test/run-all.mjs
三个套件跑在各自独立的子进程里 —— 每个套件都会用 new Function 重建一份插件副本,同进程会互相污染模块缓存。
notion logic: 230 passed, 0 failed
notion integration: 117 passed, 0 failed
notion end to end: 56 passed, 0 failed
共 403 条断言。
三个套件的分工:
- test-logic.mjs(230) —— 纯函数白盒。行内扫描(含反斜杠奇偶性)、run 合并、注解边界切分、围栏加宽、callout 双射、缩进层级、块校验。
- test-integration.mjs(117) —— 通过 defineTool 注册后的真实工具调用。参数 schema 形状(required 被提到顶层、parameters.properties 才是子项)、注册开关、错误信息、workDir 解析规则。
- test-e2e.mjs(56) —— 一份故意难伺候的 "Deploy Runbook" 文档,从 Markdown 一路走到区块再走回来。里面塞了:带 # 注释的 shell 围栏、嵌套围栏、含代码的粗体、粗体链接、嵌套清单、callout、字面星号。
一条钉住的路径规则
path 参数按 workDir 解析 —— 绝对路径会被拼到 workDir 后面,而不是被当成绝对路径使用。这不是 bug,这正是「读不到配置根目录之外」的保证。集成测试里有一条断言专门钉这个行为,免得以后有人「顺手修好」它。
白盒断言的代价与收益
测试通过 new Function 把插件的内部函数(parseInline、mergeRuns、calloutLabel …)取出来直接断言。代价是 harness 要维护一份 INTERNALS 清单,漏一个就是 plugin.X is not a function。收益是不用为了测一个纯函数去套一层 JSON 管道 —— 上面那个 callout bug 就是这么被抓到的。
目录
dsh-tool-notion/
├── lib/index.js 插件本体(4 个工具 + 全部转换逻辑) ├── cordis.patch.yml 插件加载配置 ├── _test/ │ ├── harness.mjs 用真实 dsh-tools 重建 apply,并暴露内部函数 │ ├── test-logic.mjs 230 条 │ ├── test-integration.mjs 117 条 │ ├── test-e2e.mjs 56 条 │ └── run-all.mjs 三个套件各自跑在子进程 ├── LICENSE MIT └── README.md
许可证
MIT © yuehancn
本插件属于 DeepSeek Harness 工具插件系列。同一系列还有 dsh-tool-comfyui、dsh-tool-gzh-publisher、dsh-tool-ocr、dsh-tool-media、dsh-tool-subtitle、dsh-tool-invoice、dsh-tool-qrcode、dsh-tool-epub、dsh-tool-podcast、dsh-tool-mining。*预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
yuehancn/dsh-tool-notion
在 Notion 区块与 Markdown 之间双向互转
相关插件 · 工具 / 效率
破甲管控台kingcharleslzy-ai/dsh-purge
dsh-purge Community · DeepSeek Harness 社区维护版:宿主兼容、状态诊断、提示词管理、更新与本机备份回滚。基于小杨 / YuJunZhiXue 的 dsh-purge。
★ 3,801已汉化DeepSeek 客户端
74.5
网文长卷lingfengQAQ/webnovel-writer
基于 Claude Code 的长篇网文辅助创作系统,解决 AI 写作中的「遗忘」和「幻觉」问题,支持 200…
★ 7,636已汉化DSH
72.2
多智能体协作HKUDS/AutoSwarm
AutoSwarm: Unify Your Agents. Unify Your Context (Codex · Claude Code · DeepSeek Harness · Muse · Grok · WorkBuddy· nanobot · and more)
★ 2,082已汉化DSH
71.7
Vox 拼贴视频生成Alisa0808/vox-director
🎬 Vox Director拼贴动效导演
★ 2,129已汉化DSH
71.4
Agent 演化引擎Q00/ouroboros
智能体 OS:智能体自己变聪明,我们只守住底线:面试准入、分阶段评估、带预算的进化循环。MCP 服务器,14 种运行时:Claude Code、Codex CLI、Gemini CLI、OpenCode、Copilot、Kiro 等。
★ 6,292已汉化DSH
71.3
智能体运行框架路由HarnessRouter/harnessrouter
智能体运行框架的统一接口。
★ 3,800已汉化DSH
71.2