Themes
Color theming in a terminal AI agent spans several layers: the raw terminal’s color support (16 colors, 256 colors, 24-bit truecolor), the application’s semantic color palette (primary, error, muted, syntax tokens), and the markdown-specific rendering styles (heading color, code block color, link underline). Getting all three right — and adapting to what the user’s terminal can actually display — is where most tools make mistakes. Aider takes the minimal approach: per-element color flags with two preset modes. Codex builds a runtime color adaptation system using terminal capability detection, perceptual distance, and algorithmic blending. OpenCode ships a full JSON-based theme engine with 27+ built-in themes, dark/light variants per color, and system palette querying via OSC escape sequences.
Feature Definition
Section titled “Feature Definition”A theme system for a terminal AI coding agent must address:
- Terminal capability negotiation: Not all terminals support truecolor. A TUI should detect the terminal’s color level (16-color, 256-color, 24-bit) and use the best representation available.
- Semantic color palette: Named colors for
primary,error,textMuted,background,success, etc. Components should reference semantic names, not hardcoded hex values. - Dark/light mode detection: The application should determine whether the terminal background is light or dark, either through explicit user flags or by querying the terminal.
- Code syntax highlighting: Separate from the UI palette, the syntax highlighting system needs its own color tokens mapped to language grammar scopes.
- Markdown rendering colors: Headings, inline code, blockquotes, and links each need separate colors that integrate with the active theme.
- User customization: Power users should be able to override any color without forking the codebase.
Aider Implementation
Section titled “Aider Implementation”Source: aider/args.py, aider/main.py, aider/io.py, aider/mdstream.py
Dark/Light Mode Presets
Section titled “Dark/Light Mode Presets”Aider’s theming is entirely CLI-driven. There is no config file for themes. Users pass flags, and those flags set per-element colors.
Two preset modes exist (--dark-mode and --light-mode), both implemented in main.py:532-544:
# main.py:532-544if args.dark_mode: args.user_input_color = "#32FF32" args.tool_error_color = "#FF3333" args.tool_warning_color = "#FFFF00" args.assistant_output_color = "#00FFFF" args.code_theme = "monokai"
if args.light_mode: args.user_input_color = "green" args.tool_error_color = "red" args.tool_warning_color = "#FFA500" args.assistant_output_color = "blue" args.code_theme = "default"The presets are not applied at startup by default — the user must pass --dark-mode or --light-mode explicitly. There is no auto-detection of terminal background color.
Per-Element Color Flags
Section titled “Per-Element Color Flags”Every individual color element is overridable. From args.py:316-394:
| Flag | Default | Purpose |
|---|---|---|
--user-input-color | #00cc00 | User input text color |
--tool-output-color | None | Tool/command output color |
--tool-error-color | #FF2222 | Error message color |
--tool-warning-color | #FFA500 | Warning message color |
--assistant-output-color | #0088ff | LLM response color |
--completion-menu-color | None | Completion menu text |
--completion-menu-bg-color | None | Completion menu background |
--completion-menu-current-color | None | Selected item text |
--completion-menu-current-bg-color | None | Selected item background |
Colors accept hex format with or without # prefix, or named Rich colors like green, red, blue. The ensure_hash_prefix() function in io.py:46-54 normalizes bare hex strings:
def ensure_hash_prefix(color): if not color: return color if isinstance(color, str) and color.strip() and not color.startswith("#"): if all(c in "0123456789ABCDEFabcdef" for c in color) and len(color) in (3, 6): return f"#{color}" return colorAll colors are validated at startup via Rich’s RichStyle:
# io.py:374-398def _validate_color_settings(self): color_attributes = [ "user_input_color", "tool_output_color", "tool_error_color", "tool_warning_color", "assistant_output_color", "completion_menu_color", "completion_menu_bg_color", "completion_menu_current_color", "completion_menu_current_bg_color", ] for attr_name in color_attributes: color_value = getattr(self, attr_name, None) if color_value: try: RichStyle(color=color_value) except ColorParseError: setattr(self, attr_name, None) # silently reset invalid colorsCode Theme (Pygments)
Section titled “Code Theme (Pygments)”The --code-theme flag controls syntax highlighting inside fenced code blocks. It maps directly to a Pygments theme name:
# args.py:387-393group.add_argument( "--code-theme", default="default", help=( "Set the markdown code theme (default: default, other options include monokai," " solarized-dark, solarized-light, or a Pygments builtin style," " see https://pygments.org/styles for available themes)" ),)The theme name is passed to NoInsetCodeBlock.create() → Rich’s Syntax(code, lexer_name, theme=theme). Any valid Pygments theme name is accepted, giving access to the full Pygments theme library.
Environment Variable Support
Section titled “Environment Variable Support”All color flags accept AIDER_* environment variable equivalents because Aider uses configargparse with auto_env_var_prefix="AIDER_". AIDER_CODE_THEME=monokai is equivalent to --code-theme monokai.
Codex Implementation
Section titled “Codex Implementation”Source: codex-rs/tui/src/color.rs, codex-rs/tui/src/terminal_palette.rs, codex-rs/tui/src/style.rs, codex-rs/tui/src/markdown_render.rs
Codex has no external theme files and no user-facing color configuration. Instead, it detects the terminal’s capabilities and native colors at startup and adapts its colors algorithmically.
Terminal Capability Detection
Section titled “Terminal Capability Detection”terminal_palette.rs queries the terminal’s color support via the supports-color crate and detects the native foreground/background colors via escape sequences:
// terminal_palette.rs:13-34pub fn best_color(target: (u8, u8, u8)) -> Color { let Some(color_level) = supports_color::on_cached(supports_color::Stream::Stdout) else { return Color::default(); }; if color_level.has_16m { // Truecolor: use exact RGB Color::Rgb(target.0, target.1, target.2) } else if color_level.has_256 { // 256-color: find nearest xterm color by perceptual distance Color::Indexed(find_nearest_xterm_color(target)) } else { // Basic 16-color: fall back to terminal defaults Color::default() }}On Unix, terminal_palette.rs:67-138 uses crossterm’s query_foreground_color() and query_background_color() to query the terminal’s actual foreground and background via OSC 10/11 escape sequences. The result is cached with an attempt counter to avoid repeated failed queries on terminals that do not support this.
Xterm-256 Color Palette
Section titled “Xterm-256 Color Palette”The file contains a complete mapping of all 256 xterm color indices to RGB tuples (XTERM_COLORS: [(u8, u8, u8); 256], lines 158–418). The first 16 entries are the system ANSI colors (terminal-dependent); entries 16–255 are the standardized 6×6×6 color cube plus a 24-step grayscale ramp. This table is used for CIE76 distance comparison when choosing the nearest 256-color index.
Color Utilities
Section titled “Color Utilities”color.rs provides three fundamental operations:
Brightness detection:
// color.rs:1-5pub(crate) fn is_light(bg: (u8, u8, u8)) -> bool { let (r, g, b) = bg; // Rec. 601 luminance let y = 0.299 * r as f32 + 0.587 * g as f32 + 0.114 * b as f32; y > 128.0}Alpha compositing:
// color.rs:7-12pub(crate) fn blend(fg: (u8, u8, u8), bg: (u8, u8, u8), alpha: f32) -> (u8, u8, u8) { let r = (fg.0 as f32 * alpha + bg.0 as f32 * (1.0 - alpha)) as u8; let g = (fg.1 as f32 * alpha + bg.1 as f32 * (1.0 - alpha)) as u8; let b = (fg.2 as f32 * alpha + bg.2 as f32 * (1.0 - alpha)) as u8; (r, g, b)}Perceptual distance (CIE76):
// color.rs:14-75pub(crate) fn perceptual_distance(a: (u8, u8, u8), b: (u8, u8, u8)) -> f32 { // Converts both colors to CIELAB, returns Euclidean distance in L*a*b* space // Used by find_nearest_xterm_color() to select the visually closest index}Dark/Light Adaptation
Section titled “Dark/Light Adaptation”style.rs:8-44 uses the detected terminal background to derive contextual colors. Rather than choosing between a dark palette and a light palette, it blends with the actual terminal background:
pub fn user_message_bg(terminal_bg: (u8, u8, u8)) -> Color { let (top, alpha) = if is_light(terminal_bg) { ((0, 0, 0), 0.04) // Subtle dark overlay on light background } else { ((255, 255, 255), 0.12) // Subtle light overlay on dark background }; best_color(blend(top, terminal_bg, alpha))}
pub fn user_message_style_for(terminal_bg: Option<(u8, u8, u8)>) -> Style { match terminal_bg { Some(bg) => Style::default().bg(user_message_bg(bg)), None => Style::default(), }}This approach is elegant: instead of hardcoding a background color for user message bubbles, Codex derives a color that is slightly different from the terminal’s own background, regardless of what that background is.
Markdown Styles
Section titled “Markdown Styles”Markdown rendering styles (markdown_render.rs:17-55) are all hardcoded ratatui Stylize values:
struct MarkdownStyles { h1: Style::new().bold().underlined(), h2: Style::new().bold(), h3: Style::new().bold().italic(), h4: Style::new().italic(), code: Style::new().cyan(), emphasis: Style::new().italic(), strong: Style::new().bold(), strikethrough: Style::new().crossed_out(), ordered_list_marker: Style::new().light_blue(), link: Style::new().cyan().underlined(), blockquote: Style::new().green(),}These are ratatui symbolic colors (Color::Cyan, Color::Green, Color::LightBlue), which map to the terminal’s ANSI color palette. On a dark terminal, Color::Green is typically a bright lime green; on a light terminal it may be unreadable. Codex does not adapt these colors to the detected background — they are always the ratatui defaults.
No User Theme Configuration
Section titled “No User Theme Configuration”Codex has no --theme, --dark-mode, or --light-mode flags. Color behavior is entirely automatic based on terminal detection. Users cannot override individual colors.
OpenCode Implementation
Section titled “OpenCode Implementation”Source: opencode/packages/opencode/src/cli/cmd/tui/context/theme.tsx (1152 lines), opentui/packages/core/src/lib/terminal-palette.ts, opentui/packages/core/src/zig/ansi.zig, opentui/packages/core/src/syntax-style.ts
OpenCode has the most sophisticated theming of the three tools: a JSON-based theme format, 27+ built-in themes, dark/light variants per color, OSC-4-based system palette detection, and user-defined theme discovery.
Built-in Themes
Section titled “Built-in Themes”Theme JSON files live in packages/opencode/src/cli/cmd/tui/context/theme/. The following are imported at build time:
// theme.tsx (excerpt)import aura from "./theme/aura.json"import ayu from "./theme/ayu.json"import catppuccin from "./theme/catppuccin.json"import dracula from "./theme/dracula.json"import github from "./theme/github.json"import gruvbox from "./theme/gruvbox.json"import monokai from "./theme/monokai.json"import nord from "./theme/nord.json"import tokyonight from "./theme/tokyonight.json"// ... 20+ moreAll themes are available immediately without filesystem reads.
ThemeColors Type
Section titled “ThemeColors Type”The semantic color palette (theme.tsx:45-98) has 50+ named slots, categorized by use:
type ThemeColors = { // Core UI primary: RGBA; secondary: RGBA; accent: RGBA error: RGBA; warning: RGBA; success: RGBA; info: RGBA text: RGBA; textMuted: RGBA; selectedListItemText: RGBA background: RGBA; backgroundPanel: RGBA; backgroundElement: RGBA; backgroundMenu: RGBA border: RGBA; borderActive: RGBA; borderSubtle: RGBA
// Diff view colors diffAdded: RGBA; diffRemoved: RGBA; diffContext: RGBA diffHunkHeader: RGBA; diffHighlightAdded: RGBA; diffHighlightRemoved: RGBA diffAddedBg: RGBA; diffRemovedBg: RGBA; diffContextBg: RGBA diffLineNumber: RGBA; diffAddedLineNumberBg: RGBA; diffRemovedLineNumberBg: RGBA
// Markdown rendering (14 slots) markdownText: RGBA; markdownHeading: RGBA; markdownLink: RGBA markdownLinkText: RGBA; markdownCode: RGBA; markdownBlockQuote: RGBA markdownEmph: RGBA; markdownStrong: RGBA; markdownHorizontalRule: RGBA markdownListItem: RGBA; markdownListEnumeration: RGBA markdownImage: RGBA; markdownImageText: RGBA; markdownCodeBlock: RGBA
// Syntax highlighting (9 slots) syntaxComment: RGBA; syntaxKeyword: RGBA; syntaxFunction: RGBA syntaxVariable: RGBA; syntaxString: RGBA; syntaxNumber: RGBA syntaxType: RGBA; syntaxOperator: RGBA; syntaxPunctuation: RGBA}Theme JSON Format
Section titled “Theme JSON Format”Each theme file defines a defs object (named color aliases) and a theme object (the semantic mappings):
{ "$schema": "https://opencode.ai/theme.json", "defs": { "darkStep1": "#0a0a0a", "darkStep2": "#141414", "darkStep3": "#1e1e1e", "darkAccent": "#7aa2f7", "darkRed": "#e06c75", "darkGreen": "#9ece6a", "lightStep1": "#fafafa", "lightAccent": "#0078d4" }, "theme": { "primary": { "dark": "darkStep9", "light": "lightStep9" }, "error": { "dark": "darkRed", "light": "#d32f2f" }, "markdownHeading": { "dark": "darkAccent", "light": "lightAccent" }, "syntaxKeyword": { "dark": "#9d7cd8", "light": "#0000ff" } }}Every color value is either a hex string, a reference to a defs name, a reference to another theme key, or an ANSI color number. The dark/light object form selects the variant based on the active mode.
Theme Resolution
Section titled “Theme Resolution”resolveTheme(theme, mode) (theme.tsx:176-232) recursively resolves all references:
function resolveTheme(theme: ThemeJson, mode: "dark" | "light") { const defs = theme.defs ?? {}
function resolveColor(c: ColorValue): RGBA { if (c instanceof RGBA) return c if (typeof c === "string") { if (c === "transparent" || c === "none") return RGBA.fromInts(0, 0, 0, 0) if (c.startsWith("#")) return RGBA.fromHex(c) if (defs[c] != null) return resolveColor(defs[c]) // Named def reference if (theme.theme[c as keyof ThemeColors] !== undefined) return resolveColor(theme.theme[c as keyof ThemeColors]!) // Cross-key reference throw new Error(`Color reference "${c}" not found`) } if (typeof c === "number") return ansiToRgba(c) // ANSI index support return resolveColor(c[mode]) // Dark/light variant }
return Object.fromEntries( Object.entries(theme.theme).map(([key, value]) => [key, resolveColor(value)]) ) as Theme}System Theme via OSC-4
Section titled “System Theme via OSC-4”If the user selects the "system" theme, OpenCode queries the terminal’s actual color palette using the OSC 4 escape protocol, via OpenTUI’s renderer.getPalette():
// theme.tsx:318-346function resolveSystemTheme() { renderer .getPalette({ size: 16 }) .then((colors) => { if (!colors.palette[0]) { // Terminal does not support OSC 4; fall back to "opencode" theme setStore("active", "opencode") return } setStore(produce((draft) => { draft.themes.system = generateSystem(colors, store.mode) if (store.active === "system") draft.ready = true })) })}generateSystem(colors, mode) derives a full semantic palette from the 16-color ANSI palette using color theory heuristics (lightest color = background, darkest = foreground, accent = most saturated, etc.).
OpenTUI’s terminal-palette.ts sends OSC 4 queries and parses the xterm-format responses:
async detect(options?: GetPaletteOptions): Promise<TerminalColors>For tmux users, OSC sequences are double-escaped:
function wrapForTmux(osc: string): string { const escaped = osc.replace(/\x1b/g, "\x1b\x1b") return `\x1bPtmux;${escaped}\x1b\\`}Custom Theme Discovery
Section titled “Custom Theme Discovery”OpenCode searches for user-defined theme files in a glob scan across multiple directories:
// theme.tsx:394-418const CUSTOM_THEME_GLOB = new Bun.Glob("themes/*.json")
async function getCustomThemes() { const directories = [ Global.Path.config, // ~/.config/opencode/ ...(await Array.fromAsync( // .opencode/ in every parent dir Filesystem.up({ targets: [".opencode"], start: process.cwd() }) )), ] const result: Record<string, ThemeJson> = {} for (const dir of directories) { for await (const item of CUSTOM_THEME_GLOB.scan({ absolute: true, followSymlinks: true, dot: true, cwd: dir })) { const name = path.basename(item, ".json") result[name] = await Bun.file(item).json() } } return result}User themes are merged with built-in themes. If a user places ~/.config/opencode/themes/mytheme.json, it appears as "mytheme" in the theme selector.
Syntax Highlighting Integration
Section titled “Syntax Highlighting Integration”The theme is translated into a SyntaxStyle object via getSyntaxRules() (theme.tsx:622-1152), which maps 100+ TextMate-compatible scope names to theme colors:
function getSyntaxRules(theme: Theme) { return [ { scope: ["comment", "punctuation.definition.comment"], style: { foreground: theme.syntaxComment } }, { scope: ["keyword", "storage.type", "storage.modifier"], style: { foreground: theme.syntaxKeyword } }, { scope: ["entity.name.function", "meta.function-call"], style: { foreground: theme.syntaxFunction } }, { scope: ["string", "string.quoted"], style: { foreground: theme.syntaxString } }, // markup scopes for markdown { scope: ["markup.heading"], style: { foreground: theme.markdownHeading, bold: true } }, { scope: ["markup.bold", "markup.strong"], style: { foreground: theme.markdownStrong, bold: true } }, // ... 90+ more entries ]}The SyntaxStyle.fromTheme(rules) factory (syntax-style.ts:87-96) registers each scope with the Zig native library, compiling scope names to numeric IDs. During rendering, a scope string resolves to an ID via O(1) hash lookup. Dot-separated fallback ("keyword.control.rust" → "keyword") is handled by checking for . in the scope name and retrying with the base name.
OpenTUI Zig Color System
Section titled “OpenTUI Zig Color System”At the lowest level, OpenTUI’s Zig code (ansi.zig) emits 24-bit ANSI sequences:
// ansi.zig:27-33pub fn fgColorOutput(writer: anytype, r: u8, g: u8, b: u8) AnsiError!void { writer.print("\x1b[38;2;{d};{d};{d}m", .{ r, g, b }) catch return AnsiError.WriteFailed;}
pub fn bgColorOutput(writer: anytype, r: u8, g: u8, b: u8) AnsiError!void { writer.print("\x1b[48;2;{d};{d};{d}m", .{ r, g, b }) catch return AnsiError.WriteFailed;}Text attributes are packed into a u8 bitmask:
// ansi.zig:186-218pub const BOLD = 1 << 0;pub const DIM = 1 << 1;pub const ITALIC = 1 << 2;pub const UNDERLINE = 1 << 3;pub const BLINK = 1 << 4;pub const INVERSE = 1 << 5;pub const HIDDEN = 1 << 6;pub const STRIKETHROUGH = 1 << 7;// bits 8-31 are reserved for LINK_ID (24-bit hyperlink reference)RGBA colors flow from TypeScript (as RGBA objects) through the FFI boundary into Zig as 4-float arrays [4]f32.
Pitfalls & Hard Lessons
Section titled “Pitfalls & Hard Lessons”OSC Color Queries Fail in Many Terminals
Section titled “OSC Color Queries Fail in Many Terminals”The OSC 10/11 protocol for querying terminal foreground/background colors is not universally supported. Common failure cases: ssh without $COLORTERM forwarding, multiplexers (tmux/screen) that do not proxy the query, and terminals that acknowledge the query but return incorrect values. Any system that relies on this must have a fallback (Codex caches the failure and disables the feature; OpenCode falls back to the default theme).
Truecolor Detection Is Unreliable
Section titled “Truecolor Detection Is Unreliable”$COLORTERM=truecolor is the standard env var, but many terminal emulators do not set it even when they support truecolor. The supports-color crate used by Codex checks multiple signals: $COLORTERM, $TERM, $TERM_PROGRAM, and Kitty’s $KITTY_WINDOW_ID. Even so, some environments (certain CI runners, legacy SSH sessions) will falsely report no color support.
ANSI Named Colors Break in Unexpected Themes
Section titled “ANSI Named Colors Break in Unexpected Themes”Codex uses Color::Cyan and Color::Green for inline code and blockquotes respectively. On a terminal with a Solarized Light palette, the ANSI cyan might render as a barely-visible near-white. Using ANSI symbolic colors that look good on one terminal can look terrible on another. The correct approach is always to use the terminal’s detected background and derive the actual RGB value.
JSON Theme Schemas Drift
Section titled “JSON Theme Schemas Drift”OpenCode’s $schema field in theme JSON points to https://opencode.ai/theme.json. If the ThemeColors type gains new fields, existing themes will use the default colors for those fields. The resolveTheme() function must handle missing keys gracefully or new theme additions will break all user-defined themes.
Custom Themes Must Be Versioned
Section titled “Custom Themes Must Be Versioned”When OpenCode adds a new semantic color slot (e.g., markdownCodeBlock was presumably added later), built-in themes can be updated, but user themes from the filesystem cannot. A "version" field in the theme JSON and a migration layer would prevent silent regressions.
Palette Version Counter
Section titled “Palette Version Counter”Codex’s terminal_palette.rs:59-65 exports a palette_version() -> u64 counter backed by an atomic. When the terminal palette changes (detected via OS signals on macOS), the version increments and widgets that cached derived colors can detect the invalidation. This is an underused pattern — most implementations query the palette once at startup and never update it.
OpenOxide Blueprint
Section titled “OpenOxide Blueprint”Architecture: Three-Layer Theme System
Section titled “Architecture: Three-Layer Theme System”┌─────────────────────────────────────────────┐│ Layer 3: Semantic ThemeColors struct ││ (primary, error, markdownHeading, ...) │└──────────────────────┬──────────────────────┘ │ resolveTheme(json, mode)┌──────────────────────▼──────────────────────┐│ Layer 2: Theme JSON with dark/light ││ variants, defs, ANSI color support │└──────────────────────┬──────────────────────┘ │ terminal capability detection┌──────────────────────▼──────────────────────┐│ Layer 1: Terminal Color API ││ supports-color, OSC 10/11 query, fallback │└─────────────────────────────────────────────┘Core Types
Section titled “Core Types”pub struct ThemeColors { // Core UI (12 fields) pub primary: Color, pub error: Color, pub warning: Color, pub success: Color, pub text: Color, pub text_muted: Color, pub background: Color, pub background_panel: Color, pub border: Color, pub border_active: Color, // Markdown (8 fields) pub markdown_heading: Color, pub markdown_code: Color, pub markdown_link: Color, pub markdown_strong: Color, pub markdown_emph: Color, pub markdown_blockquote: Color, // Syntax (9 fields) pub syntax_comment: Color, pub syntax_keyword: Color, pub syntax_function: Color, pub syntax_string: Color, pub syntax_number: Color, pub syntax_type: Color, pub syntax_variable: Color, pub syntax_operator: Color, pub syntax_punctuation: Color,}
pub struct MarkdownTheme { pub heading: [Style; 6], // H1-H6 pub code_block: Style, pub code_inline: Style, pub emphasis: Style, pub strong: Style, pub strikethrough: Style, pub link: Style, pub blockquote: Style, pub list_marker_ordered: Style, pub list_marker_unordered: Style,}Theme JSON Format
Section titled “Theme JSON Format”Use the same JSON schema as OpenCode (dark/light variant objects, defs for named aliases). This lets OpenCode theme files be used directly with OpenOxide with minimal modification.
pub struct ThemeJson { pub defs: HashMap<String, ColorValue>, pub theme: HashMap<String, ColorValue>,}
#[derive(Deserialize)]#[serde(untagged)]pub enum ColorValue { Hex(String), // "#7aa2f7" Named(String), // "darkAccent" or other def/key ref AnsiIndex(u8), // ANSI color number Variant { dark: Box<ColorValue>, light: Box<ColorValue> },}
pub fn resolve_theme(json: &ThemeJson, mode: ColorMode) -> Result<ThemeColors>;Terminal Capability Detection
Section titled “Terminal Capability Detection”pub enum ColorMode { Dark, Light }
pub fn detect_color_mode() -> ColorMode { // 1. Check $OPENOXIDE_COLOR_MODE env var // 2. Query OSC 11 for background color // 3. Use Rec.601 luminance threshold // 4. Default to Dark}
pub fn best_color(rgb: (u8, u8, u8)) -> ratatui::style::Color { // supports-color → truecolor → 256-color (CIE76) → default}User Theme Discovery
Section titled “User Theme Discovery”pub fn discover_themes() -> HashMap<String, PathBuf> { // 1. ~/.config/openoxide/themes/*.toml // 2. .openoxide/themes/*.toml walking up from CWD}Use TOML instead of JSON for user-editable files (comments, no trailing comma restrictions).
Crates
Section titled “Crates”| Crate | Purpose |
|---|---|
serde + serde_json | Theme JSON deserialization |
ratatui | Color, Style, Stylize traits |
supports-color | Terminal color level detection |
crossterm | OSC color query (terminal background detection) |