Cursor Skill

generating-images

Generate or edit images using the OpenAI Image API (gpt-image-2). Use when the user asks to generate, create, draw, render, illustrate, mock up, or edit an image, icon, logo, mockup, illustration, OG image, blog hero, marketing asset, or similar visual. Also use when the user sup

LLM Mart · 0 points · 24 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download spencerpauly-awesome-cursor-skills-resources_generating-images-99cd265.zip · 9 KB
Part of spencerpauly/awesome-cursor-skills — 65 skills

Install

skills CLI npx skills add https://github.com/spencerpauly/awesome-cursor-skills/tree/main/resources/generating-images
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install spencerpauly-awesome-cursor-skills@llmmart
Git git clone https://github.com/spencerpauly/awesome-cursor-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole spencerpauly/awesome-cursor-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Generating Images (OpenAI gpt-image-2)

Use this skill any time the user asks to generate or edit an image. It wraps OpenAI's gpt-image-2 model via a Python script, supports both text-only prompts and one-or-more reference images, and writes the resulting PNG/JPEG/WebP to disk.

Hard rules (do not violate)

  1. Always use gpt-image-2. Never fall back to gpt-image-1, dall-e-3, or any other model. The script has no --model flag for this reason.
  2. Fail fast on any error. Do not retry, do not swap models, do not patch around missing credentials, do not silently degrade quality. If the script exits non-zero, surface the error to the user verbatim and stop.
  3. Do not "fix" a missing OPENAI_API_KEY by reading from .env files, 1Password, etc. unless the user explicitly tells you to. If the env var is missing, ask the user how they want to provide it (or to export it) and then stop.

When to use

  • User asks for a generated image: icon, logo, illustration, mockup, OG image, blog hero, marketing asset, concept art, diagram-style image, etc.
  • User provides one or more images and asks to edit, restyle, combine, or use them as references.
  • User asks to remove/replace part of an image (use --mask).

Do not use this skill for:

  • Charts/plots/data viz (generate via code instead).
  • Sourcing existing photos (use a stock photo skill if available).
  • Screenshots of the user's app (use a screenshot skill if available).

Prerequisites

1. OpenAI API key

You need an OPENAI_API_KEY exported in your environment. Get one at platform.openai.com/api-keys.

The skill ships with a .env.example next to this SKILL.md. Copy it and fill in your key:

cp .env.example .env
# then edit .env and put your real key in

Then export it before running the script:

set -a && source .env && set +a

Or just export it directly in your shell:

export OPENAI_API_KEY="sk-..."

If OPENAI_API_KEY is not set, the script exits with code 2 immediately. Do not try to read it from anywhere else without the user's explicit permission.

2. Org verification

Your OpenAI org must be verified for gpt-image-2 at platform.openai.com/settings/organization/general. If you see a 403 mentioning "organization must be verified", surface it and stop — do not switch models.

3. Python dependency

pip install --upgrade openai

Script location

The Python script lives next to this SKILL.md at scripts/generate_image.py. When this skill is installed at ~/.cursor/skills/generating-images/, the script will be at ~/.cursor/skills/generating-images/scripts/generate_image.py.

It prints the absolute path(s) of the written image(s) to stdout. Errors go to stderr with a non-zero exit code, and the script exits immediately on the first error.

How to invoke

Always run via the Shell tool. Pick a sensible output path inside the user's current workspace (e.g. ./public/generated/<slug>.png for web projects, or ./<slug>.png otherwise).

1. Text-to-image

python3 ~/.cursor/skills/generating-images/scripts/generate_image.py \
  --prompt "Minimal flat-vector app icon for a note-taking app, indigo gradient, rounded square, soft shadow" \
  --size 1024x1024 \
  --quality high \
  --out ./icon.png

2. Image-to-image (one reference)

python3 ~/.cursor/skills/generating-images/scripts/generate_image.py \
  --prompt "Restyle this photo as a watercolor painting with warm tones" \
  --image ./photo.jpg \
  --out ./photo-watercolor.png

3. Multiple reference images

python3 ~/.cursor/skills/generating-images/scripts/generate_image.py \
  --prompt "Photorealistic flat-lay product shot combining all of these items on a white background" \
  --image ./a.png --image ./b.png --image ./c.png \
  --out ./flatlay.png

4. Masked edit (inpainting)

The mask must be the same size and format as the first input image, with an alpha channel marking the editable region.

python3 ~/.cursor/skills/generating-images/scripts/generate_image.py \
  --prompt "Replace the sky with a vivid sunset" \
  --image ./scene.png --mask ./sky-mask.png \
  --out ./scene-sunset.png

