@a-company/atelier 0.29.0 → 0.37.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.
Files changed (76) hide show
  1. package/dist/chunk-5QQESXI6.js +4432 -0
  2. package/dist/chunk-5QQESXI6.js.map +1 -0
  3. package/dist/cli.cjs +2391 -530
  4. package/dist/cli.cjs.map +1 -1
  5. package/dist/cli.js +301 -429
  6. package/dist/cli.js.map +1 -1
  7. package/dist/index.cjs +2233 -38
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.cts +584 -2
  10. package/dist/index.d.ts +584 -2
  11. package/dist/index.js +111 -3
  12. package/dist/mcp.cjs +1215 -365
  13. package/dist/mcp.cjs.map +1 -1
  14. package/dist/mcp.js +1209 -365
  15. package/dist/mcp.js.map +1 -1
  16. package/package.json +20 -9
  17. package/src/web/inline-app.ts +867 -0
  18. package/src/web/tsconfig.json +9 -0
  19. package/templates/welcome.atelier +67 -0
  20. package/university/content/notes/N-atel-001-first-render.md +114 -0
  21. package/university/content/notes/N-atel-001-install-and-launch.md +84 -0
  22. package/university/content/notes/N-atel-001-what-is-atelier.md +51 -0
  23. package/university/content/notes/N-atel-101-easings.md +97 -0
  24. package/university/content/notes/N-atel-101-layers.md +106 -0
  25. package/university/content/notes/N-atel-101-states-and-deltas.md +94 -0
  26. package/university/content/notes/N-atel-101-the-atelier-format.md +72 -0
  27. package/university/content/notes/N-atel-201-authoring-tools.md +141 -0
  28. package/university/content/notes/N-atel-201-mcp-overview.md +86 -0
  29. package/university/content/notes/N-atel-201-patterns.md +108 -0
  30. package/university/content/notes/N-atel-201-visual-and-effects.md +125 -0
  31. package/university/content/notes/N-atel-301-composition-and-overlays.md +141 -0
  32. package/university/content/notes/N-atel-301-effects.md +136 -0
  33. package/university/content/notes/N-atel-301-images-and-video.md +126 -0
  34. package/university/content/notes/N-atel-301-shapes-and-text.md +118 -0
  35. package/university/content/notes/N-atel-401-hierarchical-states.md +71 -0
  36. package/university/content/notes/N-atel-401-motion-deep-dive.md +106 -0
  37. package/university/content/notes/N-atel-401-presets-and-templates.md +98 -0
  38. package/university/content/notes/N-atel-401-transitions.md +94 -0
  39. package/university/content/notes/N-atel-501-detected-vs-user-edited.md +76 -0
  40. package/university/content/notes/N-atel-501-layer-tag-isolation.md +62 -0
  41. package/university/content/notes/N-atel-501-silence-trim.md +98 -0
  42. package/university/content/notes/N-atel-501-transcribe-and-captions.md +98 -0
  43. package/university/content/notes/N-atel-601-carousel.md +71 -0
  44. package/university/content/notes/N-atel-601-overlay-rules.md +96 -0
  45. package/university/content/notes/N-atel-601-recipe-tools-and-apply.md +84 -0
  46. package/university/content/notes/N-atel-601-studio-recipe.md +103 -0
  47. package/university/content/notes/N-atel-701-choosing-output.md +68 -0
  48. package/university/content/notes/N-atel-701-png-and-frames.md +84 -0
  49. package/university/content/notes/N-atel-701-vector.md +85 -0
  50. package/university/content/notes/N-atel-701-video.md +88 -0
  51. package/university/content/notes/N-atel-801-editing-surface.md +69 -0
  52. package/university/content/notes/N-atel-801-live-bridge.md +84 -0
  53. package/university/content/notes/N-atel-801-studio-app.md +72 -0
  54. package/university/content/notes/N-atel-801-symbiotic-loop.md +56 -0
  55. package/university/content/paths/LP-atel-001.yaml +21 -0
  56. package/university/content/paths/LP-atel-101.yaml +22 -0
  57. package/university/content/paths/LP-atel-201.yaml +23 -0
  58. package/university/content/paths/LP-atel-301.yaml +22 -0
  59. package/university/content/paths/LP-atel-401.yaml +22 -0
  60. package/university/content/paths/LP-atel-501.yaml +22 -0
  61. package/university/content/paths/LP-atel-601.yaml +22 -0
  62. package/university/content/paths/LP-atel-701.yaml +22 -0
  63. package/university/content/paths/LP-atel-801.yaml +22 -0
  64. package/university/content/quizzes/Q-atel-001-orientation.yaml +66 -0
  65. package/university/content/quizzes/Q-atel-101-document-model.yaml +66 -0
  66. package/university/content/quizzes/Q-atel-201-mcp-authoring.yaml +66 -0
  67. package/university/content/quizzes/Q-atel-301-visual-system.yaml +66 -0
  68. package/university/content/quizzes/Q-atel-401-state-machines.yaml +66 -0
  69. package/university/content/quizzes/Q-atel-501-video-pipeline.yaml +66 -0
  70. package/university/content/quizzes/Q-atel-601-recipes.yaml +66 -0
  71. package/university/content/quizzes/Q-atel-701-export.yaml +66 -0
  72. package/university/content/quizzes/Q-atel-801-studio-loop.yaml +66 -0
  73. package/university/index.yaml +720 -0
  74. package/university/pack.yaml +21 -0
  75. package/dist/chunk-JV7RGETS.js +0 -2292
  76. package/dist/chunk-JV7RGETS.js.map +0 -1
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
- return this.docs.delete(id);
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 z15 } from "zod";
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+)?%$/, {
@@ -492,6 +527,121 @@ var AtelierDocumentSchema = z14.object({
492
527
  layers: z14.array(LayerSchema),
493
528
  states: z14.record(z14.string(), StateSchema)
494
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
+ ];
495
645
  function formatErrors(error) {
496
646
  return error.issues.map((issue) => ({
497
647
  path: issue.path.join(".") || "(root)",
@@ -505,14 +655,30 @@ function validateDocument(input) {
505
655
  }
506
656
  return { success: false, errors: formatErrors(result.error) };
507
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
+ }
508
674
  function parseAtelier(yamlString) {
509
675
  let parsed;
510
676
  try {
511
677
  parsed = yamlParse(yamlString);
512
- } catch (err17) {
678
+ } catch (err20) {
513
679
  return {
514
680
  success: false,
515
- errors: [{ path: "(yaml)", message: `YAML parse error: ${err17.message}` }]
681
+ errors: [{ path: "(yaml)", message: `YAML parse error: ${err20.message}` }]
516
682
  };
517
683
  }
518
684
  return validateDocument(parsed);
@@ -538,13 +704,13 @@ function register(server, store) {
538
704
  "atelier_create",
539
705
  "Create a new Atelier animation document with canvas settings",
540
706
  {
541
- name: z15.string().describe("Animation name"),
542
- width: z15.number().positive().describe("Canvas width in pixels"),
543
- height: z15.number().positive().describe("Canvas height in pixels"),
544
- fps: z15.number().positive().int().describe("Frames per second"),
545
- background: z15.string().optional().describe("Background color (hex string)"),
546
- description: z15.string().optional().describe("Animation description"),
547
- tags: z15.array(z15.string()).optional().describe("Tags for categorization")
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")
548
714
  },
549
715
  { readOnlyHint: false, destructiveHint: false },
550
716
  async ({ name, width, height, fps, background, description, tags }) => {
@@ -569,7 +735,7 @@ function register(server, store) {
569
735
  "atelier_info",
570
736
  "Get summary information about an Atelier document",
571
737
  {
572
- id: z15.string().describe("Document ID")
738
+ id: z16.string().describe("Document ID")
573
739
  },
574
740
  { readOnlyHint: true, destructiveHint: false },
575
741
  async ({ id }) => {
@@ -597,8 +763,8 @@ function register(server, store) {
597
763
  "atelier_load",
598
764
  "Load an Atelier document from a YAML string",
599
765
  {
600
- id: z15.string().optional().describe("Custom document ID (auto-generated if omitted)"),
601
- yaml: z15.string().describe("YAML string representing an Atelier document")
766
+ id: z16.string().optional().describe("Custom document ID (auto-generated if omitted)"),
767
+ yaml: z16.string().describe("YAML string representing an Atelier document")
602
768
  },
603
769
  { readOnlyHint: false, destructiveHint: false },
604
770
  async ({ id, yaml }) => {
@@ -614,7 +780,7 @@ function register(server, store) {
614
780
  "atelier_export",
615
781
  "Export an Atelier document as a YAML string",
616
782
  {
617
- id: z15.string().describe("Document ID")
783
+ id: z16.string().describe("Document ID")
618
784
  },
619
785
  { readOnlyHint: true, destructiveHint: false },
620
786
  async ({ id }) => {
@@ -637,7 +803,7 @@ function register(server, store) {
637
803
  }
638
804
 
639
805
  // ../mcp/src/tools/layers.ts
640
- import { z as z16 } from "zod";
806
+ import { z as z17 } from "zod";
641
807
  function getDoc2(store, id) {
642
808
  const doc = store.get(id);
643
809
  if (!doc) return { error: `Document "${id}" not found` };
@@ -649,69 +815,69 @@ function ok2(data) {
649
815
  function err2(message) {
650
816
  return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
651
817
  }
652
- var VisualInputSchema = z16.object({
653
- type: z16.enum(["shape", "text", "image", "group", "ref"]).describe("Visual type"),
818
+ var VisualInputSchema = z17.object({
819
+ type: z17.enum(["shape", "text", "image", "group", "ref"]).describe("Visual type"),
654
820
  // shape visual fields
655
- shape: z16.object({
656
- type: z16.enum(["rect", "ellipse", "path"]),
657
- cornerRadius: z16.union([z16.number(), z16.tuple([z16.number(), z16.number(), z16.number(), z16.number()])]).optional(),
658
- points: z16.array(z16.object({
659
- x: z16.number(),
660
- y: z16.number(),
661
- in: z16.object({ x: z16.number(), y: z16.number() }).optional(),
662
- out: z16.object({ x: z16.number(), y: z16.number() }).optional()
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()
663
829
  })).optional(),
664
- closed: z16.boolean().optional()
830
+ closed: z17.boolean().optional()
665
831
  }).optional(),
666
- fill: z16.record(z16.unknown()).optional(),
667
- stroke: z16.record(z16.unknown()).optional(),
832
+ fill: z17.record(z17.unknown()).optional(),
833
+ stroke: z17.record(z17.unknown()).optional(),
668
834
  // text visual fields
669
- content: z16.string().optional(),
670
- style: z16.record(z16.unknown()).optional(),
835
+ content: z17.string().optional(),
836
+ style: z17.record(z17.unknown()).optional(),
671
837
  // image visual fields
672
- assetId: z16.string().optional(),
673
- sourceRect: z16.object({
674
- x: z16.number(),
675
- y: z16.number(),
676
- width: z16.number().positive(),
677
- height: z16.number().positive()
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()
678
844
  }).optional(),
679
- spritesheet: z16.object({
680
- columns: z16.number().int().positive(),
681
- rows: z16.number().int().positive(),
682
- frameCount: z16.number().int().positive().optional(),
683
- frameWidth: z16.number().positive(),
684
- frameHeight: z16.number().positive()
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()
685
851
  }).optional(),
686
- frameIndex: z16.number().int().min(0).optional(),
852
+ frameIndex: z17.number().int().min(0).optional(),
687
853
  // ref visual fields
688
- src: z16.string().optional(),
689
- state: z16.string().optional(),
690
- frame: z16.number().int().min(0).optional()
854
+ src: z17.string().optional(),
855
+ state: z17.string().optional(),
856
+ frame: z17.number().int().min(0).optional()
691
857
  }).describe("Visual content definition");
692
858
  function register2(server, store) {
693
859
  server.tool(
694
860
  "atelier_add_layer",
695
861
  "Add a new layer to an Atelier document",
696
862
  {
697
- id: z16.string().describe("Document ID"),
698
- layerId: z16.string().describe("Unique layer ID"),
863
+ id: z17.string().describe("Document ID"),
864
+ layerId: z17.string().describe("Unique layer ID"),
699
865
  visual: VisualInputSchema.describe("Visual content definition"),
700
- x: z16.union([z16.number(), z16.string()]).describe("X position (pixels or percentage)"),
701
- y: z16.union([z16.number(), z16.string()]).describe("Y position (pixels or percentage)"),
702
- width: z16.union([z16.number(), z16.string()]).describe("Width (pixels or percentage)"),
703
- height: z16.union([z16.number(), z16.string()]).describe("Height (pixels or percentage)"),
704
- description: z16.string().optional().describe("Layer description"),
705
- tags: z16.array(z16.string()).optional().describe("Tags"),
706
- opacity: z16.number().min(0).max(1).optional().describe("Opacity 0-1"),
707
- rotation: z16.number().optional().describe("Rotation in degrees"),
708
- parentId: z16.string().optional().describe("Parent layer ID for transform inheritance"),
709
- anchorPoint: z16.object({ x: z16.number(), y: z16.number() }).optional().describe("Anchor point (0-1 normalized)"),
710
- scale: z16.object({ x: z16.number(), y: z16.number() }).optional().describe("Scale factors"),
711
- visible: z16.boolean().optional().describe("Whether layer is visible"),
712
- tint: z16.object({
713
- color: z16.string().describe("Tint color (hex string)"),
714
- amount: z16.number().min(0).max(1).describe("Tint amount (0 = none, 1 = full)")
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)")
715
881
  }).optional().describe("Color tint overlay")
716
882
  },
717
883
  { readOnlyHint: false, destructiveHint: false },
@@ -748,20 +914,20 @@ function register2(server, store) {
748
914
  "atelier_edit_layer",
749
915
  "Edit properties of an existing layer",
750
916
  {
751
- id: z16.string().describe("Document ID"),
752
- layerId: z16.string().describe("Layer ID to edit"),
753
- x: z16.union([z16.number(), z16.string()]).optional().describe("New X position"),
754
- y: z16.union([z16.number(), z16.string()]).optional().describe("New Y position"),
755
- width: z16.union([z16.number(), z16.string()]).optional().describe("New width"),
756
- height: z16.union([z16.number(), z16.string()]).optional().describe("New height"),
757
- description: z16.string().optional().describe("New description"),
758
- tags: z16.array(z16.string()).optional().describe("New tags"),
759
- opacity: z16.number().min(0).max(1).optional().describe("New opacity"),
760
- rotation: z16.number().optional().describe("New rotation"),
761
- parentId: z16.string().nullable().optional().describe("New parent layer ID (null to clear)"),
762
- anchorPoint: z16.object({ x: z16.number(), y: z16.number() }).optional().describe("New anchor point"),
763
- scale: z16.object({ x: z16.number(), y: z16.number() }).optional().describe("New scale"),
764
- visible: z16.boolean().optional().describe("New visibility")
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")
765
931
  },
766
932
  { readOnlyHint: false, destructiveHint: false },
767
933
  async ({ id, layerId, x, y, width, height, description, tags, opacity, rotation, parentId, anchorPoint, scale, visible }) => {
@@ -796,8 +962,8 @@ function register2(server, store) {
796
962
  "atelier_remove_layer",
797
963
  "Remove a layer from an Atelier document",
798
964
  {
799
- id: z16.string().describe("Document ID"),
800
- layerId: z16.string().describe("Layer ID to remove")
965
+ id: z17.string().describe("Document ID"),
966
+ layerId: z17.string().describe("Layer ID to remove")
801
967
  },
802
968
  { readOnlyHint: false, destructiveHint: true },
803
969
  async ({ id, layerId }) => {
@@ -825,7 +991,7 @@ function register2(server, store) {
825
991
  "atelier_list_layers",
826
992
  "List all layers in an Atelier document",
827
993
  {
828
- id: z16.string().describe("Document ID")
994
+ id: z17.string().describe("Document ID")
829
995
  },
830
996
  { readOnlyHint: true, destructiveHint: false },
831
997
  async ({ id }) => {
@@ -852,9 +1018,9 @@ function register2(server, store) {
852
1018
  "atelier_reorder",
853
1019
  "Move a layer to a new position in the layer stack",
854
1020
  {
855
- id: z16.string().describe("Document ID"),
856
- layerId: z16.string().describe("Layer ID to move"),
857
- position: z16.number().int().min(0).describe("Target position index (0-based)")
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)")
858
1024
  },
859
1025
  { readOnlyHint: false, destructiveHint: false },
860
1026
  async ({ id, layerId, position }) => {
@@ -872,7 +1038,7 @@ function register2(server, store) {
872
1038
  }
873
1039
 
874
1040
  // ../mcp/src/tools/shapes.ts
875
- import { z as z17 } from "zod";
1041
+ import { z as z18 } from "zod";
876
1042
  function getDoc3(store, id) {
877
1043
  const doc = store.get(id);
878
1044
  if (!doc) return { error: `Document "${id}" not found` };
@@ -889,21 +1055,21 @@ function register3(server, store) {
889
1055
  "atelier_set_shape",
890
1056
  "Set the shape on a shape-type layer",
891
1057
  {
892
- id: z17.string().describe("Document ID"),
893
- layerId: z17.string().describe("Layer ID (must be a shape visual)"),
894
- shape: z17.object({
895
- type: z17.enum(["rect", "ellipse", "path"]).describe("Shape type"),
896
- cornerRadius: z17.union([
897
- z17.number(),
898
- z17.tuple([z17.number(), z17.number(), z17.number(), z17.number()])
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()])
899
1065
  ]).optional().describe("Corner radius (rect only)"),
900
- points: z17.array(z17.object({
901
- x: z17.number(),
902
- y: z17.number(),
903
- in: z17.object({ x: z17.number(), y: z17.number() }).optional(),
904
- out: z17.object({ x: z17.number(), y: z17.number() }).optional()
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()
905
1071
  })).optional().describe("Path points (path only)"),
906
- closed: z17.boolean().optional().describe("Whether path is closed (path only)")
1072
+ closed: z18.boolean().optional().describe("Whether path is closed (path only)")
907
1073
  }).describe("Shape definition")
908
1074
  },
909
1075
  { readOnlyHint: false, destructiveHint: false },
@@ -922,20 +1088,20 @@ function register3(server, store) {
922
1088
  "atelier_set_fill",
923
1089
  "Set the fill on a shape-type layer",
924
1090
  {
925
- id: z17.string().describe("Document ID"),
926
- layerId: z17.string().describe("Layer ID (must be a shape visual)"),
927
- fill: z17.object({
928
- type: z17.enum(["solid", "linear-gradient", "radial-gradient"]).describe("Fill type"),
929
- color: z17.unknown().optional().describe("Color for solid fill (hex string or RGBA/HSLA object)"),
930
- angle: z17.number().optional().describe("Angle in degrees (linear-gradient only)"),
931
- center: z17.object({
932
- x: z17.union([z17.number(), z17.string()]),
933
- y: z17.union([z17.number(), z17.string()])
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()])
934
1100
  }).optional().describe("Center point (radial-gradient only)"),
935
- radius: z17.union([z17.number(), z17.string()]).optional().describe("Radius (radial-gradient only)"),
936
- stops: z17.array(z17.object({
937
- offset: z17.number().min(0).max(1),
938
- color: z17.unknown()
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()
939
1105
  })).optional().describe("Gradient stops")
940
1106
  }).describe("Fill definition")
941
1107
  },
@@ -955,14 +1121,14 @@ function register3(server, store) {
955
1121
  "atelier_set_stroke",
956
1122
  "Set the stroke on a shape-type layer",
957
1123
  {
958
- id: z17.string().describe("Document ID"),
959
- layerId: z17.string().describe("Layer ID (must be a shape visual)"),
960
- stroke: z17.object({
961
- color: z17.unknown().describe("Stroke color (hex string or RGBA/HSLA object)"),
962
- width: z17.number().positive().describe("Stroke width in pixels"),
963
- dash: z17.array(z17.number()).optional().describe("Dash pattern"),
964
- lineCap: z17.enum(["butt", "round", "square"]).optional().describe("Line cap style"),
965
- lineJoin: z17.enum(["miter", "round", "bevel"]).optional().describe("Line join style")
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")
966
1132
  }).describe("Stroke definition")
967
1133
  },
968
1134
  { readOnlyHint: false, destructiveHint: false },
@@ -980,7 +1146,7 @@ function register3(server, store) {
980
1146
  }
981
1147
 
982
1148
  // ../mcp/src/tools/states.ts
983
- import { z as z18 } from "zod";
1149
+ import { z as z19 } from "zod";
984
1150
  function getDoc4(store, id) {
985
1151
  const doc = store.get(id);
986
1152
  if (!doc) return { error: `Document "${id}" not found` };
@@ -997,11 +1163,11 @@ function register4(server, store) {
997
1163
  "atelier_add_state",
998
1164
  "Add a named animation state to a document",
999
1165
  {
1000
- id: z18.string().describe("Document ID"),
1001
- stateName: z18.string().describe("State name (e.g. 'intro', 'hover', 'exit')"),
1002
- duration: z18.number().positive().int().describe("Duration in frames"),
1003
- description: z18.string().optional().describe("State description"),
1004
- tags: z18.array(z18.string()).optional().describe("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")
1005
1171
  },
1006
1172
  { readOnlyHint: false, destructiveHint: false },
1007
1173
  async ({ id, stateName, duration, description, tags }) => {
@@ -1024,11 +1190,11 @@ function register4(server, store) {
1024
1190
  "atelier_edit_state",
1025
1191
  "Edit metadata of an existing animation state",
1026
1192
  {
1027
- id: z18.string().describe("Document ID"),
1028
- stateName: z18.string().describe("State name to edit"),
1029
- duration: z18.number().positive().int().optional().describe("New duration in frames"),
1030
- description: z18.string().optional().describe("New description"),
1031
- tags: z18.array(z18.string()).optional().describe("New 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")
1032
1198
  },
1033
1199
  { readOnlyHint: false, destructiveHint: false },
1034
1200
  async ({ id, stateName, duration, description, tags }) => {
@@ -1047,8 +1213,8 @@ function register4(server, store) {
1047
1213
  "atelier_remove_state",
1048
1214
  "Remove an animation state and all its deltas",
1049
1215
  {
1050
- id: z18.string().describe("Document ID"),
1051
- stateName: z18.string().describe("State name to remove")
1216
+ id: z19.string().describe("Document ID"),
1217
+ stateName: z19.string().describe("State name to remove")
1052
1218
  },
1053
1219
  { readOnlyHint: false, destructiveHint: true },
1054
1220
  async ({ id, stateName }) => {
@@ -1065,7 +1231,7 @@ function register4(server, store) {
1065
1231
  "atelier_list_states",
1066
1232
  "List all animation states in a document",
1067
1233
  {
1068
- id: z18.string().describe("Document ID")
1234
+ id: z19.string().describe("Document ID")
1069
1235
  },
1070
1236
  { readOnlyHint: true, destructiveHint: false },
1071
1237
  async ({ id }) => {
@@ -1085,7 +1251,7 @@ function register4(server, store) {
1085
1251
  }
1086
1252
 
1087
1253
  // ../mcp/src/tools/deltas.ts
1088
- import { z as z19 } from "zod";
1254
+ import { z as z20 } from "zod";
1089
1255
 
1090
1256
  // ../math/dist/index.js
1091
1257
  function linear(t) {
@@ -1824,17 +1990,17 @@ function register5(server, store) {
1824
1990
  "atelier_add_delta",
1825
1991
  "Add an animation delta (keyframe transition) to a state",
1826
1992
  {
1827
- id: z19.string().describe("Document ID"),
1828
- stateName: z19.string().describe("State name"),
1829
- layer: z19.string().describe("Target layer ID"),
1993
+ id: z20.string().describe("Document ID"),
1994
+ stateName: z20.string().describe("State name"),
1995
+ layer: z20.string().describe("Target layer ID"),
1830
1996
  property: AnimatablePropertyEnum.describe("Property to animate"),
1831
- range: z19.tuple([z19.number().int().min(0), z19.number().int().min(0)]).describe("Frame range [start, end] inclusive"),
1832
- from: z19.unknown().describe("Starting value"),
1833
- to: z19.unknown().describe("Ending value"),
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"),
1834
2000
  easing: EasingInputSchema.optional().describe("Easing function"),
1835
- description: z19.string().optional().describe("Delta description"),
1836
- tags: z19.array(z19.string()).optional().describe("Tags"),
1837
- deltaId: z19.string().optional().describe("Custom delta ID")
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")
1838
2004
  },
1839
2005
  { readOnlyHint: false, destructiveHint: false },
1840
2006
  async ({ id, stateName, layer, property, range, from, to, easing, description, tags, deltaId }) => {
@@ -1872,17 +2038,17 @@ function register5(server, store) {
1872
2038
  "atelier_edit_delta",
1873
2039
  "Edit an existing delta by index within a state",
1874
2040
  {
1875
- id: z19.string().describe("Document ID"),
1876
- stateName: z19.string().describe("State name"),
1877
- deltaIndex: z19.number().int().min(0).describe("Delta index within the state"),
1878
- layer: z19.string().optional().describe("New target layer ID"),
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"),
1879
2045
  property: AnimatablePropertyEnum.optional().describe("New property to animate"),
1880
- range: z19.tuple([z19.number().int().min(0), z19.number().int().min(0)]).optional().describe("New frame range [start, end]"),
1881
- from: z19.unknown().optional().describe("New starting value"),
1882
- to: z19.unknown().optional().describe("New ending value"),
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"),
1883
2049
  easing: EasingInputSchema.optional().describe("New easing function"),
1884
- description: z19.string().optional().describe("New description"),
1885
- tags: z19.array(z19.string()).optional().describe("New tags")
2050
+ description: z20.string().optional().describe("New description"),
2051
+ tags: z20.array(z20.string()).optional().describe("New tags")
1886
2052
  },
1887
2053
  { readOnlyHint: false, destructiveHint: false },
1888
2054
  async ({ id, stateName, deltaIndex, layer, property, range, from, to, easing, description, tags }) => {
@@ -1921,9 +2087,9 @@ function register5(server, store) {
1921
2087
  "atelier_remove_delta",
1922
2088
  "Remove a delta by index from a state",
1923
2089
  {
1924
- id: z19.string().describe("Document ID"),
1925
- stateName: z19.string().describe("State name"),
1926
- deltaIndex: z19.number().int().min(0).describe("Delta index to remove")
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")
1927
2093
  },
1928
2094
  { readOnlyHint: false, destructiveHint: true },
1929
2095
  async ({ id, stateName, deltaIndex }) => {
@@ -1951,12 +2117,12 @@ function register5(server, store) {
1951
2117
  "atelier_apply_preset",
1952
2118
  "Apply a preset to a layer, expanding it into concrete deltas",
1953
2119
  {
1954
- id: z19.string().describe("Document ID"),
1955
- stateName: z19.string().describe("State name to add deltas to"),
1956
- presetName: z19.string().describe("Preset name defined in the document"),
1957
- layerId: z19.string().describe("Target layer ID"),
1958
- startFrame: z19.number().int().min(0).optional().describe("Start frame (default: 0)"),
1959
- duration: z19.number().positive().int().optional().describe("Duration for preset (default: state 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)")
1960
2126
  },
1961
2127
  { readOnlyHint: false, destructiveHint: false },
1962
2128
  async ({ id, stateName, presetName, layerId, startFrame, duration }) => {
@@ -1999,7 +2165,7 @@ ${errors.join("\n")}`);
1999
2165
  }
2000
2166
 
2001
2167
  // ../mcp/src/tools/presets.ts
2002
- import { z as z20 } from "zod";
2168
+ import { z as z21 } from "zod";
2003
2169
  function getDoc6(store, id) {
2004
2170
  const doc = store.get(id);
2005
2171
  if (!doc) return { error: `Document "${id}" not found` };
@@ -2013,11 +2179,11 @@ function err6(message) {
2013
2179
  }
2014
2180
  var AnimatablePropertyEnum2 = AnimatablePropertySchema;
2015
2181
  var EasingInputSchema2 = EasingSchema;
2016
- var PresetDeltaSchema2 = z20.object({
2182
+ var PresetDeltaSchema2 = z21.object({
2017
2183
  property: AnimatablePropertyEnum2.describe("Animatable property"),
2018
- offset: z20.tuple([z20.number().int().min(0), z20.number().int().min(0)]).optional().describe("Relative frame offset [start, end]"),
2019
- from: z20.unknown().describe("Starting value"),
2020
- to: z20.unknown().describe("Ending value"),
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"),
2021
2187
  easing: EasingInputSchema2.optional().describe("Easing function")
2022
2188
  });
2023
2189
  function register6(server, store) {
@@ -2025,11 +2191,11 @@ function register6(server, store) {
2025
2191
  "atelier_define_preset",
2026
2192
  "Define a reusable animation preset on a document",
2027
2193
  {
2028
- id: z20.string().describe("Document ID"),
2029
- presetName: z20.string().describe("Preset name"),
2030
- description: z20.string().optional().describe("Preset description"),
2031
- tags: z20.array(z20.string()).optional().describe("Tags"),
2032
- deltas: z20.array(PresetDeltaSchema2).min(1).describe("Array of preset delta definitions")
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")
2033
2199
  },
2034
2200
  { readOnlyHint: false, destructiveHint: false },
2035
2201
  async ({ id, presetName, description, tags, deltas }) => {
@@ -2050,7 +2216,7 @@ function register6(server, store) {
2050
2216
  "atelier_list_presets",
2051
2217
  "List all presets defined on a document",
2052
2218
  {
2053
- id: z20.string().describe("Document ID")
2219
+ id: z21.string().describe("Document ID")
2054
2220
  },
2055
2221
  { readOnlyHint: true, destructiveHint: false },
2056
2222
  async ({ id }) => {
@@ -2073,7 +2239,7 @@ function register6(server, store) {
2073
2239
  }
2074
2240
 
2075
2241
  // ../mcp/src/tools/preview.ts
2076
- import { z as z21 } from "zod";
2242
+ import { z as z22 } from "zod";
2077
2243
  function getDoc7(store, id) {
2078
2244
  const doc = store.get(id);
2079
2245
  if (!doc) return { error: `Document "${id}" not found` };
@@ -2090,7 +2256,7 @@ function register7(server, store) {
2090
2256
  "atelier_validate",
2091
2257
  "Validate an Atelier document for schema correctness and delta overlaps",
2092
2258
  {
2093
- id: z21.string().describe("Document ID")
2259
+ id: z22.string().describe("Document ID")
2094
2260
  },
2095
2261
  { readOnlyHint: true, destructiveHint: false },
2096
2262
  async ({ id }) => {
@@ -2119,9 +2285,9 @@ function register7(server, store) {
2119
2285
  "atelier_preview",
2120
2286
  "Preview the resolved state of all layers at a specific frame",
2121
2287
  {
2122
- id: z21.string().describe("Document ID"),
2123
- stateName: z21.string().describe("State name to preview"),
2124
- frame: z21.number().int().min(0).describe("Frame number to resolve")
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")
2125
2291
  },
2126
2292
  { readOnlyHint: true, destructiveHint: false },
2127
2293
  async ({ id, stateName, frame }) => {
@@ -2156,7 +2322,7 @@ function register7(server, store) {
2156
2322
  }
2157
2323
 
2158
2324
  // ../mcp/src/tools/templates.ts
2159
- import { z as z22 } from "zod";
2325
+ import { z as z23 } from "zod";
2160
2326
  function getDoc8(store, id) {
2161
2327
  const doc = store.get(id);
2162
2328
  if (!doc) return { error: `Document "${id}" not found` };
@@ -2173,8 +2339,8 @@ function register8(server, store) {
2173
2339
  "atelier_instantiate_template",
2174
2340
  "Instantiate a template document with variable bindings. Creates a new document in the store.",
2175
2341
  {
2176
- id: z22.string().describe("Template document ID"),
2177
- bindings: z22.record(z22.unknown()).describe("Variable bindings: { variableName: value }")
2342
+ id: z23.string().describe("Template document ID"),
2343
+ bindings: z23.record(z23.unknown()).describe("Variable bindings: { variableName: value }")
2178
2344
  },
2179
2345
  { readOnlyHint: false, destructiveHint: false },
2180
2346
  async ({ id, bindings }) => {
@@ -2197,7 +2363,7 @@ function register8(server, store) {
2197
2363
  "atelier_find_variables",
2198
2364
  "Scan a document for {{variableName}} patterns. Returns the list of variable references found.",
2199
2365
  {
2200
- id: z22.string().describe("Document ID")
2366
+ id: z23.string().describe("Document ID")
2201
2367
  },
2202
2368
  { readOnlyHint: true, destructiveHint: false },
2203
2369
  async ({ id }) => {
@@ -2217,7 +2383,7 @@ function register8(server, store) {
2217
2383
  }
2218
2384
 
2219
2385
  // ../mcp/src/tools/layer-effects.ts
2220
- import { z as z23 } from "zod";
2386
+ import { z as z24 } from "zod";
2221
2387
  function getDoc9(store, id) {
2222
2388
  const doc = store.get(id);
2223
2389
  if (!doc) return { error: `Document "${id}" not found` };
@@ -2234,9 +2400,9 @@ function register9(server, store) {
2234
2400
  "atelier_set_blend_mode",
2235
2401
  "Set or clear the blend mode on a layer",
2236
2402
  {
2237
- id: z23.string().describe("Document ID"),
2238
- layerId: z23.string().describe("Layer ID"),
2239
- blendMode: z23.enum([
2403
+ id: z24.string().describe("Document ID"),
2404
+ layerId: z24.string().describe("Layer ID"),
2405
+ blendMode: z24.enum([
2240
2406
  "normal",
2241
2407
  "multiply",
2242
2408
  "screen",
@@ -2274,13 +2440,13 @@ function register9(server, store) {
2274
2440
  "atelier_set_shadow",
2275
2441
  "Set or remove a drop shadow on a layer",
2276
2442
  {
2277
- id: z23.string().describe("Document ID"),
2278
- layerId: z23.string().describe("Layer ID"),
2279
- shadow: z23.object({
2280
- color: z23.unknown().describe("Shadow color (hex string or RGBA/HSLA object)"),
2281
- blur: z23.number().min(0).describe("Blur radius in pixels"),
2282
- offsetX: z23.number().optional().describe("Horizontal offset in pixels"),
2283
- offsetY: z23.number().optional().describe("Vertical offset in pixels")
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")
2284
2450
  }).nullable().describe("Shadow definition, or null to remove")
2285
2451
  },
2286
2452
  { readOnlyHint: false, destructiveHint: false },
@@ -2302,11 +2468,11 @@ function register9(server, store) {
2302
2468
  "atelier_set_tint",
2303
2469
  "Set or remove a color tint on a layer (works on any visual type)",
2304
2470
  {
2305
- id: z23.string().describe("Document ID"),
2306
- layerId: z23.string().describe("Layer ID"),
2307
- tint: z23.object({
2308
- color: z23.string().describe("Tint color (hex string)"),
2309
- amount: z23.number().min(0).max(1).describe("Tint amount (0 = none, 1 = full)")
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)")
2310
2476
  }).nullable().describe("Tint definition, or null to remove")
2311
2477
  },
2312
2478
  { readOnlyHint: false, destructiveHint: false },
@@ -2328,18 +2494,18 @@ function register9(server, store) {
2328
2494
  "atelier_set_motion_path",
2329
2495
  "Set or remove a motion path on a layer for path-based position animation",
2330
2496
  {
2331
- id: z23.string().describe("Document ID"),
2332
- layerId: z23.string().describe("Layer ID"),
2333
- motionPath: z23.object({
2334
- points: z23.array(z23.object({
2335
- x: z23.number(),
2336
- y: z23.number(),
2337
- in: z23.object({ x: z23.number(), y: z23.number() }).optional(),
2338
- out: z23.object({ x: z23.number(), y: z23.number() }).optional()
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()
2339
2505
  })).min(2).describe("Path points (minimum 2)"),
2340
- closed: z23.boolean().optional().describe("Whether the path is closed"),
2341
- autoRotate: z23.boolean().optional().describe("Auto-rotate layer to follow path tangent"),
2342
- autoRotateOffset: z23.number().optional().describe("Rotation offset in degrees when autoRotate is enabled")
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")
2343
2509
  }).nullable().describe("Motion path definition, or null to remove")
2344
2510
  },
2345
2511
  { readOnlyHint: false, destructiveHint: false },
@@ -2361,21 +2527,21 @@ function register9(server, store) {
2361
2527
  "atelier_set_clip_path",
2362
2528
  "Set or remove a clip path on a layer to restrict rendering to within a shape",
2363
2529
  {
2364
- id: z23.string().describe("Document ID"),
2365
- layerId: z23.string().describe("Layer ID"),
2366
- clipPath: z23.object({
2367
- type: z23.enum(["rect", "ellipse", "path"]).describe("Clip shape type"),
2368
- cornerRadius: z23.union([
2369
- z23.number(),
2370
- z23.tuple([z23.number(), z23.number(), z23.number(), z23.number()])
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()])
2371
2537
  ]).optional().describe("Corner radius (rect only)"),
2372
- points: z23.array(z23.object({
2373
- x: z23.number(),
2374
- y: z23.number(),
2375
- in: z23.object({ x: z23.number(), y: z23.number() }).optional(),
2376
- out: z23.object({ x: z23.number(), y: z23.number() }).optional()
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()
2377
2543
  })).optional().describe("Path points (path only)"),
2378
- closed: z23.boolean().optional().describe("Whether path is closed (path only)")
2544
+ closed: z24.boolean().optional().describe("Whether path is closed (path only)")
2379
2545
  }).nullable().describe("Clip path shape, or null to remove")
2380
2546
  },
2381
2547
  { readOnlyHint: false, destructiveHint: false },
@@ -2397,45 +2563,45 @@ function register9(server, store) {
2397
2563
  "atelier_edit_visual",
2398
2564
  "Edit visual content of an existing layer (text content/style, image src/assetId, shape/fill/stroke)",
2399
2565
  {
2400
- id: z23.string().describe("Document ID"),
2401
- layerId: z23.string().describe("Layer ID"),
2566
+ id: z24.string().describe("Document ID"),
2567
+ layerId: z24.string().describe("Layer ID"),
2402
2568
  // Text fields
2403
- content: z23.string().optional().describe("New text content (text layers only)"),
2404
- style: z23.record(z23.unknown()).optional().describe("Partial style update \u2014 only provided keys are overwritten (text layers only)"),
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)"),
2405
2571
  // Image fields
2406
- src: z23.string().optional().describe("Image source URL (image layers only)"),
2407
- assetId: z23.string().optional().describe("Asset ID reference (image layers only)"),
2408
- sourceRect: z23.object({
2409
- x: z23.number(),
2410
- y: z23.number(),
2411
- width: z23.number().positive(),
2412
- height: z23.number().positive()
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()
2413
2579
  }).nullable().optional().describe("Source rectangle crop (image layers only). Null to remove."),
2414
- spritesheet: z23.object({
2415
- columns: z23.number().int().positive(),
2416
- rows: z23.number().int().positive(),
2417
- frameCount: z23.number().int().positive().optional(),
2418
- frameWidth: z23.number().positive(),
2419
- frameHeight: z23.number().positive()
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()
2420
2586
  }).nullable().optional().describe("Spritesheet config (image layers only). Null to remove."),
2421
- frameIndex: z23.number().int().min(0).optional().describe("Spritesheet frame index (image layers only)"),
2587
+ frameIndex: z24.number().int().min(0).optional().describe("Spritesheet frame index (image layers only)"),
2422
2588
  // Shape fields
2423
- shape: z23.object({
2424
- type: z23.enum(["rect", "ellipse", "path"]),
2425
- cornerRadius: z23.union([
2426
- z23.number(),
2427
- z23.tuple([z23.number(), z23.number(), z23.number(), z23.number()])
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()])
2428
2594
  ]).optional(),
2429
- points: z23.array(z23.object({
2430
- x: z23.number(),
2431
- y: z23.number(),
2432
- in: z23.object({ x: z23.number(), y: z23.number() }).optional(),
2433
- out: z23.object({ x: z23.number(), y: z23.number() }).optional()
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()
2434
2600
  })).optional(),
2435
- closed: z23.boolean().optional()
2601
+ closed: z24.boolean().optional()
2436
2602
  }).optional().describe("New shape (shape layers only)"),
2437
- fill: z23.record(z23.unknown()).optional().describe("New fill definition (shape layers only)"),
2438
- stroke: z23.record(z23.unknown()).optional().describe("New stroke definition (shape layers only)")
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)")
2439
2605
  },
2440
2606
  { readOnlyHint: false, destructiveHint: false },
2441
2607
  async ({ id, layerId, content, style, src, assetId, sourceRect, spritesheet, frameIndex, shape, fill, stroke }) => {
@@ -2516,7 +2682,7 @@ function register9(server, store) {
2516
2682
  }
2517
2683
 
2518
2684
  // ../mcp/src/tools/state-config.ts
2519
- import { z as z24 } from "zod";
2685
+ import { z as z25 } from "zod";
2520
2686
  function getDoc10(store, id) {
2521
2687
  const doc = store.get(id);
2522
2688
  if (!doc) return { error: `Document "${id}" not found` };
@@ -2528,27 +2694,27 @@ function ok10(data) {
2528
2694
  function err10(message) {
2529
2695
  return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
2530
2696
  }
2531
- var EasingInputSchema3 = z24.union([
2532
- z24.enum(["ease-in", "ease-out", "ease-in-out"]),
2533
- z24.object({ type: z24.literal("linear") }),
2534
- z24.object({
2535
- type: z24.literal("cubic-bezier"),
2536
- x1: z24.number(),
2537
- y1: z24.number(),
2538
- x2: z24.number(),
2539
- y2: z24.number()
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()
2540
2706
  }),
2541
- z24.object({
2542
- type: z24.literal("spring"),
2543
- mass: z24.number().optional(),
2544
- stiffness: z24.number().optional(),
2545
- damping: z24.number().optional(),
2546
- velocity: z24.number().optional()
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()
2547
2713
  }),
2548
- z24.object({
2549
- type: z24.literal("step"),
2550
- steps: z24.number().int().positive(),
2551
- position: z24.enum(["start", "end"]).optional()
2714
+ z25.object({
2715
+ type: z25.literal("step"),
2716
+ steps: z25.number().int().positive(),
2717
+ position: z25.enum(["start", "end"]).optional()
2552
2718
  })
2553
2719
  ]).describe("Easing function");
2554
2720
  function register10(server, store) {
@@ -2556,14 +2722,14 @@ function register10(server, store) {
2556
2722
  "atelier_set_audio",
2557
2723
  "Set or remove an audio track on a state",
2558
2724
  {
2559
- id: z24.string().describe("Document ID"),
2560
- stateName: z24.string().describe("State name"),
2561
- audio: z24.object({
2562
- src: z24.string().describe("Audio source \u2014 asset reference, file path, or URL"),
2563
- offset: z24.number().min(0).optional().describe("Skip into audio in seconds (default: 0)"),
2564
- volume: z24.number().min(0).max(1).optional().describe("Playback volume 0\u20131 (default: 1)"),
2565
- loop: z24.boolean().optional().describe("Loop for state duration (default: false)"),
2566
- startFrame: z24.number().int().min(0).optional().describe("Frame to start playing (default: 0)")
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)")
2567
2733
  }).nullable().describe("Audio configuration, or null to remove")
2568
2734
  },
2569
2735
  { readOnlyHint: false, destructiveHint: false },
@@ -2585,11 +2751,11 @@ function register10(server, store) {
2585
2751
  "atelier_configure_transition",
2586
2752
  "Configure or remove a transition from one state to a target state",
2587
2753
  {
2588
- id: z24.string().describe("Document ID"),
2589
- stateName: z24.string().describe("Source state name"),
2590
- targetState: z24.string().describe("Target state name"),
2591
- transition: z24.object({
2592
- duration: z24.number().positive().int().describe("Transition duration in frames"),
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"),
2593
2759
  easing: EasingInputSchema3.optional()
2594
2760
  }).nullable().describe("Transition config, or null to remove")
2595
2761
  },
@@ -2626,9 +2792,9 @@ function register10(server, store) {
2626
2792
  "atelier_set_state_parent",
2627
2793
  "Set or clear the parent state for hierarchical delta inheritance",
2628
2794
  {
2629
- id: z24.string().describe("Document ID"),
2630
- stateName: z24.string().describe("State name"),
2631
- parent: z24.string().nullable().describe("Parent state name, or null to clear")
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")
2632
2798
  },
2633
2799
  { readOnlyHint: false, destructiveHint: false },
2634
2800
  async ({ id, stateName, parent }) => {
@@ -2663,7 +2829,7 @@ function register10(server, store) {
2663
2829
  }
2664
2830
 
2665
2831
  // ../mcp/src/tools/export.ts
2666
- import { z as z25 } from "zod";
2832
+ import { z as z26 } from "zod";
2667
2833
 
2668
2834
  // ../canvas/dist/index.js
2669
2835
  function colorToCSS(color) {
@@ -3848,12 +4014,12 @@ function register11(server, store) {
3848
4014
  "atelier_export_svg",
3849
4015
  "Export a frame as an SVG string",
3850
4016
  {
3851
- id: z25.string().describe("Document ID"),
3852
- stateName: z25.string().optional().describe("State name (defaults to first state)"),
3853
- frame: z25.number().int().min(0).optional().describe("Frame number (defaults to 0)"),
3854
- xmlDeclaration: z25.boolean().optional().describe("Include XML declaration (default: false)"),
3855
- viewBox: z25.boolean().optional().describe("Include viewBox attribute (default: true)"),
3856
- indent: z25.number().int().min(0).optional().describe("Indent size in spaces (default: 2)")
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)")
3857
4023
  },
3858
4024
  { readOnlyHint: true, destructiveHint: false },
3859
4025
  async ({ id, stateName, frame, xmlDeclaration, viewBox, indent }) => {
@@ -3884,8 +4050,8 @@ function register11(server, store) {
3884
4050
  "atelier_export_lottie",
3885
4051
  "Export a document to Lottie JSON format (lossy, best-effort)",
3886
4052
  {
3887
- id: z25.string().describe("Document ID"),
3888
- stateName: z25.string().optional().describe("State to export (defaults to first state)")
4053
+ id: z26.string().describe("Document ID"),
4054
+ stateName: z26.string().optional().describe("State to export (defaults to first state)")
3889
4055
  },
3890
4056
  { readOnlyHint: true, destructiveHint: false },
3891
4057
  async ({ id, stateName }) => {
@@ -3905,7 +4071,7 @@ function register11(server, store) {
3905
4071
  }
3906
4072
 
3907
4073
  // ../mcp/src/tools/assets.ts
3908
- import { z as z26 } from "zod";
4074
+ import { z as z27 } from "zod";
3909
4075
  function getDoc12(store, id) {
3910
4076
  const doc = store.get(id);
3911
4077
  if (!doc) return { error: `Document "${id}" not found` };
@@ -3922,17 +4088,17 @@ function register12(server, store) {
3922
4088
  "atelier_add_asset",
3923
4089
  "Register an external asset (image, SVG, font, animation, audio) on a document",
3924
4090
  {
3925
- id: z26.string().describe("Document ID"),
3926
- assetId: z26.string().describe("Unique asset identifier"),
3927
- type: z26.enum(["image", "svg", "font", "animation", "audio"]).describe("Asset type"),
3928
- src: z26.string().describe("File path or URL"),
3929
- description: z26.string().optional().describe("Human-readable description"),
3930
- spritesheet: z26.object({
3931
- columns: z26.number().int().positive().describe("Number of columns in the spritesheet grid"),
3932
- rows: z26.number().int().positive().describe("Number of rows in the spritesheet grid"),
3933
- frameCount: z26.number().int().positive().optional().describe("Total frame count (defaults to columns \xD7 rows)"),
3934
- frameWidth: z26.number().positive().describe("Width of each frame in pixels"),
3935
- frameHeight: z26.number().positive().describe("Height of each frame in pixels")
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")
3936
4102
  }).optional().describe("Spritesheet metadata (image assets only)")
3937
4103
  },
3938
4104
  { readOnlyHint: false, destructiveHint: false },
@@ -3955,7 +4121,7 @@ function register12(server, store) {
3955
4121
  "atelier_list_assets",
3956
4122
  "List all registered assets with usage info (which layers/states reference them)",
3957
4123
  {
3958
- id: z26.string().describe("Document ID")
4124
+ id: z27.string().describe("Document ID")
3959
4125
  },
3960
4126
  { readOnlyHint: true, destructiveHint: false },
3961
4127
  async ({ id }) => {
@@ -3982,8 +4148,8 @@ function register12(server, store) {
3982
4148
  "atelier_remove_asset",
3983
4149
  "Remove a registered asset. Warns if layers or state audio still reference it.",
3984
4150
  {
3985
- id: z26.string().describe("Document ID"),
3986
- assetId: z26.string().describe("Asset ID to remove")
4151
+ id: z27.string().describe("Document ID"),
4152
+ assetId: z27.string().describe("Asset ID to remove")
3987
4153
  },
3988
4154
  { readOnlyHint: false, destructiveHint: true },
3989
4155
  async ({ id, assetId }) => {
@@ -4007,7 +4173,7 @@ function register12(server, store) {
4007
4173
  }
4008
4174
 
4009
4175
  // ../mcp/src/tools/variables.ts
4010
- import { z as z27 } from "zod";
4176
+ import { z as z28 } from "zod";
4011
4177
  function getDoc13(store, id) {
4012
4178
  const doc = store.get(id);
4013
4179
  if (!doc) return { error: `Document "${id}" not found` };
@@ -4024,11 +4190,11 @@ function register13(server, store) {
4024
4190
  "atelier_add_variable",
4025
4191
  "Define a template variable on a document",
4026
4192
  {
4027
- id: z27.string().describe("Document ID"),
4028
- variableName: z27.string().describe("Variable name (used in {{variableName}} patterns)"),
4029
- type: z27.enum(["string", "number", "color", "asset", "boolean"]).describe("Variable type"),
4030
- description: z27.string().optional().describe("Human-readable description"),
4031
- default: z27.unknown().optional().describe("Default value")
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")
4032
4198
  },
4033
4199
  { readOnlyHint: false, destructiveHint: false },
4034
4200
  async (args) => {
@@ -4049,7 +4215,7 @@ function register13(server, store) {
4049
4215
  "atelier_list_variables",
4050
4216
  "List all declared variables with usage info (which {{references}} exist in the document)",
4051
4217
  {
4052
- id: z27.string().describe("Document ID")
4218
+ id: z28.string().describe("Document ID")
4053
4219
  },
4054
4220
  { readOnlyHint: true, destructiveHint: false },
4055
4221
  async ({ id }) => {
@@ -4073,8 +4239,8 @@ function register13(server, store) {
4073
4239
  "atelier_remove_variable",
4074
4240
  "Remove a declared variable. Warns if {{references}} still exist in the document.",
4075
4241
  {
4076
- id: z27.string().describe("Document ID"),
4077
- variableName: z27.string().describe("Variable name to remove")
4242
+ id: z28.string().describe("Document ID"),
4243
+ variableName: z28.string().describe("Variable name to remove")
4078
4244
  },
4079
4245
  { readOnlyHint: false, destructiveHint: true },
4080
4246
  async ({ id, variableName }) => {
@@ -4097,7 +4263,7 @@ function register13(server, store) {
4097
4263
  }
4098
4264
 
4099
4265
  // ../mcp/src/tools/interactions.ts
4100
- import { z as z28 } from "zod";
4266
+ import { z as z29 } from "zod";
4101
4267
  function getDoc14(store, id) {
4102
4268
  const doc = store.get(id);
4103
4269
  if (!doc) return { error: `Document "${id}" not found` };
@@ -4114,23 +4280,23 @@ function register14(server, store) {
4114
4280
  "atelier_add_interaction",
4115
4281
  "Add a trigger-action interaction to a layer (click, hover, timer, signal \u2192 go-to-state, emit-signal, set-variable, toggle-visibility)",
4116
4282
  {
4117
- id: z28.string().describe("Document ID"),
4118
- layerId: z28.string().describe("Layer to attach the interaction to"),
4119
- interactionId: z28.string().describe("Unique interaction ID"),
4120
- trigger: z28.object({
4121
- type: z28.enum(["click", "hover", "pointerdown", "pointerup", "timer", "signal"]).describe("Trigger type"),
4122
- delay: z28.number().optional().describe("Delay in ms (timer trigger only)"),
4123
- signal: z28.string().optional().describe("Signal name (signal trigger only)")
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)")
4124
4290
  }).describe("What triggers the interaction"),
4125
- action: z28.object({
4126
- type: z28.enum(["go-to-state", "emit-signal", "set-variable", "toggle-visibility"]).describe("Action type"),
4127
- state: z28.string().optional().describe("Target state (go-to-state only)"),
4128
- signal: z28.string().optional().describe("Signal to emit (emit-signal only)"),
4129
- variable: z28.string().optional().describe("Variable name (set-variable only)"),
4130
- value: z28.unknown().optional().describe("Value to set (set-variable only)"),
4131
- targetLayer: z28.string().optional().describe("Target layer ID (toggle-visibility, defaults to self)")
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)")
4132
4298
  }).describe("What happens when triggered"),
4133
- description: z28.string().optional().describe("Human-readable description")
4299
+ description: z29.string().optional().describe("Human-readable description")
4134
4300
  },
4135
4301
  { readOnlyHint: false, destructiveHint: false },
4136
4302
  async ({ id, layerId, interactionId, trigger, action, description }) => {
@@ -4179,8 +4345,8 @@ function register14(server, store) {
4179
4345
  "atelier_list_interactions",
4180
4346
  "List all interactions across all layers or for a specific layer",
4181
4347
  {
4182
- id: z28.string().describe("Document ID"),
4183
- layerId: z28.string().optional().describe("Optional: filter to a specific layer")
4348
+ id: z29.string().describe("Document ID"),
4349
+ layerId: z29.string().optional().describe("Optional: filter to a specific layer")
4184
4350
  },
4185
4351
  { readOnlyHint: true, destructiveHint: false },
4186
4352
  async ({ id, layerId }) => {
@@ -4207,9 +4373,9 @@ function register14(server, store) {
4207
4373
  "atelier_remove_interaction",
4208
4374
  "Remove an interaction from a layer",
4209
4375
  {
4210
- id: z28.string().describe("Document ID"),
4211
- layerId: z28.string().describe("Layer ID"),
4212
- interactionId: z28.string().describe("Interaction ID to remove")
4376
+ id: z29.string().describe("Document ID"),
4377
+ layerId: z29.string().describe("Layer ID"),
4378
+ interactionId: z29.string().describe("Interaction ID to remove")
4213
4379
  },
4214
4380
  { readOnlyHint: false, destructiveHint: true },
4215
4381
  async ({ id, layerId, interactionId }) => {
@@ -4231,7 +4397,7 @@ function register14(server, store) {
4231
4397
  }
4232
4398
 
4233
4399
  // ../mcp/src/tools/refs.ts
4234
- import { z as z29 } from "zod";
4400
+ import { z as z30 } from "zod";
4235
4401
  function getDoc15(store, id) {
4236
4402
  const doc = store.get(id);
4237
4403
  if (!doc) return { error: `Document "${id}" not found` };
@@ -4248,9 +4414,9 @@ function register15(server, store) {
4248
4414
  "atelier_set_ref",
4249
4415
  "Set the source reference on a ref-type layer. Can point to a file path or another in-memory document ID.",
4250
4416
  {
4251
- id: z29.string().describe("Document ID"),
4252
- layerId: z29.string().describe("Layer ID (must be a ref visual)"),
4253
- src: z29.string().describe("File path, URL, or in-memory document ID")
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")
4254
4420
  },
4255
4421
  { readOnlyHint: false, destructiveHint: false },
4256
4422
  async ({ id, layerId, src }) => {
@@ -4270,7 +4436,7 @@ function register15(server, store) {
4270
4436
  "atelier_resolve_refs",
4271
4437
  "Check which ref layers can be resolved (point to valid in-memory documents)",
4272
4438
  {
4273
- id: z29.string().describe("Document ID")
4439
+ id: z30.string().describe("Document ID")
4274
4440
  },
4275
4441
  { readOnlyHint: true, destructiveHint: false },
4276
4442
  async ({ id }) => {
@@ -4297,7 +4463,7 @@ function register15(server, store) {
4297
4463
  }
4298
4464
 
4299
4465
  // ../mcp/src/tools/performance.ts
4300
- import { z as z30 } from "zod";
4466
+ import { z as z31 } from "zod";
4301
4467
  function getDoc16(store, id) {
4302
4468
  const doc = store.get(id);
4303
4469
  if (!doc) return { error: `Document "${id}" not found` };
@@ -4314,9 +4480,9 @@ function register16(server, store) {
4314
4480
  "atelier_profile",
4315
4481
  "Profile frame resolution performance \u2014 returns timing, layer count, property count, and delta count for a frame",
4316
4482
  {
4317
- id: z30.string().describe("Document ID"),
4318
- stateName: z30.string().describe("State name to profile"),
4319
- frame: z30.number().int().min(0).describe("Frame number to resolve")
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")
4320
4486
  },
4321
4487
  { readOnlyHint: true, destructiveHint: false },
4322
4488
  async ({ id, stateName, frame }) => {
@@ -4362,10 +4528,10 @@ function register16(server, store) {
4362
4528
  "atelier_diff_frames",
4363
4529
  "Compare two frames and return only the properties that changed between them",
4364
4530
  {
4365
- id: z30.string().describe("Document ID"),
4366
- stateName: z30.string().describe("State name"),
4367
- frameA: z30.number().int().min(0).describe("First frame number"),
4368
- frameB: z30.number().int().min(0).describe("Second frame number")
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")
4369
4535
  },
4370
4536
  { readOnlyHint: true, destructiveHint: false },
4371
4537
  async ({ id, stateName, frameA, frameB }) => {
@@ -4404,9 +4570,9 @@ function register16(server, store) {
4404
4570
  "atelier_batch_preview",
4405
4571
  "Preview multiple frames at once \u2014 reduces round-trips for scrubbing or timeline inspection",
4406
4572
  {
4407
- id: z30.string().describe("Document ID"),
4408
- stateName: z30.string().describe("State name to preview"),
4409
- frames: z30.array(z30.number().int().min(0)).min(1).max(60).describe("Array of frame numbers to resolve (max 60)")
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)")
4410
4576
  },
4411
4577
  { readOnlyHint: true, destructiveHint: false },
4412
4578
  async ({ id, stateName, frames }) => {
@@ -4442,7 +4608,7 @@ function register16(server, store) {
4442
4608
  "atelier_complexity",
4443
4609
  "Analyze document complexity \u2014 layer count, delta count, expression usage, state hierarchy depth",
4444
4610
  {
4445
- id: z30.string().describe("Document ID")
4611
+ id: z31.string().describe("Document ID")
4446
4612
  },
4447
4613
  { readOnlyHint: true, destructiveHint: false },
4448
4614
  async ({ id }) => {
@@ -4514,9 +4680,678 @@ function register16(server, store) {
4514
4680
  );
4515
4681
  }
4516
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
+
4517
5353
  // ../mcp/src/index.ts
4518
- function createServer() {
4519
- const store = new DocumentStore();
5354
+ function createServer(store = new DocumentStore()) {
4520
5355
  const server = new McpServer(
4521
5356
  { name: "atelier", version: "0.1.0" },
4522
5357
  { capabilities: { tools: {} } }
@@ -4537,6 +5372,9 @@ function createServer() {
4537
5372
  register14(server, store);
4538
5373
  register15(server, store);
4539
5374
  register16(server, store);
5375
+ register17(server, store);
5376
+ register18(server, store);
5377
+ register19(server, store);
4540
5378
  return { server, store };
4541
5379
  }
4542
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"));
@@ -4549,18 +5387,24 @@ if (isMain) {
4549
5387
  });
4550
5388
  }
4551
5389
  export {
5390
+ BRIDGE_PROTOCOL_VERSION,
4552
5391
  DocumentStore,
5392
+ WebSocketServerTransport,
4553
5393
  createServer,
5394
+ isBridgeEnvelope,
4554
5395
  register12 as registerAssetTools,
4555
5396
  register5 as registerDeltaTools,
4556
5397
  register as registerDocumentTools,
4557
5398
  register11 as registerExportTools,
5399
+ register19 as registerImportImageTools,
4558
5400
  register14 as registerInteractionTools,
4559
5401
  register9 as registerLayerEffectTools,
4560
5402
  register2 as registerLayerTools,
5403
+ register17 as registerOverlayTools,
4561
5404
  register16 as registerPerformanceTools,
4562
5405
  register6 as registerPresetTools,
4563
5406
  register7 as registerPreviewTools,
5407
+ register18 as registerRecipeTools,
4564
5408
  register15 as registerRefTools,
4565
5409
  register3 as registerShapeTools,
4566
5410
  register10 as registerStateConfigTools,