@pebbly/aseprite-ai-artist 0.1.3 → 0.1.4

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 CHANGED
@@ -4,6 +4,34 @@ All notable changes to this project are documented here. Format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning is
5
5
  [semver](https://semver.org/).
6
6
 
7
+ ## [0.1.4] — 2026-09-08
8
+
9
+ ### Fixed
10
+
11
+ - **GIF export waited for a click nobody was there to give.** Aseprite warns
12
+ once per session that GIF cannot hold everything a sprite can, and no save API
13
+ declines it. From outside it did not look like a prompt at all: a modal pumps
14
+ events while it waits, so every other command kept answering and only the
15
+ export appeared to hang. It is now suppressed for the duration of the export
16
+ and handed straight back, so File ▸ Save As keeps whatever the user chose.
17
+ - 0.1.3 tried to fix this with `SaveFileCopyAs{ui = false}`. That flag does not
18
+ govern this dialog. GIF export now converts a throwaway copy to indexed
19
+ instead of leaving the conversion to a prompt — and on a copy, because doing
20
+ it in place would silently change the colour mode of the document being
21
+ worked in.
22
+
23
+ ### Added
24
+
25
+ - `export` op `gif` honours `scale`. A browser scaling a 128px GIF up smooths
26
+ it, and smoothed pixel art is ruined pixel art; exporting big keeps the pixels
27
+ square wherever the file is shown.
28
+
29
+ ### Known
30
+
31
+ - The first GIF export in an Aseprite session costs 20-30 seconds — measured
32
+ 20.6s, then 435ms for the identical export straight after. Something warms up
33
+ once. The 120s export budget from 0.1.3 covers it.
34
+
7
35
  ## [0.1.3] — 2026-09-08
8
36
 
9
37
  ### Fixed
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  <div align="center">
2
2
 
3
+ <img src="docs/media/hero.gif" alt="A pixel robot paints a mushroom house, stroke by stroke" width="512">
4
+
3
5
  # Aseprite AI Artist
4
6
 
5
7
  **Let a coding agent draw pixel art in your open Aseprite window** — not in a
@@ -9,6 +11,10 @@ copy, not on disk, in the document you are looking at.
9
11
  [![CI](https://github.com/with-pebbly/aseprite-ai-artist/actions/workflows/ci.yml/badge.svg)](https://github.com/with-pebbly/aseprite-ai-artist/actions/workflows/ci.yml)
10
12
  [![licence](https://img.shields.io/badge/licence-MIT-blue)](LICENSE)
11
13
 
14
+ *Forty frames, one 16-colour palette, drawn by Codex through this server in a
15
+ live Aseprite window — silhouette, flats, hue-shifted shading, then the lights
16
+ come on. Nobody touched a pixel by hand.*
17
+
12
18
  </div>
13
19
 
14
20
  ---
@@ -110,8 +116,12 @@ that surprises people:
110
116
  32×32 grid is a spatial problem: the model has to hold a coordinate frame in
111
117
  its head, keep a silhouette readable at that size, and notice when a shape has
112
118
  gone wrong. Turn reasoning up before you blame the tool.
113
- - **Codex CLI on a high-reasoning setting is the best drawer we have used**,
114
- working from a brief and the rulebook alone.
119
+ - **Codex CLI on a high-reasoning setting is the best drawer we have used** — it
120
+ drew the animation at the top of this page and the mascot below, working from
121
+ a brief and the rulebook alone.
122
+
123
+ <img src="docs/media/mascot.png" alt="The mascot" width="96">
124
+
115
125
  - **Claude Code is the better planner and critic.** Palette construction, rig
116
126
  layout, animation timing and the review pass come out noticeably stronger;
117
127
  raw pixel placement at small canvas sizes is weaker.
@@ -15,7 +15,7 @@
15
15
  --------------------------------------------------------------------------------
16
16
 
17
17
  local PROTOCOL_VERSION = 1
18
- local EXTENSION_VERSION = "0.1.3"
18
+ local EXTENSION_VERSION = "0.1.4"
19
19
 
20
20
  -- Optional capabilities. The wire version stays 1 across builds; new command
21
21
  -- families are gated on these flags plus the loud unsupported_command reply,
@@ -2210,17 +2210,66 @@ H["export.run"] = function(args)
2210
2210
  out:close()
2211
2211
  files[#files + 1] = path
2212
2212
 
2213
- elseif op == "gif" or op == "aseprite" then
2214
- -- NOT Sprite:saveCopyAs. Writing an RGB sprite to GIF needs a colour
2215
- -- quantisation the UI asks about, and that dialog has no `ui` switch to
2216
- -- turn off from the sprite method — the call simply never returns. It
2217
- -- looks like a hang rather than a prompt, because the dialog pumps events
2218
- -- while it waits, so every other command keeps answering normally.
2219
- -- Headless tests never see it: `aseprite -b` has no dialogs at all.
2220
- preserving_site(function()
2221
- app.sprite = s
2222
- app.command.SaveFileCopyAs{ ui = false, filename = path }
2213
+ elseif op == "aseprite" then
2214
+ s:saveCopyAs(path)
2215
+ files[#files + 1] = path
2216
+
2217
+ elseif op == "gif" then
2218
+ -- GIF holds 256 indexed colours, so writing an RGB sprite to one needs a
2219
+ -- quantisation and Aseprite asks a human how to do it. Neither
2220
+ -- Sprite:saveCopyAs nor SaveFileCopyAs{ui = false} declines that dialog;
2221
+ -- the export simply waits, forever if nobody is watching the screen. It
2222
+ -- does not even look like a prompt from the outside: a modal dialog pumps
2223
+ -- events while it waits, so every other command keeps answering normally
2224
+ -- and only the export appears stuck.
2225
+ --
2226
+ -- So do the conversion here, on a throwaway copy, and hand Aseprite a
2227
+ -- sprite it has nothing left to ask about. The copy keeps the sprite's
2228
+ -- own palette, which is the point of a palette-first tool: the colours
2229
+ -- that come back are the colours the artist chose.
2230
+ --
2231
+ -- Headless tests cannot catch a regression here — `aseprite -b` has no
2232
+ -- dialogs at all, so the broken version passed them.
2233
+ -- `scale` matters more here than for a still: a browser scaling a 128px
2234
+ -- GIF up smooths it, and smoothed pixel art is ruined pixel art. Exporting
2235
+ -- big means the pixels stay square wherever the file is shown.
2236
+ local scale = args.scale or 1
2237
+
2238
+ -- Aseprite warns, once per session, that GIF cannot hold everything a
2239
+ -- sprite can — and waits for a click. Nothing in the save API declines
2240
+ -- it, and from the outside it does not even look like a prompt: a modal
2241
+ -- pumps events while it waits, so every other command keeps answering and
2242
+ -- only the export appears to hang, forever, on a machine nobody is
2243
+ -- watching. Silence it for the duration and hand it back, so the user's
2244
+ -- own File > Save As keeps whatever they chose.
2245
+ local restore_alert = nil
2246
+ pcall(function()
2247
+ restore_alert = app.preferences.gif.show_alert
2248
+ app.preferences.gif.show_alert = false
2223
2249
  end)
2250
+
2251
+ local ok, err = pcall(function()
2252
+ if s.colorMode == ColorMode.INDEXED and scale == 1 then
2253
+ s:saveCopyAs(path)
2254
+ else
2255
+ preserving_site(function()
2256
+ local copy = Sprite(s)
2257
+ app.sprite = copy
2258
+ if scale > 1 then copy:resize(s.width * scale, s.height * scale) end
2259
+ if copy.colorMode ~= ColorMode.INDEXED then
2260
+ app.command.ChangePixelFormat{ ui = false, format = "indexed" }
2261
+ end
2262
+ copy:saveCopyAs(path)
2263
+ copy:close()
2264
+ end)
2265
+ end
2266
+ end)
2267
+
2268
+ if restore_alert ~= nil then
2269
+ pcall(function() app.preferences.gif.show_alert = restore_alert end)
2270
+ end
2271
+ if not ok then error(err, 0) end
2272
+
2224
2273
  files[#files + 1] = path
2225
2274
 
2226
2275
  elseif op == "frames" then
@@ -2,7 +2,7 @@
2
2
  "name": "aseprite-ai-artist",
3
3
  "displayName": "Aseprite AI Artist",
4
4
  "description": "Lets a coding agent draw in this Aseprite session over a local WebSocket link. Connects to 127.0.0.1 only; nothing leaves the machine.",
5
- "version": "0.1.3",
5
+ "version": "0.1.4",
6
6
  "author": "with-pebbly",
7
7
  "publisher": "with-pebbly",
8
8
  "license": "MIT",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pebbly/aseprite-ai-artist",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Agent-native control layer for Aseprite. MCP server (2025-11-25 spec, designed forward to 2026-07-28) + cross-agent skills for Claude Code, Codex, Gemini CLI and Cursor.",
5
5
  "license": "MIT",
6
6
  "type": "module",