@a-company/atelier 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/README.md +241 -0
- package/dist/chunk-LL2EJ6YE.js +1465 -0
- package/dist/chunk-LL2EJ6YE.js.map +1 -0
- package/dist/cli.cjs +1469 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +18 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +1502 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +118 -0
- package/dist/index.d.ts +118 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp.cjs +1817 -0
- package/dist/mcp.cjs.map +1 -0
- package/dist/mcp.js +1784 -0
- package/dist/mcp.js.map +1 -0
- package/package.json +56 -0
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,1784 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// ../mcp/src/index.ts
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
|
|
7
|
+
// ../mcp/src/store.ts
|
|
8
|
+
var counter = 0;
|
|
9
|
+
function generateId() {
|
|
10
|
+
counter++;
|
|
11
|
+
return `doc_${Date.now()}_${counter}`;
|
|
12
|
+
}
|
|
13
|
+
var DocumentStore = class {
|
|
14
|
+
docs = /* @__PURE__ */ new Map();
|
|
15
|
+
/** Create a new document entry. Returns the assigned ID. */
|
|
16
|
+
create(doc, id) {
|
|
17
|
+
const docId = id ?? generateId();
|
|
18
|
+
if (this.docs.has(docId)) {
|
|
19
|
+
throw new Error(`Document "${docId}" already exists`);
|
|
20
|
+
}
|
|
21
|
+
this.docs.set(docId, doc);
|
|
22
|
+
return docId;
|
|
23
|
+
}
|
|
24
|
+
/** Get a document by ID. */
|
|
25
|
+
get(id) {
|
|
26
|
+
return this.docs.get(id);
|
|
27
|
+
}
|
|
28
|
+
/** Set (overwrite) a document by ID. */
|
|
29
|
+
set(id, doc) {
|
|
30
|
+
this.docs.set(id, doc);
|
|
31
|
+
}
|
|
32
|
+
/** Delete a document by ID. Returns true if it existed. */
|
|
33
|
+
delete(id) {
|
|
34
|
+
return this.docs.delete(id);
|
|
35
|
+
}
|
|
36
|
+
/** Check if a document exists. */
|
|
37
|
+
has(id) {
|
|
38
|
+
return this.docs.has(id);
|
|
39
|
+
}
|
|
40
|
+
/** List all document IDs and their names. */
|
|
41
|
+
list() {
|
|
42
|
+
const result = [];
|
|
43
|
+
for (const [id, doc] of this.docs) {
|
|
44
|
+
result.push({ id, name: doc.name, canvas: doc.canvas });
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
/** Clear all documents (useful for testing). */
|
|
49
|
+
clear() {
|
|
50
|
+
this.docs.clear();
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// ../mcp/src/tools/document.ts
|
|
55
|
+
import { z as z14 } from "zod";
|
|
56
|
+
|
|
57
|
+
// ../schema/dist/index.js
|
|
58
|
+
import { z } from "zod";
|
|
59
|
+
import { z as z2 } from "zod";
|
|
60
|
+
import { z as z3 } from "zod";
|
|
61
|
+
import { z as z4 } from "zod";
|
|
62
|
+
import { z as z5 } from "zod";
|
|
63
|
+
import { z as z6 } from "zod";
|
|
64
|
+
import { z as z7 } from "zod";
|
|
65
|
+
import { z as z8 } from "zod";
|
|
66
|
+
import { z as z9 } from "zod";
|
|
67
|
+
import { z as z10 } from "zod";
|
|
68
|
+
import { z as z11 } from "zod";
|
|
69
|
+
import { z as z12 } from "zod";
|
|
70
|
+
import { z as z13 } from "zod";
|
|
71
|
+
import { parse as yamlParse, stringify as yamlStringify } from "yaml";
|
|
72
|
+
var PixelSchema = z.number();
|
|
73
|
+
var PercentageSchema = z.string().regex(/^-?\d+(\.\d+)?%$/, {
|
|
74
|
+
message: 'Percentage must be a number followed by %, e.g. "50%"'
|
|
75
|
+
});
|
|
76
|
+
var UnitValueSchema = z.union([PixelSchema, PercentageSchema]);
|
|
77
|
+
var FrameSchema = z2.object({
|
|
78
|
+
x: UnitValueSchema,
|
|
79
|
+
y: UnitValueSchema
|
|
80
|
+
});
|
|
81
|
+
var BoundsSchema = z2.object({
|
|
82
|
+
width: UnitValueSchema,
|
|
83
|
+
height: UnitValueSchema
|
|
84
|
+
});
|
|
85
|
+
var AnchorPointSchema = z2.object({
|
|
86
|
+
x: z2.number().min(0).max(1),
|
|
87
|
+
y: z2.number().min(0).max(1)
|
|
88
|
+
});
|
|
89
|
+
var RGBAColorSchema = z3.object({
|
|
90
|
+
r: z3.number().min(0).max(255),
|
|
91
|
+
g: z3.number().min(0).max(255),
|
|
92
|
+
b: z3.number().min(0).max(255),
|
|
93
|
+
a: z3.number().min(0).max(1)
|
|
94
|
+
});
|
|
95
|
+
var HSLAColorSchema = z3.object({
|
|
96
|
+
h: z3.number().min(0).max(360),
|
|
97
|
+
s: z3.number().min(0).max(100),
|
|
98
|
+
l: z3.number().min(0).max(100),
|
|
99
|
+
a: z3.number().min(0).max(1)
|
|
100
|
+
});
|
|
101
|
+
var HexColorSchema = z3.string().regex(/^#([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/, {
|
|
102
|
+
message: "Color must be a hex string: #RGB, #RGBA, #RRGGBB, or #RRGGBBAA"
|
|
103
|
+
});
|
|
104
|
+
var ColorSchema = z3.union([RGBAColorSchema, HSLAColorSchema, HexColorSchema]);
|
|
105
|
+
var PathPointSchema = z4.object({
|
|
106
|
+
x: z4.number(),
|
|
107
|
+
y: z4.number(),
|
|
108
|
+
in: z4.object({ x: z4.number(), y: z4.number() }).optional(),
|
|
109
|
+
out: z4.object({ x: z4.number(), y: z4.number() }).optional()
|
|
110
|
+
});
|
|
111
|
+
var RectShapeSchema = z4.object({
|
|
112
|
+
type: z4.literal("rect"),
|
|
113
|
+
cornerRadius: z4.union([
|
|
114
|
+
z4.number().min(0),
|
|
115
|
+
z4.tuple([z4.number().min(0), z4.number().min(0), z4.number().min(0), z4.number().min(0)])
|
|
116
|
+
]).optional()
|
|
117
|
+
});
|
|
118
|
+
var EllipseShapeSchema = z4.object({
|
|
119
|
+
type: z4.literal("ellipse")
|
|
120
|
+
});
|
|
121
|
+
var PathShapeSchema = z4.object({
|
|
122
|
+
type: z4.literal("path"),
|
|
123
|
+
points: z4.array(PathPointSchema).min(2, "Path must have at least 2 points"),
|
|
124
|
+
closed: z4.boolean().optional()
|
|
125
|
+
});
|
|
126
|
+
var ShapeSchema = z4.discriminatedUnion("type", [
|
|
127
|
+
RectShapeSchema,
|
|
128
|
+
EllipseShapeSchema,
|
|
129
|
+
PathShapeSchema
|
|
130
|
+
]);
|
|
131
|
+
var GradientStopSchema = z4.object({
|
|
132
|
+
offset: z4.number().min(0).max(1),
|
|
133
|
+
color: ColorSchema
|
|
134
|
+
});
|
|
135
|
+
var SolidFillSchema = z4.object({
|
|
136
|
+
type: z4.literal("solid"),
|
|
137
|
+
color: ColorSchema
|
|
138
|
+
});
|
|
139
|
+
var LinearGradientFillSchema = z4.object({
|
|
140
|
+
type: z4.literal("linear-gradient"),
|
|
141
|
+
angle: z4.number(),
|
|
142
|
+
stops: z4.array(GradientStopSchema).min(2, "Gradient needs at least 2 stops")
|
|
143
|
+
});
|
|
144
|
+
var RadialGradientFillSchema = z4.object({
|
|
145
|
+
type: z4.literal("radial-gradient"),
|
|
146
|
+
center: z4.object({ x: UnitValueSchema, y: UnitValueSchema }),
|
|
147
|
+
radius: UnitValueSchema,
|
|
148
|
+
stops: z4.array(GradientStopSchema).min(2, "Gradient needs at least 2 stops")
|
|
149
|
+
});
|
|
150
|
+
var FillSchema = z4.discriminatedUnion("type", [
|
|
151
|
+
SolidFillSchema,
|
|
152
|
+
LinearGradientFillSchema,
|
|
153
|
+
RadialGradientFillSchema
|
|
154
|
+
]);
|
|
155
|
+
var StrokeSchema = z4.object({
|
|
156
|
+
color: ColorSchema,
|
|
157
|
+
width: z4.number().min(0),
|
|
158
|
+
dash: z4.array(z4.number().min(0)).optional(),
|
|
159
|
+
lineCap: z4.enum(["butt", "round", "square"]).optional(),
|
|
160
|
+
lineJoin: z4.enum(["miter", "round", "bevel"]).optional(),
|
|
161
|
+
strokeStart: z4.number().min(0).max(1).optional(),
|
|
162
|
+
strokeEnd: z4.number().min(0).max(1).optional()
|
|
163
|
+
});
|
|
164
|
+
var TextStyleSchema = z4.object({
|
|
165
|
+
fontFamily: z4.string().min(1, "fontFamily is required"),
|
|
166
|
+
fontSize: z4.number().positive("fontSize must be positive"),
|
|
167
|
+
fontWeight: z4.union([z4.number(), z4.enum(["normal", "bold"])]).optional(),
|
|
168
|
+
fontStyle: z4.enum(["normal", "italic"]).optional(),
|
|
169
|
+
textAlign: z4.enum(["left", "center", "right"]).optional(),
|
|
170
|
+
lineHeight: z4.number().positive().optional(),
|
|
171
|
+
letterSpacing: z4.number().optional(),
|
|
172
|
+
color: ColorSchema
|
|
173
|
+
});
|
|
174
|
+
var LinearEasingSchema = z5.object({ type: z5.literal("linear") });
|
|
175
|
+
var CubicBezierEasingSchema = z5.object({
|
|
176
|
+
type: z5.literal("cubic-bezier"),
|
|
177
|
+
x1: z5.number().min(0).max(1),
|
|
178
|
+
y1: z5.number(),
|
|
179
|
+
x2: z5.number().min(0).max(1),
|
|
180
|
+
y2: z5.number()
|
|
181
|
+
});
|
|
182
|
+
var SpringEasingSchema = z5.object({
|
|
183
|
+
type: z5.literal("spring"),
|
|
184
|
+
mass: z5.number().positive().optional(),
|
|
185
|
+
stiffness: z5.number().positive().optional(),
|
|
186
|
+
damping: z5.number().positive().optional(),
|
|
187
|
+
velocity: z5.number().optional()
|
|
188
|
+
});
|
|
189
|
+
var StepEasingSchema = z5.object({
|
|
190
|
+
type: z5.literal("step"),
|
|
191
|
+
steps: z5.number().int().positive(),
|
|
192
|
+
position: z5.enum(["start", "end"]).optional()
|
|
193
|
+
});
|
|
194
|
+
var EasingPresetSchema = z5.enum(["ease-in", "ease-out", "ease-in-out"]);
|
|
195
|
+
var EasingSchema = z5.union([
|
|
196
|
+
LinearEasingSchema,
|
|
197
|
+
CubicBezierEasingSchema,
|
|
198
|
+
SpringEasingSchema,
|
|
199
|
+
StepEasingSchema,
|
|
200
|
+
EasingPresetSchema
|
|
201
|
+
]);
|
|
202
|
+
var ShadowSchema = z6.object({
|
|
203
|
+
color: ColorSchema,
|
|
204
|
+
blur: z6.number().min(0),
|
|
205
|
+
offsetX: z6.number().optional(),
|
|
206
|
+
offsetY: z6.number().optional()
|
|
207
|
+
});
|
|
208
|
+
var ShapeVisualSchema = z7.object({
|
|
209
|
+
type: z7.literal("shape"),
|
|
210
|
+
shape: ShapeSchema,
|
|
211
|
+
fill: FillSchema.optional(),
|
|
212
|
+
stroke: StrokeSchema.optional()
|
|
213
|
+
});
|
|
214
|
+
var TextVisualSchema = z7.object({
|
|
215
|
+
type: z7.literal("text"),
|
|
216
|
+
content: z7.string(),
|
|
217
|
+
style: TextStyleSchema
|
|
218
|
+
});
|
|
219
|
+
var ImageVisualSchema = z7.object({
|
|
220
|
+
type: z7.literal("image"),
|
|
221
|
+
assetId: z7.string().min(1, "assetId is required"),
|
|
222
|
+
src: z7.string().optional()
|
|
223
|
+
});
|
|
224
|
+
var GroupVisualSchema = z7.object({
|
|
225
|
+
type: z7.literal("group")
|
|
226
|
+
});
|
|
227
|
+
var RefVisualSchema = z7.object({
|
|
228
|
+
type: z7.literal("ref"),
|
|
229
|
+
src: z7.string().min(1, "src is required")
|
|
230
|
+
});
|
|
231
|
+
var VisualSchema = z7.discriminatedUnion("type", [
|
|
232
|
+
ShapeVisualSchema,
|
|
233
|
+
TextVisualSchema,
|
|
234
|
+
ImageVisualSchema,
|
|
235
|
+
GroupVisualSchema,
|
|
236
|
+
RefVisualSchema
|
|
237
|
+
]);
|
|
238
|
+
var LayerSchema = z7.object({
|
|
239
|
+
id: z7.string().min(1, "Layer id is required"),
|
|
240
|
+
description: z7.string().optional(),
|
|
241
|
+
tags: z7.array(z7.string()).optional(),
|
|
242
|
+
visual: VisualSchema,
|
|
243
|
+
frame: FrameSchema,
|
|
244
|
+
bounds: BoundsSchema,
|
|
245
|
+
anchorPoint: AnchorPointSchema.optional(),
|
|
246
|
+
parentId: z7.string().optional(),
|
|
247
|
+
opacity: z7.number().min(0).max(1).optional(),
|
|
248
|
+
rotation: z7.number().optional(),
|
|
249
|
+
scale: z7.object({ x: z7.number(), y: z7.number() }).optional(),
|
|
250
|
+
visible: z7.boolean().optional(),
|
|
251
|
+
shadow: ShadowSchema.optional()
|
|
252
|
+
});
|
|
253
|
+
var AnimatablePropertySchema = z8.enum([
|
|
254
|
+
"frame.x",
|
|
255
|
+
"frame.y",
|
|
256
|
+
"bounds.width",
|
|
257
|
+
"bounds.height",
|
|
258
|
+
"opacity",
|
|
259
|
+
"rotation",
|
|
260
|
+
"scale.x",
|
|
261
|
+
"scale.y",
|
|
262
|
+
"anchorPoint.x",
|
|
263
|
+
"anchorPoint.y",
|
|
264
|
+
"visual.shape.cornerRadius",
|
|
265
|
+
"visual.fill.color",
|
|
266
|
+
"visual.stroke.color",
|
|
267
|
+
"visual.stroke.width",
|
|
268
|
+
"visual.stroke.start",
|
|
269
|
+
"visual.stroke.end",
|
|
270
|
+
"visual.style.fontSize",
|
|
271
|
+
"visual.style.color",
|
|
272
|
+
"shadow.color",
|
|
273
|
+
"shadow.blur",
|
|
274
|
+
"shadow.offsetX",
|
|
275
|
+
"shadow.offsetY"
|
|
276
|
+
]);
|
|
277
|
+
var FrameRangeSchema = z8.tuple([
|
|
278
|
+
z8.number().int().min(0, "Frame start must be >= 0"),
|
|
279
|
+
z8.number().int().min(0, "Frame end must be >= 0")
|
|
280
|
+
]).refine(([start, end]) => end >= start, {
|
|
281
|
+
message: "Frame range end must be >= start"
|
|
282
|
+
});
|
|
283
|
+
var DeltaSchema = z8.object({
|
|
284
|
+
id: z8.string().optional(),
|
|
285
|
+
name: z8.string().optional(),
|
|
286
|
+
layer: z8.string().min(1, "Delta must reference a layer id"),
|
|
287
|
+
property: AnimatablePropertySchema,
|
|
288
|
+
range: FrameRangeSchema,
|
|
289
|
+
from: z8.unknown(),
|
|
290
|
+
to: z8.unknown(),
|
|
291
|
+
easing: EasingSchema.optional(),
|
|
292
|
+
description: z8.string().optional(),
|
|
293
|
+
tags: z8.array(z8.string()).optional()
|
|
294
|
+
});
|
|
295
|
+
var StateSchema = z9.object({
|
|
296
|
+
description: z9.string().optional(),
|
|
297
|
+
tags: z9.array(z9.string()).optional(),
|
|
298
|
+
duration: z9.number().int().positive("State duration must be a positive integer (frames)"),
|
|
299
|
+
deltas: z9.array(DeltaSchema)
|
|
300
|
+
});
|
|
301
|
+
var PresetDeltaSchema = z10.object({
|
|
302
|
+
property: AnimatablePropertySchema,
|
|
303
|
+
offset: z10.tuple([z10.number().int().min(0), z10.number().int().min(0)]).optional(),
|
|
304
|
+
from: z10.unknown(),
|
|
305
|
+
to: z10.unknown(),
|
|
306
|
+
easing: EasingSchema.optional()
|
|
307
|
+
});
|
|
308
|
+
var PresetSchema = z10.object({
|
|
309
|
+
description: z10.string().optional(),
|
|
310
|
+
tags: z10.array(z10.string()).optional(),
|
|
311
|
+
deltas: z10.array(PresetDeltaSchema).min(1, "Preset must have at least one delta")
|
|
312
|
+
});
|
|
313
|
+
var VariableTypeSchema = z11.enum(["string", "number", "color", "asset", "boolean"]);
|
|
314
|
+
var VariableSchema = z11.object({
|
|
315
|
+
type: VariableTypeSchema,
|
|
316
|
+
default: z11.unknown().optional(),
|
|
317
|
+
description: z11.string().optional()
|
|
318
|
+
});
|
|
319
|
+
var AssetTypeSchema = z12.enum(["image", "svg", "font", "animation"]);
|
|
320
|
+
var AssetSchema = z12.object({
|
|
321
|
+
type: AssetTypeSchema,
|
|
322
|
+
src: z12.string().min(1, "Asset src is required"),
|
|
323
|
+
description: z12.string().optional()
|
|
324
|
+
});
|
|
325
|
+
var CanvasSchema = z13.object({
|
|
326
|
+
width: z13.number().int().positive("Canvas width must be a positive integer"),
|
|
327
|
+
height: z13.number().int().positive("Canvas height must be a positive integer"),
|
|
328
|
+
fps: z13.number().int().positive("FPS must be a positive integer"),
|
|
329
|
+
background: z13.string().optional()
|
|
330
|
+
});
|
|
331
|
+
var AtelierDocumentSchema = z13.object({
|
|
332
|
+
version: z13.string().min(1, "Version is required"),
|
|
333
|
+
name: z13.string().min(1, "Animation name is required"),
|
|
334
|
+
description: z13.string().optional(),
|
|
335
|
+
tags: z13.array(z13.string()).optional(),
|
|
336
|
+
canvas: CanvasSchema,
|
|
337
|
+
variables: z13.record(z13.string(), VariableSchema).optional(),
|
|
338
|
+
assets: z13.record(z13.string(), AssetSchema).optional(),
|
|
339
|
+
presets: z13.record(z13.string(), PresetSchema).optional(),
|
|
340
|
+
layers: z13.array(LayerSchema),
|
|
341
|
+
states: z13.record(z13.string(), StateSchema)
|
|
342
|
+
});
|
|
343
|
+
function formatErrors(error) {
|
|
344
|
+
return error.issues.map((issue) => ({
|
|
345
|
+
path: issue.path.join(".") || "(root)",
|
|
346
|
+
message: issue.message
|
|
347
|
+
}));
|
|
348
|
+
}
|
|
349
|
+
function validateDocument(input) {
|
|
350
|
+
const result = AtelierDocumentSchema.safeParse(input);
|
|
351
|
+
if (result.success) {
|
|
352
|
+
return { success: true, data: result.data };
|
|
353
|
+
}
|
|
354
|
+
return { success: false, errors: formatErrors(result.error) };
|
|
355
|
+
}
|
|
356
|
+
function parseAtelier(yamlString) {
|
|
357
|
+
let parsed;
|
|
358
|
+
try {
|
|
359
|
+
parsed = yamlParse(yamlString);
|
|
360
|
+
} catch (err9) {
|
|
361
|
+
return {
|
|
362
|
+
success: false,
|
|
363
|
+
errors: [{ path: "(yaml)", message: `YAML parse error: ${err9.message}` }]
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
return validateDocument(parsed);
|
|
367
|
+
}
|
|
368
|
+
function serializeAtelier(doc) {
|
|
369
|
+
return yamlStringify(doc, { indent: 2 });
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// ../mcp/src/tools/document.ts
|
|
373
|
+
function getDoc(store, id) {
|
|
374
|
+
const doc = store.get(id);
|
|
375
|
+
if (!doc) return { error: `Document "${id}" not found` };
|
|
376
|
+
return { doc };
|
|
377
|
+
}
|
|
378
|
+
function ok(data) {
|
|
379
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...data }) }] };
|
|
380
|
+
}
|
|
381
|
+
function err(message) {
|
|
382
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
|
|
383
|
+
}
|
|
384
|
+
function register(server, store) {
|
|
385
|
+
server.tool(
|
|
386
|
+
"atelier_create",
|
|
387
|
+
"Create a new Atelier animation document with canvas settings",
|
|
388
|
+
{
|
|
389
|
+
name: z14.string().describe("Animation name"),
|
|
390
|
+
width: z14.number().positive().describe("Canvas width in pixels"),
|
|
391
|
+
height: z14.number().positive().describe("Canvas height in pixels"),
|
|
392
|
+
fps: z14.number().positive().int().describe("Frames per second"),
|
|
393
|
+
background: z14.string().optional().describe("Background color (hex string)"),
|
|
394
|
+
description: z14.string().optional().describe("Animation description"),
|
|
395
|
+
tags: z14.array(z14.string()).optional().describe("Tags for categorization")
|
|
396
|
+
},
|
|
397
|
+
async ({ name, width, height, fps, background, description, tags }) => {
|
|
398
|
+
const doc = {
|
|
399
|
+
version: "1.0",
|
|
400
|
+
name,
|
|
401
|
+
canvas: { width, height, fps, ...background ? { background } : {} },
|
|
402
|
+
layers: [],
|
|
403
|
+
states: {},
|
|
404
|
+
...description ? { description } : {},
|
|
405
|
+
...tags ? { tags } : {}
|
|
406
|
+
};
|
|
407
|
+
const validation = validateDocument(doc);
|
|
408
|
+
if (!validation.success) {
|
|
409
|
+
return err(`Validation failed: ${validation.errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`);
|
|
410
|
+
}
|
|
411
|
+
const id = store.create(doc);
|
|
412
|
+
return ok({ id, name: doc.name, canvas: doc.canvas });
|
|
413
|
+
}
|
|
414
|
+
);
|
|
415
|
+
server.tool(
|
|
416
|
+
"atelier_info",
|
|
417
|
+
"Get summary information about an Atelier document",
|
|
418
|
+
{
|
|
419
|
+
id: z14.string().describe("Document ID")
|
|
420
|
+
},
|
|
421
|
+
async ({ id }) => {
|
|
422
|
+
const result = getDoc(store, id);
|
|
423
|
+
if ("error" in result) return err(result.error);
|
|
424
|
+
const { doc } = result;
|
|
425
|
+
const stateNames = Object.keys(doc.states);
|
|
426
|
+
const totalDuration = stateNames.reduce(
|
|
427
|
+
(sum, name) => sum + doc.states[name].duration,
|
|
428
|
+
0
|
|
429
|
+
);
|
|
430
|
+
return ok({
|
|
431
|
+
name: doc.name,
|
|
432
|
+
description: doc.description,
|
|
433
|
+
tags: doc.tags,
|
|
434
|
+
canvas: doc.canvas,
|
|
435
|
+
layerCount: doc.layers.length,
|
|
436
|
+
stateNames,
|
|
437
|
+
totalDuration,
|
|
438
|
+
presetNames: doc.presets ? Object.keys(doc.presets) : []
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
);
|
|
442
|
+
server.tool(
|
|
443
|
+
"atelier_load",
|
|
444
|
+
"Load an Atelier document from a YAML string",
|
|
445
|
+
{
|
|
446
|
+
id: z14.string().optional().describe("Custom document ID (auto-generated if omitted)"),
|
|
447
|
+
yaml: z14.string().describe("YAML string representing an Atelier document")
|
|
448
|
+
},
|
|
449
|
+
async ({ id, yaml }) => {
|
|
450
|
+
const parsed = parseAtelier(yaml);
|
|
451
|
+
if (!parsed.success) {
|
|
452
|
+
return err(`Parse/validation failed: ${parsed.errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`);
|
|
453
|
+
}
|
|
454
|
+
const docId = store.create(parsed.data, id);
|
|
455
|
+
return ok({ id: docId, name: parsed.data.name });
|
|
456
|
+
}
|
|
457
|
+
);
|
|
458
|
+
server.tool(
|
|
459
|
+
"atelier_export",
|
|
460
|
+
"Export an Atelier document as a YAML string",
|
|
461
|
+
{
|
|
462
|
+
id: z14.string().describe("Document ID")
|
|
463
|
+
},
|
|
464
|
+
async ({ id }) => {
|
|
465
|
+
const result = getDoc(store, id);
|
|
466
|
+
if ("error" in result) return err(result.error);
|
|
467
|
+
const yaml = serializeAtelier(result.doc);
|
|
468
|
+
return { content: [{ type: "text", text: yaml }] };
|
|
469
|
+
}
|
|
470
|
+
);
|
|
471
|
+
server.tool(
|
|
472
|
+
"atelier_list",
|
|
473
|
+
"List all Atelier documents in the store",
|
|
474
|
+
{},
|
|
475
|
+
async () => {
|
|
476
|
+
const docs = store.list();
|
|
477
|
+
return ok({ documents: docs, count: docs.length });
|
|
478
|
+
}
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// ../mcp/src/tools/layers.ts
|
|
483
|
+
import { z as z15 } from "zod";
|
|
484
|
+
function getDoc2(store, id) {
|
|
485
|
+
const doc = store.get(id);
|
|
486
|
+
if (!doc) return { error: `Document "${id}" not found` };
|
|
487
|
+
return { doc };
|
|
488
|
+
}
|
|
489
|
+
function ok2(data) {
|
|
490
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...data }) }] };
|
|
491
|
+
}
|
|
492
|
+
function err2(message) {
|
|
493
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
|
|
494
|
+
}
|
|
495
|
+
var VisualInputSchema = z15.object({
|
|
496
|
+
type: z15.enum(["shape", "text", "image", "group", "ref"]).describe("Visual type"),
|
|
497
|
+
// shape visual fields
|
|
498
|
+
shape: z15.object({
|
|
499
|
+
type: z15.enum(["rect", "ellipse", "path"]),
|
|
500
|
+
cornerRadius: z15.union([z15.number(), z15.tuple([z15.number(), z15.number(), z15.number(), z15.number()])]).optional(),
|
|
501
|
+
points: z15.array(z15.object({
|
|
502
|
+
x: z15.number(),
|
|
503
|
+
y: z15.number(),
|
|
504
|
+
in: z15.object({ x: z15.number(), y: z15.number() }).optional(),
|
|
505
|
+
out: z15.object({ x: z15.number(), y: z15.number() }).optional()
|
|
506
|
+
})).optional(),
|
|
507
|
+
closed: z15.boolean().optional()
|
|
508
|
+
}).optional(),
|
|
509
|
+
fill: z15.record(z15.unknown()).optional(),
|
|
510
|
+
stroke: z15.record(z15.unknown()).optional(),
|
|
511
|
+
// text visual fields
|
|
512
|
+
content: z15.string().optional(),
|
|
513
|
+
style: z15.record(z15.unknown()).optional(),
|
|
514
|
+
// image visual fields
|
|
515
|
+
assetId: z15.string().optional(),
|
|
516
|
+
// ref visual fields
|
|
517
|
+
src: z15.string().optional()
|
|
518
|
+
}).describe("Visual content definition");
|
|
519
|
+
function register2(server, store) {
|
|
520
|
+
server.tool(
|
|
521
|
+
"atelier_add_layer",
|
|
522
|
+
"Add a new layer to an Atelier document",
|
|
523
|
+
{
|
|
524
|
+
id: z15.string().describe("Document ID"),
|
|
525
|
+
layerId: z15.string().describe("Unique layer ID"),
|
|
526
|
+
visual: VisualInputSchema.describe("Visual content definition"),
|
|
527
|
+
x: z15.union([z15.number(), z15.string()]).describe("X position (pixels or percentage)"),
|
|
528
|
+
y: z15.union([z15.number(), z15.string()]).describe("Y position (pixels or percentage)"),
|
|
529
|
+
width: z15.union([z15.number(), z15.string()]).describe("Width (pixels or percentage)"),
|
|
530
|
+
height: z15.union([z15.number(), z15.string()]).describe("Height (pixels or percentage)"),
|
|
531
|
+
description: z15.string().optional().describe("Layer description"),
|
|
532
|
+
tags: z15.array(z15.string()).optional().describe("Tags"),
|
|
533
|
+
opacity: z15.number().min(0).max(1).optional().describe("Opacity 0-1"),
|
|
534
|
+
rotation: z15.number().optional().describe("Rotation in degrees"),
|
|
535
|
+
parentId: z15.string().optional().describe("Parent layer ID for transform inheritance"),
|
|
536
|
+
anchorPoint: z15.object({ x: z15.number(), y: z15.number() }).optional().describe("Anchor point (0-1 normalized)"),
|
|
537
|
+
scale: z15.object({ x: z15.number(), y: z15.number() }).optional().describe("Scale factors"),
|
|
538
|
+
visible: z15.boolean().optional().describe("Whether layer is visible")
|
|
539
|
+
},
|
|
540
|
+
async ({ id, layerId, visual, x, y, width, height, description, tags, opacity, rotation, parentId, anchorPoint, scale, visible }) => {
|
|
541
|
+
const result = getDoc2(store, id);
|
|
542
|
+
if ("error" in result) return err2(result.error);
|
|
543
|
+
const { doc } = result;
|
|
544
|
+
if (doc.layers.some((l) => l.id === layerId)) {
|
|
545
|
+
return err2(`Layer "${layerId}" already exists in document "${id}"`);
|
|
546
|
+
}
|
|
547
|
+
if (parentId && !doc.layers.some((l) => l.id === parentId)) {
|
|
548
|
+
return err2(`Parent layer "${parentId}" not found`);
|
|
549
|
+
}
|
|
550
|
+
const layer = {
|
|
551
|
+
id: layerId,
|
|
552
|
+
visual,
|
|
553
|
+
frame: { x, y },
|
|
554
|
+
bounds: { width, height },
|
|
555
|
+
...description != null ? { description } : {},
|
|
556
|
+
...tags ? { tags } : {},
|
|
557
|
+
...opacity != null ? { opacity } : {},
|
|
558
|
+
...rotation != null ? { rotation } : {},
|
|
559
|
+
...parentId ? { parentId } : {},
|
|
560
|
+
...anchorPoint ? { anchorPoint } : {},
|
|
561
|
+
...scale ? { scale } : {},
|
|
562
|
+
...visible != null ? { visible } : {}
|
|
563
|
+
};
|
|
564
|
+
doc.layers.push(layer);
|
|
565
|
+
return ok2({ layerId, index: doc.layers.length - 1 });
|
|
566
|
+
}
|
|
567
|
+
);
|
|
568
|
+
server.tool(
|
|
569
|
+
"atelier_edit_layer",
|
|
570
|
+
"Edit properties of an existing layer",
|
|
571
|
+
{
|
|
572
|
+
id: z15.string().describe("Document ID"),
|
|
573
|
+
layerId: z15.string().describe("Layer ID to edit"),
|
|
574
|
+
x: z15.union([z15.number(), z15.string()]).optional().describe("New X position"),
|
|
575
|
+
y: z15.union([z15.number(), z15.string()]).optional().describe("New Y position"),
|
|
576
|
+
width: z15.union([z15.number(), z15.string()]).optional().describe("New width"),
|
|
577
|
+
height: z15.union([z15.number(), z15.string()]).optional().describe("New height"),
|
|
578
|
+
description: z15.string().optional().describe("New description"),
|
|
579
|
+
tags: z15.array(z15.string()).optional().describe("New tags"),
|
|
580
|
+
opacity: z15.number().min(0).max(1).optional().describe("New opacity"),
|
|
581
|
+
rotation: z15.number().optional().describe("New rotation"),
|
|
582
|
+
parentId: z15.string().nullable().optional().describe("New parent layer ID (null to clear)"),
|
|
583
|
+
anchorPoint: z15.object({ x: z15.number(), y: z15.number() }).optional().describe("New anchor point"),
|
|
584
|
+
scale: z15.object({ x: z15.number(), y: z15.number() }).optional().describe("New scale"),
|
|
585
|
+
visible: z15.boolean().optional().describe("New visibility")
|
|
586
|
+
},
|
|
587
|
+
async ({ id, layerId, x, y, width, height, description, tags, opacity, rotation, parentId, anchorPoint, scale, visible }) => {
|
|
588
|
+
const result = getDoc2(store, id);
|
|
589
|
+
if ("error" in result) return err2(result.error);
|
|
590
|
+
const { doc } = result;
|
|
591
|
+
const layer = doc.layers.find((l) => l.id === layerId);
|
|
592
|
+
if (!layer) return err2(`Layer "${layerId}" not found`);
|
|
593
|
+
if (x != null) layer.frame.x = x;
|
|
594
|
+
if (y != null) layer.frame.y = y;
|
|
595
|
+
if (width != null) layer.bounds.width = width;
|
|
596
|
+
if (height != null) layer.bounds.height = height;
|
|
597
|
+
if (description != null) layer.description = description;
|
|
598
|
+
if (tags) layer.tags = tags;
|
|
599
|
+
if (opacity != null) layer.opacity = opacity;
|
|
600
|
+
if (rotation != null) layer.rotation = rotation;
|
|
601
|
+
if (parentId !== void 0) {
|
|
602
|
+
if (parentId === null) {
|
|
603
|
+
delete layer.parentId;
|
|
604
|
+
} else {
|
|
605
|
+
if (!doc.layers.some((l) => l.id === parentId)) return err2(`Parent layer "${parentId}" not found`);
|
|
606
|
+
layer.parentId = parentId;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
if (anchorPoint) layer.anchorPoint = anchorPoint;
|
|
610
|
+
if (scale) layer.scale = scale;
|
|
611
|
+
if (visible != null) layer.visible = visible;
|
|
612
|
+
return ok2({ layerId, updated: true });
|
|
613
|
+
}
|
|
614
|
+
);
|
|
615
|
+
server.tool(
|
|
616
|
+
"atelier_remove_layer",
|
|
617
|
+
"Remove a layer from an Atelier document",
|
|
618
|
+
{
|
|
619
|
+
id: z15.string().describe("Document ID"),
|
|
620
|
+
layerId: z15.string().describe("Layer ID to remove")
|
|
621
|
+
},
|
|
622
|
+
async ({ id, layerId }) => {
|
|
623
|
+
const result = getDoc2(store, id);
|
|
624
|
+
if ("error" in result) return err2(result.error);
|
|
625
|
+
const { doc } = result;
|
|
626
|
+
const index = doc.layers.findIndex((l) => l.id === layerId);
|
|
627
|
+
if (index === -1) return err2(`Layer "${layerId}" not found`);
|
|
628
|
+
const warnings = [];
|
|
629
|
+
for (const [stateName, state] of Object.entries(doc.states)) {
|
|
630
|
+
const deltaCount = state.deltas.filter((d) => d.layer === layerId).length;
|
|
631
|
+
if (deltaCount > 0) {
|
|
632
|
+
warnings.push(`State "${stateName}" has ${deltaCount} delta(s) referencing this layer`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
const childCount = doc.layers.filter((l) => l.parentId === layerId).length;
|
|
636
|
+
if (childCount > 0) {
|
|
637
|
+
warnings.push(`${childCount} child layer(s) reference this layer as parent`);
|
|
638
|
+
}
|
|
639
|
+
doc.layers.splice(index, 1);
|
|
640
|
+
return ok2({ layerId, removed: true, warnings });
|
|
641
|
+
}
|
|
642
|
+
);
|
|
643
|
+
server.tool(
|
|
644
|
+
"atelier_list_layers",
|
|
645
|
+
"List all layers in an Atelier document",
|
|
646
|
+
{
|
|
647
|
+
id: z15.string().describe("Document ID")
|
|
648
|
+
},
|
|
649
|
+
async ({ id }) => {
|
|
650
|
+
const result = getDoc2(store, id);
|
|
651
|
+
if ("error" in result) return err2(result.error);
|
|
652
|
+
const { doc } = result;
|
|
653
|
+
const layers = doc.layers.map((l, index) => ({
|
|
654
|
+
index,
|
|
655
|
+
id: l.id,
|
|
656
|
+
type: l.visual.type,
|
|
657
|
+
frame: l.frame,
|
|
658
|
+
bounds: l.bounds,
|
|
659
|
+
description: l.description,
|
|
660
|
+
tags: l.tags,
|
|
661
|
+
opacity: l.opacity,
|
|
662
|
+
rotation: l.rotation,
|
|
663
|
+
parentId: l.parentId,
|
|
664
|
+
visible: l.visible
|
|
665
|
+
}));
|
|
666
|
+
return ok2({ layers, count: layers.length });
|
|
667
|
+
}
|
|
668
|
+
);
|
|
669
|
+
server.tool(
|
|
670
|
+
"atelier_reorder",
|
|
671
|
+
"Move a layer to a new position in the layer stack",
|
|
672
|
+
{
|
|
673
|
+
id: z15.string().describe("Document ID"),
|
|
674
|
+
layerId: z15.string().describe("Layer ID to move"),
|
|
675
|
+
position: z15.number().int().min(0).describe("Target position index (0-based)")
|
|
676
|
+
},
|
|
677
|
+
async ({ id, layerId, position }) => {
|
|
678
|
+
const result = getDoc2(store, id);
|
|
679
|
+
if ("error" in result) return err2(result.error);
|
|
680
|
+
const { doc } = result;
|
|
681
|
+
const currentIndex = doc.layers.findIndex((l) => l.id === layerId);
|
|
682
|
+
if (currentIndex === -1) return err2(`Layer "${layerId}" not found`);
|
|
683
|
+
const targetPos = Math.min(position, doc.layers.length - 1);
|
|
684
|
+
const [layer] = doc.layers.splice(currentIndex, 1);
|
|
685
|
+
doc.layers.splice(targetPos, 0, layer);
|
|
686
|
+
return ok2({ layerId, from: currentIndex, to: targetPos });
|
|
687
|
+
}
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// ../mcp/src/tools/shapes.ts
|
|
692
|
+
import { z as z16 } from "zod";
|
|
693
|
+
function getDoc3(store, id) {
|
|
694
|
+
const doc = store.get(id);
|
|
695
|
+
if (!doc) return { error: `Document "${id}" not found` };
|
|
696
|
+
return { doc };
|
|
697
|
+
}
|
|
698
|
+
function ok3(data) {
|
|
699
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...data }) }] };
|
|
700
|
+
}
|
|
701
|
+
function err3(message) {
|
|
702
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
|
|
703
|
+
}
|
|
704
|
+
function register3(server, store) {
|
|
705
|
+
server.tool(
|
|
706
|
+
"atelier_set_shape",
|
|
707
|
+
"Set the shape on a shape-type layer",
|
|
708
|
+
{
|
|
709
|
+
id: z16.string().describe("Document ID"),
|
|
710
|
+
layerId: z16.string().describe("Layer ID (must be a shape visual)"),
|
|
711
|
+
shape: z16.object({
|
|
712
|
+
type: z16.enum(["rect", "ellipse", "path"]).describe("Shape type"),
|
|
713
|
+
cornerRadius: z16.union([
|
|
714
|
+
z16.number(),
|
|
715
|
+
z16.tuple([z16.number(), z16.number(), z16.number(), z16.number()])
|
|
716
|
+
]).optional().describe("Corner radius (rect only)"),
|
|
717
|
+
points: z16.array(z16.object({
|
|
718
|
+
x: z16.number(),
|
|
719
|
+
y: z16.number(),
|
|
720
|
+
in: z16.object({ x: z16.number(), y: z16.number() }).optional(),
|
|
721
|
+
out: z16.object({ x: z16.number(), y: z16.number() }).optional()
|
|
722
|
+
})).optional().describe("Path points (path only)"),
|
|
723
|
+
closed: z16.boolean().optional().describe("Whether path is closed (path only)")
|
|
724
|
+
}).describe("Shape definition")
|
|
725
|
+
},
|
|
726
|
+
async ({ id, layerId, shape }) => {
|
|
727
|
+
const result = getDoc3(store, id);
|
|
728
|
+
if ("error" in result) return err3(result.error);
|
|
729
|
+
const { doc } = result;
|
|
730
|
+
const layer = doc.layers.find((l) => l.id === layerId);
|
|
731
|
+
if (!layer) return err3(`Layer "${layerId}" not found`);
|
|
732
|
+
if (layer.visual.type !== "shape") return err3(`Layer "${layerId}" is type "${layer.visual.type}", not "shape"`);
|
|
733
|
+
layer.visual.shape = shape;
|
|
734
|
+
return ok3({ layerId, shape: shape.type });
|
|
735
|
+
}
|
|
736
|
+
);
|
|
737
|
+
server.tool(
|
|
738
|
+
"atelier_set_fill",
|
|
739
|
+
"Set the fill on a shape-type layer",
|
|
740
|
+
{
|
|
741
|
+
id: z16.string().describe("Document ID"),
|
|
742
|
+
layerId: z16.string().describe("Layer ID (must be a shape visual)"),
|
|
743
|
+
fill: z16.object({
|
|
744
|
+
type: z16.enum(["solid", "linear-gradient", "radial-gradient"]).describe("Fill type"),
|
|
745
|
+
color: z16.unknown().optional().describe("Color for solid fill (hex string or RGBA/HSLA object)"),
|
|
746
|
+
angle: z16.number().optional().describe("Angle in degrees (linear-gradient only)"),
|
|
747
|
+
center: z16.object({
|
|
748
|
+
x: z16.union([z16.number(), z16.string()]),
|
|
749
|
+
y: z16.union([z16.number(), z16.string()])
|
|
750
|
+
}).optional().describe("Center point (radial-gradient only)"),
|
|
751
|
+
radius: z16.union([z16.number(), z16.string()]).optional().describe("Radius (radial-gradient only)"),
|
|
752
|
+
stops: z16.array(z16.object({
|
|
753
|
+
offset: z16.number().min(0).max(1),
|
|
754
|
+
color: z16.unknown()
|
|
755
|
+
})).optional().describe("Gradient stops")
|
|
756
|
+
}).describe("Fill definition")
|
|
757
|
+
},
|
|
758
|
+
async ({ id, layerId, fill }) => {
|
|
759
|
+
const result = getDoc3(store, id);
|
|
760
|
+
if ("error" in result) return err3(result.error);
|
|
761
|
+
const { doc } = result;
|
|
762
|
+
const layer = doc.layers.find((l) => l.id === layerId);
|
|
763
|
+
if (!layer) return err3(`Layer "${layerId}" not found`);
|
|
764
|
+
if (layer.visual.type !== "shape") return err3(`Layer "${layerId}" is type "${layer.visual.type}", not "shape"`);
|
|
765
|
+
layer.visual.fill = fill;
|
|
766
|
+
return ok3({ layerId, fillType: fill.type });
|
|
767
|
+
}
|
|
768
|
+
);
|
|
769
|
+
server.tool(
|
|
770
|
+
"atelier_set_stroke",
|
|
771
|
+
"Set the stroke on a shape-type layer",
|
|
772
|
+
{
|
|
773
|
+
id: z16.string().describe("Document ID"),
|
|
774
|
+
layerId: z16.string().describe("Layer ID (must be a shape visual)"),
|
|
775
|
+
stroke: z16.object({
|
|
776
|
+
color: z16.unknown().describe("Stroke color (hex string or RGBA/HSLA object)"),
|
|
777
|
+
width: z16.number().positive().describe("Stroke width in pixels"),
|
|
778
|
+
dash: z16.array(z16.number()).optional().describe("Dash pattern"),
|
|
779
|
+
lineCap: z16.enum(["butt", "round", "square"]).optional().describe("Line cap style"),
|
|
780
|
+
lineJoin: z16.enum(["miter", "round", "bevel"]).optional().describe("Line join style")
|
|
781
|
+
}).describe("Stroke definition")
|
|
782
|
+
},
|
|
783
|
+
async ({ id, layerId, stroke }) => {
|
|
784
|
+
const result = getDoc3(store, id);
|
|
785
|
+
if ("error" in result) return err3(result.error);
|
|
786
|
+
const { doc } = result;
|
|
787
|
+
const layer = doc.layers.find((l) => l.id === layerId);
|
|
788
|
+
if (!layer) return err3(`Layer "${layerId}" not found`);
|
|
789
|
+
if (layer.visual.type !== "shape") return err3(`Layer "${layerId}" is type "${layer.visual.type}", not "shape"`);
|
|
790
|
+
layer.visual.stroke = stroke;
|
|
791
|
+
return ok3({ layerId, strokeWidth: stroke.width });
|
|
792
|
+
}
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// ../mcp/src/tools/states.ts
|
|
797
|
+
import { z as z17 } from "zod";
|
|
798
|
+
function getDoc4(store, id) {
|
|
799
|
+
const doc = store.get(id);
|
|
800
|
+
if (!doc) return { error: `Document "${id}" not found` };
|
|
801
|
+
return { doc };
|
|
802
|
+
}
|
|
803
|
+
function ok4(data) {
|
|
804
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...data }) }] };
|
|
805
|
+
}
|
|
806
|
+
function err4(message) {
|
|
807
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
|
|
808
|
+
}
|
|
809
|
+
function register4(server, store) {
|
|
810
|
+
server.tool(
|
|
811
|
+
"atelier_add_state",
|
|
812
|
+
"Add a named animation state to a document",
|
|
813
|
+
{
|
|
814
|
+
id: z17.string().describe("Document ID"),
|
|
815
|
+
stateName: z17.string().describe("State name (e.g. 'intro', 'hover', 'exit')"),
|
|
816
|
+
duration: z17.number().positive().int().describe("Duration in frames"),
|
|
817
|
+
description: z17.string().optional().describe("State description"),
|
|
818
|
+
tags: z17.array(z17.string()).optional().describe("Tags")
|
|
819
|
+
},
|
|
820
|
+
async ({ id, stateName, duration, description, tags }) => {
|
|
821
|
+
const result = getDoc4(store, id);
|
|
822
|
+
if ("error" in result) return err4(result.error);
|
|
823
|
+
const { doc } = result;
|
|
824
|
+
if (doc.states[stateName]) {
|
|
825
|
+
return err4(`State "${stateName}" already exists`);
|
|
826
|
+
}
|
|
827
|
+
doc.states[stateName] = {
|
|
828
|
+
duration,
|
|
829
|
+
deltas: [],
|
|
830
|
+
...description ? { description } : {},
|
|
831
|
+
...tags ? { tags } : {}
|
|
832
|
+
};
|
|
833
|
+
return ok4({ stateName, duration });
|
|
834
|
+
}
|
|
835
|
+
);
|
|
836
|
+
server.tool(
|
|
837
|
+
"atelier_edit_state",
|
|
838
|
+
"Edit metadata of an existing animation state",
|
|
839
|
+
{
|
|
840
|
+
id: z17.string().describe("Document ID"),
|
|
841
|
+
stateName: z17.string().describe("State name to edit"),
|
|
842
|
+
duration: z17.number().positive().int().optional().describe("New duration in frames"),
|
|
843
|
+
description: z17.string().optional().describe("New description"),
|
|
844
|
+
tags: z17.array(z17.string()).optional().describe("New tags")
|
|
845
|
+
},
|
|
846
|
+
async ({ id, stateName, duration, description, tags }) => {
|
|
847
|
+
const result = getDoc4(store, id);
|
|
848
|
+
if ("error" in result) return err4(result.error);
|
|
849
|
+
const { doc } = result;
|
|
850
|
+
const state = doc.states[stateName];
|
|
851
|
+
if (!state) return err4(`State "${stateName}" not found`);
|
|
852
|
+
if (duration != null) state.duration = duration;
|
|
853
|
+
if (description != null) state.description = description;
|
|
854
|
+
if (tags) state.tags = tags;
|
|
855
|
+
return ok4({ stateName, updated: true });
|
|
856
|
+
}
|
|
857
|
+
);
|
|
858
|
+
server.tool(
|
|
859
|
+
"atelier_remove_state",
|
|
860
|
+
"Remove an animation state and all its deltas",
|
|
861
|
+
{
|
|
862
|
+
id: z17.string().describe("Document ID"),
|
|
863
|
+
stateName: z17.string().describe("State name to remove")
|
|
864
|
+
},
|
|
865
|
+
async ({ id, stateName }) => {
|
|
866
|
+
const result = getDoc4(store, id);
|
|
867
|
+
if ("error" in result) return err4(result.error);
|
|
868
|
+
const { doc } = result;
|
|
869
|
+
if (!doc.states[stateName]) return err4(`State "${stateName}" not found`);
|
|
870
|
+
const deltaCount = doc.states[stateName].deltas.length;
|
|
871
|
+
delete doc.states[stateName];
|
|
872
|
+
return ok4({ stateName, removed: true, deltasRemoved: deltaCount });
|
|
873
|
+
}
|
|
874
|
+
);
|
|
875
|
+
server.tool(
|
|
876
|
+
"atelier_list_states",
|
|
877
|
+
"List all animation states in a document",
|
|
878
|
+
{
|
|
879
|
+
id: z17.string().describe("Document ID")
|
|
880
|
+
},
|
|
881
|
+
async ({ id }) => {
|
|
882
|
+
const result = getDoc4(store, id);
|
|
883
|
+
if ("error" in result) return err4(result.error);
|
|
884
|
+
const { doc } = result;
|
|
885
|
+
const states = Object.entries(doc.states).map(([name, state]) => ({
|
|
886
|
+
name,
|
|
887
|
+
duration: state.duration,
|
|
888
|
+
description: state.description,
|
|
889
|
+
tags: state.tags,
|
|
890
|
+
deltaCount: state.deltas.length
|
|
891
|
+
}));
|
|
892
|
+
return ok4({ states, count: states.length });
|
|
893
|
+
}
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// ../mcp/src/tools/deltas.ts
|
|
898
|
+
import { z as z18 } from "zod";
|
|
899
|
+
|
|
900
|
+
// ../math/dist/index.js
|
|
901
|
+
function linear(t) {
|
|
902
|
+
return t;
|
|
903
|
+
}
|
|
904
|
+
function cubicBezier(x1, y1, x2, y2) {
|
|
905
|
+
return (t) => {
|
|
906
|
+
if (t <= 0) return 0;
|
|
907
|
+
if (t >= 1) return 1;
|
|
908
|
+
let lo = 0;
|
|
909
|
+
let hi = 1;
|
|
910
|
+
let mid = 0;
|
|
911
|
+
for (let i = 0; i < 20; i++) {
|
|
912
|
+
mid = (lo + hi) / 2;
|
|
913
|
+
const x = sampleBezier(x1, x2, mid);
|
|
914
|
+
if (Math.abs(x - t) < 1e-6) break;
|
|
915
|
+
if (x < t) lo = mid;
|
|
916
|
+
else hi = mid;
|
|
917
|
+
}
|
|
918
|
+
mid = (lo + hi) / 2;
|
|
919
|
+
return sampleBezier(y1, y2, mid);
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
function sampleBezier(p1, p2, t) {
|
|
923
|
+
return 3 * (1 - t) * (1 - t) * t * p1 + 3 * (1 - t) * t * t * p2 + t * t * t;
|
|
924
|
+
}
|
|
925
|
+
var easeIn = cubicBezier(0.42, 0, 1, 1);
|
|
926
|
+
var easeOut = cubicBezier(0, 0, 0.58, 1);
|
|
927
|
+
var easeInOut = cubicBezier(0.42, 0, 0.58, 1);
|
|
928
|
+
function step(steps, position = "end") {
|
|
929
|
+
return (t) => {
|
|
930
|
+
if (t <= 0) return position === "start" ? 1 / steps : 0;
|
|
931
|
+
if (t >= 1) return 1;
|
|
932
|
+
const s = Math.floor(t * steps);
|
|
933
|
+
if (position === "start") {
|
|
934
|
+
return Math.min((s + 1) / steps, 1);
|
|
935
|
+
}
|
|
936
|
+
return s / steps;
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
function spring(config = {}) {
|
|
940
|
+
const {
|
|
941
|
+
mass = 1,
|
|
942
|
+
stiffness = 100,
|
|
943
|
+
damping = 10,
|
|
944
|
+
velocity = 0
|
|
945
|
+
} = config;
|
|
946
|
+
const w0 = Math.sqrt(stiffness / mass);
|
|
947
|
+
const zeta = damping / (2 * Math.sqrt(stiffness * mass));
|
|
948
|
+
const duration = estimateSettleTime(zeta, w0);
|
|
949
|
+
return (t) => {
|
|
950
|
+
if (t <= 0) return 0;
|
|
951
|
+
if (t >= 1) return 1;
|
|
952
|
+
const time = t * duration;
|
|
953
|
+
let value;
|
|
954
|
+
if (zeta < 1) {
|
|
955
|
+
const wd = w0 * Math.sqrt(1 - zeta * zeta);
|
|
956
|
+
const A = 1;
|
|
957
|
+
const B = (zeta * w0 + velocity) / wd;
|
|
958
|
+
value = 1 - Math.exp(-zeta * w0 * time) * (A * Math.cos(wd * time) + B * Math.sin(wd * time));
|
|
959
|
+
} else if (zeta === 1) {
|
|
960
|
+
value = 1 - Math.exp(-w0 * time) * (1 + (w0 + velocity) * time);
|
|
961
|
+
} else {
|
|
962
|
+
const s1 = -w0 * (zeta - Math.sqrt(zeta * zeta - 1));
|
|
963
|
+
const s2 = -w0 * (zeta + Math.sqrt(zeta * zeta - 1));
|
|
964
|
+
const A = (velocity - s2) / (s1 - s2);
|
|
965
|
+
const B = 1 - A;
|
|
966
|
+
value = 1 - A * Math.exp(s1 * time) - B * Math.exp(s2 * time);
|
|
967
|
+
}
|
|
968
|
+
return value;
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
function estimateSettleTime(zeta, w0) {
|
|
972
|
+
if (zeta >= 1) {
|
|
973
|
+
return 10 / (zeta * w0);
|
|
974
|
+
}
|
|
975
|
+
return Math.log(1e3) / (zeta * w0);
|
|
976
|
+
}
|
|
977
|
+
function lerp(a, b, t) {
|
|
978
|
+
return a + (b - a) * t;
|
|
979
|
+
}
|
|
980
|
+
function clamp(value, min, max) {
|
|
981
|
+
return Math.min(Math.max(value, min), max);
|
|
982
|
+
}
|
|
983
|
+
function hexToRgba(hex) {
|
|
984
|
+
let h = hex.replace("#", "");
|
|
985
|
+
if (h.length === 3)
|
|
986
|
+
h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2] + "ff";
|
|
987
|
+
else if (h.length === 4)
|
|
988
|
+
h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2] + h[3] + h[3];
|
|
989
|
+
else if (h.length === 6) h = h + "ff";
|
|
990
|
+
return {
|
|
991
|
+
r: parseInt(h.slice(0, 2), 16),
|
|
992
|
+
g: parseInt(h.slice(2, 4), 16),
|
|
993
|
+
b: parseInt(h.slice(4, 6), 16),
|
|
994
|
+
a: parseInt(h.slice(6, 8), 16) / 255
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
function rgbaToHex(color) {
|
|
998
|
+
const r = clamp(Math.round(color.r), 0, 255).toString(16).padStart(2, "0");
|
|
999
|
+
const g = clamp(Math.round(color.g), 0, 255).toString(16).padStart(2, "0");
|
|
1000
|
+
const b = clamp(Math.round(color.b), 0, 255).toString(16).padStart(2, "0");
|
|
1001
|
+
const a = clamp(Math.round(color.a * 255), 0, 255).toString(16).padStart(2, "0");
|
|
1002
|
+
return `#${r}${g}${b}${a === "ff" ? "" : a}`;
|
|
1003
|
+
}
|
|
1004
|
+
function lerpRgba(a, b, t) {
|
|
1005
|
+
return {
|
|
1006
|
+
r: lerp(a.r, b.r, t),
|
|
1007
|
+
g: lerp(a.g, b.g, t),
|
|
1008
|
+
b: lerp(a.b, b.b, t),
|
|
1009
|
+
a: lerp(a.a, b.a, t)
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// ../core/dist/index.js
|
|
1014
|
+
function resolveEasing(easing) {
|
|
1015
|
+
if (!easing) return linear;
|
|
1016
|
+
if (typeof easing === "string") {
|
|
1017
|
+
switch (easing) {
|
|
1018
|
+
case "ease-in":
|
|
1019
|
+
return easeIn;
|
|
1020
|
+
case "ease-out":
|
|
1021
|
+
return easeOut;
|
|
1022
|
+
case "ease-in-out":
|
|
1023
|
+
return easeInOut;
|
|
1024
|
+
default:
|
|
1025
|
+
return linear;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
switch (easing.type) {
|
|
1029
|
+
case "linear":
|
|
1030
|
+
return linear;
|
|
1031
|
+
case "cubic-bezier":
|
|
1032
|
+
return cubicBezier(easing.x1, easing.y1, easing.x2, easing.y2);
|
|
1033
|
+
case "spring":
|
|
1034
|
+
return spring({
|
|
1035
|
+
mass: easing.mass,
|
|
1036
|
+
stiffness: easing.stiffness,
|
|
1037
|
+
damping: easing.damping,
|
|
1038
|
+
velocity: easing.velocity
|
|
1039
|
+
});
|
|
1040
|
+
case "step":
|
|
1041
|
+
return step(easing.steps, easing.position);
|
|
1042
|
+
default:
|
|
1043
|
+
return linear;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
function isFrameInRange(frame, range) {
|
|
1047
|
+
return frame >= range[0] && frame <= range[1];
|
|
1048
|
+
}
|
|
1049
|
+
function computeProgress(frame, range) {
|
|
1050
|
+
const [start, end] = range;
|
|
1051
|
+
if (start === end) return 1;
|
|
1052
|
+
return clamp((frame - start) / (end - start), 0, 1);
|
|
1053
|
+
}
|
|
1054
|
+
function resolveDeltaValue(delta, frame) {
|
|
1055
|
+
if (!isFrameInRange(frame, delta.range)) {
|
|
1056
|
+
return void 0;
|
|
1057
|
+
}
|
|
1058
|
+
const progress = computeProgress(frame, delta.range);
|
|
1059
|
+
const easingFn = resolveEasing(delta.easing);
|
|
1060
|
+
const easedProgress = easingFn(progress);
|
|
1061
|
+
return interpolateValue(delta.from, delta.to, easedProgress);
|
|
1062
|
+
}
|
|
1063
|
+
function interpolateValue(from, to, t) {
|
|
1064
|
+
if (typeof from === "number" && typeof to === "number") {
|
|
1065
|
+
return lerp(from, to, t);
|
|
1066
|
+
}
|
|
1067
|
+
if (typeof from === "string" && typeof to === "string") {
|
|
1068
|
+
if (from.startsWith("#") && to.startsWith("#")) {
|
|
1069
|
+
return rgbaToHex(lerpRgba(hexToRgba(from), hexToRgba(to), t));
|
|
1070
|
+
}
|
|
1071
|
+
return t >= 1 ? to : from;
|
|
1072
|
+
}
|
|
1073
|
+
return t >= 1 ? to : from;
|
|
1074
|
+
}
|
|
1075
|
+
function resolvePropertyAtFrame(deltas, frame) {
|
|
1076
|
+
for (const delta of deltas) {
|
|
1077
|
+
if (isFrameInRange(frame, delta.range)) {
|
|
1078
|
+
return resolveDeltaValue(delta, frame);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
let lastCompleted;
|
|
1082
|
+
for (const delta of deltas) {
|
|
1083
|
+
if (frame > delta.range[1]) {
|
|
1084
|
+
if (!lastCompleted || delta.range[1] > lastCompleted.range[1]) {
|
|
1085
|
+
lastCompleted = delta;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
return lastCompleted?.to;
|
|
1090
|
+
}
|
|
1091
|
+
function resolveFrame(doc, stateName, frame) {
|
|
1092
|
+
const state = doc.states[stateName];
|
|
1093
|
+
if (!state) {
|
|
1094
|
+
throw new Error(`State "${stateName}" not found in document "${doc.name}"`);
|
|
1095
|
+
}
|
|
1096
|
+
const deltasByLayerProperty = groupDeltas(state.deltas);
|
|
1097
|
+
const resolvedLayers = doc.layers.map((layer) => {
|
|
1098
|
+
const computedProperties = {};
|
|
1099
|
+
const layerDeltas = deltasByLayerProperty.get(layer.id);
|
|
1100
|
+
if (layerDeltas) {
|
|
1101
|
+
for (const [property, deltas] of layerDeltas) {
|
|
1102
|
+
const value = resolvePropertyAtFrame(deltas, frame);
|
|
1103
|
+
if (value !== void 0) {
|
|
1104
|
+
computedProperties[property] = value;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
return { id: layer.id, layer, computedProperties };
|
|
1109
|
+
});
|
|
1110
|
+
return { frame, stateName, layers: resolvedLayers };
|
|
1111
|
+
}
|
|
1112
|
+
function groupDeltas(deltas) {
|
|
1113
|
+
const map = /* @__PURE__ */ new Map();
|
|
1114
|
+
for (const delta of deltas) {
|
|
1115
|
+
let layerMap = map.get(delta.layer);
|
|
1116
|
+
if (!layerMap) {
|
|
1117
|
+
layerMap = /* @__PURE__ */ new Map();
|
|
1118
|
+
map.set(delta.layer, layerMap);
|
|
1119
|
+
}
|
|
1120
|
+
let propDeltas = layerMap.get(delta.property);
|
|
1121
|
+
if (!propDeltas) {
|
|
1122
|
+
propDeltas = [];
|
|
1123
|
+
layerMap.set(delta.property, propDeltas);
|
|
1124
|
+
}
|
|
1125
|
+
propDeltas.push(delta);
|
|
1126
|
+
}
|
|
1127
|
+
return map;
|
|
1128
|
+
}
|
|
1129
|
+
function rangesOverlap(a, b) {
|
|
1130
|
+
return a[0] <= b[1] && b[0] <= a[1];
|
|
1131
|
+
}
|
|
1132
|
+
function validateNoOverlap(existing, newDelta) {
|
|
1133
|
+
for (const delta of existing) {
|
|
1134
|
+
if (delta.layer === newDelta.layer && delta.property === newDelta.property && rangesOverlap(delta.range, newDelta.range)) {
|
|
1135
|
+
return {
|
|
1136
|
+
layerId: newDelta.layer,
|
|
1137
|
+
property: newDelta.property,
|
|
1138
|
+
existingRange: delta.range,
|
|
1139
|
+
newRange: newDelta.range,
|
|
1140
|
+
message: `Overlapping delta on layer "${newDelta.layer}" property "${newDelta.property}": existing [${delta.range[0]}-${delta.range[1]}] overlaps with new [${newDelta.range[0]}-${newDelta.range[1]}]`
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
return null;
|
|
1145
|
+
}
|
|
1146
|
+
function validateAllDeltas(deltas) {
|
|
1147
|
+
const errors = [];
|
|
1148
|
+
for (let i = 0; i < deltas.length; i++) {
|
|
1149
|
+
for (let j = i + 1; j < deltas.length; j++) {
|
|
1150
|
+
const a = deltas[i];
|
|
1151
|
+
const b = deltas[j];
|
|
1152
|
+
if (a.layer === b.layer && a.property === b.property && rangesOverlap(a.range, b.range)) {
|
|
1153
|
+
errors.push({
|
|
1154
|
+
layerId: a.layer,
|
|
1155
|
+
property: a.property,
|
|
1156
|
+
existingRange: a.range,
|
|
1157
|
+
newRange: b.range,
|
|
1158
|
+
message: `Overlapping deltas on layer "${a.layer}" property "${a.property}": [${a.range[0]}-${a.range[1]}] overlaps with [${b.range[0]}-${b.range[1]}]`
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
return errors;
|
|
1164
|
+
}
|
|
1165
|
+
function expandPreset(preset, layerId, startFrame, duration) {
|
|
1166
|
+
return preset.deltas.map((pd, index) => {
|
|
1167
|
+
let range;
|
|
1168
|
+
if (pd.offset) {
|
|
1169
|
+
range = [startFrame + pd.offset[0], startFrame + pd.offset[1]];
|
|
1170
|
+
} else {
|
|
1171
|
+
range = [startFrame, startFrame + duration];
|
|
1172
|
+
}
|
|
1173
|
+
return {
|
|
1174
|
+
id: `preset-${layerId}-${index}`,
|
|
1175
|
+
layer: layerId,
|
|
1176
|
+
property: pd.property,
|
|
1177
|
+
range,
|
|
1178
|
+
from: pd.from,
|
|
1179
|
+
to: pd.to,
|
|
1180
|
+
easing: pd.easing
|
|
1181
|
+
};
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
function instantiateTemplate(template, bindings) {
|
|
1185
|
+
const errors = [];
|
|
1186
|
+
const variables = template.variables ?? {};
|
|
1187
|
+
for (const [name, variable] of Object.entries(variables)) {
|
|
1188
|
+
if (bindings[name] === void 0 && variable.default === void 0) {
|
|
1189
|
+
errors.push({ variable: name, message: `Required variable "${name}" not provided` });
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
for (const name of Object.keys(bindings)) {
|
|
1193
|
+
if (!variables[name]) {
|
|
1194
|
+
errors.push({ variable: name, message: `Unknown variable "${name}"` });
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
if (errors.length > 0) return { success: false, errors };
|
|
1198
|
+
const resolved = {};
|
|
1199
|
+
for (const [name, variable] of Object.entries(variables)) {
|
|
1200
|
+
resolved[name] = bindings[name] ?? variable.default;
|
|
1201
|
+
}
|
|
1202
|
+
const doc = JSON.parse(JSON.stringify(template));
|
|
1203
|
+
substituteInObject(doc, resolved);
|
|
1204
|
+
delete doc.variables;
|
|
1205
|
+
return { success: true, document: doc };
|
|
1206
|
+
}
|
|
1207
|
+
function substituteInObject(obj, bindings) {
|
|
1208
|
+
for (const key of Object.keys(obj)) {
|
|
1209
|
+
const value = obj[key];
|
|
1210
|
+
if (typeof value === "string") {
|
|
1211
|
+
obj[key] = substituteString(value, bindings);
|
|
1212
|
+
} else if (Array.isArray(value)) {
|
|
1213
|
+
substituteInArray(value, bindings);
|
|
1214
|
+
} else if (value !== null && typeof value === "object") {
|
|
1215
|
+
substituteInObject(value, bindings);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
function substituteInArray(arr, bindings) {
|
|
1220
|
+
for (let i = 0; i < arr.length; i++) {
|
|
1221
|
+
const value = arr[i];
|
|
1222
|
+
if (typeof value === "string") {
|
|
1223
|
+
arr[i] = substituteString(value, bindings);
|
|
1224
|
+
} else if (Array.isArray(value)) {
|
|
1225
|
+
substituteInArray(value, bindings);
|
|
1226
|
+
} else if (value !== null && typeof value === "object") {
|
|
1227
|
+
substituteInObject(value, bindings);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
function substituteString(str, bindings) {
|
|
1232
|
+
const exactMatch = str.match(/^\{\{(\w+)\}\}$/);
|
|
1233
|
+
if (exactMatch) {
|
|
1234
|
+
const name = exactMatch[1];
|
|
1235
|
+
return name in bindings ? bindings[name] : str;
|
|
1236
|
+
}
|
|
1237
|
+
return str.replace(/\{\{(\w+)\}\}/g, (_, name) => {
|
|
1238
|
+
return name in bindings ? String(bindings[name]) : `{{${name}}}`;
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
function findTemplateVariables(doc) {
|
|
1242
|
+
const vars = /* @__PURE__ */ new Set();
|
|
1243
|
+
scanForVariables(doc, vars);
|
|
1244
|
+
return Array.from(vars);
|
|
1245
|
+
}
|
|
1246
|
+
function scanForVariables(value, vars) {
|
|
1247
|
+
if (typeof value === "string") {
|
|
1248
|
+
const matches = value.matchAll(/\{\{(\w+)\}\}/g);
|
|
1249
|
+
for (const match of matches) {
|
|
1250
|
+
vars.add(match[1]);
|
|
1251
|
+
}
|
|
1252
|
+
} else if (Array.isArray(value)) {
|
|
1253
|
+
for (const item of value) scanForVariables(item, vars);
|
|
1254
|
+
} else if (value !== null && typeof value === "object") {
|
|
1255
|
+
for (const v of Object.values(value)) {
|
|
1256
|
+
scanForVariables(v, vars);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
// ../mcp/src/tools/deltas.ts
|
|
1262
|
+
function getDoc5(store, id) {
|
|
1263
|
+
const doc = store.get(id);
|
|
1264
|
+
if (!doc) return { error: `Document "${id}" not found` };
|
|
1265
|
+
return { doc };
|
|
1266
|
+
}
|
|
1267
|
+
function ok5(data) {
|
|
1268
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...data }) }] };
|
|
1269
|
+
}
|
|
1270
|
+
function err5(message) {
|
|
1271
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
|
|
1272
|
+
}
|
|
1273
|
+
var AnimatablePropertyEnum = z18.enum([
|
|
1274
|
+
"frame.x",
|
|
1275
|
+
"frame.y",
|
|
1276
|
+
"bounds.width",
|
|
1277
|
+
"bounds.height",
|
|
1278
|
+
"opacity",
|
|
1279
|
+
"rotation",
|
|
1280
|
+
"scale.x",
|
|
1281
|
+
"scale.y",
|
|
1282
|
+
"anchorPoint.x",
|
|
1283
|
+
"anchorPoint.y",
|
|
1284
|
+
"visual.shape.cornerRadius",
|
|
1285
|
+
"visual.fill.color",
|
|
1286
|
+
"visual.stroke.color",
|
|
1287
|
+
"visual.stroke.width",
|
|
1288
|
+
"visual.stroke.start",
|
|
1289
|
+
"visual.stroke.end",
|
|
1290
|
+
"visual.style.fontSize",
|
|
1291
|
+
"visual.style.color",
|
|
1292
|
+
"shadow.color",
|
|
1293
|
+
"shadow.blur",
|
|
1294
|
+
"shadow.offsetX",
|
|
1295
|
+
"shadow.offsetY"
|
|
1296
|
+
]);
|
|
1297
|
+
var EasingInputSchema = z18.union([
|
|
1298
|
+
z18.enum(["ease-in", "ease-out", "ease-in-out"]),
|
|
1299
|
+
z18.object({ type: z18.literal("linear") }),
|
|
1300
|
+
z18.object({
|
|
1301
|
+
type: z18.literal("cubic-bezier"),
|
|
1302
|
+
x1: z18.number(),
|
|
1303
|
+
y1: z18.number(),
|
|
1304
|
+
x2: z18.number(),
|
|
1305
|
+
y2: z18.number()
|
|
1306
|
+
}),
|
|
1307
|
+
z18.object({
|
|
1308
|
+
type: z18.literal("spring"),
|
|
1309
|
+
mass: z18.number().optional(),
|
|
1310
|
+
stiffness: z18.number().optional(),
|
|
1311
|
+
damping: z18.number().optional(),
|
|
1312
|
+
velocity: z18.number().optional()
|
|
1313
|
+
}),
|
|
1314
|
+
z18.object({
|
|
1315
|
+
type: z18.literal("step"),
|
|
1316
|
+
steps: z18.number().int().positive(),
|
|
1317
|
+
position: z18.enum(["start", "end"]).optional()
|
|
1318
|
+
})
|
|
1319
|
+
]).describe("Easing function");
|
|
1320
|
+
function register5(server, store) {
|
|
1321
|
+
server.tool(
|
|
1322
|
+
"atelier_add_delta",
|
|
1323
|
+
"Add an animation delta (keyframe transition) to a state",
|
|
1324
|
+
{
|
|
1325
|
+
id: z18.string().describe("Document ID"),
|
|
1326
|
+
stateName: z18.string().describe("State name"),
|
|
1327
|
+
layer: z18.string().describe("Target layer ID"),
|
|
1328
|
+
property: AnimatablePropertyEnum.describe("Property to animate"),
|
|
1329
|
+
range: z18.tuple([z18.number().int().min(0), z18.number().int().min(0)]).describe("Frame range [start, end] inclusive"),
|
|
1330
|
+
from: z18.unknown().describe("Starting value"),
|
|
1331
|
+
to: z18.unknown().describe("Ending value"),
|
|
1332
|
+
easing: EasingInputSchema.optional().describe("Easing function"),
|
|
1333
|
+
description: z18.string().optional().describe("Delta description"),
|
|
1334
|
+
tags: z18.array(z18.string()).optional().describe("Tags"),
|
|
1335
|
+
deltaId: z18.string().optional().describe("Custom delta ID")
|
|
1336
|
+
},
|
|
1337
|
+
async ({ id, stateName, layer, property, range, from, to, easing, description, tags, deltaId }) => {
|
|
1338
|
+
const result = getDoc5(store, id);
|
|
1339
|
+
if ("error" in result) return err5(result.error);
|
|
1340
|
+
const { doc } = result;
|
|
1341
|
+
const state = doc.states[stateName];
|
|
1342
|
+
if (!state) return err5(`State "${stateName}" not found`);
|
|
1343
|
+
if (!doc.layers.some((l) => l.id === layer)) {
|
|
1344
|
+
return err5(`Layer "${layer}" not found`);
|
|
1345
|
+
}
|
|
1346
|
+
if (range[0] > range[1]) {
|
|
1347
|
+
return err5(`Invalid range: start (${range[0]}) must be <= end (${range[1]})`);
|
|
1348
|
+
}
|
|
1349
|
+
const delta = {
|
|
1350
|
+
layer,
|
|
1351
|
+
property,
|
|
1352
|
+
range: [range[0], range[1]],
|
|
1353
|
+
from,
|
|
1354
|
+
to,
|
|
1355
|
+
...easing ? { easing } : {},
|
|
1356
|
+
...description ? { description } : {},
|
|
1357
|
+
...tags ? { tags } : {},
|
|
1358
|
+
...deltaId ? { id: deltaId } : {}
|
|
1359
|
+
};
|
|
1360
|
+
const overlap = validateNoOverlap(state.deltas, delta);
|
|
1361
|
+
if (overlap) {
|
|
1362
|
+
return err5(overlap.message);
|
|
1363
|
+
}
|
|
1364
|
+
state.deltas.push(delta);
|
|
1365
|
+
return ok5({ stateName, deltaIndex: state.deltas.length - 1, layer, property });
|
|
1366
|
+
}
|
|
1367
|
+
);
|
|
1368
|
+
server.tool(
|
|
1369
|
+
"atelier_edit_delta",
|
|
1370
|
+
"Edit an existing delta by index within a state",
|
|
1371
|
+
{
|
|
1372
|
+
id: z18.string().describe("Document ID"),
|
|
1373
|
+
stateName: z18.string().describe("State name"),
|
|
1374
|
+
deltaIndex: z18.number().int().min(0).describe("Delta index within the state"),
|
|
1375
|
+
layer: z18.string().optional().describe("New target layer ID"),
|
|
1376
|
+
property: AnimatablePropertyEnum.optional().describe("New property to animate"),
|
|
1377
|
+
range: z18.tuple([z18.number().int().min(0), z18.number().int().min(0)]).optional().describe("New frame range [start, end]"),
|
|
1378
|
+
from: z18.unknown().optional().describe("New starting value"),
|
|
1379
|
+
to: z18.unknown().optional().describe("New ending value"),
|
|
1380
|
+
easing: EasingInputSchema.optional().describe("New easing function"),
|
|
1381
|
+
description: z18.string().optional().describe("New description"),
|
|
1382
|
+
tags: z18.array(z18.string()).optional().describe("New tags")
|
|
1383
|
+
},
|
|
1384
|
+
async ({ id, stateName, deltaIndex, layer, property, range, from, to, easing, description, tags }) => {
|
|
1385
|
+
const result = getDoc5(store, id);
|
|
1386
|
+
if ("error" in result) return err5(result.error);
|
|
1387
|
+
const { doc } = result;
|
|
1388
|
+
const state = doc.states[stateName];
|
|
1389
|
+
if (!state) return err5(`State "${stateName}" not found`);
|
|
1390
|
+
if (deltaIndex >= state.deltas.length) {
|
|
1391
|
+
return err5(`Delta index ${deltaIndex} out of range (${state.deltas.length} deltas)`);
|
|
1392
|
+
}
|
|
1393
|
+
const delta = state.deltas[deltaIndex];
|
|
1394
|
+
if (layer != null) {
|
|
1395
|
+
if (!doc.layers.some((l) => l.id === layer)) return err5(`Layer "${layer}" not found`);
|
|
1396
|
+
delta.layer = layer;
|
|
1397
|
+
}
|
|
1398
|
+
if (property != null) delta.property = property;
|
|
1399
|
+
if (range != null) {
|
|
1400
|
+
if (range[0] > range[1]) return err5(`Invalid range: start (${range[0]}) must be <= end (${range[1]})`);
|
|
1401
|
+
delta.range = [range[0], range[1]];
|
|
1402
|
+
}
|
|
1403
|
+
if (from !== void 0) delta.from = from;
|
|
1404
|
+
if (to !== void 0) delta.to = to;
|
|
1405
|
+
if (easing != null) delta.easing = easing;
|
|
1406
|
+
if (description != null) delta.description = description;
|
|
1407
|
+
if (tags) delta.tags = tags;
|
|
1408
|
+
const others = state.deltas.filter((_, i) => i !== deltaIndex);
|
|
1409
|
+
const overlap = validateNoOverlap(others, delta);
|
|
1410
|
+
if (overlap) {
|
|
1411
|
+
return err5(`Edit would cause overlap: ${overlap.message}`);
|
|
1412
|
+
}
|
|
1413
|
+
return ok5({ stateName, deltaIndex, updated: true });
|
|
1414
|
+
}
|
|
1415
|
+
);
|
|
1416
|
+
server.tool(
|
|
1417
|
+
"atelier_remove_delta",
|
|
1418
|
+
"Remove a delta by index from a state",
|
|
1419
|
+
{
|
|
1420
|
+
id: z18.string().describe("Document ID"),
|
|
1421
|
+
stateName: z18.string().describe("State name"),
|
|
1422
|
+
deltaIndex: z18.number().int().min(0).describe("Delta index to remove")
|
|
1423
|
+
},
|
|
1424
|
+
async ({ id, stateName, deltaIndex }) => {
|
|
1425
|
+
const result = getDoc5(store, id);
|
|
1426
|
+
if ("error" in result) return err5(result.error);
|
|
1427
|
+
const { doc } = result;
|
|
1428
|
+
const state = doc.states[stateName];
|
|
1429
|
+
if (!state) return err5(`State "${stateName}" not found`);
|
|
1430
|
+
if (deltaIndex >= state.deltas.length) {
|
|
1431
|
+
return err5(`Delta index ${deltaIndex} out of range (${state.deltas.length} deltas)`);
|
|
1432
|
+
}
|
|
1433
|
+
const removed = state.deltas.splice(deltaIndex, 1)[0];
|
|
1434
|
+
return ok5({
|
|
1435
|
+
stateName,
|
|
1436
|
+
removed: {
|
|
1437
|
+
layer: removed.layer,
|
|
1438
|
+
property: removed.property,
|
|
1439
|
+
range: removed.range
|
|
1440
|
+
},
|
|
1441
|
+
remainingDeltas: state.deltas.length
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
);
|
|
1445
|
+
server.tool(
|
|
1446
|
+
"atelier_apply_preset",
|
|
1447
|
+
"Apply a preset to a layer, expanding it into concrete deltas",
|
|
1448
|
+
{
|
|
1449
|
+
id: z18.string().describe("Document ID"),
|
|
1450
|
+
stateName: z18.string().describe("State name to add deltas to"),
|
|
1451
|
+
presetName: z18.string().describe("Preset name defined in the document"),
|
|
1452
|
+
layerId: z18.string().describe("Target layer ID"),
|
|
1453
|
+
startFrame: z18.number().int().min(0).optional().describe("Start frame (default: 0)"),
|
|
1454
|
+
duration: z18.number().positive().int().optional().describe("Duration for preset (default: state duration)")
|
|
1455
|
+
},
|
|
1456
|
+
async ({ id, stateName, presetName, layerId, startFrame, duration }) => {
|
|
1457
|
+
const result = getDoc5(store, id);
|
|
1458
|
+
if ("error" in result) return err5(result.error);
|
|
1459
|
+
const { doc } = result;
|
|
1460
|
+
const state = doc.states[stateName];
|
|
1461
|
+
if (!state) return err5(`State "${stateName}" not found`);
|
|
1462
|
+
if (!doc.layers.some((l) => l.id === layerId)) {
|
|
1463
|
+
return err5(`Layer "${layerId}" not found`);
|
|
1464
|
+
}
|
|
1465
|
+
if (!doc.presets || !doc.presets[presetName]) {
|
|
1466
|
+
return err5(`Preset "${presetName}" not found`);
|
|
1467
|
+
}
|
|
1468
|
+
const preset = doc.presets[presetName];
|
|
1469
|
+
const start = startFrame ?? 0;
|
|
1470
|
+
const dur = duration ?? state.duration;
|
|
1471
|
+
const deltas = expandPreset(preset, layerId, start, dur);
|
|
1472
|
+
const errors = [];
|
|
1473
|
+
for (const delta of deltas) {
|
|
1474
|
+
const overlap = validateNoOverlap(state.deltas, delta);
|
|
1475
|
+
if (overlap) {
|
|
1476
|
+
errors.push(overlap.message);
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
if (errors.length > 0) {
|
|
1480
|
+
return err5(`Preset would cause overlaps:
|
|
1481
|
+
${errors.join("\n")}`);
|
|
1482
|
+
}
|
|
1483
|
+
state.deltas.push(...deltas);
|
|
1484
|
+
return ok5({
|
|
1485
|
+
stateName,
|
|
1486
|
+
presetName,
|
|
1487
|
+
layerId,
|
|
1488
|
+
deltasAdded: deltas.length,
|
|
1489
|
+
totalDeltas: state.deltas.length
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
1492
|
+
);
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
// ../mcp/src/tools/presets.ts
|
|
1496
|
+
import { z as z19 } from "zod";
|
|
1497
|
+
function getDoc6(store, id) {
|
|
1498
|
+
const doc = store.get(id);
|
|
1499
|
+
if (!doc) return { error: `Document "${id}" not found` };
|
|
1500
|
+
return { doc };
|
|
1501
|
+
}
|
|
1502
|
+
function ok6(data) {
|
|
1503
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...data }) }] };
|
|
1504
|
+
}
|
|
1505
|
+
function err6(message) {
|
|
1506
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
|
|
1507
|
+
}
|
|
1508
|
+
var AnimatablePropertyEnum2 = z19.enum([
|
|
1509
|
+
"frame.x",
|
|
1510
|
+
"frame.y",
|
|
1511
|
+
"bounds.width",
|
|
1512
|
+
"bounds.height",
|
|
1513
|
+
"opacity",
|
|
1514
|
+
"rotation",
|
|
1515
|
+
"scale.x",
|
|
1516
|
+
"scale.y",
|
|
1517
|
+
"anchorPoint.x",
|
|
1518
|
+
"anchorPoint.y",
|
|
1519
|
+
"visual.shape.cornerRadius",
|
|
1520
|
+
"visual.fill.color",
|
|
1521
|
+
"visual.stroke.color",
|
|
1522
|
+
"visual.stroke.width",
|
|
1523
|
+
"visual.style.fontSize",
|
|
1524
|
+
"visual.style.color"
|
|
1525
|
+
]);
|
|
1526
|
+
var EasingInputSchema2 = z19.union([
|
|
1527
|
+
z19.enum(["ease-in", "ease-out", "ease-in-out"]),
|
|
1528
|
+
z19.object({ type: z19.literal("linear") }),
|
|
1529
|
+
z19.object({
|
|
1530
|
+
type: z19.literal("cubic-bezier"),
|
|
1531
|
+
x1: z19.number(),
|
|
1532
|
+
y1: z19.number(),
|
|
1533
|
+
x2: z19.number(),
|
|
1534
|
+
y2: z19.number()
|
|
1535
|
+
}),
|
|
1536
|
+
z19.object({
|
|
1537
|
+
type: z19.literal("spring"),
|
|
1538
|
+
mass: z19.number().optional(),
|
|
1539
|
+
stiffness: z19.number().optional(),
|
|
1540
|
+
damping: z19.number().optional(),
|
|
1541
|
+
velocity: z19.number().optional()
|
|
1542
|
+
}),
|
|
1543
|
+
z19.object({
|
|
1544
|
+
type: z19.literal("step"),
|
|
1545
|
+
steps: z19.number().int().positive(),
|
|
1546
|
+
position: z19.enum(["start", "end"]).optional()
|
|
1547
|
+
})
|
|
1548
|
+
]);
|
|
1549
|
+
var PresetDeltaSchema2 = z19.object({
|
|
1550
|
+
property: AnimatablePropertyEnum2.describe("Animatable property"),
|
|
1551
|
+
offset: z19.tuple([z19.number().int().min(0), z19.number().int().min(0)]).optional().describe("Relative frame offset [start, end]"),
|
|
1552
|
+
from: z19.unknown().describe("Starting value"),
|
|
1553
|
+
to: z19.unknown().describe("Ending value"),
|
|
1554
|
+
easing: EasingInputSchema2.optional().describe("Easing function")
|
|
1555
|
+
});
|
|
1556
|
+
function register6(server, store) {
|
|
1557
|
+
server.tool(
|
|
1558
|
+
"atelier_define_preset",
|
|
1559
|
+
"Define a reusable animation preset on a document",
|
|
1560
|
+
{
|
|
1561
|
+
id: z19.string().describe("Document ID"),
|
|
1562
|
+
presetName: z19.string().describe("Preset name"),
|
|
1563
|
+
description: z19.string().optional().describe("Preset description"),
|
|
1564
|
+
tags: z19.array(z19.string()).optional().describe("Tags"),
|
|
1565
|
+
deltas: z19.array(PresetDeltaSchema2).min(1).describe("Array of preset delta definitions")
|
|
1566
|
+
},
|
|
1567
|
+
async ({ id, presetName, description, tags, deltas }) => {
|
|
1568
|
+
const result = getDoc6(store, id);
|
|
1569
|
+
if ("error" in result) return err6(result.error);
|
|
1570
|
+
const { doc } = result;
|
|
1571
|
+
if (!doc.presets) doc.presets = {};
|
|
1572
|
+
const preset = {
|
|
1573
|
+
deltas,
|
|
1574
|
+
...description ? { description } : {},
|
|
1575
|
+
...tags ? { tags } : {}
|
|
1576
|
+
};
|
|
1577
|
+
doc.presets[presetName] = preset;
|
|
1578
|
+
return ok6({ presetName, deltaCount: deltas.length });
|
|
1579
|
+
}
|
|
1580
|
+
);
|
|
1581
|
+
server.tool(
|
|
1582
|
+
"atelier_list_presets",
|
|
1583
|
+
"List all presets defined on a document",
|
|
1584
|
+
{
|
|
1585
|
+
id: z19.string().describe("Document ID")
|
|
1586
|
+
},
|
|
1587
|
+
async ({ id }) => {
|
|
1588
|
+
const result = getDoc6(store, id);
|
|
1589
|
+
if ("error" in result) return err6(result.error);
|
|
1590
|
+
const { doc } = result;
|
|
1591
|
+
if (!doc.presets || Object.keys(doc.presets).length === 0) {
|
|
1592
|
+
return ok6({ presets: [], count: 0 });
|
|
1593
|
+
}
|
|
1594
|
+
const presets = Object.entries(doc.presets).map(([name, preset]) => ({
|
|
1595
|
+
name,
|
|
1596
|
+
description: preset.description,
|
|
1597
|
+
tags: preset.tags,
|
|
1598
|
+
deltaCount: preset.deltas.length,
|
|
1599
|
+
properties: preset.deltas.map((d) => d.property)
|
|
1600
|
+
}));
|
|
1601
|
+
return ok6({ presets, count: presets.length });
|
|
1602
|
+
}
|
|
1603
|
+
);
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
// ../mcp/src/tools/preview.ts
|
|
1607
|
+
import { z as z20 } from "zod";
|
|
1608
|
+
function getDoc7(store, id) {
|
|
1609
|
+
const doc = store.get(id);
|
|
1610
|
+
if (!doc) return { error: `Document "${id}" not found` };
|
|
1611
|
+
return { doc };
|
|
1612
|
+
}
|
|
1613
|
+
function ok7(data) {
|
|
1614
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...data }) }] };
|
|
1615
|
+
}
|
|
1616
|
+
function err7(message) {
|
|
1617
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
|
|
1618
|
+
}
|
|
1619
|
+
function register7(server, store) {
|
|
1620
|
+
server.tool(
|
|
1621
|
+
"atelier_validate",
|
|
1622
|
+
"Validate an Atelier document for schema correctness and delta overlaps",
|
|
1623
|
+
{
|
|
1624
|
+
id: z20.string().describe("Document ID")
|
|
1625
|
+
},
|
|
1626
|
+
async ({ id }) => {
|
|
1627
|
+
const result = getDoc7(store, id);
|
|
1628
|
+
if ("error" in result) return err7(result.error);
|
|
1629
|
+
const { doc } = result;
|
|
1630
|
+
const schemaResult = validateDocument(doc);
|
|
1631
|
+
const schemaErrors = schemaResult.success ? [] : schemaResult.errors;
|
|
1632
|
+
const overlapErrors = [];
|
|
1633
|
+
for (const [stateName, state] of Object.entries(doc.states)) {
|
|
1634
|
+
const overlaps = validateAllDeltas(state.deltas);
|
|
1635
|
+
for (const overlap of overlaps) {
|
|
1636
|
+
overlapErrors.push({ state: stateName, message: overlap.message });
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
const valid = schemaErrors.length === 0 && overlapErrors.length === 0;
|
|
1640
|
+
return ok7({
|
|
1641
|
+
valid,
|
|
1642
|
+
schemaErrors,
|
|
1643
|
+
overlapErrors,
|
|
1644
|
+
summary: valid ? "Document is valid" : `Found ${schemaErrors.length} schema error(s) and ${overlapErrors.length} overlap error(s)`
|
|
1645
|
+
});
|
|
1646
|
+
}
|
|
1647
|
+
);
|
|
1648
|
+
server.tool(
|
|
1649
|
+
"atelier_preview",
|
|
1650
|
+
"Preview the resolved state of all layers at a specific frame",
|
|
1651
|
+
{
|
|
1652
|
+
id: z20.string().describe("Document ID"),
|
|
1653
|
+
stateName: z20.string().describe("State name to preview"),
|
|
1654
|
+
frame: z20.number().int().min(0).describe("Frame number to resolve")
|
|
1655
|
+
},
|
|
1656
|
+
async ({ id, stateName, frame }) => {
|
|
1657
|
+
const result = getDoc7(store, id);
|
|
1658
|
+
if ("error" in result) return err7(result.error);
|
|
1659
|
+
const { doc } = result;
|
|
1660
|
+
if (!doc.states[stateName]) {
|
|
1661
|
+
return err7(`State "${stateName}" not found`);
|
|
1662
|
+
}
|
|
1663
|
+
try {
|
|
1664
|
+
const resolved = resolveFrame(doc, stateName, frame);
|
|
1665
|
+
const layers = resolved.layers.map((rl) => ({
|
|
1666
|
+
id: rl.id,
|
|
1667
|
+
visualType: rl.layer.visual.type,
|
|
1668
|
+
baseFrame: rl.layer.frame,
|
|
1669
|
+
baseBounds: rl.layer.bounds,
|
|
1670
|
+
baseOpacity: rl.layer.opacity,
|
|
1671
|
+
baseRotation: rl.layer.rotation,
|
|
1672
|
+
computedProperties: rl.computedProperties
|
|
1673
|
+
}));
|
|
1674
|
+
return ok7({
|
|
1675
|
+
stateName: resolved.stateName,
|
|
1676
|
+
frame: resolved.frame,
|
|
1677
|
+
duration: doc.states[stateName].duration,
|
|
1678
|
+
layers
|
|
1679
|
+
});
|
|
1680
|
+
} catch (e) {
|
|
1681
|
+
return err7(`Preview failed: ${e.message}`);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
);
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
// ../mcp/src/tools/templates.ts
|
|
1688
|
+
import { z as z21 } from "zod";
|
|
1689
|
+
function getDoc8(store, id) {
|
|
1690
|
+
const doc = store.get(id);
|
|
1691
|
+
if (!doc) return { error: `Document "${id}" not found` };
|
|
1692
|
+
return { doc };
|
|
1693
|
+
}
|
|
1694
|
+
function ok8(data) {
|
|
1695
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...data }) }] };
|
|
1696
|
+
}
|
|
1697
|
+
function err8(message) {
|
|
1698
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
|
|
1699
|
+
}
|
|
1700
|
+
function register8(server, store) {
|
|
1701
|
+
server.tool(
|
|
1702
|
+
"atelier_instantiate_template",
|
|
1703
|
+
"Instantiate a template document with variable bindings. Creates a new document in the store.",
|
|
1704
|
+
{
|
|
1705
|
+
id: z21.string().describe("Template document ID"),
|
|
1706
|
+
bindings: z21.record(z21.unknown()).describe("Variable bindings: { variableName: value }")
|
|
1707
|
+
},
|
|
1708
|
+
async ({ id, bindings }) => {
|
|
1709
|
+
const result = getDoc8(store, id);
|
|
1710
|
+
if ("error" in result) return err8(result.error);
|
|
1711
|
+
const { doc } = result;
|
|
1712
|
+
const templateResult = instantiateTemplate(doc, bindings);
|
|
1713
|
+
if (!templateResult.success) {
|
|
1714
|
+
return err8(`Template errors: ${templateResult.errors.map((e) => `${e.variable}: ${e.message}`).join("; ")}`);
|
|
1715
|
+
}
|
|
1716
|
+
const newId = store.create(templateResult.document);
|
|
1717
|
+
return ok8({
|
|
1718
|
+
id: newId,
|
|
1719
|
+
name: templateResult.document.name,
|
|
1720
|
+
message: "Template instantiated successfully"
|
|
1721
|
+
});
|
|
1722
|
+
}
|
|
1723
|
+
);
|
|
1724
|
+
server.tool(
|
|
1725
|
+
"atelier_find_variables",
|
|
1726
|
+
"Scan a document for {{variableName}} patterns. Returns the list of variable references found.",
|
|
1727
|
+
{
|
|
1728
|
+
id: z21.string().describe("Document ID")
|
|
1729
|
+
},
|
|
1730
|
+
async ({ id }) => {
|
|
1731
|
+
const result = getDoc8(store, id);
|
|
1732
|
+
if ("error" in result) return err8(result.error);
|
|
1733
|
+
const { doc } = result;
|
|
1734
|
+
const variables = findTemplateVariables(doc);
|
|
1735
|
+
const declared = doc.variables ? Object.keys(doc.variables) : [];
|
|
1736
|
+
return ok8({
|
|
1737
|
+
variables,
|
|
1738
|
+
declared,
|
|
1739
|
+
undeclared: variables.filter((v) => !declared.includes(v)),
|
|
1740
|
+
unused: declared.filter((d) => !variables.includes(d))
|
|
1741
|
+
});
|
|
1742
|
+
}
|
|
1743
|
+
);
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
// ../mcp/src/index.ts
|
|
1747
|
+
function createServer() {
|
|
1748
|
+
const store = new DocumentStore();
|
|
1749
|
+
const server = new McpServer(
|
|
1750
|
+
{ name: "atelier", version: "0.1.0" },
|
|
1751
|
+
{ capabilities: { tools: {} } }
|
|
1752
|
+
);
|
|
1753
|
+
register(server, store);
|
|
1754
|
+
register2(server, store);
|
|
1755
|
+
register3(server, store);
|
|
1756
|
+
register4(server, store);
|
|
1757
|
+
register5(server, store);
|
|
1758
|
+
register6(server, store);
|
|
1759
|
+
register7(server, store);
|
|
1760
|
+
register8(server, store);
|
|
1761
|
+
return { server, store };
|
|
1762
|
+
}
|
|
1763
|
+
var isMain = typeof process !== "undefined" && process.argv[1] && (process.argv[1].endsWith("/index.js") || process.argv[1].endsWith("/index.cjs") || process.argv[1].includes("atelier-mcp"));
|
|
1764
|
+
if (isMain) {
|
|
1765
|
+
const { server } = createServer();
|
|
1766
|
+
const transport = new StdioServerTransport();
|
|
1767
|
+
server.connect(transport).catch((error) => {
|
|
1768
|
+
console.error("Failed to start Atelier MCP server:", error);
|
|
1769
|
+
process.exit(1);
|
|
1770
|
+
});
|
|
1771
|
+
}
|
|
1772
|
+
export {
|
|
1773
|
+
DocumentStore,
|
|
1774
|
+
createServer,
|
|
1775
|
+
register5 as registerDeltaTools,
|
|
1776
|
+
register as registerDocumentTools,
|
|
1777
|
+
register2 as registerLayerTools,
|
|
1778
|
+
register6 as registerPresetTools,
|
|
1779
|
+
register7 as registerPreviewTools,
|
|
1780
|
+
register3 as registerShapeTools,
|
|
1781
|
+
register4 as registerStateTools,
|
|
1782
|
+
register8 as registerTemplateTools
|
|
1783
|
+
};
|
|
1784
|
+
//# sourceMappingURL=mcp.js.map
|