Compare commits

...

18 Commits

Author SHA1 Message Date
Jim Liu 宝玉 b12ae7374d chore: release v1.22.0 2026-01-25 21:05:37 -06:00
Jim Liu 宝玉 1afa6f5a58 refactor(baoyu-post-to-x): simplify image placeholders from bracket format to XIMGPH 2026-01-25 21:05:08 -06:00
Jim Liu 宝玉 977598d5ae feat(baoyu-post-to-wechat): add theme selection, HTML preview, and simplify image placeholders 2026-01-25 21:05:04 -06:00
Jim Liu 宝玉 eb738aa61a feat(baoyu-cover-image): add default_output_dir preference with output directory selection 2026-01-25 21:04:59 -06:00
Jim Liu 宝玉 e9a030f917 feat(baoyu-article-illustrator): add imgs-subdir output option and improve style selection 2026-01-25 21:04:56 -06:00
Jim Liu 宝玉 e6a9b217e6 chore: release v1.21.4 2026-01-25 16:10:08 -06:00
Jim Liu 宝玉 ec1743592c fix(baoyu-post-to-wechat): fix regressions from Windows compatibility PR
- Fix broken md-to-wechat-fixed.ts/wechat-article-fixed.ts filename
  references that cause runtime crash (files were never renamed)
- Restore frontmatter quote stripping for title/summary values
- Restore --title CLI parameter functionality (was silently ignored)
- Fix summary extraction to properly skip headings, quotes, lists
- Fix argument parsing to reject single-dash args as file paths
- Remove debug console.error logs left from development
2026-01-25 16:09:24 -06:00
Jim Liu 宝玉 fe647a11bb Merge pull request #20 from JadeLiang003/fix/windows-copy-paste-support
Fix: Windows compatibility for baoyu-post-to-wechat
2026-01-25 16:04:01 -06:00
JadeLiang 5a11b49b00 Fix: Windows compatibility for baoyu-post-to-wechat
## Problem
The baoyu-post-to-wechat skill failed on Windows due to:
1. `new URL(import.meta.url).pathname` returns incorrect path format on Windows
2. Copy/paste operations used `xdotool` (Linux tool) which doesn't exist on Windows

## Solution
1. **md-to-wechat.ts**: Use `fileURLToPath()` to correctly resolve file paths on Windows
2. **wechat-article.ts**: Use CDP `Input.dispatchKeyEvent` for copy/paste operations
   - Replaces system-dependent tools (xdotool/osascript)
   - Works consistently across Windows/macOS/Linux
   - More reliable as it operates within Chrome session

## Changes
- Import `fileURLToPath` from 'node:url'
- Replace `new URL(import.meta.url).pathname` with `fileURLToPath(import.meta.url)`
- Replace system tool calls with CDP keyboard events for Ctrl+C and Ctrl+V
- Add platform-specific modifier handling (Cmd for macOS, Ctrl for Windows/Linux)

