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
- Variables
- func DefaultDir() (string, error)
- func ReadRange(path string, offset, limit int) (Header, []Frame, int, bool, error)
- func RecordingID(runID, sessionID string, startedAt time.Time) string
- func ResolvePath(path string) (string, error)
- func Validate(config Config) error
- func ValidateFile(path string) error
- type Config
- type CreateOptions
- type Frame
- type Header
- type Kind
- type LifecycleEvent
- type Metadata
- type Multiplexer
- func (m *Multiplexer) Close() error
- func (m *Multiplexer) FinishSession(id terminal.SessionID, at time.Time, exitCode *int)
- func (m *Multiplexer) RecordInput(id terminal.SessionID, at time.Time, data []byte)
- func (m *Multiplexer) RecordOutput(id terminal.SessionID, at time.Time, data []byte)
- func (m *Multiplexer) RecordResize(id terminal.SessionID, at time.Time, size terminal.Size)
- func (m *Multiplexer) StartSession(info terminal.Info, size terminal.Size, at time.Time)
- type MultiplexerOptions
- type Scanner
- type Store
- func (s *Store) Close() error
- func (s *Store) Create(options CreateOptions) (Metadata, *Writer, error)
- func (s *Store) Delete(id string) error
- func (s *Store) Dir() string
- func (s *Store) Finish(id string, endedAt time.Time, exitCode *int) (Metadata, error)
- func (s *Store) Get(id string) (Metadata, error)
- func (s *Store) List() ([]Metadata, error)
- func (s *Store) Open(id string) (io.ReadCloser, error)
- func (s *Store) Prune() (int, error)
- type Writer
- func (w *Writer) Close() error
- func (w *Writer) Path() string
- func (w *Writer) RecordDropped(count int)
- func (w *Writer) Stats() (int64, int, bool, int)
- func (w *Writer) WriteInput(at time.Time, data []byte) error
- func (w *Writer) WriteMarker(at time.Time, label string) error
- func (w *Writer) WriteOutput(at time.Time, data []byte) error
- func (w *Writer) WriteResize(at time.Time, columns, rows int) error
- type WriterOptions
Constants ¶
const CastVersion = 2
CastVersion identifies the only asciicast representation written and read.
const (
// MetadataSchemaVersion identifies the sidecar representation on disk.
MetadataSchemaVersion = 1
)
Variables ¶
var ErrClosed = errors.New("recording writer is closed")
ErrClosed reports use of a recording writer after Close.
var ErrRecordingActive = errors.New("recording is open for writing")
ErrRecordingActive rejects mutating a transcript still being written.
var ErrRecordingNotFound = errors.New("recording not found")
ErrRecordingNotFound reports an unknown recording identity.
var ErrStoreClosed = errors.New("recording store is closed")
ErrStoreClosed reports use of a store after Close.
Functions ¶
func DefaultDir ¶
DefaultDir returns the private per-user recording directory.
func ReadRange ¶
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 ¶
RecordingID builds the filesystem-safe identity of one transcript. The start instant separates two sessions that reuse an identity within one run.
func ResolvePath ¶
ResolvePath returns an absolute recording directory, using DefaultDir for an empty configured value.
func ValidateFile ¶
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 ¶
Frame is one asciicast event. It marshals to the array form the format requires rather than to an object.
func (Frame) MarshalJSON ¶
MarshalJSON emits the asciicast array form [1.234567,"o","text"].
func (*Frame) UnmarshalJSON ¶
UnmarshalJSON parses the asciicast array form and rejects any kind outside the closed vocabulary.
type Header ¶
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.
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 ¶
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 ¶
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 ¶
RecordOutput queues terminal output. It never blocks the PTY read loop.
func (*Multiplexer) RecordResize ¶
RecordResize queues a geometry change.
func (*Multiplexer) StartSession ¶
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 ¶
NewScanner decodes the header line and prepares frame iteration.
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 ¶
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 ¶
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 ¶
Delete removes one recording and its sidecar. A transcript still being written is refused rather than silently lost.
func (*Store) Finish ¶
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) List ¶
List returns every recording newest first. A sidecar that cannot be decoded is skipped so one damaged file never hides the rest.
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) RecordDropped ¶
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 ¶
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 ¶
WriteInput appends operator input produced at the given instant.
func (*Writer) WriteMarker ¶
WriteMarker appends a bounded replay marker.
func (*Writer) WriteOutput ¶
WriteOutput appends terminal output produced at the given instant.