跳转到内容

Hope 的个人 AI 工作流:用 Telegram、Hermes、Obsidian 和 GitHub 串起收藏与发布

这篇文章写给已经在使用 AI 编程工具、Obsidian 或个人博客,但信息入口和执行动作仍然分散的个人开发者。你可能已经有 Telegram、GitHub、一个知识库和若干 AI 工具,却仍要在多个窗口之间复制链接、整理 Markdown、运行脚本、检查 Git 状态,再手动发布。

本文不是介绍一个“万能聊天机器人”,而是复盘我如何把这些现有工具组织成一套个人执行系统,以及在真正跑通链路时遇到的边界、安全问题和工程取舍。

我的目标不是让 AI 替我决定发布什么,而是让它承担重复、可验证的执行工作:

  • 在 Telegram 中随手发送一个明确指令。
  • 让 Hermes 判断这是收藏、写作还是发布动作。
  • 把资料可靠地保存到 Obsidian PARA Vault。
  • 从 Obsidian 的单篇草稿生成博客内容和附件。
  • 在测试、构建和敏感信息检查通过后等待人工审批。
  • 发布准备完成后等待一次明确的“确认发布”,由系统在同一事务中完成精确 commit 和 fast-forward push。

其中最关键的约束是:**AI 可以执行,但不能越权。**普通链接不能自动写入知识库;status: ready 不能等同于允许提交;一次“确认”也不能同时代表 commit 和 push。

这套工作流最终形成了四层结构:

Telegram DM
-> Hermes Gateway + personal-content Skill
-> 确定性的 article-workflow CLI
-> WeWrite 公众号模板预览与兼容性校验
-> Obsidian PARA Vault / Astro 网站仓库 / Git
-> GitHub main -> 网站部署

每层只做自己擅长的事:

  • Telegram 是低摩擦入口,不保存业务状态。
  • Hermes 理解自然语言、提取网页、生成摘要,并调用固定动作。
  • 仓库 CLI 校验参数、路径、文件变化和审批状态。
  • WeWrite 在发布前生成去除私有 frontmatter 的微信兼容 HTML 预览。
  • Obsidian 是私有内容源,GitHub 只保存公开网站内容。
  • Git 是最后一道可审计、可恢复的发布边界。

实践下来,我认为个人 AI Stack 的价值不在“用了多少模型”,而在于是否把模糊意图转换成了范围明确、结果可检查、失败会停止的工作流。

这次实施最初受到 Jimmy Song 的 Personal AI Stack 启发。他把 OpenClaw 部署在家里的 Mac mini 上,通过不同 Telegram 群组隔离工作、研究、博客等上下文,再由 Agent 统一操作 Gmail、Calendar、Obsidian、GitHub 和知识库。这个思路最有价值的部分不是某个具体产品,而是把 Agent 定位为执行系统和 Chief of Staff,而不是聊天窗口。

我的机器上已经安装了 Hermes,因此没有必要同时维护第二套功能高度重叠的 Agent。最终选择是保留这套架构思想,用 Hermes 替代 OpenClaw:

  • Telegram 继续作为随时可用的入口。
  • Hermes 负责意图理解、工具调用和会话上下文。
  • 仓库 Skill 记录个人工作流规则,而不是修改 Hermes 核心代码。
  • 确定性 CLI 负责高风险文件和 Git 操作。
  • Hermes WebUI 提供本机浏览器入口,但不替代 Telegram。

这样做的直接收益是复用已经配置好的模型、技能、会话和本地权限,减少重复部署。相应的工程要求也更明确:自然语言层不能直接拥有无限制的文件和 Git 权限,所有关键动作都必须落到可测试的仓库接口上。

这套系统运行在一台 Apple M3 Pro、18 GB 内存的 MacBook Pro 上,核心组件如下:

组件职责
Telegram移动端指令入口和审批入口
Hermes Agent自然语言理解、工具调用和工作流编排
Hermes WebUI本机浏览器中的 Hermes 操作界面
ObsidianPARA 知识库、博客草稿和 Web Clips
Firecrawl提取网页标题、元信息和 Markdown 正文
WeWrite发布前审稿提示、公众号主题排版和微信兼容性校验
young-hopeAstro/Starlight 博客仓库和发布脚本
GitHub公开内容版本库和远端发布入口

