# Story Cover

> 小说封面生成。根据书名、作者名自动分析题材风格，调用 GPT-Image-2 直接生成含标题和署名的专业级网文封面。 触发方式：/story-cover、/封面、「帮我做个封面」「生成封面图」「做个小说封面」「封面设计」

- Skill: `duiniwukenaihe/story-cover` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add duiniwukenaihe/story-cover`
- Raw SKILL.md: https://api.skillmd.com/api/skills/duiniwukenaihe/story-cover/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: duiniwukenaihe (https://skillmd.com/u/duiniwukenaihe)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/duiniwukenaihe/story-cover

---


# story-cover：小说封面生成

## L3 Workflow Contract

### Inputs From story-workflow

只接受 `workflow_type=cover` 的阶段包，读取书名、作者、平台规格、视觉方向、目标文件、覆盖策略、确认 token、`workflow_id` 和 Runner 的 `memory_context`。

### Outputs To story-workflow

每阶段回传标准 result packet，包含提示词/图片产物、实际写入路径、尺寸与文字可读性证据、确认绑定、验证结果和断点。模块不能自行宣布整个 workflow 完成。

### Memory Policy

`cover` 使用 `optional` 只读策略，只消费题材定位和已确认视觉偏好，不产生叙事 `memory_updates`，也不得反向修改人物、伏笔、大纲或正文。

你是小说封面设计师。根据书名和题材，调用 GPT-Image-2 一次性生成包含书名和作者名的完整封面。

**核心原则：封面是读者的第一印象，一眼传达题材和氛围。**

## 生产边界：封面只消费设定，不改叙事

story-cover 不参与叙事连续性治理，只消费书名、作者名、题材、视觉关键词、平台尺寸和用户提供的设定摘要。封面生成不能反向修改本书大纲、人物关系、伏笔、正文或章节承诺；如果封面构想暴露出题材定位不清，只能提示用户回到 `story-long-write` / `story-short-write` 做定位确认。

## Workflow 确认上下文硬门禁

任何生成或覆盖都只能由 `story-workflow` 的 `cover` workflow 调用。进入 Step 3 前必须读取当前任务的 `stage_execution`，并同时满足：

- `stage_execution.status=running`，`stage_id` 与 `step_id` 都是 `generate_cover_execute`；
- `stage_execution.confirmation_context.status=confirmed`，其中 `confirmation_token` 与 `stage_execution.confirmation_token`、持久化 `last_selection.confirmation_token` 完全一致；
- `confirmation_context.expires_at` 尚未过期，workflow、stage、step、selection 和可见候选哈希仍与当前任务一致；
- `confirmation_context.operation=generate` 时只允许生成新封面；`operation=overwrite` 时只允许覆盖确认上下文中指定的现有目标文件。

生成新封面和覆盖现有封面是两种独立确认，token 不得跨阶段、跨目标或跨操作复用。没有来自 workflow 的 `confirmation_context` / `confirmation_token`、token 过期、目标不一致，或用户裸调用本模块时，必须返回 `blocked_confirmation_required`，停靠并询问用户；此时不得调用 `images/generations`、`images/edits` 或写入任何封面文件。

宿主把已验证字段投影为 `WORKFLOW_CONFIRMATION_CONTEXT`（完整 JSON）和 `WORKFLOW_CONFIRMATION_TOKEN` 后，Step 3 的两种 API 调用都先执行以下门禁；失败立即退出，不得继续请求图像 API：

```bash
: "${WORKFLOW_CONFIRMATION_CONTEXT:?blocked_confirmation_required: 缺少 workflow confirmation_context}"
: "${WORKFLOW_CONFIRMATION_TOKEN:?blocked_confirmation_required: 缺少 workflow confirmation_token}"
jq -e --arg token "$WORKFLOW_CONFIRMATION_TOKEN" '
  .status == "confirmed" and
  .stage_id == "generate_cover_execute" and
  .step_id == "generate_cover_execute" and
  .confirmation_token == $token and
  (.operation == "generate" or .operation == "overwrite") and
  ((.expires_at | fromdateiso8601) > now)
