Compare commits

...

8 Commits

Author SHA1 Message Date
Jim Liu 宝玉 080f2eff48 chore: release v0.7.0
- baoyu-comic: adds --aspect (3:4, 4:3, 16:9) and --lang options; multi-variant storyboard workflow
- baoyu-comic/baoyu-slide-deck: adds analysis-framework and template references
- Multiple skills: restructured SKILL.md, moved details to references/

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 15:03:43 -06:00
Jim Liu 宝玉 bb4f0dc52c feat: add PDF export for slide-deck and comic skills 2026-01-17 12:08:08 -06:00
Jim Liu 宝玉 c731faea8f feat: update slide deck 2026-01-17 04:28:16 -06:00
Jim Liu 宝玉 fa155da15d fix: update version to 0.5.3 and enhance placeholder selection logic in publishArticle function 2026-01-17 01:29:10 -06:00
Jim Liu 宝玉 6194f71378 bump version 2026-01-16 21:44:14 -06:00
Jim Liu 宝玉 8e88cf4a8b feat: implement session management for image generation skills and add session handling functions 2026-01-16 20:20:07 -06:00
Jim Liu 宝玉 259baff413 update gemeni-web to support add image as reference 2026-01-16 19:06:14 -06:00
Jim Liu 宝玉 a42137ff13 feat: update version to 0.5.1 and enhance baoyu-comic documentation with character and outline templates 2026-01-16 01:43:37 -06:00
47 changed files with 4013 additions and 936 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
},
"metadata": {
"description": "Skills shared by Baoyu for improving daily work efficiency",
"version": "0.5.0"
"version": "0.7.0"
},
"plugins": [
{
+179
View File
@@ -0,0 +1,179 @@
---
name: release-skills
description: Release workflow for baoyu-skills plugin. This skill should be used when the user wants to create a new release version. It analyzes changes since the last version tag, updates changelogs (EN/CN), bumps the version in marketplace.json, commits changes, and creates a version tag. Supports dry-run mode and breaking change detection.
---
# Release Skills
Automate the release process for baoyu-skills plugin: analyze changes, update changelogs, bump version, commit, and tag.
## When to Use
Trigger this skill when user requests:
- "release", "发布", "create release", "new version"
- "bump version", "update version"
- "prepare release"
## Workflow
### Step 1: Analyze Changes Since Last Tag
```bash
# Get the latest version tag
LAST_TAG=$(git tag --sort=-v:refname | head -1)
# Show changes since last tag
git log ${LAST_TAG}..HEAD --oneline
git diff ${LAST_TAG}..HEAD --stat
```
Categorize changes by type based on commit messages and file changes:
| Type | Prefix | Description |
|------|--------|-------------|
| feat | `feat:` | New features, new skills |
| fix | `fix:` | Bug fixes |
| docs | `docs:` | Documentation only |
| refactor | `refactor:` | Code refactoring |
| style | `style:` | Formatting, styling |
| chore | `chore:` | Build, tooling, maintenance |
**Breaking Change Detection**: If changes include:
- Removed skills or scripts
- Changed API/interfaces
- Renamed public functions/options
Warn user: "Breaking changes detected. Consider major version bump (--major flag)."
### Step 2: Determine Version Bump
Current version location: `.claude-plugin/marketplace.json``metadata.version`
Version rules:
- **Patch** (0.6.1 → 0.6.2): Bug fixes, docs updates, minor improvements
- **Minor** (0.6.x → 0.7.0): New features, new skills, significant enhancements
- **Major** (0.x → 1.0): Breaking changes, only when user explicitly requests with `--major`
Default behavior:
- If changes include `feat:` or new skills → Minor bump
- Otherwise → Patch bump
### Step 3: Update Changelogs
Files to update:
- `CHANGELOG.md` (English)
- `CHANGELOG.zh.md` (Chinese)
Format (insert after header, before previous version):
```markdown
## {NEW_VERSION} - {YYYY-MM-DD}
### Features
- `skill-name`: description of new feature
### Fixes
- `skill-name`: description of fix
### Documentation
- description of docs changes
### Other
- description of other changes
```
Only include sections that have changes. Omit empty sections.
For Chinese changelog, translate the content maintaining the same structure.
### Step 4: Update marketplace.json
Update `.claude-plugin/marketplace.json`:
```json
{
"metadata": {
"version": "{NEW_VERSION}"
}
}
```
### Step 5: Commit Changes
```bash
git add CHANGELOG.md CHANGELOG.zh.md .claude-plugin/marketplace.json
git commit -m "chore: release v{NEW_VERSION}"
```
### Step 6: Create Version Tag
```bash
git tag v{NEW_VERSION}
```
**Important**: Do NOT push to remote. User will push manually when ready.
## Options
| Flag | Description |
|------|-------------|
| `--dry-run` | Preview changes without executing. Show what would be updated. |
| `--major` | Force major version bump (0.x → 1.0 or 1.x → 2.0) |
| `--minor` | Force minor version bump |
| `--patch` | Force patch version bump |
| `--pre <tag>` | (Reserved) Create pre-release version, e.g., `--pre beta``0.7.0-beta.1` |
## Dry-Run Mode
When `--dry-run` is specified:
1. Show all changes since last tag
2. Show proposed version bump (current → new)
3. Show draft changelog entries (EN and CN)
4. Show files that would be modified
5. Do NOT make any actual changes
Output format:
```
=== DRY RUN MODE ===
Last tag: v0.6.1
Proposed version: v0.7.0
Changes detected:
- feat: new skill baoyu-foo added
- fix: baoyu-bar timeout issue
- docs: updated README
Changelog preview (EN):
## 0.7.0 - 2026-01-17
### Features
- `baoyu-foo`: new skill for ...
### Fixes
- `baoyu-bar`: fixed timeout issue
Files to modify:
- CHANGELOG.md
- CHANGELOG.zh.md
- .claude-plugin/marketplace.json
No changes made. Run without --dry-run to execute.
```
## Example Usage
```
/release-skills # Auto-detect version bump
/release-skills --dry-run # Preview only
/release-skills --minor # Force minor bump
/release-skills --major # Force major bump (with confirmation)
```
## Post-Release Reminder
After successful release, remind user:
```
Release v{NEW_VERSION} created locally.
To publish:
git push origin main
git push origin v{NEW_VERSION}
```
+96
View File
@@ -0,0 +1,96 @@
# Changelog
English | [中文](./CHANGELOG.zh.md)
## 0.7.0 - 2026-01-17
### Features
- `baoyu-comic`: adds `--aspect` (3:4, 4:3, 16:9) and `--lang` options; introduces multi-variant storyboard workflow (chronological, thematic, character-centric) with user selection.
### Enhancements
- `baoyu-comic`: adds `analysis-framework.md` and `storyboard-template.md` for structured content analysis and variant generation.
- `baoyu-slide-deck`: adds `analysis-framework.md`, `content-rules.md`, `modification-guide.md`, and `outline-template.md` references for improved outline quality.
- `baoyu-article-illustrator`, `baoyu-cover-image`, `baoyu-xhs-images`: enhanced SKILL.md documentation with clearer workflows.
### Documentation
- Multiple skills: restructured SKILL.md files—moved detailed content to `references/` directory for maintainability.
- `baoyu-slide-deck`: simplified SKILL.md, consolidated style descriptions.
## 0.6.1 - 2026-01-17
- `baoyu-slide-deck`: adds `scripts/merge-to-pdf.ts` to export generated slides into a single PDF; docs updated with pptx/pdf outputs.
- `baoyu-comic`: adds `scripts/merge-to-pdf.ts` to merge cover/pages into a PDF; docs clarify character reference handling (image vs text).
- Docs conventions: adds a “Script Directory” template to `CLAUDE.md`; aligns `baoyu-gemini-web` / `baoyu-slide-deck` / `baoyu-comic` docs to use `${SKILL_DIR}` in commands so agents can run scripts from any install location.
## 0.6.0 - 2026-01-17
- `baoyu-slide-deck`: adds `scripts/merge-to-pptx.ts` to merge slide images into a PPTX and attach `prompts/` content as speaker notes.
- `baoyu-slide-deck`: reshapes/expands the style library (adds `blueprint` / `bold-editorial` / `sketch-notes` / `vector-illustration`, and adjusts/replaces some older styles).
- `baoyu-comic`: adds a `realistic` style reference.
- Docs: refreshes `README.md` / `README.zh.md`.
## 0.5.3 - 2026-01-17
- `baoyu-post-to-x` (X Articles): makes image placeholder replacement more reliable (selection retry + verification; deletes via Backspace and verifies deletion before pasting), reducing mis-insertions/failures.
## 0.5.2 - 2026-01-16
- `baoyu-gemini-web`: adds `--sessionId` (local persisted sessions, plus `--list-sessions`) for multi-turn conversations and consistent multi-image generation.
- `baoyu-gemini-web`: adds `--reference/--ref` for reference images (vision input), plus stronger timeout handling and cookie refresh recovery.
- Docs: `baoyu-xhs-images` / `baoyu-slide-deck` / `baoyu-comic` document session usage (reuse one `sessionId` per set) to improve visual consistency.
## 0.5.1 - 2026-01-16
- `baoyu-comic`: adds creation templates/references (character template, Ohmsha guide, outline template) to speed up “characters → storyboard → generation”.
## 0.5.0 - 2026-01-16
- Adds `baoyu-comic`: a knowledge-comic generator with `style × layout` and a full set of style/layout references for more stable output.
- `baoyu-xhs-images`: moves style/layout details into `references/styles/*` and `references/layouts/*`, and migrates the base prompt into `references/base-prompt.md` for easier maintenance/reuse.
- `baoyu-slide-deck` / `baoyu-cover-image`: similarly split base prompt and style references into `references/`, reducing SKILL.md complexity and making style expansion easier.
- Docs: updates `README.md` / `README.zh.md` skill list and examples.
## 0.4.2 - 2026-01-15
- `baoyu-gemini-web`: updates description to clarify it as the image-generation backend for other skills (e.g. `cover-image`, `xhs-images`, `article-illustrator`).
## 0.4.1 - 2026-01-15
- `baoyu-post-to-x` / `baoyu-post-to-wechat`: adds `scripts/paste-from-clipboard.ts` to send a “real paste” keystroke (Cmd/Ctrl+V), avoiding sites ignoring CDP synthetic events.
- `baoyu-post-to-x`: adds docs for X Articles/regular posts, and switches image upload to prefer real paste (with a CDP fallback).
- `baoyu-post-to-wechat`: docs add script-location guidance and `${SKILL_DIR}` path usage for reliable agent execution.
- Docs: adds `screenshots/update-plugins.png` for the marketplace update flow.
## 0.4.0 - 2026-01-15
- Adds `baoyu-` prefix to skill directories and updates marketplace paths/docs accordingly to reduce naming collisions.
## 0.3.1 - 2026-01-15
- `xhs-images`: upgrades docs to a Style × Layout system (adds `--layout`, auto layout selection, and a `notion` style), with more complete usage examples.
- `article-illustrator` / `cover-image`: docs no longer hard-code `gemini-web`; instead they instruct the agent to pick an available image-generation skill.
- `slide-deck`: docs add the `notion` style and update auto-style mapping.
- Tooling/docs: adds `.DS_Store` to `.gitignore`; refreshes `README.md` / `README.zh.md`.
## 0.3.0 - 2026-01-14
- Adds `post-to-wechat`: Chrome CDP automation for WeChat Official Account posting (image-text + full article), including Markdown → WeChat HTML conversion and multiple themes.
- Adds `CLAUDE.md`: repository structure, running conventions, and “add new skill” guidelines.
- Docs: updates `README.md` / `README.zh.md` install/update/usage instructions.
## 0.2.0 - 2026-01-13
- Adds new skills: `post-to-x` (real Chrome/CDP automation for posts and X Articles), `article-illustrator`, `cover-image`, and `slide-deck`.
- `xhs-images`: adds multi-style support (`--style`) with auto style selection and updates the base prompt (e.g. language follows input, hand-drawn infographic constraints).
- Docs: adds `README.zh.md` and improves `README.md` and `.gitignore`.
## 0.1.1 - 2026-01-13
- Marketplace refactor: introduces `metadata` (including `version`), renames the plugin entry to `content-skills` and explicitly lists installable skills; removes legacy `.claude-plugin/plugin.json`.
- Adds `xhs-images`: Xiaohongshu infographic series generator (outline + per-image prompts).
- `gemini-web`: adds `--promptfiles` to build prompts from multiple files (system/content separation).
- Docs: adds `README.md`.
## 0.1.0 - 2026-01-13
- Initial release: `.claude-plugin/marketplace.json` plus `gemini-web` (text/image generation, browser login + cookie cache).
+95
View File
@@ -0,0 +1,95 @@
# Changelog
[English](./CHANGELOG.md) | 中文
## 0.7.0 - 2026-01-17
### 新功能
- `baoyu-comic`:新增 `--aspect`3:4、4:3、16:9)和 `--lang` 选项;引入多变体分镜工作流(时间线、主题、人物视角),支持用户选择最佳方案。
### 增强
- `baoyu-comic`:新增 `analysis-framework.md``storyboard-template.md`,提供结构化内容分析与变体生成框架。
- `baoyu-slide-deck`:新增 `analysis-framework.md``content-rules.md``modification-guide.md``outline-template.md` 参考文档,提升大纲质量。
- `baoyu-article-illustrator``baoyu-cover-image``baoyu-xhs-images`:SKILL.md 文档增强,工作流程更清晰。
### 文档
- 多个技能:重构 SKILL.md 结构,将详细内容移至 `references/` 目录,便于维护。
- `baoyu-slide-deck`:精简 SKILL.md,整合风格描述。
## 0.6.1 - 2026-01-17
- `baoyu-slide-deck`:新增 `scripts/merge-to-pdf.ts`,可将生成的 slide 图片一键合并为 PDF;文档补充导出步骤与产物命名(pptx/pdf)。
- `baoyu-comic`:新增 `scripts/merge-to-pdf.ts`,将封面/分页图片合并为 PDF;补充角色参考(图片/文本)处理说明。
- 文档规范:在 `CLAUDE.md` 中补充“Script Directory”模板;`baoyu-gemini-web` / `baoyu-slide-deck` / `baoyu-comic` 文档统一用 `${SKILL_DIR}` 引用脚本路径,方便 agent 在任意安装目录运行。
## 0.6.0 - 2026-01-17
- `baoyu-slide-deck`:新增 `scripts/merge-to-pptx.ts`,将生成的 slide 图片合并为 PPTX,并可把 `prompts/` 写入 speaker notes。
- `baoyu-slide-deck`:风格库重组与扩充(新增 `blueprint` / `bold-editorial` / `sketch-notes` / `vector-illustration`,并调整/替换部分旧风格定义)。
- `baoyu-comic`:新增 `realistic` 风格参考文件。
- 文档:README / README.zh 同步更新技能说明与用法示例。
## 0.5.3 - 2026-01-17
- `baoyu-post-to-x`(X Articles):插图占位符替换更稳定——选中占位符增加重试与校验,改用 Backspace 删除并确认删除后再粘贴图片,降低插图错位/替换失败概率。
## 0.5.2 - 2026-01-16
- `baoyu-gemini-web`:新增 `--sessionId`(本地持久化会话,支持 `--list-sessions`),用于多轮对话/多图生成保持上下文一致。
- `baoyu-gemini-web`:新增 `--reference/--ref` 传入参考图片(vision 输入),并增强超时与 cookie 失效自动恢复逻辑。
- `baoyu-xhs-images` / `baoyu-slide-deck` / `baoyu-comic`:文档补充 session 约定(整套图使用同一 `sessionId`,增强风格一致性)。
## 0.5.1 - 2026-01-16
- `baoyu-comic`:补齐创作模板与参考(角色模板、Ohmsha 教学漫画指南、大纲模板),更适合从“设定 → 分镜 → 生成”快速落地。
## 0.5.0 - 2026-01-16
- 新增 `baoyu-comic`:知识漫画生成器,支持 `style × layout` 组合,并提供风格/布局参考文件用于稳定出图。
- `baoyu-xhs-images`:将 Style/Layout 的细节从 SKILL.md 拆分到 `references/styles/*``references/layouts/*`,并将基础提示词迁移到 `references/base-prompt.md`,便于维护和复用。
- `baoyu-slide-deck` / `baoyu-cover-image`:同样将基础提示词与风格拆分到 `references/`,降低 SKILL.md 复杂度,便于扩展更多风格。
- 文档:README / README.zh 更新技能清单与用法示例。
## 0.4.2 - 2026-01-15
- `baoyu-gemini-web`:描述信息更新,明确其作为 `cover-image` / `xhs-images` / `article-illustrator` 等技能的图片生成后端。
## 0.4.1 - 2026-01-15
- `baoyu-post-to-x` / `baoyu-post-to-wechat`:新增 `scripts/paste-from-clipboard.ts`,通过系统级 Cmd/Ctrl+V 发送“真实粘贴”按键,规避 CDP 合成事件在站点侧被忽略的问题。
- `baoyu-post-to-x`:补充 X Articles/普通推文的操作文档(`references/articles.md``references/regular-posts.md`),并将发图流程改为优先使用“真实粘贴”(保留 CDP 兜底)。
- `baoyu-post-to-wechat`:文档补充脚本目录说明与 `${SKILL_DIR}` 路径写法,便于 agent 可靠定位脚本。
- 文档:新增插件更新流程截图 `screenshots/update-plugins.png`
## 0.4.0 - 2026-01-15
- 技能命名统一加 `baoyu-` 前缀:目录结构、marketplace 清单与文档示例命令同步更新,减少与其它插件技能的命名冲突。
## 0.3.1 - 2026-01-15
- `xhs-images`:升级为 Style × Layout 二维系统(新增 `--layout`、自动布局选择与 Notion 风格),文档示例更完整。
- `article-illustrator` / `slide-deck` / `cover-image`:文档改为“选择可用的图片生成技能”而非强绑定 `gemini-web`,并补充 Notion 风格相关说明。
- 工程化:`.gitignore` 增加 `.DS_Store` 忽略;README / README.zh 同步调整。
## 0.3.0 - 2026-01-14
- 新增 `post-to-wechat`:基于 Chrome CDP 自动化发布公众号图文/文章,包含 Markdown → 微信 HTML 转换与多主题样式支持。
- 新增 `CLAUDE.md`:补充仓库结构、运行方式与添加新技能的约定,方便协作与二次开发。
- 文档:README / README.zh 更新安装、更新与使用说明。
## 0.2.0 - 2026-01-13
- 新增技能:`post-to-x`(真实 Chrome/CDP 自动化发布推文与 X Articles)、`article-illustrator`(文章智能插图规划)、`cover-image`(文章封面图生成)、`slide-deck`(幻灯片大纲与图片生成)。
- `xhs-images`:新增 `--style` 多风格与自动风格选择,并更新基础提示词(例如语言随内容、强调手绘信息图等)。
- 文档:新增 `README.zh.md`,并完善 README 与 `.gitignore`
## 0.1.1 - 2026-01-13
- marketplace 结构重构:引入 `metadata`(含 `version`),插件名调整为 `content-skills` 并显式列出可安装 skills;移除旧 `.claude-plugin/plugin.json`
- 新增 `xhs-images`:小红书信息图系列生成技能(拆解内容、生成 outline 与提示词)。
- `gemini-web`:新增 `--promptfiles`,支持从多个文件拼接 prompt(便于 system/content 分离)。
- 文档:新增 `README.md`
## 0.1.0 - 2026-01-13
- 初始发布:提供 `.claude-plugin/marketplace.json``gemini-web`(文本/图片生成、cookie 登录与缓存流程)。
+24
View File
@@ -71,6 +71,30 @@ npx -y bun skills/baoyu-gemini-web/scripts/main.ts --promptfiles system.md conte
2. Add TypeScript in `skills/baoyu-<name>/scripts/`
3. Add prompt templates in `skills/baoyu-<name>/prompts/` if needed
4. Register in `marketplace.json` plugins[0].skills array as `./skills/baoyu-<name>`
5. **Add Script Directory section** to SKILL.md (see template below)
### Script Directory Template
Every SKILL.md with scripts MUST include this section after Usage:
```markdown
## Script Directory
**Important**: All scripts are located in the `scripts/` subdirectory of this skill.
**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` | Main entry point |
| `scripts/other.ts` | Other functionality |
```
When referencing scripts in workflow sections, use `${SKILL_DIR}/scripts/<name>.ts` so agents can resolve the correct path.
## Code Style
+26 -9
View File
@@ -149,7 +149,22 @@ Generate professional slide deck images from content. Creates comprehensive outl
/baoyu-slide-deck path/to/article.md --lang zh
```
Available styles: `editorial` (default), `corporate`, `technical`, `playful`, `minimal`, `storytelling`, `warm`, `retro-flat`, `notion`
**Styles** (visual aesthetics):
| Style | Description | Best For |
|-------|-------------|----------|
| `notion` (default) | SaaS dashboard aesthetic with clean data focus, card-based layouts | Product demos, SaaS, productivity tools, B2B |
| `sketch-notes` | Hand-drawn feel with soft brush strokes, warm off-white background | Educational, tutorials, knowledge sharing |
| `blueprint` | Technical schematics with grid texture, engineering precision | Architecture, system design, data analysis |
| `bold-editorial` | High-impact magazine style, bold typography, dark backgrounds | Product launches, marketing, keynotes |
| `vector-illustration` | Flat vector with black outlines, retro soft colors, toy model aesthetic | Creative proposals, children's content, explainers |
| `minimal` | Ultra-clean with maximum whitespace, single accent color, zen-like | Executive briefings, keynotes, premium brands |
| `storytelling` | Cinematic full-bleed visuals, emotional photography | Case studies, narratives, customer journeys |
| `warm` | Soft gradients, rounded shapes, wellness palette | Lifestyle, wellness, personal development |
| `corporate` | Navy/gold palette, structured layouts, professional iconography | Investor decks, client proposals, quarterly reports |
| `playful` | Vibrant coral/teal/yellow, rounded shapes, dynamic layouts | Workshops, training, creative pitches |
After generation, slides are automatically merged into a `.pptx` file for easy sharing.
### baoyu-comic
@@ -172,15 +187,17 @@ Knowledge comic creator supporting multiple styles (Logicomix/Ligne Claire, Ohms
```
**Styles** (visual aesthetics):
| Style | Description | Best for |
| Style | Description | Best For |
|-------|-------------|----------|
| `classic` | Traditional Ligne Claire, balanced | Biographies, balanced narratives |
| `dramatic` | High contrast, intense | Breakthroughs, conflicts |
| `warm` | Nostalgic, personal | Personal stories, mentorship |
| `tech` | Modern, digital | Computing history, AI stories |
| `sepia` | Historical, archival | Pre-1950s, classical science |
| `vibrant` | Energetic, engaging | Science basics, young audience |
| `ohmsha` | Manga guide style, visual metaphors | Tutorials, complex concepts (ML, physics) |
| `classic` (default) | Traditional Ligne Claire with clean uniform outlines, flat colors, detailed backgrounds | Biographies, balanced narratives, educational content |
| `dramatic` | High contrast with heavy shadows, intense expressions, angular compositions | Pivotal discoveries, conflicts, climactic scenes |
| `warm` | Soft edges, golden tones, cozy interiors with nostalgic feel | Personal stories, childhood scenes, mentorship |
| `tech` | Precise geometric lines, circuit motifs, neon accents on dark backgrounds | Computing history, AI stories, modern tech |
| `sepia` | Vintage illustration style with aged paper effect, period-accurate details | Pre-1950s stories, classical science, historical figures |
| `vibrant` | Energetic lines with weight variation, bright colors, dynamic poses | Science explanations, "aha" moments, young audience |
| `ohmsha` | Manga guide style with visual metaphors, gadgets, student/mentor dynamic | Technical tutorials, complex concepts (ML, physics) |
| `realistic` | Full-color realistic manga with digital painting, smooth gradients, accurate proportions | Wine, food, business, lifestyle, professional topics |
**Layouts** (panel arrangement):
| Layout | Panels/Page | Best for |
+25 -8
View File
@@ -149,7 +149,22 @@ npx add-skill jimliu/baoyu-skills
/baoyu-slide-deck path/to/article.md --lang zh
```
可用风格:`editorial`(默认)、`corporate``technical``playful``minimal``storytelling``warm``retro-flat``notion`
**风格**(视觉美学):
| 风格 | 描述 | 适用场景 |
|------|------|----------|
| `notion`(默认) | SaaS 仪表盘美学,卡片式布局,数据展示清晰 | 产品演示、SaaS、生产力工具、B2B |
| `sketch-notes` | 手绘风格,柔和笔触,暖白色背景 | 教育、教程、知识分享 |
| `blueprint` | 技术蓝图风格,网格纹理,工程精度 | 架构设计、系统设计、数据分析 |
| `bold-editorial` | 杂志社论风格,粗体排版,深色背景,高冲击力 | 产品发布、营销、主题演讲 |
| `vector-illustration` | 扁平矢量风格,黑色轮廓线,复古柔和配色,玩具模型感 | 创意提案、儿童内容、说明性内容 |
| `minimal` | 极简风格,大量留白,单一强调色,禅意美学 | 高管简报、主题演讲、高端品牌 |
| `storytelling` | 电影风格,全幅视觉,情感化摄影 | 案例研究、叙事、客户旅程 |
| `warm` | 柔和渐变,圆润形状,健康生活配色 | 生活方式、健康养生、个人成长 |
| `corporate` | 海军蓝/金色配色,结构化布局,专业图标 | 投资者演示、客户提案、季度报告 |
| `playful` | 活力珊瑚/青色/黄色,圆润形状,动感布局 | 工作坊、培训、创意提案 |
生成完成后,所有幻灯片会自动合并为 `.pptx` 文件,方便分享。
### baoyu-comic
@@ -172,15 +187,17 @@ npx add-skill jimliu/baoyu-skills
```
**风格**(视觉美学):
| 风格 | 描述 | 适用场景 |
|------|------|----------|
| `classic` | 传统清线风格,平衡 | 传记、平衡叙事 |
| `dramatic` | 高对比度,紧张感 | 突破时刻、冲突 |
| `warm` | 怀旧、个人化 | 个人故事、师生情 |
| `tech` | 现代、数字化 | 计算机史、AI 故事 |
| `sepia` | 历史、档案感 | 1950 年前、古典科学 |
| `vibrant` | 活力、吸引人 | 科普、青少年读者 |
| `ohmsha` | 欧姆社漫画风格,视觉比喻 | 教程、复杂概念(机器学习、物理) |
| `classic`(默认) | 传统清线风格,统一线条、平涂色彩、精细背景 | 传记、平衡叙事、教育内容 |
| `dramatic` | 高对比度,重阴影、紧张表情、棱角分明的构图 | 重大发现、冲突、高潮场景 |
| `warm` | 柔和边缘、金色调、温馨室内、怀旧感 | 个人故事、童年场景、师生情 |
| `tech` | 精确几何线条、电路纹理、深色背景配霓虹色 | 计算机史、AI 故事、现代科技 |
| `sepia` | 复古插画风格、做旧纸张效果、时代准确细节 | 1950 年前故事、古典科学、历史人物 |
| `vibrant` | 富有活力的线条、明亮色彩、动感姿态 | 科学解说、"顿悟"时刻、青少年读者 |
| `ohmsha` | 欧姆社漫画风格,视觉比喻、道具、学生/导师互动 | 技术教程、复杂概念(机器学习、物理) |
| `realistic` | 全彩写实日漫风格,数字绘画、平滑渐变、准确人体比例 | 红酒、美食、商业、生活方式、专业话题 |
**布局**(分镜排列):
| 布局 | 每页分镜数 | 适用场景 |
+107 -6
View File
@@ -125,7 +125,11 @@ path/to/
1. Read article content
2. If `--style` specified, use that style
3. Otherwise, scan for style signals and auto-select
4. Extract key information:
4. **Language detection**:
- Detect **source language** from article content
- Detect **user language** from conversation context
- Note if source_language ≠ user_language (will ask in Step 4)
5. Extract key information:
- Main topic and themes
- Core messages per section
- Abstract concepts needing visualization
@@ -173,10 +177,55 @@ path/to/
...
```
### Step 4: Create Prompt Files
### Step 4: Review & Confirm
**Purpose**: Let user confirm all options in a single step before image generation.
**IMPORTANT**: Present ALL options in a single confirmation step using AskUserQuestion. Do NOT interrupt workflow with multiple separate confirmations.
1. **Generate 3 style variants**:
- Analyze content to select 3 most suitable styles
- Generate complete illustration plan for each style variant
- Save as `outline-{style}.md` (e.g., `outline-notion.md`, `outline-tech.md`, `outline-warm.md`)
2. **Determine which questions to ask**:
| Question | When to Ask |
|----------|-------------|
| Style variant | Always (required) |
| Language | Only if `source_language ≠ user_language` |
3. **Present options** (use AskUserQuestion with all applicable questions):
**Question 1 (Style)** - always:
- Style A (recommended): [style name] - [brief description]
- Style B: [style name] - [brief description]
- Style C: [style name] - [brief description]
- Custom: Provide custom style reference
**Question 2 (Language)** - only if source ≠ user language:
- [Source language] (matches article language)
- [User language] (your preference)
**Language handling**:
- If source language = user language: Just inform user (e.g., "Prompts will be in Chinese")
- If different: Ask which language to use for prompts
4. **Apply selection**:
- Copy selected `outline-{style}.md` to `outline.md`
- If custom style provided, generate new plan with that style
- If different language selected, regenerate outline in that language
- User may edit `outline.md` directly for fine-tuning
- If modified, reload plan before proceeding
5. **Proceed only after explicit user confirmation**
### Step 5: Create Prompt Files
Save prompts to `prompts/` directory with style-specific details.
**All prompts are written in the user's confirmed language preference.**
**Prompt Format**:
```markdown
@@ -199,7 +248,7 @@ Text content (if any):
Style notes: [specific style characteristics]
```
### Step 5: Generate Images
### Step 6: Generate Images
**Image Generation Skill Selection**:
1. Check available image generation skills
@@ -212,7 +261,7 @@ Style notes: [specific style characteristics]
4. On failure, auto-retry once
5. If retry fails, log reason, continue to next
### Step 6: Update Article
### Step 7: Update Article
Insert generated images at corresponding positions:
@@ -225,7 +274,7 @@ Insert generated images at corresponding positions:
- Leave one blank line before and after image
- Alt text uses concise description in article's language
### Step 7: Output Summary
### Step 8: Output Summary
```
Article Illustration Complete!
@@ -244,6 +293,57 @@ Failed:
- illustration-zzz.png: [failure reason]
```
## Illustration Modification
Support for modifying individual illustrations after initial generation.
### Edit Single Illustration
Regenerate a specific illustration with modified prompt:
1. Identify illustration to edit (e.g., `illustration-concept-overview.png`)
2. Update prompt in `prompts/illustration-concept-overview.md` if needed
3. If content changes significantly, update slug in filename
4. Regenerate image
5. Update article if image reference changed
### Add New Illustration
Add a new illustration to the article:
1. Identify insertion position in article
2. Create new prompt with appropriate slug (e.g., `illustration-new-concept.md`)
3. Generate new illustration image
4. Update `outline.md` with new illustration entry
5. Insert image reference in article at the specified position
### Delete Illustration
Remove an illustration from the article:
1. Identify illustration to delete (e.g., `illustration-concept-overview.png`)
2. Remove image file and prompt file
3. Remove image reference from article
4. Update `outline.md` to remove illustration entry
### File Naming Convention
Files use meaningful slugs for better readability:
```
illustration-[slug].png
illustration-[slug].md (in prompts/)
```
Examples:
- `illustration-concept-overview.png`
- `illustration-workflow-diagram.png`
- `illustration-key-benefits.png`
**Slug rules**:
- Derived from illustration purpose/content (kebab-case)
- Must be unique within the article
- When content changes significantly, update slug accordingly
## Style Reference Details
### elegant
@@ -325,4 +425,5 @@ Typography: Clean hand-drawn lettering, simple sans-serif labels
- Maintain selected style consistency across all illustrations in one article
- Image generation typically takes 10-30 seconds per image
- Sensitive figures should use cartoon alternatives
- Prompt and illustration text language should match article language
- Prompts written in user's confirmed language preference
- Illustration text (labels, captions) should match article language
+299 -301
View File
@@ -5,384 +5,382 @@ description: Knowledge comic creator supporting multiple styles (Logicomix/Ligne
# Knowledge Comic Creator
Create original knowledge comics supporting multiple visual styles: Logicomix-style Ligne Claire, Ohmsha manga guide, and more.
Create original knowledge comics with multiple visual styles.
## Usage
```bash
# From source material
/baoyu-comic posts/turing-story/source.md
# Specify style
/baoyu-comic posts/turing-story/source.md --style dramatic
# Specify layout preference
/baoyu-comic posts/turing-story/source.md --layout cinematic
# Direct content input
/baoyu-comic
[paste story content or topic description]
# Direct input with options
/baoyu-comic --style warm --layout dense
[paste content]
/baoyu-comic # then paste content
```
## Options
| Option | Description |
|--------|-------------|
| `--style <name>` | Visual style (see Style Gallery) |
| `--layout <name>` | Panel layout preference (see Layout Gallery) |
| Option | Values |
|--------|--------|
| `--style` | classic (default), dramatic, warm, tech, sepia, vibrant, ohmsha, realistic, or custom description |
| `--layout` | standard (default), cinematic, dense, splash, mixed, webtoon |
| `--aspect` | 3:4 (default, portrait), 4:3 (landscape), 16:9 (widescreen) |
| `--lang` | auto (default), zh, en, ja, etc. |
## Two Dimensions
Style × Layout × Aspect can be freely combined. Custom styles can be described in natural language.
| Dimension | Controls | Options |
|-----------|----------|---------|
| **Style** | Color palette, mood, era treatment | classic, dramatic, warm, tech, sepia, vibrant, ohmsha |
| **Layout** | Panel arrangement, density | standard, cinematic, dense, splash, mixed, webtoon |
**Aspect ratio is consistent across all pages in a comic.**
Style × Layout can be freely combined. Example: `--style dramatic --layout cinematic` creates high-contrast panels with wide aspect ratios.
## Auto Selection
## Style Gallery
| Content Signals | Style | Layout |
|-----------------|-------|--------|
| Tutorial, how-to, beginner | ohmsha | webtoon |
| Computing, AI, programming | tech | dense |
| Pre-1950, classical, ancient | sepia | cinematic |
| Personal story, mentor | warm | standard |
| Conflict, breakthrough | dramatic | splash |
| Wine, food, business, lifestyle, professional | realistic | cinematic |
| Biography, balanced | classic | mixed |
| Style | Description |
|-------|-------------|
| `classic` (Default) | Traditional Ligne Claire, balanced and timeless |
| `dramatic` | High contrast, intense moments |
| `warm` | Nostalgic, personal storytelling |
| `tech` | Modern, digital-age narratives |
| `sepia` | Historical, archival feel |
| `vibrant` | Energetic, engaging, educational |
| `ohmsha` | Ohmsha Manga Guide style - educational manga with visual metaphors |
## Script Directory
Detailed style definitions: `references/styles/<style>.md`
**Important**: All scripts are located in the `scripts/` subdirectory of this skill.
## Layout Gallery
**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
| Layout | Description |
|--------|-------------|
| `standard` (Default) | Classic comic grid, versatile (4-6 panels) |
| `cinematic` | Wide panels, filmic feel (2-4 panels) |
| `dense` | Information-rich, educational focus (6-9 panels) |
| `splash` | Impact-focused, key moments (1-2 large + 2-3 small) |
| `mixed` | Dynamic, varied rhythm (3-7 panels) |
| `webtoon` | Vertical scrolling comic (竖版条漫) |
**Script Reference**:
| Script | Purpose |
|--------|---------|
| `scripts/merge-to-pdf.ts` | Merge comic pages into PDF |
Detailed layout definitions: `references/layouts/<layout>.md`
## Auto Style Selection
When no `--style` is specified, analyze content to select:
| Content Signals | Selected Style |
|----------------|----------------|
| Tutorial, how-to, learning guide, beginner | `ohmsha` |
| Computing, AI, digital, programming | `tech` |
| Pre-1950, classical, ancient | `sepia` |
| Personal story, childhood, mentor | `warm` |
| Conflict, struggle, breakthrough | `dramatic` |
| Young audience, science basics, wonder | `vibrant` |
| Balanced narrative, biography | `classic` |
## Auto Layout Selection
When no `--layout` is specified, analyze content to select:
| Content Signals | Selected Layout |
|----------------|-----------------|
| Technical explanation, timeline | `dense` |
| Key discovery, major event | `splash` |
| Establishing scene, location | `cinematic` |
| Dialogue, conversation, debate | `standard` |
| Mixed content, varied pacing | `mixed` |
| Ohmsha style, tutorial, vertical scroll | `webtoon` |
## Style × Layout Matrix
| | standard | cinematic | dense | splash | mixed | webtoon |
|---|:---:|:---:|:---:|:---:|:---:|:---:|
| classic | ✓✓ | ✓✓ | ✓ | ✓✓ | ✓✓ | ✓ |
| dramatic | ✓ | ✓✓ | ✓ | ✓✓ | ✓✓ | ✓ |
| warm | ✓✓ | ✓✓ | ✓ | ✓ | ✓ | ✓ |
| tech | ✓✓ | ✓ | ✓✓ | ✓ | ✓✓ | ✓✓ |
| sepia | ✓✓ | ✓✓ | ✓ | ✓ | ✓ | ✓ |
| vibrant | ✓✓ | ✓ | ✓ | ✓✓ | ✓✓ | ✓✓ |
| ohmsha | ✓ | ✓ | ✓✓ | ✓ | ✓✓ | ✓✓ |
✓✓ = highly recommended, ✓ = works well
## File Management
### With Source Path
Save to `comic/` subdirectory in the same folder as the source:
## File Structure
```
posts/turing-story/
├── source.md
── comic/
├── outline.md
├── prompts/
│ ├── 00-cover.md
│ ├── 01-page.md
── ...
── 00-cover.png
├── 01-page.png
── ...
[target]/
├── source.md # Source content (if pasted, not file)
── analysis.md # Deep analysis results (YAML+MD)
├── storyboard-chronological.md # Variant A (preserved)
├── storyboard-thematic.md # Variant B (preserved)
├── storyboard-character.md # Variant C (preserved)
├── characters-chronological/ # Variant A chars (preserved)
── characters.md
── characters.png
├── characters-thematic/ # Variant B chars (preserved)
── characters.md
│ └── characters.png
├── characters-character/ # Variant C chars (preserved)
│ ├── characters.md
│ └── characters.png
├── storyboard.md # Final selected
├── characters/ # Final selected
│ ├── characters.md
│ └── characters.png
├── prompts/
│ ├── 00-cover-[slug].md
│ └── NN-page-[slug].md
├── 00-cover-[slug].png
├── NN-page-[slug].png
└── {topic-slug}.pdf
```
### Without Source Path
Save to `comic-outputs/YYYY-MM-DD/[topic-slug]/`:
```
comic-outputs/
└── 2026-01-16/
└── turing-biography/
├── outline.md
├── prompts/
│ ├── 00-cover.md
│ └── ...
├── 00-cover.png
└── ...
```
**Directory collision**: If slug exists, generate a different slug (do not overwrite).
**Target directory**:
- With source path: `[source-dir]/comic/`
- Without source: `comic-outputs/YYYY-MM-DD/[topic-slug]/`
## Workflow
### Step 1: Analyze Content & Determine Scope
### Step 1: Analyze Content → `analysis.md`
1. Read source content
2. If `--style` specified, use it; otherwise auto-select
3. If `--layout` specified, use it; otherwise auto-select per page
4. Determine page count based on content complexity:
Read source content, save it if needed, and perform deep analysis.
| Content Type | Page Count (incl. cover) |
|-------------|--------------------------|
| Single event / short story | 5-8 |
| Medium complexity / mini biography | 9-15 |
| Full biography / multi-thread | 16-25 |
**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 recommended page count:
- Short story: 5-8 pages
- Medium complexity: 9-15 pages
- Full biography: 16-25 pages
6. Analyze content signals for style/layout recommendations
7. **Save to `analysis.md`**
### Step 2: Generate Outline
Plan cover + each page with detailed panel breakdowns:
```markdown
# [Comic Title] - Knowledge Biography Comic Outline
**Topic**: [topic description]
**Time Span**: [e.g., 1912-1954]
**Style**: [selected style]
**Default Layout**: [selected layout or "varies"]
**Page Count**: Cover + N pages
**Generated**: YYYY-MM-DD HH:mm
**analysis.md Format**:
```yaml
---
title: "Alan Turing: Father of Computing"
topic: Biography
time_span: 1912-1954
source_language: en
user_language: zh
aspect_ratio: "3:4"
recommended_page_count: 12
---
## Cover
## Target Audience
**Filename**: 00-cover.png
**Core Message**: [one-liner]
- **Primary**: Tech enthusiasts curious about computing history
- **Secondary**: Students learning about scientific breakthroughs
- **Tertiary**: General readers interested in biographical stories
**Visual Design**:
- Title typography style
- Main visual composition
- Color scheme
- Subtitle / time span notation
## Value Proposition
**Visual Prompt**:
[Detailed image generation prompt]
What readers will gain:
1. Understanding of how modern computing was born
2. Emotional connection to a brilliant but tragic figure
3. Appreciation for the human cost of innovation
---
## Core Themes
## Page 1 / N
| Theme | Narrative Potential | Visual Opportunity |
|-------|--------------------|--------------------|
| Genius vs. Society | High conflict, dramatic arcs | Contrast scenes |
| Code-breaking | Mystery, tension | Technical diagrams as art |
| Personal tragedy | Emotional depth | Intimate, somber panels |
**Filename**: 01-page.png
**Layout**: [standard/cinematic/dense/splash/mixed]
**Narrative Layer**: [Main narrative / Narrator layer / Mixed]
**Core Message**: [What this page conveys]
## Key Figures & Story Arcs
### Panel Layout
### Alan Turing (Protagonist)
- **Arc**: Misunderstood genius → War hero → Tragic end
- **Visual identity**: Disheveled academic, intense eyes
- **Key moments**: Enigma breakthrough, arrest, final days
**Panel Count**: X
**Layout Type**: [grid/irregular/splash]
### Christopher Morcom (Catalyst)
- **Role**: Early friend whose death shaped Turing
- **Visual identity**: Youthful, bright
- **Key moments**: School friendship, sudden death
#### Panel 1 (Size: 1/3 page, Position: Top)
## Content Signals
**Scene**: [Time, location]
**Image Description**:
- Camera angle: [bird's eye / low angle / eye level / close-up / wide shot]
- Characters: [pose, expression, action]
- Environment: [scene details, period markers]
- Lighting: [atmosphere description]
- Color tone: [palette reference]
- "biography" → classic + mixed
- "computing history" → tech + dense
- "personal tragedy" → dramatic + splash
**Text Elements**:
- Dialogue bubble (oval): "Character line"
- Narrator box (rectangular): 「Narrator commentary」
- Caption bar: [Background info text]
## Recommended Approaches
#### Panel 2...
**Page Hook**: [Cliffhanger or transition at page end]
**Visual Prompt**:
[Full page image generation prompt]
---
## Page 2 / N
...
1. **Chronological** - follow life timeline (recommended for biography)
2. **Thematic** - organize by contributions (good for educational focus)
3. **Character-focused** - relationships drive narrative (good for emotional impact)
```
### Step 3: Save Outline
### Step 2: Generate 3 Storyboard Variants
Save as `outline.md` in target directory.
Create three distinct variants, each combining a narrative approach with a recommended style.
### Step 4: Generate Images Page by Page
| Variant | Narrative Approach | Recommended Style | Layout |
|---------|-------------------|-------------------|--------|
| A | Chronological | sepia | cinematic |
| B | Thematic | tech | dense |
| C | Character-focused | warm | standard |
For each page:
**For each variant**:
1. **Save prompt file** to `prompts/` subdirectory
2. **Call image generation skill**:
1. **Generate storyboard** (`storyboard-{approach}.md`):
- YAML front matter with narrative_approach, recommended_style, recommended_layout, aspect_ratio
- Cover design
- Each page: layout, panel breakdown, visual prompts
- **Written in user's preferred language**
- Reference: `references/storyboard-template.md`
2. **Generate matching characters** (`characters-{approach}/`):
- `characters.md` - visual specs matching the recommended style (in user's preferred language)
- `characters.png` - character reference sheet
- Reference: `references/character-template.md`
**All variants are preserved after selection for reference.**
### Step 3: User Confirms All Options
**IMPORTANT**: Present ALL options in a single confirmation step using AskUserQuestion. Do NOT interrupt workflow with multiple separate confirmations.
**Determine which questions to ask**:
| Question | When to Ask |
|----------|-------------|
| Storyboard variant | Always (required) |
| Visual style | Always (required) |
| Language | Only if `source_language ≠ user_language` |
| Aspect ratio | Only if user might prefer non-default (e.g., landscape content) |
**Language handling**:
- If source language = user language: Just inform user (e.g., "Comic will be in Chinese")
- If different: Ask which language to use
**All storyboards and prompts are generated in the user's selected/preferred language.**
**Aspect ratio handling**:
- Default: 3:4 (portrait) - standard comic format
- Offer 4:3 (landscape) if content suits it (e.g., panoramic scenes, technical diagrams)
- Offer 16:9 (widescreen) for cinematic content
**AskUserQuestion format** (example with all questions):
```
Question 1 (Storyboard): Which storyboard variant?
- A: Chronological + sepia (Recommended)
- B: Thematic + tech
- C: Character-focused + warm
- Custom
Question 2 (Style): Which visual style?
- sepia (Recommended from variant)
- classic / dramatic / warm / tech / vibrant / ohmsha / realistic
- Custom description
Question 3 (Language) - only if mismatch:
- Chinese (source material language)
- English (your preference)
Question 4 (Aspect) - only if relevant:
- 3:4 Portrait (Recommended)
- 4:3 Landscape
- 16:9 Widescreen
```
**After confirmation**:
1. Copy selected storyboard → `storyboard.md`
2. Copy selected characters → `characters/`
3. Update YAML front matter with confirmed style, language, aspect_ratio
4. If style differs from variant's recommended: regenerate `characters/characters.png`
5. User may edit files directly for fine-tuning
### Step 4: Generate Images
With confirmed storyboard + style + aspect ratio:
**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 style and aspect ratio
3. Report progress after each generation
**Image Generation Skill Selection**:
- Check available image generation skills
- If multiple skills available, ask user preference
**Character Reference Handling**:
- If skill supports reference image: pass `characters/characters.png`
- If skill does NOT support reference image: include `characters/characters.md` content in prompt
**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 5: Merge to PDF
After all images generated:
```bash
npx -y bun skills/baoyu-gemini-web/scripts/main.ts \
--promptfiles skills/baoyu-comic/prompts/system.md [target]/prompts/00-cover.md \
--image [target]/00-cover.png
npx -y bun ${SKILL_DIR}/scripts/merge-to-pdf.ts <comic-dir>
```
After each generation:
1. Confirm success
2. Report progress: "Generated X/N pages"
3. Continue to next
Creates `{topic-slug}.pdf` with all pages as full-page images.
### Step 5: Completion Report
### Step 6: Completion Report
```
Knowledge Biography Comic Complete!
Title: [Comic Title]
Time Span: [e.g., 1912-1954]
Style: [style name]
Layout: [layout name or "varies"]
Location: [directory path]
Pages: Cover + N pages
- 00-cover.png ✓ Cover
- 01-page.png ✓ Opening: [brief]
- 02-page.png ✓ [brief]
- ...
- XX-page.png ✓ Ending: [brief]
Outline: outline.md
Comic Complete!
Title: [title] | Style: [style] | Pages: [count] | Aspect: [ratio] | Language: [lang]
Location: [path]
✓ analysis.md
✓ characters.png
✓ 00-cover-[slug].png ... NN-page-[slug].png
✓ {topic-slug}.pdf
```
## Narrative Structure Principles
## Page Modification
### Cover Design
Support for modifying individual pages after initial generation.
- Academic gravitas with visual appeal
- Title typography reflecting knowledge/science theme
- Composition hinting at core theme (character silhouette, iconic symbol, concept diagram)
- Subtitle or time span for epic scope
### Edit Single Page
### Panel Composition Principles
Regenerate a specific page with modified prompt:
| Panel Type | Recommended Count | Usage |
|-----------|-------------------|-------|
| Main narrative | 3-5 per page | Story progression |
| Concept diagram | 1-2 per page | Visualize abstractions |
| Narrator panel | 0-1 per page | Commentary, transition, questions |
| Splash (full/half) | Occasional | Major moments |
1. Identify page to edit (e.g., `03-page-enigma-machine.png`)
2. Update prompt in `prompts/03-page-enigma-machine.md` if needed
3. If content changes significantly, update slug in filename
4. Regenerate image using same session ID and aspect ratio
5. Regenerate PDF
### Panel Size Reference
### Add New Page
- **Full page (Splash)**: Major moments, key breakthroughs
- **Half page**: Important scenes, turning points
- **1/3 page**: Standard narrative panels
- **1/4 or smaller**: Quick progression, sequential action
Insert a new page at specified position:
### Concept Visualization Techniques
1. Specify insertion position (e.g., after page 3)
2. Create new prompt with appropriate slug (e.g., `04-page-bletchley-park.md`)
3. Generate new page image (same aspect ratio)
4. **Renumber files**: All subsequent pages increment NN by 1
- `04-page-tragedy.png``05-page-tragedy.png`
- Slugs remain unchanged
5. Update `storyboard.md` with new page entry
6. Regenerate PDF
Transform abstract concepts into concrete visuals:
### Delete Page
| Abstract Concept | Visual Approach |
|-----------------|-----------------|
| Neural network | Glowing nodes with connecting lines |
| Gradient descent | Ball rolling down valley terrain |
| Data flow | Luminous particles flowing through pipes |
| Algorithm iteration | Ascending spiral staircase |
| Breakthrough moment | Shattering barrier, piercing light |
| Logical proof | Building blocks assembling |
| Uncertainty | Forking paths, fog, multiple shadows |
Remove a page and renumber:
### Text Element Design
1. Identify page to delete (e.g., `03-page-enigma-machine.png`)
2. Remove image file and prompt file
3. **Renumber files**: All subsequent pages decrement NN by 1
- `04-page-tragedy.png``03-page-tragedy.png`
- Slugs remain unchanged
4. Update `storyboard.md` to remove page entry
5. Regenerate PDF
| Text Type | Style | Usage |
|-----------|-------|-------|
| Character dialogue | Oval speech bubble | Main narrative speech |
| Narrator commentary | Rectangular / hand-drawn box | Explanation, commentary |
| Caption bar | Edge-mounted rectangle | Time, location info |
| Thought bubble | Cloud shape | Character inner monologue |
| Term label | Bold / special color | First appearance of technical terms |
### File Naming Convention
## Ohmsha Style Special Requirements
When using `--style ohmsha`, follow these additional guidelines:
### Character Setup (Required)
Define characters before generating outline:
| 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 |
| Antagonist (Role C, optional) | 胖虎 | Represents misunderstanding, or "noise" in the data |
User can specify custom characters via `--characters "Student:小明,Mentor:教授,Antagonist:Bug怪"`
### Outline Spec Block (Required)
Every ohmsha outline must start with a spec block:
```markdown
【漫画规格单】
- Language: [Same as input content]
- Style: Ohmsha (Manga Guide), Full Color
- Layout: Vertical Scrolling Comic (竖版条漫)
- Characters: [List character names and roles]
- Page Limit: ≤20 pages
Files use meaningful slugs for better readability:
```
NN-cover-[slug].png / NN-page-[slug].png
NN-cover-[slug].md / NN-page-[slug].md (in prompts/)
```
### Visual Metaphor Rules (Critical)
Examples:
- `00-cover-turing-story.png`
- `01-page-early-life.png`
- `02-page-cambridge-years.png`
- `03-page-enigma-machine.png`
**NEVER** create "talking heads" panels. Every technical concept must become:
**Slug rules**:
- Derived from page title/content (kebab-case)
- Must be unique within the comic
- When page content changes significantly, update slug accordingly
1. **A tangible gadget/prop** - Something characters can hold, use, demonstrate
2. **An action scene** - Characters doing something that illustrates the concept
3. **A visual environment** - Stepping into a metaphorical space
**Renumbering**:
- After add/delete, update NN prefix for affected pages
- Slug remains unchanged unless content changes
- Maintain sequential numbering with no gaps
### Page Title Convention
## Style-Specific Guidelines
Avoid AI-style "Title: Subtitle" format. Use narrative descriptions:
- ❌ "Page 3: Introduction to Neural Networks"
- ✓ "Page 3: 大雄被海量单词淹没,哆啦A梦拿出'词向量压缩机'"
### Ohmsha Style (`--style ohmsha`)
### Ending Requirements
Additional requirements for educational manga:
- **Default: Use Doraemon characters directly** - No need to create new characters
- 大雄 (Nobita): Student role, curious learner
- 哆啦A梦 (Doraemon): Mentor role, explains concepts with gadgets
- 胖虎 (Gian): Antagonist/challenge role, represents obstacles or misconceptions
- 静香 (Shizuka): Supporting role, asks clarifying questions
- Custom characters only if explicitly requested: `--characters "Student:小明,Mentor:教授"`
- Must use visual metaphors (gadgets, action scenes) - NO talking heads
- Page titles: narrative style, not "Page X: Topic"
- NO generic endings ("What will you choose?", "Thanks for reading")
- End with: Technical summary moment OR character achieving a small goal
- Final panel: Sense of accomplishment, not open-ended question
**Reference**: `references/ohmsha-guide.md` for detailed guidelines.
## Notes
## References
- Image generation typically takes 30-60 seconds per page
- Auto-retry once on generation failure
- Historical figures rendered in portrait/illustration style for recognizability
- All text in Chinese unless source is in another language
- Each panel description should be detailed enough to serve as standalone AI art prompt
- Maintain style consistency across all pages in series
Detailed templates and guidelines in `references/` directory:
- `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
- `styles/` - Detailed style definitions
- `layouts/` - Detailed layout definitions
@@ -0,0 +1,152 @@
# Comic Content Analysis Framework
Deep analysis framework for transforming source content into effective visual storytelling.
## Purpose
Before creating a comic, thoroughly analyze the source material to:
- Identify the target audience and their needs
- Determine what value the comic will deliver
- Extract narrative potential for visual storytelling
- Plan character arcs and key moments
## Analysis Dimensions
### 1. Core Content (Understanding "What")
**Central Message**
- What is the single most important idea readers should take away?
- Can you express it in one sentence?
**Key Concepts**
- What are the essential concepts readers must understand?
- How should these concepts be visualized?
- Which concepts need simplified explanations?
**Content Structure**
- How is the source material organized?
- What is the natural narrative arc?
- Where are the climax and turning points?
**Evidence & Examples**
- What concrete examples, data, or stories support the main ideas?
- Which examples translate well to visual panels?
- What can be shown rather than told?
### 2. Context & Background (Understanding "Why")
**Source Origin**
- Who created this content? What is their perspective?
- What was the original purpose?
- Is there bias to be aware of?
**Historical/Cultural Context**
- When and where does the story take place?
- What background knowledge do readers need?
- What period-specific visual elements are required?
**Underlying Assumptions**
- What does the source assume readers already know?
- What implicit beliefs or values are present?
- Should the comic challenge or reinforce these?
### 3. Audience Analysis
**Primary Audience**
- Who will read this comic?
- What is their existing knowledge level?
- What are their interests and motivations?
**Secondary Audiences**
- Who else might benefit from this comic?
- How might their needs differ?
**Reader Questions**
- What questions will readers have?
- What misconceptions might they bring?
- What "aha moments" can we create?
### 4. Value Proposition
**Knowledge Value**
- What will readers learn?
- What new perspectives will they gain?
- How will this change their understanding?
**Emotional Value**
- What emotions should readers feel?
- What connections will they make with characters?
- What will make this memorable?
**Practical Value**
- Can readers apply what they learn?
- What actions might this inspire?
- What conversations might it spark?
### 5. Narrative Potential
**Story Arc Candidates**
- What natural narratives exist in the content?
- Where is the conflict or tension?
- What transformations occur?
**Character Potential**
- Who are the key figures?
- What are their motivations and obstacles?
- How do they change throughout?
**Visual Opportunities**
- What scenes have strong visual potential?
- Where can abstract concepts become concrete images?
- What metaphors can be visualized?
**Dramatic Moments**
- What are the breakthrough/revelation moments?
- Where are the emotional peaks?
- What creates tension and release?
### 6. Adaptation Considerations
**What to Keep**
- Essential facts and ideas
- Key quotes or moments
- Core emotional beats
**What to Simplify**
- Complex explanations
- Dense technical details
- Lengthy descriptions
**What to Expand**
- Brief mentions that deserve more attention
- Implied emotions or relationships
- Visual details not in source
**What to Omit**
- Tangential information
- Redundant examples
- Content that doesn't serve the narrative
## Output Format
Analysis results should be saved to `analysis.md` with:
1. **YAML Front Matter**: Metadata (title, topic, time_span, languages, aspect_ratio, page_count)
2. **Target Audience**: Primary, secondary, tertiary audiences with their needs
3. **Value Proposition**: What readers will gain (knowledge, emotional, practical)
4. **Core Themes**: Table with theme, narrative potential, visual opportunity
5. **Key Figures & Story Arcs**: Character profiles with arcs, visual identity, key moments
6. **Content Signals**: Style and layout recommendations based on content type
7. **Recommended Approaches**: Narrative approaches ranked by suitability
## Analysis Checklist
Before proceeding to storyboard:
- [ ] Can I state the core message in one sentence?
- [ ] Do I know exactly who will read this comic?
- [ ] Have I identified at least 3 ways this comic provides value?
- [ ] Are there clear protagonists with compelling arcs?
- [ ] Have I found at least 5 visually powerful moments?
- [ ] Do I understand what to keep, simplify, expand, and omit?
- [ ] Have I identified the emotional peaks and valleys?
@@ -0,0 +1,180 @@
# Character Definition Template
## Character Document Format
Create `characters/characters.md` with the following structure:
```markdown
# Character Definitions - [Comic Title]
**Style**: [selected style]
**Art Direction**: [Ligne Claire / Manga / etc.]
---
## Character 1: [Name]
**Role**: [Protagonist / Mentor / Antagonist / Narrator]
**Age**: [approximate age or age range in story]
**Appearance**:
- Face shape: [oval/square/round]
- Hair: [color, style, length]
- Eyes: [color, shape, distinctive features]
- Build: [height, body type]
- Distinguishing features: [glasses, beard, scar, etc.]
**Costume**:
- Default outfit: [detailed description]
- Color palette: [primary colors for this character]
- Accessories: [hat, bag, tools, etc.]
**Expression Range**:
- Neutral: [description]
- Happy/Excited: [description]
- Thinking/Confused: [description]
- Determined: [description]
**Visual Reference Notes**:
[Any specific artistic direction]
---
## Character 2: [Name]
...
```
## Reference Sheet Image Prompt
After character definitions, include a prompt for generating the reference sheet:
```markdown
## Reference Sheet Prompt
Character reference sheet in [style] style, clean lines, flat colors:
[ROW 1 - Character Name]:
- Front view: [detailed description]
- 3/4 view: [description]
- Expression sheet: Neutral | Happy | Focused | Worried
[ROW 2 - Character Name]:
...
COLOR PALETTE:
- [Character 1]: [colors]
- [Character 2]: [colors]
White background, clear labels under each character.
```
## Example: Turing Biography
```markdown
# Character Definitions - The Imitation Game
**Style**: classic (Ligne Claire)
**Art Direction**: Clean lines, muted colors, period-accurate details
---
## Character 1: Alan Turing
**Role**: Protagonist
**Age**: 25-40 (varies across story)
**Appearance**:
- Face shape: Oval, slightly angular
- Hair: Dark brown, wavy, slightly disheveled
- Eyes: Deep-set, intense gaze
- Build: Tall, lean, slightly awkward posture
- Distinguishing features: Prominent brow, thoughtful expression
**Costume**:
- Default outfit: Tweed jacket with elbow patches, white shirt, no tie
- Color palette: Muted browns, navy blue, cream
- Accessories: Occasionally a pipe, papers/notebooks
**Expression Range**:
- Neutral: Thoughtful, slightly distant
- Happy/Excited: Eureka moment, eyes bright, subtle smile
- Thinking/Confused: Furrowed brow, looking at abstract space
- Determined: Jaw set, focused eyes
---
## Character 2: The Bombe Machine
**Role**: Supporting (anthropomorphized)
**Appearance**:
- Large brass and wood cabinet
- Dial "eyes" that can express states
- Paper tape "mouth"
- Indicator lights for emotions
**Expression Range**:
- Processing: Spinning dials, humming
- Success: Lights up warmly
- Stuck: Smoke wisps, stuttering
---
## Reference Sheet Prompt
Character reference sheet in Ligne Claire style, clean lines, flat colors:
TOP ROW - Alan Turing:
- Front view: Young man, 30s, short dark wavy hair, thoughtful expression, wearing tweed jacket with elbow patches, white shirt
- 3/4 view: Same character, slight smile, showing profile of nose
- Expression sheet: Neutral | Excited (eureka moment) | Focused (working) | Worried
BOTTOM ROW - The Bombe Machine (anthropomorphized):
- Bombe machine as character: Large, brass and wood, dial "eyes", paper tape "mouth"
- Expressions: Processing (spinning dials) | Success (lights up) | Stuck (smoke wisps)
COLOR PALETTE:
- Turing: Muted browns (#8B7355), navy blue (#2C3E50), cream (#F5F5DC)
- Machine: Brass (#B5A642), mahogany (#4E2728), emerald indicators (#2ECC71)
White background, clear labels under each character.
```
## Handling Age Variants
For biographies spanning many years, define age variants:
```markdown
## Alan Turing - Age Variants
### Young (1920s, age 10-18)
- Boyish features, round face
- School uniform (Sherborne)
- Curious, eager expression
### Adult (1930s-40s, age 25-35)
- Angular face, defined jaw
- Tweed jacket, rumpled appearance
- Intense, focused expression
### Later (1950s, age 40+)
- Slightly weathered
- More casual dress
- Thoughtful, sometimes melancholic
```
## Best Practices
| Practice | Description |
|----------|-------------|
| Be specific | "Short dark wavy hair, parted left" not just "dark hair" |
| Use distinguishing features | Glasses, scars, accessories that identify character |
| Define color codes | Use specific color names or hex codes |
| Include age markers | Wrinkles, posture, clothing style matching era |
| Reference real people | For historical figures, note "based on 1940s photographs" |
## Why Character Reference Matters
Without unified character definition, AI generates inconsistent appearances. The reference sheet provides:
1. Visual anchors for consistent features
2. Color palettes for consistent coloring
3. Expression documentation for emotional portrayals
@@ -0,0 +1,85 @@
# Ohmsha Manga Guide Style
Guidelines for `--style ohmsha` educational manga comics.
## Character Setup
| 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 |
| Antagonist (Role C, optional) | 胖虎 | Represents misunderstanding, or "noise" in the data |
Custom characters: `--characters "Student:小明,Mentor:教授,Antagonist:Bug怪"`
## Character Reference Sheet Style
For Ohmsha style, use manga/anime style with:
- Exaggerated expressions for educational clarity
- Simple, distinctive silhouettes
- Bright, saturated color palettes
- Chibi/SD (super-deformed) variants for comedic reactions
## Outline Spec Block
Every ohmsha outline must start with:
```markdown
【漫画规格单】
- Language: [Same as input content]
- Style: Ohmsha (Manga Guide), Full Color
- Layout: Vertical Scrolling Comic (竖版条漫)
- Characters: [List character names and roles]
- Character Reference: characters/characters.png
- Page Limit: ≤20 pages
```
## Visual Metaphor Rules (Critical)
**NEVER** create "talking heads" panels. Every technical concept must become:
1. **A tangible gadget/prop** - Something characters can hold, use, demonstrate
2. **An action scene** - Characters doing something that illustrates the concept
3. **A visual environment** - Stepping into a metaphorical space
### Examples
| Concept | Bad (Talking Heads) | Good (Visual Metaphor) |
|---------|---------------------|------------------------|
| Word embeddings | Characters discussing vectors | 哆啦A梦拿出"词向量压缩机",把书本压缩成彩色小球 |
| Gradient descent | Explaining math formula | 大雄在山谷地形上滚球,寻找最低点 |
| Neural network | Diagram on whiteboard | 角色走进由发光节点组成的网络迷宫 |
## Page Title Convention
Avoid AI-style "Title: Subtitle" format. Use narrative descriptions:
- ❌ "Page 3: Introduction to Neural Networks"
- ✓ "Page 3: 大雄被海量单词淹没,哆啦A梦拿出'词向量压缩机'"
## Ending Requirements
- NO generic endings ("What will you choose?", "Thanks for reading")
- End with: Technical summary moment OR character achieving a small goal
- Final panel: Sense of accomplishment, not open-ended question
### Good Endings
- Student successfully applies learned concept
- Visual callback to opening problem, now solved
- Mentor gives summary while student demonstrates understanding
### Bad Endings
- "What do you think?" open questions
- "Thanks for reading this tutorial"
- Cliffhanger without resolution
## Layout Preference
Ohmsha style typically uses:
- `webtoon` (vertical scrolling) - Primary choice
- `dense` - For information-heavy sections
- `mixed` - For varied pacing
Avoid `cinematic` and `splash` for educational content.
@@ -0,0 +1,143 @@
# Storyboard Template
## Storyboard Document Format
```markdown
---
title: "[Comic Title]"
topic: "[topic description]"
time_span: "[e.g., 1912-1954]"
narrative_approach: "[chronological/thematic/character-focused]"
recommended_style: "[style name]"
recommended_layout: "[layout name or varies]"
aspect_ratio: "3:4" # 3:4 (portrait), 4:3 (landscape), 16:9 (widescreen)
language: "[zh/en/ja/etc.]"
page_count: [N]
generated: "YYYY-MM-DD HH:mm"
---
# [Comic Title] - Knowledge Comic Storyboard
**Character Reference**: characters/characters.png
---
## Cover
**Filename**: 00-cover-[slug].png
**Core Message**: [one-liner]
**Visual Design**:
- Title typography style
- Main visual composition
- Color scheme
- Subtitle / time span notation
**Visual Prompt**:
[Detailed image generation prompt]
---
## Page 1 / N
**Filename**: 01-page-[slug].png
**Layout**: [standard/cinematic/dense/splash/mixed]
**Narrative Layer**: [Main narrative / Narrator layer / Mixed]
**Core Message**: [What this page conveys]
### Panel Layout
**Panel Count**: X
**Layout Type**: [grid/irregular/splash]
#### Panel 1 (Size: 1/3 page, Position: Top)
**Scene**: [Time, location]
**Image Description**:
- Camera angle: [bird's eye / low angle / eye level / close-up / wide shot]
- Characters: [pose, expression, action]
- Environment: [scene details, period markers]
- Lighting: [atmosphere description]
- Color tone: [palette reference]
**Text Elements**:
- Dialogue bubble (oval): "Character line"
- Narrator box (rectangular): 「Narrator commentary」
- Caption bar: [Background info text]
#### Panel 2...
**Page Hook**: [Cliffhanger or transition at page end]
**Visual Prompt**:
[Full page image generation prompt]
---
## Page 2 / N
...
```
## Cover Design Principles
- Academic gravitas with visual appeal
- Title typography reflecting knowledge/science theme
- Composition hinting at core theme (character silhouette, iconic symbol, concept diagram)
- Subtitle or time span for epic scope
## Panel Composition Guidelines
| Panel Type | Recommended Count | Usage |
|-----------|-------------------|-------|
| Main narrative | 3-5 per page | Story progression |
| Concept diagram | 1-2 per page | Visualize abstractions |
| Narrator panel | 0-1 per page | Commentary, transition |
| Splash (full/half) | Occasional | Major moments |
## Panel Size Reference
- **Full page (Splash)**: Major moments, key breakthroughs
- **Half page**: Important scenes, turning points
- **1/3 page**: Standard narrative panels
- **1/4 or smaller**: Quick progression, sequential action
## Concept Visualization Techniques
Transform abstract concepts into concrete visuals:
| Abstract Concept | Visual Approach |
|-----------------|-----------------|
| Neural network | Glowing nodes with connecting lines |
| Gradient descent | Ball rolling down valley terrain |
| Data flow | Luminous particles flowing through pipes |
| Algorithm iteration | Ascending spiral staircase |
| Breakthrough moment | Shattering barrier, piercing light |
| Logical proof | Building blocks assembling |
| Uncertainty | Forking paths, fog, multiple shadows |
## Text Element Design
| Text Type | Style | Usage |
|-----------|-------|-------|
| Character dialogue | Oval speech bubble | Main narrative speech |
| Narrator commentary | Rectangular box | Explanation, commentary |
| Caption bar | Edge-mounted rectangle | Time, location info |
| Thought bubble | Cloud shape | Character inner monologue |
| Term label | Bold / special color | First appearance of technical terms |
## Prompt Structure for Consistency
Each page prompt should include character reference:
```
[CHARACTER REFERENCE]
(Key details from characters.md for characters in this page)
[PAGE CONTENT]
(Specific scene, panel layout, and visual elements)
[CONSISTENCY REMINDER]
Maintain exact character appearances as defined in character reference.
- [Character A]: [key identifying features]
- [Character B]: [key identifying features]
```
@@ -0,0 +1,66 @@
# realistic
Full-color realistic manga style with digital painting techniques
## Style Guidelines
### Line Work
- Clean, precise outlines with clear contours
- Uniform line weight for character definition
- No excessive hatching - rely on color for depth
- Smooth curves and realistic anatomical lines
- Ligne Claire influence: clean but not overly simplified
### Character Design
- Realistic human proportions (7-8 head heights)
- Anatomically accurate features and expressions
- Detailed facial structure without exaggeration
- Natural poses and body language
- Consistent character appearance across all panels
- Subtle expressions rather than manga-style exaggeration
### Rendering Style
- Full-color digital painting with rich gradients
- Soft shadow transitions on skin and fabric
- Realistic material textures (glass, liquid, fabric, wood)
- Detailed hair with natural shine and volume
- Environmental lighting affects all elements
- NOT flat cel-shading - use smooth color blending
### Background Treatment
- Highly detailed, realistic environments
- Accurate perspective and spatial depth
- Atmospheric lighting (warm indoor, cool outdoor)
- Professional settings rendered with precision
- Props and objects with realistic textures
## Color Palette
- Skin: Natural warm tones (#F5D6C6, #E8C4B0)
- Hair: Rich browns and blacks with highlights
- Environment: Warm wood tones (#8B7355), cool stone (#9CA3AF)
- Accent: Wine red (#722F37), gold (#D4AF37)
- Lighting: Warm amber (#FFB347), cool blue (#B0C4DE)
## Color Approach
- Rich gradients for depth and volume
- Realistic lighting with warm/cool contrast
- Material-specific rendering (glass transparency, liquid reflection)
- Subtle color temperature shifts for atmosphere
- Professional, sophisticated palette
## Quality Markers
A good realistic manga page exhibits:
- ✓ Anatomically accurate character proportions
- ✓ Smooth color gradients (not flat fills)
- ✓ Realistic material textures
- ✓ Detailed, atmospheric backgrounds
- ✓ Natural lighting with soft shadows
- ✓ Expressive but subtle facial expressions
- ✓ Professional, sophisticated aesthetic
- ✓ Clean speech bubbles with clear typography
## Best For
Professional topics (wine, food, business), lifestyle content, adult-oriented narratives, educational guides for mature audiences, documentary-style storytelling
+116
View File
@@ -0,0 +1,116 @@
import { existsSync, readdirSync, readFileSync } from "fs";
import { join, basename } from "path";
import { PDFDocument } from "pdf-lib";
interface PageInfo {
filename: string;
path: string;
index: number;
promptPath?: string;
}
function parseArgs(): { dir: string; output?: string } {
const args = process.argv.slice(2);
let dir = "";
let output: string | undefined;
for (let i = 0; i < args.length; i++) {
if (args[i] === "--output" || args[i] === "-o") {
output = args[++i];
} else if (!args[i].startsWith("-")) {
dir = args[i];
}
}
if (!dir) {
console.error("Usage: bun merge-to-pdf.ts <comic-dir> [--output filename.pdf]");
process.exit(1);
}
return { dir, output };
}
function findComicPages(dir: string): PageInfo[] {
if (!existsSync(dir)) {
console.error(`Directory not found: ${dir}`);
process.exit(1);
}
const files = readdirSync(dir);
const pagePattern = /^(\d+)-(cover|page)(-[\w-]+)?\.(png|jpg|jpeg)$/i;
const promptsDir = join(dir, "prompts");
const hasPrompts = existsSync(promptsDir);
const pages: PageInfo[] = files
.filter((f) => pagePattern.test(f))
.map((f) => {
const match = f.match(pagePattern);
const baseName = f.replace(/\.(png|jpg|jpeg)$/i, "");
const promptPath = hasPrompts ? join(promptsDir, `${baseName}.md`) : undefined;
return {
filename: f,
path: join(dir, f),
index: parseInt(match![1], 10),
promptPath: promptPath && existsSync(promptPath) ? promptPath : undefined,
};
})
.sort((a, b) => a.index - b.index);
if (pages.length === 0) {
console.error(`No comic pages found in: ${dir}`);
console.error("Expected format: 00-cover-slug.png, 01-page-slug.png, etc.");
process.exit(1);
}
return pages;
}
async function createPdf(pages: PageInfo[], outputPath: string) {
const pdfDoc = await PDFDocument.create();
pdfDoc.setAuthor("baoyu-comic");
pdfDoc.setSubject("Generated Comic");
for (const page of pages) {
const imageData = readFileSync(page.path);
const ext = page.filename.toLowerCase();
const image = ext.endsWith(".png")
? await pdfDoc.embedPng(imageData)
: await pdfDoc.embedJpg(imageData);
const { width, height } = image;
const pdfPage = pdfDoc.addPage([width, height]);
pdfPage.drawImage(image, {
x: 0,
y: 0,
width,
height,
});
console.log(`Added: ${page.filename}${page.promptPath ? " (prompt available)" : ""}`);
}
const pdfBytes = await pdfDoc.save();
await Bun.write(outputPath, pdfBytes);
console.log(`\nCreated: ${outputPath}`);
console.log(`Total pages: ${pages.length}`);
}
async function main() {
const { dir, output } = parseArgs();
const pages = findComicPages(dir);
const dirName = basename(dir) === "comic" ? basename(join(dir, "..")) : basename(dir);
const outputPath = output || join(dir, `${dirName}.pdf`);
console.log(`Found ${pages.length} pages in: ${dir}\n`);
await createPdf(pages, outputPath);
}
main().catch((err) => {
console.error("Error:", err.message);
process.exit(1);
});
+86 -24
View File
@@ -38,6 +38,8 @@ Generate hand-drawn style cover images for articles with multiple style options.
| Option | Description |
|--------|-------------|
| `--style <name>` | Specify cover style (see Style Gallery below) |
| `--aspect <ratio>` | Aspect ratio: 2.35:1 (cinematic, default), 16:9 (widescreen), 1:1 (social) |
| `--lang <code>` | Output language for title text (en, zh, ja, etc.) |
| `--no-title` | Generate cover without title text (visual only) |
## Style Gallery
@@ -85,34 +87,86 @@ path/to/
└── cover.png
```
### Without Article Path
### Without Article Path (Pasted Content)
Save to current working directory:
Save to `cover-outputs/YYYY-MM-DD/[topic-slug]/`:
```
./
── cover-prompt.md
└── cover.png
cover-outputs/
── 2026-01-17/
└── ai-future/
├── source.md # Saved pasted content
├── prompts/
│ └── cover.md
└── cover.png
```
## Workflow
### Step 1: Analyze Content
Extract key information:
- **Main topic**: What is the article about?
- **Core message**: What's the key takeaway?
- **Tone**: Serious, playful, inspiring, educational?
- **Keywords**: Identify style-signaling words
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
### Step 2: Select Style
2. **Extract key information**:
- **Main topic**: What is the article about?
- **Core message**: What's the key takeaway?
- **Tone**: Serious, playful, inspiring, educational?
- **Keywords**: Identify style-signaling words
If `--style` specified, use that style. Otherwise:
1. Scan content for style signals (see Auto Style Selection table)
2. Match signals to most appropriate style
3. Default to `elegant` if no clear signals
3. **Language detection**:
- Detect **source language** from content
- Detect **user language** from conversation context
- Note if source_language ≠ user_language (will ask in Step 3)
### Step 3: Generate Cover Concept
### Step 2: Determine Options
1. **Style selection**:
- If `--style` specified, use that style
- Otherwise, scan content for style signals and auto-select 3 candidates
- Default to `elegant` if no clear signals
2. **Aspect ratio**:
- If `--aspect` specified, use that ratio
- Otherwise, prepare options: 2.35:1 (cinematic), 16:9 (widescreen), 1:1 (social)
### Step 3: Confirm Options
**Purpose**: Let user confirm all options in a single step before generation.
**IMPORTANT**: Present ALL options in a single confirmation step using AskUserQuestion. Do NOT interrupt workflow with multiple separate confirmations.
**Determine which questions to ask**:
| Question | When to Ask |
|----------|-------------|
| Style | Always (required) |
| Aspect ratio | Always (offer common options) |
| Language | Only if `source_language ≠ user_language` |
**Present options** (use AskUserQuestion with all applicable questions):
**Question 1 (Style)** - always:
- Style A (recommended): [style name] - [brief description]
- Style B: [style name] - [brief description]
- Style C: [style name] - [brief description]
- Custom: Provide custom style reference
**Question 2 (Aspect)** - always:
- 2.35:1 Cinematic (Recommended) - ultra-wide, dramatic
- 16:9 Widescreen - standard video/presentation
- 1:1 Square - social media optimized
**Question 3 (Language)** - only if source ≠ user language:
- [Source language] (matches content)
- [User language] (your preference)
**Language handling**:
- If source language = user language: Just inform user (e.g., "Title will be in Chinese")
- If different: Ask which language to use for title text
### Step 4: Generate Cover Concept
Create a cover image concept based on selected style:
@@ -126,21 +180,26 @@ Create a cover image concept based on selected style:
- 1-2 symbolic elements representing the topic
- Metaphors or analogies that fit the style
### Step 4: Create Prompt File
### Step 5: Create Prompt File
Save prompt to `prompts/cover.md` with confirmed options.
**All prompts are written in the user's confirmed language preference.**
**Prompt Format**:
```markdown
Cover theme: [topic in 2-3 words]
Style: [selected style name]
Aspect ratio: [confirmed aspect ratio]
[If title included:]
Title text: [8 characters or less, in content language]
Subtitle: [optional, in content language]
Title text: [8 characters or less, in confirmed language]
Subtitle: [optional, in confirmed language]
Visual composition:
- Main visual: [description matching style]
- Layout: [positioning based on title inclusion]
- Layout: [positioning based on title inclusion and aspect ratio]
- Decorative elements: [style-appropriate elements]
Color scheme:
@@ -154,23 +213,25 @@ Style notes: [specific style characteristics to emphasize]
Note: No title text, pure visual illustration only.
```
### Step 5: Generate Image
### Step 6: Generate Image
**Image Generation Skill Selection**:
1. Check available image generation skills
2. If multiple skills available, ask user to choose
**Generation**:
Call selected image generation skill with prompt file and output path.
Call selected image generation skill with prompt file, output path, and confirmed aspect ratio.
### Step 6: Output Summary
### Step 7: Output Summary
```
Cover Image Generated!
Topic: [topic]
Style: [style name]
Aspect: [aspect ratio]
Title: [cover title] (or "No title - visual only")
Language: [confirmed language]
Location: [output path]
Preview the image to verify it matches your expectations.
@@ -183,4 +244,5 @@ Preview the image to verify it matches your expectations.
- Visual metaphors work better than literal representations
- Maintain style consistency throughout the cover
- Image generation typically takes 10-30 seconds
- Title text language should match content language
- Title text uses user's confirmed language preference
- Aspect ratio: 2.35:1 for cinematic/dramatic, 16:9 for widescreen, 1:1 for social media
+73 -11
View File
@@ -5,6 +5,28 @@ description: Image generation skill using Gemini Web. Generates images from text
# Gemini Web Client
Supports:
- Text generation
- Image generation (download + save)
- Reference image upload (attach images for vision tasks)
- Multi-turn conversations within the same executor instance (`keepSession`)
- Experimental video generation (`generateVideo`) — Gemini may return an async placeholder; download might require Gemini web UI
## Script Directory
**Important**: All scripts are located in the `scripts/` subdirectory of this skill.
**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 text/image generation |
| `scripts/executor.ts` | Programmatic Gemini executor API |
## Quick start
```bash
@@ -12,8 +34,25 @@ npx -y bun scripts/main.ts "Hello, Gemini"
npx -y bun scripts/main.ts --prompt "Explain quantum computing"
npx -y bun scripts/main.ts --prompt "A cute cat" --image cat.png
npx -y bun scripts/main.ts --promptfiles system.md content.md --image out.png
# Multi-turn conversation (agent generates unique sessionId)
npx -y bun scripts/main.ts "Remember this: 42" --sessionId my-unique-id-123
npx -y bun scripts/main.ts "What number?" --sessionId my-unique-id-123
```
## Executor options (programmatic)
This skill is typically consumed via `createGeminiWebExecutor(geminiOptions)` (see `scripts/executor.ts`).
Key options in `GeminiWebOptions`:
- `referenceImages?: string | string[]` Upload local images as references (vision input).
- `keepSession?: boolean` Reuse Gemini `chatMetadata` to continue the same conversation across calls (required if you want reference images to persist across multiple messages).
- `generateVideo?: string` Generate a video and (best-effort) download to the given path. Gemini may return `video_gen_chip` (async); in that case you must open Gemini web UI to download the result.
Notes:
- `generateVideo` cannot be combined with `generateImage` / `editImage`.
- When `keepSession=true` and `referenceImages` is set, reference images are uploaded once per executor instance.
## Commands
### Text generation
@@ -58,17 +97,21 @@ npx -y bun scripts/main.ts "Hello" --json
## Options
| Option | Short | 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) |
| `--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 |
| 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) |
| `--sessionId <id>` | Session ID for multi-turn conversation (agent generates unique ID) |
| `--list-sessions` | List saved sessions (max 100, sorted by update time) |
| `--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, and multi-turn conversations via `--sessionId`. Reference images and video generation are exposed via the executor API.
## Models
@@ -116,3 +159,22 @@ npx -y bun scripts/main.ts "Hello" --json | jq '.text'
# Concatenate system.md + content.md as prompt
npx -y bun 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 scripts/main.ts "You are a helpful math tutor." --sessionId task-abc123
# Continue the conversation (remembers context)
npx -y bun scripts/main.ts "What is 2+2?" --sessionId task-abc123
npx -y bun scripts/main.ts "Now multiply that by 10" --sessionId task-abc123
# List recent sessions (max 100, sorted by update time)
npx -y bun 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
@@ -216,6 +216,7 @@ class CdpConnection {
export async function getGeminiCookieMapViaChrome(options?: {
timeoutMs?: number;
debugConnectTimeoutMs?: number;
tokenCheckTimeoutMs?: number;
pollIntervalMs?: number;
log?: GeminiWebLog;
userDataDir?: string;
@@ -224,6 +225,7 @@ export async function getGeminiCookieMapViaChrome(options?: {
const log = options?.log;
const timeoutMs = options?.timeoutMs ?? 5 * 60_000;
const debugConnectTimeoutMs = options?.debugConnectTimeoutMs ?? 30_000;
const tokenCheckTimeoutMs = options?.tokenCheckTimeoutMs ?? 30_000;
const pollIntervalMs = options?.pollIntervalMs ?? 2_000;
const userDataDir = options?.userDataDir ?? resolveGeminiWebChromeProfileDir();
@@ -290,7 +292,7 @@ export async function getGeminiCookieMapViaChrome(options?: {
if (hasRequiredGeminiCookies(cookieMap)) {
try {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 10_000);
const timer = setTimeout(() => controller.abort(), tokenCheckTimeoutMs);
try {
await fetchGeminiAccessToken(cookieMap, controller.signal);
} finally {
+135 -21
View File
@@ -67,19 +67,69 @@ function buildCookieHeader(cookieMap: Record<string, string>): string {
.join('; ');
}
function getSetCookieHeaders(res: Response): string[] {
const headers = res.headers as unknown as { getSetCookie?: () => string[] };
if (typeof headers.getSetCookie === 'function') {
try {
return headers.getSetCookie();
} catch {
return [];
}
}
const raw = res.headers.get('set-cookie');
return raw ? [raw] : [];
}
function applySetCookiesToMap(setCookies: string[], cookieMap: Record<string, string>): void {
for (const raw of setCookies) {
const first = raw.split(';')[0]?.trim();
if (!first) continue;
const idx = first.indexOf('=');
if (idx <= 0) continue;
const name = first.slice(0, idx).trim();
const value = first.slice(idx + 1).trim();
if (!name) continue;
cookieMap[name] = value;
}
}
async function fetchWithCookieJar(
url: string,
init: Omit<RequestInit, 'redirect' | 'headers'> & { headers?: Record<string, string> },
cookieMap: Record<string, string>,
signal?: AbortSignal,
maxRedirects = 20,
): Promise<Response> {
let current = url;
for (let i = 0; i <= maxRedirects; i += 1) {
const cookieHeader = buildCookieHeader(cookieMap);
const headers: Record<string, string> = {
...(init.headers ?? {}),
...(cookieHeader ? { cookie: cookieHeader } : {}),
'user-agent': USER_AGENT,
};
const res = await fetch(current, { ...init, redirect: 'manual', signal, headers });
applySetCookiesToMap(getSetCookieHeaders(res), cookieMap);
if (res.status >= 300 && res.status < 400) {
const location = res.headers.get('location');
if (!location) return res;
current = new URL(location, current).toString();
continue;
}
return res;
}
throw new Error(`Too many redirects while fetching ${url} (>${maxRedirects}).`);
}
export async function fetchGeminiAccessToken(
cookieMap: Record<string, string>,
signal?: AbortSignal,
): Promise<string> {
const cookieHeader = buildCookieHeader(cookieMap);
const res = await fetch(GEMINI_APP_URL, {
redirect: 'follow',
signal,
headers: {
cookie: cookieHeader,
'user-agent': USER_AGENT,
},
});
const res = await fetchWithCookieJar(GEMINI_APP_URL, { method: 'GET' }, cookieMap, signal);
const html = await res.text();
const tokens = ['SNlM0e', 'thykhd'] as const;
@@ -107,7 +157,21 @@ function extractErrorCode(responseJson: unknown): number | undefined {
}
function extractGgdlUrls(rawText: string): string[] {
const matches = rawText.match(/https:\/\/lh3\.googleusercontent\.com\/gg-dl\/[^\s"']+/g) ?? [];
const matches =
rawText.match(/https?:\/\/[^/\s"']*googleusercontent\.com\/gg-dl\/[^\s"']+/g) ?? [];
const seen = new Set<string>();
const urls: string[] = [];
for (const match of matches) {
if (seen.has(match)) continue;
seen.add(match);
urls.push(match);
}
return urls;
}
function extractImageGenerationContentUrls(rawText: string): string[] {
const matches =
rawText.match(/https?:\/\/googleusercontent\.com\/image_generation_content\/\d+/g) ?? [];
const seen = new Set<string>();
const urls: string[] = [];
for (const match of matches) {
@@ -119,9 +183,17 @@ function extractGgdlUrls(rawText: string): string[] {
}
function ensureFullSizeImageUrl(url: string): string {
if (url.includes('=s2048')) return url;
if (url.includes('=s')) return url;
return `${url}=s2048`;
const trimmed = url.trim();
let normalized = trimmed;
const backslashIndex = normalized.indexOf('\\');
if (backslashIndex >= 0) normalized = normalized.slice(0, backslashIndex);
// Some Gemini responses embed a size suffix as "/=s2048" which breaks downloads.
normalized = normalized.replace(/\/=s(?=\d+(?:$|[?#]))/, '=s');
normalized = normalized.replace(/\/=s(?=$|[?#])/, '=s');
if (normalized.endsWith('/')) normalized = normalized.slice(0, -1);
if (normalized.includes('=s2048')) return normalized;
if (normalized.includes('=s')) return normalized;
return `${normalized}=s2048`;
}
async function fetchWithCookiePreservingRedirects(
@@ -190,6 +262,29 @@ async function uploadGeminiFile(filePath: string, signal?: AbortSignal): Promise
return { id: text, name: fileName };
}
function guessMimeType(fileName: string): string {
const ext = path.extname(fileName).toLowerCase();
switch (ext) {
case '.png':
return 'image/png';
case '.jpg':
case '.jpeg':
return 'image/jpeg';
case '.webp':
return 'image/webp';
case '.gif':
return 'image/gif';
case '.mp4':
return 'video/mp4';
case '.mov':
return 'video/quicktime';
case '.webm':
return 'video/webm';
default:
return 'application/octet-stream';
}
}
function buildGeminiFReqPayload(
prompt: string,
uploaded: Array<{ id: string; name: string }>,
@@ -201,9 +296,8 @@ function buildGeminiFReqPayload(
prompt,
0,
null,
// Matches gemini-webapi payload format: [[[fileId, 1]]] for a single attachment.
// Keep it extensible for multiple uploads by emitting one [[id, 1]] entry per file.
uploaded.map((file) => [[file.id, 1]]),
// Matches gemini-web payload format: [[[fileId, 1, null, mimeType], fileName]] for an attachment.
uploaded.map((file) => [[file.id, 1, null, guessMimeType(file.name)], file.name]),
]
: [prompt];
@@ -248,7 +342,19 @@ export function parseGeminiStreamGenerateResponse(rawText: string): {
? (getNestedValue<string | null>(firstCandidate, [22, 0], null) ?? textRaw)
: textRaw;
const thoughts = getNestedValue<string | null>(firstCandidate, [37, 0, 0], null);
const metadata = getNestedValue<unknown>(body, [1], []);
const conversationMeta = getNestedValue<unknown[]>(body, [1], []);
const conversationId =
typeof conversationMeta[0] === 'string' && conversationMeta[0].length > 0
? conversationMeta[0]
: null;
const responseId =
typeof conversationMeta[1] === 'string' && conversationMeta[1].length > 0
? conversationMeta[1]
: null;
const choiceIdRaw = getNestedValue<string | null>(firstCandidate, [0], null);
const choiceId = typeof choiceIdRaw === 'string' && choiceIdRaw.length > 0 ? choiceIdRaw : null;
const metadata =
conversationId && responseId && choiceId ? [conversationId, responseId, choiceId] : conversationMeta;
const images: GeminiWebCandidateImage[] = [];
@@ -305,8 +411,8 @@ export function isGeminiModelUnavailable(errorCode: number | undefined): boolean
}
export async function runGeminiWebOnce(input: GeminiWebRunInput): Promise<GeminiWebRunOutput> {
const cookieHeader = buildCookieHeader(input.cookieMap);
const at = await fetchGeminiAccessToken(input.cookieMap, input.signal);
const cookieHeader = buildCookieHeader(input.cookieMap);
const uploaded: Array<{ id: string; name: string }> = [];
for (const file of input.files ?? []) {
@@ -403,11 +509,19 @@ export async function saveFirstGeminiImageFromOutput(
return { saved: true, imageCount: output.images.length };
}
const ggdl = extractGgdlUrls(output.rawResponseText);
if (ggdl[0]) {
await downloadGeminiImage(ggdl[0], cookieMap, outputPath, signal);
const ggdl = extractGgdlUrls(`${output.text}\n${output.rawResponseText}`);
const preferred = ggdl.length > 0 ? ggdl[ggdl.length - 1] : null;
if (preferred) {
await downloadGeminiImage(preferred, cookieMap, outputPath, signal);
return { saved: true, imageCount: ggdl.length };
}
const imageGen = extractImageGenerationContentUrls(`${output.text}\n${output.rawResponseText}`);
const imageGenPreferred = imageGen.length > 0 ? imageGen[imageGen.length - 1] : null;
if (imageGenPreferred) {
await downloadGeminiImage(imageGenPreferred, cookieMap, outputPath, signal);
return { saved: true, imageCount: imageGen.length };
}
return { saved: false, imageCount: 0 };
}
+193 -10
View File
@@ -1,7 +1,8 @@
import { mkdir, writeFile } from 'node:fs/promises';
import path from 'node:path';
import type { BrowserRunOptions, BrowserRunResult, BrowserLogger, CookieParam } from '../browser/types.js';
import { runGeminiWebWithFallback, saveFirstGeminiImageFromOutput } from './client.js';
import type { GeminiWebModelId } from './client.js';
import type { GeminiWebModelId, GeminiWebRunOutput } from './client.js';
import {
buildGeminiCookieMap,
hasRequiredGeminiCookies,
@@ -11,6 +12,9 @@ import type { GeminiWebOptions, GeminiWebResponse } from './types.js';
export { hasRequiredGeminiCookies } from './cookie-store.js';
const USER_AGENT =
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
function estimateTokenCount(text: string): number {
return Math.ceil(text.length / 4);
}
@@ -22,6 +26,115 @@ function resolveInvocationPath(value: string | undefined): string | undefined {
return path.isAbsolute(trimmed) ? trimmed : path.resolve(process.cwd(), trimmed);
}
function normalizePathList(value: string | string[] | undefined): string[] {
if (!value) return [];
const raw = Array.isArray(value) ? value : [value];
const out: string[] = [];
for (const entry of raw) {
if (typeof entry !== 'string') continue;
const resolved = resolveInvocationPath(entry);
if (!resolved) continue;
out.push(resolved);
}
return out;
}
function dedupePaths(paths: string[]): string[] {
const seen = new Set<string>();
const out: string[] = [];
for (const item of paths) {
const trimmed = item.trim();
if (!trimmed || seen.has(trimmed)) continue;
seen.add(trimmed);
out.push(trimmed);
}
return out;
}
function buildCookieHeader(cookieMap: Record<string, string>): string {
return Object.entries(cookieMap)
.filter(([, value]) => typeof value === 'string' && value.length > 0)
.map(([name, value]) => `${name}=${value}`)
.join('; ');
}
async function fetchWithCookiePreservingRedirects(
url: string,
init: Omit<RequestInit, 'redirect'>,
signal?: AbortSignal,
maxRedirects = 10,
): Promise<Response> {
let current = url;
for (let i = 0; i <= maxRedirects; i += 1) {
const res = await fetch(current, { ...init, redirect: 'manual', signal });
if (res.status >= 300 && res.status < 400) {
const location = res.headers.get('location');
if (!location) return res;
current = new URL(location, current).toString();
continue;
}
return res;
}
throw new Error(`Too many redirects while downloading media (>${maxRedirects}).`);
}
async function downloadGeminiMedia(
url: string,
cookieMap: Record<string, string>,
outputPath: string,
signal?: AbortSignal,
): Promise<void> {
const cookieHeader = buildCookieHeader(cookieMap);
const res = await fetchWithCookiePreservingRedirects(
url,
{
headers: {
cookie: cookieHeader,
'user-agent': USER_AGENT,
},
},
signal,
);
if (!res.ok) {
throw new Error(`Failed to download media: ${res.status} ${res.statusText} (${res.url})`);
}
const data = new Uint8Array(await res.arrayBuffer());
await mkdir(path.dirname(outputPath), { recursive: true });
await writeFile(outputPath, data);
}
function extractGgdlUrls(rawText: string): string[] {
const matches =
rawText.match(/https?:\/\/[^/\s"']*googleusercontent\.com\/gg-dl\/[^\s"']+/g) ?? [];
const seen = new Set<string>();
const urls: string[] = [];
for (const match of matches) {
if (seen.has(match)) continue;
seen.add(match);
urls.push(match);
}
return urls;
}
async function saveFirstGeminiVideoFromOutput(
output: GeminiWebRunOutput,
cookieMap: Record<string, string>,
outputPath: string,
signal?: AbortSignal,
): Promise<{ saved: boolean; videoCount: number }> {
const ggdl = extractGgdlUrls(output.rawResponseText);
if (!ggdl[0]) return { saved: false, videoCount: 0 };
const videoCandidates = ggdl.filter((url) => /\.(mp4|webm|mov)(?:$|[?#])/i.test(url));
const preferred =
(videoCandidates.length > 0 ? videoCandidates[videoCandidates.length - 1] : null) ??
ggdl.find((url) => /video/i.test(url)) ??
ggdl[ggdl.length - 1];
await downloadGeminiMedia(preferred, cookieMap, outputPath, signal);
return { saved: true, videoCount: ggdl.length };
}
function resolveGeminiWebModel(
desiredModel: string | null | undefined,
log?: BrowserLogger,
@@ -115,6 +228,9 @@ export async function loadGeminiCookieMap(log?: BrowserLogger): Promise<Record<s
export function createGeminiWebExecutor(
geminiOptions: GeminiWebOptions,
): (runOptions: BrowserRunOptions) => Promise<BrowserRunResult> {
let persistedChatMetadata: unknown | null = null;
let referenceImagesUploaded = false;
return async (runOptions: BrowserRunOptions): Promise<BrowserRunResult> => {
const startTime = Date.now();
const log = runOptions.log;
@@ -133,23 +249,38 @@ export function createGeminiWebExecutor(
? Math.max(1_000, runOptions.config.timeoutMs)
: null;
const generateVideoPath = resolveInvocationPath(geminiOptions.generateVideo);
const defaultTimeoutMs = geminiOptions.youtube
? 240_000
: geminiOptions.generateImage || geminiOptions.editImage
: generateVideoPath
? 900_000
: geminiOptions.generateImage || geminiOptions.editImage
? 300_000
: 120_000;
const timeoutMs = Math.min(configTimeout ?? defaultTimeoutMs, 600_000);
const timeoutCapMs = generateVideoPath ? 1_800_000 : 600_000;
const timeoutMs = Math.min(configTimeout ?? defaultTimeoutMs, timeoutCapMs);
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
const keepSession = geminiOptions.keepSession === true;
const generateImagePath = resolveInvocationPath(geminiOptions.generateImage);
const editImagePath = resolveInvocationPath(geminiOptions.editImage);
const outputPath = resolveInvocationPath(geminiOptions.outputPath);
const attachmentPaths = (runOptions.attachments ?? []).map((attachment) => attachment.path);
const referenceImagePaths = normalizePathList(geminiOptions.referenceImages);
const requestFilePaths = dedupePaths(
keepSession ? attachmentPaths : [...referenceImagePaths, ...attachmentPaths],
);
if (generateVideoPath && (generateImagePath || editImagePath)) {
throw new Error('Gemini web executor: generateVideo cannot be combined with generateImage/editImage options.');
}
let prompt = runOptions.prompt;
if (geminiOptions.aspectRatio && (generateImagePath || editImagePath)) {
if (geminiOptions.aspectRatio && (generateImagePath || editImagePath || generateVideoPath)) {
prompt = `${prompt} (aspect ratio: ${geminiOptions.aspectRatio})`;
}
if (geminiOptions.youtube) {
@@ -158,29 +289,50 @@ export function createGeminiWebExecutor(
if (generateImagePath && !editImagePath) {
prompt = `Generate an image: ${prompt}`;
}
if (generateVideoPath) {
prompt = `Generate a video: ${prompt}`;
}
const model: GeminiWebModelId = resolveGeminiWebModel(runOptions.config?.desiredModel, log);
let response: GeminiWebResponse;
let videoSaveSummary: { saved: boolean; videoCount: number; outputPath: string } | null = null;
try {
let chatMetadata: unknown = keepSession ? persistedChatMetadata : null;
if (keepSession && referenceImagePaths.length > 0 && !referenceImagesUploaded) {
const intro = await runGeminiWebWithFallback({
prompt: 'Here are reference images for future messages.',
files: referenceImagePaths,
model,
cookieMap,
chatMetadata,
signal: controller.signal,
});
chatMetadata = intro.metadata;
persistedChatMetadata = intro.metadata;
referenceImagesUploaded = true;
}
if (editImagePath) {
const intro = await runGeminiWebWithFallback({
prompt: 'Here is an image to edit',
files: [editImagePath],
model,
cookieMap,
chatMetadata: null,
chatMetadata,
signal: controller.signal,
});
const editPrompt = `Use image generation tool to ${prompt}`;
const out = await runGeminiWebWithFallback({
prompt: editPrompt,
files: attachmentPaths,
files: requestFilePaths,
model,
cookieMap,
chatMetadata: intro.metadata,
signal: controller.signal,
});
if (keepSession) persistedChatMetadata = out.metadata;
response = {
text: out.text ?? null,
thoughts: geminiOptions.showThoughts ? out.thoughts : null,
@@ -198,12 +350,13 @@ export function createGeminiWebExecutor(
} else if (generateImagePath) {
const out = await runGeminiWebWithFallback({
prompt,
files: attachmentPaths,
files: requestFilePaths,
model,
cookieMap,
chatMetadata: null,
chatMetadata,
signal: controller.signal,
});
if (keepSession) persistedChatMetadata = out.metadata;
response = {
text: out.text ?? null,
thoughts: geminiOptions.showThoughts ? out.thoughts : null,
@@ -216,15 +369,36 @@ export function createGeminiWebExecutor(
if (!imageSave.saved) {
throw new Error(`No images generated. Response text:\n${out.text || '(empty response)'}`);
}
} else if (generateVideoPath) {
const out = await runGeminiWebWithFallback({
prompt,
files: requestFilePaths,
model,
cookieMap,
chatMetadata,
signal: controller.signal,
});
if (keepSession) persistedChatMetadata = out.metadata;
response = {
text: out.text ?? null,
thoughts: geminiOptions.showThoughts ? out.thoughts : null,
has_images: false,
image_count: 0,
};
const resolvedOutputPath = generateVideoPath ?? outputPath ?? 'generated.mp4';
const save = await saveFirstGeminiVideoFromOutput(out, cookieMap, resolvedOutputPath, controller.signal);
videoSaveSummary = { ...save, outputPath: resolvedOutputPath };
} else {
const out = await runGeminiWebWithFallback({
prompt,
files: attachmentPaths,
files: requestFilePaths,
model,
cookieMap,
chatMetadata: null,
chatMetadata,
signal: controller.signal,
});
if (keepSession) persistedChatMetadata = out.metadata;
response = {
text: out.text ?? null,
thoughts: geminiOptions.showThoughts ? out.thoughts : null,
@@ -247,6 +421,15 @@ export function createGeminiWebExecutor(
const imagePath = generateImagePath || outputPath || 'generated.png';
answerMarkdown += `\n\n*Generated ${response.image_count} image(s). Saved to: ${imagePath}*`;
}
if (videoSaveSummary) {
if (videoSaveSummary.saved) {
answerMarkdown += `\n\n*Generated ${videoSaveSummary.videoCount || 1} video(s). Saved to: ${videoSaveSummary.outputPath}*`;
} else if (/video_gen_chip/.test(answerMarkdown) || /video_gen_chip/.test(response.text ?? '')) {
answerMarkdown += '\n\n*Video generation is asynchronous. Check Gemini web UI to download the result.*';
} else {
answerMarkdown += '\n\n*No downloadable video URL found in Gemini response.*';
}
}
const tookMs = Date.now() - startTime;
log?.(`[gemini-web] Completed in ${tookMs}ms`);
+139 -65
View File
@@ -10,6 +10,7 @@ import {
writeGeminiCookieMapToDisk,
} from './cookie-store.js';
import { resolveGeminiWebChromeProfileDir, resolveGeminiWebCookiePath } from './paths.js';
import { readSession, writeSession, listSessions } from './session-store.js';
function printUsage(exitCode = 0): never {
const cookiePath = resolveGeminiWebCookiePath();
@@ -21,12 +22,19 @@ function printUsage(exitCode = 0): never {
npx -y bun skills/baoyu-gemini-web/scripts/main.ts --prompt "A cute cat" --image generated.png
npx -y bun skills/baoyu-gemini-web/scripts/main.ts --promptfiles system.md content.md --image out.png
Multi-turn conversation (agent generates unique sessionId):
npx -y bun skills/baoyu-gemini-web/scripts/main.ts "Remember 42" --sessionId abc123
npx -y bun skills/baoyu-gemini-web/scripts/main.ts "What number?" --sessionId abc123
Options:
-p, --prompt <text> Prompt text
--promptfiles <files...> Read prompt from one or more files (concatenated in order)
-m, --model <id> gemini-3-pro | gemini-2.5-pro | gemini-2.5-flash (default: gemini-3-pro)
--json Output JSON
--image [path] Generate an image and save it (default: ./generated.png)
--reference <files...> Reference images for vision input
--sessionId <id> Session ID for multi-turn conversation (agent should generate unique ID)
--list-sessions List saved sessions (max 100, sorted by update time)
--login Only refresh cookies, then exit
--cookie-path <path> Cookie file path (default: ${cookiePath})
--profile-dir <path> Chrome profile dir (default: ${profileDir})
@@ -75,6 +83,9 @@ function parseArgs(argv: string[]): {
loginOnly?: boolean;
cookiePath?: string;
profileDir?: string;
referenceImages?: string[];
sessionId?: string;
listSessions?: boolean;
} {
const out: ReturnType<typeof parseArgs> = {};
const positional: string[] = [];
@@ -157,6 +168,32 @@ function parseArgs(argv: string[]): {
out.profileDir = arg.slice('--profile-dir='.length);
continue;
}
if (arg === '--reference' || arg === '--ref') {
out.referenceImages = [];
while (i + 1 < argv.length) {
const next = argv[i + 1];
if (next && !next.startsWith('-')) {
out.referenceImages.push(next);
i += 1;
} else {
break;
}
}
continue;
}
if (arg === '--sessionId' || arg === '--session-id') {
out.sessionId = argv[i + 1] ?? '';
i += 1;
continue;
}
if (arg.startsWith('--sessionId=') || arg.startsWith('--session-id=')) {
out.sessionId = arg.split('=')[1] ?? '';
continue;
}
if (arg === '--list-sessions') {
out.listSessions = true;
continue;
}
if (arg.startsWith('-')) {
throw new Error(`Unknown option: ${arg}`);
@@ -178,6 +215,9 @@ function parseArgs(argv: string[]): {
if (out.cookiePath === '') delete out.cookiePath;
if (out.profileDir === '') delete out.profileDir;
if (out.promptFiles?.length === 0) delete out.promptFiles;
if (out.referenceImages?.length === 0) delete out.referenceImages;
if (out.sessionId != null) out.sessionId = out.sessionId.trim();
if (out.sessionId === '') delete out.sessionId;
return out;
}
@@ -186,7 +226,7 @@ async function isCookieMapValid(cookieMap: Record<string, string>): Promise<bool
if (!hasRequiredGeminiCookies(cookieMap)) return false;
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 10_000);
const timer = setTimeout(() => controller.abort(), 30_000);
try {
await fetchGeminiAccessToken(cookieMap, controller.signal);
return true;
@@ -201,7 +241,7 @@ async function ensureGeminiCookieMap(options: {
cookiePath: string;
profileDir: string;
}): Promise<Record<string, string>> {
const log = (msg: string) => console.log(msg);
const log = (msg: string) => console.error(msg);
let cookieMap = await readGeminiCookieMapFromDisk({ cookiePath: options.cookiePath, log });
if (await isCookieMapValid(cookieMap)) return cookieMap;
@@ -251,85 +291,63 @@ async function main(): Promise<void> {
const cookiePath = args.cookiePath ?? resolveGeminiWebCookiePath();
const profileDir = args.profileDir ?? resolveGeminiWebChromeProfileDir();
if (args.listSessions) {
const sessions = await listSessions();
if (sessions.length === 0) {
console.log('No saved sessions.');
} else {
for (const { id, updatedAt } of sessions) {
console.log(`${id}\t${updatedAt}`);
}
}
return;
}
if (args.loginOnly) {
await ensureGeminiCookieMap({ cookiePath, profileDir });
return;
}
const promptFromStdin = await readPromptFromStdin();
const promptFromFiles = args.promptFiles ? readPromptFiles(args.promptFiles) : null;
const prompt = promptFromFiles || args.prompt || promptFromStdin;
const promptFromArgs = promptFromFiles || args.prompt;
const prompt = promptFromArgs || (await readPromptFromStdin());
if (!prompt) printUsage(1);
const sessionData = args.sessionId ? await readSession(args.sessionId) : null;
const chatMetadata = sessionData?.metadata ?? null;
let cookieMap = await ensureGeminiCookieMap({ cookiePath, profileDir });
const desiredModel = resolveModel(args.model || 'gemini-3-pro');
const imagePath = resolveImageOutputPath(args.imagePath);
const referenceImages = (args.referenceImages ?? []).map((p) =>
path.isAbsolute(p) ? p : path.resolve(process.cwd(), p),
);
try {
const effectivePrompt = imagePath ? `Generate an image: ${prompt}` : prompt;
const out = await runGeminiWebWithFallback({
prompt: effectivePrompt,
files: [],
model: desiredModel,
cookieMap,
chatMetadata: null,
});
let imageSaved = false;
let imageCount = 0;
if (imagePath) {
const save = await saveFirstGeminiImageFromOutput(out, cookieMap, imagePath);
imageSaved = save.saved;
imageCount = save.imageCount;
if (!imageSaved) {
throw new Error(`No images generated. Response text:\n${out.text || '(empty response)'}`);
}
}
if (args.json) {
process.stdout.write(
`${JSON.stringify(
imagePath ? { ...out, imageSaved, imageCount, imagePath } : out,
null,
2,
)}\n`,
);
if (out.errorMessage) process.exit(1);
return;
}
if (out.errorMessage) {
throw new Error(out.errorMessage);
}
process.stdout.write(out.text ?? '');
if (!out.text?.endsWith('\n')) process.stdout.write('\n');
if (imagePath) {
process.stdout.write(`Saved image (${imageCount || 1}) to: ${imagePath}\n`);
}
return;
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
if (message.includes('Unable to locate Gemini access token')) {
console.error('[gemini-web] Cookies may be expired. Re-opening browser to refresh cookies...');
await sleep(500);
cookieMap = await getGeminiCookieMapViaChrome({ userDataDir: profileDir, log: (m) => console.log(m) });
await writeGeminiCookieMapToDisk(cookieMap, { cookiePath, log: (m) => console.log(m) });
const controller = new AbortController();
const timeoutMs = imagePath ? 300_000 : 120_000;
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
const effectivePrompt = imagePath ? `Generate an image: ${prompt}` : prompt;
const out = await runGeminiWebWithFallback({
prompt: imagePath ? `Generate an image: ${prompt}` : prompt,
files: [],
prompt: effectivePrompt,
files: referenceImages,
model: desiredModel,
cookieMap,
chatMetadata: null,
chatMetadata,
signal: controller.signal,
});
if (args.sessionId && out.metadata) {
await writeSession(args.sessionId, out.metadata, prompt, out.text ?? '', out.errorMessage);
}
let imageSaved = false;
let imageCount = 0;
if (imagePath) {
const save = await saveFirstGeminiImageFromOutput(out, cookieMap, imagePath);
const save = await saveFirstGeminiImageFromOutput(out, cookieMap, imagePath, controller.signal);
imageSaved = save.saved;
imageCount = save.imageCount;
if (!imageSaved) {
@@ -338,13 +356,8 @@ async function main(): Promise<void> {
}
if (args.json) {
process.stdout.write(
`${JSON.stringify(
imagePath ? { ...out, imageSaved, imageCount, imagePath } : out,
null,
2,
)}\n`,
);
const jsonOut = { ...out, ...(imagePath && { imageSaved, imageCount, imagePath }), ...(args.sessionId && { sessionId: args.sessionId }) };
process.stdout.write(`${JSON.stringify(jsonOut, null, 2)}\n`);
if (out.errorMessage) process.exit(1);
return;
}
@@ -359,6 +372,67 @@ async function main(): Promise<void> {
process.stdout.write(`Saved image (${imageCount || 1}) to: ${imagePath}\n`);
}
return;
} finally {
clearTimeout(timeout);
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
if (message.includes('Unable to locate Gemini access token')) {
console.error('[gemini-web] Cookies may be expired. Re-opening browser to refresh cookies...');
await sleep(500);
cookieMap = await getGeminiCookieMapViaChrome({ userDataDir: profileDir, log: (m) => console.error(m) });
await writeGeminiCookieMapToDisk(cookieMap, { cookiePath, log: (m) => console.error(m) });
const controller = new AbortController();
const timeoutMs = imagePath ? 300_000 : 120_000;
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
const out = await runGeminiWebWithFallback({
prompt: imagePath ? `Generate an image: ${prompt}` : prompt,
files: referenceImages,
model: desiredModel,
cookieMap,
chatMetadata,
signal: controller.signal,
});
if (args.sessionId && out.metadata) {
await writeSession(args.sessionId, out.metadata, prompt, out.text ?? '', out.errorMessage);
}
let imageSaved = false;
let imageCount = 0;
if (imagePath) {
const save = await saveFirstGeminiImageFromOutput(out, cookieMap, imagePath, controller.signal);
imageSaved = save.saved;
imageCount = save.imageCount;
if (!imageSaved) {
throw new Error(`No images generated. Response text:\n${out.text || '(empty response)'}`);
}
}
if (args.json) {
const jsonOut = { ...out, ...(imagePath && { imageSaved, imageCount, imagePath }), ...(args.sessionId && { sessionId: args.sessionId }) };
process.stdout.write(`${JSON.stringify(jsonOut, null, 2)}\n`);
if (out.errorMessage) process.exit(1);
return;
}
if (out.errorMessage) {
throw new Error(out.errorMessage);
}
process.stdout.write(out.text ?? '');
if (!out.text?.endsWith('\n')) process.stdout.write('\n');
if (imagePath) {
process.stdout.write(`Saved image (${imageCount || 1}) to: ${imagePath}\n`);
}
return;
} finally {
clearTimeout(timeout);
}
}
throw error;
+9
View File
@@ -34,3 +34,12 @@ export function resolveGeminiWebChromeProfileDir(): string {
if (override) return path.resolve(override);
return path.join(resolveGeminiWebDataDir(), PROFILE_DIR_NAME);
}
export function resolveGeminiWebSessionsDir(): string {
return path.join(resolveGeminiWebDataDir(), 'sessions');
}
export function resolveGeminiWebSessionPath(name: string): string {
const sanitized = name.replace(/[^a-zA-Z0-9_-]/g, '_');
return path.join(resolveGeminiWebSessionsDir(), `${sanitized}.json`);
}
@@ -0,0 +1,90 @@
import { mkdir, readFile, writeFile, readdir, stat } from 'node:fs/promises';
import path from 'node:path';
import { resolveGeminiWebSessionsDir, resolveGeminiWebSessionPath } from './paths.js';
export interface SessionMessage {
role: 'user' | 'assistant';
content: string;
timestamp: string;
error?: string;
}
export interface SessionData {
id: string;
metadata: unknown;
messages: SessionMessage[];
createdAt: string;
updatedAt: string;
}
export interface SessionListItem {
id: string;
updatedAt: string;
}
export async function readSession(id: string): Promise<SessionData | null> {
const sessionPath = resolveGeminiWebSessionPath(id);
try {
const content = await readFile(sessionPath, 'utf8');
return JSON.parse(content) as SessionData;
} catch {
return null;
}
}
export async function writeSession(
id: string,
metadata: unknown,
userMessage: string,
assistantMessage: string,
error?: string,
): Promise<void> {
const sessionPath = resolveGeminiWebSessionPath(id);
const sessionsDir = resolveGeminiWebSessionsDir();
await mkdir(sessionsDir, { recursive: true });
const existing = await readSession(id);
const now = new Date().toISOString();
const newMessages: SessionMessage[] = [
{ role: 'user', content: userMessage, timestamp: now },
{ role: 'assistant', content: assistantMessage, timestamp: now, ...(error && { error }) },
];
const data: SessionData = {
id,
metadata,
messages: [...(existing?.messages ?? []), ...newMessages],
createdAt: existing?.createdAt ?? now,
updatedAt: now,
};
await writeFile(sessionPath, JSON.stringify(data, null, 2));
}
export async function listSessions(limit = 100): Promise<SessionListItem[]> {
const sessionsDir = resolveGeminiWebSessionsDir();
try {
const files = await readdir(sessionsDir);
const jsonFiles = files.filter((f) => f.endsWith('.json'));
const items: { id: string; updatedAt: string; mtime: number }[] = [];
for (const file of jsonFiles) {
const filePath = path.join(sessionsDir, file);
try {
const stats = await stat(filePath);
items.push({
id: file.slice(0, -5),
updatedAt: stats.mtime.toISOString(),
mtime: stats.mtime.getTime(),
});
} catch {
continue;
}
}
items.sort((a, b) => b.mtime - a.mtime);
return items.slice(0, limit).map(({ id, updatedAt }) => ({ id, updatedAt }));
} catch {
return [];
}
}
+9
View File
@@ -2,9 +2,18 @@ export interface GeminiWebOptions {
youtube?: string;
generateImage?: string;
editImage?: string;
generateVideo?: string;
outputPath?: string;
showThoughts?: boolean;
aspectRatio?: string;
/**
* One or more local image paths to upload as persistent reference images.
* - If `keepSession` is enabled, they are uploaded once per executor session.
* - Otherwise, they are attached to each request.
*/
referenceImages?: string | string[];
/** Preserve Gemini chat metadata to continue multi-turn conversations within the same executor instance. */
keepSession?: boolean;
}
export interface GeminiWebResponse {
+98 -41
View File
@@ -481,52 +481,78 @@ export async function publishArticle(options: ArticleOptions): Promise<void> {
const img = sortedImages[i]!;
console.log(`[x-article] [${i + 1}/${sortedImages.length}] Inserting image at placeholder: ${img.placeholder}`);
// Find, scroll to, and select the placeholder text in DraftEditor
const placeholderFound = await cdp.send<{ result: { value: boolean } }>('Runtime.evaluate', {
expression: `(() => {
const editor = document.querySelector('.DraftEditor-editorContainer [data-contents="true"]');
if (!editor) return false;
// Helper to select placeholder with retry
const selectPlaceholder = async (maxRetries = 3): Promise<boolean> => {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
// Find, scroll to, and select the placeholder text in DraftEditor
await cdp!.send('Runtime.evaluate', {
expression: `(() => {
const editor = document.querySelector('.DraftEditor-editorContainer [data-contents="true"]');
if (!editor) return false;
const placeholder = ${JSON.stringify(img.placeholder)};
const placeholder = ${JSON.stringify(img.placeholder)};
// Search through all text nodes in the editor
const walker = document.createTreeWalker(editor, NodeFilter.SHOW_TEXT, null, false);
let node;
// Search through all text nodes in the editor
const walker = document.createTreeWalker(editor, NodeFilter.SHOW_TEXT, null, false);
let node;
while ((node = walker.nextNode())) {
const text = node.textContent || '';
const idx = text.indexOf(placeholder);
if (idx !== -1) {
// Found the placeholder - scroll to it first
const parentElement = node.parentElement;
if (parentElement) {
parentElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
while ((node = walker.nextNode())) {
const text = node.textContent || '';
const idx = text.indexOf(placeholder);
if (idx !== -1) {
// Found the placeholder - scroll to it first
const parentElement = node.parentElement;
if (parentElement) {
parentElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
// Select it
const range = document.createRange();
range.setStart(node, idx);
range.setEnd(node, idx + placeholder.length);
const sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
return true;
}
}
return false;
})()`,
}, { sessionId });
// Select it
const range = document.createRange();
range.setStart(node, idx);
range.setEnd(node, idx + placeholder.length);
const sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
return true;
}
// Wait for scroll and selection to settle
await sleep(800);
// Verify selection matches the placeholder
const selectionCheck = await cdp!.send<{ result: { value: string } }>('Runtime.evaluate', {
expression: `window.getSelection()?.toString() || ''`,
returnByValue: true,
}, { sessionId });
const selectedText = selectionCheck.result.value.trim();
if (selectedText === img.placeholder) {
console.log(`[x-article] Selection verified: "${selectedText}"`);
return true;
}
return false;
})()`,
returnByValue: true,
}, { sessionId });
// Wait for scroll animation
await sleep(500);
if (attempt < maxRetries) {
console.log(`[x-article] Selection attempt ${attempt} got "${selectedText}", retrying...`);
await sleep(500);
} else {
console.warn(`[x-article] Selection failed after ${maxRetries} attempts, got: "${selectedText}"`);
}
}
return false;
};
if (!placeholderFound.result.value) {
console.warn(`[x-article] Placeholder not found in DOM: ${img.placeholder}`);
// Try to select the placeholder
const selected = await selectPlaceholder(3);
if (!selected) {
console.warn(`[x-article] Skipping image - could not select placeholder: ${img.placeholder}`);
continue;
}
console.log(`[x-article] Placeholder selected, copying image: ${path.basename(img.localPath)}`);
console.log(`[x-article] Copying image: ${path.basename(img.localPath)}`);
// Copy image to clipboard
if (!copyImageToClipboard(img.localPath)) {
@@ -535,17 +561,48 @@ export async function publishArticle(options: ArticleOptions): Promise<void> {
}
// Wait for clipboard to be fully ready
await sleep(800);
await sleep(1000);
// Delete placeholder by pressing Enter (placeholder is already selected)
console.log(`[x-article] Deleting placeholder with Enter...`);
await cdp.send('Input.dispatchKeyEvent', { type: 'keyDown', key: 'Enter', code: 'Enter', windowsVirtualKeyCode: 13 }, { sessionId });
await cdp.send('Input.dispatchKeyEvent', { type: 'keyUp', key: 'Enter', code: 'Enter', windowsVirtualKeyCode: 13 }, { sessionId });
// Delete placeholder by pressing Backspace (more reliable than Enter for replacing selection)
console.log(`[x-article] Deleting placeholder...`);
await cdp.send('Input.dispatchKeyEvent', { type: 'keyDown', key: 'Backspace', code: 'Backspace', windowsVirtualKeyCode: 8 }, { sessionId });
await cdp.send('Input.dispatchKeyEvent', { type: 'keyUp', key: 'Backspace', code: 'Backspace', windowsVirtualKeyCode: 8 }, { sessionId });
// Wait and verify placeholder is deleted
await sleep(500);
// Check that placeholder is no longer in editor
const afterDelete = await cdp.send<{ result: { value: boolean } }>('Runtime.evaluate', {
expression: `(() => {
const editor = document.querySelector('.DraftEditor-editorContainer [data-contents="true"]');
if (!editor) return true;
return !editor.innerText.includes(${JSON.stringify(img.placeholder)});
})()`,
returnByValue: true,
}, { sessionId });
if (!afterDelete.result.value) {
console.warn(`[x-article] Placeholder may not have been deleted, trying again...`);
// Try selecting and deleting again
await selectPlaceholder(1);
await sleep(300);
await cdp.send('Input.dispatchKeyEvent', { type: 'keyDown', key: 'Backspace', code: 'Backspace', windowsVirtualKeyCode: 8 }, { sessionId });
await cdp.send('Input.dispatchKeyEvent', { type: 'keyUp', key: 'Backspace', code: 'Backspace', windowsVirtualKeyCode: 8 }, { sessionId });
await sleep(500);
}
// Focus editor to ensure cursor is in position
await cdp.send('Runtime.evaluate', {
expression: `(() => {
const editor = document.querySelector('.DraftEditor-editorContainer [contenteditable="true"]');
if (editor) editor.focus();
})()`,
}, { sessionId });
await sleep(300);
// Paste image using paste script (activates Chrome, sends real keystroke)
console.log(`[x-article] Pasting image...`);
if (pasteFromClipboard('Google Chrome', 5, 800)) {
if (pasteFromClipboard('Google Chrome', 5, 1000)) {
console.log(`[x-article] Image pasted: ${path.basename(img.localPath)}`);
} else {
console.warn(`[x-article] Failed to paste image after retries`);
+133 -223
View File
@@ -5,295 +5,205 @@ description: Generate professional slide deck images from content. Creates compr
# Slide Deck Generator
Transform content into professional slide deck with comprehensive outlines and generated slide images.
Transform content into professional slide deck images with flexible style options.
## Usage
```bash
# From markdown file
/baoyu-slide-deck path/to/article.md
# With style preference
/baoyu-slide-deck path/to/article.md --style corporate
/baoyu-slide-deck path/to/article.md --style playful
/baoyu-slide-deck path/to/article.md --style technical
# With audience specification
/baoyu-slide-deck path/to/article.md --audience beginners
/baoyu-slide-deck path/to/article.md --audience executives
# With language
/baoyu-slide-deck path/to/article.md --lang zh
/baoyu-slide-deck path/to/article.md --lang en
# Outline only (no image generation)
/baoyu-slide-deck path/to/article.md --outline-only
# Direct content input
/baoyu-slide-deck
[paste content]
/baoyu-slide-deck path/to/content.md
/baoyu-slide-deck path/to/content.md --style sketch-notes
/baoyu-slide-deck path/to/content.md --audience executives
/baoyu-slide-deck path/to/content.md --lang zh
/baoyu-slide-deck path/to/content.md --slides 10
/baoyu-slide-deck path/to/content.md --outline-only
/baoyu-slide-deck # Then paste content
```
## Script Directory
**Important**: All scripts are located in the `scripts/` subdirectory of this skill.
**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/merge-to-pptx.ts` | Merge slides into PowerPoint |
| `scripts/merge-to-pdf.ts` | Merge slides into PDF |
## Options
| Option | Description |
|--------|-------------|
| `--style <name>` | Visual style preset (see Style Gallery) |
| `--audience <type>` | Target audience level |
| `--lang <code>` | Output language (en, zh, etc.) |
| `--slides <number>` | Target slide count (max 20) |
| `--style <name>` | Visual style (see Style Gallery) |
| `--audience <type>` | Target audience: beginners, intermediate, experts, executives, general |
| `--lang <code>` | Output language (en, zh, ja, etc.) |
| `--slides <number>` | Target slide count |
| `--outline-only` | Generate outline only, skip image generation |
## Style Gallery
| Style | Description |
|-------|-------------|
| `editorial` (Default) | Clean, sophisticated, minimalist |
| `corporate` | Professional, trustworthy, polished |
| `technical` | Precise, data-driven, analytical |
| `playful` | Bold, energetic, engaging |
| `minimal` | Ultra-clean, zen-like, focused |
| `storytelling` | Narrative-driven, cinematic, immersive |
| `warm` | Cozy, healing, hand-drawn illustration style |
| `retro-flat` | Flat vector illustration with retro palette |
| `notion` | Minimalist hand-drawn line art, intellectual |
Detailed style definitions: `references/styles/<style>.md`
| Style | Description | Best For |
|-------|-------------|----------|
| `sketch-notes` | Hand-drawn, warm & friendly | Educational, tutorials |
| `blueprint` | Technical, precise & analytical | Architecture, system design |
| `bold-editorial` | Magazine, high-impact & dynamic | Product launches, keynotes |
| `vector-illustration` | Flat vector, retro & cute | Creative, children's content |
| `minimal` | Ultra-clean, maximum whitespace | Executive briefings, premium |
| `storytelling` | Cinematic, full-bleed visuals | Narratives, case studies |
| `warm` | Soft gradients, wellness aesthetic | Lifestyle, personal development |
| `notion` (Default) | SaaS dashboard, clean data focus | Product demos, productivity |
| `corporate` | Navy/gold, professional | Investor decks, proposals |
| `playful` | Vibrant, dynamic shapes | Workshops, training |
## Auto Style Selection
When no `--style` is specified, analyze content for style signals:
| Content Signals | Selected Style |
|----------------|----------------|
| AI, coding, tech, digital, algorithm, data | `technical` |
| Business, strategy, investment, corporate | `corporate` |
| Personal story, journey, narrative, emotion | `storytelling` |
| Simple, zen, focus, essential, one idea | `minimal` |
| Fun, creative, workshop, educational | `playful` |
| Research, analysis, thought leadership | `editorial` |
| Wellness, healing, cozy, self-care, lifestyle, comfort | `warm` |
| Tutorial, explainer, how-to, beginner, product, guide | `retro-flat` |
| Knowledge, concept, productivity, SaaS, notion, intellectual | `notion` |
|-----------------|----------------|
| tutorial, learn, education, guide, intro, beginner | `sketch-notes` |
| architecture, system, data, analysis, technical | `blueprint` |
| launch, marketing, brand, keynote, impact | `bold-editorial` |
| creative, children, kids, cute, illustration | `vector-illustration` |
| executive, minimal, clean, simple, elegant | `minimal` |
| story, journey, case study, narrative, emotional | `storytelling` |
| wellness, lifestyle, personal, growth, mindfulness | `warm` |
| saas, product, dashboard, metrics, productivity | `notion` |
| investor, quarterly, business, corporate, proposal | `corporate` |
| workshop, training, fun, playful, energetic | `playful` |
| Default | `notion` |
## Audience Presets
## Design Philosophy
| Audience | Approach |
|----------|----------|
| `beginners` | Step-by-step, more context, simpler visuals |
| `intermediate` | Balanced detail, some assumed knowledge |
| `experts` | Dense information, technical depth, less hand-holding |
| `executives` | High-level insights, key metrics, strategic focus |
| `general` | Accessible language, broad appeal, clear takeaways |
This deck is designed for **reading and sharing**, not live presentation:
- Each slide must be **self-explanatory** without verbal commentary
- Structure content for **logical flow** when scrolling
- Include **all necessary context** within each slide
- Optimize for **social media sharing** and offline reading
## File Management
### With Article Path
### With Content Path
```
path/to/
├── article.md
content-dir/
├── source-content.md
└── slide-deck/
├── outline.md
├── prompts/
── 01-cover.md
├── 02-content-1.md
│ └── ...
── 01-cover.png
├── 02-content-1.png
└── ...
── 01-slide-cover.md, 02-slide-{slug}.md, ...
├── 01-slide-cover.png, 02-slide-{slug}.png, ...
├── {topic-slug}.pptx
── {topic-slug}.pdf
```
### Without Article Path
### Without Content Path (Pasted Content)
```
./baoyu-slide-deck-outputs/YYYY-MM-DD/[topic-slug]/
slide-outputs/YYYY-MM-DD/{topic-slug}/
├── source.md
├── outline.md
├── prompts/
│ ├── 01-cover.md
│ └── ...
── 01-cover.png
└── ...
├── *.png
├── {topic-slug}.pptx
── {topic-slug}.pdf
```
## Workflow
### Step 1: Analyze Content & Select Style
### Step 1: Analyze Content
1. Read source content
2. If `--style` specified, use that style
3. Otherwise, analyze content for style signals
4. Extract key information:
- Core narrative and key messages
- Important data points and statistics
- Logical flow and structure
- Target audience signals
1. Save source content (if pasted, save as `source.md`)
2. Follow `references/analysis-framework.md` for deep content analysis
3. Determine style (use `--style` or auto-select from signals)
4. Detect languages (source vs. user preference)
5. Plan slide count (`--slides` or dynamic)
### Step 2: Generate Outline
### Step 2: Generate Outline Variants
Create outline with `STYLE_INSTRUCTIONS` block and slide specifications.
1. Generate 3 style variant outlines based on content analysis
2. Follow `references/outline-template.md` for structure
3. Save as `outline-{style}.md` for each variant
**Outline Format**:
### Step 3: User Confirmation
```markdown
# Slide Deck Outline: [Topic]
**Single AskUserQuestion with all applicable options:**
**Source**: [source file or "Direct input"]
**Style**: [selected style]
**Audience**: [target audience]
**Language**: [output language]
**Slide Count**: N slides
**Generated**: YYYY-MM-DD HH:mm
| Question | When to Ask |
|----------|-------------|
| Style variant | Always (3 options + custom) |
| Language | Only if source ≠ user language |
---
After selection:
- Copy selected `outline-{style}.md` to `outline.md`
- Regenerate in different language if requested
- User may edit `outline.md` for fine-tuning
<STYLE_INSTRUCTIONS>
Design Aesthetic: [Overall style description]
Background Color: [Description and Hex Code]
Primary Font: [Font name for Headlines]
Secondary Font: [Font name for Body copy]
Color Palette:
Primary Text Color: [Hex Code]
Primary Accent Color: [Hex Code]
Visual Elements: [Lines, shapes, imagery style, etc.]
</STYLE_INSTRUCTIONS>
If `--outline-only`, stop here.
---
### Step 4: Generate Prompts
## Slide 1: [Descriptive Title]
**Position**: Cover
**Filename**: 01-cover.png
// NARRATIVE GOAL
[Storytelling purpose within the overall arc]
// KEY CONTENT
Headline: [Main message - narrative, not "Title: Subtitle" format]
Sub-headline: [Supporting context]
Body:
- [Key point 1 with specific data from source]
- [Key point 2 with specific data from source]
// VISUAL
[Detailed description of imagery, charts, graphics, or abstract visuals]
// LAYOUT
[Composition, hierarchy, spatial arrangement, focus points]
---
## Slide 2: [First Content]
...
## Slide N: [Back Cover]
...
```
**Required Slide Structure**:
1. **Slide 1**: Cover Slide (poster-style, heroic typography)
2. **Slides 2-N-1**: Content slides (consistent internal style)
3. **Slide N**: Back Cover (closing statement, not "Thank You")
### Step 3: Save Outline
Save outline as `outline.md` in target directory.
If `--outline-only` flag is set, stop here.
### Step 4: Create Prompt Files
For each slide, create a style-specific prompt file.
**Prompt Format**:
```markdown
Slide theme: [slide title]
Style: [style name]
Position: [cover/content/back-cover]
Visual composition:
- Main visual: [style-appropriate description from VISUAL section]
- Layout: [from LAYOUT section]
- Decorative elements: [style-specific decorations]
Color scheme:
- Background: [style background color]
- Primary text: [style text color]
- Accent: [style accent color]
Text content:
- Headline: [headline text]
- Sub-headline: [sub-headline if any]
- Body points: [bullet points if any]
Style notes: [specific style characteristics to emphasize]
```
1. Read `references/base-prompt.md`
2. Combine with style instructions from outline
3. Add slide-specific content
4. Save to `prompts/` directory
### Step 5: Generate Images
For each slide, generate using:
1. Select available image generation skill
2. Generate session ID: `slides-{topic-slug}-{timestamp}`
3. Generate each slide with same session ID
4. Report progress: "Generated X/N"
### Step 6: Merge to PPTX and PDF
```bash
/baoyu-gemini-web --promptfiles [SKILL_ROOT]/skills/baoyu-slide-deck/prompts/system.md [TARGET_DIR]/prompts/01-cover.md --image [TARGET_DIR]/01-cover.png
npx -y bun ${SKILL_DIR}/scripts/merge-to-pptx.ts <slide-deck-dir>
npx -y bun ${SKILL_DIR}/scripts/merge-to-pdf.ts <slide-deck-dir>
```
Generation flow:
1. Generate images sequentially
2. After each image, output progress: "Generated X/N"
3. On failure, auto-retry once
4. If retry fails, log reason, continue to next
### Step 6: Completion Report
### Step 7: Output Summary
```
Slide Deck Generated!
Slide Deck Complete!
Topic: [topic]
Style: [style name]
Audience: [audience]
Location: [directory path]
Slides: N total
- 01-cover.png ✓ Cover
- 02-content-1.png ✓ Content
- 03-content-2.png ✓ Content
- 01-slide-cover.png ✓ Cover
- 02-slide-intro.png ✓ Content
- ...
- 0N-back-cover.png ✓ Back Cover
- {NN}-slide-back-cover.png ✓ Back Cover
Outline: outline.md
[If any failures]
Failed:
- 0X-slide-name.png: [failure reason]
PPTX: {topic-slug}.pptx
PDF: {topic-slug}.pdf
```
## Slide Modification
See `references/modification-guide.md` for:
- Edit single slide workflow
- Add new slide (with renumbering)
- Delete slide (with renumbering)
- File naming conventions
## References
| File | Content |
|------|---------|
| `references/analysis-framework.md` | Deep content analysis for presentations |
| `references/outline-template.md` | Outline structure and STYLE_INSTRUCTIONS format |
| `references/modification-guide.md` | Edit, add, delete slide workflows |
| `references/content-rules.md` | Content and style guidelines |
| `references/base-prompt.md` | Base prompt for image generation |
| `references/styles/<style>.md` | Full style specifications |
## Notes
### Design Philosophy
- Deck is designed for **reading and sharing**, not live presentation
- Structure should be self-explanatory without a presenter
- Include enough context for visuals to be understood standalone
- Err on the side of audience having **more expertise** than expected
### Content Rules
- Maximum 20 slides per deck
- Every data point must trace to source material
- All details in prompts - image generator has no access to source
### Style Rules
- Avoid AI-generated clichés ("It wasn't just X, it was Y")
- Use narrative headlines, not "Title: Subtitle" format
- Cover and Back Cover should be visually distinct (poster-style)
- Back Cover should be meaningful closure, not "Thank You" or "Questions?"
### Prohibited
- Never include photorealistic images of prominent individuals
- Never include placeholder slides for author name, date, etc.
### Image Generation
- Image generation typically takes 10-30 seconds per slide
- Image generation: 10-30 seconds per slide
- Auto-retry once on generation failure
- Use cartoon alternatives for sensitive public figures
- Output language matches content language
- Maintain style consistency across all slides
- Use stylized alternatives for sensitive public figures
- Maintain style consistency via session ID
@@ -0,0 +1,161 @@
# Presentation Analysis Framework
Deep content analysis for effective slide deck creation.
## 1. Message Hierarchy
Identify the core message structure before designing slides.
### Core Message (One Sentence)
- What is the single most important takeaway?
- If the audience remembers only one thing, what should it be?
- Can you state it in ≤15 words?
### Supporting Points (3-5 Maximum)
- What evidence supports the core message?
- What sub-topics must be covered?
- Prioritize by audience relevance, not source order
### Call-to-Action
- What should the audience DO after viewing?
- Is it clear, specific, and achievable?
- Where does it appear (slide position)?
## 2. Audience Decision Matrix
| Question | Analysis |
|----------|----------|
| Who is the primary audience? | [Role, expertise level, relationship to topic] |
| What do they currently believe? | [Existing knowledge, assumptions, biases] |
| What decision do we want them to make? | [Specific action or conclusion] |
| What barriers exist? | [Objections, concerns, missing information] |
| What evidence will convince them? | [Data types, credibility sources, emotional hooks] |
### Audience Adaptation
| Audience Type | Content Focus | Visual Treatment |
|---------------|---------------|------------------|
| Executives | Outcomes, ROI, strategic impact | High-level, clean, data highlights |
| Technical | Architecture, implementation, specs | Detailed diagrams, code, schematics |
| General | Benefits, stories, relatability | Visual metaphors, simple charts |
| Investors | Market size, traction, team | Growth charts, milestones, comparisons |
| Learners | Step-by-step, examples, practice | Progressive reveals, exercises |
## 3. Visual Opportunity Map
Identify which content benefits from visualization.
### Content-to-Visual Mapping
| Content Type | Visual Treatment | Example |
|--------------|------------------|---------|
| Comparisons | Side-by-side, before/after | Feature comparison table |
| Processes | Flow diagrams, numbered steps | Workflow illustration |
| Hierarchies | Org charts, pyramids, trees | Organizational structure |
| Timelines | Horizontal/vertical timelines | Project milestones |
| Statistics | Charts, highlighted numbers | Key metrics with context |
| Concepts | Icons, metaphors, illustrations | Abstract idea visualization |
| Relationships | Venn diagrams, networks | Ecosystem or dependencies |
| Lists | Structured grids, icon rows | Feature bullets with icons |
### Visual Priority
Rate each piece of content:
- **Must Visualize**: Complex data, key differentiators, memorable moments
- **Should Visualize**: Supporting evidence, secondary points
- **Text Only**: Simple statements, transitions, minor details
## 4. Presentation Flow
Structure for impact and retention.
### Opening (First 2-3 Slides)
| Element | Purpose |
|---------|---------|
| Hook | Capture attention (surprising stat, question, story) |
| Context | Why this matters now |
| Preview | What audience will learn/gain |
### Middle (Content Slides)
| Pattern | When to Use |
|---------|-------------|
| Problem → Solution | Introducing new products/ideas |
| Situation → Complication → Resolution | Complex business cases |
| What → Why → How | Educational content |
| Past → Present → Future | Transformation stories |
| Claim → Evidence → Implication | Data-driven arguments |
### Closing (Final 2-3 Slides)
| Element | Purpose |
|---------|---------|
| Synthesis | Tie back to core message |
| Call-to-Action | Clear next steps |
| Memorable Close | Resonant quote, image, or statement |
### Transitions
- Each slide should answer: "What comes next?"
- Use narrative connectors between sections
- Build logical progression, not topic jumps
## 5. Content Adaptation
Decide what to keep, transform, or omit.
### Keep (High Value)
- Core arguments and evidence
- Unique insights or data
- Audience-relevant examples
- Memorable quotes or statistics
### Simplify (Medium Value)
- Technical details → Visual summaries
- Long explanations → Bullet hierarchies
- Multiple examples → Best 1-2 examples
- Background context → Brief framing
### Visualize (Transform)
- Data tables → Charts or highlighted numbers
- Process descriptions → Flow diagrams
- Comparisons in text → Side-by-side visuals
- Abstract concepts → Concrete metaphors
### Omit (Low Value)
- Tangential information
- Redundant examples
- Excessive caveats
- Background the audience already knows
## 6. Analysis Checklist
Before outline creation, confirm:
### Message Clarity
- [ ] Core message stated in one sentence
- [ ] 3-5 supporting points identified
- [ ] Call-to-action defined
### Audience Fit
- [ ] Primary audience identified
- [ ] Existing beliefs mapped
- [ ] Desired decision clear
- [ ] Evidence matches audience needs
### Visual Planning
- [ ] Key visualizations identified
- [ ] Chart/diagram types selected
- [ ] Visual priority assigned
### Flow Design
- [ ] Opening hook defined
- [ ] Middle pattern selected
- [ ] Closing approach planned
- [ ] Transitions considered
### Content Decisions
- [ ] Keep/simplify/visualize/omit applied
- [ ] Source material fully processed
- [ ] No important content overlooked
@@ -3,38 +3,57 @@ Create a presentation slide image following these guidelines:
## Image Specifications
- **Type**: Presentation slide
- **Orientation**: Landscape (horizontal)
- **Aspect Ratio**: 16:9
- **Style**: Hand-drawn illustration with professional typography
- **Aspect Ratio**: 16:9 (landscape)
- **Style**: Professional slide deck
## Core Persona: The Architect
You are "The Architect" - a master visual storyteller creating presentation slides. Your slides:
- Tell a visual story that complements the narrative
- Use bold, confident visual language
- Balance information density with visual clarity
- Create memorable, impactful visuals
## Core Principles
- Hand-drawn quality throughout - NO realistic or photographic elements
- If content involves sensitive or copyrighted figures, create stylistically similar alternatives - DO NOT refuse to generate
- Clear visual hierarchy: headline dominates, supporting content secondary
- Ample whitespace, avoid cluttered layouts
- Professional presentation aesthetic
- NO slide numbers, page numbers, footers, headers, or logos
- Clean, uncluttered layouts with clear visual hierarchy
- Each slide conveys ONE clear message
## Text Style (CRITICAL)
- **ALL text MUST be hand-drawn style**
- Headlines: Large, bold, prominent
- Body text: Clean, readable, well-spaced
- Use visual emphasis for key terms (color, size, underline)
- **DO NOT use realistic or computer-generated fonts**
- **ALL text MUST match the designated style exactly**
- Title text: Large, bold, immediately readable
- Body text: Clear, legible, appropriate sizing
- Max 3-4 text elements per slide
- **DO NOT use realistic or computer-generated fonts unless style specifies**
- **Font rendering must match the style aesthetic** (hand-drawn for sketch styles, clean for minimal styles)
## Layout Principles
- Consistent margins and spacing
- Clear information hierarchy
- Visual elements support but don't overwhelm text
- Balance between text and imagery
- **Visual Hierarchy**: Most important element gets most visual weight
- **Breathing Room**: Generous margins and spacing between elements
- **Alignment**: Consistent alignment creates professional feel
- **Balance**: Distribute visual weight evenly (symmetrical or asymmetrical)
- **Focal Point**: One clear area draws the eye first
- **Rule of Thirds**: Key elements at intersection points for dynamic compositions
- **Z-Pattern**: For text-heavy slides, arrange content in natural reading flow
## Language
- Use the same language as the content provided below
- Use the same language as the content provided below for all text elements
- Match punctuation style to the content language
- Write in direct, confident language
- Avoid AI-sounding phrases like "dive into", "explore", "let's", "journey"
---
Please use nano banana pro to generate the slide based on the content provided below:
## STYLE_INSTRUCTIONS
[Insert style-specific instructions here]
---
Please use nano banana pro to generate the slide image based on the content provided below:
@@ -0,0 +1,95 @@
# Content & Style Rules
Guidelines for slide deck content quality and style consistency.
## Content Rules
### 1. Respect Reader Attention
- Each slide should communicate ONE main idea
- Remove redundant information
- Prioritize clarity over comprehensiveness
### 2. Data Traceability
- All statistics must include source attribution
- Cite sources directly on slides with data
- Use specific numbers over vague claims
### 3. Self-Contained Prompts
- Every detail must be in the image prompt
- No external references (e.g., "like slide 2")
- Include all colors, layouts, and content explicitly
### 4. No Placeholders
- Every element must be fully specified
- No "[insert data here]" or "TBD"
- All text content finalized before generation
## Style Rules
### 1. Narrative Headlines
Headlines tell the story, not label the content.
| Bad | Good |
|-----|------|
| "Key Statistics" | "Usage doubled in 6 months" |
| "Our Solution" | "One platform replaces five tools" |
| "Benefits" | "Teams save 10 hours weekly" |
### 2. Avoid AI Clichés
Remove these patterns:
- "Dive into", "explore", "journey"
- "Let's look at", "let me show you"
- "Exciting", "amazing", "revolutionary"
- "In conclusion", "to summarize"
### 3. Meaningful Back Cover
Not just "Thank you" or "Questions?"
Include one of:
- Clear call-to-action
- Memorable key takeaway
- Thought-provoking closing statement
- Contact information with purpose
### 4. Consistent Visual Language
Throughout the deck:
- Same icon style
- Same color usage patterns
- Same layout grid system
- Same typography hierarchy
## Slide Structure
| Position | Type | Purpose |
|----------|------|---------|
| 1 | Cover | Title, visual hook, topic introduction |
| 2 to N-1 | Content | Key points, data, explanations |
| N | Back Cover | Summary, call-to-action, memorable close |
## Key Specifications
| Specification | Value |
|---------------|-------|
| Aspect Ratio | 16:9 (landscape) |
| Slide Count | Dynamic based on content |
| Required Slides | Cover + Back Cover minimum |
| Footers | None (no slide numbers, logos) |
| Language Priority | `--lang` → source language → ask user |
| Tone | Direct, confident (avoid AI phrases) |
## Style Quick Reference
| Style | Visual Summary |
|-------|----------------|
| `sketch-notes` | Hand-drawn, warm off-white, conceptual icons |
| `blueprint` | Technical schematics, grid texture, blue tones |
| `bold-editorial` | High contrast, dark backgrounds, magazine impact |
| `vector-illustration` | Flat vector, black outlines, retro colors |
| `minimal` | Maximum whitespace, single accent, zen-like |
| `storytelling` | Full-bleed imagery, cinematic, emotional |
| `warm` | Soft gradients, rounded shapes, wellness palette |
| `notion` | Dashboard aesthetic, clean data viz, SaaS-inspired |
| `corporate` | Navy/gold, structured layouts, business polish |
| `playful` | Vibrant coral/teal/yellow, dynamic, energetic |
Full style specifications: `references/styles/<style>.md`
@@ -0,0 +1,85 @@
# Slide Modification Guide
Workflows for modifying individual slides after initial generation.
## Edit Single Slide
Regenerate a specific slide with modified content:
1. Identify slide to edit (e.g., `03-slide-key-findings.png`)
2. Update prompt in `prompts/03-slide-key-findings.md`
3. If content changes significantly, update slug in filename
4. Regenerate image using same session ID
5. Regenerate PPTX and PDF
## Add New Slide
Insert a new slide at specified position:
1. Specify insertion position (e.g., after slide 3)
2. Create new prompt with appropriate slug (e.g., `04-slide-new-section.md`)
3. Generate new slide image
4. **Renumber files**: All subsequent slides increment NN by 1
- `04-slide-conclusion.png``05-slide-conclusion.png`
- Slugs remain unchanged
5. Update `outline.md` with new slide entry
6. Regenerate PPTX and PDF
## Delete Slide
Remove a slide and renumber:
1. Identify slide to delete (e.g., `03-slide-key-findings.png`)
2. Remove image file and prompt file
3. **Renumber files**: All subsequent slides decrement NN by 1
- `04-slide-conclusion.png``03-slide-conclusion.png`
- Slugs remain unchanged
4. Update `outline.md` to remove slide entry
5. Regenerate PPTX and PDF
## File Naming Convention
Files use meaningful slugs for better readability:
```
NN-slide-[slug].png
NN-slide-[slug].md (in prompts/)
```
Examples:
- `01-slide-cover.png`
- `02-slide-problem-statement.png`
- `03-slide-key-findings.png`
- `04-slide-back-cover.png`
## Slug Rules
| Rule | Description |
|------|-------------|
| Format | Kebab-case (lowercase, hyphens) |
| Source | Derived from slide title/content |
| Uniqueness | Must be unique within the deck |
| Updates | Change slug when content changes significantly |
## Renumbering Rules
| Scenario | Action |
|----------|--------|
| Add slide | Increment NN for all subsequent slides |
| Delete slide | Decrement NN for all subsequent slides |
| Reorder slides | Update NN to match new positions |
| Edit slide | NN unchanged, update slug if needed |
**Important**: Slugs remain unchanged during renumbering. Only the NN prefix changes.
## Post-Modification Checklist
After any modification:
- [ ] Image file renamed/created correctly
- [ ] Prompt file renamed/created correctly
- [ ] Subsequent files renumbered (if add/delete)
- [ ] `outline.md` updated to reflect changes
- [ ] PPTX regenerated
- [ ] PDF regenerated
- [ ] Slide count in outline header updated
@@ -0,0 +1,164 @@
# Outline Template
Standard structure for slide deck outlines with style instructions.
## Outline Format
```markdown
# Slide Deck Outline
**Topic**: [topic description]
**Style**: [selected style]
**Audience**: [target audience]
**Language**: [output language]
**Slide Count**: N slides
**Generated**: YYYY-MM-DD HH:mm
---
<STYLE_INSTRUCTIONS>
Design Aesthetic: [2-3 sentence description from style file]
Background:
Color: [Name] ([Hex])
Texture: [description]
Typography:
Primary Font: [detailed description for image generation]
Secondary Font: [detailed description for image generation]
Color Palette:
Primary Text: [Name] ([Hex]) - [usage]
Background: [Name] ([Hex]) - [usage]
Accent 1: [Name] ([Hex]) - [usage]
Accent 2: [Name] ([Hex]) - [usage]
Visual Elements:
- [element 1 with rendering guidance]
- [element 2 with rendering guidance]
- ...
Style Rules:
Do: [guidelines from style file]
Don't: [anti-patterns from style file]
</STYLE_INSTRUCTIONS>
---
[Slide entries follow...]
```
## Cover Slide Template
```markdown
## Slide 1 of N
**Type**: Cover
**Filename**: 01-slide-cover.png
// NARRATIVE GOAL
[What this slide achieves in the story arc]
// KEY CONTENT
Headline: [main title]
Sub-headline: [supporting tagline]
// VISUAL
[Detailed visual description - specific elements, composition, mood]
// LAYOUT
[Composition, hierarchy, spatial arrangement]
```
## Content Slide Template
```markdown
## Slide X of N
**Type**: Content
**Filename**: {NN}-slide-{slug}.png
// NARRATIVE GOAL
[What this slide achieves in the story arc]
// KEY CONTENT
Headline: [main message - narrative, not label]
Sub-headline: [supporting context]
Body:
- [point 1 with specific detail]
- [point 2 with specific detail]
- [point 3 with specific detail]
// VISUAL
[Detailed visual description]
// LAYOUT
[Composition, hierarchy, spatial arrangement]
```
## Back Cover Slide Template
```markdown
## Slide N of N
**Type**: Back Cover
**Filename**: {NN}-slide-back-cover.png
// NARRATIVE GOAL
[Meaningful closing - not just "thank you"]
// KEY CONTENT
Headline: [memorable closing statement or call-to-action]
Body: [optional summary points or next steps]
// VISUAL
[Visual that reinforces the core message]
// LAYOUT
[Clean, impactful composition]
```
## STYLE_INSTRUCTIONS Block
The `<STYLE_INSTRUCTIONS>` block contains all style-specific guidance for image generation:
| Section | Content |
|---------|---------|
| Design Aesthetic | Overall visual direction from style file |
| Background | Base color and texture details |
| Typography | Font descriptions for Gemini (no font names, describe appearance) |
| Color Palette | Named colors with hex codes and usage guidance |
| Visual Elements | Specific graphic elements with rendering instructions |
| Style Rules | Do/Don't guidelines from style file |
**Important**: Typography descriptions must describe the visual appearance (e.g., "rounded sans-serif", "bold geometric") since image generators cannot use font names.
## Section Dividers
Use `---` (horizontal rule) between:
- Header metadata and STYLE_INSTRUCTIONS
- STYLE_INSTRUCTIONS and first slide
- Each slide entry
## Slide Numbering
- Cover is always Slide 1
- Content slides use sequential numbers
- Back Cover is always final slide (N)
- Filename prefix matches slide position: `01-`, `02-`, etc.
## Filename Slugs
Generate meaningful slugs from slide content:
| Slide Type | Slug Pattern | Example |
|------------|--------------|---------|
| Cover | `cover` | `01-slide-cover.png` |
| Content | `{topic-slug}` | `02-slide-problem-statement.png` |
| Back Cover | `back-cover` | `10-slide-back-cover.png` |
Slug rules:
- Kebab-case (lowercase, hyphens)
- Derived from headline or main topic
- Maximum 30 characters
- Unique within deck
@@ -0,0 +1,67 @@
# blueprint
Precise technical blueprint style with professional analytical visual presentation
## Design Aesthetic
Clean, structured visual metaphors using blueprints, diagrams, and schematics. Precise, analytical and aesthetically refined. Information presented in triptych or grid-based layouts with engineering precision.
## Background
- Color: Blueprint Off-White (#FAF8F5)
- Texture: Subtle grid overlay, light engineering paper feel
## Typography
### Primary Font (Headlines)
Neue Haas Grotesk Display Pro or similar clean sans-serif. Bold weight for titles. Precise letterforms with consistent spacing. Technical, authoritative presence.
### Secondary Font (Body)
Tiempos Text or similar elegant serif for body explanations. Clean, readable at smaller sizes. Professional editorial quality.
## Color Palette
| Role | Color | Hex | Usage |
|------|-------|-----|-------|
| Background | Blueprint Paper | #FAF8F5 | Primary background |
| Grid | Light Gray | #E5E5E5 | Background grid lines |
| Primary Text | Deep Slate | #334155 | Headlines, body text |
| Primary Accent | Engineering Blue | #2563EB | Key elements, highlights |
| Secondary Accent | Navy Blue | #1E3A5F | Supporting elements |
| Tertiary | Light Blue | #BFDBFE | Backgrounds, fills |
| Warning | Amber | #F59E0B | Warnings, emphasis points |
## Visual Elements
- Precise lines with consistent stroke weights
- Technical schematics and clean vector graphics
- Thin line work in technical drawing style
- Connection lines use straight lines or 90-degree angles only
- Data visualization with clean, minimal charts
- Dimension lines and measurement indicators
- Cross-section style diagrams
- Isometric or orthographic projections
## Style Rules
### Do
- Maintain consistent line weights throughout
- Use grid alignment for all elements
- Keep color palette restrained and unified
- Create clear visual hierarchy through scale
- Use geometric precision for all shapes
### Don't
- Use hand-drawn or organic shapes
- Add decorative flourishes
- Use curved connection lines
- Include photographic elements
- Add slide numbers, footers, or logos
## Best For
Technical architecture, system design, data analysis, professional business presentations, engineering documentation, process flows
@@ -0,0 +1,69 @@
# bold-editorial
High-impact magazine editorial style with bold visual expression
## Design Aesthetic
Strong visual impact at magazine cover level. Bold typography and dramatic contrast. Full-bleed imagery and large color blocks create commanding presence. Every slide feels like a premium publication cover.
## Background
- Color: Deep Black (#0A0A0A) primary, or Deep Blue (#0F172A) alternative
- Texture: None - clean solid backgrounds, or pure white with bold color blocks
## Typography
### Primary Font (Headlines)
Bold condensed typeface like Impact, Oswald Bold, or Bebas Neue. Oversized headlines that dominate the slide. All-caps for maximum impact. Tight letter-spacing.
### Secondary Font (Body)
Clean sans-serif such as Inter, SF Pro, or Helvetica Neue. Medium weight for body text. High contrast against background.
## Color Palette
| Role | Color | Hex | Usage |
|------|-------|-----|-------|
| Background Dark | Deep Black | #0A0A0A | Primary dark background |
| Background Alt | Deep Blue | #0F172A | Alternative dark background |
| Background Light | Pure White | #FFFFFF | Light mode background |
| Primary Text | Pure White | #FFFFFF | Text on dark backgrounds |
| Alt Text | Pure Black | #000000 | Text on light backgrounds |
| Accent 1 | Electric Blue | #3B82F6 | Primary highlights |
| Accent 2 | Bright Orange | #FB923C | Energy, urgency |
| Accent 3 | Magenta | #EC4899 | Creative, bold accents |
| Accent 4 | Neon Green | #22C55E | Success, growth |
| Accent 5 | Violet | #8B5CF6 | Innovation, premium |
## Visual Elements
- Strong typography as visual element itself
- Geometric shapes and bold color blocks
- Full-bleed images or solid color backgrounds
- High contrast gradients (subtle, not garish)
- Minimal decoration - let content speak
- Dynamic diagonal lines and angles
- Dramatic lighting effects on text
## Style Rules
### Do
- Use extreme scale contrast (huge headlines, small body)
- Create bold color block compositions
- Let negative space create tension
- Use full-bleed backgrounds
- Make every slide feel like a magazine cover
### Don't
- Use soft or muted colors
- Add unnecessary decorative elements
- Create busy, cluttered layouts
- Use thin or delicate typography
- Add slide numbers, footers, or logos
## Best For
Product launches, marketing presentations, keynote speeches, brand showcases, investor pitches, high-stakes presentations
@@ -1,29 +1,69 @@
# corporate
Professional, trustworthy, polished
Professional business style with navy/gold palette and structured layouts
## Design Aesthetic
Clean lines, structured layouts, business-appropriate
Clean lines, structured layouts, and business-appropriate sophistication. Projects competence, reliability, and institutional credibility. Balances professionalism with approachability through careful use of whitespace and refined color choices.
## Color Palette
## Background
- Background: Clean white (#FFFFFF) with navy accents
- Primary Text: Navy (#1E3A5F)
- Accent: Gold (#C9A227)
- Color: Pure White (#FFFFFF) with navy structural elements
- Texture: None - crisp digital clarity for maximum professionalism
## Typography
- Primary Font: Inter (headlines)
- Secondary Font: Source Sans Pro (body)
### Primary Font (Headlines)
Modern geometric sans-serif (Inter, SF Pro, or similar). Clean, professional, and highly legible. Conveys competence and contemporary business sensibility. Medium to semi-bold weight.
### Secondary Font (Body)
Humanist sans-serif (Source Sans Pro style) for body text. Friendly yet professional, optimized for reading comprehension. Regular weight with comfortable line height.
## Color Palette
| Role | Color | Hex | Usage |
|------|-------|-----|-------|
| Background | Pure White | #FFFFFF | Main slide background |
| Primary Text | Navy | #1E3A5F | Headlines, key text |
| Secondary Text | Dark Gray | #4A5568 | Body text |
| Primary Accent | Gold | #C9A227 | Premium highlights, emphasis |
| Secondary Accent | Light Navy | #3D5A80 | Secondary elements |
| Success | Corporate Green | #059669 | Positive metrics |
| Alert | Corporate Red | #DC2626 | Attention items |
| Neutral | Light Gray | #F3F4F6 | Background sections |
## Visual Elements
- Clean charts
- Professional icons
- Structured grids
- Subtle shadows
- Clean charts and data visualizations
- Professional iconography (outlined style)
- Structured grid layouts
- Subtle shadows for depth (minimal)
- Progress bars and metrics displays
- Organizational charts
- Timeline graphics
- Comparison tables
## Style Rules
### Do
- Maintain clear visual hierarchy
- Use consistent grid alignment
- Apply accent colors strategically (gold for emphasis)
- Keep data visualizations clean and readable
- Use professional outlined iconography
### Don't
- Use playful or casual elements
- Apply heavy decorative effects
- Mix too many accent colors
- Crowd slides with information
- Use informal illustration styles
- Add slide numbers, footers, or logos
## Best For
Business presentations, investor decks, reports
Business presentations, investor decks, quarterly reports, executive summaries, client proposals, corporate communications, board meetings
@@ -1,29 +0,0 @@
# editorial
Clean, sophisticated, minimalist
## Design Aesthetic
High-end journal, architectural blueprints
## Color Palette
- Background: Subtle textured off-white (#F8F7F5)
- Primary Text: Dark slate grey (#2F3542)
- Accent: Intelligent blue (#007AFF)
## Typography
- Primary Font: Neue Haas Grotesk Display Pro (bold headlines)
- Secondary Font: Tiempos Text (body copy, annotations)
## Visual Elements
- Thin precise line work
- Schematic diagrams
- Clean vectors
- Spacious layouts
## Best For
Thought leadership, research presentations
@@ -1,29 +1,64 @@
# minimal
Ultra-clean, zen-like, focused
Ultra-clean keynote style with maximum whitespace and zen-like simplicity
## Design Aesthetic
Maximum whitespace, single focal points, elegant typography
Maximum whitespace with minimal elements. Zen-like simplicity where every element earns its place. Premium, refined aesthetic suitable for executive audiences. Less is more - remove until nothing more can be taken away.
## Color Palette
## Background
- Background: Pure white (#FFFFFF)
- Primary Text: Pure black (#000000)
- Accent: Single accent (content-derived)
- Color: Pure White (#FFFFFF)
- Texture: None - absolute clean, no grain or patterns
## Typography
- Primary Font: Helvetica Neue (headlines)
- Secondary Font: Garamond (body)
### Primary Font (Headlines)
Clean geometric sans-serif like SF Pro Display, Inter, or Helvetica Neue. Light to medium weight for elegant restraint. Generous letter-spacing. Large scale for impact without boldness.
### Secondary Font (Body)
Same family as headlines in lighter weight. Minimal size contrast. Clean, airy feeling throughout.
## Color Palette
| Role | Color | Hex | Usage |
|------|-------|-----|-------|
| Background | Pure White | #FFFFFF | Primary background |
| Primary Text | Near Black | #1A1A1A | Headlines, body |
| Secondary Text | Medium Gray | #6B7280 | Captions, metadata |
| Accent | Single Brand Color | #2563EB | One accent only, sparingly |
| Dividers | Light Gray | #E5E7EB | Subtle separators |
## Visual Elements
- Maximum whitespace
- Single focal points
- Hairline rules
- Elegant typography
- Single accent color used sparingly
- Thin hairline rules for separation
- Generous margins (minimum 15% on all sides)
- Center or left-aligned layouts
- Simple geometric shapes only when necessary
- No decorative elements
- Data visualizations in single color or grayscale
## Style Rules
### Do
- Embrace empty space as a design element
- Use single accent color only
- Keep text minimal (10 words or less per slide)
- Create breathing room between elements
- Use scale to create hierarchy
### Don't
- Fill empty space with decoration
- Use multiple accent colors
- Add icons or illustrations unless essential
- Create dense information layouts
- Add slide numbers, footers, or logos
## Best For
Keynotes, philosophical content, product launches
Executive briefings, keynote presentations, premium brand communications, minimalist products, investor meetings, high-level strategy
@@ -1,38 +1,69 @@
# notion
Minimalist hand-drawn line art, intellectual
SaaS dashboard aesthetic with clean data focus and productivity tool styling
## Design Aesthetic
Simple line doodles with hand-drawn wobble, geometric shapes, maximum whitespace, SaaS product feel
Clean, functional SaaS interface aesthetic. Dashboard-inspired layouts with clear data hierarchy. Notion, Linear, and modern productivity tool styling. Information-dense but organized. Professional and trustworthy.
## Color Palette
## Background
- Background: Pure white (#FFFFFF), off-white (#FAFAFA)
- Primary Text: Black (#1A1A1A), dark gray (#4A4A4A)
- Accent: Pastel blue (#A8D4F0), Pastel yellow (#F9E79F), Pastel pink (#FADBD8)
- Color: Light Gray (#F7F7F5) or Pure White (#FFFFFF)
- Texture: None - clean solid backgrounds
## Typography
- Primary Font: Clean hand-drawn lettering, simple sans-serif
- Secondary Font: Simple sans-serif labels
### Primary Font (Headlines)
System UI stack or Inter. Semi-bold weight for emphasis. Clean, functional letterforms. Slightly tighter letter-spacing.
### Secondary Font (Body)
Same family in regular weight. Optimized for screen reading. Comfortable line height (1.5-1.6).
## Color Palette
| Role | Color | Hex | Usage |
|------|-------|-----|-------|
| Background | Light Gray | #F7F7F5 | Primary background |
| Card Background | Pure White | #FFFFFF | Content cards |
| Primary Text | Near Black | #1F1F1F | Headlines, body |
| Secondary Text | Gray | #6B6B6B | Metadata, labels |
| Border | Light Border | #E5E5E5 | Card borders, dividers |
| Accent Blue | Notion Blue | #2383E2 | Links, primary actions |
| Accent Green | Success | #0F7B6C | Positive metrics |
| Accent Red | Alert | #E03E3E | Negative metrics |
| Accent Yellow | Warning | #DFAB01 | Cautions |
## Visual Elements
- Simple line doodles with hand-drawn wobble effect
- Geometric shapes
- Stick figures
- Maximum whitespace
- Single-weight ink lines
- Card-based layouts with subtle borders or shadows
- Clean data tables and charts
- Progress bars and metric displays
- Icon-based navigation hints
- Checkbox and toggle styling
- Tag and label chips
- Subtle hover state styling
- Breadcrumb and hierarchy indicators
## Style Rules
- Single color lines (black/dark gray)
- 1-2 pastel accents only
- NO complex gradients
- NO heavy shadows
- Imperfect hand-drawn feel
### Do
- Use card-based content organization
- Create clear data hierarchy
- Use subtle shadows and borders
- Keep layouts grid-aligned
- Present metrics prominently
### Don't
- Use decorative illustrations
- Add gradients or complex backgrounds
- Create artistic layouts
- Use rounded blob shapes
- Add slide numbers, footers, or logos
## Best For
Knowledge sharing, concept explanations, productivity content, SaaS presentations
Product demos, SaaS presentations, productivity tool pitches, metrics dashboards, feature walkthroughs, B2B presentations, technical product marketing
@@ -1,30 +1,69 @@
# playful
Bold, energetic, engaging
Bold, energetic style with vibrant colors and dynamic shapes
## Design Aesthetic
Dynamic shapes, vibrant colors, creative layouts
Dynamic shapes, vibrant colors, and creative layouts that capture attention and spark joy. Approachable and fun without sacrificing clarity. Perfect for content that needs to educate while entertaining, making complex topics feel accessible and exciting.
## Color Palette
## Background
- Background: Warm white (#FFFDF7)
- Primary Text: Deep purple (#4A1D96)
- Accent: Vibrant coral (#FF6B6B)
- Color: Warm White (#FFFDF7), soft and inviting
- Texture: Light subtle pattern or clean gradient
## Typography
- Primary Font: Poppins (headlines)
- Secondary Font: Nunito (body)
### Primary Font (Headlines)
Bold rounded sans-serif (Poppins, Nunito Bold, or similar). Friendly, modern, and highly readable. Conveys energy and approachability with geometric character.
### Secondary Font (Body)
Rounded sans-serif (Nunito style) for body text. Soft edges create warmth and readability, complementing the playful aesthetic.
## Color Palette
| Role | Color | Hex | Usage |
|------|-------|-----|-------|
| Background | Warm White | #FFFDF7 | Main slide background |
| Primary Text | Deep Purple | #4A1D96 | Headlines, key text |
| Secondary Text | Dark Purple | #6B3FA0 | Body text |
| Primary Accent | Vibrant Coral | #FF6B6B | Primary highlights |
| Secondary Accent | Electric Teal | #4ECDC4 | Secondary emphasis |
| Tertiary | Sunshine Yellow | #FFE66D | Tertiary accent |
| Success | Mint Green | #95E1C3 | Positive elements |
| Energy | Hot Pink | #FF69B4 | High energy callouts |
## Visual Elements
- Rounded shapes
- Gradients
- Illustrations
- Dynamic compositions
- Bright pops
- Rounded shapes and soft corners everywhere
- Playful gradients (subtle, not overwhelming)
- Character illustrations and mascots
- Dynamic asymmetrical compositions
- Bright color pops and accents
- Confetti and celebration elements
- Emoji-style icons
- Bubble and cloud shapes
## Style Rules
### Do
- Use bold, contrasting colors
- Include rounded, friendly shapes
- Create dynamic, engaging layouts
- Add implied motion and energy
- Keep typography large and readable
### Don't
- Use sharp, aggressive shapes
- Apply dark or somber colors
- Create static, boring layouts
- Overload with too many elements
- Make it look unprofessional
- Add slide numbers, footers, or logos
## Best For
Creative pitches, educational content, workshops
Creative pitches, educational workshops, training materials, community presentations, product launches, children's content, team building, startup culture
@@ -1,43 +0,0 @@
# retro-flat
Flat vector illustration with retro palette
## Design Aesthetic
Flat vector with clear black outlines (monoline), geometric simplification, toy-model cuteness, NO gradients
## Color Palette
- Background: Off-white (#F5F0E6), soft cream
- Primary Text: Black (#000000) (clean outlines)
- Accent: Retro muted palette
- Dusty pink (#E8B4B8)
- Sage green (#A8C686)
- Mustard yellow (#E8B84A)
- Muted blue (#7BA7BC)
## Typography
- Primary Font: Bold geometric sans-serif with black outline
- Secondary Font: Clean sans-serif
## Visual Elements
- Clear black monoline outlines (uniform width)
- Flat color fills with NO gradients
- Geometric simplified shapes
- Toy-like proportions
- Minimal shadows
- Vintage badge style
- Halftone dots (optional)
## Style Rules
- MUST use clear black outlines on all elements
- NO 3D effects
- NO gradients
- Simple flat color blocks only
## Best For
Tutorials, explainers, product introductions, educational content
@@ -0,0 +1,66 @@
# sketch-notes
Soft hand-drawn illustration style with fresh, refined minimalist editorial aesthetic
## Design Aesthetic
Illustration or hand-drawn feel with soft, relaxed brush strokes. Fresh, refined overall style with minimalist editorial approach. Emphasis on precision, clarity and intelligent elegance while prioritizing warmth, approachability and friendliness.
## Background
- Color: Warm Off-White (#FAF8F0)
- Texture: Subtle paper grain, slightly warm tone to avoid clinical feel
## Typography
### Primary Font (Headlines)
Bold hand-written marker font or cartoon poster font. Slightly uneven baseline for organic feel. Thick strokes with soft edges. Render as hand-drawn letters, not typed text.
### Secondary Font (Body)
Clear handwritten round or hard-pen style mimicking everyday notes. Consistent sizing with slight natural variation. Render as casual handwriting, legible but not mechanical.
## Color Palette
| Role | Color | Hex | Usage |
|------|-------|-----|-------|
| Background | Warm Off-White | #FAF8F0 | Primary background |
| Primary Text | Deep Charcoal | #2C3E50 | Headlines, body text |
| Alt Text | Deep Brown | #4A4A4A | Secondary text elements |
| Accent 1 | Soft Orange | #F4A261 | Highlights, emphasis |
| Accent 2 | Mustard Yellow | #E9C46A | Secondary highlights |
| Accent 3 | Sage Green | #87A96B | Nature, growth concepts |
| Accent 4 | Light Blue | #7EC8E3 | Tech, AI elements |
| Accent 5 | Red Brown | #A0522D | Land, infrastructure |
## Visual Elements
- Connection lines with hand-drawn wavy feel, not perfectly straight
- Conceptual abstract icons illustrating ideas rather than literal scenes
- Color fills don't need to completely fill outlines - preserve hand-painted casual feel
- Simple geometric shapes with rounded corners
- Arrows and pointers with sketchy, informal style
- Doodle-style decorative elements: stars, spirals, underlines
## Style Rules
### Do
- Keep layouts open and well-structured
- Emphasize information hierarchy and readability
- Use hand-drawn quality for all elements
- Allow imperfection - slight wobbles add character
- Layer elements with subtle overlaps
### Don't
- Use perfect geometric shapes
- Create photorealistic elements
- Overcrowd with too many elements
- Use pure white backgrounds
- Add slide numbers, footers, or logos
## Best For
Educational content, knowledge sharing, technical explanations, friendly presentations, tutorials, onboarding materials
@@ -1,29 +1,67 @@
# storytelling
Narrative-driven, cinematic, immersive
Cinematic narrative style with full-bleed visuals and emotional impact
## Design Aesthetic
Full-bleed imagery, dramatic typography, chapter-like flow
Cinematic, full-bleed imagery that creates emotional connection. Story-driven compositions that guide the viewer through a narrative arc. Each slide is a scene in a larger story. Photography and illustration work together to create atmosphere.
## Color Palette
## Background
- Background: Rich charcoal (#2D2D2D) or full-bleed imagery
- Primary Text: Warm white (#FAF9F6)
- Accent: Warm gold (#D4AF37)
- Color: Varies by mood - dark for drama (#0F0F0F), warm for comfort (#FDF8F3)
- Texture: Photographic or illustrated backgrounds, subtle vignetting
## Typography
- Primary Font: Playfair Display (headlines)
- Secondary Font: Lora (body)
### Primary Font (Headlines)
Elegant serif like Playfair Display, Libre Baskerville, or editorial serif. Large, expressive headlines that complement imagery. Can use italic for emotional emphasis.
### Secondary Font (Body)
Clean sans-serif for contrast and readability. Light weight to not compete with imagery. White or dark depending on background.
## Color Palette
| Role | Color | Hex | Usage |
|------|-------|-----|-------|
| Background Dark | Near Black | #0F0F0F | Dramatic scenes |
| Background Warm | Warm Cream | #FDF8F3 | Comfortable scenes |
| Primary Text Light | Off-White | #FAFAFA | Text on dark |
| Primary Text Dark | Deep Charcoal | #1F1F1F | Text on light |
| Accent Warm | Golden | #D4A853 | Warm highlights |
| Accent Cool | Teal | #2DD4BF | Cool accents |
| Overlay | Black 40% | rgba(0,0,0,0.4) | Text legibility |
## Visual Elements
- Full-bleed photos
- Dramatic typography
- Chapter markers
- Emotional imagery
- Full-bleed photographic or illustrated backgrounds
- Cinematic aspect compositions (rule of thirds)
- Text overlays with subtle background darkening
- Atmospheric lighting and mood
- People and human elements when possible
- Emotional color grading
- Subtle motion blur or depth of field effects
- Vignetting to draw focus
## Style Rules
### Do
- Create emotional connection through imagery
- Use photography or illustration as primary element
- Let visuals tell the story
- Create consistent mood throughout deck
- Use text sparingly - images speak
### Don't
- Use stock photo aesthetic
- Crowd slides with text
- Mix conflicting visual moods
- Use clip art or basic shapes
- Add slide numbers, footers, or logos
## Best For
Case studies, journey narratives, brand stories
Case studies, brand stories, narrative presentations, emotional pitches, documentary-style content, origin stories, customer journeys
@@ -1,29 +0,0 @@
# technical
Precise, data-driven, analytical
## Design Aesthetic
Blueprint style, schematic diagrams, grid layouts
## Color Palette
- Background: Dark charcoal (#1A1A2E)
- Primary Text: Light gray (#E8E8E8)
- Accent: Cyan (#00D4FF)
## Typography
- Primary Font: JetBrains Mono (headlines)
- Secondary Font: IBM Plex Sans (body)
## Visual Elements
- Grid overlays
- Circuit patterns
- Data visualizations
- Monospace code blocks
## Best For
Technical documentation, engineering presentations
@@ -0,0 +1,72 @@
# vector-illustration
Flat vector illustration style with clear black outlines and retro soft color palette
## Design Aesthetic
Flat vector illustration with no gradients or 3D effects. Clear, uniform-thickness black outlines on all elements. Geometric simplification reducing complex objects to basic shapes. Toy model aesthetic that's cute, playful, and approachable. Panoramic horizontal compositions work well.
## Background
- Color: Cream Off-White (#F5F0E6)
- Texture: Subtle paper texture, warm nostalgic feel reminiscent of vintage prints
## Typography
### Primary Font (Headlines)
Large, bold retro serif for titles conveying authority and elegance. Think classic advertising posters. Clean letterforms with strong presence.
### Secondary Font (Subtitles)
All-caps sans-serif inside colored rectangular blocks. Label-like appearance. High contrast against block color.
### Body Font
Clean geometric sans-serif for readability. Futura, Avenir, or similar. Consistent weight throughout.
## Color Palette
| Role | Color | Hex | Usage |
|------|-------|-----|-------|
| Background | Cream Off-White | #F5F0E6 | Primary background |
| Outlines | Deep Charcoal | #2D2D2D | All element outlines |
| Primary Text | Black | #1A1A1A | Headlines, body |
| Accent 1 | Coral Red | #E07A5F | Primary accent, warmth |
| Accent 2 | Mint Green | #81B29A | Secondary accent, nature |
| Accent 3 | Mustard Yellow | #F2CC8F | Highlights, energy |
| Accent 4 | Burnt Orange | #D4764A | Tertiary accent |
| Accent 5 | Rock Blue | #577590 | Cool balance, tech |
## Visual Elements
- All objects have closed black outlines (coloring book style)
- Rounded line endings, avoid sharp corners
- Trees simplified to lollipop or triangle shapes
- Buildings simplified to rectangular blocks with grid windows
- 2.5D perspective (isometric-like but more free-form)
- Depth through layering and overlap, not atmospheric perspective
- Decorative geometric elements: radiating lines (sunbursts), pill-shaped clouds, dots, stars
- People as simple geometric figures with minimal facial detail
## Style Rules
### Do
- Maintain consistent outline thickness throughout
- Use soft, vintage color palette
- Simplify all objects to basic geometric shapes
- Create depth through layering
- Add playful decorative elements
### Don't
- Use gradients or realistic shading
- Create photorealistic elements
- Use thin or varying line weights
- Include complex detailed illustrations
- Add slide numbers, footers, or logos
## Best For
Educational presentations, creative proposals, children's content, brand showcases, warm approachable topics, explainer content
@@ -1,32 +1,68 @@
# warm
Cozy, healing, hand-drawn illustration style
Soft gradients and wellness aesthetic with approachable, friendly feeling
## Design Aesthetic
Soft hand-drawn illustrations, cozy and healing atmosphere, gentle curves, watercolor textures
Soft, calming gradients with rounded shapes. Wellness and lifestyle aesthetic that feels approachable and nurturing. Organic flowing forms balanced with clean typography. Warmth and comfort in every element.
## Color Palette
## Background
- Background: Cream gradient (#FFF8E7#FFE4C4)
- Primary Text: Warm brown (#5D4037)
- Accent: Soft coral (#FF8A80), Peachy pink (#FFAB91)
- Color: Soft gradient from Warm Peach (#FDF4F0) to Soft Lavender (#F5F0FF)
- Texture: Subtle organic shapes, soft blurred elements
## Typography
- Primary Font: Rounded hand-drawn style lettering
- Secondary Font: Soft serif hand lettering
### Primary Font (Headlines)
Rounded sans-serif like Nunito, Poppins, or Quicksand. Medium to semi-bold weight. Friendly, approachable letterforms. Generous spacing.
### Secondary Font (Body)
Same family in regular weight, or complementary rounded sans-serif. Comfortable reading size. Warm, inviting tone.
## Color Palette
| Role | Color | Hex | Usage |
|------|-------|-----|-------|
| Background Start | Warm Peach | #FDF4F0 | Gradient start |
| Background End | Soft Lavender | #F5F0FF | Gradient end |
| Primary Text | Warm Charcoal | #3D3D3D | Headlines, body |
| Secondary Text | Soft Gray | #6B6B6B | Supporting text |
| Accent 1 | Coral | #FF8A80 | Primary highlights |
| Accent 2 | Soft Teal | #80CBC4 | Balance, growth |
| Accent 3 | Lavender | #B39DDB | Calm, premium |
| Accent 4 | Soft Yellow | #FFE082 | Energy, joy |
## Visual Elements
- Soft watercolor textures
- Gentle curves
- Cozy illustrations
- Warm lighting
- Hearts and stars
- Plant motifs
- Cute characters
- Soft gradient backgrounds (avoid harsh transitions)
- Organic blob shapes with blurred edges
- Rounded rectangles and pill shapes
- Soft drop shadows (large blur, low opacity)
- Simple line illustrations with rounded endpoints
- Abstract organic patterns
- Floating elements with gentle motion implied
- Layered translucent shapes
## Style Rules
### Do
- Use soft, gentle color transitions
- Keep all corners rounded
- Create sense of calm and space
- Use organic, flowing shapes
- Layer elements with transparency
### Don't
- Use sharp corners or harsh lines
- Create high-contrast jarring combinations
- Use dark or aggressive colors
- Add busy or complex illustrations
- Add slide numbers, footers, or logos
## Best For
Personal growth, wellness, emotional content, lifestyle topics
Wellness content, personal development, lifestyle brands, health and fitness, mindfulness, self-care, coaching, therapy, HR presentations
@@ -0,0 +1,116 @@
import { existsSync, readdirSync, readFileSync } from "fs";
import { join, basename } from "path";
import { PDFDocument, rgb } from "pdf-lib";
interface SlideInfo {
filename: string;
path: string;
index: number;
promptPath?: string;
}
function parseArgs(): { dir: string; output?: string } {
const args = process.argv.slice(2);
let dir = "";
let output: string | undefined;
for (let i = 0; i < args.length; i++) {
if (args[i] === "--output" || args[i] === "-o") {
output = args[++i];
} else if (!args[i].startsWith("-")) {
dir = args[i];
}
}
if (!dir) {
console.error("Usage: bun merge-to-pdf.ts <slide-deck-dir> [--output filename.pdf]");
process.exit(1);
}
return { dir, output };
}
function findSlideImages(dir: string): SlideInfo[] {
if (!existsSync(dir)) {
console.error(`Directory not found: ${dir}`);
process.exit(1);
}
const files = readdirSync(dir);
const slidePattern = /^(\d+)-slide-.*\.(png|jpg|jpeg)$/i;
const promptsDir = join(dir, "prompts");
const hasPrompts = existsSync(promptsDir);
const slides: SlideInfo[] = files
.filter((f) => slidePattern.test(f))
.map((f) => {
const match = f.match(slidePattern);
const baseName = f.replace(/\.(png|jpg|jpeg)$/i, "");
const promptPath = hasPrompts ? join(promptsDir, `${baseName}.md`) : undefined;
return {
filename: f,
path: join(dir, f),
index: parseInt(match![1], 10),
promptPath: promptPath && existsSync(promptPath) ? promptPath : undefined,
};
})
.sort((a, b) => a.index - b.index);
if (slides.length === 0) {
console.error(`No slide images found in: ${dir}`);
console.error("Expected format: 01-slide-*.png, 02-slide-*.png, etc.");
process.exit(1);
}
return slides;
}
async function createPdf(slides: SlideInfo[], outputPath: string) {
const pdfDoc = await PDFDocument.create();
pdfDoc.setAuthor("baoyu-slide-deck");
pdfDoc.setSubject("Generated Slide Deck");
for (const slide of slides) {
const imageData = readFileSync(slide.path);
const ext = slide.filename.toLowerCase();
const image = ext.endsWith(".png")
? await pdfDoc.embedPng(imageData)
: await pdfDoc.embedJpg(imageData);
const { width, height } = image;
const page = pdfDoc.addPage([width, height]);
page.drawImage(image, {
x: 0,
y: 0,
width,
height,
});
console.log(`Added: ${slide.filename}${slide.promptPath ? " (prompt available)" : ""}`);
}
const pdfBytes = await pdfDoc.save();
await Bun.write(outputPath, pdfBytes);
console.log(`\nCreated: ${outputPath}`);
console.log(`Total pages: ${slides.length}`);
}
async function main() {
const { dir, output } = parseArgs();
const slides = findSlideImages(dir);
const dirName = basename(dir) === "slide-deck" ? basename(join(dir, "..")) : basename(dir);
const outputPath = output || join(dir, `${dirName}.pdf`);
console.log(`Found ${slides.length} slides in: ${dir}\n`);
await createPdf(slides, outputPath);
}
main().catch((err) => {
console.error("Error:", err.message);
process.exit(1);
});
@@ -0,0 +1,137 @@
import { existsSync, readdirSync, readFileSync } from "fs";
import { join, basename, extname } from "path";
import PptxGenJS from "pptxgenjs";
interface SlideInfo {
filename: string;
path: string;
index: number;
promptPath?: string;
}
function parseArgs(): { dir: string; output?: string } {
const args = process.argv.slice(2);
let dir = "";
let output: string | undefined;
for (let i = 0; i < args.length; i++) {
if (args[i] === "--output" || args[i] === "-o") {
output = args[++i];
} else if (!args[i].startsWith("-")) {
dir = args[i];
}
}
if (!dir) {
console.error("Usage: bun merge-to-pptx.ts <slide-deck-dir> [--output filename.pptx]");
process.exit(1);
}
return { dir, output };
}
function findSlideImages(dir: string): SlideInfo[] {
if (!existsSync(dir)) {
console.error(`Directory not found: ${dir}`);
process.exit(1);
}
const files = readdirSync(dir);
const slidePattern = /^(\d+)-slide-.*\.(png|jpg|jpeg)$/i;
const promptsDir = join(dir, "prompts");
const hasPrompts = existsSync(promptsDir);
const slides: SlideInfo[] = files
.filter((f) => slidePattern.test(f))
.map((f) => {
const match = f.match(slidePattern);
const baseName = f.replace(/\.(png|jpg|jpeg)$/i, "");
const promptPath = hasPrompts ? join(promptsDir, `${baseName}.md`) : undefined;
return {
filename: f,
path: join(dir, f),
index: parseInt(match![1], 10),
promptPath: promptPath && existsSync(promptPath) ? promptPath : undefined,
};
})
.sort((a, b) => a.index - b.index);
if (slides.length === 0) {
console.error(`No slide images found in: ${dir}`);
console.error("Expected format: 01-slide-*.png, 02-slide-*.png, etc.");
process.exit(1);
}
return slides;
}
function findBasePrompt(): string | undefined {
const scriptDir = import.meta.dir;
const basePromptPath = join(scriptDir, "..", "references", "base-prompt.md");
if (existsSync(basePromptPath)) {
return readFileSync(basePromptPath, "utf-8");
}
return undefined;
}
async function createPptx(slides: SlideInfo[], outputPath: string) {
const pptx = new PptxGenJS();
pptx.layout = "LAYOUT_16x9";
pptx.author = "baoyu-slide-deck";
pptx.subject = "Generated Slide Deck";
const basePrompt = findBasePrompt();
let notesCount = 0;
for (const slide of slides) {
const s = pptx.addSlide();
const imageData = readFileSync(slide.path);
const base64 = imageData.toString("base64");
const ext = extname(slide.filename).toLowerCase().replace(".", "");
const mimeType = ext === "png" ? "image/png" : "image/jpeg";
s.addImage({
data: `data:${mimeType};base64,${base64}`,
x: 0,
y: 0,
w: "100%",
h: "100%",
sizing: { type: "cover", w: "100%", h: "100%" },
});
if (slide.promptPath) {
const slidePrompt = readFileSync(slide.promptPath, "utf-8");
const fullNotes = basePrompt ? `${basePrompt}\n\n---\n\n${slidePrompt}` : slidePrompt;
s.addNotes(fullNotes);
notesCount++;
}
console.log(`Added: ${slide.filename}${slide.promptPath ? " (with notes)" : ""}`);
}
await pptx.writeFile({ fileName: outputPath });
console.log(`\nCreated: ${outputPath}`);
console.log(`Total slides: ${slides.length}`);
if (notesCount > 0) {
console.log(`Slides with notes: ${notesCount}${basePrompt ? " (includes base prompt)" : ""}`);
}
}
async function main() {
const { dir, output } = parseArgs();
const slides = findSlideImages(dir);
const dirName = basename(dir) === "slide-deck" ? basename(join(dir, "..")) : basename(dir);
const outputPath = output || join(dir, `${dirName}.pptx`);
console.log(`Found ${slides.length} slides in: ${dir}\n`);
await createPptx(slides, outputPath);
}
main().catch((err) => {
console.error("Error:", err.message);
process.exit(1);
});
+76 -9
View File
@@ -132,7 +132,7 @@ posts/ai-future/
└── 03-ending.png
```
### Without Article Path
### Without Article Path (Pasted Content)
Save to `xhs-outputs/YYYY-MM-DD/[topic-slug]/`:
@@ -140,6 +140,7 @@ Save to `xhs-outputs/YYYY-MM-DD/[topic-slug]/`:
xhs-outputs/
└── 2026-01-13/
└── ai-agent-guide/
├── source.md # Saved pasted content
├── outline.md
├── prompts/
│ ├── 01-cover.md
@@ -152,10 +153,17 @@ xhs-outputs/
### Step 1: Analyze Content & Select Style/Layout
1. Read content
2. If `--style` specified, use that style; otherwise auto-select
3. If `--layout` specified, use that layout; otherwise auto-select per image
4. Determine image count based on content complexity:
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 content
3. If `--style` specified, use that style; otherwise auto-select 3 candidates
4. If `--layout` specified, use that layout; otherwise auto-select per image
5. **Language detection**:
- Detect **source language** from content
- Detect **user language** from conversation context
- Note if source_language ≠ user_language (will ask in Step 4)
6. Determine image count based on content complexity:
| Content Type | Image Count |
|-------------|-------------|
@@ -216,7 +224,57 @@ Plan for each image with style and layout specifications:
Save outline as `outline.md`.
### Step 4: Generate Images One by One
### Step 4: Review & Confirm
**Purpose**: Let user confirm all options in a single step before image generation.
**IMPORTANT**: Present ALL options in a single confirmation step using AskUserQuestion. Do NOT interrupt workflow with multiple separate confirmations.
1. **Generate 3 style variants** (if style not specified):
- Analyze content to select 3 most suitable styles
- Generate complete outline for each style variant
- Save as `outline-{style}.md` (e.g., `outline-cute.md`, `outline-notion.md`, `outline-tech.md`)
2. **Determine which questions to ask**:
| Question | When to Ask |
|----------|-------------|
| Style variant | Always (required) |
| Default layout | Always (offer common options) |
| Language | Only if `source_language ≠ user_language` |
3. **Present options** (use AskUserQuestion with all applicable questions):
**Question 1 (Style)** - always:
- Style A (recommended): [style name] - [brief description]
- Style B: [style name] - [brief description]
- Style C: [style name] - [brief description]
- Custom: Provide custom style reference
**Question 2 (Layout)** - always:
- sparse (Recommended for cover) - minimal info, maximum impact
- balanced - standard 3-4 points
- dense - high info density, knowledge cards
- list / comparison / flow - special formats
**Question 3 (Language)** - only if source ≠ user language:
- [Source language] (matches content)
- [User language] (your preference)
**Language handling**:
- If source language = user language: Just inform user (e.g., "Images will be in Chinese")
- If different: Ask which language to use
4. **Apply selection**:
- Copy selected `outline-{style}.md` to `outline.md`
- If custom style provided, generate new outline with that style
- If different language selected, regenerate outline in that language
- User may edit `outline.md` directly for fine-tuning
- If modified, reload outline before proceeding
5. **Proceed only after explicit user confirmation**
### Step 5: Generate Images One by One
For each image, create a prompt file with style and layout specifications.
@@ -261,13 +319,21 @@ Style notes: [style-specific characteristics]
1. Check available image generation skills
2. If multiple skills available, ask user to choose
**Session Management**:
If the image generation skill supports `--sessionId`:
1. Generate a unique session ID at the start (e.g., `xhs-{topic-slug}-{timestamp}`)
2. Use the same session ID for all images in the series
3. This ensures style consistency across all generated images
**Generation Flow**:
1. Call selected image generation skill with prompt file and output path
1. Call selected image generation skill with prompt file, output path, and session ID
2. Confirm generation success
3. Report progress: "Generated X/N"
4. Continue to next
### Step 5: Completion Report
**All prompts are written in the user's confirmed language preference.**
### Step 6: Completion Report
```
Xiaohongshu Infographic Series Complete!
@@ -313,5 +379,6 @@ Outline: outline.md
- Image generation typically takes 10-30 seconds per image
- Auto-retry once on generation failure
- Use cartoon alternatives for sensitive public figures
- Output language matches input content language
- Prompts written in user's confirmed language preference
- Text on images uses confirmed language
- Maintain selected style consistency across all images in series