Obsidian Vault 通过仓库内的 .vault 软链接接入,但 .vault 被 Git 忽略。因此代码可以用统一相对路径访问草稿,GitHub 和网站构建又不会收到整个私人知识库。

Vault 使用 PARA 结构,发布工作流只关心其中几个明确目录:

Personal-Knowledge/
├── 00 Inbox/
│ └── Web Clips/
├── 10 Projects/
│ └── Young Hope Blog/
│ └── Drafts/
├── 90 Templates/
│ ├── Blog Draft.md
│ └── Web Clip.md
└── 99 Attachments/

这种拆分解决了三个问题:临时收藏进入 Inbox,不假装它已经是成熟知识;准备写作的内容进入 Blog Drafts,拥有独立发布状态;图片等附件集中保存,发布器只复制当前文章明确引用的文件。

收藏和博客草稿必须是两种对象。收藏强调完整保留来源,博客强调作者的重组、核验和表达。如果直接把网页全文变成公开文章,不仅质量不可控,也可能带来版权风险。

Hermes 通过 Telegram 私聊接收请求,只允许授权用户。仓库中的 personal-content Skill 把中文指令映射到固定 CLI Action。

博客工作流目前支持以下无 slug 用户指令:

新建文章
写成博客
编辑这篇:<要求>
归档这篇
发布这篇
确认发布
继续发布
查看状态

Hermes 通过回复消息或当前 DM 最近选中的文章绑定对象。只有无法唯一 定位时,才返回标题、content_id 和路径候选并停止;slug 与 commit hash 是内部字段,不要求用户输入。预演文章、确认提交 和 确认推送 不再是 默认用户动作,旧命令仅保留给兼容脚本和维护场景。

收藏工作流支持 收藏 <一个公开URL>。也可以在转发或粘贴一篇纯文本文章时明确写“收藏”,或者回复一条单一来源消息发送“收藏”。普通发送链接仍然是只读对话,不会写入 Vault。这一条看似保守,却能避免聊天过程中产生大量用户并不想保存的笔记。

网页收藏的数据流如下:

“收藏 <URL>”
-> 验证明确定义和单一来源
-> Firecrawl 提取页面
-> Hermes 生成独立摘要和关键观点
-> 保留完整原始正文
-> 按 canonical URL 去重
-> 原子写入 00 Inbox/Web Clips

Web Clip 记录标题、来源 URL、canonical URL、作者、站点、发布时间、抓取时间和提取状态。作者或日期没有明确出现在来源中时保持为空,不让模型猜测。

正文分成摘要、关键观点、元信息和完整正文。摘要可以由 AI 生成,但完整正文不能被翻译、改写、裁剪或“优化”。这样未来回看时,生成内容和原始证据不会混在一起。

去重只在 00 Inbox/Web Clips 内按规范化后的 canonical URL 进行。同一 URL 再次收藏会返回 duplicate: true;如果旧记录是 failed,后续获得更完整的提取结果,则允许原位升级为 complete。

博客草稿基于统一模板,核心 frontmatter 如下:

title:
description:
status: idea
publish: false
locale: zh-cn
section: guides
slug:
sidebar_order:
created:
updated:
tags: []
sources: []

新建草稿时默认是 status: idea 和 publish: false。只有人工编辑为 status: ready、publish: true,且标题、description、locale、section、slug 等字段全部合法,发布才会继续。这两个字段只代表“允许生成网站文件”,不 代表允许 Git 提交或推送。

发布过程可以拆成四个阶段:

准备
-> 发布这篇(publish-content,目标已存在时自动转 update-content)
待确认
-> 确认发布(隐藏使用已选 slug)
已提交
-> fast-forward push origin main
-> 回写文章和索引的发布结果
完成

发布这篇 通过唯一 content_id 定位博客 Draft。它会运行 WeWrite 公众号 预览与兼容性校验、内容测试、Astro 构建、Git diff 检查和敏感信息扫描,然 后只生成本地网站文件。Hermes 把变更路径、检查结果和预览路径返回 Telegram, 进入待确认状态。

