Skill: velmavalienteqejimu22-jpg/xiaohongshu-cover-gen (version 1.0.0)

--- name: xiaohongshu-cover-gen description: "当用户需要为小红书帖子生成封面图和内容图卡时，加载此 Skill。触发词：小红书配图、封面生成、图卡设计、XHS cover、social media image。" --- # Skill：小红书配图生成 > 版本：v1.0（经 5 轮实战验证，6 次翻车迭代沉淀） > 适用平台：小红书（XHS / RedNote） > 生图工具：Lovart（主力）+ Python PIL（辅助） > 浏览器自动化：agent-browser --- ## Quick Reference | Situation | Action | |-----------|--------| | 收到帖子要配图 | 先走阶段 0 精准调研（10min 封顶），不要直接出方案 | | 构思封面创意 | 从用户情绪出发（好奇/惊讶/想要），不是从技术概念出发 | | 写 Lovart prompt | 过 8 项自检 checklist，逐字指定标题文字 | | Lovart 页面加载超时 | 两步导航法：about:blank → JS eval 跳转 | | cookies set 报错 | 改用 JS document.cookie 注入 | | 弹窗关不掉 | 先 snapshot -i 确认层级，从最外层（Close）关起 | | prompt 太长报错 | 分段 type（每段 < 1024 字节） | | 等待生图超时 | 分段 wait（每段 ≤ 20s）+ 截图轮询 | | 图片下载 SSL 报错 | 用 Node.js（rejectUnauthorized: false） | | 用户否定封面 | 走「否定后快速迭代」子流程（≤10min） | --- ## Background ### 为什么需要这个 Skill 每条小红书帖子都需要配图，但： - 每次从零开始想风格、写 prompt、调图 → 效率低且质量不稳定 - AI 默认审美（居中标题 + 渐变背景 + 装饰图标）= 小红书用户秒划走 - 小红书信息流中，每张封面只有 **0.5 秒** 的生存时间 这个 Skill 将"分析帖子 → 审美调研 → 确定方案 → 生成图片"整个流程标准化，核心价值是 **5 轮实战翻车经验的沉淀**。 ### 输入 / 输出 ``` 输入： - 帖子正文 / 图卡文案 - 用户的 IP 风格参考（品牌调性、历史封面、视觉偏好） - 用户提供的素材图（如有） 输出： - 每张图的配图方案（用途、构图、风格、关键元素） - 每张图的生图 prompt（适配 Lovart） - 生成后的图片文件 成功标准： - 封面通过"0.5 秒测试" - 图片像"朋友在分享"，不像"AI 批量生成的模板" - 整套图片风格统一 ``` ### 最容易翻车的 3 个地方 1. **审美方向错误**：从"技术概念"出发而非"用户情绪"出发（第5轮翻车教训） 2. **文图不一致**：标题提到某人/某品牌，画面里却没有对应视觉 3. **Logo/品牌元素凭想象猜**：AI 不知道 logo 长什么样，会胡编乱造 --- ## Solution: Step-by-Step ### 阶段 0：精准调研（10 分钟封顶） > ⚠️ **翻车教训**：收到帖子后直接出配图方案 = 纯靠记忆拼凑 = AI 模板垃圾。 > 必须先做精准调研。**不通过就不能进入阶段 1。** **时间预算**：≤ 10 分钟 **3 步走**： | Step | 时间 | 动作 | |------|------|------| | 1 | 3min | 翻阅审美积累文档，看有没有和本帖相关的已有灵感 | | 2 | 4min | Dribbble + 小红书各搜 1-2 个关键词，快速截图记录 | | 3 | 3min | 如果涉及特定品牌/产品，去官网截图确认品牌色 + logo | **调研平台详见** → [`references/research-platforms.md`](references/research-platforms.md) **调研产出**（必须输出才能进入阶段 1）： ```markdown ## 视觉方向决策 ### 调研数据总结 - Dribbble 看了 X 个关键词，Y 个作品 - 小红书看了 X 个帖子，高赞封面共性：... - 品牌官网：品牌色 #XXX、字体 XXX ### 可行的视觉方向（2-3 个） - 方向 A：...（推荐度 ⭐⭐⭐⭐⭐） - 方向 B：...（推荐度 ⭐⭐⭐⭐） ### 选择的方向 + 理由 ### Design Token（背景色/主色/点缀色/字体） ``` --- ### 维度 2：定期审美积累（独立定时任务） > 维度 1（精准调研）是每次配图前的战术动作；维度 2 是持续充电的战略动作。 **频率**：每天或每 3 天一次 **时间**：15-20 分钟 | Step | 时间 | 动作 | |------|------|------| | 1 | 5min | Dribbble 趋势浏览（各风格广泛看） | | 2 | 5min | 小红书高赞封面浏览（1000赞+ 分析共性） | | 3 | 5min | 品牌/产品设计语言收集 | | 4 | 5min | 更新审美积累文档 | **产出**：审美积累文档新增 5-10 条记录 --- ### 阶段 1：帖子分析 → 配图方案 1. **判断帖子类型**：产品展示 / 教程分享 / 方法论 / 观点趋势 2. **确定图片数量和用途**：图 1 = 封面（决定生死），图 2-N = 内容图卡 3. **对每张图给出配图方案**：传达什么 / 构图方向 / 风格关键词 / 是否需要素材 / 文字内容 4. **封面 0.5 秒测试**：用户看到这张封面 0.5 秒的反应是"我也想要"还是"这是在讲什么"？后者 → 方案不合格 --- ### 阶段 2：封面方案快速确认 + 素材需求前置 **核心目标**：快速拿到用户确认 + 提前告知素材需求。 1. **一次性展示方案核心信息**： - 封面核心创意（一句话） - 关键视觉元素（3-5 个词） - 风格方向 - 预期情绪（好奇 / 惊讶 / 想拥有 / 实用感） 2. **前置告知素材需求**（不要等到阶段 3 才提）： - 需要用户提供的素材清单 - 没有素材的替代方案 3. **等待确认**：✅ 通过 → 阶段 3 | 🔄 修改 → 调整后重新确认 | ❌ 否定 → 回阶段 1 --- ### 阶段 3：生图 prompt 编写 **每张图的 prompt 必须包含**： - 主体描述 + 风格描述 + 色调/光线 + 构图 + 负面提示 **⚠️ 发送前必须过自检 Checklist** → 详见 [`references/prompt-checklist.md`](references/prompt-checklist.md) --- ### 阶段 4：生成 + 筛选 + 保存 **Lovart 平台完整操作手册** → 详见 [`references/lovart-operation.md`](references/lovart-operation.md) **工具分工规则**： | 图片类型 | 使用工具 | 原因 | |---------|---------|------| | 封面图 / 视觉创意图 | **Lovart** | 创意质量高、支持设计字体 | | 纯文字排版卡（图卡内页） | **Python + PIL** | 精确可控、不消耗积分、批量快 | | 带设计字体的封面 | **Lovart 一次出完** | ⚠️ 不要生成底图再用 PIL 叠字！ | **生图执行流程**： 1. 在 Lovart 生图（按操作手册 Phase 0-5） 2. 每张图至少生成 2-4 个变体 3. 筛选标准：符合方案 / 无 AI 味 / 文字清晰 / 手机缩小后仍好看 4. 下载最终图片 --- ### 阶段 5：整合检查 1. 图片和帖子内容放一起看整体效果 2. 检查：封面和标题是否一致 / 图卡风格是否统一 / 整体是否像一个人发的 3. 如有问题，回到对应阶段修改 --- ### 附：否定后快速迭代子流程 > ⚠️ 用户否定时，不要从阶段 0 重新来过！ **目标**：一次否定 → 一次迭代 → ≤ 10 分钟 ``` Step 1（1min）：精准定位问题 ├── 提取用户关键否定词 ├── "整体方向不对" → 回阶段 1 └── "某个细节不好" → 直接修改 prompt 问题归类：🎨审美 / 📝内容 / 😱情绪 / 🔤文字 Step 2（2min）：针对性修正 ├── 审美问题 → 翻阅审美积累文档找新方向 ├── 内容问题 → 补充/替换视觉元素 ├── 情绪问题 → 调整风格/色调 └── 文字问题 → 逐字指定 + 加负面提示 → 过自检 checklist Step 3（3min）：重新生成并提交 ├── 和被否定版本做对比 └── 说明"改了什么、为什么" ``` **关键**： - 🚫 不要防御性解释上一版为什么是那样 - 📝 每次否定都记录到迭代日志（翻车原因 + 修正 + 新规则） - 🔄 同类问题连续 2 次 → 规则缺失，必须新增 --- ## 核心审美原则 > 配图能力的核心不是工具操作，而是审美和生态理解。 ### 1. 理解小红书信息流环境 - 用户拇指飞速滑动，每张图只有 **0.5 秒** - 在一堆精致到同质化的图片中，要**跳出来** - 小红书不是 Dribbble，追求的是"朋友在分享好东西"的感觉 **平台审美偏好**： - ✅ 展示成果物 > 讲述概念 - ✅ 有"生活感"的精致 > 过度完美的精致 - ✅ 一个信息点 > 多个信息点堆砌 - ❌ AI 批量生成感（过度光滑、完美对称） - ❌ 左右对比/分栏（太像 AI 模板） - ❌ 教材风/PPT 风 ### 2. 建立个人视觉辨识度 不是泛泛"做好看"，而是做出"一眼知道是谁的图"： - 确定你的品牌调性关键词 - 确定你的视觉指纹（偏好字体 + 色调 + 风格） - 图片能感受到"一个有审美的人在认真分享" ### 3. 摒弃传统 AI 审美思维 **❌ 传统 AI 审美 = 死路**： - 收到"帮我做封面"就给居中标题 + 渐变背景 + 装饰图标 - 所有帖子用同一个模板换文字 - 追求"信息完整"而塞入太多元素 - 锁定一种风格不放（也是模板化） **✅ 正确思路**： - 每篇帖子根据内容独立设计 - 敢于留白、不对称、只放一个元素 - 图片情绪和帖子情绪一致 - 风格跟着内容走，不套模板 ### 4. 封面创意从用户情绪出发 > 这是 5 轮迭代最核心的认知沉淀。 - **先想**"什么画面让人想点进去"（好奇心 / 惊讶 / 认同 / 羡慕） - **再想**技术元素如何作为辅助装饰融入 - ⚠️ **联想路径检查**：如果你的第一个联想是最直线的（论文→大脑、AI→机器人），那它大概率是错的 ### 5. 维护审美积累文档 - 每次调研至少看 **30+ 作品**，精选 **10+ 条** - 每条：作品链接/截图 + 风格标签 + 借鉴点 + 适合度判断 - 按风格分类归档 - 每次配图前先翻一遍找灵感 --- ## 约束条件 ### 工程约束 - Lovart prompt 有长度限制（agent-browser 命令 ≤ 1024 字节，需分段输入） - 小红书图片比例：3:4（竖图）或 1:1（方图），不要横图 - **积分控制**：单次配图任务 ≤ 100 积分，视频 ≤ 300，优先低速模式（免积分） ### 合规约束 - 不能用产品官方宣传图 / 品牌 logo 作为主视觉（限流风险） - 不能 1:1 复制竞品封面（可借鉴元素） - ✅ 可以有真人面部（写实 / 3D 风格化均可） - ⚠️ 不能使用未授权的真实人物照片（用 AI 生成风格化形象） - ❌ 禁止裸露器官（大脑、心脏、眼球等）作为封面主视觉 ### 绝对不要做的封面 - ❌ 左右对比 / 左右分栏结构 - ❌ 概念文字堆砌 - ❌ 新闻感封面（logo + 放大镜） - ❌ 裸露器官作为主视觉 - ❌ "技术概念可视化"的直线联想 --- ## 质量检查清单 - [ ] 每张图都有配图方案 - [ ] 每张图都有 prompt - [ ] 封面通过"0.5 秒测试" - [ ] Prompt 通过 8 项自检 checklist - [ ] 图片已生成并保存 - [ ] 整套图片风格统一 - [ ] 用户确认满意 --- ## Skill 联动 | 联动工具 | 用途 | 阶段 | |---------|------|------| | `agent-browser` | 浏览器自动化 | 阶段0（调研）、阶段4（Lovart 操作） | | 小红书 Skill | 竞品封面调研 / 发帖 | 阶段0 / 阶段5之后 | | Python + PIL | 文字卡批量生成 | 阶段4 | --- ## Gotchas > 5 轮实战踩过的所有坑，按严重程度排序。 ### 🔴 审美方向类（致命） 1. **从技术概念出发设计封面** → 应该从"什么让人想点进去"出发 2. **裸露器官做主视觉** → 信息流中引发生理不适，用户评价"恐怖" 3. **文图不一致** → 标题提到的人物/品牌，画面中必须有对应视觉 4. **凭想象描述 Logo** → 必须 web_search + 截图确认真实 logo，否则 AI 会胡编 ### 🟡 工具操作类（影响效率） 5. **Lovart 页面加载超时** → 两步导航法：about:blank → JS eval 6. **cookies set 对超长 JWT 报错** → 改用 JS document.cookie 7. **弹窗关闭顺序错误** → Canvas 引导（Close）在最上层，必须先关 8. **prompt 超 1024 字节被截断** → 先 click 获取焦点，分段 type 9. **wait 30s+ 触发 idle timeout** → 分段 ≤ 20s + 截图轮询 10. **图片下载 SSL 报错** → Node.js（rejectUnauthorized: false） ### 🟢 流程类（容易遗忘） 11. **跳过调研直接出方案** → 100% 产出 AI 模板垃圾 12. **调研了但没内化为 Design Token** → "知道但没做到" 13. **封面底图和文字分开生成** → Lovart 一次出完整封面效果好得多 14. **Lovart 连续操作触发 hCaptcha** → 每次会话只生 1-2 张，完成后关浏览器 --- ## Related - [Lovart 官网](https://lovart.ai) — AI 设计工具 - [Dribbble](https://dribbble.com) — 设计灵感 - [小红书](https://xiaohongshu.com) — 目标平台 + 竞品调研 - [Pinterest](https://pinterest.com) — 补充灵感（审美更接近小红书） --- ## Source ``` Learning IDs: crash-01 ~ crash-06 Category: social-media / content-creation / AI-image-generation Created: 2026-03-18 Validated: 2026-03-20 (5 rounds, 3 different topics) ```

