ProvenPin Install

Typer CLI with Rich tables, progress and --json output: working versions (tested 2026-10-15)

A Typer command that renders Rich tables and progress on stderr, with a --json mode for piping, tested offline with CliRunner and no-color Rich output.

passed5 passed · 0 failed2026-10-15 13:26 UTC
Status
passed
Last passed
2026-10-15 13:26 UTC
Tests
5 / 5
Language
Python
Runtime
Python 3.13.16
pinned versions3
typer@0.27.3rich@15.0.0pytest@9.1.1
run it
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt
.venv/bin/python -m pytest -q
test environment
Python 3.13.16
linux 6.17.0-1022-azure
x64
runner: github-actions 38741369695

The code

Exactly the files that passed. Your agent can pull them with get_recipe("typer-rich-table-cli") (install), or see them on GitHub.

requirements.txt
typer==0.27.3
rich==15.0.0
pytest==9.1.1
cli.py
import json
from pathlib import Path
from typing import Annotated

import typer
from rich import box
from rich.console import Console
from rich.markup import escape
from rich.progress import track
from rich.table import Table

app = typer.Typer(add_completion=False)


def make_console(*, stderr: bool = False, color: bool = True) -> Console:
    # Build the Console per invocation. A module-level Console binds to the
    # real sys.stdout at import time and misses CliRunner's captured streams.
    return Console(stderr=stderr, color_system="auto" if color else None, width=80)


@app.command()
def report(
    path: Annotated[Path, typer.Argument(exists=True, dir_okay=False, readable=True)],
    json_out: Annotated[bool, typer.Option("--json", help="Emit JSON instead of a table.")] = False,
    plain: Annotated[bool, typer.Option("--plain", help="Disable color.")] = False,
) -> None:
    rows = json.loads(path.read_text())
    if json_out:
        # Keep stdout pure JSON so it can be piped into other tools.
        typer.echo(json.dumps(rows, indent=2))
        return

    # Progress goes to stderr so it never corrupts the table on stdout.
    err = make_console(stderr=True, color=not plain)
    table = Table(box=box.SIMPLE, show_edge=False)
    table.add_column("name")
    table.add_column("count", justify="right")
    for row in track(rows, description="Building table", console=err, disable=not err.is_terminal):
        # Escape user data so values like "[bold]" are printed literally.
        table.add_row(escape(str(row["name"])), str(row["count"]))
    make_console(color=not plain).print(table)


if __name__ == "__main__":
    app()
tests/test_cli.py
import json

import pytest
from typer.testing import CliRunner

from cli import app

runner = CliRunner()
ROWS = [{"name": "alpha", "count": 3}, {"name": "beta", "count": 12}]


@pytest.fixture
def data(tmp_path):
    p = tmp_path / "rows.json"
    p.write_text(json.dumps(ROWS))
    return p


def test_table_plain_has_no_ansi(data):
    result = runner.invoke(app, [str(data), "--plain"])
    assert result.exit_code == 0
    assert "\x1b[" not in result.stdout
    assert "alpha" in result.stdout and "12" in result.stdout


def test_table_lines_fit_80_columns(data):
    result = runner.invoke(app, [str(data), "--plain"])
    assert all(len(line) <= 80 for line in result.stdout.splitlines())


def test_json_stdout_is_pure_json(data):
    result = runner.invoke(app, [str(data), "--json"])
    assert result.exit_code == 0
    assert json.loads(result.stdout) == ROWS


def test_markup_in_data_is_escaped(tmp_path):
    p = tmp_path / "r.json"
    p.write_text(json.dumps([{"name": "[bold red]x[/]", "count": 1}]))
    result = runner.invoke(app, [str(p), "--plain"])
    assert "[bold red]x[/]" in result.stdout


def test_missing_file_is_usage_error(tmp_path):
    result = runner.invoke(app, [str(tmp_path / "nope.json")])
    assert result.exit_code == 2
pytest.ini
[pytest]
pythonpath = .

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.