record

package
v0.8.19 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 9, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package record writes and reads asciicast v2 transcripts so an interactive terminal session can be replayed later for audit or training. It owns the on-disk recording layout and never interprets the bytes a session produced.

Index

Constants

View Source
const CastVersion = 2

CastVersion identifies the only asciicast representation written and read.

View Source
const (
	// MetadataSchemaVersion identifies the sidecar representation on disk.
	MetadataSchemaVersion = 1
)

Variables

View Source
var ErrClosed = errors.New("recording writer is closed")

ErrClosed reports use of a recording writer after Close.

View Source
var ErrRecordingActive = errors.New("recording is open for writing")

ErrRecordingActive rejects mutating a transcript still being written.

View Source
var ErrRecordingNotFound = errors.New("recording not found")

ErrRecordingNotFound reports an unknown recording identity.

View Source
var ErrStoreClosed = errors.New("recording store is closed")

ErrStoreClosed reports use of a store after Close.

Functions

func DefaultDir

func DefaultDir() (string, error)

DefaultDir returns the private per-user recording directory.

func ReadRange

func ReadRange(path string, offset, limit int) (Header, []Frame, int, bool, error)

ReadRange returns at most limit frames starting at the zero-based frame index offset, the index to resume from, and whether the transcript ended within this page.

func RecordingID

func RecordingID(runID, sessionID string, startedAt time.Time) string

RecordingID builds the filesystem-safe identity of one transcript. The start instant separates two sessions that reuse an identity within one run.

func ResolvePath

func ResolvePath(path string) (string, error)

ResolvePath returns an absolute recording directory, using DefaultDir for an empty configured value.

func Validate

func Validate(config Config) error

Validate rejects ambiguous or unbounded recording settings.

func ValidateFile

func ValidateFile(path string) error

ValidateFile reports whether a path holds a transcript Relayer can replay: a known header, non-negative monotonic offsets, and only known kinds.

Types

type Config

type Config struct {
	Enabled        bool   `json:"enabled" yaml:"enabled"`
	Path           string `json:"path" yaml:"path"`
	RecordInput    bool   `json:"record_input" yaml:"record_input"`
	Redact         bool   `json:"redact" yaml:"redact"`
	MaxFileSizeMB  int    `json:"max_file_size_mb" yaml:"max_file_size_mb"`
	MaxRecordings  int    `json:"max_recordings" yaml:"max_recordings"`
	MaxTotalSizeMB int    `json:"max_total_size_mb" yaml:"max_total_size_mb"`
	RetentionDays  int    `json:"retention_days" yaml:"retention_days"`
}

Config controls local session recording. It is disabled by default because a transcript retains far more than the audit trail does.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig leaves recording off and keeps redaction on, so enabling the feature never silently captures typed secrets.

type CreateOptions

type CreateOptions struct {
	RunID     string
	SessionID string
	AgentID   string
	Name      string
	Backend   string
	Adapter   string
	Title     string
	Width     int
	Height    int
	StartedAt time.Time
}

CreateOptions describes the session a new transcript belongs to.

type Frame

type Frame struct {
	Offset float64
	Kind   Kind
	Data   string
}

Frame is one asciicast event. It marshals to the array form the format requires rather than to an object.

func (Frame) MarshalJSON

func (f Frame) MarshalJSON() ([]byte, error)

MarshalJSON emits the asciicast array form [1.234567,"o","text"].

func (*Frame) UnmarshalJSON

func (f *Frame) UnmarshalJSON(data []byte) error

UnmarshalJSON parses the asciicast array form and rejects any kind outside the closed vocabulary.

type Header struct {
	Version   int               `json:"version"`
	Width     int               `json:"width"`
	Height    int               `json:"height"`
	Timestamp int64             `json:"timestamp,omitempty"`
	Title     string            `json:"title,omitempty"`
	Env       map[string]string `json:"env,omitempty"`
}

Header is the first line of a .cast file.

func ReadHeader

func ReadHeader(reader io.Reader) (Header, error)

ReadHeader decodes only the header line of a transcript stream.

type Kind

type Kind string

Kind is the closed asciicast event vocabulary.

const (
	KindOutput Kind = "o"
	KindInput  Kind = "i"
	KindResize Kind = "r"
	KindMarker Kind = "m"
)