5. Batch / parallel mode (many distinct images at once)

When you need to generate multiple different images in one go (e.g. a set of blog heroes, several icon variations with different prompts, OG images for many pages), use --batch instead of running the script N times. It runs all jobs in parallel from a single Python process — much faster than serial calls and avoids repeated SDK startup cost.

Write a JSON file describing every job, then call the script once:

cat > /tmp/img-jobs.json <<'EOF'
[
  {
    "prompt": "Minimal flat-vector app icon for a note-taking app, indigo gradient, rounded square",
    "out": "./public/icons/notes.png",
    "size": "1024x1024",
    "quality": "high"
  },
  {
    "prompt": "Photoreal blog hero: a cozy library with warm afternoon light, 5:3 ratio",
    "out": "./public/static/blog/library.png",
    "size": "1600x960",
    "quality": "medium"
  },
  {
    "prompt": "Restyle this product photo as a watercolor painting with warm tones",
    "image": ["./public/products/mug.jpg"],
    "out": "./public/products/mug-watercolor.png"
  }
]
EOF

python3 ~/.cursor/skills/generating-images/scripts/generate_image.py \
  --batch /tmp/img-jobs.json --concurrency 5

Each job object accepts the same fields as the CLI flags: prompt (required), out, size, quality, format, n, image (string or array of strings), mask. Defaults match the single-shot CLI.

Behavior:

  • All jobs run concurrently up to --concurrency (default 4). A reasonable range is 3–8; OpenAI rate-limits per org so don't go too wild.
  • Each successfully written image's absolute path is printed to stdout as soon as that job finishes, one per line.
  • If any job fails, its error is printed to stderr (ERROR: job <i> failed: ...) and the script exits with code 1 after the remaining jobs finish. Other jobs are not cancelled — partial output is fine and you can retry only the failed ones.
  • --batch is mutually exclusive with --prompt / --image / --mask.

When to prefer --batch over parallel Shell calls: any time you're generating ≥2 distinct images in the same turn. Don't fire multiple parallel Shell invocations of this script — use one batch call instead.

Don't confuse with --n. --n produces multiple variations of the same prompt in a single API call (cheaper, but all the same idea). --batch runs different prompts in parallel. They can be combined: a batch job can set "n": 4 to get 4 variations of that one prompt.

Flags reference

Flag Default Notes
--prompt required* Required unless --batch is used. Always include, even when editing.
--image none Pass multiple times for multiple references. Triggers images.edit.
--mask none Optional inpainting mask (PNG with alpha).
--out ./image.png Output path; index suffix added when --n > 1.
--size auto 1024x1024, 1536x1024, 1024x1536, 2048x2048, 3840x2160, etc. Edges must be multiples of 16, max 3840px, ratio ≤ 3:1.
--quality auto low (fast drafts), medium, high (final assets).
--format png png, jpeg, webp.
--n 1 Variations of the SAME prompt in one call.
--batch none Path to JSON array of job objects; runs them in parallel.
--concurrency 4 Max parallel workers in --batch mode.

There is intentionally no --model flag. The model is hardcoded to gpt-image-2.

Sizing guidance

  • App icons / square thumbnails → 1024x1024
  • Landing-page heroes / OG images → 1536x1024
  • Blog hero (5:3) → 1600x960 (both edges multiples of 16, ratio = 5:3)
  • Mobile / portrait illustrations → 1024x1536
  • Marketing posters / 4K assets → 3840x2160

Quality guidance

  • low for quick exploration / drafts (cheapest, fastest).
  • medium is a good default.
  • high only for final, ship-ready assets — significantly more expensive and can take up to ~2 minutes.

If the user just says "generate an image" with no signal of finality, default to --quality medium.

Prompt-writing tips

For best results, include in the prompt:

  • Subject (what is in the image)
  • Style (flat vector, watercolor, photoreal, isometric, line drawing, 3D render…)
  • Composition / camera (close-up, top-down, wide shot)
  • Color palette / mood
  • Background (white, gradient, scene — note: gpt-image-2 does not support transparent backgrounds)
  • Any text that must appear, in quotes (gpt-image-2 renders text well)

If the user gives a vague prompt, expand it with sensible defaults rather than asking back, unless the request is genuinely ambiguous.

After generating

  1. Print the output path back to the user.
  2. Do not embed the image in markdown — Cursor displays generated files automatically when they are written into the workspace.
  3. If the result is meant for a website/app, consider also running it through an optimizer (e.g. pngquant, cwebp) when file size matters.

