Ml cli tools
Skill nishide-dev/claude-code-ml-research/skills/ml-cli-tools
Claude Code Plugin for ML Research and Development.
npx -y skills add nishide-dev/claude-code-ml-research --skill ml-cli-toolsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Building professional CLIs with Typer and Rich - type-safe argument parsing, progress bars, model visualization, Hydra integration, RichHandler logging, and multi-process handling for ML workflows
SKILL.md
9.9 KB, as published. Nobody here has run it
ML CLI Tools with Typer and Rich
Building professional command-line interfaces for machine learning workflows using Typer and Rich.
Overview
This skill covers modern CLI development for ML projects using:
- Typer: Type-safe CLI framework leveraging Python type hints
- Rich: Terminal UI library for beautiful progress bars, tables, and formatting
- Integration: Combining with Hydra, PyTorch Lightning, and logging systems
Why Typer + Rich for ML?
Traditional argparse becomes unmaintainable for complex ML workflows with numerous hyperparameters. Modern alternatives provide:
Typer Benefits
- Type Safety: Automatic validation based on type hints
- Less Boilerplate: No manual parser configuration
- Auto-completion: Shell completion for Bash, Zsh, Fish, PowerShell
- Self-documenting: Help text generated from docstrings and type hints
- Subcommands: Easy organization of complex workflows
Rich Benefits
- Progress Visualization: Real-time training progress with custom metrics
- Structured Output: Tables, trees, panels for model architectures
- Logging Integration: Prevents progress bar corruption from log messages
- User Experience: Professional terminal output improves usability
Installation
# Using UV
uv add "typer[all]" rich hydra-core pydantic
# Using Pixi
pixi add --pypi typer[all] rich hydra-core pydantic
Typer Fundamentals
Basic CLI with Type Hints
import typer
from typing_extensions import Annotated
from pathlib import Path
app = typer.Typer()
@app.command()
def train(
data_path: Annotated[Path, typer.Argument(
exists=True,
dir_okay=True,
help="Path to training data directory"
)],
epochs: Annotated[int, typer.Option(
min=1,
max=1000,
help="Number of training epochs"
)] = 50,
lr: Annotated[float, typer.Option(
"--learning-rate",
"-lr",
help="Learning rate"
)] = 1e-3,
):
"""
Train the ML model with specified parameters.
Example:
python main.py train ./data --epochs 100 --learning-rate 0.001
"""
typer.echo(f"Training with {epochs} epochs at LR {lr}")
if __name__ == "__main__":
app()
What Typer provides automatically:
- Type validation:
epochsmust be int between 1-1000 - Path validation:
data_pathmust exist and be a directory - Help generation:
--helpflag shows formatted documentation - Shell completion: Tab completion in terminal
Using Enum for Choices
Restrict inputs to valid options using Enum:
from enum import Enum
class ModelArchitecture(str, Enum):
"""Supported model architectures."""
resnet50 = "resnet50"
vit_b_16 = "vit_b_16"
efficientnet_b0 = "efficientnet_b0"
@app.command()
def train(
model: Annotated[ModelArchitecture, typer.Option(
case_sensitive=False,
help="Model architecture to train"
)] = ModelArchitecture.resnet50,
):
"""Train with specified model."""
typer.echo(f"Training {model.value}")
Benefits:
- IDE autocomplete for valid values
- Type-safe: No typos like "renet50"
- Help text shows all valid options
Subcommands for Complex Workflows
# main.py
import typer
from ml_cli.commands import data, train, evaluate
app = typer.Typer(name="ml-cli", help="ML Research CLI Tool")
app.add_typer(data.app, name="data", help="Data preprocessing")
app.add_typer(train.app, name="train", help="Model training")
app.add_typer(evaluate.app, name="eval", help="Model evaluation")
if __name__ == "__main__":
app()
Usage:
python main.py data preprocess
python main.py train start --epochs 100
python main.py eval metrics --checkpoint best.ckpt
Rich: Beautiful Terminal Output
Progress Bars for Training
from rich.progress import Progress
with Progress() as progress:
task = progress.add_task("[cyan]Training...", total=100)
for epoch in range(100):
# Training logic
progress.update(task, advance=1)
Custom Progress with Training Metrics
from rich.progress import (
Progress,
SpinnerColumn,
TextColumn,
BarColumn,
TaskProgressColumn,
TimeRemainingColumn,
)
class MetricsColumn(TextColumn):
"""Custom column to display training metrics."""
def render(self, task):
loss = task.fields.get("loss", 0.0)
acc = task.fields.get("acc", 0.0)
return f"Loss: {loss:.4f} | Acc: {acc:.2f}%"
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
BarColumn(),
TaskProgressColumn(),
TimeRemainingColumn(),
MetricsColumn(),
) as progress:
task = progress.add_task("[cyan]Training...", total=num_epochs)
for epoch in range(num_epochs):
loss, acc = train_epoch(model, dataloader)
progress.update(
task,
advance=1,
loss=loss,
acc=acc,
)
Tables for Results
from rich.table import Table
from rich.console import Console
console = Console()
def show_results(results: list[dict]):
"""Display experiment results in a table."""
table = Table(title="Experiment Results")
table.add_column("Experiment", style="cyan")
table.add_column("Accuracy", justify="right")
table.add_column("Loss", justify="right")
for result in results:
table.add_row(
result["name"],
f"{result['accuracy']:.2f}%",
f"{result['loss']:.4f}",
)
console.print(table)
Model Architecture Tree
from rich.tree import Tree
import torch.nn as nn
def build_model_tree(module: nn.Module, tree: Tree | None = None) -> Tree:
"""Recursively build a Rich tree from PyTorch module."""
if tree is None:
tree = Tree("[bold blue]Model[/]")
for name, child in module.named_children():
class_name = child.__class__.__name__
branch = tree.add(f"[green]{name}[/]: [yellow]{class_name}[/]")
build_model_tree(child, branch)
return tree
# Usage
model = torchvision.models.resnet18()
tree = build_model_tree(model)
console.print(tree)
Logging Integration
RichHandler for Clean Logs
Use Rich's logging handler to prevent progress bar corruption:
import logging
from rich.logging import RichHandler
from rich.console import Console
console = Console()
logging.basicConfig(
level="INFO",
format="%(message)s",
handlers=[RichHandler(console=console)],
)
logger = logging.getLogger("ml_cli")
# Logs work cleanly with progress bars
with Progress(console=console) as progress:
task = progress.add_task("Training", total=100)
for i in range(100):
logger.info(f"Epoch {i+1} completed") # Won't corrupt progress
progress.update(task, advance=1)
Project Structure
Recommended structure for ML CLIs:
ml-cli-tool/
├── pyproject.toml # Dependencies and metadata
├── src/
│ └── ml_cli/
│ ├── __init__.py
│ ├── main.py # CLI entry point
│ ├── commands/ # Subcommands
│ │ ├── train.py
│ │ ├── evaluate.py
│ │ └── data.py
│ ├── core/ # Core logic
│ └── utils/ # Rich utilities
├── configs/ # Hydra configs
└── tests/
Complete Example
import typer
from typing_extensions import Annotated
from pathlib import Path
from rich.progress import Progress, SpinnerColumn, BarColumn
from rich.console import Console
import logging
from rich.logging import RichHandler
app = typer.Typer()
console = Console()
logging.basicConfig(
level="INFO",
format="%(message)s",
handlers=[RichHandler(console=console)],
)
logger = logging.getLogger(__name__)
@app.command()
def train(
data_path: Annotated[Path, typer.Argument(exists=True)],
epochs: Annotated[int, typer.Option(min=1)] = 50,
lr: Annotated[float, typer.Option("--learning-rate")] = 1e-3,
):
"""
Train ML model.
Example:
python main.py train ./data --epochs 100 --learning-rate 0.001
"""
logger.info(f"Starting training with LR={lr}")
with Progress(
SpinnerColumn(),
BarColumn(),
console=console,
) as progress:
task = progress.add_task("[cyan]Training...", total=epochs)
for epoch in range(epochs):
# Training logic
loss = train_epoch(data_path, lr)
logger.info(f"Epoch {epoch+1}: Loss={loss:.4f}")
progress.update(task, advance=1)
console.print("[bold green]Training completed![/]")
if __name__ == "__main__":
app()
Advanced Topics
For detailed guides on advanced features, see reference/advanced-examples.md:
- Hydra Integration: Combining Typer with Hydra Compose API
- Multi-Process Logging: QueueHandler pattern for DataLoader workers
- Complete Examples: Training scripts, data preprocessing, model evaluation
- Best Practices: CLI design, error handling, testing
Summary
Key Takeaways:
- Typer + Rich = professional ML CLIs with minimal boilerplate
- Type hints drive automatic validation and help generation
- Rich provides beautiful progress bars and structured output
- RichHandler prevents logging from corrupting progress bars
When to Use:
- ✅ Training scripts with many hyperparameters
- ✅ Data preprocessing pipelines
- ✅ Model evaluation and comparison tools
- ✅ Experiment management CLIs
- ✅ Any ML workflow that benefits from rich feedback
Resources:
- Typer: https://typer.tiangolo.com/
- Rich: https://rich.readthedocs.io/
- Hydra: https://hydra.cc/