firpty

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Aug 30, 2026 License: MIT Imports: 15 Imported by: 0

README

firpty

CI Go Reference Go Report Card

A Go-native, tmux-free terminal multiplexer for driving interactive processes. Extracted from the fir coding-agent harness, where it serves as a fallback for the tmux-driver skill on systems without tmux.

Features

  • Named sessions with multiple windows (session:window addressing)
  • Send text + Enter, send raw bytes, capture rendered output
  • Wait for a regex pattern with timeout
  • Built-in VT100/ANSI screen emulator with scrollback
  • Unix-socket JSON-RPC server (firpty serve) + tiny CLI client
  • 100% unit-test coverage of the core package (real PTY/exec wrappers excluded via .covignore)

Install

go install github.com/kfet/firpty/cmd/firpty@latest

Use as a library

m := firpty.NewManager()
sess, err := m.New("myproj", "shell")
_ = m.Send(sess.Name, "echo hello")
out, _ := m.Capture(sess.Name, 50)
fmt.Println(out)
_ = m.KillWindow("myproj", "shell")

Use the CLI

firpty serve &                  # starts server on $FIRPTY_SOCKET_DIR/pty.sock
firpty new myproj shell
firpty send myproj 'echo hi'
firpty wait  myproj 'hi' 5
firpty capture myproj 50
firpty kill myproj
firpty shutdown

Test

make test          # runs with -race -shuffle=on, gates on 100% coverage
make open_coverage # HTML report

License

MIT — see LICENSE.

Documentation

Overview

Package firpty provides a Go-native terminal multiplexer for driving interactive processes. It exposes:

  • A Manager that owns named PTY-backed sessions and windows
  • A Screen that emulates enough of VT100/ANSI to capture program output
  • A Server that exposes the Manager over a Unix-socket JSON protocol
  • A Client that talks to the Server

firpty is the standalone library/tool extracted from the fir coding-agent harness, used as a tmux fallback for headless agent orchestration.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DefaultSocketPath

func DefaultSocketPath() string

DefaultSocketPath returns the default Unix socket path for the firpty server. Honours $FIRPTY_SOCKET_DIR (preferred) and the legacy $FIR_PTY_SOCKET_DIR.

Types

type Client

type Client struct {
	SocketPath string
	// DialTimeout overrides the default 2s dial timeout (used in tests).
	DialTimeout time.Duration
	// CallDeadline overrides the per-call deadline (used in tests).
	CallDeadline time.Duration
}

Client is a single-shot client for the firpty server.

func (*Client) Call

func (c *Client) Call(method string, params any) (*Response, error)

Call sends method+params and returns the response.

type Clock

type Clock interface {
	After(time.Duration) <-chan time.Time
	NewTicker(time.Duration) Ticker
}

Clock abstracts time.Now / time.After / time.NewTicker so Wait can be tested deterministically.

type Manager

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

Manager owns a collection of named sessions, analogous to a tmux server.

func NewManager

func NewManager(opts ...ManagerOption) *Manager

NewManager creates a Manager with default real-PTY backend.

func (*Manager) Alive

func (m *Manager) Alive(target string) bool

Alive reports whether the target's process is still running.

The process, not its output stream. A window whose program has exited but whose terminal is still held open by something it spawned is not alive, and reporting otherwise would have a caller wait for a turn that nobody is going to take.

func (*Manager) Capture

func (m *Manager) Capture(target string, lines int) (string, error)

Capture returns the last lines of output from the target window. lines<=0 means everything available.

func (*Manager) Kill

func (m *Manager) Kill(session string) error

Kill destroys all windows in a session.

func (*Manager) KillWindow

func (m *Manager) KillWindow(session, window string) error

KillWindow destroys a single window.

func (*Manager) List

func (m *Manager) List(session string) []string

List returns session names (when session==""), or the windows of a specific session.

func (*Manager) New

func (m *Manager) New(session, window string) (*Session, error)

New creates a new session whose first window runs the user's shell.

func (*Manager) NewWindow

func (m *Manager) NewWindow(session, window, command string) (*Session, error)

NewWindow creates a new window in an existing (or new) session, optionally running command instead of an interactive shell.

func (*Manager) Send

func (m *Manager) Send(target, text string) error

Send writes text+"\n" to the target window.

func (*Manager) SendRaw

func (m *Manager) SendRaw(target string, data []byte) error

SendRaw writes raw bytes to the target window.

func (*Manager) Wait

