Pixel Labdeveloper docs

Reference

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#

commandsummarykind
activity listThe last MCP tool calls agents made, oldest firstread
art buildPublish: validate every source, compile each scene with an output and write the SVGswrites files
art previewRender the review PNG of an object (standing on a tile) or a scene, on the card color; palette for the swatch sheetread
art sizesBytes each object and each color adds to a compiled scene, against the document's git HEADread
chat askAsk the owner a question with two to four answers, shown as buttons in Pixel Lab's Agent chatread
chat contextBuild the selection block an Agent chat message carries: what's selected, as the saved document has itread
checkValidate the sources with the engine, all of them or one docread
doc adoptMerge a client's unsaved copy of a doc into this one, as one stepedit, session
doc closeClose an open doc (refused with unsaved changes unless --force)session
doc flushSave every open doc with changes now (or one), as autosave would soonedit, session
doc mergeMerge the changes to a doc's file on disk into its working copy, keeping the unsaved editsedit, session
doc openOpen a scene or object as a working copy, with undo and redosession
doc redoRedo the last undone edit to an open docedit, session
doc reloadLoad an open doc from disk again, only if no unsaved work is lostedit, session
doc revertLoad an open doc from disk again, dropping its changes (as one undo step)edit, session
doc saveSave an open doc now and close its version: validate, serialize with the engine and write its sourceedit, session
doc statusWhether open docs have unsaved changes, and can undo or redosession
doc undoUndo the last edit to an open docedit, session
document copySave a copy of the open document at another path (Save As)writes files
document infoShow the document in use: where it came from, its publish folder and what it holdsread
document migrateMigrate a document from format 1 (.mjs sources) to format 2 (JSON sources), in place or as a copywrites files
document newMake a new, empty .pixellab document with the starter palettewrites files
history listList the document's versions, newest firstread
history markClose the open version, so the next edit starts a new onewrites files
history renderRender a scene or object as it was after a version, without writing anythingread
history restorePut the document (or one doc) back as it was after a versionwrites files
history showShow a version: its edits and a diff of each file it changedread
history undoTake back just what one version changed, keeping everything sincewrites files
library categorySet or clear the category of objects in a .pixellib, in placewrites files
library exportExport the document's objects to a new .pixellibwrites files
library importImport the objects of a .pixellib into the document, by copyingwrites files
library infoShow an object library: its objects, their categories and the colors they usedread
object actionAdd, change or delete an action of a sprite map layeredit
object ambientSet or clear a sprite map layer's ambient loopedit
object animateTurn a still layer into one of the site's frame loops, or switch a loop's kindedit
object categorySet or clear the category of one or more objectsedit
object eraseErase pixels of an object (make them transparent), on a layer and frameedit
object fillFlood-fill from a pixel with a palette color, on a layer and frameedit
object footprintSet how many tiles a sprite covers (--to w,d), or clear it (--clear)edit
object frame addAdd a blank frame after a frame of a layer's loopedit
object frame deleteDelete a frame of a loop (not the only one)edit
object frame duplicateDuplicate a frame of a loop, just after itedit
object frame moveMove a frame of a loop later (--by 1) or earlier (--by -1)edit
object layer addAdd a blank layer on top of an objectedit
object layer deleteDelete a layer (not the only one)edit
object layer duplicateDuplicate a layer, just above itedit
object layer moveMove a layer up (--by 1, raise) or down (--by -1, lower)edit
object listList every object with its kind, category, size, layers, frames and colorsread
object map setReplace a whole map of a sprite's layer (or frame of a loop) from rows, in one undo stepedit
object newMake a new object from a starter: a blank sprite, a block, or one that extends anotheredit
object paintPaint pixels of an object a palette color, on a layer and frameedit
object pickThe color at a pixel of an object, on a layer and frame (the Picker)read
object renderRender one object to a transparent PNGread
object resizePad or crop a sprite's maps to a width and height (at the right and bottom)edit
object showShow an object's kind, category, size, anchor, footprint, layers, frames, colors and extendsread
object stillTurn a frame loop back into a still layer, keeping one frameedit
palette exportExport the document's palette to a new .pixelpal filewrites files
palette getList the palette: every color name, hex and tierread
palette importAdd the colors of a .pixelpal file the document doesn't haveedit
scene actionAdd, change or delete a scene item's action: a one-shot move the page plays by nameedit
scene ambientSet or clear a scene item's ambient loop: a move that repeats on its ownedit
scene dropDrop an item onto whatever it stands over: down to the top of the block under it, or the groundedit
scene duplicateDuplicate an item one column over, where the copy stands in depth orderedit
scene frameShow a scene's published size, fix it (--box), fit it to its content (--fit) or set its marginedit
scene group setSet or remove a group's class and data attributes, or set its data-x and data-y to its first item's footedit
scene itemsList a scene's items in depth order (back to front), with stable pathsread
scene key deleteDelete a key of an item's track (not the one at 0: remove the track for that)edit
scene key moveMove a key of an item's track to another timeedit
scene key setSet 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 assignMove items and layers into a layer, or to the top leveledit
scene layer moveMove layers (every item in them, and in the layers inside them) and items by tiles, togetheredit
scene layer newMake a layer (a named, nestable part of a scene) and move items and layers into itedit
scene layer renameRename a layeredit
scene layer ungroupUngroup a layer: what it held moves up one level and stays where it isedit
scene listList every sceneread
scene motion pathKey an item along a polyline on the grid, at a steady speed or between two timesedit
scene motion setSet the length of a scene's motion loop (making the motion if it has none)edit
scene moveMove an item by tiles and levels (--by) or by pixels (--nudge)edit
scene newMake a new, empty scene (the starter: a 128×128 view around tile 0,0,0)edit
scene placePlace an object on a tile (or at a pixel point) in a sceneedit
scene removeRemove an item (and its motion track, if it has an id)edit
scene renderRender a scene to a transparent PNG, with item boundsread
scene setSet an item's tile or pixel point, one coordinate, its level, depth override, class or idedit
scene showShow a scene's settings, size, objects and motionread
scene snapSnap a pixel-placed item to the tile under its placement point, at the level it stands onedit
scene sortPut a scene's items (or every scene's) in depth order, and say what movededit
scene track addAdd a motion track for an item (it needs an id): one key at 0, the rest frameedit
scene track removeRemove an item's motion track, with all its keysedit

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
--jsonprint the command's result as JSON
--documentthe .pixellab document (else $PIXELLAB_DOC, ~/.config/pixellab/config.json, or the document in --site)
--sitea site checkout holding the document (else $PIXELLAB_SITE, the config file, ~/Projects/jlawcordova.github.io)
--outwrite the PNG of a render command to this file
--dry-runan edit command shows what it would write (a diff) and writes nothing
--forcean edit command writes despite validation problems or a file changed on disk
-h, --helphelp 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"], typed Nature/Trees or Nature › Trees). A name is 1 to 32 letters, digits, spaces, &, ' and -, and All and Others can't be the outermost (they are the Library's own tabs). object category sets or clears it, object list --category filters by it, and check reports a bad one. A category never changes how an object renders or what art build writes.
  • .pixellib, an object library. A folder: library.json (format, kind: "library", name, exportedFrom and colors, the palette colors its objects used at export, never read as a palette) and objects/*.json, the same files a document holds, categories included. It has no scenes and no palette. library export writes 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 import copies a library's objects in: a taken name gets the first free suffix (pine-2) and every extends follows; an object identical to the document's, apart from its category, isn't copied; a library character that 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-run shows the plan. An import never changes the palette, the existing objects or the scenes, and is one version in the history. library info reads a library, library category sets categories in one in place, and check --library <path> validates one.
  • .pixelpal, a palette file. One JSON file: format, kind: "palette", and the world and outfit tiers (and colorNotes) as in a document's palette.json. palette export writes the document's palette. palette import adds the colors the document lacks, by name, at the end of their tier; it never removes one. A name with another value needs --keep or --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; --only narrows 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:

methodresult
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] }] }. png is a base64 PNG with a transparent background at an integer scale; bounds are in output pixels (scale included), for hit-testing; path is the item's dotted path ("5.2"). An object render's item has path: null and the result has its anchor. A scene render's viewBox is the box the scene publishes at (its own, or the fit to what it paints: frame says "fixed" or "fit"), or the view window asked for; a scene without a viewBox has no edge, so the app asks for the window it shows. scene.show and scene.list give the resolved viewBox and frame too.
  • Edits: { doc: "scene:<name>" | "object:<name>", changed, summary, ... }; scene item edits add path (the item's path after the edit) and item; paint edits add pixels (how many changed).
  • DocStatus (in session.info and doc.status without a doc): { doc, dirty, canUndo, canRedo }. doc.changed adds undoLabel, 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) and problems; doc.status with a doc adds those and file, exists, stale (the file changed on disk since the doc was opened or last matched it), undo: [edit summaries], redo: [...] and edits (the edits since the doc last matched its file).
  • doc.save: { doc, file, wrote, hash, bytes, diff, dirty, canUndo, canRedo } (wrote is false when the file already held exactly this text).
  • doc.undo / doc.redo / doc.revert: { doc, changed, summary, dirty, canUndo, canRedo }; doc.reload adds reloaded, doc.merge adds merged.
  • history.list: { versions: [{ number, startedAt, endedAt, author, edits, files, open }], total }, newest first; history.render is scene.render's or object.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.codewhendata.code
-32700a line that isn't JSON (id is null)
-32600not a JSON-RPC 2.0 request
-32601no such methodnot_found
-32602bad paramsusage (with details: every problem)
-32000the command ran and failednot_found, invalid (validation problems in details), conflict, dirty, exists, ambiguous, is_group, at_front, paint_blocked, color_cap, load_error, …
-32603a buginternal

Notifications from the server (no id):

methodparamswhen
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.jsonl has one { number, startedAt, endedAt, author, edits, changes: [{ file, before, after }], open } per line, before and after being the sha256 of the file's text in blobs/<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, by history.mark or doc.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>.lock and <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 MCP tools/call: { time, tool, args, ok, summary } (tool is the MCP tool name).
  • retired-proposals/: proposals left from before edits applied, moved here once with a notice.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

paramtypedescription
--limitintegerdefault 50how 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

paramtypedescription
--dirstringthe 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

paramtypedescription
name (positional)stringrequiredan object or scene (or objects/<name>, scenes/<name>), or palette
--scaleintegerdefault 1whole-pixel zoom of the PNG (1 to 16)
--kindobject | scenewhich 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

paramtypedescription
name (positional)stringrequireda 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

paramtypedescription
question (positional)stringrequiredthe question, in one or two sentences
--optionslist of stringrequiredtwo 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

paramtypedescription
name (positional)stringscene name (or scenes/<name>)
--objectstringan object: the block names the whole object (object mode) instead of a scene
--layerslist of stringselected layer ids, comma-separated on the command line
--itemslist of stringselected item paths (12, 5.2), comma-separated on the command line
--pinnedlist of stringwhich 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

paramtypedescription
name (positional)stringan object or scene (or objects/<name>, scenes/<name>); every source when left out
--kindobject | scenewhich kind name is, when both exist
--librarystringcheck 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

paramtypedescription
doc (positional)stringrequiredthe doc: scene:<name>, object:<name>, or a name (scenes/<name>, objects/<name>)
--baseanythe text the copy started from
--sourcestringrequiredthe 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

paramtypedescription
doc (positional)stringrequiredthe doc: scene:<name>, object:<name>, or a name (scenes/<name>, objects/<name>)
--forcebooleandefault falsedrop 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

paramtypedescription
doc (positional)stringthe 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

paramtypedescription
doc (positional)stringrequiredthe 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

paramtypedescription
doc (positional)stringrequiredthe 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

paramtypedescription
doc (positional)stringrequiredthe 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

paramtypedescription
doc (positional)stringrequiredthe 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

paramtypedescription
doc (positional)stringrequiredthe 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

paramtypedescription
doc (positional)stringrequiredthe doc: scene:<name>, object:<name>, or a name (scenes/<name>, objects/<name>)
--forcebooleandefault falsesave 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

paramtypedescription
doc (positional)stringthe 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

paramtypedescription
doc (positional)stringrequiredthe 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

paramtypedescription
--tostringrequiredthe 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

paramtypedescription
--tostringwrite 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

paramtypedescription
path (positional)stringrequiredwhere to make it, e.g. ~/Art/meadow.pixellab
--namestringthe 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

paramtypedescription
--docstringa scene or object: its name, scene:<name> or object:<name>, or scenes/<name>.json
--limitintegerdefault 50how 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

paramtypedescription
--labelstringa 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

paramtypedescription
version (positional)integerrequiredthe version's number (history.list) (at least 0)
doc (positional)stringrequireda scene or object: its name, scene:<name> or object:<name>, or scenes/<name>.json
--scaleintegerdefault 1whole-pixel zoom of the PNG (1 to 16)
--timeintegerms into the motion and loops (a scene: default 0; an object: overrides frame) (at least 0)
--frameintegerdefault 0an object's frame (at least 0)
--layerslist of integeronly these layers of a sprite
--themelight | darkdefault "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

paramtypedescription
version (positional)integerrequiredthe version's number (history.list) (at least 0)
--docstringa 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

paramtypedescription
version (positional)integerrequiredthe version's number (history.list) (at least 0)
--docstringa 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

paramtypedescription
version (positional)integerrequiredthe version's number (history.list) (at least 0)
--forcebooleandefault falsesave 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

paramtypedescription
library (positional)stringrequiredthe .pixellib folder
names (positional)list of stringrequiredthe objects
--categoryanythe category as names, outermost first, or null to clear it
--tostringthe category as a path: Nature/Trees or Nature › Trees
--clearbooleandefault falseremove 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

paramtypedescription
to (positional)stringrequiredthe new library (.pixellib is added when missing)
--objectslist of stringonly these objects
--categorylist of stringonly the objects in this category and below it: names outermost first, or a path such as Nature/Trees
--replacebooleandefault falsereplace 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

paramtypedescription
from (positional)stringrequiredthe .pixellib folder
--accept-differencesbooleandefault falseimport although colors differ from the library's record, or its character differs
--convertobjectfor each color the document lacks, the document's color of the same tier to use instead: {"wood-5": "wood-3"}
--dry-runbooleandefault falsereturn 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

paramtypedescription
library (positional)stringrequiredthe .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

paramtypedescription
name (positional)stringrequiredobject name (or objects/<name>)
layer (positional)integerrequiredthe map layer, from 0 at the bottom (a frame loop or a block has none) (at least 0)
action (positional)stringrequiredthe action's name: lowercase words joined by hyphens, like hop
--durationintegerthe action's length in ms, up to 600000 (at least 1)
--keyslist of objectthe 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.
--deletebooleandefault falsedelete 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

paramtypedescription
name (positional)stringrequiredobject name (or objects/<name>)
layer (positional)integerrequiredthe map layer, from 0 at the bottom (a frame loop or a block has none) (at least 0)
--durationintegerthe loop's length in ms, up to 600000 (at least 1)
--offsetintegerstart the loop this many ms in (below the duration) (at least 0)
--keyslist of objectthe 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.
--clearbooleandefault falseremove 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

paramtypedescription
name (positional)stringrequiredobject name (or objects/<name>)
--layerintegerdefault 0the layer, from 0 at the bottom (at least 0)
--loopff | wf | hfdefault "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

paramtypedescription
names (positional)list of stringrequiredthe objects (names, or objects/<name>)
--categoryanythe category as names, outermost first, or null to clear it
--tostringthe category as a path: Nature/Trees or Nature › Trees
--clearbooleandefault falseremove 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

paramtypedescription
name (positional)stringrequiredobject name (or objects/<name>)
pixels (positional)list of [integer, …]requiredpixels as [x, y] pairs; on the command line x,y pairs separated by spaces (or the flag repeated)
--layerintegerdefault 0the layer, from 0 at the bottom (at least 0)
--frameintegerdefault 0the 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

paramtypedescription
name (positional)stringrequiredobject name (or objects/<name>)
--xintegerrequired
--yintegerrequired
--colorstringrequireda palette color name (pixellab palette get)
--layerintegerdefault 0the layer, from 0 at the bottom (at least 0)
--frameintegerdefault 0the 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

paramtypedescription
name (positional)stringrequiredobject name (or objects/<name>)
--tolist of integertiles wide and deep: w,d (1 to 16 each)
--clearbooleandefault falsetake 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

paramtypedescription
name (positional)stringrequiredobject name (or objects/<name>)
--layerintegerdefault 0the layer, from 0 at the bottom (at least 0)
--frameintegerdefault 0the 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

paramtypedescription
name (positional)stringrequiredobject name (or objects/<name>)
--layerintegerdefault 0the layer, from 0 at the bottom (at least 0)
--frameintegerdefault 0the 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

paramtypedescription
name (positional)stringrequiredobject name (or objects/<name>)
--layerintegerdefault 0the layer, from 0 at the bottom (at least 0)
--frameintegerdefault 0the 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

paramtypedescription
name (positional)stringrequiredobject name (or objects/<name>)
--layerintegerdefault 0the layer, from 0 at the bottom (at least 0)
--frameintegerdefault 0the frame of a frame loop (a block: of its surface loop) (at least 0)
--byintegerrequiredsteps 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

paramtypedescription
name (positional)stringrequiredobject 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

paramtypedescription
name (positional)stringrequiredobject name (or objects/<name>)
layer (positional)integerrequiredthe 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

paramtypedescription
name (positional)stringrequiredobject name (or objects/<name>)
layer (positional)integerdefault 0the 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

paramtypedescription
name (positional)stringrequiredobject name (or objects/<name>)
layer (positional)integerrequiredthe layer, from 0 at the bottom (at least 0)
--byintegerrequiredsteps 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

paramtypedescription
--kindsprite | blockonly objects of this kind
--categorylist of stringonly 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

paramtypedescription
name (positional)stringrequiredobject name (or objects/<name>)
rows (positional)list of stringrequiredthe map's rows, top to bottom; on the command line each row a positional or the flag repeated
--keysobjecta legend: character → palette color, as JSON on the command line
--layerintegerdefault 0the layer, from 0 at the bottom (at least 0)
--frameintegerdefault 0the frame of a frame loop (a block: of its surface loop) (at least 0)
--resizebooleandefault falseresize 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

paramtypedescription
name (positional)stringrequiredthe new object's name (lowercase kebab-case)
--kindsprite | blockdefault "sprite"
--extendsstringthe object it extends (a sprite, such as character)
--widthintegera new sprite's width (default 16) (1 to 64)
--heightintegera new sprite's height (default 16) (1 to 64)
--footprintlist of integerthe 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

paramtypedescription
name (positional)stringrequiredobject name (or objects/<name>)
pixels (positional)list of [integer, …]requiredpixels as [x, y] pairs; on the command line x,y pairs separated by spaces (or the flag repeated)
--colorstringrequireda palette color name (pixellab palette get)
--layerintegerdefault 0the layer, from 0 at the bottom (at least 0)
--frameintegerdefault 0the 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

paramtypedescription
name (positional)stringrequiredobject name (or objects/<name>)
--xintegerrequired
--yintegerrequired
--layerintegerdefault 0the layer, from 0 at the bottom (at least 0)
--frameintegerdefault 0the 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

paramtypedescription
name (positional)stringrequiredobject name (or objects/<name>)
--scaleintegerdefault 1whole-pixel zoom of the PNG (1 to 16)
--frameintegerdefault 0the frame every loop shows (loops shorter than this show nothing) (at least 0)
--timeintegerms into the loops; overrides frame (at least 0)
--playstringan 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
--layerslist of integeronly 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

paramtypedescription
name (positional)stringrequiredobject name (or objects/<name>)
--widthintegerthe new width (default: as it is) (1 to 64)
--heightintegerthe 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

paramtypedescription
name (positional)stringrequiredobject name (or objects/<name>)
--docbooleandefault falsealso 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

paramtypedescription
name (positional)stringrequiredobject name (or objects/<name>)
--layerintegerdefault 0the layer, from 0 at the bottom (at least 0)
--frameintegerdefault 0the 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

paramtypedescription
to (positional)stringrequiredthe new palette file (.pixelpal is added when missing)
--replacebooleandefault falsereplace 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

paramtypedescription
from (positional)stringrequiredthe .pixelpal file
--keeplist of stringcolors that differ: keep the document's value
--use-theirslist of stringcolors that differ: take the file's value (changes how every object using it looks)
--onlylist of stringadd only these new colors
--dry-runbooleandefault falsereturn 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

paramtypedescription
name (positional)stringrequiredscene name (or scenes/<name>)
item (positional)stringrequiredthe item: its path from scene items (12, 5.2) or its id
action (positional)stringrequiredthe action's name: lowercase words joined by hyphens, like hop
--durationintegerthe action's length in ms, up to 600000 (at least 1)
--keyslist of objectthe 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.
--deletebooleandefault falsedelete 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

paramtypedescription
name (positional)stringrequiredscene name (or scenes/<name>)
item (positional)stringrequiredthe item: its path from scene items (12, 5.2) or its id
--durationintegerthe loop's length in ms, up to 600000 (at least 1)
--offsetintegerstart the loop this many ms in (below the duration) (at least 0)
--keyslist of objectthe 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.
--clearbooleandefault falseremove 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

paramtypedescription
name (positional)stringrequiredscene name (or scenes/<name>)
item (positional)stringrequiredthe item: its path from scene items (12, 5.2) or its id
--keep-groupbooleandefault truekeep 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

paramtypedescription
name (positional)stringrequiredscene name (or scenes/<name>)
item (positional)stringrequiredthe 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

paramtypedescription
name (positional)stringrequiredscene name (or scenes/<name>)
--boxlist of integerfix the size: x,y,w,h in scene pixels (w and h above 0)
--fitbooleandefault falsetake the fixed size off, so the scene publishes at the box that fits what is in it
--marginintegerpixels 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

paramtypedescription
name (positional)stringrequiredscene name (or scenes/<name>)
group (positional)stringrequiredthe group: its path, or an item (path or id) inside it
--attrsobjectattribute values; '' removes one (JSON on the command line)
--footbooleandefault falseset 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

paramtypedescription
name (positional)stringrequiredscene 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

paramtypedescription
name (positional)stringrequiredscene name (or scenes/<name>)
item (positional)stringrequiredthe item: its path from scene items (12, 5.2) or its id; an item needs an id to have a track (scene.set --id)
--timeintegerrequiredms 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

paramtypedescription
name (positional)stringrequiredscene name (or scenes/<name>)
item (positional)stringrequiredthe item: its path from scene items (12, 5.2) or its id; an item needs an id to have a track (scene.set --id)
--timeintegerrequiredms into the loop (at least 0)
--tointegerrequiredits 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

paramtypedescription
name (positional)stringrequiredscene name (or scenes/<name>)
item (positional)stringrequiredthe item: its path from scene items (12, 5.2) or its id; an item needs an id to have a track (scene.set --id)
--timeintegerrequiredms into the loop (at least 0)
--movelist of integerpixels from where the scene places the item: x,y
--showbooleanwhether the item shows from this key on
--posestringthe object the item draws from this key on
--clearlist of stringvalues 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

paramtypedescription
name (positional)stringrequiredscene name (or scenes/<name>)
--tostringrequiredthe layer to move them into: its id or name, or '' for the top level
--itemslist of stringitems: their paths from scene items (12, 5.2) or ids, comma-separated on the command line
--layerslist of stringlayers: 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

paramtypedescription
name (positional)stringrequiredscene name (or scenes/<name>)
layer (positional)stringa layer: its id, or its name when only one layer has it (scene items lists them)
--layerslist of stringlayers: their ids (or names), comma-separated on the command line
--itemslist of stringitems: their paths from scene items (12, 5.2) or ids, comma-separated on the command line
--bylist of integerrequiredtiles to move: col,row[,level]
--keep-groupbooleandefault truekeep 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

paramtypedescription
name (positional)stringrequiredscene name (or scenes/<name>)
--layer-namestringthe layer's name, 1 to 64 characters (default "Group n")
--idstringits id, lowercase kebab-case (default: made from the name)
--itemslist of stringitems: their paths from scene items (12, 5.2) or ids, comma-separated on the command line
--layerslist of stringlayers: their ids (or names), comma-separated on the command line
--parentstringthe 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

paramtypedescription
name (positional)stringrequiredscene name (or scenes/<name>)
layer (positional)stringrequireda layer: its id, or its name when only one layer has it (scene items lists them)
to (positional)stringrequiredthe 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

paramtypedescription
name (positional)stringrequiredscene name (or scenes/<name>)
layer (positional)stringrequireda 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

paramtypedescription
name (positional)stringrequiredscene name (or scenes/<name>)
item (positional)stringrequiredthe 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, …]requiredx,y moves along the path; on the command line x,y pairs separated by spaces
--startintegerdefault 0when the path starts, ms into the loop (at least 0)
--speednumberpixels per second along the path
--endintegerwhen 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

paramtypedescription
name (positional)stringrequiredscene name (or scenes/<name>)
--durationintegerrequiredthe 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

paramtypedescription
name (positional)stringrequiredscene name (or scenes/<name>)
item (positional)stringrequiredthe item: its path from scene items (12, 5.2) or its id
--keep-groupbooleandefault truekeep 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
--bylist of integertiles to move: col,row[,level]
--nudgelist of integerpixels to move: x,y
--timeintegerdefault 0ms 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

paramtypedescription
name (positional)stringrequiredthe 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

paramtypedescription
name (positional)stringrequiredscene name (or scenes/<name>)
object (positional)stringrequiredthe object to place
--atlist of integera tile: col,row[,level]; without a level the item goes on top of what stands there
--pxlist of integera point in scene pixels: x,y
--levelintegerwith --px: how high the item stands, for depth order (default 0)
--on-topbooleandefault falseput the item on top of the block on its tile, whatever level --at or --px give
--classstringa class list for the item
--idstringan id for the item (lowercase kebab-case, unique in the scene)
--groupstringan existing group to place it in: its path, or an item (path or id) inside it
--new-groupobjectattributes of a new group around the item (class, data-*), as JSON on the command line
--footbooleandefault falsewith --new-group: set its data-x and data-y to the item's foot
--layerstringa 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

paramtypedescription
name (positional)stringrequiredscene name (or scenes/<name>)
item (positional)stringrequiredthe 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

paramtypedescription
name (positional)stringrequiredscene name (or scenes/<name>)
--scaleintegerdefault 1whole-pixel zoom of the PNG (1 to 16)
--timeintegerms into the animation: frame loops and motion tracks (default 0, the rest frame) (at least 0)
--themelight | darkdefault "light"dark draws as the site's dark theme: the hero island's drop shadow (data-enter="shadow") takes the dark page color
--playstringan 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
--viewlist of integera 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)
--showstringan item path whose variant group to show, like 12.0
--hidelist of stringlayers (ids or names) whose items are left out, or item paths like 12.0 to leave out single items
--focusstringa 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

paramtypedescription
name (positional)stringrequiredscene name (or scenes/<name>)
item (positional)stringrequiredthe item: its path from scene items (12, 5.2) or its id
--keep-groupbooleandefault truekeep 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
--tilelist of integera tile: col,row,level
--pxlist of integera point in scene pixels: x,y
--colinteger
--rowinteger
--levelintegerthe level of an item on a tile, or how high a pixel-placed item stands
--xinteger
--yinteger
--depthfront | back | autofront or back draws it over or under everything else in its list; auto clears the override
--classstringa class list, or '' to remove it
--idstringan 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

paramtypedescription
name (positional)stringrequiredscene name (or scenes/<name>)
--docbooleandefault falsealso 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

paramtypedescription
name (positional)stringrequiredscene name (or scenes/<name>)
item (positional)stringrequiredthe item: its path from scene items (12, 5.2) or its id
--keep-groupbooleandefault truekeep 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

paramtypedescription
name (positional)stringscene name (or scenes/<name>); leave out with --all
--allbooleandefault falsesort 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

paramtypedescription
name (positional)stringrequiredscene name (or scenes/<name>)
item (positional)stringrequiredthe item: its path from scene items (12, 5.2) or its id; an item needs an id to have a track (scene.set --id)
--durationintegerthe 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

paramtypedescription
name (positional)stringrequiredscene name (or scenes/<name>)
item (positional)stringrequiredthe item: its path from scene items (12, 5.2) or its id; an item needs an id to have a track (scene.set --id)