确认发布 会重新核对当前 HEAD、Obsidian 源文件、网站输出、工作树变更范围 和敏感信息模式。通过后只提交批准的路径,并立即检查远端是否仍允许普通快进 推送。整个动作不接受用户手写 hash,也不执行 force push。

如果推送被远端变化或其他异常阻断,事务会保留在本地。处理原因后使用 继续发布,系统再次校验批准的提交和远端状态;不会自动 merge、rebase 或“帮用户修好”。发布成功后,草稿和对应索引会记录发布结果。

用 WeWrite 增加公众号发布前门禁

Section titled “用 WeWrite 增加公众号发布前门禁”

博客页面和微信公众号面对的是两种不同的阅读环境。Astro 构建通过,只能说明网站版本合法;它不能保证 Markdown 粘贴到微信编辑器后仍然保持段落、代码块、引用和表格样式。因此我引入了 imraywang/wewrite,把公众号模板预览放在网站发布动作之前。

本机安装的是 WeWrite 4.2.1,包括一个主入口和九个模块:写作、选题、审稿、排版发布、视觉、风格、学习、数据复盘和多平台改写。Codex 与 Hermes 共用同一份 Skill 安装,CLI 由 uv tool 管理:

Terminal window
uv tool install wewrite
# Codex 安装主入口和模块后,Hermes 链接同一份目录
ln -sfn ~/.codex/skills/wewrite ~/.hermes/skills/wewrite
ln -sfn ~/.codex/skills/wewrite-review ~/.hermes/skills/wewrite-review
ln -sfn ~/.codex/skills/wewrite-publish ~/.hermes/skills/wewrite-publish

实际安装包含全部十个 wewrite* 目录,这里只展示发布链路直接依赖的三个。Hermes 也可以尝试 hermes skills install imraywang/wewrite,但多 Skill 仓库在当前版本中没有被安装器正确解析,所以最终采用标准 folder-per-skill 目录共享方式。

默认链路刻意只启用确定性的本地动作:

Obsidian 草稿
-> article-workflow 解析 frontmatter
-> 仅把正文写入临时 Markdown
-> wewrite preview --theme professional-clean --no-open
-> wewrite validate
-> 返回 wechatPreview + wechatTheme
-> Astro 生成、测试、构建和 Git 审批

直接把 Obsidian 源文件交给 wewrite preview 会把 YAML frontmatter 排进公众号正文,因此仓库门禁必须先剥离 frontmatter。每次 发布这篇 都在仓库外创建权限为 0700 的唯一临时目录,临时正文使用后删除,HTML 权限收紧为 0600。预览供当前用户人工检查,但不进入 Git 快照和文章提交。

这个默认门禁不会生成图片,不读取公众号 AppID/Secret,也不会推送到公众号草稿箱。WeWrite 的公众号发布能力仍需要用户在当轮明确授权,并且需要封面和公众号凭据。公众号排版预览是博客发布前的质量门禁,但它不等于公众号草稿箱授权,也不会替代 GitHub 的 确认发布。

发布器会在生成网站文件前检查公开正文的结构和边界:正文不重复声明页面标题, 不携带内部验收清单、审批记录、私有发布字段或本机文件路径。发现这类内容时, 发布会直接停止并指出位置,要求先修改文章。内部验收和操作说明保留在个人 Vault 的运维文档中,公开页面只呈现可复用的方法、经验和结果。

内容优化分成两层:人工或 Agent 在编辑阶段可以使用 wewrite-review 检查准确性、观点、实用性、账号声音和可读性;真正执行 发布这篇 时,仓库 CLI 必定执行 professional-clean 公众号模板生成与兼容性校验。前者允许改正文,后者是不改稿的发布门禁。

因此,公众号排版已经纳入主流程,位置是在网站文件生成之前:

Obsidian 草稿
-> 发布这篇
-> 去除私有 frontmatter
-> WeWrite 公众号模板排版预览
-> 微信兼容性校验
-> 内容测试 + Astro build + diff + 敏感信息扫描
-> 返回 wechatPreview,等待确认发布
-> commit + fast-forward push