' <<<"$WORKFLOW_CONFIRMATION_CONTEXT" >/dev/null || {
  echo "blocked_confirmation_required: workflow 确认缺失、过期或不匹配" >&2
  exit 2
}
```

## 全局可见长回复污染门禁

封面题材分析、视觉方案、提示词解释和生成结果总结超过 800 中文字符时，不得直接输出长报告。先写入 `追踪/输出门禁/.visible_reply_draft_{YYYYMMDD_HHMMSS}.md`（没有书籍项目时用当前工作目录），运行 `node scripts/output-pollution-check.js --learn --project-root <project-root> <draft-file>`；命中重复填充、术语循环或已学习污染词组时，删除污染段并重写，复扫到 0 后再回复。若污染已经开始输出，立即停止并落盘 `paused_after_output_pollution`。

---

## 环境变量

| 变量 | 必填 | 默认 | 说明 |
|:-----|:----:|:-----|:-----|
| `GPT_IMAGE_API_KEY` | ✅ | — | OpenAI 或兼容代理的 API Key |
| `GPT_IMAGE_BASE_URL` | | `https://api.openai.com/v1` | 兼容代理时改这个 |
| `GPT_IMAGE_MODEL` | | `gpt-image-2` | 仅在测试新模型时覆盖 |
| `GPT_IMAGE_SIZE` | | `1024x1536` | 目标比例提示（番茄 3:4 → `768x1024`，默认 2:3 → `1024x1536`）。官方 gpt-image-2 支持 16 倍数尺寸，但很多中转代理会忽略 size；平台尺寸由 Step 3.5 兜底 |
| `UPLOAD_SIZE` | | — | 平台固定上传像素（番茄 `600x800`）；设置后 Step 3.5 居中裁剪+缩放出上传版 |
| `BOOK_DIR` | ✅ | — | 输出目录，建议 `./covers/<书名>` |
| `REF_IMAGE` | | — | 参考图本地路径或 URL；设置后走 `images/edits` 图生图 |

> 备注：`gpt-image-2` 始终返回 base64，请求体不要带 `response_format`（旧 DALL-E 参数，gpt-image 系列不支持）。

---

## 生成流程

### Step 1：收集信息

必填：书名、作者名（笔名）、目标平台、输出目录 `BOOK_DIR`（建议 `./covers/<书名>`，调用前 export）
选填：参考图 `REF_IMAGE`（本地路径或 URL，设置后切换到图生图）、风格偏好、尺寸

> **书名和作者名（笔名）是封面必需信息**：书名或作者名（笔名）缺失时，必须先向用户补问，不得编造、留空或用“佚名”占位。

**按目标平台定封面比例和上传尺寸**。番茄小说上传规格常用 600×800，是 3:4；若直接用 2:3 原图，平台二次裁切可能切掉书名或笔名。

| 平台 | 上传尺寸 | 比例 | 建议 `GPT_IMAGE_SIZE` |
|:-----|:--------|:-----|:-------------------|
| 番茄小说 | 600×800 | 3:4 | `768x1024` |
| 其他平台（默认竖版） | 按平台规格 | 2:3 | `1024x1536` |

`GPT_IMAGE_SIZE` 只作为生成比例提示；如果平台有固定上传像素，再设置 `UPLOAD_SIZE`（番茄 `600x800`）。平台尺寸最终由 Step 3.5 居中裁剪+缩放保证，不依赖代理是否尊重 size。平台与题材风格见 [references/cover-styles.md](references/cover-styles.md)。

### Step 1.5：题材判定

扫描书名（必要时简介）中的关键词，对照 [references/cover-styles.md](references/cover-styles.md) 的「题材推断规则」表选定题材。

- 单题材命中 → 直接采用
- 多题材命中 → 按优先级取一：仙侠 > 西幻 > 古言 > 现言 > 都市 > 悬疑 > 科幻 > 历史 > 灵异 > 轻小说
- 零命中 → 默认 `都市`

### Step 2：构建提示词

提示词 = **文字层** + **风格层** + **画面层**，全部用英文编写。

#### 文字层：书名 + 作者名字体设计

在提示词中直接包含中文书名和作者名，GPT-Image-2 可直接渲染。**重点描述字体风格**：

```
Title text '书名' at top center in [书名字体风格].
Author name '作者名' at bottom center in [作者名字体风格].
```

#### 书名字体风格

