Skip to main content
Glama
with-pebbly

aseprite-ai-artist

by with-pebbly

Aseprite AI Artist

Your coding agent, painting in the Aseprite window you already have open.

Not a generated PNG. Not a file changed behind your back. The document on your screen, one pixel at a time — and every step is one Ctrl+Z.

npm CI licence

☔ 500×400 · 384 frames at 24 fps · 16 s · 24 layers — drawn by Claude Opus 5.5 through this server.

Install · How it draws · Skills · Gallery


✨ What it feels like

"Draw me a 32×32 knight in the PICO-8 palette, then give him a 4-frame idle."

You type that, and watch it happen in Aseprite: a palette, a silhouette, shading, layers, a breathing idle, a tagged cycle. The agent looks at its own work after every step, and at the end tells you what it compromised on.

You stay in charge. Don't like the helmet? Ctrl+Z, or just say so.

Works with Claude Code, omp, OpenCode, Codex CLI, Gemini CLI, Cursor, VS Code and Windsurf. Best results so far: Claude Opus 5.5.

Related MCP server: Aseprite MCP Tools

🚀 Install

You need Aseprite 1.3+ and Node 22.6+ (macOS, Linux or Windows).

1. The Aseprite extension — then quit and reopen Aseprite:

npx @pebbly/aseprite-ai-artist install-extension

2. Your agent:

/plugin marketplace add with-pebbly/aseprite-ai-artist
/plugin install aseprite@aseprite-ai-artist
omp plugin marketplace add with-pebbly/aseprite-ai-artist
omp plugin install aseprite@aseprite-ai-artist
npx @pebbly/aseprite-ai-artist install opencode
opencode plugin add github:with-pebbly/aseprite-ai-artist#main::path:opencode
npx @pebbly/aseprite-ai-artist install codex      # or gemini, cursor, --all

Your config is backed up first; --dry-run shows the change.

3. Check it — restart your agent, then:

npx @pebbly/aseprite-ai-artist doctor

All ticks means you're ready. Updating? Run step 1 again. Odd setups (Steam, custom folders, Windows paths) are in docs/INSTALL.md.

For CI or a batch of assets, the server can run Aseprite itself with no window:

npx @pebbly/aseprite-ai-artist serve --headless --aseprite /path/to/aseprite

Nothing is on disk until the agent saves, and it never touches a file your editor has open. It is never a fallback: if your window isn't attached, the agent asks instead of quietly switching.

🎨 How it draws

Ask /aseprite:studio for anything and it runs the whole job:

Stage

What happens

1

Connect

Checks Aseprite is attached and reads what's open. If it isn't, it stops — it never edits files behind your back.

2

Brief

Only for open-ended requests: size, palette, view, light — one message, one answer.

3

Concept

For anything new: writes the design down and gives you a prompt for an image model. Send back a concept sheet, or say "continue without".

4

Draw

Silhouette first, written as a text grid (below), then shading.

5

Animate

Splits limbs onto layers, plans key poses and timing, draws each frame, tags the cycles.

6

Review

A separate critic who didn't draw it judges the result cold — what does it look like to someone never told? Blockers get fixed one at a time; a fix is kept only if the critic, shown both versions blind, prefers it.

7

Export

Spritesheet + atlas, GIF or PNGs for your engine.

After every step that changed pixels, it looks at the result before moving on.

🔤 The grid loop

New in 0.5.0. The agent doesn't draw with circles and rectangles — it types the frame out, one character per pixel, and draws that text in one call. To fix something, it reads the canvas back in the same format and changes only the characters that are wrong.

It sees the whole shape while writing it, so silhouettes stay even, limbs keep their length between frames, and an edit never spills into its neighbours. Animation is the same trick: copy the last frame's rows, move the arm, draw.

Since 0.6.0 every character stands for one palette entry, the same on every read, so a big canvas can be read and edited in pieces without the letters changing meaning between them. A grid can be as large as the canvas.

Early signal, not a measurement: with the same model and prompts, the mage benchmark went 3 → 4/10 and the road 1 → 3/10; the tree stayed at 2 (compare them).

📚 The pixel-art knowledge base

The agent doesn't improvise a hand at 16 px or a horse's walk — it looks them up.

62 rules · ~400 pixel templates · 2006 palettes — browse them at pixeli.pebbly.space/knowledge 👀

🧍 Craft

🌍 World

0x Core — loop, palette, shading, outlines

5x Creatures — quadrupeds, birds, monsters

1x Technique — lines, anti-aliasing, dithering

6x Environments — skies, water, tiles, buildings

2x Colour — hue-shifted ramps, materials, light

7x Objects & 3D — perspective, isometric, props

3x Characters — anatomy, faces, hands, clothing

8x Effects & UI — fire, magic, particles, fonts

4x Animation — idle, walk, jumps, attacks

9x Style — console eras, tells of generated art

Every rule gives size budgets, a procedure, common mistakes and grid templates the agent pastes straight onto the canvas. Any MCP client reads them as rules:// resources from rules/.

🎨 Palettes: the console classics plus Lospec's 2000 most-downloaded, each credited — preset: "endesga-32", or just a word of the name.

👀 Review by fresh eyes

New in 0.8.0. The agent that drew a sprite is the worst judge of it — it knows what it meant, so it sees it. studio hands the result to pixel-critic, which first writes a cold read (what the picture shows to someone who was never told), then a scored critique. A draft ships at 7/10 with nothing blocking; below that the agent fixes the one thing named, snapshotting first. Each fix goes back to the critic as a blind A/B — old and new under neutral names — and stays only if it wins, so a round can't quietly make good art worse.

It costs time (roughly one more critique per round) and its effect on quality is not yet measured — the thirteen experiment runs behind it are in the gallery, next to the benchmark rows.

🧰 Skills

studio picks these for you. Call one directly when you know the step you want — as /aseprite:<name> in Claude Code and omp, or just ask elsewhere.

Skill

For

Try

🎬 studio

anything — it plans and runs the rest

/aseprite:studio a fox, 32×32, sleeping loop

📝 brief

a vague idea

/aseprite:brief a cosy tavern keeper

🖼️ concept

a design or storyboard before drawing

/aseprite:concept a fire mage, 4-frame walk

📄 new

a fresh document set up right

/aseprite:new 64×64, PICO-8

🎨 palette

colour: a retro look, ramps, cleanup

/aseprite:palette give this a Game Boy look

✏️ draw

making the thing, text included

/aseprite:draw a fox curled up asleep

🌗 shade

flat art that needs light

/aseprite:shade light from the upper left

🦴 rig

splitting a character for animation

/aseprite:rig split the knight

🏃 animate

walk, idle, attack cycles

/aseprite:animate 8-frame walk cycle

🧱 tileset

terrain and autotiles

/aseprite:tileset grass-to-dirt, 16px

🔍 review

an honest critique

/aseprite:review why does this look off?

🩹 fix

changing art without wrecking it

/aseprite:fix make him more menacing

📦 export

files for your engine

/aseprite:export spritesheet for Godot

🗂️ submit

sharing it in the gallery

/aseprite:submit

In Claude Code and omp, four specialists take stages off the main agent: palette-smith, rig-builder, animation-director and the read-only pixel-critic.

🏆 Which model?

Live from the benchmark: every model draws the same fixed prompts and is scored against written criteria. Your model is not on it? Send us a sprite!

Everything drawn with the plugin — with its prompts, models and .aseprite source — is at pixeli.pebbly.space. The benchmark puts every model and plugin version through the same three fixed prompts and scores them against written criteria, and the knowledge base shows the rulebook the agents draw by.

Made something? Ask your agent for /aseprite:submit — it packages the files and opens the pull request.

🔧 Under the hood

your agent  ──MCP──▶  server  ──▶  bridge  ──▶  Aseprite extension
  • 18 tools, grouped by noun (draw, look, layer, frame, export…) instead of ninety — every tool costs context on every turn. Reference.

  • A knowledge base in rules/ — 62 files from line craft to walk cycles, with pixel templates — served over MCP, so every client gets the same craft knowledge.

  • Safe by default. Every action is one undo step. With Aseprite detached, tools refuse instead of editing files on disk. Everything binds to 127.0.0.1. Details · security.

🛠️ Development

pnpm install && pnpm run build
pnpm test                 # TypeScript
pnpm run test:extension   # the Lua handlers, inside a real Aseprite
pnpm gallery:check        # gallery and benchmark data
pnpm web:dev              # the site, locally

Read AGENTS.md before changing anything — it lists the rules that aren't negotiable and the Lua gotchas that already cost someone a day.

📜 Licence

MIT — see LICENSE. Aseprite is a trademark of Igara Studio S.A.; this project isn't affiliated with them.

Available Tools

18 tools
celCelsA
Destructive

Manage cels — one layer's image on one frame. Ops: 'list', 'create', 'clear', 'delete', 'move', 'copy', 'link', 'unlink', 'set' (position/opacity), 'tween', 'oscillate'. 'move' is how you shift a whole limb between frames without redrawing it; 'link' shares one image across frames so a static part of a cycle stays in sync. 'tween' interpolates position or opacity between two frames with an easing curve; 'oscillate' adds a sinusoidal position offset over a frame range — both fill in any missing in-between cels from the start cel, in one transaction.

ParametersJSON Schema
NameRequiredDescriptionDefault
xNoCel origin, for 'set' and 'move'.
yNo
dxNoRelative move.
dyNo
opYes
toNoFor 'tween': end value — {x,y} for property 'position', 0-255 for 'opacity'.
frameNo1-based frame number. Omit to use the active frame.
layerNoLayer name. Omit to use the active layer.
phaseNoFor 'oscillate': 0-1 fraction of one period.
easingNoFor 'tween'.linear
framesNoFor 'link' across a range.
periodNoFor 'oscillate', in frames. Default: the whole fromFrame..toFrame span.
spriteNoWhich sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused.
opacityNo
toFrameNo
toLayerNo
propertyNoFor 'tween'.
fromFrameNoFor 'tween'/'oscillate': the start frame.
amplitudeXNoFor 'oscillate'.
amplitudeYNoFor 'oscillate'.

Output Schema

ParametersJSON Schema
NameRequiredDescription
celsNo
framesNoOnly present for 'tween' and 'oscillate': one entry per frame touched, ascending.
spriteYes
appliedNo

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare destructiveHint=true and readOnlyHint=false, so the safety profile is covered. The description adds genuinely useful behavior beyond that: tween/oscillate operate 'in one transaction' and 'fill in any missing in-between cels from the start cel,' disclosing atomicity and side effects. It stops short of stating reversibility or permission needs.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose followed by the op list, then targeted explanations of the less obvious operations. Dense but nearly every sentence carries information an agent needs to disambiguate ops. Slightly long, though not padded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 20-parameter, 11-operation tool with an output schema present, return values need not be explained. The description covers the op semantics and transaction behavior well, but leaves a few parameter interactions (sprite selection, toLayer/toFrame pairing) to the schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 70%, so the schema does much of the work, but the description adds real semantic value by mapping operations to intent (move vs link vs tween vs oscillate) and clarifying the opacity/position distinction for tween. It does not document every parameter (e.g. sprite, toLayer, frames) but complements the schema well.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource and defines the concept precisely: 'Manage cels — one layer's image on one frame.' It then enumerates all 11 supported operations, so an agent immediately knows the tool's exact scope. It is clearly distinguishable from siblings like layer or frame.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explains what several operations mean and implicitly when to prefer them: 'move' for shifting a limb without redrawing, 'link' for sharing an image across frames, 'tween'/'oscillate' for procedural in-betweens. It gives clear operational context but never explicitly routes the agent away from alternative sibling tools (layer, frame).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

drawDrawA

Apply a batch of drawing operations to one cel, as a single undoable action. Ops: pixels, line, polyline, rect, ellipse, fill, replace, dither, gradient, clear, blit, text, grid. Batch aggressively — a whole sprite in one call is normal and correct, and it means the user can undo your work with one Ctrl+Z. Set paletteLock (default true) to snap every colour to the sprite's palette by perceptual distance before anything is written, so you cannot silently widen a curated palette. Ops run in array order, so paint fills before outlines and outlines before highlights. 'text' is laid out here from a bitmap font and expanded to plain pixels before it reaches Aseprite — pass measureOnly: true with only 'text' ops to get each one's ink bounds without touching the sprite, e.g. to centre a label first. 'grid' paints a picture written as text — one character per pixel, a legend mapping characters to colours, the same shape look op="ascii" returns — so you can see the whole silhouette while you write it instead of composing it from shapes. It is the easiest way to draw a small sprite or a whole animation frame, and to edit one: read a region with look op="ascii" layer=… rulers=false, change the rows, and send them back as a grid at the region's x/y.

ParametersJSON Schema
NameRequiredDescriptionDefault
opsYesApplied in order, in one transaction.
frameNo1-based frame number. Omit to use the active frame.
labelNoName shown in Aseprite's undo history. Describe the intent, e.g. 'shade helmet'.
layerNoLayer name. Omit to use the active layer.
spriteNoWhich sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused.
createCelNoCreate the cel if the target layer/frame has none.
measureOnlyNoEvery op must be 'text'. Returns each one's ink bounds without calling Aseprite at all — use to size a panel or centre a label before actually drawing it.
paletteLockNoSnap every colour to the nearest palette entry (CIELAB ΔE). Set false only when the user asked to introduce new colours.
selectionOnlyNoClip every op to the current selection.

Output Schema

ParametersJSON Schema
NameRequiredDescription
frameNo
layerNo
boundsNoBounding box actually touched.
spriteNo
opsAppliedNo
textBoundsNoOnly present when `measureOnly` was set.
measureOnlyNo
colorsSnappedNoColours palette-lock moved, and how far. A large ΔE means the palette lacks that colour.
pixelsChangedNo

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Adds substantial context beyond the annotations: ops execute in array order, paletteLock snaps to palette by CIELAB ΔE to avoid silently widening a curated palette, text is laid out and expanded to plain pixels before reaching Aseprite, and measureOnly computes ink bounds 'without touching the sprite'. The 'single undoable action' framing is consistent with destructiveHint=false rather than contradicting it.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core purpose and batching rule, then moves to paletteLock, ordering, text/measureOnly, and grid. Dense but each sentence carries distinct guidance; the grid sentences are long and could be tightened, keeping it short of a 5.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex 9-param, 13-op tool with full schema coverage and an output schema, the description covers the non-obvious behaviors an agent must know: ordering, palette snapping, measure-only measurement, and the grid round-trip. Minor gap is no explicit mention of how blit/replace/gradient interact with selection or palette locking.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds real meaning beyond the schema: paletteLock's perceptual-snap purpose and its 'set false only when the user asked to introduce new colours', measureOnly's requirement that every op be text and its no-side-effect return, and the ordering guarantee for `ops`. Remaining op-specific params (blit, dither, gradient) are left to the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Apply a batch of drawing operations to one cel, as a single undoable action') and enumerates the op vocabulary. It also positions itself against the read-side sibling by referencing `look op="ascii"`, so an agent can distinguish it from transform/select without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives strong usage direction: 'Batch aggressively', 'a whole sprite in one call is normal and correct', and the read-modify-write workflow via `look op="ascii"` then grid. It names the read counterpart but stops short of explicit when-not-to-use guidance against mutating siblings like transform or recolor.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

exportExportA
Destructive

Write game-ready files. Ops: 'png' (single frame), 'gif' (animation), 'spritesheet' (packed sheet plus a JSON atlas with per-frame and per-tag data), 'frames' (numbered PNGs), 'aseprite' (save a copy of the source). Spritesheet is what an engine actually consumes: pass sheetType and byTag to control layout. Exporting does not save the working document — use sprite_manage op 'save' for that.

ParametersJSON Schema
NameRequiredDescriptionDefault
opYes
pathYesOutput file path. For 'frames', a pattern containing {frame}, e.g. 'walk_{frame}.png' — Aseprite expands it into one file per frame, numbered from 0.
tagsNoExport only the frames these tags cover. gif, frames, spritesheet; 'png' and 'aseprite' refuse it. An unknown tag is refused.
trimNoTrim transparent margins; the atlas keeps the original offsets.
byTagNoSplit the sheet by animation tag.
frameNo1-based frame number. Omit to use the active frame.
scaleNoInteger upscale, nearest-neighbour.
layersNoExport only these layers (names or group paths; a group brings its children). png, gif, frames, spritesheet; 'aseprite' refuses it.
spriteNoWhich sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused.
paddingNoPixels between frames; prevents bleed at non-integer zoom.
sheetTypeNoSpritesheet layout.horizontal
includeJsonNoWrite a sibling JSON atlas for 'spritesheet'.

Output Schema

ParametersJSON Schema
NameRequiredDescription
opYes
atlasNo
filesYes
frameNoWhich frame a single-frame export wrote. Defaults to the active frame, which is not always the one you drew on.
widthNo
heightNo
spriteYes
frameCountNo

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With destructiveHint=true already declared, the description adds real context: it discloses that export writes to disk but leaves the working document untouched, that the atlas preserves original offsets when trimmed, and that per-op option combinations are accepted or refused. It does not address overwrite behavior for an existing path, which is the one remaining destructive concern.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four dense sentences, front-loaded with the action, then op enumeration, then the spritesheet recommendation, then the save-tool caveat. No filler and no repetition of schema content.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The output schema covers return shapes, so covering them is unnecessary, and the description handles op semantics and the save distinction well. The only gap is that the destructive hint is left to the annotation without the description clarifying that an existing file at `path` may be overwritten.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 92%, so nearly every parameter is already documented in the schema (including trim offsets, frames pattern expansion, tags/layers per-op refusal). The description only repeats sheetType and byTag as the layout controls, adding no syntax beyond the schema. Baseline 3 is correct.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Write game-ready files') and then enumerates exactly what the five ops produce, including the artifacts (JSON atlas with per-frame and per-tag data, numbered PNGs, source copy). An agent can distinguish this from look/draw/sprite_manage without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly routes the agent: 'Exporting does not save the working document — use sprite_manage op save for that,' and names the condition that selects the default op ('Spritesheet is what an engine actually consumes'). The when-to-use and the alternative are both stated, not implied.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

frameFramesA
Destructive

Manage animation frames. Ops: 'list', 'add', 'duplicate', 'delete', 'set_duration', 'activate', 'reorder'. Durations are milliseconds per frame and carry most of the life in a cycle — hold a contact pose longer than a pass pose. Use count to add several at once and durations to set a whole cycle's timing in one call.

ParametersJSON Schema
NameRequiredDescriptionDefault
opYes
countNo
frameNo1-based frame number. Omit to use the active frame.
spriteNoWhich sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused.
toIndexNoFor 'reorder': the 1-based position `frame` ends up at. Its cels and duration move with it; tag ranges stay where they are.
linkCelsNoFor 'duplicate': share the cel image instead of copying it, so edits apply to both.
durationsNoPer-frame durations from frame 1, for 'set_duration'.
afterFrameNoInsert position. Default: at the end.
durationMsNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
framesNo
spriteYes
frameCountYes
activeFrameNo
totalDurationMsNo

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare destructiveHint=true and readOnlyHint=false, so the safety profile is covered. The description adds domain context about duration semantics but never says which specific ops are destructive/irreversible (delete) or what happens to existing frames on reorder — a meaningful gap for a multiplexed mutation tool. No contradiction with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose, then ops, then parameter hints — a sensible order with no repetition. The artistic aside ('hold a contact pose longer than a pass pose') is mildly extraneous but small, and the rest of the text is tight.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be explained, and annotations cover the safety profile. For a 7-op multiplexed tool with one required param, the description is close to complete; the residual gap is that it does not map parameters to ops or flag which ops are irreversible.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 67%, and the description compensates by clarifying that durations are 'milliseconds per frame' (not stated in the durations schema description) and that `count` batches adds and `durations` sets a whole cycle in one call. This adds real operational meaning beyond the schema for the two least self-explanatory parameters.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a clear resource ('animation frames') and enumerates the seven concrete operations, so an agent knows exactly what this tool covers. 'Manage' is somewhat generic as a verb, and no sibling (e.g. cel, tag, layer) is named for disambiguation, but the op list makes the scope unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Listing the ops implies when each is used, and the closing sentence says to use `count` for bulk adds and `durations` for whole-cycle timing. However, there is no explicit when-to-use versus sibling tools (cel/tag/layer), no prerequisites, and no guidance on which ops are destructive or when to prefer 'duplicate' with linkCels.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