当前纳入的是本地排版和兼容性校验,不是自动推送公众号草稿箱。后续若接入 草稿箱,仍要作为独立授权动作,不能因为博客 确认发布 而自动执行。

最初发送“收藏 URL”后,Hermes 触发了 apple-notes,把内容写到了用户主目录的 收藏.md。问题不在 Obsidian 路径,而在自然语言路由没有稳定选中 personal-content。

修复方式是在 Telegram 平台提示中对“收藏”明确加载 personal-content,并用仓库 Skill 的软链接作为唯一维护源。经验是:仅仅安装 Skill 不代表模型一定会选择它,关键意图需要确定性路由或至少有可验证的触发约束。

2. 临时请求文件被安全校验拒绝

Section titled “2. 临时请求文件被安全校验拒绝”

Hermes 一度把收藏请求写到 macOS 的随机临时目录,文件写入验证器认为这是敏感系统路径并拒绝操作。后来改为仓库内被 Git 忽略的 .hermes-requests/:路径固定且可验证;文件只允许当前用户拥有的普通文件;最大 10 MiB;CLI 读取后无论成功失败都会删除;正文不经过 shell 插值。

这比反复申请临时目录写权限更简单,也更容易审计。

Firecrawl Cloud 使用 FIRECRAWL_API_KEY,而 FIRECRAWL_API_URL 是自托管服务地址。最初把 Key 配进 URL 字段后,插件虽然能够注册,却不能正确建立提取客户端。

另一个容易误解的点是 /reload-skills 只重新加载 Skill,不会让 Gateway 进程重新读取 .env。工具配置变更后需要重启 Hermes Gateway。

Key 修正后,Wikipedia 和微信公众号仍然被拒绝。日志显示公网域名被解析成 198.18.0.x,这是 Clash Verge Fake-IP 模式使用的保留网段。Hermes 的 URL 安全检查因此把请求判断为内网地址,并在调用 Firecrawl 之前停止。

最终启用了 security.allow_private_urls: true 来兼容本机网络环境。这个选择有明确代价:普通私网 URL 的限制被放宽,因此 Telegram 入口、用户白名单和单 URL 约束更加重要。云元数据地址仍应保持永久阻止。

5. 正文里的普通 URL 被误判为 API Key

Section titled “5. 正文里的普通 URL 被误判为 API Key”

Dota 2 的 Wikipedia 页面成功提取了二十多万字符,但保存前的敏感信息扫描失败。真正命中的是 Bloomberg URL 中 musk-... 的内部片段 sk-...,被宽泛正则当成了 OpenAI 风格 Token。

修复不是关闭敏感信息扫描,而是给 sk- 增加词边界。回归测试同时验证:musk-... 不再误报,真正以 sk- 开头的长令牌仍然必须被拒绝。

一次发布中,文章提交与远端分支的历史没有直接对齐。Git 本身可能允许普通快进, 但工作流会先停止,要求确认远端是否包含其他尚未审阅的提交。这个边界避免了把 多笔变更误当成一次发布,也避免使用强制推送。确认历史安全后,再通过恢复动作 重新执行检查和推送。

这套组合已经覆盖了内容入口、原创草稿、双语内容、公众号预览和 Git 审批:

  • Telegram 私聊能够稳定路由到 personal-content。
  • 明确收藏 URL 后,Firecrawl 能提取微信公众号和 Wikipedia 正文。
  • Web Clip 能写入正确的 Obsidian PARA Inbox。
  • canonical URL 去重和失败记录升级已验证。
  • Telegram 纯文本收藏已纳入 Skill 规则。
  • 博客新建、编辑、按 content_id 定位和无 slug 发布链路已经跑通。
  • 发布流程具备一次明确确认、安全检查和普通快进约束;失败时保留事务状态。
  • 生成英文版 能创建独立英文草稿,人工审核后通过 发布这篇 单独发布。
  • 写成博客 能从 Web Clip、Telegram、X 或手动笔记创建原创草稿,并阻止连续复制来源正文。
  • 发布事务支持状态查询、安全恢复与取消,不再依赖删除状态文件。
  • WeWrite 已安装到 Codex 和 Hermes,发布这篇 会生成并校验公众号 HTML 预览。