path: references/lovart-operation.md
content: # Lovart 平台操作手册（agent-browser 自动化）

> 基于 5 轮实战验证，14 个具体卡点的解决方案沉淀。
> 
> ⚠️ **核心原则**：每次操作遇到新卡点，必须回来更新这个文档。

---

## 前置准备

### Lovart 账号认证

Lovart 不支持 headless 浏览器的 Google/Apple OAuth，必须用 cookies 登录。

**获取 cookies 的方法**：
1. 在正常浏览器中登录 Lovart（lovart.ai）
2. 打开 DevTools → Application → Cookies → `www.lovart.ai`
3. 导出以下 3 个关键 cookies：

| Cookie 名 | 说明 | 备注 |
|-----------|------|------|
| `usertoken` | JWT 认证令牌 | 最重要，约 600 字符 |
| `useruuid` | 用户 UUID | |
| `webid` | 设备标识 | |

4. 保存为 JSON 文件（如 `.lovart_cookies.json`）

**JWT 有效期**：约 7 天，过期后需重新获取。

---

## Phase 0：页面加载

> **已知问题**：`agent-browser open "https://www.lovart.ai/zh/home"` 经常超时（25s 限制）。

**✅ 两步导航法**：

```bash
# Step 1: 先打开空白页（秒开）
agent-browser open "about:blank"

# Step 2: 用 JS 导航（不受 25s 限制）
agent-browser eval "window.location.href = 'https://www.lovart.ai/zh/home'"

# Step 3: 等待加载
agent-browser wait 8000
```

**❌ 不要直接 open**：
```bash
# 这个会超时！
agent-browser open "https://www.lovart.ai/zh/home"
```

---

## Phase 1：Cookies 注入 + 登录验证

