termsession

package
v0.0.34 Latest Latest
Warning

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

Go to latest
Published: May 12, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

README

termsession

Terminal session recording and playback in asciinema v2 format. Records PTY I/O with timing information, supports compression, secret redaction, and playback control.

Usage Examples

Recording a Session
package main

import (
	"log"

	"github.com/deepnoodle-ai/wonton/termsession"
)

func main() {
	// Create a session that records a bash shell
	session, err := termsession.NewSession(termsession.SessionOptions{
		Command: []string{"bash"},
	})
	if err != nil {
		log.Fatal(err)
	}
	defer session.Close()

	// Start recording to file
	opts := termsession.RecordingOptions{
		Compress:      true,  // gzip compression
		RedactSecrets: true,  // redact passwords/tokens
		Title:         "Demo Session",
		IdleTimeLimit: 2.0,   // cap idle time at 2 seconds
	}

	if err := session.Record("session.cast", opts); err != nil {
		log.Fatal(err)
	}

	// Wait for session to complete
	if err := session.Wait(); err != nil {
		log.Printf("Session exited with error: %v", err)
	}

	log.Printf("Exit code: %d", session.ExitCode())
}
Standalone Recording
// Record output from any source
recorder, err := termsession.NewRecorder("output.cast", 80, 24, termsession.RecordingOptions{
	Compress: true,
})
if err != nil {
	log.Fatal(err)
}
defer recorder.Close()

// Record terminal output
recorder.RecordOutput("Hello, World!\r\n")
recorder.RecordOutput("\x1b[32mGreen text\x1b[0m\r\n")

// Pause/resume recording
recorder.Pause()
// ... do something you don't want recorded ...
recorder.Resume()

// Flush to disk
recorder.Flush()
Playing Back a Recording
package main

import (
	"log"

	"github.com/deepnoodle-ai/wonton/termsession"
)

func main() {
	// Load and play a recording
	player, err := termsession.NewPlayer("session.cast", termsession.PlayerOptions{
		Speed:   1.5,    // 1.5x speed
		Loop:    false,  // don't loop
		MaxIdle: 2.0,    // cap idle time at 2 seconds
	})
	if err != nil {
		log.Fatal(err)
	}

	// Get recording metadata
	header := player.GetHeader()
	log.Printf("Recording: %s (%dx%d)", header.Title, header.Width, header.Height)
	log.Printf("Duration: %.2f seconds", player.GetDuration())

	// Play blocks until complete
	if err := player.Play(); err != nil {
		log.Fatal(err)
	}
}
Interactive Playback Control
// Create player
player, err := termsession.NewPlayer("session.cast", termsession.PlayerOptions{
	Speed: 1.0,
})
if err != nil {
	log.Fatal(err)
}

// Start playback in goroutine
go func() {
	if err := player.Play(); err != nil {
		log.Printf("Playback error: %v", err)
	}
}()

// Control playback
time.Sleep(2 * time.Second)
player.Pause()

time.Sleep(1 * time.Second)
player.Resume()

// Change speed on the fly
player.SetSpeed(2.0)

// Seek to position
player.Seek(10.0) // jump to 10 seconds

// Stop playback
player.Stop()
Loading and Analyzing Recordings
// Load recording for analysis
header, events, err := termsession.LoadCastFile("session.cast")
if err != nil {
	log.Fatal(err)
}

log.Printf("Terminal size: %dx%d", header.Width, header.Height)
log.Printf("Recorded: %s", time.Unix(header.Timestamp, 0))
log.Printf("Total events: %d", len(events))

// Calculate duration
duration := termsession.Duration(events)
log.Printf("Duration: %.2f seconds", duration)

// Filter to output events only
outputEvents := termsession.OutputEvents(events)
log.Printf("Output events: %d", len(outputEvents))

// Process events
for _, event := range outputEvents {
	log.Printf("[%.3fs] %s", event.Time, event.Data)
}

API Reference

