Install
curl -fsSL https://marmikshah.github.io/atelier/install.sh | sh
Installs the binary after verifying its published SHA-256. That's the whole setup — drive it from any shell:
atelier call doc_new '{"name":"cat","width":32,"height":32}'
# {"doc_id":"550e8400-e29b-41d4-a716-446655440000", ...} — use the returned id
atelier call doc_draw '{"doc_id":"550e8400-e29b-41d4-a716-446655440000","layer":0,"frame":0,"op":"fill_cel","color":[224,160,80]}'
atelier call doc_look '{"doc_id":"550e8400-e29b-41d4-a716-446655440000","out_path":"/tmp/cat.png"}'
Every tool is one atelier call: stdout gets the JSON report, the exit code
the verdict (0 ok, 1 tool error, 2 bad call). atelier tools lists the
surface; atelier tools --schema <name> dumps one tool's input schema. Any
agent with a shell — Claude Code, Codex, Kimi Code or Cursor — drives it
exactly this way: no registration, no daemon, no restart. Just ask:
"draw me a blinking cat sprite and export it as a GIF"
Other ways
- Binaries — macOS (ARM), Linux x86_64, Windows: latest release
- Source —
cargo install --locked --path crates/atelier, ortools/install.sh --sourceto build this checkout and install it
Optional: run as an MCP server
For clients that only speak MCP, the same binary is an MCP server — one command sets up a shared background daemon (launchd / systemd):
atelier install
# MCP HTTP port [8765]:
The prompt appears on both first install and reinstall; reinstall defaults to
the currently configured port. For scripts, use atelier install --port 9123.
Advanced/LAN setups can still choose the whole address with
atelier install --bind 0.0.0.0:9123.
Atelier deliberately does not rewrite third-party client configuration. Point
your MCP client at the endpoint printed by atelier status:
http://127.0.0.1:8765/mcp
Or configure a stdio MCP server whose command is simply atelier. Stdio needs
no daemon: each client starts its own process, while all processes still resolve
the same global or directory-local document store.
Keep your client's normal approval prompts enabled: Atelier can write exports and delete documents.
Docker
Prefer a container? One small image — a static amd64 musl binary on Alpine (~15 MB) — serving the same HTTP MCP endpoint:
docker run -d -p 127.0.0.1:9123:8765 -v atelier-data:/data ghcr.io/marmikshah/atelier
Documents persist in the atelier-data volume, so they survive restarts.
Here 9123 is the configurable host port; the Alpine container keeps its
internal endpoint on 8765.
There's a docker-compose.yml if you'd rather keep it
declarative.
MCP notes
- Your client shows 0 tools — restart it after registering; MCP clients read their server list at session start.
- stdio or daemon? The daemon is one shared server and store, and it
survives reboots; stdio means each client spawns its own
atelierprocess. Both use the same resolved store. (The CLI needs neither transport.) - The selected port is already in use — rerun
atelier installand choose another port (--port PORTin scripts).atelier statusprints the installed endpoint;atelier uninstallstops the daemon. - Where are the logs? The daemon writes
~/.atelier/logs/atelier.{out,err}.log; verbosity viaATELIER_LOG(RUST_LOGsyntax). In stdio mode the same log goes to the spawning client's stderr. - Uninstall everything —
install.sh uninstall(oratelier uninstallfor just the daemon). Your documents in~/.atelierare kept; delete that directory too if you want them gone.
Why it's different
Agents are good at describing art and bad at seeing it. So most AI pixel art is drawn blind — the model guesses, never looks, and ships the guess.
atelier gives the agent an eye. doc_look hands back the actual frame as an
image, plus measured stats. The agent looks at its own work, judges it, and fixes
it — the same loop a human uses in an editor. Nothing sends pixels back unless the
agent asks to see them: every drawing tool answers in text, so looking is a
deliberate act, and the context stays small enough to look often.
| 🎨 A real editor, headless | Layers, frames, tags, locked palettes, checkpoints. Draw with primitives or paint a whole region declaratively from a character grid. |
| 👁 An eye, not just a hand | Critique, palette, silhouette and animation audits turn "does it look right?" into numbers an agent can act on. |
| 🎮 Game-ready out of the box | Tagged spritesheets, GIF/APNG, and engine-standard JSON. |
| 🔒 Yours, offline | One self-contained Rust binary. No API keys, no network, no telemetry. Fully deterministic. |
The CLI
atelier call <tool> '<json>' # one tool call, in-process — the front door
atelier call <tool> --file ops.json # args from a file (or --stdin)
atelier call doc_draw '{"doc_id":"550e8400-e29b-41d4-a716-446655440000","layer":1,"frame":0,"op":"clear_cel"}'
atelier tools [--schema <name]] # the tool surface / one input schema
atelier replay <recipe|id> # rebuild a document from its journal
atelier library # what's in your document store
atelier skills install # write the skills (--for claude|codex|kimi|cursor|all)
And the MCP add-on:
atelier # stdio MCP server (your client spawns it)
atelier --http # HTTP server at 127.0.0.1:8765/mcp
atelier install # background daemon; asks for port (default 8765)
atelier install --port 9123 # non-interactive install/reinstall
atelier status # daemon state, endpoint, and log locations
atelier uninstall # stop and remove the daemon
Every call — CLI, replay, stdio or HTTP — travels one dispatch path, so it logs
the same line to stderr: tool name, op, target document, caller, duration, and
the error text when a call fails. The daemon collects this in
~/.atelier/logs/atelier.err.log; tune verbosity with ATELIER_LOG
(RUST_LOG syntax, default info). When several agents share the daemon,
every call logs a caller= identity: by default the TCP peer address; set an
X-Atelier-Caller header in a client's MCP config, or the per-call session
metadata below, when the name must stay stable across reconnects. (The CLI and
replay log as cli / replay.)
doc_new returns a fresh canonical UUIDv4 such as
550e8400-e29b-41d4-a716-446655440000. Its name
is only a display label and may repeat. Every later document call must carry
the returned doc_id explicitly; layer and frame targets are explicit too.
There is no active document, inferred name, CLI routing flag, or transport
default. Stdio, HTTP, CLI, and replay therefore execute exactly the same
payload.
MCP callers may attach one optional stable name for logs. It never supplies or changes tool arguments:
{
"_meta": {
"io.github.marmikshah.atelier/session": "sprite-pass"
}
}
The server retains no request context. Journals record the exact arguments that ran, so a replay never depends on a live session or another caller's state.
25 tools, all of them advertised — no profiles to pick, nothing hidden behind a flag. Registry/dispatch lockstep is test-enforced, so an advertised tool cannot turn into an unreachable dead end. Browse them in the tool reference.
Directory-local stores
By default everything lands in the global ~/.atelier. Run atelier init in
your game or app directory and it gets its own ./.atelier: art and recipes
live beside the code and recipes can be committed with the game. Resolution
per call: --home / ATELIER_HOME →
./.atelier when it exists → ~/.atelier. Standing in $HOME, the two are
the same directory.
The daemon is the exception: a shared server has no working directory, so it
always pins the global store at install time (or whatever --home you give
it). atelier status shows the daemon endpoint and store.
Skills
Tools are the hand; these are the craft. Three skills you can explicitly add to Claude Code, Codex, Kimi Code or Cursor:
| atelier-sprite | one subject — character, creature, vehicle, prop |
| atelier-scene | a place — background, environment, composed picture |
| atelier-review | judge finished art and say what's wrong, with a fix |
Both drawing skills insist on the same two things: build it in layers, and
fix the region that's wrong instead of repainting the frame. They prescribe
no style and no palette — that's the request's business. And they're
transport-free: every tool they name is one atelier call, or the same-named
MCP tool when you're connected over MCP.
Install or refresh them any time — atelier skills install (Claude Code by
default, --for codex|kimi|cursor|all for the others).
atelier works fine without them; they just make the art better.
Art is a recipe
Every document is an ordered sequence of tool calls, so a piece of art is a replayable program — and atelier keeps that sequence for you. Every document journals itself as it's drawn, so anything you make can rebuild itself:
atelier library # every document, with its step count
atelier replay 550e8400-e29b-41d4-a716-446655440000 # rebuild it from its own journal
Nothing to turn on. The journal is JSON Lines beside the art
(~/.atelier/documents/<id>/recipe.jsonl), one tool call per line — only the
deterministic calls that made something, never looks, audits, external
reference setup, or checkpoint bookkeeping. Restoring a checkpoint restores
its journal too, so discarded edits cannot survive in provenance. Replay into
a sandbox with --home /tmp/demo and you get the same pixels, anywhere.
Current journals start with exactly one doc_new whose arguments include the
minted doc_id, followed only by deterministic editing calls for that same
document. Replay accepts only this JSONL contract and rejects wrapped objects,
bindings, older shapes, or malformed lines instead of guessing.
Each doc_draw and doc_fx line applies exactly one operation to one cel.
doc_paint_grid remains the single declarative operation for dense pixel rows.
MCP clients inspect live documents through doc_info and doc_look, the same
calls used by CLI and replay.
A personal note
atelier began as one question: can agents, using only tool calls, make art good enough to ship in a game?
100% of this code was written by AI. Not assisted — written. Claude Opus 4.8 and Fable 5 did the heavy lifting, with Kimi 2.6 and Minimax 2.7 pitching in. I have not written a line. My part was direction, held to the standards I use where I do still write the code. If it helps you build something, the tokens were worth spending — and a ⭐ helps the next person find it.
[!WARNING] Below 2.0.0, no human has reviewed this code. Assume bugs and security issues I haven't caught, and breaking changes in any release. Use at your own risk. 2.0.0 is where I start reviewing in detail — tagged by hand, the one release an agent may not cut.
Contributing
Bug reports, ideas, documentation improvements, and focused pull requests are welcome. Open an issue first for new tools, public API changes, dependencies, formats, or broad refactors; the full development and review expectations are in CONTRIBUTING.md.
Maintainer releases are approved by a manually created annotated tag; the exact procedure is in docs/RELEASING.md.
Contributing · Code of Conduct · Security · Changelog
License
MIT © Marmik Shah
No comments yet
Be the first to share your take.