@linxiraos/pi-tui 1.1.0 → 1.1.2

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.
Files changed (50) hide show
  1. package/CHANGELOG.md +10 -1
  2. package/THIRD-PARTY-NOTICES.txt +22909 -0
  3. package/dist/types/autocomplete.d.ts +127 -0
  4. package/dist/types/bracketed-paste.d.ts +51 -0
  5. package/dist/types/components/box.d.ts +31 -0
  6. package/dist/types/components/cancellable-loader.d.ts +21 -0
  7. package/dist/types/components/composer/borderless.d.ts +8 -0
  8. package/dist/types/components/composer/box.d.ts +2 -0
  9. package/dist/types/components/composer/claude.d.ts +8 -0
  10. package/dist/types/components/composer/field.d.ts +3 -0
  11. package/dist/types/components/composer/index.d.ts +9 -0
  12. package/dist/types/components/composer/pi.d.ts +2 -0
  13. package/dist/types/components/composer/rail.d.ts +3 -0
  14. package/dist/types/components/composer/registry.d.ts +12 -0
  15. package/dist/types/components/composer/rule.d.ts +7 -0
  16. package/dist/types/components/composer/types.d.ts +90 -0
  17. package/dist/types/components/editor.d.ts +213 -0
  18. package/dist/types/components/image.d.ts +162 -0
  19. package/dist/types/components/input.d.ts +25 -0
  20. package/dist/types/components/loader.d.ts +25 -0
  21. package/dist/types/components/markdown.d.ts +86 -0
  22. package/dist/types/components/scroll-view.d.ts +62 -0
  23. package/dist/types/components/select-list.d.ts +78 -0
  24. package/dist/types/components/settings-list.d.ts +129 -0
  25. package/dist/types/components/spacer.d.ts +11 -0
  26. package/dist/types/components/tab-bar.d.ts +89 -0
  27. package/dist/types/components/text.d.ts +27 -0
  28. package/dist/types/components/truncated-text.d.ts +10 -0
  29. package/dist/types/deccara.d.ts +49 -0
  30. package/dist/types/desktop-notify.d.ts +52 -0
  31. package/dist/types/editor-component.d.ts +38 -0
  32. package/dist/types/fuzzy.d.ts +48 -0
  33. package/dist/types/index.d.ts +33 -0
  34. package/dist/types/keybindings.d.ts +202 -0
  35. package/dist/types/keys.d.ts +210 -0
  36. package/dist/types/kill-ring.d.ts +20 -0
  37. package/dist/types/kitty-graphics.d.ts +76 -0
  38. package/dist/types/latex-block.d.ts +8 -0
  39. package/dist/types/latex-to-unicode.d.ts +50 -0
  40. package/dist/types/loop-watchdog.d.ts +49 -0
  41. package/dist/types/mouse.d.ts +67 -0
  42. package/dist/types/stdin-buffer.d.ts +60 -0
  43. package/dist/types/symbols.d.ts +25 -0
  44. package/dist/types/terminal-capabilities.d.ts +338 -0
  45. package/dist/types/terminal.d.ts +215 -0
  46. package/dist/types/tmux.d.ts +6 -0
  47. package/dist/types/ttyid.d.ts +9 -0
  48. package/dist/types/tui.d.ts +332 -0
  49. package/dist/types/utils.d.ts +112 -0
  50. package/package.json +71 -68
