add-ecosystem
Add a new ecosystem and base model to basemodel.constants.ts. Use when onboarding a new model…
Author and generate Civitai App store listing MEDIA — icons, covers and titled banners — through the measured author→light→normalise pipeline, and attach them (including for offsite apps, which the CLI refuses). Use when the user asks to brand a new app, re-do or improve an
$ npx -y skills add civitai/civitai --skill listing-media --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/listing-mediaContext preview
The summary Claude sees to decide when to auto-load this skill.
Author and generate Civitai App store listing MEDIA — icons, covers and titled banners — through the measured author→light→normalise pipeline, and attach them (including for offsite apps, which the CLI refuses). Use when the user asks to brand a new app, re-do or improve an
name: listing-media description: Author and generate Civitai App store listing MEDIA — icons, covers and titled banners — through the measured author→light→normalise pipeline, and attach them (including for offsite apps, which the CLI refuses). Use when the user asks to brand a new app, re-do or improve an app's icon / cover / banner, extend the brand system to more apps, judge listing-media quality, or asks why generated store art looks wrong. Screenshots of a RUNNING app are the sibling `app-capture` skill. argument-hint: "[brand <slug> | icon <slug> | cover <slug> | attach <slug> | audit]" allowed-tools: Bash, Read, Edit, Write, Glob, Grep
**This skill owns AUTHORING. `app-capture` owns capture + attach.** The split:
| | this skill | `app-capture` | |---|---|---| | generate an icon / cover / banner | ✅ | ✗ | | screenshot a running app | ✗ | ✅ | | attach to a listing, bounds-gated | defer to it | ✅ `.claude/skills/app-capture/scripts/attach.sh` | | attach for an **offsite** app | ✅ (the CLI refuses) | ✗ |
🔴 **Never hand-roll an attach.** `attach.sh` is dry-run by default, gates every asset against `.claude/skills/app-capture/scripts/store-bounds.json`, and already knows the shadow-revision and re-keyed-id traps. Two attach paths would drift.
1. **Bounds live in `store-bounds.json`, never here.** Aspect/size/px limits move. 2. **A hue table here is a snapshot.** Read the live values from `brand-assets-rev5-2026-08-13/scripts/emit-svg.py` (private infra repo). 3. **"Live" claims decay.** Every asset below was submitted, and a submission is not a listing — see *Nothing ships on push*. 4. **The cover grammar CHANGED on 2026-08-17** from untitled analogy photographs to titled banners. If you find an untitled cover, it predates that decision.
| | plate | mark | |---|---|---| | **onsite** apps | brand hue | light tint | | **offsite** apps | **graphite `#24262B`** | brand hue |
🔴 **The onsite hue wheel is FULL and this is why the offsite grammar exists.** Seven hues at ≥42° separation already consume 294° of 360°; nine needs 378° and eleven needs 462°, and there are four offsite apps. Squeezing two into the widest gaps caps minimum separation at 28.5° and hits the wall again at four. Putting a neutral plate behind a hue mark makes hue **non-scarce** for that family instead. Do not "simplify" the two grammars into one.
Generation cannot meet the icon spec and authoring cannot do lighting. Each half is used only for what it is good at:
| | authored flat | generated | |---|---|---| | meaning | **exact** | unreliable — 3 of 7 motifs wrong after two prompt rounds | | colour | **impossible** — flat fills 0/20, exact hex 1/10, alpha 0/20 | **exact** after normalisation |
1. **Author** the mark flat — `.claude/skills/listing-media/scripts/author-marks.py`. Zero Buzz. Exact geometry, exact hex. This is where motifs are chosen and rejected. 2. **Light** it with img2img, which preserves composition: `--ecosystem NanoBanana --checkpoint 2725610 --image <authored.png> --aspect-ratio 1:1` (covers `16:9`). ~208 Buzz at `--quantity 2`. 3. **Normalise** the plate — `.claude/skills/listing-media/scripts/finish-icons.py`. Applies a per-channel gain ONLY where saturation is low, so the plate lands without dulling the mark.
🔴 **`finish-icons.py` runs BOTH mask controls and aborts if either fails** — an on-target neutral must survive (plate branch) and a pure mark colour must pass through unchanged (mark branch). One control proves half a mask. Measured 2026-08-16: both dE 0.000, which is what makes the per-candidate numbers readings.
**Gate**: plate dE ≤3.0, mark hue ≤8°, mark saturation kept ≥95%, aspect 0.9–1.1.
🔴 **Always `--quantity 2` and read BOTH.** Composition drift is the known img2img failure and it is not rare: one comfy candidate invented a backing panel and turned round sockets into squares, scoring 12.45° hue / 232.7% saturation while its sibling passed. The gate caught it independently of the eye. One render is a coin flip.
Authoring is free, so draw every candidate and rule on it BEFORE any Buzz moves. Judge at **320 px**, the size the store serves an icon at, not at 1024.
🔴 **Escaping one wrong prior does not mean you landed on the right one.** The catalogue of priors that have actually bitten, all of them plausible on paper:
| motif | what it actually read as | |---|---| | dial with ticks OUTSIDE the ring | a **sun** (the "outward triangles + warm dot" prior) | | the same dial with ticks INSIDE | a **clock** — the fix created a new wrong prior | | ON AIR sign without text | a card, or a text input | | a restyle of the placeholder's own motif | the placeholder |
**Generation is good at real-world objects and bad at abstract constructions** — "a disc with a wedge cut out" came back a cone twice. That is the test for whether a motif is promptable at all; an authored mark has no such limit.
Left: title on ONE line, tagline beneath. Right: product imagery. Dark ground with the app's brand hue as a gradient glow. The literal prompt template and every per-app scene string: `.claude/skills/listing-media/reference/prompt-library.md`.
Copy comes from the LIVE `/api/v1/apps` name + tagline. 🔴 **Check both before writing them into a prompt** — one app's tagline is `null`, another's stored name is a name and a descriptor jammed together, and a third contains a `°` that generation mangles.
NanoBanana renders a headline reliably and a field of small labels not at all. Measured across one suite:
| scene shape | outcome | |---|---| | no text in the scene | clean every time | | a few row/column labels | mostly right,
Repo: civitai/civitai
Add a new ecosystem and base model to basemodel.constants.ts. Use when onboarding a new model…
Wire an existing ecosystem into the generation system. Adds generation support to…
Author a prompt-enhancement system prompt for a new ecosystem and register/update it on the…
Add a new trainable base model to BOTH trainers end-to-end — the in-app trainer (main Next.js…
Wire an existing ecosystem into the LoRA training system so it appears as a trainable base…
Deterministically drive a first-party Civitai App Block in the operator's browser and produce…