mirror of
https://github.com/JimLiu/baoyu-skills.git
synced 2026-07-13 22:29:48 +08:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 21a5167b61 | |||
| 4d3957ca06 | |||
| 013c72fce7 | |||
| 40fadcb1f6 | |||
| 499a1ee478 | |||
| fa89eaf2f7 | |||
| 7964e20bc3 | |||
| 45c109ce88 | |||
| 74c08def06 | |||
| 907c8ab852 |
@@ -6,7 +6,7 @@
|
|||||||
},
|
},
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"description": "Skills shared by Baoyu for improving daily work efficiency",
|
"description": "Skills shared by Baoyu for improving daily work efficiency",
|
||||||
"version": "1.24.0"
|
"version": "1.24.4"
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -152,3 +152,8 @@ slide-deck/
|
|||||||
infographic/
|
infographic/
|
||||||
illustrations/
|
illustrations/
|
||||||
comic/
|
comic/
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
@@ -2,6 +2,29 @@
|
|||||||
|
|
||||||
English | [中文](./CHANGELOG.zh.md)
|
English | [中文](./CHANGELOG.zh.md)
|
||||||
|
|
||||||
|
## 1.24.4 - 2026-01-28
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- `baoyu-post-to-x`: fix Apply button click for cover image modal; add retry logic and wait for modal close.
|
||||||
|
|
||||||
|
## 1.24.3 - 2026-01-28
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
- Emphasize updating prompt files before regenerating images in modification workflows (article-illustrator, slide-deck, xhs-images, cover-image, comic).
|
||||||
|
|
||||||
|
## 1.24.2 - 2026-01-28
|
||||||
|
|
||||||
|
### Refactor
|
||||||
|
- `baoyu-image-gen`: default to sequential generation; parallel available on request.
|
||||||
|
|
||||||
|
## 1.24.1 - 2026-01-28
|
||||||
|
|
||||||
|
### Features
|
||||||
|
- `baoyu-image-gen`: add Aliyun Tongyi Wanxiang (DashScope) text-to-image model support (by @JianJang2017).
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
- Add Aliyun text-to-image model configuration to README.
|
||||||
|
|
||||||
## 1.24.0 - 2026-01-27
|
## 1.24.0 - 2026-01-27
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -2,6 +2,29 @@
|
|||||||
|
|
||||||
[English](./CHANGELOG.md) | 中文
|
[English](./CHANGELOG.md) | 中文
|
||||||
|
|
||||||
|
## 1.24.4 - 2026-01-28
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
- `baoyu-post-to-x`:修复封面图上传后 Apply 按钮点击问题;增加重试逻辑并等待弹窗关闭后再继续。
|
||||||
|
|
||||||
|
## 1.24.3 - 2026-01-28
|
||||||
|
|
||||||
|
### 文档
|
||||||
|
- 在修改工作流中强调先更新提示词文件再生成图片(article-illustrator、slide-deck、xhs-images、cover-image、comic)。
|
||||||
|
|
||||||
|
## 1.24.2 - 2026-01-28
|
||||||
|
|
||||||
|
### 重构
|
||||||
|
- `baoyu-image-gen`:默认改为顺序生成图片;并行生成需明确请求。
|
||||||
|
|
||||||
|
## 1.24.1 - 2026-01-28
|
||||||
|
|
||||||
|
### 新功能
|
||||||
|
- `baoyu-image-gen`:新增阿里云通义万象(DashScope)文生图模型支持 (by @JianJang2017)。
|
||||||
|
|
||||||
|
### 文档
|
||||||
|
- README 中新增阿里云文生图模型配置说明。
|
||||||
|
|
||||||
## 1.24.0 - 2026-01-27
|
## 1.24.0 - 2026-01-27
|
||||||
|
|
||||||
### 新功能
|
### 新功能
|
||||||
|
|||||||
@@ -543,7 +543,7 @@ AI-powered generation backends.
|
|||||||
|
|
||||||
#### baoyu-image-gen
|
#### baoyu-image-gen
|
||||||
|
|
||||||
AI SDK-based image generation using official OpenAI and Google APIs. Supports text-to-image, reference images, aspect ratios, and quality presets.
|
AI SDK-based image generation using official OpenAI, Google and DashScope (Aliyun Tongyi Wanxiang) APIs. Supports text-to-image, reference images, aspect ratios, and quality presets.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Basic generation (auto-detect provider)
|
# Basic generation (auto-detect provider)
|
||||||
@@ -558,6 +558,9 @@ AI SDK-based image generation using official OpenAI and Google APIs. Supports te
|
|||||||
# Specific provider
|
# Specific provider
|
||||||
/baoyu-image-gen --prompt "A cat" --image cat.png --provider openai
|
/baoyu-image-gen --prompt "A cat" --image cat.png --provider openai
|
||||||
|
|
||||||
|
# DashScope (Aliyun Tongyi Wanxiang)
|
||||||
|
/baoyu-image-gen --prompt "一只可爱的猫" --image cat.png --provider dashscope
|
||||||
|
|
||||||
# With reference images (Google multimodal only)
|
# With reference images (Google multimodal only)
|
||||||
/baoyu-image-gen --prompt "Make it blue" --image out.png --ref source.png
|
/baoyu-image-gen --prompt "Make it blue" --image out.png --ref source.png
|
||||||
```
|
```
|
||||||
@@ -568,7 +571,7 @@ AI SDK-based image generation using official OpenAI and Google APIs. Supports te
|
|||||||
| `--prompt`, `-p` | Prompt text |
|
| `--prompt`, `-p` | Prompt text |
|
||||||
| `--promptfiles` | Read prompt from files (concatenated) |
|
| `--promptfiles` | Read prompt from files (concatenated) |
|
||||||
| `--image` | Output image path (required) |
|
| `--image` | Output image path (required) |
|
||||||
| `--provider` | `google` or `openai` (default: google) |
|
| `--provider` | `google`, `openai` or `dashscope` (default: google) |
|
||||||
| `--model`, `-m` | Model ID |
|
| `--model`, `-m` | Model ID |
|
||||||
| `--ar` | Aspect ratio (e.g., `16:9`, `1:1`, `4:3`) |
|
| `--ar` | Aspect ratio (e.g., `16:9`, `1:1`, `4:3`) |
|
||||||
| `--size` | Size (e.g., `1024x1024`) |
|
| `--size` | Size (e.g., `1024x1024`) |
|
||||||
@@ -580,15 +583,18 @@ AI SDK-based image generation using official OpenAI and Google APIs. Supports te
|
|||||||
|----------|-------------|---------|
|
|----------|-------------|---------|
|
||||||
| `OPENAI_API_KEY` | OpenAI API key | - |
|
| `OPENAI_API_KEY` | OpenAI API key | - |
|
||||||
| `GOOGLE_API_KEY` | Google API key | - |
|
| `GOOGLE_API_KEY` | Google API key | - |
|
||||||
|
| `DASHSCOPE_API_KEY` | DashScope API key (Aliyun) | - |
|
||||||
| `OPENAI_IMAGE_MODEL` | OpenAI model | `gpt-image-1.5` |
|
| `OPENAI_IMAGE_MODEL` | OpenAI model | `gpt-image-1.5` |
|
||||||
| `GOOGLE_IMAGE_MODEL` | Google model | `gemini-3-pro-image-preview` |
|
| `GOOGLE_IMAGE_MODEL` | Google model | `gemini-3-pro-image-preview` |
|
||||||
|
| `DASHSCOPE_IMAGE_MODEL` | DashScope model | `z-image-turbo` |
|
||||||
| `OPENAI_BASE_URL` | Custom OpenAI endpoint | - |
|
| `OPENAI_BASE_URL` | Custom OpenAI endpoint | - |
|
||||||
| `GOOGLE_BASE_URL` | Custom Google endpoint | - |
|
| `GOOGLE_BASE_URL` | Custom Google endpoint | - |
|
||||||
|
| `DASHSCOPE_BASE_URL` | Custom DashScope endpoint | - |
|
||||||
|
|
||||||
**Provider Auto-Selection**:
|
**Provider Auto-Selection**:
|
||||||
1. If `--provider` specified → use it
|
1. If `--provider` specified → use it
|
||||||
2. If only one API key available → use that provider
|
2. If only one API key available → use that provider
|
||||||
3. If both available → default to Google
|
3. If multiple available → default to Google
|
||||||
|
|
||||||
#### baoyu-danger-gemini-web
|
#### baoyu-danger-gemini-web
|
||||||
|
|
||||||
@@ -699,6 +705,11 @@ OPENAI_IMAGE_MODEL=gpt-image-1.5
|
|||||||
GOOGLE_API_KEY=xxx
|
GOOGLE_API_KEY=xxx
|
||||||
GOOGLE_IMAGE_MODEL=gemini-3-pro-image-preview
|
GOOGLE_IMAGE_MODEL=gemini-3-pro-image-preview
|
||||||
# GOOGLE_BASE_URL=https://generativelanguage.googleapis.com/v1beta
|
# GOOGLE_BASE_URL=https://generativelanguage.googleapis.com/v1beta
|
||||||
|
|
||||||
|
# DashScope (Aliyun Tongyi Wanxiang)
|
||||||
|
DASHSCOPE_API_KEY=sk-xxx
|
||||||
|
DASHSCOPE_IMAGE_MODEL=z-image-turbo
|
||||||
|
# DASHSCOPE_BASE_URL=https://dashscope.aliyuncs.com/api/v1
|
||||||
EOF
|
EOF
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
+14
-3
@@ -543,7 +543,7 @@ AI 驱动的生成后端。
|
|||||||
|
|
||||||
#### baoyu-image-gen
|
#### baoyu-image-gen
|
||||||
|
|
||||||
基于 AI SDK 的图像生成,使用官方 OpenAI 和 Google API。支持文生图、参考图、宽高比和质量预设。
|
基于 AI SDK 的图像生成,使用官方 OpenAI、Google 和 DashScope(阿里通义万相)API。支持文生图、参考图、宽高比和质量预设。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 基础生成(自动检测服务商)
|
# 基础生成(自动检测服务商)
|
||||||
@@ -558,6 +558,9 @@ AI 驱动的生成后端。
|
|||||||
# 指定服务商
|
# 指定服务商
|
||||||
/baoyu-image-gen --prompt "一只猫" --image cat.png --provider openai
|
/baoyu-image-gen --prompt "一只猫" --image cat.png --provider openai
|
||||||
|
|
||||||
|
# DashScope(阿里通义万相)
|
||||||
|
/baoyu-image-gen --prompt "一只可爱的猫" --image cat.png --provider dashscope
|
||||||
|
|
||||||
# 带参考图(仅 Google 多模态支持)
|
# 带参考图(仅 Google 多模态支持)
|
||||||
/baoyu-image-gen --prompt "把它变成蓝色" --image out.png --ref source.png
|
/baoyu-image-gen --prompt "把它变成蓝色" --image out.png --ref source.png
|
||||||
```
|
```
|
||||||
@@ -568,7 +571,7 @@ AI 驱动的生成后端。
|
|||||||
| `--prompt`, `-p` | 提示词文本 |
|
| `--prompt`, `-p` | 提示词文本 |
|
||||||
| `--promptfiles` | 从文件读取提示词(多文件拼接) |
|
| `--promptfiles` | 从文件读取提示词(多文件拼接) |
|
||||||
| `--image` | 输出图片路径(必需) |
|
| `--image` | 输出图片路径(必需) |
|
||||||
| `--provider` | `google` 或 `openai`(默认:google) |
|
| `--provider` | `google`、`openai` 或 `dashscope`(默认:google) |
|
||||||
| `--model`, `-m` | 模型 ID |
|
| `--model`, `-m` | 模型 ID |
|
||||||
| `--ar` | 宽高比(如 `16:9`、`1:1`、`4:3`) |
|
| `--ar` | 宽高比(如 `16:9`、`1:1`、`4:3`) |
|
||||||
| `--size` | 尺寸(如 `1024x1024`) |
|
| `--size` | 尺寸(如 `1024x1024`) |
|
||||||
@@ -580,15 +583,18 @@ AI 驱动的生成后端。
|
|||||||
|------|------|--------|
|
|------|------|--------|
|
||||||
| `OPENAI_API_KEY` | OpenAI API 密钥 | - |
|
| `OPENAI_API_KEY` | OpenAI API 密钥 | - |
|
||||||
| `GOOGLE_API_KEY` | Google API 密钥 | - |
|
| `GOOGLE_API_KEY` | Google API 密钥 | - |
|
||||||
|
| `DASHSCOPE_API_KEY` | DashScope API 密钥(阿里云) | - |
|
||||||
| `OPENAI_IMAGE_MODEL` | OpenAI 模型 | `gpt-image-1.5` |
|
| `OPENAI_IMAGE_MODEL` | OpenAI 模型 | `gpt-image-1.5` |
|
||||||
| `GOOGLE_IMAGE_MODEL` | Google 模型 | `gemini-3-pro-image-preview` |
|
| `GOOGLE_IMAGE_MODEL` | Google 模型 | `gemini-3-pro-image-preview` |
|
||||||
|
| `DASHSCOPE_IMAGE_MODEL` | DashScope 模型 | `z-image-turbo` |
|
||||||
| `OPENAI_BASE_URL` | 自定义 OpenAI 端点 | - |
|
| `OPENAI_BASE_URL` | 自定义 OpenAI 端点 | - |
|
||||||
| `GOOGLE_BASE_URL` | 自定义 Google 端点 | - |
|
| `GOOGLE_BASE_URL` | 自定义 Google 端点 | - |
|
||||||
|
| `DASHSCOPE_BASE_URL` | 自定义 DashScope 端点 | - |
|
||||||
|
|
||||||
**服务商自动选择**:
|
**服务商自动选择**:
|
||||||
1. 如果指定了 `--provider` → 使用指定的
|
1. 如果指定了 `--provider` → 使用指定的
|
||||||
2. 如果只有一个 API 密钥 → 使用对应服务商
|
2. 如果只有一个 API 密钥 → 使用对应服务商
|
||||||
3. 如果两个都有 → 默认使用 Google
|
3. 如果多个可用 → 默认使用 Google
|
||||||
|
|
||||||
#### baoyu-danger-gemini-web
|
#### baoyu-danger-gemini-web
|
||||||
|
|
||||||
@@ -699,6 +705,11 @@ OPENAI_IMAGE_MODEL=gpt-image-1.5
|
|||||||
GOOGLE_API_KEY=xxx
|
GOOGLE_API_KEY=xxx
|
||||||
GOOGLE_IMAGE_MODEL=gemini-3-pro-image-preview
|
GOOGLE_IMAGE_MODEL=gemini-3-pro-image-preview
|
||||||
# GOOGLE_BASE_URL=https://generativelanguage.googleapis.com/v1beta
|
# GOOGLE_BASE_URL=https://generativelanguage.googleapis.com/v1beta
|
||||||
|
|
||||||
|
# DashScope(阿里通义万相)
|
||||||
|
DASHSCOPE_API_KEY=sk-xxx
|
||||||
|
DASHSCOPE_IMAGE_MODEL=z-image-turbo
|
||||||
|
# DASHSCOPE_BASE_URL=https://dashscope.aliyuncs.com/api/v1
|
||||||
EOF
|
EOF
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -303,10 +303,15 @@ illustrations/{topic-slug}/
|
|||||||
|
|
||||||
| Action | Steps |
|
| Action | Steps |
|
||||||
|--------|-------|
|
|--------|-------|
|
||||||
| **Edit** | Update prompt → Regenerate → Update reference |
|
| **Edit** | **Update prompt file FIRST** → Regenerate → Update reference |
|
||||||
| **Add** | Identify position → Create prompt → Generate → Update outline → Insert |
|
| **Add** | Identify position → Create prompt → Generate → Update outline → Insert |
|
||||||
| **Delete** | Delete files → Remove reference → Update outline |
|
| **Delete** | Delete files → Remove reference → Update outline |
|
||||||
|
|
||||||
|
**IMPORTANT**: When updating illustrations, ALWAYS update the prompt file (`prompts/illustration-{slug}.md`) FIRST before regenerating. This ensures:
|
||||||
|
1. Changes are documented and reproducible
|
||||||
|
2. The prompt reflects the new requirements
|
||||||
|
3. Future regeneration uses the correct prompt
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
| File | Content |
|
| File | Content |
|
||||||
|
|||||||
@@ -259,6 +259,16 @@ Schema: [references/config/preferences-schema.md](references/config/preferences-
|
|||||||
- [config/first-time-setup.md](references/config/first-time-setup.md) - First-time setup
|
- [config/first-time-setup.md](references/config/first-time-setup.md) - First-time setup
|
||||||
- [config/watermark-guide.md](references/config/watermark-guide.md) - Watermark configuration
|
- [config/watermark-guide.md](references/config/watermark-guide.md) - Watermark configuration
|
||||||
|
|
||||||
|
## Page Modification
|
||||||
|
|
||||||
|
| Action | Steps |
|
||||||
|
|--------|-------|
|
||||||
|
| **Edit** | **Update prompt file FIRST** → `--regenerate N` → Regenerate PDF |
|
||||||
|
| **Add** | Create prompt at position → Generate with character ref → Renumber subsequent → Update storyboard → Regenerate PDF |
|
||||||
|
| **Delete** | Remove files → Renumber subsequent → Update storyboard → Regenerate PDF |
|
||||||
|
|
||||||
|
**IMPORTANT**: When updating pages, ALWAYS update the prompt file (`prompts/NN-{cover|page}-[slug].md`) FIRST before regenerating. This ensures changes are documented and reproducible.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- Image generation: 10-30 seconds per page
|
- Image generation: 10-30 seconds per page
|
||||||
|
|||||||
@@ -258,8 +258,10 @@ Files:
|
|||||||
|
|
||||||
| Action | Steps |
|
| Action | Steps |
|
||||||
|--------|-------|
|
|--------|-------|
|
||||||
| **Regenerate** | Backup existing → Update prompt → Regenerate with same settings |
|
| **Regenerate** | Backup existing → **Update prompt file FIRST** → Regenerate with same settings |
|
||||||
| **Change dimension** | Backup existing → Confirm new value → Update prompt → Regenerate |
|
| **Change dimension** | Backup existing → Confirm new value → **Update prompt file FIRST** → Regenerate |
|
||||||
|
|
||||||
|
**IMPORTANT**: When regenerating, ALWAYS update the prompt file (`prompts/cover.md`) FIRST before regenerating. This ensures changes are documented and reproducible.
|
||||||
|
|
||||||
All modifications automatically backup existing `cover.png` before regenerating.
|
All modifications automatically backup existing `cover.png` before regenerating.
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
name: baoyu-image-gen
|
name: baoyu-image-gen
|
||||||
description: AI image generation with OpenAI and Google APIs. Supports text-to-image, reference images, aspect ratios, and parallel generation (recommended 4 concurrent subagents). Use when user asks to generate, create, or draw images.
|
description: AI image generation with OpenAI, Google and DashScope APIs. Supports text-to-image, reference images, aspect ratios. Sequential by default; parallel generation available on request. Use when user asks to generate, create, or draw images.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Image Generation (AI SDK)
|
# Image Generation (AI SDK)
|
||||||
|
|
||||||
Official API-based image generation. Supports OpenAI and Google providers.
|
Official API-based image generation. Supports OpenAI, Google and DashScope (阿里通义万象) providers.
|
||||||
|
|
||||||
## Script Directory
|
## Script Directory
|
||||||
|
|
||||||
@@ -63,6 +63,9 @@ npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "Make blue" --image out.png --r
|
|||||||
|
|
||||||
# Specific provider
|
# Specific provider
|
||||||
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A cat" --image out.png --provider openai
|
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A cat" --image out.png --provider openai
|
||||||
|
|
||||||
|
# DashScope (阿里通义万象)
|
||||||
|
npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "一只可爱的猫" --image out.png --provider dashscope
|
||||||
```
|
```
|
||||||
|
|
||||||
## Options
|
## Options
|
||||||
@@ -72,7 +75,7 @@ npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A cat" --image out.png --provi
|
|||||||
| `--prompt <text>`, `-p` | Prompt text |
|
| `--prompt <text>`, `-p` | Prompt text |
|
||||||
| `--promptfiles <files...>` | Read prompt from files (concatenated) |
|
| `--promptfiles <files...>` | Read prompt from files (concatenated) |
|
||||||
| `--image <path>` | Output image path (required) |
|
| `--image <path>` | Output image path (required) |
|
||||||
| `--provider google\|openai` | Force provider (default: google) |
|
| `--provider google\|openai\|dashscope` | Force provider (default: google) |
|
||||||
| `--model <id>`, `-m` | Model ID |
|
| `--model <id>`, `-m` | Model ID |
|
||||||
| `--ar <ratio>` | Aspect ratio (e.g., `16:9`, `1:1`, `4:3`) |
|
| `--ar <ratio>` | Aspect ratio (e.g., `16:9`, `1:1`, `4:3`) |
|
||||||
| `--size <WxH>` | Size (e.g., `1024x1024`) |
|
| `--size <WxH>` | Size (e.g., `1024x1024`) |
|
||||||
@@ -88,10 +91,13 @@ npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A cat" --image out.png --provi
|
|||||||
|----------|-------------|
|
|----------|-------------|
|
||||||
| `OPENAI_API_KEY` | OpenAI API key |
|
| `OPENAI_API_KEY` | OpenAI API key |
|
||||||
| `GOOGLE_API_KEY` | Google API key |
|
| `GOOGLE_API_KEY` | Google API key |
|
||||||
|
| `DASHSCOPE_API_KEY` | DashScope API key (阿里云) |
|
||||||
| `OPENAI_IMAGE_MODEL` | OpenAI model override |
|
| `OPENAI_IMAGE_MODEL` | OpenAI model override |
|
||||||
| `GOOGLE_IMAGE_MODEL` | Google model override |
|
| `GOOGLE_IMAGE_MODEL` | Google model override |
|
||||||
|
| `DASHSCOPE_IMAGE_MODEL` | DashScope model override (default: z-image-turbo) |
|
||||||
| `OPENAI_BASE_URL` | Custom OpenAI endpoint |
|
| `OPENAI_BASE_URL` | Custom OpenAI endpoint |
|
||||||
| `GOOGLE_BASE_URL` | Custom Google endpoint |
|
| `GOOGLE_BASE_URL` | Custom Google endpoint |
|
||||||
|
| `DASHSCOPE_BASE_URL` | Custom DashScope endpoint |
|
||||||
|
|
||||||
**Load Priority**: CLI args > env vars > `<cwd>/.baoyu-skills/.env` > `~/.baoyu-skills/.env`
|
**Load Priority**: CLI args > env vars > `<cwd>/.baoyu-skills/.env` > `~/.baoyu-skills/.env`
|
||||||
|
|
||||||
@@ -99,7 +105,7 @@ npx -y bun ${SKILL_DIR}/scripts/main.ts --prompt "A cat" --image out.png --provi
|
|||||||
|
|
||||||
1. `--provider` specified → use it
|
1. `--provider` specified → use it
|
||||||
2. Only one API key available → use that provider
|
2. Only one API key available → use that provider
|
||||||
3. Both available → default to Google
|
3. Multiple available → default to Google
|
||||||
|
|
||||||
## Quality Presets
|
## Quality Presets
|
||||||
|
|
||||||
@@ -118,25 +124,32 @@ Supported: `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `2.35:1`
|
|||||||
- Google Imagen: uses `aspectRatio` parameter
|
- Google Imagen: uses `aspectRatio` parameter
|
||||||
- OpenAI: maps to closest supported size
|
- OpenAI: maps to closest supported size
|
||||||
|
|
||||||
## Parallel Generation
|
## Generation Mode
|
||||||
|
|
||||||
Supports concurrent image generation via background subagents for batch operations.
|
**Default**: Sequential generation (one image at a time). This ensures stable output and easier debugging.
|
||||||
|
|
||||||
|
**Parallel Generation**: Only use when user explicitly requests parallel/concurrent generation.
|
||||||
|
|
||||||
|
| Mode | When to Use |
|
||||||
|
|------|-------------|
|
||||||
|
| Sequential (default) | Normal usage, single images, small batches |
|
||||||
|
| Parallel | User explicitly requests, large batches (10+) |
|
||||||
|
|
||||||
|
**Parallel Settings** (when requested):
|
||||||
|
|
||||||
| Setting | Value |
|
| Setting | Value |
|
||||||
|---------|-------|
|
|---------|-------|
|
||||||
| Recommended concurrency | 4 subagents |
|
| Recommended concurrency | 4 subagents |
|
||||||
| Max concurrency | 8 subagents |
|
| Max concurrency | 8 subagents |
|
||||||
| Use case | Batch generation (slides, comics, infographics) |
|
| Use case | Large batch generation when user requests parallel |
|
||||||
|
|
||||||
**Agent Implementation**:
|
**Agent Implementation** (parallel mode only):
|
||||||
```
|
```
|
||||||
# Launch multiple generations in parallel using Task tool
|
# Launch multiple generations in parallel using Task tool
|
||||||
# Each Task runs as background subagent with run_in_background=true
|
# Each Task runs as background subagent with run_in_background=true
|
||||||
# Collect results via TaskOutput when all complete
|
# Collect results via TaskOutput when all complete
|
||||||
```
|
```
|
||||||
|
|
||||||
**Best Practice**: When generating 4+ images, spawn background subagents (recommended 4 concurrent) instead of sequential execution.
|
|
||||||
|
|
||||||
## Error Handling
|
## Error Handling
|
||||||
|
|
||||||
- Missing API key → error with setup instructions
|
- Missing API key → error with setup instructions
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ Options:
|
|||||||
-p, --prompt <text> Prompt text
|
-p, --prompt <text> Prompt text
|
||||||
--promptfiles <files...> Read prompt from files (concatenated)
|
--promptfiles <files...> Read prompt from files (concatenated)
|
||||||
--image <path> Output image path (required)
|
--image <path> Output image path (required)
|
||||||
--provider google|openai Force provider (auto-detect by default)
|
--provider google|openai|dashscope Force provider (auto-detect by default)
|
||||||
-m, --model <id> Model ID
|
-m, --model <id> Model ID
|
||||||
--ar <ratio> Aspect ratio (e.g., 16:9, 1:1, 4:3)
|
--ar <ratio> Aspect ratio (e.g., 16:9, 1:1, 4:3)
|
||||||
--size <WxH> Size (e.g., 1024x1024)
|
--size <WxH> Size (e.g., 1024x1024)
|
||||||
@@ -29,10 +29,13 @@ Environment variables:
|
|||||||
OPENAI_API_KEY OpenAI API key
|
OPENAI_API_KEY OpenAI API key
|
||||||
GOOGLE_API_KEY Google API key
|
GOOGLE_API_KEY Google API key
|
||||||
GEMINI_API_KEY Gemini API key (alias for GOOGLE_API_KEY)
|
GEMINI_API_KEY Gemini API key (alias for GOOGLE_API_KEY)
|
||||||
|
DASHSCOPE_API_KEY DashScope API key (阿里云通义万象)
|
||||||
OPENAI_IMAGE_MODEL Default OpenAI model (gpt-image-1.5)
|
OPENAI_IMAGE_MODEL Default OpenAI model (gpt-image-1.5)
|
||||||
GOOGLE_IMAGE_MODEL Default Google model (gemini-3-pro-image-preview)
|
GOOGLE_IMAGE_MODEL Default Google model (gemini-3-pro-image-preview)
|
||||||
|
DASHSCOPE_IMAGE_MODEL Default DashScope model (z-image-turbo)
|
||||||
OPENAI_BASE_URL Custom OpenAI endpoint
|
OPENAI_BASE_URL Custom OpenAI endpoint
|
||||||
GOOGLE_BASE_URL Custom Google endpoint
|
GOOGLE_BASE_URL Custom Google endpoint
|
||||||
|
DASHSCOPE_BASE_URL Custom DashScope endpoint
|
||||||
|
|
||||||
Env file load order: CLI args > process.env > <cwd>/.baoyu-skills/.env > ~/.baoyu-skills/.env`);
|
Env file load order: CLI args > process.env > <cwd>/.baoyu-skills/.env > ~/.baoyu-skills/.env`);
|
||||||
}
|
}
|
||||||
@@ -105,7 +108,7 @@ function parseArgs(argv: string[]): CliArgs {
|
|||||||
|
|
||||||
if (a === "--provider") {
|
if (a === "--provider") {
|
||||||
const v = argv[++i];
|
const v = argv[++i];
|
||||||
if (v !== "google" && v !== "openai") throw new Error(`Invalid provider: ${v}`);
|
if (v !== "google" && v !== "openai" && v !== "dashscope") throw new Error(`Invalid provider: ${v}`);
|
||||||
out.provider = v;
|
out.provider = v;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -243,13 +246,15 @@ function detectProvider(args: CliArgs): Provider {
|
|||||||
|
|
||||||
const hasGoogle = !!(process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY);
|
const hasGoogle = !!(process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY);
|
||||||
const hasOpenai = !!process.env.OPENAI_API_KEY;
|
const hasOpenai = !!process.env.OPENAI_API_KEY;
|
||||||
|
const hasDashscope = !!process.env.DASHSCOPE_API_KEY;
|
||||||
|
|
||||||
if (hasGoogle && !hasOpenai) return "google";
|
const available = [hasGoogle && "google", hasOpenai && "openai", hasDashscope && "dashscope"].filter(Boolean) as Provider[];
|
||||||
if (hasOpenai && !hasGoogle) return "openai";
|
|
||||||
if (hasGoogle && hasOpenai) return "google";
|
if (available.length === 1) return available[0]!;
|
||||||
|
if (available.length > 1) return available[0]!;
|
||||||
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"No API key found. Set GOOGLE_API_KEY, GEMINI_API_KEY, or OPENAI_API_KEY.\n" +
|
"No API key found. Set GOOGLE_API_KEY, GEMINI_API_KEY, OPENAI_API_KEY, or DASHSCOPE_API_KEY.\n" +
|
||||||
"Create ~/.baoyu-skills/.env or <cwd>/.baoyu-skills/.env with your keys."
|
"Create ~/.baoyu-skills/.env or <cwd>/.baoyu-skills/.env with your keys."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -263,6 +268,9 @@ async function loadProviderModule(provider: Provider): Promise<ProviderModule> {
|
|||||||
if (provider === "google") {
|
if (provider === "google") {
|
||||||
return (await import("./providers/google")) as ProviderModule;
|
return (await import("./providers/google")) as ProviderModule;
|
||||||
}
|
}
|
||||||
|
if (provider === "dashscope") {
|
||||||
|
return (await import("./providers/dashscope")) as ProviderModule;
|
||||||
|
}
|
||||||
return (await import("./providers/openai")) as ProviderModule;
|
return (await import("./providers/openai")) as ProviderModule;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
import type { CliArgs } from "../types";
|
||||||
|
|
||||||
|
export function getDefaultModel(): string {
|
||||||
|
return process.env.DASHSCOPE_IMAGE_MODEL || "z-image-turbo";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getApiKey(): string | null {
|
||||||
|
return process.env.DASHSCOPE_API_KEY || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBaseUrl(): string {
|
||||||
|
const base = process.env.DASHSCOPE_BASE_URL || "https://dashscope.aliyuncs.com";
|
||||||
|
return base.replace(/\/+$/g, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseAspectRatio(ar: string): { width: number; height: number } | null {
|
||||||
|
const match = ar.match(/^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?)$/);
|
||||||
|
if (!match) return null;
|
||||||
|
const w = parseFloat(match[1]!);
|
||||||
|
const h = parseFloat(match[2]!);
|
||||||
|
if (w <= 0 || h <= 0) return null;
|
||||||
|
return { width: w, height: h };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSizeFromAspectRatio(ar: string | null, quality: CliArgs["quality"]): string {
|
||||||
|
const baseSize = quality === "2k" ? 1440 : 1024;
|
||||||
|
|
||||||
|
if (!ar) return `${baseSize}*${baseSize}`;
|
||||||
|
|
||||||
|
const parsed = parseAspectRatio(ar);
|
||||||
|
if (!parsed) return `${baseSize}*${baseSize}`;
|
||||||
|
|
||||||
|
const ratio = parsed.width / parsed.height;
|
||||||
|
|
||||||
|
if (Math.abs(ratio - 1) < 0.1) {
|
||||||
|
return `${baseSize}*${baseSize}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ratio > 1) {
|
||||||
|
const w = Math.round(baseSize * ratio);
|
||||||
|
return `${w}*${baseSize}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const h = Math.round(baseSize / ratio);
|
||||||
|
return `${baseSize}*${h}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSize(size: string): string {
|
||||||
|
return size.replace("x", "*");
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateImage(
|
||||||
|
prompt: string,
|
||||||
|
model: string,
|
||||||
|
args: CliArgs
|
||||||
|
): Promise<Uint8Array> {
|
||||||
|
const apiKey = getApiKey();
|
||||||
|
if (!apiKey) throw new Error("DASHSCOPE_API_KEY is required");
|
||||||
|
|
||||||
|
if (args.referenceImages.length > 0) {
|
||||||
|
console.error("Warning: Reference images not yet supported with DashScope, ignoring.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const size = args.size ? normalizeSize(args.size) : getSizeFromAspectRatio(args.aspectRatio, args.quality);
|
||||||
|
const url = `${getBaseUrl()}/api/v1/services/aigc/multimodal-generation/generation`;
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
model,
|
||||||
|
input: {
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
content: [{ text: prompt }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
prompt_extend: false,
|
||||||
|
size,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(`Generating image with DashScope (${model})...`, { size });
|
||||||
|
|
||||||
|
const res = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${apiKey}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const err = await res.text();
|
||||||
|
throw new Error(`DashScope API error (${res.status}): ${err}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await res.json() as {
|
||||||
|
output?: {
|
||||||
|
result_image?: string;
|
||||||
|
choices?: Array<{
|
||||||
|
message?: {
|
||||||
|
content?: Array<{ image?: string }>;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let imageData: string | null = null;
|
||||||
|
|
||||||
|
if (result.output?.result_image) {
|
||||||
|
imageData = result.output.result_image;
|
||||||
|
} else if (result.output?.choices?.[0]?.message?.content) {
|
||||||
|
const content = result.output.choices[0].message.content;
|
||||||
|
for (const item of content) {
|
||||||
|
if (item.image) {
|
||||||
|
imageData = item.image;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!imageData) {
|
||||||
|
console.error("Response:", JSON.stringify(result, null, 2));
|
||||||
|
throw new Error("No image in response");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageData.startsWith("http://") || imageData.startsWith("https://")) {
|
||||||
|
const imgRes = await fetch(imageData);
|
||||||
|
if (!imgRes.ok) throw new Error("Failed to download image");
|
||||||
|
const buf = await imgRes.arrayBuffer();
|
||||||
|
return new Uint8Array(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Uint8Array.from(Buffer.from(imageData, "base64"));
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export type Provider = "google" | "openai";
|
export type Provider = "google" | "openai" | "dashscope";
|
||||||
export type Quality = "normal" | "2k";
|
export type Quality = "normal" | "2k";
|
||||||
|
|
||||||
export type CliArgs = {
|
export type CliArgs = {
|
||||||
|
|||||||
@@ -229,11 +229,51 @@ export async function publishArticle(options: ArticleOptions): Promise<void> {
|
|||||||
console.log('[x-article] Waiting for Apply button...');
|
console.log('[x-article] Waiting for Apply button...');
|
||||||
const applyFound = await waitForElement('[data-testid="applyButton"]', 15_000);
|
const applyFound = await waitForElement('[data-testid="applyButton"]', 15_000);
|
||||||
if (applyFound) {
|
if (applyFound) {
|
||||||
await cdp.send('Runtime.evaluate', {
|
// Check if modal is present
|
||||||
expression: `document.querySelector('[data-testid="applyButton"]')?.click()`,
|
const isModalOpen = async (): Promise<boolean> => {
|
||||||
}, { sessionId });
|
const result = await cdp!.send<{ result: { value: boolean } }>('Runtime.evaluate', {
|
||||||
console.log('[x-article] Cover image applied');
|
expression: `!!document.querySelector('[role="dialog"][aria-modal="true"]')`,
|
||||||
await sleep(1000);
|
returnByValue: true,
|
||||||
|
}, { sessionId });
|
||||||
|
return result.result.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Click Apply button with retry logic
|
||||||
|
const maxRetries = 3;
|
||||||
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||||
|
console.log(`[x-article] Clicking Apply button (attempt ${attempt}/${maxRetries})...`);
|
||||||
|
|
||||||
|
await cdp.send('Runtime.evaluate', {
|
||||||
|
expression: `document.querySelector('[data-testid="applyButton"]')?.click()`,
|
||||||
|
}, { sessionId });
|
||||||
|
|
||||||
|
// Wait for modal to close (up to 5 seconds per attempt)
|
||||||
|
const closeTimeout = 5000;
|
||||||
|
const checkInterval = 300;
|
||||||
|
const startTime = Date.now();
|
||||||
|
let modalClosed = false;
|
||||||
|
|
||||||
|
while (Date.now() - startTime < closeTimeout) {
|
||||||
|
await sleep(checkInterval);
|
||||||
|
const stillOpen = await isModalOpen();
|
||||||
|
if (!stillOpen) {
|
||||||
|
modalClosed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modalClosed) {
|
||||||
|
console.log('[x-article] Cover image applied, modal closed');
|
||||||
|
await sleep(500);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attempt < maxRetries) {
|
||||||
|
console.log('[x-article] Modal still open, retrying...');
|
||||||
|
} else {
|
||||||
|
console.log('[x-article] Modal did not close after all attempts, continuing anyway...');
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('[x-article] Apply button not found, continuing...');
|
console.log('[x-article] Apply button not found, continuing...');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -626,16 +626,18 @@ Flow:
|
|||||||
|
|
||||||
| Action | Command | Manual Steps |
|
| Action | Command | Manual Steps |
|
||||||
|--------|---------|--------------|
|
|--------|---------|--------------|
|
||||||
| **Edit** | `--regenerate N` | Update prompt → Regenerate image → Regenerate PDF |
|
| **Edit** | `--regenerate N` | **Update prompt file FIRST** → Regenerate image → Regenerate PDF |
|
||||||
| **Add** | Manual | Create prompt → Generate image → Renumber subsequent → Update outline → Regenerate PDF |
|
| **Add** | Manual | Create prompt → Generate image → Renumber subsequent → Update outline → Regenerate PDF |
|
||||||
| **Delete** | Manual | Remove files → Renumber subsequent → Update outline → Regenerate PDF |
|
| **Delete** | Manual | Remove files → Renumber subsequent → Update outline → Regenerate PDF |
|
||||||
|
|
||||||
### Edit Single Slide
|
### Edit Single Slide
|
||||||
|
|
||||||
1. Update prompt in `prompts/NN-slide-{slug}.md`
|
1. **Update prompt file FIRST** in `prompts/NN-slide-{slug}.md`
|
||||||
2. Run: `/baoyu-slide-deck <dir> --regenerate N`
|
2. Run: `/baoyu-slide-deck <dir> --regenerate N`
|
||||||
3. Or manually regenerate image + PDF
|
3. Or manually regenerate image + PDF
|
||||||
|
|
||||||
|
**IMPORTANT**: When updating slides, ALWAYS update the prompt file (`prompts/NN-slide-{slug}.md`) FIRST before regenerating. This ensures changes are documented and reproducible.
|
||||||
|
|
||||||
### Add New Slide
|
### Add New Slide
|
||||||
|
|
||||||
1. Create prompt at position: `prompts/NN-slide-{new-slug}.md`
|
1. Create prompt at position: `prompts/NN-slide-{new-slug}.md`
|
||||||
|
|||||||
@@ -403,10 +403,12 @@ Files:
|
|||||||
|
|
||||||
| Action | Steps |
|
| Action | Steps |
|
||||||
|--------|-------|
|
|--------|-------|
|
||||||
| **Edit** | Update prompt → Regenerate with same session ID |
|
| **Edit** | **Update prompt file FIRST** → Regenerate with same session ID |
|
||||||
| **Add** | Specify position → Create prompt → Generate → Renumber subsequent files (NN+1) → Update outline |
|
| **Add** | Specify position → Create prompt → Generate → Renumber subsequent files (NN+1) → Update outline |
|
||||||
| **Delete** | Remove files → Renumber subsequent (NN-1) → Update outline |
|
| **Delete** | Remove files → Renumber subsequent (NN-1) → Update outline |
|
||||||
|
|
||||||
|
**IMPORTANT**: When updating images, ALWAYS update the prompt file (`prompts/NN-{type}-[slug].md`) FIRST before regenerating. This ensures changes are documented and reproducible.
|
||||||
|
|
||||||
## Content Breakdown Principles
|
## Content Breakdown Principles
|
||||||
|
|
||||||
1. **Cover (Image 1)**: Hook + visual impact → `sparse` layout
|
1. **Cover (Image 1)**: Hook + visual impact → `sparse` layout
|
||||||
|
|||||||
Reference in New Issue
Block a user