Montaj Native Interface

SkillWeb & browsing

The native interface — how the _contract verbs are performed when Montaj runs locally, either via the HTTP server (montaj serve) or the headless CLI. The dispatcher loads this after it detects HTTP or CLI mode; domain skills never read it directly. Covers step invocation, project read/save, file read/write, logging, GET-before-save discipline, workspace resolution, and image-step credentials.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Montaj Native Interface skill

What this skill tells your AI

The instructions your AI receives, as published by thesampadilla/montaj in skills/native/SKILL.md and read by ahel’s review.

This skill is the native implementation of the _contract vocabulary. Domain skills speak only in the canonical verbs (run step, read the project, save the project, write a file, read a file, log). This document defines HOW each of those verbs is actually performed when Montaj runs on the local machine.

Domain skills do not load this skill. The dispatcher (montaj root skill) detects the interface and loads this one; the domain skills then phrase their work in _contract verbs and the mapping here resolves them.

There are two native sub-modes:

  • HTTPmontaj serve is running a local server. All interactions go over HTTP.
  • CLI — no server. Steps run as montaj <step> subprocesses; the project is a project.json file on disk; logs go to stderr.

The dispatcher tells you which mode you are in. If you must decide yourself: GET http://localhost:<port>/api/projects?status=pending — if it responds, you are in HTTP mode; if the connection is refused, CLI mode.


Verb mapping

_contract verbHTTPCLI
run step <name> with <args>POST /api/steps/<name>montaj <step> …
read the projectGET /api/projects/<id>read project.json from the project directory
save the project (delta)GET then PUT /api/projects/<id>read-modify-write project.json
write a file <path>POST /api/files (or write to disk; both modes share one filesystem)write to disk
read a file <path>GET /api/files?path=… (or read from disk)read from disk
log <message>POST /api/projects/<id>/logprint to stderr

Both modes run on the same machine and share one filesystem, so file paths are always real on-disk absolute paths in either mode.


Workspace & config resolution

  • Project directory: {workspaceDir}/<date>-<name>/. workspaceDir defaults to ~/Montaj; override in ~/.montaj/config.json.
  • Step outputs go next to their inputs. Trim-spec outputs: <original>_spec.json. Materialized cuts: <original>_cut.mp4 (or _cut.wav with --audio). Editing proxies: <original>_proxy_<look>_<format>.mp4 beside the source, or under <workspace>/.sources/_proxycache/<hash>/ when the source lives outside the workspace. Final render: output.mp4 in the project directory. Transcripts: <clip>_transcript.json and <clip>.srt.
  • Server port (HTTP mode): montaj serve listens on port 3000 by default; override with the MONTAJ_SERVE_PORT environment variable. Use that port in every URL below.

HTTP mode

Endpoints

EndpointMethodPurpose
/api/projectsGETList projects (?status=pending)
/api/projects/:idGET / PUTRead or update (save) a project
/api/projects/:id/logPOSTAppend a status log message
/api/stepsGETList available steps
/api/steps/:namePOSTRun a step
/api/filesGET / POSTRead / write a workspace file
/api/runPOSTCreate project + start edit (clips, assets, prompt)

All URLs are http://localhost:<MONTAJ_SERVE_PORT> (default 3000).

run step — POST /api/steps/:name

# Single input
curl -s -X POST http://localhost:3000/api/steps/probe \
  -H "Content-Type: application/json" \
  -d '{"input": "/path/to/clip.mp4"}'

# Step with params
curl -s -X POST http://localhost:3000/api/steps/materialize_cut \
  -H "Content-Type: application/json" \
  -d '{"input": "/path/to/clip.mp4", "inpoint": 2.5, "outpoint": 8.3}'

# Multiple inputs (rm_fillers batch, etc.)
curl -s -X POST http://localhost:3000/api/steps/rm_fillers \
  -H "Content-Type: application/json" \
  -d '{"inputs": ["/path/clip1.mp4", "/path/clip2.mp4"]}'

Response: {"path": "..."} for file outputs, a JSON object for data outputs (probe, transcribe, etc.).

Long-running steps (transcribe, rm_fillers, resize, remove_bg) must run in the background so you stay available for conversation:

  1. Single long step — set run_in_background: true on the Bash tool call. You are notified on completion.
  2. Multiple clips in parallel — write a shell script with & + wait to /tmp, execute with run_in_background: true.

Never block the conversation waiting on ffmpeg. Log the step, fire it in the background, then tell the user what's running.

read the project — GET /api/projects/:id

# Get pending projects (to pick one up)
curl -s "http://localhost:3000/api/projects?status=pending"

# Read one project's full state
curl -s "http://localhost:3000/api/projects/{id}"

save the project (delta) — GET then PUT /api/projects/:id

The body id must match the URL id.

curl -s -X PUT http://localhost:3000/api/projects/{id} \
  -H "Content-Type: application/json" \
  -d '{"id": "{id}", "status": "draft", "tracks": [...]}'

