goatedit-mcp 1.1.6 → 1.1.8
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/bundle/goatedit-mcp.js +182 -18
- package/package.json +1 -1
package/bundle/goatedit-mcp.js
CHANGED
|
@@ -19179,6 +19179,8 @@ var TEXT_STYLE_PROPS = {
|
|
|
19179
19179
|
textShadowColor: str("Drop shadow colour."),
|
|
19180
19180
|
lineHeight: num("Line advance as a multiple of the line height. Defaults to 1.2."),
|
|
19181
19181
|
letterSpacing: num("Extra tracking in pixels @1080p."),
|
|
19182
|
+
captionStyle: str("A caption look from the editor's library, applied first so the other style fields here adjust it: purpleLuxuryScript, cyanBlueprint, greenCapital, goldScriptGala, violetChrome, iceCrystal, podcastFame, whiteFlowGrid, blackPlate, extrudedPop, yellowHit, slantedRedDesculpa, redHeroEditorial, orangeHooksHero, creamHero, blackHero, splitSerif, magazineStack, goldSerifAccent, ivoryElegance, roseGoldSerif, sageSerif, midnightSerif, champagneSerif. It brings its own fonts, layout and text animation."),
|
|
19183
|
+
wordSpacing: num("Space between words as a fraction of the font size. Default 0.26; editorial captions with wide gaps use about 1.6."),
|
|
19182
19184
|
textUnderline: bool("Underline the text."),
|
|
19183
19185
|
textShadowBlur: num("Drop shadow softness in pixels @1080p. 0 is a hard shadow."),
|
|
19184
19186
|
textShadowOffsetX: num("Drop shadow offset to the right, pixels @1080p."),
|
|
@@ -19292,14 +19294,16 @@ var BASE_TOOLS = [
|
|
|
19292
19294
|
},
|
|
19293
19295
|
{
|
|
19294
19296
|
name: "export_range",
|
|
19295
|
-
description:
|
|
19297
|
+
description: 'Sample a stretch of timeline: rendered frames spread across it, plus the measured audio for the same range. Use it to check a sequence of cuts, a transition, or a whole section in one call, where render_preview_frame answers for a single moment. Up to 6 frames come back as separate pictures; 7 to 24 come back as one tiled sheet stamped with times, which is how a motion is best judged. With video: true it encodes the range as an MP4 (up to 60 seconds) in the background: the first call starts it and answers status "running", and the same call again returns the file once it is done \u2014 the local server saves it to disk and replies with its path.',
|
|
19296
19298
|
inputSchema: {
|
|
19297
19299
|
type: "object",
|
|
19298
19300
|
properties: {
|
|
19299
19301
|
startTime: num("Start of the range in seconds."),
|
|
19300
19302
|
endTime: num("End of the range in seconds. Ranges over 10 minutes are refused."),
|
|
19301
|
-
frames: num("How many frames to sample, up to 6. Defaults to 4."),
|
|
19302
|
-
width: num("Width of each frame in pixels
|
|
19303
|
+
frames: num("How many frames to sample, up to 24; over 6 they come back as one tiled sheet. Defaults to 4."),
|
|
19304
|
+
width: num("Width of each frame in pixels (default 640), of the sheet (default 1280), or of the video (default 640)."),
|
|
19305
|
+
video: bool('Encode the range as an MP4 instead of sampling frames. Call again with the same arguments until status is "done".'),
|
|
19306
|
+
fps: num("Video frame rate. Defaults to the project's."),
|
|
19303
19307
|
skipAdjustments: SKIP_ADJUSTMENTS,
|
|
19304
19308
|
hideClipIds: HIDE_CLIP_IDS
|
|
19305
19309
|
},
|
|
@@ -19327,7 +19331,7 @@ var BASE_TOOLS = [
|
|
|
19327
19331
|
},
|
|
19328
19332
|
{
|
|
19329
19333
|
name: "clean_unused_sequences",
|
|
19330
|
-
description: "Find nested sequences that no clip uses any more \u2014 left behind when a nest clip is removed or a shot is rebuilt \u2014 and optionally delete them with their bin items. Without apply it only lists them.
|
|
19334
|
+
description: "Find nested sequences that no clip uses any more \u2014 left behind when a nest clip is removed or a shot is rebuilt \u2014 and optionally delete them with their bin items. Without apply it only lists them. A scene (a sequence once placed with add_sequence_clip) counts as a nest. Other top-level sequences, and nests reachable from one or open in a tab, are never touched; top-level ones nothing plays are listed under otherUnplayed for delete_sequence.",
|
|
19331
19335
|
inputSchema: {
|
|
19332
19336
|
type: "object",
|
|
19333
19337
|
properties: {
|
|
@@ -19336,6 +19340,18 @@ var BASE_TOOLS = [
|
|
|
19336
19340
|
},
|
|
19337
19341
|
method: "cleanUnusedSequences"
|
|
19338
19342
|
},
|
|
19343
|
+
{
|
|
19344
|
+
name: "delete_sequence",
|
|
19345
|
+
description: "Delete one or more sequences by id, with their bin items. Clips in other sequences that played them are removed too and listed in the reply. The open sequence is refused: open another first. Undo restores everything.",
|
|
19346
|
+
inputSchema: {
|
|
19347
|
+
type: "object",
|
|
19348
|
+
properties: {
|
|
19349
|
+
sequenceId: str("Sequence to delete, from list_sequences."),
|
|
19350
|
+
sequenceIds: { type: "array", items: { type: "string" }, description: "Several at once, in one undo step. Use instead of sequenceId." }
|
|
19351
|
+
}
|
|
19352
|
+
},
|
|
19353
|
+
method: "deleteSequence"
|
|
19354
|
+
},
|
|
19339
19355
|
{
|
|
19340
19356
|
name: "list_sequences",
|
|
19341
19357
|
description: "Every sequence in the project: id, name, whether it is the one open for editing, open in a tab, a nest (made by nest_clips) or top-level, its length, how many clips it holds, and the clips that play it. Every other tool edits the open sequence only \u2014 use this to find the id to pass to open_sequence.",
|
|
@@ -19594,14 +19610,17 @@ var BASE_TOOLS = [
|
|
|
19594
19610
|
},
|
|
19595
19611
|
{
|
|
19596
19612
|
name: "create_shape_clip",
|
|
19597
|
-
description: "Draw a solid shape on the timeline \u2014 a rectangle, square or circle, with an optional stroke and rounded corners. This is how you get a colour behind something: a full-frame backdrop for a title, a lower-third plate, a letterbox bar. Pass fullFrame to size a rectangle to the whole frame without working out the units. Nothing needs importing; the compositor draws it.",
|
|
19613
|
+
description: "Draw a solid shape on the timeline \u2014 a rectangle, square or circle, with an optional stroke and rounded corners, or any outline as a path (a line, an arrow, a star, an underline). This is how you get a colour behind something: a full-frame backdrop for a title, a lower-third plate, a letterbox bar. Pass fullFrame to size a rectangle to the whole frame without working out the units. A path takes `path` (SVG path data in the frame's own pixels) or `points`; it starts as a white 4-unit stroke with no fill unless color or strokeColor is given, and update_shape_clip trimEnd keyframed 0 to 1 draws it on. Nothing needs importing; the compositor draws it.",
|
|
19598
19614
|
inputSchema: {
|
|
19599
19615
|
type: "object",
|
|
19600
19616
|
properties: {
|
|
19601
19617
|
duration: num("How long the shape stays on screen, in seconds."),
|
|
19602
19618
|
startTime: num("Where it starts on the timeline, in seconds. Defaults to 0."),
|
|
19603
19619
|
trackId: str('Target video track, or "__new_video__" to pick one free across the span. Defaults to the latter.'),
|
|
19604
|
-
shapeType: { type: "string", enum: ["rectangle", "square", "circle"], description: "Shape to draw. Defaults to rectangle." },
|
|
19620
|
+
shapeType: { type: "string", enum: ["rectangle", "square", "circle", "path"], description: "Shape to draw. Defaults to rectangle, or path when path or points is given." },
|
|
19621
|
+
path: str('Path shape: SVG path data in frame pixels, e.g. "M 300 800 C 700 600 1200 1000 1620 760" for a curve across a 1920\xD71080 frame. M, L, H, V, C, S, Q and Z, absolute or relative; arcs are not supported. Each extra M starts another outline on the same layer with the same paint (not a hole). width, height and cornerRadius do not apply.'),
|
|
19622
|
+
points: { type: "array", items: { type: "object" }, description: 'Path shape as anchors instead of `path`: [{ "x": 0..1, "y": 0..1 }], fractions of the frame, with optional bezier handles hiX/hiY (incoming) and hoX/hoY (outgoing) as offsets from the anchor in the same units.' },
|
|
19623
|
+
closed: bool("Path shape: join the last anchor back to the first. Defaults to what `path` says (Z), or false for points."),
|
|
19605
19624
|
color: str('Fill colour, e.g. "#000000". Defaults to black.'),
|
|
19606
19625
|
fullFrame: bool("Size a rectangle to cover the whole frame. Overrides width and height."),
|
|
19607
19626
|
width: num("Width in design units against a 1080p-tall frame. Defaults to 300."),
|
|
@@ -19636,7 +19655,13 @@ var BASE_TOOLS = [
|
|
|
19636
19655
|
strokeCap: { type: "string", enum: ["butt", "round", "square"], description: "Ends of an open or dashed outline." },
|
|
19637
19656
|
strokeJoin: { type: "string", enum: ["miter", "round", "bevel"], description: "Outline corners." },
|
|
19638
19657
|
dash: num("Dash length for a dashed outline, design units. 0 for solid."),
|
|
19639
|
-
gap: num("Gap between dashes, design units.")
|
|
19658
|
+
gap: num("Gap between dashes, design units."),
|
|
19659
|
+
path: str("Path shapes: replace the outline, SVG path data in frame pixels (see create_shape_clip). The layer is re-centred on the new outline."),
|
|
19660
|
+
points: { type: "array", items: { type: "object" }, description: "Path shapes: replace the outline with anchors, fractions of the frame." },
|
|
19661
|
+
closed: bool("Path shapes: join the last anchor back to the first."),
|
|
19662
|
+
trimStart: num("Path shapes: where along the path drawing starts, 0..1. Keyframe it as pathTrimStart."),
|
|
19663
|
+
trimEnd: num("Path shapes: where drawing stops, 0..1. 0 hides the path; keyframing pathTrimEnd 0 to 1 with set_keyframes draws it on."),
|
|
19664
|
+
trimOffset: num("Path shapes: slides the drawn stretch along the path, degrees.")
|
|
19640
19665
|
},
|
|
19641
19666
|
required: ["clipId"]
|
|
19642
19667
|
},
|
|
@@ -19922,6 +19947,35 @@ var BASE_TOOLS = [
|
|
|
19922
19947
|
},
|
|
19923
19948
|
method: "removeBackground"
|
|
19924
19949
|
},
|
|
19950
|
+
{
|
|
19951
|
+
name: "measure_text",
|
|
19952
|
+
description: 'How big a piece of text comes out, in frame pixels and as fractions of the frame, without rendering: width, height, the box it covers at its position, how it wrapped (`lines`), and `fitScale` below 1 when it was shrunk to fit the frame. Pass text and any create_text_clip style fields to size a caption before placing it \u2014 to fit a plate behind it, or pick a fontSize that fills a width. Pass clipId to measure a clip as it is, and style fields with it to ask "what if".',
|
|
19953
|
+
inputSchema: {
|
|
19954
|
+
type: "object",
|
|
19955
|
+
properties: {
|
|
19956
|
+
text: str("The words to measure. With clipId, replaces the clip's text for this measurement only."),
|
|
19957
|
+
clipId: str("A text clip to measure. Nothing on it is changed."),
|
|
19958
|
+
time: num("With clipId: timeline time in seconds to measure at, for keyframed size or scale. Defaults to the clip's start."),
|
|
19959
|
+
...TEXT_STYLE_PROPS,
|
|
19960
|
+
posX: num("Horizontal centre as a fraction of the frame, for the box. Defaults to 0.5."),
|
|
19961
|
+
posY: num("Vertical centre as a fraction of the frame, for the box. Defaults to 0.5.")
|
|
19962
|
+
}
|
|
19963
|
+
},
|
|
19964
|
+
method: "getTextSize"
|
|
19965
|
+
},
|
|
19966
|
+
{
|
|
19967
|
+
name: "set_markers",
|
|
19968
|
+
description: 'Point markers on the timeline ruler \u2014 beats, cue points, a note to the user about a moment. With no arguments it lists every marker (scenes from set_scene included, with their duration). `add` places several at once, `remove` deletes by id, and `removeLabelPrefix` clears every point marker whose label starts with it (e.g. "Beat " to re-mark beats). Removals run before additions, all in one undo step. Scene markers are left to set_scene.',
|
|
19969
|
+
inputSchema: {
|
|
19970
|
+
type: "object",
|
|
19971
|
+
properties: {
|
|
19972
|
+
add: { type: "array", items: { type: "object" }, description: 'Markers to place: [{ "time": seconds, "label"?: "Drop", "color"?: "#eab308" }]. Colour cycles a palette when omitted.' },
|
|
19973
|
+
remove: { type: "array", items: { type: "string" }, description: "Marker ids to delete." },
|
|
19974
|
+
removeLabelPrefix: str("Delete every point marker whose label starts with this.")
|
|
19975
|
+
}
|
|
19976
|
+
},
|
|
19977
|
+
method: "setMarkers"
|
|
19978
|
+
},
|
|
19925
19979
|
{
|
|
19926
19980
|
name: "set_scene",
|
|
19927
19981
|
description: "Mark a stretch of timeline as a named scene (a ruler marker with a length), so retime_range can move or stretch it as one piece later. With start and end it makes a scene; with sceneId it changes one (start, end, label, color) or deletes it (remove: true); with no arguments it lists every scene.",
|
|
@@ -20288,7 +20342,7 @@ var BASE_TOOLS = [
|
|
|
20288
20342
|
clipId: CLIP_ID,
|
|
20289
20343
|
type: {
|
|
20290
20344
|
type: "string",
|
|
20291
|
-
description: "Effect type: blur, invert, hue, saturation, brightness, contrast, exposure, grayscale, sepia, gradientMap, vignette, filmGrain, flicker, posterizeTime, lensDistortion, chromaticAberration, lensFlare, liquidGlass, opacityFx, colorCorrection, depthMap, gradientMap, glow, pixelDissolve, motionBlur, dropShadow, reflection, colorama, outline, echo, flutedGlass, lightRays, halftone, datamosh, roughenEdges, filmStock, halation, starGlow, thermal, colorBalance, duotone, levels, monochrome, channelMixer, dither, threshold, lensBlur, directionalBlur, spinBlur, zoomBlur, sharpen, cameraShake, bulge, twirl, ripple, waveWarp, polarCoords, turbulentDisplace, elasticGrid, gridSplit, cubify, glitch, vhs, ntsc, crtScreen, ledScreen, rgbSplit, jpegGlitch, ascii, macpaint, modulation, stripe, risograph, emboss, bevel, inkBleed, paperTexture, paperCutout, crossHatch, scatter, blobTracker, edgeGlow, pageTurn, lightSweep, deepGlow, twister, fill, grid, repeater, plus transform, layer3d, model3d and showcase. Call describe_effect with a type for its parameters, their ranges and what they do before setting anything but the obvious. An unknown name comes back with the full list."
|
|
20345
|
+
description: "Effect type: blur, invert, hue, saturation, brightness, contrast, exposure, grayscale, sepia, gradientMap, vignette, filmGrain, flicker, posterizeTime, lensDistortion, chromaticAberration, lensFlare, liquidGlass, opacityFx, colorCorrection, depthMap, gradientMap, glow, pixelDissolve, motionBlur, dropShadow, reflection, colorama, outline, echo, flutedGlass, lightRays, halftone, datamosh, roughenEdges, filmStock, halation, starGlow, filmDamage, lightLeak, thermal, colorBalance, duotone, levels, monochrome, channelMixer, dither, threshold, lensBlur, directionalBlur, spinBlur, zoomBlur, sharpen, cameraShake, bulge, twirl, ripple, waveWarp, polarCoords, turbulentDisplace, elasticGrid, gridSplit, cubify, glitch, vhs, ntsc, crtScreen, ledScreen, rgbSplit, jpegGlitch, ascii, macpaint, modulation, stripe, risograph, emboss, bevel, inkBleed, paperTexture, paperCutout, crossHatch, scatter, blobTracker, edgeGlow, pageTurn, lightSweep, deepGlow, twister, fill, grid, repeater, plus transform, layer3d, model3d and showcase. Call describe_effect with a type for its parameters, their ranges and what they do before setting anything but the obvious. An unknown name comes back with the full list."
|
|
20292
20346
|
},
|
|
20293
20347
|
params: {
|
|
20294
20348
|
type: "object",
|
|
@@ -20391,13 +20445,21 @@ var BASE_TOOLS = [
|
|
|
20391
20445
|
description: "Entrance preset: fadeIn, slideUp, blurIn, typewriter, typewriterCursor, scaleIn, springUp, slideInLeft, slideInRight, riseFade, unfoldDown, unfoldUp, foldFlip, depthPop, wordPop, karaokeHighlight, karaokeScale, springPop, neonGlowPulse, karaokePill, cyberSlant, wipeUp, trackIn, slamIn, elasticPop, squashDrop, glitchIn, tumbleIn, swingIn, flip3D, cinematicRise, stretchIn, bounceDrop, rollIn, maskRise (rises from behind an invisible line), blockReveal (a bar in highlightColor wipes across, then uncovers the text), focusPull (big and blurred, pulling to crisp), whipIn (sideways with a motion smear), scatterSettle (letters scattered up and down drop into line), depthAssemble (letters fly in from scattered depth), decode (letters flicker through random glyphs, then resolve), countUp (the first number in the text counts up to its value, keeping its format, e.g. $787,539.00)."
|
|
20392
20446
|
},
|
|
20393
20447
|
unit: { type: "string", enum: ["letter", "word", "line"], description: "What the animation steps through." },
|
|
20394
|
-
durationIn: num("Seconds for one unit's entrance to complete."),
|
|
20448
|
+
durationIn: num("Seconds for one unit's entrance to complete. 0 cuts each unit on."),
|
|
20395
20449
|
stagger: num("Seconds between consecutive units starting. 0 moves them together."),
|
|
20396
20450
|
delay: num("Seconds into the clip before the first unit starts. Default 0."),
|
|
20397
20451
|
distance: num("Slide or offset distance in pixels @1080p, for the slide/spring/unfold presets."),
|
|
20398
20452
|
highlightColor: str('Highlight colour for the karaoke-style presets and the bar of blockReveal, e.g. "#ff2d55".'),
|
|
20399
20453
|
order: { type: "string", enum: ["forward", "reverse", "center", "edges", "random"], description: "Which unit goes first. Defaults to reading order." },
|
|
20400
20454
|
seed: num("Seed for `random` order, so the shuffle stays the same on every render."),
|
|
20455
|
+
syncToWords: bool("Start each unit when its word is spoken (the clip's word timings \u2014 from generate_captions, or passed as wordTimings) instead of on the stagger. With durationIn 0 and fadeIn, words cut on exactly as they are said."),
|
|
20456
|
+
wordTimings: {
|
|
20457
|
+
type: "array",
|
|
20458
|
+
description: "Per-word timings for this clip in clip seconds, [{ text, start, end }], in reading order. Drives syncToWords and the kinetic presets. Replaces any the clip had.",
|
|
20459
|
+
items: { type: "object", properties: { text: { type: "string" }, start: { type: "number" }, end: { type: "number" } }, required: ["text", "start", "end"] }
|
|
20460
|
+
},
|
|
20461
|
+
placeholder: str('Colour of a solid bar drawn where each unit will appear until it enters, e.g. "#111111" \u2014 words waiting their turn as redaction bars. Empty string removes it.'),
|
|
20462
|
+
activeScale: num("Kinetic presets: size of the active word, 1 for no pop (stops it colliding with its neighbours). Defaults per preset, 1.1\u20131.32."),
|
|
20401
20463
|
remove: bool("Take the animation off, leaving the text to cut on.")
|
|
20402
20464
|
},
|
|
20403
20465
|
required: ["clipId"]
|
|
@@ -20522,6 +20584,23 @@ var BASE_TOOLS = [
|
|
|
20522
20584
|
},
|
|
20523
20585
|
method: "setProjectSettings"
|
|
20524
20586
|
},
|
|
20587
|
+
{
|
|
20588
|
+
name: "set_audio_master",
|
|
20589
|
+
description: "The sequence\u2019s master bus: tag tracks as voice, music or sfx, duck music under voice, and put a safety limiter on the mix. Ducking follows the spans of clips on voice-role tracks (and nested sequences placed on them), dipping music-role tracks by duckDb, starting `attack` seconds before each line so the first word is clear. Playback, probe_audio and export all hear the same bus. With no arguments it reports the current roles and settings.",
|
|
20590
|
+
inputSchema: {
|
|
20591
|
+
type: "object",
|
|
20592
|
+
properties: {
|
|
20593
|
+
roles: { type: "object", description: 'Track roles: { "<trackId>": "voice" | "music" | "sfx" | "none" }.' },
|
|
20594
|
+
ducking: bool("Duck music-role tracks under voice-role tracks."),
|
|
20595
|
+
duckDb: num("How far music dips under voice, in dB (negative). Defaults to -12."),
|
|
20596
|
+
attack: num("Seconds the dip takes, finishing as the voice starts. Defaults to 0.25."),
|
|
20597
|
+
release: num("Seconds music takes to come back after the voice ends. Defaults to 0.6."),
|
|
20598
|
+
limiter: bool("Limit the mix so it does not clip."),
|
|
20599
|
+
ceilingDb: num("Limiter ceiling in dBFS. Defaults to -1.")
|
|
20600
|
+
}
|
|
20601
|
+
},
|
|
20602
|
+
method: "setAudioMaster"
|
|
20603
|
+
},
|
|
20525
20604
|
{
|
|
20526
20605
|
name: "apply_ai_edit",
|
|
20527
20606
|
description: "Tighten the cut from the transcript: remove filler words, remove long pauses, or both. Works on the transcript derived from the clips currently on the timeline, so it respects the cuts already made. Run it with dryRun first to see exactly which ranges would go before anything is removed.",
|
|
@@ -20568,7 +20647,7 @@ var BASE_TOOLS = [
|
|
|
20568
20647
|
},
|
|
20569
20648
|
{
|
|
20570
20649
|
name: "remove_clip",
|
|
20571
|
-
description: "Remove a clip from the timeline, leaving a gap where it was.",
|
|
20650
|
+
description: "Remove a clip from the timeline, leaving a gap where it was. Safe to retry: a clip an earlier MCP edit already removed answers success with alreadyRemoved, while an id never seen is still an error.",
|
|
20572
20651
|
inputSchema: { type: "object", properties: { clipId: CLIP_ID }, required: ["clipId"] },
|
|
20573
20652
|
method: "removeClip"
|
|
20574
20653
|
},
|
|
@@ -20660,6 +20739,20 @@ var BASE_TOOLS = [
|
|
|
20660
20739
|
},
|
|
20661
20740
|
method: "splitClip"
|
|
20662
20741
|
},
|
|
20742
|
+
{
|
|
20743
|
+
name: "loop_clip",
|
|
20744
|
+
description: "Loop a clip: repeat it back to back until it fills a length \u2014 a background plate, an ambience bed, a short animation held under a longer shot. Each pass is an ordinary clip (keyframes and effects included), and the clip\u2019s linked audio loops with it. Give `times` (total passes, the original included) or `until` (timeline time to end at; the last pass is cut short to land on it). Refuses when other clips sit in the way on the same track, naming them.",
|
|
20745
|
+
inputSchema: {
|
|
20746
|
+
type: "object",
|
|
20747
|
+
properties: {
|
|
20748
|
+
clipId: CLIP_ID,
|
|
20749
|
+
times: num("Total passes, the original included. 2 doubles it."),
|
|
20750
|
+
until: num("Timeline time, in seconds, the loop should end at.")
|
|
20751
|
+
},
|
|
20752
|
+
required: ["clipId"]
|
|
20753
|
+
},
|
|
20754
|
+
method: "loopClip"
|
|
20755
|
+
},
|
|
20663
20756
|
{
|
|
20664
20757
|
name: "generate_captions",
|
|
20665
20758
|
description: "Lay timed caption clips on the timeline. By default it reads the real per-word timings stored with the footage and maps them through each clip\u2019s in/out points, so the cards land on the words actually spoken. Passing `text` instead falls back to estimating from word count, which drifts \u2014 only do that when get_transcript shows no stored transcript. The cards go on one video track free across the whole caption run, creating a track above everything when none is, so a run never overwrites a watermark or a title. Styling arguments apply to every card, so a whole run is restyled here rather than one update per card.",
|
|
@@ -20750,7 +20843,11 @@ var SEQUENCE_EDITS = /* @__PURE__ */ new Set([
|
|
|
20750
20843
|
"groupClips",
|
|
20751
20844
|
"setProximity",
|
|
20752
20845
|
"setClipSpeed",
|
|
20753
|
-
"setAutoLayout"
|
|
20846
|
+
"setAutoLayout",
|
|
20847
|
+
"setMarkers",
|
|
20848
|
+
"setScene",
|
|
20849
|
+
"setAudioMaster",
|
|
20850
|
+
"loopClip"
|
|
20754
20851
|
]);
|
|
20755
20852
|
var EXPECTED_SEQUENCE = str(
|
|
20756
20853
|
"Optional guard: the sequence id this edit is meant for (from list_sequences). If the tab has a different sequence open, the call is refused instead of editing the wrong one."
|
|
@@ -20858,7 +20955,8 @@ function toContent(result) {
|
|
|
20858
20955
|
if (!result || typeof result !== "object") {
|
|
20859
20956
|
return [{ type: "text", text: JSON.stringify(result) ?? "null" }];
|
|
20860
20957
|
}
|
|
20861
|
-
const { _image, _images, ...rest } = result;
|
|
20958
|
+
const { _image, _images, _file: _file2, ...rest } = result;
|
|
20959
|
+
if (_file2) rest.fileNote = "A file was made but this connection cannot deliver it. Use the local GoatEdit MCP server, which saves it to disk.";
|
|
20862
20960
|
const blocks = [];
|
|
20863
20961
|
const captions = [];
|
|
20864
20962
|
for (const candidate of [_image, ...Array.isArray(_images) ? _images : []]) {
|
|
@@ -20916,6 +21014,25 @@ function findLocalFile(root, wanted, maxDepth) {
|
|
|
20916
21014
|
return null;
|
|
20917
21015
|
}
|
|
20918
21016
|
|
|
21017
|
+
// src/run-refs.ts
|
|
21018
|
+
function resolveRefs(value, refs) {
|
|
21019
|
+
if (typeof value === "string") {
|
|
21020
|
+
const m = /^\$([A-Za-z_][\w-]*)((?:\.[^.]+)*)$/.exec(value);
|
|
21021
|
+
if (!m || !refs.has(m[1])) return value;
|
|
21022
|
+
let x = refs.get(m[1]);
|
|
21023
|
+
for (const key of m[2].split(".").slice(1)) {
|
|
21024
|
+
x = x && typeof x === "object" ? x[key] : void 0;
|
|
21025
|
+
if (x === void 0) throw new Error(`${value}: step "${m[1]}" has no "${m[2].slice(1)}". It returned ${JSON.stringify(refs.get(m[1])).slice(0, 300)}`);
|
|
21026
|
+
}
|
|
21027
|
+
return x;
|
|
21028
|
+
}
|
|
21029
|
+
if (Array.isArray(value)) return value.map((v) => resolveRefs(v, refs));
|
|
21030
|
+
if (value && typeof value === "object") {
|
|
21031
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, resolveRefs(v, refs)]));
|
|
21032
|
+
}
|
|
21033
|
+
return value;
|
|
21034
|
+
}
|
|
21035
|
+
|
|
20919
21036
|
// src/index.ts
|
|
20920
21037
|
var WEBSOCKET_PORT = 9e3;
|
|
20921
21038
|
var BIND_HOST = "127.0.0.1";
|
|
@@ -21100,6 +21217,18 @@ async function handleGenerateVoiceover(text, projectId) {
|
|
|
21100
21217
|
}
|
|
21101
21218
|
}
|
|
21102
21219
|
}
|
|
21220
|
+
function saveFileToDisk(result) {
|
|
21221
|
+
const file = result && typeof result === "object" ? result._file : void 0;
|
|
21222
|
+
if (!file || typeof file.data !== "string") return result;
|
|
21223
|
+
fs2.mkdirSync(FRAMES_DIR, { recursive: true });
|
|
21224
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
21225
|
+
const safe = String(file.name ?? "file").replace(/[^\w.-]+/g, "_").slice(0, 80);
|
|
21226
|
+
const target = path2.join(FRAMES_DIR, `${stamp}-${safe}`);
|
|
21227
|
+
const bytes = Buffer.from(file.data, "base64");
|
|
21228
|
+
fs2.writeFileSync(target, bytes);
|
|
21229
|
+
const { _file: _file2, ...rest } = result;
|
|
21230
|
+
return { ...rest, savedTo: target, bytes: bytes.length };
|
|
21231
|
+
}
|
|
21103
21232
|
function saveFramesToDisk(result, label) {
|
|
21104
21233
|
if (!result || typeof result !== "object") return result;
|
|
21105
21234
|
const frames = [result._image, ...Array.isArray(result._images) ? result._images : []].filter((f) => f && typeof f.data === "string" && typeof f.mimeType === "string");
|
|
@@ -21509,7 +21638,7 @@ var LOCAL_TOOLS = [
|
|
|
21509
21638
|
},
|
|
21510
21639
|
{
|
|
21511
21640
|
name: "run_tools",
|
|
21512
|
-
description:
|
|
21641
|
+
description: 'Run several tool calls in order in one round trip \u2014 the fast way to make a series of edits. Stops at the first failure unless continueOnError is set, and reports each step\'s result. Every step sees the edits of the steps before it. To use an id a step makes, name that step with `as` and write "$name.path" in a later step\'s args: { tool: "create_shape_clip", args: {...}, as: "bg" } then { tool: "set_keyframes", args: { clipId: "$bg.clipId", ... } }. A whole string arg is replaced; paths walk objects and arrays ("$made.clipIds.0"). With rollback, a failed run puts the whole project back as it was before the first step.',
|
|
21513
21642
|
inputSchema: {
|
|
21514
21643
|
type: "object",
|
|
21515
21644
|
properties: {
|
|
@@ -21520,12 +21649,14 @@ var LOCAL_TOOLS = [
|
|
|
21520
21649
|
type: "object",
|
|
21521
21650
|
properties: {
|
|
21522
21651
|
tool: { type: "string", description: "Tool name, e.g. move_clip." },
|
|
21523
|
-
args: { type: "object", description:
|
|
21652
|
+
args: { type: "object", description: 'That tool\'s arguments. A string "$name.path" is replaced by that value from an earlier step named `name`.' },
|
|
21653
|
+
as: { type: "string", description: "Name this step's result so later steps can refer to it." }
|
|
21524
21654
|
},
|
|
21525
21655
|
required: ["tool"]
|
|
21526
21656
|
}
|
|
21527
21657
|
},
|
|
21528
|
-
continueOnError: { type: "boolean", description: "Run the remaining steps after one fails." }
|
|
21658
|
+
continueOnError: { type: "boolean", description: "Run the remaining steps after one fails." },
|
|
21659
|
+
rollback: { type: "boolean", description: "Take a checkpoint first and restore it if any step fails, so a run either lands whole or not at all. Cannot be combined with continueOnError." }
|
|
21529
21660
|
},
|
|
21530
21661
|
required: ["calls"]
|
|
21531
21662
|
}
|
|
@@ -21579,7 +21710,8 @@ async function callTool(name, args) {
|
|
|
21579
21710
|
const target = projectId ?? chosenProjectId;
|
|
21580
21711
|
if (name === "import_local_file") return importLocalFile(forwarded, target);
|
|
21581
21712
|
const result = await sendToBrowser(tool.method, forwarded, target);
|
|
21582
|
-
|
|
21713
|
+
const withFile = saveFileToDisk(result);
|
|
21714
|
+
return saveToDisk ? saveFramesToDisk(withFile, name) : withFile;
|
|
21583
21715
|
}
|
|
21584
21716
|
async function importLocalFile(args, target) {
|
|
21585
21717
|
const local = await callLocal("find_local_file", { filename: args.filename }, target).catch((err) => {
|
|
@@ -21602,14 +21734,32 @@ async function runTools(args) {
|
|
|
21602
21734
|
if (!Array.isArray(calls) || !calls.length) {
|
|
21603
21735
|
throw new McpError(ErrorCode.InvalidParams, "calls must be a non-empty array of { tool, args }.");
|
|
21604
21736
|
}
|
|
21737
|
+
if (args.rollback && args.continueOnError) {
|
|
21738
|
+
throw new McpError(ErrorCode.InvalidParams, "rollback and continueOnError cannot be combined: a run that keeps going after a failure has nothing to roll back to.");
|
|
21739
|
+
}
|
|
21605
21740
|
const results = [];
|
|
21606
21741
|
const images = [];
|
|
21742
|
+
const refs = /* @__PURE__ */ new Map();
|
|
21607
21743
|
let failed = 0;
|
|
21744
|
+
let checkpointId;
|
|
21745
|
+
if (args.rollback) {
|
|
21746
|
+
const made = await callTool("create_checkpoint", { name: "run_tools rollback" });
|
|
21747
|
+
checkpointId = made?.checkpointId;
|
|
21748
|
+
}
|
|
21608
21749
|
for (let i = 0; i < calls.length; i++) {
|
|
21609
21750
|
const step = calls[i];
|
|
21610
21751
|
const tool = typeof step?.tool === "string" ? step.tool : "";
|
|
21611
21752
|
try {
|
|
21612
|
-
const
|
|
21753
|
+
const stepArgs = resolveRefs(step.args && typeof step.args === "object" ? step.args : {}, refs);
|
|
21754
|
+
const raw = await callTool(tool, stepArgs);
|
|
21755
|
+
if (typeof step.as === "string" && step.as) {
|
|
21756
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
21757
|
+
const { _image, _images, ...kept } = raw;
|
|
21758
|
+
refs.set(step.as, kept);
|
|
21759
|
+
} else {
|
|
21760
|
+
refs.set(step.as, raw);
|
|
21761
|
+
}
|
|
21762
|
+
}
|
|
21613
21763
|
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
21614
21764
|
const { _image, _images, ...rest } = raw;
|
|
21615
21765
|
for (const img of [_image, ...Array.isArray(_images) ? _images : []]) {
|
|
@@ -21628,7 +21778,21 @@ async function runTools(args) {
|
|
|
21628
21778
|
}
|
|
21629
21779
|
}
|
|
21630
21780
|
}
|
|
21631
|
-
|
|
21781
|
+
let rolledBack;
|
|
21782
|
+
if (failed && checkpointId) {
|
|
21783
|
+
try {
|
|
21784
|
+
await callTool("restore_checkpoint", { checkpointId });
|
|
21785
|
+
rolledBack = true;
|
|
21786
|
+
} catch (err) {
|
|
21787
|
+
rolledBack = `failed: ${err?.message ?? err}. Restore ${checkpointId} with restore_checkpoint.`;
|
|
21788
|
+
}
|
|
21789
|
+
}
|
|
21790
|
+
return {
|
|
21791
|
+
ok: failed === 0,
|
|
21792
|
+
results,
|
|
21793
|
+
...rolledBack !== void 0 ? { rolledBack } : {},
|
|
21794
|
+
...images.length ? { _images: images } : {}
|
|
21795
|
+
};
|
|
21632
21796
|
}
|
|
21633
21797
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
21634
21798
|
const { name, arguments: rawArgs } = request.params;
|
package/package.json
CHANGED