
Nano Banana Pro
- 11 installs
- 82 repo stars
- Updated August 2, 2026
- aaaaqwq/claude-code-skills
nano-banana-pro is a Claude Code skill that generates and edits images using Google's Nano Banana Pro (Gemini 3 Pro Image) API.
About
nano-banana-pro is a Claude Code skill that generates and edits images using Google's Nano Banana Pro (Gemini 3 Pro Image) API. It optimizes the prompt first, drafts at low resolution, then renders a final at up to 4K, and can edit an existing image via --input-image. A developer uses it to create or modify images from the command line with automatic provider fallback.
- Generates and edits images with Google's Nano Banana Pro (Gemini 3 Pro Image) API
- Supports text-to-image and image-to-image at 1K/2K/4K with an optimize-then-generate workflow
- Falls back to a Boluobao provider when the primary API fails
Nano Banana Pro by the numbers
- 11 all-time installs (skills.sh)
- Ranked #1,051 of 1,337 Generative Media skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
nano-banana-pro capabilities & compatibility
Requires GEMINI_API_KEY (or --api-key), with a Boluobao fallback key.
- Capabilities
- image generation · image editing · prompt optimization
- Use cases
- image generation
- Pricing
- Bring your own API key
What nano-banana-pro says it does
Google's Nano Banana Pro API (Gemini 3 Pro Image)
Supports text-to-image + image-to-image
npx skills add https://github.com/aaaaqwq/claude-code-skills --skill nano-banana-proAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 11 |
|---|---|
| repo stars | ★ 82 |
| Last updated | August 2, 2026 |
| Repository | aaaaqwq/claude-code-skills ↗ |
What it does
Generate or edit images from text or an input image using the Nano Banana Pro (Gemini 3 Pro Image) API.
When should I use this skill?
The user asks to create or edit an image.
By the numbers
- 3 resolution options (1K/2K/4K)
- up to 14 input images for editing
Files
Nano Banana Pro Image Generation & Editing
Generate new images or edit existing ones using Google's Nano Banana Pro API (Gemini 3 Pro Image).
Usage
Run scripts using absolute path (do NOT cd to skill directory first).
⚡ MANDATORY: Always optimize prompt first before generating!
Step 1: Optimize Prompt (required)
OPTIMIZED=$(uv run ~/.openclaw/agents/content/agent/skills/nano-banana-pro/scripts/optimize_prompt.py \
--prompt "用户原始描述" \
--style photo|illustration|anime|oil-painting|3d|pixel|cinematic|watercolor)Step 2: Generate with optimized prompt
uv run ~/.openclaw/agents/content/agent/skills/nano-banana-pro/scripts/generate_image_boluobao.py \
--prompt "$OPTIMIZED" --filename "output.png" [--resolution 1k|2k|4k] [--aspect-ratio 16:9]Or using xingjiabiapi (primary):
uv run ~/.openclaw/agents/content/agent/skills/nano-banana-pro/scripts/generate_image.py \
--prompt "$OPTIMIZED" --filename "output.png" [--resolution 1K|2K|4K] [--api-key KEY]Edit existing image (skip optimizer):
uv run ~/.openclaw/agents/content/agent/skills/nano-banana-pro/scripts/generate_image.py \
--prompt "editing instructions" --filename "output.png" --input-image "path/to/input.png"Important: Always run from the user's current working directory.
Default Workflow (optimize → draft → iterate → final)
Goal: optimize prompt first, then fast iteration.
1. Optimize: Run optimize_prompt.py to enhance user's description 2. Draft (1K): Generate with optimized prompt at low res 3. Iterate: Adjust prompt or re-optimize if needed 4. Final (4K): Only when satisfied with the result
# Step 1: Optimize
OPTIMIZED=$(uv run ~/.openclaw/agents/content/agent/skills/nano-banana-pro/scripts/optimize_prompt.py -p "一只在樱花树下的猫" -s photo)
# Step 2: Draft
uv run ~/.openclaw/agents/content/agent/skills/nano-banana-pro/scripts/generate_image_boluobao.py -p "$OPTIMIZED" -f "draft.png" -r 1k
# Step 3: Final (after approval)
uv run ~/.openclaw/agents/content/agent/skills/nano-banana-pro/scripts/generate_image_boluobao.py -p "$OPTIMIZED" -f "final.png" -r 4kResolution Options
The Gemini 3 Pro Image API supports three resolutions (uppercase K required):
- 1K (default) - ~1024px resolution
- 2K - ~2048px resolution
- 4K - ~4096px resolution
Map user requests to API parameters:
- No mention of resolution →
1K - "low resolution", "1080", "1080p", "1K" →
1K - "2K", "2048", "normal", "medium resolution" →
2K - "high resolution", "high-res", "hi-res", "4K", "ultra" →
4K
API Key
Primary (xingjiabiapi / Google Gemini): 1. --api-key argument 2. GEMINI_API_KEY environment variable
Fallback (Boluobao 菠萝包): 1. --api-key argument 2. BOLUOBAO_API_KEY environment variable 3. pass show api/boluobao
If the primary provider fails (quota/403/timeout), automatically switch to Boluobao fallback.
Fallback: Boluobao Provider
When xingjiabiapi is unavailable, use the Boluobao script instead:
uv run ~/.openclaw/skills/nano-banana-pro/scripts/generate_image_boluobao.py \
--prompt "your description" \
--filename "output.jpg" \
--resolution 2k \
--aspect-ratio 16:9Boluobao-specific options:
--aspect-ratio: 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9--input-image: Pass image URLs (not local files) for editing, up to 14 images--model: Defaultgemini-3-pro-image-preview, can be changed if more models are added
Decision logic: 1. Try primary (xingjiabiapi) first 2. If fails → use generate_image_boluobao.py with same prompt/resolution
Preflight + Common Failures (fast fixes)
- Preflight:
command -v uv(must exist)test -n \"$GEMINI_API_KEY\"(or pass--api-key)- If editing:
test -f \"path/to/input.png\"
- Common failures:
Error: No API key provided.→ setGEMINI_API_KEYor pass--api-keyError loading input image:→ wrong path / unreadable file; verify--input-imagepoints to a real image- "quota/permission/403" style API errors → wrong key, no access, or quota exceeded; try a different key/account
Filename Generation
Generate filenames with the pattern: yyyy-mm-dd-hh-mm-ss-name.png
Format: {timestamp}-{descriptive-name}.png
- Timestamp: Current date/time in format
yyyy-mm-dd-hh-mm-ss(24-hour format) - Name: Descriptive lowercase text with hyphens
- Keep the descriptive part concise (1-5 words typically)
- Use context from user's prompt or conversation
- If unclear, use random identifier (e.g.,
x9k2,a7b3)
Examples:
- Prompt "A serene Japanese garden" →
2025-11-23-14-23-05-japanese-garden.png - Prompt "sunset over mountains" →
2025-11-23-15-30-12-sunset-mountains.png - Prompt "create an image of a robot" →
2025-11-23-16-45-33-robot.png - Unclear context →
2025-11-23-17-12-48-x9k2.png
Image Editing
When the user wants to modify an existing image: 1. Check if they provide an image path or reference an image in the current directory 2. Use --input-image parameter with the path to the image 3. The prompt should contain editing instructions (e.g., "make the sky more dramatic", "remove the person", "change to cartoon style") 4. Common editing tasks: add/remove elements, change style, adjust colors, blur background, etc.
Prompt Handling
For generation: Pass user's image description as-is to --prompt. Only rework if clearly insufficient.
For editing: Pass editing instructions in --prompt (e.g., "add a rainbow in the sky", "make it look like a watercolor painting")
Preserve user's creative intent in both cases.
Prompt Templates (high hit-rate)
Use templates when the user is vague or when edits must be precise.
- Generation template:
- "Create an image of: <subject>. Style: <style>. Composition: <camera/shot>. Lighting: <lighting>. Background: <background>. Color palette: <palette>. Avoid: <list>."
- Editing template (preserve everything else):
- "Change ONLY: <single change>. Keep identical: subject, composition/crop, pose, lighting, color palette, background, text, and overall style. Do not add new objects. If text exists, keep it unchanged."
Output
- Saves PNG to current directory (or specified path if filename includes directory)
- Script outputs the full path to the generated image
- Do not read the image back - just inform the user of the saved path
Examples
Generate new image:
uv run ~/.codex/skills/nano-banana-pro/scripts/generate_image.py --prompt "A serene Japanese garden with cherry blossoms" --filename "2025-11-23-14-23-05-japanese-garden.png" --resolution 4KEdit existing image:
uv run ~/.codex/skills/nano-banana-pro/scripts/generate_image.py --prompt "make the sky more dramatic with storm clouds" --filename "2025-11-23-14-25-30-dramatic-sky.png" --input-image "original-photo.jpg" --resolution 2K{
"ownerId": "kn70pywhg0fyz996kpa8xj89s57yhv26",
"slug": "nano-banana-pro",
"version": "1.0.1",
"publishedAt": 1767651987917
}{
"version": 1,
"registry": "https://clawhub.ai",
"slug": "nano-banana-pro",
"installedVersion": "1.0.1",
"installedAt": 1772790606347
}
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "requests>=2.28.0",
# ]
# ///
"""
Generate images using Boluobao API (菠萝包 image generation).
Fallback provider for nano-banana-pro when xingjiabiapi is unavailable.
API: POST https://apipark.boluobao.ai/v1/images/generations
Models: gemini-3-pro-image-preview (and others)
Usage:
uv run generate_image_boluobao.py --prompt "description" --filename "out.jpg" [--resolution 1k|2k|4k] [--aspect-ratio 16:9] [--input-image URL]
"""
import argparse
import os
import sys
import subprocess
from pathlib import Path
def get_api_key(provided_key: str | None) -> str | None:
if provided_key:
return provided_key
key = os.environ.get("BOLUOBAO_API_KEY")
if key:
return key
# Try pass
try:
result = subprocess.run(["pass", "show", "api/boluobao"], capture_output=True, text=True, timeout=5)
if result.returncode == 0:
return result.stdout.strip()
except Exception:
pass
return None
def main():
parser = argparse.ArgumentParser(description="Generate images via Boluobao API")
parser.add_argument("--prompt", "-p", required=True, help="Image prompt")
parser.add_argument("--filename", "-f", required=True, help="Output filename")
parser.add_argument("--model", "-m", default="gemini-3-pro-image-preview", help="Model name")
parser.add_argument("--resolution", "-r", choices=["1k", "2k", "4k"], default="1k", help="Image size")
parser.add_argument("--aspect-ratio", "-a", default="1:1",
choices=["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"],
help="Aspect ratio")
parser.add_argument("--input-image", "-i", nargs="*", default=[], help="Input image URLs (up to 14)")
parser.add_argument("--api-key", "-k", help="API key (overrides env/pass)")
args = parser.parse_args()
api_key = get_api_key(args.api_key)
if not api_key:
print("Error: No API key. Set BOLUOBAO_API_KEY, pass --api-key, or store in `pass api/boluobao`", file=sys.stderr)
sys.exit(1)
import requests
url = "https://apipark.boluobao.ai/v1/images/generations"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
payload = {
"model": args.model,
"prompt": args.prompt,
"aspect_ratio": args.aspect_ratio,
"image": args.input_image,
"image_size": args.resolution,
}
print(f"Generating image: model={args.model}, resolution={args.resolution}, aspect={args.aspect_ratio}")
try:
resp = requests.post(url, json=payload, headers=headers, timeout=120)
data = resp.json()
except Exception as e:
print(f"Error calling API: {e}", file=sys.stderr)
sys.exit(1)
if data.get("status") != 200 and "data" not in data:
print(f"API error: {data}", file=sys.stderr)
sys.exit(1)
results = data.get("data", [])
if not results:
print("Error: No image in response", file=sys.stderr)
sys.exit(1)
image_url = results[0].get("url", "")
revised_prompt = results[0].get("revised_prompt", "")
if revised_prompt:
print(f"Revised prompt: {revised_prompt}")
if not image_url:
print("Error: No image URL in response", file=sys.stderr)
sys.exit(1)
print(f"Image URL: {image_url}")
# Download image
output_path = Path(args.filename)
output_path.parent.mkdir(parents=True, exist_ok=True)
try:
img_resp = requests.get(image_url, timeout=60)
img_resp.raise_for_status()
output_path.write_bytes(img_resp.content)
print(f"\nImage saved: {output_path.resolve()} ({len(img_resp.content) / 1024:.0f} KB)")
except Exception as e:
print(f"Error downloading image: {e}", file=sys.stderr)
print(f"Image URL (manual download): {image_url}")
sys.exit(1)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "google-genai>=1.0.0",
# "pillow>=10.0.0",
# ]
# ///
"""
Generate images using Google's Nano Banana Pro (Gemini 3 Pro Image) API.
Usage:
uv run generate_image.py --prompt "your image description" --filename "output.png" [--resolution 1K|2K|4K] [--api-key KEY]
"""
import argparse
import os
import sys
from pathlib import Path
def get_api_key(provided_key: str | None) -> str | None:
"""Get API key from argument first, then environment."""
if provided_key:
return provided_key
return os.environ.get("GEMINI_API_KEY")
def main():
parser = argparse.ArgumentParser(
description="Generate images using Nano Banana Pro (Gemini 3 Pro Image)"
)
parser.add_argument(
"--prompt", "-p",
required=True,
help="Image description/prompt"
)
parser.add_argument(
"--filename", "-f",
required=True,
help="Output filename (e.g., sunset-mountains.png)"
)
parser.add_argument(
"--input-image", "-i",
help="Optional input image path for editing/modification"
)
parser.add_argument(
"--resolution", "-r",
choices=["1K", "2K", "4K"],
default="1K",
help="Output resolution: 1K (default), 2K, or 4K"
)
parser.add_argument(
"--api-key", "-k",
help="Gemini API key (overrides GEMINI_API_KEY env var)"
)
args = parser.parse_args()
# Get API key
api_key = get_api_key(args.api_key)
if not api_key:
print("Error: No API key provided.", file=sys.stderr)
print("Please either:", file=sys.stderr)
print(" 1. Provide --api-key argument", file=sys.stderr)
print(" 2. Set GEMINI_API_KEY environment variable", file=sys.stderr)
sys.exit(1)
# Import here after checking API key to avoid slow import on error
from google import genai
from google.genai import types
from PIL import Image as PILImage
# Initialise client
client = genai.Client(api_key=api_key)
# Set up output path
output_path = Path(args.filename)
output_path.parent.mkdir(parents=True, exist_ok=True)
# Load input image if provided
input_image = None
output_resolution = args.resolution
if args.input_image:
try:
input_image = PILImage.open(args.input_image)
print(f"Loaded input image: {args.input_image}")
# Auto-detect resolution if not explicitly set by user
if args.resolution == "1K": # Default value
# Map input image size to resolution
width, height = input_image.size
max_dim = max(width, height)
if max_dim >= 3000:
output_resolution = "4K"
elif max_dim >= 1500:
output_resolution = "2K"
else:
output_resolution = "1K"
print(f"Auto-detected resolution: {output_resolution} (from input {width}x{height})")
except Exception as e:
print(f"Error loading input image: {e}", file=sys.stderr)
sys.exit(1)
# Build contents (image first if editing, prompt only if generating)
if input_image:
contents = [input_image, args.prompt]
print(f"Editing image with resolution {output_resolution}...")
else:
contents = args.prompt
print(f"Generating image with resolution {output_resolution}...")
try:
response = client.models.generate_content(
model="gemini-3-pro-image-preview",
contents=contents,
config=types.GenerateContentConfig(
response_modalities=["TEXT", "IMAGE"],
image_config=types.ImageConfig(
image_size=output_resolution
)
)
)
# Process response and convert to PNG
image_saved = False
for part in response.parts:
if part.text is not None:
print(f"Model response: {part.text}")
elif part.inline_data is not None:
# Convert inline data to PIL Image and save as PNG
from io import BytesIO
# inline_data.data is already bytes, not base64
image_data = part.inline_data.data
if isinstance(image_data, str):
# If it's a string, it might be base64
import base64
image_data = base64.b64decode(image_data)
image = PILImage.open(BytesIO(image_data))
# Ensure RGB mode for PNG (convert RGBA to RGB with white background if needed)
if image.mode == 'RGBA':
rgb_image = PILImage.new('RGB', image.size, (255, 255, 255))
rgb_image.paste(image, mask=image.split()[3])
rgb_image.save(str(output_path), 'PNG')
elif image.mode == 'RGB':
image.save(str(output_path), 'PNG')
else:
image.convert('RGB').save(str(output_path), 'PNG')
image_saved = True
if image_saved:
full_path = output_path.resolve()
print(f"\nImage saved: {full_path}")
else:
print("Error: No image was generated in the response.", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"Error generating image: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "requests>=2.28.0",
# ]
# ///
"""
Prompt Optimizer for Image Generation.
Takes a simple user prompt and enhances it into a professional image generation prompt.
Uses a fast/cheap LLM (boluobao or xingjiabiapi) to optimize.
Usage:
uv run optimize_prompt.py --prompt "一只猫" [--style photo|illustration|anime|oil-painting|3d|pixel]
uv run optimize_prompt.py --prompt "sunset" --style "oil-painting" --lang en
"""
import argparse
import json
import os
import subprocess
import sys
import requests
SYSTEM_PROMPT = """你是一个专业的 AI 图像生成提示词优化师。
用户会给你一个简短的图像描述,你需要将其扩展为一个详细、高质量的图像生成提示词。
## 优化规则:
1. **保留用户的核心意图** — 不要改变主题,只是丰富细节
2. **添加以下维度**(如果用户没有指定):
- 主体细节(姿态、表情、材质、纹理)
- 构图(视角、景深、画面布局)
- 光线(方向、色温、氛围)
- 色彩(主色调、对比度、饱和度)
- 风格(写实/插画/油画/3D等)
- 背景(环境、氛围、层次)
- 品质关键词(8K, masterpiece, ultra detailed 等)
3. **输出纯英文** — 图像生成模型对英文效果最好
4. **长度控制** — 80-200 词之间,不要太短也不要太长
5. **不要用 markdown 格式** — 只输出纯文本提示词
6. **不要写"Prompt:"前缀** — 直接输出优化后的文本
## 风格映射:
- photo → photorealistic, DSLR quality, natural lighting
- illustration → digital illustration, clean lines, vibrant
- anime → anime style, cel-shading, Japanese animation aesthetic
- oil-painting → oil painting style, visible brush strokes, rich texture
- 3d → 3D render, octane render, volumetric lighting
- pixel → pixel art, retro gaming aesthetic, 8-bit/16-bit
- cinematic → cinematic composition, dramatic lighting, movie still
- watercolor → watercolor painting, soft edges, translucent layers"""
def get_api_key(provider: str) -> str | None:
"""Get API key for the specified provider."""
env_map = {
"boluobao": "BOLUOBAO_API_KEY",
"xingjiabiapi": "XINGJIABIAPI_API_KEY",
}
pass_map = {
"boluobao": "api/boluobao",
"xingjiabiapi": "api/xingjiabiapi",
}
# Try env first
key = os.environ.get(env_map.get(provider, ""))
if key:
return key
# Try pass
try:
result = subprocess.run(
["pass", "show", pass_map.get(provider, "")],
capture_output=True, text=True, timeout=5
)
if result.returncode == 0:
return result.stdout.strip()
except Exception:
pass
return None
def optimize_with_boluobao(prompt: str, style: str, api_key: str) -> str:
"""Use Boluobao API to optimize prompt."""
user_msg = f"请优化这个图像生成提示词:「{prompt}」"
if style:
user_msg += f"\n风格要求:{style}"
resp = requests.post(
"https://apipark.boluobao.ai/v1/chat/completions",
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
},
json={
"model": "gemini-3-pro-preview",
"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_msg},
],
"max_tokens": 500,
"temperature": 0.7,
},
timeout=30,
)
data = resp.json()
return data["choices"][0]["message"]["content"].strip()
def optimize_with_xingjiabiapi(prompt: str, style: str, api_key: str) -> str:
"""Use xingjiabiapi API to optimize prompt."""
user_msg = f"请优化这个图像生成提示词:「{prompt}」"
if style:
user_msg += f"\n风格要求:{style}"
resp = requests.post(
"https://api.xingjiabiapi.com/v1/chat/completions",
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
},
json={
"model": "gemini-2.5-flash-preview-05-20",
"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_msg},
],
"max_tokens": 500,
"temperature": 0.7,
},
timeout=30,
)
data = resp.json()
return data["choices"][0]["message"]["content"].strip()
def main():
parser = argparse.ArgumentParser(description="Optimize image generation prompts")
parser.add_argument("--prompt", "-p", required=True, help="User's original prompt")
parser.add_argument("--style", "-s", default="",
choices=["", "photo", "illustration", "anime", "oil-painting",
"3d", "pixel", "cinematic", "watercolor"],
help="Desired style")
parser.add_argument("--provider", default="auto",
choices=["auto", "boluobao", "xingjiabiapi"],
help="LLM provider for optimization")
args = parser.parse_args()
# Try providers in order
providers = (
["boluobao", "xingjiabiapi"] if args.provider == "auto"
else [args.provider]
)
optimized = None
for provider in providers:
api_key = get_api_key(provider)
if not api_key:
continue
try:
if provider == "boluobao":
optimized = optimize_with_boluobao(args.prompt, args.style, api_key)
else:
optimized = optimize_with_xingjiabiapi(args.prompt, args.style, api_key)
if optimized:
break
except Exception as e:
print(f"Warning: {provider} failed: {e}", file=sys.stderr)
continue
if not optimized:
print("Error: All providers failed. Using original prompt.", file=sys.stderr)
optimized = args.prompt
# Output the optimized prompt
print(optimized)
if __name__ == "__main__":
main()