pixellab: commands, the stdio protocol and MCP#
pixellab reads and edits the pixel art in a .pixellab document and publishes it as the SVGs a site inlines. It has three doors onto one command registry (crates/pixellab/src/registry.rs): the command line, pixellab serve --stdio for the Mac app, and pixellab serve --mcp for agents. Every command takes the same params (a JSON Schema object) and returns the same JSON through each door.
- Commands
- The command line
- Libraries and palette files
- The stdio protocol (
serve --stdio, for the Mac app) - MCP (
serve --mcp, for agents) and setup for Claude Code - The state dir: history, owners and activity
- Command reference
Commands#
| command | summary | kind |
|---|---|---|
activity list | The last MCP tool calls agents made, oldest first | read |
art build | Publish: validate every source, compile each scene with an output and write the SVGs | writes files |
art preview | Render the review PNG of an object (standing on a tile) or a scene, on the card color; palette for the swatch sheet | read |
art sizes | Bytes each object and each color adds to a compiled scene, against the document's git HEAD | read |
chat ask | Ask the owner a question with two to four answers, shown as buttons in Pixel Lab's Agent chat | read |
chat context | Build the selection block an Agent chat message carries: what's selected, as the saved document has it | read |
check | Validate the sources with the engine, all of them or one doc | read |
doc adopt | Merge a client's unsaved copy of a doc into this one, as one step | edit, session |
doc close | Close an open doc (refused with unsaved changes unless --force) | session |
doc flush | Save every open doc with changes now (or one), as autosave would soon | edit, session |
doc merge | Merge the changes to a doc's file on disk into its working copy, keeping the unsaved edits | edit, session |
doc open | Open a scene or object as a working copy, with undo and redo | session |
doc redo | Redo the last undone edit to an open doc | edit, session |
doc reload | Load an open doc from disk again, only if no unsaved work is lost | edit, session |
doc revert | Load an open doc from disk again, dropping its changes (as one undo step) | edit, session |
doc save | Save an open doc now and close its version: validate, serialize with the engine and write its source | edit, session |
doc status | Whether open docs have unsaved changes, and can undo or redo | session |
doc undo | Undo the last edit to an open doc | edit, session |
document copy | Save a copy of the open document at another path (Save As) | writes files |
document info | Show the document in use: where it came from, its publish folder and what it holds | read |
document migrate | Migrate a document from format 1 (.mjs sources) to format 2 (JSON sources), in place or as a copy | writes files |
document new | Make a new, empty .pixellab document with the starter palette | writes files |
history list | List the document's versions, newest first | read |
history mark | Close the open version, so the next edit starts a new one | writes files |
history render | Render a scene or object as it was after a version, without writing anything | read |
history restore | Put the document (or one doc) back as it was after a version | writes files |
history show | Show a version: its edits and a diff of each file it changed | read |
history undo | Take back just what one version changed, keeping everything since | writes files |
library category | Set or clear the category of objects in a .pixellib, in place | writes files |
library export | Export the document's objects to a new .pixellib | writes files |
library import | Import the objects of a .pixellib into the document, by copying | writes files |
library info | Show an object library: its objects, their categories and the colors they used | read |
object action | Add, change or delete an action of a sprite map layer | edit |
object ambient | Set or clear a sprite map layer's ambient loop | edit |
object animate | Turn a still layer into one of the site's frame loops, or switch a loop's kind | edit |
object category | Set or clear the category of one or more objects | edit |
object erase | Erase pixels of an object (make them transparent), on a layer and frame | edit |
object fill | Flood-fill from a pixel with a palette color, on a layer and frame | edit |
object footprint | Set how many tiles a sprite covers (--to w,d), or clear it (--clear) | edit |
object frame add | Add a blank frame after a frame of a layer's loop | edit |
object frame delete | Delete a frame of a loop (not the only one) | edit |
object frame duplicate | Duplicate a frame of a loop, just after it | edit |
object frame move | Move a frame of a loop later (--by 1) or earlier (--by -1) | edit |
object layer add | Add a blank layer on top of an object | edit |
object layer delete | Delete a layer (not the only one) | edit |
object layer duplicate | Duplicate a layer, just above it | edit |
object layer move | Move a layer up (--by 1, raise) or down (--by -1, lower) | edit |
object list | List every object with its kind, category, size, layers, frames and colors | read |
object map set | Replace a whole map of a sprite's layer (or frame of a loop) from rows, in one undo step | edit |
object new | Make a new object from a starter: a blank sprite, a block, or one that extends another | edit |
object paint | Paint pixels of an object a palette color, on a layer and frame | edit |
object pick | The color at a pixel of an object, on a layer and frame (the Picker) | read |
object render | Render one object to a transparent PNG | read |
object resize | Pad or crop a sprite's maps to a width and height (at the right and bottom) | edit |
object show | Show an object's kind, category, size, anchor, footprint, layers, frames, colors and extends | read |
object still | Turn a frame loop back into a still layer, keeping one frame | edit |
palette export | Export the document's palette to a new .pixelpal file | writes files |
palette get | List the palette: every color name, hex and tier | read |
palette import | Add the colors of a .pixelpal file the document doesn't have | edit |
scene action | Add, change or delete a scene item's action: a one-shot move the page plays by name | edit |
scene ambient | Set or clear a scene item's ambient loop: a move that repeats on its own | edit |
scene drop | Drop an item onto whatever it stands over: down to the top of the block under it, or the ground | edit |
scene duplicate | Duplicate an item one column over, where the copy stands in depth order | edit |
scene frame | Show a scene's published size, fix it (--box), fit it to its content (--fit) or set its margin | edit |
scene group set | Set or remove a group's class and data attributes, or set its data-x and data-y to its first item's foot | edit |
scene items | List a scene's items in depth order (back to front), with stable paths | read |
scene key delete | Delete a key of an item's track (not the one at 0: remove the track for that) | edit |
scene key move | Move a key of an item's track to another time | edit |
scene key set | Set a key of an item's track: its move, show and pose at a time (made if there's no key there) | edit |
scene layer assign | Move items and layers into a layer, or to the top level | edit |
scene layer move | Move layers (every item in them, and in the layers inside them) and items by tiles, together | edit |
scene layer new | Make a layer (a named, nestable part of a scene) and move items and layers into it | edit |
scene layer rename | Rename a layer | edit |
scene layer ungroup | Ungroup a layer: what it held moves up one level and stays where it is | edit |
scene list | List every scene | read |
scene motion path | Key an item along a polyline on the grid, at a steady speed or between two times | edit |
scene motion set | Set the length of a scene's motion loop (making the motion if it has none) | edit |
scene move | Move an item by tiles and levels (--by) or by pixels (--nudge) | edit |
scene new | Make a new, empty scene (the starter: a 128×128 view around tile 0,0,0) | edit |
scene place | Place an object on a tile (or at a pixel point) in a scene | edit |
scene remove | Remove an item (and its motion track, if it has an id) | edit |
scene render | Render a scene to a transparent PNG, with item bounds | read |
scene set | Set an item's tile or pixel point, one coordinate, its level, depth override, class or id | edit |
scene show | Show a scene's settings, size, objects and motion | read |
scene snap | Snap a pixel-placed item to the tile under its placement point, at the level it stands on | edit |
scene sort | Put a scene's items (or every scene's) in depth order, and say what moved | edit |
scene track add | Add a motion track for an item (it needs an id): one key at 0, the rest frame | edit |
scene track remove | Remove an item's motion track, with all its keys | edit |
The command line#
pixellab <group> <verb> [args] [--flags] [--json] [--document <path.pixellab>]
The leading words name the command (scene place is scene.place; the longest match wins). Positionals fill the command's arguments in order; every param is also a --kebab-case flag. Booleans: --flag, --no-flag. Lists: comma-separated (--at 14,6,1) or the flag repeated; a list of pairs (pixels) is 3,4 5,6 or the flag repeated. Exit codes: 0 ok, 1 command error (or check found problems), 2 usage error. With --json, errors are {"error": {"code", "message", "details?"}} on stderr.
| global flag | |
|---|---|
--json | print the command's result as JSON |
--document | the .pixellab document (else $PIXELLAB_DOC, ~/.config/pixellab/config.json, or the document in --site) |
--site | a site checkout holding the document (else $PIXELLAB_SITE, the config file, ~/Projects/jlawcordova.github.io) |
--out | write the PNG of a render command to this file |
--dry-run | an edit command shows what it would write (a diff) and writes nothing |
--force | an edit command writes despite validation problems or a file changed on disk |
-h, --help | help for the command or group |
Edits are one-shot. A command that edits a doc loads it, applies the edit, validates the doc with the engine, serializes it with the engine and writes it atomically (temp file and rename), then prints what changed and wrote <file>. It refuses, writing nothing, when the doc has validation problems (code invalid, the problems in details) or the file changed on disk while it ran (code conflict); --force writes anyway. --dry-run prints the unified diff it would write, any problems, and writes nothing. With --json the result gains saved: [{ doc, file, wrote, hash }] or dryRun: [{ doc, file, diff, problems, conflict }]. The doc.* commands (undo, redo, save…) only make sense in a session, so they run through pixellab serve. Every write is a version in the document's history, by the CLI (see history list).
One writer per document. While Pixel Lab has the document open, its server owns it: an edit command sends itself to the app (the edit shows on its stage, and the app saves it before answering), and a read reads the app's open copy. Without the app, the command writes the files itself. --dry-run asks the app to save first, then shows the diff against the files. document migrate refuses while the app has the document open.
The document comes from --document, else PIXELLAB_DOC, else ~/.config/pixellab/config.json ({"document": "…"}), else --site <checkout> / PIXELLAB_SITE / {"site": "…"} (the *.pixellab under the site's src/assets/pixel-art/, or its legacy source/ folder), else ~/Projects/jlawcordova.github.io.
Libraries and palette files#
Two file kinds sit beside the .pixellab document, for moving art between documents by copying. Nothing links a document to either afterwards.
- Categories. An object can carry a
category, a list of names from the outermost in (["Nature", "Trees"], typedNature/TreesorNature › Trees). A name is 1 to 32 letters, digits, spaces,&,'and-, andAllandOtherscan't be the outermost (they are the Library's own tabs).object categorysets or clears it,object list --categoryfilters by it, andcheckreports a bad one. A category never changes how an object renders or whatart buildwrites. .pixellib, an object library. A folder:library.json(format,kind: "library",name,exportedFromandcolors, the palette colors its objects used at export, never read as a palette) andobjects/*.json, the same files a document holds, categories included. It has no scenes and no palette.library exportwrites one from all of a document's objects, one category and below it, or a named list, adding every object they extend; it refuses an existing library unless--replace.library importcopies a library's objects in: a taken name gets the first free suffix (pine-2) and everyextendsfollows; an object identical to the document's, apart from its category, isn't copied; a librarycharacterthat differs isn't copied, and its figures extend the document's. Differing colors need--accept-differences, and a color the document lacks needs--convert '{"wood-5":"wood-3"}'to a color of the same tier;--dry-runshows the plan. An import never changes the palette, the existing objects or the scenes, and is one version in the history.library inforeads a library,library categorysets categories in one in place, andcheck --library <path>validates one..pixelpal, a palette file. One JSON file:format,kind: "palette", and theworldandoutfittiers (andcolorNotes) as in a document'spalette.json.palette exportwrites the document's palette.palette importadds the colors the document lacks, by name, at the end of their tier; it never removes one. A name with another value needs--keepor--use-theirs, and an import that would pass a tier's cap (32 world, 16 outfit) adds nothing and says how many colors are over;--onlynarrows it. It is one edit and one version.
An agent that is refused with differs, missing_colors or palette_differs asks the owner (chat_ask) before it accepts differences, converts a color or chooses a value.
The stdio protocol#
pixellab serve --stdio [--document <path> | --site <checkout>] is the Mac app's server: newline-delimited JSON-RPC 2.0 on stdin and stdout, one message per line. stdout carries nothing but JSON-RPC; logs go to stderr. Requests are answered one at a time, in order. Batches are not supported. Protocol version: 1 (session.info reports it).
The server holds a session: docs open as in-memory working copies with undo and redo (at most 200 steps; rapid repeats of the same edit within a second, such as a held arrow key or one paint stroke sent as many object.paint calls, merge into one step). Every command reads the working copies, so scene.render shows unsaved edits. Edits save themselves: a doc is written about a second after its last change (PIXELLAB_AUTOSAVE_MS changes the pause), and everything is saved when the server stops. A doc with validation problems is not written until they're fixed (saveState: problems); one whose file changed on disk is merged, three ways, then written (saveState: conflict and doc.stale when that clashes). doc.flush saves now; doc.save saves and closes the version. Undo works across saves.
Methods are the command names (scene.place, doc.undo, … see the reference), plus:
| method | result |
|---|---|
session.info | { name: "pixellab", version, protocol, pid, document: { root, name, kind, publishDir, siteRoot }, site, sourceDir, stateDir, saves: "direct", owner: { pid, appPid, socket, … }, openedVersion, docs: [DocStatus] } (openedVersion: the history's newest version when the server opened the document, for Revert To › Last Opened); a server whose document another one owns answers with code owned and { appPid, pid } instead, and exits |
shutdown | { ok: true }, then the server stops reading and exits 0. It also exits when stdin closes. |
→ {"jsonrpc":"2.0","id":1,"method":"scene.place","params":{"name":"hero-island","object":"pine","at":[14,6,1]}}
← {"jsonrpc":"2.0","method":"doc.changed","params":{"doc":"scene:hero-island","dirty":true,"canUndo":true,"canRedo":false}}
← {"jsonrpc":"2.0","id":1,"result":{"doc":"scene:hero-island","changed":true,"summary":"pine placed at column 14, row 6, level 1","path":"198","item":{"object":"pine","at":{"tile":[14,6,1]}}}}
Results are the command's JSON. The shapes the app needs most:
scene.render/object.render:{ png, scale, viewBox: [x, y, w, h], frame?, width, height, time | frame, items: [{ path, object, at, id?, pose?, bounds: [x, y, w, h] }] }.pngis a base64 PNG with a transparent background at an integerscale;boundsare in output pixels (scale included), for hit-testing;pathis the item's dotted path ("5.2"). An object render's item haspath: nulland the result has itsanchor. A scene render'sviewBoxis the box the scene publishes at (its own, or the fit to what it paints:framesays"fixed"or"fit"), or theviewwindow asked for; a scene without aviewBoxhas no edge, so the app asks for the window it shows.scene.showandscene.listgive the resolvedviewBoxandframetoo.- Edits:
{ doc: "scene:<name>" | "object:<name>", changed, summary, ... }; scene item edits addpath(the item's path after the edit) anditem; paint edits addpixels(how many changed). DocStatus(insession.infoanddoc.statuswithout a doc):{ doc, dirty, canUndo, canRedo }.doc.changedaddsundoLabel,redoLabel(the Edit menu's words, with whose step it is for anyone but the owner:"Place pine (Claude)"),author(who made the change),saveState(saved,pending,problems,conflict) andproblems;doc.statuswith a doc adds those andfile, exists, stale(the file changed on disk since the doc was opened or last matched it),undo: [edit summaries],redo: [...]andedits(the edits since the doc last matched its file).doc.save:{ doc, file, wrote, hash, bytes, diff, dirty, canUndo, canRedo }(wroteis false when the file already held exactly this text).doc.undo/doc.redo/doc.revert:{ doc, changed, summary, dirty, canUndo, canRedo };doc.reloadaddsreloaded,doc.mergeaddsmerged.history.list:{ versions: [{ number, startedAt, endedAt, author, edits, files, open }], total }, newest first;history.renderisscene.render's orobject.render's shape plus{ version, doc, kind }, for the Versions sheet's ghost.
Errors are JSON-RPC errors with a stable data.code:
{"jsonrpc":"2.0","id":7,"error":{"code":-32602,"message":"scene.move: item is required","data":{"code":"usage","details":["item is required"]}}}
error.code | when | data.code |
|---|---|---|
| -32700 | a line that isn't JSON (id is null) | |
| -32600 | not a JSON-RPC 2.0 request | |
| -32601 | no such method | not_found |
| -32602 | bad params | usage (with details: every problem) |
| -32000 | the command ran and failed | not_found, invalid (validation problems in details), conflict, dirty, exists, ambiguous, is_group, at_front, paint_blocked, color_cap, load_error, … |
| -32603 | a bug | internal |
Notifications from the server (no id):
| method | params | when |
|---|---|---|
doc.changed | { doc, dirty, canUndo, canRedo, undoLabel, redoLabel, author, saveState, problems } | after each edit, undo, redo, save, revert, merge or reload, whoever made it (the app, or a client through the socket), and when a clean open doc reloads because its file changed. Sent before the request's response. |
doc.stale | { doc, file } | an open doc with a save pending whose file changed on disk and whose merge clashed (the app shows Reload and Keep Mine) |
files.changed | { files: ["scenes/hero-island.json", …] } | sources changed on disk (another editor, git, a save), debounced 150 ms |
history.changed | { versions: [the latest, as history.list lists it] } | the document's version history changed |
activity.appended | { entries: [{ time, tool, args, ok, summary }] } | an MCP server logged tool calls |
An open doc without unsaved changes reloads from disk when its file changes; one with a save pending gets the change merged in (doc.merge: a three-way line merge of the file into the working copy, keeping its edits) and saves. When both changed the same lines the merge refuses (merge_conflict), the server sends doc.stale, and the doc stays unsaved until it's reverted or saved with force.
The owner. The server takes owners/<key>.lock in the state dir for its document with an OS file lock, which the system lets go of when the process ends however it ends, and listens on a Unix socket beside it (or in $TMPDIR when that path is too long). The CLI and MCP servers send their commands to it: the same JSON-RPC, after a client.hello { kind: "mcp" | "cli", name, version, protocol, chat? } that names the author. Their requests run in the server's loop between the app's own, one at a time. A second serve --stdio on an owned document answers session.info with code owned (and the holder's appPid) and exits; Pixel Lab then brings the other copy forward. Only the user's own processes can connect.
MCP#
pixellab serve --mcp [--document <path> | --site <checkout>] is an MCP server over stdio (protocol 2025-06-18, hand-written: initialize, notifications/initialized, ping, tools/list, tools/call). Tools are the commands with . → _ (scene.place → scene_place), each with its params as inputSchema. A tool's result is content: [{ type: "text", text: <the result as JSON> }] plus structuredContent (the result); render tools return the PNG as an image content block first, so agents can see the art. A failed command is a result with isError: true and structuredContent: { error: { code, message, details? } }; an unknown tool is a JSON-RPC error.
Edits apply. An agent's edit changes the document as it's made, with the agent as its author in the version history: nothing waits for approval. While Pixel Lab has the document open, the server sends every call to the app (the owner sees the edit on the stage, and the app saves it); without the app, it saves each edit itself when the call is done (the result lists saved, and unsaved for a doc with problems, which stays in memory until they're fixed). doc_undo takes back the agent's own latest step and refuses (not-yours) when someone else has edited the doc since; history_undo takes back a whole version. --direct is accepted and ignored.
The session follows disk. Before every tools/call in a server of its own, a clean doc whose file changed reloads, so the next edit starts from the file. doc_reload reloads a doc only when nothing unsaved is lost (code dirty with the edits otherwise); doc_revert, which drops edits, is annotated destructive. When the app closes the document mid-call, the call fails with owner-gone and isn't retried. Left out of the tools: art_build (it publishes the SVGs a site inlines; publishing is for a person); doc_adopt (it's how an MCP server hands its unsaved copy to Pixel Lab, not an edit); document_copy (saving a copy of the document is for a person); document_migrate (migrating the document is for a person, in Pixel Lab or with pixellab document migrate).
Setup for Claude Code#
With the app installed by scripts/install.sh, point Claude Code at the program inside it (the Agent window's Connect row shows the same command):
claude mcp add pixellab -- "$HOME/Applications/Pixel Lab.app/Contents/Helpers/pixellab" serve --mcp
Or at a release's pixellab (pixellab-darwin-arm64.tar.gz, pixellab-linux-x64.tar.gz), or this repo's build for development (cargo build --release):
claude mcp add pixellab -- /Users/jlawcordova/Projects/jlawcordova-lab/target/release/pixellab serve --mcp
Add --document <path.pixellab> (or --site <checkout>) after --mcp for another document, or set PIXELLAB_DOC. See what agents did with pixellab history list and pixellab activity list.
The state dir: history, owners and activity#
The state dir is $PIXELLAB_STATE, else ~/Library/Application Support/Pixel Lab/. Nothing here is part of the document, so a site that publishes the document never sees it. <key> is the first 16 hex digits of the sha256 of the document's real path.
history/<key>/: the document's versions.versions.jsonlhas one{ number, startedAt, endedAt, author, edits, changes: [{ file, before, after }], open }per line,beforeandafterbeing the sha256 of the file's text inblobs/<sha256>.json(null: no file). Version 0 is the document as first opened. A version is a stretch of one author's writes (owner,agent,cli,outside,restore), closed when someone else writes, after a minute's pause, byhistory.markordoc.save, or when the document closes. Versions older than a day thin to one a day, older than 30 days to one a week.owners/<key>.lockand<key>.sock: the app's server that owns the document while it's open:{ pid, appPid, document, socket, protocol, version, startedAt }, held with an OS file lock.activity.jsonl: one line per MCPtools/call:{ time, tool, args, ok, summary }(toolis the MCP tool name).retired-proposals/: proposals left from before edits applied, moved here once with anotice.json({ count, dir, at }) that the Agent window shows.
Command reference#
activity#
activity.list#
The last MCP tool calls agents made, oldest first.
pixellab activity list [--limit <n>]
Each entry is { time, tool, args, ok, summary }, from activity.jsonl in the state dir.
read-only · MCP tool activity_list
| param | type | description | |
|---|---|---|---|
--limit | integer | default 50 | how many entries (1 to 1000) |
art#
art.build#
Publish: validate every source, compile each scene with an output and write the SVGs.
pixellab art build [--dir <text>]
Validates the document's files on disk (unsaved working copies aren't in it; unsaved lists them), renders each scene with an output to a rect SVG, merges each color group into one <path>, checks the result lossless, and writes <publish dir>/<output>. A scene with an image is also rendered on the preview card color to a PNG, written to the manifest's publish.images (else --dir). Over the budget (100 KB raw / 25 KB gzip, or the manifest's) is a warning, never a failure: the SVG is still written and the result is still ok. The publish folder is the manifest's publish.dir (relative to the document), or --dir; with neither it refuses. Nothing is written when any source has a problem. Returns { ok, publishDir, outputs: [{ file, scene, kind (svg or png), raw, gzip, over, report, warnings }], previewOnly: [{ scene, raw, gzip, over, report, warnings }], problems, warnings (each "<file>: <warning>"), ms, unsaved }; a PNG has no gzip, over or report.
read-only · writes files directly · not an MCP tool
| param | type | description | |
|---|---|---|---|
--dir | string | the folder to write the SVGs to (else the manifest's publish.dir) |
pixellab art build
pixellab art build --dir ~/Desktop/art
art.preview#
Render the review PNG of an object (standing on a tile) or a scene, on the card color; palette for the swatch sheet.
pixellab art preview <name> [--scale <n>] [--kind <object|scene>] [--out <file.png>]
The picture an agent or a person checks art by: an object on one 32×16 tile outline, a scene at its viewBox (first variant, frame 0), both on the document's card color (manifest preview.card). Refuses (code invalid) when the doc or what it uses has problems. Returns { name, kind, scale, width, height, png } with the PNG as base64; --out writes it.
read-only · MCP tool art_preview
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | an object or scene (or objects/<name>, scenes/<name>), or palette |
--scale | integer | default 1 | whole-pixel zoom of the PNG (1 to 16) |
--kind | object | scene | which kind name is, when both exist |
pixellab art preview pine --scale 4 --out /tmp/pine@4x.png
pixellab art preview palette --out /tmp/palette.png
art.sizes#
Bytes each object and each color adds to a compiled scene, against the document's git HEAD.
pixellab art sizes <name>
The bytes a compiled scene loses, raw and gzip, when an object's items are left out or a color's paths are dropped (costs overlap a little), now and as committed at HEAD of the repository the document is in. Returns { scene, output, now: { raw, gzip }, head: { raw, gzip } | { reason }, objects: [{ object, placed, raw, gzip, headRaw, headGzip }], colors: [{ color, raw, gzip, headRaw, headGzip }] }.
read-only · MCP tool art_sizes
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | a scene (or scenes/<name>) |
pixellab art sizes hero-island
chat#
chat.ask#
Ask the owner a question with two to four answers, shown as buttons in Pixel Lab's Agent chat.
pixellab chat ask <question> --options <text,…>
Use it when the owner should choose (every use of an object or only these, one way or another). It returns at once: the question shows in the chat with a button per answer, and the owner's choice arrives as their next message, so end your turn after asking. With no Pixel Lab chat on this connection (a terminal), it says so: ask in your own conversation.
read-only · MCP tool chat_ask
| param | type | description | |
|---|---|---|---|
question (positional) | string | required | the question, in one or two sentences |
--options | list of string | required | two to four short answers, as button labels |
pixellab chat ask "Make every pine taller, or only these 2?" --options "Only these 2,Every pine (8)"
chat.context#
Build the selection block an Agent chat message carries: what's selected, as the saved document has it.
pixellab chat context [name] [--object <text>] [--layers <text,…>] [--items <text,…>] [--pinned <text,…>]
Pixel Lab sends this block with each chat message. Layers and items are named as the app has them (its working copy, which may have unsaved edits); the block names them by their paths in the saved file. An item placed, moved or changed since the last save, or a layer made or changed since, isn't in the saved file: it's left out and listed in unsaved. With --object, the block names a whole object instead.
read-only · MCP tool chat_context
| param | type | description | |
|---|---|---|---|
name (positional) | string | scene name (or scenes/<name>) | |
--object | string | an object: the block names the whole object (object mode) instead of a scene | |
--layers | list of string | selected layer ids, comma-separated on the command line | |
--items | list of string | selected item paths (12, 5.2), comma-separated on the command line | |
--pinned | list of string | which of those are pinned to the chat: layer:<id> or a path |
pixellab chat context harbor --items 45.0,46.0 --layers boats
check#
check#
Validate the sources with the engine, all of them or one doc.
pixellab check [name] [--kind <object|scene>] [--library <text>]
Problems are the engine's "<file>: <where>: <rule>" lines. With a name, only the problems in that doc and the files it uses (palette.json, the objects it places or extends) are listed. On the command line, the exit code is 1 when there are problems. Warnings don't fail the check, and each has a code. off_grid: a scene's key-to-key moves (the loop back to the first key included) whose steps aren't 1 px straight, 1:1 or 2:1, so the item jumps instead of walking the grid; scene.motion.path makes on-grid keys. out_of_order: a scene whose file lists its items out of depth order (it still publishes in depth order; scene.sort writes the file in order), with how many items would move. copies: a moving item that takes several places among the others as it moves is drawn once for each place in the SVG, which adds to its size; the warning says how many. cropped: a scene with a fixed viewBox paints pixels outside it, which the published SVG cuts off; the warning says how many (scene.frame --fit fits it to its content). empty: a scene with nothing in it and no viewBox, which publishes one tile around the origin. With library, it checks an object library instead (no document needed): its objects against the colors it recorded at export, extends resolving inside it, and categories.
read-only · MCP tool check
| param | type | description | |
|---|---|---|---|
name (positional) | string | an object or scene (or objects/<name>, scenes/<name>); every source when left out | |
--kind | object | scene | which kind name is, when both exist | |
--library | string | check this .pixellib instead of the document: its objects against the colors it recorded, extends within it, categories |
pixellab check
pixellab check hero-island
pixellab check objects/pine --json
pixellab check --library ~/Art/explainer.pixellib
doc#
doc.adopt#
Merge a client's unsaved copy of a doc into this one, as one step.
pixellab doc adopt <doc> [--base <text>] --source <text>
For an MCP server that edited a doc it couldn't save before Pixel Lab opened the document: base is the text it started from (null for a new doc), source its own. One undo step by that client; refuses with merge_conflict when both changed the same lines.
edits docs · session only (serve) · not an MCP tool
| param | type | description | |
|---|---|---|---|
doc (positional) | string | required | the doc: scene:<name>, object:<name>, or a name (scenes/<name>, objects/<name>) |
--base | any | the text the copy started from | |
--source | string | required | the copy's text |
doc.close#
Close an open doc (refused with unsaved changes unless --force).
pixellab doc close <doc> [--force]
read-only · session only (serve) · MCP tool doc_close
| param | type | description | |
|---|---|---|---|
doc (positional) | string | required | the doc: scene:<name>, object:<name>, or a name (scenes/<name>, objects/<name>) |
--force | boolean | default false | drop unsaved changes |
doc.flush#
Save every open doc with changes now (or one), as autosave would soon.
pixellab doc flush [doc]
Edits save themselves about a second after the last one; this saves now. A doc with problems isn't written (saveState problems), and one whose file changed on disk is merged first (saveState conflict when that clashes). Returns { saved, docs: [{ doc, saveState, problems }] }.
edits docs · session only (serve) · MCP tool doc_flush
| param | type | description | |
|---|---|---|---|
doc (positional) | string | the doc: scene:<name>, object:<name>, or a name (scenes/<name>, objects/<name>) |
doc.merge#
Merge the changes to a doc's file on disk into its working copy, keeping the unsaved edits.
pixellab doc merge <doc>
A three-way line merge: the file as the doc was opened or last saved, the file now, and the working copy. One undo step; the doc stays dirty with its own edits. A doc with no unsaved edits just reloads. When both sides changed the same lines it refuses with code merge_conflict (details: [{ base: [from, to], ours: [lines], theirs: [lines] }]) and changes nothing. The merged scene is put in depth order, in the same step (a line merge cannot judge overlaps): sorted says how many items moved. Returns { doc, merged, changed, summary, dirty, canUndo, canRedo, sorted? }.
edits docs · session only (serve) · MCP tool doc_merge
| param | type | description | |
|---|---|---|---|
doc (positional) | string | required | the doc: scene:<name>, object:<name>, or a name (scenes/<name>, objects/<name>) |
doc.open#
Open a scene or object as a working copy, with undo and redo.
pixellab doc open <doc>
Edits to an open doc save themselves (the app's server: about a second after the last one; MCP and the CLI: after each command). Editing a doc that isn't open opens it, so this is only needed to start from a clean status.
read-only · session only (serve) · MCP tool doc_open
| param | type | description | |
|---|---|---|---|
doc (positional) | string | required | the doc: scene:<name>, object:<name>, or a name (scenes/<name>, objects/<name>) |
doc.redo#
Redo the last undone edit to an open doc.
pixellab doc redo <doc>
A scene edit and its depth sort are one step, so undoing it can give items other paths. When it does, the result has reordered: every path that changed, old → new (an item whose placement the step changed pairs with itself), for a caller holding paths.
edits docs · session only (serve) · MCP tool doc_redo
| param | type | description | |
|---|---|---|---|
doc (positional) | string | required | the doc: scene:<name>, object:<name>, or a name (scenes/<name>, objects/<name>) |
doc.reload#
Load an open doc from disk again, only if no unsaved work is lost.
pixellab doc reload <doc>
Reloads when the working copy has no edits its file lacks: it is clean, or it matches the file. Refuses with code dirty (details: the edits that would be lost) when there are unsaved edits. Returns { doc, reloaded, changed, summary, dirty, canUndo, canRedo }; reloaded is false when the doc already matched the file. A scene whose file is not in depth order (a hand edit) is sorted as a step of its own: the result has sorted (how many items moved), the doc is then unsaved, and one undo gives back exactly the file. doc.revert is the one that drops edits.
edits docs · session only (serve) · MCP tool doc_reload
| param | type | description | |
|---|---|---|---|
doc (positional) | string | required | the doc: scene:<name>, object:<name>, or a name (scenes/<name>, objects/<name>) |
doc.revert#
Load an open doc from disk again, dropping its changes (as one undo step).
pixellab doc revert <doc>
A doc that was never saved (made by scene.new or object.new) is discarded. To reload without dropping anything, use doc.reload.
edits docs · session only (serve) · MCP tool doc_revert
| param | type | description | |
|---|---|---|---|
doc (positional) | string | required | the doc: scene:<name>, object:<name>, or a name (scenes/<name>, objects/<name>) |
doc.save#
Save an open doc now and close its version: validate, serialize with the engine and write its source.
pixellab doc save <doc> [--force]
Edits save themselves, so this is the Mac's Save: it writes now and starts a new version for the next edit. Refuses a doc with validation problems (code invalid, details: the problems) and a file that changed on disk since the doc was opened (code conflict), unless --force. Returns { doc, file, wrote, hash, diff, dirty, canUndo, canRedo }.
edits docs · session only (serve) · MCP tool doc_save
| param | type | description | |
|---|---|---|---|
doc (positional) | string | required | the doc: scene:<name>, object:<name>, or a name (scenes/<name>, objects/<name>) |
--force | boolean | default false | save despite problems or a conflict |
doc.status#
Whether open docs have unsaved changes, and can undo or redo.
pixellab doc status [doc]
With a doc: { doc, dirty, canUndo, canRedo, undoLabel, redoLabel, author, saveState, problems, file, exists, stale, undo: [edits], redo: [edits], edits }. saveState: saved, pending (a save is coming), problems (it can't be saved until they're fixed) or conflict (its file changed on disk and clashes). undoLabel and redoLabel name the step and, for anyone but the owner, whose it is. stale: the file changed on disk since the doc was opened or last matched it. edits: the edits since the doc last matched its file. Without a doc: { docs: [{ doc, dirty, canUndo, canRedo }] }.
read-only · session only (serve) · MCP tool doc_status
| param | type | description | |
|---|---|---|---|
doc (positional) | string | the doc: scene:<name>, object:<name>, or a name (scenes/<name>, objects/<name>) |
doc.undo#
Undo the last edit to an open doc.
pixellab doc undo <doc>
A scene edit and its depth sort are one step, so undoing it can give items other paths. When it does, the result has reordered: every path that changed, old → new (an item whose placement the step changed pairs with itself), for a caller holding paths.
edits docs · session only (serve) · MCP tool doc_undo
| param | type | description | |
|---|---|---|---|
doc (positional) | string | required | the doc: scene:<name>, object:<name>, or a name (scenes/<name>, objects/<name>) |
document#
document.copy#
Save a copy of the open document at another path (Save As).
pixellab document copy --to <text>
Copies the whole .pixellab folder (manifest, palette, objects, scenes) to --to. Refuses an existing path. The copy keeps its publish.dir, so a relative one now points next to the copy. A legacy source/ folder has no manifest: the copy gets one with the site's budgets, publish folder (..) and colors, so it publishes as the site did.
edits docs · writes files directly · not an MCP tool
| param | type | description | |
|---|---|---|---|
--to | string | required | the new path (.pixellab is added when missing) |
pixellab document copy --to ~/Art/meadow-v2.pixellab
document.info#
Show the document in use: where it came from, its publish folder and what it holds.
pixellab document info
root is the .pixellab folder (or a legacy source/ folder in a site checkout, kind legacy-site); from says how it was named (--document, --site, PIXELLAB_DOC, PIXELLAB_SITE, the config file or the default); publishDir is where art build writes (null when the manifest names none); then the git branch, and how many scenes, objects and colors it has, with the count of validation problems.
read-only · MCP tool document_info
document.migrate#
Migrate a document from format 1 (.mjs sources) to format 2 (JSON sources), in place or as a copy.
pixellab document migrate [--to <text>]
Reads every .mjs source as data (comments become notes; anything that isn't plain data is refused with its file, line and column), checks that every scene builds the same SVG from the JSON sources, then writes palette.json, objects/.json, scenes/.json and a manifest with "format": 2. In place it removes the .mjs files (a site's legacy source/ folder becomes src/assets/pixel-art/<name>.pixellab); with --to it writes a copy and leaves the document as it is. Refuses while the document has unsaved edits open in Pixel Lab.
edits docs · writes files directly · not an MCP tool
| param | type | description | |
|---|---|---|---|
--to | string | write the migrated document to this new folder instead of converting it in place |
pixellab document migrate --document ~/art/old.pixellab
pixellab document migrate --to /tmp/copy.pixellab
document.new#
Make a new, empty .pixellab document with the starter palette.
pixellab document new <path> [--name <text>]
Writes manifest.json, palette.json and empty objects/ and scenes/ folders at path (.pixellab is added when missing). Refuses an existing path. The new document is not opened: name it with --document.
edits docs · writes files directly · MCP tool document_new
| param | type | description | |
|---|---|---|---|
path (positional) | string | required | where to make it, e.g. ~/Art/meadow.pixellab |
--name | string | the document's name (the folder's, by default) |
pixellab document new ~/Art/meadow.pixellab
history#
history.list#
List the document's versions, newest first.
pixellab history list [--doc <text>] [--limit <n>]
Each is { number, startedAt, endedAt, author: { kind, name?, chat? }, edits, files, open }. A version is a stretch of one author's writes (owner, agent, cli, outside, restore; several once thinned). open marks the version still growing. With doc, only versions that changed it.
read-only · MCP tool history_list
| param | type | description | |
|---|---|---|---|
--doc | string | a scene or object: its name, scene:<name> or object:<name>, or scenes/<name>.json | |
--limit | integer | default 50 | how many versions (1 to 1000) |
history.mark#
Close the open version, so the next edit starts a new one.
pixellab history mark [--label <text>]
edits docs · writes files directly · MCP tool history_mark
| param | type | description | |
|---|---|---|---|
--label | string | a name for the version |
history.render#
Render a scene or object as it was after a version, without writing anything.
pixellab history render <version> <doc> [--scale <n>] [--time <n>] [--frame <n>] [--layers <n,…>] [--theme <light|dark>] [--out <file.png>]
The doc as it was after that version, over the rest of the document as it is now, rendered as scene.render or object.render renders (same params, same result shape), plus { version, doc, kind }. Pixel Lab draws it over the stage as a ghost.
read-only · MCP tool history_render
| param | type | description | |
|---|---|---|---|
version (positional) | integer | required | the version's number (history.list) (at least 0) |
doc (positional) | string | required | a scene or object: its name, scene:<name> or object:<name>, or scenes/<name>.json |
--scale | integer | default 1 | whole-pixel zoom of the PNG (1 to 16) |
--time | integer | ms into the motion and loops (a scene: default 0; an object: overrides frame) (at least 0) | |
--frame | integer | default 0 | an object's frame (at least 0) |
--layers | list of integer | only these layers of a sprite | |
--theme | light | dark | default "light" | a scene's theme |
history.restore#
Put the document (or one doc) back as it was after a version.
pixellab history restore <version> [--doc <text>]
Every scene and object (or just doc) goes back to its text at that version: one undo step per doc, saved at once, and a version of its own, so a restore can be undone too. A doc made since is removed; one removed since comes back.
edits docs · writes files directly · MCP tool history_restore
| param | type | description | |
|---|---|---|---|
version (positional) | integer | required | the version's number (history.list) (at least 0) |
--doc | string | a scene or object: its name, scene:<name> or object:<name>, or scenes/<name>.json |
history.show#
Show a version: its edits and a diff of each file it changed.
pixellab history show <version> [--doc <text>]
read-only · MCP tool history_show
| param | type | description | |
|---|---|---|---|
version (positional) | integer | required | the version's number (history.list) (at least 0) |
--doc | string | a scene or object: its name, scene:<name> or object:<name>, or scenes/<name>.json |
history.undo#
Take back just what one version changed, keeping everything since.
pixellab history undo <version> [--force]
Each file the version changed is merged three ways (its after as the base, the doc now, its before), so later edits stay. Refuses (code clash, the files in details) when later edits changed the same lines, writing nothing. One undo step per doc, saved, and a version of its own.
edits docs · writes files directly · MCP tool history_undo
| param | type | description | |
|---|---|---|---|
version (positional) | integer | required | the version's number (history.list) (at least 0) |
--force | boolean | default false | save even when the result has problems |
library#
library.category#
Set or clear the category of objects in a .pixellib, in place.
pixellab library category <library> <names…> [--category <text>] [--to <text>] [--clear]
Writes the library's object files in place, canonical, while holding a lock on the folder. A library has no undo and no history: it is a file, and this is the only way to edit one (no painting, adding or removing objects). The rules for a category are object.category's.
edits docs · writes files directly · MCP tool library_category
| param | type | description | |
|---|---|---|---|
library (positional) | string | required | the .pixellib folder |
names (positional) | list of string | required | the objects |
--category | any | the category as names, outermost first, or null to clear it | |
--to | string | the category as a path: Nature/Trees or Nature › Trees | |
--clear | boolean | default false | remove the category |
pixellab library category ~/Art/explainer.pixellib cat cat-white --to "Animals/Cats"
library.export#
Export the document's objects to a new .pixellib.
pixellab library export <to> [--objects <text,…>] [--category <text,…>] [--replace]
Copies all of the document's objects, the objects of one category and everything below it, or a named list into a package of the same object files, with a record of the palette colors they use (never read as a palette). Every object that a chosen one extends comes too, marked base: true in the result, so the library stands on its own. Writes a temp folder beside the target and renames it into place; refuses an existing library (exists) unless replace. The document doesn't change.
edits docs · writes files directly · MCP tool library_export
| param | type | description | |
|---|---|---|---|
to (positional) | string | required | the new library (.pixellib is added when missing) |
--objects | list of string | only these objects | |
--category | list of string | only the objects in this category and below it: names outermost first, or a path such as Nature/Trees | |
--replace | boolean | default false | replace a library that is already there |
pixellab library export ~/Art/everything.pixellib
pixellab library export ~/Art/trees.pixellib --category Nature/Trees
pixellab library export ~/Art/some.pixellib --objects pine,tree
library.import#
Import the objects of a .pixellib into the document, by copying.
pixellab library import <from> [--accept-differences] [--convert <json>] [--dry-run]
Copies the library's objects, with their categories, into the document; nothing links it to the library afterwards, and the same library can be imported again. An imported object whose name is taken gets the first free suffix (pine-2) and every extends in the import follows; the document's own objects are never renamed or changed. One that equals the document's same-named object, apart from its category, isn't copied (fate same). A library's character that differs from the document's isn't copied: its figures extend the document's character, which counts as a difference. The plan (dryRun returns it): objects with their fate (new, renamed, same, character), renames, differing colors, the character swap, and missing colors with each one's tier and the document's nearest color of that tier. Refusals, each carrying the plan in its details: differs (colors that differ from the library's record, or the character swap) unless acceptDifferences; missing_colors unless convert names a color for every missing one; wrong_tier when a conversion crosses tiers (an outfit color converts only to an outfit color); invalid when the library or the result doesn't pass check. An agent that hits differs or missing_colors asks the owner with chat_ask before accepting or converting. The import never changes the palette, the existing objects or the scenes. It is one version in the history (Imported 12 objects from explainer.pixellib), whose number is in the result, and one undo step: history_undo takes it back.
edits docs · writes files directly · MCP tool library_import
| param | type | description | |
|---|---|---|---|
from (positional) | string | required | the .pixellib folder |
--accept-differences | boolean | default false | import although colors differ from the library's record, or its character differs |
--convert | object | for each color the document lacks, the document's color of the same tier to use instead: {"wood-5": "wood-3"} | |
--dry-run | boolean | default false | return the plan and change nothing |
pixellab library import ~/Art/explainer.pixellib --dry-run
pixellab library import ~/Art/explainer.pixellib --accept-differences --convert '{"wood-5":"wood-3"}'
library.info#
Show an object library: its objects, their categories and the colors they used.
pixellab library info <library>
Lists each object (name, kind, extends, category), the distinct categories with how many objects each holds, the colors recorded at export (never a palette) and the problems check --library finds. Needs no document.
read-only · MCP tool library_info
| param | type | description | |
|---|---|---|---|
library (positional) | string | required | the .pixellib folder |
pixellab library info ~/Art/explainer.pixellib
object#
object.action#
Add, change or delete an action of a sprite map layer.
pixellab object action <name> <layer> <action> [--duration <n>] [--keys <json,…>] [--delete]
As scene action, for one layer of the sprite. A frame loop layer, a block and an outfit refuse. object render --time T --play <action>@<ms> shows a frame of it.
edits docs · MCP tool object_action
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | object name (or objects/<name>) |
layer (positional) | integer | required | the map layer, from 0 at the bottom (a frame loop or a block has none) (at least 0) |
action (positional) | string | required | the action's name: lowercase words joined by hyphens, like hop |
--duration | integer | the action's length in ms, up to 600000 (at least 1) | |
--keys | list of object | the keys, a JSON list of { "t": ms, "move": [x, y] }: the first at t 0, each after the one before and below the duration, moves in whole pixels from where the art is drawn. On the command line, the list as one JSON string. | |
--delete | boolean | default false | delete the action instead |
pixellab object action character 1 hop --duration 480 --keys '[{"t":0,"move":[0,0]},{"t":120,"move":[0,-4]}]'
pixellab object action character 1 hop --delete
object.ambient#
Set or clear a sprite map layer's ambient loop.
pixellab object ambient <name> <layer> [--duration <n>] [--offset <n>] [--keys <json,…>] [--clear]
As scene ambient, for one layer of the sprite, which moves wherever the sprite is placed. A frame loop layer, a block and an outfit (which has no layers of its own) refuse. object render --time places the loop.
edits docs · MCP tool object_ambient
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | object name (or objects/<name>) |
layer (positional) | integer | required | the map layer, from 0 at the bottom (a frame loop or a block has none) (at least 0) |
--duration | integer | the loop's length in ms, up to 600000 (at least 1) | |
--offset | integer | start the loop this many ms in (below the duration) (at least 0) | |
--keys | list of object | the keys, a JSON list of { "t": ms, "move": [x, y] }: the first at t 0, each after the one before and below the duration, moves in whole pixels from where the art is drawn. On the command line, the list as one JSON string. | |
--clear | boolean | default false | remove the ambient loop instead |
pixellab object ambient pine 1 --duration 4000 --keys '[{"t":0,"move":[0,0]},{"t":2000,"move":[1,0]}]'
pixellab object ambient pine 1 --clear
object.animate#
Turn a still layer into one of the site's frame loops, or switch a loop's kind.
pixellab object animate <name> [--layer <n>] [--loop <ff|wf|hf>]
As the lab's Animate this layer: the map becomes frame 0 and is copied into every other frame of the loop (edit.animateLayer). On a layer that's already a loop, it plays as the other loop instead, gaining copies of its last frame if it needs more (edit.setLoop).
edits docs · MCP tool object_animate
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | object name (or objects/<name>) |
--layer | integer | default 0 | the layer, from 0 at the bottom (at least 0) |
--loop | ff | wf | hf | default "ff" | ff: 4 frames, 600 ms; wf: 5, 1 s; hf: 6, 3 s |
object.category#
Set or clear the category of one or more objects.
pixellab object category <names…> [--category <text>] [--to <text>] [--clear]
A category is a path of names, outermost first (Nature › Trees), that groups an object under a tab and heading in the Library. A name is 1 to 32 letters, digits, spaces, &, ' or -, with no space at either end or doubled, and All and Others can't be the outermost (they are the Library's own tabs). Names compare without regard to case. A category never changes how an object renders or what Publish writes. Give --to on the command line, or category (a list, or null to clear) in JSON. Each object is its own edit and undo step; the command checks every object and the path first.
edits docs · MCP tool object_category
| param | type | description | |
|---|---|---|---|
names (positional) | list of string | required | the objects (names, or objects/<name>) |
--category | any | the category as names, outermost first, or null to clear it | |
--to | string | the category as a path: Nature/Trees or Nature › Trees | |
--clear | boolean | default false | remove the category |
pixellab object category pine tree --to "Nature/Trees"
pixellab object category pine --clear
object.erase#
Erase pixels of an object (make them transparent), on a layer and frame.
pixellab object erase <name> <pixels…> [--layer <n>] [--frame <n>]
edits docs · MCP tool object_erase
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | object name (or objects/<name>) |
pixels (positional) | list of [integer, …] | required | pixels as [x, y] pairs; on the command line x,y pairs separated by spaces (or the flag repeated) |
--layer | integer | default 0 | the layer, from 0 at the bottom (at least 0) |
--frame | integer | default 0 | the frame of a frame loop (a block: of its surface loop) (at least 0) |
object.fill#
Flood-fill from a pixel with a palette color, on a layer and frame.
pixellab object fill <name> --x <n> --y <n> --color <text> [--layer <n>] [--frame <n>]
As the lab's Fill: the 4-connected pixels that share the start pixel's key (edit.floodFill); on a block, those of the start pixel's color on the same face (edit.blockFill).
edits docs · MCP tool object_fill
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | object name (or objects/<name>) |
--x | integer | required | |
--y | integer | required | |
--color | string | required | a palette color name (pixellab palette get) |
--layer | integer | default 0 | the layer, from 0 at the bottom (at least 0) |
--frame | integer | default 0 | the frame of a frame loop (a block: of its surface loop) (at least 0) |
pixellab object fill pine --x 3 --y 4 --color grass-1
object.footprint#
Set how many tiles a sprite covers (--to w,d), or clear it (--clear).
pixellab object footprint <name> [--to <n,…>] [--clear]
A sprite sorts against its neighbours in depth order by the ground it covers: a footprint of [wide, deep] tiles from the tile it's placed on, so a house, a locomotive or the crane mast go in front of and behind things by the tiles under them. The default is 1,1. A block's size is its footprint, so blocks refuse this. An object that extends another has the other's footprint unless it sets its own. The scenes that use it are put in order again at their next edit; check warns until then (scene.sort does it now).
edits docs · MCP tool object_footprint
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | object name (or objects/<name>) |
--to | list of integer | tiles wide and deep: w,d (1 to 16 each) | |
--clear | boolean | default false | take the footprint off (a sprite then covers 1,1, or what it extends) |
pixellab object footprint house --to 2,2
pixellab object footprint house --clear
object.frame.add#
Add a blank frame after a frame of a layer's loop.
pixellab object frame add <name> [--layer <n>] [--frame <n>]
edits docs · MCP tool object_frame_add
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | object name (or objects/<name>) |
--layer | integer | default 0 | the layer, from 0 at the bottom (at least 0) |
--frame | integer | default 0 | the frame of a frame loop (a block: of its surface loop) (at least 0) |
object.frame.delete#
Delete a frame of a loop (not the only one).
pixellab object frame delete <name> [--layer <n>] [--frame <n>]
edits docs · MCP tool object_frame_delete
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | object name (or objects/<name>) |
--layer | integer | default 0 | the layer, from 0 at the bottom (at least 0) |
--frame | integer | default 0 | the frame of a frame loop (a block: of its surface loop) (at least 0) |
object.frame.duplicate#
Duplicate a frame of a loop, just after it.
pixellab object frame duplicate <name> [--layer <n>] [--frame <n>]
edits docs · MCP tool object_frame_duplicate
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | object name (or objects/<name>) |
--layer | integer | default 0 | the layer, from 0 at the bottom (at least 0) |
--frame | integer | default 0 | the frame of a frame loop (a block: of its surface loop) (at least 0) |
object.frame.move#
Move a frame of a loop later (--by 1) or earlier (--by -1).
pixellab object frame move <name> [--layer <n>] [--frame <n>] --by <n>
edits docs · MCP tool object_frame_move
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | object name (or objects/<name>) |
--layer | integer | default 0 | the layer, from 0 at the bottom (at least 0) |
--frame | integer | default 0 | the frame of a frame loop (a block: of its surface loop) (at least 0) |
--by | integer | required | steps later (positive) or earlier (negative) |
object.layer.add#
Add a blank layer on top of an object.
pixellab object layer add <name>
edits docs · MCP tool object_layer_add
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | object name (or objects/<name>) |
object.layer.delete#
Delete a layer (not the only one).
pixellab object layer delete <name> <layer>
edits docs · MCP tool object_layer_delete
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | object name (or objects/<name>) |
layer (positional) | integer | required | the layer, from 0 at the bottom (at least 0) |
object.layer.duplicate#
Duplicate a layer, just above it.
pixellab object layer duplicate <name> [layer]
edits docs · MCP tool object_layer_duplicate
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | object name (or objects/<name>) |
layer (positional) | integer | default 0 | the layer, from 0 at the bottom (at least 0) |
object.layer.move#
Move a layer up (--by 1, raise) or down (--by -1, lower).
pixellab object layer move <name> <layer> --by <n>
edits docs · MCP tool object_layer_move
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | object name (or objects/<name>) |
layer (positional) | integer | required | the layer, from 0 at the bottom (at least 0) |
--by | integer | required | steps up (positive) or down (negative) |
object.list#
List every object with its kind, category, size, layers, frames and colors.
pixellab object list [--kind <sprite|block>] [--category <text,…>]
read-only · MCP tool object_list
| param | type | description | |
|---|---|---|---|
--kind | sprite | block | only objects of this kind | |
--category | list of string | only objects in this category and below it, without regard to case: names outermost first, or a path such as Nature/Trees |
object.map.set#
Replace a whole map of a sprite's layer (or frame of a loop) from rows, in one undo step.
pixellab object map set <name> <rows…> [--keys <json>] [--layer <n>] [--frame <n>] [--resize]
Rows are strings of one character per pixel, top to bottom, '.' transparent. With --keys (a legend, { char: color }), each legend character becomes the object's key for that color, made if it has none, so a generated map can use any characters; without, the characters are the object's own keys. Checks the palette, outfit colors (only for objects that extend character), the 12-color cap and the 64×64 size cap. A size other than the object's is refused unless --resize, which pads or crops every map first (as object.resize). On an object that extends another, a still layer's rows become row overrides. Blocks are refused: paint their surface and sides.
edits docs · MCP tool object_map_set
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | object name (or objects/<name>) |
rows (positional) | list of string | required | the map's rows, top to bottom; on the command line each row a positional or the flag repeated |
--keys | object | a legend: character → palette color, as JSON on the command line | |
--layer | integer | default 0 | the layer, from 0 at the bottom (at least 0) |
--frame | integer | default 0 | the frame of a frame loop (a block: of its surface loop) (at least 0) |
--resize | boolean | default false | resize the object to the rows' size first |
pixellab object map set pebble .gg. gwwg gwwg .gg. --keys '{"g": "grass-2", "w": "ink"}'
object.new#
Make a new object from a starter: a blank sprite, a block, or one that extends another.
pixellab object new <name> [--kind <sprite|block>] [--extends <text>] [--width <n>] [--height <n>] [--footprint <n,…>]
The same starters as the lab's New dialog and npm run art -- --new (starter.rs): a sprite of --width × --height (16×16 by default) anchored at its bottom center (--footprint says how many tiles it covers, 1,1 unless given), a one-level grass block, or an object that --extends another with no rows overridden yet.
edits docs · MCP tool object_new
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | the new object's name (lowercase kebab-case) |
--kind | sprite | block | default "sprite" | |
--extends | string | the object it extends (a sprite, such as character) | |
--width | integer | a new sprite's width (default 16) (1 to 64) | |
--height | integer | a new sprite's height (default 16) (1 to 64) | |
--footprint | list of integer | the tiles a new sprite covers, for depth order: w,d (default 1,1) |
pixellab object new small-rock --width 12 --height 8
pixellab object new shed --width 40 --height 32 --footprint 2,1
pixellab object new tile-sand --kind block
pixellab object new range-hat --extends character
object.paint#
Paint pixels of an object a palette color, on a layer and frame.
pixellab object paint <name> <pixels…> --color <text> [--layer <n>] [--frame <n>]
Pixels are object.render coordinates at scale 1 (a sprite's map column and row). A new color gets a key, as the lab gives one. An object that extends another paints row overrides; a block paints its top surface and side maps. Outfit colors are only for objects that extend character, and an object paints at most 12 colors. Rapid repeats coalesce into one undo step.
edits docs · MCP tool object_paint
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | object name (or objects/<name>) |
pixels (positional) | list of [integer, …] | required | pixels as [x, y] pairs; on the command line x,y pairs separated by spaces (or the flag repeated) |
--color | string | required | a palette color name (pixellab palette get) |
--layer | integer | default 0 | the layer, from 0 at the bottom (at least 0) |
--frame | integer | default 0 | the frame of a frame loop (a block: of its surface loop) (at least 0) |
pixellab object paint pine 3,4 4,4 --color grass-2
pixellab object paint pine --pixels 3,4 --color ink --layer 1
object.pick#
The color at a pixel of an object, on a layer and frame (the Picker).
pixellab object pick <name> --x <n> --y <n> [--layer <n>] [--frame <n>]
A sprite's color on that layer; a block's as it's drawn. Returns { color, hex }, both null for a transparent pixel.
read-only · MCP tool object_pick
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | object name (or objects/<name>) |
--x | integer | required | |
--y | integer | required | |
--layer | integer | default 0 | the layer, from 0 at the bottom (at least 0) |
--frame | integer | default 0 | the frame of a frame loop (a block: of its surface loop) (at least 0) |
object.render#
Render one object to a transparent PNG.
pixellab object render <name> [--scale <n>] [--frame <n>] [--time <n>] [--play <text>] [--layers <n,…>] [--out <file.png>]
The anchor is at (0, 0). A sprite's viewBox is its whole map, so at scale 1 PNG pixel (x, y) is map column x, row y; a block's is the box around its pixels. Returns the same shape as scene.render, with one item. --time also places the layers' ambient loops, and --play <action>@<ms> (with --time) places the layers that have that action where it has them <ms> into it; with --time the viewBox grows to hold the moved layers (the map's box, plus what moved outside it), and viewBox says where. On the command line, --out <file.png> writes the PNG.
read-only · MCP tool object_render
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | object name (or objects/<name>) |
--scale | integer | default 1 | whole-pixel zoom of the PNG (1 to 16) |
--frame | integer | default 0 | the frame every loop shows (loops shorter than this show nothing) (at least 0) |
--time | integer | ms into the loops; overrides frame (at least 0) | |
--play | string | an action and the ms into it, like hop@120: the elements that have it are placed where it has them then, instead of on their ambient loop | |
--layers | list of integer | only these layers of a sprite, each at --frame or its last frame, as the lab's stage shows them (hidden layers, onion skin) |
pixellab object render pine --scale 4 --out /tmp/pine.png
pixellab object render character --time 0 --play hop@120 --out /tmp/hop.png
object.resize#
Pad or crop a sprite's maps to a width and height (at the right and bottom).
pixellab object resize <name> [--width <n>] [--height <n>]
edits docs · MCP tool object_resize
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | object name (or objects/<name>) |
--width | integer | the new width (default: as it is) (1 to 64) | |
--height | integer | the new height (default: as it is) (1 to 64) |
object.show#
Show an object's kind, category, size, anchor, footprint, layers, frames, colors and extends.
pixellab object show <name> [--doc]
read-only · MCP tool object_show
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | object name (or objects/<name>) |
--doc | boolean | default false | also return the object's source data as doc |
object.still#
Turn a frame loop back into a still layer, keeping one frame.
pixellab object still <name> [--layer <n>] [--frame <n>]
edits docs · MCP tool object_still
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | object name (or objects/<name>) |
--layer | integer | default 0 | the layer, from 0 at the bottom (at least 0) |
--frame | integer | default 0 | the frame of a frame loop (a block: of its surface loop) (at least 0) |
palette#
palette.export#
Export the document's palette to a new .pixelpal file.
pixellab palette export <to> [--replace]
Writes the whole palette (world and outfit tiers, names, values and color notes) as one JSON file, with a format and kind line naming it a palette. Refuses an existing file (exists) unless replace. The document doesn't change.
edits docs · writes files directly · MCP tool palette_export
| param | type | description | |
|---|---|---|---|
to (positional) | string | required | the new palette file (.pixelpal is added when missing) |
--replace | boolean | default false | replace a file that is already there |
pixellab palette export ~/Art/coast.pixelpal
palette.get#
List the palette: every color name, hex and tier.
pixellab palette get
World colors are for every object; outfit colors only for objects that extend character.
read-only · MCP tool palette_get
palette.import#
Add the colors of a .pixelpal file the document doesn't have.
pixellab palette import <from> [--keep <text,…>] [--use-theirs <text,…>] [--only <text,…>] [--dry-run]
Adds each color the document doesn't have, by name, to its tier at the end of it, with the file's note for it. It never removes a color; a color with the same name and value is left alone. A name the document has with another value is listed as differing, and the import refuses (palette_differs) until each is in keep or useTheirs; a name that is in the other tier in the file is listed in otherTier and stays as the document has it. When adding would pass a tier's cap (32 world colors, 16 outfit colors) nothing is added: it refuses (palette_full) saying how many colors are over, and only narrows the import to a chosen list. dryRun returns the plan. One edit, one undo step and one version (Imported 4 colors from coast.pixelpal). An agent that hits palette_differs asks the owner with chat_ask before choosing.
edits docs · MCP tool palette_import
| param | type | description | |
|---|---|---|---|
from (positional) | string | required | the .pixelpal file |
--keep | list of string | colors that differ: keep the document's value | |
--use-theirs | list of string | colors that differ: take the file's value (changes how every object using it looks) | |
--only | list of string | add only these new colors | |
--dry-run | boolean | default false | return the plan and change nothing |
pixellab palette import ~/Art/coast.pixelpal --dry-run
pixellab palette import ~/Art/coast.pixelpal --keep wood-3
scene#
scene.action#
Add, change or delete a scene item's action: a one-shot move the page plays by name.
pixellab scene action <name> <item> <action> [--duration <n>] [--keys <json,…>] [--delete]
An action plays when the page names it in data-play (the site contract's actions), replaces the item's ambient loop while it plays, holds its last key until --duration ms, then the item is back at rest. --keys is as for scene ambient. Giving a name the item has changes it; --delete removes it. An item has at most 16 actions, and a name is one move in a scene. scene render --play <action>@<ms> shows a frame of it.
edits docs · MCP tool scene_action
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | scene name (or scenes/<name>) |
item (positional) | string | required | the item: its path from scene items (12, 5.2) or its id |
action (positional) | string | required | the action's name: lowercase words joined by hyphens, like hop |
--duration | integer | the action's length in ms, up to 600000 (at least 1) | |
--keys | list of object | the keys, a JSON list of { "t": ms, "move": [x, y] }: the first at t 0, each after the one before and below the duration, moves in whole pixels from where the art is drawn. On the command line, the list as one JSON string. | |
--delete | boolean | default false | delete the action instead |
pixellab scene action meadow frog hop --duration 480 --keys '[{"t":0,"move":[0,0]},{"t":120,"move":[0,-4]}]'
pixellab scene action meadow frog hop --delete
scene.ambient#
Set or clear a scene item's ambient loop: a move that repeats on its own.
pixellab scene ambient <name> <item> [--duration <n>] [--offset <n>] [--keys <json,…>] [--clear]
The loop lasts --duration ms and starts --offset ms in. --keys is a JSON list of { "t", "move" }: the first at t 0, each later one after the one before and below the duration; the item is moved by [x, y] whole pixels from where the scene places it, stepping a pixel at a time between keys, and after the last key it steps back to the first over the rest of the duration. It plays in the SVG on its own, and scene render --time places it. Setting replaces the loop the item has. --clear removes it. An item with scene motion can't also have an ambient loop. An edit that would leave the scene with a problem is refused, and nothing changes.
edits docs · MCP tool scene_ambient
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | scene name (or scenes/<name>) |
item (positional) | string | required | the item: its path from scene items (12, 5.2) or its id |
--duration | integer | the loop's length in ms, up to 600000 (at least 1) | |
--offset | integer | start the loop this many ms in (below the duration) (at least 0) | |
--keys | list of object | the keys, a JSON list of { "t": ms, "move": [x, y] }: the first at t 0, each after the one before and below the duration, moves in whole pixels from where the art is drawn. On the command line, the list as one JSON string. | |
--clear | boolean | default false | remove the ambient loop instead |
pixellab scene ambient meadow cloud --duration 16000 --keys '[{"t":0,"move":[0,0]},{"t":8000,"move":[12,0]}]'
pixellab scene ambient meadow cloud --clear
scene.drop#
Drop an item onto whatever it stands over: down to the top of the block under it, or the ground.
pixellab scene drop <name> <item> [--keep-group]
As the lab's Drop key: the item's level becomes the one where it rests on the highest block under its column and row (a block of height h rests with its bottom on it), or level 0, the ground, when nothing is under it. It never moves up; an item already resting stays. One undo step. The item's group's data-x/data-y follow, as scene.move keeps them (--no-keep-group leaves them), and the scene is put in depth order.
edits docs · MCP tool scene_drop
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | scene name (or scenes/<name>) |
item (positional) | string | required | the item: its path from scene items (12, 5.2) or its id |
--keep-group | boolean | default true | keep the item's group in step: data-x/data-y that were its placement point or foot, and data-col/data-row that were its tile, follow it |
pixellab scene drop hero-island 12
pixellab scene drop hero-island crate --dry-run
scene.duplicate#
Duplicate an item one column over, where the copy stands in depth order.
pixellab scene duplicate <name> <item>
As the lab's Duplicate: the copy is moved one column (a tile, or the same screen offset by pixel) and takes no id, so no motion track. It is listed where it sorts, which is usually next to the original; the result has its path after the sort.
edits docs · MCP tool scene_duplicate
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | scene name (or scenes/<name>) |
item (positional) | string | required | the item: its path from scene items (12, 5.2) or its id |
scene.frame#
Show a scene's published size, fix it (--box), fit it to its content (--fit) or set its margin.
pixellab scene frame <name> [--box <n,…>] [--fit] [--margin <n>]
A scene with a viewBox publishes at exactly that box (fixed). One without publishes at the smallest whole-pixel box that holds everything it paints, over its whole loop (scene motion, ambient loops and actions included), plus its margin (fit). --box x,y,w,h fixes the size; --fit removes the viewBox so the scene fits its content again; --margin n sets the pixels left around a fit scene's art (a fixed box ignores it). With none of them it reports the frame: { frame: "fixed" | "fit", viewBox, size: [w, h], margin }. One undo step, saved like every edit.
edits docs · MCP tool scene_frame
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | scene name (or scenes/<name>) |
--box | list of integer | fix the size: x,y,w,h in scene pixels (w and h above 0) | |
--fit | boolean | default false | take the fixed size off, so the scene publishes at the box that fits what is in it |
--margin | integer | pixels left around the painted pixels of a fit scene (0 or more; 0 takes it off) (at least 0) |
pixellab scene frame hero-island
pixellab scene frame hero-island --box -16,-16,272,200
pixellab scene frame hero-island --fit --margin 2
scene.group.set#
Set or remove a group's class and data attributes, or set its data-x and data-y to its first item's foot.
pixellab scene group set <name> <group> [--attrs <json>] [--foot]
--group is the group's path (scene items lists groups), or an item (path or id) inside it. --attrs is an object of attribute values ({"data-enter": "object", "data-x": "96"}); '' removes one. --foot sets data-x and data-y to the foot of the group's first item: an item on a tile stands on its tile's center, a sprite placed by pixel on the center of its bottom row. One undo step.
edits docs · MCP tool scene_group_set
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | scene name (or scenes/<name>) |
group (positional) | string | required | the group: its path, or an item (path or id) inside it |
--attrs | object | attribute values; '' removes one (JSON on the command line) | |
--foot | boolean | default false | set data-x and data-y to the first item's foot |
pixellab scene group set hero-island 42 --foot
pixellab scene group set hero-island truck-a --attrs '{"data-enter": "object"}'
scene.items#
List a scene's items in depth order (back to front), with stable paths.
pixellab scene items <name>
Paths index into the scene's items: "3" is the fourth top-level item, "5.2" the third item inside the group at "5". The list is in depth order (every edit keeps it so; scene.sort fixes a file that isn't): back to front, by where each item stands. Each item has its object, at ({ tile: [col, row, level] } or { px: [x, y], level? }), level (a pixel item's, 0 unless it says), depth (front or back, when it has the override), class, id, layer, enclosing group and placement point in scene pixels. A group in groups has its path, attrs, item count and depth. layers lists the scene's layers ({ id, name, parent, items }: items counts what is in it and in the layers inside it), in the order the Layers panel shows them.
read-only · MCP tool scene_items
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | scene name (or scenes/<name>) |
scene.key.delete#
Delete a key of an item's track (not the one at 0: remove the track for that).
pixellab scene key delete <name> <item> --time <n>
edits docs · MCP tool scene_key_delete
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | scene name (or scenes/<name>) |
item (positional) | string | required | the item: its path from scene items (12, 5.2) or its id; an item needs an id to have a track (scene.set --id) |
--time | integer | required | ms into the loop (at least 0) |
scene.key.move#
Move a key of an item's track to another time.
pixellab scene key move <name> <item> --time <n> --to <n>
The key at 0 (the rest frame) stays; a key can't land on another.
edits docs · MCP tool scene_key_move
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | scene name (or scenes/<name>) |
item (positional) | string | required | the item: its path from scene items (12, 5.2) or its id; an item needs an id to have a track (scene.set --id) |
--time | integer | required | ms into the loop (at least 0) |
--to | integer | required | its new time, ms into the loop (at least 0) |
scene.key.set#
Set a key of an item's track: its move, show and pose at a time (made if there's no key there).
pixellab scene key set <name> <item> --time <n> [--move <n,…>] [--show] [--pose <text>] [--clear <move|show|pose,…>]
--move x,y is pixels from where the scene places the item; --show false hides it from that key on; --pose <object> draws another object from that key on. --clear move,show,pose removes those values from the key, so they carry on from the key before. Returns the key and the track's off-grid warnings (moves whose steps aren't 1 px straight, 1:1 or 2:1).
edits docs · MCP tool scene_key_set
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | scene name (or scenes/<name>) |
item (positional) | string | required | the item: its path from scene items (12, 5.2) or its id; an item needs an id to have a track (scene.set --id) |
--time | integer | required | ms into the loop (at least 0) |
--move | list of integer | pixels from where the scene places the item: x,y | |
--show | boolean | whether the item shows from this key on | |
--pose | string | the object the item draws from this key on | |
--clear | list of string | values to remove from the key |
pixellab scene key set hero-island truck-a --time 4200 --move 32,16
pixellab scene key set hero-island crane-hook --time 9000 --pose crane-hook-down
pixellab scene key set hero-island parcel --time 12000 --no-show
scene.layer.assign#
Move items and layers into a layer, or to the top level.
pixellab scene layer assign <name> --to <text> [--items <text,…>] [--layers <text,…>]
--to is the layer ('' for the top level). Items keep their place in the scene (paint order doesn't change); a layer can't go inside itself or a layer within it.
edits docs · MCP tool scene_layer_assign
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | scene name (or scenes/<name>) |
--to | string | required | the layer to move them into: its id or name, or '' for the top level |
--items | list of string | items: their paths from scene items (12, 5.2) or ids, comma-separated on the command line | |
--layers | list of string | layers: their ids (or names), comma-separated on the command line |
pixellab scene layer assign hero-island --to railway --items 150.0,151.0
pixellab scene layer assign hero-island --to '' --layers train
scene.layer.move#
Move layers (every item in them, and in the layers inside them) and items by tiles, together.
pixellab scene layer move <name> [layer] [--layers <text,…>] [--items <text,…>] --by <n,…> [--keep-group]
As dragging a selection on the stage: every item moves by --by col,row[,level] tiles (a pixel-placed item by the same screen offset), each once, in one undo step; rapid repeats coalesce. Placements move at rest: an animated item's keys are offsets from its placement, so its motion moves with it. Each item's group keeps its data-x/data-y and data-col/data-row in step, as scene.move does (--no-keep-group leaves them). Each item then stands at a new depth, so the scene is put in depth order: paths lists the moved items' paths after that, and reordered says which other paths moved. Layers themselves never change what is published.
edits docs · MCP tool scene_layer_move
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | scene name (or scenes/<name>) |
layer (positional) | string | a layer: its id, or its name when only one layer has it (scene items lists them) | |
--layers | list of string | layers: their ids (or names), comma-separated on the command line | |
--items | list of string | items: their paths from scene items (12, 5.2) or ids, comma-separated on the command line | |
--by | list of integer | required | tiles to move: col,row[,level] |
--keep-group | boolean | default true | keep each item's group attributes in step, as scene.move does |
pixellab scene layer move hero-island railway --by 1,0
pixellab scene layer move hero-island --layers railway,mine --items 12.0 --by 0,-1
scene.layer.new#
Make a layer (a named, nestable part of a scene) and move items and layers into it.
pixellab scene layer new <name> [--layer-name <text>] [--id <text>] [--items <text,…>] [--layers <text,…>] [--parent <text>]
A layer names part of a scene (the Mine, the Railway, the Train inside it) so it can be selected, moved and placed into as one. It never changes paint order or what is published: items keep their place in the scene, and their { group } wrappers stay as they are. Without --layer-name it is "Group n". Its parent is --parent ('' for the top level), else the deepest layer that holds every member, else the top level. A member already inside another member stays where it is. Its id is made from the name unless --id gives one. Returns the layer, how many items joined it and which layers.
edits docs · MCP tool scene_layer_new
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | scene name (or scenes/<name>) |
--layer-name | string | the layer's name, 1 to 64 characters (default "Group n") | |
--id | string | its id, lowercase kebab-case (default: made from the name) | |
--items | list of string | items: their paths from scene items (12, 5.2) or ids, comma-separated on the command line | |
--layers | list of string | layers: their ids (or names), comma-separated on the command line | |
--parent | string | the layer to make it in ('' for the top level); default: the members' common parent |
pixellab scene layer new hero-island --layer-name Railway --items 140.0,141.0,142.0
pixellab scene layer new hero-island --layer-name Train --items cart --parent railway
scene.layer.rename#
Rename a layer.
pixellab scene layer rename <name> <layer> <to>
Its id stays the same, so the scene file changes on one line.
edits docs · MCP tool scene_layer_rename
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | scene name (or scenes/<name>) |
layer (positional) | string | required | a layer: its id, or its name when only one layer has it (scene items lists them) |
to (positional) | string | required | the new name, 1 to 64 characters |
pixellab scene layer rename hero-island group-1 "Island trees"
scene.layer.ungroup#
Ungroup a layer: what it held moves up one level and stays where it is.
pixellab scene layer ungroup <name> <layer>
Takes the layer away; its layers and items now belong to its parent (or the top level). Nothing moves on the stage. Returns the parent, and the layers and items (paths) it held.
edits docs · MCP tool scene_layer_ungroup
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | scene name (or scenes/<name>) |
layer (positional) | string | required | a layer: its id, or its name when only one layer has it (scene items lists them) |
pixellab scene layer ungroup hero-island railway
scene.list#
List every scene.
pixellab scene list
read-only · MCP tool scene_list
scene.motion.path#
Key an item along a polyline on the grid, at a steady speed or between two times.
pixellab scene motion path <name> <item> <points…> [--start <n>] [--speed <n>] [--end <n>]
--points are moves (pixels from where the scene places the item), the first at --start. Each segment is split into runs that step 1 px straight, 1:1 or 2:1, each ending in a key; times follow the distance at --speed px per second, or spread from --start to --end. Keys inside the path lose their move (and go, if that leaves nothing); a key there with show or pose keeps its place on the path. Makes the track if the item (with an id) has none. Returns the keys written and the track's remaining off-grid warnings (such as the step from the path's end to the next key).
edits docs · MCP tool scene_motion_path
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | scene name (or scenes/<name>) |
item (positional) | string | required | the item: its path from scene items (12, 5.2) or its id; an item needs an id to have a track (scene.set --id) |
points (positional) | list of [integer, …] | required | x,y moves along the path; on the command line x,y pairs separated by spaces |
--start | integer | default 0 | when the path starts, ms into the loop (at least 0) |
--speed | number | pixels per second along the path | |
--end | integer | when the path ends, ms into the loop (instead of --speed) (at least 0) |
pixellab scene motion path hero-island truck-a 0,0 16,-3 48,-3 --start 2000 --speed 20
scene.motion.set#
Set the length of a scene's motion loop (making the motion if it has none).
pixellab scene motion set <name> --duration <n>
Refuses a length that would cut off keys.
edits docs · MCP tool scene_motion_set
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | scene name (or scenes/<name>) |
--duration | integer | required | the loop's length in ms (at least 100) |
scene.move#
Move an item by tiles and levels (--by) or by pixels (--nudge).
pixellab scene move <name> <item> [--keep-group] [--by <n,…>] [--nudge <n,…>] [--time <n>]
As the lab's arrow keys: --by col,row[,level] moves an item on a tile by whole tiles (a pixel-placed item moves by the same screen offset, and its level by the same levels, so it stands over the same tile a level higher); --nudge x,y moves it by pixels, and an item on a tile is then placed by pixel at the level it stood on. With --time past 0, an animated item (one with an id and a motion track) moves at that time instead: the key there, made if needed, takes the move. Rapid repeats coalesce into one undo step. A move at rest keeps the item's group in step: data-x/data-y that were its placement point or foot, and data-col/data-row that were its tile, follow it (--no-keep-group leaves them); the result has the group when they did. The scene is then put in depth order: the result has the item's path after that, and reordered says which other paths moved.
edits docs · MCP tool scene_move
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | scene name (or scenes/<name>) |
item (positional) | string | required | the item: its path from scene items (12, 5.2) or its id |
--keep-group | boolean | default true | keep the item's group in step: data-x/data-y that were its placement point or foot, and data-col/data-row that were its tile, follow it |
--by | list of integer | tiles to move: col,row[,level] | |
--nudge | list of integer | pixels to move: x,y | |
--time | integer | default 0 | ms into the scene's motion; past 0 an animated item's key moves (at least 0) |
pixellab scene move hero-island 0.0 --by 1,0
pixellab scene move hero-island jib --nudge 0,-1 --dry-run
scene.new#
Make a new, empty scene (the starter: a 128×128 view around tile 0,0,0).
pixellab scene new <name>
edits docs · MCP tool scene_new
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | the new scene's name (lowercase kebab-case) |
scene.place#
Place an object on a tile (or at a pixel point) in a scene.
pixellab scene place <name> <object> [--at <n,…>] [--px <n,…>] [--level <n>] [--on-top] [--class <text>] [--id <text>] [--group <text>] [--new-group <json>] [--foot] [--layer <text>]
Like the lab's Place tool, but the item goes where it stands: it is added to its list and the scene's items are put in depth order, so it draws behind what is in front of it and over what is behind (scene.items shows the order). With neither --at nor --px, it goes on the tile at the middle of the view, where the lab's cursor starts (the middle of the scene's content when it has no viewBox). With no level given (--at col,row, or no position) it stands on top of whatever block covers that tile: on the highest one there, in whole levels, or on the ground (level 0); a block of height h rests with its bottom on that top. --at col,row,level is used as given unless --on-top is set, which also works for --px. --px takes --level, how high the item stands (default 0), which is only for depth: its pixel point is where its foot is drawn. --group puts it inside an existing group (the group's path, or an item inside it); --new-group wraps it in a new group with those attributes (class and data-*, like {"class": "enter", "data-enter": "object"}), and --foot sets that group's data-x and data-y to the item's foot. --layer puts it in a scene layer (its id or name; layers are apart from groups and never change paint order). Returns the new item's path after the sort, and its group's; reordered says which other paths the sort moved.
edits docs · MCP tool scene_place
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | scene name (or scenes/<name>) |
object (positional) | string | required | the object to place |
--at | list of integer | a tile: col,row[,level]; without a level the item goes on top of what stands there | |
--px | list of integer | a point in scene pixels: x,y | |
--level | integer | with --px: how high the item stands, for depth order (default 0) | |
--on-top | boolean | default false | put the item on top of the block on its tile, whatever level --at or --px give |
--class | string | a class list for the item | |
--id | string | an id for the item (lowercase kebab-case, unique in the scene) | |
--group | string | an existing group to place it in: its path, or an item (path or id) inside it | |
--new-group | object | attributes of a new group around the item (class, data-*), as JSON on the command line | |
--foot | boolean | default false | with --new-group: set its data-x and data-y to the item's foot |
--layer | string | a layer of the scene for the new item to join: its id, or its name (scene items lists them) |
pixellab scene place hero-island pine --at 14,6,1
pixellab scene place range-sprite boxes --px 40,12 --dry-run
pixellab scene place hero-island crane-jib --px 40,-12 --level 4
pixellab scene place hero-island crate --at 3,1,1 --new-group '{"class": "enter", "data-enter": "object"}' --foot
pixellab scene place hero-island pine --at 3,1,1 --layer railway
scene.remove#
Remove an item (and its motion track, if it has an id).
pixellab scene remove <name> <item>
edits docs · MCP tool scene_remove
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | scene name (or scenes/<name>) |
item (positional) | string | required | the item: its path from scene items (12, 5.2) or its id |
scene.render#
Render a scene to a transparent PNG, with item bounds.
pixellab scene render <name> [--scale <n>] [--time <n>] [--theme <light|dark>] [--play <text>] [--view <n,…>] [--show <text>] [--hide <text,…>] [--focus <text>] [--out <file.png>]
Returns { png (base64), scale, viewBox, frame, width, height, time, items: [{ path, object, at, id, bounds: [x, y, w, h] }] }. viewBox is the box the scene publishes at (its own, or the fit to what it paints; frame says which: "fixed" or "fit"), or the --view window when one is given. Bounds are in output pixels (scale included), for hit-testing. Of each run of data-class variant groups only the first shows, or the one holding --show. --hide leaves out the items in those layers (and the layers inside them) and the items at those paths, as hiding them in Pixel Lab does (a name that is a layer wins over a path); --focus adds focusPng, the same render with only that layer's items. Ambient loops are placed at --time; --play <action>@<ms> places the elements that have that action where it has them <ms> into it. On the command line, --out <file.png> writes the PNG.
read-only · MCP tool scene_render
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | scene name (or scenes/<name>) |
--scale | integer | default 1 | whole-pixel zoom of the PNG (1 to 16) |
--time | integer | ms into the animation: frame loops and motion tracks (default 0, the rest frame) (at least 0) | |
--theme | light | dark | default "light" | dark draws as the site's dark theme: the hero island's drop shadow (data-enter="shadow") takes the dark page color |
--play | string | an action and the ms into it, like hop@120: the elements that have it are placed where it has them then, instead of on their ambient loop | |
--view | list of integer | a window of the scene to render instead of its whole box: x,y,w,h in scene pixels (the result's viewBox is then the window) | |
--show | string | an item path whose variant group to show, like 12.0 | |
--hide | list of string | layers (ids or names) whose items are left out, or item paths like 12.0 to leave out single items | |
--focus | string | a layer: the result adds focusPng, the render of only its items (and those of the layers inside it) |
pixellab scene render hero-island --scale 2 --out /tmp/hero.png
pixellab scene render range-sprite --time 400 --json
pixellab scene render hero-island --theme dark --out /tmp/hero-dark.png
pixellab scene render meadow --play hop@120 --out /tmp/hop.png
scene.set#
Set an item's tile or pixel point, one coordinate, its level, depth override, class or id.
pixellab scene set <name> <item> [--keep-group] [--tile <n,…>] [--px <n,…>] [--col <n>] [--row <n>] [--level <n>] [--x <n>] [--y <n>] [--depth <front|back|auto>] [--class <text>] [--id <text>]
--tile col,row,level or --px x,y place the item there (changing how it's placed if need be; a pixel point keeps the level the item stands on); --col, --row, --level (an item on a tile) and --x, --y (a pixel-placed item) set one coordinate, like the lab's Selected fields. --level also sets how high a pixel-placed item stands, which only matters for depth order. --depth is the override: front draws the item over everything else in its list, back under everything, auto takes the override off so it draws by where it stands. It also takes a group's path, as the only field (a group's attributes are scene.group.set). --class sets the class list and --id the id; an empty value removes it. An id's motion track follows it when it's renamed and goes with it when it's removed. All in one undo step, after which the scene is in depth order (the result has the item's path after that; reordered says what moved). Moving it keeps its group's data-x/data-y and data-col/data-row in step, as scene.move does (--no-keep-group leaves them).
edits docs · MCP tool scene_set
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | scene name (or scenes/<name>) |
item (positional) | string | required | the item: its path from scene items (12, 5.2) or its id |
--keep-group | boolean | default true | keep the item's group in step: data-x/data-y that were its placement point or foot, and data-col/data-row that were its tile, follow it |
--tile | list of integer | a tile: col,row,level | |
--px | list of integer | a point in scene pixels: x,y | |
--col | integer | ||
--row | integer | ||
--level | integer | the level of an item on a tile, or how high a pixel-placed item stands | |
--x | integer | ||
--y | integer | ||
--depth | front | back | auto | front or back draws it over or under everything else in its list; auto clears the override | |
--class | string | a class list, or '' to remove it | |
--id | string | an id, or '' to remove it |
pixellab scene set hero-island 0.0 --tile 3,4,0
pixellab scene set hero-island 0.0 --class 'sway slow'
pixellab scene set hero-island 140 --depth back
scene.show#
Show a scene's settings, size, objects and motion.
pixellab scene show <name> [--doc]
read-only · MCP tool scene_show
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | scene name (or scenes/<name>) |
--doc | boolean | default false | also return the scene's source data as doc |
scene.snap#
Snap a pixel-placed item to the tile under its placement point, at the level it stands on.
pixellab scene snap <name> <item> [--keep-group]
A pixel item with no level snaps to a level-0 tile. Its group's data-x/data-y and data-col/data-row follow it, as scene.move keeps them (--no-keep-group leaves them).
edits docs · MCP tool scene_snap
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | scene name (or scenes/<name>) |
item (positional) | string | required | the item: its path from scene items (12, 5.2) or its id |
--keep-group | boolean | default true | keep the item's group in step: data-x/data-y that were its placement point or foot, and data-col/data-row that were its tile, follow it |
scene.sort#
Put a scene's items (or every scene's) in depth order, and say what moved.
pixellab scene sort [name] [--all]
Every edit to a scene already keeps its items in depth order (back to front, by where each stands); this is for a scene whose file isn't, such as one written by hand or made before depth order. A group sorts as one object and its members sort inside it. One undo step per scene. Returns { moved: [{ scene, path, to, object | group }], changedPixels, scenes: [{ scene, moved, changedPixels }] }: moved lists the items the sort took out of their place (paths before and after), and changedPixels counts the pixels that differ at time 0 between the scene drawn in its file's list order (as engines before depth order publish it) and in depth order, which is what the site sees when it adopts this engine. Give a scene or --all. For one scene, reordered maps every path the sort changed to its new path. With --dry-run on the command line nothing is written and the diff shows the new order.
edits docs · MCP tool scene_sort
| param | type | description | |
|---|---|---|---|
name (positional) | string | scene name (or scenes/<name>); leave out with --all | |
--all | boolean | default false | sort every scene |
pixellab scene sort hero-island --dry-run
pixellab scene sort --all
scene.track.add#
Add a motion track for an item (it needs an id): one key at 0, the rest frame.
pixellab scene track add <name> <item> [--duration <n>]
Makes the scene's motion (one loop of --duration ms) when it has none. Then scene.key.set or scene.motion.path keys it.
edits docs · MCP tool scene_track_add
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | scene name (or scenes/<name>) |
item (positional) | string | required | the item: its path from scene items (12, 5.2) or its id; an item needs an id to have a track (scene.set --id) |
--duration | integer | the loop's length in ms, when the scene has no motion yet (at least 100) |
pixellab scene track add hero-island truck-a
pixellab scene track add meadow bird --duration 4000
scene.track.remove#
Remove an item's motion track, with all its keys.
pixellab scene track remove <name> <item>
edits docs · MCP tool scene_track_remove
| param | type | description | |
|---|---|---|---|
name (positional) | string | required | scene name (or scenes/<name>) |
item (positional) | string | required | the item: its path from scene items (12, 5.2) or its id; an item needs an id to have a track (scene.set --id) |