Compare commits

..

3 Commits

Author SHA1 Message Date
Jim Liu 宝玉 b15a95e73d chore: release v1.19.0 2026-01-24 01:24:41 -06:00
Jim Liu 宝玉 07af3c4c21 chore: release v1.18.3 2026-01-23 21:08:11 -06:00
Jim Liu 宝玉 7842e4d188 chore: release v1.18.2 2026-01-23 16:10:04 -06:00
21 changed files with 1766 additions and 1484 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
},
"metadata": {
"description": "Skills shared by Baoyu for improving daily work efficiency",
"version": "1.18.1"
"version": "1.19.0"
},
"plugins": [
{
+3
View File
@@ -149,3 +149,6 @@ xhs-images/
url-to-markdown/
cover-image/
slide-deck/
infographic/
illustrations/
comic/
+31
View File
@@ -2,6 +2,37 @@
English | [中文](./CHANGELOG.zh.md)
## 1.19.0 - 2026-01-24
### Features
- `baoyu-comic`: adds partial workflow options—`--storyboard-only`, `--prompts-only`, `--images-only`, and `--regenerate N` for flexible workflow control.
- `baoyu-image-gen`: adds `--imageSize` parameter for Google providers (1K/2K/4K), changes default quality to 2k.
- `baoyu-image-gen`: adds `GEMINI_API_KEY` as alias for `GOOGLE_API_KEY`.
### Refactor
- `baoyu-comic`: extracts detailed workflow to `references/workflow.md`, reduces SKILL.md by ~400 lines while preserving functionality.
- `baoyu-comic`: extracts content signal analysis to `references/auto-selection.md` and partial workflow docs to `references/partial-workflows.md`.
- `baoyu-image-gen`: modularizes code—extracts types to `types.ts`, provider implementations to `providers/google.ts` and `providers/openai.ts`.
### Documentation
- `baoyu-comic`: improves ohmsha preset documentation with explicit default Doraemon character definitions and visual descriptions.
## 1.18.3 - 2026-01-23
### Documentation
- `baoyu-comic`: improves character reference handling with explicit Strategy A/B selection—Strategy A uses `--ref` parameter for skills that support it, Strategy B embeds character descriptions in prompts for skills that don't. Includes concrete code examples for both approaches.
### Fixes
- `baoyu-image-gen`: removes unsupported Gemini models (`gemini-2.0-flash-exp-image-generation`, `gemini-2.5-flash-preview-native-audio-dialog`) from multimodal model list.
## 1.18.2 - 2026-01-23
### Refactor
- Streamline SKILL.md documentation across 7 skills (`baoyu-compress-image`, `baoyu-danger-gemini-web`, `baoyu-danger-x-to-markdown`, `baoyu-image-gen`, `baoyu-post-to-wechat`, `baoyu-post-to-x`, `baoyu-url-to-markdown`) following official best practices—reduces total documentation by ~300 lines while preserving all functionality.
### Documentation
- `CLAUDE.md`: adds official skill authoring best practices link, skill loading rules, description writing guidelines, and progressive disclosure patterns.
## 1.18.1 - 2026-01-23
### Documentation
+31
View File
@@ -2,6 +2,37 @@
[English](./CHANGELOG.md) | 中文
## 1.19.0 - 2026-01-24
### 新功能
- `baoyu-comic`:新增部分工作流选项——`--storyboard-only``--prompts-only``--images-only``--regenerate N`,实现灵活的工作流控制。
- `baoyu-image-gen`:新增 `--imageSize` 参数用于 Google 提供商(1K/2K/4K),默认质量改为 2k。
- `baoyu-image-gen`:新增 `GEMINI_API_KEY` 作为 `GOOGLE_API_KEY` 的别名。
### 重构
- `baoyu-comic`:将详细工作流提取至 `references/workflow.md`SKILL.md 减少约 400 行,功能完整保留。
- `baoyu-comic`:将内容信号分析提取至 `references/auto-selection.md`,部分工作流文档提取至 `references/partial-workflows.md`
- `baoyu-image-gen`:代码模块化——类型定义提取至 `types.ts`provider 实现提取至 `providers/google.ts``providers/openai.ts`
### 文档
- `baoyu-comic`:改进 ohmsha 预设文档,明确默认哆啦A梦角色定义和视觉描述。
## 1.18.3 - 2026-01-23
### 文档
- `baoyu-comic`:改进角色参考处理流程,新增明确的 Strategy A/B 选择逻辑——Strategy A 使用 `--ref` 参数(适用于支持该参数的技能),Strategy B 将角色描述嵌入提示词(适用于不支持的技能)。包含两种方法的具体代码示例。
### 修复
- `baoyu-image-gen`:从多模态模型列表中移除不支持的 Gemini 模型(`gemini-2.0-flash-exp-image-generation``gemini-2.5-flash-preview-native-audio-dialog`)。
## 1.18.2 - 2026-01-23
### 重构
- 精简 7 个技能的 SKILL.md 文档(`baoyu-compress-image``baoyu-danger-gemini-web``baoyu-danger-x-to-markdown``baoyu-image-gen``baoyu-post-to-wechat``baoyu-post-to-x``baoyu-url-to-markdown`),遵循官方最佳实践——总文档量减少约 300 行,功能完整保留。
### 文档
- `CLAUDE.md`:新增官方技能编写最佳实践链接、技能加载规则、描述编写指南和渐进式披露模式。
## 1.18.1 - 2026-01-23
### 文档
+127 -12
View File
@@ -78,6 +78,20 @@ npx -y bun skills/baoyu-danger-gemini-web/scripts/main.ts --promptfiles system.m
`.claude-plugin/marketplace.json` defines plugin metadata and skill paths. Version follows semver.
## Skill Loading Rules
**IMPORTANT**: When working in this project, follow these rules:
| Rule | Description |
|------|-------------|
| **Load project skills first** | MUST load all skills from `skills/` directory in current project. Project skills take priority over system/user-level skills with same name. |
| **Default image generation** | When image generation is needed, use `skills/baoyu-image-gen/SKILL.md` by default (unless user specifies otherwise). |
**Loading Priority** (highest → lowest):
1. Current project `skills/` directory
2. User-level skills (`$HOME/.baoyu-skills/`)
3. System-level skills
## Release Process
**IMPORTANT**: When user requests release/发布/push, ALWAYS use `/release-skills` workflow.
@@ -92,6 +106,22 @@ npx -y bun skills/baoyu-danger-gemini-web/scripts/main.ts --promptfiles system.m
**IMPORTANT**: All skills MUST use `baoyu-` prefix to avoid conflicts when users import this plugin.
**REQUIRED READING**: Before creating a new skill, read the official [Skill authoring best practices](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices).
### Key Requirements from Official Best Practices
| Requirement | Details |
|-------------|---------|
| **Concise is key** | Claude is smart—only add context it doesn't have. Challenge each token. |
| **name field** | Max 64 chars, lowercase letters/numbers/hyphens only, no "anthropic"/"claude" |
| **description field** | Max 1024 chars, non-empty, MUST be third person, include what + when to use |
| **SKILL.md body** | Keep under 500 lines; use separate files for additional content |
| **Naming convention** | Gerund form preferred (e.g., `processing-pdfs`), but `baoyu-` prefix required here |
| **References** | Keep one level deep from SKILL.md; avoid nested references |
| **No time-sensitive info** | Avoid dates/versions that become outdated |
### Steps
1. Create `skills/baoyu-<name>/SKILL.md` with YAML front matter
- Directory name: `baoyu-<name>`
- SKILL.md `name` field: `baoyu-<name>`
@@ -119,6 +149,21 @@ npx -y bun skills/baoyu-danger-gemini-web/scripts/main.ts --promptfiles system.m
- `description`: Brief description of the category
- `skills`: Array with the skill path
### Writing Effective Descriptions
**MUST write in third person** (not "I can help you" or "You can use this"):
```yaml
# Good
description: Generates Xiaohongshu infographic series from content. Use when user asks for "小红书图片", "XHS images", or "RedNote infographics".
# Bad
description: I can help you create Xiaohongshu images
description: You can use this to generate XHS content
```
Include both **what** the skill does and **when** to use it (triggers/keywords).
### Script Directory Template
Every SKILL.md with scripts MUST include this section after Usage:
@@ -142,6 +187,26 @@ Every SKILL.md with scripts MUST include this section after Usage:
When referencing scripts in workflow sections, use `${SKILL_DIR}/scripts/<name>.ts` so agents can resolve the correct path.
### Progressive Disclosure
For skills with extensive content, use separate reference files:
```
skills/baoyu-example/
├── SKILL.md # Main instructions (<500 lines)
├── references/
│ ├── styles.md # Style definitions (loaded as needed)
│ └── examples.md # Usage examples (loaded as needed)
└── scripts/
└── main.ts # Executable script
```
In SKILL.md, link to reference files (one level deep only):
```markdown
**Available styles**: See [references/styles.md](references/styles.md)
**Examples**: See [references/examples.md](references/examples.md)
```
## Code Style
- TypeScript throughout, no comments
@@ -155,9 +220,11 @@ Skills that require image generation MUST delegate to available image generation
### Image Generation Skill Selection
1. Check available image generation skills in `skills/` directory
2. Read each skill's SKILL.md to understand parameters and capabilities
3. If multiple image generation skills available, ask user to choose preferred skill
**Default**: Use `skills/baoyu-image-gen/SKILL.md` (unless user specifies otherwise).
1. Read `skills/baoyu-image-gen/SKILL.md` for parameters and capabilities
2. If user explicitly requests a different skill, check `skills/` directory for alternatives
3. Only ask user to choose when they haven't specified and multiple viable options exist
### Generation Flow Template
@@ -298,18 +365,66 @@ READMEs use 3-column tables for style previews:
## Extension Support
Every SKILL.md MUST include an Extension Support section at the end:
Every SKILL.md MUST include two parts for extension support:
### 1. Load Preferences Section (in Step 1 or as "Preferences" section)
For skills with workflows, add as Step 1.1. For utility skills, add as "Preferences (EXTEND.md)" section before Usage:
```markdown
**1.1 Load Preferences (EXTEND.md)**
Use Bash to check EXTEND.md existence (priority order):
\`\`\`bash
# Check project-level first
test -f .baoyu-skills/<skill-name>/EXTEND.md && echo "project"
# Then user-level (cross-platform: $HOME works on macOS/Linux/WSL)
test -f "$HOME/.baoyu-skills/<skill-name>/EXTEND.md" && echo "user"
\`\`\`
┌────────────────────────────────────────────┬───────────────────┐
│ Path │ Location │
├────────────────────────────────────────────┼───────────────────┤
│ .baoyu-skills/<skill-name>/EXTEND.md │ Project directory │
├────────────────────────────────────────────┼───────────────────┤
│ $HOME/.baoyu-skills/<skill-name>/EXTEND.md │ User home │
└────────────────────────────────────────────┴───────────────────┘
┌───────────┬───────────────────────────────────────────────────────────────────────────┐
│ Result │ Action │
├───────────┼───────────────────────────────────────────────────────────────────────────┤
│ Found │ Read, parse, display summary │
├───────────┼───────────────────────────────────────────────────────────────────────────┤
│ Not found │ Ask user with AskUserQuestion (see references/config/first-time-setup.md) │
└───────────┴───────────────────────────────────────────────────────────────────────────┘
**EXTEND.md Supports**: [List supported configuration options for this skill]
Schema: `references/config/preferences-schema.md`
```
### 2. Extension Support Section (at the end)
Simplified section that references the preferences section:
```markdown
## Extension Support
Custom styles and configurations via EXTEND.md.
**Check paths** (priority order):
1. `.baoyu-skills/<skill-name>/EXTEND.md` (project)
2. `~/.baoyu-skills/<skill-name>/EXTEND.md` (user)
If found, load before Step 1. Extension content overrides defaults.
Custom configurations via EXTEND.md. See **Step 1.1** for paths and supported options.
```
Replace `<skill-name>` with the actual skill name (e.g., `baoyu-cover-image`).
Or for utility skills without workflow steps:
```markdown
## Extension Support
Custom configurations via EXTEND.md. See **Preferences** section for paths and supported options.
```
**Notes**:
- Replace `<skill-name>` with actual skill name (e.g., `baoyu-cover-image`)
- Use `$HOME` instead of `~` for cross-platform compatibility (macOS/Linux/WSL)
- Use `test -f` Bash command for explicit file existence check
- ASCII tables for clear visual formatting
+130 -358
View File
@@ -27,6 +27,17 @@ Create original knowledge comics with flexible art style × tone combinations.
| `--aspect` | 3:4 (default, portrait), 4:3 (landscape), 16:9 (widescreen) | Page aspect ratio |
| `--lang` | auto (default), zh, en, ja, etc. | Output language |
### Partial Workflow Options
| Option | Description |
|--------|-------------|
| `--storyboard-only` | Generate storyboard only, skip prompts and images |
| `--prompts-only` | Generate storyboard + prompts, skip images |
| `--images-only` | Generate images from existing prompts directory |
| `--regenerate N` | Regenerate specific page(s) only (e.g., `3` or `2,5,8`) |
Details: [references/partial-workflows.md](references/partial-workflows.md)
### Art Styles (画风)
| Style | 中文 | Description |
@@ -69,27 +80,25 @@ Presets with special rules beyond art+tone:
| ink-brush | neutral, dramatic, action, vintage | warm | romantic, energetic |
| chalk | neutral, warm, energetic | vintage | dramatic, action, romantic |
Art Style × Tone × Layout can be freely combined. Incompatible combinations work but may produce unexpected results.
Details: [references/auto-selection.md](references/auto-selection.md)
## Auto Selection
Content signals determine default art + tone + layout (or preset):
| Content Signals | Art Style | Tone | Layout | Preset |
|-----------------|-----------|------|--------|--------|
| Tutorial, how-to, beginner | manga | neutral | webtoon | **ohmsha** |
| Computing, AI, programming | manga | neutral | dense | **ohmsha** |
| Technical explanation, educational | manga | neutral | webtoon | **ohmsha** |
| Pre-1950, classical, ancient | realistic | vintage | cinematic | - |
| Personal story, mentor | ligne-claire | warm | standard | - |
| Conflict, breakthrough | (inherit) | dramatic | splash | - |
| Wine, food, business, lifestyle | realistic | neutral | cinematic | - |
| Martial arts, wuxia, xianxia | ink-brush | action | splash | **wuxia** |
| Romance, love, school life | manga | romantic | standard | **shoujo** |
| Biography, balanced | ligne-claire | neutral | mixed | - |
| Content Signals | Recommended |
|-----------------|-------------|
| Tutorial, how-to, programming, educational | **ohmsha** preset |
| Pre-1950, classical, ancient | realistic + vintage |
| Personal story, mentor | ligne-claire + warm |
| Martial arts, wuxia | **wuxia** preset |
| Romance, school life | **shoujo** preset |
| Biography, balanced | ligne-claire + neutral |
**When preset is recommended**: Load `references/presets/{preset}.md` and apply all special rules.
Details: [references/auto-selection.md](references/auto-selection.md)
## Script Directory
**Important**: All scripts are located in the `scripts/` subdirectory of this skill.
@@ -111,384 +120,147 @@ Output directory: `comic/{topic-slug}/`
- Conflict: append timestamp (e.g., `turing-story-20260118-143052`)
**Contents**:
- `source-{slug}.{ext}` - Source files
- `analysis.md` - Content analysis
- `storyboard.md` - Storyboard with panel breakdown
- `characters/characters.md` - Character definitions
- `characters/characters.png` - Character reference sheet
- `prompts/NN-{cover|page}-[slug].md` - Generation prompts
- `NN-{cover|page}-[slug].png` - Generated images
- `{topic-slug}.pdf` - Final merged PDF
| File | Description |
|------|-------------|
| `source-{slug}.{ext}` | Source files |
| `analysis.md` | Content analysis |
| `storyboard.md` | Storyboard with panel breakdown |
| `characters/characters.md` | Character definitions |
| `characters/characters.png` | Character reference sheet |
| `prompts/NN-{cover\|page}-[slug].md` | Generation prompts |
| `NN-{cover\|page}-[slug].png` | Generated images |
| `{topic-slug}.pdf` | Final merged PDF |
## Language Handling
**Detection Priority**:
1. `--lang` flag (explicit)
2. EXTEND.md `language` setting
3. User's conversation language
4. Source content language
**Rule**: Use user's input language or saved language preference for ALL interactions:
- Storyboard outlines and scene descriptions
- Image generation prompts
- User selection options and confirmations
- Progress updates, questions, errors, summaries
Technical terms remain in English.
## Workflow
### Progress Checklist
Copy and track progress:
```
Comic Progress:
- [ ] Step 1: Load preferences + Analyze content
- [ ] Step 2: Confirmation 1 - Style & options ⚠️ REQUIRED
- [ ] Step 1: Setup & Analyze (1.1 Preferences, 1.2 Analyze, 1.3 Check existing)
- [ ] Step 2: Confirmation - Style & options ⚠️ REQUIRED
- [ ] Step 3: Generate storyboard + characters
- [ ] Step 4: Confirmation 2 - Review outline (if requested)
- [ ] Step 5: Generate images (sequential)
- [ ] Step 6: Merge to PDF
- [ ] Step 7: Completion report
- [ ] Step 4: Review outline (conditional)
- [ ] Step 5: Generate prompts
- [ ] Step 6: Review prompts (conditional)
- [ ] Step 7: Generate images ⚠️ CHARACTER REF REQUIRED
- [ ] 7.1 Generate character sheet FIRST → characters/characters.png
- [ ] 7.2 Generate pages WITH --ref characters/characters.png
- [ ] Step 8: Merge to PDF
- [ ] Step 9: Completion report
```
### Flow
```
Input → Preferences → Analyze → [Confirm 1: Style + Skip?] → Storyboard → [Confirm 2: if needed] → Generate → PDF → Complete
Input → Preferences → Analyze → [Check Existing?] → [Confirm: Style + Reviews] → Storyboard → [Review?] → Prompts → [Review?] → Images → PDF → Complete
```
### Step 1: Setup & Analyze
### Step Summary
**1.1 Load Preferences (EXTEND.md)**
| Step | Action | Key Output |
|------|--------|------------|
| 1.1 | Load EXTEND.md preferences | Config loaded |
| 1.2 | Analyze content | `analysis.md` |
| 1.3 | Check existing directory | Handle conflicts |
| 2 | Confirm style, focus, audience, reviews | User preferences |
| 3 | Generate storyboard + characters | `storyboard.md`, `characters/` |
| 4 | Review outline (if requested) | User approval |
| 5 | Generate prompts | `prompts/*.md` |
| 6 | Review prompts (if requested) | User approval |
| **7.1** | **Generate character sheet FIRST** | `characters/characters.png` |
| **7.2** | Generate pages **with character ref** | `*.png` files |
| 8 | Merge to PDF | `{slug}.pdf` |
| 9 | Completion report | Summary |
Use Bash to check EXTEND.md existence (priority order):
### Step 7: Image Generation ⚠️ CRITICAL
**Character reference is MANDATORY for visual consistency.**
**7.1 Generate character sheet first**:
```bash
# Use Reference Sheet Prompt from characters/characters.md
npx -y bun ${SKILL_DIR}/../baoyu-image-gen/scripts/main.ts \
--promptfiles characters/characters.md \
--image characters/characters.png --ar 4:3
```
**7.2 Generate each page WITH character reference**:
| Skill Capability | Strategy |
|------------------|----------|
| Supports `--ref` | Pass `characters/characters.png` with EVERY page |
| No `--ref` support | Prepend character descriptions to EVERY prompt file |
```bash
# Check project-level first
test -f .baoyu-skills/baoyu-comic/EXTEND.md && echo "project"
# Then user-level (cross-platform: $HOME works on macOS/Linux/WSL)
test -f "$HOME/.baoyu-skills/baoyu-comic/EXTEND.md" && echo "user"
# Example: ALWAYS include --ref for consistency
npx -y bun ${SKILL_DIR}/../baoyu-image-gen/scripts/main.ts \
--promptfiles prompts/01-page-xxx.md \
--image 01-page-xxx.png --ar 3:4 \
--ref characters/characters.png
```
┌──────────────────────────────────────────────┬───────────────────┐
│ Path │ Location │
├──────────────────────────────────────────────┼───────────────────┤
│ .baoyu-skills/baoyu-comic/EXTEND.md │ Project directory │
├──────────────────────────────────────────────┼───────────────────┤
│ $HOME/.baoyu-skills/baoyu-comic/EXTEND.md │ User home │
└──────────────────────────────────────────────┴───────────────────┘
**Full workflow details**: [references/workflow.md](references/workflow.md)
**When EXTEND.md Found** → Read, parse, **output summary to user**:
### EXTEND.md Paths
```
📋 Loaded preferences from [full path]
├─ Watermark: [enabled/disabled] [content if enabled]
├─ Art Style: [style name or "auto-select"]
├─ Tone: [tone name or "auto-select"]
├─ Layout: [layout or "auto-select"]
├─ Language: [language or "auto-detect"]
└─ Character presets: [count] defined
```
**MUST output this summary** so user knows their current configuration. Do not skip or silently load.
**When EXTEND.md Not Found** → First-time setup:
1. Inform user: "No preferences found. Let's set up your defaults."
2. Use AskUserQuestion to collect preferences (see `references/config/first-time-setup.md`)
3. Create EXTEND.md at user-chosen location
4. Confirm: "✓ Preferences saved to [path]"
| Path | Location |
|------|----------|
| `.baoyu-skills/baoyu-comic/EXTEND.md` | Project directory |
| `$HOME/.baoyu-skills/baoyu-comic/EXTEND.md` | User home |
**EXTEND.md Supports**: Watermark | Preferred art/tone/layout | Custom style definitions | Character presets | Language preference
Schema: `references/config/preferences-schema.md`
**Important**: Once EXTEND.md exists, watermark, language, and style defaults are NOT asked again in Confirmation 1 or 2. These are session-persistent settings.
**1.2 Analyze Content → `analysis.md`**
Read source content, save it if needed, and perform deep analysis.
**Actions**:
1. **Save source content** (if not already a file):
- If user provides a file path: use as-is
- If user pastes content: save to `source.md` in target directory
2. Read source content
3. **Deep analysis** following `references/analysis-framework.md`:
- Target audience identification
- Value proposition for readers
- Core themes and narrative potential
- Key figures and their story arcs
4. Detect source language
5. **Determine language**:
- If EXTEND.md has `language` → use it
- Else if `--lang` option provided → use it
- Else → use detected source language
6. Determine recommended page count:
- Short story: 5-8 pages
- Medium complexity: 9-15 pages
- Full biography: 16-25 pages
7. Analyze content signals for art/tone/layout recommendations
8. **Save to `analysis.md`**
**analysis.md Format**: YAML front matter (title, topic, time_span, source_language, user_language, aspect_ratio, recommended_page_count, recommended_art, recommended_tone) + sections for Target Audience, Value Proposition, Core Themes, Key Figures & Story Arcs, Content Signals, Recommended Approaches. See `references/analysis-framework.md` for full template.
### Step 2: Confirmation 1 - Style & Options ⚠️
**Purpose**: Select visual style + decide whether to review outline before generation. **Do NOT skip.**
**Note**: Watermark and language already configured in EXTEND.md (Step 1).
**Display summary**:
- Content type + topic identified
- Key figures extracted
- Time span detected
- Recommended page count
- Language: [from EXTEND.md or detected]
- **Recommended style**: [art] + [tone] (based on content signals)
**Use AskUserQuestion** for:
**Question 1: Visual Style**
If a preset is recommended (see Auto Selection), show it first:
```
header: "Style"
question: "Which visual style for this comic?"
options:
- label: "[preset name] preset (Recommended)" # If preset recommended
description: "[preset description] - includes special rules"
- label: "[recommended art] + [recommended tone] (Recommended)" # If no preset
description: "Best match for your content based on analysis"
- label: "ligne-claire + neutral"
description: "Classic educational, Logicomix style"
- label: "ohmsha preset"
description: "Educational manga with visual metaphors, gadgets, NO talking heads"
- label: "Custom"
description: "Specify your own art + tone or preset"
```
**Preset vs Art+Tone**: Presets include special rules beyond art+tone. `ohmsha` = manga + neutral + visual metaphor rules + character roles + NO talking heads. Plain `manga + neutral` does NOT include these rules.
**Question 2: Narrative Focus** (multiSelect: true)
```
header: "Focus"
question: "What should the comic emphasize? (Select all that apply)"
options:
- label: "Biography/life story"
description: "Follow a person's journey through key life events"
- label: "Concept explanation"
description: "Break down complex ideas visually"
- label: "Historical event"
description: "Dramatize important historical moments"
- label: "Tutorial/how-to"
description: "Step-by-step educational guide"
```
**Question 3: Target Audience**
```
header: "Audience"
question: "Who is the primary reader?"
options:
- label: "General readers"
description: "Broad appeal, accessible content"
- label: "Students/learners"
description: "Educational focus, clear explanations"
- label: "Industry professionals"
description: "Technical depth, domain knowledge"
- label: "Children/young readers"
description: "Simplified language, engaging visuals"
```
**Question 4: Outline Review**
```
header: "Review"
question: "Do you want to review the outline before image generation?"
options:
- label: "Yes, let me review (Recommended)"
description: "Review storyboard and characters before generating images"
- label: "No, generate directly"
description: "Skip outline review, start generating immediately"
```
**After response**:
1. Update `analysis.md` with user preferences
2. **Store `skip_outline_review`** flag based on Question 4 response
3. → Step 3
### Step 3: Generate Storyboard + Characters
Create storyboard and character definitions using the confirmed style from Step 2.
**Loading Style References**:
- Art style: `references/art-styles/{art}.md`
- Tone: `references/tones/{tone}.md`
- If preset (ohmsha/wuxia/shoujo): also load `references/presets/{preset}.md`
**Generate**:
1. **Storyboard** (`storyboard.md`):
- YAML front matter with art_style, tone, layout, aspect_ratio
- Cover design
- Each page: layout, panel breakdown, visual prompts
- **Written in user's preferred language** (from Step 1)
- Reference: `references/storyboard-template.md`
- **If using preset**: Load and apply preset rules from `references/presets/`
2. **Character definitions** (`characters/characters.md`):
- Visual specs matching the art style (in user's preferred language)
- Include Reference Sheet Prompt for later image generation
- Reference: `references/character-template.md`
**After generation**:
- If `skip_outline_review` is true → Skip Step 4, go directly to Step 5
- If `skip_outline_review` is false → Continue to Step 4
### Step 4: Confirmation 2 - Review Outline (Conditional)
**Skip this step** if user selected "No, generate directly" in Step 2.
**Purpose**: User reviews and confirms storyboard + characters before generation.
**Display**:
- Page count and structure
- Art style + Tone combination
- Page-by-page summary (Cover → P1 → P2...)
- Character list with brief descriptions
**Use AskUserQuestion**:
```
header: "Confirm"
question: "Ready to generate images with this outline?"
options:
- label: "Yes, proceed (Recommended)"
description: "Generate character sheet and comic pages"
- label: "Edit storyboard first"
description: "I'll modify storyboard.md before continuing"
- label: "Edit characters first"
description: "I'll modify characters/characters.md before continuing"
- label: "Edit both"
description: "I'll modify both files before continuing"
```
**After response**:
1. If user wants to edit → Wait for user to finish editing, then ask again
2. If user confirms → Continue to Step 5
### Step 5: Generate Images
With confirmed storyboard + art style + tone + aspect ratio:
**Style Reference Loading**:
- Read `references/art-styles/{art}.md` for rendering guidelines
- Read `references/tones/{tone}.md` for mood/color adjustments
- If preset: Read `references/presets/{preset}.md` for special rules
**5.1 Generate Character Reference Sheet** (first):
1. Use Reference Sheet Prompt from `characters/characters.md`
2. Generate → `characters/characters.png`
3. This ensures visual consistency for all subsequent pages
**5.2 Generate Comic Pages**:
**For each page (cover + pages)**:
1. Save prompt to `prompts/NN-{cover|page}-[slug].md` (in user's preferred language)
2. Generate image using confirmed art style and tone guidelines
3. Report progress after each generation
**Watermark Application** (if enabled in preferences):
Add to each image generation prompt:
```
Include a subtle watermark "[content]" positioned at [position]
with approximately [opacity*100]% visibility. The watermark should
be legible but not distracting from the comic panels and storytelling.
Ensure watermark does not overlap speech bubbles or key action.
```
Reference: `references/config/watermark-guide.md`
**Image Generation Skill Selection**:
- Check available image generation skills
- If multiple skills available, ask user preference
**Character Reference Handling** (IMPORTANT for visual consistency):
1. Read the selected image generation skill's SKILL.md
2. Check if it supports reference image input (look for parameters like `--ref`, `--reference`, `--image-ref`, etc.)
| Skill Support | Action |
|---------------|--------|
| **Supports reference image** | Pass `characters/characters.png` using the skill's reference parameter |
| **Does NOT support reference image** | Prepend `characters/characters.md` content to each page prompt |
Always verify the exact parameter name from the skill's documentation before calling.
**Session Management**:
If image generation skill supports `--sessionId`:
1. Generate unique session ID: `comic-{topic-slug}-{timestamp}`
2. Use same session ID for all pages
3. Ensures visual consistency across generated images
### Step 6: Merge to PDF
After all images generated:
```bash
npx -y bun ${SKILL_DIR}/scripts/merge-to-pdf.ts <comic-dir>
```
Creates `{topic-slug}.pdf` with all pages as full-page images.
### Step 7: Completion Report
```
Comic Complete!
Title: [title] | Art: [art] | Tone: [tone] | Pages: [count] | Aspect: [ratio] | Language: [lang]
Watermark: [enabled/disabled]
Location: [path]
✓ analysis.md
✓ characters.png
✓ 00-cover-[slug].png ... NN-page-[slug].png
✓ {topic-slug}.pdf
```
## Page Modification
| Action | Steps |
|--------|-------|
| **Edit** | Update prompt → Regenerate image → Regenerate PDF |
| **Add** | Create prompt at position → Generate image → Renumber subsequent (NN+1) → Update storyboard → Regenerate PDF |
| **Delete** | Remove files → Renumber subsequent (NN-1) → Update storyboard → Regenerate PDF |
**File naming**: `NN-{cover|page}-[slug].png` (e.g., `03-page-enigma-machine.png`)
- Slugs: kebab-case, unique, derived from content
- Renumbering: Update NN prefix only, slugs unchanged
## Preset: Ohmsha
Default: Use Doraemon characters (大雄=learner, 哆啦A梦=mentor, 胖虎=challenge, 静香=support). Custom characters via `--characters "Student:小明,Mentor:教授"` or character presets in EXTEND.md.
Requirements: Visual metaphors, NO talking heads, narrative page titles.
See `references/presets/ohmsha.md` and `references/ohmsha-guide.md` for details.
Schema: [references/config/preferences-schema.md](references/config/preferences-schema.md)
## References
Detailed templates and guidelines in `references/` directory:
**Core Templates**:
- `analysis-framework.md` - Deep content analysis for comic adaptation
- `character-template.md` - Character definition format and examples
- `storyboard-template.md` - Storyboard structure and panel breakdown
- `ohmsha-guide.md` - Ohmsha manga style specifics
- [analysis-framework.md](references/analysis-framework.md) - Deep content analysis
- [character-template.md](references/character-template.md) - Character definition format
- [storyboard-template.md](references/storyboard-template.md) - Storyboard structure
- [ohmsha-guide.md](references/ohmsha-guide.md) - Ohmsha manga specifics
**Style Definitions** (new 3-dimension system):
- `art-styles/` - Art style definitions (ligne-claire, manga, realistic, ink-brush, chalk)
- `tones/` - Tone definitions (neutral, warm, dramatic, romantic, energetic, vintage, action)
- `presets/` - Preset shortcuts with special rules (ohmsha, wuxia, shoujo)
- `layouts/` - Layout definitions (standard, cinematic, dense, splash, mixed, webtoon)
**Style Definitions**:
- `references/art-styles/` - Art styles (ligne-claire, manga, realistic, ink-brush, chalk)
- `references/tones/` - Tones (neutral, warm, dramatic, romantic, energetic, vintage, action)
- `references/presets/` - Presets with special rules (ohmsha, wuxia, shoujo)
- `references/layouts/` - Layouts (standard, cinematic, dense, splash, mixed, webtoon)
**Workflow**:
- [workflow.md](references/workflow.md) - Full workflow details
- [auto-selection.md](references/auto-selection.md) - Content signal analysis
- [partial-workflows.md](references/partial-workflows.md) - Partial workflow options
**Config**:
- `config/preferences-schema.md` - EXTEND.md schema
- `config/first-time-setup.md` - First-time setup flow
- `config/watermark-guide.md` - Watermark configuration
- [config/preferences-schema.md](references/config/preferences-schema.md) - EXTEND.md schema
- [config/first-time-setup.md](references/config/first-time-setup.md) - First-time setup
- [config/watermark-guide.md](references/config/watermark-guide.md) - Watermark configuration
## Notes
- Auto-retry once on failure | Cartoon alternatives for sensitive figures
- Use confirmed language preference | Maintain style consistency
- **Step 2 confirmation required** - do not skip (style selection + outline review option)
- **Step 4 conditional** - only if user requested outline review in Step 2
- Watermark/language configured once in EXTEND.md, not asked in confirmations
## Extension Support
Custom configurations via EXTEND.md. See **Step 1.1** for paths and supported options.
- Image generation: 10-30 seconds per page
- Auto-retry once on generation failure
- Use stylized alternatives for sensitive public figures
- Maintain style consistency via session ID
- **Step 2 confirmation required** - do not skip
- **Steps 4/6 conditional** - only if user requested in Step 2
- **Step 7.1 character sheet MUST be generated before pages** - ensures consistency
- **Step 7.2 EVERY page MUST reference characters** - use `--ref` or embed descriptions
- Watermark/language configured once in EXTEND.md
@@ -0,0 +1,58 @@
# Auto Selection
Content signals determine default art + tone + layout (or preset).
## Content Signal Matrix
| Content Signals | Art Style | Tone | Layout | Preset |
|-----------------|-----------|------|--------|--------|
| Tutorial, how-to, beginner | manga | neutral | webtoon | **ohmsha** |
| Computing, AI, programming | manga | neutral | dense | **ohmsha** |
| Technical explanation, educational | manga | neutral | webtoon | **ohmsha** |
| Pre-1950, classical, ancient | realistic | vintage | cinematic | - |
| Personal story, mentor | ligne-claire | warm | standard | - |
| Conflict, breakthrough | (inherit) | dramatic | splash | - |
| Wine, food, business, lifestyle | realistic | neutral | cinematic | - |
| Martial arts, wuxia, xianxia | ink-brush | action | splash | **wuxia** |
| Romance, love, school life | manga | romantic | standard | **shoujo** |
| Biography, balanced | ligne-claire | neutral | mixed | - |
## Preset Recommendation Rules
**When preset is recommended**: Load `presets/{preset}.md` and apply all special rules.
### ohmsha
- **Triggers**: Tutorial, technical, educational, computing, programming, how-to, beginner
- **Special rules**: Visual metaphors, NO talking heads, gadget reveals, Doraemon-style characters
- **Base**: manga + neutral + webtoon/dense
### wuxia
- **Triggers**: Martial arts, wuxia, xianxia, cultivation, swordplay
- **Special rules**: Qi effects, combat visuals, atmospheric elements
- **Base**: ink-brush + action + splash
### shoujo
- **Triggers**: Romance, love story, school life, emotional drama
- **Special rules**: Decorative elements, eye details, romantic beats
- **Base**: manga + romantic + standard
## Compatibility Matrix
Art Style × Tone combinations work best when matched appropriately:
| Art Style | ✓✓ Best | ✓ Works | ✗ Avoid |
|-----------|---------|---------|---------|
| ligne-claire | neutral, warm | dramatic, vintage, energetic | romantic, action |
| manga | neutral, romantic, energetic, action | warm, dramatic | vintage |
| realistic | neutral, warm, dramatic, vintage | action | romantic, energetic |
| ink-brush | neutral, dramatic, action, vintage | warm | romantic, energetic |
| chalk | neutral, warm, energetic | vintage | dramatic, action, romantic |
**Note**: Art Style × Tone × Layout can be freely combined. Incompatible combinations work but may produce unexpected results.
## Priority Order
1. User-specified options (`--art`, `--tone`, `--style`)
2. EXTEND.md defaults
3. Content signal analysis → auto-selection
4. Fallback: ligne-claire + neutral + standard
@@ -0,0 +1,123 @@
# Partial Workflows
Options to run specific parts of the workflow.
## Options Summary
| Option | Steps Executed | Output |
|--------|----------------|--------|
| `--storyboard-only` | 1-3 | `storyboard.md` + `characters/` |
| `--prompts-only` | 1-5 | + `prompts/*.md` |
| `--images-only` | 7-9 | + images + PDF |
| `--regenerate N` | 7 (partial) | Specific page(s) + PDF |
---
## Using `--storyboard-only`
Generate storyboard and characters without prompts or images:
```bash
/baoyu-comic content.md --storyboard-only
```
**Workflow**: Steps 1-3 only (stop after storyboard + characters)
**Output**:
- `analysis.md`
- `storyboard.md`
- `characters/characters.md`
**Use case**: Review and edit the storyboard before generating images. Useful for:
- Getting feedback on the narrative structure
- Making manual adjustments to panel layouts
- Defining custom characters
---
## Using `--prompts-only`
Generate storyboard, characters, and prompts without images:
```bash
/baoyu-comic content.md --prompts-only
```
**Workflow**: Steps 1-5 (generate prompts, skip images)
**Output**:
- `analysis.md`
- `storyboard.md`
- `characters/characters.md`
- `prompts/*.md`
**Use case**: Review and edit prompts before image generation. Useful for:
- Fine-tuning image generation prompts
- Ensuring visual consistency before committing to generation
- Making style adjustments at the prompt level
---
## Using `--images-only`
Generate images from existing prompts (starts at Step 7):
```bash
/baoyu-comic comic/topic-slug/ --images-only
```
**Workflow**: Skip to Step 7, then 8-9
**Prerequisites** (must exist in directory):
- `prompts/` directory with page prompt files
- `storyboard.md` with style information
- `characters/characters.md` with character definitions
**Output**:
- `characters/characters.png` (if not exists)
- `NN-{cover|page}-[slug].png` images
- `{topic-slug}.pdf`
**Use case**: Re-generate images after editing prompts. Useful for:
- Recovering from failed image generation
- Trying different image generation settings
- Regenerating after manual prompt edits
---
## Using `--regenerate`
Regenerate specific pages only:
```bash
# Single page
/baoyu-comic comic/topic-slug/ --regenerate 3
# Multiple pages
/baoyu-comic comic/topic-slug/ --regenerate 2,5,8
# Cover page
/baoyu-comic comic/topic-slug/ --regenerate 0
```
**Workflow**:
1. Read existing prompts for specified pages
2. Regenerate images only for those pages
3. Regenerate PDF
**Prerequisites** (must exist):
- `prompts/NN-{cover|page}-[slug].md` for specified pages
- `characters/characters.png` (for reference)
**Output**:
- Regenerated `NN-{cover|page}-[slug].png` for specified pages
- Updated `{topic-slug}.pdf`
**Use case**: Fix specific pages without regenerating entire comic. Useful for:
- Fixing a single problematic page
- Iterating on specific visuals
- Regenerating pages after prompt edits
**Page numbering**:
- `0` = Cover page
- `1-N` = Content pages
@@ -41,16 +41,18 @@ Every technical concept MUST be visualized as a metaphor:
### Character Roles (Required)
Define characters before generating:
**DEFAULT: Use Doraemon characters** unless user explicitly specifies `--characters` or has character presets in EXTEND.md.
| Role | Default | Traits |
|------|---------|--------|
| Student (Role A) | 大雄 | Confused, asks basic but crucial questions, represents reader |
| Mentor (Role B) | 哆啦A梦 | Knowledgeable, patient, uses gadgets as technical metaphors |
| Challenge (Role C, optional) | 胖虎 | Represents misunderstanding, or "noise" in the data |
| Support (Role D, optional) | 静香 | Asks clarifying questions, provides alternative perspectives |
| Role | Default Character | Visual | Traits |
|------|-------------------|--------|--------|
| Student (Role A) | 大雄 (Nobita) | Boy, 10yo, round glasses, black hair, yellow shirt, navy shorts | Confused, asks basic but crucial questions, represents reader |
| Mentor (Role B) | 哆啦A梦 (Doraemon) | Blue robot cat, white belly, 4D pocket, red nose, golden bell | Knowledgeable, patient, uses gadgets as technical metaphors |
| Challenge (Role C) | 胖虎 (Gian) | Stocky boy, small eyes, orange shirt | Represents misunderstanding, or "noise" in the data |
| Support (Role D) | 静香 (Shizuka) | Cute girl, black short hair, pink dress | Asks clarifying questions, provides alternative perspectives |
Custom characters via `--characters "Student:小明,Mentor:教授"` or EXTEND.md presets.
**IMPORTANT**: These Doraemon characters ARE the default for ohmsha preset. Generate character definitions using these exact characters unless user requests otherwise.
To use custom characters: `--characters "Student:小明,Mentor:教授"` or define in EXTEND.md.
### Page Title Convention
+505
View File
@@ -0,0 +1,505 @@
# Complete Workflow
Full workflow for generating knowledge comics.
## Progress Checklist
Copy and track progress:
```
Comic Progress:
- [ ] Step 1: Setup & Analyze
- [ ] 1.1 Load preferences
- [ ] 1.2 Analyze content
- [ ] 1.3 Check existing ⚠️ REQUIRED
- [ ] Step 2: Confirmation 1 - Style & options ⚠️ REQUIRED
- [ ] Step 3: Generate storyboard + characters
- [ ] Step 4: Review outline (conditional)
- [ ] Step 5: Generate prompts
- [ ] Step 6: Review prompts (conditional)
- [ ] Step 7: Generate images
- [ ] Step 8: Merge to PDF
- [ ] Step 9: Completion report
```
## Flow Diagram
```
Input → Preferences → Analyze → [Check Existing?] → [Confirm 1: Style + Reviews] → Storyboard → [Review Outline?] → Prompts → [Review Prompts?] → Images → PDF → Complete
```
---
## Step 1: Setup & Analyze
### 1.1 Load Preferences (EXTEND.md)
Use Bash to check EXTEND.md existence (priority order):
```bash
# Check project-level first
test -f .baoyu-skills/baoyu-comic/EXTEND.md && echo "project"
# Then user-level (cross-platform: $HOME works on macOS/Linux/WSL)
test -f "$HOME/.baoyu-skills/baoyu-comic/EXTEND.md" && echo "user"
```
| Path | Location |
|------|----------|
| `.baoyu-skills/baoyu-comic/EXTEND.md` | Project directory |
| `$HOME/.baoyu-skills/baoyu-comic/EXTEND.md` | User home |
**When EXTEND.md Found** → Read, parse, **output summary to user**:
```
📋 Loaded preferences from [full path]
├─ Watermark: [enabled/disabled] [content if enabled]
├─ Art Style: [style name or "auto-select"]
├─ Tone: [tone name or "auto-select"]
├─ Layout: [layout or "auto-select"]
├─ Language: [language or "auto-detect"]
└─ Character presets: [count] defined
```
**MUST output this summary** so user knows their current configuration. Do not skip or silently load.
**When EXTEND.md Not Found** → First-time setup:
1. Inform user: "No preferences found. Let's set up your defaults."
2. Use AskUserQuestion to collect preferences (see `config/first-time-setup.md`)
3. Create EXTEND.md at user-chosen location
4. Confirm: "✓ Preferences saved to [path]"
**EXTEND.md Supports**: Watermark | Preferred art/tone/layout | Custom style definitions | Character presets | Language preference
Schema: `config/preferences-schema.md`
**Important**: Once EXTEND.md exists, watermark, language, and style defaults are NOT asked again in Confirmation 1 or 2. These are session-persistent settings.
### 1.2 Analyze Content → `analysis.md`
Read source content, save it if needed, and perform deep analysis.
**Actions**:
1. **Save source content** (if not already a file):
- If user provides a file path: use as-is
- If user pastes content: save to `source.md` in target directory
2. Read source content
3. **Deep analysis** following `analysis-framework.md`:
- Target audience identification
- Value proposition for readers
- Core themes and narrative potential
- Key figures and their story arcs
4. Detect source language
5. **Determine language**:
- If EXTEND.md has `language` → use it
- Else if `--lang` option provided → use it
- Else → use detected source language
6. Determine recommended page count:
- Short story: 5-8 pages
- Medium complexity: 9-15 pages
- Full biography: 16-25 pages
7. Analyze content signals for art/tone/layout recommendations
8. **Save to `analysis.md`**
**analysis.md Format**: YAML front matter (title, topic, time_span, source_language, user_language, aspect_ratio, recommended_page_count, recommended_art, recommended_tone) + sections for Target Audience, Value Proposition, Core Themes, Key Figures & Story Arcs, Content Signals, Recommended Approaches. See `analysis-framework.md` for full template.
### 1.3 Check Existing Content ⚠️ REQUIRED
**MUST execute before proceeding to Step 2.**
Use Bash to check if output directory exists:
```bash
test -d "comic/{topic-slug}" && echo "exists"
```
**If directory exists**, use AskUserQuestion:
```
header: "Existing"
question: "Existing content found. How to proceed?"
options:
- label: "Regenerate storyboard"
description: "Keep images, regenerate storyboard and characters only"
- label: "Regenerate images"
description: "Keep storyboard, regenerate images only"
- label: "Backup and regenerate"
description: "Backup to {slug}-backup-{timestamp}, then regenerate all"
- label: "Exit"
description: "Cancel, keep existing content unchanged"
```
Save result and handle accordingly:
- **Regenerate storyboard**: Skip to Step 3, preserve `prompts/` and images
- **Regenerate images**: Skip to Step 7, use existing prompts
- **Backup and regenerate**: Move directory, start fresh from Step 2
- **Exit**: End workflow immediately
---
## Step 2: Confirmation 1 - Style & Options ⚠️
**Purpose**: Select visual style + decide whether to review outline before generation. **Do NOT skip.**
**Note**: Watermark and language already configured in EXTEND.md (Step 1).
**Display summary**:
- Content type + topic identified
- Key figures extracted
- Time span detected
- Recommended page count
- Language: [from EXTEND.md or detected]
- **Recommended style**: [art] + [tone] (based on content signals)
**Use AskUserQuestion** for:
### Question 1: Visual Style
If a preset is recommended (see `auto-selection.md`), show it first:
```
header: "Style"
question: "Which visual style for this comic?"
options:
- label: "[preset name] preset (Recommended)" # If preset recommended
description: "[preset description] - includes special rules"
- label: "[recommended art] + [recommended tone] (Recommended)" # If no preset
description: "Best match for your content based on analysis"
- label: "ligne-claire + neutral"
description: "Classic educational, Logicomix style"
- label: "ohmsha preset"
description: "Educational manga with visual metaphors, gadgets, NO talking heads"
- label: "Custom"
description: "Specify your own art + tone or preset"
```
**Preset vs Art+Tone**: Presets include special rules beyond art+tone. `ohmsha` = manga + neutral + visual metaphor rules + character roles + NO talking heads. Plain `manga + neutral` does NOT include these rules.
### Question 2: Narrative Focus (multiSelect: true)
```
header: "Focus"
question: "What should the comic emphasize? (Select all that apply)"
options:
- label: "Biography/life story"
description: "Follow a person's journey through key life events"
- label: "Concept explanation"
description: "Break down complex ideas visually"
- label: "Historical event"
description: "Dramatize important historical moments"
- label: "Tutorial/how-to"
description: "Step-by-step educational guide"
```
### Question 3: Target Audience
```
header: "Audience"
question: "Who is the primary reader?"
options:
- label: "General readers"
description: "Broad appeal, accessible content"
- label: "Students/learners"
description: "Educational focus, clear explanations"
- label: "Industry professionals"
description: "Technical depth, domain knowledge"
- label: "Children/young readers"
description: "Simplified language, engaging visuals"
```
### Question 4: Outline Review
```
header: "Review"
question: "Do you want to review the outline before image generation?"
options:
- label: "Yes, let me review (Recommended)"
description: "Review storyboard and characters before generating images"
- label: "No, generate directly"
description: "Skip outline review, start generating immediately"
```
### Question 5: Prompt Review
```
header: "Prompts"
question: "Review prompts before generating images?"
options:
- label: "Yes, review prompts (Recommended)"
description: "Review image generation prompts before generating"
- label: "No, skip prompt review"
description: "Proceed directly to image generation"
```
**After response**:
1. Update `analysis.md` with user preferences
2. **Store `skip_outline_review`** flag based on Question 4 response
3. **Store `skip_prompt_review`** flag based on Question 5 response
4. → Step 3
---
## Step 3: Generate Storyboard + Characters
Create storyboard and character definitions using the confirmed style from Step 2.
**Loading Style References**:
- Art style: `art-styles/{art}.md`
- Tone: `tones/{tone}.md`
- If preset (ohmsha/wuxia/shoujo): also load `presets/{preset}.md`
**Generate**:
1. **Storyboard** (`storyboard.md`):
- YAML front matter with art_style, tone, layout, aspect_ratio
- Cover design
- Each page: layout, panel breakdown, visual prompts
- **Written in user's preferred language** (from Step 1)
- Reference: `storyboard-template.md`
- **If using preset**: Load and apply preset rules from `presets/`
2. **Character definitions** (`characters/characters.md`):
- Visual specs matching the art style (in user's preferred language)
- Include Reference Sheet Prompt for later image generation
- Reference: `character-template.md`
- **If using ohmsha preset**: Use default Doraemon characters (see below)
**Ohmsha Default Characters** (use these unless user specifies `--characters`):
| Role | Character | Visual Description |
|------|-----------|-------------------|
| Student | 大雄 (Nobita) | Japanese boy, 10yo, round glasses, black hair parted in middle, yellow shirt, navy shorts |
| Mentor | 哆啦A梦 (Doraemon) | Round blue robot cat, big white eyes, red nose, whiskers, white belly with 4D pocket, golden bell, no ears |
| Challenge | 胖虎 (Gian) | Stocky boy, rough features, small eyes, orange shirt |
| Support | 静香 (Shizuka) | Cute girl, black short hair, pink dress, gentle expression |
These are the canonical ohmsha-style characters. Do NOT create custom characters for ohmsha unless explicitly requested.
**After generation**:
- If `skip_outline_review` is true → Skip Step 4, go directly to Step 5
- If `skip_outline_review` is false → Continue to Step 4
---
## Step 4: Review Outline (Conditional)
**Skip this step** if user selected "No, generate directly" in Step 2.
**Purpose**: User reviews and confirms storyboard + characters before generation.
**Display**:
- Page count and structure
- Art style + Tone combination
- Page-by-page summary (Cover → P1 → P2...)
- Character list with brief descriptions
**Use AskUserQuestion**:
```
header: "Confirm"
question: "Ready to generate images with this outline?"
options:
- label: "Yes, proceed (Recommended)"
description: "Generate character sheet and comic pages"
- label: "Edit storyboard first"
description: "I'll modify storyboard.md before continuing"
- label: "Edit characters first"
description: "I'll modify characters/characters.md before continuing"
- label: "Edit both"
description: "I'll modify both files before continuing"
```
**After response**:
1. If user wants to edit → Wait for user to finish editing, then ask again
2. If user confirms → Continue to Step 5
---
## Step 5: Generate Prompts
Create image generation prompts for all pages.
**Style Reference Loading**:
- Read `art-styles/{art}.md` for rendering guidelines
- Read `tones/{tone}.md` for mood/color adjustments
- If preset: Read `presets/{preset}.md` for special rules
**For each page (cover + pages)**:
1. Create prompt following art style + tone guidelines
2. Include character visual descriptions for consistency
3. Save to `prompts/NN-{cover|page}-[slug].md`
**Prompt File Format**:
```markdown
# Page NN: [Title]
## Visual Style
Art: [art style] | Tone: [tone] | Layout: [layout type]
## Character Reference
[Character descriptions from characters/characters.md]
## Panel Breakdown
[From storyboard.md - panel descriptions, actions, dialogue]
## Generation Prompt
[Combined prompt for image generation skill]
```
**Watermark Application** (if enabled in preferences):
Add to each prompt:
```
Include a subtle watermark "[content]" positioned at [position]
with approximately [opacity*100]% visibility. The watermark should
be legible but not distracting from the comic panels and storytelling.
Ensure watermark does not overlap speech bubbles or key action.
```
Reference: `config/watermark-guide.md`
**After generation**:
- If `skip_prompt_review` is true → Skip Step 6, go directly to Step 7
- If `skip_prompt_review` is false → Continue to Step 6
---
## Step 6: Review Prompts (Conditional)
**Skip this step** if user selected "No, skip prompt review" in Step 2.
**Purpose**: User reviews and confirms prompts before image generation.
**Display prompt summary table**:
| Page | Title | Key Elements |
|------|-------|--------------|
| Cover | [title] | [main visual] |
| P1 | [title] | [key elements] |
| ... | ... | ... |
**Use AskUserQuestion**:
```
header: "Confirm"
question: "Ready to generate images with these prompts?"
options:
- label: "Yes, proceed (Recommended)"
description: "Generate all comic page images"
- label: "Edit prompts first"
description: "I'll modify prompts/*.md before continuing"
- label: "Regenerate prompts"
description: "Regenerate all prompts with different approach"
```
**After response**:
1. If user wants to edit → Wait for user to finish editing, then ask again
2. If user wants to regenerate → Go back to Step 5
3. If user confirms → Continue to Step 7
---
## Step 7: Generate Images
With confirmed prompts from Step 5/6:
### 7.1 Generate Character Reference Sheet (first)
1. Use Reference Sheet Prompt from `characters/characters.md`
2. Generate → `characters/characters.png`
3. This ensures visual consistency for all subsequent pages
### 7.2 Generate Comic Pages
**CRITICAL: Character Reference is MANDATORY** for visual consistency across all pages.
**Before generating any page**:
1. Read the image generation skill's SKILL.md
2. Check if it supports reference image input (`--ref`, `--reference`, etc.)
3. Choose the appropriate strategy below
**Character Reference Strategy**:
| Skill Capability | Strategy | Action |
|------------------|----------|--------|
| Supports `--ref` | **Strategy A** | Pass `characters/characters.png` with EVERY page |
| Does NOT support `--ref` | **Strategy B** | Prepend character descriptions to EVERY prompt |
**Strategy A: Using `--ref` parameter** (e.g., baoyu-image-gen)
```bash
# Each page generation MUST include --ref
npx -y bun ${SKILL_DIR}/../baoyu-image-gen/scripts/main.ts \
--promptfiles prompts/01-page-xxx.md \
--image 01-page-xxx.png \
--ar 3:4 \
--ref characters/characters.png
```
**Strategy B: Embedding character descriptions in prompt**
When skill does NOT support reference images, create combined prompt files:
```markdown
# prompts/01-page-xxx.md (with embedded character reference)
## Character Reference (maintain consistency)
[Copy relevant sections from characters/characters.md here]
- 大雄: Japanese boy, round glasses, yellow shirt, navy shorts...
- 哆啦A梦: Round blue robot cat, white belly, red nose, golden bell...
## Page Content
[Original page prompt here]
```
**For each page (cover + pages)**:
1. Read prompt from `prompts/NN-{cover|page}-[slug].md`
2. Generate image using Strategy A or B (based on skill capability)
3. Save to `NN-{cover|page}-[slug].png`
4. Report progress after each generation: "Generated X/N: [page title]"
**Session Management**:
If image generation skill supports `--sessionId`:
1. Generate unique session ID: `comic-{topic-slug}-{timestamp}`
2. Use same session ID for all pages
3. Ensures visual consistency across generated images
---
## Step 8: Merge to PDF
After all images generated:
```bash
npx -y bun ${SKILL_DIR}/scripts/merge-to-pdf.ts <comic-dir>
```
Creates `{topic-slug}.pdf` with all pages as full-page images.
---
## Step 9: Completion Report
```
Comic Complete!
Title: [title] | Art: [art] | Tone: [tone] | Pages: [count] | Aspect: [ratio] | Language: [lang]
Watermark: [enabled/disabled]
Location: [path]
✓ analysis.md
✓ characters.png
✓ 00-cover-[slug].png ... NN-page-[slug].png
✓ {topic-slug}.pdf
```
---
## Page Modification
| Action | Steps |
|--------|-------|
| **Edit** | Update prompt → Regenerate image → Regenerate PDF |
| **Add** | Create prompt at position → Generate image → Renumber subsequent (NN+1) → Update storyboard → Regenerate PDF |
| **Delete** | Remove files → Renumber subsequent (NN-1) → Update storyboard → Regenerate PDF |
**File naming**: `NN-{cover|page}-[slug].png` (e.g., `03-page-enigma-machine.png`)
- Slugs: kebab-case, unique, derived from content
- Renumbering: Update NN prefix only, slugs unchanged
+49 -148
View File
@@ -1,188 +1,89 @@
---
name: baoyu-compress-image
description: Cross-platform image compression skill. Converts images to WebP by default with PNG-to-PNG support. Uses system tools (sips, cwebp, ImageMagick) with Sharp fallback.
description: Compresses images to WebP (default) or PNG with automatic tool selection. Use when user asks to "compress image", "optimize image", "convert to webp", or reduce image file size.
---
# Image Compressor
Cross-platform image compression with WebP default output, PNG-to-PNG support, preferring system tools with Sharp fallback.
Compresses images using best available tool (sips → cwebp → ImageMagick → Sharp).
## Script Directory
**Important**: All scripts are located in the `scripts/` subdirectory of this skill.
Scripts in `scripts/` subdirectory. Replace `${SKILL_DIR}` with this SKILL.md's directory path.
**Agent Execution Instructions**:
1. Determine this SKILL.md file's directory path as `SKILL_DIR`
2. Script path = `${SKILL_DIR}/scripts/<script-name>.ts`
3. Replace all `${SKILL_DIR}` in this document with the actual path
**Script Reference**:
| Script | Purpose |
|--------|---------|
| `scripts/main.ts` | CLI entry point for image compression |
| `scripts/main.ts` | Image compression CLI |
## Quick Start
## Preferences (EXTEND.md)
Use Bash to check EXTEND.md existence (priority order):
```bash
# Compress to WebP (default)
npx -y bun ${SKILL_DIR}/scripts/main.ts image.png
# Check project-level first
test -f .baoyu-skills/baoyu-compress-image/EXTEND.md && echo "project"
# Keep original format (PNG → PNG)
npx -y bun ${SKILL_DIR}/scripts/main.ts image.png --format png
# Custom quality
npx -y bun ${SKILL_DIR}/scripts/main.ts image.png -q 75
# Process directory
npx -y bun ${SKILL_DIR}/scripts/main.ts ./images/ -r
# Then user-level (cross-platform: $HOME works on macOS/Linux/WSL)
test -f "$HOME/.baoyu-skills/baoyu-compress-image/EXTEND.md" && echo "user"
```
## Commands
┌────────────────────────────────────────────────────────┬───────────────────┐
│ Path │ Location │
├────────────────────────────────────────────────────────┼───────────────────┤
│ .baoyu-skills/baoyu-compress-image/EXTEND.md │ Project directory │
├────────────────────────────────────────────────────────┼───────────────────┤
│ $HOME/.baoyu-skills/baoyu-compress-image/EXTEND.md │ User home │
└────────────────────────────────────────────────────────┴───────────────────┘
### Single File Compression
┌───────────┬───────────────────────────────────────────────────────────────────────────┐
│ Result │ Action │
├───────────┼───────────────────────────────────────────────────────────────────────────┤
│ Found │ Read, parse, apply settings │
├───────────┼───────────────────────────────────────────────────────────────────────────┤
│ Not found │ Use defaults │
└───────────┴───────────────────────────────────────────────────────────────────────────┘
**EXTEND.md Supports**: Default format | Default quality | Keep original preference
## Usage
```bash
# Basic (converts to WebP, replaces original)
npx -y bun ${SKILL_DIR}/scripts/main.ts image.png
# Custom output path
npx -y bun ${SKILL_DIR}/scripts/main.ts image.png -o compressed.webp
# Keep original file
npx -y bun ${SKILL_DIR}/scripts/main.ts image.png --keep
# Custom quality (0-100, default: 80)
npx -y bun ${SKILL_DIR}/scripts/main.ts image.png -q 75
# Keep original format
npx -y bun ${SKILL_DIR}/scripts/main.ts image.png -f png
```
### Directory Processing
```bash
# Process all images in directory
npx -y bun ${SKILL_DIR}/scripts/main.ts ./images/
# Recursive processing
npx -y bun ${SKILL_DIR}/scripts/main.ts ./images/ -r
# With custom quality
npx -y bun ${SKILL_DIR}/scripts/main.ts ./images/ -r -q 75
```
### Output Formats
```bash
# Plain text (default)
npx -y bun ${SKILL_DIR}/scripts/main.ts image.png
# JSON output
npx -y bun ${SKILL_DIR}/scripts/main.ts image.png --json
npx -y bun ${SKILL_DIR}/scripts/main.ts <input> [options]
```
## Options
| Option | Short | Description | Default |
|--------|-------|-------------|---------|
| `<input>` | | Input file or directory | Required |
| `--output <path>` | `-o` | Output path | Same path, new extension |
| `--format <fmt>` | `-f` | webp, png, jpeg | webp |
| `--quality <n>` | `-q` | Quality 0-100 | 80 |
| `--keep` | `-k` | Keep original file | false |
| `--recursive` | `-r` | Process directories recursively | false |
| `<input>` | | File or directory | Required |
| `--output` | `-o` | Output path | Same path, new ext |
| `--format` | `-f` | webp, png, jpeg | webp |
| `--quality` | `-q` | Quality 0-100 | 80 |
| `--keep` | `-k` | Keep original | false |
| `--recursive` | `-r` | Process subdirs | false |
| `--json` | | JSON output | false |
| `--help` | `-h` | Show help | |
## Compressor Selection
## Examples
Priority order (auto-detected):
```bash
# Single file → WebP (replaces original)
npx -y bun ${SKILL_DIR}/scripts/main.ts image.png
1. **sips** (macOS built-in, WebP support since macOS 11)
2. **cwebp** (Google's official WebP tool)
3. **ImageMagick** (`convert` command)
4. **Sharp** (npm package, auto-installed by Bun)
# Keep PNG format
npx -y bun ${SKILL_DIR}/scripts/main.ts image.png -f png --keep
The skill automatically selects the best available compressor.
# Directory recursive
npx -y bun ${SKILL_DIR}/scripts/main.ts ./images/ -r -q 75
## Output Format
### Text Mode (default)
# JSON output
npx -y bun ${SKILL_DIR}/scripts/main.ts image.png --json
```
**Output**:
```
image.png → image.webp (245KB → 89KB, 64% reduction)
```
### JSON Mode
```json
{
"input": "image.png",
"output": "image.webp",
"inputSize": 250880,
"outputSize": 91136,
"ratio": 0.36,
"compressor": "sips"
}
```
### Directory JSON Mode
```json
{
"files": [...],
"summary": {
"totalFiles": 10,
"totalInputSize": 2508800,
"totalOutputSize": 911360,
"ratio": 0.36,
"compressor": "sips"
}
}
```
## Examples
### Compress single image
```bash
npx -y bun ${SKILL_DIR}/scripts/main.ts photo.png
# photo.png → photo.webp (1.2MB → 340KB, 72% reduction)
```
### Compress with custom quality
```bash
npx -y bun ${SKILL_DIR}/scripts/main.ts photo.png -q 60
# photo.png → photo.webp (1.2MB → 280KB, 77% reduction)
```
### Keep original format
```bash
npx -y bun ${SKILL_DIR}/scripts/main.ts screenshot.png -f png --keep
# screenshot.png → screenshot-compressed.png (500KB → 380KB, 24% reduction)
```
### Process entire directory
```bash
npx -y bun ${SKILL_DIR}/scripts/main.ts ./screenshots/ -r
# Processed 15 files: 12.5MB → 4.2MB (66% reduction)
```
### Get JSON for scripting
```bash
npx -y bun ${SKILL_DIR}/scripts/main.ts image.png --json | jq '.ratio'
```
## Extension Support
Custom configurations via EXTEND.md.
**Check paths** (priority order):
1. `.baoyu-skills/baoyu-compress-image/EXTEND.md` (project)
2. `~/.baoyu-skills/baoyu-compress-image/EXTEND.md` (user)
If found, load before workflow. Extension content overrides defaults.
Custom configurations via EXTEND.md. See **Preferences** section for paths and supported options.
+81 -226
View File
@@ -1,15 +1,11 @@
---
name: baoyu-danger-gemini-web
description: Image generation skill using Gemini Web. Generates images from text prompts via Google Gemini. Also supports text generation. Use as the image generation backend for other skills like cover-image, xhs-images, article-illustrator.
description: Generates images and text via reverse-engineered Gemini Web API. Supports text generation, image generation from prompts, reference images for vision input, and multi-turn conversations. Use when other skills need image generation backend, or when user requests "generate image with Gemini", "Gemini text generation", or needs vision-capable AI generation.
---
# Gemini Web Client
Supports:
- Text generation
- Image generation (download + save)
- Reference images for vision input (attach local images)
- Multi-turn conversations via persisted `--sessionId`
Text/image generation via Gemini Web API. Supports reference images and multi-turn conversations.
## Script Directory
@@ -26,146 +22,73 @@ Supports:
| `scripts/main.ts` | CLI entry point for text/image generation |
| `scripts/gemini-webapi/*` | TypeScript port of `gemini_webapi` (GeminiClient, types, utils) |
## ⚠️ Disclaimer (REQUIRED)
## Consent Check (REQUIRED)
**Before using this skill**, the consent check MUST be performed.
Before first use, verify user consent for reverse-engineered API usage.
### Consent Check Flow
**Consent file locations**:
- macOS: `~/Library/Application Support/baoyu-skills/gemini-web/consent.json`
- Linux: `~/.local/share/baoyu-skills/gemini-web/consent.json`
- Windows: `%APPDATA%\baoyu-skills\gemini-web\consent.json`
**Step 1**: Check consent file
```bash
# macOS
cat ~/Library/Application\ Support/baoyu-skills/gemini-web/consent.json 2>/dev/null
# Linux
cat ~/.local/share/baoyu-skills/gemini-web/consent.json 2>/dev/null
# Windows (PowerShell)
Get-Content "$env:APPDATA\baoyu-skills\gemini-web\consent.json" 2>$null
```
**Step 2**: If consent exists and `accepted: true` with matching `disclaimerVersion: "1.0"`:
Print warning and proceed:
```
⚠️ Warning: Using reverse-engineered Gemini Web API (not official). Accepted on: <acceptedAt date>
```
**Step 3**: If consent file doesn't exist or `disclaimerVersion` mismatch:
Display disclaimer and ask user:
```
⚠️ DISCLAIMER
This tool uses a reverse-engineered Gemini Web API, NOT an official Google API.
Risks:
- May break without notice if Google changes their API
- No official support or guarantees
- Use at your own risk
Do you accept these terms and wish to continue?
```
Use `AskUserQuestion` tool with options:
- **Yes, I accept** - Continue and save consent
- **No, I decline** - Exit immediately
**Step 4**: On acceptance, create consent file:
```bash
# macOS
mkdir -p ~/Library/Application\ Support/baoyu-skills/gemini-web
cat > ~/Library/Application\ Support/baoyu-skills/gemini-web/consent.json << 'EOF'
{
"version": 1,
"accepted": true,
"acceptedAt": "<ISO timestamp>",
"disclaimerVersion": "1.0"
}
EOF
# Linux
mkdir -p ~/.local/share/baoyu-skills/gemini-web
cat > ~/.local/share/baoyu-skills/gemini-web/consent.json << 'EOF'
{
"version": 1,
"accepted": true,
"acceptedAt": "<ISO timestamp>",
"disclaimerVersion": "1.0"
}
EOF
```
**Step 5**: On decline, output message and stop:
```
User declined the disclaimer. Exiting.
```
**Flow**:
1. Check if consent file exists with `accepted: true` and `disclaimerVersion: "1.0"`
2. If valid consent exists → print warning with `acceptedAt` date, proceed
3. If no consent → show disclaimer, ask user via `AskUserQuestion`:
- "Yes, I accept" → create consent file with ISO timestamp, proceed
- "No, I decline" → output decline message, stop
4. Consent file format: `{"version":1,"accepted":true,"acceptedAt":"<ISO>","disclaimerVersion":"1.0"}`
---
## Quick start
## Preferences (EXTEND.md)
Use Bash to check EXTEND.md existence (priority order):
```bash
npx -y bun ${SKILL_DIR}/scripts/main.ts "Hello, Gemini"
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "Explain quantum computing"
# Check project-level first
test -f .baoyu-skills/baoyu-danger-gemini-web/EXTEND.md && echo "project"
# Then user-level (cross-platform: $HOME works on macOS/Linux/WSL)
test -f "$HOME/.baoyu-skills/baoyu-danger-gemini-web/EXTEND.md" && echo "user"
```
┌──────────────────────────────────────────────────────────┬───────────────────┐
│ Path │ Location │
├──────────────────────────────────────────────────────────┼───────────────────┤
│ .baoyu-skills/baoyu-danger-gemini-web/EXTEND.md │ Project directory │
├──────────────────────────────────────────────────────────┼───────────────────┤
│ $HOME/.baoyu-skills/baoyu-danger-gemini-web/EXTEND.md │ User home │
└──────────────────────────────────────────────────────────┴───────────────────┘
┌───────────┬───────────────────────────────────────────────────────────────────────────┐
│ Result │ Action │
├───────────┼───────────────────────────────────────────────────────────────────────────┤
│ Found │ Read, parse, apply settings │
├───────────┼───────────────────────────────────────────────────────────────────────────┤
│ Not found │ Use defaults │
└───────────┴───────────────────────────────────────────────────────────────────────────┘
**EXTEND.md Supports**: Default model | Proxy settings | Custom data directory
## Usage
```bash
# Text generation
npx -y bun ${SKILL_DIR}/scripts/main.ts "Your prompt"
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "Your prompt" --model gemini-2.5-pro
# Image generation
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A cute cat" --image cat.png
npx -y bun ${SKILL_DIR}/scripts/main.ts --promptfiles system.md content.md --image out.png
# Multi-turn conversation (agent generates unique sessionId)
npx -y bun ${SKILL_DIR}/scripts/main.ts "Remember this: 42" --sessionId my-unique-id-123
npx -y bun ${SKILL_DIR}/scripts/main.ts "What number?" --sessionId my-unique-id-123
```
# Vision input (reference images)
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "Describe this" --reference image.png
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "Create variation" --reference a.png --image out.png
## Commands
### Text generation
```bash
# Simple prompt (positional)
npx -y bun ${SKILL_DIR}/scripts/main.ts "Your prompt here"
# Explicit prompt flag
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "Your prompt here"
npx -y bun ${SKILL_DIR}/scripts/main.ts -p "Your prompt here"
# With model selection
npx -y bun ${SKILL_DIR}/scripts/main.ts -p "Hello" -m gemini-2.5-pro
# Pipe from stdin
echo "Summarize this" | npx -y bun ${SKILL_DIR}/scripts/main.ts
```
### Image generation
```bash
# Generate image with default path (./generated.png)
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A sunset over mountains" --image
# Generate image with custom path
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A cute robot" --image robot.png
# Shorthand
npx -y bun ${SKILL_DIR}/scripts/main.ts "A dragon" --image=dragon.png
```
### Vision input (reference images)
```bash
# Text + image -> text
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "Describe this image" --reference a.png
# Text + image -> image
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "Generate a variation" --reference a.png --image out.png
```
### Output formats
```bash
# Plain text (default)
npx -y bun ${SKILL_DIR}/scripts/main.ts "Hello"
# Multi-turn conversation
npx -y bun ${SKILL_DIR}/scripts/main.ts "Remember: 42" --sessionId session-abc
npx -y bun ${SKILL_DIR}/scripts/main.ts "What number?" --sessionId session-abc
# JSON output
npx -y bun ${SKILL_DIR}/scripts/main.ts "Hello" --json
@@ -175,45 +98,35 @@ npx -y bun ${SKILL_DIR}/scripts/main.ts "Hello" --json
| Option | Description |
|--------|-------------|
| `--prompt <text>`, `-p` | Prompt text |
| `--promptfiles <files...>` | Read prompt from files (concatenated in order) |
| `--model <id>`, `-m` | Model: gemini-3-pro (default), gemini-2.5-pro, gemini-2.5-flash |
| `--image [path]` | Generate image, save to path (default: generated.png) |
| `--reference <files...>`, `--ref <files...>` | Reference images for vision input |
| `--sessionId <id>` | Session ID for multi-turn conversation (agent generates unique ID) |
| `--list-sessions` | List saved sessions (max 100, sorted by update time) |
| `--prompt`, `-p` | Prompt text |
| `--promptfiles` | Read prompt from files (concatenated) |
| `--model`, `-m` | Model: gemini-3-pro (default), gemini-2.5-pro, gemini-2.5-flash |
| `--image [path]` | Generate image (default: generated.png) |
| `--reference`, `--ref` | Reference images for vision input |
| `--sessionId` | Session ID for multi-turn conversation |
| `--list-sessions` | List saved sessions |
| `--json` | Output as JSON |
| `--login` | Refresh cookies only, then exit |
| `--cookie-path <path>` | Custom cookie file path |
| `--profile-dir <path>` | Chrome profile directory |
| `--help`, `-h` | Show help |
CLI note: `scripts/main.ts` supports text generation, image generation, reference images (`--reference/--ref`), and multi-turn conversations via `--sessionId`.
| `--login` | Refresh cookies, then exit |
| `--cookie-path` | Custom cookie file path |
| `--profile-dir` | Chrome profile directory |
## Models
- `gemini-3-pro` - Default, latest model
- `gemini-2.5-pro` - Previous generation pro
- `gemini-2.5-flash` - Fast, lightweight
| Model | Description |
|-------|-------------|
| `gemini-3-pro` | Default, latest |
| `gemini-2.5-pro` | Previous pro |
| `gemini-2.5-flash` | Fast, lightweight |
## Authentication
First run opens a browser to authenticate with Google. Cookies are cached for subsequent runs.
First run opens browser for Google auth. Cookies cached automatically.
**Supported browsers** (auto-detected in order):
- Google Chrome
- Google Chrome Canary / Beta
- Chromium
- Microsoft Edge
Supported browsers (auto-detected): Chrome, Chrome Canary/Beta, Chromium, Edge.
Override with `GEMINI_WEB_CHROME_PATH` environment variable if needed.
Force refresh: `--login` flag. Override browser: `GEMINI_WEB_CHROME_PATH` env var.
```bash
# Force cookie refresh
npx -y bun ${SKILL_DIR}/scripts/main.ts --login
```
## Environment variables
## Environment Variables
| Variable | Description |
|----------|-------------|
@@ -221,72 +134,14 @@ npx -y bun ${SKILL_DIR}/scripts/main.ts --login
| `GEMINI_WEB_COOKIE_PATH` | Cookie file path |
| `GEMINI_WEB_CHROME_PROFILE_DIR` | Chrome profile directory |
| `GEMINI_WEB_CHROME_PATH` | Chrome executable path |
| `HTTP_PROXY`, `HTTPS_PROXY` | Proxy for Google access (set inline with command) |
## Proxy Configuration
## Sessions
If you need a proxy to access Google services (e.g., in China), set `HTTP_PROXY` and `HTTPS_PROXY` environment variables before running:
Session files stored in data directory under `sessions/<id>.json`.
```bash
# Example with local proxy
HTTP_PROXY=http://127.0.0.1:7890 HTTPS_PROXY=http://127.0.0.1:7890 npx -y bun ${SKILL_DIR}/scripts/main.ts "Hello"
# Image generation with proxy
HTTP_PROXY=http://127.0.0.1:7890 HTTPS_PROXY=http://127.0.0.1:7890 npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A cat" --image cat.png
# Cookie refresh with proxy
HTTP_PROXY=http://127.0.0.1:7890 HTTPS_PROXY=http://127.0.0.1:7890 npx -y bun ${SKILL_DIR}/scripts/main.ts --login
```
**Note**: Environment variables must be set inline with the command. Shell profile settings (e.g., `.bashrc`) may not be inherited by subprocesses.
## Examples
### Generate text response
```bash
npx -y bun ${SKILL_DIR}/scripts/main.ts "What is the capital of France?"
```
### Generate image
```bash
npx -y bun ${SKILL_DIR}/scripts/main.ts "A photorealistic image of a golden retriever puppy" --image puppy.png
```
### Get JSON output for parsing
```bash
npx -y bun ${SKILL_DIR}/scripts/main.ts "Hello" --json | jq '.text'
```
### Generate image from prompt files
```bash
# Concatenate system.md + content.md as prompt
npx -y bun ${SKILL_DIR}/scripts/main.ts --promptfiles system.md content.md --image output.png
```
### Multi-turn conversation
```bash
# Start a session with unique ID (agent generates this)
npx -y bun ${SKILL_DIR}/scripts/main.ts "You are a helpful math tutor." --sessionId task-abc123
# Continue the conversation (remembers context)
npx -y bun ${SKILL_DIR}/scripts/main.ts "What is 2+2?" --sessionId task-abc123
npx -y bun ${SKILL_DIR}/scripts/main.ts "Now multiply that by 10" --sessionId task-abc123
# List recent sessions (max 100, sorted by update time)
npx -y bun ${SKILL_DIR}/scripts/main.ts --list-sessions
```
Session files are stored in `~/Library/Application Support/baoyu-skills/gemini-web/sessions/<id>.json` and contain:
- `id`: Session ID
- `metadata`: Gemini chat metadata for continuation
- `messages`: Array of `{role, content, timestamp, error?}`
- `createdAt`, `updatedAt`: Timestamps
Contains: `id`, `metadata` (Gemini chat state), `messages` array, timestamps.
## Extension Support
Custom configurations via EXTEND.md.
**Check paths** (priority order):
1. `.baoyu-skills/baoyu-danger-gemini-web/EXTEND.md` (project)
2. `~/.baoyu-skills/baoyu-danger-gemini-web/EXTEND.md` (user)
If found, load before workflow. Extension content overrides defaults.
Custom configurations via EXTEND.md. See **Preferences** section for paths and supported options.
+69 -102
View File
@@ -1,119 +1,107 @@
---
name: baoyu-danger-x-to-markdown
description: Convert X (Twitter) tweet or article URL to markdown. Uses reverse-engineered X API (private). Requires user consent before use.
description: Converts X (Twitter) tweets and articles to markdown with YAML front matter. Uses reverse-engineered API requiring user consent. Use when user mentions "X to markdown", "tweet to markdown", "save tweet", or provides x.com/twitter.com URLs for conversion.
---
# X to Markdown
Converts X (Twitter) content to markdown format:
- Tweet threads → Markdown with YAML front matter
- X Articles → Full article content extraction
Converts X content to markdown:
- Tweets/threads → Markdown with YAML front matter
- X Articles → Full content extraction
## Script Directory
**Important**: All scripts are located in the `scripts/` subdirectory of this skill.
Scripts located in `scripts/` subdirectory.
**Agent Execution Instructions**:
1. Determine this SKILL.md file's directory path as `SKILL_DIR`
2. Script path = `${SKILL_DIR}/scripts/<script-name>.ts`
3. Replace all `${SKILL_DIR}` in this document with the actual path
**Path Resolution**:
1. `SKILL_DIR` = this SKILL.md's directory
2. Script path = `${SKILL_DIR}/scripts/main.ts`
**Script Reference**:
| Script | Purpose |
|--------|---------|
| `scripts/main.ts` | CLI entry point for URL conversion |
## Consent Requirement
## ⚠️ Disclaimer (REQUIRED)
**Before any conversion**, check and obtain consent.
**Before using this skill**, the consent check MUST be performed.
### Consent Check Flow
### Consent Flow
**Step 1**: Check consent file
```bash
# macOS
cat ~/Library/Application\ Support/baoyu-skills/x-to-markdown/consent.json 2>/dev/null
cat ~/Library/Application\ Support/baoyu-skills/x-to-markdown/consent.json
# Linux
cat ~/.local/share/baoyu-skills/x-to-markdown/consent.json 2>/dev/null
# Windows (PowerShell)
Get-Content "$env:APPDATA\baoyu-skills\x-to-markdown\consent.json" 2>$null
cat ~/.local/share/baoyu-skills/x-to-markdown/consent.json
```
**Step 2**: If consent exists and `accepted: true` with matching `disclaimerVersion: "1.0"`:
Print warning and proceed:
**Step 2**: If `accepted: true` and `disclaimerVersion: "1.0"` → print warning and proceed:
```
⚠️ Warning: Using reverse-engineered X API (not official). Accepted on: <acceptedAt date>
Warning: Using reverse-engineered X API. Accepted on: <acceptedAt>
```
**Step 3**: If consent file doesn't exist or `disclaimerVersion` mismatch:
Display disclaimer and ask user:
**Step 3**: If missing or version mismatch → display disclaimer:
```
⚠️ DISCLAIMER
DISCLAIMER
This tool uses a reverse-engineered X (Twitter) API, NOT an official API.
This tool uses a reverse-engineered X API, NOT official.
Risks:
- May break without notice if X changes their API
- No official support or guarantees
- Account restrictions possible if API usage detected
- May break if X changes API
- No guarantees or support
- Possible account restrictions
- Use at your own risk
Do you accept these terms and wish to continue?
Accept terms and continue?
```
Use `AskUserQuestion` tool with options:
- **Yes, I accept** - Continue and save consent
- **No, I decline** - Exit immediately
Use `AskUserQuestion` with options: "Yes, I accept" | "No, I decline"
**Step 4**: On acceptance, create consent file:
**Step 4**: On accept create consent file:
```json
{
"version": 1,
"accepted": true,
"acceptedAt": "<ISO timestamp>",
"disclaimerVersion": "1.0"
}
```
**Step 5**: On decline → output "User declined. Exiting." and stop.
## Preferences (EXTEND.md)
Use Bash to check EXTEND.md existence (priority order):
```bash
# macOS
mkdir -p ~/Library/Application\ Support/baoyu-skills/x-to-markdown
cat > ~/Library/Application\ Support/baoyu-skills/x-to-markdown/consent.json << 'EOF'
{
"version": 1,
"accepted": true,
"acceptedAt": "<ISO timestamp>",
"disclaimerVersion": "1.0"
}
EOF
# Check project-level first
test -f .baoyu-skills/baoyu-danger-x-to-markdown/EXTEND.md && echo "project"
# Linux
mkdir -p ~/.local/share/baoyu-skills/x-to-markdown
cat > ~/.local/share/baoyu-skills/x-to-markdown/consent.json << 'EOF'
{
"version": 1,
"accepted": true,
"acceptedAt": "<ISO timestamp>",
"disclaimerVersion": "1.0"
}
EOF
# Then user-level (cross-platform: $HOME works on macOS/Linux/WSL)
test -f "$HOME/.baoyu-skills/baoyu-danger-x-to-markdown/EXTEND.md" && echo "user"
```
**Step 5**: On decline, output message and stop:
```
User declined the disclaimer. Exiting.
```
┌────────────────────────────────────────────────────────────┬───────────────────┐
│ Path │ Location │
├────────────────────────────────────────────────────────────┼───────────────────┤
│ .baoyu-skills/baoyu-danger-x-to-markdown/EXTEND.md │ Project directory │
├────────────────────────────────────────────────────────────┼───────────────────┤
│ $HOME/.baoyu-skills/baoyu-danger-x-to-markdown/EXTEND.md │ User home │
└────────────────────────────────────────────────────────────┴───────────────────┘
---
┌───────────┬───────────────────────────────────────────────────────────────────────────┐
│ Result │ Action │
├───────────┼───────────────────────────────────────────────────────────────────────────┤
│ Found │ Read, parse, apply settings │
├───────────┼───────────────────────────────────────────────────────────────────────────┤
│ Not found │ Use defaults │
└───────────┴───────────────────────────────────────────────────────────────────────────┘
**EXTEND.md Supports**: Default output directory | Output format preferences
## Usage
```bash
# Convert tweet (outputs markdown path)
npx -y bun ${SKILL_DIR}/scripts/main.ts <url>
# Save to specific file
npx -y bun ${SKILL_DIR}/scripts/main.ts <url> -o output.md
# JSON output
npx -y bun ${SKILL_DIR}/scripts/main.ts <url> --json
```
@@ -122,56 +110,35 @@ npx -y bun ${SKILL_DIR}/scripts/main.ts <url> --json
| Option | Description |
|--------|-------------|
| `<url>` | Tweet or article URL |
| `-o <path>` | Output path (file or dir) |
| `--json` | Output as JSON |
| `-o <path>` | Output path |
| `--json` | JSON output |
| `--login` | Refresh cookies only |
## File Structure
```
x-to-markdown/
└── {username}/
└── {tweet-id}.md
```
## Supported URLs
- `https://x.com/<user>/status/<id>`
- `https://twitter.com/<user>/status/<id>`
- `https://x.com/i/article/<id>`
## Output Format
## Output
```markdown
---
url: https://x.com/username/status/123
author: "Display Name (@username)"
url: https://x.com/user/status/123
author: "Name (@user)"
tweet_count: 3
---
Tweet content...
---
Thread continuation...
Content...
```
**File structure**: `x-to-markdown/{username}/{tweet-id}.md`
## Authentication
**Option 1**: Environment variables (recommended)
- `X_AUTH_TOKEN` - auth_token cookie
- `X_CT0` - ct0 cookie
**Option 2**: Chrome login (auto if env vars not set)
- First run opens Chrome for login
- Cookies cached locally
1. **Environment variables** (preferred): `X_AUTH_TOKEN`, `X_CT0`
2. **Chrome login** (fallback): Auto-opens Chrome, caches cookies locally
## Extension Support
Custom configurations via EXTEND.md.
**Check paths** (priority order):
1. `.baoyu-skills/baoyu-danger-x-to-markdown/EXTEND.md` (project)
2. `~/.baoyu-skills/baoyu-danger-x-to-markdown/EXTEND.md` (user)
If found, load before workflow. Extension content overrides defaults.
Custom configurations via EXTEND.md. See **Preferences** section for paths and supported options.
+71 -160
View File
@@ -1,98 +1,68 @@
---
name: baoyu-image-gen
description: AI SDK-based image generation using official OpenAI and Google APIs. Supports text-to-image, reference images, aspect ratios, and quality presets.
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.
---
# Image Generation (AI SDK)
Official API-based image generation via AI SDK. Supports OpenAI (DALL-E, GPT Image) and Google (Imagen, Gemini multimodal).
Official API-based image generation. Supports OpenAI and Google providers.
## Script Directory
**Important**: All scripts are located in the `scripts/` subdirectory of this skill.
**Agent Execution**:
1. `SKILL_DIR` = this SKILL.md file's directory
2. Script path = `${SKILL_DIR}/scripts/main.ts`
**Agent Execution Instructions**:
1. Determine this SKILL.md file's directory path as `SKILL_DIR`
2. Script path = `${SKILL_DIR}/scripts/<script-name>.ts`
3. Replace all `${SKILL_DIR}` in this document with the actual path
## Preferences (EXTEND.md)
**Script Reference**:
| Script | Purpose |
|--------|---------|
| `scripts/main.ts` | CLI entry point for image generation |
## Quick Start
Use Bash to check EXTEND.md existence (priority order):
```bash
# Basic generation (auto-detect provider)
# Check project-level first
test -f .baoyu-skills/baoyu-image-gen/EXTEND.md && echo "project"
# Then user-level (cross-platform: $HOME works on macOS/Linux/WSL)
test -f "$HOME/.baoyu-skills/baoyu-image-gen/EXTEND.md" && echo "user"
```
┌──────────────────────────────────────────────────┬───────────────────┐
│ Path │ Location │
├──────────────────────────────────────────────────┼───────────────────┤
│ .baoyu-skills/baoyu-image-gen/EXTEND.md │ Project directory │
├──────────────────────────────────────────────────┼───────────────────┤
│ $HOME/.baoyu-skills/baoyu-image-gen/EXTEND.md │ User home │
└──────────────────────────────────────────────────┴───────────────────┘
┌───────────┬───────────────────────────────────────────────────────────────────────────┐
│ Result │ Action │
├───────────┼───────────────────────────────────────────────────────────────────────────┤
│ Found │ Read, parse, apply settings │
├───────────┼───────────────────────────────────────────────────────────────────────────┤
│ Not found │ Use defaults │
└───────────┴───────────────────────────────────────────────────────────────────────────┘
**EXTEND.md Supports**: Default provider | Default quality | Default aspect ratio
## Usage
```bash
# Basic
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A cat" --image cat.png
# With aspect ratio
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A landscape" --image landscape.png --ar 16:9
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A landscape" --image out.png --ar 16:9
# High quality (2k)
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A cat" --image cat.png --quality 2k
# Specific provider
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A cat" --image cat.png --provider openai
# High quality
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A cat" --image out.png --quality 2k
# From prompt files
npx -y bun ${SKILL_DIR}/scripts/main.ts --promptfiles system.md content.md --image out.png
# With reference images (Google multimodal only)
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "Make blue" --image out.png --ref source.png
```
## Commands
### Basic Image Generation
```bash
# Generate with prompt
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A sunset over mountains" --image sunset.png
# Shorthand
npx -y bun ${SKILL_DIR}/scripts/main.ts -p "A cute robot" --image robot.png
```
### Aspect Ratios
```bash
# Common ratios: 1:1, 16:9, 9:16, 4:3, 3:4, 2.35:1
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A portrait" --image portrait.png --ar 3:4
# Or specify exact size
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "Banner" --image banner.png --size 1792x1024
```
### Reference Images (Google Multimodal)
```bash
# Image editing with reference
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "Make it blue" --image blue.png --ref original.png
# Multiple references
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "Combine these styles" --image out.png --ref a.png b.png
```
### Quality Presets
```bash
# Normal quality (default)
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A cat" --image cat.png --quality normal
# High quality (2k resolution)
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A cat" --image cat.png --quality 2k
```
### Output Formats
```bash
# Plain output (prints saved path)
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A cat" --image cat.png
# JSON output
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A cat" --image cat.png --json
# Specific provider
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A cat" --image out.png --provider openai
```
## Options
@@ -106,114 +76,55 @@ npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A cat" --image cat.png --json
| `--model <id>`, `-m` | Model ID |
| `--ar <ratio>` | Aspect ratio (e.g., `16:9`, `1:1`, `4:3`) |
| `--size <WxH>` | Size (e.g., `1024x1024`) |
| `--quality normal\|2k` | Quality preset (default: normal) |
| `--quality normal\|2k` | Quality preset (default: 2k) |
| `--imageSize 1K\|2K\|4K` | Image size for Google (default: from quality) |
| `--ref <files...>` | Reference images (Google multimodal only) |
| `--n <count>` | Number of images |
| `--json` | JSON output |
| `--help`, `-h` | Show help |
## Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `OPENAI_API_KEY` | OpenAI API key | - |
| `GOOGLE_API_KEY` | Google API key | - |
| `OPENAI_IMAGE_MODEL` | OpenAI model | `gpt-image-1.5` |
| `GOOGLE_IMAGE_MODEL` | Google model | `gemini-3-pro-image-preview` |
| `OPENAI_BASE_URL` | Custom OpenAI endpoint | - |
| `GOOGLE_BASE_URL` | Custom Google endpoint | - |
| Variable | Description |
|----------|-------------|
| `OPENAI_API_KEY` | OpenAI API key |
| `GOOGLE_API_KEY` | Google API key |
| `OPENAI_IMAGE_MODEL` | OpenAI model override |
| `GOOGLE_IMAGE_MODEL` | Google model override |
| `OPENAI_BASE_URL` | Custom OpenAI endpoint |
| `GOOGLE_BASE_URL` | Custom Google endpoint |
**Load Priority**: CLI args > `process.env` > `<cwd>/.baoyu-skills/.env` > `~/.baoyu-skills/.env`
**Load Priority**: CLI args > env vars > `<cwd>/.baoyu-skills/.env` > `~/.baoyu-skills/.env`
## Provider & Model Strategy
## Provider Selection
### Auto-Selection
1. If `--provider` specified → use it
2. If only one API key available → use that provider
3. If both available → default to Google (multimodal LLMs more versatile)
### API Selection by Model Type
| Model Category | API Function | Example Models |
|----------------|--------------|----------------|
| Google Multimodal | `generateText` | `gemini-2.0-flash-exp-image-generation` |
| Google Imagen | `experimental_generateImage` | `imagen-3.0-generate-002` |
| OpenAI | `experimental_generateImage` | `gpt-image-1`, `dall-e-3` |
### Available Models
**Google**:
- `gemini-3-pro-image-preview` - Default, multimodal generation
- `gemini-2.0-flash-exp-image-generation` - Gemini 2.0 Flash
- `imagen-3.0-generate-002` - Imagen 3
**OpenAI**:
- `gpt-image-1.5` - Default, GPT Image 1.5
- `gpt-image-1` - GPT Image 1
- `dall-e-3` - DALL-E 3
1. `--provider` specified → use it
2. Only one API key available → use that provider
3. Both available → default to Google
## Quality Presets
| Preset | OpenAI | Google | Use Case |
|--------|--------|--------|----------|
| `normal` | 1024x1024 | Default | Covers, illustrations |
| `2k` | 2048x2048 | "2048px" in prompt | Infographics, slides |
| Preset | Google imageSize | OpenAI Size | Use Case |
|--------|------------------|-------------|----------|
| `normal` | 1K | 1024px | Quick previews |
| `2k` (default) | 2K | 2048px | Covers, illustrations, infographics |
## Aspect Ratio Handling
**Google imageSize**: Can be overridden with `--imageSize 1K|2K|4K`
- **Multimodal LLMs**: Embedded in prompt (e.g., `"... aspect ratio 16:9"`)
- **Image-only models**: Uses `aspectRatio` or `size` parameter
- **Common ratios**: 1:1, 16:9, 9:16, 4:3, 3:4, 2.35:1
## Aspect Ratios
## Examples
Supported: `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `2.35:1`
### Generate Cover Image
```bash
npx -y bun ${SKILL_DIR}/scripts/main.ts \
--prompt "A minimalist tech illustration with blue gradients" \
--image cover.png --ar 2.35:1 --quality 2k
```
### Generate Social Media Post
```bash
npx -y bun ${SKILL_DIR}/scripts/main.ts \
--prompt "Instagram post about coffee" \
--image post.png --ar 1:1
```
### Edit Image with Reference
```bash
npx -y bun ${SKILL_DIR}/scripts/main.ts \
--prompt "Change the background to sunset" \
--image edited.png --ref original.png --provider google
```
### Batch Generation from Prompt File
```bash
# Create prompt file with detailed instructions
npx -y bun ${SKILL_DIR}/scripts/main.ts \
--promptfiles style-guide.md scene-description.md \
--image scene.png
```
- Google multimodal: uses `imageConfig.aspectRatio`
- Google Imagen: uses `aspectRatio` parameter
- OpenAI: maps to closest supported size
## Error Handling
- **Missing API key**: Clear error with setup instructions
- **Generation failure**: Auto-retry once, then error
- **Invalid aspect ratio**: Warning, proceed with default
- **Reference images with image-only model**: Warning, ignore refs
- Missing API key error with setup instructions
- Generation failure → auto-retry once
- Invalid aspect ratio → warning, proceed with default
- Reference images with non-multimodal model → warning, ignore refs
## Extension Support
Custom configurations via EXTEND.md.
**Check paths** (priority order):
1. `.baoyu-skills/baoyu-image-gen/EXTEND.md` (project)
2. `~/.baoyu-skills/baoyu-image-gen/EXTEND.md` (user)
If found, load before workflow. Extension content overrides defaults.
Custom configurations via EXTEND.md. See **Preferences** section for paths and supported options.
+26 -257
View File
@@ -1,36 +1,8 @@
import fs from "node:fs";
import path from "node:path";
import process from "node:process";
import { homedir } from "node:os";
import { mkdir, readFile, writeFile } from "node:fs/promises";
type Provider = "google" | "openai";
type Quality = "normal" | "2k";
type CliArgs = {
prompt: string | null;
promptFiles: string[];
imagePath: string | null;
provider: Provider | null;
model: string | null;
aspectRatio: string | null;
size: string | null;
quality: Quality;
referenceImages: string[];
n: number;
json: boolean;
help: boolean;
};
const GOOGLE_MULTIMODAL_MODELS = [
"gemini-3-pro-image-preview",
"gemini-2.0-flash-exp-image-generation",
"gemini-2.5-flash-preview-native-audio-dialog",
];
const GOOGLE_IMAGEN_MODELS = ["imagen-3.0-generate-002", "imagen-3.0-generate-001"];
const OPENAI_IMAGE_MODELS = ["gpt-image-1.5", "gpt-image-1", "dall-e-3", "dall-e-2"];
import type { CliArgs, Provider } from "./types";
function printUsage(): void {
console.log(`Usage:
@@ -46,7 +18,8 @@ Options:
-m, --model <id> Model ID
--ar <ratio> Aspect ratio (e.g., 16:9, 1:1, 4:3)
--size <WxH> Size (e.g., 1024x1024)
--quality normal|2k Quality preset (default: normal)
--quality normal|2k Quality preset (default: 2k)
--imageSize 1K|2K|4K Image size for Google (default: from quality)
--ref <files...> Reference images (Google multimodal only)
--n <count> Number of images (default: 1)
--json JSON output
@@ -55,6 +28,7 @@ Options:
Environment variables:
OPENAI_API_KEY OpenAI API key
GOOGLE_API_KEY Google API key
GEMINI_API_KEY Gemini API key (alias for GOOGLE_API_KEY)
OPENAI_IMAGE_MODEL Default OpenAI model (gpt-image-1.5)
GOOGLE_IMAGE_MODEL Default Google model (gemini-3-pro-image-preview)
OPENAI_BASE_URL Custom OpenAI endpoint
@@ -72,7 +46,8 @@ function parseArgs(argv: string[]): CliArgs {
model: null,
aspectRatio: null,
size: null,
quality: "normal",
quality: "2k",
imageSize: null,
referenceImages: [],
n: 1,
json: false,
@@ -163,6 +138,13 @@ function parseArgs(argv: string[]): CliArgs {
continue;
}
if (a === "--imageSize") {
const v = argv[++i]?.toUpperCase();
if (v !== "1K" && v !== "2K" && v !== "4K") throw new Error(`Invalid imageSize: ${v}`);
out.imageSize = v;
continue;
}
if (a === "--ref" || a === "--reference") {
const { items, next } = takeMany(i);
if (items.length === 0) throw new Error(`Missing files for ${a}`);
@@ -259,7 +241,7 @@ function normalizeOutputImagePath(p: string): string {
function detectProvider(args: CliArgs): Provider {
if (args.provider) return args.provider;
const hasGoogle = !!process.env.GOOGLE_API_KEY;
const hasGoogle = !!(process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY);
const hasOpenai = !!process.env.OPENAI_API_KEY;
if (hasGoogle && !hasOpenai) return "google";
@@ -267,235 +249,21 @@ function detectProvider(args: CliArgs): Provider {
if (hasGoogle && hasOpenai) return "google";
throw new Error(
"No API key found. Set GOOGLE_API_KEY or OPENAI_API_KEY.\n" +
"No API key found. Set GOOGLE_API_KEY, GEMINI_API_KEY, or OPENAI_API_KEY.\n" +
"Create ~/.baoyu-skills/.env or <cwd>/.baoyu-skills/.env with your keys."
);
}
function getDefaultModel(provider: Provider): string {
type ProviderModule = {
getDefaultModel: () => string;
generateImage: (prompt: string, model: string, args: CliArgs) => Promise<Uint8Array>;
};
async function loadProviderModule(provider: Provider): Promise<ProviderModule> {
if (provider === "google") {
return process.env.GOOGLE_IMAGE_MODEL || "gemini-3-pro-image-preview";
return (await import("./providers/google")) as ProviderModule;
}
return process.env.OPENAI_IMAGE_MODEL || "gpt-image-1.5";
}
function isGoogleMultimodal(model: string): boolean {
return GOOGLE_MULTIMODAL_MODELS.some((m) => model.includes(m));
}
function isGoogleImagen(model: string): boolean {
return GOOGLE_IMAGEN_MODELS.some((m) => model.includes(m));
}
function buildPromptWithAspect(prompt: string, ar: string | null, quality: Quality): string {
let result = prompt;
if (ar) {
result += ` Aspect ratio: ${ar}.`;
}
if (quality === "2k") {
result += " High resolution 2048px.";
}
return result;
}
function parseAspectRatio(ar: string): { width: number; height: number } | null {
const match = ar.match(/^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?)$/);
if (!match) return null;
const w = parseFloat(match[1]!);
const h = parseFloat(match[2]!);
if (w <= 0 || h <= 0) return null;
return { width: w, height: h };
}
function getOpenAISize(ar: string | null, quality: Quality): string {
const base = quality === "2k" ? 2048 : 1024;
if (!ar) return `${base}x${base}`;
const parsed = parseAspectRatio(ar);
if (!parsed) return `${base}x${base}`;
const ratio = parsed.width / parsed.height;
if (Math.abs(ratio - 1) < 0.1) return `${base}x${base}`;
if (ratio > 1.5) return quality === "2k" ? "2048x1024" : "1792x1024";
if (ratio < 0.67) return quality === "2k" ? "1024x2048" : "1024x1792";
return `${base}x${base}`;
}
async function readImageAsBase64(p: string): Promise<{ data: string; mimeType: string }> {
const buf = await readFile(p);
const ext = path.extname(p).toLowerCase();
let mimeType = "image/png";
if (ext === ".jpg" || ext === ".jpeg") mimeType = "image/jpeg";
else if (ext === ".gif") mimeType = "image/gif";
else if (ext === ".webp") mimeType = "image/webp";
return { data: buf.toString("base64"), mimeType };
}
async function generateWithGoogleMultimodal(
prompt: string,
model: string,
args: CliArgs
): Promise<Uint8Array> {
const { generateText } = await import("ai");
const { createGoogleGenerativeAI } = await import("@ai-sdk/google");
const google = createGoogleGenerativeAI({
apiKey: process.env.GOOGLE_API_KEY,
baseURL: process.env.GOOGLE_BASE_URL,
});
const fullPrompt = buildPromptWithAspect(prompt, args.aspectRatio, args.quality);
const messages: any[] = [];
const content: any[] = [];
for (const refPath of args.referenceImages) {
const { data, mimeType } = await readImageAsBase64(refPath);
content.push({ type: "image", image: data, mimeType });
}
content.push({ type: "text", text: fullPrompt });
messages.push({ role: "user", content });
const result = await generateText({
model: google(model, { useSearchGrounding: false }),
messages,
providerOptions: {
google: {
responseModalities: ["TEXT", "IMAGE"],
},
},
});
const files = (result as any).files;
if (!files || files.length === 0) {
const expRes = (result as any).response?.body?.candidates?.[0]?.content?.parts;
if (expRes) {
for (const part of expRes) {
if (part.inlineData?.data) {
return Uint8Array.from(Buffer.from(part.inlineData.data, "base64"));
}
}
}
throw new Error("No image in response");
}
const img = files[0];
if (img.uint8Array) return img.uint8Array;
if (img.base64) return Uint8Array.from(Buffer.from(img.base64, "base64"));
throw new Error("Cannot extract image data");
}
async function generateWithGoogleImagen(
prompt: string,
model: string,
args: CliArgs
): Promise<Uint8Array> {
const { experimental_generateImage: generateImage } = await import("ai");
const { createGoogleGenerativeAI } = await import("@ai-sdk/google");
const google = createGoogleGenerativeAI({
apiKey: process.env.GOOGLE_API_KEY,
baseURL: process.env.GOOGLE_BASE_URL,
});
const fullPrompt = buildPromptWithAspect(prompt, args.aspectRatio, args.quality);
const result = await generateImage({
model: google.image(model),
prompt: fullPrompt,
n: args.n,
aspectRatio: args.aspectRatio || undefined,
});
const img = result.images[0];
if (!img) throw new Error("No image in response");
if (img.uint8Array) return img.uint8Array;
if (img.base64) return Uint8Array.from(Buffer.from(img.base64, "base64"));
throw new Error("Cannot extract image data");
}
async function generateWithOpenAI(
prompt: string,
model: string,
args: CliArgs
): Promise<Uint8Array> {
const baseURL = process.env.OPENAI_BASE_URL || "https://api.openai.com/v1";
const apiKey = process.env.OPENAI_API_KEY;
if (!apiKey) throw new Error("OPENAI_API_KEY is required");
const size = args.size || getOpenAISize(args.aspectRatio, args.quality);
const body: Record<string, any> = {
model,
prompt,
size,
};
if (model.includes("dall-e-3")) {
body.quality = args.quality === "2k" ? "hd" : "standard";
}
const res = await fetch(`${baseURL}/images/generations`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify(body),
});
if (!res.ok) {
const err = await res.text();
throw new Error(`OpenAI API error: ${err}`);
}
const result = (await res.json()) as { data: Array<{ url?: string; b64_json?: string }> };
const img = result.data[0];
if (img?.b64_json) {
return Uint8Array.from(Buffer.from(img.b64_json, "base64"));
}
if (img?.url) {
const imgRes = await fetch(img.url);
if (!imgRes.ok) throw new Error("Failed to download image");
const buf = await imgRes.arrayBuffer();
return new Uint8Array(buf);
}
throw new Error("No image in response");
}
async function generate(
provider: Provider,
model: string,
prompt: string,
args: CliArgs
): Promise<Uint8Array> {
if (provider === "google") {
if (isGoogleMultimodal(model)) {
return generateWithGoogleMultimodal(prompt, model, args);
}
if (isGoogleImagen(model)) {
if (args.referenceImages.length > 0) {
console.error("Warning: Reference images not supported with Imagen models, ignoring.");
}
return generateWithGoogleImagen(prompt, model, args);
}
return generateWithGoogleMultimodal(prompt, model, args);
}
if (args.referenceImages.length > 0) {
console.error("Warning: Reference images not supported with OpenAI, ignoring.");
}
return generateWithOpenAI(prompt, model, args);
return (await import("./providers/openai")) as ProviderModule;
}
async function main(): Promise<void> {
@@ -527,7 +295,8 @@ async function main(): Promise<void> {
}
const provider = detectProvider(args);
const model = args.model || getDefaultModel(provider);
const providerModule = await loadProviderModule(provider);
const model = args.model || providerModule.getDefaultModel();
const outputPath = normalizeOutputImagePath(args.imagePath);
let imageData: Uint8Array;
@@ -535,7 +304,7 @@ async function main(): Promise<void> {
while (true) {
try {
imageData = await generate(provider, model, prompt, args);
imageData = await providerModule.generateImage(prompt, model, args);
break;
} catch (e) {
if (!retried) {
@@ -0,0 +1,149 @@
import path from "node:path";
import { readFile } from "node:fs/promises";
import type { CliArgs } from "../types";
const GOOGLE_MULTIMODAL_MODELS = ["gemini-3-pro-image-preview"];
const GOOGLE_IMAGEN_MODELS = ["imagen-3.0-generate-002", "imagen-3.0-generate-001"];
export function getDefaultModel(): string {
return process.env.GOOGLE_IMAGE_MODEL || "gemini-3-pro-image-preview";
}
function isGoogleMultimodal(model: string): boolean {
return GOOGLE_MULTIMODAL_MODELS.some((m) => model.includes(m));
}
function isGoogleImagen(model: string): boolean {
return GOOGLE_IMAGEN_MODELS.some((m) => model.includes(m));
}
function getGoogleApiKey(): string | null {
return process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY || null;
}
function getGoogleImageSize(args: CliArgs): "1K" | "2K" | "4K" {
if (args.imageSize) return args.imageSize as "1K" | "2K" | "4K";
return args.quality === "2k" ? "2K" : "1K";
}
function buildPromptWithAspect(prompt: string, ar: string | null, quality: CliArgs["quality"]): string {
let result = prompt;
if (ar) {
result += ` Aspect ratio: ${ar}.`;
}
if (quality === "2k") {
result += " High resolution 2048px.";
}
return result;
}
async function readImageAsBase64(p: string): Promise<{ data: string; mimeType: string }> {
const buf = await readFile(p);
const ext = path.extname(p).toLowerCase();
let mimeType = "image/png";
if (ext === ".jpg" || ext === ".jpeg") mimeType = "image/jpeg";
else if (ext === ".gif") mimeType = "image/gif";
else if (ext === ".webp") mimeType = "image/webp";
return { data: buf.toString("base64"), mimeType };
}
async function generateWithGemini(
prompt: string,
model: string,
args: CliArgs
): Promise<Uint8Array> {
const { GoogleGenAI } = await import("@google/genai");
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) {
const { data, mimeType } = await readImageAsBase64(refPath);
input.push({ type: "image", data, mime_type: mimeType });
}
input.push({ type: "text", text: prompt });
const imageConfig: { image_size: "1K" | "2K" | "4K"; aspect_ratio?: string } = {
image_size: getGoogleImageSize(args),
};
if (args.aspectRatio) {
imageConfig.aspect_ratio = args.aspectRatio;
}
console.log("Generating image with Gemini...", imageConfig);
const interaction = await ai.interactions.create({
model,
input,
response_modalities: ["image"],
generation_config: {
image_config: imageConfig,
},
});
console.log("Generation completed.");
for (const output of interaction.outputs || []) {
if (output.type === "image" && output.data) {
return Uint8Array.from(Buffer.from(output.data, "base64"));
}
}
throw new Error("No image in response");
}
async function generateWithImagen(
prompt: string,
model: string,
args: CliArgs
): 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 result = await generateImage({
model: google.image(model),
prompt: fullPrompt,
n: args.n,
aspectRatio: args.aspectRatio || undefined,
});
const img = result.images[0];
if (!img) throw new Error("No image in response");
if (img.uint8Array) return img.uint8Array;
if (img.base64) return Uint8Array.from(Buffer.from(img.base64, "base64"));
throw new Error("Cannot extract image data");
}
export async function generateImage(
prompt: string,
model: string,
args: CliArgs
): Promise<Uint8Array> {
if (isGoogleImagen(model)) {
if (args.referenceImages.length > 0) {
console.error("Warning: Reference images not supported with Imagen models, ignoring.");
}
return generateWithImagen(prompt, model, args);
}
if (!isGoogleMultimodal(model) && args.referenceImages.length > 0) {
console.error("Warning: Reference images are only supported with Gemini multimodal models.");
}
return generateWithGemini(prompt, model, args);
}
@@ -0,0 +1,114 @@
import type { CliArgs } from "../types";
export function getDefaultModel(): string {
return process.env.OPENAI_IMAGE_MODEL || "gpt-image-1.5";
}
function parseAspectRatio(ar: string): { width: number; height: number } | null {
const match = ar.match(/^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?)$/);
if (!match) return null;
const w = parseFloat(match[1]!);
const h = parseFloat(match[2]!);
if (w <= 0 || h <= 0) return null;
return { width: w, height: h };
}
type SizeMapping = {
square: string;
landscape: string;
portrait: string;
};
function getOpenAISize(
model: string,
ar: string | null,
quality: CliArgs["quality"]
): string {
const isDalle3 = model.includes("dall-e-3");
const isDalle2 = model.includes("dall-e-2");
if (isDalle2) {
return "1024x1024";
}
const sizes: SizeMapping = isDalle3
? {
square: "1024x1024",
landscape: "1792x1024",
portrait: "1024x1792",
}
: {
square: "1024x1024",
landscape: "1536x1024",
portrait: "1024x1536",
};
if (!ar) return sizes.square;
const parsed = parseAspectRatio(ar);
if (!parsed) return sizes.square;
const ratio = parsed.width / parsed.height;
if (Math.abs(ratio - 1) < 0.1) return sizes.square;
if (ratio > 1.5) return sizes.landscape;
if (ratio < 0.67) return sizes.portrait;
return sizes.square;
}
export async function generateImage(
prompt: string,
model: string,
args: CliArgs
): Promise<Uint8Array> {
const baseURL = process.env.OPENAI_BASE_URL || "https://api.openai.com/v1";
const apiKey = process.env.OPENAI_API_KEY;
if (!apiKey) throw new Error("OPENAI_API_KEY is required");
if (args.referenceImages.length > 0) {
console.error("Warning: Reference images not supported with OpenAI, ignoring.");
}
const size = args.size || getOpenAISize(model, args.aspectRatio, args.quality);
const body: Record<string, any> = {
model,
prompt,
size,
};
if (model.includes("dall-e-3")) {
body.quality = args.quality === "2k" ? "hd" : "standard";
}
const res = await fetch(`${baseURL}/images/generations`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify(body),
});
if (!res.ok) {
const err = await res.text();
throw new Error(`OpenAI API error: ${err}`);
}
const result = (await res.json()) as { data: Array<{ url?: string; b64_json?: string }> };
const img = result.data[0];
if (img?.b64_json) {
return Uint8Array.from(Buffer.from(img.b64_json, "base64"));
}
if (img?.url) {
const imgRes = await fetch(img.url);
if (!imgRes.ok) throw new Error("Failed to download image");
const buf = await imgRes.arrayBuffer();
return new Uint8Array(buf);
}
throw new Error("No image in response");
}
+18
View File
@@ -0,0 +1,18 @@
export type Provider = "google" | "openai";
export type Quality = "normal" | "2k";
export type CliArgs = {
prompt: string | null;
promptFiles: string[];
imagePath: string | null;
provider: Provider | null;
model: string | null;
aspectRatio: string | null;
size: string | null;
quality: Quality;
imageSize: string | null;
referenceImages: string[];
n: number;
json: boolean;
help: boolean;
};
+57 -47
View File
@@ -1,84 +1,94 @@
---
name: baoyu-post-to-wechat
description: Post content to WeChat Official Account (微信公众号). Supports both article posting (文章) and image-text posting (图文).
description: Posts content to WeChat Official Account (微信公众号) via Chrome CDP automation. Supports article posting (文章) with full markdown formatting and image-text posting (图文) with multiple images. Use when user mentions "发布公众号", "post to wechat", "微信公众号", or "图文/文章".
---
# Post to WeChat Official Account (微信公众号)
Post content to WeChat Official Account using Chrome CDP automation.
# Post to WeChat Official Account
## Script Directory
**Important**: All scripts are located in the `scripts/` subdirectory of this skill.
**Agent Execution**: Determine this SKILL.md directory as `SKILL_DIR`, then use `${SKILL_DIR}/scripts/<name>.ts`.
**Agent Execution Instructions**:
1. Determine this SKILL.md file's directory path as `SKILL_DIR`
2. Script path = `${SKILL_DIR}/scripts/<script-name>.ts`
3. Replace all `${SKILL_DIR}` in this document with the actual path
**Script Reference**:
| Script | Purpose |
|--------|---------|
| `scripts/wechat-browser.ts` | Image-text posts (图文) |
| `scripts/wechat-article.ts` | Full article posting (文章) |
| `scripts/md-to-wechat.ts` | Markdown → WeChat HTML conversion |
| `scripts/copy-to-clipboard.ts` | Copy content to clipboard |
| `scripts/paste-from-clipboard.ts` | Send real paste keystroke |
| `scripts/wechat-article.ts` | Article posting (文章) |
| `scripts/md-to-wechat.ts` | Markdown → WeChat HTML |
## Quick Usage
## Preferences (EXTEND.md)
### Image-Text (图文) - Multiple images with title/content
Use Bash to check EXTEND.md existence (priority order):
```bash
# From markdown file and image directory
npx -y bun ${SKILL_DIR}/scripts/wechat-browser.ts --markdown article.md --images ./images/
# Check project-level first
test -f .baoyu-skills/baoyu-post-to-wechat/EXTEND.md && echo "project"
# With explicit parameters
npx -y bun ${SKILL_DIR}/scripts/wechat-browser.ts --title "标题" --content "内容" --image img1.png --image img2.png --submit
# Then user-level (cross-platform: $HOME works on macOS/Linux/WSL)
test -f "$HOME/.baoyu-skills/baoyu-post-to-wechat/EXTEND.md" && echo "user"
```
### Article (文章) - Full markdown with formatting
┌────────────────────────────────────────────────────────┬───────────────────┐
│ Path │ Location │
├────────────────────────────────────────────────────────┼───────────────────┤
│ .baoyu-skills/baoyu-post-to-wechat/EXTEND.md │ Project directory │
├────────────────────────────────────────────────────────┼───────────────────┤
│ $HOME/.baoyu-skills/baoyu-post-to-wechat/EXTEND.md │ User home │
└────────────────────────────────────────────────────────┴───────────────────┘
┌───────────┬───────────────────────────────────────────────────────────────────────────┐
│ Result │ Action │
├───────────┼───────────────────────────────────────────────────────────────────────────┤
│ Found │ Read, parse, apply settings │
├───────────┼───────────────────────────────────────────────────────────────────────────┤
│ Not found │ Use defaults │
└───────────┴───────────────────────────────────────────────────────────────────────────┘
**EXTEND.md Supports**: Default theme | Auto-submit preference | Chrome profile path
## Usage
### Image-Text (图文)
```bash
npx -y bun ${SKILL_DIR}/scripts/wechat-browser.ts --markdown article.md --images ./images/
npx -y bun ${SKILL_DIR}/scripts/wechat-browser.ts --title "标题" --content "内容" --image img.png --submit
```
### Article (文章)
```bash
# Post markdown article
npx -y bun ${SKILL_DIR}/scripts/wechat-article.ts --markdown article.md --theme grace
```
> **Note**: `${SKILL_DIR}` represents this skill's installation directory. Agent replaces with actual path at runtime.
## Detailed References
## References
| Topic | Reference |
|-------|-----------|
| Image-text parameters, auto-compression | [references/image-text-posting.md](references/image-text-posting.md) |
| Article themes, image handling | [references/article-posting.md](references/article-posting.md) |
- **Image-Text Posting**: See `references/image-text-posting.md` for detailed image-text posting guide
- **Article Posting**: See `references/article-posting.md` for detailed article posting guide
## Prerequisites
- Google Chrome installed
- `bun` runtime (via `npx -y bun`)
- First run: log in to WeChat Official Account in the opened browser window
## Features
## Feature Comparison
| Feature | Image-Text | Article |
|---------|------------|---------|
| Multiple images | ✓ (up to 9) | ✓ (inline) |
| Markdown support | Title/content extraction | Full formatting |
| Auto title compression | ✓ (to 20 chars) | ✗ |
| Content compression | ✓ (to 1000 chars) | ✗ |
| Auto compression | ✓ (title: 20, content: 1000 chars) | ✗ |
| Themes | ✗ | ✓ (default, grace, simple) |
## Prerequisites
- Google Chrome
- First run: log in to WeChat Official Account (session preserved)
## Troubleshooting
- **Not logged in**: First run opens browser - scan QR code to log in, session is preserved
- **Chrome not found**: Set `WECHAT_BROWSER_CHROME_PATH` environment variable
- **Paste fails**: Check system clipboard permissions
| Issue | Solution |
|-------|----------|
| Not logged in | First run opens browser - scan QR to log in |
| Chrome not found | Set `WECHAT_BROWSER_CHROME_PATH` env var |
| Paste fails | Check system clipboard permissions |
## Extension Support
Custom configurations via EXTEND.md.
**Check paths** (priority order):
1. `.baoyu-skills/baoyu-post-to-wechat/EXTEND.md` (project)
2. `~/.baoyu-skills/baoyu-post-to-wechat/EXTEND.md` (user)
If found, load before workflow. Extension content overrides defaults.
Custom configurations via EXTEND.md. See **Preferences** section for paths and supported options.
+68 -71
View File
@@ -1,11 +1,11 @@
---
name: baoyu-post-to-x
description: Post content and articles to X (Twitter). Supports regular posts with images/videos and X Articles (long-form Markdown). Uses real Chrome with CDP to bypass anti-automation.
description: Posts content and articles to X (Twitter). Supports regular posts with images/videos and X Articles (long-form Markdown). Uses real Chrome with CDP to bypass anti-automation. Use when user asks to "post to X", "tweet", "publish to Twitter", or "share on X".
---
# Post to X (Twitter)
Post content, images, videos, and long-form articles to X using real Chrome browser (bypasses anti-bot detection).
Posts text, images, videos, and long-form articles to X via real Chrome browser (bypasses anti-bot detection).
## Script Directory
@@ -27,11 +27,41 @@ Post content, images, videos, and long-form articles to X using real Chrome brow
| `scripts/copy-to-clipboard.ts` | Copy content to clipboard |
| `scripts/paste-from-clipboard.ts` | Send real paste keystroke |
## Preferences (EXTEND.md)
Use Bash to check EXTEND.md existence (priority order):
```bash
# Check project-level first
test -f .baoyu-skills/baoyu-post-to-x/EXTEND.md && echo "project"
# Then user-level (cross-platform: $HOME works on macOS/Linux/WSL)
test -f "$HOME/.baoyu-skills/baoyu-post-to-x/EXTEND.md" && echo "user"
```
┌──────────────────────────────────────────────────┬───────────────────┐
│ Path │ Location │
├──────────────────────────────────────────────────┼───────────────────┤
│ .baoyu-skills/baoyu-post-to-x/EXTEND.md │ Project directory │
├──────────────────────────────────────────────────┼───────────────────┤
│ $HOME/.baoyu-skills/baoyu-post-to-x/EXTEND.md │ User home │
└──────────────────────────────────────────────────┴───────────────────┘
┌───────────┬───────────────────────────────────────────────────────────────────────────┐
│ Result │ Action │
├───────────┼───────────────────────────────────────────────────────────────────────────┤
│ Found │ Read, parse, apply settings │
├───────────┼───────────────────────────────────────────────────────────────────────────┤
│ Not found │ Use defaults │
└───────────┴───────────────────────────────────────────────────────────────────────────┘
**EXTEND.md Supports**: Default Chrome profile | Auto-submit preference
## Prerequisites
- Google Chrome or Chromium installed
- `bun` installed (for running scripts)
- First run: log in to X in the opened browser window
- Google Chrome or Chromium
- `bun` runtime
- First run: log in to X manually (session saved)
## References
@@ -45,72 +75,57 @@ Post content, images, videos, and long-form articles to X using real Chrome brow
Text + up to 4 images.
```bash
# Preview mode (doesn't post)
npx -y bun ${SKILL_DIR}/scripts/x-browser.ts "Hello from Claude!" --image ./screenshot.png
# Actually post
npx -y bun ${SKILL_DIR}/scripts/x-browser.ts "Hello!" --image ./photo.png --submit
npx -y bun ${SKILL_DIR}/scripts/x-browser.ts "Hello!" --image ./photo.png # Preview
npx -y bun ${SKILL_DIR}/scripts/x-browser.ts "Hello!" --image ./photo.png --submit # Post
```
> **Note**: `${SKILL_DIR}` represents this skill's installation directory. Agent replaces with actual path at runtime.
**Parameters**:
| Parameter | Description |
|-----------|-------------|
| `<text>` | Post content (positional argument) |
| `--image <path>` | Image file path (can be repeated, max 4) |
| `--submit` | Actually post (default: preview only) |
| `--profile <dir>` | Custom Chrome profile directory |
| `<text>` | Post content (positional) |
| `--image <path>` | Image file (repeatable, max 4) |
| `--submit` | Post (default: preview) |
| `--profile <dir>` | Custom Chrome profile |
---
## Video Posts
Text + video file (MP4, MOV, WebM).
Text + video file.
```bash
# Preview mode (doesn't post)
npx -y bun ${SKILL_DIR}/scripts/x-video.ts "Check out this video!" --video ./clip.mp4
# Actually post
npx -y bun ${SKILL_DIR}/scripts/x-video.ts "Amazing content" --video ./demo.mp4 --submit
npx -y bun ${SKILL_DIR}/scripts/x-video.ts "Check this out!" --video ./clip.mp4 # Preview
npx -y bun ${SKILL_DIR}/scripts/x-video.ts "Amazing content" --video ./demo.mp4 --submit # Post
```
**Parameters**:
| Parameter | Description |
|-----------|-------------|
| `<text>` | Post content (positional argument) |
| `--video <path>` | Video file path (required) |
| `--submit` | Actually post (default: preview only) |
| `--profile <dir>` | Custom Chrome profile directory |
| `<text>` | Post content (positional) |
| `--video <path>` | Video file (MP4, MOV, WebM) |
| `--submit` | Post (default: preview) |
| `--profile <dir>` | Custom Chrome profile |
**Video Limits**:
- Regular accounts: 140 seconds max
- X Premium: up to 60 minutes
- Supported formats: MP4, MOV, WebM
- Processing time: 30-60 seconds depending on file size
**Limits**: Regular 140s max, Premium 60min. Processing: 30-60s.
---
## Quote Tweets
Quote an existing tweet with your comment - a way to share content while giving credit to the original creator.
Quote an existing tweet with comment.
```bash
# Preview mode (doesn't post)
npx -y bun ${SKILL_DIR}/scripts/x-quote.ts https://x.com/user/status/123456789 "Great insight!"
# Actually post
npx -y bun ${SKILL_DIR}/scripts/x-quote.ts https://x.com/user/status/123456789 "I agree!" --submit
npx -y bun ${SKILL_DIR}/scripts/x-quote.ts https://x.com/user/status/123 "Great insight!" # Preview
npx -y bun ${SKILL_DIR}/scripts/x-quote.ts https://x.com/user/status/123 "I agree!" --submit # Post
```
**Parameters**:
| Parameter | Description |
|-----------|-------------|
| `<tweet-url>` | URL of the tweet to quote (positional argument) |
| `<comment>` | Your comment text (positional argument, optional) |
| `--submit` | Actually post (default: preview only) |
| `--profile <dir>` | Custom Chrome profile directory |
| `<tweet-url>` | URL to quote (positional) |
| `<comment>` | Comment text (positional, optional) |
| `--submit` | Post (default: preview) |
| `--profile <dir>` | Custom Chrome profile |
---
@@ -119,47 +134,29 @@ npx -y bun ${SKILL_DIR}/scripts/x-quote.ts https://x.com/user/status/123456789 "
Long-form Markdown articles (requires X Premium).
```bash
# Preview mode
npx -y bun ${SKILL_DIR}/scripts/x-article.ts article.md
# With cover image
npx -y bun ${SKILL_DIR}/scripts/x-article.ts article.md --cover ./cover.jpg
# Publish
npx -y bun ${SKILL_DIR}/scripts/x-article.ts article.md --submit
npx -y bun ${SKILL_DIR}/scripts/x-article.ts article.md # Preview
npx -y bun ${SKILL_DIR}/scripts/x-article.ts article.md --cover ./cover.jpg # With cover
npx -y bun ${SKILL_DIR}/scripts/x-article.ts article.md --submit # Publish
```
**Parameters**:
| Parameter | Description |
|-----------|-------------|
| `<markdown>` | Markdown file path (positional argument) |
| `--cover <path>` | Cover image path |
| `--title <text>` | Override article title |
| `--submit` | Actually publish (default: preview only) |
| `<markdown>` | Markdown file (positional) |
| `--cover <path>` | Cover image |
| `--title <text>` | Override title |
| `--submit` | Publish (default: preview) |
**Frontmatter** (optional):
```yaml
---
title: My Article Title
cover_image: /path/to/cover.jpg
---
```
**Frontmatter**: `title`, `cover_image` supported in YAML front matter.
---
## Notes
- First run requires manual login (session is saved)
- Always preview before using `--submit`
- Browser closes automatically after operation
- Supports macOS, Linux, and Windows
- First run: manual login required (session persists)
- Always preview before `--submit`
- Cross-platform: macOS, Linux, Windows
## Extension Support
Custom configurations via EXTEND.md.
**Check paths** (priority order):
1. `.baoyu-skills/baoyu-post-to-x/EXTEND.md` (project)
2. `~/.baoyu-skills/baoyu-post-to-x/EXTEND.md` (user)
If found, load before workflow. Extension content overrides defaults.
Custom configurations via EXTEND.md. See **Preferences** section for paths and supported options.
+45 -94
View File
@@ -21,6 +21,36 @@ Fetches any URL via Chrome CDP and converts HTML to clean markdown.
|--------|---------|
| `scripts/main.ts` | CLI entry point for URL fetching |
## Preferences (EXTEND.md)
Use Bash to check EXTEND.md existence (priority order):
```bash
# Check project-level first
test -f .baoyu-skills/baoyu-url-to-markdown/EXTEND.md && echo "project"
# Then user-level (cross-platform: $HOME works on macOS/Linux/WSL)
test -f "$HOME/.baoyu-skills/baoyu-url-to-markdown/EXTEND.md" && echo "user"
```
┌────────────────────────────────────────────────────────┬───────────────────┐
│ Path │ Location │
├────────────────────────────────────────────────────────┼───────────────────┤
│ .baoyu-skills/baoyu-url-to-markdown/EXTEND.md │ Project directory │
├────────────────────────────────────────────────────────┼───────────────────┤
│ $HOME/.baoyu-skills/baoyu-url-to-markdown/EXTEND.md │ User home │
└────────────────────────────────────────────────────────┴───────────────────┘
┌───────────┬───────────────────────────────────────────────────────────────────────────┐
│ Result │ Action │
├───────────┼───────────────────────────────────────────────────────────────────────────┤
│ Found │ Read, parse, apply settings │
├───────────┼───────────────────────────────────────────────────────────────────────────┤
│ Not found │ Use defaults │
└───────────┴───────────────────────────────────────────────────────────────────────────┘
**EXTEND.md Supports**: Default output directory | Default capture mode | Timeout settings
## Features
- Chrome CDP for full JavaScript rendering
@@ -52,103 +82,28 @@ npx -y bun ${SKILL_DIR}/scripts/main.ts <url> -o output.md
## Capture Modes
### Auto Mode (default)
| Mode | Behavior | Use When |
|------|----------|----------|
| Auto (default) | Capture on network idle | Public pages, static content |
| Wait (`--wait`) | User signals when ready | Login-required, lazy loading, paywalls |
Page loads → waits for network idle → captures immediately.
Best for:
- Public pages
- Static content
- No login required
### Wait Mode (`--wait`)
Page opens → user can interact (login, scroll, etc.) → user signals ready → captures.
Best for:
- Login-required pages
- Dynamic content needing interaction
- Pages with lazy loading
**Agent workflow for wait mode**:
1. Run script with `--wait` flag
2. Script outputs: `Page opened. Press Enter when ready to capture...`
3. Use `AskUserQuestion` to ask user if page is ready
4. When user confirms, send newline to stdin to trigger capture
**Wait mode workflow**:
1. Run with `--wait` → script outputs "Press Enter when ready"
2. Ask user to confirm page is ready
3. Send newline to stdin to trigger capture
## Output Format
```markdown
---
url: https://example.com/page
title: "Page Title"
description: "Meta description if available"
author: "Author if available"
published: "2024-01-01"
captured_at: "2024-01-15T10:30:00Z"
---
# Page Title
Converted markdown content...
```
## Mode Selection Guide
When user requests URL capture, help select appropriate mode:
**Suggest Auto Mode when**:
- URL is public (no login wall visible)
- Content appears static
- User doesn't mention login requirements
**Suggest Wait Mode when**:
- User mentions needing to log in
- Site known to require authentication
- User wants to scroll/interact before capture
- Content is behind paywall
**Ask user when unclear**:
```
The page may require login or interaction before capturing.
Which mode should I use?
1. Auto - Capture immediately when loaded
2. Wait - Wait for you to interact first
```
YAML front matter with `url`, `title`, `description`, `author`, `published`, `captured_at` fields, followed by converted markdown content.
## Output Directory
Each capture creates a file organized by domain:
```
url-to-markdown/
└── <domain>/
└── <slug>.md
url-to-markdown/<domain>/<slug>.md
```
**Path Components**:
- `<domain>`: Site domain (e.g., `example.com`, `github.com`)
- `<slug>`: Generated from page title or URL path (kebab-case)
**Slug Generation**:
1. Extract from page title (preferred) or URL path
2. Convert to kebab-case, 2-6 words
3. Example: "Getting Started with React" → `getting-started-with-react`
**Conflict Resolution**:
If `url-to-markdown/<domain>/<slug>.md` already exists:
- Append timestamp: `<slug>-YYYYMMDD-HHMMSS.md`
- Example: `getting-started.md` exists → `getting-started-20260118-143052.md`
## Error Handling
| Error | Resolution |
|-------|------------|
| Chrome not found | Install Chrome or set `URL_CHROME_PATH` env |
| Page timeout | Increase `--timeout` value |
| Capture failed | Try wait mode for complex pages |
| Empty content | Page may need JS rendering time |
- `<slug>`: From page title or URL path (kebab-case, 2-6 words)
- Conflict resolution: Append timestamp `<slug>-YYYYMMDD-HHMMSS.md`
## Environment Variables
@@ -158,12 +113,8 @@ If `url-to-markdown/<domain>/<slug>.md` already exists:
| `URL_DATA_DIR` | Custom data directory |
| `URL_CHROME_PROFILE_DIR` | Custom Chrome profile directory |
**Troubleshooting**: Chrome not found → set `URL_CHROME_PATH`. Timeout → increase `--timeout`. Complex pages → try `--wait` mode.
## Extension Support
Custom configurations via EXTEND.md.
**Check paths** (priority order):
1. `.baoyu-skills/baoyu-url-to-markdown/EXTEND.md` (project)
2. `~/.baoyu-skills/baoyu-url-to-markdown/EXTEND.md` (user)
If found, load before workflow. Extension content overrides defaults.
Custom configurations via EXTEND.md. See **Preferences** section for paths and supported options.