Communitygithub.com

JimLiu/baocut

Open-source Agent Skill that drives the BaoCut macOS app CLI (transcribe · subtitle · translate · cut) from Claude Code, Codex, and other agents

What is baocut?

baocut is a Claude Code agent skill that open-source Agent Skill that drives the BaoCut macOS app CLI (transcribe · subtitle · translate · cut) from Claude Code, Codex, and other agents.

Works withClaude CodeCodex CLI~Cursor
npx skills add JimLiu/baocut

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

Documentation

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 start run.
  • 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> on transcribe/auto/task start polish|translate — one line per term canonical = variant1 | variant2. Locked terms are authoritative (never respelled) and always residual-checked.
  • Residual after polish (polishQuality WARN, polish-residual-term): baocut task start polish <pid> --from-audit re-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/--desc on auto/transcribe so the run's OWN polish/translate already have the context.
  • --json project show <pid> returns attention hints 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), then baocut 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 BEFORE task start polish|translate; after an auto run 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 show for 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-check autonomously, 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 through task claim + task submit --next.
  • Write your answer/scratch files to a temp directory (e.g. $TMPDIR or a dedicated scratch dir), NEVER the repo/cwd — task submit --file takes any path, and stray *.json drafts 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 resume promptly.
  • 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.

Related Skills