npx claudepluginhub gopherguides/gopher-ai --plugin llm-toolsThis skill uses the workspace's default tool permissions.
You detected an image generation request. Confirm intent before proceeding.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Migrates code, prompts, and API calls from Claude Sonnet 4.0/4.5 or Opus 4.1 to Opus 4.5, updating model strings on Anthropic, AWS, GCP, Azure platforms.
Performs token-optimized structural code search using tree-sitter AST parsing to discover symbols, outline files, and unfold code without reading full files.
You detected an image generation request. Confirm intent before proceeding.
Say: "It sounds like you want to generate an image. I can do that using the Gemini API. Let me walk you through the options."
If the user confirms, proceed. If not, stop.
Tip: You can also use /gemini-image <description> to generate images directly.
Verify the environment:
echo "GEMINI_API_KEY set: $([ -n "$GEMINI_API_KEY" ] && echo 'yes' || echo 'no')"
which python3
If GEMINI_API_KEY is not set:
GEMINI_API_KEYis not set. Get one free at https://aistudio.google.com/apikey Then export it:export GEMINI_API_KEY="your-key-here"
Stop and wait for the user to set the key.
If python3 is not available, inform the user that Python 3 is required.
Note: The Gemini CLI's Nano Banana extension was investigated as an alternative generation path, but it has a known MCP tool registration bug (Gemini CLI v0.34.0 / Nano Banana v1.0.12) where tools fail to load at runtime. Only the REST API is reliable for image generation at this time.
Extract the image description from the user's request and confirm it.
Ask the user which model to use:
| Model ID | Best For | Known Issues |
|---|---|---|
gemini-3.1-flash-image-preview | Fast, high-volume, newest (Recommended) | aspectRatio may be ignored in edit/background operations |
gemini-2.5-flash-image | Stable, proven, fewest bugs | Most reliable for imageConfig params |
Default: gemini-3.1-flash-image-preview
Infer from context when possible, then confirm:
| Context Clue | Suggested Ratio |
|---|---|
| "hero image", "banner", "header" | 16:9 |
| "profile pic", "avatar", "icon", "logo" | 1:1 |
| "story", "mobile", "vertical" | 9:16 |
| "poster", "book cover" | 3:4 |
| "presentation", "thumbnail" | 4:3 |
| "ultrawide", "cinematic" | 21:9 |
If no context clue, default to 1:1.
All supported 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
Note: On
gemini-3.1-flash-image-preview,aspectRatiomay be silently ignored during image editing or background replacement operations. If aspect ratio is critical for an edit operation, consider usinggemini-2.5-flash-imageinstead.
Ask the user:
| Resolution | Notes |
|---|---|
1K | Good quality, fast (default) |
2K | Higher detail |
4K | Maximum detail, slower |
512 | Only available on gemini-3.1-flash-image-preview |
Default: 1K
Important:
imageSizevalues are case-sensitive. Use"1K","2K","4K"exactly — lowercase (e.g.,"1k") silently falls back to 512px resolution.
If user selects 512 with a model other than gemini-3.1-flash-image-preview, warn them and switch to 1K.
Ask if they want to include a reference image (path to file). Default: no.
Ask or auto-generate a descriptive filename in the current directory (e.g., hero-banner.png, coffee-logo.png).
First, export the gathered values as environment variables so the python3 script can read them:
Important: Always single-quote user-provided values to prevent shell injection (quotes, backticks, $ in prompts):
export GEMINI_PROMPT='<the user'"'"'s image description — single-quote wrapped>'
export GEMINI_MODEL='<selected model, e.g. gemini-3.1-flash-image-preview>'
export GEMINI_ASPECT_RATIO='<selected ratio, e.g. 1:1>'
export GEMINI_IMAGE_SIZE='<selected resolution, e.g. 1K>'
export GEMINI_REF_IMAGE='<path to reference image, or empty>'
export GEMINI_OUTPUT_PATH='<output file path>'
Then run the builder:
python3 << 'PYEOF'
import json, base64, sys, os
prompt = os.environ.get("GEMINI_PROMPT", "")
model = os.environ.get("GEMINI_MODEL", "gemini-3.1-flash-image-preview")
aspect_ratio = os.environ.get("GEMINI_ASPECT_RATIO", "1:1")
image_size = os.environ.get("GEMINI_IMAGE_SIZE", "1K")
ref_image_path = os.environ.get("GEMINI_REF_IMAGE", "")
pid = os.getpid()
parts = []
if ref_image_path and os.path.exists(ref_image_path):
ext = ref_image_path.lower().rsplit(".", 1)[-1]
mime_map = {"png": "image/png", "jpg": "image/jpeg", "jpeg": "image/jpeg", "webp": "image/webp", "gif": "image/gif"}
mime = mime_map.get(ext, "image/png")
with open(ref_image_path, "rb") as f:
b64 = base64.b64encode(f.read()).decode()
parts.append({"inlineData": {"mimeType": mime, "data": b64}})
parts.append({"text": prompt})
payload = {
"contents": [{"parts": parts}],
"generationConfig": {
"responseModalities": ["TEXT", "IMAGE"],
"imageConfig": {
"aspectRatio": aspect_ratio
}
}
}
if image_size != "1K":
payload["generationConfig"]["imageConfig"]["imageSize"] = image_size
outfile = f"/tmp/gemini-image-request-{pid}.json"
with open(outfile, "w") as f:
json.dump(payload, f)
print(outfile)
PYEOF
Capture the printed path as REQUEST_FILE.
Use the REQUEST_FILE path from Step 3 and the GEMINI_MODEL env var:
RESPONSE_FILE="/tmp/gemini-image-response-$$.json"
HTTP_STATUS=$(curl -s -o "$RESPONSE_FILE" -w "%{http_code}" -X POST \
"https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent?key=$GEMINI_API_KEY" \
-H "Content-Type: application/json" \
-d @"${REQUEST_FILE}")
export GEMINI_RESPONSE_FILE="$RESPONSE_FILE"
echo "HTTP status: $HTTP_STATUS"
If HTTP_STATUS is 429, wait 30s and retry once. If 400/403, show the error and suggest fixes. Only proceed if 200.
Use python3 to parse the response and save:
python3 << 'PYEOF'
import json, base64, sys, os
response_file = os.environ.get("GEMINI_RESPONSE_FILE", "")
output_path = os.environ.get("GEMINI_OUTPUT_PATH", "output.png")
with open(response_file) as f:
data = json.load(f)
if "error" in data:
print(f"API Error: {data['error'].get('message', str(data['error']))}", file=sys.stderr)
sys.exit(1)
candidates = data.get("candidates", [])
if not candidates:
print("No candidates in response", file=sys.stderr)
sys.exit(1)
parts = candidates[0].get("content", {}).get("parts", [])
text_parts = []
image_data = None
image_mime = None
for part in parts:
if "text" in part:
text_parts.append(part["text"])
elif "inlineData" in part:
image_data = part["inlineData"]["data"]
image_mime = part["inlineData"].get("mimeType", "image/png")
if not image_data:
print("No image data in response", file=sys.stderr)
if text_parts:
print(f"Model response: {' '.join(text_parts)}", file=sys.stderr)
sys.exit(1)
raw_bytes = base64.b64decode(image_data)
if output_path.lower().endswith(".png") and image_mime == "image/jpeg":
try:
from PIL import Image
import io
img = Image.open(io.BytesIO(raw_bytes))
img.save(output_path, "PNG")
print(f"Converted JPEG→PNG and saved to {output_path}")
except ImportError:
import subprocess, shutil
if shutil.which("magick"):
tmp_jpg = output_path + ".tmp.jpg"
with open(tmp_jpg, "wb") as f:
f.write(raw_bytes)
result = subprocess.run(["magick", tmp_jpg, output_path], capture_output=True)
os.remove(tmp_jpg)
if result.returncode == 0:
print(f"Converted JPEG→PNG (magick) and saved to {output_path}")
else:
output_path = output_path.rsplit(".", 1)[0] + ".jpg"
with open(output_path, "wb") as f:
f.write(raw_bytes)
print(f"magick failed, saved as JPEG: {output_path}")
else:
output_path = output_path.rsplit(".", 1)[0] + ".jpg"
with open(output_path, "wb") as f:
f.write(raw_bytes)
print(f"No PNG converter available (install Pillow or ImageMagick), saved as JPEG: {output_path}")
else:
with open(output_path, "wb") as f:
f.write(raw_bytes)
print(f"Saved to {output_path}")
size_kb = os.path.getsize(output_path) / 1024
print(f"Size: {size_kb:.1f} KB")
if text_parts:
print(f"Model notes: {' '.join(text_parts)}")
PYEOF
Write a {name}_prompt.txt alongside the image:
PROMPT_FILE="${GEMINI_OUTPUT_PATH%.*}_prompt.txt"
cat > "$PROMPT_FILE" << EOF
Prompt: ${GEMINI_PROMPT}
Model: ${GEMINI_MODEL}
Aspect Ratio: ${GEMINI_ASPECT_RATIO}
Resolution: ${GEMINI_IMAGE_SIZE}
Reference Image: ${GEMINI_REF_IMAGE:-none}
Date: $(date -u +"%Y-%m-%dT%H:%M:%SZ")
EOF
rm -f "${REQUEST_FILE}" "${RESPONSE_FILE}"
Report:
{output_path}{prompt_file}Ask: "Would you like to regenerate with different settings, adjust the prompt, or generate another image?"
| Error | Action |
|---|---|
Missing GEMINI_API_KEY | Link to https://aistudio.google.com/apikey |
Missing python3 | Inform user Python 3 is required |
| API 429 (rate limit) | Wait 30s and retry once |
| API 400 (bad request) | Show error, suggest simpler prompt |
| API 403 (forbidden) | Check API key, suggest regenerating |
| JPEG when PNG requested | Auto-convert: Pillow → magick → .jpg fallback |
| No image in response | Show model text, suggest rephrasing |
imageSize lowercase value | Warn about case sensitivity before sending request |