@zzclub/pipeline 0.13.2 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/ARTICLE-THEMES.md +86 -0
  2. package/LICENSE +21 -0
  3. package/README.md +182 -788
  4. package/THIRD-PARTY-NOTICES.md +25 -0
  5. package/package.json +6 -2
  6. package/src/adapter-types.ts +3 -0
  7. package/src/adapters/builtin-markdown-renderer.ts +1 -0
  8. package/src/article-theme.test.ts +87 -0
  9. package/src/article-theme.ts +115 -0
  10. package/src/commands/abandon.test.ts +1 -1
  11. package/src/commands/article-theme.ts +16 -0
  12. package/src/commands/checkpoint.test.ts +2 -2
  13. package/src/commands/ingest-handoff.ts +6 -0
  14. package/src/commands/init.ts +10 -0
  15. package/src/commands/prepare.ts +5 -1
  16. package/src/commands/publish.ts +4 -0
  17. package/src/commands/reset.ts +2 -0
  18. package/src/commands/wechat-export.ts +4 -0
  19. package/src/commands/wechat-preview.ts +35 -0
  20. package/src/plugins.ts +2 -0
  21. package/src/providers/index.ts +17 -2
  22. package/src/providers/publish-core.ts +32 -1
  23. package/src/providers/wechat.test.ts +12 -0
  24. package/src/providers/wechat.ts +60 -39
  25. package/src/render-branding.ts +1 -1
  26. package/src/schema/config.ts +12 -0
  27. package/src/schema/state.ts +5 -0
  28. package/src/wechat-preview/assets/browser-dist/.vite/manifest.json +21 -1
  29. package/src/wechat-preview/assets/browser-dist/assets/style-C9-YtTIm.css +1 -0
  30. package/src/wechat-preview/assets/browser-dist/assets/wechat-renderer-D1jHoaM2.js +624 -0
  31. package/src/wechat-preview/assets/browser-dist/editor-export.js +1 -512
  32. package/src/wechat-preview/assets/browser-dist/src/wechat-preview/browser/studio/studio.html +293 -0
  33. package/src/wechat-preview/assets/browser-dist/studio.js +332 -0
  34. package/src/wechat-preview/browser/editor-export.ts +17 -2
  35. package/src/wechat-preview/browser/highlight.ts +2 -0
  36. package/src/wechat-preview/browser/studio/color-palettes.test.ts +31 -0
  37. package/src/wechat-preview/browser/studio/color-palettes.ts +115 -0
  38. package/src/wechat-preview/browser/studio/studio.css +638 -0
  39. package/src/wechat-preview/browser/studio/studio.html +292 -0
  40. package/src/wechat-preview/browser/studio/studio.ts +721 -0
  41. package/src/wechat-preview/browser/studio/syntax-presets.test.ts +74 -0
  42. package/src/wechat-preview/browser/studio/syntax-presets.ts +590 -0
  43. package/src/wechat-preview/fixtures/all-nodes.md +38 -0
  44. package/src/wechat-preview/index.ts +36 -5
  45. package/src/wechat-preview/plugins/builtin-badge.ts +152 -0
  46. package/src/wechat-preview/plugins/builtin-callout.ts +261 -0
  47. package/src/wechat-preview/plugins/builtin-highlight.ts +46 -0
  48. package/src/wechat-preview/plugins/builtin-kbd.ts +133 -0
  49. package/src/wechat-preview/plugins/index.test.ts +313 -0
  50. package/src/wechat-preview/plugins/index.ts +6 -0
  51. package/src/wechat-preview/plugins/registry.ts +62 -0
  52. package/src/wechat-preview/plugins/types.ts +31 -0
  53. package/src/wechat-preview/server/http.ts +160 -3
  54. package/src/wechat-preview/server/paths.ts +2 -2
  55. package/src/wechat-preview/server/singleton.ts +14 -12
  56. package/src/wechat-preview/server/studio.test.ts +92 -0
  57. package/src/wechat-preview/server/ui.ts +1 -0
  58. package/src/wechat-preview/wechat-export.integration.test.ts +9 -1
  59. package/src/wechat-preview/wechat-renderer.ts +72 -7
  60. package/src/workflow.test.ts +1 -0
package/README.md CHANGED
@@ -1,897 +1,291 @@
1
1
  # zzhub-pipeline
2
2
 
3
- 面向 Agent 的内容发布状态机,将文案、图片和发布意图收敛为可恢复的工作流,推进到微信公众号文章或图片消息。
3
+ 一个可恢复的内容发布工作流。它把 Markdown、图片、账号和发布意图组织成一条明确的流水线,生成微信公众号草稿,或把文章同步到自己的博客项目。
4
4
 
5
- ## 核心原则
5
+ Pipeline 可以独立使用,也可以作为 Web、App、桌面客户端背后的执行服务:
6
6
 
7
- - `state` 是唯一真相源
8
- - Agent 先查任务状态,再决定下一步
9
- - 外部工具先把素材写入 pipeline,再由 pipeline 计算缺口
10
- - 中断后按实际状态恢复,而不是靠会话历史猜测
7
+ - **本机 CLI**:在本地准备、排版、渲染和写入草稿。
8
+ - **私有 Cloud Worker**:在服务器执行同一套 Pipeline,业务后端通过 HTTP 提交任务。
9
+ - **Agent 编排**:Agent 负责写稿、审核和修订,Pipeline 负责确定性的状态推进。
11
10
 
12
- ## 快速开始
13
-
14
- 前置条件:
15
-
16
- - Bun 运行时
17
- - Chrome,用于 `render` 和 `wechat-export`
18
-
19
- 安装:
20
-
21
- ```bash
22
- # 从 npm
23
- npm install -g @zzclub/pipeline
24
-
25
- # 或从源码
26
- bun install
27
- bun install --global .
28
- ```
29
-
30
- 二进制别名:
31
-
32
- - `zzhub-pipeline`
33
- - `zzp`
11
+ 微信的 `publish` 只会创建或更新草稿,不会群发或公开发布。博客路由会执行你配置的发布命令,实际效果由该命令决定。
34
12
 
35
- 配置文件位置,macOS 默认是 `~/Library/Application Support/zzhub-pipeline/config.json`。
13
+ > Cloud Worker 已在本仓库实现,可从源码构建 Docker 镜像部署。它不包含在 npm CLI 包中;npm 只分发 CLI 和渲染资源。`v0.14.0` tag 包含初版 Worker,账号同步和正文模板等接口以实际部署的 revision 为准,生产环境请固定 commit/tag。
36
14
 
37
- ## 架构概览
15
+ ## 能做什么
38
16
 
39
- 这是一个状态机模型,输入是任务意图、正文、图片、路由和发布要求,输出是可恢复的工作流状态、渲染产物和发布结果。
40
-
41
- 当前有三条主工作流路由:
42
-
43
- - `wechat-article` — 公众号文章草稿(HTML)
44
- - `wechat-newspic` — 公众号图片消息(PNG 卡片/图集)
45
- - `blog` — 将 Markdown 同步到配置的博客项目并执行发布命令
46
-
47
- `blog` 参与统一的 `prepare → publish` 工作流;作为唯一目标时跳过 render。`sync-blog` 继续保留为事后同步命令,两种路径都会把结果写回 `workflow-state.json` 的 `publish.results`。
48
-
49
- 状态文件分两类:
50
-
51
- - 运行态,临时文件,`{workspace}/.zzhub-media/runs/{run_id}.json`
52
- - 定稿态,最终文件,`{workspace}/posts/{date-slug}/workflow-state.json`
53
-
54
- 正文全文不会进入 state,正文内容统一放在 `{workspace}/.zzhub-media/tmp/{run_id}/`。
55
-
56
- ## 核心流程
17
+ | 能力 | 说明 |
18
+ | --- | --- |
19
+ | 公众号文章 | Markdown 转微信兼容 HTML,生成封面,上传图片,创建或更新文章草稿 |
20
+ | 公众号贴图 | 渲染单页卡片或多页 PNG 图集,创建 `newspic` 草稿 |
21
+ | 博客同步 | 把 Markdown 和图片同步到配置的博客项目,并执行发布命令 |
22
+ | 可恢复工作流 | 保存任务状态、素材、审核决定、渲染版本和发布结果,失败后可以继续或重置 |
23
+ | 多账号与多目标 | 支持多个公众号账号,以及微信、博客等发布目标 |
24
+ | 排版扩展 | 封面主题、正文模板、品牌配置和可替换渲染插件 |
25
+ | 排版工作台 | `zzp wechat-preview studio` 提供所见即所得排版调优、配色选型、语法预设与配置持久化 |
26
+ | 本机监控 | `zzp monitor` 提供任务快照、日志和 SSE 事件流 |
27
+ | 云端执行 | 私有 HTTP Worker 提供账号同步、串行队列、持久化任务和状态轮询 |
57
28
 
