Compare commits

...

29 Commits

Author SHA1 Message Date
Jim Liu 宝玉 b0554f8d0c chore: release v1.28.2 2026-02-03 11:01:25 -06:00
Jim Liu 宝玉 12f3d95639 fix(baoyu-post-to-x): improve Chrome reuse and placeholder matching in x-article 2026-02-03 11:01:09 -06:00
Jim Liu 宝玉 809fb29ca9 chore: release v1.28.1 2026-02-02 09:11:49 -06:00
Jim Liu 宝玉 be20bdf0be refactor(baoyu-article-illustrator): simplify SKILL.md and add Core Styles tier
- Extract detailed workflow to references/workflow.md
- Add Core Styles for quick selection (vector, minimal-flat, sci-fi, hand-drawn, editorial, scene)
- Add vector-illustration as recommended default style
- Add Illustration Purpose (information/visualization/imagination)
- Add default composition, character rendering, text styling to prompts
2026-02-02 09:11:43 -06:00
Jim Liu 宝玉 7c36b2a6a5 chore: release v1.28.0 2026-02-01 19:49:49 -06:00
Jim Liu 宝玉 7979cb9dde chore: ignore baoyu-skill-evolution directory 2026-02-01 19:49:22 -06:00
Jim Liu 宝玉 e563bbae7b feat(baoyu-post-to-wechat): add newspic article type support
- Add ArticleOptions interface with news/newspic types
- Track all image media IDs for newspic format
- Restructure publishToDraft for dual article type handling
2026-02-01 19:49:14 -06:00
Jim Liu 宝玉 c63d1eec69 refactor(baoyu-xhs-images): enforce blocking first-time setup
- Mark preferences loading as BLOCKING operation
- Update workflow flow diagram and checklist
2026-02-01 19:49:05 -06:00
Jim Liu 宝玉 78d6d23359 refactor(baoyu-comic): enforce blocking first-time setup
- Mark preferences loading as BLOCKING operation
- Update workflow flow diagram
2026-02-01 19:48:56 -06:00
Jim Liu 宝玉 ec6781a231 feat(baoyu-article-illustrator): add reference image support
- Add reference image handling with direct/style/palette usage types
- Enhance first-time setup as blocking operation
- Update prompt construction with reference frontmatter format
2026-02-01 19:48:46 -06:00
Jim Liu 宝玉 edf9030675 feat(baoyu-cover-image): add reference image support and visual elements library
- Add --ref parameter for reference images (style/palette/direct usage)
- Add visual-elements.md with icon vocabulary by topic
- Enhance first-time setup as blocking operation
- Remove character limits from titles, use original source titles
- Update prompt template with reference handling
2026-02-01 19:48:37 -06:00
Jim Liu 宝玉 10e1c426a0 chore: release v1.27.0 2026-02-01 02:15:08 -06:00
Jim Liu 宝玉 dbab83ff82 feat(baoyu-markdown-to-html): add remark-cjk-friendly for CJK emphasis
Preprocess markdown with remark-cjk-friendly to properly handle
CJK emphasis markers before HTML conversion.
2026-02-01 02:14:12 -06:00
Jim Liu 宝玉 478e66a93a docs(baoyu-infographic): clarify AskUserQuestion usage
Emphasize combining multiple questions into single call.
2026-02-01 02:14:06 -06:00
Jim Liu 宝玉 3a5866eb4b refactor(baoyu-format-markdown): use remark-cjk-friendly for CJK emphasis
Replace custom CJK emphasis handling with remark-cjk-friendly library,
significantly simplifying the codebase.
2026-02-01 02:13:58 -06:00
Jim Liu 宝玉 c0162bb3af chore: release v1.26.1 2026-01-29 22:02:42 -06:00
Jim Liu 宝玉 e9342a16f8 fix(baoyu-xhs-images): remove notebook style, add backup rules for prompts and images 2026-01-29 22:01:47 -06:00
Jim Liu 宝玉 5cb86d8e51 feat(baoyu-slide-deck): add backup rules for source, prompt, and slide image files 2026-01-29 22:01:42 -06:00
Jim Liu 宝玉 66ed08f010 feat(baoyu-infographic): add backup rules for source, analysis, prompt, and image files 2026-01-29 22:01:22 -06:00
Jim Liu 宝玉 b179166d28 feat(baoyu-cover-image): add backup rules for source and prompt files 2026-01-29 22:01:17 -06:00
Jim Liu 宝玉 4805526649 feat(baoyu-comic): add backup rules for source, character sheet, prompts, and page images 2026-01-29 22:00:59 -06:00
Jim Liu 宝玉 89159bf86e feat(baoyu-article-illustrator): add backup rules for source, prompt, and image files 2026-01-29 22:00:37 -06:00
Jim Liu 宝玉 b45b5d2c6e chore: release v1.26.0 2026-01-29 20:59:45 -06:00
Jim Liu 宝玉 62866426c5 feat(baoyu-xhs-images): add notebook and study-notes styles with mindmap and quadrant layouts
- notebook: hand-drawn infographic style with watercolor rendering and Morandi palette
- study-notes: realistic handwritten photo style with blue pen, red annotations, yellow highlighter
- mindmap: center radial layout (4-8 branches)
- quadrant: four-quadrant / circular section layout
2026-01-29 20:59:24 -06:00
Jim Liu 宝玉 28a7db6129 chore: release v1.25.4 2026-01-29 17:17:58 -06:00
Jim Liu 宝玉 7c5e81fd15 chore: release v1.25.3 2026-01-28 22:31:47 -06:00
Jim Liu 宝玉 058d25d02c feat(baoyu-format-markdown): add content type detection and CJK punctuation handling 2026-01-28 22:31:25 -06:00
Jim Liu 宝玉 fe09ec546f chore: release v1.25.2 2026-01-28 22:10:32 -06:00
Jim Liu 宝玉 cf611a4514 docs(baoyu-post-to-wechat): add WeChat API credentials configuration guide 2026-01-28 22:10:27 -06:00
39 changed files with 4514 additions and 620 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
},
"metadata": {
"description": "Skills shared by Baoyu for improving daily work efficiency",
"version": "1.25.1"
"version": "1.28.2"
},
"plugins": [
{
+3 -1
View File
@@ -156,4 +156,6 @@ comic/
.idea
*.iws
*.iml
*.ipr
*.ipr
.claude/skills/baoyu-skill-evolution
+53
View File
@@ -2,6 +2,59 @@
English | [中文](./CHANGELOG.zh.md)
## 1.28.2 - 2026-02-03
### Fixes
- `baoyu-post-to-x`: reuse existing Chrome instance when available; fix placeholder matching issue where `XIMGPH_1` incorrectly matched `XIMGPH_10`; improve image sorting by placeholder index; use `execCommand` for more reliable placeholder deletion.
## 1.28.1 - 2026-02-02
### Refactor
- `baoyu-article-illustrator`: simplify main SKILL.md by extracting detailed procedures to `workflow.md`; add Core Styles tier (vector, minimal-flat, sci-fi, hand-drawn, editorial, scene) for quick selection; add `vector-illustration` as recommended default style; add Illustration Purpose (information/visualization/imagination) for better type/style recommendations; add default composition requirements, character rendering guidelines, and text styling rules to prompt construction.
## 1.28.0 - 2026-02-01
### Features
- `baoyu-cover-image`: add reference image support (`--ref` parameter) with direct/style/palette usage types; add visual elements library with icon vocabulary by topic.
- `baoyu-article-illustrator`: add reference image support with direct/style/palette usage types.
- `baoyu-post-to-wechat`: add `newspic` article type for image-text posts.
### Refactor
- `baoyu-cover-image`, `baoyu-article-illustrator`, `baoyu-comic`, `baoyu-xhs-images`: enforce first-time setup as blocking operation before any other workflow steps.
- `baoyu-cover-image`: remove character limits from titles, use original source titles.
## 1.26.1 - 2026-01-29
### Features
- `baoyu-article-illustrator`, `baoyu-comic`, `baoyu-cover-image`, `baoyu-infographic`, `baoyu-slide-deck`, `baoyu-xhs-images`: add backup rules for existing files—automatically renames source, prompt, and image files with timestamp suffix before overwriting.
### Fixes
- `baoyu-xhs-images`: remove `notebook` style (10 styles remaining).
## 1.26.0 - 2026-01-29
### Features
- `baoyu-xhs-images`: add `notebook` style (hand-drawn infographic with watercolor rendering and Morandi palette) and `study-notes` style (realistic handwritten photo aesthetic).
- `baoyu-xhs-images`: add `mindmap` (center radial) and `quadrant` (four-section grid) layouts.
## 1.25.4 - 2026-01-29
### Fixes
- `baoyu-markdown-to-html`: generate proper `<img>` tags with `data-local-path` attribute instead of text placeholders.
- `baoyu-post-to-wechat`: fix API publishing to read image paths from `data-local-path` attribute; fix title/cover extraction from corresponding `.md` frontmatter when publishing HTML files.
- `baoyu-post-to-wechat`: fix CLI argument parsing to handle unknown parameters gracefully; add `--summary` parameter support.
- `baoyu-post-to-wechat`: fix browser publishing to convert `<img>` tags back to text placeholders before paste.
## 1.25.3 - 2026-01-28
### Features
- `baoyu-format-markdown`: add content type detection with user confirmation for markdown files; add CJK punctuation handling to move paired punctuation outside emphasis markers.
## 1.25.2 - 2026-01-28
### Documentation
- `baoyu-post-to-wechat`: add WeChat API credentials configuration guide to README.
## 1.25.1 - 2026-01-28
### Features
+53
View File
@@ -2,6 +2,59 @@
[English](./CHANGELOG.md) | 中文
## 1.28.2 - 2026-02-03
### 修复
- `baoyu-post-to-x`:复用已有 Chrome 实例;修复占位符匹配问题(`XIMGPH_1` 错误匹配 `XIMGPH_10`);改进图片按占位符序号排序;使用 `execCommand` 提高占位符删除可靠性。
## 1.28.1 - 2026-02-02
### 重构
- `baoyu-article-illustrator`:简化主 SKILL.md,将详细步骤提取到 `workflow.md`;新增 Core Styles 快速选择层(vector、minimal-flat、sci-fi、hand-drawn、editorial、scene);新增 `vector-illustration` 作为推荐默认风格;新增插图目的(information/visualization/imagination)以优化类型/风格推荐;在提示词构建中新增默认构图要求、人物渲染指南和文本样式规则。
## 1.28.0 - 2026-02-01
### 新功能
- `baoyu-cover-image`:新增参考图片支持(`--ref` 参数),支持 direct/style/palette 三种用法;新增视觉元素库,按主题分类图标词汇。
- `baoyu-article-illustrator`:新增参考图片支持,支持 direct/style/palette 三种用法。
- `baoyu-post-to-wechat`:新增 `newspic` 图文消息类型支持。
### 重构
- `baoyu-cover-image``baoyu-article-illustrator``baoyu-comic``baoyu-xhs-images`:强化首次设置为阻塞操作,必须在其他工作流步骤之前完成。
- `baoyu-cover-image`:移除标题字符数限制,使用原始来源标题。
## 1.26.1 - 2026-01-29
### 新功能
- `baoyu-article-illustrator``baoyu-comic``baoyu-cover-image``baoyu-infographic``baoyu-slide-deck``baoyu-xhs-images`:新增文件备份规则,覆盖前自动将现有源文件、提示词和图片重命名为带时间戳后缀的备份文件。
### 修复
- `baoyu-xhs-images`:移除 `notebook` 风格(保留 10 种风格)。
## 1.26.0 - 2026-01-29
### 新功能
- `baoyu-xhs-images`:新增 `notebook` 风格(水彩渲染手绘信息图 + 莫兰迪配色)和 `study-notes` 风格(真实手写照片美学)。
- `baoyu-xhs-images`:新增 `mindmap`(中心发散式)和 `quadrant`(四象限)布局。
## 1.25.4 - 2026-01-29
### 修复
- `baoyu-markdown-to-html`:生成带 `data-local-path` 属性的 `<img>` 标签,而非纯文本占位符。
- `baoyu-post-to-wechat`:修复 API 发布时从 `data-local-path` 属性读取图片路径;修复发布 HTML 文件时从对应 `.md` 的 frontmatter 提取标题和封面图。
- `baoyu-post-to-wechat`:修复命令行参数解析,正确跳过未知参数;新增 `--summary` 参数支持。
- `baoyu-post-to-wechat`:修复浏览器发布模式,粘贴前将 `<img>` 标签转换回文本占位符。
## 1.25.3 - 2026-01-28
### 新功能
- `baoyu-format-markdown`:新增内容类型检测,对已有 markdown 格式的文件提供用户确认选项;新增 CJK 配对标点处理,将括号、引号等标点移出加粗标记外。
## 1.25.2 - 2026-01-28
### 文档
- `baoyu-post-to-wechat`README 新增微信公众号 API 凭证配置说明。
## 1.25.1 - 2026-01-28
### 新功能
+22 -1
View File
@@ -535,7 +535,28 @@ Post content to WeChat Official Account (微信公众号). Two modes available:
/baoyu-post-to-wechat 文章 --html article.html
```
Prerequisites: Google Chrome installed. First run requires QR code login (session preserved).
**Publishing Methods**:
| Method | Speed | Requirements |
|--------|-------|--------------|
| API (Recommended) | Fast | API credentials |
| Browser | Slow | Chrome, login session |
**API Configuration** (for faster publishing):
```bash
# Add to .baoyu-skills/.env (project-level) or ~/.baoyu-skills/.env (user-level)
WECHAT_APP_ID=your_app_id
WECHAT_APP_SECRET=your_app_secret
```
To obtain credentials:
1. Visit https://developers.weixin.qq.com/platform/
2. Go to: 我的业务 → 公众号 → 开发密钥
3. Create development key and copy AppID/AppSecret
4. Add your machine's IP to the whitelist
**Browser Method** (no API setup needed): Requires Google Chrome. First run opens browser for QR code login (session preserved).
### AI Generation Skills
+22 -1
View File
@@ -535,7 +535,28 @@ npx skills add jimliu/baoyu-skills
/baoyu-post-to-wechat 文章 --html article.html
```
前置要求:已安装 Google Chrome,首次运行需扫码登录(登录状态会保存)
**发布方式**
| 方式 | 速度 | 要求 |
|------|------|------|
| API(推荐) | 快 | API 凭证 |
| 浏览器 | 慢 | Chrome,登录会话 |
**API 配置**(更快的发布方式):
```bash
# 添加到 .baoyu-skills/.env(项目级)或 ~/.baoyu-skills/.env(用户级)
WECHAT_APP_ID=你的AppID
WECHAT_APP_SECRET=你的AppSecret
```
获取凭证方法:
1. 访问 https://developers.weixin.qq.com/platform/
2. 进入:我的业务 → 公众号 → 开发密钥
3. 添加开发密钥,复制 AppID 和 AppSecret
4. 将你操作的机器 IP 加入白名单
**浏览器方式**(无需 API 配置):需已安装 Google Chrome,首次运行需扫码登录(登录状态会保存)
### AI 生成技能 (AI Generation Skills)
+57 -188
View File
@@ -16,6 +16,16 @@ Analyze articles, identify illustration positions, generate images with Type ×
Type × Style can be freely combined. Example: `--type infographic --style blueprint`
## Illustration Purpose
Auto-detected during content analysis. Influences type/style recommendations.
| Purpose | Description | Best Types |
|---------|-------------|------------|
| **information** | Help understand abstract concepts | infographic, flowchart, comparison |
| **visualization** | Turn abstract ideas into concrete visuals | framework, comparison, infographic |
| **imagination** | Create atmosphere, spark imagination | scene, timeline |
## Type Gallery
| Type | Best For |
@@ -27,92 +37,37 @@ Type × Style can be freely combined. Example: `--type infographic --style bluep
| `framework` | Methodologies, models, architecture |
| `timeline` | History, progress, evolution |
## Style Gallery
## Styles
| Style | Best For |
|-------|----------|
| `notion` (Default) | Knowledge sharing, SaaS, productivity |
| `elegant` | Business, thought leadership |
| `warm` | Personal growth, lifestyle, education |
| `minimal` | Philosophy, core concepts |
| `blueprint` | Architecture, system design |
| `watercolor` | Lifestyle, travel, creative |
| `editorial` | Tech explainers, journalism |
| `scientific` | Academic, technical research |
Full styles: [references/styles.md](references/styles.md)
## Auto Selection
| Content Signals | Type | Style |
|-----------------|------|-------|
| API, metrics, data, numbers | infographic | blueprint, notion |
| Story, emotion, journey | scene | warm, watercolor |
| How-to, steps, workflow | flowchart | notion, minimal |
| vs, pros/cons, before/after | comparison | notion, elegant |
| Framework, model, architecture | framework | blueprint, notion |
| History, timeline, progress | timeline | elegant, warm |
See [references/styles.md](references/styles.md) for:
- **Core Styles**: Simplified tier for quick selection (vector, minimal-flat, sci-fi, hand-drawn, editorial, scene)
- **Style Gallery**: Full 20+ style options with descriptions
- **Auto Selection**: Content signals → Type/Style recommendations
- **Compatibility Matrix**: Type × Style combinations
## Workflow
Copy this checklist and track progress:
```
Progress:
- [ ] Step 1: Pre-check
- [ ] 1.5 Load preferences (EXTEND.md) ⛔ BLOCKING
- [ ] 1.0 Reference images ⚠️ (if provided)
- [ ] 1.2-1.4 Config questions (1 AskUserQuestion, max 4 Qs)
- [ ] Step 2: Setup & Analyze
- [ ] Step 3: Confirm Settings ⚠️ REQUIRED
- [ ] Step 3: Confirm Settings (1 AskUserQuestion, max 4 Qs)
- [ ] Q1: Type ⚠️
- [ ] Q2: Density ⚠️ MUST ASK
- [ ] Q3: Style ⚠️
- [ ] Step 4: Generate Outline
- [ ] Step 5: Generate Images
- [ ] Step 6: Finalize
```
---
### Step 1: Pre-check
**1.1 Determine Input Type**
**1.5 Load Preferences (EXTEND.md) ⛔ BLOCKING**
| Input | Output Directory | Next |
|-------|------------------|------|
| File path | Ask user (1.2) | → 1.2 |
| Pasted content | `illustrations/{topic-slug}/` | → 1.4 |
**1.2 Determine Output Directory** (file path input only)
Check `default_output_dir` in preferences:
| Preference Value | Action |
|------------------|--------|
| `same-dir` | Use `{article-dir}/`, display "Output: {path}" |
| `imgs-subdir` | Use `{article-dir}/imgs/`, display "Output: {path}" |
| `illustrations-subdir` | Use `{article-dir}/illustrations/`, display "Output: {path}" |
| `independent` | Use `illustrations/{topic-slug}/`, display "Output: {path}" |
| Not configured | **MUST** ask with AskUserQuestion ↓ |
**AskUserQuestion** (when no preference):
- `{article-dir}/` - Same directory as article
- `{article-dir}/imgs/` - Images subdirectory
- `{article-dir}/illustrations/` - Illustrations subdirectory (Recommended)
- `illustrations/{topic-slug}/` - Independent directory
- Save as default - Remember this choice for future runs
**1.3 Check Existing Images**
Scan target directory for `.png/.jpg/.webp` files.
If images exist → AskUserQuestion: How to handle?
- `supplement` - Keep existing, generate only new positions
- `overwrite` - Overwrite same-name files
- `regenerate` - Clear all and regenerate
**1.4 Confirm Article Update Method** (file path input only)
AskUserQuestion: How to update article?
- `update` - Modify original file directly
- `copy` - Create `{name}-illustrated.md` copy
**1.5 Load Preferences (EXTEND.md)**
**CRITICAL**: If EXTEND.md not found, MUST complete first-time setup before ANY other steps.
```bash
test -f .baoyu-skills/baoyu-article-illustrator/EXTEND.md && echo "project"
@@ -121,166 +76,86 @@ test -f "$HOME/.baoyu-skills/baoyu-article-illustrator/EXTEND.md" && echo "user"
| Result | Action |
|--------|--------|
| Found | Read, parse, display summary |
| Not found | Ask with AskUserQuestion (see references/config/first-time-setup.md) |
| Found | Read, parse, display summary → Continue |
| Not found | ⛔ Run first-time setup ([references/config/first-time-setup.md](references/config/first-time-setup.md)) |
**Supports**: Watermark | Preferred type/style | Custom styles | Language | Output directory
**1.0-1.4**: Handle reference images, determine input type, ask config questions.
Full procedures: [references/workflow.md](references/workflow.md#step-1-pre-check)
---
### Step 2: Setup & Analyze
**2.1 Analyze Content**
| Analysis | Description |
|----------|-------------|
| Content type | Technical / Tutorial / Methodology / Narrative |
| Illustration purpose | information / visualization / imagination |
| Core arguments | 2-5 main points to visualize |
| Visual opportunities | Positions where illustrations add value |
| Recommended type | Based on content signals |
| Recommended density | Based on length and complexity |
**2.2 Extract Core Arguments**
**CRITICAL**: If article uses metaphors, do NOT illustrate literally. Visualize the **underlying concept**.
- Main thesis
- Key concepts reader needs
- Comparisons/contrasts
- Framework/model proposed
**CRITICAL**: If article uses metaphors (e.g., "电锯切西瓜"), do NOT illustrate literally. Visualize the **underlying concept**.
**2.3 Identify Positions**
**Illustrate**:
- Core arguments (REQUIRED)
- Abstract concepts
- Data comparisons
- Processes, workflows
**Do NOT Illustrate**:
- Metaphors literally
- Decorative scenes
- Generic illustrations
Full procedures: [references/workflow.md](references/workflow.md#step-2-setup--analyze)
---
### Step 3: Confirm Settings ⚠️
**Do NOT skip.** Use AskUserQuestion with 3-4 questions in ONE call.
**Do NOT skip.** Use ONE AskUserQuestion call with max 4 questions. **Q1, Q2, Q3 are ALL REQUIRED.**
**Q1: Illustration Type**
- [Recommended based on analysis] (Recommended)
- infographic / scene / flowchart / comparison / framework / timeline / mixed
| Question | Options |
|----------|---------|
| **Q1: Type** ⚠️ | [Recommended], infographic, scene, flowchart, comparison, framework, timeline, mixed |
| **Q2: Density** ⚠️ | minimal (1-2), balanced (3-5), per-section (Recommended), rich (6+) |
| **Q3: Style** ⚠️ | [Recommended], minimal-flat, sci-fi, hand-drawn, editorial, scene, Other |
| **Q4: Language** | When article language ≠ EXTEND.md setting |
**Q2: Density**
- minimal (1-2) - Core concepts only
- balanced (3-5) (Recommended) - Major sections
- rich (6+) - Comprehensive support
**Q3: Style** (ALWAYS ask, even with preferred_style in EXTEND.md)
If EXTEND.md has `preferred_style`:
- [Custom style name + brief description] (Recommended)
- [Top compatible built-in style 1]
- [Top compatible built-in style 2]
- [Top compatible built-in style 3]
If no `preferred_style`:
- [Best compatible from matrix] (Recommended)
- [Other ✓✓ style 1]
- [Other ✓✓ style 2]
- [Other ✓ style]
Style selection based on Type × Style compatibility matrix (references/styles.md).
Full specs: `references/styles/<style>.md`
**Q4** (only if source ≠ user language):
- Language: Source / User language
Full procedures: [references/workflow.md](references/workflow.md#step-3-confirm-settings-)
---
### Step 4: Generate Outline
Save as `outline.md`:
Save as `outline.md` with frontmatter (type, density, style, image_count, references) and illustration entries:
```yaml
---
type: infographic
density: balanced
style: blueprint
image_count: 4
---
## Illustration 1
**Position**: [section] / [paragraph]
**Purpose**: [why this helps]
**Visual Content**: [what to show]
**Type Application**: [how type applies]
**Filename**: 01-infographic-concept-name.png
## Illustration 2
...
```
**Requirements**:
- Each position justified by content needs
- Type applied consistently
- Style reflected in descriptions
- Count matches density
Full template: [references/workflow.md](references/workflow.md#step-4-generate-outline)
---
### Step 5: Generate Images
**5.1 Create Prompts**
1. **Create Prompts**: Follow [references/prompt-construction.md](references/prompt-construction.md)
2. **Select Generation Skill**: Check available skills
3. **Process References**: Handle `direct`/`style`/`palette` usage
4. **Apply Watermark**: If enabled in EXTEND.md
5. **Generate**: Sequential, retry once on failure
Follow [references/prompt-construction.md](references/prompt-construction.md). Save to `prompts/illustration-{slug}.md`.
**5.2 Select Generation Skill**
Check available skills. If multiple, ask user.
**5.3 Apply Watermark** (if enabled)
Add: `Include a subtle watermark "[content]" at [position].`
**5.4 Generate**
1. Generate sequentially
2. After each: "Generated X/N"
3. On failure: retry once, then log and continue
Full procedures: [references/workflow.md](references/workflow.md#step-5-generate-images)
---
### Step 6: Finalize
**6.1 Update Article**
Insert after corresponding paragraph:
```markdown
![description](illustrations/{slug}/NN-{type}-{slug}.png)
```
Alt text: concise description in article's language.
**6.2 Output Summary**
**Update Article**: Insert `![description](path/NN-{type}-{slug}.png)` after corresponding paragraphs.
**Output Summary**:
```
Article Illustration Complete!
Article: [path]
Type: [type] | Density: [level] | Style: [style]
Location: [directory]
Article: [path] | Type: [type] | Density: [level] | Style: [style]
Images: X/N generated
Positions:
- 01-xxx.png → After "[Section]"
- 02-yyy.png → After "[Section]"
[If failures]
Failed:
- NN-zzz.png: [reason]
```
---
@@ -290,6 +165,8 @@ Failed:
```
illustrations/{topic-slug}/
├── source-{slug}.{ext}
├── references/ # Only if references provided
│ └── NN-ref-{slug}.png
├── outline.md
├── prompts/
│ └── illustration-{slug}.md
@@ -303,26 +180,18 @@ illustrations/{topic-slug}/
| Action | Steps |
|--------|-------|
| **Edit** | **Update prompt file FIRST** → Regenerate → Update reference |
| **Edit** | Update prompt file FIRST → Regenerate → Update reference |
| **Add** | Identify position → Create prompt → Generate → Update outline → Insert |
| **Delete** | Delete files → Remove reference → Update outline |
**IMPORTANT**: When updating illustrations, ALWAYS update the prompt file (`prompts/illustration-{slug}.md`) FIRST before regenerating. This ensures:
1. Changes are documented and reproducible
2. The prompt reflects the new requirements
3. Future regeneration uses the correct prompt
## References
| File | Content |
|------|---------|
| [references/workflow.md](references/workflow.md) | Detailed workflow procedures |
| [references/usage.md](references/usage.md) | Command syntax and options |
| [references/styles.md](references/styles.md) | Style gallery & compatibility |
| [references/prompt-construction.md](references/prompt-construction.md) | Prompt templates |
| `references/styles/<style>.md` | Full style specifications |
| `references/config/preferences-schema.md` | EXTEND.md schema |
| `references/config/first-time-setup.md` | First-time setup flow |
## Extension Support
Custom configurations via EXTEND.md. See **Step 1.5** for paths and supported options.
@@ -9,6 +9,14 @@ description: First-time setup flow for baoyu-article-illustrator preferences
When no EXTEND.md is found, guide user through preference setup.
**⛔ BLOCKING OPERATION**: This setup MUST complete before ANY other workflow steps. Do NOT:
- Ask about reference images
- Ask about content/article
- Ask about type or style preferences
- Proceed to content analysis
ONLY ask the questions in this setup flow, save EXTEND.md, then continue.
## Setup Flow
```
@@ -1,5 +1,102 @@
# Prompt Construction
## Prompt File Format
Each prompt file uses YAML frontmatter + content:
```yaml
---
illustration_id: 01
type: infographic
style: blueprint
references: # ⚠️ ONLY if files EXIST in references/ directory
- ref_id: 01
filename: 01-ref-diagram.png
usage: direct # direct | style | palette
---
[Type-specific template content below...]
```
**⚠️ CRITICAL - When to include `references` field**:
| Situation | Action |
|-----------|--------|
| Reference file saved to `references/` | Include in frontmatter ✓ |
| Style extracted verbally (no file) | DO NOT include in frontmatter, append to prompt body instead |
| File path in frontmatter but file doesn't exist | ERROR - remove references field |
**Reference Usage Types** (only when file exists):
| Usage | Description | Generation Action |
|-------|-------------|-------------------|
| `direct` | Primary visual reference | Pass to `--ref` parameter |
| `style` | Style characteristics only | Describe style in prompt text |
| `palette` | Color palette extraction | Include colors in prompt |
**If no reference file but style/palette extracted verbally**, append directly to prompt body:
```
COLORS (from reference):
- Primary: #E8756D coral
- Secondary: #7ECFC0 mint
...
STYLE (from reference):
- Clean lines, minimal shadows
- Gradient backgrounds
...
```
---
## Default Composition Requirements
**Apply to ALL prompts by default**:
| Requirement | Description |
|-------------|-------------|
| **Clean composition** | Simple layouts, no visual clutter |
| **White space** | Generous margins, breathing room around elements |
| **No complex backgrounds** | Solid colors or subtle gradients only, avoid busy textures |
| **Centered or content-appropriate** | Main visual elements centered or positioned by content needs |
| **Matching graphics** | Use graphic elements that align with content theme |
| **Highlight core info** | White space draws attention to key information |
**Add to ALL prompts**:
> Clean composition with generous white space. Simple or no background. Main elements centered or positioned by content needs.
---
## Character Rendering
When depicting people:
| Guideline | Description |
|-----------|-------------|
| **Style** | Simplified cartoon silhouettes or symbolic expressions |
| **Avoid** | Realistic human portrayals, detailed faces |
| **Diversity** | Varied body types when showing multiple people |
| **Emotion** | Express through posture and simple gestures |
**Add to ALL prompts with human figures**:
> Human figures: simplified stylized silhouettes or symbolic representations, not photorealistic.
---
## Text in Illustrations
| Element | Guideline |
|---------|-----------|
| **Size** | Large, prominent, immediately readable |
| **Style** | Handwritten fonts preferred for warmth |
| **Content** | Concise keywords and core concepts only |
| **Language** | Match article language |
**Add to prompts with text**:
> Text should be large and prominent with handwritten-style fonts. Keep minimal, focus on keywords.
---
## Principles
Good prompts must include:
@@ -31,6 +128,13 @@ STYLE: [style characteristics]
ASPECT: 16:9
```
**Infographic + vector-illustration**:
```
Flat vector illustration infographic. Clean black outlines on all elements.
COLORS: Cream background (#F5F0E6), Coral Red (#E07A5F), Mint Green (#81B29A), Mustard Yellow (#F2CC8F)
ELEMENTS: Geometric simplified icons, no gradients, playful decorative elements (dots, stars)
```
### Scene
```
@@ -61,6 +165,13 @@ STYLE: [style characteristics]
ASPECT: 16:9
```
**Flowchart + vector-illustration**:
```
Flat vector flowchart with bold arrows and geometric step containers.
COLORS: Cream background (#F5F0E6), steps in Coral/Mint/Mustard, black outlines
ELEMENTS: Rounded rectangles, thick arrows, simple icons per step
```
### Comparison
```
@@ -79,6 +190,13 @@ STYLE: [style characteristics]
ASPECT: 16:9
```
**Comparison + vector-illustration**:
```
Flat vector comparison with split layout. Clear visual separation.
COLORS: Left side Coral (#E07A5F), Right side Mint (#81B29A), cream background
ELEMENTS: Bold icons, black outlines, centered divider line
```
### Framework
```
@@ -95,6 +213,13 @@ STYLE: [style characteristics]
ASPECT: 16:9
```
**Framework + vector-illustration**:
```
Flat vector framework diagram with geometric nodes and bold connectors.
COLORS: Cream background (#F5F0E6), nodes in Coral/Mint/Mustard/Blue, black outlines
ELEMENTS: Rounded rectangles or circles for nodes, thick connecting lines
```
### Timeline
```
@@ -1,10 +1,28 @@
# Style Reference
## Core Styles
Simplified style tier for quick selection:
| Core Style | Maps To | Best For |
|------------|---------|----------|
| `vector` | vector-illustration | Knowledge articles, tutorials, tech content |
| `minimal-flat` | notion | General, knowledge sharing, SaaS |
| `sci-fi` | blueprint | AI, frontier tech, system design |
| `hand-drawn` | sketch/warm | Relaxed, reflective, casual content |
| `editorial` | editorial | Processes, data, journalism |
| `scene` | warm/watercolor | Narratives, emotional, lifestyle |
Use Core Styles for most cases. See full Style Gallery below for granular control.
---
## Style Gallery
| Style | Description | Best For |
|-------|-------------|----------|
| `notion` (Default) | Minimalist hand-drawn line art | Knowledge sharing, SaaS, productivity |
| `vector-illustration` | Clean flat vector art with bold shapes | Knowledge articles, tutorials, tech content |
| `notion` | Minimalist hand-drawn line art | Knowledge sharing, SaaS, productivity |
| `elegant` | Refined, sophisticated | Business, thought leadership |
| `warm` | Friendly, approachable | Personal growth, lifestyle, education |
| `minimal` | Ultra-clean, zen-like | Philosophy, minimalism, core concepts |
@@ -12,19 +30,31 @@
| `watercolor` | Soft artistic with natural warmth | Lifestyle, travel, creative |
| `editorial` | Magazine-style infographic | Tech explainers, journalism |
| `scientific` | Academic precise diagrams | Biology, chemistry, technical research |
| `chalkboard` | Classroom chalk drawing style | Education, teaching, explanations |
| `fantasy-animation` | Ghibli/Disney-inspired hand-drawn | Storybook, magical, emotional |
| `flat` | Modern bold geometric shapes | Modern digital, contemporary |
| `flat-doodle` | Cute flat with bold outlines | Cute, friendly, approachable |
| `intuition-machine` | Technical briefing with aged paper | Technical briefings, academic |
| `nature` | Organic earthy illustration | Environmental, wellness |
| `pixel-art` | Retro 8-bit gaming aesthetic | Gaming, retro tech |
| `playful` | Whimsical pastel doodles | Fun, casual, educational |
| `retro` | 80s/90s neon geometric | 80s/90s nostalgic, bold |
| `sketch` | Raw pencil notebook style | Brainstorming, creative exploration |
| `sketch-notes` | Soft hand-drawn warm notes | Educational, warm notes |
| `vintage` | Aged parchment historical | Historical, heritage |
Full specifications: `references/styles/<style>.md`
## Type × Style Compatibility Matrix
| | notion | warm | minimal | blueprint | watercolor | elegant | editorial | scientific |
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| infographic | ✓✓ | ✓ | ✓✓ | ✓✓ | ✓ | ✓✓ | ✓✓ | ✓✓ |
| scene | ✓ | ✓✓ | ✓ | ✗ | ✓✓ | ✓ | ✓ | ✗ |
| flowchart | ✓✓ | ✓ | ✓ | ✓✓ | ✗ | ✓ | ✓✓ | ✓ |
| comparison | ✓✓ | ✓ | ✓✓ | ✓ | ✓ | ✓✓ | ✓✓ | ✓ |
| framework | ✓✓ | ✓ | ✓✓ | ✓✓ | ✗ | ✓✓ | ✓ | ✓✓ |
| timeline | ✓✓ | ✓ | ✓ | ✓ | ✓✓ | ✓✓ | ✓✓ | ✓ |
| | vector-illustration | notion | warm | minimal | blueprint | watercolor | elegant | editorial | scientific |
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| infographic | ✓✓ | ✓✓ | ✓ | ✓✓ | ✓✓ | ✓ | ✓✓ | ✓✓ | ✓✓ |
| scene | ✓ | ✓ | ✓✓ | ✓ | ✗ | ✓✓ | ✓ | ✓ | ✗ |
| flowchart | ✓✓ | ✓✓ | ✓ | ✓ | ✓✓ | ✗ | ✓ | ✓✓ | ✓ |
| comparison | ✓✓ | ✓✓ | ✓ | ✓✓ | ✓ | ✓ | ✓✓ | ✓✓ | ✓ |
| framework | ✓✓ | ✓✓ | ✓ | ✓✓ | ✓✓ | ✗ | ✓✓ | ✓ | ✓✓ |
| timeline | ✓ | ✓✓ | ✓ | ✓ | ✓ | ✓✓ | ✓✓ | ✓✓ | ✓ |
✓✓ = highly recommended | ✓ = compatible | ✗ = not recommended
@@ -32,30 +62,57 @@ Full specifications: `references/styles/<style>.md`
| Type | Primary Style | Secondary Styles |
|------|---------------|------------------|
| infographic | blueprint | notion, editorial, scientific |
| infographic | vector-illustration | notion, blueprint, editorial |
| scene | warm | watercolor, elegant |
| flowchart | notion | blueprint, editorial |
| comparison | notion | elegant, editorial |
| framework | blueprint | notion, scientific |
| flowchart | vector-illustration | notion, blueprint |
| comparison | vector-illustration | notion, elegant |
| framework | blueprint | vector-illustration, notion |
| timeline | elegant | warm, editorial |
## Auto Selection by Content Signals
| Content Signals | Recommended Type | Recommended Style |
|-----------------|------------------|-------------------|
| API, metrics, data, comparison, numbers | infographic | blueprint, notion |
| API, metrics, data, comparison, numbers | infographic | blueprint, vector-illustration |
| Knowledge, concept, tutorial, learning, guide | infographic | vector-illustration, notion |
| Tech, AI, programming, development, code | infographic | vector-illustration, blueprint |
| How-to, steps, workflow, process, tutorial | flowchart | vector-illustration, notion |
| Framework, model, architecture, principles | framework | blueprint, vector-illustration |
| vs, pros/cons, before/after, alternatives | comparison | vector-illustration, notion |
| Story, emotion, journey, experience, personal | scene | warm, watercolor |
| How-to, steps, workflow, process, tutorial | flowchart | notion, minimal |
| vs, pros/cons, before/after, alternatives | comparison | notion, elegant |
| Framework, model, architecture, principles | framework | blueprint, notion |
| History, timeline, progress, evolution | timeline | elegant, warm |
| Knowledge, concept, productivity, SaaS, tool | infographic | notion |
| Productivity, SaaS, tool, app, software | infographic | notion, vector-illustration |
| Business, professional, strategy, corporate | framework | elegant |
| Biology, chemistry, medical, scientific | infographic | scientific |
| Explainer, journalism, magazine, investigation | infographic | editorial |
## Style Characteristics by Type
### infographic + vector-illustration
- Clean flat vector shapes, bold geometric forms
- Vibrant but harmonious color palette
- Clear visual hierarchy with icons and labels
- Modern, professional, highly readable
- Perfect for knowledge articles and tutorials
### flowchart + vector-illustration
- Bold arrows and connectors
- Distinct step containers with icons
- Clean progression flow
- High contrast for readability
### comparison + vector-illustration
- Split layout with clear visual separation
- Bold iconography for each side
- Color-coded distinctions
- Easy at-a-glance comparison
### framework + vector-illustration
- Geometric node representations
- Clear hierarchical structure
- Bold connecting lines
- Modern system diagram aesthetic
### infographic + blueprint
- Technical precision, schematic lines
- Grid-based layout, clear zones
@@ -0,0 +1,355 @@
# Detailed Workflow Procedures
## Step 1: Pre-check
### 1.0 Detect & Save Reference Images ⚠️ REQUIRED if images provided
Check if user provided reference images. Handle based on input type:
| Input Type | Action |
|------------|--------|
| Image file path provided | Copy to `references/` subdirectory → can use `--ref` |
| Image in conversation (no path) | **ASK user for file path** with AskUserQuestion |
| User can't provide path | Extract style/palette verbally → append to prompts (NO frontmatter references) |
**CRITICAL**: Only add `references` to prompt frontmatter if files are ACTUALLY SAVED to `references/` directory.
**If user provides file path**:
1. Copy to `references/NN-ref-{slug}.png`
2. Create description: `references/NN-ref-{slug}.md`
3. Verify files exist before proceeding
**If user can't provide path** (extracted verbally):
1. Analyze image visually, extract: colors, style, composition
2. Create `references/extracted-style.md` with extracted info
3. DO NOT add `references` to prompt frontmatter
4. Instead, append extracted style/colors directly to prompt text
**Description File Format** (only when file saved):
```yaml
---
ref_id: NN
filename: NN-ref-{slug}.png
---
[User's description or auto-generated description]
```
**Verification** (only for saved files):
```
Reference Images Saved:
- 01-ref-{slug}.png ✓ (can use --ref)
- 02-ref-{slug}.png ✓ (can use --ref)
```
**Or for extracted style**:
```
Reference Style Extracted (no file):
- Colors: #E8756D coral, #7ECFC0 mint...
- Style: minimal flat vector, clean lines...
→ Will append to prompt text (not --ref)
```
---
### 1.1 Determine Input Type
| Input | Output Directory | Next |
|-------|------------------|------|
| File path | Ask user (1.2) | → 1.2 |
| Pasted content | `illustrations/{topic-slug}/` | → 1.4 |
**Backup rule for pasted content**: If `source.md` exists in target directory, rename to `source-backup-YYYYMMDD-HHMMSS.md` before saving.
### 1.2-1.4 Configuration (file path input only)
Check preferences and existing state, then ask ALL needed questions in ONE AskUserQuestion call (max 4 questions).
**Questions to include** (skip if preference exists or not applicable):
| Question | When to Ask | Options |
|----------|-------------|---------|
| Output directory | No `default_output_dir` in EXTEND.md | `{article-dir}/`, `{article-dir}/imgs/` (Recommended), `{article-dir}/illustrations/`, `illustrations/{topic-slug}/` |
| Existing images | Target dir has `.png/.jpg/.webp` files | `supplement`, `overwrite`, `regenerate` |
| Article update | Always (file path input) | `update`, `copy` |
**Preference Values** (if configured, skip asking):
| `default_output_dir` | Path |
|----------------------|------|
| `same-dir` | `{article-dir}/` |
| `imgs-subdir` | `{article-dir}/imgs/` |
| `illustrations-subdir` | `{article-dir}/illustrations/` |
| `independent` | `illustrations/{topic-slug}/` |
### 1.5 Load Preferences (EXTEND.md) ⛔ BLOCKING
**CRITICAL**: If EXTEND.md not found, MUST complete first-time setup before ANY other questions or steps. Do NOT proceed to reference images, do NOT ask about content, do NOT ask about type/style — ONLY complete the preferences setup first.
```bash
test -f .baoyu-skills/baoyu-article-illustrator/EXTEND.md && echo "project"
test -f "$HOME/.baoyu-skills/baoyu-article-illustrator/EXTEND.md" && echo "user"
```
| Result | Action |
|--------|--------|
| Found | Read, parse, display summary → Continue |
| Not found | ⛔ **BLOCKING**: Run first-time setup ONLY ([config/first-time-setup.md](config/first-time-setup.md)) → Complete and save EXTEND.md → Then continue |
**Supports**: Watermark | Preferred type/style | Custom styles | Language | Output directory
---
## Step 2: Setup & Analyze
### 2.1 Analyze Content
| Analysis | Description |
|----------|-------------|
| Content type | Technical / Tutorial / Methodology / Narrative |
| Illustration purpose | information / visualization / imagination |
| Core arguments | 2-5 main points to visualize |
| Visual opportunities | Positions where illustrations add value |
| Recommended type | Based on content signals and purpose |
| Recommended density | Based on length and complexity |
### 2.2 Extract Core Arguments
- Main thesis
- Key concepts reader needs
- Comparisons/contrasts
- Framework/model proposed
**CRITICAL**: If article uses metaphors (e.g., "电锯切西瓜"), do NOT illustrate literally. Visualize the **underlying concept**.
### 2.3 Identify Positions
**Illustrate**:
- Core arguments (REQUIRED)
- Abstract concepts
- Data comparisons
- Processes, workflows
**Do NOT Illustrate**:
- Metaphors literally
- Decorative scenes
- Generic illustrations
### 2.4 Analyze Reference Images (if provided in Step 1.0)
For each reference image:
| Analysis | Description |
|----------|-------------|
| Visual characteristics | Style, colors, composition |
| Content/subject | What the reference depicts |
| Suitable positions | Which sections match this reference |
| Style match | Which illustration types/styles align |
| Usage recommendation | `direct` / `style` / `palette` |
| Usage | When to Use |
|-------|-------------|
| `direct` | Reference matches desired output closely |
| `style` | Extract visual style characteristics only |
| `palette` | Extract color scheme only |
---
## Step 3: Confirm Settings ⚠️
**Do NOT skip.** Use ONE AskUserQuestion call with max 4 questions. **Q1, Q2, Q3 are ALL REQUIRED.**
### Q1: Illustration Type ⚠️ REQUIRED
- [Recommended based on analysis] (Recommended)
- infographic / scene / flowchart / comparison / framework / timeline / mixed
### Q2: Density ⚠️ REQUIRED - DO NOT SKIP
- minimal (1-2) - Core concepts only
- balanced (3-5) - Major sections
- per-section - At least 1 per section/chapter (Recommended)
- rich (6+) - Comprehensive coverage
### Q3: Style ⚠️ REQUIRED (ALWAYS ask, even with preferred_style in EXTEND.md)
If EXTEND.md has `preferred_style`:
- [Custom style name + brief description] (Recommended)
- [Top compatible core style 1]
- [Top compatible core style 2]
- Other (see full Style Gallery)
If no `preferred_style` (present Core Styles first):
- [Best compatible core style] (Recommended)
- [Other compatible core style 1]
- [Other compatible core style 2]
- Other (see full Style Gallery)
**Core Styles** (simplified selection):
| Core Style | Best For |
|------------|----------|
| `minimal-flat` | General, knowledge sharing, SaaS |
| `sci-fi` | AI, frontier tech, system design |
| `hand-drawn` | Relaxed, reflective, casual |
| `editorial` | Processes, data, journalism |
| `scene` | Narratives, emotional, lifestyle |
Style selection based on Type × Style compatibility matrix (styles.md).
Full specs: `styles/<style>.md`
### Q4: Image Text Language ⚠️ REQUIRED when article language ≠ EXTEND.md `language`
Detect article language from content. If different from EXTEND.md `language` setting, MUST ask:
- Article language (match article content) (Recommended)
- EXTEND.md language (user's general preference)
**Skip only if**: Article language matches EXTEND.md `language`, or EXTEND.md has no `language` setting.
### Display Reference Usage (if references detected in Step 1.0)
When presenting outline preview to user, show reference assignments:
```
Reference Images:
| Ref | Filename | Recommended Usage |
|-----|----------|-------------------|
| 01 | 01-ref-diagram.png | direct → Illustration 1, 3 |
| 02 | 02-ref-chart.png | palette → Illustration 2 |
```
---
## Step 4: Generate Outline
Save as `outline.md`:
```yaml
---
type: infographic
density: balanced
style: blueprint
image_count: 4
references: # Only if references provided
- ref_id: 01
filename: 01-ref-diagram.png
description: "Technical diagram showing system architecture"
- ref_id: 02
filename: 02-ref-chart.png
description: "Color chart with brand palette"
---
## Illustration 1
**Position**: [section] / [paragraph]
**Purpose**: [why this helps]
**Visual Content**: [what to show]
**Type Application**: [how type applies]
**References**: [01] # Optional: list ref_ids used
**Reference Usage**: direct # direct | style | palette
**Filename**: 01-infographic-concept-name.png
## Illustration 2
...
```
**Requirements**:
- Each position justified by content needs
- Type applied consistently
- Style reflected in descriptions
- Count matches density
- References assigned based on Step 2.4 analysis
---
## Step 5: Generate Images
### 5.1 Create Prompts
Follow [prompt-construction.md](prompt-construction.md). Save to `prompts/illustration-{slug}.md`.
- **Backup rule**: If prompt file exists, rename to `prompts/illustration-{slug}-backup-YYYYMMDD-HHMMSS.md`
**CRITICAL - References in Frontmatter**:
- Only add `references` field if files ACTUALLY EXIST in `references/` directory
- If style/palette was extracted verbally (no file), append info to prompt BODY instead
- Before writing frontmatter, verify: `test -f references/NN-ref-{slug}.png`
### 5.2 Select Generation Skill
Check available skills. If multiple, ask user.
### 5.3 Process References ⚠️ REQUIRED if references saved in Step 1.0
**DO NOT SKIP if user provided reference images.** For each illustration with references:
1. **VERIFY files exist first**:
```bash
test -f references/NN-ref-{slug}.png && echo "exists" || echo "MISSING"
```
- If file MISSING but in frontmatter → ERROR, fix frontmatter or remove references field
- If file exists → proceed with processing
2. Read prompt frontmatter for reference info
3. Process based on usage type:
| Usage | Action | Example |
|-------|--------|---------|
| `direct` | Add reference path to `--ref` parameter | `--ref references/01-ref-brand.png` |
| `style` | Analyze reference, append style traits to prompt | "Style: clean lines, gradient backgrounds..." |
| `palette` | Extract colors from reference, append to prompt | "Colors: #E8756D coral, #7ECFC0 mint..." |
4. Check image generation skill capability:
| Skill Supports `--ref` | Action |
|------------------------|--------|
| Yes (e.g., baoyu-image-gen with Google) | Pass reference images via `--ref` |
| No | Convert to text description, append to prompt |
**Verification**: Before generating, confirm reference processing:
```
Reference Processing:
- Illustration 1: using 01-ref-brand.png (direct) ✓
- Illustration 2: extracted palette from 02-ref-style.png ✓
```
### 5.4 Apply Watermark (if enabled)
Add: `Include a subtle watermark "[content]" at [position].`
### 5.5 Generate
1. For each illustration:
- **Backup rule**: If image file exists, rename to `NN-{type}-{slug}-backup-YYYYMMDD-HHMMSS.md`
- If references with `direct` usage: include `--ref` parameter
- Generate image
2. After each: "Generated X/N"
3. On failure: retry once, then log and continue
---
## Step 6: Finalize
### 6.1 Update Article
Insert after corresponding paragraph:
```markdown
![description](illustrations/{slug}/NN-{type}-{slug}.png)
```
Alt text: concise description in article's language.
### 6.2 Output Summary
```
Article Illustration Complete!
Article: [path]
Type: [type] | Density: [level] | Style: [style]
Location: [directory]
Images: X/N generated
Positions:
- 01-xxx.png → After "[Section]"
- 02-yyy.png → After "[Section]"
[If failures]
Failed:
- NN-zzz.png: [reason]
```
+28 -4
View File
@@ -153,7 +153,11 @@ Technical terms remain in English.
```
Comic Progress:
- [ ] Step 1: Setup & Analyze (1.1 Preferences, 1.2 Analyze, 1.3 Check existing)
- [ ] Step 1: Setup & Analyze
- [ ] 1.1 Preferences (EXTEND.md) ⛔ BLOCKING
- [ ] Found → load preferences → continue
- [ ] Not found → run first-time setup → MUST complete before other steps
- [ ] 1.2 Analyze, 1.3 Check existing
- [ ] Step 2: Confirmation - Style & options ⚠️ REQUIRED
- [ ] Step 3: Generate storyboard + characters
- [ ] Step 4: Review outline (conditional)
@@ -169,14 +173,22 @@ Comic Progress:
### Flow
```
Input → Preferences → Analyze → [Check Existing?] → [Confirm: Style + Reviews] → Storyboard → [Review?] → Prompts → [Review?] → Images → PDF → Complete
Input → [Preferences] ─┬─ Found → Continue
└─ Not found → First-Time Setup ⛔ BLOCKING
└─ Complete setup → Save EXTEND.md → Continue
┌─────────────────────────────────────────────────────────────────────┘
Analyze → [Check Existing?] → [Confirm: Style + Reviews] → Storyboard → [Review?] → Prompts → [Review?] → Images → PDF → Complete
```
### Step Summary
| Step | Action | Key Output |
|------|--------|------------|
| 1.1 | Load EXTEND.md preferences | Config loaded |
| 1.1 | Load EXTEND.md preferences ⛔ BLOCKING if not found | Config loaded |
| 1.2 | Analyze content | `analysis.md` |
| 1.3 | Check existing directory | Handle conflicts |
| 2 | Confirm style, focus, audience, reviews | User preferences |
@@ -194,6 +206,7 @@ Input → Preferences → Analyze → [Check Existing?] → [Confirm: Style + Re
**Character reference is MANDATORY for visual consistency.**
**7.1 Generate character sheet first**:
- **Backup rule**: If `characters/characters.png` exists, rename to `characters/characters-backup-YYYYMMDD-HHMMSS.png`
```bash
# Use Reference Sheet Prompt from characters/characters.md
npx -y bun ${SKILL_DIR}/../baoyu-image-gen/scripts/main.ts \
@@ -214,6 +227,10 @@ Compress to reduce token usage when used as reference image:
| Supports `--ref` | Pass `characters/characters.png` with EVERY page |
| No `--ref` support | Prepend character descriptions to EVERY prompt file |
**Backup rules for page generation**:
- If prompt file exists: rename to `prompts/NN-{cover|page}-[slug]-backup-YYYYMMDD-HHMMSS.md`
- If image file exists: rename to `NN-{cover|page}-[slug]-backup-YYYYMMDD-HHMMSS.png`
```bash
# Example: ALWAYS include --ref for consistency
npx -y bun ${SKILL_DIR}/../baoyu-image-gen/scripts/main.ts \
@@ -224,13 +241,20 @@ npx -y bun ${SKILL_DIR}/../baoyu-image-gen/scripts/main.ts \
**Full workflow details**: [references/workflow.md](references/workflow.md)
### EXTEND.md Paths
### EXTEND.md Paths ⛔ BLOCKING
**CRITICAL**: If EXTEND.md not found, MUST complete first-time setup before ANY other questions or steps. Do NOT proceed to content analysis, do NOT ask about art style, do NOT ask about tone — ONLY complete the preferences setup first.
| Path | Location |
|------|----------|
| `.baoyu-skills/baoyu-comic/EXTEND.md` | Project directory |
| `$HOME/.baoyu-skills/baoyu-comic/EXTEND.md` | User home |
| Result | Action |
|--------|--------|
| Found | Read, parse, display summary → Continue |
| Not found | ⛔ **BLOCKING**: Run first-time setup ONLY ([references/config/first-time-setup.md](references/config/first-time-setup.md)) → Complete and save EXTEND.md → Then continue |
**EXTEND.md Supports**: Watermark | Preferred art/tone/layout | Custom style definitions | Character presets | Language preference
Schema: [references/config/preferences-schema.md](references/config/preferences-schema.md)
@@ -9,6 +9,14 @@ description: First-time setup flow for baoyu-comic preferences
When no EXTEND.md is found, guide user through preference setup.
**⛔ BLOCKING OPERATION**: This setup MUST complete before ANY other workflow steps. Do NOT:
- Ask about content/source material
- Ask about art style or tone
- Ask about layout preferences
- Proceed to content analysis
ONLY ask the questions in this setup flow, save EXTEND.md, then continue.
## Setup Flow
```
+9 -5
View File
@@ -84,6 +84,7 @@ Read source content, save it if needed, and perform deep analysis.
1. **Save source content** (if not already a file):
- If user provides a file path: use as-is
- If user pastes content: save to `source.md` in target directory
- **Backup rule**: If `source.md` exists, rename to `source-backup-YYYYMMDD-HHMMSS.md`
2. Read source content
3. **Deep analysis** following `analysis-framework.md`:
- Target audience identification
@@ -329,6 +330,7 @@ Create image generation prompts for all pages.
1. Create prompt following art style + tone guidelines
2. Include character visual descriptions for consistency
3. Save to `prompts/NN-{cover|page}-[slug].md`
- **Backup rule**: If prompt file exists, rename to `prompts/NN-{cover|page}-[slug]-backup-YYYYMMDD-HHMMSS.md`
**Prompt File Format**:
```markdown
@@ -405,8 +407,9 @@ With confirmed prompts from Step 5/6:
### 7.1 Generate Character Reference Sheet (first)
1. Use Reference Sheet Prompt from `characters/characters.md`
2. Generate → `characters/characters.png`
3. This ensures visual consistency for all subsequent pages
2. **Backup rule**: If `characters/characters.png` exists, rename to `characters/characters-backup-YYYYMMDD-HHMMSS.png`
3. Generate → `characters/characters.png`
4. This ensures visual consistency for all subsequent pages
### 7.2 Generate Comic Pages
@@ -453,9 +456,10 @@ When skill does NOT support reference images, create combined prompt files:
**For each page (cover + pages)**:
1. Read prompt from `prompts/NN-{cover|page}-[slug].md`
2. Generate image using Strategy A or B (based on skill capability)
3. Save to `NN-{cover|page}-[slug].png`
4. Report progress after each generation: "Generated X/N: [page title]"
2. **Backup rule**: If image file exists, rename to `NN-{cover|page}-[slug]-backup-YYYYMMDD-HHMMSS.png`
3. Generate image using Strategy A or B (based on skill capability)
4. Save to `NN-{cover|page}-[slug].png`
5. Report progress after each generation: "Generated X/N: [page title]"
**Session Management**:
If image generation skill supports `--sessionId`:
+227 -20
View File
@@ -1,6 +1,6 @@
---
name: baoyu-cover-image
description: Generates article cover images with 5 dimensions (type, palette, rendering, text, mood) combining 9 color palettes and 6 rendering styles. Supports cinematic (2.35:1), widescreen (16:9), and square (1:1) aspects. Use when user asks to "generate cover image", "create article cover", "make cover", or mentions "封面图".
description: Generates article cover images with 5 dimensions (type, palette, rendering, text, mood) combining 9 color palettes and 6 rendering styles. Supports cinematic (2.35:1), widescreen (16:9), and square (1:1) aspects. Use when user asks to "generate cover image", "create article cover", or "make cover".
---
# Cover Image Generator
@@ -34,6 +34,10 @@ Generate elegant cover images for articles with 5-dimensional customization.
# Direct input with options
/baoyu-cover-image --palette mono --rendering digital --aspect 1:1 --quick
[paste content]
# With reference images
/baoyu-cover-image article.md --ref style-ref.png
/baoyu-cover-image article.md --ref ref1.png ref2.png --quick
```
## Options
@@ -50,6 +54,7 @@ Generate elegant cover images for articles with 5-dimensional customization.
| `--lang <code>` | Title language (en, zh, ja, etc.) |
| `--no-title` | Alias for `--text none` |
| `--quick` | Skip confirmation, use auto-selection for missing dimensions |
| `--ref <files...>` | Reference images for style/composition guidance |
## Five Dimensions
@@ -110,8 +115,8 @@ Rendering definitions: [references/renderings/](references/renderings/)
| Text Level | Title | Subtitle | Tags | Use Case |
|------------|:-----:|:--------:|:----:|----------|
| `none` | - | - | - | Pure visual, no text |
| `title-only` | ✓ (≤8 字) | - | - | Simple headline (default) |
| `title-subtitle` | ✓ | ✓ (≤15 字) | - | Title + supporting context |
| `title-only` | ✓ | - | - | Simple headline (default) |
| `title-subtitle` | ✓ | ✓ | - | Title + supporting context |
| `text-rich` | ✓ | ✓ | ✓ (2-4) | Information-dense |
| Mood | Contrast | Saturation | Weight | Use Case |
@@ -142,6 +147,12 @@ Output directory depends on `default_output_dir` preference:
```
<output-dir>/
├── source-{slug}.{ext} # Source files (text, images, etc.)
├── refs/ # Reference images (if provided)
│ ├── ref-01-{slug}.{ext}
│ ├── ref-01-{slug}.md # Description file (optional)
│ ├── ref-02-{slug}.{ext}
│ ├── ref-02-{slug}.md # Description file (optional)
│ └── extracted-style.md # Verbally extracted style (if no file path)
├── prompts/cover.md # Generation prompt
└── cover.png # Output image
```
@@ -156,25 +167,45 @@ Output directory depends on `default_output_dir` preference:
```
Cover Image Progress:
- [ ] Step 0: Check preferences (EXTEND.md) ⚠️ REQUIRED if not found
- [ ] Step 1: Analyze content + determine output directory ⚠️ MUST ask if not configured
- [ ] Step 0: Check preferences (EXTEND.md) ⛔ BLOCKING
- [ ] Found → load preferences → continue
- [ ] Not found → run first-time setup → MUST complete before Step 1
- [ ] Step 1: Analyze content + determine output directory
- [ ] 1.1 Reference images ⚠️ (if provided)
- [ ] File path given → saved to refs/ ✓
- [ ] No path → asked user OR extracted verbally
- [ ] 1.2 Output directory determined
- [ ] Step 2: Confirm options (5 dimensions) ⚠️ REQUIRED unless --quick or all specified
- [ ] Step 3: Create prompt
- [ ] References in prompt ONLY if files exist in refs/
- [ ] Extracted style/palette appended to prompt body (if no file)
- [ ] Step 4: Generate image
- [ ] 4.1 References verified before generation
- [ ] 4.2 Pass refs via --ref if skill supports AND files exist
- [ ] Step 5: Completion report
```
### Flow
```
Input → [Step 0: Preferences/Setup] → Analyze → [Output Dir ⚠️][Confirm: 5 Dimensions] → Prompt → Generate → Complete
(skip if --quick or all specified)
Input → [Step 0: Preferences] ─┬─ Found → Continue
└─ Not found → First-Time Setup ⛔ BLOCKING
└─ Complete setup → Save EXTEND.md → Continue
┌───────────────────────────────────────────────────────────────────────────┘
Analyze + Save Refs → [Output Dir ⚠️] → [Confirm: 5 Dimensions] → Prompt → Generate → Complete
(skip if --quick or all specified)
```
### Step 0: Load Preferences (EXTEND.md) ⚠️
### Step 0: Load Preferences (EXTEND.md) ⛔ BLOCKING
**Purpose**: Load user preferences or run first-time setup. **Do NOT skip setup if EXTEND.md not found.**
**Purpose**: Load user preferences or run first-time setup.
**CRITICAL**: If EXTEND.md not found, MUST complete first-time setup before ANY other questions or steps. Do NOT proceed to content analysis, do NOT ask about reference images, do NOT ask about dimensions — ONLY complete the preferences setup first.
Use Bash to check EXTEND.md existence (priority order):
@@ -189,7 +220,7 @@ test -f "$HOME/.baoyu-skills/baoyu-cover-image/EXTEND.md" && echo "user"
| Result | Action |
|--------|--------|
| Found | Read, parse, display preferences summary → Continue to Step 1 |
| Not found | ⚠️ MUST run first-time setup ([references/config/first-time-setup.md](references/config/first-time-setup.md)) → Then continue to Step 1 |
| Not found | **BLOCKING**: Run first-time setup ONLY ([references/config/first-time-setup.md](references/config/first-time-setup.md)) → Complete and save EXTEND.md → Then continue to Step 1 |
**Preferences Summary** (when found):
@@ -208,10 +239,90 @@ Schema: [references/config/preferences-schema.md](references/config/preferences-
### Step 1: Analyze Content
1. **Save source content** (if pasted, save to `source.md` in target directory; if file path, use as-is)
2. **Content analysis**: Extract topic, core message, tone, keywords; identify visual metaphors; detect content type
3. **Language detection**: Detect source language, note user's input language, compare with EXTEND.md preference
4. **Determine output directory** per File Structure rules. If no `default_output_dir` preference + file path input, include in Step 2 Q4
**1.0 Detect & Save Reference Images** ⚠️ REQUIRED if images provided
Check if user provided reference images. Handle based on input type:
| Input Type | Action |
|------------|--------|
| Image file path provided | Copy to `refs/` subdirectory → can use `--ref` |
| Image in conversation (no path) | **ASK user for file path** with AskUserQuestion |
| User can't provide path | Extract style/palette verbally → append to prompt (NO frontmatter references) |
**CRITICAL**: Only add `references` to prompt frontmatter if files are ACTUALLY SAVED to `refs/` directory.
**If user provides file path**:
1. Copy to `refs/ref-NN-{slug}.{ext}` (NN = 01, 02, ...)
2. Create description: `refs/ref-NN-{slug}.md`
3. Verify files exist before proceeding
**If user can't provide path** (extracted verbally):
1. Analyze image visually, extract: colors, style, composition
2. Create `refs/extracted-style.md` with extracted info
3. DO NOT add `references` to prompt frontmatter
4. Instead, append extracted style/colors directly to prompt text
**Description File Format** (only when file saved):
```yaml
---
ref_id: NN
filename: ref-NN-{slug}.{ext}
usage: direct | style | palette
---
[User's description or auto-generated description]
```
| Usage | When to Use |
|-------|-------------|
| `direct` | Reference matches desired output closely |
| `style` | Extract visual style characteristics only |
| `palette` | Extract color scheme only |
**Verification** (only for saved files):
```
Reference Images Saved:
- ref-01-{slug}.png ✓ (can use --ref)
- ref-02-{slug}.png ✓ (can use --ref)
```
**Or for extracted style**:
```
Reference Style Extracted (no file):
- Colors: #E8756D coral, #7ECFC0 mint...
- Style: minimal flat vector, clean lines...
→ Will append to prompt text (not --ref)
```
---
**1.1 Save Source Content**
- If pasted, save to `source.md` in target directory; if file path, use as-is
- **Backup rule**: If `source.md` exists, rename to `source-backup-YYYYMMDD-HHMMSS.md`
**1.2 Content Analysis**
- Extract topic, core message, tone, keywords
- Identify visual metaphors
- Detect content type
**1.3 Reference Image Analysis** (if provided in Step 1.0)
For each reference image:
| Analysis | Description |
|----------|-------------|
| Visual characteristics | Style, colors, composition |
| Content/subject | What the reference depicts |
| Style match | Which type/palette/rendering align |
| Usage recommendation | `direct` / `style` / `palette` |
**1.4 Language Detection**
- Detect source language
- Note user's input language
- Compare with EXTEND.md preference
**1.5 Determine Output Directory**
- Per File Structure rules
- If no `default_output_dir` preference + file path input, include in Step 2 Q4
### Step 2: Confirm Options ⚠️
@@ -226,14 +337,92 @@ Validate all 5 dimensions + aspect ratio. Full confirmation flow: [references/wo
### Step 3: Create Prompt
**Backup rule**: If `prompts/cover.md` exists, rename to `prompts/cover-backup-YYYYMMDD-HHMMSS.md`
Save to `prompts/cover.md`. Full template: [references/workflow/prompt-template.md](references/workflow/prompt-template.md)
**CRITICAL - References in YAML Frontmatter**:
When reference files are saved to `refs/`, **MUST add `references` field in frontmatter**:
```yaml
---
type: cover
palette: warm
rendering: flat-vector
references:
- ref_id: 01
filename: refs/ref-01-podcast-thumbnail.jpg
usage: style
---
```
| Rule | Action |
|------|--------|
| Files saved to `refs/` | Add to frontmatter `references` list |
| Style extracted verbally (no file) | Omit `references` field, describe in body |
| Before writing | Verify: `test -f refs/ref-NN-{slug}.{ext}` |
**Reference Embedding**:
| Situation | Frontmatter | Body |
|-----------|-------------|------|
| Reference file saved to `refs/` | Add to `references` ✓ | Brief style note |
| Style extracted verbally (no file) | Omit `references` | Full style description |
| File in frontmatter but doesn't exist | ERROR - fix or remove | — |
### Step 4: Generate Image
1. Backup existing `cover.png``cover-backup-YYYYMMDD-HHMMSS.png` (if regenerating)
2. Check available image generation skills; if multiple, ask user preference
3. Call selected skill with prompt file path, output path (`cover.png`), aspect ratio
4. On failure: auto-retry once before reporting error
**4.1 Backup existing** `cover.png``cover-backup-YYYYMMDD-HHMMSS.png` (if regenerating)
**4.2 Check available image generation skills**; if multiple, ask user preference
**4.3 Process References** ⚠️ REQUIRED if references in frontmatter
**Read `references` from prompt frontmatter** and process each entry:
1. **Parse frontmatter** to get references list:
```yaml
references:
- ref_id: 01
filename: refs/ref-01-podcast-thumbnail.jpg
usage: style
```
2. **VERIFY each file exists**:
```bash
test -f refs/ref-NN-{slug}.{ext} && echo "exists" || echo "MISSING"
```
- If file MISSING → ERROR, fix prompt or remove from references
- If file exists → proceed with processing
3. Process based on `usage` type:
| Usage | Action | Example |
|-------|--------|---------|
| `direct` | Add reference path to `--ref` parameter | `--ref refs/ref-01-brand.png` |
| `style` | Analyze reference, append style traits to prompt | "Style: clean lines, gradient backgrounds..." |
| `palette` | Extract colors from reference, append to prompt | "Colors: #E8756D coral, #7ECFC0 mint..." |
3. Check image generation skill capability:
| Skill Supports `--ref` | Action |
|------------------------|--------|
| Yes (e.g., baoyu-image-gen with Google) | Pass reference images via `--ref` |
| No | Convert to text description, append to prompt |
**Verification**: Before generating, confirm reference processing:
```
Reference Processing:
- ref-01-brand.png: using as direct reference ✓
- ref-02-style.png: extracted palette ✓
```
**4.4 Generate**
1. Call selected skill with prompt file path, output path (`cover.png`), aspect ratio
2. If references with `direct` usage AND skill supports `--ref`: include `--ref` parameter
3. On failure: auto-retry once before reporting error
### Step 5: Completion Report
@@ -245,10 +434,14 @@ Type: [type] | Palette: [palette] | Rendering: [rendering]
Text: [text] | Mood: [mood] | Aspect: [ratio]
Title: [title text or "visual only"]
Language: [lang] | Watermark: [enabled/disabled]
References: [N images (direct/style/palette) or "extracted style" or "none"]
Location: [directory path]
Files:
✓ source-{slug}.{ext}
[✓ refs/ref-01-{slug}.{ext} ... (if references saved)]
[✓ refs/ref-01-{slug}.md ... (description files)]
[✓ refs/extracted-style.md (if style extracted verbally)]
✓ prompts/cover.md
✓ cover.png
[✓ cover-backup-{timestamp}.png (if regenerated)]
@@ -269,7 +462,7 @@ All modifications automatically backup existing `cover.png` before regenerating.
- Cover must be readable at small preview sizes
- Visual metaphors > literal representations
- Title: max 8 chars, readable, impactful
- Title: readable, impactful
- Two confirmation points: Step 0 (first-time setup) + Step 2 (options) - skip Step 2 with `--quick`
- Use confirmed language for title text
- Maintain watermark consistency if enabled
@@ -277,6 +470,19 @@ All modifications automatically backup existing `cover.png` before regenerating.
- `--no-title` is alias for `--text none`
- `--style` presets are backward-compatible; explicit `--palette`/`--rendering` override preset values
### Composition Principles
- **Generous whitespace**: 40-60% breathing room; avoid cluttered layouts
- **Visual anchor**: Main element centered or offset left (reserve right for title)
- **Character handling**: Simplified silhouettes or icon-style figures; NO realistic humans
- **Icon vocabulary**: Use simple, recognizable symbols (see [references/visual-elements.md](references/visual-elements.md))
### Title Handling
- **Source**: Use the exact title provided by user, or extract from source content
- **Do NOT invent titles**: Stay faithful to the original
- If no title in source and user doesn't provide one, ask user to specify
## References
**Dimensions**: [text.md](references/dimensions/text.md) | [mood.md](references/dimensions/mood.md)
@@ -286,5 +492,6 @@ All modifications automatically backup existing `cover.png` before regenerating.
**Style Presets**: [references/style-presets.md](references/style-presets.md)
**Compatibility**: [references/compatibility.md](references/compatibility.md)
**Types**: [references/types.md](references/types.md)
**Visual Elements**: [references/visual-elements.md](references/visual-elements.md)
**Workflow**: [confirm-options.md](references/workflow/confirm-options.md) | [prompt-template.md](references/workflow/prompt-template.md)
**Config**: [preferences-schema.md](references/config/preferences-schema.md) | [first-time-setup.md](references/config/first-time-setup.md) | [watermark-guide.md](references/config/watermark-guide.md)
@@ -39,8 +39,8 @@ Apply the specified rendering's characteristics:
### Text (Density Level)
- `none`: No text elements, full visual area
- `title-only`: Single headline (≤8 characters), 85% visual area
- `title-subtitle`: Title + context (≤15 chars), 75% visual area
- `title-only`: Single headline, 85% visual area
- `title-subtitle`: Title + context, 75% visual area
- `text-rich`: Title + subtitle + 2-4 keyword tags, 60% visual area
### Mood (Emotional Intensity)
@@ -50,17 +50,44 @@ Apply the specified rendering's characteristics:
## Text Style (When Title Included)
- Title text: Large, eye-catching, max 8 characters
- Subtitle: Secondary, max 15 characters (if title-subtitle or text-rich)
- **Title source**: Use the exact title provided by user, or extract from source content. Do NOT invent or modify titles.
- Title text: Large, eye-catching, faithful to source
- Subtitle: Secondary element (if title-subtitle or text-rich)
- Tags: 2-4 keyword badges (if text-rich)
- Font style harmonizes with rendering style
- **Engagement hooks**: Use numbers ("3个关键"), questions ("Why?"), contrasts ("A vs B"), pain points ("别再X了"), or suspense ("隐藏的X") for compelling titles
## Composition Guidance
**Icon Vocabulary**: Represent concepts with simple, recognizable icons rather than detailed illustrations. Use the rendering style to determine icon complexity (flat-vector = geometric, hand-drawn = sketchy, etc.)
### Layout Principles
**Character Handling**: When people are needed, use simplified silhouettes or abstract representations. NO realistic faces, detailed anatomy, or photographic representations.
- **Generous whitespace**: Maintain 40-60% breathing room; avoid cluttered compositions
- **Visual anchor placement**: Main element centered or offset left (reserve right side for title if included)
- **Information hierarchy**: One dominant focal point, 1-2 supporting elements, decorative accents
- **Clean backgrounds**: Solid colors or subtle gradients; no complex textures or patterns
### Icon & Symbol Vocabulary
Represent concepts with simple, recognizable icons rather than detailed illustrations:
| Category | Examples |
|----------|----------|
| Tech | Code window, gear, circuit, cloud, lock, API brackets |
| Ideas | Lightbulb, rocket, target, puzzle, key, magnifier |
| Communication | Speech bubble, chat dots, megaphone, mail |
| Growth | Plant/sprout, tree, arrow, chart, mountain |
| Tools | Wrench, pencil, brush, checklist, clock |
Use the rendering style to determine icon complexity (flat-vector = geometric, hand-drawn = sketchy, etc.)
Full library: [references/visual-elements.md](visual-elements.md)
### Character Handling
When people are needed:
- Use simplified silhouettes or abstract stick figures
- Symbolic representations (head + shoulders outline)
- NO realistic faces, detailed anatomy, or photographic representations
- Cartoon/icon style consistent with rendering choice
## Mood Application
@@ -77,6 +104,16 @@ Apply mood adjustments to the base palette:
- Use the same language as the content provided below for any text elements
- Match punctuation style to the content language
## Reference Images
When reference images are provided:
- **Style extraction**: Identify rendering technique, line quality, texture, and visual vocabulary
- **Composition learning**: Note layout patterns, whitespace usage, element placement
- **Mood matching**: Capture the emotional tone and visual weight
- **Adaptation**: Apply extracted characteristics while respecting the specified Type, Palette, and Rendering dimensions
- **Priority**: If reference style conflicts with specified dimensions, dimensions take precedence for structural choices; reference influences decorative details
---
Please generate the cover image based on the content provided below:
@@ -9,6 +9,14 @@ description: First-time setup flow for baoyu-cover-image preferences
When no EXTEND.md is found, guide user through preference setup.
**⛔ BLOCKING OPERATION**: This setup MUST complete before ANY other workflow steps. Do NOT:
- Ask about reference images
- Ask about content/article
- Ask about dimensions (type, palette, rendering)
- Proceed to content analysis
ONLY ask the questions in this setup flow, save EXTEND.md, then continue.
## Setup Flow
```
@@ -103,8 +103,8 @@ custom_palettes:
| Value | Description |
|-------|-------------|
| `none` | Pure visual, no text elements |
| `title-only` | Single headline (≤8 characters) |
| `title-subtitle` | Title + subtitle (≤15 characters) |
| `title-only` | Single headline |
| `title-subtitle` | Title + subtitle |
| `text-rich` | Title + subtitle + keyword tags (2-4) |
## Mood Options
@@ -12,8 +12,8 @@ Controls text density and information hierarchy on cover images.
| Value | Title | Subtitle | Tags | Visual Area |
|-------|:-----:|:--------:|:----:|:-----------:|
| `none` | - | - | - | 100% |
| `title-only` | ✓ (≤8字) | - | - | 85% |
| `title-subtitle` | ✓ | ✓ (≤15字) | - | 75% |
| `title-only` | ✓ | - | - | 85% |
| `title-subtitle` | ✓ | ✓ | - | 75% |
| `text-rich` | ✓ | ✓ | ✓ (2-4) | 60% |
## Detail
@@ -43,24 +43,14 @@ Single headline, maximum impact.
- Strong brand recognition
**Composition**:
- Title: ≤8 characters, prominent
- Title: prominent placement
- Reserved zone: top or bottom 15%
- Visual supports title message
**Title Guidelines**:
- Punchy, action-oriented
- Use exact title from source content or user-provided title
- Do NOT invent or modify titles
- Match content language
- Use engagement hooks (see below)
**Engagement Hooks**:
| Hook Type | Examples (EN) | Examples (ZH) |
|-----------|---------------|---------------|
| Numbers | "3 Traps", "5 Keys" | "3个陷阱", "5个关键" |
| Questions | "Why X?", "How?" | "为什么X?", "怎么做?" |
| Contrasts | "A vs B", "Old→New" | "A还是B", "旧→新" |
| Pain Points | "Stop X", "Never Do" | "别再X了", "千万别" |
| Suspense | "Hidden X", "Secret" | "隐藏的X", "秘密" |
### title-subtitle
@@ -72,14 +62,14 @@ Title with supporting context.
- Content with dual messages
**Composition**:
- Title: ≤8 characters, primary
- Subtitle: ≤15 characters, secondary
- Title: primary element
- Subtitle: secondary element
- Reserved zone: 25%
- Clear hierarchy between title/subtitle
**Title Guidelines**:
- Use engagement hooks: numbers, questions, contrasts, pain points, suspense
- Punchy, action-oriented
- Use exact title from source content or user-provided title
- Do NOT invent or modify titles
**Subtitle Guidelines**:
- Clarify or contextualize title
@@ -104,8 +94,8 @@ Information-dense cover with multiple text elements.
- Clear visual hierarchy
**Title Guidelines**:
- Use engagement hooks: numbers, questions, contrasts, pain points, suspense
- Punchy, action-oriented
- Use exact title from source content or user-provided title
- Do NOT invent or modify titles
**Tag Guidelines**:
- 2-4 tags maximum
@@ -0,0 +1,101 @@
# Visual Elements Library
Icon and symbol vocabulary organized by topic. Use these as building blocks for cover compositions.
## Tech & Development
| Element | Use For |
|---------|---------|
| Code window / Terminal | Programming, development |
| Gear / Cog | Engineering, settings, process |
| Circuit board / Chip | Hardware, AI, computing |
| Binary / Data stream | Data, algorithms |
| API brackets `</>` | Web development, APIs |
| Cloud | Cloud computing, SaaS |
| Lock / Shield | Security, privacy |
| Network nodes | Distributed systems, connections |
## Ideas & Innovation
| Element | Use For |
|---------|---------|
| Lightbulb | Ideas, insights, innovation |
| Rocket | Launch, growth, startups |
| Target / Bullseye | Goals, precision, focus |
| Puzzle piece | Problem solving, integration |
| Key | Solutions, access, unlocking |
| Magnifying glass | Analysis, search, discovery |
| Chart / Graph | Data, trends, growth |
| Arrow / Path | Direction, journey, progress |
## Communication & Collaboration
| Element | Use For |
|---------|---------|
| Speech bubble | Communication, dialogue |
| Chat dots `...` | Conversation, messaging |
| Handshake | Partnership, agreement |
| Team / Figures | Collaboration, community |
| Mail / Envelope | Notifications, outreach |
| Megaphone | Announcements, marketing |
| Network / Web | Social, connections |
## Nature & Growth
| Element | Use For |
|---------|---------|
| Plant / Sprout | Growth, organic, sustainability |
| Tree | Established, structure, branching |
| Leaf | Eco, natural, fresh |
| Sun / Rays | Energy, positivity, new beginnings |
| Mountain | Challenge, achievement, scale |
| Wave | Flow, change, rhythm |
| Seed → Plant | Transformation, potential |
## Tools & Actions
| Element | Use For |
|---------|---------|
| Wrench / Hammer | Building, fixing, tools |
| Pencil / Pen | Writing, creation, editing |
| Brush | Design, creativity, art |
| Scissors | Cutting, editing, trimming |
| Clock / Timer | Time, scheduling, deadlines |
| Calendar | Planning, events, milestones |
| Checklist / Checkbox | Tasks, completion, validation |
## Abstract Concepts
| Element | Use For |
|---------|---------|
| Infinity ∞ | Continuous, endless, loops |
| Yin-yang | Balance, duality, harmony |
| Spiral | Evolution, recursion, cycles |
| Stack / Layers | Depth, hierarchy, structure |
| Bridge | Connection, transition, spanning |
| Door / Portal | Opportunity, entry, access |
| Mirror / Reflection | Self-improvement, analysis |
## Combination Patterns
Create visual metaphors by combining elements:
| Combination | Represents |
|-------------|------------|
| Lightbulb + Gear | Innovative engineering |
| Plant + Code | Organic tech growth |
| Rocket + Target | Precise acceleration |
| Key + Lock | Security solutions |
| Bridge + People | Team connections |
| Magnifier + Data | Analytics, insights |
## Rendering-Specific Treatment
| Rendering | Element Style |
|-----------|---------------|
| `flat-vector` | Geometric, simple shapes, uniform fills |
| `hand-drawn` | Sketchy, organic, doodle-like |
| `painterly` | Soft edges, brush strokes |
| `digital` | Precise, gradient hints, polished |
| `pixel` | 8-bit chunky, grid-aligned |
| `chalk` | Dusty, textured, board style |
@@ -3,6 +3,19 @@
Save to `prompts/cover.md`:
```markdown
---
type: cover
palette: [confirmed palette]
rendering: [confirmed rendering]
references:
- ref_id: 01
filename: refs/ref-01-{slug}.{ext}
usage: direct | style | palette
- ref_id: 02
filename: refs/ref-02-{slug}.{ext}
usage: direct | style | palette
---
# Content Context
Article title: [full original title from source]
Content summary: [2-3 sentence summary of key points and themes]
@@ -21,9 +34,9 @@ Language: [confirmed language]
# Text Elements
[Based on text level:]
- none: "No text elements"
- title-only: "Title: [max 8 chars headline]"
- title-subtitle: "Title: [headline] / Subtitle: [max 15 chars context]"
- text-rich: "Title: [headline] / Subtitle: [context] / Tags: [2-4 keywords]"
- title-only: "Title: [exact title from source or user]"
- title-subtitle: "Title: [title] / Subtitle: [context]"
- text-rich: "Title: [title] / Subtitle: [context] / Tags: [2-4 keywords]"
# Mood Application
[Based on mood level:]
@@ -46,6 +59,8 @@ Type notes: [key characteristics from type definition]
Palette notes: [key characteristics from palette definition]
[Watermark section if enabled]
[Reference images section if provided — see below]
```
## Content-Driven Design
@@ -54,6 +69,21 @@ Palette notes: [key characteristics from palette definition]
- Keywords guide decorative elements and symbols
- The skill controls visual style; the content drives meaning
## Visual Element Selection
Match content themes to icon vocabulary:
| Content Theme | Suggested Elements |
|---------------|-------------------|
| Programming/Dev | Code window, terminal, API brackets, gear |
| AI/ML | Brain, neural network, robot, circuit |
| Growth/Business | Chart, rocket, plant, mountain, arrow |
| Security | Lock, shield, key, fingerprint |
| Communication | Speech bubble, megaphone, mail, handshake |
| Tools/Methods | Wrench, checklist, pencil, puzzle |
Full library: [../visual-elements.md](../visual-elements.md)
## Type-Specific Composition
| Type | Composition Guidelines |
@@ -68,8 +98,8 @@ Palette notes: [key characteristics from palette definition]
## Title Guidelines
When text level includes title:
- Max 8 characters, punchy headline
- Use engagement hooks: numbers ("3个陷阱"), questions ("Why X?"), contrasts ("A vs B"), pain points ("别再X了")
- **Source**: Use the exact title provided by user, or extract from source content
- **Do NOT invent titles**: Stay faithful to the original
- Match confirmed language
## Watermark Application
@@ -82,3 +112,118 @@ The watermark should be legible but not distracting from the main content.
```
Reference: `config/watermark-guide.md`
## Reference Image Handling
When user provides reference images (`--ref` or pasted images):
### ⚠️ CRITICAL - Frontmatter References
**MUST add `references` field in YAML frontmatter** when reference files are saved to `refs/`:
```yaml
---
type: cover
palette: warm
rendering: flat-vector
references:
- ref_id: 01
filename: refs/ref-01-podcast-thumbnail.jpg
usage: style
---
```
| Field | Description |
|-------|-------------|
| `ref_id` | Sequential number (01, 02, ...) |
| `filename` | Relative path from prompt file's parent directory |
| `usage` | `direct` / `style` / `palette` |
**Omit `references` field entirely** if no reference files saved (style extracted verbally only).
### When to Include References in Frontmatter
| Situation | Frontmatter Action | Generation Action |
|-----------|-------------------|-------------------|
| Reference file saved to `refs/` | Add to `references` list ✓ | Pass via `--ref` parameter |
| Style extracted verbally (no file) | Omit `references` field | Describe in prompt body only |
| File path in frontmatter but doesn't exist | ERROR - fix or remove | Generation will fail |
**Before writing prompt with references, verify**: `test -f refs/ref-NN-{slug}.{ext}`
### Reference Usage Types
| Usage | When to Use | Generation Action |
|-------|-------------|-------------------|
| `direct` | Reference matches desired output closely | Pass to `--ref` parameter |
| `style` | Extract visual style characteristics only | Describe style in prompt text |
| `palette` | Extract color palette only | Include colors in prompt |
### Step 1: Analyze References
For each reference image, extract:
- **Style**: Rendering technique, line quality, texture
- **Composition**: Layout, visual hierarchy, focal points
- **Color mood**: Palette characteristics (without specific colors)
- **Elements**: Key visual elements and symbols used
### Step 2: Embed in Prompt
**If file saved AND skill supports `--ref`** (e.g., baoyu-image-gen with Google):
- Pass ref images directly via `--ref` parameter
- Add brief style note in prompt:
```
# Reference Style
Follow the visual style of the attached reference image(s):
- [1-2 sentence style summary from analysis]
```
**If file saved BUT skill does NOT support `--ref`**:
- Embed detailed text description in prompt:
```
# Reference Style (Text Description)
Emulate the following visual style from reference image(s):
Reference 1: [filename]
- Style: [detailed rendering technique description]
- Composition: [layout and hierarchy description]
- Elements: [key visual elements used]
- Mood: [emotional tone and visual weight]
[Repeat for additional references]
Apply these style characteristics to the cover design while maintaining the specified Type, Palette, and Rendering dimensions.
```
**If style/palette extracted verbally (NO file saved)**:
- DO NOT add references metadata to prompt
- Append extracted info directly to prompt body:
```
# Extracted Style (from reference)
COLORS (from reference):
- Primary: #E8756D coral
- Secondary: #7ECFC0 mint
...
STYLE (from reference):
- Clean lines, minimal shadows
- Gradient backgrounds
- Flat vector icons
...
```
### Reference Analysis Template
Use this format when analyzing reference images:
| Aspect | Analysis Points |
|--------|-----------------|
| **Rendering** | Line quality (clean/sketchy/painted), texture presence, depth treatment |
| **Composition** | Element placement, whitespace ratio, visual flow |
| **Typography** | Font style, text placement, hierarchy (if present) |
| **Elements** | Icon vocabulary, decorative motifs, character style |
| **Mood** | Contrast level, saturation, visual weight |
+54 -2
View File
@@ -13,8 +13,7 @@ Scripts in `scripts/` subdirectory. Replace `${SKILL_DIR}` with this SKILL.md's
| Script | Purpose |
|--------|---------|
| `scripts/main.ts` | Main entry point with CLI options |
| `scripts/cjk-emphasis.ts` | Fix CJK emphasis/bold punctuation issues |
| `scripts/main.ts` | Main entry point with CLI options (uses remark-cjk-friendly for CJK emphasis) |
| `scripts/quotes.ts` | Replace ASCII quotes with fullwidth quotes |
| `scripts/autocorrect.ts` | Add CJK/English spacing via autocorrect |
@@ -58,6 +57,55 @@ Claude performs content analysis and formatting (Steps 1-6), then runs the scrip
Read the user-specified markdown or plain text file.
### Step 1.5: Detect Content Type & Confirm
**Content Type Detection:**
| Indicator | Classification |
|-----------|----------------|
| Has `---` YAML frontmatter | Markdown |
| Has `#`, `##`, `###` headings | Markdown |
| Has `**bold**`, `*italic*` | Markdown |
| Has `- ` or `1. ` lists | Markdown |
| Has ``` code blocks | Markdown |
| Has `> ` blockquotes | Markdown |
| None of above | Plain text |
**Decision Flow:**
┌─────────────────┬────────────────────────────────────────────────┐
│ Content Type │ Action │
├─────────────────┼────────────────────────────────────────────────┤
│ Plain text │ Proceed to Step 2 (format to markdown) │
├─────────────────┼────────────────────────────────────────────────┤
│ Markdown │ Use AskUserQuestion to confirm optimization │
└─────────────────┴────────────────────────────────────────────────┘
**If Markdown detected, ask user:**
```
Detected existing markdown formatting. What would you like to do?
1. Optimize formatting (Recommended)
- Add/improve frontmatter, headings, bold, lists
- Run typography script (spacing, emphasis fixes)
- Output: {filename}-formatted.md
2. Keep original formatting
- Preserve existing markdown structure
- Run typography script (spacing, emphasis fixes)
- Output: {filename}-formatted.md
3. Typography fixes only
- Run typography script on original file in-place
- No copy created, modifies original file directly
```
**Based on user choice:**
- **Optimize**: Continue to Step 2-8 (full workflow)
- **Keep original**: Skip Steps 2-5, copy file → Step 6-8 (run script on copy)
- **Typography only**: Skip Steps 2-6, run Step 7 on original file directly
### Step 2: Analyze Content Structure
Identify:
@@ -138,6 +186,10 @@ Examples:
- `final.md``final-formatted.md`
- `draft-v1.md``draft-v1-formatted.md`
**If user chose "Keep original formatting" (from Step 1.5):**
- Copy original file to `{filename}-formatted.md` without modifications
- Proceed to Step 7 for typography fixes only
**Backup existing file:**
If `{filename}-formatted.md` already exists, backup before overwriting:
@@ -1,187 +0,0 @@
const CJK_PUNCT_COMMON = "。.,、?!:;";
const CJK_OPENING_PUNCT = "(〔〖〘〚「『〈《【\u201C\u2018";
const CJK_CLOSING_PUNCT = ")〕〗〙〛」』〉》】\u201D\u2019" + CJK_PUNCT_COMMON;
const CJK_SCRIPTS =
"\\p{Script=Han}\\p{Script=Hiragana}\\p{Script=Katakana}\\p{Script=Hangul}";
export const CJK_CLOSING_PUNCT_RE = new RegExp(`[${CJK_CLOSING_PUNCT}]`);
export const CJK_OPENING_PUNCT_RE = new RegExp(`^[${CJK_OPENING_PUNCT}]`);
export const CJK_CHAR_RE = new RegExp(`[${CJK_SCRIPTS}]`, "u");
const PUNCT_OR_SYMBOL_RE = /[\p{P}\p{S}]/u;
const WORD_CHAR_RE = /[\p{L}\p{N}]/u;
function findInlineCodeRanges(text: string): Array<[number, number]> {
const ranges: Array<[number, number]> = [];
let i = 0;
while (i < text.length) {
if (text[i] !== "`") {
i += 1;
continue;
}
let run = 1;
while (i + run < text.length && text[i + run] === "`") {
run += 1;
}
const start = i;
let j = i + run;
let found = false;
while (j < text.length) {
if (text[j] !== "`") {
j += 1;
continue;
}
let closeRun = 1;
while (j + closeRun < text.length && text[j + closeRun] === "`") {
closeRun += 1;
}
if (closeRun === run) {
ranges.push([start, j + closeRun - 1]);
i = j + closeRun;
found = true;
break;
}
j += closeRun;
}
if (!found) {
i = start + run;
}
}
return ranges;
}
function isEscaped(text: string, pos: number): boolean {
let count = 0;
for (let i = pos - 1; i >= 0 && text[i] === "\\"; i -= 1) {
count += 1;
}
return count % 2 === 1;
}
function isWhitespaceChar(ch: string | undefined): boolean {
return !ch || /\s/u.test(ch);
}
function isPunctuationOrSymbol(ch: string | undefined): boolean {
return !!ch && PUNCT_OR_SYMBOL_RE.test(ch);
}
function fixCjkEmphasisSpacingInBlock(block: string): string {
const codeRanges = findInlineCodeRanges(block);
let rangeIndex = 0;
const delimiters: Array<{
pos: number;
canOpen: boolean;
canClose: boolean;
}> = [];
let cursor = 0;
while (cursor < block.length - 1) {
if (rangeIndex < codeRanges.length && cursor >= codeRanges[rangeIndex][0]) {
if (cursor <= codeRanges[rangeIndex][1]) {
cursor = codeRanges[rangeIndex][1] + 1;
continue;
}
rangeIndex += 1;
continue;
}
if (
block[cursor] === "*" &&
block[cursor + 1] === "*" &&
block[cursor - 1] !== "*" &&
block[cursor + 2] !== "*" &&
!isEscaped(block, cursor)
) {
const before = block[cursor - 1];
const after = block[cursor + 2];
const beforeIsSpace = isWhitespaceChar(before);
const afterIsSpace = isWhitespaceChar(after);
const beforeIsPunct = isPunctuationOrSymbol(before);
const afterIsPunct = isPunctuationOrSymbol(after);
const leftFlanking =
!afterIsSpace && (!afterIsPunct || beforeIsSpace || beforeIsPunct);
const rightFlanking =
!beforeIsSpace && (!beforeIsPunct || afterIsSpace || afterIsPunct);
const cjkPunctBefore = !!before && CJK_CLOSING_PUNCT_RE.test(before);
const wordAfter = !!after && WORD_CHAR_RE.test(after);
delimiters.push({
pos: cursor,
canOpen: leftFlanking,
canClose: rightFlanking || (cjkPunctBefore && wordAfter),
});
cursor += 2;
continue;
}
cursor += 1;
}
const stack: Array<{ pos: number }> = [];
const pairs: Array<{ open: number; close: number }> = [];
for (const delimiter of delimiters) {
if (delimiter.canClose) {
let openerIndex = -1;
for (let j = stack.length - 1; j >= 0; j -= 1) {
openerIndex = j;
break;
}
if (openerIndex !== -1) {
const opener = stack.splice(openerIndex, 1)[0];
pairs.push({ open: opener.pos, close: delimiter.pos });
}
}
if (delimiter.canOpen) {
stack.push({ pos: delimiter.pos });
}
}
if (pairs.length === 0) return block;
const insertPositions = new Set<number>();
for (const pair of pairs) {
const insideLast = block[pair.close - 1];
const afterClose = block[pair.close + 2];
if (!afterClose) continue;
if (
CJK_CLOSING_PUNCT_RE.test(insideLast) &&
WORD_CHAR_RE.test(afterClose)
) {
insertPositions.add(pair.close + 2);
}
}
if (insertPositions.size === 0) return block;
let result = "";
for (let idx = 0; idx < block.length; idx += 1) {
if (insertPositions.has(idx)) {
result += " ";
}
result += block[idx];
}
if (insertPositions.has(block.length)) {
result += " ";
}
return result;
}
export function fixCjkEmphasisSpacing(content: string): string {
const parts = content.split(/(^```[\s\S]*?^```|^~~~[\s\S]*?^~~~)/m);
return parts
.map((part, i) => {
if (i % 2 === 1) return part;
const blocks = part.split(/(\n\s*\n+)/);
return blocks
.map((block, index) => {
if (index % 2 === 1) return block;
return fixCjkEmphasisSpacingInBlock(block);
})
.join("");
})
.join("");
}
+10 -55
View File
@@ -1,17 +1,12 @@
import { readFileSync, writeFileSync } from "fs";
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkCjkFriendly from "remark-cjk-friendly";
import remarkGfm from "remark-gfm";
import remarkFrontmatter from "remark-frontmatter";
import remarkStringify from "remark-stringify";
import { visit } from "unist-util-visit";
import YAML from "yaml";
import {
fixCjkEmphasisSpacing,
CJK_CLOSING_PUNCT_RE,
CJK_OPENING_PUNCT_RE,
CJK_CHAR_RE,
} from "./cjk-emphasis";
import { replaceQuotes } from "./quotes";
import { applyAutocorrect } from "./autocorrect";
@@ -36,6 +31,12 @@ const DEFAULT_OPTIONS: Required<FormatOptions> = {
emphasis: true,
};
function decodeHtmlEntities(text: string): string {
return text.replace(/&#x([0-9A-Fa-f]+);/g, (_, hex) =>
String.fromCodePoint(parseInt(hex, 16))
);
}
function formatFrontmatter(value: string): string | null {
try {
const doc = YAML.parseDocument(value);
@@ -49,12 +50,9 @@ function formatMarkdownContent(
content: string,
options: Required<FormatOptions>
): string {
if (options.emphasis) {
content = fixCjkEmphasisSpacing(content);
}
const processor = unified()
.use(remarkParse)
.use(options.emphasis ? remarkCjkFriendly : [])
.use(remarkGfm)
.use(remarkFrontmatter, ["yaml"])
.use(remarkStringify, {
@@ -63,7 +61,7 @@ function formatMarkdownContent(
const tree = processor.parse(content);
visit(tree, (node, _index, parent) => {
visit(tree, (node) => {
if (node.type === "text" && options.quotes) {
const textNode = node as { value: string };
textNode.value = replaceQuotes(textNode.value);
@@ -77,54 +75,11 @@ function formatMarkdownContent(
}
return;
}
if (
options.emphasis &&
(node.type === "strong" ||
node.type === "emphasis" ||
node.type === "delete") &&
parent
) {
const siblings = (parent as { children: typeof node[] }).children;
const idx = siblings.indexOf(node);
const children = (node as { children: typeof node[] }).children;
if (!children || children.length === 0) return;
const lastChild = children[children.length - 1];
if (lastChild.type === "text") {
const lastText = (lastChild as { value: string }).value;
if (
CJK_CLOSING_PUNCT_RE.test(lastText.slice(-1)) &&
idx + 1 < siblings.length
) {
const nextSib = siblings[idx + 1];
if (nextSib.type === "text") {
const nextText = (nextSib as { value: string }).value;
if (CJK_CHAR_RE.test(nextText.charAt(0))) {
(nextSib as { value: string }).value = " " + nextText;
}
}
}
}
const firstChild = children[0];
if (firstChild.type === "text") {
const firstText = (firstChild as { value: string }).value;
if (CJK_OPENING_PUNCT_RE.test(firstText) && idx > 0) {
const prevSib = siblings[idx - 1];
if (prevSib.type === "text") {
const prevText = (prevSib as { value: string }).value;
if (CJK_CHAR_RE.test(prevText.charAt(prevText.length - 1))) {
(prevSib as { value: string }).value = prevText + " ";
}
}
}
}
}
});
let result = processor.stringify(tree);
if (options.emphasis) {
result = fixCjkEmphasisSpacing(result);
result = decodeHtmlEntities(result);
}
return result;
}
File diff suppressed because it is too large Load Diff
@@ -1,5 +1,6 @@
{
"dependencies": {
"remark-cjk-friendly": "^1.1.0",
"remark-frontmatter": "^5.0.0",
"remark-gfm": "^4.0.1",
"remark-parse": "^11.0.0",
+17 -6
View File
@@ -153,10 +153,12 @@ Schema: `references/config/preferences-schema.md`
**1.2 Analyze Content → `analysis.md`**
1. Save source content (file path or paste → `source.md`)
- **Backup rule**: If `source.md` exists, rename to `source-backup-YYYYMMDD-HHMMSS.md`
2. Analyze: topic, data type, complexity, tone, audience
3. Detect source language and user language
4. Extract design instructions from user input
5. Save analysis
- **Backup rule**: If `analysis.md` exists, rename to `analysis-backup-YYYYMMDD-HHMMSS.md`
See `references/analysis-framework.md` for detailed format.
@@ -182,13 +184,20 @@ Recommend 3-5 layout×style combinations based on:
### Step 4: Confirm Options
Present all options in single confirmation:
1. **Combination** (always): 3+ options with rationale
2. **Aspect** (always): landscape/portrait/square
3. **Language** (only if source ≠ user language): which language for text
Use **single AskUserQuestion call** with multiple questions to confirm all options together:
| Question | When | Options |
|----------|------|---------|
| **Combination** | Always | 3+ layout×style combos with rationale |
| **Aspect** | Always | landscape (16:9), portrait (9:16), square (1:1) |
| **Language** | Only if source ≠ user language | Language for text content |
**Important**: Do NOT split into separate AskUserQuestion calls. Combine all applicable questions into one call.
### Step 5: Generate Prompt → `prompts/infographic.md`
**Backup rule**: If `prompts/infographic.md` exists, rename to `prompts/infographic-backup-YYYYMMDD-HHMMSS.md`
Combine:
1. Layout definition from `references/layouts/<layout>.md`
2. Style definition from `references/styles/<style>.md`
@@ -199,8 +208,10 @@ Combine:
### Step 6: Generate Image
1. Select available image generation skill (ask user if multiple)
2. Call with prompt file and output path
3. On failure, auto-retry once
2. **Check for existing file**: Before generating, check if `infographic.png` exists
- If exists: Rename to `infographic-backup-YYYYMMDD-HHMMSS.png`
3. Call with prompt file and output path
4. On failure, auto-retry once
### Step 7: Output Summary
@@ -214,7 +214,6 @@ export async function convertMarkdown(markdownPath: string, options?: { title?:
}
fs.copyFileSync(tempHtmlPath, finalHtmlPath);
console.error(`[markdown-to-html] HTML saved to: ${finalHtmlPath}`);
const contentImages: ImageInfo[] = [];
for (const img of images) {
@@ -226,6 +225,15 @@ export async function convertMarkdown(markdownPath: string, options?: { title?:
});
}
let htmlContent = fs.readFileSync(finalHtmlPath, 'utf-8');
for (const img of contentImages) {
const imgTag = `<img src="${img.placeholder}" data-local-path="${img.localPath}" style="display: block; width: 100%; margin: 1.5em auto;">`;
htmlContent = htmlContent.replace(img.placeholder, imgTag);
}
fs.writeFileSync(finalHtmlPath, htmlContent, 'utf-8');
console.error(`[markdown-to-html] HTML saved to: ${finalHtmlPath}`);
return {
title,
author,
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,14 @@
{
"dependencies": {
"fflate": "^0.8.2",
"front-matter": "^4.0.2",
"highlight.js": "^11.11.1",
"juice": "^11.0.1",
"marked": "^15.0.6",
"reading-time": "^1.5.0",
"remark-cjk-friendly": "^1.1.0",
"remark-parse": "^11.0.0",
"remark-stringify": "^11.0.0",
"unified": "^11.0.5"
}
}
@@ -7,6 +7,10 @@ import frontMatter from "front-matter";
import hljs from "highlight.js/lib/core";
import { marked, type RendererObject, type Tokens } from "marked";
import readingTime, { type ReadTimeResults } from "reading-time";
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkCjkFriendly from "remark-cjk-friendly";
import remarkStringify from "remark-stringify";
import {
markedAlert,
@@ -552,12 +556,47 @@ interface CliOptions {
keepTitle: boolean;
}
function preprocessCjkEmphasis(markdown: string): string {
const processor = unified()
.use(remarkParse)
.use(remarkCjkFriendly);
const tree = processor.parse(markdown);
const visit = (node: any, parent?: any, index?: number) => {
if (node.children) {
for (let i = 0; i < node.children.length; i++) {
visit(node.children[i], node, i);
}
}
if (node.type === "strong" && parent && typeof index === "number") {
const text = extractText(node);
parent.children[index] = { type: "html", value: `<strong>${text}</strong>` };
}
if (node.type === "emphasis" && parent && typeof index === "number") {
const text = extractText(node);
parent.children[index] = { type: "html", value: `<em>${text}</em>` };
}
};
const extractText = (node: any): string => {
if (node.type === "text") return node.value;
if (node.children) return node.children.map(extractText).join("");
return "";
};
visit(tree);
const stringify = unified().use(remarkStringify);
let result = stringify.stringify(tree);
result = result.replace(/&#x([0-9A-Fa-f]+);/g, (_, hex) =>
String.fromCodePoint(parseInt(hex, 16))
);
return result;
}
function renderMarkdown(raw: string, renderer: RendererAPI): {
html: string;
readingTime: ReadTimeResults;
} {
const preprocessed = preprocessCjkEmphasis(raw);
const { markdownContent, readingTime: readingTimeResult } =
renderer.parseFrontMatterAndContent(raw);
renderer.parseFrontMatterAndContent(preprocessed);
const html = marked.parse(markdownContent) as string;
+128 -23
View File
@@ -28,6 +28,18 @@ interface PublishResponse {
errmsg?: string;
}
type ArticleType = "news" | "newspic";
interface ArticleOptions {
title: string;
author?: string;
digest?: string;
content: string;
thumbMediaId: string;
articleType: ArticleType;
imageMediaIds?: string[];
}
const TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token";
const UPLOAD_URL = "https://api.weixin.qq.com/cgi-bin/material/add_material";
const DRAFT_URL = "https://api.weixin.qq.com/cgi-bin/draft/add";
@@ -176,19 +188,20 @@ async function uploadImagesInHtml(
html: string,
accessToken: string,
baseDir: string
): Promise<{ html: string; firstMediaId: string }> {
const imgRegex = /<img[^>]+src=["']([^"']+)["'][^>]*>/gi;
): Promise<{ html: string; firstMediaId: string; allMediaIds: string[] }> {
const imgRegex = /<img[^>]*\ssrc=["']([^"']+)["'][^>]*>/gi;
const matches = [...html.matchAll(imgRegex)];
if (matches.length === 0) {
return { html, firstMediaId: "" };
return { html, firstMediaId: "", allMediaIds: [] };
}
let firstMediaId = "";
let updatedHtml = html;
const allMediaIds: string[] = [];
for (const match of matches) {
const [, src] = match;
const [fullTag, src] = match;
if (!src) continue;
if (src.startsWith("https://mmbiz.qpic.cn")) {
@@ -198,40 +211,70 @@ async function uploadImagesInHtml(
continue;
}
console.error(`[wechat-api] Uploading image: ${src}`);
const localPathMatch = fullTag.match(/data-local-path=["']([^"']+)["']/);
const imagePath = localPathMatch ? localPathMatch[1]! : src;
console.error(`[wechat-api] Uploading image: ${imagePath}`);
try {
const resp = await uploadImage(src, accessToken, baseDir);
updatedHtml = updatedHtml.replace(src, resp.url);
const resp = await uploadImage(imagePath, accessToken, baseDir);
const newTag = fullTag
.replace(/\ssrc=["'][^"']+["']/, ` src="${resp.url}"`)
.replace(/\sdata-local-path=["'][^"']+["']/, "");
updatedHtml = updatedHtml.replace(fullTag, newTag);
allMediaIds.push(resp.media_id);
if (!firstMediaId) {
firstMediaId = resp.media_id;
}
} catch (err) {
console.error(`[wechat-api] Failed to upload ${src}:`, err);
console.error(`[wechat-api] Failed to upload ${imagePath}:`, err);
}
}
return { html: updatedHtml, firstMediaId };
return { html: updatedHtml, firstMediaId, allMediaIds };
}
async function publishToDraft(
title: string,
html: string,
thumbMediaId: string,
options: ArticleOptions,
accessToken: string
): Promise<PublishResponse> {
const url = `${DRAFT_URL}?access_token=${accessToken}`;
let article: Record<string, unknown>;
if (options.articleType === "newspic") {
if (!options.imageMediaIds || options.imageMediaIds.length === 0) {
throw new Error("newspic requires at least one image");
}
article = {
article_type: "newspic",
title: options.title,
content: options.content,
need_open_comment: 1,
only_fans_can_comment: 0,
image_info: {
image_list: options.imageMediaIds.map(id => ({ image_media_id: id })),
},
};
if (options.author) article.author = options.author;
} else {
article = {
article_type: "news",
title: options.title,
content: options.content,
thumb_media_id: options.thumbMediaId,
need_open_comment: 1,
only_fans_can_comment: 0,
};
if (options.author) article.author = options.author;
if (options.digest) article.digest = options.digest;
}
const res = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
articles: [{
title,
content: html,
thumb_media_id: thumbMediaId,
}],
}),
body: JSON.stringify({ articles: [article] }),
});
const data = await res.json() as PublishResponse;
@@ -309,12 +352,24 @@ Arguments:
file Markdown (.md) or HTML (.html) file
Options:
--type <type> Article type: news (, default) or newspic ()
--title <title> Override title
--author <name> Author name (max 16 chars)
--summary <text> Article summary/digest (max 128 chars)
--theme <name> Theme name for markdown (default, grace, simple). Default: default
--cover <path> Cover image path (local or URL)
--dry-run Parse and render only, don't publish
--help Show this help
Frontmatter Fields (markdown):
title Article title
author Author name
digest/summary Article summary
featureImage/coverImage/cover/image Cover image path
Comments:
Comments are enabled by default, open to all users.
Environment Variables:
WECHAT_APP_ID WeChat App ID
WECHAT_APP_SECRET WeChat App Secret
@@ -327,7 +382,9 @@ Config File Locations (in priority order):
Example:
npx -y bun wechat-api.ts article.md
npx -y bun wechat-api.ts article.md --theme grace --cover cover.png
npx -y bun wechat-api.ts article.md --author "Author Name" --summary "Brief intro"
npx -y bun wechat-api.ts article.html --title "My Article"
npx -y bun wechat-api.ts images/ --type newspic --title "Photo Album"
npx -y bun wechat-api.ts article.md --dry-run
`);
process.exit(0);
@@ -336,7 +393,10 @@ Example:
interface CliArgs {
filePath: string;
isHtml: boolean;
articleType: ArticleType;
title?: string;
author?: string;
summary?: string;
theme: string;
cover?: string;
dryRun: boolean;
@@ -350,20 +410,32 @@ function parseArgs(argv: string[]): CliArgs {
const args: CliArgs = {
filePath: "",
isHtml: false,
articleType: "news",
theme: "default",
dryRun: false,
};
for (let i = 0; i < argv.length; i++) {
const arg = argv[i]!;
if (arg === "--title" && argv[i + 1]) {
if (arg === "--type" && argv[i + 1]) {
const t = argv[++i]!.toLowerCase();
if (t === "news" || t === "newspic") {
args.articleType = t;
}
} else if (arg === "--title" && argv[i + 1]) {
args.title = argv[++i];
} else if (arg === "--author" && argv[i + 1]) {
args.author = argv[++i];
} else if (arg === "--summary" && argv[i + 1]) {
args.summary = argv[++i];
} else if (arg === "--theme" && argv[i + 1]) {
args.theme = argv[++i]!;
} else if (arg === "--cover" && argv[i + 1]) {
args.cover = argv[++i];
} else if (arg === "--dry-run") {
args.dryRun = true;
} else if (arg.startsWith("--") && argv[i + 1] && !argv[i + 1]!.startsWith("-")) {
i++;
} else if (!arg.startsWith("-")) {
args.filePath = arg;
}
@@ -398,6 +470,8 @@ async function main(): Promise<void> {
const baseDir = path.dirname(filePath);
let title = args.title || "";
let author = args.author || "";
let digest = args.summary || "";
let htmlPath: string;
let htmlContent: string;
let frontmatter: Record<string, string> = {};
@@ -405,6 +479,15 @@ async function main(): Promise<void> {
if (args.isHtml) {
htmlPath = filePath;
htmlContent = extractHtmlContent(htmlPath);
const mdPath = filePath.replace(/\.html$/i, ".md");
if (fs.existsSync(mdPath)) {
const mdContent = fs.readFileSync(mdPath, "utf-8");
const parsed = parseFrontmatter(mdContent);
frontmatter = parsed.frontmatter;
if (!title && frontmatter.title) title = frontmatter.title;
if (!author) author = frontmatter.author || "";
if (!digest) digest = frontmatter.digest || frontmatter.summary || frontmatter.description || "";
}
if (!title) {
title = extractHtmlTitle(fs.readFileSync(htmlPath, "utf-8"));
}
@@ -420,6 +503,8 @@ async function main(): Promise<void> {
const h1Match = body.match(/^#\s+(.+)$/m);
if (h1Match) title = h1Match[1]!;
}
if (!author) author = frontmatter.author || "";
if (!digest) digest = frontmatter.digest || frontmatter.summary || frontmatter.description || "";
console.error(`[wechat-api] Theme: ${args.theme}`);
htmlPath = renderMarkdownToHtml(filePath, args.theme);
@@ -433,10 +518,16 @@ async function main(): Promise<void> {
}
console.error(`[wechat-api] Title: ${title}`);
if (author) console.error(`[wechat-api] Author: ${author}`);
if (digest) console.error(`[wechat-api] Digest: ${digest.slice(0, 50)}...`);
console.error(`[wechat-api] Type: ${args.articleType}`);
if (args.dryRun) {
console.log(JSON.stringify({
articleType: args.articleType,
title,
author: author || undefined,
digest: digest || undefined,
htmlPath,
contentLength: htmlContent.length,
}, null, 2));
@@ -448,7 +539,7 @@ async function main(): Promise<void> {
const accessToken = await fetchAccessToken(config.appId, config.appSecret);
console.error("[wechat-api] Uploading images...");
const { html: processedHtml, firstMediaId } = await uploadImagesInHtml(
const { html: processedHtml, firstMediaId, allMediaIds } = await uploadImagesInHtml(
htmlContent,
accessToken,
baseDir
@@ -476,18 +567,32 @@ async function main(): Promise<void> {
}
}
if (!thumbMediaId) {
if (args.articleType === "news" && !thumbMediaId) {
console.error("Error: No cover image. Provide via --cover, frontmatter.featureImage, or include an image in content.");
process.exit(1);
}
if (args.articleType === "newspic" && allMediaIds.length === 0) {
console.error("Error: newspic requires at least one image in content.");
process.exit(1);
}
console.error("[wechat-api] Publishing to draft...");
const result = await publishToDraft(title, htmlContent, thumbMediaId, accessToken);
const result = await publishToDraft({
title,
author: author || undefined,
digest: digest || undefined,
content: htmlContent,
thumbMediaId,
articleType: args.articleType,
imageMediaIds: args.articleType === "newspic" ? allMediaIds : undefined,
}, accessToken);
console.log(JSON.stringify({
success: true,
media_id: result.media_id,
title,
articleType: args.articleType,
}, null, 2));
console.error(`[wechat-api] Published successfully! media_id: ${result.media_id}`);
@@ -115,7 +115,7 @@ async function sendPaste(cdp?: CdpConnection, sessionId?: string): Promise<void>
}
}
async function copyHtmlFromBrowser(cdp: CdpConnection, htmlFilePath: string): Promise<void> {
async function copyHtmlFromBrowser(cdp: CdpConnection, htmlFilePath: string, contentImages: ImageInfo[] = []): Promise<void> {
const absolutePath = path.isAbsolute(htmlFilePath) ? htmlFilePath : path.resolve(process.cwd(), htmlFilePath);
const fileUrl = `file://${absolutePath}`;
@@ -128,6 +128,28 @@ async function copyHtmlFromBrowser(cdp: CdpConnection, htmlFilePath: string): Pr
await cdp.send('Runtime.enable', {}, { sessionId });
await sleep(2000);
if (contentImages.length > 0) {
console.log('[wechat] Replacing img tags with placeholders for browser paste...');
const replacements = contentImages.map(img => ({ placeholder: img.placeholder, localPath: img.localPath }));
await cdp.send<{ result: { value: unknown } }>('Runtime.evaluate', {
expression: `
(function() {
const replacements = ${JSON.stringify(replacements)};
for (const r of replacements) {
const imgs = document.querySelectorAll('img[src="' + r.placeholder + '"], img[data-local-path="' + r.localPath + '"]');
for (const img of imgs) {
const text = document.createTextNode(r.placeholder);
img.parentNode.replaceChild(text, img);
}
}
return true;
})()
`,
returnByValue: true,
}, { sessionId });
await sleep(500);
}
console.log('[wechat] Selecting #output content...');
await cdp.send<{ result: { value: unknown } }>('Runtime.evaluate', {
expression: `
@@ -176,7 +198,7 @@ async function parseMarkdownWithPlaceholders(markdownPath: string, theme?: strin
return JSON.parse(output);
}
function parseHtmlMeta(htmlPath: string): { title: string; author: string; summary: string } {
function parseHtmlMeta(htmlPath: string): { title: string; author: string; summary: string; contentImages: ImageInfo[] } {
const content = fs.readFileSync(htmlPath, 'utf-8');
let title = '';
@@ -203,7 +225,45 @@ function parseHtmlMeta(htmlPath: string): { title: string; author: string; summa
}
}
return { title, author, summary };
const mdPath = htmlPath.replace(/\.html$/i, '.md');
if (fs.existsSync(mdPath)) {
const mdContent = fs.readFileSync(mdPath, 'utf-8');
const fmMatch = mdContent.match(/^---\r?\n([\s\S]*?)\r?\n---/);
if (fmMatch) {
const lines = fmMatch[1]!.split('\n');
for (const line of lines) {
const colonIdx = line.indexOf(':');
if (colonIdx > 0) {
const key = line.slice(0, colonIdx).trim();
let value = line.slice(colonIdx + 1).trim();
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
value = value.slice(1, -1);
}
if (key === 'title' && !title) title = value;
if (key === 'author' && !author) author = value;
if ((key === 'description' || key === 'summary') && !summary) summary = value;
}
}
}
}
const contentImages: ImageInfo[] = [];
const imgRegex = /<img[^>]*\ssrc=["']([^"']+)["'][^>]*>/gi;
const matches = [...content.matchAll(imgRegex)];
for (const match of matches) {
const [fullTag, src] = match;
if (!src || src.startsWith('http')) continue;
const localPathMatch = fullTag.match(/data-local-path=["']([^"']+)["']/);
if (localPathMatch) {
contentImages.push({
placeholder: src,
localPath: localPathMatch[1]!,
originalPath: src,
});
}
}
return { title, author, summary, contentImages };
}
async function selectAndReplacePlaceholder(session: ChromeSession, placeholder: string): Promise<boolean> {
@@ -273,9 +333,13 @@ export async function postArticle(options: ArticleOptions): Promise<void> {
effectiveAuthor = effectiveAuthor || meta.author;
effectiveSummary = effectiveSummary || meta.summary;
effectiveHtmlFile = htmlFile;
if (meta.contentImages.length > 0) {
contentImages = meta.contentImages;
}
console.log(`[wechat] Title: ${effectiveTitle || '(empty)'}`);
console.log(`[wechat] Author: ${effectiveAuthor || '(empty)'}`);
console.log(`[wechat] Summary: ${effectiveSummary || '(empty)'}`);
console.log(`[wechat] Found ${contentImages.length} images to insert`);
}
if (effectiveTitle && effectiveTitle.length > 64) throw new Error(`Title too long: ${effectiveTitle.length} chars (max 64)`);
@@ -416,7 +480,7 @@ export async function postArticle(options: ArticleOptions): Promise<void> {
if (effectiveHtmlFile && fs.existsSync(effectiveHtmlFile)) {
console.log(`[wechat] Copying HTML content from: ${effectiveHtmlFile}`);
await copyHtmlFromBrowser(cdp, effectiveHtmlFile);
await copyHtmlFromBrowser(cdp, effectiveHtmlFile, contentImages);
await sleep(500);
console.log('[wechat] Pasting into editor...');
await pasteFromClipboardInEditor(session);
+104 -41
View File
@@ -61,6 +61,25 @@ interface ArticleOptions {
chromePath?: string;
}
async function findExistingDebugPort(profileDir: string): Promise<number | null> {
const portFile = path.join(profileDir, 'DevToolsActivePort');
if (!fs.existsSync(portFile)) return null;
try {
const content = fs.readFileSync(portFile, 'utf-8').trim();
if (!content) return null;
const [portLine] = content.split(/\r?\n/);
const port = Number(portLine);
if (!Number.isFinite(port) || port <= 0) return null;
// Verify the port is actually active.
await waitForChromeDebugPort(port, 1500, { includeLastError: true });
return port;
} catch {
return null;
}
}
export async function publishArticle(options: ArticleOptions): Promise<void> {
const { markdownPath, submit = false, profileDir = getDefaultProfileDir() } = options;
@@ -83,28 +102,33 @@ export async function publishArticle(options: ArticleOptions): Promise<void> {
if (!chromePath) throw new Error('Chrome not found');
await mkdir(profileDir, { recursive: true });
const port = await getFreePort();
const existingPort = await findExistingDebugPort(profileDir);
const port = existingPort ?? await getFreePort();
console.log(`[x-article] Launching Chrome...`);
const chrome = spawn(chromePath, [
`--remote-debugging-port=${port}`,
`--user-data-dir=${profileDir}`,
'--no-first-run',
'--no-default-browser-check',
'--disable-blink-features=AutomationControlled',
'--start-maximized',
X_ARTICLES_URL,
], { stdio: 'ignore' });
if (existingPort) {
console.log(`[x-article] Reusing existing Chrome instance on port ${port}`);
} else {
console.log(`[x-article] Launching Chrome...`);
spawn(chromePath, [
`--remote-debugging-port=${port}`,
`--user-data-dir=${profileDir}`,
'--no-first-run',
'--no-default-browser-check',
'--disable-blink-features=AutomationControlled',
'--start-maximized',
X_ARTICLES_URL,
], { stdio: 'ignore' });
}
let cdp: CdpConnection | null = null;
try {
const wsUrl = await waitForChromeDebugPort(port, 30_000);
const wsUrl = await waitForChromeDebugPort(port, 30_000, { includeLastError: true });
cdp = await CdpConnection.connect(wsUrl, 30_000, { defaultTimeoutMs: 30_000 });
// Get page target
const targets = await cdp.send<{ targetInfos: Array<{ targetId: string; url: string; type: string }> }>('Target.getTargets');
let pageTarget = targets.targetInfos.find((t) => t.type === 'page' && t.url.includes('x.com'));
let pageTarget = targets.targetInfos.find((t) => t.type === 'page' && t.url.startsWith(X_ARTICLES_URL));
if (!pageTarget) {
const { targetId } = await cdp.send<{ targetId: string }>('Target.createTarget', { url: X_ARTICLES_URL });
@@ -409,15 +433,23 @@ export async function publishArticle(options: ArticleOptions): Promise<void> {
console.log('[x-article] Checking for placeholders in content...');
for (const img of parsed.contentImages) {
if (editorContent.result.value.includes(img.placeholder)) {
// Use regex for exact match (not followed by digit, e.g., XIMGPH_1 should not match XIMGPH_10)
const regex = new RegExp(img.placeholder + '(?!\\d)');
if (regex.test(editorContent.result.value)) {
console.log(`[x-article] Found: ${img.placeholder}`);
} else {
console.log(`[x-article] NOT found: ${img.placeholder}`);
}
}
// Process images in sequential order (1, 2, 3, ...)
const sortedImages = [...parsed.contentImages].sort((a, b) => a.blockIndex - b.blockIndex);
// Process images in XIMGPH order (1, 2, 3, ...) regardless of blockIndex
const getPlaceholderIndex = (placeholder: string): number => {
const match = placeholder.match(/XIMGPH_(\d+)/);
return match ? Number(match[1]) : Number.POSITIVE_INFINITY;
};
const sortedImages = [...parsed.contentImages].sort(
(a, b) => getPlaceholderIndex(a.placeholder) - getPlaceholderIndex(b.placeholder),
);
for (let i = 0; i < sortedImages.length; i++) {
const img = sortedImages[i]!;
@@ -440,22 +472,30 @@ export async function publishArticle(options: ArticleOptions): Promise<void> {
while ((node = walker.nextNode())) {
const text = node.textContent || '';
const idx = text.indexOf(placeholder);
if (idx !== -1) {
// Found the placeholder - scroll to it first
const parentElement = node.parentElement;
if (parentElement) {
parentElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
let searchStart = 0;
let idx;
// Search for exact match (not prefix of longer placeholder like XIMGPH_1 in XIMGPH_10)
while ((idx = text.indexOf(placeholder, searchStart)) !== -1) {
const afterIdx = idx + placeholder.length;
const charAfter = text[afterIdx];
// Exact match if next char is not a digit (XIMGPH_1 should not match XIMGPH_10)
if (charAfter === undefined || !/\\d/.test(charAfter)) {
// Found exact placeholder - scroll to it first
const parentElement = node.parentElement;
if (parentElement) {
parentElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
// Select it
const range = document.createRange();
range.setStart(node, idx);
range.setEnd(node, idx + placeholder.length);
const sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
return true;
// Select it
const range = document.createRange();
range.setStart(node, idx);
range.setEnd(node, idx + placeholder.length);
const sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
return true;
}
searchStart = afterIdx;
}
}
return false;
@@ -505,31 +545,54 @@ export async function publishArticle(options: ArticleOptions): Promise<void> {
// Wait for clipboard to be fully ready
await sleep(1000);
// Delete placeholder by pressing Backspace (more reliable than Enter for replacing selection)
// Delete placeholder using execCommand (more reliable than keyboard events for DraftJS)
console.log(`[x-article] Deleting placeholder...`);
await cdp.send('Input.dispatchKeyEvent', { type: 'keyDown', key: 'Backspace', code: 'Backspace', windowsVirtualKeyCode: 8 }, { sessionId });
await cdp.send('Input.dispatchKeyEvent', { type: 'keyUp', key: 'Backspace', code: 'Backspace', windowsVirtualKeyCode: 8 }, { sessionId });
const deleteResult = await cdp.send<{ result: { value: boolean } }>('Runtime.evaluate', {
expression: `(() => {
const sel = window.getSelection();
if (!sel || sel.isCollapsed) return false;
// Try execCommand delete first
if (document.execCommand('delete', false)) return true;
// Fallback: replace selection with empty using insertText
document.execCommand('insertText', false, '');
return true;
})()`,
returnByValue: true,
}, { sessionId });
// Wait and verify placeholder is deleted
await sleep(500);
// Check that placeholder is no longer in editor
// Check that placeholder is no longer in editor (exact match, not substring)
const afterDelete = await cdp.send<{ result: { value: boolean } }>('Runtime.evaluate', {
expression: `(() => {
const editor = document.querySelector('.DraftEditor-editorContainer [data-contents="true"]');
if (!editor) return true;
return !editor.innerText.includes(${JSON.stringify(img.placeholder)});
const text = editor.innerText;
const placeholder = ${JSON.stringify(img.placeholder)};
// Use regex to find exact match (not followed by digit)
const regex = new RegExp(placeholder + '(?!\\\\d)');
return !regex.test(text);
})()`,
returnByValue: true,
}, { sessionId });
if (!afterDelete.result.value) {
console.warn(`[x-article] Placeholder may not have been deleted, trying again...`);
// Try selecting and deleting again
console.warn(`[x-article] Placeholder may not have been deleted, trying dispatchEvent...`);
// Try selecting and deleting with InputEvent
await selectPlaceholder(1);
await sleep(300);
await cdp.send('Input.dispatchKeyEvent', { type: 'keyDown', key: 'Backspace', code: 'Backspace', windowsVirtualKeyCode: 8 }, { sessionId });
await cdp.send('Input.dispatchKeyEvent', { type: 'keyUp', key: 'Backspace', code: 'Backspace', windowsVirtualKeyCode: 8 }, { sessionId });
await cdp.send('Runtime.evaluate', {
expression: `(() => {
const editor = document.querySelector('.DraftEditor-editorContainer [contenteditable="true"]');
if (!editor) return;
editor.focus();
// Dispatch beforeinput and input events for deletion
const beforeEvent = new InputEvent('beforeinput', { inputType: 'deleteContentBackward', bubbles: true, cancelable: true });
editor.dispatchEvent(beforeEvent);
const inputEvent = new InputEvent('input', { inputType: 'deleteContentBackward', bubbles: true });
editor.dispatchEvent(inputEvent);
})()`,
}, { sessionId });
await sleep(500);
}
+5 -1
View File
@@ -226,6 +226,7 @@ Schema: `references/config/preferences-schema.md`
**1.2 Analyze Content**
1. Save source content (if pasted, save as `source.md`)
- **Backup rule**: If `source.md` exists, rename to `source-backup-YYYYMMDD-HHMMSS.md`
2. Follow `references/analysis-framework.md` for content analysis
3. Analyze content signals for style recommendations
4. Detect source language
@@ -483,6 +484,7 @@ options:
- Add slide-specific content
- If `Layout:` specified, include layout guidance from `references/layouts.md`
3. Save to `prompts/` directory
- **Backup rule**: If prompt file exists, rename to `prompts/NN-slide-{slug}-backup-YYYYMMDD-HHMMSS.md`
**After generation**:
- If `--prompts-only`, stop here and output prompt summary
@@ -539,7 +541,9 @@ options:
**Standard flow**:
1. Select available image generation skill
2. Generate session ID: `slides-{topic-slug}-{timestamp}`
3. Generate each slide sequentially with same session ID
3. For each slide:
- **Backup rule**: If image file exists, rename to `NN-slide-{slug}-backup-YYYYMMDD-HHMMSS.png`
- Generate image sequentially with same session ID
4. Report progress: "Generated X/N" (in user's language)
5. Auto-retry once on failure before reporting error
+45 -25
View File
@@ -1,6 +1,6 @@
---
name: baoyu-xhs-images
description: Generates Xiaohongshu (Little Red Book) infographic series with 9 visual styles and 6 layouts. Breaks content into 1-10 cartoon-style images optimized for XHS engagement. Use when user mentions "小红书图片", "XHS images", "RedNote infographics", "小红书种草", or wants social media infographics for Chinese platforms.
description: Generates Xiaohongshu (Little Red Book) infographic series with 10 visual styles and 8 layouts. Breaks content into 1-10 cartoon-style images optimized for XHS engagement. Use when user mentions "小红书图片", "XHS images", "RedNote infographics", "小红书种草", or wants social media infographics for Chinese platforms.
---
# Xiaohongshu Infographic Series Generator
@@ -42,8 +42,8 @@ Break down complex content into eye-catching infographic series for Xiaohongshu
| Dimension | Controls | Options |
|-----------|----------|---------|
| **Style** | Visual aesthetics: colors, lines, decorations | cute, fresh, warm, bold, minimal, retro, pop, notion, chalkboard |
| **Layout** | Information structure: density, arrangement | sparse, balanced, dense, list, comparison, flow |
| **Style** | Visual aesthetics: colors, lines, decorations | cute, fresh, warm, bold, minimal, retro, pop, notion, chalkboard, study-notes |
| **Layout** | Information structure: density, arrangement | sparse, balanced, dense, list, comparison, flow, mindmap, quadrant |
Style × Layout can be freely combined. Example: `--style notion --layout dense` creates an intellectual-looking knowledge card with high information density.
@@ -60,6 +60,7 @@ Style × Layout can be freely combined. Example: `--style notion --layout dense`
| `pop` | Vibrant, energetic, eye-catching |
| `notion` | Minimalist hand-drawn line art, intellectual |
| `chalkboard` | Colorful chalk on black board, educational |
| `study-notes` | Realistic handwritten photo style, blue pen + red annotations + yellow highlighter |
Detailed style definitions: `references/presets/<style>.md`
@@ -73,6 +74,8 @@ Detailed style definitions: `references/presets/<style>.md`
| `list` | Enumeration and ranking format (4-7 items) |
| `comparison` | Side-by-side contrast layout |
| `flow` | Process and timeline layout (3-6 steps) |
| `mindmap` | Center radial mind map layout (4-8 branches) |
| `quadrant` | Four-quadrant / circular section layout |
Detailed layout definitions: `references/elements/canvas.md`
@@ -89,6 +92,7 @@ Detailed layout definitions: `references/elements/canvas.md`
| Fun, exciting, wow, amazing | `pop` | sparse/list |
| Knowledge, concept, productivity, SaaS | `notion` | dense/list |
| Education, tutorial, learning, teaching, classroom | `chalkboard` | balanced/dense |
| Notes, handwritten, study guide, knowledge, realistic, photo | `study-notes` | dense/list/mindmap |
## Outline Strategies
@@ -164,7 +168,9 @@ Copy and track progress:
```
XHS Infographic Progress:
- [ ] Step 0: Check preferences (EXTEND.md) ⚠️ REQUIRED if not found
- [ ] Step 0: Check preferences (EXTEND.md) ⛔ BLOCKING
- [ ] Found → load preferences → continue
- [ ] Not found → run first-time setup → MUST complete before Step 1
- [ ] Step 1: Analyze content → analysis.md
- [ ] Step 2: Confirmation 1 - Content understanding ⚠️ REQUIRED
- [ ] Step 3: Generate 3 outline + style variants
@@ -176,12 +182,22 @@ XHS Infographic Progress:
### Flow
```
Input → Analyze → [Confirm 1] → 3 Outlines → [Confirm 2: Outline + Style + Elements] → Generate → Complete
Input → [Step 0: Preferences] ─┬─ Found → Continue
└─ Not found → First-Time Setup ⛔ BLOCKING
└─ Complete setup → Save EXTEND.md → Continue
┌───────────────────────────────────────────────────────────────────────────┘
Analyze → [Confirm 1] → 3 Outlines → [Confirm 2: Outline + Style + Elements] → Generate → Complete
```
### Step 0: Load Preferences (EXTEND.md) ⚠️
### Step 0: Load Preferences (EXTEND.md) ⛔ BLOCKING
**Purpose**: Load user preferences or run first-time setup. **Do NOT skip setup if EXTEND.md not found.**
**Purpose**: Load user preferences or run first-time setup.
**CRITICAL**: If EXTEND.md not found, MUST complete first-time setup before ANY other questions or steps. Do NOT proceed to content analysis, do NOT ask about style, do NOT ask about layout — ONLY complete the preferences setup first.
Use Bash to check EXTEND.md existence (priority order):
@@ -201,13 +217,13 @@ test -f "$HOME/.baoyu-skills/baoyu-xhs-images/EXTEND.md" && echo "user"
│ $HOME/.baoyu-skills/baoyu-xhs-images/EXTEND.md │ User home │
└────────────────────────────────────────────────────┴───────────────────┘
┌───────────┬───────────────────────────────────────────────────────────────────────────┐
│ Result │ Action
├───────────┼───────────────────────────────────────────────────────────────────────────┤
│ Found │ Read, parse, display summary → Continue to Step 1 │
├───────────┼───────────────────────────────────────────────────────────────────────────┤
│ Not found │ ⚠️ MUST run first-time setup (see below) → Then continue to Step 1
└───────────┴───────────────────────────────────────────────────────────────────────────┘
┌───────────┬─────────────────────────────────────────────────────────────────────────────────────────────────────
│ Result │ Action
├───────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────
│ Found │ Read, parse, display summary → Continue to Step 1
├───────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────
│ Not found │ ⛔ BLOCKING: Run first-time setup ONLY (see below) → Complete and save EXTEND.md → Then Step 1 │
└───────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────
**First-Time Setup** (when EXTEND.md not found):
@@ -227,6 +243,7 @@ Read source content, save it if needed, and perform deep analysis.
1. **Save source content** (if not already a file):
- If user provides a file path: use as-is
- If user pastes content: save to `source.md` in target directory
- **Backup rule**: If `source.md` exists, rename to `source-backup-YYYYMMDD-HHMMSS.md`
2. Read source content
3. **Deep analysis** following `references/workflows/analysis-framework.md`:
- Content type classification (种草/干货/测评/教程/避坑...)
@@ -353,7 +370,9 @@ With confirmed outline + style + layout:
**For each image (cover + content + ending)**:
1. Save prompt to `prompts/NN-{type}-[slug].md` (in user's preferred language)
- **Backup rule**: If prompt file exists, rename to `prompts/NN-{type}-[slug]-backup-YYYYMMDD-HHMMSS.md`
2. Generate image using confirmed style and layout
- **Backup rule**: If image file exists, rename to `NN-{type}-[slug]-backup-YYYYMMDD-HHMMSS.png`
3. Report progress after each generation
**Watermark Application** (if enabled in preferences):
@@ -417,17 +436,18 @@ Files:
**Style × Layout Matrix** (✓✓ = highly recommended, ✓ = works well):
| | sparse | balanced | dense | list | comparison | flow |
|---|:---:|:---:|:---:|:---:|:---:|:---:|
| cute | ✓✓ | ✓✓ | ✓ | ✓✓ | ✓ | ✓ |
| fresh | ✓✓ | ✓✓ | ✓ | ✓ | ✓ | ✓✓ |
| warm | ✓✓ | ✓✓ | ✓ | ✓ | ✓✓ | ✓ |
| bold | ✓✓ | ✓ | ✓ | ✓✓ | ✓✓ | ✓ |
| minimal | ✓✓ | ✓✓ | ✓✓ | ✓ | ✓ | ✓ |
| retro | ✓✓ | ✓✓ | ✓ | ✓✓ | ✓ | ✓ |
| pop | ✓✓ | ✓✓ | ✓ | ✓✓ | ✓✓ | ✓ |
| notion | ✓✓ | ✓✓ | ✓✓ | ✓✓ | ✓✓ | ✓✓ |
| chalkboard | ✓✓ | ✓✓ | ✓✓ | ✓✓ | ✓ | ✓✓ |
| | sparse | balanced | dense | list | comparison | flow | mindmap | quadrant |
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| cute | ✓✓ | ✓✓ | ✓ | ✓✓ | ✓ | ✓ | ✓ | ✓ |
| fresh | ✓✓ | ✓✓ | ✓ | ✓ | ✓ | ✓✓ | ✓ | ✓ |
| warm | ✓✓ | ✓✓ | ✓ | ✓ | ✓✓ | ✓ | ✓ | ✓ |
| bold | ✓✓ | ✓ | ✓ | ✓✓ | ✓✓ | ✓ | ✓ | ✓✓ |
| minimal | ✓✓ | ✓✓ | ✓✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| retro | ✓✓ | ✓✓ | ✓ | ✓✓ | ✓ | ✓ | ✓ | ✓ |
| pop | ✓✓ | ✓✓ | ✓ | ✓✓ | ✓✓ | ✓ | ✓ | ✓ |
| notion | ✓✓ | ✓✓ | ✓✓ | ✓✓ | ✓✓ | ✓✓ | ✓✓ | ✓✓ |
| chalkboard | ✓✓ | ✓✓ | ✓✓ | ✓✓ | ✓ | ✓✓ | ✓✓ | ✓ |
| study-notes | ✗ | ✓ | ✓✓ | ✓✓ | ✓ | ✓ | ✓✓ | ✓ |
## References
@@ -9,6 +9,14 @@ description: First-time setup flow for baoyu-xhs-images preferences
When no EXTEND.md is found, guide user through preference setup.
**⛔ BLOCKING OPERATION**: This setup MUST complete before ANY other workflow steps. Do NOT:
- Ask about content/article
- Ask about style or layout
- Ask about target audience
- Proceed to content analysis
ONLY ask the questions in this setup flow, save EXTEND.md, then continue.
## Setup Flow
```
@@ -51,6 +51,8 @@ Avoid placing critical content in these areas:
| list | Vertical enumeration | 4-7 | Rankings, checklists, step guides |
| comparison | Left vs Right | 2 sections | Before/after, pros/cons |
| flow | Connected nodes | 3-6 steps | Processes, timelines, workflows |
| mindmap | Center radial | 4-8 branches | Concept maps, brainstorming, topic overview |
| quadrant | 4-section grid | 4 sections | SWOT analysis, priority matrix, classification |
## Layout by Position
@@ -106,3 +108,15 @@ For multi-element compositions:
- Directional flow (top→bottom or left→right)
- Connected nodes with arrows
- Clear progression indicators
### Mindmap Layout
- Central topic node
- Radial branches outward
- Hierarchical sub-branches
- Organic curved connections
### Quadrant Layout
- 4-section grid (2×2)
- Clear axis labels
- Each quadrant with distinct content
- Optional circular variant for cycles
@@ -0,0 +1,115 @@
---
name: study-notes
category: realistic
---
# Study Notes Style
Realistic handwritten photo aesthetic - student notes style, dense and messy but readable.
## Element Combination
```yaml
canvas:
ratio: portrait-3-4
grid: single
image_effects:
cutout: none
stroke: none
filter: natural-photo
typography:
decorated: none
tags: none
direction: horizontal
decorations:
emphasis: circle-mark | underline | checkmark | cross | star-simple
background: lined-paper-white
doodles: arrows-simple | margin-notes | corrections | explanatory-diagrams
frames: none
```
## Color Palette (Three-Color Annotation System)
| Role | Colors | Hex |
|------|--------|-----|
| Primary | Blue ballpoint, Black ink | #1E3A5F, #1A1A1A |
| Highlights | Yellow highlighter | #FFFF00 (50% opacity) |
| Accents | Red pen (circles, underlines) | #CC0000 |
| Background | White lined paper | #FFFFFF |
## Visual Elements
- Realistic photo perspective: top-down view of study desk
- Hand holding blue ballpoint pen, actively underlining
- Extremely dense handwritten content, filling entire page
- Red pen annotations: circles, underlines, stars, boxes
- Yellow highlighter marking key terms
- Correction marks, cramped notes squeezed into margins
- Simple hand-drawn symbols: → * ✓ ✗ !
- Varying pen pressure creating lighter and darker strokes
## Typography
- Authentic student handwriting
- Messy but readable, clear structure maintained
- Varying font sizes (large titles, small body, tiny margin notes)
- CJK optimized
## Content Structure
Three-section layout:
### Top Section
- Core topic (circled multiple times in red)
- First section title + 3-4 key points
- Arrow connections, red underlines
### Middle Section
- Second section title (red pen box)
- Numbered steps ①②③
- Specific methods and supplementary notes
### Bottom Section
- Third section title (red star)
- Time points / key metrics
- Key quotes / core tips (tiny corner notes)
## Best Layout Pairings
| Layout | Compatibility | Use Case |
|--------|---------------|----------|
| sparse | ✗ | Not suitable - style requires dense content |
| balanced | ✓ | When content is lighter |
| dense | ✓✓ | Best fit - knowledge notes, summaries |
| list | ✓✓ | Step checklists, rankings |
| comparison | ✓ | Comparative analysis |
| flow | ✓ | Process flows |
| mindmap | ✓✓ | Mind map notes |
| quadrant | ✓ | Quadrant analysis |
## Best For
- Study guides, exam notes
- Knowledge organization, framework summaries
- Tutorial summaries, quick notes
- "Top student notes" style content
- Knowledge sharing requiring authentic feel
## Style Rules
### DO ✓
- Keep content extremely dense
- Use simple symbols (→ * ✓ ✗ !)
- Annotate key points with red pen
- Include correction marks
- Squeeze tiny notes into margins
### DON'T ✗
- Use complex emojis
- Leave too much whitespace
- Make neat, tidy layouts
- Add colorful decorations
- Include cartoon elements