skills/generate-mascot/SKILL.md
Generate a character mascot for any project — a wide hero illustration plus two character-consistent variants (avatar + empty-state) — derive a favicon set from the mascot avatar, and wire them into the README and the landing page. Uses OpenAI gpt-image-1 (with /v1/images/edits for character consistency across variants) or falls back to Google Gemini Nano Banana 2. Derives the prompt from the README + 'Why <name>' rationale + any CONTEXT.md / parent PRD so the character matches the app's spine. Idempotent — re-run to regenerate. Use when the user says "generate a mascot", "give this project a mascot", "make a little character for the app", "brand this with a character", or "add a mascot to the README". Sibling of /ro:share-assets (which embeds this as its hero/mascot step) and /ro:generate-image (which is generic image-gen, not project-aware).
npx skillsauth add RonanCodes/ronan-skills generate-mascotInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Give the project a face: one wide hero illustration plus two square mascot variants of the same character. Drop them into public/brand/, wire them into the README and the landing/home page if one exists. The character stays consistent across all three because the variants are generated via OpenAI's /v1/images/edits endpoint with the hero as the reference image, plus a re-used CHARACTER REFERENCE paragraph.
This is the standalone artist tool. Run it on any existing project to add (or refresh) a mascot. /ro:share-assets calls it as its hero/mascot step; this skill can also be invoked directly.
public/brand/<app>-hero.png is missing on a project that looks shipped enough to warrant a face./ro:share-assets. (Favicons + app icons ARE generated here now — derived from the mascot avatar, see Step 6.5 — so the brand face and the browser-tab icon are always the same character.)/ro:generate-image (generic)./ro:generate-mascot # auto-detect everything from the cwd
/ro:generate-mascot --app acorn # override the app name (defaults to package.json name / wrangler name / dirname)
/ro:generate-mascot --hero-prompt "..." # override the auto-derived hero prompt
/ro:generate-mascot --no-variants # generate the hero only, skip the mascot variants
/ro:generate-mascot --no-favicon # skip deriving the favicon + app icons from the mascot avatar (Step 6.5)
/ro:generate-mascot --no-wire # skip the README + landing-page wiring; just produce the PNGs
/ro:generate-mascot --refresh # regenerate even if the files already exist (otherwise: skip and report)
/ro:generate-mascot --commit # commit the result (matches the repo's commit convention)
Find the project root (cwd if it has README.md + package.json / wrangler.jsonc / pyproject.toml, else walk up).
App name resolution order:
--app <name> flag.package.json "name".wrangler.jsonc / wrangler.toml "name".Lowercase + kebab-case the name. This is the <app> slug used in file names: public/brand/<app>-hero.png, <app>-mascot.png, <app>-mascot-asleep.png.
Read, in order, and stop when there's enough signal:
README.md first paragraph + any > **Why <name>:** blockquote near the top + any ## What it is section.CONTEXT.md (Pocock domain doc).docs/specs/*.md or docs/agents/*.md.gh issue view <n>).From those, extract:
If the README is too thin to extract these, ask the user via AskUserQuestion for exactly three things: the central metaphor, the mood (3 words), and one reference product. Don't interview further.
Order:
OPENAI_API_KEY_<APP_UPPER> (e.g. OPENAI_API_KEY_ACORN). The recommended per-app key convention.OPENAI_API_KEY (the catch-all).OPENAI_API_KEY_* if the user explicitly picks one via AskUserQuestion.billing_hard_limit_reached, fall back to Google Gemini via /ro:generate-image using GOOGLE_AI_API_KEY. Gemini gives a less reliable character match on variants but produces the hero fine.Verify the key with a low-cost call (GET /v1/models) before spending on a generation.
The single most important artefact. The paragraph that locks the character. Write it once, save it in working memory, re-use in every subsequent prompt verbatim.
Shape:
A small <species/build descriptor> <name'd character or unnamed>: <colour palette
of the creature>, <fur/feather/skin texture>, <distinctive feature like tail or
ears or eyes>, <expression vocabulary the character defaults to>. Same warm
painterly hand-painted illustration style: <2-3 brushwork / lighting / palette
notes that pin the visual style>.
Concrete example (Acorn): "The same small plump red squirrel from the reference image: reddish-brown fur, soft white belly, fluffy bushy tail, small triangular ears, gentle round dark eyes, friendly approachable expression. Same warm painterly hand-painted illustration style as the reference: soft amber and warm tones, gentle muted shadows, slight golden glow on the acorn."
The character reference is what carries the brand across the three (or more) variants. Drift happens when a variant prompt re-imagines the character; the reference paragraph prevents that.
mkdir -p public/brand
HERO_PROMPT="A warm hand-painted illustration in [palette from Step 2]: <the central metaphor, rendered concretely, with 2-3 supporting props that reinforce the app's spine>. <Mood adjectives from Step 2>. Painterly brushwork, gentle muted shadows, slight glow on the focal subject. No text, no logos, no UI elements, no people unless the persona is human. Wide composition, suitable for a website hero image."
curl -s -X POST https://api.openai.com/v1/images/generations \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg p "$HERO_PROMPT" '{model:"gpt-image-1", prompt:$p, size:"1536x1024", n:1, quality:"medium"}')" \
| jq -r '.data[0].b64_json' | base64 -d > "public/brand/<app>-hero.png"
# compress
pngquant --quality=70-90 --output "public/brand/<app>-hero.png" --force "public/brand/<app>-hero.png" \
|| sips -s format png --resampleHeightWidthMax 1200 "public/brand/<app>-hero.png" --out "public/brand/<app>-hero.png"
Target file size: 400-800 KB after compression. 2.4 MB out of gpt-image-1 medium quality is normal; pngquant gets it under 1 MB cleanly.
Skip if --no-variants. Otherwise generate two: an avatar version and one mood variant. Every variant prompt opens with the CHARACTER REFERENCE paragraph from Step 4 verbatim, and uses the hero PNG as the reference image for the /v1/images/edits call.
<app>-mascot.png (avatar)<CHARACTER REFERENCE paragraph from Step 4>
A square illustration. The character sits upright facing forward,
<pose anchored to the metaphor — e.g. "holding the glowing golden acorn with
both paws close to its chest" / "perched on a small branch looking forward">,
eyes open and gentle, looking thoughtful and approachable.
No text, no logos, no other props. Soft transparent background.
Suitable for use as an app mascot avatar.
Choose ONE mood that matches the app's central feature:
Filename: <app>-mascot-<mood>.png (e.g. acorn-mascot-asleep.png).
<CHARACTER REFERENCE paragraph from Step 4>
A square illustration. The character is <pose for the chosen mood>.
<Any prop the mood requires, kept minimal>.
No text, no logos, no other props (no extra background details).
Soft transparent background.
Suitable for use as a small empty-state / mood illustration.
curl -s -X POST https://api.openai.com/v1/images/edits \
-H "Authorization: Bearer $KEY" \
-F model=gpt-image-1 \
-F "image=@public/brand/<app>-hero.png" \
-F size=1024x1024 \
-F background=transparent \
-F quality=medium \
-F n=1 \
-F "prompt=$VARIANT_PROMPT" \
| jq -r '.data[0].b64_json' | base64 -d > "public/brand/<app>-mascot[-mood].png"
pngquant --quality=70-90 --output "public/brand/<app>-mascot.png" --force "public/brand/<app>-mascot.png"
Each variant is ~$0.04 at medium quality. Two variants over the hero = ~$0.12 per project, plus ~$0.16 for the hero. Budget ~$0.30 per mascot pass.
Always run this (unless --no-favicon). The browser-tab icon should BE the mascot, so the brand face and the favicon never drift apart. Source is the avatar variant public/brand/<app>-mascot.png (the square, transparent, forward-facing one from Step 6); if --no-variants was passed, fall back to a centre-crop of the hero.
The mascot is a painterly raster, so the favicon set is PNG + ICO (no vector favicon.svg — that's a logo mark, out of scope). Generate into public/ with ImageMagick (magick), falling back to sips:
SRC="public/brand/<app>-mascot.png" # avatar; else hero centre-crop
BG="#ffffff" # opaque bg for apple-touch (iOS ignores alpha); use the brand bg from Step 2 if there is one
# square master at 512, transparent
magick "$SRC" -background none -gravity center -resize 512x512 -extent 512x512 public/icon-512.png
magick public/icon-512.png -resize 192x192 public/icon-192.png # PWA manifest
magick public/icon-512.png -resize 32x32 public/favicon-32x32.png
magick public/icon-512.png -resize 16x16 public/favicon-16x16.png
# apple-touch: flatten onto opaque bg (iOS squares + drops transparency)
magick public/icon-512.png -resize 180x180 -background "$BG" -flatten public/apple-touch-icon.png
# multi-resolution .ico
magick public/icon-512.png -define icon:auto-resize=16,32,48 public/favicon.ico
pngquant --quality=70-95 --force --ext .png public/icon-512.png public/icon-192.png \
public/favicon-32x32.png public/favicon-16x16.png public/apple-touch-icon.png 2>/dev/null || true
sips fallback when ImageMagick is absent: sips -z 512 512 "$SRC" --out public/icon-512.png then sips -z N N per size; generate the .ico with magick only (sips can't write multi-res ico — if neither is available, ship favicon-32x32.png and skip the .ico, and say so).
Files written: public/favicon.ico, public/favicon-16x16.png, public/favicon-32x32.png, public/apple-touch-icon.png, public/icon-192.png, public/icon-512.png.
Head wiring (part of Step 7/8, skip with --no-wire): add to the app's <head> (TanStack Start __root.tsx head() / Next app/layout.tsx metadata / Astro layout):
<link rel="icon" href="/favicon.ico" sizes="any">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
If the project has no public/ yet (pre-scaffold), still write the files to public/brand/'s sibling public/ and note that the scaffold (or the share-assets / bootstrap story) will pick them up. /ro:share-assets defers to these files when they already exist rather than regenerating — the mascot is the source of truth for the favicon.
Insert near the top, after the # <Title> and any > **Why <name>:** blockquote, before the first prose paragraph. Markdown is the format because GitHub renders it.
<p align="center">
<img src="public/brand/<app>-hero.png" alt="<one-sentence alt text describing the illustration>" width="640">
</p>
If the README already has a hero block (idempotent re-run), replace it. Detect by grepping for public/brand/.*-hero.png in the README.
Skip with --no-wire.
Detect a home route in this order:
src/routes/index.tsx.app/page.tsx or pages/index.tsx.src/pages/index.astro.src/routes/+page.svelte.If a home route exists, insert a <figure> block above the page's main heading. Match the framework's idioms:
// TanStack Start / React (`src/routes/index.tsx`)
<figure className="overflow-hidden rounded-xl border border-neutral-800 shadow-lg">
<img
src="/brand/<app>-hero.png"
alt="<same alt text as the README>"
width={1200}
height={800}
className="h-auto w-full"
/>
</figure>
If the home route is auth-gated (the app sits behind Cloudflare Access or a login) and shows the hero only post-auth, that's fine. The hero is a brand asset, not a marketing one.
If the home route already references a *-hero.png in public/brand/, replace; otherwise insert. Idempotent.
One mascot per surface — do NOT stack the hero and the avatar on the same screen. If this surface already shows the hero (above), keep its wordmark text-only; do NOT also drop the small avatar next to it. Two of the same character on one screen reads as clutter and cheapens the painted hero. (Lesson from Settle + the ADHD helper, where the hero plus a 32px avatar by the wordmark stacked two capybaras on the sign-in page.)
The small <app>-mascot.png avatar belongs on surfaces that do NOT show the hero: the persistent app header / nav kicker on inner (post-auth) pages. If such an inner header exists (and it isn't the sacred primary working screen), replace its text-only kicker with a <div> containing the mascot at 32-48px alongside the name. If the only branded surface is the one already showing the hero, skip the avatar entirely.
Skip all wiring with --no-wire.
With --commit, stage public/brand/* and the touched README + home route file, and commit with the repo's commit convention (✨ feat: / 📝 docs: per repo CLAUDE.md). Respect the weekday-hours timestamp rule from ~/CLAUDE.md. Do NOT push automatically — leave that to the user or the next ship skill.
Without --commit, leave the working tree dirty and report the files written so the user can review before committing.
/ro:generate-mascot --audit-only reports which files are present and whether the README + home page reference them. Exit 0 if all present and wired; 1 if missing.
/ro:generate-image or a human illustrator./ro:share-assets (which calls this skill for the hero/mascot part, and now defers to the mascot-derived favicon set rather than making its own).Prefer OPENAI_API_KEY_<APP_UPPER> so spend stays scoped to the project. A billing-limit hit on one app doesn't block the others, and rotation is per-app. If a per-app key isn't set, the global OPENAI_API_KEY catches all. Pair with /ro:env to add a per-app key cleanly.
/ro:share-assets — wraps this skill as its hero/mascot step; generates the OG image, manifest, head meta. Favicons now come from THIS skill (Step 6.5, mascot-derived); share-assets defers to them when present./ro:generate-image — generic image gen (Gemini Nano Banana 2); used as the fallback when no OpenAI key is available./ro:env — adds API keys to ~/.claude/.env without them passing through chat./ro:share-assets § Step 3 — the embedded copy of these instructions, kept in sync./ro:share-assets so it can be invoked on any existing project, not only during a from-scratch scaffold./ro:share-assets to here (share-assets now defers). Per Ronan: "whenever we generate a new mascot, use that generated version as the favicon too." Added --no-favicon.testing
--- name: linear-pipeline description: The Fable orchestrator for a single dispatched Linear ticket. Holds almost no context itself; it receives `--issue <ID> --detached`, decides the stage sequence, and fans out a sub-agent per stage, passing forward only each stage's artifact (never re-derived, never inlined into its own context). Step zero, before any planning or stage routing, is a boundary triage against `canon/security-boundary.md` (#199): a match tags Ronan Connolly and stops the run, no
development
--- name: in-your-face description: Capture a chat-only answer into a durable artifact (markdown + HTML, PDF when cheap) and launch it automatically so the user cannot miss it. Use when user says "in your face", "don't let me lose this", "save that answer", "make that durable", or right after answering a substantive side question (a recipe, comparison, how-to, or generated prompt) that would otherwise die with the context. category: workflow argument-hint: [--no-open] [--vault <short>] [hint of
tools
One-shot headless OpenAI Codex CLI calls for background/admin AI tasks — summaries, classification, extraction, admin glue. The default engine for anything that runs AI constantly in the background (daemon-driven, per-event), because it bills the flat ChatGPT subscription instead of Claude usage or per-token API spend, and it keeps working while Claude is rate-limited. NEVER for coding — coding stays Claude. Use when a skill or daemon needs a cheap always-on AI call, when the user says "use codex", "ask codex", "codex as backup", or when building a background summarizer/classifier into a listener or loop. Reads auth from ~/.codex/auth.json (ChatGPT account, no API key).
research
Turn a warranty rejection, repair quote, or RMA email into a cited decision brief — legal read (NL/EU consumer law), is the part user-serviceable, live part and new-unit prices, repair-vs-DIY-vs-new economics, before-you-send-it checklist, deadlines. Use when the user pastes or screenshots a repair quote, warranty rejection, "not covered" email, onderzoekskosten fee, or asks "should I repair or replace this".