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
  • Sourcecargo install --locked --path crates/atelier, or tools/install.sh --source to 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 atelier process. Both use the same resolved store. (The CLI needs neither transport.)
  • The selected port is already in use — rerun atelier install and choose another port (--port PORT in scripts). atelier status prints the installed endpoint; atelier uninstall stops the daemon.
  • Where are the logs? The daemon writes ~/.atelier/logs/atelier.{out,err}.log; verbosity via ATELIER_LOG (RUST_LOG syntax). In stdio mode the same log goes to the spawning client's stderr.
  • Uninstall everythinginstall.sh uninstall (or atelier uninstall for just the daemon). Your documents in ~/.atelier are 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