Terminal API
createTerminal(options)
Creates a Terminal instance wrapping a backend with optional PTY support.
import { createTerminal } from "@termless/monorepo"
import type { Terminal, TerminalCreateOptions } from "@termless/monorepo"
import { createXtermBackend } from "@termless/xtermjs"
const term = createTerminal({
backend: createXtermBackend(),
cols: 80, // default: 80
rows: 24, // default: 24
scrollbackLimit: 1000,
})TerminalCreateOptions
interface TerminalCreateOptions {
backend: TerminalBackend // Required
cols?: number // Default: 80
rows?: number // Default: 24
scrollbackLimit?: number
}| Option | Type | Default | Description |
|---|---|---|---|
backend | TerminalBackend | required | Backend instance (e.g., from createXtermBackend()) |
cols | number | 80 | Terminal width in columns |
rows | number | 24 | Terminal height in rows |
scrollbackLimit | number | -- | Maximum scrollback lines (backend-dependent) |
Terminal Interface
interface Terminal extends TerminalReadable {
// Properties
readonly cols: number
readonly rows: number
readonly backend: TerminalBackend
readonly alive: boolean // PTY process running?
readonly exitInfo: string | null // e.g. "exit=0"
// Region selectors (WHERE) -- getter properties
readonly screen: RegionView // visible rows x cols area
readonly scrollback: RegionView // history above screen
readonly buffer: RegionView // everything (scrollback + screen)
readonly viewport: RegionView // current scroll position view
// Region selectors (WHERE) -- methods
row(n: number): RowView // screen row (negative from bottom)
cell(row: number, col: number): CellView // single cell
range(r1: number, c1: number, r2: number, c2: number): RegionView // rectangular region
firstRow(): RowView // convenience: first screen row
lastRow(): RowView // convenience: last screen row
// Data feed (no PTY)
feed(data: Uint8Array | string): void
// PTY lifecycle
spawn(command: string[], options?: { env?: Record<string, string>; cwd?: string }): Promise<void>
// Input (requires PTY)
press(key: string): void
type(text: string): void
// Waiting
waitFor(text: string, timeout?: number): Promise<void>
waitForStable(stableMs?: number, timeout?: number): Promise<void>
// Search
find(text: string): TextPosition | null
findAll(pattern: RegExp): TextPosition[]
// Screenshot
screenshotSvg(options?: SvgScreenshotOptions): string
screenshotPng(options?: PngScreenshotOptions): Promise<Uint8Array>
// Resize
resize(cols: number, rows: number): void
// Cleanup
close(): Promise<void>
[Symbol.asyncDispose](): Promise<void>
}Properties
term.cols // number -- current column count
term.rows // number -- current row count
term.backend // TerminalBackend -- the underlying backend
term.alive // boolean -- true if a spawned process is still running
term.exitInfo // string | null -- e.g. "exit=0" after process exitsRegion Selectors
Region selectors separate where to look from what to assert.
Properties (no parentheses)
term.screen // RegionView -- the rows x cols visible area
term.scrollback // RegionView -- history above screen (empty in alt screen)
term.buffer // RegionView -- everything (scrollback + screen)
term.viewport // RegionView -- current scroll position viewMethods
term.row(0) // RowView -- first screen row
term.row(-1) // RowView -- last screen row (negative from bottom)
term.cell(0, 0) // CellView -- single cell at row 0, col 0
term.range(0, 0, 5, 40) // RegionView -- rectangular region
term.firstRow() // RowView -- convenience for first screen row
term.lastRow() // RowView -- convenience for last screen rowView Types
// RegionView -- text access for a region
interface RegionView {
getText(): string
getLines(): string[]
containsText(text: string): boolean
}
// RowView -- a row with positional context (extends RegionView)
interface RowView extends RegionView {
readonly row: number
readonly cells: Cell[]
cellAt(col: number): CellView
}
// CellView -- a single cell with positional context and style
interface CellView {
readonly text: string
readonly row: number
readonly col: number
readonly fg: RGB | null
readonly bg: RGB | null
readonly bold: boolean
readonly faint: boolean
readonly italic: boolean
readonly underline: UnderlineStyle
readonly strikethrough: boolean
readonly inverse: boolean
readonly wide: boolean
}Data Feed
feed(data)
Write data directly to the terminal (no PTY). Useful for testing rendering of ANSI output.
term.feed("Hello, world!") // string
term.feed("\x1b[1;31mRed bold\x1b[0m") // ANSI escape sequences
term.feed(new Uint8Array([0x48, 0x69])) // raw bytesPTY Lifecycle
spawn(command, options?)
Spawn a child process with a pseudo-terminal. The process output is fed to the backend automatically.
await term.spawn(["ls", "-la"])
await term.spawn(["my-app", "--port", "3000"], {
env: { NODE_ENV: "test" },
cwd: "/path/to/project",
})Throws if a process is already spawned or if the terminal is closed.
SpawnOptions
interface SpawnOptions {
command: string[]
env?: Record<string, string>
cwd?: string
}Input
press(key)
Send a keypress to the spawned process. Parses human-readable key descriptions into ANSI escape sequences.
term.press("a") // Single character
term.press("Enter") // Named keys
term.press("ArrowUp") // Arrow keys
term.press("Ctrl+c") // Modifier + key
term.press("Ctrl+Shift+a") // Multiple modifiers
term.press("Alt+x") // Alt modifier
term.press("F5") // Function keys F1-F12
term.press("Shift+Tab") // Reverse tabSupported modifiers: Ctrl, Control, Alt, Option, Shift, Meta, Cmd, Super.
Supported named keys: ArrowUp, ArrowDown, ArrowLeft, ArrowRight, Home, End, PageUp, PageDown, Enter, Tab, Backspace, Delete, Escape, Space, F1--F12.
type(text)
Send raw text to the spawned process. Unlike press(), this does not parse key descriptions -- it writes the string directly to the PTY.
term.type("Hello, world!")
term.type("search query\r") // \r for EnterWaiting
waitFor(text, timeout?)
Wait for specific text to appear in the terminal buffer. Polls every 50ms.
await term.waitFor("ready>") // Default timeout: 5000ms
await term.waitFor("Loading...", 10000) // Custom timeoutThrows Error if the text doesn't appear within the timeout.
waitForStable(stableMs?, timeout?)
Wait for terminal content to stop changing. Useful after keypresses or when waiting for rendering to complete.
await term.waitForStable() // Default: stable for 200ms, timeout 5000ms
await term.waitForStable(100, 3000) // Stable for 100ms, timeout 3000msSearch
find(text)
Find the first occurrence of text in the terminal buffer. Returns position or null.
const pos = term.find("Error")
if (pos) {
console.log(`Found at row ${pos.row}, col ${pos.col}`)
}Returns: { row: number, col: number, text: string } | null
findAll(pattern)
Find all regex matches in the terminal buffer.
const matches = term.findAll(/\d+\.\d+/g) // Find all decimal numbers
for (const m of matches) {
console.log(`${m.text} at (${m.row}, ${m.col})`)
}Returns: TextPosition[] -- array of { row, col, text }.
Reading State
These methods are part of the TerminalReadable interface and work with @termless/test matchers.
getText()
Get all terminal text as a single string with newline-separated rows.
getTextRange(startRow, startCol, endRow, endCol)
Get text from a rectangular region of the terminal.
getCell(row, col)
Get a single cell with all attributes. Returns a Cell object:
const cell = term.getCell(0, 0)
cell.text // string -- character
cell.fg // RGB | null -- foreground color
cell.bg // RGB | null -- background color
cell.bold // boolean
cell.faint // boolean
cell.italic // boolean
cell.underline // "none" | "single" | "double" | "curly" | "dotted" | "dashed"
cell.strikethrough // boolean
cell.inverse // boolean
cell.wide // boolean -- double-width charactergetLine(row)
Get all cells in a row as Cell[].
getLines()
Get the entire terminal grid as Cell[][].
getCursor()
Get cursor state: { x: number, y: number, visible: boolean, style: "block" | "underline" | "beam" }.
getMode(mode)
Check if a terminal mode is active. Returns boolean.
getTitle()
Get the terminal title (set via OSC 2 escape sequence).
getScrollback()
Get scrollback state: { viewportOffset: number, totalLines: number, screenLines: number }.
TerminalReadable Interface
The read-only subset that terminal matchers accept:
interface TerminalReadable {
getText(): string
getTextRange(startRow: number, startCol: number, endRow: number, endCol: number): string
getCell(row: number, col: number): Cell
getLine(row: number): Cell[]
getLines(): Cell[][]
getCursor(): CursorState
getMode(mode: TerminalMode): boolean
getTitle(): string
getScrollback(): ScrollbackState
}TextPosition
interface TextPosition {
row: number
col: number
text: string
}Screenshots
screenshotSvg(options?)
Capture the terminal as an SVG string. See Screenshots for options.
const svg = term.screenshotSvg()
const svg = term.screenshotSvg({ theme: { background: "#282a36" } })screenshotPng(options?)
Capture the terminal as a PNG buffer. Requires @resvg/resvg-js (bun add -d @resvg/resvg-js). See Screenshots for options.
const png = await term.screenshotPng()
const png = await term.screenshotPng({ scale: 3, theme: { background: "#282a36" } })Resize
resize(cols, rows)
Resize the terminal. Updates both backend and PTY (if spawned).
term.resize(120, 40)Cleanup
close()
Close the terminal, kill any spawned process, and destroy the backend. Safe to call multiple times.
await term.close()Symbol.asyncDispose
Supports the using declaration for automatic cleanup:
await using term = createTerminal({ backend: createXtermBackend() })
// term.close() called automatically when scope exitsKey Utilities
import { parseKey, keyToAnsi } from "@termless/monorepo"
// Parse "Ctrl+a" -> { key: "a", ctrl: true }
const desc = parseKey("Ctrl+Shift+ArrowUp")
// Convert to ANSI escape sequence
const ansi = keyToAnsi("Ctrl+c") // "\x03"
const ansi = keyToAnsi({ key: "ArrowUp", ctrl: true }) // "\x1b[1;5A"hasExtension
Type-safe extension check for backend capabilities:
import { hasExtension } from "@termless/monorepo"
import type { MouseEncodingExtension } from "@termless/monorepo"
if (hasExtension<MouseEncodingExtension>(backend, "mouse")) {
const encoded = backend.encodeMouse({ x: 5, y: 10, button: "left", action: "press" })
}