### Step 0：JWT 过期预警

> 注入前先检查 JWT 是否过期，避免浪费时间。

```bash
node -e "
const fs = require('fs');
const cookies = JSON.parse(fs.readFileSync('.lovart_cookies.json', 'utf8'));
const [REDACTED_SECRET] => c.name === 'usertoken');
if (!token) { console.log('❌ 未找到 usertoken'); process.exit(1); }
const payload = JSON.parse(Buffer.from(token.value.split('.')[1], 'base64').toString());
const exp = new Date(payload.exp * 1000);
const now = new Date();
const hoursLeft = (exp - now) / 3600000;
console.log('JWT 过期时间:', exp.toISOString());
console.log('剩余时间:', hoursLeft.toFixed(1), '小时');
if (hoursLeft <= 0) console.log('❌ 已过期！需重新获取 cookies');
else if (hoursLeft <= 24) console.log('⚠️ 24h 内过期，建议更新');
else console.log('✅ JWT 有效');
"
```

### Step 1：确认域名

> ⚠️ cookies 不能在 `about:blank` 设置（会报 "Blank page can not have cookie"）。
> Phase 0 完成后，当前页面已在 lovart.ai 域名下。

### Step 2：注入 cookies

**方法 A**（推荐）：`agent-browser cookies set`

```bash
agent-browser cookies set name=usertoken value=<JWT> domain=www.lovart.ai path=/ secure=true sameSite=Lax
agent-browser cookies set name=useruuid value=<UUID> domain=www.lovart.ai path=/ secure=true sameSite=None
agent-browser cookies set name=webid value=<WEBID> domain=www.lovart.ai path=/ secure=true sameSite=Lax
```

**方法 B**（备选，解决超长值报错）：`JS document.cookie`

> JWT token 约 600 字符时，方法 A 可能报 `Protocol error (Storage.setCookies): Invalid cookie fields`。

```bash
agent-browser eval "document.cookie='usertoken=<JWT>; path=/; secure; SameSite=Lax'; document.cookie='useruuid=<UUID>; path=/; secure; SameSite=Lax'; document.cookie='webid=<WEBID>; path=/; secure; SameSite=Lax'"
```

> 注意：JS `document.cookie` 无法设置 httpOnly cookies，但 Lovart 的 3 个关键 cookies 都不是 httpOnly。

### Step 3：刷新验证

```bash
agent-browser eval "window.location.reload()"
agent-browser wait 5000
agent-browser screenshot
```

**✅ 成功**：页面跳转到 `/zh/home`，右上角有用户头像，可见积分数字
**❌ 失败**：停留在登录弹窗 → 检查 JWT 是否过期

---

## Phase 2：关闭引导弹窗

> 每次新会话都会弹出引导弹窗，**关闭顺序极其重要**！

| 顺序 | 弹窗 | 按钮 | 说明 |
|------|------|------|------|
| 1️⃣ | "品牌套件"引导（首页） | **"跳过"** | |
| 2️⃣ | "The All-New Lovart Canvas"（Canvas 页） | **"Close"** | ⚠️ 最上层，全屏遮罩 |
| 3️⃣ | Fast mode 提示（Canvas） | **"Got it"** | 必须先关 2️⃣ |
| 4️⃣ | 低速生成提示（偶发） | **"知道了"** | |

```bash
agent-browser screenshot  # 先看状态
agent-browser snapshot -i  # 确认弹窗层级

# Canvas 页面：必须先关 Close，再关 Got it
# 如果 snapshot 只有 Close/Next 按钮 → 有全屏遮罩，先关掉
agent-browser click @eX   # Close 按钮
agent-browser wait 1000
agent-browser find text "Got it" click
agent-browser wait 1000
```

> ⚠️ **翻车教训**：先点 "Got it" 会被 Canvas 引导的遮罩层拦截，超时 25s。
> **识别方法**：snapshot 中只有 Close/Next 等按钮，看不到页面其他元素 → 有全屏遮罩。

---

## Phase 3：输入 Prompt + 生图

### 输入 prompt

```bash
# 1. 找到输入框
agent-browser snapshot -i

# 2. 点击获取焦点 + 分段输入
agent-browser click @e8
agent-browser type @e8 "第一段：场景描述..."
agent-browser type @e8 " 第二段：品牌元素..."
agent-browser type @e8 " 第三段：文字需求..."
agent-browser type @e8 " 第四段：风格配色..."

# 3. 找发送按钮（输入后从 disabled 变为 enabled 的按钮）
agent-browser snapshot -i
agent-browser click @eXX
```

> ⚠️ **长 prompt 处理**：agent-browser 命令 ≤ 1024 字节。
> - `type` = 追加输入（不清空），`fill` = 清空后填入
> - 分段时用 `type`，每段开头加空格分隔

> ⚠️ **发送按钮识别**：输入前是 `[disabled]`，输入后变 enabled。
> 最可靠：输入前后各做一次 snapshot，对比哪个 button 状态变了。

### 等待生成

```bash
agent-browser wait 10000
agent-browser screenshot    # 检查是否进入 Canvas

# 分段等待（每段 ≤ 20s）+ 截图轮询
agent-browser wait 15000
agent-browser screenshot
agent-browser wait 20000
agent-browser screenshot
# 重复 3-5 轮直到看到结果
```

> ⚠️ **不要** `wait 30000` 以上！会触发 idle timeout 被系统中断。

### Prompt 编写规则

| 规则 | 说明 |
|------|------|
| 语言 | 英文优先（效果更精准），中文也可 |
| 格式 | 自然语言描述，不是 SD/MJ 式 tag |
| Logo | 必须精确描述视觉元素，不能只写品牌名 |
| 文字 | 逐字指定标题内容（Lovart 有设计字体能力） |
| 比例 | 明确写 "portrait 3:4 ratio" 或 "1080x1440px" |
| 风格 | 明确写风格关键词 |

---

## Phase 4：获取图片 + 下载

### 获取图片 URL

```bash
agent-browser eval "JSON.stringify(Array.from(document.querySelectorAll('img[src*=\"artifact\"]')).map(i => ({src: i.src, w: i.naturalWidth, h: i.naturalHeight})))"
```

> **URL 技巧**：去掉 `?x-oss-process=...` 参数可获取原始高质量 PNG。

### 下载图片

> ⚠️ `a.lovart.ai` 的 SSL 与 curl/Python requests 不兼容。

**✅ 用 Node.js 下载**：

```bash
NODE_TLS_REJECT_UNAUTHORIZED=0 node -e "
const https = require('https');
const fs = require('fs');
const url = '<IMAGE_URL>';
https.get(url, {rejectUnauthorized: false}, res => {
  const chunks = [];
  res.on('data', c => chunks.push(c));
  res.on('end', () => {
    fs.writeFileSync('<OUTPUT_PATH>', Buffer.concat(chunks));
    console.log('Downloaded:', Buffer.concat(chunks).length, 'bytes');
  });
}).on('error', e => console.error(e));
"
```

### 方法 C：浏览器内 fetch + base64 分块提取（🔴 当 Node.js 也 SSL 失败时）

> ⚠️ 某些情况下本地所有 SSL 客户端（Node.js / curl / Python）都会失败
> （如 macOS LibreSSL 3.3.6 与 CDN 的 TLS 配置不兼容）。
> 浏览器（Chromium）有独立的 SSL 栈（BoringSSL），不受本地限制。