Gather context BEFORE generating

Unless the user has spelled out exactly what they want (subject, style, palette, size, destination), do a quick context-gathering pass first. The goal is for the generated image to feel like it belongs where it's going, not like a random asset dropped into the project. Skipping this step is the #1 way this skill produces off-brand results.

Things to look at, in roughly this order:

  1. Sibling images at the destination. If the image will live in public/static/blog/, public/static/marketing/, assets/, etc., open one or two existing images in that folder with the Read tool. Match their:

    • Illustration style (3D cartoon, flat vector, photoreal, line art, isometric…)
    • Color palette and lighting
    • Subject conventions (e.g. "always features the product mascot", "always a metaphor, never literal screenshots", etc.)
    • Aspect ratio and resolution
  2. The surface that will display it. Read the relevant file:

    • Blog post → read the MDX/Markdown (title, tags, opening paragraphs, key metaphors).
    • Landing page section → read the component, headline, and surrounding copy.
    • README → read the top of the README.
    • Component → read the component to understand what it represents.

    Pull the image's meaning from the actual content, not just the filename.

  3. Brand / design tokens. If the project has a clearly defined palette, logo, or mascot, mirror them. Quick places to check:

    • tailwind.config.* for brand colors
    • globals.css / theme files for CSS variables
    • public/ for logos / mascot assets
    • Any existing OG images or marketing assets
  4. Aspect ratio / size. Pick --size based on the surface: blog hero, OG image, square avatar, mobile portrait, etc. Match what's already there.

Then write the prompt incorporating what you learned: subject pulled from the content, style + palette pulled from sibling assets and brand tokens, composition matched to the surface.

If the user did give explicit direction (style, colors, exact subject), honor it and skip context-gathering. If they gave partial direction, gather context for the parts they left open.

Don't ask the user clarifying questions for things you can reasonably infer from the codebase — infer first, ask only when something is genuinely ambiguous (e.g. two equally valid styles already exist in the project).

Place it AND wire it up — don't just dump a file

When the user asks for an image for a specific surface (a blog post, a landing page, an OG card, a README, a component, etc.), you are responsible for the whole job, not just the PNG. Always do these in order:

  1. Pick the correct on-disk location for that surface. Look at what already exists and match it. Examples:

    • Blog hero → wherever existing blog images live (e.g. apps/<app>/public/static/blog/<slug>.png).
    • Landing page asset → wherever other landing assets live (e.g. apps/<app>/public/static/marketing/...).
    • README / docs image → docs/images/, assets/, or next to the doc.
    • Component-specific asset → next to the component or in its public//assets/ folder.

    Use the file's slug, component name, or section name for the filename. Don't invent a new convention if one already exists.

  2. Wire the image up so it actually shows where the user wanted it. This is not optional. Examples:

    • Blog post MDX → update the image: (or equivalent) frontmatter field to point at the new path. Replace any placeholder Unsplash/stock URL.
    • Landing page section → import or reference the new asset in the relevant component/JSX.
    • OG image → update the <meta property="og:image"> / metadata config.
    • README → add the appropriate Markdown image tag.
  3. Match existing conventions for paths (relative vs /static/... vs @/assets/...), file format (png/webp/jpg), and any wrapper components (next/image, custom <Image>, etc.).

  4. Don't ask first. If the user asked for an image for a known surface, do the placement + wiring automatically and tell them what you changed at the end. Only ask when the destination is genuinely ambiguous.

Errors — surface, don't hide

If any of the following happen, stop immediately and report the error to the user. Do not retry, do not change the model, do not change the prompt.

  • OPENAI_API_KEY is not set → ask the user how to provide it.
  • openai package not installed → tell the user to run pip install --upgrade openai.
  • 403 "organization must be verified" → tell the user to verify at platform.openai.com/settings/organization/general. Do not switch models.
  • 400 size error → report it; let the user pick a valid size.
  • 400 about transparent background → report it; gpt-image-2 doesn't support transparency.
  • Any other API error → report verbatim and stop.