仍然需要独立建设的能力包括:

  1. **公众号草稿箱发布。**当前默认只生成本地微信 HTML 预览。缺少 AppID/Secret 和封面时必须停止,不能把“发布博客”解释为授权公众号发布。
  2. **WeWrite 风格学习。**当前使用默认 professional-clean 主题,尚未用历史人工修改建立 style.yaml、playbook.md 和账号范文库。
  3. **更多执行端。**飞书知识库、Gmail、Calendar 和定时任务尚未接入,应该在内容主链持续稳定后再扩展。

同一篇 Obsidian 草稿可以从本地 Codex、Telegram 或 Hermes WebUI 发起操作。三个入口最终都应收敛到仓库里的 article-workflow,不能各自实现一套 Git 逻辑。

+----------------+
| Obsidian Draft |
+-------+--------+
|
+----------------+----------------+
| | |
v v v
+-----------+ +-----------+ +------------+
| Codex CLI | | Telegram | | Hermes UI |
| local | | DM | | localhost |
+-----+-----+ +-----+-----+ +------+-----+
| | |
| Hermes Gateway |
| | local Hermes Agent
+----------------+-----------------+
|
v
+----------------------+
| article-workflow CLI |
+----------+-----------+
|
WeWrite preview -> generate -> confirm -> push
|
v
GitHub / Website

入口一:在本地用 Codex 或编程 Agent 发布

Section titled “入口一:在本地用 Codex 或编程 Agent 发布”

本地写作适合长文整理、批量修改、图片检查和发布前审阅。先在 Obsidian 中编辑:

10 Projects/Young Hope Blog/Drafts/<slug>.md

准备发布时,把 frontmatter 调整为:

status: ready
publish: true
locale: zh-cn
section: guides
slug: hope-personal-ai-workflow

然后在 Codex 中明确指定仓库、文章对象和权限边界,例如:

在博客仓库中处理当前选中的 hope-personal-ai-workflow 文章。
只读取这篇文章,执行发布检查并等待我的确认,不提交、不推送。

Codex 或其他本地 Agent 应调用仓库命令,而不是直接复制文件。默认入口 使用 Content Index 的 content_id;旧的 slug 命令只用于兼容:

Terminal window
cd path/to/young-hope
pnpm article:workflow -- publish-content "<content-id>"

返回结果中的 wechatPreview 是 professional-clean 主题的本地 HTML, wechatTheme 记录本次使用的主题。publish-content 会写入网站文件并把事务 进入待确认状态,但不会提交或推送。目标页面已存在时 CLI 自动进入 更新模式。

检查变更文件、测试、构建、公众号预览和 diff 后,再明确要求一次性发布:

确认发布

对应内部命令是:

Terminal window
pnpm article:workflow -- confirm "<hidden-slug>"

该命令重新验证源文件、生成文件、工作树、远端快进关系,然后完成精确 commit 和 git push origin main。推送失败会保留事务,处理原因后使用 继续发布,不需要复制 hash。

完整流程图如下:

Obsidian 编辑完成
|
v
status=ready + publish=true
|
v
Codex: publish-content
|
+--> WeWrite 模板 + 微信兼容校验 --失败--> 修改草稿/环境
|
通过
v
写入网站文件 ---> 测试、构建、diff、敏感扫描
|
v
人工检查变更
|
v
确认发布 ---> 精确 commit + fast-forward push
|
+----------异常----------> 保留事务,继续发布
|
v
GitHub / 网站

如果 CLI 提示已有工作流状态,必须先处理上一笔发布。不要删除状态后假装审批仍然有效,也不要绕过 CLI 直接强推。

Telegram 适合移动端触发、查看结果和做一次发布确认。Telegram 平台提示会把 这些中文指令路由到 personal-content Skill。

新建文章
写成博客
编辑这篇:补充文章结构和正文
归档这篇
发布这篇
确认发布
继续发布
查看状态