Session Types
Type Description
Session Interactive PTY session with recording capability
SessionOptions Configuration for creating a session
RecordingOptions Configuration for recording behavior
Recorder Standalone recorder for terminal output
Player Playback engine with control capabilities
PlayerOptions Configuration for playback behavior
RecordingHeader Metadata from asciinema v2 header
RecordingEvent Single recording event with timing
Session Functions
Function Description Inputs Outputs
NewSession Creates a new PTY session SessionOptions *Session, error
Session.Record Starts session and records to file filename string, opts RecordingOptions error
Session.Start Starts session without recording none error
Session.Wait Blocks until session ends none error
Session.Close Terminates session and cleans up none error
Session.ExitCode Returns exit code after Wait none int
Session.Resize Changes terminal dimensions width, height int error
Session.PauseRecording Pauses recording none none
Session.ResumeRecording Resumes recording none none
Session.IsRecording Checks if recording is active none bool
Recorder Functions
Function Description Inputs Outputs
NewRecorder Creates standalone recorder filename string, width, height int, opts RecordingOptions *Recorder, error
RecordOutput Records terminal output data string none
RecordInput Records user input data string none
Pause Pauses recording none none
Resume Resumes recording none none
IsPaused Checks pause state none bool
UpdateSize Updates terminal dimensions width, height int none
Flush Writes buffered data to disk none error
Close Finalizes recording none error
Player Functions
Function Description Inputs Outputs
NewPlayer Creates player from file filename string, opts PlayerOptions *Player, error
Play Starts playback (blocks) none error
Pause Pauses playback none none
Resume Resumes playback none none
TogglePause Toggles pause state none none
IsPaused Checks pause state none bool
Stop Stops playback completely none none
SetSpeed Changes playback speed speed float64 none
Speed Gets current speed none float64
SetLoop Enables/disables looping loop bool none
Seek Jumps to time offset seconds float64 none
GetHeader Returns recording metadata none RecordingHeader
GetDuration Returns total duration none float64
GetPosition Returns current position none float64
GetProgress Returns progress (0.0-1.0) none float64
EventCount Returns total events none int
Utility Functions
Function Description Inputs Outputs
LoadCastFile Loads recording from file filename string *RecordingHeader, []RecordingEvent, error
LoadCast Loads recording from reader r io.Reader *RecordingHeader, []RecordingEvent, error
Duration Calculates total duration events []RecordingEvent float64
OutputEvents Filters to output events only events []RecordingEvent []RecordingEvent
DefaultRecordingOptions Returns default recording options none RecordingOptions
DefaultPlayerOptions Returns default player options none PlayerOptions
  • terminal - Low-level terminal control and ANSI sequences
  • termtest - Terminal output testing with screen simulation
  • tui - Declarative terminal UI framework

Documentation

Overview

Package termsession provides terminal session recording and playback in asciinema v2 format.

This package enables you to record interactive terminal sessions (PTY), save them as .cast files (asciinema v2 format), and play them back with accurate timing. It's ideal for creating terminal demos, testing terminal applications, and building CLI tutorials.

Recording Sessions

Record an interactive PTY session:

session, _ := termsession.NewSession(termsession.SessionOptions{
    Command: []string{"bash"},
})
session.Record("session.cast", RecordingOptions{
    Compress: true,
    Title: "My Demo",
})
session.Wait()

Record directly without a PTY:

recorder, _ := termsession.NewRecorder("output.cast", 80, 24, RecordingOptions{})
recorder.RecordOutput("Hello, World!\n")
recorder.Close()

Playing Back Sessions

Play back a recorded session with timing preserved:

player, _ := termsession.NewPlayer("session.cast", PlayerOptions{
    Speed: 2.0,    // 2x speed
    MaxIdle: 1.0,  // Cap idle time at 1 second
})
player.Play() // Blocks until complete

Control playback dynamically:

go player.Play()
time.Sleep(time.Second)
player.Pause()
time.Sleep(time.Second)
player.Resume()
player.SetSpeed(3.0)

Loading and Analyzing Recordings

Load a .cast file to inspect or process events:

header, events, _ := termsession.LoadCastFile("session.cast")
fmt.Printf("Duration: %.2fs\n", termsession.Duration(events))
fmt.Printf("Terminal size: %dx%d\n", header.Width, header.Height)

