omapixel is a pixel art and animation studio where the document is a text file, the drawing is a list of commands, and a human and an agent edit the same open document at the same time.
Both previews come from editable .omapixel projects. Open one in the Studio,
inspect every layer and frame, or browse all examples.
Select a rectangle in the Studio. The agent reads exactly what you marked:
$ omapixel where heart.json
{"sessions":[{"pid":1234,"path":"/work/heart.json","dirty":false,
"view":{"clip":"idle","frame":0,"layerId":"hero","scope":"frame"},
"selection":{"clip":"idle","frame":0,"layerId":"hero",
"x":2,"y":3,"width":6,"height":7,"count":42}}]}"Fix the left eye" is ambiguous. A rectangle with a clip, a frame and a layer is
not. dirty says whether you have unsaved strokes before the agent writes, and
if it does land on top of your work, one Ctrl+Z brings your version back.
The endpoint is authenticated per process. The CLI never reads Studio selection
state on its own — where is the one place it is offered, and only on request.
No image was imported. The whole source is thirteen lines:
line --from 3,3 --to 6,3 --slot R
line --from 9,3 --to 12,3 --slot R
line --from 2,4 --to 13,4 --slot R
...
# a specular highlight, so it reads as drawn and not as a glyph
line --from 4,4 --to 5,4 --slot S
paint --at 3,5 --slot S
That comment is the agent's. Run the file yourself — the same batch gives back the same picture, byte for byte:
omapixel new icon.json --size 16x16
omapixel batch icon.json --script packaging/icon/omapixel.batch
omapixel show icon.jsonAn agent asked for 256 pixel values produces noise. An agent asked for ten lines produces a drawing. That is the whole design: the interface is geometry and palette, never a grid of colours.
The full source, and how the .svg the desktop installs is derived from it, is
in packaging/icon/.
Three claims, and the command that settles each one.
The source is text, so art lives in git.
omapixel diff before.json after.jsonA binary sprite gives you two pictures to compare. A text document gives you a line to comment on. You cannot ask an onion skin why a row moved.
A pixel stores a palette slot, not a colour.
omapixel palette heart.json set --slot R --colour "#F7768E"R is a letter in the document; the palette holds the colour. There is no other
representation, so recolouring every R across a 109-frame animation is one
edit, not a hundred and nine. See the format.
The agent can see what it drew.
omapixel text heart.jsonThe composite comes back as letters, on stdout, with no window and no screenshot. An agent that cannot see its own work is guessing, and you end up reviewing every attempt by hand.
omapixel newwrites a document, or open one that already exists.- Draw:
paint,line,rect,fill,edit, from a prompt, a script or the studio. - Look:
textfor the letters,showfor the terminal,renderfor a PNG. - Animate with
clipandframe; the studio plays it back, looping or not. - Hand over the JSON, compressed
.omapixel, a PNG, or a sprite sheet.
Anything generated goes through batch: one pass over one document, read once
and written once. Everything an agent does should go through it.
On Omarchy:
sudo pacman -S omapixel
omapixel skill install # teach supported coding agents how to drive itFrom a checkout:
mise run deps # says what is missing and how to install it
mise run build # the command line and the studio
mise run install # into ~/.local/binDraw something and look at it without leaving the terminal:
omapixel new heart.json --size 16x16
omapixel rect heart.json --from 4,4 --to 11,9 --slot R --filled
omapixel show heart.jsonUse a .omapixel suffix when the editable document should be compressed;
.json remains the default readable interchange form:
omapixel new animation.omapixel --size 160x90
omapixel-studio animation.omapixelThe Studio's Save As dialog offers both representations and subsequent saves preserve the selected one.
Then open the same file in the window:
omapixel-studio heart.jsonNew here? Start with Getting started.
Three commands answer "what is actually there?":
omapixel text heart.json # the composite, flattened to palette slots
omapixel show heart.json # the composite in the terminal
omapixel render heart.json -o h.png --scale 12 --sheet
omapixel render heart.json -o layer.png --isolated --layer-id hero
omapixel render heart.json -o animation.gif --format gif --fps 12 --loopThese surfaces use the visible bottom-to-top composite by default. --isolated --layer-id ID or an exact --layer NAME renders one visible layer with its
opacity, without its neighbours. text keeps transparent pixels as ., while
show and PNG retain composed RGBA; --checker is a final background for those
visual surfaces and is never written into a layer. render --sheet applies the
selected view to every frame side by side.
Without these, a complete command set still leaves somebody screenshotting a running window. With them, the window is optional.
Layer stack control is available without opening Studio:
omapixel layer heart.json list
omapixel layer heart.json rename --layer-id hero --name "Main Hero"
omapixel layer heart.json move --layer-id hero --index 1Multilayer mutations require explicit layer identity. See the CLI
reference for scope, lock/hidden safety, batch behavior, and
flatten reports.
| Command | What it does |
|---|---|
new info check |
make one, describe one, say what is wrong with one |
show text render |
look at one: in the terminal, as letters, as PNG or animated GIF |
paint line rect fill |
draw |
edit |
clear shift flip swap over a whole frame |
clip frame |
add dup rm move rename fps |
palette |
list set rm |
resize trim |
resize around the centre, or remove empty outer borders |
layer flatten |
inspect and control the layer stack, or write a composite |
batch |
many commands over one document, read once and written once |
diff |
what differs between two documents |
where |
which live studios hold a document, and what is selected in them |
import export |
read and write somebody else's sprite catalog |
import-image |
turn PNG, JPEG or WebP into a new document or layer |
config i18n |
the settings file, and what a translation is missing |
skill |
inspect or install the Omapixel skill for coding agents |
plugin |
list, check and run local export plugins |
--clip and --frame default to the first clip and frame 0, so a document with
one clip needs no flags at all. Exit 1 means the answer was no; exit 2 means
the command was wrong.
Use
batchfor generated art. It applies many commands after one read and commits once, instead of starting a process and rewriting the document for every operation.
The complete cross-surface gate is mise run check. It includes offscreen
Studio/CLI fixtures, deterministic v2 properties, i18n coverage and the canonical
performance benchmark. The workload and stop limits are in How it is
built.
An export plugin is a separate executable, in any language. omapixel speaks to it over a pipe, and the contract is short enough to read in one sitting.
The host validates the document, drops a snapshot into a private workspace and starts the plugin there. One JSON request goes in on stdin:
{"type":"request","requestId":"req-1","action":"png","document":"input/document.json","outputDir":"output","params":[{"key":"scale","value":"2"}]}JSONL comes back on stdout: any number of progress records, then exactly one result.
{"type":"progress","requestId":"req-1","message":"encoding","percent":50}
{"type":"result","requestId":"req-1","ok":true,"artifact":"sprite.png"}The plugin writes inside output/ and names the file. It never chooses where
that file lands — the host publishes it atomically to the --out you gave, and
only after a clean exit and a valid result. A crash, a timeout, an unexpected
line on stdout, or an artifact reaching outside the workspace is a failed
invocation, and your destination is left untouched.
omapixel plugin list
omapixel plugin check example-exporter
omapixel plugin run example-exporter png art.json --out sprite.png --param scale=2list and check only read manifests; they never start anything. Execution is
bounded: sixty seconds, a megabyte of protocol, one artifact of at most 64 MiB.
The plugin inherits PATH, HOME, LANG, LC_ALL, LC_CTYPE and TMPDIR,
and nothing else of yours.
Plugins are trusted, unsandboxed executables. The workspace protects your document and your destination from a plugin that is wrong; it is not a security boundary against one that is hostile. Read a plugin before you copy it into place.
Plugin API 1 is the whole contract: the manifest schema,
discovery order, every limit, and the failure matrix that mise run plugin-e2e
exercises.
omapixel-studio person.jsonTools and the ten colours in hand down the left, the drawing in the middle, layers and inspector panels on the right, the clip and its frames along the bottom, and a bar of live keys under the canvas.
- Every control is reachable from the keyboard. A cursor walks the drawing a pixel at a time and paints where it stands; Tab walks the window; Esc always hands the keyboard back to the drawing.
- It wears your omarchy theme, live.
omarchy theme setrecolours it without a restart, corner rounding included. Your art never changes colour because your desktop did. - It follows the file you have open, live. An agent editing through the command line lands in the window as it happens, the status line says what changed, and if it replaced unsaved work one Ctrl+Z brings your version back. Even an untitled window is backed by a runtime file, so "draw something" works before any save.
- Its settings and every keybinding are one TOML file in the shape the rest of an omarchy machine uses, watched, so saving it rebinds the keys while the window is open.
- Its words come from a JSON file per language, so translating is copying one file and changing the right-hand sides.
Not a replacement for drawing by hand. Aseprite's canvas is better than this one and will stay better; if you sit down to illustrate for an afternoon, use it.
omapixel is for art that is specified: icons, tiles, UI, palette work, sprite catalogues, and animation with a rule behind it — a dissolve, a pan, a pulse, a sequence too long to hand-place. Those are the jobs where the drawing is a program, and where a program should be the source.
Original illustration with volume and hand-tuned anti-aliasing is still a human's job. Geometry, palette and timing are where an agent is good, and that is what this is shaped for.
Full docs live in docs/.
- Getting started: draw, animate and export something, start to finish.
- Dependencies: runtime, checkout, WebP, and test environments.
- Examples: editable projects paired with their rendered GIF previews.
- The command line: every command, every flag, with examples.
- The studio: the window, its panels and its keys.
- The format: the schema, composition rules, and layer behavior.
- Plugins: install, inspect, run, or author an API 1 export plugin.
- Settings and keys: one TOML file, every setting, every binding.
- Adding a language: translate the JSON catalogue and check its coverage.
- How it is built: one core, two front ends, and why it is shaped this way.
omapixel is available under the MIT License.