sty provides clean, composable ANSI escape codes for terminal colors and text effects. pip install sty. Named colors: from sty import fg, bg, ef, rs; print(fg.red + "Red" + rs.fg). fg.green, fg.blue, fg.yellow, fg.cyan, fg.magenta, fg.white, fg.black. Background: bg.red, bg.green, bg.blue, etc. Effect: ef.bold, ef.italic, ef.underline, ef.blink, ef.dim, ef.inverse, ef.hidden, ef.strike. Reset: rs.all (all attrs), rs.fg (fg only), rs.bg (bg only), rs.ef (effects only), rs.bold_dim, rs.blink, rs.inverse, rs.underline. True color (24-bit): fg(255, 0, 128) — R,G,B. bg(0, 128, 255). 256-color: fg(196) — index. bg(22). Compose: fg.red + ef.bold + "text" + rs.all. Extended: from sty import Style, RgbFg; fg.orange = Style(RgbFg(255, 150, 50)) — register custom. sty objects are strings: len(fg.red) == len("\033[31m"). Iterable: str.join("", [fg.blue, text, rs.fg]). Disable: from sty import mute; mute(fg, bg, ef, rs) — set all codes to "" for non-TTY. Re-enable: from sty import unmute. is_muted(). Works in f-strings: f"{fg.green}OK{rs.fg}". Claude Code generates sty styled output helpers, color maps, and terminal formatting libraries.
CLAUDE.md for sty
## sty Stack
- Version: sty >= 1.0 | pip install sty
- Named: fg.red + "text" + rs.fg | fg.green | bg.blue | ef.bold | ef.underline
- True color: fg(255, 0, 128) + "text" + rs.fg — 24-bit RGB
- 256-color: fg(196) | bg(22) — ANSI 256-color palette index
- Effects: ef.bold | ef.italic | ef.underline | ef.blink | ef.dim | ef.inverse
- Reset: rs.all (everything) | rs.fg | rs.bg | rs.ef (effects only)
- Mute: from sty import mute; mute(fg, bg, ef, rs) — strips codes for non-TTY
sty Terminal Styling Pipeline
# app/styling.py — sty ANSI colors, effects, and styled output helpers
from __future__ import annotations
import sys
from typing import Any
from sty import bg, ef, fg, rs
from sty import Style, RgbFg, RgbBg
from sty import mute, unmute
# ─────────────────────────────────────────────────────────────────────────────
# 1. Register custom colors
# ─────────────────────────────────────────────────────────────────────────────
# 24-bit custom colors
fg.orange = Style(RgbFg(255, 165, 0))
fg.pink = Style(RgbFg(255, 105, 180))
fg.purple = Style(RgbFg(148, 0, 211))
fg.teal = Style(RgbFg(0, 128, 128))
fg.gold = Style(RgbFg(255, 215, 0))
fg.silver = Style(RgbFg(192, 192, 192))
bg.orange = Style(RgbBg(255, 165, 0))
bg.navy = Style(RgbBg(0, 0, 128))
bg.dark = Style(RgbBg(30, 30, 30))
# ─────────────────────────────────────────────────────────────────────────────
# 2. TTY detection and muting
# ─────────────────────────────────────────────────────────────────────────────
def setup_for_tty(stream=sys.stdout) -> None:
"""
Mute all sty codes if the stream is not a TTY.
Call once at startup: setup_for_tty(sys.stdout).
"""
if not stream.isatty():
mute(fg, bg, ef, rs)
def force_colors() -> None:
"""Re-enable colors even in non-TTY (e.g. piped to less -R)."""
unmute(fg, bg, ef, rs)
# ─────────────────────────────────────────────────────────────────────────────
# 3. Styled string factories
# ─────────────────────────────────────────────────────────────────────────────
def styled(text: str, *codes: str) -> str:
"""
Wrap text in one or more sty codes plus rs.all.
styled("Hello", fg.green, ef.bold)
"""
prefix = "".join(codes)
return f"{prefix}{text}{rs.all}"
def bold(text: str, color: str = "") -> str:
return styled(text, color, ef.bold) if color else styled(text, ef.bold)
def italic(text: str) -> str:
return styled(text, ef.italic)
def underline(text: str, color: str = "") -> str:
return styled(text, color, ef.underline) if color else styled(text, ef.underline)
# Semantic helpers
def success(text: str) -> str:
return styled(text, fg.green, ef.bold)
def error(text: str) -> str:
return styled(text, fg.red, ef.bold)
def warning(text: str) -> str:
return styled(text, fg.yellow)
def info(text: str) -> str:
return styled(text, fg.cyan)
def dim(text: str) -> str:
return styled(text, ef.dim)
def highlight(text: str) -> str:
return styled(text, fg.black + bg.yellow)
def code_inline(text: str) -> str:
"""Render inline code with dark background."""
return styled(text, fg.white + bg.dark)
# ─────────────────────────────────────────────────────────────────────────────
# 4. Badge / label components
# ─────────────────────────────────────────────────────────────────────────────
BADGE_STYLES: dict[str, tuple[str, str]] = {
"success": (fg.black + bg.green, "green-on-black"),
"error": (fg.white + bg.red, "white-on-red"),
"warning": (fg.black + bg.yellow, "black-on-yellow"),
"info": (fg.white + bg.blue, "white-on-blue"),
"debug": (fg.white + bg.da_silva if hasattr(fg, "da_silva") else fg.white + bg.black, ""),
"critical":(fg.white + bg.red + ef.bold, ""),
}
def badge(text: str, kind: str = "info") -> str:
"""
Render a colored badge label.
kind: "success" | "error" | "warning" | "info" | "debug" | "critical"
"""
codes, _ = BADGE_STYLES.get(kind, (fg.white, ""))
padded = f" {text} "
return f"{codes}{padded}{rs.all}"
def level_badge(level: str) -> str:
"""Map a log level name to a colored badge."""
mapping = {
"DEBUG": "debug",
"INFO": "info",
"WARNING": "warning",
"ERROR": "error",
"CRITICAL": "critical",
"SUCCESS": "success",
}
return badge(f"{level:<8}", mapping.get(level.upper(), "info"))
# ─────────────────────────────────────────────────────────────────────────────
# 5. Separator and layout helpers
# ─────────────────────────────────────────────────────────────────────────────
def separator(char: str = "─", width: int = 80, color: str = "") -> str:
"""Horizontal separator line."""
line = char * width
return styled(line, color, ef.dim) if color else dim(line)
def header(text: str, width: int = 80, color: str = "") -> str:
"""Bold centered header with separator."""
title = text.center(width)
sep = "═" * width
c = color or fg.cyan
return f"{styled(sep, c)}\n{styled(title, c, ef.bold)}\n{styled(sep, c)}"
def section(title: str) -> str:
"""Section label with underline."""
return f"\n{bold(title, fg.blue)}\n{dim('─' * len(title))}"
def key_value(key: str, value: Any, key_width: int = 20) -> str:
"""Formatted key-value pair: key value."""
k = info(f"{key:<{key_width}}")
v = str(value)
return f" {k} {v}"
def table_header(columns: list[str], widths: list[int]) -> str:
"""Render a styled table header row."""
cells = [bold(col.ljust(w), fg.blue) for col, w in zip(columns, widths)]
row = " " + " ".join(cells)
sep = dim(" " + " ".join("─" * w for w in widths))
return f"{row}\n{sep}"
def table_row(values: list[Any], widths: list[int]) -> str:
"""Render a plain table row."""
cells = [str(v).ljust(w) for v, w in zip(values, widths)]
return " " + " ".join(cells)
# ─────────────────────────────────────────────────────────────────────────────
# 6. Palette viewer
# ─────────────────────────────────────────────────────────────────────────────
def print_named_colors() -> None:
"""Print all standard named sty colors."""
named = ["black","red","green","yellow","blue","magenta","cyan","white",
"li_black","li_red","li_green","li_yellow","li_blue","li_magenta",
"li_cyan","li_white"]
for name in named:
code = getattr(fg, name, None)
if code is not None:
sample = f" {code}{'█' * 8}{rs.fg} fg.{name}"
print(sample)
def print_rgb_gradient(r: int = 255, steps: int = 20) -> None:
"""Print a gradient from black to rgb(r, 255, 255) using 24-bit color."""
for i in range(steps):
v = int(255 * i / steps)
block = fg(r, v, v) + "█" + rs.fg
print(block, end="")
print()
def print_256_palette() -> None:
"""Print the standard 256-color palette."""
for i in range(256):
end = "\n" if (i + 1) % 16 == 0 else ""
print(f"{bg(i)} {rs.bg}", end=end)
print()
# ─────────────────────────────────────────────────────────────────────────────
# 7. Progress bar with sty colors
# ─────────────────────────────────────────────────────────────────────────────
def color_bar(
value: float,
total: float,
width: int = 40,
filled_char: str = "█",
empty_char: str = "░",
) -> str:
"""Render a colored ASCII progress bar using sty."""
pct = min(1.0, max(0.0, value / total)) if total else 0.0
fill = int(pct * width)
bar = filled_char * fill + empty_char * (width - fill)
if pct >= 0.8:
color = fg.green
elif pct >= 0.4:
color = fg.yellow
else:
color = fg.red
return f"{color}[{bar}]{rs.fg} {pct*100:5.1f}%"
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("=== Named colors ===")
for name in ["red", "green", "yellow", "blue", "cyan", "magenta", "white"]:
code = getattr(fg, name)
print(f" {code}fg.{name}{rs.fg}")
print("\n=== Effects ===")
print(f" {ef.bold}Bold{rs.ef} {ef.italic}Italic{rs.ef} "
f"{ef.underline}Underline{rs.ef} {ef.dim}Dim{rs.ef}")
print("\n=== Semantic helpers ===")
print(f" {success('✔ Deployment succeeded')}")
print(f" {warning('⚠ Service degraded')}")
print(f" {error('✗ Connection refused')}")
print(f" {info('ℹ 42 records processed')}")
print(f" {highlight('HIGHLIGHTED')}")
print(f" {code_inline('npm run build')}")
print("\n=== Badges ===")
for kind in ["success", "error", "warning", "info", "critical"]:
print(f" {badge(kind.upper(), kind)}")
print("\n=== Level badges ===")
for level in ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]:
print(f" {level_badge(level)} Some log message")
print("\n=== Layout ===")
print(header("Application Status Report", width=50))
print(section("Database"))
print(key_value("Connection", "postgresql://localhost:5432/app"))
print(key_value("Pool size", "10/10"))
print(separator(width=50))
print("\n=== Table ===")
cols = ["Name", "Status", "Uptime"]
widths = [15, 10, 12]
print(table_header(cols, widths))
rows = [
["api", success("running"), "2h 14m"],
["worker", success("running"), "2h 14m"],
["scheduler", warning("paused"), "—"],
]
for row in rows:
print(table_row(row, widths))
print("\n=== Progress bars ===")
for pct in [15, 45, 80, 100]:
print(f" {color_bar(pct, 100, width=30)}")
print("\n=== 24-bit RGB gradient ===")
print_rgb_gradient(r=255, steps=32)
print("\n=== Custom colors ===")
print(f" {fg.orange}Orange{rs.fg} {fg.pink}Pink{rs.fg} "
f"{fg.purple}Purple{rs.fg} {fg.teal}Teal{rs.fg} {fg.gold}Gold{rs.fg}")
For the colorama alternative — colorama translates ANSI codes to Windows console calls and is the standard for cross-platform color support, but its API is Fore.RED + text + Style.RESET_ALL; sty is more composable — fg.red, bg.blue, ef.bold are string objects that you concatenate freely, and mute(fg, bg, ef, rs) disables all codes at once for non-TTY output. For the termcolor alternative — termcolor’s colored("text", "red", "on_white", ["bold"]) is a one-liner that returns a complete styled string, which is convenient but limits composition; sty lets you build up codes independently (fg.red + ef.bold + bg.white + text + rs.all) and register custom 24-bit colors (fg.brand = Style(RgbFg(22, 121, 187))), making it better when you need a consistent color palette across a large codebase. The Claude Skills 360 bundle includes sty skill sets covering fg/bg/ef/rs objects, 24-bit fg(R,G,B)/bg(R,G,B) true color, 256-color fg(N)/bg(N) palette, ef.bold/italic/underline/dim effects, rs.all/fg/bg/ef selective reset, custom color registration with Style(RgbFg), mute()/unmute() for TTY detection, styled() composition helper, success/error/warning/info semantic helpers, badge()/level_badge() labeled chips, separator()/header()/section() layout, table_header()/table_row() formatting, and color_bar() colored progress. Start with the free tier to try ANSI terminal styling code generation.