painted — terminal UI from print to TUI
Description: One Python library for styled terminal output, composable blocks, CLI harnesses, and full TUI apps. One dependency.

## Print styled output

Replace `print()` one call at a time. Auto-detects TTY.

    from painted import Block, Style, print_block

    block = Block.text("deploy OK", Style(fg="green", bold=True))
    print_block(block)

## Compose

Blocks are immutable rectangles. Compose them with functions.

    from painted import border, join_vertical, ROUNDED

    header = Block.text(" api-gateway ", Style(bold=True, reverse=True))
    status = join_vertical(
        Block.text("  replicas: 2/3 ready", Style(fg="yellow")),
        Block.text("  /health:  200  12ms", Style(fg="green")),
    )
    card = border(join_vertical(header, status), chars=ROUNDED)
    print_block(card)

## CLI harness

One render function, three output modes.

    from painted import run_cli, CliContext, Block

    def render(ctx: CliContext, data: dict) -> Block:
        ...  # your render logic

    def fetch() -> dict:
        return {"status": "ok", "replicas": 3}

    run_cli(sys.argv[1:], render=render, fetch=fetch)

    myapp           # auto-detect
    myapp -q        # quiet
    myapp -v        # verbose
    myapp -i        # interactive TUI
    myapp --json    # JSON output
    myapp | grep ok # plain text, no ANSI

## Full TUI

Alt screen, keyboard input, diff-flush render loop.

    import asyncio
    from painted import Block, Style, border
    from painted.tui import Surface

    class MyApp(Surface):
        def render(self):
            block = Block.text("Hello!", Style(fg="green"))
            border(block, title="Demo").paint(self._buf)

        def on_key(self, key: str):
            if key == "q":
                self.quit()

    asyncio.run(MyApp().run())

$ pip install painted

One dependency: wcwidth