blessed provides a Pythonic wrapper around terminal capabilities — colors, cursor movement, and keyboard input. pip install blessed. Basic: from blessed import Terminal; term = Terminal(). Clear: print(term.clear). Home: print(term.home). Move: print(term.move(y=5, x=10) + "Hello"). Color text: print(term.bold_red("Error")). term.green("OK"). term.blue_on_white("styled"). Background: term.on_green("green bg"). 256-color: term.color(196)("red"). term.on_color(22)("bg"). Bright: term.bright_cyan("text"). Bold: term.bold("text"). term.underline term.reverse term.blink. Width/Height: term.width; term.height. Center: term.center("text"). Rjust: term.rjust("right"). Terminal resize: term.width re-evaluates dynamically. Fullscreen: with term.fullscreen(): draw(). cbreak: with term.cbreak(): key = term.inkey(timeout=0.1). Key: key.is_sequence, key.name — “KEY_UP”, “KEY_DOWN”, “KEY_LEFT”, “KEY_RIGHT”, “KEY_ENTER”, “KEY_ESCAPE”, “q”. term.raw(). term.hidden_cursor(). term.location(x, y) context manager – moves cursor then restores. term.move_xy(x, y) string. Wrap: term.wrap(text, width=term.width). Standout: term.standout("text"). term.dim. Italic: term.italic. Underline: term.underline. term.normal resets all. Claude Code generates blessed TUIs, dashboard layouts, and interactive keyboard-driven terminal applications.
CLAUDE.md for blessed
## blessed Stack
- Version: blessed >= 1.20 | pip install blessed
- Terminal: term = Terminal() — access term.width, term.height, term.clear, term.home
- Color: term.bold_green("text") | term.red("x") | term.on_blue("bg") | term.color(N)
- Cursor: term.move(y, x) | term.move_xy(x, y) | with term.location(x, y): print(...)
- Keys: with term.cbreak(): key = term.inkey(timeout=0.1) | key.name == "KEY_UP"
- Fullscreen: with term.fullscreen(): draw_loop() — saves/restores terminal
- Reset: term.normal | term.clear | term.home | term.clear_eol
blessed Terminal UI Pipeline
# app/tui.py — blessed terminal UI, keyboard input, and dashboard components
from __future__ import annotations
import signal
import sys
import time
import threading
from contextlib import contextmanager
from typing import Any, Callable, Generator, Iterator
from blessed import Terminal
# ─────────────────────────────────────────────────────────────────────────────
# 1. Terminal helpers
# ─────────────────────────────────────────────────────────────────────────────
term = Terminal()
def clear() -> None:
"""Clear screen and move cursor to home position."""
print(term.home + term.clear, end="")
def write_at(x: int, y: int, text: str) -> None:
"""Write text at absolute (x, y) position."""
print(term.move_xy(x, y) + text, end="", flush=True)
def write_centered(text: str, y: int, style: Callable | None = None) -> None:
"""Write text horizontally centered on row y."""
x = max(0, (term.width - len(text)) // 2)
styled = style(text) if style else text
print(term.move_xy(x, y) + styled, end="", flush=True)
def write_line(y: int, text: str, style: Callable | None = None) -> None:
"""Write a full-width line at row y, padded/truncated to terminal width."""
padded = text.ljust(term.width)[:term.width]
styled = style(padded) if style else padded
print(term.move_xy(0, y) + styled, end="", flush=True)
def clear_line(y: int) -> None:
"""Clear a single row."""
print(term.move_xy(0, y) + term.clear_eol, end="", flush=True)
def draw_box(x: int, y: int, w: int, h: int, title: str = "") -> None:
"""Draw a single-line Unicode box at (x, y) with width w and height h."""
top = "┌" + "─" * (w - 2) + "┐"
bottom = "└" + "─" * (w - 2) + "┘"
side = "│" + " " * (w - 2) + "│"
if title:
max_title = w - 4
t = title[:max_title]
top = "┌─ " + t + " " + "─" * (w - 4 - len(t)) + "┐"
write_at(x, y, top)
for row in range(1, h - 1):
write_at(x, y + row, side)
write_at(x, y + h - 1, bottom)
def draw_hline(y: int, char: str = "─") -> None:
"""Draw a horizontal line across the full terminal width."""
print(term.move_xy(0, y) + char * term.width, end="", flush=True)
# ─────────────────────────────────────────────────────────────────────────────
# 2. Text styling helpers
# ─────────────────────────────────────────────────────────────────────────────
def header(text: str) -> str:
return term.bold_white_on_blue(text.center(term.width))
def error_text(text: str) -> str:
return term.bold_red(text)
def success_text(text: str) -> str:
return term.bold_green(text)
def warning_text(text: str) -> str:
return term.bold_yellow(text)
def info_text(text: str) -> str:
return term.cyan(text)
def dim_text(text: str) -> str:
return term.dim(text)
def highlight(text: str) -> str:
return term.reverse(text)
# ─────────────────────────────────────────────────────────────────────────────
# 3. Progress bar
# ─────────────────────────────────────────────────────────────────────────────
def render_progress_bar(
value: float,
total: float,
width: int = 40,
filled_char: str = "█",
empty_char: str = "░",
color_fn: Callable | None = None,
) -> str:
"""
Render an ASCII progress bar string.
Returns colored or plain bar string.
"""
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)
text = f"[{bar}] {pct*100:5.1f}% ({int(value)}/{int(total)})"
return color_fn(text) if color_fn else text
def draw_progress(
y: int,
x: int = 2,
value: float = 0,
total: float = 100,
label: str = "",
width: int = 40,
) -> None:
"""Draw a progress bar at (x, y) on screen."""
pct = min(1.0, max(0.0, value / total)) if total else 0.0
color_fn = term.green if pct >= 0.9 else (term.yellow if pct >= 0.5 else term.red)
bar = render_progress_bar(value, total, width, color_fn=color_fn)
prefix = f"{label:<20} " if label else ""
write_at(x, y, prefix + bar)
# ─────────────────────────────────────────────────────────────────────────────
# 4. Keyboard input
# ─────────────────────────────────────────────────────────────────────────────
def read_key(timeout: float = 0.1):
"""
Read a single keypress (non-blocking with timeout).
Must be called inside `with term.cbreak():`.
Returns a blessed Keystroke or None.
"""
return term.inkey(timeout=timeout) or None
@contextmanager
def interactive_mode() -> Iterator[None]:
"""
Context manager for interactive input: fullscreen + cbreak + hidden cursor.
Restores terminal on exit.
"""
with term.fullscreen(), term.cbreak(), term.hidden_cursor():
try:
yield
except KeyboardInterrupt:
pass
def wait_for_key(
valid_keys: set[str] | None = None,
timeout: float | None = None,
) -> str | None:
"""
Block until a key is pressed (optionally filtered to valid_keys set).
Returns key name string (e.g. "KEY_UP") or character, or None on timeout.
"""
end = time.time() + timeout if timeout else None
with term.cbreak():
while True:
remaining = max(0.01, end - time.time()) if end else 0.1
key = term.inkey(timeout=remaining)
if key:
name = key.name or str(key)
if valid_keys is None or name in valid_keys or str(key) in valid_keys:
return name
if end and time.time() >= end:
return None
# ─────────────────────────────────────────────────────────────────────────────
# 5. Simple scrollable list widget
# ─────────────────────────────────────────────────────────────────────────────
class ScrollableList:
"""
Arrow-key navigable list rendered in a fixed region.
Usage:
items = ["alpha", "beta", "gamma", "delta"]
lst = ScrollableList(items, x=5, y=3, visible_rows=4)
with interactive_mode():
lst.draw()
while True:
key = read_key()
if key and key.name == "KEY_UP": lst.up(); lst.draw()
if key and key.name == "KEY_DOWN": lst.down(); lst.draw()
if key and key.name == "KEY_ENTER": break
chosen = lst.selected_item
"""
def __init__(
self,
items: list[str],
x: int = 2,
y: int = 2,
visible_rows: int = 10,
):
self.items = items
self.x = x
self.y = y
self.visible_rows = visible_rows
self._cursor = 0
self._offset = 0
@property
def selected_index(self) -> int:
return self._offset + self._cursor
@property
def selected_item(self) -> str | None:
idx = self.selected_index
return self.items[idx] if 0 <= idx < len(self.items) else None
def up(self) -> None:
if self._cursor > 0:
self._cursor -= 1
elif self._offset > 0:
self._offset -= 1
def down(self) -> None:
total = len(self.items)
if self._cursor < min(self.visible_rows, total) - 1:
self._cursor += 1
elif self._offset + self.visible_rows < total:
self._offset += 1
def draw(self) -> None:
visible = self.items[self._offset: self._offset + self.visible_rows]
for i, item in enumerate(visible):
text = f" {item:<{term.width - self.x - 4}} "
if i == self._cursor:
styled = term.reverse(text)
else:
styled = text
write_at(self.x, self.y + i, styled)
# clear remaining rows if list is shorter than visible_rows
for i in range(len(visible), self.visible_rows):
write_at(self.x, self.y + i, " " * (term.width - self.x))
# ─────────────────────────────────────────────────────────────────────────────
# 6. Simple status dashboard
# ─────────────────────────────────────────────────────────────────────────────
class StatusDashboard:
"""
Live dashboard with a header, metrics rows, and footer status line.
Runs in a background thread, refreshing at `interval` seconds.
Usage:
dash = StatusDashboard(fetch_fn=collect_metrics, interval=1.0)
with interactive_mode():
dash.start()
while True:
key = read_key(timeout=0.5)
if key and str(key) == "q":
break
dash.stop()
"""
def __init__(
self,
fetch_fn: Callable[[], dict[str, Any]],
interval: float = 1.0,
title: str = "Dashboard",
):
self._fetch = fetch_fn
self._interval = interval
self._title = title
self._running = False
self._thread: threading.Thread | None = None
self._data: dict[str, Any] = {}
def start(self) -> None:
self._running = True
self._thread = threading.Thread(target=self._loop, daemon=True)
self._thread.start()
def stop(self) -> None:
self._running = False
if self._thread:
self._thread.join(timeout=2)
def _loop(self) -> None:
while self._running:
self._data = self._fetch()
self._render()
time.sleep(self._interval)
def _render(self) -> None:
# Header
write_line(0, self._title, style=lambda t: term.bold_white_on_blue(t))
draw_hline(1)
# Metrics
row = 2
for key, value in self._data.items():
label = f" {key:<20}"
val = str(value)
color = term.green if "ok" in val.lower() else term.yellow
write_at(0, row, label + color(val))
row += 1
# Footer
draw_hline(term.height - 2)
write_line(term.height - 1, " [q] Quit [r] Refresh", style=dim_text)
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
if not sys.stdout.isatty():
print("Demo requires a TTY — run in a real terminal.")
sys.exit(0)
print("=== Text styling ===")
print(term.bold("Bold text"))
print(term.green("Green text"))
print(term.bold_red("Bold red"))
print(term.on_blue(term.white("White on blue")))
print(term.underline("Underlined"))
print(term.reverse("Reversed"))
print(dim_text("Dim/secondary text"))
print("\n=== Progress bars ===")
for i in range(0, 110, 25):
bar = render_progress_bar(i, 100, width=30)
print(f" {i:3d}% {bar}")
print("\n=== Box drawing ===")
with term.location(0, 0):
pass # can't draw boxes to stdout in non-fullscreen, show in fullscreen demo
print("\n=== Fullscreen demo (3 seconds) ===")
print(" Starting in 1s... (press Ctrl+C to skip)")
time.sleep(1)
with interactive_mode():
clear()
write_line(0, " blessed TUI Demo", style=lambda t: term.bold_white_on_blue(t))
draw_hline(1)
# Progress bars
write_at(2, 3, term.bold("Progress bars:"))
metrics = [("Download", 75), ("Upload", 40), ("Install", 95)]
for idx, (label, pct) in enumerate(metrics):
draw_progress(y=4+idx, x=2, value=pct, total=100, label=label, width=35)
# Box
draw_hline(8)
draw_box(2, 10, 40, 5, title="System Status")
write_at(4, 11, term.green("●") + " API OK")
write_at(4, 12, term.green("●") + " Database OK")
write_at(4, 13, term.yellow("●") + " Cache DEGRADED")
# Footer
draw_hline(term.height - 2)
write_line(term.height - 1, " Press any key to exit", style=dim_text)
read_key(timeout=3.0)
For the curses alternative — Python’s stdlib curses is powerful but requires understanding window management, padding, and manual refresh coordination; blessed wraps curses (and Windows ANSI) into a simple API where term.bold_green("text") just works and cursor positioning is a string operation (term.move(y, x) + content), making it far faster to build simple TUIs. For the urwid alternative — urwid is a full widget toolkit with layouts, event loops, and composable widgets (Text, Edit, Button, Columns, Pile); blessed is lower-level — you manage the screen yourself — but this gives you precise pixel-level control for custom dashboards, progress meters, and games where urwid’s layout model would be overkill. The Claude Skills 360 bundle includes blessed skill sets covering Terminal() object, term.move/move_xy/location cursor control, bold/color/on_color text styling, term.clear/home/clear_eol screen management, draw_box() Unicode box drawing, term.cbreak()/inkey() keyboard input, interactive_mode() fullscreen context, render_progress_bar() and draw_progress(), ScrollableList arrow-key widget, StatusDashboard threaded refresh, wait_for_key() with key filtering, and draw_hline() separator. Start with the free tier to try terminal UI code generation.