.opencode/skills/imgx-template-generator/SKILL.md
生成符合 Satori 渲染约束的 IMGX Vue 模板,将文本转换为精美的卡片图片
npx skillsauth add aatrooox/imgx imgx-template-generatorInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
生成符合 Satori 渲染约束的 IMGX Vue 模板,将文本转换为精美的卡片图片。
在开始之前,必须阅读以下文档:
| 文档 | 时长 | 内容 | |------|------|------| | 📖 架构说明 | 2 分钟 | 了解模板字符串是唯一真相源,避免创建无用的 Vue 组件 | | ⚠️ Satori 约束 | 3 分钟 | 必须遵守的渲染规则(Flexbox、支持的 CSS) | | 🔧 Props 系统 | 3 分钟 | 理解数据结构(content、styleProps) |
为什么必须先读?
根据用户需求,选择对应的模板蓝图:
| 蓝图类型 | 适用场景 | 阅读时长 | 文档链接 | |---------|---------|---------|---------| | 📝 简单文本 | 单行/多行纯文本,统一样式,居中布局 | 5 分钟 | blueprint-simple-text.md | | 📋 多行布局 | 多行文本,每行不同颜色/大小/对齐方式 | 6 分钟 | blueprint-multi-line.md | | 🎨 强调文本 | 需要高亮关键词(背景盒子/特殊颜色) | 7 分钟 | blueprint-with-accent.md | | 🌈 渐变背景 | 使用线性/径向渐变,复杂背景效果 | 6 分钟 | blueprint-with-gradient.md | | 🎯 带图标 | 需要显示图标/emoji/装饰元素 | 7 分钟 | blueprint-with-icons.md | | 🧱 像素矩阵 | 像素艺术风格,用 emoji/颜色拼成文字或图案 | 5 分钟 | blueprint-pixel-matrix.md |
⚠️ 图标使用限制(重要)
IMGX 系统仅支持本地图标库(assets/icons/ 目录),不支持远程图标库:
| 支持 | 格式 | 说明 |
|------|------|------|
| ✅ 本地图标 | prefix:icon-name | 从 assets/icons/ 加载的图标集 |
| ❌ lucide | ~~lucide:sparkles~~ | 远程图标(网络不稳定) |
| ❌ material | ~~material:home~~ | 远程图标(未实现) |
| ❌ fa | ~~fa:github~~ | 远程图标(未实现) |
| ❌ heroicons | ~~heroicons:check~~ | 远程图标(未实现) |
当前可用图标集:
twemoji:beaming-face-with-smiling-eyes 😁, twemoji:cowboy-hat-face 🤠assets/icons/twemoji-face-icons.json如何添加新图标集:
assets/icons/lib/icons.ts 中导入并注册新图标集<prefix>:icon-name(prefix 为图标集的 prefix 字段)查看可用图标: 查看 assets/icons/ 目录下的 .json 文件
✨ 每个蓝图包含:
// server/templates/[Name].ts
export const [Name]Template = `<div class="w-full h-full flex">
<!-- 根据蓝图填充内容 -->
</div>`
要点:
[Name]Templatew-full h-full flexclass="flex"// server/utils/image.ts
import { [Name]Template } from '../templates/[Name]'
const templateStrings: Record<string, string> = {
// ... 其他模板
'[Name]': [Name]Template, // ← 新增这行
}
# 1. 检查下一个可用编号
ls presets/*.json | sort
# 2. 创建 preset 文件
# presets/[code].json
**📋 Preset 命名规范**:参考 [preset-naming-convention.md](references/preset-naming-convention.md)
- 文章封面(2.35:1)→ `1xx` (如 `101.json`)
- 小红书(3:4)→ `2xx` (如 `201.json`)
- 视频封面(16:9)→ `3xx` (如 `301.json`)
- 方形(1:1)→ `4xx` (如 `401.json`)
- OG 分享(1.91:1)→ `5xx` (如 `501.json`)
{
"code": "007",
"name": "My Template",
"size": { "width": 1200, "height": 630 },
"ratio": "1.91:1",
"template": "[Name]",
"contentProps": { "content": [[...]] },
"styleProps": {
"bgColor": "#FFFFFF",
"bgImage": "linear-gradient(...)",
"textWrapBgColor": "transparent",
"textWrapPadding": "0px",
"colors": ["#000000"],
"accentColors": ["#FF0000"],
"fontSizes": ["48px"],
"aligns": ["justify-center"],
"verticalAligns": ["center"],
"fontFamily": "YouSheBiaoTiHei",
"padding": "60px"
}
}
关键字段:
template: 必须与 templateStrings 的 key 一致styleProps: 必须包含所有 12 个必需字段(见蓝图文档)# 启动开发服务器(如未运行)
pnpm dev
# 访问测试 URL
http://localhost:4573/[code]/default
http://localhost:4573/[code]/测试文本*强调*内容
使用质量检查清单: checklist.md
atob() Errors in ProductionIf you encounter InvalidCharacterError: Invalid character at atob in production (but works locally), follow this diagnostic checklist:
Symptom:
[Image] Invalid base64 characters detected: 137,80,78,71,13,10...
Root Cause:
storage.getItemRaw<Buffer>() may return Uint8Array or plain objects in production.toString('base64') methodFix (server/utils/image-loader.ts):
// ❌ WRONG: Assumes buffer is always Buffer
const base64 = buffer.toString('base64')
// ✅ CORRECT: Defensive type conversion
const properBuffer = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer)
const base64 = properBuffer.toString('base64')
const dataUrl = `data:${mimeType};base64,${base64}`
// Add diagnostic logging
console.log('[ImageLoader] Base64 preview:', base64.substring(0, 50))
console.log(`[ImageLoader] type: ${properBuffer.constructor.name}`)
Symptom:
InvalidCharacterError: Invalid character
at atob (node:buffer:1292:13)
at ef (satori/dist/index.js:3:49379)
Root Cause:
backgroundImage: url(data:image/...;base64,...)atob() fails on certain base64 formatsFix: Use <img> instead of backgroundImage
// ❌ AVOID: CSS backgroundImage with data URLs
<div :style="{ backgroundImage: `url(${logoUrl})` }"></div>
// ✅ RECOMMENDED: Direct img element
<img :src="logoUrl" :style="{ objectFit: 'cover' }" />
Why <img> works better:
Symptom:
`nowrap` unknown or invalid utility
Fix:
// ❌ WRONG: Not a valid Tailwind class
<span class="text-nowrap flex">
// ✅ CORRECT: Valid Tailwind class
<span class="whitespace-nowrap flex">
Invalid CSS classes can interfere with Satori's template parsing.
Symptom:
atob() errors with SVG iconsRoot Cause:
btoa() is a browser API, unreliable in Node.jsFix (server/utils/icons.ts, lib/icons.ts):
// ❌ WRONG: Browser API in server code
const base64 = btoa(svgHTML)
// ✅ CORRECT: Node.js Buffer API
const base64 = Buffer.from(svgHTML, 'utf8').toString('base64')
Symptom:
?bgColor=transparent produces black background instead of transparentinherit, currentColor) also failRoot Cause:
paramNormalizer.ts auto-adds # prefix to all color valuestransparent becomes #transparent (invalid color)Fix (server/utils/paramNormalizer.ts):
// In normalizeValue() for COLOR_PROPS
if (COLOR_PROPS.has(key)) {
if (strValue.startsWith('#')) {
return strValue
}
// ✅ NEW: Preserve CSS color keywords
const cssColorKeywords = ['transparent', 'inherit', 'currentColor', 'none']
if (cssColorKeywords.includes(strValue.toLowerCase())) {
return strValue.toLowerCase()
}
return `#${strValue}`
}
Usage:
# ✅ Now works correctly
GET /api/104/text?bgColor=transparent
GET /api/104/text?bgColor=inherit
Symptom:
[Image] Base64 contains whitespace - sanitizing
Root Cause:
+ to spaceatob() rejects base64 with whitespaceFix (server/utils/image.ts):
// Add sanitization before Satori rendering
if (styleFinalProps.logoUrl?.startsWith('data:')) {
const idx = styleFinalProps.logoUrl.indexOf(',');
if (idx !== -1) {
const head = styleFinalProps.logoUrl.slice(0, idx + 1);
let body = styleFinalProps.logoUrl.slice(idx + 1);
// Remove whitespace corruption
if (/\s/.test(body)) {
console.warn('[Image] Base64 contains whitespace - sanitizing');
body = body.replace(/\s+/g, '');
}
// Validate charset
if (!/^[A-Za-z0-9+/=]+$/.test(body)) {
throw createError({
statusCode: 500,
statusMessage: 'Invalid image data encoding'
});
}
styleFinalProps.logoUrl = head + body;
}
}
Prevention:
logoPath=images/logo.png+ characters)1. Add Logging at Key Points:
// In image-loader.ts
console.log('[ImageLoader] Base64 preview:', base64.substring(0, 50))
console.log('[ImageLoader] Buffer type:', buffer.constructor.name)
// In image.ts
console.log('[Image] logoUrl preview:', styleFinalProps.logoUrl?.substring(0, 60))
// In satori.ts
console.log('[Satori] Rendering HTML length:', html.length)
2. Check Production Logs:
✅ Success Pattern:
[ImageLoader] Base64 preview: iVBORw0KGgoAAAANSUhEU...
[ImageLoader] type: Buffer
❌ Failure Pattern:
[Image] Invalid base64 characters: 137,80,78,71...
→ Indicates Buffer serialization issue
3. Verify Base64 Integrity:
# Extract base64 from logs and test decode
echo "iVBORw0KGgoAAAA..." | base64 -d | file -
# Should show: PNG image data
| Component | Best Practice | Avoid |
|-----------|---------------|-------|
| Images | Use <img src="data:..."> | backgroundImage: url(...) |
| Buffer | Buffer.isBuffer() check | Assume storage returns Buffer |
| Base64 | Buffer.from().toString('base64') | btoa() on server |
| CSS | Valid Tailwind classes | text-nowrap (invalid) |
| Data URLs | Pass as asset paths | Query string parameters |
| Logging | Preview first 50 chars | Log full 10KB base64 |
If after applying all fixes you still see atob() errors:
npm ls satori)format=svg)class="flex"用户需求
↓
📖 阅读架构文档(architecture.md, satori-constraints.md, props-system.md)
↓
🎨 选择蓝图(simple-text / multi-line / accent / gradient / icons)
↓
📝 阅读蓝图文档(了解完整代码和配置)
↓
💻 创建 server/templates/[Name].ts
↓
⚙️ 注册到 server/utils/image.ts
↓
📦 创建 presets/[code].json
↓
🧪 测试 http://localhost:4573/[code]/default
↓
✅ 质量检查(checklist.md)
↓
🎉 完成!
问题 1: 是否需要强调特定文字(高亮/背景盒子)?
├─ 是 → 🎨 使用 blueprint-with-accent.md
└─ 否 → 继续
问题 2: 是否需要渐变背景或复杂背景效果?
├─ 是 → 🌈 使用 blueprint-with-gradient.md
└─ 否 → 继续
问题 3: 是否需要显示图标或 emoji?
├─ 是 → 🎯 使用 blueprint-with-icons.md
└─ 否 → 继续
问题 4: 是否需要像素艺术风格(用色块/emoji拼成图案)?
├─ 是 → 🧱 使用 blueprint-pixel-matrix.md
└─ 否 → 继续
问题 5: 是否需要多行文本且每行样式不同?
├─ 是 → 📋 使用 blueprint-multi-line.md
└─ 否 → 📝 使用 blueprint-simple-text.md
预计时间:30 分钟完成第一个模板
预计时间:15 分钟完成新模板
一个合格的模板必须满足:
/{code}/default 正常显示开始前必读: 架构说明 → Satori 约束 → 选择蓝图
遇到问题? 查阅 故障排除指南 或 Production Debugging
tools
Use when work should span one or more detached tasks but still behave like one job with a single owner context. TaskFlow is the durable flow substrate under authoring layers like Lobster, ACPX, plugins, or plain code. Keep conditional logic in the caller; use TaskFlow for flow identity, child-task linkage, waiting state, revision-checked mutations, and user-facing emergence.
tools
# Lobster Lobster executes multi-step workflows with approval checkpoints. Use it when: - User wants a repeatable automation (triage, monitor, sync) - Actions need human approval before executing (send, post, delete) - Multiple tool calls should run as one deterministic operation ## When to use Lobster | User intent | Use Lobster? | | ------------------------------------------------------ | --------------------------
tools
# Lobster Lobster executes multi-step workflows with approval checkpoints. Use it when: - User wants a repeatable automation (triage, monitor, sync) - Actions need human approval before executing (send, post, delete) - Multiple tool calls should run as one deterministic operation ## When to use Lobster | User intent | Use Lobster? | | ------------------------------------------------------ | --------------------------
tools
A CLI tool for making authenticated requests to the X (Twitter) API. Use this skill when you need to post tweets, reply, quote, search, read posts, manage followers, send DMs, upload media, or interact with any X API v2 endpoint.