从 tinify-cli 到 imgasset:把博客配图做成一条流水线
从图片压缩工具 tinify-cli 到 AI 生图与压缩一体化工具 imgasset,记录一套博客配图工作流如何从临时脚本沉淀成可复用的 npm CLI。
最近给博客补配图时,我又遇到了一个很熟悉的问题:流程本身并不复杂,但步骤很多,而且每次都差不多。
先根据文章内容写提示词,用图片模型生成原图;原图不能直接进仓库,要放在临时目录里;选中的图要压缩、转成适合网页使用的格式,再放到 public/assets/ 下;最后把 Markdown 里的图片路径补上,跑构建和链接检查。
一两篇文章手工做没什么问题。文章多了之后,这件事就开始变成一种重复劳动。
于是我把这个流程拆成了两个工具:
@yigemo/tinify-cli:负责图片压缩和格式转换。@yigemo/imgasset:负责 AI 生图、原图保存、压缩和发布输出。
它们不是一开始就设计好的“产品”。更准确地说,是从真实写博客的流程里,一层一层提炼出来的。
English version: From tinify-cli to imgasset: Turning Blog Images into a Pipeline

如果要跑完整流程,可以先安装 imgasset:
npm install -g @yigemo/imgasset
imgasset 已经把 tinify-cli 作为依赖带上了。只需要单独做图片压缩时,也可以只安装压缩工具:
npm install -g @yigemo/tinify-cli
第一层:先把图片压缩标准化
在写 imgasset 之前,我先写了 tinify-cli。
图片压缩这件事看起来很小,但在内容站里非常高频。博客文章、官网配图、产品截图、社交分享图,最后都要面对同一个问题:原图太大,直接放线上不合适。
TinyPNG / Tinify 的压缩效果一直不错,但如果每次都打开网页上传下载,或者在不同项目里复制一段调用 API 的脚本,长期维护会很麻烦。
所以 tinify-cli 的目标很简单:把压缩变成一条稳定命令。
tinify login
登录后压缩一个目录:
tinify temp/article/raw \
--recursive \
--out-dir public/assets/article \
--format jpeg \
--background white \
--suffix ""
这里几个参数对博客配图很关键。
--recursive 可以保留目录结构,适合一篇文章有多张图的情况。
--out-dir 可以把压缩后的图片写到发布目录,而不是覆盖原图。
--format jpeg 和 --background white 让生成图可以从 PNG 转成更适合网页展示的 JPEG,同时处理透明背景。
--suffix "" 则是为了让最终文件名保持干净。原图可能在 temp/article/raw/01-context.png,发布图可以直接变成 public/assets/article/01-context.jpg。

这一步解决的是压缩和格式转换的可重复性。
但很快又出现了第二个问题:原图从哪里来?
第二层:AI 生图也需要工作流
给文章配图时,生图本身只是一个环节。
真正麻烦的是围绕生图的上下文:
- 每张图的提示词要能保存和复用。
- 生成出来的原图要有固定位置。
- 已经生成过的图不要重复生成。
- API key 不能写进项目仓库。
- 模型、尺寸、质量、代理、base URL 这些配置应该可以复用。
- 生成完之后最好能直接进入压缩流程。
如果每次都临时写脚本,这些细节会不断散落在不同项目里。脚本一多,就会出现新的问题:哪个脚本能用,哪个脚本已经过期,哪个脚本里写死了本地路径,哪个脚本里不小心带了敏感配置。
imgasset 解决的就是这部分。
它把生图配置分成三层:
- 全局 profile:保存 base URL、模型、尺寸、质量、代理等非敏感配置。
- 全局 secret:保存 API key,不进入项目仓库。
- 项目配置:保存当前项目的原图目录、发布目录、压缩参数。
初始化配置:
imgasset config init
创建一个 profile:
imgasset profile set default \
--base-url https://api.example.com/v1 \
--model gpt-image-2 \
--size 1536x1024 \
--quality medium \
--output-format png \
--default
保存 API key:
imgasset secret set default
然后在项目里写一个 JSONL 提示词文件:
{"out":"01-context.png","prompt":"Minimal surreal isometric 3D editorial poster..."}
{"out":"02-flow.png","prompt":"Minimal surreal isometric 3D editorial poster..."}
生成原图:
imgasset generate prompts.jsonl \
--raw-dir temp/imgasset/article/raw \
--skip-existing
这里的 --skip-existing 很实用。图片生成经常比较慢,也可能遇到网络波动。一批图如果生成到第三张失败,下一次重跑时不应该把前两张再生成一遍。
把两步连起来
单独有 tinify-cli 和 imgasset generate 已经能覆盖大部分场景,但最顺手的还是一条命令跑完整流程。
imgasset run prompts.jsonl \
--raw-dir temp/imgasset/article/raw \
--publish-dir public/assets/article \
--format jpeg \
--background white \
--skip-existing
这个命令会先生成原图,再调用内置依赖里的 tinify-cli 做压缩和格式转换。

