181 lines
5.7 KiB
JavaScript
181 lines
5.7 KiB
JavaScript
#!/usr/bin/env node
|
|
'use strict';
|
|
|
|
/**
|
|
* ANSI Terminal Colors - Cross-platform color support for statusline
|
|
* Supports NO_COLOR, FORCE_COLOR, COLORTERM auto-detection
|
|
* @module colors
|
|
*/
|
|
|
|
// ANSI escape codes (standard + bright palette)
|
|
const RESET = '\x1b[0m';
|
|
const DIM = '\x1b[2m';
|
|
const CLEAR_INTENSITY = '\x1b[22m';
|
|
const CLEAR_FOREGROUND = '\x1b[39m';
|
|
const RED = '\x1b[31m';
|
|
const GREEN = '\x1b[32m';
|
|
const YELLOW = '\x1b[33m';
|
|
const BLUE = '\x1b[34m';
|
|
const MAGENTA = '\x1b[35m';
|
|
const CYAN = '\x1b[36m';
|
|
const BRIGHT_RED = '\x1b[91m';
|
|
const BRIGHT_GREEN = '\x1b[92m';
|
|
const BRIGHT_YELLOW = '\x1b[93m';
|
|
const BRIGHT_BLUE = '\x1b[94m';
|
|
const BRIGHT_MAGENTA = '\x1b[95m';
|
|
const BRIGHT_CYAN = '\x1b[96m';
|
|
const BRIGHT_WHITE = '\x1b[97m';
|
|
const STABLE_PREFIX = `${CLEAR_INTENSITY}${CLEAR_FOREGROUND}`;
|
|
const STABLE_SUFFIX = `${RESET}${CLEAR_INTENSITY}${CLEAR_FOREGROUND}`;
|
|
const COLOR_CODES = {
|
|
green: GREEN,
|
|
yellow: YELLOW,
|
|
red: RED,
|
|
blue: BLUE,
|
|
cyan: CYAN,
|
|
magenta: MAGENTA,
|
|
dim: DIM,
|
|
brightRed: BRIGHT_RED,
|
|
brightGreen: BRIGHT_GREEN,
|
|
brightYellow: BRIGHT_YELLOW,
|
|
brightBlue: BRIGHT_BLUE,
|
|
brightMagenta: BRIGHT_MAGENTA,
|
|
brightCyan: BRIGHT_CYAN,
|
|
brightWhite: BRIGHT_WHITE,
|
|
};
|
|
|
|
// Detect color support at module load (cached)
|
|
// Claude Code statusline runs via pipe but output displays in TTY - default to true
|
|
const shouldUseColor = (() => {
|
|
if (process.env.NO_COLOR) return false;
|
|
if (process.env.FORCE_COLOR) return true;
|
|
// Default true for statusline context (Claude Code handles TTY display)
|
|
return true;
|
|
})();
|
|
|
|
// Mutable override (set by statusline.cjs from config)
|
|
// null = use env detection, true/false = explicit override
|
|
let _colorOverride = null;
|
|
|
|
/**
|
|
* Set explicit color enable/disable override (from config)
|
|
* Pass null to revert to env-var detection
|
|
* @param {boolean} enabled
|
|
*/
|
|
function setColorEnabled(enabled) {
|
|
_colorOverride = enabled;
|
|
}
|
|
|
|
/**
|
|
* Determine if colors should be rendered, respecting env vars and config override
|
|
* NO_COLOR env var always takes precedence over config override
|
|
* @returns {boolean}
|
|
*/
|
|
function isColorEnabled() {
|
|
// NO_COLOR env var is a hard override that always wins
|
|
if (process.env.NO_COLOR) return false;
|
|
if (_colorOverride !== null) return _colorOverride;
|
|
return shouldUseColor;
|
|
}
|
|
|
|
// Detect 256-color support via COLORTERM
|
|
const has256Color = (() => {
|
|
const ct = process.env.COLORTERM;
|
|
return ct === 'truecolor' || ct === '24bit' || ct === '256color';
|
|
})();
|
|
|
|
/**
|
|
* Wrap text with ANSI color code
|
|
* @param {string} text - Text to colorize
|
|
* @param {string} code - ANSI escape code
|
|
* @returns {string} Colorized text or plain text if colors disabled
|
|
*/
|
|
function colorize(text, code) {
|
|
if (!isColorEnabled() || !code) return String(text);
|
|
return `${STABLE_PREFIX}${code}${text}${STABLE_SUFFIX}`;
|
|
}
|
|
|
|
function green(text) { return colorize(text, GREEN); }
|
|
function yellow(text) { return colorize(text, YELLOW); }
|
|
function red(text) { return colorize(text, RED); }
|
|
function blue(text) { return colorize(text, BLUE); }
|
|
function cyan(text) { return colorize(text, CYAN); }
|
|
function magenta(text) { return colorize(text, MAGENTA); }
|
|
function dim(text) { return colorize(text, DIM); }
|
|
function brightRed(text) { return colorize(text, BRIGHT_RED); }
|
|
function brightGreen(text) { return colorize(text, BRIGHT_GREEN); }
|
|
function brightYellow(text) { return colorize(text, BRIGHT_YELLOW); }
|
|
function brightBlue(text) { return colorize(text, BRIGHT_BLUE); }
|
|
function brightMagenta(text) { return colorize(text, BRIGHT_MAGENTA); }
|
|
function brightCyan(text) { return colorize(text, BRIGHT_CYAN); }
|
|
function brightWhite(text) { return colorize(text, BRIGHT_WHITE); }
|
|
|
|
/**
|
|
* Get color code based on context percentage threshold
|
|
* @param {number} percent - Context usage percentage (0-100)
|
|
* @returns {string} ANSI color code
|
|
*/
|
|
function resolveColorCode(colorName) {
|
|
if (colorName === 'white' || colorName === 'none' || colorName === 'default') return '';
|
|
return COLOR_CODES[colorName] || '';
|
|
}
|
|
|
|
function getContextColor(percent, palette = {}) {
|
|
const high = resolveColorCode(palette.high || 'red') || RED;
|
|
const mid = resolveColorCode(palette.mid || 'yellow') || YELLOW;
|
|
const low = resolveColorCode(palette.low || 'green') || GREEN;
|
|
if (percent >= 85) return high;
|
|
if (percent >= 70) return mid;
|
|
return low;
|
|
}
|
|
|
|
/**
|
|
* Generate colored progress bar for context window
|
|
* Uses ▰▱ characters (smooth horizontal rectangles) for consistent rendering
|
|
* @param {number} percent - Usage percentage (0-100)
|
|
* @param {number} width - Bar width in characters (default 12)
|
|
* @returns {string} Unicode progress bar with threshold-based colors
|
|
*/
|
|
function coloredBar(percent, width = 12, palette = {}) {
|
|
const clamped = Math.max(0, Math.min(100, percent));
|
|
const filled = Math.round((clamped / 100) * width);
|
|
const empty = width - filled;
|
|
|
|
if (!isColorEnabled()) {
|
|
return '▰'.repeat(filled) + '▱'.repeat(empty);
|
|
}
|
|
|
|
const color = getContextColor(percent, palette);
|
|
return `${STABLE_PREFIX}${color}${'▰'.repeat(filled)}${STABLE_PREFIX}${DIM}${'▱'.repeat(empty)}${STABLE_SUFFIX}`;
|
|
}
|
|
|
|
/**
|
|
* Resolve a color name from theme config to its color function.
|
|
* Used by section renderers to apply theme-configurable colors.
|
|
* Falls back to identity function (no color) for unknown names.
|
|
* @param {string} colorName - Color name (e.g. "green", "yellow", "dim")
|
|
* @returns {Function} Color function (string) => string
|
|
*/
|
|
function resolveColor(colorName) {
|
|
const code = resolveColorCode(colorName);
|
|
return code ? (s) => colorize(s, code) : (s) => String(s);
|
|
}
|
|
|
|
module.exports = {
|
|
RESET,
|
|
green,
|
|
yellow,
|
|
red,
|
|
cyan,
|
|
magenta,
|
|
dim,
|
|
getContextColor,
|
|
coloredBar,
|
|
shouldUseColor,
|
|
has256Color,
|
|
setColorEnabled,
|
|
isColorEnabled,
|
|
resolveColorCode,
|
|
resolveColor,
|
|
};
|