| 题材 | 描述关键词 |
|:-----|:-----------|
| 玄幻/仙侠 | `bold golden brush calligraphy with metallic glow and sharp strokes` |
| 都市 | `modern bold sans-serif with metallic silver finish` |
| 古言/宫斗 | `elegant golden traditional Kai script with ornate decoration` |
| 现言/甜宠 | `soft rounded handwritten style in white with pink glow` |
| 悬疑/推理 | `distorted bold cracked letters in blood red` |
| 科幻/末世 | `neon glowing futuristic font in electric blue` |
| 西幻 | `metallic embossed fantasy lettering with glow effect` |
| 历史/军事 | `heavy stone-carved seal script in deep red` |
| 灵异/恐怖 | `eerie dripping handwritten font in sickly green` |
| 轻小说 | `colorful cartoon outlined bubbly font` |

#### 作者名字体风格（重点：作者名必须精心设计，不能只是"小字"）

作者名虽小，但是封面专业感的关键。必须指定：**字体 + 颜色 + 装饰元素**，让作者名与书名风格呼应但不抢焦点。

| 题材 | 作者名风格提示词 |
|:-----|:----------------|
| 玄幻/仙侠 | `small refined white serif text with faint golden glow, flanked by delicate cloud-scroll ornaments on both sides, resting on a thin horizontal gold line` |
| 都市 | `small clean white modern text with subtle drop shadow, positioned above a thin silver horizontal divider line` |
| 古言/宫斗 | `small elegant dark red traditional text inside a thin golden rectangular border frame with corner decorations` |
| 现言/甜宠 | `small soft pink-white handwritten text with a tiny heart motif on the left side, light sparkle effect` |
| 悬疑/推理 | `small pale grey text with slight blur effect, almost hidden in the shadows, a thin cracked line underneath` |
| 科幻/末世 | `small crisp white monospace text with subtle cyan scanline overlay, flanked by small geometric brackets` |
| 西幻 | `small bronze medieval script text with aged parchment texture, enclosed in a small decorative shield or banner shape` |
| 历史/军事 | `small dignified white Song typeface text above a double horizontal line in dark red` |
| 灵异/恐怖 | `small faded grey-green text slightly tilted, with a thin dripping ink line above` |
| 轻小说 | `small playful rounded white text with pastel color outline, tiny star decorations on both sides` |

**作者名通用规则**：
- 大小：`small`（不能太大抢书名焦点，也不能太小看不清）
- 位置：`at bottom center`，与画面底部保持适当间距
- 必须有装饰元素：线条/边框/小图标/光效中至少一种
- 颜色与背景形成对比但不刺眼

#### 风格层：平台风格

平台风格的描述关键词统一来自 [references/cover-styles.md](references/cover-styles.md) 的「平台风格」节，按目标平台直接取对应关键词串使用，不在本文件维护副本以免与参考文件漂移。

#### 画面层：题材 + 构图

从 [references/cover-styles.md](references/cover-styles.md) 读取题材对应的风格标签、色彩、人物、背景描述。

构图变体（首次输出 2-3 个方案）：

| 方案 | 构图 | 适合题材 |
|:-----|:-----|:---------|
| A | 人物特写 + 场景 | 全题材通用 |
| B | 全身像 + 动态姿势 | 玄幻、都市、西幻 |
| C | 纯场景/氛围图 | 悬疑、科幻、历史 |

#### 完整提示词模板

```
Chinese web novel cover design, [平台风格].
Title text '{书名}' at top center in [书名字体风格].
Author name '{作者名}' at bottom center in [作者名字体风格 — 从上表选择].
[题材风格标签]. [人物描述]. [背景描述].
[色彩指令]. [光效指令].
Professional book cover, high detail digital painting, portrait [平台比例：番茄=3:4，默认=2:3] ratio, keep title and author name inside the central safe area away from edges (inner about 85%), no watermark
```

#### 提示词技巧（实测验证）

- 人物描述越具体越好：服饰、姿态、发型、表情、道具每个维度都指定
- 背景分层：前景（人物）→ 中景（场景）→ 远景（氛围）
- 光效是指定光源方向 + 颜色（如 `dramatic golden light from above`）
- 用 `digital painting style` 而非 `photo`，避免真人照片感

### Step 3：调用 API 并保存

`gpt-image-2` 始终返回 base64，请求体不要带 `response_format`。`$PROMPT` 为 Step 2 拼出的完整提示词。

