spb/khaelor Public
KHAELOR — a terminal-native autonomous engineering agent powered by Anthropic.
TypeScript 82.9%
HTML 14.9%
CSS 1.1%
JavaScript 0.7%
1/**2 * KHAELOR3 * File: src/tui/renderer/capabilities.ts4 * Description: Terminal capability detection — color depth ladder, NO_COLOR, DEC 2026 probe, OSC 11 background query.5 *6 * Author: Simon-Pierre Boucher7 * Contact: contact@spboucher.ai8 */910export type ColorDepth = "truecolor" | "ansi256" | "ansi16" | "mono";11export type Background = "dark" | "light";1213export interface TerminalCapabilities {14 colorDepth: ColorDepth;15 /** DEC 2026 synchronized-output supported (probed; false when unknown). */16 syncUpdates: boolean;17 background: Background;18}1920/**21 * Color capability ladder (TUI_DESIGN §12): truecolor → ANSI-256 → ANSI-16 →22 * monochrome. `NO_COLOR` and `TERM=dumb` are honored absolutely; non-TTY23 * output is monochrome.24 */25export function detectColorDepth(env: NodeJS.ProcessEnv, isTty: boolean): ColorDepth {26 if (!isTty) return "mono";27 if (env["NO_COLOR"] !== undefined && env["NO_COLOR"] !== "") return "mono";28 const term = env["TERM"] ?? "";29 if (term === "dumb" || term === "") return "mono";30 const colorterm = env["COLORTERM"] ?? "";31 if (colorterm === "truecolor" || colorterm === "24bit") return "truecolor";32 if (term.includes("256color")) return "ansi256";33 return "ansi16";34}3536/**37 * Parse a DECRQM reply for mode 2026: `CSI ? 2026 ; Ps $ y`.38 * Ps 1 (set) / 2 (reset) / 3 (permanently set) mean the mode is recognized →39 * synchronized updates are supported. Ps 0 (unrecognized) / 4 (permanently40 * reset) mean unsupported. Returns null when no complete reply is present.41 */42export function parseDecrqmReply(data: string): boolean | null {43 const m = /\x1b\[\?2026;(\d+)\$y/.exec(data);44 if (!m) return null;45 const ps = Number(m[1]);46 return ps === 1 || ps === 2 || ps === 3;47}4849/**50 * Parse an OSC 11 background-color reply (`OSC 11 ; rgb:RRRR/GGGG/BBBB ST|BEL`)51 * into light/dark via relative luma. Returns null when no complete reply is present.52 */53export function parseOsc11Reply(data: string): Background | null {54 const m = /\x1b\]11;rgb:([0-9a-fA-F]+)\/([0-9a-fA-F]+)\/([0-9a-fA-F]+)(?:\x07|\x1b\\)/.exec(data);55 if (!m) return null;56 const channel = (hex: string): number => {57 const max = Math.pow(16, hex.length) - 1;58 return parseInt(hex, 16) / max;59 };60 const r = channel(m[1] as string);61 const g = channel(m[2] as string);62 const b = channel(m[3] as string);63 const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;64 return luma > 0.5 ? "light" : "dark";65}6667export interface ProbeStreams {68 stdin: NodeJS.ReadStream;69 stdout: NodeJS.WriteStream;70}7172/**73 * Probe the terminal for DEC 2026 support and background color. Requires raw74 * mode to already be active (replies arrive on stdin without a newline).75 * Resolves within `timeoutMs` (default 100 ms — TUI_DESIGN §12) with whatever76 * was learned; absence of a reply degrades to `{ syncUpdates: false,77 * background: null }` — never a hang, never a guess presented as fact.78 */79export function probeTerminal(80 streams: ProbeStreams,81 timeoutMs = 100,82): Promise<{ syncUpdates: boolean; background: Background | null }> {83 const { stdin, stdout } = streams;84 if (!stdin.isTTY || !stdout.isTTY) {85 return Promise.resolve({ syncUpdates: false, background: null });86 }87 return new Promise((resolve) => {88 let buffer = "";89 let sync: boolean | null = null;90 let background: Background | null = null;91 let done = false;9293 const finish = (): void => {94 if (done) return;95 done = true;96 stdin.off("data", onData);97 clearTimeout(timer);98 resolve({ syncUpdates: sync ?? false, background });99 };100101 const onData = (chunk: Buffer | string): void => {102 buffer += chunk.toString("utf8");103 if (sync === null) sync = parseDecrqmReply(buffer);104 if (background === null) background = parseOsc11Reply(buffer);105 if (sync !== null && background !== null) finish();106 };107108 const timer = setTimeout(finish, timeoutMs);109 timer.unref();110 stdin.on("data", onData);111 // DECRQM for mode 2026, then OSC 11 background query.112 stdout.write("\x1b[?2026$p\x1b]11;?\x07");113 });114}115