CLI reference¶
Two identical entry points are installed: comicforge and the short alias
cmf, used throughout these docs. python -m comicforge works too.
Commands at a glance¶
| Command | Does |
|---|---|
init |
Scaffold a new data-only project |
render |
Render a comic page spec |
scene |
Render a standalone illustration |
panel |
Render one panel, or every panel, for review |
character |
Render one character on a plain canvas |
validate |
Check a spec against its libraries, without drawing |
characters |
Print the character manifest as JSON |
scenes |
Print the scene manifest as JSON |
inspire |
Generate reference images from a theme + descriptions |
Asset directory flags¶
render, scene, panel and validate all accept the same three overrides:
| Flag | Overrides the spec key |
|---|---|
--library <dir> |
library: |
--scenes <dir> |
scenes_dir: |
--pixel-dir <dir> |
pixel_dir: |
A flag wins over the spec key. Flags resolve against your current working directory; relative paths inside a spec resolve against the spec file's directory. That difference is deliberate and it is the single most common source of confusion — see path resolution.
Default output¶
render, scene, panel and character all accept -o / --out. Omit it
and the file lands in a gitignored output/ directory with a timestamp:
output/slepice-20260614-191613.png
output/slepice-r0c1-20260614-191702.png
output/slepice-panels-20260614-191744/
Successive renders accumulate side by side, so you can watch a page evolve. Pass
-o when you want an exact path. The format follows the extension —
.svg, .png or .pdf.
init¶
Scaffolds a data-only project: the characters/, scenes/, pixel/ and
pages/ asset directories, a renderable pages/hello.yaml, a seed
pixel/heart.yaml, a .gitignore, a project README.md, and a copy of the
authoring skill under .claude/skills/comicforge/.
Idempotent — files that already exist are left alone and reported as skipped.
| Flag | Meaning |
|---|---|
--force |
Overwrite the scaffolded files instead of skipping them |
Re-run with --force after upgrading the engine to refresh the bundled skill.
render¶
Renders a comic page — a spec with rows:. Handed a type: scene spec it
refuses and points you at cmf scene rather than crashing.
cmf render pages/slepice.yaml -o slepice.png
cmf render pages/slepice.yaml -o slepice.pdf # true vector
cmf render pages/slepice.yaml # -> output/slepice-<ts>.png
scene¶
Renders a standalone illustration — one background filling the canvas, no
panel grid. Handed a page spec it refuses and points you at cmf render.
panel¶
cmf panel <spec.yaml> [-o <output>]
[--row 0] [--col 0] [--all] [--scale 0.5] [--on-page] [--layout]
[--library <dir>] [--scenes <dir>] [--pixel-dir <dir>]
Renders individual panels of a page for review.
| Flag | Default | Meaning |
|---|---|---|
--row |
0 |
Row index, 0-based |
--col |
0 |
Column index within the row, 0-based |
--all |
off | Render every panel into the -o directory |
--scale |
0.5 |
Size relative to the full-page panel |
--on-page |
off | Crop the panel, plus half a gutter, out of the page render |
--layout |
off | Print every panel's position on the page as JSON; render nothing |
cmf panel pages/strip.yaml --row 0 --col 1
cmf panel pages/strip.yaml --all -o panels/
cmf panel pages/strip.yaml --row 1 --col 0 --scale 1.0
cmf panel pages/strip.yaml --row 1 --col 0 --scale 1.0 --on-page
A plain panel render is drawn on its own canvas, which cuts the outer half of
the frame stroke and leaves the rounded corners transparent. --on-page
instead renders the whole page and frames the panel's box with half a gutter
around it, so at --scale 1.0 it matches the page pixel for pixel — full
frame, page background in the corners, anything of the page (a title
descender, say) that reaches into that margin.
With --all, files are written as panel_r<R>c<C>.png, next to a
layout.json saying where each sits on the page — the same JSON --layout
prints:
{"width": 840.0, "height": 1188.0,
"panels": [{"row": 0, "col": 0, "file": "panel_r0c0.png",
"box": {"x": 56.0, "y": 96.0, "w": 352.0, "h": 506.0},
"image": {"x": 56.0, "y": 96.0, "w": 352.0, "h": 506.0}}]}
Everything is in page px. box is the panel; image is the area its render
covers — the box itself, or the box plus half a gutter with --on-page (pass
the same --on-page to --layout). To rebuild the page in HTML, place each
file absolutely at its image rect, as a percentage of width / height. The default --scale
of 0.5 is deliberately low-resolution — this is for looking, not for output.
This is the supported way to inspect one panel. Rendering the whole page and cropping it with an image tool gets you the same picture for more work.
character¶
cmf character <name> [selection ...] --library <dir>
[-o <output>] [--pose <pose>] [--scale 2.0] [--bg "#ffffff"] [--flip] [--thumb-px 320]
Renders one character alone on a plain canvas, cropped to its pose — a quick visual test of a pose or an expression with no page around it.
Each selection token is either a bare name — matched first against the
character's poses, then against the variants of every slot — or an explicit
key=value. An unknown token errors with the available poses and slots listed.
| Flag | Default | Meaning |
|---|---|---|
--library |
required | Character library directory |
--pose |
character's default | Which pose to draw |
--scale |
2.0 |
Output px per viewBox unit |
--bg |
#ffffff |
Canvas colour |
--flip |
off | Mirror horizontally |
--thumb-px |
320 |
Body width of the small companion PNG; 0 to skip it |
cmf character bara sit happy --library characters
cmf character bara pose=walk face=neutral --library characters
cmf character tom --library characters -o tom.png
It writes two files: the full render at -o, and a smaller
<stem>.small.png beside it. When an agent reads the result back, the small one
costs far fewer tokens.
validate¶
Statically checks a page or scene spec against the libraries it points at, and reports every problem at once. Nothing is drawn.
This catches things render cannot. render stops at the first unrenderable
item, and silently ignores keys it does not recognise — so fcae: happy or
post: walk renders a default-faced, default-posed actor without a word of
complaint. validate flags:
- characters, scenes and pixel sprites missing from the library
- poses an actor asks for that the character does not have
- slot variants that do not exist for the chosen character + pose, or scene
- actor and scene keys that are neither reserved nor a real slot — likely typos
- panel keys the renderer does not know, such as
imge:forimage: - raster
image:files that are missing, unreadable or of an unsupported type; animage:with nosrc; an unknown image key; an unknownfit - a bubble
speakernaming neither an actor nor aspeakers:point in the panel; a malformedspeakers:point; an unknown bubblekind,tailortail_from, atail_bendoutside -1..1 - structural holes — a page with no
rows, a row with nopanels, a bubble with notext, a scene spec with neitherscene:norimage: - an unknown
type:, or atype:that contradicts the structure (ascenespec carryingrows)
Exit code 0 when the spec is sound, 1 with a bulleted list otherwise — so it
drops straight into a pre-render check or CI.
It also lays out every panel's bubbles and prints warnings for things that
render but read badly: a bubble out of reading order, tails that cross, a
bubble covering a speaker point (see
layout warnings). Warnings do not change
the exit code unless you pass --strict.
pages/strip.yaml: 3 problem(s)
- r0c0: unknown panel key 'imge' (ignored when rendering). Known: ['actors',
'bg', 'bubbles', 'caption', 'frame', 'image', 'pixel', 'scene', 'width']
- r0c0: "character 'tomm' not found in characters. Have: ['bara', 'tom']"
- r0c0: tom has no slot 'fcae' (ignored when rendering). Slots: ['arms', 'face']
r0c0 is row 0, column 0. Note the phrase ignored when rendering — those are
exactly the mistakes that would otherwise cost you nothing but a wrong-looking
panel.
characters¶
Prints the character manifest as JSON: every character, its label, its slots and their variants, its defaults, and — for multi-pose characters — the pose list with each pose's own slots.
--library is required; there is no default, because the engine ships no art.
{
"bara": {
"label": "Bára",
"slots": {"face": ["neutral", "happy"]},
"default": {"pose": "sit", "face": "neutral"},
"default_pose": "sit",
"poses": {"sit": {"slots": {}, "default": {}},
"walk": {"slots": {}, "default": {}}}
},
"tom": {
"label": "Tom",
"slots": {"arms": ["down", "wave", "point", "crossed", "hips", "thumbsup"],
"face": ["neutral", "happy", "surprised", "sad", "angry", "laugh", "wink"]},
"default": {"face": "neutral", "arms": "down"}
}
}
This is a stable machine-readable contract — it is what a generator or an agent reads before writing a spec.
scenes¶
Prints the scene manifest as JSON: every scene, its label, its slots and their
variants, and its defaults. --scenes is required.
{
"dvur": {"label": "Dvůr", "slots": {"weather": ["clear", "rain"]},
"default": {"weather": "clear"}},
"pokoj": {"label": "Pokoj", "slots": {}, "default": {}}
}
inspire¶
cmf inspire <references.yaml> [-o <dir>]
[--theme <theme.yaml>] [--only id1,id2] [--force] [--dry-run] [--review]
Generates reference images from a project theme plus per-item descriptions. Not shipped assets — see Reference images.
| Flag | Default | Meaning |
|---|---|---|
-o |
references/ beside the spec |
Output directory |
--theme |
theme.yaml beside the spec |
Theme file |
--only |
all | Comma-separated ids to generate |
--force |
off | Regenerate even when a .png already exists |
--dry-run |
off | Compose prompts only — no API call, no token needed |
--review |
off | Also write a review.html grid of images and prompts |
Each item writes <id>.png and <id>.prompt.txt. Live generation needs the
optional extra and a REPLICATE_API_TOKEN:
uv tool install "comicforge[inspire]"
export REPLICATE_API_TOKEN=...
cmf inspire references.yaml --review
cmf inspire references.yaml --dry-run # no token required
Exit codes¶
| Code | Meaning |
|---|---|
0 |
Success |
1 |
validate found problems, or a render failed with a spec error (the message names the file and the cause) |
2 |
Bad command line — argparse's own usage error |