关于 Nodiom Cloud
Nodiom Cloud 提供基于 Markdown 的共享看板和文档,让 AI Agent 提交工作供人审核。它沿用 Nodiom 的结构化选择器,并支持跨 Agent 与人员共享状态、记录操作归属和审批流程。
主要功能
- 01共享 Markdown 文档
- 02由标题和清单项构成的看板
- 03原子化领取任务
- 04人工批准或退回步骤
- 05Agent 操作归属与审核提醒
适用场景
- 01协调多个 Agent 之间的任务交接
- 02在 Agent 工作继续前审核成果
- 03跨设备和人员共享文档状态
MCP 相关
Nodiom Cloud 提供 MCP 端点,可供 Claude、ChatGPT、Cursor 和 VS Code 等客户端连接。MCP 工具支持共享文档与看板工作流,包括 Agent 交接和人工审核。
配置
远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。
https://api.nodiom.md/mcpclaude mcp add --transport http synexiom-labs-nodiom-cloud https://api.nodiom.md/mcp{"mcpServers":{"synexiom-labs-nodiom-cloud":{"type":"http","url":"https://api.nodiom.md/mcp"}}}在 Cursor 中打开{"servers":{"synexiom-labs-nodiom-cloud":{"type":"http","url":"https://api.nodiom.md/mcp"}}}在 VS Code 中打开{"mcpServers":{"synexiom-labs-nodiom-cloud":{"type":"http","url":"https://api.nodiom.md/mcp"}}}{"mcpServers":{"synexiom-labs-nodiom-cloud":{"serverUrl":"https://api.nodiom.md/mcp"}}}命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。
工具
详细介绍
nodiom
以结构化方式读取和写入 Markdown。
让 AI Agent 能够可靠地处理文档结构,无需脆弱的字符串操作。
Nodiom 将 Markdown 作为树来读取和编辑。标题是分支;列表、段落、表格和代码块是节点。你可以像查询 DOM 一样,使用类似路径的选择器定位节点,并修改它,而不必重写文件的其余部分,也不会破坏其他内容。
三种使用方式
| 是什么 | 位置 | |
|---|---|---|
| 库(本仓库) | 在 Node.js 中对文件或字符串进行 Markdown 结构化读写 | npm install @synexiom-labs/nodiom |
| nodiom-mcp | 一个 MCP 服务,让 AI Agent 能对本地 Markdown 文件执行这些操作 | Synexiom-Labs/nodiom-mcp |
| Nodiom Cloud | 托管服务:让 Agent 与人员共享状态。AI Agent 在同一看板上提交已完成的工作,由监督人员审批或退回 | nodiom.md |
在此处,结构化状态位于 Markdown 中;在 Cloud 中,Agent 与人员共享状态。三种方式都使用相同的引擎和选择器。
问题所在
当 AI Agent 或任何程序需要修改 Markdown 文档时,通常会将整个文件加载为字符串,用正则表达式查找相应部分,插入新文本,然后祈祷格式没有被破坏。若两个写入者同时操作,其中一个的修改就会悄无声息地丢失。
Nodiom 将 Markdown 视为它本来的样子:一棵树。你指定位置(# Project > ## Tasks > li[contains="c_7f3a"])和操作(读取、写入、追加、删除),该节点之外的内容不会改变。
快速示例
import { Nodiom } from '@synexiom-labs/nodiom';
const doc = await Nodiom.fromFile('./project.md', { lock: true });
// Read everything under a heading (the heading line itself isn't included)
const summary = doc.read('# Project > ## Summary');
// Add a task
doc.append('# Project > ## Tasks', '- [ ] Deploy to staging');
// Tick it, addressed by its text rather than its position
doc.write('# Project > ## Tasks > li[contains="Deploy to staging"]', '- [x] Deploy to staging');
// Remove the first finished item
doc.delete('# Project > ## Done > li[0]');
// Write back to disk and release the lock
await doc.save();
安装
npm install @synexiom-labs/nodiom
要求: Node.js 18+
选择器
选择器是由 " > " 分隔的路径,由多个片段组成,每个片段都与文档结构中的一个节点匹配。
标题片段
"# Project Alpha" → The H1 "Project Alpha" and all its content
"## Tasks" → A top-level H2 named "Tasks"
"# Project Alpha > ## Tasks" → The H2 "Tasks" nested under H1 "Project Alpha"
# 字符的数量必须与标题在文档中的层级相符。
范围。“## Tasks 下的内容”指该标题之后、下一个同级或更高级标题之前的所有内容,这也是读取时的方式。
**重复标题。**如果同一范围内出现多个相同标题,单独使用标题片段会产生歧义并抛出 AmbiguousSelectorError;nodiom 绝不会悄悄选中第一个。可使用父标题限定,或按索引指定其中一个;索引从零开始,也支持负数,例如 li[n]:
"# Doc > ## Notes[0]" → The first "## Notes" under "# Doc"
"# Doc > ## Notes[1]" → The second
"# Doc > ## Notes[-1]" → The last
索引必须紧挨文本。带空格的 "## References [1]" 会被当作字面标题匹配;若标题本身确实是 Tasks[1],则始终会优先尝试按字面匹配。
**Frontmatter。**文档顶部的 YAML(---)或 TOML(+++)区块会被识别为 frontmatter。它不会出现在 tree() 中,也无法通过选择器定位;任何编辑都不会改变其中的任何字节。
元素片段
"## Tasks > li[0]" → First list item under ## Tasks
"## Tasks > li[-1]" → Last list item (negative indexing)
"## Notes > p[0]" → First paragraph under ## Notes
"## Arch > table[0]" → First table under ## Arch
"## Arch > code[0]" → First code block under ## Arch
元素类型:p、li、code、blockquote、table、hr。
内容片段
索引表示位置,而非身份。如果在你计算出 li[2] 和写入该位置之间,另一个写入者向同一部分追加了内容,那么该索引现在指向不同的节点,写入就会悄无声息地落到错误的位置。
只要有多个写入者会操作同一文档,就应改为按内容定位:
'## In Progress > li[contains="c_7f3a"]' → The list item whose text contains "c_7f3a"
"## Notes > p[contains='TODO']" → The paragraph containing "TODO"
子字符串会与节点自身的源文本进行匹配,并且在范围内必须只匹配一个节点。如果匹配到多个节点,则会抛出 AmbiguousSelectorError,而不是选中第一个:这正是为了避免悄无声息地编辑多个候选项之一。
API
加载
// From a file
const doc = await Nodiom.fromFile('./wiki.md');
// From a file, with an advisory lock (see Concurrency)
const doc = await Nodiom.fromFile('./wiki.md', { lock: true, lockTimeout: 5000 });
// From a string: no filesystem, safe for serverless
const doc = Nodiom.fromString(markdown);
读取
// The Markdown under a selector. For a heading, its body without the heading
// line, so write(sel, read(sel)) changes nothing.
const content: string = doc.read('# Project > ## Summary');
// Each list item's source under a selector, marker and nested lines included:
// ['- [ ] Task A', '- [x] Task B\n - [x] sub-step']
const items: string[] = doc.readList('# Project > ## Tasks');
// Whether a location exists, and what it is
const info = doc.query('# Project > ## Tasks');
// { exists: true, type: 'heading', depth: 2, childCount: 7, index: 3 }
// The heading outline
const outline = doc.tree();
// [{ heading: 'Project', depth: 1, children: [{ heading: 'Tasks', depth: 2, children: [] }] }]
更改
修改操作会返回 this,因此可以链式调用。
// Replace the body of a section; the heading is kept
doc.write('# Project > ## Summary', 'On track for Q3.');
// Replace one list item. Text without its own bullet keeps the item's marker
// and checkbox: this stays "- [ ] Final review (Friday)"
doc.write('## Tasks > li[contains="Final review"]', 'Final review (Friday)');
// Add after the last node in a section, separated by a blank line
doc.append('# Project > ## Tasks', '- [ ] Task C');
// Remove a node, or a heading together with its whole section
doc.delete('# Project > ## Done > li[0]');
doc
.append('# Project > ## Tasks', '- [ ] Task D')
.delete('# Project > ## Done > li[-1]');
你的内容会按原样插入(仅进行修剪),不会重新序列化;未涉及的内容则保持逐字节不变。
保存
const markdown: string = doc.toString();
await doc.save(); // write back to the original file, release any lock
await doc.saveAs('./out.md'); // write to a new file (keeps the original lock)
await doc.unlock(); // release the lock explicitly
错误
import {
NodiomError, // base class of the errors below
SelectorNotFoundError, // nothing matched; .suggestions has near misses
SelectorParseError, // the selector itself is malformed
AmbiguousSelectorError, // more than one node matched
LockError, // couldn't get the file lock in time
} from '@synexiom-labs/nodiom';
try {
doc.read('# Project > ## Taks'); // typo
} catch (e) {
if (e instanceof SelectorNotFoundError) {
console.log(e.selector); // "# Project > ## Taks"
console.log(e.suggestions); // ["Tasks"]
}
}
保真度
只有在不会损坏未触及内容的前提下,结构化编辑器才有用。Nodiom 会在节点的确切位置编辑源文本,而不是重新打印整个文档,因此编辑节点之外的格式、空格、列表标记和转义都保持原样。在 2,369 个真实 Markdown 文件(早期用户自己的笔记)样本中,每个文件往返后都与原文件逐字节一致。
在 AI Agent 中使用
在自己的机器上:nodiom-mcp
nodiom-mcp 将这些操作作为 MCP 工具(nodiom_tree、nodiom_read、nodiom_read_list、nodiom_query、nodiom_write、nodiom_append、nodiom_delete)提供给 Claude Code、Claude Desktop、Cursor 或任何 MCP 客户端,用于处理磁盘上的 Markdown 文件。
claude mcp add -s user nodiom -- npx -y @synexiom-labs/nodiom-mcp
跨机器、Agent 和人员:Nodiom Cloud
当 Agent 在不同机器上运行,或来自不同供应商时,他们需要一个共享状态的地方。Nodiom Cloud 就是这样一个地方,基于此引擎构建:
- 共享文档:多个 Agent 可以同时编辑,彼此不会覆盖对方的更改。
- 看板:列对应标题,卡片对应待办事项,因此看板就是一份可在任何地方阅读的 Markdown 文档。Agent 通过一次原子操作认领卡片,因此不会有两个 Agent 领取同一项任务。
- 人工审核步骤。 Agent 将已完成的工作移交到审核列,然后停止。人员可以批准,也可以附上备注退回;Agent 会读取备注,而且 Agent 无法跳过此步骤。
- 归属信息和提醒。 每次写入都会标明执行操作的 Agent,审核提醒会通过电子邮件、Slack 或 Discord 发出。
在 Claude、ChatGPT、Cursor 或 VS Code 中,只需添加一个地址并登录即可连接,无需复制密钥:
https://api.nodiom.md/mcp
提供免费套餐。各应用的设置说明见 nodiom.md,完整工具参考见文档。
并发
在单台机器上,nodiom 通过 proper-lockfile 获取建议式文件锁。fromFile 会在读取前获取锁,因此两个进程无法同时读取旧版本并相互覆盖。
const doc = await Nodiom.fromFile('./shared-wiki.md', { lock: true });
// Other nodiom processes locking this file wait, or throw LockError after lockTimeout
doc.append('## Log', `- ${new Date().toISOString()} Agent B finished the task`);
await doc.save(); // writes and releases the lock
该锁适用于同一台机器上的 Node.js 进程。跨机器或使用无服务器函数时,请使用 Nodiom Cloud,它会在数据库文档行锁下执行每次写入。
对比
| 方案 | 结构化读取? | 结构化写入? | 往返保真度 | 无服务器 | 并发 |
|---|---|---|---|---|---|
fs.readFile + 正则表达式 | 否 | 脆弱 | 取决于正则表达式 | 否 | 否 |
gray-matter | 仅 Frontmatter | 仅 Frontmatter | 是 | 是 | 否 |
| 数据库 | 否 | 否 | 否(会丢失 Markdown) | 是 | 是 |
| 向量数据库 | 仅语义 | 否 | 否 | 是 | 是 |
| nodiom | 是 | 是 | 是 | 是(fromString) | 同一台机器 |
| Nodiom Cloud | 是 | 是 | 是 | 是 | 跨机器 |
路线图
计划中的事项,顺序未定:
- 支持带位置控制的
insert():在同级节点之前或之后插入。 diff():对文档的两个版本进行结构化比较。- 通配标题选择器(
## *Tasks*)。 - 1.0:稳定的选择器语法和 API。
发布记录见 CHANGELOG。
贡献
欢迎贡献。对于较大的更改,请在提交 PR 前先提出 issue。
git clone https://github.com/Synexiom-Labs/nodiom
cd nodiom
npm install
npm test
许可证
MIT — Synexiom Labs Inc.
源自为 Cortexiom 推理架构提供支持的状态层。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。