mcpretentious
Version:
MCPretentious - Universal Terminal MCP. High-performance terminal automation for iTerm2 (WebSocket) and tmux (control mode). Cross-platform support with cursor position, colors, and layered screenshots.
257 lines (226 loc) • 7.16 kB
JavaScript
/**
* Color Utilities for MCPretentious
*
* Converts iTerm2 color representations to RGB values
* Handles standard ANSI colors, 256-color palette, and true RGB colors
*/
/**
* Standard ANSI colors (0-15) to RGB mapping
* Based on default iTerm2 color scheme
*/
const ANSI_TO_RGB = {
// Normal colors (0-7)
0: { r: 0, g: 0, b: 0 }, // Black
1: { r: 194, g: 54, b: 33 }, // Red
2: { r: 37, g: 188, b: 36 }, // Green
3: { r: 173, g: 173, b: 39 }, // Yellow
4: { r: 73, g: 46, b: 225 }, // Blue
5: { r: 211, g: 56, b: 211 }, // Magenta
6: { r: 51, g: 187, b: 200 }, // Cyan
7: { r: 203, g: 204, b: 205 }, // White
// Bright colors (8-15)
8: { r: 129, g: 131, b: 131 }, // Bright Black
9: { r: 252, g: 57, b: 31 }, // Bright Red
10: { r: 49, g: 231, b: 34 }, // Bright Green
11: { r: 234, g: 236, b: 35 }, // Bright Yellow
12: { r: 88, g: 51, b: 255 }, // Bright Blue
13: { r: 249, g: 53, b: 248 }, // Bright Magenta
14: { r: 20, g: 240, b: 240 }, // Bright Cyan
15: { r: 233, g: 235, b: 235 } // Bright White
};
/**
* Convert 256-color palette index to RGB
* Colors 0-15: Standard ANSI
* Colors 16-231: 6x6x6 RGB cube
* Colors 232-255: Grayscale
*/
function color256ToRGB(index) {
if (index < 16) {
// Standard ANSI colors
return ANSI_TO_RGB[index] || { r: 0, g: 0, b: 0 };
} else if (index < 232) {
// 6x6x6 RGB cube (216 colors)
const i = index - 16;
const r = Math.floor(i / 36);
const g = Math.floor((i % 36) / 6);
const b = i % 6;
// Convert 0-5 range to 0-255
const levels = [0, 95, 135, 175, 215, 255];
return {
r: levels[r],
g: levels[g],
b: levels[b]
};
} else {
// Grayscale (24 shades)
const gray = 8 + (index - 232) * 10;
return { r: gray, g: gray, b: gray };
}
}
/**
* Extract RGB values from iTerm2 style color object
* Handles different color formats: standard, RGB, alternate
*/
export function extractColorRGB(colorInfo, isBackground = false) {
if (!colorInfo) return null;
// Check for different color format fields based on our debug output
// The style uses fgStandard/bgStandard, fgRgb/bgRgb, fgAlternate/bgAlternate
// Direct RGB color (24-bit true color)
if (colorInfo.rgb) {
return {
r: colorInfo.rgb.red || 0,
g: colorInfo.rgb.green || 0,
b: colorInfo.rgb.blue || 0
};
}
// For styles from getScreenContents
if (typeof colorInfo === 'object') {
// Check for RGB fields
if (colorInfo.fgRgb && !isBackground) {
return {
r: colorInfo.fgRgb.red || 0,
g: colorInfo.fgRgb.green || 0,
b: colorInfo.fgRgb.blue || 0
};
}
if (colorInfo.bgRgb && isBackground) {
return {
r: colorInfo.bgRgb.red || 0,
g: colorInfo.bgRgb.green || 0,
b: colorInfo.bgRgb.blue || 0
};
}
// Standard color (0-255 palette)
if (colorInfo.fgStandard !== undefined && !isBackground) {
return color256ToRGB(colorInfo.fgStandard);
}
if (colorInfo.bgStandard !== undefined && isBackground) {
return color256ToRGB(colorInfo.bgStandard);
}
// Check simple standard field
if (colorInfo.standard !== undefined) {
return color256ToRGB(colorInfo.standard);
}
}
// If it's just a number, treat as standard color index
if (typeof colorInfo === 'number') {
return color256ToRGB(colorInfo);
}
// Default color (usually means terminal default)
if (colorInfo.alternate === 'DEFAULT' ||
colorInfo.fgAlternate === 'DEFAULT' ||
colorInfo.bgAlternate === 'DEFAULT') {
// Return default terminal colors (can be customized)
return isBackground
? { r: 0, g: 0, b: 0 } // Default background (black)
: { r: 203, g: 204, b: 205 }; // Default foreground (light gray)
}
return null;
}
/**
* Parse style run from iTerm2 and extract colors and attributes
*/
export function parseStyleRun(styleRun) {
const result = {
foreground: null,
background: null,
attributes: {
bold: styleRun.bold || false,
italic: styleRun.italic || false,
underline: styleRun.underline || false,
strikethrough: styleRun.strikethrough || false,
blink: styleRun.blink || false,
inverse: styleRun.inverse || false,
invisible: styleRun.invisible || false,
faint: styleRun.faint || false
},
repeats: styleRun.repeats || 1
};
// Extract foreground color
if (styleRun.fgStandard !== undefined) {
result.foreground = color256ToRGB(styleRun.fgStandard);
} else if (styleRun.fgRgb) {
result.foreground = {
r: styleRun.fgRgb.red || 0,
g: styleRun.fgRgb.green || 0,
b: styleRun.fgRgb.blue || 0
};
} else if (styleRun.fgAlternate === 'DEFAULT') {
result.foreground = { r: 203, g: 204, b: 205 }; // Default foreground
}
// Extract background color
if (styleRun.bgStandard !== undefined) {
result.background = color256ToRGB(styleRun.bgStandard);
} else if (styleRun.bgRgb) {
result.background = {
r: styleRun.bgRgb.red || 0,
g: styleRun.bgRgb.green || 0,
b: styleRun.bgRgb.blue || 0
};
} else if (styleRun.bgAlternate === 'DEFAULT') {
result.background = { r: 0, g: 0, b: 0 }; // Default background
}
return result;
}
/**
* Convert RGB object to hex color string
*/
export function rgbToHex(rgb) {
if (!rgb) return null;
const toHex = (n) => {
const hex = Math.round(n).toString(16).padStart(2, '0');
return hex;
};
return `#${toHex(rgb.r)}${toHex(rgb.g)}${toHex(rgb.b)}`;
}
/**
* Parse a complete line with styles and return structured data
*/
export function parseLineWithStyles(lineContent) {
if (!lineContent) return null;
const result = {
text: lineContent.text || '',
styles: []
};
if (lineContent.style && Array.isArray(lineContent.style)) {
// Parse each style run
result.styles = lineContent.style.map(styleRun => parseStyleRun(styleRun));
}
return result;
}
/**
* Get character at position with its color and style
*/
export function getCharacterStyle(lineContent, position) {
if (!lineContent || !lineContent.style) return null;
let currentPos = 0;
for (const styleRun of lineContent.style) {
const runLength = styleRun.repeats || 1;
if (position >= currentPos && position < currentPos + runLength) {
// Found the style run for this position
const parsed = parseStyleRun(styleRun);
return {
character: lineContent.text ? lineContent.text[position] : '',
foreground: parsed.foreground,
foregroundHex: rgbToHex(parsed.foreground),
background: parsed.background,
backgroundHex: rgbToHex(parsed.background),
...parsed.attributes
};
}
currentPos += runLength;
}
return null;
}
// Also export helper functions
export { color256ToRGB, ANSI_TO_RGB };
// Default export for convenience
export default {
extractColorRGB,
parseStyleRun,
parseLineWithStyles,
getCharacterStyle,
rgbToHex,
color256ToRGB,
ANSI_TO_RGB
};