```bash
# Step 1: 确保 agent-browser 在 lovart.ai 域名下

# Step 2: 浏览器内 JS fetch → base64
agent-browser eval "fetch('<IMAGE_URL>').then(r=>r.blob()).then(b=>{const reader=new FileReader();reader.onloadend=()=>{window._imgData=reader.result;document.title='IMG_READY_'+reader.result.length};reader.readAsDataURL(b)})"

# Step 3: 等待完成
agent-browser wait 10000
agent-browser eval "document.title"
# 输出: "IMG_READY_<总长度>"

# Step 4: 分块提取（每块 ~500KB）
agent-browser eval "window._imgData.split(',')[1].length"
# 假设 7000000，分 14 块

rm -f /tmp/lovart_b64.txt
for i in $(seq 0 13); do
  START=$((i * 500000))
  END=$(((i + 1) * 500000))
  agent-browser eval "window._imgData.split(',')[1].substring($START, $END)" \
    | sed 's/^"//;s/"$//' >> /tmp/lovart_b64.txt
done

# Step 5: base64 解码
node -e "
const fs = require('fs');
const b64 = fs.readFileSync('/tmp/lovart_b64.txt', 'utf8').replace(/\n/g, '');
const buf = Buffer.from(b64, 'base64');
fs.writeFileSync('<OUTPUT_PATH>', buf);
console.log('Downloaded:', buf.length, 'bytes');
console.log('PNG header:', buf.slice(0,4).toString('hex'));
"
```

> **注意**：不要尝试 `agent-browser open "<图片URL>"`，会触发浏览器下载行为而非渲染。

---

## Phase 5：关闭浏览器

> 必须做！不关闭 → 下次操作可能触发 hCaptcha。

```bash
agent-browser close
```

---

## hCaptcha 防触发规则

- 每次会话只生图 **1-2 张**
- 生成完毕**立即关闭浏览器**
- 下次：重新打开 → 注入 cookies → 生图
- 触发 hCaptcha → 关浏览器，等 5 分钟，重新开始

## 积分控制

- 低速模式（Relax）不消耗积分，但可能繁忙失败
- 单次配图任务 **≤ 100 积分**
- 每次生图前后记录积分变化
- 超标前停下来和用户确认


path: references/prompt-checklist.md
content: # Prompt 发送前自检 Checklist

> ⚠️ 每次 prompt 写完、发送给 Lovart 之前，必须逐项过一遍。
> 这是防止"生成后才发现问题"的最后一道关卡。

---

## 8 项必检

| # | 检查维度 | 检查内容 | 不通过怎么办 |
|---|---------|---------|-------------|
| 1 | **联想路径检查** | 核心创意是不是"最直线的联想"？（论文→大脑、AI→机器人、编程→代码）如果是，换一条路 | 回到阶段 1 重新想创意 |
| 2 | **文图一致性** | 标题/正文提到的人物、品牌、产品，画面中是否都有对应视觉？ | 补充缺失的视觉元素 |
| 3 | **情绪检查** | 封面传递的情绪是否和帖子一致？是否有引发不适的可能？ | 调整视觉元素或风格 |
| 4 | **文字逐字指定** | prompt 中的标题文字是否逐字写清楚？（不能只说"写标题"） | 补全文字内容 |
| 5 | **禁止项检查** | 是否包含以下禁忌内容？↓ | 删除违规元素 |
| 6 | **AI 生图缺陷预防** | 是否加入了必要的质量约束？↓ | 补充负面提示 |
| 7 | **Logo/品牌精确性** | 品牌 logo 是基于搜索/截图精确描述的？还是凭想象猜的？ | 去搜索确认后再写 |
| 8 | **比例和尺寸** | 是否指定了图片比例？（3:4 竖图 / 1:1 方图） | 补充比例要求 |

---

## 第 5 项：禁止项完整清单

| 禁忌类别 | 具体内容 | 原因 |
|---------|---------|------|
| 裸露器官 | 大脑、心脏、眼球、骨骼、神经网络可视化 | 信息流中引发生理不适 |
| 恐怖/惊悚元素 | 骷髅、血液、裂缝、腐烂、暗黑风格 | 小红书用户群体不接受 |
| 过度写实机械/赛博朋克 | 布满管线的机械脑、赛博眼 | "恐怖谷"效果 |
| 争议性符号 | 特定旗帜、宗教符号、政治元素 | 违规风险 |
| 暴力/武器 | 枪械、刀具、爆炸场景 | 平台违规 |
| 色情/低俗 | 暴露着装、挑逗姿态 | 平台违规 |
| 未经验证的品牌 logo | 凭想象描述的 logo | AI 会胡编 |

---

## 第 6 项：AI 生图常见缺陷预防

在 prompt 末尾加入负面提示（按需选用）：

```
Negative: deformed hands, extra fingers, mutated fingers, bad anatomy,
blurry text, illegible text, misspelled text, garbled characters,
uncanny valley face, plastic skin, cross-eyed, asymmetric eyes,
watermark, signature, low quality, pixelated, oversaturated colors,
generic AI stock photo look, left-right comparison layout
```

---

## Logo/品牌元素精确描述规则

> 这是第3轮的血泪教训。

**禁止凭想象描述 logo！必须：**

1. `web_search "<品牌名> logo icon"` 搜索
2. 在品牌官网截图找到 logo
3. 如果都找不到 → **直接问用户要截图**
4. 用精确的视觉语言描述：形状、颜色、线条、构成元素

**正确示例**：
- ✅ `"a dark circle icon with two white dots arranged like eyes (cute mascot face)"`
- ✅ `"a black circle with white concentric arc lines like sound waves"`

**错误示例**：
- ❌ `"NotebookLM logo"`（AI 不知道长什么样）
- ❌ `"Kimi logo"`（AI 会胡编一个不存在的图形）

---

## 文字逐字指定规则

Lovart 有文字设计能力，但必须明确告诉它每一个字：

**正确**：
```
The exact title text must be: 马斯克点赞的AI论文
Subtitle text: Kimi注意力残差
```

**错误**：
```
Include a title about the paper  ← Lovart 会自由发挥，可能翻译错误
Add Chinese text for the topic   ← 太模糊
```

> 教训：Lovart 曾自行将"Attention Residuals"翻译为"专注力残差"（不准确）。


path: references/aesthetic-guide.md
content: # 审美积累指南 & 模板

> 本文件是审美积累文档的模板和指南。
> 使用这个 Skill 时，你需要在项目中创建自己的 `aesthetic-library.md`，按本模板的格式记录。

---

## 为什么要维护审美积累文档

- 每次配图前**不用从零找灵感**，翻文档就有
- **避免审美退化**：不积累 → 每次输出都是 AI 默认审美
- **风格多样性**：积累越多，封面越不会千篇一律
- **团队协作**：新人接手也能快速了解审美标准

---

## 文档结构模板

```markdown
# 审美积累文档

> 最后更新：YYYY-MM-DD
> 维护规则：每次调研后更新，按风格分类

---

## 📂 按风格分类

### 🏺 3D Clay / 黏土风格

**适合场景**：科技人物、产品展示、趣味性封面

| # | 描述 | 借鉴点 | 适合我的 IP？ | 来源 |
|---|------|--------|-------------|------|
| 1 | [描述] | [值得借鉴的元素] | ✅/❌ | [链接或截图路径] |

**关键要诀**：
- [从调研中提炼的该风格核心技巧]

---

### 📸 产品截图 + 标注流
### 👤 真人出镜流
### 🔤 纯文字卡 / 极简设计
### 🎨 [你发现的新风格分类]

---

## 🚫 反面案例库

| # | 描述 | 为什么不好 | 来源 |
|---|------|----------|------|
| 1 | [描述] | [原因] | [来源] |

---

## 📅 定期浏览记录

### YYYY-MM-DD

**浏览平台**：Dribbble / 小红书 / Pinterest
**沉淀内容**：[本次新增了什么]
**下次浏览方向**：[计划搜什么关键词]
```

---

## 推荐的风格分类

根据 5 轮小红书实战，以下是常见且有效的封面风格：

### 1. 3D Clay / 黏土风格
- **关键词**：暖色、圆角、黏土质感、柔和光影、玩具感
- **适合**：科技主题但要亲和力的场景
- **要诀**：哑光材质 + 暖色调 + 头大身小的卡通比例

### 2. 产品截图 + 标注流
- **关键词**：真实截图、手写标注、箭头圈注
- **适合**：教程帖、工具推荐、实战记录
- **要诀**：截图必须真实 + 标注不超过 3 处 + 暖色背景

### 3. 权威人物 / 真人出镜流
- **关键词**：人脸、3D 风格化、利益点大字
- **适合**：观点帖、趋势帖、权威背书
- **要诀**：优先 3D 风格化（规避版权）+ 表情生动 + 人物占 60%

