@zzclub/pipeline 0.1.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.
- package/README.md +559 -0
- package/dist/assets/imgx/assets/browser/obstacle-flow.d.ts +27 -0
- package/dist/assets/imgx/assets/browser/obstacle-flow.js +326 -0
- package/dist/assets/imgx/assets/icons/avatar_jinx_cartoon.jpg +0 -0
- package/dist/assets/imgx/assets/icons/clover.svg +15 -0
- package/dist/assets/imgx/assets/icons/fishbone-logo-square.png +0 -0
- package/dist/assets/imgx/assets/icons/fishbone-logo.jpg +0 -0
- package/dist/assets/imgx/assets/icons/fishbone-logo.png +0 -0
- package/dist/assets/imgx/assets/icons/openclaw-logo.svg +22 -0
- package/dist/assets/imgx/assets/icons/zzclub-logo-black.jpg +0 -0
- package/dist/assets/imgx/assets/icons/zzclub-logo-gray.svg +25 -0
- package/dist/assets/imgx/assets/templates/ascii-portrait-3-4.html +342 -0
- package/dist/assets/imgx/assets/templates/ascii-portrait-tile.html +144 -0
- package/dist/assets/imgx/assets/templates/longform-3-4.html +179 -0
- package/dist/assets/imgx/assets/templates/poster-3-4.html +359 -0
- package/dist/assets/imgx/assets/templates/tips-3-4.html +300 -0
- package/dist/assets/imgx/assets/templates/wechat-cover-split.html +307 -0
- package/dist/assets/imgx/assets/templates/x-like-posts.html +246 -0
- package/dist/assets/wechat-preview/assets/browser-dist/.vite/manifest.json +314 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/SweiCurveLegCJKsc-Regular-B3Ut5bwH.woff +0 -0
- package/dist/assets/wechat-preview/assets/browser-dist/assets/style-BYNXnUvc.css +1 -0
- package/dist/assets/wechat-preview/assets/browser-dist/editor-export.js +969 -0
- package/dist/assets/wechat-preview/assets/templates/export-shell.html +38 -0
- package/dist/cli.js +404 -0
- package/dist/node_modules/@chenglou/pretext/dist/analysis.d.ts +33 -0
- package/dist/node_modules/@chenglou/pretext/dist/analysis.js +1063 -0
- package/dist/node_modules/@chenglou/pretext/dist/bidi.d.ts +1 -0
- package/dist/node_modules/@chenglou/pretext/dist/bidi.js +175 -0
- package/dist/node_modules/@chenglou/pretext/dist/generated/bidi-data.d.ts +4 -0
- package/dist/node_modules/@chenglou/pretext/dist/generated/bidi-data.js +979 -0
- package/dist/node_modules/@chenglou/pretext/dist/layout.d.ts +70 -0
- package/dist/node_modules/@chenglou/pretext/dist/layout.js +496 -0
- package/dist/node_modules/@chenglou/pretext/dist/line-break.d.ts +36 -0
- package/dist/node_modules/@chenglou/pretext/dist/line-break.js +820 -0
- package/dist/node_modules/@chenglou/pretext/dist/measurement.d.ts +28 -0
- package/dist/node_modules/@chenglou/pretext/dist/measurement.js +219 -0
- package/dist/node_modules/@chenglou/pretext/dist/rich-inline.d.ts +51 -0
- package/dist/node_modules/@chenglou/pretext/dist/rich-inline.js +401 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
# zzhub-pipeline
|
|
2
|
+
|
|
3
|
+
面向 Agent 的内容发布状态机,将文案、图片和发布意图收敛为可恢复的工作流,推进到微信公众号文章或图片消息。
|
|
4
|
+
|
|
5
|
+
## 核心原则
|
|
6
|
+
|
|
7
|
+
- `state` 是唯一真相源
|
|
8
|
+
- Agent 先查任务状态,再决定下一步
|
|
9
|
+
- 外部工具先把素材写入 pipeline,再由 pipeline 计算缺口
|
|
10
|
+
- 中断后按实际状态恢复,而不是靠会话历史猜测
|
|
11
|
+
|
|
12
|
+
## 快速开始
|
|
13
|
+
|
|
14
|
+
前置条件:
|
|
15
|
+
|
|
16
|
+
- Bun 运行时
|
|
17
|
+
- Chrome,用于 `render` 和 `wechat-export`
|
|
18
|
+
|
|
19
|
+
安装依赖:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
bun install
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
二进制别名:
|
|
26
|
+
|
|
27
|
+
- `zzhub-pipeline`
|
|
28
|
+
- `zzp`
|
|
29
|
+
|
|
30
|
+
配置文件位置,macOS 默认是 `~/Library/Application Support/zzhub-pipeline/config.json`。
|
|
31
|
+
|
|
32
|
+
## 架构概览
|
|
33
|
+
|
|
34
|
+
这是一个状态机模型,输入是任务意图、正文、图片、路由和发布要求,输出是可恢复的工作流状态、渲染产物和发布结果。
|
|
35
|
+
|
|
36
|
+
当前只有两条主工作流路由:
|
|
37
|
+
|
|
38
|
+
- `wechat-article`
|
|
39
|
+
- `wechat-newspic`
|
|
40
|
+
|
|
41
|
+
`blog` 只做事后同步,不参与主工作流分支。
|
|
42
|
+
|
|
43
|
+
状态文件分两类:
|
|
44
|
+
|
|
45
|
+
- 运行态,临时文件,`{workspace}/.zzhub-media/runs/{run_id}.json`
|
|
46
|
+
- 定稿态,最终文件,`{workspace}/posts/{date-slug}/workflow-state.json`
|
|
47
|
+
|
|
48
|
+
正文全文不会进入 state,正文内容统一放在 `{workspace}/.zzhub-media/tmp/{run_id}/`。
|
|
49
|
+
|
|
50
|
+
## 目录结构
|
|
51
|
+
|
|
52
|
+
```text
|
|
53
|
+
src/
|
|
54
|
+
cli.ts # 入口,命令分发
|
|
55
|
+
plugins.ts # 命令注册表,workflow 和 ops 两组
|
|
56
|
+
state.ts # WorkflowState 类型 + CRUD,权威合约
|
|
57
|
+
task-manager.ts # 任务列举,查找,状态报告
|
|
58
|
+
task-views.ts # markdown / agent 视图渲染
|
|
59
|
+
workflow-materials.ts # 正文路径解析,素材对账
|
|
60
|
+
routes.ts # 确定性路由表,关键词匹配 + 账号解析
|
|
61
|
+
profiles.ts # 创作规则,改写许可,风格模式决策树
|
|
62
|
+
text.ts # 文本格式化工具,frontmatter,markdown 规范化
|
|
63
|
+
output.ts # TTY 感知输出层,pretty / JSON
|
|
64
|
+
config.ts # 配置加载,env 覆盖,工作区路径解析
|
|
65
|
+
args.ts # 参数解析
|
|
66
|
+
spawn.ts # 子进程封装,PATH 增强,bun 二进制定位
|
|
67
|
+
adapter-types.ts # 插件接口定义(ImageRenderPlugin / MarkdownRenderPlugin)
|
|
68
|
+
adapter-loader.ts # 插件加载器,resolveImageRenderer / resolveMarkdownRenderer
|
|
69
|
+
runtime-paths.ts # 资产路径解析(dev/compiled/npm 三种模式),字体缓存
|
|
70
|
+
adapters/ # 内置适配器实现
|
|
71
|
+
builtin-image-renderer.ts # 包装 imgx 的图片渲染适配器
|
|
72
|
+
builtin-markdown-renderer.ts # 包装 wechat-preview 的 Markdown 渲染适配器
|
|
73
|
+
commands/ # 每个 CLI 命令一个文件
|
|
74
|
+
imgx/ # 图片渲染子系统,Chrome headless + @napi-rs/canvas
|
|
75
|
+
runtime.ts # Chrome 截图,DOM dump,模板工具
|
|
76
|
+
render-article.ts # longform-3-4 长文渲染器
|
|
77
|
+
render-card.ts # 封面卡片渲染器
|
|
78
|
+
render-ascii-portrait.ts # ASCII 人像渲染
|
|
79
|
+
render-x-like-posts.ts # X / Twitter 风格帖子渲染
|
|
80
|
+
poster-recipe.ts # 海报配方系统
|
|
81
|
+
geometry.ts # 页面几何计算
|
|
82
|
+
longform-theme.ts # 长文主题定义
|
|
83
|
+
pretext-adapter.ts # pretext 分页适配器
|
|
84
|
+
pretext-runtime.ts # 进程内分页运行时(@napi-rs/canvas 懒加载)
|
|
85
|
+
providers/ # 发布提供者
|
|
86
|
+
index.ts # 提供者注册表
|
|
87
|
+
wechat.ts # 微信公众号,文章草稿和图片消息
|
|
88
|
+
cos.ts # 腾讯云 COS 图片 CDN
|
|
89
|
+
blog.ts # 博客 markdown 同步
|
|
90
|
+
wechat-preview/ # 微信文章 HTML 预览,Milkdown + 主题
|
|
91
|
+
index.ts
|
|
92
|
+
themes.ts
|
|
93
|
+
wechat-formatter.ts
|
|
94
|
+
frontmatter-handler.ts
|
|
95
|
+
browser/
|
|
96
|
+
editor-export.ts
|
|
97
|
+
editor-export.css
|
|
98
|
+
assets/
|
|
99
|
+
templates/
|
|
100
|
+
fonts/
|
|
101
|
+
browser-dist/
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## 命令参考
|
|
105
|
+
|
|
106
|
+
`workflow` 组,16 个命令:
|
|
107
|
+
|
|
108
|
+
| 命令 | 说明 |
|
|
109
|
+
| --- | --- |
|
|
110
|
+
| `init` | Create run state from intent classification |
|
|
111
|
+
| `attach-body` | Attach a source body file to a managed task |
|
|
112
|
+
| `attach-body-images` | Attach body image marker files to a managed task |
|
|
113
|
+
| `attach-newspic-spec` | Attach or update newspic render intent |
|
|
114
|
+
| `prepare` | Route + author + format + metadata |
|
|
115
|
+
| `prepare-finalize` | Highlight words + asset save |
|
|
116
|
+
| `render` | Image plan + imgx render |
|
|
117
|
+
| `publish` | Execute publish routes |
|
|
118
|
+
| `reconcile` | Reconcile managed task materials and derived state |
|
|
119
|
+
| `checkpoint` | Read task state and validate current phase |
|
|
120
|
+
| `status` | Read a managed task with gaps and next action |
|
|
121
|
+
| `find-run` | Find the best matching managed task |
|
|
122
|
+
| `tasks` | List managed tasks in the workspace |
|
|
123
|
+
| `reset` | Reset phases for revision |
|
|
124
|
+
| `review` | Update content review status |
|
|
125
|
+
| `abandon` | Mark one or more tasks as abandoned |
|
|
126
|
+
|
|
127
|
+
`ops` 组,7 个命令:
|
|
128
|
+
|
|
129
|
+
| 命令 | 说明 |
|
|
130
|
+
| --- | --- |
|
|
131
|
+
| `sync-blog` | Copy canonical markdown to the blog repo and publish there |
|
|
132
|
+
| `imgx` | Run bundled imgx renderer subcommands |
|
|
133
|
+
| `wechat-export` | Render markdown to WeChat HTML with bundled preview styles |
|
|
134
|
+
| `cos-upload` | Upload a local image to configured COS CDN |
|
|
135
|
+
| `config` | Read or update pipeline config |
|
|
136
|
+
| `doctor` | Inspect resolved paths and provider health |
|
|
137
|
+
| `hermes-metrics` | Show Hermes execution metrics per task |
|
|
138
|
+
|
|
139
|
+
## 核心流程
|
|
140
|
+
|
|
141
|
+
```mermaid
|
|
142
|
+
flowchart TD
|
|
143
|
+
A["find-run / tasks / status"] --> B{"任务是否已存在?"}
|
|
144
|
+
B -- "否" --> C["init"]
|
|
145
|
+
B -- "是" --> D["读取当前 state"]
|
|
146
|
+
C --> D
|
|
147
|
+
D --> E{"缺什么?"}
|
|
148
|
+
E -- "缺正文" --> F["attach-body"]
|
|
149
|
+
E -- "缺正文图片" --> G["attach-body-images"]
|
|
150
|
+
E -- "缺 newspic 规格" --> H["attach-newspic-spec"]
|
|
151
|
+
E -- "缺 prepare 数据" --> I["prepare"]
|
|
152
|
+
E -- "缺 review 结论" --> J["review"]
|
|
153
|
+
E -- "缺 canonical 产物" --> K["prepare-finalize"]
|
|
154
|
+
E -- "缺渲染" --> L["render"]
|
|
155
|
+
E -- "缺发布" --> M["publish"]
|
|
156
|
+
F --> N["reconcile / status"]
|
|
157
|
+
G --> N
|
|
158
|
+
H --> N
|
|
159
|
+
I --> N
|
|
160
|
+
J --> N
|
|
161
|
+
K --> N
|
|
162
|
+
L --> N
|
|
163
|
+
M --> N
|
|
164
|
+
N --> E
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## 常用命令
|
|
168
|
+
|
|
169
|
+
### 任务管理
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
bun run src/cli.ts tasks --workspace {workspace}
|
|
173
|
+
bun run src/cli.ts tasks --workspace {workspace} --active
|
|
174
|
+
bun run src/cli.ts tasks --workspace {workspace} --active --view markdown
|
|
175
|
+
bun run src/cli.ts find-run --workspace {workspace} --active
|
|
176
|
+
bun run src/cli.ts find-run --workspace {workspace} --active --view agent
|
|
177
|
+
bun run src/cli.ts status --workspace {workspace}
|
|
178
|
+
bun run src/cli.ts status --state {workspace}/posts/{date-slug}/workflow-state.json
|
|
179
|
+
bun run src/cli.ts status --state {workspace}/posts/{date-slug}/workflow-state.json --view agent
|
|
180
|
+
bun run src/cli.ts reconcile --state {workspace}/posts/{date-slug}/workflow-state.json
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### 新建与接入素材
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
bun run src/cli.ts init \
|
|
187
|
+
--workspace {workspace} \
|
|
188
|
+
--task-kind publish \
|
|
189
|
+
--content-form article \
|
|
190
|
+
--targets wechat \
|
|
191
|
+
--content-origin user \
|
|
192
|
+
--intent-text "发公众号文章给大号" \
|
|
193
|
+
--requires-render \
|
|
194
|
+
--requires-publish
|
|
195
|
+
|
|
196
|
+
bun run src/cli.ts attach-body \
|
|
197
|
+
--state {workspace}/.zzhub-media/runs/{run_id}.json \
|
|
198
|
+
--body-text "正文内容"
|
|
199
|
+
|
|
200
|
+
bun run src/cli.ts attach-body-images \
|
|
201
|
+
--state {workspace}/.zzhub-media/runs/{run_id}.json \
|
|
202
|
+
--images-file {workspace}/images.json
|
|
203
|
+
|
|
204
|
+
bun run src/cli.ts attach-newspic-spec \
|
|
205
|
+
--state {workspace}/.zzhub-media/runs/{run_id}.json \
|
|
206
|
+
--file {workspace}/newspic-spec.json
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
`images.json` 支持两种形式:
|
|
210
|
+
|
|
211
|
+
```json
|
|
212
|
+
[
|
|
213
|
+
{ "marker": "插图1", "path": "{workspace}/1.png" },
|
|
214
|
+
{ "marker": "插图2", "path": "{workspace}/2.png" }
|
|
215
|
+
]
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
或:
|
|
219
|
+
|
|
220
|
+
```json
|
|
221
|
+
{
|
|
222
|
+
"插图1": "{workspace}/1.png",
|
|
223
|
+
"插图2": "{workspace}/2.png"
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
`newspic-spec.json` 常用字段:
|
|
228
|
+
|
|
229
|
+
```json
|
|
230
|
+
{
|
|
231
|
+
"pagination_mode": "multi",
|
|
232
|
+
"min_pages": 3,
|
|
233
|
+
"max_pages": 0,
|
|
234
|
+
"require_image_every_page": true,
|
|
235
|
+
"default_image_layout": "editorial",
|
|
236
|
+
"target_fill_ratio": 0.8,
|
|
237
|
+
"page_specs": [
|
|
238
|
+
{
|
|
239
|
+
"page": 1,
|
|
240
|
+
"image_markers": ["插图1", "插图2"],
|
|
241
|
+
"image_layout": "staggered",
|
|
242
|
+
"target_fill_ratio": 0.85
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"page": 2,
|
|
246
|
+
"image_markers": ["插图3"]
|
|
247
|
+
}
|
|
248
|
+
]
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
说明:
|
|
253
|
+
|
|
254
|
+
- `target_fill_ratio` 是可选字段,默认 `0.8`
|
|
255
|
+
- `page_specs[].target_fill_ratio` 优先级高于顶层
|
|
256
|
+
- imgx 会把它当作这一页文字和图片尽量占到内容区多少的近似目标
|
|
257
|
+
- 实际值会被规范化到 `0.35` 到 `0.95`
|
|
258
|
+
|
|
259
|
+
如果外部工具需要某段文字固定落在某一页,除了传 `page_specs`,还应该在正文里加页标记:
|
|
260
|
+
|
|
261
|
+
```text
|
|
262
|
+
【第一页】
|
|
263
|
+
第一页正文
|
|
264
|
+
|
|
265
|
+
【第二页】
|
|
266
|
+
第二页正文
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
也支持英文页标记:
|
|
270
|
+
|
|
271
|
+
```text
|
|
272
|
+
【Page 1】
|
|
273
|
+
...
|
|
274
|
+
【Page 2】
|
|
275
|
+
...
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
兼容性说明:
|
|
279
|
+
|
|
280
|
+
- 不加页标记,继续走自动流排,按正文 block 顺序自动分配到多页
|
|
281
|
+
- 加了页标记并且存在 `page_specs`,会切换到 spec 驱动分页,页标记变成硬边界
|
|
282
|
+
- 旧调用方可以继续工作,只有需要固定某段文字属于某一页时,才需要补正文页标记或新增 `target_fill_ratio`
|
|
283
|
+
|
|
284
|
+
`longform-3-4` 的几何约定:
|
|
285
|
+
|
|
286
|
+
- 长文分页测量优先在进程内跑 `pretext`,不再依赖 Chrome dump
|
|
287
|
+
- 内容区不再假定固定值,默认由页面尺寸、header、footer、padding 推导
|
|
288
|
+
- 调用方需要显式指定内容区或页面几何时,可以传这些参数:
|
|
289
|
+
- `--page-width` / `--page-height`
|
|
290
|
+
- `--body-padding-x` / `--body-padding-y`
|
|
291
|
+
- `--logo-size` / `--logo-gap`
|
|
292
|
+
- `--footer-height` / `--footer-margin-top`
|
|
293
|
+
- `--content-width` / `--content-height`
|
|
294
|
+
- `--content-bottom-gap`
|
|
295
|
+
|
|
296
|
+
如果没有显式传 `--content-width` 和 `--content-height`,imgx 会按当前页面几何自动推导内容区。
|
|
297
|
+
|
|
298
|
+
### 推进工作流
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
bun run src/cli.ts prepare --state {workspace}/.zzhub-media/runs/{run_id}.json --body {workspace}/body.md
|
|
302
|
+
bun run src/cli.ts review --state {workspace}/.zzhub-media/runs/{run_id}.json --status passed
|
|
303
|
+
bun run src/cli.ts prepare-finalize --state {workspace}/.zzhub-media/runs/{run_id}.json --body {workspace}/body.md
|
|
304
|
+
bun run src/cli.ts render --state {workspace}/posts/{date-slug}/workflow-state.json
|
|
305
|
+
bun run src/cli.ts publish --state {workspace}/posts/{date-slug}/workflow-state.json
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Blog 同步
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
bun run src/cli.ts sync-blog --state {workspace}/posts/{date-slug}/workflow-state.json
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
这条命令会:
|
|
315
|
+
|
|
316
|
+
- 读取 canonical `post.md`
|
|
317
|
+
- 复制到博客仓库 `content/posts/<slug>.md`
|
|
318
|
+
- 执行博客发布命令
|
|
319
|
+
|
|
320
|
+
### 配置管理
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
bun run src/cli.ts config
|
|
324
|
+
bun run src/cli.ts config --key paths.workspaceRoot
|
|
325
|
+
bun run src/cli.ts config --key paths.workspaceRoot --value /abs/path
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### 诊断
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
bun run src/cli.ts doctor
|
|
332
|
+
bun run src/cli.ts hermes-metrics --workspace /abs/workspace
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### 微信 HTML 预览导出
|
|
336
|
+
|
|
337
|
+
```bash
|
|
338
|
+
bun run src/cli.ts wechat-export --body /abs/path/body.md --account default
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### COS 图片上传
|
|
342
|
+
|
|
343
|
+
```bash
|
|
344
|
+
bun run src/cli.ts cos-upload --file /abs/path/image.png --folder notes/note-id --alt image
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## 状态文件
|
|
348
|
+
|
|
349
|
+
最重要的字段是:
|
|
350
|
+
|
|
351
|
+
| 字段 | 作用 |
|
|
352
|
+
| --- | --- |
|
|
353
|
+
| `run_id` | 任务唯一标识 |
|
|
354
|
+
| `created_at` / `updated_at` | 任务时间信息 |
|
|
355
|
+
| `phase.current` | 当前阶段 |
|
|
356
|
+
| `intent` | 上游分类结果与发布要求 |
|
|
357
|
+
| `route` | 当前微信路由和账号 |
|
|
358
|
+
| `metadata` | 标题、slug、日期、摘要 |
|
|
359
|
+
| `images.body_inputs` | 正文插图缺口与已接入图片 |
|
|
360
|
+
| `images.render_assets` | 已渲染出的封面与分页图 |
|
|
361
|
+
| `publish.results` | 发布结果 |
|
|
362
|
+
|
|
363
|
+
正文全文不进 state。state 只保存恢复流程需要的事实、路径、版本和结果,正文内容放在 `{workspace}/.zzhub-media/tmp/{run_id}/`。
|
|
364
|
+
|
|
365
|
+
## 配置系统
|
|
366
|
+
|
|
367
|
+
配置文件位置:
|
|
368
|
+
|
|
369
|
+
- macOS, `~/Library/Application Support/zzhub-pipeline/config.json`
|
|
370
|
+
- Linux, `~/.config/zzhub-pipeline/config.json`
|
|
371
|
+
|
|
372
|
+
可以用环境变量覆盖配置文件路径:
|
|
373
|
+
|
|
374
|
+
- `ZZHUB_PIPELINE_CONFIG=/abs/path/config.json`
|
|
375
|
+
|
|
376
|
+
其他环境变量覆盖:
|
|
377
|
+
|
|
378
|
+
- `ZZHUB_PIPELINE_WORKSPACE_ROOT`,workspace root path
|
|
379
|
+
- `ZZHUB_PIPELINE_POSTS_DIR`,posts directory name,默认 `posts`
|
|
380
|
+
- `ZZHUB_PIPELINE_BLOG_ROOT`,blog repository root
|
|
381
|
+
|
|
382
|
+
兼容旧配置时,会自动读取 `zzclub-z-cli` 的配置。
|
|
383
|
+
|
|
384
|
+
配置结构概览:
|
|
385
|
+
|
|
386
|
+
- `paths`
|
|
387
|
+
- `services`
|
|
388
|
+
- `commands`
|
|
389
|
+
- `wx`,accounts
|
|
390
|
+
- `cos`
|
|
391
|
+
- `plugins`,渲染插件覆盖
|
|
392
|
+
|
|
393
|
+
### 插件配置
|
|
394
|
+
|
|
395
|
+
通过 `config.plugins` 可以替换内置渲染实现:
|
|
396
|
+
|
|
397
|
+
```json
|
|
398
|
+
{
|
|
399
|
+
"plugins": {
|
|
400
|
+
"imageRenderer": "./my-image-renderer.js",
|
|
401
|
+
"markdownRenderer": "./my-md-renderer.js"
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
不设置时使用内置适配器(`builtin-imgx` 和 `builtin-wechat-preview`)。
|
|
407
|
+
|
|
408
|
+
运行 `zzp doctor` 可以检查所有渲染依赖的状态(Chrome、@napi-rs/canvas、字体等)。
|
|
409
|
+
|
|
410
|
+
## 输出系统
|
|
411
|
+
|
|
412
|
+
- 非 TTY,或者重定向和管道场景,固定输出原始 JSON
|
|
413
|
+
- TTY 场景输出带 ANSI 颜色的 pretty 结果
|
|
414
|
+
- `FORCE_COLOR=1` 可以在非 TTY 下强制 pretty 输出
|
|
415
|
+
- `NO_COLOR=1` 可以在 TTY 下强制原始 JSON
|
|
416
|
+
- `--view` 支持 `json`,`markdown`,`agent`
|
|
417
|
+
- `--view agent` 更适合 orchestrator 直接读取下一步
|
|
418
|
+
|
|
419
|
+
## imgx 渲染子系统
|
|
420
|
+
|
|
421
|
+
- 基于 Chrome headless 和 `@napi-rs/canvas`
|
|
422
|
+
- 模板包括 `longform-3-4`,用于 newspic 长文,和 `wechat-cover-split`,用于文章封面
|
|
423
|
+
- 主题包括 `paper-sage`,默认账号,以及 `linen-news`,`ancientone` 账号
|
|
424
|
+
- 几何参数会从 header,footer,padding 自动推导,也可以通过 CLI 旗标显式控制
|
|
425
|
+
- 分页在进程内通过 `@chenglou/pretext` 完成,不依赖 Chrome dump
|
|
426
|
+
- `render` 和 `wechat-export` 都需要 Chrome
|
|
427
|
+
|
|
428
|
+
## wechat-preview 子系统
|
|
429
|
+
|
|
430
|
+
- 基于 Milkdown 的 markdown 到微信 HTML 转换
|
|
431
|
+
- 支持多主题
|
|
432
|
+
- 构建命令:`bun run build:wechat-preview`
|
|
433
|
+
- `wechat-export` 会直接使用这套预览样式
|
|
434
|
+
|
|
435
|
+
## 插件系统
|
|
436
|
+
|
|
437
|
+
渲染通过 adapter 接口实现可插拔:
|
|
438
|
+
|
|
439
|
+
- `ImageRenderPlugin` — 替换 imgx 图片渲染
|
|
440
|
+
- `MarkdownRenderPlugin` — 替换 wechat-preview HTML 导出
|
|
441
|
+
|
|
442
|
+
接口定义在 `src/adapter-types.ts`,加载逻辑在 `src/adapter-loader.ts`。
|
|
443
|
+
|
|
444
|
+
内置适配器:
|
|
445
|
+
- `src/adapters/builtin-image-renderer.ts` — 包装 imgx
|
|
446
|
+
- `src/adapters/builtin-markdown-renderer.ts` — 包装 wechat-preview
|
|
447
|
+
|
|
448
|
+
每个适配器可实现 `doctor()` 方法,报告运行时依赖状态。
|
|
449
|
+
|
|
450
|
+
运行时依赖检查:
|
|
451
|
+
- `@napi-rs/canvas` — 图片渲染需要,懒加载,缺失时提示安装命令
|
|
452
|
+
- Chrome — HTML 导出需要,缺失时提示安装命令
|
|
453
|
+
- CJK 字体 — npm 模式下自动从 CDN 下载到 `~/.config/zzhub-pipeline/fonts/`
|
|
454
|
+
|
|
455
|
+
## 发布提供者
|
|
456
|
+
|
|
457
|
+
| 提供者 | 路由 | 说明 |
|
|
458
|
+
| --- | --- | --- |
|
|
459
|
+
| wechat | `wechat-article` | 创建公众号文章草稿 |
|
|
460
|
+
| wechat | `wechat-newspic` | 发送图片消息 |
|
|
461
|
+
| cos | - | 腾讯云 COS 图片 CDN |
|
|
462
|
+
| blog | - | Markdown 同步到博客仓库 |
|
|
463
|
+
|
|
464
|
+
Markdown → WeChat HTML 转换通过插件系统完成(默认使用内置 `builtin-wechat-preview` 适配器)。
|
|
465
|
+
|
|
466
|
+
## newspic 规格
|
|
467
|
+
|
|
468
|
+
`newspic` 的渲染规格支持两种分页模式:
|
|
469
|
+
|
|
470
|
+
- `auto`,自动流排
|
|
471
|
+
- `single`,单页
|
|
472
|
+
- `multi`,多页
|
|
473
|
+
|
|
474
|
+
常用 JSON 字段:
|
|
475
|
+
|
|
476
|
+
```json
|
|
477
|
+
{
|
|
478
|
+
"pagination_mode": "multi",
|
|
479
|
+
"min_pages": 3,
|
|
480
|
+
"max_pages": 0,
|
|
481
|
+
"require_image_every_page": true,
|
|
482
|
+
"default_image_layout": "editorial",
|
|
483
|
+
"target_fill_ratio": 0.8,
|
|
484
|
+
"page_specs": [
|
|
485
|
+
{
|
|
486
|
+
"page": 1,
|
|
487
|
+
"image_markers": ["插图1", "插图2"],
|
|
488
|
+
"image_layout": "staggered",
|
|
489
|
+
"target_fill_ratio": 0.85
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
"page": 2,
|
|
493
|
+
"image_markers": ["插图3"]
|
|
494
|
+
}
|
|
495
|
+
]
|
|
496
|
+
}
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
说明:
|
|
500
|
+
|
|
501
|
+
- `target_fill_ratio` 默认是 `0.8`
|
|
502
|
+
- `page_specs[].target_fill_ratio` 优先级高于顶层
|
|
503
|
+
- 这个值表示文字和图片尽量占到内容区多少,是一个近似目标
|
|
504
|
+
- 实际值会被规范化到 `0.35` 到 `0.95`
|
|
505
|
+
|
|
506
|
+
如果外部工具需要把某段文字固定到某一页,除了传 `page_specs`,还应该在正文里加页标记:
|
|
507
|
+
|
|
508
|
+
```text
|
|
509
|
+
【第一页】
|
|
510
|
+
第一页正文
|
|
511
|
+
|
|
512
|
+
【第二页】
|
|
513
|
+
第二页正文
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
也支持英文页标记:
|
|
517
|
+
|
|
518
|
+
```text
|
|
519
|
+
【Page 1】
|
|
520
|
+
...
|
|
521
|
+
【Page 2】
|
|
522
|
+
...
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
兼容性说明:
|
|
526
|
+
|
|
527
|
+
- 不加页标记时,仍然走自动流排,按正文 block 顺序分配到多页
|
|
528
|
+
- 加了页标记并且存在 `page_specs` 时,会切换到 spec 驱动分页,页标记变成硬边界
|
|
529
|
+
- 旧调用方可以继续工作,只有需要固定页面归属时才需要补页标记或调整 `target_fill_ratio`
|
|
530
|
+
|
|
531
|
+
`longform-3-4` 的几何参数包括:
|
|
532
|
+
|
|
533
|
+
- `--page-width` / `--page-height`
|
|
534
|
+
- `--body-padding-x` / `--body-padding-y`
|
|
535
|
+
- `--logo-size` / `--logo-gap`
|
|
536
|
+
- `--footer-height` / `--footer-margin-top`
|
|
537
|
+
- `--content-width` / `--content-height`
|
|
538
|
+
- `--content-bottom-gap`
|
|
539
|
+
|
|
540
|
+
## 测试与验证
|
|
541
|
+
|
|
542
|
+
```bash
|
|
543
|
+
bun test
|
|
544
|
+
bun x tsc --noEmit
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
## 代码阅读顺序
|
|
548
|
+
|
|
549
|
+
1. `src/cli.ts`
|
|
550
|
+
2. `src/plugins.ts`
|
|
551
|
+
3. `src/state.ts`
|
|
552
|
+
4. `src/task-manager.ts`
|
|
553
|
+
5. `src/routes.ts`
|
|
554
|
+
6. `src/profiles.ts`
|
|
555
|
+
7. `src/workflow-materials.ts`
|
|
556
|
+
8. `src/commands/prepare.ts`
|
|
557
|
+
9. `src/commands/prepare-finalize.ts`
|
|
558
|
+
10. `src/commands/render.ts`
|
|
559
|
+
11. `src/commands/publish.ts`
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type ObstacleFlowRuntime = {
|
|
2
|
+
layoutBlocks(
|
|
3
|
+
blocks: any[],
|
|
4
|
+
width: number,
|
|
5
|
+
height: number,
|
|
6
|
+
bodyImages: any[],
|
|
7
|
+
): void;
|
|
8
|
+
paginateBlocks(
|
|
9
|
+
blocks: any[],
|
|
10
|
+
width: number,
|
|
11
|
+
height: number,
|
|
12
|
+
bodyImages: any[],
|
|
13
|
+
options?: {
|
|
14
|
+
pageImageLimit?: number;
|
|
15
|
+
pageImageGroups?: any[][] | null;
|
|
16
|
+
},
|
|
17
|
+
): any[];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function createObstacleFlowRuntime(options: {
|
|
21
|
+
prepareWithSegments: (...args: any[]) => any;
|
|
22
|
+
layoutNextLineRange: (...args: any[]) => any;
|
|
23
|
+
obstacleGap: number;
|
|
24
|
+
minSlotWidth: number;
|
|
25
|
+
renderLine: (...args: any[]) => void;
|
|
26
|
+
renderImage: (...args: any[]) => void;
|
|
27
|
+
}): ObstacleFlowRuntime;
|