asciinema-claude
Turn asciinema recordings of Claude Code sessions into the ANSI scrollback a user would see.
Install
pip install asciinema-claude
For development: uv sync, then uv run pytest -q. To record test fixtures, run
scripts/install-asciinema.sh (installs asciinema 3.2.1 to tools/bin/asciinema).
Command line
asciinema-claude session.cast | less -R
asciinema-claude --no-color session.cast > session.txt
cat session.cast | asciinema-claude -
Options:
--clear-separator: mark each/clearwith a separator line.--no-color: write plain text, with no ANSI codes.--follow: keep reading while the file grows (polls every 0.25 s). It waits for the file to exist. It stops on Ctrl-C, or at the exit event ("x") that asciinema 3 writes to v3 recordings. A v2 recording has no exit event, so stop it with Ctrl-C. A line is written only when it is final: when it scrolls off the top of the screen, or when the recording ends. A short session that fits on the screen shows nothing until then. At the end, the rest of the screen is written. Not allowed with-.--version: print the version.
Exit codes: 0 on success, 1 for a file error (for example a missing file), 2 for a bad recording or bad usage,
130 for Ctrl-C (without --follow).
Library
One call:
from asciinema_claude import transcribe
text = transcribe("session.cast") # path, str, bytes or file object
text, info = transcribe("session.cast", return_info=True)
print(info.max_width, info.clears, info.reprints_removed, info.gaps)
Incremental, for a recording that is still growing:
from asciinema_claude import Transcriber
t = Transcriber(80, 24) # the cast header resets the size
for chunk in chunks_of_cast_text: # any split of the file is fine
t.feed_raw(chunk)
send(t.take_committed()) # final text, append it
show_tail(t.tail()) # live tail, see below
send(t.finish())
info = t.info() # max_width, clears, reprints_removed, gaps
Output in take_committed() is final: append it and never change it. tail() is the
live part: the screen rows that are not final yet, and lines held while a redraw is
checked. It is replaced on each update, so do not append it. It can be taller than the
screen (37 lines on a 30-row screen in the basic fixture).
Web app with xterm.js
Show the committed text in one terminal and the tail in a separate element, which you replace on each update:
// committed: new text from take_committed(); tail: tail(); width: info.max_width
const term = new Terminal({ cols: width, scrollback: 1000000 }); // default is 1000
term.write(committed); // append only; lines already end in \r\n
tailTerm.reset(); // a second Terminal (or a <pre>) under it
tailTerm.write(tail);
- Use
info.max_widthas the number of columns, so no line wraps. The width can grow while the recording grows: callterm.resize(width, term.rows)when it does. - Set
scrollbackhigh. With the default of 1000 lines, xterm.js drops the start of a long transcript. - To use one terminal only, call
term.reset()and write all committed text and then the tail on each update. This costs more for long transcripts.
A Transcriber cannot be pickled or stored between requests. A stateless backend can
feed the whole recording again on each request (about 3 MB/s on a laptop). Do not call
finish() while the recording grows. The committed text of a part of a recording is
always the start of the committed text of the whole recording. So the client can send
the length it already has, and the server returns only what is new and the tail:
def update(cast_text: str, offset: int) -> dict:
t = Transcriber(80, 24)
t.feed_raw(cast_text)
committed = t.take_committed()
return {"new": committed[offset:], "offset": len(committed), "tail": t.tail(),
"width": t.info().max_width}
Claude Code modes
- Inline mode (the default) and
"tui": "fullscreen"mode (alternate screen) give the same kind of transcript. In fullscreen mode, the library copies each frame and finds how far the conversation moved. The rows that leave the top of the viewport are kept, as in inline mode. - Fullscreen mode, scrolling back: frames that show older rows are ignored. Claude Code marks these frames with
a pill (
Jump to bottom (ctrl+End) ↓,1 new message (ctrl+End) ↓) and a sticky prompt at the top row. A frame that shows newer rows is copied, also while the user is scrolled back. Thus, if the user pages down, every row that was on the screen is kept once. The pill, the sticky prompt and the1 new messagedivider are not kept. - Fullscreen mode, overlays: the slash menu, the
@file picker and other overlays above the input box are not kept. The conversation rows behind an overlay are kept. - Fullscreen mode, an external editor (ctrl+g): the editor's screen is not kept. Only frames that show Claude Code's input box or a permission prompt are copied.
- A recording made with an idle-time limit (
asciinema rec -i) gives the same transcript as one without it, for all limits down to 0 s. - After a resize, the repaint is matched with the history by paragraph. A table drawn again at another width
(or as a
Name: valuelist, on a narrow screen) is matched by its cells, so it is kept once. The size of the table does not matter.
Known limits
- In fullscreen mode, Claude Code draws only the viewport. Rows that were never on the screen are not in the
recording, so they cannot be in the transcript. Three cases cause this. The user jumps to the bottom after
rows arrived while the view was scrolled back. More than one screen of rows arrives in one frame. An overlay
hid the rows. At a jump, the transcript has this line where rows can be missing:
⋯ rows may be missing here: they were never on the screen.info.gapscounts these lines. Rows that only an overlay hid are not marked. - In fullscreen mode, if the viewport shows only repeated rows (for example a log of equal lines), the frames do not show how many rows left the top. The library takes the smallest number that agrees with the frames, and writes the gap line once for each such stretch. Some repeated rows can be missing. Other rows are not lost.
- In fullscreen mode, an overlay above the input box is found by its form: indented rows with a blank row above them. An overlay of another form is kept as text. The library prefers this to the loss of a conversation row.
- A table drawn again after a resize is matched by its cells. If the repaint starts so that only the end of one cell value is on the screen, that part is not matched, and it is kept twice. A duplicate is preferred over losing content.
Metadata
Release files for asciinema-claude 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| asciinema_claude-0.1.0.tar.gz | 41.1 kB | With provenance | Details for asciinema_claude-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| asciinema_claude-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for asciinema_claude-0.1.0-py3-none-any.whl |
Total release size: 86.8 kB
Release files / asciinema_claude-0.1.0.tar.gz
| Download URL | asciinema_claude-0.1.0.tar.gz |
|---|---|
| Size | 41.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
da8bc0685823f009cce0e5a5ad2e601fcf8935a461612b92ec4cac2d1cb63d35
|
|
BLAKE2b-256 checksum How to use checksums |
d4f5886f368784ece2e96b9b242bd66820e15bbc748f58b6f433dfac468102ac
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.
Transparency logRelease files / asciinema_claude-0.1.0-py3-none-any.whl
| Download URL | asciinema_claude-0.1.0-py3-none-any.whl |
|---|---|
| Size | 45.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2044e019d685153c5b78c18d446ec0dbd4a972c0290c443459c97e9cd9a60a5a
|
|
BLAKE2b-256 checksum How to use checksums |
5754377c6576fff483881f5a45e0763849f8866954b79781d0236d83c81e2d1d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.
Transparency logSecurity for asciinema-claude 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for asciinema_claude-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.