58
- ### Agent 编排循环
29
+ Pipeline 不内置 LLM、用户登录、计费或配额服务。调用方需要自己提供正文和审核决定;云端 Worker 接收已经确认的完整正文,并只推进支持的 CLI 动作。
59
30
 
60
- Agent 通过无状态循环驱动流水线,每轮只做一个动作。所有上下文存储在状态文件中,中断后重新执行 `status` 即可精准续接。
31
+ ## 工作方式
61
32
 
62
33
  ```mermaid
63
- flowchart TD
64
- START["Agent 启动"] --> FIND["find-run --view agent"]
65
- FIND --> HAS{"存在活跃任务?"}
66
- HAS -- "否" --> INIT["init 创建新任务"]
67
- INIT --> STATUS
68
- HAS -- "是" --> STATUS["status --view agent<br/>读取 next_action"]
69
- STATUS --> EXEC["执行 next_action.action<br/>对应的命令"]
70
- EXEC --> DONE{"action == complete?"}
71
- DONE -- "否" --> STATUS
72
- DONE -- "是" --> END["任务完成"]
73
-
74
- style START fill:#e0e7ff,stroke:#6366f1,color:#3730a3
75
- style END fill:#d1fae5,stroke:#10b981,color:#065f46
76
- style STATUS fill:#fef3c7,stroke:#f59e0b,color:#92400e
77
- style EXEC fill:#fef3c7,stroke:#f59e0b,color:#92400e
78
- ```
79
-
80
- ### 如何正确调用 Agent Skill
81
-
82
- Agent 通过 `.claude/skills/zzhub-publish/SKILL.md` 获取操作指引。任务描述的质量直接决定执行效果。
83
-
84
- **反模式(避免):**
85
-
86
- | 不要这样说 | 问题 |
87
- |-----------|------|
88
- | "帮我发一篇文章到公众号" | Agent 不知道主题、没有具体方向。大概率跳过 status 检查或写出空洞内容。 |
89
- | "发一篇关于 AI 的文章" | 主题太泛,agent 会写出套话文章,review 也难以判断好坏。 |
90
- | 不提 handoff 里的已有文件 | Agent 按默认路径重新写稿,浪费已有素材。 |
91
-
92
- **模式 A:Agent 从头写稿**
93
-
94
- ```
95
- 调用 zzhub-publish skill,完成以下任务:
96
-
97
- 文章主题:[具体 topic,一句话说清]
98
- 发布目标:大号
99
- 要求:
100
- - 每完成一步报告当前状态
101
- - 到 content review 通过后先暂停,等我确认再继续
102
- ```
103
-
104
- 给了具体主题 agent 才有明确的写稿方向,设边界让发布前有人工确认点,每步报告减少盲跳。
105
-
106
- **模式 B:从准备好的文件交接**
107
-
108
- ```
109
- 调用 zzhub-publish skill,从我准备好的素材继续:
110
-
111
- 我已在 /path/to/handoff.json 提供了 handoff 文件,
112
- body 文件也在 handoff 里指定好了(已经是一篇完整文章)。
113
-
114
- 1. 先读 skill 文件,然后从 ingest-handoff 开始
115
- 2. body 已经在 handoff 中指定了路径,不需要重新写稿
116
- 3. 按 skill 的 loop 走完后续所有步骤(prepare → review → render → publish)
117
- 4. 每一步报告动作和结果
118
- ```
119
-
120
- 明确告知 body 已就绪防止重复劳动,指出入口(ingest-handoff)让 agent 不用猜测,强调"后续所有"让 agent 一口气跑完。
121
-
122
- **模式 C:快速出草稿,暂不发布**
123
-
34
+ flowchart LR
35
+ Client[Web / App / 桌面端] --> Backend[你的业务后端]
36
+ Backend --> Worker[私有 Cloud Worker]
37
+ Worker --> Pipeline[Pipeline CLI]
38
+ Pipeline --> Relay[微信中转 API]
39
+ Relay --> Draft[公众号草稿箱]
40
+ Pipeline --> Blog[博客项目]
41
+ CLI[本机 zzp CLI] --> Pipeline
124
42
  ```
125
- 调用 zzhub-publish skill:
126
43
 
127
- 主题:周末咖啡馆随笔,轻松风格,~500字
128
- 发布目标:大号
129
- 重要:只到 render 完成就停,不要 publish。我要在草稿箱里人工预览后再决定。
130
- ```
44
+ 客户端不需要运行 Electron,也不需要直接访问 Worker。推荐让业务后端负责用户鉴权、账号归属、任务归属、配额和幂等键隔离,再通过内部网络调用 Worker。
131
45
 
132
- 风格+长度约束写稿质量,负向约束("不要 publish")设明确边界。
46
+ ## 快速开始
133
47
 
134
- **核心原则:**
48
+ ### 安装
135
49
 
