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"])