### 4. 纯文字卡 / 极简设计
- **关键词**：大号字体、留白、一句话核心
- **适合**：金句帖、方法论帖
- **要诀**：圆角无衬线体 + 暖色/品牌色背景 + 装饰不喧宾夺主

### 5. Logo/工具矩阵
- **关键词**：多 Logo 整齐排列、干净背景
- **适合**：工具合集、盘点类
- **要诀**：间距统一 + 品牌色调协调 + 标题简洁

---

## 每次调研的最低要求

| 维度 | 最低标准 |
|------|---------|
| 作品浏览量 | ≥ 30 个 |
| 精选记录数 | ≥ 10 条 |
| 记录格式 | 链接/截图 + 风格标签 + 借鉴点 + 适合度 |
| 新增分类 | 如果发现新风格，及时新增分类 |

---

## 通用反面案例

> 这些是经过实战验证的"绝对不要做"的封面类型。

| 反面案例 | 原因 |
|---------|------|
| 左右对比 / 左右分栏 | 没辨识度，像 AI 模板 |
| 概念文字堆砌 | 数据证明无人关心 |
| 新闻感封面（logo + 放大镜） | 像营销号 |
| 品牌官方宣传图做主视觉 | 小红书限流风险 |
| 裸露器官（大脑/心脏/眼球） | 信息流中生理不适 |
| "技术概念"直线联想 | AI 最缺乏创意的默认路径 |
| 所有帖子用同一模板 | 模板化 = 没有辨识度 |


path: references/research-platforms.md
content: # 调研平台操作指南

> 阶段 0 精准调研和维度 2 定期审美积累的具体操作方法。

---

## 平台 A：Dribbble（设计灵感）

**为什么用**：高质量设计作品聚集地，3D Clay / 插画 / 卡片设计参考最多。

**注意**：Dribbble 审美 ≠ 小红书审美。Dribbble 追求设计完美度，小红书追求"朋友在分享"。

### 操作流程

```bash
# 1. 打开搜索
agent-browser open "https://dribbble.com/search/<关键词>"

# 2. 搜索关键词模板
#    - "3d clay style <主题>"
#    - "social media card <主题>"
#    - "tech product card cover"
#    - "minimal infographic card"
#    - "character illustration cute"
#    - "<品牌名> brand design"

# 3. 浏览 + 截图
agent-browser screenshot "dribbble-<关键词>-1.png"
agent-browser scroll down 500
agent-browser screenshot "dribbble-<关键词>-2.png"
```

### 注意事项
- 关键词**必须用英文**
- 不需要登录
- 每个关键词至少看 2 屏（20-30 个作品）
- 高赞（❤️ 100+）和高浏览（👁 50k+）重点记录
- **陷阱**：Dribbble 的"好看"≠ 小红书的"高赞"

---

## 平台 B：小红书（竞品调研）

**为什么用**：直接看同赛道什么封面数据好 — 最真实的反馈。

> ⚠️ **绝对禁止登录用户的小红书账号！**
> - 无登录状态可以浏览和搜索
> - 登录后 headless 浏览器容易被识别为机器人，有账号风险
> - 弹出登录弹窗 → 直接关闭，继续浏览
> - 不要注入任何小红书相关 cookies

### 操作流程

```bash
# 方法 1（推荐）：web_search 搜索
web_search "site:xiaohongshu.com <关键词>"

# 方法 2：agent-browser 直接访问（不登录！）
agent-browser open "https://www.xiaohongshu.com/search_result?keyword=<关键词>"
# 弹出登录弹窗 → 关闭继续看
# 安全验证/IP 风险 → 放弃，改用方法 1

# 方法 3：web_fetch 抓取特定帖子
web_fetch "https://www.xiaohongshu.com/explore/<帖子ID>" "分析封面设计特征"
```

### 搜索关键词策略
- 直接主题词：如 "AI工具推荐"、"效率工具"
- 赛道泛词：如 "学习方法"、"AI教程"
- 竞品账号名

### 必须记录

| 记录项 | 说明 |
|--------|------|
| 作者 + 赞数 | 数据是最硬的参考 |
| 封面类型 | 真人出镜 / 产品截图 / Logo 矩阵 / 文字卡 / 3D 插画 |
| 封面关键特征 | 颜色、字体、构图、元素 |
| 为什么数据好 | "真实感强" / "信息直给" / "有态度" |
| 能不能借鉴 | 具体可以借鉴哪个元素 |

**数量要求**：至少看 10-15 个帖子，精选 5+ 条

---

## 平台 C：品牌官网（品牌视觉分析）

**为什么用**：如果帖子涉及某个品牌/产品，必须去官网看设计语言。

### 操作流程

```bash
# 1. 打开品牌官网
agent-browser open "https://<品牌官网>"
agent-browser wait 5000
agent-browser screenshot "<品牌>-homepage.png"

# 2. 提取设计语言
agent-browser eval "JSON.stringify({
  fonts: getComputedStyle(document.querySelector('h1')).fontFamily,
  bodyFont: getComputedStyle(document.querySelector('p')).fontFamily,
  bgColor: getComputedStyle(document.body).backgroundColor
})"
```

### 必须提取

| 元素 | 怎么获取 |
|------|---------|
| 品牌主色 | 看 logo、按钮、高亮色 |
| 背景色 | JS 获取 body 背景色 |
| 字体 | JS 获取 h1/p 的 fontFamily |
| Logo 精确描述 | ⚠️ 必须截图确认，不能凭想象 |
| 排版哲学 | 留白 / 信息密度 / 极简 vs 丰富 |
| 情绪/调性 | 冷冰冰 / 温暖 / 科技感 / 人文感 |

---

## 平台 D：Pinterest（补充灵感，可选）

**什么时候用**：Dribbble 搜不到合适的，或需要更贴近"生活感"的灵感。

```bash
agent-browser open "https://pinterest.com/search/pins/?q=<关键词>"
# 不需要登录，弹出登录弹窗 → 关掉继续看
```

**vs Dribbble 的区别**：
- Dribbble = 设计师社区 → 偏"专业设计"
- Pinterest = 混合社区 → 偏"好看的生活方式"
- 小红书审美更接近 Pinterest

---

## 小红书已验证的封面类型（按数据排序）

> 基于 5 轮实战中的竞品调研总结。

| 排名 | 封面类型 | 典型数据 | 特征 |
|------|---------|---------|------|
| 1 | AI 生成的精美产品 UI 展示图 | 200+ 赞 | 产品截图+设计处理 |
| 2 | 真实产品截图 + 好看角度 | 140+ 赞 | 真实感强 |
| 3 | 权威人脸 + 利益点大字 | 115+ 赞 | 人脸吸引注意力 |
| 4 | Logo/工具矩阵 + 干净背景 | 4000+ 赞 | 信息一目了然 |
| 5 | 产品截图 + 手写标注 | 7000+ 赞 | 教程类首选 |
| 6 | 真人出镜 + 产品实拍 | 13000+ 赞 | 最高赞类型 |

**关键发现**：
- 真人出镜 + 产品实拍 = 最高赞
- 产品截图 + 红色/手写标注 = 教程类稳定高赞
- 手写体/课堂笔记风 = 最有"人味儿"


path: README.md
content: # 🎨 xiaohongshu-cover-gen

> AI Agent Skill：为小红书帖子生成高质量封面图和内容图卡

**经 5 轮实战验证、6 次翻车迭代沉淀的配图方法论。** 不是"调 API 生成图片"——是一套从审美调研、创意构思、prompt 编写到生成交付的完整工作流。

---

## ✨ 特性

- 🎯 **6 阶段标准流程**：调研 → 分析 → 确认 → prompt → 生成 → 检查
- 🧠 **14 条实战翻车经验**（Gotchas）：按严重程度分级，避免重复踩坑
- 🖌️ **核心审美原则**：从用户情绪出发设计封面，而非技术概念
- 🤖 **Lovart 完整操作手册**：agent-browser 自动化 Phase 0-5 全流程
- ✅ **8 项 prompt 自检 checklist**：发送前最后一道质量关卡
- 📊 **审美积累体系**：定期浏览 + 分类归档，持续提升审美水平
- 🐍 **Python 图卡生成器**：PIL 批量生成纯文字卡，Design Token 可配置

