Declarative image-project renderer.
mosaico reads a YAML manifest of image artifacts, builds the dependency
graph, topologically sorts it, and renders only what's missing or stale —
content-addressed caching, idempotent re-runs, two-phase plan/save flow.
Two commands:
mosaico gen "<prompt>"— low-level: prompt → single image (with optional--grid RxCsheet + auto-cut).mosaico render <project.yml>— high-level: declarative project, full graph render with caching.
Both are designed to be agent-friendly: --tour, --dry-run, and
mosaico itself prints next-step instructions on every failure.
uv add mosaico
# or
pip install mosaicoexport OPENROUTER_API_KEY=sk-...
mosaico gen "watercolor of a heron at dawn" --save --out heron.jpg# project.yml
version: 1
name: my-book
defaults:
out_root: out
state: state.json
artifacts:
- id: cover
prompt_template: "book cover, watercolor style, title '{{ templates.title }}'"
out: cover.jpg
templates:
title: "Érase una vez el Conocimiento"mosaico render project.yml --saveIf you already have generated images on disk and want to bring them under
mosaico's cache without re-rendering, use --bootstrap:
# Preview what would be anchored (dry-run, no state write)
mosaico render project.yml --bootstrap --dry-run
# Anchor existing files to the current manifest's hashes
mosaico render project.yml --bootstrap
# Anchor existing files, then render anything still missing on disk
mosaico render project.yml --bootstrap --saveFor each artifact whose out: already exists, mosaico computes the
manifest's input_hash and the file's output_hash and writes that
entry to state — no API call. Pendings (artifacts whose out: is missing)
are reported but not rendered unless --save is also passed.
--bootstrap is also the right tool for prompt refactors that shouldn't
trigger a re-render: edit the manifest, run --bootstrap, and the new
input_hash is anchored to the existing output. A subsequent
mosaico render --save will see zero pending and call no API.
mosaico gen reads its OpenRouter token from, in order:
$OPENROUTER_API_KEY$MOSAICO_TOKEN_FILE(path to a file containing the key)$CLAUDE_TOOLKIT_WORKSPACE/.claude/openrouter.token(set automatically when invoked underclaude-toolkit image …)
mosaico exposes its App so a parent CLI can mount it:
import microcli as m
from mosaico import app as mosaico_app
root = m.App(name="my-tool")
root.mount("image", mosaico_app)
root.main()
# my-tool image gen "<prompt>" --save
# my-tool image render project.yml --saveMIT.