Hard rule — GET fresh, merge your delta, then PUT. Every time.

While the server is running, the user can edit project.json from the UI at any moment — repositioning overlays via drag, retiming items, editing text, toggling assets. Montaj only auto-commits to git on status transitions (pendingdraft, draftfinal); mid-status UI edits are written to the working tree but never committed. A stale PUT from the agent silently overwrites those edits with no warning, no SSE distinction, and no recovery path beyond Time Machine.

Pattern — always GET, merge in your delta, then PUT:

# 1. Fetch fresh state
curr=$(curl -s http://localhost:3000/api/projects/{id})

# 2. Apply your delta (here: append a new overlay item to tracks[1]'s items).
#    `tracks` is an array of track OBJECTS (`{id, items, ...}`), not bare item
#    arrays — appending to `.tracks[1]` directly would append into the wrong
#    place, and on a project with only one track `.tracks[1]` doesn't exist
#    yet, so the delta creates a properly-shaped track (`trk-1`) for it.
new=$(echo "$curr" | jq --argjson item '{"id":"ov-new","type":"overlay","src":"...","start":12,"end":14}' \
  '.tracks |= (if length > 1 then .[1].items += [$item] else . + [{"id": "trk-1", "items": [$item]}] end)')

# 3. PUT the merged result
curl -s -X PUT http://localhost:3000/api/projects/{id} \
  -H "Content-Type: application/json" -d "$new"

Notes:

  • Montaj's PUT does a shallow top-level merge — top-level fields you omit from the body are preserved (useful for narrow updates like {id, status}). The shallow merge does NOT help when you update a field the user also edited (e.g. tracks) — it replaces the whole field. For any additive change to a nested array (adding an overlay, asset, or track item), always read-modify-write the whole top-level field.
  • The rule applies to every PUT — status transitions, re-runs, overlay edits, asset toggles. No exceptions.
  • If you do several updates in sequence (add overlay → log → update settings), re-GET between each one. The user may have edited in between.

log — POST /api/projects/:id/log

POST /api/projects/{id}/log
{"message": "Transcribing clip 3 of 6…"}

Call before each step. Short and human-readable — what you're doing, not why. Appears live in the UI.

write / read a file — /api/files

Files are real on-disk artifacts (overlay JSX, fetched images, assets). Because the server shares the machine's filesystem, you may either use /api/files or write/read the path directly on disk — both reach the same bytes. Step outputs already land on disk next to their inputs.


CLI mode

Use CLI mode when montaj serve is NOT running.

run step — montaj <step> …

montaj probe clip.mp4
montaj snapshot clip.mp4
montaj materialize-cut clip.mp4 --inpoint 2.0 --outpoint 8.0    # keep one range
montaj materialize-cut clip.mp4 --cuts '[[0,1.2],[5.3,7.8]]'    # remove ranges, one ffmpeg pass
montaj materialize-cut spec.json                                 # apply every keep in a trim spec
montaj waveform-trim clip.mp4 --threshold -30 --min-silence 0.3
montaj rm-nonspeech clip_spec.json --model base
montaj transcribe clip.mp4 --model base.en
montaj caption clip.mp4 --style word-by-word
montaj step crop_spec --input spec.json --keep 8.5:14.8 --keep 40.0:end
montaj step virtual_to_original --input spec.json 47.32
montaj normalize clip.mp4 --target youtube
montaj resize clip.mp4 --ratio 9:16

Not every step has a top-level command, and montaj -h is not the way to find out which do: about twenty step commands are deliberately hidden from that listing (_HIDDEN in cli/main.py), including probe, transcribe, caption, normalize, resize and materialize-cut — every one demoed above. They work as shown. Anything without a top-level command runs as montaj step <name>, which accepts either spelling (crop_spec or crop-spec). To see all available steps, including user and project-local custom ones: montaj step -h.

Fire long-running CLI steps in the background (run_in_background: true on the Bash call) for the same reasons as HTTP mode.

read / save the project — project.json on disk

The project lives at project.json in the project directory. Read it by reading the file; save by read-modify-writing the file — read the current contents, merge in only your changed fields, write it back. The GET-fresh-merge-save discipline from _contract still applies: re-read immediately before writing so you don't clobber a concurrent edit.

write / read a file — filesystem

Write and read workspace files directly on disk at their absolute paths.

log — stderr

Print progress messages to stderr.


Image-step credentials

Steps that reach external image providers (search_images, fetch_image) read credentials from ~/.montaj/credentials.json. This is native machine config: the keys live on the operator's machine, not in the project. If an image step fails with an auth/credentials error, check that ~/.montaj/credentials.json exists and contains the provider key (e.g. SERPAPI_API_KEY). This applies in both HTTP and CLI mode — the step subprocess reads the same file either way.

Signals

GitHub stars
25
Forks
11
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
native-thesampadilla
Source
github.com/thesampadilla/montaj