clir

Documentation

Every clir feature, with character-perfect rendered output. Click "Open in Playground" on any example to edit and re-run.

Hello, world!

The simplest possible clir program: print styled output.

from clir.output import success, info, warning, error

success("Hello, world!")
info("Welcome to clir.")
warning("This is a warning.")
error("This is an error.")

Styled output

The six core output functions, with --debug enabled to show debug() too.

from clir.output import echo, success, info, warning, error, debug
from clir.runtime import set_verbosity, Verbosity

set_verbosity(Verbosity(debug=True))
echo("Plain echo line.")
success("Operation completed successfully.")
info("Loaded 42 records.")
warning("Cache is stale.")
error("Connection refused.")
debug("Internal state: idle")

Tables

Tables with styled cells using inline rich markup.

from clir.output import Table

t = Table("Component", "Status", "Latency")
t.add_row("API", "[green]running[/green]", "12ms")
t.add_row("Database", "[green]running[/green]", "3ms")
t.add_row("Cache", "[yellow]degraded[/yellow]", "84ms")
t.add_row("Queue", "[red]down[/red]", "—")
t.show()

Panels

Bordered panels with titles for grouping output.

from clir.output import Panel

Panel(
    "All systems operational.\nFour services healthy.",
    title="Status",
).show()

Trees

Hierarchical tree output.

from clir.output import Tree

t = Tree("project")
src = t.branch("src")
src.add("main.py")
src.add("utils.py")
tests = t.branch("tests")
tests.add("test_main.py")
t.add("README.md")
t.show()

Spinners & Progress

Animated indicators (only the final frame shows in the docs; full animation in the playground).

from clir.output import Spinner, Progress
import time

with Spinner("Loading data..."):
    time.sleep(0.3)

with Progress("Indexing files") as p:
    p.set_total(4)
    for _ in range(4):
        time.sleep(0.05)
        p.update(advance=1)

Markdown

Rich-rendered Markdown output.

from clir.output import Markdown

Markdown().render("""# Welcome
This is **bold** and *italic*.

- bullet one
- bullet two

> A blockquote.
""")

JSON output

Pretty-printed JSON with syntax highlighting.

from clir.output import json

json({"status": "ok", "count": 3, "items": ["a", "b", "c"]})

Prompts

A static walkthrough; open in the playground to actually type input. Full prompt bridging arrives in the next site update.

# In docs we render a static representation; the playground will
# bridge actual prompt() calls to a DOM input widget.
from clir.output import Panel, info

Panel(
    "$ name = prompt(\"What is your name?\")\n"
    "  > World\n"
    "$ confirm(\"Continue?\", default=True)\n"
    "  > Y\n"
    "$ select(\"Pick a color:\", [\"red\", \"green\", \"blue\"])\n"
    "  > red",
    title="Interactive prompts (open in playground to try)",
).show()
info("clir's prompt API is non-blocking and integrates with prompt_toolkit.")

Wizards

Multi-step interactive flows. Static depiction in docs.

from clir.output import Panel

Panel(
    "Step 1/3: project name → my-app\n"
    "Step 2/3: language    → Python\n"
    "Step 3/3: use a database? → Yes\n\n"
    "Result: {'name': 'my-app', 'language': 'Python', 'database': 'Yes'}",
    title="Wizard (multi-step prompt flow)",
).show()

Commands & Groups

@app.command(), @app.group(), --help, and arguments/options.

from clir import ClirApp, argument, option

app = ClirApp(name="mycli", description="A small CLI demonstrating commands and groups.")

@app.command()
@argument("name")
@option("--count", "-c", default=1)
def greet(name: str, count: int):
    """Greet someone warmly."""
    for _ in range(count):
        print(f"Hello, {name}!")

@app.group()
def db():
    """Database operations."""
    pass

@db.command()
def migrate():
    """Run pending migrations."""
    print("Migrations applied.")

app.run(["--help"])

Aliases

Command shortcuts that resolve before dispatch.

from clir import ClirApp

app = ClirApp(name="mycli")

@app.command()
def hello():
    print("Hello!")

app.aliases.add("hi", "hello")
app.aliases.add("greet", "hello")

print("Resolved 'hi' -->", app.aliases.resolve("hi"))
print("Resolved 'greet' -->", app.aliases.resolve("greet"))
app.run(["hi"])

Async commands

await app.run_async() works from inside an existing event loop.

import asyncio
from clir import ClirApp, option

app = ClirApp(name="async-demo")

@app.command()
@option("--url", default="https://example.com")
async def fetch(url: str = "https://example.com"):
    """Async command -- runs on the same event loop as run_async."""
    await asyncio.sleep(0.05)
    print(f"Fetched {url}")

asyncio.run(app.run_async(["fetch"]))

Errors

ClirError + UsageError surface a styled message and exit code; --debug shows the traceback.

from clir import ClirApp, ClirError, UsageError

app = ClirApp(name="demo")

@app.command()
def bad():
    raise UsageError("Missing required --name flag.")

try:
    app.run(["bad"])
except SystemExit as e:
    print(f"\nExit code was: {e.code}")

Validation

Pydantic validation errors are surfaced per-field via the dispatcher.

from clir import BaseModel, Field, ValidationError, ClirApp, argument, option
from clir.output import error

class Config(BaseModel):
    port: int = Field(ge=1, le=65535)
    host: str = "localhost"

app = ClirApp(name="serve")

@app.command()
@argument("port")
@option("--host", default="localhost")
def start(port: str, host: str = "localhost"):
    try:
        cfg = Config(port=int(port), host=host)
        print(f"Starting on {cfg.host}:{cfg.port}")
    except ValidationError as e:
        for err in e.errors():
            error(f"{'.'.join(str(p) for p in err['loc'])}: {err['msg']}")

app.run(["start", "99999"])

Config files

Auto-discover and load YAML / JSON / TOML config; env-var override.

import json, tempfile, os
from clir import ClirApp

# Write a temp config and load it
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as f:
    json.dump({"debug": True, "port": 8080}, f)
    cfg_path = f.name

app = ClirApp(name="myapp")
app.load_config(cfg_path)
print("debug =", app.get_config_value("debug"))
print("port  =", app.get_config_value("port"))
os.unlink(cfg_path)

Shell completion

Generate bash, zsh, or fish completion scripts.

from clir import ClirApp

app = ClirApp(name="mycli")

@app.command()
def hello():
    pass

@app.command()
def world():
    pass

print(app.generate_completion("zsh")[:400] + "\n... (truncated)")

Theme tour

A few themes side by side. Visit /themes for the full 15-theme gallery (next update).

from clir.output import set_theme, get_console, success, Panel

for theme in ["default", "dracula", "monokai", "nord"]:
    set_theme(theme)
    success(f"--- {theme} ---")
    Panel("Hello from " + theme, title=theme).show()

Plugins

Register additional commands at runtime via the plugin manager.

from clir import ClirApp
from clir.plugins import PluginManager, Plugin

app = ClirApp(name="myapp")
pm = PluginManager(app)


# Register a plugin in code (the real plugin system also discovers
# entry-point packages and on-disk plugin files via load_from_directory).
class HelloPlugin(Plugin):
    name = "hello"
    version = "1.0.0"

    def on_register(self):
        @self.app.command(name="hello")
        def hello_cmd():
            """Hello from a plugin."""
            print("Hello from a plugin!")


pm.register(HelloPlugin)
print("Plugins registered:", pm.list())
app.run(["hello"])