也就是说,一个项目只要安装 imgasset,就同时拥有了生图和压缩能力,不需要再单独维护一套压缩脚本。
实际使用时,我通常会让目录结构保持这样:
temp/
imgasset/
my-article/
raw/
public/
assets/
posts/
2026/
my-article/
prompts.jsonl
temp/ 放原图和临时文件,进入 .gitignore。
public/assets/ 放压缩后的发布图,可以被文章引用。
Markdown 里只引用最终输出:

这个边界很重要。原图是生产资料,发布图才是网站资产。
为什么不用一个脚本解决
最开始当然可以用一个脚本解决。
甚至对一个项目来说,一个脚本往往是最省事的。把 API key 从环境变量里读出来,循环请求图片接口,生成后再调用压缩 API,几十行代码就能跑起来。
问题在于,这类脚本很容易变成一次性资产。
当第二个项目也需要类似流程时,就会开始复制脚本。复制之后又会改目录、改模型、改压缩格式、改代理、改错误处理。再过一段时间,就很难判断哪一份才是最新实践。
工具化的价值不在于代码量更少,而在于把边界固定下来:
- API key 永远不进项目。
- 原图默认进入临时目录。
- 提示词用 JSONL 保存。
- 输出路径由命令或项目配置决定。
- 压缩能力通过依赖提供,不要求每个项目额外安装。
- 中断后可以继续跑。
这些约定一旦稳定下来,后续每个项目都能复用同一套工作流。
关于安全和开源
这两个工具都发布到了 npm,也放到了 GitHub 上。
开源前我重点处理了三件事。
第一是密钥。API key 只能存在全局 secret 文件或环境变量里,项目配置、提示词文件、日志和报告都不应该包含密钥。

第二是示例。示例里只能出现 https://api.example.com/v1 这种占位 base URL,不能把任何实际使用的服务地址写进去。
第三是发布流程。两个包都尽量走标准 npm 包形态,imgasset 还配置了 GitHub Actions 和 npm Trusted Publishing。发版时只需要:
pnpm run release
脚本会递增版本号、打 tag,GitHub Actions 再根据 tag 发布到 npm。
这套流程看起来比手动 npm publish 麻烦一点,但长期更可靠。尤其是开源包,发布过程越可追溯越好。
最后还是回到写文章
这套工具做完之后,变化最明显的地方不是少敲了几行命令,而是配图不再打断写作节奏。
以前一想到要给文章补图,脑子里会先冒出一串杂事:提示词放哪,原图放哪,压缩后叫什么名字,路径会不会写错,失败后要从哪里接着跑。每件事都很小,但它们会把注意力从文章里拉出来。
现在流程更接近这样:
- 读文章,决定需要几张图。
- 写
prompts.jsonl。 - 跑
imgasset run。 - 把输出图插进 Markdown。
- 构建检查。
真正需要判断的部分还在:文章适合什么意象,几张图够不够,图片放在哪里能帮助阅读,哪张图虽然好看但不该用。工具只是把目录、命令、格式转换和失败重试这些事情固定下来。
所以这篇文章想记录的不是“又写了两个 npm 包”,而是一个小流程变稳的过程。
小工具最有价值的状态,大概就是平时感觉不到它的存在,但换一个项目时又能马上带走。tinify-cli 处理压缩这一步,imgasset 把生图、原图保存和发布输出串起来。它们加在一起,解决的不是某一次图片生成,而是下一篇文章、下一个站点、下一个项目里仍然能复用的图片资产流程。