Filter and process events:

outputOnly := termsession.OutputEvents(events)
for _, event := range outputOnly {
    fmt.Printf("[%.2fs] %s", event.Time, event.Data)
}

Format Details

The package uses asciinema v2 format (.cast files), which is a simple JSON-based format:

  • First line: JSON header with metadata (version, dimensions, title, etc.)
  • Subsequent lines: JSON arrays [time, type, data] representing events
  • Supports gzip compression automatically
  • Compatible with asciinema.org and other asciinema tools

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrPlayerStopped = errors.New("player has been stopped and cannot be restarted")

ErrPlayerStopped is returned when Play() is called on a stopped player.

Functions

func Duration

func Duration(events []RecordingEvent) float64

Duration returns the total duration of a recording in seconds.

The duration is determined by the timestamp of the last event. Returns 0 if there are no events.

Example

ExampleDuration demonstrates calculating recording duration.

events := []RecordingEvent{
	{Time: 0.0, Type: "o", Data: "Start"},
	{Time: 1.5, Type: "o", Data: "Middle"},
	{Time: 3.0, Type: "o", Data: "End"},
}

duration := Duration(events)
fmt.Printf("Duration: %.1f seconds\n", duration)
Output:
Duration: 3.0 seconds

func LoadCast

func LoadCast(r io.Reader) (*RecordingHeader, []RecordingEvent, error)

LoadCast loads a .cast recording from an io.Reader.

This function automatically detects and handles gzip compression by checking the magic bytes. It parses the asciinema v2 format: first line is a JSON header, subsequent lines are JSON arrays representing events.

Malformed events are silently skipped to handle recordings with corruption.

Use this when you have a recording in memory or from a network stream. For loading from a file path, use LoadCastFile instead.

func LoadCastFile

func LoadCastFile(filename string) (*RecordingHeader, []RecordingEvent, error)

LoadCastFile loads a .cast file from disk and returns its contents.

The file is automatically decompressed if it's gzip-compressed. This is a convenience wrapper around LoadCast that opens the file for you.

Returns:

  • RecordingHeader: The file metadata (dimensions, title, etc.)
  • []RecordingEvent: All events in chronological order
  • error: Any error encountered while loading

Example:

header, events, err := termsession.LoadCastFile("recording.cast")
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Recording is %.2f seconds long\n", events[len(events)-1].Time)
Example

ExampleLoadCastFile demonstrates loading and inspecting a .cast file.

// Create a sample recording
tmpfile := filepath.Join(os.TempDir(), "load-example.cast")
defer os.Remove(tmpfile)

recorder, _ := NewRecorder(tmpfile, 80, 24, RecordingOptions{
	Compress: false,
	Title:    "Load Example",
})
recorder.RecordOutput("Hello, World!\n")
recorder.Close()

// Load it back
header, events, err := LoadCastFile(tmpfile)
if err != nil {
	panic(err)
}

fmt.Printf("Terminal size: %dx%d\n", header.Width, header.Height)
fmt.Printf("Title: %s\n", header.Title)
fmt.Printf("Events: %d\n", len(events))
Output:
Terminal size: 80x24
Title: Load Example
Events: 1

Types

type Player

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

Player plays back recorded terminal sessions with timing preservation.

Player loads asciinema v2 format recordings and plays them back to an io.Writer, preserving the original timing between events. It supports speed adjustment, pause/resume, seeking, and looping.

Playback is performed in a blocking manner by Play(), or you can run it in a goroutine and control it with the various control methods.

All methods are safe for concurrent use.

func NewPlayer

func NewPlayer(filename string, opts PlayerOptions) (*Player, error)

NewPlayer creates a new player from a .cast recording file.

The recording file is loaded completely into memory. Files can be gzip-compressed (detected automatically). Invalid or malformed events are silently skipped during loading.

Example:

player, err := NewPlayer("demo.cast", PlayerOptions{
    Speed: 2.0,
    MaxIdle: 1.0,
    Output: os.Stdout,
})
if err != nil {
    log.Fatal(err)
}
player.Play() // Blocks until playback completes
Example