type LifecycleEvent added in v0.8.3

type LifecycleEvent struct {
	// Finished is false when the transcript opened and true when it closed.
	Finished bool
	// Metadata is the store's description of the transcript. When Err is set
	// it may hold no more than the session the transcript was opened for.
	Metadata Metadata
	// Err is set when the store could not open or finalize the transcript.
	Err error
}

LifecycleEvent reports one transcript opening or closing. It carries Metadata, which describes a transcript's identity and shape and has no field for a frame of its content.

type Metadata

type Metadata struct {
	SchemaVersion int       `json:"schema_version"`
	ID            string    `json:"id"`
	RunID         string    `json:"run_id"`
	SessionID     string    `json:"session_id"`
	AgentID       string    `json:"agent_id,omitempty"`
	Name          string    `json:"name,omitempty"`
	Backend       string    `json:"backend,omitempty"`
	Adapter       string    `json:"adapter,omitempty"`
	StartedAt     time.Time `json:"started_at"`
	EndedAt       time.Time `json:"ended_at,omitempty"`
	Width         int       `json:"width"`
	Height        int       `json:"height"`
	Bytes         int64     `json:"bytes"`
	Frames        int       `json:"frames"`
	Truncated     bool      `json:"truncated"`
	DroppedFrames int       `json:"dropped_frames"`
	InputRecorded bool      `json:"input_recorded"`
	Redacted      bool      `json:"redacted"`
	ExitCode      *int      `json:"exit_code,omitempty"`
	Active        bool      `json:"active"`
	Path          string    `json:"path,omitempty"`
}

Metadata is the sidecar a replay interface reads without parsing the whole transcript. Path is recomputed on every read so a moved directory still resolves.

type Multiplexer

type Multiplexer struct {
	// contains filtered or unexported fields
}

Multiplexer implements terminal.Recorder by owning one goroutine and one bounded queue per session. Every method is safe on a nil receiver, so a caller never needs a nil check around recording.

func NewMultiplexer

func NewMultiplexer(options MultiplexerOptions) *Multiplexer

NewMultiplexer returns nil when recording is disabled or has no store; the nil-safe methods make that an ordinary, silent no-op for every caller.

func (*Multiplexer) Close

func (m *Multiplexer) Close() error

Close finishes every open session and joins their goroutines.

func (*Multiplexer) FinishSession

func (m *Multiplexer) FinishSession(id terminal.SessionID, at time.Time, exitCode *int)

FinishSession closes the queue and returns immediately. The session goroutine drains what is still queued before it closes the transcript and rewrites the sidecar; Close joins that work.

func (*Multiplexer) RecordInput

func (m *Multiplexer) RecordInput(id terminal.SessionID, at time.Time, data []byte)

RecordInput queues operator input when the configuration allows it. With redaction on, printable bytes are masked so a transcript keeps the shape of what was typed without keeping the secret itself.

func (*Multiplexer) RecordOutput

func (m *Multiplexer) RecordOutput(id terminal.SessionID, at time.Time, data []byte)

RecordOutput queues terminal output. It never blocks the PTY read loop.

func (*Multiplexer) RecordResize

func (m *Multiplexer) RecordResize(id terminal.SessionID, at time.Time, size terminal.Size)

RecordResize queues a geometry change.

func (*Multiplexer) StartSession

func (m *Multiplexer) StartSession(info terminal.Info, size terminal.Size, at time.Time)

StartSession opens a transcript for a session. The store is only touched by the session goroutine, so no filesystem work happens under a mutex a PTY read loop can wait on.

type MultiplexerOptions

type MultiplexerOptions struct {
	Store       *Store
	Config      Config
	RunID       string
	Diagnostics io.Writer
	QueueSize   int
	// Lifecycle, when set, is told each time a transcript opens or closes, so
	// the run can journal it. It is called on the session's own goroutine and
	// never under a lock a PTY read loop can wait on. Close returns only after
	// the last call has.
	Lifecycle func(LifecycleEvent)
}

MultiplexerOptions configures the recorder bridge between the terminal backends and the store.

type Scanner

type Scanner struct {
	// contains filtered or unexported fields
}

Scanner streams a transcript one frame at a time. The header is decoded by NewScanner, so Header is available before the first Next.