Files (awesome-cursor-skills)
  • scripts
    • generate_image.py 7.9 KB
      #!/usr/bin/env python3
      """
      Generate or edit images via OpenAI's gpt-image-2 model.
      
      This script ALWAYS uses gpt-image-2. There is no fallback model and no way
      to override it. If anything goes wrong (missing key, missing package, API
      error, etc.) it exits immediately with a non-zero status. Do not silently
      recover.
      
      Usage:
        # Single image from prompt
        generate_image.py --prompt "a serene winter landscape" --out ./out.png
      
        # Edit / use one or more reference images (also requires a prompt)
        generate_image.py --prompt "make it look like a watercolor painting" \\
            --image ./photo.png --out ./out.png
      
        generate_image.py --prompt "combine these into a flat-lay product shot" \\
            --image ./a.png --image ./b.png --image ./c.png --out ./combined.png
      
        # Batch / parallel mode: run many distinct jobs concurrently
        generate_image.py --batch ./jobs.json --concurrency 5
      
        # Optional knobs
        --size 1024x1024 | 1536x1024 | 1024x1536 | 2048x2048 | 3840x2160 | auto
        --quality low | medium | high | auto
        --format png | jpeg | webp
        --n 1                       # variations of the SAME prompt in one call
        --concurrency 4             # parallel workers in --batch mode
      
      Batch file format (JSON array of jobs):
        [
          {
            "prompt": "...",
            "out": "./a.png",
            "size": "1024x1024",
            "quality": "high",
            "format": "png",
            "n": 1,
            "image": ["./ref1.png", "./ref2.png"],
            "mask": "./mask.png"
          },
          { "prompt": "...", "out": "./b.png" }
        ]
      
      Each job uses the same defaults as the single-shot CLI when fields are omitted.
      
      Requires: OPENAI_API_KEY in env, and `pip install openai>=1.x`.
      """
      
      from __future__ import annotations
      
      import argparse
      import base64
      import json
      import os
      import sys
      import threading
      from concurrent.futures import ThreadPoolExecutor, as_completed
      from pathlib import Path
      from typing import Any
      
      MODEL = "gpt-image-2"
      
      _print_lock = threading.Lock()
      
      
      def parse_args() -> argparse.Namespace:
          p = argparse.ArgumentParser(
              description="Generate or edit images with OpenAI gpt-image-2. "
                          "Always uses gpt-image-2; fails fast on any error."
          )
          p.add_argument("--prompt", help="Text prompt describing the image. Required unless --batch is used.")
          p.add_argument(
              "--image",
              action="append",
              default=[],
              help="Path to a reference image. Pass multiple times for multiple inputs. "
                   "If provided, uses the edits endpoint.",
          )
          p.add_argument("--mask", help="Optional mask PNG (must match first image size, with alpha channel).")
          p.add_argument("--out", default="./image.png", help="Output file path. For --n>1, an index suffix is added.")
          p.add_argument("--size", default="auto", help="Image size, e.g. 1024x1024 or 'auto'.")
          p.add_argument("--quality", default="auto", choices=["low", "medium", "high", "auto"])
          p.add_argument("--format", dest="output_format", default="png", choices=["png", "jpeg", "webp"])
          p.add_argument("--n", type=int, default=1, help="Number of variations of the same prompt to generate in one call.")
          p.add_argument(
              "--batch",
              help="Path to a JSON file describing multiple jobs. Each job runs in parallel. "
                   "Mutually exclusive with --prompt/--image/--mask/--out.",
          )
          p.add_argument(
              "--concurrency",
              type=int,
              default=4,
              help="Max parallel workers in --batch mode (default 4).",
          )
          return p.parse_args()
      
      
      def die(msg: str, code: int = 1) -> "None":
          print(f"ERROR: {msg}", file=sys.stderr)
          sys.exit(code)
      
      
      def write_image(b64: str, out_path: Path, idx: int, total: int) -> Path:
          if total > 1:
              out_path = out_path.with_name(f"{out_path.stem}_{idx + 1}{out_path.suffix}")
          out_path.parent.mkdir(parents=True, exist_ok=True)
          out_path.write_bytes(base64.b64decode(b64))
          return out_path
      
      
      def run_job(client: Any, job: dict) -> list[Path]:
          """Run a single image job and return the written paths."""
          prompt = job.get("prompt")
          if not prompt:
              raise ValueError("job missing 'prompt'")
      
          out = job.get("out", "./image.png")
          size = job.get("size", "auto")
          quality = job.get("quality", "auto")
          output_format = job.get("format", "png")
          n = int(job.get("n", 1))
          images = job.get("image") or []
          if isinstance(images, str):
              images = [images]
          mask = job.get("mask")
      
          common: dict[str, Any] = {
              "model": MODEL,
              "prompt": prompt,
              "size": size,
              "quality": quality,
              "n": n,
          }
          if output_format != "png":
              common["output_format"] = output_format
      
          open_files: list = []
          try:
              if images:
                  files = [open(p, "rb") for p in images]
                  open_files.extend(files)
                  kwargs = dict(common, image=files if len(files) > 1 else files[0])
                  if mask:
                      mf = open(mask, "rb")
                      open_files.append(mf)
                      kwargs["mask"] = mf
                  result = client.images.edit(**kwargs)
              else:
                  result = client.images.generate(**common)
          finally:
              for f in open_files:
                  try:
                      f.close()
                  except Exception:
                      pass
      
          out_base = Path(out)
          written: list[Path] = []
          for idx, item in enumerate(result.data):
              b64 = item.b64_json
              if not b64:
                  raise RuntimeError(f"image {idx} for out={out} returned no b64_json payload.")
              written.append(write_image(b64, out_base, idx, len(result.data)))
      
          if not written:
              raise RuntimeError(f"API returned no images for out={out}.")
          return written
      
      
      def main() -> int:
          args = parse_args()
      
          if not os.environ.get("OPENAI_API_KEY"):
              die("OPENAI_API_KEY is not set in the environment. Aborting.", code=2)
      
          try:
              from openai import OpenAI
          except ImportError:
              die("openai package not installed. Run: pip install --upgrade openai", code=2)
      
          client = OpenAI()
      
          if args.batch:
              if args.prompt or args.image or args.mask:
                  die("--batch is mutually exclusive with --prompt/--image/--mask.", code=2)
              try:
                  jobs = json.loads(Path(args.batch).read_text())
              except Exception as e:
                  die(f"failed to read --batch file {args.batch}: {e}", code=2)
              if not isinstance(jobs, list) or not jobs:
                  die("--batch file must contain a non-empty JSON array of job objects.", code=2)
      
              concurrency = max(1, int(args.concurrency))
              all_paths: list[Path] = []
              first_error: BaseException | None = None
      
              with ThreadPoolExecutor(max_workers=concurrency) as ex:
                  futures = {ex.submit(run_job, client, job): i for i, job in enumerate(jobs)}
                  for fut in as_completed(futures):
                      i = futures[fut]
                      try:
                          paths = fut.result()
                      except BaseException as e:
                          if first_error is None:
                              first_error = e
                          with _print_lock:
                              print(f"ERROR: job {i} failed: {e}", file=sys.stderr)
                          continue
                      with _print_lock:
                          for path in paths:
                              print(str(path.resolve()))
                      all_paths.extend(paths)
      
              if first_error is not None:
                  return 1
              if not all_paths:
                  die("batch produced no images.")
              return 0
      
          if not args.prompt:
              die("--prompt is required (or use --batch).", code=2)
      
          job = {
              "prompt": args.prompt,
              "out": args.out,
              "size": args.size,
              "quality": args.quality,
              "format": args.output_format,
              "n": args.n,
              "image": list(args.image),
              "mask": args.mask,
          }
          try:
              written = run_job(client, job)
          except Exception as e:
              die(str(e))
      
          for path in written:
              print(str(path.resolve()))
          return 0
      
      
      if __name__ == "__main__":
          sys.exit(main())
      
  • .env.example 348 B · in bundle
  • SKILL.md 14.7 KB
    ---
    name: generating-images
    description: >-
      Generate or edit images using the OpenAI Image API (gpt-image-2). Use when
      the user asks to generate, create, draw, render, illustrate, mock up, or edit
      an image, icon, logo, mockup, illustration, OG image, blog hero, marketing
      asset, or similar visual. Also use when the user supplies a reference image
      and asks to modify, restyle, or remix it. Triggers on: "generate an image",
      "create an image", "make a picture of", "edit this image", "restyle this",
      "make a mockup of", "draw a", "render a", "illustration of".
    user-invocable: true
    ---
    
    # Generating Images (OpenAI gpt-image-2)
    
    Use this skill any time the user asks to generate or edit an image. It wraps
    OpenAI's `gpt-image-2` model via a Python script, supports both text-only
    prompts and one-or-more reference images, and writes the resulting PNG/JPEG/WebP
    to disk.
    
    ## Hard rules (do not violate)
    
    1. **Always use `gpt-image-2`.** Never fall back to `gpt-image-1`, `dall-e-3`,
       or any other model. The script has no `--model` flag for this reason.
    2. **Fail fast on any error.** Do not retry, do not swap models, do not patch
       around missing credentials, do not silently degrade quality. If the script
       exits non-zero, surface the error to the user verbatim and stop.
    3. **Do not "fix" a missing `OPENAI_API_KEY`** by reading from `.env` files,
       1Password, etc. unless the user explicitly tells you to. If the env var is
       missing, ask the user how they want to provide it (or to `export` it) and
       then stop.
    
    ## When to use
    
    - User asks for a generated image: icon, logo, illustration, mockup, OG image,
      blog hero, marketing asset, concept art, diagram-style image, etc.
    - User provides one or more images and asks to edit, restyle, combine, or use
      them as references.
    - User asks to remove/replace part of an image (use `--mask`).
    
    Do **not** use this skill for:
    - Charts/plots/data viz (generate via code instead).
    - Sourcing existing photos (use a stock photo skill if available).
    - Screenshots of the user's app (use a screenshot skill if available).
    
    ## Prerequisites
    
    ### 1. OpenAI API key
    
    You need an `OPENAI_API_KEY` exported in your environment. Get one at
    [platform.openai.com/api-keys](https://platform.openai.com/api-keys).
    
    The skill ships with a `.env.example` next to this `SKILL.md`. Copy it and
    fill in your key:
    
    ```bash
    cp .env.example .env
    # then edit .env and put your real key in
    ```
    
    Then export it before running the script:
    
    ```bash
    set -a && source .env && set +a
    ```
    
    Or just export it directly in your shell:
    
    ```bash
    export OPENAI_API_KEY="sk-..."
    ```
    
    If `OPENAI_API_KEY` is not set, the script exits with code 2 immediately.
    **Do not** try to read it from anywhere else without the user's explicit
    permission.
    
    ### 2. Org verification
    
    Your OpenAI org must be verified for `gpt-image-2` at
    [platform.openai.com/settings/organization/general](https://platform.openai.com/settings/organization/general).
    If you see a 403 mentioning "organization must be verified", surface it and
    stop — do not switch models.
    
    ### 3. Python dependency
    
    ```bash
    pip install --upgrade openai
    ```
    
    ## Script location
    
    The Python script lives next to this `SKILL.md` at `scripts/generate_image.py`.
    When this skill is installed at `~/.cursor/skills/generating-images/`, the
    script will be at `~/.cursor/skills/generating-images/scripts/generate_image.py`.
    
    It prints the absolute path(s) of the written image(s) to stdout. Errors go
    to stderr with a non-zero exit code, and the script exits immediately on the
    first error.
    
    ## How to invoke
    
    Always run via the Shell tool. Pick a sensible output path inside the user's
    current workspace (e.g. `./public/generated/<slug>.png` for web projects, or
    `./<slug>.png` otherwise).
    
    ### 1. Text-to-image
    
    ```bash
    python3 ~/.cursor/skills/generating-images/scripts/generate_image.py \
      --prompt "Minimal flat-vector app icon for a note-taking app, indigo gradient, rounded square, soft shadow" \
      --size 1024x1024 \
      --quality high \
      --out ./icon.png
    ```
    
    ### 2. Image-to-image (one reference)
    
    ```bash
    python3 ~/.cursor/skills/generating-images/scripts/generate_image.py \
      --prompt "Restyle this photo as a watercolor painting with warm tones" \
      --image ./photo.jpg \
      --out ./photo-watercolor.png
    ```
    
    ### 3. Multiple reference images
    
    ```bash
    python3 ~/.cursor/skills/generating-images/scripts/generate_image.py \
      --prompt "Photorealistic flat-lay product shot combining all of these items on a white background" \
      --image ./a.png --image ./b.png --image ./c.png \
      --out ./flatlay.png
    ```
    
    ### 4. Masked edit (inpainting)
    
    The mask must be the same size and format as the first input image, with an
    alpha channel marking the editable region.
    
    ```bash
    python3 ~/.cursor/skills/generating-images/scripts/generate_image.py \
      --prompt "Replace the sky with a vivid sunset" \
      --image ./scene.png --mask ./sky-mask.png \
      --out ./scene-sunset.png
    ```
    
    ### 5. Batch / parallel mode (many distinct images at once)
    
    When you need to generate **multiple different images** in one go (e.g. a set
    of blog heroes, several icon variations with different prompts, OG images for
    many pages), use `--batch` instead of running the script N times. It runs all
    jobs in parallel from a single Python process — much faster than serial calls
    and avoids repeated SDK startup cost.
    
    Write a JSON file describing every job, then call the script once:
    
    ```bash
    cat > /tmp/img-jobs.json <<'EOF'
    [
      {
        "prompt": "Minimal flat-vector app icon for a note-taking app, indigo gradient, rounded square",
        "out": "./public/icons/notes.png",
        "size": "1024x1024",
        "quality": "high"
      },
      {
        "prompt": "Photoreal blog hero: a cozy library with warm afternoon light, 5:3 ratio",
        "out": "./public/static/blog/library.png",
        "size": "1600x960",
        "quality": "medium"
      },
      {
        "prompt": "Restyle this product photo as a watercolor painting with warm tones",
        "image": ["./public/products/mug.jpg"],
        "out": "./public/products/mug-watercolor.png"
      }
    ]
    EOF
    
    python3 ~/.cursor/skills/generating-images/scripts/generate_image.py \
      --batch /tmp/img-jobs.json --concurrency 5
    ```
    
    Each job object accepts the same fields as the CLI flags: `prompt` (required),
    `out`, `size`, `quality`, `format`, `n`, `image` (string or array of strings),
    `mask`. Defaults match the single-shot CLI.
    
    Behavior:
    
    - All jobs run concurrently up to `--concurrency` (default 4). A reasonable
      range is 3–8; OpenAI rate-limits per org so don't go too wild.
    - Each successfully written image's absolute path is printed to stdout as soon
      as that job finishes, one per line.
    - If any job fails, its error is printed to stderr (`ERROR: job <i> failed: ...`)
      and the script exits with code 1 **after** the remaining jobs finish. Other
      jobs are not cancelled — partial output is fine and you can retry only the
      failed ones.
    - `--batch` is mutually exclusive with `--prompt` / `--image` / `--mask`.
    
    **When to prefer `--batch` over parallel Shell calls:** any time you're
    generating ≥2 distinct images in the same turn. Don't fire multiple parallel
    Shell invocations of this script — use one batch call instead.
    
    **Don't confuse with `--n`.** `--n` produces multiple variations of the *same*
    prompt in a single API call (cheaper, but all the same idea). `--batch` runs
    *different* prompts in parallel. They can be combined: a batch job can set
    `"n": 4` to get 4 variations of that one prompt.
    
    ## Flags reference
    
    | Flag | Default | Notes |
    |------|---------|-------|
    | `--prompt` | required* | Required unless `--batch` is used. Always include, even when editing. |
    | `--image` | none | Pass multiple times for multiple references. Triggers `images.edit`. |
    | `--mask` | none | Optional inpainting mask (PNG with alpha). |
    | `--out` | `./image.png` | Output path; index suffix added when `--n > 1`. |
    | `--size` | `auto` | `1024x1024`, `1536x1024`, `1024x1536`, `2048x2048`, `3840x2160`, etc. Edges must be multiples of 16, max 3840px, ratio ≤ 3:1. |
    | `--quality` | `auto` | `low` (fast drafts), `medium`, `high` (final assets). |
    | `--format` | `png` | `png`, `jpeg`, `webp`. |
    | `--n` | `1` | Variations of the SAME prompt in one call. |
    | `--batch` | none | Path to JSON array of job objects; runs them in parallel. |
    | `--concurrency` | `4` | Max parallel workers in `--batch` mode. |
    
    There is intentionally **no `--model` flag**. The model is hardcoded to
    `gpt-image-2`.
    
    ## Sizing guidance
    
    - App icons / square thumbnails → `1024x1024`
    - Landing-page heroes / OG images → `1536x1024`
    - Blog hero (5:3) → `1600x960` (both edges multiples of 16, ratio = 5:3)
    - Mobile / portrait illustrations → `1024x1536`
    - Marketing posters / 4K assets → `3840x2160`
    
    ## Quality guidance
    
    - `low` for quick exploration / drafts (cheapest, fastest).
    - `medium` is a good default.
    - `high` only for final, ship-ready assets — significantly more expensive
      and can take up to ~2 minutes.
    
    If the user just says "generate an image" with no signal of finality, default
    to `--quality medium`.
    
    ## Prompt-writing tips
    
    For best results, include in the prompt:
    - Subject (what is in the image)
    - Style (flat vector, watercolor, photoreal, isometric, line drawing, 3D render…)
    - Composition / camera (close-up, top-down, wide shot)
    - Color palette / mood
    - Background (white, gradient, scene — note: `gpt-image-2` does not support
      transparent backgrounds)
    - Any text that must appear, in quotes (`gpt-image-2` renders text well)
    
    If the user gives a vague prompt, expand it with sensible defaults rather than
    asking back, unless the request is genuinely ambiguous.
    
    ## After generating
    
    1. Print the output path back to the user.
    2. Do **not** embed the image in markdown — Cursor displays generated files
       automatically when they are written into the workspace.
    3. If the result is meant for a website/app, consider also running it through
       an optimizer (e.g. `pngquant`, `cwebp`) when file size matters.
    
    ## Gather context BEFORE generating
    
    Unless the user has spelled out exactly what they want (subject, style, palette,
    size, destination), do a quick context-gathering pass first. The goal is for
    the generated image to feel like it belongs where it's going, not like a
    random asset dropped into the project. Skipping this step is the #1 way this
    skill produces off-brand results.
    
    Things to look at, in roughly this order:
    
    1. **Sibling images at the destination.** If the image will live in
       `public/static/blog/`, `public/static/marketing/`, `assets/`, etc., open
       one or two existing images in that folder with the Read tool. Match their:
       - Illustration style (3D cartoon, flat vector, photoreal, line art, isometric…)
       - Color palette and lighting
       - Subject conventions (e.g. "always features the product mascot", "always a
         metaphor, never literal screenshots", etc.)
       - Aspect ratio and resolution
    
    2. **The surface that will display it.** Read the relevant file:
       - Blog post → read the MDX/Markdown (title, tags, opening paragraphs, key metaphors).
       - Landing page section → read the component, headline, and surrounding copy.
       - README → read the top of the README.
       - Component → read the component to understand what it represents.
    
       Pull the image's *meaning* from the actual content, not just the filename.
    
    3. **Brand / design tokens.** If the project has a clearly defined palette,
       logo, or mascot, mirror them. Quick places to check:
       - `tailwind.config.*` for brand colors
       - `globals.css` / theme files for CSS variables
       - `public/` for logos / mascot assets
       - Any existing OG images or marketing assets
    
    4. **Aspect ratio / size.** Pick `--size` based on the surface:
       blog hero, OG image, square avatar, mobile portrait, etc. Match what's
       already there.
    
    Then write the prompt incorporating what you learned: subject pulled from the
    content, style + palette pulled from sibling assets and brand tokens,
    composition matched to the surface.
    
    If the user *did* give explicit direction (style, colors, exact subject),
    honor it and skip context-gathering. If they gave partial direction, gather
    context for the parts they left open.
    
    Don't ask the user clarifying questions for things you can reasonably infer
    from the codebase — infer first, ask only when something is genuinely
    ambiguous (e.g. two equally valid styles already exist in the project).
    
    ## Place it AND wire it up — don't just dump a file
    
    When the user asks for an image for a specific surface (a blog post, a landing
    page, an OG card, a README, a component, etc.), you are responsible for the
    whole job, not just the PNG. Always do these in order:
    
    1. **Pick the correct on-disk location** for that surface. Look at what already
       exists and match it. Examples:
       - Blog hero → wherever existing blog images live (e.g.
         `apps/<app>/public/static/blog/<slug>.png`).
       - Landing page asset → wherever other landing assets live (e.g.
         `apps/<app>/public/static/marketing/...`).
       - README / docs image → `docs/images/`, `assets/`, or next to the doc.
       - Component-specific asset → next to the component or in its
         `public/`/`assets/` folder.
    
       Use the file's slug, component name, or section name for the filename. Don't
       invent a new convention if one already exists.
    
    2. **Wire the image up** so it actually shows where the user wanted it. This is
       not optional. Examples:
       - Blog post MDX → update the `image:` (or equivalent) frontmatter field to
         point at the new path. Replace any placeholder Unsplash/stock URL.
       - Landing page section → import or reference the new asset in the relevant
         component/JSX.
       - OG image → update the `<meta property="og:image">` / metadata config.
       - README → add the appropriate Markdown image tag.
    
    3. **Match existing conventions** for paths (relative vs `/static/...` vs
       `@/assets/...`), file format (png/webp/jpg), and any wrapper components
       (`next/image`, custom `<Image>`, etc.).
    
    4. **Don't ask first.** If the user asked for an image for a known surface, do
       the placement + wiring automatically and tell them what you changed at the
       end. Only ask when the destination is genuinely ambiguous.
    
    ## Errors — surface, don't hide
    
    If any of the following happen, **stop immediately** and report the error to
    the user. Do not retry, do not change the model, do not change the prompt.
    
    - `OPENAI_API_KEY is not set` → ask the user how to provide it.
    - `openai package not installed` → tell the user to run `pip install --upgrade openai`.
    - 403 "organization must be verified" → tell the user to verify at
      [platform.openai.com/settings/organization/general](https://platform.openai.com/settings/organization/general).
      Do not switch models.
    - 400 size error → report it; let the user pick a valid size.
    - 400 about transparent background → report it; `gpt-image-2` doesn't
      support transparency.
    - Any other API error → report verbatim and stop.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related