从微信对话到线上博客:我用 Hermes Agent 打通 Trilium 自动化发布链路
记录如何用 Hermes Agent 打通微信对话到 Trilium 博客的自动化发布链路:MCP 配置、图片处理、云端 poller 轮询、GitHub + EdgeOne 部署,以及数据一致性修复实践。
前言
你有没有过这样的体验:灵感来的时候在微信里跟 AI 聊得火热,聊完却要手动整理成文章、找图、排版、上传、发布,一套流程走下来热情已经凉了一半。我一直在折腾自己的博客(longBlog,基于 Astro 搭建),最大的痛点就是写文章的成本:内容生产、图片处理、发布流程,每一步都在消耗精力。
这篇文章记录的是我最近做的一件事:把「微信对话 → 文章整理 → Trilium 笔记 → 自动发布到互联网」整条链路打通。现在我在微信里跟 Hermes Agent 说一声"把这篇发到博客",剩下的全部自动化完成。
先放一张全貌架构图,本文就是围绕这条链路展开的:

背景:我的博客架构
在开始之前,先交代一下我的博客整体架构。longBlog 是一套三层分离的自动化系统:
本地 Trilium(NAS,写作后台)
↓ 自动同步
云端 Trilium(blog.ssaw.top)
↓ poller 轮询 + 同步脚本
GitHub 仓库
↓ EdgeOne Pages 自动部署
https://huowenlong.com/这个架构里,Trilium 是文章编辑后台,也是标签状态管理中心。文章写在哪、打什么标签、是否发布,都由 Trilium 里的笔记属性控制。
第一部分:把 Hermes 接进 Trilium
为什么需要 Hermes
Trilium 本身是一个强大的笔记软件,支持脚本、标签、关系、附件,甚至可以通过 ETAPI(REST API)完全编程控制。但它没有一个"智能代理"来帮你完成端到端的任务——比如从微信对话里提取内容、整理成文章、处理好图片、再导入 Trilium。
Hermes Agent 是我运行在 NAS 上的 AI 代理,通过微信接入。它的价值在于:能理解上下文、能调用工具、能自主完成多步骤任务。
配置 MCP 连接
让 Hermes 操作 Trilium,最优雅的方式是 MCP(Model Context Protocol)。Hermes 内置 MCP 客户端,我在 config.yaml 里配置了 Trilium 的 MCP 服务器:
mcp_servers:
trilium:
url: "http://192.168.10.100:25399/mcp"
headers:
Authorization: "Bearer <ETAPI_TOKEN>"
timeout: 180
connect_timeout: 60配置完成后,Hermes 启动时会自动发现 Trilium 的 23 个工具:搜索笔记、读取内容、创建笔记、设置属性、移动笔记、删除笔记等。这些工具直接以 mcp_trilium_* 的形式出现在 Hermes 的工具箱里,我可以在微信里直接指挥它操作 Trilium。
连通性验证
配置完成后第一件事是验证连通性。我在微信里让 Hermes 搜索 Trilium 里的笔记,它成功返回了我的博客文章列表,还能完整读取《Overleaf 部署实战指南》这样的长文。这证明了:微信 → Hermes → Trilium 的链路完全打通。
第二部分:图片处理(关键细节)
博客文章通常需要配图。Trilium 的图片是作为笔记的附件存储的,引用格式是 api/attachments/{attachmentId}/image/{title}。
三种图片来源
- 微信发的图片:用户直接发图给 Hermes,图片自动缓存在 NAS 上,Hermes 可以读取
- 自己寻找的图片:截图、网页图片,Hermes 可以自行获取
- Excalidraw 绘制:通过 Docker 里的 Excalidraw 应用辅助绘制示意图
上传流程
MCP 工具里没有"上传附件"的能力,所以图片上传要走 Trilium 的 ETAPI。实测正确的格式是 JSON body + base64 编码:
import json, base64, urllib.request
payload = {
"ownerId": "<笔记ID>", # 图片所属的笔记
"role": "image", # 角色:图片
"mime": "image/png",
"title": "image.png",
"content": base64.b64encode(图片数据).decode(),
"position": 1
}
req = urllib.request.Request(
"http://192.168.10.100:25399/etapi/attachments",
data=json.dumps(payload).encode(),
headers={"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"},
method="POST"
)上传成功返回 201 + attachmentId,然后在笔记正文里插入引用即可。注意不要创建子文件夹放图片——Trilium 的附件机制天然把图片挂在笔记下,这正好符合博客的需求。
第三部分:文章发布流程
创建笔记
Hermes 通过 MCP 在 longBlog 目录下创建一篇新笔记:
parentNoteId: zB8WioyKlvOw # longBlog 目录
title: 文章标题
content: Markdown 正文
type: text设置模板
longBlog 有一个 longTemplate 模板笔记,定义了博客需要的所有属性:publish、tags、summary、slug、sync、publishedAt、updatedAt 等。新笔记通过 ~template 关系关联到模板:
type: relation
name: template
value: MC7PtiChdF5S # longTemplate 笔记 ID设置标签
发布前需要设置几个关键标签:
| 标签 | 值 | 说明 |
|---|---|---|
| publish | false | 先不发布,等确认 |
| tags | 逗号分隔 | 文章标签 |
| summary | 一句话摘要 | 博客列表页显示 |
| slug | URL 标识 | 通常 = 标题 |
自动同步到云端
Trilium 的同步是全自动的数据库级同步:本地改动会自动推送到云端 Trilium(blog.ssaw.top),全程不需要手动点击任何按钮。
发布流程中没有任何手动同步操作——用户只需要聊天和确认内容,剩下的全部自动化。
勾选发布
确认文章无误后,把 publish 标签设为 true,Trilium 自动同步到云端,云端 poller 检测到发布状态变化后自动完成后续所有工作。
第四部分:云端自动化链路(重点)
这里我要重点介绍一下云端服务器上的自动化——这是整个系统最核心的部分,也是我踩过最多坑的地方。
主链路:服务器轮询
最初的设计是依赖 Trilium 内部的后端脚本(runOnAttributeChange)在标签变化时主动发 webhook。但实际使用中发现,TriliumNext 新版的后端脚本事件触发不一定稳定,而且其他客户端同步来的属性变化不一定能触发服务器端脚本。
所以我把主链路改成了服务器端轮询:
poll_trilium_changes.py
↓ 每分钟轮询 Trilium ETAPI,检测变化
run_sync_and_build.sh
↓ 调度器:并发锁 + 准备 Git 工作区 + 构建
sync_trilium_posts.py
↓ 同步文章/附件,生成 Astro 数据,push GitHub
GitHub 仓库 → EdgeOne Pages 自动部署三个脚本各司其职:
poll_trilium_changes.py(主触发器):每分钟调用 Trilium ETAPI,扫描 longBlog 根节点下的文章,读取标题、标签、正文,与上一次快照对比。检测字段包括:title、publish、sync、pinned、aiRefresh、contentHash。
run_sync_and_build.sh(调度器):把触发事件转成一次完整同步任务。它做并发锁(避免多个任务同时操作 Git 工作区)、准备 Git 工作区、调用同步脚本、执行构建验证。
sync_trilium_posts.py(核心同步):真正把 Trilium 内容转成博客仓库数据的脚本。它读取文章树、正文、标签,下载并本地化附件,生成 Astro 前端数据文件(trilium-posts.content.generated.ts 和 trilium-posts.meta.generated.ts),判断 Git 是否有变化,然后自动 commit + push。
关键的标签系统
云端同步脚本处理这些 Trilium 标签:
| 标签 | 作用 |
|---|---|
| publish | 控制文章是否发布到博客 |
| sync | 手动请求同步 |
| pinned | 控制是否置顶 |
| aiRefresh | 请求刷新 AI 摘要 / 标签 |
| syncStatus | 同步状态(publishing、published、error) |
| syncHash | 内容同步 hash,判断是否变化 |
| publishedAt / updatedAt | 发布时间 / 更新时间 |
| slug | 文章 URL 标识 |
| summary | 摘要 |
| tags | 标签 |
排序规则:置顶文章优先;非置顶文章按原始 publishedAt 排序。
systemd 定时器
云端通过 systemd 定时器实现每分钟轮询:
longblog-poller.timer → 每分钟触发
longblog-poller.service → 执行一次 poll_trilium_changes.py排查文件
这套系统最实用的部分是完整的日志体系。出问题时按这个顺序查:
tail -80 /root/longblog-automation/runtime/logs/poller.log # poller 是否发现变化
cat /root/longblog-automation/runtime/reports/last_report.json # 同步/构建/push 结果
cd /root/longblog-automation/workspace/current && git log -5 --oneline # GitHub 提交poller 日志的关键信息:
no changes notes=14 # 没有检测到变化
changes=1 event=pinned_changed noteId=xxx title=xxx changed=['pinned'] # 检测到变化
runner ok; snapshot updated # runner 执行成功
ERROR runner failed; snapshot not updated # 同步失败,快照不更新,下次继续尝试为什么不用 webhook 作主链路
旧的 webhook 服务(trilium_sync_webhook.py)仍然在运行,但不再作为稳定主链路依赖。原因:
- TriliumNext 新版本中,后端脚本事件触发不一定稳定
- 其他客户端同步来的属性变化,不一定触发服务器端
runOnAttributeChange - 后端脚本安全权限较高
所以现在的理解是:poller 是主链路,webhook 是备用实时入口。
第五部分:数据一致性修复
在整套系统跑起来的过程中,我遇到过一个很典型的问题:升级 Trilium 后同步失败。
现象
升级 Trilium 到 v0.105 后,同步一直失败,日志里大量报错:
ERROR: Cannot find entity for entity change {...}
Sync failed: Cannot read properties of undefined (reading 'entityChange')根因
数据库的 entity_changes 表里有大量"悬空记录"——指向已删除实体的变更记录。这些是升级时 Demo 内容、损坏主题的变更残留。同步时逐个查找这些记录对应的实体,发现不存在 → 报错 → 同步崩溃。
解决
清理 entity_changes 表里的悬空记录(指向不存在实体的记录):
# 对 notes / attributes / branches 三类实体
DELETE FROM entity_changes
WHERE entityName = 'xxx'
AND entityId NOT IN (SELECT id FROM xxx)清理了 1489 条悬空记录后,同步恢复正常。这个坑提醒我:升级后如果同步异常,先检查 entity_changes 表的一致性。
总结
现在我的写作流程变成了这样:
- 在微信里跟 Hermes 聊天,聊出内容
- 让 Hermes 整理成文章(自动处理图片)
- Hermes 通过 MCP 创建 Trilium 笔记、设置模板和标签
- 云端自动完成:自动同步 → poller 轮询 → 构建 → 推送 GitHub → 部署上线
整个链路中,人只需要做一件事:聊天。剩下的内容整理、图片处理、笔记创建、云端发布,全部由自动化完成。
这套系统的核心设计理念是:
- Trilium 是内容中枢:所有文章以笔记形式存在,标签控制状态
- Hermes 是智能入口:理解对话、调用工具、完成任务
- 云端 poller 是可靠触发器:轮询代替 webhook,稳定可靠
- GitHub + EdgeOne 是发布通道:仓库更新即部署
如果你也在折腾个人博客,希望这篇文章能给你一些启发:写博客最贵的不是服务器,是你的注意力。把重复劳动交给自动化,把精力留给真正重要的内容创作。
本文由 Hermes Agent 根据微信对话与实践过程整理,从对话整理到同步上云的完整自动化流程记录。