init
This commit is contained in:
180
.opencode/plugin/lib/colors.cjs
Normal file
180
.opencode/plugin/lib/colors.cjs
Normal 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,
|
||||
};
|
||||
Reference in New Issue
Block a user