两种调用方式二选一：未设置 `REF_IMAGE` → 走「文生图」；设置了 → 走「图生图」。

#### 文生图（默认）

```bash
set -euo pipefail
: "${GPT_IMAGE_API_KEY:?请设置 export GPT_IMAGE_API_KEY=你的key}"
: "${PROMPT:?请先 export PROMPT=Step 2 拼好的完整提示词}"
BASE_URL="${GPT_IMAGE_BASE_URL:-https://api.openai.com/v1}"
MODEL="${GPT_IMAGE_MODEL:-gpt-image-2}"
SIZE="${GPT_IMAGE_SIZE:-1024x1536}"
BOOK_DIR="${BOOK_DIR:?请先 export BOOK_DIR=./covers/<书名>}"

mkdir -p "$BOOK_DIR/封面"

# 自增版本号，避免覆盖之前生成的封面
i=1
while [ -f "$BOOK_DIR/封面/封面_v${i}.png" ]; do i=$((i+1)); done
OUT="$BOOK_DIR/封面/封面_v${i}.png"
RESP=$(mktemp)
trap 'rm -f "$RESP"' EXIT

# 用 jq 拼 JSON 体，避免 PROMPT 里的引号/换行/中文把 shell 字符串撑破
BODY=$(jq -n \
  --arg m "$MODEL" \
  --arg p "$PROMPT" \
  --arg s "$SIZE" \
  '{model:$m, prompt:$p, size:$s}')

curl -fsS --max-time 180 --retry 2 --retry-delay 5 \
  "$BASE_URL/images/generations" \
  -H "Authorization: Bearer $GPT_IMAGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d "$BODY" > "$RESP"

# API 出错时早退，避免把 error JSON 当成 base64 写成损坏 PNG
if jq -e '.error' "$RESP" >/dev/null 2>&1; then
  echo "API error:" >&2
  jq '.error' "$RESP" >&2
  exit 1
fi

# `// empty` 让缺失字段输出空串而非 "null"，配合下面的 -s 检查避免写出 3 字节假 PNG
jq -er '.data[0].b64_json // empty' "$RESP" | base64 --decode > "$OUT"
[ -s "$OUT" ] || { echo "empty or malformed output: $OUT" >&2; head -c 300 "$RESP" >&2; exit 1; }

# 落地提示词副本，方便迭代时基于上一次微调
printf '%s\n' "$PROMPT" > "${OUT%.png}.prompt.txt"

file "$OUT"
ls -lt "$BOOK_DIR/封面/"
```

#### 图生图（提供参考图时）

`/v1/images/edits` 走 `multipart/form-data`，**不能** 用 `Content-Type: application/json`。文本字段用 `--form-string`（避免 `@` 被误判为文件引用），图片字段用 `-F image=@path`。

```bash
set -euo pipefail
: "${GPT_IMAGE_API_KEY:?请设置 export GPT_IMAGE_API_KEY=你的key}"
: "${PROMPT:?请先 export PROMPT=Step 2 拼好的完整提示词}"
BASE_URL="${GPT_IMAGE_BASE_URL:-https://api.openai.com/v1}"
MODEL="${GPT_IMAGE_MODEL:-gpt-image-2}"
SIZE="${GPT_IMAGE_SIZE:-1024x1536}"
BOOK_DIR="${BOOK_DIR:?请先 export BOOK_DIR=./covers/<书名>}"
REF_IMAGE="${REF_IMAGE:?请先 export REF_IMAGE=本地路径或 URL}"

mkdir -p "$BOOK_DIR/封面"

# 自增版本号
i=1
while [ -f "$BOOK_DIR/封面/封面_v${i}.png" ]; do i=$((i+1)); done
OUT="$BOOK_DIR/封面/封面_v${i}.png"
RESP=$(mktemp)
REF_TMP=""
trap '[ -n "$REF_TMP" ] && rm -f "$REF_TMP"; rm -f "$RESP"' EXIT

# URL 先下载到临时文件，本地路径直接用。用裸 mktemp 以保证 macOS/Linux 行为一致。
case "$REF_IMAGE" in
  http://*|https://*)
    REF_TMP=$(mktemp)
    curl -fsSL --max-time 60 -o "$REF_TMP" "$REF_IMAGE"
    REF_LOCAL="$REF_TMP"
    ;;
  *)
    [ -f "$REF_IMAGE" ] || { echo "参考图不存在: $REF_IMAGE" >&2; exit 1; }
    REF_LOCAL="$REF_IMAGE"
    ;;