---

## 📁 目录结构

```
xiaohongshu-cover-gen/
├── SKILL.md                          # ⭐ 核心 Skill 文件（加载此文件）
├── README.md                         # 项目说明
├── .gitignore
│
├── references/                       # 参考文档
│   ├── lovart-operation.md           # Lovart 平台完整操作手册
│   ├── prompt-checklist.md           # Prompt 发送前 8 项自检
│   ├── research-platforms.md         # 调研平台操作指南
│   └── aesthetic-guide.md            # 审美积累文档模板 & 指南
│
├── scripts/                          # 辅助脚本
│   ├── gen_text_cards.py             # Python 图卡生成器
│   ├── check_jwt.sh                  # Lovart JWT 过期检查
│   └── download_image.sh             # Lovart 图片下载（绕过 SSL）
│
└── templates/                        # 模板文件
    ├── design-tokens.md              # Design Token 模板 + 3 种配色示例
    └── iteration-log.md              # 迭代日志模板
```

---

## 🚀 Quick Start

### 1. 作为 Agent Skill 使用

将 `SKILL.md` 加载到你的 AI Agent（如 CodeBuddy、Cursor、Claude 等），Agent 会自动按照 Skill 中定义的流程工作。

**触发词**：`小红书配图`、`封面生成`、`图卡设计`、`XHS cover`、`social media image`

### 2. 首次使用前准备

#### Lovart 账号（生成封面图）

