Packages
Multi-surface application runtime for Elixir. One TEA module renders to terminal, browser (LiveView), SSH, and MCP (agents). 30+ widgets, flexbox + CSS grid, AI agent runtime, distributed swarm with CRDTs, time-travel debugging, session recording, sandboxed REPL, and agentic commerce.
Current section
Files
Jump to
Current section
Files
lib/mix/tasks/raxol.replay.ex
defmodule Mix.Tasks.Raxol.Replay do
@moduledoc """
Replays a recorded Raxol session from an asciicast (.cast) file.
## Usage
mix raxol.replay demo.cast
mix raxol.replay demo.cast --speed 2.0
mix raxol.replay demo.cast --speed 0.5
mix raxol.replay demo.cast --no-interactive
## Options
* `--speed` / `-s` - Playback speed multiplier (default: 1.0).
2.0 plays at double speed, 0.5 at half speed.
* `--max-delay` - Maximum pause between frames in seconds (default: 5.0).
Prevents long idle gaps from stalling replay.
* `--no-interactive` - Disable keyboard controls (simple playback).
* `--info` - Print recording info without playing.
## Controls (interactive mode)
* `space` - Pause / resume
* `+` / `-` - Increase / decrease speed
* `>` / `<` - Skip forward / backward 5 seconds
* `0`..`9` - Jump to 0%-90% of recording
* `q` / `ESC` - Quit
Files are asciinema v2 format, compatible with `asciinema play`.
"""
use Mix.Task
alias Raxol.Recording.{Asciicast, Player, Session}
@shortdoc "Replay a recorded .cast session"
@switches [
speed: :float,
max_delay: :float,
info: :boolean,
interactive: :boolean
]
@aliases [s: :speed]
@impl Mix.Task
def run(args) do
case OptionParser.parse(args, strict: @switches, aliases: @aliases) do
{opts, [path], _} ->
unless File.exists?(path) do
Mix.raise("File not found: #{path}")
end
if Keyword.get(opts, :info, false) do
print_info(path)
else
replay(path, opts)
end
_ ->
print_usage()
end
end
defp replay(path, opts) do
speed = Keyword.get(opts, :speed, 1.0)
max_delay = Keyword.get(opts, :max_delay, 5.0)
interactive = Keyword.get(opts, :interactive, true)
speed_label = if speed != 1.0, do: " (#{speed}x)", else: ""
Mix.shell().info([:cyan, "Replaying #{path}#{speed_label}...", :reset])
if interactive do
Mix.shell().info("Controls: space=pause +/-=speed </>:seek q=quit\n")
else
Mix.shell().info("Press Ctrl+C to stop.\n")
end
Process.sleep(500)
case Player.play(path,
speed: speed,
max_delay: max_delay,
interactive: interactive
) do
:ok -> :ok
{:error, reason} -> Mix.shell().error("Replay failed: #{inspect(reason)}")
end
Mix.shell().info([:green, "\nReplay complete.", :reset])
end
defp print_info(path) do
case Asciicast.read(path) do
{:ok, session} ->
do_print_info(path, session)
{:error, reason} ->
Mix.raise("Failed to read #{path}: #{inspect(reason)}")
end
end
defp do_print_info(path, session) do
Mix.shell().info([:bright, "Recording: ", :reset, path])
Mix.shell().info(" Size: #{session.width}x#{session.height}")
Mix.shell().info(
" Duration: #{Float.round(Session.duration(session), 1)}s"
)
Mix.shell().info(" Events: #{Session.event_count(session)}")
if session.title do
Mix.shell().info(" Title: #{session.title}")
end
if session.command do
Mix.shell().info(" Command: #{session.command}")
end
Mix.shell().info(" Recorded: #{DateTime.to_string(session.started_at)}")
env_term = get_in(session.env, ["TERM"]) || "unknown"
env_shell = get_in(session.env, ["SHELL"]) || "unknown"
Mix.shell().info(" Terminal: #{env_term}")
Mix.shell().info(" Shell: #{env_shell}")
end
defp print_usage do
Mix.shell().error("Usage: mix raxol.replay FILE [options]")
Mix.shell().error("")
Mix.shell().error("Options:")
Mix.shell().error(" --speed FLOAT Playback speed (default: 1.0)")
Mix.shell().error(
" --max-delay SECS Max pause between frames (default: 5.0)"
)
Mix.shell().error(" --no-interactive Disable keyboard controls")
Mix.shell().error(
" --info Print recording info without playing"
)
Mix.shell().error("")
Mix.shell().error("Example: mix raxol.replay demo.cast --speed 2.0")
end
end