Three public packages share the nimbleterminal.dev/ntrecord module:
| Import | Purpose | Native dependencies |
|---|---|---|
nimbleterminal.dev/ntrecord/gifencode |
Timed RGBA images to optimized animated GIFs | None; standard library only |
nimbleterminal.dev/ntrecord/asciicast |
Streaming v2/v3 reader and v3 output writer | None; standard library only |
nimbleterminal.dev/ntrecord/render |
Terminal bytes to RGBA snapshots, including Kitty graphics and Unicode | cgo and the pinned libghostty-vt static library |
They do not execute recorded commands or open a PTY. Recording commands, tape execution, FFmpeg orchestration, and the interactive terminal player remain internal to the CLI. No public signature requires an internal type.
var encoder gifencode.Encoder // ready to use; defaults to a 512 MiB budget
if err := encoder.Add(frame, 100*time.Millisecond); err != nil {
return err
}
return encoder.Encode(output) // output is an io.WriterInputs are opaque, zero-origin *image.RGBA frames with constant dimensions.
Composite transparent input onto a background first. Add copies the pixels, so
the caller can reuse the input buffer. Configure Seed, FixedPalette, and
MaxBytes before the first Add; do not mutate those options afterward.
Durations accumulate and round to centiseconds. Rotate changes the starting
point within the loop; deferred errors are returned by Encode. The GIF loops
forever. Encode can write the accumulated animation more than once and does not
close its writer. The encoder retains its frames until it becomes unreachable.
This is a buffered encoder, not constant-memory streaming: palette construction uses all retained frames. Its working-set budget is not a total process RSS cap. See GIF encoding for palette and resource tradeoffs. A runnable example shows frame-buffer reuse.
asciicast.NewReader(io.Reader) validates the header; Next() returns events
with absolute time.Duration timestamps and preserves unknown event codes.
Header.Size() and Header.Colors() work with both v2 and v3. Input lines are
limited to asciicast.MaxLine (64 MiB).
asciicast.NewWriter(io.Writer, asciicast.Terminal) writes a v3 header.
Output(at, bytes) consumes terminal output, retaining incomplete UTF-8 until the
next call. Call End(at) once after the final output to flush the last bytes and
preserve trailing silence. It deliberately does not record input, environment,
command metadata, or invented exit statuses. The writer does not impose the
reader's line-size limit; keep individual events below it for round trips.
Readers and writers do not close their underlying streams. See the
runnable round-trip example.
config := render.DefaultConfig()
config.Cols, config.Rows = 80, 24
renderer, err := render.New(config)
if err != nil {
return err
}
defer renderer.Close()
if err := renderer.Write([]byte("\x1b[?25lHello 👍🏽\r\n")); err != nil {
return err
}
frame, err := renderer.FrameAfter(0)
if err != nil {
return err
}
return png.Encode(output, frame)Start with DefaultConfig; the zero configuration is intentionally invalid.
Configuration is copied at construction. It controls the grid, font, theme,
spacing, decorations, cursor, and advertised version, independently of tape
settings. Width and height are fixed for a renderer's lifetime. Exported dimension
fields are read-only; OutputSize() includes decorations.
Frame images are cached and must not be modified. They remain valid after
later writes and after Close; copy pixels before editing. Write consumes its
input synchronously. TakeReplies drains terminal replies into a caller-owned
slice; a caller connecting a live process can forward those bytes to its PTY.
FrameAfter uses a relative clock for reproducible cursor timing. FrameAt uses
an explicit time. CaptureFrameAfter additionally applies image-replacement
smoothing; RenderHeldAfter reports synchronized-output holds for a recorder to
honor. Raw Frame/FrameAt snapshots can show an in-progress update.
ProgramStatuses() returns an independent OSC 7501 snapshot sorted by record ID.
Each render.ProgramStatus contains ID, State, Kind, App, Title,
Message, and Progress (-1 when unspecified). App names include ancestor
inheritance. Records replace earlier reports in full; subtree clears, shell
prompts, and terminal resets follow the protocol. At most 256 records are stored,
evicting the least recently updated when full. Status never alters frame pixels.
Forward TakeReplies() to the live process to answer OSC 7501 capability queries.
After feeding its final output, call ProgramExited() to discard working and
blocked records; idle, done, and error remain. Keep all calls on the renderer's
own goroutine. Snapshots contain Go-owned values that survive further writes and
closing the renderer. Display titles and messages as plain text; strip invisible
formatting characters if presenting them outside the terminal grid.
Serialize calls on each renderer, including Close. Separate instances may run
concurrently. Close is idempotent; do not call other methods after closing.
Codec instances also require serialized access. Retained immutable frame images
can be read by other goroutines.
See rendering for font fallback, environment controls, color emoji, protocol support, and limits. The renderer installs a process-wide libghostty PNG decoder once on first construction. Redistribution of binaries using the bundled fonts must include their notices, Go Mono notice, and relevant native/dependency licenses.
The codecs build with CGO_ENABLED=0; importing them does not link the renderer.
The renderer needs Go 1.26 or later, cgo, and the same native library as the CLI.
Build it from a checkout of the same ntrecord version you depend on:
cd /path/to/ntrecord
task deps
export CGO_ENABLED=1
export PKG_CONFIG="$PWD/scripts/pkg-config.sh"
export PKG_CONFIG_PATH="$PWD/.deps/libghostty-vt/share/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}"
cd /path/to/your/application
go test ./...Depend on a published module version once the vanity endpoint and release are
available. During local development, use a Go replace directive pointing to the
checkout. See development for native prerequisites and module
hosting setup. This does not require a separate Go module for each package.
task test:library creates an unrelated temporary module, tests both codecs with
cgo disabled, then tests rendering and GIF composition with cgo enabled. It checks
UTF-8 event round trips, frame ownership after input reuse and renderer shutdown,
terminal replies, and GIF timing. task ci includes it. Existing renderer golden
frames and GIF composition tests continue to test the implementation in its new
public location.