@greppleai/grip-tui 0.0.0-stage → 0.0.1

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 (158) hide show
  1. package/README.md +818 -2
  2. package/dist/autocomplete.d.ts +48 -0
  3. package/dist/autocomplete.d.ts.map +1 -0
  4. package/dist/autocomplete.js +566 -0
  5. package/dist/autocomplete.js.map +1 -0
  6. package/dist/components/alt-screen-flash.d.ts +13 -0
  7. package/dist/components/alt-screen-flash.d.ts.map +1 -0
  8. package/dist/components/alt-screen-flash.js +37 -0
  9. package/dist/components/alt-screen-flash.js.map +1 -0
  10. package/dist/components/box.d.ts +22 -0
  11. package/dist/components/box.d.ts.map +1 -0
  12. package/dist/components/box.js +104 -0
  13. package/dist/components/box.js.map +1 -0
  14. package/dist/components/cancellable-loader.d.ts +22 -0
  15. package/dist/components/cancellable-loader.d.ts.map +1 -0
  16. package/dist/components/cancellable-loader.js +35 -0
  17. package/dist/components/cancellable-loader.js.map +1 -0
  18. package/dist/components/editor.d.ts +292 -0
  19. package/dist/components/editor.d.ts.map +1 -0
  20. package/dist/components/editor.js +1887 -0
  21. package/dist/components/editor.js.map +1 -0
  22. package/dist/components/h-stack.d.ts +7 -0
  23. package/dist/components/h-stack.d.ts.map +1 -0
  24. package/dist/components/h-stack.js +43 -0
  25. package/dist/components/h-stack.js.map +1 -0
  26. package/dist/components/image.d.ts +32 -0
  27. package/dist/components/image.d.ts.map +1 -0
  28. package/dist/components/image.js +76 -0
  29. package/dist/components/image.js.map +1 -0
  30. package/dist/components/input.d.ts +46 -0
  31. package/dist/components/input.d.ts.map +1 -0
  32. package/dist/components/input.js +352 -0
  33. package/dist/components/input.js.map +1 -0
  34. package/dist/components/loader.d.ts +32 -0
  35. package/dist/components/loader.d.ts.map +1 -0
  36. package/dist/components/loader.js +80 -0
  37. package/dist/components/loader.js.map +1 -0
  38. package/dist/components/markdown.d.ts +132 -0
  39. package/dist/components/markdown.d.ts.map +1 -0
  40. package/dist/components/markdown.js +717 -0
  41. package/dist/components/markdown.js.map +1 -0
  42. package/dist/components/scroll-view.d.ts +53 -0
  43. package/dist/components/scroll-view.d.ts.map +1 -0
  44. package/dist/components/scroll-view.js +173 -0
  45. package/dist/components/scroll-view.js.map +1 -0
  46. package/dist/components/select-list.d.ts +50 -0
  47. package/dist/components/select-list.d.ts.map +1 -0
  48. package/dist/components/select-list.js +159 -0
  49. package/dist/components/select-list.js.map +1 -0
  50. package/dist/components/settings-list.d.ts +52 -0
  51. package/dist/components/settings-list.d.ts.map +1 -0
  52. package/dist/components/settings-list.js +187 -0
  53. package/dist/components/settings-list.js.map +1 -0
  54. package/dist/components/spacer.d.ts +12 -0
  55. package/dist/components/spacer.d.ts.map +1 -0
  56. package/dist/components/spacer.js +23 -0
  57. package/dist/components/spacer.js.map +1 -0
  58. package/dist/components/stack.d.ts +32 -0
  59. package/dist/components/stack.d.ts.map +1 -0
  60. package/dist/components/stack.js +122 -0
  61. package/dist/components/stack.js.map +1 -0
  62. package/dist/components/text.d.ts +19 -0
  63. package/dist/components/text.d.ts.map +1 -0
  64. package/dist/components/text.js +89 -0
  65. package/dist/components/text.js.map +1 -0
  66. package/dist/components/truncated-text.d.ts +13 -0
  67. package/dist/components/truncated-text.d.ts.map +1 -0
  68. package/dist/components/truncated-text.js +51 -0
  69. package/dist/components/truncated-text.js.map +1 -0
  70. package/dist/components/v-stack.d.ts +8 -0
  71. package/dist/components/v-stack.d.ts.map +1 -0
  72. package/dist/components/v-stack.js +27 -0
  73. package/dist/components/v-stack.js.map +1 -0
  74. package/dist/editor-component.d.ts +39 -0
  75. package/dist/editor-component.d.ts.map +1 -0
  76. package/dist/editor-component.js +2 -0
  77. package/dist/editor-component.js.map +1 -0
  78. package/dist/fuzzy.d.ts +16 -0
  79. package/dist/fuzzy.d.ts.map +1 -0
  80. package/dist/fuzzy.js +101 -0
  81. package/dist/fuzzy.js.map +1 -0
  82. package/dist/index.d.ts +30 -0
  83. package/dist/index.d.ts.map +1 -0
  84. package/dist/index.js +41 -0
  85. package/dist/index.js.map +1 -0
  86. package/dist/keybindings.d.ts +243 -0
  87. package/dist/keybindings.d.ts.map +1 -0
  88. package/dist/keybindings.js +209 -0
  89. package/dist/keybindings.js.map +1 -0
  90. package/dist/keys.d.ts +184 -0
  91. package/dist/keys.d.ts.map +1 -0
  92. package/dist/keys.js +1053 -0
  93. package/dist/keys.js.map +1 -0
  94. package/dist/kill-ring.d.ts +30 -0
  95. package/dist/kill-ring.d.ts.map +1 -0
  96. package/dist/kill-ring.js +37 -0
  97. package/dist/kill-ring.js.map +1 -0
  98. package/dist/latex.d.ts +10 -0
  99. package/dist/latex.d.ts.map +1 -0
  100. package/dist/latex.js +1406 -0
  101. package/dist/latex.js.map +1 -0
  102. package/dist/layout-node.d.ts +43 -0
  103. package/dist/layout-node.d.ts.map +1 -0
  104. package/dist/layout-node.js +6 -0
  105. package/dist/layout-node.js.map +1 -0
  106. package/dist/layout.d.ts +40 -0
  107. package/dist/layout.d.ts.map +1 -0
  108. package/dist/layout.js +386 -0
  109. package/dist/layout.js.map +1 -0
  110. package/dist/native-modifiers.d.ts +3 -0
  111. package/dist/native-modifiers.d.ts.map +1 -0
  112. package/dist/native-modifiers.js +53 -0
  113. package/dist/native-modifiers.js.map +1 -0
  114. package/dist/stdin-buffer.d.ts +58 -0
  115. package/dist/stdin-buffer.d.ts.map +1 -0
  116. package/dist/stdin-buffer.js +318 -0
  117. package/dist/stdin-buffer.js.map +1 -0
  118. package/dist/terminal-control.d.ts +5 -0
  119. package/dist/terminal-control.d.ts.map +1 -0
  120. package/dist/terminal-control.js +26 -0
  121. package/dist/terminal-control.js.map +1 -0
  122. package/dist/terminal-image.d.ts +112 -0
  123. package/dist/terminal-image.d.ts.map +1 -0
  124. package/dist/terminal-image.js +508 -0
  125. package/dist/terminal-image.js.map +1 -0
  126. package/dist/terminal.d.ts +117 -0
  127. package/dist/terminal.d.ts.map +1 -0
  128. package/dist/terminal.js +490 -0
  129. package/dist/terminal.js.map +1 -0
  130. package/dist/tui-alt-screen.d.ts +114 -0
  131. package/dist/tui-alt-screen.d.ts.map +1 -0
  132. package/dist/tui-alt-screen.js +950 -0
  133. package/dist/tui-alt-screen.js.map +1 -0
  134. package/dist/tui-main-screen.d.ts +52 -0
  135. package/dist/tui-main-screen.d.ts.map +1 -0
  136. package/dist/tui-main-screen.js +511 -0
  137. package/dist/tui-main-screen.js.map +1 -0
  138. package/dist/tui.d.ts +295 -0
  139. package/dist/tui.d.ts.map +1 -0
  140. package/dist/tui.js +868 -0
  141. package/dist/tui.js.map +1 -0
  142. package/dist/undo-stack.d.ts +17 -0
  143. package/dist/undo-stack.d.ts.map +1 -0
  144. package/dist/undo-stack.js +25 -0
  145. package/dist/undo-stack.js.map +1 -0
  146. package/dist/utils.d.ts +96 -0
  147. package/dist/utils.d.ts.map +1 -0
  148. package/dist/utils.js +1106 -0
  149. package/dist/utils.js.map +1 -0
  150. package/dist/word-navigation.d.ts +25 -0
  151. package/dist/word-navigation.d.ts.map +1 -0
  152. package/dist/word-navigation.js +94 -0
  153. package/dist/word-navigation.js.map +1 -0
  154. package/native/darwin/prebuilds/darwin-arm64/darwin-modifiers.node +0 -0
  155. package/native/darwin/prebuilds/darwin-x64/darwin-modifiers.node +0 -0
  156. package/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  157. package/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  158. package/package.json +47 -5