@@ -0,0 +1,338 @@
1
+ import type { HangulCompatibilityJamoWidth } from "./utils.js";
2
+ export { isInsideTmux, wrapTmuxPassthrough } from "./tmux.js";
3
+ export declare enum ImageProtocol {
4
+ Kitty = "\u001B_G",
5
+ Iterm2 = "\u001B]1337;File=",
6
+ Sixel = "\u001BPq"
7
+ }
8
+ export declare enum NotifyProtocol {
9
+ Bell = "\u0007",
10
+ Osc99 = "\u001B]99;;",
11
+ Osc9 = "\u001B]9;"
12
+ }
13
+ export type TerminalId = "kitty" | "ghostty" | "wezterm" | "iterm2" | "vscode" | "alacritty" | "warp" | "orca" | "base" | "trueColor";
14
+ /** Terminal capability details used for rendering and protocol selection. */
15
+ export declare class TerminalInfo {
16
+ readonly id: TerminalId;
17
+ readonly imageProtocol: ImageProtocol | null;
18
+ readonly trueColor: boolean;
19
+ readonly hyperlinks: boolean;
20
+ readonly notifyProtocol: NotifyProtocol;
21
+ readonly deccara: boolean;
22
+ readonly supportsScreenToScrollback: boolean;
23
+ /** Renders the Kitty OSC 66 text-sizing protocol (scaled spans). Kitty only. */
24
+ readonly supportsTextSizing: boolean;
25
+ /**
26
+ * Hangul Compatibility Jamo (U+3131..=U+318E) cell width. Ghostty and Orca follow
27
+ * UAX#11 (2 cells); Warp paints 1; "platform" keeps the OS default
28
+ * (macOS narrow, otherwise UAX#11).
29
+ */
30
+ readonly hangulJamoWidth: HangulCompatibilityJamoWidth;
31
+ constructor(id: TerminalId, imageProtocol: ImageProtocol | null, trueColor: boolean, hyperlinks: boolean, notifyProtocol?: NotifyProtocol, deccara?: boolean, supportsScreenToScrollback?: boolean,
32
+ /** Renders the Kitty OSC 66 text-sizing protocol (scaled spans). Kitty only. */
33
+ supportsTextSizing?: boolean,
34
+ /**
35
+ * Hangul Compatibility Jamo (U+3131..=U+318E) cell width. Ghostty and Orca follow
36
+ * UAX#11 (2 cells); Warp paints 1; "platform" keeps the OS default
37
+ * (macOS narrow, otherwise UAX#11).
38
+ */
39
+ hangulJamoWidth?: HangulCompatibilityJamoWidth);
40
+ /**
41
+ * Mutable clone for the {@link TERMINAL} singleton: copies every field and
42
+ * keeps the prototype methods, so the builder and runtime setters flip
43
+ * runtime-resolved {@link RuntimeTerminal} capabilities in place instead of
44
+ * reconstructing positional constructor args.
45
+ */
46
+ clone(): RuntimeTerminal;
47
+ isImageLine(line: string): boolean;
48
+ formatNotification(message: string | TerminalNotification): string;
49
+ sendNotification(message: string | TerminalNotification): void;
50
+ }
51
+ /** Detect terminal multiplexers where scrollback clearing and height-change redraws are hostile. */
52
+ export declare function isInsideTerminalMultiplexer(env?: NodeJS.ProcessEnv): boolean;
53
+ /**
54
+ * Whether the agent process is running inside a Zellij session. Read fresh on
55
+ * each call (like {@link isInsideTmux}) so a session attached/detached mid-run
56
+ * is observed and tests can toggle `Bun.env.ZELLIJ` per case.
57
+ */
58
+ export declare function isInsideZellij(env?: NodeJS.ProcessEnv): boolean;
59
+ export declare function isNotificationSuppressed(): boolean;
60
+ /**
61
+ * Whether `PI_FORCE_IMAGE_PROTOCOL` pins the image protocol, including its
62
+ * `off`/`none` kill switch. A runtime capability probe must not override an
63
+ * explicit user choice: a forced protocol is already applied to {@link TERMINAL},
64
+ * and a forced "off" leaves `imageProtocol` null on purpose.
65
+ */
66
+ export declare function isImageProtocolForced(): boolean;
67
+ /**
68
+ * Returns true when running in Windows Terminal with known SIXEL support.
69
+ *
70
+ * Windows Terminal introduced SIXEL support in preview 1.22.
71
+ */
72
+ export declare function isWindowsTerminalPreviewSixelSupported(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): boolean;
73
+ /**
74
+ * Resolve an explicit user override for DEC 2026 synchronized output. Returns
75
+ * `false` for an opt-out, `true` for a force-on, or `null` when the user has
76
+ * expressed no preference. Shared by the static default and the runtime DECRQM
77
+ * probe so both honor the same precedence — an opt-out beats a force-on.
78
+ */
79
+ export declare function synchronizedOutputUserOverride(env?: NodeJS.ProcessEnv): boolean | null;
80
+ /**
81
+ * Whether DEC 2026 synchronized-output wrappers should be enabled by default.
82
+ *
83
+ * Policy (highest precedence first):
84
+ * 1. Explicit user override (`PI_NO_SYNC_OUTPUT`/`PI_TUI_SYNC_OUTPUT=0` off,
85
+ * `PI_FORCE_SYNC_OUTPUT=1`/`PI_TUI_SYNC_OUTPUT=1` on).
86
+ * 2. Positive `TERM_FEATURES` advertisement (`Sy`) — survives SSH/mux wrapping.
87
+ * 3. Windows Terminal (1.24+) via `WT_SESSION`, on native win32 and the
88
+ * WSL/SSH-fronted host alike.
89
+ * 4. Known direct terminals with confirmed support. SSH does *not* disable —
90
+ * DEC 2026 passes through SSH when the outer terminal honors it.
91
+ * 5. Everything else starts off, including risky multiplexers; the runtime
92
+ * DECRQM probe upgrades any of them when the terminal actually reports
93
+ * `?2026` supported (current zellij, tmux master, foot, contour, mintty…).
94
+ */
95
+ export declare function shouldEnableSynchronizedOutputByDefault(env?: NodeJS.ProcessEnv, terminalId?: TerminalId): boolean;
96
+ /**
97
+ * Whether the terminal applies Kitty-style DECCARA rectangular SGR changes
98
+ * (`CSI Pt ; Pl ; Pb ; Pr ; <sgr> $ r`) extended to background color, so large
99
+ * filled regions can be painted as rectangles instead of background-padded
100
+ * strings on every row.
101
+ *
102
+ * Verified against terminal sources rather than terminfo, because a bare
103
+ * `Cara`/DECCARA terminfo capability does not imply the Kitty SGR-background
104
+ * extension:
105
+ * - Kitty implements it for *all* SGR attributes including background (see
106
+ * kitty `docs/deccara.rst` and the `test_deccara` parser test).
107
+ * - Ghostty does NOT: its `CSI $ r` dispatch falls through to an "unknown CSI"
108
+ * warning and DECCARA/DECSACE are tracked as unsupported
109
+ * (ghostty-org/ghostty#632). Enabling it there would silently drop panel
110
+ * backgrounds, so ghostty stays on the padded-string fallback.
111
+ *
112
+ * Disabled under tmux/screen/zellij multiplexers — screen-coordinate rectangle
113
+ * protocols are not safe to assume through a multiplexer — and via the
114
+ * `PI_NO_DECCARA` kill switch. Pure helper for tests and `TERMINAL` construction.
115
+ */
116
+ export declare function detectRectangularSgrSupport(terminalId: TerminalId, env?: NodeJS.ProcessEnv): boolean;
117
+ /**
118
+ * Resolve an explicit user override for OSC 8 hyperlinks. Returns `false` for
119
+ * an opt-out, `true` for a force-on, or `null` when the user has expressed no
120
+ * preference. Opt-out beats force-on so a kill switch is unambiguous, mirroring
121
+ * {@link synchronizedOutputUserOverride}.
122
+ */
123
+ export declare function hyperlinksUserOverride(env?: NodeJS.ProcessEnv): boolean | null;
124
+ /**
125
+ * Whether OSC 8 hyperlinks should be enabled by default.
126
+ *
127
+ * Policy (highest precedence first):
128
+ * 1. Explicit user override (`PI_NO_HYPERLINKS=1` off, `PI_FORCE_HYPERLINKS=1`
129
+ * on). Opt-out wins ties.
130
+ * 2. Static terminal capability — terminals whose {@link TerminalInfo} marks
131
+ * `hyperlinks: false` (e.g. `base`) stay off unless the user forced on.
132
+ * 3. GNU screen's explicit session marker (`STY`) always off, even if tmux is
133
+ * also present: a screen layer anywhere in the path cannot forward OSC 8.
134
+ * 4. tmux session (`TMUX` set): enabled when tmux self-reports >= 3.4 via
135
+ * `TERM_PROGRAM_VERSION` (tmux 3.4 stores OSC 8 as a cell attribute and
136
+ * forwards it to outer terminals whose `terminal-features` include
137
+ * `hyperlinks`). Older or unknown versions stay off; on outer terminals
138
+ * without the feature configured, tmux silently drops the sequence —
139
+ * identical to today. Checked before the screen-family TERM heuristic
140
+ * because tmux's historical `default-terminal` is `screen-256color`, so
141
+ * `TERM=screen*` inside a tmux session must NOT short-circuit to off.
142
+ * 5. screen-family TERM without `TMUX` always off: screen never gained OSC 8
143
+ * support.
144
+ * 6. tmux-family TERM without `TMUX` env — unusual (e.g. inspection scripts);
145
+ * no version available, so off.
146
+ * 7. Otherwise honor the static terminal capability.
147
+ */
148
+ export declare function shouldEnableHyperlinksByDefault(env?: NodeJS.ProcessEnv, terminalId?: TerminalId): boolean;
149
+ /**
150
+ * Warp implements the Kitty graphics protocol only on macOS/Linux; its Windows
151
+ * build (including Warp-hosted WSL shells) renders the same APC sequences as
152
+ * visible garbage. Keep platform/env injectable so the carve-out is testable
153
+ * without mutating `process.platform`.
154
+ */
155
+ export declare function resolveWarpImageProtocol(platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv): ImageProtocol | null;
156
+ /** Resolve terminal identity from environment markers used by common emulators. */
157
+ export declare function detectTerminalId(env?: NodeJS.ProcessEnv): TerminalId;
158
+ export declare const TERMINAL_ID: TerminalId;
159
+ /**
160
+ * The process-wide {@link TERMINAL} singleton: a {@link TerminalInfo} whose
161
+ * post-construction capabilities — the image protocol and the probe-driven
162
+ * flags — are writable, so the runtime setters and tests mutate them directly
163
+ * instead of through an unsound cast. Every other field stays readonly.
164
+ */
165
+ export interface RuntimeTerminal extends TerminalInfo {
166
+ imageProtocol: ImageProtocol | null;
167
+ hyperlinks: boolean;
168
+ deccara: boolean;
169
+ supportsScreenToScrollback: boolean;
170
+ /** Whether OSC 66 text sizing is currently enabled. */
171
+ textSizing: boolean;
172
+ }
173
+ export declare const TERMINAL: RuntimeTerminal;
174
+ /**
175
+ * Override terminal image protocol at runtime after capability probes complete.
176
+ */
177
+ export declare function setTerminalImageProtocol(imageProtocol: ImageProtocol | null): void;
178
+ /**
179
+ * Override DECCARA rectangular-SGR capability at runtime. Used by tests to
180
+ * exercise the optimizer and fallback paths deterministically — the default is
181
+ * resolved once at import and force-disabled under the test runtime.
182
+ */
183
+ export declare function setTerminalDeccara(enabled: boolean): void;
184
+ /** Override screen-to-scrollback clear support for targeted renderer tests. */
185
+ export declare function setTerminalScreenToScrollback(enabled: boolean): void;
186
+ /**
187
+ * Enable/disable OSC 66 text-sizing at runtime. The coding-agent calls this from
188
+ * the `tui.textSizing` setting (gated on the terminal's static `supportsTextSizing`
189
+ * capability); tests flip it directly to exercise the scaled-heading path.
190
+ */
191
+ export declare function setTerminalTextSizing(enabled: boolean): void;
192
+ export declare function getTerminalInfo(terminalId: TerminalId, platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv): TerminalInfo;
193
+ export interface CellDimensions {
194
+ widthPx: number;
195
+ heightPx: number;
196
+ }
197
+ export interface ImageDimensions {
198
+ widthPx: number;
199
+ heightPx: number;
200
+ }
201
+ export interface ImageRenderOptions {
202
+ maxWidthCells?: number;
203
+ maxHeightCells?: number;
204
+ preserveAspectRatio?: boolean;
205
+ /**
206
+ * Stable Kitty image id (`i=`). When set, the image is displayed via a
207
+ * transmit-once + placement scheme keyed off this id instead of re-sending the
208
+ * base64 each frame.
209
+ */
210
+ imageId?: number;
211
+ /** Stable Kitty placement id (`p=`); defaults to {@link imageId}. */
212
+ placementId?: number;
213
+ /** When true (Kitty + {@link imageId}), also return the one-time transmit sequence. */
214
+ includeTransmit?: boolean;
215
+ }
216
+ export declare function getCellDimensions(): CellDimensions;
217
+ export declare function setCellDimensions(dims: CellDimensions): void;
218
+ /** Transmit-and-display (`a=T`) — the self-contained form used when no stable id is available. */
219
+ export declare function encodeKitty(base64Data: string, options?: {
220
+ columns?: number;
221
+ rows?: number;
222
+ imageId?: number;
223
+ }): string;
224
+ /**
225
+ * Transmit image data only (`a=t`), keyed by `imageId`, without displaying it.
226
+ * Sent once per image; the data then persists in the terminal's store (it
227
+ * survives scroll-off and text clears for images with a non-zero id), so
228
+ * subsequent frames display it with the tiny {@link encodeKittyPlacement}
229
+ * sequence instead of re-sending the base64.
230
+ */
231
+ export declare function encodeKittyTransmit(base64Data: string, imageId: number): string;
232
+ /**
233
+ * Display a previously transmitted image (`a=p`) at the cursor. `C=1` keeps
234
+ * the terminal cursor anchored at the placement origin so the renderer's
235
+ * explicit cursor movement remains the only row accounting. Carrying a stable
236
+ * `placementId` (`p=`) means re-emitting the sequence on a repaint *replaces*
237
+ * the existing placement (moving/resizing it without flicker) rather than
238
+ * stacking a duplicate.
239
+ */
240
+ export declare function encodeKittyPlacement(options: {
241
+ imageId: number;
242
+ placementId?: number;
243
+ columns?: number;
244
+ rows?: number;
245
+ }): string;
246
+ export interface ParsedKittyPlacementLine {
247
+ imageId: number;
248
+ placementId: number | undefined;
249
+ columns: number;
250
+ rows: number;
251
+ }
252
+ /**
253
+ * Parse a frame line that consists solely of a Kitty direct placement (the
254
+ * last line of an {@link Image} block). Returns null for anything else —
255
+ * placeholder grids, tmux-wrapped placements, sixel/iTerm2 payloads — so
256
+ * callers fall back to writing the line verbatim.
257
+ */
258
+ export declare function parseKittyDirectPlacementLine(line: string): ParsedKittyPlacementLine | null;
259
+ /**
260
+ * Rebuild an {@link Image} direct-placement line for the viewport row it is
261
+ * written at. The component-rendered line encodes `CUU(rows-1)`, which clamps
262
+ * at the viewport top once the block's leading rows have scrolled out — the
263
+ * placement then re-anchors the full image shifted down over foreign rows.
264
+ * Anchor at the block's first *visible* row instead, clipping the source
265
+ * rectangle (`y=`/`h=`, image pixels) to the visible bottom slice.
266
+ */
267
+ export declare function encodeKittyPlacementLine(options: {
268
+ imageId: number;
269
+ placementId: number;
270
+ columns: number;
271
+ /** Total cell rows of the image block. */
272
+ rows: number;
273
+ /** Viewport row the block's last line is being written at. */
274
+ screenRow: number;
275
+ /** Source image height in pixels, for the clipped source rectangle. */
276
+ imageHeightPx: number;
277
+ }): string;
278
+ /**
279
+ * Kitty graphics delete command for a single image id. Uses `d=I` (capital)
280
+ * which removes the image and every one of its placements — on screen *and* in
281
+ * scrollback — and frees the backing data. `q=2` suppresses the terminal reply.
282
+ * Text-clearing escapes (`CSI 2 J` / `CSI 3 J`) do not remove Kitty graphics, so
283
+ * this is the only way to actually purge a placed image.
284
+ */
285
+ export declare function encodeKittyDeleteImage(imageId: number): string;
286
+ /**
287
+ * Delete every Kitty image and placement in the terminal. Used only by an
288
+ * explicit destructive display reset: text erases leave untracked placements
289
+ * painted, so per-image bookkeeping cannot guarantee a clean viewport.
290
+ */
291
+ export declare function encodeKittyDeleteAllImages(): string;
292
+ /**
293
+ * Delete a single placement of an image (`d=i`, lowercase): removes its cells
294
+ * and registry entry but keeps the transmitted data, so a later `a=p` under a
295
+ * fresh placement id needs no retransmit. Used to clear stale placement-epoch
296
+ * entries after a destructive history clear.
297
+ */
298
+ export declare function encodeKittyDeletePlacement(imageId: number, placementId: number): string;
299
+ export declare function encodeITerm2(base64Data: string, options?: {
300
+ width?: number | string;
301
+ height?: number | string;
302
+ name?: string;
303
+ preserveAspectRatio?: boolean;
304
+ inline?: boolean;
305
+ }): string;
306
+ export declare function calculateImageRows(imageDimensions: ImageDimensions, targetWidthCells: number, cellDimensions?: CellDimensions): number;
307
+ export declare function getPngDimensions(base64Data: string): ImageDimensions | null;
308
+ export declare function getJpegDimensions(base64Data: string): ImageDimensions | null;
309
+ export declare function getGifDimensions(base64Data: string): ImageDimensions | null;
310
+ export declare function getWebpDimensions(base64Data: string): ImageDimensions | null;
311
+ export declare function getImageDimensions(base64Data: string, mimeType: string): ImageDimensions | null;
312
+ export declare function renderImage(base64Data: string, imageDimensions: ImageDimensions, options?: ImageRenderOptions): {
313
+ sequence?: string;
314
+ lines?: string[];
315
+ rows: number;
316
+ transmit?: string;
317
+ } | null;
318
+ export declare function imageFallback(mimeType: string, dimensions?: ImageDimensions, filename?: string): string;
319
+ /**
320
+ * Structured terminal notification. Rich fields are honored only by OSC 99
321
+ * (Kitty) once support is confirmed; other protocols and the unconfirmed Kitty
322
+ * path collapse to a single `title: body` line.
323
+ */
324
+ export interface TerminalNotification {
325
+ title?: string;
326
+ body?: string;
327
+ id?: string;
328
+ type?: string | string[];
329
+ urgency?: "low" | "normal" | "critical";
330
+ iconName?: string;
331
+ sound?: "silent" | "system" | "info" | "warning" | "error" | "question";
332
+ actions?: "focus" | "report" | "focus-report" | "none";
333
+ expiresMs?: number;
334
+ }
335
+ /** Record the OSC 99 capability-probe result (called by ProcessTerminal). */
336
+ export declare function setOsc99Supported(supported: boolean): void;
337
+ /** True when OSC 99 structured notifications have been confirmed available. */
338
+ export declare function isOsc99Supported(): boolean;
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Split `data` into chunks whose encoded UTF-8 byte length is no greater than
3
+ * `maxChunkBytes`, preferring a line boundary (`\n`) as the cut point so
4
+ * escape sequences (which never contain `\n`) stay intact. The TUI's
5
+ * full-paint buffers are line-structured (`buffer += "\r\n"` between rows),
6
+ * so a newline almost always exists within the window. The fallback for a
7
+ * buffer with no newline in range is a hard cut at the last UTF-8 code-point
8
+ * boundary that still fits — the ConPTY viewport bug from a single oversized
9
+ * write is strictly worse than a one-frame escape-sequence glitch on a
10
+ * buffer the renderer effectively never produces.
11
+ *
12
+ * UTF-16 code units are walked manually rather than measuring with
13
+ * `Buffer.byteLength` per slice candidate: each code unit's UTF-8 width is
14
+ * known from its value (BMP `<0x80` → 1, `<0x800` → 2, surrogate pair → 4
15
+ * bytes across two units, other BMP → 3), and surrogate pairs are kept
16
+ * together so the chunker never splits a non-BMP character.
17
+ *
18
+ * Exported for unit testing of the chunking contract; `#safeWrite` is the
19
+ * sole production caller.
20
+ */
21
+ export declare function chunkForConPTY(data: string, maxChunkBytes?: number): string[];
22
+ /**
23
+ * Turns an unbounded, never-draining stdout writable buffer into a bounded
24
+ * disconnect signal.
25
+ *
26
+ * `process.stdout.write()` returns `false` once its buffer exceeds the stream
27
+ * high-water mark; the bytes stay queued and are only freed when the consumer
28
+ * drains (the `drain` event). While the consumer keeps up, writes are accepted
29
+ * and nothing accumulates. When it stalls, every subsequent write piles onto
30
+ * the buffer — a stalled-but-alive PTY reader never throws, so the write path
31
+ * has no other signal that output is going nowhere. This guard sums the bytes
32
+ * queued since backpressure began and reports when that backlog crosses the
33
+ * cap, at which point the caller treats the terminal as disconnected.
34
+ *
35
+ * Exported for unit testing; `ProcessTerminal` is the sole production user.
36
+ */
37
+ export declare class OutputBacklogGuard {
38
+ #private;
39
+ private readonly capBytes;
40
+ constructor(capBytes?: number);
41
+ /** True once a refused write started a backlog that has not yet drained. */
42
+ get tracking(): boolean;
43
+ /**
44
+ * Record one `stdout.write()`: `accepted` is that call's return value and
45
+ * `bytes` its encoded size. Returns true when the pending backlog now
46
+ * exceeds the cap and the terminal should be treated as disconnected.
47
+ */
48
+ record(accepted: boolean, bytes: number): boolean;
49
+ /** Called on the stdout `drain` event: the buffer emptied, backlog cleared. */
50
+ reset(): void;
51
+ }
52
+ /** Record alternate-screen state (called by the TUI on `?1049h`/`?1049l` writes). */
53
+ export declare function setAltScreenActive(active: boolean): void;
54
+ /**
55
+ * Route an out-of-band escape sequence (e.g. an OSC title update) through the
56
+ * active terminal's output path. While a TUI owns stdout, frame paints go
57
+ * through the off-thread write pump and can split across multiple write(2)
58
+ * calls; a direct main-thread `process.stdout.write` can land between two of
59
+ * them — mid escape sequence — and the host terminal then prints the payload
60
+ * as literal text at the cursor position. Returns false when no terminal has
61
+ * started, in which case the caller owns stdout and may write directly.
62
+ */
63
+ export declare function writeThroughActiveTerminal(data: string): boolean;
64
+ /**
65
+ * Emergency terminal restore - call this from signal/crash handlers
66
+ * Resets terminal state without requiring access to the ProcessTerminal instance
67
+ */
68
+ export declare function emergencyTerminalRestore(): void;
69
+ /** Terminal-reported appearance (dark/light mode). */
70
+ export type TerminalAppearance = "dark" | "light";
71
+ /** Options for {@link Terminal.start}. */
72
+ export interface TerminalStartOptions {
73
+ /**
74
+ * Paint-only start: skip raw mode, stdin ownership, and every probe that
75
+ * elicits a response on stdin. The host tty keeps cooked-mode line editing
76
+ * (kernel echo lands at the hardware cursor), and typed bytes stay queued
77
+ * in the kernel until {@link Terminal.enableInput} takes ownership and
78
+ * replays them through `onInput`. Used for the startup prepaint so typing
79
+ * echoes even while module loading blocks the event loop.
80
+ */
81
+ deferInput?: boolean;
82
+ }
83
+ /** Identity of an accepted explicit terminal appearance refresh request. */
84
+ export type TerminalAppearanceRequestToken = number;
85
+ export interface Terminal {
86
+ start(onInput: (data: string) => void, onResize: () => void, onDisconnect?: () => void, options?: TerminalStartOptions): void;
87
+ /**
88
+ * Take ownership of stdin after a `deferInput` start: enable raw mode,
89
+ * attach input handlers, and run the capability probes start() skipped.
90
+ * Bytes the user typed in cooked mode meanwhile are replayed through
91
+ * `onInput`. No-op when input was never deferred. Optional so custom
92
+ * Terminals built against older pi-tui versions keep working.
93
+ */
94
+ enableInput?(): void;
95
+ stop(): void;
96
+ /**
97
+ * Drain stdin before exiting to prevent Kitty key release events from
98
+ * leaking to the parent shell over slow SSH connections.
99
+ * @param maxMs - Maximum time to drain (default: 1000ms)
100
+ * @param idleMs - Exit early if no input arrives within this time (default: 50ms)
101
+ */
102
+ drainInput(maxMs?: number, idleMs?: number): Promise<void>;
103
+ write(data: string): void;
104
+ get columns(): number;
105
+ get rows(): number;
106
+ /**
107
+ * Output bytes accepted but not yet delivered to the terminal, when the
108
+ * implementation can report it. The renderer skips composing new frames
109
+ * while this backlog is deep, so a slow terminal receives only fresh
110
+ * frames instead of a queue of stale ones. Optional so custom Terminals
111
+ * built against older pi-tui versions keep working.
112
+ */
113
+ readonly pendingOutputBytes?: number;
114
+ get kittyProtocolActive(): boolean;
115
+ get kittyEnableSequence(): string | null;
116
+ readonly keyboardEnhancementEnterSequence?: string | null;
117
+ readonly keyboardEnhancementExitSequence?: string | null;
118
+ moveBy(lines: number): void;
119
+ hideCursor(force?: boolean): void;
120
+ showCursor(force?: boolean): void;
121
+ clearLine(): void;
122
+ clearFromCursor(): void;
123
+ clearScreen(): void;
124
+ setTitle(title: string): void;
125
+ setProgress(active: boolean): void;
126
+ /**
127
+ * Register a callback for terminal appearance (dark/light) changes.
128
+ * Detection uses OSC 11 background color query with Mode 2031 as a change trigger.
129
+ * Fires when the detected appearance changes, including the initial detection.
130
+ * Subscribers registered after detection are invoked immediately with the
131
+ * already-detected appearance so late subscribers never miss it.
132
+ */
133
+ onAppearanceChange(callback: (appearance: TerminalAppearance, requestToken?: TerminalAppearanceRequestToken) => void): void;
134
+ /**
135
+ * Register a callback fired for every valid OSC 11 appearance report,
136
+ * including reports whose classification matches the current appearance.
137
+ * Unlike onAppearanceChange, this does not replay an earlier report.
138
+ * Optional so custom Terminals built against older pi-tui versions keep working.
139
+ */
140
+ onAppearanceReport?(callback: (appearance: TerminalAppearance, requestToken?: TerminalAppearanceRequestToken) => void): (() => void) | void;
141
+ /**
142
+ * Start a bounded OSC 11 background-color refresh cycle, driving appearance
143
+ * callbacks through the same parse/dedup pipeline used at startup and on Mode
144
+ * 2031 notifications. Direct terminals need one query; tmux needs a
145
+ * passthrough query to update its cache followed by one delayed direct cache
146
+ * read. Invoked on the user's explicit display-reset gesture so terminals
147
+ * without end-to-end Mode 2031 notifications pick up a light/dark switch
148
+ * without a restart. No periodic probes are armed.
149
+ *
150
+ * A caller-provided token must be propagated unchanged to callbacks and
151
+ * returned when the request is accepted. This lets callers establish ownership
152
+ * before implementations synchronously dispatch a cached response. Optional so
153
+ * custom Terminals built against older pi-tui versions keep working.
154
+ */
155
+ refreshAppearance?(requestToken?: TerminalAppearanceRequestToken): TerminalAppearanceRequestToken | void;
156
+ /** The last detected terminal appearance, or undefined if not yet known. */
157
+ get appearance(): TerminalAppearance | undefined;
158
+ /**
159
+ * Register a callback fired once per DEC private mode when its DECRQM support
160
+ * status resolves. `confirmed` is false when the terminal answered the DA1
161
+ * sentinel without answering DECRQM, which proves only that querying support
162
+ * is unavailable — not that the private mode itself is unsupported.
163
+ */
164
+ onPrivateModeReport?(callback: (mode: number, supported: boolean, confirmed?: boolean) => void): void;
165
+ }
166
+ /**
167
+ * True when stdout flows through a ConPTY pseudo-console (native win32, or
168
+ * Linux running under WSL where stdout still crosses into ConPTY at the
169
+ * `wslhost` boundary). ConPTY hosts share the per-WriteFile viewport-tracking
170
+ * quirks documented above and on {@link MAX_CONPTY_WRITE_CHUNK_BYTES}, so both
171
+ * `#safeWrite` and the renderer's post-big-paint settle gate hang off this
172
+ * single predicate.
173
+ */
174
+ export declare function isConPTYHosted(): boolean;
175
+ /**
176
+ * Real terminal using process.stdin/stdout
177
+ */
178
+ export declare class ProcessTerminal implements Terminal {
179
+ #private;
180
+ get kittyProtocolActive(): boolean;
181
+ get kittyEnableSequence(): string | null;
182
+ get keyboardEnhancementEnterSequence(): string | null;
183
+ get keyboardEnhancementExitSequence(): string | null;
184
+ get appearance(): TerminalAppearance | undefined;
185
+ onAppearanceChange(callback: (appearance: TerminalAppearance, requestToken?: TerminalAppearanceRequestToken) => void): void;
186
+ onAppearanceReport(callback: (appearance: TerminalAppearance, requestToken?: TerminalAppearanceRequestToken) => void): () => void;
187
+ /**
188
+ * Re-query the terminal background through the startup DA1-sentinel FIFO,
189
+ * pending/queued gating, parsing, dedup, and appearance callbacks. Inside
190
+ * tmux, only this explicit path first passes an OSC 11 query to the outer
191
+ * terminal, waits briefly for tmux to consume the response into its cache,
192
+ * then reads that cache with a direct query. The outer query deliberately has
193
+ * no DA1 sentinel: multiplexers can decode a fragmented DA1 response as a key
194
+ * sequence and leak the remaining bytes into the editor. Startup and Mode 2031
195
+ * probes remain direct. Suppressed while inactive, headless, or after teardown.
196
+ */
197
+ refreshAppearance(requestToken?: TerminalAppearanceRequestToken): TerminalAppearanceRequestToken | void;
198
+ onPrivateModeReport(callback: (mode: number, supported: boolean, confirmed?: boolean) => void): void;
199
+ start(onInput: (data: string) => void, onResize: () => void, onDisconnect?: () => void, options?: TerminalStartOptions): void;
200
+ enableInput(): void;
201
+ drainInput(maxMs?: number, idleMs?: number): Promise<void>;
202
+ stop(): void;
203
+ write(data: string): void;
204
+ get columns(): number;
205
+ get pendingOutputBytes(): number;
206
+ get rows(): number;
207
+ moveBy(lines: number): void;
208
+ hideCursor(force?: boolean): void;
209
+ showCursor(force?: boolean): void;
210
+ clearLine(): void;
211
+ clearFromCursor(): void;
212
+ clearScreen(): void;
213
+ setTitle(title: string): void;
214
+ setProgress(active: boolean): void;
215
+ }
@@ -0,0 +1,6 @@
1
+ /** Whether the process is running inside a tmux session. */
2
+ export declare function isInsideTmux(env?: NodeJS.ProcessEnv): boolean;
3
+ /** Wrap a control sequence in tmux's DCS passthrough envelope. */
4
+ export declare function wrapTmuxPassthrough(payload: string): string;
5
+ /** Pass a control sequence through tmux, leaving direct-terminal output unchanged. */
6
+ export declare function wrapTmuxPassthroughIfNeeded(payload: string, env?: NodeJS.ProcessEnv): string;
@@ -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;