Appearance
$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:
| Variable | Value | Purpose |
|---|---|---|
TERM | xterm-256color (default) | terminfo capability database |
COLORTERM | truecolor | Advertises 24-bit color. nvim, modern shells, and ls light up on this. |
TERM_PROGRAM | otty | The canonical "which terminal am I in?" probe. |
TERM_PROGRAM_VERSION | build version | Paired with TERM_PROGRAM for version gating. |
CW_TERM | otty | Stops Amazon Q / Fig / CodeWhisperer from exec-ing cwterm mid-.zshrc (which would suppress shell-integration marks). |
OTTY_PANE_ID | per-pane id | Identifies the pane a process runs in. Pass it to otty pane show --pane "$OTTY_PANE_ID" (or send-keys / capture) to act on the caller's own pane instead of whichever pane is focused. |
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=truecolorTERM_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.
Locating the caller's pane
OTTY_PANE_ID is a stable identifier for the pane a process runs in, inherited by every child process. It's the reliable way for a script to act on its own pane: the otty pane … commands otherwise default to the focused pane, which may not be the one your script is running in.
bash
# Info about the pane this script runs in — not the focused one
otty pane show --pane "$OTTY_PANE_ID"
# Capture the caller's own pane, or send it keys
otty pane capture --pane "$OTTY_PANE_ID" --lines 50The same id works as a --pane selector for the otty CLI; AppleScript exposes the equivalent per-tab state — including the tty device path — through its scripting dictionary.
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.
| Query | Otty's reply | Meaning |
|---|---|---|
DA1 — CSI c / CSI 0 c | CSI ? 6 c | "I'm a VT102-class terminal." Compact and conservative. |
DA2 — CSI > c | CSI > 0 ; <version> ; 1 c | Model 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:
| Query | Reply | Meaning |
|---|---|---|
CSI 5 n | CSI 0 n | Terminal is ready / OK. |
CSI 6 n (cursor position) | CSI <row> ; <col> R | 1-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: on the first connection to a host it checks whether the remote can already resolve your TERM. If it can — which is the case for a standard name like xterm-256color on any Linux — nothing is installed, because the distribution's own entry is the better one. Only when the remote has never heard of the name does Otty extract your terminfo with infocmp, pipe it over, and compile it there with tic into ~/.terminfo. That's what keeps a custom TERM such as xterm-ghostty working on a fresh box with no manual install. The result is cached per user@host, and connections that carry a remote command (ssh host 'some command') skip the check entirely.
Cleaning up entries left by Otty 1.3.0 and earlier
Through 1.3.0 that check was broken in a way that installed the entry anyway, under the standard name, on every host you ran a remote command against. It shadows the distribution's entry, which is usually the newer and more complete one — enough to change what the Backspace key and mouse reporting look like to remote programs.
To see whether a host is carrying one:
bash
otty ssh terminfo myserverIt prints which capabilities come back and which values change if the entry goes. otty ssh terminfo --clean myserver then moves it into ~/.terminfo-otty-backup on that host — moved, not deleted, so you can put it back with mv if it turns out to be one you installed yourself. Nothing is touched when the entry is the only one the host has for that name, since that is the install working as intended.
See also
- Shell Integration — the
sshwrapper that pushes terminfo and forwardsCOLORTERM/TERM_PROGRAM. - Unicode and Text Styles — what
COLORTERM=truecoloractually unlocks. - Configuration Reference — the
termkey.