Skip to main content

Extending the CLI

Introduction

The Dhenara Agent DSL CLI is designed to be highly extensible, allowing developers to add custom commands that integrate seamlessly with the existing command structure. This guide will walk you through the process of extending the CLI with your own commands.

Extension Architecture

The DAD CLI follows a modular architecture where each set of related commands is encapsulated in a separate Python module. These modules are automatically discovered and loaded when the CLI starts.

To extend the CLI, you'll create a new Python module that follows the established command registration pattern.

Creating a Custom Command Module

Step 1: Create a New Module

Start by creating a new Python module in the dhenara/cli/commands/ directory. For example, to add custom analytics commands, you might create a file named analytics.py:

# dhenara/cli/commands/analytics.py
import click
from pathlib import Path
import asyncio

def register(cli):
"""Register the analytics commands with the CLI."""
cli.add_command(analytics)

@click.group("analytics")
def analytics():
"""Commands for analyzing agent performance and behavior."""
pass

Step 2: Add Commands to Your Group

Next, add specific commands to your command group:

@analytics.command("performance")
@click.argument("agent_id")
@click.option("--runs", default=5, help="Number of recent runs to analyze")
@click.option("--output", type=click.Path(), help="Path to save the report")
def analyze_performance(agent_id, runs, output):
"""Analyze the performance of an agent across recent runs."""
click.echo(f"Analyzing performance for agent '{agent_id}' across {runs} runs...")
# Implementation code here
if output:
click.echo(f"Report saved to {output}")
else:
click.echo("Performance analysis complete")

@analytics.command("patterns")
@click.argument("agent_id")
@click.option("--depth", default=3, help="Depth of pattern analysis")
def analyze_patterns(agent_id, depth):
"""Identify common patterns in agent behavior."""
click.echo(f"Analyzing behavior patterns for agent '{agent_id}' with depth {depth}...")
# Implementation code here

Step 3: Implement Async Commands (Optional)

For commands that need to perform asynchronous operations, use an async implementation pattern:

@analytics.command("realtime")
@click.argument("agent_id")
@click.option("--duration", default=60, help="Duration of monitoring in seconds")
def monitor_realtime(agent_id, duration):
"""Monitor agent performance in real-time."""
click.echo(f"Starting real-time monitoring for agent '{agent_id}'...")
asyncio.run(_monitor_realtime(agent_id, duration))

async def _monitor_realtime(agent_id, duration):
"""Async implementation of real-time monitoring."""
# Async implementation code here
for i in range(duration):
# Simulate monitoring
await asyncio.sleep(1)
click.echo(f"Monitoring... {i+1}/{duration} seconds")
click.echo("Monitoring complete")

Best Practices for CLI Extensions

1. Follow Command Structure Conventions

Maintain consistency with existing commands by following these conventions:

  • Use descriptive command and option names
  • Provide helpful docstrings that appear in help text
  • Group related commands under a common group
  • Use appropriate option types and defaults

2. Provide Rich User Feedback

Ensure your commands provide appropriate feedback to users:

@click.command()
def my_command():
with click.progressbar(range(100), label="Processing") as bar:
for i in bar:
# Do work
pass
click.echo(click.style("Command completed successfully!", fg="green"))

3. Handle Errors Gracefully

Implement error handling that provides clear messages to users:

import sys

@click.command()
def my_command():
try:
# Command implementation
result = perform_operation()
click.echo(f"Success: {result}")
except Exception as e:
click.echo(click.style(f"Error: {str(e)}", fg="red"), err=True)
sys.exit(1)

4. Support Verbose Mode

Implement verbose output for debugging:

@click.command()
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output")
def my_command(verbose):
if verbose:
click.echo("Detailed information: ...")
# Command implementation

5. Include Documentation

Ensure your custom commands are well-documented with examples:

@click.command()
@click.argument("input_file")
@click.option("--output", "-o", help="Output file path")
def process_file(input_file, output):
"""Process a file and generate output.

Example usage:
dhenara process-file data.json -o results.json
"""
# Command implementation

Integration with Run Context

Many DAD CLI commands need to interact with the run context. Here's how to access it in your custom commands:

from dhenara.agent.run import RunContext
from pathlib import Path

@click.command()
@click.argument("agent_id")
@click.option("--project-root", help="Project root directory")
def my_command(agent_id, project_root):
# Initialize run context
if not project_root:
project_root = find_project_root()

run_context = RunContext(
root_component_id=agent_id,
project_root=Path(project_root),
)

# Use run context
run_context.setup_run()
# Command implementation

Example: Creating a Visualization Command

Here's a complete example of adding a visualization command for agent runs:

# dhenara/cli/commands/visualize.py
import click
import asyncio
from pathlib import Path
from dhenara.agent.run import RunContext
from dhenara.agent.observability.visualization import generate_flow_diagram

def register(cli):
"""Register visualization commands."""
cli.add_command(visualize)

@click.group("visualize")
def visualize():
"""Commands for visualizing agent execution."""
pass

@visualize.command("flow")
@click.argument("run_id")
@click.option("--project-root", help="Project root directory")
@click.option("--output", "-o", help="Output file path")
@click.option("--format", "-f", type=click.Choice(["svg", "png", "pdf"]), default="svg",
help="Output format")
def visualize_flow(run_id, project_root, output, format):
"""Generate a visual diagram of a flow execution.

Example:
dhenara visualize flow run_20230517_123456 -o flow.svg
"""
asyncio.run(_visualize_flow(run_id, project_root, output, format))

async def _visualize_flow(run_id, project_root, output, format):
# Find project root if not specified
if not project_root:
project_root = find_project_root()
if not project_root:
click.echo("Error: Could not determine project root. Please specify with --project-root")
return

# Set default output path if not specified
if not output:
output = f"{run_id}_flow.{format}"

click.echo(f"Generating flow diagram for run {run_id}...")

try:
# Generate the diagram
run_context = RunContext(
root_component_id="visualization",
project_root=Path(project_root),
previous_run_id=run_id
)

diagram_path = await generate_flow_diagram(run_context, output, format)
click.echo(click.style(f"Flow diagram generated at: {diagram_path}", fg="green"))

except Exception as e:
click.echo(click.style(f"Error generating diagram: {str(e)}", fg="red"), err=True)

# Helper function to find project root
def find_project_root():
# Implementation to find project root by looking for .dhenara directory
current = Path.cwd()
while current != current.parent:
if (current / ".dhenara").exists():
return current
current = current.parent
return None

Publishing Your Extension

There are several ways to make your CLI extension available:

1. Include in Your Project

For project-specific commands, add your module to the project's command directory.

2. Installable Package

Create a separate Python package that developers can install alongside DAD:

# setup.py example for a CLI extension package
from setuptools import setup, find_packages

setup(
name="dhenara-cli-analytics",
version="0.1.0",
packages=find_packages(),
entry_points={
"dhenara.cli.plugins": [
"analytics=dhenara_cli_analytics:register",
],
},
install_requires=[
"dhenara",
"click",
# Other dependencies
],
)

3. Contribute to Core

For broadly useful commands, consider contributing them to the core DAD project by submitting a pull request.

Conclusion

Extending the DAD CLI allows you to customize and enhance the functionality to meet your specific needs. By following the established patterns and best practices, you can create seamless extensions that integrate with the existing command structure while adding powerful new capabilities.