goatedit-mcp 1.1.13 → 1.1.15

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.
@@ -923,10 +923,10 @@ var require_codegen = __commonJS({
923
923
  return this._leafNode(new Throw(error2));
924
924
  }
925
925
  // start self-balancing block
926
- block(body, nodeCount) {
926
+ block(body2, nodeCount) {
927
927
  this._blockStarts.push(this._nodes.length);
928
- if (body)
929
- this.code(body).endBlock(nodeCount);
928
+ if (body2)
929
+ this.code(body2).endBlock(nodeCount);
930
930
  return this;
931
931
  }
932
932
  // end the current self-balancing block
@@ -2351,15 +2351,15 @@ var require_validate = __commonJS({
2351
2351
  validateFunction(it, () => (0, boolSchema_1.topBoolOrEmptySchema)(it));
2352
2352
  }
2353
2353
  exports.validateFunctionCode = validateFunctionCode;
2354
- function validateFunction({ gen, validateName, schema, schemaEnv, opts }, body) {
2354
+ function validateFunction({ gen, validateName, schema, schemaEnv, opts }, body2) {
2355
2355
  if (opts.code.es5) {
2356
2356
  gen.func(validateName, (0, codegen_1._)`${names_1.default.data}, ${names_1.default.valCxt}`, schemaEnv.$async, () => {
2357
2357
  gen.code((0, codegen_1._)`"use strict"; ${funcSourceUrl(schema, opts)}`);
2358
2358
  destructureValCxtES5(gen, opts);
2359
- gen.code(body);
2359
+ gen.code(body2);
2360
2360
  });
2361
2361
  } else {
2362
- gen.func(validateName, (0, codegen_1._)`${names_1.default.data}, ${destructureValCxt(opts)}`, schemaEnv.$async, () => gen.code(funcSourceUrl(schema, opts)).code(body));
2362
+ gen.func(validateName, (0, codegen_1._)`${names_1.default.data}, ${destructureValCxt(opts)}`, schemaEnv.$async, () => gen.code(funcSourceUrl(schema, opts)).code(body2));
2363
2363
  }
2364
2364
  }
2365
2365
  function destructureValCxt(opts) {
@@ -10257,12 +10257,12 @@ var require_websocket_server = __commonJS({
10257
10257
  }
10258
10258
  if (options.port != null) {
10259
10259
  this._server = http2.createServer((req, res) => {
10260
- const body = http2.STATUS_CODES[426];
10260
+ const body2 = http2.STATUS_CODES[426];
10261
10261
  res.writeHead(426, {
10262
- "Content-Length": body.length,
10262
+ "Content-Length": body2.length,
10263
10263
  "Content-Type": "text/plain"
10264
10264
  });
10265
- res.end(body);
10265
+ res.end(body2);
10266
10266
  });
10267
10267
  this._server.listen(
10268
10268
  options.port,
@@ -10359,8 +10359,8 @@ var require_websocket_server = __commonJS({
10359
10359
  */
10360
10360
  shouldHandle(req) {
10361
10361
  if (this.options.path) {
10362
- const index = req.url.indexOf("?");
10363
- const pathname = index !== -1 ? req.url.slice(0, index) : req.url;
10362
+ const index2 = req.url.indexOf("?");
10363
+ const pathname = index2 !== -1 ? req.url.slice(0, index2) : req.url;
10364
10364
  if (pathname !== this.options.path) return false;
10365
10365
  }
10366
10366
  return true;
@@ -13027,11 +13027,11 @@ var $ZodDate = /* @__PURE__ */ $constructor("$ZodDate", (inst, def) => {
13027
13027
  return payload;
13028
13028
  };
13029
13029
  });
13030
- function handleArrayResult(result, final, index) {
13030
+ function handleArrayResult(result, final, index2) {
13031
13031
  if (result.issues.length) {
13032
- final.issues.push(...prefixIssues(index, result.issues));
13032
+ final.issues.push(...prefixIssues(index2, result.issues));
13033
13033
  }
13034
- final.value[index] = result.value;
13034
+ final.value[index2] = result.value;
13035
13035
  }
13036
13036
  var $ZodArray = /* @__PURE__ */ $constructor("$ZodArray", (inst, def) => {
13037
13037
  $ZodType.init(inst, def);
@@ -13512,14 +13512,14 @@ function mergeValues(a, b) {
13512
13512
  return { valid: false, mergeErrorPath: [] };
13513
13513
  }
13514
13514
  const newArray = [];
13515
- for (let index = 0; index < a.length; index++) {
13516
- const itemA = a[index];
13517
- const itemB = b[index];
13515
+ for (let index2 = 0; index2 < a.length; index2++) {
13516
+ const itemA = a[index2];
13517
+ const itemB = b[index2];
13518
13518
  const sharedValue = mergeValues(itemA, itemB);
13519
13519
  if (!sharedValue.valid) {
13520
13520
  return {
13521
13521
  valid: false,
13522
- mergeErrorPath: [index, ...sharedValue.mergeErrorPath]
13522
+ mergeErrorPath: [index2, ...sharedValue.mergeErrorPath]
13523
13523
  };
13524
13524
  }
13525
13525
  newArray.push(sharedValue.data);
@@ -14692,13 +14692,13 @@ function _array(Class2, element, params) {
14692
14692
  }
14693
14693
  // @__NO_SIDE_EFFECTS__
14694
14694
  function _custom(Class2, fn, _params) {
14695
- const norm = normalizeParams(_params);
14696
- norm.abort ?? (norm.abort = true);
14695
+ const norm2 = normalizeParams(_params);
14696
+ norm2.abort ?? (norm2.abort = true);
14697
14697
  const schema = new Class2({
14698
14698
  type: "custom",
14699
14699
  check: "custom",
14700
14700
  fn,
14701
- ...norm
14701
+ ...norm2
14702
14702
  });
14703
14703
  return schema;
14704
14704
  }
@@ -19363,7 +19363,7 @@ var Server = class extends Protocol {
19363
19363
  super(options);
19364
19364
  this._serverInfo = _serverInfo;
19365
19365
  this._loggingLevels = /* @__PURE__ */ new Map();
19366
- this.LOG_LEVEL_SEVERITY = new Map(LoggingLevelSchema.options.map((level, index) => [level, index]));
19366
+ this.LOG_LEVEL_SEVERITY = new Map(LoggingLevelSchema.options.map((level, index2) => [level, index2]));
19367
19367
  this.isMessageIgnored = (level, sessionId) => {
19368
19368
  const currentLevel = this._loggingLevels.get(sessionId);
19369
19369
  return currentLevel ? this.LOG_LEVEL_SEVERITY.get(level) < this.LOG_LEVEL_SEVERITY.get(currentLevel) : false;
@@ -19746,12 +19746,12 @@ var ReadBuffer = class {
19746
19746
  if (!this._buffer) {
19747
19747
  return null;
19748
19748
  }
19749
- const index = this._buffer.indexOf("\n");
19750
- if (index === -1) {
19749
+ const index2 = this._buffer.indexOf("\n");
19750
+ if (index2 === -1) {
19751
19751
  return null;
19752
19752
  }
19753
- const line = this._buffer.toString("utf8", 0, index).replace(/\r$/, "");
19754
- this._buffer = this._buffer.subarray(index + 1);
19753
+ const line = this._buffer.toString("utf8", 0, index2).replace(/\r$/, "");
19754
+ this._buffer = this._buffer.subarray(index2 + 1);
19755
19755
  return deserializeMessage(line);
19756
19756
  }
19757
19757
  clear() {
@@ -19909,10 +19909,21 @@ var TEXT_STYLE_PROPS = {
19909
19909
  var BASE_TOOLS = [
19910
19910
  {
19911
19911
  name: "get_session_status",
19912
- description: "Check whether a GoatEdit editor tab is currently attached to this session, and which project it has open. Call this first if any other tool reports the editor is not connected.",
19912
+ description: "Check which GoatEdit editor tabs are attached to this session, and the project each has open (editors: projectId, projectName). Pass a projectId on other tools to pick one when several are open. Call this first if any other tool reports the editor is not connected.",
19913
19913
  inputSchema: { type: "object", properties: {} },
19914
19914
  method: null
19915
19915
  },
19916
+ {
19917
+ name: "get_design_guide",
19918
+ description: "Use this before designing, animating or cutting a video, and whenever you are unsure how something should look, move or sound. Returns GoatEdit's design guide: the director's workflow and quality bar, visual design (layout, safe areas, type sizes, colour, ready looks), motion design (easing, timing, joins between scenes), editing (story, hooks, cutting, captions, sound, grade) and playbooks per kind of video, with exact numbers and the GoatEdit tool for each step. No topic returns the list of skills and sections. Does not touch the project.",
19919
+ inputSchema: {
19920
+ type: "object",
19921
+ properties: {
19922
+ topic: str('A skill ("goatedit-video-director", or short: "director", "visual", "motion", "editing", "formats"), a section heading ("captions", "safe", "joins", "explainer") or a keyword ("ducking", "hook"). Several at once separated by commas: "captions, ducking". Omit for the index.')
19923
+ }
19924
+ },
19925
+ method: null
19926
+ },
19916
19927
  {
19917
19928
  name: "get_project_state",
19918
19929
  description: "Retrieve the project: settings, media bin, and every timeline track and clip. Thumbnails and waveform data are stripped, but the whole project is still large \u2014 pass `include` to fetch only the sections you need. If you only want tracks and clips, get_timeline is cheaper still.",
@@ -19973,7 +19984,7 @@ var BASE_TOOLS = [
19973
19984
  columns: num("With times: tiles per row. Defaults to a near-square grid."),
19974
19985
  saveToDisk: {
19975
19986
  type: "boolean",
19976
- description: "Write the frame to disk through the local bridge and return its path instead of the image itself. A frame is around 200KB, and inline it is paid for twice \u2014 as tokens and as a cached object \u2014 so a review pass over twenty cuts is expensive. With this set, read the returned path with your own file tools. Falls back to an inline image, with a warning, when the bridge is not running."
19987
+ description: "Write the frame to disk through the local bridge and return its path instead of the image itself. A frame is around 200KB, and inline it is paid for twice \u2014 as tokens and as a cached object \u2014 so a review pass over twenty cuts is expensive. With this set, read the returned path with your own file tools; a file is also full resolution rather than the reply-sized thumbnail. Defaults to true over the local connector and false over the cloud one, whose callers cannot read this machine's disk. Falls back to an inline image, with a warning, when the bridge is not running."
19977
19988
  },
19978
19989
  width: num("Width of the returned image in pixels, up to 1280. Defaults to 1024."),
19979
19990
  outlines: bool(`Draw every layer's box, handles and anchor point over the picture, with a timecode \u2014 the look of the editor's selection, on every layer at once. The same option the Export dialog offers as "Show layer outlines".`),
@@ -20064,6 +20075,30 @@ var BASE_TOOLS = [
20064
20075
  },
20065
20076
  method: "listProjects"
20066
20077
  },
20078
+ {
20079
+ name: "switch_project",
20080
+ description: "Open one of the person's saved projects (id from list_projects) in the connected GoatEdit tab, saving the project it had open first. Works from the projects page, where nothing else does. With several tabs connected, pass projectId to pick which tab switches.",
20081
+ inputSchema: {
20082
+ type: "object",
20083
+ properties: { id: { type: "string", description: "The saved project to open, from list_projects." } },
20084
+ required: ["id"]
20085
+ },
20086
+ method: "switchProject"
20087
+ },
20088
+ {
20089
+ name: "create_project",
20090
+ description: "Make a new, empty project and open it in the connected GoatEdit tab, saving the project it had open first. Use it when the person wants something new or has no projects yet; it works from the projects page. Size defaults to 1920x1080 at 30 fps: use 1080x1920 for a reel, short or story, 1080x1080 for a square post.",
20091
+ inputSchema: {
20092
+ type: "object",
20093
+ properties: {
20094
+ name: { type: "string", description: 'Project name. Defaults to "Untitled Project".' },
20095
+ width: { type: "number", description: "Frame width in pixels." },
20096
+ height: { type: "number", description: "Frame height in pixels." },
20097
+ fps: { type: "number", description: "Frames per second, e.g. 24, 25, 30 or 60." }
20098
+ }
20099
+ },
20100
+ method: "createProject"
20101
+ },
20067
20102
  {
20068
20103
  name: "list_sequences",
20069
20104
  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. Tools act on the open sequence unless they are given expectedSequenceId \u2014 pass one of these ids to read or edit another sequence without opening it (open_sequence moves the person's view; prefer expectedSequenceId).",
@@ -20476,7 +20511,8 @@ var BASE_TOOLS = [
20476
20511
  parentId: { type: ["string", "null"], description: "Parent clip id, or null to unparent." },
20477
20512
  position: { type: ["string", "null"], description: "Take position only from this clip." },
20478
20513
  scale: { type: ["string", "null"], description: "Take scale only from this clip." },
20479
- rotation: { type: ["string", "null"], description: "Take rotation only from this clip." }
20514
+ rotation: { type: ["string", "null"], description: "Take rotation only from this clip." },
20515
+ time: num("The moment whose parent pose the clip is measured from, in seconds on this clip's sequence. Default: when the clip starts (or the parent, if it starts later) \u2014 not the playhead. Set the child's own position and scale for the parent as it is at this time.")
20480
20516
  },
20481
20517
  required: ["clipId"]
20482
20518
  },
@@ -20659,6 +20695,36 @@ var BASE_TOOLS = [
20659
20695
  },
20660
20696
  method: "create3dObject"
20661
20697
  },
20698
+ {
20699
+ name: "create_showcase",
20700
+ description: "Place a Showcase from the Presets panel: pictures, clips or 3D models on cards moving through a seamless 3D loop \u2014 a ring, a globe, a tunnel, an orbit, a fan, a carousel, a grid. One clip, drawn on a transparent background, so it floats over whatever is beneath. Use it for a collage ring, a wall of work, UI cards orbiting a line, or a product with text rings round it (the product templates). Templates: ring (Showcase Ring), globe (Card Globe), tunnel (Card Tunnel), sphereWall (Inside Sphere), verticalRing, helix (Spiral Tower), flyThrough, orbit, doubleOrbit, wheel (Spin Wheel), risingWheel, fan (Card Fan), coverFlow, verticalFlow, carousel, filmStrip, diagonal, focusSlider, flipGrid, popGrid, isoGrid, isoColumns, masonry, columnDrift, deckPeel, stackShuffle, productRing, productOrbit, productRibbon, productWave, productHalo, productNeon, productStack. Tune it with set_clip_effect on its showcase effect (describe_effect showcase: count, spread, cardSize, tilt, speed, zoom\u2026).",
20701
+ inputSchema: {
20702
+ type: "object",
20703
+ properties: {
20704
+ template: str("Template id from the list above."),
20705
+ mediaFileIds: { type: "array", items: { type: "string" }, description: "Bin items for the cards, in order: images, videos or 3D models. Default every usable item in the bin (none for a product template, which then shows the GoatEdit logo)." },
20706
+ startTime: num("Start in seconds. Default 0."),
20707
+ duration: num("Length in seconds. Default one loop of the template."),
20708
+ trackId: str("Track to place it on. Default a new video track on top."),
20709
+ name: str("Clip name.")
20710
+ },
20711
+ required: ["template"]
20712
+ },
20713
+ method: "createShowcase"
20714
+ },
20715
+ {
20716
+ name: "replace_clip_media",
20717
+ description: "Swap the picture, footage or sound a clip plays for another bin item, keeping the clip where it is and everything done to it: position, scale, rotation, effects, colour grade, masks, keyframes and parent. Use this instead of deleting a clip and inserting the new file, which starts the new clip from defaults. A linked video/audio pair switches together. Video plays from its start; a source shorter than the clip shortens it.",
20718
+ inputSchema: {
20719
+ type: "object",
20720
+ properties: {
20721
+ clipId: CLIP_ID,
20722
+ mediaFileId: str("The bin item the clip should play instead.")
20723
+ },
20724
+ required: ["clipId", "mediaFileId"]
20725
+ },
20726
+ method: "replaceClipMedia"
20727
+ },
20662
20728
  {
20663
20729
  name: "remove_background",
20664
20730
  description: "Cut the subject out of a still image \u2014 a product, an object, a person \u2014 into a transparent PNG, the way stock photos become floating cut-outs. Runs in the editor tab (the model downloads once, ~9 MB for quality). The cut-out is added to the bin as a new file and, by default, every clip showing the photo switches to it (or only clipId when given); the original stays. For video use roto instead.",
@@ -20806,7 +20872,7 @@ var BASE_TOOLS = [
20806
20872
  },
20807
20873
  {
20808
20874
  name: "set_mask",
20809
- description: `Cut a clip to a shape: add, reshape, animate or remove a mask. Coordinates are fractions (0..1, x right, y down) of the clip's own picture by default (space "layer"), so the mask moves, scales and rotates with the clip; pass space "frame" to measure against the whole output frame instead, so the mask stays fixed on screen whatever the clip does. Switching an existing mask's space without new points keeps it where it is on screen at the playhead. Give a rectangle or ellipse as "box" { x, y, width, height }, or any outline as "points" [{ x, y }] (optional bezier handles hiX/hiY/hoX/hoY as offsets from the point). Several masks combine: the clip shows inside the union of normal masks, minus every inverted one. Omit maskId to add a new mask; pass it (from get_timeline's maskSummary or this tool's reply) to change one. keyframes animates the outline (rotoscoping): [{ time: seconds from the clip's start, box | points }] \u2014 all keys need the same number of points, so use one kind throughout.`,
20875
+ description: `Cut a clip to a shape: add, reshape, animate or remove a mask. Coordinates are fractions (0..1, x right, y down) of the clip's own picture by default (space "layer"), so the mask moves, scales and rotates with the clip; pass space "frame" to measure against the whole output frame instead, so the mask stays fixed on screen whatever the clip does. Switching an existing mask's space without new points keeps it where it is on screen at the playhead. Give a rectangle or ellipse as "box" { x, y, width, height }, or any outline as "points" [{ x, y }] (optional bezier handles hiX/hiY/hoX/hoY as offsets from the point). Several masks combine: the clip shows inside the union of normal masks, minus every inverted one. Omit maskId to add a new mask; pass it (from get_timeline's maskSummary or this tool's reply) to change one. keyframes animates the outline (rotoscoping): [{ time: seconds from the clip's start, box | points }] \u2014 all keys need the same number of points, so use one kind throughout. crop cuts the layer in from its edges with rounded corners, like Premiere's Crop: it rides the layer and trims whatever the masks leave; pass it alone (no other mask fields) to crop without adding a mask. Animate it with set_keyframes on cropLeft, cropTop, cropRight, cropBottom (0..1), cropRadius, cropFeather (px).`,
20810
20876
  inputSchema: {
20811
20877
  type: "object",
20812
20878
  properties: {
@@ -20823,7 +20889,8 @@ var BASE_TOOLS = [
20823
20889
  inverted: bool("Hide the inside and keep the outside."),
20824
20890
  enabled: bool("Turn the mask off without deleting it."),
20825
20891
  keyframes: { type: "array", items: { type: "object" }, description: "Animated outline: [{ time, box | points }]. Replaces existing keys; null removes them." },
20826
- remove: bool("Delete the mask named by maskId.")
20892
+ remove: bool("Delete the mask named by maskId."),
20893
+ crop: { type: ["object", "null"], description: "{ left?, top?, right?, bottom? } as fractions of the layer cut from each side (0.1 = 10%), radius? (corner rounding, px at 1080p), feather? (soft edge, px). Fields left out keep their value; null removes the crop and its keyframes." }
20827
20894
  },
20828
20895
  required: ["clipId"]
20829
20896
  },
@@ -21044,9 +21111,15 @@ var BASE_TOOLS = [
21044
21111
  "pathTrimEnd",
21045
21112
  "pathTrimOffset",
21046
21113
  "textPathOffset",
21047
- "textPathWave"
21114
+ "textPathWave",
21115
+ "cropLeft",
21116
+ "cropTop",
21117
+ "cropRight",
21118
+ "cropBottom",
21119
+ "cropRadius",
21120
+ "cropFeather"
21048
21121
  ],
21049
- description: "Property to animate. posX/posY and opacity are 0..1 fractions, rotation is degrees, scaleX/scaleY are multipliers (1 = unchanged), pathTrimEnd 0\u21921 draws a pen stroke on, textPathOffset slides text along its path (0..1) and textPathWave is the height of the wave bending it (px @1080p)."
21122
+ description: "Property to animate. posX/posY and opacity are 0..1 fractions, rotation is degrees, scaleX/scaleY are multipliers (1 = unchanged), pathTrimEnd 0\u21921 draws a pen stroke on, textPathOffset slides text along its path (0..1) and textPathWave is the height of the wave bending it (px @1080p). cropLeft/Top/Right/Bottom are 0..1 of the layer cut from that side (set_mask crop sets them once), cropRadius rounds the crop's corners and cropFeather softens its edge, both px @1080p."
21050
21123
  },
21051
21124
  keyframes: {
21052
21125
  type: "array",
@@ -21203,7 +21276,7 @@ var BASE_TOOLS = [
21203
21276
  width: num("Width of the returned image in pixels. Defaults to 512."),
21204
21277
  saveToDisk: {
21205
21278
  type: "boolean",
21206
- description: "Write the frame to disk through the local bridge and return its path instead of the image. Falls back to an inline image, with a warning, when the bridge is not running."
21279
+ description: "Write the frame to disk through the local bridge and return its path instead of the image. Defaults to true over the local connector, false over the cloud one. Falls back to an inline image, with a warning, when the bridge is not running."
21207
21280
  }
21208
21281
  },
21209
21282
  required: ["fileId"]
@@ -21936,6 +22009,225 @@ function toContent(result) {
21936
22009
  return blocks;
21937
22010
  }
21938
22011
 
22012
+ // ../api/_lib/skills-content.ts
22013
+ var SKILLS = [
22014
+ {
22015
+ "name": "goatedit-editing-story",
22016
+ "description": "Editing craft for GoatEdit \u2014 story structure and hooks, retention, where and why to cut (emotion, story, rhythm, eye trace), J and L cuts, pacing by format, cutting talking heads, choosing B-roll, captions with exact reading-speed and line rules, music, sound effects, ducking and loudness targets, and colour grading. Use when cutting footage, building a story from a transcript, adding captions, mixing sound, grading, or when an edit drags, confuses, or sounds amateur.",
22017
+ "frontmatter": {
22018
+ "name": "goatedit-editing-story",
22019
+ "description": "Editing craft for GoatEdit \u2014 story structure and hooks, retention, where and why to cut (emotion, story, rhythm, eye trace), J and L cuts, pacing by format, cutting talking heads, choosing B-roll, captions with exact reading-speed and line rules, music, sound effects, ducking and loudness targets, and colour grading. Use when cutting footage, building a story from a transcript, adding captions, mixing sound, grading, or when an edit drags, confuses, or sounds amateur."
22020
+ },
22021
+ "uri": "skill://goatedit/goatedit-editing-story/SKILL.md",
22022
+ "files": [
22023
+ {
22024
+ "path": "SKILL.md",
22025
+ "uri": "skill://goatedit/goatedit-editing-story/SKILL.md",
22026
+ "text": "---\nname: goatedit-editing-story\ndescription: Editing craft for GoatEdit \u2014 story structure and hooks, retention, where and why to cut (emotion, story, rhythm, eye trace), J and L cuts, pacing by format, cutting talking heads, choosing B-roll, captions with exact reading-speed and line rules, music, sound effects, ducking and loudness targets, and colour grading. Use when cutting footage, building a story from a transcript, adding captions, mixing sound, grading, or when an edit drags, confuses, or sounds amateur.\n---\n\n# Editing and story\n\n## Story first\n\nWrite or find the story before touching visuals. Every video, however short,\nhas the same spine:\n\n1. **Hook (0\u20133 s).** Say what this is and why it matters, in words and\n picture, before anything else. Open in the middle of the action, on a\n close-up, on the result, or on a bold claim. No logos, greetings or\n \"in this video\" first.\n2. **Promise.** Tell them what they will get \u2014 the list, the answer, the\n transformation. Previewing a list opens a loop the viewer wants closed.\n3. **Build.** One idea per beat, each raising a question the next answers.\n Cut anything that does not move the story, however good it looks.\n4. **Re-engage.** In anything over about a minute, add a turn, a reveal or a\n change of scene or pace every 45\u201390 s; long videos lose people at the\n points where nothing new arrives.\n5. **Payoff.** Answer the hook. Call back to the opening image.\n6. **Direction.** One clear next step (follow, comment a keyword, visit,\n buy), shown as well as said.\n\n**The test:** watch the first three seconds muted. Would a stranger know what\nthis is and want the rest?\n\n## Where to cut\n\nWhen choosing a cut, weigh in this order (Walter Murch's rule of six):\n**emotion** first, then **story**, then **rhythm**, then **eye trace** (where\nthe viewer is looking at the cut), then the screen's 2D plane, then 3D\ncontinuity. A cut that is right emotionally survives a continuity error; the\nreverse does not.\n\n- **Cut on action.** Cut in the middle of a movement \u2014 a turn, a hand\n reaching, a door \u2014 so the motion hides the cut.\n- **Cut on the eye.** Put the incoming subject where the viewer is already\n looking at the end of the outgoing shot.\n- **J cut:** the next shot's sound starts before its picture (we hear the\n answer, then see the speaker). **L cut:** the current sound carries over the\n next picture (the speaker keeps talking over B-roll). Both make dialogue\n flow; a straight cut on picture and sound together feels abrupt.\n- **Change the shot size or angle** across a cut of the same subject\n (wide to close, or 30\xB0 or more round). A small change looks like a jump.\n- **Keep screen direction.** People and objects keep moving the same way\n across cuts unless something turns them.\n- **Cut music on the beat or the bar**, and music edits on phrase boundaries.\n- **Let it breathe after a big moment** \u2014 hold 0.5\u20131 s before the next idea.\n\n## Pacing by format\n\n| Format | Average shot | Notes |\n| --- | --- | --- |\n| Reel / TikTok / Short | 1.1\u20131.5 s, none over about 3 s | first cut within 1 s, on the first charged word |\n| Ad (6\u201330 s) | 1\u20132 s | product or brand within the first 3\u20135 s |\n| YouTube explainer | 2\u20134 s visuals over steady voice | change the picture at least every 4\u20136 s |\n| Tutorial | as long as the action needs | zoom to the part being discussed; cut dead time |\n| Documentary / brand film | 3\u20136 s, longer holds on faces | let emotional beats breathe |\n| Music video / montage | on the beat; vary 1, 2 and 4 beats | build cut speed toward the drop |\n| Trailer | accelerate across the act; stop dead before the title | |\n\nFaster is not better by itself: change the pace to show what matters. Slow\ndown for the important line, speed up through set-up.\n\n## Cutting talking heads\n\n1. `get_transcript` (or `transcribe`). Every time comes from real word\n timings.\n2. Read every line. Keep the last clean take of each line; cut false starts\n and repeats.\n3. Cut between phrases, never inside one. Trim dead air to under 0.2 s but\n keep natural beats after commas and list markers (0.2\u20130.4 s). Remove a\n filler only when there is silence on both sides. Never speed up a voice.\n4. `apply_ai_edit` with `dryRun: true` first; check each proposed range\n against the transcript before applying.\n5. Hide jump cuts with a punch-in (scale 110\u2013120% on alternate cuts), a\n B-roll cover, or a graphic \u2014 not a dissolve.\n6. Transcripts end words 40\u201380 ms before the sound dies: cut after the decay.\n7. `create_checkpoint` before any destructive pass.\n\n## B-roll\n\n- One visual per noun phrase, starting on its word (at most 0.15 s early).\n- Prefer, in order: real footage of the thing named; a real screen recording\n zoomed to the part discussed; a clearly labelled mock; generated video with\n a concrete metaphor. `search_stock` for footage; check every result with\n `preview_media` before using it.\n- Every still or B-roll shot moves: a slow push of 3\u20138%, or a pan along the\n subject.\n- Use each B-roll shot once. Never invent results or numbers on screen.\n- In vertical video keep the speaker on screen about 45% of the time and\n never gone longer than about 3.5 s; mix split layouts (B-roll above,\n speaker below), full B-roll, and punch-ins.\n\n## Captions\n\n**Readable subtitles** (explainers, interviews, anything people must follow):\n- At most 42 characters per line and two lines, one line when it fits.\n- Reading speed at most 17\u201320 characters a second (about 160\u2013180 words a\n minute); children's content slower.\n- Break lines after punctuation and before conjunctions and prepositions.\n Never split an article from its noun, a first name from a last name, or a\n verb from its pronoun. Bottom-heavy shape.\n- On screen from the first word to just after the last; never across a scene\n cut if it can be avoided \u2014 split it at the cut instead.\n- Numbers one to ten in words, 11 and above in figures.\n\n**Social captions** (reels, shorts, ads):\n- 1\u20133 words per card, split on phrase boundaries (`generate_captions`\n `wordsPerChunk` 2\u20133), hard swaps, on screen two frames before the word.\n- Heavy sans, uppercase, about 60\u201364 px on a 1080\xD71920 frame, in the safe\n area \u2014 never over a mouth.\n- Colour on a quarter to a third of cards, whole card only, with fixed\n meaning (one colour for the key idea, one for the payoff or warning).\n- One style for the whole video; every card in the same box or the same\n treatment. Mixing boxed and bare looks cheap.\n- Captions are an overlay on top of the film, not a reserved band: keep the\n composition centred on the true centre of the frame.\n- Promote a word to a big designed graphic only at a real peak \u2014 at most one\n per beat and never two in a row.\n- Fix names the transcriber cannot know with `glossary`.\n\n## Sound\n\nSound is half the video. You cannot hear it, so measure it (`probe_audio`)\nand ask the user to listen.\n\n- **Tracks:** voice, music and effects on audio tracks of their own \u2014 sound on\n a video track is not mixed. Tag them with `set_audio_master` `roles`.\n- **Voice on top.** Music sits roughly 10\u201314 dB under speech\n (`set_audio_master` `ducking: true`, `duckDb` \u221210 to \u221214) and comes up\n between lines. The first second or two of a voiceover often wants the\n music lower still.\n- **Loudness targets** (integrated, from `probe_audio` `loudness`): YouTube,\n TikTok, Reels and Spotify about \u221214 LUFS; podcasts and Apple about \u221216 LUFS;\n broadcast \u221223 LUFS. True peak at or below \u22121 dBTP \u2014 turn on the limiter\n (`set_audio_master` `limiter: true`, `ceilingDb: -1`).\n- **Music** by mood: confident, darker beats for claims and news; light\n grooves for methods and how-tos; swelling strings or piano for emotion.\n Cut it on bars; end on the music's own ending (or a clean button), never a\n fade in the middle of a phrase. Fade in 0.3\u20131 s; fade out 1\u20133 s.\n- **Sound effects only on visible actions:** a soft air move on a real\n slide, a damped tap when a logo lands, a dry click for UI, paper for\n documents, one warm tone for a success. Vary files on repeated actions.\n No whoosh on every cut; no cartoon zaps unless the look is cartoon.\n- **Room tone.** Never cut voice to digital silence mid-scene; it sounds\n broken.\n- Never fade the last spoken word.\n\n## Grade\n\n- **Fix, then style.** First balance each shot: exposure so faces sit in the\n mids, white balance so whites are white, contrast so blacks are black but\n not crushed (`set_color_grade` exposure, temperature, tint, contrast).\n- **Match shots** in a scene to each other before adding a look; compare\n with `render_preview_frame` side by side.\n- **Skin first.** Midtones (`gamma`) are where skin lives; protect it when\n pushing a look. A look that turns skin green or grey is wrong whatever the\n palette.\n- **One look per video**, chosen in the brief: warm and soft for lifestyle\n and documentary; cool and contrasty for tech and thriller; lifted blacks\n (`lift`) for a film or matte feel; light saturation for premium.\n- Finish with texture when the look wants it: `filmGrain`, `halation`,\n `filmStock`, a gentle `vignette` \u2014 subtle enough that nobody names it.\n- Verify every grade by looking at frames; numbers can look right while the\n picture is wrong.\n",
22027
+ "digest": "sha256:f056af026658635792303a5992c77751b19e3e4413f83660ffc7322aeed501b5"
22028
+ }
22029
+ ]
22030
+ },
22031
+ {
22032
+ "name": "goatedit-formats",
22033
+ "description": "Playbooks for the kinds of video people make in GoatEdit \u2014 vertical reel or short from a talking head, ad, motion-graphics launch ad, product launch, explainer (Vox-style), tutorial or screen recording, podcast or interview clip, kinetic typography and lyric video, data story, montage, music video and trailer, event or wedding film, logo reveal and title sequence. Each gives the structure with timings, the look, the motion feel, sound and the checks that matter most. Use after the brief, once you know which kind of video it is.",
22034
+ "frontmatter": {
22035
+ "name": "goatedit-formats",
22036
+ "description": "Playbooks for the kinds of video people make in GoatEdit \u2014 vertical reel or short from a talking head, ad, motion-graphics launch ad, product launch, explainer (Vox-style), tutorial or screen recording, podcast or interview clip, kinetic typography and lyric video, data story, montage, music video and trailer, event or wedding film, logo reveal and title sequence. Each gives the structure with timings, the look, the motion feel, sound and the checks that matter most. Use after the brief, once you know which kind of video it is."
22037
+ },
22038
+ "uri": "skill://goatedit/goatedit-formats/SKILL.md",
22039
+ "files": [
22040
+ {
22041
+ "path": "SKILL.md",
22042
+ "uri": "skill://goatedit/goatedit-formats/SKILL.md",
22043
+ "text": "---\nname: goatedit-formats\ndescription: Playbooks for the kinds of video people make in GoatEdit \u2014 vertical reel or short from a talking head, ad, motion-graphics launch ad, product launch, explainer (Vox-style), tutorial or screen recording, podcast or interview clip, kinetic typography and lyric video, data story, montage, music video and trailer, event or wedding film, logo reveal and title sequence. Each gives the structure with timings, the look, the motion feel, sound and the checks that matter most. Use after the brief, once you know which kind of video it is.\n---\n\n# Format playbooks\n\nEach playbook is a starting point. The user's request and references win.\nSizes: vertical 1080\xD71920 (reels, shorts, stories), 16:9 1920\xD71080 (YouTube,\nweb, presentations), 1:1 or 4:5 1080\xD71350 (feeds). Ask before changing a\nsequence's size.\n\n## Vertical reel or short from a talking head\n\n**Structure (30\u201360 s):** hook in the first 3 s (a claim or command naming the\ntool, person or result) \u2192 one line of credibility \u2192 the list or story,\npreviewed early \u2192 each item: a title, a one-line definition, two or three\nliteral visuals, a punch-in on the key line \u2192 payoff calling back to the hook\n\u2192 CTA (show the thing, then a punch-in with the comment keyword).\n\n- Shots average 1.1\u20131.5 s; first cut at 0.6\u20131.0 s on the first charged word.\n- Layouts, every change a hard cut: split (B-roll top, speaker bottom) 30\u201340%\n of the time, full B-roll 50\u201355%, punch-in on the speaker 5\u201315%. The first\n frame is a split with the topic visible on top.\n- Social captions (see `goatedit-editing-story`), text inside x 90\u2013930,\n y 288\u20131248 on 1080\xD71920.\n- Music about 11 dB under the voice; effects only on visible actions.\n- **Raw variant:** cuts only, no B-roll or music, one word at a time, small,\n lower middle, on the exact word.\n- **From a long video:** pick the moment with value in its first 3 s; screen\n on top, face below, captions on the seam; end on a complete thought.\n\n## Ad (6, 15, 30 s)\n\nBuilt on the four things that make ads work (Google's ABCD research):\n- **Attention:** open on action, a close-up or the result \u2014 the hook\n is in the first 2\u20133 s and works muted.\n- **Brand:** show the product or brand early (within 3\u20135 s) and again at\n the end; in the picture, in the words, and said aloud.\n- **Connection:** people, faces, a feeling or a relatable problem; show the\n product being used, not just shown.\n- **Direction:** one call to action, on screen and spoken, held for at least\n 2 s at the end.\n\n- 6 s: one idea, one shot or three, logo and line at the end.\n- 15 s: problem (3 s) \u2192 product as answer (6 s) \u2192 proof or benefit (3 s) \u2192\n CTA (3 s).\n- 30 s: hook \u2192 problem \u2192 product \u2192 two or three benefits shown \u2192 proof\n (number, review, demo) \u2192 CTA.\n- Native-looking ads (phone footage, a real person talking, captions) often\n beat glossy ones on social; offer both when the user is testing.\n- Make three to five clearly different variants for testing rather than one\n polished one (`record_choice` when the user picks).\n\n## Motion-graphics ad (SaaS, app, fintech)\n\nThe anatomy most polished launch ads share, 30 s split into 5\u20138 sections\nof 3\u20136 s, each cut at a real scene change:\n\n| Section | What happens | Typical beats |\n| --- | --- | --- |\n| Hook | the pain or promise in huge type | a small setup line, then one giant word |\n| Transition | energy: a burst, a shape wipe, a coin flip | burst, shape |\n| Reveal | the logo lands, then what it is | logo, hub of connected icons |\n| Product | the app on a phone or in a window, two screens | phone, window, input being typed, chat |\n| Features | two or three capabilities, one per beat | cards, list, notification, chart |\n| Proof | logos of users, a number, a quote | logo wall, count-up |\n| End card | logo, line, URL \u2014 held 2 s or more | logo with subline |\n\n- Describe each section in one or two beats before building (\"Hook: text\n 'Global payments' small, then 'a pain' huge\"), build them as a grey\n animatic, then polish.\n- One palette of three colours (ground, ink, one accent) across all sections.\n- Every section change is a join in the film's current or a hard cut on a\n music hit; the reveal is the one inverse zoom.\n\n**Devices that make these ads feel premium** (each built per the director's\nbuild map):\n- **Type-only cards between product shots.** White or pale ground, one line\n of mid-weight type arriving word by word, the current word in the accent\n colour, a soft colour glow along one edge. They give the eye a rest and\n carry the story; alternate them with picture sections.\n- **A word swaps for a thing.** Mid-line, one word steps aside and a small\n icon or 3D object takes its place, then the line carries on. Separate\n clips, joined with a morph.\n- **Problem as clutter, answer as order.** Small icons or cards scatter\n around a short line, then everything clears for the product name.\n- **Collage ring.** Photos, UI cards and props orbit a short centred line:\n `create_showcase` (`ring`, `orbit`, `globe`, `fan`\u2026) with the pictures\n from the bin, the line as a text clip above it.\n- **Pull back to reveal.** Open full-frame on the product's screen, then pull\n out to show it on a device in a real room \u2014 the inverse zoom, spent once.\n- **Device in hand.** Real hands holding a phone over real footage, the UI\n composited on the screen; the UI moves as the product does.\n- **A typed call to action.** The last line types on with a cursor, then the\n logo assembles from its mark.\n- No hard cuts at all in the smoothest of these: every change is a push, a\n morph or a zoom, and the music carries the rhythm.\n\n## Product launch or feature video\n\n**Structure (45\u201390 s):** a cold open on the product doing its best thing \u2192\nthe problem in one line \u2192 the product name and promise \u2192 three features,\neach shown working in the real UI \u2192 the moment it all comes together \u2192 name,\ndate or link.\n\n- Look: dark product or Swiss minimal; real UI cropped tight and scaled up to\n fill its panel, never a whole window small.\n- Motion: smooth camera pushes into the UI, an oversized cursor leading to\n each click, the click triggering the next beat; pushes between features in\n one direction.\n- UI states animate as the product really behaves (progress fills, counts\n tick).\n- Sound: a modern bed with a clear build, UI clicks and soft whooshes on\n real moves, a hit on the name.\n\n## Explainer (Vox-style)\n\n**Structure (2\u201310 min, or 45\u201390 s short):** a puzzling question or surprising\nfact \u2192 why it matters \u2192 the story in chapters, each answering the previous\nchapter's question \u2192 the answer, with a callback to the opening \u2192 what it\nmeans now.\n\n- The voiceover is the clock. Script first, record or generate it,\n `transcribe`, then time each visual to its word.\n- Look: paper explainer \u2014 off-white paper ground, ink, one red, highlighter\n yellow, grain, torn edges, cut-out archival photos with hard shadows,\n stamps, handwritten notes, maps and charts. Archival footage tinted to the\n palette.\n- Motion: snappy, graphics stepped at 12 fps, a camera that pushes into\n documents and highlights the line being read, highlighter strokes drawing\n on, stamps slamming with a small shake.\n- Every claim gets a visible source on screen (newspaper, document, chart).\n- Change the picture every 4\u20136 s; vary between documents, maps, charts,\n footage and type.\n- Sound: understated music, paper and typewriter textures, a stamp thud.\n\n## Tutorial or screen recording\n\n**Structure:** what you will be able to do (show the result first) \u2192 what you\nneed \u2192 the steps, one per chapter, each with a title card \u2192 the result again\n\u2192 next step.\n\n- Zoom into the part of the screen being used (150\u2013250%), follow the cursor,\n and zoom out when moving to another area. Never leave a full desktop\n unreadable on a phone.\n- Cut every wait (loading, typing long strings); speed up repetitive action\n with `set_clip_speed` and say so.\n- Highlight the click target a beat before the click; label shortcuts on\n screen.\n- Chapter titles and a progress marker help people find their place.\n\n## Podcast or interview clip\n\n- Pick a moment that stands alone and starts strong; the first sentence is\n the hook, so trim any lead-in.\n- Vertical: speakers stacked or the active speaker full-frame, switching on\n who is talking; readable subtitles or social captions.\n- Speaker names as small lower thirds on first appearance.\n- Clean the audio: `set_clip_denoise` on noisy rooms, voices levelled to\n each other, \u221216 LUFS for podcast feeds, \u221214 for social.\n- An audiogram (waveform plus captions over a still) only when there is no\n video.\n\n## Kinetic typography or lyric video\n\n- Every word timed to the audio from real word timings; one to four words\n on screen.\n- The stressed word is biggest, heaviest or in the accent; soft words small.\n- Vary layout with the rhythm: stack, scatter, centre, along a path (text\n `path` with `pathOffset` keyed to slide words along a curve).\n- Move the camera across a large typographic canvas rather than swapping\n frames; push between lines in one direction.\n- Hits on the beat: a scale slam, a colour flip, a hard cut to a new layout.\n\n## Data story\n\n- One number or comparison per beat; the number is the hero.\n- Build the chart in the order it is spoken; count numbers up; highlight the\n one series that matters in the accent, the rest grey.\n- Label directly; put the source in small type on every chart frame.\n- Use a familiar comparison for scale (\"the size of three football pitches\").\n- Transitions between charts as morphs where the same data carries over.\n\n## Montage, music video, trailer\n\n- Cut to the music: mark beats (`set_markers`), then cut on beats, mostly on\n the strong ones; use 1, 2 and 4-beat shots and speed up into the drop.\n- Order shots for contrast: wide to close, still to moving, dark to light.\n- Speed ramps on action, slow motion on the emotional peak.\n- Trailer: three acts that accelerate; title cards between acts; a hard stop\n to silence before the final title; a sting after.\n- Text sparingly, big and short.\n\n## Event or wedding film\n\n- Story order, not clock order: the feeling of the day, the build, the\n peak (vows, speech, first dance), the release, the ending.\n- Lead with the best moment in the first 10 s as a teaser.\n- Speech audio carries the story; lay footage over it with L cuts.\n- Warm documentary look; slow, long holds on faces; music with an arc.\n\n## Logo reveal and title sequence\n\n- 3\u20136 s for a logo; the reveal lands on a sound hit and holds at least 1.5 s.\n- Build from the logo's own shapes (draw-on strokes, parts assembling, a\n mask reveal), never by redrawing the logo; use the real asset.\n- Titles: names on a consistent grid and timing, 2\u20133 s each, over picture\n that moves.\n- `list_presets` category `logo` and `text` have built-in starting points.\n",
22044
+ "digest": "sha256:3bbe7455c8762affb7029a3dfc5aa4c396b69247dc22207e9a19f68962674dd1"
22045
+ }
22046
+ ]
22047
+ },
22048
+ {
22049
+ "name": "goatedit-motion-design",
22050
+ "description": 'Motion design for GoatEdit \u2014 easing, durations, stagger, overshoot, hierarchy and holds with exact numbers; how one scene hands motion to the next (direction, matched speed, cutting mid-motion, carriers); the seam techniques (push, zoom-through, inverse zoom, match cut, morph, rack focus); keeping a scene alive without idle wobble; kinetic typography; and which GoatEdit tool does each. Use before animating anything, joining scenes, or when a video feels static, jumpy, floaty or "like slides".',
22051
+ "frontmatter": {
22052
+ "name": "goatedit-motion-design",
22053
+ "description": 'Motion design for GoatEdit \u2014 easing, durations, stagger, overshoot, hierarchy and holds with exact numbers; how one scene hands motion to the next (direction, matched speed, cutting mid-motion, carriers); the seam techniques (push, zoom-through, inverse zoom, match cut, morph, rack focus); keeping a scene alive without idle wobble; kinetic typography; and which GoatEdit tool does each. Use before animating anything, joining scenes, or when a video feels static, jumpy, floaty or "like slides".'
22054
+ },
22055
+ "uri": "skill://goatedit/goatedit-motion-design/SKILL.md",
22056
+ "files": [
22057
+ {
22058
+ "path": "SKILL.md",
22059
+ "uri": "skill://goatedit/goatedit-motion-design/SKILL.md",
22060
+ "text": '---\nname: goatedit-motion-design\ndescription: Motion design for GoatEdit \u2014 easing, durations, stagger, overshoot, hierarchy and holds with exact numbers; how one scene hands motion to the next (direction, matched speed, cutting mid-motion, carriers); the seam techniques (push, zoom-through, inverse zoom, match cut, morph, rack focus); keeping a scene alive without idle wobble; kinetic typography; and which GoatEdit tool does each. Use before animating anything, joining scenes, or when a video feels static, jumpy, floaty or "like slides".\n---\n\n# Motion design\n\nMotion has one job: show the viewer where to look next. Every move answers\n"what changed, and where should the eye go?" A video should feel like one\ncontinuous camera move through ideas, not a stack of slides that each animate\non their own.\n\n## The numbers\n\n| Principle | Rule |\n| --- | --- |\n| Easing | Never linear. Arriving: ease-out `[0.22,1,0.36,1]`. Leaving: ease-in `[0.55,0,1,0.45]`. Moving on screen: in-out `[0.65,0,0.35,1]`. Snappy arrivals: expo-out. |\n| Duration | Nudge 0.15 s. Entrance 0.3\u20130.5 s, never one element over 0.8 s. Across the frame 0.4\u20130.7 s. Camera push at a cut 0.35\u20130.45 s each side. Exit about 75% of its entrance. |\n| Stagger | 30\u201360 ms between siblings, in reading order or outward from the hero; the whole group inside 0.4\u20130.5 s. With 8+ items tighten the gap rather than lengthen the group. |\n| Overshoot | Pops and arrivals overshoot 5\u201310% and settle. Never on camera moves. No bounce-out or elastic-out eases: they read as a toy. |\n| Anticipation | A big exit starts with 0.1 s the other way, 3\u20135% of the distance. |\n| Hierarchy | One hero move at a time; everything else smaller, shorter or later. |\n| Consistency | Similar elements share one ease and one duration. Never a unique pair per element. |\n| Arcs | Organic things travel on curves; type and UI may go straight. |\n| Squash and stretch | Fast round things stretch along their travel and squash on impact. |\n| Motion blur | Only on fast moves, 2D layers only. |\n| Parallax | Background layers move 30\u201360% as far as the foreground. |\n| Holds | After an impact or reveal, hold 0.3\u20130.6 s. Text stays 0.3 s a word plus 0.5 s. |\n| Stillness before the payoff | Leave 0.3\u20130.75 s between the action and its result \u2014 the dramatic comma. |\n| Rhythm | Alternate fast and slow. Don\'t use the same join twice running unless it is a pattern. |\n| Sync | A visual lands 0.1\u20130.25 s before the word it shows, never after. With music, hero moves land on beats. |\n\n## One film, one current\n\n**Pick one direction for the whole video** \u2014 say, leftward \u2014 and send every\nordinary scene change that way. Other directions are reserved and mean\nsomething when spent:\n\n| Vector | Means |\n| --- | --- |\n| The current (e.g. left) | next beat, forward progress |\n| Up | a conclusion or reveal rising above what came before |\n| Zoom forward (push in) | going deeper into the same thought |\n| Zoom backward (pull out) | arrival: something bigger lands, the payoff |\n\n- Never run two scene changes back to back in opposite directions; ping-pong\n reads as a mistake. A change of direction needs a visible cause (a click,\n an impact) or a new chapter.\n\n## How one scene hands to the next\n\n**The rule:** how scene A leaves decides how scene B arrives \u2014 same axis,\nsame direction, matched speed, and the cut falls mid-motion on both sides.\n\n1. **Axis.** Sideways stays sideways, vertical stays vertical, zoom stays\n zoom.\n2. **Direction.** Never mirror. On zoom, direction is whether things grow or\n shrink: a shrinking (pull-back) exit must be answered by an entrance that\n also shrinks into place (starts oversized), not one that grows from small.\n This is the most common mistake, because grow-from-small is everyone\'s\n default entrance.\n3. **Speed.** Exit with ease-in, enter with the matching ease-out, over the\n same distance and time \u2014 the two halves of one in-out curve, so the speed\n matches at the cut.\n4. **Phase.** Cut while both sides are moving. A scene that settles to rest\n before the cut, or an entrance that starts from rest after it, is a dead\n beat.\n5. **No crossfades between scenes.** A dissolve has nothing for the eye to\n follow. Keep dissolves for time passing or a dream-like mood.\n\n**Carriers.** The eye follows objects. The strongest join hands a concrete\nthing across the cut at the same place and speed: a card that becomes the\nnext screen, a word that becomes a heading, a dot that becomes a map pin, a\ncursor mid-path. Plan carriers at storyboard time.\n\n**Partial travel.** Scene pushes travel about 12% of the frame (about 230 px\nat 1920) with a quick fade, not a full-frame slide. Full-frame slides look\nlike a slideshow.\n\n## The joins, and the GoatEdit tool for each\n\n`list_cuts` shows every cut and which ones are still (`stillCuts`).\n`join_scenes` makes them flow. `auto` picks one and says why in `picked`.\n\n| Technique | Use for | GoatEdit |\n| --- | --- | --- |\n| **Push** (cut the curve) | the default scene change, in the film\'s current | `join_scenes` type `push`, `direction` = the current, `duration` 0.3\u20130.4 |\n| **Zoom-through** | going deeper: fly into one hero element, the next scene opens out of it | `join_scenes` type `zoomThrough`, `clipId` = that element. Never into one letter of a row |\n| **Inverse zoom** | the payoff arriving: outgoing shrinks away, incoming arrives oversized and settles | push-out camera keys with `set_keyframes`: out 1\u21920.8, in 1.25\u21921, expo-out, about 0.7 s total. Spend it once or twice per video |\n| **Match cut** | one layer becomes another in the same spot | `join_scenes` type `matchCut` with `fromClipId`/`toClipId` |\n| **Morph** | every layer grows out of its partner (Smart Animate) \u2014 the strongest join | `join_scenes` type `morph`; name partners with the same last word |\n| **Rack focus** | the one cut you want seen: same surface, new state | blur spike 8\u201312 px peak at the cut with a 1.06 scale breath (`set_clip_effect` blur keyframes); at most once every 8 s |\n| **Hard cut on a beat** | energy, music videos, reels | no join; cut exactly on the beat marker |\n\nBlur on zoom joins: about 10 px for text-sized subjects, 18\u201320 px for\nfull-frame surfaces. Use two or three join types per video and repeat them;\nvariety for its own sake reads as a template showcase.\n\n## Keeping a scene alive\n\nA scene that finishes its entrance with seconds left is a planning problem.\nFill it with story, not wobble. Idle loops on graphics (floating, breathing,\npulsing glow) read as "the video is waiting" \u2014 don\'t. Each stretch between\nentrance and exit belongs to one of these:\n\n- **Staged reveals** \u2014 hold content back and reveal it on the words that\n name it. The default when a scene has two or more ideas.\n- **Camera with intent** \u2014 start wide, travel, arrive on the subject. A slow\n push on a photo or footage (3\u20138% over the shot) counts; `animate_scene`\'s\n `hold` drift on pictures does this.\n A push through a scene with depth is not one zoom: split it into planes\n (backdrop, subject, props in front) and let each grow at its own rate \u2014\n near things grow faster than far ones. Everything on one null scaled\n equally reads flat.\n- **The product doing its thing** \u2014 a progress bar fills, a count ticks, a\n highlight steps down a list.\n- **Acted-out beats** \u2014 a card files into a stack, an item is dragged, a\n result assembles.\n- **Cursor-led** \u2014 an oversized cursor walks to a button and its click\n triggers the next beat.\n\nTest: pause on any second. Something meaningful should be mid-flight.\n\n**Cause and effect.** Chain moves so each is launched by the last \u2014 click,\nsquash, release, flight, impact, recoil, reveal. A reaction starts on the\nframe of its cause, not "shortly after". Heavy things rebound slower; small\nthings snap.\n\n## Entrances\n\n- **Waterfall entry** for titles, openers and lists: words arrive from below\n one after another, each starting before the last settles, gaps shrinking,\n the last one snapping. Heavy words travel 60\u201380 px over 0.16\u20130.2 s, normal\n words 40\u201350 px over 0.13\u20130.16 s, small words 30\u201345 px over 0.1\u20130.13 s.\n Opacity cuts on rather than fading.\n- **One direction per cascade.** Never ease-in-out on an entrance.\n- **Text keeps its own animation.** A text layer gets a text preset\n (`set_text_animation`, letters or words, `delay` for its place in the\n cascade), never a block keyframe move.\n\n| Feel | Hero title | Text | Row of words | Picture hero | Support |\n| --- | --- | --- | --- | --- | --- |\n| smooth | `cinematicRise` | `riseFade` | `riseFade` | blurIn, zoomIn, maskReveal | fadeUp |\n| snappy | `maskRise` | `slideUp` | `maskRise` | popIn, slideIn, maskReveal | slideIn outward from the hero |\n| bouncy | `springUp` | `scaleIn` | `bounceDrop` | dropIn, popIn, spinIn | popIn |\n\nPick one feel for the whole video. `animate_scene` applies these by role\n(ground, hero, support, text, detail, follower) with `style` = the feel; use\n`dryRun: true` to see the plan, then hand-key only the hero move.\n\n## Kinetic typography\n\n- Time every word to the voice: `set_text_animation` with `syncToWords`, or\n cut each word on at its real start from `get_transcript`.\n- One to four words on screen at a time; the key word bigger, heavier or in\n the accent.\n- Vary scale and position with the rhythm of the line \u2014 a stressed word hits\n harder (`slamIn`, `focusPull`), a soft word drifts (`riseFade`).\n- Move the camera, not every word: a group of words can push or rotate as one\n (parent them to a null with `add_null` and `set_parent`).\n- Hand from line to line with a word-level push in the current\'s direction.\n\n## Stepped motion\n\nFor a hand-made, editorial or collage feel, step graphics at 12 fps inside a\n24\u201330 fps video (`set_clip_effect` `posterizeTime`). Keep camera moves and\nfootage smooth.\n\n## Workflow in GoatEdit\n\n1. Approved stills, one element per layer, layers named for their partners.\n2. `animate_scene` (`all: true` or per scene) in one feel.\n3. Hand-key the hero move with `set_keyframes` (times relative to the clip\n start; posX/posY are fractions of the frame).\n4. `list_cuts`, then `join_scenes` on each still cut, in the current.\n5. `lint_motion`; run each finding\'s `fix` calls one at a time; lint again\n until only judgement notes remain.\n6. `export_range` strips around every join and hero move, then fix what you\n see. A lint pass is not a watch.\n\nGoatEdit specifics: HTML clips crop at their box when moved \u2014 animate inside\nthem with CSS. 3D frames render slowly through the bridge, so keep strips\nshort. `describe_motion` returns the numbers `lint_motion` checks.\n',
22061
+ "digest": "sha256:d702e8725b07fef02ada42d2a48e236c71d7b1228f00202cad640cf69b6b5033"
22062
+ }
22063
+ ]
22064
+ },
22065
+ {
22066
+ "name": "goatedit-video-director",
22067
+ "description": "Start here for any GoatEdit video \u2014 reel, short, ad, explainer, product launch, tutorial, talking head, kinetic type, montage. The director's workflow (brief, references, script, storyboard, style frames, build, motion, sound, checks), which other GoatEdit skill to load for each step, the quality bar a finished video must clear, and the self-review that catches what lint cannot. Use before the first edit of a new video or a big change to one.",
22068
+ "frontmatter": {
22069
+ "name": "goatedit-video-director",
22070
+ "description": "Start here for any GoatEdit video \u2014 reel, short, ad, explainer, product launch, tutorial, talking head, kinetic type, montage. The director's workflow (brief, references, script, storyboard, style frames, build, motion, sound, checks), which other GoatEdit skill to load for each step, the quality bar a finished video must clear, and the self-review that catches what lint cannot. Use before the first edit of a new video or a big change to one."
22071
+ },
22072
+ "uri": "skill://goatedit/goatedit-video-director/SKILL.md",
22073
+ "files": [
22074
+ {
22075
+ "path": "SKILL.md",
22076
+ "uri": "skill://goatedit/goatedit-video-director/SKILL.md",
22077
+ "text": '---\nname: goatedit-video-director\ndescription: Start here for any GoatEdit video \u2014 reel, short, ad, explainer, product launch, tutorial, talking head, kinetic type, montage. The director\'s workflow (brief, references, script, storyboard, style frames, build, motion, sound, checks), which other GoatEdit skill to load for each step, the quality bar a finished video must clear, and the self-review that catches what lint cannot. Use before the first edit of a new video or a big change to one.\n---\n\n# GoatEdit video director\n\nYou are directing a video, not filling a timeline. Tools do the mechanics; this\nskill and its four siblings carry the taste. Load them as you reach each step:\n\n| Step | Load |\n| --- | --- |\n| Look: type, colour, layout, safe areas, choosing a look | `goatedit-visual-design` |\n| Motion: easing, timing, joins between scenes, kinetic type | `goatedit-motion-design` |\n| Story, cutting, B-roll, captions, sound, grade | `goatedit-editing-story` |\n| The playbook for the kind of video asked for | `goatedit-formats` |\n\nNo skill support in your app? Call `get_design_guide` \u2014 it serves the same\ntext by skill, section or keyword (`get_design_guide({ topic: "captions" })`).\n\n## Order of authority\n\n1. What the user asked for in this conversation.\n2. Their saved style (`get_style_profile`) and brand.\n3. These skills.\n\nA reference video the user gives beats every number here. Measure it\n(`begin_request` with `referenceMediaIds`) and match it.\n\n## The workflow\n\nNever skip ahead. Each step ends with something the user can approve.\n\n1. **Brief.** `begin_request` with the edit type and the user\'s style words,\n then `get_style_profile`. Write back, in three lines: who it is for, the\n one thing they should remember, the platform and length. Ask only what you\n cannot infer: platform, length, brand, whether there is a voiceover.\n2. **References.** Ask for one or two videos they like, or name two you will\n aim at ("a Vox explainer", "an Apple product film"). Say what you take from\n each: pacing, type, colour, motion feel.\n3. **Script and beat sheet.** For anything with words, write the script first.\n Split it into beats, one idea each, with a target duration. Spoken words run\n about 2.5 a second; a 30 s reel holds about 70 words. With a voiceover the\n voice is the clock: generate or record it, `transcribe`, and time every beat\n from real word timings, never from word counts.\n4. **Storyboard.** One line per beat: what is on screen, the hero element, the\n layout, what each element is built as (see Build map), the move into the\n next beat. Vary the layouts (full-bleed, split,\n type-only, close-up, data); never the same composition three beats running.\n Plan joins now: layers that become each other across a cut share a name\n ending ("S2 tag Price" then "S3 card Price") so `join_scenes` can morph them.\n5. **Animatic, then style frames.** For motion-graphics pieces, first build\n a rough draft of every section: black and white, plain type, flat shapes,\n one simple move each, timed to the voice or music. It proves the plan \u2014\n which idea goes in which section and when each move lands \u2014 before any\n polish. Then build two or three key frames as finished stills (the hook,\n the busiest beat, the end card), `render_preview_frame` each and show the\n user. Polish nothing until the plan and the look are approved. One element\n per layer.\n6. **Build.** Lay every beat on the timeline with its real duration. Plan\n tracks first (overlapping clips on one track replace each other): V1 main\n picture, V2 B-roll, V3 graphics, V4 captions; A1 voice, A2 music, A3\n effects. Mark beats with `set_scene`. Where the connector offers `run_tools`,\n send several edits as one call.\n7. **Motion.** `animate_scene` per scene in one feel for the whole video,\n hand-key only the hero move, then join the cuts (`list_cuts`, `join_scenes`).\n See `goatedit-motion-design`.\n8. **Sound.** Music bed, voice, effects only on visible actions, ducking,\n loudness. See `goatedit-editing-story`.\n9. **Check** (below). Fix, re-check, then hand over with the report.\n\nFor a quick edit (trim this, add captions, swap the music) skip to the step it\ntouches, but still run the checks that cover it.\n\n## Build map: what each element is made of\n\nDecide this per element at storyboard time and write it in the storyboard as\na "build as" column. The rule under every row: **anything the user might want\nto move, retime, recolour or swap is its own layer.**\n\n| Element | Build as | Not as |\n| --- | --- | --- |\n| Designed words \u2014 a headline, a kinetic line, a word that changes colour | text clips (`create_text_clip` + `set_text_animation`), one clip per line, or per word when that word moves on its own | words inside an HTML clip, or caption cards |\n| Spoken words, timed to the voice | captions (`generate_captions`), on their own track, one style | text clips placed by hand, words in HTML |\n| A word that turns into an icon or a picture | the word, the icon and the next word as separate clips; `join_scenes` `morph` or `matchCut` between them | one clip with everything baked in |\n| UI that behaves like UI \u2014 a typing field, chat bubbles arriving, a list scrolling, a toggle, a progress bar | one HTML clip per UI surface, CSS or WAAPI animation, no scripts. Text inside the UI stays in the HTML: it is part of the interface, not the film\'s type | a pile of text and shape clips pretending to be an app |\n| A moving backdrop \u2014 mesh gradient, soft glow blobs, grid, grain | one HTML clip on the lowest track, or a shape with effects | baked into a picture or into the scene\'s HTML |\n| A phone, laptop or monitor showing something | the device as its own layer; the screen content (HTML, footage, still) as another, masked to the screen with `set_mask` and parented to the device with `set_parent`; hands on top as a cut-out | one flat picture of a device with a screen |\n| A ring, globe, tunnel, orbit, fan or carousel of cards \u2014 pictures, clips or UI stills looping in 3D | `create_showcase` with the right template (the Presets panel\'s Showcase shelf), filled from the bin; tune it with `set_clip_effect` on its showcase effect | cards keyframed one by one round a null |\n| A product with words circling it | `create_showcase` with a product template (`productRing`, `productOrbit`\u2026) and the cut-out or 3D model | hand-built text on a path |\n| A few floating photos or cut-outs that each move on their own | one image clip each (`remove_background` for cut-outs), each with its own shadow, parented to a null (`add_null`) so the group drifts as one | a flattened collage image |\n| Props with depth \u2014 candy shapes, a tumbling object, a glass ring | `create_3d_shape` with one `material` family for the whole video | 3D for flat UI, body text or anything that must be read |\n| A logo with depth, a word as a solid | `create_3d_object` (text, or a transparent PNG or SVG) | \u2014 |\n| Logo reveal | the mark and the wordmark as separate layers so they can assemble | one image that fades in |\n| A scene change | `join_scenes` between the scenes | a transition drawn inside an HTML clip |\n\n**Text: designed or caption?** Ask: if the voice were deleted, would this text\nstill need to be on screen? Yes \u2014 it is designed type: a text clip, placed and\nanimated with the layout. No \u2014 it transcribes speech: a caption. A video can\nhave both; they never share a track or a style, and a designed headline never\ndoubles as the caption of the line it shows.\n\n**When HTML is the right call.** Something that behaves like a web page or an\napp, or motion that is trivial in CSS and miserable to keyframe (a ticker, a\ntyping cursor, a looping gradient). When it is wrong: the parts must animate\nseparately, morph into the next scene, be retimed by the user, or the text is\nthe hero. An HTML clip is one layer to everything else in GoatEdit. HTML clip\nrules: layout in px for the composition size, longhand font properties rather\nthan the `font` shorthand, class-styled divs rather than styled `b` or `span`,\nand animate inside the snippet \u2014 moving the clip crops it at its box.\n\n**When 3D earns its place.** One hero object or a handful of accents a scene,\nlit and finished the same way across the video (one material family, one\nlight direction). It renders slower than anything else, so keep it to the\nmoments that need depth: a logo, a product, props that burst or tumble past a\ndevice. Depth comes from scale, blur and parallax as much as from the model.\n\n**Track order, bottom to top:** backdrop; footage and devices; collage and 3D\nprops; designed type; UI overlays and cursor; captions; brand bug.\n\n**Breaking down a reference.** Watch it through, then for each section write:\ntime in and out, what is on screen, every element with its "build as" from\nthe table above, the move into the next section, and what the music does\nthere. That table is the storyboard; build from it, never from memory of the\nvideo.\n\n**Measure a reference shot before moving anything.** Thumbnails and contact\nsheets are too small to read perspective or depth; guesses from them are\nwrong. Pull full-resolution frames every ~0.1 s across the move (`ffmpeg`, or\n`preview_media` with `saveToDisk`) and measure a few things per frame: the\nsubject\'s width, where its top edge sits, how much that edge narrows (tilt).\nThose numbers tell a camera move (subject grows, edge barely moves) from a\nslide, give the starting tilt, and show *when* it moves \u2014 most moves happen in\na short burst and then hold. Match that timing with the eases; never spread a\nmove evenly because the reference "moves for a second". After every change,\nrender your frame and the reference at the same timestamps, side by side.\n\n## Tool habits that save rework\n\n- **Swap a picture, don\'t re-add it.** `replace_clip_media` changes what a\n clip plays and keeps its position, scale, effects, grade, keyframes and\n parent. Deleting and inserting starts the new clip from defaults. A picture\n of a different shape keeps the same scale \u2014 check the framing.\n- **Parent first, then animate the parent.** `set_parent` measures the child\n against the parent\'s pose when the child starts (or at `time`), not the\n playhead. Set the child\'s position and scale for the parent as it is at\n that moment. To follow a parent that is already animated, pass the `time`\n you laid the child out at.\n- **One effect on many clips.** Applying an effect with several clips\n selected adds it to each; in the editor, editing or removing it from the\n inspector changes all of them. Over MCP, `set_clip_effect` acts on the\n clip you name \u2014 call it per clip, or use `batch_edit`.\n- **Frames to disk.** Over the local connector `render_preview_frame` and\n `preview_media` write a full-resolution file by default and return its path;\n read it with your file tools. Over the cloud connector they come back\n inline, shrunk to fit. Contact sheets (`times`) are one image either way.\n- **Cut-outs keep their transparency through a colour grade and the CSS-type\n effects** (brightness, contrast, blur). Grade the cut-out to match the\n ground it sits on.\n- **3D limits today.** Each 3D layer has its own camera \u2014 fake a shared camera\n by giving near layers more scale growth than far ones. A 3D layer cannot be\n a parent: for a screen that tilts with a phone, nest the phone and its\n screen (`nest_clips`) and put the 3D layer on the nest. A `.glb` model\'s\n screen cannot show an image or video.\n\n## The quality bar\n\nA finished video clears all of these. Each is something a viewer notices.\n\n- **The first frame works as a thumbnail** and the first 2\u20133 seconds say what\n the video is about, even muted.\n- **One focal point per frame.** The hero is clearly bigger, brighter or\n sharper than everything else \u2014 about 2\u20133\xD7 the next element. If you squint\n and two things compete, one goes.\n- **Everything is readable.** Text holds on screen at least 0.3 s a word plus\n 0.5 s, sits inside the safe area, and has contrast of 4.5:1 or better\n (body) and 3:1 (large display type) against what is actually behind it.\n- **Nothing moves linearly, nothing moves for no reason.** Every move points\n the eye at what changed. No idle bobbing to fill time \u2014 add story instead.\n- **The cuts flow.** Every scene change is either a deliberate hard cut on a\n beat or a join where motion carries through.\n- **The layouts vary** and the look stays one look: one type family pairing,\n one palette, one motion feel, the same caption style throughout.\n- **Every picture earns its place.** B-roll shows the noun being said, at the\n moment it is said. No generic stock filler, no tiny UI floating in space.\n- **Sound is finished.** Voice clear and on top, music ducked under it, no\n clipped peaks, loudness near the platform target.\n- **It ends on purpose** \u2014 a payoff, a call to action, or a held end card \u2014\n never a fade to nothing mid-thought.\n\n## Checks before handing over\n\nRun them and say which ran and what they showed. A check not run is reported\nas not run, never as passed.\n\n1. `lint_timeline` and `lint_motion`; apply each fix one call at a time, lint\n again until only judgement notes remain.\n2. `list_cuts` \u2014 every cut intended; `stillCuts` joined or deliberate.\n3. `export_range` with 12\u201324 frames over each section (one tiled sheet), and\n a strip around every join (\u22120.3, \u22120.15, 0, +0.15, +0.35 s). Look for blank\n frames, flashes, text over faces, captions over the wrong shot.\n4. `render_preview_frame` at the first frame, the busiest frame, the end card,\n and behind every caption style.\n5. `probe_audio` over the whole video: voice present, `truePeak` at or below\n \u22121 dBTP, integrated loudness near the platform target.\n6. The self-review below.\n\n## Self-review: critique it like a hard client\n\nLook at the frames from step 3 and score each line 1\u201310. Anything under 7\ngets fixed before you hand over, and you say what you changed.\n\n- **Impact** \u2014 does the first frame stop a scroll? Is there one clear idea?\n- **Hierarchy** \u2014 one dominant element per frame, a clear reading order,\n weight and size contrast (not everything bold).\n- **Colour and type** \u2014 a deliberate palette (not default pure red, pure\n black, pure white), at most two families, sizes from one scale.\n- **Motion** \u2014 eases with character, staggered entrances, joins that carry\n momentum, no dead air and no wobble.\n- **Craft** \u2014 alignment to a grid, consistent margins, no clipped text, no\n stray layers, captions consistent.\n- **Story** \u2014 hook, build, payoff; every beat earns its seconds.\n\nThen name the single weakest frame and improve it. A lint pass is not a watch:\nyou cannot hear, so ask the user to watch for lip sync and clipped words.\n\n## Failure list \u2014 what makes a video look AI-made\n\n- The same centred title-over-background layout on every beat.\n- Small subjects floating in big empty frames; whole web pages shrunk to phone\n size.\n- Everything bold, everything the same size, everything the same colour.\n- Default palettes: pure #FF0000, #000000 and #FFFFFF together; rainbow\n accents; a different accent per scene.\n- Linear moves, everything entering at once, every element fading up the same\n way, crossfades between every scene.\n- Text that leaves before it can be read; captions that blink or jump styles.\n- Stock shots that only loosely match the words; AI stills used as B-roll;\n invented numbers or redrawn logos.\n- A music bed at full level over the voice, a whoosh on every cut.\n- Ending on a fade to black in the middle of a sentence.\n\n## Talking to the user\n\n- Before a long run of edits, say the plan in two or three lines, then work\n without stopping to ask.\n- Offer variants for open-ended requests (one sequence each: fast cut, story\n cut, minimal cut) and call `record_choice` when they pick, so GoatEdit\n learns their taste.\n- Never publish or post anything, never promise a video will go viral, never\n call a draft final \u2014 the user\'s review is the last gate.\n',
22078
+ "digest": "sha256:2e7e3bbc86aeb1f37c50e77c898ca1e9af330fd7ee225a3a5cc2122e8f840d48"
22079
+ }
22080
+ ]
22081
+ },
22082
+ {
22083
+ "name": "goatedit-visual-design",
22084
+ "description": 'Visual design for GoatEdit videos \u2014 layout grids and safe areas for 16:9, 9:16 and 1:1, a type scale with real pixel sizes, hierarchy, colour palettes and contrast, picking and holding one look (eight ready looks with palettes and fonts), treating footage, screenshots, data and 3D. Use when designing frames, titles, lower thirds, cards, end cards, or when a video looks cheap, cluttered or "AI-made".',
22085
+ "frontmatter": {
22086
+ "name": "goatedit-visual-design",
22087
+ "description": 'Visual design for GoatEdit videos \u2014 layout grids and safe areas for 16:9, 9:16 and 1:1, a type scale with real pixel sizes, hierarchy, colour palettes and contrast, picking and holding one look (eight ready looks with palettes and fonts), treating footage, screenshots, data and 3D. Use when designing frames, titles, lower thirds, cards, end cards, or when a video looks cheap, cluttered or "AI-made".'
22088
+ },
22089
+ "uri": "skill://goatedit/goatedit-visual-design/SKILL.md",
22090
+ "files": [
22091
+ {
22092
+ "path": "SKILL.md",
22093
+ "uri": "skill://goatedit/goatedit-visual-design/SKILL.md",
22094
+ "text": '---\nname: goatedit-visual-design\ndescription: Visual design for GoatEdit videos \u2014 layout grids and safe areas for 16:9, 9:16 and 1:1, a type scale with real pixel sizes, hierarchy, colour palettes and contrast, picking and holding one look (eight ready looks with palettes and fonts), treating footage, screenshots, data and 3D. Use when designing frames, titles, lower thirds, cards, end cards, or when a video looks cheap, cluttered or "AI-made".\n---\n\n# Visual design for video\n\nA video frame is a poster seen for two seconds. It has to read at a glance,\non a phone, while moving. Design every key frame as a still first\n(`render_preview_frame`), get it approved, then animate it.\n\n## The frame and safe areas\n\n**Margins (1920\xD71080).** Keep text and key subjects 120 px from the sides and\n100 px from top and bottom. Full-bleed pictures and backgrounds may run to the\nedge; words may not.\n\n**Vertical 9:16 (1080\xD71920).** Platform buttons and captions cover the edges.\nThe zone that is clear on TikTok, Reels and Shorts together is about\n**x 120\u2013888, y 288\u20131248**. Keep every word, face and logo inside it. The\nbottom 670 px is the most covered (captions, handle, buttons); the right\nedge from y 840 down holds the like/comment column. Platforms move their UI\nseveral times a year, so check a frame against the app before shipping.\n\n**1:1 (1080\xD71080).** 90 px margins all round.\n\n**Grid.** Use a 12-column grid (16:9) or 6-column (9:16) with 24\u201332 px\ngutters. Align every text block\'s left edge to a column; ragged starts look\ncareless. Centre only single short lines (titles, end cards); left-align\nanything with two or more lines.\n\n**Negative space.** Statement frames (a title, a number, a quote) leave\n50\u201360% of the frame empty. Dense frames (a chart, a list of three) are the\nexception, and still keep the margins.\n\n**Depth.** Build frames in three planes: ground (backdrop, texture,\nfootage), subject (the hero), and foreground accents (a label, an arrow, a\nhighlight). Shadow, blur or scale separates them. A frame with everything on\none plane looks like a slide.\n\n## Hierarchy\n\n- **One hero per frame**, 2\u20133\xD7 the size (or brightness, or sharpness) of the\n next element. Squint at the frame: the hero is what is left.\n- **Reading order** is size, then contrast, then position (top-left first in\n left-to-right languages). Arrange those three to agree.\n- **Weight contrast.** Pair a heavy display weight with a light or regular\n body weight. When everything is bold, nothing is.\n- **Three levels at most** on one frame: hero, support, detail.\n\n## Type\n\n**Scale for a 1080-tall frame** (scale by height for other sizes):\n\n| Role | Size px | Weight | Notes |\n| --- | --- | --- | --- |\n| Display / hero number | 140\u2013220 | 700\u2013900 or a display face | 1\u20134 words |\n| Headline | 80\u2013110 | 600\u2013800 | up to about 8 words |\n| Subhead / label | 44\u201356 | 500\u2013600 | tracking +2\u20134% if uppercase |\n| Body / lower-third line | 34\u201340 | 400\u2013500 | 1\u20132 lines |\n| Small print / source | 24\u201328 | 400 | never below 22 |\n\nFor vertical video sized for phones, captions run about 60\u201364 px, headlines\n90\u2013130 px, and nothing is under 36 px.\n\n**Rules.**\n- At most two families: one display or headline face, one text face. A mono\n or a script is a third only as a small accent.\n- Line height 1.0\u20131.1 for display, 1.2\u20131.35 for body. Tight tracking\n (\u22121 to \u22123%) on large display type, wider (+5\u201310%) on small uppercase labels.\n- Lines of body text run 25\u201345 characters in video, never a paragraph.\n- Break lines on phrases, never between an article and its noun or a first\n and last name. Prefer a bottom-heavy shape (short line over long line).\n- Text on footage needs a plate, a gradient scrim, or a shadow \u2014 check the\n contrast against the actual busiest frame behind it, not the first frame.\n- Before placing a long title, `measure_text` to check it fits its box.\n\n**Pairings that work.**\n- Editorial: a condensed sans headline (Oswald, Bebas Neue, Anton) + a serif\n body (Libre Baskerville, Source Serif, Lora).\n- Modern product: one grotesk in two weights (Inter, Manrope, Satoshi, DM\n Sans, General Sans), tight display tracking.\n- Luxury: a high-contrast serif display (Playfair Display, DM Serif Display,\n Cormorant) + a light sans.\n- Loud social: a heavy sans (Montserrat Black, Archivo Black, Poppins\n ExtraBold) for captions and hooks, uppercase, with one accent colour.\n- Technical: a grotesk + a mono for labels and numbers (JetBrains Mono, IBM\n Plex Mono, Space Mono).\n\n## Colour\n\n**Build a palette, not a set of colours.** Four roles:\n\n| Role | Share of frame | Example |\n| --- | --- | --- |\n| Ground | 60% | off-white #F4F1EA, or deep ink #0E0F12 |\n| Ink | 30% | near-black #1A1A1A on light; #EDEDED on dark |\n| Accent | \u226410% | one saturated colour that means "look here" |\n| Support | optional | a muted second colour for charts and secondary shapes |\n\n- One accent per video. It marks the hero word, the key number, the button.\n Spend it sparingly or it stops meaning anything.\n- Avoid pure #FFFFFF on pure #000000 and the stock primaries (#FF0000,\n #0000FF). Off-whites, warm blacks and tuned accents (tomato #E5432D, cobalt\n #2F5BEA, signal yellow #F6D743, mint #3DDC97) read as designed.\n- **Contrast:** 4.5:1 for body text and captions, 3:1 for display type of\n about 48 px and up, 7:1 when the text sits over moving footage.\n- Colour carries meaning in a video, so keep it consistent: if red marks\n "the problem" in beat 2, it marks the problem everywhere.\n- Grade the footage and the graphics into one world: a warm graded shot under\n a cold blue graphic looks pasted on. Pull the accent from the footage when\n you can.\n\n## Pick one look, then hold it\n\nChoose a look in the brief, write down its atoms (palette, fonts, texture,\ncorner radius, shadow, motion feel), and use only those for the whole video.\nThese eight cover most requests; adapt colours to the brand.\n\n| Look | Ground / ink / accent | Fonts | Texture and shape | Motion feel |\n| --- | --- | --- | --- | --- |\n| **Paper explainer** (Vox-style) | #EFE7D6 / #1E1B18 / #D7372B + highlighter #F6D743 | Oswald + Libre Baskerville, Caveat for notes | grain, torn paper, hard offset shadows, highlighter bands, stamps | snappy, stepped on 12 fps for graphics |\n| **Swiss minimal** | #F5F5F2 / #111 / #2F5BEA | one grotesk, two weights | flat, hairline rules, strict grid, no shadows | smooth, precise, short travel |\n| **Dark product** | #0B0C0F / #F2F2F2 / #7C5CFF or brand | Inter or Manrope | soft glow, glass panels, rounded 16\u201324 px | smooth, camera push-ins |\n| **Bold poster** | #FFFFFF / #1C1410 / #D8000F | slab or script display + serif body | flat plane, thick rules, tilted display type | snappy, slams |\n| **Warm documentary** | graded footage / #FAF7F0 / #E0A458 | serif titles + small sans labels | film grain, halation, letterbox optional | slow, long holds |\n| **Neon night** | #07060B / #F5F5F5 / #FF2D95 + #00E5FF | wide grotesk or mono | glow, scanlines sparingly | snappy, glitch only on beats |\n| **Playful pastel** | #FFF4E8 / #2B2B2B / #FF7A59 + #7BDFF2 | rounded sans (Nunito, Baloo) | blobs, thick outlines, stickers | bouncy |\n| **Luxury serif** | #F7F3EE or #101010 / ink / champagne #C9A96E | high-contrast serif + light sans | lots of space, thin rules | slow, fades and mask reveals |\n\nGoatEdit\'s caption styles (`captionStyle` on text tools) and the preset\nlibrary (`list_presets`, categories text, paper, logo, lower-third, bg) carry\nready versions of several of these.\n\n## Pictures\n\n- **Crop to the point.** A screenshot is cropped to the phrase or button being\n discussed and scaled up until it fills its panel; a whole page at phone size\n is unreadable.\n- **Real beats generated.** Real footage of the thing named, then a real\n screen recording, then a clearly labelled mock, then generated video with a\n concrete metaphor. Never an AI still as B-roll; never a redrawn logo.\n- **Faces.** Eyes about one third from the top of the frame; leave look room\n in the direction they face. Never put text over a mouth.\n- **Cut-outs.** `remove_background` on a person or product and set it over a\n designed ground gives depth that a rectangle never does; add a shadow\n (`set_clip_effect` dropShadow) so it sits on the surface.\n- **Texture.** A little film grain (`filmGrain`) or `paperTexture` over flat\n graphics stops them looking like a slide; keep it subtle.\n- **3D.** Use a 3D object or shape when depth or rotation explains something\n (a product turn, a layered system). Light it from one side, keep it on the\n palette, and keep camera moves slow.\n\n## Data on screen\n\n- One number or one comparison per frame. The number is the hero, the label\n is small, the source is smallest.\n- Label lines and bars directly instead of using a legend.\n- Grey for context, the accent for the one series that matters.\n- Animate the data building in the order it is spoken; count numbers up\n (`set_text_animation` preset `countUp`) when the size of the change is the\n point.\n- Never invent a number. Every figure comes from the user or a source you\n can name on screen.\n\n## Checks for a frame\n\n- Squint: one thing dominates.\n- Margins and the safe area hold; nothing is clipped.\n- Two families at most, sizes from the scale, one accent.\n- Text contrast checked against the busiest frame behind it.\n- It still reads at phone size: shrink the render to 360 px wide and look.\n',
22095
+ "digest": "sha256:93bc81a8e924ca3451b78f5f3e043427001c7f6e668160cc1b3331b88de3066d"
22096
+ }
22097
+ ]
22098
+ }
22099
+ ];
22100
+
22101
+ // ../api/_lib/design-guide.ts
22102
+ var body = (text) => text.replace(/^---\n[\s\S]*?\n---\n/, "");
22103
+ var SECTIONS = SKILLS.flatMap((skill) => {
22104
+ const parts = body(skill.files[0].text).split(/^(?=## )/m);
22105
+ return parts.map((text, i) => ({
22106
+ skill,
22107
+ heading: i === 0 ? "Introduction" : text.slice(3, text.indexOf("\n")).trim(),
22108
+ text: text.trim()
22109
+ }));
22110
+ });
22111
+ var SKILL_ALIASES = {
22112
+ director: "goatedit-video-director",
22113
+ workflow: "goatedit-video-director",
22114
+ start: "goatedit-video-director",
22115
+ visual: "goatedit-visual-design",
22116
+ design: "goatedit-visual-design",
22117
+ look: "goatedit-visual-design",
22118
+ motion: "goatedit-motion-design",
22119
+ animation: "goatedit-motion-design",
22120
+ editing: "goatedit-editing-story",
22121
+ edit: "goatedit-editing-story",
22122
+ story: "goatedit-editing-story",
22123
+ formats: "goatedit-formats",
22124
+ format: "goatedit-formats",
22125
+ playbook: "goatedit-formats",
22126
+ playbooks: "goatedit-formats"
22127
+ };
22128
+ var MAX_REPLY = 24e3;
22129
+ var MAX_TOPICS = 10;
22130
+ var norm = (s) => s.toLowerCase().replace(/[^a-z0-9:]+/g, " ").trim();
22131
+ function findSkill(term) {
22132
+ const t = norm(term).replace(/ /g, "-");
22133
+ const name = SKILL_ALIASES[t] ?? (t.startsWith("goatedit-") ? t : `goatedit-${t}`);
22134
+ return SKILLS.find((s) => s.name === name);
22135
+ }
22136
+ function findSections(term) {
22137
+ const t = norm(term);
22138
+ if (!t) return [];
22139
+ const byHeading = SECTIONS.filter((s) => norm(s.heading).includes(t));
22140
+ if (byHeading.length) return byHeading;
22141
+ const words = t.split(" ").filter((w) => w.length > 2 || /\d/.test(w));
22142
+ if (!words.length) return [];
22143
+ const scored = SECTIONS.map((section) => {
22144
+ const hay = norm(section.text);
22145
+ let score = 0;
22146
+ for (const w of words) {
22147
+ const hits = hay.split(w).length - 1;
22148
+ if (!hits) return { section, score: 0 };
22149
+ score += Math.min(hits, 6) + (norm(section.heading).includes(w) ? 10 : 0);
22150
+ }
22151
+ return { section, score };
22152
+ }).filter((s) => s.score > 0).sort((a, b) => b.score - a.score);
22153
+ return scored.slice(0, 3).map((s) => s.section);
22154
+ }
22155
+ function index() {
22156
+ const lines = [
22157
+ "# GoatEdit design guide",
22158
+ "",
22159
+ "Five skills carry the taste GoatEdit's tools cannot: how to direct, design, animate, cut and finish a video. Start with goatedit-video-director, then read the sections each step needs.",
22160
+ "",
22161
+ 'Ask for a whole skill (topic "goatedit-motion-design" or just "motion"), a section by its heading ("captions", "safe", "joins"), or any keyword ("ducking", "hook", "kinetic"). Several topics can go in one call.',
22162
+ ""
22163
+ ];
22164
+ for (const skill of SKILLS) {
22165
+ lines.push(`## ${skill.name}`, skill.description, "", `Sections: ${SECTIONS.filter((s) => s.skill === skill && s.heading !== "Introduction").map((s) => s.heading).join(" \xB7 ")}`, "");
22166
+ }
22167
+ return lines.join("\n");
22168
+ }
22169
+ function getDesignGuide(topic) {
22170
+ const topics = (Array.isArray(topic) ? topic : topic === void 0 || topic === null ? [] : [topic]).flatMap((t) => String(t).split(/[,;]/)).map((t) => t.trim()).filter(Boolean).slice(0, MAX_TOPICS);
22171
+ if (!topics.length) return index();
22172
+ const parts = [];
22173
+ const seen = /* @__PURE__ */ new Set();
22174
+ const missed = [];
22175
+ for (const term of topics) {
22176
+ const skill = findSkill(term);
22177
+ if (skill) {
22178
+ if (!seen.has(skill.name)) {
22179
+ seen.add(skill.name);
22180
+ parts.push(`# ${skill.name}
22181
+
22182
+ ${body(skill.files[0].text).trim()}`);
22183
+ }
22184
+ continue;
22185
+ }
22186
+ const sections = findSections(term);
22187
+ if (!sections.length) {
22188
+ missed.push(term);
22189
+ continue;
22190
+ }
22191
+ for (const section of sections) {
22192
+ const key = `${section.skill.name}#${section.heading}`;
22193
+ if (seen.has(key) || seen.has(section.skill.name)) continue;
22194
+ seen.add(key);
22195
+ parts.push(`From ${section.skill.name}:
22196
+
22197
+ ${section.text}`);
22198
+ }
22199
+ }
22200
+ let reply = parts.join("\n\n---\n\n");
22201
+ if (reply.length > MAX_REPLY) {
22202
+ reply = `${reply.slice(0, MAX_REPLY)}
22203
+
22204
+ [Cut at ${MAX_REPLY} characters. Ask for fewer topics or single sections.]`;
22205
+ }
22206
+ if (missed.length) {
22207
+ reply += `${reply ? "\n\n" : ""}Nothing matched ${missed.map((m) => `"${m}"`).join(", ")}. Call get_design_guide with no topic for the list of skills and sections.`;
22208
+ }
22209
+ return reply;
22210
+ }
22211
+ function listSkillResources() {
22212
+ return {
22213
+ resources: SKILLS.flatMap(
22214
+ (skill) => skill.files.map((f) => ({
22215
+ uri: f.uri,
22216
+ name: `${skill.name}/${f.path}`,
22217
+ description: f.path === "SKILL.md" ? skill.description : void 0,
22218
+ mimeType: "text/markdown"
22219
+ }))
22220
+ )
22221
+ };
22222
+ }
22223
+ function readSkillResource(uri) {
22224
+ for (const skill of SKILLS) {
22225
+ const file = skill.files.find((f) => f.uri === uri);
22226
+ if (file) return { contents: [{ uri: file.uri, mimeType: "text/markdown", text: file.text }] };
22227
+ }
22228
+ return null;
22229
+ }
22230
+
21939
22231
  // src/find-local-file.ts
21940
22232
  import fs from "fs";
21941
22233
  import path from "path";
@@ -22011,11 +22303,11 @@ function withRunSequence(stepArgs, sequenceId, tool) {
22011
22303
 
22012
22304
  // src/tab-registry.ts
22013
22305
  function indexTabs(reports, isOpen) {
22014
- const index = /* @__PURE__ */ new Map();
22306
+ const index2 = /* @__PURE__ */ new Map();
22015
22307
  for (const r of [...reports].sort((a, b) => a.at - b.at)) {
22016
- if (isOpen(r.socket)) index.set(r.projectId, r);
22308
+ if (isOpen(r.socket)) index2.set(r.projectId, r);
22017
22309
  }
22018
- return index;
22310
+ return index2;
22019
22311
  }
22020
22312
 
22021
22313
  // src/open-project.ts
@@ -23270,14 +23562,14 @@ function applyPublicAuth(clientId, params) {
23270
23562
  }
23271
23563
  async function parseErrorResponse(input) {
23272
23564
  const statusCode = input instanceof Response ? input.status : void 0;
23273
- const body = input instanceof Response ? await input.text() : input;
23565
+ const body2 = input instanceof Response ? await input.text() : input;
23274
23566
  try {
23275
- const result = OAuthErrorResponseSchema.parse(JSON.parse(body));
23567
+ const result = OAuthErrorResponseSchema.parse(JSON.parse(body2));
23276
23568
  const { error: error2, error_description, error_uri } = result;
23277
23569
  const errorClass = OAUTH_ERRORS[error2] || ServerError;
23278
23570
  return new errorClass(error_description || "", error_uri);
23279
23571
  } catch (error2) {
23280
- const errorMessage = `${statusCode ? `HTTP ${statusCode}: ` : ""}Invalid OAuth error response: ${error2}. Raw body: ${body}`;
23572
+ const errorMessage = `${statusCode ? `HTTP ${statusCode}: ` : ""}Invalid OAuth error response: ${error2}. Raw body: ${body2}`;
23281
23573
  return new ServerError(errorMessage);
23282
23574
  }
23283
23575
  }
@@ -24534,7 +24826,7 @@ var VERSION = (() => {
24534
24826
  }
24535
24827
  return "0.0.0";
24536
24828
  })();
24537
- var INSTRUCTIONS = `Edits a GoatEdit video project live in the user's browser tab at ai.goatedit.com. At the start of each new request from the user, call begin_request with the kind of edit, its style words and any reference videos, then get_style_profile for how this user edits; treat that style as defaults, the request wins. Call get_timeline before your first edit \u2014 clip ids change after every split, trim or delete, and a stale id is rejected. Each edit's reply carries timelineChanges (clips added, with their ids and placement; clips changed; ids removed), so take the next ids from there instead of reading the timeline again. get_timeline is lean by default; pass detail: "full" only when you need effect parameters, and trackId or from/to to read part of it. Several edits in a row go faster as one run_tools call. With more than one tab open, call list_connected_projects, then use_project to pick one. To work on a project that is not open, find it with list_projects and call open_project. To work on several projects at once (one helper agent per video), do not use use_project \u2014 it moves every caller \u2014 but pass projectId on each call, or once on run_tools for all its steps. To read or edit a sequence other than the open one, do not call open_sequence \u2014 it moves the user's view \u2014 but pass expectedSequenceId on each call, or once on run_tools; the edit lands in that sequence while the user keeps working wherever they are. render_preview_frame and preview_media accept saveToDisk: true, which writes the frame to a file and returns its path instead of the image. If a tool reports the editor is not connected, ask the user to open ai.goatedit.com and switch on Local in the AI status menu.`;
24829
+ var INSTRUCTIONS = `Edits a GoatEdit video project live in the user's browser tab at ai.goatedit.com. Direct the video, don't just fill the timeline: before designing, animating or cutting, call get_design_guide (no topic for the index, then "director", "visual", "motion", "editing" or "formats") and work in its order: brief, references, script, storyboard, rough draft and style frames approved, build, motion, sound, then its checks. At the start of each new request from the user, call begin_request with the kind of edit, its style words and any reference videos, then get_style_profile for how this user edits; treat that style as defaults, the request wins. Call get_timeline before your first edit \u2014 clip ids change after every split, trim or delete, and a stale id is rejected. Each edit's reply carries timelineChanges (clips added, with their ids and placement; clips changed; ids removed), so take the next ids from there instead of reading the timeline again. get_timeline is lean by default; pass detail: "full" only when you need effect parameters, and trackId or from/to to read part of it. Several edits in a row go faster as one run_tools call. With more than one tab open, call list_connected_projects, then use_project to pick one. To work on a project that is not open, find it with list_projects and call open_project. To work on several projects at once (one helper agent per video), do not use use_project \u2014 it moves every caller \u2014 but pass projectId on each call, or once on run_tools for all its steps. To read or edit a sequence other than the open one, do not call open_sequence \u2014 it moves the user's view \u2014 but pass expectedSequenceId on each call, or once on run_tools; the edit lands in that sequence while the user keeps working wherever they are. render_preview_frame and preview_media accept saveToDisk: true, which writes the frame to a file and returns its path instead of the image. If a tool reports the editor is not connected, ask the user to open ai.goatedit.com and switch on Local in the AI status menu.`;
24538
24830
  var MEDIA_TYPES = {
24539
24831
  ".mp4": { mime: "video/mp4", kind: "video" },
24540
24832
  ".m4v": { mime: "video/mp4", kind: "video" },
@@ -25096,7 +25388,7 @@ async function callLocal(method, args, projectId) {
25096
25388
  }
25097
25389
  var server = new Server(
25098
25390
  { name: "goatedit-mcp", version: VERSION },
25099
- { capabilities: { tools: {} }, instructions: INSTRUCTIONS }
25391
+ { capabilities: { tools: {}, resources: {} }, instructions: INSTRUCTIONS }
25100
25392
  );
25101
25393
  var LOCAL_TOOLS = [
25102
25394
  {
@@ -25168,6 +25460,7 @@ var LOCAL_TOOLS = [
25168
25460
  }
25169
25461
  ];
25170
25462
  var BROWSER_TOOLS = TOOLS.filter((t) => t.method !== null);
25463
+ var GUIDE_TOOL = TOOLS.find((t) => t.name === "get_design_guide");
25171
25464
  var BROWSER_TOOLS_BY_NAME = new Map(BROWSER_TOOLS.map((t) => [t.name, t]));
25172
25465
  for (const [alias, name] of Object.entries(TOOL_ALIASES)) {
25173
25466
  const tool = BROWSER_TOOLS_BY_NAME.get(name);
@@ -25179,6 +25472,7 @@ var SAVES_FRAMES = new Set(
25179
25472
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
25180
25473
  tools: [
25181
25474
  ...LOCAL_TOOLS.map(({ name, description, inputSchema }) => ({ name, description, inputSchema })),
25475
+ { name: GUIDE_TOOL.name, description: GUIDE_TOOL.description, inputSchema: GUIDE_TOOL.inputSchema },
25182
25476
  ...BROWSER_TOOLS.map(({ name, description, inputSchema }) => ({ name, description, inputSchema }))
25183
25477
  ]
25184
25478
  }));
@@ -25186,6 +25480,8 @@ async function callTool(name, args) {
25186
25480
  const { projectId: rawProjectId, ...forwarded } = args;
25187
25481
  const projectId = typeof rawProjectId === "string" && rawProjectId ? rawProjectId : void 0;
25188
25482
  switch (name) {
25483
+ case "get_design_guide":
25484
+ return getDesignGuide(forwarded.topic);
25189
25485
  case "list_connected_projects":
25190
25486
  case "list_local_assets":
25191
25487
  case "generate_voiceover":
@@ -25300,10 +25596,17 @@ async function runTools(args) {
25300
25596
  ...images.length ? { _images: images } : {}
25301
25597
  };
25302
25598
  }
25599
+ server.setRequestHandler(ListResourcesRequestSchema, async () => listSkillResources());
25600
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
25601
+ const found = readSkillResource(request.params.uri);
25602
+ if (!found) throw new McpError(ErrorCode.InvalidParams, `Unknown resource: ${request.params.uri}`);
25603
+ return found;
25604
+ });
25303
25605
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
25304
25606
  const { name, arguments: rawArgs } = request.params;
25305
25607
  const args = rawArgs ?? {};
25306
25608
  try {
25609
+ if (name === "get_design_guide") return { content: [{ type: "text", text: getDesignGuide(args.topic) }] };
25307
25610
  const result = name === "run_tools" ? await runTools(args) : await callTool(name, args);
25308
25611
  return { content: toContent(result) };
25309
25612
  } catch (error2) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "goatedit-mcp",
3
- "version": "1.1.13",
3
+ "version": "1.1.15",
4
4
  "description": "Local MCP server for the GoatEdit video editor: drive your open editor tab from Claude Code or any MCP client",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",