CLI Reference
Complete command reference for the Montaj CLI — every operation the UI performs is available from the terminal.
CLI Reference
Every operation is available from the terminal. montaj is the full command, mtj is the short alias.
Version
montaj --version # or: montaj -v
# montaj 3.2.5Global Output Flags
Available on all commands:
--json # output result as JSON (for scripting and agent use)
--out <path> # specify output path (default: workspace/)
--quiet # suppress progress output, result only on stdoutTier 1 — Workflow Commands
The primary interface for most users.
montaj run ./clips --prompt "tight cuts, remove filler, 9:16"
# Runs default workflow against all clips in the directory
montaj run ./clips --workflow tight-reel --prompt "..."
# Runs a named workflow
montaj run --canvas --workflow animations --prompt "60s animated explainer, dark theme"
# Animation project — no source footage required
montaj serve
# Start local HTTP server + open UI at http://localhost:3000
montaj serve --network
# Bind to all network interfaces — accessible to other devices on the local network.
# WARNING: only use on trusted networks.
montaj render
# Render project.json [final] → final.mp4
montaj render --project ./workspace/project.json --out ./output/final.mp4
# Explicit paths
montaj render --clean
# Delete intermediate files after compositingmontaj run works headlessly — no UI, no montaj serve required.
Render Pipeline
montaj render runs three stages:
- Normalize + base video — normalize all sources to the project's working color space (
settings.colorSpace), then trim and prepare source clips - Overlay segments — each JSX overlay is bundled with esbuild, rendered frame-by-frame in headless Chromium, and encoded to lossless FFV1/MKV
- Compose — segment-based pipeline encodes each timeline segment independently, concats via
-c:v copy, then mixes audio tracks. Codec follows the project's color space — H.264 for SDR projects, HEVC 10-bit for HDR projects.
Tier 2 — Workflow Management
montaj workflow list
# List all available workflows (native + custom)
montaj workflow new <name>
# Scaffold a new workflow file at workflows/<name>.json
montaj workflow edit <name>
# Open workflow in the node graph UI
montaj workflow run <name> ./clips --prompt "..."
# Run a specific workflow (alias for: montaj run --workflow <name>)Tier 3 — Steps
montaj step is the interface for running any step directly. Steps are discovered automatically across three scopes: built-in, user-global (~/.montaj/steps/), and project-local (./steps/).
montaj step --help # list all available steps
montaj step <name> --help # show params for a specific step
montaj step <name> --input <file> # run a step
montaj create-step <name> # scaffold steps/<name>.py + .json
montaj validate step <filename> # validate a step schema
montaj validate project <filename> # validate a project.json
montaj validate workflow <filename> # validate a workflow .jsonStep Chaining
Steps chain via stdout — the output path of one step becomes the --input of the next:
FILE=$(montaj step rm_fillers --input clip.mp4 --model base.en)
FILE=$(montaj step waveform_trim --input "$FILE")
FILE=$(montaj step resize --input "$FILE" --ratio 9:16)Tier 4 — Project Commands
montaj fetch --url "https://www.tiktok.com/@handle/video/123"
# Download a single video via yt-dlp
montaj fetch --url "https://www.tiktok.com/@handle" --limit 15 --out ./clips/
# Download up to N videos from a profile or playlist
montaj init --prompt "tight cuts, remove filler"
# Create empty project.json in current directory
montaj init --prompt "..." --color-space hdr_hlg
# Force the project's working color space. Default is `auto`, which picks
# the MODAL (most common) color space across clips — outliers are
# converted on the fly. 27 HLG + 1 SDR → hdr_hlg (the SDR clip is stretched).
# Tiebreaks: PQ wins HDR-only ties; SDR wins SDR-vs-HDR ties.
# Choices: auto | sdr_bt709 | hdr_hlg | hdr_pq. Peer of --resolution.
montaj init --prompt "..." --resolution 1080x1920
# Force the project's output resolution. Default is `auto`, which preserves
# the MODAL source resolution. Mixed-aspect inputs (portrait + landscape)
# are scale-fit and center-padded to the chosen canvas at compose time.
# Format: WxH (e.g. 1080x1920, 3840x2160). Peer of --color-space.
montaj init --prompt "..." --project-path my-project
# Pin the project directory's path under the workspace root. Single segment
# for flat layouts (`my-project`), multi-segment slash-separated for nested
# layouts (`teamA/my-project`). Intermediate directories are created on
# demand. When omitted, the directory name is generated as `<date>-<slug>`
# from --name (or `<date>-<HHMMSS>` if --name is also absent). Useful for
# sidecar deploys where the caller already has its own project ID and wants
# to skip the date prefix. Segments must match `[A-Za-z0-9_-]+`; collisions
# on an explicit path are hard errors, not auto-suffixed.
montaj init --workflow carousel --carousel-aspect portrait --prompt "..."
# Create an image-carousel project. --carousel-aspect picks the slide ratio:
# `square` (1:1, 1080x1080), `portrait` (4:5, 1080x1350), or `vertical`
# (9:16, 1080x1920). Locked at creation. Add reference images with --asset
# (repeatable); they're copied into the workspace and surface in
# project.assets[]. The agent reads project.json + project.assets and the
# `montaj/carousel` callable skill to author slides. See projects/carousel
# for the full flow.
montaj init --prompt "..." \
--remote-clip '{"url":"https://...","destPath":"clip1.mp4","contentType":"video/mp4","sizeBytes":12345}' \
--remote-asset '{"url":"https://...","destPath":"logo.png","contentType":"image/png","sizeBytes":678}'
# Fetch clips/assets from remote URLs at init time (S3 pre-signed, R2, GCS,
# Azure SAS, plain HTTPS) directly into the project workspace. Repeatable.
# For batch input use --remote-clips-file / --remote-assets-file pointing at
# a JSON array of the same item shape. Fail-closed: requires
# MONTAJ_HTTP_ALLOWED_HOSTS in the environment (comma-separated lowercase
# hostnames). Content-type and byte count are verified against declared
# values; writes are atomic.
montaj upload --project <id-or-path> \
--src output/render.mp4 --to "https://..." \
--src output/manifest.json --to "https://..."
# Push project workspace files to caller-supplied URLs. --src/--to is a
# repeatable pair. Returns 200 on full success or 207 Multi-Status on
# partial failure (per-op results in stdout). Use this with cloud
# storage's pre-signed PUT URLs to ship renders without provider-specific
# code in Montaj. Same allowlist gate as remote inputs.
montaj status
# Show current project.json state + step progress
montaj approve
# ai_video projects only — mark the storyboard as approved
montaj doctor
# Check all system dependencies (ffmpeg filters, ffprobe, node, whisper)
# Exit 0 = OK, exit 1 = issues
montaj regen <clip_id>
# Queue a full-scene regeneration for an ai_video clip
montaj regen subcut <clip_id>
# Queue a sub-range regeneration within a clip
montaj profile
# Manage creator style profiles
montaj profile asset list <name>
# List all assets registered in the profile's asset library
montaj profile asset add <name> <path> [--description TEXT] [--tags A,B,C]
# Copy a file into the profile's assets dir and register it in the manifest
montaj profile asset rm <name> <filename>
# Remove an asset from the library (deletes the file and its manifest entry)
montaj profile asset notes <name> --get
# Print the profile-wide creative notes
montaj profile asset notes <name> --set "your notes here"
# Overwrite the profile-wide creative notesSample — Preview PNG Without a Full Render
Two commands for visual inspection without running a full render. The production render pipeline is unchanged — these are read-only sampling tools.
montaj sample overlay
Renders one frame of a JSX overlay through the same Puppeteer path the production renderer uses and writes a PNG. ~3s wall time. Use this to catch text overflow, font-width mismatches, and layout bugs before adding the overlay to a project.
montaj sample overlay <overlay.jsx> --out <path.png>
# Render frame 0 at default canvas (1080×1920)
montaj sample overlay <overlay.jsx> --frame 30 --out /tmp/frame30.png
# Render frame 30
montaj sample overlay <overlay.jsx> --measure --out /tmp/check.png
# Also return per-element bounding-box and overflow data as JSON| Arg | Default | Description |
|---|---|---|
<overlay.jsx> | required | Path to the JSX overlay file |
--out <path.png> | required | Output PNG path |
--frame <N> | 0 | Frame number to render |
--width <W> | 1080 | Canvas width in pixels |
--height <H> | 1920 | Canvas height in pixels |
--props <JSON> | {} | Props to pass to the overlay component |
--google-fonts <spec> | — | Comma-separated Google Fonts spec (e.g. Syne:wght@800) |
--measure | off | Walk the DOM and return per-element bbox + overflow data |
Without --measure, stdout is the absolute PNG path. With --measure, stdout is a JSON object:
{
"pngPath": "/tmp/check.png",
"measurements": {
"anyOverflow": true,
"viewport": { "w": 1080, "h": 1920 },
"texts": [
{
"text": "RECURSIVE",
"tag": "DIV",
"fontFamily": "Syne, sans-serif",
"fontSize": "160px",
"bbox": { "x": -257, "y": 740, "w": 1594, "h": 154 },
"overflow": { "left": 257, "right": 257, "top": 0, "bottom": 0 },
"clippingAncestor": null
}
]
}
}anyOverflow is the go/no-go boolean — true means at least one text element overflows the design canvas and will be visually clipped in the final render. A non-null clippingAncestor means the element is inside an overflow: hidden parent (common in animation sections that intentionally clip in/out elements — not a real overflow).
Results are content-hash cached under ${tmpdir()}/montaj-sample-cache/ (24h GC), so repeated calls on an unchanged file return immediately.
Example:
# Check a headline overlay before adding it to project.json
montaj sample overlay ~/.montaj/overlays/headline.jsx \
--google-fonts "Syne:wght@800" \
--measure \
--out /tmp/headline-check.pngmontaj sample frame
Renders the fully composited frame at a given timestamp: active video clip, image items, and overlay JSXs — all at project resolution. ~10–30s wall time. Use this to catch layout collisions visible only in the full composition (overlay sitting on the speaker's face, image item covering a headline, etc.).
montaj sample frame <project.json> --at <seconds>
# Default output: <project_dir>/render/samples/frame-<t>s.png
montaj sample frame <project.json> --at 5.5 --out /tmp/frame5.5.png| Arg | Default | Description |
|---|---|---|
<project.json> | required | Path to project.json |
--at <seconds> | required | Timestamp in seconds to sample |
--out <path.png> | <project_dir>/render/samples/frame-<at>s.png | Output PNG path |
Stdout is the absolute PNG path.
Video seek is accurate (not keyframe-aligned), so the frame you request is the frame you get. HDR projects produce sRGB BT.709 PNGs so the sample looks correct on any display; the production render still emits properly-tagged HDR output. A timestamp in a timeline gap returns an all-black frame; a timestamp past project end returns an error.
Example:
# Verify an overlay isn't covering the speaker's face at t=5.5s
montaj sample frame ./my-project/project.json --at 5.5 --out /tmp/t5.5.pngStep Params Quick Reference
All steps accept --out <path>. Run montaj step <name> --help for full details.
| Step | Key params |
|---|---|
probe | — |
snapshot | --cols <n>, --rows <n>, --at <t> |
jump_cut | --cuts <json> or --keeps <json> |
cross_cut | --b-roll <file>, --segment-duration <s> |
montage | --inputs <files>, --beat-duration <s> |
resize | --ratio <9:16|1:1|16:9> |
normalize | --target <youtube|podcast|broadcast|custom>, --lufs <n> |
extract_audio | --format <wav|mp3|aac> |
stem_separation | --stems <vocals|drums|bass|other>, --out-dir <path> |
rm_fillers | --model <tiny.en|base.en|medium.en|large> |
waveform_trim | --threshold <dB>, --min-silence <s> |
waveform_image | --chunk-duration <s> |
rm_nonspeech | --model <name> (any whisper model, default base), --max-word-gap <s>, --sentence-edge <s>, --language <code> |
crop_spec | --keep <start:end> (repeatable), --out <path> |
virtual_to_original | --inverse |
transcribe | --model <base.en|medium.en>, --language <code> |
caption | --style <word-by-word|pop|karaoke|subtitle|highlight-box|outline|clean> |
generate_captions | --project-id <id>, --model <name>, --language <code>, --style <name> |
normalize_window | --input <file>, --inpoint <s>, --outpoint <s>, --color-space <key>, --out <path> |
lyrics_sync | --lyrics <txt>, --model <tiny.en|base.en|medium.en|large>, --language <code>, --start <s>, --end <s>, --out <path> |
lyrics_render | --captions <json>, --audio <mp3>, --input <video>, --position, --color, --fontsize, --box, --accumulate, --window-size <n>, --words-per-line <n>, --duration <s>, --audio-inpoint <s> |
kling_generate | --prompt, --out, --first-frame, --last-frame, --ref-image (max 7), --duration, --mode, --model, --aspect-ratio, --multi-shot, --shot-type, --multi-prompt, --project-id, --scene-id, --external-task-id |
analyze_media | <input>, --prompt, --model, --json-output, --out |
generate_image | --prompt, --out, --provider <gemini|openai>, --ref-image, --size, --aspect-ratio |
generate_music | --prompt, --out, --with-vocals, --seed |
generate_voiceover | --text, --text-file, --voice, --vendor <kling|gemini>, --speed |
search_images | --query <str>, --provider <commons|sportsdb|web>, --limit <n> |
fetch_image | --url <str>, --out <path>, --max-bytes <n> |
fetch | --url, --limit, --format |
doctor | (no params) |
CLI Flag Conventions
Two flags look similar and must not be conflated:
| Flag | Purpose |
|---|---|
--json | Wrap stdout in a JSON envelope for machine-readable output. Available on every command. |
--json-output | Ask the underlying model or API to return structured JSON data. Only on steps that call a model/API (e.g., analyze-media). |
Output Convention
All commands follow the same contract:
- stdout — the result: file path or JSON. Nothing else.
- stderr — errors only:
{"error":"code","message":"detail"} - exit 0 on success; on failure, the step's own exit code (as of v3.7.0 — previously a failed step always exited
1)