第一次新建时 Hermes 会追问标题。写成博客 会把当前选中的收藏、Telegram、 X 或手动笔记改写成原创草稿;不会复制长段原文。发布这篇 返回 wechatPreview、wechatTheme、变更文件和检查摘要,然后等待 确认发布。 确认发布 一次完成 commit 和 push;如果 push 被阻断,使用 继续发布。

Telegram DM
|
v
Hermes 身份白名单
|
v
personal-content Skill
|
+-- 新建/编辑 ------> 指定 Obsidian 草稿
|
+-- 写成博客 -------> 原创 Draft
|
+-- 发布这篇 -------> WeWrite + 测试/构建/扫描
|
待确认
|
确认发布
v
commit + fast-forward push
|
推送异常
|
继续发布
v
GitHub main

收藏网页则使用:

收藏 https://example.com/article

Hermes 返回标题、正文字符数、笔记路径、提取状态和是否重复。资料只进入 00 Inbox/Web Clips,不会自动进入博客发布状态机。

入口三:通过 Hermes WebUI 对话触发

Section titled “入口三:通过 Hermes WebUI 对话触发”

Hermes WebUI 运行在本机回环地址:

http://127.0.0.1:8787

它共享本机 Hermes 的配置、Skill 和会话,并把博客仓库作为默认工作区。不过 WebUI 没有 Telegram 的平台路由提示,因此高风险动作不要只依赖自然语言自动选 Skill。

在 WebUI 新对话中先执行:

/skills personal-content

确认 Skill 存在后输入:

/use personal-content

WebUI 会提示下一轮强制使用该 Skill。紧接着发送具体动作。WebUI 与 Telegram 一样使用当前选中对象,不需要 slug:

发布这篇

或:

确认发布

/use personal-content 只保证下一条消息强制加载 Skill。因此开始新的高风险 Action 前,应再次执行 /use personal-content。公众号排版预览由 发布这篇 自动执行,不会因 WebUI 入口而跳过。

打开 Hermes WebUI
|
v
选择 young-hope 工作区
|
v
/skills personal-content ----不存在----> 检查 Skill 软链接/配置
|
存在
v
/use personal-content
|
v
发送一个明确 Action
|
v
Hermes 调用 article-workflow
|
v
WeWrite 本地预览与兼容校验
|
v
返回结果并等待下一次明确审批

WebUI、Telegram 和 Codex 的差别只是入口与 Skill 触发方式,审批规则不应改变。 每一步都可能停止;停止意味着当前状态与审批时看到的状态不同,需要重新检查, 而不是让 Agent 自动修复或强推。

这套系统为什么不是“另一个聊天机器人”

Section titled “这套系统为什么不是“另一个聊天机器人””

聊天机器人通常以回答结束;执行系统以状态变化结束。

在这套架构里,Hermes 的价值是统一入口和编排能力,但真正保证结果可靠的是外围约束:路径白名单、模板、schema、原子写入、测试、构建、Git 快照和一次明确发布确认。模型负责理解“我想做什么”,确定性代码负责限制“它实际上可以做什么”。

这也是我对个人 AI Stack 最重要的认识:不要从“选哪个最强模型”开始,而要从以下问题开始:

  • 我的信息从哪里进入?
  • 哪些动作允许自动执行?
  • 哪些动作必须明确确认?
  • 私有资料和公开内容在哪里分界?
  • 出错后系统是停止、覆盖,还是悄悄继续?
  • 我能否从 Git、日志和文件状态还原发生了什么?

当这些问题有了明确答案,AI 才从临时助手变成一个可以长期协作的个人执行层。

后续可以沿着三个方向扩展:用 wewrite-learn 从人工修改中沉淀公众号风格, 接入 RSS 和官方博客作为稳定的信息源,以及把同一篇文章适配到更多平台。每个 扩展都应复用同一份内容索引和审批边界,而不是重新建立一套孤立的自动化脚本。

公众号草稿箱、飞书知识库、Gmail、Calendar 和定时任务可以在主链稳定后按需接入, 并继续保持独立授权。