1. 注册 [Lovart](https://lovart.ai) 账号
2. 在浏览器 DevTools 中导出 3 个关键 cookies：`usertoken`、`useruuid`、`webid`
3. 保存为 `.lovart_cookies.json`（格式见 `references/lovart-operation.md`）

#### Python 环境（生成文字图卡）

```bash
pip install Pillow
```

### 3. 工作流概览

```
收到帖子内容
    ↓
阶段 0：精准调研（10min）— Dribbble + 小红书 + 品牌官网
    ↓
阶段 1：帖子分析 → 配图方案
    ↓
阶段 2：方案确认 + 素材需求前置
    ↓
阶段 3：写 prompt（过 8 项自检）
    ↓
阶段 4：Lovart 生成 / Python 图卡
    ↓
阶段 5：整合检查
```

---

## 🛠️ 前置依赖

| 依赖 | 用途 | 必需？ |
|------|------|--------|
| [Lovart](https://lovart.ai) 账号 | AI 生图 | ✅ 封面图必需 |
| `agent-browser` | 浏览器自动化 | ✅ Lovart 操作必需 |
| Node.js | JWT 检查 + 图片下载 | ✅ |
| Python 3 + Pillow | 文字图卡生成 | 可选（纯文字卡时用） |

---

## 📖 核心理念

### 封面只有 0.5 秒的生存时间

小红书信息流中，用户拇指飞速滑动。封面的职责不是"完整传达信息"，而是**在 0.5 秒内制造好奇心和点击欲**。

### 从用户情绪出发，不是从技术概念出发

> 这是 5 轮迭代最核心的认知沉淀。

❌ 错误：帖子讲 AI 论文 → 画一个大脑（最直线的联想 = 最缺乏创意的路径）

✅ 正确：帖子讲"马斯克点赞的 AI 论文" → 画一个 3D Clay 马斯克竖大拇指

### 审美 > 工具

配图能力的核心不是工具操作，而是审美和生态理解。工具可以学会，审美才是壁垒。

---

## ⚠️ 已知限制

- Lovart 不支持 headless 浏览器 OAuth 登录，必须手动提供 cookies
- Lovart JWT 有效期约 7 天，需定期更新
- `agent-browser` 命令长度限制 ≤ 1024 字节，长 prompt 需分段输入
- `agent-browser wait` 超过 30s 会触发 idle timeout，需分段等待
- Lovart 连续操作可能触发 hCaptcha，建议每次会话只生 1-2 张
- `a.lovart.ai` SSL 证书与 curl/Python 不兼容，图片下载必须用 Node.js

---

## 📝 License

MIT

---

## 🙏 致谢

- [Lovart](https://lovart.ai) — AI 设计生成工具
- [Dribbble](https://dribbble.com) — 设计灵感来源
- [Pillow (PIL)](https://pillow.readthedocs.io) — Python 图像处理
- 5 轮实战中踩过的所有坑 🕳️


path: scripts/gen_text_cards.py
content: #!/usr/bin/env python3
"""
小红书图卡生成器（模板版）

使用方法：
1. 修改 DESIGN_TOKENS 中的配色方案为你的品牌色
2. 修改 CARDS 列表中的图卡内容
3. 运行：python3 gen_text_cards.py

依赖：pip install Pillow

输出：1080x1440px 的 3:4 竖图 PNG
"""

from PIL import Image, ImageDraw, ImageFont
import os

# ============================================================
# Design Tokens（修改这里来匹配你的品牌风格）
# ============================================================

DESIGN_TOKENS = {
    # 画布
    "width": 1080,
    "height": 1440,
    
    # 调色板（示例：暖色极简风）
    "bg_color": (245, 240, 232),        # 背景色（奶油米色）
    "text_primary": (30, 30, 30),        # 标题文字（近黑）
    "text_secondary": (60, 55, 50),      # 正文文字（深棕）
    "text_tertiary": (120, 115, 108),    # 辅助文字（中灰）
    "accent_warm": (212, 168, 83),       # 暖色强调（黄金色）
    "accent_highlight": (199, 91, 58),   # 高亮色（暖红）
    "accent_soft": (225, 215, 200),      # 柔和色（米色，分隔线等）
    
    # 间距
    "margin_x": 96,         # 左右边距
    "margin_top": 140,      # 顶部留白
    "line_height_title": 80,  # 标题行高
    "line_height_body": 52,   # 正文行高
    
    # 字体（修改为你系统中可用的字体路径）
    "font_title": None,      # 中文标题字体路径（粗体）
    "font_body": None,       # 中文正文字体路径
    "font_en_title": None,   # 英文标题字体路径
    "font_en_body": None,    # 英文正文字体路径
}

# ============================================================
# 字体加载
# ============================================================

def load_fonts(tokens):
    """加载字体，失败时回退到默认字体"""
    fonts = {}
    
    # macOS 常用字体路径
    mac_fonts = {
        "cn_bold": "/System/Library/Fonts/Hiragino Sans GB.ttc",
        "cn_regular": "/System/Library/Fonts/Hiragino Sans GB.ttc",
    }
    
    # 尝试加载中文标题字体
    try:
        path = tokens["font_title"] or mac_fonts.get("cn_bold")
        if path and os.path.exists(path):
            fonts["title_48"] = ImageFont.truetype(path, 48, index=1)
            fonts["title_36"] = ImageFont.truetype(path, 36, index=1)
            fonts["title_28"] = ImageFont.truetype(path, 28, index=1)
        else:
            fonts["title_48"] = ImageFont.load_default()
            fonts["title_36"] = ImageFont.load_default()
            fonts["title_28"] = ImageFont.load_default()
    except Exception:
        fonts["title_48"] = ImageFont.load_default()
        fonts["title_36"] = ImageFont.load_default()
        fonts["title_28"] = ImageFont.load_default()
    
    # 尝试加载中文正文字体
    try:
        path = tokens["font_body"] or mac_fonts.get("cn_regular")
        if path and os.path.exists(path):
            fonts["body_28"] = ImageFont.truetype(path, 28, index=0)
            fonts["body_24"] = ImageFont.truetype(path, 24, index=0)
            fonts["body_20"] = ImageFont.truetype(path, 20, index=0)
        else:
            fonts["body_28"] = ImageFont.load_default()
            fonts["body_24"] = ImageFont.load_default()
            fonts["body_20"] = ImageFont.load_default()
    except Exception:
        fonts["body_28"] = ImageFont.load_default()
        fonts["body_24"] = ImageFont.load_default()
        fonts["body_20"] = ImageFont.load_default()
    
    # 英文字体
    try:
        path = tokens["font_en_title"]
        if path and os.path.exists(path):
            fonts["en_title_160"] = ImageFont.truetype(path, 160)
            fonts["en_title_20"] = ImageFont.truetype(path, 20)
        else:
            fonts["en_title_160"] = fonts["title_48"]
            fonts["en_title_20"] = fonts["body_20"]
    except Exception:
        fonts["en_title_160"] = fonts["title_48"]
        fonts["en_title_20"] = fonts["body_20"]
    
    return fonts


# ============================================================
# 绘制辅助函数
# ============================================================

def draw_tag(draw, x, y, text, font, bg_color, text_color, padding=(12, 6)):
    """绘制圆角标签"""
    bbox = font.getbbox(text)
    tw, th = bbox[2] - bbox[0], bbox[3] - bbox[1]
    rect = [x, y, x + tw + padding[0] * 2, y + th + padding[1] * 2]
    draw.rounded_rectangle(rect, radius=6, fill=bg_color)
    draw.text((x + padding[0], y + padding[1]), text, fill=text_color, font=font)
    return rect[3] + 12  # 返回下一行 y 坐标


def draw_separator(draw, y, tokens):
    """绘制分隔线"""
    mx = tokens["margin_x"]
    w = tokens["width"]
    draw.line([(mx, y), (w - mx, y)], fill=tokens["accent_soft"], width=1)
    return y + 24


def wrap_text(text, font, max_width):
    """中文文本自动换行"""
    lines = []
    current_line = ""
    for char in text:
        test = current_line + char
        bbox = font.getbbox(test)
        if bbox[2] - bbox[0] > max_width:
            lines.append(current_line)
            current_line = char
        else:
            current_line = test
    if current_line:
        lines.append(current_line)
    return lines


# ============================================================
# 图卡生成
# ============================================================

def generate_card(card_config, tokens, fonts, output_path):
    """生成单张图卡"""
    w, h = tokens["width"], tokens["height"]
    img = Image.new("RGB", (w, h), tokens["bg_color"])
    draw = ImageDraw.Draw(img)
    
    mx = tokens["margin_x"]
    max_text_w = w - mx * 2
    y = tokens["margin_top"]
    
    # 标签
    if "tag" in card_config:
        y = draw_tag(
            draw, mx, y, card_config["tag"],
            fonts["body_20"], tokens["accent_warm"], (255, 255, 255)
        )
        y += 8
    
    # 标题
    if "title" in card_config:
        title_lines = wrap_text(card_config["title"], fonts["title_48"], max_text_w)
        for line in title_lines:
            draw.text((mx, y), line, fill=tokens["text_primary"], font=fonts["title_48"])
            y += tokens["line_height_title"]
        y += 16
    
    # 分隔线
    y = draw_separator(draw, y, tokens)
    
    # 正文内容
    if "body" in card_config:
        for paragraph in card_config["body"]:
            body_lines = wrap_text(paragraph, fonts["body_28"], max_text_w)
            for line in body_lines:
                draw.text((mx, y), line, fill=tokens["text_secondary"], font=fonts["body_28"])
                y += tokens["line_height_body"]
            y += 16  # 段间距
    
    # 列表项
    if "list_items" in card_config:
        for i, item in enumerate(card_config["list_items"], 1):
            # 序号圆圈
            cx, cy = mx + 16, y + 14
            draw.ellipse([cx - 14, cy - 14, cx + 14, cy + 14], fill=tokens["accent_warm"])
            draw.text((cx - 5, cy - 10), str(i), fill=(255, 255, 255), font=fonts["body_20"])
            
            # 列表文字
            item_lines = wrap_text(item, fonts["body_28"], max_text_w - 50)
            for line in item_lines:
                draw.text((mx + 44, y), line, fill=tokens["text_secondary"], font=fonts["body_28"])
                y += tokens["line_height_body"]
            y += 12
    
    # 底部引用/金句
    if "quote" in card_config:
        y += 16
        # 大引号装饰
        draw.text((mx, y - 10), """, fill=tokens["accent_soft"], font=fonts["title_48"])
        y += 40
        quote_lines = wrap_text(card_config["quote"], fonts["title_36"], max_text_w - 20)
        for line in quote_lines:
            draw.text((mx + 10, y), line, fill=tokens["text_primary"], font=fonts["title_36"])
            y += 60
    
    # 高亮句
    if "highlight" in card_config:
        y += 12
        hl_lines = wrap_text(card_config["highlight"], fonts["body_28"], max_text_w)
        for line in hl_lines:
            draw.text((mx, y), line, fill=tokens["accent_highlight"], font=fonts["body_28"])
            y += tokens["line_height_body"]
    
    # 保存
    img.save(output_path)
    print(f"✅ 生成: {output_path} ({w}x{h})")


# ============================================================
# 示例图卡配置
# ============================================================

SAMPLE_CARDS = [
    {
        "filename": "card-intro.png",
        "tag": "科普",
        "title": "这是一个示例标题",
        "body": [
            "这是第一段正文内容，用来介绍主题背景。",
            "这是第二段正文，进一步展开说明。"
        ],
    },
    {
        "filename": "card-list.png",
        "tag": "方法",
        "title": "3 个关键步骤",
        "list_items": [
            "第一步：先做精准调研（10分钟）",
            "第二步：确定视觉方向和 Design Token",
            "第三步：写 prompt 并过自检 checklist",
        ],
    },
    {
        "filename": "card-quote.png",
        "quote": "配图能力的核心不是工具操作，而是审美和生态理解。",
        "highlight": "工具可以学会，审美才是真正的壁垒。",
    },
]


# ============================================================
# 主函数
# ============================================================

if __name__ == "__main__":
    tokens = DESIGN_TOKENS
    fonts = load_fonts(tokens)
    
    output_dir = "output"
    os.makedirs(output_dir, exist_ok=True)
    
    for card in SAMPLE_CARDS:
        output_path = os.path.join(output_dir, card["filename"])
        generate_card(card, tokens, fonts, output_path)
    
    print(f"\n✅ 完成！共生成 {len(SAMPLE_CARDS)} 张图卡")
    print(f"📁 输出目录: {output_dir}/")


path: scripts/download_image.sh
content: #!/bin/bash
# Lovart 图片下载器（绕过 SSL 问题）
# 用法：./download_image.sh <IMAGE_URL> <OUTPUT_PATH>
#
# ⚠️ Lovart 的 a.lovart.ai 域名 SSL 证书与 curl/Python 不兼容
# 必须用 Node.js 下载

if [ $# -lt 2 ]; then
    echo "用法: ./download_image.sh <IMAGE_URL> <OUTPUT_PATH>"
    echo ""
    echo "示例:"
    echo "  ./download_image.sh 'https://a.lovart.ai/artifacts/agent/xxx.png' ./output/cover.png"
    echo ""
    echo "💡 提示: URL 中去掉 ?x-oss-process=... 参数可获取原始高质量图片"
    exit 1
fi

URL="$1"
OUTPUT="$2"

# 去掉 OSS 处理参数（获取原图）
CLEAN_URL=$(echo "$URL" | sed 's/\?x-oss-process=.*//')

echo "📥 下载图片..."
echo "   URL: $CLEAN_URL"
echo "   输出: $OUTPUT"

# 确保输出目录存在
mkdir -p "$(dirname "$OUTPUT")"

NODE_TLS_REJECT_UNAUTHORIZED=0 node -e "
const https = require('https');
const http = require('http');
const fs = require('fs');
const url = '$CLEAN_URL';
const mod = url.startsWith('https') ? https : http;
mod.get(url, {rejectUnauthorized: false}, res => {
  if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
    console.log('↪️  重定向到:', res.headers.location);
    return;
  }
  const chunks = [];
  res.on('data', c => chunks.push(c));
  res.on('end', () => {
    const buf = Buffer.concat(chunks);
    fs.writeFileSync('$OUTPUT', buf);
    console.log('✅ 下载完成:', (buf.length / 1024).toFixed(1), 'KB');
  });
}).on('error', e => {
  console.error('❌ Node.js 下载失败:', e.message);
  console.error('');
  console.error('💡 如果所有本地 SSL 客户端都失败（EPROTO / tlsv1 alert / WRONG_VERSION_NUMBER），');
  console.error('   请使用浏览器内 fetch + base64 分块提取方案（方法 C）。');
  console.error('   详见 references/lovart-operation.md 的 Phase 4 方法 C。');
  process.exit(1);
});
"


path: scripts/check_jwt.sh
content: #!/bin/bash
# JWT 过期检查脚本
# 用法：./check_jwt.sh [cookies文件路径]
# 默认：.lovart_cookies.json

COOKIES_FILE="${1:-.lovart_cookies.json}"

if [ ! -f "$COOKIES_FILE" ]; then
    echo "❌ 未找到 cookies 文件: $COOKIES_FILE"
    echo "请先获取 Lovart cookies 并保存为 JSON 文件"
    exit 1
fi

node -e "
const fs = require('fs');
const cookies = JSON.parse(fs.readFileSync('$COOKIES_FILE', 'utf8'));
const [REDACTED_SECRET] => c.name === 'usertoken');
if (!token) { console.log('❌ 未找到 usertoken cookie'); process.exit(1); }
try {
    const payload = JSON.parse(Buffer.from(token.value.split('.')[1], 'base64').toString());
    const exp = new Date(payload.exp * 1000);
    const now = new Date();
    const hoursLeft = (exp - now) / 3600000;
    console.log('📋 Cookies 文件:', '$COOKIES_FILE');
    console.log('⏰ JWT 过期时间:', exp.toISOString());
    console.log('⏳ 剩余时间:', hoursLeft.toFixed(1), '小时');
    if (hoursLeft <= 0) {
        console.log('❌ JWT 已过期！需要重新登录 Lovart 并提供新 cookies');
        process.exit(1);
    } else if (hoursLeft <= 24) {
        console.log('⚠️  JWT 将在 24 小时内过期，建议尽快更新');
    } else {
        console.log('✅ JWT 有效');
    }
} catch (e) {
    console.log('❌ JWT 解析失败:', e.message);
    process.exit(1);
}
"


path: templates/iteration-log.md
content: # 迭代日志模板

> 每次真实执行后在这里追加记录。
> 重点记"哪里翻车了、为什么、补了什么规则"。

---

## 错误分类参考

| 编号 | 类别 | 说明 |
|------|------|------|
| 1 | 信息发现不完整 | 漏分析帖子内容、遗漏用户 IP 偏好 |
| 2 | 交付物缺失 | 少了方案/prompt/图片就宣布完成 |
| 3 | 工程约束没考虑 | prompt 超长、比例不对、格式不符 |
| 4 | 结构完整性不足 | 图片风格不统一、封面和内容割裂 |
| 5 | 消费场景适配不够 | 封面不吸睛、手机缩小看不清 |

## 规则类型参考

- **触发条件**：什么情况下触发
- **必做动作**：触发后必须做什么
- **自检方式**：怎么验证做到了
- **不通过后果**：没做到会怎样

---

## 执行记录

### 第 1 轮执行

**日期**：YYYY-MM-DD
**输入帖子**：[帖子标题]
**执行版本**：v1

#### 配图方案

| 图序 | 用途 | 方案摘要 |
|------|------|---------|
| 1 | 封面 | |
| 2 | 图卡1 | |

#### 生成结果

| 图序 | prompt 摘要 | 效果 | 问题 |
|------|------------|------|------|
| 1 | | 好/一般/差 | |

#### 翻车记录

| 问题 | 错误类别(1-5) | 根因分析 | 补充的规则 | 规则类型 |
|------|-------------|---------|-----------|---------|
| | | | | |

#### 本轮总结

- 这轮最大的问题是：
- 下一轮要重点改的是：
- Skill 更新了哪些内容：

---

<!--
### 第 N 轮执行
（复制上面的模板继续记录）
-->


path: templates/design-tokens.md
content: # Design Token 模板

> 每次配图任务前，基于调研结果填写本模板。
> 代码生成图卡时必须使用这些 Token，不要自由发挥。

---

## 基础模板

```python
# ============================================================
# Design Tokens — [你的品牌/本帖主题]
# ============================================================

# 调色板
BG_COLOR     = (245, 240, 232)  # 背景色
TEXT_PRIMARY  = (30, 30, 30)     # 标题文字
TEXT_SECONDARY = (60, 55, 50)    # 正文文字
TEXT_TERTIARY = (120, 115, 108)  # 辅助文字
ACCENT_MAIN  = (212, 168, 83)   # 主强调色
ACCENT_HIGH  = (199, 91, 58)    # 高亮色
ACCENT_SOFT  = (225, 215, 200)  # 柔和色（分隔线等）

# 间距
MARGIN_X     = 96       # 左右边距
MARGIN_TOP   = 140      # 顶部留白
LINE_H_TITLE = 80       # 标题行高
LINE_H_BODY  = 52       # 正文行高

# 字体
FONT_CN_TITLE = "/path/to/chinese-bold-font"
FONT_CN_BODY  = "/path/to/chinese-regular-font"
FONT_EN_TITLE = "/path/to/english-bold-font"
FONT_EN_BODY  = "/path/to/english-regular-font"
```

---

## 示例：暖色极简风

```python
BG_COLOR      = (245, 240, 232)  # #F5F0E8 奶油米色
TEXT_PRIMARY   = (30, 30, 30)
TEXT_SECONDARY = (60, 55, 50)
TEXT_TERTIARY  = (120, 115, 108)
ACCENT_MAIN   = (212, 168, 83)   # #D4A853 暖黄金
ACCENT_HIGH   = (199, 91, 58)    # #C75B3A 暖红
ACCENT_SOFT   = (225, 215, 200)
```

## 示例：蓝紫科技风

```python
BG_COLOR      = (240, 237, 248)  # #F0EDF8 浅薰衣草
TEXT_PRIMARY   = (30, 30, 40)
TEXT_SECONDARY = (55, 50, 65)
TEXT_TERTIARY  = (110, 105, 120)
ACCENT_MAIN   = (108, 92, 231)   # #6C5CE7 主紫
ACCENT_HIGH   = (66, 133, 244)   # #4285F4 蓝
ACCENT_SOFT   = (210, 205, 225)
```

## 示例：深色科技风

```python
BG_COLOR      = (22, 25, 30)     # 深灰黑
TEXT_PRIMARY   = (245, 249, 255)  # 近白
TEXT_SECONDARY = (180, 185, 195)
TEXT_TERTIARY  = (120, 125, 135)
ACCENT_MAIN   = (0, 200, 150)    # 科技绿
ACCENT_HIGH   = (255, 165, 0)    # 橙色
ACCENT_SOFT   = (45, 48, 55)
```

---

## 推荐字体方案

### macOS

| 用途 | 字体 | 路径 |
|------|------|------|
| 中文粗体 | Hiragino Sans GB W6 | `/System/Library/Fonts/Hiragino Sans GB.ttc` (index=1) |
| 中文常规 | Hiragino Sans GB W3 | `/System/Library/Fonts/Hiragino Sans GB.ttc` (index=0) |
| 英文标题 | Inter Bold | 开源下载 [Google Fonts](https://fonts.google.com/specimen/Inter) |
| 英文正文 | Inter Regular | 同上 |

### 开源替代

| 用途 | 字体 | 说明 |
|------|------|------|
| 中文 | 思源黑体 (Noto Sans CJK) | 开源，全平台 |
| 英文标题 | Nunito Bold | 圆角无衬线，可爱科技感 |
| 英文正文 | Inter | 21世纪标准无衬线 |
| 圆角字体 | SF Pro Rounded / Varela Round | 友好感 |

---

## 图片尺寸规范

| 用途 | 尺寸 | 比例 | 说明 |
|------|------|------|------|
| 小红书封面 | 1080×1440 px | 3:4 | 竖图（推荐） |
| 小红书方图 | 1080×1080 px | 1:1 | 方图 |
| 内页图卡 | 1080×1440 px | 3:4 | 和封面保持一致 |
| Lovart 生成 | 1792×2400 px | 3:4 | Lovart 默认输出 |


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)