## Testing
- Tested successfully on Windows 11
- Markdown conversion works correctly
- HTML copy/paste to WeChat editor works correctly

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-25 15:24:04 +08:00
Jim Liu 宝玉 04692515bb fix: remove opacity of watermark 2026-01-24 17:23:04 -06:00
Jim Liu 宝玉 eb54c2a2e7 chore: release v1.21.3 2026-01-24 17:20:02 -06:00
Jim Liu 宝玉 a39c8eb0bd refactor(baoyu-article-illustrator): extract content to reference files
- Simplify SKILL.md by extracting usage and prompt templates
- Add references/usage.md for command syntax
- Add references/prompt-construction.md for prompt templates
- Add default_output_dir preference option
- Reorganize workflow from 5 to 6 steps with Pre-check phase
2026-01-24 17:19:42 -06:00
Jim Liu 宝玉 715555c2ab chore: release v1.21.2 2026-01-24 13:37:58 -06:00
Jim Liu 宝玉 02b039f2ff feat(baoyu-image-gen): add parallel generation documentation 2026-01-24 13:37:42 -06:00
Jim Liu 宝玉 00306414fe docs(release-skills): add skill grouping and user confirmation workflow 2026-01-24 13:37:38 -06:00
Jim Liu 宝玉 db1179e057 chore: release v1.21.1 2026-01-24 13:25:15 -06:00
Jim Liu 宝玉 7b0c9ca372 chore: release v1.21.0 2026-01-24 03:33:10 -06:00
Jim Liu 宝玉 e7255efdd6 chore: release v1.20.0 2026-01-24 02:30:28 -06:00
31 changed files with 1580 additions and 590 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
}, },
"metadata": { "metadata": {
"description": "Skills shared by Baoyu for improving daily work efficiency", "description": "Skills shared by Baoyu for improving daily work efficiency",
"version": "1.19.0" "version": "1.22.0"
}, },
"plugins": [ "plugins": [
{ {
+141 -39
View File
@@ -186,18 +186,61 @@ Japanese (CHANGELOG.ja.md):
- コネクションプールのメモリリークを修正 - コネクションプールのメモリリークを修正
``` ```
### Step 5: Check README Updates ### Step 5: Group Changes by Skill/Module
**IMPORTANT**: Before updating version file, check if README needs updates. Analyze commits since last tag and group by affected skill/module:
1. **Scan for README files**: `README*.md` in project root 1. **Identify changed files** per commit
2. **For each skill/feature changed**, check if README documentation matches: 2. **Group by skill/module**:
- Options/flags documented correctly - `skills/<skill-name>/*` → Group under that skill
- Usage examples reflect current syntax - Root files (CLAUDE.md, etc.) → Group as "project"
- Feature descriptions are accurate - Multiple skills in one commit → Split into multiple groups
3. **If README is outdated**: 3. **For each group**, identify related README updates needed
- Update README.md (and README.zh.md if exists) to reflect changes
- Include in release commit **Example Grouping**:
```
baoyu-cover-image:
- feat: add new style options
- fix: handle transparent backgrounds
→ README updates: options table
baoyu-comic:
- refactor: improve panel layout algorithm
→ No README updates needed
project:
- docs: update CLAUDE.md architecture section
```
### Step 6: Commit Each Skill/Module Separately
For each skill/module group (in order of changes):
1. **Check README updates needed**:
- Scan `README*.md` for mentions of this skill/module
- Verify options/flags documented correctly
- Update usage examples if syntax changed
- Update feature descriptions if behavior changed
2. **Stage and commit**:
```bash
git add skills/<skill-name>/*
git add README.md README.zh.md # If updated for this skill
git commit -m "<type>(<skill-name>): <meaningful description>"
```
3. **Commit message format**:
- Use conventional commit format: `<type>(<scope>): <description>`
- `<type>`: feat, fix, refactor, docs, perf, etc.
- `<scope>`: skill name or "project"
- `<description>`: Clear, meaningful description of changes
**Example Commits**:
```bash
git commit -m "feat(baoyu-cover-image): add watercolor and minimalist styles"
git commit -m "fix(baoyu-comic): improve panel layout for long dialogues"
git commit -m "docs(project): update architecture documentation"
```
**Common README Updates Needed**: **Common README Updates Needed**:
| Change Type | README Section to Check | | Change Type | README Section to Check |
@@ -208,11 +251,13 @@ Japanese (CHANGELOG.ja.md):
| Breaking changes | Migration notes, deprecation warnings | | Breaking changes | Migration notes, deprecation warnings |
| Restructured internals | Architecture section (if exposed to users) | | Restructured internals | Architecture section (if exposed to users) |
### Step 6: Update Version File ### Step 7: Generate Changelog and Update Version
1. Read version file (JSON/TOML/text) 1. **Generate multi-language changelogs** (as described in Step 4)
2. Update version number 2. **Update version file**:
3. Write back (preserve formatting) - Read version file (JSON/TOML/text)
- Update version number
- Write back (preserve formatting)
**Version Paths by File Type**: **Version Paths by File Type**:
@@ -224,25 +269,77 @@ Japanese (CHANGELOG.ja.md):
| marketplace.json | `$.metadata.version` | | marketplace.json | `$.metadata.version` |
| VERSION / version.txt | Direct content | | VERSION / version.txt | Direct content |
### Step 7: Commit and Tag ### Step 8: User Confirmation
```bash Before creating the release commit, ask user to confirm:
git add <all modified files>
git commit -m "chore: release v{VERSION}" **Use AskUserQuestion with two questions**:
git tag v{VERSION}
1. **Version bump** (single select):
- Show recommended version based on Step 3 analysis
- Options: recommended (with label), other semver options
- Example: `1.2.3 → 1.3.0 (Recommended)`, `1.2.3 → 1.2.4`, `1.2.3 → 2.0.0`
2. **Push to remote** (single select):
- Options: "Yes, push after commit", "No, keep local only"
**Example Output Before Confirmation**:
``` ```
Commits created:
1. feat(baoyu-cover-image): add watercolor and minimalist styles
2. fix(baoyu-comic): improve panel layout for long dialogues
3. docs(project): update architecture documentation
Changelog preview (en):
## 1.3.0 - 2026-01-22
### Features
- Add watercolor and minimalist styles to cover-image
### Fixes
- Improve panel layout for long dialogues in comic
Ready to create release commit and tag.
```
### Step 9: Create Release Commit and Tag
After user confirmation:
1. **Stage version and changelog files**:
```bash
git add <version-file>
git add CHANGELOG*.md
```
2. **Create release commit**:
```bash
git commit -m "chore: release v{VERSION}"
```
3. **Create tag**:
```bash
git tag v{VERSION}
```
4. **Push if user confirmed** (Step 8):
```bash
git push origin main
git push origin v{VERSION}
```
**Note**: Do NOT add Co-Authored-By line. This is a release commit, not a code contribution. **Note**: Do NOT add Co-Authored-By line. This is a release commit, not a code contribution.
**Important**: Do NOT push to remote. User will push manually when ready.
**Post-Release Output**: **Post-Release Output**:
``` ```
Release v1.3.0 created locally. Release v1.3.0 created.
To publish: Commits:
git push origin main 1. feat(baoyu-cover-image): add watercolor and minimalist styles
git push origin v1.3.0 2. fix(baoyu-comic): improve panel layout for long dialogues
3. docs(project): update architecture documentation
4. chore: release v1.3.0
Tag: v1.3.0
Status: Pushed to origin # or "Local only - run git push when ready"
``` ```
## Configuration (.releaserc.yml) ## Configuration (.releaserc.yml)
@@ -307,31 +404,36 @@ Project detected:
Last tag: v1.2.3 Last tag: v1.2.3
Proposed version: v1.3.0 Proposed version: v1.3.0
Changes detected: Changes grouped by skill/module:
feat: add user authentication baoyu-cover-image:
feat: support oauth2 login - feat: add watercolor style
fix: memory leak in pool - feat: add minimalist style
→ Commit: feat(baoyu-cover-image): add watercolor and minimalist styles
→ README updates: options table
baoyu-comic:
- fix: panel layout for long dialogues
→ Commit: fix(baoyu-comic): improve panel layout for long dialogues
→ No README updates
Changelog preview (en): Changelog preview (en):
## 1.3.0 - 2026-01-22 ## 1.3.0 - 2026-01-22
### Features ### Features
- Add user authentication module - Add watercolor and minimalist styles to cover-image
- Support OAuth2 login
### Fixes ### Fixes
- Fix memory leak in connection pool - Improve panel layout for long dialogues in comic
Changelog preview (zh): Changelog preview (zh):
## 1.3.0 - 2026-01-22 ## 1.3.0 - 2026-01-22
### 新功能 ### 新功能
- 新增用户认证模块 - 为 cover-image 添加水彩和极简风格
- 支持 OAuth2 登录
### 修复 ### 修复
- 修复连接池内存泄漏问题 - 改进 comic 长对话的面板布局
Files to modify: Commits to create:
- package.json 1. feat(baoyu-cover-image): add watercolor and minimalist styles
- CHANGELOG.md 2. fix(baoyu-comic): improve panel layout for long dialogues
- CHANGELOG.zh.md 3. chore: release v1.3.0
No changes made. Run without --dry-run to execute. No changes made. Run without --dry-run to execute.
``` ```
+51
View File
@@ -2,6 +2,57 @@
English | [中文](./CHANGELOG.zh.md) English | [中文](./CHANGELOG.zh.md)
## 1.22.0 - 2026-01-25
### Features
- `baoyu-article-illustrator`: adds `imgs-subdir` output directory option; improves style selection to always ask and show preferred_style from EXTEND.md.
- `baoyu-cover-image`: adds `default_output_dir` preference supporting `same-dir`, `imgs-subdir`, and `independent` options with Step 1.5 for output directory selection.
- `baoyu-post-to-wechat`: adds theme selection (default/grace/simple) with AskUserQuestion before posting; adds HTML preview step; simplifies image placeholders to `WECHATIMGPH_N` format; refactors copy/paste to cross-platform helpers.
### Refactor
- `baoyu-post-to-x`: simplifies image placeholders from `[[IMAGE_PLACEHOLDER_N]]` to `XIMGPH_N` format.
## 1.21.4 - 2026-01-25
### Fixes
- `baoyu-post-to-wechat`: adds Windows compatibility—uses `fileURLToPath` for correct path resolution, replaces system-dependent copy/paste tools (osascript/xdotool) with CDP keyboard events for cross-platform support.
- `baoyu-post-to-wechat`: fixes regressions from Windows compatibility PR—corrects broken `-fixed` filename references, restores frontmatter quote stripping, restores `--title` CLI parameter, fixes summary extraction to skip headings/quotes/lists, fixes argument parsing for single-dash flags, removes debug logs.
- `baoyu-article-illustrator`, `baoyu-cover-image`, `baoyu-xhs-images`: removes opacity option from watermark configuration.
## 1.21.3 - 2026-01-24
### Refactor
- `baoyu-article-illustrator`: simplifies SKILL.md by extracting content to reference files—adds `references/usage.md` for command syntax, `references/prompt-construction.md` for prompt templates. Reorganizes workflow from 5 to 6 steps with new Pre-check phase. Adds `default_output_dir` preference option.
## 1.21.2 - 2026-01-24
### Features
- `baoyu-image-gen`: adds parallel generation documentation with recommended 4 concurrent subagents for batch operations.
### Documentation
- `release-skills`: adds skill/module grouping workflow and user confirmation step before release.
## 1.21.1 - 2026-01-24
### Documentation
- `baoyu-comic`: adds character sheet compression step after generation to reduce token usage when used as reference image.
## 1.21.0 - 2026-01-24
### Features
- `baoyu-cover-image`: expands aspect ratio options—adds 4:3, 3:2, 3:4 ratios; changes default from 2.35:1 to 16:9 for better versatility. Aspect ratio is now always confirmed unless explicitly specified via `--aspect` flag.
- `baoyu-image-gen`: refactors Google provider to support both Gemini multimodal and Imagen models with unified API. Adds `--imageSize` parameter support (1K/2K/4K) for Gemini models.
## 1.20.0 - 2026-01-24
### Features
- `baoyu-cover-image`: upgrades from Type × Style two-dimension system to **4-dimension system**—adds `--text` dimension (none, title-only, title-subtitle, text-rich) for text density control and `--mood` dimension (subtle, balanced, bold) for emotional intensity. New `--quick` flag skips confirmation and uses auto-selection.
### Documentation
- `baoyu-cover-image`: adds dimension reference files—`references/dimensions/text.md` (text density levels) and `references/dimensions/mood.md` (mood intensity levels).
- `baoyu-cover-image`: updates base-prompt, first-time-setup, and preferences-schema to support new 4-dimension system with v2 schema.
- `README.md`, `README.zh.md`: updates baoyu-cover-image documentation to reflect new 4-dimension system with `--text`, `--mood`, and `--quick` options.
## 1.19.0 - 2026-01-24 ## 1.19.0 - 2026-01-24
### Features ### Features
+51
View File
@@ -2,6 +2,57 @@
[English](./CHANGELOG.md) | 中文 [English](./CHANGELOG.md) | 中文
## 1.22.0 - 2026-01-25
### 新功能
- `baoyu-article-illustrator`:新增 `imgs-subdir` 输出目录选项;改进风格选择,始终询问并展示 EXTEND.md 中的 preferred_style。
- `baoyu-cover-image`:新增 `default_output_dir` 偏好设置,支持 `same-dir``imgs-subdir``independent` 选项,新增 Step 1.5 输出目录选择流程。
- `baoyu-post-to-wechat`:发布前新增主题选择(default/grace/simple);新增 HTML 预览步骤;图片占位符简化为 `WECHATIMGPH_N` 格式;重构复制粘贴为跨平台辅助函数。
### 重构
- `baoyu-post-to-x`:图片占位符从 `[[IMAGE_PLACEHOLDER_N]]` 简化为 `XIMGPH_N` 格式。
## 1.21.4 - 2026-01-25
### 修复
- `baoyu-post-to-wechat`:新增 Windows 兼容性——使用 `fileURLToPath` 正确解析路径,将系统依赖的复制粘贴工具(osascript/xdotool)替换为 CDP 键盘事件,实现跨平台支持。
- `baoyu-post-to-wechat`:修复 Windows 兼容性 PR 引入的回退问题——修正错误的 `-fixed` 文件名引用、恢复 frontmatter 引号剥离、恢复 `--title` CLI 参数、修复摘要提取逻辑以正确跳过标题/引用/列表、修复单横线参数解析、移除调试日志。
- `baoyu-article-illustrator``baoyu-cover-image``baoyu-xhs-images`:移除水印配置中的透明度选项。
## 1.21.3 - 2026-01-24
### 重构
- `baoyu-article-illustrator`:简化 SKILL.md,提取内容至引用文件——新增 `references/usage.md` 用于命令语法,`references/prompt-construction.md` 用于提示词模板。工作流从 5 步重组为 6 步,新增 Pre-check 预检阶段。新增 `default_output_dir` 偏好设置选项。
## 1.21.2 - 2026-01-24
### 新功能
- `baoyu-image-gen`:添加并行生成文档,推荐使用 4 个并发 subagent 进行批量操作。
### 文档
- `release-skills`:新增按 skill/module 分组提交流程和发布前用户确认步骤。
## 1.21.1 - 2026-01-24
### 文档
- `baoyu-comic`:在角色参考图生成后添加压缩步骤,减少作为参考图使用时的 token 消耗。
## 1.21.0 - 2026-01-24
### 新功能
- `baoyu-cover-image`:扩展宽高比选项——新增 4:3、3:2、3:4 比例;默认值从 2.35:1 改为 16:9 以提高通用性。现在除非通过 `--aspect` 标志明确指定,否则始终确认宽高比。
- `baoyu-image-gen`:重构 Google provider 以统一支持 Gemini 多模态和 Imagen 模型。为 Gemini 模型新增 `--imageSize` 参数支持(1K/2K/4K)。
## 1.20.0 - 2026-01-24
### 新功能
- `baoyu-cover-image`:从类型 × 风格二维系统升级为**四维系统**——新增 `--text` 维度(none 无文字、title-only 仅标题、title-subtitle 标题+副标题、text-rich 丰富文字)控制文字密度,新增 `--mood` 维度(subtle 低调、balanced 平衡、bold 醒目)控制情感强度。新增 `--quick` 标志跳过确认,直接使用自动选择。
### 文档
- `baoyu-cover-image`:新增维度参考文件——`references/dimensions/text.md`(文字密度级别)和 `references/dimensions/mood.md`(氛围强度级别)。
- `baoyu-cover-image`:更新 base-prompt、first-time-setup 和 preferences-schema 以支持新的四维系统及 v2 配置模式。
- `README.md``README.zh.md`:更新 baoyu-cover-image 文档,反映新的四维系统及 `--text``--mood``--quick` 选项。
## 1.19.0 - 2026-01-24 ## 1.19.0 - 2026-01-24
### 新功能 ### 新功能
+13 -6
View File
@@ -245,24 +245,31 @@ Generate professional infographics with 20 layout types and 17 visual styles. An
#### baoyu-cover-image #### baoyu-cover-image
Generate cover images for articles with Type × Style two-dimension system. Generate cover images for articles with 4 dimensions: Type × Style × Text × Mood.
```bash ```bash
# Auto-select type and style based on content # Auto-select all dimensions based on content
/baoyu-cover-image path/to/article.md /baoyu-cover-image path/to/article.md
# Specify type and/or style # Quick mode: skip confirmation, use auto-selection
/baoyu-cover-image path/to/article.md --quick
# Specify dimensions
/baoyu-cover-image path/to/article.md --type conceptual --style blueprint /baoyu-cover-image path/to/article.md --type conceptual --style blueprint
/baoyu-cover-image path/to/article.md --style warm /baoyu-cover-image path/to/article.md --text title-subtitle --mood bold
# Specify aspect ratio (default: 2.35:1) # Specify aspect ratio (default: 2.35:1)
/baoyu-cover-image path/to/article.md --aspect 16:9 /baoyu-cover-image path/to/article.md --aspect 16:9
# Without title text # Visual only (no title text)
/baoyu-cover-image path/to/article.md --no-title /baoyu-cover-image path/to/article.md --no-title
``` ```
Available types: `hero`, `conceptual`, `typography`, `metaphor`, `scene`, `minimal` **Four Dimensions**:
- **Type**: `hero`, `conceptual`, `typography`, `metaphor`, `scene`, `minimal`
- **Style**: 20 built-in styles (see previews below)
- **Text**: `none`, `title-only` (default), `title-subtitle`, `text-rich`
- **Mood**: `subtle`, `balanced` (default), `bold`
Available styles: `elegant` (default), `blueprint`, `bold-editorial`, `chalkboard`, `dark-atmospheric`, `editorial-infographic`, `fantasy-animation`, `flat-doodle`, `intuition-machine`, `minimal`, `nature`, `notion`, `pixel-art`, `playful`, `retro`, `sketch-notes`, `vector-illustration`, `vintage`, `warm`, `watercolor` Available styles: `elegant` (default), `blueprint`, `bold-editorial`, `chalkboard`, `dark-atmospheric`, `editorial-infographic`, `fantasy-animation`, `flat-doodle`, `intuition-machine`, `minimal`, `nature`, `notion`, `pixel-art`, `playful`, `retro`, `sketch-notes`, `vector-illustration`, `vintage`, `warm`, `watercolor`
+13 -6
View File
@@ -245,24 +245,31 @@ npx skills add jimliu/baoyu-skills
#### baoyu-cover-image #### baoyu-cover-image
为文章生成封面图,支持类型 × 风格二维系统 为文章生成封面图,支持四维定制系统:类型 × 风格 × 文字 × 氛围
```bash ```bash
# 根据内容自动选择类型和风格 # 根据内容自动选择所有维度
/baoyu-cover-image path/to/article.md /baoyu-cover-image path/to/article.md
# 指定类型和/或风格 # 快速模式:跳过确认,使用自动选择
/baoyu-cover-image path/to/article.md --quick
# 指定维度
/baoyu-cover-image path/to/article.md --type conceptual --style blueprint /baoyu-cover-image path/to/article.md --type conceptual --style blueprint
/baoyu-cover-image path/to/article.md --style warm /baoyu-cover-image path/to/article.md --text title-subtitle --mood bold
# 指定宽高比(默认:2.35:1 # 指定宽高比(默认:2.35:1
/baoyu-cover-image path/to/article.md --aspect 16:9 /baoyu-cover-image path/to/article.md --aspect 16:9
# 不含标题文字 # 纯视觉(不含标题文字
/baoyu-cover-image path/to/article.md --no-title /baoyu-cover-image path/to/article.md --no-title
``` ```
可用类型:`hero``conceptual``typography``metaphor``scene``minimal` **四个维度**
- **类型 (Type)**`hero``conceptual``typography``metaphor``scene``minimal`
- **风格 (Style)**:20 种内置风格(见下方预览)
- **文字 (Text)**`none``title-only`(默认)、`title-subtitle``text-rich`
- **氛围 (Mood)**`subtle``balanced`(默认)、`bold`
可用风格:`elegant`(默认)、`blueprint``bold-editorial``chalkboard``dark-atmospheric``editorial-infographic``fantasy-animation``flat-doodle``intuition-machine``minimal``nature``notion``pixel-art``playful``retro``sketch-notes``vector-illustration``vintage``warm``watercolor` 可用风格:`elegant`(默认)、`blueprint``bold-editorial``chalkboard``dark-atmospheric``editorial-infographic``fantasy-animation``flat-doodle``intuition-machine``minimal``nature``notion``pixel-art``playful``retro``sketch-notes``vector-illustration``vintage``warm``watercolor`
+172 -301
View File
@@ -7,236 +7,201 @@ description: Analyzes article structure, identifies positions requiring visual a
Analyze articles, identify illustration positions, generate images with Type × Style consistency. Analyze articles, identify illustration positions, generate images with Type × Style consistency.
## Usage
```bash
# Auto-select type and style based on content
/baoyu-article-illustrator path/to/article.md
# Specify type
/baoyu-article-illustrator path/to/article.md --type infographic
# Specify style
/baoyu-article-illustrator path/to/article.md --style blueprint
# Combine type and style
/baoyu-article-illustrator path/to/article.md --type flowchart --style notion
# Specify density
/baoyu-article-illustrator path/to/article.md --density rich
# Direct content input
/baoyu-article-illustrator
[paste content]
```
## Options
| Option | Description |
|--------|-------------|
| `--type <name>` | Illustration type (see Type Gallery) |
| `--style <name>` | Visual style (see Style Gallery) |
| `--density <level>` | Image count: minimal / balanced / rich |
## Two Dimensions ## Two Dimensions
| Dimension | Controls | Examples | | Dimension | Controls | Examples |
|-----------|----------|----------| |-----------|----------|----------|
| **Type** | Information structure, content layout | infographic, scene, flowchart, comparison, framework, timeline | | **Type** | Information structure, layout | infographic, scene, flowchart, comparison, framework, timeline |
| **Style** | Visual aesthetics, colors, mood | notion, warm, minimal, blueprint, watercolor, elegant | | **Style** | Visual aesthetics, mood | notion, warm, minimal, blueprint, watercolor, elegant |
Type × Style can be freely combined. Example: `--type infographic --style blueprint` creates technical data visualization with schematic aesthetics. Type × Style can be freely combined. Example: `--type infographic --style blueprint`
## Type Gallery ## Type Gallery
| Type | Description | Best For | | Type | Best For |
|------|-------------|----------| |------|----------|
| `infographic` | Data visualization, charts, metrics | Technical articles, data analysis, comparisons | | `infographic` | Data, metrics, technical articles |
| `scene` | Atmospheric illustration, mood rendering | Narrative articles, personal stories, emotional content | | `scene` | Narratives, personal stories, emotional content |
| `flowchart` | Process diagrams, step visualization | Tutorials, workflows, decision trees | | `flowchart` | Tutorials, workflows, processes |
| `comparison` | Side-by-side, before/after contrast | Product comparisons, option evaluations | | `comparison` | Side-by-side, before/after, options |
| `framework` | Concept maps, relationship diagrams | Methodologies, models, architecture design | | `framework` | Methodologies, models, architecture |
| `timeline` | Chronological progression | History, project progress, evolution | | `timeline` | History, progress, evolution |
## Density Options
| Density | Count | Description |
|---------|-------|-------------|
| `minimal` | 1-2 | Core concepts only |
| `balanced` (Default) | 3-5 | Major sections coverage |
| `rich` | 6+ | Rich visual support |
## Style Gallery ## Style Gallery
| Style | Description | Best For | | Style | Best For |
|-------|-------------|----------| |-------|----------|
| `notion` (Default) | Minimalist hand-drawn line art | Knowledge sharing, SaaS, productivity | | `notion` (Default) | Knowledge sharing, SaaS, productivity |
| `elegant` | Refined, sophisticated | Business, thought leadership | | `elegant` | Business, thought leadership |
| `warm` | Friendly, approachable | Personal growth, lifestyle, education | | `warm` | Personal growth, lifestyle, education |
| `minimal` | Ultra-clean, zen-like | Philosophy, minimalism, core concepts | | `minimal` | Philosophy, core concepts |
| `blueprint` | Technical schematics | Architecture, system design, engineering | | `blueprint` | Architecture, system design |
| `watercolor` | Soft artistic with natural warmth | Lifestyle, travel, creative | | `watercolor` | Lifestyle, travel, creative |
| `editorial` | Magazine-style infographic | Tech explainers, journalism | | `editorial` | Tech explainers, journalism |
| `scientific` | Academic precise diagrams | Biology, chemistry, technical research | | `scientific` | Academic, technical research |
Full style specifications: `references/styles/<style>.md` Full styles: [references/styles.md](references/styles.md)
## Type × Style Compatibility
| | notion | warm | minimal | blueprint | watercolor | elegant | editorial | scientific |
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| infographic | ✓✓ | ✓ | ✓✓ | ✓✓ | ✓ | ✓✓ | ✓✓ | ✓✓ |
| scene | ✓ | ✓✓ | ✓ | ✗ | ✓✓ | ✓ | ✓ | ✗ |
| flowchart | ✓✓ | ✓ | ✓ | ✓✓ | ✗ | ✓ | ✓✓ | ✓ |
| comparison | ✓✓ | ✓ | ✓✓ | ✓ | ✓ | ✓✓ | ✓✓ | ✓ |
| framework | ✓✓ | ✓ | ✓✓ | ✓✓ | ✗ | ✓✓ | ✓ | ✓✓ |
| timeline | ✓✓ | ✓ | ✓ | ✓ | ✓✓ | ✓✓ | ✓✓ | ✓ |
✓✓ = highly recommended | ✓ = compatible | ✗ = not recommended
## Auto Selection ## Auto Selection
| Content Signals | Recommended Type | Recommended Style | | Content Signals | Type | Style |
|-----------------|------------------|-------------------| |-----------------|------|-------|
| API, metrics, data, comparison, numbers | infographic | blueprint, notion | | API, metrics, data, numbers | infographic | blueprint, notion |
| Story, emotion, journey, experience, personal | scene | warm, watercolor | | Story, emotion, journey | scene | warm, watercolor |
| How-to, steps, workflow, process, tutorial | flowchart | notion, minimal | | How-to, steps, workflow | flowchart | notion, minimal |
| vs, pros/cons, before/after, alternatives | comparison | notion, elegant | | vs, pros/cons, before/after | comparison | notion, elegant |
| Framework, model, architecture, principles | framework | blueprint, notion | | Framework, model, architecture | framework | blueprint, notion |
| History, timeline, progress, evolution | timeline | elegant, warm | | History, timeline, progress | timeline | elegant, warm |
## Output Directory
```
illustrations/{topic-slug}/
├── source-{slug}.{ext}
├── outline.md
├── prompts/
│ └── illustration-{slug}.md
└── NN-{type}-{slug}.png
```
**Slug**: Extract 2-4 word topic in kebab-case.
**Conflict**: Append `-YYYYMMDD-HHMMSS` if exists.
## Workflow ## Workflow
### Progress Copy this checklist and track progress:
``` ```
- [ ] Step 1: Setup & Analyze Progress:
- [ ] Step 2: Confirm Settings ⚠️ REQUIRED - [ ] Step 1: Pre-check
- [ ] Step 3: Generate Outline - [ ] Step 2: Setup & Analyze
- [ ] Step 4: Generate Images - [ ] Step 3: Confirm Settings ⚠️ REQUIRED
- [ ] Step 5: Finalize - [ ] Step 4: Generate Outline
- [ ] Step 5: Generate Images
- [ ] Step 6: Finalize
``` ```
### Step 1: Setup & Analyze ---
**1.1 Load Preferences (EXTEND.md)** ### Step 1: Pre-check
Use Bash to check EXTEND.md existence (priority order): **1.1 Determine Input Type**
| 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)**
```bash ```bash
# Check project-level first
test -f .baoyu-skills/baoyu-article-illustrator/EXTEND.md && echo "project" test -f .baoyu-skills/baoyu-article-illustrator/EXTEND.md && echo "project"
# Then user-level (cross-platform: $HOME works on macOS/Linux/WSL)
test -f "$HOME/.baoyu-skills/baoyu-article-illustrator/EXTEND.md" && echo "user" test -f "$HOME/.baoyu-skills/baoyu-article-illustrator/EXTEND.md" && echo "user"
``` ```
┌──────────────────────────────────────────────────────────┬───────────────────┐ | Result | Action |
│ Path │ Location │ |--------|--------|
├──────────────────────────────────────────────────────────┼───────────────────┤ | Found | Read, parse, display summary |
│ .baoyu-skills/baoyu-article-illustrator/EXTEND.md │ Project directory │ | Not found | Ask with AskUserQuestion (see references/config/first-time-setup.md) |
├──────────────────────────────────────────────────────────┼───────────────────┤
│ $HOME/.baoyu-skills/baoyu-article-illustrator/EXTEND.md │ User home │
└──────────────────────────────────────────────────────────┴───────────────────┘
┌───────────┬───────────────────────────────────────────────────────────────────────────┐ **Supports**: Watermark | Preferred type/style | Custom styles | Language | Output directory
│ Result │ Action │
├───────────┼───────────────────────────────────────────────────────────────────────────┤
│ Found │ Read, parse, display summary │
├───────────┼───────────────────────────────────────────────────────────────────────────┤
│ Not found │ Ask user with AskUserQuestion (see references/config/first-time-setup.md) │
└───────────┴───────────────────────────────────────────────────────────────────────────┘
**EXTEND.md Supports**: Watermark | Preferred type/style | Custom style definitions | Language preference ---
Schema: `references/config/preferences-schema.md` ### Step 2: Setup & Analyze
**1.2 Analyze Content** **2.1 Analyze Content**
Read article, detect language, classify content.
| Analysis | Description | | Analysis | Description |
|----------|-------------| |----------|-------------|
| Content type | Technical / Tutorial / Methodology / Narrative | | Content type | Technical / Tutorial / Methodology / Narrative |
| Core arguments | 2-5 main points that MUST be visualized | | Core arguments | 2-5 main points to visualize |
| Visual opportunities | Positions where illustrations add value | | Visual opportunities | Positions where illustrations add value |
| Recommended type | Based on content signals | | Recommended type | Based on content signals |
| Recommended density | Based on article length and complexity | | Recommended density | Based on length and complexity |
**1.3 Extract Core Arguments** **2.2 Extract Core Arguments**
Extract 2-5 core arguments that MUST be visualized:
- Main thesis - Main thesis
- Key concepts reader needs - Key concepts reader needs
- Comparisons/contrasts being made - Comparisons/contrasts
- Framework/model proposed - Framework/model proposed
**CRITICAL**: If article uses metaphors (e.g., "电锯切西瓜"), do NOT illustrate literally. Visualize the **underlying concept** instead. **CRITICAL**: If article uses metaphors (e.g., "电锯切西瓜"), do NOT illustrate literally. Visualize the **underlying concept**.
**1.4 Identify Positions** **2.3 Identify Positions**
**What to Illustrate**: **Illustrate**:
- Core arguments (REQUIRED) - Core arguments (REQUIRED)
- Abstract concepts needing visualization - Abstract concepts
- Data comparisons, metrics - Data comparisons
- Processes, workflows - Processes, workflows
**What NOT to Illustrate**: **Do NOT Illustrate**:
- Metaphors literally - Metaphors literally
- Decorative scenes without information - Decorative scenes
- Generic illustrations - Generic illustrations
### Step 2: Confirm Settings ⚠️ ---
### Step 3: Confirm Settings ⚠️
**Do NOT skip.** Use AskUserQuestion with 3-4 questions in ONE call. **Do NOT skip.** Use AskUserQuestion with 3-4 questions in ONE call.
**Question 1: Illustration Type** **Q1: Illustration Type**
- [Recommended based on analysis] (Recommended)
- infographic / scene / flowchart / comparison / framework / timeline / mixed
Based on content analysis, recommend type: **Q2: Density**
- [Recommended type based on signals] (Recommended) - minimal (1-2) - Core concepts only
- infographic - Data visualization, charts - balanced (3-5) (Recommended) - Major sections
- scene - Atmospheric, mood rendering - rich (6+) - Comprehensive support
- flowchart - Process, steps
- comparison - Side-by-side contrast
- framework - Concept relationships
- timeline - Chronological progression
- mixed - Combine multiple types
**Question 2: Density** **Q3: Style** (ALWAYS ask, even with preferred_style in EXTEND.md)
- minimal (1-2 images) - Core concepts only If EXTEND.md has `preferred_style`:
- balanced (3-5 images) (Recommended) - Major sections - [Custom style name + brief description] (Recommended)
- rich (6+ images) - Comprehensive visual support - [Top compatible built-in style 1]
- [Top compatible built-in style 2]
- [Top compatible built-in style 3]
**Question 3: Style** If no `preferred_style`:
- [Best compatible from matrix] (Recommended)
- [Other ✓✓ style 1]
- [Other ✓✓ style 2]
- [Other ✓ style]
Based on recommended Type, suggest compatible styles (see Type × Style Compatibility matrix): Style selection based on Type × Style compatibility matrix (references/styles.md).
- [Best compatible style for recommended type] (Recommended) Full specs: `references/styles/<style>.md`
- [Other highly compatible styles: ✓✓ from matrix]
- [Compatible styles: ✓ from matrix]
**Question 4** (only if source ≠ user language): **Q4** (only if source ≠ user language):
- Language: Source language / User language - Language: Source / User language
### Step 3: Generate Outline ---
Based on confirmed Type + Density + Style, generate illustration outline. ### Step 4: Generate Outline
**Outline Format** (`outline.md`): Save as `outline.md`:
```yaml ```yaml
--- ---
@@ -249,44 +214,48 @@ image_count: 4
## Illustration 1 ## Illustration 1
**Position**: [section] / [paragraph] **Position**: [section] / [paragraph]
**Purpose**: [why this illustration helps] **Purpose**: [why this helps]
**Visual Content**: [what to show] **Visual Content**: [what to show]
**Type Application**: [how type applies here] **Type Application**: [how type applies]
**Filename**: 01-infographic-concept-name.png **Filename**: 01-infographic-concept-name.png
## Illustration 2 ## Illustration 2
... ...
``` ```
**Outline Requirements**: **Requirements**:
- Each illustration position justified by content needs - Each position justified by content needs
- Type applied consistently across all illustrations - Type applied consistently
- Style characteristics reflected in visual descriptions - Style reflected in descriptions
- Count matches density selection - Count matches density
### Step 4: Generate Images ---
**4.1 Create Prompts** ### Step 5: Generate Images
Follow Prompt Construction principles below. Save each to `prompts/illustration-{slug}.md`. **5.1 Create Prompts**
**4.2 Select Generation Skill** Follow [references/prompt-construction.md](references/prompt-construction.md). Save to `prompts/illustration-{slug}.md`.
Check available image generation skills. If multiple, ask user to choose. **5.2 Select Generation Skill**
**4.3 Apply Watermark** (if enabled in preferences) Check available skills. If multiple, ask user.
Add to prompt: `Include a subtle watermark "[content]" positioned at [position] with approximately [opacity*100]% visibility.` **5.3 Apply Watermark** (if enabled)
**4.4 Generate** Add: `Include a subtle watermark "[content]" at [position].`
**5.4 Generate**
1. Generate sequentially 1. Generate sequentially
2. After each: "Generated X/N" 2. After each: "Generated X/N"
3. On failure: auto-retry once, then log and continue 3. On failure: retry once, then log and continue
### Step 5: Finalize ---
**5.1 Update Article** ### Step 6: Finalize
**6.1 Update Article**
Insert after corresponding paragraph: Insert after corresponding paragraph:
```markdown ```markdown
@@ -295,158 +264,60 @@ Insert after corresponding paragraph:
Alt text: concise description in article's language. Alt text: concise description in article's language.
**5.2 Output Summary** **6.2 Output Summary**
``` ```
Article Illustration Complete! Article Illustration Complete!
Article: [path] Article: [path]
Type: [type name] Type: [type] | Density: [level] | Style: [style]
Density: [minimal/balanced/rich] Location: [directory]
Style: [style name]
Location: [directory path]
Images: X/N generated Images: X/N generated
Positions: Positions:
- 01-infographic-xxx.png → After "[Section]" - 01-xxx.png → After "[Section]"
- 02-infographic-yyy.png → After "[Section]" - 02-yyy.png → After "[Section]"
[If failures] [If failures]
Failed: Failed:
- NN-type-zzz.png: [reason] - NN-zzz.png: [reason]
``` ```
## Prompt Construction ---
### Principles ## Output Directory
Good prompts must include:
1. **Layout Structure First**: Describe composition, zones, flow direction
2. **Specific Data/Labels**: Use actual numbers, terms from article
3. **Visual Relationships**: How elements connect
4. **Semantic Colors**: Meaning-based color choices (red=warning, green=efficient)
5. **Style Characteristics**: Line treatment, texture, mood
6. **Aspect Ratio**: End with ratio and complexity level
### Type-Specific Prompts
**Infographic**:
``` ```
[Title] - Data Visualization illustrations/{topic-slug}/
├── source-{slug}.{ext}
Layout: [grid/radial/hierarchical] ├── outline.md
├── prompts/
ZONES: │ └── illustration-{slug}.md
- Zone 1: [data point with specific values] └── NN-{type}-{slug}.png
- Zone 2: [comparison with metrics]
- Zone 3: [summary/conclusion]
LABELS: [specific numbers, percentages, terms from article]
COLORS: [semantic color mapping]
STYLE: [style characteristics]
ASPECT: 16:9
``` ```
**Scene**: **Slug**: 2-4 word topic in kebab-case.
``` **Conflict**: Append `-YYYYMMDD-HHMMSS` if exists.
[Title] - Atmospheric Scene
FOCAL POINT: [main subject]
ATMOSPHERE: [lighting, mood, environment]
MOOD: [emotion to convey]
COLOR TEMPERATURE: [warm/cool/neutral]
STYLE: [style characteristics]
ASPECT: 16:9
```
**Flowchart**:
```
[Title] - Process Flow
Layout: [left-right/top-down/circular]
STEPS:
1. [Step name] - [brief description]
2. [Step name] - [brief description]
...
CONNECTIONS: [arrow types, decision points]
STYLE: [style characteristics]
ASPECT: 16:9
```
**Comparison**:
```
[Title] - Comparison View
LEFT SIDE - [Option A]:
- [Point 1]
- [Point 2]
RIGHT SIDE - [Option B]:
- [Point 1]
- [Point 2]
DIVIDER: [visual separator]
STYLE: [style characteristics]
ASPECT: 16:9
```
**Framework**:
```
[Title] - Conceptual Framework
STRUCTURE: [hierarchical/network/matrix]
NODES:
- [Concept 1] - [role]
- [Concept 2] - [role]
RELATIONSHIPS: [how nodes connect]
STYLE: [style characteristics]
ASPECT: 16:9
```
**Timeline**:
```
[Title] - Chronological View
DIRECTION: [horizontal/vertical]
EVENTS:
- [Date/Period 1]: [milestone]
- [Date/Period 2]: [milestone]
MARKERS: [visual indicators]
STYLE: [style characteristics]
ASPECT: 16:9
```
### What to Avoid
- Vague descriptions ("a nice image")
- Literal metaphor illustrations
- Missing concrete labels/annotations
- Generic decorative elements
## Modification ## Modification
| Action | Steps | | Action | Steps |
|--------|-------| |--------|-------|
| **Edit** | Update prompt → Regenerate → Update reference | | **Edit** | Update prompt → Regenerate → Update reference |
| **Add** | Identify position → Create prompt → Generate → Update outline → Insert reference | | **Add** | Identify position → Create prompt → Generate → Update outline → Insert |
| **Delete** | Delete files → Remove reference → Update outline | | **Delete** | Delete files → Remove reference → Update outline |
## References ## References
| File | Content | | File | Content |
|------|---------| |------|---------|
| [references/styles.md](references/styles.md) | Style gallery & compatibility matrix | | [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/styles/<style>.md` | Full style specifications |
| `references/config/preferences-schema.md` | EXTEND.md schema | | `references/config/preferences-schema.md` | EXTEND.md schema |
| `references/config/first-time-setup.md` | First-time setup flow | | `references/config/first-time-setup.md` | First-time setup flow |
## Extension Support ## Extension Support
Custom configurations via EXTEND.md. See **Step 1.1** for paths and supported options. Custom configurations via EXTEND.md. See **Step 1.5** for paths and supported options.
@@ -15,7 +15,6 @@ watermark:
enabled: false enabled: false
content: "" content: ""
position: bottom-right # bottom-right|bottom-left|bottom-center|top-right position: bottom-right # bottom-right|bottom-left|bottom-center|top-right
opacity: 0.7 # 0.1-1.0
preferred_style: preferred_style:
name: null # Built-in or custom style name name: null # Built-in or custom style name
@@ -23,6 +22,8 @@ preferred_style:
language: null # zh|en|ja|ko|auto language: null # zh|en|ja|ko|auto
default_output_dir: null # same-dir|illustrations-subdir|independent
custom_styles: custom_styles:
- name: my-style - name: my-style
description: "Style description" description: "Style description"
@@ -44,10 +45,10 @@ custom_styles:
| `watermark.enabled` | bool | false | Enable watermark | | `watermark.enabled` | bool | false | Enable watermark |
| `watermark.content` | string | "" | Watermark text (@username or custom) | | `watermark.content` | string | "" | Watermark text (@username or custom) |
| `watermark.position` | enum | bottom-right | Position on image | | `watermark.position` | enum | bottom-right | Position on image |
| `watermark.opacity` | float | 0.7 | Transparency (0.1-1.0) |
| `preferred_style.name` | string | null | Style name or null | | `preferred_style.name` | string | null | Style name or null |
| `preferred_style.description` | string | "" | Custom notes/override | | `preferred_style.description` | string | "" | Custom notes/override |
| `language` | string | null | Output language (null = auto-detect) | | `language` | string | null | Output language (null = auto-detect) |
| `default_output_dir` | enum | null | Output directory preference (null = ask each time) |
| `custom_styles` | array | [] | User-defined styles | | `custom_styles` | array | [] | User-defined styles |
## Position Options ## Position Options
@@ -59,6 +60,14 @@ custom_styles:
| `bottom-center` | Bottom center | | `bottom-center` | Bottom center |
| `top-right` | Upper right corner | | `top-right` | Upper right corner |
## Output Directory Options
| Value | Description |
|-------|-------------|
| `same-dir` | Same directory as article |
| `illustrations-subdir` | `{article-dir}/illustrations/` subdirectory |
| `independent` | `illustrations/{topic-slug}/` in working directory |
## Custom Style Fields ## Custom Style Fields
| Field | Required | Description | | Field | Required | Description |
@@ -94,7 +103,6 @@ watermark:
enabled: true enabled: true
content: "@myaccount" content: "@myaccount"
position: bottom-right position: bottom-right
opacity: 0.5
preferred_style: preferred_style:
name: notion name: notion
@@ -0,0 +1,127 @@
# Prompt Construction
## Principles
Good prompts must include:
1. **Layout Structure First**: Describe composition, zones, flow direction
2. **Specific Data/Labels**: Use actual numbers, terms from article
3. **Visual Relationships**: How elements connect
4. **Semantic Colors**: Meaning-based color choices (red=warning, green=efficient)
5. **Style Characteristics**: Line treatment, texture, mood
6. **Aspect Ratio**: End with ratio and complexity level
## Type-Specific Templates
### Infographic
```
[Title] - Data Visualization
Layout: [grid/radial/hierarchical]
ZONES:
- Zone 1: [data point with specific values]
- Zone 2: [comparison with metrics]
- Zone 3: [summary/conclusion]
LABELS: [specific numbers, percentages, terms from article]
COLORS: [semantic color mapping]
STYLE: [style characteristics]
ASPECT: 16:9
```
### Scene
```
[Title] - Atmospheric Scene
FOCAL POINT: [main subject]
ATMOSPHERE: [lighting, mood, environment]
MOOD: [emotion to convey]
COLOR TEMPERATURE: [warm/cool/neutral]
STYLE: [style characteristics]
ASPECT: 16:9
```
### Flowchart
```
[Title] - Process Flow
Layout: [left-right/top-down/circular]
STEPS:
1. [Step name] - [brief description]
2. [Step name] - [brief description]
...
CONNECTIONS: [arrow types, decision points]
STYLE: [style characteristics]
ASPECT: 16:9
```
### Comparison
```
[Title] - Comparison View
LEFT SIDE - [Option A]:
- [Point 1]
- [Point 2]
RIGHT SIDE - [Option B]:
- [Point 1]
- [Point 2]
DIVIDER: [visual separator]
STYLE: [style characteristics]
ASPECT: 16:9
```
### Framework
```
[Title] - Conceptual Framework
STRUCTURE: [hierarchical/network/matrix]
NODES:
- [Concept 1] - [role]
- [Concept 2] - [role]
RELATIONSHIPS: [how nodes connect]
STYLE: [style characteristics]
ASPECT: 16:9
```
### Timeline
```
[Title] - Chronological View
DIRECTION: [horizontal/vertical]
EVENTS:
- [Date/Period 1]: [milestone]
- [Date/Period 2]: [milestone]
MARKERS: [visual indicators]
STYLE: [style characteristics]
ASPECT: 16:9
```
## What to Avoid
- Vague descriptions ("a nice image")
- Literal metaphor illustrations
- Missing concrete labels/annotations
- Generic decorative elements
## Watermark Integration
If watermark enabled in preferences, append:
```
Include a subtle watermark "[content]" positioned at [position] with approximately [opacity*100]% visibility.
```
@@ -0,0 +1,66 @@
# Usage
## Command Syntax
```bash
# Auto-select type and style based on content
/baoyu-article-illustrator path/to/article.md
# Specify type
/baoyu-article-illustrator path/to/article.md --type infographic
# Specify style
/baoyu-article-illustrator path/to/article.md --style blueprint
# Combine type and style
/baoyu-article-illustrator path/to/article.md --type flowchart --style notion
# Specify density
/baoyu-article-illustrator path/to/article.md --density rich
# Direct content input (paste mode)
/baoyu-article-illustrator
[paste content]
```
## Options
| Option | Description |
|--------|-------------|
| `--type <name>` | Illustration type (see Type Gallery in SKILL.md) |
| `--style <name>` | Visual style (see references/styles.md) |
| `--density <level>` | Image count: minimal / balanced / rich |
## Input Modes
| Mode | Trigger | Output Directory |
|------|---------|------------------|
| File path | `path/to/article.md` | Use `default_output_dir` preference, or ask if not set |
| Paste content | No path argument | `illustrations/{topic-slug}/` |
## Output Directory Options
| Value | Path |
|-------|------|
| `same-dir` | `{article-dir}/` |
| `illustrations-subdir` | `{article-dir}/illustrations/` |
| `independent` | `illustrations/{topic-slug}/` |
Configure in EXTEND.md: `default_output_dir: illustrations-subdir`
## Examples
**Technical article with data**:
```bash
/baoyu-article-illustrator api-design.md --type infographic --style blueprint
```
**Personal story**:
```bash
/baoyu-article-illustrator journey.md --type scene --style warm
```
**Tutorial with steps**:
```bash
/baoyu-article-illustrator how-to-deploy.md --type flowchart --density rich
```
+6
View File
@@ -201,6 +201,12 @@ npx -y bun ${SKILL_DIR}/../baoyu-image-gen/scripts/main.ts \
--image characters/characters.png --ar 4:3 --image characters/characters.png --ar 4:3
``` ```
**Compress character sheet** (recommended):
Compress to reduce token usage when used as reference image:
- Use available image compression skill (if any)
- Or system tools: `pngquant`, `optipng`, `sips` (macOS)
- **Keep PNG format**, lossless compression preferred
**7.2 Generate each page WITH character reference**: **7.2 Generate each page WITH character reference**:
| Skill Capability | Strategy | | Skill Capability | Strategy |
@@ -15,7 +15,6 @@ watermark:
enabled: false enabled: false
content: "" content: ""
position: bottom-right # bottom-right|bottom-left|bottom-center|top-right position: bottom-right # bottom-right|bottom-left|bottom-center|top-right
opacity: 0.5 # 0.1-1.0 (lower default for comics)
preferred_art: null # ligne-claire|manga|realistic|ink-brush|chalk preferred_art: null # ligne-claire|manga|realistic|ink-brush|chalk
preferred_tone: null # neutral|warm|dramatic|romantic|energetic|vintage|action preferred_tone: null # neutral|warm|dramatic|romantic|energetic|vintage|action
@@ -42,7 +41,6 @@ character_presets:
| `watermark.enabled` | bool | false | Enable watermark | | `watermark.enabled` | bool | false | Enable watermark |
| `watermark.content` | string | "" | Watermark text (@username or custom) | | `watermark.content` | string | "" | Watermark text (@username or custom) |
| `watermark.position` | enum | bottom-right | Position on image | | `watermark.position` | enum | bottom-right | Position on image |
| `watermark.opacity` | float | 0.5 | Transparency (0.1-1.0, lower for comics) |
| `preferred_art` | string | null | Art style (ligne-claire, manga, realistic, ink-brush, chalk) | | `preferred_art` | string | null | Art style (ligne-claire, manga, realistic, ink-brush, chalk) |
| `preferred_tone` | string | null | Tone (neutral, warm, dramatic, romantic, energetic, vintage, action) | | `preferred_tone` | string | null | Tone (neutral, warm, dramatic, romantic, energetic, vintage, action) |
| `preferred_layout` | string | null | Layout preference or null | | `preferred_layout` | string | null | Layout preference or null |
@@ -113,7 +111,6 @@ watermark:
enabled: true enabled: true
content: "@comicstudio" content: "@comicstudio"
position: bottom-right position: bottom-right
opacity: 0.4
preferred_art: manga preferred_art: manga
preferred_tone: neutral preferred_tone: neutral
@@ -28,19 +28,6 @@ description: Watermark configuration guide for baoyu-comic
| `bottom-center` | Webtoon vertical scroll, centered designs | Text-heavy bottom area | | `bottom-center` | Webtoon vertical scroll, centered designs | Text-heavy bottom area |
| `top-right` | **Not recommended for comics** | Always - conflicts with page numbers | | `top-right` | **Not recommended for comics** | Always - conflicts with page numbers |
## Opacity for Comics
Comics typically use lower opacity than infographics to avoid disrupting panel flow:
| Opacity | Visual Effect | Use Case |
|---------|---------------|----------|
| 0.3 | Very subtle, barely visible | **Recommended for comics** |
| 0.4 | Light presence | Balance of visibility and subtlety |
| 0.5 | Noticeable | Standard comics default |
| 0.6+ | Strong presence | Not recommended (distracting) |
**Default**: 0.5 (compared to 0.7 for infographics)
## Content Format ## Content Format
| Format | Example | Style | | Format | Example | Style |
@@ -63,10 +50,9 @@ Comics typically use lower opacity than infographics to avoid disrupting panel f
When watermark is enabled, add to image generation prompt: When watermark is enabled, add to image generation prompt:
``` ```
Include a subtle watermark "[content]" positioned at [position] Include a subtle watermark "[content]" positioned at [position].
with approximately [opacity*100]% visibility. The watermark should The watermark should be legible but not distracting from the comic panels
be legible but not distracting from the comic panels and storytelling. and storytelling. Ensure watermark does not overlap speech bubbles or key action.
Ensure watermark does not overlap speech bubbles or key action.
``` ```
## Common Issues ## Common Issues
@@ -76,5 +62,5 @@ Ensure watermark does not overlap speech bubbles or key action.
| Watermark invisible on dark panels | Adjust contrast or add subtle outline | | Watermark invisible on dark panels | Adjust contrast or add subtle outline |
| Watermark overlaps speech bubble | Change position or lower on page | | Watermark overlaps speech bubble | Change position or lower on page |
| Watermark inconsistent across pages | Use session ID for consistency | | Watermark inconsistent across pages | Use session ID for consistency |
| Watermark too prominent | Decrease opacity (0.3-0.4 for comics) | | Watermark too prominent | Change position or reduce size |
| Conflicts with page number | Never use top-right position | | Conflicts with page number | Never use top-right position |
+314 -66
View File
@@ -1,23 +1,24 @@
--- ---
name: baoyu-cover-image name: baoyu-cover-image
description: Generates article cover images with 20 hand-drawn styles and auto-style selection. 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 4 dimensions (type, style, text, mood) and 20 hand-drawn 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 "封面图".
--- ---
# Cover Image Generator # Cover Image Generator
Generate elegant cover images for articles with multiple style options. Generate elegant cover images for articles with 4-dimensional customization.
## Usage ## Usage
```bash ```bash
# Auto-select style and aspect based on content # Auto-select all dimensions based on content
/baoyu-cover-image path/to/article.md /baoyu-cover-image path/to/article.md
# Specify style # Quick mode: skip confirmation, use auto-selection
/baoyu-cover-image article.md --style blueprint /baoyu-cover-image article.md --quick
# Specify aspect ratio # Specify dimensions
/baoyu-cover-image article.md --aspect 16:9 /baoyu-cover-image article.md --type conceptual --style blueprint
/baoyu-cover-image article.md --text title-subtitle --mood bold
# Visual only (no title text) # Visual only (no title text)
/baoyu-cover-image article.md --no-title /baoyu-cover-image article.md --no-title
@@ -27,7 +28,7 @@ Generate elegant cover images for articles with multiple style options.
[paste content] [paste content]
# Direct input with options # Direct input with options
/baoyu-cover-image --style notion --aspect 1:1 /baoyu-cover-image --style notion --aspect 1:1 --quick
[paste content] [paste content]
``` ```
@@ -35,20 +36,25 @@ Generate elegant cover images for articles with multiple style options.
| Option | Description | | Option | Description |
|--------|-------------| |--------|-------------|
| `--type <name>` | Cover type (see Type Gallery) | | `--type <name>` | Cover type: hero, conceptual, typography, metaphor, scene, minimal |
| `--style <name>` | Cover style (see Style Gallery) | | `--style <name>` | Cover style (see Style Gallery) |
| `--aspect <ratio>` | 2.35:1 (default), 16:9, 1:1 | | `--text <level>` | Text density: none, title-only, title-subtitle, text-rich |
| `--mood <level>` | Emotional intensity: subtle, balanced, bold |
| `--aspect <ratio>` | 16:9 (default), 2.35:1, 4:3, 3:2, 1:1, 3:4 |
| `--lang <code>` | Title language (en, zh, ja, etc.) | | `--lang <code>` | Title language (en, zh, ja, etc.) |
| `--no-title` | Visual only, no title text | | `--no-title` | Alias for `--text none` |
| `--quick` | Skip confirmation, use auto-selection for missing dimensions |
## Two Dimensions ## Four Dimensions
| Dimension | Controls | Examples | | Dimension | Controls | Values | Default |
|-----------|----------|----------| |-----------|----------|--------|---------|
| **Type** | Visual composition, information structure | hero, conceptual, typography, metaphor, scene, minimal | | **Type** | Visual composition, information structure | hero, conceptual, typography, metaphor, scene, minimal | auto |
| **Style** | Visual aesthetics, colors, mood | elegant, blueprint, notion, warm, minimal, watercolor | | **Style** | Visual aesthetics, colors, technique | 20 built-in styles | auto |
| **Text** | Text density, information hierarchy | none, title-only, title-subtitle, text-rich | title-only |
| **Mood** | Emotional intensity, visual weight | subtle, balanced, bold | balanced |
Type × Style can be freely combined. Example: `--type conceptual --style blueprint` creates technical concept visualization with schematic aesthetics. Dimensions can be freely combined. Example: `--type conceptual --style blueprint --text title-only --mood subtle` creates a calm technical concept visualization.
## Type Gallery ## Type Gallery
@@ -101,19 +107,6 @@ When `--type` is omitted, select based on content signals:
Style definitions: [references/styles/](references/styles/) Style definitions: [references/styles/](references/styles/)
## Type × Style Compatibility
| | elegant | blueprint | notion | warm | minimal | watercolor | bold-editorial | dark-atmospheric |
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| hero | ✓✓ | ✓ | ✓ | ✓✓ | ✓ | ✓✓ | ✓✓ | ✓✓ |
| conceptual | ✓✓ | ✓✓ | ✓✓ | ✓ | ✓✓ | ✗ | ✓ | ✓ |
| typography | ✓✓ | ✓ | ✓✓ | ✓ | ✓✓ | ✓ | ✓✓ | ✓✓ |
| metaphor | ✓✓ | ✗ | ✓ | ✓✓ | ✓ | ✓✓ | ✓ | ✓ |
| scene | ✓ | ✗ | ✗ | ✓✓ | ✓ | ✓✓ | ✓ | ✓✓ |
| minimal | ✓✓ | ✓ | ✓✓ | ✓ | ✓✓ | ✓ | ✗ | ✓ |
✓✓ = highly recommended | ✓ = compatible | ✗ = not recommended
## Auto Style Selection ## Auto Style Selection
When `--style` is omitted, select based on content signals: When `--style` is omitted, select based on content signals:
@@ -140,12 +133,102 @@ When `--style` is omitted, select based on content signals:
| Lifestyle, travel | `watercolor` | | Lifestyle, travel | `watercolor` |
| Business, professional | `elegant` | | Business, professional | `elegant` |
## Text Dimension
| Value | Title | Subtitle | Tags | Use Case |
|-------|:-----:|:--------:|:----:|----------|
| `none` | - | - | - | Pure visual, no text |
| `title-only` | ✓ (≤8字) | - | - | Simple headline (default) |
| `title-subtitle` | ✓ | ✓ (≤15字) | - | Title + supporting context |
| `text-rich` | ✓ | ✓ | ✓ (2-4) | Information-dense |
Full guide: [references/dimensions/text.md](references/dimensions/text.md)
## Auto Text Selection
When `--text` is omitted, select based on content signals:
| Signals | Text Level |
|---------|------------|
| Visual-only, photography, abstract, art | `none` |
| Article, blog, standard cover | `title-only` |
| Series, tutorial, technical with context | `title-subtitle` |
| Announcement, features, multiple points, infographic | `text-rich` |
Default: `title-only`
## Mood Dimension
| Value | Contrast | Saturation | Weight | Use Case |
|-------|:--------:|:----------:|:------:|----------|
| `subtle` | Low | Muted | Light | Corporate, thought leadership |
| `balanced` | Medium | Normal | Medium | General articles (default) |
| `bold` | High | Vivid | Heavy | Announcements, promotions |
Full guide: [references/dimensions/mood.md](references/dimensions/mood.md)
## Auto Mood Selection
When `--mood` is omitted, select based on content signals:
| Signals | Mood Level |
|---------|------------|
| Professional, corporate, thought leadership, academic, luxury | `subtle` |
| General, educational, standard, blog, documentation | `balanced` |
| Launch, announcement, promotion, event, gaming, entertainment | `bold` |
Default: `balanced`
## Compatibility Matrices
### Type × Style
| | elegant | blueprint | notion | warm | minimal | watercolor | bold-editorial | dark-atmospheric |
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| hero | ✓✓ | ✓ | ✓ | ✓✓ | ✓ | ✓✓ | ✓✓ | ✓✓ |
| conceptual | ✓✓ | ✓✓ | ✓✓ | ✓ | ✓✓ | ✗ | ✓ | ✓ |
| typography | ✓✓ | ✓ | ✓✓ | ✓ | ✓✓ | ✓ | ✓✓ | ✓✓ |
| metaphor | ✓✓ | ✗ | ✓ | ✓✓ | ✓ | ✓✓ | ✓ | ✓ |
| scene | ✓ | ✗ | ✗ | ✓✓ | ✓ | ✓✓ | ✓ | ✓✓ |
| minimal | ✓✓ | ✓ | ✓✓ | ✓ | ✓✓ | ✓ | ✗ | ✓ |
### Type × Text
| | none | title-only | title-subtitle | text-rich |
|---|:---:|:---:|:---:|:---:|
| hero | ✓ | ✓✓ | ✓✓ | ✓ |
| conceptual | ✓✓ | ✓✓ | ✓ | ✓ |
| typography | ✗ | ✓ | ✓✓ | ✓✓ |
| metaphor | ✓✓ | ✓ | ✓ | ✗ |
| scene | ✓✓ | ✓ | ✓ | ✗ |
| minimal | ✓✓ | ✓✓ | ✓ | ✗ |
### Type × Mood
| | subtle | balanced | bold |
|---|:---:|:---:|:---:|
| hero | ✓ | ✓✓ | ✓✓ |
| conceptual | ✓✓ | ✓✓ | ✓ |
| typography | ✓ | ✓✓ | ✓✓ |
| metaphor | ✓✓ | ✓✓ | ✓ |
| scene | ✓✓ | ✓✓ | ✓ |
| minimal | ✓✓ | ✓✓ | ✗ |
✓✓ = highly recommended | ✓ = compatible | ✗ = not recommended
## File Structure ## File Structure
Each session creates an independent directory named by content slug: Output directory depends on `default_output_dir` preference:
| Preference | Output Path |
|------------|-------------|
| `same-dir` | `{article-dir}/` |
| `imgs-subdir` | `{article-dir}/imgs/` |
| `independent` (default) | `cover-image/{topic-slug}/` |
| Pasted content | `cover-image/{topic-slug}/` (always) |
``` ```
cover-image/{topic-slug}/ <output-dir>/
├── source-{slug}.{ext} # Source files (text, images, etc.) ├── source-{slug}.{ext} # Source files (text, images, etc.)
├── prompts/cover.md # Generation prompt ├── prompts/cover.md # Generation prompt
└── cover.png # Output image └── cover.png # Output image
@@ -174,8 +257,8 @@ Copy and track progress:
``` ```
Cover Image Progress: Cover Image Progress:
- [ ] Step 0: Check preferences (EXTEND.md) ⚠️ REQUIRED if not found - [ ] Step 0: Check preferences (EXTEND.md) ⚠️ REQUIRED if not found
- [ ] Step 1: Analyze content - [ ] Step 1: Analyze content + determine output directory ⚠️ MUST ask if not configured
- [ ] Step 2: Confirm options (type + style + aspect) ⚠️ REQUIRED - [ ] Step 2: Confirm options (4 dimensions) ⚠️ REQUIRED unless --quick or all specified
- [ ] Step 3: Create prompt - [ ] Step 3: Create prompt
- [ ] Step 4: Generate image - [ ] Step 4: Generate image
- [ ] Step 5: Completion report - [ ] Step 5: Completion report
@@ -184,7 +267,9 @@ Cover Image Progress:
### Flow ### Flow
``` ```
Input → [Step 0: Preferences/Setup] → Analyze → [Confirm: Type + Style + Aspect] → Prompt → Generate → Complete Input → [Step 0: Preferences/Setup] → Analyze → [Output Dir ⚠️] → [Confirm: 4 Dimensions] → Prompt → Generate → Complete
(skip if --quick or all specified)
``` ```
### Step 0: Load Preferences (EXTEND.md) ⚠️ ### Step 0: Load Preferences (EXTEND.md) ⚠️
@@ -226,7 +311,11 @@ Preferences loaded from [project/user]:
• Watermark: [enabled/disabled] [content if enabled] • Watermark: [enabled/disabled] [content if enabled]
• Type: [preferred_type or "auto"] • Type: [preferred_type or "auto"]
• Style: [preferred_style or "auto"] • Style: [preferred_style or "auto"]
• Text: [preferred_text or "title-only"]
• Mood: [preferred_mood or "balanced"]
• Aspect: [default_aspect] • Aspect: [default_aspect]
• Output: [default_output_dir or "not set — will ask in Step 1.5"]
• Quick mode: [enabled/disabled]
• Language: [language or "auto"] • Language: [language or "auto"]
``` ```
@@ -276,15 +365,43 @@ options:
header: "Aspect" header: "Aspect"
question: "Default aspect ratio for cover images?" question: "Default aspect ratio for cover images?"
options: options:
- label: "2.35:1 (Recommended)" - label: "16:9 (Recommended)"
description: "Cinematic widescreen, best for article headers" description: "Standard widescreen - YouTube, presentations, versatile"
- label: "16:9" - label: "2.35:1"
description: "Standard widescreen, versatile" description: "Cinematic widescreen - article headers, blog posts"
- label: "1:1" - label: "1:1"
description: "Square, social media friendly" description: "Square - Instagram, WeChat, social cards"
- label: "3:4"
description: "Portrait - Xiaohongshu, Pinterest, mobile content"
``` ```
**Q5: Save Location** Note: More ratios (4:3, 3:2) available during generation. This sets the default recommendation.
**Q5: Default Output Directory**
```yaml
header: "Output"
question: "Default output directory for cover images?"
options:
- label: "Independent (Recommended)"
description: "cover-image/{topic-slug}/ - separate from article"
- label: "Same directory"
description: "{article-dir}/ - alongside the article file"
- label: "imgs subdirectory"
description: "{article-dir}/imgs/ - images folder near article"
```
**Q6: Quick Mode**
```yaml
header: "Quick"
question: "Enable quick mode by default?"
options:
- label: "No (Recommended)"
description: "Confirm dimension choices each time"
- label: "Yes"
description: "Skip confirmation, use auto-selection"
```
**Q7: Save Location**
```yaml ```yaml
header: "Save" header: "Save"
question: "Where to save preferences?" question: "Where to save preferences?"
@@ -299,7 +416,7 @@ options:
Full setup details: `references/config/first-time-setup.md` Full setup details: `references/config/first-time-setup.md`
**EXTEND.md Supports**: Watermark | Preferred type | Preferred style | Default aspect ratio | Custom style definitions | Language preference **EXTEND.md Supports**: Watermark | Preferred type | Preferred style | Preferred text | Preferred mood | Default aspect ratio | Default output directory | Quick mode | Custom style definitions | Language preference
Schema: `references/config/preferences-schema.md` Schema: `references/config/preferences-schema.md`
@@ -321,9 +438,57 @@ Read source content, save it if needed, and perform analysis.
- Note user's input language (from conversation) - Note user's input language (from conversation)
- Compare with language preference in EXTEND.md - Compare with language preference in EXTEND.md
**1.5 Determine Output Directory** ⚠️
**MUST ask user when `default_output_dir` is not set in EXTEND.md and input is a file path.**
| Input | Behavior |
|-------|----------|
| File path + preference set | Use configured `default_output_dir` |
| File path + **no preference** | ⚠️ **MUST** ask with AskUserQuestion ↓ |
| Pasted content | `cover-image/{topic-slug}/` (always independent) |
`default_output_dir` preference values:
| Preference Value | Output Path |
|------------------|-------------|
| `same-dir` | `{article-dir}/` |
| `imgs-subdir` | `{article-dir}/imgs/` |
| `independent` | `cover-image/{topic-slug}/` |
**AskUserQuestion** (when no preference, file path input only):
- `{article-dir}/imgs/` - Images subdirectory near article
- `{article-dir}/` - Same directory as article
- `cover-image/{topic-slug}/` - Independent directory
- Save as default - Remember this choice for future runs
### Step 2: Confirm Options ⚠️ ### Step 2: Confirm Options ⚠️
**Purpose**: Validate type, style and aspect ratio. **Do NOT skip.** **Purpose**: Validate all 4 dimensions + aspect ratio.
**Skip Conditions**:
| Condition | Skipped Questions | Still Asked |
|-----------|-------------------|-------------|
| `--quick` flag | Type, Style, Text, Mood | **Aspect Ratio** (unless `--aspect` specified) |
| All 4 dimensions + `--aspect` specified | All | None |
| `quick_mode: true` in EXTEND.md | Type, Style, Text, Mood | **Aspect Ratio** (unless `--aspect` specified) |
| Otherwise | None | All 5 questions |
**Important**: Aspect ratio is ALWAYS asked unless explicitly specified via `--aspect` CLI flag. User presets in EXTEND.md are shown as recommended option, not auto-selected.
**Quick Mode Output** (when skipping 4 dimensions):
```
Quick Mode: Auto-selected dimensions
• Type: [type] ([reason])
• Style: [style] ([reason])
• Text: [text] ([reason])
• Mood: [mood] ([reason])
[Then ask Question 5: Aspect Ratio]
```
**Confirmation Flow** (when NOT skipping):
**Language**: Auto-determined (user's input language > saved preference > source language). No need to ask. **Language**: Auto-determined (user's input language > saved preference > source language). No need to ask.
@@ -364,50 +529,118 @@ options:
description: "[reason]" description: "[reason]"
``` ```
**Question 3: Aspect Ratio** (if not specified via `--aspect`) **Question 3: Text** (if not specified via `--text`)
- Based on selected Type, show compatible text levels (✓✓ first from compatibility matrix)
```yaml
header: "Text"
question: "Text density level?"
multiSelect: false
options:
- label: "title-only (Recommended)"
description: "Simple headline, ≤8 characters"
- label: "none"
description: "Pure visual, no text elements"
- label: "title-subtitle"
description: "Title + supporting context"
- label: "text-rich"
description: "Title + subtitle + keyword tags"
```
**Question 4: Mood** (if not specified via `--mood`)
- Based on content analysis, show recommended mood
```yaml
header: "Mood"
question: "Emotional intensity?"
multiSelect: false
options:
- label: "balanced (Recommended)"
description: "Medium contrast and saturation, versatile"
- label: "subtle"
description: "Low contrast, muted colors, calm"
- label: "bold"
description: "High contrast, vivid colors, dynamic"
```
**Question 5: Aspect Ratio** (ALWAYS ask unless `--aspect` specified via CLI)
Note: Even if user has a preset in EXTEND.md, still ask this question. The preset is shown as the recommended option.
```yaml ```yaml
header: "Aspect" header: "Aspect"
question: "Cover aspect ratio?" question: "Cover aspect ratio?"
multiSelect: false multiSelect: false
options: options:
- label: "2.35:1 (Recommended)" - label: "[user preset or 16:9] (Recommended)"
description: "Cinematic widescreen, best for article headers" description: "[based on preset or default: Standard widescreen, versatile]"
- label: "16:9" - label: "2.35:1"
description: "Standard widescreen, versatile" description: "Cinematic widescreen - article headers, blog posts"
- label: "4:3"
description: "Traditional screen - PPT slides, classic displays"
- label: "3:2"
description: "Photography ratio - blog articles, Medium posts"
- label: "1:1" - label: "1:1"
description: "Square, social media friendly" description: "Square - Instagram, WeChat moments, social cards"
- label: "3:4"
description: "Portrait - Xiaohongshu, Pinterest, mobile-first content"
``` ```
**After response**: Proceed to Step 3 with confirmed type + style + aspect ratio. **After response**: Proceed to Step 3 with confirmed dimensions.
### Step 3: Create Prompt ### Step 3: Create Prompt
Save to `prompts/cover.md`: Save to `prompts/cover.md`:
```markdown ```markdown
Cover theme: [2-3 words] # Content Context
Article title: [full original title from source]
Content summary: [2-3 sentence summary of key points and themes]
Keywords: [5-8 key terms extracted from content]
# Visual Design
Cover theme: [2-3 words visual interpretation]
Type: [confirmed type] Type: [confirmed type]
Style: [confirmed style] Style: [confirmed style]
Text level: [confirmed text level]
Mood: [confirmed mood]
Aspect ratio: [confirmed ratio] Aspect ratio: [confirmed ratio]
Title text: [max 8 chars, or "none" if --no-title]
Language: [confirmed language] 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]"
# Mood Application
[Based on mood level:]
- subtle: "Use low contrast, muted colors, light visual weight, calm aesthetic"
- balanced: "Use medium contrast, normal saturation, balanced visual weight"
- bold: "Use high contrast, vivid saturated colors, heavy visual weight, dynamic energy"
# Composition
Type composition: Type composition:
- [Type-specific layout and structure] - [Type-specific layout and structure]
Visual composition: Visual composition:
- Main visual: [type + style appropriate metaphor] - Main visual: [metaphor derived from content meaning]
- Layout: [positioning based on type and aspect ratio] - Layout: [positioning based on type and aspect ratio]
- Decorative: [style elements] - Decorative: [style elements that reinforce content theme]
Color scheme: [primary, background, accent from style] Color scheme: [primary, background, accent from style, adjusted by mood]
Type notes: [key characteristics from type definition] Type notes: [key characteristics from type definition]
Style notes: [key characteristics from style definition] Style notes: [key characteristics from style definition]
[Watermark section if enabled] [Watermark section if enabled]
``` ```
**Content-Driven Design**:
- Article title and summary inform the visual metaphor choice
- Keywords guide decorative elements and symbols
- The skill controls visual style; the content drives meaning
**Type-Specific Composition**: **Type-Specific Composition**:
| Type | Composition Guidelines | | Type | Composition Guidelines |
@@ -419,7 +652,7 @@ Style notes: [key characteristics from style definition]
| `scene` | Atmospheric environment, narrative elements, mood-setting lighting and colors | | `scene` | Atmospheric environment, narrative elements, mood-setting lighting and colors |
| `minimal` | Single focal element, generous whitespace (60%+), essential shapes only | | `minimal` | Single focal element, generous whitespace (60%+), essential shapes only |
**Title guidelines** (if included): **Title guidelines** (when text level includes title):
- Max 8 characters, punchy headline - Max 8 characters, punchy headline
- Use hooks: numbers, questions, contrasts - Use hooks: numbers, questions, contrasts
- Match confirmed language - Match confirmed language
@@ -427,14 +660,17 @@ Style notes: [key characteristics from style definition]
**Watermark Application** (if enabled in preferences): **Watermark Application** (if enabled in preferences):
Add to prompt: Add to prompt:
``` ```
Include a subtle watermark "[content]" positioned at [position] Include a subtle watermark "[content]" positioned at [position].
with approximately [opacity*100]% visibility. The watermark should The watermark should be legible but not distracting from the main content.
be legible but not distracting from the main content.
``` ```
Reference: `references/config/watermark-guide.md` Reference: `references/config/watermark-guide.md`
### Step 4: Generate Image ### Step 4: Generate Image
**Backup Existing Cover** (if regenerating):
If `cover.png` already exists in the output directory:
- Rename to `cover-backup-YYYYMMDD-HHMMSS.png`
**Image Generation Skill Selection**: **Image Generation Skill Selection**:
1. Check available image generation skills 1. Check available image generation skills
2. If multiple skills available, ask user preference 2. If multiple skills available, ask user preference
@@ -453,8 +689,10 @@ Cover Generated!
Topic: [topic] Topic: [topic]
Type: [type name] Type: [type name]
Style: [style name] Style: [style name]
Text: [text level]
Mood: [mood level]
Aspect: [ratio] Aspect: [ratio]
Title: [title or "visual only"] Title: [title text or "visual only"]
Language: [lang] Language: [lang]
Watermark: [enabled/disabled] Watermark: [enabled/disabled]
Location: [directory path] Location: [directory path]
@@ -463,29 +701,39 @@ Files:
✓ source-{slug}.{ext} ✓ source-{slug}.{ext}
✓ prompts/cover.md ✓ prompts/cover.md
✓ cover.png ✓ cover.png
[✓ cover-backup-{timestamp}.png (if regenerated)]
``` ```
## Image Modification ## Image Modification
| Action | Steps | | Action | Steps |
|--------|-------| |--------|-------|
| **Regenerate** | Update prompt → Regenerate with same settings | | **Regenerate** | Backup existing → Update prompt → Regenerate with same settings |
| **Change type** | Confirm new type → Update prompt → Regenerate | | **Change type** | Backup existing → Confirm new type → Update prompt → Regenerate |
| **Change style** | Confirm new style → Update prompt → Regenerate | | **Change style** | Backup existing → Confirm new style → Update prompt → Regenerate |
| **Change aspect** | Confirm new aspect → Update prompt → Regenerate | | **Change text** | Backup existing → Confirm new text level → Update prompt → Regenerate |
| **Change mood** | Backup existing → Confirm new mood → Update prompt → Regenerate |
| **Change aspect** | Backup existing → Confirm new aspect → Update prompt → Regenerate |
All modifications automatically backup the existing `cover.png` before regenerating.
## Notes ## Notes
- Cover must be readable at small preview sizes - Cover must be readable at small preview sizes
- Visual metaphors > literal representations - Visual metaphors > literal representations
- Title: max 8 chars, readable, impactful - Title: max 8 chars, readable, impactful
- **Two confirmation points**: Step 0 (first-time setup if no EXTEND.md) + Step 2 (options) - do NOT skip - **Two confirmation points**: Step 0 (first-time setup if no EXTEND.md) + Step 2 (options) - can skip Step 2 with `--quick`
- Use confirmed language for title text - Use confirmed language for title text
- Maintain watermark consistency if enabled - Maintain watermark consistency if enabled
- Check Type × Style compatibility matrix when selecting combinations - Check compatibility matrices when selecting combinations
- `--no-title` is preserved as alias for `--text none`
## References ## References
**Dimensions**:
- `references/dimensions/text.md` - Text density dimension
- `references/dimensions/mood.md` - Mood intensity dimension
**Styles**: `references/styles/<name>.md` - Style definitions **Styles**: `references/styles/<name>.md` - Style definitions
**Config**: **Config**:
@@ -13,14 +13,49 @@ Create a WeChat article cover image following these guidelines:
- Ample whitespace, highlight core message, avoid cluttered layouts - Ample whitespace, highlight core message, avoid cluttered layouts
- Main visual elements centered or slightly left (leave right side for title area if title included) - Main visual elements centered or slightly left (leave right side for title area if title included)
## Four Dimensions
### Type (Visual Composition)
- `hero`: Large focal visual (60-70% area), dramatic composition
- `conceptual`: Abstract shapes, information hierarchy, clean zones
- `typography`: Title as primary element (40%+ area), minimal visuals
- `metaphor`: Concrete object representing abstract idea, symbolic elements
- `scene`: Atmospheric environment, narrative elements, mood lighting
- `minimal`: Single focal element, generous whitespace (60%+)
### Style (Visual Aesthetics)
Apply the specified style's color palette, visual elements, and typography 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
- `text-rich`: Title + subtitle + 2-4 keyword tags, 60% visual area
### Mood (Emotional Intensity)
- `subtle`: Low contrast, muted/desaturated colors, light visual weight, calm aesthetic
- `balanced`: Medium contrast, normal saturation, balanced visual weight
- `bold`: High contrast, vivid/saturated colors, heavy visual weight, dynamic energy
## Text Style (When Title Included) ## Text Style (When Title Included)
- **ALL text MUST be hand-drawn style** - **ALL text MUST be hand-drawn style**
- Title text: Large, eye-catching, max 8 characters - Title text: Large, eye-catching, max 8 characters
- May include 1 line of subtitle or keyword tags - Subtitle: Secondary, max 15 characters (if title-subtitle or text-rich)
- Tags: 2-4 keyword badges (if text-rich)
- Font style harmonizes with illustration style - Font style harmonizes with illustration style
- **DO NOT use realistic or computer-generated fonts** - **DO NOT use realistic or computer-generated fonts**
## Mood Application
Apply mood adjustments to the base style:
| Mood | Contrast | Saturation | Weight |
|------|----------|------------|--------|
| subtle | Reduce 20-30% | Desaturate 20-30% | Lighter strokes/fills |
| balanced | Standard | Standard | Standard |
| bold | Increase 20-30% | Increase 20-30% | Heavier strokes/fills |
## Language ## Language
- Use the same language as the content provided below for any text elements - Use the same language as the content provided below for any text elements
@@ -93,7 +93,19 @@ options:
description: "Square, social media friendly" description: "Square, social media friendly"
``` ```
### Question 5: Save Location ### Question 5: Quick Mode
```
header: "Quick"
question: "Enable quick mode by default?"
options:
- label: "No (Recommended)"
description: "Confirm dimension choices each time"
- label: "Yes"
description: "Skip confirmation, use auto-selection"
```
### Question 6: Save Location
``` ```
header: "Save" header: "Save"
@@ -123,7 +135,7 @@ options:
```yaml ```yaml
--- ---
version: 1 version: 2
watermark: watermark:
enabled: [true/false] enabled: [true/false]
content: "[user input or empty]" content: "[user input or empty]"
@@ -131,12 +143,25 @@ watermark:
opacity: 0.7 opacity: 0.7
preferred_type: [selected type or null] preferred_type: [selected type or null]
preferred_style: [selected style or null] preferred_style: [selected style or null]
preferred_text: title-only
preferred_mood: balanced
default_aspect: [2.35:1/16:9/1:1] default_aspect: [2.35:1/16:9/1:1]
quick_mode: [true/false]
language: null language: null
custom_styles: [] custom_styles: []
--- ---
``` ```
## New Fields in v2
| Field | Default | Description |
|-------|---------|-------------|
| `preferred_text` | title-only | Text density (none, title-only, title-subtitle, text-rich) |
| `preferred_mood` | balanced | Mood intensity (subtle, balanced, bold) |
| `quick_mode` | false | Skip confirmation step when true |
Note: Text and Mood preferences use sensible defaults (title-only, balanced) and don't require setup questions. Users can modify these in EXTEND.md directly.
## Modifying Preferences Later ## Modifying Preferences Later
Users can edit EXTEND.md directly or run setup again: Users can edit EXTEND.md directly or run setup again:
@@ -9,20 +9,25 @@ description: EXTEND.md YAML schema for baoyu-cover-image user preferences
```yaml ```yaml
--- ---
version: 1 version: 2
watermark: watermark:
enabled: false enabled: false
content: "" content: ""
position: bottom-right # bottom-right|bottom-left|bottom-center|top-right position: bottom-right # bottom-right|bottom-left|bottom-center|top-right
opacity: 0.7 # 0.1-1.0
preferred_type: null # hero|conceptual|typography|metaphor|scene|minimal or null for auto-select preferred_type: null # hero|conceptual|typography|metaphor|scene|minimal or null for auto-select
preferred_style: null # Built-in style name or null for auto-select preferred_style: null # Built-in style name or null for auto-select
preferred_text: title-only # none|title-only|title-subtitle|text-rich
preferred_mood: balanced # subtle|balanced|bold
default_aspect: "2.35:1" # 2.35:1|16:9|1:1 default_aspect: "2.35:1" # 2.35:1|16:9|1:1
quick_mode: false # Skip confirmation when true
language: null # zh|en|ja|ko|auto (null = auto-detect) language: null # zh|en|ja|ko|auto (null = auto-detect)
custom_styles: custom_styles:
@@ -42,14 +47,16 @@ custom_styles:
| Field | Type | Default | Description | | Field | Type | Default | Description |
|-------|------|---------|-------------| |-------|------|---------|-------------|
| `version` | int | 1 | Schema version | | `version` | int | 2 | Schema version |
| `watermark.enabled` | bool | false | Enable watermark | | `watermark.enabled` | bool | false | Enable watermark |
| `watermark.content` | string | "" | Watermark text (@username or custom) | | `watermark.content` | string | "" | Watermark text (@username or custom) |
| `watermark.position` | enum | bottom-right | Position on image | | `watermark.position` | enum | bottom-right | Position on image |
| `watermark.opacity` | float | 0.7 | Transparency (0.1-1.0) |
| `preferred_type` | string | null | Type name or null for auto | | `preferred_type` | string | null | Type name or null for auto |
| `preferred_style` | string | null | Style name or null for auto | | `preferred_style` | string | null | Style name or null for auto |
| `preferred_text` | string | title-only | Text density level |
| `preferred_mood` | string | balanced | Mood intensity level |
| `default_aspect` | string | "2.35:1" | Default aspect ratio | | `default_aspect` | string | "2.35:1" | Default aspect ratio |
| `quick_mode` | bool | false | Skip confirmation step |
| `language` | string | null | Output language (null = auto-detect) | | `language` | string | null | Output language (null = auto-detect) |
| `custom_styles` | array | [] | User-defined styles | | `custom_styles` | array | [] | User-defined styles |
@@ -64,6 +71,23 @@ custom_styles:
| `scene` | Atmospheric scene, narrative feel | | `scene` | Atmospheric scene, narrative feel |
| `minimal` | Minimalist composition, generous whitespace | | `minimal` | Minimalist composition, generous whitespace |
## Text Options
| Value | Description |
|-------|-------------|
| `none` | Pure visual, no text elements |
| `title-only` | Single headline (≤8 characters) |
| `title-subtitle` | Title + subtitle (≤15 characters) |
| `text-rich` | Title + subtitle + keyword tags (2-4) |
## Mood Options
| Value | Description |
|-------|-------------|
| `subtle` | Low contrast, muted colors, calm aesthetic |
| `balanced` | Medium contrast, normal saturation, versatile |
| `bold` | High contrast, vivid colors, dynamic energy |
## Position Options ## Position Options
| Value | Description | | Value | Description |
@@ -98,12 +122,15 @@ custom_styles:
```yaml ```yaml
--- ---
version: 1 version: 2
watermark: watermark:
enabled: true enabled: true
content: "@myhandle" content: "@myhandle"
preferred_type: null preferred_type: null
preferred_style: elegant preferred_style: elegant
preferred_text: title-only
preferred_mood: balanced
quick_mode: false
--- ---
``` ```
@@ -111,19 +138,24 @@ preferred_style: elegant
```yaml ```yaml
--- ---
version: 1 version: 2
watermark: watermark:
enabled: true enabled: true
content: "myblog.com" content: "myblog.com"
position: bottom-right position: bottom-right
opacity: 0.5
preferred_type: conceptual preferred_type: conceptual
preferred_style: blueprint preferred_style: blueprint
preferred_text: title-subtitle
preferred_mood: subtle
default_aspect: "16:9" default_aspect: "16:9"
quick_mode: true
language: en language: en
custom_styles: custom_styles:
@@ -138,3 +170,16 @@ custom_styles:
best_for: "SaaS, enterprise, technical" best_for: "SaaS, enterprise, technical"
--- ---
``` ```
## Migration from v1
When loading v1 schema, auto-upgrade:
| v1 Field | v2 Field | Default Value |
|----------|----------|---------------|
| (missing) | `version` | 2 |
| (missing) | `preferred_text` | title-only |
| (missing) | `preferred_mood` | balanced |
| (missing) | `quick_mode` | false |
v1 `--no-title` flag maps to `preferred_text: none`.
@@ -28,15 +28,6 @@ description: Watermark configuration guide for baoyu-cover-image
| `bottom-center` | Centered designs | Text-heavy bottom area | | `bottom-center` | Centered designs | Text-heavy bottom area |
| `top-right` | Bottom-heavy content | Title/header in top-right | | `top-right` | Bottom-heavy content | Title/header in top-right |
## Opacity Examples
| Opacity | Visual Effect | Use Case |
|---------|---------------|----------|
| 0.3 | Very subtle, barely visible | Clean aesthetic priority |
| 0.5 | Balanced, noticeable but not distracting | Default recommendation |
| 0.7 | Clearly visible | Brand recognition priority |
| 0.9 | Strong presence | Anti-copy protection |
## Content Format ## Content Format
| Format | Example | Style | | Format | Example | Style |
@@ -51,16 +42,14 @@ description: Watermark configuration guide for baoyu-cover-image
1. **Consistency**: Use same watermark across all covers 1. **Consistency**: Use same watermark across all covers
2. **Legibility**: Ensure watermark readable on both light/dark areas 2. **Legibility**: Ensure watermark readable on both light/dark areas
3. **Size**: Keep subtle - should not distract from content 3. **Size**: Keep subtle - should not distract from content
4. **Contrast**: Adjust opacity based on cover background
## Prompt Integration ## Prompt Integration
When watermark is enabled, add to image generation prompt: When watermark is enabled, add to image generation prompt:
``` ```
Include a subtle watermark "[content]" positioned at [position] Include a subtle watermark "[content]" positioned at [position].
with approximately [opacity*100]% visibility. The watermark should The watermark should be legible but not distracting from the main content.
be legible but not distracting from the main content.
``` ```
## Cover-Specific Considerations ## Cover-Specific Considerations
@@ -75,7 +64,6 @@ be legible but not distracting from the main content.
| Issue | Solution | | Issue | Solution |
|-------|----------| |-------|----------|
| Watermark invisible | Increase opacity or adjust position | | Watermark invisible | Adjust position or check contrast |
| Watermark too prominent | Decrease opacity (0.3-0.5) | | Watermark too prominent | Change position or reduce size |
| Watermark overlaps title | Change position or reduce title area | | Watermark overlaps title | Change position or reduce title area |
| Inconsistent appearance | Use fixed opacity/position in preferences |
@@ -0,0 +1,125 @@
---
name: mood-dimension
description: Emotional intensity dimension for cover images
---
# Mood Dimension
Controls emotional intensity and visual weight of cover images.
## Values
| Value | Contrast | Saturation | Weight | Energy |
|-------|:--------:|:----------:|:------:|:------:|
| `subtle` | Low | Muted | Light | Calm |
| `balanced` | Medium | Normal | Medium | Moderate |
| `bold` | High | Vivid | Heavy | Dynamic |
## Detail
### subtle
Calm, understated visual presence.
**Characteristics**:
- Low contrast between elements
- Muted, desaturated colors
- Light visual weight
- Gentle, refined aesthetic
- Soft edges and transitions
**Use Cases**:
- Thought leadership content
- Professional/corporate communications
- Meditation, wellness topics
- Academic or scholarly articles
- Luxury brand aesthetics
**Color Guidance**:
- Pastels, earth tones, neutrals
- Low saturation (30-50%)
- Soft gradients
- Minimal color variety (2-3 colors)
### balanced
Versatile, harmonious visual presence.
**Characteristics**:
- Medium contrast
- Natural saturation levels
- Balanced visual weight
- Clear but not aggressive
- Standard aesthetic approach
**Use Cases**:
- General articles (default)
- Most blog content
- Educational material
- Product documentation
- News and updates
**Color Guidance**:
- Standard saturation (50-70%)
- Complementary color schemes
- Clear foreground/background separation
- Moderate color variety (3-4 colors)
### bold
Dynamic, high-impact visual presence.
**Characteristics**:
- High contrast between elements
- Vivid, saturated colors
- Heavy visual weight
- Energetic, attention-grabbing
- Sharp edges and strong shapes
**Use Cases**:
- Product launches
- Promotional announcements
- Event marketing
- Call-to-action content
- Entertainment/gaming topics
**Color Guidance**:
- High saturation (70-100%)
- Vibrant, primary colors
- Strong contrast ratios
- Dynamic color combinations (4+ colors)
## Type Compatibility
| Type | subtle | balanced | bold |
|------|:------:|:--------:|:----:|
| hero | ✓ | ✓✓ | ✓✓ |
| conceptual | ✓✓ | ✓✓ | ✓ |
| typography | ✓ | ✓✓ | ✓✓ |
| metaphor | ✓✓ | ✓✓ | ✓ |
| scene | ✓✓ | ✓✓ | ✓ |
| minimal | ✓✓ | ✓✓ | ✗ |
✓✓ = highly recommended | ✓ = compatible | ✗ = not recommended
## Style Interaction
Mood modifies the base style characteristics:
| Style Category | subtle | balanced | bold |
|----------------|--------|----------|------|
| Technical (blueprint, notion) | Lighter lines, softer colors | Standard rendering | Stronger contrast, sharper edges |
| Artistic (watercolor, sketch-notes) | More whitespace, lighter strokes | Standard rendering | Deeper colors, heavier strokes |
| Editorial (bold-editorial, dark-atmospheric) | Reduced contrast, softer tones | Standard rendering | Maximum impact, vivid colors |
## Auto Selection
When `--mood` is omitted, select based on signals:
| Signals | Mood Level |
|---------|------------|
| Professional, corporate, thought leadership, academic, luxury | `subtle` |
| General, educational, standard, blog, documentation | `balanced` |
| Launch, announcement, promotion, event, gaming, entertainment | `bold` |
Default: `balanced`
@@ -0,0 +1,122 @@
---
name: text-dimension
description: Text density dimension for cover images
---
# Text Dimension
Controls text density and information hierarchy on cover images.
## Values
| Value | Title | Subtitle | Tags | Visual Area |
|-------|:-----:|:--------:|:----:|:-----------:|
| `none` | - | - | - | 100% |
| `title-only` | ✓ (≤8字) | - | - | 85% |
| `title-subtitle` | ✓ | ✓ (≤15字) | - | 75% |
| `text-rich` | ✓ | ✓ | ✓ (2-4) | 60% |
## Detail
### none
Pure visual cover with no text elements.
**Use Cases**:
- Photography-focused covers
- Abstract art pieces
- Visual-only social sharing
- When title added externally
**Composition**:
- Full visual area available
- No reserved text zones
- Emphasis on visual metaphor
### title-only
Single headline, maximum impact.
**Use Cases**:
- Most article covers (default)
- Clear single message
- Strong brand recognition
**Composition**:
- Title: ≤8 characters, prominent
- Reserved zone: top or bottom 15%
- Visual supports title message
**Title Guidelines**:
- Punchy, action-oriented
- Numbers, questions, contrasts work well
- Match content language
### title-subtitle
Title with supporting context.
**Use Cases**:
- Technical articles needing clarification
- Series with episode/part info
- Content with dual messages
**Composition**:
- Title: ≤8 characters, primary
- Subtitle: ≤15 characters, secondary
- Reserved zone: 25%
- Clear hierarchy between title/subtitle
**Subtitle Guidelines**:
- Clarify or contextualize title
- Can include series name, author, date
- Smaller, less prominent than title
### text-rich
Information-dense cover with multiple text elements.
**Use Cases**:
- Infographic-style covers
- Event announcements with details
- Promotional material with features
- Content with multiple key points
**Composition**:
- Title: primary focus
- Subtitle: supporting info
- Tags: 2-4 keyword labels
- Reserved zone: 40%
- Clear visual hierarchy
**Tag Guidelines**:
- 2-4 tags maximum
- Short keywords (1-2 words each)
- Positioned as badges/labels
- Can highlight: category, date, author, key features
## Type Compatibility
| Type | none | title-only | title-subtitle | text-rich |
|------|:----:|:----------:|:--------------:|:---------:|
| hero | ✓ | ✓✓ | ✓✓ | ✓ |
| conceptual | ✓✓ | ✓✓ | ✓ | ✓ |
| typography | ✗ | ✓ | ✓✓ | ✓✓ |
| metaphor | ✓✓ | ✓ | ✓ | ✗ |
| scene | ✓✓ | ✓ | ✓ | ✗ |
| minimal | ✓✓ | ✓✓ | ✓ | ✗ |
✓✓ = highly recommended | ✓ = compatible | ✗ = not recommended
## Auto Selection
When `--text` is omitted, select based on signals:
| Signals | Text Level |
|---------|------------|
| Visual-only, photography, abstract, art | `none` |
| Article, blog, standard cover | `title-only` |
| Series, tutorial, technical with context | `title-subtitle` |
| Announcement, features, multiple points, infographic | `text-rich` |
Default: `title-only`
+20 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: baoyu-image-gen name: baoyu-image-gen
description: Generates images using official OpenAI and Google APIs via AI SDK. Supports text-to-image, reference images, aspect ratios, and quality presets. Use when user asks to "generate image with API", "use official API for images", "create image with OpenAI/Google", or needs API-based generation instead of browser-based. description: AI image generation with OpenAI and Google APIs. Supports text-to-image, reference images, aspect ratios, and parallel generation (recommended 4 concurrent subagents). Use when user asks to generate, create, or draw images.
--- ---
# Image Generation (AI SDK) # Image Generation (AI SDK)
@@ -118,6 +118,25 @@ Supported: `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `2.35:1`
- Google Imagen: uses `aspectRatio` parameter - Google Imagen: uses `aspectRatio` parameter
- OpenAI: maps to closest supported size - OpenAI: maps to closest supported size
## Parallel Generation
Supports concurrent image generation via background subagents for batch operations.
| Setting | Value |
|---------|-------|
| Recommended concurrency | 4 subagents |
| Max concurrency | 8 subagents |
| Use case | Batch generation (slides, comics, infographics) |
**Agent Implementation**:
```
# Launch multiple generations in parallel using Task tool
# Each Task runs as background subagent with run_in_background=true
# Collect results via TaskOutput when all complete
```
**Best Practice**: When generating 4+ images, spawn background subagents (recommended 4 concurrent) instead of sequential execution.
## Error Handling ## Error Handling
- Missing API key → error with setup instructions - Missing API key → error with setup instructions
@@ -2,19 +2,25 @@ import path from "node:path";
import { readFile } from "node:fs/promises"; import { readFile } from "node:fs/promises";
import type { CliArgs } from "../types"; import type { CliArgs } from "../types";
const GOOGLE_MULTIMODAL_MODELS = ["gemini-3-pro-image-preview"]; const GOOGLE_MULTIMODAL_MODELS = ["gemini-3-pro-image-preview", "gemini-3-flash-preview"];
const GOOGLE_IMAGEN_MODELS = ["imagen-3.0-generate-002", "imagen-3.0-generate-001"]; const GOOGLE_IMAGEN_MODELS = ["imagen-3.0-generate-002", "imagen-3.0-generate-001"];
export function getDefaultModel(): string { export function getDefaultModel(): string {
return process.env.GOOGLE_IMAGE_MODEL || "gemini-3-pro-image-preview"; return process.env.GOOGLE_IMAGE_MODEL || "gemini-3-pro-image-preview";
} }
function normalizeGoogleModelId(model: string): string {
return model.startsWith("models/") ? model.slice("models/".length) : model;
}
function isGoogleMultimodal(model: string): boolean { function isGoogleMultimodal(model: string): boolean {
return GOOGLE_MULTIMODAL_MODELS.some((m) => model.includes(m)); const normalized = normalizeGoogleModelId(model);
return GOOGLE_MULTIMODAL_MODELS.some((m) => normalized.includes(m));
} }
function isGoogleImagen(model: string): boolean { function isGoogleImagen(model: string): boolean {
return GOOGLE_IMAGEN_MODELS.some((m) => model.includes(m)); const normalized = normalizeGoogleModelId(model);
return GOOGLE_IMAGEN_MODELS.some((m) => normalized.includes(m));
} }
function getGoogleApiKey(): string | null { function getGoogleApiKey(): string | null {
@@ -26,6 +32,44 @@ function getGoogleImageSize(args: CliArgs): "1K" | "2K" | "4K" {
return args.quality === "2k" ? "2K" : "1K"; return args.quality === "2k" ? "2K" : "1K";
} }
function getGoogleBaseUrl(): string {
const base = process.env.GOOGLE_BASE_URL || "https://generativelanguage.googleapis.com";
return base.replace(/\/+$/g, "");
}
function buildGoogleUrl(pathname: string): string {
const base = getGoogleBaseUrl();
const cleanedPath = pathname.replace(/^\/+/g, "");
if (base.endsWith("/v1beta")) return `${base}/${cleanedPath}`;
return `${base}/v1beta/${cleanedPath}`;
}
function toModelPath(model: string): string {
const modelId = normalizeGoogleModelId(model);
return `models/${modelId}`;
}
async function postGoogleJson<T>(pathname: string, body: unknown): Promise<T> {
const apiKey = getGoogleApiKey();
if (!apiKey) throw new Error("GOOGLE_API_KEY or GEMINI_API_KEY is required");
const res = await fetch(buildGoogleUrl(pathname), {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-goog-api-key": apiKey,
},
body: JSON.stringify(body),
});
if (!res.ok) {
const err = await res.text();
throw new Error(`Google API error (${res.status}): ${err}`);
}
return (await res.json()) as T;
}
function buildPromptWithAspect(prompt: string, ar: string | null, quality: CliArgs["quality"]): string { function buildPromptWithAspect(prompt: string, ar: string | null, quality: CliArgs["quality"]): string {
let result = prompt; let result = prompt;
if (ar) { if (ar) {
@@ -37,6 +81,11 @@ function buildPromptWithAspect(prompt: string, ar: string | null, quality: CliAr
return result; return result;
} }
function addAspectRatioToPrompt(prompt: string, ar: string | null): string {
if (!ar) return prompt;
return `${prompt} Aspect ratio: ${ar}.`;
}
async function readImageAsBase64(p: string): Promise<{ data: string; mimeType: string }> { async function readImageAsBase64(p: string): Promise<{ data: string; mimeType: string }> {
const buf = await readFile(p); const buf = await readFile(p);
const ext = path.extname(p).toLowerCase(); const ext = path.extname(p).toLowerCase();
@@ -47,53 +96,74 @@ async function readImageAsBase64(p: string): Promise<{ data: string; mimeType: s
return { data: buf.toString("base64"), mimeType }; return { data: buf.toString("base64"), mimeType };
} }
function extractInlineImageData(response: {
candidates?: Array<{ content?: { parts?: Array<{ inlineData?: { data?: string } }> } }>;
}): string | null {
for (const candidate of response.candidates || []) {
for (const part of candidate.content?.parts || []) {
const data = part.inlineData?.data;
if (typeof data === "string" && data.length > 0) return data;
}
}
return null;
}
function extractPredictedImageData(response: {
predictions?: Array<any>;
generatedImages?: Array<any>;
}): string | null {
const candidates = [...(response.predictions || []), ...(response.generatedImages || [])];
for (const candidate of candidates) {
if (!candidate || typeof candidate !== "object") continue;
if (typeof candidate.imageBytes === "string") return candidate.imageBytes;
if (typeof candidate.bytesBase64Encoded === "string") return candidate.bytesBase64Encoded;
if (typeof candidate.data === "string") return candidate.data;
const image = candidate.image;
if (image && typeof image === "object") {
if (typeof image.imageBytes === "string") return image.imageBytes;
if (typeof image.bytesBase64Encoded === "string") return image.bytesBase64Encoded;
if (typeof image.data === "string") return image.data;
}
}
return null;
}
async function generateWithGemini( async function generateWithGemini(
prompt: string, prompt: string,
model: string, model: string,
args: CliArgs args: CliArgs
): Promise<Uint8Array> { ): Promise<Uint8Array> {
const { GoogleGenAI } = await import("@google/genai"); const promptWithAspect = addAspectRatioToPrompt(prompt, args.aspectRatio);
const parts: Array<{ text?: string; inlineData?: { data: string; mimeType: string } }> = [];
const apiKey = getGoogleApiKey();
if (!apiKey) throw new Error("GOOGLE_API_KEY or GEMINI_API_KEY is required");
const ai = new GoogleGenAI({
apiKey,
httpOptions: {
baseUrl: process.env.GOOGLE_BASE_URL || undefined,
},
});
const input: Array<{ type: "text" | "image"; text?: string; data?: string; mime_type?: string }> = [];
for (const refPath of args.referenceImages) { for (const refPath of args.referenceImages) {
const { data, mimeType } = await readImageAsBase64(refPath); const { data, mimeType } = await readImageAsBase64(refPath);
input.push({ type: "image", data, mime_type: mimeType }); parts.push({ inlineData: { data, mimeType } });
} }
input.push({ type: "text", text: prompt }); parts.push({ text: promptWithAspect });
const imageConfig: { image_size: "1K" | "2K" | "4K"; aspect_ratio?: string } = { const imageConfig: { imageSize: "1K" | "2K" | "4K" } = {
image_size: getGoogleImageSize(args), imageSize: getGoogleImageSize(args),
}; };
if (args.aspectRatio) {
imageConfig.aspect_ratio = args.aspectRatio;
}
console.log("Generating image with Gemini...", imageConfig); console.log("Generating image with Gemini...", imageConfig);
const interaction = await ai.interactions.create({ const response = await postGoogleJson<{
model, candidates?: Array<{ content?: { parts?: Array<{ inlineData?: { data?: string } }> } }>;
input, }>(`${toModelPath(model)}:generateContent`, {
response_modalities: ["image"], contents: [
generation_config: { {
image_config: imageConfig, role: "user",
parts,
},
],
generationConfig: {
responseModalities: ["IMAGE"],
imageConfig,
}, },
}); });
console.log("Generation completed."); console.log("Generation completed.");
for (const output of interaction.outputs || []) { const imageData = extractInlineImageData(response);
if (output.type === "image" && output.data) { if (imageData) return Uint8Array.from(Buffer.from(imageData, "base64"));
return Uint8Array.from(Buffer.from(output.data, "base64"));
}
}
throw new Error("No image in response"); throw new Error("No image in response");
} }
@@ -103,30 +173,40 @@ async function generateWithImagen(
model: string, model: string,
args: CliArgs args: CliArgs
): Promise<Uint8Array> { ): Promise<Uint8Array> {
const { experimental_generateImage: generateImage } = await import("ai");
const { createGoogleGenerativeAI } = await import("@ai-sdk/google");
const google = createGoogleGenerativeAI({
apiKey: getGoogleApiKey() || undefined,
baseURL: process.env.GOOGLE_BASE_URL,
});
const fullPrompt = buildPromptWithAspect(prompt, args.aspectRatio, args.quality); const fullPrompt = buildPromptWithAspect(prompt, args.aspectRatio, args.quality);
const imageSize = getGoogleImageSize(args);
if (imageSize === "4K") {
console.error("Warning: Imagen models do not support 4K imageSize, using 2K instead.");
}
const result = await generateImage({ const parameters: Record<string, unknown> = {
model: google.image(model), sampleCount: args.n,
prompt: fullPrompt, };
n: args.n, if (args.aspectRatio) {
aspectRatio: args.aspectRatio || undefined, parameters.aspectRatio = args.aspectRatio;
}
if (imageSize === "1K" || imageSize === "2K") {
parameters.imageSize = imageSize;
} else {
parameters.imageSize = "2K";
}
const response = await postGoogleJson<{
predictions?: Array<any>;
generatedImages?: Array<any>;
}>(`${toModelPath(model)}:predict`, {
instances: [
{
prompt: fullPrompt,
},
],
parameters,
}); });
const img = result.images[0]; const imageData = extractPredictedImageData(response);
if (!img) throw new Error("No image in response"); if (imageData) return Uint8Array.from(Buffer.from(imageData, "base64"));
if (img.uint8Array) return img.uint8Array; throw new Error("No image in response");
if (img.base64) return Uint8Array.from(Buffer.from(img.base64, "base64"));
throw new Error("Cannot extract image data");
} }
export async function generateImage( export async function generateImage(
+18 -1
View File
@@ -56,8 +56,25 @@ npx -y bun ${SKILL_DIR}/scripts/wechat-browser.ts --title "标题" --content "
### Article (文章) ### Article (文章)
Before posting, ask user to choose a theme using AskUserQuestion:
| Theme | Description |
|-------|-------------|
| `default` | 经典主题 - 传统排版,标题居中带底边,二级标题白字彩底 |
| `grace` | 优雅主题 - 文字阴影,圆角卡片,精致引用块 (by @brzhang) |
| `simple` | 简洁主题 - 现代极简风,不对称圆角,清爽留白 (by @okooo5km) |
Default: `default`. If user has already specified a theme, skip the question.
**Workflow**:
1. Generate HTML preview and print the full `htmlPath` from JSON output so user can click to preview:
```bash ```bash
npx -y bun ${SKILL_DIR}/scripts/wechat-article.ts --markdown article.md --theme grace npx -y bun ${SKILL_DIR}/scripts/md-to-wechat.ts article.md --theme <chosen-theme>
```
2. Post to WeChat:
```bash
npx -y bun ${SKILL_DIR}/scripts/wechat-article.ts --markdown article.md --theme <chosen-theme>
``` ```
## Detailed References ## Detailed References
@@ -53,7 +53,7 @@ Regular paragraph with **bold** and *italic*.
## Image Handling ## Image Handling
1. **Parse**: Images in markdown are replaced with `[[IMAGE_PLACEHOLDER_N]]` 1. **Parse**: Images in markdown are replaced with `WECHATIMGPH_N`
2. **Render**: HTML is generated with placeholders in text 2. **Render**: HTML is generated with placeholders in text
3. **Paste**: HTML content is pasted into WeChat editor 3. **Paste**: HTML content is pasted into WeChat editor
4. **Replace**: For each placeholder: 4. **Replace**: For each placeholder:
@@ -81,7 +81,7 @@ Claude:
3. Opens Chrome, navigates to WeChat editor 3. Opens Chrome, navigates to WeChat editor
4. Pastes HTML content 4. Pastes HTML content
5. For each image: 5. For each image:
- Selects [[IMAGE_PLACEHOLDER_1]] - Selects WECHATIMGPH_1
- Scrolls into view - Scrolls into view
- Presses Backspace to delete - Presses Backspace to delete
- Pastes image - Pastes image
@@ -3,6 +3,7 @@ import path from 'node:path';
import { mkdir, writeFile } from 'node:fs/promises'; import { mkdir, writeFile } from 'node:fs/promises';
import os from 'node:os'; import os from 'node:os';
import { createHash } from 'node:crypto'; import { createHash } from 'node:crypto';
import { fileURLToPath } from 'node:url';
import https from 'node:https'; import https from 'node:https';
import http from 'node:http'; import http from 'node:http';
import { spawnSync } from 'node:child_process'; import { spawnSync } from 'node:child_process';
@@ -111,17 +112,11 @@ function parseFrontmatter(content: string): { frontmatter: Record<string, string
return { frontmatter, body: match[2]! }; return { frontmatter, body: match[2]! };
} }
async function parseMarkdownForWechat( export async function convertMarkdown(markdownPath: string, options?: { title?: string; theme?: string }): Promise<ParsedResult> {
markdownPath: string,
options?: { title?: string; theme?: string; tempDir?: string },
): Promise<ParsedResult> {
const content = fs.readFileSync(markdownPath, 'utf-8');
const baseDir = path.dirname(markdownPath); const baseDir = path.dirname(markdownPath);
const tempDir = options?.tempDir ?? path.join(os.tmpdir(), 'wechat-article-images'); const content = fs.readFileSync(markdownPath, 'utf-8');
const theme = options?.theme ?? 'default'; const theme = options?.theme ?? 'default';
await mkdir(tempDir, { recursive: true });
const { frontmatter, body } = parseFrontmatter(content); const { frontmatter, body } = parseFrontmatter(content);
let title = options?.title ?? frontmatter.title ?? ''; let title = options?.title ?? frontmatter.title ?? '';
@@ -129,9 +124,9 @@ async function parseMarkdownForWechat(
const h1Match = body.match(/^#\s+(.+)$/m); const h1Match = body.match(/^#\s+(.+)$/m);
if (h1Match) title = h1Match[1]!; if (h1Match) title = h1Match[1]!;
} }
if (!title) title = path.basename(markdownPath, path.extname(markdownPath));
const author = frontmatter.author ?? ''; const author = frontmatter.author || '';
let summary = frontmatter.summary ?? frontmatter.description ?? ''; let summary = frontmatter.description || frontmatter.summary || '';
if (!summary) { if (!summary) {
const lines = body.split('\n'); const lines = body.split('\n');
@@ -161,20 +156,23 @@ async function parseMarkdownForWechat(
let imageCounter = 0; let imageCounter = 0;
const modifiedBody = body.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (match, alt, src) => { const modifiedBody = body.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (match, alt, src) => {
const placeholder = `[[IMAGE_PLACEHOLDER_${++imageCounter}]]`; const placeholder = `WECHATIMGPH_${++imageCounter}`;
images.push({ src, placeholder }); images.push({ src, placeholder });
return placeholder; return placeholder;
}); });
const modifiedMarkdown = `---\n${Object.entries(frontmatter).map(([k, v]) => `${k}: ${v}`).join('\n')}\n---\n${modifiedBody}`; const modifiedMarkdown = `---\n${Object.entries(frontmatter).map(([k, v]) => `${k}: ${v}`).join('\n')}\n---\n${modifiedBody}`;
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'wechat-article-images-'));
const tempMdPath = path.join(tempDir, 'temp-article.md'); const tempMdPath = path.join(tempDir, 'temp-article.md');
await writeFile(tempMdPath, modifiedMarkdown, 'utf-8'); await writeFile(tempMdPath, modifiedMarkdown, 'utf-8');
const scriptDir = path.dirname(new URL(import.meta.url).pathname); const __filename = fileURLToPath(import.meta.url);
const renderScript = path.join(scriptDir, 'md', 'render.ts'); const __dirname = path.dirname(__filename);
const renderScript = path.join(__dirname, 'md', 'render.ts');
console.error(`[md-to-wechat] Rendering markdown with theme: ${theme}`); console.error(`[md-to-wechat] Rendering markdown with theme: ${theme}`);
const result = spawnSync('npx', ['-y', 'bun', renderScript, tempMdPath, '--theme', theme], { const result = spawnSync('npx', ['-y', 'bun', renderScript, tempMdPath, '--theme', theme], {
stdio: ['inherit', 'pipe', 'pipe'], stdio: ['inherit', 'pipe', 'pipe'],
cwd: baseDir, cwd: baseDir,
@@ -226,8 +224,8 @@ Output JSON format:
"htmlPath": "/tmp/wechat-article-images/temp-article.html", "htmlPath": "/tmp/wechat-article-images/temp-article.html",
"contentImages": [ "contentImages": [
{ {
"placeholder": "[[IMAGE_PLACEHOLDER_1]]", "placeholder": "WECHATIMGPH_1",
"localPath": "/tmp/wechat-article-images/img.png", "localPath": "/tmp/wechat-image/img.png",
"originalPath": "imgs/image.png" "originalPath": "imgs/image.png"
} }
] ]
@@ -262,7 +260,7 @@ async function main(): Promise<void> {
} }
if (!markdownPath) { if (!markdownPath) {
console.error('Error: Markdown file path required'); console.error('Error: Markdown file path is required');
process.exit(1); process.exit(1);
} }
@@ -271,7 +269,7 @@ async function main(): Promise<void> {
process.exit(1); process.exit(1);
} }
const result = await parseMarkdownForWechat(markdownPath, { title, theme }); const result = await convertMarkdown(markdownPath, { title, theme });
console.log(JSON.stringify(result, null, 2)); console.log(JSON.stringify(result, null, 2));
} }
@@ -2,6 +2,7 @@ import fs from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import { spawnSync } from 'node:child_process'; import { spawnSync } from 'node:child_process';
import process from 'node:process'; import process from 'node:process';
import { fileURLToPath } from 'node:url';
import { launchChrome, getPageSession, waitForNewTab, clickElement, typeText, evaluate, sleep, type ChromeSession, type CdpConnection } from './cdp.ts'; import { launchChrome, getPageSession, waitForNewTab, clickElement, typeText, evaluate, sleep, type ChromeSession, type CdpConnection } from './cdp.ts';
const WECHAT_URL = 'https://mp.weixin.qq.com/'; const WECHAT_URL = 'https://mp.weixin.qq.com/';
@@ -65,8 +66,9 @@ async function clickMenuByText(session: ChromeSession, text: string): Promise<vo
} }
async function copyImageToClipboard(imagePath: string): Promise<void> { async function copyImageToClipboard(imagePath: string): Promise<void> {
const scriptDir = path.dirname(new URL(import.meta.url).pathname); const __filename = fileURLToPath(import.meta.url);
const copyScript = path.join(scriptDir, './copy-to-clipboard.ts'); const __dirname = path.dirname(__filename);
const copyScript = path.join(__dirname, './copy-to-clipboard.ts');
const result = spawnSync('npx', ['-y', 'bun', copyScript, 'image', imagePath], { stdio: 'inherit' }); const result = spawnSync('npx', ['-y', 'bun', copyScript, 'image', imagePath], { stdio: 'inherit' });
if (result.status !== 0) throw new Error(`Failed to copy image: ${imagePath}`); if (result.status !== 0) throw new Error(`Failed to copy image: ${imagePath}`);
} }
@@ -78,6 +80,30 @@ async function pasteInEditor(session: ChromeSession): Promise<void> {
await session.cdp.send('Input.dispatchKeyEvent', { type: 'keyUp', key: 'v', code: 'KeyV', modifiers, windowsVirtualKeyCode: 86 }, { sessionId: session.sessionId }); await session.cdp.send('Input.dispatchKeyEvent', { type: 'keyUp', key: 'v', code: 'KeyV', modifiers, windowsVirtualKeyCode: 86 }, { sessionId: session.sessionId });
} }
async function sendCopy(cdp?: CdpConnection, sessionId?: string): Promise<void> {
if (process.platform === 'darwin') {
spawnSync('osascript', ['-e', 'tell application "System Events" to keystroke "c" using command down']);
} else if (process.platform === 'linux') {
spawnSync('xdotool', ['key', 'ctrl+c']);
} else if (cdp && sessionId) {
await cdp.send('Input.dispatchKeyEvent', { type: 'keyDown', key: 'c', code: 'KeyC', modifiers: 2, windowsVirtualKeyCode: 67 }, { sessionId });
await sleep(50);
await cdp.send('Input.dispatchKeyEvent', { type: 'keyUp', key: 'c', code: 'KeyC', modifiers: 2, windowsVirtualKeyCode: 67 }, { sessionId });
}
}
async function sendPaste(cdp?: CdpConnection, sessionId?: string): Promise<void> {
if (process.platform === 'darwin') {
spawnSync('osascript', ['-e', 'tell application "System Events" to keystroke "v" using command down']);
} else if (process.platform === 'linux') {
spawnSync('xdotool', ['key', 'ctrl+v']);
} else if (cdp && sessionId) {
await cdp.send('Input.dispatchKeyEvent', { type: 'keyDown', key: 'v', code: 'KeyV', modifiers: 2, windowsVirtualKeyCode: 86 }, { sessionId });
await sleep(50);
await cdp.send('Input.dispatchKeyEvent', { type: 'keyUp', key: 'v', code: 'KeyV', modifiers: 2, windowsVirtualKeyCode: 86 }, { sessionId });
}
}
async function copyHtmlFromBrowser(cdp: CdpConnection, htmlFilePath: string): Promise<void> { async function copyHtmlFromBrowser(cdp: CdpConnection, htmlFilePath: string): Promise<void> {
const absolutePath = path.isAbsolute(htmlFilePath) ? htmlFilePath : path.resolve(process.cwd(), htmlFilePath); const absolutePath = path.isAbsolute(htmlFilePath) ? htmlFilePath : path.resolve(process.cwd(), htmlFilePath);
const fileUrl = `file://${absolutePath}`; const fileUrl = `file://${absolutePath}`;
@@ -108,30 +134,24 @@ async function copyHtmlFromBrowser(cdp: CdpConnection, htmlFilePath: string): Pr
}, { sessionId }); }, { sessionId });
await sleep(300); await sleep(300);
console.log('[wechat] Copying with system Cmd+C...'); console.log('[wechat] Copying content...');
if (process.platform === 'darwin') { await sendCopy(cdp, sessionId);
spawnSync('osascript', ['-e', 'tell application "System Events" to keystroke "c" using command down']);
} else {
spawnSync('xdotool', ['key', 'ctrl+c']);
}
await sleep(1000); await sleep(1000);
console.log('[wechat] Closing HTML tab...'); console.log('[wechat] Closing HTML tab...');
await cdp.send('Target.closeTarget', { targetId }); await cdp.send('Target.closeTarget', { targetId });
} }
async function pasteFromClipboardInEditor(): Promise<void> { async function pasteFromClipboardInEditor(session: ChromeSession): Promise<void> {
if (process.platform === 'darwin') { console.log('[wechat] Pasting content...');
spawnSync('osascript', ['-e', 'tell application "System Events" to keystroke "v" using command down']); await sendPaste(session.cdp, session.sessionId);
} else {
spawnSync('xdotool', ['key', 'ctrl+v']);
}
await sleep(1000); await sleep(1000);
} }
async function parseMarkdownWithPlaceholders(markdownPath: string, theme?: string): Promise<{ title: string; author: string; summary: string; htmlPath: string; contentImages: ImageInfo[] }> { async function parseMarkdownWithPlaceholders(markdownPath: string, theme?: string): Promise<{ title: string; author: string; summary: string; htmlPath: string; contentImages: ImageInfo[] }> {
const scriptDir = path.dirname(new URL(import.meta.url).pathname); const __filename = fileURLToPath(import.meta.url);
const mdToWechatScript = path.join(scriptDir, 'md-to-wechat.ts'); const __dirname = path.dirname(__filename);
const mdToWechatScript = path.join(__dirname, 'md-to-wechat.ts');
const args = ['-y', 'bun', mdToWechatScript, markdownPath]; const args = ['-y', 'bun', mdToWechatScript, markdownPath];
if (theme) args.push('--theme', theme); if (theme) args.push('--theme', theme);
@@ -295,6 +315,10 @@ export async function postArticle(options: ArticleOptions): Promise<void> {
console.log('[wechat] Clicking on editor...'); console.log('[wechat] Clicking on editor...');
await clickElement(session, '.ProseMirror'); await clickElement(session, '.ProseMirror');
await sleep(1000);
console.log('[wechat] Ensuring editor focus...');
await clickElement(session, '.ProseMirror');
await sleep(500); await sleep(500);
if (effectiveHtmlFile && fs.existsSync(effectiveHtmlFile)) { if (effectiveHtmlFile && fs.existsSync(effectiveHtmlFile)) {
@@ -302,7 +326,7 @@ export async function postArticle(options: ArticleOptions): Promise<void> {
await copyHtmlFromBrowser(cdp, effectiveHtmlFile); await copyHtmlFromBrowser(cdp, effectiveHtmlFile);
await sleep(500); await sleep(500);
console.log('[wechat] Pasting into editor...'); console.log('[wechat] Pasting into editor...');
await pasteFromClipboardInEditor(); await pasteFromClipboardInEditor(session);
await sleep(3000); await sleep(3000);
if (contentImages.length > 0) { if (contentImages.length > 0) {
@@ -328,7 +352,7 @@ export async function postArticle(options: ArticleOptions): Promise<void> {
await sleep(200); await sleep(200);
console.log('[wechat] Pasting image...'); console.log('[wechat] Pasting image...');
await pasteFromClipboardInEditor(); await pasteFromClipboardInEditor(session);
await sleep(3000); await sleep(3000);
} }
console.log('[wechat] All images inserted.'); console.log('[wechat] All images inserted.');
@@ -67,7 +67,7 @@ Code blocks become blockquotes (X doesn't support code)
1. **Cover Image**: First image or `cover_image` from frontmatter 1. **Cover Image**: First image or `cover_image` from frontmatter
2. **Remote Images**: Automatically downloaded to temp directory 2. **Remote Images**: Automatically downloaded to temp directory
3. **Placeholders**: Images in content use `[[IMAGE_PLACEHOLDER_N]]` format 3. **Placeholders**: Images in content use `XIMGPH_N` format
4. **Insertion**: Placeholders are found, selected, and replaced with actual images 4. **Insertion**: Placeholders are found, selected, and replaced with actual images
## Markdown to HTML Script ## Markdown to HTML Script
@@ -92,7 +92,7 @@ JSON output:
"coverImage": "/path/to/cover.jpg", "coverImage": "/path/to/cover.jpg",
"contentImages": [ "contentImages": [
{ {
"placeholder": "[[IMAGE_PLACEHOLDER_1]]", "placeholder": "XIMGPH_1",
"localPath": "/tmp/x-article-images/img.png", "localPath": "/tmp/x-article-images/img.png",
"blockIndex": 5 "blockIndex": 5
} }
+5 -5
View File
@@ -280,7 +280,7 @@ export async function parseMarkdown(
let imageCounter = 0; let imageCounter = 0;
const { html, totalBlocks } = convertMarkdownToHtml(body, (src, alt) => { const { html, totalBlocks } = convertMarkdownToHtml(body, (src, alt) => {
const placeholder = `[[IMAGE_PLACEHOLDER_${++imageCounter}]]`; const placeholder = `XIMGPH_${++imageCounter}`;
const currentBlockIndex = images.length; // Will be set properly after HTML generation const currentBlockIndex = images.length; // Will be set properly after HTML generation
images.push({ src, alt, blockIndex: -1 }); // blockIndex set later images.push({ src, alt, blockIndex: -1 }); // blockIndex set later
@@ -292,7 +292,7 @@ export async function parseMarkdown(
let blockIdx = 0; let blockIdx = 0;
for (const line of htmlLines) { for (const line of htmlLines) {
for (let i = 0; i < images.length; i++) { for (let i = 0; i < images.length; i++) {
const placeholder = `[[IMAGE_PLACEHOLDER_${i + 1}]]`; const placeholder = `XIMGPH_${i + 1}`;
if (line.includes(placeholder)) { if (line.includes(placeholder)) {
images[i]!.blockIndex = blockIdx; images[i]!.blockIndex = blockIdx;
} }
@@ -312,7 +312,7 @@ export async function parseMarkdown(
// First image becomes cover if no cover specified // First image becomes cover if no cover specified
if (isFirstImage && !coverImagePath) { if (isFirstImage && !coverImagePath) {
coverImagePath = localPath; coverImagePath = localPath;
coverPlaceholder = `[[IMAGE_PLACEHOLDER_${i + 1}]]`; coverPlaceholder = `XIMGPH_${i + 1}`;
isFirstImage = false; isFirstImage = false;
// Don't add to contentImages, it's the cover // Don't add to contentImages, it's the cover
continue; continue;
@@ -320,7 +320,7 @@ export async function parseMarkdown(
isFirstImage = false; isFirstImage = false;
contentImages.push({ contentImages.push({
placeholder: `[[IMAGE_PLACEHOLDER_${i + 1}]]`, placeholder: `XIMGPH_${i + 1}`,
localPath, localPath,
originalPath: img.src, originalPath: img.src,
blockIndex: img.blockIndex, blockIndex: img.blockIndex,
@@ -331,7 +331,7 @@ export async function parseMarkdown(
let finalHtml = html; let finalHtml = html;
if (coverPlaceholder) { if (coverPlaceholder) {
// Remove the placeholder and its containing <p> tag // Remove the placeholder and its containing <p> tag
finalHtml = finalHtml.replace(new RegExp(`<p>${coverPlaceholder.replace(/[[\]]/g, '\\$&')}</p>\\n?`, 'g'), ''); finalHtml = finalHtml.replace(new RegExp(`<p>${coverPlaceholder}</p>\\n?`, 'g'), '');
} }
// Resolve cover image path // Resolve cover image path
+2 -3
View File
@@ -359,9 +359,8 @@ With confirmed outline + style + layout:
**Watermark Application** (if enabled in preferences): **Watermark Application** (if enabled in preferences):
Add to each image generation prompt: Add to each image generation prompt:
``` ```
Include a subtle watermark "[content]" positioned at [position] Include a subtle watermark "[content]" positioned at [position].
with approximately [opacity*100]% visibility. The watermark should The watermark should be legible but not distracting from the main content.
be legible but not distracting from the main content.
``` ```
Reference: `references/config/watermark-guide.md` Reference: `references/config/watermark-guide.md`
@@ -15,7 +15,6 @@ watermark:
enabled: false enabled: false
content: "" content: ""
position: bottom-right # bottom-right|bottom-left|bottom-center|top-right position: bottom-right # bottom-right|bottom-left|bottom-center|top-right
opacity: 0.7 # 0.1-1.0
preferred_style: preferred_style:
name: null # Built-in or custom style name name: null # Built-in or custom style name
@@ -46,7 +45,6 @@ custom_styles:
| `watermark.enabled` | bool | false | Enable watermark | | `watermark.enabled` | bool | false | Enable watermark |
| `watermark.content` | string | "" | Watermark text (@username or custom) | | `watermark.content` | string | "" | Watermark text (@username or custom) |
| `watermark.position` | enum | bottom-right | Position on image | | `watermark.position` | enum | bottom-right | Position on image |
| `watermark.opacity` | float | 0.7 | Transparency (0.1-1.0) |
| `preferred_style.name` | string | null | Style name or null | | `preferred_style.name` | string | null | Style name or null |
| `preferred_style.description` | string | "" | Custom notes/override | | `preferred_style.description` | string | "" | Custom notes/override |
| `preferred_layout` | string | null | Layout preference or null | | `preferred_layout` | string | null | Layout preference or null |
@@ -97,7 +95,6 @@ watermark:
enabled: true enabled: true
content: "@myxhsaccount" content: "@myxhsaccount"
position: bottom-right position: bottom-right
opacity: 0.5
preferred_style: preferred_style:
name: notion name: notion
@@ -28,15 +28,6 @@ description: Watermark configuration guide for baoyu-xhs-images
| `bottom-center` | Centered designs | Text-heavy bottom area | | `bottom-center` | Centered designs | Text-heavy bottom area |
| `top-right` | Bottom-heavy content | Title/header in top-right | | `top-right` | Bottom-heavy content | Title/header in top-right |
## Opacity Examples
| Opacity | Visual Effect | Use Case |
|---------|---------------|----------|
| 0.3 | Very subtle, barely visible | Clean aesthetic priority |
| 0.5 | Balanced, noticeable but not distracting | Default recommendation |
| 0.7 | Clearly visible | Brand recognition priority |
| 0.9 | Strong presence | Anti-copy protection |
## Content Format ## Content Format
| Format | Example | Style | | Format | Example | Style |
@@ -51,23 +42,21 @@ description: Watermark configuration guide for baoyu-xhs-images
1. **Consistency**: Use same watermark across all images in series 1. **Consistency**: Use same watermark across all images in series
2. **Legibility**: Ensure watermark readable on both light/dark areas 2. **Legibility**: Ensure watermark readable on both light/dark areas
3. **Size**: Keep subtle - should not distract from content 3. **Size**: Keep subtle - should not distract from content
4. **Contrast**: Opacity may need adjustment per image background
## Prompt Integration ## Prompt Integration
When watermark is enabled, add to image generation prompt: When watermark is enabled, add to image generation prompt:
``` ```
Include a subtle watermark "[content]" positioned at [position] Include a subtle watermark "[content]" positioned at [position].
with approximately [opacity*100]% visibility. The watermark should The watermark should be legible but not distracting from the main content.
be legible but not distracting from the main content.
``` ```
## Common Issues ## Common Issues
| Issue | Solution | | Issue | Solution |
|-------|----------| |-------|----------|
| Watermark invisible | Increase opacity or adjust position | | Watermark invisible | Adjust position or check contrast |
| Watermark too prominent | Decrease opacity (0.3-0.5) | | Watermark too prominent | Change position or reduce size |
| Watermark overlaps content | Change position | | Watermark overlaps content | Change position |
| Inconsistent across images | Use session ID for consistency | | Inconsistent across images | Use session ID for consistency |