func NewScanner

func NewScanner(reader io.Reader) (*Scanner, error)

NewScanner decodes the header line and prepares frame iteration.

func (*Scanner) Err

func (s *Scanner) Err() error

Err returns the first decoding error encountered.

func (*Scanner) Frame

func (s *Scanner) Frame() Frame

Frame returns the frame decoded by the last successful Next.

func (*Scanner) Header

func (s *Scanner) Header() Header

Header returns the decoded transcript header.

func (*Scanner) Next

func (s *Scanner) Next() bool

Next advances to the next frame, skipping blank separator lines. It reports false at the end of the transcript and on the first malformed line.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store owns the on-disk recording layout: one directory per run holding one transcript and one sidecar per session.

func OpenStore

func OpenStore(config Config) (*Store, error)

OpenStore prepares the private recording directory. It is usable while recording is disabled so a replay interface can still list past sessions.

func (*Store) Close

func (s *Store) Close() error

Close closes every transcript still open. It does not finalize their sidecars: an unfinished sidecar is exactly how a crash is reported.

func (*Store) Create

func (s *Store) Create(options CreateOptions) (Metadata, *Writer, error)

Create reserves a transcript, writes its opening sidecar and returns a writer the caller owns until Finish.

func (*Store) Delete

func (s *Store) Delete(id string) error

Delete removes one recording and its sidecar. A transcript still being written is refused rather than silently lost.

func (*Store) Dir

func (s *Store) Dir() string

Dir returns the absolute recording directory.

func (*Store) Finish

func (s *Store) Finish(id string, endedAt time.Time, exitCode *int) (Metadata, error)

Finish closes the transcript and rewrites its sidecar with the final counters, so a crash is the only way a sidecar keeps no EndedAt.

func (*Store) Get

func (s *Store) Get(id string) (Metadata, error)

Get returns one recording's metadata.

func (*Store) List

func (s *Store) List() ([]Metadata, error)

List returns every recording newest first. A sidecar that cannot be decoded is skipped so one damaged file never hides the rest.

func (*Store) Open

func (s *Store) Open(id string) (io.ReadCloser, error)

Open returns the transcript bytes of one recording.

func (*Store) Prune

func (s *Store) Prune() (int, error)

Prune enforces the retention limits oldest first and returns how many recordings it removed. A transcript currently open for writing is skipped: removing it loses the live session on Unix and fails outright on Windows.

type Writer

type Writer struct {
	// contains filtered or unexported fields
}

Writer appends asciicast frames to a private file under a hard byte cap. It is safe for concurrent use and never grows past MaxBytes.

func NewWriter

func NewWriter(options WriterOptions) (*Writer, error)

NewWriter creates the transcript file and writes its header line. The file must not already exist: a recording never adopts a foreign file.

func (*Writer) Close

func (w *Writer) Close() error

Close syncs and closes the transcript once.

func (*Writer) Path

func (w *Writer) Path() string

Path returns the transcript file path.

func (*Writer) RecordDropped

func (w *Writer) RecordDropped(count int)

RecordDropped accounts for frames a caller could not hand over. The count reaches the sidecar through Stats so the loss is never invisible.

func (*Writer) Stats

func (w *Writer) Stats() (int64, int, bool, int)

Stats reports the bytes written, the frames appended, whether the byte cap stopped the transcript, and how many frames were dropped before it.

func (*Writer) WriteInput

func (w *Writer) WriteInput(at time.Time, data []byte) error

WriteInput appends operator input produced at the given instant.

func (*Writer) WriteMarker

func (w *Writer) WriteMarker(at time.Time, label string) error

WriteMarker appends a bounded replay marker.

func (*Writer) WriteOutput

func (w *Writer) WriteOutput(at time.Time, data []byte) error

WriteOutput appends terminal output produced at the given instant.

func (*Writer) WriteResize

func (w *Writer) WriteResize(at time.Time, columns, rows int) error

WriteResize appends a terminal geometry change.

type WriterOptions

type WriterOptions struct {
	Path     string
	Header   Header
	MaxBytes int64
	Clock    func() time.Time
	Base     time.Time
}

WriterOptions configures one streaming asciicast emitter. Base anchors every offset; Clock only times a call that supplies no instant of its own.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL