@iborymagic/aseprite-mcp 0.3.4 → 0.3.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/README.md +4 -4
- package/package.json +1 -1
- package/build/aseprite/steam.js +0 -96
- package/build/generator/image-generator.js +0 -55
- package/build/generator/tools.js +0 -210
- package/build/localtest.js +0 -16
- package/build/lua/templates.js +0 -60
- package/build/pipeline/character.js +0 -66
- package/build/pipeline/tools.js +0 -226
- package/build/server.js +0 -177
- package/build/tools.js +0 -145
package/README.md
CHANGED
|
@@ -28,14 +28,14 @@ Adds deeper control using Aseprite Lua scripting, enabling safe AI-driven editin
|
|
|
28
28
|
## How to use
|
|
29
29
|
1) Run directly with npx
|
|
30
30
|
```bash
|
|
31
|
-
npx -y aseprite-mcp
|
|
31
|
+
npx -y @iborymagic/aseprite-mcp
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
2) Local Build & Run (for development)
|
|
35
35
|
```bash
|
|
36
36
|
npm install
|
|
37
37
|
npm run build
|
|
38
|
-
npx aseprite-mcp
|
|
38
|
+
npx @iborymagic/aseprite-mcp
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
### Using with Claude Desktop
|
|
@@ -45,7 +45,7 @@ Add the following to your claude_desktop_config.json
|
|
|
45
45
|
"mcpServers": {
|
|
46
46
|
"aseprite-mcp": {
|
|
47
47
|
"command": "npx",
|
|
48
|
-
"args": ["-y", "aseprite-mcp"]
|
|
48
|
+
"args": ["-y", "@iborymagic/aseprite-mcp"]
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
}
|
|
@@ -58,7 +58,7 @@ Add the following to your mcp.json
|
|
|
58
58
|
"mcpServers": {
|
|
59
59
|
"aseprite-mcp": {
|
|
60
60
|
"command": "npx",
|
|
61
|
-
"args": ["-y", "aseprite-mcp"]
|
|
61
|
+
"args": ["-y", "@iborymagic/aseprite-mcp"]
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
}
|
package/package.json
CHANGED
package/build/aseprite/steam.js
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import { exec } from "node:child_process";
|
|
3
|
-
import { promisify } from "node:util";
|
|
4
|
-
import * as os from "node:os";
|
|
5
|
-
import * as path from "node:path";
|
|
6
|
-
const execAsync = promisify(exec);
|
|
7
|
-
const defaultCandidates = {
|
|
8
|
-
win32: [
|
|
9
|
-
"C:\\Program Files\\Aseprite\\aseprite.exe",
|
|
10
|
-
"C:\\Program Files (x86)\\Aseprite\\aseprite.exe"
|
|
11
|
-
],
|
|
12
|
-
darwin: [
|
|
13
|
-
"/Applications/Aseprite.app/Contents/MacOS/aseprite",
|
|
14
|
-
"/usr/local/bin/aseprite",
|
|
15
|
-
"/opt/homebrew/bin/aseprite"
|
|
16
|
-
],
|
|
17
|
-
linux: [
|
|
18
|
-
"/usr/bin/aseprite",
|
|
19
|
-
"/usr/local/bin/aseprite"
|
|
20
|
-
]
|
|
21
|
-
};
|
|
22
|
-
function getSteamVdfPaths() {
|
|
23
|
-
switch (process.platform) {
|
|
24
|
-
case "win32":
|
|
25
|
-
return [
|
|
26
|
-
path.join(process.env.PROGRAMFILESX86 ??
|
|
27
|
-
"C:\\Program Files (x86)", "Steam/steamapps/libraryfolders.vdf"),
|
|
28
|
-
path.join(process.env.PROGRAMFILES ??
|
|
29
|
-
"C:\\Program Files", "Steam/steamapps/libraryfolders.vdf")
|
|
30
|
-
];
|
|
31
|
-
case "darwin":
|
|
32
|
-
return [
|
|
33
|
-
path.join(os.homedir(), "Library/Application Support/Steam/steamapps/libraryfolders.vdf")
|
|
34
|
-
];
|
|
35
|
-
default:
|
|
36
|
-
// linux
|
|
37
|
-
return [
|
|
38
|
-
path.join(os.homedir(), ".steam/steam/steamapps/libraryfolders.vdf"),
|
|
39
|
-
path.join(os.homedir(), ".local/share/Steam/steamapps/libraryfolders.vdf")
|
|
40
|
-
];
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
function parseSteamLibraries(vdfContent) {
|
|
44
|
-
const lines = vdfContent.split("\n");
|
|
45
|
-
const paths = [];
|
|
46
|
-
for (const line of lines) {
|
|
47
|
-
const match = line.match(/"(\d+)"\s+"(.+?)"/);
|
|
48
|
-
if (match) {
|
|
49
|
-
let p = match[2];
|
|
50
|
-
if (process.platform === "win32") {
|
|
51
|
-
p = p.replace(/\\\\/g, "\\");
|
|
52
|
-
}
|
|
53
|
-
paths.push(p);
|
|
54
|
-
}
|
|
55
|
-
// libraryfolders 2.0
|
|
56
|
-
const kvMatch = line.match(/"path"\s+"(.+?)"/);
|
|
57
|
-
if (kvMatch) {
|
|
58
|
-
let p = kvMatch[1];
|
|
59
|
-
if (process.platform === "win32") {
|
|
60
|
-
p = p.replace(/\\\\/g, "\\");
|
|
61
|
-
}
|
|
62
|
-
paths.push(p);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return Array.from(new Set(paths));
|
|
66
|
-
}
|
|
67
|
-
export function getSteamAsepritePaths() {
|
|
68
|
-
const files = getSteamVdfPaths();
|
|
69
|
-
for (const file of files) {
|
|
70
|
-
if (!existsSync(file))
|
|
71
|
-
continue;
|
|
72
|
-
try {
|
|
73
|
-
const content = readFileSync(file, "utf8");
|
|
74
|
-
const libs = parseSteamLibraries(content);
|
|
75
|
-
const paths = [];
|
|
76
|
-
for (const lib of libs) {
|
|
77
|
-
switch (process.platform) {
|
|
78
|
-
case "win32":
|
|
79
|
-
paths.push(path.join(lib, "steamapps/common/Aseprite/aseprite.exe"));
|
|
80
|
-
break;
|
|
81
|
-
case "darwin":
|
|
82
|
-
paths.push(path.join(lib, "steamapps/common/Aseprite/Aseprite.app/Contents/MacOS/aseprite"));
|
|
83
|
-
break;
|
|
84
|
-
default: // linux
|
|
85
|
-
paths.push(path.join(lib, "steamapps/common/Aseprite/aseprite"));
|
|
86
|
-
break;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return paths;
|
|
90
|
-
}
|
|
91
|
-
catch {
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return [];
|
|
96
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import fs from "node:fs/promises";
|
|
3
|
-
import axios from "axios";
|
|
4
|
-
export class StubImageGenerator {
|
|
5
|
-
async generate(params) {
|
|
6
|
-
throw new Error(`Image generator not configured. Tried to generate: "${params.prompt}" to ${params.outputPath}`);
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
export class StableDiffusionWebuiGenerator {
|
|
10
|
-
baseUrl;
|
|
11
|
-
constructor(baseUrl) {
|
|
12
|
-
this.baseUrl = baseUrl;
|
|
13
|
-
}
|
|
14
|
-
async generate(params) {
|
|
15
|
-
const url = `${this.baseUrl}/sdapi/v1/txt2img`;
|
|
16
|
-
const prompt = `${params.prompt}, ` +
|
|
17
|
-
`pixel art, retro 16-bit, clean outline, sharp edges, high contrast, game sprite`;
|
|
18
|
-
const negative = params.negativePrompt ??
|
|
19
|
-
"photo, realistic, blurry, lowres, smear, oil painting";
|
|
20
|
-
const body = {
|
|
21
|
-
prompt,
|
|
22
|
-
negative_prompt: negative,
|
|
23
|
-
width: params.width,
|
|
24
|
-
height: params.height,
|
|
25
|
-
steps: 28,
|
|
26
|
-
sampler_name: "DPM++ 2M",
|
|
27
|
-
cfg_scale: 7,
|
|
28
|
-
seed: params.seed ?? -1,
|
|
29
|
-
restore_faces: false,
|
|
30
|
-
tiling: false
|
|
31
|
-
};
|
|
32
|
-
const res = await axios.post(url, body, {
|
|
33
|
-
timeout: 1000 * 60 * 3
|
|
34
|
-
});
|
|
35
|
-
if (!res.data?.images?.length) {
|
|
36
|
-
throw new Error("Stable Diffusion returned no images");
|
|
37
|
-
}
|
|
38
|
-
const imgBase64 = res.data.images[0];
|
|
39
|
-
const buffer = Buffer.from(imgBase64, "base64");
|
|
40
|
-
const dir = path.dirname(params.outputPath);
|
|
41
|
-
await fs.mkdir(dir, { recursive: true });
|
|
42
|
-
await fs.writeFile(params.outputPath, buffer);
|
|
43
|
-
let seed = params.seed;
|
|
44
|
-
try {
|
|
45
|
-
const info = JSON.parse(res.data.info ?? "{}");
|
|
46
|
-
if (info.seed)
|
|
47
|
-
seed = info.seed;
|
|
48
|
-
}
|
|
49
|
-
catch { }
|
|
50
|
-
return {
|
|
51
|
-
imagePath: params.outputPath,
|
|
52
|
-
seed
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
}
|
package/build/generator/tools.js
DELETED
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import z from "zod";
|
|
2
|
-
import { errorResult, successResult } from "../util.js";
|
|
3
|
-
import { ensureSafePath } from "../aseprite/path.js";
|
|
4
|
-
import path from "path";
|
|
5
|
-
import fs from "node:fs/promises";
|
|
6
|
-
import { runLuaScriptFile } from "../lua/cli.js";
|
|
7
|
-
import { createToolHandlers as createPipelineToolHandlers } from "../pipeline/tools.js";
|
|
8
|
-
import { fileURLToPath } from "node:url";
|
|
9
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
-
const __dirname = path.dirname(__filename);
|
|
11
|
-
const toolSchemas = createToolSchemas();
|
|
12
|
-
export function createToolHandlers() {
|
|
13
|
-
const character_generate_concept = async ({ params: { prompt, workspaceDir, fileName = "concept.png", width = 256, height = 256, seed, stylePreset = "pixel_art" } }, generator) => {
|
|
14
|
-
try {
|
|
15
|
-
const workspaceAbs = ensureSafePath(workspaceDir, { mustExist: false });
|
|
16
|
-
await fs.mkdir(workspaceAbs, { recursive: true });
|
|
17
|
-
const outputPath = path.join(workspaceAbs, fileName);
|
|
18
|
-
const result = await generator.generate({
|
|
19
|
-
prompt,
|
|
20
|
-
negativePrompt: undefined,
|
|
21
|
-
width,
|
|
22
|
-
height,
|
|
23
|
-
seed,
|
|
24
|
-
stylePreset,
|
|
25
|
-
outputPath
|
|
26
|
-
});
|
|
27
|
-
return successResult("character_generate_concept", {
|
|
28
|
-
prompt,
|
|
29
|
-
workspaceDir: workspaceAbs,
|
|
30
|
-
imagePath: result.imagePath,
|
|
31
|
-
seed: result.seed
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
catch (err) {
|
|
35
|
-
return errorResult("character_generate_concept", `Character generate concept failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
const character_import_from_concept = async ({ params: { conceptImage, outputFile, spriteSize: spriteSizeParam = 0, animationSpec: animationSpecParam = "Idle:4" }, }) => {
|
|
39
|
-
try {
|
|
40
|
-
const conceptAbs = ensureSafePath(conceptImage, {
|
|
41
|
-
mustExist: true
|
|
42
|
-
});
|
|
43
|
-
const outputAbs = ensureSafePath(outputFile, {
|
|
44
|
-
mustExist: false
|
|
45
|
-
});
|
|
46
|
-
const spriteSize = spriteSizeParam ?? 0;
|
|
47
|
-
const animationSpec = animationSpecParam ?? "Idle:4";
|
|
48
|
-
// Lua templates are in the source directory, reference from project root
|
|
49
|
-
const luaScriptPath = path.join(process.cwd(), "src", "lua", "templates", "character_import_from_concept.lua");
|
|
50
|
-
const result = await runLuaScriptFile(luaScriptPath, {
|
|
51
|
-
conceptImage: conceptAbs,
|
|
52
|
-
outputFile: outputAbs,
|
|
53
|
-
spriteSize,
|
|
54
|
-
animationSpec
|
|
55
|
-
});
|
|
56
|
-
if (result.timedOut) {
|
|
57
|
-
return errorResult("character_import_from_concept", `Lua script timed out while importing from concept: ${luaScriptPath}`);
|
|
58
|
-
}
|
|
59
|
-
return successResult("character_import_from_concept", {
|
|
60
|
-
command: result.command,
|
|
61
|
-
conceptImage: conceptAbs,
|
|
62
|
-
outputFile: outputAbs,
|
|
63
|
-
spriteSize,
|
|
64
|
-
animationSpec
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
catch (err) {
|
|
68
|
-
return errorResult("character_import_from_concept", `Character import from concept failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
const character_generate_full = async ({ params: { prompt, config, paths } }, generator) => {
|
|
72
|
-
try {
|
|
73
|
-
const spriteSize = config?.spriteSize ?? 32;
|
|
74
|
-
const animations = config?.animations ?? [
|
|
75
|
-
{ name: "Idle", frames: 4 },
|
|
76
|
-
{ name: "Walk", frames: 8 }
|
|
77
|
-
];
|
|
78
|
-
const seed = config?.seed;
|
|
79
|
-
const charName = (paths.name ??
|
|
80
|
-
prompt
|
|
81
|
-
.toLowerCase()
|
|
82
|
-
.replace(/[^a-z0-9]+/g, "_")
|
|
83
|
-
.replace(/^_+|_+$/g, "")
|
|
84
|
-
.slice(0, 32)) ||
|
|
85
|
-
"character";
|
|
86
|
-
const workspaceBase = ensureSafePath(paths.workspaceDir, {
|
|
87
|
-
mustExist: false
|
|
88
|
-
});
|
|
89
|
-
const exportBase = ensureSafePath(paths.exportDir, {
|
|
90
|
-
mustExist: false
|
|
91
|
-
});
|
|
92
|
-
const workspaceDir = path.join(workspaceBase, charName);
|
|
93
|
-
const exportDir = path.join(exportBase, charName);
|
|
94
|
-
await fs.mkdir(workspaceDir, { recursive: true });
|
|
95
|
-
await fs.mkdir(exportDir, { recursive: true });
|
|
96
|
-
const conceptResult = await character_generate_concept({
|
|
97
|
-
params: {
|
|
98
|
-
prompt,
|
|
99
|
-
workspaceDir,
|
|
100
|
-
fileName: "concept.png",
|
|
101
|
-
width: spriteSize * 8,
|
|
102
|
-
height: spriteSize * 8,
|
|
103
|
-
seed,
|
|
104
|
-
stylePreset: "pixel_art"
|
|
105
|
-
}
|
|
106
|
-
}, generator);
|
|
107
|
-
const conceptContent = conceptResult.content?.[0];
|
|
108
|
-
const conceptJson = conceptContent
|
|
109
|
-
? JSON.parse(conceptContent.text)
|
|
110
|
-
: null;
|
|
111
|
-
if (!conceptJson || !conceptJson.success) {
|
|
112
|
-
return errorResult("character_generate_full", {
|
|
113
|
-
stage: "generate_concept",
|
|
114
|
-
error: conceptJson?.error ?? "Concept generation failed"
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
const conceptImage = conceptJson.result.imagePath;
|
|
118
|
-
const animationSpec = animations
|
|
119
|
-
.map((a) => `${a.name}:${a.frames}`)
|
|
120
|
-
.join(",");
|
|
121
|
-
const outputFile = path.join(workspaceDir, `${charName}.aseprite`);
|
|
122
|
-
const importResult = await character_import_from_concept({
|
|
123
|
-
params: {
|
|
124
|
-
conceptImage,
|
|
125
|
-
outputFile,
|
|
126
|
-
spriteSize,
|
|
127
|
-
animationSpec
|
|
128
|
-
}
|
|
129
|
-
}, {});
|
|
130
|
-
const importContent = importResult.content?.[0];
|
|
131
|
-
const importJson = importContent ? JSON.parse(importContent.text) : null;
|
|
132
|
-
if (!importJson || !importJson.success) {
|
|
133
|
-
return errorResult("character_generate_full", {
|
|
134
|
-
stage: "import_sprite",
|
|
135
|
-
error: importJson?.error ?? "Sprite import failed"
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
const asepriteFile = importJson.result.outputFile;
|
|
139
|
-
const pipelineToolHandlers = createPipelineToolHandlers();
|
|
140
|
-
const buildResult = await pipelineToolHandlers.character_pipeline_build({
|
|
141
|
-
inputFile: asepriteFile,
|
|
142
|
-
exportDir
|
|
143
|
-
}, {});
|
|
144
|
-
const buildContent = buildResult.content?.[0];
|
|
145
|
-
const buildJson = buildContent ? JSON.parse(buildContent.text) : null;
|
|
146
|
-
if (!buildJson || !buildJson.success) {
|
|
147
|
-
return errorResult("character_generate_full", {
|
|
148
|
-
stage: "pipeline_build",
|
|
149
|
-
error: buildJson?.error ?? "Character pipeline build failed"
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
return successResult("character_generate_full", {
|
|
153
|
-
name: charName,
|
|
154
|
-
prompt,
|
|
155
|
-
spriteSize,
|
|
156
|
-
workspaceDir,
|
|
157
|
-
exportDir,
|
|
158
|
-
concept: conceptJson.result,
|
|
159
|
-
asepriteFile,
|
|
160
|
-
pipeline: buildJson.result
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
catch (err) {
|
|
164
|
-
return errorResult("character_generate_full", err instanceof Error ? err : new Error(String(err)));
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
return {
|
|
168
|
-
character_generate_concept: async (params, generator) => character_generate_concept(params, generator),
|
|
169
|
-
character_import_from_concept,
|
|
170
|
-
character_generate_full: async (params, generator) => character_generate_full(params, generator),
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
export function createToolSchemas() {
|
|
174
|
-
return {
|
|
175
|
-
character_generate_concept: z.object({
|
|
176
|
-
params: z.object({
|
|
177
|
-
prompt: z.string(),
|
|
178
|
-
workspaceDir: z.string(),
|
|
179
|
-
fileName: z.string().optional(),
|
|
180
|
-
width: z.number().optional(),
|
|
181
|
-
height: z.number().optional(),
|
|
182
|
-
seed: z.number().optional(),
|
|
183
|
-
stylePreset: z.string().optional(),
|
|
184
|
-
}),
|
|
185
|
-
}),
|
|
186
|
-
character_import_from_concept: z.object({
|
|
187
|
-
params: z.object({
|
|
188
|
-
conceptImage: z.string(),
|
|
189
|
-
outputFile: z.string(),
|
|
190
|
-
spriteSize: z.number().optional(),
|
|
191
|
-
animationSpec: z.string().optional(),
|
|
192
|
-
}),
|
|
193
|
-
}),
|
|
194
|
-
character_generate_full: z.object({
|
|
195
|
-
params: z.object({
|
|
196
|
-
prompt: z.string(),
|
|
197
|
-
config: z.object({
|
|
198
|
-
spriteSize: z.number().optional(),
|
|
199
|
-
animations: z.array(z.object({ name: z.string(), frames: z.number() })).optional(),
|
|
200
|
-
seed: z.number().optional(),
|
|
201
|
-
}),
|
|
202
|
-
paths: z.object({
|
|
203
|
-
workspaceDir: z.string(),
|
|
204
|
-
exportDir: z.string(),
|
|
205
|
-
name: z.string().optional(),
|
|
206
|
-
}),
|
|
207
|
-
}),
|
|
208
|
-
}),
|
|
209
|
-
};
|
|
210
|
-
}
|
package/build/localtest.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { resolveAsepritePath } from "./aseprite/env.js";
|
|
2
|
-
import { runAsepriteCommand } from "./aseprite/cli.js";
|
|
3
|
-
async function main() {
|
|
4
|
-
try {
|
|
5
|
-
const path = await resolveAsepritePath();
|
|
6
|
-
const { stdout: version } = await runAsepriteCommand(["--version"]);
|
|
7
|
-
console.log("✅ Aseprite OK");
|
|
8
|
-
console.log("Path:", path);
|
|
9
|
-
console.log("Version:", version);
|
|
10
|
-
}
|
|
11
|
-
catch (err) {
|
|
12
|
-
console.error("❌ Failed");
|
|
13
|
-
console.error(err.message ?? err);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
main();
|
package/build/lua/templates.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { fileURLToPath } from "node:url";
|
|
3
|
-
import { ensureSafePath } from "../aseprite/path.js";
|
|
4
|
-
import { runAsepriteCommand } from "../aseprite/cli.js";
|
|
5
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
-
const __dirname = path.dirname(__filename);
|
|
7
|
-
export const LUA_TEMPLATES = [
|
|
8
|
-
{
|
|
9
|
-
id: "remove_layer_by_name",
|
|
10
|
-
description: "Removes a layer with a given name and saves to a new file (or overwrites).",
|
|
11
|
-
params: ["inputFile", "layerName"],
|
|
12
|
-
optionalParams: ["saveOutput"],
|
|
13
|
-
scriptPath: path.join(__dirname, "templates", "remove_layer_by_name.lua")
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
id: "export_tag_frames",
|
|
17
|
-
description: "Exports only the frames of a specific tag as PNG images.",
|
|
18
|
-
params: ["inputFile", "tag", "outputDir"],
|
|
19
|
-
optionalParams: ["filenamePrefix"],
|
|
20
|
-
scriptPath: path.join(__dirname, "templates", "export_tag_frames.lua")
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
id: "recolor_palette",
|
|
24
|
-
description: "Recolors the palette based on a mapping of from->to colors.",
|
|
25
|
-
params: ["inputFile", "saveOutput", "mapping"],
|
|
26
|
-
optionalParams: [],
|
|
27
|
-
scriptPath: path.join(__dirname, "templates", "recolor_palette.lua")
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
id: "normalize_animation_speed",
|
|
31
|
-
description: "Normalizes all frame durations to a single target duration (in seconds).",
|
|
32
|
-
params: ["inputFile", "saveOutput", "targetDuration"],
|
|
33
|
-
optionalParams: [],
|
|
34
|
-
scriptPath: path.join(__dirname, "templates", "normalize_animation_speed.lua")
|
|
35
|
-
}
|
|
36
|
-
];
|
|
37
|
-
export function findLuaTemplate(id) {
|
|
38
|
-
return LUA_TEMPLATES.find(t => t.id === id);
|
|
39
|
-
}
|
|
40
|
-
export async function runLuaTemplate(template, params) {
|
|
41
|
-
return runLua(template, params);
|
|
42
|
-
}
|
|
43
|
-
export async function runLuaScript(scriptPath, params) {
|
|
44
|
-
return runLua(scriptPath, params);
|
|
45
|
-
}
|
|
46
|
-
export async function runLua(script, params) {
|
|
47
|
-
const args = ["--batch"];
|
|
48
|
-
if (typeof params.inputFile === "string") {
|
|
49
|
-
const inputAbs = ensureSafePath(params.inputFile, { mustExist: true });
|
|
50
|
-
args.push(`"${inputAbs}"`);
|
|
51
|
-
}
|
|
52
|
-
const scriptPath = typeof script === "string" ? script : script.scriptPath;
|
|
53
|
-
args.push("--script", `"${scriptPath}"`);
|
|
54
|
-
for (const [key, value] of Object.entries(params)) {
|
|
55
|
-
if (key === "inputFile" || value == null)
|
|
56
|
-
continue;
|
|
57
|
-
args.push("--script-param", `${key}=${value}`);
|
|
58
|
-
}
|
|
59
|
-
return runAsepriteCommand(args);
|
|
60
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
export function analyzeCharacterFromMetadata(inputFile, metaJson) {
|
|
2
|
-
const framesArray = Object.values(metaJson.frames);
|
|
3
|
-
const totalFrames = framesArray.length;
|
|
4
|
-
const width = metaJson.meta.size?.w ?? 0;
|
|
5
|
-
const height = metaJson.meta.size?.h ?? 0;
|
|
6
|
-
const tags = metaJson.meta.frameTags ?? [];
|
|
7
|
-
const tagAnalyses = [];
|
|
8
|
-
const warnings = [];
|
|
9
|
-
const recommendations = [];
|
|
10
|
-
const recommendedTags = ["Idle", "Walk", "Attack"];
|
|
11
|
-
for (const tag of tags) {
|
|
12
|
-
const { name, from, to } = tag;
|
|
13
|
-
const durationPattern = [];
|
|
14
|
-
const issues = [];
|
|
15
|
-
for (let i = from; i <= to; i++) {
|
|
16
|
-
const frame = framesArray[i];
|
|
17
|
-
if (frame) {
|
|
18
|
-
durationPattern.push(frame.duration);
|
|
19
|
-
}
|
|
20
|
-
else {
|
|
21
|
-
issues.push(`missing_frame_index_${i}`);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
if (durationPattern.length > 1) {
|
|
25
|
-
const first = durationPattern[0];
|
|
26
|
-
const inconsistent = durationPattern.some((duration) => duration !== first);
|
|
27
|
-
if (inconsistent) {
|
|
28
|
-
issues.push("duration_inconsistent");
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
tagAnalyses.push({
|
|
32
|
-
name,
|
|
33
|
-
frames: to - from + 1,
|
|
34
|
-
from,
|
|
35
|
-
to,
|
|
36
|
-
durationPattern,
|
|
37
|
-
issues
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
const existingTagNames = new Set(tags.map((tag) => tag.name));
|
|
41
|
-
for (const tag of recommendedTags) {
|
|
42
|
-
const exists = Array.from(existingTagNames).some((tagName) => tagName.toLowerCase() === tag.toLowerCase());
|
|
43
|
-
if (!exists) {
|
|
44
|
-
warnings.push(`Missing recommended animation tag: ${tag}`);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
if (tags.length === 0) {
|
|
48
|
-
warnings.push("No frame tags found. Consider defining Idle/Walk/Attack tags.");
|
|
49
|
-
recommendations.push("Define basic animation tags like Idle, Walk, and Attack.");
|
|
50
|
-
}
|
|
51
|
-
if (tagAnalyses.some((tagAnalysis) => tagAnalysis.issues.includes("duration_inconsistent"))) {
|
|
52
|
-
warnings.push("Some tags have inconsistent frame durations.");
|
|
53
|
-
recommendations.push("Normalize frame durations for smoother animations.");
|
|
54
|
-
}
|
|
55
|
-
return {
|
|
56
|
-
file: inputFile,
|
|
57
|
-
sprite: {
|
|
58
|
-
width,
|
|
59
|
-
height,
|
|
60
|
-
frames: totalFrames
|
|
61
|
-
},
|
|
62
|
-
tags: tagAnalyses,
|
|
63
|
-
warnings,
|
|
64
|
-
recommendations
|
|
65
|
-
};
|
|
66
|
-
}
|
package/build/pipeline/tools.js
DELETED
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
import z from "zod";
|
|
6
|
-
import { errorResult, successResult } from "../util.js";
|
|
7
|
-
import { analyzeCharacterFromMetadata } from "./character.js";
|
|
8
|
-
import { ensureSafePath } from "../aseprite/path.js";
|
|
9
|
-
import { runAsepriteCommand } from "../aseprite/cli.js";
|
|
10
|
-
import { runLuaScriptFile } from "../lua/cli.js";
|
|
11
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
-
const __dirname = path.dirname(__filename);
|
|
13
|
-
const toolSchemas = createToolSchemas();
|
|
14
|
-
export function createToolHandlers() {
|
|
15
|
-
const character_pipeline_analyze = async ({ inputFile }) => {
|
|
16
|
-
try {
|
|
17
|
-
const inputAbs = ensureSafePath(inputFile, { mustExist: true });
|
|
18
|
-
const tempJsonPath = path.join(os.tmpdir(), `${inputFile}-analyze-${Date.now()}.json`);
|
|
19
|
-
const args = [
|
|
20
|
-
"--batch",
|
|
21
|
-
`"${inputAbs}"`,
|
|
22
|
-
"--data",
|
|
23
|
-
`"${tempJsonPath}"`,
|
|
24
|
-
"--list-tags",
|
|
25
|
-
"--list-layers"
|
|
26
|
-
];
|
|
27
|
-
const result = await runAsepriteCommand(args);
|
|
28
|
-
const metaJson = await fs.readFile(tempJsonPath, "utf8");
|
|
29
|
-
const parsedMeta = JSON.parse(metaJson);
|
|
30
|
-
const analysis = analyzeCharacterFromMetadata(inputAbs, parsedMeta);
|
|
31
|
-
return successResult("character_pipeline_analyze", {
|
|
32
|
-
command: result.command,
|
|
33
|
-
inputFile: inputAbs,
|
|
34
|
-
analysis,
|
|
35
|
-
stdout: result.stdout.trim(),
|
|
36
|
-
stderr: result.stderr.trim(),
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
catch (err) {
|
|
40
|
-
return errorResult("character_pipeline_analyze", `Character analysis failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
const character_pipeline_normalize = async ({ inputFile, saveOutput, targetMs, autoCrop }) => {
|
|
44
|
-
try {
|
|
45
|
-
const inputAbs = ensureSafePath(inputFile, { mustExist: true });
|
|
46
|
-
const baseDir = path.dirname(inputAbs);
|
|
47
|
-
const baseName = path.basename(inputAbs, path.extname(inputAbs));
|
|
48
|
-
const outputPath = saveOutput ??
|
|
49
|
-
path.join(baseDir, `${baseName}_normalized.aseprite`);
|
|
50
|
-
const outputAbs = ensureSafePath(outputPath, { mustExist: false });
|
|
51
|
-
const targetDuration = targetMs ?? 100;
|
|
52
|
-
const autoCropEnabled = autoCrop ?? true;
|
|
53
|
-
// Lua templates are in the source directory, reference from project root
|
|
54
|
-
const luaScriptPath = path.join(process.cwd(), "src", "lua", "templates", "character_normalize.lua");
|
|
55
|
-
const result = await runLuaScriptFile(luaScriptPath, {
|
|
56
|
-
inputFile: inputAbs,
|
|
57
|
-
saveOutput: outputAbs,
|
|
58
|
-
targetMs: targetDuration,
|
|
59
|
-
autoCrop: autoCropEnabled
|
|
60
|
-
});
|
|
61
|
-
if (result.timedOut) {
|
|
62
|
-
return errorResult("character_pipeline_normalize", "Lua script timed out while normalizing character");
|
|
63
|
-
}
|
|
64
|
-
return successResult("character_pipeline_normalize", {
|
|
65
|
-
command: result.command,
|
|
66
|
-
inputFile: inputAbs,
|
|
67
|
-
outputFile: outputAbs,
|
|
68
|
-
targetMs: targetDuration,
|
|
69
|
-
autoCrop: autoCropEnabled,
|
|
70
|
-
stdout: result.stdout.trim(),
|
|
71
|
-
stderr: result.stderr.trim(),
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
catch (err) {
|
|
75
|
-
return errorResult("character_pipeline_normalize", `Character normalization failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
const character_pipeline_export = async ({ inputFile, exportDir, sheetType = "packed", format = "json-hash" }) => {
|
|
79
|
-
try {
|
|
80
|
-
const inputAbs = ensureSafePath(inputFile, { mustExist: true });
|
|
81
|
-
const exportDirAbs = ensureSafePath(exportDir, { mustExist: false });
|
|
82
|
-
await fs.mkdir(exportDirAbs, { recursive: true });
|
|
83
|
-
const tempJsonPath = path.join(os.tmpdir(), `${inputFile}-export-${Date.now()}.json`);
|
|
84
|
-
const metaArgs = [
|
|
85
|
-
"--batch",
|
|
86
|
-
`"${inputAbs}"`,
|
|
87
|
-
"--data",
|
|
88
|
-
`"${tempJsonPath}"`,
|
|
89
|
-
"--list-tags"
|
|
90
|
-
];
|
|
91
|
-
await runAsepriteCommand(metaArgs);
|
|
92
|
-
const metaJson = await fs.readFile(tempJsonPath, "utf8");
|
|
93
|
-
const parsedMeta = JSON.parse(metaJson);
|
|
94
|
-
const tags = parsedMeta.meta.frameTags ?? [];
|
|
95
|
-
if (tags.length === 0) {
|
|
96
|
-
return errorResult("character_pipeline_export", "No tags found in sprite. Define animation tags before export.");
|
|
97
|
-
}
|
|
98
|
-
const baseName = path.basename(inputAbs, path.extname(inputAbs));
|
|
99
|
-
const generated = [];
|
|
100
|
-
for (const tag of tags) {
|
|
101
|
-
const tagName = tag.name;
|
|
102
|
-
const safeTag = tagName.toLowerCase();
|
|
103
|
-
const pngPath = path.join(exportDirAbs, `${baseName}_${safeTag}.png`);
|
|
104
|
-
const jsonPath = path.join(exportDirAbs, `${baseName}_${safeTag}.json`);
|
|
105
|
-
const args = [
|
|
106
|
-
"--batch",
|
|
107
|
-
`"${inputAbs}"`,
|
|
108
|
-
"--tag",
|
|
109
|
-
`"${tagName}"`,
|
|
110
|
-
"--sheet",
|
|
111
|
-
`"${pngPath}"`,
|
|
112
|
-
"--data",
|
|
113
|
-
`"${jsonPath}"`,
|
|
114
|
-
"--sheet-type",
|
|
115
|
-
sheetType,
|
|
116
|
-
"--format",
|
|
117
|
-
format
|
|
118
|
-
];
|
|
119
|
-
await runAsepriteCommand(args);
|
|
120
|
-
generated.push({
|
|
121
|
-
tag: tagName,
|
|
122
|
-
png: pngPath,
|
|
123
|
-
json: jsonPath,
|
|
124
|
-
frames: tag.to - tag.from + 1
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
return successResult("character_pipeline_export", {
|
|
128
|
-
inputFile: inputAbs,
|
|
129
|
-
exportDir: exportDirAbs,
|
|
130
|
-
sheetType,
|
|
131
|
-
format,
|
|
132
|
-
generated,
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
catch (err) {
|
|
136
|
-
return errorResult("character_pipeline_export", `Character export failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
137
|
-
}
|
|
138
|
-
};
|
|
139
|
-
const character_pipeline_build = async ({ inputFile, tempOutput, exportDir, normalizeOption = { targetMs: 100, autoCrop: true }, exportOption = { sheetType: "packed", format: "json-hash" } }) => {
|
|
140
|
-
try {
|
|
141
|
-
const inputAbs = ensureSafePath(inputFile, { mustExist: true });
|
|
142
|
-
const baseDir = path.dirname(inputAbs);
|
|
143
|
-
const baseName = path.basename(inputAbs, path.extname(inputAbs));
|
|
144
|
-
const normalizedOutput = tempOutput ??
|
|
145
|
-
path.join(baseDir, `${baseName}_normalized.aseprite`);
|
|
146
|
-
const exportDirAbs = ensureSafePath(exportDir, { mustExist: false });
|
|
147
|
-
const analyzeResponse = await character_pipeline_analyze({ inputFile: inputAbs }, {});
|
|
148
|
-
const analyzeContent = analyzeResponse.content[0];
|
|
149
|
-
const analyzeParsed = JSON.parse(analyzeContent.text);
|
|
150
|
-
if (!analyzeParsed.success) {
|
|
151
|
-
return errorResult("character_pipeline_build", analyzeParsed.error);
|
|
152
|
-
}
|
|
153
|
-
const normalizeResponse = await character_pipeline_normalize({
|
|
154
|
-
inputFile: inputAbs,
|
|
155
|
-
saveOutput: normalizedOutput,
|
|
156
|
-
targetMs: normalizeOption.targetMs,
|
|
157
|
-
autoCrop: normalizeOption.autoCrop,
|
|
158
|
-
}, {});
|
|
159
|
-
const normalizeContent = normalizeResponse.content[0];
|
|
160
|
-
const normalizeParsed = JSON.parse(normalizeContent.text);
|
|
161
|
-
if (!normalizeParsed.success) {
|
|
162
|
-
return errorResult("character_pipeline_build", normalizeParsed.error);
|
|
163
|
-
}
|
|
164
|
-
const exportResponse = await character_pipeline_export({
|
|
165
|
-
inputFile: normalizedOutput,
|
|
166
|
-
exportDir: exportDirAbs,
|
|
167
|
-
sheetType: exportOption.sheetType,
|
|
168
|
-
format: exportOption.format,
|
|
169
|
-
}, {});
|
|
170
|
-
const exportContent = exportResponse.content[0];
|
|
171
|
-
const exportParsed = JSON.parse(exportContent.text);
|
|
172
|
-
if (!exportParsed.success) {
|
|
173
|
-
return errorResult("character_pipeline_build", exportParsed.error);
|
|
174
|
-
}
|
|
175
|
-
return successResult("character_pipeline_build", {
|
|
176
|
-
inputFile: inputAbs,
|
|
177
|
-
normalizedFile: normalizedOutput,
|
|
178
|
-
exportDir: exportDirAbs,
|
|
179
|
-
analyze: analyzeParsed.result,
|
|
180
|
-
normalize: normalizeParsed.result,
|
|
181
|
-
export: exportParsed.result,
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
catch (err) {
|
|
185
|
-
return errorResult("character_pipeline_build", `Character pipeline failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
return {
|
|
189
|
-
character_pipeline_analyze,
|
|
190
|
-
character_pipeline_normalize,
|
|
191
|
-
character_pipeline_export,
|
|
192
|
-
character_pipeline_build,
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
export function createToolSchemas() {
|
|
196
|
-
return {
|
|
197
|
-
character_pipeline_analyze: z.object({
|
|
198
|
-
inputFile: z.string(),
|
|
199
|
-
}),
|
|
200
|
-
character_pipeline_normalize: z.object({
|
|
201
|
-
inputFile: z.string(),
|
|
202
|
-
saveOutput: z.string().optional(),
|
|
203
|
-
targetMs: z.number().optional(),
|
|
204
|
-
autoCrop: z.boolean().optional(),
|
|
205
|
-
}),
|
|
206
|
-
character_pipeline_export: z.object({
|
|
207
|
-
inputFile: z.string(),
|
|
208
|
-
exportDir: z.string(),
|
|
209
|
-
sheetType: z.enum(["packed", "rows"]).optional(),
|
|
210
|
-
format: z.enum(["json-hash", "json-array"]).optional(),
|
|
211
|
-
}),
|
|
212
|
-
character_pipeline_build: z.object({
|
|
213
|
-
inputFile: z.string(),
|
|
214
|
-
tempOutput: z.string().optional(),
|
|
215
|
-
exportDir: z.string(),
|
|
216
|
-
normalizeOption: z.object({
|
|
217
|
-
targetMs: z.number().optional(),
|
|
218
|
-
autoCrop: z.boolean().optional(),
|
|
219
|
-
}).optional(),
|
|
220
|
-
exportOption: z.object({
|
|
221
|
-
sheetType: z.enum(["packed", "rows"]).optional(),
|
|
222
|
-
format: z.enum(["json-hash", "json-array"]).optional(),
|
|
223
|
-
}).optional(),
|
|
224
|
-
}),
|
|
225
|
-
};
|
|
226
|
-
}
|
package/build/server.js
DELETED
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import * as z from "zod/v4";
|
|
3
|
-
import { readFileSync as readFileSyncNode } from "node:fs";
|
|
4
|
-
import { resolveAsepritePath as resolveAsepritePathDefault } from "./aseprite/env.js";
|
|
5
|
-
import { runAsepriteCommand as runAsepriteCommandDefault } from "./aseprite/cli.js";
|
|
6
|
-
import { ensureSafePath as ensureSafePathDefault } from "./aseprite/path.js";
|
|
7
|
-
import { errorResult as errorResultDefault, successResult as successResultDefault } from "./util.js";
|
|
8
|
-
export function createServer(deps = {}) {
|
|
9
|
-
const d = {
|
|
10
|
-
resolveAsepritePath: resolveAsepritePathDefault,
|
|
11
|
-
runAsepriteCommand: runAsepriteCommandDefault,
|
|
12
|
-
ensureSafePath: ensureSafePathDefault,
|
|
13
|
-
readFileSync: readFileSyncNode,
|
|
14
|
-
successResult: successResultDefault,
|
|
15
|
-
errorResult: errorResultDefault,
|
|
16
|
-
...deps,
|
|
17
|
-
};
|
|
18
|
-
const server = new McpServer({
|
|
19
|
-
name: "aseprite-mcp",
|
|
20
|
-
version: "0.1.0",
|
|
21
|
-
});
|
|
22
|
-
const tools = new Map();
|
|
23
|
-
tools.set("aseprite_check_environment", server.registerTool("aseprite_check_environment", {
|
|
24
|
-
description: "Check the environment of Aseprite",
|
|
25
|
-
inputSchema: undefined,
|
|
26
|
-
outputSchema: undefined,
|
|
27
|
-
}, async () => {
|
|
28
|
-
try {
|
|
29
|
-
const asepritePath = await d.resolveAsepritePath();
|
|
30
|
-
const { stdout: version } = await d.runAsepriteCommand(["--version"]);
|
|
31
|
-
return d.successResult("aseprite_check_environment", {
|
|
32
|
-
path: asepritePath,
|
|
33
|
-
version,
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
catch (e) {
|
|
37
|
-
return d.errorResult("aseprite_check_environment", e instanceof Error ? e.message : String(e));
|
|
38
|
-
}
|
|
39
|
-
}));
|
|
40
|
-
tools.set("aseprite_export_sheet", server.registerTool("aseprite_export_sheet", {
|
|
41
|
-
description: "Export Aseprite file to sprite sheet image",
|
|
42
|
-
inputSchema: z.object({
|
|
43
|
-
inputFile: z.string(),
|
|
44
|
-
outputSheet: z.string(),
|
|
45
|
-
sheetType: z.enum(["rows", "columns", "packed"]).optional().default("packed"),
|
|
46
|
-
dataFile: z.string().optional(),
|
|
47
|
-
tag: z.string().optional(),
|
|
48
|
-
}),
|
|
49
|
-
outputSchema: z.object({
|
|
50
|
-
content: z.array(z.object({
|
|
51
|
-
type: z.literal("text"),
|
|
52
|
-
text: z.string(),
|
|
53
|
-
})),
|
|
54
|
-
}),
|
|
55
|
-
}, async ({ inputFile, outputSheet, sheetType, dataFile, tag }) => {
|
|
56
|
-
try {
|
|
57
|
-
const inputAbsPath = d.ensureSafePath(inputFile, { mustExist: true });
|
|
58
|
-
const sheetAbsPath = d.ensureSafePath(outputSheet, { createDirIfNeeded: true });
|
|
59
|
-
const dataAbsPath = dataFile
|
|
60
|
-
? d.ensureSafePath(dataFile, { createDirIfNeeded: true })
|
|
61
|
-
: undefined;
|
|
62
|
-
const args = [
|
|
63
|
-
"--batch",
|
|
64
|
-
`"${inputAbsPath}"`,
|
|
65
|
-
"--sheet",
|
|
66
|
-
`"${sheetAbsPath}"`,
|
|
67
|
-
"--sheet-type",
|
|
68
|
-
sheetType,
|
|
69
|
-
];
|
|
70
|
-
if (tag)
|
|
71
|
-
args.push("--tag", `"${tag}"`);
|
|
72
|
-
if (dataAbsPath)
|
|
73
|
-
args.push("--data", `"${dataAbsPath}"`);
|
|
74
|
-
const result = await d.runAsepriteCommand(args);
|
|
75
|
-
return d.successResult("aseprite_export_sheet", {
|
|
76
|
-
command: result.command,
|
|
77
|
-
inputFile: inputAbsPath,
|
|
78
|
-
outputSheet: sheetAbsPath,
|
|
79
|
-
sheetType,
|
|
80
|
-
dataFile: dataAbsPath ? dataAbsPath : undefined,
|
|
81
|
-
tag: tag ? tag : undefined,
|
|
82
|
-
stdout: result.stdout.trim(),
|
|
83
|
-
stderr: result.stderr.trim(),
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
catch (e) {
|
|
87
|
-
return d.errorResult("aseprite_export_sheet", e instanceof Error ? e.message : String(e));
|
|
88
|
-
}
|
|
89
|
-
}));
|
|
90
|
-
tools.set("aseprite_export_frames", server.registerTool("aseprite_export_frames", {
|
|
91
|
-
description: "Export each frame of Aseprite file",
|
|
92
|
-
inputSchema: z.object({
|
|
93
|
-
inputFile: z.string(),
|
|
94
|
-
outputPattern: z.string(),
|
|
95
|
-
tag: z.string().optional(),
|
|
96
|
-
}),
|
|
97
|
-
outputSchema: z.object({
|
|
98
|
-
content: z.array(z.object({
|
|
99
|
-
type: z.literal("text"),
|
|
100
|
-
text: z.string(),
|
|
101
|
-
})),
|
|
102
|
-
}),
|
|
103
|
-
}, async ({ inputFile, outputPattern, tag }) => {
|
|
104
|
-
try {
|
|
105
|
-
const inputAbsPath = d.ensureSafePath(inputFile, { mustExist: true });
|
|
106
|
-
const outputAbsPath = d.ensureSafePath(outputPattern, { createDirIfNeeded: true });
|
|
107
|
-
const args = [
|
|
108
|
-
"--batch",
|
|
109
|
-
`"${inputAbsPath}"`,
|
|
110
|
-
"--save-as",
|
|
111
|
-
`"${outputAbsPath}"`,
|
|
112
|
-
];
|
|
113
|
-
if (tag)
|
|
114
|
-
args.push("--tag", `"${tag}"`);
|
|
115
|
-
const result = await d.runAsepriteCommand(args);
|
|
116
|
-
return d.successResult("aseprite_export_frames", {
|
|
117
|
-
command: result.command,
|
|
118
|
-
inputFile: inputAbsPath,
|
|
119
|
-
outputPattern: outputAbsPath,
|
|
120
|
-
tag: tag ? tag : undefined,
|
|
121
|
-
stdout: result.stdout.trim(),
|
|
122
|
-
stderr: result.stderr.trim(),
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
catch (e) {
|
|
126
|
-
return d.errorResult("aseprite_export_frames", e instanceof Error ? e.message : String(e));
|
|
127
|
-
}
|
|
128
|
-
}));
|
|
129
|
-
tools.set("aseprite_export_metadata", server.registerTool("aseprite_export_metadata", {
|
|
130
|
-
description: "Export metadata json from Aseprite file",
|
|
131
|
-
inputSchema: z.object({
|
|
132
|
-
inputFile: z.string(),
|
|
133
|
-
dataFile: z.string(),
|
|
134
|
-
format: z.string().optional(),
|
|
135
|
-
}),
|
|
136
|
-
outputSchema: z.object({
|
|
137
|
-
content: z.array(z.object({
|
|
138
|
-
type: z.literal("text"),
|
|
139
|
-
text: z.string(),
|
|
140
|
-
})),
|
|
141
|
-
}),
|
|
142
|
-
}, async ({ inputFile, dataFile, format }) => {
|
|
143
|
-
try {
|
|
144
|
-
const inputAbsPath = d.ensureSafePath(inputFile, { mustExist: true });
|
|
145
|
-
const dataAbsPath = d.ensureSafePath(dataFile, { createDirIfNeeded: true });
|
|
146
|
-
const args = [
|
|
147
|
-
"--batch",
|
|
148
|
-
`"${inputAbsPath}"`,
|
|
149
|
-
"--data",
|
|
150
|
-
`"${dataAbsPath}"`,
|
|
151
|
-
];
|
|
152
|
-
if (format)
|
|
153
|
-
args.push("--format", `"${format}"`);
|
|
154
|
-
const result = await d.runAsepriteCommand(args);
|
|
155
|
-
let metaText = "";
|
|
156
|
-
try {
|
|
157
|
-
metaText = d.readFileSync(dataAbsPath, "utf8");
|
|
158
|
-
}
|
|
159
|
-
catch (e) {
|
|
160
|
-
metaText = `Failed to read metadata: ${e instanceof Error ? e.message : String(e)}`;
|
|
161
|
-
}
|
|
162
|
-
return d.successResult("aseprite_export_metadata", {
|
|
163
|
-
command: result.command,
|
|
164
|
-
inputFile: inputAbsPath,
|
|
165
|
-
dataFile: dataAbsPath,
|
|
166
|
-
format: format ? format : undefined,
|
|
167
|
-
stdout: result.stdout.trim(),
|
|
168
|
-
stderr: result.stderr.trim(),
|
|
169
|
-
metadata: metaText,
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
catch (e) {
|
|
173
|
-
return d.errorResult("aseprite_export_metadata", e instanceof Error ? e.message : String(e));
|
|
174
|
-
}
|
|
175
|
-
}));
|
|
176
|
-
return { server, tools };
|
|
177
|
-
}
|
package/build/tools.js
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import z from "zod";
|
|
2
|
-
import { resolveAsepritePath } from "./aseprite/env.js";
|
|
3
|
-
import { runAsepriteCommand } from "./aseprite/cli.js";
|
|
4
|
-
import { errorResult, successResult } from "./util.js";
|
|
5
|
-
import { ensureSafePath } from "./aseprite/path.js";
|
|
6
|
-
import { readFileSync } from "node:fs";
|
|
7
|
-
const toolSchemas = createToolSchemas();
|
|
8
|
-
export function createToolHandlers() {
|
|
9
|
-
const aseprite_check_environment = async () => {
|
|
10
|
-
try {
|
|
11
|
-
const asepritePath = await resolveAsepritePath();
|
|
12
|
-
const { stdout: version } = await runAsepriteCommand(["--version"]);
|
|
13
|
-
return successResult("aseprite_check_environment", { path: asepritePath, version });
|
|
14
|
-
}
|
|
15
|
-
catch (e) {
|
|
16
|
-
return errorResult("aseprite_check_environment", e instanceof Error ? e.message : String(e));
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
const aseprite_export_sheet = async ({ inputFile, outputSheet, sheetType = "packed", dataFile, tag }) => {
|
|
20
|
-
try {
|
|
21
|
-
const inputAbsPath = ensureSafePath(inputFile, { mustExist: true });
|
|
22
|
-
const sheetAbsPath = ensureSafePath(outputSheet, { createDirIfNeeded: true });
|
|
23
|
-
const dataAbsPath = dataFile ? ensureSafePath(dataFile, { createDirIfNeeded: true }) : undefined;
|
|
24
|
-
const args = [
|
|
25
|
-
"--batch",
|
|
26
|
-
`"${inputAbsPath}"`,
|
|
27
|
-
"--sheet",
|
|
28
|
-
`"${sheetAbsPath}"`,
|
|
29
|
-
"--sheet-type",
|
|
30
|
-
sheetType
|
|
31
|
-
];
|
|
32
|
-
if (tag)
|
|
33
|
-
args.push("--tag", `"${tag}"`);
|
|
34
|
-
if (dataAbsPath)
|
|
35
|
-
args.push("--data", `"${dataAbsPath}"`);
|
|
36
|
-
const result = await runAsepriteCommand(args);
|
|
37
|
-
return successResult("aseprite_export_sheet", {
|
|
38
|
-
command: result.command,
|
|
39
|
-
inputFile: inputAbsPath,
|
|
40
|
-
outputSheet: sheetAbsPath,
|
|
41
|
-
sheetType,
|
|
42
|
-
dataFile: dataAbsPath ? dataAbsPath : undefined,
|
|
43
|
-
tag: tag ? tag : undefined,
|
|
44
|
-
stdout: result.stdout.trim(),
|
|
45
|
-
stderr: result.stderr.trim(),
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
catch (e) {
|
|
49
|
-
return errorResult("aseprite_export_sheet", e instanceof Error ? e.message : String(e));
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
const aseprite_export_frames = async ({ inputFile, outputPattern, tag }) => {
|
|
53
|
-
try {
|
|
54
|
-
const inputAbsPath = ensureSafePath(inputFile, { mustExist: true });
|
|
55
|
-
const outputAbsPath = ensureSafePath(outputPattern, { createDirIfNeeded: true });
|
|
56
|
-
const args = [
|
|
57
|
-
"--batch",
|
|
58
|
-
`"${inputAbsPath}"`,
|
|
59
|
-
"--save-as",
|
|
60
|
-
`"${outputAbsPath}"`
|
|
61
|
-
];
|
|
62
|
-
if (tag)
|
|
63
|
-
args.push("--tag", `"${tag}"`);
|
|
64
|
-
const result = await runAsepriteCommand(args);
|
|
65
|
-
return successResult("aseprite_export_frames", {
|
|
66
|
-
command: result.command,
|
|
67
|
-
inputFile: inputAbsPath,
|
|
68
|
-
outputPattern: outputAbsPath,
|
|
69
|
-
tag: tag ? tag : undefined,
|
|
70
|
-
stdout: result.stdout.trim(),
|
|
71
|
-
stderr: result.stderr.trim(),
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
catch (e) {
|
|
75
|
-
return errorResult("aseprite_export_frames", e instanceof Error ? e.message : String(e));
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
const aseprite_export_metadata = async ({ inputFile, dataFile, format }) => {
|
|
79
|
-
try {
|
|
80
|
-
const inputAbsPath = ensureSafePath(inputFile, { mustExist: true });
|
|
81
|
-
const dataAbsPath = ensureSafePath(dataFile, { createDirIfNeeded: true });
|
|
82
|
-
const args = [
|
|
83
|
-
"--batch",
|
|
84
|
-
`"${inputAbsPath}"`,
|
|
85
|
-
"--data",
|
|
86
|
-
`"${dataAbsPath}"`
|
|
87
|
-
];
|
|
88
|
-
if (format)
|
|
89
|
-
args.push("--format", `"${format}"`);
|
|
90
|
-
const result = await runAsepriteCommand(args);
|
|
91
|
-
let metaText = "";
|
|
92
|
-
try {
|
|
93
|
-
metaText = readFileSync(dataAbsPath, "utf8");
|
|
94
|
-
}
|
|
95
|
-
catch (e) {
|
|
96
|
-
metaText = `Failed to read metadata: ${e instanceof Error ? e.message : String(e)}`;
|
|
97
|
-
}
|
|
98
|
-
return successResult("aseprite_export_metadata", {
|
|
99
|
-
command: result.command,
|
|
100
|
-
inputFile: inputAbsPath,
|
|
101
|
-
dataFile: dataAbsPath,
|
|
102
|
-
format: format ? format : undefined,
|
|
103
|
-
stdout: result.stdout.trim(),
|
|
104
|
-
stderr: result.stderr.trim(),
|
|
105
|
-
metadata: metaText,
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
catch (e) {
|
|
109
|
-
return errorResult("aseprite_export_metadata", e instanceof Error ? e.message : String(e));
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
return {
|
|
113
|
-
aseprite_check_environment,
|
|
114
|
-
aseprite_export_sheet,
|
|
115
|
-
aseprite_export_frames,
|
|
116
|
-
aseprite_export_metadata
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
export function createToolSchemas() {
|
|
120
|
-
return {
|
|
121
|
-
aseprite_export_sheet: z.object({
|
|
122
|
-
inputFile: z.string(),
|
|
123
|
-
outputSheet: z.string(),
|
|
124
|
-
sheetType: z.enum(["rows", "columns", "packed"]).optional().default("packed"),
|
|
125
|
-
dataFile: z.string().optional(),
|
|
126
|
-
tag: z.string().optional(),
|
|
127
|
-
}),
|
|
128
|
-
aseprite_export_frames: z.object({
|
|
129
|
-
inputFile: z.string(),
|
|
130
|
-
outputPattern: z.string(),
|
|
131
|
-
tag: z.string().optional(),
|
|
132
|
-
}),
|
|
133
|
-
aseprite_export_metadata: z.object({
|
|
134
|
-
inputFile: z.string(),
|
|
135
|
-
dataFile: z.string(),
|
|
136
|
-
format: z.string().optional(),
|
|
137
|
-
}),
|
|
138
|
-
aseprite_output_result: z.object({
|
|
139
|
-
content: z.array(z.object({
|
|
140
|
-
type: z.literal("text"),
|
|
141
|
-
text: z.string(),
|
|
142
|
-
})),
|
|
143
|
-
}),
|
|
144
|
-
};
|
|
145
|
-
}
|