diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 719a7bf..f53bb34 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -6,7 +6,7 @@ }, "metadata": { "description": "Skills shared by Baoyu for improving daily work efficiency", - "version": "1.18.3" + "version": "1.19.0" }, "plugins": [ { diff --git a/CHANGELOG.md b/CHANGELOG.md index 436f379..286bd5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ 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 diff --git a/CHANGELOG.zh.md b/CHANGELOG.zh.md index d3a4e11..c5c2c0c 100644 --- a/CHANGELOG.zh.md +++ b/CHANGELOG.zh.md @@ -2,6 +2,21 @@ [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 ### 文档 diff --git a/skills/baoyu-comic/SKILL.md b/skills/baoyu-comic/SKILL.md index c7aff99..3b35bef 100644 --- a/skills/baoyu-comic/SKILL.md +++ b/skills/baoyu-comic/SKILL.md @@ -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,409 +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" -``` - -┌──────────────────────────────────────────────┬───────────────────┐ -│ 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 `references/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: `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**: - -**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 +# 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 \ + --image 01-page-xxx.png --ar 3:4 \ --ref characters/characters.png ``` -**Strategy B: Embedding character descriptions in prompt** +**Full workflow details**: [references/workflow.md](references/workflow.md) -When skill does NOT support reference images, create combined prompt files: +### EXTEND.md Paths -```markdown -# prompts/01-page-xxx.md (with embedded character reference) +| Path | Location | +|------|----------| +| `.baoyu-skills/baoyu-comic/EXTEND.md` | Project directory | +| `$HOME/.baoyu-skills/baoyu-comic/EXTEND.md` | User home | -## 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... +**EXTEND.md Supports**: Watermark | Preferred art/tone/layout | Custom style definitions | Character presets | Language preference -## Page Content -[Original page prompt here] -``` - -**For each page (cover + pages)**: -1. Save prompt to `prompts/NN-{cover|page}-[slug].md` -2. Generate image using Strategy A or B (based on skill capability) -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` - -**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 -``` - -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 diff --git a/skills/baoyu-comic/references/auto-selection.md b/skills/baoyu-comic/references/auto-selection.md new file mode 100644 index 0000000..3ad593a --- /dev/null +++ b/skills/baoyu-comic/references/auto-selection.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 diff --git a/skills/baoyu-comic/references/partial-workflows.md b/skills/baoyu-comic/references/partial-workflows.md new file mode 100644 index 0000000..ae04943 --- /dev/null +++ b/skills/baoyu-comic/references/partial-workflows.md @@ -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 diff --git a/skills/baoyu-comic/references/presets/ohmsha.md b/skills/baoyu-comic/references/presets/ohmsha.md index 8609cf9..2ff392c 100644 --- a/skills/baoyu-comic/references/presets/ohmsha.md +++ b/skills/baoyu-comic/references/presets/ohmsha.md @@ -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 diff --git a/skills/baoyu-comic/references/workflow.md b/skills/baoyu-comic/references/workflow.md new file mode 100644 index 0000000..ce2df39 --- /dev/null +++ b/skills/baoyu-comic/references/workflow.md @@ -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 +``` + +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 diff --git a/skills/baoyu-image-gen/SKILL.md b/skills/baoyu-image-gen/SKILL.md index 758f0dd..8668460 100644 --- a/skills/baoyu-image-gen/SKILL.md +++ b/skills/baoyu-image-gen/SKILL.md @@ -76,7 +76,8 @@ npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A cat" --image out.png --provi | `--model `, `-m` | Model ID | | `--ar ` | Aspect ratio (e.g., `16:9`, `1:1`, `4:3`) | | `--size ` | 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 ` | Reference images (Google multimodal only) | | `--n ` | Number of images | | `--json` | JSON output | @@ -102,17 +103,20 @@ npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A cat" --image out.png --provi ## Quality Presets -| Preset | Resolution | Use Case | -|--------|------------|----------| -| `normal` | ~1024px | Covers, illustrations | -| `2k` | ~2048px | Infographics, slides | +| Preset | Google imageSize | OpenAI Size | Use Case | +|--------|------------------|-------------|----------| +| `normal` | 1K | 1024px | Quick previews | +| `2k` (default) | 2K | 2048px | Covers, illustrations, infographics | + +**Google imageSize**: Can be overridden with `--imageSize 1K|2K|4K` ## Aspect Ratios Supported: `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `2.35:1` -- Multimodal models: embedded in prompt -- Image-only models: uses `aspectRatio` or `size` parameter +- Google multimodal: uses `imageConfig.aspectRatio` +- Google Imagen: uses `aspectRatio` parameter +- OpenAI: maps to closest supported size ## Error Handling diff --git a/skills/baoyu-image-gen/scripts/main.ts b/skills/baoyu-image-gen/scripts/main.ts index ed46b0d..88bd331 100644 --- a/skills/baoyu-image-gen/scripts/main.ts +++ b/skills/baoyu-image-gen/scripts/main.ts @@ -1,34 +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", -]; - -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: @@ -44,7 +18,8 @@ Options: -m, --model Model ID --ar Aspect ratio (e.g., 16:9, 1:1, 4:3) --size 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 Reference images (Google multimodal only) --n Number of images (default: 1) --json JSON output @@ -53,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 @@ -70,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, @@ -161,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}`); @@ -257,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"; @@ -265,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 /.baoyu-skills/.env with your keys." ); } -function getDefaultModel(provider: Provider): string { +type ProviderModule = { + getDefaultModel: () => string; + generateImage: (prompt: string, model: string, args: CliArgs) => Promise; +}; + +async function loadProviderModule(provider: Provider): Promise { 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 { - 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 { - 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 { - 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 = { - 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 { - 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 { @@ -525,7 +295,8 @@ async function main(): Promise { } 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; @@ -533,7 +304,7 @@ async function main(): Promise { while (true) { try { - imageData = await generate(provider, model, prompt, args); + imageData = await providerModule.generateImage(prompt, model, args); break; } catch (e) { if (!retried) { diff --git a/skills/baoyu-image-gen/scripts/providers/google.ts b/skills/baoyu-image-gen/scripts/providers/google.ts new file mode 100644 index 0000000..4b3ef86 --- /dev/null +++ b/skills/baoyu-image-gen/scripts/providers/google.ts @@ -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 { + 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 { + 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 { + 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); +} diff --git a/skills/baoyu-image-gen/scripts/providers/openai.ts b/skills/baoyu-image-gen/scripts/providers/openai.ts new file mode 100644 index 0000000..f42a762 --- /dev/null +++ b/skills/baoyu-image-gen/scripts/providers/openai.ts @@ -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 { + 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 = { + 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"); +} diff --git a/skills/baoyu-image-gen/scripts/types.ts b/skills/baoyu-image-gen/scripts/types.ts new file mode 100644 index 0000000..6121391 --- /dev/null +++ b/skills/baoyu-image-gen/scripts/types.ts @@ -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; +};