@iborymagic/aseprite-mcp 0.4.4 → 0.4.6

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/build/index.js CHANGED
File without changes
@@ -0,0 +1,23 @@
1
+ -- Auto-crops a sprite and saves the result.
2
+ -- Params:
3
+ -- saveOutput : string
4
+
5
+ local p = app.params
6
+ if not p or not p.saveOutput then
7
+ print("ERROR: saveOutput is required")
8
+ return
9
+ end
10
+
11
+ local sprite = app.activeSprite
12
+
13
+ if not sprite then
14
+ print("ERROR: No active sprite")
15
+ return
16
+ end
17
+
18
+ app.command.AutocropSprite()
19
+
20
+ sprite:saveAs(p.saveOutput)
21
+
22
+ sprite:close()
23
+ print("auto_crop_transparent script executed successfully")
@@ -0,0 +1,41 @@
1
+ -- Exports a single layer as a PNG file.
2
+ -- Params:
3
+ -- layerName : string
4
+ -- outputDir : string (must exist or will be created by Node side)
5
+
6
+ local p = app.params
7
+ if not p or not p.layerName or not p.outputDir then
8
+ print("ERROR: layerName and outputDir are required")
9
+ return
10
+ end
11
+
12
+ local sprite = app.activeSprite
13
+
14
+ if not sprite then
15
+ print("ERROR: No active sprite")
16
+ return
17
+ end
18
+
19
+ local target = nil
20
+
21
+ for _, layer in ipairs(sprite.layers) do
22
+ if layer.name == p.layerName then
23
+ target = layer
24
+ end
25
+ end
26
+
27
+ if not target then
28
+ print("ERROR: Layer not found: " .. p.layerName)
29
+ return
30
+ end
31
+
32
+ for _, layer in ipairs(sprite.layers) do
33
+ layer.isVisible = false
34
+ end
35
+
36
+ target.isVisible = true
37
+
38
+ sprite:saveAs(p.outputDir .. "/" .. p.layerName .. ".png")
39
+
40
+ sprite:close()
41
+ print("export_layer_only script executed successfully")
@@ -0,0 +1,67 @@
1
+ -- Exports only frames inside a specific tag as individual PNG files.
2
+ -- Params:
3
+ -- tag : string
4
+ -- outputDir : string (must exist or will be created by Node side)
5
+ -- filenamePrefix : string (optional, default "frame")
6
+
7
+ local p = app.params
8
+
9
+ if not p or not p.outputDir then
10
+ print("ERROR: outputDir is required")
11
+ return
12
+ end
13
+
14
+ local sprite = app.activeSprite
15
+
16
+ if not sprite then
17
+ print("ERROR: No active sprite")
18
+ return
19
+ end
20
+
21
+ local tagName = p.tag
22
+ if not tagName or tagName == "" then
23
+ print("ERROR: tag is required")
24
+ return
25
+ end
26
+
27
+ local outputDir = p.outputDir or "."
28
+ local prefix = p.filenamePrefix or "frame"
29
+
30
+ local tag = nil
31
+ for _, t in ipairs(sprite.tags) do
32
+ if t.name == tagName then
33
+ tag = t
34
+ break
35
+ end
36
+ end
37
+
38
+ if not tag then
39
+ print("ERROR: Tag not found: " .. tagName)
40
+ return
41
+ end
42
+
43
+ local startFrame = tag.fromFrame.frameNumber
44
+ local endFrame = tag.toFrame.frameNumber
45
+
46
+ for frameNumber = startFrame, endFrame do
47
+ local frameIndex = frameNumber - 1
48
+
49
+ local newSprite = Sprite(sprite.spec)
50
+ for i, layer in ipairs(sprite.layers) do
51
+ if i > 1 then
52
+ newSprite:newLayer(layer.name)
53
+ end
54
+
55
+ local cel = layer:cel(frameIndex)
56
+ if cel then
57
+ newSprite:newCel(newSprite.layers[i], newSprite.frames[1], cel.image, cel.position)
58
+ end
59
+ end
60
+
61
+ local filename = string.format("%s/%s-%04d.png", outputDir, prefix, frameNumber)
62
+ newSprite:saveAs(filename)
63
+ newSprite:close()
64
+ end
65
+
66
+ sprite:close()
67
+ print("export_tag_frames script executed successfully")
@@ -0,0 +1,19 @@
1
+ -- Returns information about the active sprite.
2
+ -- Params:
3
+ -- none
4
+
5
+ local sprite = app.activeSprite
6
+
7
+ if not sprite then
8
+ print("ERROR: No active sprite")
9
+ return
10
+ end
11
+
12
+ print(json.encode({
13
+ width = sprite.width,
14
+ height = sprite.height,
15
+ colorMode = sprite.colorMode == ColorMode.RGB and "rgb"
16
+ or sprite.colorMode == ColorMode.INDEXED and "indexed"
17
+ or "grayscale",
18
+ frameCount = #sprite.frames
19
+ }))
@@ -0,0 +1,24 @@
1
+ -- Returns information about the current frame.
2
+ -- Params:
3
+ -- none
4
+
5
+ local sprite = app.activeSprite
6
+
7
+ if not sprite then
8
+ print("ERROR: No active sprite")
9
+ return
10
+ end
11
+
12
+ local result = {
13
+ currentFrame = app.activeFrame.frameNumber,
14
+ frames = {}
15
+ }
16
+
17
+ for i, frame in ipairs(sprite.frames) do
18
+ table.insert(result.frames, {
19
+ index = i,
20
+ duration = frame.duration
21
+ })
22
+ end
23
+
24
+ print(json.encode(result))
@@ -0,0 +1,33 @@
1
+ -- Returns true if the layer exists in the active sprite.
2
+ -- Params:
3
+ -- layerName : string
4
+
5
+ local p = app.params
6
+ local sprite = app.activeSprite
7
+
8
+ if not sprite then
9
+ print("ERROR: No active sprite")
10
+ return
11
+ end
12
+
13
+ if not p or not p.layerName then
14
+ print("ERROR: layerName is required")
15
+ return
16
+ end
17
+
18
+ local function getIsLayerExists(layers)
19
+ for _, layer in ipairs(layers) do
20
+ if layer.name == p.layerName then
21
+ print(true)
22
+ return
23
+ end
24
+ if layer.isGroup and getIsLayerExists(layer.layers) then
25
+ print(true)
26
+ return
27
+ end
28
+ end
29
+ print(false)
30
+ return
31
+ end
32
+
33
+ print(getIsLayerExists(sprite.layers))
@@ -0,0 +1,25 @@
1
+ -- Returns true if the tag exists in the active sprite.
2
+ -- Params:
3
+ -- tagName : string
4
+
5
+ local p = app.params
6
+ local sprite = app.activeSprite
7
+
8
+ if not sprite then
9
+ print("ERROR: No active sprite")
10
+ return
11
+ end
12
+
13
+ if not p or not p.tagName then
14
+ print("ERROR: tagName is required")
15
+ return
16
+ end
17
+
18
+ for _, tag in ipairs(sprite.tags) do
19
+ if tag.name == p.tagName then
20
+ print(true)
21
+ return
22
+ end
23
+ end
24
+
25
+ print(false)
@@ -0,0 +1,31 @@
1
+ -- Returns a list of all layers in the active sprite.
2
+ -- Params:
3
+ -- none
4
+
5
+ local sprite = app.activeSprite
6
+
7
+ if not sprite then
8
+ print("ERROR: No active sprite")
9
+ return
10
+ end
11
+
12
+ local layers = {}
13
+
14
+ local function listLayers(layersList)
15
+ for _, layer in ipairs(layersList) do
16
+ table.insert(layers, {
17
+ name = layer.name,
18
+ index = layer.stackIndex,
19
+ isGroup = layer.isGroup,
20
+ visible = layer.isVisible,
21
+ editable = layer.isEditable
22
+ })
23
+
24
+ if layer.isGroup then
25
+ listLayers(layer.layers)
26
+ end
27
+ end
28
+ end
29
+
30
+ listLayers(sprite.layers)
31
+ print(json.encode(layers))
@@ -0,0 +1,34 @@
1
+ -- Returns information about the palette of the active sprite.
2
+ -- Params:
3
+ -- none
4
+
5
+ local sprite = app.activeSprite
6
+
7
+ if not sprite then
8
+ print("ERROR: No active sprite")
9
+ return
10
+ end
11
+
12
+ local palette = sprite.palettes[1]
13
+
14
+ if not palette then
15
+ print("ERROR: No palette found")
16
+ return
17
+ end
18
+
19
+ local colors = {}
20
+
21
+ for i = 0, #colors - 1 do
22
+ local c = palette:getColor(i)
23
+ table.insert(colors, {
24
+ r = c.red,
25
+ g = c.green,
26
+ b = c.blue,
27
+ a = c.alpha
28
+ })
29
+ end
30
+
31
+ print(json.encode({
32
+ size = #colors,
33
+ colors = colors
34
+ }))
@@ -0,0 +1,24 @@
1
+ -- Returns the bounds of the selection in the active sprite.
2
+ -- Params:
3
+ -- none
4
+
5
+ local sprite = app.activeSprite
6
+
7
+ if not sprite then
8
+ print("ERROR: No active sprite")
9
+ return
10
+ end
11
+
12
+ local sel = sprite.selection
13
+
14
+ if sel.isEmpty then
15
+ print("ERROR: No selection")
16
+ return
17
+ end
18
+
19
+ print(json.encode({
20
+ x = sel.bounds.x,
21
+ y = sel.bounds.y,
22
+ width = sel.bounds.width,
23
+ height = sel.bounds.height
24
+ }))
@@ -0,0 +1,25 @@
1
+ -- Returns a list of all tags in the active sprite.
2
+ -- Params:
3
+ -- none
4
+
5
+ local sprite = app.activeSprite
6
+
7
+ if not sprite then
8
+ print("ERROR: No active sprite")
9
+ return
10
+ end
11
+
12
+ local tags = {}
13
+
14
+ for _, tag in ipairs(sprite.tags) do
15
+ table.insert(tags, {
16
+ name = tag.name,
17
+ from = tag.fromFrame.frameNumber,
18
+ to = tag.toFrame.frameNumber,
19
+ direction = tag.aniDir == AniDir.FORWARD and "forward"
20
+ or tag.aniDir == AniDir.REVERSE and "reverse"
21
+ or "pingpong"
22
+ })
23
+ end
24
+
25
+ print(json.encode(tags))
@@ -0,0 +1,24 @@
1
+ -- Merges all visible layers into a single layer and saves the result.
2
+ -- Params:
3
+ -- saveOutput : string
4
+
5
+ local p = app.params
6
+
7
+ if not p or not p.saveOutput then
8
+ print("ERROR: saveOutput is required")
9
+ return
10
+ end
11
+
12
+ local sprite = app.activeSprite
13
+
14
+ if not sprite then
15
+ print("ERROR: No active sprite")
16
+ return
17
+ end
18
+
19
+ app.command.FlattenLayers()
20
+
21
+ sprite:saveAs(p.saveOutput)
22
+
23
+ sprite:close()
24
+ print("merge_visible_layers script executed successfully")
@@ -0,0 +1,40 @@
1
+ -- Normalize all frame durations to a single targetDuration (seconds).
2
+ -- Params:
3
+ -- inputFile : string (already opened in Aseprite CLI or app.activeSprite)
4
+ -- saveOutput : string
5
+ -- targetDuration : string/number (seconds)
6
+
7
+ local p = app.params
8
+
9
+ local targetStr = p.targetDuration
10
+ if not targetStr then
11
+ print("ERROR: targetDuration is required")
12
+ return
13
+ end
14
+
15
+ local target = tonumber(targetStr)
16
+ if not target or target <= 0 then
17
+ print("ERROR: targetDuration must be positive number, got: " .. tostring(targetStr))
18
+ return
19
+ end
20
+
21
+ local spr = app.activeSprite
22
+ if not spr then
23
+ print("ERROR: No active sprite")
24
+ return
25
+ end
26
+
27
+ for i, frame in ipairs(spr.frames) do
28
+ frame.duration = target
29
+ end
30
+
31
+ local saveOutput = p.saveOutput or p.inputFile
32
+ if saveOutput and saveOutput ~= "" then
33
+ app.command.SaveFileAs{
34
+ filename = saveOutput
35
+ }
36
+ else
37
+ app.command.SaveFile()
38
+ end
39
+
40
+ print("normalize_animation_speed script executed successfully")
@@ -0,0 +1,131 @@
1
+ -- Recolors the palette based on a mapping of from->to colors.
2
+ -- Params:
3
+ -- saveOutput : string
4
+ -- mapping : string ("RRGGBB:RRGGBB;RRGGBB:RRGGBB;...")
5
+
6
+ local p = app.params
7
+
8
+ if not p or not p.saveOutput or not p.mapping then
9
+ print("ERROR: saveOutput and mapping are required")
10
+ return
11
+ end
12
+
13
+ local sprite = app.activeSprite
14
+ if not sprite then
15
+ print("ERROR: No active sprite")
16
+ return
17
+ end
18
+
19
+ local function parseColor(hex)
20
+ hex = hex:lower()
21
+ if #hex ~= 6 then return nil end
22
+ local r = tonumber(hex:sub(1, 2), 16)
23
+ local g = tonumber(hex:sub(3, 4), 16)
24
+ local b = tonumber(hex:sub(5, 6), 16)
25
+ return r,g,b
26
+ end
27
+
28
+ local rules = {}
29
+ for pair in string.gmatch(p.mapping, "([^;]+)") do
30
+ local fromHex, toHex = pair:match("([^:]+):([^:]+)")
31
+ if fromHex and toHex then
32
+ table.insert(rules, {
33
+ from = fromHex:lower(),
34
+ to = toHex:lower()
35
+ })
36
+ end
37
+ end
38
+
39
+ if #rules == 0 then
40
+ print("ERROR: No valid mapping rules found")
41
+ return
42
+ end
43
+
44
+
45
+ local function recolorIndexed()
46
+ print("Indexed mode detected: Using palette recolor")
47
+
48
+ local basePal = sprite.palettes[1] or sprite.palette
49
+ if not basePal then
50
+ print("ERROR: No palette found")
51
+ return false
52
+ end
53
+
54
+ for i = 0, #basePal - 1 do
55
+ local c = basePal:getColor(i)
56
+ local cr = string.format("%02x%02x%02x", c.red, c.green, c.blue)
57
+
58
+ for _, rule in ipairs(rules) do
59
+ if cr == rule.from then
60
+ local r,g,b = parseColor(rule.to)
61
+ if r and g and b then
62
+ basePal:setColor(i,
63
+ Color{ r=r, g=g, b=b, a=c.alpha }
64
+ )
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ return true
71
+ end
72
+
73
+
74
+ local function recolorRGBA()
75
+ print("RGBA mode detected: Using pixel replacement")
76
+
77
+ app.transaction(function()
78
+ for _, cel in ipairs(sprite.cels) do
79
+ local img = cel.image
80
+ local w = img.width
81
+ local h = img.height
82
+
83
+ for y = 0, h - 1 do
84
+ for x = 0, w - 1 do
85
+ local px = img:getPixel(x, y)
86
+
87
+ local r = app.pixelColor.rgbaR(px)
88
+ local g = app.pixelColor.rgbaG(px)
89
+ local b = app.pixelColor.rgbaB(px)
90
+ local a = app.pixelColor.rgbaA(px)
91
+
92
+ local key = string.format("%02x%02x%02x", r,g,b)
93
+
94
+ for _, rule in ipairs(rules) do
95
+ if key == rule.from then
96
+ local nr,ng,nb = parseColor(rule.to)
97
+ if nr and ng and nb then
98
+ img:putPixel(x, y,
99
+ app.pixelColor.rgba(nr,ng,nb,a))
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end)
107
+
108
+ return true
109
+ end
110
+
111
+
112
+ local ok = false
113
+
114
+ if sprite.colorMode == ColorMode.INDEXED then
115
+ ok = recolorIndexed()
116
+ else
117
+ print("WARNING: Sprite is NOT Indexed Color.")
118
+ print("Fallback to RGBA pixel replacement recoloring.")
119
+ ok = recolorRGBA()
120
+ end
121
+
122
+ if not ok then
123
+ print("ERROR: recoloring failed")
124
+ return
125
+ end
126
+
127
+ if p.saveOutput and p.saveOutput ~= "" then
128
+ sprite:saveAs(p.saveOutput)
129
+ end
130
+
131
+ print("recolor_palette script executed successfully")
@@ -0,0 +1,38 @@
1
+ -- Removes a layer by name and saves to a new file (or overwrites).
2
+ -- Params (app.params):
3
+ -- layerName : string
4
+ -- saveOutput : string (optional, default = inputFile)
5
+
6
+ local p = app.params
7
+
8
+ if not p or not p.layerName or not p.saveOutput then
9
+ print("ERROR: layerName and saveOutput are required")
10
+ return
11
+ end
12
+
13
+ local sprite = app.activeSprite
14
+ if not sprite then
15
+ print("ERROR: No active sprite")
16
+ return
17
+ end
18
+
19
+ local found = false
20
+
21
+ for i, layer in ipairs(sprite.layers) do
22
+ if layer.name == p.layerName then
23
+ app.activeLayer = layer
24
+ app.command.RemoveLayer()
25
+ found = true
26
+ break
27
+ end
28
+ end
29
+
30
+ if not found then
31
+ print("WARN: Layer not found: " .. p.layerName)
32
+ end
33
+
34
+ if p.saveOutput and p.saveOutput ~= "" then
35
+ sprite:saveAs(p.saveOutput)
36
+ end
37
+
38
+ print("remove_layer_by_name script executed successfully")
package/package.json CHANGED
@@ -1,8 +1,7 @@
1
1
  {
2
2
  "name": "@iborymagic/aseprite-mcp",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
4
4
  "description": "MCP server for using Aseprite API",
5
- "main": "index.js",
6
5
  "type": "module",
7
6
  "bin": {
8
7
  "aseprite-mcp": "./build/index.js"
@@ -10,8 +9,12 @@
10
9
  "files": [
11
10
  "build"
12
11
  ],
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
13
15
  "scripts": {
14
- "build": "tsc",
16
+ "build": "tsc && npm run copy:lua",
17
+ "copy:lua": "cp -R src/lua/templates build/lua/templates",
15
18
  "start": "node build/index.js",
16
19
  "dev": "npm run build && npm run start",
17
20
  "test": "vitest"