
Pw Image Generation
- 57 installs
- 22 repo stars
- Updated January 24, 2026
- plugins-world/pw-skills
pw-image-generation is a Claude Code skill for ai & agent building.
About
pw-image-generation is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- pw-image-generation
- AI & Agent Building
- AI-coding skill
Pw Image Generation by the numbers
- 57 all-time installs (skills.sh)
- Ranked #6,669 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/plugins-world/pw-skills --skill pw-image-generationAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 57 |
|---|---|
| repo stars | ★ 22 |
| Last updated | January 24, 2026 |
| Repository | plugins-world/pw-skills ↗ |
How do I helps with ai & agent building tasks.?
Helps with ai & agent building tasks.
Who is it for?
Best when you're working on ai & agent building and need structured help with pw image generation.
Skip if: Teams with no ai & agent building needs, or anyone wanting a generic chat assistant without this specific workflow.
When should I use this skill?
When you need to helps with ai & agent building tasks., or when pw-image-generation is a claude code skill for ai & agent building.
What you get
Structured output aligned to pw-image-generation: pw-image-generation, AI & Agent Building.
Files
Image-Generation - AI 图像生成工作流
核心理念: 分析风格 → 设计提示词 → 用户确认 → 生成图像,避免额度浪费
核心功能
图像生成:
- 文生图: 根据文本描述生成图像
- 图生图: 基于参考图片生成类似风格的图像
- 批量生成: 使用提示词模板批量生成多张图像
- 风格一致: 保持系列图像的视觉风格统一
图像处理:
- 长图合并: 将系列图片垂直拼接为一张长图
- PPT 打包: 将系列图片打包为 PPT 文件 (每张图片一页)
- 图床管理: 上传图片到图床并管理删除链接
使用时机
适用场景:
- 用户明确要求 "生成图像"、"创建配图"、"制作插画"
- 用户提供文本描述并需要视觉化呈现
- 用户提供参考图片并要求生成类似风格的图像
- 用户需要为文章、PPT、社交媒体制作配图
- 用户需要批量生成风格一致的系列图像
- 用户需要将多张图片合并为长图或 PPT
不适用场景:
- 用户只是询问如何生成图像 (提供建议即可)
- 用户需要编辑现有图片 (使用图片编辑工具)
- 用户需要图像识别或分析 (使用视觉分析工具)
- 用户需要截图或屏幕录制 (使用系统工具)
---
工作流程
分析风格 → 设计提示词 → 安装依赖 → 配置API → 确认生成 → 保存图像
↓ ↓ ↓ ↓ ↓ ↓
风格文件 提示词文件 node-fetch secrets.md 逐张确认 images/---
脚本目录
所有脚本位于 ~/.claude/skills/pw-image-generation/scripts/ 目录中。
Agent 执行说明: 1. 确定此 SKILL.md 文件的目录路径为 SKILL_DIR 2. 脚本路径 = ${SKILL_DIR}/scripts/<script-name>.ts 3. 将本文档中的所有 ${SKILL_DIR} 替换为实际路径
可用脚本:
| 脚本 | 功能 | 参数 |
|---|---|---|
generate-image.ts | 生成图像 (逐张确认) | [输出目录] |
upload-image.ts | 上传图片到图床 | <图片路径> |
delete-image.ts | 管理图床图片 | `list\ |
merge-to-long-image.ts | 合并为长图 | <图片目录> <输出文件> |
merge-to-pptx.ts | 打包为 PPT | <图片目录> <输出文件> |
analyze-image.ts | 分析图像风格 | <图像URL或路径> |
快速开始
Step 1: 创建项目目录
mkdir my-image-project && cd my-image-projectStep 2: 复制配置模板(可选)
cp -r ~/.claude/skills/pw-image-generation/config.example ./config
cp ~/.claude/skills/pw-image-generation/references/.gitignore.template ./.gitignore
# 编辑 config/secrets.md 自定义 API 密钥(可选)Step 3: 创建提示词
mkdir -p prompts
cp ~/.claude/skills/pw-image-generation/references/prompt-templates/提示词模板.md ./prompts/我的提示词.md
vim ./prompts/我的提示词.md参考 references/style-library.md 选择合适的风格。
Step 4: 生成图像
npx -y bun ~/.claude/skills/pw-image-generation/scripts/generate-image.ts脚本会逐张询问确认,避免浪费额度。
---
工具脚本详解
1. 生成图像
命令:
npx -y bun ~/.claude/skills/pw-image-generation/scripts/generate-image.ts [输出目录]参数说明:
[输出目录]: 可选,指定图像保存目录,默认为./images
工作流程: 1. 读取 prompts/ 目录下的所有提示词文件 2. 逐张显示提示词内容 3. 询问是否生成该图像 (y/n/q) 4. 生成图像并保存到输出目录 5. 支持中断和恢复
确认选项:
y(yes): 生成当前图像n(no): 跳过当前图像,继续下一张q(quit): 退出生成流程
示例:
# 使用默认输出目录
npx -y bun ~/.claude/skills/pw-image-generation/scripts/generate-image.ts
# 指定输出目录
npx -y bun ~/.claude/skills/pw-image-generation/scripts/generate-image.ts ./my-images---
2. 上传图片到图床
命令:
npx -y bun ~/.claude/skills/pw-image-generation/scripts/upload-image.ts <图片路径>参数说明:
<图片路径>: 必需,要上传的本地图片文件路径
功能特性:
- 自动上传到 freeimage.host (永久存储)
- 返回可用的图片 URL
- 自动保存删除链接到
.upload-history.json - 用于图生图的参考图上传
示例:
# 上传单张图片
npx -y bun ~/.claude/skills/pw-image-generation/scripts/upload-image.ts ./template/参考图.png
# 上传后会返回:
# - 图片 URL (用于提示词中的 image_url)
# - 删除链接 (保存在历史记录中)输出示例:
上传成功!
图片 URL: https://iili.io/xxx.png
删除链接已保存到 .upload-history.json---
3. 管理图床图片
命令:
# 列出所有上传的图片
npx -y bun ~/.claude/skills/pw-image-generation/scripts/delete-image.ts list
# 删除指定索引的图片
npx -y bun ~/.claude/skills/pw-image-generation/scripts/delete-image.ts delete <索引>
# 删除所有图片
npx -y bun ~/.claude/skills/pw-image-generation/scripts/delete-image.ts delete-all参数说明:
list: 列出所有上传记录delete <索引>: 删除指定索引的图片 (索引从 0 开始)delete-all: 删除所有图片
功能特性:
- 查看上传历史和删除链接
- 单个或批量删除图片
- 自动更新历史记录文件
示例:
# 查看上传历史
npx -y bun ~/.claude/skills/pw-image-generation/scripts/delete-image.ts list
# 输出:
# 0: 参考图.png - https://iili.io/xxx.png (2024-01-15)
# 1: 配图1.png - https://iili.io/yyy.png (2024-01-16)
# 删除索引为 0 的图片
npx -y bun ~/.claude/skills/pw-image-generation/scripts/delete-image.ts delete 0
# 删除所有图片 (会要求确认)
npx -y bun ~/.claude/skills/pw-image-generation/scripts/delete-image.ts delete-all详细说明见 references/图床上传.md
---
4. 合并长图
命令:
npx -y bun ~/.claude/skills/pw-image-generation/scripts/merge-to-long-image.ts <图片目录> <输出文件>参数说明:
<图片目录>: 必需,包含要合并的图片的目录<输出文件>: 必需,输出的长图文件名
功能特性:
- 垂直拼接多张图片
- 自动识别 jpg/png/gif/webp 格式
- 按文件名数字排序
- 保持原图宽度,自动计算高度
依赖要求: 需要安装 ImageMagick:
brew install imagemagick示例:
# 合并 images 目录下的所有图片
npx -y bun ~/.claude/skills/pw-image-generation/scripts/merge-to-long-image.ts ./images 长图.png
# 指定其他目录
npx -y bun ~/.claude/skills/pw-image-generation/scripts/merge-to-long-image.ts ./my-images 合集.jpg使用场景:
- 制作小红书/微信公众号长图
- 合并系列教程截图
- 制作图片合集展示
---
5. 合并为 PPT
命令:
npx -y bun ~/.claude/skills/pw-image-generation/scripts/merge-to-pptx.ts <图片目录> <输出文件>参数说明:
<图片目录>: 必需,包含要打包的图片的目录<输出文件>: 必需,输出的 PPT 文件名
功能特性:
- 每张图片占一页
- 自动识别 jpg/png/gif/webp 格式
- 按文件名数字排序
- 16:9 比例,自动适应页面大小
- 图片居中显示
示例:
# 打包 images 目录下的图片为 PPT
npx -y bun ~/.claude/skills/pw-image-generation/scripts/merge-to-pptx.ts ./images 配图.pptx
# 指定其他目录
npx -y bun ~/.claude/skills/pw-image-generation/scripts/merge-to-pptx.ts ./my-images 展示.pptx使用场景:
- 快速制作图片演示 PPT
- 打包系列配图用于分享
- 将生成的图像整理成演示文档
---
6. 分析图像风格
命令:
# 从 URL 分析
npx -y bun ~/.claude/skills/pw-image-generation/scripts/analyze-image.ts <图像URL>
# 从本地文件分析
npx -y bun ~/.claude/skills/pw-image-generation/scripts/analyze-image.ts <本地路径>参数说明:
<图像URL或路径>: 必需,要分析的图像 URL 或本地文件路径
功能特性:
- 分析图像的视觉风格
- 生成适合的提示词建议
- 识别颜色、构图、艺术风格
- 保存分析结果到
analysis/目录
示例:
# 分析在线图片
npx -y bun ~/.claude/skills/pw-image-generation/scripts/analyze-image.ts https://example.com/image.jpg
# 分析本地图片
npx -y bun ~/.claude/skills/pw-image-generation/scripts/analyze-image.ts ./template/参考图.png使用场景:
- 学习参考图片的风格特征
- 为图生图准备提示词
- 了解如何描述特定视觉风格
文件结构
项目目录结构
my-image-project/
├── config/
│ └── secrets.md # API 配置(可选)
├── template/ # PDF 模板图片
├── prompts/ # 提示词文件
├── analysis/ # 风格分析(可选)
├── images/ # 生成的图像
└── .gitignoreSkill 目录结构
pw-image-generation/
├── SKILL.md # 本文件(核心文档)
├── config.example/ # 配置模板
│ ├── README.md # 配置说明
│ └── secrets.md # API 配置模板
├── references/ # 参考文档
│ ├── .gitignore.template # Git 忽略文件模板
│ ├── 图床上传.md # 图床上传指南
│ ├── style-library.md # 风格库(9种预设风格)
│ └── prompt-templates/
│ └── 提示词模板.md # 提示词模板
└── scripts/
├── analyze-image.ts # 分析图像风格
├── generate-image.ts # 生成图像(支持确认和跳过)
├── upload-image.ts # 上传图片到图床
├── delete-image.ts # 管理和删除图床图片
├── merge-to-long-image.ts # 合并长图
└── merge-to-pptx.ts # 打包为 PPT---
常见问题和错误处理
1. 生成图像失败
问题: 图像生成失败或返回错误
可能原因:
- API 密钥未配置或无效
- 提示词格式不正确
- 网络连接问题
- API 额度不足
解决方案:
# 检查配置文件
cat config/secrets.md
# 验证提示词格式
cat prompts/提示词.md
# 测试网络连接
curl -I https://api.openai.com
# 查看详细错误信息 (脚本会自动显示)---
2. 图床上传失败
问题: 上传图片到图床失败
可能原因:
- 图片文件不存在或路径错误
- 图片格式不支持
- 图片文件过大 (超过 10MB)
- 网络连接问题
解决方案:
# 检查文件是否存在
ls -lh ./template/图片.png
# 检查文件大小
du -h ./template/图片.png
# 如果文件过大,压缩图片
# 使用 ImageMagick 压缩
convert ./template/图片.png -quality 85 -resize 2000x2000\> ./template/图片_compressed.png---
3. 合并长图失败
问题: 合并长图时报错 "ImageMagick not found"
可能原因:
- 未安装 ImageMagick
- ImageMagick 未添加到 PATH
解决方案:
# macOS 安装
brew install imagemagick
# 验证安装
convert --version
# 如果仍然失败,检查 PATH
which convert---
4. PPT 生成失败
问题: 生成 PPT 时报错或 PPT 无法打开
可能原因:
- 图片目录为空或不存在
- 图片格式不支持
- 输出文件路径无效
解决方案:
# 检查图片目录
ls -la ./images
# 检查图片格式 (支持 jpg/png/gif/webp)
file ./images/*.png
# 确保输出目录存在
mkdir -p ./output
npx -y bun ~/.claude/skills/pw-image-generation/scripts/merge-to-pptx.ts ./images ./output/配图.pptx---
5. 提示词不生效
问题: 生成的图像与提示词描述不符
可能原因:
- 提示词描述不够具体
- 提示词语言混乱 (中英文混用)
- 提示词过长或过短
- 风格描述不明确
解决方案:
# 不好的提示词
一张图片
# 好的提示词
水彩风格,温馨的咖啡馆场景,柔和的光线,暖色调,手绘质感
# 使用风格库
参考 references/style-library.md 选择合适的风格描述---
6. 批量生成中断
问题: 批量生成过程中意外中断
可能原因:
- 网络不稳定
- 用户手动中断 (Ctrl+C)
- API 限流
解决方案:
- 脚本会自动跳过已生成的图像
- 重新运行生成命令即可继续
- 已生成的图像不会重复生成
- 使用
n选项跳过不需要的图像
---
7. 配置文件问题
问题: 配置文件不生效或找不到
可能原因:
- 配置文件路径错误
- 配置文件格式不正确
- 未复制配置模板
解决方案:
# 复制配置模板
cp -r ~/.claude/skills/pw-image-generation/config.example ./config
# 检查配置文件
cat config/secrets.md
# 配置文件格式示例
# API_BASE_URL=https://api.openai.com/v1
# API_KEY=sk-xxx---
最佳实践
图像尺寸规范
根据不同使用场景选择合适的图像尺寸:
| 场景 | 比例 | 推荐像素 | 说明 |
|---|---|---|---|
| 文章配图 | 16:9 | 1920×1080 | 高清标准, 适配大屏阅读 |
| 公众号封面 | 2.35:1 | 900×383 | 微信官方推荐尺寸 |
| 小红书封面/配图 | 3:4 | 1242×1660 | 小红书推荐, 适配 iPhone 屏幕 |
| X 文章封面 | 5:2 | 1500×600 | X Articles 官方推荐 |
| 论文配图 | 16:9 | 1920×1080 | 高清标准 |
补充说明:
- 小红书也支持 1:1 (1080×1080) 和 4:3 (1440×1080)
- 公众号次图封面可用 200×200 (1:1)
使用建议:
- 在提示词中指定尺寸:
size: 1920x1080或aspect_ratio: 16:9 - 优先使用推荐尺寸, 避免后期裁剪导致构图失衡
- 不同平台的尺寸要求不同, 生成前确认目标平台
---
提示词设计
基本原则:
- 具体明确: 详细描述场景、风格、颜色、构图
- 风格统一: 使用风格库中的预设风格描述
- 语言一致: 全英文或全中文,避免混用
- 长度适中: 50-200 字为宜,过短缺乏细节,过长容易混乱
推荐结构:
[风格] + [主体] + [场景] + [氛围] + [技术细节]
示例:
水彩风格,一只可爱的小猫,坐在窗台上,温暖的午后阳光,柔和的色彩,手绘质感参考资源:
references/style-library.md: 9 种预设风格references/prompt-templates/提示词模板.md: 提示词模板
---
图像生成流程
推荐步骤: 1. 先生成 1-2 张测试图像,验证提示词效果 2. 根据测试结果调整提示词 3. 确认效果满意后,批量生成剩余图像 4. 使用确认机制,避免浪费 API 额度 5. 定期备份生成的图像
避免浪费:
- 每次生成前仔细检查提示词
- 使用
n选项跳过不需要的图像 - 使用
q选项及时退出 - 不要盲目批量生成
---
图生图技巧
上传参考图:
# 1. 上传参考图到图床
npx -y bun ~/.claude/skills/pw-image-generation/scripts/upload-image.ts ./template/参考图.png
# 2. 复制返回的 URL
# 3. 在提示词中使用
# image_url: https://iili.io/xxx.png
# prompt: 类似风格的场景,保持色调和构图注意事项:
- 参考图尺寸建议 1024x1024 或更大
- 参考图清晰度要高
- 提示词要明确说明保留哪些特征
- 可以多次尝试不同的提示词组合
---
文件管理
目录结构建议:
my-image-project/
├── config/ # 配置文件 (不提交到 git)
├── template/ # 参考图片
├── prompts/ # 提示词文件
│ ├── 01-封面.md
│ ├── 02-内容1.md
│ └── 03-内容2.md
├── images/ # 生成的图像
│ ├── 01-封面.png
│ ├── 02-内容1.png
│ └── 03-内容2.png
└── output/ # 最终输出 (长图/PPT)命名规范:
- 提示词文件: 使用数字前缀排序 (01-, 02-, 03-)
- 生成的图像: 自动使用提示词文件名
- 便于批量处理和排序
---
性能优化
提高生成速度:
- 使用较小的图像尺寸 (1024x1024 而非 2048x2048)
- 避免过于复杂的提示词
- 合理使用图生图 (比文生图更快)
节省 API 额度:
- 使用确认机制,不要跳过确认
- 先测试单张,再批量生成
- 保存好的提示词作为模板复用
- 定期清理图床上不需要的图片
---
质量控制
检查清单:
- [ ] 提示词描述清晰具体
- [ ] 风格描述统一一致
- [ ] 测试图像效果满意
- [ ] 图像尺寸和格式正确
- [ ] 文件命名规范有序
- [ ] 备份重要图像
常见问题:
- 图像风格不一致: 检查提示词中的风格描述
- 图像质量不佳: 增加技术细节描述 (如 "高清"、"细节丰富")
- 图像内容偏差: 提示词更具体,增加约束条件
---
重要提示
避免额度浪费
- 每次生成前都会询问确认
- 支持跳过已生成的图像
- 一张一张生成,避免批量消耗额度
配置管理
- 配置文件
config/secrets.md是可选的 - 未配置时使用默认配置 (需要设置环境变量 API_KEY)
- 配置文件不会提交到版本控制 (已在 .gitignore 中)
- 支持自定义 API 端点和模型
运行环境
脚本使用 Bun 运行,无需本地安装依赖:
# 直接运行,Bun 会自动处理依赖
npx -y bun ~/.claude/skills/pw-image-generation/scripts/generate-image.ts系统要求:
- Node.js 或 Bun 运行时
- macOS/Linux/Windows (WSL)
- 合并长图需要 ImageMagick
---
配置说明
查看 config.example/secrets.md 了解配置选项:
可配置项:
API_BASE_URL: API 基础 URL (默认: OpenAI API)ANALYSIS_MODEL_ID: 图像分析模型 (默认: gpt-4-vision-preview)GENERATION_MODEL_ID: 图像生成模型 (默认: dall-e-3)API_KEY: API 密钥 (必需)
配置示例:
# config/secrets.md
API_BASE_URL=https://api.openai.com/v1
ANALYSIS_MODEL_ID=gpt-4-vision-preview
GENERATION_MODEL_ID=dall-e-3
API_KEY=sk-your-api-key-here---
风格库
Skill 提供 9 种预设风格,保证图像风格一致性:
- 水彩风格 (watercolor) - 柔和温馨
- 扁平化设计 (flat-design) - 现代简洁
- 3D 渲染 (3d-render) - 立体真实
- 油画风格 (oil-painting) - 艺术经典
- 赛博朋克 (cyberpunk) - 科幻未来
- 像素艺术 (pixel-art) - 复古怀旧
- 手绘插画 (hand-drawn) - 温暖个性
- 照片写实 (photorealistic) - 高度真实
- 抽象艺术 (abstract) - 情感表达
查看 references/style-library.md 了解每种风格的详细说明和使用场景。
---
使用建议
Agent 使用指南
当用户请求生成图像时:
1. 确认需求: 询问用户图像用途、风格偏好、数量 2. 准备环境: 引导用户创建项目目录和配置 3. 设计提示词: 根据需求设计提示词,参考风格库 4. 生成测试: 先生成 1-2 张测试图像 5. 批量生成: 确认效果后批量生成 6. 后期处理: 根据需要合并长图或打包 PPT
典型工作流
场景 1: 文章配图
# 1. 创建项目
mkdir article-images && cd article-images
# 2. 准备提示词
mkdir prompts
# 创建 prompts/01-封面.md, 02-配图1.md 等
# 3. 生成图像
npx -y bun ~/.claude/skills/pw-image-generation/scripts/generate-image.ts
# 4. 合并长图 (可选)
npx -y bun ~/.claude/skills/pw-image-generation/scripts/merge-to-long-image.ts ./images 长图.png场景 2: PPT 配图
# 1. 创建项目
mkdir ppt-images && cd ppt-images
# 2. 准备提示词 (按页面顺序命名)
mkdir prompts
# 创建 prompts/01-标题页.md, 02-内容1.md 等
# 3. 生成图像
npx -y bun ~/.claude/skills/pw-image-generation/scripts/generate-image.ts
# 4. 打包为 PPT
npx -y bun ~/.claude/skills/pw-image-generation/scripts/merge-to-pptx.ts ./images 配图.pptx场景 3: 图生图
# 1. 上传参考图
npx -y bun ~/.claude/skills/pw-image-generation/scripts/upload-image.ts ./template/参考图.png
# 2. 在提示词中使用返回的 URL
# prompts/01-类似风格.md:
# image_url: https://iili.io/xxx.png
# prompt: 类似风格的场景,保持色调和构图
# 3. 生成图像
npx -y bun ~/.claude/skills/pw-image-generation/scripts/generate-image.ts---
命令速查
# 生成图像
npx -y bun ~/.claude/skills/pw-image-generation/scripts/generate-image.ts [输出目录]
# 上传图片
npx -y bun ~/.claude/skills/pw-image-generation/scripts/upload-image.ts <图片路径>
# 管理图床
npx -y bun ~/.claude/skills/pw-image-generation/scripts/delete-image.ts list
npx -y bun ~/.claude/skills/pw-image-generation/scripts/delete-image.ts delete <索引>
npx -y bun ~/.claude/skills/pw-image-generation/scripts/delete-image.ts delete-all
# 合并长图
npx -y bun ~/.claude/skills/pw-image-generation/scripts/merge-to-long-image.ts <图片目录> <输出文件>
# 打包 PPT
npx -y bun ~/.claude/skills/pw-image-generation/scripts/merge-to-pptx.ts <图片目录> <输出文件>
# 分析图像
npx -y bun ~/.claude/skills/pw-image-generation/scripts/analyze-image.ts <图像URL或路径>配置模板
这个目录包含 API 配置模板。配置是可选的,只有需要自定义 API 设置时才需要。
使用方法
复制到项目中
# 在项目根目录执行
cp ~/.claude/skills/pw-image-generation/config.example/secrets.md ./config/secrets.md配置 API 密钥
编辑 config/secrets.md,将 your-api-key-here 替换为实际的 API Key。
默认配置
如果不需要自定义配置,可以直接使用默认配置:
node ~/.claude/skills/pw-image-generation/scripts/generate-image.js脚本会使用内置的默认配置。
参考文档
references/style-library.md- 9 种预设图像风格references/prompt-templates/- 提示词模板references/图床上传.md- 图床上传指南references/.gitignore.template- Git 忽略文件模板
<!-- input: 无 output: API 配置供脚本读取 pos: 配置文件模板
使用方法: 1. 复制 config.example 目录到项目的 config 目录 2. 修改 config/secrets.md 中的配置项 -->
API 配置
API 基础配置
API 基础 URL(包含域名和路径前缀)
API_BASE_URL=https://ai-router.plugins-world.cn
模型配置
图像分析模型(用于分析图像风格)
ANALYSIS_MODEL_ID=gemini-2.0-flash-exp
图像生成模型(用于生成图像)
GENERATION_MODEL_ID=gemini-3-pro-image-preview
API 端点配置
分析端点路径(会拼接在 API_BASE_URL 后面)
ANALYSIS_ENDPOINT=/v1beta/models/{model}:generateContent
生成端点路径(会拼接在 API_BASE_URL 后面)
GENERATION_ENDPOINT=/v1beta/models/{model}:generateContent
认证配置
AI Router API 密钥(支持多种模型,不局限于特定平台)
获取地址:https://ai-router.plugins-world.cn/console/token
API_KEY=your-ai-router-api-key-here
图床配置(可选)
图床上传端点(如果不配置,则使用 BASE64 内嵌)
IMAGE_UPLOAD_ENDPOINT=
# 项目 .gitignore 文件
# 依赖
node_modules/
package-lock.json
yarn.lock
# 配置文件
config/secrets.md
config/local.config.js
config/.env*
# 输出文件
output/
*.log
*.tmp
# 编辑器
.vscode/
.idea/
*.swp
*.swo
*~
# 系统
.DS_Store
Thumbs.db
# 临时文件
*.tmp
*.temp
.DS_Store
node_modules/
package-lock.json
*.log
prompts/
images/
analysis/
references/
提示词模板
模板结构
提示词由两部分组成:通用约束条件 + 具体内容描述
通用约束条件(可复用)
## 通用约束
- 分辨率: 4K, high resolution
- 质量: high quality, detailed, professional
- 构图: well-composed, balanced
- 光照: natural lighting, soft shadows具体内容描述
## 提示词
[主体描述] + [动作/状态] + [场景环境] + [色彩氛围] + [细节补充]
## 风格(可选)
参考风格库中的 [风格名称] 风格---
完整示例
## 通用约束
- 分辨率: 4K, high resolution
- 质量: high quality, detailed
## 提示词
一只可爱的柴犬在樱花树下睡觉,毛发蓬松,柔和的粉色调,春天的氛围
## 风格
参考风格库中的水彩风格---
编写技巧
- 分离通用和具体:通用约束可复用,具体内容按需修改
- 详细描述:场景、主体、颜色、光照等
- 避免歧义:使用具体的名词和形容词
- 引用风格库:使用预设风格保证一致性
常见错误
1. 太过模糊:\"一幅漂亮的画\" 2. 包含矛盾元素:\"一只透明的柴犬\" 3. 缺少风格约束:没有指定风格导致结果不可控
---
风格库
查看 config.example/style-library.md 了解所有预设风格:
- 水彩风格 (watercolor) - 柔和温馨
- 扁平化设计 (flat-design) - 现代简洁
- 3D 渲染 (3d-render) - 立体真实
- 油画风格 (oil-painting) - 艺术经典
- 赛博朋克 (cyberpunk) - 科幻未来
- 像素艺术 (pixel-art) - 复古怀旧
- 手绘插画 (hand-drawn) - 温暖个性
- 照片写实 (photorealistic) - 高度真实
- 抽象艺术 (abstract) - 情感表达
配置和模板文件
这些文件是 pw-image-generation skill 的配置和模板文件。配置文件是可选的,只有需要自定义配置时才需要。
目录结构
config.example/
├── README.md # 本说明文件
├── secrets.md # API 配置模板
├── .gitignore # 项目 gitignore 模板
├── style-library.md # 风格库
└── prompt-templates/
└── 提示词模板.md # 提示词模板使用方法
复制到项目中(需要自定义配置时)
# 在项目根目录执行
cp -r ~/.claude/skills/pw-image-generation/config.example ./config
cp ~/.claude/skills/pw-image-generation/config.example/.gitignore ./.gitignore配置 API 密钥
编辑 config/secrets.md 文件,将 your-api-key-here 替换为实际的 API Key。
文件说明
- secrets.md: API 和模型配置(可选)
- .gitignore: 项目 Git 忽略文件模板
- style-library.md: 9 种预设图像风格
- prompt-templates/: 提示词模板和示例
快速开始(使用默认配置)
如果不需要自定义配置,可以直接使用默认配置:
# 创建项目目录
mkdir my-image-project && cd my-image-project
# 生成图像(逐张确认)
node ~/.claude/skills/pw-image-generation/scripts/generate-image.js脚本会使用内置的默认配置。
<!-- input: 无 output: API 配置供脚本读取 pos: 配置文件模板
使用方法: 1. 复制 config.example 目录到项目的 config 目录 2. 修改 config/secrets.md 中的配置项 -->
API 配置
API 基础配置
API 基础 URL(包含域名和路径前缀)
API_BASE_URL=https://ai-router.plugins-world.cn
模型配置
图像分析模型(用于分析图像风格)
ANALYSIS_MODEL_ID=gemini-2.0-flash-exp
图像生成模型(用于生成图像)
GENERATION_MODEL_ID=gemini-3-pro-image-preview
API 端点配置
分析端点路径(会拼接在 API_BASE_URL 后面)
ANALYSIS_ENDPOINT=/v1beta/models/{model}:generateContent
生成端点路径(会拼接在 API_BASE_URL 后面)
GENERATION_ENDPOINT=/v1beta/models/{model}:generateContent
认证配置
AI Router API 密钥(支持多种模型,不局限于特定平台)
API_KEY=sk-1p6RyJLH3e3TnfMEfN9clcwZru2V513pAIwK8h5fMVUTDnEv
图床配置(可选)
图床上传端点(如果不配置,则使用 BASE64 内嵌)
IMAGE_UPLOAD_ENDPOINT=
图像风格库
使用说明
这个文件包含预设的图像风格选项,Agent 可以根据用户需求自动推荐或让用户手动选择。
风格列表
1. 水彩风格 (watercolor)
适用场景: 柔和、温馨的主题,儿童插画,情感表达
风格特征:
- 柔和的色彩过渡
- 半透明的颜色叠加
- 纸张纹理感
- 边缘模糊自然
提示词关键词: watercolor painting, soft colors, gentle transitions, paper texture
---
2. 扁平化设计 (flat-design)
适用场景: 现代科技、UI 设计、信息图表、商业演示
风格特征:
- 简洁的几何形状
- 纯色填充,无渐变
- 清晰的轮廓线
- 极简主义
提示词关键词: flat design, minimalist, geometric shapes, solid colors, clean lines
---
3. 3D 渲染 (3d-render)
适用场景: 产品展示、建筑可视化、游戏资产
风格特征:
- 立体感强
- 光影效果明显
- 材质质感真实
- 高清细节
提示词关键词: 3D render, volumetric lighting, realistic materials, high detail, octane render
---
4. 油画风格 (oil-painting)
适用场景: 艺术作品、肖像画、风景画
风格特征:
- 厚重的笔触
- 丰富的色彩层次
- 强烈的质感
- 经典艺术感
提示词关键词: oil painting, thick brushstrokes, rich colors, classical art style
---
5. 赛博朋克 (cyberpunk)
适用场景: 科幻主题、未来城市、游戏场景
风格特征:
- 霓虹灯光效果
- 暗色调背景
- 高科技元素
- 城市夜景
提示词关键词: cyberpunk, neon lights, dark atmosphere, futuristic city, high-tech
---
6. 像素艺术 (pixel-art)
适用场景: 复古游戏、怀旧主题、简约图标
风格特征:
- 像素化的图形
- 有限的色板
- 清晰的边缘
- 8-bit/16-bit 风格
提示词关键词: pixel art, 8-bit style, retro gaming, limited color palette
---
7. 手绘插画 (hand-drawn)
适用场景: 儿童读物、漫画、创意表达
风格特征:
- 手绘线条感
- 不完美的边缘
- 温暖的色调
- 个性化表达
提示词关键词: hand-drawn illustration, sketch style, warm colors, personal touch
---
8. 照片写实 (photorealistic)
适用场景: 产品摄影、人物肖像、真实场景
风格特征:
- 极高的真实度
- 精确的光影
- 细腻的纹理
- 摄影级质量
提示词关键词: photorealistic, high resolution, professional photography, detailed textures
---
9. 抽象艺术 (abstract)
适用场景: 艺术表达、情绪传达、装饰画
风格特征:
- 非具象形式
- 色彩和形状的组合
- 情感表达
- 自由创作
提示词关键词: abstract art, non-representational, color composition, emotional expression
---
自动选择规则
Agent 可以根据以下信号自动推荐风格:
| 内容信号 | 推荐风格 | 理由 |
|---|---|---|
| 儿童、童话、温馨 | 水彩风格 | 柔和亲切 |
| 科技、商业、数据 | 扁平化设计 | 现代专业 |
| 产品、建筑、展示 | 3D 渲染 | 立体真实 |
| 艺术、经典、肖像 | 油画风格 | 艺术感强 |
| 科幻、未来、夜景 | 赛博朋克 | 氛围独特 |
| 复古、游戏、简约 | 像素艺术 | 怀旧趣味 |
| 创意、个性、漫画 | 手绘插画 | 亲和力强 |
| 真实、摄影、产品 | 照片写实 | 高度真实 |
| 情绪、装饰、艺术 | 抽象艺术 | 表达自由 |
使用方法
在 generate-prompt.js 中使用
// 读取风格库
const styleLibrary = fs.readFileSync(
path.join(__dirname, '../config.example/style-library.md'),
'utf-8'
);
// 根据用户描述推荐风格
function recommendStyle(description) {
const keywords = {
'水彩风格': ['儿童', '童话', '温馨', '柔和'],
'扁平化设计': ['科技', '商业', '数据', '现代'],
'3D 渲染': ['产品', '建筑', '展示', '立体'],
// ... 其他风格
};
for (const [style, words] of Object.entries(keywords)) {
if (words.some(word => description.includes(word))) {
return style;
}
}
return null; // 无法自动推荐,让用户手动选择
}在提示词中引用
## 提示词
[具体内容描述]
## 风格
参考风格库中的 [风格名称] 风格# 项目 .gitignore 文件
# 依赖
node_modules/
package-lock.json
yarn.lock
# 配置文件
config/secrets.md
config/local.config.js
config/.env*
# 输出文件
output/
*.log
*.tmp
# 编辑器
.vscode/
.idea/
*.swp
*.swo
*~
# 系统
.DS_Store
Thumbs.db
# 临时文件
*.tmp
*.temp
提示词模板
模板结构
提示词由两部分组成:通用约束条件 + 具体内容描述
通用约束条件(可复用)
## 通用约束
- 分辨率: 4K, high resolution
- 质量: high quality, detailed, professional
- 构图: well-composed, balanced
- 光照: natural lighting, soft shadows具体内容描述
## 提示词
[主体描述] + [动作/状态] + [场景环境] + [色彩氛围] + [细节补充]
## 风格(可选)
参考风格库中的 [风格名称] 风格---
完整示例
## 通用约束
- 分辨率: 4K, high resolution
- 质量: high quality, detailed
## 提示词
一只可爱的柴犬在樱花树下睡觉,毛发蓬松,柔和的粉色调,春天的氛围
## 风格
参考风格库中的水彩风格---
编写技巧
- 分离通用和具体:通用约束可复用,具体内容按需修改
- 详细描述:场景、主体、颜色、光照等
- 避免歧义:使用具体的名词和形容词
- 引用风格库:使用预设风格保证一致性
常见错误
1. 太过模糊:\"一幅漂亮的画\" 2. 包含矛盾元素:\"一只透明的柴犬\" 3. 缺少风格约束:没有指定风格导致结果不可控
---
风格库
查看 config.example/style-library.md 了解所有预设风格:
- 水彩风格 (watercolor) - 柔和温馨
- 扁平化设计 (flat-design) - 现代简洁
- 3D 渲染 (3d-render) - 立体真实
- 油画风格 (oil-painting) - 艺术经典
- 赛博朋克 (cyberpunk) - 科幻未来
- 像素艺术 (pixel-art) - 复古怀旧
- 手绘插画 (hand-drawn) - 温暖个性
- 照片写实 (photorealistic) - 高度真实
- 抽象艺术 (abstract) - 情感表达
图像风格库
使用说明
这个文件包含预设的图像风格选项,Agent 可以根据用户需求自动推荐或让用户手动选择。
风格列表
1. 水彩风格 (watercolor)
适用场景: 柔和、温馨的主题,儿童插画,情感表达
风格特征:
- 柔和的色彩过渡
- 半透明的颜色叠加
- 纸张纹理感
- 边缘模糊自然
提示词关键词: watercolor painting, soft colors, gentle transitions, paper texture
---
2. 扁平化设计 (flat-design)
适用场景: 现代科技、UI 设计、信息图表、商业演示
风格特征:
- 简洁的几何形状
- 纯色填充,无渐变
- 清晰的轮廓线
- 极简主义
提示词关键词: flat design, minimalist, geometric shapes, solid colors, clean lines
---
3. 3D 渲染 (3d-render)
适用场景: 产品展示、建筑可视化、游戏资产
风格特征:
- 立体感强
- 光影效果明显
- 材质质感真实
- 高清细节
提示词关键词: 3D render, volumetric lighting, realistic materials, high detail, octane render
---
4. 油画风格 (oil-painting)
适用场景: 艺术作品、肖像画、风景画
风格特征:
- 厚重的笔触
- 丰富的色彩层次
- 强烈的质感
- 经典艺术感
提示词关键词: oil painting, thick brushstrokes, rich colors, classical art style
---
5. 赛博朋克 (cyberpunk)
适用场景: 科幻主题、未来城市、游戏场景
风格特征:
- 霓虹灯光效果
- 暗色调背景
- 高科技元素
- 城市夜景
提示词关键词: cyberpunk, neon lights, dark atmosphere, futuristic city, high-tech
---
6. 像素艺术 (pixel-art)
适用场景: 复古游戏、怀旧主题、简约图标
风格特征:
- 像素化的图形
- 有限的色板
- 清晰的边缘
- 8-bit/16-bit 风格
提示词关键词: pixel art, 8-bit style, retro gaming, limited color palette
---
7. 手绘插画 (hand-drawn)
适用场景: 儿童读物、漫画、创意表达
风格特征:
- 手绘线条感
- 不完美的边缘
- 温暖的色调
- 个性化表达
提示词关键词: hand-drawn illustration, sketch style, warm colors, personal touch
---
8. 照片写实 (photorealistic)
适用场景: 产品摄影、人物肖像、真实场景
风格特征:
- 极高的真实度
- 精确的光影
- 细腻的纹理
- 摄影级质量
提示词关键词: photorealistic, high resolution, professional photography, detailed textures
---
9. 抽象艺术 (abstract)
适用场景: 艺术表达、情绪传达、装饰画
风格特征:
- 非具象形式
- 色彩和形状的组合
- 情感表达
- 自由创作
提示词关键词: abstract art, non-representational, color composition, emotional expression
---
自动选择规则
Agent 可以根据以下信号自动推荐风格:
| 内容信号 | 推荐风格 | 理由 |
|---|---|---|
| 儿童、童话、温馨 | 水彩风格 | 柔和亲切 |
| 科技、商业、数据 | 扁平化设计 | 现代专业 |
| 产品、建筑、展示 | 3D 渲染 | 立体真实 |
| 艺术、经典、肖像 | 油画风格 | 艺术感强 |
| 科幻、未来、夜景 | 赛博朋克 | 氛围独特 |
| 复古、游戏、简约 | 像素艺术 | 怀旧趣味 |
| 创意、个性、漫画 | 手绘插画 | 亲和力强 |
| 真实、摄影、产品 | 照片写实 | 高度真实 |
| 情绪、装饰、艺术 | 抽象艺术 | 表达自由 |
使用方法
在 generate-prompt.js 中使用
// 读取风格库
const styleLibrary = fs.readFileSync(
path.join(__dirname, '../config.example/style-library.md'),
'utf-8'
);
// 根据用户描述推荐风格
function recommendStyle(description) {
const keywords = {
'水彩风格': ['儿童', '童话', '温馨', '柔和'],
'扁平化设计': ['科技', '商业', '数据', '现代'],
'3D 渲染': ['产品', '建筑', '展示', '立体'],
// ... 其他风格
};
for (const [style, words] of Object.entries(keywords)) {
if (words.some(word => description.includes(word))) {
return style;
}
}
return null; // 无法自动推荐,让用户手动选择
}在提示词中引用
## 提示词
[具体内容描述]
## 风格
参考风格库中的 [风格名称] 风格图片上传(图生图必需)
本地图片需先上传到图床获取 URL,才能用于图生图。
推荐图床
sm.ms(推荐,永久存储,支持删除)
curl -s -X POST -F "smfile=@本地图片路径" https://sm.ms/api/v2/upload返回 JSON,提取 data.url 字段获取直链,如 https://s2.loli.net/xxxxx.png
优点:
- 永久存储
- 支持删除(通过 delete hash)
- 稳定性高
- 无需注册即可使用
freeimage.host(备选,永久存储)
curl -s -X POST -F "source=@本地图片路径" "https://freeimage.host/api/1/upload?key=6d207e02198a847aa98d0a2a901485a5"返回 JSON,提取 image.url 字段获取直链,如 https://iili.io/xxxxx.png
Litterbox(临时,1小时有效)
curl -s -F "reqtype=fileupload" -F "time=1h" -F "fileToUpload=@本地图片路径" https://litterbox.catbox.moe/resources/internals/api.php注意:部分网络环境可能 SSL 连接失败
Catbox(永久)
curl -s -F "reqtype=fileupload" -F "fileToUpload=@本地图片路径" https://catbox.moe/user/api.php注意:部分网络环境可能 SSL 连接失败
图床对比
| 图床 | 有效期 | 稳定性 | 删除支持 | 备注 |
|---|---|---|---|---|
| sm.ms | 永久 | 高 | ✅ 支持 | 推荐首选 |
| freeimage.host | 永久 | 高 | ✅ 支持 | 备选 |
| litterbox | 1小时 | 中 | ❌ 自动删除 | 部分网络不可用 |
| catbox | 永久 | 中 | ⚠️ 需注册 | 部分网络不可用 |
| imgbb | 永久 | 高 | ⚠️ 需注册 | 需要 API Key |
使用方式
上传图片
node ~/.claude/skills/pw-image-generation/scripts/upload-image.js <图片路径>
# 示例
node ~/.claude/skills/pw-image-generation/scripts/upload-image.js ./template/图.001.png功能:
- 优先上传到 sm.ms
- 如果失败,自动尝试 freeimage.host
- 返回图片 URL
- 自动保存删除链接到
.upload-history.json
管理上传的图片
列出所有上传的图片:
node ~/.claude/skills/pw-image-generation/scripts/delete-image.js list删除指定图片:
# 先列出图片,查看索引
node ~/.claude/skills/pw-image-generation/scripts/delete-image.js list
# 删除索引为 0 的图片
node ~/.claude/skills/pw-image-generation/scripts/delete-image.js delete 0批量删除所有图片:
node ~/.claude/skills/pw-image-generation/scripts/delete-image.js delete-all在提示词中使用
上传后,将 URL 放在 prompt 开头:
https://iili.io/xxxxx.png 参考这张图片,生成类似风格的新图片---
删除图床文件
freeimage.host
方法 1: 通过网页删除 1. 访问图片 URL(如 https://iili.io/xxxxx.png) 2. 点击页面上的 "Delete" 按钮 3. 确认删除
方法 2: 通过删除链接
- 上传时返回的 JSON 中包含
delete_url字段 - 访问该链接即可删除图片
- 建议保存删除链接以便后续管理
注意:
- 无需注册即可上传,但删除需要删除链接
- 建议保存上传返回的完整 JSON 响应
Litterbox
- 1小时后自动删除,无需手动操作
- 无法提前删除
Catbox
通过 API 删除:
curl -X POST -F "userhash=你的userhash" -F "files=文件名" https://catbox.moe/user/api.php注意:
- 需要注册账号获取 userhash
- 匿名上传的文件无法删除
---
最佳实践
1. 临时测试: 使用 Litterbox(1小时自动删除) 2. 正式使用: 使用 freeimage.host,保存删除链接 3. 长期管理: 使用 Catbox 或 imgbb,注册账号便于管理 4. 隐私保护: 不要上传包含敏感信息的图片到公共图床
#!/usr/bin/env bun
/**
* 分析图像的风格和特征
*
* 使用方法:
* npx -y bun ${SKILL_DIR}/scripts/analyze-image.ts <图像URL或路径> [输出目录]
*
* 示例:
* npx -y bun ${SKILL_DIR}/scripts/analyze-image.ts https://example.com/image.png
* npx -y bun ${SKILL_DIR}/scripts/analyze-image.ts ./local-image.png ./output
*/
import fs from 'fs';
import path from 'path';
import https from 'https';
import http from 'http';
// 配置类型定义
interface Config {
API_BASE_URL: string;
ANALYSIS_MODEL_ID: string;
GENERATION_MODEL_ID: string;
ANALYSIS_ENDPOINT: string;
GENERATION_ENDPOINT: string;
API_KEY: string;
IMAGE_UPLOAD_ENDPOINT: string;
}
// 读取配置(支持默认配置)
function loadConfig(): Config {
const configPath = path.join(process.cwd(), 'config', 'secrets.md');
const defaultConfig: Config = {
API_BASE_URL: 'https://ai-router.plugins-world.cn',
ANALYSIS_MODEL_ID: 'gemini-2.0-flash-exp',
GENERATION_MODEL_ID: 'gemini-3-pro-image-preview',
ANALYSIS_ENDPOINT: '/v1beta/models/{model}:generateContent',
GENERATION_ENDPOINT: '/v1beta/models/{model}:generateContent',
API_KEY: '',
IMAGE_UPLOAD_ENDPOINT: ''
};
// 如果项目下没有配置文件,返回默认配置
if (!fs.existsSync(configPath)) {
console.warn('警告: 未找到配置文件 config/secrets.md,使用默认配置');
console.warn('提示: 如果需要自定义配置,请创建 config/secrets.md 文件');
console.warn('');
return defaultConfig;
}
// 读取项目配置
try {
const configContent = fs.readFileSync(configPath, 'utf8');
const config = Object.assign({}, defaultConfig);
configContent.split('\n').forEach(line => {
const match = line.match(/^([A-Z_]+)=(.+)$/);
if (match) {
(config as any)[match[1]] = match[2];
}
});
return config;
} catch (error: any) {
console.warn('警告: 读取配置文件失败,使用默认配置:', error.message);
return defaultConfig;
}
}
// 构建完整的 API URL
function buildApiUrl(config: Config, endpointTemplate: string, modelId: string): string {
const endpoint = endpointTemplate.replace('{model}', modelId);
return `${config.API_BASE_URL}${endpoint}`;
}
// 下载图像
function downloadImage(url: string, outputPath: string): Promise<string> {
return new Promise((resolve, reject) => {
const protocol = url.startsWith('https') ? https : http;
protocol.get(url, (response) => {
if (response.statusCode !== 200) {
reject(new Error(`下载失败: ${response.statusCode}`));
return;
}
const chunks: Buffer[] = [];
response.on('data', (chunk) => chunks.push(chunk));
response.on('end', () => {
const buffer = Buffer.concat(chunks);
fs.writeFileSync(outputPath, buffer);
resolve(outputPath);
});
}).on('error', reject);
});
}
// 调用 API 分析图像
async function analyzeImage(imagePath: string, config: Config): Promise<string> {
// 读取图像并转换为 base64
const imageBuffer = fs.readFileSync(imagePath);
const base64Image = imageBuffer.toString('base64');
const apiUrl = buildApiUrl(config, config.ANALYSIS_ENDPOINT, config.ANALYSIS_MODEL_ID);
const requestBody = {
contents: [
{
role: "user",
parts: [
{
inlineData: {
mime_type: "image/png",
data: base64Image
}
},
{
text: `请详细分析这张图片的艺术风格和特征,包括:
1. 艺术风格(如水彩、油画、动漫等)
2. 色彩方案(主色调、色彩搭配)
3. 构图方式(如三分法、对称构图等)
4. 光照和氛围
5. 笔触和纹理特征
6. 整体感觉和情绪
请用简洁的中文描述,每个要点不超过两句话。`
}
]
}
]
};
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Authorization': `Bearer ${config.API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(requestBody)
});
const data = await response.json();
if (data.candidates && data.candidates.length > 0) {
const parts = data.candidates[0].content?.parts || [];
for (const part of parts) {
if (part.text) {
return part.text;
}
}
}
throw new Error('无法获取分析结果');
}
// 主函数
async function main() {
const imageUrl = process.argv[2];
const outputDir = process.argv[3] || path.join(process.cwd(), 'output');
if (!imageUrl) {
console.error('使用方法: npx -y bun ${SKILL_DIR}/scripts/analyze-image.ts <图像URL或路径> [输出目录]');
console.error('');
console.error('示例:');
console.error(' npx -y bun ${SKILL_DIR}/scripts/analyze-image.ts https://example.com/image.png');
console.error(' npx -y bun ${SKILL_DIR}/scripts/analyze-image.ts ./local-image.png ./output');
process.exit(1);
}
try {
// 加载配置
console.log('加载配置...');
const config = loadConfig();
// 创建输出目录
fs.mkdirSync(outputDir, { recursive: true });
// 下载或读取图像
console.log('读取图像...');
let localImagePath: string;
let isLocal = !imageUrl.startsWith('http');
if (!isLocal) {
const filename = path.basename(imageUrl).split('?')[0] || 'image.png';
localImagePath = path.join(outputDir, 'config.example', '_temp_download.png');
fs.mkdirSync(path.dirname(localImagePath), { recursive: true });
console.log('下载图像到:', localImagePath);
await downloadImage(imageUrl, localImagePath);
} else {
localImagePath = imageUrl;
// 复制到 config.example 目录
const refPath = path.join(outputDir, 'config.example', path.basename(imageUrl));
fs.mkdirSync(path.dirname(refPath), { recursive: true });
fs.copyFileSync(imageUrl, refPath);
localImagePath = refPath;
}
// 分析图像
console.log('分析图像风格...');
const analysis = await analyzeImage(localImagePath, config);
// 保存分析结果
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
const outputFile = path.join(outputDir, 'analysis', `01_图像风格分析_${timestamp}.md`);
fs.mkdirSync(path.dirname(outputFile), { recursive: true });
// 计算相对路径
const relativeImagePath = path.relative(path.dirname(outputFile), localImagePath);
fs.writeFileSync(outputFile, `# 图像风格分析
生成时间: ${new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })}
原始图像: ${isLocal ? path.basename(imageUrl) : imageUrl}
## 风格分析
${analysis}
## 参考图像

`);
console.log('分析完成!');
console.log('结果保存到:', outputFile);
console.log('参考图像保存在:', path.join(outputDir, 'config.example', path.basename(imageUrl)));
} catch (error: any) {
console.error('错误:', error.message);
process.exit(1);
}
}
main();
#!/usr/bin/env bun
/**
* delete-image.ts - 删除图床上的图片
*
* 用法:
* npx -y bun ${SKILL_DIR}/scripts/delete-image.ts list # 列出所有上传的图片
* npx -y bun ${SKILL_DIR}/scripts/delete-image.ts delete <index> # 删除指定索引的图片
* npx -y bun ${SKILL_DIR}/scripts/delete-image.ts delete-all # 删除所有图片
*/
import fs from 'fs';
import path from 'path';
// 历史记录类型
interface HistoryEntry {
timestamp: string;
file: string;
url: string;
deleteUrl: string | null;
deleteHash: string | null;
provider: string;
}
const historyFile = path.join(process.cwd(), '.upload-history.json');
// 读取上传历史
function loadHistory(): HistoryEntry[] {
if (fs.existsSync(historyFile)) {
try {
return JSON.parse(fs.readFileSync(historyFile, 'utf-8'));
} catch (err: any) {
console.error('读取历史记录失败:', err.message);
return [];
}
}
return [];
}
// 保存上传历史
function saveHistory(history: HistoryEntry[]): void {
fs.writeFileSync(historyFile, JSON.stringify(history, null, 2), 'utf-8');
}
// 列出所有图片
function listImages(): void {
const history = loadHistory();
if (history.length === 0) {
console.log('没有上传历史记录');
console.log(`\n历史记录文件: ${historyFile}`);
return;
}
console.log(`\n上传历史 (共 ${history.length} 张):\n`);
history.forEach((item, index) => {
const date = new Date(item.timestamp).toLocaleString('zh-CN');
console.log(`[${index}] ${item.file}`);
console.log(` 上传时间: ${date}`);
console.log(` 图床: ${item.provider || '未知'}`);
console.log(` 图片 URL: ${item.url}`);
console.log(` 删除链接: ${item.deleteUrl || '无'}`);
console.log('');
});
console.log(`历史记录文件: ${historyFile}`);
console.log(`\n💡 提示: 访问删除链接在浏览器中手动删除图片`);
}
// 删除指定图片
function deleteByIndex(index: number): void {
const history = loadHistory();
if (index < 0 || index >= history.length) {
console.error(`错误: 索引 ${index} 超出范围 (0-${history.length - 1})`);
process.exit(1);
}
const item = history[index];
if (!item.deleteUrl) {
console.error('错误: 该图片没有删除链接');
process.exit(1);
}
console.log(`准备从历史记录中移除: ${item.file}`);
console.log(`图床: ${item.provider || '未知'}`);
console.log(`图片 URL: ${item.url}`);
console.log(`删除链接: ${item.deleteUrl}`);
console.log(`\n⚠️ 请手动访问删除链接删除图片`);
// 从历史记录中移除
history.splice(index, 1);
saveHistory(history);
console.log('✅ 已从历史记录中移除');
}
// 删除所有图片
function deleteAll(): void {
const history = loadHistory();
if (history.length === 0) {
console.log('没有需要删除的图片');
return;
}
console.log(`准备从历史记录中移除 ${history.length} 张图片...\n`);
history.forEach((item, index) => {
console.log(`[${index}] ${item.file}`);
console.log(` 图床: ${item.provider || '未知'}`);
console.log(` 删除链接: ${item.deleteUrl || '无'}`);
});
console.log(`\n⚠️ 请手动访问删除链接删除图片`);
console.log(`\n清空历史记录...`);
saveHistory([]);
console.log(`✅ 已清空历史记录 (${history.length} 条)`);
}
// 主函数
const args = process.argv.slice(2);
if (args.length === 0) {
console.log('用法:');
console.log(' npx -y bun ${SKILL_DIR}/scripts/delete-image.ts list # 列出所有上传的图片');
console.log(' npx -y bun ${SKILL_DIR}/scripts/delete-image.ts delete <index> # 删除指定索引的图片');
console.log(' npx -y bun ${SKILL_DIR}/scripts/delete-image.ts delete-all # 删除所有图片');
process.exit(1);
}
const command = args[0];
switch (command) {
case 'list':
listImages();
break;
case 'delete':
if (args.length < 2) {
console.error('错误: 请指定要删除的图片索引');
console.log('用法: npx -y bun ${SKILL_DIR}/scripts/delete-image.ts delete <index>');
process.exit(1);
}
const index = parseInt(args[1]);
if (isNaN(index)) {
console.error('错误: 索引必须是数字');
process.exit(1);
}
deleteByIndex(index);
break;
case 'delete-all':
deleteAll();
break;
default:
console.error(`错误: 未知命令 "${command}"`);
console.log('支持的命令: list, delete, delete-all');
process.exit(1);
}
#!/usr/bin/env bun
/**
* 生成图像脚本
*
* 使用方法:
* npx -y bun ${SKILL_DIR}/scripts/generate-image.ts [输出目录]
*
* 输出目录结构:
* images/ # 生成的图像 (默认)
* prompts/ # 提示词文件
* analysis/ # 图像风格分析
*/
import fs from 'fs';
import path from 'path';
import readline from 'readline';
// 配置类型定义
interface Config {
API_BASE_URL: string;
ANALYSIS_MODEL_ID: string;
GENERATION_MODEL_ID: string;
ANALYSIS_ENDPOINT: string;
GENERATION_ENDPOINT: string;
API_KEY: string;
IMAGE_UPLOAD_ENDPOINT: string;
}
// 提示词文件类型
interface PromptFile {
file: string;
prompt: string;
content: string;
}
// 创建 readline 接口用于用户交互
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
// 读取配置(支持默认配置)
function loadConfig(): Config {
const projectConfigPath = path.join(process.cwd(), 'config', 'secrets.md');
const globalConfigPath = path.join(process.env.HOME || '', '.claude', 'skills', 'pw-image-generation', 'config', 'secrets.md');
const defaultConfig: Config = {
API_BASE_URL: 'https://ai-router.plugins-world.cn',
ANALYSIS_MODEL_ID: 'gemini-2.0-flash-exp',
GENERATION_MODEL_ID: 'gemini-3-pro-image-preview',
ANALYSIS_ENDPOINT: '/v1beta/models/{model}:generateContent',
GENERATION_ENDPOINT: '/v1beta/models/{model}:generateContent',
API_KEY: '',
IMAGE_UPLOAD_ENDPOINT: ''
};
// 优先使用项目配置,其次全局配置,最后默认配置
let configPath: string | null = null;
if (fs.existsSync(projectConfigPath)) {
configPath = projectConfigPath;
console.log('使用项目配置:', projectConfigPath);
} else if (fs.existsSync(globalConfigPath)) {
configPath = globalConfigPath;
console.log('使用全局配置:', globalConfigPath);
} else {
console.warn('警告: 未找到配置文件,使用默认配置');
console.warn('提示: 可以创建以下任一配置文件:');
console.warn(' 项目配置: config/secrets.md');
console.warn(' 全局配置:', globalConfigPath);
console.warn('');
return defaultConfig;
}
// 读取配置
try {
const configContent = fs.readFileSync(configPath, 'utf8');
const config = Object.assign({}, defaultConfig);
configContent.split('\n').forEach(line => {
const match = line.match(/^([A-Z_]+)=(.+)$/);
if (match) {
(config as any)[match[1]] = match[2];
}
});
return config;
} catch (error: any) {
console.warn('警告: 读取配置文件失败,使用默认配置:', error.message);
return defaultConfig;
}
}
// 构建完整的 API URL
function buildApiUrl(config: Config, endpointTemplate: string, modelId: string): string {
const endpoint = endpointTemplate.replace('{model}', modelId);
return `${config.API_BASE_URL}${endpoint}`;
}
// 用户确认
function askUser(question: string): Promise<string> {
return new Promise((resolve) => {
rl.question(question, (answer) => {
resolve(answer.trim().toLowerCase());
});
});
}
// 调用 API 生成图像
async function generateImage(prompt: string, referenceImagePath: string | null, config: Config): Promise<Buffer> {
const requestBody: any = {
contents: [
{
role: "user",
parts: []
}
],
generationConfig: {
responseModalities: ["TEXT", "IMAGE"]
}
};
// 添加参考图片
if (referenceImagePath && fs.existsSync(referenceImagePath)) {
const imageBuffer = fs.readFileSync(referenceImagePath);
const base64Image = imageBuffer.toString('base64');
requestBody.contents[0].parts.push({
inlineData: {
mime_type: "image/png",
data: base64Image
}
});
}
// 添加提示词
requestBody.contents[0].parts.push({
text: prompt
});
console.log(' 调用 API...');
const apiUrl = buildApiUrl(config, config.GENERATION_ENDPOINT, config.GENERATION_MODEL_ID);
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Authorization': `Bearer ${config.API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(requestBody)
});
const data = await response.json();
// 调试:打印响应数据
console.log(' API 响应状态:', response.status);
if (!response.ok) {
console.log(' 错误响应:', JSON.stringify(data, null, 2).substring(0, 500));
throw new Error(`API 请求失败: ${response.status}`);
}
// 提取图像数据
if (data.candidates && data.candidates.length > 0) {
const parts = data.candidates[0].content?.parts || [];
for (const part of parts) {
if (part.inlineData) {
return Buffer.from(part.inlineData.data, 'base64');
}
}
}
// 调试:打印响应结构
console.log(' 响应结构:', JSON.stringify(data, null, 2).substring(0, 500));
throw new Error('无法获取图像数据');
}
// 读取风格分析文件
function readStyleAnalysis(outputDir: string): string | null {
const analysisDir = path.join(outputDir, 'analysis');
if (!fs.existsSync(analysisDir)) {
return null;
}
const files = fs.readdirSync(analysisDir);
const styleFile = files.find(f => f.startsWith('01_图像风格分析'));
if (!styleFile) {
return null;
}
const content = fs.readFileSync(path.join(analysisDir, styleFile), 'utf8');
return content;
}
// 读取提示词文件
function readPromptFiles(outputDir: string): PromptFile[] {
// 优先从当前目录的 prompts/ 查找
let promptsDir = path.join(process.cwd(), 'prompts');
// 如果不存在,尝试从 outputDir 的父目录查找
if (!fs.existsSync(promptsDir) && outputDir) {
const parentDir = path.dirname(outputDir);
promptsDir = path.join(parentDir, 'prompts');
}
if (!fs.existsSync(promptsDir)) {
return [];
}
const files = fs.readdirSync(promptsDir);
const promptFiles = files.filter(f => f.endsWith('.md'));
return promptFiles.map(file => {
const content = fs.readFileSync(path.join(promptsDir, file), 'utf8');
// 尝试匹配多种格式:
// 1. ## 提示词 或 ## 完整提示词 后跟代码块
const codeBlockMatch = content.match(/##\s*(?:完整)?提示词\s*\n\s*```[^\n]*\n([\s\S]+?)```/);
if (codeBlockMatch) {
return {
file,
prompt: codeBlockMatch[1].trim(),
content
};
}
// 2. ## 提示词 后直接跟文本
const directMatch = content.match(/##\s*提示词\s*\n\s*(.+?)(?:\n\n|$)/s);
if (directMatch) {
return {
file,
prompt: directMatch[1].trim(),
content
};
}
// 3. 兜底: 使用整个文件内容
return {
file,
prompt: content,
content
};
});
}
// 主函数
async function main() {
const outputDir = process.argv[2] || path.join(process.cwd(), 'images');
if (!fs.existsSync(outputDir)) {
console.warn('警告: 输出目录不存在,将创建:', outputDir);
fs.mkdirSync(outputDir, { recursive: true });
}
try {
// 加载配置
console.log('加载配置...');
const config = loadConfig();
// 图片直接保存在输出目录
const imagesDir = outputDir;
// 读取风格分析
console.log('\n读取风格分析...');
const styleAnalysis = readStyleAnalysis(outputDir);
if (styleAnalysis) {
console.log(' 找到风格分析文件');
} else {
console.log(' 未找到风格分析文件');
}
// 读取提示词文件
console.log('\n读取提示词文件...');
const prompts = readPromptFiles(outputDir);
if (prompts.length === 0) {
console.error('错误: 未找到提示词文件');
console.error('');
console.error('请先创建提示词文件,参考:');
console.error(' ~/.claude/skills/pw-image-generation/references/prompt-templates/提示词模板.md');
console.error('');
console.error('提示词文件格式:');
console.error(' ## 提示词');
console.error(' ');
console.error(' 一只可爱的柴犬在樱花树下睡觉,水彩风格');
process.exit(1);
}
console.log(` 找到 ${prompts.length} 个提示词文件\n`);
// 询问生成模式
console.log('请选择生成模式:');
console.log(' 1. 逐个确认 (每张图像生成前都会询问)');
console.log(' 2. 批量生成 (跳过确认,直接生成所有图像)');
const modeChoice = await askUser('请输入选项 (1/2, 默认: 1): ');
const batchMode = modeChoice === '2';
if (batchMode) {
console.log('\n【批量模式】将自动生成所有图像,跳过已存在的图像\n');
} else {
console.log('\n【逐个确认模式】每张图像生成前都会询问\n');
}
// 生成每张图像
for (let i = 0; i < prompts.length; i++) {
const { file, prompt, content } = prompts[i];
console.log('='.repeat(60));
console.log(`任务 ${i + 1}/${prompts.length}: ${file}`);
console.log('='.repeat(60));
console.log('\n提示词:');
console.log(prompt);
console.log('');
// 检查是否已生成
const imageFileName = file.replace('.md', '.png');
const imagePath = path.join(imagesDir, imageFileName);
if (fs.existsSync(imagePath)) {
console.log(`图像已存在: ${imageFileName}`);
if (batchMode) {
console.log('批量模式: 自动跳过\n');
continue;
}
const skip = await askUser('是否跳过已生成的图像? (y/n, 默认: y): ');
if (skip !== 'n') {
console.log('跳过\n');
continue;
}
}
// 用户确认(重点:避免额度浪费)
if (!batchMode) {
console.log('【重要提示】生成将消耗 API 额度');
const confirm = await askUser('是否生成此图像? (y/n, 默认: y): ');
if (confirm === 'n') {
console.log('跳过\n');
continue;
}
}
// 检查是否有参考图像
let referenceImagePath: string | null = null;
const refMatch = content.match(/参考图像:\s*\[!\[.*?\]\((.+?)\)\]/);
if (refMatch) {
const relativeRefPath = refMatch[1];
referenceImagePath = path.resolve(path.join(outputDir, 'analysis', relativeRefPath));
if (fs.existsSync(referenceImagePath)) {
console.log('\n使用参考图像:', path.basename(referenceImagePath));
} else {
referenceImagePath = null;
}
}
// 生成图像
console.log('\n开始生成图像...');
const startTime = Date.now();
try {
const imageData = await generateImage(prompt, referenceImagePath, config);
// 保存图像
fs.writeFileSync(imagePath, imageData);
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
console.log(`\n✓ 图像生成成功! (${elapsed}秒)`);
console.log(` 保存到: ${imagePath}\n`);
} catch (error: any) {
console.error('\n✗ 图像生成失败:', error.message);
const retry = await askUser('是否重试? (y/n, 默认: n): ');
if (retry === 'y') {
i--; // 重试当前任务
}
console.log('');
}
}
console.log('='.repeat(60));
console.log('所有任务完成!');
console.log('='.repeat(60));
console.log(`\n生成的图像保存在: ${imagesDir}\n`);
} catch (error: any) {
console.error('\n错误:', error.message);
process.exit(1);
} finally {
rl.close();
}
}
main();
#!/usr/bin/env bun
/**
* 图片合并为长图脚本
*
* 使用方法:
* npx -y bun ${SKILL_DIR}/scripts/merge-to-long-image.ts <图片目录> <输出文件>
*
* 示例:
* npx -y bun ${SKILL_DIR}/scripts/merge-to-long-image.ts ./images output.png
*/
import fs from 'fs';
import path from 'path';
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
// 支持的图片格式
const IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.webp'];
// 读取目录下的所有图片
function getImageFiles(dir: string): string[] {
const files = fs.readdirSync(dir);
return files
.filter(file => {
const ext = path.extname(file).toLowerCase();
return IMAGE_EXTENSIONS.includes(ext);
})
.sort((a, b) => {
// 按文件名中的数字排序
const numA = parseInt(a.match(/\d+/)?.[0] || '0');
const numB = parseInt(b.match(/\d+/)?.[0] || '0');
return numA - numB;
})
.map(file => path.join(dir, file));
}
// 使用 ImageMagick 合并图片
async function mergeImagesWithImageMagick(imageFiles: string[], outputFile: string): Promise<boolean> {
const command = `convert ${imageFiles.map(f => `"${f}"`).join(' ')} -append "${outputFile}"`;
try {
await execAsync(command);
return true;
} catch (error) {
return false;
}
}
// 主函数
async function main() {
const args = process.argv.slice(2);
if (args.length < 2) {
console.error('用法: npx -y bun ${SKILL_DIR}/scripts/merge-to-long-image.ts <图片目录> <输出文件>');
console.error('');
console.error('示例:');
console.error(' npx -y bun ${SKILL_DIR}/scripts/merge-to-long-image.ts ./images output.png');
process.exit(1);
}
const [imageDir, outputFile] = args;
// 检查目录是否存在
if (!fs.existsSync(imageDir)) {
console.error(`错误: 目录不存在: ${imageDir}`);
process.exit(1);
}
// 读取图片文件
const imageFiles = getImageFiles(imageDir);
if (imageFiles.length === 0) {
console.error(`错误: 目录中没有找到图片文件: ${imageDir}`);
process.exit(1);
}
console.log(`找到 ${imageFiles.length} 张图片:`);
imageFiles.forEach((file, index) => {
console.log(` ${index + 1}. ${path.basename(file)}`);
});
console.log('');
// 尝试使用 ImageMagick
console.log('正在合并图片...');
const success = await mergeImagesWithImageMagick(imageFiles, outputFile);
if (success) {
console.log(`✅ 长图生成成功: ${outputFile}`);
} else {
console.error('❌ 合并失败');
console.error('');
console.error('请确保已安装 ImageMagick:');
console.error(' brew install imagemagick');
process.exit(1);
}
}
main().catch(error => {
console.error('错误:', error.message);
process.exit(1);
});
#!/usr/bin/env bun
/**
* images2pptx.ts - 将图片文件夹打包成 PPT
*
* 用法: npx -y bun ${SKILL_DIR}/scripts/merge-to-pptx.ts <图片目录> [输出文件名]
* 示例: npx -y bun ${SKILL_DIR}/scripts/merge-to-pptx.ts ./slides output.pptx
*/
import pptxgen from 'pptxgenjs';
import fs from 'fs';
import path from 'path';
// 参数解析
const args = process.argv.slice(2);
if (args.length < 1) {
console.log('用法: npx -y bun ${SKILL_DIR}/scripts/merge-to-pptx.ts <图片目录> [输出文件名]');
console.log('示例: npx -y bun ${SKILL_DIR}/scripts/merge-to-pptx.ts ./slides output.pptx');
process.exit(1);
}
const inputDir = path.resolve(args[0]);
const outputFile = args[1] || 'output.pptx';
const outputPath = path.resolve(inputDir, outputFile);
// 检查目录
if (!fs.existsSync(inputDir)) {
console.error(`错误: 目录不存在 - ${inputDir}`);
process.exit(1);
}
// 获取图片文件
const imageExts = ['.jpg', '.jpeg', '.png', '.gif', '.webp'];
const images = fs.readdirSync(inputDir)
.filter(f => imageExts.includes(path.extname(f).toLowerCase()))
.sort((a, b) => {
// 尝试按数字排序(支持 "第1页", "slide1", "1" 等格式)
const numA = parseInt(a.match(/\d+/)?.[0] || '0');
const numB = parseInt(b.match(/\d+/)?.[0] || '0');
if (numA !== numB) return numA - numB;
return a.localeCompare(b, 'zh-CN');
});
if (images.length === 0) {
console.error('错误: 目录中没有找到图片文件');
process.exit(1);
}
console.log(`找到 ${images.length} 张图片:`);
images.forEach((img, i) => console.log(` ${i + 1}. ${img}`));
// 创建 PPT
const pptx = new pptxgen();
pptx.layout = 'LAYOUT_16x9';
pptx.title = path.basename(inputDir);
images.forEach((filename) => {
const slide = pptx.addSlide();
slide.addImage({
path: path.join(inputDir, filename),
x: 0,
y: 0,
w: '100%',
h: '100%'
});
});
// 保存
pptx.writeFile({ fileName: outputPath })
.then(() => {
console.log(`\n✅ PPT 生成成功: ${outputPath}`);
console.log(` 共 ${images.length} 页`);
})
.catch((err: any) => {
console.error('生成失败:', err);
process.exit(1);
});
#!/usr/bin/env bun
/**
* upload-image.ts - 上传图片到图床获取 URL
*
* 用法: npx -y bun ${SKILL_DIR}/scripts/upload-image.ts <图片路径>
* 示例: npx -y bun ${SKILL_DIR}/scripts/upload-image.ts ./template/图.001.png
*/
import fs from 'fs';
import path from 'path';
import { execSync } from 'child_process';
// 上传结果类型
interface UploadResult {
success: boolean;
url?: string;
deleteUrl?: string | null;
deleteHash?: string | null;
provider?: string;
error?: string;
note?: string;
}
// 历史记录类型
interface HistoryEntry {
timestamp: string;
file: string;
url: string;
deleteUrl: string | null;
deleteHash: string | null;
provider: string;
}
// 参数解析
const args = process.argv.slice(2);
if (args.length < 1) {
console.log('用法: npx -y bun ${SKILL_DIR}/scripts/upload-image.ts <图片路径>');
console.log('示例: npx -y bun ${SKILL_DIR}/scripts/upload-image.ts ./template/图.001.png');
process.exit(1);
}
const imagePath = path.resolve(args[0]);
const historyFile = path.join(process.cwd(), '.upload-history.json');
// 检查文件
if (!fs.existsSync(imagePath)) {
console.error(`错误: 文件不存在 - ${imagePath}`);
process.exit(1);
}
// 读取上传历史
function loadHistory(): HistoryEntry[] {
if (fs.existsSync(historyFile)) {
try {
return JSON.parse(fs.readFileSync(historyFile, 'utf-8'));
} catch (err) {
return [];
}
}
return [];
}
// 保存上传历史
function saveHistory(history: HistoryEntry[]): void {
fs.writeFileSync(historyFile, JSON.stringify(history, null, 2), 'utf-8');
}
// 上传到 sm.ms
function uploadToSmMs(imagePath: string): UploadResult {
console.log('尝试上传到 sm.ms...');
try {
const cmd = `curl -s -X POST -F "smfile=@${imagePath}" https://sm.ms/api/v2/upload`;
const result = execSync(cmd, { encoding: 'utf-8' });
const json = JSON.parse(result);
if (json.success && json.data && json.data.url) {
return {
success: true,
url: json.data.url,
deleteHash: json.data.hash || null,
deleteUrl: json.data.delete ? `https://sm.ms/delete/${json.data.hash}` : null,
provider: 'sm.ms'
};
} else if (json.code === 'image_repeated' && json.images) {
// 图片已存在,返回已有的 URL
return {
success: true,
url: json.images,
deleteHash: null,
deleteUrl: null,
provider: 'sm.ms',
note: '图片已存在'
};
} else {
return { success: false, error: json.message || '未知错误' };
}
} catch (err: any) {
return { success: false, error: err.message };
}
}
// 上传到 freeimage.host
function uploadToFreeimage(imagePath: string): UploadResult {
console.log('尝试上传到 freeimage.host...');
try {
const cmd = `curl -s -X POST -F "source=@${imagePath}" "https://freeimage.host/api/1/upload?key=6d207e02198a847aa98d0a2a901485a5"`;
const result = execSync(cmd, { encoding: 'utf-8' });
const json = JSON.parse(result);
if (json.status_code === 200 && json.image && json.image.url) {
return {
success: true,
url: json.image.url,
deleteUrl: json.image.url_viewer || null,
provider: 'freeimage.host'
};
} else {
return { success: false, error: json.error || '未知错误' };
}
} catch (err: any) {
return { success: false, error: err.message };
}
}
// 主逻辑:优先 sm.ms,失败则尝试 freeimage.host
console.log(`正在上传: ${imagePath}`);
let result = uploadToSmMs(imagePath);
if (!result.success) {
console.log(`sm.ms 上传失败: ${result.error}`);
result = uploadToFreeimage(imagePath);
}
if (result.success && result.url) {
console.log(`\n✅ 上传成功 (${result.provider}): ${result.url}`);
if (result.note) {
console.log(` 注意: ${result.note}`);
}
console.log(`\n可以在提示词中使用:\n${result.url} 参考这张图片...`);
// 保存到历史记录
const history = loadHistory();
history.push({
timestamp: new Date().toISOString(),
file: path.basename(imagePath),
url: result.url,
deleteUrl: result.deleteUrl || null,
deleteHash: result.deleteHash || null,
provider: result.provider || 'unknown'
});
saveHistory(history);
console.log(`\n📝 删除链接已保存到: ${historyFile}`);
if (result.deleteUrl) {
console.log(` 删除链接: ${result.deleteUrl}`);
}
if (result.deleteHash) {
console.log(` 删除 Hash: ${result.deleteHash}`);
}
console.log(`\n💡 提示: 使用 delete-image.ts 可以批量删除图片`);
} else {
console.error(`\n❌ 所有图床上传失败`);
console.error(` 最后错误: ${result.error}`);
process.exit(1);
}
Related skills
FAQ
What does pw-image-generation do?
pw-image-generation is a Claude Code skill for ai & agent building.
When should I use pw-image-generation?
When you need to helps with ai & agent building tasks., or when pw-image-generation is a claude code skill for ai & agent building.
What are the main capabilities?
pw-image-generation; AI & Agent Building; AI-coding skill.