| name | cast-edit |
|---|---|
| description | Edit and render asciinema / asciicast terminal recordings (.cast files): trim idle/dead time, speed up regions or detected typing runs, and render a .cast to a GIF with the agg flags that avoid Claude Code glyph artifacts (tofu auto-mode arrows, a stray block under the first typed char). Use this whenever the user wants to tighten, shorten, trim pauses from, speed up, convert, or render a terminal recording or the .cast behind a terminal GIF (agg/asciinema), even if they don't say "asciicast". Phrases like "trim the pauses", "this demo GIF is too slow", "convert this cast to a gif", or "the arrows in my gif look broken" should trigger it. |
Post-process asciicast v3 recordings: remove idle windows and speed up chosen
regions. The engine is scripts/castedit.py. Modern asciinema (3.x) records v3 by
default; if you have a v2 cast, convert it first with
asciinema convert old.cast new.cast (writes v3).
In v3 each event line is [interval, code, data] where interval is seconds since
the previous event. Editing time is pure arithmetic on interval — there are no
absolute timestamps to recompute. So trimming and speeding are robust and lossless to
the rendered output.
The cast never goes into your context. A real recording can be megabytes of ANSI
output. castedit.py analyze does the parsing and hands you a few-hundred-token
summary; you reason over that. Only run show (a small slice) if you must inspect raw
events. Never Read a whole .cast to plan an edit — that defeats the design.
-
Analyze. Get the timeline, idle gaps, and detected typing runs:
uv run python "${CLAUDE_SKILL_DIR}/scripts/castedit.py" analyze RECORDING.castTune thresholds if the defaults miss things:
--idle-threshold 0.7,--type-max-gap 0.4,--min-keystrokes 3. -
Decide regions, then verify. Idle gaps map directly to an
idle_cap. For "speed up the typing" (or similar criteria), the typing runs in the analyze output are candidates — each comes with atext~snippet reconstructed from the events. Read those snippets to confirm a run is really the kind of region the user means before speeding it up. If a snippet is ambiguous, inspect it precisely:uv run python "${CLAUDE_SKILL_DIR}/scripts/castedit.py" show RECORDING.cast --start 2.4 --end 3.9 -
Propose the plan to the user in plain terms (which gaps capped, which regions sped up and by how much, before/after duration estimate) and get approval. Editing a recording is the kind of change worth confirming before applying.
-
Write the plan and apply it:
uv run python "${CLAUDE_SKILL_DIR}/scripts/castedit.py" edit RECORDING.cast RECORDING.edited.cast --plan plan.jsonFor the trivial "just cap idle" case you can skip the plan file:
... edit IN OUT --idle-cap 0.5. -
Validate, then report. Always confirm the output is a parseable cast — a bad interval can silently corrupt it:
asciinema convert RECORDING.edited.cast /tmp/_v.txt --overwriteThe
editcommand already prints the before/after duration. If the user is making a GIF, render with the renderer and font flags that avoid agg's two Claude-Code artifacts — a stray block bleeding under the first typed character, and tofu auto-mode arrows:agg --renderer resvg \ --text-font-family "JetBrains Mono,Fira Code,SF Mono,Menlo,Consolas,DejaVu Sans Mono,Liberation Mono,STIX Two Math" \ RECORDING.edited.cast out.gifWhy: agg's default
swashrenderer composites incrementally and leaves the reverse-video placeholder cursor under the first letter;resvgclears cells correctly.--text-font-familyextends (not replaces) the fallback list, so the arrows (U+23F5) resolve via STIX Two Math while keeping agg's normal glyph fallbacks. On a machine without STIX Two Math, substitute any font carrying U+23F5 (fc-list ":charset=23f5").
All fields optional; times are in original-timeline seconds (the same numbers
analyze and show print), so a region you saw in analyze pastes straight in.
{
"idle_cap": 0.5,
"speed_regions": [
{"start": 2.40, "end": 3.85, "factor": 2.0}
],
"cuts": [
{"start": 40.0, "end": 55.0}
],
"resume_gap": 0.1
}idle_cap(s): clamp every gap longer than this to this value. The simplest "remove dead air" knob; one global threshold handles most cases.speed_regions: each event whose firing time is in[start, end]has its interval divided byfactor.factor> 1 is faster, < 1 slower. This is how both "speed up 2.4s–3.85s" (explicit bounds) and "speed up the typing" (bounds taken from a detected run) are expressed. Keep regions non-overlapping; the first match wins.cuts: drop every event in[start, end]and the time it occupied. Use only for self-contained boring/idle stretches — cuts remove the output those events drew, so cutting mid-render can leave the screen in a wrong state. The gap left behind is closed toresume_gapseconds (default 0.1).
Application order per event: cut (drop) → idle_cap → speed_regions.
- Typing detection uses cadence, not payload size: a run of consecutive events spaced
--type-min-gap..--type-max-gapapart (default 0.04–0.5s), at least--min-keystrokeslong. Output bursts (~0s gaps) and idle (one big gap) are excluded by construction. Snippets are approximate because terminals redraw — verify, don't trust blindly. - The engine rounds intervals to millisecond precision with error diffusion (so total
duration stays accurate) and clamps at 0. This matters: asciinema's v3 parser rejects
a
-0.0interval with the misleading errorinvalid digit found in string, and naive rounding can produce one. The script already guards against this; preserve that clamp if you modify it. analyzelists every idle gap and typing run by default, so you never plan an edit blind to part of the recording. On a very long cast that output can get large; pass--limit Nto cap it to the N biggest, but prefer scoping with a future segment/zoom step over hiding rows.