136
- 1. **给具体主题** — "咖啡馆里的宇宙学" 远好于 "写一篇文章"。主题越具体产出越可控。
137
- 2. **明说上下文差异** — 已有素材或 body 文件一定要说出来,否则 agent 默认走 "init → 写稿"。
138
- 3. **在分叉点给提示** — 比如 "review 通过后暂停等我确认",或 "body 已在 handoff 里准备好了,跳过写稿"。
139
- 4. **用负向约束设边界** — "不要 publish"、"到 review 就停" 比正向的 "帮我做 X" 更能防止 agent 跑过头。
140
- 5. **先读 skill 再行动** — 指令里加上 "先读 SKILL.md",尤其是对 sub-agent。不假设 agent 已经知道规则。
50
+ Pipeline 需要 [Bun](https://bun.sh/) 运行。npm 可以安装包,但命令本身仍由 Bun 执行;渲染文章还需要 Chrome 或 Chromium。
141
51
 
142
- ### next_action 决策树(优先级 1→12)
52
+ ```bash
53
+ # 从 npm 安装
54
+ npm install -g @zzclub/pipeline
143
55
 
144
- `buildTaskStatus()`(`src/task-manager.ts`)按以下优先级依次判断,输出唯一的下一步动作。
145
- 每个分支互斥,一旦命中即返回,不会继续判断后续条件。
56
+ # 或从源码运行
57
+ git clone https://github.com/aatrooox/zzhub-pipeline.git
58
+ cd zzhub-pipeline
59
+ bun install
60
+ bun run src/cli.ts --help
146
61
 
147
- ```mermaid
148
- flowchart TD
149
- S["📄 读取 workflow state"] --> C1{"mode=done 或 phase=done?"}
150
- C1 -- "是" --> A1["✅ complete"]
151
- C1 -- "否" --> C2{"mode=failed?"}
152
- C2 -- "是" --> A2["🔧 reset-or-repair<br/>checkpoint 诊断 → reset 重置"]
153
- C2 -- "否" --> C3{"mode=handoff?"}
154
- C3 -- "是" --> C3a{"body_inputs 待处理?"}
155
- C3a -- "是" --> A3a["🖼️ attach-body-images<br/>等待用户提供插图"]
156
- C3a -- "否" --> A3b["📎 resolve-handoff<br/>checkpoint 解决外部依赖"]
157
- C3 -- "否" --> C4{"正文是否已挂载?<br/>source_body_path 或 asset_path"}
158
- C4 -- "否" --> A4["✍️ attach-body<br/>Worker 撰写正文"]
159
- C4 -- "是" --> C5{"body_inputs 待处理?"}
160
- C5 -- "是" --> A5["🖼️ attach-body-images"]
161
- C5 -- "否" --> C6{"元数据是否就绪?<br/>title, slug, date"}
162
- C6 -- "否" --> A6["📝 prepare<br/>路由+作者+排版+元数据"]
163
- C6 -- "是" --> C7{"content_review 状态?"}
164
- C7 -- "needs_revision" --> A7["🔁 revise-content<br/>Worker 改写正文"]
165
- C7 -- "unchecked" --> A8["👀 review-content<br/>Worker 审核内容"]
166
- C7 -- "passed" --> C7a{"asset_path 是否存在?"}
167
- C7a -- "否" --> A9["📦 prepare-finalize<br/>生成正式产物到 posts/"]
168
- C7a -- "是" --> C9{"render 是否需推进?"}
169
- C9 -- "是" --> A10["🎨 render<br/>imgx 图片渲染"]
170
- C9 -- "否" --> C10{"publish 是否需推进?"}
171
- C10 -- "是" --> A11["🚀 publish<br/>推送到发布渠道"]
172
- C10 -- "否" --> A12["📝 prepare<br/>兜底重新 prepare"]
173
-
174
- style A1 fill:#d1fae5,stroke:#10b981,color:#065f46
175
- style A2 fill:#fee2e2,stroke:#ef4444,color:#991b1b
176
- style A3a fill:#fef3c7,stroke:#f59e0b,color:#92400e
177
- style A3b fill:#fef3c7,stroke:#f59e0b,color:#92400e
178
- style A4 fill:#fef3c7,stroke:#f59e0b,color:#92400e
179
- style A5 fill:#fef3c7,stroke:#f59e0b,color:#92400e
180
- style A6 fill:#fef3c7,stroke:#f59e0b,color:#92400e
181
- style A7 fill:#ede9fe,stroke:#8b5cf6,color:#5b21b6
182
- style A8 fill:#ede9fe,stroke:#8b5cf6,color:#5b21b6
183
- style A9 fill:#fef3c7,stroke:#f59e0b,color:#92400e
184
- style A10 fill:#fef3c7,stroke:#f59e0b,color:#92400e
185
- style A11 fill:#fef3c7,stroke:#f59e0b,color:#92400e
186
- style A12 fill:#e5e7eb,stroke:#6b7280,color:#374151
62
+ # 可选:把当前源码安装成全局命令
63
+ bun install --global .
187
64
  ```
188
65
 
189
- 关键说明:
190
-
191
- - C7(content_review)仅在 `asset_path` 尚未存在时触发,`review-content` 和 `prepare-finalize` 都在同一 `!asset_path` 外层分支内
192
- - C9/C10 进入时 `asset_path` 已必然存在(由 `prepare-finalize` 写入),因此直接按 render/publish 阶段状态判断
193
- - `attach-newspic-spec` 不在决策树中 — 它是可选命令,在 render 前由 Agent 自行决定是否附加分页规格
194
- - `reconcile` 不在决策树中 — 它是手动维护命令,用于刷新素材对账,不自动触发
195
-
196
- 每个 `next_action` 包含:
197
-
198
- | 字段 | 说明 |
199
- | --- | --- |
200
- | `action` | 动作标识(如 `attach-body`、`prepare`、`render`) |
201
- | `executor` | 执行者类型:`cli` / `worker` / `await-input` / `repair` / `complete` |
202
- | `command` | 建议的 CLI 命令,含 `--state` 路径 |
203
- | `params` | 执行参数:`state_path`、`source_body_path`、`worker_mode` 等 |
204
- | `on_complete` | 执行后的后续指令(通常是 "re-run status") |
66
+ 两个命令别名都可用:
205
67
 
206
- ### 任务入口:init 与 ingest-handoff
207
-
208
- 任务有两种创建方式:`init`(Agent 直接初始化)和 `ingest-handoff`(外部系统交接 JSON 文件)。
209
-
210
- ```mermaid
211
- flowchart TD
212
- ENTRY{"如何启动任务?"}
213
- ENTRY -- "Agent 初始化" --> INIT["init --task-kind ... --content-form ...<br/>--targets ... --intent-text ..."]
214
- ENTRY -- "外部交接" --> HANDOFF["ingest-handoff --file handoff.json"]
215
-
216
- HANDOFF --> HF_TYPE{"handoff 类型?"}
217
- HF_TYPE -- "publish_handoff" --> NEW1["mode=new(固定)<br/>解析 body_path / title / account<br/>创建新任务"]
218
- HF_TYPE -- "workflow_handoff" --> HF_MODE{"handoff.mode?"}
219
-
220
- HF_MODE -- "new 新建" --> NEW2["解析 content_form / body_path<br/>或 materials_path / policies<br/>创建新任务"]
221
- HF_MODE -- "resume 续接" --> FIND["findTask<br/>按 state_path 或 run_id 查找已有任务"]
222
- FIND --> COMPARE{"核心输入是否变化?<br/>正文 / 标题 / 路由 / intent_text<br/>/ explicit_constraints"}
223
- COMPARE -- "有变化" --> RESET["resetDerivedState()<br/>全部阶段重置为 pending<br/>保留 run_id 与 workspace_root"]
224
- COMPARE -- "无变化" --> POLICY{"仅 review_policy 变化<br/>且为 trust_user?"}
225
- POLICY -- "是 + 有 handoff.body_path" --> AUTOPASS["自动设置<br/>content_review=passed<br/>跳过审核步骤"]
226
- POLICY -- "否" --> KEEP["保持当前状态不变"]
227
-
228
- NEW1 --> STATUS["status 重新计算 → next_action"]
229
- NEW2 --> STATUS
230
- RESET --> STATUS
231
- AUTOPASS --> STATUS
232
- KEEP --> STATUS
233
- INIT --> STATUS
234
-
235
- style HANDOFF fill:#e0e7ff,stroke:#6366f1,color:#3730a3
236
- style RESET fill:#fee2e2,stroke:#ef4444,color:#991b1b
237
- style AUTOPASS fill:#d1fae5,stroke:#10b981,color:#065f46
238
- style STATUS fill:#fef3c7,stroke:#f59e0b,color:#92400e
68
+ ```bash
69
+ zzp --help
70
+ zzhub-pipeline --help
239
71
  ```
240
72
 
241
- **handoff JSON 格式示例**:
242
-
243
- ```json
244
- // workflow_handoff — 新建任务
245
- {
246
- "workflow_handoff": {
247
- "mode": "new",
248
- "content_form": "article",
249
- "body_path": "/abs/path/body.md",
250
- "target_account": "default",
251
- "title": "文章标题",
252
- "user_intent_text": "发公众号文章给大号",
253
- "review_policy": "required"
254
- }
255
- }
256
-
257
- // workflow_handoff — 续接已有任务
258
- {
259
- "workflow_handoff": {
260
- "mode": "resume",
261
- "state_path": "/abs/path/workflow-state.json",
262
- "body_path": "/abs/path/revised-body.md",
263
- "review_policy": "trust_user"
264
- }
265
- }
73
+ 检查本机依赖:
266
74
 
267
- // publish_handoff — 简化格式(固定新建)
268
- {
269
- "publish_handoff": {
270
- "content_form": "article",
271
- "body_path": "/abs/path/body.md",
272
- "target_account": "default",
273
- "title": "文章标题",
274
- "user_intent_text": "发公众号"
275
- }
276
- }
75
+ ```bash
76
+ zzp doctor
277
77
  ```
278
78
 
279
- `resetDerivedState()` 会重置的内容:
79
+ 图片渲染需要 `@napi-rs/canvas`,COS 上传需要 `cos-nodejs-sdk-v5`。云端 Docker 镜像会安装 Chromium 和 CJK 字体。
280
80
 
281
- - 全部阶段状态(prepare / render / publish)→ pending
282
- - `asset_path` → 清空,`formatted_body_path` → null
283
- - `metadata`(保留 title)→ 默认值
284
- - `artifacts` / `images` / `publish` / `content_review` → 默认值
285
- - 保留:`run_id`、`workspace_root`、`state_path`、`intent.content_form`
81
+ ### 先预览一篇文章
286
82
 
287
- ### 三阶段流水线
83
+ 下面的命令只生成本地产物,不访问公众号:
288
84
 
289
- 三个活跃阶段和两个终态:
290
-
291
- ```
292
- prepare → render → publish → done
293
- ↘ failed
85
+ ```bash
86
+ zzp wechat-export \
87
+ --markdown ./article.md \
88
+ --out ./article.html \
89
+ --preview-shell-out ./article-preview.html \
90
+ --no-preview
294
91
  ```
295
92
 
296
- **prepare 阶段子步骤**(在 `prepare` 命令内部按序执行):
297
-
298
- 1. `channel-route` — 解析路由与公众号账号
299
- 2. `author-select` — 确定改写权限与风格模式(`rewrite_allowed` / `style_mode`)
300
- 3. `format` — 文本排版规则处理
301
- 4. `asset-meta` — 提取标题/日期/摘要
302
- 5. `body-inputs` — 扫描正文中的插图标记
303
- 6. `highlight-words` — 从标题提取封面高亮词(`prepare-finalize` 中完成)
304
- 7. `asset-save` — 写入正式文件到 `posts/` 目录(`prepare-finalize` 中完成)
305
-
306
- > `style` 润色不在 `prepare` 命令内 —— 它需要 LLM 调用,由外部 Worker 完成后再交回 pipeline。
307
-
308
- **render 阶段子步骤**:
309
-
310
- 1. `image-plan` — 模板选择(wechat-cover-split / poster-3-4 / longform-3-4)
311
- 2. `body-inputs` — 收集插图图片(longform 模式下可能暂停等待用户输入)
312
- 3. `adapter-render` — 调用图片渲染插件
313
- 4. 记录 `render_assets`,递增 `render_version`
314
-
315
- **publish 阶段子步骤**:
316
-
317
- 1. 收集发布路由(primary + extras)
318
- 2. 幂等性检查(content_version + render_version 匹配则跳过)
319
- 3. `provider.publish()` — 微信公众号 / COS / 博客
320
- 4. 记录发布结果,全部成功则 mode=done
93
+ ### 视觉排版工作台 (WeChat Visual Studio)
321
94
 
322
- ### 状态文件生命周期
95
+ Pipeline 提供了开箱即用的所见即所得排版工作台:
323
96
 
324
- ```
325
- 临时区(准备阶段) 正式区(prepare-finalize 之后)
326
- {workspace}/.zzhub-media/ {workspace}/posts/{date-slug}/
327
- runs/{run_id}.json ←───────────────── workflow-state.json
328
- tmp/{run_id}/source-body.md ──────→ post.md
329
- images/wechat/ ← 文章渲染图
330
- images/newspic/ ← 贴图渲染图
97
+ ```bash
98
+ zzp wechat-preview studio
331
99
  ```
332
100
 
333
- `prepare-finalize` 命令负责将临时状态固化到正式目录,此后所有命令自动解析 `posts/{date-slug}/workflow-state.json` 作为权威状态源。
101
+ 终端启动后会自动唤起浏览器打开 `http://127.0.0.1:18765/studio`。你可以在界面中直观调整主题配色、二级标题与表格语法预设、提示卡片(Callout)自定义 Emoji,并一键复制到微信后台或保存为全局配置。
334
102
 
335
- ## 目录结构
103
+ > **云端使用者注意**:本地 Studio 保存的样式会写入本地 `config.json`。若使用私有云端 Worker,需将调整后的配置同步至服务器,详见 [Visual Studio 排版调优与云端配置同步指南](docs/visual-studio-and-config-sync.md)。
336
104
 
337
- ```text
338
- src/
339
- cli.ts # 入口,命令分发
340
- plugins.ts # 命令注册表,workflow 和 ops 两组
341
- state.ts # WorkflowState 类型 + CRUD,权威合约
342
- task-manager.ts # 任务列举,查找,状态报告
343
- task-views.ts # markdown / agent 视图渲染
344
- workflow-materials.ts # 正文路径解析,素材对账
345
- routes.ts # 确定性路由表,关键词匹配 + 账号解析
346
- profiles.ts # 创作规则,改写许可,风格模式决策树
347
- text.ts # 文本格式化工具,frontmatter,markdown 规范化
348
- output.ts # TTY 感知输出层,pretty / JSON
349
- config.ts # 配置加载,env 覆盖,工作区路径解析
350
- args.ts # 参数解析
351
- spawn.ts # 子进程封装,PATH 增强,bun 二进制定位
352
- adapter-types.ts # 插件接口定义(ImageRenderPlugin / MarkdownRenderPlugin)
353
- adapter-loader.ts # 插件加载器,resolveImageRenderer / resolveMarkdownRenderer
354
- runtime-paths.ts # 资产路径解析(dev/compiled/npm 三种模式),字体缓存
355
- adapters/ # 内置适配器实现
356
- builtin-image-renderer.ts # 包装 imgx 的图片渲染适配器
357
- builtin-markdown-renderer.ts # 包装 wechat-preview 的 Markdown 渲染适配器
358
- commands/ # 每个 CLI 命令一个文件
359
- imgx/ # 图片渲染子系统,Chrome headless + @napi-rs/canvas
360
- runtime.ts # Chrome 截图,DOM dump,模板工具
361
- render-article.ts # longform-3-4 长文渲染器
362
- render-card.ts # 封面卡片渲染器
363
- render-ascii-portrait.ts # ASCII 人像渲染
364
- render-x-like-posts.ts # X / Twitter 风格帖子渲染
365
- poster-recipe.ts # 海报配方系统
366
- geometry.ts # 页面几何计算
367
- longform-theme.ts # 长文主题定义
368
- pretext-adapter.ts # pretext 分页适配器
369
- pretext-runtime.ts # 进程内分页运行时(@napi-rs/canvas 懒加载)
370
- providers/ # 发布提供者
371
- index.ts # 提供者注册表
372
- wechat.ts # 微信公众号,文章草稿和图片消息
373
- cos.ts # 腾讯云 COS 图片 CDN
374
- blog.ts # 博客 markdown 同步
375
- wechat-preview/ # 微信文章 HTML 预览,Milkdown + 主题
376
- index.ts
377
- themes.ts
378
- wechat-renderer.ts # 元素 renderer 注册表、CSS 内联和微信安全清理
379
- frontmatter-handler.ts
380
- browser/
381
- editor-export.ts
382
- editor-export.css
383
- assets/
384
- templates/
385
- fonts/
386
- browser-dist/
387
- ```
105
+ ### 推进一个发布任务
388
106
 
389
- ## 命令参考
390
-
391
- `workflow` 组,17 个命令:
392
-
393
- | 命令 | 说明 |
394
- | --- | --- |
395
- | `init` | Create run state from intent classification |
396
- | `ingest-handoff` | Create or resume a task from a publish_handoff/workflow_handoff JSON file |
397
- | `attach-body` | Attach a source body file to a managed task |
398
- | `attach-body-images` | Attach body image marker files to a managed task |
399
- | `attach-newspic-spec` | Attach or update newspic render intent |
400
- | `prepare` | Route + author + format + metadata |
401
- | `prepare-finalize` | Highlight words + asset save |
402
- | `render` | Image plan + imgx render |
403
- | `publish` | Execute publish routes |
404
- | `reconcile` | Reconcile managed task materials and derived state |
405
- | `checkpoint` | Read task state and validate current phase |
406
- | `status` | Read a managed task with gaps and next action |
407
- | `find-run` | Find the best matching managed task |
408
- | `tasks` | List managed tasks in the workspace |
409
- | `reset` | Reset phases for revision |
410
- | `review` | Update content review status |
411
- | `abandon` | Mark one or more tasks as abandoned |
412
-
413
- `ops` 组,11 个命令:
414
-
415
- | 命令 | 说明 |
416
- | --- | --- |
417
- | `sync-blog` | Copy post.md to blog repo, publish, and record result in state JSON |
418
- | `republish` | Publish a completed task to an additional account or platform |
419
- | `imgx` | Run bundled imgx renderer subcommands |
420
- | `wechat-export` | Render markdown to WeChat HTML with bundled preview styles |
421
- | `wechat-preview` | Local singleton server to preview exported WeChat HTML |
422
- | `cos-upload` | Upload a local image to configured COS CDN |
423
- | `config` | Read or update pipeline config |
424
- | `doctor` | Inspect resolved paths and provider health |
425
- | `hermes-metrics` | Show Hermes execution metrics per task |
426
- | `wx-drafts` | List or get drafts from WeChat draft box |
427
- | `wx-draft-delete` | Delete a draft from WeChat draft box |
428
-
429
- ## 常用命令
430
-
431
- ### 任务管理
107
+ Pipeline 每次只推进一个动作。创建任务后,反复执行 `status`,按照返回的 `next_action` 继续;不要直接修改 `workflow-state.json`。
432
108
 
433
109
  ```bash
434
- bun run src/cli.ts tasks --workspace {workspace}
435
- bun run src/cli.ts tasks --workspace {workspace} --active
436
- bun run src/cli.ts tasks --workspace {workspace} --active --view markdown
437
- bun run src/cli.ts find-run --workspace {workspace} --active
438
- bun run src/cli.ts find-run --workspace {workspace} --active --view agent
439
- bun run src/cli.ts status --workspace {workspace}
440
- bun run src/cli.ts status --state {workspace}/posts/{date-slug}/workflow-state.json
441
- bun run src/cli.ts status --state {workspace}/posts/{date-slug}/workflow-state.json --view agent
442
- bun run src/cli.ts reconcile --state {workspace}/posts/{date-slug}/workflow-state.json
443
- ```
444
-
445
- ### 新建与接入素材
110
+ WORKSPACE="$PWD/workspace"
446
111
 
447
- ```bash
448
- bun run src/cli.ts init \
449
- --workspace {workspace} \
112
+ zzp init \
113
+ --workspace "$WORKSPACE" \
450
114
  --task-kind publish \
451
115
  --content-form article \
452
- --targets wechat \
453
116
  --content-origin user \
454
- --intent-text "发公众号文章给大号" \
455
- --requires-render \
456
- --requires-publish
117
+ --targets wechat \
118
+ --intent-text "发一篇公众号文章"
457
119
 
458
- bun run src/cli.ts attach-body \
459
- --state {workspace}/.zzhub-media/runs/{run_id}.json \
460
- --body-text "正文内容"
120
+ # init 会返回 state_path;后续命令使用它
121
+ zzp status --state "$WORKSPACE/.zzhub-media/runs/<run_id>.json" --view agent
461
122
 
462
- bun run src/cli.ts attach-body-images \
463
- --state {workspace}/.zzhub-media/runs/{run_id}.json \
464
- --images-file {workspace}/images.json
123
+ # 正文可以来自文件,也可以使用 --body-text
124
+ zzp attach-body \
125
+ --state "$WORKSPACE/.zzhub-media/runs/<run_id>.json" \
126
+ --body ./article.md
465
127
 
466
- bun run src/cli.ts attach-newspic-spec \
467
- --state {workspace}/.zzhub-media/runs/{run_id}.json \
468
- --file {workspace}/newspic-spec.json
128
+ # 后续按 status 的 next_action 执行,常见顺序如下
129
+ zzp prepare --state <state_path>
130
+ zzp review --state <state_path> --status passed
131
+ zzp prepare-finalize --state <state_path>
132
+ zzp render --state <state_path>
133
+ zzp publish --state <state_path>
469
134
  ```
470
135
 
471
- `images.json` 支持两种形式:
136
+ 实际顺序以 `status --view agent` 为准。需要发布贴图时,把 `--content-form` 改为 `newspic`;多页图片可以在渲染前附加 `newspic` 规格。
472
137
 
473
- ```json
474
- [
475
- { "marker": "插图1", "path": "{workspace}/1.png" },
476
- { "marker": "插图2", "path": "{workspace}/2.png" }
477
- ]
478
- ```
138
+ 如果正文由 Agent 生成或审核,可以直接使用仓库里的 [`zzhub-publish` Skill](skills/zzhub-publish/SKILL.md)。Skill 只负责编排 Agent,文章正文仍由调用方提供。
479
139
 
480
- 或:
140
+ ## 云端 Worker
481
141
 
482
- ```json
483
- {
484
- "插图1": "{workspace}/1.png",
485
- "插图2": "{workspace}/2.png"
486
- }
487
- ```
142
+ Worker 是仓库里的独立 Bun 服务,入口为 `services/cloud-worker/server.ts`。它在服务器上调用 Pipeline CLI,当前支持公众号 `article` 和 `newspic` 任务,不支持博客任务,也不提供任意命令执行。
488
143
 
489
- ### 推进工作流
144
+ 推荐链路:
490
145
 
491
- ```bash
492
- bun run src/cli.ts prepare --state {workspace}/.zzhub-media/runs/{run_id}.json
493
- bun run src/cli.ts review --state {workspace}/.zzhub-media/runs/{run_id}.json --status passed
494
- bun run src/cli.ts prepare-finalize --state {workspace}/.zzhub-media/runs/{run_id}.json
495
- bun run src/cli.ts render --state {workspace}/posts/{date-slug}/workflow-state.json
496
- bun run src/cli.ts publish --state {workspace}/posts/{date-slug}/workflow-state.json
146
+ ```text
147
+ Web / App / 桌面端
148
+ ↓
149
+ 已鉴权的业务后端
150
+ ↓ 内部网络 + PIPELINE_WORKER_TOKEN
151
+ Cloud Worker
152
+ ↓
153
+ Pipeline CLI → 微信中转 API → 草稿箱
497
154
  ```
498
155
 
499
- ### 外部交接
500
-
501
- ```bash
502
- bun run src/cli.ts ingest-handoff --file handoff.json
503
- ```
156
+ Worker 已经可以部署,但应作为**内部服务**使用:
504
157
 
505
- `handoff.json` 支持 `publish_handoff`(简化,固定新建)和 `workflow_handoff`(支持 new/resume 模式,可控制 research/authoring/review 策略)两种格式,详见上方"任务入口:init 与 ingest-handoff"。
158
+ - 默认监听 `127.0.0.1:18887`。
159
+ - 非回环监听必须显式设置 `PIPELINE_WORKER_PRIVATE_NETWORK=1`,并由容器网络或防火墙完成隔离。
160
+ - `PIPELINE_WORKER_TOKEN` 是服务令牌,不是用户登录 token,也不是微信中转 PAT;不要下发给客户端。
161
+ - 当前是单进程串行队列,使用 JSON 文件保存任务、正文和账号;数据目录必须挂载持久卷,不支持多个副本共享同一目录。
162
+ - 任务状态为 `queued`、`running`、`succeeded`、`failed` 或 `interrupted`,`step` 表示当前 Pipeline 动作。
163
+ - 当前 Worker 只提供步骤和结果轮询,不提供云端 SSE、逐文件进度或原始日志。本机 `zzp monitor` 是另一套观测接口。
506
164
 
507
- ### Reset 回退
165
+ 最短 Docker 启动示例:
508
166
 
509
167
  ```bash
510
- bun run src/cli.ts reset --state {state_path} --mode content # 完整重新准备(回到 prepare 起点)
511
- bun run src/cli.ts reset --state {state_path} --mode redo.style # 仅重做风格润色
512
- bun run src/cli.ts reset --state {state_path} --mode redo.format # 仅重做排版
513
- bun run src/cli.ts reset --state {state_path} --mode redo.metadata # 仅重做元数据提取
514
- bun run src/cli.ts reset --state {state_path} --mode redo.route # 仅重做路由解析
515
- bun run src/cli.ts reset --state {state_path} --mode render # 重做渲染
516
- bun run src/cli.ts reset --state {state_path} --mode publish # 重做发布
517
- bun run src/cli.ts reset --state {state_path} --mode full # 完全放弃当前任务(mode=failed)
518
- ```
168
+ docker build -f services/cloud-worker/Dockerfile \
169
+ -t zzhub-pipeline-worker:local .
519
170
 
520
- 每种模式均会设置 `redo_hint`,确保 Agent 恢复时知道从哪个子步骤开始。
171
+ # 让业务后端和 Worker 只在内部网络通信
172
+ docker network create pipeline-internal
521
173
 
522
- ### Blog 同步
523
-
524
- ```bash
525
- bun run src/cli.ts sync-blog --state {workspace}/posts/{date-slug}/workflow-state.json
174
+ docker run -d --name pipeline-worker --restart unless-stopped \
175
+ --network pipeline-internal \
176
+ -e PIPELINE_WORKER_TOKEN \
177
+ -e PIPELINE_WORKER_HOST=0.0.0.0 \
178
+ -e PIPELINE_WORKER_PRIVATE_NETWORK=1 \
179
+ -e PIPELINE_CONFIG_FILE=/run/secrets/pipeline.json \
180
+ --mount type=bind,src=/absolute/path/to/pipeline.json,dst=/run/secrets/pipeline.json,readonly \
181
+ --mount type=bind,src=/absolute/path/to/worker-data,dst=/data \
182
+ zzhub-pipeline-worker:local
526
183
  ```
527
184
 
528
- 这条命令会:
529
-
530
- - 读取 canonical `post.md`
531
- - 复制到博客仓库 `content/posts/<slug>.md`
532
- - 执行博客发布命令
533
- - 将 blog publish result 写回 `workflow-state.json`,方便筛查是否遗漏同步
534
-
535
- ### 配置管理
536
-
537
- ```bash
538
- bun run src/cli.ts config
539
- bun run src/cli.ts config --key paths.workspaceRoot
540
- bun run src/cli.ts config --key paths.workspaceRoot --value /abs/path
541
- ```
185
+ 业务后端使用同一个服务令牌调用:
542
186
 
543
- ### 诊断
187
+ | 方法 | 路径 | 用途 |
188
+ | --- | --- | --- |
189
+ | `GET` | `/health` | 查看 Worker 是否存活、排队数和账号数 |
190
+ | `PUT` | `/v1/accounts/:account` | 写入或替换公众号凭据 |
191
+ | `DELETE` | `/v1/accounts/:account` | 删除 Worker 中的公众号凭据 |
192
+ | `POST` | `/v1/jobs` | 提交完整正文,返回 job ID |
193
+ | `GET` | `/v1/jobs/:id` | 查询步骤、状态和发布结果 |
544
194
 
545
- ```bash
546
- bun run src/cli.ts doctor
547
- bun run src/cli.ts hermes-metrics --workspace /abs/workspace
548
- ```
195
+ 正文必须在 Worker 环境中可读取。客户端本机路径不会自动上传;正文中的图片应使用 Worker 可访问的 URL,或使用请求允许的 Base64 数据。相同 `idempotencyKey` 且输入不变时会返回已有任务,输入变化要使用新的键。
549
196
 
550
- ### 微信 HTML 预览导出
197
+ 详细的环境变量、请求字段、Docker 网络、更新和故障恢复流程见 [`services/cloud-worker/README.md`](services/cloud-worker/README.md)。
551
198
 
552
- ```bash
553
- bun run src/cli.ts wechat-export \
554
- --markdown /abs/path/body.md \
555
- --out /abs/path/article.html \
556
- --account default \
557
- --preview-shell-out /abs/path/article-preview.html
558
- ```
199
+ ### 云端恢复
559
200
 
560
- `--preview-shell-out` 生成最终发布 HTML 的精确预览:预览页直接嵌入 `--out`
561
- 中的内联样式片段,不再加载 Milkdown 编辑器或复制浏览器 bundle。
201
+ `WX_RESULT_UNKNOWN`、超时或 `interrupted` 不代表草稿一定没有创建。先检查原 Pipeline run 和公众号草稿箱;确认已有草稿后,优先使用 `existingDraftMediaId` 更新。不要为了盲目重试而更换幂等键,否则可能创建重复草稿。
562
202
 
563
- 默认会将导出结果登记到本地 **单例预览服务**(`http://127.0.0.1:18765`),可在浏览器
564
- 侧栏切换多篇产物,无需发到微信草稿箱。服务未启动时会自动拉起;失败导出也会登记并展示
565
- `error_kind` / timeout / Chrome stderr 等调试信息。
203
+ ## 配置微信和外部服务
566
204
 
567
- ```bash
568
- # 显式启动预览服务(全局只允许一个实例;重复启动会复用)
569
- bun run src/cli.ts wechat-preview serve --open
570
-
571
- # 导出并打开该条目
572
- bun run src/cli.ts wechat-export \
573
- --markdown /abs/path/body.md \
574
- --out /tmp/article.html \
575
- --account default \
576
- --open
577
-
578
- # 调试中间产物
579
- bun run src/cli.ts wechat-export \
580
- --markdown /abs/path/body.md \
581
- --out /tmp/article.html \
582
- --debug-dir /tmp/wx-debug \
583
- --timeout-ms 20000
584
-
585
- # 关闭预览登记
586
- bun run src/cli.ts wechat-export ... --no-preview
587
- ```
205
+ Pipeline 不直接调用 `api.weixin.qq.com`。公众号发布需要一个兼容以下路径的微信中转 API:
588
206
 
589
- | 子命令 | 说明 |
590
- | --- | --- |
591
- | `wechat-preview serve` | 启动/复用单例服务 |
592
- | `wechat-preview status` | 是否在运行 |
593
- | `wechat-preview open` | 打开 dashboard |
594
- | `wechat-preview list` | 列出条目 |
595
- | `wechat-preview clear` | 清空条目 |
207
+ - `/api/v1/wx/cgi-bin/token`
208
+ - `/api/v1/wx/cgi-bin/material/add_material`
209
+ - `/api/v1/wx/cgi-bin/draft/add`
210
+ - `/api/v1/wx/cgi-bin/draft/update`
211
+ - `/api/v1/wx/cgi-bin/draft/batchget`
212
+ - `/api/v1/wx/cgi-bin/draft/get`
213
+ - `/api/v1/wx/cgi-bin/draft/delete`
596
214
 
597
- 环境变量:`ZZHUB_WECHAT_PREVIEW_PORT`(默认 18765)、`ZZHUB_WECHAT_PREVIEW_HOST`、
598
- `ZZHUB_WECHAT_PREVIEW_DIR`(锁与条目落盘目录)。发布草稿时默认也会登记一条
599
- `[publish] ...` 预览;设 `ZZHUB_WECHAT_PREVIEW_ON_PUBLISH=0` 可关闭。
215
+ 本仓库不包含中转服务。`wx.baseUrl` 应填写中转服务地址,账号 PAT、公众号 AppID 和 AppSecret 由使用者自行保管。COS 上传使用单独的 STS API;只做本地排版时无需配置微信或 COS。
600
216
 
601
- 源码(CSS/browser 入口)新于 dist 时会自动尝试 rebuild;失败则回退已有 bundle 并标记
602
- `bundle_stale`。手动重建:`bun run build:wechat-preview`。
217
+ 本机默认配置文件:
603
218
 
604
- #### 自定义 CSS
219
+ - macOS:`~/Library/Application Support/zzhub-pipeline/config.json`
220
+ - Linux:`~/.config/zzhub-pipeline/config.json`
221
+ - Windows:`%APPDATA%/zzhub-pipeline/config.json`
605
222
 
606
- 可以通过账号配置或 CLI 覆盖内置样式:
223
+ 最小配置示例:
607
224
 
608
225
  ```json
609
226
  {
227
+ "paths": {
228
+ "workspaceRoot": "/absolute/path/to/workspace"
229
+ },
610
230
  "wx": {
231
+ "baseUrl": "https://wx-api.example.com",
232
+ "defaultAccount": "main",
611
233
  "accounts": {
612
- "default": {
613
- "customCss": "./themes/default-wechat.css"
234
+ "main": {
235
+ "name": "我的公众号",
236
+ "appId": "YOUR_WECHAT_APP_ID",
237
+ "appSecret": "YOUR_WECHAT_APP_SECRET",
238
+ "pat": "YOUR_RELAY_API_PAT"
614
239
  }
615
240
  }
616
241
  }
617
242
  }
618
243
  ```
619
244
 
620
- ```bash
621
- bun run src/cli.ts wechat-export \
622
- --markdown /abs/path/body.md \
623
- --out /abs/path/article.html \
624
- --custom-css ./local-wechat.css
625
- ```
245
+ 配置文件含凭据,请放在受控目录,不要提交到 Git。也可以使用 `ZZHUB_PIPELINE_CONFIG` 指定其他配置路径;`ZZHUB_WX_BASE_URL`、`WX_APPID`、`WX_APPSECRET` 和 `ZZCLUB_PAT` 可覆盖对应配置。
626
246
 
627
- 样式优先级固定为:内置排版 CSS → 账号主题令牌 → 账号 `customCss`。传入
628
- `--custom-css` 时,它会替代账号 `customCss`。CLI 相对路径按当前工作目录解析,
629
- 配置中的相对路径按 `config.json` 所在目录解析。
630
-
631
- 推荐使用语义标签或稳定节点钩子:
632
-
633
- ```css
634
- .milkdown .editor p { line-height: 1.9; }
635
- .milkdown .editor h2 { color: #a94473; }
636
- .milkdown .editor [data-wechat-node="inline-code"] { background: #f7eef2; }
637
- .milkdown .editor [data-wechat-node="image-caption"] { font-size: 13px; }
638
- ```
247
+ ## 状态、监控与恢复
639
248
 
640
- `.milkdown .editor` 只是 CSS 内联阶段的兼容包装;最终 HTML 会移除 `class` 和
641
- `data-wechat-node`。外部 CSS 支持变量、层叠和常规选择器,随后由 Juice 解析为
642
- 内联样式。最终片段只保留微信稳定标签、图片/链接/表格必要属性和保守 CSS;
643
- 脚本、事件属性、外部 `<style>`、未解析变量、`flex`/`grid`、定位和伪元素都会被移除。
249
+ `workflow-state.json` 是单个任务的业务真相源,记录路由、素材、阶段、渲染版本和发布结果。正文文件保存在工作区,不直接写入状态 JSON。
644
250
 
645
- ### COS 图片上传
251
+ 本机监控是可选的只读服务:
646
252
 
647
253
  ```bash
648
- bun run src/cli.ts cos-upload --file /abs/path/image.png --folder notes/note-id --alt image
254
+ zzp monitor start
255
+ zzp monitor status
256
+ zzp monitor stop
649
257
  ```
650
258
 
651
- ### 草稿箱管理
259
+ 它提供 HTTP 快照和 SSE,适合桌面端或本机 GUI 观察多个 CLI 执行。它不负责调度、重试或取消,也不应暴露到公网。详见 [`docs/monitor.md`](docs/monitor.md)。
652
260
 
653
- ```bash
654
- # 列出草稿(账号不传时使用 wx.defaultAccount)
655
- bun run src/cli.ts wx-drafts --limit 10
656
-
657
- # 获取某篇草稿的完整内容(含 HTML)
658
- bun run src/cli.ts wx-drafts --media-id MEDIA_ID
659
-
660
- # 删除一篇草稿
661
- bun run src/cli.ts wx-draft-delete --media-id MEDIA_ID
662
- ```
261
+ ## 其他文档
663
262
 
664
- `--account` 参数可选,默认使用配置中的 `wx.defaultAccount`。
263
+ - [Visual Studio 排版调优与云端配置同步指南](docs/visual-studio-and-config-sync.md)
264
+ - [文章发布入门](docs/cli-intro-for-article.md):面向第一次接入的使用说明
265
+ - [云端 Worker API、部署与更新](services/cloud-worker/README.md)
266
+ - [封面主题和图片排版](docs/render-themes.md)
267
+ - [离线正文模板](ARTICLE-THEMES.md)
268
+ - [Agent 发布 Skill](skills/zzhub-publish/SKILL.md)
269
+ - [第三方声明](THIRD-PARTY-NOTICES.md)
665
270
 
666
- ### init 可选参数
271
+ ## 开发与验证
667
272
 
668
273
  ```bash
669
- # 更新已有草稿而非新建(配合 --existing-draft-media-id)
670
- bun run src/cli.ts init ... --existing-draft-media-id MEDIA_ID
671
-
672
- # 关联 Nezus note 用于发布结果回调
673
- bun run src/cli.ts init ... --note-id NOTE_ID
674
- ```
675
-
676
- ## 状态文件
677
-
678
- 最重要的字段是:
679
-
680
- | 字段 | 作用 |
681
- | --- | --- |
682
- | `run_id` | 任务唯一标识 |
683
- | `created_at` / `updated_at` | 任务时间信息 |
684
- | `phase.current` | 当前阶段 |
685
- | `intent` | 上游分类结果与发布要求 |
686
- | `route` | 当前微信路由和账号 |
687
- | `metadata` | 标题、slug、日期、摘要 |
688
- | `images.body_inputs` | 正文插图缺口与已接入图片 |
689
- | `images.render_assets` | 已渲染出的封面与分页图 |
690
- | `publish.results` | 发布结果 |
691
-
692
- 正文全文不进 state。state 只保存恢复流程需要的事实、路径、版本和结果,正文内容放在 `{workspace}/.zzhub-media/tmp/{run_id}/`。
693
-
694
- ## 配置系统
695
-
696
- 配置文件位置:
697
-
698
- - macOS, `~/Library/Application Support/zzhub-pipeline/config.json`
699
- - Linux, `~/.config/zzhub-pipeline/config.json`
700
-
701
- 可以用环境变量覆盖配置文件路径:
702
-
703
- - `ZZHUB_PIPELINE_CONFIG=/abs/path/config.json`
704
-
705
- 其他环境变量覆盖:
706
-
707
- - `ZZHUB_PIPELINE_WORKSPACE_ROOT`,workspace root path
708
- - `ZZHUB_PIPELINE_POSTS_DIR`,posts directory name,默认 `posts`
709
- - `ZZHUB_PIPELINE_BLOG_ROOT`,blog repository root
710
-
711
- 兼容旧配置时,会自动读取旧版 CLI 的配置。
712
-
713
- 配置结构概览:
714
-
715
- - `paths`
716
- - `services`
717
- - `commands`
718
- - `wx`,accounts
719
- - `cos`
720
- - `plugins`,渲染插件覆盖
721
-
722
- ### 插件配置
723
-
724
- 通过 `config.plugins` 可以替换内置渲染实现:
725
-
726
- ```json
727
- {
728
- "plugins": {
729
- "imageRenderer": "./my-image-renderer.js",
730
- "markdownRenderer": "./my-md-renderer.js"
731
- }
732
- }
733
- ```
734
-
735
- 不设置时使用内置适配器(`builtin-imgx` 和 `builtin-wechat-preview`)。
736
-
737
- 运行 `zzp doctor` 可以检查所有渲染依赖的状态(Chrome、@napi-rs/canvas、字体等)。
738
-
739
- ## 输出系统
740
-
741
- - 非 TTY,或者重定向和管道场景,固定输出原始 JSON
742
- - TTY 场景输出带 ANSI 颜色的 pretty 结果
743
- - `FORCE_COLOR=1` 可以在非 TTY 下强制 pretty 输出
744
- - `NO_COLOR=1` 可以在 TTY 下强制原始 JSON
745
- - `--view` 支持 `json`,`markdown`,`agent`,`agent-json`
746
- - `--view agent` 更适合 orchestrator 直接读取下一步
747
-
748
- ## imgx 渲染子系统
749
-
750
- 封面支持 `render.cover` 配置、十套主题、背景图与透明边框,以及供 App 使用的 JSON Schema 和预览输出。详见 [封面主题与图片排版](docs/render-themes.md),可直接导入 [配置示例](examples/cover-themes.json)。
751
-
752
- - 基于 Chrome headless 和 `@napi-rs/canvas`
753
- - 模板包括 `longform-3-4`(newspic 长文)、`poster-3-4`(单页卡片)和 `wechat-cover-split`(文章封面)
754
- - 主题包括 `paper-sage`(默认账号)和 `linen-news`(`ancientone` 账号)
755
- - 几何参数会从 header,footer,padding 自动推导,也可以通过 CLI 旗标显式控制
756
- - 分页在进程内通过 `@chenglou/pretext` 完成,不依赖 Chrome dump
757
- - `render` 和 `wechat-export` 都需要 Chrome
758
-
759
- ### newspic 分页规格
760
-
761
- `newspic` 的渲染通过 `pagination_mode` 控制分页行为:
762
-
763
- | 模式 | 行为 |
764
- | --- | --- |
765
- | `single` | 单页卡片(poster-3-4) |
766
- | `multi` | 多页图集(longform-3-4) |
767
-
768
- `newspic-spec.json` 常用字段:
769
-
770
- ```json
771
- {
772
- "pagination_mode": "multi",
773
- "min_pages": 3,
774
- "max_pages": 0,
775
- "require_image_every_page": true,
776
- "default_image_layout": "editorial",
777
- "target_fill_ratio": 0.8,
778
- "page_specs": [
779
- {
780
- "page": 1,
781
- "image_markers": ["插图1", "插图2"],
782
- "image_layout": "staggered",
783
- "target_fill_ratio": 0.85
784
- },
785
- {
786
- "page": 2,
787
- "image_markers": ["插图3"]
788
- }
789
- ]
790
- }
791
- ```
792
-
793
- - `target_fill_ratio` 是可选字段,默认 `0.8`,实际值会被规范化到 `0.35` 到 `0.95`
794
- - `page_specs[].target_fill_ratio` 优先级高于顶层
795
- - 长文分页测量优先在进程内跑 `pretext`,不再依赖 Chrome dump
796
-
797
- **页标记**:如果外部工具需要把某段文字固定到某一页,除了传 `page_specs`,还应该在正文里加页标记:
798
-
799
- ```text
800
- 【第一页】
801
- 第一页正文
802
-
803
- 【第二页】
804
- 第二页正文
274
+ bun install
275
+ bun test
276
+ bun x tsc --noEmit
277
+ bun run build:wechat-preview
278
+ bun run build
805
279
  ```
806
280
 
807
- 也支持 `【Page 1】` / `【Page 2】` 等英文页标记。
808
-
809
- 不加页标记时走自动流排;加了页标记并且存在 `page_specs` 时切换到 spec 驱动分页,页标记变成硬边界。
810
-
811
- `longform-3-4` 的几何参数包括:
812
-
813
- - `--page-width` / `--page-height`
814
- - `--body-padding-x` / `--body-padding-y`
815
- - `--logo-size` / `--logo-gap`
816
- - `--footer-height` / `--footer-margin-top`
817
- - `--content-width` / `--content-height`
818
- - `--content-bottom-gap`
819
-
820
- ## wechat-preview 子系统
821
-
822
- - 基于 Milkdown core + CommonMark/GFM 生成语义 HTML,不读取 Crepe/CodeMirror 可视 DOM
823
- - 固定管线:Markdown → Milkdown HTML → 元素 renderer prepare → Juice 内联 → renderer finalize → 微信白名单清理
824
- - 元素 renderer 覆盖段落/标题、强调、行内代码、代码块、引用、图片标题、链接脚注、列表、表格、分隔线和页脚
825
- - 内置默认采用“静默编辑”:深色标题、低装饰引用和中性行内代码,品牌色只用于链接等识别点
826
- - `default` 与 `ancientone` 共用排版骨架,只通过主题令牌切换品牌色、字体栈和页脚
827
- - 构建命令:`bun run build:wechat-preview`
828
- - `wechat-export` 和微信草稿发布使用同一份最终内联 HTML
829
-
830
- `@zzclub/milkdown-article-style`(`article.css`)会通过 Vite 以 raw CSS 打入浏览器
831
- bundle。修改该 CSS 或浏览器 renderer 后必须执行:
281
+ 只修改 Worker 时可以先运行:
832
282
 
833
283
  ```bash
834
- bun run build:wechat-preview
835
- bun install --global .
284
+ bun test services/cloud-worker/server.test.ts
836
285
  ```
837
286
 
838
- 全局 `zzp` 使用编译产物;只修改源码而不重新全局安装不会更新全局命令。
839
-
840
- ## 插件系统
841
-
842
- 渲染通过 adapter 接口实现可插拔:
843
-
844
- - `ImageRenderPlugin` — 替换 imgx 图片渲染
845
- - `MarkdownRenderPlugin` — 替换 wechat-preview HTML 导出
846
-
847
- 接口定义在 `src/adapter-types.ts`,加载逻辑在 `src/adapter-loader.ts`。
848
-
849
- 内置适配器:
850
- - `src/adapters/builtin-image-renderer.ts` — 包装 imgx
851
- - `src/adapters/builtin-markdown-renderer.ts` — 包装 wechat-preview
852
-
853
- 每个适配器可实现 `doctor()` 方法,报告运行时依赖状态。
854
-
855
- 运行时依赖检查:
856
- - `@napi-rs/canvas` — 图片渲染需要,懒加载,缺失时提示安装命令
857
- - Chrome — HTML 导出需要,缺失时提示安装命令
858
- - CJK 字体 — 随 CLI 附带,生成图片时按需读取本地文件,不触发网络下载;封面也支持本机字体名称
287
+ `bun run build:npm` 会生成独立的 npm bundle;`services/cloud-worker/` 不在 npm 包的 `files` 清单中,Worker 需要从源码仓库构建。
859
288
 
860
- ## 发布提供者
861
-
862
- | 提供者 | 路由 | 说明 |
863
- | --- | --- | --- |
864
- | wechat | `wechat-article` | 创建公众号文章草稿 |
865
- | wechat | `wechat-newspic` | 发送图片消息 |
866
- | cos | - | 腾讯云 COS 图片 CDN |
867
- | blog | - | Markdown 同步到博客仓库 |
868
-
869
- Markdown → WeChat HTML 转换通过插件系统完成(默认使用内置 `builtin-wechat-preview` 适配器)。
870
-
871
- ## 本机多任务监控
872
-
873
- `zzp monitor start` 按需启动共享 HTTP/SSE 服务,`monitor status` 查看状态,`monitor stop` 只停止监控。
874
- CLI 独立记录执行和错误,GUI 可同时订阅多个任务。新版业务失败会退出 1,调用方仍应读取失败响应和最新任务状态。
875
-
876
- 详见 [监控接口与退出码迁移](docs/monitor.md)。
877
-
878
- ## 测试与验证
879
-
880
- ```bash
881
- bun test
882
- bun x tsc --noEmit
883
- ```
289
+ ## 许可
884
290
 
885
- ## 代码阅读顺序
886
-
887
- 1. `src/cli.ts`
888
- 2. `src/plugins.ts`
889
- 3. `src/state.ts`
890
- 4. `src/task-manager.ts`
891
- 5. `src/routes.ts`
892
- 6. `src/profiles.ts`
893
- 7. `src/workflow-materials.ts`
894
- 8. `src/commands/prepare.ts`
895
- 9. `src/commands/prepare-finalize.ts`
896
- 10. `src/commands/render.ts`
897
- 11. `src/commands/publish.ts`
291
+ Pipeline 使用 [MIT License](LICENSE)。排版基础和第三方依赖的授权说明见 [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md);外部中转服务、COS 服务和第三方模板的费用、权限及许可由各自提供方负责。