layerLayersA
Destructive

Manage layers. Ops: 'list', 'create', 'rename', 'delete', 'reorder', 'set' (visibility/opacity/blend/lock), 'group', 'ungroup', 'merge', 'duplicate', 'activate'. Pass batch to run several in one undoable action — building a rig in one call is the normal use. 'duplicate' takes an optional toSprite to copy the layer's cels into another OPEN sprite instead of this one, by frame index; frames past the target's frame count are dropped and reported. A character that will be animated wants its parts on separate layers (head, torso, arm-far, arm-near, leg-far, leg-near) before any frames exist; splitting baked pixels apart later is far more work.

ParametersJSON Schema
NameRequiredDescriptionDefault
opNoSingle operation. Use `batch` instead for several.
nameNo
batchNoOperations applied in order, in one undoable action. Same shape as the single-op arguments.
indexNoTarget stack position for 'reorder'; 0 is bottom.
namesNoFor 'merge' and bulk 'group'.
parentNoGroup layer to nest under.
spriteNoWhich sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused.
newNameNo
opacityNo
visibleNo
editableNo
toSpriteNoFor 'duplicate': copy the layer into another OPEN sprite instead of this one.
blendModeNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
layersNo
spriteYes
appliedNo
duplicatedNoPresent when a 'duplicate' op in this call used `toSprite`.
activeLayerNo

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare destructiveHint=true and readOnlyHint=false, so the safety profile is covered. The description adds real behavioral detail beyond that: batch is a single undoable action, and duplicate-to-another-sprite drops and reports frames past the target's frame count.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the op list, then batch semantics, then the duplicate edge case, then a workflow tip. Efficient overall, though the final rigging-advice sentence is more ambient guidance than operational instruction.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Output schema exists so return values need no explanation, and annotations carry the destructive flag. However, with 13 parameters at 54% coverage, the description never says which parameters apply to which op (e.g. name vs newName, what 'set' can change), leaving ambiguity for a multi-mode tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 54%, so the schema documents some parameters while leaving others bare (name, newName, opacity, visible, editable have no descriptions). The description clarifies batch grouping, toSprite target semantics, and that 'set' covers visibility/opacity/blend/lock, but does not map the remaining params to their ops.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States the resource ('Manage layers') and enumerates all eleven operations, so an agent can see exactly what surface it covers. It clearly reads as the layer-level counterpart to siblings like cel and frame, though it never names a sibling to differentiate itself explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives concrete context: pass `batch` to run several ops in one undoable action and 'building a rig in one call is the normal use', plus the constraint that `duplicate`'s toSprite must be another OPEN sprite. No explicit when-not or named alternative tools, but the conditions that select the main modes are stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

lookLook at the spriteA
Read-only

See what is actually on the canvas. Ops: • 'preview' — a nearest-neighbour upscaled PNG of the active frame (~1024px long edge). Use for overall read: silhouette, colour, whether it looks like the thing. • 'ascii' — an exact text grid, one glyph per pixel with a colour legend and coordinate rulers. Use to verify precise pixel positions and values, to count cells, or on any client without vision. Reads up to the whole canvas; pass a region to crop. Glyphs are bound to palette indices, so every region and frame of a sprite agrees on what a glyph means; each read's legend lists only the colours it contains, so merge legends when rows from one piece go into another. Only colours outside the palette (listed in offPalette) get per-read glyphs. rulers=false prints the bare rows, which draw kind 'grid' takes back as-is: read a layer, edit the rows, send them at the same x/y. • 'filmstrip' — every frame composited into one image. The only reliable way to review an animation, since a vision model reads just the first frame of a GIF. • 'onion' — the target frame at full opacity over ghosted neighbouring frames, oldest-first. Use to check in-betweens and spacing while animating, without stepping through frames one at a time. Over an opaque background the target hides every ghost — pass layer to onion-skin just the part that moves. • 'diff' — a pixel-level text diff between two frames: '.' unchanged, '-' erased, glyph = the new colour. Use it to confirm exactly what an edit touched. • 'compare' — the reference layer (left, full opacity) beside the art without any reference layer (right), same frame, same scale. Use it when drawing from a concept or storyboard: name the few largest mismatches and fix only those. Draw, then look, then fix. Do not report a sprite finished without looking at it.

ParametersJSON Schema
NameRequiredDescriptionDefault
opNopreview
frameNo1-based frame number. Omit to use the active frame.
layerNoRead a single layer instead of the composited image. Honoured by preview, ascii, diff, filmstrip and onion; compare has its own `reference`.
scaleNoInteger upscale for image ops, 1-128 (clamped so the output stays under ~2048px). Omit it — the automatic choice targets a ~1024px long edge, which is what a vision model can actually read.
regionNoCrop. Omit to read the whole canvas.
rulersNoascii: false drops the coordinate rulers and row labels, leaving rows you can edit and pass straight to `draw` kind 'grid' with the reported origin.
spriteNoWhich sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused.
toFrameNoDiff: the later frame.
fromFrameNoDiff: the earlier frame.
referenceNoCompare: the reference layer. Default 'reference'.
framesAfterNoOnion: ghost frames after the target.
framesBeforeNoOnion: ghost frames before the target.
ghostOpacityNoOnion: opacity of the ghosted (non-target) frames.

Output Schema

ParametersJSON Schema
NameRequiredDescription
opYes
rowsNo
textNoThe grid, for 'ascii' and 'diff'.
scaleNo
widthYes
framesNoFilmstrip: frame count.
heightYes
legendNoglyph → #rrggbb. Palette colours keep the same glyph across every read of the sprite.
originNoascii: sprite position of the grid's top-left cell — the `x`/`y` for `draw` kind 'grid'.
spriteYes
columnsNo
gridRowsNoascii: the bare rows, '.' transparent — `draw` kind 'grid' `rows`, with `legend` and `origin`.
framesUsedNoOnion: 1-based frame numbers composited.
offPaletteNoascii/diff: glyphs whose colour is not in the palette. Their assignment can change between reads — do not reuse them across regions.
totalPixelsNo
changedBoundsNoDiff: tight bounding box of every changed pixel. Null when nothing changed.
changedPixelsNo
percentChangedNoDiff: changed / total * 100.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already establish readOnly/no-open-world, but the description layers on substantial behavior: glyphs are bound to palette indices so regions agree, each legend lists only contained colours, off-palette colours get per-read glyphs, rulers=false output is reusable by draw kind 'grid', and onion ghosts are occluded by opaque backgrounds. This is detail an agent could not infer from annotations or schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the intent sentence, then a scannable bullet per op with bolded op names. It is long, but nearly every clause carries operational information; a few ascii details (legend merging, glyph binding) run longer than strictly necessary.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 13-param, six-mode read tool with an output schema already present, the description covers mode selection, crop/scale behavior, cross-tool round-tripping (ascii -> draw grid), and the animation-review caveat. Nothing an agent needs to invoke it correctly is missing, and return formats are appropriately left to the output schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 92%, so the baseline is 3, but the description adds real meaning beyond it: what ascii/rulers output composes with, that scale defaults to a vision-readable ~1024px long edge, the crop semantics of region, and how layer/reference interact. It stops short of documenting fromFrame/toFrame and ghost opacity fully, leaving some to the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb+resource ('See what is actually on the canvas') and then enumerates six named ops, each described by what it renders, clearly separating this inspection tool from mutating siblings like draw and transform. An agent can tell exactly what it gets back per op without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Every op carries an explicit 'Use for…' clause ('total read: silhouette, colour', 'verify precise pixel positions', 'the only reliable way to review an animation', 'check in-betweens while animating', 'confirm exactly what an edit touched'). It also gives conditional caveats (opaque background hides ghosts, use vision-less clients with ascii) and a workflow rule: draw, then look, then fix.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

palettePaletteA

Read and shape the sprite's palette. Ops: • 'get' — current palette with per-colour usage counts. • 'set' — write specific indices, or replace the palette wholesale. • 'preset' — load a bundled palette by key: ~2000 of them, the classics (pico8, gameboy, cga, nes…) and the most-downloaded on Lospec (sweetie-16, endesga-32, resurrect-64, aap-64…). An unknown key or a name answers with the matching keys, so 'preset: "gameboy"' or 'preset: "endesga"' is a search. • 'load' — read a .gpl/.hex/.pal/.png palette file from disk. • 'ramp' — generate a hue-shifted ramp from a base colour and append it. Shadows rotate toward blue, highlights toward orange; a ramp that only changes brightness is the clearest tell of machine-made pixel art. • 'analyze' — report ramp structure, contrast, near-duplicate entries and colours used in the art that are not in the palette. • 'extract' — replace the palette with one quantized from the art itself (RGB sprites only). Use to derive a curated palette from a reference image imported at full colour. Decide the palette before drawing. Retro-fitting one onto finished art means repainting.

ParametersJSON Schema
NameRequiredDescriptionDefault
opYes
baseNoBase colour for op 'ramp'.
pathNoPalette file for op 'load'.
stepsNoRamp length.
colorsNoFor op 'set': the colours to write.
presetNoPreset key for op 'preset'.
spreadNoHow far the ramp reaches into shadow and light.
spriteNoWhich sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused.
replaceNoFor op 'set'/'preset': replace the whole palette instead of merging.
remapArtNoWhen replacing a palette, repaint existing pixels to the nearest new colour instead of leaving them off-palette.
maxColorsNoFor op 'extract': target palette size.
startIndexNoFor op 'set': where `colors` starts. Ignored when `replace` is true.

Output Schema