ExampleNewPlayer demonstrates basic playback.

// Create a sample recording
tmpfile := filepath.Join(os.TempDir(), "player-example.cast")
defer os.Remove(tmpfile)

recorder, _ := NewRecorder(tmpfile, 80, 24, RecordingOptions{
	Compress: false,
})
recorder.RecordOutput("Hello from recording!\n")
recorder.Close()

// Play it back
var buf bytes.Buffer
player, err := NewPlayer(tmpfile, PlayerOptions{
	Output: &buf,
	Speed:  1.0,
})
if err != nil {
	panic(err)
}

player.Play()
fmt.Print(buf.String())
Output:
Hello from recording!

func (*Player) EventCount

func (p *Player) EventCount() int

EventCount returns the total number of events in the recording.

func (*Player) GetDuration

func (p *Player) GetDuration() float64

GetDuration returns the total duration of the recording in seconds.

If MaxIdle was configured, returns the adjusted duration (with idle times capped). Returns 0 if the recording has no events.

func (*Player) GetHeader

func (p *Player) GetHeader() RecordingHeader

GetHeader returns the recording metadata.

This includes terminal dimensions, title, timestamp, and environment variables.

func (*Player) GetPosition

func (p *Player) GetPosition() float64

GetPosition returns the current playback position in seconds.

This is the timestamp of the current event being played. If MaxIdle was configured, returns the adjusted position.

func (*Player) GetProgress

func (p *Player) GetProgress() float64

GetProgress returns playback progress as a fraction between 0.0 and 1.0.

Returns 0.0 at the start, 1.0 at the end, and values in between during playback.

func (*Player) IsPaused

func (p *Player) IsPaused() bool

IsPaused returns true if playback is currently paused.

func (*Player) Pause

func (p *Player) Pause()

Pause pauses playback.

Playback can be resumed with Resume(). While paused, no events are written to the output. The pause time is tracked internally to prevent time jumps when resuming.

func (*Player) Play

func (p *Player) Play() error

Play starts playback of the recording.

This method blocks until playback completes, is stopped via Stop(), or an error occurs. Events are written to the configured output writer with timing preserved according to the speed multiplier.

If Loop is enabled, playback will restart from the beginning when it reaches the end. Call Stop() from another goroutine to end looped playback.

After Stop() is called, the player cannot be restarted. Create a new player if you need to play the recording again.

Example:

// Blocking playback
err := player.Play()

// Playback in background with controls
go player.Play()
time.Sleep(5 * time.Second)
player.Pause()

func (*Player) Resume

func (p *Player) Resume()

Resume resumes a paused playback.

If playback is not paused, this is a no-op.

func (*Player) Seek

func (p *Player) Seek(seconds float64)

Seek jumps to a specific time offset in the recording.

The time is specified in seconds from the start of the recording. Seeking adjusts the playback position to the event closest to the target time. This can be called during playback.

func (*Player) SetLoop

func (p *Player) SetLoop(loop bool)

SetLoop enables or disables looping.

When enabled, playback restarts from the beginning when it reaches the end.

func (*Player) SetSpeed

func (p *Player) SetSpeed(speed float64)

SetSpeed changes the playback speed multiplier.

The speed is adjusted smoothly to prevent jumps in playback position. Values less than or equal to 0 are ignored. Common values:

  • 0.5 = half speed (slower)
  • 1.0 = normal speed
  • 2.0 = double speed (faster)
Example

ExamplePlayer_SetSpeed demonstrates changing playback speed.

tmpfile := filepath.Join(os.TempDir(), "speed-example.cast")
defer os.Remove(tmpfile)

recorder, _ := NewRecorder(tmpfile, 80, 24, RecordingOptions{
	Compress: false,
})
recorder.RecordOutput("Speed test\n")
recorder.Close()

player, _ := NewPlayer(tmpfile, PlayerOptions{
	Output: os.Stdout,
})

fmt.Printf("Initial speed: %.1fx\n", player.Speed())
player.SetSpeed(2.0)
fmt.Printf("New speed: %.1fx\n", player.Speed())
Output:
Initial speed: 1.0x
New speed: 2.0x

