@a-company/atelier 0.36.0 → 0.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-JPZ4F4PW.js → chunk-3ARBOSWY.js} +64 -5
- package/dist/chunk-3ARBOSWY.js.map +1 -0
- package/dist/cli.js +11469 -413
- package/dist/cli.js.map +1 -1
- package/dist/{dist-M67UZGFQ.js → dist-3YQK6PI6.js} +2 -2
- package/dist/index.cjs +3193 -227
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +701 -8
- package/dist/index.d.ts +701 -8
- package/dist/index.js +7237 -72
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +2898 -507
- package/dist/mcp.js.map +1 -1
- package/package.json +14 -9
- package/src/web/inline-app.ts +55 -4
- package/src/web/timeline-state-types.ts +28 -0
- package/src/web/timeline-view.test.ts +99 -0
- package/src/web/timeline-view.ts +339 -0
- package/src/web/workspace-app.ts +3146 -0
- package/templates/workspace/.claude/agents/atelier-iris.md +75 -0
- package/templates/workspace/.claude/agents/atelier-lux.md +67 -0
- package/templates/workspace/.claude/agents/atelier-quill.md +61 -0
- package/templates/workspace/.gitignore +30 -0
- package/templates/workspace/.paradigm/personas/_shared/cascade-merge.md +172 -0
- package/templates/workspace/CLAUDE.md +93 -0
- package/templates/workspace/README.md +75 -0
- package/templates/workspace/SETUP.md +127 -0
- package/templates/workspace/_brand/.atelier-brand.yaml +34 -0
- package/templates/workspace/_brand/DESIGN.md +56 -0
- package/templates/workspace/_brand/SCRIPT.md +41 -0
- package/templates/workspace/_brand/STORYBOARD.md +33 -0
- package/templates/workspace/_packs/README.md +54 -0
- package/templates/workspace/projects/README.md +49 -0
- package/templates/workspace/workspace.atelier +22 -0
- package/university/content/notes/N-atel-001-first-render.md +114 -0
- package/university/content/notes/N-atel-001-install-and-launch.md +84 -0
- package/university/content/notes/N-atel-001-what-is-atelier.md +51 -0
- package/university/content/notes/N-atel-101-easings.md +97 -0
- package/university/content/notes/N-atel-101-layers.md +106 -0
- package/university/content/notes/N-atel-101-states-and-deltas.md +94 -0
- package/university/content/notes/N-atel-101-the-atelier-format.md +72 -0
- package/university/content/notes/N-atel-201-authoring-tools.md +141 -0
- package/university/content/notes/N-atel-201-mcp-overview.md +86 -0
- package/university/content/notes/N-atel-201-patterns.md +108 -0
- package/university/content/notes/N-atel-201-visual-and-effects.md +125 -0
- package/university/content/notes/N-atel-301-composition-and-overlays.md +141 -0
- package/university/content/notes/N-atel-301-effects.md +136 -0
- package/university/content/notes/N-atel-301-images-and-video.md +126 -0
- package/university/content/notes/N-atel-301-shapes-and-text.md +118 -0
- package/university/content/notes/N-atel-401-hierarchical-states.md +71 -0
- package/university/content/notes/N-atel-401-motion-deep-dive.md +106 -0
- package/university/content/notes/N-atel-401-presets-and-templates.md +98 -0
- package/university/content/notes/N-atel-401-transitions.md +94 -0
- package/university/content/notes/N-atel-501-detected-vs-user-edited.md +76 -0
- package/university/content/notes/N-atel-501-layer-tag-isolation.md +62 -0
- package/university/content/notes/N-atel-501-silence-trim.md +98 -0
- package/university/content/notes/N-atel-501-transcribe-and-captions.md +98 -0
- package/university/content/notes/N-atel-601-carousel.md +71 -0
- package/university/content/notes/N-atel-601-overlay-rules.md +96 -0
- package/university/content/notes/N-atel-601-recipe-tools-and-apply.md +84 -0
- package/university/content/notes/N-atel-601-studio-recipe.md +103 -0
- package/university/content/notes/N-atel-701-choosing-output.md +68 -0
- package/university/content/notes/N-atel-701-png-and-frames.md +84 -0
- package/university/content/notes/N-atel-701-vector.md +85 -0
- package/university/content/notes/N-atel-701-video.md +88 -0
- package/university/content/notes/N-atel-801-editing-surface.md +69 -0
- package/university/content/notes/N-atel-801-live-bridge.md +84 -0
- package/university/content/notes/N-atel-801-studio-app.md +72 -0
- package/university/content/notes/N-atel-801-symbiotic-loop.md +56 -0
- package/university/content/paths/LP-atel-001.yaml +21 -0
- package/university/content/paths/LP-atel-101.yaml +22 -0
- package/university/content/paths/LP-atel-201.yaml +23 -0
- package/university/content/paths/LP-atel-301.yaml +22 -0
- package/university/content/paths/LP-atel-401.yaml +22 -0
- package/university/content/paths/LP-atel-501.yaml +22 -0
- package/university/content/paths/LP-atel-601.yaml +22 -0
- package/university/content/paths/LP-atel-701.yaml +22 -0
- package/university/content/paths/LP-atel-801.yaml +22 -0
- package/university/content/quizzes/Q-atel-001-orientation.yaml +66 -0
- package/university/content/quizzes/Q-atel-101-document-model.yaml +66 -0
- package/university/content/quizzes/Q-atel-201-mcp-authoring.yaml +66 -0
- package/university/content/quizzes/Q-atel-301-visual-system.yaml +66 -0
- package/university/content/quizzes/Q-atel-401-state-machines.yaml +66 -0
- package/university/content/quizzes/Q-atel-501-video-pipeline.yaml +66 -0
- package/university/content/quizzes/Q-atel-601-recipes.yaml +66 -0
- package/university/content/quizzes/Q-atel-701-export.yaml +66 -0
- package/university/content/quizzes/Q-atel-801-studio-loop.yaml +66 -0
- package/university/index.yaml +720 -0
- package/university/pack.yaml +21 -0
- package/dist/chunk-5QQESXI6.js +0 -4432
- package/dist/chunk-5QQESXI6.js.map +0 -1
- package/dist/chunk-JPZ4F4PW.js.map +0 -1
- package/dist/cli.cjs +0 -6313
- package/dist/cli.cjs.map +0 -1
- package/dist/cli.d.cts +0 -1
- package/dist/cli.d.ts +0 -1
- package/dist/mcp.cjs +0 -5462
- package/dist/mcp.cjs.map +0 -1
- /package/dist/{dist-M67UZGFQ.js.map → dist-3YQK6PI6.js.map} +0 -0
package/dist/mcp.js
CHANGED
|
@@ -86,7 +86,7 @@ var DocumentStore = class {
|
|
|
86
86
|
};
|
|
87
87
|
|
|
88
88
|
// ../mcp/src/tools/document.ts
|
|
89
|
-
import { z as
|
|
89
|
+
import { z as z26 } from "zod";
|
|
90
90
|
|
|
91
91
|
// ../schema/dist/index.js
|
|
92
92
|
import { z } from "zod";
|
|
@@ -102,9 +102,20 @@ import { z as z10 } from "zod";
|
|
|
102
102
|
import { z as z11 } from "zod";
|
|
103
103
|
import { z as z12 } from "zod";
|
|
104
104
|
import { z as z13 } from "zod";
|
|
105
|
+
import { z as z17 } from "zod";
|
|
105
106
|
import { z as z14 } from "zod";
|
|
106
107
|
import { z as z15 } from "zod";
|
|
108
|
+
import { z as z16 } from "zod";
|
|
109
|
+
import { z as z18 } from "zod";
|
|
107
110
|
import { parse as yamlParse, stringify as yamlStringify } from "yaml";
|
|
111
|
+
import { z as z19 } from "zod";
|
|
112
|
+
import { z as z20 } from "zod";
|
|
113
|
+
import { z as z21 } from "zod";
|
|
114
|
+
import { z as z22 } from "zod";
|
|
115
|
+
import { z as z23 } from "zod";
|
|
116
|
+
import { parse as yamlParse2, stringify as yamlStringify2 } from "yaml";
|
|
117
|
+
import { z as z24 } from "zod";
|
|
118
|
+
import { z as z25 } from "zod";
|
|
108
119
|
var PixelSchema = z.number();
|
|
109
120
|
var PercentageSchema = z.string().regex(/^-?\d+(\.\d+)?%$/, {
|
|
110
121
|
message: 'Percentage must be a number followed by %, e.g. "50%"'
|
|
@@ -357,6 +368,19 @@ var VideoVisualSchema = z8.object({
|
|
|
357
368
|
muted: z8.boolean().optional(),
|
|
358
369
|
objectFit: z8.enum(["contain", "cover", "fill"]).optional()
|
|
359
370
|
});
|
|
371
|
+
var AudioVisualSchema = z8.object({
|
|
372
|
+
type: z8.literal("audio"),
|
|
373
|
+
assetId: z8.string().min(1, "assetId is required"),
|
|
374
|
+
src: z8.string().optional(),
|
|
375
|
+
startFrame: z8.number().int().min(0).optional(),
|
|
376
|
+
sourceOffset: z8.number().min(0).optional(),
|
|
377
|
+
sourceEnd: z8.number().positive().optional(),
|
|
378
|
+
playbackRate: z8.number().positive().optional(),
|
|
379
|
+
volume: z8.number().min(0).max(1).optional(),
|
|
380
|
+
muted: z8.boolean().optional(),
|
|
381
|
+
fadeIn: z8.number().min(0).optional(),
|
|
382
|
+
fadeOut: z8.number().min(0).optional()
|
|
383
|
+
});
|
|
360
384
|
var GroupVisualSchema = z8.object({
|
|
361
385
|
type: z8.literal("group")
|
|
362
386
|
});
|
|
@@ -371,6 +395,7 @@ var VisualSchema = z8.discriminatedUnion("type", [
|
|
|
371
395
|
TextVisualSchema,
|
|
372
396
|
ImageVisualSchema,
|
|
373
397
|
VideoVisualSchema,
|
|
398
|
+
AudioVisualSchema,
|
|
374
399
|
GroupVisualSchema,
|
|
375
400
|
RefVisualSchema
|
|
376
401
|
]);
|
|
@@ -452,13 +477,6 @@ var DeltaSchema = z9.object({
|
|
|
452
477
|
description: z9.string().optional(),
|
|
453
478
|
tags: z9.array(z9.string()).optional()
|
|
454
479
|
});
|
|
455
|
-
var AudioSchema = z10.object({
|
|
456
|
-
src: z10.string().min(1, "Audio src is required"),
|
|
457
|
-
offset: z10.number().min(0, "Audio offset must be non-negative").optional(),
|
|
458
|
-
volume: z10.number().min(0).max(1, "Audio volume must be 0\u20131").optional(),
|
|
459
|
-
loop: z10.boolean().optional(),
|
|
460
|
-
startFrame: z10.number().int().min(0, "Audio startFrame must be a non-negative integer").optional()
|
|
461
|
-
});
|
|
462
480
|
var StateTransitionConfigSchema = z10.object({
|
|
463
481
|
duration: z10.number().int().positive("Transition duration must be a positive integer (frames)"),
|
|
464
482
|
easing: EasingSchema.optional()
|
|
@@ -469,7 +487,6 @@ var StateSchema = z10.object({
|
|
|
469
487
|
parent: z10.string().optional(),
|
|
470
488
|
duration: z10.number().int().positive("State duration must be a positive integer (frames)"),
|
|
471
489
|
deltas: z10.array(DeltaSchema),
|
|
472
|
-
audio: AudioSchema.optional(),
|
|
473
490
|
transitions: z10.record(z10.string(), StateTransitionConfigSchema).optional()
|
|
474
491
|
});
|
|
475
492
|
var PresetDeltaSchema = z11.object({
|
|
@@ -509,61 +526,119 @@ var AssetSchema = z13.object({
|
|
|
509
526
|
height: z13.number().int().positive()
|
|
510
527
|
}).optional()
|
|
511
528
|
});
|
|
512
|
-
var
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
529
|
+
var TimelineClipSchema = z14.object({
|
|
530
|
+
layerId: z14.string().min(1, "TimelineClip.layerId is required"),
|
|
531
|
+
startFrame: z14.number().int().min(0, "startFrame must be a non-negative integer"),
|
|
532
|
+
endFrame: z14.number().int().min(0, "endFrame must be a non-negative integer"),
|
|
533
|
+
source: z14.string().min(1, "TimelineClip.source is required")
|
|
534
|
+
});
|
|
535
|
+
var TimelineTrackSchema = z14.object({
|
|
536
|
+
id: z14.string().min(1, "TimelineTrack.id is required"),
|
|
537
|
+
kind: z14.enum(["video", "audio"]),
|
|
538
|
+
clips: z14.array(TimelineClipSchema)
|
|
539
|
+
});
|
|
540
|
+
var TimelineSchema = z14.object({
|
|
541
|
+
fps: z14.number().int().positive("Timeline.fps must be a positive integer"),
|
|
542
|
+
totalFrames: z14.number().int().min(0, "Timeline.totalFrames must be a non-negative integer"),
|
|
543
|
+
tracks: z14.array(TimelineTrackSchema)
|
|
544
|
+
});
|
|
545
|
+
var SlideTransitionSchema = z15.object({
|
|
546
|
+
kind: z15.enum(["cut", "crossfade", "fade"]),
|
|
547
|
+
durationFrames: z15.number().int().min(0, "transition durationFrames must be a non-negative integer").optional(),
|
|
548
|
+
color: z15.string().optional()
|
|
549
|
+
});
|
|
550
|
+
var SlideRefSchema = z15.object({
|
|
551
|
+
src: z15.string().min(1, "SlideRef.src is required"),
|
|
552
|
+
duration: z15.number().int().min(0, "SlideRef.duration must be a non-negative integer (frames)").optional(),
|
|
553
|
+
transition: SlideTransitionSchema.optional(),
|
|
554
|
+
label: z15.string().optional()
|
|
555
|
+
});
|
|
556
|
+
var DesignRelationSchema = z16.enum(["derived-from", "sibling", "references"]);
|
|
557
|
+
var AtelierDesignRelationSchema = z16.object({
|
|
558
|
+
source: z16.string().min(1, "AtelierDesignRelation.source is required"),
|
|
559
|
+
relation: DesignRelationSchema,
|
|
560
|
+
note: z16.string().optional()
|
|
561
|
+
});
|
|
562
|
+
var DesignTypographySchema = z16.object({
|
|
563
|
+
headingFamily: z16.string().optional(),
|
|
564
|
+
bodyFamily: z16.string().optional(),
|
|
565
|
+
monoFamily: z16.string().optional(),
|
|
566
|
+
sizes: z16.record(z16.string(), z16.number().positive()).optional(),
|
|
567
|
+
weights: z16.record(z16.string(), z16.string()).optional()
|
|
568
|
+
});
|
|
569
|
+
var DesignMotionSchema = z16.object({
|
|
570
|
+
defaultEasing: z16.string().optional(),
|
|
571
|
+
defaultDuration: z16.number().int().positive().optional()
|
|
572
|
+
});
|
|
573
|
+
var AtelierDesignSchema = z16.object({
|
|
574
|
+
palette: z16.record(z16.string(), z16.string()).optional(),
|
|
575
|
+
typography: DesignTypographySchema.optional(),
|
|
576
|
+
motion: DesignMotionSchema.optional(),
|
|
577
|
+
relatedTo: z16.array(AtelierDesignRelationSchema).optional()
|
|
578
|
+
});
|
|
579
|
+
var CanvasSchema = z17.object({
|
|
580
|
+
width: z17.number().int().positive("Canvas width must be a positive integer"),
|
|
581
|
+
height: z17.number().int().positive("Canvas height must be a positive integer"),
|
|
582
|
+
fps: z17.number().int().positive("FPS must be a positive integer"),
|
|
583
|
+
background: z17.string().optional()
|
|
517
584
|
});
|
|
518
|
-
var
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
585
|
+
var ProjectKindSchema = z17.enum(["video", "image", "carousel"]);
|
|
586
|
+
var AtelierDocumentSchema = z17.object({
|
|
587
|
+
version: z17.string().min(1, "Version is required"),
|
|
588
|
+
name: z17.string().min(1, "Animation name is required"),
|
|
589
|
+
kind: ProjectKindSchema.optional(),
|
|
590
|
+
description: z17.string().optional(),
|
|
591
|
+
tags: z17.array(z17.string()).optional(),
|
|
523
592
|
canvas: CanvasSchema,
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
593
|
+
/** Per-file design / brand block (self-contained — no auto-merge). */
|
|
594
|
+
design: AtelierDesignSchema.optional(),
|
|
595
|
+
variables: z17.record(z17.string(), VariableSchema).optional(),
|
|
596
|
+
assets: z17.record(z17.string(), AssetSchema).optional(),
|
|
597
|
+
presets: z17.record(z17.string(), PresetSchema).optional(),
|
|
598
|
+
layers: z17.array(LayerSchema),
|
|
599
|
+
states: z17.record(z17.string(), StateSchema),
|
|
600
|
+
/** Derived timeline summary for video-kind projects. Optional. */
|
|
601
|
+
timeline: TimelineSchema.optional(),
|
|
602
|
+
/** Carousel slides — only meaningful when kind === "carousel". */
|
|
603
|
+
slides: z17.array(SlideRefSchema).optional()
|
|
529
604
|
});
|
|
530
|
-
var SilencePolicySchema =
|
|
531
|
-
noise:
|
|
532
|
-
min_silence:
|
|
533
|
-
default_padding_pre:
|
|
534
|
-
default_padding_post:
|
|
535
|
-
match_tolerance:
|
|
605
|
+
var SilencePolicySchema = z18.object({
|
|
606
|
+
noise: z18.string().optional(),
|
|
607
|
+
min_silence: z18.number().nonnegative().optional(),
|
|
608
|
+
default_padding_pre: z18.number().nonnegative().optional(),
|
|
609
|
+
default_padding_post: z18.number().nonnegative().optional(),
|
|
610
|
+
match_tolerance: z18.number().nonnegative().optional()
|
|
536
611
|
}).strict();
|
|
537
|
-
var CaptionStyleSchema =
|
|
538
|
-
font_family:
|
|
539
|
-
font_size:
|
|
540
|
-
font_weight:
|
|
541
|
-
text_align:
|
|
542
|
-
color:
|
|
543
|
-
y_ratio:
|
|
544
|
-
width_ratio:
|
|
545
|
-
fade_seconds:
|
|
612
|
+
var CaptionStyleSchema = z18.object({
|
|
613
|
+
font_family: z18.string().optional(),
|
|
614
|
+
font_size: z18.number().positive().optional(),
|
|
615
|
+
font_weight: z18.union([z18.literal("normal"), z18.literal("bold"), z18.number()]).optional(),
|
|
616
|
+
text_align: z18.enum(["left", "center", "right"]).optional(),
|
|
617
|
+
color: z18.string().optional(),
|
|
618
|
+
y_ratio: z18.number().min(0).max(1).optional(),
|
|
619
|
+
width_ratio: z18.number().min(0).max(1).optional(),
|
|
620
|
+
fade_seconds: z18.number().nonnegative().optional()
|
|
546
621
|
}).strict();
|
|
547
|
-
var CaptionGroupingSchema =
|
|
548
|
-
max_words:
|
|
549
|
-
pause_gap:
|
|
622
|
+
var CaptionGroupingSchema = z18.object({
|
|
623
|
+
max_words: z18.number().int().positive().optional(),
|
|
624
|
+
pause_gap: z18.number().nonnegative().optional()
|
|
550
625
|
}).strict();
|
|
551
|
-
var OverlayAnchorSchema =
|
|
626
|
+
var OverlayAnchorSchema = z18.enum([
|
|
552
627
|
"top-left",
|
|
553
628
|
"top-right",
|
|
554
629
|
"bottom-left",
|
|
555
630
|
"bottom-right"
|
|
556
631
|
]);
|
|
557
|
-
var OverlayTextStyleSchema =
|
|
558
|
-
font_family:
|
|
559
|
-
font_size:
|
|
560
|
-
font_weight:
|
|
561
|
-
color:
|
|
632
|
+
var OverlayTextStyleSchema = z18.object({
|
|
633
|
+
font_family: z18.string().optional(),
|
|
634
|
+
font_size: z18.number().positive().optional(),
|
|
635
|
+
font_weight: z18.union([z18.literal("normal"), z18.literal("bold"), z18.number()]).optional(),
|
|
636
|
+
color: z18.string().optional()
|
|
562
637
|
}).strict();
|
|
563
638
|
function validatePageNumberFormat(format, ctx) {
|
|
564
639
|
if (format.length === 0) {
|
|
565
640
|
ctx.addIssue({
|
|
566
|
-
code:
|
|
641
|
+
code: z18.ZodIssueCode.custom,
|
|
567
642
|
message: "format must be a non-empty string"
|
|
568
643
|
});
|
|
569
644
|
return;
|
|
@@ -572,7 +647,7 @@ function validatePageNumberFormat(format, ctx) {
|
|
|
572
647
|
const close = (format.match(/\}/g) ?? []).length;
|
|
573
648
|
if (open !== close) {
|
|
574
649
|
ctx.addIssue({
|
|
575
|
-
code:
|
|
650
|
+
code: z18.ZodIssueCode.custom,
|
|
576
651
|
message: `format has unbalanced braces (${open} '{' vs ${close} '}')`
|
|
577
652
|
});
|
|
578
653
|
return;
|
|
@@ -586,7 +661,7 @@ function validatePageNumberFormat(format, ctx) {
|
|
|
586
661
|
const inner = m[1];
|
|
587
662
|
if (!groupRule.test(inner)) {
|
|
588
663
|
ctx.addIssue({
|
|
589
|
-
code:
|
|
664
|
+
code: z18.ZodIssueCode.custom,
|
|
590
665
|
message: `format placeholder "{${inner}}" is not recognized \u2014 expected {current}, {total}, {current:0Nd}, or {total:0Nd}`
|
|
591
666
|
});
|
|
592
667
|
return;
|
|
@@ -596,44 +671,44 @@ function validatePageNumberFormat(format, ctx) {
|
|
|
596
671
|
}
|
|
597
672
|
if (!sawCurrent && !sawTotal) {
|
|
598
673
|
ctx.addIssue({
|
|
599
|
-
code:
|
|
674
|
+
code: z18.ZodIssueCode.custom,
|
|
600
675
|
message: "format must contain at least one {current} or {total} placeholder"
|
|
601
676
|
});
|
|
602
677
|
}
|
|
603
678
|
}
|
|
604
|
-
var OverlayHandleRuleSchema =
|
|
605
|
-
text:
|
|
679
|
+
var OverlayHandleRuleSchema = z18.object({
|
|
680
|
+
text: z18.string().min(1),
|
|
606
681
|
anchor: OverlayAnchorSchema,
|
|
607
|
-
margin:
|
|
682
|
+
margin: z18.number().nonnegative().optional(),
|
|
608
683
|
style: OverlayTextStyleSchema.optional()
|
|
609
684
|
}).strict();
|
|
610
|
-
var OverlayPageNumberRuleSchema =
|
|
611
|
-
format:
|
|
685
|
+
var OverlayPageNumberRuleSchema = z18.object({
|
|
686
|
+
format: z18.string().superRefine(validatePageNumberFormat),
|
|
612
687
|
anchor: OverlayAnchorSchema,
|
|
613
|
-
margin:
|
|
688
|
+
margin: z18.number().nonnegative().optional(),
|
|
614
689
|
style: OverlayTextStyleSchema.optional()
|
|
615
690
|
}).strict();
|
|
616
|
-
var OverlayRulesSchema =
|
|
691
|
+
var OverlayRulesSchema = z18.object({
|
|
617
692
|
handle: OverlayHandleRuleSchema.optional(),
|
|
618
693
|
page_number: OverlayPageNumberRuleSchema.optional()
|
|
619
694
|
}).strict();
|
|
620
|
-
var StudioRecipeSchema =
|
|
621
|
-
version:
|
|
622
|
-
name:
|
|
623
|
-
description:
|
|
624
|
-
author:
|
|
625
|
-
tags:
|
|
695
|
+
var StudioRecipeSchema = z18.object({
|
|
696
|
+
version: z18.string(),
|
|
697
|
+
name: z18.string(),
|
|
698
|
+
description: z18.string().optional(),
|
|
699
|
+
author: z18.string().optional(),
|
|
700
|
+
tags: z18.array(z18.string()).optional(),
|
|
626
701
|
silence_policy: SilencePolicySchema.optional(),
|
|
627
702
|
caption_style: CaptionStyleSchema.optional(),
|
|
628
703
|
caption_grouping: CaptionGroupingSchema.optional(),
|
|
629
704
|
// Phase 1.5 — first-class overlay rules
|
|
630
705
|
overlay_rules: OverlayRulesSchema.optional(),
|
|
631
706
|
// Reserved — Phase 3 (parse-opaque)
|
|
632
|
-
caption_highlight:
|
|
633
|
-
transition_kit:
|
|
634
|
-
palette:
|
|
635
|
-
audio_policy:
|
|
636
|
-
aspect_targets:
|
|
707
|
+
caption_highlight: z18.unknown().optional(),
|
|
708
|
+
transition_kit: z18.unknown().optional(),
|
|
709
|
+
palette: z18.unknown().optional(),
|
|
710
|
+
audio_policy: z18.unknown().optional(),
|
|
711
|
+
aspect_targets: z18.array(z18.unknown()).optional()
|
|
637
712
|
}).strict();
|
|
638
713
|
var RESERVED_RECIPE_FIELDS = [
|
|
639
714
|
"caption_highlight",
|
|
@@ -675,10 +750,10 @@ function parseAtelier(yamlString) {
|
|
|
675
750
|
let parsed;
|
|
676
751
|
try {
|
|
677
752
|
parsed = yamlParse(yamlString);
|
|
678
|
-
} catch (
|
|
753
|
+
} catch (err24) {
|
|
679
754
|
return {
|
|
680
755
|
success: false,
|
|
681
|
-
errors: [{ path: "(yaml)", message: `YAML parse error: ${
|
|
756
|
+
errors: [{ path: "(yaml)", message: `YAML parse error: ${err24.message}` }]
|
|
682
757
|
};
|
|
683
758
|
}
|
|
684
759
|
return validateDocument(parsed);
|
|
@@ -686,6 +761,290 @@ function parseAtelier(yamlString) {
|
|
|
686
761
|
function serializeAtelier(doc) {
|
|
687
762
|
return yamlStringify(doc, { indent: 2 });
|
|
688
763
|
}
|
|
764
|
+
var SlotKindSchema = z19.enum([
|
|
765
|
+
"image",
|
|
766
|
+
"video",
|
|
767
|
+
"audio",
|
|
768
|
+
"text",
|
|
769
|
+
"font",
|
|
770
|
+
"voice",
|
|
771
|
+
"logo"
|
|
772
|
+
]);
|
|
773
|
+
var SLOT_KINDS = SlotKindSchema.options;
|
|
774
|
+
function parseSlotRef(ref) {
|
|
775
|
+
if (typeof ref !== "string" || ref.length === 0) return null;
|
|
776
|
+
const segments = ref.split(".");
|
|
777
|
+
if (segments.length < 2) return null;
|
|
778
|
+
if (segments.some((s) => s.length === 0)) return null;
|
|
779
|
+
const [rawKind, category, ...rest] = segments;
|
|
780
|
+
const kindParsed = SlotKindSchema.safeParse(rawKind);
|
|
781
|
+
if (!kindParsed.success) return null;
|
|
782
|
+
const specifier = rest.join(".");
|
|
783
|
+
return { kind: kindParsed.data, category, specifier };
|
|
784
|
+
}
|
|
785
|
+
var SlotRefSchema = z19.string().superRefine((value, ctx) => {
|
|
786
|
+
const parsed = parseSlotRef(value);
|
|
787
|
+
if (parsed !== null) return;
|
|
788
|
+
const segments = value.split(".");
|
|
789
|
+
if (segments.length < 2) {
|
|
790
|
+
ctx.addIssue({
|
|
791
|
+
code: z19.ZodIssueCode.custom,
|
|
792
|
+
message: `slot reference "${value}" must have at least two segments (<kind>.<open-tail>)`
|
|
793
|
+
});
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
if (segments.some((s) => s.length === 0)) {
|
|
797
|
+
ctx.addIssue({
|
|
798
|
+
code: z19.ZodIssueCode.custom,
|
|
799
|
+
message: `slot reference "${value}" has an empty segment`
|
|
800
|
+
});
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
const kindOk = SlotKindSchema.safeParse(segments[0]).success;
|
|
804
|
+
if (!kindOk) {
|
|
805
|
+
ctx.addIssue({
|
|
806
|
+
code: z19.ZodIssueCode.custom,
|
|
807
|
+
message: `slot reference "${value}" has unknown kind "${segments[0]}" \u2014 expected one of ${SLOT_KINDS.join(", ")}`
|
|
808
|
+
});
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
ctx.addIssue({
|
|
812
|
+
code: z19.ZodIssueCode.custom,
|
|
813
|
+
message: `slot reference "${value}" is malformed`
|
|
814
|
+
});
|
|
815
|
+
});
|
|
816
|
+
var TechniqueLibrarySchema = z20.object({
|
|
817
|
+
version: z20.number().int().positive(),
|
|
818
|
+
techniques: z20.array(z20.string().min(1))
|
|
819
|
+
}).strict();
|
|
820
|
+
var DesignAudienceSchema = z21.object({
|
|
821
|
+
primary: z21.string().min(1),
|
|
822
|
+
secondary: z21.string().optional(),
|
|
823
|
+
platform: z21.array(z21.string().min(1)).min(1)
|
|
824
|
+
}).strict();
|
|
825
|
+
var DesignVoiceSchema = z21.object({
|
|
826
|
+
descriptors: z21.array(z21.string().min(1)).min(3).max(5),
|
|
827
|
+
references: z21.array(z21.string().min(1)).optional()
|
|
828
|
+
}).strict();
|
|
829
|
+
var PaletteRoleSchema = z21.enum([
|
|
830
|
+
"background",
|
|
831
|
+
"surface",
|
|
832
|
+
"primary",
|
|
833
|
+
"accent",
|
|
834
|
+
"text-on-dark",
|
|
835
|
+
"text-on-light"
|
|
836
|
+
]);
|
|
837
|
+
var PaletteEntrySchema = z21.object({
|
|
838
|
+
token: z21.string().min(1),
|
|
839
|
+
hex: z21.string().regex(/^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/, {
|
|
840
|
+
message: "hex must be a #RGB / #RRGGBB / #RRGGBBAA string"
|
|
841
|
+
}),
|
|
842
|
+
role: PaletteRoleSchema
|
|
843
|
+
}).strict();
|
|
844
|
+
var TypographyUsageSchema = z21.enum(["display", "body", "caption", "mono"]);
|
|
845
|
+
var TypographyEntrySchema = z21.object({
|
|
846
|
+
token: z21.string().min(1),
|
|
847
|
+
family: z21.string().min(1),
|
|
848
|
+
weights: z21.array(z21.number().int().min(1).max(1e3)).min(1),
|
|
849
|
+
usage: TypographyUsageSchema
|
|
850
|
+
}).strict();
|
|
851
|
+
var MotionRegisterSchema = z21.object({
|
|
852
|
+
tempo: z21.enum(["snappy", "steady", "calm", "languid"]),
|
|
853
|
+
easing_bias: z21.enum(["linear", "ease-out", "spring"]),
|
|
854
|
+
camera_bias: z21.enum(["static", "drift", "snap"]).optional()
|
|
855
|
+
}).strict();
|
|
856
|
+
var VisualRegisterSchema = z21.object({
|
|
857
|
+
palette: z21.array(PaletteEntrySchema).min(1),
|
|
858
|
+
typography: z21.array(TypographyEntrySchema).min(1),
|
|
859
|
+
motion_register: MotionRegisterSchema
|
|
860
|
+
}).strict();
|
|
861
|
+
var AspectRatioSchema = z21.enum(["9:16", "1:1", "16:9", "4:5"]);
|
|
862
|
+
var DesignConstraintsSchema = z21.object({
|
|
863
|
+
max_duration_seconds: z21.number().positive().optional(),
|
|
864
|
+
aspect_ratios: z21.array(AspectRatioSchema).min(1),
|
|
865
|
+
do_not: z21.array(z21.string().min(1))
|
|
866
|
+
}).strict();
|
|
867
|
+
var BrandLogoSchema = z21.object({
|
|
868
|
+
token: z21.string().min(1),
|
|
869
|
+
role: z21.enum(["primary", "mark", "wordmark"])
|
|
870
|
+
}).strict();
|
|
871
|
+
var BrandHandleSchema = z21.object({
|
|
872
|
+
platform: z21.string().min(1),
|
|
873
|
+
value: z21.string().min(1)
|
|
874
|
+
}).strict();
|
|
875
|
+
var BrandReferencesSchema = z21.object({
|
|
876
|
+
logos: z21.array(BrandLogoSchema).min(1),
|
|
877
|
+
handles: z21.array(BrandHandleSchema).optional(),
|
|
878
|
+
page_number_convention: z21.string().optional()
|
|
879
|
+
}).strict();
|
|
880
|
+
var DesignVarianceSchema = z21.object({
|
|
881
|
+
field: z21.string().min(1),
|
|
882
|
+
value: z21.unknown(),
|
|
883
|
+
reason: z21.string().min(1)
|
|
884
|
+
}).strict();
|
|
885
|
+
var DesignArtifactSchema = z21.object({
|
|
886
|
+
audience: DesignAudienceSchema,
|
|
887
|
+
voice: DesignVoiceSchema,
|
|
888
|
+
visual_register: VisualRegisterSchema,
|
|
889
|
+
constraints: DesignConstraintsSchema,
|
|
890
|
+
brand_references: BrandReferencesSchema,
|
|
891
|
+
variances: z21.array(DesignVarianceSchema).optional(),
|
|
892
|
+
/** Markdown body verbatim — attached by the parser, not part of frontmatter. */
|
|
893
|
+
body: z21.string()
|
|
894
|
+
}).strict();
|
|
895
|
+
var BEAT_ID_RE = /^[a-z][a-z0-9-]*(\.[a-z0-9][a-z0-9-]*)*$/;
|
|
896
|
+
var ScriptBeatIdSchema = z22.string().regex(BEAT_ID_RE, {
|
|
897
|
+
message: "beat id must be kebab-case (optionally dot-namespaced, e.g. `proof-1` or `b-roll.intro`)"
|
|
898
|
+
});
|
|
899
|
+
var NarratedBeatSchema = z22.object({
|
|
900
|
+
id: ScriptBeatIdSchema,
|
|
901
|
+
copy: z22.string().min(1),
|
|
902
|
+
intent: z22.string().min(1),
|
|
903
|
+
est_duration_s: z22.number().nonnegative(),
|
|
904
|
+
bind_to_transcript: z22.boolean().default(true)
|
|
905
|
+
}).strict();
|
|
906
|
+
var NonNarratedBeatSchema = z22.object({
|
|
907
|
+
id: ScriptBeatIdSchema,
|
|
908
|
+
copy: z22.string().min(1),
|
|
909
|
+
intent: z22.string().min(1),
|
|
910
|
+
est_duration_s: z22.number().nonnegative()
|
|
911
|
+
}).strict();
|
|
912
|
+
var NarratedBlocksSchema = z22.object({
|
|
913
|
+
hook: z22.array(NarratedBeatSchema).min(1),
|
|
914
|
+
story: z22.array(NarratedBeatSchema),
|
|
915
|
+
proof: z22.array(NarratedBeatSchema),
|
|
916
|
+
cta: z22.array(NarratedBeatSchema).min(1)
|
|
917
|
+
}).strict();
|
|
918
|
+
var NarratedScriptSchema = z22.object({
|
|
919
|
+
mode: z22.literal("narrated"),
|
|
920
|
+
target_duration_s: z22.number().positive(),
|
|
921
|
+
language: z22.string().min(1),
|
|
922
|
+
/** Optional slot reference for TTS voice (e.g. `voice.host.primary`). */
|
|
923
|
+
tts_voice: SlotRefSchema.optional(),
|
|
924
|
+
blocks: NarratedBlocksSchema,
|
|
925
|
+
/** Markdown body verbatim — attached by the parser when the body has stray prose. */
|
|
926
|
+
body: z22.string()
|
|
927
|
+
}).strict();
|
|
928
|
+
var CarouselScriptSchema = z22.object({
|
|
929
|
+
mode: z22.literal("carousel"),
|
|
930
|
+
target_duration_s: z22.number().positive(),
|
|
931
|
+
language: z22.string().min(1),
|
|
932
|
+
tts_voice: SlotRefSchema.optional(),
|
|
933
|
+
beats: z22.array(NonNarratedBeatSchema).min(1),
|
|
934
|
+
body: z22.string()
|
|
935
|
+
}).strict();
|
|
936
|
+
var TextOnlyScriptSchema = z22.object({
|
|
937
|
+
mode: z22.literal("text-only"),
|
|
938
|
+
target_duration_s: z22.number().positive(),
|
|
939
|
+
language: z22.string().min(1),
|
|
940
|
+
tts_voice: SlotRefSchema.optional(),
|
|
941
|
+
beats: z22.array(NonNarratedBeatSchema).min(1),
|
|
942
|
+
body: z22.string()
|
|
943
|
+
}).strict();
|
|
944
|
+
var ScriptArtifactSchema = z22.discriminatedUnion("mode", [
|
|
945
|
+
NarratedScriptSchema,
|
|
946
|
+
CarouselScriptSchema,
|
|
947
|
+
TextOnlyScriptSchema
|
|
948
|
+
]);
|
|
949
|
+
var BeatWindowSchema = z23.object({
|
|
950
|
+
start_s: z23.number().nonnegative(),
|
|
951
|
+
end_s: z23.number().nonnegative()
|
|
952
|
+
}).strict().refine((w) => w.end_s > w.start_s, {
|
|
953
|
+
message: "window.end_s must be > window.start_s"
|
|
954
|
+
});
|
|
955
|
+
var CameraSchema = z23.object({
|
|
956
|
+
kind: z23.enum(["static", "drift", "push", "pull", "whip"]),
|
|
957
|
+
subject: z23.string().min(1).optional()
|
|
958
|
+
}).strict();
|
|
959
|
+
var MoodSchema = z23.string().min(1);
|
|
960
|
+
var TransitionKindSchema = z23.enum(["cut", "dip-to-color", "whip", "crossfade"]);
|
|
961
|
+
var TransitionOutSchema = z23.object({
|
|
962
|
+
kind: TransitionKindSchema,
|
|
963
|
+
duration_s: z23.number().nonnegative().optional(),
|
|
964
|
+
color: z23.string().min(1).optional()
|
|
965
|
+
}).strict();
|
|
966
|
+
var SfxEntrySchema = z23.object({
|
|
967
|
+
slot: SlotRefSchema,
|
|
968
|
+
gain_db: z23.number()
|
|
969
|
+
}).strict();
|
|
970
|
+
var StoryboardBeatSchema = z23.object({
|
|
971
|
+
id: ScriptBeatIdSchema,
|
|
972
|
+
window: BeatWindowSchema,
|
|
973
|
+
mood: z23.array(MoodSchema).min(1),
|
|
974
|
+
camera: CameraSchema,
|
|
975
|
+
slots: z23.array(SlotRefSchema),
|
|
976
|
+
techniques: z23.array(z23.string().min(1)),
|
|
977
|
+
transition_out: TransitionOutSchema,
|
|
978
|
+
sfx: z23.array(SfxEntrySchema).optional()
|
|
979
|
+
}).strict();
|
|
980
|
+
var StoryboardAspectRatioSchema = z23.enum(["9:16", "1:1", "16:9", "4:5"]);
|
|
981
|
+
var StoryboardArtifactSchema = z23.object({
|
|
982
|
+
technique_library_version: z23.number().int().positive(),
|
|
983
|
+
aspect_ratio: StoryboardAspectRatioSchema,
|
|
984
|
+
beats: z23.array(StoryboardBeatSchema).min(1)
|
|
985
|
+
}).strict();
|
|
986
|
+
var WorkspaceManifestSchema = z24.object({
|
|
987
|
+
/** Manifest schema version. Pinned to '1.0' for the initial release. */
|
|
988
|
+
version: z24.literal("1.0"),
|
|
989
|
+
/** Display name; defaults to the workspace dir basename at create time. */
|
|
990
|
+
name: z24.string().min(1),
|
|
991
|
+
/** ISO 8601 timestamp set by the writer. */
|
|
992
|
+
created: z24.string().min(1),
|
|
993
|
+
/**
|
|
994
|
+
* Inherited by new projects created via `atelier init` inside this
|
|
995
|
+
* workspace. The creator pre-declares the autonomy posture once at the
|
|
996
|
+
* workspace level; per-project overrides still allowed via per-project
|
|
997
|
+
* `.atelier/learning-mode.yaml` (writes always happen at workspace level
|
|
998
|
+
* unless explicitly overridden).
|
|
999
|
+
*/
|
|
1000
|
+
default_mode: z24.enum(["ambient", "explicit"]),
|
|
1001
|
+
/**
|
|
1002
|
+
* Relative paths (single-segment subdir names) of known Projects inside
|
|
1003
|
+
* the workspace. Advisory only — on-disk `project.atelier` presence is
|
|
1004
|
+
* authoritative. Refresh via `listProjects(workspaceDir)`.
|
|
1005
|
+
*/
|
|
1006
|
+
projects: z24.array(z24.string().min(1)).optional(),
|
|
1007
|
+
/** Free-text creator description. Optional. */
|
|
1008
|
+
description: z24.string().optional()
|
|
1009
|
+
}).strict();
|
|
1010
|
+
var NonEmptyStringListSchema = z25.array(z25.string().min(1));
|
|
1011
|
+
var MediaNotesConstraintsSchema = z25.object({
|
|
1012
|
+
/**
|
|
1013
|
+
* Hard exclusions. Lux + Iris MUST refuse to bind this media to a slot if
|
|
1014
|
+
* any do_not rule applies (the rule is a free-text string the agent reads
|
|
1015
|
+
* with judgement — e.g. "never use in a CTA beat", "no client logos").
|
|
1016
|
+
*/
|
|
1017
|
+
do_not: NonEmptyStringListSchema.optional(),
|
|
1018
|
+
/**
|
|
1019
|
+
* Soft preferences — weight up candidacy when these conditions hold. Same
|
|
1020
|
+
* free-text shape as do_not but the polarity is positive ("good for hooks",
|
|
1021
|
+
* "prefer for warm tone").
|
|
1022
|
+
*/
|
|
1023
|
+
prefer_for: NonEmptyStringListSchema.optional()
|
|
1024
|
+
}).strict();
|
|
1025
|
+
var MediaNotesFrontmatterSchema = z25.object({
|
|
1026
|
+
/** Basename of the media file this notes file sits next to. */
|
|
1027
|
+
file: z25.string().min(1),
|
|
1028
|
+
/** ISO 8601 timestamp captured at ingest. */
|
|
1029
|
+
added: z25.string().min(1),
|
|
1030
|
+
/** User identifier (env $USER at ingest, "creator" fallback). */
|
|
1031
|
+
added_by: z25.string().min(1),
|
|
1032
|
+
/** Free-form labels. Empty by default; agents and creator both append. */
|
|
1033
|
+
tags: NonEmptyStringListSchema.default([]),
|
|
1034
|
+
/** Optional do_not / prefer_for object. Absent means "no rules yet." */
|
|
1035
|
+
constraints: MediaNotesConstraintsSchema.optional()
|
|
1036
|
+
}).strict();
|
|
1037
|
+
var MediaNotesSchema = z25.object({
|
|
1038
|
+
frontmatter: MediaNotesFrontmatterSchema,
|
|
1039
|
+
body: z25.string()
|
|
1040
|
+
}).strict();
|
|
1041
|
+
var MediaNotesAuthorSchema = z25.enum(["lux", "quill", "iris", "creator"]);
|
|
1042
|
+
var MEDIA_NOTES_HEADINGS = {
|
|
1043
|
+
lux: "## Lux notes (cinematographer)",
|
|
1044
|
+
quill: "## Quill notes (showrunner)",
|
|
1045
|
+
iris: "## Iris notes (composer)",
|
|
1046
|
+
creator: "## Creator notes"
|
|
1047
|
+
};
|
|
689
1048
|
|
|
690
1049
|
// ../mcp/src/tools/document.ts
|
|
691
1050
|
function getDoc(store, id) {
|
|
@@ -704,13 +1063,13 @@ function register(server, store) {
|
|
|
704
1063
|
"atelier_create",
|
|
705
1064
|
"Create a new Atelier animation document with canvas settings",
|
|
706
1065
|
{
|
|
707
|
-
name:
|
|
708
|
-
width:
|
|
709
|
-
height:
|
|
710
|
-
fps:
|
|
711
|
-
background:
|
|
712
|
-
description:
|
|
713
|
-
tags:
|
|
1066
|
+
name: z26.string().describe("Animation name"),
|
|
1067
|
+
width: z26.number().positive().describe("Canvas width in pixels"),
|
|
1068
|
+
height: z26.number().positive().describe("Canvas height in pixels"),
|
|
1069
|
+
fps: z26.number().positive().int().describe("Frames per second"),
|
|
1070
|
+
background: z26.string().optional().describe("Background color (hex string)"),
|
|
1071
|
+
description: z26.string().optional().describe("Animation description"),
|
|
1072
|
+
tags: z26.array(z26.string()).optional().describe("Tags for categorization")
|
|
714
1073
|
},
|
|
715
1074
|
{ readOnlyHint: false, destructiveHint: false },
|
|
716
1075
|
async ({ name, width, height, fps, background, description, tags }) => {
|
|
@@ -735,7 +1094,7 @@ function register(server, store) {
|
|
|
735
1094
|
"atelier_info",
|
|
736
1095
|
"Get summary information about an Atelier document",
|
|
737
1096
|
{
|
|
738
|
-
id:
|
|
1097
|
+
id: z26.string().describe("Document ID")
|
|
739
1098
|
},
|
|
740
1099
|
{ readOnlyHint: true, destructiveHint: false },
|
|
741
1100
|
async ({ id }) => {
|
|
@@ -763,8 +1122,8 @@ function register(server, store) {
|
|
|
763
1122
|
"atelier_load",
|
|
764
1123
|
"Load an Atelier document from a YAML string",
|
|
765
1124
|
{
|
|
766
|
-
id:
|
|
767
|
-
yaml:
|
|
1125
|
+
id: z26.string().optional().describe("Custom document ID (auto-generated if omitted)"),
|
|
1126
|
+
yaml: z26.string().describe("YAML string representing an Atelier document")
|
|
768
1127
|
},
|
|
769
1128
|
{ readOnlyHint: false, destructiveHint: false },
|
|
770
1129
|
async ({ id, yaml }) => {
|
|
@@ -780,7 +1139,7 @@ function register(server, store) {
|
|
|
780
1139
|
"atelier_export",
|
|
781
1140
|
"Export an Atelier document as a YAML string",
|
|
782
1141
|
{
|
|
783
|
-
id:
|
|
1142
|
+
id: z26.string().describe("Document ID")
|
|
784
1143
|
},
|
|
785
1144
|
{ readOnlyHint: true, destructiveHint: false },
|
|
786
1145
|
async ({ id }) => {
|
|
@@ -803,7 +1162,7 @@ function register(server, store) {
|
|
|
803
1162
|
}
|
|
804
1163
|
|
|
805
1164
|
// ../mcp/src/tools/layers.ts
|
|
806
|
-
import { z as
|
|
1165
|
+
import { z as z27 } from "zod";
|
|
807
1166
|
function getDoc2(store, id) {
|
|
808
1167
|
const doc = store.get(id);
|
|
809
1168
|
if (!doc) return { error: `Document "${id}" not found` };
|
|
@@ -815,69 +1174,79 @@ function ok2(data) {
|
|
|
815
1174
|
function err2(message) {
|
|
816
1175
|
return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
|
|
817
1176
|
}
|
|
818
|
-
var VisualInputSchema =
|
|
819
|
-
type:
|
|
1177
|
+
var VisualInputSchema = z27.object({
|
|
1178
|
+
type: z27.enum(["shape", "text", "image", "video", "audio", "group", "ref"]).describe("Visual type"),
|
|
820
1179
|
// shape visual fields
|
|
821
|
-
shape:
|
|
822
|
-
type:
|
|
823
|
-
cornerRadius:
|
|
824
|
-
points:
|
|
825
|
-
x:
|
|
826
|
-
y:
|
|
827
|
-
in:
|
|
828
|
-
out:
|
|
1180
|
+
shape: z27.object({
|
|
1181
|
+
type: z27.enum(["rect", "ellipse", "path"]),
|
|
1182
|
+
cornerRadius: z27.union([z27.number(), z27.tuple([z27.number(), z27.number(), z27.number(), z27.number()])]).optional(),
|
|
1183
|
+
points: z27.array(z27.object({
|
|
1184
|
+
x: z27.number(),
|
|
1185
|
+
y: z27.number(),
|
|
1186
|
+
in: z27.object({ x: z27.number(), y: z27.number() }).optional(),
|
|
1187
|
+
out: z27.object({ x: z27.number(), y: z27.number() }).optional()
|
|
829
1188
|
})).optional(),
|
|
830
|
-
closed:
|
|
1189
|
+
closed: z27.boolean().optional()
|
|
831
1190
|
}).optional(),
|
|
832
|
-
fill:
|
|
833
|
-
stroke:
|
|
1191
|
+
fill: z27.record(z27.unknown()).optional(),
|
|
1192
|
+
stroke: z27.record(z27.unknown()).optional(),
|
|
834
1193
|
// text visual fields
|
|
835
|
-
content:
|
|
836
|
-
style:
|
|
1194
|
+
content: z27.string().optional(),
|
|
1195
|
+
style: z27.record(z27.unknown()).optional(),
|
|
837
1196
|
// image visual fields
|
|
838
|
-
assetId:
|
|
839
|
-
sourceRect:
|
|
840
|
-
x:
|
|
841
|
-
y:
|
|
842
|
-
width:
|
|
843
|
-
height:
|
|
1197
|
+
assetId: z27.string().optional(),
|
|
1198
|
+
sourceRect: z27.object({
|
|
1199
|
+
x: z27.number(),
|
|
1200
|
+
y: z27.number(),
|
|
1201
|
+
width: z27.number().positive(),
|
|
1202
|
+
height: z27.number().positive()
|
|
844
1203
|
}).optional(),
|
|
845
|
-
spritesheet:
|
|
846
|
-
columns:
|
|
847
|
-
rows:
|
|
848
|
-
frameCount:
|
|
849
|
-
frameWidth:
|
|
850
|
-
frameHeight:
|
|
1204
|
+
spritesheet: z27.object({
|
|
1205
|
+
columns: z27.number().int().positive(),
|
|
1206
|
+
rows: z27.number().int().positive(),
|
|
1207
|
+
frameCount: z27.number().int().positive().optional(),
|
|
1208
|
+
frameWidth: z27.number().positive(),
|
|
1209
|
+
frameHeight: z27.number().positive()
|
|
851
1210
|
}).optional(),
|
|
852
|
-
frameIndex:
|
|
1211
|
+
frameIndex: z27.number().int().min(0).optional(),
|
|
853
1212
|
// ref visual fields
|
|
854
|
-
src:
|
|
855
|
-
state:
|
|
856
|
-
frame:
|
|
1213
|
+
src: z27.string().optional(),
|
|
1214
|
+
state: z27.string().optional(),
|
|
1215
|
+
frame: z27.number().int().min(0).optional(),
|
|
1216
|
+
// video / audio visual fields (v1.0+)
|
|
1217
|
+
startFrame: z27.number().int().min(0).optional().describe("Video/audio: composition frame when playback begins"),
|
|
1218
|
+
sourceOffset: z27.number().min(0).optional().describe("Video/audio: seconds into source to begin playback"),
|
|
1219
|
+
sourceEnd: z27.number().positive().optional().describe("Video/audio: seconds of source to stop at"),
|
|
1220
|
+
playbackRate: z27.number().positive().optional().describe("Video/audio: playback speed multiplier"),
|
|
1221
|
+
volume: z27.number().min(0).max(1).optional().describe("Video/audio: volume 0\u20131"),
|
|
1222
|
+
muted: z27.boolean().optional().describe("Video/audio: mute"),
|
|
1223
|
+
objectFit: z27.enum(["contain", "cover", "fill"]).optional().describe("Video: fit mode"),
|
|
1224
|
+
fadeIn: z27.number().min(0).optional().describe("Audio: fade-in duration in seconds"),
|
|
1225
|
+
fadeOut: z27.number().min(0).optional().describe("Audio: fade-out duration in seconds")
|
|
857
1226
|
}).describe("Visual content definition");
|
|
858
1227
|
function register2(server, store) {
|
|
859
1228
|
server.tool(
|
|
860
1229
|
"atelier_add_layer",
|
|
861
1230
|
"Add a new layer to an Atelier document",
|
|
862
1231
|
{
|
|
863
|
-
id:
|
|
864
|
-
layerId:
|
|
1232
|
+
id: z27.string().describe("Document ID"),
|
|
1233
|
+
layerId: z27.string().describe("Unique layer ID"),
|
|
865
1234
|
visual: VisualInputSchema.describe("Visual content definition"),
|
|
866
|
-
x:
|
|
867
|
-
y:
|
|
868
|
-
width:
|
|
869
|
-
height:
|
|
870
|
-
description:
|
|
871
|
-
tags:
|
|
872
|
-
opacity:
|
|
873
|
-
rotation:
|
|
874
|
-
parentId:
|
|
875
|
-
anchorPoint:
|
|
876
|
-
scale:
|
|
877
|
-
visible:
|
|
878
|
-
tint:
|
|
879
|
-
color:
|
|
880
|
-
amount:
|
|
1235
|
+
x: z27.union([z27.number(), z27.string()]).describe("X position (pixels or percentage)"),
|
|
1236
|
+
y: z27.union([z27.number(), z27.string()]).describe("Y position (pixels or percentage)"),
|
|
1237
|
+
width: z27.union([z27.number(), z27.string()]).describe("Width (pixels or percentage)"),
|
|
1238
|
+
height: z27.union([z27.number(), z27.string()]).describe("Height (pixels or percentage)"),
|
|
1239
|
+
description: z27.string().optional().describe("Layer description"),
|
|
1240
|
+
tags: z27.array(z27.string()).optional().describe("Tags"),
|
|
1241
|
+
opacity: z27.number().min(0).max(1).optional().describe("Opacity 0-1"),
|
|
1242
|
+
rotation: z27.number().optional().describe("Rotation in degrees"),
|
|
1243
|
+
parentId: z27.string().optional().describe("Parent layer ID for transform inheritance"),
|
|
1244
|
+
anchorPoint: z27.object({ x: z27.number(), y: z27.number() }).optional().describe("Anchor point (0-1 normalized)"),
|
|
1245
|
+
scale: z27.object({ x: z27.number(), y: z27.number() }).optional().describe("Scale factors"),
|
|
1246
|
+
visible: z27.boolean().optional().describe("Whether layer is visible"),
|
|
1247
|
+
tint: z27.object({
|
|
1248
|
+
color: z27.string().describe("Tint color (hex string)"),
|
|
1249
|
+
amount: z27.number().min(0).max(1).describe("Tint amount (0 = none, 1 = full)")
|
|
881
1250
|
}).optional().describe("Color tint overlay")
|
|
882
1251
|
},
|
|
883
1252
|
{ readOnlyHint: false, destructiveHint: false },
|
|
@@ -914,20 +1283,20 @@ function register2(server, store) {
|
|
|
914
1283
|
"atelier_edit_layer",
|
|
915
1284
|
"Edit properties of an existing layer",
|
|
916
1285
|
{
|
|
917
|
-
id:
|
|
918
|
-
layerId:
|
|
919
|
-
x:
|
|
920
|
-
y:
|
|
921
|
-
width:
|
|
922
|
-
height:
|
|
923
|
-
description:
|
|
924
|
-
tags:
|
|
925
|
-
opacity:
|
|
926
|
-
rotation:
|
|
927
|
-
parentId:
|
|
928
|
-
anchorPoint:
|
|
929
|
-
scale:
|
|
930
|
-
visible:
|
|
1286
|
+
id: z27.string().describe("Document ID"),
|
|
1287
|
+
layerId: z27.string().describe("Layer ID to edit"),
|
|
1288
|
+
x: z27.union([z27.number(), z27.string()]).optional().describe("New X position"),
|
|
1289
|
+
y: z27.union([z27.number(), z27.string()]).optional().describe("New Y position"),
|
|
1290
|
+
width: z27.union([z27.number(), z27.string()]).optional().describe("New width"),
|
|
1291
|
+
height: z27.union([z27.number(), z27.string()]).optional().describe("New height"),
|
|
1292
|
+
description: z27.string().optional().describe("New description"),
|
|
1293
|
+
tags: z27.array(z27.string()).optional().describe("New tags"),
|
|
1294
|
+
opacity: z27.number().min(0).max(1).optional().describe("New opacity"),
|
|
1295
|
+
rotation: z27.number().optional().describe("New rotation"),
|
|
1296
|
+
parentId: z27.string().nullable().optional().describe("New parent layer ID (null to clear)"),
|
|
1297
|
+
anchorPoint: z27.object({ x: z27.number(), y: z27.number() }).optional().describe("New anchor point"),
|
|
1298
|
+
scale: z27.object({ x: z27.number(), y: z27.number() }).optional().describe("New scale"),
|
|
1299
|
+
visible: z27.boolean().optional().describe("New visibility")
|
|
931
1300
|
},
|
|
932
1301
|
{ readOnlyHint: false, destructiveHint: false },
|
|
933
1302
|
async ({ id, layerId, x, y, width, height, description, tags, opacity, rotation, parentId, anchorPoint, scale, visible }) => {
|
|
@@ -962,8 +1331,8 @@ function register2(server, store) {
|
|
|
962
1331
|
"atelier_remove_layer",
|
|
963
1332
|
"Remove a layer from an Atelier document",
|
|
964
1333
|
{
|
|
965
|
-
id:
|
|
966
|
-
layerId:
|
|
1334
|
+
id: z27.string().describe("Document ID"),
|
|
1335
|
+
layerId: z27.string().describe("Layer ID to remove")
|
|
967
1336
|
},
|
|
968
1337
|
{ readOnlyHint: false, destructiveHint: true },
|
|
969
1338
|
async ({ id, layerId }) => {
|
|
@@ -991,7 +1360,7 @@ function register2(server, store) {
|
|
|
991
1360
|
"atelier_list_layers",
|
|
992
1361
|
"List all layers in an Atelier document",
|
|
993
1362
|
{
|
|
994
|
-
id:
|
|
1363
|
+
id: z27.string().describe("Document ID")
|
|
995
1364
|
},
|
|
996
1365
|
{ readOnlyHint: true, destructiveHint: false },
|
|
997
1366
|
async ({ id }) => {
|
|
@@ -1018,9 +1387,9 @@ function register2(server, store) {
|
|
|
1018
1387
|
"atelier_reorder",
|
|
1019
1388
|
"Move a layer to a new position in the layer stack",
|
|
1020
1389
|
{
|
|
1021
|
-
id:
|
|
1022
|
-
layerId:
|
|
1023
|
-
position:
|
|
1390
|
+
id: z27.string().describe("Document ID"),
|
|
1391
|
+
layerId: z27.string().describe("Layer ID to move"),
|
|
1392
|
+
position: z27.number().int().min(0).describe("Target position index (0-based)")
|
|
1024
1393
|
},
|
|
1025
1394
|
{ readOnlyHint: false, destructiveHint: false },
|
|
1026
1395
|
async ({ id, layerId, position }) => {
|
|
@@ -1038,7 +1407,7 @@ function register2(server, store) {
|
|
|
1038
1407
|
}
|
|
1039
1408
|
|
|
1040
1409
|
// ../mcp/src/tools/shapes.ts
|
|
1041
|
-
import { z as
|
|
1410
|
+
import { z as z28 } from "zod";
|
|
1042
1411
|
function getDoc3(store, id) {
|
|
1043
1412
|
const doc = store.get(id);
|
|
1044
1413
|
if (!doc) return { error: `Document "${id}" not found` };
|
|
@@ -1055,21 +1424,21 @@ function register3(server, store) {
|
|
|
1055
1424
|
"atelier_set_shape",
|
|
1056
1425
|
"Set the shape on a shape-type layer",
|
|
1057
1426
|
{
|
|
1058
|
-
id:
|
|
1059
|
-
layerId:
|
|
1060
|
-
shape:
|
|
1061
|
-
type:
|
|
1062
|
-
cornerRadius:
|
|
1063
|
-
|
|
1064
|
-
|
|
1427
|
+
id: z28.string().describe("Document ID"),
|
|
1428
|
+
layerId: z28.string().describe("Layer ID (must be a shape visual)"),
|
|
1429
|
+
shape: z28.object({
|
|
1430
|
+
type: z28.enum(["rect", "ellipse", "path"]).describe("Shape type"),
|
|
1431
|
+
cornerRadius: z28.union([
|
|
1432
|
+
z28.number(),
|
|
1433
|
+
z28.tuple([z28.number(), z28.number(), z28.number(), z28.number()])
|
|
1065
1434
|
]).optional().describe("Corner radius (rect only)"),
|
|
1066
|
-
points:
|
|
1067
|
-
x:
|
|
1068
|
-
y:
|
|
1069
|
-
in:
|
|
1070
|
-
out:
|
|
1435
|
+
points: z28.array(z28.object({
|
|
1436
|
+
x: z28.number(),
|
|
1437
|
+
y: z28.number(),
|
|
1438
|
+
in: z28.object({ x: z28.number(), y: z28.number() }).optional(),
|
|
1439
|
+
out: z28.object({ x: z28.number(), y: z28.number() }).optional()
|
|
1071
1440
|
})).optional().describe("Path points (path only)"),
|
|
1072
|
-
closed:
|
|
1441
|
+
closed: z28.boolean().optional().describe("Whether path is closed (path only)")
|
|
1073
1442
|
}).describe("Shape definition")
|
|
1074
1443
|
},
|
|
1075
1444
|
{ readOnlyHint: false, destructiveHint: false },
|
|
@@ -1088,20 +1457,20 @@ function register3(server, store) {
|
|
|
1088
1457
|
"atelier_set_fill",
|
|
1089
1458
|
"Set the fill on a shape-type layer",
|
|
1090
1459
|
{
|
|
1091
|
-
id:
|
|
1092
|
-
layerId:
|
|
1093
|
-
fill:
|
|
1094
|
-
type:
|
|
1095
|
-
color:
|
|
1096
|
-
angle:
|
|
1097
|
-
center:
|
|
1098
|
-
x:
|
|
1099
|
-
y:
|
|
1460
|
+
id: z28.string().describe("Document ID"),
|
|
1461
|
+
layerId: z28.string().describe("Layer ID (must be a shape visual)"),
|
|
1462
|
+
fill: z28.object({
|
|
1463
|
+
type: z28.enum(["solid", "linear-gradient", "radial-gradient"]).describe("Fill type"),
|
|
1464
|
+
color: z28.unknown().optional().describe("Color for solid fill (hex string or RGBA/HSLA object)"),
|
|
1465
|
+
angle: z28.number().optional().describe("Angle in degrees (linear-gradient only)"),
|
|
1466
|
+
center: z28.object({
|
|
1467
|
+
x: z28.union([z28.number(), z28.string()]),
|
|
1468
|
+
y: z28.union([z28.number(), z28.string()])
|
|
1100
1469
|
}).optional().describe("Center point (radial-gradient only)"),
|
|
1101
|
-
radius:
|
|
1102
|
-
stops:
|
|
1103
|
-
offset:
|
|
1104
|
-
color:
|
|
1470
|
+
radius: z28.union([z28.number(), z28.string()]).optional().describe("Radius (radial-gradient only)"),
|
|
1471
|
+
stops: z28.array(z28.object({
|
|
1472
|
+
offset: z28.number().min(0).max(1),
|
|
1473
|
+
color: z28.unknown()
|
|
1105
1474
|
})).optional().describe("Gradient stops")
|
|
1106
1475
|
}).describe("Fill definition")
|
|
1107
1476
|
},
|
|
@@ -1121,14 +1490,14 @@ function register3(server, store) {
|
|
|
1121
1490
|
"atelier_set_stroke",
|
|
1122
1491
|
"Set the stroke on a shape-type layer",
|
|
1123
1492
|
{
|
|
1124
|
-
id:
|
|
1125
|
-
layerId:
|
|
1126
|
-
stroke:
|
|
1127
|
-
color:
|
|
1128
|
-
width:
|
|
1129
|
-
dash:
|
|
1130
|
-
lineCap:
|
|
1131
|
-
lineJoin:
|
|
1493
|
+
id: z28.string().describe("Document ID"),
|
|
1494
|
+
layerId: z28.string().describe("Layer ID (must be a shape visual)"),
|
|
1495
|
+
stroke: z28.object({
|
|
1496
|
+
color: z28.unknown().describe("Stroke color (hex string or RGBA/HSLA object)"),
|
|
1497
|
+
width: z28.number().positive().describe("Stroke width in pixels"),
|
|
1498
|
+
dash: z28.array(z28.number()).optional().describe("Dash pattern"),
|
|
1499
|
+
lineCap: z28.enum(["butt", "round", "square"]).optional().describe("Line cap style"),
|
|
1500
|
+
lineJoin: z28.enum(["miter", "round", "bevel"]).optional().describe("Line join style")
|
|
1132
1501
|
}).describe("Stroke definition")
|
|
1133
1502
|
},
|
|
1134
1503
|
{ readOnlyHint: false, destructiveHint: false },
|
|
@@ -1146,7 +1515,7 @@ function register3(server, store) {
|
|
|
1146
1515
|
}
|
|
1147
1516
|
|
|
1148
1517
|
// ../mcp/src/tools/states.ts
|
|
1149
|
-
import { z as
|
|
1518
|
+
import { z as z29 } from "zod";
|
|
1150
1519
|
function getDoc4(store, id) {
|
|
1151
1520
|
const doc = store.get(id);
|
|
1152
1521
|
if (!doc) return { error: `Document "${id}" not found` };
|
|
@@ -1163,11 +1532,11 @@ function register4(server, store) {
|
|
|
1163
1532
|
"atelier_add_state",
|
|
1164
1533
|
"Add a named animation state to a document",
|
|
1165
1534
|
{
|
|
1166
|
-
id:
|
|
1167
|
-
stateName:
|
|
1168
|
-
duration:
|
|
1169
|
-
description:
|
|
1170
|
-
tags:
|
|
1535
|
+
id: z29.string().describe("Document ID"),
|
|
1536
|
+
stateName: z29.string().describe("State name (e.g. 'intro', 'hover', 'exit')"),
|
|
1537
|
+
duration: z29.number().positive().int().describe("Duration in frames"),
|
|
1538
|
+
description: z29.string().optional().describe("State description"),
|
|
1539
|
+
tags: z29.array(z29.string()).optional().describe("Tags")
|
|
1171
1540
|
},
|
|
1172
1541
|
{ readOnlyHint: false, destructiveHint: false },
|
|
1173
1542
|
async ({ id, stateName, duration, description, tags }) => {
|
|
@@ -1190,11 +1559,11 @@ function register4(server, store) {
|
|
|
1190
1559
|
"atelier_edit_state",
|
|
1191
1560
|
"Edit metadata of an existing animation state",
|
|
1192
1561
|
{
|
|
1193
|
-
id:
|
|
1194
|
-
stateName:
|
|
1195
|
-
duration:
|
|
1196
|
-
description:
|
|
1197
|
-
tags:
|
|
1562
|
+
id: z29.string().describe("Document ID"),
|
|
1563
|
+
stateName: z29.string().describe("State name to edit"),
|
|
1564
|
+
duration: z29.number().positive().int().optional().describe("New duration in frames"),
|
|
1565
|
+
description: z29.string().optional().describe("New description"),
|
|
1566
|
+
tags: z29.array(z29.string()).optional().describe("New tags")
|
|
1198
1567
|
},
|
|
1199
1568
|
{ readOnlyHint: false, destructiveHint: false },
|
|
1200
1569
|
async ({ id, stateName, duration, description, tags }) => {
|
|
@@ -1213,8 +1582,8 @@ function register4(server, store) {
|
|
|
1213
1582
|
"atelier_remove_state",
|
|
1214
1583
|
"Remove an animation state and all its deltas",
|
|
1215
1584
|
{
|
|
1216
|
-
id:
|
|
1217
|
-
stateName:
|
|
1585
|
+
id: z29.string().describe("Document ID"),
|
|
1586
|
+
stateName: z29.string().describe("State name to remove")
|
|
1218
1587
|
},
|
|
1219
1588
|
{ readOnlyHint: false, destructiveHint: true },
|
|
1220
1589
|
async ({ id, stateName }) => {
|
|
@@ -1231,7 +1600,7 @@ function register4(server, store) {
|
|
|
1231
1600
|
"atelier_list_states",
|
|
1232
1601
|
"List all animation states in a document",
|
|
1233
1602
|
{
|
|
1234
|
-
id:
|
|
1603
|
+
id: z29.string().describe("Document ID")
|
|
1235
1604
|
},
|
|
1236
1605
|
{ readOnlyHint: true, destructiveHint: false },
|
|
1237
1606
|
async ({ id }) => {
|
|
@@ -1251,7 +1620,7 @@ function register4(server, store) {
|
|
|
1251
1620
|
}
|
|
1252
1621
|
|
|
1253
1622
|
// ../mcp/src/tools/deltas.ts
|
|
1254
|
-
import { z as
|
|
1623
|
+
import { z as z30 } from "zod";
|
|
1255
1624
|
|
|
1256
1625
|
// ../math/dist/index.js
|
|
1257
1626
|
function linear(t) {
|
|
@@ -1796,12 +2165,15 @@ function resolveFrame(doc, stateName, frame, overrideDeltas) {
|
|
|
1796
2165
|
throw new Error(`State "${stateName}" not found in document "${doc.name}"`);
|
|
1797
2166
|
}
|
|
1798
2167
|
const deltasByLayerProperty = groupDeltas(overrideDeltas ?? state.deltas);
|
|
2168
|
+
const ancestorClipStartFrame = computeAncestorClipStartFrames(doc);
|
|
1799
2169
|
const resolvedLayers = doc.layers.map((layer) => {
|
|
1800
2170
|
const computedProperties = {};
|
|
1801
2171
|
const layerDeltas = deltasByLayerProperty.get(layer.id);
|
|
1802
2172
|
if (layerDeltas) {
|
|
2173
|
+
const offset = ancestorClipStartFrame.get(layer.id) ?? 0;
|
|
2174
|
+
const localFrame = frame - offset;
|
|
1803
2175
|
for (const [property, deltas] of layerDeltas) {
|
|
1804
|
-
const value = resolvePropertyAtFrame(deltas,
|
|
2176
|
+
const value = resolvePropertyAtFrame(deltas, localFrame);
|
|
1805
2177
|
if (value !== void 0) {
|
|
1806
2178
|
computedProperties[property] = value;
|
|
1807
2179
|
}
|
|
@@ -1822,6 +2194,26 @@ function resolveFrame(doc, stateName, frame, overrideDeltas) {
|
|
|
1822
2194
|
});
|
|
1823
2195
|
return { frame, stateName, layers: resolvedLayers };
|
|
1824
2196
|
}
|
|
2197
|
+
function computeAncestorClipStartFrames(doc) {
|
|
2198
|
+
const byId = /* @__PURE__ */ new Map();
|
|
2199
|
+
for (const l of doc.layers) byId.set(l.id, l);
|
|
2200
|
+
const result = /* @__PURE__ */ new Map();
|
|
2201
|
+
for (const layer of doc.layers) {
|
|
2202
|
+
let cursor = byId.get(layer.parentId ?? "");
|
|
2203
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2204
|
+
while (cursor) {
|
|
2205
|
+
if (visited.has(cursor.id)) break;
|
|
2206
|
+
visited.add(cursor.id);
|
|
2207
|
+
if (cursor.visual.type === "video") {
|
|
2208
|
+
const v = cursor.visual;
|
|
2209
|
+
result.set(layer.id, v.startFrame ?? 0);
|
|
2210
|
+
break;
|
|
2211
|
+
}
|
|
2212
|
+
cursor = byId.get(cursor.parentId ?? "");
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
return result;
|
|
2216
|
+
}
|
|
1825
2217
|
function groupDeltas(deltas) {
|
|
1826
2218
|
const map = /* @__PURE__ */ new Map();
|
|
1827
2219
|
for (const delta of deltas) {
|
|
@@ -1990,17 +2382,17 @@ function register5(server, store) {
|
|
|
1990
2382
|
"atelier_add_delta",
|
|
1991
2383
|
"Add an animation delta (keyframe transition) to a state",
|
|
1992
2384
|
{
|
|
1993
|
-
id:
|
|
1994
|
-
stateName:
|
|
1995
|
-
layer:
|
|
2385
|
+
id: z30.string().describe("Document ID"),
|
|
2386
|
+
stateName: z30.string().describe("State name"),
|
|
2387
|
+
layer: z30.string().describe("Target layer ID"),
|
|
1996
2388
|
property: AnimatablePropertyEnum.describe("Property to animate"),
|
|
1997
|
-
range:
|
|
1998
|
-
from:
|
|
1999
|
-
to:
|
|
2389
|
+
range: z30.tuple([z30.number().int().min(0), z30.number().int().min(0)]).describe("Frame range [start, end] inclusive"),
|
|
2390
|
+
from: z30.unknown().describe("Starting value"),
|
|
2391
|
+
to: z30.unknown().describe("Ending value"),
|
|
2000
2392
|
easing: EasingInputSchema.optional().describe("Easing function"),
|
|
2001
|
-
description:
|
|
2002
|
-
tags:
|
|
2003
|
-
deltaId:
|
|
2393
|
+
description: z30.string().optional().describe("Delta description"),
|
|
2394
|
+
tags: z30.array(z30.string()).optional().describe("Tags"),
|
|
2395
|
+
deltaId: z30.string().optional().describe("Custom delta ID")
|
|
2004
2396
|
},
|
|
2005
2397
|
{ readOnlyHint: false, destructiveHint: false },
|
|
2006
2398
|
async ({ id, stateName, layer, property, range, from, to, easing, description, tags, deltaId }) => {
|
|
@@ -2038,17 +2430,17 @@ function register5(server, store) {
|
|
|
2038
2430
|
"atelier_edit_delta",
|
|
2039
2431
|
"Edit an existing delta by index within a state",
|
|
2040
2432
|
{
|
|
2041
|
-
id:
|
|
2042
|
-
stateName:
|
|
2043
|
-
deltaIndex:
|
|
2044
|
-
layer:
|
|
2433
|
+
id: z30.string().describe("Document ID"),
|
|
2434
|
+
stateName: z30.string().describe("State name"),
|
|
2435
|
+
deltaIndex: z30.number().int().min(0).describe("Delta index within the state"),
|
|
2436
|
+
layer: z30.string().optional().describe("New target layer ID"),
|
|
2045
2437
|
property: AnimatablePropertyEnum.optional().describe("New property to animate"),
|
|
2046
|
-
range:
|
|
2047
|
-
from:
|
|
2048
|
-
to:
|
|
2438
|
+
range: z30.tuple([z30.number().int().min(0), z30.number().int().min(0)]).optional().describe("New frame range [start, end]"),
|
|
2439
|
+
from: z30.unknown().optional().describe("New starting value"),
|
|
2440
|
+
to: z30.unknown().optional().describe("New ending value"),
|
|
2049
2441
|
easing: EasingInputSchema.optional().describe("New easing function"),
|
|
2050
|
-
description:
|
|
2051
|
-
tags:
|
|
2442
|
+
description: z30.string().optional().describe("New description"),
|
|
2443
|
+
tags: z30.array(z30.string()).optional().describe("New tags")
|
|
2052
2444
|
},
|
|
2053
2445
|
{ readOnlyHint: false, destructiveHint: false },
|
|
2054
2446
|
async ({ id, stateName, deltaIndex, layer, property, range, from, to, easing, description, tags }) => {
|
|
@@ -2087,9 +2479,9 @@ function register5(server, store) {
|
|
|
2087
2479
|
"atelier_remove_delta",
|
|
2088
2480
|
"Remove a delta by index from a state",
|
|
2089
2481
|
{
|
|
2090
|
-
id:
|
|
2091
|
-
stateName:
|
|
2092
|
-
deltaIndex:
|
|
2482
|
+
id: z30.string().describe("Document ID"),
|
|
2483
|
+
stateName: z30.string().describe("State name"),
|
|
2484
|
+
deltaIndex: z30.number().int().min(0).describe("Delta index to remove")
|
|
2093
2485
|
},
|
|
2094
2486
|
{ readOnlyHint: false, destructiveHint: true },
|
|
2095
2487
|
async ({ id, stateName, deltaIndex }) => {
|
|
@@ -2117,12 +2509,12 @@ function register5(server, store) {
|
|
|
2117
2509
|
"atelier_apply_preset",
|
|
2118
2510
|
"Apply a preset to a layer, expanding it into concrete deltas",
|
|
2119
2511
|
{
|
|
2120
|
-
id:
|
|
2121
|
-
stateName:
|
|
2122
|
-
presetName:
|
|
2123
|
-
layerId:
|
|
2124
|
-
startFrame:
|
|
2125
|
-
duration:
|
|
2512
|
+
id: z30.string().describe("Document ID"),
|
|
2513
|
+
stateName: z30.string().describe("State name to add deltas to"),
|
|
2514
|
+
presetName: z30.string().describe("Preset name defined in the document"),
|
|
2515
|
+
layerId: z30.string().describe("Target layer ID"),
|
|
2516
|
+
startFrame: z30.number().int().min(0).optional().describe("Start frame (default: 0)"),
|
|
2517
|
+
duration: z30.number().positive().int().optional().describe("Duration for preset (default: state duration)")
|
|
2126
2518
|
},
|
|
2127
2519
|
{ readOnlyHint: false, destructiveHint: false },
|
|
2128
2520
|
async ({ id, stateName, presetName, layerId, startFrame, duration }) => {
|
|
@@ -2165,7 +2557,7 @@ ${errors.join("\n")}`);
|
|
|
2165
2557
|
}
|
|
2166
2558
|
|
|
2167
2559
|
// ../mcp/src/tools/presets.ts
|
|
2168
|
-
import { z as
|
|
2560
|
+
import { z as z31 } from "zod";
|
|
2169
2561
|
function getDoc6(store, id) {
|
|
2170
2562
|
const doc = store.get(id);
|
|
2171
2563
|
if (!doc) return { error: `Document "${id}" not found` };
|
|
@@ -2179,11 +2571,11 @@ function err6(message) {
|
|
|
2179
2571
|
}
|
|
2180
2572
|
var AnimatablePropertyEnum2 = AnimatablePropertySchema;
|
|
2181
2573
|
var EasingInputSchema2 = EasingSchema;
|
|
2182
|
-
var PresetDeltaSchema2 =
|
|
2574
|
+
var PresetDeltaSchema2 = z31.object({
|
|
2183
2575
|
property: AnimatablePropertyEnum2.describe("Animatable property"),
|
|
2184
|
-
offset:
|
|
2185
|
-
from:
|
|
2186
|
-
to:
|
|
2576
|
+
offset: z31.tuple([z31.number().int().min(0), z31.number().int().min(0)]).optional().describe("Relative frame offset [start, end]"),
|
|
2577
|
+
from: z31.unknown().describe("Starting value"),
|
|
2578
|
+
to: z31.unknown().describe("Ending value"),
|
|
2187
2579
|
easing: EasingInputSchema2.optional().describe("Easing function")
|
|
2188
2580
|
});
|
|
2189
2581
|
function register6(server, store) {
|
|
@@ -2191,11 +2583,11 @@ function register6(server, store) {
|
|
|
2191
2583
|
"atelier_define_preset",
|
|
2192
2584
|
"Define a reusable animation preset on a document",
|
|
2193
2585
|
{
|
|
2194
|
-
id:
|
|
2195
|
-
presetName:
|
|
2196
|
-
description:
|
|
2197
|
-
tags:
|
|
2198
|
-
deltas:
|
|
2586
|
+
id: z31.string().describe("Document ID"),
|
|
2587
|
+
presetName: z31.string().describe("Preset name"),
|
|
2588
|
+
description: z31.string().optional().describe("Preset description"),
|
|
2589
|
+
tags: z31.array(z31.string()).optional().describe("Tags"),
|
|
2590
|
+
deltas: z31.array(PresetDeltaSchema2).min(1).describe("Array of preset delta definitions")
|
|
2199
2591
|
},
|
|
2200
2592
|
{ readOnlyHint: false, destructiveHint: false },
|
|
2201
2593
|
async ({ id, presetName, description, tags, deltas }) => {
|
|
@@ -2216,7 +2608,7 @@ function register6(server, store) {
|
|
|
2216
2608
|
"atelier_list_presets",
|
|
2217
2609
|
"List all presets defined on a document",
|
|
2218
2610
|
{
|
|
2219
|
-
id:
|
|
2611
|
+
id: z31.string().describe("Document ID")
|
|
2220
2612
|
},
|
|
2221
2613
|
{ readOnlyHint: true, destructiveHint: false },
|
|
2222
2614
|
async ({ id }) => {
|
|
@@ -2239,7 +2631,7 @@ function register6(server, store) {
|
|
|
2239
2631
|
}
|
|
2240
2632
|
|
|
2241
2633
|
// ../mcp/src/tools/preview.ts
|
|
2242
|
-
import { z as
|
|
2634
|
+
import { z as z32 } from "zod";
|
|
2243
2635
|
function getDoc7(store, id) {
|
|
2244
2636
|
const doc = store.get(id);
|
|
2245
2637
|
if (!doc) return { error: `Document "${id}" not found` };
|
|
@@ -2256,7 +2648,7 @@ function register7(server, store) {
|
|
|
2256
2648
|
"atelier_validate",
|
|
2257
2649
|
"Validate an Atelier document for schema correctness and delta overlaps",
|
|
2258
2650
|
{
|
|
2259
|
-
id:
|
|
2651
|
+
id: z32.string().describe("Document ID")
|
|
2260
2652
|
},
|
|
2261
2653
|
{ readOnlyHint: true, destructiveHint: false },
|
|
2262
2654
|
async ({ id }) => {
|
|
@@ -2285,9 +2677,9 @@ function register7(server, store) {
|
|
|
2285
2677
|
"atelier_preview",
|
|
2286
2678
|
"Preview the resolved state of all layers at a specific frame",
|
|
2287
2679
|
{
|
|
2288
|
-
id:
|
|
2289
|
-
stateName:
|
|
2290
|
-
frame:
|
|
2680
|
+
id: z32.string().describe("Document ID"),
|
|
2681
|
+
stateName: z32.string().describe("State name to preview"),
|
|
2682
|
+
frame: z32.number().int().min(0).describe("Frame number to resolve")
|
|
2291
2683
|
},
|
|
2292
2684
|
{ readOnlyHint: true, destructiveHint: false },
|
|
2293
2685
|
async ({ id, stateName, frame }) => {
|
|
@@ -2322,7 +2714,7 @@ function register7(server, store) {
|
|
|
2322
2714
|
}
|
|
2323
2715
|
|
|
2324
2716
|
// ../mcp/src/tools/templates.ts
|
|
2325
|
-
import { z as
|
|
2717
|
+
import { z as z33 } from "zod";
|
|
2326
2718
|
function getDoc8(store, id) {
|
|
2327
2719
|
const doc = store.get(id);
|
|
2328
2720
|
if (!doc) return { error: `Document "${id}" not found` };
|
|
@@ -2339,8 +2731,8 @@ function register8(server, store) {
|
|
|
2339
2731
|
"atelier_instantiate_template",
|
|
2340
2732
|
"Instantiate a template document with variable bindings. Creates a new document in the store.",
|
|
2341
2733
|
{
|
|
2342
|
-
id:
|
|
2343
|
-
bindings:
|
|
2734
|
+
id: z33.string().describe("Template document ID"),
|
|
2735
|
+
bindings: z33.record(z33.unknown()).describe("Variable bindings: { variableName: value }")
|
|
2344
2736
|
},
|
|
2345
2737
|
{ readOnlyHint: false, destructiveHint: false },
|
|
2346
2738
|
async ({ id, bindings }) => {
|
|
@@ -2363,7 +2755,7 @@ function register8(server, store) {
|
|
|
2363
2755
|
"atelier_find_variables",
|
|
2364
2756
|
"Scan a document for {{variableName}} patterns. Returns the list of variable references found.",
|
|
2365
2757
|
{
|
|
2366
|
-
id:
|
|
2758
|
+
id: z33.string().describe("Document ID")
|
|
2367
2759
|
},
|
|
2368
2760
|
{ readOnlyHint: true, destructiveHint: false },
|
|
2369
2761
|
async ({ id }) => {
|
|
@@ -2383,7 +2775,7 @@ function register8(server, store) {
|
|
|
2383
2775
|
}
|
|
2384
2776
|
|
|
2385
2777
|
// ../mcp/src/tools/layer-effects.ts
|
|
2386
|
-
import { z as
|
|
2778
|
+
import { z as z34 } from "zod";
|
|
2387
2779
|
function getDoc9(store, id) {
|
|
2388
2780
|
const doc = store.get(id);
|
|
2389
2781
|
if (!doc) return { error: `Document "${id}" not found` };
|
|
@@ -2400,9 +2792,9 @@ function register9(server, store) {
|
|
|
2400
2792
|
"atelier_set_blend_mode",
|
|
2401
2793
|
"Set or clear the blend mode on a layer",
|
|
2402
2794
|
{
|
|
2403
|
-
id:
|
|
2404
|
-
layerId:
|
|
2405
|
-
blendMode:
|
|
2795
|
+
id: z34.string().describe("Document ID"),
|
|
2796
|
+
layerId: z34.string().describe("Layer ID"),
|
|
2797
|
+
blendMode: z34.enum([
|
|
2406
2798
|
"normal",
|
|
2407
2799
|
"multiply",
|
|
2408
2800
|
"screen",
|
|
@@ -2440,13 +2832,13 @@ function register9(server, store) {
|
|
|
2440
2832
|
"atelier_set_shadow",
|
|
2441
2833
|
"Set or remove a drop shadow on a layer",
|
|
2442
2834
|
{
|
|
2443
|
-
id:
|
|
2444
|
-
layerId:
|
|
2445
|
-
shadow:
|
|
2446
|
-
color:
|
|
2447
|
-
blur:
|
|
2448
|
-
offsetX:
|
|
2449
|
-
offsetY:
|
|
2835
|
+
id: z34.string().describe("Document ID"),
|
|
2836
|
+
layerId: z34.string().describe("Layer ID"),
|
|
2837
|
+
shadow: z34.object({
|
|
2838
|
+
color: z34.unknown().describe("Shadow color (hex string or RGBA/HSLA object)"),
|
|
2839
|
+
blur: z34.number().min(0).describe("Blur radius in pixels"),
|
|
2840
|
+
offsetX: z34.number().optional().describe("Horizontal offset in pixels"),
|
|
2841
|
+
offsetY: z34.number().optional().describe("Vertical offset in pixels")
|
|
2450
2842
|
}).nullable().describe("Shadow definition, or null to remove")
|
|
2451
2843
|
},
|
|
2452
2844
|
{ readOnlyHint: false, destructiveHint: false },
|
|
@@ -2468,11 +2860,11 @@ function register9(server, store) {
|
|
|
2468
2860
|
"atelier_set_tint",
|
|
2469
2861
|
"Set or remove a color tint on a layer (works on any visual type)",
|
|
2470
2862
|
{
|
|
2471
|
-
id:
|
|
2472
|
-
layerId:
|
|
2473
|
-
tint:
|
|
2474
|
-
color:
|
|
2475
|
-
amount:
|
|
2863
|
+
id: z34.string().describe("Document ID"),
|
|
2864
|
+
layerId: z34.string().describe("Layer ID"),
|
|
2865
|
+
tint: z34.object({
|
|
2866
|
+
color: z34.string().describe("Tint color (hex string)"),
|
|
2867
|
+
amount: z34.number().min(0).max(1).describe("Tint amount (0 = none, 1 = full)")
|
|
2476
2868
|
}).nullable().describe("Tint definition, or null to remove")
|
|
2477
2869
|
},
|
|
2478
2870
|
{ readOnlyHint: false, destructiveHint: false },
|
|
@@ -2494,18 +2886,18 @@ function register9(server, store) {
|
|
|
2494
2886
|
"atelier_set_motion_path",
|
|
2495
2887
|
"Set or remove a motion path on a layer for path-based position animation",
|
|
2496
2888
|
{
|
|
2497
|
-
id:
|
|
2498
|
-
layerId:
|
|
2499
|
-
motionPath:
|
|
2500
|
-
points:
|
|
2501
|
-
x:
|
|
2502
|
-
y:
|
|
2503
|
-
in:
|
|
2504
|
-
out:
|
|
2889
|
+
id: z34.string().describe("Document ID"),
|
|
2890
|
+
layerId: z34.string().describe("Layer ID"),
|
|
2891
|
+
motionPath: z34.object({
|
|
2892
|
+
points: z34.array(z34.object({
|
|
2893
|
+
x: z34.number(),
|
|
2894
|
+
y: z34.number(),
|
|
2895
|
+
in: z34.object({ x: z34.number(), y: z34.number() }).optional(),
|
|
2896
|
+
out: z34.object({ x: z34.number(), y: z34.number() }).optional()
|
|
2505
2897
|
})).min(2).describe("Path points (minimum 2)"),
|
|
2506
|
-
closed:
|
|
2507
|
-
autoRotate:
|
|
2508
|
-
autoRotateOffset:
|
|
2898
|
+
closed: z34.boolean().optional().describe("Whether the path is closed"),
|
|
2899
|
+
autoRotate: z34.boolean().optional().describe("Auto-rotate layer to follow path tangent"),
|
|
2900
|
+
autoRotateOffset: z34.number().optional().describe("Rotation offset in degrees when autoRotate is enabled")
|
|
2509
2901
|
}).nullable().describe("Motion path definition, or null to remove")
|
|
2510
2902
|
},
|
|
2511
2903
|
{ readOnlyHint: false, destructiveHint: false },
|
|
@@ -2527,21 +2919,21 @@ function register9(server, store) {
|
|
|
2527
2919
|
"atelier_set_clip_path",
|
|
2528
2920
|
"Set or remove a clip path on a layer to restrict rendering to within a shape",
|
|
2529
2921
|
{
|
|
2530
|
-
id:
|
|
2531
|
-
layerId:
|
|
2532
|
-
clipPath:
|
|
2533
|
-
type:
|
|
2534
|
-
cornerRadius:
|
|
2535
|
-
|
|
2536
|
-
|
|
2922
|
+
id: z34.string().describe("Document ID"),
|
|
2923
|
+
layerId: z34.string().describe("Layer ID"),
|
|
2924
|
+
clipPath: z34.object({
|
|
2925
|
+
type: z34.enum(["rect", "ellipse", "path"]).describe("Clip shape type"),
|
|
2926
|
+
cornerRadius: z34.union([
|
|
2927
|
+
z34.number(),
|
|
2928
|
+
z34.tuple([z34.number(), z34.number(), z34.number(), z34.number()])
|
|
2537
2929
|
]).optional().describe("Corner radius (rect only)"),
|
|
2538
|
-
points:
|
|
2539
|
-
x:
|
|
2540
|
-
y:
|
|
2541
|
-
in:
|
|
2542
|
-
out:
|
|
2930
|
+
points: z34.array(z34.object({
|
|
2931
|
+
x: z34.number(),
|
|
2932
|
+
y: z34.number(),
|
|
2933
|
+
in: z34.object({ x: z34.number(), y: z34.number() }).optional(),
|
|
2934
|
+
out: z34.object({ x: z34.number(), y: z34.number() }).optional()
|
|
2543
2935
|
})).optional().describe("Path points (path only)"),
|
|
2544
|
-
closed:
|
|
2936
|
+
closed: z34.boolean().optional().describe("Whether path is closed (path only)")
|
|
2545
2937
|
}).nullable().describe("Clip path shape, or null to remove")
|
|
2546
2938
|
},
|
|
2547
2939
|
{ readOnlyHint: false, destructiveHint: false },
|
|
@@ -2563,45 +2955,45 @@ function register9(server, store) {
|
|
|
2563
2955
|
"atelier_edit_visual",
|
|
2564
2956
|
"Edit visual content of an existing layer (text content/style, image src/assetId, shape/fill/stroke)",
|
|
2565
2957
|
{
|
|
2566
|
-
id:
|
|
2567
|
-
layerId:
|
|
2958
|
+
id: z34.string().describe("Document ID"),
|
|
2959
|
+
layerId: z34.string().describe("Layer ID"),
|
|
2568
2960
|
// Text fields
|
|
2569
|
-
content:
|
|
2570
|
-
style:
|
|
2961
|
+
content: z34.string().optional().describe("New text content (text layers only)"),
|
|
2962
|
+
style: z34.record(z34.unknown()).optional().describe("Partial style update \u2014 only provided keys are overwritten (text layers only)"),
|
|
2571
2963
|
// Image fields
|
|
2572
|
-
src:
|
|
2573
|
-
assetId:
|
|
2574
|
-
sourceRect:
|
|
2575
|
-
x:
|
|
2576
|
-
y:
|
|
2577
|
-
width:
|
|
2578
|
-
height:
|
|
2964
|
+
src: z34.string().optional().describe("Image source URL (image layers only)"),
|
|
2965
|
+
assetId: z34.string().optional().describe("Asset ID reference (image layers only)"),
|
|
2966
|
+
sourceRect: z34.object({
|
|
2967
|
+
x: z34.number(),
|
|
2968
|
+
y: z34.number(),
|
|
2969
|
+
width: z34.number().positive(),
|
|
2970
|
+
height: z34.number().positive()
|
|
2579
2971
|
}).nullable().optional().describe("Source rectangle crop (image layers only). Null to remove."),
|
|
2580
|
-
spritesheet:
|
|
2581
|
-
columns:
|
|
2582
|
-
rows:
|
|
2583
|
-
frameCount:
|
|
2584
|
-
frameWidth:
|
|
2585
|
-
frameHeight:
|
|
2972
|
+
spritesheet: z34.object({
|
|
2973
|
+
columns: z34.number().int().positive(),
|
|
2974
|
+
rows: z34.number().int().positive(),
|
|
2975
|
+
frameCount: z34.number().int().positive().optional(),
|
|
2976
|
+
frameWidth: z34.number().positive(),
|
|
2977
|
+
frameHeight: z34.number().positive()
|
|
2586
2978
|
}).nullable().optional().describe("Spritesheet config (image layers only). Null to remove."),
|
|
2587
|
-
frameIndex:
|
|
2979
|
+
frameIndex: z34.number().int().min(0).optional().describe("Spritesheet frame index (image layers only)"),
|
|
2588
2980
|
// Shape fields
|
|
2589
|
-
shape:
|
|
2590
|
-
type:
|
|
2591
|
-
cornerRadius:
|
|
2592
|
-
|
|
2593
|
-
|
|
2981
|
+
shape: z34.object({
|
|
2982
|
+
type: z34.enum(["rect", "ellipse", "path"]),
|
|
2983
|
+
cornerRadius: z34.union([
|
|
2984
|
+
z34.number(),
|
|
2985
|
+
z34.tuple([z34.number(), z34.number(), z34.number(), z34.number()])
|
|
2594
2986
|
]).optional(),
|
|
2595
|
-
points:
|
|
2596
|
-
x:
|
|
2597
|
-
y:
|
|
2598
|
-
in:
|
|
2599
|
-
out:
|
|
2987
|
+
points: z34.array(z34.object({
|
|
2988
|
+
x: z34.number(),
|
|
2989
|
+
y: z34.number(),
|
|
2990
|
+
in: z34.object({ x: z34.number(), y: z34.number() }).optional(),
|
|
2991
|
+
out: z34.object({ x: z34.number(), y: z34.number() }).optional()
|
|
2600
2992
|
})).optional(),
|
|
2601
|
-
closed:
|
|
2993
|
+
closed: z34.boolean().optional()
|
|
2602
2994
|
}).optional().describe("New shape (shape layers only)"),
|
|
2603
|
-
fill:
|
|
2604
|
-
stroke:
|
|
2995
|
+
fill: z34.record(z34.unknown()).optional().describe("New fill definition (shape layers only)"),
|
|
2996
|
+
stroke: z34.record(z34.unknown()).optional().describe("New stroke definition (shape layers only)")
|
|
2605
2997
|
},
|
|
2606
2998
|
{ readOnlyHint: false, destructiveHint: false },
|
|
2607
2999
|
async ({ id, layerId, content, style, src, assetId, sourceRect, spritesheet, frameIndex, shape, fill, stroke }) => {
|
|
@@ -2682,7 +3074,7 @@ function register9(server, store) {
|
|
|
2682
3074
|
}
|
|
2683
3075
|
|
|
2684
3076
|
// ../mcp/src/tools/state-config.ts
|
|
2685
|
-
import { z as
|
|
3077
|
+
import { z as z35 } from "zod";
|
|
2686
3078
|
function getDoc10(store, id) {
|
|
2687
3079
|
const doc = store.get(id);
|
|
2688
3080
|
if (!doc) return { error: `Document "${id}" not found` };
|
|
@@ -2694,68 +3086,39 @@ function ok10(data) {
|
|
|
2694
3086
|
function err10(message) {
|
|
2695
3087
|
return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
|
|
2696
3088
|
}
|
|
2697
|
-
var EasingInputSchema3 =
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
type:
|
|
2702
|
-
x1:
|
|
2703
|
-
y1:
|
|
2704
|
-
x2:
|
|
2705
|
-
y2:
|
|
3089
|
+
var EasingInputSchema3 = z35.union([
|
|
3090
|
+
z35.enum(["ease-in", "ease-out", "ease-in-out"]),
|
|
3091
|
+
z35.object({ type: z35.literal("linear") }),
|
|
3092
|
+
z35.object({
|
|
3093
|
+
type: z35.literal("cubic-bezier"),
|
|
3094
|
+
x1: z35.number(),
|
|
3095
|
+
y1: z35.number(),
|
|
3096
|
+
x2: z35.number(),
|
|
3097
|
+
y2: z35.number()
|
|
2706
3098
|
}),
|
|
2707
|
-
|
|
2708
|
-
type:
|
|
2709
|
-
mass:
|
|
2710
|
-
stiffness:
|
|
2711
|
-
damping:
|
|
2712
|
-
velocity:
|
|
3099
|
+
z35.object({
|
|
3100
|
+
type: z35.literal("spring"),
|
|
3101
|
+
mass: z35.number().optional(),
|
|
3102
|
+
stiffness: z35.number().optional(),
|
|
3103
|
+
damping: z35.number().optional(),
|
|
3104
|
+
velocity: z35.number().optional()
|
|
2713
3105
|
}),
|
|
2714
|
-
|
|
2715
|
-
type:
|
|
2716
|
-
steps:
|
|
2717
|
-
position:
|
|
3106
|
+
z35.object({
|
|
3107
|
+
type: z35.literal("step"),
|
|
3108
|
+
steps: z35.number().int().positive(),
|
|
3109
|
+
position: z35.enum(["start", "end"]).optional()
|
|
2718
3110
|
})
|
|
2719
3111
|
]).describe("Easing function");
|
|
2720
3112
|
function register10(server, store) {
|
|
2721
|
-
server.tool(
|
|
2722
|
-
"atelier_set_audio",
|
|
2723
|
-
"Set or remove an audio track on a state",
|
|
2724
|
-
{
|
|
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)")
|
|
2733
|
-
}).nullable().describe("Audio configuration, or null to remove")
|
|
2734
|
-
},
|
|
2735
|
-
{ readOnlyHint: false, destructiveHint: false },
|
|
2736
|
-
async ({ id, stateName, audio }) => {
|
|
2737
|
-
const result = getDoc10(store, id);
|
|
2738
|
-
if ("error" in result) return err10(result.error);
|
|
2739
|
-
const { doc } = result;
|
|
2740
|
-
const state = doc.states[stateName];
|
|
2741
|
-
if (!state) return err10(`State "${stateName}" not found`);
|
|
2742
|
-
if (audio === null) {
|
|
2743
|
-
delete state.audio;
|
|
2744
|
-
return ok10({ stateName, audio: null, removed: true });
|
|
2745
|
-
}
|
|
2746
|
-
state.audio = audio;
|
|
2747
|
-
return ok10({ stateName, audioSrc: audio.src });
|
|
2748
|
-
}
|
|
2749
|
-
);
|
|
2750
3113
|
server.tool(
|
|
2751
3114
|
"atelier_configure_transition",
|
|
2752
3115
|
"Configure or remove a transition from one state to a target state",
|
|
2753
3116
|
{
|
|
2754
|
-
id:
|
|
2755
|
-
stateName:
|
|
2756
|
-
targetState:
|
|
2757
|
-
transition:
|
|
2758
|
-
duration:
|
|
3117
|
+
id: z35.string().describe("Document ID"),
|
|
3118
|
+
stateName: z35.string().describe("Source state name"),
|
|
3119
|
+
targetState: z35.string().describe("Target state name"),
|
|
3120
|
+
transition: z35.object({
|
|
3121
|
+
duration: z35.number().positive().int().describe("Transition duration in frames"),
|
|
2759
3122
|
easing: EasingInputSchema3.optional()
|
|
2760
3123
|
}).nullable().describe("Transition config, or null to remove")
|
|
2761
3124
|
},
|
|
@@ -2792,9 +3155,9 @@ function register10(server, store) {
|
|
|
2792
3155
|
"atelier_set_state_parent",
|
|
2793
3156
|
"Set or clear the parent state for hierarchical delta inheritance",
|
|
2794
3157
|
{
|
|
2795
|
-
id:
|
|
2796
|
-
stateName:
|
|
2797
|
-
parent:
|
|
3158
|
+
id: z35.string().describe("Document ID"),
|
|
3159
|
+
stateName: z35.string().describe("State name"),
|
|
3160
|
+
parent: z35.string().nullable().describe("Parent state name, or null to clear")
|
|
2798
3161
|
},
|
|
2799
3162
|
{ readOnlyHint: false, destructiveHint: false },
|
|
2800
3163
|
async ({ id, stateName, parent }) => {
|
|
@@ -2829,7 +3192,7 @@ function register10(server, store) {
|
|
|
2829
3192
|
}
|
|
2830
3193
|
|
|
2831
3194
|
// ../mcp/src/tools/export.ts
|
|
2832
|
-
import { z as
|
|
3195
|
+
import { z as z36 } from "zod";
|
|
2833
3196
|
|
|
2834
3197
|
// ../canvas/dist/index.js
|
|
2835
3198
|
function colorToCSS(color) {
|
|
@@ -3924,8 +4287,11 @@ function mapBlendMode(mode) {
|
|
|
3924
4287
|
}
|
|
3925
4288
|
function collectUnsupportedWarnings(doc, state) {
|
|
3926
4289
|
const warnings = [];
|
|
3927
|
-
|
|
3928
|
-
|
|
4290
|
+
const audioLayers = doc.layers.filter((l) => l.visual.type === "audio");
|
|
4291
|
+
if (audioLayers.length > 0) {
|
|
4292
|
+
warnings.push(
|
|
4293
|
+
`${audioLayers.length} audio layer(s) are not supported in Lottie format and will be dropped`
|
|
4294
|
+
);
|
|
3929
4295
|
}
|
|
3930
4296
|
for (const delta of state.deltas) {
|
|
3931
4297
|
if (isExpression22(delta.from) || isExpression22(delta.to)) {
|
|
@@ -4014,12 +4380,12 @@ function register11(server, store) {
|
|
|
4014
4380
|
"atelier_export_svg",
|
|
4015
4381
|
"Export a frame as an SVG string",
|
|
4016
4382
|
{
|
|
4017
|
-
id:
|
|
4018
|
-
stateName:
|
|
4019
|
-
frame:
|
|
4020
|
-
xmlDeclaration:
|
|
4021
|
-
viewBox:
|
|
4022
|
-
indent:
|
|
4383
|
+
id: z36.string().describe("Document ID"),
|
|
4384
|
+
stateName: z36.string().optional().describe("State name (defaults to first state)"),
|
|
4385
|
+
frame: z36.number().int().min(0).optional().describe("Frame number (defaults to 0)"),
|
|
4386
|
+
xmlDeclaration: z36.boolean().optional().describe("Include XML declaration (default: false)"),
|
|
4387
|
+
viewBox: z36.boolean().optional().describe("Include viewBox attribute (default: true)"),
|
|
4388
|
+
indent: z36.number().int().min(0).optional().describe("Indent size in spaces (default: 2)")
|
|
4023
4389
|
},
|
|
4024
4390
|
{ readOnlyHint: true, destructiveHint: false },
|
|
4025
4391
|
async ({ id, stateName, frame, xmlDeclaration, viewBox, indent }) => {
|
|
@@ -4050,8 +4416,8 @@ function register11(server, store) {
|
|
|
4050
4416
|
"atelier_export_lottie",
|
|
4051
4417
|
"Export a document to Lottie JSON format (lossy, best-effort)",
|
|
4052
4418
|
{
|
|
4053
|
-
id:
|
|
4054
|
-
stateName:
|
|
4419
|
+
id: z36.string().describe("Document ID"),
|
|
4420
|
+
stateName: z36.string().optional().describe("State to export (defaults to first state)")
|
|
4055
4421
|
},
|
|
4056
4422
|
{ readOnlyHint: true, destructiveHint: false },
|
|
4057
4423
|
async ({ id, stateName }) => {
|
|
@@ -4071,7 +4437,7 @@ function register11(server, store) {
|
|
|
4071
4437
|
}
|
|
4072
4438
|
|
|
4073
4439
|
// ../mcp/src/tools/assets.ts
|
|
4074
|
-
import { z as
|
|
4440
|
+
import { z as z37 } from "zod";
|
|
4075
4441
|
function getDoc12(store, id) {
|
|
4076
4442
|
const doc = store.get(id);
|
|
4077
4443
|
if (!doc) return { error: `Document "${id}" not found` };
|
|
@@ -4088,17 +4454,17 @@ function register12(server, store) {
|
|
|
4088
4454
|
"atelier_add_asset",
|
|
4089
4455
|
"Register an external asset (image, SVG, font, animation, audio) on a document",
|
|
4090
4456
|
{
|
|
4091
|
-
id:
|
|
4092
|
-
assetId:
|
|
4093
|
-
type:
|
|
4094
|
-
src:
|
|
4095
|
-
description:
|
|
4096
|
-
spritesheet:
|
|
4097
|
-
columns:
|
|
4098
|
-
rows:
|
|
4099
|
-
frameCount:
|
|
4100
|
-
frameWidth:
|
|
4101
|
-
frameHeight:
|
|
4457
|
+
id: z37.string().describe("Document ID"),
|
|
4458
|
+
assetId: z37.string().describe("Unique asset identifier"),
|
|
4459
|
+
type: z37.enum(["image", "svg", "font", "animation", "audio"]).describe("Asset type"),
|
|
4460
|
+
src: z37.string().describe("File path or URL"),
|
|
4461
|
+
description: z37.string().optional().describe("Human-readable description"),
|
|
4462
|
+
spritesheet: z37.object({
|
|
4463
|
+
columns: z37.number().int().positive().describe("Number of columns in the spritesheet grid"),
|
|
4464
|
+
rows: z37.number().int().positive().describe("Number of rows in the spritesheet grid"),
|
|
4465
|
+
frameCount: z37.number().int().positive().optional().describe("Total frame count (defaults to columns \xD7 rows)"),
|
|
4466
|
+
frameWidth: z37.number().positive().describe("Width of each frame in pixels"),
|
|
4467
|
+
frameHeight: z37.number().positive().describe("Height of each frame in pixels")
|
|
4102
4468
|
}).optional().describe("Spritesheet metadata (image assets only)")
|
|
4103
4469
|
},
|
|
4104
4470
|
{ readOnlyHint: false, destructiveHint: false },
|
|
@@ -4121,7 +4487,7 @@ function register12(server, store) {
|
|
|
4121
4487
|
"atelier_list_assets",
|
|
4122
4488
|
"List all registered assets with usage info (which layers/states reference them)",
|
|
4123
4489
|
{
|
|
4124
|
-
id:
|
|
4490
|
+
id: z37.string().describe("Document ID")
|
|
4125
4491
|
},
|
|
4126
4492
|
{ readOnlyHint: true, destructiveHint: false },
|
|
4127
4493
|
async ({ id }) => {
|
|
@@ -4130,15 +4496,16 @@ function register12(server, store) {
|
|
|
4130
4496
|
const { doc } = result;
|
|
4131
4497
|
const assets = doc.assets ?? {};
|
|
4132
4498
|
const entries = Object.entries(assets).map(([assetId, asset]) => {
|
|
4133
|
-
const referencingLayers = doc.layers.filter((l) =>
|
|
4134
|
-
|
|
4499
|
+
const referencingLayers = doc.layers.filter((l) => {
|
|
4500
|
+
const v = l.visual;
|
|
4501
|
+
return (v.type === "image" || v.type === "video" || v.type === "audio") && "assetId" in v && v.assetId === assetId;
|
|
4502
|
+
}).map((l) => l.id);
|
|
4135
4503
|
return {
|
|
4136
4504
|
assetId,
|
|
4137
4505
|
type: asset.type,
|
|
4138
4506
|
src: asset.src,
|
|
4139
4507
|
description: asset.description,
|
|
4140
|
-
usedByLayers: referencingLayers
|
|
4141
|
-
usedByStates: referencingStates
|
|
4508
|
+
usedByLayers: referencingLayers
|
|
4142
4509
|
};
|
|
4143
4510
|
});
|
|
4144
4511
|
return ok12({ count: entries.length, assets: entries });
|
|
@@ -4146,10 +4513,10 @@ function register12(server, store) {
|
|
|
4146
4513
|
);
|
|
4147
4514
|
server.tool(
|
|
4148
4515
|
"atelier_remove_asset",
|
|
4149
|
-
"Remove a registered asset. Warns if layers
|
|
4516
|
+
"Remove a registered asset. Warns if layers still reference it.",
|
|
4150
4517
|
{
|
|
4151
|
-
id:
|
|
4152
|
-
assetId:
|
|
4518
|
+
id: z37.string().describe("Document ID"),
|
|
4519
|
+
assetId: z37.string().describe("Asset ID to remove")
|
|
4153
4520
|
},
|
|
4154
4521
|
{ readOnlyHint: false, destructiveHint: true },
|
|
4155
4522
|
async ({ id, assetId }) => {
|
|
@@ -4158,14 +4525,13 @@ function register12(server, store) {
|
|
|
4158
4525
|
const { doc } = result;
|
|
4159
4526
|
if (!doc.assets?.[assetId]) return err12(`Asset "${assetId}" not found`);
|
|
4160
4527
|
const warnings = [];
|
|
4161
|
-
const referencingLayers = doc.layers.filter((l) =>
|
|
4528
|
+
const referencingLayers = doc.layers.filter((l) => {
|
|
4529
|
+
const v = l.visual;
|
|
4530
|
+
return (v.type === "image" || v.type === "video" || v.type === "audio") && "assetId" in v && v.assetId === assetId;
|
|
4531
|
+
}).map((l) => l.id);
|
|
4162
4532
|
if (referencingLayers.length > 0) {
|
|
4163
4533
|
warnings.push(`Asset is referenced by layer(s): ${referencingLayers.join(", ")}`);
|
|
4164
4534
|
}
|
|
4165
|
-
const referencingStates = Object.entries(doc.states).filter(([, state]) => state.audio?.src === assetId).map(([name]) => name);
|
|
4166
|
-
if (referencingStates.length > 0) {
|
|
4167
|
-
warnings.push(`Asset is referenced by state audio: ${referencingStates.join(", ")}`);
|
|
4168
|
-
}
|
|
4169
4535
|
delete doc.assets[assetId];
|
|
4170
4536
|
return ok12({ assetId, warnings });
|
|
4171
4537
|
}
|
|
@@ -4173,7 +4539,7 @@ function register12(server, store) {
|
|
|
4173
4539
|
}
|
|
4174
4540
|
|
|
4175
4541
|
// ../mcp/src/tools/variables.ts
|
|
4176
|
-
import { z as
|
|
4542
|
+
import { z as z38 } from "zod";
|
|
4177
4543
|
function getDoc13(store, id) {
|
|
4178
4544
|
const doc = store.get(id);
|
|
4179
4545
|
if (!doc) return { error: `Document "${id}" not found` };
|
|
@@ -4190,11 +4556,11 @@ function register13(server, store) {
|
|
|
4190
4556
|
"atelier_add_variable",
|
|
4191
4557
|
"Define a template variable on a document",
|
|
4192
4558
|
{
|
|
4193
|
-
id:
|
|
4194
|
-
variableName:
|
|
4195
|
-
type:
|
|
4196
|
-
description:
|
|
4197
|
-
default:
|
|
4559
|
+
id: z38.string().describe("Document ID"),
|
|
4560
|
+
variableName: z38.string().describe("Variable name (used in {{variableName}} patterns)"),
|
|
4561
|
+
type: z38.enum(["string", "number", "color", "asset", "boolean"]).describe("Variable type"),
|
|
4562
|
+
description: z38.string().optional().describe("Human-readable description"),
|
|
4563
|
+
default: z38.unknown().optional().describe("Default value")
|
|
4198
4564
|
},
|
|
4199
4565
|
{ readOnlyHint: false, destructiveHint: false },
|
|
4200
4566
|
async (args) => {
|
|
@@ -4215,7 +4581,7 @@ function register13(server, store) {
|
|
|
4215
4581
|
"atelier_list_variables",
|
|
4216
4582
|
"List all declared variables with usage info (which {{references}} exist in the document)",
|
|
4217
4583
|
{
|
|
4218
|
-
id:
|
|
4584
|
+
id: z38.string().describe("Document ID")
|
|
4219
4585
|
},
|
|
4220
4586
|
{ readOnlyHint: true, destructiveHint: false },
|
|
4221
4587
|
async ({ id }) => {
|
|
@@ -4239,8 +4605,8 @@ function register13(server, store) {
|
|
|
4239
4605
|
"atelier_remove_variable",
|
|
4240
4606
|
"Remove a declared variable. Warns if {{references}} still exist in the document.",
|
|
4241
4607
|
{
|
|
4242
|
-
id:
|
|
4243
|
-
variableName:
|
|
4608
|
+
id: z38.string().describe("Document ID"),
|
|
4609
|
+
variableName: z38.string().describe("Variable name to remove")
|
|
4244
4610
|
},
|
|
4245
4611
|
{ readOnlyHint: false, destructiveHint: true },
|
|
4246
4612
|
async ({ id, variableName }) => {
|
|
@@ -4263,7 +4629,7 @@ function register13(server, store) {
|
|
|
4263
4629
|
}
|
|
4264
4630
|
|
|
4265
4631
|
// ../mcp/src/tools/interactions.ts
|
|
4266
|
-
import { z as
|
|
4632
|
+
import { z as z39 } from "zod";
|
|
4267
4633
|
function getDoc14(store, id) {
|
|
4268
4634
|
const doc = store.get(id);
|
|
4269
4635
|
if (!doc) return { error: `Document "${id}" not found` };
|
|
@@ -4280,23 +4646,23 @@ function register14(server, store) {
|
|
|
4280
4646
|
"atelier_add_interaction",
|
|
4281
4647
|
"Add a trigger-action interaction to a layer (click, hover, timer, signal \u2192 go-to-state, emit-signal, set-variable, toggle-visibility)",
|
|
4282
4648
|
{
|
|
4283
|
-
id:
|
|
4284
|
-
layerId:
|
|
4285
|
-
interactionId:
|
|
4286
|
-
trigger:
|
|
4287
|
-
type:
|
|
4288
|
-
delay:
|
|
4289
|
-
signal:
|
|
4649
|
+
id: z39.string().describe("Document ID"),
|
|
4650
|
+
layerId: z39.string().describe("Layer to attach the interaction to"),
|
|
4651
|
+
interactionId: z39.string().describe("Unique interaction ID"),
|
|
4652
|
+
trigger: z39.object({
|
|
4653
|
+
type: z39.enum(["click", "hover", "pointerdown", "pointerup", "timer", "signal"]).describe("Trigger type"),
|
|
4654
|
+
delay: z39.number().optional().describe("Delay in ms (timer trigger only)"),
|
|
4655
|
+
signal: z39.string().optional().describe("Signal name (signal trigger only)")
|
|
4290
4656
|
}).describe("What triggers the interaction"),
|
|
4291
|
-
action:
|
|
4292
|
-
type:
|
|
4293
|
-
state:
|
|
4294
|
-
signal:
|
|
4295
|
-
variable:
|
|
4296
|
-
value:
|
|
4297
|
-
targetLayer:
|
|
4657
|
+
action: z39.object({
|
|
4658
|
+
type: z39.enum(["go-to-state", "emit-signal", "set-variable", "toggle-visibility"]).describe("Action type"),
|
|
4659
|
+
state: z39.string().optional().describe("Target state (go-to-state only)"),
|
|
4660
|
+
signal: z39.string().optional().describe("Signal to emit (emit-signal only)"),
|
|
4661
|
+
variable: z39.string().optional().describe("Variable name (set-variable only)"),
|
|
4662
|
+
value: z39.unknown().optional().describe("Value to set (set-variable only)"),
|
|
4663
|
+
targetLayer: z39.string().optional().describe("Target layer ID (toggle-visibility, defaults to self)")
|
|
4298
4664
|
}).describe("What happens when triggered"),
|
|
4299
|
-
description:
|
|
4665
|
+
description: z39.string().optional().describe("Human-readable description")
|
|
4300
4666
|
},
|
|
4301
4667
|
{ readOnlyHint: false, destructiveHint: false },
|
|
4302
4668
|
async ({ id, layerId, interactionId, trigger, action, description }) => {
|
|
@@ -4345,8 +4711,8 @@ function register14(server, store) {
|
|
|
4345
4711
|
"atelier_list_interactions",
|
|
4346
4712
|
"List all interactions across all layers or for a specific layer",
|
|
4347
4713
|
{
|
|
4348
|
-
id:
|
|
4349
|
-
layerId:
|
|
4714
|
+
id: z39.string().describe("Document ID"),
|
|
4715
|
+
layerId: z39.string().optional().describe("Optional: filter to a specific layer")
|
|
4350
4716
|
},
|
|
4351
4717
|
{ readOnlyHint: true, destructiveHint: false },
|
|
4352
4718
|
async ({ id, layerId }) => {
|
|
@@ -4373,9 +4739,9 @@ function register14(server, store) {
|
|
|
4373
4739
|
"atelier_remove_interaction",
|
|
4374
4740
|
"Remove an interaction from a layer",
|
|
4375
4741
|
{
|
|
4376
|
-
id:
|
|
4377
|
-
layerId:
|
|
4378
|
-
interactionId:
|
|
4742
|
+
id: z39.string().describe("Document ID"),
|
|
4743
|
+
layerId: z39.string().describe("Layer ID"),
|
|
4744
|
+
interactionId: z39.string().describe("Interaction ID to remove")
|
|
4379
4745
|
},
|
|
4380
4746
|
{ readOnlyHint: false, destructiveHint: true },
|
|
4381
4747
|
async ({ id, layerId, interactionId }) => {
|
|
@@ -4397,7 +4763,7 @@ function register14(server, store) {
|
|
|
4397
4763
|
}
|
|
4398
4764
|
|
|
4399
4765
|
// ../mcp/src/tools/refs.ts
|
|
4400
|
-
import { z as
|
|
4766
|
+
import { z as z40 } from "zod";
|
|
4401
4767
|
function getDoc15(store, id) {
|
|
4402
4768
|
const doc = store.get(id);
|
|
4403
4769
|
if (!doc) return { error: `Document "${id}" not found` };
|
|
@@ -4414,9 +4780,9 @@ function register15(server, store) {
|
|
|
4414
4780
|
"atelier_set_ref",
|
|
4415
4781
|
"Set the source reference on a ref-type layer. Can point to a file path or another in-memory document ID.",
|
|
4416
4782
|
{
|
|
4417
|
-
id:
|
|
4418
|
-
layerId:
|
|
4419
|
-
src:
|
|
4783
|
+
id: z40.string().describe("Document ID"),
|
|
4784
|
+
layerId: z40.string().describe("Layer ID (must be a ref visual)"),
|
|
4785
|
+
src: z40.string().describe("File path, URL, or in-memory document ID")
|
|
4420
4786
|
},
|
|
4421
4787
|
{ readOnlyHint: false, destructiveHint: false },
|
|
4422
4788
|
async ({ id, layerId, src }) => {
|
|
@@ -4436,7 +4802,7 @@ function register15(server, store) {
|
|
|
4436
4802
|
"atelier_resolve_refs",
|
|
4437
4803
|
"Check which ref layers can be resolved (point to valid in-memory documents)",
|
|
4438
4804
|
{
|
|
4439
|
-
id:
|
|
4805
|
+
id: z40.string().describe("Document ID")
|
|
4440
4806
|
},
|
|
4441
4807
|
{ readOnlyHint: true, destructiveHint: false },
|
|
4442
4808
|
async ({ id }) => {
|
|
@@ -4463,7 +4829,7 @@ function register15(server, store) {
|
|
|
4463
4829
|
}
|
|
4464
4830
|
|
|
4465
4831
|
// ../mcp/src/tools/performance.ts
|
|
4466
|
-
import { z as
|
|
4832
|
+
import { z as z41 } from "zod";
|
|
4467
4833
|
function getDoc16(store, id) {
|
|
4468
4834
|
const doc = store.get(id);
|
|
4469
4835
|
if (!doc) return { error: `Document "${id}" not found` };
|
|
@@ -4480,9 +4846,9 @@ function register16(server, store) {
|
|
|
4480
4846
|
"atelier_profile",
|
|
4481
4847
|
"Profile frame resolution performance \u2014 returns timing, layer count, property count, and delta count for a frame",
|
|
4482
4848
|
{
|
|
4483
|
-
id:
|
|
4484
|
-
stateName:
|
|
4485
|
-
frame:
|
|
4849
|
+
id: z41.string().describe("Document ID"),
|
|
4850
|
+
stateName: z41.string().describe("State name to profile"),
|
|
4851
|
+
frame: z41.number().int().min(0).describe("Frame number to resolve")
|
|
4486
4852
|
},
|
|
4487
4853
|
{ readOnlyHint: true, destructiveHint: false },
|
|
4488
4854
|
async ({ id, stateName, frame }) => {
|
|
@@ -4528,10 +4894,10 @@ function register16(server, store) {
|
|
|
4528
4894
|
"atelier_diff_frames",
|
|
4529
4895
|
"Compare two frames and return only the properties that changed between them",
|
|
4530
4896
|
{
|
|
4531
|
-
id:
|
|
4532
|
-
stateName:
|
|
4533
|
-
frameA:
|
|
4534
|
-
frameB:
|
|
4897
|
+
id: z41.string().describe("Document ID"),
|
|
4898
|
+
stateName: z41.string().describe("State name"),
|
|
4899
|
+
frameA: z41.number().int().min(0).describe("First frame number"),
|
|
4900
|
+
frameB: z41.number().int().min(0).describe("Second frame number")
|
|
4535
4901
|
},
|
|
4536
4902
|
{ readOnlyHint: true, destructiveHint: false },
|
|
4537
4903
|
async ({ id, stateName, frameA, frameB }) => {
|
|
@@ -4570,9 +4936,9 @@ function register16(server, store) {
|
|
|
4570
4936
|
"atelier_batch_preview",
|
|
4571
4937
|
"Preview multiple frames at once \u2014 reduces round-trips for scrubbing or timeline inspection",
|
|
4572
4938
|
{
|
|
4573
|
-
id:
|
|
4574
|
-
stateName:
|
|
4575
|
-
frames:
|
|
4939
|
+
id: z41.string().describe("Document ID"),
|
|
4940
|
+
stateName: z41.string().describe("State name to preview"),
|
|
4941
|
+
frames: z41.array(z41.number().int().min(0)).min(1).max(60).describe("Array of frame numbers to resolve (max 60)")
|
|
4576
4942
|
},
|
|
4577
4943
|
{ readOnlyHint: true, destructiveHint: false },
|
|
4578
4944
|
async ({ id, stateName, frames }) => {
|
|
@@ -4608,7 +4974,7 @@ function register16(server, store) {
|
|
|
4608
4974
|
"atelier_complexity",
|
|
4609
4975
|
"Analyze document complexity \u2014 layer count, delta count, expression usage, state hierarchy depth",
|
|
4610
4976
|
{
|
|
4611
|
-
id:
|
|
4977
|
+
id: z41.string().describe("Document ID")
|
|
4612
4978
|
},
|
|
4613
4979
|
{ readOnlyHint: true, destructiveHint: false },
|
|
4614
4980
|
async ({ id }) => {
|
|
@@ -4637,7 +5003,9 @@ function register16(server, store) {
|
|
|
4637
5003
|
duration: state.duration,
|
|
4638
5004
|
deltaCount,
|
|
4639
5005
|
expressionCount,
|
|
4640
|
-
hasAudio
|
|
5006
|
+
// hasAudio removed in v1.0 — audio is no longer per-state, it's an
|
|
5007
|
+
// AudioVisual layer. Audio-layer counts appear in the top-level
|
|
5008
|
+
// layer stats below.
|
|
4641
5009
|
hasParent: !!state.parent,
|
|
4642
5010
|
transitionCount: state.transitions ? Object.keys(state.transitions).length : 0
|
|
4643
5011
|
});
|
|
@@ -4681,7 +5049,7 @@ function register16(server, store) {
|
|
|
4681
5049
|
}
|
|
4682
5050
|
|
|
4683
5051
|
// ../mcp/src/tools/overlays.ts
|
|
4684
|
-
import { z as
|
|
5052
|
+
import { z as z42 } from "zod";
|
|
4685
5053
|
function getDoc17(store, id) {
|
|
4686
5054
|
const doc = store.get(id);
|
|
4687
5055
|
if (!doc) return { error: `Document "${id}" not found` };
|
|
@@ -4693,12 +5061,12 @@ function ok17(data) {
|
|
|
4693
5061
|
function err17(message) {
|
|
4694
5062
|
return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
|
|
4695
5063
|
}
|
|
4696
|
-
var AnchorEnum =
|
|
4697
|
-
var OverlayStyleSchema =
|
|
4698
|
-
font_family:
|
|
4699
|
-
font_size:
|
|
4700
|
-
font_weight:
|
|
4701
|
-
color:
|
|
5064
|
+
var AnchorEnum = z42.enum(["top-left", "top-right", "bottom-left", "bottom-right"]);
|
|
5065
|
+
var OverlayStyleSchema = z42.object({
|
|
5066
|
+
font_family: z42.string().optional(),
|
|
5067
|
+
font_size: z42.number().positive().optional(),
|
|
5068
|
+
font_weight: z42.union([z42.number(), z42.enum(["normal", "bold"])]).optional(),
|
|
5069
|
+
color: z42.string().optional()
|
|
4702
5070
|
}).optional();
|
|
4703
5071
|
var DEFAULT_OVERLAY_MARGIN = 24;
|
|
4704
5072
|
var DEFAULT_TEXT_STYLE = {
|
|
@@ -4749,12 +5117,12 @@ function register17(server, store) {
|
|
|
4749
5117
|
"atelier_add_handle",
|
|
4750
5118
|
"Add an anchored creator-handle TextVisual overlay layer (tag: overlay)",
|
|
4751
5119
|
{
|
|
4752
|
-
id:
|
|
4753
|
-
text:
|
|
5120
|
+
id: z42.string().describe("Document ID"),
|
|
5121
|
+
text: z42.string().min(1).describe('Handle text, e.g. "@username"'),
|
|
4754
5122
|
anchor: AnchorEnum.describe("Anchored corner"),
|
|
4755
|
-
margin:
|
|
5123
|
+
margin: z42.number().nonnegative().optional().describe("Px from anchored edges (default 24)"),
|
|
4756
5124
|
style: OverlayStyleSchema.describe("Text style overrides (snake_case to match recipe)"),
|
|
4757
|
-
layerId:
|
|
5125
|
+
layerId: z42.string().optional().describe("Layer ID (default: overlay-handle)")
|
|
4758
5126
|
},
|
|
4759
5127
|
{ readOnlyHint: false, destructiveHint: false },
|
|
4760
5128
|
async ({ id, text, anchor, margin, style, layerId }) => {
|
|
@@ -4784,14 +5152,14 @@ function register17(server, store) {
|
|
|
4784
5152
|
"atelier_add_page_number",
|
|
4785
5153
|
"Add an anchored page-number TextVisual overlay layer (tag: overlay). Substitutes {current} / {total} (and zero-pad forms {current:02d} / {total:02d}) into format.",
|
|
4786
5154
|
{
|
|
4787
|
-
id:
|
|
4788
|
-
format:
|
|
5155
|
+
id: z42.string().describe("Document ID"),
|
|
5156
|
+
format: z42.string().min(1).describe('Format string, e.g. "{current}/{total}" or "{current:02d}/{total:02d}"'),
|
|
4789
5157
|
anchor: AnchorEnum.describe("Anchored corner"),
|
|
4790
|
-
currentIndex:
|
|
4791
|
-
totalCount:
|
|
4792
|
-
margin:
|
|
5158
|
+
currentIndex: z42.number().int().nonnegative().describe("1-based current index"),
|
|
5159
|
+
totalCount: z42.number().int().positive().describe("Total count"),
|
|
5160
|
+
margin: z42.number().nonnegative().optional().describe("Px from anchored edges (default 24)"),
|
|
4793
5161
|
style: OverlayStyleSchema.describe("Text style overrides (snake_case to match recipe)"),
|
|
4794
|
-
layerId:
|
|
5162
|
+
layerId: z42.string().optional().describe("Layer ID (default: overlay-page-number)")
|
|
4795
5163
|
},
|
|
4796
5164
|
{ readOnlyHint: false, destructiveHint: false },
|
|
4797
5165
|
async ({ id, format, anchor, currentIndex, totalCount, margin, style, layerId }) => {
|
|
@@ -4824,7 +5192,7 @@ function register17(server, store) {
|
|
|
4824
5192
|
import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync } from "fs";
|
|
4825
5193
|
import { join, resolve, isAbsolute, dirname } from "path";
|
|
4826
5194
|
import { homedir } from "os";
|
|
4827
|
-
import { z as
|
|
5195
|
+
import { z as z43 } from "zod";
|
|
4828
5196
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
4829
5197
|
function getDoc18(store, id) {
|
|
4830
5198
|
const doc = store.get(id);
|
|
@@ -4991,7 +5359,7 @@ function register18(server, store) {
|
|
|
4991
5359
|
"atelier_recipe_list",
|
|
4992
5360
|
"List discoverable Studio Recipes \u2014 project-local <projectDir>/.atelier/recipes/*.recipe.* plus ~/.atelier/recipes/*.recipe.*. Returns { name, path } entries with absolute paths.",
|
|
4993
5361
|
{
|
|
4994
|
-
projectDir:
|
|
5362
|
+
projectDir: z43.string().optional().describe("Project root to search (default: process.cwd())")
|
|
4995
5363
|
},
|
|
4996
5364
|
{ readOnlyHint: true, destructiveHint: false },
|
|
4997
5365
|
async ({ projectDir }) => {
|
|
@@ -5007,8 +5375,8 @@ function register18(server, store) {
|
|
|
5007
5375
|
"atelier_recipe_get",
|
|
5008
5376
|
"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
5377
|
{
|
|
5010
|
-
path:
|
|
5011
|
-
projectDir:
|
|
5378
|
+
path: z43.string().describe("Recipe path or bare name (resolved like the CLI)"),
|
|
5379
|
+
projectDir: z43.string().optional().describe("Project root for name resolution (default: process.cwd())")
|
|
5012
5380
|
},
|
|
5013
5381
|
{ readOnlyHint: true, destructiveHint: false },
|
|
5014
5382
|
async ({ path, projectDir }) => {
|
|
@@ -5030,9 +5398,9 @@ function register18(server, store) {
|
|
|
5030
5398
|
"atelier_recipe_validate",
|
|
5031
5399
|
"Validate a recipe from a path OR inline YAML text. Returns { success, warnings, errors }.",
|
|
5032
5400
|
{
|
|
5033
|
-
path:
|
|
5034
|
-
yaml:
|
|
5035
|
-
projectDir:
|
|
5401
|
+
path: z43.string().optional().describe("Recipe path or bare name to validate"),
|
|
5402
|
+
yaml: z43.string().optional().describe("Inline YAML text to validate (alternative to path)"),
|
|
5403
|
+
projectDir: z43.string().optional().describe("Project root for name resolution (default: process.cwd())")
|
|
5036
5404
|
},
|
|
5037
5405
|
{ readOnlyHint: true, destructiveHint: false },
|
|
5038
5406
|
async ({ path, yaml, projectDir }) => {
|
|
@@ -5062,9 +5430,9 @@ function register18(server, store) {
|
|
|
5062
5430
|
"atelier_recipe_save",
|
|
5063
5431
|
"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
5432
|
{
|
|
5065
|
-
path:
|
|
5066
|
-
recipe:
|
|
5067
|
-
yaml:
|
|
5433
|
+
path: z43.string().describe("Absolute or relative file path to write the recipe to"),
|
|
5434
|
+
recipe: z43.record(z43.unknown()).optional().describe("Recipe object (will be serialized to YAML)"),
|
|
5435
|
+
yaml: z43.string().optional().describe("Raw YAML text (alternative to `recipe`)")
|
|
5068
5436
|
},
|
|
5069
5437
|
{ readOnlyHint: false, destructiveHint: true },
|
|
5070
5438
|
async ({ path, recipe, yaml }) => {
|
|
@@ -5102,12 +5470,12 @@ function register18(server, store) {
|
|
|
5102
5470
|
"atelier_recipe_apply",
|
|
5103
5471
|
`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
5472
|
{
|
|
5105
|
-
documentId:
|
|
5106
|
-
path:
|
|
5107
|
-
recipe:
|
|
5108
|
-
projectDir:
|
|
5109
|
-
currentIndex:
|
|
5110
|
-
totalCount:
|
|
5473
|
+
documentId: z43.string().describe("In-store document ID to apply the recipe to"),
|
|
5474
|
+
path: z43.string().optional().describe("Recipe path or bare name (alternative to `recipe`)"),
|
|
5475
|
+
recipe: z43.record(z43.unknown()).optional().describe("Recipe object (alternative to `path`)"),
|
|
5476
|
+
projectDir: z43.string().optional().describe("Project root for name resolution (default: process.cwd())"),
|
|
5477
|
+
currentIndex: z43.number().int().nonnegative().optional().describe("1-based carousel index for page_number"),
|
|
5478
|
+
totalCount: z43.number().int().positive().optional().describe("Total carousel size for page_number")
|
|
5111
5479
|
},
|
|
5112
5480
|
{ readOnlyHint: false, destructiveHint: false },
|
|
5113
5481
|
async ({ documentId, path, recipe, projectDir, currentIndex, totalCount }) => {
|
|
@@ -5150,7 +5518,7 @@ function register18(server, store) {
|
|
|
5150
5518
|
// ../mcp/src/tools/import-images.ts
|
|
5151
5519
|
import { existsSync as existsSync2, readdirSync as readdirSync2, statSync } from "fs";
|
|
5152
5520
|
import { resolve as resolve2, join as join2, basename, extname } from "path";
|
|
5153
|
-
import { z as
|
|
5521
|
+
import { z as z44 } from "zod";
|
|
5154
5522
|
function getDoc19(store, id) {
|
|
5155
5523
|
const doc = store.get(id);
|
|
5156
5524
|
if (!doc) return { error: `Document "${id}" not found` };
|
|
@@ -5198,10 +5566,10 @@ function register19(server, store) {
|
|
|
5198
5566
|
"atelier_import_images",
|
|
5199
5567
|
`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
5568
|
{
|
|
5201
|
-
documentId:
|
|
5202
|
-
dir:
|
|
5203
|
-
glob:
|
|
5204
|
-
asLayers:
|
|
5569
|
+
documentId: z44.string().describe("In-store document ID to import into"),
|
|
5570
|
+
dir: z44.string().describe("Directory containing the images"),
|
|
5571
|
+
glob: z44.string().optional().describe('Optional filename glob, e.g. "*.png" (default: all images)'),
|
|
5572
|
+
asLayers: z44.boolean().optional().describe("Create layers per image (default: true). false = register assets only.")
|
|
5205
5573
|
},
|
|
5206
5574
|
{ readOnlyHint: false, destructiveHint: false },
|
|
5207
5575
|
async ({ documentId, dir, glob, asLayers }) => {
|
|
@@ -5262,12 +5630,2023 @@ function register19(server, store) {
|
|
|
5262
5630
|
);
|
|
5263
5631
|
}
|
|
5264
5632
|
|
|
5633
|
+
// ../mcp/src/tools/mind-map.ts
|
|
5634
|
+
import { existsSync as existsSync3, statSync as statSync2, readdirSync as readdirSync3 } from "fs";
|
|
5635
|
+
import { resolve as resolve3, join as join3, basename as basename2, extname as extname2 } from "path";
|
|
5636
|
+
import { z as z45 } from "zod";
|
|
5637
|
+
function ok20(data) {
|
|
5638
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...data }) }] };
|
|
5639
|
+
}
|
|
5640
|
+
function err20(message) {
|
|
5641
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: message }) }], isError: true };
|
|
5642
|
+
}
|
|
5643
|
+
var KIND_EXTENSIONS = {
|
|
5644
|
+
image: /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".webp", ".gif", ".svg", ".avif"]),
|
|
5645
|
+
video: /* @__PURE__ */ new Set([".mp4", ".mov", ".webm", ".mkv", ".avi"]),
|
|
5646
|
+
audio: /* @__PURE__ */ new Set([".mp3", ".wav", ".m4a", ".ogg", ".flac", ".aac"]),
|
|
5647
|
+
font: /* @__PURE__ */ new Set([".ttf", ".otf", ".woff", ".woff2"]),
|
|
5648
|
+
text: /* @__PURE__ */ new Set(),
|
|
5649
|
+
// empty by design — handled with a per-call note
|
|
5650
|
+
voice: /* @__PURE__ */ new Set([".mp3", ".wav", ".m4a", ".ogg", ".flac", ".aac"]),
|
|
5651
|
+
logo: /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".webp", ".gif", ".svg", ".avif"])
|
|
5652
|
+
};
|
|
5653
|
+
var TEXT_NOTE = "text slots resolve at composition time, not from disk";
|
|
5654
|
+
var MAX_DEPTH = 4;
|
|
5655
|
+
function chooseSearchRoot(projectDir) {
|
|
5656
|
+
const mindMap = join3(projectDir, ".atelier", "mind-map");
|
|
5657
|
+
if (existsSync3(mindMap) && statSync2(mindMap).isDirectory()) return mindMap;
|
|
5658
|
+
const assets = join3(projectDir, "assets");
|
|
5659
|
+
if (existsSync3(assets) && statSync2(assets).isDirectory()) return assets;
|
|
5660
|
+
return projectDir;
|
|
5661
|
+
}
|
|
5662
|
+
function walk(root, searchedDirs) {
|
|
5663
|
+
const out = [];
|
|
5664
|
+
function recurse(dir, depth, relSegments) {
|
|
5665
|
+
searchedDirs.push(dir);
|
|
5666
|
+
let entries;
|
|
5667
|
+
try {
|
|
5668
|
+
entries = readdirSync3(dir);
|
|
5669
|
+
} catch {
|
|
5670
|
+
return;
|
|
5671
|
+
}
|
|
5672
|
+
for (const entry of entries) {
|
|
5673
|
+
const abs = join3(dir, entry);
|
|
5674
|
+
let stat;
|
|
5675
|
+
try {
|
|
5676
|
+
stat = statSync2(abs);
|
|
5677
|
+
} catch {
|
|
5678
|
+
continue;
|
|
5679
|
+
}
|
|
5680
|
+
if (stat.isDirectory()) {
|
|
5681
|
+
if (depth >= MAX_DEPTH) continue;
|
|
5682
|
+
recurse(abs, depth + 1, [...relSegments, entry]);
|
|
5683
|
+
} else if (stat.isFile()) {
|
|
5684
|
+
out.push({
|
|
5685
|
+
path: abs,
|
|
5686
|
+
name: entry,
|
|
5687
|
+
mtimeMs: stat.mtimeMs,
|
|
5688
|
+
relDirSegments: relSegments
|
|
5689
|
+
});
|
|
5690
|
+
}
|
|
5691
|
+
}
|
|
5692
|
+
}
|
|
5693
|
+
recurse(root, 0, []);
|
|
5694
|
+
return out;
|
|
5695
|
+
}
|
|
5696
|
+
function matchedHints(candidate, hints) {
|
|
5697
|
+
const haystacks = [candidate.name.toLowerCase(), ...candidate.relDirSegments.map((s) => s.toLowerCase())];
|
|
5698
|
+
return hints.filter((h) => {
|
|
5699
|
+
const needle = h.toLowerCase();
|
|
5700
|
+
return haystacks.some((hs) => hs.includes(needle));
|
|
5701
|
+
});
|
|
5702
|
+
}
|
|
5703
|
+
var DEFAULT_LIMIT = 20;
|
|
5704
|
+
var MAX_LIMIT = 100;
|
|
5705
|
+
function register20(server, _store) {
|
|
5706
|
+
server.tool(
|
|
5707
|
+
"atelier_mind_map_query",
|
|
5708
|
+
"Resolve a typed asset slot against the creator's local asset library. Walks <projectDir>/.atelier/mind-map/ (preferred), else <projectDir>/assets/, else <projectDir> itself. Recursive with a depth cap of 4 levels. Filters by the slot `kind` extension table (image, video, audio, font, voice, logo); `text` returns no matches by design (text resolves at composition time). Optional `hints` (case-insensitive substrings) narrow by filename or ancestor directory name within the search root. Results sorted by mtime (most-recent first). Pure read \u2014 does not touch the document store. Stub for Phase 4 mind-map.",
|
|
5709
|
+
{
|
|
5710
|
+
projectDir: z45.string().describe("Absolute or cwd-relative path to the project root"),
|
|
5711
|
+
kind: SlotKindSchema.describe(
|
|
5712
|
+
"Slot kind from the closed SlotKind enum (image|video|audio|text|font|voice|logo)"
|
|
5713
|
+
),
|
|
5714
|
+
hints: z45.array(z45.string().min(1)).min(1).optional().describe(
|
|
5715
|
+
"Optional case-insensitive substrings; a candidate must match at least one against its filename or an ancestor directory name within the search root."
|
|
5716
|
+
),
|
|
5717
|
+
limit: z45.number().int().positive().max(MAX_LIMIT).optional().describe(`Max matches to return (default ${DEFAULT_LIMIT}, max ${MAX_LIMIT})`)
|
|
5718
|
+
},
|
|
5719
|
+
{ readOnlyHint: true, destructiveHint: false },
|
|
5720
|
+
async ({ projectDir, kind, hints, limit }) => {
|
|
5721
|
+
const absProject = resolve3(projectDir);
|
|
5722
|
+
if (!existsSync3(absProject)) {
|
|
5723
|
+
return err20(`projectDir "${absProject}" does not exist`);
|
|
5724
|
+
}
|
|
5725
|
+
if (!statSync2(absProject).isDirectory()) {
|
|
5726
|
+
return err20(`projectDir "${absProject}" is not a directory`);
|
|
5727
|
+
}
|
|
5728
|
+
const searchRoot = chooseSearchRoot(absProject);
|
|
5729
|
+
const searchedDirs = [];
|
|
5730
|
+
const candidates = walk(searchRoot, searchedDirs);
|
|
5731
|
+
if (kind === "text") {
|
|
5732
|
+
return ok20({
|
|
5733
|
+
matches: [],
|
|
5734
|
+
searched_dirs: searchedDirs,
|
|
5735
|
+
total_candidates: candidates.length,
|
|
5736
|
+
note: TEXT_NOTE
|
|
5737
|
+
});
|
|
5738
|
+
}
|
|
5739
|
+
const allowedExts = KIND_EXTENSIONS[kind];
|
|
5740
|
+
const kindMatched = candidates.filter((c) => allowedExts.has(extname2(c.name).toLowerCase()));
|
|
5741
|
+
const hintFiltered = hints ? kindMatched.map((c) => ({ c, matched: matchedHints(c, hints) })).filter(({ matched }) => matched.length > 0) : kindMatched.map((c) => ({ c, matched: [] }));
|
|
5742
|
+
hintFiltered.sort((a, b) => b.c.mtimeMs - a.c.mtimeMs);
|
|
5743
|
+
const effectiveLimit = limit ?? DEFAULT_LIMIT;
|
|
5744
|
+
const limited = hintFiltered.slice(0, effectiveLimit);
|
|
5745
|
+
const matches = limited.map(({ c, matched }) => ({
|
|
5746
|
+
path: c.path,
|
|
5747
|
+
name: basename2(c.path),
|
|
5748
|
+
kind,
|
|
5749
|
+
matched_hints: matched
|
|
5750
|
+
}));
|
|
5751
|
+
return ok20({
|
|
5752
|
+
matches,
|
|
5753
|
+
searched_dirs: searchedDirs,
|
|
5754
|
+
total_candidates: candidates.length
|
|
5755
|
+
});
|
|
5756
|
+
}
|
|
5757
|
+
);
|
|
5758
|
+
}
|
|
5759
|
+
|
|
5760
|
+
// ../mcp/src/tools/effective-transcript.ts
|
|
5761
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2, statSync as statSync3, readdirSync as readdirSync4 } from "fs";
|
|
5762
|
+
import { createHash } from "crypto";
|
|
5763
|
+
import { join as join4, resolve as resolve4 } from "path";
|
|
5764
|
+
import { z as z46 } from "zod";
|
|
5765
|
+
function ok21(data) {
|
|
5766
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...data }) }] };
|
|
5767
|
+
}
|
|
5768
|
+
function err21(message) {
|
|
5769
|
+
return {
|
|
5770
|
+
content: [{ type: "text", text: JSON.stringify({ error: message }) }],
|
|
5771
|
+
isError: true
|
|
5772
|
+
};
|
|
5773
|
+
}
|
|
5774
|
+
function applyCuts(transcript, cuts, options) {
|
|
5775
|
+
const sortedCuts = [...cuts].sort((a, b) => a.rawStart - b.rawStart);
|
|
5776
|
+
const keepRanges = sortedCuts.map((c) => ({
|
|
5777
|
+
start: Math.max(0, c.rawStart - c.paddingPre),
|
|
5778
|
+
end: c.rawEnd + c.paddingPost
|
|
5779
|
+
}));
|
|
5780
|
+
function mapTime(t) {
|
|
5781
|
+
let cumulativeOffset = 0;
|
|
5782
|
+
for (const r of keepRanges) {
|
|
5783
|
+
if (t < r.start) return null;
|
|
5784
|
+
if (t <= r.end) return t - r.start + cumulativeOffset;
|
|
5785
|
+
cumulativeOffset += r.end - r.start;
|
|
5786
|
+
}
|
|
5787
|
+
return null;
|
|
5788
|
+
}
|
|
5789
|
+
const newSegments = [];
|
|
5790
|
+
for (const seg of transcript.segments) {
|
|
5791
|
+
const newWords = [];
|
|
5792
|
+
for (const w of seg.words) {
|
|
5793
|
+
const ns = mapTime(w.start);
|
|
5794
|
+
const ne = mapTime(w.end);
|
|
5795
|
+
if (ns === null || ne === null) continue;
|
|
5796
|
+
newWords.push({ ...w, start: ns, end: ne });
|
|
5797
|
+
}
|
|
5798
|
+
if (newWords.length === 0) continue;
|
|
5799
|
+
newSegments.push({
|
|
5800
|
+
text: seg.text,
|
|
5801
|
+
start: newWords[0].start,
|
|
5802
|
+
end: newWords[newWords.length - 1].end,
|
|
5803
|
+
words: newWords
|
|
5804
|
+
});
|
|
5805
|
+
}
|
|
5806
|
+
const totalCutDurationS = keepRanges.reduce((sum, r) => sum + (r.end - r.start), 0);
|
|
5807
|
+
const derived_from = {
|
|
5808
|
+
transcriptName: options.transcriptName,
|
|
5809
|
+
cutsHash: options.cutsHash,
|
|
5810
|
+
derivedAt: options.derivedAt,
|
|
5811
|
+
cutsApplied: sortedCuts.length,
|
|
5812
|
+
totalCutDurationS
|
|
5813
|
+
};
|
|
5814
|
+
return {
|
|
5815
|
+
version: transcript.version,
|
|
5816
|
+
...transcript.language !== void 0 && { language: transcript.language },
|
|
5817
|
+
segments: newSegments,
|
|
5818
|
+
derived_from
|
|
5819
|
+
};
|
|
5820
|
+
}
|
|
5821
|
+
function hashCuts(cuts) {
|
|
5822
|
+
const canonical = cuts.map((c) => [c.rawStart, c.rawEnd, c.paddingPre, c.paddingPost]);
|
|
5823
|
+
return createHash("sha256").update(JSON.stringify(canonical)).digest("hex").slice(0, 16);
|
|
5824
|
+
}
|
|
5825
|
+
function discoverTranscripts(projectDir) {
|
|
5826
|
+
const out = [];
|
|
5827
|
+
const canonical = join4(projectDir, "transcript.json");
|
|
5828
|
+
if (existsSync4(canonical) && statSync3(canonical).isFile()) {
|
|
5829
|
+
out.push({ absPath: canonical, name: "transcript.json" });
|
|
5830
|
+
}
|
|
5831
|
+
const transcriptsDir = join4(projectDir, "transcripts");
|
|
5832
|
+
if (existsSync4(transcriptsDir) && statSync3(transcriptsDir).isDirectory()) {
|
|
5833
|
+
try {
|
|
5834
|
+
const entries = readdirSync4(transcriptsDir).filter((f) => f.endsWith(".json")).sort();
|
|
5835
|
+
for (const entry of entries) {
|
|
5836
|
+
out.push({
|
|
5837
|
+
absPath: join4(transcriptsDir, entry),
|
|
5838
|
+
name: `transcripts/${entry}`
|
|
5839
|
+
});
|
|
5840
|
+
}
|
|
5841
|
+
} catch {
|
|
5842
|
+
}
|
|
5843
|
+
}
|
|
5844
|
+
return out;
|
|
5845
|
+
}
|
|
5846
|
+
function register21(server, _store) {
|
|
5847
|
+
server.tool(
|
|
5848
|
+
"atelier_get_effective_transcript",
|
|
5849
|
+
"Read a project's raw transcript.json + cuts.json and return the post-cut (effective) transcript \u2014 words inside cut gaps dropped, surviving timestamps shifted to the trimmed timeline. Pure read; does not touch the document store. When cuts.json is absent or has zero cuts, returns the raw transcript verbatim (no `derived_from` stamp). Optional `transcriptName` disambiguates when the project carries multiple transcripts (transcripts/*.json); without it, transcript.json wins, and an error is returned if multiple sit in transcripts/.",
|
|
5850
|
+
{
|
|
5851
|
+
projectDir: z46.string().describe("Absolute or cwd-relative project root"),
|
|
5852
|
+
transcriptName: z46.string().optional().describe(
|
|
5853
|
+
"Optional transcript filename (e.g. 'transcript.json' or 'transcripts/take-2.json'). Disambiguates multi-transcript projects."
|
|
5854
|
+
)
|
|
5855
|
+
},
|
|
5856
|
+
{ readOnlyHint: true, destructiveHint: false },
|
|
5857
|
+
async ({ projectDir, transcriptName }) => {
|
|
5858
|
+
const absProject = resolve4(projectDir);
|
|
5859
|
+
if (!existsSync4(absProject)) {
|
|
5860
|
+
return err21(`projectDir "${absProject}" does not exist`);
|
|
5861
|
+
}
|
|
5862
|
+
if (!statSync3(absProject).isDirectory()) {
|
|
5863
|
+
return err21(`projectDir "${absProject}" is not a directory`);
|
|
5864
|
+
}
|
|
5865
|
+
let chosen;
|
|
5866
|
+
if (transcriptName !== void 0) {
|
|
5867
|
+
const absPath = transcriptName.startsWith("/") ? transcriptName : join4(absProject, transcriptName);
|
|
5868
|
+
if (!existsSync4(absPath) || !statSync3(absPath).isFile()) {
|
|
5869
|
+
return err21(`transcript "${transcriptName}" not found in ${absProject}`);
|
|
5870
|
+
}
|
|
5871
|
+
chosen = { absPath, name: transcriptName };
|
|
5872
|
+
} else {
|
|
5873
|
+
const candidates = discoverTranscripts(absProject);
|
|
5874
|
+
if (candidates.length === 0) {
|
|
5875
|
+
return err21(`no transcript found in ${absProject} (looked for transcript.json and transcripts/*.json)`);
|
|
5876
|
+
}
|
|
5877
|
+
if (candidates.length > 1) {
|
|
5878
|
+
return err21(
|
|
5879
|
+
`multiple transcripts present in ${absProject}; pass transcriptName explicitly. Candidates: ${candidates.map((c) => c.name).join(", ")}`
|
|
5880
|
+
);
|
|
5881
|
+
}
|
|
5882
|
+
chosen = candidates[0];
|
|
5883
|
+
}
|
|
5884
|
+
let rawTranscript;
|
|
5885
|
+
try {
|
|
5886
|
+
rawTranscript = JSON.parse(readFileSync2(chosen.absPath, "utf-8"));
|
|
5887
|
+
} catch (e) {
|
|
5888
|
+
return err21(`failed to parse transcript at ${chosen.absPath}: ${e.message}`);
|
|
5889
|
+
}
|
|
5890
|
+
const cutsPath = join4(absProject, "cuts.json");
|
|
5891
|
+
if (!existsSync4(cutsPath)) {
|
|
5892
|
+
return ok21({
|
|
5893
|
+
effective: rawTranscript,
|
|
5894
|
+
source: {
|
|
5895
|
+
transcriptName: chosen.name,
|
|
5896
|
+
cutsApplied: 0,
|
|
5897
|
+
totalCutDurationS: 0
|
|
5898
|
+
}
|
|
5899
|
+
});
|
|
5900
|
+
}
|
|
5901
|
+
let rawCuts;
|
|
5902
|
+
try {
|
|
5903
|
+
rawCuts = JSON.parse(readFileSync2(cutsPath, "utf-8"));
|
|
5904
|
+
} catch (e) {
|
|
5905
|
+
return err21(`failed to parse cuts at ${cutsPath}: ${e.message}`);
|
|
5906
|
+
}
|
|
5907
|
+
const cuts = rawCuts.cuts ?? [];
|
|
5908
|
+
if (cuts.length === 0) {
|
|
5909
|
+
return ok21({
|
|
5910
|
+
effective: rawTranscript,
|
|
5911
|
+
source: {
|
|
5912
|
+
transcriptName: chosen.name,
|
|
5913
|
+
cutsApplied: 0,
|
|
5914
|
+
totalCutDurationS: 0
|
|
5915
|
+
}
|
|
5916
|
+
});
|
|
5917
|
+
}
|
|
5918
|
+
const effective = applyCuts(rawTranscript, cuts, {
|
|
5919
|
+
transcriptName: chosen.name,
|
|
5920
|
+
cutsHash: hashCuts(cuts),
|
|
5921
|
+
derivedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5922
|
+
});
|
|
5923
|
+
return ok21({
|
|
5924
|
+
effective,
|
|
5925
|
+
source: {
|
|
5926
|
+
transcriptName: chosen.name,
|
|
5927
|
+
cutsApplied: effective.derived_from?.cutsApplied ?? cuts.length,
|
|
5928
|
+
totalCutDurationS: effective.derived_from?.totalCutDurationS ?? 0
|
|
5929
|
+
}
|
|
5930
|
+
});
|
|
5931
|
+
}
|
|
5932
|
+
);
|
|
5933
|
+
}
|
|
5934
|
+
|
|
5935
|
+
// ../mcp/src/tools/media-notes.ts
|
|
5936
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, statSync as statSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
5937
|
+
import { join as join5, resolve as resolve5 } from "path";
|
|
5938
|
+
import { z as z47 } from "zod";
|
|
5939
|
+
import { parse as parseYaml2, stringify as stringifyYaml2 } from "yaml";
|
|
5940
|
+
function ok22(data) {
|
|
5941
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...data }) }] };
|
|
5942
|
+
}
|
|
5943
|
+
function err22(message) {
|
|
5944
|
+
return {
|
|
5945
|
+
content: [{ type: "text", text: JSON.stringify({ error: message }) }],
|
|
5946
|
+
isError: true
|
|
5947
|
+
};
|
|
5948
|
+
}
|
|
5949
|
+
var MEDIA_NOTES_SUFFIX = ".notes.md";
|
|
5950
|
+
function resolveMediaPaths(projectDir, filename) {
|
|
5951
|
+
const absProject = resolve5(projectDir);
|
|
5952
|
+
if (!existsSync5(absProject) || !statSync4(absProject).isDirectory()) {
|
|
5953
|
+
return { error: `projectDir "${absProject}" is not a directory` };
|
|
5954
|
+
}
|
|
5955
|
+
if (filename.includes("/") || filename.includes("\\") || filename === "..") {
|
|
5956
|
+
return { error: `filename must be a bare basename (got "${filename}")` };
|
|
5957
|
+
}
|
|
5958
|
+
const mediaPath = join5(absProject, "media", filename);
|
|
5959
|
+
const notesPath = `${mediaPath}${MEDIA_NOTES_SUFFIX}`;
|
|
5960
|
+
return { project: absProject, mediaPath, notesPath };
|
|
5961
|
+
}
|
|
5962
|
+
function splitFrontmatter(raw) {
|
|
5963
|
+
const trimmed = raw.replace(/^/, "");
|
|
5964
|
+
if (!trimmed.startsWith("---")) return null;
|
|
5965
|
+
const lines = trimmed.split(/\r?\n/);
|
|
5966
|
+
if (lines[0].trim() !== "---") return null;
|
|
5967
|
+
let closeIdx = -1;
|
|
5968
|
+
for (let i = 1; i < lines.length; i++) {
|
|
5969
|
+
if (lines[i].trim() === "---") {
|
|
5970
|
+
closeIdx = i;
|
|
5971
|
+
break;
|
|
5972
|
+
}
|
|
5973
|
+
}
|
|
5974
|
+
if (closeIdx === -1) return null;
|
|
5975
|
+
return {
|
|
5976
|
+
frontmatter: lines.slice(1, closeIdx).join("\n"),
|
|
5977
|
+
body: lines.slice(closeIdx + 1).join("\n").replace(/^\n+/, "")
|
|
5978
|
+
};
|
|
5979
|
+
}
|
|
5980
|
+
function readNotesFile(notesPath) {
|
|
5981
|
+
if (!existsSync5(notesPath)) return null;
|
|
5982
|
+
const raw = readFileSync3(notesPath, "utf-8");
|
|
5983
|
+
const split = splitFrontmatter(raw);
|
|
5984
|
+
if (split === null) return null;
|
|
5985
|
+
let parsed;
|
|
5986
|
+
try {
|
|
5987
|
+
parsed = parseYaml2(split.frontmatter);
|
|
5988
|
+
} catch {
|
|
5989
|
+
return null;
|
|
5990
|
+
}
|
|
5991
|
+
const validated = MediaNotesFrontmatterSchema.safeParse(parsed);
|
|
5992
|
+
if (!validated.success) return null;
|
|
5993
|
+
return { frontmatter: validated.data, body: split.body };
|
|
5994
|
+
}
|
|
5995
|
+
function writeNotesFile(notesPath, notes) {
|
|
5996
|
+
const yamlText = stringifyYaml2(notes.frontmatter);
|
|
5997
|
+
const body = notes.body.endsWith("\n") ? notes.body : `${notes.body}
|
|
5998
|
+
`;
|
|
5999
|
+
writeFileSync2(notesPath, `---
|
|
6000
|
+
${yamlText}---
|
|
6001
|
+
|
|
6002
|
+
${body}`, "utf-8");
|
|
6003
|
+
}
|
|
6004
|
+
function upsertSection(body, heading, newBlock) {
|
|
6005
|
+
const lines = body.split(/\r?\n/);
|
|
6006
|
+
let startIdx = -1;
|
|
6007
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6008
|
+
if (lines[i].trim() === heading) {
|
|
6009
|
+
startIdx = i;
|
|
6010
|
+
break;
|
|
6011
|
+
}
|
|
6012
|
+
}
|
|
6013
|
+
if (startIdx === -1) {
|
|
6014
|
+
const existing = body.trimEnd();
|
|
6015
|
+
return existing.length > 0 ? `${existing}
|
|
6016
|
+
|
|
6017
|
+
${newBlock.trimEnd()}
|
|
6018
|
+
` : `${newBlock.trimEnd()}
|
|
6019
|
+
`;
|
|
6020
|
+
}
|
|
6021
|
+
let endIdx = lines.length;
|
|
6022
|
+
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
6023
|
+
if (lines[i].startsWith("## ")) {
|
|
6024
|
+
endIdx = i;
|
|
6025
|
+
break;
|
|
6026
|
+
}
|
|
6027
|
+
}
|
|
6028
|
+
const before = lines.slice(0, startIdx);
|
|
6029
|
+
const after = lines.slice(endIdx);
|
|
6030
|
+
const replacement = newBlock.split(/\r?\n/);
|
|
6031
|
+
while (replacement.length > 0 && replacement[replacement.length - 1] === "") {
|
|
6032
|
+
replacement.pop();
|
|
6033
|
+
}
|
|
6034
|
+
const joined = [...before, ...replacement];
|
|
6035
|
+
if (after.length > 0 && after[0].startsWith("## ")) {
|
|
6036
|
+
joined.push("");
|
|
6037
|
+
}
|
|
6038
|
+
joined.push(...after);
|
|
6039
|
+
return joined.join("\n").replace(/\n+$/, "\n");
|
|
6040
|
+
}
|
|
6041
|
+
function register22(server, _store) {
|
|
6042
|
+
server.tool(
|
|
6043
|
+
"atelier_get_media_notes",
|
|
6044
|
+
"Read the sidecar notes file for a media entry \u2014 <projectDir>/media/<filename>.notes.md. Returns the parsed frontmatter (file, added, added_by, tags, optional constraints) plus the Markdown body. Returns an error envelope when the notes file is absent or malformed; pure read, does not touch the document store.",
|
|
6045
|
+
{
|
|
6046
|
+
projectDir: z47.string().describe("Absolute or cwd-relative project root"),
|
|
6047
|
+
filename: z47.string().describe("Bare basename of the media file (e.g. 'b-roll-laptop.mp4')")
|
|
6048
|
+
},
|
|
6049
|
+
{ readOnlyHint: true, destructiveHint: false },
|
|
6050
|
+
async ({ projectDir, filename }) => {
|
|
6051
|
+
const paths = resolveMediaPaths(projectDir, filename);
|
|
6052
|
+
if ("error" in paths) return err22(paths.error);
|
|
6053
|
+
if (!existsSync5(paths.notesPath)) {
|
|
6054
|
+
return err22(`notes file not found: ${paths.notesPath}`);
|
|
6055
|
+
}
|
|
6056
|
+
const notes = readNotesFile(paths.notesPath);
|
|
6057
|
+
if (notes === null) {
|
|
6058
|
+
return err22(`notes file at ${paths.notesPath} is malformed (frontmatter parse / schema validation failed)`);
|
|
6059
|
+
}
|
|
6060
|
+
return ok22({
|
|
6061
|
+
path: paths.notesPath,
|
|
6062
|
+
frontmatter: notes.frontmatter,
|
|
6063
|
+
body: notes.body
|
|
6064
|
+
});
|
|
6065
|
+
}
|
|
6066
|
+
);
|
|
6067
|
+
server.tool(
|
|
6068
|
+
"atelier_write_media_note",
|
|
6069
|
+
"Append or replace an author's section in a media file's sidecar notes. Section boundaries are detected by the per-author sentinel headings (## Lux notes / Quill notes / Iris notes / Creator notes); a second call with the same author replaces the prior section rather than duplicating. The notes file MUST exist already (scaffold via `atelier media ingest` or `atelier media note`). Mutates disk; the calling agent is tagged via the `author` argument.",
|
|
6070
|
+
{
|
|
6071
|
+
projectDir: z47.string().describe("Absolute or cwd-relative project root"),
|
|
6072
|
+
filename: z47.string().describe("Bare basename of the media file"),
|
|
6073
|
+
author: MediaNotesAuthorSchema.describe("Section owner: lux | quill | iris | creator"),
|
|
6074
|
+
section: z47.string().describe("Markdown body for the section (heading is added automatically)")
|
|
6075
|
+
},
|
|
6076
|
+
{ readOnlyHint: false, destructiveHint: false },
|
|
6077
|
+
async ({ projectDir, filename, author, section }) => {
|
|
6078
|
+
const paths = resolveMediaPaths(projectDir, filename);
|
|
6079
|
+
if ("error" in paths) return err22(paths.error);
|
|
6080
|
+
if (!existsSync5(paths.notesPath)) {
|
|
6081
|
+
return err22(
|
|
6082
|
+
`notes file not found: ${paths.notesPath} \u2014 scaffold via \`atelier media ingest\` or \`atelier media note\` first`
|
|
6083
|
+
);
|
|
6084
|
+
}
|
|
6085
|
+
const existing = readNotesFile(paths.notesPath);
|
|
6086
|
+
if (existing === null) {
|
|
6087
|
+
return err22(
|
|
6088
|
+
`notes file at ${paths.notesPath} is malformed \u2014 refusing to write into a file with unparseable frontmatter`
|
|
6089
|
+
);
|
|
6090
|
+
}
|
|
6091
|
+
const typedAuthor = author;
|
|
6092
|
+
const heading = MEDIA_NOTES_HEADINGS[typedAuthor];
|
|
6093
|
+
const trimmed = section.trim();
|
|
6094
|
+
const newBlock = `${heading}
|
|
6095
|
+
${trimmed}
|
|
6096
|
+
`;
|
|
6097
|
+
const hasExistingSection = existing.body.split(/\r?\n/).some((l) => l.trim() === heading);
|
|
6098
|
+
const updatedBody = upsertSection(existing.body, heading, newBlock);
|
|
6099
|
+
const updated = { frontmatter: existing.frontmatter, body: updatedBody };
|
|
6100
|
+
writeNotesFile(paths.notesPath, updated);
|
|
6101
|
+
return ok22({
|
|
6102
|
+
path: paths.notesPath,
|
|
6103
|
+
action: hasExistingSection ? "replaced" : "appended",
|
|
6104
|
+
author: typedAuthor
|
|
6105
|
+
});
|
|
6106
|
+
}
|
|
6107
|
+
);
|
|
6108
|
+
}
|
|
6109
|
+
|
|
6110
|
+
// ../mcp/src/tools/clips.ts
|
|
6111
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
6112
|
+
import { basename as basename4, extname as extname4, isAbsolute as isAbsolute2, join as join6, resolve as resolve6 } from "path";
|
|
6113
|
+
import { z as z48 } from "zod";
|
|
6114
|
+
|
|
6115
|
+
// ../mcp/src/lib/timeline-ops.ts
|
|
6116
|
+
import { basename as basename3, extname as extname3 } from "path";
|
|
6117
|
+
import { spawn } from "child_process";
|
|
6118
|
+
var VIDEO_TRACK_ID = "track-video-1";
|
|
6119
|
+
var AUDIO_TRACK_ID = "track-audio-1";
|
|
6120
|
+
function slugifyBasename(name) {
|
|
6121
|
+
const base = basename3(name);
|
|
6122
|
+
const stem = stripExt(base);
|
|
6123
|
+
const lower = stem.toLowerCase();
|
|
6124
|
+
const replaced = lower.replace(/[^a-z0-9]+/g, "-");
|
|
6125
|
+
return replaced.replace(/^-+|-+$/g, "");
|
|
6126
|
+
}
|
|
6127
|
+
function stripExt(name) {
|
|
6128
|
+
const ext = extname3(name);
|
|
6129
|
+
return ext ? name.slice(0, -ext.length) : name;
|
|
6130
|
+
}
|
|
6131
|
+
function endOfTrackFrame(layers, trackId, fps) {
|
|
6132
|
+
let maxEnd = 0;
|
|
6133
|
+
for (const layer of layers) {
|
|
6134
|
+
if (layer.parentId !== trackId) continue;
|
|
6135
|
+
const v = layer.visual;
|
|
6136
|
+
if (v.type !== "video" && v.type !== "audio") continue;
|
|
6137
|
+
const tv = v;
|
|
6138
|
+
const start = tv.startFrame ?? 0;
|
|
6139
|
+
const offset = tv.sourceOffset ?? 0;
|
|
6140
|
+
const end = tv.sourceEnd;
|
|
6141
|
+
if (end === void 0) continue;
|
|
6142
|
+
const durFrames = Math.max(0, Math.round((end - offset) * fps));
|
|
6143
|
+
const layerEnd = start + durFrames;
|
|
6144
|
+
if (layerEnd > maxEnd) maxEnd = layerEnd;
|
|
6145
|
+
}
|
|
6146
|
+
return maxEnd;
|
|
6147
|
+
}
|
|
6148
|
+
function ensureTrackLayer(doc, trackId, kind) {
|
|
6149
|
+
const exists = doc.layers.some((l) => l.id === trackId);
|
|
6150
|
+
if (exists) return { doc, created: false };
|
|
6151
|
+
const trackLayer = {
|
|
6152
|
+
id: trackId,
|
|
6153
|
+
tags: ["track", kind],
|
|
6154
|
+
description: kind === "video" ? "Video track 1" : "Audio track 1",
|
|
6155
|
+
visual: { type: "group" },
|
|
6156
|
+
frame: { x: 0, y: 0 },
|
|
6157
|
+
bounds: { width: doc.canvas.width, height: doc.canvas.height }
|
|
6158
|
+
};
|
|
6159
|
+
return {
|
|
6160
|
+
doc: { ...doc, layers: [trackLayer, ...doc.layers] },
|
|
6161
|
+
created: true
|
|
6162
|
+
};
|
|
6163
|
+
}
|
|
6164
|
+
function ensureClipLayer(doc, args) {
|
|
6165
|
+
const existing = doc.layers.find((l) => l.id === args.clipLayerId);
|
|
6166
|
+
if (existing) {
|
|
6167
|
+
return { doc, addedLayer: false, clipLayerId: args.clipLayerId };
|
|
6168
|
+
}
|
|
6169
|
+
const ensured = ensureTrackLayer(doc, args.trackId, args.kind);
|
|
6170
|
+
const working = ensured.doc;
|
|
6171
|
+
const startFrame = args.startFrame !== void 0 ? args.startFrame : endOfTrackFrame(working.layers, args.trackId, working.canvas.fps);
|
|
6172
|
+
const sourceOffset = args.sourceOffset ?? 0;
|
|
6173
|
+
const sourceEnd = args.sourceEnd ?? args.clipDurationSeconds;
|
|
6174
|
+
const newAsset = {
|
|
6175
|
+
type: args.kind === "video" ? "video" : "audio",
|
|
6176
|
+
src: args.sourceName,
|
|
6177
|
+
description: `Source: ${args.sourceName}`
|
|
6178
|
+
};
|
|
6179
|
+
const assets = {
|
|
6180
|
+
...working.assets ?? {},
|
|
6181
|
+
[args.clipLayerId]: newAsset
|
|
6182
|
+
};
|
|
6183
|
+
let newClip;
|
|
6184
|
+
if (args.kind === "video") {
|
|
6185
|
+
const visual = {
|
|
6186
|
+
type: "video",
|
|
6187
|
+
assetId: args.clipLayerId,
|
|
6188
|
+
src: args.sourceName,
|
|
6189
|
+
startFrame,
|
|
6190
|
+
sourceOffset,
|
|
6191
|
+
sourceEnd,
|
|
6192
|
+
playbackRate: 1,
|
|
6193
|
+
objectFit: "contain"
|
|
6194
|
+
};
|
|
6195
|
+
newClip = {
|
|
6196
|
+
id: args.clipLayerId,
|
|
6197
|
+
parentId: args.trackId,
|
|
6198
|
+
visual,
|
|
6199
|
+
frame: { x: 0, y: 0 },
|
|
6200
|
+
bounds: { width: working.canvas.width, height: working.canvas.height }
|
|
6201
|
+
};
|
|
6202
|
+
} else {
|
|
6203
|
+
const visual = {
|
|
6204
|
+
type: "audio",
|
|
6205
|
+
assetId: args.clipLayerId,
|
|
6206
|
+
src: args.sourceName,
|
|
6207
|
+
startFrame,
|
|
6208
|
+
sourceOffset,
|
|
6209
|
+
sourceEnd
|
|
6210
|
+
};
|
|
6211
|
+
newClip = {
|
|
6212
|
+
id: args.clipLayerId,
|
|
6213
|
+
parentId: args.trackId,
|
|
6214
|
+
visual,
|
|
6215
|
+
frame: { x: 0, y: 0 },
|
|
6216
|
+
bounds: { width: 0, height: 0 }
|
|
6217
|
+
};
|
|
6218
|
+
}
|
|
6219
|
+
return {
|
|
6220
|
+
doc: {
|
|
6221
|
+
...working,
|
|
6222
|
+
assets,
|
|
6223
|
+
layers: [...working.layers, newClip]
|
|
6224
|
+
},
|
|
6225
|
+
addedLayer: true,
|
|
6226
|
+
clipLayerId: args.clipLayerId
|
|
6227
|
+
};
|
|
6228
|
+
}
|
|
6229
|
+
function recomputeTimeline(doc) {
|
|
6230
|
+
const fps = doc.canvas.fps;
|
|
6231
|
+
const trackGroups = doc.layers.filter(
|
|
6232
|
+
(l) => l.visual.type === "group" && (l.tags ?? []).includes("track")
|
|
6233
|
+
);
|
|
6234
|
+
if (trackGroups.length === 0) {
|
|
6235
|
+
if (doc.timeline === void 0) return doc;
|
|
6236
|
+
const { timeline: _omit, ...rest } = doc;
|
|
6237
|
+
void _omit;
|
|
6238
|
+
return rest;
|
|
6239
|
+
}
|
|
6240
|
+
const tracks = [];
|
|
6241
|
+
let totalFrames = 0;
|
|
6242
|
+
for (const track of trackGroups) {
|
|
6243
|
+
const tags = track.tags ?? [];
|
|
6244
|
+
const kind = tags.includes("audio") ? "audio" : "video";
|
|
6245
|
+
const children = doc.layers.filter(
|
|
6246
|
+
(l) => l.parentId === track.id && l.visual.type === kind
|
|
6247
|
+
);
|
|
6248
|
+
const clips = children.map((layer) => {
|
|
6249
|
+
const v = layer.visual;
|
|
6250
|
+
const start = v.startFrame ?? 0;
|
|
6251
|
+
const offset = v.sourceOffset ?? 0;
|
|
6252
|
+
const end = v.sourceEnd ?? offset;
|
|
6253
|
+
const durFrames = Math.max(0, Math.round((end - offset) * fps));
|
|
6254
|
+
return {
|
|
6255
|
+
layerId: layer.id,
|
|
6256
|
+
startFrame: start,
|
|
6257
|
+
endFrame: start + durFrames,
|
|
6258
|
+
source: v.src ?? ""
|
|
6259
|
+
};
|
|
6260
|
+
}).sort((a, b) => a.startFrame - b.startFrame);
|
|
6261
|
+
const trackEnd = clips.reduce((m, c) => Math.max(m, c.endFrame), 0);
|
|
6262
|
+
if (trackEnd > totalFrames) totalFrames = trackEnd;
|
|
6263
|
+
tracks.push({ id: track.id, kind, clips });
|
|
6264
|
+
}
|
|
6265
|
+
const timeline = { fps, totalFrames, tracks };
|
|
6266
|
+
return { ...doc, timeline };
|
|
6267
|
+
}
|
|
6268
|
+
var DEFAULT_STYLE = {
|
|
6269
|
+
fontFamily: "Inter",
|
|
6270
|
+
fontSize: 84,
|
|
6271
|
+
fontWeight: "bold",
|
|
6272
|
+
textAlign: "center",
|
|
6273
|
+
color: "#FFFFFF",
|
|
6274
|
+
yRatio: 0.85,
|
|
6275
|
+
widthRatio: 0.9,
|
|
6276
|
+
fadeSeconds: 0.05
|
|
6277
|
+
};
|
|
6278
|
+
var DEFAULT_PHRASE_MAX_WORDS = 5;
|
|
6279
|
+
var DEFAULT_PHRASE_PAUSE_GAP_SECONDS = 0.4;
|
|
6280
|
+
var END_PUNCT = /[.!?,;:]\s*$/;
|
|
6281
|
+
function flattenWords(transcript) {
|
|
6282
|
+
return transcript.segments.flatMap((s) => s.words);
|
|
6283
|
+
}
|
|
6284
|
+
function groupIntoPhrases(transcript, options = {}) {
|
|
6285
|
+
const maxWords = options.maxWords ?? DEFAULT_PHRASE_MAX_WORDS;
|
|
6286
|
+
const pauseGap = options.pauseGap ?? DEFAULT_PHRASE_PAUSE_GAP_SECONDS;
|
|
6287
|
+
const phrases = [];
|
|
6288
|
+
const visibleWords = flattenWords(transcript).filter((w) => !w.hidden);
|
|
6289
|
+
let current = [];
|
|
6290
|
+
for (let i = 0; i < visibleWords.length; i++) {
|
|
6291
|
+
const w = visibleWords[i];
|
|
6292
|
+
current.push(w);
|
|
6293
|
+
const next = visibleWords[i + 1];
|
|
6294
|
+
const gap = next ? next.start - w.end : 0;
|
|
6295
|
+
const endsOnPunct = END_PUNCT.test(w.text);
|
|
6296
|
+
const atMax = current.length >= maxWords;
|
|
6297
|
+
if (!next || atMax || endsOnPunct || gap > pauseGap) {
|
|
6298
|
+
phrases.push({
|
|
6299
|
+
start: current[0].start,
|
|
6300
|
+
end: current[current.length - 1].end,
|
|
6301
|
+
text: current.map((c) => c.text).join(" "),
|
|
6302
|
+
words: current
|
|
6303
|
+
});
|
|
6304
|
+
current = [];
|
|
6305
|
+
}
|
|
6306
|
+
}
|
|
6307
|
+
return phrases;
|
|
6308
|
+
}
|
|
6309
|
+
function buildCaptionLayers(transcript, canvas, options = {}) {
|
|
6310
|
+
const style = { ...DEFAULT_STYLE, ...options.style };
|
|
6311
|
+
const phrases = groupIntoPhrases(transcript, {
|
|
6312
|
+
maxWords: options.maxWords,
|
|
6313
|
+
pauseGap: options.pauseGap
|
|
6314
|
+
});
|
|
6315
|
+
const layers = [];
|
|
6316
|
+
const deltas = [];
|
|
6317
|
+
const fps = canvas.fps;
|
|
6318
|
+
phrases.forEach((phrase, idx) => {
|
|
6319
|
+
const layerId = `caption-${idx}`;
|
|
6320
|
+
layers.push(buildPhraseLayer(layerId, phrase, canvas, style));
|
|
6321
|
+
deltas.push(...buildPhraseDeltas(layerId, phrase, style.fadeSeconds, fps));
|
|
6322
|
+
});
|
|
6323
|
+
return { layers, deltas };
|
|
6324
|
+
}
|
|
6325
|
+
function buildPhraseLayer(id, phrase, canvas, style) {
|
|
6326
|
+
return {
|
|
6327
|
+
id,
|
|
6328
|
+
tags: ["caption"],
|
|
6329
|
+
visual: {
|
|
6330
|
+
type: "text",
|
|
6331
|
+
content: phrase.text,
|
|
6332
|
+
style: {
|
|
6333
|
+
fontFamily: style.fontFamily,
|
|
6334
|
+
fontSize: style.fontSize,
|
|
6335
|
+
fontWeight: style.fontWeight,
|
|
6336
|
+
textAlign: style.textAlign,
|
|
6337
|
+
color: style.color
|
|
6338
|
+
}
|
|
6339
|
+
},
|
|
6340
|
+
frame: {
|
|
6341
|
+
x: canvas.width / 2,
|
|
6342
|
+
y: canvas.height * style.yRatio
|
|
6343
|
+
},
|
|
6344
|
+
bounds: {
|
|
6345
|
+
width: canvas.width * style.widthRatio,
|
|
6346
|
+
height: Math.max(120, style.fontSize * 1.6)
|
|
6347
|
+
},
|
|
6348
|
+
anchorPoint: { x: 0.5, y: 0.5 },
|
|
6349
|
+
opacity: 0
|
|
6350
|
+
};
|
|
6351
|
+
}
|
|
6352
|
+
function buildPhraseDeltas(layerId, phrase, fadeSeconds, fps) {
|
|
6353
|
+
const fadeFrames = Math.max(1, Math.round(fadeSeconds * fps));
|
|
6354
|
+
const startFrame = Math.floor(phrase.start * fps);
|
|
6355
|
+
const endFrame = Math.ceil(phrase.end * fps);
|
|
6356
|
+
const fadeOutStart = Math.max(startFrame + 1, endFrame - fadeFrames);
|
|
6357
|
+
return [
|
|
6358
|
+
{
|
|
6359
|
+
layer: layerId,
|
|
6360
|
+
property: "opacity",
|
|
6361
|
+
range: [Math.max(0, startFrame - fadeFrames), startFrame],
|
|
6362
|
+
from: 0,
|
|
6363
|
+
to: 1,
|
|
6364
|
+
easing: "ease-out"
|
|
6365
|
+
},
|
|
6366
|
+
{
|
|
6367
|
+
layer: layerId,
|
|
6368
|
+
property: "opacity",
|
|
6369
|
+
range: [fadeOutStart, endFrame],
|
|
6370
|
+
from: 1,
|
|
6371
|
+
to: 0,
|
|
6372
|
+
easing: "ease-in"
|
|
6373
|
+
}
|
|
6374
|
+
];
|
|
6375
|
+
}
|
|
6376
|
+
function stripCaptionsForClip(doc, clipLayerId) {
|
|
6377
|
+
const droppedIds = /* @__PURE__ */ new Set();
|
|
6378
|
+
const keptLayers = doc.layers.filter((layer) => {
|
|
6379
|
+
if (!(layer.tags ?? []).includes("caption")) return true;
|
|
6380
|
+
if (layer.parentId === clipLayerId) {
|
|
6381
|
+
droppedIds.add(layer.id);
|
|
6382
|
+
return false;
|
|
6383
|
+
}
|
|
6384
|
+
return true;
|
|
6385
|
+
});
|
|
6386
|
+
if (droppedIds.size === 0) return doc;
|
|
6387
|
+
const states = {};
|
|
6388
|
+
for (const [name, st] of Object.entries(doc.states)) {
|
|
6389
|
+
states[name] = {
|
|
6390
|
+
...st,
|
|
6391
|
+
deltas: st.deltas.filter((d) => !droppedIds.has(d.layer))
|
|
6392
|
+
};
|
|
6393
|
+
}
|
|
6394
|
+
return { ...doc, layers: keptLayers, states };
|
|
6395
|
+
}
|
|
6396
|
+
function rebuildClipCaptions(doc, clipLayerId, transcript, canvas, captionOptions) {
|
|
6397
|
+
const clipSlug = clipLayerId.startsWith("clip-") ? clipLayerId.slice("clip-".length) : clipLayerId;
|
|
6398
|
+
let working = stripCaptionsForClip(doc, clipLayerId);
|
|
6399
|
+
const built = buildCaptionLayers(transcript, canvas, captionOptions ?? {});
|
|
6400
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
6401
|
+
const renamedLayers = built.layers.map((layer, idx) => {
|
|
6402
|
+
const newId = `cap-${clipSlug}-${idx}`;
|
|
6403
|
+
idMap.set(layer.id, newId);
|
|
6404
|
+
return { ...layer, id: newId, parentId: clipLayerId };
|
|
6405
|
+
});
|
|
6406
|
+
const renamedDeltas = built.deltas.map((d) => ({
|
|
6407
|
+
...d,
|
|
6408
|
+
layer: idMap.get(d.layer) ?? d.layer
|
|
6409
|
+
}));
|
|
6410
|
+
working = { ...working, layers: [...working.layers, ...renamedLayers] };
|
|
6411
|
+
const stateNames = Object.keys(working.states);
|
|
6412
|
+
const targetStateName = stateNames.includes("default") ? "default" : stateNames[0] ?? "default";
|
|
6413
|
+
const states = { ...working.states };
|
|
6414
|
+
const existing = states[targetStateName];
|
|
6415
|
+
if (existing) {
|
|
6416
|
+
states[targetStateName] = {
|
|
6417
|
+
...existing,
|
|
6418
|
+
deltas: [...existing.deltas, ...renamedDeltas]
|
|
6419
|
+
};
|
|
6420
|
+
} else {
|
|
6421
|
+
states[targetStateName] = { duration: 0, deltas: renamedDeltas };
|
|
6422
|
+
}
|
|
6423
|
+
return { ...working, states };
|
|
6424
|
+
}
|
|
6425
|
+
async function probeDuration(sourcePath) {
|
|
6426
|
+
const stdout = await runCapture("ffprobe", [
|
|
6427
|
+
"-v",
|
|
6428
|
+
"error",
|
|
6429
|
+
"-show_entries",
|
|
6430
|
+
"format=duration",
|
|
6431
|
+
"-of",
|
|
6432
|
+
"csv=p=0",
|
|
6433
|
+
sourcePath
|
|
6434
|
+
]);
|
|
6435
|
+
const n = parseFloat(stdout.trim());
|
|
6436
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
6437
|
+
throw new Error(`ffprobe returned invalid duration for ${sourcePath}: "${stdout}"`);
|
|
6438
|
+
}
|
|
6439
|
+
return n;
|
|
6440
|
+
}
|
|
6441
|
+
function runCapture(cmd, args) {
|
|
6442
|
+
return new Promise((resolvePromise, reject) => {
|
|
6443
|
+
const child = spawn(cmd, args);
|
|
6444
|
+
let stdout = "";
|
|
6445
|
+
let stderr = "";
|
|
6446
|
+
child.stdout.on("data", (d) => {
|
|
6447
|
+
stdout += d.toString();
|
|
6448
|
+
});
|
|
6449
|
+
child.stderr.on("data", (d) => {
|
|
6450
|
+
stderr += d.toString();
|
|
6451
|
+
});
|
|
6452
|
+
child.on("error", reject);
|
|
6453
|
+
child.on("close", (code) => {
|
|
6454
|
+
if (code === 0) resolvePromise(stdout);
|
|
6455
|
+
else reject(new Error(`${cmd} exited ${code}: ${stderr}`));
|
|
6456
|
+
});
|
|
6457
|
+
});
|
|
6458
|
+
}
|
|
6459
|
+
|
|
6460
|
+
// ../mcp/src/tools/clips.ts
|
|
6461
|
+
function ok23(data) {
|
|
6462
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, ...data }) }] };
|
|
6463
|
+
}
|
|
6464
|
+
function err23(message) {
|
|
6465
|
+
return {
|
|
6466
|
+
content: [{ type: "text", text: JSON.stringify({ error: message }) }],
|
|
6467
|
+
isError: true
|
|
6468
|
+
};
|
|
6469
|
+
}
|
|
6470
|
+
function loadDocFromDisk(projectDir) {
|
|
6471
|
+
const absProject = resolve6(projectDir);
|
|
6472
|
+
const compositionPath = join6(absProject, "project.atelier");
|
|
6473
|
+
if (!existsSync6(compositionPath)) {
|
|
6474
|
+
return { error: `project.atelier not found at ${compositionPath}` };
|
|
6475
|
+
}
|
|
6476
|
+
let raw;
|
|
6477
|
+
try {
|
|
6478
|
+
raw = readFileSync4(compositionPath, "utf-8");
|
|
6479
|
+
} catch (e) {
|
|
6480
|
+
return { error: `failed to read ${compositionPath}: ${e.message}` };
|
|
6481
|
+
}
|
|
6482
|
+
let doc;
|
|
6483
|
+
try {
|
|
6484
|
+
doc = JSON.parse(raw);
|
|
6485
|
+
} catch (e) {
|
|
6486
|
+
return { error: `failed to parse ${compositionPath}: ${e.message}` };
|
|
6487
|
+
}
|
|
6488
|
+
return { doc, compositionPath };
|
|
6489
|
+
}
|
|
6490
|
+
function persistDoc(store, documentId, doc, compositionPath) {
|
|
6491
|
+
writeFileSync3(compositionPath, JSON.stringify(doc, null, 2), "utf-8");
|
|
6492
|
+
if (store.has(documentId)) {
|
|
6493
|
+
store.set(documentId, doc, "llm");
|
|
6494
|
+
} else {
|
|
6495
|
+
store.create(doc, documentId, "llm");
|
|
6496
|
+
}
|
|
6497
|
+
}
|
|
6498
|
+
function canonicalSourceName(source, projectDir) {
|
|
6499
|
+
const absProject = resolve6(projectDir);
|
|
6500
|
+
const abs = isAbsolute2(source) ? source : resolve6(absProject, source);
|
|
6501
|
+
if (abs.startsWith(`${absProject}/`)) {
|
|
6502
|
+
return { srcRef: abs.slice(absProject.length + 1), absPath: abs };
|
|
6503
|
+
}
|
|
6504
|
+
return { srcRef: basename4(abs), absPath: abs };
|
|
6505
|
+
}
|
|
6506
|
+
function clipEndFrame(visual, fps) {
|
|
6507
|
+
const start = visual.startFrame ?? 0;
|
|
6508
|
+
const offset = visual.sourceOffset ?? 0;
|
|
6509
|
+
const end = visual.sourceEnd ?? offset;
|
|
6510
|
+
const durFrames = Math.max(0, Math.round((end - offset) * fps));
|
|
6511
|
+
return start + durFrames;
|
|
6512
|
+
}
|
|
6513
|
+
var injectedTranscribeFn;
|
|
6514
|
+
var injectedProbeDurationFn;
|
|
6515
|
+
function __setTranscribeClipFn(fn) {
|
|
6516
|
+
injectedTranscribeFn = fn;
|
|
6517
|
+
}
|
|
6518
|
+
function __setProbeDurationFn(fn) {
|
|
6519
|
+
injectedProbeDurationFn = fn;
|
|
6520
|
+
}
|
|
6521
|
+
function register23(server, store) {
|
|
6522
|
+
server.tool(
|
|
6523
|
+
"atelier_add_clip",
|
|
6524
|
+
"Register a media source as a clip layer on a video or audio track. Idempotent: re-running with the same source preserves the existing clip's placement and applies only explicitly-passed startFrame/sourceOffset/sourceEnd overrides.",
|
|
6525
|
+
{
|
|
6526
|
+
documentId: z48.string().describe("In-memory document ID to reload after disk write"),
|
|
6527
|
+
projectDir: z48.string().describe("Absolute path to the project root (contains project.atelier)"),
|
|
6528
|
+
source: z48.string().describe("Absolute media path OR project-relative path (e.g. media/clip.mp4)"),
|
|
6529
|
+
kind: z48.enum(["video", "audio"]).optional().describe("Clip kind (default: video)"),
|
|
6530
|
+
trackId: z48.string().optional().describe("Track layer id (default: track-video-1 / track-audio-1)"),
|
|
6531
|
+
startFrame: z48.number().int().nonnegative().optional().describe("Composition-frame start (default: end-of-track)"),
|
|
6532
|
+
sourceOffset: z48.number().nonnegative().optional().describe("Seconds into source to begin from"),
|
|
6533
|
+
sourceEnd: z48.number().positive().optional().describe("Seconds into source to stop at"),
|
|
6534
|
+
layerIdHint: z48.string().optional().describe("Explicit layer id override (default: clip-<slug> or aud-<slug>)")
|
|
6535
|
+
},
|
|
6536
|
+
{ readOnlyHint: false, destructiveHint: false },
|
|
6537
|
+
async ({ documentId, projectDir, source, kind, trackId, startFrame, sourceOffset, sourceEnd, layerIdHint }) => {
|
|
6538
|
+
const loaded = loadDocFromDisk(projectDir);
|
|
6539
|
+
if ("error" in loaded) return err23(loaded.error);
|
|
6540
|
+
let { doc } = loaded;
|
|
6541
|
+
const { compositionPath } = loaded;
|
|
6542
|
+
const clipKind = kind ?? "video";
|
|
6543
|
+
const resolvedTrackId = trackId ?? (clipKind === "video" ? VIDEO_TRACK_ID : AUDIO_TRACK_ID);
|
|
6544
|
+
const { srcRef, absPath } = canonicalSourceName(source, projectDir);
|
|
6545
|
+
const slug = slugifyBasename(srcRef);
|
|
6546
|
+
if (!slug) return err23(`cannot derive a slug from source "${source}"`);
|
|
6547
|
+
const layerId = layerIdHint ?? (clipKind === "video" ? `clip-${slug}` : `aud-${slug}`);
|
|
6548
|
+
const probeFn = injectedProbeDurationFn ?? probeDuration;
|
|
6549
|
+
let clipDurationSeconds = 0;
|
|
6550
|
+
try {
|
|
6551
|
+
clipDurationSeconds = await probeFn(absPath);
|
|
6552
|
+
} catch {
|
|
6553
|
+
clipDurationSeconds = 0;
|
|
6554
|
+
}
|
|
6555
|
+
const fps = doc.canvas.fps;
|
|
6556
|
+
const clipDurationFrames = Math.max(1, Math.round(clipDurationSeconds * fps));
|
|
6557
|
+
const existing = doc.layers.find((l) => l.id === layerId);
|
|
6558
|
+
let addedLayer = false;
|
|
6559
|
+
if (existing) {
|
|
6560
|
+
if (existing.visual.type !== clipKind) {
|
|
6561
|
+
return err23(`layer "${layerId}" exists but is type "${existing.visual.type}", not "${clipKind}"`);
|
|
6562
|
+
}
|
|
6563
|
+
const v = existing.visual;
|
|
6564
|
+
const patched = { ...v };
|
|
6565
|
+
if (startFrame !== void 0) patched.startFrame = startFrame;
|
|
6566
|
+
if (sourceOffset !== void 0) patched.sourceOffset = sourceOffset;
|
|
6567
|
+
if (sourceEnd !== void 0) patched.sourceEnd = sourceEnd;
|
|
6568
|
+
doc = {
|
|
6569
|
+
...doc,
|
|
6570
|
+
layers: doc.layers.map((l) => l.id === layerId ? { ...l, visual: patched } : l)
|
|
6571
|
+
};
|
|
6572
|
+
} else {
|
|
6573
|
+
const ensured = ensureClipLayer(doc, {
|
|
6574
|
+
clipLayerId: layerId,
|
|
6575
|
+
trackId: resolvedTrackId,
|
|
6576
|
+
sourceName: srcRef,
|
|
6577
|
+
clipDurationSeconds,
|
|
6578
|
+
clipDurationFrames,
|
|
6579
|
+
kind: clipKind,
|
|
6580
|
+
...startFrame !== void 0 ? { startFrame } : {},
|
|
6581
|
+
...sourceOffset !== void 0 ? { sourceOffset } : {},
|
|
6582
|
+
...sourceEnd !== void 0 ? { sourceEnd } : {}
|
|
6583
|
+
});
|
|
6584
|
+
doc = ensured.doc;
|
|
6585
|
+
addedLayer = ensured.addedLayer;
|
|
6586
|
+
}
|
|
6587
|
+
doc = recomputeTimeline(doc);
|
|
6588
|
+
const final = doc.layers.find((l) => l.id === layerId);
|
|
6589
|
+
if (!final) return err23(`internal: layer "${layerId}" missing after ensureClipLayer`);
|
|
6590
|
+
const visual = final.visual;
|
|
6591
|
+
const resolvedStart = visual.startFrame ?? 0;
|
|
6592
|
+
const resolvedEnd = clipEndFrame(visual, fps);
|
|
6593
|
+
persistDoc(store, documentId, doc, compositionPath);
|
|
6594
|
+
return ok23({
|
|
6595
|
+
layerId,
|
|
6596
|
+
trackId: resolvedTrackId,
|
|
6597
|
+
startFrame: resolvedStart,
|
|
6598
|
+
endFrame: resolvedEnd,
|
|
6599
|
+
addedLayer
|
|
6600
|
+
});
|
|
6601
|
+
}
|
|
6602
|
+
);
|
|
6603
|
+
server.tool(
|
|
6604
|
+
"atelier_move_clip",
|
|
6605
|
+
"Shift a clip's composition-frame start. Caption layers parented to the clip stay aligned automatically (renderer uses clip-local time). Recomputes the timeline.",
|
|
6606
|
+
{
|
|
6607
|
+
documentId: z48.string().describe("In-memory document ID to reload after disk write"),
|
|
6608
|
+
projectDir: z48.string().describe("Absolute path to the project root"),
|
|
6609
|
+
layerId: z48.string().describe("Layer id of the clip to move"),
|
|
6610
|
+
newStartFrame: z48.number().int().nonnegative().describe("New composition-frame start")
|
|
6611
|
+
},
|
|
6612
|
+
{ readOnlyHint: false, destructiveHint: false },
|
|
6613
|
+
async ({ documentId, projectDir, layerId, newStartFrame }) => {
|
|
6614
|
+
const loaded = loadDocFromDisk(projectDir);
|
|
6615
|
+
if ("error" in loaded) return err23(loaded.error);
|
|
6616
|
+
let { doc } = loaded;
|
|
6617
|
+
const { compositionPath } = loaded;
|
|
6618
|
+
const layer = doc.layers.find((l) => l.id === layerId);
|
|
6619
|
+
if (!layer) return err23(`clip layer "${layerId}" not found`);
|
|
6620
|
+
if (layer.visual.type !== "video" && layer.visual.type !== "audio") {
|
|
6621
|
+
return err23(`layer "${layerId}" is type "${layer.visual.type}", not a clip`);
|
|
6622
|
+
}
|
|
6623
|
+
const oldVisual = layer.visual;
|
|
6624
|
+
const oldStartFrame = oldVisual.startFrame ?? 0;
|
|
6625
|
+
const newVisual = { ...oldVisual, startFrame: newStartFrame };
|
|
6626
|
+
doc = {
|
|
6627
|
+
...doc,
|
|
6628
|
+
layers: doc.layers.map((l) => l.id === layerId ? { ...l, visual: newVisual } : l)
|
|
6629
|
+
};
|
|
6630
|
+
doc = recomputeTimeline(doc);
|
|
6631
|
+
const endFrame = clipEndFrame(newVisual, doc.canvas.fps);
|
|
6632
|
+
persistDoc(store, documentId, doc, compositionPath);
|
|
6633
|
+
return ok23({ layerId, oldStartFrame, newStartFrame, endFrame });
|
|
6634
|
+
}
|
|
6635
|
+
);
|
|
6636
|
+
server.tool(
|
|
6637
|
+
"atelier_trim_clip",
|
|
6638
|
+
"Adjust a clip's sourceOffset / sourceEnd (in seconds). Captions outside the new effective range are not auto-pruned \u2014 the renderer skips out-of-range deltas.",
|
|
6639
|
+
{
|
|
6640
|
+
documentId: z48.string().describe("In-memory document ID to reload after disk write"),
|
|
6641
|
+
projectDir: z48.string().describe("Absolute path to the project root"),
|
|
6642
|
+
layerId: z48.string().describe("Layer id of the clip to trim"),
|
|
6643
|
+
sourceOffset: z48.number().nonnegative().optional().describe("New seconds-into-source start"),
|
|
6644
|
+
sourceEnd: z48.number().positive().optional().describe("New seconds-into-source end")
|
|
6645
|
+
},
|
|
6646
|
+
{ readOnlyHint: false, destructiveHint: false },
|
|
6647
|
+
async ({ documentId, projectDir, layerId, sourceOffset, sourceEnd }) => {
|
|
6648
|
+
if (sourceOffset === void 0 && sourceEnd === void 0) {
|
|
6649
|
+
return err23("at least one of sourceOffset / sourceEnd must be provided");
|
|
6650
|
+
}
|
|
6651
|
+
const loaded = loadDocFromDisk(projectDir);
|
|
6652
|
+
if ("error" in loaded) return err23(loaded.error);
|
|
6653
|
+
let { doc } = loaded;
|
|
6654
|
+
const { compositionPath } = loaded;
|
|
6655
|
+
const layer = doc.layers.find((l) => l.id === layerId);
|
|
6656
|
+
if (!layer) return err23(`clip layer "${layerId}" not found`);
|
|
6657
|
+
if (layer.visual.type !== "video" && layer.visual.type !== "audio") {
|
|
6658
|
+
return err23(`layer "${layerId}" is type "${layer.visual.type}", not a clip`);
|
|
6659
|
+
}
|
|
6660
|
+
const oldVisual = layer.visual;
|
|
6661
|
+
const newVisual = { ...oldVisual };
|
|
6662
|
+
if (sourceOffset !== void 0) newVisual.sourceOffset = sourceOffset;
|
|
6663
|
+
if (sourceEnd !== void 0) newVisual.sourceEnd = sourceEnd;
|
|
6664
|
+
doc = {
|
|
6665
|
+
...doc,
|
|
6666
|
+
layers: doc.layers.map((l) => l.id === layerId ? { ...l, visual: newVisual } : l)
|
|
6667
|
+
};
|
|
6668
|
+
doc = recomputeTimeline(doc);
|
|
6669
|
+
persistDoc(store, documentId, doc, compositionPath);
|
|
6670
|
+
return ok23({
|
|
6671
|
+
layerId,
|
|
6672
|
+
sourceOffset: newVisual.sourceOffset ?? 0,
|
|
6673
|
+
sourceEnd: newVisual.sourceEnd,
|
|
6674
|
+
startFrame: newVisual.startFrame ?? 0,
|
|
6675
|
+
endFrame: clipEndFrame(newVisual, doc.canvas.fps)
|
|
6676
|
+
});
|
|
6677
|
+
}
|
|
6678
|
+
);
|
|
6679
|
+
server.tool(
|
|
6680
|
+
"atelier_remove_clip",
|
|
6681
|
+
"Remove a clip layer plus all transitively-parented descendants (e.g. its caption layers) and any deltas referencing them. Refuses to remove a track group layer.",
|
|
6682
|
+
{
|
|
6683
|
+
documentId: z48.string().describe("In-memory document ID to reload after disk write"),
|
|
6684
|
+
projectDir: z48.string().describe("Absolute path to the project root"),
|
|
6685
|
+
layerId: z48.string().describe("Layer id of the clip to remove")
|
|
6686
|
+
},
|
|
6687
|
+
{ readOnlyHint: false, destructiveHint: true },
|
|
6688
|
+
async ({ documentId, projectDir, layerId }) => {
|
|
6689
|
+
const loaded = loadDocFromDisk(projectDir);
|
|
6690
|
+
if ("error" in loaded) return err23(loaded.error);
|
|
6691
|
+
let { doc } = loaded;
|
|
6692
|
+
const { compositionPath } = loaded;
|
|
6693
|
+
const layer = doc.layers.find((l) => l.id === layerId);
|
|
6694
|
+
if (!layer) return err23(`layer "${layerId}" not found`);
|
|
6695
|
+
if ((layer.tags ?? []).includes("track")) {
|
|
6696
|
+
return err23("Cannot remove track group; use atelier_remove_track (not implemented in v1)");
|
|
6697
|
+
}
|
|
6698
|
+
const toRemove = /* @__PURE__ */ new Set([layerId]);
|
|
6699
|
+
let grew = true;
|
|
6700
|
+
while (grew) {
|
|
6701
|
+
grew = false;
|
|
6702
|
+
for (const l of doc.layers) {
|
|
6703
|
+
if (toRemove.has(l.id)) continue;
|
|
6704
|
+
if (l.parentId && toRemove.has(l.parentId)) {
|
|
6705
|
+
toRemove.add(l.id);
|
|
6706
|
+
grew = true;
|
|
6707
|
+
}
|
|
6708
|
+
}
|
|
6709
|
+
}
|
|
6710
|
+
const remainingLayers = doc.layers.filter((l) => !toRemove.has(l.id));
|
|
6711
|
+
const newStates = {};
|
|
6712
|
+
for (const [name, st] of Object.entries(doc.states)) {
|
|
6713
|
+
newStates[name] = {
|
|
6714
|
+
...st,
|
|
6715
|
+
deltas: st.deltas.filter((d) => !toRemove.has(d.layer))
|
|
6716
|
+
};
|
|
6717
|
+
}
|
|
6718
|
+
const assets = { ...doc.assets ?? {} };
|
|
6719
|
+
for (const id of toRemove) delete assets[id];
|
|
6720
|
+
if (Object.keys(assets).length === 0) {
|
|
6721
|
+
const { assets: _omit, ...rest } = doc;
|
|
6722
|
+
void _omit;
|
|
6723
|
+
doc = { ...rest, layers: remainingLayers, states: newStates };
|
|
6724
|
+
} else {
|
|
6725
|
+
doc = { ...doc, layers: remainingLayers, states: newStates, assets };
|
|
6726
|
+
}
|
|
6727
|
+
doc = recomputeTimeline(doc);
|
|
6728
|
+
persistDoc(store, documentId, doc, compositionPath);
|
|
6729
|
+
return ok23({
|
|
6730
|
+
layerId,
|
|
6731
|
+
removedLayerIds: Array.from(toRemove),
|
|
6732
|
+
removedCount: toRemove.size
|
|
6733
|
+
});
|
|
6734
|
+
}
|
|
6735
|
+
);
|
|
6736
|
+
server.tool(
|
|
6737
|
+
"atelier_transcribe_clip",
|
|
6738
|
+
"Run transcription on a clip's source media and (re)build parented caption layers. Audio-layer captions are not supported in v1. Requires a transcribeFn injected via __setTranscribeClipFn (tests) or a pre-existing transcript at <projectDir>/transcripts/<stem>.json.",
|
|
6739
|
+
{
|
|
6740
|
+
documentId: z48.string().describe("In-memory document ID to reload after disk write"),
|
|
6741
|
+
projectDir: z48.string().describe("Absolute path to the project root"),
|
|
6742
|
+
layerId: z48.string().describe("Video clip layer id"),
|
|
6743
|
+
model: z48.string().optional().describe("Whisper model selection forwarded to the injected transcribeFn")
|
|
6744
|
+
},
|
|
6745
|
+
{ readOnlyHint: false, destructiveHint: false },
|
|
6746
|
+
async ({ documentId, projectDir, layerId, model }) => {
|
|
6747
|
+
const loaded = loadDocFromDisk(projectDir);
|
|
6748
|
+
if ("error" in loaded) return err23(loaded.error);
|
|
6749
|
+
let { doc } = loaded;
|
|
6750
|
+
const { compositionPath } = loaded;
|
|
6751
|
+
const layer = doc.layers.find((l) => l.id === layerId);
|
|
6752
|
+
if (!layer) return err23(`clip layer "${layerId}" not found`);
|
|
6753
|
+
if (layer.visual.type === "audio") {
|
|
6754
|
+
return err23("audio layer captions are not supported in v1");
|
|
6755
|
+
}
|
|
6756
|
+
if (layer.visual.type !== "video") {
|
|
6757
|
+
return err23(`layer "${layerId}" is type "${layer.visual.type}", not a video clip`);
|
|
6758
|
+
}
|
|
6759
|
+
const v = layer.visual;
|
|
6760
|
+
const srcRef = v.src;
|
|
6761
|
+
if (!srcRef) return err23(`video layer "${layerId}" has no src`);
|
|
6762
|
+
const mediaBasename = basename4(srcRef);
|
|
6763
|
+
let transcript;
|
|
6764
|
+
let transcriptPath;
|
|
6765
|
+
if (injectedTranscribeFn) {
|
|
6766
|
+
const result = await injectedTranscribeFn({
|
|
6767
|
+
projectDir,
|
|
6768
|
+
mediaBasename,
|
|
6769
|
+
...model !== void 0 ? { model } : {}
|
|
6770
|
+
});
|
|
6771
|
+
transcript = result.transcript;
|
|
6772
|
+
transcriptPath = result.transcriptPath;
|
|
6773
|
+
} else {
|
|
6774
|
+
const stem = stripExt2(mediaBasename);
|
|
6775
|
+
const candidate = join6(resolve6(projectDir), "transcripts", `${stem}.json`);
|
|
6776
|
+
if (!existsSync6(candidate)) {
|
|
6777
|
+
return err23(
|
|
6778
|
+
`no transcribeFn injected and no transcript mirror at ${candidate}. Install a host-provided transcribeFn via __setTranscribeClipFn or pre-write the transcript.`
|
|
6779
|
+
);
|
|
6780
|
+
}
|
|
6781
|
+
try {
|
|
6782
|
+
transcript = JSON.parse(readFileSync4(candidate, "utf-8"));
|
|
6783
|
+
transcriptPath = candidate;
|
|
6784
|
+
} catch (e) {
|
|
6785
|
+
return err23(`failed to read transcript at ${candidate}: ${e.message}`);
|
|
6786
|
+
}
|
|
6787
|
+
}
|
|
6788
|
+
if (!transcript) return err23("transcribeFn returned no transcript");
|
|
6789
|
+
doc = rebuildClipCaptions(doc, layerId, transcript, doc.canvas);
|
|
6790
|
+
doc = recomputeTimeline(doc);
|
|
6791
|
+
const captionCount = doc.layers.filter(
|
|
6792
|
+
(l) => l.parentId === layerId && (l.tags ?? []).includes("caption")
|
|
6793
|
+
).length;
|
|
6794
|
+
persistDoc(store, documentId, doc, compositionPath);
|
|
6795
|
+
return ok23({
|
|
6796
|
+
layerId,
|
|
6797
|
+
transcriptPath: transcriptPath ?? null,
|
|
6798
|
+
captionCount
|
|
6799
|
+
});
|
|
6800
|
+
}
|
|
6801
|
+
);
|
|
6802
|
+
server.tool(
|
|
6803
|
+
"atelier_move_slide",
|
|
6804
|
+
"Reorder a carousel project's slides[] array. Refuses on non-carousel projects.",
|
|
6805
|
+
{
|
|
6806
|
+
documentId: z48.string().describe("In-memory document ID to reload after disk write"),
|
|
6807
|
+
projectDir: z48.string().describe("Absolute path to the project root"),
|
|
6808
|
+
fromIndex: z48.number().int().nonnegative().describe("Current index in slides[]"),
|
|
6809
|
+
toIndex: z48.number().int().nonnegative().describe("Target index in slides[]")
|
|
6810
|
+
},
|
|
6811
|
+
{ readOnlyHint: false, destructiveHint: false },
|
|
6812
|
+
async ({ documentId, projectDir, fromIndex, toIndex }) => {
|
|
6813
|
+
const loaded = loadDocFromDisk(projectDir);
|
|
6814
|
+
if ("error" in loaded) return err23(loaded.error);
|
|
6815
|
+
let { doc } = loaded;
|
|
6816
|
+
const { compositionPath } = loaded;
|
|
6817
|
+
if (doc.kind !== "carousel") {
|
|
6818
|
+
return err23(`project kind is "${doc.kind ?? "video"}", not "carousel"`);
|
|
6819
|
+
}
|
|
6820
|
+
const slides = [...doc.slides ?? []];
|
|
6821
|
+
if (fromIndex >= slides.length) {
|
|
6822
|
+
return err23(`fromIndex ${fromIndex} out of bounds (slides.length=${slides.length})`);
|
|
6823
|
+
}
|
|
6824
|
+
if (toIndex >= slides.length) {
|
|
6825
|
+
return err23(`toIndex ${toIndex} out of bounds (slides.length=${slides.length})`);
|
|
6826
|
+
}
|
|
6827
|
+
const [moved] = slides.splice(fromIndex, 1);
|
|
6828
|
+
slides.splice(toIndex, 0, moved);
|
|
6829
|
+
doc = { ...doc, slides };
|
|
6830
|
+
persistDoc(store, documentId, doc, compositionPath);
|
|
6831
|
+
return ok23({ slides });
|
|
6832
|
+
}
|
|
6833
|
+
);
|
|
6834
|
+
}
|
|
6835
|
+
function stripExt2(name) {
|
|
6836
|
+
const ext = extname4(name);
|
|
6837
|
+
return ext ? name.slice(0, -ext.length) : name;
|
|
6838
|
+
}
|
|
6839
|
+
|
|
6840
|
+
// ../mcp/src/tools/rename-media.ts
|
|
6841
|
+
import { z as z49 } from "zod";
|
|
6842
|
+
|
|
6843
|
+
// ../mcp/src/lib/rename-media.ts
|
|
6844
|
+
import {
|
|
6845
|
+
existsSync as existsSync7,
|
|
6846
|
+
readFileSync as readFileSync5,
|
|
6847
|
+
renameSync,
|
|
6848
|
+
writeFileSync as writeFileSync4
|
|
6849
|
+
} from "fs";
|
|
6850
|
+
import { extname as extname5, join as join7, resolve as resolve7 } from "path";
|
|
6851
|
+
var renameLocks = /* @__PURE__ */ new Map();
|
|
6852
|
+
function readAtelierDoc(absPath) {
|
|
6853
|
+
const raw = readFileSync5(absPath, "utf-8");
|
|
6854
|
+
const parsed = parseAtelier(raw);
|
|
6855
|
+
if (parsed.success) return parsed.data;
|
|
6856
|
+
return JSON.parse(raw);
|
|
6857
|
+
}
|
|
6858
|
+
function stripExt3(name) {
|
|
6859
|
+
const ext = extname5(name);
|
|
6860
|
+
return ext ? name.slice(0, -ext.length) : name;
|
|
6861
|
+
}
|
|
6862
|
+
function srcReferencesBasename(src, oldBasename) {
|
|
6863
|
+
if (typeof src !== "string" || src.length === 0) return false;
|
|
6864
|
+
if (src === oldBasename) return true;
|
|
6865
|
+
if (src === `media/${oldBasename}`) return true;
|
|
6866
|
+
if (src.endsWith(`/${oldBasename}`)) return true;
|
|
6867
|
+
return false;
|
|
6868
|
+
}
|
|
6869
|
+
function rewriteSrc(src, oldBasename, newBasename) {
|
|
6870
|
+
if (src === oldBasename) return newBasename;
|
|
6871
|
+
if (src === `media/${oldBasename}`) return `media/${newBasename}`;
|
|
6872
|
+
if (src.endsWith(`/${oldBasename}`)) {
|
|
6873
|
+
return src.slice(0, -oldBasename.length) + newBasename;
|
|
6874
|
+
}
|
|
6875
|
+
return src;
|
|
6876
|
+
}
|
|
6877
|
+
function applyMediaRenameToDoc(doc, oldBasename, newBasename) {
|
|
6878
|
+
const assetKeysUpdated = [];
|
|
6879
|
+
const layersUpdated = [];
|
|
6880
|
+
let newAssets = void 0;
|
|
6881
|
+
if (doc.assets && typeof doc.assets === "object") {
|
|
6882
|
+
const out2 = {};
|
|
6883
|
+
for (const [key, asset] of Object.entries(doc.assets)) {
|
|
6884
|
+
if (asset && typeof asset === "object" && srcReferencesBasename(asset.src, oldBasename)) {
|
|
6885
|
+
out2[key] = { ...asset, src: rewriteSrc(asset.src, oldBasename, newBasename) };
|
|
6886
|
+
assetKeysUpdated.push(key);
|
|
6887
|
+
} else {
|
|
6888
|
+
out2[key] = asset;
|
|
6889
|
+
}
|
|
6890
|
+
}
|
|
6891
|
+
newAssets = out2;
|
|
6892
|
+
}
|
|
6893
|
+
const newLayers = doc.layers.map((layer) => {
|
|
6894
|
+
const v = layer.visual;
|
|
6895
|
+
if (!v || v.type !== "video" && v.type !== "audio" && v.type !== "image") {
|
|
6896
|
+
return layer;
|
|
6897
|
+
}
|
|
6898
|
+
const visualWithSrc = v;
|
|
6899
|
+
if (!srcReferencesBasename(visualWithSrc.src, oldBasename)) return layer;
|
|
6900
|
+
layersUpdated.push(layer.id);
|
|
6901
|
+
return {
|
|
6902
|
+
...layer,
|
|
6903
|
+
visual: {
|
|
6904
|
+
...v,
|
|
6905
|
+
src: rewriteSrc(visualWithSrc.src, oldBasename, newBasename)
|
|
6906
|
+
}
|
|
6907
|
+
};
|
|
6908
|
+
});
|
|
6909
|
+
const out = { ...doc, layers: newLayers };
|
|
6910
|
+
if (newAssets !== void 0) out.assets = newAssets;
|
|
6911
|
+
return { doc: out, assetKeysUpdated, layersUpdated };
|
|
6912
|
+
}
|
|
6913
|
+
function scanArtifactsForOldBasename(projectDir, oldBasename) {
|
|
6914
|
+
const warnings = [];
|
|
6915
|
+
const artifactFiles = ["DESIGN.md", "SCRIPT.md", "STORYBOARD.md"];
|
|
6916
|
+
for (const name of artifactFiles) {
|
|
6917
|
+
const absPath = join7(projectDir, name);
|
|
6918
|
+
if (!existsSync7(absPath)) continue;
|
|
6919
|
+
try {
|
|
6920
|
+
const raw = readFileSync5(absPath, "utf-8");
|
|
6921
|
+
if (raw.includes(oldBasename)) {
|
|
6922
|
+
warnings.push(`${name} references old path '${oldBasename}'`);
|
|
6923
|
+
}
|
|
6924
|
+
} catch {
|
|
6925
|
+
}
|
|
6926
|
+
}
|
|
6927
|
+
return warnings;
|
|
6928
|
+
}
|
|
6929
|
+
async function renameMedia(opts) {
|
|
6930
|
+
const projectDir = resolve7(opts.projectDir);
|
|
6931
|
+
const oldBasename = opts.oldBasename;
|
|
6932
|
+
const newBasename = opts.newBasename;
|
|
6933
|
+
if (oldBasename === newBasename) {
|
|
6934
|
+
return {
|
|
6935
|
+
docUpdated: false,
|
|
6936
|
+
transcriptRenamed: false,
|
|
6937
|
+
assetKeysUpdated: [],
|
|
6938
|
+
layersUpdated: [],
|
|
6939
|
+
timelineRegenerated: false,
|
|
6940
|
+
warnings: []
|
|
6941
|
+
};
|
|
6942
|
+
}
|
|
6943
|
+
const lockKey = projectDir;
|
|
6944
|
+
const prev = renameLocks.get(lockKey) ?? Promise.resolve();
|
|
6945
|
+
const run = prev.catch(() => {
|
|
6946
|
+
}).then(() => renameMediaImpl(projectDir, oldBasename, newBasename, opts.dryRun));
|
|
6947
|
+
renameLocks.set(lockKey, run);
|
|
6948
|
+
try {
|
|
6949
|
+
return await run;
|
|
6950
|
+
} finally {
|
|
6951
|
+
if (renameLocks.get(lockKey) === run) renameLocks.delete(lockKey);
|
|
6952
|
+
}
|
|
6953
|
+
}
|
|
6954
|
+
async function renameMediaImpl(projectDir, oldBasename, newBasename, dryRun) {
|
|
6955
|
+
const mediaDir = join7(projectDir, "media");
|
|
6956
|
+
if (!existsSync7(mediaDir)) {
|
|
6957
|
+
throw new Error(`renameMedia: project has no media/ dir at ${mediaDir}`);
|
|
6958
|
+
}
|
|
6959
|
+
const mediaOldAbs = join7(mediaDir, oldBasename);
|
|
6960
|
+
const mediaNewAbs = join7(mediaDir, newBasename);
|
|
6961
|
+
if (!existsSync7(mediaOldAbs)) {
|
|
6962
|
+
throw new Error(`renameMedia: media file not found: ${mediaOldAbs}`);
|
|
6963
|
+
}
|
|
6964
|
+
if (existsSync7(mediaNewAbs)) {
|
|
6965
|
+
throw new Error(`renameMedia: destination file already exists: ${mediaNewAbs}`);
|
|
6966
|
+
}
|
|
6967
|
+
const transcriptsDir = join7(projectDir, "transcripts");
|
|
6968
|
+
const transcriptOldAbs = join7(transcriptsDir, `${stripExt3(oldBasename)}.json`);
|
|
6969
|
+
const transcriptNewAbs = join7(transcriptsDir, `${stripExt3(newBasename)}.json`);
|
|
6970
|
+
const transcriptExists = existsSync7(transcriptOldAbs);
|
|
6971
|
+
const docPath = join7(projectDir, "project.atelier");
|
|
6972
|
+
if (!existsSync7(docPath)) {
|
|
6973
|
+
throw new Error(`renameMedia: missing project.atelier at ${docPath}`);
|
|
6974
|
+
}
|
|
6975
|
+
const originalDoc = readAtelierDoc(docPath);
|
|
6976
|
+
const walked = applyMediaRenameToDoc(originalDoc, oldBasename, newBasename);
|
|
6977
|
+
const hadTimeline = originalDoc.timeline !== void 0;
|
|
6978
|
+
const newDoc = recomputeTimeline(walked.doc);
|
|
6979
|
+
const hasTimelineAfter = newDoc.timeline !== void 0;
|
|
6980
|
+
const timelineRegenerated = hadTimeline || hasTimelineAfter;
|
|
6981
|
+
const warnings = scanArtifactsForOldBasename(projectDir, oldBasename);
|
|
6982
|
+
if (dryRun === true) {
|
|
6983
|
+
return {
|
|
6984
|
+
docUpdated: false,
|
|
6985
|
+
transcriptRenamed: false,
|
|
6986
|
+
assetKeysUpdated: walked.assetKeysUpdated,
|
|
6987
|
+
layersUpdated: walked.layersUpdated,
|
|
6988
|
+
timelineRegenerated: false,
|
|
6989
|
+
warnings
|
|
6990
|
+
};
|
|
6991
|
+
}
|
|
6992
|
+
let mediaRenamed = false;
|
|
6993
|
+
try {
|
|
6994
|
+
renameSync(mediaOldAbs, mediaNewAbs);
|
|
6995
|
+
mediaRenamed = true;
|
|
6996
|
+
} catch (err24) {
|
|
6997
|
+
throw new Error(
|
|
6998
|
+
`renameMedia: failed to rename media file: ${err24 instanceof Error ? err24.message : String(err24)}`
|
|
6999
|
+
);
|
|
7000
|
+
}
|
|
7001
|
+
let transcriptRenamed = false;
|
|
7002
|
+
if (transcriptExists) {
|
|
7003
|
+
try {
|
|
7004
|
+
renameSync(transcriptOldAbs, transcriptNewAbs);
|
|
7005
|
+
transcriptRenamed = true;
|
|
7006
|
+
} catch (err24) {
|
|
7007
|
+
try {
|
|
7008
|
+
renameSync(mediaNewAbs, mediaOldAbs);
|
|
7009
|
+
} catch (rbErr) {
|
|
7010
|
+
console.error(
|
|
7011
|
+
`renameMedia: CRITICAL \u2014 rollback of media rename failed: ${rbErr instanceof Error ? rbErr.message : String(rbErr)}`
|
|
7012
|
+
);
|
|
7013
|
+
}
|
|
7014
|
+
throw new Error(
|
|
7015
|
+
`renameMedia: failed to rename transcript: ${err24 instanceof Error ? err24.message : String(err24)}`
|
|
7016
|
+
);
|
|
7017
|
+
}
|
|
7018
|
+
}
|
|
7019
|
+
try {
|
|
7020
|
+
writeFileSync4(docPath, JSON.stringify(newDoc, null, 2), "utf-8");
|
|
7021
|
+
} catch (err24) {
|
|
7022
|
+
if (transcriptRenamed) {
|
|
7023
|
+
try {
|
|
7024
|
+
renameSync(transcriptNewAbs, transcriptOldAbs);
|
|
7025
|
+
} catch (rbErr) {
|
|
7026
|
+
console.error(
|
|
7027
|
+
`renameMedia: CRITICAL \u2014 rollback of transcript rename failed: ${rbErr instanceof Error ? rbErr.message : String(rbErr)}`
|
|
7028
|
+
);
|
|
7029
|
+
}
|
|
7030
|
+
}
|
|
7031
|
+
if (mediaRenamed) {
|
|
7032
|
+
try {
|
|
7033
|
+
renameSync(mediaNewAbs, mediaOldAbs);
|
|
7034
|
+
} catch (rbErr) {
|
|
7035
|
+
console.error(
|
|
7036
|
+
`renameMedia: CRITICAL \u2014 rollback of media rename failed: ${rbErr instanceof Error ? rbErr.message : String(rbErr)}`
|
|
7037
|
+
);
|
|
7038
|
+
}
|
|
7039
|
+
}
|
|
7040
|
+
throw new Error(
|
|
7041
|
+
`renameMedia: failed to write project.atelier: ${err24 instanceof Error ? err24.message : String(err24)}`
|
|
7042
|
+
);
|
|
7043
|
+
}
|
|
7044
|
+
return {
|
|
7045
|
+
docUpdated: true,
|
|
7046
|
+
transcriptRenamed,
|
|
7047
|
+
assetKeysUpdated: walked.assetKeysUpdated,
|
|
7048
|
+
layersUpdated: walked.layersUpdated,
|
|
7049
|
+
timelineRegenerated,
|
|
7050
|
+
warnings
|
|
7051
|
+
};
|
|
7052
|
+
}
|
|
7053
|
+
function isValidMediaBasenameForRename(name) {
|
|
7054
|
+
if (typeof name !== "string") return false;
|
|
7055
|
+
if (name.length === 0) return false;
|
|
7056
|
+
if (name !== name.trim()) return false;
|
|
7057
|
+
if (name.includes("/") || name.includes("\\")) return false;
|
|
7058
|
+
if (name === "." || name === "..") return false;
|
|
7059
|
+
if (name.startsWith(".")) return false;
|
|
7060
|
+
if (/[\x00-\x1f]/.test(name)) return false;
|
|
7061
|
+
return true;
|
|
7062
|
+
}
|
|
7063
|
+
|
|
7064
|
+
// ../mcp/src/tools/rename-media.ts
|
|
7065
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
|
|
7066
|
+
import { join as join8, resolve as resolve8, sep } from "path";
|
|
7067
|
+
function register24(server, store) {
|
|
7068
|
+
server.tool(
|
|
7069
|
+
"atelier_rename_media",
|
|
7070
|
+
"Rename a media file in a project, cascading the rename through transcripts and the project document. Atomic with rollback on partial failure.",
|
|
7071
|
+
{
|
|
7072
|
+
documentId: z49.string().describe("Document ID"),
|
|
7073
|
+
projectDir: z49.string().describe("Absolute project directory path"),
|
|
7074
|
+
oldBasename: z49.string().describe("Current basename inside media/, e.g. 'IMG_7346.MOV'"),
|
|
7075
|
+
newBasename: z49.string().describe("New basename inside media/"),
|
|
7076
|
+
dryRun: z49.boolean().optional().describe("If true, compute the result without writing"),
|
|
7077
|
+
workspaceDir: z49.string().optional().describe("Workspace root for security scoping (defaults to process.cwd())")
|
|
7078
|
+
},
|
|
7079
|
+
{ readOnlyHint: false, destructiveHint: true },
|
|
7080
|
+
async ({ documentId, projectDir, oldBasename, newBasename, dryRun, workspaceDir }) => {
|
|
7081
|
+
try {
|
|
7082
|
+
if (!isValidMediaBasenameForRename(oldBasename)) {
|
|
7083
|
+
return {
|
|
7084
|
+
content: [{ type: "text", text: JSON.stringify({ error: "invalid oldBasename" }) }],
|
|
7085
|
+
isError: true
|
|
7086
|
+
};
|
|
7087
|
+
}
|
|
7088
|
+
if (!isValidMediaBasenameForRename(newBasename)) {
|
|
7089
|
+
return {
|
|
7090
|
+
content: [{ type: "text", text: JSON.stringify({ error: "invalid newBasename" }) }],
|
|
7091
|
+
isError: true
|
|
7092
|
+
};
|
|
7093
|
+
}
|
|
7094
|
+
const workspaceRoot = resolve8(workspaceDir ?? process.cwd());
|
|
7095
|
+
const resolvedProject = resolve8(projectDir);
|
|
7096
|
+
if (resolvedProject !== workspaceRoot && !resolvedProject.startsWith(workspaceRoot + sep)) {
|
|
7097
|
+
return {
|
|
7098
|
+
content: [{ type: "text", text: JSON.stringify({ error: `projectDir must be inside workspace ${workspaceRoot}` }) }],
|
|
7099
|
+
isError: true
|
|
7100
|
+
};
|
|
7101
|
+
}
|
|
7102
|
+
if (!existsSync8(join8(resolvedProject, "project.atelier"))) {
|
|
7103
|
+
return {
|
|
7104
|
+
content: [{ type: "text", text: JSON.stringify({ error: "projectDir does not contain project.atelier" }) }],
|
|
7105
|
+
isError: true
|
|
7106
|
+
};
|
|
7107
|
+
}
|
|
7108
|
+
const result = await renameMedia({
|
|
7109
|
+
projectDir: resolvedProject,
|
|
7110
|
+
oldBasename,
|
|
7111
|
+
newBasename,
|
|
7112
|
+
...dryRun !== void 0 ? { dryRun } : {}
|
|
7113
|
+
});
|
|
7114
|
+
if (!dryRun) {
|
|
7115
|
+
const docPath = join8(resolvedProject, "project.atelier");
|
|
7116
|
+
const raw = readFileSync6(docPath, "utf-8");
|
|
7117
|
+
const parsed = parseAtelier(raw);
|
|
7118
|
+
if (parsed.success) {
|
|
7119
|
+
store.set(documentId, parsed.data, "system");
|
|
7120
|
+
}
|
|
7121
|
+
}
|
|
7122
|
+
return {
|
|
7123
|
+
content: [{ type: "text", text: JSON.stringify({ success: true, result }) }]
|
|
7124
|
+
};
|
|
7125
|
+
} catch (e) {
|
|
7126
|
+
return {
|
|
7127
|
+
content: [{ type: "text", text: JSON.stringify({ error: e.message }) }],
|
|
7128
|
+
isError: true
|
|
7129
|
+
};
|
|
7130
|
+
}
|
|
7131
|
+
}
|
|
7132
|
+
);
|
|
7133
|
+
}
|
|
7134
|
+
|
|
7135
|
+
// ../mcp/src/tools/docs.ts
|
|
7136
|
+
import { z as z50 } from "zod";
|
|
7137
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
|
|
7138
|
+
import { join as join10, resolve as resolve10, sep as sep2 } from "path";
|
|
7139
|
+
|
|
7140
|
+
// ../mcp/src/lib/doc-management.ts
|
|
7141
|
+
import {
|
|
7142
|
+
existsSync as existsSync9,
|
|
7143
|
+
mkdirSync as mkdirSync2,
|
|
7144
|
+
readdirSync as readdirSync5,
|
|
7145
|
+
readFileSync as readFileSync7,
|
|
7146
|
+
statSync as statSync5,
|
|
7147
|
+
unlinkSync,
|
|
7148
|
+
writeFileSync as writeFileSync5
|
|
7149
|
+
} from "fs";
|
|
7150
|
+
import { basename as basename5, dirname as dirname2, extname as extname6, isAbsolute as isAbsolute3, join as join9, relative, resolve as resolve9 } from "path";
|
|
7151
|
+
|
|
7152
|
+
// ../mcp/src/lib/doc-management-types.ts
|
|
7153
|
+
function isValidDocName(name) {
|
|
7154
|
+
if (typeof name !== "string") return false;
|
|
7155
|
+
const trimmed = name.trim();
|
|
7156
|
+
if (trimmed.length === 0) return false;
|
|
7157
|
+
if (trimmed.length > 200) return false;
|
|
7158
|
+
if (trimmed.startsWith(".")) return false;
|
|
7159
|
+
if (trimmed.includes("/") || trimmed.includes("\\") || trimmed.includes("..")) return false;
|
|
7160
|
+
if (/[\x00-\x1f]/.test(trimmed)) return false;
|
|
7161
|
+
return true;
|
|
7162
|
+
}
|
|
7163
|
+
|
|
7164
|
+
// ../mcp/src/lib/doc-management.ts
|
|
7165
|
+
function slugifyBasename2(name) {
|
|
7166
|
+
const noExt = name.replace(/\.[^.]+$/, "");
|
|
7167
|
+
const slugged = noExt.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
7168
|
+
return slugged;
|
|
7169
|
+
}
|
|
7170
|
+
function readAtelierDoc2(absPath) {
|
|
7171
|
+
const raw = readFileSync7(absPath, "utf-8");
|
|
7172
|
+
const parsed = parseAtelier(raw);
|
|
7173
|
+
if (parsed.success) return parsed.data;
|
|
7174
|
+
return JSON.parse(raw);
|
|
7175
|
+
}
|
|
7176
|
+
function scaffoldDoc(slug, kind) {
|
|
7177
|
+
const base = {
|
|
7178
|
+
version: "1.0",
|
|
7179
|
+
name: slug,
|
|
7180
|
+
kind,
|
|
7181
|
+
canvas: { width: 1080, height: 1920, fps: 30 },
|
|
7182
|
+
layers: [],
|
|
7183
|
+
states: { default: { duration: 1, deltas: [] } }
|
|
7184
|
+
};
|
|
7185
|
+
if (kind === "carousel") {
|
|
7186
|
+
base.slides = [];
|
|
7187
|
+
}
|
|
7188
|
+
return base;
|
|
7189
|
+
}
|
|
7190
|
+
function createDoc(opts) {
|
|
7191
|
+
if (!isValidDocName(opts.name)) {
|
|
7192
|
+
throw new Error("invalid doc name");
|
|
7193
|
+
}
|
|
7194
|
+
const projectDir = resolve9(opts.projectDir);
|
|
7195
|
+
const slug = slugifyBasename2(opts.name);
|
|
7196
|
+
if (slug.length === 0) {
|
|
7197
|
+
throw new Error("invalid doc name");
|
|
7198
|
+
}
|
|
7199
|
+
const subdir = opts.asCarouselSlide ? "slides" : "";
|
|
7200
|
+
const baseDir = subdir ? join9(projectDir, subdir) : projectDir;
|
|
7201
|
+
let finalStem = slug;
|
|
7202
|
+
let attempt = 0;
|
|
7203
|
+
while (existsSync9(join9(baseDir, `${finalStem}.atelier`))) {
|
|
7204
|
+
attempt += 1;
|
|
7205
|
+
if (attempt > 999) {
|
|
7206
|
+
throw new Error(`createDoc: could not find a non-colliding stem for "${slug}"`);
|
|
7207
|
+
}
|
|
7208
|
+
finalStem = `${slug}-${attempt}`;
|
|
7209
|
+
}
|
|
7210
|
+
const newDocAbsPath = join9(baseDir, `${finalStem}.atelier`);
|
|
7211
|
+
const newDocPath = subdir ? `${subdir}/${finalStem}.atelier` : `${finalStem}.atelier`;
|
|
7212
|
+
const newDoc = scaffoldDoc(finalStem, opts.kind);
|
|
7213
|
+
mkdirSync2(dirname2(newDocAbsPath), { recursive: true });
|
|
7214
|
+
writeFileSync5(newDocAbsPath, JSON.stringify(newDoc, null, 2), "utf-8");
|
|
7215
|
+
if (opts.asCarouselSlide) {
|
|
7216
|
+
if (!opts.carouselDocPath) {
|
|
7217
|
+
throw new Error("createDoc: asCarouselSlide requires carouselDocPath");
|
|
7218
|
+
}
|
|
7219
|
+
const carouselAbs = resolve9(opts.carouselDocPath);
|
|
7220
|
+
if (!existsSync9(carouselAbs)) {
|
|
7221
|
+
throw new Error(`createDoc: carouselDocPath does not exist: ${carouselAbs}`);
|
|
7222
|
+
}
|
|
7223
|
+
const carouselDoc = readAtelierDoc2(carouselAbs);
|
|
7224
|
+
if (carouselDoc.kind !== "carousel") {
|
|
7225
|
+
throw new Error(`createDoc: carouselDocPath is not a carousel doc (kind=${carouselDoc.kind ?? "video"})`);
|
|
7226
|
+
}
|
|
7227
|
+
const slideSrc = relative(projectDir, newDocAbsPath).split("\\").join("/");
|
|
7228
|
+
const slides = carouselDoc.slides ?? [];
|
|
7229
|
+
if (slides.some((s) => s.src === slideSrc)) {
|
|
7230
|
+
try {
|
|
7231
|
+
unlinkSync(newDocAbsPath);
|
|
7232
|
+
} catch {
|
|
7233
|
+
}
|
|
7234
|
+
throw new Error(`createDoc: slide ${slideSrc} already exists in carousel`);
|
|
7235
|
+
}
|
|
7236
|
+
const insertAt = typeof opts.insertAt === "number" && opts.insertAt >= 0 && opts.insertAt <= slides.length ? opts.insertAt : slides.length;
|
|
7237
|
+
const newSlide = { src: slideSrc };
|
|
7238
|
+
const newSlides = [...slides.slice(0, insertAt), newSlide, ...slides.slice(insertAt)];
|
|
7239
|
+
const updatedCarousel = { ...carouselDoc, slides: newSlides };
|
|
7240
|
+
writeFileSync5(carouselAbs, JSON.stringify(updatedCarousel, null, 2), "utf-8");
|
|
7241
|
+
return { newDocPath, newDocAbsPath, appendedSlideIndex: insertAt };
|
|
7242
|
+
}
|
|
7243
|
+
return { newDocPath, newDocAbsPath };
|
|
7244
|
+
}
|
|
7245
|
+
function duplicateDoc(opts) {
|
|
7246
|
+
const projectDir = resolve9(opts.projectDir);
|
|
7247
|
+
const sourceAbs = join9(projectDir, opts.sourceDocPath);
|
|
7248
|
+
if (!existsSync9(sourceAbs)) {
|
|
7249
|
+
throw new Error(`duplicateDoc: source doc not found: ${opts.sourceDocPath}`);
|
|
7250
|
+
}
|
|
7251
|
+
const sourceDoc = readAtelierDoc2(sourceAbs);
|
|
7252
|
+
const sourceDir = dirname2(sourceAbs);
|
|
7253
|
+
const sourceBase = basename5(sourceAbs);
|
|
7254
|
+
const sourceExt = extname6(sourceBase);
|
|
7255
|
+
const sourceStem = sourceExt ? sourceBase.slice(0, -sourceExt.length) : sourceBase;
|
|
7256
|
+
let finalStem;
|
|
7257
|
+
for (let attempt = 1; attempt <= 999; attempt++) {
|
|
7258
|
+
const candidate = attempt === 1 ? `${sourceStem}-copy` : `${sourceStem}-copy-${attempt}`;
|
|
7259
|
+
if (!existsSync9(join9(sourceDir, `${candidate}.atelier`))) {
|
|
7260
|
+
finalStem = candidate;
|
|
7261
|
+
break;
|
|
7262
|
+
}
|
|
7263
|
+
}
|
|
7264
|
+
if (!finalStem) {
|
|
7265
|
+
throw new Error(`duplicateDoc: could not find a non-colliding stem for "${sourceStem}-copy"`);
|
|
7266
|
+
}
|
|
7267
|
+
const newDocAbsPath = join9(sourceDir, `${finalStem}.atelier`);
|
|
7268
|
+
const sourceRel = opts.sourceDocPath;
|
|
7269
|
+
const lastSlash = sourceRel.lastIndexOf("/");
|
|
7270
|
+
const parentRel = lastSlash >= 0 ? sourceRel.slice(0, lastSlash + 1) : "";
|
|
7271
|
+
const newDocPath = `${parentRel}${finalStem}.atelier`;
|
|
7272
|
+
const dupDoc = { ...sourceDoc };
|
|
7273
|
+
writeFileSync5(newDocAbsPath, JSON.stringify(dupDoc, null, 2), "utf-8");
|
|
7274
|
+
const report = {
|
|
7275
|
+
sourceDocPath: opts.sourceDocPath,
|
|
7276
|
+
newDocPath
|
|
7277
|
+
};
|
|
7278
|
+
if (opts.appendSlideRef) {
|
|
7279
|
+
const sourceDirRel = dirname2(opts.sourceDocPath);
|
|
7280
|
+
if (sourceDirRel !== "slides") {
|
|
7281
|
+
try {
|
|
7282
|
+
unlinkSync(newDocAbsPath);
|
|
7283
|
+
} catch {
|
|
7284
|
+
}
|
|
7285
|
+
throw new Error("appendSlideRef requires a slide source");
|
|
7286
|
+
}
|
|
7287
|
+
const manifestAbs = join9(projectDir, "project.atelier");
|
|
7288
|
+
if (!existsSync9(manifestAbs)) {
|
|
7289
|
+
throw new Error("duplicateDoc: appendSlideRef requested but project.atelier is missing");
|
|
7290
|
+
}
|
|
7291
|
+
const manifest = readAtelierDoc2(manifestAbs);
|
|
7292
|
+
if (manifest.kind !== "carousel") {
|
|
7293
|
+
throw new Error(`duplicateDoc: appendSlideRef requires a carousel manifest (kind=${manifest.kind ?? "video"})`);
|
|
7294
|
+
}
|
|
7295
|
+
const slideSrc = relative(projectDir, newDocAbsPath).split("\\").join("/");
|
|
7296
|
+
const slides = manifest.slides ?? [];
|
|
7297
|
+
if (slides.some((s) => s.src === slideSrc)) {
|
|
7298
|
+
throw new Error(`duplicateDoc: slide ${slideSrc} already exists in carousel`);
|
|
7299
|
+
}
|
|
7300
|
+
const sourceSlideSrc = relative(projectDir, sourceAbs).split("\\").join("/");
|
|
7301
|
+
const sourceIndex = slides.findIndex((s) => s.src === sourceSlideSrc);
|
|
7302
|
+
const insertAt = sourceIndex >= 0 ? sourceIndex + 1 : slides.length;
|
|
7303
|
+
const newSlide = { src: slideSrc };
|
|
7304
|
+
const newSlides = [...slides.slice(0, insertAt), newSlide, ...slides.slice(insertAt)];
|
|
7305
|
+
const updatedManifest = { ...manifest, slides: newSlides };
|
|
7306
|
+
writeFileSync5(manifestAbs, JSON.stringify(updatedManifest, null, 2), "utf-8");
|
|
7307
|
+
report.appendedSlideIndex = insertAt;
|
|
7308
|
+
}
|
|
7309
|
+
return report;
|
|
7310
|
+
}
|
|
7311
|
+
function deleteDoc(opts) {
|
|
7312
|
+
const workspaceDir = resolve9(opts.workspaceDir);
|
|
7313
|
+
const projectDir = join9(workspaceDir, opts.projectName);
|
|
7314
|
+
const docAbs = join9(projectDir, opts.docPath);
|
|
7315
|
+
if (!existsSync9(docAbs)) {
|
|
7316
|
+
throw new Error(`deleteDoc: doc not found: ${opts.docPath}`);
|
|
7317
|
+
}
|
|
7318
|
+
const report = {
|
|
7319
|
+
docPath: opts.docPath,
|
|
7320
|
+
slideRefsRemoved: [],
|
|
7321
|
+
refLayersDangling: []
|
|
7322
|
+
};
|
|
7323
|
+
let projectNames = [];
|
|
7324
|
+
try {
|
|
7325
|
+
projectNames = readdirSync5(workspaceDir).filter((entry) => {
|
|
7326
|
+
if (entry.startsWith(".")) return false;
|
|
7327
|
+
try {
|
|
7328
|
+
const projDir = join9(workspaceDir, entry);
|
|
7329
|
+
if (!statSync5(projDir).isDirectory()) return false;
|
|
7330
|
+
return existsSync9(join9(projDir, "project.atelier"));
|
|
7331
|
+
} catch {
|
|
7332
|
+
return false;
|
|
7333
|
+
}
|
|
7334
|
+
});
|
|
7335
|
+
} catch {
|
|
7336
|
+
projectNames = [];
|
|
7337
|
+
}
|
|
7338
|
+
for (const projName of projectNames) {
|
|
7339
|
+
const projDir = join9(workspaceDir, projName);
|
|
7340
|
+
const projectAtelierFiles = listAtelierFilesUnderProject(projDir);
|
|
7341
|
+
for (const atelierAbs of projectAtelierFiles) {
|
|
7342
|
+
let doc;
|
|
7343
|
+
try {
|
|
7344
|
+
doc = readAtelierDoc2(atelierAbs);
|
|
7345
|
+
} catch {
|
|
7346
|
+
continue;
|
|
7347
|
+
}
|
|
7348
|
+
let mutated = false;
|
|
7349
|
+
const docRel = relative(projDir, atelierAbs).split("\\").join("/");
|
|
7350
|
+
if (doc.kind === "carousel" && Array.isArray(doc.slides)) {
|
|
7351
|
+
const keptSlides = [];
|
|
7352
|
+
doc.slides.forEach((slide, idx) => {
|
|
7353
|
+
const slideAbs = resolveRefSrc(slide.src, atelierAbs, projDir, workspaceDir);
|
|
7354
|
+
if (slideAbs === docAbs) {
|
|
7355
|
+
report.slideRefsRemoved.push({ project: projName, slideIndex: idx });
|
|
7356
|
+
mutated = true;
|
|
7357
|
+
return;
|
|
7358
|
+
}
|
|
7359
|
+
keptSlides.push(slide);
|
|
7360
|
+
});
|
|
7361
|
+
if (mutated) {
|
|
7362
|
+
doc = { ...doc, slides: keptSlides };
|
|
7363
|
+
}
|
|
7364
|
+
}
|
|
7365
|
+
if (Array.isArray(doc.layers)) {
|
|
7366
|
+
for (const layer of doc.layers) {
|
|
7367
|
+
const v = layer.visual;
|
|
7368
|
+
if (!v || v.type !== "ref") continue;
|
|
7369
|
+
const refV = v;
|
|
7370
|
+
if (typeof refV.src !== "string" || !refV.src) continue;
|
|
7371
|
+
const refAbs = resolveRefSrc(refV.src, atelierAbs, projDir, workspaceDir);
|
|
7372
|
+
if (refAbs === docAbs) {
|
|
7373
|
+
report.refLayersDangling.push({
|
|
7374
|
+
project: projName,
|
|
7375
|
+
docPath: docRel,
|
|
7376
|
+
layerId: layer.id
|
|
7377
|
+
});
|
|
7378
|
+
}
|
|
7379
|
+
}
|
|
7380
|
+
}
|
|
7381
|
+
if (mutated) {
|
|
7382
|
+
writeFileSync5(atelierAbs, JSON.stringify(doc, null, 2), "utf-8");
|
|
7383
|
+
}
|
|
7384
|
+
}
|
|
7385
|
+
}
|
|
7386
|
+
unlinkSync(docAbs);
|
|
7387
|
+
return report;
|
|
7388
|
+
}
|
|
7389
|
+
function listAtelierFilesUnderProject(projectDir) {
|
|
7390
|
+
const out = [];
|
|
7391
|
+
try {
|
|
7392
|
+
for (const entry of readdirSync5(projectDir)) {
|
|
7393
|
+
if (entry === ".atelier") continue;
|
|
7394
|
+
if (!entry.endsWith(".atelier")) continue;
|
|
7395
|
+
const abs = join9(projectDir, entry);
|
|
7396
|
+
try {
|
|
7397
|
+
if (statSync5(abs).isFile()) out.push(abs);
|
|
7398
|
+
} catch {
|
|
7399
|
+
}
|
|
7400
|
+
}
|
|
7401
|
+
} catch {
|
|
7402
|
+
}
|
|
7403
|
+
const slidesDir = join9(projectDir, "slides");
|
|
7404
|
+
if (existsSync9(slidesDir)) {
|
|
7405
|
+
try {
|
|
7406
|
+
for (const entry of readdirSync5(slidesDir)) {
|
|
7407
|
+
if (!entry.endsWith(".atelier")) continue;
|
|
7408
|
+
const abs = join9(slidesDir, entry);
|
|
7409
|
+
try {
|
|
7410
|
+
if (statSync5(abs).isFile()) out.push(abs);
|
|
7411
|
+
} catch {
|
|
7412
|
+
}
|
|
7413
|
+
}
|
|
7414
|
+
} catch {
|
|
7415
|
+
}
|
|
7416
|
+
}
|
|
7417
|
+
return out;
|
|
7418
|
+
}
|
|
7419
|
+
function resolveRefSrc(src, currentDocAbs, ownerProjectDir, workspaceRoot) {
|
|
7420
|
+
if (isAbsolute3(src)) {
|
|
7421
|
+
return existsSync9(src) ? resolve9(src) : void 0;
|
|
7422
|
+
}
|
|
7423
|
+
const projectRelative = resolve9(ownerProjectDir, src);
|
|
7424
|
+
if (existsSync9(projectRelative)) return projectRelative;
|
|
7425
|
+
const docRelative = resolve9(dirname2(currentDocAbs), src);
|
|
7426
|
+
if (existsSync9(docRelative)) return docRelative;
|
|
7427
|
+
const workspaceRelative = resolve9(workspaceRoot, src);
|
|
7428
|
+
if (existsSync9(workspaceRelative)) return workspaceRelative;
|
|
7429
|
+
return void 0;
|
|
7430
|
+
}
|
|
7431
|
+
function listDocs(projectDir) {
|
|
7432
|
+
const projAbs = resolve9(projectDir);
|
|
7433
|
+
const collected = [];
|
|
7434
|
+
try {
|
|
7435
|
+
for (const entry of readdirSync5(projAbs)) {
|
|
7436
|
+
if (entry === ".atelier") continue;
|
|
7437
|
+
if (!entry.endsWith(".atelier")) continue;
|
|
7438
|
+
const abs = join9(projAbs, entry);
|
|
7439
|
+
try {
|
|
7440
|
+
if (!statSync5(abs).isFile()) continue;
|
|
7441
|
+
} catch {
|
|
7442
|
+
continue;
|
|
7443
|
+
}
|
|
7444
|
+
collected.push({ rel: entry, abs });
|
|
7445
|
+
}
|
|
7446
|
+
} catch {
|
|
7447
|
+
}
|
|
7448
|
+
const slidesDir = join9(projAbs, "slides");
|
|
7449
|
+
if (existsSync9(slidesDir)) {
|
|
7450
|
+
try {
|
|
7451
|
+
for (const entry of readdirSync5(slidesDir)) {
|
|
7452
|
+
if (!entry.endsWith(".atelier")) continue;
|
|
7453
|
+
const abs = join9(slidesDir, entry);
|
|
7454
|
+
try {
|
|
7455
|
+
if (!statSync5(abs).isFile()) continue;
|
|
7456
|
+
} catch {
|
|
7457
|
+
continue;
|
|
7458
|
+
}
|
|
7459
|
+
collected.push({ rel: `slides/${entry}`, abs });
|
|
7460
|
+
}
|
|
7461
|
+
} catch {
|
|
7462
|
+
}
|
|
7463
|
+
}
|
|
7464
|
+
collected.sort((a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0);
|
|
7465
|
+
const out = [];
|
|
7466
|
+
for (const { rel, abs } of collected) {
|
|
7467
|
+
let kind;
|
|
7468
|
+
try {
|
|
7469
|
+
kind = readAtelierDoc2(abs).kind;
|
|
7470
|
+
} catch {
|
|
7471
|
+
}
|
|
7472
|
+
const entry = { path: rel };
|
|
7473
|
+
if (kind !== void 0) entry.kind = kind;
|
|
7474
|
+
out.push(entry);
|
|
7475
|
+
}
|
|
7476
|
+
return out;
|
|
7477
|
+
}
|
|
7478
|
+
|
|
7479
|
+
// ../mcp/src/tools/docs.ts
|
|
7480
|
+
function errorEnvelope(message) {
|
|
7481
|
+
return {
|
|
7482
|
+
content: [{ type: "text", text: JSON.stringify({ error: message }) }],
|
|
7483
|
+
isError: true
|
|
7484
|
+
};
|
|
7485
|
+
}
|
|
7486
|
+
function successEnvelope(payload) {
|
|
7487
|
+
return {
|
|
7488
|
+
content: [{ type: "text", text: JSON.stringify({ success: true, ...payload }) }]
|
|
7489
|
+
};
|
|
7490
|
+
}
|
|
7491
|
+
function resolveProjectInWorkspace(projectDir, workspaceDir) {
|
|
7492
|
+
const workspaceAbs = resolve10(workspaceDir ?? process.cwd());
|
|
7493
|
+
const projectAbs = resolve10(projectDir);
|
|
7494
|
+
if (projectAbs !== workspaceAbs && !projectAbs.startsWith(workspaceAbs + sep2)) {
|
|
7495
|
+
return { ok: false, error: `projectDir must be inside workspace ${workspaceAbs}` };
|
|
7496
|
+
}
|
|
7497
|
+
return { ok: true, workspaceAbs, projectAbs };
|
|
7498
|
+
}
|
|
7499
|
+
function readDocFromDisk(absPath) {
|
|
7500
|
+
try {
|
|
7501
|
+
const raw = readFileSync8(absPath, "utf-8");
|
|
7502
|
+
const parsed = parseAtelier(raw);
|
|
7503
|
+
if (parsed.success) return parsed.data;
|
|
7504
|
+
return JSON.parse(raw);
|
|
7505
|
+
} catch {
|
|
7506
|
+
return void 0;
|
|
7507
|
+
}
|
|
7508
|
+
}
|
|
7509
|
+
function register25(server, store) {
|
|
7510
|
+
server.tool(
|
|
7511
|
+
"atelier_create_doc",
|
|
7512
|
+
"Create a new .atelier doc inside a workspace project. Optionally append it to a carousel's slides[].",
|
|
7513
|
+
{
|
|
7514
|
+
documentId: z50.string().describe("Document ID under which to reload the affected doc"),
|
|
7515
|
+
projectDir: z50.string().describe("Absolute project directory (must contain project.atelier)"),
|
|
7516
|
+
workspaceDir: z50.string().optional().describe("Workspace root for security scoping (defaults to process.cwd())"),
|
|
7517
|
+
name: z50.string().describe("Human-typed doc name; slugged internally to a filesystem-safe stem"),
|
|
7518
|
+
kind: z50.enum(["video", "image", "carousel"]).describe("Doc kind"),
|
|
7519
|
+
asCarouselSlide: z50.boolean().optional().describe("If true, write under slides/ and append a SlideRef to carouselDocPath"),
|
|
7520
|
+
carouselDocPath: z50.string().optional().describe("Absolute path to the carousel doc whose slides[] to mutate (required when asCarouselSlide:true)"),
|
|
7521
|
+
insertAt: z50.number().int().optional().describe("Index in carousel slides[] (default = append)")
|
|
7522
|
+
},
|
|
7523
|
+
{ readOnlyHint: false, destructiveHint: false },
|
|
7524
|
+
async ({ documentId, projectDir, workspaceDir, name, kind, asCarouselSlide, carouselDocPath, insertAt }) => {
|
|
7525
|
+
try {
|
|
7526
|
+
if (!isValidDocName(name)) return errorEnvelope("invalid doc name");
|
|
7527
|
+
const scope = resolveProjectInWorkspace(projectDir, workspaceDir);
|
|
7528
|
+
if (!scope.ok) return errorEnvelope(scope.error);
|
|
7529
|
+
if (!existsSync10(join10(scope.projectAbs, "project.atelier"))) {
|
|
7530
|
+
return errorEnvelope("projectDir does not contain project.atelier");
|
|
7531
|
+
}
|
|
7532
|
+
if (asCarouselSlide && carouselDocPath) {
|
|
7533
|
+
const carouselAbs = resolve10(carouselDocPath);
|
|
7534
|
+
if (carouselAbs !== scope.workspaceAbs && !carouselAbs.startsWith(scope.workspaceAbs + sep2)) {
|
|
7535
|
+
return errorEnvelope(`carouselDocPath must be inside workspace ${scope.workspaceAbs}`);
|
|
7536
|
+
}
|
|
7537
|
+
}
|
|
7538
|
+
const result = createDoc({
|
|
7539
|
+
projectDir: scope.projectAbs,
|
|
7540
|
+
name,
|
|
7541
|
+
kind,
|
|
7542
|
+
...asCarouselSlide !== void 0 ? { asCarouselSlide } : {},
|
|
7543
|
+
...carouselDocPath !== void 0 ? { carouselDocPath } : {},
|
|
7544
|
+
...insertAt !== void 0 ? { insertAt } : {}
|
|
7545
|
+
});
|
|
7546
|
+
const reloadTarget = asCarouselSlide && carouselDocPath ? resolve10(carouselDocPath) : result.newDocAbsPath;
|
|
7547
|
+
const doc = readDocFromDisk(reloadTarget);
|
|
7548
|
+
if (doc) store.set(documentId, doc, "llm");
|
|
7549
|
+
const payload = { docPath: result.newDocPath };
|
|
7550
|
+
if (result.appendedSlideIndex !== void 0) {
|
|
7551
|
+
payload.appendedSlideIndex = result.appendedSlideIndex;
|
|
7552
|
+
}
|
|
7553
|
+
return successEnvelope(payload);
|
|
7554
|
+
} catch (e) {
|
|
7555
|
+
return errorEnvelope(e.message);
|
|
7556
|
+
}
|
|
7557
|
+
}
|
|
7558
|
+
);
|
|
7559
|
+
server.tool(
|
|
7560
|
+
"atelier_duplicate_doc",
|
|
7561
|
+
"Duplicate an existing .atelier doc inside a project. Optionally appends a SlideRef to the project's carousel manifest (requires the source to live directly under slides/).",
|
|
7562
|
+
{
|
|
7563
|
+
documentId: z50.string().describe("Document ID under which to reload the affected doc"),
|
|
7564
|
+
projectDir: z50.string().describe("Absolute project directory (must contain project.atelier)"),
|
|
7565
|
+
workspaceDir: z50.string().optional().describe("Workspace root for security scoping (defaults to process.cwd())"),
|
|
7566
|
+
sourceDocPath: z50.string().describe("Project-relative path of the doc to duplicate (e.g. 'slides/slide-1.atelier')"),
|
|
7567
|
+
appendSlideRef: z50.boolean().optional().describe("If true, append a SlideRef pointing at the duplicate to the project manifest")
|
|
7568
|
+
},
|
|
7569
|
+
{ readOnlyHint: false, destructiveHint: false },
|
|
7570
|
+
async ({ documentId, projectDir, workspaceDir, sourceDocPath, appendSlideRef }) => {
|
|
7571
|
+
try {
|
|
7572
|
+
const scope = resolveProjectInWorkspace(projectDir, workspaceDir);
|
|
7573
|
+
if (!scope.ok) return errorEnvelope(scope.error);
|
|
7574
|
+
if (!existsSync10(join10(scope.projectAbs, "project.atelier"))) {
|
|
7575
|
+
return errorEnvelope("projectDir does not contain project.atelier");
|
|
7576
|
+
}
|
|
7577
|
+
const report = duplicateDoc({
|
|
7578
|
+
projectDir: scope.projectAbs,
|
|
7579
|
+
sourceDocPath,
|
|
7580
|
+
...appendSlideRef !== void 0 ? { appendSlideRef } : {}
|
|
7581
|
+
});
|
|
7582
|
+
const reloadTarget = appendSlideRef ? join10(scope.projectAbs, "project.atelier") : join10(scope.projectAbs, report.newDocPath);
|
|
7583
|
+
const doc = readDocFromDisk(reloadTarget);
|
|
7584
|
+
if (doc) store.set(documentId, doc, "llm");
|
|
7585
|
+
return successEnvelope(report);
|
|
7586
|
+
} catch (e) {
|
|
7587
|
+
return errorEnvelope(e.message);
|
|
7588
|
+
}
|
|
7589
|
+
}
|
|
7590
|
+
);
|
|
7591
|
+
server.tool(
|
|
7592
|
+
"atelier_delete_doc",
|
|
7593
|
+
"Delete any .atelier doc (including project.atelier), scrubbing matching SlideRefs across the workspace and reporting dangling RefVisual layers.",
|
|
7594
|
+
{
|
|
7595
|
+
documentId: z50.string().describe("Document ID \u2014 if equal to the deleted doc's path, the studio handles fallback"),
|
|
7596
|
+
workspaceDir: z50.string().optional().describe("Workspace root for security scoping (defaults to process.cwd())"),
|
|
7597
|
+
projectName: z50.string().describe("Project subdir name inside workspaceDir"),
|
|
7598
|
+
docPath: z50.string().describe("Project-relative path of the doc to delete (e.g. 'slides/slide-2.atelier')")
|
|
7599
|
+
},
|
|
7600
|
+
{ readOnlyHint: false, destructiveHint: true },
|
|
7601
|
+
async ({ documentId: _documentId, workspaceDir, projectName, docPath }) => {
|
|
7602
|
+
try {
|
|
7603
|
+
const workspaceAbs = resolve10(workspaceDir ?? process.cwd());
|
|
7604
|
+
const projectAbs = resolve10(join10(workspaceAbs, projectName));
|
|
7605
|
+
if (projectAbs !== workspaceAbs && !projectAbs.startsWith(workspaceAbs + sep2)) {
|
|
7606
|
+
return errorEnvelope(`projectName must resolve inside workspace ${workspaceAbs}`);
|
|
7607
|
+
}
|
|
7608
|
+
if (!existsSync10(join10(projectAbs, "project.atelier"))) {
|
|
7609
|
+
return errorEnvelope("project does not contain project.atelier");
|
|
7610
|
+
}
|
|
7611
|
+
const report = deleteDoc({
|
|
7612
|
+
workspaceDir: workspaceAbs,
|
|
7613
|
+
projectName,
|
|
7614
|
+
docPath
|
|
7615
|
+
});
|
|
7616
|
+
void _documentId;
|
|
7617
|
+
return successEnvelope(report);
|
|
7618
|
+
} catch (e) {
|
|
7619
|
+
return errorEnvelope(e.message);
|
|
7620
|
+
}
|
|
7621
|
+
}
|
|
7622
|
+
);
|
|
7623
|
+
server.tool(
|
|
7624
|
+
"atelier_list_docs",
|
|
7625
|
+
"Enumerate every .atelier doc inside a project (flat alphabetical by project-relative path). Returns each as { path, kind? }.",
|
|
7626
|
+
{
|
|
7627
|
+
projectDir: z50.string().describe("Absolute project directory"),
|
|
7628
|
+
workspaceDir: z50.string().optional().describe("Workspace root for security scoping (defaults to process.cwd())")
|
|
7629
|
+
},
|
|
7630
|
+
{ readOnlyHint: true, destructiveHint: false },
|
|
7631
|
+
async ({ projectDir, workspaceDir }) => {
|
|
7632
|
+
try {
|
|
7633
|
+
const scope = resolveProjectInWorkspace(projectDir, workspaceDir);
|
|
7634
|
+
if (!scope.ok) return errorEnvelope(scope.error);
|
|
7635
|
+
const docs = listDocs(scope.projectAbs);
|
|
7636
|
+
return successEnvelope({ docs });
|
|
7637
|
+
} catch (e) {
|
|
7638
|
+
return errorEnvelope(e.message);
|
|
7639
|
+
}
|
|
7640
|
+
}
|
|
7641
|
+
);
|
|
7642
|
+
}
|
|
7643
|
+
|
|
5265
7644
|
// ../mcp/src/bridge-protocol.ts
|
|
5266
7645
|
var BRIDGE_PROTOCOL_VERSION = 1;
|
|
5267
7646
|
function isBridgeEnvelope(value) {
|
|
5268
7647
|
if (!value || typeof value !== "object") return false;
|
|
5269
7648
|
const t = value.type;
|
|
5270
|
-
return t === "hello" || t === "doc:load" || t === "doc:loaded" || t === "doc:patch" || t === "llm:mutation" || t === "error";
|
|
7649
|
+
return t === "hello" || t === "doc:load" || t === "doc:loaded" || t === "doc:patch" || t === "llm:mutation" || t === "error" || t === "media:list:changed" || t === "transcript:added" || t === "media:ingest:progress" || t === "file:deleted" || t === "media:deleted" || t === "project:deleted" || t === "doc:list:changed";
|
|
5271
7650
|
}
|
|
5272
7651
|
|
|
5273
7652
|
// ../mcp/src/transports/ws-transport.ts
|
|
@@ -5302,17 +7681,17 @@ var WebSocketServerTransport = class {
|
|
|
5302
7681
|
} else {
|
|
5303
7682
|
text = String(data);
|
|
5304
7683
|
}
|
|
5305
|
-
} catch (
|
|
5306
|
-
this.onerror?.(
|
|
7684
|
+
} catch (err24) {
|
|
7685
|
+
this.onerror?.(err24 instanceof Error ? err24 : new Error(String(err24)));
|
|
5307
7686
|
return;
|
|
5308
7687
|
}
|
|
5309
7688
|
let parsed;
|
|
5310
7689
|
try {
|
|
5311
7690
|
parsed = JSON.parse(text);
|
|
5312
|
-
} catch (
|
|
7691
|
+
} catch (err24) {
|
|
5313
7692
|
this.onerror?.(
|
|
5314
7693
|
new Error(
|
|
5315
|
-
`WebSocketServerTransport: failed to parse JSON-RPC message: ${
|
|
7694
|
+
`WebSocketServerTransport: failed to parse JSON-RPC message: ${err24 instanceof Error ? err24.message : String(err24)}`
|
|
5316
7695
|
)
|
|
5317
7696
|
);
|
|
5318
7697
|
return;
|
|
@@ -5324,8 +7703,8 @@ var WebSocketServerTransport = class {
|
|
|
5324
7703
|
this.closed = true;
|
|
5325
7704
|
this.onclose?.();
|
|
5326
7705
|
});
|
|
5327
|
-
this.ws.on("error", (
|
|
5328
|
-
this.onerror?.(
|
|
7706
|
+
this.ws.on("error", (err24) => {
|
|
7707
|
+
this.onerror?.(err24);
|
|
5329
7708
|
});
|
|
5330
7709
|
}
|
|
5331
7710
|
async send(message) {
|
|
@@ -5375,6 +7754,12 @@ function createServer(store = new DocumentStore()) {
|
|
|
5375
7754
|
register17(server, store);
|
|
5376
7755
|
register18(server, store);
|
|
5377
7756
|
register19(server, store);
|
|
7757
|
+
register20(server, store);
|
|
7758
|
+
register21(server, store);
|
|
7759
|
+
register22(server, store);
|
|
7760
|
+
register23(server, store);
|
|
7761
|
+
register24(server, store);
|
|
7762
|
+
register25(server, store);
|
|
5378
7763
|
return { server, store };
|
|
5379
7764
|
}
|
|
5380
7765
|
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"));
|
|
@@ -5390,22 +7775,28 @@ export {
|
|
|
5390
7775
|
BRIDGE_PROTOCOL_VERSION,
|
|
5391
7776
|
DocumentStore,
|
|
5392
7777
|
WebSocketServerTransport,
|
|
7778
|
+
__setProbeDurationFn,
|
|
7779
|
+
__setTranscribeClipFn,
|
|
5393
7780
|
createServer,
|
|
5394
7781
|
isBridgeEnvelope,
|
|
5395
7782
|
register12 as registerAssetTools,
|
|
7783
|
+
register23 as registerClipTools,
|
|
5396
7784
|
register5 as registerDeltaTools,
|
|
7785
|
+
register25 as registerDocTools,
|
|
5397
7786
|
register as registerDocumentTools,
|
|
5398
7787
|
register11 as registerExportTools,
|
|
5399
7788
|
register19 as registerImportImageTools,
|
|
5400
7789
|
register14 as registerInteractionTools,
|
|
5401
7790
|
register9 as registerLayerEffectTools,
|
|
5402
7791
|
register2 as registerLayerTools,
|
|
7792
|
+
register20 as registerMindMapTools,
|
|
5403
7793
|
register17 as registerOverlayTools,
|
|
5404
7794
|
register16 as registerPerformanceTools,
|
|
5405
7795
|
register6 as registerPresetTools,
|
|
5406
7796
|
register7 as registerPreviewTools,
|
|
5407
7797
|
register18 as registerRecipeTools,
|
|
5408
7798
|
register15 as registerRefTools,
|
|
7799
|
+
register24 as registerRenameMediaTool,
|
|
5409
7800
|
register3 as registerShapeTools,
|
|
5410
7801
|
register10 as registerStateConfigTools,
|
|
5411
7802
|
register4 as registerStateTools,
|