Driving CapCut / JianYing video drafts from an LLM agent (Claude, DeepSeek, GLM) with a zero-dep Node CLI
Driving CapCut / JianYing video drafts from an LLM agent (Claude, DeepSeek, GLM) with a zero-dep Node CLI
Production AI Automation Notes #4 — how an LLM agent edits a draft_content.json over JSON-in / JSON-out, no MCP server, no Python.
Last tested: May 2026 (capcut-cli v0.5.0, CapCut 9.x, JianYing 5.9.0). See Changelog at the bottom.
If this saves you setup time, follow @renezander030 for more production AI automation notes — agent approval gates, MCP server design, video pipelines.
Full repo with scripts: github.com/renezander030/capcut-cli
| Agent goal | capcut command |
What it writes in draft_content.json |
|---|---|---|
| Discover the project | capcut info ./project |
reads id, name, duration, fps, canvas_config, track counts |
| List every subtitle | capcut texts ./project |
walks tracks[].segments[] of type text, joined with materials.texts[] |
| Replace a subtitle | capcut set-text ./project <segment-id> "New line" |
rewrites materials.texts[].content.text + adjusts the range byte offsets |
| Add a speech-bubble | capcut bubble-text ./project <segment-id> --bubble heart |
sets effect/bubble_id on the text material |
| Fade audio | capcut audio-fade ./project <segment-id> --in 0.5 --fade-out 1.0 |
appends to materials.audio_fades[] |
| Set blend mode | capcut mix-mode ./project <segment-id> screen |
sets segment.mix_mode on the video segment |
| Set draft cover | capcut add-cover ./project ./cover.png |
sets the draft's cover frame at time 0 |
| Discover what's possible | capcut enums --filters --jianying |
dumps the 468-slug filter catalogue for JianYing namespace |
| Run an agent loop | capcut serve reads JSONL on stdin, emits JSONL on stdout |
stateless, one process per loop |
JSON in, JSON out, exit code 0/1/2 for CI. Zero runtime dependencies (Node ≥ 18 stdlib only). Both CapCut and JianYing namespaces in one binary — --jianying toggles enums.
One CLI + one model + one wrapper. No MCP server, no HTTP daemon, no Python venv.
# install
npm i -g capcut-cli
# verify
capcut version ./path/to/draft_content.json
# → {"app":"cc","app_version":"9.0.0","support":{"status":"tested"},
# "schema":{"mask_field":"common_masks","has_text_ranges":true}}That's it. The agent now has a deterministic surface: every command is JSON-in / JSON-out, every error has an exit code, every enum is discoverable from enums.
┌─────────────────┐ ┌────────────────────┐ ┌─────────────────┐ ┌────────────┐
│ 1. cut source │ → │ 2. LLM emits JSON │ → │ 3. shell runs │ → │ 4. review │
│ (capcut cut) │ │ (Claude/DeepSeek) │ │ capcut commands │ │ (capcut │
│ → short.mp4 │ │ → array of edits │ │ → draft updated │ │ info/lint) │
└─────────────────┘ └────────────────────┘ └─────────────────┘ └────────────┘
The agent never touches draft_content.json directly. It emits structured edit instructions. The shell wrapper validates them against the CLI's known command set, runs them, captures JSON output, feeds it back to the agent on the next turn.
This is the same pattern as agent-approval-gate (PAAN #1): the LLM proposes, the CLI executes, the schema gates the contract.
| Surface | What it costs | What it gives you |
|---|---|---|
| HTTP server (Flask, CapCutAPI, capcut-mate) | Process to keep alive, port to manage, state to debug | One canonical instance, hot in-memory cache |
| MCP server | stdio/HTTP transport, tool schema, model-specific quirks | Tighter integration with one ecosystem (Claude) |
| Python library (pyJianYingDraft, pyCapCut) | Python venv, dependency resolution, agent has to wrap stdin/stdout itself | Mature, well-documented, large community |
| Zero-dep CLI (this) | One npm install | Same agent can drive it whether it's Claude, DeepSeek, GLM, Kimi, or a shell script. No transport layer. Exit codes work. Every command pipes to jq. |
The CLI bet: if your agent can write JSON and your shell can read it, you don't need a protocol on top. capcut serve is the only concession — a stateless JSONL stdin/stdout streamer for n8n / Coze / 扣子 / Make workflows. No port, no daemon.
The hardest part of agent-driving any tool isn't the commands — it's the enums. CapCut and JianYing ship hundreds of slugs across transitions, filters, bubble shapes, sticker IDs, animation presets. They differ between platforms. They change between releases.
capcut enums is the discovery API:
# CapCut namespace defaults
capcut enums --filters
capcut enums --bubbles
capcut enums --transitions
capcut enums --animations
capcut enums --effects
# JianYing namespace
capcut enums --filters --jianying # 468 slugs
capcut enums --bubbles --jianyingFeed the JSON output back into the agent's context. It now knows exactly which strings are legal for the next bubble-text or add-filter call. No guessing, no hallucinated slugs.
Shipped this week from Discussion #1 community vote:
mix-mode <id> <mode>— blend mode on a video segment (normal·multiply·screen·overlay·soft-light·hard-light·color-dodge·color-burn·darken·lighten·difference·exclusion).audio-fade <id> --in 0.5 --fade-out 1.0— realmaterials.audio_fades[]entries, not faked withvolumekeyframes.add-cover <project> ./cover.png --time 0— set the draft's cover frame.add-filter <slug> <start> <duration>+enums --filters— colour-filter chain separate fromadd-effect. 10-slug starter for CapCut, 468 for JianYing.bubble-text <id> --bubble heart+enums --bubbles— 花字 / speech-bubble shapes. 7-slug starter, or--effect-id/--resource-idfor your own IDs.import-ass <project> ./sub.ass— ASS / SSA subtitle import alongsideimport-srt. Same--track-name/--style-ref/--time-offsetflag set.
Combined with v0.4: caption (whisper → real caption objects, not SRT-as-text), migrate (5.9 ↔ 9.6 mask ↔ common_masks), lint (schema-aware caption checks, exit code for CI), version (app detection + schema flags), translate (whole-draft clone via Anthropic API), serve (JSONL queue), add-sfx, chroma, export --batch. The agent now has a 30-command surface to compose against.
# 1. create a project from scratch
capcut init smoke-test
# → {"ok":true,"path":"~/Movies/CapCut/User Data/Projects/.../smoke-test/draft_content.json"}
# 2. add a video, a text overlay, an audio track
capcut add-video ./smoke-test ./clip.mp4 0 5000000
# → {"ok":true,"segment_id":"a1b2c3d4-...","track_name":"video"}
capcut add-text ./smoke-test 500000 2500000 "Welcome to the video"
# → {"ok":true,"segment_id":"e5f6a7b8-...","material_id":"...","track_name":"text"}
capcut add-audio ./smoke-test ./voiceover.wav 0 5000000
# → {"ok":true,"segment_id":"...","material_id":"...","track_name":"audio"}
# 3. apply v0.5 polish — bubble the text, fade the audio
capcut bubble-text ./smoke-test e5f6a7b8 --bubble heart
# → {"ok":true,"id":"e5f6a7b8-...","bubble":"heart","effect_id":"..."}
capcut audio-fade ./smoke-test <audio-segment-id> --in 0.5 --fade-out 1.0
# → {"ok":true,"id":"...","fade_in_ms":500,"fade_out_ms":1000}
# 4. lint before opening in CapCut
capcut lint ./smoke-test
# → {"errors":0,"warnings":0,"checks":["overlap","line-length","missing-files","missing-refs"]}
# → exit 0
# 5. agent inspects what it built
capcut info ./smoke-test -H
# human-readable summary table for the agent's contextFive commands, one shell loop, zero draft_content.json editing by hand.
#!/usr/bin/env bash
# agent-edit.sh — wrap an LLM into a capcut-cli edit loop
set -euo pipefail
PROJECT="${1:?path to draft}"
PROMPT="${2:?what the agent should do}"
# 1. give the agent the current state + the legal enums
STATE="$(capcut info "$PROJECT")"
TEXTS="$(capcut texts "$PROJECT")"
BUBBLES="$(capcut enums --bubbles --jianying)"
# 2. ask the LLM for a plan as a JSON array of capcut commands
PLAN="$(claude -p --model opus <<EOF
You are editing a CapCut/JianYing draft via the capcut-cli.
Current state: $STATE
Current texts: $TEXTS
Legal bubbles: $BUBBLES
User goal: $PROMPT
Emit a JSON array of capcut command arrays, e.g.:
[["set-text", "<id>", "New caption"], ["bubble-text", "<id>", "--bubble", "heart"]]
Only emit the JSON array. No prose.
EOF
)"
# 3. execute each command, capture exit codes
echo "$PLAN" | jq -c '.[]' | while read -r cmd; do
ARGS=$(echo "$cmd" | jq -r '.[]')
capcut $ARGS "$PROJECT" || { echo "FAIL: $cmd"; exit 1; }
done
# 4. lint + report
capcut lint "$PROJECT"That's the entire agent loop. Substitute claude -p with deepseek-cli / glm-cli / kimi-cli — same shape. Anything that emits JSON works.
- Don't ask the agent to edit
draft_content.jsondirectly. It's deeply nested, microseconds-typed, with text buried inside escaped JSON-in-JSON insidematerials.texts[].content. Even a 1M-context model will mis-edit it 1 call in 3. - Don't bundle 12 alternative tools and ask the agent to choose. Pick
capcut-cli(orpyJianYingDraft, orCapCutAPI— whichever you standardise on) and put only that command surface in the prompt. The agent will compose well within one tool, badly across three. - Don't skip
capcut versionat the start of a session. JianYing 6.0+ encrypts the draft. If the agent tries to edit an encrypted file, it will fail in confusing ways.capcut versionexits cleanly withsupport.statusset — branch on that. - Don't trust hallucinated slugs. Always pass
capcut enumsoutput into the agent's context before asking for a bubble / filter / transition. The slug namespaces between CapCut and JianYing are not interchangeable.
This is Production AI Automation Notes #4. The series covers gates, contracts, and deterministic interfaces for AI agents in production.
- PAAN #1 — Agent Approval Gates: schema-validated proposals + human-in-the-loop dispatch.
- PAAN #2 — Context7 v2 enterprise GraphQL MCP pattern: what changes when an MCP server can write, not just read.
- PAAN #3 — Agentic Task System (ATS): structured memory for long-running agent loops.
- PAAN #4 — this gist: LLM-driven video draft editing via zero-dep CLI.
Follow @renezander030 for the next entry.
- capcut-cli repo — full source, ~3,700 lines of
docs/draft-schema/, 60+ tests - v0.5 release Discussion #1 — what shipped and what's open for v0.6 vote
- pyJianYingDraft — the Python alternative, mature and well-documented
- CapCutAPI — the HTTP server alternative
Drop a comment with your setup:
- Model (Claude Opus / Sonnet / DeepSeek-R1 / GLM-4.7 / Kimi-K2 / local Qwen)
- App + version (CapCut 9.x / JianYing 5.9 / JianYing 10.x encrypted)
- The command sequence you're driving
- What worked / what the agent got wrong
If you've wired capcut serve into n8n / Coze / 扣子 / Make, a workflow JSON export is gold for the next reader.
- Initial publication. capcut-cli v0.5.0 + the six commands from Discussion #1.
- Hook map: this gist (curiosity-driven), capcut-cli repo (search-driven), v0.5 release notes (pain-driven) — three distinct hook types across the surface.
The Production AI Automation Notes series has grown to 7 entries:
- Agent Approval Gates — proposed actions, schema validation, audit log
- Token Budgets — per-step, per-pipeline, per-day enforcement
- Agentic Task System — your task app as the agent's context layer, with pluggable storage adapters
- Driving CapCut / JianYing from an LLM agent — deterministic JSON command boundary
- SQLite dedup + crash safety — WAL mode, seen_items, audit log
- Prompt-injection defense — input sanitization, schema validation, deterministic boundary
- PDF cite verification — auditable LLM extraction with per-fragment bounding boxes
Reference implementation for entries #1, #2, #5, #6, #7: draftcat (Go, MIT).
Follow @renezander030 for new entries.
Independent project — not affiliated with or endorsed by ByteDance. "CapCut"/"JianYing" are trademarks of their owner; used nominatively.