BaoCut CLI
Drive the BaoCut transcription/subtitle pipeline headlessly. The binary is
baocut, from the BaoCut macOS app: run the baocut the app puts on your PATH,
or the full path /Applications/BaoCut.app/Contents/MacOS/baocut-cli (this skill
also ships bin/baocut, which resolves that path and checks the app is installed).
Every command supports --help; add --json (global, before the command)
whenever you parse output — always do when scripting.
Requirements — macOS + the BaoCut app. This skill only runs on macOS and
drives the BaoCut app's bundled CLI. If baocut is not found, or a command
reports the app is missing, tell the user to install BaoCut for Mac from
https://baocut.app — the skill cannot run without it.
You are the orchestrator. The LLM pipeline stages (segment / polish / repunct / translate / align) run inside a background worker process that asks YOU for each model answer. Never read prompt or answer text into your own context — prompt bodies live in files; you pass file paths around.
Read the reference for the part of the job you're doing (same directory):
- references/orchestration.md — the answer-worker
pool (claim → answer → submit --next), contracts table, model tiering,
liveness, prompt-caching economics. Read this before driving any
auto/task startrun. - references/editing.md — talking-head video editing: the cleanup decision rules (fillers/retakes/pauses), cut review, the cleanup/broll LLM stages, B-roll sourcing & placement, edit verification. Read this before any 剪口播 / rough-cut / B-roll job.
- references/transcript-quality.md — ASR
model policy, Agent-driven polish,
PolishQuality, Speaker routing and cleanup levels. Read before promising corrected captions or rough cuts. - references/speakers.md — reviewing & fixing speaker identification, naming speakers, the evidence loop (show / view / frames).
- references/versions.md — staleness, incremental re-translate, branches.
- references/export.md — SRT/VTT/ASS/markdown/video export, bilingual modes, time-range clipping.
- references/recovery.md — diagnostics (
task log/calls/report/audit), stalled workers, orphan projects, postmortems. - references/known-errors.md — literal error → cause → exact recovery recipes. Check here FIRST when any command errors, before improvising a workaround.
A plain transcription/translation run needs ONLY orchestration.md — don't read the speaker/version/export docs until the task actually calls for them.
Quick start: one command, one loop
# Transcribe → polish → translate a media file behind ONE task:
baocut --json auto talk.mp4 --lang zh # -> {"taskId":"t-…","projectId":"p7"}
# The positional also takes any yt-dlp-supported URL (M78) — the video
# downloads first ("Fetching video" phase), lands in ~/Downloads named after
# its title (--save-dir overrides), and the project auto-seeds title/desc
# from the video metadata:
baocut --json auto "https://www.youtube.com/watch?v=…" --lang zh
URL sources need yt-dlp on PATH (brew install yt-dlp); a bad URL fails
fast (metadata is probed before anything persists). Common knobs (details in
baocut auto --help): --source-lang X ·
--model qwen3-asr-0.6b · --title X · --desc "…" · --instructions "…" · --tone formal ·
--speakers N / --no-speakers · --no-polish · --no-autocorrect ·
--save-dir <dir> · --align-batch N · --align-concurrency N ·
--second-look targeted|retranslated|off (targeted is the quality default;
use off only for an explicitly latency-first run).
Language flags (M78, everywhere): --lang is always the TRANSLATE TARGET;
the source language is --source-lang (transcribe/auto/project edit).
JSON envelope (M78): every --json result carries a string status —
"ok", "error", "rejected" (submit lint), or a richer state
("prompt"/"claimed"/"done"/"stalled"…); action results reference their
project as projectId (the old "ok": true/false booleans and bare-id keys
are gone).
Confirmed single-speaker subtitle-only ask? Skip diarization. Speaker identification is ON by
default and costs ~4-5 minutes of local compute. When the user only wants
subtitles/translation, the recording is single-speaker, and speaker boundaries
are irrelevant, pass --no-speakers. Never use this shortcut for rough-cut,
interview, podcast, dialogue, or other multi-person work;
it can always be added later (speakers reidentify, see speakers.md).
Set --align-concurrency to the number of answer workers you will actually
run (see orchestration.md — usually 3). The default 4 leaves one request
permanently queued when only 3 workers answer, and its growing ageSec looks
like a slow agent.
Then drive it (full strategy in orchestration.md). Start the persistent pool
immediately after auto returns — task claim --timeout safely waits through
local transcription, so the first analysis call no longer pays agent spin-up:
# workers: baocut --json task claim <taskId> --worker w1
# … answer … task submit <taskId> --call <id> --lease-id <leaseId> --file a.json --next
# root: baocut --json task status <taskId> # observe waitingOn + queue ownership
task wait / task claim return status = prompt/claimed (work to do) or
done · review (--no-apply runs: task review then task apply) ·
stalled (worker died: task resume <taskId>) · failed (read
task log <taskId>). The transcribe phase of auto emits progress only —
no prompts until the first LLM stage.
Talking-head edit (口播剪辑) — M79/M80
Two iron rules: A-roll timing finalizes before any visual layer or caption burn-in, and confirm the rough cut with the user before layering (each checkpoint its own turn) unless they asked for end-to-end. Full decision rules in editing.md.
# Quality-first: finalize Agent polish before changing A-roll timing.
baocut --json auto talk.mp4 --align-concurrency 3 # qwen3-asr-0.6b → Agent polish
baocut --json task report <taskId>
baocut --json audit p7 # polishQuality must PASS
baocut --json task start cleanup p7 --cleanup-level standard
# latency-first one-task shortcut remains available: auto talk.mp4 --rough-cut
baocut --json cut list p7 # review — soft cuts, fully reversible
baocut --json cut restore p7 clip-12 # disagree with one? restore it (--all resets)
baocut --json audit p7 # edits section must be clean
# optional B-roll: suggest → source footage YOURSELF → attach → verify
baocut --json task start broll p7
baocut --json broll add p7 --file shot.png --suggestion bs1
baocut --json broll preview p7 --at 63 # composite proof BEFORE reporting placed
baocut export p7 --video -o final.mp4 # cuts + B-roll apply by default
Cuts are SOFT (skipped at playback, filtered at export; --include-cuts
exports the raw recording). Translation still covers cut words (cuts are
AV-level) — cut before translating if you want the token savings to matter to
the user, not the pipeline. Never surface word ids / cut ids / clip ids to
the user — quote the spoken content instead.
Single stages
baocut --json transcribe talk.mp4 # -> {projectId} (no LLM)
baocut --json transcribe "https://youtu.be/…" --no-speakers # URL works here too
baocut --json transcribe --project p7 # re-transcribe from the project's own source
# (REBUILDS the doc; auto-forks a branch;
# a URL project re-downloads if its file is gone)
baocut --json task start segment|polish|translate|align <pid> [--lang zh --stale-only …]
task start spawns the same kind of worker — same claim/submit loop.
translate runs + APPLIES polish first by default (skip: --no-polish);
--stale-only refreshes only out-of-date paragraphs (versions.md).
Translation runs as TWO big phases: Translating (whole natural sentences,
1:1 source↔translation) then Splitting & aligning (over-long pairs are
split & re-aligned). The align flow (M68) redoes ONLY Phase 2 against an
existing translation — no re-translation, only align-kind prompts:
baocut --json align list <pid> --lang zh # over-aim lines (keys, seams, overHard)
baocut --json task start align <pid> --lang zh [--groups k1,k2] [--from current|pristine]
--groups (keys from align list) scopes the redo to those lines' paragraphs;
--from current (default) re-aligns the doc's translations as they stand
(manual edits survive; untouched groups stay byte-identical), --from pristine
restores the last FULL translate's Phase-1 sentence texts from
ai/sentences-<lang>.json (whole language; fingerprint-gated — a re-polish or
edit invalidates it, fall back to --from current). Re-align never flips
staleness.
For repeatable polish/translation benchmarks, clone one immutable version into a
clean project instead of retranscribing or reusing a mutable AI cache — the full
recipe (controlled A/B, project clone --from-version) is in versions.md.
Older projects can be back-filled with the M56 CJK autocorrect pass:
baocut autocorrect <pid>|--all [--dry-run] (each changed project gets a
restorable "Autocorrect" version snapshot).
Before delivery, run the read-only quality gate:
baocut --json audit <pid> [--lang zh]. task report measures performance;
audit measures subtitle quality/invariants — including source-content loss
and duplicate coverage against the current branch's transcription ancestor,
residual target fragments that can still be safely compacted, and over-aim
target groups that still have a natural balanced split —
and exits 2 on FAIL. For an A/B speed claim, follow the controlled comparison
checklist in orchestration.md — faster is never accepted by timing alone.
For a corrected transcript, also require polishQuality.status == PASS; WARN
means the terms are re-polishable WITHOUT a full rerun (see below).
Fix problems without a full rerun (M81)
When audit/project show flag a specific problem, converge on it — don't
re-run the whole stage:
baocut --json finish-check <pid> [--lang zh] # ONE verdict: {ready, blockers, warnings, next}
finish-check aggregates status + audit + polishQuality + attention + export
preconditions and ALWAYS exits 0 (the verdict is ready), so it chains:
finish-check <pid> && export …. Work its next[] list, then re-run it.
- Wrong ASR term (克拉克→Claude Code, Grab→grep…):
baocut terms fix <pid> --map "克拉克=Claude Code,Grab=grep" [--dry-run]rewrites the source in place (ids/timings preserved, integrity-gated) and LOCKS the terms. It changes the source, so re-align the translation:task start translate <pid> --lang zh --stale-only. - Lock terms up front:
--terms-file <f>ontranscribe/auto/task start polish|translate— one line per termcanonical = variant1 | variant2. Locked terms are authoritative (never respelled) and always residual-checked. - Residual after polish (
polishQualityWARN,polish-residual-term):baocut task start polish <pid> --from-auditre-polishes ONLY the affected paragraphs (also--ranges w1-w2/--terms "v=c"). Needs an already polished+segmented doc. - Zero-duration words (
zero-duration-words,cleanup-filler-zero-duration,polish-introduced-zero-duration):baocut timing repair <pid> [--dry-run](or--all) re-fits unhealthy word timings; never marks anything stale. - Placeholder speaker names:
baocut speakers propose-names <pid>suggests names from self-intro/invitation cues + analysis entities (never auto-applies). - Watch a long run instead of polling:
baocut task watch <taskId> [--jsonl]streams one line per state change until terminal.
Project metadata: title, description, speaker names
Title + description ride into EVERY LLM stage as grounding context
(--no-media-context opts out), so keep them real — proactively:
- Know the content up front (the user described it, a video title, …)? Pass
--title/--desconauto/transcribeso the run's OWN polish/translate already have the context. --json project show <pid>returnsattentionhints when metadata is missing: a filename-ish title, no description, or placeholder "Speaker N" names. Act on them, don't just relay them.- Backfilling: read the transcript (
export --markdown, or what you already saw answering the analysis call — its summary is a ready-made source), thenbaocut project edit <pid> --title "…" --desc "…". The description is 2–4 plain sentences on what the recording is about (people, products, topics) — background context, never copied into subtitles. On an EXISTING project do this BEFOREtask start polish|translate; after anautorun do it at terminal (the worker holds the project lock while running, so metadata edits must wait). - Name the speakers the content identifies — batch form, one save:
baocut speakers rename <pid> s1="Ada Lovelace" s2=Host. Only high-confidence names; evidence rules in speakers.md.
Ground rules
- A successful submit/exit-0 is not verification. Before declaring a run
done, re-read the outcome:
baocut --json audit <pid> --lang <l>for translation quality (exit 2 = FAIL),finish-check <pid>for the aggregate verdict,project showfor doc stats, or re-parse the exported SRT. Report what the evidence shows, not what the commands returned. - Pipeline verification vs formal edit. A verification/benchmark run may go
all the way to
audit/finish-checkautonomously, but must NOT export a deliverable without the user. A formal talking-head edit still stops after the rough cut for a human checkpoint (see editing.md) unless the user asked for end-to-end. - Polish is CLI-orchestrated Agent work. Never hand-edit
doc.json, apply global search/replace, or treat ASR output as polished. Analysis/polish/ repunct calls must be drained throughtask claim+task submit --next. - Write your answer/scratch files to a temp directory (e.g.
$TMPDIRor a dedicated scratch dir), NEVER the repo/cwd —task submit --filetakes any path, and stray*.jsondrafts in a repo root end up in commits. - The BaoCut GUI may stay open — never ask the user to close it; it picks up
CLI writes live, and the worker streams phase/progress into it (M58), so a
stalled run is user-visible. Keep workers fed;
task resumepromptly. - One mutating run per project: a transcribe/worker holds a project-level lock; a second run on the SAME project fails with "project … is busy".
- Reading files under
~/Library/Application Support/BaoCut/is always safe; never WRITE there while the app or a worker runs. - Expect the saved doc's punctuation/spacing to differ slightly from your submitted answers on CJK projects — that's the M56 autocorrect normalizer, not a lint failure.
- If your environment needs authorization to spawn parallel subagents, ask ONCE up front, not per batch. If parallel subagents are unavailable, run the worker loop inline sequentially — slower but correct.