@pebbly/aseprite-ai-artist 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +170 -0
- package/LICENSE +21 -0
- package/README.md +169 -0
- package/agents/animation-director.md +65 -0
- package/agents/palette-smith.md +57 -0
- package/agents/pixel-critic.md +69 -0
- package/agents/rig-builder.md +46 -0
- package/bin/aseprite-ai-artist +6 -0
- package/bin/aseprite-ai-artist.mjs +44 -0
- package/dist/bridge/bridge.d.ts +52 -0
- package/dist/bridge/bridge.js +230 -0
- package/dist/bridge/bridge.js.map +1 -0
- package/dist/bridge/client.d.ts +54 -0
- package/dist/bridge/client.js +214 -0
- package/dist/bridge/client.js.map +1 -0
- package/dist/cli.d.ts +10 -0
- package/dist/cli.js +272 -0
- package/dist/cli.js.map +1 -0
- package/dist/extension.d.ts +21 -0
- package/dist/extension.js +65 -0
- package/dist/extension.js.map +1 -0
- package/dist/install.d.ts +35 -0
- package/dist/install.js +185 -0
- package/dist/install.js.map +1 -0
- package/dist/lib/color.d.ts +66 -0
- package/dist/lib/color.js +170 -0
- package/dist/lib/color.js.map +1 -0
- package/dist/lib/protocol.d.ts +78 -0
- package/dist/lib/protocol.js +72 -0
- package/dist/lib/protocol.js.map +1 -0
- package/dist/lib/render.d.ts +62 -0
- package/dist/lib/render.js +134 -0
- package/dist/lib/render.js.map +1 -0
- package/dist/lib/skills.d.ts +43 -0
- package/dist/lib/skills.js +112 -0
- package/dist/lib/skills.js.map +1 -0
- package/dist/lib/version.d.ts +4 -0
- package/dist/lib/version.js +25 -0
- package/dist/lib/version.js.map +1 -0
- package/dist/server.d.ts +15 -0
- package/dist/server.js +144 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/assets.d.ts +3 -0
- package/dist/tools/assets.js +181 -0
- package/dist/tools/assets.js.map +1 -0
- package/dist/tools/craft.d.ts +12 -0
- package/dist/tools/craft.js +143 -0
- package/dist/tools/craft.js.map +1 -0
- package/dist/tools/draw.d.ts +3 -0
- package/dist/tools/draw.js +256 -0
- package/dist/tools/draw.js.map +1 -0
- package/dist/tools/escape.d.ts +12 -0
- package/dist/tools/escape.js +49 -0
- package/dist/tools/escape.js.map +1 -0
- package/dist/tools/kit.d.ts +29 -0
- package/dist/tools/kit.js +85 -0
- package/dist/tools/kit.js.map +1 -0
- package/dist/tools/look.d.ts +7 -0
- package/dist/tools/look.js +215 -0
- package/dist/tools/look.js.map +1 -0
- package/dist/tools/palette.d.ts +3 -0
- package/dist/tools/palette.js +232 -0
- package/dist/tools/palette.js.map +1 -0
- package/dist/tools/session.d.ts +3 -0
- package/dist/tools/session.js +231 -0
- package/dist/tools/session.js.map +1 -0
- package/dist/tools/structure.d.ts +3 -0
- package/dist/tools/structure.js +218 -0
- package/dist/tools/structure.js.map +1 -0
- package/examples/mcp-configs/claude-code.json +9 -0
- package/examples/mcp-configs/codex.toml +13 -0
- package/examples/mcp-configs/cursor.json +9 -0
- package/examples/mcp-configs/gemini-cli.json +9 -0
- package/examples/mcp-configs/vscode.json +10 -0
- package/extension/ai-artist.lua +3291 -0
- package/extension/package.json +14 -0
- package/hooks/hooks.json +25 -0
- package/hooks/look-nudge.mjs +46 -0
- package/hooks/session-status.mjs +47 -0
- package/knowledge/palettes.json +60 -0
- package/package.json +69 -0
- package/rules/00-core-principles.md +57 -0
- package/rules/01-palette-and-color.md +51 -0
- package/rules/02-shading-and-light.md +56 -0
- package/rules/03-silhouette-and-form.md +60 -0
- package/rules/04-outlines-and-edges.md +46 -0
- package/rules/05-animation.md +62 -0
- package/rules/06-layers-and-rigging.md +52 -0
- package/rules/07-review-checklist.md +51 -0
- package/rules/README.md +16 -0
- package/skills/README.md +29 -0
- package/skills/pixel-animate/SKILL.md +91 -0
- package/skills/pixel-brief/SKILL.md +61 -0
- package/skills/pixel-draw/SKILL.md +93 -0
- package/skills/pixel-export/SKILL.md +80 -0
- package/skills/pixel-fix/SKILL.md +91 -0
- package/skills/pixel-new/SKILL.md +70 -0
- package/skills/pixel-palette/SKILL.md +80 -0
- package/skills/pixel-review/SKILL.md +81 -0
- package/skills/pixel-rig/SKILL.md +76 -0
- package/skills/pixel-shade/SKILL.md +77 -0
- package/skills/pixel-tileset/SKILL.md +104 -0
- package/templates/AGENTS.section.md +33 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. Format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning is
|
|
5
|
+
[semver](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [0.1.0] — 2026-09-08
|
|
8
|
+
|
|
9
|
+
First release. Includes every fix from the pre-release audit below.
|
|
10
|
+
|
|
11
|
+
### Fixed — 2026-09-08 multi-expert audit
|
|
12
|
+
|
|
13
|
+
Six reviewers plus two live Codex CLI runs; every item below was reproduced
|
|
14
|
+
before it was fixed and has a regression test that fails without the fix.
|
|
15
|
+
|
|
16
|
+
- **Closed polylines lost their closing edge.** The outline loop stopped one
|
|
17
|
+
short of the wrap the fill loop already did, so a "closed" triangle shipped
|
|
18
|
+
with one side missing and the call reported success.
|
|
19
|
+
- **Thick lines were silently clipped.** The bounding box ignored `thickness`,
|
|
20
|
+
so the cel was grown to the endpoints only and the rest of the brush was
|
|
21
|
+
dropped — a 7×7 stamp landing one pixel, reported as success.
|
|
22
|
+
- **Fuzzy tile packing merged unrelated tiles on indexed sprites.** The distance
|
|
23
|
+
function read RGB channels out of palette indices. It now refuses non-RGB
|
|
24
|
+
sprites instead of guessing.
|
|
25
|
+
- **`transform` op `rotate` with `angle: 0` reported a full-canvas change** for
|
|
26
|
+
an operation that touched nothing, breaking idempotency checks.
|
|
27
|
+
- **`validate` never ran its `outline` or `banding` checks.** Both were in the
|
|
28
|
+
schema and in the handler's own default set, and no branch read either — the
|
|
29
|
+
tool answered "Clean." without running them. Both are now implemented.
|
|
30
|
+
- **`cel` op `list` always reported `linked: false`**, and `frame` op
|
|
31
|
+
`duplicate` with `linkCels` never actually linked (it called a command that
|
|
32
|
+
does not exist in Aseprite 1.3).
|
|
33
|
+
- **`draw` op `gradient` ignored `dither`**, and `diagonal` and `radial`
|
|
34
|
+
silently rendered as `vertical`. All four directions and the dither flag now
|
|
35
|
+
work.
|
|
36
|
+
- **`transform`'s `scope` parameter did nothing.** Removed rather than
|
|
37
|
+
half-implemented; transforms act on one cel, and the docs now say so.
|
|
38
|
+
- **`tag`'s `repeat` was ignored.** The schema said `repeat` on input and
|
|
39
|
+
output while Aseprite's property is `repeats`, so loop counts were silently
|
|
40
|
+
dropped. Renamed to `repeats` on both sides.
|
|
41
|
+
- **`sprite_manage` op `new` returned an identifier that could not be used.** It
|
|
42
|
+
answered `"untitled"` while the sprite was called `"Sprite"`, so feeding a
|
|
43
|
+
tool's own output into the next call failed. Every result now carries a stable
|
|
44
|
+
`id`, and two unsaved documents are no longer ambiguous.
|
|
45
|
+
- **Previews were capped far below a readable size.** The upscale factor was
|
|
46
|
+
capped at 16, rendering a 16px sprite at 256px — the case where upscaling
|
|
47
|
+
matters most. The bound is now on output size (~2048px), so small sprites
|
|
48
|
+
reach the documented ~1024px.
|
|
49
|
+
- **`selectionOnly` silently widened to the whole cel** when nothing was
|
|
50
|
+
selected. It now refuses.
|
|
51
|
+
- **Text grids collided past 71 colours**, quietly misreporting which colour was
|
|
52
|
+
where in the tool used to verify edits. It now refuses.
|
|
53
|
+
- **Config writes were not atomic.** A partial write to `~/.claude.json` (100KB+
|
|
54
|
+
of Claude Code's own state) would have broken the user's whole setup. Writes
|
|
55
|
+
go through a temp file and a rename.
|
|
56
|
+
- **A dropped bridge left in-flight calls waiting out their 20s timeout**, which
|
|
57
|
+
reads to an agent as "slow" rather than "disconnected". They now fail
|
|
58
|
+
immediately, and a bridge that dies after being spawned can be respawned.
|
|
59
|
+
- **Non-`EADDRINUSE` bind failures were reported as "another bridge owns this
|
|
60
|
+
port"**, sending users after a process that does not exist.
|
|
61
|
+
- **`run_lua` could leave Lua's global `print` hijacked** for the rest of the
|
|
62
|
+
Aseprite session if the transaction threw.
|
|
63
|
+
- **The Claude Code plugin could not start on Windows.** Its MCP `command`
|
|
64
|
+
pointed at a bash script, and Windows does not interpret `#!`. The launcher is
|
|
65
|
+
now Node.
|
|
66
|
+
- **`package.json` claimed the 2026-07-28 spec**, contradicting ADR-0004's
|
|
67
|
+
decision to target 2025-11-25. `engines.node` also promised 20.10 while the
|
|
68
|
+
test script needs 22.6.
|
|
69
|
+
|
|
70
|
+
### Fixed — audit round two
|
|
71
|
+
|
|
72
|
+
The six reviewers' full reports arrived after the first round of fixes and
|
|
73
|
+
carried a further fourteen items, all closed here.
|
|
74
|
+
|
|
75
|
+
- **`snapToPalette` promised a transparency guard it did not have.** A fully
|
|
76
|
+
transparent input now snaps to itself instead of to the nearest opaque colour,
|
|
77
|
+
which would have painted over deliberate holes.
|
|
78
|
+
- **`refused` and `too_large` error codes were declared and never raised**, so
|
|
79
|
+
every "you asked for something out of bounds" refusal arrived as a generic
|
|
80
|
+
`aseprite_error` and an agent could not tell it apart from an internal
|
|
81
|
+
failure. Both are now used at the real refusal sites.
|
|
82
|
+
- **The bridge had no frame-size or client cap** on an unauthenticated socket —
|
|
83
|
+
`ws` defaults to 100 MiB per frame. Now 16 MiB and 64 clients.
|
|
84
|
+
- **The Codex TOML editor matched its block header anywhere in the file**,
|
|
85
|
+
including inside a comment or a string. The match is now anchored to a line.
|
|
86
|
+
- **`layer`'s `batch` was the least-typed path in the surface** while being the
|
|
87
|
+
documented way to build a rig: `z.record(z.unknown())` accepted an opacity of
|
|
88
|
+
`"hello"` and Lua assigned it. It is now the same typed object as the
|
|
89
|
+
single-op form.
|
|
90
|
+
- **The Lua reconnect state machine had no per-socket identity check**, so an
|
|
91
|
+
event from a superseded socket could clobber the new connection's state. The
|
|
92
|
+
TypeScript side already guarded the mirror-image race.
|
|
93
|
+
- **A rejected `close()`/`stop()` left a floating promise rejection** in the
|
|
94
|
+
CLI's shutdown paths.
|
|
95
|
+
- **Four output schemas under-documented what Lua actually returns** —
|
|
96
|
+
`palette` op `load`'s `path`, `sprite_manage` op `list`'s colour mode and
|
|
97
|
+
counts, and the layer entries' `editable`/`isTilemap`/`cels`.
|
|
98
|
+
- **Lua test cleanup was not exception-safe.** One failing assertion left the
|
|
99
|
+
active document pointing at a closed scratch sprite and cascaded into every
|
|
100
|
+
later check — the failure this project already hit once. All sixteen scratch
|
|
101
|
+
blocks now go through a helper that always closes and always restores.
|
|
102
|
+
- **The cross-language CIELAB claim was not actually enforced.** ADR-0001 and
|
|
103
|
+
ARCHITECTURE.md both said the two ports are held to the same expectations;
|
|
104
|
+
only the TypeScript side had numeric fixtures. The Lua side now pins the same
|
|
105
|
+
white/black L\*, ΔE bounds, grey-snaps-to-grey case and hue-shift direction.
|
|
106
|
+
- **`export` op `frames` had no coverage at any level.** Verified it does write
|
|
107
|
+
one file per frame (numbered from 0, now documented) and locked that in.
|
|
108
|
+
- **The e2e test used the real default ports**, so running it on a machine with
|
|
109
|
+
a live Aseprite session could steal that session — the bridge accepts the last
|
|
110
|
+
plugin to connect. Ports are now overridable, the risk is documented, and the
|
|
111
|
+
test removes the files it writes.
|
|
112
|
+
- **`SECURITY.md` overstated the `allowLua` gate.** It gates the tool surface
|
|
113
|
+
your agent sees, not the capability: the extension implements `lua.run`
|
|
114
|
+
whenever installed and the bridge has no authentication. Now stated plainly,
|
|
115
|
+
alongside the filesystem reach of every `path` argument.
|
|
116
|
+
|
|
117
|
+
### Added
|
|
118
|
+
|
|
119
|
+
- `SECURITY.md` — threat model, the localhost bridge's real exposure, what
|
|
120
|
+
`install` touches, and the `run_lua` gate.
|
|
121
|
+
|
|
122
|
+
### Added — initial implementation
|
|
123
|
+
|
|
124
|
+
- **MCP server** (`serve`) speaking protocol `2025-11-25`, with 18 tools grouped
|
|
125
|
+
by noun. Every tool declares an `outputSchema` and returns
|
|
126
|
+
`structuredContent`. See [ADR-0003](docs/adr/0003-compact-tool-surface.md) and
|
|
127
|
+
[ADR-0004](docs/adr/0004-protocol-version.md).
|
|
128
|
+
- **Standalone WebSocket bridge** (`bridge`), singleton by port ownership,
|
|
129
|
+
outliving MCP server restarts and serving several agent windows at once. See
|
|
130
|
+
[ADR-0002](docs/adr/0002-standalone-bridge.md).
|
|
131
|
+
- **Aseprite extension** (`extension/ai-artist.lua`) implementing 23 commands
|
|
132
|
+
against Aseprite 1.3's Lua API, with every mutation inside a transaction so
|
|
133
|
+
one Ctrl+Z undoes one agent action.
|
|
134
|
+
- **`look`** — upscaled previews, exact one-glyph-per-pixel text grids,
|
|
135
|
+
animation filmstrips and pixel-level frame diffs.
|
|
136
|
+
- **Palette discipline** — CIELAB ΔE snapping on by default in `draw` and
|
|
137
|
+
`recolor`, with a report of every colour moved and how far.
|
|
138
|
+
- **Hue-shifted shading** (`recolor` op `shade`, `palette` op `ramp`): shadows
|
|
139
|
+
cool, highlights warm.
|
|
140
|
+
- **`validate`** — a lint pass with located findings for palette sprawl, stray
|
|
141
|
+
pixels, semi-transparent pixels, untagged animation and uniform timing.
|
|
142
|
+
- **Skills over MCP** — 11 `/pixel-*` workflows and an 8-chapter pixel-art
|
|
143
|
+
rulebook, served as `skill://` and `rules://` resources and as MCP prompts, so
|
|
144
|
+
Codex, Gemini CLI and Cursor get the same discipline as Claude Code.
|
|
145
|
+
- **Cross-client installer** (`install`) for Claude Code, Codex, Gemini CLI,
|
|
146
|
+
Cursor, VS Code and Windsurf, with backups and `--dry-run`, plus an
|
|
147
|
+
`AGENTS.md` writer.
|
|
148
|
+
- **`doctor`** — a diagnosis command that reports each link in the chain.
|
|
149
|
+
- **Claude Code plugin** with four specialist subagents (pixel-critic,
|
|
150
|
+
palette-smith, rig-builder, animation-director) and two hooks.
|
|
151
|
+
- **Tests** — TypeScript unit and integration tests for colour, rendering, the
|
|
152
|
+
bridge transport and the MCP surface; a headless Lua harness running the real
|
|
153
|
+
extension handlers inside `aseprite -b` against a real sprite.
|
|
154
|
+
|
|
155
|
+
- **Tilesets** — `pack` turns a hand-painted mockup into a deduplicated tileset
|
|
156
|
+
plus a tilemap that reconstructs it pixel for pixel (with an optional
|
|
157
|
+
`tolerance` for merging near-identical cells), and `export` writes Tiled
|
|
158
|
+
(`.tsj` tileset plus a `.tmj` map that uses it), Godot 4 (`.tres`) or JSON,
|
|
159
|
+
each beside a packed PNG. `layout: "blob47"` adds a Tiled wangset whose 47
|
|
160
|
+
canonical masks are computed, not hardcoded.
|
|
161
|
+
|
|
162
|
+
### Known limits
|
|
163
|
+
|
|
164
|
+
- `blob47` export assumes the tileset is authored in canonical blob-mask order
|
|
165
|
+
and refuses an incomplete set rather than writing a wangset that would
|
|
166
|
+
autotile wrongly.
|
|
167
|
+
- Godot export targets Godot 4 (`TileSetAtlasSource`); Godot 3 is not emitted.
|
|
168
|
+
- Re-establishing a *dropped* Aseprite connection can wait until the Aseprite
|
|
169
|
+
window is focused once. A live connection keeps working unfocused.
|
|
170
|
+
- `look` op `ascii` refuses above 64×64; pass a region.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 with-pebbly
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Aseprite AI Artist
|
|
2
|
+
|
|
3
|
+
Let a coding agent draw pixel art in your **open Aseprite window** — not in a
|
|
4
|
+
copy, not on disk, in the document you are looking at.
|
|
5
|
+
|
|
6
|
+
One MCP server, one Aseprite extension, and a pixel-art rulebook the agent
|
|
7
|
+
actually has to follow. Works with **Claude Code, Codex CLI, Gemini CLI, Cursor,
|
|
8
|
+
VS Code and Windsurf** from the same one-line config.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npx @pebbly/aseprite-ai-artist install --all --agents
|
|
12
|
+
npx @pebbly/aseprite-ai-artist install-extension
|
|
13
|
+
# restart Aseprite, restart your agent
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
> **Requires Aseprite 1.3+** and Node 22.6+.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## What it does
|
|
21
|
+
|
|
22
|
+
> *"Draw me a 32×32 knight in the PICO-8 palette, then a 4-frame idle."*
|
|
23
|
+
|
|
24
|
+
The agent inspects the document, picks a palette, blocks in a silhouette,
|
|
25
|
+
**looks at what it drew**, shades with proper hue shifting, rigs the character
|
|
26
|
+
onto layers, animates, tags the cycle, validates, and tells you what it
|
|
27
|
+
compromised on. In your window, undoable, one Ctrl+Z per edit.
|
|
28
|
+
|
|
29
|
+
## What makes it different
|
|
30
|
+
|
|
31
|
+
**It works everywhere, not just in Claude Code.** Most Aseprite MCP projects put
|
|
32
|
+
their craft knowledge in a Claude Code plugin. Codex, Gemini and Cursor then get
|
|
33
|
+
raw tools and none of the discipline — which is most of what separates a sprite
|
|
34
|
+
from coloured noise. Here the rules and workflows are served over MCP as
|
|
35
|
+
`rules://` and `skill://` resources *and* as MCP prompts, so every client gets
|
|
36
|
+
them from one source of truth.
|
|
37
|
+
|
|
38
|
+
**Eighteen tools, not ninety.** Every tool schema sits in the model's context on
|
|
39
|
+
every turn, whether or not you are drawing. Grouping by noun with an `op` enum,
|
|
40
|
+
plus batch arrays, covers the same ground at roughly a sixth of the cost — and
|
|
41
|
+
makes batching the default, so one `draw` call is one undo step for you.
|
|
42
|
+
|
|
43
|
+
**One config line on every OS.** No compiled binary, no per-platform path, no
|
|
44
|
+
native dependencies. `npx` and go.
|
|
45
|
+
|
|
46
|
+
**It has to look at its own work.** `look` gives the agent an upscaled preview,
|
|
47
|
+
an exact one-glyph-per-pixel text grid, a filmstrip of every animation frame,
|
|
48
|
+
and a pixel-level diff between frames. `validate` then checks the sprite
|
|
49
|
+
mechanically before anything gets called finished.
|
|
50
|
+
|
|
51
|
+
**It cannot quietly wreck your file.** When Aseprite is not attached, every tool
|
|
52
|
+
refuses immediately with `doNotFallBackToDisk` rather than timing out — because
|
|
53
|
+
an agent that "recovers" by editing the `.aseprite` file makes changes you never
|
|
54
|
+
see and your next save overwrites.
|
|
55
|
+
|
|
56
|
+
There is [a whole page](docs/RESEARCH.md) on the other projects in this space,
|
|
57
|
+
what was taken from them, and where they are still better.
|
|
58
|
+
|
|
59
|
+
## Install
|
|
60
|
+
|
|
61
|
+
See **[docs/INSTALL.md](docs/INSTALL.md)** for per-client detail and
|
|
62
|
+
troubleshooting.
|
|
63
|
+
|
|
64
|
+
### Claude Code
|
|
65
|
+
|
|
66
|
+
The plugin brings the `/pixel-*` skills, the specialist subagents and the hooks,
|
|
67
|
+
not just the tools:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
/plugin marketplace add with-pebbly/aseprite-ai-artist
|
|
71
|
+
/plugin install aseprite-ai-artist
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Everything else
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npx @pebbly/aseprite-ai-artist install codex # ~/.codex/config.toml
|
|
78
|
+
npx @pebbly/aseprite-ai-artist install gemini # ~/.gemini/settings.json
|
|
79
|
+
npx @pebbly/aseprite-ai-artist install cursor # ~/.cursor/mcp.json
|
|
80
|
+
npx @pebbly/aseprite-ai-artist install --all # all of the above
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Existing config is backed up first. `--dry-run` shows the change without making
|
|
84
|
+
it. `--project` writes into the repository instead of your home directory.
|
|
85
|
+
|
|
86
|
+
### Check it
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npx @pebbly/aseprite-ai-artist doctor
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## The tools
|
|
93
|
+
|
|
94
|
+
Eighteen, grouped by noun. Full reference in **[docs/TOOLS.md](docs/TOOLS.md)**.
|
|
95
|
+
|
|
96
|
+
| | |
|
|
97
|
+
|---|---|
|
|
98
|
+
| **Session** | `preflight` · `sprite_info` · `sprite_manage` |
|
|
99
|
+
| **Looking** | `look` · `read_pixels` |
|
|
100
|
+
| **Drawing** | `draw` · `select` · `transform` · `recolor` |
|
|
101
|
+
| **Structure** | `layer` · `frame` · `tag` · `cel` |
|
|
102
|
+
| **Colour & quality** | `palette` · `validate` |
|
|
103
|
+
| **Assets** | `reference` · `export` · `tileset` |
|
|
104
|
+
|
|
105
|
+
Plus `run_lua` as an escape hatch, off by default.
|
|
106
|
+
|
|
107
|
+
## The skills
|
|
108
|
+
|
|
109
|
+
Workflows the agent follows, served to every client:
|
|
110
|
+
|
|
111
|
+
`pixel-brief` · `pixel-new` · `pixel-palette` · `pixel-draw` · `pixel-shade` ·
|
|
112
|
+
`pixel-rig` · `pixel-animate` · `pixel-tileset` · `pixel-review` · `pixel-fix` ·
|
|
113
|
+
`pixel-export`
|
|
114
|
+
|
|
115
|
+
And in Claude Code, four specialist subagents: **pixel-critic** (visual QA),
|
|
116
|
+
**palette-smith** (colour), **rig-builder** (layer rigs), **animation-director**
|
|
117
|
+
(cycle planning).
|
|
118
|
+
|
|
119
|
+
## The rules
|
|
120
|
+
|
|
121
|
+
The craft is encoded in [`rules/`](rules/) and served as `rules://` resources —
|
|
122
|
+
palette discipline, hue-shifted shading, silhouette and proportion, outlines and
|
|
123
|
+
edges, animation timing, layer rigging, and a review checklist. Skills reference
|
|
124
|
+
rules rather than restating them, so a rule has exactly one place to be wrong.
|
|
125
|
+
|
|
126
|
+
The mechanical parts are enforced by the tools themselves: `draw` and `recolor`
|
|
127
|
+
snap to the palette by perceptual distance and report every colour they moved,
|
|
128
|
+
so an agent that never reads a word of the rulebook still cannot casually widen
|
|
129
|
+
your palette.
|
|
130
|
+
|
|
131
|
+
## How it works
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
your agent ──stdio/MCP──▶ server ──ws:9932──▶ bridge ──ws:9931──▶ Aseprite
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Aseprite's Lua WebSocket is a client only, so the bridge holds the listening
|
|
138
|
+
socket. It runs as its own singleton process so that restarting the MCP server —
|
|
139
|
+
which agent hosts do freely — does not drop your Aseprite connection, and so a
|
|
140
|
+
second agent window can attach without stealing the first one's replies.
|
|
141
|
+
|
|
142
|
+
Details in **[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)** and the
|
|
143
|
+
[ADRs](docs/adr/).
|
|
144
|
+
|
|
145
|
+
## Development
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
npm install
|
|
149
|
+
npm run build
|
|
150
|
+
npm test # TypeScript: colour, render, bridge, MCP surface
|
|
151
|
+
npm run test:extension # Lua handlers, headless, against a real sprite
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
The Lua tests run the real command handlers inside `aseprite -b` against a real
|
|
155
|
+
sprite — the only way to prove that side works without a human clicking.
|
|
156
|
+
|
|
157
|
+
## Security
|
|
158
|
+
|
|
159
|
+
The bridge binds `127.0.0.1` only, on both ports. There is no remote surface and
|
|
160
|
+
no authentication because there is nothing remote to authenticate. `run_lua` is
|
|
161
|
+
arbitrary code execution inside the app holding your unsaved work, and is off
|
|
162
|
+
unless you turn it on.
|
|
163
|
+
|
|
164
|
+
## Licence
|
|
165
|
+
|
|
166
|
+
MIT. See [LICENSE](LICENSE).
|
|
167
|
+
|
|
168
|
+
Aseprite is a trademark of Igara Studio S.A. This project is not affiliated with
|
|
169
|
+
or endorsed by them.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: animation-director
|
|
3
|
+
description: Animation planning specialist. Use when planning idle, walk, run, attack or other cycles — it designs key poses, breakdowns, millisecond timing and tags before any frame is drawn, so the motion reads and volume stays consistent. Plans on its own; executes frames on request.
|
|
4
|
+
model: sonnet
|
|
5
|
+
effort: medium
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You plan motion before anyone draws it.
|
|
9
|
+
|
|
10
|
+
Animating by nudging pixels frame to frame accumulates drift; by frame six the
|
|
11
|
+
character is a different size. Keys first, always.
|
|
12
|
+
|
|
13
|
+
## Procedure
|
|
14
|
+
|
|
15
|
+
1. `preflight`, `sprite_info`. Check the sprite is rigged — if limbs are baked
|
|
16
|
+
into one layer, say so and hand off to `rig-builder` first.
|
|
17
|
+
2. Read `rules://05-animation`.
|
|
18
|
+
3. Plan the cycle in writing. Get agreement. Then build.
|
|
19
|
+
4. Review with `look` op `filmstrip` — a vision model reads only the first frame
|
|
20
|
+
of a GIF, so this is the only way to actually see motion.
|
|
21
|
+
|
|
22
|
+
## What a plan contains
|
|
23
|
+
|
|
24
|
+
- **Frame count**, and why that number.
|
|
25
|
+
- **Key poses** — what each one shows, in words specific enough to draw from.
|
|
26
|
+
- **Breakdowns** — which in-betweens are needed and which are not.
|
|
27
|
+
- **Timing in milliseconds per frame**, with contacts held longer than passes.
|
|
28
|
+
- **Tag name and direction.**
|
|
29
|
+
|
|
30
|
+
## Cycle templates
|
|
31
|
+
|
|
32
|
+
**Walk (8)**: contact, down, pass, up, then mirrored. Body lowest at down,
|
|
33
|
+
highest at pass. The pass frame is the one people forget, and its absence is why
|
|
34
|
+
a walk looks like sliding.
|
|
35
|
+
|
|
36
|
+
**Idle (2–4)**: a breath. Chest rises a pixel; shoulders follow one frame later.
|
|
37
|
+
Slow — 200–400ms.
|
|
38
|
+
|
|
39
|
+
**Attack (4–6)**: anticipation (wind back), strike (fast, 40–60ms), hold
|
|
40
|
+
(150–250ms), recover. Without anticipation it reads weightless.
|
|
41
|
+
|
|
42
|
+
**Run (6–8)**: like a walk but with a airborne frame where neither foot is down,
|
|
43
|
+
and more forward lean.
|
|
44
|
+
|
|
45
|
+
## Output
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
WALK — 8 frames, tag "walk", forward.
|
|
49
|
+
|
|
50
|
+
1 contact L 150ms widest stance, front heel down, arms opposed
|
|
51
|
+
2 down 100ms body 1px lower, front knee bent
|
|
52
|
+
3 pass 80ms body 1px HIGHER than contact, legs together
|
|
53
|
+
4 up 100ms pushing off, rear heel lifting
|
|
54
|
+
5-8 mirrored
|
|
55
|
+
|
|
56
|
+
Notes: body height must vary by exactly 2px across the cycle (contact 0,
|
|
57
|
+
down -1, pass +1). Arms swing opposite the legs. Cape follows the torso one
|
|
58
|
+
frame late.
|
|
59
|
+
|
|
60
|
+
Risks: at 32px the knee bend in frame 2 is 2 pixels; if it reads as noise,
|
|
61
|
+
drop it and carry the weight in the body height alone.
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Say what you are unsure about. A plan that hides its risks produces eight frames
|
|
65
|
+
of work that has to be redone.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: palette-smith
|
|
3
|
+
description: Colour and palette specialist. Use when the user needs a cohesive palette, hue-shifted ramps, a specific retro preset, a palette sampled from reference art, or a muddy palette cleaned up. Proposes colours and explains the choice; applies them only when asked.
|
|
4
|
+
model: sonnet
|
|
5
|
+
effort: medium
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You choose colours for pixel art, and you can say why.
|
|
9
|
+
|
|
10
|
+
## Procedure
|
|
11
|
+
|
|
12
|
+
1. `preflight`, then `sprite_info` — the existing palette and colour mode are
|
|
13
|
+
usually half the answer.
|
|
14
|
+
2. Read `rules://01-palette-and-color` and `rules://02-shading-and-light`.
|
|
15
|
+
3. `palette` op `analyze` if a palette already exists. Report before proposing.
|
|
16
|
+
4. Propose, with reasoning. Apply only when the user says yes, or when they
|
|
17
|
+
clearly asked you to just do it.
|
|
18
|
+
|
|
19
|
+
## How you choose
|
|
20
|
+
|
|
21
|
+
- **The user named a game or palette** → use it. `palette op="preset"`,
|
|
22
|
+
`op="load"`, or `reference op="sample_palette"` on a screenshot.
|
|
23
|
+
- **They described an era** → PICO-8 for "8-bit", gameboy for "monochrome",
|
|
24
|
+
a hand-built 12–16 for "NES-like".
|
|
25
|
+
- **The sprite joins a project** → match an existing asset exactly. Consistency
|
|
26
|
+
beats a nicer palette.
|
|
27
|
+
- **Nothing said** → PICO-8.
|
|
28
|
+
|
|
29
|
+
## How you build ramps
|
|
30
|
+
|
|
31
|
+
`palette op="ramp"` — hue-shifted by construction. 3–5 steps per material.
|
|
32
|
+
Reuse the darkest step across materials; shared darks tie a sprite together and
|
|
33
|
+
cost no slots.
|
|
34
|
+
|
|
35
|
+
## How you clean up a sprawled palette
|
|
36
|
+
|
|
37
|
+
`palette op="analyze"` → merge near-duplicates (ΔE < 3 is the same colour to a
|
|
38
|
+
viewer) → `recolor op="snap"` → look at the result. The risk is merging two
|
|
39
|
+
colours that were doing different jobs, so check for form that has gone flat.
|
|
40
|
+
|
|
41
|
+
## Output
|
|
42
|
+
|
|
43
|
+
Show the palette as hex with names for what each is for, and say what you would
|
|
44
|
+
not do:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
PICO-8, 16 colours. Knight uses 7 of them:
|
|
48
|
+
|
|
49
|
+
plate #5f574f → #c2c3c7 → #fff1e8 (3-step, cool)
|
|
50
|
+
leather #ab5236 → #ffa300 (2-step, warm)
|
|
51
|
+
plume #ff004d (accent, 1 colour, no ramp)
|
|
52
|
+
outline #1d2b53 (shared dark, not black)
|
|
53
|
+
|
|
54
|
+
Not proposing a skin ramp: the visor is closed, so no skin is visible. If you
|
|
55
|
+
want the visor open, that needs 2 more slots and PICO-8 has no good midtone
|
|
56
|
+
for skin — I'd suggest a custom palette instead.
|
|
57
|
+
```
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pixel-critic
|
|
3
|
+
description: Visual QA for pixel art. Use PROACTIVELY before telling a user a sprite is finished, and whenever they ask "is this any good", "why does this look off" or "review this". Inspects the live sprite and returns a scored, located critique against the project rulebook. Read-only — it never edits the sprite.
|
|
4
|
+
model: sonnet
|
|
5
|
+
effort: medium
|
|
6
|
+
tools:
|
|
7
|
+
- Read
|
|
8
|
+
- Glob
|
|
9
|
+
- Grep
|
|
10
|
+
disallowedTools:
|
|
11
|
+
- Write
|
|
12
|
+
- Edit
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
You are a pixel-art critic. You look at sprites and say precisely what is wrong
|
|
16
|
+
with them, with coordinates.
|
|
17
|
+
|
|
18
|
+
You do not edit. Someone else fixes what you find; conflating the two produces
|
|
19
|
+
critiques written to be easy to fix rather than true.
|
|
20
|
+
|
|
21
|
+
## Procedure
|
|
22
|
+
|
|
23
|
+
1. `preflight`. Stop if not ready.
|
|
24
|
+
2. `sprite_info` — size, palette, layers, frames, tags.
|
|
25
|
+
3. `validate` — the mechanical findings.
|
|
26
|
+
4. `look` op `preview` — the overall read.
|
|
27
|
+
5. `look` op `ascii` on anything that felt wrong but you could not name.
|
|
28
|
+
6. `look` op `filmstrip` if there is more than one frame.
|
|
29
|
+
7. Read the rules you are judging against: `rules://07-review-checklist` and
|
|
30
|
+
whichever specific rule a finding touches.
|
|
31
|
+
|
|
32
|
+
## What you are looking for
|
|
33
|
+
|
|
34
|
+
In descending order of how much it costs the user:
|
|
35
|
+
|
|
36
|
+
1. **Silhouette does not read.** Nothing else matters if this fails.
|
|
37
|
+
2. **Value contrast too low.** Would it survive desaturation?
|
|
38
|
+
3. **Flat shading.** Shadows that are the same hue, darker.
|
|
39
|
+
4. **Palette sprawl.** Near-duplicate colours; off-palette pixels.
|
|
40
|
+
5. **Semi-transparent pixels.** From soft brushes or non-integer resizes.
|
|
41
|
+
6. **Strays and noise.** Isolated pixels, detail that is just dirt.
|
|
42
|
+
7. **Inconsistent lines.** Uneven runs, doubled pixels, jaggies.
|
|
43
|
+
8. **Inconsistent light.** Direction that moves across the sprite or frames.
|
|
44
|
+
9. **Animation: volume drift, height drift, uniform timing, no anticipation.**
|
|
45
|
+
|
|
46
|
+
## Output
|
|
47
|
+
|
|
48
|
+
A score out of 10, then findings, then the single highest-value fix.
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
7/10 — reads well at 1×, held back by flat shading.
|
|
52
|
+
|
|
53
|
+
BLOCKING
|
|
54
|
+
· Shadow ramp is pure luminance: #c04030 → #802b20 has the same hue.
|
|
55
|
+
Shadows should cool. rules://02-shading-and-light. (torso, whole cel)
|
|
56
|
+
|
|
57
|
+
WORTH FIXING
|
|
58
|
+
· 3 stray pixels with no neighbour at (4,19), (5,19), (17,3) — reads as dirt.
|
|
59
|
+
· Palette has #7e2553 and #7d2452 (ΔE 1.1). One of them is doing no work.
|
|
60
|
+
|
|
61
|
+
NOTE
|
|
62
|
+
· The far arm fuses with the torso outline at (9,14)-(9,16). A one-pixel gap
|
|
63
|
+
would separate them.
|
|
64
|
+
|
|
65
|
+
FIRST FIX: rebuild the torso ramp with `palette op="ramp"` and re-snap.
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Never say "looks good". If you genuinely find nothing, say what you checked and
|
|
69
|
+
what the sprite does well — that is information; "looks good" is not.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rig-builder
|
|
3
|
+
description: Sprite rigging specialist. Use when starting a character that will be animated, or when limbs are baked into one layer and need splitting into animatable parts. Plans the layer rig and builds it on request.
|
|
4
|
+
model: sonnet
|
|
5
|
+
effort: medium
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You split characters into animatable parts.
|
|
9
|
+
|
|
10
|
+
Pixels baked into one layer cannot be animated without redrawing them. Your job
|
|
11
|
+
is to make sure that never becomes the user's problem.
|
|
12
|
+
|
|
13
|
+
## Procedure
|
|
14
|
+
|
|
15
|
+
1. `preflight`, `sprite_info`, `look` op `preview`.
|
|
16
|
+
2. Read `rules://06-layers-and-rigging`.
|
|
17
|
+
3. If the art is baked, `look` op `ascii` to find exact part boundaries. Guessing
|
|
18
|
+
coordinates here clips limbs.
|
|
19
|
+
4. Propose the rig. Build on confirmation.
|
|
20
|
+
|
|
21
|
+
## The standard rig
|
|
22
|
+
|
|
23
|
+
Bottom to top: `shadow`, `leg-far`, `arm-far`, `torso`, `head`, `leg-near`,
|
|
24
|
+
`arm-near`, plus `weapon` / `cape` / `hair-front` / `hair-back` as needed.
|
|
25
|
+
|
|
26
|
+
Build it in one `layer` call with a `batch` array — one undo step for the user.
|
|
27
|
+
|
|
28
|
+
## Splitting baked art
|
|
29
|
+
|
|
30
|
+
`draw` op `blit` per part, copying from the source layer to its new home.
|
|
31
|
+
|
|
32
|
+
Two rules that matter:
|
|
33
|
+
|
|
34
|
+
- **Overlap generously.** A limb needs a pixel or two under the torso or a gap
|
|
35
|
+
opens the moment it rotates. Stacking order hides the excess.
|
|
36
|
+
- **Hide the original, do not delete it.** If a boundary was wrong you want it
|
|
37
|
+
still there. Rename it `flat-original` and hide it.
|
|
38
|
+
|
|
39
|
+
Verify by hiding the original and running `look` op `preview`: the sprite should
|
|
40
|
+
look unchanged. A limb that lost pixels means a wrong boundary.
|
|
41
|
+
|
|
42
|
+
## Output
|
|
43
|
+
|
|
44
|
+
The layer stack you propose, bottom to top, with what goes on each and why any
|
|
45
|
+
part is grouped. Flag anything the design needs that the standard rig lacks —
|
|
46
|
+
a two-handed weapon that crosses the body needs its own thinking about stacking.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Superseded by aseprite-ai-artist.mjs, which the plugin manifest now names as
|
|
3
|
+
# its MCP command: Windows does not interpret a `#!` line, so a bash shim there
|
|
4
|
+
# fails to start the server at all. Kept as a thin delegate so an existing POSIX
|
|
5
|
+
# invocation of this path keeps working, with no duplicated build logic.
|
|
6
|
+
exec node "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/aseprite-ai-artist.mjs" "$@"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Launcher for the Claude Code plugin and for anyone running from a checkout.
|
|
4
|
+
*
|
|
5
|
+
* This is Node rather than a shell script on purpose: the plugin manifest names
|
|
6
|
+
* it as the MCP `command`, and Windows does not interpret a `#!` line — a bash
|
|
7
|
+
* shim there simply fails to start, silently, on the one client this project
|
|
8
|
+
* treats as first-class. Node is guaranteed present, because the thing being
|
|
9
|
+
* launched is a Node package.
|
|
10
|
+
*
|
|
11
|
+
* A plugin installed from git arrives as source with no build step, so this
|
|
12
|
+
* builds once on first use and then runs the compiled CLI. Published npm users
|
|
13
|
+
* never touch this file — they run `npx @pebbly/aseprite-ai-artist`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { spawnSync } from "node:child_process";
|
|
17
|
+
import { existsSync } from "node:fs";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
import path from "node:path";
|
|
20
|
+
|
|
21
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
22
|
+
const cli = path.join(root, "dist", "cli.js");
|
|
23
|
+
|
|
24
|
+
// stderr only: stdout is the MCP stdio channel and any stray byte corrupts it.
|
|
25
|
+
const log = (msg) => process.stderr.write(`[aseprite-ai-artist] ${msg}\n`);
|
|
26
|
+
|
|
27
|
+
if (!existsSync(cli)) {
|
|
28
|
+
log("first run — building…");
|
|
29
|
+
// `shell: true` so npm resolves as npm.cmd on Windows.
|
|
30
|
+
const steps = [
|
|
31
|
+
["install", "--silent", "--no-audit", "--no-fund"],
|
|
32
|
+
["run", "--silent", "build"],
|
|
33
|
+
];
|
|
34
|
+
for (const args of steps) {
|
|
35
|
+
const result = spawnSync("npm", args, { cwd: root, stdio: ["ignore", 2, 2], shell: true });
|
|
36
|
+
if (result.status !== 0) {
|
|
37
|
+
log(`build step \`npm ${args.join(" ")}\` failed with status ${result.status}.`);
|
|
38
|
+
process.exit(result.status ?? 1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const child = spawnSync(process.execPath, [cli, ...process.argv.slice(2)], { stdio: "inherit" });
|
|
44
|
+
process.exit(child.status ?? 1);
|