@a-company/atelier 0.28.2 → 0.36.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/dist/chunk-5QQESXI6.js +4432 -0
- package/dist/chunk-5QQESXI6.js.map +1 -0
- package/dist/{chunk-C5DBTHXB.js → chunk-JPZ4F4PW.js} +44 -2
- package/dist/chunk-JPZ4F4PW.js.map +1 -0
- package/dist/cli.cjs +2577 -529
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +405 -433
- package/dist/cli.js.map +1 -1
- package/dist/{dist-6IHF7WA7.js → dist-M67UZGFQ.js} +2 -2
- package/dist/index.cjs +2296 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +584 -2
- package/dist/index.d.ts +584 -2
- package/dist/index.js +112 -4
- package/dist/mcp.cjs +1247 -367
- package/dist/mcp.cjs.map +1 -1
- package/dist/mcp.js +1241 -367
- package/dist/mcp.js.map +1 -1
- package/package.json +15 -9
- package/src/web/inline-app.ts +867 -0
- package/src/web/tsconfig.json +9 -0
- package/templates/welcome.atelier +67 -0
- package/dist/chunk-C5DBTHXB.js.map +0 -1
- package/dist/chunk-LC7ICNMN.js +0 -2242
- package/dist/chunk-LC7ICNMN.js.map +0 -1
- /package/dist/{dist-6IHF7WA7.js.map → dist-M67UZGFQ.js.map} +0 -0
package/dist/mcp.js
CHANGED
|
@@ -12,13 +12,15 @@ function generateId() {
|
|
|
12
12
|
}
|
|
13
13
|
var DocumentStore = class {
|
|
14
14
|
docs = /* @__PURE__ */ new Map();
|
|
15
|
+
listeners = [];
|
|
15
16
|
/** Create a new document entry. Returns the assigned ID. */
|
|
16
|
-
create(doc, id) {
|
|
17
|
+
create(doc, id, source = "system") {
|
|
17
18
|
const docId = id ?? generateId();
|
|
18
19
|
if (this.docs.has(docId)) {
|
|
19
20
|
throw new Error(`Document "${docId}" already exists`);
|
|
20
21
|
}
|
|
21
22
|
this.docs.set(docId, doc);
|
|
23
|
+
this.emit(docId, doc, source);
|
|
22
24
|
return docId;
|
|
23
25
|
}
|
|
24
26
|
/** Get a document by ID. */
|
|
@@ -26,12 +28,15 @@ var DocumentStore = class {
|
|
|
26
28
|
return this.docs.get(id);
|
|
27
29
|
}
|
|
28
30
|
/** Set (overwrite) a document by ID. */
|
|
29
|
-
set(id, doc) {
|
|
31
|
+
set(id, doc, source = "system") {
|
|
30
32
|
this.docs.set(id, doc);
|
|
33
|
+
this.emit(id, doc, source);
|
|
31
34
|
}
|
|
32
35
|
/** Delete a document by ID. Returns true if it existed. */
|
|
33
|
-
delete(id) {
|
|
34
|
-
|
|
36
|
+
delete(id, source = "system") {
|
|
37
|
+
const existed = this.docs.delete(id);
|
|
38
|
+
if (existed) this.emit(id, null, source);
|
|
39
|
+
return existed;
|
|
35
40
|
}
|
|
36
41
|
/** Check if a document exists. */
|
|
37
42
|
has(id) {
|
|
@@ -45,14 +50,43 @@ var DocumentStore = class {
|
|
|
45
50
|
}
|
|
46
51
|
return result;
|
|
47
52
|
}
|
|
48
|
-
/** Clear all documents (useful for testing). */
|
|
53
|
+
/** Clear all documents (useful for testing). Does NOT fire onChange. */
|
|
49
54
|
clear() {
|
|
50
55
|
this.docs.clear();
|
|
51
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Subscribe to mutation events. The listener is invoked after every
|
|
59
|
+
* create()/set()/delete() with the changed document ID, the new value
|
|
60
|
+
* (or `null` when deleted), and the source tag.
|
|
61
|
+
*
|
|
62
|
+
* Returns an unsubscribe function. Multiple listeners are supported and
|
|
63
|
+
* delivered in registration order. A listener that throws is isolated —
|
|
64
|
+
* the error is swallowed and remaining listeners still fire.
|
|
65
|
+
*
|
|
66
|
+
* TODO(#10): the live WS bridge will subscribe here to forward LLM-side
|
|
67
|
+
* mutations into the in-browser Studio (which exposes the symmetric
|
|
68
|
+
* `AtelierStudio.applyMutation()` API).
|
|
69
|
+
*/
|
|
70
|
+
onChange(listener) {
|
|
71
|
+
this.listeners.push(listener);
|
|
72
|
+
return () => {
|
|
73
|
+
const idx = this.listeners.indexOf(listener);
|
|
74
|
+
if (idx !== -1) this.listeners.splice(idx, 1);
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
emit(id, doc, source) {
|
|
78
|
+
const snapshot = this.listeners.slice();
|
|
79
|
+
for (const listener of snapshot) {
|
|
80
|
+
try {
|
|
81
|
+
listener(id, doc, source);
|
|
82
|
+
} catch {
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
52
86
|
};
|
|
53
87
|
|
|
54
88
|
// ../mcp/src/tools/document.ts
|
|
55
|
-
import { z as
|
|
89
|
+
import { z as z16 } from "zod";
|
|
56
90
|
|
|
57
91
|
// ../schema/dist/index.js
|
|
58
92
|
import { z } from "zod";
|
|
@@ -69,6 +103,7 @@ import { z as z11 } from "zod";
|
|
|
69
103
|
import { z as z12 } from "zod";
|
|
70
104
|
import { z as z13 } from "zod";
|
|
71
105
|
import { z as z14 } from "zod";
|
|
106
|
+
import { z as z15 } from "zod";
|
|
72
107
|
import { parse as yamlParse, stringify as yamlStringify } from "yaml";
|
|
73
108
|
var PixelSchema = z.number();
|
|
74
109
|
var PercentageSchema = z.string().regex(/^-?\d+(\.\d+)?%$/, {
|
|
@@ -310,6 +345,18 @@ var ImageVisualSchema = z8.object({
|
|
|
310
345
|
spritesheet: SpritesheetConfigSchema.optional(),
|
|
311
346
|
frameIndex: z8.number().int().min(0).optional()
|
|
312
347
|
});
|
|
348
|
+
var VideoVisualSchema = z8.object({
|
|
349
|
+
type: z8.literal("video"),
|
|
350
|
+
assetId: z8.string().min(1, "assetId is required"),
|
|
351
|
+
src: z8.string().optional(),
|
|
352
|
+
startFrame: z8.number().int().min(0).optional(),
|
|
353
|
+
sourceOffset: z8.number().min(0).optional(),
|
|
354
|
+
sourceEnd: z8.number().positive().optional(),
|
|
355
|
+
playbackRate: z8.number().positive().optional(),
|
|
356
|
+
volume: z8.number().min(0).max(1).optional(),
|
|
357
|
+
muted: z8.boolean().optional(),
|
|
358
|
+
objectFit: z8.enum(["contain", "cover", "fill"]).optional()
|
|
359
|
+
});
|
|
313
360
|
var GroupVisualSchema = z8.object({
|
|
314
361
|
type: z8.literal("group")
|
|
315
362
|
});
|
|
@@ -323,6 +370,7 @@ var VisualSchema = z8.discriminatedUnion("type", [
|
|
|
323
370
|
ShapeVisualSchema,
|
|
324
371
|
TextVisualSchema,
|
|
325
372
|
ImageVisualSchema,
|
|
373
|
+
VideoVisualSchema,
|
|
326
374
|
GroupVisualSchema,
|
|
327
375
|
RefVisualSchema
|
|
328
376
|
]);
|
|
@@ -442,7 +490,7 @@ var VariableSchema = z12.object({
|
|
|
442
490
|
default: z12.unknown().optional(),
|
|
443
491
|
description: z12.string().optional()
|
|
444
492
|
});
|
|
445
|
-
var AssetTypeSchema = z13.enum(["image", "svg", "font", "animation", "audio"]);
|
|
493
|
+
var AssetTypeSchema = z13.enum(["image", "svg", "font", "animation", "audio", "video"]);
|
|
446
494
|
var AssetSchema = z13.object({
|
|
447
495
|
type: AssetTypeSchema,
|
|
448
496
|
src: z13.string().min(1, "Asset src is required"),
|
|
@@ -453,6 +501,12 @@ var AssetSchema = z13.object({
|
|
|
453
501
|
frameCount: z13.number().int().positive().optional(),
|
|
454
502
|
frameWidth: z13.number().positive(),
|
|
455
503
|
frameHeight: z13.number().positive()
|
|
504
|
+
}).optional(),
|
|
505
|
+
videoMeta: z13.object({
|
|
506
|
+
duration: z13.number().positive("videoMeta.duration must be positive"),
|
|
507
|
+
fps: z13.number().positive("videoMeta.fps must be positive"),
|
|
508
|
+
width: z13.number().int().positive(),
|
|
509
|
+
height: z13.number().int().positive()
|
|
456
510
|
}).optional()
|
|
457
511
|
});
|
|
458
512
|
var CanvasSchema = z14.object({
|
|
@@ -473,6 +527,121 @@ var AtelierDocumentSchema = z14.object({
|
|
|
473
527
|
layers: z14.array(LayerSchema),
|
|
474
528
|
states: z14.record(z14.string(), StateSchema)
|
|
475
529
|
});
|
|
530
|
+
var SilencePolicySchema = z15.object({
|
|
531
|
+
noise: z15.string().optional(),
|
|
532
|
+
min_silence: z15.number().nonnegative().optional(),
|
|
533
|
+
default_padding_pre: z15.number().nonnegative().optional(),
|
|
534
|
+
default_padding_post: z15.number().nonnegative().optional(),
|
|
535
|
+
match_tolerance: z15.number().nonnegative().optional()
|
|
536
|
+
}).strict();
|
|
537
|
+
var CaptionStyleSchema = z15.object({
|
|
538
|
+
font_family: z15.string().optional(),
|
|
539
|
+
font_size: z15.number().positive().optional(),
|
|
540
|
+
font_weight: z15.union([z15.literal("normal"), z15.literal("bold"), z15.number()]).optional(),
|
|
541
|
+
text_align: z15.enum(["left", "center", "right"]).optional(),
|
|
542
|
+
color: z15.string().optional(),
|
|
543
|
+
y_ratio: z15.number().min(0).max(1).optional(),
|
|
544
|
+
width_ratio: z15.number().min(0).max(1).optional(),
|
|
545
|
+
fade_seconds: z15.number().nonnegative().optional()
|
|
546
|
+
}).strict();
|
|
547
|
+
var CaptionGroupingSchema = z15.object({
|
|
548
|
+
max_words: z15.number().int().positive().optional(),
|
|
549
|
+
pause_gap: z15.number().nonnegative().optional()
|
|
550
|
+
}).strict();
|
|
551
|
+
var OverlayAnchorSchema = z15.enum([
|
|
552
|
+
"top-left",
|
|
553
|
+
"top-right",
|
|
554
|
+
"bottom-left",
|
|
555
|
+
"bottom-right"
|
|
556
|
+
]);
|
|
557
|
+
var OverlayTextStyleSchema = z15.object({
|
|
558
|
+
font_family: z15.string().optional(),
|
|
559
|
+
font_size: z15.number().positive().optional(),
|
|
560
|
+
font_weight: z15.union([z15.literal("normal"), z15.literal("bold"), z15.number()]).optional(),
|
|
561
|
+
color: z15.string().optional()
|
|
562
|
+
}).strict();
|
|
563
|
+
function validatePageNumberFormat(format, ctx) {
|
|
564
|
+
if (format.length === 0) {
|
|
565
|
+
ctx.addIssue({
|
|
566
|
+
code: z15.ZodIssueCode.custom,
|
|
567
|
+
message: "format must be a non-empty string"
|
|
568
|
+
});
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
const open = (format.match(/\{/g) ?? []).length;
|
|
572
|
+
const close = (format.match(/\}/g) ?? []).length;
|
|
573
|
+
if (open !== close) {
|
|
574
|
+
ctx.addIssue({
|
|
575
|
+
code: z15.ZodIssueCode.custom,
|
|
576
|
+
message: `format has unbalanced braces (${open} '{' vs ${close} '}')`
|
|
577
|
+
});
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
const groupRe = /\{([^{}]*)\}/g;
|
|
581
|
+
const groupRule = /^(current|total)(:0\d+d)?$/;
|
|
582
|
+
let m;
|
|
583
|
+
let sawCurrent = false;
|
|
584
|
+
let sawTotal = false;
|
|
585
|
+
while ((m = groupRe.exec(format)) !== null) {
|
|
586
|
+
const inner = m[1];
|
|
587
|
+
if (!groupRule.test(inner)) {
|
|
588
|
+
ctx.addIssue({
|
|
589
|
+
code: z15.ZodIssueCode.custom,
|
|
590
|
+
message: `format placeholder "{${inner}}" is not recognized \u2014 expected {current}, {total}, {current:0Nd}, or {total:0Nd}`
|
|
591
|
+
});
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
if (inner.startsWith("current")) sawCurrent = true;
|
|
595
|
+
if (inner.startsWith("total")) sawTotal = true;
|
|
596
|
+
}
|
|
597
|
+
if (!sawCurrent && !sawTotal) {
|
|
598
|
+
ctx.addIssue({
|
|
599
|
+
code: z15.ZodIssueCode.custom,
|
|
600
|
+
message: "format must contain at least one {current} or {total} placeholder"
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
var OverlayHandleRuleSchema = z15.object({
|
|
605
|
+
text: z15.string().min(1),
|
|
606
|
+
anchor: OverlayAnchorSchema,
|
|
607
|
+
margin: z15.number().nonnegative().optional(),
|
|
608
|
+
style: OverlayTextStyleSchema.optional()
|
|
609
|
+
}).strict();
|
|
610
|
+
var OverlayPageNumberRuleSchema = z15.object({
|
|
611
|
+
format: z15.string().superRefine(validatePageNumberFormat),
|
|
612
|
+
anchor: OverlayAnchorSchema,
|
|
613
|
+
margin: z15.number().nonnegative().optional(),
|
|
614
|
+
style: OverlayTextStyleSchema.optional()
|
|
615
|
+
}).strict();
|
|
616
|
+
var OverlayRulesSchema = z15.object({
|
|
617
|
+
handle: OverlayHandleRuleSchema.optional(),
|
|
618
|
+
page_number: OverlayPageNumberRuleSchema.optional()
|
|
619
|
+
}).strict();
|
|
620
|
+
var StudioRecipeSchema = z15.object({
|
|
621
|
+
version: z15.string(),
|
|
622
|
+
name: z15.string(),
|
|
623
|
+
description: z15.string().optional(),
|
|
624
|
+
author: z15.string().optional(),
|
|
625
|
+
tags: z15.array(z15.string()).optional(),
|
|
626
|
+
silence_policy: SilencePolicySchema.optional(),
|
|
627
|
+
caption_style: CaptionStyleSchema.optional(),
|
|
628
|
+
caption_grouping: CaptionGroupingSchema.optional(),
|
|
629
|
+
// Phase 1.5 — first-class overlay rules
|
|
630
|
+
overlay_rules: OverlayRulesSchema.optional(),
|
|
631
|
+
// Reserved — Phase 3 (parse-opaque)
|
|
632
|
+
caption_highlight: z15.unknown().optional(),
|
|
633
|
+
transition_kit: z15.unknown().optional(),
|
|
634
|
+
palette: z15.unknown().optional(),
|
|
635
|
+
audio_policy: z15.unknown().optional(),
|
|
636
|
+
aspect_targets: z15.array(z15.unknown()).optional()
|
|
637
|
+
}).strict();
|
|
638
|
+
var RESERVED_RECIPE_FIELDS = [
|
|
639
|
+
"caption_highlight",
|
|
640
|
+
"transition_kit",
|
|
641
|
+
"palette",
|
|
642
|
+
"audio_policy",
|
|
643
|
+
"aspect_targets"
|
|
644
|
+
];
|
|
476
645
|
function formatErrors(error) {
|
|
477
646
|
return error.issues.map((issue) => ({
|
|
478
647
|
path: issue.path.join(".") || "(root)",
|
|
@@ -486,14 +655,30 @@ function validateDocument(input) {
|
|
|
486
655
|
}
|
|
487
656
|
return { success: false, errors: formatErrors(result.error) };
|
|
488
657
|
}
|
|
658
|
+
function validateRecipe(recipe) {
|
|
659
|
+
const parsed = StudioRecipeSchema.safeParse(recipe);
|
|
660
|
+
if (!parsed.success) {
|
|
661
|
+
return { success: false, errors: formatErrors(parsed.error) };
|
|
662
|
+
}
|
|
663
|
+
const warnings = [];
|
|
664
|
+
const data = parsed.data;
|
|
665
|
+
for (const field of RESERVED_RECIPE_FIELDS) {
|
|
666
|
+
if (data[field] !== void 0) {
|
|
667
|
+
warnings.push(
|
|
668
|
+
`${field} is reserved for Phase 3 and currently has no effect.`
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
return { success: true, data, ...warnings.length > 0 && { warnings } };
|
|
673
|
+
}
|
|
489
674
|
function parseAtelier(yamlString) {
|
|
490
675
|
let parsed;
|
|
491
676
|
try {
|
|
492
677
|
parsed = yamlParse(yamlString);
|
|
493
|
-
} catch (
|
|
678
|
+
} catch (err20) {
|
|
494
679
|
return {
|
|
495
680
|
success: false,
|
|
496
|
-
errors: [{ path: "(yaml)", message: `YAML parse error: ${
|
|
681
|
+
errors: [{ path: "(yaml)", message: `YAML parse error: ${err20.message}` }]
|
|
497
682
|
};
|
|
498
683
|
}
|
|
499
684
|
return validateDocument(parsed);
|
|
@@ -519,13 +704,13 @@ function register(server, store) {
|
|
|
519
704
|
"atelier_create",
|
|
520
705
|
"Create a new Atelier animation document with canvas settings",
|
|
521
706
|
{
|
|
522
|
-
name:
|
|
523
|
-
width:
|
|
524
|
-
height:
|
|
525
|
-
fps:
|
|
526
|
-
background:
|
|
527
|
-
description:
|
|
528
|
-
tags:
|
|
707
|
+
name: z16.string().describe("Animation name"),
|
|
708
|
+
width: z16.number().positive().describe("Canvas width in pixels"),
|
|
709
|
+
height: z16.number().positive().describe("Canvas height in pixels"),
|
|
710
|
+
fps: z16.number().positive().int().describe("Frames per second"),
|
|
711
|
+
background: z16.string().optional().describe("Background color (hex string)"),
|
|
712
|
+
description: z16.string().optional().describe("Animation description"),
|
|
713
|
+
tags: z16.array(z16.string()).optional().describe("Tags for categorization")
|
|
529
714
|
},
|
|
530
715
|
{ readOnlyHint: false, destructiveHint: false },
|
|
531
716
|
async ({ name, width, height, fps, background, description, tags }) => {
|
|
@@ -550,7 +735,7 @@ function register(server, store) {
|
|
|
550
735
|
"atelier_info",
|
|
551
736
|
"Get summary information about an Atelier document",
|
|
552
737
|
{
|
|
553
|
-
id:
|
|
738
|
+
id: z16.string().describe("Document ID")
|
|
554
739
|
},
|
|
555
740
|
{ readOnlyHint: true, destructiveHint: false },
|
|
556
741
|
async ({ id }) => {
|
|
@@ -578,8 +763,8 @@ function register(server, store) {
|
|
|
578
763
|
"atelier_load",
|
|
579
764
|
"Load an Atelier document from a YAML string",
|
|
580
765
|
{
|
|
581
|
-
id:
|
|
582
|
-
yaml:
|
|
766
|
+
id: z16.string().optional().describe("Custom document ID (auto-generated if omitted)"),
|
|
767
|
+
yaml: z16.string().describe("YAML string representing an Atelier document")
|
|
583
768
|
},
|
|
584
769
|
{ readOnlyHint: false, destructiveHint: false },
|
|
585
770
|
async ({ id, yaml }) => {
|
|
@@ -595,7 +780,7 @@ function register(server, store) {
|
|
|
595
780
|
"atelier_export",
|
|
596
781
|
"Export an Atelier document as a YAML string",
|
|
597
782
|
{
|
|
598
|
-
id:
|
|
783
|
+
id: z16.string().describe("Document ID")
|
|
599
784
|
},
|
|
600
785
|
{ readOnlyHint: true, destructiveHint: false },
|
|
601
786
|
async ({ id }) => {
|
|
@@ -618,7 +803,7 @@ function register(server, store) {
|
|
|
618
803
|
}
|
|
619
804
|
|
|
620
805
|
// ../mcp/src/tools/layers.ts
|
|
621
|
-
import { z as
|
|
806
|
+
import { z as z17 } from "zod";
|
|
622
807
|
function getDoc2(store, id) {
|
|
623
808
|
const doc = store.get(id);
|
|
624
809
|
if (!doc) return { error: `Document "${id}" not found` };
|
|
@@ -630,69 +815,69 @@ function ok2(data) {
|
|
|
630
815
|
function err2(message) {
|
|
631
816
|
return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
|
|
632
817
|
}
|
|
633
|
-
var VisualInputSchema =
|
|
634
|
-
type:
|
|
818
|
+
var VisualInputSchema = z17.object({
|
|
819
|
+
type: z17.enum(["shape", "text", "image", "group", "ref"]).describe("Visual type"),
|
|
635
820
|
// shape visual fields
|
|
636
|
-
shape:
|
|
637
|
-
type:
|
|
638
|
-
cornerRadius:
|
|
639
|
-
points:
|
|
640
|
-
x:
|
|
641
|
-
y:
|
|
642
|
-
in:
|
|
643
|
-
out:
|
|
821
|
+
shape: z17.object({
|
|
822
|
+
type: z17.enum(["rect", "ellipse", "path"]),
|
|
823
|
+
cornerRadius: z17.union([z17.number(), z17.tuple([z17.number(), z17.number(), z17.number(), z17.number()])]).optional(),
|
|
824
|
+
points: z17.array(z17.object({
|
|
825
|
+
x: z17.number(),
|
|
826
|
+
y: z17.number(),
|
|
827
|
+
in: z17.object({ x: z17.number(), y: z17.number() }).optional(),
|
|
828
|
+
out: z17.object({ x: z17.number(), y: z17.number() }).optional()
|
|
644
829
|
})).optional(),
|
|
645
|
-
closed:
|
|
830
|
+
closed: z17.boolean().optional()
|
|
646
831
|
}).optional(),
|
|
647
|
-
fill:
|
|
648
|
-
stroke:
|
|
832
|
+
fill: z17.record(z17.unknown()).optional(),
|
|
833
|
+
stroke: z17.record(z17.unknown()).optional(),
|
|
649
834
|
// text visual fields
|
|
650
|
-
content:
|
|
651
|
-
style:
|
|
835
|
+
content: z17.string().optional(),
|
|
836
|
+
style: z17.record(z17.unknown()).optional(),
|
|
652
837
|
// image visual fields
|
|
653
|
-
assetId:
|
|
654
|
-
sourceRect:
|
|
655
|
-
x:
|
|
656
|
-
y:
|
|
657
|
-
width:
|
|
658
|
-
height:
|
|
838
|
+
assetId: z17.string().optional(),
|
|
839
|
+
sourceRect: z17.object({
|
|
840
|
+
x: z17.number(),
|
|
841
|
+
y: z17.number(),
|
|
842
|
+
width: z17.number().positive(),
|
|
843
|
+
height: z17.number().positive()
|
|
659
844
|
}).optional(),
|
|
660
|
-
spritesheet:
|
|
661
|
-
columns:
|
|
662
|
-
rows:
|
|
663
|
-
frameCount:
|
|
664
|
-
frameWidth:
|
|
665
|
-
frameHeight:
|
|
845
|
+
spritesheet: z17.object({
|
|
846
|
+
columns: z17.number().int().positive(),
|
|
847
|
+
rows: z17.number().int().positive(),
|
|
848
|
+
frameCount: z17.number().int().positive().optional(),
|
|
849
|
+
frameWidth: z17.number().positive(),
|
|
850
|
+
frameHeight: z17.number().positive()
|
|
666
851
|
}).optional(),
|
|
667
|
-
frameIndex:
|
|
852
|
+
frameIndex: z17.number().int().min(0).optional(),
|
|
668
853
|
// ref visual fields
|
|
669
|
-
src:
|
|
670
|
-
state:
|
|
671
|
-
frame:
|
|
854
|
+
src: z17.string().optional(),
|
|
855
|
+
state: z17.string().optional(),
|
|
856
|
+
frame: z17.number().int().min(0).optional()
|
|
672
857
|
}).describe("Visual content definition");
|
|
673
858
|
function register2(server, store) {
|
|
674
859
|
server.tool(
|
|
675
860
|
"atelier_add_layer",
|
|
676
861
|
"Add a new layer to an Atelier document",
|
|
677
862
|
{
|
|
678
|
-
id:
|
|
679
|
-
layerId:
|
|
863
|
+
id: z17.string().describe("Document ID"),
|
|
864
|
+
layerId: z17.string().describe("Unique layer ID"),
|
|
680
865
|
visual: VisualInputSchema.describe("Visual content definition"),
|
|
681
|
-
x:
|
|
682
|
-
y:
|
|
683
|
-
width:
|
|
684
|
-
height:
|
|
685
|
-
description:
|
|
686
|
-
tags:
|
|
687
|
-
opacity:
|
|
688
|
-
rotation:
|
|
689
|
-
parentId:
|
|
690
|
-
anchorPoint:
|
|
691
|
-
scale:
|
|
692
|
-
visible:
|
|
693
|
-
tint:
|
|
694
|
-
color:
|
|
695
|
-
amount:
|
|
866
|
+
x: z17.union([z17.number(), z17.string()]).describe("X position (pixels or percentage)"),
|
|
867
|
+
y: z17.union([z17.number(), z17.string()]).describe("Y position (pixels or percentage)"),
|
|
868
|
+
width: z17.union([z17.number(), z17.string()]).describe("Width (pixels or percentage)"),
|
|
869
|
+
height: z17.union([z17.number(), z17.string()]).describe("Height (pixels or percentage)"),
|
|
870
|
+
description: z17.string().optional().describe("Layer description"),
|
|
871
|
+
tags: z17.array(z17.string()).optional().describe("Tags"),
|
|
872
|
+
opacity: z17.number().min(0).max(1).optional().describe("Opacity 0-1"),
|
|
873
|
+
rotation: z17.number().optional().describe("Rotation in degrees"),
|
|
874
|
+
parentId: z17.string().optional().describe("Parent layer ID for transform inheritance"),
|
|
875
|
+
anchorPoint: z17.object({ x: z17.number(), y: z17.number() }).optional().describe("Anchor point (0-1 normalized)"),
|
|
876
|
+
scale: z17.object({ x: z17.number(), y: z17.number() }).optional().describe("Scale factors"),
|
|
877
|
+
visible: z17.boolean().optional().describe("Whether layer is visible"),
|
|
878
|
+
tint: z17.object({
|
|
879
|
+
color: z17.string().describe("Tint color (hex string)"),
|
|
880
|
+
amount: z17.number().min(0).max(1).describe("Tint amount (0 = none, 1 = full)")
|
|
696
881
|
}).optional().describe("Color tint overlay")
|
|
697
882
|
},
|
|
698
883
|
{ readOnlyHint: false, destructiveHint: false },
|
|
@@ -729,20 +914,20 @@ function register2(server, store) {
|
|
|
729
914
|
"atelier_edit_layer",
|
|
730
915
|
"Edit properties of an existing layer",
|
|
731
916
|
{
|
|
732
|
-
id:
|
|
733
|
-
layerId:
|
|
734
|
-
x:
|
|
735
|
-
y:
|
|
736
|
-
width:
|
|
737
|
-
height:
|
|
738
|
-
description:
|
|
739
|
-
tags:
|
|
740
|
-
opacity:
|
|
741
|
-
rotation:
|
|
742
|
-
parentId:
|
|
743
|
-
anchorPoint:
|
|
744
|
-
scale:
|
|
745
|
-
visible:
|
|
917
|
+
id: z17.string().describe("Document ID"),
|
|
918
|
+
layerId: z17.string().describe("Layer ID to edit"),
|
|
919
|
+
x: z17.union([z17.number(), z17.string()]).optional().describe("New X position"),
|
|
920
|
+
y: z17.union([z17.number(), z17.string()]).optional().describe("New Y position"),
|
|
921
|
+
width: z17.union([z17.number(), z17.string()]).optional().describe("New width"),
|
|
922
|
+
height: z17.union([z17.number(), z17.string()]).optional().describe("New height"),
|
|
923
|
+
description: z17.string().optional().describe("New description"),
|
|
924
|
+
tags: z17.array(z17.string()).optional().describe("New tags"),
|
|
925
|
+
opacity: z17.number().min(0).max(1).optional().describe("New opacity"),
|
|
926
|
+
rotation: z17.number().optional().describe("New rotation"),
|
|
927
|
+
parentId: z17.string().nullable().optional().describe("New parent layer ID (null to clear)"),
|
|
928
|
+
anchorPoint: z17.object({ x: z17.number(), y: z17.number() }).optional().describe("New anchor point"),
|
|
929
|
+
scale: z17.object({ x: z17.number(), y: z17.number() }).optional().describe("New scale"),
|
|
930
|
+
visible: z17.boolean().optional().describe("New visibility")
|
|
746
931
|
},
|
|
747
932
|
{ readOnlyHint: false, destructiveHint: false },
|
|
748
933
|
async ({ id, layerId, x, y, width, height, description, tags, opacity, rotation, parentId, anchorPoint, scale, visible }) => {
|
|
@@ -777,8 +962,8 @@ function register2(server, store) {
|
|
|
777
962
|
"atelier_remove_layer",
|
|
778
963
|
"Remove a layer from an Atelier document",
|
|
779
964
|
{
|
|
780
|
-
id:
|
|
781
|
-
layerId:
|
|
965
|
+
id: z17.string().describe("Document ID"),
|
|
966
|
+
layerId: z17.string().describe("Layer ID to remove")
|
|
782
967
|
},
|
|
783
968
|
{ readOnlyHint: false, destructiveHint: true },
|
|
784
969
|
async ({ id, layerId }) => {
|
|
@@ -806,7 +991,7 @@ function register2(server, store) {
|
|
|
806
991
|
"atelier_list_layers",
|
|
807
992
|
"List all layers in an Atelier document",
|
|
808
993
|
{
|
|
809
|
-
id:
|
|
994
|
+
id: z17.string().describe("Document ID")
|
|
810
995
|
},
|
|
811
996
|
{ readOnlyHint: true, destructiveHint: false },
|
|
812
997
|
async ({ id }) => {
|
|
@@ -833,9 +1018,9 @@ function register2(server, store) {
|
|
|
833
1018
|
"atelier_reorder",
|
|
834
1019
|
"Move a layer to a new position in the layer stack",
|
|
835
1020
|
{
|
|
836
|
-
id:
|
|
837
|
-
layerId:
|
|
838
|
-
position:
|
|
1021
|
+
id: z17.string().describe("Document ID"),
|
|
1022
|
+
layerId: z17.string().describe("Layer ID to move"),
|
|
1023
|
+
position: z17.number().int().min(0).describe("Target position index (0-based)")
|
|
839
1024
|
},
|
|
840
1025
|
{ readOnlyHint: false, destructiveHint: false },
|
|
841
1026
|
async ({ id, layerId, position }) => {
|
|
@@ -853,7 +1038,7 @@ function register2(server, store) {
|
|
|
853
1038
|
}
|
|
854
1039
|
|
|
855
1040
|
// ../mcp/src/tools/shapes.ts
|
|
856
|
-
import { z as
|
|
1041
|
+
import { z as z18 } from "zod";
|
|
857
1042
|
function getDoc3(store, id) {
|
|
858
1043
|
const doc = store.get(id);
|
|
859
1044
|
if (!doc) return { error: `Document "${id}" not found` };
|
|
@@ -870,21 +1055,21 @@ function register3(server, store) {
|
|
|
870
1055
|
"atelier_set_shape",
|
|
871
1056
|
"Set the shape on a shape-type layer",
|
|
872
1057
|
{
|
|
873
|
-
id:
|
|
874
|
-
layerId:
|
|
875
|
-
shape:
|
|
876
|
-
type:
|
|
877
|
-
cornerRadius:
|
|
878
|
-
|
|
879
|
-
|
|
1058
|
+
id: z18.string().describe("Document ID"),
|
|
1059
|
+
layerId: z18.string().describe("Layer ID (must be a shape visual)"),
|
|
1060
|
+
shape: z18.object({
|
|
1061
|
+
type: z18.enum(["rect", "ellipse", "path"]).describe("Shape type"),
|
|
1062
|
+
cornerRadius: z18.union([
|
|
1063
|
+
z18.number(),
|
|
1064
|
+
z18.tuple([z18.number(), z18.number(), z18.number(), z18.number()])
|
|
880
1065
|
]).optional().describe("Corner radius (rect only)"),
|
|
881
|
-
points:
|
|
882
|
-
x:
|
|
883
|
-
y:
|
|
884
|
-
in:
|
|
885
|
-
out:
|
|
1066
|
+
points: z18.array(z18.object({
|
|
1067
|
+
x: z18.number(),
|
|
1068
|
+
y: z18.number(),
|
|
1069
|
+
in: z18.object({ x: z18.number(), y: z18.number() }).optional(),
|
|
1070
|
+
out: z18.object({ x: z18.number(), y: z18.number() }).optional()
|
|
886
1071
|
})).optional().describe("Path points (path only)"),
|
|
887
|
-
closed:
|
|
1072
|
+
closed: z18.boolean().optional().describe("Whether path is closed (path only)")
|
|
888
1073
|
}).describe("Shape definition")
|
|
889
1074
|
},
|
|
890
1075
|
{ readOnlyHint: false, destructiveHint: false },
|
|
@@ -903,20 +1088,20 @@ function register3(server, store) {
|
|
|
903
1088
|
"atelier_set_fill",
|
|
904
1089
|
"Set the fill on a shape-type layer",
|
|
905
1090
|
{
|
|
906
|
-
id:
|
|
907
|
-
layerId:
|
|
908
|
-
fill:
|
|
909
|
-
type:
|
|
910
|
-
color:
|
|
911
|
-
angle:
|
|
912
|
-
center:
|
|
913
|
-
x:
|
|
914
|
-
y:
|
|
1091
|
+
id: z18.string().describe("Document ID"),
|
|
1092
|
+
layerId: z18.string().describe("Layer ID (must be a shape visual)"),
|
|
1093
|
+
fill: z18.object({
|
|
1094
|
+
type: z18.enum(["solid", "linear-gradient", "radial-gradient"]).describe("Fill type"),
|
|
1095
|
+
color: z18.unknown().optional().describe("Color for solid fill (hex string or RGBA/HSLA object)"),
|
|
1096
|
+
angle: z18.number().optional().describe("Angle in degrees (linear-gradient only)"),
|
|
1097
|
+
center: z18.object({
|
|
1098
|
+
x: z18.union([z18.number(), z18.string()]),
|
|
1099
|
+
y: z18.union([z18.number(), z18.string()])
|
|
915
1100
|
}).optional().describe("Center point (radial-gradient only)"),
|
|
916
|
-
radius:
|
|
917
|
-
stops:
|
|
918
|
-
offset:
|
|
919
|
-
color:
|
|
1101
|
+
radius: z18.union([z18.number(), z18.string()]).optional().describe("Radius (radial-gradient only)"),
|
|
1102
|
+
stops: z18.array(z18.object({
|
|
1103
|
+
offset: z18.number().min(0).max(1),
|
|
1104
|
+
color: z18.unknown()
|
|
920
1105
|
})).optional().describe("Gradient stops")
|
|
921
1106
|
}).describe("Fill definition")
|
|
922
1107
|
},
|
|
@@ -936,14 +1121,14 @@ function register3(server, store) {
|
|
|
936
1121
|
"atelier_set_stroke",
|
|
937
1122
|
"Set the stroke on a shape-type layer",
|
|
938
1123
|
{
|
|
939
|
-
id:
|
|
940
|
-
layerId:
|
|
941
|
-
stroke:
|
|
942
|
-
color:
|
|
943
|
-
width:
|
|
944
|
-
dash:
|
|
945
|
-
lineCap:
|
|
946
|
-
lineJoin:
|
|
1124
|
+
id: z18.string().describe("Document ID"),
|
|
1125
|
+
layerId: z18.string().describe("Layer ID (must be a shape visual)"),
|
|
1126
|
+
stroke: z18.object({
|
|
1127
|
+
color: z18.unknown().describe("Stroke color (hex string or RGBA/HSLA object)"),
|
|
1128
|
+
width: z18.number().positive().describe("Stroke width in pixels"),
|
|
1129
|
+
dash: z18.array(z18.number()).optional().describe("Dash pattern"),
|
|
1130
|
+
lineCap: z18.enum(["butt", "round", "square"]).optional().describe("Line cap style"),
|
|
1131
|
+
lineJoin: z18.enum(["miter", "round", "bevel"]).optional().describe("Line join style")
|
|
947
1132
|
}).describe("Stroke definition")
|
|
948
1133
|
},
|
|
949
1134
|
{ readOnlyHint: false, destructiveHint: false },
|
|
@@ -961,7 +1146,7 @@ function register3(server, store) {
|
|
|
961
1146
|
}
|
|
962
1147
|
|
|
963
1148
|
// ../mcp/src/tools/states.ts
|
|
964
|
-
import { z as
|
|
1149
|
+
import { z as z19 } from "zod";
|
|
965
1150
|
function getDoc4(store, id) {
|
|
966
1151
|
const doc = store.get(id);
|
|
967
1152
|
if (!doc) return { error: `Document "${id}" not found` };
|
|
@@ -978,11 +1163,11 @@ function register4(server, store) {
|
|
|
978
1163
|
"atelier_add_state",
|
|
979
1164
|
"Add a named animation state to a document",
|
|
980
1165
|
{
|
|
981
|
-
id:
|
|
982
|
-
stateName:
|
|
983
|
-
duration:
|
|
984
|
-
description:
|
|
985
|
-
tags:
|
|
1166
|
+
id: z19.string().describe("Document ID"),
|
|
1167
|
+
stateName: z19.string().describe("State name (e.g. 'intro', 'hover', 'exit')"),
|
|
1168
|
+
duration: z19.number().positive().int().describe("Duration in frames"),
|
|
1169
|
+
description: z19.string().optional().describe("State description"),
|
|
1170
|
+
tags: z19.array(z19.string()).optional().describe("Tags")
|
|
986
1171
|
},
|
|
987
1172
|
{ readOnlyHint: false, destructiveHint: false },
|
|
988
1173
|
async ({ id, stateName, duration, description, tags }) => {
|
|
@@ -1005,11 +1190,11 @@ function register4(server, store) {
|
|
|
1005
1190
|
"atelier_edit_state",
|
|
1006
1191
|
"Edit metadata of an existing animation state",
|
|
1007
1192
|
{
|
|
1008
|
-
id:
|
|
1009
|
-
stateName:
|
|
1010
|
-
duration:
|
|
1011
|
-
description:
|
|
1012
|
-
tags:
|
|
1193
|
+
id: z19.string().describe("Document ID"),
|
|
1194
|
+
stateName: z19.string().describe("State name to edit"),
|
|
1195
|
+
duration: z19.number().positive().int().optional().describe("New duration in frames"),
|
|
1196
|
+
description: z19.string().optional().describe("New description"),
|
|
1197
|
+
tags: z19.array(z19.string()).optional().describe("New tags")
|
|
1013
1198
|
},
|
|
1014
1199
|
{ readOnlyHint: false, destructiveHint: false },
|
|
1015
1200
|
async ({ id, stateName, duration, description, tags }) => {
|
|
@@ -1028,8 +1213,8 @@ function register4(server, store) {
|
|
|
1028
1213
|
"atelier_remove_state",
|
|
1029
1214
|
"Remove an animation state and all its deltas",
|
|
1030
1215
|
{
|
|
1031
|
-
id:
|
|
1032
|
-
stateName:
|
|
1216
|
+
id: z19.string().describe("Document ID"),
|
|
1217
|
+
stateName: z19.string().describe("State name to remove")
|
|
1033
1218
|
},
|
|
1034
1219
|
{ readOnlyHint: false, destructiveHint: true },
|
|
1035
1220
|
async ({ id, stateName }) => {
|
|
@@ -1046,7 +1231,7 @@ function register4(server, store) {
|
|
|
1046
1231
|
"atelier_list_states",
|
|
1047
1232
|
"List all animation states in a document",
|
|
1048
1233
|
{
|
|
1049
|
-
id:
|
|
1234
|
+
id: z19.string().describe("Document ID")
|
|
1050
1235
|
},
|
|
1051
1236
|
{ readOnlyHint: true, destructiveHint: false },
|
|
1052
1237
|
async ({ id }) => {
|
|
@@ -1066,7 +1251,7 @@ function register4(server, store) {
|
|
|
1066
1251
|
}
|
|
1067
1252
|
|
|
1068
1253
|
// ../mcp/src/tools/deltas.ts
|
|
1069
|
-
import { z as
|
|
1254
|
+
import { z as z20 } from "zod";
|
|
1070
1255
|
|
|
1071
1256
|
// ../math/dist/index.js
|
|
1072
1257
|
function linear(t) {
|
|
@@ -1622,7 +1807,18 @@ function resolveFrame(doc, stateName, frame, overrideDeltas) {
|
|
|
1622
1807
|
}
|
|
1623
1808
|
}
|
|
1624
1809
|
}
|
|
1625
|
-
|
|
1810
|
+
const resolvedLayer = { id: layer.id, layer, computedProperties };
|
|
1811
|
+
if (layer.visual.type === "video") {
|
|
1812
|
+
const video = layer.visual;
|
|
1813
|
+
const fps = doc.canvas.fps;
|
|
1814
|
+
const startFrame = video.startFrame ?? 0;
|
|
1815
|
+
const sourceOffset = video.sourceOffset ?? 0;
|
|
1816
|
+
const playbackRate = video.playbackRate ?? 1;
|
|
1817
|
+
const relativeFrame = Math.max(0, frame - startFrame);
|
|
1818
|
+
const sourceTime = relativeFrame / fps * playbackRate + sourceOffset;
|
|
1819
|
+
resolvedLayer.videoSourceTime = video.sourceEnd !== void 0 ? Math.min(sourceTime, video.sourceEnd) : sourceTime;
|
|
1820
|
+
}
|
|
1821
|
+
return resolvedLayer;
|
|
1626
1822
|
});
|
|
1627
1823
|
return { frame, stateName, layers: resolvedLayers };
|
|
1628
1824
|
}
|
|
@@ -1794,17 +1990,17 @@ function register5(server, store) {
|
|
|
1794
1990
|
"atelier_add_delta",
|
|
1795
1991
|
"Add an animation delta (keyframe transition) to a state",
|
|
1796
1992
|
{
|
|
1797
|
-
id:
|
|
1798
|
-
stateName:
|
|
1799
|
-
layer:
|
|
1993
|
+
id: z20.string().describe("Document ID"),
|
|
1994
|
+
stateName: z20.string().describe("State name"),
|
|
1995
|
+
layer: z20.string().describe("Target layer ID"),
|
|
1800
1996
|
property: AnimatablePropertyEnum.describe("Property to animate"),
|
|
1801
|
-
range:
|
|
1802
|
-
from:
|
|
1803
|
-
to:
|
|
1997
|
+
range: z20.tuple([z20.number().int().min(0), z20.number().int().min(0)]).describe("Frame range [start, end] inclusive"),
|
|
1998
|
+
from: z20.unknown().describe("Starting value"),
|
|
1999
|
+
to: z20.unknown().describe("Ending value"),
|
|
1804
2000
|
easing: EasingInputSchema.optional().describe("Easing function"),
|
|
1805
|
-
description:
|
|
1806
|
-
tags:
|
|
1807
|
-
deltaId:
|
|
2001
|
+
description: z20.string().optional().describe("Delta description"),
|
|
2002
|
+
tags: z20.array(z20.string()).optional().describe("Tags"),
|
|
2003
|
+
deltaId: z20.string().optional().describe("Custom delta ID")
|
|
1808
2004
|
},
|
|
1809
2005
|
{ readOnlyHint: false, destructiveHint: false },
|
|
1810
2006
|
async ({ id, stateName, layer, property, range, from, to, easing, description, tags, deltaId }) => {
|
|
@@ -1842,17 +2038,17 @@ function register5(server, store) {
|
|
|
1842
2038
|
"atelier_edit_delta",
|
|
1843
2039
|
"Edit an existing delta by index within a state",
|
|
1844
2040
|
{
|
|
1845
|
-
id:
|
|
1846
|
-
stateName:
|
|
1847
|
-
deltaIndex:
|
|
1848
|
-
layer:
|
|
2041
|
+
id: z20.string().describe("Document ID"),
|
|
2042
|
+
stateName: z20.string().describe("State name"),
|
|
2043
|
+
deltaIndex: z20.number().int().min(0).describe("Delta index within the state"),
|
|
2044
|
+
layer: z20.string().optional().describe("New target layer ID"),
|
|
1849
2045
|
property: AnimatablePropertyEnum.optional().describe("New property to animate"),
|
|
1850
|
-
range:
|
|
1851
|
-
from:
|
|
1852
|
-
to:
|
|
2046
|
+
range: z20.tuple([z20.number().int().min(0), z20.number().int().min(0)]).optional().describe("New frame range [start, end]"),
|
|
2047
|
+
from: z20.unknown().optional().describe("New starting value"),
|
|
2048
|
+
to: z20.unknown().optional().describe("New ending value"),
|
|
1853
2049
|
easing: EasingInputSchema.optional().describe("New easing function"),
|
|
1854
|
-
description:
|
|
1855
|
-
tags:
|
|
2050
|
+
description: z20.string().optional().describe("New description"),
|
|
2051
|
+
tags: z20.array(z20.string()).optional().describe("New tags")
|
|
1856
2052
|
},
|
|
1857
2053
|
{ readOnlyHint: false, destructiveHint: false },
|
|
1858
2054
|
async ({ id, stateName, deltaIndex, layer, property, range, from, to, easing, description, tags }) => {
|
|
@@ -1891,9 +2087,9 @@ function register5(server, store) {
|
|
|
1891
2087
|
"atelier_remove_delta",
|
|
1892
2088
|
"Remove a delta by index from a state",
|
|
1893
2089
|
{
|
|
1894
|
-
id:
|
|
1895
|
-
stateName:
|
|
1896
|
-
deltaIndex:
|
|
2090
|
+
id: z20.string().describe("Document ID"),
|
|
2091
|
+
stateName: z20.string().describe("State name"),
|
|
2092
|
+
deltaIndex: z20.number().int().min(0).describe("Delta index to remove")
|
|
1897
2093
|
},
|
|
1898
2094
|
{ readOnlyHint: false, destructiveHint: true },
|
|
1899
2095
|
async ({ id, stateName, deltaIndex }) => {
|
|
@@ -1921,12 +2117,12 @@ function register5(server, store) {
|
|
|
1921
2117
|
"atelier_apply_preset",
|
|
1922
2118
|
"Apply a preset to a layer, expanding it into concrete deltas",
|
|
1923
2119
|
{
|
|
1924
|
-
id:
|
|
1925
|
-
stateName:
|
|
1926
|
-
presetName:
|
|
1927
|
-
layerId:
|
|
1928
|
-
startFrame:
|
|
1929
|
-
duration:
|
|
2120
|
+
id: z20.string().describe("Document ID"),
|
|
2121
|
+
stateName: z20.string().describe("State name to add deltas to"),
|
|
2122
|
+
presetName: z20.string().describe("Preset name defined in the document"),
|
|
2123
|
+
layerId: z20.string().describe("Target layer ID"),
|
|
2124
|
+
startFrame: z20.number().int().min(0).optional().describe("Start frame (default: 0)"),
|
|
2125
|
+
duration: z20.number().positive().int().optional().describe("Duration for preset (default: state duration)")
|
|
1930
2126
|
},
|
|
1931
2127
|
{ readOnlyHint: false, destructiveHint: false },
|
|
1932
2128
|
async ({ id, stateName, presetName, layerId, startFrame, duration }) => {
|
|
@@ -1969,7 +2165,7 @@ ${errors.join("\n")}`);
|
|
|
1969
2165
|
}
|
|
1970
2166
|
|
|
1971
2167
|
// ../mcp/src/tools/presets.ts
|
|
1972
|
-
import { z as
|
|
2168
|
+
import { z as z21 } from "zod";
|
|
1973
2169
|
function getDoc6(store, id) {
|
|
1974
2170
|
const doc = store.get(id);
|
|
1975
2171
|
if (!doc) return { error: `Document "${id}" not found` };
|
|
@@ -1983,11 +2179,11 @@ function err6(message) {
|
|
|
1983
2179
|
}
|
|
1984
2180
|
var AnimatablePropertyEnum2 = AnimatablePropertySchema;
|
|
1985
2181
|
var EasingInputSchema2 = EasingSchema;
|
|
1986
|
-
var PresetDeltaSchema2 =
|
|
2182
|
+
var PresetDeltaSchema2 = z21.object({
|
|
1987
2183
|
property: AnimatablePropertyEnum2.describe("Animatable property"),
|
|
1988
|
-
offset:
|
|
1989
|
-
from:
|
|
1990
|
-
to:
|
|
2184
|
+
offset: z21.tuple([z21.number().int().min(0), z21.number().int().min(0)]).optional().describe("Relative frame offset [start, end]"),
|
|
2185
|
+
from: z21.unknown().describe("Starting value"),
|
|
2186
|
+
to: z21.unknown().describe("Ending value"),
|
|
1991
2187
|
easing: EasingInputSchema2.optional().describe("Easing function")
|
|
1992
2188
|
});
|
|
1993
2189
|
function register6(server, store) {
|
|
@@ -1995,11 +2191,11 @@ function register6(server, store) {
|
|
|
1995
2191
|
"atelier_define_preset",
|
|
1996
2192
|
"Define a reusable animation preset on a document",
|
|
1997
2193
|
{
|
|
1998
|
-
id:
|
|
1999
|
-
presetName:
|
|
2000
|
-
description:
|
|
2001
|
-
tags:
|
|
2002
|
-
deltas:
|
|
2194
|
+
id: z21.string().describe("Document ID"),
|
|
2195
|
+
presetName: z21.string().describe("Preset name"),
|
|
2196
|
+
description: z21.string().optional().describe("Preset description"),
|
|
2197
|
+
tags: z21.array(z21.string()).optional().describe("Tags"),
|
|
2198
|
+
deltas: z21.array(PresetDeltaSchema2).min(1).describe("Array of preset delta definitions")
|
|
2003
2199
|
},
|
|
2004
2200
|
{ readOnlyHint: false, destructiveHint: false },
|
|
2005
2201
|
async ({ id, presetName, description, tags, deltas }) => {
|
|
@@ -2020,7 +2216,7 @@ function register6(server, store) {
|
|
|
2020
2216
|
"atelier_list_presets",
|
|
2021
2217
|
"List all presets defined on a document",
|
|
2022
2218
|
{
|
|
2023
|
-
id:
|
|
2219
|
+
id: z21.string().describe("Document ID")
|
|
2024
2220
|
},
|
|
2025
2221
|
{ readOnlyHint: true, destructiveHint: false },
|
|
2026
2222
|
async ({ id }) => {
|
|
@@ -2043,7 +2239,7 @@ function register6(server, store) {
|
|
|
2043
2239
|
}
|
|
2044
2240
|
|
|
2045
2241
|
// ../mcp/src/tools/preview.ts
|
|
2046
|
-
import { z as
|
|
2242
|
+
import { z as z22 } from "zod";
|
|
2047
2243
|
function getDoc7(store, id) {
|
|
2048
2244
|
const doc = store.get(id);
|
|
2049
2245
|
if (!doc) return { error: `Document "${id}" not found` };
|
|
@@ -2060,7 +2256,7 @@ function register7(server, store) {
|
|
|
2060
2256
|
"atelier_validate",
|
|
2061
2257
|
"Validate an Atelier document for schema correctness and delta overlaps",
|
|
2062
2258
|
{
|
|
2063
|
-
id:
|
|
2259
|
+
id: z22.string().describe("Document ID")
|
|
2064
2260
|
},
|
|
2065
2261
|
{ readOnlyHint: true, destructiveHint: false },
|
|
2066
2262
|
async ({ id }) => {
|
|
@@ -2089,9 +2285,9 @@ function register7(server, store) {
|
|
|
2089
2285
|
"atelier_preview",
|
|
2090
2286
|
"Preview the resolved state of all layers at a specific frame",
|
|
2091
2287
|
{
|
|
2092
|
-
id:
|
|
2093
|
-
stateName:
|
|
2094
|
-
frame:
|
|
2288
|
+
id: z22.string().describe("Document ID"),
|
|
2289
|
+
stateName: z22.string().describe("State name to preview"),
|
|
2290
|
+
frame: z22.number().int().min(0).describe("Frame number to resolve")
|
|
2095
2291
|
},
|
|
2096
2292
|
{ readOnlyHint: true, destructiveHint: false },
|
|
2097
2293
|
async ({ id, stateName, frame }) => {
|
|
@@ -2126,7 +2322,7 @@ function register7(server, store) {
|
|
|
2126
2322
|
}
|
|
2127
2323
|
|
|
2128
2324
|
// ../mcp/src/tools/templates.ts
|
|
2129
|
-
import { z as
|
|
2325
|
+
import { z as z23 } from "zod";
|
|
2130
2326
|
function getDoc8(store, id) {
|
|
2131
2327
|
const doc = store.get(id);
|
|
2132
2328
|
if (!doc) return { error: `Document "${id}" not found` };
|
|
@@ -2143,8 +2339,8 @@ function register8(server, store) {
|
|
|
2143
2339
|
"atelier_instantiate_template",
|
|
2144
2340
|
"Instantiate a template document with variable bindings. Creates a new document in the store.",
|
|
2145
2341
|
{
|
|
2146
|
-
id:
|
|
2147
|
-
bindings:
|
|
2342
|
+
id: z23.string().describe("Template document ID"),
|
|
2343
|
+
bindings: z23.record(z23.unknown()).describe("Variable bindings: { variableName: value }")
|
|
2148
2344
|
},
|
|
2149
2345
|
{ readOnlyHint: false, destructiveHint: false },
|
|
2150
2346
|
async ({ id, bindings }) => {
|
|
@@ -2167,7 +2363,7 @@ function register8(server, store) {
|
|
|
2167
2363
|
"atelier_find_variables",
|
|
2168
2364
|
"Scan a document for {{variableName}} patterns. Returns the list of variable references found.",
|
|
2169
2365
|
{
|
|
2170
|
-
id:
|
|
2366
|
+
id: z23.string().describe("Document ID")
|
|
2171
2367
|
},
|
|
2172
2368
|
{ readOnlyHint: true, destructiveHint: false },
|
|
2173
2369
|
async ({ id }) => {
|
|
@@ -2187,7 +2383,7 @@ function register8(server, store) {
|
|
|
2187
2383
|
}
|
|
2188
2384
|
|
|
2189
2385
|
// ../mcp/src/tools/layer-effects.ts
|
|
2190
|
-
import { z as
|
|
2386
|
+
import { z as z24 } from "zod";
|
|
2191
2387
|
function getDoc9(store, id) {
|
|
2192
2388
|
const doc = store.get(id);
|
|
2193
2389
|
if (!doc) return { error: `Document "${id}" not found` };
|
|
@@ -2204,9 +2400,9 @@ function register9(server, store) {
|
|
|
2204
2400
|
"atelier_set_blend_mode",
|
|
2205
2401
|
"Set or clear the blend mode on a layer",
|
|
2206
2402
|
{
|
|
2207
|
-
id:
|
|
2208
|
-
layerId:
|
|
2209
|
-
blendMode:
|
|
2403
|
+
id: z24.string().describe("Document ID"),
|
|
2404
|
+
layerId: z24.string().describe("Layer ID"),
|
|
2405
|
+
blendMode: z24.enum([
|
|
2210
2406
|
"normal",
|
|
2211
2407
|
"multiply",
|
|
2212
2408
|
"screen",
|
|
@@ -2244,13 +2440,13 @@ function register9(server, store) {
|
|
|
2244
2440
|
"atelier_set_shadow",
|
|
2245
2441
|
"Set or remove a drop shadow on a layer",
|
|
2246
2442
|
{
|
|
2247
|
-
id:
|
|
2248
|
-
layerId:
|
|
2249
|
-
shadow:
|
|
2250
|
-
color:
|
|
2251
|
-
blur:
|
|
2252
|
-
offsetX:
|
|
2253
|
-
offsetY:
|
|
2443
|
+
id: z24.string().describe("Document ID"),
|
|
2444
|
+
layerId: z24.string().describe("Layer ID"),
|
|
2445
|
+
shadow: z24.object({
|
|
2446
|
+
color: z24.unknown().describe("Shadow color (hex string or RGBA/HSLA object)"),
|
|
2447
|
+
blur: z24.number().min(0).describe("Blur radius in pixels"),
|
|
2448
|
+
offsetX: z24.number().optional().describe("Horizontal offset in pixels"),
|
|
2449
|
+
offsetY: z24.number().optional().describe("Vertical offset in pixels")
|
|
2254
2450
|
}).nullable().describe("Shadow definition, or null to remove")
|
|
2255
2451
|
},
|
|
2256
2452
|
{ readOnlyHint: false, destructiveHint: false },
|
|
@@ -2272,11 +2468,11 @@ function register9(server, store) {
|
|
|
2272
2468
|
"atelier_set_tint",
|
|
2273
2469
|
"Set or remove a color tint on a layer (works on any visual type)",
|
|
2274
2470
|
{
|
|
2275
|
-
id:
|
|
2276
|
-
layerId:
|
|
2277
|
-
tint:
|
|
2278
|
-
color:
|
|
2279
|
-
amount:
|
|
2471
|
+
id: z24.string().describe("Document ID"),
|
|
2472
|
+
layerId: z24.string().describe("Layer ID"),
|
|
2473
|
+
tint: z24.object({
|
|
2474
|
+
color: z24.string().describe("Tint color (hex string)"),
|
|
2475
|
+
amount: z24.number().min(0).max(1).describe("Tint amount (0 = none, 1 = full)")
|
|
2280
2476
|
}).nullable().describe("Tint definition, or null to remove")
|
|
2281
2477
|
},
|
|
2282
2478
|
{ readOnlyHint: false, destructiveHint: false },
|
|
@@ -2298,18 +2494,18 @@ function register9(server, store) {
|
|
|
2298
2494
|
"atelier_set_motion_path",
|
|
2299
2495
|
"Set or remove a motion path on a layer for path-based position animation",
|
|
2300
2496
|
{
|
|
2301
|
-
id:
|
|
2302
|
-
layerId:
|
|
2303
|
-
motionPath:
|
|
2304
|
-
points:
|
|
2305
|
-
x:
|
|
2306
|
-
y:
|
|
2307
|
-
in:
|
|
2308
|
-
out:
|
|
2497
|
+
id: z24.string().describe("Document ID"),
|
|
2498
|
+
layerId: z24.string().describe("Layer ID"),
|
|
2499
|
+
motionPath: z24.object({
|
|
2500
|
+
points: z24.array(z24.object({
|
|
2501
|
+
x: z24.number(),
|
|
2502
|
+
y: z24.number(),
|
|
2503
|
+
in: z24.object({ x: z24.number(), y: z24.number() }).optional(),
|
|
2504
|
+
out: z24.object({ x: z24.number(), y: z24.number() }).optional()
|
|
2309
2505
|
})).min(2).describe("Path points (minimum 2)"),
|
|
2310
|
-
closed:
|
|
2311
|
-
autoRotate:
|
|
2312
|
-
autoRotateOffset:
|
|
2506
|
+
closed: z24.boolean().optional().describe("Whether the path is closed"),
|
|
2507
|
+
autoRotate: z24.boolean().optional().describe("Auto-rotate layer to follow path tangent"),
|
|
2508
|
+
autoRotateOffset: z24.number().optional().describe("Rotation offset in degrees when autoRotate is enabled")
|
|
2313
2509
|
}).nullable().describe("Motion path definition, or null to remove")
|
|
2314
2510
|
},
|
|
2315
2511
|
{ readOnlyHint: false, destructiveHint: false },
|
|
@@ -2331,21 +2527,21 @@ function register9(server, store) {
|
|
|
2331
2527
|
"atelier_set_clip_path",
|
|
2332
2528
|
"Set or remove a clip path on a layer to restrict rendering to within a shape",
|
|
2333
2529
|
{
|
|
2334
|
-
id:
|
|
2335
|
-
layerId:
|
|
2336
|
-
clipPath:
|
|
2337
|
-
type:
|
|
2338
|
-
cornerRadius:
|
|
2339
|
-
|
|
2340
|
-
|
|
2530
|
+
id: z24.string().describe("Document ID"),
|
|
2531
|
+
layerId: z24.string().describe("Layer ID"),
|
|
2532
|
+
clipPath: z24.object({
|
|
2533
|
+
type: z24.enum(["rect", "ellipse", "path"]).describe("Clip shape type"),
|
|
2534
|
+
cornerRadius: z24.union([
|
|
2535
|
+
z24.number(),
|
|
2536
|
+
z24.tuple([z24.number(), z24.number(), z24.number(), z24.number()])
|
|
2341
2537
|
]).optional().describe("Corner radius (rect only)"),
|
|
2342
|
-
points:
|
|
2343
|
-
x:
|
|
2344
|
-
y:
|
|
2345
|
-
in:
|
|
2346
|
-
out:
|
|
2538
|
+
points: z24.array(z24.object({
|
|
2539
|
+
x: z24.number(),
|
|
2540
|
+
y: z24.number(),
|
|
2541
|
+
in: z24.object({ x: z24.number(), y: z24.number() }).optional(),
|
|
2542
|
+
out: z24.object({ x: z24.number(), y: z24.number() }).optional()
|
|
2347
2543
|
})).optional().describe("Path points (path only)"),
|
|
2348
|
-
closed:
|
|
2544
|
+
closed: z24.boolean().optional().describe("Whether path is closed (path only)")
|
|
2349
2545
|
}).nullable().describe("Clip path shape, or null to remove")
|
|
2350
2546
|
},
|
|
2351
2547
|
{ readOnlyHint: false, destructiveHint: false },
|
|
@@ -2367,45 +2563,45 @@ function register9(server, store) {
|
|
|
2367
2563
|
"atelier_edit_visual",
|
|
2368
2564
|
"Edit visual content of an existing layer (text content/style, image src/assetId, shape/fill/stroke)",
|
|
2369
2565
|
{
|
|
2370
|
-
id:
|
|
2371
|
-
layerId:
|
|
2566
|
+
id: z24.string().describe("Document ID"),
|
|
2567
|
+
layerId: z24.string().describe("Layer ID"),
|
|
2372
2568
|
// Text fields
|
|
2373
|
-
content:
|
|
2374
|
-
style:
|
|
2569
|
+
content: z24.string().optional().describe("New text content (text layers only)"),
|
|
2570
|
+
style: z24.record(z24.unknown()).optional().describe("Partial style update \u2014 only provided keys are overwritten (text layers only)"),
|
|
2375
2571
|
// Image fields
|
|
2376
|
-
src:
|
|
2377
|
-
assetId:
|
|
2378
|
-
sourceRect:
|
|
2379
|
-
x:
|
|
2380
|
-
y:
|
|
2381
|
-
width:
|
|
2382
|
-
height:
|
|
2572
|
+
src: z24.string().optional().describe("Image source URL (image layers only)"),
|
|
2573
|
+
assetId: z24.string().optional().describe("Asset ID reference (image layers only)"),
|
|
2574
|
+
sourceRect: z24.object({
|
|
2575
|
+
x: z24.number(),
|
|
2576
|
+
y: z24.number(),
|
|
2577
|
+
width: z24.number().positive(),
|
|
2578
|
+
height: z24.number().positive()
|
|
2383
2579
|
}).nullable().optional().describe("Source rectangle crop (image layers only). Null to remove."),
|
|
2384
|
-
spritesheet:
|
|
2385
|
-
columns:
|
|
2386
|
-
rows:
|
|
2387
|
-
frameCount:
|
|
2388
|
-
frameWidth:
|
|
2389
|
-
frameHeight:
|
|
2580
|
+
spritesheet: z24.object({
|
|
2581
|
+
columns: z24.number().int().positive(),
|
|
2582
|
+
rows: z24.number().int().positive(),
|
|
2583
|
+
frameCount: z24.number().int().positive().optional(),
|
|
2584
|
+
frameWidth: z24.number().positive(),
|
|
2585
|
+
frameHeight: z24.number().positive()
|
|
2390
2586
|
}).nullable().optional().describe("Spritesheet config (image layers only). Null to remove."),
|
|
2391
|
-
frameIndex:
|
|
2587
|
+
frameIndex: z24.number().int().min(0).optional().describe("Spritesheet frame index (image layers only)"),
|
|
2392
2588
|
// Shape fields
|
|
2393
|
-
shape:
|
|
2394
|
-
type:
|
|
2395
|
-
cornerRadius:
|
|
2396
|
-
|
|
2397
|
-
|
|
2589
|
+
shape: z24.object({
|
|
2590
|
+
type: z24.enum(["rect", "ellipse", "path"]),
|
|
2591
|
+
cornerRadius: z24.union([
|
|
2592
|
+
z24.number(),
|
|
2593
|
+
z24.tuple([z24.number(), z24.number(), z24.number(), z24.number()])
|
|
2398
2594
|
]).optional(),
|
|
2399
|
-
points:
|
|
2400
|
-
x:
|
|
2401
|
-
y:
|
|
2402
|
-
in:
|
|
2403
|
-
out:
|
|
2595
|
+
points: z24.array(z24.object({
|
|
2596
|
+
x: z24.number(),
|
|
2597
|
+
y: z24.number(),
|
|
2598
|
+
in: z24.object({ x: z24.number(), y: z24.number() }).optional(),
|
|
2599
|
+
out: z24.object({ x: z24.number(), y: z24.number() }).optional()
|
|
2404
2600
|
})).optional(),
|
|
2405
|
-
closed:
|
|
2601
|
+
closed: z24.boolean().optional()
|
|
2406
2602
|
}).optional().describe("New shape (shape layers only)"),
|
|
2407
|
-
fill:
|
|
2408
|
-
stroke:
|
|
2603
|
+
fill: z24.record(z24.unknown()).optional().describe("New fill definition (shape layers only)"),
|
|
2604
|
+
stroke: z24.record(z24.unknown()).optional().describe("New stroke definition (shape layers only)")
|
|
2409
2605
|
},
|
|
2410
2606
|
{ readOnlyHint: false, destructiveHint: false },
|
|
2411
2607
|
async ({ id, layerId, content, style, src, assetId, sourceRect, spritesheet, frameIndex, shape, fill, stroke }) => {
|
|
@@ -2486,7 +2682,7 @@ function register9(server, store) {
|
|
|
2486
2682
|
}
|
|
2487
2683
|
|
|
2488
2684
|
// ../mcp/src/tools/state-config.ts
|
|
2489
|
-
import { z as
|
|
2685
|
+
import { z as z25 } from "zod";
|
|
2490
2686
|
function getDoc10(store, id) {
|
|
2491
2687
|
const doc = store.get(id);
|
|
2492
2688
|
if (!doc) return { error: `Document "${id}" not found` };
|
|
@@ -2498,27 +2694,27 @@ function ok10(data) {
|
|
|
2498
2694
|
function err10(message) {
|
|
2499
2695
|
return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
|
|
2500
2696
|
}
|
|
2501
|
-
var EasingInputSchema3 =
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
type:
|
|
2506
|
-
x1:
|
|
2507
|
-
y1:
|
|
2508
|
-
x2:
|
|
2509
|
-
y2:
|
|
2697
|
+
var EasingInputSchema3 = z25.union([
|
|
2698
|
+
z25.enum(["ease-in", "ease-out", "ease-in-out"]),
|
|
2699
|
+
z25.object({ type: z25.literal("linear") }),
|
|
2700
|
+
z25.object({
|
|
2701
|
+
type: z25.literal("cubic-bezier"),
|
|
2702
|
+
x1: z25.number(),
|
|
2703
|
+
y1: z25.number(),
|
|
2704
|
+
x2: z25.number(),
|
|
2705
|
+
y2: z25.number()
|
|
2510
2706
|
}),
|
|
2511
|
-
|
|
2512
|
-
type:
|
|
2513
|
-
mass:
|
|
2514
|
-
stiffness:
|
|
2515
|
-
damping:
|
|
2516
|
-
velocity:
|
|
2707
|
+
z25.object({
|
|
2708
|
+
type: z25.literal("spring"),
|
|
2709
|
+
mass: z25.number().optional(),
|
|
2710
|
+
stiffness: z25.number().optional(),
|
|
2711
|
+
damping: z25.number().optional(),
|
|
2712
|
+
velocity: z25.number().optional()
|
|
2517
2713
|
}),
|
|
2518
|
-
|
|
2519
|
-
type:
|
|
2520
|
-
steps:
|
|
2521
|
-
position:
|
|
2714
|
+
z25.object({
|
|
2715
|
+
type: z25.literal("step"),
|
|
2716
|
+
steps: z25.number().int().positive(),
|
|
2717
|
+
position: z25.enum(["start", "end"]).optional()
|
|
2522
2718
|
})
|
|
2523
2719
|
]).describe("Easing function");
|
|
2524
2720
|
function register10(server, store) {
|
|
@@ -2526,14 +2722,14 @@ function register10(server, store) {
|
|
|
2526
2722
|
"atelier_set_audio",
|
|
2527
2723
|
"Set or remove an audio track on a state",
|
|
2528
2724
|
{
|
|
2529
|
-
id:
|
|
2530
|
-
stateName:
|
|
2531
|
-
audio:
|
|
2532
|
-
src:
|
|
2533
|
-
offset:
|
|
2534
|
-
volume:
|
|
2535
|
-
loop:
|
|
2536
|
-
startFrame:
|
|
2725
|
+
id: z25.string().describe("Document ID"),
|
|
2726
|
+
stateName: z25.string().describe("State name"),
|
|
2727
|
+
audio: z25.object({
|
|
2728
|
+
src: z25.string().describe("Audio source \u2014 asset reference, file path, or URL"),
|
|
2729
|
+
offset: z25.number().min(0).optional().describe("Skip into audio in seconds (default: 0)"),
|
|
2730
|
+
volume: z25.number().min(0).max(1).optional().describe("Playback volume 0\u20131 (default: 1)"),
|
|
2731
|
+
loop: z25.boolean().optional().describe("Loop for state duration (default: false)"),
|
|
2732
|
+
startFrame: z25.number().int().min(0).optional().describe("Frame to start playing (default: 0)")
|
|
2537
2733
|
}).nullable().describe("Audio configuration, or null to remove")
|
|
2538
2734
|
},
|
|
2539
2735
|
{ readOnlyHint: false, destructiveHint: false },
|
|
@@ -2555,11 +2751,11 @@ function register10(server, store) {
|
|
|
2555
2751
|
"atelier_configure_transition",
|
|
2556
2752
|
"Configure or remove a transition from one state to a target state",
|
|
2557
2753
|
{
|
|
2558
|
-
id:
|
|
2559
|
-
stateName:
|
|
2560
|
-
targetState:
|
|
2561
|
-
transition:
|
|
2562
|
-
duration:
|
|
2754
|
+
id: z25.string().describe("Document ID"),
|
|
2755
|
+
stateName: z25.string().describe("Source state name"),
|
|
2756
|
+
targetState: z25.string().describe("Target state name"),
|
|
2757
|
+
transition: z25.object({
|
|
2758
|
+
duration: z25.number().positive().int().describe("Transition duration in frames"),
|
|
2563
2759
|
easing: EasingInputSchema3.optional()
|
|
2564
2760
|
}).nullable().describe("Transition config, or null to remove")
|
|
2565
2761
|
},
|
|
@@ -2596,9 +2792,9 @@ function register10(server, store) {
|
|
|
2596
2792
|
"atelier_set_state_parent",
|
|
2597
2793
|
"Set or clear the parent state for hierarchical delta inheritance",
|
|
2598
2794
|
{
|
|
2599
|
-
id:
|
|
2600
|
-
stateName:
|
|
2601
|
-
parent:
|
|
2795
|
+
id: z25.string().describe("Document ID"),
|
|
2796
|
+
stateName: z25.string().describe("State name"),
|
|
2797
|
+
parent: z25.string().nullable().describe("Parent state name, or null to clear")
|
|
2602
2798
|
},
|
|
2603
2799
|
{ readOnlyHint: false, destructiveHint: false },
|
|
2604
2800
|
async ({ id, stateName, parent }) => {
|
|
@@ -2633,7 +2829,7 @@ function register10(server, store) {
|
|
|
2633
2829
|
}
|
|
2634
2830
|
|
|
2635
2831
|
// ../mcp/src/tools/export.ts
|
|
2636
|
-
import { z as
|
|
2832
|
+
import { z as z26 } from "zod";
|
|
2637
2833
|
|
|
2638
2834
|
// ../canvas/dist/index.js
|
|
2639
2835
|
function colorToCSS(color) {
|
|
@@ -3818,12 +4014,12 @@ function register11(server, store) {
|
|
|
3818
4014
|
"atelier_export_svg",
|
|
3819
4015
|
"Export a frame as an SVG string",
|
|
3820
4016
|
{
|
|
3821
|
-
id:
|
|
3822
|
-
stateName:
|
|
3823
|
-
frame:
|
|
3824
|
-
xmlDeclaration:
|
|
3825
|
-
viewBox:
|
|
3826
|
-
indent:
|
|
4017
|
+
id: z26.string().describe("Document ID"),
|
|
4018
|
+
stateName: z26.string().optional().describe("State name (defaults to first state)"),
|
|
4019
|
+
frame: z26.number().int().min(0).optional().describe("Frame number (defaults to 0)"),
|
|
4020
|
+
xmlDeclaration: z26.boolean().optional().describe("Include XML declaration (default: false)"),
|
|
4021
|
+
viewBox: z26.boolean().optional().describe("Include viewBox attribute (default: true)"),
|
|
4022
|
+
indent: z26.number().int().min(0).optional().describe("Indent size in spaces (default: 2)")
|
|
3827
4023
|
},
|
|
3828
4024
|
{ readOnlyHint: true, destructiveHint: false },
|
|
3829
4025
|
async ({ id, stateName, frame, xmlDeclaration, viewBox, indent }) => {
|
|
@@ -3854,8 +4050,8 @@ function register11(server, store) {
|
|
|
3854
4050
|
"atelier_export_lottie",
|
|
3855
4051
|
"Export a document to Lottie JSON format (lossy, best-effort)",
|
|
3856
4052
|
{
|
|
3857
|
-
id:
|
|
3858
|
-
stateName:
|
|
4053
|
+
id: z26.string().describe("Document ID"),
|
|
4054
|
+
stateName: z26.string().optional().describe("State to export (defaults to first state)")
|
|
3859
4055
|
},
|
|
3860
4056
|
{ readOnlyHint: true, destructiveHint: false },
|
|
3861
4057
|
async ({ id, stateName }) => {
|
|
@@ -3875,7 +4071,7 @@ function register11(server, store) {
|
|
|
3875
4071
|
}
|
|
3876
4072
|
|
|
3877
4073
|
// ../mcp/src/tools/assets.ts
|
|
3878
|
-
import { z as
|
|
4074
|
+
import { z as z27 } from "zod";
|
|
3879
4075
|
function getDoc12(store, id) {
|
|
3880
4076
|
const doc = store.get(id);
|
|
3881
4077
|
if (!doc) return { error: `Document "${id}" not found` };
|
|
@@ -3892,17 +4088,17 @@ function register12(server, store) {
|
|
|
3892
4088
|
"atelier_add_asset",
|
|
3893
4089
|
"Register an external asset (image, SVG, font, animation, audio) on a document",
|
|
3894
4090
|
{
|
|
3895
|
-
id:
|
|
3896
|
-
assetId:
|
|
3897
|
-
type:
|
|
3898
|
-
src:
|
|
3899
|
-
description:
|
|
3900
|
-
spritesheet:
|
|
3901
|
-
columns:
|
|
3902
|
-
rows:
|
|
3903
|
-
frameCount:
|
|
3904
|
-
frameWidth:
|
|
3905
|
-
frameHeight:
|
|
4091
|
+
id: z27.string().describe("Document ID"),
|
|
4092
|
+
assetId: z27.string().describe("Unique asset identifier"),
|
|
4093
|
+
type: z27.enum(["image", "svg", "font", "animation", "audio"]).describe("Asset type"),
|
|
4094
|
+
src: z27.string().describe("File path or URL"),
|
|
4095
|
+
description: z27.string().optional().describe("Human-readable description"),
|
|
4096
|
+
spritesheet: z27.object({
|
|
4097
|
+
columns: z27.number().int().positive().describe("Number of columns in the spritesheet grid"),
|
|
4098
|
+
rows: z27.number().int().positive().describe("Number of rows in the spritesheet grid"),
|
|
4099
|
+
frameCount: z27.number().int().positive().optional().describe("Total frame count (defaults to columns \xD7 rows)"),
|
|
4100
|
+
frameWidth: z27.number().positive().describe("Width of each frame in pixels"),
|
|
4101
|
+
frameHeight: z27.number().positive().describe("Height of each frame in pixels")
|
|
3906
4102
|
}).optional().describe("Spritesheet metadata (image assets only)")
|
|
3907
4103
|
},
|
|
3908
4104
|
{ readOnlyHint: false, destructiveHint: false },
|
|
@@ -3925,7 +4121,7 @@ function register12(server, store) {
|
|
|
3925
4121
|
"atelier_list_assets",
|
|
3926
4122
|
"List all registered assets with usage info (which layers/states reference them)",
|
|
3927
4123
|
{
|
|
3928
|
-
id:
|
|
4124
|
+
id: z27.string().describe("Document ID")
|
|
3929
4125
|
},
|
|
3930
4126
|
{ readOnlyHint: true, destructiveHint: false },
|
|
3931
4127
|
async ({ id }) => {
|
|
@@ -3952,8 +4148,8 @@ function register12(server, store) {
|
|
|
3952
4148
|
"atelier_remove_asset",
|
|
3953
4149
|
"Remove a registered asset. Warns if layers or state audio still reference it.",
|
|
3954
4150
|
{
|
|
3955
|
-
id:
|
|
3956
|
-
assetId:
|
|
4151
|
+
id: z27.string().describe("Document ID"),
|
|
4152
|
+
assetId: z27.string().describe("Asset ID to remove")
|
|
3957
4153
|
},
|
|
3958
4154
|
{ readOnlyHint: false, destructiveHint: true },
|
|
3959
4155
|
async ({ id, assetId }) => {
|
|
@@ -3977,7 +4173,7 @@ function register12(server, store) {
|
|
|
3977
4173
|
}
|
|
3978
4174
|
|
|
3979
4175
|
// ../mcp/src/tools/variables.ts
|
|
3980
|
-
import { z as
|
|
4176
|
+
import { z as z28 } from "zod";
|
|
3981
4177
|
function getDoc13(store, id) {
|
|
3982
4178
|
const doc = store.get(id);
|
|
3983
4179
|
if (!doc) return { error: `Document "${id}" not found` };
|
|
@@ -3994,11 +4190,11 @@ function register13(server, store) {
|
|
|
3994
4190
|
"atelier_add_variable",
|
|
3995
4191
|
"Define a template variable on a document",
|
|
3996
4192
|
{
|
|
3997
|
-
id:
|
|
3998
|
-
variableName:
|
|
3999
|
-
type:
|
|
4000
|
-
description:
|
|
4001
|
-
default:
|
|
4193
|
+
id: z28.string().describe("Document ID"),
|
|
4194
|
+
variableName: z28.string().describe("Variable name (used in {{variableName}} patterns)"),
|
|
4195
|
+
type: z28.enum(["string", "number", "color", "asset", "boolean"]).describe("Variable type"),
|
|
4196
|
+
description: z28.string().optional().describe("Human-readable description"),
|
|
4197
|
+
default: z28.unknown().optional().describe("Default value")
|
|
4002
4198
|
},
|
|
4003
4199
|
{ readOnlyHint: false, destructiveHint: false },
|
|
4004
4200
|
async (args) => {
|
|
@@ -4019,7 +4215,7 @@ function register13(server, store) {
|
|
|
4019
4215
|
"atelier_list_variables",
|
|
4020
4216
|
"List all declared variables with usage info (which {{references}} exist in the document)",
|
|
4021
4217
|
{
|
|
4022
|
-
id:
|
|
4218
|
+
id: z28.string().describe("Document ID")
|
|
4023
4219
|
},
|
|
4024
4220
|
{ readOnlyHint: true, destructiveHint: false },
|
|
4025
4221
|
async ({ id }) => {
|
|
@@ -4043,8 +4239,8 @@ function register13(server, store) {
|
|
|
4043
4239
|
"atelier_remove_variable",
|
|
4044
4240
|
"Remove a declared variable. Warns if {{references}} still exist in the document.",
|
|
4045
4241
|
{
|
|
4046
|
-
id:
|
|
4047
|
-
variableName:
|
|
4242
|
+
id: z28.string().describe("Document ID"),
|
|
4243
|
+
variableName: z28.string().describe("Variable name to remove")
|
|
4048
4244
|
},
|
|
4049
4245
|
{ readOnlyHint: false, destructiveHint: true },
|
|
4050
4246
|
async ({ id, variableName }) => {
|
|
@@ -4067,7 +4263,7 @@ function register13(server, store) {
|
|
|
4067
4263
|
}
|
|
4068
4264
|
|
|
4069
4265
|
// ../mcp/src/tools/interactions.ts
|
|
4070
|
-
import { z as
|
|
4266
|
+
import { z as z29 } from "zod";
|
|
4071
4267
|
function getDoc14(store, id) {
|
|
4072
4268
|
const doc = store.get(id);
|
|
4073
4269
|
if (!doc) return { error: `Document "${id}" not found` };
|
|
@@ -4084,23 +4280,23 @@ function register14(server, store) {
|
|
|
4084
4280
|
"atelier_add_interaction",
|
|
4085
4281
|
"Add a trigger-action interaction to a layer (click, hover, timer, signal \u2192 go-to-state, emit-signal, set-variable, toggle-visibility)",
|
|
4086
4282
|
{
|
|
4087
|
-
id:
|
|
4088
|
-
layerId:
|
|
4089
|
-
interactionId:
|
|
4090
|
-
trigger:
|
|
4091
|
-
type:
|
|
4092
|
-
delay:
|
|
4093
|
-
signal:
|
|
4283
|
+
id: z29.string().describe("Document ID"),
|
|
4284
|
+
layerId: z29.string().describe("Layer to attach the interaction to"),
|
|
4285
|
+
interactionId: z29.string().describe("Unique interaction ID"),
|
|
4286
|
+
trigger: z29.object({
|
|
4287
|
+
type: z29.enum(["click", "hover", "pointerdown", "pointerup", "timer", "signal"]).describe("Trigger type"),
|
|
4288
|
+
delay: z29.number().optional().describe("Delay in ms (timer trigger only)"),
|
|
4289
|
+
signal: z29.string().optional().describe("Signal name (signal trigger only)")
|
|
4094
4290
|
}).describe("What triggers the interaction"),
|
|
4095
|
-
action:
|
|
4096
|
-
type:
|
|
4097
|
-
state:
|
|
4098
|
-
signal:
|
|
4099
|
-
variable:
|
|
4100
|
-
value:
|
|
4101
|
-
targetLayer:
|
|
4291
|
+
action: z29.object({
|
|
4292
|
+
type: z29.enum(["go-to-state", "emit-signal", "set-variable", "toggle-visibility"]).describe("Action type"),
|
|
4293
|
+
state: z29.string().optional().describe("Target state (go-to-state only)"),
|
|
4294
|
+
signal: z29.string().optional().describe("Signal to emit (emit-signal only)"),
|
|
4295
|
+
variable: z29.string().optional().describe("Variable name (set-variable only)"),
|
|
4296
|
+
value: z29.unknown().optional().describe("Value to set (set-variable only)"),
|
|
4297
|
+
targetLayer: z29.string().optional().describe("Target layer ID (toggle-visibility, defaults to self)")
|
|
4102
4298
|
}).describe("What happens when triggered"),
|
|
4103
|
-
description:
|
|
4299
|
+
description: z29.string().optional().describe("Human-readable description")
|
|
4104
4300
|
},
|
|
4105
4301
|
{ readOnlyHint: false, destructiveHint: false },
|
|
4106
4302
|
async ({ id, layerId, interactionId, trigger, action, description }) => {
|
|
@@ -4149,8 +4345,8 @@ function register14(server, store) {
|
|
|
4149
4345
|
"atelier_list_interactions",
|
|
4150
4346
|
"List all interactions across all layers or for a specific layer",
|
|
4151
4347
|
{
|
|
4152
|
-
id:
|
|
4153
|
-
layerId:
|
|
4348
|
+
id: z29.string().describe("Document ID"),
|
|
4349
|
+
layerId: z29.string().optional().describe("Optional: filter to a specific layer")
|
|
4154
4350
|
},
|
|
4155
4351
|
{ readOnlyHint: true, destructiveHint: false },
|
|
4156
4352
|
async ({ id, layerId }) => {
|
|
@@ -4177,9 +4373,9 @@ function register14(server, store) {
|
|
|
4177
4373
|
"atelier_remove_interaction",
|
|
4178
4374
|
"Remove an interaction from a layer",
|
|
4179
4375
|
{
|
|
4180
|
-
id:
|
|
4181
|
-
layerId:
|
|
4182
|
-
interactionId:
|
|
4376
|
+
id: z29.string().describe("Document ID"),
|
|
4377
|
+
layerId: z29.string().describe("Layer ID"),
|
|
4378
|
+
interactionId: z29.string().describe("Interaction ID to remove")
|
|
4183
4379
|
},
|
|
4184
4380
|
{ readOnlyHint: false, destructiveHint: true },
|
|
4185
4381
|
async ({ id, layerId, interactionId }) => {
|
|
@@ -4201,7 +4397,7 @@ function register14(server, store) {
|
|
|
4201
4397
|
}
|
|
4202
4398
|
|
|
4203
4399
|
// ../mcp/src/tools/refs.ts
|
|
4204
|
-
import { z as
|
|
4400
|
+
import { z as z30 } from "zod";
|
|
4205
4401
|
function getDoc15(store, id) {
|
|
4206
4402
|
const doc = store.get(id);
|
|
4207
4403
|
if (!doc) return { error: `Document "${id}" not found` };
|
|
@@ -4218,9 +4414,9 @@ function register15(server, store) {
|
|
|
4218
4414
|
"atelier_set_ref",
|
|
4219
4415
|
"Set the source reference on a ref-type layer. Can point to a file path or another in-memory document ID.",
|
|
4220
4416
|
{
|
|
4221
|
-
id:
|
|
4222
|
-
layerId:
|
|
4223
|
-
src:
|
|
4417
|
+
id: z30.string().describe("Document ID"),
|
|
4418
|
+
layerId: z30.string().describe("Layer ID (must be a ref visual)"),
|
|
4419
|
+
src: z30.string().describe("File path, URL, or in-memory document ID")
|
|
4224
4420
|
},
|
|
4225
4421
|
{ readOnlyHint: false, destructiveHint: false },
|
|
4226
4422
|
async ({ id, layerId, src }) => {
|
|
@@ -4240,7 +4436,7 @@ function register15(server, store) {
|
|
|
4240
4436
|
"atelier_resolve_refs",
|
|
4241
4437
|
"Check which ref layers can be resolved (point to valid in-memory documents)",
|
|
4242
4438
|
{
|
|
4243
|
-
id:
|
|
4439
|
+
id: z30.string().describe("Document ID")
|
|
4244
4440
|
},
|
|
4245
4441
|
{ readOnlyHint: true, destructiveHint: false },
|
|
4246
4442
|
async ({ id }) => {
|
|
@@ -4267,7 +4463,7 @@ function register15(server, store) {
|
|
|
4267
4463
|
}
|
|
4268
4464
|
|
|
4269
4465
|
// ../mcp/src/tools/performance.ts
|
|
4270
|
-
import { z as
|
|
4466
|
+
import { z as z31 } from "zod";
|
|
4271
4467
|
function getDoc16(store, id) {
|
|
4272
4468
|
const doc = store.get(id);
|
|
4273
4469
|
if (!doc) return { error: `Document "${id}" not found` };
|
|
@@ -4284,9 +4480,9 @@ function register16(server, store) {
|
|
|
4284
4480
|
"atelier_profile",
|
|
4285
4481
|
"Profile frame resolution performance \u2014 returns timing, layer count, property count, and delta count for a frame",
|
|
4286
4482
|
{
|
|
4287
|
-
id:
|
|
4288
|
-
stateName:
|
|
4289
|
-
frame:
|
|
4483
|
+
id: z31.string().describe("Document ID"),
|
|
4484
|
+
stateName: z31.string().describe("State name to profile"),
|
|
4485
|
+
frame: z31.number().int().min(0).describe("Frame number to resolve")
|
|
4290
4486
|
},
|
|
4291
4487
|
{ readOnlyHint: true, destructiveHint: false },
|
|
4292
4488
|
async ({ id, stateName, frame }) => {
|
|
@@ -4332,10 +4528,10 @@ function register16(server, store) {
|
|
|
4332
4528
|
"atelier_diff_frames",
|
|
4333
4529
|
"Compare two frames and return only the properties that changed between them",
|
|
4334
4530
|
{
|
|
4335
|
-
id:
|
|
4336
|
-
stateName:
|
|
4337
|
-
frameA:
|
|
4338
|
-
frameB:
|
|
4531
|
+
id: z31.string().describe("Document ID"),
|
|
4532
|
+
stateName: z31.string().describe("State name"),
|
|
4533
|
+
frameA: z31.number().int().min(0).describe("First frame number"),
|
|
4534
|
+
frameB: z31.number().int().min(0).describe("Second frame number")
|
|
4339
4535
|
},
|
|
4340
4536
|
{ readOnlyHint: true, destructiveHint: false },
|
|
4341
4537
|
async ({ id, stateName, frameA, frameB }) => {
|
|
@@ -4374,9 +4570,9 @@ function register16(server, store) {
|
|
|
4374
4570
|
"atelier_batch_preview",
|
|
4375
4571
|
"Preview multiple frames at once \u2014 reduces round-trips for scrubbing or timeline inspection",
|
|
4376
4572
|
{
|
|
4377
|
-
id:
|
|
4378
|
-
stateName:
|
|
4379
|
-
frames:
|
|
4573
|
+
id: z31.string().describe("Document ID"),
|
|
4574
|
+
stateName: z31.string().describe("State name to preview"),
|
|
4575
|
+
frames: z31.array(z31.number().int().min(0)).min(1).max(60).describe("Array of frame numbers to resolve (max 60)")
|
|
4380
4576
|
},
|
|
4381
4577
|
{ readOnlyHint: true, destructiveHint: false },
|
|
4382
4578
|
async ({ id, stateName, frames }) => {
|
|
@@ -4412,7 +4608,7 @@ function register16(server, store) {
|
|
|
4412
4608
|
"atelier_complexity",
|
|
4413
4609
|
"Analyze document complexity \u2014 layer count, delta count, expression usage, state hierarchy depth",
|
|
4414
4610
|
{
|
|
4415
|
-
id:
|
|
4611
|
+
id: z31.string().describe("Document ID")
|
|
4416
4612
|
},
|
|
4417
4613
|
{ readOnlyHint: true, destructiveHint: false },
|
|
4418
4614
|
async ({ id }) => {
|
|
@@ -4484,9 +4680,678 @@ function register16(server, store) {
|
|
|
4484
4680
|
);
|
|
4485
4681
|
}
|
|
4486
4682
|
|
|
4683
|
+
// ../mcp/src/tools/overlays.ts
|
|
4684
|
+
import { z as z32 } from "zod";
|
|
4685
|
+
function getDoc17(store, id) {
|
|
4686
|
+
const doc = store.get(id);
|
|
4687
|
+
if (!doc) return { error: `Document "${id}" not found` };
|
|
4688
|
+
return { doc };
|
|
4689
|
+
}
|
|
4690
|
+
function ok17(data) {
|
|
4691
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...data }) }] };
|
|
4692
|
+
}
|
|
4693
|
+
function err17(message) {
|
|
4694
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
|
|
4695
|
+
}
|
|
4696
|
+
var AnchorEnum = z32.enum(["top-left", "top-right", "bottom-left", "bottom-right"]);
|
|
4697
|
+
var OverlayStyleSchema = z32.object({
|
|
4698
|
+
font_family: z32.string().optional(),
|
|
4699
|
+
font_size: z32.number().positive().optional(),
|
|
4700
|
+
font_weight: z32.union([z32.number(), z32.enum(["normal", "bold"])]).optional(),
|
|
4701
|
+
color: z32.string().optional()
|
|
4702
|
+
}).optional();
|
|
4703
|
+
var DEFAULT_OVERLAY_MARGIN = 24;
|
|
4704
|
+
var DEFAULT_TEXT_STYLE = {
|
|
4705
|
+
fontFamily: "Inter",
|
|
4706
|
+
fontSize: 24,
|
|
4707
|
+
fontWeight: 600,
|
|
4708
|
+
color: "#F5F5F7"
|
|
4709
|
+
};
|
|
4710
|
+
function anchorToFrame(anchor, canvas, margin) {
|
|
4711
|
+
switch (anchor) {
|
|
4712
|
+
case "top-left":
|
|
4713
|
+
return { frame: { x: margin, y: margin }, anchorPoint: { x: 0, y: 0 } };
|
|
4714
|
+
case "top-right":
|
|
4715
|
+
return { frame: { x: canvas.width - margin, y: margin }, anchorPoint: { x: 1, y: 0 } };
|
|
4716
|
+
case "bottom-left":
|
|
4717
|
+
return { frame: { x: margin, y: canvas.height - margin }, anchorPoint: { x: 0, y: 1 } };
|
|
4718
|
+
case "bottom-right":
|
|
4719
|
+
return {
|
|
4720
|
+
frame: { x: canvas.width - margin, y: canvas.height - margin },
|
|
4721
|
+
anchorPoint: { x: 1, y: 1 }
|
|
4722
|
+
};
|
|
4723
|
+
}
|
|
4724
|
+
}
|
|
4725
|
+
function mergeStyle(style) {
|
|
4726
|
+
return {
|
|
4727
|
+
fontFamily: style?.font_family ?? DEFAULT_TEXT_STYLE.fontFamily,
|
|
4728
|
+
fontSize: style?.font_size ?? DEFAULT_TEXT_STYLE.fontSize,
|
|
4729
|
+
fontWeight: style?.font_weight ?? DEFAULT_TEXT_STYLE.fontWeight,
|
|
4730
|
+
color: style?.color ?? DEFAULT_TEXT_STYLE.color
|
|
4731
|
+
};
|
|
4732
|
+
}
|
|
4733
|
+
function renderPageNumberFormat(format, currentIndex, totalCount) {
|
|
4734
|
+
return format.replace(
|
|
4735
|
+
/\{(current|total)(?::0(\d+)d)?\}/g,
|
|
4736
|
+
(_, name, padWidth) => {
|
|
4737
|
+
const value = name === "current" ? currentIndex : totalCount;
|
|
4738
|
+
const str = String(value);
|
|
4739
|
+
if (padWidth) {
|
|
4740
|
+
const width = parseInt(padWidth, 10);
|
|
4741
|
+
return str.padStart(width, "0");
|
|
4742
|
+
}
|
|
4743
|
+
return str;
|
|
4744
|
+
}
|
|
4745
|
+
);
|
|
4746
|
+
}
|
|
4747
|
+
function register17(server, store) {
|
|
4748
|
+
server.tool(
|
|
4749
|
+
"atelier_add_handle",
|
|
4750
|
+
"Add an anchored creator-handle TextVisual overlay layer (tag: overlay)",
|
|
4751
|
+
{
|
|
4752
|
+
id: z32.string().describe("Document ID"),
|
|
4753
|
+
text: z32.string().min(1).describe('Handle text, e.g. "@username"'),
|
|
4754
|
+
anchor: AnchorEnum.describe("Anchored corner"),
|
|
4755
|
+
margin: z32.number().nonnegative().optional().describe("Px from anchored edges (default 24)"),
|
|
4756
|
+
style: OverlayStyleSchema.describe("Text style overrides (snake_case to match recipe)"),
|
|
4757
|
+
layerId: z32.string().optional().describe("Layer ID (default: overlay-handle)")
|
|
4758
|
+
},
|
|
4759
|
+
{ readOnlyHint: false, destructiveHint: false },
|
|
4760
|
+
async ({ id, text, anchor, margin, style, layerId }) => {
|
|
4761
|
+
const result = getDoc17(store, id);
|
|
4762
|
+
if ("error" in result) return err17(result.error);
|
|
4763
|
+
const { doc } = result;
|
|
4764
|
+
const targetId = layerId ?? "overlay-handle";
|
|
4765
|
+
if (doc.layers.some((l) => l.id === targetId)) {
|
|
4766
|
+
return err17(`Layer "${targetId}" already exists in document "${id}"`);
|
|
4767
|
+
}
|
|
4768
|
+
const m = margin ?? DEFAULT_OVERLAY_MARGIN;
|
|
4769
|
+
const { frame, anchorPoint } = anchorToFrame(anchor, doc.canvas, m);
|
|
4770
|
+
const layer = {
|
|
4771
|
+
id: targetId,
|
|
4772
|
+
tags: ["overlay"],
|
|
4773
|
+
visual: { type: "text", content: text, style: mergeStyle(style) },
|
|
4774
|
+
frame,
|
|
4775
|
+
bounds: { width: 600, height: 80 },
|
|
4776
|
+
anchorPoint
|
|
4777
|
+
};
|
|
4778
|
+
doc.layers.push(layer);
|
|
4779
|
+
store.set(id, doc);
|
|
4780
|
+
return ok17({ layerId: targetId, layer });
|
|
4781
|
+
}
|
|
4782
|
+
);
|
|
4783
|
+
server.tool(
|
|
4784
|
+
"atelier_add_page_number",
|
|
4785
|
+
"Add an anchored page-number TextVisual overlay layer (tag: overlay). Substitutes {current} / {total} (and zero-pad forms {current:02d} / {total:02d}) into format.",
|
|
4786
|
+
{
|
|
4787
|
+
id: z32.string().describe("Document ID"),
|
|
4788
|
+
format: z32.string().min(1).describe('Format string, e.g. "{current}/{total}" or "{current:02d}/{total:02d}"'),
|
|
4789
|
+
anchor: AnchorEnum.describe("Anchored corner"),
|
|
4790
|
+
currentIndex: z32.number().int().nonnegative().describe("1-based current index"),
|
|
4791
|
+
totalCount: z32.number().int().positive().describe("Total count"),
|
|
4792
|
+
margin: z32.number().nonnegative().optional().describe("Px from anchored edges (default 24)"),
|
|
4793
|
+
style: OverlayStyleSchema.describe("Text style overrides (snake_case to match recipe)"),
|
|
4794
|
+
layerId: z32.string().optional().describe("Layer ID (default: overlay-page-number)")
|
|
4795
|
+
},
|
|
4796
|
+
{ readOnlyHint: false, destructiveHint: false },
|
|
4797
|
+
async ({ id, format, anchor, currentIndex, totalCount, margin, style, layerId }) => {
|
|
4798
|
+
const result = getDoc17(store, id);
|
|
4799
|
+
if ("error" in result) return err17(result.error);
|
|
4800
|
+
const { doc } = result;
|
|
4801
|
+
const targetId = layerId ?? "overlay-page-number";
|
|
4802
|
+
if (doc.layers.some((l) => l.id === targetId)) {
|
|
4803
|
+
return err17(`Layer "${targetId}" already exists in document "${id}"`);
|
|
4804
|
+
}
|
|
4805
|
+
const content = renderPageNumberFormat(format, currentIndex, totalCount);
|
|
4806
|
+
const m = margin ?? DEFAULT_OVERLAY_MARGIN;
|
|
4807
|
+
const { frame, anchorPoint } = anchorToFrame(anchor, doc.canvas, m);
|
|
4808
|
+
const layer = {
|
|
4809
|
+
id: targetId,
|
|
4810
|
+
tags: ["overlay"],
|
|
4811
|
+
visual: { type: "text", content, style: mergeStyle(style) },
|
|
4812
|
+
frame,
|
|
4813
|
+
bounds: { width: 200, height: 80 },
|
|
4814
|
+
anchorPoint
|
|
4815
|
+
};
|
|
4816
|
+
doc.layers.push(layer);
|
|
4817
|
+
store.set(id, doc);
|
|
4818
|
+
return ok17({ layerId: targetId, layer, rendered: content });
|
|
4819
|
+
}
|
|
4820
|
+
);
|
|
4821
|
+
}
|
|
4822
|
+
|
|
4823
|
+
// ../mcp/src/tools/recipes.ts
|
|
4824
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync } from "fs";
|
|
4825
|
+
import { join, resolve, isAbsolute, dirname } from "path";
|
|
4826
|
+
import { homedir } from "os";
|
|
4827
|
+
import { z as z33 } from "zod";
|
|
4828
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
4829
|
+
function getDoc18(store, id) {
|
|
4830
|
+
const doc = store.get(id);
|
|
4831
|
+
if (!doc) return { error: `Document "${id}" not found` };
|
|
4832
|
+
return { doc };
|
|
4833
|
+
}
|
|
4834
|
+
function ok18(data) {
|
|
4835
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...data }) }] };
|
|
4836
|
+
}
|
|
4837
|
+
function err18(message, extra) {
|
|
4838
|
+
return {
|
|
4839
|
+
content: [{ type: "text", text: JSON.stringify({ error: message, ...extra }) }],
|
|
4840
|
+
isError: true
|
|
4841
|
+
};
|
|
4842
|
+
}
|
|
4843
|
+
function resolveRecipePath(pathOrName, projectDir) {
|
|
4844
|
+
if (isAbsolute(pathOrName) || pathOrName.includes("/") || pathOrName.includes("\\")) {
|
|
4845
|
+
return resolve(pathOrName);
|
|
4846
|
+
}
|
|
4847
|
+
const candidates = [];
|
|
4848
|
+
const exts = [".recipe.yaml", ".recipe.json", ".yaml", ".yml", ".json"];
|
|
4849
|
+
const projectRecipesDir = join(resolve(projectDir), ".atelier", "recipes");
|
|
4850
|
+
for (const ext of exts) candidates.push(join(projectRecipesDir, `${pathOrName}${ext}`));
|
|
4851
|
+
const userRecipesDir = join(homedir(), ".atelier", "recipes");
|
|
4852
|
+
for (const ext of exts) candidates.push(join(userRecipesDir, `${pathOrName}${ext}`));
|
|
4853
|
+
for (const candidate of candidates) {
|
|
4854
|
+
if (existsSync(candidate)) return candidate;
|
|
4855
|
+
}
|
|
4856
|
+
throw new Error(
|
|
4857
|
+
`Recipe "${pathOrName}" not found. Looked in:
|
|
4858
|
+
${candidates.map((c) => ` ${c}`).join("\n")}`
|
|
4859
|
+
);
|
|
4860
|
+
}
|
|
4861
|
+
function parseRecipeText(raw, path) {
|
|
4862
|
+
if (path.endsWith(".json")) return JSON.parse(raw);
|
|
4863
|
+
return parseYaml(raw);
|
|
4864
|
+
}
|
|
4865
|
+
function loadRecipe(pathOrName, projectDir) {
|
|
4866
|
+
const path = resolveRecipePath(pathOrName, projectDir);
|
|
4867
|
+
const raw = readFileSync(path, "utf-8");
|
|
4868
|
+
const parsed = parseRecipeText(raw, path);
|
|
4869
|
+
const result = validateRecipe(parsed);
|
|
4870
|
+
if (!result.success) {
|
|
4871
|
+
const msg = result.errors.map((e) => ` ${e.path}: ${e.message}`).join("\n");
|
|
4872
|
+
throw new Error(`Invalid recipe at ${path}:
|
|
4873
|
+
${msg}`);
|
|
4874
|
+
}
|
|
4875
|
+
return { recipe: result.data, path, warnings: result.warnings ?? [] };
|
|
4876
|
+
}
|
|
4877
|
+
function recipeToYaml(recipe) {
|
|
4878
|
+
return stringifyYaml(recipe);
|
|
4879
|
+
}
|
|
4880
|
+
var DEFAULT_OVERLAY_MARGIN2 = 24;
|
|
4881
|
+
var DEFAULT_OVERLAY_TEXT_STYLE = {
|
|
4882
|
+
fontFamily: "Inter",
|
|
4883
|
+
fontSize: 24,
|
|
4884
|
+
fontWeight: 600,
|
|
4885
|
+
color: "#F5F5F7"
|
|
4886
|
+
};
|
|
4887
|
+
var HANDLE_LAYER_ID = "overlay-handle";
|
|
4888
|
+
var PAGE_NUMBER_LAYER_ID = "overlay-page-number";
|
|
4889
|
+
function applyRecipeToOverlay(doc, recipe, ctx) {
|
|
4890
|
+
const preserved = doc.layers.filter((l) => !(l.tags ?? []).includes("overlay"));
|
|
4891
|
+
const overlayLayers = [];
|
|
4892
|
+
const warnings = [];
|
|
4893
|
+
const rules = recipe.overlay_rules;
|
|
4894
|
+
if (!rules) {
|
|
4895
|
+
return { doc: { ...doc, layers: preserved }, warnings };
|
|
4896
|
+
}
|
|
4897
|
+
if (rules.handle) {
|
|
4898
|
+
overlayLayers.push(buildHandleLayer(rules.handle, doc.canvas));
|
|
4899
|
+
}
|
|
4900
|
+
if (rules.page_number) {
|
|
4901
|
+
if (ctx?.currentIndex != null && ctx?.totalCount != null) {
|
|
4902
|
+
overlayLayers.push(
|
|
4903
|
+
buildPageNumberLayer(rules.page_number, doc.canvas, ctx.currentIndex, ctx.totalCount)
|
|
4904
|
+
);
|
|
4905
|
+
} else {
|
|
4906
|
+
warnings.push(
|
|
4907
|
+
"recipe.overlay_rules.page_number is present but currentIndex/totalCount were not provided \u2014 the page_number layer was skipped."
|
|
4908
|
+
);
|
|
4909
|
+
}
|
|
4910
|
+
}
|
|
4911
|
+
return { doc: { ...doc, layers: [...preserved, ...overlayLayers] }, warnings };
|
|
4912
|
+
}
|
|
4913
|
+
function buildHandleLayer(rule, canvas) {
|
|
4914
|
+
const margin = rule.margin ?? DEFAULT_OVERLAY_MARGIN2;
|
|
4915
|
+
const { frame, anchorPoint } = anchorToFrame2(rule.anchor, canvas, margin);
|
|
4916
|
+
return {
|
|
4917
|
+
id: HANDLE_LAYER_ID,
|
|
4918
|
+
tags: ["overlay"],
|
|
4919
|
+
visual: { type: "text", content: rule.text, style: mergeOverlayStyle(rule.style) },
|
|
4920
|
+
frame,
|
|
4921
|
+
bounds: { width: 600, height: 80 },
|
|
4922
|
+
anchorPoint
|
|
4923
|
+
};
|
|
4924
|
+
}
|
|
4925
|
+
function buildPageNumberLayer(rule, canvas, currentIndex, totalCount) {
|
|
4926
|
+
const margin = rule.margin ?? DEFAULT_OVERLAY_MARGIN2;
|
|
4927
|
+
const { frame, anchorPoint } = anchorToFrame2(rule.anchor, canvas, margin);
|
|
4928
|
+
return {
|
|
4929
|
+
id: PAGE_NUMBER_LAYER_ID,
|
|
4930
|
+
tags: ["overlay"],
|
|
4931
|
+
visual: {
|
|
4932
|
+
type: "text",
|
|
4933
|
+
content: renderPageNumberFormat2(rule.format, currentIndex, totalCount),
|
|
4934
|
+
style: mergeOverlayStyle(rule.style)
|
|
4935
|
+
},
|
|
4936
|
+
frame,
|
|
4937
|
+
bounds: { width: 200, height: 80 },
|
|
4938
|
+
anchorPoint
|
|
4939
|
+
};
|
|
4940
|
+
}
|
|
4941
|
+
function anchorToFrame2(anchor, canvas, margin) {
|
|
4942
|
+
switch (anchor) {
|
|
4943
|
+
case "top-left":
|
|
4944
|
+
return { frame: { x: margin, y: margin }, anchorPoint: { x: 0, y: 0 } };
|
|
4945
|
+
case "top-right":
|
|
4946
|
+
return { frame: { x: canvas.width - margin, y: margin }, anchorPoint: { x: 1, y: 0 } };
|
|
4947
|
+
case "bottom-left":
|
|
4948
|
+
return { frame: { x: margin, y: canvas.height - margin }, anchorPoint: { x: 0, y: 1 } };
|
|
4949
|
+
case "bottom-right":
|
|
4950
|
+
return {
|
|
4951
|
+
frame: { x: canvas.width - margin, y: canvas.height - margin },
|
|
4952
|
+
anchorPoint: { x: 1, y: 1 }
|
|
4953
|
+
};
|
|
4954
|
+
}
|
|
4955
|
+
}
|
|
4956
|
+
function mergeOverlayStyle(style) {
|
|
4957
|
+
return {
|
|
4958
|
+
fontFamily: style?.font_family ?? DEFAULT_OVERLAY_TEXT_STYLE.fontFamily,
|
|
4959
|
+
fontSize: style?.font_size ?? DEFAULT_OVERLAY_TEXT_STYLE.fontSize,
|
|
4960
|
+
fontWeight: style?.font_weight ?? DEFAULT_OVERLAY_TEXT_STYLE.fontWeight,
|
|
4961
|
+
color: style?.color ?? DEFAULT_OVERLAY_TEXT_STYLE.color
|
|
4962
|
+
};
|
|
4963
|
+
}
|
|
4964
|
+
function renderPageNumberFormat2(format, currentIndex, totalCount) {
|
|
4965
|
+
return format.replace(
|
|
4966
|
+
/\{(current|total)(?::0(\d+)d)?\}/g,
|
|
4967
|
+
(_, name, padWidth) => {
|
|
4968
|
+
const value = name === "current" ? currentIndex : totalCount;
|
|
4969
|
+
const str = String(value);
|
|
4970
|
+
if (padWidth) return str.padStart(parseInt(padWidth, 10), "0");
|
|
4971
|
+
return str;
|
|
4972
|
+
}
|
|
4973
|
+
);
|
|
4974
|
+
}
|
|
4975
|
+
var RECIPE_EXTS = [".recipe.yaml", ".recipe.yml", ".recipe.json"];
|
|
4976
|
+
function isRecipeFile(name) {
|
|
4977
|
+
return RECIPE_EXTS.some((ext) => name.endsWith(ext));
|
|
4978
|
+
}
|
|
4979
|
+
function listRecipesIn(dir) {
|
|
4980
|
+
if (!existsSync(dir)) return [];
|
|
4981
|
+
let entries;
|
|
4982
|
+
try {
|
|
4983
|
+
entries = readdirSync(dir);
|
|
4984
|
+
} catch {
|
|
4985
|
+
return [];
|
|
4986
|
+
}
|
|
4987
|
+
return entries.filter(isRecipeFile).map((name) => ({ name, path: join(dir, name) })).sort((a, b) => a.name.localeCompare(b.name));
|
|
4988
|
+
}
|
|
4989
|
+
function register18(server, store) {
|
|
4990
|
+
server.tool(
|
|
4991
|
+
"atelier_recipe_list",
|
|
4992
|
+
"List discoverable Studio Recipes \u2014 project-local <projectDir>/.atelier/recipes/*.recipe.* plus ~/.atelier/recipes/*.recipe.*. Returns { name, path } entries with absolute paths.",
|
|
4993
|
+
{
|
|
4994
|
+
projectDir: z33.string().optional().describe("Project root to search (default: process.cwd())")
|
|
4995
|
+
},
|
|
4996
|
+
{ readOnlyHint: true, destructiveHint: false },
|
|
4997
|
+
async ({ projectDir }) => {
|
|
4998
|
+
const root = resolve(projectDir ?? process.cwd());
|
|
4999
|
+
const projectRecipes = listRecipesIn(join(root, ".atelier", "recipes"));
|
|
5000
|
+
const userRecipes = listRecipesIn(join(homedir(), ".atelier", "recipes"));
|
|
5001
|
+
const seen = new Set(projectRecipes.map((r) => r.name));
|
|
5002
|
+
const recipes = [...projectRecipes, ...userRecipes.filter((r) => !seen.has(r.name))];
|
|
5003
|
+
return ok18({ count: recipes.length, recipes });
|
|
5004
|
+
}
|
|
5005
|
+
);
|
|
5006
|
+
server.tool(
|
|
5007
|
+
"atelier_recipe_get",
|
|
5008
|
+
"Load a recipe and return BOTH the parsed object and its serialized YAML text (so the agent can show/explain it), plus any validation warnings.",
|
|
5009
|
+
{
|
|
5010
|
+
path: z33.string().describe("Recipe path or bare name (resolved like the CLI)"),
|
|
5011
|
+
projectDir: z33.string().optional().describe("Project root for name resolution (default: process.cwd())")
|
|
5012
|
+
},
|
|
5013
|
+
{ readOnlyHint: true, destructiveHint: false },
|
|
5014
|
+
async ({ path, projectDir }) => {
|
|
5015
|
+
const root = resolve(projectDir ?? process.cwd());
|
|
5016
|
+
try {
|
|
5017
|
+
const loaded = loadRecipe(path, root);
|
|
5018
|
+
return ok18({
|
|
5019
|
+
path: loaded.path,
|
|
5020
|
+
recipe: loaded.recipe,
|
|
5021
|
+
yaml: recipeToYaml(loaded.recipe),
|
|
5022
|
+
warnings: loaded.warnings
|
|
5023
|
+
});
|
|
5024
|
+
} catch (e) {
|
|
5025
|
+
return err18(e.message);
|
|
5026
|
+
}
|
|
5027
|
+
}
|
|
5028
|
+
);
|
|
5029
|
+
server.tool(
|
|
5030
|
+
"atelier_recipe_validate",
|
|
5031
|
+
"Validate a recipe from a path OR inline YAML text. Returns { success, warnings, errors }.",
|
|
5032
|
+
{
|
|
5033
|
+
path: z33.string().optional().describe("Recipe path or bare name to validate"),
|
|
5034
|
+
yaml: z33.string().optional().describe("Inline YAML text to validate (alternative to path)"),
|
|
5035
|
+
projectDir: z33.string().optional().describe("Project root for name resolution (default: process.cwd())")
|
|
5036
|
+
},
|
|
5037
|
+
{ readOnlyHint: true, destructiveHint: false },
|
|
5038
|
+
async ({ path, yaml, projectDir }) => {
|
|
5039
|
+
if (!path && yaml === void 0) {
|
|
5040
|
+
return err18("Provide either `path` or `yaml` to validate");
|
|
5041
|
+
}
|
|
5042
|
+
const root = resolve(projectDir ?? process.cwd());
|
|
5043
|
+
let parsed;
|
|
5044
|
+
try {
|
|
5045
|
+
if (yaml !== void 0) {
|
|
5046
|
+
parsed = parseYaml(yaml);
|
|
5047
|
+
} else {
|
|
5048
|
+
const resolved = resolveRecipePath(path, root);
|
|
5049
|
+
parsed = parseRecipeText(readFileSync(resolved, "utf-8"), resolved);
|
|
5050
|
+
}
|
|
5051
|
+
} catch (e) {
|
|
5052
|
+
return ok18({ valid: false, warnings: [], errors: [{ path: "(yaml)", message: e.message }] });
|
|
5053
|
+
}
|
|
5054
|
+
const result = validateRecipe(parsed);
|
|
5055
|
+
if (!result.success) {
|
|
5056
|
+
return ok18({ valid: false, warnings: [], errors: result.errors });
|
|
5057
|
+
}
|
|
5058
|
+
return ok18({ valid: true, warnings: result.warnings ?? [], errors: [] });
|
|
5059
|
+
}
|
|
5060
|
+
);
|
|
5061
|
+
server.tool(
|
|
5062
|
+
"atelier_recipe_save",
|
|
5063
|
+
"Write a recipe to disk. Accepts a recipe object (serialized via YAML) OR raw YAML text. Validates BEFORE writing and REFUSES to write an invalid recipe (returns an error with the validation issues). Creates parent directories as needed.",
|
|
5064
|
+
{
|
|
5065
|
+
path: z33.string().describe("Absolute or relative file path to write the recipe to"),
|
|
5066
|
+
recipe: z33.record(z33.unknown()).optional().describe("Recipe object (will be serialized to YAML)"),
|
|
5067
|
+
yaml: z33.string().optional().describe("Raw YAML text (alternative to `recipe`)")
|
|
5068
|
+
},
|
|
5069
|
+
{ readOnlyHint: false, destructiveHint: true },
|
|
5070
|
+
async ({ path, recipe, yaml }) => {
|
|
5071
|
+
if (recipe === void 0 && yaml === void 0) {
|
|
5072
|
+
return err18("Provide either `recipe` (object) or `yaml` (text) to save");
|
|
5073
|
+
}
|
|
5074
|
+
let parsed;
|
|
5075
|
+
let yamlText;
|
|
5076
|
+
if (yaml !== void 0) {
|
|
5077
|
+
yamlText = yaml;
|
|
5078
|
+
try {
|
|
5079
|
+
parsed = parseYaml(yaml);
|
|
5080
|
+
} catch (e) {
|
|
5081
|
+
return err18(`YAML parse error: ${e.message}`);
|
|
5082
|
+
}
|
|
5083
|
+
} else {
|
|
5084
|
+
parsed = recipe;
|
|
5085
|
+
yamlText = stringifyYaml(recipe);
|
|
5086
|
+
}
|
|
5087
|
+
const result = validateRecipe(parsed);
|
|
5088
|
+
if (!result.success) {
|
|
5089
|
+
return err18("Refusing to write an invalid recipe", { errors: result.errors });
|
|
5090
|
+
}
|
|
5091
|
+
const absPath = resolve(path);
|
|
5092
|
+
try {
|
|
5093
|
+
mkdirSync(dirname(absPath), { recursive: true });
|
|
5094
|
+
writeFileSync(absPath, yamlText, "utf-8");
|
|
5095
|
+
} catch (e) {
|
|
5096
|
+
return err18(`Failed to write recipe: ${e.message}`);
|
|
5097
|
+
}
|
|
5098
|
+
return ok18({ path: absPath, warnings: result.warnings ?? [] });
|
|
5099
|
+
}
|
|
5100
|
+
);
|
|
5101
|
+
server.tool(
|
|
5102
|
+
"atelier_recipe_apply",
|
|
5103
|
+
`Apply a recipe's overlay_rules to an in-store document (handle + page-number overlays). Accepts a recipe object OR a path/name. The result is stored back with source="llm". NOTE: only overlay_rules are applied here \u2014 silence-trim / transcribe / caption application remain CLI-only. page_number overlays require currentIndex + totalCount; if a recipe requests page_number without them, that layer is skipped and a warning is returned.`,
|
|
5104
|
+
{
|
|
5105
|
+
documentId: z33.string().describe("In-store document ID to apply the recipe to"),
|
|
5106
|
+
path: z33.string().optional().describe("Recipe path or bare name (alternative to `recipe`)"),
|
|
5107
|
+
recipe: z33.record(z33.unknown()).optional().describe("Recipe object (alternative to `path`)"),
|
|
5108
|
+
projectDir: z33.string().optional().describe("Project root for name resolution (default: process.cwd())"),
|
|
5109
|
+
currentIndex: z33.number().int().nonnegative().optional().describe("1-based carousel index for page_number"),
|
|
5110
|
+
totalCount: z33.number().int().positive().optional().describe("Total carousel size for page_number")
|
|
5111
|
+
},
|
|
5112
|
+
{ readOnlyHint: false, destructiveHint: false },
|
|
5113
|
+
async ({ documentId, path, recipe, projectDir, currentIndex, totalCount }) => {
|
|
5114
|
+
const result = getDoc18(store, documentId);
|
|
5115
|
+
if ("error" in result) return err18(result.error);
|
|
5116
|
+
const { doc } = result;
|
|
5117
|
+
if (path === void 0 && recipe === void 0) {
|
|
5118
|
+
return err18("Provide either `path` or `recipe` to apply");
|
|
5119
|
+
}
|
|
5120
|
+
let resolvedRecipe;
|
|
5121
|
+
if (recipe !== void 0) {
|
|
5122
|
+
const validated = validateRecipe(recipe);
|
|
5123
|
+
if (!validated.success) {
|
|
5124
|
+
return err18("Invalid recipe", { errors: validated.errors });
|
|
5125
|
+
}
|
|
5126
|
+
resolvedRecipe = validated.data;
|
|
5127
|
+
} else {
|
|
5128
|
+
const root = resolve(projectDir ?? process.cwd());
|
|
5129
|
+
try {
|
|
5130
|
+
resolvedRecipe = loadRecipe(path, root).recipe;
|
|
5131
|
+
} catch (e) {
|
|
5132
|
+
return err18(e.message);
|
|
5133
|
+
}
|
|
5134
|
+
}
|
|
5135
|
+
const { doc: updated, warnings } = applyRecipeToOverlay(doc, resolvedRecipe, {
|
|
5136
|
+
currentIndex,
|
|
5137
|
+
totalCount
|
|
5138
|
+
});
|
|
5139
|
+
store.set(documentId, updated, "llm");
|
|
5140
|
+
return ok18({
|
|
5141
|
+
documentId,
|
|
5142
|
+
warnings,
|
|
5143
|
+
layerCount: updated.layers.length,
|
|
5144
|
+
document: updated
|
|
5145
|
+
});
|
|
5146
|
+
}
|
|
5147
|
+
);
|
|
5148
|
+
}
|
|
5149
|
+
|
|
5150
|
+
// ../mcp/src/tools/import-images.ts
|
|
5151
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, statSync } from "fs";
|
|
5152
|
+
import { resolve as resolve2, join as join2, basename, extname } from "path";
|
|
5153
|
+
import { z as z34 } from "zod";
|
|
5154
|
+
function getDoc19(store, id) {
|
|
5155
|
+
const doc = store.get(id);
|
|
5156
|
+
if (!doc) return { error: `Document "${id}" not found` };
|
|
5157
|
+
return { doc };
|
|
5158
|
+
}
|
|
5159
|
+
function ok19(data) {
|
|
5160
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...data }) }] };
|
|
5161
|
+
}
|
|
5162
|
+
function err19(message) {
|
|
5163
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
|
|
5164
|
+
}
|
|
5165
|
+
var IMAGE_EXTS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".webp", ".gif", ".svg"]);
|
|
5166
|
+
function isImageFile(name) {
|
|
5167
|
+
return IMAGE_EXTS.has(extname(name).toLowerCase());
|
|
5168
|
+
}
|
|
5169
|
+
function matchesGlob(name, glob) {
|
|
5170
|
+
if (!glob) return true;
|
|
5171
|
+
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
5172
|
+
return new RegExp(`^${escaped}$`, "i").test(name);
|
|
5173
|
+
}
|
|
5174
|
+
function deriveAssetId(filename, used) {
|
|
5175
|
+
const stem = basename(filename, extname(filename));
|
|
5176
|
+
const base = stem.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "image";
|
|
5177
|
+
let candidate = base;
|
|
5178
|
+
let n = 2;
|
|
5179
|
+
while (used.has(candidate)) {
|
|
5180
|
+
candidate = `${base}-${n}`;
|
|
5181
|
+
n++;
|
|
5182
|
+
}
|
|
5183
|
+
used.add(candidate);
|
|
5184
|
+
return candidate;
|
|
5185
|
+
}
|
|
5186
|
+
function nextImageLayerId(existing, seq) {
|
|
5187
|
+
let candidate = `image-${seq}`;
|
|
5188
|
+
let n = seq;
|
|
5189
|
+
while (existing.has(candidate)) {
|
|
5190
|
+
n++;
|
|
5191
|
+
candidate = `image-${n}`;
|
|
5192
|
+
}
|
|
5193
|
+
existing.add(candidate);
|
|
5194
|
+
return candidate;
|
|
5195
|
+
}
|
|
5196
|
+
function register19(server, store) {
|
|
5197
|
+
server.tool(
|
|
5198
|
+
"atelier_import_images",
|
|
5199
|
+
`Import every image in a folder into a document. Registers each as an asset and, unless asLayers is false, creates one fit-to-canvas ImageVisual layer per image (centered). Extensions: .png .jpg .jpeg .webp .gif .svg. Optional glob filters by filename (e.g. "*.png"); results are sorted by name. If the folder has exactly one image AND the doc's layer[0] is the welcome "background" placeholder, that image replaces the placeholder in place. Asset src is the absolute file path. Layer bounds default to a centered min(canvas.w, canvas.h) square because natural image dimensions are not measured (no image-dimension dependency).`,
|
|
5200
|
+
{
|
|
5201
|
+
documentId: z34.string().describe("In-store document ID to import into"),
|
|
5202
|
+
dir: z34.string().describe("Directory containing the images"),
|
|
5203
|
+
glob: z34.string().optional().describe('Optional filename glob, e.g. "*.png" (default: all images)'),
|
|
5204
|
+
asLayers: z34.boolean().optional().describe("Create layers per image (default: true). false = register assets only.")
|
|
5205
|
+
},
|
|
5206
|
+
{ readOnlyHint: false, destructiveHint: false },
|
|
5207
|
+
async ({ documentId, dir, glob, asLayers }) => {
|
|
5208
|
+
const result = getDoc19(store, documentId);
|
|
5209
|
+
if ("error" in result) return err19(result.error);
|
|
5210
|
+
const { doc } = result;
|
|
5211
|
+
const absDir = resolve2(dir);
|
|
5212
|
+
if (!existsSync2(absDir)) return err19(`Directory "${absDir}" does not exist`);
|
|
5213
|
+
if (!statSync(absDir).isDirectory()) return err19(`Path "${absDir}" is not a directory`);
|
|
5214
|
+
const files = readdirSync2(absDir).filter((name) => isImageFile(name) && matchesGlob(name, glob)).sort((a, b) => a.localeCompare(b));
|
|
5215
|
+
if (files.length === 0) {
|
|
5216
|
+
return err19(`No image files matched in "${absDir}"${glob ? ` (glob: ${glob})` : ""}`);
|
|
5217
|
+
}
|
|
5218
|
+
const makeLayers = asLayers !== false;
|
|
5219
|
+
const canvas = doc.canvas;
|
|
5220
|
+
const square = Math.min(canvas.width, canvas.height);
|
|
5221
|
+
const allowBackgroundSwap = files.length === 1 && doc.layers[0]?.id === "background";
|
|
5222
|
+
const newAssets = { ...doc.assets ?? {} };
|
|
5223
|
+
const newLayers = [...doc.layers];
|
|
5224
|
+
const usedAssetIds = new Set(Object.keys(newAssets));
|
|
5225
|
+
const existingLayerIds = new Set(newLayers.map((l) => l.id));
|
|
5226
|
+
const assetIds = [];
|
|
5227
|
+
const layerIds = [];
|
|
5228
|
+
let layerSeq = newLayers.length + 1;
|
|
5229
|
+
for (const file of files) {
|
|
5230
|
+
const absPath = join2(absDir, file);
|
|
5231
|
+
const assetId = deriveAssetId(file, usedAssetIds);
|
|
5232
|
+
newAssets[assetId] = { type: "image", src: absPath, description: file };
|
|
5233
|
+
assetIds.push(assetId);
|
|
5234
|
+
if (!makeLayers) continue;
|
|
5235
|
+
const layerId = nextImageLayerId(existingLayerIds, layerSeq);
|
|
5236
|
+
layerSeq++;
|
|
5237
|
+
const layer = {
|
|
5238
|
+
id: layerId,
|
|
5239
|
+
visual: { type: "image", assetId, src: absPath },
|
|
5240
|
+
frame: { x: canvas.width / 2, y: canvas.height / 2 },
|
|
5241
|
+
bounds: { width: square, height: square },
|
|
5242
|
+
anchorPoint: { x: 0.5, y: 0.5 },
|
|
5243
|
+
opacity: 1
|
|
5244
|
+
};
|
|
5245
|
+
if (allowBackgroundSwap) {
|
|
5246
|
+
newLayers[0] = layer;
|
|
5247
|
+
} else {
|
|
5248
|
+
newLayers.unshift(layer);
|
|
5249
|
+
}
|
|
5250
|
+
layerIds.push(layerId);
|
|
5251
|
+
}
|
|
5252
|
+
const newDoc = { ...doc, assets: newAssets, layers: newLayers };
|
|
5253
|
+
store.set(documentId, newDoc, "llm");
|
|
5254
|
+
return ok19({
|
|
5255
|
+
imported: files.length,
|
|
5256
|
+
assetIds,
|
|
5257
|
+
layerIds,
|
|
5258
|
+
asLayers: makeLayers,
|
|
5259
|
+
note: "Layer bounds default to a centered square (min canvas dimension) \u2014 natural image dimensions are not measured."
|
|
5260
|
+
});
|
|
5261
|
+
}
|
|
5262
|
+
);
|
|
5263
|
+
}
|
|
5264
|
+
|
|
5265
|
+
// ../mcp/src/bridge-protocol.ts
|
|
5266
|
+
var BRIDGE_PROTOCOL_VERSION = 1;
|
|
5267
|
+
function isBridgeEnvelope(value) {
|
|
5268
|
+
if (!value || typeof value !== "object") return false;
|
|
5269
|
+
const t = value.type;
|
|
5270
|
+
return t === "hello" || t === "doc:load" || t === "doc:loaded" || t === "doc:patch" || t === "llm:mutation" || t === "error";
|
|
5271
|
+
}
|
|
5272
|
+
|
|
5273
|
+
// ../mcp/src/transports/ws-transport.ts
|
|
5274
|
+
var WebSocketServerTransport = class {
|
|
5275
|
+
constructor(ws) {
|
|
5276
|
+
this.ws = ws;
|
|
5277
|
+
}
|
|
5278
|
+
onclose;
|
|
5279
|
+
onerror;
|
|
5280
|
+
onmessage;
|
|
5281
|
+
sessionId;
|
|
5282
|
+
started = false;
|
|
5283
|
+
closed = false;
|
|
5284
|
+
async start() {
|
|
5285
|
+
if (this.started) {
|
|
5286
|
+
throw new Error(
|
|
5287
|
+
"WebSocketServerTransport already started \u2014 the MCP SDK calls start() automatically; do not call it again"
|
|
5288
|
+
);
|
|
5289
|
+
}
|
|
5290
|
+
this.started = true;
|
|
5291
|
+
this.ws.on("message", (data) => {
|
|
5292
|
+
let text;
|
|
5293
|
+
try {
|
|
5294
|
+
if (typeof data === "string") {
|
|
5295
|
+
text = data;
|
|
5296
|
+
} else if (data instanceof Buffer) {
|
|
5297
|
+
text = data.toString("utf-8");
|
|
5298
|
+
} else if (Array.isArray(data)) {
|
|
5299
|
+
text = Buffer.concat(data).toString("utf-8");
|
|
5300
|
+
} else if (data instanceof ArrayBuffer) {
|
|
5301
|
+
text = Buffer.from(data).toString("utf-8");
|
|
5302
|
+
} else {
|
|
5303
|
+
text = String(data);
|
|
5304
|
+
}
|
|
5305
|
+
} catch (err20) {
|
|
5306
|
+
this.onerror?.(err20 instanceof Error ? err20 : new Error(String(err20)));
|
|
5307
|
+
return;
|
|
5308
|
+
}
|
|
5309
|
+
let parsed;
|
|
5310
|
+
try {
|
|
5311
|
+
parsed = JSON.parse(text);
|
|
5312
|
+
} catch (err20) {
|
|
5313
|
+
this.onerror?.(
|
|
5314
|
+
new Error(
|
|
5315
|
+
`WebSocketServerTransport: failed to parse JSON-RPC message: ${err20 instanceof Error ? err20.message : String(err20)}`
|
|
5316
|
+
)
|
|
5317
|
+
);
|
|
5318
|
+
return;
|
|
5319
|
+
}
|
|
5320
|
+
this.onmessage?.(parsed);
|
|
5321
|
+
});
|
|
5322
|
+
this.ws.on("close", () => {
|
|
5323
|
+
if (this.closed) return;
|
|
5324
|
+
this.closed = true;
|
|
5325
|
+
this.onclose?.();
|
|
5326
|
+
});
|
|
5327
|
+
this.ws.on("error", (err20) => {
|
|
5328
|
+
this.onerror?.(err20);
|
|
5329
|
+
});
|
|
5330
|
+
}
|
|
5331
|
+
async send(message) {
|
|
5332
|
+
if (this.closed) {
|
|
5333
|
+
throw new Error("WebSocketServerTransport: cannot send after close");
|
|
5334
|
+
}
|
|
5335
|
+
if (this.ws.readyState !== 1) {
|
|
5336
|
+
throw new Error(
|
|
5337
|
+
`WebSocketServerTransport: WebSocket is not OPEN (readyState=${this.ws.readyState})`
|
|
5338
|
+
);
|
|
5339
|
+
}
|
|
5340
|
+
this.ws.send(JSON.stringify(message));
|
|
5341
|
+
}
|
|
5342
|
+
async close() {
|
|
5343
|
+
if (this.closed) return;
|
|
5344
|
+
this.closed = true;
|
|
5345
|
+
try {
|
|
5346
|
+
this.ws.close();
|
|
5347
|
+
} catch {
|
|
5348
|
+
}
|
|
5349
|
+
this.onclose?.();
|
|
5350
|
+
}
|
|
5351
|
+
};
|
|
5352
|
+
|
|
4487
5353
|
// ../mcp/src/index.ts
|
|
4488
|
-
function createServer() {
|
|
4489
|
-
const store = new DocumentStore();
|
|
5354
|
+
function createServer(store = new DocumentStore()) {
|
|
4490
5355
|
const server = new McpServer(
|
|
4491
5356
|
{ name: "atelier", version: "0.1.0" },
|
|
4492
5357
|
{ capabilities: { tools: {} } }
|
|
@@ -4507,6 +5372,9 @@ function createServer() {
|
|
|
4507
5372
|
register14(server, store);
|
|
4508
5373
|
register15(server, store);
|
|
4509
5374
|
register16(server, store);
|
|
5375
|
+
register17(server, store);
|
|
5376
|
+
register18(server, store);
|
|
5377
|
+
register19(server, store);
|
|
4510
5378
|
return { server, store };
|
|
4511
5379
|
}
|
|
4512
5380
|
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"));
|
|
@@ -4519,18 +5387,24 @@ if (isMain) {
|
|
|
4519
5387
|
});
|
|
4520
5388
|
}
|
|
4521
5389
|
export {
|
|
5390
|
+
BRIDGE_PROTOCOL_VERSION,
|
|
4522
5391
|
DocumentStore,
|
|
5392
|
+
WebSocketServerTransport,
|
|
4523
5393
|
createServer,
|
|
5394
|
+
isBridgeEnvelope,
|
|
4524
5395
|
register12 as registerAssetTools,
|
|
4525
5396
|
register5 as registerDeltaTools,
|
|
4526
5397
|
register as registerDocumentTools,
|
|
4527
5398
|
register11 as registerExportTools,
|
|
5399
|
+
register19 as registerImportImageTools,
|
|
4528
5400
|
register14 as registerInteractionTools,
|
|
4529
5401
|
register9 as registerLayerEffectTools,
|
|
4530
5402
|
register2 as registerLayerTools,
|
|
5403
|
+
register17 as registerOverlayTools,
|
|
4531
5404
|
register16 as registerPerformanceTools,
|
|
4532
5405
|
register6 as registerPresetTools,
|
|
4533
5406
|
register7 as registerPreviewTools,
|
|
5407
|
+
register18 as registerRecipeTools,
|
|
4534
5408
|
register15 as registerRefTools,
|
|
4535
5409
|
register3 as registerShapeTools,
|
|
4536
5410
|
register10 as registerStateConfigTools,
|