skills/share-assets/SKILL.md
--- name: share-assets description: From one source brand mark, generate the full set of identity + share assets (favicon, app icons, PWA manifest, static OG image, Twitter card, dynamic OG route, JSON-LD) and wire them into a TanStack Start app's head + public/. Use when starting a new app or when an existing app's link previews look broken on X / WhatsApp / iMessage / LinkedIn / Slack. category: development argument-hint: [--source <path-to-svg>] [--no-dynamic-og] [--brand-color <hex>] <app-na
npx skillsauth add RonanCodes/ronan-skills skills/share-assetsInstall 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.
Builds out the brand-identity + social-share asset set every shipped app needs but every new repo forgets. One source SVG mark in, the full deliverable set out:
public/favicon.ico (multi-resolution, 16/32/48)public/favicon.svg (vector, theme-aware via prefers-color-scheme)public/icons/icon.svg (master)public/icons/icon-192.png, icon-512.png, icon-maskable-512.png (PWA manifest)public/icons/apple-touch-icon.png (180px)public/manifest.webmanifest (with the icons array wired)public/og-static.png (1200x630 fallback for any URL)src/routes/api/og.ts (dynamic per-URL OG via Satori on Workers; delegates to /ro:og-image-dynamic if not yet wired)src/routes/__root.tsx: og:*, twitter:*, link rel="icon", link rel="manifest", link rel="apple-touch-icon", theme-color, JSON-LD application schema--audit-only to report what's missing without writing.Every link a customer or investor pastes into X / Reddit / LinkedIn / WhatsApp / iMessage / Slack triggers an OpenGraph fetch. The default "no preview" rendering looks broken; a wrong-aspect-ratio static image looks lazy; a per-URL dynamic image looks polished. Same with the favicon: a default Vite/Next.js placeholder favicon is the visual equivalent of a "Hello World" landing page.
Fix once, fix permanently, automate the rest.
/ro:share-assets --source brand/dataforce-mark.svg dataforce
/ro:share-assets --source ./logo.svg --brand-color "#10b981" my-app
/ro:share-assets --no-dynamic-og my-app # skip the /api/og route, static-only
/ro:share-assets --no-hero my-app # skip the README/landing hero illustration
/ro:share-assets --source-hero ./hero.png my-app # bring your own hero image, skip generation
/ro:share-assets --hero-prompt "calm, monoline cat" my-app # override the auto-derived prompt
/ro:share-assets --no-mascots my-app # skip the avatar / empty-state mascot variants
/ro:share-assets --audit-only # report gaps; don't write
If --source is omitted, the skill prompts (AskUserQuestion) for an existing brand mark path or offers to call /ro:generate-image to create one from a brief.
Hero + mascot variants are on by default. Every new app gets: (a) a wide hero illustration (public/brand/<app>-hero.png) for README + landing, and (b) two character-consistent mascot variants (<app>-mascot.png for avatar use, <app>-mascot-asleep.png or another mood for empty states). Character consistency comes from using the hero as the reference image in OpenAI's /v1/images/edits endpoint, plus a CHARACTER REFERENCE paragraph reused across variant prompts. See Step 3.
Verify the source SVG:
If the source fails, prompt for a different path or call /ro:generate-image with a "brand mark, square, single-colour, modern" prompt.
Use ImageMagick (magick on macOS / convert on Linux) or sharp (Node) to derive PNGs at 16, 32, 48, 180, 192, 512. Bundle the 16/32/48 PNGs into a multi-resolution favicon.ico:
magick -density 300 -background none source.svg \
-define icon:auto-resize=16,32,48 public/favicon.ico
magick -density 300 -background none source.svg -resize 180x180 public/icons/apple-touch-icon.png
magick -density 300 -background none source.svg -resize 192x192 public/icons/icon-192.png
magick -density 300 -background none source.svg -resize 512x512 public/icons/icon-512.png
# Maskable: pad with 20% safe zone on all sides
magick -density 300 -background "#<brand-color>" source.svg \
-resize 320x320 -gravity center -extent 512x512 \
public/icons/icon-maskable-512.png
cp source.svg public/favicon.svg
cp source.svg public/icons/icon.svg
Theme-aware favicon (optional but nice): the SVG can use <style> with prefers-color-scheme: dark so the favicon adapts to the user's OS. Document this in the source.
Delegates to /ro:generate-mascot for the implementation. That skill is the standalone artist tool; this one wraps it inside the broader share-assets suite.
It produces three character-consistent images under public/brand/:
<app>-hero.png — wide hero (1536x1024), the README + landing-page anchor image.<app>-mascot.png — square avatar (1024x1024, transparent), the canonical mascot for headers / app-icon foreground.<app>-mascot-<mood>.png — square mood variant (1024x1024, transparent) matching the app's spine (asleep for nightly-job apps, curious for research, writing for notes, etc.).The variants are character-consistent because /ro:generate-mascot uses OpenAI's /v1/images/edits endpoint with the hero as the reference image, plus a re-used CHARACTER REFERENCE paragraph in every variant prompt.
Pass through the flags share-assets surfaces (--no-hero → --no-hero --no-variants; --no-mascots → --no-variants; --source-hero → --source-hero; --hero-prompt → --hero-prompt). And the README + landing-page wiring is handled by /ro:generate-mascot Step 7-8.
See /ro:generate-mascot for the full instructions (prompt derivation, key resolution, generation, compression, README + landing wiring, audit mode).
public/og-static.png is the fallback when /api/og isn't reachable or for very old crawlers. 1200x630, brand-coloured, with the app name and a one-line tagline.
If the user already has a designed marketing hero, derive from that. Otherwise compose a minimal layout:
magick -size 1200x630 \
-background "#<brand-color>" \
-fill white \
-font Inter-Bold \
-pointsize 92 \
-gravity center \
-annotate 0 "<App Name>" \
public/og-static.png
manifest.webmanifest{
"name": "<App Name>",
"short_name": "<Short>",
"description": "<one-line description>",
"start_url": "/?source=pwa",
"scope": "/",
"display": "standalone",
"display_override": ["standalone", "browser"],
"background_color": "#ffffff",
"theme_color": "#<brand-color>",
"orientation": "portrait-primary",
"categories": ["productivity"],
"icons": [
{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" },
{ "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" },
{ "src": "/icons/icon-maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
]
}
In src/routes/__root.tsx head function:
const SITE_ORIGIN = 'https://<your-domain>'
const TITLE = '<App Name>: <one-line tagline>'
const DESCRIPTION = '<150-character description>'
const OG_IMAGE = `${SITE_ORIGIN}/api/og` // dynamic; falls back to /og-static.png if route unreachable
head: () => ({
meta: [
{ charSet: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ title: TITLE },
{ name: 'description', content: DESCRIPTION },
{ name: 'theme-color', content: '#<brand-color>' },
// OpenGraph
{ property: 'og:type', content: 'website' },
{ property: 'og:site_name', content: '<App Name>' },
{ property: 'og:title', content: TITLE },
{ property: 'og:description', content: DESCRIPTION },
{ property: 'og:url', content: SITE_ORIGIN },
{ property: 'og:image', content: OG_IMAGE },
{ property: 'og:image:width', content: '1200' },
{ property: 'og:image:height', content: '630' },
// Twitter / X
{ name: 'twitter:card', content: 'summary_large_image' },
{ name: 'twitter:title', content: TITLE },
{ name: 'twitter:description', content: DESCRIPTION },
{ name: 'twitter:image', content: OG_IMAGE },
],
links: [
{ rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' },
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
{ rel: 'apple-touch-icon', href: '/icons/apple-touch-icon.png' },
{ rel: 'manifest', href: '/manifest.webmanifest' },
],
scripts: [
{
type: 'application/ld+json',
children: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'WebApplication',
name: '<App Name>',
url: SITE_ORIGIN,
description: DESCRIPTION,
applicationCategory: '<Productivity / Game / Tool>',
operatingSystem: 'Web',
}),
},
],
})
Delegates to the /ro:og-image-dynamic skill for the implementation. The skill scaffolds src/routes/api/og.ts using Satori + @resvg/resvg-wasm on Workers.
When this skill (/ro:share-assets) runs and the OG route doesn't exist yet, it invokes /ro:og-image-dynamic --runtime cf automatically. Pass --no-dynamic-og to skip and stick with the static og-static.png.
After scaffolding, the skill runs:
curl -sI https://<deployed-url>/favicon.ico # 200 image/x-icon
curl -sI https://<deployed-url>/favicon.svg # 200 image/svg+xml
curl -sI https://<deployed-url>/manifest.webmanifest # 200 application/manifest+json
curl -sI https://<deployed-url>/brand/<app>-hero.png # 200 image/png, ~400-800kb (skip if gated)
curl -sI https://<deployed-url>/og-static.png # 200 image/png, ~30-80kb
curl -sI https://<deployed-url>/api/og # 200 image/png if dynamic wired
And renders the head + checks:
curl -s https://<deployed-url> | grep -E 'og:image|twitter:card|rel="(icon|manifest|apple-touch)"'
Then opens https://www.opengraph.xyz/?url=https://<deployed-url> and https://cards-dev.twitter.com/validator (if it still exists) to visually verify the share preview.
--audit-only reports what's missing without writing:
✓ favicon.svg
✓ favicon.ico
✗ apple-touch-icon.png (missing)
✓ manifest.webmanifest
✗ icon-maskable-512.png (missing; manifest references it)
✗ public/brand/<app>-hero.png (missing; README + landing have no hero illustration)
✗ public/brand/<app>-mascot.png (missing; no avatar mascot)
✗ public/brand/<app>-mascot-asleep.png (missing; no empty-state mascot variant)
✗ /api/og route (no dynamic OG)
✓ og-static.png
✓ Head meta tags (og:*, twitter:*)
✗ JSON-LD application schema (missing in __root.tsx)
Returns exit code 0 if all present, 1 if any missing. Useful in CI as a launch-readiness gate.
/ro:generate-image or use an existing source).useHead() pattern in route components, scoped to each story)./ro:seo-launch-ready (which handles sitemap, robots, JSON-LD basics, canonical URLs)./ro:pwa-install (which handles service worker + install prompt).For the hero step, prefer a per-app key (OPENAI_API_KEY_<APP_UPPER>) over a global one. Reasons: budgets stay scoped to the project that spent them; a billing-limit hit on one app doesn't block the others; rotation is per-app. The skill resolves keys in this order: app-specific → global OPENAI_API_KEY → fallback to /ro:generate-image (Gemini, different key entirely). Pair with /ro:env to add a new key cleanly without it landing in the chat transcript.
[[ideal-tech-setup]] § Greenfield Spec Baseline — share-assets is item #11/ro:og-image-dynamic — wires the per-URL dynamic OG route/ro:seo-launch-ready — sitemap, robots, JSON-LD, canonical URLs/ro:pwa-install — manifest + service worker for installability/ro:app-polish — 10-point launch-readiness audit (this skill is one of the points)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".