
Nanobanana Skill
- 114 installs
- 230 repo stars
- Updated July 27, 2026
- feiskyer/codex-settings
Apply a compact Codex agent profile and skill preset from codex-settings for fast, lightweight coding assistance on small tasks and tight feedback loops.
About
nanobanana-skill from feiskyer/codex-settings is a compact Codex agent configuration preset optimized for quick, low-overhead coding assistance—pairing skill hooks and runtime defaults for small scoped changes.
- Lightweight agent preset
- Codex profile tuning
- Fast iteration defaults
- Repo-aligned skill wiring
- Minimal-context workflows
Nanobanana Skill by the numbers
- 114 all-time installs (skills.sh)
- Ranked #3,946 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/feiskyer/codex-settings --skill nanobanana-skillAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 114 |
|---|---|
| repo stars | ★ 230 |
| Last updated | July 27, 2026 |
| Repository | feiskyer/codex-settings ↗ |
What it does
Apply a compact Codex agent profile and skill preset from codex-settings for fast, lightweight coding assistance on small tasks and tight feedback loops.
Files
Nanobanana Image Skill
Use the bundled nanobanana.py tool to generate or edit images with Gemini image models. The default path now targets Nano Banana 2 (gemini-3.1-flash-image-preview) and turns on thinking summaries plus Google Search grounding by default, because those defaults are the main reason to use this skill instead of a generic image prompt.
When to use this skill
Use it for:
1. Creating a new image from text 2. Editing or remixing one or more existing images 3. Combining several reference images into one composition 4. Requests where factual freshness matters and image grounding should use live web data 5. Requests that need strong text rendering, layout reasoning, or more deliberate composition
Do not use it for:
1. Non-image tasks 2. Image work that explicitly must use another provider or another installed image skill
Requirements
1. GEMINI_API_KEY must exist in ~/.nanobanana.env or the shell environment. 2. Python dependencies from requirements.txt must be installed. 3. The executable is the nanobanana.py file in this same skill directory. Resolve its absolute path once before running it.
Example env file:
GEMINI_API_KEY=sk-dummyDefault behavior
Unless the user explicitly asks otherwise, prefer these defaults:
1. Model: gemini-3.1-flash-image-preview 2. Search grounding: enabled 3. Thinking summaries: enabled 4. Thinking level: high 5. Resolution: 1K 6. Aspect ratio: leave unspecified unless the user clearly wants a shape
Leaving aspect ratio unspecified is usually better for edits because Gemini can match the input image shape. For text-only generation, pick an aspect ratio only when the user implies a format such as poster, square post, banner, phone wallpaper, or ultrawide hero image.
Workflow
1. Clarify only the missing constraints
Ask only for details that materially affect the result:
1. The image brief or editing instruction 2. Any input/reference images to use 3. Target format or aspect ratio if implied by the use case 4. Output filename if the user cares where it lands
Do not force the user to choose a model, search mode, or thinking mode unless they asked for that level of control. The latest Nanobanana 2 path is already the default.
2. Choose the right mode
Use generate when there are no input images.
Use edit/composite when there are one or more input images. Nanobanana 2 can mix multiple references, so do not artificially limit the task to a single image if the user is clearly asking for a blend, lineup, storyboard, or consistency pass.
3. Run the bundled tool
Use the script beside this skill file.
Basic generation:
python3 /absolute/path/to/nanobanana.py \
--prompt "Create a high-end coffee bag package design with tactile paper texture and clear typography" \
--output /absolute/path/to/output/package.pngEditing or compositing:
python3 /absolute/path/to/nanobanana.py \
--prompt "Turn these product photos into a clean 4:5 ecommerce hero image with a soft studio shadow and subtle headline area" \
--input /absolute/path/to/ref1.png /absolute/path/to/ref2.png \
--aspect-ratio 4:5 \
--output /absolute/path/to/output/hero.pngGrounded generation with saved text metadata:
python3 /absolute/path/to/nanobanana.py \
--prompt "Use Google Search to ground an editorial illustration about the most recent lunar mission and create a clean magazine cover concept" \
--aspect-ratio 2:3 \
--text-output /absolute/path/to/output/cover.txt \
--metadata-output /absolute/path/to/output/cover.json \
--output /absolute/path/to/output/cover.png4. Return the result clearly
Always tell the user:
1. The saved image path or paths 2. Whether search grounding stayed enabled 3. Whether text/thought summaries were saved anywhere 4. Any relevant limitation or warning from the run
If the model returns text but no image, report that plainly and suggest a more explicit image-focused prompt instead of pretending the run succeeded.
Recommended options
Models
1. gemini-3.1-flash-image-preview: default. Best default for fast, high-volume image generation and editing with Nanobanana 2 features. 2. gemini-3-pro-image-preview: slower, but a good override for very detail-heavy or typography-sensitive work. 3. gemini-2.5-flash-image: legacy fallback if the user specifically wants the older Nanobanana model.
Aspect ratios
Supported ratios:
1. 1:1 2. 1:4 3. 1:8 4. 2:3 5. 3:2 6. 3:4 7. 4:1 8. 4:3 9. 4:5 10. 5:4 11. 8:1 12. 9:16 13. 16:9 14. 21:9
Quick picks:
1. 1:1 for logos, icons, thumbnails, and general social posts 2. 4:5 for feed posts and product cards 3. 2:3 for posters and book-cover style work 4. 9:16 for stories, shorts, and phone wallpaper 5. 16:9 or 21:9 for slides, banners, and desktop hero art 6. 1:4, 4:1, 1:8, 8:1 for very tall or very wide experimental layouts now supported by Nanobanana 2
Resolution
1. 512px: Nanobanana 2 only. Best for quick ideation. 2. 1K: default. Good tradeoff for most requests. 3. 2K: use for polished deliverables. 4. 4K: use when the user explicitly needs a high-resolution final.
Thinking and search
1. Keep search grounding on by default when the request could benefit from live facts, current events, or real product references. 2. Keep thinking summaries on by default because Nanobanana 2 often composes better on multi-constraint tasks. 3. Lower --thinking-level to low or minimal when the user prioritizes latency over refinement. 4. Disable search only when the user wants a purely imaginative result or explicitly requests no web grounding.
Prompting guidance
Good Nanobanana prompts are direct production briefs, not vague art wishes. Include:
1. Subject 2. Visual style 3. Composition or camera framing 4. Required text if any 5. Output use case 6. Constraints such as brand colors, empty space, or realism level
Prefer prompts like:
Create a premium sparkling water can advertisement. Use a cold studio product-photo look, silver highlights, condensation droplets, and a clean dark-teal background. Leave negative space in the upper-right for headline copy.Instead of:
make a cool drink adFor edits, tell the model what to preserve and what to change:
Keep the shoe silhouette and logo placement intact. Replace the background with a bright outdoor basketball court, add dynamic afternoon shadows, and keep the image looking like a real sports campaign photo.Nanobanana 2 capabilities to lean on
1. Multi-reference composition with many input images 2. Better grounded visuals with Google Search and Google Image Search support behind the built-in search tool 3. Wider aspect-ratio support, including very tall and very wide outputs 4. 512px fast ideation output in addition to 1K, 2K, and 4K 5. Stronger iterative reasoning via Gemini 3 thinking controls
Error handling
If the run fails:
1. Check ~/.nanobanana.env and confirm GEMINI_API_KEY is present. 2. Confirm each input image path exists and is readable. 3. Confirm the output directory is writable. 4. If a feature looks unsupported, retry with gemini-3.1-flash-image-preview first. 5. If the response contains only text, rewrite the prompt so the image deliverable is explicit.
Examples
Fast ideation
python3 /absolute/path/to/nanobanana.py \
--prompt "Create three-dimensional sticker-style fruit mascots on white" \
--resolution 512px \
--output /absolute/path/to/output/stickers.pngGrounded current-events visual
python3 /absolute/path/to/nanobanana.py \
--prompt "Use Google Search to ground a newspaper-style illustration about the latest Mars mission and create a restrained front-page visual" \
--aspect-ratio 3:2 \
--output /absolute/path/to/output/mars.pngMulti-reference composite
python3 /absolute/path/to/nanobanana.py \
--prompt "Create a single brand moodboard from these references. Keep the ceramic texture from the first image, the palette from the second, and the lighting mood from the third." \
--input /absolute/path/to/a.png /absolute/path/to/b.png /absolute/path/to/c.png \
--aspect-ratio 16:9 \
--output /absolute/path/to/output/moodboard.png#!/usr/bin/env python3
"""Generate or edit images with Gemini image models via Nanobanana."""
import argparse
import json
import os
import uuid
from io import BytesIO
from pathlib import Path
from dotenv import load_dotenv
from google import genai
from google.genai import types
from PIL import Image
# Load environment variables
load_dotenv(os.path.expanduser("~") + "/.nanobanana.env")
# Google API configuration from environment variables
api_key = os.getenv("GEMINI_API_KEY") or ""
if not api_key:
raise ValueError(
"Missing GEMINI_API_KEY environment variable. Please check your .env file."
)
# Initialize Gemini client
client = genai.Client(api_key=api_key)
ASPECT_RATIO_MAP = {
"1024x1024": "1:1",
"832x1248": "2:3",
"1248x832": "3:2",
"864x1184": "3:4",
"1184x864": "4:3",
"896x1152": "4:5",
"1152x896": "5:4",
"768x1344": "9:16",
"1344x768": "16:9",
"1536x672": "21:9",
}
SUPPORTED_ASPECT_RATIOS = [
"1:1",
"1:4",
"1:8",
"2:3",
"3:2",
"3:4",
"4:1",
"4:3",
"4:5",
"5:4",
"8:1",
"9:16",
"16:9",
"21:9",
]
SUPPORTED_MODELS = [
"gemini-3.1-flash-image-preview",
"gemini-3-pro-image-preview",
"gemini-2.5-flash-image",
]
SUPPORTED_RESOLUTIONS = ["512px", "1K", "2K", "4K"]
def parse_args():
parser = argparse.ArgumentParser(
description="Generate or edit images using Google Gemini API"
)
parser.add_argument(
"--prompt",
type=str,
required=True,
help="Prompt for image generation or editing",
)
parser.add_argument(
"--output",
type=str,
default=f"nanobanana-{uuid.uuid4()}.png",
help="Output image filename (default: nanobanana-<UUID>.png)",
)
parser.add_argument(
"--input",
dest="input_files",
type=str,
nargs="*",
help="Input image files for editing or multi-reference composition",
)
parser.add_argument(
"--aspect-ratio",
type=str,
choices=SUPPORTED_ASPECT_RATIOS,
default=None,
help="Aspect ratio of the generated image. If omitted, Gemini chooses automatically.",
)
parser.add_argument(
"--size",
type=str,
choices=list(ASPECT_RATIO_MAP.keys()),
default=None,
help="Deprecated alias for aspect ratio using legacy size presets.",
)
parser.add_argument(
"--model",
type=str,
default="gemini-3.1-flash-image-preview",
choices=SUPPORTED_MODELS,
help="Model to use for image generation (default: gemini-3.1-flash-image-preview)",
)
parser.add_argument(
"--resolution",
type=str,
default="1K",
choices=SUPPORTED_RESOLUTIONS,
help="Resolution of the generated image (default: 1K)",
)
parser.add_argument(
"--disable-google-search",
action="store_true",
help="Disable Google Search grounding (enabled by default).",
)
parser.add_argument(
"--disable-thinking",
action="store_true",
help="Reduce or disable thinking where the model family supports it.",
)
parser.add_argument(
"--thinking-level",
type=str,
default="high",
choices=["minimal", "low", "medium", "high"],
help="Thinking level for Gemini 3 image models (default: high).",
)
parser.add_argument(
"--exclude-thoughts",
action="store_true",
help="Do not include thought summaries in stdout or saved text output.",
)
parser.add_argument(
"--text-output",
type=str,
default=None,
help="Optional path to save response text and thought summaries.",
)
parser.add_argument(
"--metadata-output",
type=str,
default=None,
help="Optional path to save structured metadata about the run.",
)
return parser.parse_args()
def resolve_aspect_ratio(args):
if args.aspect_ratio:
return args.aspect_ratio
if args.size:
return ASPECT_RATIO_MAP[args.size]
return None
def build_thinking_config(args):
if args.disable_thinking:
if args.model.startswith("gemini-2.5"):
return types.ThinkingConfig(
include_thoughts=False,
thinking_budget=0,
)
return types.ThinkingConfig(
include_thoughts=False,
thinking_level="minimal",
)
kwargs = {"include_thoughts": not args.exclude_thoughts}
if args.model.startswith("gemini-2.5"):
return types.ThinkingConfig(**kwargs)
kwargs["thinking_level"] = args.thinking_level
return types.ThinkingConfig(**kwargs)
def ensure_parent_dir(path_str):
if not path_str:
return
Path(path_str).expanduser().resolve().parent.mkdir(parents=True, exist_ok=True)
def save_text_output(path_str, text_parts, thought_parts):
if not path_str:
return
ensure_parent_dir(path_str)
lines = []
if text_parts:
lines.append("# Response Text")
lines.append("")
lines.extend(text_parts)
lines.append("")
if thought_parts:
lines.append("# Thought Summaries")
lines.append("")
lines.extend(thought_parts)
lines.append("")
Path(path_str).write_text("\n".join(lines).strip() + "\n", encoding="utf-8")
def save_metadata_output(path_str, payload):
if not path_str:
return
ensure_parent_dir(path_str)
Path(path_str).write_text(
json.dumps(payload, indent=2, ensure_ascii=False) + "\n",
encoding="utf-8",
)
def save_image_parts(parts, output_path):
output_path = Path(output_path).expanduser().resolve()
output_path.parent.mkdir(parents=True, exist_ok=True)
saved_paths = []
image_index = 0
for part in parts:
if part.inline_data is None or part.inline_data.data is None:
continue
image_index += 1
target_path = output_path
if image_index > 1:
target_path = output_path.with_name(
f"{output_path.stem}-{image_index}{output_path.suffix}"
)
image = Image.open(BytesIO(part.inline_data.data))
image.save(target_path)
saved_paths.append(str(target_path))
return saved_paths
def main():
args = parse_args()
aspect_ratio = resolve_aspect_ratio(args)
if args.input_files and len(args.input_files) > 14:
raise ValueError("Nanobanana supports at most 14 input reference images.")
contents = []
if args.input_files:
print(f"Editing images with prompt: {args.prompt}")
print(f"Input images: {args.input_files}")
contents.append(args.prompt)
for img_path in args.input_files:
if not os.path.isfile(img_path):
raise FileNotFoundError(f"Input image not found: {img_path}")
image = Image.open(img_path)
contents.append(image)
else:
print(f"Generating image with prompt: {args.prompt}")
contents.append(args.prompt)
print(f"Model: {args.model}")
print(f"Resolution: {args.resolution}")
print(
"Google Search grounding: "
+ ("disabled" if args.disable_google_search else "enabled")
)
if aspect_ratio:
print(f"Aspect ratio: {aspect_ratio}")
else:
print("Aspect ratio: auto")
config_kwargs = {
"response_modalities": ["TEXT", "IMAGE"],
"thinking_config": build_thinking_config(args),
}
if aspect_ratio or args.resolution:
image_config_kwargs = {"image_size": args.resolution}
if aspect_ratio:
image_config_kwargs["aspect_ratio"] = aspect_ratio
config_kwargs["image_config"] = types.ImageConfig(**image_config_kwargs)
if not args.disable_google_search:
config_kwargs["tools"] = [types.Tool(google_search=types.GoogleSearch())]
response = client.models.generate_content(
model=args.model,
contents=contents,
config=types.GenerateContentConfig(**config_kwargs),
)
if (
response.candidates is None
or len(response.candidates) == 0
or response.candidates[0].content is None
or response.candidates[0].content.parts is None
):
raise ValueError("No data received from the API.")
parts = response.candidates[0].content.parts
text_parts = []
thought_parts = []
for part in parts:
if part.text is not None:
if getattr(part, "thought", False):
thought_parts.append(part.text)
else:
text_parts.append(part.text)
for block_title, block_parts in (
("Response Text", text_parts),
("Thought Summaries", thought_parts),
):
if not block_parts:
continue
print(f"\n{block_title}:")
for text in block_parts:
print(text)
saved_paths = save_image_parts(parts, args.output)
save_text_output(args.text_output, text_parts, thought_parts)
metadata = {
"model": args.model,
"prompt": args.prompt,
"input_files": args.input_files or [],
"aspect_ratio": aspect_ratio,
"resolution": args.resolution,
"google_search_enabled": not args.disable_google_search,
"thinking_disabled": args.disable_thinking,
"thinking_level": None if args.disable_thinking else args.thinking_level,
"include_thought_summaries": not args.exclude_thoughts and not args.disable_thinking,
"response_text": text_parts,
"thought_summaries": thought_parts,
"saved_images": saved_paths,
}
save_metadata_output(args.metadata_output, metadata)
if saved_paths:
for saved_path in saved_paths:
print(f"\nImage saved to: {saved_path}")
else:
print(
"\nWarning: No image data found in the API response. "
"This usually means the model returned only text. "
"Please try again with a more explicit image-generation prompt."
)
if args.text_output:
print(f"Text output saved to: {Path(args.text_output).expanduser().resolve()}")
if args.metadata_output:
print(
f"Metadata output saved to: {Path(args.metadata_output).expanduser().resolve()}"
)
if __name__ == "__main__":
main()
python-dotenv
httpx[socks]
google-genai>=1.52.0
Pillow