ParametersJSON Schema
NameRequiredDescription
opYes
pathNoPalette file read by op 'load'.
rampNo
sizeNo
totalNoOp 'preset' with no such key: presets bundled.
usageNo
colorsNo
spriteNo
matchesNoOp 'preset' with no such key: presets whose key, name, author or tags match it.
analysisNo

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With readOnlyHint=false the annotations already signal mutation; the description goes further by disclosing that 'extract' replaces the palette and is RGB-only, that 'ramp' appends rather than replaces, that presets search on unknown keys, and that remapping can repaint pixels. It does not state permission requirements or failure modes for load of bad files.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded summary sentence followed by a scannable bullet list keyed by op value, with zero filler. It is longer than most descriptions, but nearly every clause carries invocation-relevant information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 12-parameter mutation tool with an output schema (so return values need no explanation) and annotations covering the safety profile, the description covers every op, the destructive side effects, and the sequencing convention. Nothing needed to invoke it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 92%, so the baseline is 3, but the description adds real meaning the schema cannot: a preset key may be a fuzzy name, ramp shadows rotate toward blue and highlights toward orange, and analyze reports off-palette colours. This meaningfully extends the structured fields.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb+resource ('Read and shape the sprite's palette') and then enumerates all seven ops with concrete semantics for each. An agent can tell this apart from siblings like recolor or transform without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives per-op guidance ('Use to derive a curated palette from a reference image'), a workflow rule ('Decide the palette before drawing. Retro-fitting one onto finished art means repainting'), and operational advice ('a preset name is a search'). However it never explicitly contrasts this tool with the closest sibling, recolor, which also changes colour; that exclusion is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

preflightPreflightA
Read-only

Check that Aseprite is connected and report what this session can do. Call this FIRST in any pixel-art task and stop if ready is false — every editing tool writes into the user's open Aseprite window ('live') or into a batch Aseprite this server owns ('headless'), and there is no useful fallback when that Aseprite is not there. Pass mode to choose which one the session works with from now on — only because the user asked (e.g. --headless) or the request plainly needs no window (files for CI, a batch of assets). Never switch to get around a live session that is not ready: ask the user instead. Switching loses nothing; each side keeps its documents.

ParametersJSON Schema
NameRequiredDescriptionDefault
modeNoSwitch the session to this Aseprite before checking. Omit to keep the current one.

Output Schema

ParametersJSON Schema
NameRequiredDescription
modeYes'live' = the user's open window. 'headless' = a batch Aseprite with no window; nothing is on disk until you save or export.
readyYesTrue only when Aseprite is attached and accepting commands.
featuresYesOptional capabilities this extension build supports.
switchedYesTrue when this call changed the session's mode.
directiveYesWhat to do next, in one sentence.
activeSpriteNo
asepriteVersionNo
bridgeConnectedYes
pluginConnectedYes
extensionVersionNo

TDQS

A4.3/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description is genuinely rich about behavior — it explains that every editing tool writes into the live window or into a server-owned headless Aseprite, that there is no fallback, and that switching loses nothing because each side keeps its documents. However, it also states that passing mode changes what the session works with 'from now on', i.e. a persistent state mutation, which conflicts with the readOnlyHint=true annotation. The disclosure itself is strong, but it directly contradicts the structured safety hint, so this is flagged.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the action and the 'call FIRST' imperative, then the failure condition, then the mode guidance. It is longer than average but every sentence carries decision-relevant information; nothing is padding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists (ready/capabilities), so return values need not be described. Combined with the safety, prerequisite, and mode-selection context the description supplies, an agent has everything needed to call this correctly first in a workflow.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the enum is documented, so the baseline is 3. The description adds meaning beyond the schema by explaining the consequence of the parameter ('which one the session works with from now on'), the conditions that justify switching, and the risk of switching for the wrong reason.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Check that Aseprite is connected and report what this session can do') and immediately positions itself as the mandatory entry point ('Call this FIRST in any pixel-art task'). An agent can distinguish it from every sibling editing tool without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit when-to-use ('FIRST in any pixel-art task'), when-to-stop ('stop if ready is false'), when to pass mode ('only because the user asked... or the request plainly needs no window'), and an explicit anti-pattern ('Never switch to get around a live session that is not ready: ask the user instead'). This is about as complete as usage guidance gets.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

read_pixelsRead pixelsA
Read-only

Read a rectangular region as structured data: a list of distinct colours plus a row-major index grid. Use this when you need to compute over pixels (sample a palette from art, find a silhouette edge, copy a region) rather than just look at them. For eyeballing, 'look' is cheaper.

ParametersJSON Schema
NameRequiredDescriptionDefault
frameNo1-based frame number. Omit to use the active frame.
layerNoLayer name. Omit to use the active layer.
regionNoOmit to read the whole canvas.
spriteNoWhich sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused.
compositeNoRead the flattened image. False reads only the target layer's cel.

Output Schema

ParametersJSON Schema
NameRequiredDescription
xYes
yYes
gridYesRow-major indices into `colors`.
widthYes
colorsYesDistinct colours; index 0 is fully transparent.
heightYes
spriteYes
uniqueColorsYes

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, so the read-only behavior is known. The description adds value by describing the output structure ('a list of distinct colours plus a row-major index grid'), which goes beyond the annotation. It doesn't contradict annotations and provides useful context about the tool's non-mutating nature and return format.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise: two sentences that front-load the core function, then provide usage context and an alternative. Every word earns its place, with no redundancy or fluff.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has an output schema (as indicated by context signals), the description does not need to detail return values. It covers purpose, usage, and differentiation from a key sibling. With read-only annotations and a fully documented parameter schema, the description is complete for an agent to select and invoke correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 100% coverage with descriptions for all 5 parameters, including nested 'region' details. The tool description does not add any additional parameter-specific meaning beyond what the schema already provides. Per calibration, with high schema coverage, baseline is 3, and no extra param info is given, so this is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's function: 'Read a rectangular region as structured data: a list of distinct colours plus a row-major index grid.' It uses a specific verb (read) and resource (rectangular region), and explicitly differentiates from the sibling 'look' tool by contrasting computation vs. eyeballing. This allows an agent to distinguish it immediately.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit usage guidance: 'Use this when you need to compute over pixels (sample a palette from art, find a silhouette edge, copy a region) rather than just look at them. For eyeballing, 'look' is cheaper.' This clearly states when to use this tool and when to use an alternative, leaving no ambiguity.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

recolorRecolorA

Change colours in a region by intent, staying palette-legal. Ops: • 'shade' — darken or lighten with proper hue shifting (shadows cool, highlights warm). Use this instead of picking a darker hex by hand. • 'snap' — pull off-palette pixels onto the nearest palette colour by perceptual (CIELAB) distance. • 'replace' — swap one exact colour for another. • 'hue_shift' — rotate hue, e.g. to make a colour variant of a finished sprite. • 'desaturate' — drop toward grey, useful for a value check. Operates on a region's distinct colours in one pass, so a 64×64 recolour is one undo step, not four thousand.

ParametersJSON Schema
NameRequiredDescriptionDefault
opYes
toNoFor 'replace'.
fromNoFor 'replace'.
frameNo1-based frame number. Omit to use the active frame.
layerNoLayer name. Omit to use the active layer.
amountNoFor 'shade': negative darkens, positive lightens. One ramp step is roughly 0.15.
regionNoOmit to affect the whole cel.
spriteNoWhich sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused.
degreesNoFor 'hue_shift'.
strengthNoFor 'desaturate'.
selectionOnlyNo
clampToPaletteNoSnap the result back onto the palette. Turn off only when growing the palette deliberately.

Output Schema

ParametersJSON Schema
NameRequiredDescription
opYes
spriteYes
mappingYesExactly which colour became which, and how many pixels each move touched.
pixelsChangedYes

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description goes beyond annotations by explaining behavioral details: operations run in one pass so a 64×64 recolour is one undo step, and it emphasizes palette legality and the clampToPalette option. It also clarifies that shade does proper hue shifting (shadows cool, highlights warm). Annotations indicate it's not read-only, which aligns with the mutation described. No contradictions, and the description adds valuable context about how the tool behaves.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is structured with a clear opening statement followed by a bulleted list of ops, making it scannable. While it is fairly long, each sentence adds information about operations or parameters. The front-loaded purpose and op list help an agent quickly grasp the tool's scope. Some redundancy exists (e.g., palette-legal mentioned twice), but overall it's efficient.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity (12 parameters, nested region object), the description is remarkably complete: it explains each op's use case, relevant parameters, and notes on palette behavior and undo granularity. The output schema is present, so return values are covered elsewhere. It could mention edge cases like invalid colors or when clampToPalette might fail, but for an agent deciding to call the tool, it's sufficient.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is high (83%), but the description adds crucial meaning: it explains the amount parameter with 'negative darkens, positive lightens. One ramp step is roughly 0.15,' defines from/to for replace, and clarifies clampToPalette's role in palette growth. These enrich the raw schema and help an agent select correct parameter values without needing extra inference.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Change colours in a region by intent, staying palette-legal.' It then enumerates specific operations (shade, snap, replace, hue_shift, desaturate) with examples, making it obvious what the tool does and how it differs from manual color editing. It's specific and distinct from sibling tools like draw or transform.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit guidance for each operation, e.g., 'Use this instead of picking a darker hex by hand' for shade, and 'useful for a value check' for desaturate. However, it does not explicitly mention alternative tools or when NOT to use recolor in favor of another sibling, so it's not fully exhaustive but gives solid contextual usage.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

referenceReference imageA

Bring an external image into the sprite as a reference layer, so you can trace proportions or sample colours from it. Ops: 'import' (place an image on a locked, semi-transparent layer above the art), 'sample_palette' (read its dominant colours without importing), 'list', 'remove'. A concept sheet or storyboard arrives as one image: region crops one panel of it, and grid cuts it into panels and puts panel i on frame i of a single reference layer, so each animation frame is drawn over its own storyboard panel. Importing at a different size uses nearest-neighbour; a reference scaled down to 32×32 is a guide for proportion and pose, never a finished sprite.

ParametersJSON Schema
NameRequiredDescriptionDefault
xNo
yNo
opYes
fitNoHow to size the image (or each panel) against the canvas.contain
gridNoImport a storyboard: split the (cropped) source into equal panels and place panel i on frame `frame`+i-1 of one reference layer. The sprite must already have enough frames.
nameNoLayer name. Default: 'reference'.
pathNoImage file to read.
frameNoImport: the frame the image — or the first `grid` panel — lands on. Default: the active frame, or frame 1 with `grid`.
colorsNoFor 'sample_palette'.
regionNoUse only this rectangle of the SOURCE image, in its own pixels — one panel of a concept sheet. Applies to 'import' and 'sample_palette'.
spriteNoWhich sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused.
opacityNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
opYes
layerNo
widthNo
framesNoImport: the frames that received a reference cel.
heightNo
panelsNoImport: how many panels were placed.
spriteYes
paletteNoDominant colours with their share of the image.
referencesNo

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint=false and openWorldHint=false; the description adds real behavior: import lands on a locked, semi-transparent layer above the art; resizing uses nearest-neighbour; a 32×32 reference is a proportion guide, never a finished sprite; grid requires the sprite to already have enough frames. Missing specifics like whether 'remove' is reversible, but coverage is strong.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core purpose, then the op list, then the storyboard/grid mechanism, then the scaling caveat. Dense and mostly waste-free, though the panel-index explanation is slightly elaborate for the space it consumes.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need no explanation, and the description covers the four ops, region, and grid for a 12-param nested tool. Gaps remain around 'list'/'remove' semantics and the 'fit'/'opacity' parameters, but nothing critical to correct invocation is absent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 67% schema coverage and 12 params, the description meaningfully supplements the schema: 'region' crops one panel of the SOURCE image, 'grid' places panel i on frame i of one reference layer, and import's scaling behavior is explained. It does not unpack the 'fit' enum values (contain/cover/stretch) or 'colors'/'opacity' effects, which remains a gap.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States the specific resource (external image as reference layer) and enumerates the four ops ('import', 'sample_palette', 'list', 'remove') with a one-clause gloss of each. An agent can distinguish this from the sibling 'layer', 'palette', and 'look' tools without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives clear when-to-use context per op: 'sample_palette' reads colours 'without importing', 'region' handles a single panel of a concept sheet, 'grid' handles a storyboard. It never names an alternative tool (e.g. why not use 'palette' to sample colours), so it stops short of the 5 bar.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

selectSelectionA
Idempotent

Read or change the active selection. Ops: 'get', 'none', 'all', 'rect', 'ellipse', 'color' (select every pixel matching a colour), 'invert', 'grow', 'shrink'. A selection scopes draw, transform and recolor, which is usually cheaper and safer than masking by hand.

ParametersJSON Schema
NameRequiredDescriptionDefault
opYes
modeNoHow this selection combines with the existing one.replace
rectNo
colorNoFor op 'color'.
frameNo1-based frame number. Omit to use the active frame.
layerNoLayer name. Omit to use the active layer.
amountNoPixels, for grow/shrink.
spriteNoWhich sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused.
toleranceNoLargest per-channel difference (0–255) still counted as the clicked colour. RGB sprites only; indexed and grayscale fills stay exact.
contiguousNoFor op 'color'.

Output Schema

ParametersJSON Schema
NameRequiredDescription
emptyYes
boundsNo
spriteYes
pixelCountYes

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare readOnlyHint=false, idempotentHint=true and openWorldHint=false, so safety/idempotency are already covered. The description adds genuinely new behavioral context: a selection is state that scopes draw, transform and recolor, which explains the side effects of this tool on other tools. It doesn't say what happens to a prior selection under each op, but with annotations present this is solid added value.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three tight sentences: purpose first, op inventory second, rationale third. Zero filler; every clause carries information an agent needs.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema, annotations and a richly described 10-parameter schema, the description covers the conceptual surface well. Its only notable gap is that it never links ops to their required parameters (e.g., rect needs 'rect', color needs 'color'/tolerance/contiguous), which the agent must infer from the schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 80%, so most parameters (mode, frame, layer, amount, sprite, tolerance, contiguous) are already documented — baseline 3. The description goes beyond by explaining the meaning of each op value, including that 'color' selects every pixel matching a colour, which the enum alone does not convey.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource ('Read or change the active selection') and then enumerates all nine ops with brief semantics for the non-obvious ones ('color' = every pixel matching a colour). An agent can distinguish this from draw, transform, recolor and read_pixels without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides a clear rationale for using selections: 'A selection scopes draw, transform and recolor, which is usually cheaper and safer than masking by hand.' That tells the agent when a selection is the right mechanism, but it gives no explicit exclusions or routing between ops (e.g., get vs read_pixels) and no prerequisites.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sprite_infoSprite infoA
Read-only

Full structured state of a sprite: dimensions, colour mode, palette, every layer (with opacity, blend mode, visibility, group nesting), every frame with its duration, animation tags, slices and the current selection. Read this before editing — guessing at layer names or frame counts is the most common way an agent corrupts someone's file.

ParametersJSON Schema
NameRequiredDescriptionDefault
spriteNoWhich sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused.
includeSlicesNo
includePaletteNoInclude the full palette as hex.

Output Schema

ParametersJSON Schema
NameRequiredDescription
idYesStable id for this open document. Pass it back as '#<id>'.
nameYes
tagsYes
widthYes
framesYes
heightYes
layersYes
slicesNo
paletteNo
filenameNo
modifiedYesUnsaved changes present in this document.
colorModeYes
selectionNo
frameCountYes
activeFrameNo
activeLayerNo
transparentIndexNo

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true and openWorldHint=false, so the safety profile is covered structurally; the description adds the operational insight that this is a mandatory pre-edit read to avoid corrupting state. It does not add auth, rate-limit, or cost context, but for a local read-only tool that is a minor omission.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two tightly written sentences, front-loaded with the resource and its contents, with the rationale for use placed last as a motivation rather than filler. The field enumeration slightly overlaps the output schema but remains useful for matching this tool against siblings.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present, the description correctly avoids explaining return values and instead covers scope, contents, and the pre-edit workflow. It leaves `includeSlices` unexplained, but the only remaining gap is minor for a zero-required-parameter read tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 67%: `sprite` and `includePalette` carry their own schema descriptions, while `includeSlices` is undocumented. The prose only indirectly alludes to slices and palette ('slices and the current selection'), adding no format or default semantics beyond the schema. Baseline 3 is appropriate since the schema does most of the work.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource ('Full structured state of a sprite') and enumerates the exact contents: dimensions, colour mode, palette, layers with opacity/blend/visibility/nesting, frames with durations, tags, slices, and selection. This is far more specific than sibling inspection tools and lets an agent tell it apart from `look`, `validate`, or `layer` without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly prescribes when to use it: 'Read this before editing,' with a concrete failure mode ('guessing at layer names or frame counts is the most common way an agent corrupts someone's file'). It does not name an alternative inspection sibling (e.g. `look` or `validate`) or state when not to use it, so it stops short of full routing guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sprite_manageManage spritesA
Destructive

Open, create, focus, resize, save and close sprites in the running Aseprite session. Ops: 'list' (open documents), 'new', 'open', 'activate', 'save', 'save_as', 'close', 'resize_canvas', 'set_properties', 'slice_create', 'slice_update', 'slice_delete'. Canvas resize keeps existing pixels — pass an anchor to say where they land. Slices name a rectangular region of the canvas for an engine to read back — a 9-patch panel's center, or a hotspot's pivot.

ParametersJSON Schema
NameRequiredDescriptionDefault
opYes
nameNoSlice name. Required for 'slice_create'; identifies it for update/delete.
pathNoFile path for 'open' and 'save_as'.
colorNoFor slice ops: slice colour in the timeline, #rrggbb.
forceNoAllow 'close' to discard unsaved changes. Ask the user before setting this.
pivotNoFor slice ops.
widthNo
anchorNoWhere existing pixels sit after 'resize_canvas'.center
boundsNoSlice rectangle. Required for 'slice_create'.
centerNoFor slice ops: nine-slice centre region, relative to `bounds`.
heightNo
spriteNoWhich sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused.
colorModeNoFor 'new'. Indexed keeps a sprite honest about its palette.
pixelAspectNoe.g. '1:1' or '1:2'.

Output Schema

ParametersJSON Schema
NameRequiredDescription
idNoStable id of the affected document; pass it back as '#<id>'.
opYes
nameNoSlice name, for 'slice_delete'.
pathNo
sliceNoThe affected slice, for 'slice_create'/'slice_update'.
widthNo
heightNo
spriteNo
spritesNo

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare destructiveHint=true, idempotentHint=false and readOnlyHint=false, so the safety profile is carried by structured data. The description adds useful operational context beyond that ('Canvas resize keeps existing pixels', what slices are for), but it never says which ops are the destructive ones (close/save), nor that ops act on a session-bound focused sprite. Solid addition, not rich.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three dense sentences, front-loaded with the verb/resource scope before the op list and the two clarifying notes. Every sentence contributes (op inventory, resize behaviour, slice semantics); no filler, though the op list is long by necessity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 14-parameter, 12-op multiplexed tool with nested objects and an output schema, the description gives an adequate map: full op inventory plus targeted clarifications for the two least obvious families (resize anchor, slices). It stops short of per-op parameter pairing, but the schema's per-field op hints largely cover that.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is a high 79%, so the baseline is 3, and the description genuinely adds meaning beyond it: the anchor parameter's effect on where existing pixels land, and the purpose of slice fields (nine-slice 'center', hotspot 'pivot'). It clarifies semantics of ops-scoped parameters rather than just restating names.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb list and resource ('Open, create, focus, resize, save and close sprites in the running Aseprite session') and enumerates all 12 supported ops, so the agent knows the exact capability surface. It does not, however, name or differentiate itself from close siblings like sprite_info, transform, or look, leaving that routing to inference.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is only implied through the op enumeration and one conditional note ('Omit to use the sprite Aseprite has focused'). There is no explicit when-to-use-this-vs-alternative guidance, e.g. why resize_canvas here rather than transform, or when to prefer sprite_info over 'list'. A capable agent can infer intent from op names, but nothing routes it deliberately.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

tagAnimation tagsA
Destructive

Manage animation tags — the named frame ranges a game engine imports as 'idle', 'walk', 'attack'. Ops: 'list', 'create', 'update', 'delete'. Tag every cycle before export; an untagged spritesheet is a pile of frames the engine cannot address.

ParametersJSON Schema
NameRequiredDescriptionDefault
opYes
toNoLast frame, 1-based, inclusive.
fromNoFirst frame, 1-based, inclusive.
nameNo
colorNoTag colour in the timeline, #rrggbb.
spriteNoWhich sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused.
newNameNo
repeatsNoLoop count; 0 means forever.
directionNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
tagsYes
spriteYes

TDQS

A3.7/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The annotations already indicate destructiveHint=true and readOnlyHint=false, so the agent is aware this can modify state. The description adds that untagged spritesheets are unusable by the engine, but it does not disclose behavioral details such as immediate persistence or side effects of update/delete. This is adequate but not rich.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact, front-loaded with the core concept, and every sentence earns its place. The first sentence defines the resource and operations, and the second gives a practical workflow warning without padding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description covers what tags are and why they matter, and the schema provides parameter details. However, it does not clarify which parameters are required for each operation (e.g., create vs. delete vs. update), which is a notable gap for correct invocation. Output schema exists, so return-value documentation is not the missing piece.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already documents several parameters (from, to, color, sprite, repeats), while 'name' and 'newName' benefit only from the description's 'named frame ranges' context. With 56% schema coverage, the description adds some conceptual meaning but does not fully compensate for the undocumented parameters or op-specific requirements.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool manages animation tags (named frame ranges) and enumerates the supported operations: list, create, update, delete. It uses concrete examples like 'idle', 'walk', 'attack' to make the resource unambiguous. It does not explicitly differentiate from sibling tools, but the resource and examples make the purpose clear.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives a clear workflow cue: 'Tag every cycle before export', and explains why tagging matters. It does not explicitly state when not to use this tool or point to alternatives among the siblings, so it stops short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

tilesetTilesetsA

Work with Aseprite tilemap layers. Ops: • 'list' — tilesets in the sprite. • 'create_layer' — a new empty tilemap layer on a given grid. • 'get' — tile count and, with a path, the tiles as one packed PNG. • 'stamp' — place tiles by grid coordinate. x/y are grid cells, not pixels. • 'pack' — turn a hand-painted mockup layer into a tileset plus a tilemap that reconstructs it exactly. Deduplicates identical cells; tolerance also merges near-identical ones. The canvas must be a whole number of tiles, and the source layer is hidden rather than deleted so you can compare. • 'export' — writes the packed PNG plus the file the engine reads: 'tiled' (.tsj tileset and a .tmj map that uses it), 'godot' (Godot 4 .tres TileSet), or 'json' (tile grid plus the tilemap layout). Painting a level by hand and packing it produces better tilesets than authoring tiles in isolation, because you see the whole picture while drawing. layout: 'blob47' adds a Tiled wangset for autotiling, and assumes the tileset is authored in canonical blob47 order after the empty tile — it refuses rather than writing a wangset that would autotile wrongly. Requires an extension build advertising the 'tileset' feature — check preflight first.

ParametersJSON Schema
NameRequiredDescriptionDefault
opYes
nameNo
pathNoOutput path for 'export' and 'get'.
frameNo1-based frame number. Omit to use the active frame.
layerNoLayer name. Omit to use the active layer.
tilesNoFor 'stamp'. x/y are tile-grid cells, not pixels.
formatNotiled
layoutNoAutotile layout for 'export'.grid
spriteNoWhich sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused.
tileWidthNo
toleranceNoFor 'pack': maximum per-channel difference at which two cells count as the same tile. 0 means exact, which is also much faster — anything above 0 compares every cell against every tile found so far.
tileHeightNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
opYes
rowsNo
filesNo
layerNo
formatNo
layoutNo
spriteYes
columnsNo
skippedNoFor 'stamp': placements outside the grid or naming a tile that does not exist.
tilesetsNo
cellCountNoFor 'pack': grid cells examined.
tileCountNo
tileWidthNo
tileHeightNo
reusedExactNoFor 'pack': cells that matched an existing tile exactly.
reusedFuzzyNoFor 'pack': cells merged by `tolerance`. A high number here with a low tolerance means the mockup has near-duplicate tiles worth cleaning up.
sourceLayerNoFor 'pack': the mockup layer, now hidden.

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only set readOnlyHint=false and openWorldHint=false, leaving the description to carry behavioral detail. It discloses non-obvious behaviors: 'stamp' uses grid cells not pixels, 'pack' deduplicates and hides (not deletes) the source layer, 'blob47' refuses when ordering is wrong, and 'tolerance' has performance implications. This goes well beyond the schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but appropriately so for a multi-operation tool. It front-loads the purpose, then lists ops in a scannable bullet format, and adds a rationale paragraph at the end. Every sentence serves a purpose, with no filler. It could be slightly more compact, but the structure aids readability.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With 12 parameters and six operations, the description covers most critical details: output formats, grid semantics, preconditions, and error behavior. An output schema exists (per context), which likely covers return structures. Minor gaps include exact return values for 'list' and 'create_layer', but these are not critical for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 58%, so the description must compensate for undocumented parameters. It explains 'tolerance', 'layout', 'sprite', and 'tiles' in detail, and clarifies grid-vs-pixel semantics. While 'name', 'tileWidth', and 'tileHeight' lack description-level context, they are straightforward from the schema defaults and bounds. The description meaningfully enhances understanding of the complex parameters.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description begins with 'Work with Aseprite tilemap layers' and then enumerates six distinct operations (list, create_layer, get, stamp, pack, export). It clearly states what the tool does and each op has a specific verb and resource. It distinguishes itself from sibling tools like 'layer' and 'export' by focusing on tileset-specific functionality, so an agent can immediately understand its scope.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear guidance on when to use the 'pack' operation ('Painting a level by hand and packing it produces better tilesets than authoring tiles in isolation') and warns about prerequisites ('Requires an extension build advertising the 'tileset' feature — check preflight first'). It does not explicitly contrast with sibling tools like 'layer' or 'export', but the internal op routing is clear and context is sufficient.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

transformTransformA

Move, flip, rotate or scale pixels on ONE cel — the target layer's image on the target frame. Ops: 'translate', 'flip', 'rotate', 'scale', 'outline', 'crop_to_content'. To transform part of a cel, crop the region out with draw op 'blit' first; there is no selection-scoped transform. Rotation is only clean at 90° multiples — arbitrary angles destroy pixel art, so anything else needs allowLossy. Scaling is nearest-neighbour and integer-only for the same reason.

ParametersJSON Schema
NameRequiredDescriptionDefault
dxNo
dyNo
opYes
axisNoFor 'flip'.
sideNoFor 'outline'. 'outside' paints transparent pixels touching opaque ones, growing the cel by one pixel each way. 'inside' recolours opaque pixels that touch transparency instead, same size in and out.outside
angleNoDegrees, clockwise. For 'rotate'.
colorNoOutline colour, for 'outline'.
frameNo1-based frame number. Omit to use the active frame.
layerNoLayer name. Omit to use the active layer.
factorNoFor 'scale'.
spriteNoWhich sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused.
diagonalsNoFor 'outline'. Treat diagonal neighbours as touching too (8-neighbour instead of 4).
thicknessNo
allowLossyNoPermit a non-90° rotation or non-integer scale. Ask the user first.

Output Schema

ParametersJSON Schema
NameRequiredDescription
opYes
boundsNo
spriteYes
pixelsChangedYes

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare the mutation/read profile, and the description adds meaningful behavioral context on top: rotation is clean only at 90° multiples, arbitrary angles 'destroy pixel art' and require `allowLossy`, and scaling is nearest-neighbour and integer-only. This is genuinely useful operational disclosure beyond the annotation trio. It does not describe output or failure modes, keeping it from a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, front-loaded with the verb/resource/scope, then the op list, then the two constraints. Every clause carries information (no selection transform, lossy gating, integer scaling) with no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 14-parameter mutation tool with an output schema present, the description covers scope, ops, lossy caveats and the partial-transform workaround adequately. Minor gaps remain, e.g. how translation handles out-of-bounds pixels (clip vs wrap) and the default outline color, but nothing blocking correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 71%, so the schema documents most parameters (axis, side, angle, color, factor, sprite, diagonals, allowLossy). The description still adds value by defining op-level semantics and the integer/90° constraints that explain why `factor` is integer-only and why `allowLossy` exists, which the schema's max/min bounds alone do not convey.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb set and resource ('Move, flip, rotate or scale pixels on ONE cel'), scopes it precisely to 'the target layer's image on the target frame', and enumerates the six ops. It also distinguishes itself from siblings by naming `draw` and ruling out any selection-scoped transform, so an agent can tell it apart from `select`/`draw` without opening schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives a concrete when-not case and a workaround: partial transforms must go through `draw` op 'blit' because 'there is no selection-scoped transform'. It also gates lossy behavior behind `allowLossy` with an explicit 'Ask the user first'. No explicit statement of the ordinary when-to-use happy path, but the boundaries are clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

validateValidateA
Read-only

Lint a sprite against pixel-art rules and report concrete, located problems. Checks: off-palette colours, orphan/stray pixels, broken or doubled outlines, banding, semi-transparent (soft or resized) pixels, empty layers, untagged frames and uniform frame timing; expect adds per-layer frame ranges and layers that must not overlap. Symmetry and cross-frame volume drift are not checked — judge those with look op 'ascii' and 'filmstrip'. Run this before telling the user a sprite is finished. It answers 'is this actually done' with evidence rather than optimism.

ParametersJSON Schema
NameRequiredDescriptionDefault
checksNoOmit to run everything.
expectNoThe animation contract you already know. Runs independently of `checks`, whenever given: flags art outside a layer's expected frame ranges (or a missing cel inside one), and any frame where two named layers' opaque pixels overlap.
spriteNoWhich sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused.
strictNoTreat style warnings as failures. Use when the user asked for a specific discipline.

Output Schema

ParametersJSON Schema
NameRequiredDescription
scoreYes
passedYes
spriteYes
summaryYes
findingsYes

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint/openWorldHint, so the description carries the disclosure burden, and it does so well: it lists exactly what is inspected, what `expect` enforces, and explicitly states the tool is evidence-based rather than optimistic. It stops short of describing the report shape, but an output schema covers that.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core verb and check list, then the exclusion note, then the usage directive. Dense but every sentence carries information; only the closing 'evidence rather than optimism' is mild flourish.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 4-parameter read-only tool with full schema coverage and an output schema, the description supplies everything needed: what it checks, the `expect` semantics, what it deliberately omits, and when to run it. The main residual gap is the relationship to the sibling 'preflight'.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3, but the description adds real meaning to the `checks` enum by spelling out what each category (palette, strays, outline, banding, antialiasing, layers, animation) actually detects, plus the `expect` contract semantics. This goes beyond the enum strings alone.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource ('Lint a sprite against pixel-art rules') and enumerates the exact checks performed (off-palette colours, strays, broken outlines, banding, etc.). It also distinguishes itself from the 'look' op by naming what it does NOT check (symmetry, cross-frame volume drift), so an agent can separate it from siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit trigger ('Run this before telling the user a sprite is finished') and routes the uncovered concerns to a named alternative. However it does not differentiate from the sibling 'preflight', which likely overlaps with the 'export_readiness' check — that ambiguity is left unresolved.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 2 tool updatesv0.8.2
    • Changedpalette3 fields changed
      • removedOutput schema / properties / availablePresets
        Removed value: -{
        -  "items": {
        -    "additionalProperties": false,
        -    "properties": {
        -      "key": {
        -        "type": "string"
        -      },
        -      "name": {
        -        "type": "string"
        -      },
        -      "notes": {
        -        "type": "string"
        -      },
        -      "size": {
        -        "type": "integer"
        -      }
        -    },
        -    "required": [
        -      "key",
        -      "name",
        -      "size",
        -      "notes"
        -    ],
        -    "type": "object"
        -  },
        -  "type": "array"
        -}
      • addedOutput schema / properties / matches
        Added value: +{
        +  "description": "Op 'preset' with no such key: presets whose key, name, author or tags match it.",
        +  "items": {
        +    "additionalProperties": false,
        +    "properties": {
        +      "author": {
        +        "type": "string"
        +      },
        +      "key": {
        +        "type": "string"
        +      },
        +      "name": {
        +        "type": "string"
        +      },
        +      "notes": {
        +        "type": "string"
        +      },
        +      "size": {
        +        "type": "integer"
        +      }
        +    },
        +    "required": [
        +      "key",
        +      "name",
        +      "author",
        +      "size"
        +    ],
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / total
        Added value: +{
        +  "description": "Op 'preset' with no such key: presets bundled.",
        +  "type": "integer"
        +}
    • Changedsprite_info2 fields changed
      • addedOutput schema / properties / modified
        Added value: +{
        +  "description": "Unsaved changes present in this document.",
        +  "type": "boolean"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "id",
        -  "name",
        -  "width",
        -  "height",
        -  "colorMode",
        -  "frameCount",
        -  "layers",
        -  "frames",
        -  "tags"
        -]New value: +[
        +  "id",
        +  "name",
        +  "width",
        +  "height",
        +  "colorMode",
        +  "frameCount",
        +  "modified",
        +  "layers",
        +  "frames",
        +  "tags"
        +]
  2. 2 tool updatesv0.7.0
    • Changeddraw1 field changed
      • changedInput schema / properties / ops / items / anyOf
        Previous value: -[
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "color": {
        -        "description": "Colour as #rrggbb or #rrggbbaa.",
        -        "pattern": "^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$",
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "pixels",
        -        "type": "string"
        -      },
        -      "points": {
        -        "items": {
        -          "additionalProperties": false,
        -          "properties": {
        -            "color": {
        -              "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        -              "description": "Per-pixel colour. Falls back to the op's `color`."
        -            },
        -            "x": {
        -              "type": "integer"
        -            },
        -            "y": {
        -              "type": "integer"
        -            }
        -          },
        -          "required": [
        -            "x",
        -            "y"
        -          ],
        -          "type": "object"
        -        },
        -        "maxItems": 20000,
        -        "minItems": 1,
        -        "type": "array"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "points"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "color": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      },
        -      "from": {
        -        "additionalProperties": false,
        -        "properties": {
        -          "x": {
        -            "type": "integer"
        -          },
        -          "y": {
        -            "type": "integer"
        -          }
        -        },
        -        "required": [
        -          "x",
        -          "y"
        -        ],
        -        "type": "object"
        -      },
        -      "kind": {
        -        "const": "line",
        -        "type": "string"
        -      },
        -      "thickness": {
        -        "default": 1,
        -        "exclusiveMinimum": 0,
        -        "maximum": 64,
        -        "type": "integer"
        -      },
        -      "to": {
        -        "additionalProperties": false,
        -        "properties": {
        -          "x": {
        -            "type": "integer"
        -          },
        -          "y": {
        -            "type": "integer"
        -          }
        -        },
        -        "required": [
        -          "x",
        -          "y"
        -        ],
        -        "type": "object"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "color",
        -      "from",
        -      "to"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "closed": {
        -        "default": false,
        -        "type": "boolean"
        -      },
        -      "color": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      },
        -      "fill": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        -        "description": "Fill colour; only meaningful when closed."
        -      },
        -      "kind": {
        -        "const": "polyline",
        -        "type": "string"
        -      },
        -      "points": {
        -        "items": {
        -          "additionalProperties": false,
        -          "properties": {
        -            "x": {
        -              "type": "integer"
        -            },
        -            "y": {
        -              "type": "integer"
        -            }
        -          },
        -          "required": [
        -            "x",
        -            "y"
        -          ],
        -          "type": "object"
        -        },
        -        "minItems": 2,
        -        "type": "array"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "color",
        -      "points"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "color": {
        -        "description": "Outline colour.",
        -        "pattern": "^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$",
        -        "type": "string"
        -      },
        -      "fill": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        -        "description": "Omit for an outline only."
        -      },
        -      "kind": {
        -        "const": "rect",
        -        "type": "string"
        -      },
        -      "rect": {
        -        "additionalProperties": false,
        -        "properties": {
        -          "height": {
        -            "exclusiveMinimum": 0,
        -            "type": "integer"
        -          },
        -          "width": {
        -            "exclusiveMinimum": 0,
        -            "type": "integer"
        -          },
        -          "x": {
        -            "type": "integer"
        -          },
        -          "y": {
        -            "type": "integer"
        -          }
        -        },
        -        "required": [
        -          "x",
        -          "y",
        -          "width",
        -          "height"
        -        ],
        -        "type": "object"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "color",
        -      "rect"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "color": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      },
        -      "fill": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        -        "description": "Colour as #rrggbb or #rrggbbaa."
        -      },
        -      "kind": {
        -        "const": "ellipse",
        -        "type": "string"
        -      },
        -      "rect": {
        -        "additionalProperties": false,
        -        "description": "Bounding box of the ellipse.",
        -        "properties": {
        -          "height": {
        -            "$ref": "#/properties/ops/items/anyOf/3/properties/rect/properties/height"
        -          },
        -          "width": {
        -            "$ref": "#/properties/ops/items/anyOf/3/properties/rect/properties/width"
        -          },
        -          "x": {
        -            "$ref": "#/properties/ops/items/anyOf/3/properties/rect/properties/x"
        -          },
        -          "y": {
        -            "$ref": "#/properties/ops/items/anyOf/3/properties/rect/properties/y"
        -          }
        -        },
        -        "required": [
        -          "x",
        -          "y",
        -          "width",
        -          "height"
        -        ],
        -        "type": "object"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "color",
        -      "rect"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "at": {
        -        "additionalProperties": false,
        -        "properties": {
        -          "x": {
        -            "type": "integer"
        -          },
        -          "y": {
        -            "type": "integer"
        -          }
        -        },
        -        "required": [
        -          "x",
        -          "y"
        -        ],
        -        "type": "object"
        -      },
        -      "color": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      },
        -      "contiguous": {
        -        "default": true,
        -        "type": "boolean"
        -      },
        -      "kind": {
        -        "const": "fill",
        -        "type": "string"
        -      },
        -      "tolerance": {
        -        "default": 0,
        -        "description": "Largest per-channel difference (0–255) still counted as the clicked colour. RGB sprites only; indexed and grayscale fills stay exact.",
        -        "maximum": 255,
        -        "minimum": 0,
        -        "type": "integer"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "color",
        -      "at"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "from": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      },
        -      "kind": {
        -        "const": "replace",
        -        "type": "string"
        -      },
        -      "region": {
        -        "$ref": "#/properties/ops/items/anyOf/3/properties/rect"
        -      },
        -      "to": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "from",
        -      "to"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "colorA": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      },
        -      "colorB": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      },
        -      "kind": {
        -        "const": "dither",
        -        "type": "string"
        -      },
        -      "pattern": {
        -        "default": "bayer4",
        -        "enum": [
        -          "checker",
        -          "bayer2",
        -          "bayer4",
        -          "bayer8",
        -          "noise"
        -        ],
        -        "type": "string"
        -      },
        -      "ratio": {
        -        "default": 0.5,
        -        "description": "0 = all colorA, 1 = all colorB.",
        -        "maximum": 1,
        -        "minimum": 0,
        -        "type": "number"
        -      },
        -      "rect": {
        -        "$ref": "#/properties/ops/items/anyOf/3/properties/rect"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "rect",
        -      "colorA",
        -      "colorB"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "direction": {
        -        "default": "vertical",
        -        "enum": [
        -          "vertical",
        -          "horizontal",
        -          "diagonal",
        -          "radial"
        -        ],
        -        "type": "string"
        -      },
        -      "dither": {
        -        "default": false,
        -        "description": "Dither the band boundaries.",
        -        "type": "boolean"
        -      },
        -      "from": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      },
        -      "kind": {
        -        "const": "gradient",
        -        "type": "string"
        -      },
        -      "rect": {
        -        "$ref": "#/properties/ops/items/anyOf/3/properties/rect"
        -      },
        -      "steps": {
        -        "default": 4,
        -        "description": "Banded, not smooth — a smooth gradient is not pixel art.",
        -        "maximum": 64,
        -        "minimum": 2,
        -        "type": "integer"
        -      },
        -      "to": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "rect",
        -      "from",
        -      "to"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "kind": {
        -        "const": "clear",
        -        "type": "string"
        -      },
        -      "region": {
        -        "$ref": "#/properties/ops/items/anyOf/3/properties/rect",
        -        "description": "Omit to clear the whole cel."
        -      }
        -    },
        -    "required": [
        -      "kind"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "flipHorizontal": {
        -        "default": false,
        -        "type": "boolean"
        -      },
        -      "flipVertical": {
        -        "default": false,
        -        "type": "boolean"
        -      },
        -      "from": {
        -        "$ref": "#/properties/ops/items/anyOf/3/properties/rect"
        -      },
        -      "fromFrame": {
        -        "exclusiveMinimum": 0,
        -        "type": "integer"
        -      },
        -      "fromLayer": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "blit",
        -        "type": "string"
        -      },
        -      "skipTransparent": {
        -        "default": true,
        -        "type": "boolean"
        -      },
        -      "to": {
        -        "additionalProperties": false,
        -        "properties": {
        -          "x": {
        -            "type": "integer"
        -          },
        -          "y": {
        -            "type": "integer"
        -          }
        -        },
        -        "required": [
        -          "x",
        -          "y"
        -        ],
        -        "type": "object"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "from",
        -      "to"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "anchor": {
        -        "default": "top_left",
        -        "description": "Resolved against the glyph ink box, not the full advance box — outline and shadow never shift it.",
        -        "enum": [
        -          "top_left",
        -          "top",
        -          "top_right",
        -          "left",
        -          "center",
        -          "right",
        -          "bottom_left",
        -          "bottom",
        -          "bottom_right",
        -          "baseline_left",
        -          "baseline",
        -          "baseline_right"
        -        ],
        -        "type": "string"
        -      },
        -      "bold": {
        -        "default": 0,
        -        "description": "Grid-cell passes grown rightward; 0 is regular weight.",
        -        "maximum": 3,
        -        "minimum": 0,
        -        "type": "integer"
        -      },
        -      "color": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      },
        -      "font": {
        -        "default": "pixel5x7",
        -        "description": "Name in knowledge/fonts, without the .json extension.",
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "text",
        -        "type": "string"
        -      },
        -      "letterSpacing": {
        -        "default": 1,
        -        "type": "integer"
        -      },
        -      "lineSpacing": {
        -        "default": 1,
        -        "type": "integer"
        -      },
        -      "outlineColor": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        -        "description": "Colour as #rrggbb or #rrggbbaa."
        -      },
        -      "outlineDiagonals": {
        -        "default": true,
        -        "description": "8-neighbour outline instead of 4.",
        -        "type": "boolean"
        -      },
        -      "scale": {
        -        "default": 1,
        -        "maximum": 8,
        -        "minimum": 1,
        -        "type": "integer"
        -      },
        -      "shadowColor": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        -        "description": "Colour as #rrggbb or #rrggbbaa."
        -      },
        -      "shadowOffset": {
        -        "additionalProperties": false,
        -        "default": {
        -          "x": 1,
        -          "y": 1
        -        },
        -        "properties": {
        -          "x": {
        -            "type": "integer"
        -          },
        -          "y": {
        -            "type": "integer"
        -          }
        -        },
        -        "required": [
        -          "x",
        -          "y"
        -        ],
        -        "type": "object"
        -      },
        -      "text": {
        -        "description": "Multiline via \\n.",
        -        "minLength": 1,
        -        "type": "string"
        -      },
        -      "x": {
        -        "type": "integer"
        -      },
        -      "y": {
        -        "type": "integer"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "text",
        -      "x",
        -      "y",
        -      "color"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "kind": {
        -        "const": "grid",
        -        "type": "string"
        -      },
        -      "legend": {
        -        "additionalProperties": {
        -          "anyOf": [
        -            {
        -              "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -            },
        -            {
        -              "type": "null"
        -            }
        -          ],
        -          "description": "Colour as #rrggbb or #rrggbbaa."
        -        },
        -        "description": "One character → colour; null is transparent. '.' is transparent unless you redefine it.",
        -        "type": "object"
        -      },
        -      "rows": {
        -        "description": "Top to bottom, one character per pixel, every row the same width.",
        -        "items": {
        -          "minLength": 1,
        -          "type": "string"
        -        },
        -        "maxItems": 256,
        -        "minItems": 1,
        -        "type": "array"
        -      },
        -      "transparent": {
        -        "default": "erase",
        -        "description": "Transparent cells: 'erase' clears them, so the grid's rectangle ends up exactly as written; 'skip' leaves the pixel underneath, to stamp a shape over existing art.",
        -        "enum": [
        -          "erase",
        -          "skip"
        -        ],
        -        "type": "string"
        -      },
        -      "x": {
        -        "default": 0,
        -        "description": "Sprite x of the grid's left column.",
        -        "type": "integer"
        -      },
        -      "y": {
        -        "default": 0,
        -        "description": "Sprite y of the grid's top row.",
        -        "type": "integer"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "legend",
        -      "rows"
        -    ],
        -    "type": "object"
        -  }
        -]New value: +[
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "color": {
        +        "description": "Colour as #rrggbb or #rrggbbaa.",
        +        "pattern": "^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$",
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "pixels",
        +        "type": "string"
        +      },
        +      "points": {
        +        "items": {
        +          "additionalProperties": false,
        +          "properties": {
        +            "color": {
        +              "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        +              "description": "Per-pixel colour. Falls back to the op's `color`."
        +            },
        +            "x": {
        +              "type": "integer"
        +            },
        +            "y": {
        +              "type": "integer"
        +            }
        +          },
        +          "required": [
        +            "x",
        +            "y"
        +          ],
        +          "type": "object"
        +        },
        +        "maxItems": 20000,
        +        "minItems": 1,
        +        "type": "array"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "points"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "color": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      },
        +      "from": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "x": {
        +            "type": "integer"
        +          },
        +          "y": {
        +            "type": "integer"
        +          }
        +        },
        +        "required": [
        +          "x",
        +          "y"
        +        ],
        +        "type": "object"
        +      },
        +      "kind": {
        +        "const": "line",
        +        "type": "string"
        +      },
        +      "thickness": {
        +        "default": 1,
        +        "exclusiveMinimum": 0,
        +        "maximum": 64,
        +        "type": "integer"
        +      },
        +      "to": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "x": {
        +            "type": "integer"
        +          },
        +          "y": {
        +            "type": "integer"
        +          }
        +        },
        +        "required": [
        +          "x",
        +          "y"
        +        ],
        +        "type": "object"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "color",
        +      "from",
        +      "to"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "closed": {
        +        "default": false,
        +        "type": "boolean"
        +      },
        +      "color": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      },
        +      "fill": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        +        "description": "Fill colour; only meaningful when closed."
        +      },
        +      "kind": {
        +        "const": "polyline",
        +        "type": "string"
        +      },
        +      "points": {
        +        "items": {
        +          "additionalProperties": false,
        +          "properties": {
        +            "x": {
        +              "type": "integer"
        +            },
        +            "y": {
        +              "type": "integer"
        +            }
        +          },
        +          "required": [
        +            "x",
        +            "y"
        +          ],
        +          "type": "object"
        +        },
        +        "minItems": 2,
        +        "type": "array"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "color",
        +      "points"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "color": {
        +        "description": "Outline colour.",
        +        "pattern": "^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$",
        +        "type": "string"
        +      },
        +      "fill": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        +        "description": "Omit for an outline only."
        +      },
        +      "kind": {
        +        "const": "rect",
        +        "type": "string"
        +      },
        +      "rect": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "height": {
        +            "exclusiveMinimum": 0,
        +            "type": "integer"
        +          },
        +          "width": {
        +            "exclusiveMinimum": 0,
        +            "type": "integer"
        +          },
        +          "x": {
        +            "type": "integer"
        +          },
        +          "y": {
        +            "type": "integer"
        +          }
        +        },
        +        "required": [
        +          "x",
        +          "y",
        +          "width",
        +          "height"
        +        ],
        +        "type": "object"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "color",
        +      "rect"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "color": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      },
        +      "fill": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        +        "description": "Colour as #rrggbb or #rrggbbaa."
        +      },
        +      "kind": {
        +        "const": "ellipse",
        +        "type": "string"
        +      },
        +      "rect": {
        +        "additionalProperties": false,
        +        "description": "Bounding box of the ellipse.",
        +        "properties": {
        +          "height": {
        +            "$ref": "#/properties/ops/items/anyOf/3/properties/rect/properties/height"
        +          },
        +          "width": {
        +            "$ref": "#/properties/ops/items/anyOf/3/properties/rect/properties/width"
        +          },
        +          "x": {
        +            "$ref": "#/properties/ops/items/anyOf/3/properties/rect/properties/x"
        +          },
        +          "y": {
        +            "$ref": "#/properties/ops/items/anyOf/3/properties/rect/properties/y"
        +          }
        +        },
        +        "required": [
        +          "x",
        +          "y",
        +          "width",
        +          "height"
        +        ],
        +        "type": "object"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "color",
        +      "rect"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "at": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "x": {
        +            "type": "integer"
        +          },
        +          "y": {
        +            "type": "integer"
        +          }
        +        },
        +        "required": [
        +          "x",
        +          "y"
        +        ],
        +        "type": "object"
        +      },
        +      "color": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      },
        +      "contiguous": {
        +        "default": true,
        +        "type": "boolean"
        +      },
        +      "kind": {
        +        "const": "fill",
        +        "type": "string"
        +      },
        +      "tolerance": {
        +        "default": 0,
        +        "description": "Largest per-channel difference (0–255) still counted as the clicked colour. RGB sprites only; indexed and grayscale fills stay exact.",
        +        "maximum": 255,
        +        "minimum": 0,
        +        "type": "integer"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "color",
        +      "at"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "from": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      },
        +      "kind": {
        +        "const": "replace",
        +        "type": "string"
        +      },
        +      "region": {
        +        "$ref": "#/properties/ops/items/anyOf/3/properties/rect"
        +      },
        +      "to": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "from",
        +      "to"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "colorA": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      },
        +      "colorB": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      },
        +      "kind": {
        +        "const": "dither",
        +        "type": "string"
        +      },
        +      "pattern": {
        +        "default": "bayer4",
        +        "enum": [
        +          "checker",
        +          "bayer2",
        +          "bayer4",
        +          "bayer8",
        +          "noise"
        +        ],
        +        "type": "string"
        +      },
        +      "ratio": {
        +        "default": 0.5,
        +        "description": "0 = all colorA, 1 = all colorB.",
        +        "maximum": 1,
        +        "minimum": 0,
        +        "type": "number"
        +      },
        +      "rect": {
        +        "$ref": "#/properties/ops/items/anyOf/3/properties/rect"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "rect",
        +      "colorA",
        +      "colorB"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "direction": {
        +        "default": "vertical",
        +        "enum": [
        +          "vertical",
        +          "horizontal",
        +          "diagonal",
        +          "radial"
        +        ],
        +        "type": "string"
        +      },
        +      "dither": {
        +        "default": false,
        +        "description": "Dither the band boundaries.",
        +        "type": "boolean"
        +      },
        +      "from": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      },
        +      "kind": {
        +        "const": "gradient",
        +        "type": "string"
        +      },
        +      "rect": {
        +        "$ref": "#/properties/ops/items/anyOf/3/properties/rect"
        +      },
        +      "steps": {
        +        "default": 4,
        +        "description": "Banded, not smooth — a smooth gradient is not pixel art.",
        +        "maximum": 64,
        +        "minimum": 2,
        +        "type": "integer"
        +      },
        +      "to": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "rect",
        +      "from",
        +      "to"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "kind": {
        +        "const": "clear",
        +        "type": "string"
        +      },
        +      "region": {
        +        "$ref": "#/properties/ops/items/anyOf/3/properties/rect",
        +        "description": "Omit to clear the whole cel."
        +      }
        +    },
        +    "required": [
        +      "kind"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "flipHorizontal": {
        +        "default": false,
        +        "type": "boolean"
        +      },
        +      "flipVertical": {
        +        "default": false,
        +        "type": "boolean"
        +      },
        +      "from": {
        +        "$ref": "#/properties/ops/items/anyOf/3/properties/rect"
        +      },
        +      "fromFrame": {
        +        "exclusiveMinimum": 0,
        +        "type": "integer"
        +      },
        +      "fromLayer": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "blit",
        +        "type": "string"
        +      },
        +      "skipTransparent": {
        +        "default": true,
        +        "type": "boolean"
        +      },
        +      "to": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "x": {
        +            "type": "integer"
        +          },
        +          "y": {
        +            "type": "integer"
        +          }
        +        },
        +        "required": [
        +          "x",
        +          "y"
        +        ],
        +        "type": "object"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "from",
        +      "to"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "anchor": {
        +        "default": "top_left",
        +        "description": "Resolved against the glyph ink box, not the full advance box — outline and shadow never shift it.",
        +        "enum": [
        +          "top_left",
        +          "top",
        +          "top_right",
        +          "left",
        +          "center",
        +          "right",
        +          "bottom_left",
        +          "bottom",
        +          "bottom_right",
        +          "baseline_left",
        +          "baseline",
        +          "baseline_right"
        +        ],
        +        "type": "string"
        +      },
        +      "bold": {
        +        "default": 0,
        +        "description": "Grid-cell passes grown rightward; 0 is regular weight.",
        +        "maximum": 3,
        +        "minimum": 0,
        +        "type": "integer"
        +      },
        +      "color": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      },
        +      "font": {
        +        "default": "pixel5x7",
        +        "description": "Name in knowledge/fonts, without the .json extension.",
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "text",
        +        "type": "string"
        +      },
        +      "letterSpacing": {
        +        "default": 1,
        +        "type": "integer"
        +      },
        +      "lineSpacing": {
        +        "default": 1,
        +        "type": "integer"
        +      },
        +      "outlineColor": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        +        "description": "Colour as #rrggbb or #rrggbbaa."
        +      },
        +      "outlineDiagonals": {
        +        "default": true,
        +        "description": "8-neighbour outline instead of 4.",
        +        "type": "boolean"
        +      },
        +      "scale": {
        +        "default": 1,
        +        "maximum": 8,
        +        "minimum": 1,
        +        "type": "integer"
        +      },
        +      "shadowColor": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        +        "description": "Colour as #rrggbb or #rrggbbaa."
        +      },
        +      "shadowOffset": {
        +        "additionalProperties": false,
        +        "default": {
        +          "x": 1,
        +          "y": 1
        +        },
        +        "properties": {
        +          "x": {
        +            "type": "integer"
        +          },
        +          "y": {
        +            "type": "integer"
        +          }
        +        },
        +        "required": [
        +          "x",
        +          "y"
        +        ],
        +        "type": "object"
        +      },
        +      "text": {
        +        "description": "Multiline via \\n.",
        +        "minLength": 1,
        +        "type": "string"
        +      },
        +      "x": {
        +        "type": "integer"
        +      },
        +      "y": {
        +        "type": "integer"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "text",
        +      "x",
        +      "y",
        +      "color"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "kind": {
        +        "const": "grid",
        +        "type": "string"
        +      },
        +      "legend": {
        +        "additionalProperties": {
        +          "anyOf": [
        +            {
        +              "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +            },
        +            {
        +              "type": "null"
        +            }
        +          ],
        +          "description": "Colour as #rrggbb or #rrggbbaa."
        +        },
        +        "description": "One character → colour; null is transparent. '.' is transparent unless you redefine it.",
        +        "type": "object"
        +      },
        +      "rows": {
        +        "description": "Top to bottom, one character per pixel, every row the same width. Up to the canvas size.",
        +        "items": {
        +          "minLength": 1,
        +          "type": "string"
        +        },
        +        "minItems": 1,
        +        "type": "array"
        +      },
        +      "transparent": {
        +        "default": "erase",
        +        "description": "Transparent cells: 'erase' clears them, so the grid's rectangle ends up exactly as written; 'skip' leaves the pixel underneath, to stamp a shape over existing art.",
        +        "enum": [
        +          "erase",
        +          "skip"
        +        ],
        +        "type": "string"
        +      },
        +      "x": {
        +        "default": 0,
        +        "description": "Sprite x of the grid's left column.",
        +        "type": "integer"
        +      },
        +      "y": {
        +        "default": 0,
        +        "description": "Sprite y of the grid's top row.",
        +        "type": "integer"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "legend",
        +      "rows"
        +    ],
        +    "type": "object"
        +  }
        +]
    • Changedlook3 fields changed
      • changedInput schema / properties / region / description
        Previous value: -"Crop. Required for 'ascii' on sprites larger than 64×64."New value: +"Crop. Omit to read the whole canvas."
      • changedOutput schema / properties / legend / description
        Previous value: -"glyph → #rrggbb."New value: +"glyph → #rrggbb. Palette colours keep the same glyph across every read of the sprite."
      • addedOutput schema / properties / offPalette
        Added value: +{
        +  "description": "ascii/diff: glyphs whose colour is not in the palette. Their assignment can change between reads — do not reuse them across regions.",
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
  3. 5 tool updatesv0.5.0
    • Changeddraw1 field changed
      • changedInput schema / properties / ops / items / anyOf
        Previous value: -[
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "color": {
        -        "description": "Colour as #rrggbb or #rrggbbaa.",
        -        "pattern": "^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$",
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "pixels",
        -        "type": "string"
        -      },
        -      "points": {
        -        "items": {
        -          "additionalProperties": false,
        -          "properties": {
        -            "color": {
        -              "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        -              "description": "Per-pixel colour. Falls back to the op's `color`."
        -            },
        -            "x": {
        -              "type": "integer"
        -            },
        -            "y": {
        -              "type": "integer"
        -            }
        -          },
        -          "required": [
        -            "x",
        -            "y"
        -          ],
        -          "type": "object"
        -        },
        -        "maxItems": 20000,
        -        "minItems": 1,
        -        "type": "array"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "points"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "color": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      },
        -      "from": {
        -        "additionalProperties": false,
        -        "properties": {
        -          "x": {
        -            "type": "integer"
        -          },
        -          "y": {
        -            "type": "integer"
        -          }
        -        },
        -        "required": [
        -          "x",
        -          "y"
        -        ],
        -        "type": "object"
        -      },
        -      "kind": {
        -        "const": "line",
        -        "type": "string"
        -      },
        -      "thickness": {
        -        "default": 1,
        -        "exclusiveMinimum": 0,
        -        "maximum": 64,
        -        "type": "integer"
        -      },
        -      "to": {
        -        "additionalProperties": false,
        -        "properties": {
        -          "x": {
        -            "type": "integer"
        -          },
        -          "y": {
        -            "type": "integer"
        -          }
        -        },
        -        "required": [
        -          "x",
        -          "y"
        -        ],
        -        "type": "object"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "color",
        -      "from",
        -      "to"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "closed": {
        -        "default": false,
        -        "type": "boolean"
        -      },
        -      "color": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      },
        -      "fill": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        -        "description": "Fill colour; only meaningful when closed."
        -      },
        -      "kind": {
        -        "const": "polyline",
        -        "type": "string"
        -      },
        -      "points": {
        -        "items": {
        -          "additionalProperties": false,
        -          "properties": {
        -            "x": {
        -              "type": "integer"
        -            },
        -            "y": {
        -              "type": "integer"
        -            }
        -          },
        -          "required": [
        -            "x",
        -            "y"
        -          ],
        -          "type": "object"
        -        },
        -        "minItems": 2,
        -        "type": "array"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "color",
        -      "points"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "color": {
        -        "description": "Outline colour.",
        -        "pattern": "^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$",
        -        "type": "string"
        -      },
        -      "fill": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        -        "description": "Omit for an outline only."
        -      },
        -      "kind": {
        -        "const": "rect",
        -        "type": "string"
        -      },
        -      "rect": {
        -        "additionalProperties": false,
        -        "properties": {
        -          "height": {
        -            "exclusiveMinimum": 0,
        -            "type": "integer"
        -          },
        -          "width": {
        -            "exclusiveMinimum": 0,
        -            "type": "integer"
        -          },
        -          "x": {
        -            "type": "integer"
        -          },
        -          "y": {
        -            "type": "integer"
        -          }
        -        },
        -        "required": [
        -          "x",
        -          "y",
        -          "width",
        -          "height"
        -        ],
        -        "type": "object"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "color",
        -      "rect"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "color": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      },
        -      "fill": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        -        "description": "Colour as #rrggbb or #rrggbbaa."
        -      },
        -      "kind": {
        -        "const": "ellipse",
        -        "type": "string"
        -      },
        -      "rect": {
        -        "additionalProperties": false,
        -        "description": "Bounding box of the ellipse.",
        -        "properties": {
        -          "height": {
        -            "$ref": "#/properties/ops/items/anyOf/3/properties/rect/properties/height"
        -          },
        -          "width": {
        -            "$ref": "#/properties/ops/items/anyOf/3/properties/rect/properties/width"
        -          },
        -          "x": {
        -            "$ref": "#/properties/ops/items/anyOf/3/properties/rect/properties/x"
        -          },
        -          "y": {
        -            "$ref": "#/properties/ops/items/anyOf/3/properties/rect/properties/y"
        -          }
        -        },
        -        "required": [
        -          "x",
        -          "y",
        -          "width",
        -          "height"
        -        ],
        -        "type": "object"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "color",
        -      "rect"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "at": {
        -        "additionalProperties": false,
        -        "properties": {
        -          "x": {
        -            "type": "integer"
        -          },
        -          "y": {
        -            "type": "integer"
        -          }
        -        },
        -        "required": [
        -          "x",
        -          "y"
        -        ],
        -        "type": "object"
        -      },
        -      "color": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      },
        -      "contiguous": {
        -        "default": true,
        -        "type": "boolean"
        -      },
        -      "kind": {
        -        "const": "fill",
        -        "type": "string"
        -      },
        -      "tolerance": {
        -        "default": 0,
        -        "maximum": 255,
        -        "minimum": 0,
        -        "type": "integer"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "color",
        -      "at"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "from": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      },
        -      "kind": {
        -        "const": "replace",
        -        "type": "string"
        -      },
        -      "region": {
        -        "$ref": "#/properties/ops/items/anyOf/3/properties/rect"
        -      },
        -      "to": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "from",
        -      "to"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "colorA": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      },
        -      "colorB": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      },
        -      "kind": {
        -        "const": "dither",
        -        "type": "string"
        -      },
        -      "pattern": {
        -        "default": "bayer4",
        -        "enum": [
        -          "checker",
        -          "bayer2",
        -          "bayer4",
        -          "bayer8",
        -          "noise"
        -        ],
        -        "type": "string"
        -      },
        -      "ratio": {
        -        "default": 0.5,
        -        "description": "0 = all colorA, 1 = all colorB.",
        -        "maximum": 1,
        -        "minimum": 0,
        -        "type": "number"
        -      },
        -      "rect": {
        -        "$ref": "#/properties/ops/items/anyOf/3/properties/rect"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "rect",
        -      "colorA",
        -      "colorB"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "direction": {
        -        "default": "vertical",
        -        "enum": [
        -          "vertical",
        -          "horizontal",
        -          "diagonal",
        -          "radial"
        -        ],
        -        "type": "string"
        -      },
        -      "dither": {
        -        "default": false,
        -        "description": "Dither the band boundaries.",
        -        "type": "boolean"
        -      },
        -      "from": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      },
        -      "kind": {
        -        "const": "gradient",
        -        "type": "string"
        -      },
        -      "rect": {
        -        "$ref": "#/properties/ops/items/anyOf/3/properties/rect"
        -      },
        -      "steps": {
        -        "default": 4,
        -        "description": "Banded, not smooth — a smooth gradient is not pixel art.",
        -        "maximum": 64,
        -        "minimum": 2,
        -        "type": "integer"
        -      },
        -      "to": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "rect",
        -      "from",
        -      "to"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "kind": {
        -        "const": "clear",
        -        "type": "string"
        -      },
        -      "region": {
        -        "$ref": "#/properties/ops/items/anyOf/3/properties/rect",
        -        "description": "Omit to clear the whole cel."
        -      }
        -    },
        -    "required": [
        -      "kind"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "flipHorizontal": {
        -        "default": false,
        -        "type": "boolean"
        -      },
        -      "flipVertical": {
        -        "default": false,
        -        "type": "boolean"
        -      },
        -      "from": {
        -        "$ref": "#/properties/ops/items/anyOf/3/properties/rect"
        -      },
        -      "fromFrame": {
        -        "exclusiveMinimum": 0,
        -        "type": "integer"
        -      },
        -      "fromLayer": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "blit",
        -        "type": "string"
        -      },
        -      "skipTransparent": {
        -        "default": true,
        -        "type": "boolean"
        -      },
        -      "to": {
        -        "additionalProperties": false,
        -        "properties": {
        -          "x": {
        -            "type": "integer"
        -          },
        -          "y": {
        -            "type": "integer"
        -          }
        -        },
        -        "required": [
        -          "x",
        -          "y"
        -        ],
        -        "type": "object"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "from",
        -      "to"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "anchor": {
        -        "default": "top_left",
        -        "description": "Resolved against the glyph ink box, not the full advance box — outline and shadow never shift it.",
        -        "enum": [
        -          "top_left",
        -          "top",
        -          "top_right",
        -          "left",
        -          "center",
        -          "right",
        -          "bottom_left",
        -          "bottom",
        -          "bottom_right",
        -          "baseline_left",
        -          "baseline",
        -          "baseline_right"
        -        ],
        -        "type": "string"
        -      },
        -      "bold": {
        -        "default": 0,
        -        "description": "Grid-cell passes grown rightward; 0 is regular weight.",
        -        "maximum": 3,
        -        "minimum": 0,
        -        "type": "integer"
        -      },
        -      "color": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      },
        -      "font": {
        -        "default": "pixel5x7",
        -        "description": "Name in knowledge/fonts, without the .json extension.",
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "text",
        -        "type": "string"
        -      },
        -      "letterSpacing": {
        -        "default": 1,
        -        "type": "integer"
        -      },
        -      "lineSpacing": {
        -        "default": 1,
        -        "type": "integer"
        -      },
        -      "outlineColor": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        -        "description": "Colour as #rrggbb or #rrggbbaa."
        -      },
        -      "outlineDiagonals": {
        -        "default": true,
        -        "description": "8-neighbour outline instead of 4.",
        -        "type": "boolean"
        -      },
        -      "scale": {
        -        "default": 1,
        -        "maximum": 8,
        -        "minimum": 1,
        -        "type": "integer"
        -      },
        -      "shadowColor": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        -        "description": "Colour as #rrggbb or #rrggbbaa."
        -      },
        -      "shadowOffset": {
        -        "additionalProperties": false,
        -        "default": {
        -          "x": 1,
        -          "y": 1
        -        },
        -        "properties": {
        -          "x": {
        -            "type": "integer"
        -          },
        -          "y": {
        -            "type": "integer"
        -          }
        -        },
        -        "required": [
        -          "x",
        -          "y"
        -        ],
        -        "type": "object"
        -      },
        -      "text": {
        -        "description": "Multiline via \\n.",
        -        "minLength": 1,
        -        "type": "string"
        -      },
        -      "x": {
        -        "type": "integer"
        -      },
        -      "y": {
        -        "type": "integer"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "text",
        -      "x",
        -      "y",
        -      "color"
        -    ],
        -    "type": "object"
        -  }
        -]New value: +[
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "color": {
        +        "description": "Colour as #rrggbb or #rrggbbaa.",
        +        "pattern": "^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$",
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "pixels",
        +        "type": "string"
        +      },
        +      "points": {
        +        "items": {
        +          "additionalProperties": false,
        +          "properties": {
        +            "color": {
        +              "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        +              "description": "Per-pixel colour. Falls back to the op's `color`."
        +            },
        +            "x": {
        +              "type": "integer"
        +            },
        +            "y": {
        +              "type": "integer"
        +            }
        +          },
        +          "required": [
        +            "x",
        +            "y"
        +          ],
        +          "type": "object"
        +        },
        +        "maxItems": 20000,
        +        "minItems": 1,
        +        "type": "array"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "points"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "color": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      },
        +      "from": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "x": {
        +            "type": "integer"
        +          },
        +          "y": {
        +            "type": "integer"
        +          }
        +        },
        +        "required": [
        +          "x",
        +          "y"
        +        ],
        +        "type": "object"
        +      },
        +      "kind": {
        +        "const": "line",
        +        "type": "string"
        +      },
        +      "thickness": {
        +        "default": 1,
        +        "exclusiveMinimum": 0,
        +        "maximum": 64,
        +        "type": "integer"
        +      },
        +      "to": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "x": {
        +            "type": "integer"
        +          },
        +          "y": {
        +            "type": "integer"
        +          }
        +        },
        +        "required": [
        +          "x",
        +          "y"
        +        ],
        +        "type": "object"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "color",
        +      "from",
        +      "to"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "closed": {
        +        "default": false,
        +        "type": "boolean"
        +      },
        +      "color": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      },
        +      "fill": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        +        "description": "Fill colour; only meaningful when closed."
        +      },
        +      "kind": {
        +        "const": "polyline",
        +        "type": "string"
        +      },
        +      "points": {
        +        "items": {
        +          "additionalProperties": false,
        +          "properties": {
        +            "x": {
        +              "type": "integer"
        +            },
        +            "y": {
        +              "type": "integer"
        +            }
        +          },
        +          "required": [
        +            "x",
        +            "y"
        +          ],
        +          "type": "object"
        +        },
        +        "minItems": 2,
        +        "type": "array"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "color",
        +      "points"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "color": {
        +        "description": "Outline colour.",
        +        "pattern": "^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$",
        +        "type": "string"
        +      },
        +      "fill": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        +        "description": "Omit for an outline only."
        +      },
        +      "kind": {
        +        "const": "rect",
        +        "type": "string"
        +      },
        +      "rect": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "height": {
        +            "exclusiveMinimum": 0,
        +            "type": "integer"
        +          },
        +          "width": {
        +            "exclusiveMinimum": 0,
        +            "type": "integer"
        +          },
        +          "x": {
        +            "type": "integer"
        +          },
        +          "y": {
        +            "type": "integer"
        +          }
        +        },
        +        "required": [
        +          "x",
        +          "y",
        +          "width",
        +          "height"
        +        ],
        +        "type": "object"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "color",
        +      "rect"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "color": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      },
        +      "fill": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        +        "description": "Colour as #rrggbb or #rrggbbaa."
        +      },
        +      "kind": {
        +        "const": "ellipse",
        +        "type": "string"
        +      },
        +      "rect": {
        +        "additionalProperties": false,
        +        "description": "Bounding box of the ellipse.",
        +        "properties": {
        +          "height": {
        +            "$ref": "#/properties/ops/items/anyOf/3/properties/rect/properties/height"
        +          },
        +          "width": {
        +            "$ref": "#/properties/ops/items/anyOf/3/properties/rect/properties/width"
        +          },
        +          "x": {
        +            "$ref": "#/properties/ops/items/anyOf/3/properties/rect/properties/x"
        +          },
        +          "y": {
        +            "$ref": "#/properties/ops/items/anyOf/3/properties/rect/properties/y"
        +          }
        +        },
        +        "required": [
        +          "x",
        +          "y",
        +          "width",
        +          "height"
        +        ],
        +        "type": "object"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "color",
        +      "rect"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "at": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "x": {
        +            "type": "integer"
        +          },
        +          "y": {
        +            "type": "integer"
        +          }
        +        },
        +        "required": [
        +          "x",
        +          "y"
        +        ],
        +        "type": "object"
        +      },
        +      "color": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      },
        +      "contiguous": {
        +        "default": true,
        +        "type": "boolean"
        +      },
        +      "kind": {
        +        "const": "fill",
        +        "type": "string"
        +      },
        +      "tolerance": {
        +        "default": 0,
        +        "description": "Largest per-channel difference (0–255) still counted as the clicked colour. RGB sprites only; indexed and grayscale fills stay exact.",
        +        "maximum": 255,
        +        "minimum": 0,
        +        "type": "integer"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "color",
        +      "at"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "from": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      },
        +      "kind": {
        +        "const": "replace",
        +        "type": "string"
        +      },
        +      "region": {
        +        "$ref": "#/properties/ops/items/anyOf/3/properties/rect"
        +      },
        +      "to": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "from",
        +      "to"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "colorA": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      },
        +      "colorB": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      },
        +      "kind": {
        +        "const": "dither",
        +        "type": "string"
        +      },
        +      "pattern": {
        +        "default": "bayer4",
        +        "enum": [
        +          "checker",
        +          "bayer2",
        +          "bayer4",
        +          "bayer8",
        +          "noise"
        +        ],
        +        "type": "string"
        +      },
        +      "ratio": {
        +        "default": 0.5,
        +        "description": "0 = all colorA, 1 = all colorB.",
        +        "maximum": 1,
        +        "minimum": 0,
        +        "type": "number"
        +      },
        +      "rect": {
        +        "$ref": "#/properties/ops/items/anyOf/3/properties/rect"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "rect",
        +      "colorA",
        +      "colorB"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "direction": {
        +        "default": "vertical",
        +        "enum": [
        +          "vertical",
        +          "horizontal",
        +          "diagonal",
        +          "radial"
        +        ],
        +        "type": "string"
        +      },
        +      "dither": {
        +        "default": false,
        +        "description": "Dither the band boundaries.",
        +        "type": "boolean"
        +      },
        +      "from": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      },
        +      "kind": {
        +        "const": "gradient",
        +        "type": "string"
        +      },
        +      "rect": {
        +        "$ref": "#/properties/ops/items/anyOf/3/properties/rect"
        +      },
        +      "steps": {
        +        "default": 4,
        +        "description": "Banded, not smooth — a smooth gradient is not pixel art.",
        +        "maximum": 64,
        +        "minimum": 2,
        +        "type": "integer"
        +      },
        +      "to": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "rect",
        +      "from",
        +      "to"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "kind": {
        +        "const": "clear",
        +        "type": "string"
        +      },
        +      "region": {
        +        "$ref": "#/properties/ops/items/anyOf/3/properties/rect",
        +        "description": "Omit to clear the whole cel."
        +      }
        +    },
        +    "required": [
        +      "kind"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "flipHorizontal": {
        +        "default": false,
        +        "type": "boolean"
        +      },
        +      "flipVertical": {
        +        "default": false,
        +        "type": "boolean"
        +      },
        +      "from": {
        +        "$ref": "#/properties/ops/items/anyOf/3/properties/rect"
        +      },
        +      "fromFrame": {
        +        "exclusiveMinimum": 0,
        +        "type": "integer"
        +      },
        +      "fromLayer": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "blit",
        +        "type": "string"
        +      },
        +      "skipTransparent": {
        +        "default": true,
        +        "type": "boolean"
        +      },
        +      "to": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "x": {
        +            "type": "integer"
        +          },
        +          "y": {
        +            "type": "integer"
        +          }
        +        },
        +        "required": [
        +          "x",
        +          "y"
        +        ],
        +        "type": "object"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "from",
        +      "to"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "anchor": {
        +        "default": "top_left",
        +        "description": "Resolved against the glyph ink box, not the full advance box — outline and shadow never shift it.",
        +        "enum": [
        +          "top_left",
        +          "top",
        +          "top_right",
        +          "left",
        +          "center",
        +          "right",
        +          "bottom_left",
        +          "bottom",
        +          "bottom_right",
        +          "baseline_left",
        +          "baseline",
        +          "baseline_right"
        +        ],
        +        "type": "string"
        +      },
        +      "bold": {
        +        "default": 0,
        +        "description": "Grid-cell passes grown rightward; 0 is regular weight.",
        +        "maximum": 3,
        +        "minimum": 0,
        +        "type": "integer"
        +      },
        +      "color": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      },
        +      "font": {
        +        "default": "pixel5x7",
        +        "description": "Name in knowledge/fonts, without the .json extension.",
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "text",
        +        "type": "string"
        +      },
        +      "letterSpacing": {
        +        "default": 1,
        +        "type": "integer"
        +      },
        +      "lineSpacing": {
        +        "default": 1,
        +        "type": "integer"
        +      },
        +      "outlineColor": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        +        "description": "Colour as #rrggbb or #rrggbbaa."
        +      },
        +      "outlineDiagonals": {
        +        "default": true,
        +        "description": "8-neighbour outline instead of 4.",
        +        "type": "boolean"
        +      },
        +      "scale": {
        +        "default": 1,
        +        "maximum": 8,
        +        "minimum": 1,
        +        "type": "integer"
        +      },
        +      "shadowColor": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        +        "description": "Colour as #rrggbb or #rrggbbaa."
        +      },
        +      "shadowOffset": {
        +        "additionalProperties": false,
        +        "default": {
        +          "x": 1,
        +          "y": 1
        +        },
        +        "properties": {
        +          "x": {
        +            "type": "integer"
        +          },
        +          "y": {
        +            "type": "integer"
        +          }
        +        },
        +        "required": [
        +          "x",
        +          "y"
        +        ],
        +        "type": "object"
        +      },
        +      "text": {
        +        "description": "Multiline via \\n.",
        +        "minLength": 1,
        +        "type": "string"
        +      },
        +      "x": {
        +        "type": "integer"
        +      },
        +      "y": {
        +        "type": "integer"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "text",
        +      "x",
        +      "y",
        +      "color"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "kind": {
        +        "const": "grid",
        +        "type": "string"
        +      },
        +      "legend": {
        +        "additionalProperties": {
        +          "anyOf": [
        +            {
        +              "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +            },
        +            {
        +              "type": "null"
        +            }
        +          ],
        +          "description": "Colour as #rrggbb or #rrggbbaa."
        +        },
        +        "description": "One character → colour; null is transparent. '.' is transparent unless you redefine it.",
        +        "type": "object"
        +      },
        +      "rows": {
        +        "description": "Top to bottom, one character per pixel, every row the same width.",
        +        "items": {
        +          "minLength": 1,
        +          "type": "string"
        +        },
        +        "maxItems": 256,
        +        "minItems": 1,
        +        "type": "array"
        +      },
        +      "transparent": {
        +        "default": "erase",
        +        "description": "Transparent cells: 'erase' clears them, so the grid's rectangle ends up exactly as written; 'skip' leaves the pixel underneath, to stamp a shape over existing art.",
        +        "enum": [
        +          "erase",
        +          "skip"
        +        ],
        +        "type": "string"
        +      },
        +      "x": {
        +        "default": 0,
        +        "description": "Sprite x of the grid's left column.",
        +        "type": "integer"
        +      },
        +      "y": {
        +        "default": 0,
        +        "description": "Sprite y of the grid's top row.",
        +        "type": "integer"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "legend",
        +      "rows"
        +    ],
        +    "type": "object"
        +  }
        +]
    • Changedexport2 fields changed
      • changedInput schema / properties / layers / description
        Previous value: -"Export only these layers."New value: +"Export only these layers (names or group paths; a group brings its children). png, gif, frames, spritesheet; 'aseprite' refuses it."
      • changedInput schema / properties / tags / description
        Previous value: -"Export only these tags."New value: +"Export only the frames these tags cover. gif, frames, spritesheet; 'png' and 'aseprite' refuse it. An unknown tag is refused."
    • Changedframe1 field changed
      • changedInput schema / properties / toIndex / description
        Previous value: -"For 'reorder'."New value: +"For 'reorder': the 1-based position `frame` ends up at. Its cels and duration move with it; tag ranges stay where they are."
    • Changedlook4 fields changed
      • changedInput schema / properties / layer / description
        Previous value: -"Read a single layer instead of the composited image."New value: +"Read a single layer instead of the composited image. Honoured by preview, ascii, diff, filmstrip and onion; compare has its own `reference`."
      • addedInput schema / properties / rulers
        Added value: +{
        +  "default": true,
        +  "description": "ascii: false drops the coordinate rulers and row labels, leaving rows you can edit and pass straight to `draw` kind 'grid' with the reported origin.",
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / gridRows
        Added value: +{
        +  "description": "ascii: the bare rows, '.' transparent — `draw` kind 'grid' `rows`, with `legend` and `origin`.",
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / origin
        Added value: +{
        +  "additionalProperties": false,
        +  "description": "ascii: sprite position of the grid's top-left cell — the `x`/`y` for `draw` kind 'grid'.",
        +  "properties": {
        +    "x": {
        +      "type": "integer"
        +    },
        +    "y": {
        +      "type": "integer"
        +    }
        +  },
        +  "required": [
        +    "x",
        +    "y"
        +  ],
        +  "type": "object"
        +}
    • Changedselect1 field changed
      • addedInput schema / properties / tolerance / description
        Added value: +"Largest per-channel difference (0–255) still counted as the clicked colour. RGB sprites only; indexed and grayscale fills stay exact."
  4. 11 tool updatesv0.4.0
    • Changedcel10 fields changed
      • addedInput schema / properties / amplitudeX
        Added value: +{
        +  "default": 0,
        +  "description": "For 'oscillate'.",
        +  "type": "integer"
        +}
      • addedInput schema / properties / amplitudeY
        Added value: +{
        +  "default": 0,
        +  "description": "For 'oscillate'.",
        +  "type": "integer"
        +}
      • addedInput schema / properties / easing
        Added value: +{
        +  "default": "linear",
        +  "description": "For 'tween'.",
        +  "enum": [
        +    "linear",
        +    "ease_in",
        +    "ease_out",
        +    "ease_in_out",
        +    "smoothstep"
        +  ],
        +  "type": "string"
        +}
      • addedInput schema / properties / fromFrame
        Added value: +{
        +  "description": "For 'tween'/'oscillate': the start frame.",
        +  "exclusiveMinimum": 0,
        +  "type": "integer"
        +}
      • changedInput schema / properties / op / enum
        Previous value: -[
        -  "list",
        -  "create",
        -  "clear",
        -  "delete",
        -  "move",
        -  "copy",
        -  "link",
        -  "unlink",
        -  "set"
        -]New value: +[
        +  "list",
        +  "create",
        +  "clear",
        +  "delete",
        +  "move",
        +  "copy",
        +  "link",
        +  "unlink",
        +  "set",
        +  "tween",
        +  "oscillate"
        +]
      • addedInput schema / properties / period
        Added value: +{
        +  "description": "For 'oscillate', in frames. Default: the whole fromFrame..toFrame span.",
        +  "minimum": 2,
        +  "type": "integer"
        +}
      • addedInput schema / properties / phase
        Added value: +{
        +  "default": 0,
        +  "description": "For 'oscillate': 0-1 fraction of one period.",
        +  "maximum": 1,
        +  "minimum": 0,
        +  "type": "number"
        +}
      • addedInput schema / properties / property
        Added value: +{
        +  "description": "For 'tween'.",
        +  "enum": [
        +    "position",
        +    "opacity"
        +  ],
        +  "type": "string"
        +}
      • addedInput schema / properties / to
        Added value: +{
        +  "anyOf": [
        +    {
        +      "additionalProperties": false,
        +      "properties": {
        +        "x": {
        +          "type": "integer"
        +        },
        +        "y": {
        +          "type": "integer"
        +        }
        +      },
        +      "required": [
        +        "x",
        +        "y"
        +      ],
        +      "type": "object"
        +    },
        +    {
        +      "maximum": 255,
        +      "minimum": 0,
        +      "type": "integer"
        +    }
        +  ],
        +  "description": "For 'tween': end value — {x,y} for property 'position', 0-255 for 'opacity'."
        +}
      • addedOutput schema / properties / frames
        Added value: +{
        +  "description": "Only present for 'tween' and 'oscillate': one entry per frame touched, ascending.",
        +  "items": {
        +    "additionalProperties": false,
        +    "properties": {
        +      "frame": {
        +        "type": "integer"
        +      },
        +      "opacity": {
        +        "type": "integer"
        +      },
        +      "x": {
        +        "type": "integer"
        +      },
        +      "y": {
        +        "type": "integer"
        +      }
        +    },
        +    "required": [
        +      "frame"
        +    ],
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
    • Changeddraw5 fields changed
      • addedInput schema / properties / measureOnly
        Added value: +{
        +  "default": false,
        +  "description": "Every op must be 'text'. Returns each one's ink bounds without calling Aseprite at all — use to size a panel or centre a label before actually drawing it.",
        +  "type": "boolean"
        +}
      • changedInput schema / properties / ops / items / anyOf
        Previous value: -[
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "color": {
        -        "description": "Colour as #rrggbb or #rrggbbaa.",
        -        "pattern": "^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$",
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "pixels",
        -        "type": "string"
        -      },
        -      "points": {
        -        "items": {
        -          "additionalProperties": false,
        -          "properties": {
        -            "color": {
        -              "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        -              "description": "Per-pixel colour. Falls back to the op's `color`."
        -            },
        -            "x": {
        -              "type": "integer"
        -            },
        -            "y": {
        -              "type": "integer"
        -            }
        -          },
        -          "required": [
        -            "x",
        -            "y"
        -          ],
        -          "type": "object"
        -        },
        -        "maxItems": 20000,
        -        "minItems": 1,
        -        "type": "array"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "points"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "color": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      },
        -      "from": {
        -        "additionalProperties": false,
        -        "properties": {
        -          "x": {
        -            "type": "integer"
        -          },
        -          "y": {
        -            "type": "integer"
        -          }
        -        },
        -        "required": [
        -          "x",
        -          "y"
        -        ],
        -        "type": "object"
        -      },
        -      "kind": {
        -        "const": "line",
        -        "type": "string"
        -      },
        -      "thickness": {
        -        "default": 1,
        -        "exclusiveMinimum": 0,
        -        "maximum": 64,
        -        "type": "integer"
        -      },
        -      "to": {
        -        "additionalProperties": false,
        -        "properties": {
        -          "x": {
        -            "type": "integer"
        -          },
        -          "y": {
        -            "type": "integer"
        -          }
        -        },
        -        "required": [
        -          "x",
        -          "y"
        -        ],
        -        "type": "object"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "color",
        -      "from",
        -      "to"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "closed": {
        -        "default": false,
        -        "type": "boolean"
        -      },
        -      "color": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      },
        -      "fill": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        -        "description": "Fill colour; only meaningful when closed."
        -      },
        -      "kind": {
        -        "const": "polyline",
        -        "type": "string"
        -      },
        -      "points": {
        -        "items": {
        -          "additionalProperties": false,
        -          "properties": {
        -            "x": {
        -              "type": "integer"
        -            },
        -            "y": {
        -              "type": "integer"
        -            }
        -          },
        -          "required": [
        -            "x",
        -            "y"
        -          ],
        -          "type": "object"
        -        },
        -        "minItems": 2,
        -        "type": "array"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "color",
        -      "points"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "color": {
        -        "description": "Outline colour.",
        -        "pattern": "^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$",
        -        "type": "string"
        -      },
        -      "fill": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        -        "description": "Omit for an outline only."
        -      },
        -      "kind": {
        -        "const": "rect",
        -        "type": "string"
        -      },
        -      "rect": {
        -        "additionalProperties": false,
        -        "properties": {
        -          "height": {
        -            "exclusiveMinimum": 0,
        -            "type": "integer"
        -          },
        -          "width": {
        -            "exclusiveMinimum": 0,
        -            "type": "integer"
        -          },
        -          "x": {
        -            "type": "integer"
        -          },
        -          "y": {
        -            "type": "integer"
        -          }
        -        },
        -        "required": [
        -          "x",
        -          "y",
        -          "width",
        -          "height"
        -        ],
        -        "type": "object"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "color",
        -      "rect"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "color": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      },
        -      "fill": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        -        "description": "Colour as #rrggbb or #rrggbbaa."
        -      },
        -      "kind": {
        -        "const": "ellipse",
        -        "type": "string"
        -      },
        -      "rect": {
        -        "additionalProperties": false,
        -        "description": "Bounding box of the ellipse.",
        -        "properties": {
        -          "height": {
        -            "$ref": "#/properties/ops/items/anyOf/3/properties/rect/properties/height"
        -          },
        -          "width": {
        -            "$ref": "#/properties/ops/items/anyOf/3/properties/rect/properties/width"
        -          },
        -          "x": {
        -            "$ref": "#/properties/ops/items/anyOf/3/properties/rect/properties/x"
        -          },
        -          "y": {
        -            "$ref": "#/properties/ops/items/anyOf/3/properties/rect/properties/y"
        -          }
        -        },
        -        "required": [
        -          "x",
        -          "y",
        -          "width",
        -          "height"
        -        ],
        -        "type": "object"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "color",
        -      "rect"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "at": {
        -        "additionalProperties": false,
        -        "properties": {
        -          "x": {
        -            "type": "integer"
        -          },
        -          "y": {
        -            "type": "integer"
        -          }
        -        },
        -        "required": [
        -          "x",
        -          "y"
        -        ],
        -        "type": "object"
        -      },
        -      "color": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      },
        -      "contiguous": {
        -        "default": true,
        -        "type": "boolean"
        -      },
        -      "kind": {
        -        "const": "fill",
        -        "type": "string"
        -      },
        -      "tolerance": {
        -        "default": 0,
        -        "maximum": 255,
        -        "minimum": 0,
        -        "type": "integer"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "color",
        -      "at"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "from": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      },
        -      "kind": {
        -        "const": "replace",
        -        "type": "string"
        -      },
        -      "region": {
        -        "$ref": "#/properties/ops/items/anyOf/3/properties/rect"
        -      },
        -      "to": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "from",
        -      "to"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "colorA": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      },
        -      "colorB": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      },
        -      "kind": {
        -        "const": "dither",
        -        "type": "string"
        -      },
        -      "pattern": {
        -        "default": "bayer4",
        -        "enum": [
        -          "checker",
        -          "bayer2",
        -          "bayer4",
        -          "bayer8",
        -          "noise"
        -        ],
        -        "type": "string"
        -      },
        -      "ratio": {
        -        "default": 0.5,
        -        "description": "0 = all colorA, 1 = all colorB.",
        -        "maximum": 1,
        -        "minimum": 0,
        -        "type": "number"
        -      },
        -      "rect": {
        -        "$ref": "#/properties/ops/items/anyOf/3/properties/rect"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "rect",
        -      "colorA",
        -      "colorB"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "direction": {
        -        "default": "vertical",
        -        "enum": [
        -          "vertical",
        -          "horizontal",
        -          "diagonal",
        -          "radial"
        -        ],
        -        "type": "string"
        -      },
        -      "dither": {
        -        "default": false,
        -        "description": "Dither the band boundaries.",
        -        "type": "boolean"
        -      },
        -      "from": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      },
        -      "kind": {
        -        "const": "gradient",
        -        "type": "string"
        -      },
        -      "rect": {
        -        "$ref": "#/properties/ops/items/anyOf/3/properties/rect"
        -      },
        -      "steps": {
        -        "default": 4,
        -        "description": "Banded, not smooth — a smooth gradient is not pixel art.",
        -        "maximum": 64,
        -        "minimum": 2,
        -        "type": "integer"
        -      },
        -      "to": {
        -        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "rect",
        -      "from",
        -      "to"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "kind": {
        -        "const": "clear",
        -        "type": "string"
        -      },
        -      "region": {
        -        "$ref": "#/properties/ops/items/anyOf/3/properties/rect",
        -        "description": "Omit to clear the whole cel."
        -      }
        -    },
        -    "required": [
        -      "kind"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "flipHorizontal": {
        -        "default": false,
        -        "type": "boolean"
        -      },
        -      "flipVertical": {
        -        "default": false,
        -        "type": "boolean"
        -      },
        -      "from": {
        -        "$ref": "#/properties/ops/items/anyOf/3/properties/rect"
        -      },
        -      "fromFrame": {
        -        "exclusiveMinimum": 0,
        -        "type": "integer"
        -      },
        -      "fromLayer": {
        -        "type": "string"
        -      },
        -      "kind": {
        -        "const": "blit",
        -        "type": "string"
        -      },
        -      "skipTransparent": {
        -        "default": true,
        -        "type": "boolean"
        -      },
        -      "to": {
        -        "additionalProperties": false,
        -        "properties": {
        -          "x": {
        -            "type": "integer"
        -          },
        -          "y": {
        -            "type": "integer"
        -          }
        -        },
        -        "required": [
        -          "x",
        -          "y"
        -        ],
        -        "type": "object"
        -      }
        -    },
        -    "required": [
        -      "kind",
        -      "from",
        -      "to"
        -    ],
        -    "type": "object"
        -  }
        -]New value: +[
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "color": {
        +        "description": "Colour as #rrggbb or #rrggbbaa.",
        +        "pattern": "^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$",
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "pixels",
        +        "type": "string"
        +      },
        +      "points": {
        +        "items": {
        +          "additionalProperties": false,
        +          "properties": {
        +            "color": {
        +              "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        +              "description": "Per-pixel colour. Falls back to the op's `color`."
        +            },
        +            "x": {
        +              "type": "integer"
        +            },
        +            "y": {
        +              "type": "integer"
        +            }
        +          },
        +          "required": [
        +            "x",
        +            "y"
        +          ],
        +          "type": "object"
        +        },
        +        "maxItems": 20000,
        +        "minItems": 1,
        +        "type": "array"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "points"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "color": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      },
        +      "from": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "x": {
        +            "type": "integer"
        +          },
        +          "y": {
        +            "type": "integer"
        +          }
        +        },
        +        "required": [
        +          "x",
        +          "y"
        +        ],
        +        "type": "object"
        +      },
        +      "kind": {
        +        "const": "line",
        +        "type": "string"
        +      },
        +      "thickness": {
        +        "default": 1,
        +        "exclusiveMinimum": 0,
        +        "maximum": 64,
        +        "type": "integer"
        +      },
        +      "to": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "x": {
        +            "type": "integer"
        +          },
        +          "y": {
        +            "type": "integer"
        +          }
        +        },
        +        "required": [
        +          "x",
        +          "y"
        +        ],
        +        "type": "object"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "color",
        +      "from",
        +      "to"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "closed": {
        +        "default": false,
        +        "type": "boolean"
        +      },
        +      "color": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      },
        +      "fill": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        +        "description": "Fill colour; only meaningful when closed."
        +      },
        +      "kind": {
        +        "const": "polyline",
        +        "type": "string"
        +      },
        +      "points": {
        +        "items": {
        +          "additionalProperties": false,
        +          "properties": {
        +            "x": {
        +              "type": "integer"
        +            },
        +            "y": {
        +              "type": "integer"
        +            }
        +          },
        +          "required": [
        +            "x",
        +            "y"
        +          ],
        +          "type": "object"
        +        },
        +        "minItems": 2,
        +        "type": "array"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "color",
        +      "points"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "color": {
        +        "description": "Outline colour.",
        +        "pattern": "^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$",
        +        "type": "string"
        +      },
        +      "fill": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        +        "description": "Omit for an outline only."
        +      },
        +      "kind": {
        +        "const": "rect",
        +        "type": "string"
        +      },
        +      "rect": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "height": {
        +            "exclusiveMinimum": 0,
        +            "type": "integer"
        +          },
        +          "width": {
        +            "exclusiveMinimum": 0,
        +            "type": "integer"
        +          },
        +          "x": {
        +            "type": "integer"
        +          },
        +          "y": {
        +            "type": "integer"
        +          }
        +        },
        +        "required": [
        +          "x",
        +          "y",
        +          "width",
        +          "height"
        +        ],
        +        "type": "object"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "color",
        +      "rect"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "color": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      },
        +      "fill": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        +        "description": "Colour as #rrggbb or #rrggbbaa."
        +      },
        +      "kind": {
        +        "const": "ellipse",
        +        "type": "string"
        +      },
        +      "rect": {
        +        "additionalProperties": false,
        +        "description": "Bounding box of the ellipse.",
        +        "properties": {
        +          "height": {
        +            "$ref": "#/properties/ops/items/anyOf/3/properties/rect/properties/height"
        +          },
        +          "width": {
        +            "$ref": "#/properties/ops/items/anyOf/3/properties/rect/properties/width"
        +          },
        +          "x": {
        +            "$ref": "#/properties/ops/items/anyOf/3/properties/rect/properties/x"
        +          },
        +          "y": {
        +            "$ref": "#/properties/ops/items/anyOf/3/properties/rect/properties/y"
        +          }
        +        },
        +        "required": [
        +          "x",
        +          "y",
        +          "width",
        +          "height"
        +        ],
        +        "type": "object"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "color",
        +      "rect"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "at": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "x": {
        +            "type": "integer"
        +          },
        +          "y": {
        +            "type": "integer"
        +          }
        +        },
        +        "required": [
        +          "x",
        +          "y"
        +        ],
        +        "type": "object"
        +      },
        +      "color": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      },
        +      "contiguous": {
        +        "default": true,
        +        "type": "boolean"
        +      },
        +      "kind": {
        +        "const": "fill",
        +        "type": "string"
        +      },
        +      "tolerance": {
        +        "default": 0,
        +        "maximum": 255,
        +        "minimum": 0,
        +        "type": "integer"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "color",
        +      "at"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "from": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      },
        +      "kind": {
        +        "const": "replace",
        +        "type": "string"
        +      },
        +      "region": {
        +        "$ref": "#/properties/ops/items/anyOf/3/properties/rect"
        +      },
        +      "to": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "from",
        +      "to"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "colorA": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      },
        +      "colorB": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      },
        +      "kind": {
        +        "const": "dither",
        +        "type": "string"
        +      },
        +      "pattern": {
        +        "default": "bayer4",
        +        "enum": [
        +          "checker",
        +          "bayer2",
        +          "bayer4",
        +          "bayer8",
        +          "noise"
        +        ],
        +        "type": "string"
        +      },
        +      "ratio": {
        +        "default": 0.5,
        +        "description": "0 = all colorA, 1 = all colorB.",
        +        "maximum": 1,
        +        "minimum": 0,
        +        "type": "number"
        +      },
        +      "rect": {
        +        "$ref": "#/properties/ops/items/anyOf/3/properties/rect"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "rect",
        +      "colorA",
        +      "colorB"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "direction": {
        +        "default": "vertical",
        +        "enum": [
        +          "vertical",
        +          "horizontal",
        +          "diagonal",
        +          "radial"
        +        ],
        +        "type": "string"
        +      },
        +      "dither": {
        +        "default": false,
        +        "description": "Dither the band boundaries.",
        +        "type": "boolean"
        +      },
        +      "from": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      },
        +      "kind": {
        +        "const": "gradient",
        +        "type": "string"
        +      },
        +      "rect": {
        +        "$ref": "#/properties/ops/items/anyOf/3/properties/rect"
        +      },
        +      "steps": {
        +        "default": 4,
        +        "description": "Banded, not smooth — a smooth gradient is not pixel art.",
        +        "maximum": 64,
        +        "minimum": 2,
        +        "type": "integer"
        +      },
        +      "to": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "rect",
        +      "from",
        +      "to"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "kind": {
        +        "const": "clear",
        +        "type": "string"
        +      },
        +      "region": {
        +        "$ref": "#/properties/ops/items/anyOf/3/properties/rect",
        +        "description": "Omit to clear the whole cel."
        +      }
        +    },
        +    "required": [
        +      "kind"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "flipHorizontal": {
        +        "default": false,
        +        "type": "boolean"
        +      },
        +      "flipVertical": {
        +        "default": false,
        +        "type": "boolean"
        +      },
        +      "from": {
        +        "$ref": "#/properties/ops/items/anyOf/3/properties/rect"
        +      },
        +      "fromFrame": {
        +        "exclusiveMinimum": 0,
        +        "type": "integer"
        +      },
        +      "fromLayer": {
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "blit",
        +        "type": "string"
        +      },
        +      "skipTransparent": {
        +        "default": true,
        +        "type": "boolean"
        +      },
        +      "to": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "x": {
        +            "type": "integer"
        +          },
        +          "y": {
        +            "type": "integer"
        +          }
        +        },
        +        "required": [
        +          "x",
        +          "y"
        +        ],
        +        "type": "object"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "from",
        +      "to"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "anchor": {
        +        "default": "top_left",
        +        "description": "Resolved against the glyph ink box, not the full advance box — outline and shadow never shift it.",
        +        "enum": [
        +          "top_left",
        +          "top",
        +          "top_right",
        +          "left",
        +          "center",
        +          "right",
        +          "bottom_left",
        +          "bottom",
        +          "bottom_right",
        +          "baseline_left",
        +          "baseline",
        +          "baseline_right"
        +        ],
        +        "type": "string"
        +      },
        +      "bold": {
        +        "default": 0,
        +        "description": "Grid-cell passes grown rightward; 0 is regular weight.",
        +        "maximum": 3,
        +        "minimum": 0,
        +        "type": "integer"
        +      },
        +      "color": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color"
        +      },
        +      "font": {
        +        "default": "pixel5x7",
        +        "description": "Name in knowledge/fonts, without the .json extension.",
        +        "type": "string"
        +      },
        +      "kind": {
        +        "const": "text",
        +        "type": "string"
        +      },
        +      "letterSpacing": {
        +        "default": 1,
        +        "type": "integer"
        +      },
        +      "lineSpacing": {
        +        "default": 1,
        +        "type": "integer"
        +      },
        +      "outlineColor": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        +        "description": "Colour as #rrggbb or #rrggbbaa."
        +      },
        +      "outlineDiagonals": {
        +        "default": true,
        +        "description": "8-neighbour outline instead of 4.",
        +        "type": "boolean"
        +      },
        +      "scale": {
        +        "default": 1,
        +        "maximum": 8,
        +        "minimum": 1,
        +        "type": "integer"
        +      },
        +      "shadowColor": {
        +        "$ref": "#/properties/ops/items/anyOf/0/properties/color",
        +        "description": "Colour as #rrggbb or #rrggbbaa."
        +      },
        +      "shadowOffset": {
        +        "additionalProperties": false,
        +        "default": {
        +          "x": 1,
        +          "y": 1
        +        },
        +        "properties": {
        +          "x": {
        +            "type": "integer"
        +          },
        +          "y": {
        +            "type": "integer"
        +          }
        +        },
        +        "required": [
        +          "x",
        +          "y"
        +        ],
        +        "type": "object"
        +      },
        +      "text": {
        +        "description": "Multiline via \\n.",
        +        "minLength": 1,
        +        "type": "string"
        +      },
        +      "x": {
        +        "type": "integer"
        +      },
        +      "y": {
        +        "type": "integer"
        +      }
        +    },
        +    "required": [
        +      "kind",
        +      "text",
        +      "x",
        +      "y",
        +      "color"
        +    ],
        +    "type": "object"
        +  }
        +]
      • addedOutput schema / properties / measureOnly
        Added value: +{
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / textBounds
        Added value: +{
        +  "description": "Only present when `measureOnly` was set.",
        +  "items": {
        +    "additionalProperties": false,
        +    "properties": {
        +      "bounds": {
        +        "anyOf": [
        +          {
        +            "additionalProperties": false,
        +            "properties": {
        +              "height": {
        +                "type": "integer"
        +              },
        +              "width": {
        +                "type": "integer"
        +              },
        +              "x": {
        +                "type": "integer"
        +              },
        +              "y": {
        +                "type": "integer"
        +              }
        +            },
        +            "required": [
        +              "x",
        +              "y",
        +              "width",
        +              "height"
        +            ],
        +            "type": "object"
        +          },
        +          {
        +            "type": "null"
        +          }
        +        ],
        +        "description": "Ink bounds of the laid-out text; null for text with no ink (e.g. all spaces)."
        +      },
        +      "index": {
        +        "description": "Position of this op in the `ops` array.",
        +        "type": "integer"
        +      }
        +    },
        +    "required": [
        +      "index"
        +    ],
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
      • removedOutput schema / required
        Removed value: -[
        -  "sprite",
        -  "layer",
        -  "frame",
        -  "opsApplied",
        -  "pixelsChanged",
        -  "colorsSnapped"
        -]
    • Changedlayer3 fields changed
      • addedInput schema / properties / batch / items / properties / toSprite
        Added value: +{
        +  "description": "For 'duplicate': copy the layer into another OPEN sprite instead of this one.",
        +  "type": "string"
        +}
      • addedInput schema / properties / toSprite
        Added value: +{
        +  "description": "For 'duplicate': copy the layer into another OPEN sprite instead of this one.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / duplicated
        Added value: +{
        +  "description": "Present when a 'duplicate' op in this call used `toSprite`.",
        +  "items": {
        +    "additionalProperties": false,
        +    "properties": {
        +      "framesDropped": {
        +        "description": "Cels whose frame index exceeded the target's frame count.",
        +        "type": "integer"
        +      },
        +      "name": {
        +        "description": "Name it got in the target sprite; suffixed on a name clash.",
        +        "type": "string"
        +      },
        +      "sourceLayer": {
        +        "type": "string"
        +      },
        +      "toSprite": {
        +        "description": "Target as '#<id>' — stable even when several documents share a name.",
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "sourceLayer",
        +      "name",
        +      "toSprite",
        +      "framesDropped"
        +    ],
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
    • Changedlook9 fields changed
      • addedInput schema / properties / framesAfter
        Added value: +{
        +  "default": 1,
        +  "description": "Onion: ghost frames after the target.",
        +  "maximum": 8,
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • addedInput schema / properties / framesBefore
        Added value: +{
        +  "default": 1,
        +  "description": "Onion: ghost frames before the target.",
        +  "maximum": 8,
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • addedInput schema / properties / ghostOpacity
        Added value: +{
        +  "default": 90,
        +  "description": "Onion: opacity of the ghosted (non-target) frames.",
        +  "maximum": 255,
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • changedInput schema / properties / op / enum
        Previous value: -[
        -  "preview",
        -  "ascii",
        -  "filmstrip",
        -  "diff"
        -]New value: +[
        +  "preview",
        +  "ascii",
        +  "filmstrip",
        +  "diff",
        +  "onion",
        +  "compare"
        +]
      • addedInput schema / properties / reference
        Added value: +{
        +  "description": "Compare: the reference layer. Default 'reference'.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / changedBounds
        Added value: +{
        +  "anyOf": [
        +    {
        +      "additionalProperties": false,
        +      "properties": {
        +        "height": {
        +          "type": "integer"
        +        },
        +        "width": {
        +          "type": "integer"
        +        },
        +        "x": {
        +          "type": "integer"
        +        },
        +        "y": {
        +          "type": "integer"
        +        }
        +      },
        +      "required": [
        +        "x",
        +        "y",
        +        "width",
        +        "height"
        +      ],
        +      "type": "object"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "description": "Diff: tight bounding box of every changed pixel. Null when nothing changed."
        +}
      • addedOutput schema / properties / frames / description
        Added value: +"Filmstrip: frame count."
      • addedOutput schema / properties / framesUsed
        Added value: +{
        +  "description": "Onion: 1-based frame numbers composited.",
        +  "items": {
        +    "type": "integer"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / percentChanged
        Added value: +{
        +  "description": "Diff: changed / total * 100.",
        +  "type": "number"
        +}
    • Changedpalette2 fields changed
      • addedInput schema / properties / maxColors
        Added value: +{
        +  "default": 16,
        +  "description": "For op 'extract': target palette size.",
        +  "maximum": 256,
        +  "minimum": 2,
        +  "type": "integer"
        +}
      • changedInput schema / properties / op / enum
        Previous value: -[
        -  "get",
        -  "set",
        -  "preset",
        -  "load",
        -  "ramp",
        -  "analyze"
        -]New value: +[
        +  "get",
        +  "set",
        +  "preset",
        +  "load",
        +  "ramp",
        +  "analyze",
        +  "extract"
        +]
    • Changedpreflight5 fields changed
      • addedInput schema / additionalProperties
        Added value: +false
      • addedInput schema / properties / mode
        Added value: +{
        +  "description": "Switch the session to this Aseprite before checking. Omit to keep the current one.",
        +  "enum": [
        +    "live",
        +    "headless"
        +  ],
        +  "type": "string"
        +}
      • addedOutput schema / properties / mode
        Added value: +{
        +  "description": "'live' = the user's open window. 'headless' = a batch Aseprite with no window; nothing is on disk until you save or export.",
        +  "enum": [
        +    "live",
        +    "headless"
        +  ],
        +  "type": "string"
        +}
      • addedOutput schema / properties / switched
        Added value: +{
        +  "description": "True when this call changed the session's mode.",
        +  "type": "boolean"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "ready",
        -  "bridgeConnected",
        -  "pluginConnected",
        -  "features",
        -  "directive"
        -]New value: +[
        +  "ready",
        +  "switched",
        +  "mode",
        +  "bridgeConnected",
        +  "pluginConnected",
        +  "features",
        +  "directive"
        +]
    • Changedreference6 fields changed
      • changedInput schema / properties / fit / description
        Previous value: -"How to size the image against the canvas."New value: +"How to size the image (or each panel) against the canvas."
      • addedInput schema / properties / frame
        Added value: +{
        +  "description": "Import: the frame the image — or the first `grid` panel — lands on. Default: the active frame, or frame 1 with `grid`.",
        +  "exclusiveMinimum": 0,
        +  "type": "integer"
        +}
      • addedInput schema / properties / grid
        Added value: +{
        +  "additionalProperties": false,
        +  "description": "Import a storyboard: split the (cropped) source into equal panels and place panel i on frame `frame`+i-1 of one reference layer. The sprite must already have enough frames.",
        +  "properties": {
        +    "columns": {
        +      "exclusiveMinimum": 0,
        +      "type": "integer"
        +    },
        +    "count": {
        +      "description": "Panels actually used, read row-major. Default: columns × rows.",
        +      "exclusiveMinimum": 0,
        +      "type": "integer"
        +    },
        +    "gap": {
        +      "default": 0,
        +      "description": "Source pixels between panels.",
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "rows": {
        +      "exclusiveMinimum": 0,
        +      "type": "integer"
        +    }
        +  },
        +  "required": [
        +    "columns",
        +    "rows"
        +  ],
        +  "type": "object"
        +}
      • addedInput schema / properties / region
        Added value: +{
        +  "additionalProperties": false,
        +  "description": "Use only this rectangle of the SOURCE image, in its own pixels — one panel of a concept sheet. Applies to 'import' and 'sample_palette'.",
        +  "properties": {
        +    "height": {
        +      "exclusiveMinimum": 0,
        +      "type": "integer"
        +    },
        +    "width": {
        +      "exclusiveMinimum": 0,
        +      "type": "integer"
        +    },
        +    "x": {
        +      "minimum": 0,
        +      "type": "integer"
        +    },
        +    "y": {
        +      "minimum": 0,
        +      "type": "integer"
        +    }
        +  },
        +  "required": [
        +    "x",
        +    "y",
        +    "width",
        +    "height"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / properties / frames
        Added value: +{
        +  "description": "Import: the frames that received a reference cel.",
        +  "items": {
        +    "type": "integer"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / panels
        Added value: +{
        +  "description": "Import: how many panels were placed.",
        +  "type": "integer"
        +}
    • Changedsprite_info5 fields changed
      • changedOutput schema / properties / slices / items / properties / bounds / additionalProperties
        Previous value: -{
        -  "type": "number"
        -}New value: +false
      • addedOutput schema / properties / slices / items / properties / bounds / properties
        Added value: +{
        +  "height": {
        +    "exclusiveMinimum": 0,
        +    "type": "integer"
        +  },
        +  "width": {
        +    "exclusiveMinimum": 0,
        +    "type": "integer"
        +  },
        +  "x": {
        +    "type": "integer"
        +  },
        +  "y": {
        +    "type": "integer"
        +  }
        +}
      • addedOutput schema / properties / slices / items / properties / bounds / required
        Added value: +[
        +  "x",
        +  "y",
        +  "width",
        +  "height"
        +]
      • addedOutput schema / properties / slices / items / properties / center
        Added value: +{
        +  "$ref": "#/properties/slices/items/properties/bounds",
        +  "description": "Nine-slice centre region, relative to the slice. Only when set."
        +}
      • addedOutput schema / properties / slices / items / properties / pivot
        Added value: +{
        +  "additionalProperties": false,
        +  "properties": {
        +    "x": {
        +      "type": "integer"
        +    },
        +    "y": {
        +      "type": "integer"
        +    }
        +  },
        +  "required": [
        +    "x",
        +    "y"
        +  ],
        +  "type": "object"
        +}
    • Changedsprite_manage8 fields changed
      • addedInput schema / properties / bounds
        Added value: +{
        +  "additionalProperties": false,
        +  "description": "Slice rectangle. Required for 'slice_create'.",
        +  "properties": {
        +    "height": {
        +      "exclusiveMinimum": 0,
        +      "type": "integer"
        +    },
        +    "width": {
        +      "exclusiveMinimum": 0,
        +      "type": "integer"
        +    },
        +    "x": {
        +      "type": "integer"
        +    },
        +    "y": {
        +      "type": "integer"
        +    }
        +  },
        +  "required": [
        +    "x",
        +    "y",
        +    "width",
        +    "height"
        +  ],
        +  "type": "object"
        +}
      • addedInput schema / properties / center
        Added value: +{
        +  "$ref": "#/properties/bounds",
        +  "description": "For slice ops: nine-slice centre region, relative to `bounds`."
        +}
      • addedInput schema / properties / color
        Added value: +{
        +  "description": "For slice ops: slice colour in the timeline, #rrggbb.",
        +  "type": "string"
        +}
      • addedInput schema / properties / name
        Added value: +{
        +  "description": "Slice name. Required for 'slice_create'; identifies it for update/delete.",
        +  "type": "string"
        +}
      • changedInput schema / properties / op / enum
        Previous value: -[
        -  "list",
        -  "new",
        -  "open",
        -  "activate",
        -  "save",
        -  "save_as",
        -  "close",
        -  "resize_canvas",
        -  "set_properties"
        -]New value: +[
        +  "list",
        +  "new",
        +  "open",
        +  "activate",
        +  "save",
        +  "save_as",
        +  "close",
        +  "resize_canvas",
        +  "set_properties",
        +  "slice_create",
        +  "slice_update",
        +  "slice_delete"
        +]
      • addedInput schema / properties / pivot
        Added value: +{
        +  "additionalProperties": false,
        +  "description": "For slice ops.",
        +  "properties": {
        +    "x": {
        +      "type": "integer"
        +    },
        +    "y": {
        +      "type": "integer"
        +    }
        +  },
        +  "required": [
        +    "x",
        +    "y"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / properties / name
        Added value: +{
        +  "description": "Slice name, for 'slice_delete'.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / slice
        Added value: +{
        +  "additionalProperties": false,
        +  "description": "The affected slice, for 'slice_create'/'slice_update'.",
        +  "properties": {
        +    "bounds": {
        +      "additionalProperties": false,
        +      "properties": {
        +        "height": {
        +          "exclusiveMinimum": 0,
        +          "type": "integer"
        +        },
        +        "width": {
        +          "exclusiveMinimum": 0,
        +          "type": "integer"
        +        },
        +        "x": {
        +          "type": "integer"
        +        },
        +        "y": {
        +          "type": "integer"
        +        }
        +      },
        +      "required": [
        +        "x",
        +        "y",
        +        "width",
        +        "height"
        +      ],
        +      "type": "object"
        +    },
        +    "center": {
        +      "$ref": "#/properties/slice/properties/bounds",
        +      "description": "Nine-slice centre region, relative to the slice. Only when set."
        +    },
        +    "name": {
        +      "type": "string"
        +    },
        +    "pivot": {
        +      "additionalProperties": false,
        +      "properties": {
        +        "x": {
        +          "type": "integer"
        +        },
        +        "y": {
        +          "type": "integer"
        +        }
        +      },
        +      "required": [
        +        "x",
        +        "y"
        +      ],
        +      "type": "object"
        +    }
        +  },
        +  "required": [
        +    "name",
        +    "bounds"
        +  ],
        +  "type": "object"
        +}
    • Changedtransform2 fields changed
      • addedInput schema / properties / diagonals
        Added value: +{
        +  "default": false,
        +  "description": "For 'outline'. Treat diagonal neighbours as touching too (8-neighbour instead of 4).",
        +  "type": "boolean"
        +}
      • addedInput schema / properties / side
        Added value: +{
        +  "default": "outside",
        +  "description": "For 'outline'. 'outside' paints transparent pixels touching opaque ones, growing the cel by one pixel each way. 'inside' recolours opaque pixels that touch transparency instead, same size in and out.",
        +  "enum": [
        +    "outside",
        +    "inside"
        +  ],
        +  "type": "string"
        +}
    • Changedvalidate1 field changed
      • addedInput schema / properties / expect
        Added value: +{
        +  "additionalProperties": false,
        +  "description": "The animation contract you already know. Runs independently of `checks`, whenever given: flags art outside a layer's expected frame ranges (or a missing cel inside one), and any frame where two named layers' opaque pixels overlap.",
        +  "properties": {
        +    "layerFrames": {
        +      "additionalProperties": {
        +        "items": {
        +          "items": [
        +            {
        +              "exclusiveMinimum": 0,
        +              "type": "integer"
        +            },
        +            {
        +              "exclusiveMinimum": 0,
        +              "type": "integer"
        +            }
        +          ],
        +          "maxItems": 2,
        +          "minItems": 2,
        +          "type": "array"
        +        },
        +        "type": "array"
        +      },
        +      "description": "Per layer, the [from,to] frame ranges (1-based, inclusive) it should hold ink in.",
        +      "type": "object"
        +    },
        +    "mustNotOverlap": {
        +      "description": "Layer name pairs whose opaque pixels must never intersect on the same frame.",
        +      "items": {
        +        "items": [
        +          {
        +            "type": "string"
        +          },
        +          {
        +            "type": "string"
        +          }
        +        ],
        +        "maxItems": 2,
        +        "minItems": 2,
        +        "type": "array"
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "type": "object"
        +}
  5. 17 tool updatesv0.1.6
    • Changedcel1 field changed
      • changedInput schema / properties / sprite / description
        Previous value: -"Sprite filename or id. Omit to use the sprite Aseprite has focused."New value: +"Which sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused."
    • Changeddraw1 field changed
      • changedInput schema / properties / sprite / description
        Previous value: -"Sprite filename or id. Omit to use the sprite Aseprite has focused."New value: +"Which sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused."
    • Changedexport2 fields changed
      • changedInput schema / properties / path / description
        Previous value: -"Output file path. For 'frames', a pattern like 'walk_{frame}.png'."New value: +"Output file path. For 'frames', a pattern containing {frame}, e.g. 'walk_{frame}.png' — Aseprite expands it into one file per frame, numbered from 0."
      • changedInput schema / properties / sprite / description
        Previous value: -"Sprite filename or id. Omit to use the sprite Aseprite has focused."New value: +"Which sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused."
    • Changedframe1 field changed
      • changedInput schema / properties / sprite / description
        Previous value: -"Sprite filename or id. Omit to use the sprite Aseprite has focused."New value: +"Which sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused."
    • Changedlayer10 fields changed
      • changedInput schema / properties / batch / description
        Previous value: -"Array of operation objects, each shaped like the single-op arguments. Applied in order."New value: +"Operations applied in order, in one undoable action. Same shape as the single-op arguments."
      • changedInput schema / properties / batch / items / additionalProperties
        Previous value: -{}New value: +false
      • addedInput schema / properties / batch / items / properties
        Added value: +{
        +  "blendMode": {
        +    "$ref": "#/properties/blendMode"
        +  },
        +  "editable": {
        +    "type": "boolean"
        +  },
        +  "index": {
        +    "description": "Target stack position; 0 is bottom.",
        +    "type": "integer"
        +  },
        +  "name": {
        +    "type": "string"
        +  },
        +  "names": {
        +    "description": "For 'merge' and bulk 'group'.",
        +    "items": {
        +      "type": "string"
        +    },
        +    "type": "array"
        +  },
        +  "newName": {
        +    "type": "string"
        +  },
        +  "op": {
        +    "$ref": "#/properties/op"
        +  },
        +  "opacity": {
        +    "maximum": 255,
        +    "minimum": 0,
        +    "type": "integer"
        +  },
        +  "parent": {
        +    "description": "Group layer to nest under.",
        +    "type": "string"
        +  },
        +  "visible": {
        +    "type": "boolean"
        +  }
        +}
      • addedInput schema / properties / batch / items / required
        Added value: +[
        +  "op"
        +]
      • addedInput schema / properties / batch / maxItems
        Added value: +128
      • changedInput schema / properties / sprite / description
        Previous value: -"Sprite filename or id. Omit to use the sprite Aseprite has focused."New value: +"Which sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused."
      • addedOutput schema / properties / layers / items / properties / cels
        Added value: +{
        +  "description": "1-based frames that have a cel on this layer.",
        +  "items": {
        +    "type": "integer"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / layers / items / properties / editable
        Added value: +{
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / layers / items / properties / isTilemap
        Added value: +{
        +  "type": "boolean"
        +}
      • changedOutput schema / properties / layers / items / required
        Previous value: -[
        -  "name",
        -  "index",
        -  "visible",
        -  "opacity",
        -  "blendMode",
        -  "isGroup"
        -]New value: +[
        +  "name",
        +  "index",
        +  "visible",
        +  "editable",
        +  "opacity",
        +  "blendMode",
        +  "isGroup",
        +  "isTilemap",
        +  "cels"
        +]
    • Changedlook3 fields changed
      • changedInput schema / properties / scale / description
        Previous value: -"Integer upscale for image ops. Omitted means auto."New value: +"Integer upscale for image ops, 1-128 (clamped so the output stays under ~2048px). Omit it — the automatic choice targets a ~1024px long edge, which is what a vision model can actually read."
      • changedInput schema / properties / scale / maximum
        Previous value: -16New value: +128
      • changedInput schema / properties / sprite / description
        Previous value: -"Sprite filename or id. Omit to use the sprite Aseprite has focused."New value: +"Which sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused."
    • Changedpalette2 fields changed
      • changedInput schema / properties / sprite / description
        Previous value: -"Sprite filename or id. Omit to use the sprite Aseprite has focused."New value: +"Which sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused."
      • addedOutput schema / properties / path
        Added value: +{
        +  "description": "Palette file read by op 'load'.",
        +  "type": "string"
        +}
    • Changedread_pixels1 field changed
      • changedInput schema / properties / sprite / description
        Previous value: -"Sprite filename or id. Omit to use the sprite Aseprite has focused."New value: +"Which sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused."
    • Changedrecolor1 field changed
      • changedInput schema / properties / sprite / description
        Previous value: -"Sprite filename or id. Omit to use the sprite Aseprite has focused."New value: +"Which sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused."
    • Changedreference1 field changed
      • changedInput schema / properties / sprite / description
        Previous value: -"Sprite filename or id. Omit to use the sprite Aseprite has focused."New value: +"Which sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused."
    • Changedselect1 field changed
      • changedInput schema / properties / sprite / description
        Previous value: -"Sprite filename or id. Omit to use the sprite Aseprite has focused."New value: +"Which sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused."
    • Changedsprite_info5 fields changed
      • changedInput schema / properties / sprite / description
        Previous value: -"Sprite filename or id. Omit to use the sprite Aseprite has focused."New value: +"Which sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused."
      • addedOutput schema / properties / id
        Added value: +{
        +  "description": "Stable id for this open document. Pass it back as '#<id>'.",
        +  "type": "integer"
        +}
      • removedOutput schema / properties / tags / items / properties / repeat
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "type": "integer"
        -    },
        -    {
        -      "type": "null"
        -    }
        -  ]
        -}
      • addedOutput schema / properties / tags / items / properties / repeats
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "integer"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "description": "Loop count; 0 means forever."
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "name",
        -  "width",
        -  "height",
        -  "colorMode",
        -  "frameCount",
        -  "layers",
        -  "frames",
        -  "tags"
        -]New value: +[
        +  "id",
        +  "name",
        +  "width",
        +  "height",
        +  "colorMode",
        +  "frameCount",
        +  "layers",
        +  "frames",
        +  "tags"
        +]
    • Changedsprite_manage7 fields changed
      • changedInput schema / properties / sprite / description
        Previous value: -"Sprite filename or id. Omit to use the sprite Aseprite has focused."New value: +"Which sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused."
      • addedOutput schema / properties / id
        Added value: +{
        +  "description": "Stable id of the affected document; pass it back as '#<id>'.",
        +  "type": "integer"
        +}
      • addedOutput schema / properties / sprites / items / properties / colorMode
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / sprites / items / properties / frames
        Added value: +{
        +  "type": "integer"
        +}
      • addedOutput schema / properties / sprites / items / properties / id
        Added value: +{
        +  "type": "integer"
        +}
      • addedOutput schema / properties / sprites / items / properties / layers
        Added value: +{
        +  "type": "integer"
        +}
      • changedOutput schema / properties / sprites / items / required
        Previous value: -[
        -  "name",
        -  "width",
        -  "height",
        -  "active",
        -  "modified"
        -]New value: +[
        +  "id",
        +  "name",
        +  "width",
        +  "height",
        +  "colorMode",
        +  "frames",
        +  "layers",
        +  "active",
        +  "modified"
        +]
    • Changedtag3 fields changed
      • removedInput schema / properties / repeat
        Removed value: -{
        -  "description": "0 means loop forever.",
        -  "minimum": 0,
        -  "type": "integer"
        -}
      • addedInput schema / properties / repeats
        Added value: +{
        +  "description": "Loop count; 0 means forever.",
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • changedInput schema / properties / sprite / description
        Previous value: -"Sprite filename or id. Omit to use the sprite Aseprite has focused."New value: +"Which sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused."
    • Changedtileset14 fields changed
      • changedInput schema / properties / sprite / description
        Previous value: -"Sprite filename or id. Omit to use the sprite Aseprite has focused."New value: +"Which sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused."
      • changedInput schema / properties / tolerance / description
        Previous value: -"For 'pack': treat near-identical tiles as one."New value: +"For 'pack': maximum per-channel difference at which two cells count as the same tile. 0 means exact, which is also much faster — anything above 0 compares every cell against every tile found so far."
      • changedInput schema / properties / tolerance / maximum
        Previous value: -64New value: +255
      • addedOutput schema / properties / cellCount
        Added value: +{
        +  "description": "For 'pack': grid cells examined.",
        +  "type": "integer"
        +}
      • addedOutput schema / properties / columns
        Added value: +{
        +  "type": "integer"
        +}
      • addedOutput schema / properties / format
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / layout
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / reusedExact
        Added value: +{
        +  "description": "For 'pack': cells that matched an existing tile exactly.",
        +  "type": "integer"
        +}
      • addedOutput schema / properties / reusedFuzzy
        Added value: +{
        +  "description": "For 'pack': cells merged by `tolerance`. A high number here with a low tolerance means the mockup has near-duplicate tiles worth cleaning up.",
        +  "type": "integer"
        +}
      • addedOutput schema / properties / rows
        Added value: +{
        +  "type": "integer"
        +}
      • addedOutput schema / properties / skipped
        Added value: +{
        +  "description": "For 'stamp': placements outside the grid or naming a tile that does not exist.",
        +  "type": "integer"
        +}
      • addedOutput schema / properties / sourceLayer
        Added value: +{
        +  "description": "For 'pack': the mockup layer, now hidden.",
        +  "type": "string"
        +}
      • addedOutput schema / properties / tileHeight
        Added value: +{
        +  "type": "integer"
        +}
      • addedOutput schema / properties / tileWidth
        Added value: +{
        +  "type": "integer"
        +}
    • Changedtransform4 fields changed
      • removedInput schema / properties / scope
        Removed value: -{
        -  "default": "selection",
        -  "description": "What the transform applies to. 'selection' falls back to the cel when nothing is selected.",
        -  "enum": [
        -    "selection",
        -    "cel",
        -    "layer",
        -    "sprite"
        -  ],
        -  "type": "string"
        -}
      • changedInput schema / properties / sprite / description
        Previous value: -"Sprite filename or id. Omit to use the sprite Aseprite has focused."New value: +"Which sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused."
      • removedOutput schema / properties / scope
        Removed value: -{
        -  "type": "string"
        -}
      • changedOutput schema / required
        Previous value: -[
        -  "sprite",
        -  "op",
        -  "scope",
        -  "pixelsChanged"
        -]New value: +[
        +  "sprite",
        +  "op",
        +  "pixelsChanged"
        +]
    • Changedvalidate1 field changed
      • changedInput schema / properties / sprite / description
        Previous value: -"Sprite filename or id. Omit to use the sprite Aseprite has focused."New value: +"Which sprite: its id as '#7' (what every result reports back, and the only unambiguous form — two unsaved documents are both called 'Sprite'), its filename, or its display name. Omit to use the sprite Aseprite has focused."
  6. 18 tool updatesv0.1.0
    • First observedcel
    • First observeddraw
    • First observedexport
    • First observedframe
    • First observedlayer
    • First observedlook
    • First observedpalette
    • First observedpreflight
    • First observedread_pixels
    • First observedrecolor
    • First observedreference
    • First observedselect
    • First observedsprite_info
    • First observedsprite_manage
    • First observedtag
    • First observedtileset
    • First observedtransform
    • First observedvalidate

TDQS

A4/5.0

Scored across 18 tools

Disambiguation4/5

Most tools map cleanly to a distinct resource or action (draw, transform, recolor, layer, frame, cel, tag). A few boundaries blur slightly — look vs read_pixels (visual read vs structured pixel read) and draw vs transform vs recolor could be confused by an agent without reading the descriptions — but the descriptions themselves draw sharp lines (e.g. transform explicitly notes there is no selection-scoped transform).

Naming Consistency3/5

There's a loose pattern (bare verbs for direct actions like draw/look/select/export, nouns for op-bundled resource managers like palette/layer/frame/cel/tileset), but it's not consistent — 'sprite_info', 'sprite_manage' and 'read_pixels' use snake_case while everything else is single-word, and 'preflight'/'validate' are neither verb_noun nor noun. Readable, but conventions are mixed.

Tool Count4/5

18 tools for a full Aseprite editing surface (drawing, palette, layers, frames, tags, cels, selection, transform, export, tilesets, validation) is heavy but each tool earns its place via the op-bundling pattern that keeps related operations under one name. Slightly above the ideal 3-15 range.

Completeness5/5

Covers the full lifecycle: connection check (preflight), read state (sprite_info, look, read_pixels), palette, drawing, selection, transform, layer/cel/frame/tag management, recolor, validation, reference importing and export/tilesets. There is no obvious dead end for pixel-art creation and game-ready output.

Maintenance

ActivityMaintained
ResponsivenessResponsive

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    C
    maintenance
    Enables AI assistants to create and edit pixel art and animated sprites in Aseprite through 104 tools covering drawing, layers, animation, palettes, tilemaps, exports, visual analysis, and raw Lua scripting.
    116
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    Enables AI assistants to draw live in Aseprite, creating and editing sprites, using drawing tools, managing layers and frames, and viewing results as images.
    2
    23
    7
    MIT
  • A
    license
    B
    quality
    B
    maintenance
    Enables an AI agent to create and edit pixel art directly in Aseprite — building sprites, layers, frames, animation tags and palettes, painting pixels and primitives, inspecting the rendered result, and exporting GIFs or sprite sheets. All work lands on real .aseprite files that can be opened and edited by hand.
    43
    1
    MIT