@juliusbrussee/caveman-tui 0.65.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 (162) hide show
  1. package/README.md +767 -0
  2. package/dist/autocomplete.d.ts +52 -0
  3. package/dist/autocomplete.d.ts.map +1 -0
  4. package/dist/autocomplete.js +623 -0
  5. package/dist/autocomplete.js.map +1 -0
  6. package/dist/chord.d.ts +57 -0
  7. package/dist/chord.d.ts.map +1 -0
  8. package/dist/chord.js +97 -0
  9. package/dist/chord.js.map +1 -0
  10. package/dist/color-depth.d.ts +17 -0
  11. package/dist/color-depth.d.ts.map +1 -0
  12. package/dist/color-depth.js +147 -0
  13. package/dist/color-depth.js.map +1 -0
  14. package/dist/components/Chapters.d.ts +41 -0
  15. package/dist/components/Chapters.d.ts.map +1 -0
  16. package/dist/components/Chapters.js +103 -0
  17. package/dist/components/Chapters.js.map +1 -0
  18. package/dist/components/DiffView.d.ts +75 -0
  19. package/dist/components/DiffView.d.ts.map +1 -0
  20. package/dist/components/DiffView.js +170 -0
  21. package/dist/components/DiffView.js.map +1 -0
  22. package/dist/components/StatusLine.d.ts +135 -0
  23. package/dist/components/StatusLine.d.ts.map +1 -0
  24. package/dist/components/StatusLine.js +133 -0
  25. package/dist/components/StatusLine.js.map +1 -0
  26. package/dist/components/SubagentOverlay.d.ts +63 -0
  27. package/dist/components/SubagentOverlay.d.ts.map +1 -0
  28. package/dist/components/SubagentOverlay.js +124 -0
  29. package/dist/components/SubagentOverlay.js.map +1 -0
  30. package/dist/components/box.d.ts +22 -0
  31. package/dist/components/box.d.ts.map +1 -0
  32. package/dist/components/box.js +104 -0
  33. package/dist/components/box.js.map +1 -0
  34. package/dist/components/cancellable-loader.d.ts +22 -0
  35. package/dist/components/cancellable-loader.d.ts.map +1 -0
  36. package/dist/components/cancellable-loader.js +35 -0
  37. package/dist/components/cancellable-loader.js.map +1 -0
  38. package/dist/components/editor.d.ts +244 -0
  39. package/dist/components/editor.d.ts.map +1 -0
  40. package/dist/components/editor.js +1861 -0
  41. package/dist/components/editor.js.map +1 -0
  42. package/dist/components/grouped-select-list.d.ts +60 -0
  43. package/dist/components/grouped-select-list.d.ts.map +1 -0
  44. package/dist/components/grouped-select-list.js +312 -0
  45. package/dist/components/grouped-select-list.js.map +1 -0
  46. package/dist/components/image.d.ts +28 -0
  47. package/dist/components/image.d.ts.map +1 -0
  48. package/dist/components/image.js +69 -0
  49. package/dist/components/image.js.map +1 -0
  50. package/dist/components/input.d.ts +37 -0
  51. package/dist/components/input.d.ts.map +1 -0
  52. package/dist/components/input.js +426 -0
  53. package/dist/components/input.js.map +1 -0
  54. package/dist/components/loader.d.ts +26 -0
  55. package/dist/components/loader.d.ts.map +1 -0
  56. package/dist/components/loader.js +67 -0
  57. package/dist/components/loader.js.map +1 -0
  58. package/dist/components/markdown.d.ts +95 -0
  59. package/dist/components/markdown.d.ts.map +1 -0
  60. package/dist/components/markdown.js +663 -0
  61. package/dist/components/markdown.js.map +1 -0
  62. package/dist/components/select-list.d.ts +50 -0
  63. package/dist/components/select-list.d.ts.map +1 -0
  64. package/dist/components/select-list.js +159 -0
  65. package/dist/components/select-list.js.map +1 -0
  66. package/dist/components/settings-list.d.ts +50 -0
  67. package/dist/components/settings-list.d.ts.map +1 -0
  68. package/dist/components/settings-list.js +185 -0
  69. package/dist/components/settings-list.js.map +1 -0
  70. package/dist/components/spacer.d.ts +12 -0
  71. package/dist/components/spacer.d.ts.map +1 -0
  72. package/dist/components/spacer.js +23 -0
  73. package/dist/components/spacer.js.map +1 -0
  74. package/dist/components/spinner.d.ts +35 -0
  75. package/dist/components/spinner.d.ts.map +1 -0
  76. package/dist/components/spinner.js +77 -0
  77. package/dist/components/spinner.js.map +1 -0
  78. package/dist/components/streaming-markdown.d.ts +39 -0
  79. package/dist/components/streaming-markdown.d.ts.map +1 -0
  80. package/dist/components/streaming-markdown.js +137 -0
  81. package/dist/components/streaming-markdown.js.map +1 -0
  82. package/dist/components/text.d.ts +19 -0
  83. package/dist/components/text.d.ts.map +1 -0
  84. package/dist/components/text.js +89 -0
  85. package/dist/components/text.js.map +1 -0
  86. package/dist/components/truncated-text.d.ts +13 -0
  87. package/dist/components/truncated-text.d.ts.map +1 -0
  88. package/dist/components/truncated-text.js +51 -0
  89. package/dist/components/truncated-text.js.map +1 -0
  90. package/dist/editor-component.d.ts +39 -0
  91. package/dist/editor-component.d.ts.map +1 -0
  92. package/dist/editor-component.js +2 -0
  93. package/dist/editor-component.js.map +1 -0
  94. package/dist/fuzzy.d.ts +16 -0
  95. package/dist/fuzzy.d.ts.map +1 -0
  96. package/dist/fuzzy.js +107 -0
  97. package/dist/fuzzy.js.map +1 -0
  98. package/dist/index.d.ts +38 -0
  99. package/dist/index.d.ts.map +1 -0
  100. package/dist/index.js +59 -0
  101. package/dist/index.js.map +1 -0
  102. package/dist/keybindings.d.ts +193 -0
  103. package/dist/keybindings.d.ts.map +1 -0
  104. package/dist/keybindings.js +174 -0
  105. package/dist/keybindings.js.map +1 -0
  106. package/dist/keys.d.ts +170 -0
  107. package/dist/keys.d.ts.map +1 -0
  108. package/dist/keys.js +1124 -0
  109. package/dist/keys.js.map +1 -0
  110. package/dist/kill-ring.d.ts +28 -0
  111. package/dist/kill-ring.d.ts.map +1 -0
  112. package/dist/kill-ring.js +44 -0
  113. package/dist/kill-ring.js.map +1 -0
  114. package/dist/notifications.d.ts +35 -0
  115. package/dist/notifications.d.ts.map +1 -0
  116. package/dist/notifications.js +62 -0
  117. package/dist/notifications.js.map +1 -0
  118. package/dist/osc52.d.ts +28 -0
  119. package/dist/osc52.d.ts.map +1 -0
  120. package/dist/osc52.js +53 -0
  121. package/dist/osc52.js.map +1 -0
  122. package/dist/scroll-buffer.d.ts +67 -0
  123. package/dist/scroll-buffer.d.ts.map +1 -0
  124. package/dist/scroll-buffer.js +222 -0
  125. package/dist/scroll-buffer.js.map +1 -0
  126. package/dist/spinners.d.ts +26 -0
  127. package/dist/spinners.d.ts.map +1 -0
  128. package/dist/spinners.js +136 -0
  129. package/dist/spinners.js.map +1 -0
  130. package/dist/stdin-buffer.d.ts +48 -0
  131. package/dist/stdin-buffer.d.ts.map +1 -0
  132. package/dist/stdin-buffer.js +317 -0
  133. package/dist/stdin-buffer.js.map +1 -0
  134. package/dist/sync-output.d.ts +58 -0
  135. package/dist/sync-output.d.ts.map +1 -0
  136. package/dist/sync-output.js +79 -0
  137. package/dist/sync-output.js.map +1 -0
  138. package/dist/terminal-detect.d.ts +66 -0
  139. package/dist/terminal-detect.d.ts.map +1 -0
  140. package/dist/terminal-detect.js +315 -0
  141. package/dist/terminal-detect.js.map +1 -0
  142. package/dist/terminal-image.d.ts +68 -0
  143. package/dist/terminal-image.d.ts.map +1 -0
  144. package/dist/terminal-image.js +288 -0
  145. package/dist/terminal-image.js.map +1 -0
  146. package/dist/terminal.d.ts +105 -0
  147. package/dist/terminal.d.ts.map +1 -0
  148. package/dist/terminal.js +427 -0
  149. package/dist/terminal.js.map +1 -0
  150. package/dist/tui.d.ts +268 -0
  151. package/dist/tui.d.ts.map +1 -0
  152. package/dist/tui.js +1161 -0
  153. package/dist/tui.js.map +1 -0
  154. package/dist/undo-stack.d.ts +17 -0
  155. package/dist/undo-stack.d.ts.map +1 -0
  156. package/dist/undo-stack.js +25 -0
  157. package/dist/undo-stack.js.map +1 -0
  158. package/dist/utils.d.ts +78 -0
  159. package/dist/utils.d.ts.map +1 -0
  160. package/dist/utils.js +960 -0
  161. package/dist/utils.js.map +1 -0
  162. package/package.json +59 -0