func (*Player) Speed

func (p *Player) Speed() float64

Speed returns the current playback speed multiplier.

func (*Player) Stop

func (p *Player) Stop()

Stop stops playback immediately and permanently.

After calling Stop, the player cannot be restarted. Create a new player if you need to play the recording again.

func (*Player) TogglePause

func (p *Player) TogglePause()

TogglePause toggles between paused and playing states.

type PlayerOptions

type PlayerOptions struct {
	Speed   float64   // Playback speed multiplier (1.0 = normal speed, 2.0 = 2x, etc.)
	Loop    bool      // Loop playback when finished (restart from beginning)
	MaxIdle float64   // Max idle time between events in seconds (0 = preserve original timing)
	Output  io.Writer // Output destination (default: os.Stdout)
}

PlayerOptions configures playback behavior.

func DefaultPlayerOptions

func DefaultPlayerOptions() PlayerOptions

DefaultPlayerOptions returns sensible defaults for playback.

Returns options with normal speed (1.0), no looping, and output to stdout.

type Recorder

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

Recorder captures terminal output to an asciinema v2 format file.

Use this when you want to record terminal output directly without a PTY. For recording interactive PTY sessions, see Session.Record instead.

Recorder is safe for concurrent use (all methods are thread-safe).

func NewRecorder

func NewRecorder(filename string, width, height int, opts RecordingOptions) (*Recorder, error)

NewRecorder creates a new recorder that writes to the specified file.

The file is created immediately and the asciinema v2 header is written. The recorder must be closed with Close() when done to ensure all data is flushed.

Parameters:

  • filename: Path to the .cast file to create
  • width: Terminal width in columns
  • height: Terminal height in rows
  • opts: Recording options (compression, redaction, metadata)

Example:

recorder, err := NewRecorder("demo.cast", 80, 24, RecordingOptions{
    Compress: true,
    Title: "My Demo",
})
if err != nil {
    log.Fatal(err)
}
defer recorder.Close()
Example

ExampleNewRecorder demonstrates basic recording usage.

// Create a temporary file for the recording
tmpfile := filepath.Join(os.TempDir(), "example.cast")
defer os.Remove(tmpfile)

// Create a recorder
recorder, err := NewRecorder(tmpfile, 80, 24, RecordingOptions{
	Compress: false,
	Title:    "Example Recording",
})
if err != nil {
	panic(err)
}
defer recorder.Close()

// Record some output
recorder.RecordOutput("Hello, ")
recorder.RecordOutput("World!\n")

func (*Recorder) Close

func (r *Recorder) Close() error

Close finalizes the recording and closes the file.

This flushes all buffered data and closes any compression streams. Always call Close when finished recording to ensure data is not lost. It's safe to call Close multiple times.

func (*Recorder) Flush

func (r *Recorder) Flush() error

Flush writes any buffered data to the underlying file.

Events are buffered for performance. Call Flush to ensure all recorded events are written to disk immediately.

func (*Recorder) IsPaused

func (r *Recorder) IsPaused() bool

IsPaused returns true if recording is currently paused.

func (*Recorder) Pause

func (r *Recorder) Pause()

Pause temporarily suspends recording.

While paused, calls to RecordOutput and RecordInput are ignored. Use Resume to continue recording. This is useful for temporarily stopping recording during sensitive operations.

Example

ExampleRecorder_Pause demonstrates pausing and resuming recording.

tmpfile := filepath.Join(os.TempDir(), "pause-example.cast")
defer os.Remove(tmpfile)

recorder, err := NewRecorder(tmpfile, 80, 24, RecordingOptions{
	Compress: false,
})
if err != nil {
	panic(err)
}
defer recorder.Close()

recorder.RecordOutput("Before pause\n")
recorder.Pause()
recorder.RecordOutput("During pause (not recorded)\n")
recorder.Resume()
recorder.RecordOutput("After resume\n")

func (*Recorder) RecordInput

func (r *Recorder) RecordInput(data string)

RecordInput records user input data.

Input events are recorded but typically not displayed during playback (most players only render output events). They're useful for analysis and understanding user interactions with the terminal.

