Wire capcut-cli into n8n / Make / Coze
June 20, 2026 · View on GitHub
capcut serve is a stateless JSONL queue runner. It reads one job per line from
stdin (or a --queue file), dispatches each to the CLI, and writes one JSON result
per line to stdout. No daemon or port. Within one drain it supports bounded workers,
retries, stable-ID deduplication, and per-project serialization. That makes it a clean
fit for any automation tool that can run a shell command or pipe bytes.
The job format
One JSON object per line. cmd is required; id, project, args, timeoutMs, and retries are optional:
{"id":"inspect-42","cmd":"info","project":"/work/draft_content.json"}
{"id":"title-42","cmd":"add-text","project":"/work/draft_content.json","args":["8s","2s","Subscribe","--font-size","16"],"retries":2}
{"cmd":"import-srt","project":"/work/draft_content.json","args":["/work/captions.srt"]}
{"cmd":"lint","project":"/work/draft_content.json"}
Each result line is {id, ok, cmd, args, status, stdout, stderr, attempts, duration_ms, deduplicated}. ok is true when the
command exited 0; stdout is the parsed JSON the command would have printed. Because
lint exits 2 on errors, a lint job comes back {"ok":false,"status":2,...} — handle
that in your flow to gate a render.
Local / cron
cat jobs.jsonl | capcut serve > results.jsonl
# or read from a file the upstream step wrote:
capcut serve --queue jobs.jsonl --workers 4 --retries 2 --timeout 300000 > results.jsonl
Jobs for the same project always run sequentially; different projects can use the worker pool. Add --fail-fast to stop at the first failing job. --backoff-ms controls exponential retry delay and --max-buffer-mb bounds captured output.
n8n (self-hosted — Execute Command node)
n8n's Execute Command node runs on the n8n host, so capcut just needs to be on its
PATH (npm install -g capcut-cli, or use the Docker image below). Build the JSONL in a
Function node, then pipe it in:
Command: capcut serve
Input: {{ $json.jobs }} // a JSONL string from the previous node
A Function node to turn structured items into JSONL:
// one n8n item per job → a single JSONL string
return [{ json: { jobs: items.map(i => JSON.stringify(i.json)).join("\n") } }];
Parse results.jsonl back out in the next Function node by splitting on newlines and
JSON.parse-ing each line.
Make / Coze (cloud) — webhook → queue file → serve
Cloud builders can't run a binary directly. The stateless model still fits: have the
cloud scenario write a queue file (or POST JSONL to a tiny endpoint on a host you
control), then run capcut serve --queue on that host from cron or a file-watch. The
boundary is the JSONL file — the cloud side never needs to know about the CLI internals.
# on your host: drain whatever the cloud scenario dropped, every minute
* * * * * test -s /srv/capcut/inbox.jsonl && \
capcut serve --queue /srv/capcut/inbox.jsonl > /srv/capcut/outbox.jsonl && \
: > /srv/capcut/inbox.jsonl
Docker (no global install)
The published image runs serve over a stdin pipe — drafts are mounted at /work:
cat jobs.jsonl | docker run --rm -i -v "$PWD:/work" capcut-cli serve > results.jsonl
Build the image from this repo with docker build -t capcut-cli ..
Why no HTTP mode? A long-lived server adds a port to secure, state to reset, and a process to babysit. A queue runner that starts, drains, and exits composes with the retry/idempotency model your automation tool already has. If you genuinely need HTTP, put
servebehind a one-line handler that pipes the request body to it.