TTYCast

TTYCast records terminal sessions into seekable, compressed .ttycast files for Elixir and Erlang applications.

It is useful when you need more than a plain terminal log: fast snapshots, timestamp seeking, safe input handling, and application-specific timeline events.

Why TTYCast?

Install

def deps do
  [
    {:ttycast, "~> 0.1.0"}
  ]
end

Record a command

mix ttycast.record --output /tmp/demo.ttycast -- sh -lc 'echo hello'

Inspect it:

mix ttycast.info /tmp/demo.ttycast
mix ttycast.snapshot /tmp/demo.ttycast
mix ttycast.find /tmp/demo.ttycast hello

Record an interactive command in your current terminal:

mix ttycast.rec --output /tmp/shell.ttycast -- bash

Raw input is not recorded by default. To opt in for disposable/debug sessions:

mix ttycast.rec --output /tmp/shell.ttycast --input raw -- bash

Use from Elixir

For most applications, use scoped writer lifecycle:

TTYCast.write("/tmp/demo.ttycast", [width: 120, height: 40], fn writer ->
  TTYCast.Writer.write(writer, "hello\r\n")
  TTYCast.Writer.marker(writer, :checkpoint, %{label: "first screen"})
end)

Read and seek:

cast = TTYCast.open!("/tmp/demo.ttycast")

TTYCast.info(cast)
TTYCast.snapshot!(cast, time_ms: 1_000)
TTYCast.stream(cast) |> Enum.to_list()
TTYCast.export(cast, :asciinema, "/tmp/demo.cast")

Stream existing IO into a recording:

TTYCast.write("/tmp/log.ttycast", [width: 120, height: 40], fn writer ->
  File.stream!("app.log")
  |> Enum.into(TTYCast.into(writer))
end)

Input policy

TTYCast defaults to redacted input:

TTYCast.Writer.input(writer, "secret")
# records {:input_redacted, t_us, 6}

Available policies:

Set policy when starting a writer:

TTYCast.start_writer(path: path, width: 80, height: 24, input_policy: :none)

Recovery

Writers maintain a live sidecar index while recording. If a process crashes before the final trailer/footer is written, TTYCast can still open the file through that live index when available.

If the live index is missing but chunks are intact, rebuild the trailer/footer:

mix ttycast.reindex /tmp/demo.ttycast

or:

TTYCast.reindex("/tmp/demo.ttycast")

Benchmarks

Run a local benchmark comparing .ttycast, asciinema JSONL, and gzipped asciinema JSONL sizes plus open/seek timings:

mix ttycast.bench --events 10000

Format

See FORMAT.md for the binary container layout and event schema.