This method is safe to call from multiple goroutines.

func (*Recorder) RecordOutput

func (r *Recorder) RecordOutput(data string)

RecordOutput records terminal output data.

The data is timestamped relative to when the recorder was created. If RedactSecrets is enabled, passwords and API keys will be automatically redacted. Recording is skipped if the recorder is paused or if data is empty.

This method is safe to call from multiple goroutines.

func (*Recorder) Resume

func (r *Recorder) Resume()

Resume continues a paused recording.

Recording resumes from the point where Pause was called, preventing a large time gap from appearing in the recording.

func (*Recorder) UpdateSize

func (r *Recorder) UpdateSize(width, height int)

UpdateSize updates the recorded terminal dimensions.

This should be called when the terminal is resized to keep the metadata accurate. Note: asciinema v2 doesn't have explicit resize events, so this only updates internal tracking.

type RecordingEvent

type RecordingEvent struct {
	Time float64 // Seconds since recording start
	Type string  // "o" for terminal output, "i" for user input
	Data string  // The actual terminal content (may contain ANSI codes)
}

RecordingEvent represents a single terminal I/O event in asciinema v2 format.

Events are encoded as JSON arrays: [time, type, data] where time is seconds elapsed, type is "o" (output) or "i" (input), and data is the actual terminal content.

func OutputEvents

func OutputEvents(events []RecordingEvent) []RecordingEvent

OutputEvents filters and returns only output events (type "o").

Input events (type "i") are filtered out. This is useful because most playback scenarios only need to render output, and input events are primarily kept for analysis purposes.

The returned slice contains references to the original events (not copies).

Example

ExampleOutputEvents demonstrates filtering output events.

events := []RecordingEvent{
	{Time: 0.0, Type: "o", Data: "Output line 1"},
	{Time: 0.5, Type: "i", Data: "User input"},
	{Time: 1.0, Type: "o", Data: "Output line 2"},
}

outputOnly := OutputEvents(events)
fmt.Printf("Total events: %d\n", len(events))
fmt.Printf("Output events: %d\n", len(outputOnly))
Output:
Total events: 3
Output events: 2

func (RecordingEvent) MarshalJSON

func (e RecordingEvent) MarshalJSON() ([]byte, error)

MarshalJSON implements custom JSON encoding for asciinema array format

type RecordingHeader

type RecordingHeader struct {
	Version   int               `json:"version"`   // Always 2 for asciinema v2 format
	Width     int               `json:"width"`     // Terminal width in columns
	Height    int               `json:"height"`    // Terminal height in rows
	Timestamp int64             `json:"timestamp"` // Unix timestamp of recording start
	Env       map[string]string `json:"env,omitempty"`
	Title     string            `json:"title,omitempty"` // Optional recording title
}

RecordingHeader represents the asciinema v2 header line.

This is the first line of every .cast file, containing metadata about the recording session. All recordings must start with a valid header.

type RecordingOptions

type RecordingOptions struct {
	Compress      bool              // Enable gzip compression to reduce file size
	RedactSecrets bool              // Automatically redact passwords, API keys, and tokens
	Title         string            // Recording title for metadata (shown in players)
	Env           map[string]string // Environment variables to include in metadata
	IdleTimeLimit float64           // Max idle time between events in seconds (0 = no limit)
}

RecordingOptions configures recording behavior.

These options control how terminal output is captured and stored.

func DefaultRecordingOptions

func DefaultRecordingOptions() RecordingOptions

DefaultRecordingOptions returns sensible defaults for recording.

By default, recordings are compressed and secrets are redacted.

type Session

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

Session represents an interactive PTY (pseudo-terminal) session.

Session manages a command running in a PTY, handles terminal I/O, and optionally records the session to an asciinema v2 format file. It's designed for creating interactive terminal sessions that feel like a real terminal (with proper handling of colors, cursor movement, etc.).

The session handles:

  • PTY creation and management
  • Terminal size synchronization (including SIGWINCH)
  • Raw mode terminal setup
  • Optional recording with the Recorder

Use Start() to begin an interactive session, or Record() to start and record simultaneously.

func NewSession

