Skip to content

$TERM and Identification

📸 TL;DR

DO NOT change it unless you understand what it is.

How Otty tells the programs running inside it what kind of terminal they're talking to — the $TERM value, the identification environment variables, and the escape-sequence replies that TUIs probe to unlock features.

$TERM

Every shell Otty launches gets a TERM environment variable. It controls which terminfo entry programs load, and therefore which capabilities they believe are available.

term = auto      # auto | xterm-256color | <any installed terminfo name>

auto (the default) resolves to xterm-256color — a deliberately conservative choice that's present on every Unix terminfo install and covers everything line editors need (cursor motion, line erase, 256-color). Truecolor is advertised separately through COLORTERM, so you don't lose 24-bit color by staying on xterm-256color.

Set term to any other value and Otty checks that a matching terminfo entry actually exists first. If it doesn't, Otty logs a warning and falls back to xterm-256color so the shell still gets a working terminfo — rather than leaving you with a broken TERM that mangles line editing over SSH or in less.

WARNING

Don't set term = xterm-kitty, term = xterm-ghostty, or another terminal's name hoping to inherit its features. TERM selects a capability database, not a behaviour — claiming to be a terminal you can't fully emulate makes programs emit sequences Otty doesn't handle. Otty advertises its real capabilities through device attributes and COLORTERM instead.

Environment variables

Alongside TERM, Otty exports a fixed set of identification variables into every child process:

VariableValuePurpose
TERMxterm-256color (default)terminfo capability database
COLORTERMtruecolorAdvertises 24-bit color. nvim, modern shells, and ls light up on this.
TERM_PROGRAMottyThe canonical "which terminal am I in?" probe.
TERM_PROGRAM_VERSIONbuild versionPaired with TERM_PROGRAM for version gating.
CW_TERMottyStops Amazon Q / Fig / CodeWhisperer from exec-ing cwterm mid-.zshrc (which would suppress shell-integration marks).

To check from inside a pane:

bash
echo "$TERM_PROGRAM ($TERM_PROGRAM_VERSION), TERM=$TERM, COLORTERM=$COLORTERM"
# otty (1.0.2), TERM=xterm-256color, COLORTERM=truecolor

TERM_PROGRAM=otty is the recommended way for scripts and prompts to detect Otty — it's stable across the TERM value you choose, survives tmux, and doesn't require parsing escape replies.

Device attributes

When a program sends a Device Attributes query, Otty answers synchronously over the PTY. These are the legacy DEC identification handshakes; most modern TUIs prefer XTVERSION below, but plenty still probe DA1/DA2.

QueryOtty's replyMeaning
DA1CSI c / CSI 0 cCSI ? 6 c"I'm a VT102-class terminal." Compact and conservative.
DA2CSI > cCSI > 0 ; <version> ; 1 cModel ID 0 (generic), build version, ROM slot 1.

The <version> in the DA2 reply is the build version encoded as a single integer: major × 10000 + minor × 100 + patch (any -rc/-beta suffix is dropped first). So a 1.0.2 build reports 10002. This matches the encoding xterm and Alacritty use, so version-gating logic written for them works unchanged.

DA3 (tertiary attributes) is not implemented — Otty sends no reply, which is the correct behaviour for an unsupported query.

XTVERSION

The modern terminal-identification probe. nvim, kakoune, and others send CSI > q and read back a free-form name+version string to gate features like undercurl, truecolor, and the Kitty keyboard protocol.

Otty replies with a DCS string:

DCS > | otty(<version>) ST

— transmitted as ESC P > | otty(<version>) ESC \. The reply is terminated with ESC \ (7-bit ST) so it survives 8-bit-clean PTYs, and <version> is the plain build version string (e.g. otty(1.0.2)). Programs that recognize the otty( prefix can enable Otty-supported features without guessing from TERM.

Cursor and status reports

Device Status Report queries are answered immediately:

QueryReplyMeaning
CSI 5 nCSI 0 nTerminal is ready / OK.
CSI 6 n (cursor position)CSI <row> ; <col> R1-based cursor position.

These power things like a shell's ability to detect where the cursor landed after printing a prompt.

terminfo

Otty ships a few precompiled terminfo entries inside the app bundle (Otty.app/Contents/Resources/terminfo/) and splices that directory to the front of TERMINFO_DIRS for every shell. This lets xterm-ghostty, alacritty, and friends resolve even if you've never installed those terminals — useful when a config or remote dotfile references one. The bundle path is searched first, so a shipped entry wins over an outdated system copy, and /usr/share/terminfo is appended so nothing else is hidden.

Over SSH, the ssh wrapper goes a step further: it extracts your current terminfo with infocmp, pipes it to the remote host, and compiles it there with tic into ~/.terminfo. The result is cached per user@host so it only happens on the first connection. That's why your TERM keeps working on a fresh remote box that's never heard of it — no manual terminfo install required.

See also

Otty