func (m *Manager) Wait(target, pattern string, timeout time.Duration) error

Wait polls the target's screen for a regex match, returning nil on match, a timeout error on deadline, or an error if the process exits without a match.

type ManagerOption

type ManagerOption func(*Manager)

ManagerOption customises a Manager (used by tests).

func WithClock

func WithClock(c Clock) ManagerOption

WithClock overrides the clock used by Wait (default: real time).

func WithDrainGrace added in v0.2.0

func WithDrainGrace(d time.Duration) ManagerOption

WithDrainGrace overrides how long teardown waits for the terminal to be released before closing the master anyway (default: 5s).

func WithKillGrace added in v0.2.0

func WithKillGrace(d time.Duration) ManagerOption

WithKillGrace overrides how long a window's process group gets to exit after SIGHUP before it is SIGKILLed (default: 2s).

func WithStarter

func WithStarter(s Starter) ManagerOption

WithStarter overrides the PTY starter (default: real PTY via creack/pty).

func WithTickInterval

func WithTickInterval(d time.Duration) ManagerOption

WithTickInterval overrides Wait's poll interval (default: 200ms).

type PTYProcess

type PTYProcess interface {
	// Read reads bytes from the PTY master.
	Read(p []byte) (int, error)
	// Write writes bytes to the PTY master.
	Write(p []byte) (int, error)
	// Close closes the PTY master.
	Close() error
	// SignalGroup sends sig to the process GROUP led by the child, so that
	// everything the child spawned is signalled too. A group that is
	// already gone is not an error.
	SignalGroup(sig syscall.Signal) error
	// Wait blocks until the child process exits and returns its error.
	Wait() error
}

PTYProcess is the abstraction over a PTY-backed child process used by Manager. It is implemented by the real (creack/pty + os/exec) backend in ext.go and by fakes in tests.

type Request

type Request struct {
	Method string          `json:"method"`
	Params json.RawMessage `json:"params"`
}

Request is a JSON-RPC-style request to the firpty server.

type Response

type Response struct {
	Result json.RawMessage `json:"result,omitempty"`
	Error  string          `json:"error,omitempty"`
}

Response is a JSON-RPC-style response from the firpty server.

type Screen

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

Screen is a minimal VT100/ANSI terminal emulator that tracks a grid of runes plus a scrollback buffer. It implements just enough escape-sequence handling to faithfully capture the visible output of typical CLI programs.

func NewScreen

func NewScreen(rows, cols int) *Screen

NewScreen creates a screen with the given dimensions.

func (*Screen) Capture

func (s *Screen) Capture(lines int) string

Capture returns the last n lines (scrollback + visible). n<=0 means all.

func (*Screen) CaptureVisible

func (s *Screen) CaptureVisible() string

CaptureVisible returns only the visible (non-scrollback) screen content.

func (*Screen) Write

func (s *Screen) Write(p []byte) (int, error)

Write processes raw terminal output, including VT100/ANSI escape codes.

type Server

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

Server runs a Manager behind a Unix socket.

func NewServer

func NewServer(sockPath string, mgr *Manager) (*Server, error)

NewServer creates a Server bound to sockPath with the given Manager. If mgr is nil, a default Manager is created.

func (*Server) Addr

func (s *Server) Addr() net.Addr

Addr returns the listener's address.

func (*Server) Close

func (s *Server) Close() error

Close stops accepting and waits for in-flight handlers.

func (*Server) Manager

func (s *Server) Manager() *Manager

Manager exposes the underlying Manager (useful for tests / advanced use).

func (*Server) Serve

func (s *Server) Serve() error

Serve accepts connections until the listener is closed. It always returns a non-nil error (typically the Accept error from a closed listener).

func (*Server) SetShutdownDelay

func (s *Server) SetShutdownDelay(d time.Duration)

SetShutdownDelay overrides the post-shutdown listener-close delay (default 100ms). Tests use 0 for determinism.

type Session

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

Session represents a single PTY-backed window.

type Starter

type Starter func(command string) (PTYProcess, error)

Starter creates a new PTYProcess running the given shell command. If command is empty, the process is the user's interactive shell.

type Ticker

type Ticker interface {
	C() <-chan time.Time
	Stop()
}

Ticker is the minimal subset of time.Ticker that Wait uses.

Directories

Path Synopsis
cmd
firpty command
Command firpty is a tiny CLI front-end for the firpty server.
Command firpty is a tiny CLI front-end for the firpty server.

Jump to

Keyboard shortcuts

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