func NewSession(opts SessionOptions) (*Session, error)

NewSession creates a new PTY session with the given options.

The session is not started until Start() or Record() is called. If no command is specified, the user's shell ($SHELL or /bin/sh) is used.

Example:

session, err := NewSession(SessionOptions{
    Command: []string{"bash", "-i"},
    Dir: "/tmp",
    Env: []string{"TERM=xterm-256color"},
})
if err != nil {
    log.Fatal(err)
}
defer session.Close()
Example

ExampleNewSession demonstrates creating a PTY session.

// Create a session that will run a command
session, err := NewSession(SessionOptions{
	Command: []string{"bash", "-c", "exit 0"},
	Dir:     "/tmp",
})
if err != nil {
	fmt.Printf("Error: %v\n", err)
	return
}

fmt.Printf("Session created with command: %v\n", session.command)
fmt.Printf("Working directory: %s\n", session.dir)
Output:
Session created with command: [bash -c exit 0]
Working directory: /tmp

func (*Session) Close

func (s *Session) Close() error

Close terminates the session and cleans up resources.

This method:

  • Restores the terminal to its original state
  • Closes and finalizes any recording
  • Closes the PTY

It's safe to call Close multiple times or before the session completes.

func (*Session) ExitCode

func (s *Session) ExitCode() int

ExitCode returns the exit code of the command.

This is only valid after Wait() returns. Returns 0 before the session completes.

func (*Session) IsRecording

func (s *Session) IsRecording() bool

IsRecording returns true if the session is being recorded.

func (*Session) PauseRecording

func (s *Session) PauseRecording()

PauseRecording temporarily pauses recording.

Terminal I/O continues normally, but events are not recorded while paused. Has no effect if the session is not being recorded.

func (*Session) Record

func (s *Session) Record(filename string, opts RecordingOptions) error

Record starts the PTY session and records it to the specified file.

This is a convenience method that creates a Recorder, attaches it to the session, and calls Start(). The terminal size is detected automatically (or defaults to 80x24 if detection fails).

The recording file is created immediately with the header written. Call Wait() to block until the session ends, then Close() to finalize.

Example:

session, _ := NewSession(SessionOptions{
    Command: []string{"bash", "-c", "echo 'Hello, World!'"},
})
err := session.Record("demo.cast", RecordingOptions{
    Compress: true,
    Title: "Hello Demo",
})
if err != nil {
    log.Fatal(err)
}
session.Wait()
session.Close()

func (*Session) Resize

func (s *Session) Resize(width, height int) error

Resize manually sets the terminal size.

This is useful for programmatically resizing the terminal or when automatic resize detection isn't available (non-TTY scenarios). The recorder is also updated if recording is active.

func (*Session) ResumeRecording

func (s *Session) ResumeRecording()

ResumeRecording resumes a paused recording.

Recording continues from where it was paused. Has no effect if the session is not being recorded or is not paused.

func (*Session) Start

func (s *Session) Start() error

Start begins the PTY session without recording.

This method:

  • Creates a PTY and starts the command
  • Sets the terminal to raw mode (if stdin is a terminal)
  • Starts goroutines to handle I/O between stdin/stdout and the PTY
  • Sets up terminal resize handling (SIGWINCH)

The session runs in the background. Use Wait() to block until it completes.

Example:

session, _ := NewSession(SessionOptions{
    Command: []string{"bash"},
})
err := session.Start()
if err != nil {
    log.Fatal(err)
}
// Session is now interactive
session.Wait()

func (*Session) Wait

func (s *Session) Wait() error

Wait blocks until the session ends and returns any error.

This waits for the command to exit. The exit code can be retrieved with ExitCode() after Wait returns.

type SessionOptions

type SessionOptions struct {
	Command []string  // Command to run (default: user's shell from $SHELL)
	Dir     string    // Working directory (default: current directory)
	Env     []string  // Additional environment variables (added to inherited environment)
	Input   io.Reader // Input source (default: os.Stdin)
	Output  io.Writer // Output destination (default: os.Stdout)
}

SessionOptions configures a new PTY session.

Jump to

Keyboard shortcuts

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