第 15 篇

为博客转邮件简报工具添加新的内容类型

Simon Willison 分析一条简短提示词,展示如何让编程智能体参考博客源码,为邮件简报工具加入 beats 内容,并用浏览器验证结果。

Hex4C59 4 分钟阅读 更新于 2026-10-17

译文信息

本文为非官方中文译文,依据提供的原文正文翻译。正文中的「我」、项目状态和结果判断均指原作者。示例代码逐字保留,均为原文片段,不是完整的可运行程序;译者未执行提示词、SQL 或浏览器测试。

下面这个例子里,我只用了一条意外简短的提示词,就一次完成了相当多的工作。

先介绍一点背景。我大约每周发送一期免费的 Substack 邮件简报,内容从博客复制粘贴而来。实际上,我把 Substack 当作一种轻量的方式,让读者通过邮件订阅我的博客。

我使用自己的 blog-to-newsletter 工具生成简报。这是一个用 HTML 和 JavaScript 编写的应用,会从这个 Datasette 实例获取最新内容,把它整理成富文本 HTML。随后,我可以把结果复制到剪贴板,再粘贴进 Substack 编辑器。这里有一篇详细介绍其工作方式的文章。

最近,我给博客添加了一种新的内容类型,用来收集我在其他地方发布的内容。我把它叫作「beats」。这类条目包括开源项目的新版本、我构建的新工具、我参观过的博物馆(收录在 niche-museums.com),以及其他外部内容。

我想把这些内容也加入生成的邮件简报。下面是我通过网页版 Claude Code,针对存放 blog-to-newsletter 工具的 simonw/tools 仓库发出的提示词:

从 GitHub 把 simonw/simonwillisonblog 克隆到 /tmp,作为参考。

更新 blog-to-newsletter.html,加入有描述的 beats,做法类似博客的 Atom everything 订阅源。

用 python -m http.server 启动它,再使用 uvx rodney --help 进行测试。比较邮件简报中显示的内容与 https://simonwillison.net 首页上的内容。

这让我得到了完全符合需求的解决方案。下面拆开分析这条提示词。

从 GitHub 把 simonw/simonwillisonblog 克隆到 /tmp,作为参考。

我经常使用这种做法。编程智能体可以从 GitHub 克隆代码,而解释问题最好的方式,往往就是让它们查看相关代码。指定克隆到 /tmp,是为了避免它们后来不小心把这些参考代码也加入自己的提交。

simonw/simonwillisonblog 仓库包含我的 Django 博客 simonwillison.net 的源码,其中也有新功能「beats」的逻辑和数据库结构。

更新 blog-to-newsletter.html,加入有描述的 beats,做法类似博客的 Atom everything 订阅源。

只要提到 blog-to-newsletter.html,就足以告诉 Claude:在 simonw/tools 仓库的 200 多个 HTML 应用中,应该修改哪一个。

Beats 会自动从多个来源导入。很多条目并不太值得关注,比如我的某个小型开源项目发布了一个只修复缺陷的小版本。

我可以在博客里为任何一条 beat 添加描述。这段描述既提供了补充说明,也表明我认为这条 beat 比没有说明的条目更值得关注。

我已经用这个区别来决定哪些 beats 应该进入网站的 Atom 订阅源。让 Claude 仿照现有做法,就不必再额外详细描述筛选逻辑。

用 python -m http.server 启动它,再使用 uvx rodney --help 进行测试。比较邮件简报中显示的内容与 https://simonwillison.net 首页上的内容。

编程智能体能用某种方式测试自己的工作时,表现总是最好。

这一次,我希望 Claude Code 主动检查:它对工具的修改,是否确实能够正确获取并显示最新数据。

我提醒它用 python -m http.server 作为静态服务器,是因为过去遇到过这样的情况:应用需要获取数据,直接从磁盘打开文件时会出问题,通过 localhost 服务器访问则不会。在这次任务中,这一步可能并不必要,但如今 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'
};

让智能体参考另一个代码库,是传达复杂概念的一条有效捷径。提示词只需补充很少的信息,就能让它理解需求。


上一篇原文:使用 WebAssembly 和 Gifsicle 构建 GIF 优化工具 · 下一篇原文:我使用的提示词

一起讨论

选中正文中的文字,可以针对这段内容发起讨论。

正在读取讨论…

在 GitHub 查看与管理讨论 ↗

文章讨论