0Z4C0
// navigation
// @ozaco/cli

Terminal

The Bun terminal backend: sizing, raw-input sessions, key streams and rendering.

import from@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.

01

Implementation

const

BunTerminal

The Bun `Terminal` implementation providing every terminal action.

signature
const BunTerminal: TerminalDef

Install 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.

example
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)
02

Actions

Available on the `Terminal` protocol once `BunTerminal` is installed.

fn

size

Probe the current terminal size, falling back to 80×24.

signature
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.

fn

isInteractive

Whether the terminal is interactive (TTY + raw-mode capable).

signature
isInteractive(): Future<boolean>
fn

session

Run `fn` inside a raw-input session with a live key stream, restoring state on exit.

signature
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.

example
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()
  }
})
fn

write

Write raw text (or a stream of text) to the output stream.

signature
write(text: string | Stream<string, unknown>): Future<void>
fn

keys

The decoded key stream for the active `session()`.

signature
keys(): Future<Stream<Key, void>>

Subscribe with yield* stream. Only valid inside a session(); outside one the key-stream context is unset.

fn

stripAnsi

Remove ANSI escape sequences from text (via Bun’s `stripANSI`).

signature
stripAnsi(text: string): Future<string>
fn

wrapAnsi

Word-wrap text to a column width while preserving ANSI styling.

signature
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.

fn

displayWidth

Count the visible width of a line in terminal cells (ANSI stripped).

signature
displayWidth(line: string): Future<number>

Strips ANSI first, then counts code points — matching how the table layout measures cells.

fn

renderer

Create an in-place frame renderer for a given column width.

signature
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.

03

Renderer

The in-place renderer returned by `renderer`.

iface

Renderer

Draw, clear and finalize a frame in place (`TerminalDef.Renderer`).

signature
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.