
Nano Banana Pro Openrouter
- 9.8k installs
- 37.1k repo stars
- Updated July 28, 2026
- github/awesome-copilot
How do I generate, edit, or composite images programmatically using a managed API and advanced generative model?
About
Nano Banana Pro OpenRouter wraps the google/gemini-3-pro-image-preview model for prompt-driven image generation, single-image editing, and multi-image composition. Developers invoke it via CLI commands using uv to generate cinematic scenes, edit existing images with text prompts, or combine up to 3 source images into composites. The skill reads optional system prompts from assets/SYSTEM_TEMPLATE for customization and outputs images with MEDIA: path logging; supports 1K/2K/4K resolutions and persists multiple results with auto-indexed filenames.
- Prompt-only image generation and single-image editing with text instructions
- Multi-image composition: combine up to 3 input images into one output
- Selectable resolution: 1K, 2K, or 4K output sizes
- System prompt customization via assets/SYSTEM_TEMPLATE without code changes
- CLI-driven via uv with OPENROUTER_API_KEY authentication and MEDIA: logging
Nano Banana Pro Openrouter by the numbers
- 9,790 all-time installs (skills.sh)
- +24 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #84 of 1,340 Generative Media skills by installs in the Skillselion catalog
- Security screen: CRITICAL risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
nano-banana-pro-openrouter capabilities & compatibility
- Capabilities
- prompt driven image generation · single image editing via text prompts · multi image composition · resolution selection (1k/2k/4k) · system prompt customization · batch filename indexing
- Use cases
- image generation · ui design · copywriting
- Platforms
- macOS · Windows · Linux · WSL
- Runs
- Remote server
- Pricing
- Bring your own API key
What nano-banana-pro-openrouter says it does
Generate or edit images via OpenRouter with the Gemini 3 Pro Image model. Use for prompt-only image generation, image edits, and multi-image compositing; supports 1K/2K/4K output.
The skill reads an optional system prompt from `assets/SYSTEM_TEMPLATE`. This allows you to customize the image generation behavior without modifying code.
npx skills add https://github.com/github/awesome-copilot --skill nano-banana-pro-openrouterAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 9.8k |
|---|---|
| repo stars | ★ 37.1k |
| Security audit | 1 / 3 scanners passed |
| Last updated | July 28, 2026 |
| Repository | github/awesome-copilot ↗ |
What it does
Generate, edit, and composite images via OpenRouter's Gemini 3 Pro model at 1K/2K/4K resolution.
Who is it for?
Rapid image generation, editing, and composition in development scripts or agent workflows; no GPU setup required.
Skip if: Real-time interactive image editing GUIs; batch processing at massive scale; models requiring specialized hardware.
When should I use this skill?
Need to generate marketing visuals, edit reference images, or composite scene elements as part of a build or agent task.
What you get
Images are generated or edited on-demand via OpenRouter CLI, saved to disk with metadata logging, and support multi-image workflows.
- Generated or edited PNG image(s) saved to specified --filename path
- MEDIA: <path> log for each saved image
By the numbers
- Supports up to 3 input images per composition
- 3 resolution tiers: 1K, 2K, 4K
Files
Nano Banana Pro OpenRouter
Overview
Generate or edit images with OpenRouter using the google/gemini-3-pro-image-preview model. Support prompt-only generation, single-image edits, and multi-image composition.
Prompt-only generation
uv run {baseDir}/scripts/generate_image.py \
--prompt "A cinematic sunset over snow-capped mountains" \
--filename sunset.pngEdit a single image
uv run {baseDir}/scripts/generate_image.py \
--prompt "Replace the sky with a dramatic aurora" \
--input-image input.jpg \
--filename aurora.pngCompose multiple images
uv run {baseDir}/scripts/generate_image.py \
--prompt "Combine the subjects into a single studio portrait" \
--input-image face1.jpg \
--input-image face2.jpg \
--filename composite.pngResolution
- Use
--resolutionwith1K,2K, or4K. - Default is
1Kif not specified.
System prompt customization
The skill reads an optional system prompt from assets/SYSTEM_TEMPLATE. This allows you to customize the image generation behavior without modifying code.
Behavior and constraints
- Accept up to 3 input images via repeated
--input-image. --filenameaccepts relative paths (saves to current directory) or absolute paths.- If multiple images are returned, append
-1,-2, etc. to the filename. - Print
MEDIA: <path>for each saved image. Do not read images back into the response.
Troubleshooting
If the script exits non-zero, check stderr against these common blockers:
| Symptom | Resolution |
|---|---|
OPENROUTER_API_KEY is not set | Ask the user to set it. PowerShell: $env:OPENROUTER_API_KEY = "sk-or-..." / bash: export OPENROUTER_API_KEY="sk-or-..." |
uv: command not found or not recognized | macOS/Linux: <code>curl -LsSf https://astral.sh/uv/install.sh | sh</code>. Windows: <code>powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"</code>. Then restart the terminal. |
AuthenticationError / HTTP 401 | Key is invalid or has no credits. Verify at <https://openrouter.ai/settings/keys>. |
For transient errors (HTTP 429, network timeouts), retry once after 30 seconds. Do not retry the same error more than twice — surface the issue to the user instead.
You are a visionary image‑creation artist with a poetic, dreamlike imagination.
Your role is to transform any user request—whether highly detailed or very minimal—into a vivid, concrete, and model‑ready image description.
When information is missing, infer the user's intent in a gentle and intuitive way (such as creating a character portrait, sticker design, sci‑fi avatar, creature concept, etc.).
If the user does not specify an art style, you may offer subtle optional suggestions (for example, "soft illustration," "minimal line style," or "playful entertainment‑meme style") without imposing them.
Your responsibilities:
- Ensure any text appearing in the image matches the user's language (unless explicitly specified otherwise)
- Create visually compelling and technically excellent images
- Pay attention to composition, lighting, color, and visual balance
- Follow the user's specific style preferences and requirements
- For image edits, preserve the original context while making requested modifications
- For multi-image composition, seamlessly blend subjects into cohesive results
Remember: Output only the generated image without additional commentary.
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "openai",
# ]
# ///
"""
Generate or edit images via OpenRouter using openai-python.
"""
import argparse
import base64
import mimetypes
import os
from pathlib import Path
from openai import OpenAI
# Configuration
MAX_INPUT_IMAGES = 3
MIME_TO_EXT = {
"image/png": ".png",
"image/jpeg": ".jpg",
"image/jpg": ".jpg",
"image/webp": ".webp",
}
def parse_args():
parser = argparse.ArgumentParser(description="Generate or edit images via OpenRouter.")
parser.add_argument("--prompt", required=True, help="Prompt describing the desired image.")
parser.add_argument("--filename", required=True, help="Output filename (relative to CWD).")
parser.add_argument(
"--resolution",
type=str.upper,
choices=["1K", "2K", "4K"],
default="1K",
help="Output resolution: 1K, 2K, or 4K.",
)
parser.add_argument(
"--input-image",
action="append",
default=[],
help=f"Optional input image path (repeatable, max {MAX_INPUT_IMAGES}).",
)
return parser.parse_args()
def require_api_key():
api_key = os.environ.get("OPENROUTER_API_KEY")
if not api_key:
raise SystemExit("OPENROUTER_API_KEY is not set in the environment.")
return api_key
def encode_image_to_data_url(path: Path) -> str:
if not path.exists():
raise SystemExit(f"Input image not found: {path}")
mime, _ = mimetypes.guess_type(str(path))
if not mime:
mime = "image/png"
data = path.read_bytes()
encoded = base64.b64encode(data).decode("utf-8")
return f"data:{mime};base64,{encoded}"
def build_message_content(prompt: str, input_images: list[str]) -> list[dict]:
content: list[dict] = [{"type": "text", "text": prompt}]
for image_path in input_images:
data_url = encode_image_to_data_url(Path(image_path))
content.append({"type": "image_url", "image_url": {"url": data_url}})
return content
def parse_data_url(data_url: str) -> tuple[str, bytes]:
if not data_url.startswith("data:") or ";base64," not in data_url:
raise SystemExit("Image URL is not a base64 data URL.")
header, encoded = data_url.split(",", 1)
mime = header[5:].split(";", 1)[0]
try:
raw = base64.b64decode(encoded)
except Exception as e:
raise SystemExit(f"Failed to decode base64 image payload: {e}")
return mime, raw
def resolve_output_path(filename: str, image_index: int, total_count: int, mime: str) -> Path:
output_path = Path(filename)
suffix = output_path.suffix
# Validate/correct suffix matches MIME type
expected_suffix = MIME_TO_EXT.get(mime, ".png")
if suffix and suffix.lower() != expected_suffix.lower():
print(f"Warning: filename extension '{suffix}' doesn't match returned MIME type '{mime}'. Using '{expected_suffix}' instead.")
suffix = expected_suffix
elif not suffix:
suffix = expected_suffix
# Single image: use original stem + corrected suffix
if total_count <= 1:
return output_path.with_suffix(suffix)
# Multiple images: append numbering
return output_path.with_name(f"{output_path.stem}-{image_index + 1}{suffix}")
def extract_image_url(image: dict | object) -> str | None:
if isinstance(image, dict):
return image.get("image_url", {}).get("url") or image.get("url")
return None
def load_system_prompt():
"""Load system prompt from assets/SYSTEM_TEMPLATE if it exists and is not empty."""
script_dir = Path(__file__).parent.parent
template_path = script_dir / "assets" / "SYSTEM_TEMPLATE"
if template_path.exists():
content = template_path.read_text(encoding="utf-8").strip()
if content:
return content
return None
def main():
args = parse_args()
if len(args.input_image) > MAX_INPUT_IMAGES:
raise SystemExit(f"Too many input images: {len(args.input_image)} (max {MAX_INPUT_IMAGES}).")
image_size = args.resolution
client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=require_api_key())
# Build messages with optional system prompt
messages = []
system_prompt = load_system_prompt()
if system_prompt:
messages.append({
"role": "system",
"content": system_prompt,
})
messages.append({
"role": "user",
"content": build_message_content(args.prompt, args.input_image),
})
response = client.chat.completions.create(
model="google/gemini-3-pro-image-preview",
messages=messages,
extra_body={
"modalities": ["image", "text"],
# https://openrouter.ai/docs/guides/overview/multimodal/image-generation#image-configuration-options
"image_config": {
# "aspect_ratio": "16:9",
"image_size": image_size,
}
},
)
message = response.choices[0].message
images = getattr(message, "images", None)
if not images:
raise SystemExit("No images returned by the API.")
# Create output directory once before processing images
output_base_path = Path(args.filename)
if output_base_path.parent and str(output_base_path.parent) != '.':
output_base_path.parent.mkdir(parents=True, exist_ok=True)
saved_paths = []
for idx, image in enumerate(images):
image_url = extract_image_url(image)
if not image_url:
raise SystemExit("Image payload missing image_url.url.")
mime, raw = parse_data_url(image_url)
output_path = resolve_output_path(args.filename, idx, len(images), mime)
output_path.write_bytes(raw)
saved_paths.append(output_path.resolve())
for path in saved_paths:
print(f"Saved image to: {path}")
print(f"MEDIA: {path}")
if __name__ == "__main__":
main()
Related skills
How it compares
Use nano-banana-pro-openrouter when images must be produced through OpenRouter's Gemini endpoint inside agent scripts, not for general HTML/CSS mockups.
FAQ
What image models does this skill use?
It uses google/gemini-3-pro-image-preview via OpenRouter. Supports prompt-only generation, single-image edits, and multi-image composition.
How many input images can I combine?
Up to 3 input images via repeated --input-image flags. Output appends -1, -2, etc. if multiple results are returned.
What resolution options are available?
1K, 2K, or 4K via --resolution flag. Default is 1K if not specified.
Is Nano Banana Pro Openrouter safe to install?
skills.sh reports 1 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.