Terminal
The Bun terminal backend: sizing, raw-input sessions, key streams and rendering.
@ozaco/cli/terminal/bun@ozaco/cli/terminal/bun is the Bun implementation of the Terminal protocol — the lowest layer every interactive CLI module renders through. It exposes one plugin, BunTerminal, built on Bun’s native stripANSI/wrapAnsi plus Node-compatible stream and TTY handling.
Setup defaults input/output/error to the process std streams and infers interactive from their isTTY and raw-mode support (overridable). It requires the std:io plugin to be installed and warns if it is missing. Terminal size is probed from the live streams first, then COLUMNS/LINES, then platform-specific fallbacks (/dev/tty, tput, resize), settling on 80×24 if nothing works.
The centerpiece is session: it enables raw mode, hides the cursor, and spawns a reader that decodes bytes into Key events on a shared stream — then restores raw mode and the cursor when the block exits, whether by success, error, or halt. Inside a session, keys returns that stream and renderer gives an in-place frame renderer used by spinners, prompts and tables.
Implementation
BunTerminal
The Bun `Terminal` implementation providing every terminal action.
const BunTerminal: TerminalDefInstall it before any interactive module (spinner, prompt, table). Accepts TerminalDef.Options at install time to override the input/output/error streams, force interactive, or set the encoding.
import { install } from '@ozaco/std/plugin'
import { BunIO } from '@ozaco/std/io/impl/bun'
import { BunTerminal } from '@ozaco/cli/terminal/bun'
yield* install(BunIO)
yield* install(BunTerminal)Actions
Available on the `Terminal` protocol once `BunTerminal` is installed.
size
Probe the current terminal size, falling back to 80×24.
size(): Future<Size>Tries the output/error stream dimensions, then COLUMNS/LINES, then platform probes (/dev/tty, tput, resize). Returns { columns: 80, rows: 24 } when none succeed.
isInteractive
Whether the terminal is interactive (TTY + raw-mode capable).
isInteractive(): Future<boolean>session
Run `fn` inside a raw-input session with a live key stream, restoring state on exit.
session<R>(fn: () => Operation<R>): Future<R>Enables raw mode, hides the cursor, and spawns a reader that feeds a shared Key stream. On exit (success, error, or halt) raw mode and the cursor are restored and the stream closed. Prompts run inside a session so they can read keypresses.
yield* Terminal.actions.session(function* () {
const keys = yield* Terminal.actions.keys()
for (const key of yield* each(keys)) {
if (key.name === 'return') break
yield* each.next()
}
})write
Write raw text (or a stream of text) to the output stream.
write(text: string | Stream<string, unknown>): Future<void>keys
The decoded key stream for the active `session()`.
keys(): Future<Stream<Key, void>>Subscribe with yield* stream. Only valid inside a session(); outside one the key-stream context is unset.
stripAnsi
Remove ANSI escape sequences from text (via Bun’s `stripANSI`).
stripAnsi(text: string): Future<string>wrapAnsi
Word-wrap text to a column width while preserving ANSI styling.
wrapAnsi(text: string, columns: number, options: TerminalDef.WrapAnsiOptions): Future<string>options control hard breaking, wordWrap, trim and ambiguousIsNarrow (see TerminalDef.WrapAnsiOptions). Backed by Bun’s wrapAnsi.
displayWidth
Count the visible width of a line in terminal cells (ANSI stripped).
displayWidth(line: string): Future<number>Strips ANSI first, then counts code points — matching how the table layout measures cells.
renderer
Create an in-place frame renderer for a given column width.
renderer(columns: number): Future<TerminalDef.Renderer>Returns a Renderer that tracks how many rows the last frame occupied so render can clear and repaint it. Used by the spinner, prompt and table modules to animate and update output.
Renderer
The in-place renderer returned by `renderer`.
Renderer
Draw, clear and finalize a frame in place (`TerminalDef.Renderer`).
interface Renderer {
render(frame: string): Future<void>
clear(): Future<void>
done(frame?: string): Future<void>
}render clears the previous frame and writes a new one, remembering its row count. clear erases the current frame. done writes an optional final frame then a newline, leaving it in the scrollback and resetting tracking.