@@ -0,0 +1,317 @@
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
+ const ESC = "\x1b";
21
+ const BRACKETED_PASTE_START = "\x1b[200~";
22
+ const BRACKETED_PASTE_END = "\x1b[201~";
23
+ /**
24
+ * Check if a string is a complete escape sequence or needs more data
25
+ */
26
+ function isCompleteSequence(data) {
27
+ if (!data.startsWith(ESC)) {
28
+ return "not-escape";
29
+ }
30
+ if (data.length === 1) {
31
+ return "incomplete";
32
+ }
33
+ const afterEsc = data.slice(1);
34
+ // CSI sequences: ESC [
35
+ if (afterEsc.startsWith("[")) {
36
+ // Check for old-style mouse sequence: ESC[M + 3 bytes
37
+ if (afterEsc.startsWith("[M")) {
38
+ // Old-style mouse needs ESC[M + 3 bytes = 6 total
39
+ return data.length >= 6 ? "complete" : "incomplete";
40
+ }
41
+ return isCompleteCsiSequence(data);
42
+ }
43
+ // OSC sequences: ESC ]
44
+ if (afterEsc.startsWith("]")) {
45
+ return isCompleteOscSequence(data);
46
+ }
47
+ // DCS sequences: ESC P ... ESC \ (includes XTVersion responses)
48
+ if (afterEsc.startsWith("P")) {
49
+ return isCompleteDcsSequence(data);
50
+ }
51
+ // APC sequences: ESC _ ... ESC \ (includes Kitty graphics responses)
52
+ if (afterEsc.startsWith("_")) {
53
+ return isCompleteApcSequence(data);
54
+ }
55
+ // SS3 sequences: ESC O
56
+ if (afterEsc.startsWith("O")) {
57
+ // ESC O followed by a single character
58
+ return afterEsc.length >= 2 ? "complete" : "incomplete";
59
+ }
60
+ // Meta key sequences: ESC followed by a single character
61
+ if (afterEsc.length === 1) {
62
+ return "complete";
63
+ }
64
+ // Unknown escape sequence - treat as complete
65
+ return "complete";
66
+ }
67
+ /**
68
+ * Check if CSI sequence is complete
69
+ * CSI sequences: ESC [ ... followed by a final byte (0x40-0x7E)
70
+ */
71
+ function isCompleteCsiSequence(data) {
72
+ if (!data.startsWith(`${ESC}[`)) {
73
+ return "complete";
74
+ }
75
+ // Need at least ESC [ and one more character
76
+ if (data.length < 3) {
77
+ return "incomplete";
78
+ }
79
+ const payload = data.slice(2);
80
+ // CSI sequences end with a byte in the range 0x40-0x7E (@-~)
81
+ // This includes all letters and several special characters
82
+ const lastChar = payload[payload.length - 1];
83
+ const lastCharCode = lastChar.charCodeAt(0);
84
+ if (lastCharCode >= 0x40 && lastCharCode <= 0x7e) {
85
+ // Special handling for SGR mouse sequences
86
+ // Format: ESC[<B;X;Ym or ESC[<B;X;YM
87
+ if (payload.startsWith("<")) {
88
+ // Must have format: <digits;digits;digits[Mm]
89
+ const mouseMatch = /^<\d+;\d+;\d+[Mm]$/.test(payload);
90
+ if (mouseMatch) {
91
+ return "complete";
92
+ }
93
+ // If it ends with M or m but doesn't match the pattern, still incomplete
94
+ if (lastChar === "M" || lastChar === "m") {
95
+ // Check if we have the right structure
96
+ const parts = payload.slice(1, -1).split(";");
97
+ if (parts.length === 3 && parts.every((p) => /^\d+$/.test(p))) {
98
+ return "complete";
99
+ }
100
+ }
101
+ return "incomplete";
102
+ }
103
+ return "complete";
104
+ }
105
+ return "incomplete";
106
+ }
107
+ /**
108
+ * Check if OSC sequence is complete
109
+ * OSC sequences: ESC ] ... ST (where ST is ESC \ or BEL)
110
+ */
111
+ function isCompleteOscSequence(data) {
112
+ if (!data.startsWith(`${ESC}]`)) {
113
+ return "complete";
114
+ }
115
+ // OSC sequences end with ST (ESC \) or BEL (\x07)
116
+ if (data.endsWith(`${ESC}\\`) || data.endsWith("\x07")) {
117
+ return "complete";
118
+ }
119
+ return "incomplete";
120
+ }
121
+ /**
122
+ * Check if DCS (Device Control String) sequence is complete
123
+ * DCS sequences: ESC P ... ST (where ST is ESC \)
124
+ * Used for XTVersion responses like ESC P >| ... ESC \
125
+ */
126
+ function isCompleteDcsSequence(data) {
127
+ if (!data.startsWith(`${ESC}P`)) {
128
+ return "complete";
129
+ }
130
+ // DCS sequences end with ST (ESC \)
131
+ if (data.endsWith(`${ESC}\\`)) {
132
+ return "complete";
133
+ }
134
+ return "incomplete";
135
+ }
136
+ /**
137
+ * Check if APC (Application Program Command) sequence is complete
138
+ * APC sequences: ESC _ ... ST (where ST is ESC \)
139
+ * Used for Kitty graphics responses like ESC _ G ... ESC \
140
+ */
141
+ function isCompleteApcSequence(data) {
142
+ if (!data.startsWith(`${ESC}_`)) {
143
+ return "complete";
144
+ }
145
+ // APC sequences end with ST (ESC \)
146
+ if (data.endsWith(`${ESC}\\`)) {
147
+ return "complete";
148
+ }
149
+ return "incomplete";
150
+ }
151
+ /**
152
+ * Split accumulated buffer into complete sequences
153
+ */
154
+ function extractCompleteSequences(buffer) {
155
+ const sequences = [];
156
+ let pos = 0;
157
+ while (pos < buffer.length) {
158
+ const remaining = buffer.slice(pos);
159
+ // Try to extract a sequence starting at this position
160
+ if (remaining.startsWith(ESC)) {
161
+ // Find the end of this escape sequence
162
+ let seqEnd = 1;
163
+ while (seqEnd <= remaining.length) {
164
+ const candidate = remaining.slice(0, seqEnd);
165
+ const status = isCompleteSequence(candidate);
166
+ if (status === "complete") {
167
+ sequences.push(candidate);
168
+ pos += seqEnd;
169
+ break;
170
+ }
171
+ else if (status === "incomplete") {
172
+ seqEnd++;
173
+ }
174
+ else {
175
+ // Should not happen when starting with ESC
176
+ sequences.push(candidate);
177
+ pos += seqEnd;
178
+ break;
179
+ }
180
+ }
181
+ if (seqEnd > remaining.length) {
182
+ return { sequences, remainder: remaining };
183
+ }
184
+ }
185
+ else {
186
+ // Not an escape sequence - take a single character
187
+ sequences.push(remaining[0]);
188
+ pos++;
189
+ }
190
+ }
191
+ return { sequences, remainder: "" };
192
+ }
193
+ /**
194
+ * Buffers stdin input and emits complete sequences via the 'data' event.
195
+ * Handles partial escape sequences that arrive across multiple chunks.
196
+ */
197
+ export class StdinBuffer extends EventEmitter {
198
+ buffer = "";
199
+ timeout = null;
200
+ timeoutMs;
201
+ pasteMode = false;
202
+ pasteBuffer = "";
203
+ constructor(options = {}) {
204
+ super();
205
+ this.timeoutMs = options.timeout ?? 10;
206
+ }
207
+ process(data) {
208
+ // Clear any pending timeout
209
+ if (this.timeout) {
210
+ clearTimeout(this.timeout);
211
+ this.timeout = null;
212
+ }
213
+ // Handle high-byte conversion (for compatibility with parseKeypress)
214
+ // If buffer has single byte > 127, convert to ESC + (byte - 128)
215
+ let str;
216
+ if (Buffer.isBuffer(data)) {
217
+ if (data.length === 1 && data[0] > 127) {
218
+ const byte = data[0] - 128;
219
+ str = `\x1b${String.fromCharCode(byte)}`;
220
+ }
221
+ else {
222
+ str = data.toString();
223
+ }
224
+ }
225
+ else {
226
+ str = data;
227
+ }
228
+ if (str.length === 0 && this.buffer.length === 0) {
229
+ this.emit("data", "");
230
+ return;
231
+ }
232
+ this.buffer += str;
233
+ if (this.pasteMode) {
234
+ this.pasteBuffer += this.buffer;
235
+ this.buffer = "";
236
+ const endIndex = this.pasteBuffer.indexOf(BRACKETED_PASTE_END);
237
+ if (endIndex !== -1) {
238
+ const pastedContent = this.pasteBuffer.slice(0, endIndex);
239
+ const remaining = this.pasteBuffer.slice(endIndex + BRACKETED_PASTE_END.length);
240
+ this.pasteMode = false;
241
+ this.pasteBuffer = "";
242
+ this.emit("paste", pastedContent);
243
+ if (remaining.length > 0) {
244
+ this.process(remaining);
245
+ }
246
+ }
247
+ return;
248
+ }
249
+ const startIndex = this.buffer.indexOf(BRACKETED_PASTE_START);
250
+ if (startIndex !== -1) {
251
+ if (startIndex > 0) {
252
+ const beforePaste = this.buffer.slice(0, startIndex);
253
+ const result = extractCompleteSequences(beforePaste);
254
+ for (const sequence of result.sequences) {
255
+ this.emit("data", sequence);
256
+ }
257
+ }
258
+ this.buffer = this.buffer.slice(startIndex + BRACKETED_PASTE_START.length);
259
+ this.pasteMode = true;
260
+ this.pasteBuffer = this.buffer;
261
+ this.buffer = "";
262
+ const endIndex = this.pasteBuffer.indexOf(BRACKETED_PASTE_END);
263
+ if (endIndex !== -1) {
264
+ const pastedContent = this.pasteBuffer.slice(0, endIndex);
265
+ const remaining = this.pasteBuffer.slice(endIndex + BRACKETED_PASTE_END.length);
266
+ this.pasteMode = false;
267
+ this.pasteBuffer = "";
268
+ this.emit("paste", pastedContent);
269
+ if (remaining.length > 0) {
270
+ this.process(remaining);
271
+ }
272
+ }
273
+ return;
274
+ }
275
+ const result = extractCompleteSequences(this.buffer);
276
+ this.buffer = result.remainder;
277
+ for (const sequence of result.sequences) {
278
+ this.emit("data", sequence);
279
+ }
280
+ if (this.buffer.length > 0) {
281
+ this.timeout = setTimeout(() => {
282
+ const flushed = this.flush();
283
+ for (const sequence of flushed) {
284
+ this.emit("data", sequence);
285
+ }
286
+ }, this.timeoutMs);
287
+ }
288
+ }
289
+ flush() {
290
+ if (this.timeout) {
291
+ clearTimeout(this.timeout);
292
+ this.timeout = null;
293
+ }
294
+ if (this.buffer.length === 0) {
295
+ return [];
296
+ }
297
+ const sequences = [this.buffer];
298
+ this.buffer = "";
299
+ return sequences;
300
+ }
301
+ clear() {
302
+ if (this.timeout) {
303
+ clearTimeout(this.timeout);
304
+ this.timeout = null;
305
+ }
306
+ this.buffer = "";
307
+ this.pasteMode = false;
308
+ this.pasteBuffer = "";
309
+ }
310
+ getBuffer() {
311
+ return this.buffer;
312
+ }
313
+ destroy() {
314
+ this.clear();
315
+ }
316
+ }
317
+ //# sourceMappingURL=stdin-buffer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdin-buffer.js","sourceRoot":"","sources":["../src/stdin-buffer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * StdinBuffer buffers input and emits complete sequences.\n *\n * This is necessary because stdin data events can arrive in partial chunks,\n * especially for escape sequences like mouse events. Without buffering,\n * partial sequences can be misinterpreted as regular keypresses.\n *\n * For example, the mouse SGR sequence `\\x1b[<35;20;5m` might arrive as:\n * - Event 1: `\\x1b`\n * - Event 2: `[<35`\n * - Event 3: `;20;5m`\n *\n * The buffer accumulates these until a complete sequence is detected.\n * Call the `process()` method to feed input data.\n *\n * Based on code from OpenTUI (https://github.com/anomalyco/opentui)\n * MIT License - Copyright (c) 2025 opentui\n */\n\nimport { EventEmitter } from \"events\";\n\nconst ESC = \"\\x1b\";\nconst BRACKETED_PASTE_START = \"\\x1b[200~\";\nconst BRACKETED_PASTE_END = \"\\x1b[201~\";\n\n/**\n * Check if a string is a complete escape sequence or needs more data\n */\nfunction isCompleteSequence(data: string): \"complete\" | \"incomplete\" | \"not-escape\" {\n\tif (!data.startsWith(ESC)) {\n\t\treturn \"not-escape\";\n\t}\n\n\tif (data.length === 1) {\n\t\treturn \"incomplete\";\n\t}\n\n\tconst afterEsc = data.slice(1);\n\n\t// CSI sequences: ESC [\n\tif (afterEsc.startsWith(\"[\")) {\n\t\t// Check for old-style mouse sequence: ESC[M + 3 bytes\n\t\tif (afterEsc.startsWith(\"[M\")) {\n\t\t\t// Old-style mouse needs ESC[M + 3 bytes = 6 total\n\t\t\treturn data.length >= 6 ? \"complete\" : \"incomplete\";\n\t\t}\n\t\treturn isCompleteCsiSequence(data);\n\t}\n\n\t// OSC sequences: ESC ]\n\tif (afterEsc.startsWith(\"]\")) {\n\t\treturn isCompleteOscSequence(data);\n\t}\n\n\t// DCS sequences: ESC P ... ESC \\ (includes XTVersion responses)\n\tif (afterEsc.startsWith(\"P\")) {\n\t\treturn isCompleteDcsSequence(data);\n\t}\n\n\t// APC sequences: ESC _ ... ESC \\ (includes Kitty graphics responses)\n\tif (afterEsc.startsWith(\"_\")) {\n\t\treturn isCompleteApcSequence(data);\n\t}\n\n\t// SS3 sequences: ESC O\n\tif (afterEsc.startsWith(\"O\")) {\n\t\t// ESC O followed by a single character\n\t\treturn afterEsc.length >= 2 ? \"complete\" : \"incomplete\";\n\t}\n\n\t// Meta key sequences: ESC followed by a single character\n\tif (afterEsc.length === 1) {\n\t\treturn \"complete\";\n\t}\n\n\t// Unknown escape sequence - treat as complete\n\treturn \"complete\";\n}\n\n/**\n * Check if CSI sequence is complete\n * CSI sequences: ESC [ ... followed by a final byte (0x40-0x7E)\n */\nfunction isCompleteCsiSequence(data: string): \"complete\" | \"incomplete\" {\n\tif (!data.startsWith(`${ESC}[`)) {\n\t\treturn \"complete\";\n\t}\n\n\t// Need at least ESC [ and one more character\n\tif (data.length < 3) {\n\t\treturn \"incomplete\";\n\t}\n\n\tconst payload = data.slice(2);\n\n\t// CSI sequences end with a byte in the range 0x40-0x7E (@-~)\n\t// This includes all letters and several special characters\n\tconst lastChar = payload[payload.length - 1];\n\tconst lastCharCode = lastChar.charCodeAt(0);\n\n\tif (lastCharCode >= 0x40 && lastCharCode <= 0x7e) {\n\t\t// Special handling for SGR mouse sequences\n\t\t// Format: ESC[<B;X;Ym or ESC[<B;X;YM\n\t\tif (payload.startsWith(\"<\")) {\n\t\t\t// Must have format: <digits;digits;digits[Mm]\n\t\t\tconst mouseMatch = /^<\\d+;\\d+;\\d+[Mm]$/.test(payload);\n\t\t\tif (mouseMatch) {\n\t\t\t\treturn \"complete\";\n\t\t\t}\n\t\t\t// If it ends with M or m but doesn't match the pattern, still incomplete\n\t\t\tif (lastChar === \"M\" || lastChar === \"m\") {\n\t\t\t\t// Check if we have the right structure\n\t\t\t\tconst parts = payload.slice(1, -1).split(\";\");\n\t\t\t\tif (parts.length === 3 && parts.every((p) => /^\\d+$/.test(p))) {\n\t\t\t\t\treturn \"complete\";\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn \"incomplete\";\n\t\t}\n\n\t\treturn \"complete\";\n\t}\n\n\treturn \"incomplete\";\n}\n\n/**\n * Check if OSC sequence is complete\n * OSC sequences: ESC ] ... ST (where ST is ESC \\ or BEL)\n */\nfunction isCompleteOscSequence(data: string): \"complete\" | \"incomplete\" {\n\tif (!data.startsWith(`${ESC}]`)) {\n\t\treturn \"complete\";\n\t}\n\n\t// OSC sequences end with ST (ESC \\) or BEL (\\x07)\n\tif (data.endsWith(`${ESC}\\\\`) || data.endsWith(\"\\x07\")) {\n\t\treturn \"complete\";\n\t}\n\n\treturn \"incomplete\";\n}\n\n/**\n * Check if DCS (Device Control String) sequence is complete\n * DCS sequences: ESC P ... ST (where ST is ESC \\)\n * Used for XTVersion responses like ESC P >| ... ESC \\\n */\nfunction isCompleteDcsSequence(data: string): \"complete\" | \"incomplete\" {\n\tif (!data.startsWith(`${ESC}P`)) {\n\t\treturn \"complete\";\n\t}\n\n\t// DCS sequences end with ST (ESC \\)\n\tif (data.endsWith(`${ESC}\\\\`)) {\n\t\treturn \"complete\";\n\t}\n\n\treturn \"incomplete\";\n}\n\n/**\n * Check if APC (Application Program Command) sequence is complete\n * APC sequences: ESC _ ... ST (where ST is ESC \\)\n * Used for Kitty graphics responses like ESC _ G ... ESC \\\n */\nfunction isCompleteApcSequence(data: string): \"complete\" | \"incomplete\" {\n\tif (!data.startsWith(`${ESC}_`)) {\n\t\treturn \"complete\";\n\t}\n\n\t// APC sequences end with ST (ESC \\)\n\tif (data.endsWith(`${ESC}\\\\`)) {\n\t\treturn \"complete\";\n\t}\n\n\treturn \"incomplete\";\n}\n\n/**\n * Split accumulated buffer into complete sequences\n */\nfunction extractCompleteSequences(buffer: string): { sequences: string[]; remainder: string } {\n\tconst sequences: string[] = [];\n\tlet pos = 0;\n\n\twhile (pos < buffer.length) {\n\t\tconst remaining = buffer.slice(pos);\n\n\t\t// Try to extract a sequence starting at this position\n\t\tif (remaining.startsWith(ESC)) {\n\t\t\t// Find the end of this escape sequence\n\t\t\tlet seqEnd = 1;\n\t\t\twhile (seqEnd <= remaining.length) {\n\t\t\t\tconst candidate = remaining.slice(0, seqEnd);\n\t\t\t\tconst status = isCompleteSequence(candidate);\n\n\t\t\t\tif (status === \"complete\") {\n\t\t\t\t\tsequences.push(candidate);\n\t\t\t\t\tpos += seqEnd;\n\t\t\t\t\tbreak;\n\t\t\t\t} else if (status === \"incomplete\") {\n\t\t\t\t\tseqEnd++;\n\t\t\t\t} else {\n\t\t\t\t\t// Should not happen when starting with ESC\n\t\t\t\t\tsequences.push(candidate);\n\t\t\t\t\tpos += seqEnd;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (seqEnd > remaining.length) {\n\t\t\t\treturn { sequences, remainder: remaining };\n\t\t\t}\n\t\t} else {\n\t\t\t// Not an escape sequence - take a single character\n\t\t\tsequences.push(remaining[0]!);\n\t\t\tpos++;\n\t\t}\n\t}\n\n\treturn { sequences, remainder: \"\" };\n}\n\nexport type StdinBufferOptions = {\n\t/**\n\t * Maximum time to wait for sequence completion (default: 10ms)\n\t * After this time, the buffer is flushed even if incomplete\n\t */\n\ttimeout?: number;\n};\n\nexport type StdinBufferEventMap = {\n\tdata: [string];\n\tpaste: [string];\n};\n\n/**\n * Buffers stdin input and emits complete sequences via the 'data' event.\n * Handles partial escape sequences that arrive across multiple chunks.\n */\nexport class StdinBuffer extends EventEmitter<StdinBufferEventMap> {\n\tprivate buffer: string = \"\";\n\tprivate timeout: ReturnType<typeof setTimeout> | null = null;\n\tprivate readonly timeoutMs: number;\n\tprivate pasteMode: boolean = false;\n\tprivate pasteBuffer: string = \"\";\n\n\tconstructor(options: StdinBufferOptions = {}) {\n\t\tsuper();\n\t\tthis.timeoutMs = options.timeout ?? 10;\n\t}\n\n\tpublic process(data: string | Buffer): void {\n\t\t// Clear any pending timeout\n\t\tif (this.timeout) {\n\t\t\tclearTimeout(this.timeout);\n\t\t\tthis.timeout = null;\n\t\t}\n\n\t\t// Handle high-byte conversion (for compatibility with parseKeypress)\n\t\t// If buffer has single byte > 127, convert to ESC + (byte - 128)\n\t\tlet str: string;\n\t\tif (Buffer.isBuffer(data)) {\n\t\t\tif (data.length === 1 && data[0]! > 127) {\n\t\t\t\tconst byte = data[0]! - 128;\n\t\t\t\tstr = `\\x1b${String.fromCharCode(byte)}`;\n\t\t\t} else {\n\t\t\t\tstr = data.toString();\n\t\t\t}\n\t\t} else {\n\t\t\tstr = data;\n\t\t}\n\n\t\tif (str.length === 0 && this.buffer.length === 0) {\n\t\t\tthis.emit(\"data\", \"\");\n\t\t\treturn;\n\t\t}\n\n\t\tthis.buffer += str;\n\n\t\tif (this.pasteMode) {\n\t\t\tthis.pasteBuffer += this.buffer;\n\t\t\tthis.buffer = \"\";\n\n\t\t\tconst endIndex = this.pasteBuffer.indexOf(BRACKETED_PASTE_END);\n\t\t\tif (endIndex !== -1) {\n\t\t\t\tconst pastedContent = this.pasteBuffer.slice(0, endIndex);\n\t\t\t\tconst remaining = this.pasteBuffer.slice(endIndex + BRACKETED_PASTE_END.length);\n\n\t\t\t\tthis.pasteMode = false;\n\t\t\t\tthis.pasteBuffer = \"\";\n\n\t\t\t\tthis.emit(\"paste\", pastedContent);\n\n\t\t\t\tif (remaining.length > 0) {\n\t\t\t\t\tthis.process(remaining);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst startIndex = this.buffer.indexOf(BRACKETED_PASTE_START);\n\t\tif (startIndex !== -1) {\n\t\t\tif (startIndex > 0) {\n\t\t\t\tconst beforePaste = this.buffer.slice(0, startIndex);\n\t\t\t\tconst result = extractCompleteSequences(beforePaste);\n\t\t\t\tfor (const sequence of result.sequences) {\n\t\t\t\t\tthis.emit(\"data\", sequence);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.buffer = this.buffer.slice(startIndex + BRACKETED_PASTE_START.length);\n\t\t\tthis.pasteMode = true;\n\t\t\tthis.pasteBuffer = this.buffer;\n\t\t\tthis.buffer = \"\";\n\n\t\t\tconst endIndex = this.pasteBuffer.indexOf(BRACKETED_PASTE_END);\n\t\t\tif (endIndex !== -1) {\n\t\t\t\tconst pastedContent = this.pasteBuffer.slice(0, endIndex);\n\t\t\t\tconst remaining = this.pasteBuffer.slice(endIndex + BRACKETED_PASTE_END.length);\n\n\t\t\t\tthis.pasteMode = false;\n\t\t\t\tthis.pasteBuffer = \"\";\n\n\t\t\t\tthis.emit(\"paste\", pastedContent);\n\n\t\t\t\tif (remaining.length > 0) {\n\t\t\t\t\tthis.process(remaining);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst result = extractCompleteSequences(this.buffer);\n\t\tthis.buffer = result.remainder;\n\n\t\tfor (const sequence of result.sequences) {\n\t\t\tthis.emit(\"data\", sequence);\n\t\t}\n\n\t\tif (this.buffer.length > 0) {\n\t\t\tthis.timeout = setTimeout(() => {\n\t\t\t\tconst flushed = this.flush();\n\n\t\t\t\tfor (const sequence of flushed) {\n\t\t\t\t\tthis.emit(\"data\", sequence);\n\t\t\t\t}\n\t\t\t}, this.timeoutMs);\n\t\t}\n\t}\n\n\tflush(): string[] {\n\t\tif (this.timeout) {\n\t\t\tclearTimeout(this.timeout);\n\t\t\tthis.timeout = null;\n\t\t}\n\n\t\tif (this.buffer.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst sequences = [this.buffer];\n\t\tthis.buffer = \"\";\n\t\treturn sequences;\n\t}\n\n\tclear(): void {\n\t\tif (this.timeout) {\n\t\t\tclearTimeout(this.timeout);\n\t\t\tthis.timeout = null;\n\t\t}\n\t\tthis.buffer = \"\";\n\t\tthis.pasteMode = false;\n\t\tthis.pasteBuffer = \"\";\n\t}\n\n\tgetBuffer(): string {\n\t\treturn this.buffer;\n\t}\n\n\tdestroy(): void {\n\t\tthis.clear();\n\t}\n}\n"]}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * DEC private mode 2026 — Synchronized Output.
3
+ *
4
+ * Wraps a render frame in `\e[?2026h` (begin) and `\e[?2026l` (end) so the
5
+ * terminal commits the entire frame atomically. Eliminates intra-frame flicker
6
+ * (interleaved redraws) on terminals that support the sequence: kitty, iTerm2,
7
+ * WezTerm, foot, alacritty (>=0.13), Ghostty, Windows Terminal (>=1.18),
8
+ * Konsole, contour, mintty, recent xterm. Terminals that do not understand
9
+ * the sequence ignore it and the output renders normally — i.e. emitting it
10
+ * is safe everywhere, but we still gate on capability detection because:
11
+ *
12
+ * 1. Some legacy terminals print the literal sequence as text (broken
13
+ * private-mode handling).
14
+ * 2. Multiplexers (tmux <3.4) need passthrough to be enabled or the
15
+ * sequence is silently dropped, leaving rendering unchanged but adding
16
+ * bytes to the output. We disable in those environments by default.
17
+ * 3. SSH+screen sessions sometimes lie via TERM=xterm-256color; the
18
+ * classification in `terminal-detect.ts` lets us be conservative.
19
+ */
20
+ import type { TerminalIdentity } from "./terminal-detect.js";
21
+ export declare const SYNC_OUTPUT_BEGIN = "\u001B[?2026h";
22
+ export declare const SYNC_OUTPUT_END = "\u001B[?2026l";
23
+ export type SyncOutputSupport = "supported" | "unsupported" | "unknown";
24
+ /**
25
+ * Inputs required to classify whether the current terminal honors DEC 2026.
26
+ *
27
+ * We accept a `TerminalIdentity` rather than re-reading the env so the same
28
+ * probe result drives every consumer (TUI, status line, diff view).
29
+ */
30
+ export interface SyncOutputCapabilityInput {
31
+ identity: TerminalIdentity;
32
+ /** Override knob — set CAVE_SYNC_OUTPUT=on|off|auto. Default: auto. */
33
+ override?: "on" | "off" | "auto";
34
+ }
35
+ /**
36
+ * Classify DEC 2026 support from a probed terminal identity.
37
+ *
38
+ * Conservative: we return `unsupported` when the identity is unknown rather
39
+ * than emitting bytes that may print as literal text. The override `on`
40
+ * forces emission (useful when running under a wrapper that strips the
41
+ * TERM_PROGRAM env). The override `off` disables emission unconditionally.
42
+ */
43
+ export declare function classifySyncOutputSupport(input: SyncOutputCapabilityInput): SyncOutputSupport;
44
+ /**
45
+ * Wrap a buffer in DEC 2026 begin/end markers when supported.
46
+ *
47
+ * On `unsupported`/`unknown`, returns the buffer unchanged. The caller
48
+ * should still call `wrap` and not branch — this is the cheap path.
49
+ */
50
+ export declare function wrapSyncOutput(buffer: string, support: SyncOutputSupport): string;
51
+ /**
52
+ * Standalone emit helper for renderers that build their buffer in two steps
53
+ * (e.g. a status-line redraw that wants begin → write → end without holding
54
+ * the whole frame in memory).
55
+ */
56
+ export declare function emitSyncOutputBegin(write: (s: string) => void, support: SyncOutputSupport): void;
57
+ export declare function emitSyncOutputEnd(write: (s: string) => void, support: SyncOutputSupport): void;
58
+ //# sourceMappingURL=sync-output.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-output.d.ts","sourceRoot":"","sources":["../src/sync-output.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,eAAO,MAAM,iBAAiB,kBAAgB,CAAC;AAC/C,eAAO,MAAM,eAAe,kBAAgB,CAAC;AAE7C,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,aAAa,GAAG,SAAS,CAAC;AAExE;;;;;GAKG;AACH,MAAM,WAAW,yBAAyB;IACzC,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,yEAAuE;IACvE,QAAQ,CAAC,EAAE,IAAI,GAAG,KAAK,GAAG,MAAM,CAAC;CACjC;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,yBAAyB,GAAG,iBAAiB,CAmB7F;AA4BD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAGjF;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,EAAE,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAEhG;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,EAAE,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAE9F","sourcesContent":["/**\n * DEC private mode 2026 — Synchronized Output.\n *\n * Wraps a render frame in `\\e[?2026h` (begin) and `\\e[?2026l` (end) so the\n * terminal commits the entire frame atomically. Eliminates intra-frame flicker\n * (interleaved redraws) on terminals that support the sequence: kitty, iTerm2,\n * WezTerm, foot, alacritty (>=0.13), Ghostty, Windows Terminal (>=1.18),\n * Konsole, contour, mintty, recent xterm. Terminals that do not understand\n * the sequence ignore it and the output renders normally — i.e. emitting it\n * is safe everywhere, but we still gate on capability detection because:\n *\n * 1. Some legacy terminals print the literal sequence as text (broken\n * private-mode handling).\n * 2. Multiplexers (tmux <3.4) need passthrough to be enabled or the\n * sequence is silently dropped, leaving rendering unchanged but adding\n * bytes to the output. We disable in those environments by default.\n * 3. SSH+screen sessions sometimes lie via TERM=xterm-256color; the\n * classification in `terminal-detect.ts` lets us be conservative.\n */\nimport type { TerminalIdentity } from \"./terminal-detect.js\";\n\nexport const SYNC_OUTPUT_BEGIN = \"\\x1b[?2026h\";\nexport const SYNC_OUTPUT_END = \"\\x1b[?2026l\";\n\nexport type SyncOutputSupport = \"supported\" | \"unsupported\" | \"unknown\";\n\n/**\n * Inputs required to classify whether the current terminal honors DEC 2026.\n *\n * We accept a `TerminalIdentity` rather than re-reading the env so the same\n * probe result drives every consumer (TUI, status line, diff view).\n */\nexport interface SyncOutputCapabilityInput {\n\tidentity: TerminalIdentity;\n\t/** Override knob — set CAVE_SYNC_OUTPUT=on|off|auto. Default: auto. */\n\toverride?: \"on\" | \"off\" | \"auto\";\n}\n\n/**\n * Classify DEC 2026 support from a probed terminal identity.\n *\n * Conservative: we return `unsupported` when the identity is unknown rather\n * than emitting bytes that may print as literal text. The override `on`\n * forces emission (useful when running under a wrapper that strips the\n * TERM_PROGRAM env). The override `off` disables emission unconditionally.\n */\nexport function classifySyncOutputSupport(input: SyncOutputCapabilityInput): SyncOutputSupport {\n\tconst override = input.override ?? \"auto\";\n\tif (override === \"on\") return \"supported\";\n\tif (override === \"off\") return \"unsupported\";\n\n\tconst { identity } = input;\n\n\t// Multiplexers need explicit passthrough; treat as unknown unless an\n\t// inner-host hint says otherwise. Modern tmux (>=3.4) advertises\n\t// passthrough but we cannot detect that from env alone, so be safe.\n\tif (identity.multiplexer === \"tmux\" || identity.multiplexer === \"screen\") {\n\t\t// Allow if the host program is known-supporting AND user opts in via env.\n\t\tif (process.env.CAVE_SYNC_OUTPUT_MULTIPLEXER === \"1\" && identity.hostProgram) {\n\t\t\treturn classifyByProgram(identity.hostProgram);\n\t\t}\n\t\treturn \"unsupported\";\n\t}\n\n\treturn classifyByProgram(identity.program);\n}\n\nfunction classifyByProgram(program: TerminalIdentity[\"program\"]): SyncOutputSupport {\n\tswitch (program) {\n\t\tcase \"kitty\":\n\t\tcase \"iterm2\":\n\t\tcase \"wezterm\":\n\t\tcase \"alacritty\":\n\t\tcase \"ghostty\":\n\t\tcase \"windows-terminal\":\n\t\tcase \"vscode\":\n\t\t\treturn \"supported\";\n\t\tcase \"vte\": // GNOME Terminal/Tilix — VTE >= 0.71. Cannot version-check from env. Conservative on.\n\t\t\treturn \"supported\";\n\t\tcase \"apple-terminal\":\n\t\tcase \"linux-console\":\n\t\t\treturn \"unsupported\";\n\t\tcase \"tmux\":\n\t\tcase \"screen\":\n\t\t\treturn \"unsupported\";\n\t\tcase \"cmux\":\n\t\tcase \"unknown\":\n\t\t\treturn \"unknown\";\n\t\tdefault:\n\t\t\treturn \"unknown\";\n\t}\n}\n\n/**\n * Wrap a buffer in DEC 2026 begin/end markers when supported.\n *\n * On `unsupported`/`unknown`, returns the buffer unchanged. The caller\n * should still call `wrap` and not branch — this is the cheap path.\n */\nexport function wrapSyncOutput(buffer: string, support: SyncOutputSupport): string {\n\tif (support !== \"supported\") return buffer;\n\treturn `${SYNC_OUTPUT_BEGIN}${buffer}${SYNC_OUTPUT_END}`;\n}\n\n/**\n * Standalone emit helper for renderers that build their buffer in two steps\n * (e.g. a status-line redraw that wants begin → write → end without holding\n * the whole frame in memory).\n */\nexport function emitSyncOutputBegin(write: (s: string) => void, support: SyncOutputSupport): void {\n\tif (support === \"supported\") write(SYNC_OUTPUT_BEGIN);\n}\n\nexport function emitSyncOutputEnd(write: (s: string) => void, support: SyncOutputSupport): void {\n\tif (support === \"supported\") write(SYNC_OUTPUT_END);\n}\n"]}
@@ -0,0 +1,79 @@
1
+ export const SYNC_OUTPUT_BEGIN = "\x1b[?2026h";
2
+ export const SYNC_OUTPUT_END = "\x1b[?2026l";
3
+ /**
4
+ * Classify DEC 2026 support from a probed terminal identity.
5
+ *
6
+ * Conservative: we return `unsupported` when the identity is unknown rather
7
+ * than emitting bytes that may print as literal text. The override `on`
8
+ * forces emission (useful when running under a wrapper that strips the
9
+ * TERM_PROGRAM env). The override `off` disables emission unconditionally.
10
+ */
11
+ export function classifySyncOutputSupport(input) {
12
+ const override = input.override ?? "auto";
13
+ if (override === "on")
14
+ return "supported";
15
+ if (override === "off")
16
+ return "unsupported";
17
+ const { identity } = input;
18
+ // Multiplexers need explicit passthrough; treat as unknown unless an
19
+ // inner-host hint says otherwise. Modern tmux (>=3.4) advertises
20
+ // passthrough but we cannot detect that from env alone, so be safe.
21
+ if (identity.multiplexer === "tmux" || identity.multiplexer === "screen") {
22
+ // Allow if the host program is known-supporting AND user opts in via env.
23
+ if (process.env.CAVE_SYNC_OUTPUT_MULTIPLEXER === "1" && identity.hostProgram) {
24
+ return classifyByProgram(identity.hostProgram);
25
+ }
26
+ return "unsupported";
27
+ }
28
+ return classifyByProgram(identity.program);
29
+ }
30
+ function classifyByProgram(program) {
31
+ switch (program) {
32
+ case "kitty":
33
+ case "iterm2":
34
+ case "wezterm":
35
+ case "alacritty":
36
+ case "ghostty":
37
+ case "windows-terminal":
38
+ case "vscode":
39
+ return "supported";
40
+ case "vte": // GNOME Terminal/Tilix — VTE >= 0.71. Cannot version-check from env. Conservative on.
41
+ return "supported";
42
+ case "apple-terminal":
43
+ case "linux-console":
44
+ return "unsupported";
45
+ case "tmux":
46
+ case "screen":
47
+ return "unsupported";
48
+ case "cmux":
49
+ case "unknown":
50
+ return "unknown";
51
+ default:
52
+ return "unknown";
53
+ }
54
+ }
55
+ /**
56
+ * Wrap a buffer in DEC 2026 begin/end markers when supported.
57
+ *
58
+ * On `unsupported`/`unknown`, returns the buffer unchanged. The caller
59
+ * should still call `wrap` and not branch — this is the cheap path.
60
+ */
61
+ export function wrapSyncOutput(buffer, support) {
62
+ if (support !== "supported")
63
+ return buffer;
64
+ return `${SYNC_OUTPUT_BEGIN}${buffer}${SYNC_OUTPUT_END}`;
65
+ }
66
+ /**
67
+ * Standalone emit helper for renderers that build their buffer in two steps
68
+ * (e.g. a status-line redraw that wants begin → write → end without holding
69
+ * the whole frame in memory).
70
+ */
71
+ export function emitSyncOutputBegin(write, support) {
72
+ if (support === "supported")
73
+ write(SYNC_OUTPUT_BEGIN);
74
+ }
75
+ export function emitSyncOutputEnd(write, support) {
76
+ if (support === "supported")
77
+ write(SYNC_OUTPUT_END);
78
+ }
79
+ //# sourceMappingURL=sync-output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-output.js","sourceRoot":"","sources":["../src/sync-output.ts"],"names":[],"mappings":"AAqBA,MAAM,CAAC,MAAM,iBAAiB,GAAG,aAAa,CAAC;AAC/C,MAAM,CAAC,MAAM,eAAe,GAAG,aAAa,CAAC;AAgB7C;;;;;;;GAOG;AACH,MAAM,UAAU,yBAAyB,CAAC,KAAgC,EAAqB;IAC9F,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,IAAI,MAAM,CAAC;IAC1C,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,WAAW,CAAC;IAC1C,IAAI,QAAQ,KAAK,KAAK;QAAE,OAAO,aAAa,CAAC;IAE7C,MAAM,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IAE3B,qEAAqE;IACrE,iEAAiE;IACjE,oEAAoE;IACpE,IAAI,QAAQ,CAAC,WAAW,KAAK,MAAM,IAAI,QAAQ,CAAC,WAAW,KAAK,QAAQ,EAAE,CAAC;QAC1E,0EAA0E;QAC1E,IAAI,OAAO,CAAC,GAAG,CAAC,4BAA4B,KAAK,GAAG,IAAI,QAAQ,CAAC,WAAW,EAAE,CAAC;YAC9E,OAAO,iBAAiB,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QAChD,CAAC;QACD,OAAO,aAAa,CAAC;IACtB,CAAC;IAED,OAAO,iBAAiB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;AAAA,CAC3C;AAED,SAAS,iBAAiB,CAAC,OAAoC,EAAqB;IACnF,QAAQ,OAAO,EAAE,CAAC;QACjB,KAAK,OAAO,CAAC;QACb,KAAK,QAAQ,CAAC;QACd,KAAK,SAAS,CAAC;QACf,KAAK,WAAW,CAAC;QACjB,KAAK,SAAS,CAAC;QACf,KAAK,kBAAkB,CAAC;QACxB,KAAK,QAAQ;YACZ,OAAO,WAAW,CAAC;QACpB,KAAK,KAAK,EAAE,wFAAsF;YACjG,OAAO,WAAW,CAAC;QACpB,KAAK,gBAAgB,CAAC;QACtB,KAAK,eAAe;YACnB,OAAO,aAAa,CAAC;QACtB,KAAK,MAAM,CAAC;QACZ,KAAK,QAAQ;YACZ,OAAO,aAAa,CAAC;QACtB,KAAK,MAAM,CAAC;QACZ,KAAK,SAAS;YACb,OAAO,SAAS,CAAC;QAClB;YACC,OAAO,SAAS,CAAC;IACnB,CAAC;AAAA,CACD;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,MAAc,EAAE,OAA0B,EAAU;IAClF,IAAI,OAAO,KAAK,WAAW;QAAE,OAAO,MAAM,CAAC;IAC3C,OAAO,GAAG,iBAAiB,GAAG,MAAM,GAAG,eAAe,EAAE,CAAC;AAAA,CACzD;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAA0B,EAAE,OAA0B,EAAQ;IACjG,IAAI,OAAO,KAAK,WAAW;QAAE,KAAK,CAAC,iBAAiB,CAAC,CAAC;AAAA,CACtD;AAED,MAAM,UAAU,iBAAiB,CAAC,KAA0B,EAAE,OAA0B,EAAQ;IAC/F,IAAI,OAAO,KAAK,WAAW;QAAE,KAAK,CAAC,eAAe,CAAC,CAAC;AAAA,CACpD","sourcesContent":["/**\n * DEC private mode 2026 — Synchronized Output.\n *\n * Wraps a render frame in `\\e[?2026h` (begin) and `\\e[?2026l` (end) so the\n * terminal commits the entire frame atomically. Eliminates intra-frame flicker\n * (interleaved redraws) on terminals that support the sequence: kitty, iTerm2,\n * WezTerm, foot, alacritty (>=0.13), Ghostty, Windows Terminal (>=1.18),\n * Konsole, contour, mintty, recent xterm. Terminals that do not understand\n * the sequence ignore it and the output renders normally — i.e. emitting it\n * is safe everywhere, but we still gate on capability detection because:\n *\n * 1. Some legacy terminals print the literal sequence as text (broken\n * private-mode handling).\n * 2. Multiplexers (tmux <3.4) need passthrough to be enabled or the\n * sequence is silently dropped, leaving rendering unchanged but adding\n * bytes to the output. We disable in those environments by default.\n * 3. SSH+screen sessions sometimes lie via TERM=xterm-256color; the\n * classification in `terminal-detect.ts` lets us be conservative.\n */\nimport type { TerminalIdentity } from \"./terminal-detect.js\";\n\nexport const SYNC_OUTPUT_BEGIN = \"\\x1b[?2026h\";\nexport const SYNC_OUTPUT_END = \"\\x1b[?2026l\";\n\nexport type SyncOutputSupport = \"supported\" | \"unsupported\" | \"unknown\";\n\n/**\n * Inputs required to classify whether the current terminal honors DEC 2026.\n *\n * We accept a `TerminalIdentity` rather than re-reading the env so the same\n * probe result drives every consumer (TUI, status line, diff view).\n */\nexport interface SyncOutputCapabilityInput {\n\tidentity: TerminalIdentity;\n\t/** Override knob — set CAVE_SYNC_OUTPUT=on|off|auto. Default: auto. */\n\toverride?: \"on\" | \"off\" | \"auto\";\n}\n\n/**\n * Classify DEC 2026 support from a probed terminal identity.\n *\n * Conservative: we return `unsupported` when the identity is unknown rather\n * than emitting bytes that may print as literal text. The override `on`\n * forces emission (useful when running under a wrapper that strips the\n * TERM_PROGRAM env). The override `off` disables emission unconditionally.\n */\nexport function classifySyncOutputSupport(input: SyncOutputCapabilityInput): SyncOutputSupport {\n\tconst override = input.override ?? \"auto\";\n\tif (override === \"on\") return \"supported\";\n\tif (override === \"off\") return \"unsupported\";\n\n\tconst { identity } = input;\n\n\t// Multiplexers need explicit passthrough; treat as unknown unless an\n\t// inner-host hint says otherwise. Modern tmux (>=3.4) advertises\n\t// passthrough but we cannot detect that from env alone, so be safe.\n\tif (identity.multiplexer === \"tmux\" || identity.multiplexer === \"screen\") {\n\t\t// Allow if the host program is known-supporting AND user opts in via env.\n\t\tif (process.env.CAVE_SYNC_OUTPUT_MULTIPLEXER === \"1\" && identity.hostProgram) {\n\t\t\treturn classifyByProgram(identity.hostProgram);\n\t\t}\n\t\treturn \"unsupported\";\n\t}\n\n\treturn classifyByProgram(identity.program);\n}\n\nfunction classifyByProgram(program: TerminalIdentity[\"program\"]): SyncOutputSupport {\n\tswitch (program) {\n\t\tcase \"kitty\":\n\t\tcase \"iterm2\":\n\t\tcase \"wezterm\":\n\t\tcase \"alacritty\":\n\t\tcase \"ghostty\":\n\t\tcase \"windows-terminal\":\n\t\tcase \"vscode\":\n\t\t\treturn \"supported\";\n\t\tcase \"vte\": // GNOME Terminal/Tilix — VTE >= 0.71. Cannot version-check from env. Conservative on.\n\t\t\treturn \"supported\";\n\t\tcase \"apple-terminal\":\n\t\tcase \"linux-console\":\n\t\t\treturn \"unsupported\";\n\t\tcase \"tmux\":\n\t\tcase \"screen\":\n\t\t\treturn \"unsupported\";\n\t\tcase \"cmux\":\n\t\tcase \"unknown\":\n\t\t\treturn \"unknown\";\n\t\tdefault:\n\t\t\treturn \"unknown\";\n\t}\n}\n\n/**\n * Wrap a buffer in DEC 2026 begin/end markers when supported.\n *\n * On `unsupported`/`unknown`, returns the buffer unchanged. The caller\n * should still call `wrap` and not branch — this is the cheap path.\n */\nexport function wrapSyncOutput(buffer: string, support: SyncOutputSupport): string {\n\tif (support !== \"supported\") return buffer;\n\treturn `${SYNC_OUTPUT_BEGIN}${buffer}${SYNC_OUTPUT_END}`;\n}\n\n/**\n * Standalone emit helper for renderers that build their buffer in two steps\n * (e.g. a status-line redraw that wants begin → write → end without holding\n * the whole frame in memory).\n */\nexport function emitSyncOutputBegin(write: (s: string) => void, support: SyncOutputSupport): void {\n\tif (support === \"supported\") write(SYNC_OUTPUT_BEGIN);\n}\n\nexport function emitSyncOutputEnd(write: (s: string) => void, support: SyncOutputSupport): void {\n\tif (support === \"supported\") write(SYNC_OUTPUT_END);\n}\n"]}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Terminal identity and background detection.
3
+ *
4
+ * Covers cavekit-terminal-blend R1 (background luminance detection via OSC 11,
5
+ * COLORFGBG, and CAVE_TERM_BG override) and R2 (terminal program identity).
6
+ */
7
+ import type { ProcessTerminal } from "./terminal.js";
8
+ export type TerminalProgram = "ghostty" | "iterm2" | "apple-terminal" | "kitty" | "wezterm" | "alacritty" | "vte" | "tmux" | "screen" | "linux-console" | "cmux" | "windows-terminal" | "vscode" | "unknown";
9
+ export type Multiplexer = "tmux" | "screen" | "none";
10
+ export type BackgroundClassification = "dark" | "light";
11
+ export interface TerminalIdentity {
12
+ program: TerminalProgram;
13
+ /** Outer host terminal when running under a multiplexer. */
14
+ hostProgram?: TerminalProgram;
15
+ version?: string;
16
+ multiplexer: Multiplexer;
17
+ isSsh: boolean;
18
+ raw: Record<string, string | undefined>;
19
+ }
20
+ export interface TerminalBackground {
21
+ r: number;
22
+ g: number;
23
+ b: number;
24
+ /** Relative luminance per WCAG 2.x (0-1). */
25
+ luminance: number;
26
+ classification: BackgroundClassification;
27
+ source: "osc11" | "colorfgbg" | "override";
28
+ }
29
+ export interface ProbeResult {
30
+ identity: TerminalIdentity;
31
+ background: TerminalBackground | null;
32
+ /** Final classification — always a concrete value (falls back to "dark"). */
33
+ classification: BackgroundClassification;
34
+ }
35
+ export declare function detectTerminalIdentity(env?: NodeJS.ProcessEnv): TerminalIdentity;
36
+ /** WCAG 2.x relative luminance from sRGB 0-255 components. */
37
+ export declare function relativeLuminance(r: number, g: number, b: number): number;
38
+ /**
39
+ * Query the terminal's background color. Tries CAVE_TERM_BG override, then OSC 11,
40
+ * then COLORFGBG. Returns null when no signal is available. Total wall time capped
41
+ * by timeoutMs (default 200ms for the OSC 11 part; env lookups are instant).
42
+ */
43
+ export declare function queryTerminalBackground(terminal: ProcessTerminal | null, timeoutMs?: number, env?: NodeJS.ProcessEnv): Promise<TerminalBackground | null>;
44
+ /**
45
+ * Standalone OSC 11 query that works before the TUI has started.
46
+ *
47
+ * Briefly installs its own stdin listener in raw mode, writes the OSC 11
48
+ * query, waits up to `timeoutMs` for the response, then restores stdin.
49
+ * Returns null on timeout / non-TTY / parse failure.
50
+ *
51
+ * Use this during startup when we need the background classification before
52
+ * theme init but haven't yet constructed a ProcessTerminal.
53
+ */
54
+ export declare function queryOsc11Standalone(timeoutMs?: number): Promise<TerminalBackground | null>;
55
+ /**
56
+ * One-call probe: identity + background + final classification (with "dark" fallback).
57
+ * The classification field is always concrete per R1.
58
+ */
59
+ export declare function probeTerminal(options: {
60
+ terminal?: ProcessTerminal | null;
61
+ timeoutMs?: number;
62
+ env?: NodeJS.ProcessEnv;
63
+ /** Whether to run the standalone OSC 11 query (needs raw stdin). Default: true when no terminal is supplied. */
64
+ useStandaloneOsc?: boolean;
65
+ }): Promise<ProbeResult>;
66
+ //# sourceMappingURL=terminal-detect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal-detect.d.ts","sourceRoot":"","sources":["../src/terminal-detect.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,MAAM,MAAM,eAAe,GACxB,SAAS,GACT,QAAQ,GACR,gBAAgB,GAChB,OAAO,GACP,SAAS,GACT,WAAW,GACX,KAAK,GACL,MAAM,GACN,QAAQ,GACR,eAAe,GACf,MAAM,GACN,kBAAkB,GAClB,QAAQ,GACR,SAAS,CAAC;AAEb,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;AACrD,MAAM,MAAM,wBAAwB,GAAG,MAAM,GAAG,OAAO,CAAC;AAExD,MAAM,WAAW,gBAAgB;IAChC,OAAO,EAAE,eAAe,CAAC;IACzB,4DAA4D;IAC5D,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;IACzB,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,kBAAkB;IAClC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,wBAAwB,CAAC;IACzC,MAAM,EAAE,OAAO,GAAG,WAAW,GAAG,UAAU,CAAC;CAC3C;AAED,MAAM,WAAW,WAAW;IAC3B,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,UAAU,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACtC,+EAA6E;IAC7E,cAAc,EAAE,wBAAwB,CAAC;CACzC;AA+CD,wBAAgB,sBAAsB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,gBAAgB,CAwC7F;AAED,8DAA8D;AAC9D,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAMzE;AAsDD;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC5C,QAAQ,EAAE,eAAe,GAAG,IAAI,EAChC,SAAS,SAAM,EACf,GAAG,GAAE,MAAM,CAAC,UAAwB,GAClC,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CA8CpC;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,SAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CA4DxF;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAC5C,QAAQ,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,gHAAgH;IAChH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC3B,GAAG,OAAO,CAAC,WAAW,CAAC,CA0CvB","sourcesContent":["/**\n * Terminal identity and background detection.\n *\n * Covers cavekit-terminal-blend R1 (background luminance detection via OSC 11,\n * COLORFGBG, and CAVE_TERM_BG override) and R2 (terminal program identity).\n */\n\nimport type { ProcessTerminal } from \"./terminal.js\";\n\nexport type TerminalProgram =\n\t| \"ghostty\"\n\t| \"iterm2\"\n\t| \"apple-terminal\"\n\t| \"kitty\"\n\t| \"wezterm\"\n\t| \"alacritty\"\n\t| \"vte\"\n\t| \"tmux\"\n\t| \"screen\"\n\t| \"linux-console\"\n\t| \"cmux\"\n\t| \"windows-terminal\"\n\t| \"vscode\"\n\t| \"unknown\";\n\nexport type Multiplexer = \"tmux\" | \"screen\" | \"none\";\nexport type BackgroundClassification = \"dark\" | \"light\";\n\nexport interface TerminalIdentity {\n\tprogram: TerminalProgram;\n\t/** Outer host terminal when running under a multiplexer. */\n\thostProgram?: TerminalProgram;\n\tversion?: string;\n\tmultiplexer: Multiplexer;\n\tisSsh: boolean;\n\traw: Record<string, string | undefined>;\n}\n\nexport interface TerminalBackground {\n\tr: number;\n\tg: number;\n\tb: number;\n\t/** Relative luminance per WCAG 2.x (0-1). */\n\tluminance: number;\n\tclassification: BackgroundClassification;\n\tsource: \"osc11\" | \"colorfgbg\" | \"override\";\n}\n\nexport interface ProbeResult {\n\tidentity: TerminalIdentity;\n\tbackground: TerminalBackground | null;\n\t/** Final classification — always a concrete value (falls back to \"dark\"). */\n\tclassification: BackgroundClassification;\n}\n\nfunction _getEnv(name: string): string | undefined {\n\tconst v = process.env[name];\n\treturn v === undefined || v === \"\" ? undefined : v;\n}\n\nfunction classifyProgram(env: NodeJS.ProcessEnv): { program: TerminalProgram; version?: string } {\n\tconst termProgram = env.TERM_PROGRAM?.toLowerCase();\n\tconst termProgramVersion = env.TERM_PROGRAM_VERSION;\n\n\tif (env.GHOSTTY_RESOURCES_DIR || termProgram === \"ghostty\") {\n\t\treturn { program: \"ghostty\", version: termProgramVersion };\n\t}\n\tif (env.KITTY_WINDOW_ID || env.TERM?.toLowerCase().includes(\"kitty\")) {\n\t\treturn { program: \"kitty\", version: termProgramVersion };\n\t}\n\tif (env.WEZTERM_EXECUTABLE || termProgram === \"wezterm\") {\n\t\treturn { program: \"wezterm\", version: termProgramVersion };\n\t}\n\tif (env.ALACRITTY_LOG || termProgram === \"alacritty\") {\n\t\treturn { program: \"alacritty\", version: termProgramVersion };\n\t}\n\tif (termProgram === \"iterm.app\" || env.ITERM_SESSION_ID) {\n\t\treturn { program: \"iterm2\", version: termProgramVersion };\n\t}\n\tif (termProgram === \"apple_terminal\") {\n\t\treturn { program: \"apple-terminal\", version: termProgramVersion };\n\t}\n\tif (env.VTE_VERSION) {\n\t\treturn { program: \"vte\", version: env.VTE_VERSION };\n\t}\n\tif (env.WT_SESSION) {\n\t\treturn { program: \"windows-terminal\" };\n\t}\n\tif (env.VSCODE_INJECTION || termProgram === \"vscode\") {\n\t\treturn { program: \"vscode\", version: termProgramVersion };\n\t}\n\tif (env.CMUX_SESSION || env.CMUX || termProgram === \"cmux\") {\n\t\treturn { program: \"cmux\", version: termProgramVersion };\n\t}\n\tif (env.TERM === \"linux\") {\n\t\treturn { program: \"linux-console\" };\n\t}\n\treturn { program: \"unknown\" };\n}\n\nexport function detectTerminalIdentity(env: NodeJS.ProcessEnv = process.env): TerminalIdentity {\n\tconst tmux = Boolean(env.TMUX);\n\tconst screen = Boolean(env.STY);\n\tconst multiplexer: Multiplexer = tmux ? \"tmux\" : screen ? \"screen\" : \"none\";\n\n\tconst host = classifyProgram(env);\n\tlet program: TerminalProgram = host.program;\n\tlet hostProgram: TerminalProgram | undefined;\n\n\tif (tmux) {\n\t\thostProgram = host.program !== \"unknown\" ? host.program : undefined;\n\t\tprogram = \"tmux\";\n\t} else if (screen) {\n\t\thostProgram = host.program !== \"unknown\" ? host.program : undefined;\n\t\tprogram = \"screen\";\n\t}\n\n\treturn {\n\t\tprogram,\n\t\thostProgram,\n\t\tversion: host.version,\n\t\tmultiplexer,\n\t\tisSsh: Boolean(env.SSH_TTY || env.SSH_CONNECTION),\n\t\traw: {\n\t\t\tTERM_PROGRAM: env.TERM_PROGRAM,\n\t\t\tTERM_PROGRAM_VERSION: env.TERM_PROGRAM_VERSION,\n\t\t\tTERM: env.TERM,\n\t\t\tCOLORTERM: env.COLORTERM,\n\t\t\tCOLORFGBG: env.COLORFGBG,\n\t\t\tTMUX: env.TMUX,\n\t\t\tSTY: env.STY,\n\t\t\tSSH_TTY: env.SSH_TTY,\n\t\t\tGHOSTTY_RESOURCES_DIR: env.GHOSTTY_RESOURCES_DIR,\n\t\t\tKITTY_WINDOW_ID: env.KITTY_WINDOW_ID,\n\t\t\tWEZTERM_EXECUTABLE: env.WEZTERM_EXECUTABLE,\n\t\t\tITERM_SESSION_ID: env.ITERM_SESSION_ID,\n\t\t\tVTE_VERSION: env.VTE_VERSION,\n\t\t\tWT_SESSION: env.WT_SESSION,\n\t\t},\n\t};\n}\n\n/** WCAG 2.x relative luminance from sRGB 0-255 components. */\nexport function relativeLuminance(r: number, g: number, b: number): number {\n\tconst toLinear = (c: number): number => {\n\t\tconst s = c / 255;\n\t\treturn s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;\n\t};\n\treturn 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);\n}\n\nfunction classifyLuminance(lum: number): BackgroundClassification {\n\treturn lum >= 0.5 ? \"light\" : \"dark\";\n}\n\nfunction parseOsc11Response(data: string): { r: number; g: number; b: number } | null {\n\t// Match: ESC ]11;rgb:RRRR/GGGG/BBBB (BEL | ESC \\)\n\t// Components may be 1-4 hex digits (commonly 4).\n\tconst match = data.match(/\\x1b\\]11;rgb:([0-9a-fA-F]{1,4})\\/([0-9a-fA-F]{1,4})\\/([0-9a-fA-F]{1,4})(?:\\x07|\\x1b\\\\)/);\n\tif (!match) return null;\n\tconst normalize = (hex: string): number => {\n\t\tconst v = parseInt(hex, 16);\n\t\tconst max = (1 << (hex.length * 4)) - 1;\n\t\treturn Math.round((v / max) * 255);\n\t};\n\treturn { r: normalize(match[1]), g: normalize(match[2]), b: normalize(match[3]) };\n}\n\nfunction parseColorFgBg(raw: string | undefined): BackgroundClassification | null {\n\tif (!raw) return null;\n\tconst parts = raw.split(\";\");\n\tconst bg = parts[parts.length - 1];\n\tconst n = parseInt(bg, 10);\n\tif (!Number.isFinite(n)) return null;\n\t// 0-6 and 8 = dark variants; 7 and 9-15 = light variants (per xterm convention).\n\tif (n >= 0 && n <= 6) return \"dark\";\n\tif (n === 8) return \"dark\";\n\tif (n === 7) return \"light\";\n\tif (n >= 9 && n <= 15) return \"light\";\n\treturn null;\n}\n\nfunction parseOverride(raw: string | undefined): TerminalBackground | null {\n\tif (!raw) return null;\n\tconst v = raw.trim().toLowerCase();\n\tif (v === \"dark\") {\n\t\treturn { r: 0, g: 0, b: 0, luminance: 0, classification: \"dark\", source: \"override\" };\n\t}\n\tif (v === \"light\") {\n\t\treturn { r: 255, g: 255, b: 255, luminance: 1, classification: \"light\", source: \"override\" };\n\t}\n\t// Accept #rrggbb\n\tconst hex = v.startsWith(\"#\") ? v.slice(1) : v;\n\tif (/^[0-9a-f]{6}$/.test(hex)) {\n\t\tconst r = parseInt(hex.slice(0, 2), 16);\n\t\tconst g = parseInt(hex.slice(2, 4), 16);\n\t\tconst b = parseInt(hex.slice(4, 6), 16);\n\t\tconst lum = relativeLuminance(r, g, b);\n\t\treturn { r, g, b, luminance: lum, classification: classifyLuminance(lum), source: \"override\" };\n\t}\n\treturn null;\n}\n\n/**\n * Query the terminal's background color. Tries CAVE_TERM_BG override, then OSC 11,\n * then COLORFGBG. Returns null when no signal is available. Total wall time capped\n * by timeoutMs (default 200ms for the OSC 11 part; env lookups are instant).\n */\nexport async function queryTerminalBackground(\n\tterminal: ProcessTerminal | null,\n\ttimeoutMs = 200,\n\tenv: NodeJS.ProcessEnv = process.env,\n): Promise<TerminalBackground | null> {\n\t// 1. CAVE_TERM_BG override wins unconditionally.\n\tconst override = parseOverride(env.CAVE_TERM_BG);\n\tif (override) return override;\n\n\t// 2. OSC 11 query (when we have a terminal and it's a TTY)\n\tif (terminal && typeof (terminal as unknown as { queryOsc?: unknown }).queryOsc === \"function\") {\n\t\ttry {\n\t\t\tconst response = await (\n\t\t\t\tterminal as unknown as {\n\t\t\t\t\tqueryOsc: (seq: string, prefix: string, ms: number) => Promise<string | null>;\n\t\t\t\t}\n\t\t\t).queryOsc(\"\\x1b]11;?\\x07\", \"\\x1b]11;\", Math.min(timeoutMs, 150));\n\t\t\tif (response) {\n\t\t\t\tconst rgb = parseOsc11Response(response);\n\t\t\t\tif (rgb) {\n\t\t\t\t\tconst lum = relativeLuminance(rgb.r, rgb.g, rgb.b);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tr: rgb.r,\n\t\t\t\t\t\tg: rgb.g,\n\t\t\t\t\t\tb: rgb.b,\n\t\t\t\t\t\tluminance: lum,\n\t\t\t\t\t\tclassification: classifyLuminance(lum),\n\t\t\t\t\t\tsource: \"osc11\",\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Fall through to COLORFGBG.\n\t\t}\n\t}\n\n\t// 3. COLORFGBG parse\n\tconst fgbg = parseColorFgBg(env.COLORFGBG);\n\tif (fgbg) {\n\t\treturn {\n\t\t\tr: fgbg === \"dark\" ? 0 : 255,\n\t\t\tg: fgbg === \"dark\" ? 0 : 255,\n\t\t\tb: fgbg === \"dark\" ? 0 : 255,\n\t\t\tluminance: fgbg === \"dark\" ? 0 : 1,\n\t\t\tclassification: fgbg,\n\t\t\tsource: \"colorfgbg\",\n\t\t};\n\t}\n\n\treturn null;\n}\n\n/**\n * Standalone OSC 11 query that works before the TUI has started.\n *\n * Briefly installs its own stdin listener in raw mode, writes the OSC 11\n * query, waits up to `timeoutMs` for the response, then restores stdin.\n * Returns null on timeout / non-TTY / parse failure.\n *\n * Use this during startup when we need the background classification before\n * theme init but haven't yet constructed a ProcessTerminal.\n */\nexport function queryOsc11Standalone(timeoutMs = 150): Promise<TerminalBackground | null> {\n\treturn new Promise((resolve) => {\n\t\tif (!process.stdout.isTTY || !process.stdin.isTTY) {\n\t\t\tresolve(null);\n\t\t\treturn;\n\t\t}\n\n\t\tconst wasRaw = process.stdin.isRaw;\n\t\tlet settled = false;\n\t\tlet buffer = \"\";\n\t\tlet timer: NodeJS.Timeout | undefined;\n\n\t\tconst cleanup = () => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\ttry {\n\t\t\t\tprocess.stdin.removeListener(\"data\", onData);\n\t\t\t\tif (process.stdin.setRawMode && !wasRaw) {\n\t\t\t\t\tprocess.stdin.setRawMode(false);\n\t\t\t\t}\n\t\t\t\tprocess.stdin.pause();\n\t\t\t} catch {\n\t\t\t\t// swallow\n\t\t\t}\n\t\t};\n\n\t\tconst onData = (chunk: Buffer | string) => {\n\t\t\tbuffer += typeof chunk === \"string\" ? chunk : chunk.toString(\"utf8\");\n\t\t\tconst rgb = parseOsc11Response(buffer);\n\t\t\tif (rgb) {\n\t\t\t\tcleanup();\n\t\t\t\tconst lum = relativeLuminance(rgb.r, rgb.g, rgb.b);\n\t\t\t\tresolve({\n\t\t\t\t\tr: rgb.r,\n\t\t\t\t\tg: rgb.g,\n\t\t\t\t\tb: rgb.b,\n\t\t\t\t\tluminance: lum,\n\t\t\t\t\tclassification: classifyLuminance(lum),\n\t\t\t\t\tsource: \"osc11\",\n\t\t\t\t});\n\t\t\t}\n\t\t};\n\n\t\ttry {\n\t\t\tif (process.stdin.setRawMode) process.stdin.setRawMode(true);\n\t\t\tprocess.stdin.resume();\n\t\t\tprocess.stdin.on(\"data\", onData);\n\t\t\tprocess.stdout.write(\"\\x1b]11;?\\x07\");\n\t\t} catch {\n\t\t\tcleanup();\n\t\t\tresolve(null);\n\t\t\treturn;\n\t\t}\n\n\t\ttimer = setTimeout(() => {\n\t\t\tcleanup();\n\t\t\tresolve(null);\n\t\t}, timeoutMs);\n\t});\n}\n\n/**\n * One-call probe: identity + background + final classification (with \"dark\" fallback).\n * The classification field is always concrete per R1.\n */\nexport async function probeTerminal(options: {\n\tterminal?: ProcessTerminal | null;\n\ttimeoutMs?: number;\n\tenv?: NodeJS.ProcessEnv;\n\t/** Whether to run the standalone OSC 11 query (needs raw stdin). Default: true when no terminal is supplied. */\n\tuseStandaloneOsc?: boolean;\n}): Promise<ProbeResult> {\n\tconst env = options.env ?? process.env;\n\tconst identity = detectTerminalIdentity(env);\n\tconst timeoutMs = options.timeoutMs ?? 200;\n\tconst useStandalone = options.useStandaloneOsc ?? !options.terminal;\n\n\t// Override first (R1 AC7)\n\tconst override = parseOverride(env.CAVE_TERM_BG);\n\tif (override) {\n\t\treturn { identity, background: override, classification: override.classification };\n\t}\n\n\t// Pre-TUI OSC 11 (R1 AC1+2)\n\tif (useStandalone) {\n\t\tconst osc = await queryOsc11Standalone(Math.min(timeoutMs, 150));\n\t\tif (osc) {\n\t\t\treturn { identity, background: osc, classification: osc.classification };\n\t\t}\n\t} else if (options.terminal) {\n\t\tconst bg = await queryTerminalBackground(options.terminal, timeoutMs, env);\n\t\tif (bg) return { identity, background: bg, classification: bg.classification };\n\t}\n\n\t// COLORFGBG (R1 AC3)\n\tconst fgbg = parseColorFgBg(env.COLORFGBG);\n\tif (fgbg) {\n\t\treturn {\n\t\t\tidentity,\n\t\t\tbackground: {\n\t\t\t\tr: fgbg === \"dark\" ? 0 : 255,\n\t\t\t\tg: fgbg === \"dark\" ? 0 : 255,\n\t\t\t\tb: fgbg === \"dark\" ? 0 : 255,\n\t\t\t\tluminance: fgbg === \"dark\" ? 0 : 1,\n\t\t\t\tclassification: fgbg,\n\t\t\t\tsource: \"colorfgbg\",\n\t\t\t},\n\t\t\tclassification: fgbg,\n\t\t};\n\t}\n\n\t// Fallback (R1 AC4)\n\treturn { identity, background: null, classification: \"dark\" };\n}\n"]}