@prometheus-ai/tui 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +704 -0
- package/dist/types/autocomplete.d.ts +76 -0
- package/dist/types/bracketed-paste.d.ts +26 -0
- package/dist/types/components/box.d.ts +17 -0
- package/dist/types/components/cancellable-loader.d.ts +21 -0
- package/dist/types/components/editor.d.ts +105 -0
- package/dist/types/components/image.d.ts +84 -0
- package/dist/types/components/input.d.ts +18 -0
- package/dist/types/components/loader.d.ts +13 -0
- package/dist/types/components/markdown.d.ts +61 -0
- package/dist/types/components/scroll-view.d.ts +40 -0
- package/dist/types/components/select-list.d.ts +48 -0
- package/dist/types/components/settings-list.d.ts +41 -0
- package/dist/types/components/spacer.d.ts +11 -0
- package/dist/types/components/tab-bar.d.ts +56 -0
- package/dist/types/components/text.d.ts +13 -0
- package/dist/types/components/truncated-text.d.ts +10 -0
- package/dist/types/deccara.d.ts +49 -0
- package/dist/types/editor-component.d.ts +36 -0
- package/dist/types/fuzzy.d.ts +15 -0
- package/dist/types/index.d.ts +28 -0
- package/dist/types/keybindings.d.ts +189 -0
- package/dist/types/keys.d.ts +208 -0
- package/dist/types/kill-ring.d.ts +27 -0
- package/dist/types/kitty-graphics.d.ts +94 -0
- package/dist/types/stdin-buffer.d.ts +43 -0
- package/dist/types/symbols.d.ts +25 -0
- package/dist/types/terminal-capabilities.d.ts +196 -0
- package/dist/types/terminal.d.ts +103 -0
- package/dist/types/ttyid.d.ts +9 -0
- package/dist/types/tui.d.ts +275 -0
- package/dist/types/utils.d.ts +89 -0
- package/package.json +73 -0
- package/src/autocomplete.ts +871 -0
- package/src/bracketed-paste.ts +47 -0
- package/src/components/box.ts +156 -0
- package/src/components/cancellable-loader.ts +40 -0
- package/src/components/editor.ts +2695 -0
- package/src/components/image.ts +318 -0
- package/src/components/input.ts +459 -0
- package/src/components/loader.ts +86 -0
- package/src/components/markdown.ts +1189 -0
- package/src/components/scroll-view.ts +166 -0
- package/src/components/select-list.ts +331 -0
- package/src/components/settings-list.ts +212 -0
- package/src/components/spacer.ts +28 -0
- package/src/components/tab-bar.ts +175 -0
- package/src/components/text.ts +110 -0
- package/src/components/truncated-text.ts +61 -0
- package/src/deccara.ts +314 -0
- package/src/editor-component.ts +71 -0
- package/src/fuzzy.ts +143 -0
- package/src/index.ts +44 -0
- package/src/keybindings.ts +279 -0
- package/src/keys.ts +537 -0
- package/src/kill-ring.ts +46 -0
- package/src/kitty-graphics.ts +270 -0
- package/src/stdin-buffer.ts +423 -0
- package/src/symbols.ts +26 -0
- package/src/terminal-capabilities.ts +1009 -0
- package/src/terminal.ts +1114 -0
- package/src/ttyid.ts +70 -0
- package/src/tui.ts +2988 -0
- package/src/utils.ts +452 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StdinBuffer buffers input and emits complete sequences.
|
|
3
|
+
*
|
|
4
|
+
* This is necessary because stdin data events can arrive in partial chunks,
|
|
5
|
+
* especially for escape sequences like mouse events. Without buffering,
|
|
6
|
+
* partial sequences can be misinterpreted as regular keypresses.
|
|
7
|
+
*
|
|
8
|
+
* For example, the mouse SGR sequence `\x1b[<35;20;5m` might arrive as:
|
|
9
|
+
* - Event 1: `\x1b`
|
|
10
|
+
* - Event 2: `[<35`
|
|
11
|
+
* - Event 3: `;20;5m`
|
|
12
|
+
*
|
|
13
|
+
* The buffer accumulates these until a complete sequence is detected.
|
|
14
|
+
* Call the `process()` method to feed input data.
|
|
15
|
+
*
|
|
16
|
+
* Based on code from OpenTUI (https://github.com/anomalyco/opentui)
|
|
17
|
+
* MIT License - Copyright (c) 2025 opentui
|
|
18
|
+
*/
|
|
19
|
+
import { EventEmitter } from "events";
|
|
20
|
+
export type StdinBufferOptions = {
|
|
21
|
+
/**
|
|
22
|
+
* Maximum time to wait for sequence completion (default: 75ms).
|
|
23
|
+
* After this time, a genuinely incomplete escape is flushed.
|
|
24
|
+
*/
|
|
25
|
+
timeout?: number;
|
|
26
|
+
};
|
|
27
|
+
export type StdinBufferEventMap = {
|
|
28
|
+
data: [string];
|
|
29
|
+
paste: [string];
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Buffers stdin input and emits complete sequences via the 'data' event.
|
|
33
|
+
* Handles partial escape sequences that arrive across multiple chunks.
|
|
34
|
+
*/
|
|
35
|
+
export declare class StdinBuffer extends EventEmitter<StdinBufferEventMap> {
|
|
36
|
+
#private;
|
|
37
|
+
constructor(options?: StdinBufferOptions);
|
|
38
|
+
process(data: string | Buffer): void;
|
|
39
|
+
flush(): string[];
|
|
40
|
+
clear(): void;
|
|
41
|
+
getBuffer(): string;
|
|
42
|
+
destroy(): void;
|
|
43
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface BoxSymbols {
|
|
2
|
+
topLeft: string;
|
|
3
|
+
topRight: string;
|
|
4
|
+
bottomLeft: string;
|
|
5
|
+
bottomRight: string;
|
|
6
|
+
horizontal: string;
|
|
7
|
+
vertical: string;
|
|
8
|
+
teeDown: string;
|
|
9
|
+
teeUp: string;
|
|
10
|
+
teeLeft: string;
|
|
11
|
+
teeRight: string;
|
|
12
|
+
cross: string;
|
|
13
|
+
}
|
|
14
|
+
export interface SymbolTheme {
|
|
15
|
+
cursor: string;
|
|
16
|
+
inputCursor: string;
|
|
17
|
+
boxRound: Omit<BoxSymbols, "teeDown" | "teeUp" | "teeLeft" | "teeRight" | "cross">;
|
|
18
|
+
boxSharp: BoxSymbols;
|
|
19
|
+
table: BoxSymbols;
|
|
20
|
+
quoteBorder: string;
|
|
21
|
+
hrChar: string;
|
|
22
|
+
/** Chip glyph drawn (painted with the referenced color) before inline hex colors. */
|
|
23
|
+
colorSwatch?: string;
|
|
24
|
+
spinnerFrames: string[];
|
|
25
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
export declare enum ImageProtocol {
|
|
2
|
+
Kitty = "\u001B_G",
|
|
3
|
+
Iterm2 = "\u001B]1337;File=",
|
|
4
|
+
Sixel = "\u001BPq"
|
|
5
|
+
}
|
|
6
|
+
export declare enum NotifyProtocol {
|
|
7
|
+
Bell = "\u0007",
|
|
8
|
+
Osc99 = "\u001B]99;;",
|
|
9
|
+
Osc9 = "\u001B]9;"
|
|
10
|
+
}
|
|
11
|
+
export type TerminalId = "kitty" | "ghostty" | "wezterm" | "iterm2" | "vscode" | "alacritty" | "base" | "trueColor";
|
|
12
|
+
/** Terminal capability details used for rendering and protocol selection. */
|
|
13
|
+
export declare class TerminalInfo {
|
|
14
|
+
readonly id: TerminalId;
|
|
15
|
+
readonly imageProtocol: ImageProtocol | null;
|
|
16
|
+
readonly trueColor: boolean;
|
|
17
|
+
readonly hyperlinks: boolean;
|
|
18
|
+
readonly notifyProtocol: NotifyProtocol;
|
|
19
|
+
readonly eagerEraseScrollbackRisk: boolean;
|
|
20
|
+
readonly deccara: boolean;
|
|
21
|
+
readonly supportsScreenToScrollback: boolean;
|
|
22
|
+
/** Renders the Kitty OSC 66 text-sizing protocol (scaled spans). Kitty only. */
|
|
23
|
+
readonly textSizing: boolean;
|
|
24
|
+
constructor(id: TerminalId, imageProtocol: ImageProtocol | null, trueColor: boolean, hyperlinks: boolean, notifyProtocol?: NotifyProtocol, eagerEraseScrollbackRisk?: boolean, deccara?: boolean, supportsScreenToScrollback?: boolean,
|
|
25
|
+
/** Renders the Kitty OSC 66 text-sizing protocol (scaled spans). Kitty only. */
|
|
26
|
+
textSizing?: boolean);
|
|
27
|
+
isImageLine(line: string): boolean;
|
|
28
|
+
formatNotification(message: string | TerminalNotification): string;
|
|
29
|
+
sendNotification(message: string | TerminalNotification): void;
|
|
30
|
+
}
|
|
31
|
+
export declare function isNotificationSuppressed(): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Returns true when running in Windows Terminal with known SIXEL support.
|
|
34
|
+
*
|
|
35
|
+
* Windows Terminal introduced SIXEL support in preview 1.22.
|
|
36
|
+
*/
|
|
37
|
+
export declare function isWindowsTerminalPreviewSixelSupported(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Whether live-frame native scrollback rebuilds are unsafe when the terminal
|
|
40
|
+
* viewport position is unobservable.
|
|
41
|
+
*
|
|
42
|
+
* A TUI history rebuild emits xterm ED3 (`CSI 3 J`, erase saved lines). Many
|
|
43
|
+
* terminals either clamp a scrolled reader back to the active tail or erase host
|
|
44
|
+
* scrollback when ED3 lands. The important property is not the brand name — it
|
|
45
|
+
* is that an unknown viewport position cannot be proven safe. Environment
|
|
46
|
+
* markers are therefore only used to prove *risk* or a strongly-known profile;
|
|
47
|
+
* unknown POSIX/remote/multiplexer shapes default to risky for passive renders.
|
|
48
|
+
*
|
|
49
|
+
* Native win32 is excluded here because the renderer has dedicated ConPTY
|
|
50
|
+
* deferral paths; a `WT_SESSION` sighting on POSIX means Windows Terminal is the
|
|
51
|
+
* outer host fronting WSL, where the same ED3 yank applies. See #1610/#1682/#1799.
|
|
52
|
+
*/
|
|
53
|
+
export declare function detectTerminalEagerEraseScrollbackRisk(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): boolean;
|
|
54
|
+
/** Whether DEC 2026 synchronized-output wrappers should be enabled by default. */
|
|
55
|
+
export declare function shouldEnableSynchronizedOutputByDefault(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform, terminalId?: TerminalId): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Whether the terminal applies Kitty-style DECCARA rectangular SGR changes
|
|
58
|
+
* (`CSI Pt ; Pl ; Pb ; Pr ; <sgr> $ r`) extended to background color, so large
|
|
59
|
+
* filled regions can be painted as rectangles instead of background-padded
|
|
60
|
+
* strings on every row.
|
|
61
|
+
*
|
|
62
|
+
* Verified against terminal sources rather than terminfo, because a bare
|
|
63
|
+
* `Cara`/DECCARA terminfo capability does not imply the Kitty SGR-background
|
|
64
|
+
* extension:
|
|
65
|
+
* - Kitty implements it for *all* SGR attributes including background (see
|
|
66
|
+
* kitty `docs/deccara.rst` and the `test_deccara` parser test).
|
|
67
|
+
* - Ghostty does NOT: its `CSI $ r` dispatch falls through to an "unknown CSI"
|
|
68
|
+
* warning and DECCARA/DECSACE are tracked as unsupported
|
|
69
|
+
* (ghostty-org/ghostty#632). Enabling it there would silently drop panel
|
|
70
|
+
* backgrounds, so ghostty stays on the padded-string fallback.
|
|
71
|
+
*
|
|
72
|
+
* Disabled under tmux/screen/zellij multiplexers — screen-coordinate rectangle
|
|
73
|
+
* protocols are not safe to assume through a multiplexer — and via the
|
|
74
|
+
* `PROMETHEUS_NO_DECCARA` kill switch. Pure helper for tests and `TERMINAL` construction.
|
|
75
|
+
*/
|
|
76
|
+
export declare function detectRectangularSgrSupport(terminalId: TerminalId, env?: NodeJS.ProcessEnv): boolean;
|
|
77
|
+
export declare const TERMINAL_ID: TerminalId;
|
|
78
|
+
export declare const TERMINAL: TerminalInfo;
|
|
79
|
+
/**
|
|
80
|
+
* Override terminal image protocol at runtime after capability probes complete.
|
|
81
|
+
*/
|
|
82
|
+
export declare function setTerminalImageProtocol(imageProtocol: ImageProtocol | null): void;
|
|
83
|
+
/**
|
|
84
|
+
* Override DECCARA rectangular-SGR capability at runtime. Used by tests to
|
|
85
|
+
* exercise the optimizer and fallback paths deterministically — the default is
|
|
86
|
+
* resolved once at import and force-disabled under the test runtime.
|
|
87
|
+
*/
|
|
88
|
+
export declare function setTerminalDeccara(enabled: boolean): void;
|
|
89
|
+
/** Override screen-to-scrollback clear support for targeted renderer tests. */
|
|
90
|
+
export declare function setTerminalScreenToScrollback(enabled: boolean): void;
|
|
91
|
+
/**
|
|
92
|
+
* Enable/disable OSC 66 text-sizing at runtime. The coding-agent calls this from
|
|
93
|
+
* the `tui.textSizing` setting (gated on the terminal's static `textSizing`
|
|
94
|
+
* capability); tests flip it directly to exercise the scaled-heading path.
|
|
95
|
+
*/
|
|
96
|
+
export declare function setTerminalTextSizing(enabled: boolean): void;
|
|
97
|
+
export declare function getTerminalInfo(terminalId: TerminalId): TerminalInfo;
|
|
98
|
+
export interface CellDimensions {
|
|
99
|
+
widthPx: number;
|
|
100
|
+
heightPx: number;
|
|
101
|
+
}
|
|
102
|
+
export interface ImageDimensions {
|
|
103
|
+
widthPx: number;
|
|
104
|
+
heightPx: number;
|
|
105
|
+
}
|
|
106
|
+
export interface ImageRenderOptions {
|
|
107
|
+
maxWidthCells?: number;
|
|
108
|
+
maxHeightCells?: number;
|
|
109
|
+
preserveAspectRatio?: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Stable Kitty image id (`i=`). When set, the image is displayed via a
|
|
112
|
+
* transmit-once + placement scheme keyed off this id instead of re-sending the
|
|
113
|
+
* base64 each frame.
|
|
114
|
+
*/
|
|
115
|
+
imageId?: number;
|
|
116
|
+
/** Stable Kitty placement id (`p=`); defaults to {@link imageId}. */
|
|
117
|
+
placementId?: number;
|
|
118
|
+
/** When true (Kitty + {@link imageId}), also return the one-time transmit sequence. */
|
|
119
|
+
includeTransmit?: boolean;
|
|
120
|
+
}
|
|
121
|
+
export declare function getCellDimensions(): CellDimensions;
|
|
122
|
+
export declare function setCellDimensions(dims: CellDimensions): void;
|
|
123
|
+
/** Transmit-and-display (`a=T`) — the self-contained form used when no stable id is available. */
|
|
124
|
+
export declare function encodeKitty(base64Data: string, options?: {
|
|
125
|
+
columns?: number;
|
|
126
|
+
rows?: number;
|
|
127
|
+
imageId?: number;
|
|
128
|
+
}): string;
|
|
129
|
+
/**
|
|
130
|
+
* Transmit image data only (`a=t`), keyed by `imageId`, without displaying it.
|
|
131
|
+
* Sent once per image; the data then persists in the terminal's store (it
|
|
132
|
+
* survives scroll-off and text clears for images with a non-zero id), so
|
|
133
|
+
* subsequent frames display it with the tiny {@link encodeKittyPlacement}
|
|
134
|
+
* sequence instead of re-sending the base64.
|
|
135
|
+
*/
|
|
136
|
+
export declare function encodeKittyTransmit(base64Data: string, imageId: number): string;
|
|
137
|
+
/**
|
|
138
|
+
* Display a previously transmitted image (`a=p`) at the cursor. Carrying a
|
|
139
|
+
* stable `placementId` (`p=`) means re-emitting the sequence on a repaint
|
|
140
|
+
* *replaces* the existing placement (moving/resizing it without flicker) rather
|
|
141
|
+
* than stacking a duplicate.
|
|
142
|
+
*/
|
|
143
|
+
export declare function encodeKittyPlacement(options: {
|
|
144
|
+
imageId: number;
|
|
145
|
+
placementId?: number;
|
|
146
|
+
columns?: number;
|
|
147
|
+
rows?: number;
|
|
148
|
+
}): string;
|
|
149
|
+
/**
|
|
150
|
+
* Kitty graphics delete command for a single image id. Uses `d=I` (capital)
|
|
151
|
+
* which removes the image and every one of its placements — on screen *and* in
|
|
152
|
+
* scrollback — and frees the backing data. `q=2` suppresses the terminal reply.
|
|
153
|
+
* Text-clearing escapes (`CSI 2 J` / `CSI 3 J`) do not remove Kitty graphics, so
|
|
154
|
+
* this is the only way to actually purge a placed image.
|
|
155
|
+
*/
|
|
156
|
+
export declare function encodeKittyDeleteImage(imageId: number): string;
|
|
157
|
+
export declare function encodeITerm2(base64Data: string, options?: {
|
|
158
|
+
width?: number | string;
|
|
159
|
+
height?: number | string;
|
|
160
|
+
name?: string;
|
|
161
|
+
preserveAspectRatio?: boolean;
|
|
162
|
+
inline?: boolean;
|
|
163
|
+
}): string;
|
|
164
|
+
export declare function calculateImageRows(imageDimensions: ImageDimensions, targetWidthCells: number, cellDimensions?: CellDimensions): number;
|
|
165
|
+
export declare function getPngDimensions(base64Data: string): ImageDimensions | null;
|
|
166
|
+
export declare function getJpegDimensions(base64Data: string): ImageDimensions | null;
|
|
167
|
+
export declare function getGifDimensions(base64Data: string): ImageDimensions | null;
|
|
168
|
+
export declare function getWebpDimensions(base64Data: string): ImageDimensions | null;
|
|
169
|
+
export declare function getImageDimensions(base64Data: string, mimeType: string): ImageDimensions | null;
|
|
170
|
+
export declare function renderImage(base64Data: string, imageDimensions: ImageDimensions, options?: ImageRenderOptions): {
|
|
171
|
+
sequence?: string;
|
|
172
|
+
lines?: string[];
|
|
173
|
+
rows: number;
|
|
174
|
+
transmit?: string;
|
|
175
|
+
} | null;
|
|
176
|
+
export declare function imageFallback(mimeType: string, dimensions?: ImageDimensions, filename?: string): string;
|
|
177
|
+
/**
|
|
178
|
+
* Structured terminal notification. Rich fields are honored only by OSC 99
|
|
179
|
+
* (Kitty) once support is confirmed; other protocols and the unconfirmed Kitty
|
|
180
|
+
* path collapse to a single `title: body` line.
|
|
181
|
+
*/
|
|
182
|
+
export interface TerminalNotification {
|
|
183
|
+
title?: string;
|
|
184
|
+
body?: string;
|
|
185
|
+
id?: string;
|
|
186
|
+
type?: string | string[];
|
|
187
|
+
urgency?: "low" | "normal" | "critical";
|
|
188
|
+
iconName?: string;
|
|
189
|
+
sound?: "silent" | "system" | "info" | "warning" | "error" | "question";
|
|
190
|
+
actions?: "focus" | "report" | "focus-report" | "none";
|
|
191
|
+
expiresMs?: number;
|
|
192
|
+
}
|
|
193
|
+
/** Record the OSC 99 capability-probe result (called by ProcessTerminal). */
|
|
194
|
+
export declare function setOsc99Supported(supported: boolean): void;
|
|
195
|
+
/** True when OSC 99 structured notifications have been confirmed available. */
|
|
196
|
+
export declare function isOsc99Supported(): boolean;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emergency terminal restore - call this from signal/crash handlers
|
|
3
|
+
* Resets terminal state without requiring access to the ProcessTerminal instance
|
|
4
|
+
*/
|
|
5
|
+
export declare function emergencyTerminalRestore(): void;
|
|
6
|
+
/** Terminal-reported appearance (dark/light mode). */
|
|
7
|
+
export type TerminalAppearance = "dark" | "light";
|
|
8
|
+
export interface Terminal {
|
|
9
|
+
start(onInput: (data: string) => void, onResize: () => void): void;
|
|
10
|
+
stop(): void;
|
|
11
|
+
/**
|
|
12
|
+
* Drain stdin before exiting to prevent Kitty key release events from
|
|
13
|
+
* leaking to the parent shell over slow SSH connections.
|
|
14
|
+
* @param maxMs - Maximum time to drain (default: 1000ms)
|
|
15
|
+
* @param idleMs - Exit early if no input arrives within this time (default: 50ms)
|
|
16
|
+
*/
|
|
17
|
+
drainInput(maxMs?: number, idleMs?: number): Promise<void>;
|
|
18
|
+
write(data: string): void;
|
|
19
|
+
get columns(): number;
|
|
20
|
+
get rows(): number;
|
|
21
|
+
get kittyProtocolActive(): boolean;
|
|
22
|
+
moveBy(lines: number): void;
|
|
23
|
+
hideCursor(): void;
|
|
24
|
+
showCursor(): void;
|
|
25
|
+
clearLine(): void;
|
|
26
|
+
clearFromCursor(): void;
|
|
27
|
+
clearScreen(): void;
|
|
28
|
+
setTitle(title: string): void;
|
|
29
|
+
setProgress(active: boolean): void;
|
|
30
|
+
/**
|
|
31
|
+
* Returns whether the native terminal viewport is at the scrollback tail when
|
|
32
|
+
* the host exposes that state. `undefined` means the terminal cannot report it.
|
|
33
|
+
*
|
|
34
|
+
* `ProcessTerminal` deliberately does not implement this — no real terminal
|
|
35
|
+
* can answer it truthfully:
|
|
36
|
+
*
|
|
37
|
+
* - POSIX terminals expose no scrollback-position API at all.
|
|
38
|
+
* - Every modern Windows terminal host (Windows Terminal, VS Code, Tabby,
|
|
39
|
+
* Hyper, Alacritty, WezTerm, JetBrains, …) fronts console apps through
|
|
40
|
+
* ConPTY, where kernel32's `GetConsoleScreenBufferInfo` describes the
|
|
41
|
+
* pseudo-console buffer. That buffer is pinned to the visible grid —
|
|
42
|
+
* scrollback lives in the host UI, invisible to console APIs
|
|
43
|
+
* (microsoft/terminal#10191) — so a probe reads "at bottom" no matter
|
|
44
|
+
* where the user scrolled. Trusting it let streaming-time rebuilds emit
|
|
45
|
+
* `\x1b[3J` and yank scrolled readers: #1635 (Windows Terminal), #1746
|
|
46
|
+
* (Tabby and other ConPTY hosts). No env var distinguishes these hosts
|
|
47
|
+
* (Tabby sets none), so trust cannot be conditional on the environment.
|
|
48
|
+
* - Legacy conhost (the only non-ConPTY host) keeps a real scrollback
|
|
49
|
+
* buffer, but its window follows the output cursor: a probe comparing
|
|
50
|
+
* `srWindow.Bottom` against `dwSize.Y - 1` reads "scrolled up" for a user
|
|
51
|
+
* following live output until all ~9001 buffer rows fill, permanently
|
|
52
|
+
* blocking checkpoint scrollback reconciliation.
|
|
53
|
+
*
|
|
54
|
+
* The renderer treats a missing implementation / `undefined` as "unknown":
|
|
55
|
+
* live mutations defer destructive rebuilds and reconcile native scrollback
|
|
56
|
+
* at explicit checkpoints (prompt submit), where the user's keystroke has
|
|
57
|
+
* already pinned the host viewport to the bottom. Only test terminals
|
|
58
|
+
* (xterm.js-backed) implement this with a real answer.
|
|
59
|
+
*/
|
|
60
|
+
isNativeViewportAtBottom?(): boolean | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* Override the global terminal-profile ED3 risk decision for custom/test
|
|
63
|
+
* terminals. `undefined` falls back to the resolved `TERMINAL` profile.
|
|
64
|
+
*/
|
|
65
|
+
hasEagerEraseScrollbackRisk?(): boolean | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* Register a callback for terminal appearance (dark/light) changes.
|
|
68
|
+
* Detection uses OSC 11 background color query with Mode 2031 as a change trigger.
|
|
69
|
+
* Fires when the detected appearance changes, including the initial detection.
|
|
70
|
+
*/
|
|
71
|
+
onAppearanceChange(callback: (appearance: TerminalAppearance) => void): void;
|
|
72
|
+
/** The last detected terminal appearance, or undefined if not yet known. */
|
|
73
|
+
get appearance(): TerminalAppearance | undefined;
|
|
74
|
+
/**
|
|
75
|
+
* Register a callback fired once per DEC private mode when its DECRQM support
|
|
76
|
+
* status resolves. Optional: only real terminals implement capability probing.
|
|
77
|
+
*/
|
|
78
|
+
onPrivateModeReport?(callback: (mode: number, supported: boolean) => void): void;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Real terminal using process.stdin/stdout
|
|
82
|
+
*/
|
|
83
|
+
export declare class ProcessTerminal implements Terminal {
|
|
84
|
+
#private;
|
|
85
|
+
get kittyProtocolActive(): boolean;
|
|
86
|
+
get appearance(): TerminalAppearance | undefined;
|
|
87
|
+
onAppearanceChange(callback: (appearance: TerminalAppearance) => void): void;
|
|
88
|
+
onPrivateModeReport(callback: (mode: number, supported: boolean) => void): void;
|
|
89
|
+
start(onInput: (data: string) => void, onResize: () => void): void;
|
|
90
|
+
drainInput(maxMs?: number, idleMs?: number): Promise<void>;
|
|
91
|
+
stop(): void;
|
|
92
|
+
write(data: string): void;
|
|
93
|
+
get columns(): number;
|
|
94
|
+
get rows(): number;
|
|
95
|
+
moveBy(lines: number): void;
|
|
96
|
+
hideCursor(): void;
|
|
97
|
+
showCursor(): void;
|
|
98
|
+
clearLine(): void;
|
|
99
|
+
clearFromCursor(): void;
|
|
100
|
+
clearScreen(): void;
|
|
101
|
+
setTitle(title: string): void;
|
|
102
|
+
setProgress(active: boolean): void;
|
|
103
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Resolve the TTY device path for stdin (fd 0) via POSIX `ttyname(3)`. */
|
|
2
|
+
export declare function getTtyPath(): string | null;
|
|
3
|
+
/**
|
|
4
|
+
* Get a stable identifier for the current terminal.
|
|
5
|
+
* Uses the TTY device path (e.g., /dev/pts/3), falling back to environment
|
|
6
|
+
* variables for terminal multiplexers or terminal emulators.
|
|
7
|
+
* Returns null if no terminal can be identified (e.g., piped input).
|
|
8
|
+
*/
|
|
9
|
+
export declare function getTerminalId(): string | null;
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { ImageBudget } from "./components/image";
|
|
2
|
+
import type { Terminal } from "./terminal";
|
|
3
|
+
import { visibleWidth } from "./utils";
|
|
4
|
+
type InputListenerResult = {
|
|
5
|
+
consume?: boolean;
|
|
6
|
+
data?: string;
|
|
7
|
+
} | undefined;
|
|
8
|
+
type InputListener = (data: string) => InputListenerResult;
|
|
9
|
+
export interface RenderTimer {
|
|
10
|
+
cancel(): void;
|
|
11
|
+
}
|
|
12
|
+
export interface RenderScheduler {
|
|
13
|
+
now(): number;
|
|
14
|
+
scheduleImmediate(callback: () => void): void;
|
|
15
|
+
scheduleRender(callback: () => void, delayMs: number): RenderTimer;
|
|
16
|
+
}
|
|
17
|
+
export interface TUIOptions {
|
|
18
|
+
renderScheduler?: RenderScheduler;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Component interface - all components must implement this
|
|
22
|
+
*/
|
|
23
|
+
export interface Component {
|
|
24
|
+
/**
|
|
25
|
+
* Render the component to lines for the given viewport width
|
|
26
|
+
* @param width - Current viewport width
|
|
27
|
+
* @returns Array of strings, each representing a line
|
|
28
|
+
*/
|
|
29
|
+
render(width: number): string[];
|
|
30
|
+
/**
|
|
31
|
+
* Optional handler for keyboard input when component has focus
|
|
32
|
+
*/
|
|
33
|
+
handleInput?(data: string): void;
|
|
34
|
+
/**
|
|
35
|
+
* If true, component receives key release events (Kitty protocol).
|
|
36
|
+
* Default is false - release events are filtered out.
|
|
37
|
+
*/
|
|
38
|
+
wantsKeyRelease?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Invalidate any cached rendering state.
|
|
41
|
+
* Called when theme changes or when component needs to re-render from scratch.
|
|
42
|
+
*/
|
|
43
|
+
invalidate(): void;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Optional component seam for native-scrollback pinning. A component that
|
|
47
|
+
* renders a stable prefix followed by a live/transient suffix reports the local
|
|
48
|
+
* line index where that suffix begins after each render. TUI treats that suffix
|
|
49
|
+
* — and every root child rendered below it — as not yet safe to commit to native
|
|
50
|
+
* scrollback on ED3-risk terminals whose viewport position is unobservable.
|
|
51
|
+
*
|
|
52
|
+
* `getNativeScrollbackCommitSafeEnd` optionally reports a *deeper* boundary
|
|
53
|
+
* inside that live suffix: the line index up to which the live region is
|
|
54
|
+
* append-only (its earlier rows never re-layout, only new rows append at the
|
|
55
|
+
* bottom — a streaming assistant message). Rows in `[liveRegionStart,
|
|
56
|
+
* commitSafeEnd)` that scroll above the viewport are safe to commit to native
|
|
57
|
+
* scrollback even though they are technically live, because they will never
|
|
58
|
+
* change. Without this, a single live block that alone overflows the viewport
|
|
59
|
+
* loses its scrolled-off head (committed nowhere, repainted nowhere). Volatile
|
|
60
|
+
* live blocks (tool previews that collapse) omit it, so their mutable rows stay
|
|
61
|
+
* deferred. Defaults to `liveRegionStart` when absent.
|
|
62
|
+
*/
|
|
63
|
+
export interface NativeScrollbackLiveRegion {
|
|
64
|
+
getNativeScrollbackLiveRegionStart(): number | undefined;
|
|
65
|
+
getNativeScrollbackCommitSafeEnd?(): number | undefined;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Interface for components that can receive focus and display a cursor.
|
|
69
|
+
* When focused, the component should emit CURSOR_MARKER at the cursor position
|
|
70
|
+
* in its render output. TUI will find this marker and position the hardware
|
|
71
|
+
* cursor there for proper IME candidate window positioning.
|
|
72
|
+
*
|
|
73
|
+
* Components that can switch between terminal-cursor and software-cursor
|
|
74
|
+
* rendering expose `setUseTerminalCursor`; TUI keeps that mode in sync with
|
|
75
|
+
* its resolved hardware-cursor preference whenever focus or the preference
|
|
76
|
+
* changes.
|
|
77
|
+
*/
|
|
78
|
+
export interface Focusable {
|
|
79
|
+
/** Set by TUI when focus changes. Component should emit CURSOR_MARKER when true. */
|
|
80
|
+
focused: boolean;
|
|
81
|
+
/** Set by TUI when hardware cursor rendering is enabled or disabled. */
|
|
82
|
+
setUseTerminalCursor?(useTerminalCursor: boolean): void;
|
|
83
|
+
}
|
|
84
|
+
/** Options for scheduling a TUI render. */
|
|
85
|
+
export interface RenderRequestOptions {
|
|
86
|
+
/** Clear terminal scrollback for intentional transcript replacement. */
|
|
87
|
+
clearScrollback?: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Bypass the unknown-Windows-viewport deferral for this render so the
|
|
90
|
+
* caller's intentional live UI mutation reaches the terminal even when
|
|
91
|
+
* `Terminal#isNativeViewportAtBottom()` cannot answer.
|
|
92
|
+
*
|
|
93
|
+
* Use only for renders driven by direct user interaction (autocomplete
|
|
94
|
+
* updates, IME, etc.). Any background/offscreen transcript change that
|
|
95
|
+
* coalesces into the same frame WILL also bypass the deferral and reach
|
|
96
|
+
* native scrollback — that is the trade-off, and the reason ordinary
|
|
97
|
+
* `requestRender()` calls must continue to omit this flag.
|
|
98
|
+
*/
|
|
99
|
+
allowUnknownViewportMutation?: boolean;
|
|
100
|
+
}
|
|
101
|
+
/** Options for deferred native scrollback rebuild checkpoints. Reserved for API stability. */
|
|
102
|
+
export interface NativeScrollbackRefreshOptions {
|
|
103
|
+
allowUnknownViewport?: boolean;
|
|
104
|
+
}
|
|
105
|
+
/** Type guard to check if a component implements Focusable */
|
|
106
|
+
export declare function isFocusable(component: Component | null): component is Component & Focusable;
|
|
107
|
+
/**
|
|
108
|
+
* Cursor position marker - APC (Application Program Command) sequence.
|
|
109
|
+
* This is a zero-width escape sequence that terminals ignore.
|
|
110
|
+
* Components emit this at the cursor position when focused.
|
|
111
|
+
* TUI finds and strips this marker, then positions the hardware cursor there.
|
|
112
|
+
*/
|
|
113
|
+
export declare const CURSOR_MARKER = "\u001B_pi:c\u0007";
|
|
114
|
+
export { visibleWidth };
|
|
115
|
+
/**
|
|
116
|
+
* Anchor position for overlays
|
|
117
|
+
*/
|
|
118
|
+
export type OverlayAnchor = "center" | "top-left" | "top-right" | "bottom-left" | "bottom-right" | "top-center" | "bottom-center" | "left-center" | "right-center";
|
|
119
|
+
/**
|
|
120
|
+
* Margin configuration for overlays
|
|
121
|
+
*/
|
|
122
|
+
export interface OverlayMargin {
|
|
123
|
+
top?: number;
|
|
124
|
+
right?: number;
|
|
125
|
+
bottom?: number;
|
|
126
|
+
left?: number;
|
|
127
|
+
}
|
|
128
|
+
/** Value that can be absolute (number) or percentage (string like "50%") */
|
|
129
|
+
export type SizeValue = number | `${number}%`;
|
|
130
|
+
/**
|
|
131
|
+
* Options for overlay positioning and sizing.
|
|
132
|
+
* Values can be absolute numbers or percentage strings (e.g., "50%").
|
|
133
|
+
*/
|
|
134
|
+
export interface OverlayOptions {
|
|
135
|
+
/** Width in columns, or percentage of terminal width (e.g., "50%") */
|
|
136
|
+
width?: SizeValue;
|
|
137
|
+
/** Minimum width in columns */
|
|
138
|
+
minWidth?: number;
|
|
139
|
+
/** Maximum height in rows, or percentage of terminal height (e.g., "50%") */
|
|
140
|
+
maxHeight?: SizeValue;
|
|
141
|
+
/** Anchor point for positioning (default: 'center') */
|
|
142
|
+
anchor?: OverlayAnchor;
|
|
143
|
+
/** Horizontal offset from anchor position (positive = right) */
|
|
144
|
+
offsetX?: number;
|
|
145
|
+
/** Vertical offset from anchor position (positive = down) */
|
|
146
|
+
offsetY?: number;
|
|
147
|
+
/** Row position: absolute number, or percentage (e.g., "25%" = 25% from top) */
|
|
148
|
+
row?: SizeValue;
|
|
149
|
+
/** Column position: absolute number, or percentage (e.g., "50%" = centered horizontally) */
|
|
150
|
+
col?: SizeValue;
|
|
151
|
+
/** Margin from terminal edges. Number applies to all sides. */
|
|
152
|
+
margin?: OverlayMargin | number;
|
|
153
|
+
/**
|
|
154
|
+
* Control overlay visibility based on terminal dimensions.
|
|
155
|
+
* If provided, overlay is only rendered when this returns true.
|
|
156
|
+
* Called each render cycle with current terminal dimensions.
|
|
157
|
+
*/
|
|
158
|
+
visible?: (termWidth: number, termHeight: number) => boolean;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Handle returned by showOverlay for controlling the overlay
|
|
162
|
+
*/
|
|
163
|
+
export interface OverlayHandle {
|
|
164
|
+
/** Permanently remove the overlay (cannot be shown again) */
|
|
165
|
+
hide(): void;
|
|
166
|
+
/** Temporarily hide or show the overlay */
|
|
167
|
+
setHidden(hidden: boolean): void;
|
|
168
|
+
/** Check if overlay is temporarily hidden */
|
|
169
|
+
isHidden(): boolean;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Container - a component that contains other components
|
|
173
|
+
*/
|
|
174
|
+
export declare class Container implements Component {
|
|
175
|
+
children: Component[];
|
|
176
|
+
addChild(component: Component): void;
|
|
177
|
+
removeChild(component: Component): void;
|
|
178
|
+
clear(): void;
|
|
179
|
+
invalidate(): void;
|
|
180
|
+
render(width: number): string[];
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* TUI - Main class for managing terminal UI with differential rendering
|
|
184
|
+
*/
|
|
185
|
+
export declare class TUI extends Container {
|
|
186
|
+
#private;
|
|
187
|
+
terminal: Terminal;
|
|
188
|
+
/** Global callback for debug key (Shift+Ctrl+D). Called before input is forwarded to focused component. */
|
|
189
|
+
onDebug?: () => void;
|
|
190
|
+
overlayStack: {
|
|
191
|
+
component: Component;
|
|
192
|
+
options?: OverlayOptions;
|
|
193
|
+
preFocus: Component | null;
|
|
194
|
+
hidden: boolean;
|
|
195
|
+
}[];
|
|
196
|
+
constructor(terminal: Terminal, showHardwareCursor?: boolean, options?: TUIOptions);
|
|
197
|
+
render(width: number): string[];
|
|
198
|
+
get fullRedraws(): number;
|
|
199
|
+
/** Shared budget that caps how many inline images render as live graphics. */
|
|
200
|
+
get imageBudget(): ImageBudget;
|
|
201
|
+
/**
|
|
202
|
+
* Set how many inline images stay live graphics before older ones fall back
|
|
203
|
+
* to text (`0` disables the cap). Older images are hidden via a graphics purge
|
|
204
|
+
* plus a full redraw on the frame after a new image exceeds the cap.
|
|
205
|
+
*/
|
|
206
|
+
setMaxInlineImages(cap: number): void;
|
|
207
|
+
getShowHardwareCursor(): boolean;
|
|
208
|
+
setShowHardwareCursor(enabled: boolean): void;
|
|
209
|
+
getClearOnShrink(): boolean;
|
|
210
|
+
/**
|
|
211
|
+
* Set whether to trigger full re-render when content shrinks.
|
|
212
|
+
* When true (default), empty rows are cleared when content shrinks.
|
|
213
|
+
* When false, empty rows remain (reduces redraws on slower terminals).
|
|
214
|
+
*/
|
|
215
|
+
setClearOnShrink(enabled: boolean): void;
|
|
216
|
+
/**
|
|
217
|
+
* Whether DEC 2026 synchronized-output wrappers are currently emitted around
|
|
218
|
+
* paints. Starts from conservative terminal/env detection and is force-disabled
|
|
219
|
+
* at runtime if the terminal reports mode 2026 unsupported via DECRQM.
|
|
220
|
+
*/
|
|
221
|
+
get synchronizedOutput(): boolean;
|
|
222
|
+
/**
|
|
223
|
+
* When enabled, live render frames rebuild native scrollback on offscreen and
|
|
224
|
+
* structural changes even when the viewport position is unobservable (POSIX,
|
|
225
|
+
* where `isNativeViewportAtBottom()` is `undefined`), instead of deferring to a
|
|
226
|
+
* non-destructive repaint. This trades the anti-yank guarantee for a clean,
|
|
227
|
+
* duplicate-free history and is meant for windows where output above the fold
|
|
228
|
+
* is actively re-rendering — e.g. a tool whose result is still streaming and
|
|
229
|
+
* re-laying-out rows that have already scrolled into history. A terminal that
|
|
230
|
+
* reports a *known*-scrolled viewport still defers, as does native Windows
|
|
231
|
+
* (the viewport is never observable there and ConPTY hosts erase host
|
|
232
|
+
* scrollback on ED3 — #1635/#1746); only the unknown POSIX case is forced to
|
|
233
|
+
* rebuild. POSIX hosts known to disturb scrolled readers on xterm ED3
|
|
234
|
+
* (`CSI 3 J`, erase saved lines) also defer the eager opt-in; checkpoint
|
|
235
|
+
* rebuilds are unaffected.
|
|
236
|
+
*
|
|
237
|
+
* Disabling stays active through one already-requested frame: the event batch
|
|
238
|
+
* that ends a foreground stream both removes its UI rows (loader/status
|
|
239
|
+
* teardown — a shrink) and clears this flag before the throttled render timer
|
|
240
|
+
* fires. If the flag dropped immediately, that teardown frame would hit the
|
|
241
|
+
* ED3-risk idle deferral and freeze on screen (stale spinner) until the next
|
|
242
|
+
* keystroke. When no render is pending, disable immediately so a later
|
|
243
|
+
* unrelated content mutation does not inherit foreground-stream privileges.
|
|
244
|
+
*/
|
|
245
|
+
setEagerNativeScrollbackRebuild(enabled: boolean): void;
|
|
246
|
+
setFocus(component: Component | null): void;
|
|
247
|
+
/**
|
|
248
|
+
* Show an overlay component with configurable positioning and sizing.
|
|
249
|
+
* Returns a handle to control the overlay's visibility.
|
|
250
|
+
*/
|
|
251
|
+
showOverlay(component: Component, options?: OverlayOptions): OverlayHandle;
|
|
252
|
+
/** Hide the topmost overlay and restore previous focus. */
|
|
253
|
+
hideOverlay(): void;
|
|
254
|
+
/** Check if there are any visible overlays */
|
|
255
|
+
hasOverlay(): boolean;
|
|
256
|
+
invalidate(): void;
|
|
257
|
+
start(): void;
|
|
258
|
+
addInputListener(listener: InputListener): () => void;
|
|
259
|
+
removeInputListener(listener: InputListener): void;
|
|
260
|
+
stop(): void;
|
|
261
|
+
/**
|
|
262
|
+
* Rebuild native terminal scrollback if live rendering deferred a history rewrite.
|
|
263
|
+
* Callers should only invoke this at checkpoints where the user is expected to be
|
|
264
|
+
* at the terminal bottom, such as after submitting a new prompt.
|
|
265
|
+
*/
|
|
266
|
+
refreshNativeScrollbackIfDirty(_options?: NativeScrollbackRefreshOptions): boolean;
|
|
267
|
+
/**
|
|
268
|
+
* Force an immediate full replay of the current frame, including native
|
|
269
|
+
* scrollback. This is the keyboard-accessible equivalent of the resize reset:
|
|
270
|
+
* no queued diff frame or terminal scrollback probe can downgrade it to a
|
|
271
|
+
* viewport-only repaint.
|
|
272
|
+
*/
|
|
273
|
+
resetDisplay(): void;
|
|
274
|
+
requestRender(force?: boolean, options?: RenderRequestOptions): void;
|
|
275
|
+
}
|