src/orchestrator/colors.ts — ANSI color gate
Purpose
Section titled “Purpose”Decide whether to emit color and provide a single paint(color, text, colors, opts) helper everyone else uses. Centralizing the
gate means a programmatic logger passing { enabled: false } always
gets plain text, regardless of TTY / env.
Public surface
Section titled “Public surface”export interface ColorSupport { enabled: boolean}
export interface PaintOptions { bold?: boolean dim?: boolean}
export function detectColors(stream?: NodeJS.WriteStream): ColorSupportexport function paint( color: string, text: string, colors: ColorSupport, opts?: PaintOptions,): stringDetection rules
Section titled “Detection rules”Standard env precedence:
| Env | Result |
|---|---|
NO_COLOR=<any> | off — overrides FORCE_COLOR |
FORCE_COLOR=<any> | on |
| neither | on iff stream.isTTY === true |
orchestrator.run() always passes { enabled: false } when the
caller provides a custom log — programmatic embedders see plain
text, which keeps test assertions clean.
paint(color, text, colors, opts)
Section titled “paint(color, text, colors, opts)”colors.enabled === false→ returnstextunchanged.- Otherwise wraps
textin:\x1b[1m(bold) whenopts.bold\x1b[2m(dim) whenopts.dimBun.color(color, 'ansi-16m')truecolor sequence\x1b[0mreset
color accepts the same strings as Bun.color ('red', 'sky-blue',
hex codes, etc.). We use 'ansi-16m' (24-bit truecolor) deliberately:
'ansi' is broken in current Bun versions (emits raw bytes), and every
modern terminal supports truecolor. We don’t downgrade for legacy
terminals — accents are the only UI use case, and “no color” is the
safe fallback.
tests/colors.test.ts:
detectColorsprecedence (NO_COLOR > FORCE_COLOR > TTY).paintno-op when disabled.paintemits sequences when enabled.