Skip to content
Back to skills

Product Shots Image Gen

DSecurity

Unified image-generation engine for the product-shots ecosystem. Dispatches to the right model family (OpenAI gpt-image-2 / Gemini gemini-3-pro-image-preview Nano Banana Pro) with one parameterised script for text-to-image and image-to-image. Primary backend: OmniMaaS / Cloubic gateway (https://api.omnimaas.com/v1); also supports any OpenAI-SDK-compatible gateway via fallback env vars. Auto-resizes oversized reference images to prevent edge-proxy timeouts. Use when the user says "generate an ...

  • 642 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 22, 2026
ai-agentspythonrustgoshellbashgitapibackendsecurity

Works with

  • claude code
  • cursor
  • terminal
  • cli
  • api

Security analysis

D50/100
  • criticalModifies startup scripts or system services for persistence
  • criticalModifies startup scripts or system services for persistence

Pro shows the line behind each finding and how to fix it

Scanned September 22, 2026

npx -y skills add majiayu000/claude-skill-registry --skill product-shots-image-gen --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Product Shots Image Gen?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Product Shots Image Gen
[![Security: D — Skills Directory](https://www.skillsdirectory.com/api/skills/majiayu000-product-shots-image-gen/badge)](https://www.skillsdirectory.com/skills/majiayu000-product-shots-image-gen)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: product-shots-image-gen
description: 'Unified image-generation engine for the product-shots ecosystem. Dispatches to the right model family (OpenAI gpt-image-2 / Gemini gemini-3-pro-image-preview Nano Banana Pro) with one parameterised script for text-to-image and image-to-image. Primary backend: OmniMaaS / Cloubic gateway (https://api.omnimaas.com/v1); also supports any OpenAI-SDK-compatible gateway via fallback env vars. Auto-resizes oversized reference images to prevent edge-proxy timeouts. Use when the user says "generate an image", "make a picture", "create a visual", "render this", "edit this image", "做一张图", "生成图片", "改这张图" — or when invoked as the image-generation backend by another product-shots skill (main-image / detail-page / ad-creative / social-post / multi-angle). NOT a creative-direction skill: takes a prompt + optional reference images, returns a file path.'
license: MIT
metadata:
  author: motiful
  source: product-shots ecosystem
  skill_id: product_shots_image_gen
  version: "1.0"
---

# Image Gen

The **image-generation engine** of the product-shots ecosystem. A single, parameterised entry point: routes the request to the correct API endpoint based on model family (OpenAI's `/v1/images/generations` + `/v1/images/edits` vs Gemini's `/v1/chat/completions` multimodal), preprocesses reference images, and returns a saved file path.

**Primary backend: OmniMaaS / Cloubic image gateway** — `https://api.omnimaas.com/v1`. The gateway is OpenAI-SDK-compatible and unifies access to GPT image 2 and the Gemini Nano Banana family behind one auth token. See:

- GPT image 2 via OmniMaaS: https://docs.cloubic.com/docs/zh-CN/image-generation/image-openai
- Gemini via OmniMaaS: https://docs.cloubic.com/docs/zh-CN/image-generation/image-gemini

**Fallback backends:** any other OpenAI-SDK-compatible image gateway works — set `PRODUCT_SHOTS_IMAGEGEN_BASE_URL` + `PRODUCT_SHOTS_IMAGEGEN_API_KEY`, or keep the legacy `CANVASFLOW_IMAGEGEN_*` env vars for migrated installs.

## Onboarding (First-Use Setup)

**Before generating any image, this skill MUST verify an API gateway is configured.** If env vars are missing, surface the instructions below to the user verbatim — never silently fall back to a hard-coded key, never ask the user to fork the repo, never ask them to paste a key into chat.

### Detection logic

Run on every invocation. The resolution order:

1. `OMNIMAAS_API_KEY` (env var, preferred — unified Cloubic / OmniMaaS gateway)
2. `PRODUCT_SHOTS_IMAGEGEN_API_KEY` (env var, generic fallback)
3. `CANVASFLOW_IMAGEGEN_API_KEY` (env var, legacy compatibility)
4. `~/.product_shots_imagegen_api_key` (file, chmod 600, one-line key)
5. `~/.canvasflow_imagegen_api_key` (file, legacy)

If none resolve, **stop and emit the onboarding message** in the next section. Do **NOT** attempt the API call.

### Onboarding message (show to user verbatim when env missing)

> **This skill needs an image-gateway API key before it can generate.** You haven't configured one yet — here's the 30-second setup:
>
> **Option A — temporary (this terminal only):**
> ```bash
> export OMNIMAAS_API_KEY="sk-..."             # your OmniMaaS / Cloubic token
> # optional: only set this if you have a private gateway endpoint
> # export OMNIMAAS_BASE_URL="https://api.omnimaas.com/v1"
> ```
>
> **Option B — persistent across sessions:**
> Add the same `export` line to your `~/.zshrc` (or `~/.bashrc`), then `source ~/.zshrc`.
>
> **Option C — file-based (no env vars):**
> ```bash
> echo "sk-..." > ~/.product_shots_imagegen_api_key
> chmod 600 ~/.product_shots_imagegen_api_key
> ```
>
> **Where the key comes from:** [docs.cloubic.com](https://docs.cloubic.com) — get a token from the OmniMaaS / Cloubic dashboard. The same token covers both OpenAI `gpt-image-2` and Gemini `gemini-3-pro-image-preview` (Nano Banana Pro).
>
> Any OpenAI-SDK-compatible image gateway also works — replace `OMNIMAAS_*` with `PRODUCT_SHOTS_IMAGEGEN_BASE_URL` + `PRODUCT_SHOTS_IMAGEGEN_API_KEY` pointing at your gateway.
>
> Once configured, re-run the original request.

### Security invariants

- **Never** echo the full key. When confirming detection, show only the first 8 characters (`sk-wPfH6K…`).
- **Never** write the key to logs, stdout, or any file inside the repo.
- **Never** instruct the user to fork the repo, edit skill source, or paste their key into chat. The key lives in env / `~/.*_api_key` files only.
- API key is transmitted **only** via the `Authorization: Bearer <key>` header.

### Local-proxy bypass (CN users)

ClashX / Shadowsocks / similar local proxies inject `ALL_PROXY=socks5://...` into the environment, which breaks `requests` (`Missing dependencies for SOCKS support`). The bundled `scripts/generate.py` already handles this via `Session.trust_env = False`. If you implement a custom caller, replicate this pattern.

## Engagement Principles

These rules always apply. Read them before acting.

1. **Auto-detect model family — never ask the user.** The model name (e.g., `gpt-image-2` vs `gemini-3-pro-image-preview`) determines the endpoint and request shape. Caller specifies model; skill resolves family.
2. **Default to `gemini-3-pro-image-preview`** when the caller does not specify a model. It is the strongest single-model performer (Nano Banana Pro) and supports both text-to-image and image-to-image through one endpoint. See `references/model-selection.md`.
3. **Reference images MUST be ≤1024px max dimension AND ≤1MB before being sent.** Auto-resize otherwise. Reason: most edge proxies time out at ~100s; a 2.5MB PNG + base64 expansion + Gemini processing reliably triggers HTTP 524 at that ceiling. See `references/reference-image-handling.md`.
4. **Negative constraints are appended to the prompt as "Avoid: …", never sent as a separate parameter.** No supported model exposes a true `negative_prompt` field; this is by API design, not a workaround.
5. **Aspect ratio handling diverges by family.** OpenAI: translated to a pixel `size` param. Gemini: appended to the prompt text ("aspect ratio: X:Y") since chat-completions has no size field. Caller passes `--aspect-ratio`; skill handles both.
6. **Output is always saved to disk and the path returned.** Never return base64 or URL to the caller — the file on disk is the canonical artifact. Default location uses a timestamp; caller can override with `--output`.
7. **Cost and token usage are logged on every call.** Caller can budget. Estimated rates: gpt-image-2 ≈ ¥0.35/image, gemini-3-pro-image-preview ≈ ¥1/image, gemini-3.1-flash-image-preview ≈ ¥0.20/image.
8. **Bounded retries for image-to-image; fail-fast everywhere else.** Image-to-image calls retry up to **3 attempts** on retryable errors (HTTP 429, 5xx, 524, connection errors, read timeouts) with exponential backoff (1s, 4s, 16s between attempts). Text-to-image calls do **not** retry. Auth errors (401 / 403) fail fast — never retried. See `references/error-handling.md`.
9. **API keys never reach logs or stdout.** Loaded in this order: `OMNIMAAS_API_KEY` (preferred — Cloubic / OmniMaaS gateway) → `PRODUCT_SHOTS_IMAGEGEN_API_KEY` (canonical generic) → `RENDER_API_KEY` (short alias) → `CANVASFLOW_IMAGEGEN_API_KEY` (legacy) → `~/.product_shots_imagegen_api_key` → `~/.product_shots_render_api_key` (compat) → `~/.canvasflow_imagegen_api_key` (legacy). Passed only via the `Authorization` header. Never echoed. The base URL is auto-resolved (defaults to `https://api.omnimaas.com/v1` when `OMNIMAAS_API_KEY` is set without an explicit `OMNIMAAS_BASE_URL`).

## Execution Procedure

Follow this procedure exactly. Each step maps to a section of `scripts/generate.py`.

```
generate_image(prompt, model, [aspect_ratio], [negative_prompt],
               [reference_images], [output_path]) → file_path

# Step 0 — Resolve API key + base URL (MUST come first)
api_key,  key_source = load_api_key()
    # 1. OMNIMAAS_API_KEY env var (preferred — OmniMaaS gateway)
    # 2. PRODUCT_SHOTS_IMAGEGEN_API_KEY env var (canonical generic)
    # 3. RENDER_API_KEY env var (short alias)
    # 4. CANVASFLOW_IMAGEGEN_API_KEY env var (legacy)
    # 5. ~/.product_shots_imagegen_api_key file
    # 6. ~/.product_shots_render_api_key file (compat)
    # 7. ~/.canvasflow_imagegen_api_key file (legacy)
    # fail with clear message if none present
base_url, url_source = load_base_url()
    # 1. OMNIMAAS_BASE_URL env var
    # 2. PRODUCT_SHOTS_IMAGEGEN_BASE_URL env var (canonical generic)
    # 3. RENDER_BASE_URL env var (short alias)
    # 4. CANVASFLOW_IMAGEGEN_BASE_URL env var (legacy)
    # 5. https://api.omnimaas.com/v1 (default when OMNIMAAS_API_KEY is set)

# Step 0a — Validate caller arguments + fill caller-context defaults
args = validate_args(args)                       # see references/parameter-spec.md
    # rejects empty prompt, unknown model, invalid aspect_ratio
    # auto-fills size for OpenAI from aspect_ratio
    # warns when Gemini ignores --n / --size
args = lookup_caller_defaults(caller_skill, surface) | args
    # caller-skill-specific defaults from parameter-spec.md §Family-Specific Defaults
    # explicit args win over defaults (dict merge with args on the right)

# Step 1 — Resolve model + identify family
if not model:
    model = select_default_model(use_case)       # see references/model-selection.md
        # use_case from caller brief: "text-overlay" / "photorealistic" /
        # "creative" / "image-to-image" / "cost-sensitive" / "general" (default)
        # ⚠ "text-overlay" WINS over all others — any caller whose prompt
        # asks for on-image letters or digits (headlines, labels, CTAs,
        # price chips, callouts) MUST pass "text-overlay" so the dispatcher
        # routes to gpt-image-2. Gemini family garbles small text.
family = model_family(model)                     # see references/model-selection.md
    # openai  → {gpt-image-1, gpt-image-2, dall-e-3}
    # gemini  → {gemini-3-pro-image-preview, gemini-3.1-flash-image-preview, ...}
    # unknown → exit with the supported model list

# Step 2 — Preprocess reference images (if any)
for img_path in reference_images:
    img_path = maybe_resize(img_path, max_dim=1024, max_bytes=1MB)
    # Pillow thumbnail → temp file
    # passthrough if already within limits
    # see references/reference-image-handling.md

# Step 3 — Compose effective prompt and size
final_prompt = compose_prompt(prompt, negative_prompt, aspect_ratio, family)
    # Gemini: prompt + "(aspect ratio: X:Y)" + "Avoid: ..."
    # OpenAI: prompt + "Avoid: ..."  (aspect handled via size param instead)
if family == "openai":
    size = OPENAI_SIZE_BY_RATIO[aspect_ratio] OR explicit --size OR "1024x1024"

# Step 4 — Dispatch
if family == "openai":
    if reference_images:
        response = POST <base_url>/images/edits  (multipart, image[]=@file...)
    else:
        response = POST <base_url>/images/generations  (JSON, {model, prompt, n, size})
elif family == "gemini":
    response = POST <base_url>/chat/completions
        body: {model, messages:[{role:"user", content:[
            {type:"text", text: final_prompt},
            {type:"image_url", image_url:{url: data_url_per_ref_image}}, ...
        ]}]}

if response.status != 200:
    handle_http_error(family, response.status, response.body)
                                                 # see references/error-handling.md

# Step 5 — Parse response (family-specific)
if family == "openai":
    first = response.body["data"][0]
    if "b64_json" not in first and "url" not in first:
        classify_response_error(family, response.body)  # references/error-handling.md
    image_bytes = base64_decode(first["b64_json"]) if "b64_json" in first else fetch(first["url"])
    ext = "png"
elif family == "gemini":
    content = response.body["choices"][0]["message"]["content"]
    # content is a markdown string: ![image](data:image/jpeg;base64,...)
    if no "data:image/...;base64" pattern in content:
        classify_response_error(family, response.body)  # references/error-handling.md
    image_bytes = base64_decode(extract_data_url(content))
    ext = "jpeg"

# Step 6 — Save + log
out_path = output_path OR ./output-<unix_ts>.<ext>
write_bytes(out_path, image_bytes)
log(gateway_source, elapsed, file_size, total_tokens, out_path)

# Self-check
assert out_path.exists()
assert out_path.stat().st_size > 10_000        # tiny files = broken response
return out_path
```

## TOC of Module Files

- `references/model-selection.md` — Model catalogue (image models supported by this skill), family routing rules, decision tree for choosing the right model per use case (photorealism / text rendering / cheap-and-fast / CJK text rendering).
- `references/parameter-spec.md` — Every CLI flag of `scripts/generate.py`: name, type, default, applicability per family, valid range. Single source of truth for what callers can pass — including the full API key / base URL resolution order.
- `references/reference-image-handling.md` — The 524 finding, the 1024px / 1MB ceiling, multi-reference behaviour (Gemini ≤9 images per call), passthrough rule, why we use Pillow `thumbnail` (preserves aspect ratio).
- `references/error-handling.md` — Common API failure modes (401 / 429 / 524 / malformed response / connection timeout) with diagnostic messages and recommended caller behaviour. The 524 → resize-and-retry pattern.

## Section Index

```
Goal                                              → SKILL.md intro paragraph
Engagement Principles                             → SKILL.md §Engagement Principles
Execution Procedure                               → SKILL.md §Execution Procedure
Model Selection                                   → references/model-selection.md
   Family Routing
   Model Catalogue
   Decision Tree
Parameter Spec                                    → references/parameter-spec.md
   CLI Flags
   Aspect Ratio → OpenAI Size Mapping
   API Key + Base URL Resolution
Reference Image Handling                          → references/reference-image-handling.md
   The 524 Finding
   Auto-Resize Rule
   Multi-Reference Constraints
Error Handling                                    → references/error-handling.md
   Common Failure Modes
   Retry Patterns
```

## Tooling

- `scripts/generate.py` — the parameterised entry point. Stdlib + `requests` + `Pillow` only. No host capability requirements.
- `scripts/setup.sh` — idempotent dependency installer (Python 3 + pip + requests + Pillow). Run once before first use.
- **No** sub-skill calls. This skill is a leaf: it does not invoke other skills.
- Callers (other product-shots specialists) invoke via shell. Resolve the script path from the host's skill directory rather than hardcoding any platform path:
  ```
  # Path examples — pick whichever your harness uses; do NOT bake one in:
  #   Claude Code:  ~/.claude/skills/product-shots-image-gen/scripts/generate.py
  #   Codex:        ~/.agents/skills/product-shots-image-gen/scripts/generate.py
  #   Cursor:       ~/.cursor/skills/product-shots-image-gen/scripts/generate.py
  #   Windsurf:     ~/.codeium/windsurf/skills/product-shots-image-gen/scripts/generate.py
  #   Copilot:      ~/.copilot/skills/product-shots-image-gen/scripts/generate.py
  python <skill-dir>/scripts/generate.py \
      --prompt "..." --model gemini-3-pro-image-preview --output ./out.jpeg
  ```
  or via direct Python import of the `generate_openai` / `generate_gemini` functions.

## Caller Contract

When invoked from another skill in the product-shots ecosystem (e.g., `product-shots-main-image` reaching its `image = generate_image(prompt)` step), the caller passes:

| Field | Required | Format | Skill behaviour |
|---|---|---|---|
| `prompt` | yes | string, ≤4000 chars | passed through (+ negative + aspect ratio appended) |
| `model` | yes | from supported set | family-routed to correct endpoint |
| `aspect_ratio` | no | "1:1" / "16:9" / "9:16" / "4:3" / "3:4" / "3:2" / "2:3" | OpenAI: → `size`; Gemini: appended to prompt |
| `reference_images` | no | list of file paths | auto-resized to ≤1024px / ≤1MB before send |
| `negative_prompt` | no | string | appended as "Avoid: …" |
| `output` | no | file path | defaults to `./output-<ts>.<png\|jpeg>` |

Returns: file path on disk (PNG for OpenAI, JPEG for Gemini). Caller MUST treat the file as the canonical artifact.

## OmniMaaS gateway setup quickstart

```
# 1. Get an OmniMaaS / Cloubic API token from https://docs.cloubic.com
export OMNIMAAS_API_KEY="your-omnimaas-token"

# 2. (optional) Override the base URL if you have a private endpoint
# export OMNIMAAS_BASE_URL="https://api.omnimaas.com/v1"

# 3. Install dependencies
bash scripts/setup.sh

# 4. Test
python scripts/generate.py --prompt "a red apple on wood" \
                           --model gemini-3-pro-image-preview \
                           --output /tmp/test.jpeg
```

When `OMNIMAAS_API_KEY` is set, the script auto-defaults the base URL to `https://api.omnimaas.com/v1` — no additional configuration required.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…