Install
$ agentstack add skill-lossless-group-lossless-agent-skills-generate-consistent-og-images ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Generate Consistent OG Images
> Two variables per request: aspect ratio and a one-sentence prompt. Everything else — brand reference image, color palette, style type, negative prompt, seed, rendering speed, magic-prompt flag — is locked at the project's DESIGN.md level so every image in a set looks like it belongs in the same family.
When to use this skill
- A site, splash page, plugin page, or fundraise deck needs an OG image generated for the first time
- An existing OG image needs to be refreshed (rebrand, stale visual, new feature launch)
- A new format is needed alongside an existing one (e.g. you have
bannerbut now needbanner_tallfor WhatsApp / iMessage) - The user asks for "an image for this page" or "an image for this component" that may or may not follow the OG canon
- Scaffolding a new Astro Knots site that doesn't yet have a
DESIGN.mdat all - "The link unfurl looks wrong" — the OG image is missing, off-brand, or in the wrong format
Philosophy (Lossless OG preference)
The Lossless Group optimizes share imagery for mobile chat previews — iMessage and WhatsApp first — and treats LinkedIn / Twitter / Bluesky / Slack as concentric rings around that target.
In practice this means:
- Tall aspect ratios get production attention, not just landscape banners.
3x4(WhatsApp / iMessage default) and2x3(extra-tall) variants are first-class assets, not afterthoughts. - Subject lives in the lower portion of the frame by default so the top half stays clean for the receiver's preview crop (chat platforms aggressively top-crop tall images).
- The 1200×630 OG-canonical banner is one format among several, not the only target. Other social-specific banners (LinkedIn portrait, Twitter Card) are still produced when needed, but never at the expense of a polished tall variant.
This shapes the aspect-ratio enum below and the naming convention.
Prerequisites
IDEOGRAM_API_KEY in ~/.secrets
Same pattern as the crawl-fetch-ingest skill: keys never live in source.
# ~/.secrets (chmod 600)
export IDEOGRAM_API_KEY="..."
# ~/.zshenv adds one line (already in place if other Lossless skills work)
[ -f ~/.secrets ] && source ~/.secrets
No MCP server is required. Ideogram's v3 endpoint accepts a direct HTTPS request with Api-Key header. Bash + curl is enough. If a future MCP wrapper is preferred, register it the same way the firecrawl/tavily wrappers are registered in crawl-fetch-ingest's setup.md — but it's not necessary for this skill.
A target project with a known visual identity
The skill assumes the project has some established aesthetic. If it doesn't — brand-new site, no design decisions yet — that conversation has to happen before image generation. Use the astro-knots skill or pair with the user on a Brand & Style first pass before invoking this one.
The flow
Five steps, in order. Steps 1–2 are setup the first time the skill runs against a project; steps 3–5 are the per-image work that repeats.
Step 1 — Check (or create) DESIGN.md
Look for DESIGN.md at the project root.
- If present: verify it has YAML frontmatter and the standard Google Stitch spec sections (
Brand & Style,Colors,Typography,Layout,Elevation & Depth,Shapes,Components). - If absent: hand off to the
maintain-design-mdskill to bootstrap one. That skill owns the canonical authoring flow, the spec mapping, and the scaffold template (templates/design-md-scaffold.md). Once a first-passDESIGN.mdexists, return here to add theimagery:extension and proceed.
The skill cannot proceed to image generation without a DESIGN.md because the imagery recipe (next step) references its color palette tokens.
Step 2 — Check (or add) the imagery: block to DESIGN.md
The imagery: block is outside the Google Stitch spec's standard token groups, but spec-compliant consumers preserve unknown top-level keys (per the spec's "Consumer Behavior for Unknown Content" table). It's safe to keep there as the single source of truth.
The canonical block lives at templates/imagery-block.yaml in this skill. Open that file, adapt three things to the target project, paste into the project's DESIGN.md frontmatter (after components:, before the closing ---):
| Field | What to adapt | |---|---| | style_reference.path | The project's canonical hero/banner image. If the project has none yet, generate a one-off using a quick prompt + the project's colors: palette, save it, and use that as the reference for all subsequent runs. | | color_palette.members | Map from the project's colors: tokens to Ideogram's color_hex + color_weight shape. Weight the surface background highest (0.4–0.5) and brand accents lower. | | defaults.seed | Pick a fixed seed for the project. Any integer is fine; bump only when the canon shifts. |
Everything else in templates/imagery-block.yaml — style_type: AUTO, magic_prompt: OFF, rendering_speed: QUALITY, the aspect_ratios enum, the negative_prompt set, the prompt constraints — is project-agnostic and should be copied verbatim. The whole point of locking those is that they don't vary across projects either.
> API constraint worth knowing: style_type: AUTO is required whenever style_reference_images is uploaded. The v3 endpoint rejects DESIGN / REALISTIC / FICTION in that combination with the error "Please use AUTO or GENERAL style type with stylecodes, stylereferenceimages or stylepreset." The reference image carries the aesthetic; AUTO lets it through.
The matching prose section (## Imagery) belongs in the body of DESIGN.md immediately before ## Do's and Don'ts. The reference implementation in content-farm/splash/DESIGN.md is the canonical example.
Step 3 — Construct the request
Read the project's DESIGN.md imagery: block. The Ideogram request is built by:
- Taking every field from
imagery.defaultsas-is. - Taking
imagery.color_paletteas-is. - Uploading the file at
imagery.style_reference.pathasstyle_reference_images. - Taking
imagery.negative_promptas-is. - Picking one entry from
imagery.aspect_ratiosforaspect_ratio— based on the user's stated target format (see § "The aspect-ratio enum" below). - Authoring one prompt for the
promptfield — see § "Prompt convention" below.
No other field varies. If the user asks to vary something else (e.g., "use a brighter palette"), that's a DESIGN.md change, not a per-request override.
Step 4 — Call Ideogram v3 generate
The canonical curl call lives at templates/ideogram-request.sh. Substitute {prompt} and {aspect_ratio} and run. The script reads IDEOGRAM_API_KEY from the environment (~/.secrets).
POST https://api.ideogram.ai/v1/ideogram-v3/generate
Content-Type: multipart/form-data
Api-Key: $IDEOGRAM_API_KEY
The response carries a url for each generated image. Images expire on the Ideogram side; download immediately to the target project's public/ (or equivalent static asset directory).
Step 5 — Save with the canonical naming convention
ogimage__{Site-Name}--{Format-Or-Variant}.{ext}
Site-Name— the project's canonical name in PascalCase or Hyphen-Joined-PascalCase:Content-Farm,Reach-Edu-Hub,Calmstorm-Decks,Perplexed.Format-Or-Variant— one of:Default,Banner,BannerTall,BannerTallMax,Portrait,PortraitTall,Square.Defaultaliases whichever format is the canonical share image (typicallyBanner).- Extension: prefer
.jpgper theopen-graph-share-seo-geoskill's JPEG-over-WebP rule. Convert from Ideogram's PNG output withffmpeg -i in.png -q:v 2 out.jpg(quality 2 is near-lossless and lands ~95 KB at 1200×630).
Example set for content-farm:
public/ogimage__Content-Farm--Default.jpg # the canonical banner (also serves as style_reference)
public/ogimage__Content-Farm--Banner.jpg # alias of Default
public/ogimage__Content-Farm--BannerTall.jpg # WhatsApp / iMessage default tall
public/ogimage__Content-Farm--BannerTallMax.jpg # dramatic-tall variant
public/ogimage__Content-Farm--Portrait.jpg # LinkedIn portrait / IG feed
public/ogimage__Content-Farm--PortraitTall.jpg # Stories / Reels / TikTok
public/ogimage__Content-Farm--Square.jpg # avatars, square unfurls
If the image is destined for a CDN (per the open-graph-share-seo-geo skill's "host the OG image remotely" rule), upload there as well; the same file naming travels.
The aspect-ratio enum
| Format key | Ideogram aspect_ratio | When to use | |---|---|---| | banner | 16x9 | Default share — OpenGraph, Twitter / X, Slack, generic | | banner_tall | 3x4 | WhatsApp & iMessage chat-preview cards (the Lossless default tall) | | banner_tall_max | 2x3 | Dramatic-tall variant; use when the subject genuinely benefits from height | | portrait | 4x5 | LinkedIn portrait, Instagram feed post | | portrait_tall | 9x16 | Instagram Stories, Reels, TikTok | | square | 1x1 | Avatars, square OG fallbacks, Discord embeds |
The keys live in imagery.aspect_ratios of the project's DESIGN.md; the values may differ if a project has a different target mix, but the format names stay the same across projects so cross-site automation can talk in one vocabulary.
Prompt convention
The prompt is the only free-text channel in the request. Two ingredients per prompt, nothing else:
- Subject — what the image depicts. One short noun phrase. "Isometric sprout growing from a stack of paper-cut tiles", "Three nested transparent cubes drifting above a horizon line", "A spool of thread unwinding into a typeset block of characters".
- Composition — empty space as a first-class subject. This is the rule that matters most for share imagery (where SVG overlay text needs clean room). Lead the prompt with the empty region — declare it explicitly, give it concrete content (a "dark gradient sky", a "muted atmospheric backdrop"). Then state what fills the bottom region. The model treats both clauses as renderings the sky is just as describable as the quill — and the subject can't grow into the overlay zone because the sky is already there.
Bad framing (subject-first; produces 85%-tall subjects that swallow the overlay zone):
> "An isometric quill pen with paper-leaf feathers, growing from a stack of paper-cut card tiles, in the lower third of the frame, top two-thirds open."
Good framing (empty-region-first, two clauses, explicit numeric split):
> "Top 1/3 of frame is empty negative space, dark gradient sky. Bottom 2/3 contains an isometric quill pen with paper-leaf feathers, growing from a stack of paper-cut card tiles."
Three rules that survive aspect-ratio changes:
- Lead with the empty region, not the subject. The first sentence describes what's empty and what content fills that emptiness (a sky, a backdrop, a gradient).
- Use explicit numeric proportions ("top 1/3 / bottom 2/3"), never soft terms ("lower portion", "below the horizon").
- Reinforce in
negative_promptwith the specific failure mode you observe (e.g.subject in top half). The negative prompt isn't a substitute for the positive empty-region declaration — it's a belt alongside the positive suspenders.
The underlying principle: empty space won't be left as residue. It has to be declared, named, and given content — otherwise the subject expands to fill the canvas. This was discovered empirically over iter1 → iter2 → iter3 on the Perplexed OG set; iter1 and iter2 used subject-first framing and produced subjects at 75-85% canvas height; iter3 used empty-region-first framing and produced subjects at 40-65% canvas height with the upper region genuinely empty.
Target ≤220 characters total. Past that, hard composition asks start losing to subject elaboration.
Forbidden in the prompt (already encoded via locked channels, never repeat):
- Brand names (
Lossless,Content Farm, plugin names) - Color words (
cyan,dark,warm) - Aesthetic adjectives (
vibrant-minimal,build-in-public,monospace) - Texture descriptors (
paper-cut,isometric,atmospheric) — unless the subject genuinely requires them and the style reference image already establishes them as canon
Composition templates that survive aspect-ratio shifts cleanly. Note the two-clause structure — empty-region declaration first (with concrete content), subject placement second:
Top 1/3 of frame is empty negative space, dark gradient sky. Bottom 2/3 contains {subject}.Left 1/3 of frame is empty negative space, soft atmospheric backdrop. Right 2/3 contains {subject}.Top half of frame is empty atmospheric gradient. Bottom half contains {subject}, centered horizontally.Top-right 2/3 of frame is empty negative space, dark muted sky. Bottom-left 1/3 contains {subject}.
When to break the rules (per-page / per-component custom imagery)
The locked recipe is for share imagery — the assets a third party sees when a link is unfurled. The user / team may also need:
- A hero illustration for a specific page or section
- An icon-like image for a component card
- A decorative asset embedded in long-form content
- A presentation slide background
These are legitimate departures from OG-image canon. They may use different palette weights, different aspect ratios, different style references, even a different style_type. When the user asks for one of these:
- Confirm out loud that this is a display asset, not a share asset. ("Just so I have this right — this image is meant to live inside the page as illustration, not be the page's OG share preview. Is that correct?")
- Author a prompt that's free of OG-image constraints — the "subject in lower third" rule doesn't apply when there's no SVG overlay to leave space for.
- Pick the aspect ratio that fits the layout slot, even if it's not in the
aspect_ratiosenum (use Ideogram'sresolutionfield instead ofaspect_ratiofor pixel-exact sizing). - Save outside the
ogimage__naming convention.illustration__Section-Name--Variant.jpg,hero__Page-Name.jpg, or whatever the project's local convention is — but never use theogimage__prefix, since that name implies the canonical recipe was followed.
The point: don't pollute the OG-image set with one-off custom imagery, and don't constrain one-off custom imagery with OG-image rules.
Two short worked examples
Example A — refreshing the splash's WhatsApp tall variant
User says: "Regenerate the WhatsApp tall card for content-farm."
# 1. Project: content-farm/splash. DESIGN.md exists, imagery: block exists.
# No setup work needed.
# 2. Pick aspect ratio: banner_tall (3x4)
# 3. Author one-sentence prompt with subject + composition:
PROMPT="Top 1/3 of frame is empty negative space, dark gradient sky. Bottom 2/3 contains an isometric sprout growing from a stack of paper-cut tiles."
# 4. Call Ideogram (see templates/ideogram-request.sh):
bash templates/ideogram-request.sh "$PROMPT" 3x4
# 5. Download response[0].url, convert PNG → JPEG, save as:
# public/ogimage__Content-Farm--BannerTall.jpg
Example B — bootstrapping a brand-new splash
User says: "I just scaffolded reach-edu-hub. Generate the OG banner."
# 1. Check for DESIGN.md at reach-edu-hub/ → absent
# 2. Pair with the user to author DESIGN.md from the spec:
# - Frontmatter token groups (colors, typography, rounded, spacing, components)
# - Eight prose sections in spec order
# Reference: github.com/google-labs-code/design.md and
# content-farm/splash/DESIGN.md as a shape example
# 3. No style_reference exists yet. Generate a one-off with only color_palette
# + style_type=AUTO to seed the canon, save as the reference, then add
# the imag
…
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [lossless-group](https://github.com/lossless-group)
- **Source:** [lossless-group/lossless-agent-skills](https://github.com/lossless-group/lossless-agent-skills)
- **License:** MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.