name: ai-image-creator
description: Generate, edit-from-reference, or analyze images with AI via OpenRouter (Gemini, GPT Image, Seedream, Qwen, MAI, Grok, FLUX.2, Recraft, Muse, Riverflow; Cloudflare AI Gateway BYOK). Also analyze a video (--analyze-video, read-only — no video generated) into a text description for video prompts. Use when the user asks to generate an image, create a PNG, make an icon, make it transparent, edit with a reference, design a logo/banner, describe/analyze/explain an image ("what's in this image"), or describe/analyze a video ("what happens in this video").
allowed-tools: Bash, Read, Write
compatibility: Requires uv (Python runner) and network access. Environment variables for CF AI Gateway or direct API keys must be configured in shell profile (~/.zshrc on macOS, ~/.bashrc on Linux, or System Environment Variables on Windows).
metadata:
tags: image-generation, ai, openrouter, cloudflare, gemini, flux2, riverflow, seedream, gpt54, gpt-image, qwen, mai, grok, recraft, muse
AI Image Creator
Generate PNG images via multiple AI models, routed through Cloudflare AI Gateway BYOK or directly via OpenRouter/Google AI Studio.
Model Selection
When the user mentions a model keyword in their image request, use the corresponding --model flag:
| Keyword | Model | Use When User Says | Best For (measured cost · time per image) |
|---|
gemini | Google Gemini 3.1 Flash | "gemini", "nano banana 2" | Previous default; still marginally closer on the Opus benchmark; up to 4K ($0.067 · 12s at 1K) |
gemini-lite | Google Gemini 3.1 Flash Lite | "gemini lite", "nano banana lite", "fast draft" | Fast cheap iteration; 1K only ($0.034 · 5s) |
nano-banana-2.1 | Google Nano Banana 2.1 (default) | "nano banana 2.1", "nb 2.1", "generate an image" (no model specified) | Versatile default, Flash-tier successor to gemini: refs, --analyze, up to 4K; near-identical fidelity for ~44% less ($0.038 · 12s at 1K) |
geminipro | Google Gemini 3 Pro | "geminipro", "gemini pro", "use gemini pro" | Highest-quality Gemini (~$0.17 at 2K) |
riverflow | Sourceful Riverflow v2 Pro | "riverflow", "use riverflow" | Artistic/illustration ($0.15) |
flux2 | FLUX.2 Max | "flux2", "flux", "use flux" | Illustration, clean lines (~$0.07/MP) |
seedream | ByteDance Seedream 5.0 Lite | "seedream", "use seedream" | 2K/4K only, web-connected knowledge, 14 refs ($0.035 · 40s at 2K) |
gpt5.4 | OpenAI GPT-5.4 Image 2 | "gpt5.4", "gpt-5.4 image", "use gpt5.4" | Multimodal GPT; also --analyze (token-billed) |
gpt-sunburst | OpenAI GPT Image 2.5 Sunburst | "gpt image", "gpt image 2.5", "sunburst" | Precision editing, 16 refs, --quality up to max, native -t ($0.015 · 26s at default quality; token-billed, rises with --quality) |
gpt-flare | OpenAI GPT Image 2.5 Flare | "gpt flare", "fast gpt image" | Same features as Sunburst, speed tier ($0.015 · 19s at default quality) |
mai | Microsoft MAI-Image-2.6 | "mai", "microsoft image" | Multi-reference compositing of people/products/styles, 5 refs ($0.041 · 25s) |
mai-flash | Microsoft MAI-Image-2.6 Flash | "mai flash" | Faster MAI, same 5-ref editing ($0.020 · 15s) |
grok | xAI Grok Imagine Image 2.0 | "grok", "grok imagine" | 1K/2K, --quality low|medium, 3 refs ($0.060 · 66s — billed above its $0.04 list price; +$0.01/ref) |
qwen | Qwen Image 3 | "qwen", "qwen image" | Small legible text (posters, UI, infographics), 1K/2K, 4 refs ($0.030 · 63s) |
qwen-pro | Qwen Image 3 Pro | "qwen pro" | Qwen Image 3 with richer world knowledge ($0.040 · 57s) |
muse | Meta Muse Image | "muse", "meta image" | Complex multi-part prompts; reasons first, may search the web; prompt only ($0.010 · 18s, 1600px) |
recraft-flash | Recraft V4.1 Flash | "recraft", "cheapest", "quick draft" | Cheapest/fastest ~1K drafts; no -r ($0.007 · 5s) |
Models from seedream down to recraft-flash (except gpt5.4) use the OpenRouter Images API (/v1/images). Each accepts only the -a/-s/--quality/-r options it supports, and the script rejects anything else before calling the API. Run --list-models to see each model's limits.
Cost and time are real OpenRouter charges and end-to-end times (through the Cloudflare gateway) at default settings. They come from the Claude Opus robot benchmark, one sample per model on 2026-09-28 (nano-banana-2.1 on 2026-10-07). geminipro, riverflow, flux2 and gpt5.4 were not benchmarked; their figures are list prices or earlier cost-log values. For per-model output format, resolution and quality notes, read references/model-benchmarks.md. Re-run the benchmark with the ai-image-test-run skill.
Instructions
Routing check: If the user asks to describe, analyze, or explain an existing image (not generate a new one), skip directly to the Image Analysis (--analyze) section below. No prompt enhancement or output path needed.
Video routing: If the user asks to describe, analyze, or explain a video (or wants a text description of a clip to seed/extend a video prompt), skip directly to the Video Analysis (--analyze-video) section below.
Step 1: Write Prompt
For long or complex prompts (recommended), write to ${CLAUDE_SKILL_DIR}/tmp/prompt.txt using the Write tool:
Write prompt text to ${CLAUDE_SKILL_DIR}/tmp/prompt.txt
For short prompts (under 200 chars, no special characters), pass inline via --prompt.
CRITICAL — Prompt Quality Tips:
- Be detailed and descriptive. Include style, colors, composition, background, and intended use.
- Good: "A flat-design globe icon with vertical timezone band lines in blue and teal, white background, clean vector style, suitable for a web app at 512x512 pixels"
- Bad: "globe icon"
- Specify "transparent background" or "white background" explicitly.
- For icons, mention the target size (e.g., "512x512", "favicon at 32x32").
- For photos, describe lighting, camera angle, and mood.
Step 1.5: Prompt Enhancement (Optional — Progressive Disclosure)
Professional prompt patterns are available in 3 reference files. These are not loaded by default — only read them when the user's request matches a category or they explicitly ask for enhancement.
Category Detection — Match the user's request to a category:
| If request mentions... | Category | Also read |
|---|
| "product shot", "product photo", "hero image" | product_hero | prompt-core.md + prompt-categories.md § product_hero |
| "lifestyle", "in-use", "in context" | lifestyle | prompt-core.md + prompt-categories.md § lifestyle |
| "instagram", "social media", "tiktok", "pinterest" | social_media | prompt-core.md + prompt-platforms.md + prompt-categories.md § social_media |
| "banner", "ad", "email header" | marketing_banner | prompt-core.md + prompt-platforms.md + prompt-categories.md § marketing_banner. Routing hint: If user has an existing logo and wants multiple standard sizes → use composite mode instead (see ## Composite Banners). |
| "website", "app", "logo", "ad format", "leaderboard", "skyscraper" | web_app | prompt-core.md + prompt-platforms.md + prompt-categories.md § web_app. Routing hint: For "logo banners" or "OG images with my logo" where user has existing logo → use composite-banners.py. For "design me a new logo" → use generate-image.py. |
| "brand kit", "logo banners", "banner sizes", "IAB sizes", "consistent banners" + user has existing logo | composite | Read references/composite-reference.md, use composite-banners.py |
| "icon", "favicon", "app icon" | icon_logo | prompt-core.md + prompt-categories.md § icon_logo |
| "mascot", "character", "illustration", "artwork" | illustration | prompt-core.md + prompt-categories.md § illustration |
| "food", "drink", "recipe", "restaurant" | food_drink | prompt-core.md + prompt-categories.md § food_drink |
| "building", "interior", "room", "architecture" | architecture | prompt-core.md + prompt-categories.md § architecture |
| "chart", "infographic", "data", "diagram" | infographic | prompt-core.md + prompt-categories.md § infographic |
| "t-shirt", "mug design", "poster", "POD", "print-on-demand" | pod_design | prompt-core.md + prompt-platforms.md + prompt-categories.md § pod_design |
| "consistent character", "same character/product across frames", "comic strip", "storyboard", "frame set", "start and last frame", "panels", "before/after" | frame_consistency | Read references/consistency-presets.md — keep people/objects/scenes consistent across a SET of frames (for video first/last frames or stitched comic strips) |
| "describe", "analyze", "what's in this image", "explain image" | analyze | Handled by the top Routing check — read references/analyze-reference.md only for advanced/structured analysis patterns |
| No match / simple request | — | Skip patterns, generate directly |
When to skip enhancement:
- User's prompt is already detailed (150+ words with camera/lighting/composition specifics)
- Simple/direct requests ("generate a blue circle on white background")
- User says "no pattern" or provides a fully formed prompt
When to apply:
- User says "use product_hero pattern" or "apply social_media pattern" (explicit)
- Request clearly matches a category above (auto-detect)
- User asks for "enhanced prompt" or "professional quality"
Reference files (in references/ directory):
prompt-core.md — Foundational rules: narrative prompting, camera/lens/lighting specs, text rendering rules, model recommendations
prompt-platforms.md — Social media ratios, IAB ad sizes, web dimensions, POD specs — all mapped to -a/-s flags
prompt-categories.md — 11 category formulas with templates and complete example prompts
Step 2: Run Generation Script
uv run python ${CLAUDE_SKILL_DIR}/scripts/generate-image.py \
-o "OUTPUT_PATH" \
[--provider openrouter|google] \
[-a "16:9"] \
[-s "2K"] \
[-m "model-id"] \
[-r "ref-image.png"] \
[--quality "high"] \
[-t]
With a specific model:
uv run python ${CLAUDE_SKILL_DIR}/scripts/generate-image.py \
-o "OUTPUT_PATH" \
-m riverflow \
-p "A serene mountain lake at sunset"
With transparent background (requires ffmpeg + imagemagick):
uv run python ${CLAUDE_SKILL_DIR}/scripts/generate-image.py \
-o "mascot.png" \
-t \
-p "A friendly robot mascot character"
With reference image for editing/style transfer (see Reference Images for which models accept -r):
uv run python ${CLAUDE_SKILL_DIR}/scripts/generate-image.py \
-o "edited.png" \
-r "original.png" \
-p "Change the background to a sunset scene"
Or with inline prompt (default model):
uv run python ${CLAUDE_SKILL_DIR}/scripts/generate-image.py \
-o "OUTPUT_PATH" \
-p "A simple blue circle on white background"
Step 3: Clean Up (if temp file used)
rm -f ${CLAUDE_SKILL_DIR}/tmp/prompt.txt
Step 4: Verify Output
file OUTPUT_PATH
Confirm it shows "PNG image data" and report the file path and size to the user.
Step 5: Post-Processing (optional)
If the user needs resizing, format conversion, or other manipulation, first detect available image tools, then use them. See Image Tools section below.
Parameters
| Argument | Short | Required | Default | Description |
|---|
--output | -o | Yes | -- | Output file path (parent dirs auto-created). Saved in the format its extension names (.png, .jpg, .webp; anything else means PNG). Models that return another format (Gemini Flash Lite, Grok and Seedream send JPEG; Muse and Recraft send WebP; Nano Banana 2.1 also sends a non-PNG format) are converted with ImageMagick, or saved unconverted with a warning if it's missing. The result JSON's format field reports what was written |
--prompt | -p | No | -- | Inline prompt text |
--prompt-file | -- | No | ../tmp/prompt.txt | Path to prompt file |
--provider | -- | No | openrouter | openrouter or google |
--aspect-ratio | -a | No | model default | OpenRouter only: 1:1, 16:9, 9:16, 3:2, 2:3, 4:3, 3:4, 4:5, 5:4, 21:9 |
--image-size | -s | No | model default | OpenRouter only: 1K, 2K, 4K. Images-API models accept only their listed sizes (seedream 2K/4K; grok/qwen/qwen-pro 1K/2K; the rest none). gemini-lite is 1K only. 0.5K is accepted only on the Gemini 3.1 Flash preview build (-m google/gemini-3.1-flash-image-preview-20260226); every selectable keyword rejects it |
--model | -m | No | nano-banana-2.1 | Model keyword (see Model Selection or --list-models) or full model ID |
--ref | -r | No | -- | Reference image file (repeatable). For editing/style transfer. See Reference Images for supported models and per-model limits |
--quality | -- | No | model default | Images-API models only: gpt-sunburst/gpt-flare take auto, low, medium, high, xhigh, max; grok takes low, medium |
--analyze | -- | No | -- | Analyze/describe a reference image (text-only output, no image generated). Requires -r. Multimodal chat models only (gemini, gemini-lite, nano-banana-2.1, geminipro, gpt5.4) |
--analyze-video | -- | No | -- | Analyze/describe a video. Pass the video via -r (local file or URL). OpenRouter only. Choose a model/preset with -m (default gemini3.5-flash). Returns structured JSON by default |
--prose | -- | No | -- | (--analyze-video only) Return free-text prose instead of the default structured JSON |
--contact-sheet | -- | No | -- | (--analyze-video, local file only) Extract evenly-spaced keyframes with ffmpeg and save a labeled contact-sheet image to PATH — a human ground-truth reference. Skipped for URL sources / if ffmpeg is missing |
--verify | -- | No | -- | (--analyze-video, local file only) Second pass that checks the analysis against extracted frames (no video re-sent) and classifies each claim supported/contradicted/not_visible. Adds a verification object. Costs one extra model call |
--transparent | -t | No | -- | Generate with transparent background. Native on gpt-sunburst/gpt-flare (no extra tools); every other model requires ffmpeg + imagemagick |
--costs | -- | No | -- | Display generation/cost history for this project and exit |
--list-models | -- | No | -- | List available model keywords and exit |
Environment Variables
| Variable | Required For | Description |
|---|
AI_IMG_CREATOR_CF_ACCOUNT_ID | Gateway mode | Cloudflare account ID |
AI_IMG_CREATOR_CF_GATEWAY_ID | Gateway mode | AI Gateway name |
AI_IMG_CREATOR_CF_TOKEN | Gateway mode | Gateway auth token |
AI_IMG_CREATOR_OPENROUTER_KEY | Direct OpenRouter | OpenRouter API key (sk-or-...) |
AI_IMG_CREATOR_GEMINI_KEY | Direct Google | Google AI Studio API key |
Gateway mode activates when all 3 CF_* vars are set. Falls back to direct mode if gateway fails.
For first-time setup, see references/setup-guide.md.
Transparent Mode (-t)
Generates images with transparent backgrounds using a 3-step pipeline:
- Green screen generation — Prompt is augmented to place subject on solid #00FF00 green
- FFmpeg chroma key — Removes green background + green fringe from edges
- ImageMagick auto-crop — Trims transparent padding
Requirements: brew install ffmpeg imagemagick
Native transparency: With -m gpt-sunburst or -m gpt-flare, -t sends background: transparent to the Images API instead. The model renders the alpha channel directly: there is no green-screen prompt, no chroma key and no ffmpeg/imagemagick requirement. Output must still be PNG or WebP.
Use cases: Game sprites, icons, logos, mascots, marketing assets with transparency.
uv run python ${CLAUDE_SKILL_DIR}/scripts/generate-image.py \
-o "sprite.png" -t -p "A pixel art treasure chest"
Reference Images (-r)
Send existing images alongside text prompts for editing, style transfer, or guided generation. Supports multiple references, up to each model's limit:
| Models | Max -r |
|---|
gemini, gemini-lite, nano-banana-2.1, geminipro, gpt5.4 (chat) | no script limit |
gpt-sunburst, gpt-flare | 16 |
seedream | 14 |
mai, mai-flash | 5 |
qwen, qwen-pro | 4 |
grok | 3 |
riverflow, flux2, muse, recraft-flash | not supported (errors) |
# Edit an existing image
uv run python ${CLAUDE_SKILL_DIR}/scripts/generate-image.py \
-o "edited.png" -r "photo.png" -p "Make the background white"
# Style transfer with multiple references
uv run python ${CLAUDE_SKILL_DIR}/scripts/generate-image.py \
-o "combined.png" -r "style1.png" -r "content.png" -p "Apply the style of the first image to the second"
Supported formats: PNG, JPEG, WebP, GIF.
Image Analysis (--analyze)
Describe, analyze, or explain existing images using multimodal AI vision. Returns text-only output (no image generated). Multimodal chat models only (gemini, gemini-lite, nano-banana-2.1, geminipro, gpt5.4). Images-API models output images only and are rejected.
No -o output path needed. No prompt enhancement needed. The script outputs JSON to stdout with the model's analysis in the analysis field.
# Analyze with default prompt (describes subject, style, colors, composition, mood, text)
uv run python ${CLAUDE_SKILL_DIR}/scripts/generate-image.py \
--analyze -r "photo.png"
# Analyze with custom prompt
uv run python ${CLAUDE_SKILL_DIR}/scripts/generate-image.py \
--analyze -r "photo.png" -p "Describe this image in plain text and also in JSON structured output"
# Analyze with a specific model
uv run python ${CLAUDE_SKILL_DIR}/scripts/generate-image.py \
--analyze -r "photo.png" -m gpt5.4 -p "What text is visible in this image?"
# Analyze multiple images together
uv run python ${CLAUDE_SKILL_DIR}/scripts/generate-image.py \
--analyze -r "before.png" -r "after.png" -p "Compare these two images and describe the differences"
JSON output format:
{"ok": true, "analyze": true, "analysis": "<model text>", "provider": "openrouter", "model": "...", "mode": "gateway", "elapsed_seconds": 3.2, "ref_images": 1}
Incompatible flags: --analyze cannot be combined with -t, -a, or -s. (-o is accepted but ignored in analyze mode, which returns text only.)
For advanced analysis prompt patterns (structured output, comparison, targeted analysis), read references/analyze-reference.md.
Video Analysis (--analyze-video)
Describe or analyze a video using OpenRouter video-input LLMs (no image generated). Use this to turn an existing clip into a description you can feed back as a prompt to generate or extend a video (e.g. with the ai-video-creator skill).
Structured JSON is the default. All 15 video models support strict structured outputs (response_format json_schema, verified), so by default analysis is a structured object with these fields: summary, setting, subjects[] (each with role/appearance/confidence), shot_timeline[] (timestamp/action/camera), camera_techniques[], editing_stylization[], lighting, color_palette[], mood, uncertain_details[], and a distilled video_generation_prompt. The editing_stylization and uncertain_details fields specifically counter the two main failure modes (missed freeze-frame/black-and-white stylization, and confabulated details). Pass --prose for a free-text description instead. The envelope's structured field is true when JSON parsed cleanly.
Pass the video via -r — either a local file (mp4/mov/webm/mkv/avi; sent as a base64 data URL) or a URL (publicly accessible, including YouTube). OpenRouter only; no -o, prompt enhancement, or output path needed.
Model selection (-m) — three presets cover the common cases; or pick any model by keyword (see --list-models):
| Preset | Resolves to | When to use |
|---|
video-default (or omit -m) | gemini3.5-flash (Google Gemini 3.5 Flash) | Default — best accuracy + fastest; reads audio. ~11× the cost of the cheap tier |
video-cheap | qwen3.5-flash (Qwen3.5 Flash) | Rock-bottom cost for quick scene summaries (or mimo for a cheap, more detailed read) |
video-quality | gemini3-pro (Google Gemini 3.1 Pro) | Highest-accuracy reading when it matters most |
All 15 video-capable models are selectable by keyword: qwen3.5-flash, seed-1.6-flash, seed-2.0-mini, mimo, qwen3.6-35b, qwen3.6-flash, step-3.7-flash, gemini3-flash-lite, seed-2.0-lite, seed-1.6, qwen3.5-plus, minimax-m3, qwen3.6-plus, gemini3.5-flash, gemini3-pro (cheapest → priciest). Run --list-models for IDs and per-1M-token pricing.
Bare family names are not keywords. -m gemini, -m seed, or -m qwen (the image-model families) are not valid --analyze-video selectors and error with "unknown video model". Use a preset (video-default/video-cheap/video-quality) or a full keyword from the list above (e.g. gemini3.5-flash, seed-1.6-flash).
# Default model (gemini3.5-flash), structured JSON output
uv run python ${CLAUDE_SKILL_DIR}/scripts/generate-image.py \
--analyze-video -r "clip.mp4"
# Free-text prose instead of JSON
uv run python ${CLAUDE_SKILL_DIR}/scripts/generate-image.py \
--analyze-video -r "clip.mp4" --prose
# Rock-bottom cost preset
uv run python ${CLAUDE_SKILL_DIR}/scripts/generate-image.py \
--analyze-video -r "clip.mp4" -m video-cheap
# Highest-accuracy preset on a YouTube URL with a custom focus
uv run python ${CLAUDE_SKILL_DIR}/scripts/generate-image.py \
--analyze-video -r "https://youtu.be/VIDEO_ID" -m video-quality \
-p "Focus on camera movement and lighting"
JSON output format (default — analysis is a structured object):
{"ok": true, "analyze": true, "analyze_video": true, "structured": true, "analysis": {"summary": "...", "setting": "...", "subjects": [{"role": "protagonist", "appearance": "...", "confidence": "high"}], "shot_timeline": [{"timestamp": "0:00", "action": "...", "camera": "..."}], "camera_techniques": ["..."], "editing_stylization": ["monochrome freeze-frame", "..."], "lighting": "...", "color_palette": ["..."], "mood": "...", "uncertain_details": ["..."], "video_generation_prompt": "..."}, "provider": "openrouter", "model": "google/gemini-3.5-flash", "mode": "gateway", "elapsed_seconds": 16.9, "video_source": "clip.mp4"}
With --prose, analysis is a plain text string and structured is false.
Frame grounding (--contact-sheet, --verify)
The model samples its own frames internally, but it can still slip a confabulation into a
single shot (e.g. a "golden glowing eye" in the final beat that isn't there). Two opt-in,
local-file-only aids ground the analysis against real pixels using ffmpeg-extracted
keyframes:
--contact-sheet PATH — extracts ~12 evenly-spaced keyframes (always including first
and last; capped uniform sampling, not scene-detect) and tiles them into one labeled image
at PATH. This is the highest-leverage aid: a human (or you) can eyeball the whole clip at
a glance to sanity-check the description. Built with ImageMagick montage (timestamp
labels) or, if absent, ffmpeg's tile filter. The path is echoed back as contact_sheet
in the JSON envelope.
--verify — runs a cheap second pass that sends the contact sheet + a few full
keyframes (with timestamps) and the pass-1 analysis back to the same model, and asks it
to classify each claim supported / contradicted / not_visible strictly from the
frames. The video is not re-sent (that would just re-confabulate from the same pixels),
and undiscernible details stay not_visible rather than being "resolved" into a guess. Adds
a verification object: {claims[]{claim,verdict,evidence}, corrections[], overall_accuracy}.
Both are skipped with a warning (never a hard error) for URL/YouTube sources or if ffmpeg is
missing — the analysis itself always proceeds. Extracted frames go to a temp dir that is
cleaned up automatically; only the --contact-sheet image is kept.
# Save a ground-truth contact sheet alongside the analysis, and verify the claims
uv run python ${CLAUDE_SKILL_DIR}/scripts/generate-image.py \
--analyze-video -r "clip.mp4" \
--contact-sheet "exports/clip_frames.png" --verify
Notes:
- Incompatible flags: cannot be combined with
--analyze, -t, -a, or -s, and requires --provider openrouter.
- Large local files (>20 MB) trigger a warning — base64 payloads can be slow or rejected; prefer a hosted/YouTube URL or a shorter/lower-res clip.
- Context limits: Seed/Step models cap at ~256K tokens (fine for short clips); the 1M-context models (Qwen, Gemini, MiMo, MiniMax) are safer for longer footage.
Cost Tracking (--costs)
Every generation is logged to .ai-image-creator/costs.json in your project directory. View history:
uv run python ${CLAUDE_SKILL_DIR}/scripts/generate-image.py --costs
Shows per-model breakdown: generation count, total tokens, elapsed time, and recent entries. Security: Only non-sensitive data is logged (model, tokens, timing, file path). No API keys or credentials are ever stored.
Token totals may under-count. OpenRouter image-generation responses (and Cloudflare-gateway responses) often omit the usage block, so those entries log 0 tokens. Elapsed time and generation counts are always accurate; treat token totals as best-effort.
Consider adding .ai-image-creator/ to your .gitignore.
Composite Banners
Generate consistent logo banners across multiple sizes from a JSON config. Uses ImageMagick for offline compositing — no API calls, no network required. Composites an existing logo/mark onto branded backgrounds with text at standard dimensions.
Composite vs. AI Generation — Decision Rule
Use composite-banners.py when ALL of these are true:
- User has an existing logo/mark they want to use as-is (provides or references a logo file)
- User wants consistent branding across multiple standard sizes (not one creative image)
- The output is logo + text on a solid/gradient background (not a photograph, illustration, or creative design)
Use generate-image.py (AI generation) when ANY of these are true:
- User wants a creative/artistic banner design (describes a scene, mood, concept, or style)
- User wants AI to design the visual content (product shots, illustrations, creative layouts)
- User wants a single banner with artistic content, not a multi-size brand kit
When composite mode applies, read references/composite-reference.md for full config schema, preset dimensions, and font handling details.
Quick Start
- Init config:
uv run python ${CLAUDE_SKILL_DIR}/scripts/composite-banners.py --init
- Edit
banner-config.json — set logo path, brand text, colors, banner sizes
- Validate:
uv run python ${CLAUDE_SKILL_DIR}/scripts/composite-banners.py --validate
- Generate:
uv run python ${CLAUDE_SKILL_DIR}/scripts/composite-banners.py -c banner-config.json -o ./banners/
Composite Parameters
| Argument | Short | Default | Description |
|---|
--config | -c | banner-config.json | Config JSON path |
--output-dir | -o | . | Output directory |
--name | -n | all | Generate single banner by name |
--format | -f | png | png, webp, jpeg |
--list-presets | | | List IAB/social/web size presets |
--init | | | Generate starter config |
--validate | | | Check config, exit 0 or 2 |
--dry-run | | | Preview without rendering |
--json | | | Structured JSON to stdout |
--verbose | -v | | Verbose output |
Requirements: ImageMagick 7 (brew install imagemagick or apt install imagemagick).
Workflow Hints
Starting composite mode:
- Ask user for: logo file path, brand name, tagline text, brand colors (hex)
- If user doesn't have a logo yet → use generate-image.py to create one first
- Run
--init to scaffold config, then help user fill in their brand values
During generation:
- Always run
--validate before generating to catch font/logo issues early
- Use
--name to iterate on one banner before generating the full set
- Show user 3-4 representative sizes (hero, OG, square, leaderboard) for approval
After generation:
- If user wants creative/artistic redesign of banner visuals → switch to generate-image.py (composite only does logo + text on gradient/solid backgrounds)
- If banners look too plain → suggest AI-generating a textured or photographic background first, then compositing the logo onto it
Combined workflow (most powerful):
- Use generate-image.py to AI-create a hero background or textured pattern
- Use composite-banners.py to overlay the logo + text onto that background at all standard sizes
This gives both creative AI visuals AND pixel-perfect logo consistency.
Image Tools
On first invocation, detect available image manipulation tools:
which magick convert sips ffmpeg 2>/dev/null
Available Tools
| Tool | Check | Key Operations |
|---|
ImageMagick 7 (magick) | magick --version | Resize, crop, convert, composite |
ImageMagick 6 (convert) | convert --version | Same ops, legacy command name |
| sips (macOS) | sips --help | Resize, format conversion |
| ffmpeg | ffmpeg -version | Convert formats, resize |
Common Post-Processing
# Resize
magick output.png -resize 512x512 icon-512.png
# Multiple sizes (icons)
for s in 16 32 48 64 128 256 512; do magick output.png -resize ${s}x${s} icon-${s}.png; done
# Convert to WebP
magick output.png output.webp
# Maskable icon (add safe-zone padding)
magick output.png -gravity center -extent 120%x120% maskable.png
# macOS sips resize
sips --resampleWidth 512 --resampleHeight 512 output.png --out icon-512.png
CRITICAL: Check tool availability before using. Prefer magick (IM7) over convert (IM6). If no tools found, inform user: brew install imagemagick.
Common Issues
"No API credentials configured"
Cause: Environment variables not set or not exported.
Fix: Add exports to ~/.zshrc and run source ~/.zshrc. See references/setup-guide.md.
"HTTP 401: Unauthorized"
Cause: Invalid or expired API key/token.
Fix: Check AI_IMG_CREATOR_CF_TOKEN (gateway) or AI_IMG_CREATOR_OPENROUTER_KEY (direct). Regenerate if needed.
"No images in response"
Cause: Model returned text only (safety filter, unclear prompt, or unsupported request).
Fix: Make the prompt more specific and descriptive. Avoid prohibited content.
"Connection error" / timeout
Cause: Network issue or image generation taking too long (300s timeout).
Fix: Retry. If persistent, try --provider google as alternative. Check CF gateway status.
Detailed API Reference
For full API formats, response schemas, BYOK configuration, and curl examples:
see references/api-reference.md
For first-time setup instructions:
see references/setup-guide.md