Translate a whole EPUB into a bilingual book with bilingual_book_maker's plan mode - greedy partition, agent-reviewed classification plan, cheap smoke test, then a full resumable run. Use when the user wants a book translated well (running heads, page numbers, and apparatus skipped deliberately) rather than a quick --translate-tags pass, or asks for "plan mode" / "bbm" translation.
Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prüfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
Translate a whole EPUB into a bilingual book with bilingual_book_maker's plan mode - greedy partition, agent-reviewed classification plan, cheap smoke test, then a full resumable run. Use when the user wants a book translated well (running heads, page numbers, and apparatus skipped deliberately) rather than a quick --translate-tags pass, or asks for "plan mode" / "bbm" translation.
bbm-plan: plan-mode EPUB translation
Hard constraint: this skill uses --plan-classify agent, and only
that. No other --plan-classify value exists as far as this skill is
concerned. The point of the skill is that you, the coding agent, own the
classification step against the real samples — the plan arrives with its
uncertain signatures set to "action": null, and the translate run
refuses to start while any null remains. There is no shortcut around
answering them. The planner role is judgment work expected of an advanced
agent: do it in the main agent with the full session context, never
delegate plan editing to a subagent or a small/fast model.
Repo: the repository this skill ships in (make_book.py at its root). Run
every command from the repo root. Plan mode is epub-only.
You are the trained CLI operator here: you pick every flag from the guide
below and state the choice with a one-line reason. The user is never asked
to experiment with flags, halt semantics, or resume mechanics — they hand
over a book and credentials, approve the plan and the cost, and get a
bilingual epub back.
The workflow is one curl plus three runs of one command with small flag
changes (plan → smoke → full). All state
lives on disk (.env, <book>_plan.json, the resume cache, run.log) — any
step can be redone after a crash or a new session.
0. Credentials
Copy assets/env.example (this skill dir) to .env at the repo root and
have the user fill it. Two things matter: — the exact model id —
and one API key for wherever that model lives. and
are skill-level fields; make_book.py does not read them from env, you
translate them into flags.
MODEL
MODEL
BBM_API_BASE
The model name picks the route; the key only proves you may use it. An
id the repo has never heard of cannot go in --model at all (argparse
limits it to MODEL_DICT keys) — it travels in --model_list, and which
--model carries it depends on the endpoint shape, which step 1b
establishes by probing. Route table, probe recipes, and the --provider
mechanism for non-OpenAI gateways: references/providers.md, to read
before the first command whenever MODEL is not a plain gpt-* id.
Source .env in the same Bash call as the run:
set -a; source .env; set +a; …. Never echo values; verify presence with
[ -n "$MODEL" ]-style checks. If .env is unfilled, stop and ask — do not
accept a pasted key as an argument (it leaks into shell history and prompt
logs).
1. Intake
Ask for: book path and target --language (e.g. zh-hans, ja). The
model comes from .env. Optional: --single_translate (replace instead
of bilingual), --translation_style.
Base command used by every step below, with the route flags step 1b
settled (OpenAI shape shown — the common case):
(The conditional flag is an array on purpose: ${VAR:+--flag "$VAR"}
mis-tokenizes under zsh — macOS's default shell — into a single argv word
that argparse rejects. The array form works in bash and zsh alike.)
Prompt-file probe
Look for an existing prompt template in two places: the book's own
directory, and the repo root you run make_book.py from — prompt.json,
prompt.txt, prompt*.md, prompt_template*.
A candidate must carry a diff: in a git repo, only files that are
untracked (new) or modified against HEAD count
(git status --short -- 'prompt*') — cleanly tracked prompt* files are
the repo's shipped examples, not the user's voice, and are never offered.
The moment any such new or diff'd file is found, ask the user first —
before linting it, before building any command around it, and never
silently adopting or ignoring it. Only once the user says yes, lint it and
keep it out of git: contract and commands in
references/prompt-files.md.
1b. Endpoint probe — infer the route, then verify it (sub-cent)
Never assume a route from the key alone. Infer it from the model name,
then prove it, so a typo or a wrong shape surfaces here and not after the
classify work. Three ordered questions, each answered by a call:
0. Bind $KEY and $ROOT for the shape you are about to probe, and
refuse to curl without them. The shape names the key variable — never
scan for whichever key happens to be set, because a stale export in
~/.zshenv would silently route the run somewhere the user never asked
for. route_env below is copied verbatim from references/providers.md,
which also carries the per-shape defaults:
set -a; source .env; set +a
route_env openai # or: anthropic | gemini — sets KEY, ROOT, or exits
1. Does this model id exist here? On any OpenAI-shaped base the model
listing is free:
curl -sS "$ROOT/v1/models" -H "Authorization: Bearer $KEY" |
python3 -c 'import json,sys; d=json.load(sys.stdin)["data"]; print([m["id"] for m in d])'
If $MODEL is absent, stop and show the near matches — a typo'd id and an
unsupported path both return 404 later, and only this tells them apart.
Some gateways also return supported_endpoint_types per row; when present
that is the answer to question 2, so read it instead of guessing. A
non-OpenAI-shaped endpoint has no such listing: skip to question 2 there
and let the probe itself judge the id.
2. Which shape does it speak? Take the first candidate from the model
name — gpt-* and unknown ids → OpenAI; claude-*/gemini-* → OpenAI
first when BBM_API_BASE names a gateway, native when it is the vendor's
own endpoint — send that shape's smallest request, fall through on failure.
Use the reference's three recipes verbatim, token-cap rule included: no
cap on the OpenAI shape (a cap of 1 is rejected by gateway floors and by
o-series/gpt-5, which reads as a dead endpoint when nothing is wrong),
max_tokens mandatory on the anthropic one.
3. Which flags does that make? Record them once as the ROUTE array
every later step uses, and state the choice in one line with its reason.
Probe the format, don't ask the user to fix it: on 404 retry with /v1
added or removed; on an auth rejection try that shape's native scheme.
Whatever passes is what --api_base gets. Stop and ask only when the key
itself is rejected by its own provider.
Structured-output capability is not tested here — the run's own probe
does that at first paid use, and its verdict is not a pass/fail.
2. Plan (free — agent mode makes no API call)
Run the base command once. It partitions the whole book, writes
<book>_plan.json, prints a handoff block, and exits without translating.
How the partition works (read this before judging its report)
partition → group → plan JSON → your edits → coverage gate → translate
Partition is greedy and coverage-complete: every text node in each
rendered <body> becomes exactly one of — a translation unit, or a skip
with a structural reason (whitespace, link, symbol, hidden, ruby,
pagebreak, non-content, excluded tag). No content heuristics; the run
prints and checks the invariant
total_chars == translated + sum(skip reasons). Nothing is silently
dropped — anything questionable surfaces as a signature for you to judge.
Grouping (poetry only): sibling runs whose line median is short
are stanza-shaped; they are windowed (--poetry-group-size, default 8
lines/request) so verse gets translated with its neighbors as context.
Everything else is one request per unit — short apparatus is judged by
classification, not batched away.
Plan JSON: one row per tag signature (p.calibre_13 …) with unit
count, char total, up to 5 real samples, and an action.
Your edits, then at translate time: fail-closed validation plus a
coverage gate — if the edited plan would translate less than
--plan-min-coverage (default 0.5) of the book's text, hard stop.
lower --plan-min-coverage deliberately, say so in the plan summary
visible text under a hidden skip reason, or vice versa
inspect the epub's CSS before overriding; the resolver follows stylesheet source order
3. Classify (you are the classifier)
Read <book>_plan.json. Rows with "action": null are the plan's open
questions — every one must become "translate" or "skip", and the
translate run refuses to start while any null remains. For each: name
what the text is first (prose, verse, dialogue, heading, caption,
running head, page/line number, sigla, cross-reference label,
boilerplate, decorative marker), then rule — naming before ruling is
the same discipline the schema forces on LLM classifiers, and it prevents
rationalizing a snap verdict. Judge from samples, units, chars,
pct, mean_chars; when the samples do not settle it, choose "translate" —
over-translating is cheap, losing content is not. Want more evidence?
unzip -p <book> <file> and read the markup around the signature.
Non-null rows (prose spine, headings, poetry) may also be changed if their
samples convince you, but the nulls are the required work. Hold a non-null
override to the same name-then-rule discipline, and record every one in
the step-6 report (signature, what you named it, why you overrode) — the
user should see where you disagreed with the plan's defaults, not discover
it in the output. Edit onlyaction fields. Validation is fail-closed
— a typo'd action, missing hash, or edited book is a hard error on the
next run, not a silent default.
4. Smoke test (pennies)
Base command + --quiet --test --test_num 8 > smoke.log 2>&1. --quiet
turns off the progress bars and the per-unit source/target echoes (without
it, a --test run prints the full text of every translated unit); the
redirect catches what remains. You check results after the run, from
files — never from live output.
The run probes the endpoint's structured-output support, translates the
first 8 units of the real plan (sequentially — --test forces it), and
writes the resume cache. Verify by unzipping the partial
<book>_bilingual.epub: right target language? formatting intact
(translation carries the same tag/class as its original)? Check
smoke.log for error lines. The cache is plan-fingerprint-guarded and
carries into the full run — nothing paid here is re-paid.
A wrong-language reply and broken formatting surface here, not three
chapters into the paid run. (Key, model id and route were proven at step
1b.) What is not a failure at this step: the probe grading the endpoint
below strict and announcing the delimiter method — that is the expected
line on claude, on most proxies, and on anything not natively OpenAI. The
output is what you judge, not the probe verdict.
5. Full run
Base command + --quiet --resume, minus --test. Always in the
background with output to a log:
… --quiet --resume > run.log 2>&1
(Bash run_in_background: true; poll with tail -5 run.log.) On any crash,
rerun the identical command — resume is positional and fingerprint-guarded.
If the run stops with a fatal translation error, fix the cause (key quota,
endpoint down) and rerun; do not delete the cache unless the book or plan
changed intentionally.
Flag guide — you choose, per book
decision
flag
choose it when
route
--model openai --model_list "$MODEL"
the endpoint took the OpenAI shape at step 1b — the default for gateways, and the only route that accepts an arbitrary model id without a provider file
--model <MODEL_DICT key>
$MODEL is literally one of those keys and you want its native client (claude-…, gemini, groq, qwen)
--provider <name> --model_list "$MODEL"
a non-OpenAI shape and a custom model id — the only combination the other two cannot express (references/providers.md)
output form
(default) bilingual
user reads both languages side by side — the usual ask
--single_translate
user wants a translated-only book, original replaced
speed
(default) sequential
small book (< ~30 chapters), or first run with a new endpoint
--parallel-workers 4
large book; plan mode isolates per-chapter state, safe with or without context
consistency
--use_context
fiction with recurring names/terms; costs extra tokens; in parallel runs context is chapter-local (until the phase-2 brief exists)
voice/style
--prompt prompt.json
user states a register ("literary", "plain modern") — encode it once in the system message
bilingual output should visually separate the translation
scope
--only_filelist / --exclude_filelist
user wants specific chapters; exact internal names — a typo fails loud at the coverage gate
sampling
--temperature
leave the default unless output is erratic; then lower it and re-smoke
smoke size
--test_num 8 (default here)
raise to ~20 on poetry-heavy books so the smoke spans a full stanza window or two
log noise
--quiet (always, on smoke and full run)
bars and per-unit echoes off; reports and errors still print — logs stay readable, context stays clean
Do not pass in plan mode: --accumulated_num and
--allow_navigable_strings (both explicitly ignored — plan mode batches for
itself and accounts every node already; a non-1 accumulated_num also
disables the interrupt-save path), --batch (OpenAI batch API, untested
with plan mode), --translate-tags (plan mode overrides it), --interval
(Gemini route only, and only when it rate-limits), --plan-dry-run (it writes a plan with
every action already decided, which suppresses the null questions and the
agent handoff — the plan run of this workflow is already free).
Halt / resume — safe by construction
Progress saves after every chapter and on interrupt or crash. To halt
a background run: kill -INT <pid> (SIGINT fires the save handler; even
SIGKILL loses at most the current chapter).
SIGINT does not halt a --parallel-workers run promptly. Every
chapter is dispatched up front and the pool drains before the handler
runs, so the process exits only after all of them finish — measured
260807: a signal at 20/70 chapters done still exited having translated
70/70. Checkpoints stay correct and resume cleanly; it is the stopping
that does not work. Tell the user this before starting a big parallel run,
and use SIGKILL when a run must actually stop now.
Resume = rerun the identical command with --resume. Replay is
positional and fingerprint-guarded: same book, same plan, continues where
it stopped. This is also why the smoke test is never wasted money.
Do not edit the plan or swap the book between halt and resume — the
fingerprint refusal is deliberate, protecting against translations landing
on the wrong paragraphs. Changed your mind about a signature mid-book?
Finish the run, or delete .<book>.temp.bin and restart cleanly. Never
work around the refusal by deleting the cache without telling the user
what gets re-paid.
6. Deliver
Report the end-of-run coverage/skip stats, every classification decision
you made (the resolved nulls and any non-null overrides, with the
name-then-rule reasoning), and hand over <book>_bilingual.epub. Suggest
spot-checking one early and one late chapter.
Context hygiene
Never let a translation run stream into the conversation — every
paid run (smoke and full) gets --quietand a log-file redirect. This
applies to any command that translates, whichever step it appears in.
Everything the next step needs is on disk; nothing critical lives only in
conversation.
Compaction threshold: for a small book (smoke + full run expected to
finish within the session comfortably), do not compact at all. Only
compact when context is genuinely pressured (≳70% used), at most once,
and at the natural boundary: after plan editing, before the full run.
Compacting more often than that costs more than it saves — each compact
forces re-reading plan/log state that was already in context.
Failure modes (all fail loud by design)
symptom
meaning
doesn't apply JSON schema … using delimiter method, honors JSON schema shape but not value constraints, no strict structured-output support
not a failure. The endpoint does not do strict schema decoding, so translation uses the delimiter method instead. Every route except a strict OpenAI-shaped one prints one of these, including claude and most proxies. Note it in the report; do not switch models over it
refused the … request shape; using a simpler one
classification's ladder descended a rung. Informational
fingerprint refusal on --resume
book file or plan changed since the cache was written; delete cache only if that was intentional
undecided signature(s) on plan load
null actions remain — answer every open question in the plan JSON, then rerun
invalid action on plan load
typo in a hand-edited action — fix the JSON, rerun
coverage-gate error / empty plan
--only_filelist misspelled, or the plan skips nearly everything — re-check the plan
legacy-cache refusal
the cache came from an old tag-mode run — delete it
Next phase
references/next-phase.md in this skill dir: a book brief (short intro + character-name
glossary) drafted by the agent at classify time and injected through
prompt.json, making --parallel-workers runs terminology-consistent
without --use_context's sequential warmup. Planned, not yet built.