优化SKILL
parent
286e28b7f3
commit
68c9ed7bcb
|
|
@ -25,10 +25,14 @@ maomomo-article-writer/
|
||||||
├── README.md # 面向使用者的说明文档
|
├── README.md # 面向使用者的说明文档
|
||||||
├── agents/
|
├── agents/
|
||||||
│ └── openai.yaml # 技能展示名称、简介和默认提示语
|
│ └── openai.yaml # 技能展示名称、简介和默认提示语
|
||||||
|
├── scripts/
|
||||||
|
│ └── maomomo_image_gen.py # OpenAI 兼容 API/CLI 配图 fallback
|
||||||
└── references/
|
└── references/
|
||||||
├── source-and-fact-check.md # 来源读取、事实核查和风险口径
|
├── source-and-fact-check.md # 来源读取、事实核查和风险口径
|
||||||
├── style-guide.md # MAOMOMO 写作风格、模板和文章骨架
|
├── style-guide.md # MAOMOMO 写作风格、模板和文章骨架
|
||||||
├── visual-assets.md # 配图清单、视觉规则和图片 QA
|
├── visual-assets.md # 配图清单、视觉规则和图片 QA
|
||||||
|
├── recommended-styles.md # 推荐配图风格和提示词片段
|
||||||
|
├── image-generation.md # 图片生成脚本、manifest 和后端选择
|
||||||
└── delivery-rules.md # 完整交付、打包和最终检查
|
└── delivery-rules.md # 完整交付、打包和最终检查
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -122,7 +126,7 @@ maomomo-article-writer
|
||||||
2. 读取来源并列出已读 / 未读材料,整理事实表和待确认项。
|
2. 读取来源并列出已读 / 未读材料,整理事实表和待确认项。
|
||||||
3. 确认文章范围、大纲、标题方向、核心口径和图片规划。
|
3. 确认文章范围、大纲、标题方向、核心口径和图片规划。
|
||||||
4. 写作或优化发布用 Markdown。
|
4. 写作或优化发布用 Markdown。
|
||||||
5. 生成或准备独立 PNG 配图,插入 Markdown。
|
5. 确认配图风格,生成或准备独立 PNG 配图,插入 Markdown。
|
||||||
6. 检查来源、图片路径、风险提示、编辑痕迹和 ZIP 内容,最后交付文件路径。
|
6. 检查来源、图片路径、风险提示、编辑痕迹和 ZIP 内容,最后交付文件路径。
|
||||||
|
|
||||||
用户明确说“只要文字”“只要大纲”“不要图片”“不要打包”时,会按用户限制执行,不强行创建完整发布包。
|
用户明确说“只要文字”“只要大纲”“不要图片”“不要打包”时,会按用户限制执行,不强行创建完整发布包。
|
||||||
|
|
@ -147,6 +151,7 @@ maomomo-article-writer
|
||||||
- 语气像懂行朋友,不像官方宣传稿
|
- 语气像懂行朋友,不像官方宣传稿
|
||||||
- 涉及金融、券商、银行、数字货币和合规内容时,保留谨慎措辞
|
- 涉及金融、券商、银行、数字货币和合规内容时,保留谨慎措辞
|
||||||
- 配图统一使用白橘猫、暖橙色、干净金融科技教程风
|
- 配图统一使用白橘猫、暖橙色、干净金融科技教程风
|
||||||
|
- 如果用户没有指定配图风格,先在 `warm-fintech-guide`、`data-card-dashboard`、`clean-editorial`、`handdrawn-note`、`xiaohongshu-vertical` 中推荐 2-3 个方向。
|
||||||
|
|
||||||
文章不应当:
|
文章不应当:
|
||||||
|
|
||||||
|
|
@ -161,6 +166,8 @@ maomomo-article-writer
|
||||||
- 详细文章模板、标题套路和常用词写在 `references/style-guide.md`。
|
- 详细文章模板、标题套路和常用词写在 `references/style-guide.md`。
|
||||||
- 来源核查、时效性、冲突口径和风险表达写在 `references/source-and-fact-check.md`。
|
- 来源核查、时效性、冲突口径和风险表达写在 `references/source-and-fact-check.md`。
|
||||||
- 配图类型、猫咪视觉、文件命名和图片 QA 写在 `references/visual-assets.md`。
|
- 配图类型、猫咪视觉、文件命名和图片 QA 写在 `references/visual-assets.md`。
|
||||||
|
- 推荐配图风格、适用场景和提示词片段写在 `references/recommended-styles.md`。
|
||||||
|
- 图片 API/CLI fallback、批量 manifest 和脚本用法写在 `references/image-generation.md`,脚本为 `scripts/maomomo_image_gen.py`。
|
||||||
- 完整交付、ZIP、附件和最终检查写在 `references/delivery-rules.md`。
|
- 完整交付、ZIP、附件和最终检查写在 `references/delivery-rules.md`。
|
||||||
- 修改技能展示名称或默认提示语时,同步更新 `agents/openai.yaml`。
|
- 修改技能展示名称或默认提示语时,同步更新 `agents/openai.yaml`。
|
||||||
- 如果新增具体行业资料、平台规则或长期复用模板,优先放入 `references/`,避免让 `SKILL.md` 过长。
|
- 如果新增具体行业资料、平台规则或长期复用模板,优先放入 `references/`,避免让 `SKILL.md` 过长。
|
||||||
|
|
|
||||||
6
SKILL.md
6
SKILL.md
|
|
@ -70,8 +70,10 @@ description: 生成或优化符合 maomomo / 猫MOMO 风格的中文出海金融
|
||||||
|
|
||||||
5. 配图规划和生成。
|
5. 配图规划和生成。
|
||||||
- 需要配图时读 `references/visual-assets.md`。
|
- 需要配图时读 `references/visual-assets.md`。
|
||||||
- 先生成图片清单:类型、标题、放置位置、核心文案、文件名、alt text、是否依赖用户截图。
|
- 若用户没有指定视觉方向,读 `references/recommended-styles.md`,先提供 2-3 个 MAOMOMO 配图风格选项并推荐一个;确认后整篇文章保持同一视觉身份。
|
||||||
|
- 先生成图片清单:类型、标题、放置位置、核心文案、文件名、alt text、风格、是否依赖用户截图。
|
||||||
- 每张图独立生成或准备为高清 PNG,放入 `assets/`,用相对路径插入 Markdown。
|
- 每张图独立生成或准备为高清 PNG,放入 `assets/`,用相对路径插入 Markdown。
|
||||||
|
- 优先使用当前 agent 的内置图片生成工具;当用户要求 API/CLI、内置工具不可用或需要批量 manifest 时,读 `references/image-generation.md` 并使用 `scripts/maomomo_image_gen.py`。
|
||||||
- 没有真实截图时只能生成“示意图 / 信息图”,不能伪造官方 App 截图。
|
- 没有真实截图时只能生成“示意图 / 信息图”,不能伪造官方 App 截图。
|
||||||
|
|
||||||
6. 打包与最终 QA。
|
6. 打包与最终 QA。
|
||||||
|
|
@ -117,4 +119,6 @@ description: 生成或优化符合 maomomo / 猫MOMO 风格的中文出海金融
|
||||||
- `references/source-and-fact-check.md`:来源读取、事实表、时效性、冲突口径和金融风险核查。
|
- `references/source-and-fact-check.md`:来源读取、事实表、时效性、冲突口径和金融风险核查。
|
||||||
- `references/style-guide.md`:MAOMOMO 文章类型、标题套路、语气、结构模板、小红书模式和写作细节。
|
- `references/style-guide.md`:MAOMOMO 文章类型、标题套路、语气、结构模板、小红书模式和写作细节。
|
||||||
- `references/visual-assets.md`:配图类型、猫咪视觉、文件命名、Markdown 插图、禁止伪造截图和图片 QA。
|
- `references/visual-assets.md`:配图类型、猫咪视觉、文件命名、Markdown 插图、禁止伪造截图和图片 QA。
|
||||||
|
- `references/recommended-styles.md`:推荐配图风格、适用场景、推荐话术和提示词片段。
|
||||||
|
- `references/image-generation.md`:图片生成后端选择、`scripts/maomomo_image_gen.py` 用法、manifest 和故障处理。
|
||||||
- `references/delivery-rules.md`:完整交付、ZIP、更新打包、附件策略和最终检查清单。
|
- `references/delivery-rules.md`:完整交付、ZIP、更新打包、附件策略和最终检查清单。
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
interface:
|
interface:
|
||||||
display_name: "MAOMOMO 文章生成器"
|
display_name: "MAOMOMO 文章生成器"
|
||||||
short_description: "阶段化生成 MAOMOMO 文章、配图和 ZIP 发布包"
|
short_description: "阶段化生成 MAOMOMO 文章、多风格配图和 ZIP 发布包"
|
||||||
default_prompt: "使用 $maomomo-article-writer 生成或优化一篇 MAOMOMO 风格的中文出海金融实操文章。请先核查来源和口径,再交付 Markdown、assets 独立配图目录和 ZIP 包。"
|
default_prompt: "使用 $maomomo-article-writer 生成或优化一篇 MAOMOMO 风格的中文出海金融实操文章。请先核查来源和口径,再确认配图风格,最后交付 Markdown、assets 独立配图目录和 ZIP 包。"
|
||||||
|
|
||||||
policy:
|
policy:
|
||||||
allow_implicit_invocation: true
|
allow_implicit_invocation: true
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
# 图片生成脚本与后端选择
|
||||||
|
|
||||||
|
需要实际生成配图文件、使用 API/CLI fallback、批量生成 assets,或排查图片接口配置时,先读本文件。
|
||||||
|
|
||||||
|
## 后端选择
|
||||||
|
|
||||||
|
优先顺序:
|
||||||
|
|
||||||
|
1. Codex / 当前 agent 的内置图片生成工具。适合大多数配图任务。
|
||||||
|
2. `scripts/maomomo_image_gen.py` API/CLI fallback。适合用户明确要求 API/CLI、内置工具不可用、需要批量 manifest、或希望把生成过程记录到文件时。
|
||||||
|
|
||||||
|
不要因为脚本存在就强制使用脚本。若内置图片工具可用,仍优先使用内置工具生成独立 PNG,再保存到 `assets/`。
|
||||||
|
|
||||||
|
## 配置来源
|
||||||
|
|
||||||
|
脚本按以下顺序读取配置:
|
||||||
|
|
||||||
|
- `MAOMOMO_IMAGE_API_KEY` / `OPENAI_API_KEY`
|
||||||
|
- `MAOMOMO_IMAGE_BASE_URL` / `OPENAI_BASE_URL`
|
||||||
|
- `MAOMOMO_IMAGE_MODEL` / `CODEX_PPT_IMAGE_MODEL`
|
||||||
|
- `~/.codex/config.toml` 中的 `base_url`
|
||||||
|
- `~/.codex/auth.json` 中可识别的 API key
|
||||||
|
|
||||||
|
不要在日志、回答、Markdown 或 manifest 中写入完整 API key。
|
||||||
|
|
||||||
|
## 单张生成
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 scripts/maomomo_image_gen.py generate \
|
||||||
|
--out article/assets/hsbc-mastercard-alipay-campaign-01-cover.png \
|
||||||
|
--style warm-fintech-guide \
|
||||||
|
--title "汇丰 Mastercard 支付宝活动" \
|
||||||
|
--image-type "封面图" \
|
||||||
|
--core-text "先看报名、门槛和封顶,避免白刷"
|
||||||
|
```
|
||||||
|
|
||||||
|
先只看 prompt:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 scripts/maomomo_image_gen.py generate \
|
||||||
|
--out article/assets/hsbc-mastercard-alipay-campaign-01-cover.png \
|
||||||
|
--style data-card-dashboard \
|
||||||
|
--title "返现计算图" \
|
||||||
|
--image-type "计算图" \
|
||||||
|
--core-text "HK$190 ÷ 9% ≈ HK$2112,超过部分没有额外收益" \
|
||||||
|
--dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
## 批量 manifest
|
||||||
|
|
||||||
|
manifest 示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"file_name": "assets/hsbc-mastercard-alipay-campaign-01-cover.png",
|
||||||
|
"type": "封面图",
|
||||||
|
"title": "汇丰 Mastercard 支付宝活动",
|
||||||
|
"core_text": "先报名,再看门槛和封顶",
|
||||||
|
"alt_text": "MAOMOMO 汇丰 Mastercard 支付宝返现活动封面图",
|
||||||
|
"style": "warm-fintech-guide",
|
||||||
|
"aspect_ratio": "16:9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file_name": "assets/hsbc-mastercard-alipay-campaign-02-calculation.png",
|
||||||
|
"type": "计算图",
|
||||||
|
"title": "怎么刷到刚刚好",
|
||||||
|
"core_text": "HK$190 ÷ 9% ≈ HK$2112,超过部分没有额外收益",
|
||||||
|
"style": "data-card-dashboard"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
运行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 scripts/maomomo_image_gen.py batch \
|
||||||
|
--manifest article/image_manifest.json \
|
||||||
|
--base-dir article
|
||||||
|
```
|
||||||
|
|
||||||
|
`file_name` 必须与 Markdown 中的相对路径一致,且最终文件必须真实存在。
|
||||||
|
|
||||||
|
## 内置风格名
|
||||||
|
|
||||||
|
可用:
|
||||||
|
|
||||||
|
- `warm-fintech-guide`
|
||||||
|
- `clean-editorial`
|
||||||
|
- `data-card-dashboard`
|
||||||
|
- `handdrawn-note`
|
||||||
|
- `xiaohongshu-vertical`
|
||||||
|
|
||||||
|
查看完整描述:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 scripts/maomomo_image_gen.py styles
|
||||||
|
```
|
||||||
|
|
||||||
|
## 失败处理
|
||||||
|
|
||||||
|
- API key 缺失:提示用户配置环境变量或检查 Codex 配置,不要编造图片文件。
|
||||||
|
- 接口失败:保留已生成图片,说明失败项,必要时用 `--dry-run` 交付 prompt 清单作为临时排查材料。
|
||||||
|
- 图片文字乱码或金额错误:重新生成该单张图片;不要手工覆盖文字伪装成模型输出。
|
||||||
|
- 图片不符合风格:先更新 manifest 的 `style` / `core_text` / `extra`,再重跑对应项。
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
# MAOMOMO 推荐配图风格
|
||||||
|
|
||||||
|
需要选择、推荐、复用或调整配图风格时,读本文件。风格名要与 `scripts/maomomo_image_gen.py` 的 `--style` 保持一致。
|
||||||
|
|
||||||
|
## 推荐顺序
|
||||||
|
|
||||||
|
默认优先推荐 `warm-fintech-guide`。如果文章明显更偏数字、对比、避坑或小红书,再切换到更合适的风格。
|
||||||
|
|
||||||
|
| 风格名 | 优先使用场景 | 不适合 |
|
||||||
|
|---|---|---|
|
||||||
|
| `warm-fintech-guide` | 常规 MAOMOMO 文章、港卡、银行活动、返现攻略、开户教程 | 数字极密集的计算页、小红书竖版 |
|
||||||
|
| `data-card-dashboard` | 返现计算、费用测算、活动门槛、产品对比、时间线 | 情绪化避坑故事、截图驱动步骤 |
|
||||||
|
| `clean-editorial` | SEO 长文、总结盘点、品牌感封面、解释型文章 | 需要强步骤感或大量数字卡片的图 |
|
||||||
|
| `handdrawn-note` | 避坑经验、保号教程、操作步骤、实测提醒 | 严肃金融报告感、密集表格 |
|
||||||
|
| `xiaohongshu-vertical` | 小红书、4 图看完、手机端竖版内容 | 常规网站横版封面和正文配图 |
|
||||||
|
|
||||||
|
## warm-fintech-guide
|
||||||
|
|
||||||
|
推荐语:
|
||||||
|
|
||||||
|
```text
|
||||||
|
我建议用 warm-fintech-guide,因为它最贴近 MAOMOMO 默认视觉:白底、暖橙、猫咪向导和金融教程卡片,适合大多数港卡、银行活动和返现攻略。
|
||||||
|
```
|
||||||
|
|
||||||
|
视觉要点:
|
||||||
|
|
||||||
|
- 明亮白底,暖橙色强调。
|
||||||
|
- 白橘猫作为向导、提示牌或角落品牌元素。
|
||||||
|
- 信息卡片清晰,数字、日期和步骤不拥挤。
|
||||||
|
- 适合 16:9 横版封面、总览图、流程图。
|
||||||
|
|
||||||
|
提示词片段:
|
||||||
|
|
||||||
|
```text
|
||||||
|
MAOMOMO 暖橙金融教程风,明亮白底,暖橙强调色,干净卡片布局,友好的白橘猫作为向导,现代 App 教程质感,中文标题清楚,包含 MAOMOMO 标识。
|
||||||
|
```
|
||||||
|
|
||||||
|
## data-card-dashboard
|
||||||
|
|
||||||
|
推荐语:
|
||||||
|
|
||||||
|
```text
|
||||||
|
我建议用 data-card-dashboard,因为这篇重点是规则、门槛和金额,读者需要一眼看懂数字和限制。
|
||||||
|
```
|
||||||
|
|
||||||
|
视觉要点:
|
||||||
|
|
||||||
|
- 指标卡、对比表、时间线、计算公式。
|
||||||
|
- 数字层级强,适合“封顶”“门槛”“成本”“到账时间”。
|
||||||
|
- 猫咪比例较小,只做提示和品牌识别。
|
||||||
|
- 适合返现计算图、路线对比图、活动总览图。
|
||||||
|
|
||||||
|
提示词片段:
|
||||||
|
|
||||||
|
```text
|
||||||
|
MAOMOMO 数据卡片仪表盘风,白底,暖橙和蓝灰辅助色,使用指标卡、表格、时间线和计算公式展示关键信息,中文数字清晰可读,猫咪作为小型提示元素。
|
||||||
|
```
|
||||||
|
|
||||||
|
## clean-editorial
|
||||||
|
|
||||||
|
推荐语:
|
||||||
|
|
||||||
|
```text
|
||||||
|
我建议用 clean-editorial,因为这篇更像站内解释型长文,需要封面干净、标题有传播感,但不要太像促销海报。
|
||||||
|
```
|
||||||
|
|
||||||
|
视觉要点:
|
||||||
|
|
||||||
|
- 大标题、留白、轻杂志感。
|
||||||
|
- 猫咪放在角落或标题区附近,不抢正文信息。
|
||||||
|
- 更适合封面、文章总结图、观点图。
|
||||||
|
- 画面克制,避免过度卡通。
|
||||||
|
|
||||||
|
提示词片段:
|
||||||
|
|
||||||
|
```text
|
||||||
|
MAOMOMO 清爽编辑部风,白底大标题,轻杂志排版,暖橙点缀,留白充足,白橘猫作为角落品牌元素,适合中文金融经验文章封面。
|
||||||
|
```
|
||||||
|
|
||||||
|
## handdrawn-note
|
||||||
|
|
||||||
|
推荐语:
|
||||||
|
|
||||||
|
```text
|
||||||
|
我建议用 handdrawn-note,因为这篇是避坑 / 步骤教程,手绘箭头、便签和圈注更容易把翻车点讲明白。
|
||||||
|
```
|
||||||
|
|
||||||
|
视觉要点:
|
||||||
|
|
||||||
|
- 手绘箭头、便签、圈注、风险提示贴纸。
|
||||||
|
- 适合“不要这样做”“先做这一步”“最大坑点”。
|
||||||
|
- 信息密度中等,避免塞太多长句。
|
||||||
|
- 猫咪可以举提示牌或指向关键步骤。
|
||||||
|
|
||||||
|
提示词片段:
|
||||||
|
|
||||||
|
```text
|
||||||
|
MAOMOMO 手绘便签风,白底纸感,暖橙便利贴,手绘箭头和圈注,白橘猫举提示牌,中文步骤清楚,适合避坑经验和保姆式教程。
|
||||||
|
```
|
||||||
|
|
||||||
|
## xiaohongshu-vertical
|
||||||
|
|
||||||
|
推荐语:
|
||||||
|
|
||||||
|
```text
|
||||||
|
我建议用 xiaohongshu-vertical,因为用户要发小红书,应该重新做 9:16 竖图,而不是复用站内横版图。
|
||||||
|
```
|
||||||
|
|
||||||
|
视觉要点:
|
||||||
|
|
||||||
|
- 9:16 竖版,手机端可读。
|
||||||
|
- 大字少字,标题钩子强。
|
||||||
|
- 4 图系列统一:封面、核心拆解、操作流程、避坑总结。
|
||||||
|
- 每张图都有 MAOMOMO 标识或猫咪水印。
|
||||||
|
|
||||||
|
提示词片段:
|
||||||
|
|
||||||
|
```text
|
||||||
|
MAOMOMO 小红书竖版攻略风,9:16,手机端可读,大标题,少量关键数字,暖橙白底,白橘猫水印,适合 4 图看完的中文金融攻略。
|
||||||
|
```
|
||||||
|
|
||||||
|
## 风格选择输出格式
|
||||||
|
|
||||||
|
向用户推荐时使用:
|
||||||
|
|
||||||
|
```text
|
||||||
|
我建议用 A,因为【一句话说明与文章类型的匹配】。
|
||||||
|
|
||||||
|
A. warm-fintech-guide(推荐):【适合原因】
|
||||||
|
B. data-card-dashboard:【适合原因】
|
||||||
|
C. handdrawn-note:【适合原因】
|
||||||
|
|
||||||
|
你选哪个?也可以指定更偏简洁、数据感、手绘感或小红书感。
|
||||||
|
```
|
||||||
|
|
||||||
|
用户选择后,把风格名写入图片清单的 `风格` 字段或 manifest 的 `style` 字段。
|
||||||
|
|
@ -11,6 +11,40 @@
|
||||||
- 用户提供真实截图时,优先用真实截图,并隐去姓名、账号、余额、地址、手机号、二维码、交易号等个人信息。
|
- 用户提供真实截图时,优先用真实截图,并隐去姓名、账号、余额、地址、手机号、二维码、交易号等个人信息。
|
||||||
- 每张图必须包含清晰可见的 `MAOMOMO` 标识。
|
- 每张图必须包含清晰可见的 `MAOMOMO` 标识。
|
||||||
|
|
||||||
|
## 风格选择
|
||||||
|
|
||||||
|
如果用户没有指定视觉方向,生成图片前先读 `references/recommended-styles.md`,给 2-3 个可选风格,并推荐一个。确认后,整篇文章的配图保持同一套视觉身份:稳定的配色、猫咪造型、标题区、图标语言和信息密度;不同图片可以根据用途变化构图。
|
||||||
|
|
||||||
|
默认推荐逻辑:
|
||||||
|
|
||||||
|
- 活动 / 返现 / 港卡攻略:优先 `warm-fintech-guide`。
|
||||||
|
- 规则、门槛、费用、返现计算和产品对比:优先 `data-card-dashboard`。
|
||||||
|
- SEO 长文、经验整理、品牌感更强的文章:优先 `clean-editorial`。
|
||||||
|
- 保姆式教程、避坑经验和轻量操作步骤:优先 `handdrawn-note`。
|
||||||
|
- 小红书 / 4 图看完:必须用 `xiaohongshu-vertical`,并生成 9:16 竖图。
|
||||||
|
|
||||||
|
可选风格:
|
||||||
|
|
||||||
|
| 风格名 | 适合场景 | 视觉方向 |
|
||||||
|
|---|---|---|
|
||||||
|
| `warm-fintech-guide` | 默认 MAOMOMO 金融攻略、港卡、银行活动 | 白底、暖橙强调色、干净卡片、白橘猫向导 |
|
||||||
|
| `clean-editorial` | SEO 长文、解释型文章、站内封面 | 大标题、留白、轻杂志感、猫咪作为角落品牌元素 |
|
||||||
|
| `data-card-dashboard` | 返现计算、活动对比、费用路径、时间线 | 指标卡、表格感、仪表盘、清晰数字层级 |
|
||||||
|
| `handdrawn-note` | 避坑、步骤教程、保号经验 | 手绘箭头、便签、圈注、猫咪提示牌 |
|
||||||
|
| `xiaohongshu-vertical` | 小红书发布版、4 图看完 | 9:16 竖版、大字少字、强钩子、系列化版式 |
|
||||||
|
|
||||||
|
示例确认话术:
|
||||||
|
|
||||||
|
```text
|
||||||
|
我建议用 A,因为这篇是活动攻略,读者最需要快速看懂门槛、封顶和翻车点。
|
||||||
|
|
||||||
|
A. warm-fintech-guide(推荐):白底暖橙、猫咪向导、金融教程感,适合 MAOMOMO 常规文章。
|
||||||
|
B. data-card-dashboard:更强调数字卡片和计算表,适合门槛 / 返现 / 费用信息密集的文章。
|
||||||
|
C. handdrawn-note:便签和手绘箭头更明显,适合避坑经验和保姆式步骤。
|
||||||
|
|
||||||
|
你选哪个?也可以指定颜色、猫咪比例或是否更像小红书图。
|
||||||
|
```
|
||||||
|
|
||||||
## 常见配图类型
|
## 常见配图类型
|
||||||
|
|
||||||
| 类型 | 用途 |
|
| 类型 | 用途 |
|
||||||
|
|
@ -29,10 +63,10 @@
|
||||||
生成图片前先规划清单:
|
生成图片前先规划清单:
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
| 文件名 | 类型 | 放置位置 | 核心文案 | alt text | 依赖素材 |
|
| 文件名 | 类型 | 放置位置 | 核心文案 | alt text | 风格 | 依赖素材 |
|
||||||
|---|---|---|---|---|---|
|
|---|---|---|---|---|---|---|
|
||||||
| hong-kong-bank-account-01-cover.png | 封面 | 标题下方 | 港卡保号关键点 | MAOMOMO 港卡保号教程封面图 | 无 |
|
| hong-kong-bank-account-01-cover.png | 封面 | 标题下方 | 港卡保号关键点 | MAOMOMO 港卡保号教程封面图 | warm-fintech-guide | 无 |
|
||||||
| hong-kong-bank-account-02-flow.png | 流程图 | 操作步骤前 | 三步完成账户活跃 | 港卡保号操作流程图 | 用户截图 1 |
|
| hong-kong-bank-account-02-flow.png | 流程图 | 操作步骤前 | 三步完成账户活跃 | 港卡保号操作流程图 | handdrawn-note | 用户截图 1 |
|
||||||
```
|
```
|
||||||
|
|
||||||
确认用户不需要先看清单时,可以直接执行,但最终必须让文件名、alt text 和插图位置与正文一致。
|
确认用户不需要先看清单时,可以直接执行,但最终必须让文件名、alt text 和插图位置与正文一致。
|
||||||
|
|
@ -117,6 +151,7 @@ MAOMOMO 风格中文金融教程信息图,干净明亮白色背景,暖橙色
|
||||||
- 每张图有 MAOMOMO 标识或猫咪水印。
|
- 每张图有 MAOMOMO 标识或猫咪水印。
|
||||||
- 手机端可读,大字少字。
|
- 手机端可读,大字少字。
|
||||||
- 默认结构:封面、核心拆解、操作流程、避坑总结。
|
- 默认结构:封面、核心拆解、操作流程、避坑总结。
|
||||||
|
- 风格固定为 `xiaohongshu-vertical`,不直接复用横版文章图。
|
||||||
|
|
||||||
## 图片 QA
|
## 图片 QA
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,373 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""MAOMOMO article image generation CLI.
|
||||||
|
|
||||||
|
This is a lightweight OpenAI-compatible fallback for article assets. Prefer the
|
||||||
|
agent's built-in image tool when it is available; use this script when the user
|
||||||
|
explicitly wants API/CLI generation or the built-in backend is unavailable.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from typing import Any, Dict, Iterable, List, Optional
|
||||||
|
import urllib.error
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_BASE_URL = "https://api.openai.com/v1"
|
||||||
|
DEFAULT_MODEL = "gpt-image-2"
|
||||||
|
DEFAULT_SIZE = "1536x1024"
|
||||||
|
DEFAULT_QUALITY = "medium"
|
||||||
|
DEFAULT_STYLE = "warm-fintech-guide"
|
||||||
|
|
||||||
|
|
||||||
|
STYLE_PRESETS: Dict[str, str] = {
|
||||||
|
"warm-fintech-guide": (
|
||||||
|
"MAOMOMO 暖橙金融教程风:明亮白底,暖橙强调色,干净卡片布局,友好的白橘猫作为向导,"
|
||||||
|
"适合港卡、银行活动、返现攻略和教程总览。"
|
||||||
|
),
|
||||||
|
"clean-editorial": (
|
||||||
|
"清爽编辑部风:大标题、留白充足、少量暖橙和黑灰文字,像一张信息密度适中的中文攻略头图。"
|
||||||
|
),
|
||||||
|
"data-card-dashboard": (
|
||||||
|
"数据卡片仪表盘风:指标卡、时间线、计算公式和对比表清晰分区,适合返现、费用、门槛和路径对比。"
|
||||||
|
),
|
||||||
|
"handdrawn-note": (
|
||||||
|
"手绘便签风:白底纸感、手绘箭头、便利贴、重点圈注和轻量猫咪贴纸,适合避坑经验和保姆式步骤。"
|
||||||
|
),
|
||||||
|
"xiaohongshu-vertical": (
|
||||||
|
"小红书竖版攻略风:9:16 手机端可读,大字少字,强标题钩子,4 图系列一致视觉,带 MAOMOMO 标识。"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def die(message: str, code: int = 1) -> None:
|
||||||
|
print(f"错误:{message}", file=sys.stderr)
|
||||||
|
raise SystemExit(code)
|
||||||
|
|
||||||
|
|
||||||
|
def warn(message: str) -> None:
|
||||||
|
print(f"警告:{message}", file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
def read_text(path: Path) -> str:
|
||||||
|
return path.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def write_text(path: Path, text: str) -> None:
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with path.open("w", encoding="utf-8", newline="\n") as handle:
|
||||||
|
handle.write(text)
|
||||||
|
|
||||||
|
|
||||||
|
def read_json(path: Path) -> Any:
|
||||||
|
return json.loads(read_text(path))
|
||||||
|
|
||||||
|
|
||||||
|
def load_codex_config_base_url() -> Optional[str]:
|
||||||
|
config_path = Path.home() / ".codex" / "config.toml"
|
||||||
|
if not config_path.exists():
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
import tomllib
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
data = tomllib.loads(read_text(config_path))
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
provider_name = data.get("model_provider")
|
||||||
|
providers = data.get("model_providers")
|
||||||
|
if isinstance(provider_name, str) and isinstance(providers, dict):
|
||||||
|
provider = providers.get(provider_name)
|
||||||
|
if isinstance(provider, dict) and isinstance(provider.get("base_url"), str):
|
||||||
|
return provider["base_url"].strip()
|
||||||
|
|
||||||
|
if isinstance(data.get("base_url"), str):
|
||||||
|
return data["base_url"].strip()
|
||||||
|
if isinstance(providers, dict):
|
||||||
|
for provider in providers.values():
|
||||||
|
if isinstance(provider, dict) and isinstance(provider.get("base_url"), str):
|
||||||
|
return provider["base_url"].strip()
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def find_secret(value: Any) -> Optional[str]:
|
||||||
|
if isinstance(value, dict):
|
||||||
|
for key in ("MAOMOMO_IMAGE_API_KEY", "OPENAI_API_KEY", "api_key", "openai_api_key", "token"):
|
||||||
|
raw = value.get(key)
|
||||||
|
if isinstance(raw, str) and raw.strip():
|
||||||
|
return raw.strip()
|
||||||
|
for child in value.values():
|
||||||
|
found = find_secret(child)
|
||||||
|
if found:
|
||||||
|
return found
|
||||||
|
if isinstance(value, list):
|
||||||
|
for child in value:
|
||||||
|
found = find_secret(child)
|
||||||
|
if found:
|
||||||
|
return found
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def load_codex_auth_api_key() -> Optional[str]:
|
||||||
|
auth_path = Path.home() / ".codex" / "auth.json"
|
||||||
|
if not auth_path.exists():
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return find_secret(read_json(auth_path))
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def api_key() -> str:
|
||||||
|
value = os.getenv("MAOMOMO_IMAGE_API_KEY") or os.getenv("OPENAI_API_KEY") or load_codex_auth_api_key()
|
||||||
|
if not value:
|
||||||
|
die(
|
||||||
|
"未找到 API Key。请设置 MAOMOMO_IMAGE_API_KEY 或 OPENAI_API_KEY,"
|
||||||
|
"或确保 ~/.codex/auth.json 中存在可用 key。"
|
||||||
|
)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def base_url() -> str:
|
||||||
|
return (
|
||||||
|
os.getenv("MAOMOMO_IMAGE_BASE_URL")
|
||||||
|
or os.getenv("OPENAI_BASE_URL")
|
||||||
|
or load_codex_config_base_url()
|
||||||
|
or DEFAULT_BASE_URL
|
||||||
|
).rstrip("/")
|
||||||
|
|
||||||
|
|
||||||
|
def image_model() -> str:
|
||||||
|
return os.getenv("MAOMOMO_IMAGE_MODEL") or os.getenv("CODEX_PPT_IMAGE_MODEL") or DEFAULT_MODEL
|
||||||
|
|
||||||
|
|
||||||
|
def style_prompt(style: str) -> str:
|
||||||
|
if style in STYLE_PRESETS:
|
||||||
|
return STYLE_PRESETS[style]
|
||||||
|
return style
|
||||||
|
|
||||||
|
|
||||||
|
def build_prompt(
|
||||||
|
*,
|
||||||
|
title: str,
|
||||||
|
image_type: str,
|
||||||
|
core_text: str,
|
||||||
|
style: str,
|
||||||
|
aspect_ratio: str,
|
||||||
|
extra: str = "",
|
||||||
|
) -> str:
|
||||||
|
parts = [
|
||||||
|
"生成一张 MAOMOMO 中文金融实操文章配图。",
|
||||||
|
f"图片类型:{image_type}",
|
||||||
|
f"标题 / 主题:{title}",
|
||||||
|
f"核心文案:{core_text}",
|
||||||
|
f"画幅:{aspect_ratio}",
|
||||||
|
f"视觉风格:{style_prompt(style)}",
|
||||||
|
"硬性要求:包含清晰可见的 MAOMOMO 标识;中文文字清楚可读;信息层级明确;不要伪造真实 App 截图;不要使用未经提供的银行、券商、支付机构或卡组织 Logo;不要出现真实个人信息;猫咪不能承载第三方品牌 Logo。",
|
||||||
|
]
|
||||||
|
if extra.strip():
|
||||||
|
parts.append(f"补充要求:{extra.strip()}")
|
||||||
|
return "\n".join(parts)
|
||||||
|
|
||||||
|
|
||||||
|
def request_image(prompt: str, *, size: str, quality: str, output_format: str) -> bytes:
|
||||||
|
payload = {
|
||||||
|
"model": image_model(),
|
||||||
|
"prompt": prompt,
|
||||||
|
"size": size,
|
||||||
|
"quality": quality,
|
||||||
|
"n": 1,
|
||||||
|
"response_format": "b64_json",
|
||||||
|
}
|
||||||
|
if output_format:
|
||||||
|
payload["output_format"] = output_format
|
||||||
|
|
||||||
|
body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
|
||||||
|
req = urllib.request.Request(
|
||||||
|
base_url() + "/images/generations",
|
||||||
|
data=body,
|
||||||
|
headers={
|
||||||
|
"Authorization": f"Bearer {api_key()}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
method="POST",
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, timeout=180) as resp:
|
||||||
|
data = json.loads(resp.read().decode("utf-8"))
|
||||||
|
except urllib.error.HTTPError as exc:
|
||||||
|
detail = exc.read().decode("utf-8", "replace")
|
||||||
|
die(f"图片接口返回 HTTP {exc.code}: {detail}")
|
||||||
|
except Exception as exc:
|
||||||
|
die(f"图片接口请求失败:{exc}")
|
||||||
|
|
||||||
|
items = data.get("data")
|
||||||
|
if not isinstance(items, list) or not items:
|
||||||
|
die("图片接口响应缺少 data。")
|
||||||
|
first = items[0]
|
||||||
|
if not isinstance(first, dict):
|
||||||
|
die("图片接口响应格式不正确。")
|
||||||
|
if isinstance(first.get("b64_json"), str):
|
||||||
|
return base64.b64decode(first["b64_json"])
|
||||||
|
if isinstance(first.get("url"), str):
|
||||||
|
with urllib.request.urlopen(first["url"], timeout=180) as resp:
|
||||||
|
return resp.read()
|
||||||
|
die("图片接口响应中没有 b64_json 或 url。")
|
||||||
|
return b""
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_output(path: str, *, base_dir: Optional[Path]) -> Path:
|
||||||
|
out = Path(path)
|
||||||
|
if not out.is_absolute() and base_dir is not None:
|
||||||
|
out = base_dir / out
|
||||||
|
return out.resolve()
|
||||||
|
|
||||||
|
|
||||||
|
def generate_one(args: argparse.Namespace) -> Dict[str, str]:
|
||||||
|
if args.prompt_file:
|
||||||
|
prompt = read_text(Path(args.prompt_file)).strip()
|
||||||
|
elif args.prompt:
|
||||||
|
prompt = args.prompt.strip()
|
||||||
|
else:
|
||||||
|
prompt = build_prompt(
|
||||||
|
title=args.title,
|
||||||
|
image_type=args.image_type,
|
||||||
|
core_text=args.core_text,
|
||||||
|
style=args.style,
|
||||||
|
aspect_ratio=args.aspect_ratio,
|
||||||
|
extra=args.extra,
|
||||||
|
)
|
||||||
|
|
||||||
|
out = normalize_output(args.out, base_dir=None)
|
||||||
|
if args.dry_run:
|
||||||
|
write_text(out.with_suffix(".prompt.txt"), prompt + "\n")
|
||||||
|
return {"out": str(out), "prompt": str(out.with_suffix(".prompt.txt")), "status": "dry-run"}
|
||||||
|
|
||||||
|
image_bytes = request_image(prompt, size=args.size, quality=args.quality, output_format=args.output_format)
|
||||||
|
out.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
out.write_bytes(image_bytes)
|
||||||
|
return {"out": str(out), "status": "generated"}
|
||||||
|
|
||||||
|
|
||||||
|
def manifest_items(path: Path) -> List[Dict[str, Any]]:
|
||||||
|
data = read_json(path)
|
||||||
|
if isinstance(data, dict):
|
||||||
|
items = data.get("images")
|
||||||
|
else:
|
||||||
|
items = data
|
||||||
|
if not isinstance(items, list):
|
||||||
|
die("manifest 必须是图片数组,或包含 images 数组的对象。")
|
||||||
|
normalized: List[Dict[str, Any]] = []
|
||||||
|
for index, item in enumerate(items, start=1):
|
||||||
|
if not isinstance(item, dict):
|
||||||
|
die(f"manifest 第 {index} 项不是对象。")
|
||||||
|
normalized.append(item)
|
||||||
|
return normalized
|
||||||
|
|
||||||
|
|
||||||
|
def batch(args: argparse.Namespace) -> List[Dict[str, str]]:
|
||||||
|
manifest_path = Path(args.manifest).resolve()
|
||||||
|
base_dir = Path(args.base_dir).resolve() if args.base_dir else manifest_path.parent
|
||||||
|
results: List[Dict[str, str]] = []
|
||||||
|
|
||||||
|
for index, item in enumerate(manifest_items(manifest_path), start=1):
|
||||||
|
filename = item.get("file_name") or item.get("filename") or item.get("out")
|
||||||
|
if not isinstance(filename, str) or not filename.strip():
|
||||||
|
die(f"manifest 第 {index} 项缺少 file_name。")
|
||||||
|
style = str(item.get("style") or args.style or DEFAULT_STYLE)
|
||||||
|
aspect_ratio = str(item.get("aspect_ratio") or args.aspect_ratio)
|
||||||
|
prompt = item.get("prompt")
|
||||||
|
if not isinstance(prompt, str) or not prompt.strip():
|
||||||
|
prompt = build_prompt(
|
||||||
|
title=str(item.get("title") or item.get("alt_text") or filename),
|
||||||
|
image_type=str(item.get("type") or "文章配图"),
|
||||||
|
core_text=str(item.get("core_text") or item.get("caption") or item.get("alt_text") or ""),
|
||||||
|
style=style,
|
||||||
|
aspect_ratio=aspect_ratio,
|
||||||
|
extra=str(item.get("extra") or item.get("requirements") or ""),
|
||||||
|
)
|
||||||
|
|
||||||
|
out = normalize_output(filename, base_dir=base_dir)
|
||||||
|
if args.dry_run:
|
||||||
|
write_text(out.with_suffix(".prompt.txt"), prompt + "\n")
|
||||||
|
results.append({"out": str(out), "prompt": str(out.with_suffix(".prompt.txt")), "status": "dry-run"})
|
||||||
|
continue
|
||||||
|
|
||||||
|
image_bytes = request_image(
|
||||||
|
prompt,
|
||||||
|
size=str(item.get("size") or args.size),
|
||||||
|
quality=str(item.get("quality") or args.quality),
|
||||||
|
output_format=str(item.get("output_format") or args.output_format),
|
||||||
|
)
|
||||||
|
out.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
out.write_bytes(image_bytes)
|
||||||
|
results.append({"out": str(out), "status": "generated"})
|
||||||
|
if args.sleep > 0:
|
||||||
|
time.sleep(args.sleep)
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def print_styles(_: argparse.Namespace) -> int:
|
||||||
|
print(json.dumps(STYLE_PRESETS, ensure_ascii=False, indent=2))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def build_parser() -> argparse.ArgumentParser:
|
||||||
|
parser = argparse.ArgumentParser(description="Generate MAOMOMO article images with an OpenAI-compatible API.")
|
||||||
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||||
|
|
||||||
|
common = argparse.ArgumentParser(add_help=False)
|
||||||
|
common.add_argument("--style", default=DEFAULT_STYLE, help="Style preset name or custom style text.")
|
||||||
|
common.add_argument("--size", default=DEFAULT_SIZE)
|
||||||
|
common.add_argument("--quality", default=DEFAULT_QUALITY)
|
||||||
|
common.add_argument("--output-format", default="png")
|
||||||
|
common.add_argument("--aspect-ratio", default="16:9")
|
||||||
|
common.add_argument("--dry-run", action="store_true", help="Only write prompt files; do not call the image API.")
|
||||||
|
|
||||||
|
generate = subparsers.add_parser("generate", parents=[common], help="Generate one image.")
|
||||||
|
generate.add_argument("--out", required=True)
|
||||||
|
generate.add_argument("--prompt")
|
||||||
|
generate.add_argument("--prompt-file")
|
||||||
|
generate.add_argument("--title", default="MAOMOMO 文章配图")
|
||||||
|
generate.add_argument("--image-type", default="文章配图")
|
||||||
|
generate.add_argument("--core-text", default="")
|
||||||
|
generate.add_argument("--extra", default="")
|
||||||
|
|
||||||
|
batch_parser = subparsers.add_parser("batch", parents=[common], help="Generate images from a JSON manifest.")
|
||||||
|
batch_parser.add_argument("--manifest", required=True)
|
||||||
|
batch_parser.add_argument("--base-dir", help="Base directory for relative file_name paths. Defaults to manifest dir.")
|
||||||
|
batch_parser.add_argument("--sleep", type=float, default=0.0, help="Seconds to sleep between API calls.")
|
||||||
|
|
||||||
|
subparsers.add_parser("styles", help="Print bundled style presets.")
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: Optional[Iterable[str]] = None) -> int:
|
||||||
|
parser = build_parser()
|
||||||
|
args = parser.parse_args(list(argv) if argv is not None else None)
|
||||||
|
if args.command == "styles":
|
||||||
|
return print_styles(args)
|
||||||
|
if args.command == "generate":
|
||||||
|
result = generate_one(args)
|
||||||
|
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||||
|
return 0
|
||||||
|
if args.command == "batch":
|
||||||
|
results = batch(args)
|
||||||
|
print(json.dumps(results, ensure_ascii=False, indent=2))
|
||||||
|
return 0
|
||||||
|
parser.print_help()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Loading…
Reference in New Issue