This commit is contained in:
2026-04-12 01:06:31 +07:00
commit 10d660cbcb
1066 changed files with 228596 additions and 0 deletions

View File

@@ -0,0 +1,180 @@
#!/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,
};