/** * PTY Manager - Manages terminal sessions using node-pty */ import * as pty from '@homebridge/node-pty-prebuilt-multiarch'; import { TerminalInfo } from './types'; interface TerminalSession { id: string; pty: pty.IPty; cwd: string; shell: string; cols: number; rows: number; created: number; lastActivity: number; outputBuffer: string; activeModes: Set; dataListeners: Set<(data: string) => void>; exitListeners: Set<(code: number) => void>; } export declare class PtyManager { private sessions; /** * Get the default shell for the current platform */ private getDefaultShell; /** * Normalize a terminal name to a valid ID * - Lowercase * - Only alphanumeric, hyphens, underscores * - Max 32 chars * - Falls back to 'default' if empty/invalid */ normalizeTerminalName(name: string): string; /** * Create a new terminal session */ create(options?: { id?: string; cwd?: string; shell?: string; cols?: number; rows?: number; }): TerminalInfo; /** * Get terminal info from session */ private getInfo; /** * Get a terminal session by ID */ get(id: string): TerminalSession | undefined; /** Foreground process names that mean "idle prompt, nothing running". */ private static PLAIN_SHELL_RE; /** * Whether something OWNS THE TTY FOREGROUND of this pty — an agent TUI, an * ssh hop, a command the user ran. `false` means the foreground is the shell * itself (idle prompt) with certainty; `null` means unknown (no such pty, or * the foreground can't be read) and callers must not conclude anything. * * Foreground, NOT a child-process scan: a login shell sourcing its rc files * (modules, conda init — seconds on GPFS) has transient CHILDREN from t=0 * but never yields the tty to them, so a child-scan reported every freshly * created pty as busy — the launch guard then adopted a nonexistent agent * instead of typing the launch command ("start a new agent only opens a * terminal"). The tty's foreground process group is the honest signal, and * a single O(1) native read (tcgetpgrp) — no process-table walk. */ isBusy(id: string): boolean | null; /** * Get terminal info by ID */ getTerminalInfo(id: string): TerminalInfo | undefined; /** * List all active terminals */ list(): TerminalInfo[]; /** * Write data to a terminal */ write(id: string, data: string): boolean; /** * Resize a terminal */ resize(id: string, cols: number, rows: number): boolean; /** * Get or create a terminal by name * If a terminal with the normalized name exists, return it * Otherwise create a new one with that name */ getOrCreate(name: string, options?: { cwd?: string; shell?: string; cols?: number; rows?: number; }): TerminalInfo; /** * Kill a terminal session */ kill(id: string): boolean; /** * Subscribe to a terminal's output. Returns an unsubscribe function * (no-op if the terminal is gone). */ addDataListener(id: string, callback: (data: string) => void): () => void; /** * Subscribe to a terminal's exit. Returns an unsubscribe function. */ addExitListener(id: string, callback: (code: number) => void): () => void; /** * Get buffered output for reconnection */ getOutputBuffer(id: string): string; /** * Sequences that reassert the sticky input modes (see TRACKED_INPUT_MODES) to * the authoritative state observed on the full pty stream. Appended after the * replay buffer so a reconnect never leaves mouse/focus reporting dangling on * at the shell: the 100 KB buffer trim can drop or split the app's own * mode-reset, and this puts it back deterministically. */ getModeReset(id: string): string; /** * Kill all terminal sessions (for cleanup) */ killAll(): void; } export declare const ptyManager: PtyManager; export {};