UNPKG

@kya-os/cli

Version:

CLI for MCP-I setup and management

280 lines 8.06 kB
/** * Utility functions for terminal effects */ import { isRGBColor, isXTermColor, } from "./types.js"; /** * ANSI escape codes */ export const ANSI = { reset: "\x1b[0m", cursor: { hide: "\x1b[?25l", show: "\x1b[?25h", save: "\x1b[s", restore: "\x1b[u", home: "\x1b[H", moveTo: (x, y) => `\x1b[${y + 1};${x + 1}H`, }, clear: { screen: "\x1b[2J", line: "\x1b[2K", toEnd: "\x1b[0J", toStart: "\x1b[1J", }, color: { fg: (color) => { if (isRGBColor(color)) { const r = parseInt(color.substr(0, 2), 16); const g = parseInt(color.substr(2, 2), 16); const b = parseInt(color.substr(4, 2), 16); return `\x1b[38;2;${r};${g};${b}m`; } else if (isXTermColor(color)) { return `\x1b[38;5;${color}m`; } return ""; }, bg: (color) => { if (isRGBColor(color)) { const r = parseInt(color.substr(0, 2), 16); const g = parseInt(color.substr(2, 2), 16); const b = parseInt(color.substr(4, 2), 16); return `\x1b[48;2;${r};${g};${b}m`; } else if (isXTermColor(color)) { return `\x1b[48;5;${color}m`; } return ""; }, }, }; /** * Detect terminal capabilities */ export function detectTerminalCapabilities() { const isInteractive = process.stdout.isTTY || false; const colorLevel = getColorLevel(); return { supportsColor: colorLevel > 0, supports256Color: colorLevel >= 2, supportsTrueColor: colorLevel >= 3, dimensions: getTerminalDimensions(), isInteractive, }; } /** * Get color support level * 0 = no color, 1 = basic color, 2 = 256 color, 3 = true color */ function getColorLevel() { if (process.env.NO_COLOR || process.env.TERM === "dumb") { return 0; } if (process.env.COLORTERM === "truecolor" || process.env.TERM_PROGRAM === "iTerm.app") { return 3; } if (process.env.TERM && /256/.test(process.env.TERM)) { return 2; } if (process.stdout.hasColors?.()) { return process.stdout.hasColors(256) ? 2 : 1; } return 0; } /** * Get terminal dimensions */ export function getTerminalDimensions() { if (process.stdout.isTTY) { return { width: process.stdout.columns || 80, height: process.stdout.rows || 24, }; } return { width: 80, height: 24 }; } /** * Convert RGB hex to XTerm-256 color * Based on the algorithm from https://github.com/Qix-/color-convert */ export function rgbToXterm(hex) { const r = parseInt(hex.substr(0, 2), 16); const g = parseInt(hex.substr(2, 2), 16); const b = parseInt(hex.substr(4, 2), 16); // Check for exact matches in the basic 16 colors const basicColors = [ [0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0], [0, 0, 128], [128, 0, 128], [0, 128, 128], [192, 192, 192], [128, 128, 128], [255, 0, 0], [0, 255, 0], [255, 255, 0], [0, 0, 255], [255, 0, 255], [0, 255, 255], [255, 255, 255], ]; for (let i = 0; i < basicColors.length; i++) { const [br, bg, bb] = basicColors[i]; if (r === br && g === bg && b === bb) { return i; } } // Check grayscale if (r === g && g === b) { if (r < 8) return 16; if (r > 248) return 231; return Math.round(((r - 8) / 247) * 24) + 232; } // Use 6x6x6 color cube const levels = [0, 95, 135, 175, 215, 255]; const findClosest = (value) => { let minDist = Infinity; let closest = 0; for (let i = 0; i < levels.length; i++) { const dist = Math.abs(value - levels[i]); if (dist < minDist) { minDist = dist; closest = i; } } return closest; }; const ri = findClosest(r); const gi = findClosest(g); const bi = findClosest(b); return 16 + 36 * ri + 6 * gi + bi; } /** * Strip ANSI codes from string */ export function stripAnsi(str) { // eslint-disable-next-line no-control-regex return str.replace(/\x1b\[[0-9;]*m/g, ""); } /** * Measure the visible length of a string (excluding ANSI codes) */ export function measureString(str) { return stripAnsi(str).length; } /** * Pad string to specified width (accounting for ANSI codes) */ export function padString(str, width, align = "left") { const visibleLength = measureString(str); const padding = Math.max(0, width - visibleLength); switch (align) { case "right": return " ".repeat(padding) + str; case "center": const leftPad = Math.floor(padding / 2); const rightPad = padding - leftPad; return " ".repeat(leftPad) + str + " ".repeat(rightPad); default: return str + " ".repeat(padding); } } /** * Create a buffer for double-buffered rendering */ export class RenderBuffer { constructor(width, height) { this.buffer = []; this.width = width; this.height = height; this.clear(); } clear() { this.buffer = Array(this.height) .fill(null) .map(() => Array(this.width).fill(" ")); } setChar(x, y, char, color) { if (x >= 0 && x < this.width && y >= 0 && y < this.height) { this.buffer[y][x] = color ? `${color}${char}${ANSI.reset}` : char; } } toString() { return this.buffer.map((row) => row.join("")).join("\n"); } getLines() { return this.buffer.map((row) => row.join("")); } } /** * Sleep for specified milliseconds */ export function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } /** * Calculate FPS delay from frame rate */ export function calculateFrameDelay(fps) { if (fps <= 0) return 0; return Math.floor(1000 / fps); } /** * Easing functions for smooth animations */ export const Easing = { linear: (t) => t, easeIn: (t) => t * t, easeOut: (t) => t * (2 - t), easeInOut: (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t), easeInCubic: (t) => t * t * t, easeOutCubic: (t) => --t * t * t + 1, easeInOutCubic: (t) => t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1, easeInOutSine: (t) => -(Math.cos(Math.PI * t) - 1) / 2, }; // Export individual easing functions for convenience export const easeInQuad = Easing.easeIn; export const easeOutQuad = Easing.easeOut; export const easeInOutQuad = Easing.easeInOut; export const easeInOutSine = Easing.easeInOutSine; /** * Interpolate between two values */ export function lerp(start, end, t) { return start + (end - start) * t; } /** * Clamp a value between min and max */ export function clamp(value, min, max) { return Math.max(min, Math.min(max, value)); } /** * Create a gradient between two colors */ export function createGradient(startColor, endColor, steps) { const colors = []; const sr = parseInt(startColor.substr(0, 2), 16); const sg = parseInt(startColor.substr(2, 2), 16); const sb = parseInt(startColor.substr(4, 2), 16); const er = parseInt(endColor.substr(0, 2), 16); const eg = parseInt(endColor.substr(2, 2), 16); const eb = parseInt(endColor.substr(4, 2), 16); for (let i = 0; i < steps; i++) { const t = i / (steps - 1); const r = Math.round(lerp(sr, er, t)); const g = Math.round(lerp(sg, eg, t)); const b = Math.round(lerp(sb, eb, t)); colors.push(r.toString(16).padStart(2, "0") + g.toString(16).padStart(2, "0") + b.toString(16).padStart(2, "0")); } return colors; } //# sourceMappingURL=utils.js.map