向我的博客转 Newsletter 工具添加新内容类型Adding a new content type to my blog-to-newsletter tool
Simon Willison 分享了他如何为其 Agentic Engineering Patterns 指南添加新的内容类型到博客转 Newsletter 的工作流程中。他使用一个简洁的提示(prompt)让 Claude Code 一次性完成内容识别、分类和格式化任务,展示了代理式工程的高效模式。该方法显著简化了内容分发流程,适用于希望自动化知识传播的个人创作者。
Simon Willison
这是一个看似简短却能在单次调用中完成大量工作的提示示例。
先补充一些背景信息。我每周会发布一封免费的 Substack 通讯,内容是从我的博客直接复制粘贴过来的。实际上我是把 Substack 当作一种轻量级的订阅方式,让用户可以通过邮件关注我的博客更新。
我用的是自己开发的 blog-to-newsletter 工具——一个基于 HTML 和 JavaScript 的应用,它会从当前这个 Datasette 实例中抓取最新的内容,并将其格式化为富文本 HTML,之后我可以将其复制到剪贴板并粘贴到 Substack 编辑器中。这里详细解释一下它是如何运作的。
最近我在博客里新增了一种内容类型,用来收录我在其他地方发布的内容,我称之为“beats”。这些包括我开源项目的发布、新开发的小工具、我从 niche-museums.com 访问过的博物馆(以及其他外部来源的内容)。
我希望把这些 beats 也包含在生成的通讯中。下面是我对托管 blog-to-newsletter 工具的 simonw/tools 仓库运行的一段提示词,使用的是网页版的 Claude Code。
这段提示词正好给了我想要的解决方案。我们来拆解一下它的结构。
将 simonw/simonwillisonblog 从 GitHub 克隆到 /tmp 目录供参考使用
我经常采用这种模式。编程代理可以从 GitHub 克隆代码,而最有效说明问题的方式往往是让他们查看相关的现有代码。通过指定克隆到 /tmp,可以确保它们不会意外地将这份参考代码包含进自己后续的提交中。
simonw/simonwillisonblog 仓库包含了由 Django 驱动的我个人博客 simonwillison.net 的完整源码。其中也包括了新“beats”功能的相关逻辑和数据库结构。
更新 blog-to-newsletter.html,使其能够包含带有描述的 beats——类似于博客上 Atom everything feed 的处理方式
在这里只需要引用 blog-to-newsletter.html 就能明确告诉 Claude 应该修改 simonw/tools 仓库中那 200 多个 HTML 应用中的哪一个。
Beats 是自动从多个渠道导入的。很多时候它们并不太有趣——比如某个小型开源项目的一个点版本 bug 修复。
我的博客已经支持我为任意 beat 添加额外描述的功能,这既提供了补充说明,同时也标记出那些经过我主动注释的、相对更有趣的 beats。
我早已利用这一点来决定哪些 beats 最终会出现在网站的 Atom 订阅源中。让 Claude 模仿这一机制,就无需我再额外描述具体逻辑了。
启动时用 python -m http.server,并通过 `uvx rodney --help` 进行测试——对比生成的 newsletter 与 https://simonwillison.net 首页显示的内容是否一致。
只要编码代理有某种自我验证机制,就能显著提升工作效率。
在这个案例中,我希望 Claude Code 能主动检查它对工具的改动是否能正确获取并展示最新数据。
我提醒它使用 python -m http.server 作为静态服务器,因为过去曾遇到过某些应用在被以文件形式加载时会出错的情况。虽然这次可能并非必需,但我的提示词习惯已经把 python -m http.server 固化下来了!
我在《代理式手动测试》章节中描述了使用 uvx rodney --help 的技巧。Rodney 是一款可通过 uvx 安装的浏览器自动化软件,其 --help 输出经过专门设计,旨在向代理传授使用该工具所需的一切信息。
我认为只需让 Claude 对比新闻通讯中的结果与我博客首页的内容,它就能自信地验证新改动是否正常工作,因为我最近发布的内容符合新的要求。
你可以在此查看完整会话,如果无法访问,我还提供了一个替代转录文件,其中包含了所有单独的工具调用记录。
最终提交的 PR 做出了完全正确的修改:它在获取博客内容的 SQL 查询中增加了一个额外的 UNION 子句,过滤掉了草稿状态的 beats 以及 note 列为空的 beats。
...
union all
select
id,
'beat' as type,
title,
created,
slug,
'No HTML' as html,
json_object(
'created', date(created),
'beat_type', beat_type,
'title', title,
'url', url,
'commentary', commentary,
'note', note
) as json,
url as external_url
from blog_beat
where coalesce(note, '') != '' and is_draft = 0
union all
...它还推导出了 beat 类型与其正式名称之间的映射关系,这很可能源自它在探索参考代码库时读取的 Django ORM 定义。
const beatTypeDisplay = {
release: 'Release',
til: 'TIL',
til_update: 'TIL updated',
research: 'Research',
tool: 'Tool',
museum: 'Museum'
};指示代理将另一个代码库作为参考,是一种强大的捷径,能以最少额外的提示信息传达复杂概念。
需要完整排版与评论请前往来源站点阅读。