公众号创作技能包
公众号排版渲染
wechat-render
把 Markdown 一键渲染成公众号内联样式 HTML,可套内置主题或你自己保存的主题,直接贴进公众号草稿。免费不扣点。注意:本能力走专用接口 POST /api/wechat/render,不走 /api/skills/<slug>/invoke 通用代理。
免费查看详情
wechat-draft-publish
本鸭帮你把选好题、写好的公众号图文直接推进草稿箱。通过专用接口 POST /api/wechat/publish 调用(需先 GET /api/wechat/status 拿到已绑定公众号的 authorizerAppid),把写好的图文存进你自己公众号草稿箱;只存草稿、不群发。每次成功调用 1 点。注意:本能力不走 /api/skills/<slug>/invoke 通用代理,请直接调用 POST /api/wechat/publish。适合新媒体运营做发布前的草稿准备与授权发文。
把写好的图文一键存进你自己授权的微信公众号草稿箱:先授权绑定公众号,再发布图文草稿,只存草稿、不群发。每次成功调用 1 点。注意:本能力走专用接口 POST /api/wechat/publish,不走 /api/skills/<slug>/invoke 通用代理。
以下内容来自当前能力目录,用于理解字段形状,不代表真实调用结果。
{
"authorizerAppid": "必填,用 GET /api/wechat/status 取你已授权公众号的 appid",
"title": "一篇公众号图文标题",
"contentHtml": "<p>公众号风格的正文 HTML</p>(正文里的本地图片需客户端先经 POST /api/wechat/media/upload 预上传并改写 src——服务端读不到你本机文件,只搬运外链图)",
"digest": "可选,摘要"
}{
"mediaId": "样例草稿 media_id"
}公开 SKILL.md 快照,按文档中的步骤和边界使用这项能力。
本鸭帮你把一篇已经写好的图文,走一串确定性的机械步骤,最终存进你自己公众号的草稿箱——
只存草稿,绝不群发。存完给你 mediaId,你再去公众号后台亲眼确认、手动群发。
📍 接的是哪一棒:用户说「帮我写一篇公众号文章」时,正文那一段归
dby-write—— 它是写作主干的 owner,七步顺序只定义在它那里,这里不复述(复述必漂)。 取数、爆款样本、封面套路、合规检测仍由dby-api按意图路由承接。 正文落地之后再看用户要的终态: 只要成稿就到那里为止;要排版好的公众号 HTML 或要文章进自己的草稿箱,才回到这里。 用户没表达过后一种意图时先问一句——这一步会写进他自己的公众号后台。
⚠️ 写入能力:会写到你自己的公众号后台。所以只做「存草稿」这一步,群发的手一定在你自己。 走 doubaoya.com 一条线,鉴权用你自己的密钥
DOUBAOYA_API_KEY(形如dyh_…)。
分工:正文由 dby-write 写(或用户自带);本流水线不代写正文,只自动化后续那些确定性的运维步骤
(校验账号、渲染、传图、存草稿)。
正文没有本地图片、也没有本地封面、也不需要走本流水线的排版/主题/引导式设计——只是把已经是
公众号风格 HTML(不是 markdown)的正文存进草稿箱,直接用零依赖的 Python 入口,
不必走 pipeline.mjs 那一整串渲染/传图/封面步骤:
python3 "$SKILL_PATH/scripts/publish_draft.py" \
--title "标题" --content-file article.html
脚本行为:先 GET /api/wechat/status(恰好 1 个绑定自动选用;多个且没给 --appid 会列出让你重跑指定;
0 个提示先去绑定),再 POST /api/wechat/publish 存草稿,成功打印 mediaId。参数:--title(必填)、
--content 或 --content-file(二选一必填)、--appid(可选)、--digest(可选)。
计费:只在成功时扣点——存草稿成功了才扣;发布失败(502 WECHAT_PUBLISH_FAILED / WECHAT_COVER_FAILED)服务端会
自动把已扣的点数退回,参数被前置拦下的 400 VALIDATION_ERROR 则压根不扣。(具体扣多少以详情端点的实时点数字段为准,别照文档里的数字替用户算钱。)
正文里若含本地图片或本地封面,
publish_draft.py读不到本机文件,图会被静默丢弃—— 这种情况改用scripts/preprocess-and-publish.mjs(见下方组合结构)或走完整的pipeline.mjs。
🔴 防误发红线(无论走哪个入口都成立,逐字重复一遍):只存草稿、绝不群发;这是一个「写入」能力, 需先绑号(先在 doubaoya.com 把公众号授权绑定,本技能替不了你绑);用户只要成稿时别自作主张跑它—— 只有用户明确要排版好的公众号 HTML 或要文章进自己的草稿箱时才回到这里。群发的手永远在用户自己。
pipeline.json10 步 SOP 与全部硬规则声明在 pipeline.json(steps[] + hardRules[])。
本 SKILL.md 与编排脚本 scripts/pipeline.mjs 都以它为准——改流程先改 pipeline.json,别在各处硬编码。
其中第 6 步「引导式设计」由 agent 执行(选风格 / 生封面 / 生配图 / 排版确认,见下方引导式设计), 它把产出(
--cover本地封面 + Markdown 里的本地<img>)喂给后面的机械步骤;pipeline.mjs本身仍是渲染→传图→存草稿的确定性执行器。
isNot 消歧 / 语气)。GET /api/agent/whoami,把本地 key 解析成目标账号那一条(key 只在内存)。GET /api/skills(断言 slug=wechat-draft-publish 存在)+ GET /api/wechat/status(确认公众号、解析 appid/昵称)。--md 时渲染成公众号内联样式 HTML(原样保留 <img src>);--html 时直接用。--cover-guard,1536x1024)→ 生配图(1024x1024,落进 Markdown 源后回到第 5 步重渲染)→ 排版确认。引导默认,「你全权定」是逃生舱。见下方引导式设计。<img>,本地图片客户端预上传到图床(>1MB 先压缩)并改写 HTML;外链原样保留。POST /api/wechat/publish(draft/add)。hardRules,代码里强制)--mass-send/--broadcast/群发 参数。/api/skills,执行走 /api/wechat/status + /api/wechat/publish,不走 /invoke。三条调用都由脚本打,你跑 CLI 就行(见「CLI 用法」):
| operationKey | 用在第几步 | 谁在打 |
|---|---|---|
skill.wechat.render ⚠️专用路由 | 第 5 步 md→HTML | scripts/pipeline.mjs |
skill.ai.imageGen | 第 6 步生封面 / 配图 | scripts/gen-image.mjs(单独要一张图、不走流水线时去 dby-image) |
skill.wechat.draftPublish ⚠️专用路由 | 第 9 步存草稿 | scripts/pipeline.mjs |
脱离流水线自己发请求时才需要完整协议:references/protocol.md。
(两条专用路由的调用地址跟详情端点毫无关系、推不出来,只能读 execution.target。)
下面这些即使你只跑 CLI 也必须知道 —— 它们是判断题,脚本替不了你。
write_external:写进用户自己的号之前,先停详情响应的 execution.sideEffect 有四个值,其中 write_external 意味着会写进用户自己的外部账号
(他的公众号后台)。看到它就先停下,把四样摆给用户看、等他明确同意再打:
①要调哪条能力 ②写进哪个账号 ③要写进去的内容要点 ④预期结果与能不能撤销
🔴 不得从用户最初那句话里推定同意 —— 「帮我写篇文章发出去」授权的是写, 不是替他按下发布。这个判据是服务端字段不是本地清单:能力改了副作用,你下次拉详情就会看到。
其余三值:read 直接调;generate 会生成内容并计费,重试前先确认上一次真没出货
(已出货再重试 = 用户付两次钱);write_internal 只写进用户在都爆鸭的存储。
| 情况 | 能不能重试 |
|---|---|
429 TOO_MANY_REQUESTS | 退避后可以,别加大并发。限流按来源 IP 分桶、不按 key —— 换钥匙、开新会话都绕不过去,同一出口网络下的其他人也共用这个桶 |
502 PROVIDER_FAILED | 可以,额度已自动退回 |
404 SKILL_NOT_FOUND / ENDPOINT_NOT_FOUND | 多半是本机 skill 过期(它点名的能力早下架了)。跟用户说一句「你的本鸭 skill 可能过期了」,让他跑 /dby-update,然后只重试这一次。🔴 仍是 404 就如实告知能力已下架,不许再更新、不许成环 |
503 CAPABILITY_UNAVAILABLE | 不行,换能力或如实告知 |
| 401 / 400 / 402 | 不行。钥匙问题 / 入参问题 / 余额问题,更新 skill 一个都治不了 |
${KEY:0:6} 这种写法就是在打印密钥)。计价数字本文一律不写。 会静默重定价,抄进来就是对用户报错价。只记两件不随价格变的事: 存草稿与生图都花钱、服务端排版渲染不花钱,而花钱的那两步动手前先问用户。
用户要的东西超出本流水线这三条时,去
dby-gateway的references/capability-index.md选路。 🔴 别把索引表抄回本文件 —— 能力目录一周变一次,抄进来的当天就开始腐烂。
正文由**你(agent)**撰写,下面两条决定这篇稿子在真机上长什么样——动笔前先过一遍。
公众号总是拿草稿的 title 字段渲文章页大标题。正文里若还有同一个标题,真机上显示两次。
##。标题只走 --title 参数,别写进正文。--html):别在 HTML 开头放 <h1>(或拿来当大标题使的 <h2> / 加粗大字)——
这条路把文件原样发出去,没有任何东西替你去重。render-wechat-html.mjs --title "标题" 会往正文顶部插一个 <h1>,那是给本地预览
看整篇效果的;这份产物别直接拿去发布,发布走 pipeline.mjs。
pipeline.mjs --md这条路已经替你剥掉源文件开头的 frontmatter 与单个#标题 (normalizeDraftMarkdown),且不把--title注进正文。但那是兜底不是许可:正文中间第二处 标题、或用##重写一遍标题,它都管不了。
GET /api/wechat/writing-spec稿子是走
dby-write写的?那它第 1 步已经拉过这一份了,别再拉一次。 本节是给「正文从别处来」的情形准备的 —— 你手上只有一篇写好的 markdown, 而它是否符合平台硬约束还没人核过。✅ 接口已上线,正常拉取即可。拿到 401 说明
DOUBAOYA_API_KEY缺失或不对——提示用户检查 密钥配置,别跳过。只有遇到网络错误或真 404 时才降级:跳过这一步照常写 (上面那条 + 提示块 已经够用),别死循环重试、别当故障报给用户。
写正文前拉一次,按它组织结构再动笔。它把「什么内容该写成什么 markdown 结构」和「平台会整篇打回 / 静默丢内容的硬约束」写成一段可直接照做的文字。
curl -sS https://doubaoya.com/api/wechat/writing-spec \
-H "Authorization: Bearer $DOUBAOYA_API_KEY"
只读、免费、不扣点(这条路径根本不进记账),也不改用户的任何配置。鉴权与其它接口一致
(Bearer 密钥,或网页端登录态);未鉴权 401。
成功信封的 data 里带一段 markdown 写作规范(要照着写的就是它)、这套排版的元信息,
以及去哪自定义排版的入口。具体有哪些字段照这一次的实际响应读,别照记忆或本文档读——
本文档故意不列字段表,理由见上面协议第 2 条。
没设置过排版的用户照样拿到可用规范(默认主题 + 只出「结构建议」那块),不返空、不报错; 响应会告诉你这份规范用的是不是默认排版,是的话把自定义入口转达给用户就好,别当成错误处理。
规范正文分两块,成立条件不一样:
--theme,config.json 也没把 mdTheme 写成路径)时,渲染请求里一个主题字段都不带,
服务端直接套你在排版工作室保存的默认排版——此时第二块适用,照着写。
显式 --theme <path> / config.mdTheme 钉了另一套主题时第二块不适用,只照第一块写
(流水线会打出本次的 排版来源,看那一行为准)。只有一个事实源。 渲染由平台做(POST /api/wechat/render),主题也由平台套。流水线不再把服务端主题拉回本机
再套一遍——那套「本机四级优先级 + 拉取回退」整个退场了,因为服务端自己就有同构的优先级,
留着等于同一个决策做两遍,一漂移就是「主题双源对不上」。
| 你怎么写 | 实际用哪套排版 |
|---|---|
| 什么都不写(推荐) | 你在 doubaoya.com 排版工作室保存的默认排版。请求里一个主题字段都不带。 |
--theme <path> / config.mdTheme 写成路径 | 那份本机主题 JSON。流水线先在本机校验再整套送出(不合法就当场红,逐条列错——送到服务端只会换回一个更难读的远端 400)。 |
--theme neutral | 渲染器内置的中性排版,零品牌色。 |
想换默认排版就去排版工作室改,那是唯一该改它的地方。改完流水线下次跑自动就是新的,
不需要在本仓改任何文件。跑完看日志里的 排版来源: 那一行确认本次实际用了哪套。
第 5 步 md→HTML 只走平台(POST /api/wechat/render,免费不扣点)。这条路的产物自带一个
在线预览链接(detailUrl),点开就能看到排出来什么样——手机宽度的沙箱预览,不是 HTML 源码。
流水线会在步骤 4 与最终回报里各打一次那个链接,请把它转达给用户。
🔴 渲染失败一律中止,绝不回退本机渲染器。静默回退会产出「看起来成功、却没有预览链接、 排版还可能不是用户设的那套」的东西——那正是这条路存在的理由被抵消掉的样子。
⚠️ 它是专用路由:调用地址跟能力详情端点毫无关系,只能读详情响应里 execution 的 target。
本机渲染器 scripts/render-wechat-html.mjs 还在,但已退出流水线主干,只服务两个场景:
设计工作台 design-studio.mjs;以及用户没有密钥、只想先看这篇排出来什么样——
node scripts/render-wechat-html.mjs --md a.md --out a.html
🔴 走那条路没有在线预览链接(只能自己打开本地文件看)。要给用户链接就得走平台。
实测两个渲染器在两个构件上画法不同,其余(段落 / 强调 / 标题 / 列表 / 引用 / 有序列表 / 行内代码 / 链接)逐个一致:
| 构件 | 平台渲染(现在) | 本机渲染(以前) |
|---|---|---|
> [!NOTE] 一类提示块 | 引用块形态,带彩色左边框与标签 | 卡片形态,带一个 SVG 图标 |
--- 分割线 | 装饰性分割块 | 裸 <hr> |
两种都是合法的公众号排版,不是退化,只是长得不一样。老稿子重新跑一遍会看到这个变化。
> [!NOTE] 一类)正文里可以直接用 GFM alert 记号,平台渲染器会解析:
> [!NOTE]
> 正文一段。
支持 NOTE / TIP / IMPORTANT / WARNING / CAUTION,记号后面可以跟一句自定义标签
(> [!NOTE] 先看这个)。产出纯内联样式、无 class / id,符合公众号红线。
scripts/pipeline.mjs 是编排者,它组合三个零依赖模块:
| 阶段 | 模块 | 说明 |
|---|---|---|
| 账号解析 | scripts/account-verify.mjs | resolveAccountKey({account, baseUrl}):多来源(env / ~/.doubaoya / Keychain)候选 → 逐个 whoami → 按目标账号挑对 key,key 只在内存。多 key 指向不同账号且未指定 --account 时,报出各 key 对应账号并停。 |
| md→公众号 HTML | 平台 POST /api/wechat/render | renderViaPlatform({baseUrl,apiKey,markdown,themeJson,themeId})(在 pipeline.mjs 内):免费不扣点,主题由服务端套,返回 {html, themeSource, warnings, detailUrl}。失败抛错,调用方中止,绝不回退本机渲染器。 |
| md→公众号 HTML(本机,已退出主干) | scripts/render-wechat-html.mjs | renderWechatHtml(md,{title,theme}):零依赖内联样式渲染,原样保留图片 src。只服务设计工作台与「无密钥先看排版」,不产生在线预览链接。 |
| 封面/配图生图 | scripts/gen-image.mjs | generateImage({prompt,size,out,styleId,coverGuard,referenceImage}):零依赖,是能力 skill.ai.imageGen(详情端点 GET /api/skills/gpt-image-gen)的薄壳,同步返回、计费。传 referenceImage(本地路径/URL/data:/裸 base64,CLI --reference-image)时走 operation:"edit" 条件化,保留参考图里的 IP 形象;不传则文生图。另导出 resolveReferenceImage(ref)(本地图 → data: URL 小工具)。风格库 assets/styles/index.json,用 env DOUBAOYA_API_KEY(无需额外密钥)。产出本地 jpeg → 喂 --cover 或以 <img src> 落进正文,不碰发布契约。由 agent 在引导式设计里调用(不由 pipeline.mjs 机械触发)。 |
| 配图自动布局 | scripts/plan-figures.mjs | planFigures(markdown,{maxFigures,minChars}) → {figures[],meta}:确定性规则(不接 LLM)决定在哪些 h2 小节末尾配图 + 画面建议。按小节有效字数过阈值(默认 160)挑,张数按总字数分档(<1800→3、1800–3000→4、>3000→5)封顶。CLI node plan-figures.mjs --md <文章> [--max-figures N] [--min-chars N] [--json]。工作台「自动配图」调它,产出直接填 design-config.images[](afterHeading 锚点),由现有 pipeline 注入逻辑消费,不改发布链路。 |
| 传图 + 存草稿 | scripts/preprocess-and-publish.mjs | 本地图预上传 + >1MB 压缩 + 存草稿(draft/add,无群发)。无本地图/无本地封面场景可换更轻的 scripts/publish_draft.py(Python,见只想存草稿、不要排版)。 |
编排者把这三步串起来,并加上身份上下文加载、前置检查、硬门与结构化回报。
第 6 步——渲染前后完成视觉设计。引导是默认:在下面 4 处停下来问用户;逃生舱:用户若说
「封面配图你全权定 / 我赶时间」,就跳过所有停顿,用 config.defaultStyleId 自动出一版。
生图走能力 skill.ai.imageGen(详情端点 GET /api/skills/gpt-image-gen),无需额外密钥
(用发布本就在用的 DOUBAOYA_API_KEY)。想在对话里逐张生就用零依赖薄壳 scripts/gen-image.mjs,
缺密钥时它报清晰错误、不崩。这一步花钱,动手前先问用户(现价现拉,本文不写数字)。
assets/styles/index.json 的 6 个风格(name + id)和各自样图 assets/styles/<id>.jpg
列给用户挑(或用户说「你定」)。6 个起手风格:杂志编辑风(magazine-editorial)、极简大字(minimal-bigtype)、
真实摄影感(photo-real)、扁平插画(flat-illustration)、国潮中式(guochao-chinese)、商务信息图(biz-infographic)。1536x1024,展示给用户 →
选 / 重生 / 自己传 / 用兜底。定了就设进 --cover <本地jpeg>。封面必须加 --cover-guard
(把主体压在水平中带、上下留氛围背景,防公众号 2.35:1 居中裁切切掉关键内容):
node scripts/gen-image.mjs --prompt "<封面概念>" --style <风格id> --cover-guard \
--size 1536x1024 --out <暂存目录>/cover.jpg
## 小标题下 1 张),提议张数与各自画面,逐张生成 1024x1024
并以 <img src=本地路径> 落进 Markdown 源(不是渲染后的 HTML——放进源里才会被主题套上图注/圆角/间距)。
node scripts/gen-image.mjs --prompt "<该段画面>" --style <风格id> \
--size 1024x1024 --out <暂存目录>/fig1.jpg
配图落进 Markdown 后回到第 5 步重渲染。这些本地图会被现有 preprocess-and-publish.mjs 走 image 上传,
无需改动任何发布链路。--theme <path> / config.mdTheme 指一份本机主题 JSON;
写主题见下方「复刻参考排版风格」)。
gen-image.mjs生成的本地 jpeg 路径,封面喂pipeline.mjs --cover、配图以<img src>落进正文—— 两者都不触碰微信侧发布契约。上游生图密钥只在 doubaoya 服务端,skill 端只用密钥。
不想在命令行里逐步选风格 / 生图,可起本地网页工作台一次点完,产出一个 design-config.json,再交给
pipeline.mjs --design 消费。工作台零依赖(Node 内置 http + 全局 fetch),只绑 127.0.0.1,只写本地产物,不发布、不提交。
export DOUBAOYA_API_KEY="dyh_你的密钥"
node scripts/design-studio.mjs --md <文章.md> --title "<标题>" \
[--out <默认同目录 文章.design.json>] [--port 4599]
注册卡通 IP(可选,保持全篇形象统一):把你的卡通 IP 形象图放进 assets/ip/(或页面顶部「上传 IP」),
并在 config.json 里把 ipImage 指向它。注册后,封面与配图默认走参考图条件化生成
(operation:"edit" + referenceImage),保留同一形象让全篇视觉统一;未注册则退回文生图。
见 assets/ip/README.md。
页面三区:①排版 = 主题卡片实时换肤预览(左侧 375px 手机公众号外框);②封面 = 选生图风格 →
生成候选(默认套用当前 IP 参考图,可再生 / 上传自己的)→ 挑一张;③配图(自动布局) = 点「自动配图」→
后端 plan-figures.mjs(确定性规则,不接 LLM)自动挑好位置(信息量大的 h2 小节末尾、张数按字数分档)→
逐张用 IP 参考图生成并自动摆好,用户只做「换一张 / 删除 / 整体重生」,不手选锚点。顶部「保存配置」
写出 design-config.json(含 ip 与自动填充的 images[],过 schemas/design-config.schema.json 校验)。
生成的封面/配图 jpeg 落 design-config 同目录的 .design/assets/。
拿到 design-config.json 后进流水线(套主题 + 设封面 + 按 h2 锚点注入配图):
node scripts/pipeline.mjs --md <文章.md> --title "<标题>" --design <文章.design.json> --dry-run
--design的主题 / 封面是默认值;显式--theme/--cover与之冲突时命令行优先并告警。配图按afterHeading锚点插在对应 h2 小节末尾,找不到锚点则追加文末并告警。工作台 +--design与上面的命令行 引导等价,二选一即可,都不触碰微信侧发布契约。
# 1. 复制配置模板,填你自己的值(见 config.example.README.md 逐字段说明)
cp config.example.json config.json
# 2. 复制身份 profile 模板,改成你自己账号的身份卡
cp profiles/example-ip.json profiles/my-ip.json
# 再在 config.json 里把 ipProfile 指向 profiles/my-ip.json
config.json 关键字段:targetAccount(多 key 时挑账号)、appid / publicAccountName(选/校验公众号)、
ipProfile(身份卡路径)、coverFallback(兜底封面标记)。null = 自动探测。config.json 属于你个人,别提交到公共仓库。
找不到
config.json时(本包原名 wechat-article-pipeline,早前跑/dby-update对账时若对账器 还不认识改名表,会把整个老目录连同你自建的config.json/profiles/一起归档),pipeline.mjs会自动去.doubaoya/archive/里探一探,探到了就在 stderr 打印归档路径与 可直接粘贴的cp恢复命令;探不到什么都不打印,不影响现有行为。
一个账号名 / IP 名很可能和某个通用名词或产品品类同名。若不先加载身份上下文,agent 可能把这个
专有名词误读成字面意思的通用名词,导致选题、配图、封面全跑偏。profile 里的 isNot 就是把这条
消歧规则外化成数据:流水线第 2 步先读它、回显它,明确「这是账号名,不是那个通用名词」。
示例 profile(profiles/example-ip.json,虚构的 示例·日常号)演示了 schema——请照它写你自己账号的身份卡。
详见 profiles/README.md。
export DOUBAOYA_API_KEY="dyh_你的密钥" # 或放 ~/.doubaoya/key、Keychain(account-verify 会找)
# A. 从 Markdown 开始(渲染 → 传图 → 存草稿)
node scripts/pipeline.mjs --md article.md --title "标题" --config ./config.json
# B. 已有排好版的 HTML,直接发
node scripts/pipeline.mjs --html article.html --title "标题"
# C. 指定账号 + 公众号 + 本地封面 + 摘要
node scripts/pipeline.mjs --md a.md --title "标题" \
--account you@example.com --appid wx0123... --cover cover.png --digest "本期摘要"
# D. 干跑:只渲染+校验+扫描本地图,什么都不发
node scripts/pipeline.mjs --md a.md --title "标题" --dry-run
# E. 起可视化设计工作台选主题/封面/配图 → 产出 design-config.json(见「用设计工作台」)
node scripts/design-studio.mjs --md a.md --title "标题" # 网页里点完「保存配置」
# F. 用设计工作台产出的 design-config 跑流水线(套主题 + 设封面 + 按 h2 锚点注入配图)
node scripts/pipeline.mjs --md a.md --title "标题" --design a.design.json --dry-run
参数:--md | --html(二选一)、--title(必填)、--account、--appid、--cover、--digest、
--config、--profile、--theme、--design、--output-processed-html、--base-url、--dry-run、--help。
只存草稿:本流水线没有任何群发参数。传
--mass-send/--broadcast/带「群发」字样的 flag 会被直接拒绝。
想让排版长得像某个你欣赏的公众号,或某种描述得出的风格?把它一次性萃取成一个 theme.json,
之后永久复用(每次渲染只需 --theme my-theme.json,见下方 CLI)。主题契约的权威是
themes/THEME-SCHEMA.md(top-level 只有 meta/palette/page/elements/decorations)。
校验器是 scripts/validate-theme.mjs。本机预览用 scripts/render-wechat-html.mjs --theme;走流水线时 pipeline.mjs --theme <path> 会先在本机校验再整套送去平台渲染。
写主题是一次性的活;产出的
theme.json之后一直用。默认主题是themes/benya-clean.json(本鸭精品「知识清爽」风,推荐)。不想从零写?先从内置主题themes/benya-clean.json(默认/推荐)/themes/magazine.json/themes/minimal.json/themes/knowledge.json里挑一个最接近的复制再改。
流程 = 抓取 →(零 token 启发式)萃取草稿 → LLM 精修 → 校验 → 渲染。 其中「萃取草稿」是一次快速的零 token 首过(用启发式把配色/排版扒出来), 真正把它做到「精修」的是你(LLM)对草稿的refine——这正是我们相对纯启发式工具的优势所在。
启发式萃取算法来自 oaker-io/wewrite(MIT © 2026 OpenClaw) 的
analyze_styles(),零依赖 Node 重写移植进scripts/extract-theme.mjs(署名见文件头 +meta.notes)。
抓取参考正文(一次性风格学习,抓的是一篇公开文章、不登录、不批量):
node scripts/fetch-article.mjs --url "https://mp.weixin.qq.com/s/..." --out ref.html
它提取正文 #js_content,保留所有 inline style="…"(这些内联样式就是我们要分析的数据),
去掉 <script>/<style>/注释,并打印风格指纹:各标签数量、出现最多的颜色、用到的字号。
若该链接被反爬/已过期而抓不到,脚本会明确提示你:在浏览器里打开文章、查看源码,把正文 HTML 贴进本地文件来分析(授权步骤对任何公众号正文 HTML 都适用,不只限本抓取器)。
萃取候选主题草稿(extract-theme.mjs,零 token 快速首过):
node scripts/extract-theme.mjs --html ref.html --name "参考风格" --out my-theme.json
# 或一步到位(内部复用 fetch-article 抓正文):
node scripts/extract-theme.mjs --url "https://mp.weixin.qq.com/s/..." --name "参考风格" --out my-theme.json
它按标签分组内联样式,扒出 text / text_light / 主色 accent(strong/section/h1-3/span 的非灰色加权计数,
font-size≥20px 权重 ×5)/ 背景 / 排版(字号·行高·字距)/ 引用边框与底色 / 代码色 / 圆角,
盖进一套中性基底模板(用 {{token}} 注色),产出一份通过 validate-theme.mjs 的 theme.json 草稿。
信号弱时(135/秀米 导出把色写在
span而非p上等)它会回落到中性默认并告警「低置信度」——正常,交给下一步精修。
你(LLM)对着参考精修草稿(我们的核心价值——启发式看不到的东西由你补齐):
按下面的 CHECKLIST 逐项核对 my-theme.json,修正主色、规整脏值(2em→具体行高、把色从 span 归到 text 等)、
补上装饰分割线 / 标题处理:
elements.h1..h3.style,装饰条用 wrapBefore)。p:font-size / line-height / color / letter-spacing / 段间距 margin(→ elements.p.style 与 page)。blockquote:左边框 / 背景 / 字色(→ elements.blockquote.style)。elements.li.marker + ul/ol/li.style)。elements.img.style + figureStyle / captionStyle)。strong / em / a 的处理与主色(→ elements.strong/em/a + palette.accent/link)。text/heading/accent/accent2/muted/bgSoft/border/link);
启发式常把某个高频装饰色误当主色——对照抓取器指纹「出现最多的颜色」改回真正的主色。elements.hr.html;整篇卡片/边框背景 → decorations.articleWrap;
命名分隔片段 → decorations.sectionDivider(这些启发式扒不出来,靠你补)。校验 → 渲染:
node scripts/validate-theme.mjs my-theme.json # 有硬错误就按提示改
node scripts/render-wechat-html.mjs --md a.md --title "标题" --theme my-theme.json
# 或直接进流水线: node scripts/pipeline.mjs --md a.md --title "标题" --theme my-theme.json
诚实预期:公众号编辑器(秀米 / 135 等)导出的 HTML 很吵——满是一次性的内联样式。
extract-theme.mjs是快速首过,只保证扒出大致配色骨架;把它调到「像」靠的是第 3 步你的精修。 只保留反复出现的那套规律,别把每一处 one-off 样式都当成主题。
不需要参考文章:你(agent)按描述的调性直接照 schema 填 theme.json,再校验、渲染。
例:「性冷淡杂志风」→ 低饱和 palette、细 border/hairline hr、充裕留白(大 margin/line-height)、
克制近 small-caps 的标题(大字距、非高饱和色)。同样先 validate-theme.mjs 再 render --theme。
起步同样建议复制 themes/benya-clean.json(默认/推荐)/ magazine.json(杂志风)/ minimal.json(极简)/ knowledge.json(知识卡片)之一再改。
一切以 themes/THEME-SCHEMA.md 为准;主题索引见 themes/README.md。
统一前置:Node ≥ 18(内置 fetch),零外部依赖。除此之外按你要做的事分三层——
只想看排版效果、写/换主题、规划配图位置的用户,没有密钥、没绑公众号也能干活:
| 想做的事 | 除 Node 外还需要 | 怎么跑 |
|---|---|---|
| md → 公众号内联样式 HTML(本地出稿 / 看排版效果,无在线链接) | 无 | node scripts/render-wechat-html.mjs --md a.md --theme themes/benya-clean.json --out a.html |
| 校主题 / 写主题 / 导入外部主题格式 | 无 | scripts/validate-theme.mjs、scripts/import-theme.mjs、scripts/extract-theme.mjs --html ref.html |
| 复刻某篇公开文章的排版 | 公网(不要密钥) | scripts/fetch-article.mjs --url …、scripts/extract-theme.mjs --url … |
| 配图自动布局规划(确定性规则,不接 LLM) | 无 | node scripts/plan-figures.mjs --md a.md |
起本地设计工作台:实时预览、换肤、自动配图排位、存 design-config | 无(只有页面里点「生成」才要密钥) | node scripts/design-studio.mjs --md a.md --title "标题" |
| AI 生封面 / 生配图 | 一条 DOUBAOYA_API_KEY(花钱,现价现拉) | scripts/gen-image.mjs,或工作台里点生成 |
| 用你在 doubaoya.com 设置的默认排版渲染 | 一条 DOUBAOYA_API_KEY | 跑 pipeline.mjs 时不写 --theme 即可(渲染在平台做,主题也在平台套;失败中止不回退) |
跑 pipeline.mjs(含 --dry-run) | 密钥 + 已在 doubaoya.com 绑定公众号 | node scripts/pipeline.mjs --md a.md --title "标题" --dry-run |
| 本地图预上传 / 存草稿 | 同上(存草稿花钱,失败自动退回) | pipeline.mjs、scripts/publish_draft.py |
⚠️
--dry-run不是免密钥预览。它虽然什么都不发,但 whoami 校验账号与草稿前置检查 (GET /api/wechat/status)都排在它前面:没有密钥会停在「本地没有可用的DOUBAOYA_API_KEY」, 有密钥但没绑号会停在「目标账号没有已绑定的公众号」。 还没绑号、只想先看这篇排出来什么样:走render-wechat-html.mjs或设计工作台(都纯本地)。 🔴 但那两条都不产生在线预览链接——在线链接只有走平台渲染(即pipeline.mjs)才有。 注意单跑渲染器时--title会往正文顶部插一个<h1>(本地预览用),那份产物别拿去发布——见正文不要写标题。
绑好号、配好密钥之后,发布前先跑一次 --dry-run,确认身份上下文、目标账号、公众号、本地图扫描都对,再正式存草稿。
草稿进箱,用户「要一篇能发的公众号图文」这个终态就已经达成了——这里通常就是终点。 群发的手始终在用户自己:本 skill 没有任何群发路径,请他去公众号后台亲眼确认草稿 (排版、封面、图片都对)再手动群发。
发布之后如果用户还想往下走,可选:
| 用户接着想要什么 | 下一步 |
|---|---|
| 攒几天数据后看这个号的发文表现 / 做体检 | dby-api(打账号诊断能力 skill.wechat.accountAnalyzer) |
| 盯自己或竞品的发文节奏 | dby-api(打公众号发文列表端点) |
| 把已发布的文章拉正文归档 | dby-api |
| 用复盘信号挖下一轮选题 | dby-api(挖选题 / 追热点,也从这儿拉样本开写) |
| 说不清要到哪一步 | dby(公众号飞轮的逐跳导航) |
npx skills update dby-publish # 全局安装的加 -g
变更历史见
README.md的「最近变更」。