mirror of
https://github.com/JimLiu/baoyu-skills.git
synced 2026-07-12 13:59:47 +08:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 90b2205914 | |||
| 8e111c17b3 | |||
| 80d7675355 | |||
| 538ede2b32 | |||
| 038e67fd9b | |||
| 049462d6dd | |||
| c8042cef0d | |||
| 15508eae43 | |||
| 94eab2de63 | |||
| 126b72ed36 | |||
| 38f4f253df | |||
| 8bc01debac | |||
| c5c54e26da | |||
| 2a0bba6161 | |||
| c44a524fa6 | |||
| 826535abe4 | |||
| fc50f31694 | |||
| 204765a137 | |||
| 4874cd2dae | |||
| b791ee5dc7 | |||
| 450c76d955 | |||
| db33da26e7 | |||
| c7c98ba034 | |||
| 60ab574559 |
@@ -6,7 +6,7 @@
|
||||
},
|
||||
"metadata": {
|
||||
"description": "Skills shared by Baoyu for improving daily work efficiency",
|
||||
"version": "1.89.0"
|
||||
"version": "1.94.0"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
|
||||
@@ -2,6 +2,37 @@
|
||||
|
||||
English | [中文](./CHANGELOG.zh.md)
|
||||
|
||||
## 1.93.0 - 2026-04-08
|
||||
|
||||
### Features
|
||||
- `baoyu-article-illustrator`: add `hand-drawn-edu` preset — flowchart + sketch-notes + macaron combination for hand-drawn educational diagrams
|
||||
|
||||
### Refactor
|
||||
- `baoyu-article-illustrator`: extract palette as independent third dimension in Type × Style × Palette system
|
||||
|
||||
### Fixes
|
||||
- `baoyu-article-illustrator`: add explicit style file loading step in workflow
|
||||
|
||||
## 1.92.0 - 2026-04-08
|
||||
|
||||
### Features
|
||||
- `baoyu-article-illustrator`: add `macaron` style — soft macaron pastel color blocks (blue, mint, lavender, peach) on warm cream background with optional hand-drawn mode; add `edu-visual` preset
|
||||
|
||||
## 1.90.1 - 2026-04-05
|
||||
|
||||
### Fixes
|
||||
- `baoyu-post-to-wechat`: detect actual image format from buffer magic bytes to fix CDN content-type mismatches (e.g. WebP served for .png URLs); treat WebP as PNG-preferred for transparency handling
|
||||
|
||||
## 1.89.1 - 2026-04-01
|
||||
|
||||
### Features
|
||||
- `baoyu-chrome-cdp`: add `gracefulKillChrome` that waits for Chrome to exit and release its port; fix `killChrome` to use `exitCode`/`signalCode` instead of `.killed` for reliable process state detection
|
||||
- `baoyu-fetch`: auto-detect login state before extraction in interaction wait mode
|
||||
|
||||
### Maintenance
|
||||
- Sync vendor baoyu-chrome-cdp across CDP skills
|
||||
- `baoyu-url-to-markdown`: sync vendor baoyu-fetch with login auto-detect
|
||||
|
||||
## 1.89.0 - 2026-03-31
|
||||
|
||||
### Features
|
||||
|
||||
@@ -2,6 +2,37 @@
|
||||
|
||||
[English](./CHANGELOG.md) | 中文
|
||||
|
||||
## 1.93.0 - 2026-04-08
|
||||
|
||||
### 新功能
|
||||
- `baoyu-article-illustrator`:新增 `hand-drawn-edu` 预设 — flowchart + sketch-notes + macaron 组合,用于手绘教育图解
|
||||
|
||||
### 重构
|
||||
- `baoyu-article-illustrator`:将色板(Palette)提取为独立的第三维度,形成 Type × Style × Palette 三维系统
|
||||
|
||||
### 修复
|
||||
- `baoyu-article-illustrator`:在工作流中添加显式的风格文件加载步骤
|
||||
|
||||
## 1.92.0 - 2026-04-08
|
||||
|
||||
### 新功能
|
||||
- `baoyu-article-illustrator`:新增 `macaron` 风格 — 马卡龙柔和色块(浅蓝、浅绿、浅紫、浅橙)配暖白底色,可选手绘模式;新增 `edu-visual` 预设
|
||||
|
||||
## 1.90.1 - 2026-04-05
|
||||
|
||||
### 修复
|
||||
- `baoyu-post-to-wechat`:通过 magic bytes 检测实际图片格式,修复 CDN 返回与 URL 扩展名不一致的 content-type 问题(如 .png URL 实际返回 WebP);WebP 格式按 PNG 策略处理以保留透明度
|
||||
|
||||
## 1.89.1 - 2026-04-01
|
||||
|
||||
### 新功能
|
||||
- `baoyu-chrome-cdp`:新增 `gracefulKillChrome`,等待 Chrome 进程退出并释放端口;修复 `killChrome` 使用 `exitCode`/`signalCode` 替代 `.killed` 以更可靠地检测进程状态
|
||||
- `baoyu-fetch`:在交互等待模式下自动检测登录状态,未登录时提示用户先登录再提取内容
|
||||
|
||||
### 维护
|
||||
- 同步 vendor baoyu-chrome-cdp 至所有 CDP 技能
|
||||
- `baoyu-url-to-markdown`:同步 vendor baoyu-fetch 的登录自动检测功能
|
||||
|
||||
## 1.89.0 - 2026-03-31
|
||||
|
||||
### 新功能
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# CLAUDE.md
|
||||
|
||||
Claude Code marketplace plugin providing AI-powered content generation skills. Version: **1.89.0**.
|
||||
Claude Code marketplace plugin providing AI-powered content generation skills. Version: **1.93.0**.
|
||||
|
||||
## Architecture
|
||||
|
||||
|
||||
@@ -116,6 +116,10 @@ Xiaohongshu (RedNote) infographic series generator. Breaks down content into 1-1
|
||||
|
||||
# Direct content input
|
||||
/baoyu-xhs-images 今日星座运势
|
||||
|
||||
# Non-interactive (skip all confirmations, for scheduled tasks)
|
||||
/baoyu-xhs-images posts/ai-future/article.md --yes
|
||||
/baoyu-xhs-images posts/ai-future/article.md --yes --preset knowledge-card
|
||||
```
|
||||
|
||||
**Styles** (visual aesthetics): `cute` (default), `fresh`, `warm`, `bold`, `minimal`, `retro`, `pop`, `notion`, `chalkboard`
|
||||
@@ -265,7 +269,7 @@ Generate professional infographics with 20 layout types and 17 visual styles. An
|
||||
|
||||
#### baoyu-cover-image
|
||||
|
||||
Generate cover images for articles with 5 dimensions: Type × Palette × Rendering × Text × Mood. Combines 9 color palettes with 6 rendering styles for 54 unique combinations.
|
||||
Generate cover images for articles with 5 dimensions: Type × Palette × Rendering × Text × Mood. Combines 11 color palettes with 7 rendering styles for 77 unique combinations.
|
||||
|
||||
```bash
|
||||
# Auto-select all dimensions based on content
|
||||
@@ -290,8 +294,8 @@ Generate cover images for articles with 5 dimensions: Type × Palette × Renderi
|
||||
|
||||
**Five Dimensions**:
|
||||
- **Type**: `hero`, `conceptual`, `typography`, `metaphor`, `scene`, `minimal`
|
||||
- **Palette**: `warm`, `elegant`, `cool`, `dark`, `earth`, `vivid`, `pastel`, `mono`, `retro`
|
||||
- **Rendering**: `flat-vector`, `hand-drawn`, `painterly`, `digital`, `pixel`, `chalk`
|
||||
- **Palette**: `warm`, `elegant`, `cool`, `dark`, `earth`, `vivid`, `pastel`, `mono`, `retro`, `duotone`, `macaron`
|
||||
- **Rendering**: `flat-vector`, `hand-drawn`, `painterly`, `digital`, `pixel`, `chalk`, `screen-print`
|
||||
- **Text**: `none`, `title-only` (default), `title-subtitle`, `text-rich`
|
||||
- **Mood**: `subtle`, `balanced` (default), `bold`
|
||||
|
||||
@@ -469,20 +473,17 @@ Knowledge comic creator with flexible art style × tone combinations. Creates or
|
||||
|
||||
#### baoyu-article-illustrator
|
||||
|
||||
Smart article illustration skill with Type × Style two-dimension approach. Analyzes article structure, identifies positions requiring visual aids, and generates illustrations.
|
||||
Smart article illustration skill with Type × Style × Palette three-dimension approach. Analyzes article structure, identifies positions requiring visual aids, and generates illustrations.
|
||||
|
||||
```bash
|
||||
# Auto-select type and style based on content
|
||||
/baoyu-article-illustrator path/to/article.md
|
||||
|
||||
# Specify type
|
||||
/baoyu-article-illustrator path/to/article.md --type infographic
|
||||
|
||||
# Specify style
|
||||
/baoyu-article-illustrator path/to/article.md --style blueprint
|
||||
|
||||
# Combine type and style
|
||||
# Specify type and style
|
||||
/baoyu-article-illustrator path/to/article.md --type flowchart --style notion
|
||||
|
||||
# With palette override
|
||||
/baoyu-article-illustrator path/to/article.md --style vector-illustration --palette macaron
|
||||
```
|
||||
|
||||
**Types** (information structure):
|
||||
@@ -496,7 +497,7 @@ Smart article illustration skill with Type × Style two-dimension approach. Anal
|
||||
| `framework` | Concept maps, relationship diagrams | Methodologies, architecture |
|
||||
| `timeline` | Chronological progression | History, project progress |
|
||||
|
||||
**Styles** (visual aesthetics):
|
||||
**Styles** (rendering approach):
|
||||
|
||||
| Style | Description | Best For |
|
||||
|-------|-------------|----------|
|
||||
@@ -509,6 +510,14 @@ Smart article illustration skill with Type × Style two-dimension approach. Anal
|
||||
| `editorial` | Magazine-style infographic | Tech explainers, journalism |
|
||||
| `scientific` | Academic precise diagrams | Biology, chemistry, technical |
|
||||
|
||||
**Palettes** (optional color override):
|
||||
|
||||
| Palette | Description | Best For |
|
||||
|---------|-------------|----------|
|
||||
| `macaron` | Soft pastel blocks (blue, mint, lavender, peach) on warm cream | Educational, knowledge, tutorials |
|
||||
| `warm` | Warm earth tones on soft peach, no cool colors | Brand, product, lifestyle |
|
||||
| `neon` | Vibrant neon on dark purple | Gaming, retro, pop culture |
|
||||
|
||||
**Style Previews**:
|
||||
|
||||
| | | |
|
||||
|
||||
+20
-11
@@ -116,6 +116,10 @@ clawhub install baoyu-markdown-to-html
|
||||
|
||||
# 直接输入内容
|
||||
/baoyu-xhs-images 今日星座运势
|
||||
|
||||
# 非交互模式(跳过所有确认,适用于定时任务)
|
||||
/baoyu-xhs-images posts/ai-future/article.md --yes
|
||||
/baoyu-xhs-images posts/ai-future/article.md --yes --preset knowledge-card
|
||||
```
|
||||
|
||||
**风格**(视觉美学):`cute`(默认)、`fresh`、`warm`、`bold`、`minimal`、`retro`、`pop`、`notion`、`chalkboard`
|
||||
@@ -265,7 +269,7 @@ clawhub install baoyu-markdown-to-html
|
||||
|
||||
#### baoyu-cover-image
|
||||
|
||||
为文章生成封面图,支持五维定制系统:类型 × 配色 × 渲染 × 文字 × 氛围。9 种配色方案与 6 种渲染风格组合,提供 54 种独特效果。
|
||||
为文章生成封面图,支持五维定制系统:类型 × 配色 × 渲染 × 文字 × 氛围。11 种配色方案与 7 种渲染风格组合,提供 77 种独特效果。
|
||||
|
||||
```bash
|
||||
# 根据内容自动选择所有维度
|
||||
@@ -290,8 +294,8 @@ clawhub install baoyu-markdown-to-html
|
||||
|
||||
**五个维度**:
|
||||
- **类型 (Type)**:`hero`、`conceptual`、`typography`、`metaphor`、`scene`、`minimal`
|
||||
- **配色 (Palette)**:`warm`、`elegant`、`cool`、`dark`、`earth`、`vivid`、`pastel`、`mono`、`retro`
|
||||
- **渲染 (Rendering)**:`flat-vector`、`hand-drawn`、`painterly`、`digital`、`pixel`、`chalk`
|
||||
- **配色 (Palette)**:`warm`、`elegant`、`cool`、`dark`、`earth`、`vivid`、`pastel`、`mono`、`retro`、`duotone`、`macaron`
|
||||
- **渲染 (Rendering)**:`flat-vector`、`hand-drawn`、`painterly`、`digital`、`pixel`、`chalk`、`screen-print`
|
||||
- **文字 (Text)**:`none`、`title-only`(默认)、`title-subtitle`、`text-rich`
|
||||
- **氛围 (Mood)**:`subtle`、`balanced`(默认)、`bold`
|
||||
|
||||
@@ -469,20 +473,17 @@ clawhub install baoyu-markdown-to-html
|
||||
|
||||
#### baoyu-article-illustrator
|
||||
|
||||
智能文章插图技能,采用类型 × 风格二维系统。分析文章结构,识别需要视觉辅助的位置,生成插图。
|
||||
智能文章插图技能,采用类型 × 风格 × 色板三维系统。分析文章结构,识别需要视觉辅助的位置,生成插图。
|
||||
|
||||
```bash
|
||||
# 根据内容自动选择类型和风格
|
||||
/baoyu-article-illustrator path/to/article.md
|
||||
|
||||
# 指定类型
|
||||
/baoyu-article-illustrator path/to/article.md --type infographic
|
||||
|
||||
# 指定风格
|
||||
/baoyu-article-illustrator path/to/article.md --style blueprint
|
||||
|
||||
# 组合类型和风格
|
||||
/baoyu-article-illustrator path/to/article.md --type flowchart --style notion
|
||||
|
||||
# 使用色板覆盖
|
||||
/baoyu-article-illustrator path/to/article.md --style vector-illustration --palette macaron
|
||||
```
|
||||
|
||||
**类型**(信息结构):
|
||||
@@ -496,7 +497,7 @@ clawhub install baoyu-markdown-to-html
|
||||
| `framework` | 概念图、关系图 | 方法论、架构 |
|
||||
| `timeline` | 时间线进展 | 历史、项目进度 |
|
||||
|
||||
**风格**(视觉美学):
|
||||
**风格**(渲染手法):
|
||||
|
||||
| 风格 | 描述 | 适用场景 |
|
||||
|------|------|----------|
|
||||
@@ -509,6 +510,14 @@ clawhub install baoyu-markdown-to-html
|
||||
| `editorial` | 杂志风格信息图 | 科技解说、新闻 |
|
||||
| `scientific` | 学术精确图表 | 生物、化学、技术 |
|
||||
|
||||
**色板**(可选配色覆盖):
|
||||
|
||||
| 色板 | 描述 | 适用场景 |
|
||||
|------|------|----------|
|
||||
| `macaron` | 马卡龙柔和色块(浅蓝、浅绿、浅紫、浅橙)暖白底 | 教育、知识分享、教程 |
|
||||
| `warm` | 暖色系(橙、赭石、金)无冷色 | 品牌、产品、生活方式 |
|
||||
| `neon` | 霓虹色(粉、青、黄)深色底 | 游戏、复古、潮流 |
|
||||
|
||||
**风格预览**:
|
||||
|
||||
| | | |
|
||||
|
||||
@@ -15,5 +15,8 @@
|
||||
"turndown": "^7.2.2",
|
||||
"turndown-plugin-gfm": "^1.0.2",
|
||||
"tsx": "^4.20.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"pdf-lib": "^1.17.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
discoverRunningChromeDebugPort,
|
||||
findChromeExecutable,
|
||||
findExistingChromeDebugPort,
|
||||
gracefulKillChrome,
|
||||
getFreePort,
|
||||
openPageSession,
|
||||
resolveSharedChromeProfileDir,
|
||||
@@ -110,6 +111,44 @@ async function stopProcess(child: ChildProcess | null): Promise<void> {
|
||||
await new Promise((resolve) => child.once("exit", resolve));
|
||||
}
|
||||
|
||||
async function startPortHoldingProcess(port: number): Promise<ChildProcess> {
|
||||
const child = spawn(
|
||||
process.execPath,
|
||||
[
|
||||
"-e",
|
||||
`
|
||||
const http = require("node:http");
|
||||
const port = Number(process.argv[1]);
|
||||
const server = http.createServer((_req, res) => res.end("ok"));
|
||||
server.listen(port, "127.0.0.1", () => process.stdout.write("ready\\n"));
|
||||
setInterval(() => {}, 1000);
|
||||
`,
|
||||
String(port),
|
||||
],
|
||||
{
|
||||
stdio: ["ignore", "pipe", "ignore"],
|
||||
},
|
||||
);
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const timer = setTimeout(() => reject(new Error("Timed out waiting for child server to start.")), 3_000);
|
||||
child.once("error", (error) => {
|
||||
clearTimeout(timer);
|
||||
reject(error);
|
||||
});
|
||||
child.stdout?.once("data", () => {
|
||||
clearTimeout(timer);
|
||||
resolve();
|
||||
});
|
||||
child.once("exit", () => {
|
||||
clearTimeout(timer);
|
||||
reject(new Error("Child server exited before becoming ready."));
|
||||
});
|
||||
});
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
test("getFreePort honors a fixed environment override and otherwise allocates a TCP port", async (t) => {
|
||||
useEnv(t, { TEST_FIXED_PORT: "45678" });
|
||||
assert.equal(await getFreePort("TEST_FIXED_PORT"), 45678);
|
||||
@@ -305,3 +344,19 @@ test("waitForChromeDebugPort retries until the debug endpoint becomes available"
|
||||
|
||||
assert.equal(websocketUrl, `ws://127.0.0.1:${port}/devtools/browser/demo`);
|
||||
});
|
||||
|
||||
test("gracefulKillChrome waits for the Chrome process to exit and release its port", async (t) => {
|
||||
const port = await getFreePort();
|
||||
const child = await startPortHoldingProcess(port);
|
||||
t.after(async () => { await stopProcess(child); });
|
||||
|
||||
assert.equal(await waitForChromeDebugPort(port, 1_000).catch(() => null), null);
|
||||
|
||||
await gracefulKillChrome(child, port, 4_000);
|
||||
|
||||
assert.ok(child.exitCode !== null || child.signalCode !== null);
|
||||
assert.equal(
|
||||
await fetch(`http://127.0.0.1:${port}`).then(() => true).catch(() => false),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -478,7 +478,7 @@ export function killChrome(chrome: ChildProcess): void {
|
||||
chrome.kill("SIGTERM");
|
||||
} catch {}
|
||||
setTimeout(() => {
|
||||
if (!chrome.killed) {
|
||||
if (chrome.exitCode === null && chrome.signalCode === null) {
|
||||
try {
|
||||
chrome.kill("SIGKILL");
|
||||
} catch {}
|
||||
@@ -486,6 +486,37 @@ export function killChrome(chrome: ChildProcess): void {
|
||||
}, 2_000).unref?.();
|
||||
}
|
||||
|
||||
export async function gracefulKillChrome(
|
||||
chrome: ChildProcess,
|
||||
port?: number,
|
||||
timeoutMs = 6_000,
|
||||
): Promise<void> {
|
||||
if (chrome.exitCode !== null || chrome.signalCode !== null) return;
|
||||
|
||||
const exitPromise = new Promise<void>((resolve) => {
|
||||
chrome.once("exit", () => resolve());
|
||||
});
|
||||
|
||||
killChrome(chrome);
|
||||
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < timeoutMs) {
|
||||
if (chrome.exitCode !== null || chrome.signalCode !== null) return;
|
||||
if (port !== undefined && !await isPortListening(port, 250)) return;
|
||||
|
||||
const exited = await Promise.race([
|
||||
exitPromise.then(() => true),
|
||||
sleep(100).then(() => false),
|
||||
]);
|
||||
if (exited) return;
|
||||
}
|
||||
|
||||
await Promise.race([
|
||||
exitPromise,
|
||||
sleep(250),
|
||||
]);
|
||||
}
|
||||
|
||||
export async function openPageSession(options: OpenPageSessionOptions): Promise<PageSession> {
|
||||
let targetId: string;
|
||||
let createdTarget = false;
|
||||
|
||||
@@ -399,6 +399,22 @@ export async function runConvertCommand(options: ConvertCommandOptions): Promise
|
||||
if (restored) logger.info(`Restored ${adapter.name} session cookies from sidecar.`);
|
||||
}
|
||||
|
||||
if (options.waitMode === "interaction" && adapter.checkLogin) {
|
||||
await context.browser.goto(url.toString(), options.timeoutMs).catch(() => {});
|
||||
const preLogin = await adapter.checkLogin(context);
|
||||
if (preLogin.state !== "logged_in") {
|
||||
didLogin = true;
|
||||
await waitForInteraction(adapter, context, {
|
||||
type: "wait_for_interaction",
|
||||
kind: "login",
|
||||
provider: preLogin.provider ?? adapter.name,
|
||||
prompt: `Please sign in to ${adapter.name === "x" ? "X" : adapter.name} in the opened Chrome window. Extraction will continue automatically once login is detected.`,
|
||||
reason: preLogin.reason ?? `Not logged in to ${adapter.name}`,
|
||||
requiresVisibleBrowser: true,
|
||||
}, options);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.waitMode === "force") {
|
||||
await context.browser.goto(url.toString(), options.timeoutMs).catch(() => {});
|
||||
await waitForForceResume(adapter, context, options);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: baoyu-article-illustrator
|
||||
description: Analyzes article structure, identifies positions requiring visual aids, generates illustrations with Type × Style two-dimension approach. Use when user asks to "illustrate article", "add images", "generate images for article", or "为文章配图".
|
||||
description: Analyzes article structure, identifies positions requiring visual aids, generates illustrations with Type × Style × Palette three-dimension approach. Use when user asks to "illustrate article", "add images", "generate images for article", or "为文章配图".
|
||||
version: 1.57.0
|
||||
metadata:
|
||||
openclaw:
|
||||
@@ -9,18 +9,19 @@ metadata:
|
||||
|
||||
# Article Illustrator
|
||||
|
||||
Analyze articles, identify illustration positions, generate images with Type × Style consistency.
|
||||
Analyze articles, identify illustration positions, generate images with Type × Style × Palette consistency.
|
||||
|
||||
## Two Dimensions
|
||||
## Three Dimensions
|
||||
|
||||
| Dimension | Controls | Examples |
|
||||
|-----------|----------|----------|
|
||||
| **Type** | Information structure | infographic, scene, flowchart, comparison, framework, timeline |
|
||||
| **Style** | Visual aesthetics | notion, warm, minimal, blueprint, watercolor, elegant |
|
||||
| **Style** | Rendering approach | notion, warm, minimal, blueprint, watercolor, elegant |
|
||||
| **Palette** | Color scheme (optional) | macaron, warm, neon — overrides style's default colors |
|
||||
|
||||
Combine freely: `--type infographic --style blueprint`
|
||||
Combine freely: `--type infographic --style vector-illustration --palette macaron`
|
||||
|
||||
Or use presets: `--preset tech-explainer` → type + style in one flag. See [Style Presets](references/style-presets.md).
|
||||
Or use presets: `--preset edu-visual` → type + style + palette in one flag. See [Style Presets](references/style-presets.md).
|
||||
|
||||
## Types
|
||||
|
||||
@@ -96,13 +97,14 @@ Full procedures: [references/workflow.md](references/workflow.md#step-2-setup--a
|
||||
| **Q1: Preset or Type** | [Recommended preset], [alt preset], or manual: infographic, scene, flowchart, comparison, framework, timeline, mixed |
|
||||
| **Q2: Density** | minimal (1-2), balanced (3-5), per-section (Recommended), rich (6+) |
|
||||
| **Q3: Style** | [Recommended], minimal-flat, sci-fi, hand-drawn, editorial, scene, poster, Other — **skip if preset chosen** |
|
||||
| Q4: Language | When article language ≠ EXTEND.md setting |
|
||||
| Q4: Palette | Default (style colors), macaron, warm, neon — **skip if preset includes palette or preferred_palette set** |
|
||||
| Q5: Language | When article language ≠ EXTEND.md setting |
|
||||
|
||||
Full procedures: [references/workflow.md](references/workflow.md#step-3-confirm-settings-)
|
||||
|
||||
### Step 4: Generate Outline
|
||||
|
||||
Save `outline.md` with frontmatter (type, density, style, image_count) and entries:
|
||||
Save `outline.md` with frontmatter (type, density, style, palette, image_count) and entries:
|
||||
|
||||
```yaml
|
||||
## Illustration 1
|
||||
@@ -137,7 +139,7 @@ Insert `` after paragraphs.
|
||||
|
||||
```
|
||||
Article Illustration Complete!
|
||||
Article: [path] | Type: [type] | Density: [level] | Style: [style]
|
||||
Article: [path] | Type: [type] | Density: [level] | Style: [style] | Palette: [palette or default]
|
||||
Images: X/N generated
|
||||
```
|
||||
|
||||
@@ -180,7 +182,7 @@ When input is **pasted content** (no file path), always uses `illustrations/{top
|
||||
|------|---------|
|
||||
| [references/workflow.md](references/workflow.md) | Detailed procedures |
|
||||
| [references/usage.md](references/usage.md) | Command syntax |
|
||||
| [references/styles.md](references/styles.md) | Style gallery |
|
||||
| [references/style-presets.md](references/style-presets.md) | Preset shortcuts (type + style) |
|
||||
| [references/styles.md](references/styles.md) | Style gallery + Palette gallery |
|
||||
| [references/style-presets.md](references/style-presets.md) | Preset shortcuts (type + style + palette) |
|
||||
| [references/prompt-construction.md](references/prompt-construction.md) | Prompt templates |
|
||||
| [references/config/first-time-setup.md](references/config/first-time-setup.md) | First-time setup |
|
||||
|
||||
@@ -20,6 +20,8 @@ preferred_style:
|
||||
name: null # Built-in or custom style name
|
||||
description: "" # Override/notes
|
||||
|
||||
preferred_palette: null # Built-in palette name (macaron|warm|neon) or null
|
||||
|
||||
language: null # zh|en|ja|ko|auto
|
||||
|
||||
default_output_dir: null # same-dir|illustrations-subdir|independent
|
||||
@@ -47,6 +49,7 @@ custom_styles:
|
||||
| `watermark.position` | enum | bottom-right | Position on image |
|
||||
| `preferred_style.name` | string | null | Style name or null |
|
||||
| `preferred_style.description` | string | "" | Custom notes/override |
|
||||
| `preferred_palette` | string | null | Palette override (macaron, warm, neon, or null) |
|
||||
| `language` | string | null | Output language (null = auto-detect) |
|
||||
| `default_output_dir` | enum | null | Output directory preference (null = ask each time) |
|
||||
| `custom_styles` | array | [] | User-defined styles |
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# macaron
|
||||
|
||||
Soft macaron pastel color blocks on warm cream
|
||||
|
||||
## Background
|
||||
|
||||
- Color: Warm Cream (#F5F0E8)
|
||||
- Texture: Subtle warm paper grain
|
||||
|
||||
## Colors
|
||||
|
||||
| Role | Color | Hex | Usage |
|
||||
|------|-------|-----|-------|
|
||||
| Background | Warm Cream | #F5F0E8 | Primary background |
|
||||
| Primary Text | Deep Charcoal | #2D2D2D | Headlines, main text, outlines |
|
||||
| Macaron Blue | Sky Blue | #A8D8EA | Info block fill, cool-toned zones |
|
||||
| Macaron Mint | Mint Green | #B5E5CF | Info block fill, growth/positive zones |
|
||||
| Macaron Lavender | Lavender | #D5C6E0 | Info block fill, abstract/concept zones |
|
||||
| Macaron Peach | Peach | #FFD5C2 | Info block fill, warm-toned zones |
|
||||
| Accent | Coral Red | #E8655A | Key data, warnings, emphasis |
|
||||
| Muted Text | Warm Gray | #6B6B6B | Secondary annotations, small labels |
|
||||
|
||||
## Accent
|
||||
|
||||
Coral Red (#E8655A) for key data, warnings, and emphasis highlights. Use sparingly — one or two elements per illustration.
|
||||
|
||||
## Best For
|
||||
|
||||
Educational content, knowledge sharing, concept explainers, tutorials, tech summaries, onboarding materials
|
||||
@@ -0,0 +1,29 @@
|
||||
# neon
|
||||
|
||||
Vibrant neon colors on dark backgrounds
|
||||
|
||||
## Background
|
||||
|
||||
- Color: Deep Purple (#2D1B4E)
|
||||
- Texture: Subtle grid pattern or solid dark
|
||||
|
||||
## Colors
|
||||
|
||||
| Role | Color | Hex | Usage |
|
||||
|------|-------|-----|-------|
|
||||
| Background | Deep Purple | #2D1B4E | Primary background |
|
||||
| Alt Background | Dark Teal | #0F4C5C | Alternative sections |
|
||||
| Primary | Hot Pink | #FF1493 | Main accent |
|
||||
| Secondary | Electric Cyan | #00FFFF | Supporting elements |
|
||||
| Tertiary | Neon Yellow | #FFFF00 | Highlights |
|
||||
| Accent 1 | Lime Green | #32CD32 | Energy, success |
|
||||
| Accent 2 | Orange | #FF6B35 | Warmth |
|
||||
| Text | White | #FFFFFF | Text elements |
|
||||
|
||||
## Accent
|
||||
|
||||
Hot Pink (#FF1493) for primary emphasis. High contrast neon-on-dark creates immediate visual impact.
|
||||
|
||||
## Best For
|
||||
|
||||
Gaming, retro tech, 80s/90s nostalgic content, bold editorial, trend and pop culture
|
||||
@@ -0,0 +1,28 @@
|
||||
# warm
|
||||
|
||||
Warm earth tones on soft peach, no cool colors
|
||||
|
||||
## Background
|
||||
|
||||
- Color: Soft Peach (#FFECD2)
|
||||
- Texture: Warm paper texture
|
||||
|
||||
## Colors
|
||||
|
||||
| Role | Color | Hex | Usage |
|
||||
|------|-------|-----|-------|
|
||||
| Background | Soft Peach | #FFECD2 | Primary background |
|
||||
| Outlines | Deep Charcoal | #2D2D2D | All element outlines |
|
||||
| Primary | Warm Orange | #ED8936 | Main accent color |
|
||||
| Secondary | Terracotta | #C05621 | Warm depth |
|
||||
| Tertiary | Golden Yellow | #F6AD55 | Highlights, energy |
|
||||
| Accent | Deep Brown | #744210 | Grounding, anchoring |
|
||||
| Text | Warm Charcoal | #4A4A4A | Text elements |
|
||||
|
||||
## Accent
|
||||
|
||||
Warm Orange (#ED8936) for primary emphasis. Warm-only palette — no cool colors (no green, blue, purple). Modern-retro feel.
|
||||
|
||||
## Best For
|
||||
|
||||
Product showcases, team introductions, feature grids, brand content, personal growth, lifestyle
|
||||
@@ -135,10 +135,10 @@ COLORS: Cream background (#F5F0E6), Coral Red (#E07A5F), Mint Green (#81B29A), M
|
||||
ELEMENTS: Geometric simplified icons, no gradients, playful decorative elements (dots, stars)
|
||||
```
|
||||
|
||||
**Infographic + vector-illustration (warm palette)**:
|
||||
**Infographic + vector-illustration + warm palette**:
|
||||
```
|
||||
Flat vector illustration infographic. Clean black outlines on all elements.
|
||||
Warm-only color palette, no cool colors.
|
||||
PALETTE OVERRIDE (warm): Warm-only color palette, no cool colors.
|
||||
COLORS: Soft Peach background (#FFECD2), Warm Orange (#ED8936),
|
||||
Terracotta (#C05621), Golden Yellow (#F6AD55), Deep Brown (#744210)
|
||||
ELEMENTS: Geometric simplified icons, no gradients, rounded corners,
|
||||
@@ -182,6 +182,17 @@ COLORS: Cream background (#F5F0E6), steps in Coral/Mint/Mustard, black outlines
|
||||
ELEMENTS: Rounded rectangles, thick arrows, simple icons per step
|
||||
```
|
||||
|
||||
**Flowchart + sketch-notes + macaron palette**:
|
||||
```
|
||||
Hand-drawn educational flowchart on warm cream paper. Slight wobble on all lines.
|
||||
PALETTE: macaron — soft pastel color blocks
|
||||
COLORS: Warm Cream background (#F5F0E8), zone fills in Macaron Blue (#A8D8EA),
|
||||
Lavender (#D5C6E0), Mint (#B5E5CF), Coral Red (#E8655A) for emphasis
|
||||
ELEMENTS: Rounded cards with dashed/solid borders, wavy hand-drawn arrows with labels,
|
||||
simple stick-figure characters, doodle decorations (stars, underlines)
|
||||
STYLE: Color fills don't completely fill outlines, hand-drawn lettering, generous white space
|
||||
```
|
||||
|
||||
### Comparison
|
||||
|
||||
```
|
||||
@@ -207,10 +218,10 @@ COLORS: Left side Coral (#E07A5F), Right side Mint (#81B29A), cream background
|
||||
ELEMENTS: Bold icons, black outlines, centered divider line
|
||||
```
|
||||
|
||||
**Comparison + vector-illustration (warm palette)**:
|
||||
**Comparison + vector-illustration + warm palette**:
|
||||
```
|
||||
Flat vector comparison with split layout. Clear visual separation.
|
||||
Warm-only color palette, no cool colors.
|
||||
PALETTE OVERRIDE (warm): Warm-only color palette, no cool colors.
|
||||
COLORS: Left side Warm Orange (#ED8936), Right side Terracotta (#C05621),
|
||||
Soft Peach background (#FFECD2), Deep Brown (#744210) accents
|
||||
ELEMENTS: Bold icons, black outlines, centered divider line
|
||||
@@ -239,10 +250,10 @@ COLORS: Cream background (#F5F0E6), nodes in Coral/Mint/Mustard/Blue, black outl
|
||||
ELEMENTS: Rounded rectangles or circles for nodes, thick connecting lines
|
||||
```
|
||||
|
||||
**Framework + vector-illustration (warm palette)**:
|
||||
**Framework + vector-illustration + warm palette**:
|
||||
```
|
||||
Flat vector framework diagram with geometric nodes and bold connectors.
|
||||
Warm-only color palette, no cool colors.
|
||||
PALETTE OVERRIDE (warm): Warm-only color palette, no cool colors.
|
||||
COLORS: Soft Peach background (#FFECD2), nodes in Warm Orange (#ED8936),
|
||||
Terracotta (#C05621), Golden Yellow (#F6AD55), black outlines
|
||||
ELEMENTS: Rounded rectangles or circles for nodes, thick connecting lines
|
||||
@@ -296,6 +307,39 @@ TEXTURE: Halftone transitions between sides
|
||||
|
||||
---
|
||||
|
||||
## Palette Override
|
||||
|
||||
When a palette is specified (via `--palette` or preset), it overrides the style's default colors:
|
||||
|
||||
1. Read style file → get rendering rules (Visual Elements, Style Rules, line treatment)
|
||||
2. Read palette file (`palettes/<palette>.md`) → get Colors + Background
|
||||
3. Palette Colors **replace** style's default Color Palette in prompt
|
||||
4. Palette Background **replaces** style's Background color (keep style's texture description)
|
||||
5. Build prompt: style rendering instructions + palette colors
|
||||
|
||||
**Prompt frontmatter** includes palette when specified:
|
||||
```yaml
|
||||
---
|
||||
illustration_id: 01
|
||||
type: infographic
|
||||
style: vector-illustration
|
||||
palette: macaron
|
||||
---
|
||||
```
|
||||
|
||||
**Example**: `vector-illustration` + `macaron` palette:
|
||||
```
|
||||
Flat vector illustration infographic. Clean black outlines on all elements.
|
||||
PALETTE: macaron — soft pastel color blocks
|
||||
COLORS: Warm Cream background (#F5F0E8), Macaron Blue (#A8D8EA), Mint (#B5E5CF),
|
||||
Lavender (#D5C6E0), Peach (#FFD5C2), Coral Red (#E8655A) for emphasis
|
||||
ELEMENTS: Geometric simplified icons, no gradients, playful decorative elements
|
||||
```
|
||||
|
||||
When no palette is specified, use the style's built-in Color Palette as before.
|
||||
|
||||
---
|
||||
|
||||
## What to Avoid
|
||||
|
||||
- Vague descriptions ("a nice image")
|
||||
|
||||
@@ -1,52 +1,54 @@
|
||||
# Style Presets
|
||||
|
||||
`--preset X` expands to a type + style combination. Users can override either dimension.
|
||||
`--preset X` expands to a type + style + optional palette combination. Users can override any dimension.
|
||||
|
||||
## By Category
|
||||
|
||||
### Technical & Engineering
|
||||
|
||||
| --preset | Type | Style | Best For |
|
||||
|----------|------|-------|----------|
|
||||
| `tech-explainer` | `infographic` | `blueprint` | API docs, system metrics, technical deep-dives |
|
||||
| `system-design` | `framework` | `blueprint` | Architecture diagrams, system design |
|
||||
| `architecture` | `framework` | `vector-illustration` | Component relationships, module structure |
|
||||
| `science-paper` | `infographic` | `scientific` | Research findings, lab results, academic |
|
||||
| --preset | Type | Style | Palette | Best For |
|
||||
|----------|------|-------|---------|----------|
|
||||
| `tech-explainer` | `infographic` | `blueprint` | — | API docs, system metrics, technical deep-dives |
|
||||
| `system-design` | `framework` | `blueprint` | — | Architecture diagrams, system design |
|
||||
| `architecture` | `framework` | `vector-illustration` | — | Component relationships, module structure |
|
||||
| `science-paper` | `infographic` | `scientific` | — | Research findings, lab results, academic |
|
||||
|
||||
### Knowledge & Education
|
||||
|
||||
| --preset | Type | Style | Best For |
|
||||
|----------|------|-------|----------|
|
||||
| `knowledge-base` | `infographic` | `vector-illustration` | Concept explainers, tutorials, how-to |
|
||||
| `saas-guide` | `infographic` | `notion` | Product guides, SaaS docs, tool walkthroughs |
|
||||
| `tutorial` | `flowchart` | `vector-illustration` | Step-by-step tutorials, setup guides |
|
||||
| `process-flow` | `flowchart` | `notion` | Workflow documentation, onboarding flows |
|
||||
| `warm-knowledge` | `infographic` | `vector-illustration` (warm palette) | Product showcases, team intros, feature cards, brand content |
|
||||
| --preset | Type | Style | Palette | Best For |
|
||||
|----------|------|-------|---------|----------|
|
||||
| `knowledge-base` | `infographic` | `vector-illustration` | — | Concept explainers, tutorials, how-to |
|
||||
| `saas-guide` | `infographic` | `notion` | — | Product guides, SaaS docs, tool walkthroughs |
|
||||
| `tutorial` | `flowchart` | `vector-illustration` | — | Step-by-step tutorials, setup guides |
|
||||
| `process-flow` | `flowchart` | `notion` | — | Workflow documentation, onboarding flows |
|
||||
| `warm-knowledge` | `infographic` | `vector-illustration` | `warm` | Product showcases, team intros, feature cards, brand content |
|
||||
| `edu-visual` | `infographic` | `vector-illustration` | `macaron` | Knowledge summaries, concept explainers, educational articles |
|
||||
| `hand-drawn-edu` | `flowchart` | `sketch-notes` | `macaron` | Hand-drawn educational diagrams, process explainers, onboarding visuals |
|
||||
|
||||
### Data & Analysis
|
||||
|
||||
| --preset | Type | Style | Best For |
|
||||
|----------|------|-------|----------|
|
||||
| `data-report` | `infographic` | `editorial` | Data journalism, metrics reports, dashboards |
|
||||
| `versus` | `comparison` | `vector-illustration` | Tech comparisons, framework shootouts |
|
||||
| `business-compare` | `comparison` | `elegant` | Product evaluations, strategy options |
|
||||
| --preset | Type | Style | Palette | Best For |
|
||||
|----------|------|-------|---------|----------|
|
||||
| `data-report` | `infographic` | `editorial` | — | Data journalism, metrics reports, dashboards |
|
||||
| `versus` | `comparison` | `vector-illustration` | — | Tech comparisons, framework shootouts |
|
||||
| `business-compare` | `comparison` | `elegant` | — | Product evaluations, strategy options |
|
||||
|
||||
### Narrative & Creative
|
||||
|
||||
| --preset | Type | Style | Best For |
|
||||
|----------|------|-------|----------|
|
||||
| `storytelling` | `scene` | `warm` | Personal essays, reflections, growth stories |
|
||||
| `lifestyle` | `scene` | `watercolor` | Travel, wellness, lifestyle, creative |
|
||||
| `history` | `timeline` | `elegant` | Historical overviews, milestones |
|
||||
| `evolution` | `timeline` | `warm` | Progress narratives, growth journeys |
|
||||
| --preset | Type | Style | Palette | Best For |
|
||||
|----------|------|-------|---------|----------|
|
||||
| `storytelling` | `scene` | `warm` | — | Personal essays, reflections, growth stories |
|
||||
| `lifestyle` | `scene` | `watercolor` | — | Travel, wellness, lifestyle, creative |
|
||||
| `history` | `timeline` | `elegant` | — | Historical overviews, milestones |
|
||||
| `evolution` | `timeline` | `warm` | — | Progress narratives, growth journeys |
|
||||
|
||||
### Editorial & Opinion
|
||||
|
||||
| --preset | Type | Style | Best For |
|
||||
|----------|------|-------|----------|
|
||||
| `opinion-piece` | `scene` | `screen-print` | Op-eds, commentary, critical essays |
|
||||
| `editorial-poster` | `comparison` | `screen-print` | Debate, contrasting viewpoints |
|
||||
| `cinematic` | `scene` | `screen-print` | Dramatic narratives, cultural essays |
|
||||
| --preset | Type | Style | Palette | Best For |
|
||||
|----------|------|-------|---------|----------|
|
||||
| `opinion-piece` | `scene` | `screen-print` | — | Op-eds, commentary, critical essays |
|
||||
| `editorial-poster` | `comparison` | `screen-print` | — | Debate, contrasting viewpoints |
|
||||
| `cinematic` | `scene` | `screen-print` | — | Dramatic narratives, cultural essays |
|
||||
|
||||
## Content Type → Preset Recommendations
|
||||
|
||||
@@ -55,7 +57,7 @@ Use this table during Step 3 to recommend presets based on Step 2 content analys
|
||||
| Content Type (Step 2) | Primary Preset | Alternatives |
|
||||
|------------------------|----------------|--------------|
|
||||
| Technical | `tech-explainer` | `system-design`, `architecture` |
|
||||
| Tutorial | `tutorial` | `process-flow`, `knowledge-base` |
|
||||
| Tutorial | `tutorial` | `process-flow`, `knowledge-base`, `edu-visual` |
|
||||
| Methodology / Framework | `system-design` | `architecture`, `process-flow` |
|
||||
| Data / Metrics | `data-report` | `versus`, `tech-explainer` |
|
||||
| Comparison / Review | `versus` | `business-compare`, `editorial-poster` |
|
||||
@@ -64,6 +66,7 @@ Use this table during Step 3 to recommend presets based on Step 2 content analys
|
||||
| Historical / Timeline | `history` | `evolution` |
|
||||
| Academic / Research | `science-paper` | `tech-explainer`, `data-report` |
|
||||
| SaaS / Product | `saas-guide` | `knowledge-base`, `process-flow`, `warm-knowledge` |
|
||||
| Education / Knowledge | `edu-visual` | `knowledge-base`, `tutorial`, `hand-drawn-edu` |
|
||||
|
||||
## Override Examples
|
||||
|
||||
|
||||
@@ -98,12 +98,6 @@ Full specifications: `references/styles/<style>.md`
|
||||
- Modern, professional, highly readable
|
||||
- Perfect for knowledge articles and tutorials
|
||||
|
||||
### infographic + vector-illustration (warm palette)
|
||||
- Same rendering: flat vector, black outlines, geometric simplification
|
||||
- Warm-only palette: terracotta, gold, brown tones on soft peach
|
||||
- Modern-retro feel, approachable and brand-friendly
|
||||
- Best for product showcases, team intros, feature grids, brand content
|
||||
|
||||
### flowchart + vector-illustration
|
||||
- Bold arrows and connectors
|
||||
- Distinct step containers with icons
|
||||
@@ -200,3 +194,28 @@ Full specifications: `references/styles/<style>.md`
|
||||
- Limited color coding (one color per concept level)
|
||||
- Clean silhouette-based iconography
|
||||
- Poster-style hierarchy with bold typography
|
||||
|
||||
---
|
||||
|
||||
## Palette Gallery
|
||||
|
||||
Palettes override a style's default colors. Combine any style with any palette: `--style vector-illustration --palette macaron`.
|
||||
|
||||
| Palette | Description | Best For |
|
||||
|---------|-------------|----------|
|
||||
| `macaron` | Soft pastel blocks (blue, mint, lavender, peach) on warm cream | Educational, knowledge, tutorials |
|
||||
| `warm` | Warm earth tones (orange, terracotta, gold) on soft peach, no cool colors | Brand, product, lifestyle |
|
||||
| `neon` | Vibrant neon (pink, cyan, yellow) on dark purple | Gaming, retro, pop culture |
|
||||
|
||||
Full specifications: `references/palettes/<palette>.md`
|
||||
|
||||
When no palette is specified, the style's built-in Color Palette is used.
|
||||
|
||||
## Palette Override Rules
|
||||
|
||||
1. Read style file → rendering rules (Visual Elements, Style Rules)
|
||||
2. Read palette file → Colors + Background
|
||||
3. Palette colors **replace** style's default Color Palette
|
||||
4. Palette Background **replaces** style's default Background color
|
||||
5. Style's texture description is preserved
|
||||
|
||||
|
||||
@@ -24,26 +24,6 @@ Flat vector illustration with no gradients or 3D effects. Clear, uniform-thickne
|
||||
| Accent 2 | Rock Blue | #577590 | Cool balance |
|
||||
| Text | Black | #1A1A1A | Text elements |
|
||||
|
||||
## Palette Variants
|
||||
|
||||
### Warm Palette
|
||||
|
||||
Use with `--preset warm-knowledge` or specify in prompt. For product showcases, team introductions, feature grids, brand content.
|
||||
|
||||
| Role | Color | Hex | Usage |
|
||||
|------|-------|-----|-------|
|
||||
| Background | Soft Peach | #FFECD2 | Primary background |
|
||||
| Outlines | Deep Charcoal | #2D2D2D | All element outlines |
|
||||
| Primary | Warm Orange | #ED8936 | Main accent color |
|
||||
| Secondary | Terracotta | #C05621 | Warm depth |
|
||||
| Tertiary | Golden Yellow | #F6AD55 | Highlights, energy |
|
||||
| Accent | Deep Brown | #744210 | Grounding, anchoring |
|
||||
| Text | Warm Charcoal | #4A4A4A | Text elements |
|
||||
|
||||
Warm-only palette, no cool colors (no green, blue, purple). Modern-retro feel.
|
||||
|
||||
Default palette includes Mint Green (#81B29A) and Rock Blue (#577590) for colorful educational content. Warm palette uses exclusively warm tones for brand/product/team content.
|
||||
|
||||
## Visual Elements
|
||||
|
||||
- All objects have closed black outlines (coloring book style)
|
||||
|
||||
@@ -211,9 +211,22 @@ If no `preferred_style` (present Core Styles first):
|
||||
| `poster` | screen-print | Opinion, editorial, cultural, cinematic |
|
||||
|
||||
Style selection based on Type × Style compatibility matrix (styles.md).
|
||||
Full specs: `styles/<style>.md`
|
||||
**In Step 5.1**, read `styles/<style>.md` for visual elements and rendering rules.
|
||||
|
||||
### Q4: Image Text Language ⚠️ REQUIRED when article language ≠ EXTEND.md `language`
|
||||
### Q4: Palette (optional)
|
||||
|
||||
If preset did not specify a palette, and the user may benefit from a palette override, offer available palettes:
|
||||
|
||||
- Default (use style's built-in colors) (Recommended)
|
||||
- `macaron` — soft pastel blocks on warm cream
|
||||
- `warm` — warm earth tones, no cool colors
|
||||
- `neon` — vibrant neon on dark backgrounds
|
||||
|
||||
**Skip if**: preset already resolved palette, or `preferred_palette` set in EXTEND.md.
|
||||
|
||||
See Palette Gallery in [styles.md](styles.md#palette-gallery) and full specs in `palettes/<palette>.md`.
|
||||
|
||||
### Q5: Image Text Language ⚠️ REQUIRED when article language ≠ EXTEND.md `language`
|
||||
|
||||
Detect article language from content. If different from EXTEND.md `language` setting, MUST ask:
|
||||
- Article language (match article content) (Recommended)
|
||||
@@ -294,16 +307,18 @@ For each illustration in the outline:
|
||||
style: custom-flat-vector
|
||||
---
|
||||
```
|
||||
3. **Follow type-specific template** from [prompt-construction.md](prompt-construction.md)
|
||||
4. **Prompt quality requirements** (all REQUIRED):
|
||||
3. **Load style specs**: Read `styles/<style>.md` for visual elements, style rules, and rendering instructions
|
||||
4. **Load palette specs** (if palette specified): Read `palettes/<palette>.md` for colors and background. Palette colors **replace** the style's default Color Palette. If no palette specified, use the style's built-in colors.
|
||||
5. **Follow type-specific template** from [prompt-construction.md](prompt-construction.md), using rendering from style + colors from palette (or style default)
|
||||
6. **Prompt quality requirements** (all REQUIRED):
|
||||
- `Layout`: Describe overall composition (grid / radial / hierarchical / left-right / top-down)
|
||||
- `ZONES`: Describe each visual area with specific content, not vague descriptions
|
||||
- `LABELS`: Use **actual numbers, terms, metrics, quotes from the article** — NOT generic placeholders
|
||||
- `COLORS`: Specify hex codes with semantic meaning (e.g., `Coral (#E07A5F) for emphasis`)
|
||||
- `STYLE`: Describe line treatment, texture, mood, character rendering
|
||||
- `COLORS`: Specify hex codes from palette (or style default) with semantic meaning
|
||||
- `STYLE`: Describe line treatment, texture, mood, character rendering per style rules
|
||||
- `ASPECT`: Specify ratio (e.g., `16:9`)
|
||||
5. **Apply defaults**: composition requirements, character rendering, text guidelines, watermark
|
||||
6. **Backup rule**: If prompt file exists, rename to `prompts/NN-{type}-{slug}-backup-YYYYMMDD-HHMMSS.md`
|
||||
7. **Apply defaults**: composition requirements, character rendering, text guidelines, watermark
|
||||
8. **Backup rule**: If prompt file exists, rename to `prompts/NN-{type}-{slug}-backup-YYYYMMDD-HHMMSS.md`
|
||||
|
||||
**Verification** ⛔: Before proceeding to 5.2, confirm ALL prompt files exist:
|
||||
```
|
||||
|
||||
+47
-26
@@ -29,9 +29,9 @@ Create original knowledge comics with flexible art style × tone combinations.
|
||||
|
||||
| Option | Values | Description |
|
||||
|--------|--------|-------------|
|
||||
| `--art` | ligne-claire (default), manga, realistic, ink-brush, chalk | Art style / rendering technique |
|
||||
| `--art` | ligne-claire (default), manga, realistic, ink-brush, chalk, minimalist | Art style / rendering technique |
|
||||
| `--tone` | neutral (default), warm, dramatic, romantic, energetic, vintage, action | Mood / atmosphere |
|
||||
| `--layout` | standard (default), cinematic, dense, splash, mixed, webtoon | Panel arrangement |
|
||||
| `--layout` | standard (default), cinematic, dense, splash, mixed, webtoon, four-panel | Panel arrangement |
|
||||
| `--aspect` | 3:4 (default, portrait), 4:3 (landscape), 16:9 (widescreen) | Page aspect ratio |
|
||||
| `--lang` | auto (default), zh, en, ja, etc. | Output language |
|
||||
|
||||
@@ -55,6 +55,7 @@ Details: [references/partial-workflows.md](references/partial-workflows.md)
|
||||
| `realistic` | 写实 | Digital painting, realistic proportions, sophisticated |
|
||||
| `ink-brush` | 水墨 | Chinese brush strokes, ink wash effects |
|
||||
| `chalk` | 粉笔 | Chalkboard aesthetic, hand-drawn warmth |
|
||||
| `minimalist` | 极简 | Clean black line art, limited spot color, stick-figure characters |
|
||||
|
||||
### Tones (基调)
|
||||
|
||||
@@ -77,6 +78,8 @@ Presets with special rules beyond art+tone:
|
||||
| `--style ohmsha` | `--art manga --tone neutral` | Visual metaphors, NO talking heads, gadget reveals |
|
||||
| `--style wuxia` | `--art ink-brush --tone action` | Qi effects, combat visuals, atmospheric elements |
|
||||
| `--style shoujo` | `--art manga --tone romantic` | Decorative elements, eye details, romantic beats |
|
||||
| `--style concept-story` | `--art manga --tone warm` | Visual symbol system, growth arc, dialogue+action balance |
|
||||
| `--style four-panel` | `--art minimalist --tone neutral --layout four-panel` | 起承转合 4-panel structure, B&W + spot color, stick-figure characters |
|
||||
|
||||
### Compatibility Matrix
|
||||
|
||||
@@ -87,6 +90,7 @@ Presets with special rules beyond art+tone:
|
||||
| realistic | neutral, warm, dramatic, vintage | action | romantic, energetic |
|
||||
| ink-brush | neutral, dramatic, action, vintage | warm | romantic, energetic |
|
||||
| chalk | neutral, warm, energetic | vintage | dramatic, action, romantic |
|
||||
| minimalist | neutral | warm, energetic | dramatic, vintage, romantic, action |
|
||||
|
||||
Details: [references/auto-selection.md](references/auto-selection.md)
|
||||
|
||||
@@ -101,6 +105,8 @@ Content signals determine default art + tone + layout (or preset):
|
||||
| Personal story, mentor | ligne-claire + warm |
|
||||
| Martial arts, wuxia | **wuxia** preset |
|
||||
| Romance, school life | **shoujo** preset |
|
||||
| Psychology, motivation, business narrative | **concept-story** preset |
|
||||
| Business allegory, fable, parable, short insight, 四格 | **four-panel** preset |
|
||||
| Biography, balanced | ligne-claire + neutral |
|
||||
|
||||
**When preset is recommended**: Load `references/presets/{preset}.md` and apply all special rules.
|
||||
@@ -172,9 +178,9 @@ Comic Progress:
|
||||
- [ ] Step 4: Review outline (conditional)
|
||||
- [ ] Step 5: Generate prompts
|
||||
- [ ] Step 6: Review prompts (conditional)
|
||||
- [ ] Step 7: Generate images ⚠️ CHARACTER REF REQUIRED
|
||||
- [ ] 7.1 Generate character sheet FIRST → characters/characters.png
|
||||
- [ ] 7.2 Generate pages WITH --ref characters/characters.png
|
||||
- [ ] Step 7: Generate images
|
||||
- [ ] 7.1 Generate character sheet (if needed) → characters/characters.png
|
||||
- [ ] 7.2 Generate pages (with --ref if character sheet exists)
|
||||
- [ ] Step 8: Merge to PDF
|
||||
- [ ] Step 9: Completion report
|
||||
```
|
||||
@@ -205,16 +211,24 @@ Analyze → [Check Existing?] → [Confirm: Style + Reviews] → Storyboard →
|
||||
| 4 | Review outline (if requested) | User approval |
|
||||
| 5 | Generate prompts | `prompts/*.md` |
|
||||
| 6 | Review prompts (if requested) | User approval |
|
||||
| **7.1** | **Generate character sheet FIRST** | `characters/characters.png` |
|
||||
| **7.2** | Generate pages **with character ref** | `*.png` files |
|
||||
| 7.1 | Generate character sheet (if needed) | `characters/characters.png` |
|
||||
| 7.2 | Generate pages (with character ref if available) | `*.png` files |
|
||||
| 8 | Merge to PDF | `{slug}.pdf` |
|
||||
| 9 | Completion report | Summary |
|
||||
|
||||
### Step 7: Image Generation ⚠️ CRITICAL
|
||||
### Step 7: Image Generation
|
||||
|
||||
**Character reference is MANDATORY for visual consistency.**
|
||||
**7.1 Generate character sheet (conditional)**:
|
||||
|
||||
**7.1 Generate character sheet first**:
|
||||
Character sheet is recommended for multi-page comics with recurring characters, but **NOT required** for all presets:
|
||||
|
||||
| Condition | Action |
|
||||
|-----------|--------|
|
||||
| Multi-page comic with detailed characters | Generate character sheet (recommended) |
|
||||
| Preset with simplified characters (e.g., four-panel minimalist) | Skip — prompt descriptions are sufficient |
|
||||
| Single-page comic | Skip unless characters are complex |
|
||||
|
||||
**When generating character sheet**:
|
||||
- **Backup rule**: If `characters/characters.png` exists, rename to `characters/characters-backup-YYYYMMDD-HHMMSS.png`
|
||||
- Invoke an installed image generation skill such as `baoyu-imagine`
|
||||
- Read that skill's `SKILL.md` and follow its documented interface rather than calling its scripts directly
|
||||
@@ -222,18 +236,25 @@ Analyze → [Check Existing?] → [Confirm: Style + Reviews] → Storyboard →
|
||||
- Save output to `characters/characters.png`
|
||||
- Use aspect ratio `4:3`
|
||||
|
||||
**Compress character sheet** (recommended):
|
||||
Compress to reduce token usage when used as reference image:
|
||||
**Compress character sheet** (recommended when using as `--ref`):
|
||||
- Use available image compression skill (if any)
|
||||
- Or system tools: `pngquant`, `optipng`, `sips` (macOS)
|
||||
- **Keep PNG format**, lossless compression preferred
|
||||
- Or system tools: `sips -s format jpeg -s formatOptions 80 input.png --out output.jpg` (macOS)
|
||||
- Or: `pngquant --quality=65-80 input.png -o output.png`
|
||||
- Compression reduces API payload size and avoids `--ref` failures
|
||||
|
||||
**7.2 Generate each page WITH character reference**:
|
||||
**7.2 Generate each page**:
|
||||
|
||||
| Skill Capability | Strategy |
|
||||
|------------------|----------|
|
||||
| Supports `--ref` | Pass `characters/characters.png` with EVERY page |
|
||||
| No `--ref` support | Prepend character descriptions to EVERY prompt file |
|
||||
| Character Sheet | Skill Capability | Strategy |
|
||||
|-----------------|------------------|----------|
|
||||
| Exists | Supports `--ref` | Pass `characters/characters.png` with EVERY page |
|
||||
| Exists | No `--ref` support | Prepend character descriptions to EVERY prompt file |
|
||||
| Skipped | — | Prompt file contains all character descriptions inline |
|
||||
|
||||
**`--ref` failure recovery**: If generation fails with `--ref`:
|
||||
1. **Compress**: Convert reference image to JPEG with reduced quality:
|
||||
`sips -s format jpeg -s formatOptions 70 characters.png --out characters-compressed.jpg`
|
||||
2. **Retry** with compressed image as `--ref`
|
||||
3. **If still fails**: Fall back to generating WITHOUT `--ref` (prompt-only, character descriptions embedded in prompt text)
|
||||
|
||||
**Backup rules for page generation**:
|
||||
- If prompt file exists: rename to `prompts/NN-{cover|page}-[slug]-backup-YYYYMMDD-HHMMSS.md`
|
||||
@@ -241,8 +262,8 @@ Compress to reduce token usage when used as reference image:
|
||||
- Invoke the installed image generation skill for each page
|
||||
- Use `prompts/01-page-xxx.md` as the prompt-file input
|
||||
- Save output to `01-page-xxx.png`
|
||||
- Use aspect ratio `3:4`
|
||||
- If the chosen skill supports reference images, pass `characters/characters.png` as `--ref`
|
||||
- Use aspect ratio from storyboard (default `3:4`, preset may override)
|
||||
- If character sheet exists and skill supports reference images, pass as `--ref`
|
||||
|
||||
**Full workflow details**: [references/workflow.md](references/workflow.md)
|
||||
|
||||
@@ -273,10 +294,10 @@ Schema: [references/config/preferences-schema.md](references/config/preferences-
|
||||
- [ohmsha-guide.md](references/ohmsha-guide.md) - Ohmsha manga specifics
|
||||
|
||||
**Style Definitions**:
|
||||
- `references/art-styles/` - Art styles (ligne-claire, manga, realistic, ink-brush, chalk)
|
||||
- `references/art-styles/` - Art styles (ligne-claire, manga, realistic, ink-brush, chalk, minimalist)
|
||||
- `references/tones/` - Tones (neutral, warm, dramatic, romantic, energetic, vintage, action)
|
||||
- `references/presets/` - Presets with special rules (ohmsha, wuxia, shoujo)
|
||||
- `references/layouts/` - Layouts (standard, cinematic, dense, splash, mixed, webtoon)
|
||||
- `references/presets/` - Presets with special rules (ohmsha, wuxia, shoujo, concept-story, four-panel)
|
||||
- `references/layouts/` - Layouts (standard, cinematic, dense, splash, mixed, webtoon, four-panel)
|
||||
|
||||
**Workflow**:
|
||||
- [workflow.md](references/workflow.md) - Full workflow details
|
||||
@@ -306,6 +327,6 @@ Schema: [references/config/preferences-schema.md](references/config/preferences-
|
||||
- Maintain style consistency via session ID
|
||||
- **Step 2 confirmation required** - do not skip
|
||||
- **Steps 4/6 conditional** - only if user requested in Step 2
|
||||
- **Step 7.1 character sheet MUST be generated before pages** - ensures consistency
|
||||
- **Step 7.2 EVERY page MUST reference characters** - use `--ref` or embed descriptions
|
||||
- **Step 7.1 character sheet** - recommended for multi-page comics, optional for simple presets
|
||||
- **Step 7.2 character reference** - use `--ref` if sheet exists; compress/convert on failure; fall back to prompt-only
|
||||
- Watermark/language configured once in EXTEND.md
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
# minimalist
|
||||
|
||||
极简画风 - Clean black line art, limited spot color, simplified stick-figure characters
|
||||
|
||||
## Overview
|
||||
|
||||
Minimalist cartoon illustration characterized by clean black line art on white background with very limited spot color for emphasis. Characters are simplified to near-stick-figure abstraction, focusing on gesture and concept rather than anatomical detail. Designed for business allegory, quick-read educational content, and concept illustration.
|
||||
|
||||
## Line Work
|
||||
|
||||
- Clean, uniform black lines (1.5-2px)
|
||||
- No hatching, cross-hatching, or shading techniques
|
||||
- Minimal detail — every line serves a purpose
|
||||
- Bold outlines for characters, thinner lines for props/labels
|
||||
- No decorative flourishes or ornamental lines
|
||||
|
||||
## Character Design
|
||||
|
||||
- Highly simplified, stick-figure-like business characters
|
||||
- Circle or oval heads with minimal facial features (dot eyes, simple line mouth)
|
||||
- Body as simple geometric shapes or line constructions
|
||||
- Distinguishing features through props only (tie, hat, briefcase, glasses)
|
||||
- No anatomical detail — expressive through posture and gesture
|
||||
- 4-5 head height proportions (squat, iconic)
|
||||
|
||||
## Background Treatment
|
||||
|
||||
- Mostly blank/white — negative space is a design element
|
||||
- Minimal environmental cues (a line for ground, simple desk outline)
|
||||
- Concept labels and text annotations replace detailed environments
|
||||
- Icons and symbols over realistic rendering
|
||||
- No perspective or spatial depth
|
||||
|
||||
## Color Approach
|
||||
|
||||
- Primarily black and white (90%+ of the image)
|
||||
- 1-2 spot accent colors for emphasis on key concepts
|
||||
- Accent color used sparingly: highlighting key objects, text labels, concept indicators
|
||||
- No gradients, no shading, no color fills on backgrounds
|
||||
- Color draws the eye to the most important element in each panel
|
||||
|
||||
## Default Color Palette
|
||||
|
||||
| Role | Color | Hex |
|
||||
|------|-------|-----|
|
||||
| Primary | Black ink | `#1A1A1A` |
|
||||
| Background | Clean white | `#FFFFFF` |
|
||||
| Accent 1 | Spot orange | `#FF6B35` |
|
||||
| Accent 2 | Spot blue (optional) | `#3182CE` |
|
||||
| Text labels | Dark gray | `#4A4A4A` |
|
||||
| Panel border | Medium gray | `#666666` |
|
||||
|
||||
## Visual Elements
|
||||
|
||||
- Text labels with accent-color backgrounds or underlines for key terms
|
||||
- Simple icons: arrows, circles, checkmarks, crosses
|
||||
- Concept highlight boxes with spot color
|
||||
- Minimal speech bubbles (simple oval or rectangle, thin black outline)
|
||||
- No sound effects, no motion lines, no screen tones
|
||||
|
||||
## Quality Markers
|
||||
|
||||
- ✓ Clean, purposeful line work with no unnecessary detail
|
||||
- ✓ 90%+ black-and-white with strategic spot color
|
||||
- ✓ Simplified characters readable at small sizes
|
||||
- ✓ Text labels integrated naturally into panels
|
||||
- ✓ Strong negative space usage
|
||||
- ✓ Every element serves the narrative point
|
||||
|
||||
## Compatibility
|
||||
|
||||
| Tone | Fit | Notes |
|
||||
|------|-----|-------|
|
||||
| neutral | ✓✓ | Ideal for business/educational content |
|
||||
| warm | ✓ | Works for gentle stories, slight warmth in accent |
|
||||
| energetic | ✓ | Works for punchy, high-energy content |
|
||||
| dramatic | ✗ | Style too stripped down for dramatic intensity |
|
||||
| vintage | ✗ | Minimalist aesthetic conflicts with aged/textured look |
|
||||
| romantic | ✗ | No capacity for decorative/soft elements |
|
||||
| action | ✗ | No dynamic line capability for speed/impact |
|
||||
|
||||
## Best For
|
||||
|
||||
Business allegory, management fables, short concept illustration, four-panel comic strips, quick-insight education, social media content
|
||||
@@ -11,10 +11,13 @@ Content signals determine default art + tone + layout (or preset).
|
||||
| Technical explanation, educational | manga | neutral | webtoon | **ohmsha** |
|
||||
| Pre-1950, classical, ancient | realistic | vintage | cinematic | - |
|
||||
| Personal story, mentor | ligne-claire | warm | standard | - |
|
||||
| Psychology, motivation, self-help, coaching | manga | warm | standard | **concept-story** |
|
||||
| Business narrative, management, leadership | manga | warm | standard | **concept-story** |
|
||||
| Conflict, breakthrough | (inherit) | dramatic | splash | - |
|
||||
| Wine, food, business, lifestyle | realistic | neutral | cinematic | - |
|
||||
| Wine, food, lifestyle | realistic | neutral | cinematic | - |
|
||||
| Martial arts, wuxia, xianxia | ink-brush | action | splash | **wuxia** |
|
||||
| Romance, love, school life | manga | romantic | standard | **shoujo** |
|
||||
| Business allegory, fable, parable, short insight, 四格 | minimalist | neutral | four-panel | **four-panel** |
|
||||
| Biography, balanced | ligne-claire | neutral | mixed | - |
|
||||
|
||||
## Preset Recommendation Rules
|
||||
@@ -36,6 +39,16 @@ Content signals determine default art + tone + layout (or preset).
|
||||
- **Special rules**: Decorative elements, eye details, romantic beats
|
||||
- **Base**: manga + romantic + standard
|
||||
|
||||
### concept-story
|
||||
- **Triggers**: Psychology, motivation, self-help, business narrative, management, leadership, personal growth, coaching, soft skills, abstract concept through story
|
||||
- **Special rules**: Visual symbol system, growth arc, dialogue+action balance, original characters
|
||||
- **Base**: manga + warm + standard
|
||||
|
||||
### four-panel
|
||||
- **Triggers**: Business allegory, fable, parable, short insight, four-panel, 四格, 四格漫画, single-page comic, minimalist comic strip
|
||||
- **Special rules**: Strict 起承转合 4-panel structure, B&W + spot color, simplified stick-figure characters, single-page story
|
||||
- **Base**: minimalist + neutral + four-panel
|
||||
|
||||
## Compatibility Matrix
|
||||
|
||||
Art Style × Tone combinations work best when matched appropriately:
|
||||
@@ -47,6 +60,7 @@ Art Style × Tone combinations work best when matched appropriately:
|
||||
| realistic | neutral, warm, dramatic, vintage | action | romantic, energetic |
|
||||
| ink-brush | neutral, dramatic, action, vintage | warm | romantic, energetic |
|
||||
| chalk | neutral, warm, energetic | vintage | dramatic, action, romantic |
|
||||
| minimalist | neutral | warm, energetic | dramatic, vintage, romantic, action |
|
||||
|
||||
**Note**: Art Style × Tone × Layout can be freely combined. Incompatible combinations work but may produce unexpected results.
|
||||
|
||||
|
||||
@@ -16,9 +16,9 @@ watermark:
|
||||
content: ""
|
||||
position: bottom-right # bottom-right|bottom-left|bottom-center|top-right
|
||||
|
||||
preferred_art: null # ligne-claire|manga|realistic|ink-brush|chalk
|
||||
preferred_art: null # ligne-claire|manga|realistic|ink-brush|chalk|minimalist
|
||||
preferred_tone: null # neutral|warm|dramatic|romantic|energetic|vintage|action
|
||||
preferred_layout: null # standard|cinematic|dense|splash|mixed|webtoon
|
||||
preferred_layout: null # standard|cinematic|dense|splash|mixed|webtoon|four-panel
|
||||
preferred_aspect: null # 3:4|4:3|16:9
|
||||
|
||||
language: null # zh|en|ja|ko|auto
|
||||
@@ -41,7 +41,7 @@ character_presets:
|
||||
| `watermark.enabled` | bool | false | Enable watermark |
|
||||
| `watermark.content` | string | "" | Watermark text (@username or custom) |
|
||||
| `watermark.position` | enum | bottom-right | Position on image |
|
||||
| `preferred_art` | string | null | Art style (ligne-claire, manga, realistic, ink-brush, chalk) |
|
||||
| `preferred_art` | string | null | Art style (ligne-claire, manga, realistic, ink-brush, chalk, minimalist) |
|
||||
| `preferred_tone` | string | null | Tone (neutral, warm, dramatic, romantic, energetic, vintage, action) |
|
||||
| `preferred_layout` | string | null | Layout preference or null |
|
||||
| `preferred_aspect` | string | null | Aspect ratio (3:4, 4:3, 16:9) |
|
||||
@@ -57,6 +57,7 @@ character_presets:
|
||||
| `realistic` | 写实 | Digital painting, realistic proportions |
|
||||
| `ink-brush` | 水墨 | Chinese brush strokes, ink wash effects |
|
||||
| `chalk` | 粉笔 | Chalkboard aesthetic, hand-drawn warmth |
|
||||
| `minimalist` | 极简 | Clean black line art, limited spot color, stick-figure characters |
|
||||
|
||||
## Tone Options
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# four-panel
|
||||
|
||||
四格漫画 - Strict 2×2 grid, single-page story
|
||||
|
||||
## Panel Structure
|
||||
|
||||
- **Panels per page**: 4 (exactly, no variation)
|
||||
- **Structure**: Strict 2×2 equal grid
|
||||
- **Gutters**: Consistent white space (8-10px), uniform on all sides
|
||||
|
||||
## Grid Configuration
|
||||
|
||||
- 2 columns × 2 rows, all panels identical size
|
||||
- Panel sizes: Exactly equal (each panel = 25% of content area)
|
||||
- Reading flow: Z-pattern — Panel 1 (top-left) → Panel 2 (top-right) → Panel 3 (bottom-left) → Panel 4 (bottom-right)
|
||||
|
||||
## Narrative Structure
|
||||
|
||||
Each panel serves a specific narrative role (起承转合 / kishōtenketsu):
|
||||
|
||||
| Panel | Position | Role | Purpose |
|
||||
|-------|----------|------|---------|
|
||||
| 1 | Top-left | 起 Setup | Establish situation, introduce characters/problem |
|
||||
| 2 | Top-right | 承 Development | Build on setup, add complication or attempt |
|
||||
| 3 | Bottom-left | 转 Turn | Twist, key insight, or reversal — the pivotal moment |
|
||||
| 4 | Bottom-right | 合 Conclusion | Resolution, punchline, or takeaway |
|
||||
|
||||
## Aspect Ratio
|
||||
|
||||
- Recommended page aspect: **4:3** (landscape)
|
||||
- Landscape gives each panel a comfortable wide rectangle
|
||||
- Portrait (3:4) makes panels tall and narrow — avoid for this layout
|
||||
|
||||
## Best For
|
||||
|
||||
Business allegory, quick-insight education, social media comics, fables, parables, single-concept explanation
|
||||
|
||||
## Best Style Pairings
|
||||
|
||||
minimalist, ligne-claire, chalk
|
||||
@@ -0,0 +1,121 @@
|
||||
# concept-story
|
||||
|
||||
概念故事预设 - Narrative comics that visualize abstract concepts through character-driven stories
|
||||
|
||||
## Base Configuration
|
||||
|
||||
| Dimension | Value |
|
||||
|-----------|-------|
|
||||
| Art Style | manga |
|
||||
| Tone | warm |
|
||||
| Layout | standard (default) |
|
||||
|
||||
Equivalent to: `--art manga --tone warm`
|
||||
|
||||
## Unique Rules
|
||||
|
||||
This preset includes special rules beyond the art+tone combination. When `--style concept-story` is used, ALL rules below must be applied.
|
||||
|
||||
### Concept Visualization System (CRITICAL)
|
||||
|
||||
Each major abstract concept SHOULD have a recurring visual symbol/metaphor:
|
||||
|
||||
| Concept Type | Visualization Approach |
|
||||
|-------------|----------------------|
|
||||
| Psychological need | Tangible object character holds or discovers (e.g., glowing energy ball = competence) |
|
||||
| Management principle | Environmental metaphor character navigates (e.g., ship wheel = autonomy) |
|
||||
| Growth/development | Living organic symbol that transforms (e.g., seed → flowering plant = relatedness) |
|
||||
| Abstract framework | Spatial structure characters can enter or observe |
|
||||
| Emotional state | Color/lighting shift in the scene atmosphere |
|
||||
|
||||
**Unlike ohmsha**: Dialogue panels are allowed and expected. The goal is to COMBINE visual metaphors WITH dialogue, not replace dialogue entirely.
|
||||
|
||||
**Pattern**: "Dialogue introduces idea" → "Visual metaphor illustrates it" → "Character reacts/applies it"
|
||||
|
||||
### Visual Symbol Continuity
|
||||
|
||||
Symbols must persist across the story:
|
||||
|
||||
| Stage | Treatment |
|
||||
|-------|-----------|
|
||||
| Introduction | Symbol appears with soft glow effect when concept is first mentioned |
|
||||
| Recurrence | Same symbol reappears in background or character interaction when concept is referenced |
|
||||
| Resolution | ALL symbols gather in the final composition, showing integration of learned concepts |
|
||||
|
||||
**Storyboard requirement**: Include a Symbol Mapping Table defining concept → visual symbol before panel breakdown.
|
||||
|
||||
### Character Archetypes (Flexible)
|
||||
|
||||
Create original characters based on content domain. No fixed defaults:
|
||||
|
||||
| Role | Archetype | Visual Cues |
|
||||
|------|-----------|------------|
|
||||
| Protagonist | Learner/worker facing a challenge | Modern professional or student, relatable, starts with constrained posture |
|
||||
| Mentor | Experienced guide who teaches through experience | Slightly older, calm demeanor, warm color accents |
|
||||
| Catalyst | Person or event that triggers transformation | Can be a colleague, situation, challenge, or opportunity |
|
||||
|
||||
**IMPORTANT**: Characters are created fresh each time based on the source content's domain (business, psychology, education, etc.). No default character set.
|
||||
|
||||
### Narrative Arc Structure
|
||||
|
||||
Enforce a five-stage growth arc:
|
||||
|
||||
| Act | Structure | Visual Tone |
|
||||
|-----|-----------|------------|
|
||||
| Opening | Protagonist stuck in routine, faces frustration | Muted warm tones, tight framing, constrained compositions |
|
||||
| Inciting moment | Mentor appears or opportunity arrives | Brightness increases, panels open up |
|
||||
| Learning | Concepts introduced through visual metaphors | Rich warm palette, symbols introduced one by one |
|
||||
| Turning point | Protagonist applies knowledge, faces test | Contrast increases, dynamic compositions |
|
||||
| Transformation | Growth demonstrated, new understanding visible | Full warm palette, expansive composition, all symbols present |
|
||||
|
||||
### Dialogue + Action Balance
|
||||
|
||||
- Dialogue is encouraged and expected (unlike ohmsha's NO talking heads rule)
|
||||
- Every page should combine at least one dialogue panel with at least one visual/action panel
|
||||
- Avoid pure "lecture" pages where a character explains for 4+ panels straight
|
||||
- When a character explains a concept verbally, the NEXT panel should visualize it
|
||||
|
||||
**Wrong approach**: Four consecutive panels of mentor lecturing at protagonist
|
||||
**Right approach**: Mentor introduces concept → visual metaphor panel → protagonist reacts → applies understanding
|
||||
|
||||
### Scene Atmosphere Rules
|
||||
|
||||
| Scene Type | Atmosphere |
|
||||
|------------|-----------|
|
||||
| Problem/frustration | Cool muted tones over warm base, tight framing, cluttered environment |
|
||||
| Mentoring moment | Golden hour lighting, open composition, warm indoor glow |
|
||||
| Concept visualization | Soft glow effects, clean simplified backgrounds, symbol spotlight |
|
||||
| Growth/transformation | Warm light expanding outward, character posture opening up |
|
||||
| Resolution | Full warm palette, spacious composition, all visual symbols visible |
|
||||
|
||||
### Ending Requirements
|
||||
|
||||
Final page MUST include:
|
||||
|
||||
1. Protagonist demonstrating transformed understanding (not just being told)
|
||||
2. Visual callback showing contrast with opening state (e.g., wilted plant → thriving plant)
|
||||
3. All concept symbols visible together in the composition
|
||||
4. A forward-looking element suggesting ongoing growth (not a closed ending)
|
||||
|
||||
### Page Title Convention
|
||||
|
||||
Every page MUST have a narrative title:
|
||||
|
||||
**Wrong**: "Chapter 3: Self-Determination Theory"
|
||||
**Right**: "The Day Xiao Ming Found His Own Engine"
|
||||
|
||||
## Quality Markers
|
||||
|
||||
- ✓ Each major concept has a recurring visual symbol
|
||||
- ✓ Dialogue and visual metaphors work together (not one replacing the other)
|
||||
- ✓ Clear growth arc from problem to transformation
|
||||
- ✓ Original characters suited to the content domain
|
||||
- ✓ Warm, professional atmosphere throughout
|
||||
- ✓ Visual symbols recur and accumulate through the story
|
||||
- ✓ Final page integrates all concept symbols with transformation callback
|
||||
|
||||
## Best For
|
||||
|
||||
Psychology concepts, business/management principles, motivation theory, personal development,
|
||||
self-help content, leadership frameworks, coaching narratives, soft skill education,
|
||||
abstract concept explanation through character-driven stories
|
||||
@@ -0,0 +1,107 @@
|
||||
# four-panel
|
||||
|
||||
四格漫画预设 - Minimalist four-panel business allegory comics
|
||||
|
||||
## Base Configuration
|
||||
|
||||
| Dimension | Value |
|
||||
|-----------|-------|
|
||||
| Art Style | minimalist |
|
||||
| Tone | neutral |
|
||||
| Layout | four-panel (default) |
|
||||
| Aspect | 4:3 (landscape) |
|
||||
|
||||
Equivalent to: `--art minimalist --tone neutral --layout four-panel --aspect 4:3`
|
||||
|
||||
## Unique Rules
|
||||
|
||||
This preset includes special rules beyond the art+tone combination. When `--style four-panel` is used, ALL rules below must be applied.
|
||||
|
||||
### 起承转合 Narrative Structure (CRITICAL)
|
||||
|
||||
Every comic MUST follow the four-panel 起承转合 structure:
|
||||
|
||||
| Panel | Role | Requirements |
|
||||
|-------|------|-------------|
|
||||
| 1 (起 Setup) | Introduce the situation | Show character(s) in a recognizable context. Establish the "normal" state or problem |
|
||||
| 2 (承 Development) | Build on the setup | Add complication, show an attempt, or introduce the concept. Stakes become clearer |
|
||||
| 3 (转 Turn) | The twist or key insight | **Most important panel.** Show the unexpected reversal, contrast, or "aha" moment that makes the allegory work |
|
||||
| 4 (合 Conclusion) | Resolution and takeaway | Show the result, consequence, or lesson learned. Can be a visual punchline or summary |
|
||||
|
||||
**CRITICAL**: Do NOT deviate from exactly 4 panels. No 5th panel, no title panel, no footer panel within the image.
|
||||
|
||||
### Single-Page Story Rule (CRITICAL)
|
||||
|
||||
- The entire story is told in ONE page with exactly 4 panels
|
||||
- Page count: always 1 (plus optional cover)
|
||||
- No multi-page four-panel stories — if content requires more, create multiple separate four-panel comics
|
||||
- Storyboard structure: Cover (optional) + 1 page
|
||||
|
||||
### Accent Color System
|
||||
|
||||
- The image is primarily black-and-white line art
|
||||
- Use exactly 1-2 spot colors per strip (default: orange `#FF6B35`)
|
||||
- Rules:
|
||||
- Key concept label or object: filled with accent color or outlined in accent
|
||||
- Panel 3 (转 Turn) should have the strongest color emphasis
|
||||
- Characters remain B&W — color is for concepts/objects/labels only
|
||||
- Consistent accent color across all 4 panels (do not switch colors between panels)
|
||||
|
||||
### Character Design Rules
|
||||
|
||||
- Simplified stick-figure-like characters
|
||||
- Distinguish characters through simple props: ties, glasses, hats, briefcases, aprons
|
||||
- No detailed faces — dot eyes, line mouth at most
|
||||
- Characters should be generic enough to represent archetypes (the manager, the employee, the customer)
|
||||
- Maximum 2-3 characters per strip
|
||||
|
||||
### Text in Panels
|
||||
|
||||
- Chinese text for dialogue and labels (or match source language)
|
||||
- Keep text minimal — 1-2 short lines per panel maximum
|
||||
- Key concept terms can be highlighted with accent color background
|
||||
- No narrator boxes — dialogue and labels only
|
||||
- Speech bubbles: simple rectangles or ovals, thin black outline
|
||||
|
||||
### Optional Title & Caption
|
||||
|
||||
- A brief descriptive title above the 4 panels
|
||||
- An optional one-line caption/moral below the panels
|
||||
- These are part of the page composition, not separate panels
|
||||
|
||||
### Character Archetypes (Flexible)
|
||||
|
||||
Create simple stick-figure characters based on content. No fixed defaults:
|
||||
|
||||
| Role | Archetype | Visual Cues |
|
||||
|------|-----------|------------|
|
||||
| Protagonist | Worker/employee facing a situation | Simple figure, minimal distinguishing feature (glasses, tie) |
|
||||
| Authority | Boss/manager/expert | Slightly larger figure, or prop like pointer/clipboard |
|
||||
| Object | The concept itself | Labeled object, icon, or highlighted text with accent color |
|
||||
|
||||
### Prompt Template
|
||||
|
||||
When generating image prompts for four-panel comics, include these keywords:
|
||||
|
||||
> A minimalist, clean line art digital comic strip in a four-panel grid layout (2×2). The style is simplified cartoon illustration with clear black outlines and a minimal color palette of black, white, and specific spot [accent color] for key concepts.
|
||||
|
||||
Each panel description should specify:
|
||||
- Panel position (Top Left / Top Right / Bottom Left / Bottom Right)
|
||||
- Character poses and gestures (simple, stick-figure style)
|
||||
- Dialogue text in Chinese (hand-drawn style)
|
||||
- Any accent-colored elements (concept labels, key objects)
|
||||
|
||||
## Quality Markers
|
||||
|
||||
- ✓ Exactly 4 panels in strict 2×2 grid
|
||||
- ✓ 起承转合 narrative arc clearly present
|
||||
- ✓ 90%+ black-and-white with strategic spot color
|
||||
- ✓ Simplified stick-figure characters
|
||||
- ✓ Key concept visually highlighted with accent color
|
||||
- ✓ Text is minimal and in Chinese (or source language)
|
||||
- ✓ Single complete story in one page
|
||||
- ✓ Panel 3 delivers a clear "turn" or insight
|
||||
|
||||
## Best For
|
||||
|
||||
Business allegory, management fables, short insights, workplace parables, concept contrasts, social media educational content, quick-read comics
|
||||
@@ -18,6 +18,8 @@ Comic Progress:
|
||||
- [ ] Step 5: Generate prompts
|
||||
- [ ] Step 6: Review prompts (conditional)
|
||||
- [ ] Step 7: Generate images
|
||||
- [ ] 7.1 Character sheet (if needed)
|
||||
- [ ] 7.2 Generate pages
|
||||
- [ ] Step 8: Merge to PDF
|
||||
- [ ] Step 9: Completion report
|
||||
```
|
||||
@@ -410,28 +412,41 @@ options:
|
||||
|
||||
With confirmed prompts from Step 5/6:
|
||||
|
||||
### 7.1 Generate Character Reference Sheet (first)
|
||||
### 7.1 Generate Character Reference Sheet (conditional)
|
||||
|
||||
Character sheet is recommended for multi-page comics with recurring characters, but **NOT required** for all presets.
|
||||
|
||||
**When to generate**:
|
||||
|
||||
| Condition | Action |
|
||||
|-----------|--------|
|
||||
| Multi-page comic with detailed/recurring characters | Generate character sheet (recommended) |
|
||||
| Preset with simplified characters (e.g., four-panel minimalist) | Skip — prompt descriptions are sufficient |
|
||||
| Single-page comic | Skip unless characters are complex |
|
||||
|
||||
**When generating**:
|
||||
1. Use Reference Sheet Prompt from `characters/characters.md`
|
||||
2. **Backup rule**: If `characters/characters.png` exists, rename to `characters/characters-backup-YYYYMMDD-HHMMSS.png`
|
||||
3. Generate → `characters/characters.png`
|
||||
4. This ensures visual consistency for all subsequent pages
|
||||
4. **Compress** to reduce API payload size when used as `--ref`:
|
||||
- `sips -s format jpeg -s formatOptions 80 characters.png --out characters-compressed.jpg` (macOS)
|
||||
- Or: `pngquant --quality=65-80 characters.png -o characters-compressed.png`
|
||||
|
||||
### 7.2 Generate Comic Pages
|
||||
|
||||
**CRITICAL: Character Reference is MANDATORY** for visual consistency across all pages.
|
||||
|
||||
**Before generating any page**:
|
||||
1. Read the image generation skill's SKILL.md
|
||||
2. Check if it supports reference image input (`--ref`, `--reference`, etc.)
|
||||
3. Choose the appropriate strategy below
|
||||
3. Determine if character sheet exists
|
||||
4. Choose the appropriate strategy below
|
||||
|
||||
**Character Reference Strategy**:
|
||||
**Page Generation Strategy**:
|
||||
|
||||
| Skill Capability | Strategy | Action |
|
||||
|------------------|----------|--------|
|
||||
| Supports `--ref` | **Strategy A** | Pass `characters/characters.png` with EVERY page |
|
||||
| Does NOT support `--ref` | **Strategy B** | Prepend character descriptions to EVERY prompt |
|
||||
| Character Sheet | Skill Capability | Strategy |
|
||||
|-----------------|------------------|----------|
|
||||
| Exists | Supports `--ref` | **A**: Pass character sheet as `--ref` with every page |
|
||||
| Exists | No `--ref` support | **B**: Embed character descriptions in every prompt |
|
||||
| Skipped | — | **C**: Prompt file contains all descriptions inline |
|
||||
|
||||
**Strategy A: Using `--ref` parameter** (e.g., baoyu-imagine)
|
||||
|
||||
@@ -439,8 +454,16 @@ With confirmed prompts from Step 5/6:
|
||||
- Invoke that installed skill via its documented interface, not by calling its scripts directly
|
||||
- For every page, use `prompts/01-page-xxx.md` as the prompt-file input
|
||||
- Save output to `01-page-xxx.png`
|
||||
- Use aspect ratio `3:4`
|
||||
- Pass `characters/characters.png` as `--ref` on every page generation
|
||||
- Use aspect ratio from storyboard (default `3:4`, preset may override)
|
||||
- Pass `characters/characters.png` (or compressed version) as `--ref`
|
||||
|
||||
**`--ref` failure recovery**:
|
||||
If generation fails when using `--ref`:
|
||||
1. **Compress/convert** reference image:
|
||||
- `sips -s format jpeg -s formatOptions 70 characters.png --out characters-compressed.jpg`
|
||||
- Or reduce resolution: `sips -Z 1024 characters.png --out characters-small.png`
|
||||
2. **Retry** with compressed/converted image as `--ref`
|
||||
3. **If still fails**: Fall back to **Strategy C** — generate WITHOUT `--ref`, with character descriptions embedded in prompt text
|
||||
|
||||
**Strategy B: Embedding character descriptions in prompt**
|
||||
|
||||
@@ -458,10 +481,17 @@ When skill does NOT support reference images, create combined prompt files:
|
||||
[Original page prompt here]
|
||||
```
|
||||
|
||||
**Strategy C: Prompt-only (no character sheet)**
|
||||
|
||||
When character sheet was skipped or `--ref` failed:
|
||||
- Prompt file already contains all character descriptions inline
|
||||
- No `--ref` parameter needed
|
||||
- Rely on detailed text descriptions for character consistency
|
||||
|
||||
**For each page (cover + pages)**:
|
||||
1. Read prompt from `prompts/NN-{cover|page}-[slug].md`
|
||||
2. **Backup rule**: If image file exists, rename to `NN-{cover|page}-[slug]-backup-YYYYMMDD-HHMMSS.png`
|
||||
3. Generate image using Strategy A or B (based on skill capability)
|
||||
3. Generate image using Strategy A, B, or C
|
||||
4. Save to `NN-{cover|page}-[slug].png`
|
||||
5. Report progress after each generation: "Generated X/N: [page title]"
|
||||
|
||||
@@ -493,7 +523,7 @@ Title: [title] | Art: [art] | Tone: [tone] | Pages: [count] | Aspect: [ratio] |
|
||||
Watermark: [enabled/disabled]
|
||||
Location: [path]
|
||||
✓ analysis.md
|
||||
✓ characters.png
|
||||
✓ characters.png (if generated)
|
||||
✓ 00-cover-[slug].png ... NN-page-[slug].png
|
||||
✓ {topic-slug}.pdf
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: baoyu-cover-image
|
||||
description: Generates article cover images with 5 dimensions (type, palette, rendering, text, mood) combining 10 color palettes and 7 rendering styles. Supports cinematic (2.35:1), widescreen (16:9), and square (1:1) aspects. Use when user asks to "generate cover image", "create article cover", or "make cover".
|
||||
description: Generates article cover images with 5 dimensions (type, palette, rendering, text, mood) combining 11 color palettes and 7 rendering styles. Supports cinematic (2.35:1), widescreen (16:9), and square (1:1) aspects. Use when user asks to "generate cover image", "create article cover", or "make cover".
|
||||
version: 1.56.1
|
||||
metadata:
|
||||
openclaw:
|
||||
@@ -39,7 +39,7 @@ Generate elegant cover images for articles with 5-dimensional customization.
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--type <name>` | hero, conceptual, typography, metaphor, scene, minimal |
|
||||
| `--palette <name>` | warm, elegant, cool, dark, earth, vivid, pastel, mono, retro, duotone |
|
||||
| `--palette <name>` | warm, elegant, cool, dark, earth, vivid, pastel, mono, retro, duotone, macaron |
|
||||
| `--rendering <name>` | flat-vector, hand-drawn, painterly, digital, pixel, chalk, screen-print |
|
||||
| `--style <name>` | Preset shorthand (see [Style Presets](references/style-presets.md)) |
|
||||
| `--text <level>` | none, title-only, title-subtitle, text-rich |
|
||||
@@ -56,7 +56,7 @@ Generate elegant cover images for articles with 5-dimensional customization.
|
||||
| Dimension | Values | Default |
|
||||
|-----------|--------|---------|
|
||||
| **Type** | hero, conceptual, typography, metaphor, scene, minimal | auto |
|
||||
| **Palette** | warm, elegant, cool, dark, earth, vivid, pastel, mono, retro, duotone | auto |
|
||||
| **Palette** | warm, elegant, cool, dark, earth, vivid, pastel, mono, retro, duotone, macaron | auto |
|
||||
| **Rendering** | flat-vector, hand-drawn, painterly, digital, pixel, chalk, screen-print | auto |
|
||||
| **Text** | none, title-only, title-subtitle, text-rich | title-only |
|
||||
| **Mood** | subtle, balanced, bold | balanced |
|
||||
@@ -69,7 +69,7 @@ Auto-selection rules: [references/auto-selection.md](references/auto-selection.m
|
||||
**Types**: hero, conceptual, typography, metaphor, scene, minimal
|
||||
→ Details: [references/types.md](references/types.md)
|
||||
|
||||
**Palettes**: warm, elegant, cool, dark, earth, vivid, pastel, mono, retro, duotone
|
||||
**Palettes**: warm, elegant, cool, dark, earth, vivid, pastel, mono, retro, duotone, macaron
|
||||
→ Details: [references/palettes/](references/palettes/)
|
||||
|
||||
**Renderings**: flat-vector, hand-drawn, painterly, digital, pixel, chalk, screen-print
|
||||
|
||||
@@ -27,6 +27,7 @@ When a dimension is omitted, select based on content signals.
|
||||
| Zen, focus, essential, pure, simple | `mono` |
|
||||
| History, vintage, retro, classic, exploration | `retro` |
|
||||
| Movie poster, album cover, concert, cinematic, dramatic, two-color | `duotone` |
|
||||
| Education, tutorial, knowledge, onboarding, concept explainer | `macaron` |
|
||||
|
||||
## Auto Rendering Selection
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ watermark:
|
||||
|
||||
preferred_type: null # hero|conceptual|typography|metaphor|scene|minimal or null for auto-select
|
||||
|
||||
preferred_palette: null # warm|elegant|cool|dark|earth|vivid|pastel|mono|retro or null for auto-select
|
||||
preferred_palette: null # warm|elegant|cool|dark|earth|vivid|pastel|mono|retro|duotone|macaron or null for auto-select
|
||||
|
||||
preferred_rendering: null # flat-vector|hand-drawn|painterly|digital|pixel|chalk or null for auto-select
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# macaron
|
||||
|
||||
Soft macaron pastel color blocks on warm cream
|
||||
|
||||
## Color Palette
|
||||
|
||||
| Role | Color | Hex |
|
||||
|------|-------|-----|
|
||||
| Primary 1 | Sky Blue | #A8D8EA |
|
||||
| Primary 2 | Mint Green | #B5E5CF |
|
||||
| Primary 3 | Lavender | #D5C6E0 |
|
||||
| Background | Warm Cream | #F5F0E8 |
|
||||
| Background Alt | Peach | #FFD5C2 |
|
||||
| Accent 1 | Coral Red | #E8655A |
|
||||
| Accent 2 | Deep Charcoal | #2D2D2D |
|
||||
|
||||
## Decorative Hints
|
||||
|
||||
- Rounded pastel color blocks
|
||||
- Subtle warm paper grain texture
|
||||
- Soft shadows, no hard edges
|
||||
- Gentle gradient transitions between zones
|
||||
|
||||
## Best For
|
||||
|
||||
Educational content, knowledge sharing, concept explainers, tutorials, tech summaries, onboarding materials
|
||||
@@ -23,6 +23,7 @@
|
||||
| `vintage` | `retro` | `hand-drawn` |
|
||||
| `warm` | `warm` | `hand-drawn` |
|
||||
| `warm-flat` | `warm` | `flat-vector` |
|
||||
| `hand-drawn-edu` | `macaron` | `hand-drawn` |
|
||||
| `watercolor` | `earth` | `painterly` |
|
||||
| `poster-art` | `retro` | `screen-print` |
|
||||
| `mondo` | `mono` | `screen-print` |
|
||||
|
||||
+32
-1
@@ -478,7 +478,7 @@ export function killChrome(chrome: ChildProcess): void {
|
||||
chrome.kill("SIGTERM");
|
||||
} catch {}
|
||||
setTimeout(() => {
|
||||
if (!chrome.killed) {
|
||||
if (chrome.exitCode === null && chrome.signalCode === null) {
|
||||
try {
|
||||
chrome.kill("SIGKILL");
|
||||
} catch {}
|
||||
@@ -486,6 +486,37 @@ export function killChrome(chrome: ChildProcess): void {
|
||||
}, 2_000).unref?.();
|
||||
}
|
||||
|
||||
export async function gracefulKillChrome(
|
||||
chrome: ChildProcess,
|
||||
port?: number,
|
||||
timeoutMs = 6_000,
|
||||
): Promise<void> {
|
||||
if (chrome.exitCode !== null || chrome.signalCode !== null) return;
|
||||
|
||||
const exitPromise = new Promise<void>((resolve) => {
|
||||
chrome.once("exit", () => resolve());
|
||||
});
|
||||
|
||||
killChrome(chrome);
|
||||
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < timeoutMs) {
|
||||
if (chrome.exitCode !== null || chrome.signalCode !== null) return;
|
||||
if (port !== undefined && !await isPortListening(port, 250)) return;
|
||||
|
||||
const exited = await Promise.race([
|
||||
exitPromise.then(() => true),
|
||||
sleep(100).then(() => false),
|
||||
]);
|
||||
if (exited) return;
|
||||
}
|
||||
|
||||
await Promise.race([
|
||||
exitPromise,
|
||||
sleep(250),
|
||||
]);
|
||||
}
|
||||
|
||||
export async function openPageSession(options: OpenPageSessionOptions): Promise<PageSession> {
|
||||
let targetId: string;
|
||||
let createdTarget = false;
|
||||
|
||||
+32
-1
@@ -478,7 +478,7 @@ export function killChrome(chrome: ChildProcess): void {
|
||||
chrome.kill("SIGTERM");
|
||||
} catch {}
|
||||
setTimeout(() => {
|
||||
if (!chrome.killed) {
|
||||
if (chrome.exitCode === null && chrome.signalCode === null) {
|
||||
try {
|
||||
chrome.kill("SIGKILL");
|
||||
} catch {}
|
||||
@@ -486,6 +486,37 @@ export function killChrome(chrome: ChildProcess): void {
|
||||
}, 2_000).unref?.();
|
||||
}
|
||||
|
||||
export async function gracefulKillChrome(
|
||||
chrome: ChildProcess,
|
||||
port?: number,
|
||||
timeoutMs = 6_000,
|
||||
): Promise<void> {
|
||||
if (chrome.exitCode !== null || chrome.signalCode !== null) return;
|
||||
|
||||
const exitPromise = new Promise<void>((resolve) => {
|
||||
chrome.once("exit", () => resolve());
|
||||
});
|
||||
|
||||
killChrome(chrome);
|
||||
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < timeoutMs) {
|
||||
if (chrome.exitCode !== null || chrome.signalCode !== null) return;
|
||||
if (port !== undefined && !await isPortListening(port, 250)) return;
|
||||
|
||||
const exited = await Promise.race([
|
||||
exitPromise.then(() => true),
|
||||
sleep(100).then(() => false),
|
||||
]);
|
||||
if (exited) return;
|
||||
}
|
||||
|
||||
await Promise.race([
|
||||
exitPromise,
|
||||
sleep(250),
|
||||
]);
|
||||
}
|
||||
|
||||
export async function openPageSession(options: OpenPageSessionOptions): Promise<PageSession> {
|
||||
let targetId: string;
|
||||
let createdTarget = false;
|
||||
|
||||
@@ -352,7 +352,7 @@ WECHAT_APP_SECRET=<user_input>
|
||||
| Field | If Missing |
|
||||
|-------|------------|
|
||||
| Title | Prompt: "Enter title, or press Enter to auto-generate from content" |
|
||||
| Summary | Prompt: "Enter summary, or press Enter to auto-generate (recommended for SEO)" |
|
||||
| Summary | Use fallback chain: frontmatter `description` → frontmatter `summary` → prompt user or auto-generate |
|
||||
| Author | Use fallback chain: CLI `--author` → frontmatter `author` → EXTEND.md `default_author` |
|
||||
|
||||
**Auto-Generation Logic**:
|
||||
|
||||
@@ -478,7 +478,7 @@ export function killChrome(chrome: ChildProcess): void {
|
||||
chrome.kill("SIGTERM");
|
||||
} catch {}
|
||||
setTimeout(() => {
|
||||
if (!chrome.killed) {
|
||||
if (chrome.exitCode === null && chrome.signalCode === null) {
|
||||
try {
|
||||
chrome.kill("SIGKILL");
|
||||
} catch {}
|
||||
@@ -486,6 +486,37 @@ export function killChrome(chrome: ChildProcess): void {
|
||||
}, 2_000).unref?.();
|
||||
}
|
||||
|
||||
export async function gracefulKillChrome(
|
||||
chrome: ChildProcess,
|
||||
port?: number,
|
||||
timeoutMs = 6_000,
|
||||
): Promise<void> {
|
||||
if (chrome.exitCode !== null || chrome.signalCode !== null) return;
|
||||
|
||||
const exitPromise = new Promise<void>((resolve) => {
|
||||
chrome.once("exit", () => resolve());
|
||||
});
|
||||
|
||||
killChrome(chrome);
|
||||
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < timeoutMs) {
|
||||
if (chrome.exitCode !== null || chrome.signalCode !== null) return;
|
||||
if (port !== undefined && !await isPortListening(port, 250)) return;
|
||||
|
||||
const exited = await Promise.race([
|
||||
exitPromise.then(() => true),
|
||||
sleep(100).then(() => false),
|
||||
]);
|
||||
if (exited) return;
|
||||
}
|
||||
|
||||
await Promise.race([
|
||||
exitPromise,
|
||||
sleep(250),
|
||||
]);
|
||||
}
|
||||
|
||||
export async function openPageSession(options: OpenPageSessionOptions): Promise<PageSession> {
|
||||
let targetId: string;
|
||||
let createdTarget = false;
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
type WechatUploadAsset,
|
||||
prepareWechatBodyImageUpload,
|
||||
needsWechatBodyImageProcessing,
|
||||
detectImageFormatFromBuffer,
|
||||
} from "./wechat-image-processor.ts";
|
||||
|
||||
interface AccessTokenResponse {
|
||||
@@ -138,6 +139,16 @@ async function loadUploadAsset(
|
||||
contentType = mimeTypes[fileExt] || "image/jpeg";
|
||||
}
|
||||
|
||||
// Detect actual format from magic bytes to fix extension/content-type mismatches
|
||||
// (e.g. CDNs serving WebP for URLs with .png extension)
|
||||
const detected = detectImageFormatFromBuffer(fileBuffer);
|
||||
if (detected && detected.contentType !== contentType) {
|
||||
console.error(`[wechat-api] Format mismatch: ${filename} declared as ${contentType}, actual ${detected.contentType}`);
|
||||
contentType = detected.contentType;
|
||||
fileExt = detected.fileExt;
|
||||
filename = `${path.basename(filename, path.extname(filename))}${detected.fileExt}`;
|
||||
}
|
||||
|
||||
return {
|
||||
buffer: fileBuffer,
|
||||
filename,
|
||||
|
||||
@@ -52,6 +52,39 @@ const MIME_TO_EXT: Record<string, string> = {
|
||||
const JPEG_QUALITY_STEPS = [82, 74, 66, 58, 50, 42, 34];
|
||||
const MAX_WIDTH_STEPS = [2560, 2048, 1600, 1280, 1024, 800, 640, 480];
|
||||
|
||||
/**
|
||||
* Detect actual image format from buffer magic bytes.
|
||||
* Returns corrected { contentType, fileExt } or null if unknown.
|
||||
*/
|
||||
export function detectImageFormatFromBuffer(buffer: Buffer): { contentType: string; fileExt: string } | null {
|
||||
if (buffer.length < 12) return null;
|
||||
|
||||
// WebP: RIFF....WEBP
|
||||
if (
|
||||
buffer[0] === 0x52 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x46 &&
|
||||
buffer[8] === 0x57 && buffer[9] === 0x45 && buffer[10] === 0x42 && buffer[11] === 0x50
|
||||
) {
|
||||
return { contentType: "image/webp", fileExt: ".webp" };
|
||||
}
|
||||
// PNG: 89 50 4E 47
|
||||
if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4e && buffer[3] === 0x47) {
|
||||
return { contentType: "image/png", fileExt: ".png" };
|
||||
}
|
||||
// JPEG: FF D8 FF
|
||||
if (buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) {
|
||||
return { contentType: "image/jpeg", fileExt: ".jpg" };
|
||||
}
|
||||
// GIF: GIF8
|
||||
if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x38) {
|
||||
return { contentType: "image/gif", fileExt: ".gif" };
|
||||
}
|
||||
// BMP: BM
|
||||
if (buffer[0] === 0x42 && buffer[1] === 0x4d) {
|
||||
return { contentType: "image/bmp", fileExt: ".bmp" };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
let webpDecoderReady: Promise<void> | undefined;
|
||||
|
||||
type JimpImage = Awaited<ReturnType<typeof Jimp.read>>;
|
||||
@@ -209,7 +242,8 @@ export async function prepareWechatBodyImageUpload(
|
||||
|
||||
const image = await loadImageForProcessing(asset);
|
||||
const widths = buildCandidateWidths(image.bitmap.width);
|
||||
const preferPng = imageHasTransparency(image) || ensureFileExt(asset) === ".png";
|
||||
const ext = ensureFileExt(asset);
|
||||
const preferPng = imageHasTransparency(image) || ext === ".png" || ext === ".webp";
|
||||
const processingNotes = buildProcessingNotes(asset);
|
||||
|
||||
for (const width of widths) {
|
||||
|
||||
@@ -478,7 +478,7 @@ export function killChrome(chrome: ChildProcess): void {
|
||||
chrome.kill("SIGTERM");
|
||||
} catch {}
|
||||
setTimeout(() => {
|
||||
if (!chrome.killed) {
|
||||
if (chrome.exitCode === null && chrome.signalCode === null) {
|
||||
try {
|
||||
chrome.kill("SIGKILL");
|
||||
} catch {}
|
||||
@@ -486,6 +486,37 @@ export function killChrome(chrome: ChildProcess): void {
|
||||
}, 2_000).unref?.();
|
||||
}
|
||||
|
||||
export async function gracefulKillChrome(
|
||||
chrome: ChildProcess,
|
||||
port?: number,
|
||||
timeoutMs = 6_000,
|
||||
): Promise<void> {
|
||||
if (chrome.exitCode !== null || chrome.signalCode !== null) return;
|
||||
|
||||
const exitPromise = new Promise<void>((resolve) => {
|
||||
chrome.once("exit", () => resolve());
|
||||
});
|
||||
|
||||
killChrome(chrome);
|
||||
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < timeoutMs) {
|
||||
if (chrome.exitCode !== null || chrome.signalCode !== null) return;
|
||||
if (port !== undefined && !await isPortListening(port, 250)) return;
|
||||
|
||||
const exited = await Promise.race([
|
||||
exitPromise.then(() => true),
|
||||
sleep(100).then(() => false),
|
||||
]);
|
||||
if (exited) return;
|
||||
}
|
||||
|
||||
await Promise.race([
|
||||
exitPromise,
|
||||
sleep(250),
|
||||
]);
|
||||
}
|
||||
|
||||
export async function openPageSession(options: OpenPageSessionOptions): Promise<PageSession> {
|
||||
let targetId: string;
|
||||
let createdTarget = false;
|
||||
|
||||
@@ -478,7 +478,7 @@ export function killChrome(chrome: ChildProcess): void {
|
||||
chrome.kill("SIGTERM");
|
||||
} catch {}
|
||||
setTimeout(() => {
|
||||
if (!chrome.killed) {
|
||||
if (chrome.exitCode === null && chrome.signalCode === null) {
|
||||
try {
|
||||
chrome.kill("SIGKILL");
|
||||
} catch {}
|
||||
@@ -486,6 +486,37 @@ export function killChrome(chrome: ChildProcess): void {
|
||||
}, 2_000).unref?.();
|
||||
}
|
||||
|
||||
export async function gracefulKillChrome(
|
||||
chrome: ChildProcess,
|
||||
port?: number,
|
||||
timeoutMs = 6_000,
|
||||
): Promise<void> {
|
||||
if (chrome.exitCode !== null || chrome.signalCode !== null) return;
|
||||
|
||||
const exitPromise = new Promise<void>((resolve) => {
|
||||
chrome.once("exit", () => resolve());
|
||||
});
|
||||
|
||||
killChrome(chrome);
|
||||
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < timeoutMs) {
|
||||
if (chrome.exitCode !== null || chrome.signalCode !== null) return;
|
||||
if (port !== undefined && !await isPortListening(port, 250)) return;
|
||||
|
||||
const exited = await Promise.race([
|
||||
exitPromise.then(() => true),
|
||||
sleep(100).then(() => false),
|
||||
]);
|
||||
if (exited) return;
|
||||
}
|
||||
|
||||
await Promise.race([
|
||||
exitPromise,
|
||||
sleep(250),
|
||||
]);
|
||||
}
|
||||
|
||||
export async function openPageSession(options: OpenPageSessionOptions): Promise<PageSession> {
|
||||
let targetId: string;
|
||||
let createdTarget = false;
|
||||
|
||||
+16
@@ -399,6 +399,22 @@ export async function runConvertCommand(options: ConvertCommandOptions): Promise
|
||||
if (restored) logger.info(`Restored ${adapter.name} session cookies from sidecar.`);
|
||||
}
|
||||
|
||||
if (options.waitMode === "interaction" && adapter.checkLogin) {
|
||||
await context.browser.goto(url.toString(), options.timeoutMs).catch(() => {});
|
||||
const preLogin = await adapter.checkLogin(context);
|
||||
if (preLogin.state !== "logged_in") {
|
||||
didLogin = true;
|
||||
await waitForInteraction(adapter, context, {
|
||||
type: "wait_for_interaction",
|
||||
kind: "login",
|
||||
provider: preLogin.provider ?? adapter.name,
|
||||
prompt: `Please sign in to ${adapter.name === "x" ? "X" : adapter.name} in the opened Chrome window. Extraction will continue automatically once login is detected.`,
|
||||
reason: preLogin.reason ?? `Not logged in to ${adapter.name}`,
|
||||
requiresVisibleBrowser: true,
|
||||
}, options);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.waitMode === "force") {
|
||||
await context.browser.goto(url.toString(), options.timeoutMs).catch(() => {});
|
||||
await waitForForceResume(adapter, context, options);
|
||||
|
||||
@@ -39,6 +39,10 @@ Break down complex content into eye-catching infographic series for Xiaohongshu
|
||||
# Direct input with options
|
||||
/baoyu-xhs-images --style bold --layout comparison
|
||||
[paste content]
|
||||
|
||||
# Non-interactive (for scheduled tasks / automation)
|
||||
/baoyu-xhs-images posts/ai-future/article.md --yes
|
||||
/baoyu-xhs-images posts/ai-future/article.md --yes --preset knowledge-card
|
||||
```
|
||||
|
||||
## Options
|
||||
@@ -48,6 +52,7 @@ Break down complex content into eye-catching infographic series for Xiaohongshu
|
||||
| `--style <name>` | Visual style (see Style Gallery) |
|
||||
| `--layout <name>` | Information layout (see Layout Gallery) |
|
||||
| `--preset <name>` | Style + layout shorthand (see [Style Presets](references/style-presets.md)) |
|
||||
| `--yes` | Non-interactive mode: skip all confirmations. Uses EXTEND.md preferences if found, otherwise uses defaults (no watermark, auto style/layout). Auto-confirms recommended plan (Path A). Suitable for scheduled tasks and automation. |
|
||||
|
||||
## Two Dimensions
|
||||
|
||||
@@ -237,11 +242,11 @@ Copy and track progress:
|
||||
|
||||
```
|
||||
XHS Infographic Progress:
|
||||
- [ ] Step 0: Check preferences (EXTEND.md) ⛔ BLOCKING
|
||||
- [ ] Step 0: Check preferences (EXTEND.md) ⛔ BLOCKING (--yes: use defaults if not found)
|
||||
- [ ] Found → load preferences → continue
|
||||
- [ ] Not found → run first-time setup → MUST complete before Step 1
|
||||
- [ ] Not found → run first-time setup → MUST complete before Step 1 (--yes: skip setup, use defaults)
|
||||
- [ ] Step 1: Analyze content → analysis.md
|
||||
- [ ] Step 2: Smart Confirm ⚠️ REQUIRED
|
||||
- [ ] Step 2: Smart Confirm ⚠️ REQUIRED (--yes: auto-confirm Path A)
|
||||
- [ ] Path A: Quick confirm → generate recommended outline
|
||||
- [ ] Path B: Customize → adjust then generate outline
|
||||
- [ ] Path C: Detailed → 3 outlines → second confirm → generate outline
|
||||
@@ -252,26 +257,30 @@ XHS Infographic Progress:
|
||||
### Flow
|
||||
|
||||
```
|
||||
Input → [Step 0: Preferences] ─┬─ Found → Continue
|
||||
│
|
||||
└─ Not found → First-Time Setup ⛔ BLOCKING
|
||||
│
|
||||
└─ Complete setup → Save EXTEND.md → Continue
|
||||
│
|
||||
┌───────────────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
Analyze → [Smart Confirm] ─┬─ Quick: confirm recommended → outline.md → Generate → Complete
|
||||
│
|
||||
├─ Customize: adjust options → outline.md → Generate → Complete
|
||||
│
|
||||
└─ Detailed: 3 outlines → [Confirm 2] → outline.md → Generate → Complete
|
||||
Input → [--yes?] ─┬─ Yes → [Step 0: Load or defaults] → Analyze → Auto-confirm → Generate → Complete
|
||||
│
|
||||
└─ No → [Step 0: Preferences] ─┬─ Found → Continue
|
||||
│
|
||||
└─ Not found → First-Time Setup ⛔ BLOCKING
|
||||
│
|
||||
└─ Complete setup → Save EXTEND.md → Continue
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
Analyze → [Smart Confirm] ─┬─ Quick: confirm recommended → outline.md → Generate → Complete
|
||||
│
|
||||
├─ Customize: adjust options → outline.md → Generate → Complete
|
||||
│
|
||||
└─ Detailed: 3 outlines → [Confirm 2] → outline.md → Generate → Complete
|
||||
```
|
||||
|
||||
### Step 0: Load Preferences (EXTEND.md) ⛔ BLOCKING
|
||||
|
||||
**Purpose**: Load user preferences or run first-time setup.
|
||||
|
||||
**CRITICAL**: If EXTEND.md not found, MUST complete first-time setup before ANY other questions or steps. Do NOT proceed to content analysis, do NOT ask about style, do NOT ask about layout — ONLY complete the preferences setup first.
|
||||
**`--yes` mode**: If EXTEND.md found → load it. If not found → use built-in defaults (no watermark, style/layout auto-select, language from content). Do NOT run first-time setup, do NOT create EXTEND.md, do NOT ask any questions. Proceed directly to Step 1.
|
||||
|
||||
**CRITICAL** (interactive mode only): If EXTEND.md not found, MUST complete first-time setup before ANY other questions or steps. Do NOT proceed to content analysis, do NOT ask about style, do NOT ask about layout — ONLY complete the preferences setup first.
|
||||
|
||||
Check EXTEND.md existence (priority order):
|
||||
|
||||
@@ -340,7 +349,11 @@ Read source content, save it if needed, and perform deep analysis.
|
||||
|
||||
### Step 2: Smart Confirm ⚠️
|
||||
|
||||
**Purpose**: Present auto-recommended plan, let user confirm or adjust. **Do NOT skip.**
|
||||
**Purpose**: Present auto-recommended plan, let user confirm or adjust.
|
||||
|
||||
**`--yes` mode**: Skip this entire step. Use auto-recommended strategy + style + layout from Step 1 analysis (or `--style`/`--layout`/`--preset` if provided). Generate outline directly using Path A logic → save to `outline.md` → proceed to Step 3. No AskUserQuestion calls.
|
||||
|
||||
**Interactive mode**: Do NOT skip.
|
||||
|
||||
**Auto-Recommendation Logic**:
|
||||
1. Use Auto Selection table to match content signals → best strategy + style + layout
|
||||
@@ -491,7 +504,7 @@ Reference: `references/config/watermark-guide.md`
|
||||
|
||||
**Image Generation Skill Selection**:
|
||||
- Check available image generation skills
|
||||
- If multiple skills available, ask user preference
|
||||
- If multiple skills available: ask user preference (interactive) or use first available skill (`--yes` mode)
|
||||
|
||||
**Session Management**:
|
||||
If image generation skill supports `--sessionId`:
|
||||
|
||||
@@ -2,7 +2,7 @@ import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { findTranscript, parseTranscriptJson3, parseWebVtt } from "./transcript.ts";
|
||||
import { buildTranscriptListFromYtDlp, resolveVideoSource, selectYtDlpTrack } from "./youtube.ts";
|
||||
import { buildTranscriptListFromYtDlp, fetchTranscriptWithFallback, resolveVideoSource, selectYtDlpTrack } from "./youtube.ts";
|
||||
|
||||
test("selectYtDlpTrack prefers json3 over xml and vtt", () => {
|
||||
const track = selectYtDlpTrack([
|
||||
@@ -123,3 +123,61 @@ test("resolveVideoSource falls back to yt-dlp only after fallback-eligible error
|
||||
assert.equal(fallbackCalled, true);
|
||||
assert.equal(source.transcripts[0].languageCode, "en");
|
||||
});
|
||||
|
||||
test("fetchTranscriptWithFallback retries with yt-dlp when InnerTube transcript payload is empty", async () => {
|
||||
const warnings: string[] = [];
|
||||
let fallbackCalled = false;
|
||||
const result = await fetchTranscriptWithFallback(
|
||||
"video12345ab",
|
||||
{
|
||||
kind: "innertube",
|
||||
data: { videoDetails: { title: "Primary" } },
|
||||
transcripts: [{
|
||||
language: "English",
|
||||
languageCode: "en",
|
||||
isGenerated: false,
|
||||
isTranslatable: false,
|
||||
baseUrl: "https://www.youtube.com/api/timedtext?v=video12345ab&lang=en&fmt=json3",
|
||||
translationLanguages: [],
|
||||
}],
|
||||
},
|
||||
{
|
||||
languages: ["en"],
|
||||
translate: "",
|
||||
excludeGenerated: false,
|
||||
excludeManual: false,
|
||||
},
|
||||
async (info) => {
|
||||
if (info.baseUrl.includes("youtube.com/api/timedtext")) {
|
||||
return { snippets: [], language: info.language, languageCode: info.languageCode };
|
||||
}
|
||||
return {
|
||||
snippets: [{ text: "Recovered subtitle", start: 0, duration: 2 }],
|
||||
language: info.language,
|
||||
languageCode: info.languageCode,
|
||||
};
|
||||
},
|
||||
async () => {
|
||||
fallbackCalled = true;
|
||||
return {
|
||||
kind: "yt-dlp",
|
||||
info: { title: "Fallback" },
|
||||
transcripts: [{
|
||||
language: "English",
|
||||
languageCode: "en",
|
||||
isGenerated: false,
|
||||
isTranslatable: false,
|
||||
baseUrl: "https://example.com/subtitles.en.json3",
|
||||
translationLanguages: [],
|
||||
}],
|
||||
};
|
||||
},
|
||||
(message) => warnings.push(message)
|
||||
);
|
||||
|
||||
assert.equal(fallbackCalled, true);
|
||||
assert.equal(result.source.kind, "yt-dlp");
|
||||
assert.equal(result.snippets.length, 1);
|
||||
assert.equal(result.snippets[0].text, "Recovered subtitle");
|
||||
assert.match(warnings[0] || "", /Retrying with yt-dlp fallback/);
|
||||
});
|
||||
|
||||
@@ -13,13 +13,13 @@ import {
|
||||
registerVideoDir,
|
||||
resolveBaseDir,
|
||||
} from "./storage.ts";
|
||||
import { findTranscript, formatListOutput, formatMarkdown, formatSrt, segmentIntoSentences } from "./transcript.ts";
|
||||
import { formatListOutput, formatMarkdown, formatSrt, segmentIntoSentences } from "./transcript.ts";
|
||||
import type { Options, Sentence, Snippet, VideoMeta, VideoResult } from "./types.ts";
|
||||
import {
|
||||
buildVideoMeta,
|
||||
buildVideoMetaFromYtDlp,
|
||||
downloadCoverImage,
|
||||
fetchTranscriptSnippets,
|
||||
fetchTranscriptWithFallback,
|
||||
fetchVideoSource,
|
||||
getThumbnailUrls,
|
||||
getYtDlpThumbnailUrls,
|
||||
@@ -31,10 +31,12 @@ async function fetchAndCache(
|
||||
baseDir: string,
|
||||
opts: Options
|
||||
): Promise<{ meta: VideoMeta; snippets: Snippet[]; sentences: Sentence[]; videoDir: string }> {
|
||||
const source = await fetchVideoSource(videoId);
|
||||
const requestedLanguages = source.kind === "yt-dlp" && opts.translate ? [opts.translate] : opts.languages;
|
||||
const transcript = findTranscript(source.transcripts, requestedLanguages, opts.excludeGenerated, opts.excludeManual);
|
||||
const result = await fetchTranscriptSnippets(transcript, source.kind === "yt-dlp" ? undefined : opts.translate || undefined);
|
||||
const initialSource = await fetchVideoSource(videoId);
|
||||
const { source, transcript, snippets, language, languageCode } = await fetchTranscriptWithFallback(
|
||||
videoId,
|
||||
initialSource,
|
||||
opts
|
||||
);
|
||||
const description = source.kind === "yt-dlp"
|
||||
? source.info.description || ""
|
||||
: source.data?.videoDetails?.shortDescription || "";
|
||||
@@ -42,21 +44,21 @@ async function fetchAndCache(
|
||||
? Number(source.info.duration || 0)
|
||||
: parseInt(source.data?.videoDetails?.lengthSeconds || "0");
|
||||
const chapters = parseChapters(description, duration);
|
||||
const language = {
|
||||
code: result.languageCode,
|
||||
name: result.language,
|
||||
const languageMeta = {
|
||||
code: languageCode,
|
||||
name: language,
|
||||
isGenerated: transcript.isGenerated,
|
||||
};
|
||||
const meta = source.kind === "yt-dlp"
|
||||
? buildVideoMetaFromYtDlp(source.info, videoId, language, chapters)
|
||||
: buildVideoMeta(source.data, videoId, language, chapters);
|
||||
? buildVideoMetaFromYtDlp(source.info, videoId, languageMeta, chapters)
|
||||
: buildVideoMeta(source.data, videoId, languageMeta, chapters);
|
||||
|
||||
const videoDir = registerVideoDir(videoId, slugify(meta.channel), slugify(meta.title), baseDir);
|
||||
ensureDir(join(videoDir, "meta.json"));
|
||||
|
||||
writeFileSync(join(videoDir, "transcript-raw.json"), JSON.stringify(result.snippets, null, 2));
|
||||
writeFileSync(join(videoDir, "transcript-raw.json"), JSON.stringify(snippets, null, 2));
|
||||
|
||||
const sentences = segmentIntoSentences(result.snippets);
|
||||
const sentences = segmentIntoSentences(snippets);
|
||||
writeFileSync(join(videoDir, "transcript-sentences.json"), JSON.stringify(sentences, null, 2));
|
||||
|
||||
const imagePath = join(videoDir, "imgs", "cover.jpg");
|
||||
@@ -69,7 +71,7 @@ async function fetchAndCache(
|
||||
|
||||
writeFileSync(join(videoDir, "meta.json"), JSON.stringify(meta, null, 2));
|
||||
|
||||
return { meta, snippets: result.snippets, sentences, videoDir };
|
||||
return { meta, snippets, sentences, videoDir };
|
||||
}
|
||||
|
||||
async function processVideo(videoId: string, opts: Options): Promise<VideoResult> {
|
||||
|
||||
@@ -2,12 +2,13 @@ import { spawnSync } from "child_process";
|
||||
import { writeFileSync } from "fs";
|
||||
|
||||
import { makeError, normalizeError, normalizePublishDate, shouldTryAlternateClient, shouldTryYtDlpFallback } from "./shared.ts";
|
||||
import { parseTranscriptPayload } from "./transcript.ts";
|
||||
import { findTranscript, parseTranscriptPayload } from "./transcript.ts";
|
||||
import type {
|
||||
Chapter,
|
||||
InnerTubeClient,
|
||||
InnerTubeSession,
|
||||
LanguageMeta,
|
||||
Options,
|
||||
Snippet,
|
||||
TranscriptInfo,
|
||||
VideoMeta,
|
||||
@@ -219,6 +220,68 @@ export async function fetchTranscriptSnippets(
|
||||
};
|
||||
}
|
||||
|
||||
function buildYtDlpVideoSource(videoId: string, info: YtDlpInfo): VideoSource {
|
||||
const transcripts = buildTranscriptListFromYtDlp(info);
|
||||
if (!transcripts.length) throw makeError(`Transcripts disabled for ${videoId}`, "TRANSCRIPTS_DISABLED");
|
||||
return { kind: "yt-dlp", info, transcripts };
|
||||
}
|
||||
|
||||
function getRequestedLanguages(
|
||||
source: VideoSource,
|
||||
opts: Pick<Options, "languages" | "translate">
|
||||
): string[] {
|
||||
return source.kind === "yt-dlp" && opts.translate ? [opts.translate] : opts.languages;
|
||||
}
|
||||
|
||||
export async function fetchTranscriptWithFallback(
|
||||
videoId: string,
|
||||
source: VideoSource,
|
||||
opts: Pick<Options, "languages" | "translate" | "excludeGenerated" | "excludeManual">,
|
||||
fetchSnippets: (
|
||||
info: TranscriptInfo,
|
||||
translateTo?: string
|
||||
) => Promise<{ snippets: Snippet[]; language: string; languageCode: string }> = fetchTranscriptSnippets,
|
||||
fetchFallbackSource: (videoId: string) => Promise<VideoSource> | VideoSource = (requestedVideoId) =>
|
||||
buildYtDlpVideoSource(requestedVideoId, fetchYtDlpInfo(requestedVideoId)),
|
||||
logWarning: (message: string) => void = (message) => console.error(message)
|
||||
): Promise<{
|
||||
source: VideoSource;
|
||||
transcript: TranscriptInfo;
|
||||
snippets: Snippet[];
|
||||
language: string;
|
||||
languageCode: string;
|
||||
}> {
|
||||
const transcript = findTranscript(
|
||||
source.transcripts,
|
||||
getRequestedLanguages(source, opts),
|
||||
opts.excludeGenerated,
|
||||
opts.excludeManual
|
||||
);
|
||||
const result = await fetchSnippets(transcript, source.kind === "yt-dlp" ? undefined : opts.translate || undefined);
|
||||
if (result.snippets.length > 0) return { source, transcript, ...result };
|
||||
|
||||
if (source.kind === "yt-dlp") {
|
||||
throw makeError(`Transcript fetch returned empty snippets for ${videoId}`, "EMPTY_TRANSCRIPT");
|
||||
}
|
||||
|
||||
logWarning(`Warning (${videoId}): Transcript fetch returned empty snippets. Retrying with yt-dlp fallback.`);
|
||||
const fallbackSource = await fetchFallbackSource(videoId);
|
||||
const fallbackTranscript = findTranscript(
|
||||
fallbackSource.transcripts,
|
||||
getRequestedLanguages(fallbackSource, opts),
|
||||
opts.excludeGenerated,
|
||||
opts.excludeManual
|
||||
);
|
||||
const fallbackResult = await fetchSnippets(
|
||||
fallbackTranscript,
|
||||
fallbackSource.kind === "yt-dlp" ? undefined : opts.translate || undefined
|
||||
);
|
||||
if (!fallbackResult.snippets.length) {
|
||||
throw makeError(`Transcript fetch returned empty snippets for ${videoId} after yt-dlp fallback`, "EMPTY_TRANSCRIPT");
|
||||
}
|
||||
return { source: fallbackSource, transcript: fallbackTranscript, ...fallbackResult };
|
||||
}
|
||||
|
||||
export function detectYtDlpCommand(): { command: string; args: string[]; label: string } | null {
|
||||
if (cachedYtDlpCommand !== undefined) return cachedYtDlpCommand;
|
||||
const candidates = [
|
||||
@@ -366,10 +429,7 @@ export async function resolveVideoSource(
|
||||
const normalized = normalizeError(error);
|
||||
if (!shouldTryYtDlpFallback(normalized)) throw normalized;
|
||||
logWarning(`Warning (${videoId}): ${normalized.message}. Retrying with yt-dlp fallback.`);
|
||||
const info = fetchFallback(videoId);
|
||||
const transcripts = buildTranscriptListFromYtDlp(info);
|
||||
if (!transcripts.length) throw makeError(`Transcripts disabled for ${videoId}`, "TRANSCRIPTS_DISABLED");
|
||||
return { kind: "yt-dlp", info, transcripts };
|
||||
return buildYtDlpVideoSource(videoId, fetchFallback(videoId));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user