What does character-refs do?
Input a character description and output a set of character reference images—the character can come from anywhere: novel adaptations, your own stories, or a character you just thought of. One front full-body anchor image, and every other image references only this anchor. Use as reference images for video models (H3 / Wan / Seedance) or for human viewers.
{baseDir} = directory containing this file. Script {baseDir}/scripts/character-refs.mjs, zero dependencies, runs directly with node.
This is the only skill in the repository that actually generates images. Other skills only output prompts, because the model and art style can only be decided at generation time; this skill is that "moment"—the model is chosen by the user at Step 0, and the art style is selected when creating the character (default is anime, with a realistic preset available, and custom styles can also be used).
Core Rules (Read First, Then Act)
-
One and only one root per set: the front full-body anchor. Only this one is text-to-image; the rest are "anchor + one modification instruction." Hair, face, and collar details additionally reference a confirmed front-facing headshot. Images no longer reference each other— so if any image turns out bad, regenerate only that one without causing a chain reaction.
-
Tiered output, default to Tier 2:
Tier Content When to Generate 1 Front full-body (anchor) Always, first image 2 (default) Front-facing headshot, 90° side (facing right), back Generated by default 3 Detail images (up to 8): user-specified areas, most recognizable parts of the character; if none specified, default to hair / hair accessories, collar, cuffs, shoes When there are accessories or clothing details needing close-ups 4 (not default) 45° headshot (facing left) Only when explicitly requested by user H3 testing: providing only the front full-body gives correct clothing and back, but the face distorts; adding one headshot keeps the face most consistent. So default output includes Tier 2. For only the anchor (e.g., to check appearance first):
gen <asset.json> --tier 1. -
Model not locked: The main image and derived images can use different models; regenerating a single image can also switch models. Consistency comes from "same anchor," not from "same model" (tested: Qwen anchor + GPT derived, GPT anchor + Qwen derived, mixed within a group all align). Each image records its own model; mixed models in a group are only noted in the report.
-
Only three things are frozen: anchor file (sha256), art style snapshot, and text description fingerprint. If any changes, images depending on it are marked "expired"—only marked, not deleted, old versions are all retained.
Step 0 — Initial Setup (Once Per Machine)
node {baseDir}/scripts/character-refs.mjs config --show
If the output ends with "Missing: …", ask the user for these three things all at once:
- Model for main image (anchor) :
qwen— Qwen Image 2.1, via your own ComfyUI server. Fast (25–45s per image), precise dimensions, doesn't consume subscription quota. RequiresCOMFY_URL(optionalCOMFY_USER/COMFY_PASS), set in environment variables or a .env filecodex— codex's built-in image generation (GPT). No API key needed, but consumes ChatGPT subscription quota (Plus hits the limit around 25 images, recovers after a few hours), 80–160s per imageopenai— OpenAI Images API, defaultgpt-image-2. Pay-as-you-go, requiresOPENAI_API_KEY; can also point to a compatible API (--openai-base-url)custom:<name>— user's own image generation command, seereferences/models.md
- Model for derived images: Defaults to the same as the main image. Can be different (e.g., anchor with GPT, derived with Qwen)
- Whether to pause after generating the anchor for human confirmation: Recommended
yes—the anchor is the root of the whole set; if it's wrong, everything else is wasted. Choosingnoproceeds automatically, with a note in the report "not human-confirmed"; anchors that fail the check gate are not automatically approved—must be reviewed andconfirmed or regenerated
node {baseDir}/scripts/character-refs.mjs config --model qwen --confirm-anchor yes --qwen-env-file <.env path>
# Separate settings: --anchor-model codex --derive-model qwen
Configuration is saved to {baseDir}/config.local.json (not in version control, not overwritten on reinstall). It will be used by default from then on;
if the user says "switch to xx," rerun config. Do not print passwords or keys from .env—config --show already masks them.
Step 1 — One-Time Input
The user provides one paragraph describing the character—no need to ask item by item. You (the model) decompose it:
node {baseDir}/scripts/character-refs.mjs intake-template
Split the description into fields according to the template and write to <working directory>/<character-name>-intake.json. Rules (see references/intake.md):
langshould be the language the user spoke (Chinesezh, Englishen, Japanesejaare built-in). The confirmation table and report use this language; for other languages, first runui-template <lang>, translate the printed English text item by item, and place the whole block into the intake'suifield- Mark user-specified items as
stated, items you infer asinferred, and items filled by convention asdefault—mark them truthfully; the confirmation table uses this to show the user which items to check - Age, gender, and era cannot be inferred—ask the user. Only these three must not be invented; infer everything else without asking
engoes into prompts: always in English, no character names, no art style terms;textis for human reading, written inlang(old inputs withzhare still recognized)- Clothing is split into
top/bottom—headshots use only the top; if both are included, the model will pull the camera away - Detail images are chosen per character: prioritize user-named areas; if none named, pick the most recognizable parts of that character, with four default slots as fallback; facial features are not part of detail images (the headshot already shows them clearly). See
references/intake.md
node {baseDir}/scripts/character-refs.mjs intake-check <intake.json>
If there are errors, fix them until it passes. Once it passes, it prints a confirmation table—show it exactly as-is to the user, with inferred and default items marked. Proceed only after user confirms (or modifies and confirms).
Step 2 — Create Asset
node {baseDir}/scripts/character-refs.mjs new <intake.json> --out <output directory> [--look art-style]
Creates <output directory>/<character-name>/asset.json. The art style is snapped into the asset at this point; later changes to presets do not affect existing characters.
Art style: Use what the user specified, or default to anime if not specified. Characters in the same series must all use the same art style.
| Preset | Name recognized by --look |
|---|---|
| Anime (default) | 动漫 卡通 anime cartoon 二次元 アニメ |
| Realistic photo | 写实 realistic photo 真人 |
looks lists all presets. For custom styles (e.g., Chinese ink, American comic): look-template <closest preset> > my-look.json, then edit style (medium, lines),
clean (background and lighting, must keep white background), neg (negative prompt—for a drawn style, remove anime illustration from the negative prompts),
and for drawn art styles also change medium to drawn, then use --look my-look.json. Custom styles have not been tested; generate the anchor first to check.
To change the art style of an existing character:
node {baseDir}/scripts/character-refs.mjs restyle <asset.json> --look anime
After the change, all existing images (including the anchor) are marked expired; old images are kept, and regeneration starts from the anchor.
Step 3 — Generate Default Tier 2
node {baseDir}/scripts/character-refs.mjs gen <asset.json>
Without specifying view or tier, it fills in the first and second tiers—images not yet generated or expired.
If confirmation is configured: this command stops after generating just the anchor. Show the image to the user (via report or by directly opening the PNG). If the user says it's okay, then:
node {baseDir}/scripts/character-refs.mjs confirm <asset.json>
node {baseDir}/scripts/character-refs.mjs gen <asset.json> # Same command; continues with headshot, side, back
If unsatisfied, rerun gen <asset.json> front-full (generates v2; can use --seed to change seed, --model to change model),
or go back to Step 1 to modify the description. If the anchor is not confirmed, derived images are rejected. If configured to not confirm, all four images are generated in one command.
Step 4 — Upgrade Tiers on Demand
node {baseDir}/scripts/character-refs.mjs gen <asset.json> --tier 3 --reason "E03 has hair ornament and collar close-ups"
--reason documents why the tier was upgraded, recorded in the asset. You can also generate only specific images: gen <asset.json> detail-hair detail-neck.
Step 5 — Check and Regenerate
node {baseDir}/scripts/character-refs.mjs check <asset.json>
Each image passes through a code gate (dimensions 300–5760, aspect ratio 0.4–2.5, ratio matches expectations, ≤20MB, background sufficiently white), then checks for expiration. Exits with code 1 on failure. For images that failed the gate or are expired, regenerate only that specific image:
node {baseDir}/scripts/character-refs.mjs gen <asset.json> detail-neck --model codex
In the gate, "background sufficiently white" checks the four edges for full-body images, only the top edge and upper sides for headshots (since the body naturally touches the bottom), and is skipped for detail images with a clear note. The gate cannot check appearance, angle, or framing—always view the images after generation; these are eyeball checks.
Step 6 — Report
node {baseDir}/scripts/character-refs.mjs render <asset.json>... --out <output directory>/character-refs.html
Multiple characters can be combined. The language defaults to the asset's lang; switch temporarily with --lang en; for non-built-in languages without a ui field, pass translated text via --ui <file>,
if missing it will error directly rather than outputting a half-translated report. The UI text changes language, but character descriptions remain in their original language.
Tier 1 shows only one card; from Tier 2 onward, it uses a design sheet layout (left: headshot, right top: front/side/back, right bottom: detail strip),
detail images are hidden by default, with a one-click toggle on the page. Expired images have a red border; a legend at the top of the page explains, and hovering over a red-bordered image shows the reason; mixed models are indicated.
Step 7 — Report Back
In one sentence: which images were generated, which model was used, and the report path. Point out any images that failed the gate or are expired. Without looking at the images, do not say "Looks great."
Output Structure
<output directory>/
├── character-refs.html ← output of render
└── <character-name>/
├── asset.json ← character description + all versions of each image
└── default/ ← outfit (v1 only has "default" set)
├── front-full.v1.png ← anchor
├── face-front.v1.png
├── face-front.v2.png ← regeneration does not overwrite
└── detail-hair.v1.png …
Each PNG has embedded identifiers (character/outfit/view/version), model, and references to which images it was based on—so even if a PNG is copied elsewhere, its origin can be traced.
The structure of asset.json is documented in references/schema.md.
Boundaries
- Only two art styles have been tested in practice: realistic and anime (both run through Qwen with all four tiers); results for custom styles depend on how they are written—generate the anchor first to evaluate
- Detail images only cover items physically attached to the character (hair ornaments, collar, cuffs, shoes); props belong to novel-art
- Known limitations: small scars tend to appear as fresh wounds; large-area details like double-breasted jackets are framed too far away; Qwen's collar details may render rough fabric as velvet (regenerate that image with codex); skin exposed in Qwen detail images may appear older (the prompt already includes an age statement, effective for the 16-year-old test case but not extensively validated)
- The
openaiadapter has only been tested with a local fake server for request formatting—never run with a real API key; generate one anchor image first to verify - The same set of prompts has been tested on both Qwen and GPT; results for custom models depend on that model
Self-Test
node {baseDir}/scripts/selftest.mjs
230 assertions, no model calls, no quota consumption: PNG identifier read/write, check gate, input validation, multilingual confirmation table and report, art style presets and restyling, prompts, expiration propagation, request format for image adapters (Qwen and GPT Image using local fake servers), and a full workflow using custom commands. Run this after modifying any script.
Built-In Examples
{baseDir}/examples/阿禾-description.txt is a one-paragraph user-style description (a 1920s Jiangnan tea-picking girl),
阿禾-intake.json is the decomposed input with inferred and default fields marked. Sample images are not in version control; to see them, run through your own model.