Engineer production-grade Python CLI tools with UV for package management, Ruff for linting, Pyright for strict typing, Typer for commands, and Rich for polished output. Addresses fail-fast patterns, pydantic-settings configuration, modular code organization, and professional UX conventions. Apply when creating admin utilities, data processors, or developer tooling.
Engineers production-grade Python CLI tools with modern tooling and fail-fast patterns for admin utilities and data processors.
npx claudepluginhub aeyeops/aeo-skill-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
patterns/make-integration.mdpatterns/multi-method-auth.mdpatterns/postgresql-jsonb.mdpatterns/pydantic-flexible.mdpatterns/schema-resilience.mdreference/database-migrations.mdreferences/architecture.mdreferences/configuration.mdreferences/tech-stack.mdtemplates/Makefiletemplates/pyproject.tomlModern patterns for building production-grade Python command-line applications.
Use when building:
Installation:
curl -LsSf https://astral.sh/uv/install.sh | sh # Install UV
uv init my-cli # Initialize project
uv add typer rich # Add dependencies
For complete tool guides, configuration examples, and advanced patterns: See @references/tech-stack.md
# Custom exceptions (core/exceptions.py)
class AppError(Exception):
"""Base exception - let it bubble up."""
pass
# CLI main entry (cli/main.py)
def main() -> None:
"""Main entry point - ONLY catch at top level."""
try:
app()
except AppError as e:
console.print(f"[red]ERROR: {e}[/red]")
raise typer.Exit(code=1) from e
Key Rules:
except Exception: pass or except: return NoneCRITICAL RULE: 500 lines maximum per module
When module approaches 500 lines:
my_cli/
├── pyproject.toml # UV project config
├── .env.example # Template for .env
├── config.yaml # Application defaults
├── Makefile # Development tasks
└── src/
└── my_cli/
├── __init__.py
├── __main__.py # Entry point
├── cli/ # CLI commands
│ ├── __init__.py
│ └── commands.py # Typer app
├── core/ # Business logic
│ ├── __init__.py
│ ├── config.py # pydantic-settings
│ └── exceptions.py
├── db/ # Database layer
│ ├── __init__.py
│ ├── models.py # SQLAlchemy models
│ └── connection.py
└── services/ # External integrations
└── __init__.py
For complete architecture patterns, module organization, and size management: See @references/architecture.md
Pattern: Merge YAML defaults with .env overrides using pydantic-settings
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=False,
)
# Database
db_host: str = "localhost"
db_port: int = 5432
db_name: str
db_user: str
db_password: str # Must be in .env
# API
api_key: str
api_url: str = "https://api.example.com"
settings = Settings() # Fails if db_password or api_key missing
config.yaml (defaults):
db_host: localhost
db_port: 5432
api_url: https://api.example.com
.env (secrets):
DB_PASSWORD=secret
API_KEY=abc123
For complete configuration patterns, validation, and advanced examples: See @references/configuration.md
uv init my-cli
cd my-cli
uv add typer rich pydantic-settings pyyaml
uv add --dev ruff pyright pytest
# src/my_cli/__main__.py
import typer
from rich.console import Console
app = typer.Typer()
console = Console()
@app.command()
def hello(name: str) -> None:
"""Say hello."""
console.print(f"[green]Hello {name}![/green]")
if __name__ == "__main__":
app()
Copy configuration templates:
uv run python -m my_cli hello World
make lint # Run ruff check + format
make type # Run pyright
make test # Run pytest
make check # lint + type + test
make clean # Remove build artifacts
Always run before committing:
make check
Why: Catches type errors, style issues, and test failures early
Build integration: See patterns/make-integration.md - Integrate CLI with Makefiles Multi-method authentication: See patterns/multi-method-auth.md - Support OAuth, TBA, API keys PostgreSQL JSONB: See patterns/postgresql-jsonb.md - Flexible schema with JSONB columns Pydantic flexibility: See patterns/pydantic-flexible.md - Handle dynamic/unknown fields Schema resilience: See patterns/schema-resilience.md - Robust API schema handling Database migrations: See @reference/database-migrations.md - Alembic migration patterns
@app.command()
def sync(
config_file: Path = typer.Option("config.yaml"),
verbose: bool = typer.Option(False, "--verbose", "-v"),
) -> None:
"""Sync data from source."""
settings = Settings(_env_file=".env")
if verbose:
console.print(f"[yellow]Connecting to {settings.db_host}[/yellow]")
# Implementation
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, declarative_base
Base = declarative_base()
engine = create_engine(f"postgresql://{settings.db_user}:{settings.db_password}@{settings.db_host}/{settings.db_name}")
Session = sessionmaker(bind=engine)
from rich.progress import track
for item in track(items, description="Processing..."):
process(item)
class AppError(Exception):
"""Base for all app exceptions."""
pass
class ConfigurationError(AppError):
"""Config missing/invalid."""
pass
class DatabaseError(AppError):
"""DB operation failed."""
pass
class ExternalServiceError(AppError):
"""External API failed."""
pass
@app.command()
def process() -> None:
"""Process data."""
if not settings.api_key:
raise ConfigurationError("API_KEY not set in .env")
try:
response = api.fetch_data()
except Exception as e:
raise ExternalServiceError(f"API fetch failed: {e}") from e
# Process response (let exceptions bubble)
[tool.pyright]
typeCheckingMode = "strict"
pythonVersion = "3.12"
reportMissingTypeStubs = true
reportUnknownMemberType = true
from typing import Any
from collections.abc import Sequence
# Function signatures
def process_items(items: Sequence[dict[str, Any]]) -> list[str]:
return [item["name"] for item in items]
# Optional values
from typing import Optional
def get_value(key: str) -> Optional[str]:
return cache.get(key) # Returns str | None
# Type guards
from typing import TypeGuard
def is_string_list(val: list[Any]) -> TypeGuard[list[str]]:
return all(isinstance(x, str) for x in val)
tests/
├── conftest.py # Fixtures
├── test_cli.py # CLI command tests
├── test_config.py # Config loading tests
└── test_services.py # Service integration tests
from typer.testing import CliRunner
runner = CliRunner()
def test_hello_command():
result = runner.invoke(app, ["hello", "World"])
assert result.exit_code == 0
assert "Hello World" in result.stdout
uv build # Creates wheel + sdist
ls dist/ # my_cli-0.1.0-py3-none-any.whl
uv pip install dist/my_cli-0.1.0-py3-none-any.whl
my-cli hello World # Now available as command
For detailed guides: See the references/ directory
For project templates: See the templates/ directory
End of Skill
Activates when the user asks about AI prompts, needs prompt templates, wants to search for prompts, or mentions prompts.chat. Use for discovering, retrieving, and improving prompts.
Search, retrieve, and install Agent Skills from the prompts.chat registry using MCP tools. Use when the user asks to find skills, browse skill catalogs, install a skill for Claude, or extend Claude's capabilities with reusable AI agent components.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.