From merge-movies
Create code walkthrough movies using merge.mov — from git diffs, feature walkthroughs, architecture overviews, setup guides, or free-form narratives.
How this skill is triggered — by the user, by Claude, or both
Slash command
/merge-movies:create-movieThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Create code walkthrough movies using merge.mov — from git diffs, feature walkthroughs, architecture overviews, setup guides, or free-form narratives.
Create code walkthrough movies using merge.mov — from git diffs, feature walkthroughs, architecture overviews, setup guides, or free-form narratives.
$ARGUMENTS
This skill creates engaging code walkthrough videos by:
/merge-movies:create-movie HEAD~3..HEAD - From a commit range/merge-movies:create-movie uncommitted - From uncommitted changes/merge-movies:create-movie <branch> - From branch changes vs main/merge-movies:create-movie walkthrough <feature> - Feature walkthrough/merge-movies:create-movie architecture - Architecture overview/merge-movies:create-movie setup - Setup / getting started guide/merge-movies:create-movie - Interactive mode (free-form or guided)This skill uses the merge-movies MCP server. All tools are available automatically — use them directly by name. Authentication is handled automatically via OAuth — on first use, a browser window opens for you to log in, and tokens are managed by Claude Code.
Available tools:
| Tool | Description |
|---|---|
list_movies | List all movies |
get_movie | Get a movie by ID with all scenes |
create_movie | Create a new movie |
update_movie | Update an existing movie (full replacement) |
delete_movie | Delete a movie |
list_scenes | List all scenes in a movie |
get_scene | Get a single scene |
create_scene | Create a new scene in a movie |
update_scene | Full replacement of a scene |
patch_scene | Partial update of a scene |
delete_scene | Delete a scene |
reorder_scenes | Reorder scenes within a movie |
list_codeblocks | List code blocks in a scene |
get_codeblock | Get a single code block |
create_codeblock | Create a code block in a scene |
update_codeblock | Update a code block |
delete_codeblock | Delete a code block |
Parse $ARGUMENTS to detect the creation mode:
| Argument Pattern | Mode | Description |
|---|---|---|
HEAD~N..HEAD, abc123..def456 | Git diff (commit range) | Diff between two commits |
uncommitted | Git diff (working tree) | Uncommitted changes vs HEAD |
<branch-name> (matches a git branch) | Git diff (branch) | Branch changes vs main |
walkthrough <feature> | Feature walkthrough | Explain how a feature works |
architecture | Architecture overview | Explore and explain the system |
setup | Setup guide | Document how to set up the project |
| (no args or free text) | Free-form / interactive | Ask user what story to tell |
If the mode is ambiguous, ask the user to clarify.
# For commit range
git diff --name-status HEAD~3..HEAD # scope first
git diff HEAD~3..HEAD # full diff
# For uncommitted changes
git diff --name-status HEAD
git diff HEAD
# For branch comparison
git diff --name-status main..feature-branch
git diff main..feature-branch
ls, Glob for key patterns)package.json, main.ts, app.ts, config filesCreate a scene outline before building. Group material into logical scenes:
For git diff modes:
For walkthrough modes:
For architecture modes:
For setup modes:
Scene Duration Guidelines:
Effective narration:
Examples:
Bad: "Here we see the addition of a new function called handleSubmit."
Good: "The handleSubmit function validates user input before sending it to the API, preventing invalid data from reaching the server."
Always read the actual source file before creating code view scenes. The diff tells you which lines changed; the file gives you the content with proper surrounding context.
For non-diff modes, read files to get the exact content for the lines you want to show.
Use the MCP tools in order:
create_movie:create_movie({
movie: {
metadata: {
title: "Add User Authentication",
description: "JWT-based auth with route protection",
repository: "acme/web-app",
branch: "feature/auth",
commitRange: { from: "abc123", to: "def456" }
},
scenes: []
}
})
→ Returns { id, studioUrl }
For non-diff modes, omit branch and commitRange from metadata — just use title, description, and optionally repository.
Portrait/Mobile videos: To create a mobile-optimized video (1080x1920, 9:16), add orientation: "portrait" to the movie object. When creating portrait videos:
side-by-side code layout (use stacked or single instead)useVideoConfig() for dimensions)create_scene (see Scene Types below for view structures).Always include a title — a short label (2-5 words) for the scene that appears in the timeline and scene list:
create_scene({
movieId: "<movie-id>",
scene: {
title: "Email Validation",
narration: "We update the user validation logic to check email format.",
view: {
type: "code",
layout: "single",
codeBlocks: [{
filePath: "src/validators/user.ts",
lineRanges: [{ start: 15, end: 28 }],
changeType: "modify",
content: "// The actual code content..."
}]
}
}
})
Use a mix of scene types for engaging movies: code views for implementation, terminal views for CLI demos, and React views for most visual/explanatory scenes (bullet lists, diagrams, overviews, architecture flows). Reserve slide views only for simple title cards and closing slides — for anything with animation, progressive reveal, or visual complexity, use React views instead.
Use the studioUrl returned by create_movie and prepend the base URL. Use $MERGE_MOVIES_URL if set, otherwise default to https://merge.mov.
{MERGE_MOVIES_URL}{studioUrl}
Display code with syntax highlighting. Layouts:
single - One code block, full widthside-by-side - Compare two files horizontallystacked - Multiple blocks verticallyinline-diff - Unified diff view{
"narration": "We update the user validation logic to check email format.",
"view": {
"type": "code",
"layout": "single",
"codeBlocks": [{
"filePath": "src/validators/user.ts",
"lineRanges": [{ "start": 15, "end": 28 }],
"changeType": "modify",
"content": "// The actual code content..."
}]
}
}
Use slide views only for simple title cards and closing/summary slides. For bullet lists, diagrams, architecture flows, or anything that benefits from animation, use React views instead.
Create simple title cards with positioned elements on a dark canvas.
Canvas details:
#0d1117 (customizable via backgroundColor)Text elements (type: "text"):
| Style | Default size | Weight | Color | Notes |
|---|---|---|---|---|
title | 64px | Bold (700) | #ffffff | Main headings |
body | 32px | Normal (400) | #e6edf3 | Paragraphs |
bullet | 28px | Normal (400) | #e6edf3 | Blue • prefix, split on \n |
Use fontSize to override the default size. Use size.width (percentage) to constrain the text box width.
Shape elements:
| Type | Default size | Default stroke | Default fill |
|---|---|---|---|
rect | 30% x 20% | #58a6ff | transparent |
circle | 15% diameter | #58a6ff | transparent |
line | start to endPosition | #58a6ff | n/a |
Shapes support text, textColor, and textFontSize for centered labels. Rects also support borderRadius (0-50).
Image elements (type: "image"):
src with an uploaded image URL (no direct file upload via API)size.width and size.heightExample: Title card
{
"narration": "Let's walk through the authentication changes in this PR.",
"view": {
"type": "slide",
"elements": [
{
"id": "title",
"type": "text",
"style": "title",
"content": "Authentication Overhaul",
"position": { "x": 10, "y": 30 }
},
{
"id": "subtitle",
"type": "text",
"style": "body",
"content": "Adding JWT tokens, refresh flow, and route protection",
"position": { "x": 10, "y": 50 }
}
]
}
}
Aesthetic tips:
#0d1117, white titles, #e6edf3 body text, #58a6ff accentsCustom animated scenes using React/JSX with Remotion APIs.
Before writing React view code from scratch, search the community template library for a matching starting point:
list_react_templates with a relevant query or tags (e.g. query: "diagram", tags: ["architecture"])get_react_template to get the full codeThis saves time and produces higher-quality animations. Only write from scratch if no template is a close match.
The code field is the body of a React function component that must return JSX. It receives a scope object containing Remotion APIs — destructure what you need at the top.
Available via scope:
Core: React, useCurrentFrame(), useVideoConfig(), spring({ frame, fps, config? }), interpolate(value, inputRange, outputRange, options?), Easing (easing curves), interpolateColors(frame, inputRange, colorRange), random(seed) (deterministic), Sequence, Series, AbsoluteFill, Loop, Freeze, Img, Video, AuthenticatedVideo, useAuthenticatedImage(url)
Shapes (@remotion/shapes): Circle, Rect, Star, Triangle, Ellipse, Pie, Heart, Polygon — SVG shapes with fill, stroke, and stroke-draw animation support
Paths (@remotion/paths): evolvePath(progress, path) (animate path drawing 0->1), getPointAtLength(path, length), getLength(path), interpolatePath(progress, path1, path2) (morph between paths), parsePath(d), resetPath, scalePath, translatePath, getBoundingBox, getSubpaths
Noise (@remotion/noise): noise2D(seed, x, y), noise3D(seed, x, y, z), noise4D(seed, x, y, z, w) — Perlin noise (-1 to 1) for organic wave/particle effects
Animation Utils (@remotion/animation-utils): makeTransform([...transforms]) (compose CSS transforms safely), interpolateStyles(frame, inputRange, styleRanges) (interpolate entire style objects)
Transitions (@remotion/transitions): TransitionSeries (like Series with transitions between children), springTiming({ config? }), linearTiming({ durationInFrames }), transitionFade(), transitionSlide({ direction? }), transitionWipe({ direction? }), transitionFlip({ direction? }), transitionClockWipe()
Layout Utils (@remotion/layout-utils): measureText({ text, fontFamily, fontSize }), fitText({ text, withinWidth, fontFamily })
{
"narration": "Welcome to the code walkthrough.",
"view": {
"type": "react",
"code": "const { useCurrentFrame, useVideoConfig, spring, interpolate, AbsoluteFill } = scope;\n\nconst frame = useCurrentFrame();\nconst { fps } = useVideoConfig();\n\nconst opacity = interpolate(frame, [0, 30], [0, 1], { extrapolateRight: 'clamp' });\nconst scale = spring({ frame, fps, config: { damping: 200 } });\n\nreturn (\n <AbsoluteFill style={{ justifyContent: 'center', alignItems: 'center' }}>\n <div style={{ opacity, transform: `scale(${scale})`, fontSize: 80, color: '#58a6ff' }}>\n Hello World\n </div>\n </AbsoluteFill>\n);",
"backgroundColor": "#0d1117"
}
}
React views are the preferred scene type for all visual/explanatory content — bullet lists, diagrams, architecture flows, feature overviews, and anything that isn't pure code or terminal output. They produce more engaging, animated scenes than static slides.
Example: Animated bullet list
{
"narration": "Here's what we'll cover in this walkthrough.",
"view": {
"type": "react",
"code": "const { useCurrentFrame, interpolate, AbsoluteFill } = scope;\nconst frame = useCurrentFrame();\n\nconst items = [\n 'New User model with password hashing',\n 'JWT-based auth controller',\n 'Route protection middleware',\n 'Updated API routes'\n];\n\nconst titleOpacity = interpolate(frame, [0, 20], [0, 1], { extrapolateRight: 'clamp' });\n\nreturn (\n <AbsoluteFill style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', padding: '0 120px', backgroundColor: '#0d1117' }}>\n <div style={{ opacity: titleOpacity, fontSize: 64, fontWeight: 700, color: '#ffffff', marginBottom: 48 }}>\n What Changed\n </div>\n {items.map((item, i) => {\n const delay = 20 + i * 15;\n const opacity = interpolate(frame, [delay, delay + 15], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' });\n const translateX = interpolate(frame, [delay, delay + 15], [30, 0], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' });\n return (\n <div key={i} style={{ opacity, transform: `translateX(${translateX}px)`, fontSize: 36, color: '#e6edf3', marginBottom: 32, display: 'flex', alignItems: 'center', gap: 16 }}>\n <span style={{ color: '#58a6ff', fontSize: 28 }}>•</span>\n {item}\n </div>\n );\n })}\n </AbsoluteFill>\n);",
"backgroundColor": "#0d1117"
}
}
Example: Architecture diagram with animated flow
{
"narration": "The request flows from client through middleware to the API handler.",
"view": {
"type": "react",
"code": "const { useCurrentFrame, interpolate, spring, useVideoConfig, AbsoluteFill } = scope;\nconst frame = useCurrentFrame();\nconst { fps, height } = useVideoConfig();\n\nconst boxes = [\n { label: 'Client', color: '#58a6ff', x: 160 },\n { label: 'Auth Middleware', color: '#d2a8ff', x: 600 },\n { label: 'API Handler', color: '#3fb950', x: 1100 }\n];\n\nreturn (\n <AbsoluteFill style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', backgroundColor: '#0d1117' }}>\n <div style={{ fontSize: 48, fontWeight: 700, color: '#fff', marginBottom: 80 }}>\n Request Flow\n </div>\n <div style={{ display: 'flex', alignItems: 'center', gap: 48 }}>\n {boxes.map((box, i) => {\n const delay = i * 20;\n const scale = spring({ frame: frame - delay, fps, config: { damping: 200 } });\n const opacity = interpolate(frame, [delay, delay + 10], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' });\n const arrowOpacity = i < boxes.length - 1 ? interpolate(frame, [delay + 10, delay + 20], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }) : 0;\n return (\n <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 48 }}>\n <div style={{ opacity, transform: `scale(${scale})`, border: `2px solid ${box.color}`, borderRadius: 12, padding: '24px 36px', color: box.color, fontSize: 24, fontWeight: 600 }}>\n {box.label}\n </div>\n {i < boxes.length - 1 && <div style={{ opacity: arrowOpacity, color: '#58a6ff', fontSize: 32 }}>→</div>}\n </div>\n );\n })}\n </div>\n </AbsoluteFill>\n);",
"backgroundColor": "#0d1117"
}
}
React view tips:
useCurrentFrame), not time-based or statefulinterpolate(frame - 20, [0, 30], [0, 1], { extrapolateLeft: 'clamp' })spring() returns 0->1 by default — use for scale, translateY, opacitydisplay: 'flex', flexDirection: 'column', justifyContent: 'center' on the root AbsoluteFill to vertically center content, or justifyContent: 'space-between' with generous padding to distribute elements across the full height. Never leave the bottom half of the screen empty. Horizontally: spread elements across the full width — cards in a row should span the available space with even gaps, not huddle on one side. Use the full 1920px (landscape) or 1080px (portrait) width.code field is a string, so \u2713 renders as the literal text \u2713, not a checkmark. Always paste the actual character (e.g. ✓, →, •, ℹ️) directly into the code stringDisplay terminal sessions with animated command input and output.
{
"narration": "First, install the dependencies and run the tests.",
"view": {
"type": "terminal",
"title": "~/project",
"theme": "generic",
"entries": [
{
"id": "install",
"command": "npm install",
"output": "added 127 packages in 4s"
},
{
"id": "test",
"command": "npm test",
"output": "Tests: 12 passed, 12 total\nTime: 2.4s",
"exitCode": 0
}
],
"inputAnimation": "type",
"outputAnimation": "fade"
}
}
Terminal options:
theme: "generic", "claude-code", or "codex" — styles the promptentryType (per entry): Override the theme for individual entriesinputAnimation: "type" (typewriter), "fade", or "cut" (instant)outputAnimation: "type", "fade", or "cut"prompt: Custom prompt text override per entrymodify - Existing file being changed. This is the most common type — use it whenever showing changes to a file that already exists, even if the scene shows added lines within that file.add - Brand-new files or entirely new functions/classes being introduceddelete - File or code being removedcontext - Unchanged code shown purely for reference, no changes being discussedImportant: If a commit modifies an existing file (adds lines, changes lines, or removes lines within it), use modify — not add. Most scenes in a typical movie should use modify.
For non-diff modes: Use context for walkthrough/architecture scenes where you're showing existing code without changes. Use add only when showing newly created files.
When creating code view scenes, provide proper context so viewers can understand where changes occur in the source file. Without context, viewers see isolated changed lines and can't understand the surrounding code structure.
... gap separator.content must exactly equal the total number of lines spanned by all lineRanges.lineRanges with actual source line numbers — The renderer uses these to display correct line numbers and visual gap separators (...) between non-contiguous sections.lines array in highlights must use real source file line numbers.Bad (no context, just the changed lines):
{
"narration": "We add email validation.",
"view": {
"type": "code",
"layout": "single",
"codeBlocks": [{
"filePath": "src/validators/user.ts",
"lineRanges": [{ "start": 47, "end": 50 }],
"changeType": "modify",
"content": " const email = input.email.trim();\n if (!isValidEmail(email)) {\n throw new ValidationError('Invalid email format');\n }"
}]
}
}
Good (enclosing function, 3+ context lines before/after change):
{
"narration": "We add email validation inside validateUser.",
"view": {
"type": "code",
"layout": "single",
"codeBlocks": [{
"filePath": "src/validators/user.ts",
"lineRanges": [
{ "start": 38, "end": 40 },
{ "start": 44, "end": 55 }
],
"changeType": "modify",
"content": "export function validateUser(input: UserInput): ValidationResult {\n const errors: string[] = [];\n\n const name = input.name?.trim();\n if (!name || name.length < 2) {\n errors.push('Name must be at least 2 characters');\n }\n const email = input.email.trim();\n if (!isValidEmail(email)) {\n throw new ValidationError('Invalid email format');\n }\n const age = Number(input.age);\n if (isNaN(age) || age < 0 || age > 150) {\n errors.push('Invalid age');\n }\n return { valid: errors.length === 0, errors };"
}],
"animations": {
"highlights": [
{ "id": "h1", "lines": [50, 51, 52, 53], "color": "rgba(255, 213, 79, 0.3)" }
]
}
}
}
Animations bring code to life by scrolling through long files.
For files too long to fit on screen, use scrolling to navigate through the code during playback.
Note: A 2-second pause at the start of scrolling blocks is automatically added by the backend — you don't need to include it.
{
"narration": "Let's walk through this implementation step by step.",
"view": {
"type": "code",
"layout": "single",
"codeBlocks": [{
"filePath": "src/services/auth.ts",
"lineRanges": [{ "start": 1, "end": 60 }],
"changeType": "add",
"content": "// Long file content..."
}],
"animations": {
"scroll": {
"id": "scroll1",
"linesPerSecond": 3,
"pauses": [
{ "lineNumber": 15, "durationMs": 2000 },
{ "lineNumber": 42, "durationMs": 3000 }
]
}
}
}
}
Scroll parameters:
linesPerSecond - Scroll speed (0.1 to 50, recommend 2-5 for readability). Enables scroll animation.pauses - Stop at specific lines to let viewers read important codetargetBlockId - Which code block to scroll (optional, defaults to first block)Tips:
Highlight important lines in code view scenes with a colored background.
Place highlights inside view.animations.highlights:
{
"view": {
"type": "code",
"codeBlocks": [{ "..." : "..." }],
"animations": {
"highlights": [
{
"id": "h1",
"lines": [5, 6, 7, 8],
"color": "rgba(255, 213, 79, 0.3)"
}
]
}
}
}
Each highlight object:
| Field | Required | Description |
|---|---|---|
id | Yes | Highlight ID |
lines | Yes | Source line numbers to highlight (must be within code block's lineRanges) |
targetBlockId | No | Which code block to highlight (defaults to first). Must match one of the code block IDs in the same request — see warning below |
color | No | RGBA color string (default: yellow rgba(255, 213, 79, 0.3)) |
startTimeMs | No | Start time in ms from scene start (omit for whole scene) |
endTimeMs | No | End time in ms from scene start (omit for whole scene) |
glow | No | Enable glow effect (pulsing box-shadow) |
size | No | Enable size effect (scale up highlighted lines) |
focus | No | Enable focus effect (zoom in and blur non-highlighted lines) |
Common Pitfall: Non-contiguous lineRanges
When using multiple
lineRanges(e.g.,[{start: 3, end: 13}, {start: 54, end: 58}]), highlightlinesmust use source file line numbers (e.g.,[54, 55, 56]), NOT content string positions. The API validates this and will reject highlights referencing lines outside the declared ranges.
Common Pitfall: targetBlockId must match a code block ID in the same request
When using
targetBlockIdon highlights (or scroll animations), the value must exactly match one of theidfields you provide on thecodeBlocksarray in the same API call. The server remaps all code block IDs to UUIDs internally — if atargetBlockIddoesn't match any code block ID in the request, the highlight will be silently removed and a warning returned.For example, if your code blocks use
"id": "left-block"and"id": "right-block", then highlights must use"targetBlockId": "left-block"or"targetBlockId": "right-block". Using any other value (like"cb-left") will cause the highlight to be dropped.
Preset colors:
rgba(255, 213, 79, 0.3) (default)rgba(96, 165, 250, 0.3)rgba(192, 132, 252, 0.3)rgba(251, 146, 60, 0.3)rgba(34, 211, 238, 0.3)rgba(244, 114, 182, 0.3)Timed highlights (sequential):
{
"view": {
"type": "code",
"codeBlocks": [{
"filePath": "src/services/parser.ts",
"lineRanges": [{ "start": 1, "end": 30 }],
"changeType": "add",
"content": "// code..."
}],
"animations": {
"highlights": [
{ "id": "h1", "lines": [3, 4, 5], "color": "rgba(96, 165, 250, 0.3)", "startTimeMs": 0, "endTimeMs": 3000 },
{ "id": "h2", "lines": [12, 13, 14], "color": "rgba(244, 114, 182, 0.3)", "startTimeMs": 3000, "endTimeMs": 6000 }
]
}
}
}
Highlights combined with scroll:
{
"view": {
"type": "code",
"codeBlocks": [{
"filePath": "src/services/auth.ts",
"lineRanges": [{ "start": 1, "end": 60 }],
"changeType": "add",
"content": "// long file..."
}],
"animations": {
"scroll": {
"id": "scroll1",
"linesPerSecond": 3,
"pauses": [
{ "lineNumber": 15, "durationMs": 3000 },
{ "lineNumber": 40, "durationMs": 3000 }
]
},
"highlights": [
{ "id": "h1", "lines": [15, 16, 17], "color": "rgba(34, 211, 238, 0.3)" },
{ "id": "h2", "lines": [40, 41, 42, 43], "color": "rgba(192, 132, 252, 0.3)" }
]
}
}
}
Control how scenes transition in and out using startTransition and endTransition fields on the scene:
{
"narration": "Now let's look at the implementation.",
"startTransition": { "type": "fade", "duration": 0.5 },
"endTransition": { "type": "fade", "duration": 0.3 },
"view": { "..." : "..." }
}
| Type | When to use |
|---|---|
cut | Between closely related scenes (same topic, quick progression) |
fade | Between distinct topics or for dramatic effect |
slide | For sequential steps or progression |
zoom | For drilling into details |
npx claudepluginhub marked-gold/merge-movies-claude-code --plugin merge-moviesCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.