Drop a screenshot. Ship a clip.
In: one app screenshot. Out: layers, a push-in, a caption, a still and a looping video. Six seconds. This sample also tilts a 2.5D device slab — one layer trick among others. An agent rendered this.
1440×810 MP4 @ 30 fps, looping. The still and loop show a 2.5D spaceBlack slab. Full-canvas still is 1920×1080 at 3 seconds — mid push-in, caption “Meet the new dashboard”.

What went in
- A dashboard screenshot, copied into
Resources/ - Palette ground, a title, a timed push-in
- Optional 2.5D slab:
spaceBlackframe,tiltY: 10 - Caption: “Meet the new dashboard”
Headless CLI/MCP (since 0.2.2) bakes the slab at stage time. Tilt keyframes re-bake per frame only in the apps; headless holds the resource's stored tilt and promo validate warns.
promo_init + promo_upsert_layer scaffold the same layers and caption. Motion past the first placement ispromo_upsert_keyframe, or ordinary JSON.
Commands that produced these files
promo validate /ABS/PATH/Promo/ProductCard.promo --json
# {"ok":true,"warnings":[]}
promo still /ABS/PATH/Promo/ProductCard.promo \
--out /ABS/PATH/Promo/ProductCard.promo/Exports/still-3s.png \
--time 3 --json
# {"height":1080,"time":3.0,"width":1920,"wrote":".../still-3s.png"}
promo video /ABS/PATH/Promo/ProductCard.promo \
--out /ABS/PATH/Promo/ProductCard.promo/Exports/export-1440x810.mp4 \
--fps 30 --size 1440x810MCP is the same pixels — the server shells to promo:
{
"jsonrpc": "2.0",
"id": 12,
"method": "tools/call",
"params": {
"name": "promo_render_still",
"arguments": {
"project": "/ABS/PATH/Promo/ProductCard.promo",
"time": 3.0
}
}
}
{
"jsonrpc": "2.0",
"id": 13,
"method": "tools/call",
"params": {
"name": "promo_render_video",
"arguments": {
"project": "/ABS/PATH/Promo/ProductCard.promo",
"fps": 30,
"size": "1440x810"
}
}
}Inspect: Product Card, 1920×1080, 6.00 s, 3 of 3 layers renderable. Layer ids land in promo inspect /ABS/PATH/Promo/ProductCard.promo --json(flag after the path).
Next: look at a clip before you compose. Footage-first recipe → · Headless MCP →