"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.allocateImageId = allocateImageId;exports.calculateImageRows = calculateImageRows;exports.deleteAllKittyImages = deleteAllKittyImages;exports.deleteKittyImage = deleteKittyImage;exports.detectCapabilities = detectCapabilities;exports.encodeITerm2 = encodeITerm2;exports.encodeKitty = encodeKitty;exports.getCapabilities = getCapabilities;exports.getCellDimensions = getCellDimensions;exports.getGifDimensions = getGifDimensions;exports.getImageDimensions = getImageDimensions;exports.getJpegDimensions = getJpegDimensions;exports.getPngDimensions = getPngDimensions;exports.getWebpDimensions = getWebpDimensions;exports.imageFallback = imageFallback;exports.isImageLine = isImageLine;exports.renderImage = renderImage;exports.resetCapabilitiesCache = resetCapabilitiesCache;exports.setCellDimensions = setCellDimensions;let cachedCapabilities = null; // Default cell dimensions - updated by TUI when terminal responds to query let cellDimensions = { widthPx: 9, heightPx: 18 }; function getCellDimensions() { return cellDimensions; } function setCellDimensions(dims) { cellDimensions = dims; } function detectCapabilities() { const termProgram = process.env.TERM_PROGRAM?.toLowerCase() || ""; const term = process.env.TERM?.toLowerCase() || ""; const colorTerm = process.env.COLORTERM?.toLowerCase() || ""; if (process.env.KITTY_WINDOW_ID || termProgram === "kitty") { return { images: "kitty", trueColor: true, hyperlinks: true }; } if (termProgram === "ghostty" || term.includes("ghostty") || process.env.GHOSTTY_RESOURCES_DIR) { return { images: "kitty", trueColor: true, hyperlinks: true }; } if (process.env.WEZTERM_PANE || termProgram === "wezterm") { return { images: "kitty", trueColor: true, hyperlinks: true }; } if (process.env.ITERM_SESSION_ID || termProgram === "iterm.app") { return { images: "iterm2", trueColor: true, hyperlinks: true }; } if (termProgram === "vscode") { return { images: null, trueColor: true, hyperlinks: true }; } if (termProgram === "alacritty") { return { images: null, trueColor: true, hyperlinks: true }; } const trueColor = colorTerm === "truecolor" || colorTerm === "24bit"; return { images: null, trueColor, hyperlinks: true }; } function getCapabilities() { if (!cachedCapabilities) { cachedCapabilities = detectCapabilities(); } return cachedCapabilities; } function resetCapabilitiesCache() { cachedCapabilities = null; } const KITTY_PREFIX = "\x1b_G"; const ITERM2_PREFIX = "\x1b]1337;File="; function isImageLine(line) { // Fast path: sequence at line start (single-row images) if (line.startsWith(KITTY_PREFIX) || line.startsWith(ITERM2_PREFIX)) { return true; } // Slow path: sequence elsewhere (multi-row images have cursor-up prefix) return line.includes(KITTY_PREFIX) || line.includes(ITERM2_PREFIX); } /** * Generate a random image ID for Kitty graphics protocol. * Uses random IDs to avoid collisions between different module instances * (e.g., main app vs extensions). */ function allocateImageId() { // Use random ID in range [1, 0xffffffff] to avoid collisions return Math.floor(Math.random() * 0xfffffffe) + 1; } function encodeKitty(base64Data, options = {}) { const CHUNK_SIZE = 4096; const params = ["a=T", "f=100", "q=2"]; if (options.columns) params.push(`c=${options.columns}`); if (options.rows) params.push(`r=${options.rows}`); if (options.imageId) params.push(`i=${options.imageId}`); if (base64Data.length <= CHUNK_SIZE) { return `\x1b_G${params.join(",")};${base64Data}\x1b\\`; } const chunks = []; let offset = 0; let isFirst = true; while (offset < base64Data.length) { const chunk = base64Data.slice(offset, offset + CHUNK_SIZE); const isLast = offset + CHUNK_SIZE >= base64Data.length; if (isFirst) { chunks.push(`\x1b_G${params.join(",")},m=1;${chunk}\x1b\\`); isFirst = false; } else if (isLast) { chunks.push(`\x1b_Gm=0;${chunk}\x1b\\`); } else { chunks.push(`\x1b_Gm=1;${chunk}\x1b\\`); } offset += CHUNK_SIZE; } return chunks.join(""); } /** * Delete a Kitty graphics image by ID. * Uses uppercase 'I' to also free the image data. */ function deleteKittyImage(imageId) { return `\x1b_Ga=d,d=I,i=${imageId}\x1b\\`; } /** * Delete all visible Kitty graphics images. * Uses uppercase 'A' to also free the image data. */ function deleteAllKittyImages() { return `\x1b_Ga=d,d=A\x1b\\`; } function encodeITerm2(base64Data, options = {}) { const params = [`inline=${options.inline !== false ? 1 : 0}`]; if (options.width !== undefined) params.push(`width=${options.width}`); if (options.height !== undefined) params.push(`height=${options.height}`); if (options.name) { const nameBase64 = Buffer.from(options.name).toString("base64"); params.push(`name=${nameBase64}`); } if (options.preserveAspectRatio === false) { params.push("preserveAspectRatio=0"); } return `\x1b]1337;File=${params.join(";")}:${base64Data}\x07`; } function calculateImageRows(imageDimensions, targetWidthCells, cellDimensions = { widthPx: 9, heightPx: 18 }) { const targetWidthPx = targetWidthCells * cellDimensions.widthPx; const scale = targetWidthPx / imageDimensions.widthPx; const scaledHeightPx = imageDimensions.heightPx * scale; const rows = Math.ceil(scaledHeightPx / cellDimensions.heightPx); return Math.max(1, rows); } function getPngDimensions(base64Data) { try { const buffer = Buffer.from(base64Data, "base64"); if (buffer.length < 24) { return null; } if (buffer[0] !== 0x89 || buffer[1] !== 0x50 || buffer[2] !== 0x4e || buffer[3] !== 0x47) { return null; } const width = buffer.readUInt32BE(16); const height = buffer.readUInt32BE(20); return { widthPx: width, heightPx: height }; } catch { return null; } } function getJpegDimensions(base64Data) { try { const buffer = Buffer.from(base64Data, "base64"); if (buffer.length < 2) { return null; } if (buffer[0] !== 0xff || buffer[1] !== 0xd8) { return null; } let offset = 2; while (offset < buffer.length - 9) { if (buffer[offset] !== 0xff) { offset++; continue; } const marker = buffer[offset + 1]; if (marker >= 0xc0 && marker <= 0xc2) { const height = buffer.readUInt16BE(offset + 5); const width = buffer.readUInt16BE(offset + 7); return { widthPx: width, heightPx: height }; } if (offset + 3 >= buffer.length) { return null; } const length = buffer.readUInt16BE(offset + 2); if (length < 2) { return null; } offset += 2 + length; } return null; } catch { return null; } } function getGifDimensions(base64Data) { try { const buffer = Buffer.from(base64Data, "base64"); if (buffer.length < 10) { return null; } const sig = buffer.slice(0, 6).toString("ascii"); if (sig !== "GIF87a" && sig !== "GIF89a") { return null; } const width = buffer.readUInt16LE(6); const height = buffer.readUInt16LE(8); return { widthPx: width, heightPx: height }; } catch { return null; } } function getWebpDimensions(base64Data) { try { const buffer = Buffer.from(base64Data, "base64"); if (buffer.length < 30) { return null; } const riff = buffer.slice(0, 4).toString("ascii"); const webp = buffer.slice(8, 12).toString("ascii"); if (riff !== "RIFF" || webp !== "WEBP") { return null; } const chunk = buffer.slice(12, 16).toString("ascii"); if (chunk === "VP8 ") { if (buffer.length < 30) return null; const width = buffer.readUInt16LE(26) & 0x3fff; const height = buffer.readUInt16LE(28) & 0x3fff; return { widthPx: width, heightPx: height }; } else if (chunk === "VP8L") { if (buffer.length < 25) return null; const bits = buffer.readUInt32LE(21); const width = (bits & 0x3fff) + 1; const height = (bits >> 14 & 0x3fff) + 1; return { widthPx: width, heightPx: height }; } else if (chunk === "VP8X") { if (buffer.length < 30) return null; const width = (buffer[24] | buffer[25] << 8 | buffer[26] << 16) + 1; const height = (buffer[27] | buffer[28] << 8 | buffer[29] << 16) + 1; return { widthPx: width, heightPx: height }; } return null; } catch { return null; } } function getImageDimensions(base64Data, mimeType) { if (mimeType === "image/png") { return getPngDimensions(base64Data); } if (mimeType === "image/jpeg") { return getJpegDimensions(base64Data); } if (mimeType === "image/gif") { return getGifDimensions(base64Data); } if (mimeType === "image/webp") { return getWebpDimensions(base64Data); } return null; } function renderImage(base64Data, imageDimensions, options = {}) { const caps = getCapabilities(); if (!caps.images) { return null; } const maxWidth = options.maxWidthCells ?? 80; const rows = calculateImageRows(imageDimensions, maxWidth, getCellDimensions()); if (caps.images === "kitty") { // Only use imageId if explicitly provided - static images don't need IDs const sequence = encodeKitty(base64Data, { columns: maxWidth, rows, imageId: options.imageId }); return { sequence, rows, imageId: options.imageId }; } if (caps.images === "iterm2") { const sequence = encodeITerm2(base64Data, { width: maxWidth, height: "auto", preserveAspectRatio: options.preserveAspectRatio ?? true }); return { sequence, rows }; } return null; } function imageFallback(mimeType, dimensions, filename) { const parts = []; if (filename) parts.push(filename); parts.push(`[${mimeType}]`); if (dimensions) parts.push(`${dimensions.widthPx}x${dimensions.heightPx}`); return `[Image: ${parts.join(" ")}]`; } /* v9-25fa77f0fc301efb */