/** * Terminal capability detection via environment variables. * * Computed once at import time — terminal capabilities don't change mid-session. * No async queries (DECRQM, DA) needed; env vars cover the common terminals. */ const env = process.env; const termProgram = env.TERM_PROGRAM ?? ""; const termVar = env.TERM ?? ""; /** Detected terminal capabilities. */ export interface TerminalCaps { /** OSC 8 hyperlinks supported */ hyperlinks: boolean; /** Kitty keyboard protocol (CSI u) supported */ kittyKeyboard: boolean; /** SGR mouse reporting supported */ mouse: boolean; /** Synchronized output (DEC 2026) supported */ syncOutput: boolean; /** Terminal name (best guess) */ terminal: string; /** 24-bit true color supported */ trueColor: boolean; } // --- Shared terminal sets --- const SYNC_TERM_PROGRAMS = new Set([ "iTerm.app", "WezTerm", "WarpTerminal", "ghostty", "contour", "vscode", "alacritty", "zed", ]); const TRUECOLOR_TERM_PROGRAMS = new Set([ "iTerm.app", "WezTerm", "WarpTerminal", "ghostty", "vscode", "alacritty", "Hyper", "zed", ]); const HYPERLINK_TERM_PROGRAMS = new Set([ "iTerm.app", "WezTerm", "ghostty", "alacritty", "Hyper", "vscode", "zed", ]); const KITTY_KEYBOARD_TERM_PROGRAMS = new Set([ "iTerm.app", "WezTerm", "ghostty", "zed", ]); const KITTY_TERM_VARS = new Set([ "kitty", "xterm-ghostty", "foot", "alacritty", ]); function getVteVersion(): number { const raw = env.VTE_VERSION; if (!raw) return 0; const v = Number.parseInt(raw, 10); return Number.isNaN(v) ? 0 : v; } function isKittyEnv(): boolean { return !!env.KITTY_WINDOW_ID; } // --- Detectors --- function detectTerminal(): string { if (env.TERM_PROGRAM) return env.TERM_PROGRAM; if (env.KITTY_WINDOW_ID) return "kitty"; if (env.WT_SESSION) return "windows-terminal"; if (env.ZED_TERM) return "zed"; if (env.TERM) return env.TERM; return "unknown"; } function detectSyncOutput(): boolean { if (env.TMUX) return false; if (SYNC_TERM_PROGRAMS.has(termProgram)) return true; if (KITTY_TERM_VARS.has(termVar)) return true; if (isKittyEnv()) return true; if (env.ZED_TERM) return true; if (env.WT_SESSION) return true; if (getVteVersion() >= 6800) return true; return false; } function detectTrueColor(): boolean { if (env.COLORTERM === "truecolor" || env.COLORTERM === "24bit") return true; if (TRUECOLOR_TERM_PROGRAMS.has(termProgram)) return true; if (KITTY_TERM_VARS.has(termVar)) return true; if (isKittyEnv()) return true; if (env.WT_SESSION) return true; return false; } function detectKittyKeyboard(): boolean { if (KITTY_KEYBOARD_TERM_PROGRAMS.has(termProgram)) return true; if (KITTY_TERM_VARS.has(termVar)) return true; if (isKittyEnv()) return true; if (env.TMUX) return true; return false; } function detectHyperlinks(): boolean { if (env.TMUX) return false; if (HYPERLINK_TERM_PROGRAMS.has(termProgram)) return true; if (KITTY_TERM_VARS.has(termVar)) return true; if (isKittyEnv()) return true; if (env.WT_SESSION) return true; if (getVteVersion() >= 5000) return true; return false; } function detectMouse(): boolean { return process.stdout.isTTY === true; } /** Cached terminal capabilities — computed once at import time. */ export const terminalCaps: TerminalCaps = { terminal: detectTerminal(), syncOutput: detectSyncOutput(), trueColor: detectTrueColor(), kittyKeyboard: detectKittyKeyboard(), hyperlinks: detectHyperlinks(), mouse: detectMouse(), };