9600 星的 Kami,拆开看是给 AI Agent 写的一本排版纪律
- Kami 项目斩获 9600 Star,起源于 tw93 对 Claude 输出研报排版质量的不满
- 核心思想不是做新的 AI 能力,而是给 AI Agent 输出建立一套排版纪律和约束系统
- 本质是 AI 时代的 CSS 设计系统:统一字号、配色、间距、布局,让每份文档都达到专业出版级质量
- 从个人研报排版需求演化成通用 AI 文档框架,证明 AI 时代的产品机会藏在细节体验里
tw93 是个喜欢炒美股的人,平时总让 Claude 帮他写个股研报。问题来了,每次输出都长一个样,灰扑扑的默认文档,排版扁平,结构散,每次会话还给你换个布局。内容其实不差,但那页面看着就让人不想读下去。
他没去骂模型笨,而是干了一件大多数人不干的事。一条一条改排版,改字号,改配色,改间距,改到这份研报变成一页他自己愿意读的纸。后来他要做一场叫《你不知道的 Agent》的演讲,又把同一套审美搬到幻灯片上,反复迭代。改着改着他发现,自己其实在反复给 AI 讲同一套规则。
于是有了 Kami。GitHub 上 tw93/Kami,9673 颗星,MIT 协议,最新版本 V1.9.3,2026 年 4 月才建仓,两个多月迭代到第九个大版本。它的 slogan 只有一句,Good content deserves good paper,好内容值得一张好纸。
大家好,我是若风。今天拆这个项目,我想搞清楚的不是它能不能排版,这谁都能做。我想搞清楚的是,tw93 怎么把「这页排得好看」这件极主观的事,变成一个 AI Agent 能稳定复现的工程系统。
它根本不是个 PDF 工具
先把定位掰扯清楚,这点最容易看走眼。
Kami 不是又一个 Typst,不是 Pandoc 的皮肤,也不是 Marp 的升级版。它本质是一个装在 Claude Code、Codex、Claude Desktop 里的 Skill,或者说插件。你用自然语言跟 Agent 说「帮我做份简历」「把这段调研排成一页纸」「做个产品落地页」,Agent 就自动套上 Kami 的模板和规则,吐出 PDF 或 HTML。
关键在它的 README 第一句话点破的那层窗户纸。AI can produce documents better than most humans do manually. The missing piece is not capability but constraint.
缺的不是能力,是约束。
这句话是整个项目的灵魂,也是它和所有同类工具的分水岭。Typst 的思路是给你一门更强的排版语言,让你学会它然后自己排。LaTeX 给你一套强大的宏系统,代价是你得配环境、查报错。Pandoc 啥都能转,但它没有审美,转出来还是默认那副灰样。这些都是在「能力层」做加法。
Kami 反过来想。模型已经够强了,真正让它产出不稳定的是每次会话都在重新发明排版。那就别让它发挥,把它关进一套精心设计过的约束里。模板是固定的,颜色是锁死的,字号是钉好的,连破折号该不该用都写成规则。Agent 只负责填内容,不负责做设计决策。
这个思路一旦想通,你会发现 Kami 的所有工程细节都在服务同一件事,把品味降维成机器能跑的规则。
这张图把 Kami 从上到下拆成五层。最上面是宿主接入层,Claude Code、Codex、Claude Desktop、通用 Agent 四个入口,全靠自然语言触发。中间的核心是第四层约束规范层,design.md、tokens.json、anti-patterns.md 这些 reference 文件,把设计品味固化成机器能读的规则。再往下是模板资产层、工程构建层、最终输出层。每一层都是约束的载体,缺一层约束,Agent 就会在那个环节开始「自由发挥」。
13 个颜色 token,和一条 5% 的红线
先看它怎么把「好看」量化。
翻开 references/tokens.json,整个项目的设计变量就这么多。
{
"--parchment": "#f5f4ed",
"--ivory": "#faf9f5",
"--border": "#e8e6dc",
"--brand": "#1B365D",
"--brand-tint": "#EEF2F7",
"--tag-bg": "#E4ECF5",
"--near-black": "#141413",
"--dark-warm": "#3d3d3a",
"--olive": "#504e49",
"--stone": "#6b6a64",
"--breaking-bg": "#f0e0d8",
"--breaking-fg": "#8b4513"
}
13 个。就 13 个。你对比一下 Tailwind 默认调色板有几百个色阶,再想想为什么 Kami 敢只用 13 个。
因为它的设计哲学写在 references/design.md 开头的十条不变量里。第一条,页面背景永远是 #f5f4ed 暖羊皮纸,绝不用纯白。第二条,全篇只有一个强调色,ink blue #1B365D,没有第二个彩色。第三条,所有灰色都带暖底(黄褐调),绝不用冷蓝灰。
最狠的是这条。ink blue 覆盖面积不得超过整页的 5%。design.md 原话是,超过这个比例就是装饰,不是克制。
你想想这意味着什么。一个 PDF 模板系统,居然给自己的主色划了 5% 的表面积上限。这不是设计师嘴上说说,这是写进文档、写进 lint 检查里的硬规则。scripts/checks.py 里有 check_off_palette 会扫描所有模板,任何超出这 13 个 token 的颜色都会被标红。整个项目 15 个模板文件,每个用到的颜色都得能追溯回这 13 个值,sync_check 专门管这个一致性。
这就是「约束系统」和「UI 框架」的本质区别。UI 框架给你选项,越多越好,你想用啥色用啥色。约束系统给你删选项,删到只剩 13 个,然后逼你在这些框框里把活干漂亮。
它还顺带解决了一个 AI 排版的通病。AI 生成文档最爱堆形容词,「显著增长」「重大突破」「行业领先」,没一个数字。Kami 把这病也写进规则了,references/anti-patterns.md 第 1 条就是,别说「achieved significant growth」,得说「Revenue grew 34% YoY to $12M」。第 2 条,删掉「In today’s rapidly evolving landscape」这种开头废话。48 条反模式,条条都在治 AI 的表达病。
WeasyPrint 是个难伺候的主
讲完审美,讲工程。这部分才是 Kami 真正硬核的地方。
Kami 选的渲染引擎是 WeasyPrint,一个纯 Python 的 HTML 转 PDF 库。为什么不选 Puppeteer + Chromium 那套,因为 Kami 要装进 Claude Code 插件里,不能让用户为了排个版去下 150MB 的浏览器内核。WeasyPrint 用 pip 装一下就能跑,轻。
但 WeasyPrint 有个要命的毛病,它对现代 CSS 的支持很滞后。最典型的两个坑。
第一个坑叫 double rectangle,双矩形 bug。production.md 第四部分把它列为 P0 级第一号陷阱。原因是 WeasyPrint 渲染 rgba(27, 54, 93, 0.18) 这种半透明背景时,会把元素的 padding 区域和文字像素区域分开做 alpha 合成,两个区域反走样的方式不一样,结果你看到一个标签外面套了一圈浅一点的边,像重影。
Kami 的解法很工程。design.md 里直接给了一张 rgba 到实色的换算表,把所有半透明色预计算成不透明的 hex。比如 ink blue 在 0.18 透明度下对应 #E4ECF5,这个值就被钉死成 --tag-bg token。换句话说,Kami 不让模板用 rgba,全部预先算成实色,从源头绕开这个 bug。这种「把运行时坑前置成编译期常量」的思路,是处理烂引擎最干净的方式。
第二个坑更难,WeasyPrint 不支持 color-mix() 函数,也不靠谱地解析 SVG 里的 CSS 变量级联。
V1.9.0 版本 Kami 加了个新能力,能从 Mermaid 文本生成时序图、类图、ER 图。它依赖另一个项目 beautiful-mermaid,那个项目输出的 SVG 用了 CSS 自定义属性挂在根 <svg> 上,靠 color-mix(in srgb, ...) 派生颜色。这种 SVG 在浏览器里好好的,扔进 WeasyPrint 直接全是黑块。
你去看 scripts/mermaid_normalize.py,这个 343 行的脚本就是专门解决这个问题的。它干三件事。
第一,重新着色。读 references/mermaid-theme.json 里的 Kami 配色,覆盖 beautiful-mermaid 的七个根颜色角色,这样不管原图用啥主题生成,都变成 Kami 调色。
第二,把所有 var() 和 color-mix(in srgb, ...) 解析成静态 hex。这是最精巧的部分。脚本自己实现了一个 _Resolver 类,递归解析 CSS 自定义属性,还实现了 _mix_srgb 函数做 sRGB 线性混合,甚至处理了 var() 的 fallback 和循环引用保护。完全是纯 Python stdlib,没依赖任何 CSS 解析库。
第三,剥掉 beautiful-mermaid 从 Google Fonts 拉字体的 @import,把 font-family 改写成 Kami 的 serif 栈。
整个过程零 Node,零网络,纯 stdlib。这种「我明知道你的渲染器有缺陷,我不去换渲染器,而是写个预处理器把你的输入变成你能消化的样子」的思路,是工程上很成熟的一种克制。换 Puppeteer 当然能解决,但代价是把一个轻量 Python 工具变成一个要下浏览器的重家伙。tw93 选了难走的那条路,换来的是插件包能稳定跑在任何装了 Python 的环境里。
6MB 是一条不能破的天花板
接着上面这个点往下挖,你会发现 Kami 对「体积」的执念到了变态的程度。
Claude Desktop 的 Skill 上传有 6MB 的限制。一个文档系统,要带字体,要带模板,要带脚本,6MB 听起来根本不够。光一个中文字体 TsangerJinKai02 的 W04 和 W05 两个文件加起来就好几 MB,韩文 Source Han Serif K 的 Regular 和 Medium 也是大头。
去看 scripts/package-skill.sh,第 12 行有个正则常量叫 PACKAGE_FORBIDDEN_RE,长这样。
PACKAGE_FORBIDDEN_RE='^(\.agents/|\.claude/|\.claude-plugin/|\.github/|plugins/|assets/(showcase|demos|examples|illustrations)/|assets/images/[123]\.png$|assets/fonts/TsangerJinKai02-W0[45]\.ttf$|assets/fonts/SourceHanSerifKR-(Regular|Medium)\.otf$|dist/|index(-[^/]+)?\.html$|styles\.css$|llms\.txt$|robots\.txt$|sitemap\.xml$|vercel\.json$|AGENTS\.md$|CLAUDE\.md$|README\.md$|\.gitignore$|scripts/(build_metadata|draft-release-notes|package-skill)\.py$|scripts/package-skill\.sh$|scripts/tests/)'
这个正则把所有不该进 Skill 包的东西全列出来了。那几个最大的字体文件被显式排除,.github/、README.md、CLAUDE.md、dist/、网站源码统统不进包。脚本最后还有个硬检查,size_bytes > 6000000 就报错退出,一个字节都不让超。
那字体不进包,用户怎么排中文。靠 scripts/ensure-fonts.sh,它在用户第一次排中文或韩文文档时,从 jsDelivr CDN 把字体下载到用户的 XDG 字体目录 ~/.local/share/fonts/kami,不在 Skill 目录里。fontconfig 默认会扫这个目录,所以 WeasyPrint 照样能找到 TsangerJinKai02。在线渲染则走模板里写好的 jsDelivr @font-face 回退 URL。
这套设计很妙。Skill 包永远小,用户环境第一次用时自动补字体,之后就一直有了。把「分发的体积」和「运行的依赖」彻底解耦。
顺带说个有意思的工程细节,scripts/build.py 里有个 infer_author 函数,用 @functools.lru_cache 缓存,给 PDF 的 Author 元信息赋值。它三级回退,先读 git config user.name,不行就读 KAMI_AUTHOR 环境变量,再不行就用字符串 Kami。set_pdf_metadata 还会检查 PDF 里现有的 Author 是不是还带着 {{...}} 占位符,是的话才覆盖,避免把用户已经填好的名字冲掉。这种边界处理,是判断一个项目是不是「真在用」而不是「写完就丢」的试金石。
把「别用破折号」写成机器可检查的规则
刚才提到 anti-patterns.md 有 48 条反模式。我想单独拎一条出来讲,因为它最能体现 Kami 的工程哲学。
第 27 条,叫 AI tone cliches。它直接给了一段自检命令。
grep -nE '本质是|这意味着|值得注意的是|不仅.*而且|[——–]'
你看明白这意味着什么了吗。这个项目自己就在用 AI 写文档,它太清楚 AI 生成中文时最爱犯什么病了。破折号滥用,「本质上」「这意味着」「值得注意的是」「不仅……而且」,这些是中文 AI 文的典型指纹。Kami 不是在文档里写一句「请避免 AI 腔」就完事,它给了个能跑的 grep,让你直接扫自己的稿子。
这就是「约束」和「建议」的区别。建议是「你应该写得自然一点」,约束是「这里有个脚本,扫出来就改」。前者靠人自觉,后者靠机器执行。Kami 整套系统的价值就在这,它把那些「大家都知道但做不到」的写作纪律,变成了 build 流程里的检查项。
scripts/checks.py 里还有几个很能说明问题的检查。check_density 会算每页的填充率,正文页目标 60% 到 80%,低于 50% 就报 SPARSE 警告,逼你合并页面。阈值写在 references/checks_thresholds.json 里,warn 是 0.25,sparse 是 0.50,全是可调的数字。check_resume_balance 专门管简历,填充率得在 0.83 到 0.95 之间,间隙不能超过 12%。check_rhythm 扫幻灯片序列,连续内容页超过 5 页就提醒你插个过渡,divider 至少在 12 页以上的 deck 才有意义。
你发现没有,这些全是数字化的纪律。不是「简历要排得均衡」,是「填充率 0.83 到 0.95」。不是「幻灯片别太单调」,是「连续内容页不超过 5」。Agent 能跑数字,跑不了感觉。把感觉翻译成数字,是 Kami 最值钱的一手。
几个得提前知道的坑
讲了一路好话,该说局限了。这项目有些地方你得用之前心里有数。
字体有商用雷区。TsangerJinKai02 这个中文字体,README 和 SKILL.md 里都写了,仅限个人使用,商用要去 tsanger.cn 买授权。Kami 把它放在仓库里方便预览,但 package-skill.sh 打包时会显式排除它,就是为了避免把这个法律风险分发出去。你要拿 Kami 给公司排正式文档,要么换字体,要么先把授权买了。这点不看清,哪天吃官司都不知道怎么吃的。
日文是「尽力而为」。README 展示了 8 种模板,但实际文件树里你找不到一个 -ja 后缀的模板。日文走的是中文的 CJK 模板路径,靠 YuMincho 字体栈「尽量」排好,排完还得人工检查换行和标点节奏。对比一下,韩文有独立的 -ko 模板族,英文有 -en 族,日文是明显被怠慢的那个。你要做正式日文交付,别指望它开箱即用。
Marp 路线暗藏 Chromium 依赖。Kami 支持三种幻灯片渲染路径,WeasyPrint 转 PDF 是默认,python-pptx 生成可编辑 PPTX 是备选,Marp 是给喜欢 Markdown 写幻灯片的人。但 Marp 的 PDF 和 PPTX 输出走的是 headless Chromium,第一次跑会触发 Puppeteer 下一个约 150MB 的浏览器内核到 ~/.cache/puppeteer/。你以为装了个轻量 Skill 就完事了,真用 Marp 出 PDF 时还是逃不掉那个浏览器。production.md 里白纸黑字写着这条。
bus factor 风险真实存在。贡献者只有 6 个人,核心基本就 tw93 一个。建仓两个多月,版本号从 1.7 冲到 1.9.3,节奏很猛,但这种单人驱动的项目,生命周期高度绑定作者一个人的精力。issue #37 有人反馈 npx skills add tw93/Kami 只同步了根目录的 SKILL.md,真正的 Skill 内容在 plugins/kami/skills/kami/ 下被丢了,因为仓库根目录同时充当网站源码,根级 SKILL.md 会误导 Skills CLI。这种「一个仓库三个身份」的复杂度,后续维护是个隐患。
它到底该用在哪,不该用在哪
聊完坑,给个选型结论。
你该用 Kami 的场景是,你或者你的团队天天用 Claude Code、Codex 这类 Agent 干活,频繁要产出简历、一页纸方案、研报、信件、作品集、changelog 这类「正经文档」,受够了每次都是默认那副灰样,但又不想学 Typst 或 LaTeX。Kami 能让你一句自然语言就拿到一份排得体的 PDF,且每次结果稳定。
你不该用的场景也很多。你要做的是一个有强交互的 Web 应用,Kami 是给静态印刷品和落地页的,不是给 dashboard 的。你要的是赛博朋克、Material、Fluent 这类风格,Kami 故意把自己设计成「反未来」的暖色调编辑风,审美不匹配。你要做的是高度定制化的设计,每个文档都要独一无二,那约束系统反而是你的枷锁。
跟同类比一下。Typst 更强大更灵活,但你得学一门新语言,Agent 不好稳定驱动它。LaTeX 是排版之王,但编译环境和报错信息能劝退大多数人。Pandoc 是万能转换器,但它不带审美,转出来还是默认样。Marp 只管幻灯片,不管文档。Kami 的独特位置是,它是唯一一个把「设计品味」前置打包好、专门给 Agent 用的约束系统,不要求你学任何东西,会用自然语言就行。
一个值得带走的方法
最后说个我从拆 Kami 里得到的东西。
tw93 在 README 里写了句容易被略过的话,one constraint language, simple enough for Agents to run reliably, strict enough that every output is something you actually want to ship。一套约束语言,简单到 Agent 能稳定跑,严格到每次产出都拿得出手。
我把它提炼成一个模式,叫 Constraint-as-a-Skill,约束即技能。
这个模式讲的是,在 AI 能力已经过剩的今天,真正稀缺的不是更强的模型,而是能把人类专家的「隐性判断」编码成「显性约束」的中间层。tw93 是个有审美的人,但他一个人一天能排几份文档。他把审美拆解成 13 个颜色 token、10 条不变量、48 条反模式、一堆可执行的 lint 脚本之后,任何一个装了 Kami 的 Agent 都能复现他的品味。他的能力被「约束化」之后,就不再绑定他个人了。
这个思路能迁移到很多地方。你是个资深 code reviewer,与其每次人肉审,不如把你判断「好代码」的标准写成一套可执行的规则。你是个有经验的产品经理,与其每个需求都重新讲一遍优先级逻辑,不如把判断框架固化成模板。但凡一个领域存在「专家凭直觉,新手凭运气」的鸿沟,就存在一个把它约束化、产品化的机会。
Kami 9600 多颗星,背后是这个时代一个很实在的产品判断。能力层的仗快打完了,下一个值得卷的是约束层。谁能把专家的品味变成机器跑得稳的规则,谁就能在 Agent 时代卡住一个位置。
tw93 用两个多月证明了这件事有人买单。你所在的领域,那个「约束层」的空位还在不在,值得想一想。
评论互动