Agent kit
A prompt to paste, a skill your agent loads by itself and FFmpeg recipes to finish ads.
Agent prompt
Paste it at the start of a Claude Code, Codex or Cursor session, after you connect the MCP server.
You make e-commerce ads with Staglight through its MCP tools (server "staglight"). Docs: https://mobyvid.com/en/docs
Money rules
- Before anything paid, tell me the price (estimate_cost is free) and ask me once. Never spend more than I approved.
- Send max_credits and a new idempotency_key (a UUID) with every paid call.
- Paid tools answer at once with a job_id or run_id. Poll get_job, get_ad or studio_get_run with wait_seconds=50 until it finishes.
- Just a product link ("make me an ad for https://..."): studio_helper(studio=link_ad, name=link_scan, payload.url, always with an idempotency_key that a retry reuses, or the retry reads and charges again) reads that page (the scan's small flat price) and returns the photos, name and a line; then studio_estimate and studio_run on link_ad with the best photo.
- After a timeout or an error, never repeat a paid call blindly: call list_jobs with the same idempotency_key first.
- If credits run out, give me the link from buy_credits_link and stop.
How to work
- When I attach a product photo or clip: upload_file, then create_product with its name and photos. Or use list_products, or upload_from_url with a direct file link.
- A finished video ad: estimate_cost for create_ad (tier "draft" unless I ask for better), tell me the price, and after my yes call create_ad with auto=true and max_credits = the price I approved. If my request is vague, use create_ad_from_brief with auto=true and the budget I approved.
- Poll get_ad until it is succeeded and give me the file. If it stops (auto_stopped), tell me why and what it costs to continue.
- Product images (store carousel, static ads, UGC photos): the Ad Studio with studio_list, studio_estimate, studio_run, then studio_get_run.
- Download finished files with curl -L -o into ./ads/ and finish them with ffmpeg: 9:16 at 1080x1920, captions, -14 LUFS.
- Keep the AI-generated label. Never present a creator-style video as a real customer review.
Ask in your own words
With the MCP server connected, attach a product photo and say what you want. The agent uploads it, saves the product, tells you the price, asks you once and hands back the finished file. For example:
- "Make me a 15 second UGC ad for this serum, for Reels. Keep it under $3."
- "Here are 3 photos of my ceramic mug. Save it as a product and make a vertical product video in Spanish."
- "Turn this photo into a store carousel: white background, a lifestyle shot and a close-up of the label."
- "Something for TikTok that sells this water bottle to gym people. Tell me the price first."
Your OK sets a credit cap for the whole ad: the run never spends more, and stops to ask if the next step would go over it.
Skill for Claude Code, Codex and Cursor
The skill tells your agent when to use Staglight, the money rules and how to finish ads. Agents load it on their own when the task fits.
- SKILL.md to read it first.
- staglight-ads.zip with the skill, the FFmpeg recipes and a finishing script.
- It needs the MCP server connected, plus curl and ffmpeg on the computer.
Claude Code
mkdir -p ~/.claude/skills && curl -sL -o /tmp/staglight-ads.zip https://mobyvid.com/agent/staglight-ads.zip \
&& unzip -o /tmp/staglight-ads.zip -d ~/.claude/skills/
Codex and Cursor
mkdir -p ~/.agents/skills && curl -sL -o /tmp/staglight-ads.zip https://mobyvid.com/agent/staglight-ads.zip \
&& unzip -o /tmp/staglight-ads.zip -d ~/.agents/skills/
Claude Code picks up the skill in the session you have open; other agents may need a new session to see it.
Claude Code plugin files
For teams that run their own plugin marketplace: the plugin folder has .claude-plugin/plugin.json, .mcp.json and the skill. On install it asks for the key and keeps it in the system's secure storage.
curl -sL -o staglight-plugin.zip https://mobyvid.com/agent/staglight-plugin.zip && unzip -o staglight-plugin.zip
claude plugin validate ./staglight
For one person, the claude mcp add command and the skill above do the same job.
Finish ads with FFmpeg
Download the takes, then finish them on your computer. Output: H.264 yuv420p, AAC at 48 kHz, +faststart so the video plays before it fully loads. Captions need FFmpeg built with libass.
finish_ad.sh fits a take to the aspect, sets loudness to -14 LUFS in two passes and adds faststart:
curl -sLO https://mobyvid.com/agent/staglight-ads/scripts/finish_ad.sh
bash finish_ad.sh take.mp4 final.mp4 9:16
Probe a file
Duration, size, frame rate and sample rate as JSON.
ffprobe -v error -show_entries format=duration:stream=codec_type,width,height,r_frame_rate,sample_rate -of json in.mp4
Fit into 9:16 with bars
Keeps the whole frame and pads to 1080x1920.
ffmpeg -i in.mp4 -vf "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:color=black,setsar=1,fps=30,format=yuv420p" \
-c:v libx264 -preset medium -crf 18 -c:a aac -b:a 192k -ar 48000 -ac 2 -movflags +faststart out_916.mp4
Fill 9:16 with a center crop
Fills the frame and crops the sides.
ffmpeg -i in.mp4 -vf "scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,setsar=1,fps=30,format=yuv420p" \
-c:v libx264 -crf 18 -c:a aac -b:a 192k -ar 48000 -movflags +faststart out_916.mp4
9:16 with a blurred background
For product shots made in 16:9 or 1:1. The 0:a? map skips audio when there is none.
ffmpeg -i in.mp4 -filter_complex "[0:v]split=2[bg][fg];[bg]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,gblur=sigma=40[bg];[fg]scale=1080:1920:force_original_aspect_ratio=decrease[fg];[bg][fg]overlay=(W-w)/2:(H-h)/2,setsar=1,fps=30,format=yuv420p[v]" \
-map "[v]" -map 0:a? -c:v libx264 -crf 18 -c:a aac -b:a 192k -movflags +faststart out_916.mp4
Join clips of different sizes
One re-encode. The third clip has no sound, so it gets a silent track as long as the clip (5.04 s here, from ffprobe).
ffmpeg -i a.mp4 -i b.mp4 -i c.mp4 -filter_complex "
[0:v]scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2,setsar=1,fps=30,format=yuv420p[v0];
[1:v]scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2,setsar=1,fps=30,format=yuv420p[v1];
[2:v]scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2,setsar=1,fps=30,format=yuv420p[v2];
[0:a]aresample=48000,aformat=channel_layouts=stereo[a0];
[1:a]aresample=48000,aformat=channel_layouts=stereo[a1];
anullsrc=r=48000:cl=stereo,atrim=0:5.04[a2];
[v0][a0][v1][a1][v2][a2]concat=n=3:v=1:a=1[v][a]" \
-map "[v]" -map "[a]" -c:v libx264 -crf 18 -preset medium -c:a aac -b:a 192k -movflags +faststart joined.mp4
Burn styled captions (ASS)
Set PlayResX 1080 and PlayResY 1920 in the .ass header so margins match 9:16. Use fonts with ñ, á, ¿ and ¡.
ffmpeg -i in.mp4 -vf "ass=captions.ass:fontsdir=./fonts" -c:v libx264 -crf 18 -c:a copy -movflags +faststart captioned.mp4
Burn SRT captions with a style
MarginV lifts the captions above the platform buttons.
ffmpeg -i in.mp4 -vf "subtitles=captions.srt:fontsdir=./fonts:force_style='FontName=Archivo,FontSize=14,PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,BorderStyle=1,Outline=3,Alignment=2,MarginV=160'" \
-c:v libx264 -crf 18 -c:a copy -movflags +faststart captioned.mp4
Loudness to -14 LUFS (two passes)
Pass 1 measures. Copy the measured values from loud.txt into pass 2.
ffmpeg -hide_banner -i in.mp4 -af loudnorm=I=-14:TP=-1.0:LRA=11:print_format=json -f null - 2> loud.txt
ffmpeg -i in.mp4 -af "loudnorm=I=-14:TP=-1.0:LRA=11:measured_I=-20.3:measured_TP=-3.1:measured_LRA=6.2:measured_thresh=-30.6:offset=0.4:linear=true" \
-ar 48000 -c:v copy -c:a aac -b:a 192k out_loud.mp4
Voiceover over ducked music
The music drops while the voice speaks, then the mix is set to -14 LUFS.
ffmpeg -i video.mp4 -i vo.wav -stream_loop -1 -i music.mp3 -filter_complex "
[1:a]aresample=48000,aformat=channel_layouts=stereo,asplit=2[vo][vokey];
[2:a]aresample=48000,aformat=channel_layouts=stereo,volume=0.35[mus];
[mus][vokey]sidechaincompress=threshold=0.03:ratio=8:attack=20:release=400[ducked];
[vo][ducked]amix=inputs=2:duration=longest:dropout_transition=0:normalize=0,loudnorm=I=-14:TP=-1.0:LRA=11[a]" \
-map 0:v -map "[a]" -c:v copy -c:a aac -b:a 192k -ar 48000 -shortest -movflags +faststart final.mp4
Logo
Bottom right, above the area where Reels and TikTok put their buttons. Adjust the margin to your placement.
ffmpeg -i in.mp4 -i logo.png -filter_complex "[1:v]scale=220:-1,format=rgba,colorchannelmixer=aa=0.85[lg];[0:v][lg]overlay=W-w-40:H-h-260" \
-c:v libx264 -crf 18 -c:a copy -movflags +faststart branded.mp4
Trim
The first command is fast and cuts on keyframes. The second is frame-accurate.
ffmpeg -ss 2.0 -i in.mp4 -t 10 -c copy cut_fast.mp4
ffmpeg -ss 2.0 -i in.mp4 -t 10 -c:v libx264 -crf 18 -c:a aac cut_exact.mp4
Frames
First frame, last frame (to continue a clip from it) and a contact sheet to review a take.
ffmpeg -i in.mp4 -frames:v 1 first.png
ffmpeg -sseof -1 -i in.mp4 -update 1 -q:v 1 last.jpg
ffmpeg -i in.mp4 -vf "fps=1/2,scale=270:-2,tile=4x3" -frames:v 1 contact.jpg
Hold the last frame
Two extra seconds for an end card or a CTA.
ffmpeg -i in.mp4 -vf "tpad=stop_mode=clone:stop_duration=2" -af "apad=pad_dur=2" -c:v libx264 -crf 18 -c:a aac out.mp4
Other aspects
4:5 for Feed, 1:1 and 16:9 from the same take.
ffmpeg -i in.mp4 -vf "scale=1080:1350:force_original_aspect_ratio=increase,crop=1080:1350,setsar=1" -c:v libx264 -crf 18 -c:a copy out_45.mp4
ffmpeg -i in.mp4 -vf "scale=1080:1080:force_original_aspect_ratio=increase,crop=1080:1080,setsar=1" -c:v libx264 -crf 18 -c:a copy out_11.mp4
ffmpeg -i in.mp4 -vf "scale=1920:1080:force_original_aspect_ratio=increase,crop=1920:1080,setsar=1" -c:v libx264 -crf 18 -c:a copy out_169.mp4
Remove technical metadata
Removes GPS, device and encoder tags only. Keep the AI-generated label on the ad.
ffmpeg -i in.mp4 -map 0 -map_metadata -1 -map_chapters -1 -c copy -fflags +bitexact out_clean.mp4