@@ -0,0 +1,508 @@
1
+ import { execSync } from "node:child_process";
2
+ import { sanitizeHyperlinkText, sanitizeTerminalControlPayload } from "./terminal-control.js";
3
+ let cachedCapabilities = null;
4
+ // Default cell dimensions - updated by TUI when terminal responds to query
5
+ let cellDimensions = { widthPx: 9, heightPx: 18 };
6
+ export function getCellDimensions() {
7
+ return cellDimensions;
8
+ }
9
+ export function setCellDimensions(dims) {
10
+ cellDimensions = dims;
11
+ }
12
+ /**
13
+ * Checks whether the attached tmux client forwards OSC 8 hyperlinks to the
14
+ * outer terminal. tmux only re-emits them when its `client_termfeatures` lists
15
+ * `hyperlinks`, and strips them otherwise. On any error fallbacks `false`.
16
+ */
17
+ function probeTmuxHyperlinks() {
18
+ try {
19
+ const termfeatures = execSync("tmux display-message -p '#{client_termfeatures}'", {
20
+ encoding: "utf8",
21
+ timeout: 250,
22
+ stdio: ["ignore", "pipe", "ignore"],
23
+ });
24
+ return termfeatures
25
+ .split(",")
26
+ .map((feature) => feature.trim())
27
+ .includes("hyperlinks");
28
+ }
29
+ catch {
30
+ return false;
31
+ }
32
+ }
33
+ function detectKnownTerminalCapabilities(env, termProgram, terminalEmulator, term) {
34
+ const detections = [
35
+ {
36
+ matches: () => Boolean(env.KITTY_WINDOW_ID) || termProgram === "kitty",
37
+ capabilities: { images: "kitty", trueColor: true, hyperlinks: true },
38
+ },
39
+ {
40
+ matches: () => termProgram === "ghostty" || term.includes("ghostty") || Boolean(env.GHOSTTY_RESOURCES_DIR),
41
+ capabilities: { images: "kitty", trueColor: true, hyperlinks: true },
42
+ },
43
+ {
44
+ matches: () => termProgram === "warpterminal" || Boolean(env.WARP_SESSION_ID) || Boolean(env.WARP_TERMINAL_SESSION_UUID),
45
+ capabilities: { images: "kitty", trueColor: true, hyperlinks: true },
46
+ },
47
+ {
48
+ matches: () => Boolean(env.WEZTERM_PANE) || termProgram === "wezterm",
49
+ capabilities: { images: "kitty", trueColor: true, hyperlinks: true },
50
+ },
51
+ {
52
+ matches: () => Boolean(env.ITERM_SESSION_ID) || termProgram === "iterm.app",
53
+ capabilities: { images: "iterm2", trueColor: true, hyperlinks: true },
54
+ },
55
+ {
56
+ matches: () => Boolean(env.WT_SESSION),
57
+ capabilities: { images: null, trueColor: true, hyperlinks: true },
58
+ },
59
+ {
60
+ matches: () => termProgram === "vscode",
61
+ capabilities: { images: null, trueColor: true, hyperlinks: true },
62
+ },
63
+ {
64
+ matches: () => termProgram === "alacritty",
65
+ capabilities: { images: null, trueColor: true, hyperlinks: true },
66
+ },
67
+ {
68
+ matches: () => terminalEmulator === "jetbrains-jediterm",
69
+ capabilities: { images: null, trueColor: true, hyperlinks: false },
70
+ },
71
+ ];
72
+ return detections.find((detection) => detection.matches())?.capabilities;
73
+ }
74
+ export function detectCapabilities(tmuxForwardsHyperlink = probeTmuxHyperlinks) {
75
+ const env = process.env;
76
+ const termProgram = env.TERM_PROGRAM?.toLowerCase() || "";
77
+ const terminalEmulator = env.TERMINAL_EMULATOR?.toLowerCase() || "";
78
+ const term = env.TERM?.toLowerCase() || "";
79
+ const colorTerm = env.COLORTERM?.toLowerCase() || "";
80
+ const hasTrueColorHint = colorTerm === "truecolor" || colorTerm === "24bit";
81
+ // Emit OSC 8 hyperlinks only when tmux confirms it forwards.
82
+ // Image protocols are unreliable under tmux, so leave `images: null`.
83
+ if (env.TMUX || term.startsWith("tmux")) {
84
+ return { images: null, trueColor: hasTrueColorHint, hyperlinks: tmuxForwardsHyperlink() };
85
+ }
86
+ // screen does not forward OSC 8 hyperlinks, so keep them off there.
87
+ if (term.startsWith("screen")) {
88
+ return { images: null, trueColor: hasTrueColorHint, hyperlinks: false };
89
+ }
90
+ const knownCapabilities = detectKnownTerminalCapabilities(env, termProgram, terminalEmulator, term);
91
+ if (knownCapabilities)
92
+ return knownCapabilities;
93
+ // Unknown terminal: be conservative. OSC 8 is rendered invisibly as "just
94
+ // text" on terminals that swallow it, which means the URL disappears from
95
+ // the rendered output. Default to the legacy `text (url)` behavior unless we
96
+ // have positively identified a hyperlink-capable terminal above.
97
+ return { images: null, trueColor: hasTrueColorHint, hyperlinks: false };
98
+ }
99
+ export function getCapabilities() {
100
+ if (!cachedCapabilities) {
101
+ cachedCapabilities = detectCapabilities();
102
+ }
103
+ return cachedCapabilities;
104
+ }
105
+ export function resetCapabilitiesCache() {
106
+ cachedCapabilities = null;
107
+ }
108
+ /** Override the cached capabilities. Useful in tests to exercise both code paths. */
109
+ export function setCapabilities(caps) {
110
+ cachedCapabilities = caps;
111
+ }
112
+ const KITTY_PREFIX = "\x1b_G";
113
+ const ITERM2_PREFIX = "\x1b]1337;File=";
114
+ export function isImageLine(line) {
115
+ // Fast path: sequence at line start (single-row images)
116
+ if (line.startsWith(KITTY_PREFIX) || line.startsWith(ITERM2_PREFIX)) {
117
+ return true;
118
+ }
119
+ // Slow path: sequence elsewhere (multi-row images have cursor-up prefix)
120
+ return line.includes(KITTY_PREFIX) || line.includes(ITERM2_PREFIX);
121
+ }
122
+ /**
123
+ * Generate a random image ID for Kitty graphics protocol.
124
+ * Uses random IDs to avoid collisions between different module instances
125
+ * (e.g., main app vs extensions).
126
+ */
127
+ export function allocateImageId() {
128
+ // Use random ID in range [1, 0xffffffff] to avoid collisions
129
+ return Math.floor(Math.random() * 0xfffffffe) + 1;
130
+ }
131
+ export function encodeKitty(base64Data, options = {}) {
132
+ const sanitizedData = sanitizeTerminalControlPayload(base64Data);
133
+ const CHUNK_SIZE = 4096;
134
+ const params = ["a=T", "f=100", "q=2"];
135
+ if (options.moveCursor === false)
136
+ params.push("C=1");
137
+ if (options.columns)
138
+ params.push(`c=${options.columns}`);
139
+ if (options.rows)
140
+ params.push(`r=${options.rows}`);
141
+ if (options.imageId)
142
+ params.push(`i=${options.imageId}`);
143
+ if (sanitizedData.length <= CHUNK_SIZE) {
144
+ return `\x1b_G${params.join(",")};${sanitizedData}\x1b\\`;
145
+ }
146
+ const chunks = [];
147
+ let offset = 0;
148
+ let isFirst = true;
149
+ while (offset < sanitizedData.length) {
150
+ const chunk = sanitizedData.slice(offset, offset + CHUNK_SIZE);
151
+ const isLast = offset + CHUNK_SIZE >= sanitizedData.length;
152
+ if (isFirst) {
153
+ chunks.push(`\x1b_G${params.join(",")},m=1;${chunk}\x1b\\`);
154
+ isFirst = false;
155
+ }
156
+ else if (isLast) {
157
+ chunks.push(`\x1b_Gm=0;${chunk}\x1b\\`);
158
+ }
159
+ else {
160
+ chunks.push(`\x1b_Gm=1;${chunk}\x1b\\`);
161
+ }
162
+ offset += CHUNK_SIZE;
163
+ }
164
+ return chunks.join("");
165
+ }
166
+ /**
167
+ * Delete a Kitty graphics image by ID.
168
+ * Uses uppercase 'I' to also free the image data.
169
+ */
170
+ export function deleteKittyImage(imageId) {
171
+ return `\x1b_Ga=d,d=I,i=${imageId},q=2\x1b\\`;
172
+ }
173
+ /**
174
+ * Delete all visible Kitty graphics images.
175
+ * Uses uppercase 'A' to also free the image data.
176
+ */
177
+ export function deleteAllKittyImages() {
178
+ return "\x1b_Ga=d,d=A,q=2\x1b\\";
179
+ }
180
+ /** Delete all visible Kitty placements while retaining their uploaded image data. */
181
+ export function deleteAllKittyPlacements() {
182
+ return "\x1b_Ga=d,d=a,q=2\x1b\\";
183
+ }
184
+ export function encodeITerm2(base64Data, options = {}) {
185
+ const sanitizedData = sanitizeTerminalControlPayload(base64Data);
186
+ const params = [
187
+ `inline=${options.inline !== false ? 1 : 0}`,
188
+ `size=${Buffer.byteLength(sanitizedData, "base64")}`,
189
+ ];
190
+ if (options.width !== undefined)
191
+ params.push(`width=${sanitizeTerminalControlPayload(String(options.width))}`);
192
+ if (options.height !== undefined)
193
+ params.push(`height=${sanitizeTerminalControlPayload(String(options.height))}`);
194
+ if (options.name) {
195
+ const nameBase64 = Buffer.from(options.name).toString("base64");
196
+ params.push(`name=${nameBase64}`);
197
+ }
198
+ if (options.preserveAspectRatio === false) {
199
+ params.push("preserveAspectRatio=0");
200
+ }
201
+ return `\x1b]1337;File=${params.join(";")}:${sanitizedData}\x07`;
202
+ }
203
+ const kittyImageMetadata = new Map();
204
+ let kittyTransmissionGeneration = 0;
205
+ export function registerKittyImageMetadata(metadata) {
206
+ kittyTransmissionGeneration += 1;
207
+ kittyImageMetadata.delete(metadata.imageId);
208
+ kittyImageMetadata.set(metadata.imageId, { ...metadata, transmissionGeneration: kittyTransmissionGeneration });
209
+ if (kittyImageMetadata.size > 1000) {
210
+ const oldestImageId = kittyImageMetadata.keys().next().value;
211
+ if (oldestImageId !== undefined)
212
+ kittyImageMetadata.delete(oldestImageId);
213
+ }
214
+ }
215
+ function getRegisteredKittyImageMetadata(line) {
216
+ const controls = /\x1b_G([^;]*);/.exec(line)?.[1];
217
+ if (!controls)
218
+ return undefined;
219
+ const imageId = /(?:^|,)i=(\d+)(?:,|$)/.exec(controls)?.[1];
220
+ return imageId === undefined ? undefined : kittyImageMetadata.get(Number.parseInt(imageId, 10));
221
+ }
222
+ export function getKittyImageMetadata(line) {
223
+ const metadata = getRegisteredKittyImageMetadata(line);
224
+ if (!metadata)
225
+ return undefined;
226
+ return {
227
+ imageId: metadata.imageId,
228
+ columns: metadata.columns,
229
+ rows: metadata.rows,
230
+ widthPx: metadata.widthPx,
231
+ heightPx: metadata.heightPx,
232
+ };
233
+ }
234
+ const KITTY_PLACEMENT_CONTROL_KEYS = new Set([
235
+ "i",
236
+ "p",
237
+ "x",
238
+ "y",
239
+ "w",
240
+ "h",
241
+ "X",
242
+ "Y",
243
+ "c",
244
+ "r",
245
+ "C",
246
+ "U",
247
+ "z",
248
+ "P",
249
+ "Q",
250
+ "H",
251
+ "V",
252
+ ]);
253
+ /** Build a placement-only command for an image line emitted by {@link renderImage}. */
254
+ export function getKittyImagePlacement(line) {
255
+ const match = /\x1b_G([^;]*);/.exec(line);
256
+ const metadata = getRegisteredKittyImageMetadata(line);
257
+ if (!match || !metadata)
258
+ return undefined;
259
+ let commandStart = match.index;
260
+ let commandControls = match[1];
261
+ let transmissionEnd;
262
+ while (true) {
263
+ const terminator = line.indexOf("\x1b\\", commandStart + KITTY_PREFIX.length);
264
+ if (terminator === -1)
265
+ return undefined;
266
+ transmissionEnd = terminator + 2;
267
+ if (!/(?:^|,)m=1(?:,|$)/.test(commandControls))
268
+ break;
269
+ commandStart = transmissionEnd;
270
+ if (!line.startsWith(KITTY_PREFIX, commandStart))
271
+ return undefined;
272
+ const controlsEnd = line.indexOf(";", commandStart + KITTY_PREFIX.length);
273
+ if (controlsEnd === -1)
274
+ return undefined;
275
+ commandControls = line.slice(commandStart + KITTY_PREFIX.length, controlsEnd);
276
+ }
277
+ const controls = match[1]
278
+ .split(",")
279
+ .filter((control) => KITTY_PLACEMENT_CONTROL_KEYS.has(control.split("=", 1)[0] ?? ""));
280
+ const sequence = `\x1b_Ga=p,q=2,${controls.join(",")}\x1b\\`;
281
+ return {
282
+ imageId: metadata.imageId,
283
+ transmissionGeneration: metadata.transmissionGeneration,
284
+ transmissionBytes: transmissionEnd - match.index,
285
+ estimatedDecodedBytes: metadata.widthPx * metadata.heightPx * 4,
286
+ sequence,
287
+ replacementLine: `${line.slice(0, match.index)}${sequence}${line.slice(transmissionEnd)}`,
288
+ };
289
+ }
290
+ export function cropKittyImageLine(line, hiddenRows, visibleRows) {
291
+ const metadata = getKittyImageMetadata(line);
292
+ const match = /\x1b_G([^;]*);/.exec(line);
293
+ if (!metadata || !match || hiddenRows < 0 || hiddenRows >= metadata.rows || visibleRows <= 0)
294
+ return line;
295
+ const croppedRows = Math.min(visibleRows, metadata.rows - hiddenRows);
296
+ if (hiddenRows === 0 && croppedRows === metadata.rows)
297
+ return line;
298
+ const sourceY = Math.floor((metadata.heightPx * hiddenRows) / metadata.rows);
299
+ const sourceEnd = Math.ceil((metadata.heightPx * (hiddenRows + croppedRows)) / metadata.rows);
300
+ const sourceHeight = Math.max(1, Math.min(metadata.heightPx, sourceEnd) - sourceY);
301
+ const controls = match[1].split(",").filter((control) => !/^[yhr]=/.test(control));
302
+ controls.push(`y=${sourceY}`, `h=${sourceHeight}`, `r=${croppedRows}`);
303
+ return `${line.slice(0, match.index)}\x1b_G${controls.join(",")};${line.slice(match.index + match[0].length)}`;
304
+ }
305
+ export function calculateImageCellSize(imageDimensions, maxWidthCells, maxHeightCells, cellDimensions = { widthPx: 9, heightPx: 18 }) {
306
+ const maxWidth = Math.max(1, Math.floor(maxWidthCells));
307
+ const maxHeight = maxHeightCells === undefined ? undefined : Math.max(1, Math.floor(maxHeightCells));
308
+ const imageWidth = Math.max(1, imageDimensions.widthPx);
309
+ const imageHeight = Math.max(1, imageDimensions.heightPx);
310
+ const widthScale = (maxWidth * cellDimensions.widthPx) / imageWidth;
311
+ const heightScale = maxHeight === undefined ? widthScale : (maxHeight * cellDimensions.heightPx) / imageHeight;
312
+ const scale = Math.min(widthScale, heightScale);
313
+ const scaledWidthPx = imageWidth * scale;
314
+ const scaledHeightPx = imageHeight * scale;
315
+ const columns = Math.ceil(scaledWidthPx / cellDimensions.widthPx);
316
+ const rows = Math.ceil(scaledHeightPx / cellDimensions.heightPx);
317
+ return {
318
+ columns: Math.max(1, Math.min(maxWidth, columns)),
319
+ rows: Math.max(1, maxHeight === undefined ? rows : Math.min(maxHeight, rows)),
320
+ };
321
+ }
322
+ export function calculateImageRows(imageDimensions, targetWidthCells, cellDimensions = { widthPx: 9, heightPx: 18 }) {
323
+ return calculateImageCellSize(imageDimensions, targetWidthCells, undefined, cellDimensions).rows;
324
+ }
325
+ export function getPngDimensions(base64Data) {
326
+ try {
327
+ const buffer = Buffer.from(base64Data, "base64");
328
+ if (buffer.length < 24) {
329
+ return null;
330
+ }
331
+ if (buffer[0] !== 0x89 || buffer[1] !== 0x50 || buffer[2] !== 0x4e || buffer[3] !== 0x47) {
332
+ return null;
333
+ }
334
+ const width = buffer.readUInt32BE(16);
335
+ const height = buffer.readUInt32BE(20);
336
+ return { widthPx: width, heightPx: height };
337
+ }
338
+ catch {
339
+ return null;
340
+ }
341
+ }
342
+ export function getJpegDimensions(base64Data) {
343
+ try {
344
+ const buffer = Buffer.from(base64Data, "base64");
345
+ if (buffer.length < 2) {
346
+ return null;
347
+ }
348
+ if (buffer[0] !== 0xff || buffer[1] !== 0xd8) {
349
+ return null;
350
+ }
351
+ let offset = 2;
352
+ while (offset < buffer.length - 9) {
353
+ if (buffer[offset] !== 0xff) {
354
+ offset++;
355
+ continue;
356
+ }
357
+ const marker = buffer[offset + 1];
358
+ if (marker >= 0xc0 && marker <= 0xc2) {
359
+ const height = buffer.readUInt16BE(offset + 5);
360
+ const width = buffer.readUInt16BE(offset + 7);
361
+ return { widthPx: width, heightPx: height };
362
+ }
363
+ if (offset + 3 >= buffer.length) {
364
+ return null;
365
+ }
366
+ const length = buffer.readUInt16BE(offset + 2);
367
+ if (length < 2) {
368
+ return null;
369
+ }
370
+ offset += 2 + length;
371
+ }
372
+ return null;
373
+ }
374
+ catch {
375
+ return null;
376
+ }
377
+ }
378
+ export function getGifDimensions(base64Data) {
379
+ try {
380
+ const buffer = Buffer.from(base64Data, "base64");
381
+ if (buffer.length < 10) {
382
+ return null;
383
+ }
384
+ const sig = buffer.slice(0, 6).toString("ascii");
385
+ if (sig !== "GIF87a" && sig !== "GIF89a") {
386
+ return null;
387
+ }
388
+ const width = buffer.readUInt16LE(6);
389
+ const height = buffer.readUInt16LE(8);
390
+ return { widthPx: width, heightPx: height };
391
+ }
392
+ catch {
393
+ return null;
394
+ }
395
+ }
396
+ export function getWebpDimensions(base64Data) {
397
+ try {
398
+ const buffer = Buffer.from(base64Data, "base64");
399
+ if (buffer.length < 30) {
400
+ return null;
401
+ }
402
+ const riff = buffer.slice(0, 4).toString("ascii");
403
+ const webp = buffer.slice(8, 12).toString("ascii");
404
+ if (riff !== "RIFF" || webp !== "WEBP") {
405
+ return null;
406
+ }
407
+ const chunk = buffer.slice(12, 16).toString("ascii");
408
+ if (chunk === "VP8 ") {
409
+ if (buffer.length < 30)
410
+ return null;
411
+ const width = buffer.readUInt16LE(26) & 0x3fff;
412
+ const height = buffer.readUInt16LE(28) & 0x3fff;
413
+ return { widthPx: width, heightPx: height };
414
+ }
415
+ else if (chunk === "VP8L") {
416
+ if (buffer.length < 25)
417
+ return null;
418
+ const bits = buffer.readUInt32LE(21);
419
+ const width = (bits & 0x3fff) + 1;
420
+ const height = ((bits >> 14) & 0x3fff) + 1;
421
+ return { widthPx: width, heightPx: height };
422
+ }
423
+ else if (chunk === "VP8X") {
424
+ if (buffer.length < 30)
425
+ return null;
426
+ const width = (buffer[24] | (buffer[25] << 8) | (buffer[26] << 16)) + 1;
427
+ const height = (buffer[27] | (buffer[28] << 8) | (buffer[29] << 16)) + 1;
428
+ return { widthPx: width, heightPx: height };
429
+ }
430
+ return null;
431
+ }
432
+ catch {
433
+ return null;
434
+ }
435
+ }
436
+ export function getImageDimensions(base64Data, mimeType) {
437
+ if (mimeType === "image/png") {
438
+ return getPngDimensions(base64Data);
439
+ }
440
+ if (mimeType === "image/jpeg") {
441
+ return getJpegDimensions(base64Data);
442
+ }
443
+ if (mimeType === "image/gif") {
444
+ return getGifDimensions(base64Data);
445
+ }
446
+ if (mimeType === "image/webp") {
447
+ return getWebpDimensions(base64Data);
448
+ }
449
+ return null;
450
+ }
451
+ export function renderImage(base64Data, imageDimensions, options = {}) {
452
+ const caps = getCapabilities();
453
+ if (!caps.images) {
454
+ return null;
455
+ }
456
+ const maxWidth = options.maxWidthCells ?? 80;
457
+ const size = calculateImageCellSize(imageDimensions, maxWidth, options.maxHeightCells, getCellDimensions());
458
+ if (caps.images === "kitty") {
459
+ if (options.imageId !== undefined) {
460
+ registerKittyImageMetadata({
461
+ imageId: options.imageId,
462
+ columns: size.columns,
463
+ rows: size.rows,
464
+ widthPx: imageDimensions.widthPx,
465
+ heightPx: imageDimensions.heightPx,
466
+ });
467
+ }
468
+ const sequence = encodeKitty(base64Data, {
469
+ columns: size.columns,
470
+ rows: size.rows,
471
+ imageId: options.imageId,
472
+ moveCursor: options.moveCursor,
473
+ });
474
+ return { sequence, columns: size.columns, rows: size.rows, imageId: options.imageId };
475
+ }
476
+ if (caps.images === "iterm2") {
477
+ const sequence = encodeITerm2(base64Data, {
478
+ width: size.columns,
479
+ height: "auto",
480
+ preserveAspectRatio: options.preserveAspectRatio ?? true,
481
+ });
482
+ return { sequence, columns: size.columns, rows: size.rows };
483
+ }
484
+ return null;
485
+ }
486
+ /**
487
+ * Wrap text in an OSC 8 hyperlink sequence.
488
+ * The text is rendered as a clickable hyperlink in terminals that support OSC 8
489
+ * (Ghostty, Kitty, WezTerm, iTerm2, VSCode, and others).
490
+ * In terminals that do not support OSC 8, the escape sequences are ignored
491
+ * and only the plain text is displayed.
492
+ *
493
+ * @param text - The visible text to display
494
+ * @param url - The URL to link to
495
+ */
496
+ export function hyperlink(text, url) {
497
+ return `\x1b]8;;${sanitizeTerminalControlPayload(url)}\x1b\\${sanitizeHyperlinkText(text)}\x1b]8;;\x1b\\`;
498
+ }
499
+ export function imageFallback(mimeType, dimensions, filename) {
500
+ const parts = [];
501
+ if (filename)
502
+ parts.push(filename);
503
+ parts.push(`[${mimeType}]`);
504
+ if (dimensions)
505
+ parts.push(`${dimensions.widthPx}x${dimensions.heightPx}`);
506
+ return `[Image: ${parts.join(" ")}]`;
507
+ }
508
+ //# sourceMappingURL=terminal-image.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal-image.js","sourceRoot":"","sources":["../src/terminal-image.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync } from \"node:child_process\";\nimport { sanitizeHyperlinkText, sanitizeTerminalControlPayload } from \"./terminal-control.ts\";\n\nexport type ImageProtocol = \"kitty\" | \"iterm2\" | null;\n\nexport interface TerminalCapabilities {\n\timages: ImageProtocol;\n\ttrueColor: boolean;\n\thyperlinks: boolean;\n}\n\nexport interface CellDimensions {\n\twidthPx: number;\n\theightPx: number;\n}\n\n// grip-ignore noNearIdenticalDataStructures: Bitmap dimensions and terminal-cell pixel metrics come from separate measurement sources and serve distinct rendering APIs.\nexport interface ImageDimensions {\n\twidthPx: number;\n\theightPx: number;\n}\n\nexport interface ImageRenderOptions {\n\tmaxWidthCells?: number;\n\tmaxHeightCells?: number;\n\tpreserveAspectRatio?: boolean;\n\t/** Kitty image ID. If provided, reuses/replaces existing image with this ID. */\n\timageId?: number;\n\t/** Whether Kitty should apply its default cursor movement after placement. */\n\tmoveCursor?: boolean;\n}\n\nexport interface KittyImageEncodeOptions {\n\tcolumns?: number;\n\trows?: number;\n\timageId?: number;\n\t/** Whether Kitty should apply its default cursor movement after placement. Default: true. */\n\tmoveCursor?: boolean;\n}\n\nexport interface ITerm2ImageEncodeOptions {\n\twidth?: number | string;\n\theight?: number | string;\n\tname?: string;\n\tpreserveAspectRatio?: boolean;\n\tinline?: boolean;\n}\nlet cachedCapabilities: TerminalCapabilities | null = null;\n\n// Default cell dimensions - updated by TUI when terminal responds to query\nlet cellDimensions: CellDimensions = { widthPx: 9, heightPx: 18 };\n\nexport function getCellDimensions(): CellDimensions {\n\treturn cellDimensions;\n}\n\nexport function setCellDimensions(dims: CellDimensions): void {\n\tcellDimensions = dims;\n}\n\n/**\n * Checks whether the attached tmux client forwards OSC 8 hyperlinks to the\n * outer terminal. tmux only re-emits them when its `client_termfeatures` lists\n * `hyperlinks`, and strips them otherwise. On any error fallbacks `false`.\n */\nfunction probeTmuxHyperlinks(): boolean {\n\ttry {\n\t\tconst termfeatures = execSync(\"tmux display-message -p '#{client_termfeatures}'\", {\n\t\t\tencoding: \"utf8\",\n\t\t\ttimeout: 250,\n\t\t\tstdio: [\"ignore\", \"pipe\", \"ignore\"],\n\t\t});\n\t\treturn termfeatures\n\t\t\t.split(\",\")\n\t\t\t.map((feature) => feature.trim())\n\t\t\t.includes(\"hyperlinks\");\n\t} catch {\n\t\treturn false;\n\t}\n}\n\ninterface TerminalCapabilityDetection {\n\tmatches: () => boolean;\n\tcapabilities: TerminalCapabilities;\n}\n\nfunction detectKnownTerminalCapabilities(\n\tenv: NodeJS.ProcessEnv,\n\ttermProgram: string,\n\tterminalEmulator: string,\n\tterm: string,\n): TerminalCapabilities | undefined {\n\tconst detections: TerminalCapabilityDetection[] = [\n\t\t{\n\t\t\tmatches: () => Boolean(env.KITTY_WINDOW_ID) || termProgram === \"kitty\",\n\t\t\tcapabilities: { images: \"kitty\", trueColor: true, hyperlinks: true },\n\t\t},\n\t\t{\n\t\t\tmatches: () => termProgram === \"ghostty\" || term.includes(\"ghostty\") || Boolean(env.GHOSTTY_RESOURCES_DIR),\n\t\t\tcapabilities: { images: \"kitty\", trueColor: true, hyperlinks: true },\n\t\t},\n\t\t{\n\t\t\tmatches: () =>\n\t\t\t\ttermProgram === \"warpterminal\" || Boolean(env.WARP_SESSION_ID) || Boolean(env.WARP_TERMINAL_SESSION_UUID),\n\t\t\tcapabilities: { images: \"kitty\", trueColor: true, hyperlinks: true },\n\t\t},\n\t\t{\n\t\t\tmatches: () => Boolean(env.WEZTERM_PANE) || termProgram === \"wezterm\",\n\t\t\tcapabilities: { images: \"kitty\", trueColor: true, hyperlinks: true },\n\t\t},\n\t\t{\n\t\t\tmatches: () => Boolean(env.ITERM_SESSION_ID) || termProgram === \"iterm.app\",\n\t\t\tcapabilities: { images: \"iterm2\", trueColor: true, hyperlinks: true },\n\t\t},\n\t\t{\n\t\t\tmatches: () => Boolean(env.WT_SESSION),\n\t\t\tcapabilities: { images: null, trueColor: true, hyperlinks: true },\n\t\t},\n\t\t{\n\t\t\tmatches: () => termProgram === \"vscode\",\n\t\t\tcapabilities: { images: null, trueColor: true, hyperlinks: true },\n\t\t},\n\t\t{\n\t\t\tmatches: () => termProgram === \"alacritty\",\n\t\t\tcapabilities: { images: null, trueColor: true, hyperlinks: true },\n\t\t},\n\t\t{\n\t\t\tmatches: () => terminalEmulator === \"jetbrains-jediterm\",\n\t\t\tcapabilities: { images: null, trueColor: true, hyperlinks: false },\n\t\t},\n\t];\n\treturn detections.find((detection) => detection.matches())?.capabilities;\n}\n\nexport function detectCapabilities(tmuxForwardsHyperlink: () => boolean = probeTmuxHyperlinks): TerminalCapabilities {\n\tconst env = process.env;\n\tconst termProgram = env.TERM_PROGRAM?.toLowerCase() || \"\";\n\tconst terminalEmulator = env.TERMINAL_EMULATOR?.toLowerCase() || \"\";\n\tconst term = env.TERM?.toLowerCase() || \"\";\n\tconst colorTerm = env.COLORTERM?.toLowerCase() || \"\";\n\tconst hasTrueColorHint = colorTerm === \"truecolor\" || colorTerm === \"24bit\";\n\n\t// Emit OSC 8 hyperlinks only when tmux confirms it forwards.\n\t// Image protocols are unreliable under tmux, so leave `images: null`.\n\tif (env.TMUX || term.startsWith(\"tmux\")) {\n\t\treturn { images: null, trueColor: hasTrueColorHint, hyperlinks: tmuxForwardsHyperlink() };\n\t}\n\n\t// screen does not forward OSC 8 hyperlinks, so keep them off there.\n\tif (term.startsWith(\"screen\")) {\n\t\treturn { images: null, trueColor: hasTrueColorHint, hyperlinks: false };\n\t}\n\n\tconst knownCapabilities = detectKnownTerminalCapabilities(env, termProgram, terminalEmulator, term);\n\tif (knownCapabilities) return knownCapabilities;\n\n\t// Unknown terminal: be conservative. OSC 8 is rendered invisibly as \"just\n\t// text\" on terminals that swallow it, which means the URL disappears from\n\t// the rendered output. Default to the legacy `text (url)` behavior unless we\n\t// have positively identified a hyperlink-capable terminal above.\n\treturn { images: null, trueColor: hasTrueColorHint, hyperlinks: false };\n}\n\nexport function getCapabilities(): TerminalCapabilities {\n\tif (!cachedCapabilities) {\n\t\tcachedCapabilities = detectCapabilities();\n\t}\n\treturn cachedCapabilities;\n}\n\nexport function resetCapabilitiesCache(): void {\n\tcachedCapabilities = null;\n}\n\n/** Override the cached capabilities. Useful in tests to exercise both code paths. */\nexport function setCapabilities(caps: TerminalCapabilities): void {\n\tcachedCapabilities = caps;\n}\n\nconst KITTY_PREFIX = \"\\x1b_G\";\nconst ITERM2_PREFIX = \"\\x1b]1337;File=\";\n\nexport function isImageLine(line: string): boolean {\n\t// Fast path: sequence at line start (single-row images)\n\tif (line.startsWith(KITTY_PREFIX) || line.startsWith(ITERM2_PREFIX)) {\n\t\treturn true;\n\t}\n\t// Slow path: sequence elsewhere (multi-row images have cursor-up prefix)\n\treturn line.includes(KITTY_PREFIX) || line.includes(ITERM2_PREFIX);\n}\n\n/**\n * Generate a random image ID for Kitty graphics protocol.\n * Uses random IDs to avoid collisions between different module instances\n * (e.g., main app vs extensions).\n */\nexport function allocateImageId(): number {\n\t// Use random ID in range [1, 0xffffffff] to avoid collisions\n\treturn Math.floor(Math.random() * 0xfffffffe) + 1;\n}\n\nexport function encodeKitty(base64Data: string, options: KittyImageEncodeOptions = {}): string {\n\tconst sanitizedData = sanitizeTerminalControlPayload(base64Data);\n\tconst CHUNK_SIZE = 4096;\n\n\tconst params: string[] = [\"a=T\", \"f=100\", \"q=2\"];\n\n\tif (options.moveCursor === false) params.push(\"C=1\");\n\tif (options.columns) params.push(`c=${options.columns}`);\n\tif (options.rows) params.push(`r=${options.rows}`);\n\tif (options.imageId) params.push(`i=${options.imageId}`);\n\n\tif (sanitizedData.length <= CHUNK_SIZE) {\n\t\treturn `\\x1b_G${params.join(\",\")};${sanitizedData}\\x1b\\\\`;\n\t}\n\n\tconst chunks: string[] = [];\n\tlet offset = 0;\n\tlet isFirst = true;\n\n\twhile (offset < sanitizedData.length) {\n\t\tconst chunk = sanitizedData.slice(offset, offset + CHUNK_SIZE);\n\t\tconst isLast = offset + CHUNK_SIZE >= sanitizedData.length;\n\n\t\tif (isFirst) {\n\t\t\tchunks.push(`\\x1b_G${params.join(\",\")},m=1;${chunk}\\x1b\\\\`);\n\t\t\tisFirst = false;\n\t\t} else if (isLast) {\n\t\t\tchunks.push(`\\x1b_Gm=0;${chunk}\\x1b\\\\`);\n\t\t} else {\n\t\t\tchunks.push(`\\x1b_Gm=1;${chunk}\\x1b\\\\`);\n\t\t}\n\n\t\toffset += CHUNK_SIZE;\n\t}\n\n\treturn chunks.join(\"\");\n}\n\n/**\n * Delete a Kitty graphics image by ID.\n * Uses uppercase 'I' to also free the image data.\n */\nexport function deleteKittyImage(imageId: number): string {\n\treturn `\\x1b_Ga=d,d=I,i=${imageId},q=2\\x1b\\\\`;\n}\n\n/**\n * Delete all visible Kitty graphics images.\n * Uses uppercase 'A' to also free the image data.\n */\nexport function deleteAllKittyImages(): string {\n\treturn \"\\x1b_Ga=d,d=A,q=2\\x1b\\\\\";\n}\n\n/** Delete all visible Kitty placements while retaining their uploaded image data. */\nexport function deleteAllKittyPlacements(): string {\n\treturn \"\\x1b_Ga=d,d=a,q=2\\x1b\\\\\";\n}\n\nexport function encodeITerm2(base64Data: string, options: ITerm2ImageEncodeOptions = {}): string {\n\tconst sanitizedData = sanitizeTerminalControlPayload(base64Data);\n\tconst params: string[] = [\n\t\t`inline=${options.inline !== false ? 1 : 0}`,\n\t\t`size=${Buffer.byteLength(sanitizedData, \"base64\")}`,\n\t];\n\n\tif (options.width !== undefined) params.push(`width=${sanitizeTerminalControlPayload(String(options.width))}`);\n\tif (options.height !== undefined) params.push(`height=${sanitizeTerminalControlPayload(String(options.height))}`);\n\tif (options.name) {\n\t\tconst nameBase64 = Buffer.from(options.name).toString(\"base64\");\n\t\tparams.push(`name=${nameBase64}`);\n\t}\n\tif (options.preserveAspectRatio === false) {\n\t\tparams.push(\"preserveAspectRatio=0\");\n\t}\n\n\treturn `\\x1b]1337;File=${params.join(\";\")}:${sanitizedData}\\x07`;\n}\n\nexport interface ImageCellSize {\n\tcolumns: number;\n\trows: number;\n}\n\nexport interface ImageRenderResult extends ImageCellSize {\n\tsequence: string;\n\timageId?: number;\n}\n\nexport interface KittyImageMetadata extends ImageCellSize {\n\timageId: number;\n\twidthPx: number;\n\theightPx: number;\n}\ninterface RegisteredKittyImageMetadata extends KittyImageMetadata {\n\ttransmissionGeneration: number;\n}\n\nexport interface KittyImagePlacement {\n\timageId: number;\n\ttransmissionGeneration: number;\n\ttransmissionBytes: number;\n\testimatedDecodedBytes: number;\n\tsequence: string;\n\treplacementLine: string;\n}\n\nconst kittyImageMetadata = new Map<number, RegisteredKittyImageMetadata>();\nlet kittyTransmissionGeneration = 0;\n\nexport function registerKittyImageMetadata(metadata: KittyImageMetadata): void {\n\tkittyTransmissionGeneration += 1;\n\tkittyImageMetadata.delete(metadata.imageId);\n\tkittyImageMetadata.set(metadata.imageId, { ...metadata, transmissionGeneration: kittyTransmissionGeneration });\n\tif (kittyImageMetadata.size > 1000) {\n\t\tconst oldestImageId = kittyImageMetadata.keys().next().value;\n\t\tif (oldestImageId !== undefined) kittyImageMetadata.delete(oldestImageId);\n\t}\n}\n\nfunction getRegisteredKittyImageMetadata(line: string): RegisteredKittyImageMetadata | undefined {\n\tconst controls = /\\x1b_G([^;]*);/.exec(line)?.[1];\n\tif (!controls) return undefined;\n\tconst imageId = /(?:^|,)i=(\\d+)(?:,|$)/.exec(controls)?.[1];\n\treturn imageId === undefined ? undefined : kittyImageMetadata.get(Number.parseInt(imageId, 10));\n}\n\nexport function getKittyImageMetadata(line: string): KittyImageMetadata | undefined {\n\tconst metadata = getRegisteredKittyImageMetadata(line);\n\tif (!metadata) return undefined;\n\treturn {\n\t\timageId: metadata.imageId,\n\t\tcolumns: metadata.columns,\n\t\trows: metadata.rows,\n\t\twidthPx: metadata.widthPx,\n\t\theightPx: metadata.heightPx,\n\t};\n}\n\nconst KITTY_PLACEMENT_CONTROL_KEYS = new Set([\n\t\"i\",\n\t\"p\",\n\t\"x\",\n\t\"y\",\n\t\"w\",\n\t\"h\",\n\t\"X\",\n\t\"Y\",\n\t\"c\",\n\t\"r\",\n\t\"C\",\n\t\"U\",\n\t\"z\",\n\t\"P\",\n\t\"Q\",\n\t\"H\",\n\t\"V\",\n]);\n\n/** Build a placement-only command for an image line emitted by {@link renderImage}. */\nexport function getKittyImagePlacement(line: string): KittyImagePlacement | undefined {\n\tconst match = /\\x1b_G([^;]*);/.exec(line);\n\tconst metadata = getRegisteredKittyImageMetadata(line);\n\tif (!match || !metadata) return undefined;\n\n\tlet commandStart = match.index;\n\tlet commandControls = match[1];\n\tlet transmissionEnd: number;\n\twhile (true) {\n\t\tconst terminator = line.indexOf(\"\\x1b\\\\\", commandStart + KITTY_PREFIX.length);\n\t\tif (terminator === -1) return undefined;\n\t\ttransmissionEnd = terminator + 2;\n\t\tif (!/(?:^|,)m=1(?:,|$)/.test(commandControls)) break;\n\t\tcommandStart = transmissionEnd;\n\t\tif (!line.startsWith(KITTY_PREFIX, commandStart)) return undefined;\n\t\tconst controlsEnd = line.indexOf(\";\", commandStart + KITTY_PREFIX.length);\n\t\tif (controlsEnd === -1) return undefined;\n\t\tcommandControls = line.slice(commandStart + KITTY_PREFIX.length, controlsEnd);\n\t}\n\n\tconst controls = match[1]\n\t\t.split(\",\")\n\t\t.filter((control) => KITTY_PLACEMENT_CONTROL_KEYS.has(control.split(\"=\", 1)[0] ?? \"\"));\n\tconst sequence = `\\x1b_Ga=p,q=2,${controls.join(\",\")}\\x1b\\\\`;\n\treturn {\n\t\timageId: metadata.imageId,\n\t\ttransmissionGeneration: metadata.transmissionGeneration,\n\t\ttransmissionBytes: transmissionEnd - match.index,\n\t\testimatedDecodedBytes: metadata.widthPx * metadata.heightPx * 4,\n\t\tsequence,\n\t\treplacementLine: `${line.slice(0, match.index)}${sequence}${line.slice(transmissionEnd)}`,\n\t};\n}\n\nexport function cropKittyImageLine(line: string, hiddenRows: number, visibleRows: number): string {\n\tconst metadata = getKittyImageMetadata(line);\n\tconst match = /\\x1b_G([^;]*);/.exec(line);\n\tif (!metadata || !match || hiddenRows < 0 || hiddenRows >= metadata.rows || visibleRows <= 0) return line;\n\tconst croppedRows = Math.min(visibleRows, metadata.rows - hiddenRows);\n\tif (hiddenRows === 0 && croppedRows === metadata.rows) return line;\n\tconst sourceY = Math.floor((metadata.heightPx * hiddenRows) / metadata.rows);\n\tconst sourceEnd = Math.ceil((metadata.heightPx * (hiddenRows + croppedRows)) / metadata.rows);\n\tconst sourceHeight = Math.max(1, Math.min(metadata.heightPx, sourceEnd) - sourceY);\n\tconst controls = match[1].split(\",\").filter((control) => !/^[yhr]=/.test(control));\n\tcontrols.push(`y=${sourceY}`, `h=${sourceHeight}`, `r=${croppedRows}`);\n\treturn `${line.slice(0, match.index)}\\x1b_G${controls.join(\",\")};${line.slice(match.index + match[0].length)}`;\n}\n\nexport function calculateImageCellSize(\n\timageDimensions: ImageDimensions,\n\tmaxWidthCells: number,\n\tmaxHeightCells?: number,\n\tcellDimensions: CellDimensions = { widthPx: 9, heightPx: 18 },\n): ImageCellSize {\n\tconst maxWidth = Math.max(1, Math.floor(maxWidthCells));\n\tconst maxHeight = maxHeightCells === undefined ? undefined : Math.max(1, Math.floor(maxHeightCells));\n\tconst imageWidth = Math.max(1, imageDimensions.widthPx);\n\tconst imageHeight = Math.max(1, imageDimensions.heightPx);\n\n\tconst widthScale = (maxWidth * cellDimensions.widthPx) / imageWidth;\n\tconst heightScale = maxHeight === undefined ? widthScale : (maxHeight * cellDimensions.heightPx) / imageHeight;\n\tconst scale = Math.min(widthScale, heightScale);\n\n\tconst scaledWidthPx = imageWidth * scale;\n\tconst scaledHeightPx = imageHeight * scale;\n\tconst columns = Math.ceil(scaledWidthPx / cellDimensions.widthPx);\n\tconst rows = Math.ceil(scaledHeightPx / cellDimensions.heightPx);\n\n\treturn {\n\t\tcolumns: Math.max(1, Math.min(maxWidth, columns)),\n\t\trows: Math.max(1, maxHeight === undefined ? rows : Math.min(maxHeight, rows)),\n\t};\n}\n\nexport function calculateImageRows(\n\timageDimensions: ImageDimensions,\n\ttargetWidthCells: number,\n\tcellDimensions: CellDimensions = { widthPx: 9, heightPx: 18 },\n): number {\n\treturn calculateImageCellSize(imageDimensions, targetWidthCells, undefined, cellDimensions).rows;\n}\n\nexport function getPngDimensions(base64Data: string): ImageDimensions | null {\n\ttry {\n\t\tconst buffer = Buffer.from(base64Data, \"base64\");\n\n\t\tif (buffer.length < 24) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (buffer[0] !== 0x89 || buffer[1] !== 0x50 || buffer[2] !== 0x4e || buffer[3] !== 0x47) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst width = buffer.readUInt32BE(16);\n\t\tconst height = buffer.readUInt32BE(20);\n\n\t\treturn { widthPx: width, heightPx: height };\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nexport function getJpegDimensions(base64Data: string): ImageDimensions | null {\n\ttry {\n\t\tconst buffer = Buffer.from(base64Data, \"base64\");\n\n\t\tif (buffer.length < 2) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (buffer[0] !== 0xff || buffer[1] !== 0xd8) {\n\t\t\treturn null;\n\t\t}\n\n\t\tlet offset = 2;\n\t\twhile (offset < buffer.length - 9) {\n\t\t\tif (buffer[offset] !== 0xff) {\n\t\t\t\toffset++;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst marker = buffer[offset + 1];\n\n\t\t\tif (marker >= 0xc0 && marker <= 0xc2) {\n\t\t\t\tconst height = buffer.readUInt16BE(offset + 5);\n\t\t\t\tconst width = buffer.readUInt16BE(offset + 7);\n\t\t\t\treturn { widthPx: width, heightPx: height };\n\t\t\t}\n\n\t\t\tif (offset + 3 >= buffer.length) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tconst length = buffer.readUInt16BE(offset + 2);\n\t\t\tif (length < 2) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\toffset += 2 + length;\n\t\t}\n\n\t\treturn null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nexport function getGifDimensions(base64Data: string): ImageDimensions | null {\n\ttry {\n\t\tconst buffer = Buffer.from(base64Data, \"base64\");\n\n\t\tif (buffer.length < 10) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst sig = buffer.slice(0, 6).toString(\"ascii\");\n\t\tif (sig !== \"GIF87a\" && sig !== \"GIF89a\") {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst width = buffer.readUInt16LE(6);\n\t\tconst height = buffer.readUInt16LE(8);\n\n\t\treturn { widthPx: width, heightPx: height };\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nexport function getWebpDimensions(base64Data: string): ImageDimensions | null {\n\ttry {\n\t\tconst buffer = Buffer.from(base64Data, \"base64\");\n\n\t\tif (buffer.length < 30) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst riff = buffer.slice(0, 4).toString(\"ascii\");\n\t\tconst webp = buffer.slice(8, 12).toString(\"ascii\");\n\t\tif (riff !== \"RIFF\" || webp !== \"WEBP\") {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst chunk = buffer.slice(12, 16).toString(\"ascii\");\n\t\tif (chunk === \"VP8 \") {\n\t\t\tif (buffer.length < 30) return null;\n\t\t\tconst width = buffer.readUInt16LE(26) & 0x3fff;\n\t\t\tconst height = buffer.readUInt16LE(28) & 0x3fff;\n\t\t\treturn { widthPx: width, heightPx: height };\n\t\t} else if (chunk === \"VP8L\") {\n\t\t\tif (buffer.length < 25) return null;\n\t\t\tconst bits = buffer.readUInt32LE(21);\n\t\t\tconst width = (bits & 0x3fff) + 1;\n\t\t\tconst height = ((bits >> 14) & 0x3fff) + 1;\n\t\t\treturn { widthPx: width, heightPx: height };\n\t\t} else if (chunk === \"VP8X\") {\n\t\t\tif (buffer.length < 30) return null;\n\t\t\tconst width = (buffer[24] | (buffer[25] << 8) | (buffer[26] << 16)) + 1;\n\t\t\tconst height = (buffer[27] | (buffer[28] << 8) | (buffer[29] << 16)) + 1;\n\t\t\treturn { widthPx: width, heightPx: height };\n\t\t}\n\n\t\treturn null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nexport function getImageDimensions(base64Data: string, mimeType: string): ImageDimensions | null {\n\tif (mimeType === \"image/png\") {\n\t\treturn getPngDimensions(base64Data);\n\t}\n\tif (mimeType === \"image/jpeg\") {\n\t\treturn getJpegDimensions(base64Data);\n\t}\n\tif (mimeType === \"image/gif\") {\n\t\treturn getGifDimensions(base64Data);\n\t}\n\tif (mimeType === \"image/webp\") {\n\t\treturn getWebpDimensions(base64Data);\n\t}\n\treturn null;\n}\n\nexport function renderImage(\n\tbase64Data: string,\n\timageDimensions: ImageDimensions,\n\toptions: ImageRenderOptions = {},\n): ImageRenderResult | null {\n\tconst caps = getCapabilities();\n\n\tif (!caps.images) {\n\t\treturn null;\n\t}\n\n\tconst maxWidth = options.maxWidthCells ?? 80;\n\tconst size = calculateImageCellSize(imageDimensions, maxWidth, options.maxHeightCells, getCellDimensions());\n\n\tif (caps.images === \"kitty\") {\n\t\tif (options.imageId !== undefined) {\n\t\t\tregisterKittyImageMetadata({\n\t\t\t\timageId: options.imageId,\n\t\t\t\tcolumns: size.columns,\n\t\t\t\trows: size.rows,\n\t\t\t\twidthPx: imageDimensions.widthPx,\n\t\t\t\theightPx: imageDimensions.heightPx,\n\t\t\t});\n\t\t}\n\t\tconst sequence = encodeKitty(base64Data, {\n\t\t\tcolumns: size.columns,\n\t\t\trows: size.rows,\n\t\t\timageId: options.imageId,\n\t\t\tmoveCursor: options.moveCursor,\n\t\t});\n\t\treturn { sequence, columns: size.columns, rows: size.rows, imageId: options.imageId };\n\t}\n\n\tif (caps.images === \"iterm2\") {\n\t\tconst sequence = encodeITerm2(base64Data, {\n\t\t\twidth: size.columns,\n\t\t\theight: \"auto\",\n\t\t\tpreserveAspectRatio: options.preserveAspectRatio ?? true,\n\t\t});\n\t\treturn { sequence, columns: size.columns, rows: size.rows };\n\t}\n\n\treturn null;\n}\n\n/**\n * Wrap text in an OSC 8 hyperlink sequence.\n * The text is rendered as a clickable hyperlink in terminals that support OSC 8\n * (Ghostty, Kitty, WezTerm, iTerm2, VSCode, and others).\n * In terminals that do not support OSC 8, the escape sequences are ignored\n * and only the plain text is displayed.\n *\n * @param text - The visible text to display\n * @param url - The URL to link to\n */\nexport function hyperlink(text: string, url: string): string {\n\treturn `\\x1b]8;;${sanitizeTerminalControlPayload(url)}\\x1b\\\\${sanitizeHyperlinkText(text)}\\x1b]8;;\\x1b\\\\`;\n}\n\nexport function imageFallback(mimeType: string, dimensions?: ImageDimensions, filename?: string): string {\n\tconst parts: string[] = [];\n\tif (filename) parts.push(filename);\n\tparts.push(`[${mimeType}]`);\n\tif (dimensions) parts.push(`${dimensions.widthPx}x${dimensions.heightPx}`);\n\treturn `[Image: ${parts.join(\" \")}]`;\n}\n"]}