gemini-icon
Skill0 starsChanged 5 months agoMITView on GitHub
Generate iOS-style app icons via Google's Gemini image model (Nano Banana). Use when the user says "generate an icon", "make an iOS-style icon", "Gemini icon", "app icon for <thing>", "branded icon", "illustrated icon", or asks for an app-icon-style asset for the Stream Deck or any small tile that needs a glossy, gradient, branded look. For utility line-art primitives (arrows, refresh, simple geometric glyphs), use `svg-icon-render` instead. For marketing imagery (heroes, social, ads), use `image`.
- Reads credentials
- Sends data out
In this repository18 other files it gives its agents.
- app-icon-gridSkill
- archive-manualSkill
- commitSkill
- component-consistencySkill
- component-locatorSkill
- debug-triageSkill
- parity-auditorSkill
- prd-writingSkill
- refactor-and-safety-passSkill
- release-helperSkill
- smellSkill
- streamdeck-aerospace-rewireSkill
- streamdeck-workspace-buttonSkill
- svg-icon-renderSkill
- test-conventionsSkill
- verification-gateSkill
- work-breakdownSkill
- yt-audioSkill
The file
We split this skill into the rules it gives an agent
4 rules. About 0 are common, 3 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 102
Use this only if Imagen is unavailable.
- Very specific: line 181
After writing the icon, those skills handle the copy into the active page's Images/ subdir via set-streamdeck-button.sh --icon or the …
- Distinctive: line 210
If you want just the icon shape on transparent (for compositing into a different context), Gemini's "plain white background" wording makes …
- Distinctive: line 224
Use sparingly — the inner icon shape (white speech bubble) shares the background color, so this also keys the subject.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Gemini iOS-style icon
Generate single iOS-app-icon-style PNGs via Google's image models. Output is the kind of glossy, gradient-backed icon that looks at home on a phone home screen — and at 288×288 looks great on a Stream Deck XL key for branded workspaces (chat, video, music, calendar, project workspaces).
Model choice — strongly prefer Imagen 4 over gemini-2.5-flash-image for Stream Deck / app-icon work. gemini-2.5-flash-image (a.k.a. Nano Banana) produces flat silhouettes inside a white-frame rounded-square. Imagen 4 produces actual skeuomorphic depth (real materials — brushed metal, glass, leather) and respects "full-bleed canvas" prompts. Cost difference is small relative to the quality jump. Use gemini-2.5-flash-image only if Imagen 4 is unavailable.
When to use this skill vs. neighbors
- This skill (
gemini-icon) — branded / illustrated / "iOS-style" tile icons. Glossy. Gradient background. A single subject. Stream Deck branded workspace keys ("communication", "recording", "music"). Replaces the cases where PIL hand-drawing felt utilitarian and you wanted personality. svg-icon-render— utility primitives. Arrows, refresh, gear, simple line glyphs. Crisp vector → PNG. Use when the icon is a system affordance, not an identity.streamdeck-grid-update/streamdeck-workspace-button— these are the consumers. When they need a branded icon for a workspace key, they call into this skill to mint one; then they copy the resulting288.pnginto the page'sImages/dir via the existing helper script.image— generic marketing imagery (blog heroes, social, mockups). Different shape (16:9, 1:1 at larger sizes), different intent. Do not use this skill for marketing.
Setup
- API key: export
GEMINI_API_KEYin your environment before any curl call. - Default model:
imagen-4.0-generate-001(uses:predict, different request shape — see "Imagen 4 recipe" below). Also available:imagen-4.0-ultra-generate-001(higher quality, slower) andimagen-4.0-fast-generate-001(cheaper, lower fidelity). - Fallback model:
gemini-2.5-flash-image(stable,:generateContent). Use only when Imagen is unavailable or you specifically want the flat-silhouette aesthetic. Notgemini-2.5-flash-image-preview— that's a 404 now. Confirm available models:curl -sS "https://generativelanguage.googleapis.com/v1beta/models?key=${GEMINI_API_KEY}" \ | python3 -c "import json,sys; [print(m['name']) for m in json.load(sys.stdin)['models'] if 'image' in m['name']]" - Alternatives in the same account if quality regresses:
gemini-3-pro-image-preview,gemini-3.1-flash-image-preview,nano-banana-pro-preview. All take the same request shape.imagen-4.0-generate-001uses thepredictmethod instead — different request shape, skip unless flash-image quality drops. - The pattern here is a direct curl + extract + resize, no SDK.
- Needs
python3+ Pillow for the resize step.
The recipe (Imagen 4 — DEFAULT, full-bleed skeuomorphic)
Imagen 4 uses :predict (not :generateContent) — different request shape than the Gemini fallback below. This recipe is the one that produced the icons currently on Tarik's Stream Deck.
set -euo pipefail
: "${GEMINI_API_KEY:?export GEMINI_API_KEY before running}"
SLUG="video-camera"
OUT_DIR="$HOME/.config/aerospace/icons"
TMP_DIR="$(mktemp -d)"
# Slight-skeuomorphic + full-bleed template. Drop the "rounded square on
# white" framing the old gemini-2.5-flash-image template used — Imagen 4
# fills the entire canvas with the gradient and adds real material depth.
PROMPT='A slightly skeuomorphic app icon. The entire square canvas is filled edge-to-edge with the gradient background — no white margin, no rounded-square frame inside a larger canvas. The subject is rendered with subtle real-world depth: soft drop shadows, gentle highlights, a hint of physical material where natural (glass, brushed metal, matte plastic) — Apple-style dimensionality without 2009-era leather or wood textures. Centered. No text, no labels, no logos. 1024x1024 square PNG. Subject: a vintage camcorder in three-quarter view — black matte body, brushed metal lens barrel, small red record light, a hint of leather grain on the grip — centered on a red-to-orange gradient background that fills the entire canvas. Subtle soft shadow under the camera body.'
jq -n --arg p "$PROMPT" '{instances: [{prompt: $p}], parameters: {sampleCount: 1, aspectRatio: "1:1"}}' > "$TMP_DIR/req.json"
curl -sS -X POST \
"https://generativelanguage.googleapis.com/v1beta/models/imagen-4.0-generate-001:predict" \
-H "x-goog-api-key: ${GEMINI_API_KEY}" \
-H "Content-Type: application/json" \
-d @"$TMP_DIR/req.json" > "$TMP_DIR/resp.json"
# Imagen response: { "predictions": [{"bytesBase64Encoded": "...", "mimeType": "image/png"}] }
python3 - "$TMP_DIR/resp.json" "$OUT_DIR/$SLUG.png" <<'PY'
import json, base64, sys
from pathlib import Path
from PIL import Image
resp_path, out_path = sys.argv[1], sys.argv[2]
d = json.loads(Path(resp_path).read_text())
if 'error' in d: sys.exit(f"Imagen error: {d['error']}")
b64 = d['predictions'][0].get('bytesBase64Encoded')
if not b64: sys.exit(f"No image in response: {json.dumps(d)[:300]}")
raw = Path(out_path).with_suffix('.1024.png')
raw.write_bytes(base64.b64decode(b64))
Image.open(raw).resize((288, 288), Image.LANCZOS).save(out_path, 'PNG')
print(f"wrote {out_path}")
PYSubject substitution patterns that worked on Imagen 4 (verified — these are the prompts that landed the four icons currently on Tarik's SD):
- Clipboard / checklist (planning):
a clipboard with three checked-off rows on cream paper, a brushed-metal clip at the top, soft cyan-to-teal gradient background that fills the entire canvas. The checkmarks are crisp green; the paper has the faintest grain. - Speech bubble (chat):
a single rounded speech bubble in soft glass white with a subtle inner glow, floating slightly off the surface with a soft drop shadow, against a purple-to-blue gradient background that fills the entire canvas. Glass-like translucency on the bubble. - Camcorder (video):
a vintage camcorder in three-quarter view — black matte body, brushed metal lens barrel, small red record light, a hint of leather grain on the grip — centered on a red-to-orange gradient background that fills the entire canvas. Subtle soft shadow under the camera body. - Rocket (launch):
a sleek white rocket in three-quarter view lifting off, brushed-metal nose cone and fins, a soft warm exhaust plume below, against an indigo-to-magenta gradient background that fills the entire canvas. Subtle highlight on the rocket body suggesting metal.
Residual artifact + the fix that worked: Imagen 4 always applies a dark beveled rounded-corner mask at the four canvas corners despite any "no rounded-square frame" wording. Empirically visible on the Stream Deck face (Tarik flagged it on every batch). The fix is a fixed-percentage inset crop of the 1024×1024 raw before the 288 resize:
# Tested: 12% inset is the sweet spot for Imagen 4 — eats the bevel
# without over-zooming the subject. 5% leaves the bevel visible; 15%+
# crops too tight on edge-detailed subjects.
INSET_PCT = 0.12
img = Image.open(raw_1024)
W, H = img.size
inset = int(min(W, H) * INSET_PCT)
img.crop((inset, inset, W - inset, H - inset)).resize((288, 288), Image.LANCZOS).save(out_288)Bake this into the recipe — don't ship a Stream Deck icon without it.
Gradient distinctness: Imagen 4 collapses similar gradient prompts to similar outputs (e.g. "purple-to-blue" for chat and "indigo-to-magenta" for rocket both came back purple-dominant). When generating a batch of icons, give each subject a deliberately different gradient family — cyan/teal, purple/blue, red/orange, orange/gold, green/emerald, slate/charcoal. Verify by eyeballing the batch side-by-side before applying.
Contrast for subjects with light-colored content: A bright-white subject (paper, glass) needs a dark gradient background to read. Asking for "cream paper on cyan-teal" produced a low-contrast result; "bright white paper on dark navy-to-deep-emerald" with an explicit "soft drop shadow under the subject separates it from the gradient" was the fix.
Fallback recipe (gemini-2.5-flash-image)
Use this only if Imagen is unavailable. Different aesthetic — flat silhouettes inside an iOS rounded-square frame with white background outside (not full-bleed). Tarik specifically disliked the white-frame output; the Imagen 4 recipe above is the default for a reason.
set -euo pipefail
: "${GEMINI_API_KEY:?export GEMINI_API_KEY before running}"
SLUG="chat-bubble" # output filename stem
OUT_DIR="$HOME/.config/aerospace/icons" # Stream Deck destination
TMP_DIR="$(mktemp -d)"
PROMPT='An iOS-style app icon: a chat bubble. Rounded square icon with soft purple-to-blue gradient background. Single white speech bubble centered, slight soft inner shadow giving depth. Glossy modern Apple design language, minimal, no text, no skeuomorphism, no rounded corners cropped (full square canvas — the rounded corners are visible inside the frame). High-resolution app icon, 1024x1024, plain white background outside the icon shape.'
# 1. Build request body. jq handles escaping safely for any prompt.
jq -n --arg p "$PROMPT" '{contents: [{parts: [{text: $p}]}]}' > "$TMP_DIR/req.json"
# 2. Call Gemini.
curl -sS -X POST \
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image:generateContent" \
-H "x-goog-api-key: ${GEMINI_API_KEY}" \
-H "Content-Type: application/json" \
-d @"$TMP_DIR/req.json" > "$TMP_DIR/resp.json"
# 3. Extract image + resize to 288×288 for Stream Deck.
python3 - "$TMP_DIR/resp.json" "$OUT_DIR/$SLUG.png" <<'PY'
import json, base64, sys
from pathlib import Path
from PIL import Image
resp_path, out_path = sys.argv[1], sys.argv[2]
d = json.loads(Path(resp_path).read_text())
if 'error' in d:
sys.exit(f"Gemini error: {d['error']}")
img_b64 = None
for p in d['candidates'][0]['content']['parts']:
if 'inlineData' in p:
img_b64 = p['inlineData']['data']
break
if img_b64 is None:
sys.exit("No image in response. Parts: " + json.dumps(d['candidates'][0]['content']['parts'])[:300])
raw = Path(out_path).with_suffix('.1024.png')
raw.write_bytes(base64.b64decode(img_b64))
Image.open(raw).resize((288, 288), Image.LANCZOS).save(out_path, 'PNG')
print(f"wrote {out_path} (288x288)")
print(f"raw {raw} (1024x1024, keep for re-resize if needed)")
PYThe .1024.png raw stays next to the 288 deliberately — if you later need a 192 or 512 (other devices, retina tiles), re-resize from the raw rather than re-generating (saves quota; same icon stays identical).
Prompt patterns that work
The shape that produced both samples/ (chat bubble + video camera, verified) is:
An iOS-style app icon: a
<SUBJECT>. Rounded square icon with<COLOR-A>-to-<COLOR-B>gradient background. Single white<SUBJECT-NOUN><POSITION>, slight soft inner shadow giving depth. Glossy modern Apple design language, minimal, no text, no skeuomorphism, no rounded corners cropped (full square canvas — the rounded corners are visible inside the frame). High-resolution app icon, 1024x1024, plain white background outside the icon shape.
Knobs that materially change the output:
| Knob | Effect | Default |
| Gradient pair | Sets the "mood" — purple/blue = communication, red/orange = recording, teal/cyan = focus/water, green/lime = health, pink/magenta = creative, slate/charcoal = developer. | Pick one matching the subject's role. |
| "Single white silhouette centered" | Forces clean iconography vs. a busy illustration. | Keep. |
| "no text, no skeuomorphism" | Critical. Without it Gemini sometimes adds a label ribbon or a stitched-leather frame. | Always include. |
| "no rounded corners cropped" + "plain white background outside" | Ensures the icon shape lives inside the frame, not bleeding to the edges. Lets the 288 resize land cleanly without losing the corner radius. | Always include. |
| "subtle inner shadow / depth" | Adds the iOS gloss. Drop for a flatter look. | Keep for SD branded keys. |
| Size hint "1024x1024" | Gemini honors it. Output is reliably 1024×1024 RGB PNG. | Always include. |
Mistakes that produce bad icons (observed):
- Asking for multiple objects → cluttered. One subject per icon.
- Mentioning text or a label → Gemini inserts garbled text.
- Asking for "transparent background" → it sometimes still bakes a checkered transparency pattern into the PNG. The "plain white background outside the icon shape" wording is the reliable way to get a clean fill.
- Omitting "rounded square" → you can get a rectangular billboard or a circle.
Output sizing for Stream Deck
- Generated: 1024×1024 RGB PNG.
- Stream Deck XL key target: 288×288 PNG. PIL
Image.LANCZOSresize from the 1024 is the right step (the recipe above does this). - Output destination for SD-bound icons:
~/.config/aerospace/icons/<slug>.png(the directory thestreamdeck-grid-update/streamdeck-workspace-buttonskills already read from). - After writing the icon, those skills handle the copy into the active page's
Images/subdir viaset-streamdeck-button.sh --iconor thecopy_iconhelper. This skill stops at "icon exists at the canonical path."
Sample icons (verified output)
Generated 2026-05-23 with the recipe above, exactly the prompts shown. Saved at samples/ for reference + future "match this style" prompting:
samples/chat-bubble-1024.png+samples/chat-bubble-288.png— purple-to-blue gradient, white speech bubble. Replacement candidate for the Slack key on Stream Deck XL row 0 (communication workspace).samples/video-camera-1024.png+samples/video-camera-288.png— red-to-orange gradient, white camcorder silhouette. Workspace key candidate for the recording workspace.
Both came out usable on the first generation. No regeneration needed for either. If a future generation comes out lower-quality, regenerate (Gemini is non-deterministic) before tweaking the prompt — usually a re-roll lands.
Optional post-processing
Dark-bg composite (rare — most iOS-style icons look fine raw on Stream Deck)
The Stream Deck XL's surround is matte black. iOS icons with light gradients (the chat bubble) read fine against that — the icon's own rounded-square frame separates it visually. Don't add a dark plate by default. Only if a particular icon has the same gradient color as adjacent keys (visual collision), or if you want the look of "icon floating in a dark tile," composite onto #1a1a1a (the utility-icon background color from streamdeck-grid-update):
from PIL import Image
icon = Image.open("chat-bubble-288.png").convert("RGBA")
plate = Image.new("RGBA", (288, 288), "#1a1a1a")
# Inset the icon to leave a 16px dark frame
sized = icon.resize((256, 256), Image.LANCZOS)
plate.paste(sized, (16, 16), sized if sized.mode == 'RGBA' else None)
plate.save("chat-bubble-288-darkplate.png", "PNG")Transparent-background trim (rarely needed)
If you want just the icon shape on transparent (for compositing into a different context), Gemini's "plain white background" wording makes white-keying straightforward. Threshold-key the white in PIL:
from PIL import Image
im = Image.open("chat-bubble-1024.png").convert("RGBA")
pixels = im.load()
for y in range(im.height):
for x in range(im.width):
r, g, b, a = pixels[x, y]
if r > 245 and g > 245 and b > 245:
pixels[x, y] = (0, 0, 0, 0)
im.save("chat-bubble-transparent.png", "PNG")Use sparingly — the inner icon shape (white speech bubble) shares the background color, so this also keys the subject. Better to ask the prompt for a specific non-white background and key that instead.
Failure modes
- Model 404 (
models/gemini-2.5-flash-image-preview is not found). Wrong model name. Usegemini-2.5-flash-image(no-previewsuffix). The-previewmodel was deprecated; the stable rename is what's live. responseModalities: ["IMAGE"]ingenerationConfigreturns 400 from this model. Don't include it — the model returns image parts by default. The plants-gemini text path uses nogenerationConfigeither; same shape works here.- Response has only a
textpart, noinlineData. Gemini occasionally returns a refusal or just a textual description. Re-roll once; if it happens twice in a row, the prompt is hitting a safety filter — usually because of a real-world brand name (e.g. "Slack icon" → safety-filtered). Use generic terms ("chat bubble", "messaging app icon", "communication icon") instead of brand names. This is also the right answer for the user's "replace Slack with a generic chat icon" use case — generic looks better and avoids the filter. - Image is rectangular, not square. Reinforce "rounded square" and "1024x1024" in the prompt; the model honors size hints when they're explicit.
- 288 resize looks soft. The 1024 raw is RGB, not RGBA, with antialiased edges against a white background. LANCZOS is the right downsampler; if the result still feels mushy, the raw probably is — regenerate. Don't downsample twice (1024 → 512 → 288 is worse than 1024 → 288).
Cross-references
- Consumers:
streamdeck-grid-update(operation H, "Generate icons"),streamdeck-workspace-button(step "Icon source"). Both skills already know to look in~/.config/aerospace/icons/. - Sibling:
svg-icon-renderfor utility line-art (arrows, refresh, gear). Use when the icon is a system affordance, not an identity. - Existing Gemini integration in this workspace (text-only, reference for env/header shape):
lib/plants-gemini.ts. - Verified prompt outputs: samples/.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