esac

curl -fsS --max-time 240 --retry 2 --retry-delay 5 \
  "$BASE_URL/images/edits" \
  -H "Authorization: Bearer $GPT_IMAGE_API_KEY" \
  --form-string "model=$MODEL" \
  --form-string "size=$SIZE" \
  --form-string "prompt=$PROMPT" \
  -F "image=@$REF_LOCAL" > "$RESP"

if jq -e '.error' "$RESP" >/dev/null 2>&1; then
  echo "API error:" >&2
  jq '.error' "$RESP" >&2
  exit 1
fi

# `// empty` 让缺失字段输出空串而非 "null"，配合 -s 检查避免写出 3 字节假 PNG
jq -er '.data[0].b64_json // empty' "$RESP" | base64 --decode > "$OUT"
[ -s "$OUT" ] || { echo "empty or malformed output: $OUT" >&2; head -c 300 "$RESP" >&2; exit 1; }

printf '%s\n' "$PROMPT"    > "${OUT%.png}.prompt.txt"
printf '%s\n' "$REF_IMAGE" > "${OUT%.png}.ref.txt"

file "$OUT"
ls -lt "$BOOK_DIR/封面/"
```

### Step 3.5：导出平台上传尺寸（平台有固定像素时）

设置 `UPLOAD_SIZE` 后，把原图**居中裁剪+缩放**成平台精确像素，不拉伸变形，避免平台再次裁掉书名或笔名。原图保留，另存 `_上传` 版，例如 `封面_v${i}_上传.png`。

```bash
SRC="${OUT:-$(ls -t "${BOOK_DIR:-.}"/封面/封面_v*.png 2>/dev/null | grep -v _上传 | head -1)}"
TARGET="${UPLOAD_SIZE:-}"   # 番茄=600x800；未设则跳过
if [ -n "$TARGET" ] && [ -f "$SRC" ]; then
  UP="${SRC%.png}_上传.png"
  W="${TARGET%x*}"
  H="${TARGET#*x}"
  if command -v magick >/dev/null 2>&1; then
    magick "$SRC" -resize "${W}x${H}^" -gravity center -extent "${W}x${H}" "$UP"
  elif command -v convert >/dev/null 2>&1; then
    convert "$SRC" -resize "${W}x${H}^" -gravity center -extent "${W}x${H}" "$UP"
  elif command -v sips >/dev/null 2>&1; then
    cp "$SRC" "$UP"
    sw=$(sips -g pixelWidth "$UP" | awk '/pixelWidth/{print $NF}')
    sh=$(sips -g pixelHeight "$UP" | awk '/pixelHeight/{print $NF}')
    if [ $((sw*H)) -ge $((sh*W)) ]; then
      sips --resampleHeight "$H" "$UP" >/dev/null
    else
      sips --resampleWidth "$W" "$UP" >/dev/null
    fi
    sips -c "$H" "$W" "$UP" >/dev/null
  else
    echo "无 magick/convert/sips，跳过；请手动把 $SRC 居中裁剪+缩放到 $TARGET 再上传" >&2
  fi
  [ -f "$UP" ] && file "$UP"
fi
```

> 书名和笔名已在提示词里要求留中心安全区，居中裁剪不会切到。

### Step 4：质量检查 + 迭代

| 检查项 | 标准 |
|:-------|:-----|
| 文字渲染 | 书名清晰可辨，字体风格匹配题材 |
| 题材匹配 | 视觉风格与书名题材一致 |
| 构图合理 | 主体突出，文字不遮挡核心画面 |
| 平台适配 | 符合目标平台的封面风格调性 |
| 平台尺寸 | 上传版比例与平台一致；缩放到上传尺寸后书名、笔名完整可见、未被裁切 |

不满意时调整方向：更换构图、调整色调、换字体风格、换平台风格。

---

## 参考资料

| 文件 | 何时加载 |
|:-----|:---------|
| [references/cover-styles.md](references/cover-styles.md) | 题材→视觉风格映射、平台风格详情、提示词模板 |

---

## 语言

- 跟随用户的语言回复，用户用什么语言就用什么语言回复
- 中文回复遵循《中文文案排版指北》

