Skip to main content
Glama
cli.pyโ€ข12.1 kB
"""CLI for executing workflows.""" import asyncio import click import sys from pathlib import Path from rich.console import Console from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn from rich.table import Table from rich.panel import Panel from rich.syntax import Syntax from .workflow import WorkflowEngine, WorkflowDefinition from .orchestrator import OrchestratorRegistry from .config import DelegationConfig from .logging_config import setup_logging from .agent_discovery import AgentDiscovery console = Console() @click.group() @click.option('--verbose', '-v', is_flag=True, help='Verbose output') @click.option('--config', '-c', type=click.Path(exists=True), help='Config file path') @click.pass_context def cli(ctx, verbose, config): """Delegation MCP - Multi-Agent Workflow Orchestration.""" ctx.ensure_object(dict) ctx.obj['verbose'] = verbose ctx.obj['config_path'] = Path(config) if config else Path("config/delegation_rules.yaml") # Setup logging import logging setup_logging(level=logging.DEBUG if verbose else logging.INFO, verbose=verbose) @cli.command('list') @click.pass_context def list_workflows(ctx): """List all available workflows.""" workflows_dir = Path("workflows") if not workflows_dir.exists(): console.print("[red]โŒ Workflows directory not found[/red]") sys.exit(1) console.print("\n[bold cyan]๐Ÿ“š Available Workflows[/bold cyan]\n") table = Table(show_header=True, header_style="bold magenta") table.add_column("Name", style="cyan", no_wrap=True) table.add_column("Steps", justify="center", style="yellow") table.add_column("Agents", style="green") table.add_column("Category", style="blue") table.add_column("Difficulty", style="magenta") table.add_column("Duration", justify="right", style="yellow") for workflow_file in sorted(workflows_dir.glob("*.yaml")): try: workflow = WorkflowDefinition.from_yaml(workflow_file) agents = ", ".join(sorted(set(step.agent for step in workflow.steps))) category = workflow.metadata.get("category", "general") difficulty = workflow.metadata.get("difficulty", "intermediate") duration = workflow.metadata.get("estimated_duration", 0) table.add_row( workflow.name, str(len(workflow.steps)), agents, category, difficulty, f"{duration // 60}min" ) except Exception as e: console.print(f"[red]โš ๏ธ Failed to load {workflow_file.name}: {e}[/red]") console.print(table) console.print() @cli.command() @click.argument('workflow_name') @click.pass_context def show(ctx, workflow_name): """Show workflow details.""" workflows_dir = Path("workflows") workflow = _find_workflow(workflow_name, workflows_dir) if not workflow: sys.exit(1) # Display workflow info console.print() console.print(Panel( f"[bold]{workflow.name}[/bold]\n\n{workflow.description}", title="๐Ÿ“‹ Workflow Details", border_style="cyan" )) # Display steps console.print("\n[bold cyan]๐Ÿ”„ Workflow Steps[/bold cyan]\n") for i, step in enumerate(workflow.steps, 1): console.print(f"[bold yellow]Step {i}:[/bold yellow] {step.id}") console.print(f" [cyan]Agent:[/cyan] {step.agent}") console.print(f" [green]Task:[/green] {step.task}") if step.output: console.print(f" [blue]Output:[/blue] {step.output}") if step.condition: console.print(f" [magenta]Condition:[/magenta] {step.condition}") console.print() # Display metadata if workflow.metadata: console.print("[bold cyan]๐Ÿ“Š Metadata[/bold cyan]") for key, value in workflow.metadata.items(): console.print(f" [cyan]{key}:[/cyan] {value}") console.print() @cli.command() @click.argument('workflow_name') @click.option('--context', '-c', multiple=True, help='Context variables (key=value)') @click.option('--dry-run', is_flag=True, help='Show what would be executed without running') @click.pass_context def execute(ctx, workflow_name, context, dry_run): """Execute a workflow.""" workflows_dir = Path("workflows") workflow = _find_workflow(workflow_name, workflows_dir) if not workflow: sys.exit(1) # Parse context context_dict = {} for ctx_item in context: if '=' in ctx_item: key, value = ctx_item.split('=', 1) context_dict[key.strip()] = value.strip() if dry_run: console.print("\n[bold yellow]๐Ÿ” Dry Run Mode[/bold yellow]\n") console.print(f"[cyan]Workflow:[/cyan] {workflow.name}") console.print(f"[cyan]Steps:[/cyan] {len(workflow.steps)}") console.print(f"[cyan]Context:[/cyan] {context_dict}") console.print("\n[bold green]Would execute:[/bold green]\n") for i, step in enumerate(workflow.steps, 1): console.print(f" {i}. [{step.agent}] {step.task}") console.print() return # Execute workflow asyncio.run(_execute_workflow(ctx, workflow, context_dict)) async def _execute_workflow(ctx, workflow, context): """Execute workflow with progress display.""" # Setup config_path = ctx.obj['config_path'] if config_path.exists(): config = DelegationConfig.from_yaml(config_path) else: config = DelegationConfig(orchestrator="claude") registry = OrchestratorRegistry() for name, orch_config in config.orchestrators.items(): registry.register(orch_config) engine = WorkflowEngine(registry) # Display header console.print() console.print(Panel( f"[bold]{workflow.name}[/bold]\n{workflow.description}", title="๐Ÿš€ Executing Workflow", border_style="green" )) console.print() # Execute with progress with Progress( SpinnerColumn(), TextColumn("[progress.description]{task.description}"), BarColumn(), console=console, ) as progress: task = progress.add_task( f"[cyan]Executing {len(workflow.steps)} steps...", total=len(workflow.steps) ) # We need to modify the engine to support progress callbacks # For now, just execute result = await engine.execute(workflow, initial_context=context) progress.update(task, completed=len(workflow.steps)) # Display results console.print() if result.success: console.print(Panel( f"[bold green]โœ… Workflow Completed Successfully[/bold green]\n\n" f"Steps: {result.steps_completed}/{result.total_steps}\n" f"Duration: {result.duration:.2f}s", border_style="green" )) else: console.print(Panel( f"[bold red]โŒ Workflow Failed[/bold red]\n\n" f"Steps: {result.steps_completed}/{result.total_steps}\n" f"Duration: {result.duration:.2f}s", border_style="red" )) # Display outputs if result.outputs: console.print("\n[bold cyan]๐Ÿ“ค Outputs[/bold cyan]\n") for key, value in result.outputs.items(): console.print(f"[bold]{key}:[/bold]") # Truncate long outputs display_value = str(value) if len(display_value) > 1000: display_value = display_value[:1000] + "\n... (truncated)" console.print(Panel(display_value, border_style="blue")) # Display errors if result.errors: console.print("\n[bold red]โŒ Errors[/bold red]\n") for error in result.errors: console.print(f" โ€ข {error}") console.print() @cli.command() @click.argument('workflow_file', type=click.Path(exists=True)) @click.pass_context def validate(ctx, workflow_file): """Validate a workflow file.""" try: workflow = WorkflowDefinition.from_yaml(Path(workflow_file)) console.print(f"[green]โœ… Workflow '{workflow.name}' is valid[/green]") console.print(f" Steps: {len(workflow.steps)}") console.print(f" Agents: {', '.join(set(step.agent for step in workflow.steps))}") except Exception as e: console.print(f"[red]โŒ Invalid workflow: {e}[/red]") sys.exit(1) @cli.command('discover-agents') @click.option('--force-refresh', '-f', is_flag=True, help='Force re-discovery even if cache exists') @click.option('--json', 'output_json', is_flag=True, help='Output results as JSON') @click.pass_context def discover_agents_cmd(ctx, force_refresh, output_json): """Discover available CLI agents on the system.""" asyncio.run(_discover_agents(force_refresh, output_json)) async def _discover_agents(force_refresh, output_json): """Execute agent discovery.""" console.print() console.print("[bold cyan]Discovering CLI Agents...[/bold cyan]\n") # Create discovery instance and run discovery discovery = AgentDiscovery() console.print("[cyan]Scanning system PATH...[/cyan]") discovered = await discovery.discover_agents(force_refresh=force_refresh) console.print() summary = discovery.get_discovery_summary() if output_json: # Output as JSON import json console.print(json.dumps(summary, indent=2)) return # Display results in table format console.print() console.print(Panel( f"[bold]Agent Discovery Results[/bold]\n\n" f"Total agents scanned: {summary['total_agents']}\n" f"Available: [green]{summary['available']}[/green]\n" f"Unavailable: [red]{summary['unavailable']}[/red]", title="Summary", border_style="cyan" )) console.print() # Display available agents if summary['available_agents']: console.print("[bold green]Available Agents[/bold green]\n") table = Table(show_header=True, header_style="bold magenta") table.add_column("Agent", style="cyan", no_wrap=True) table.add_column("Version", style="yellow") table.add_column("Path", style="green") for agent in summary['available_agents']: table.add_row( agent['name'], agent['version'][:50] if agent['version'] else 'Unknown', agent['path'] ) console.print(table) console.print() # Display unavailable agents if summary['unavailable_agents']: console.print("[bold red]Unavailable Agents[/bold red]\n") for agent in summary['unavailable_agents']: console.print(f"[red]x[/red] [bold]{agent['name']}[/bold]") console.print(f" {agent['error']}\n") console.print() console.print("[dim]Tip: Use --force-refresh to re-scan, or --json for machine-readable output[/dim]") console.print() def _find_workflow(name, workflows_dir): """Find workflow by name or filename.""" if not workflows_dir.exists(): console.print("[red]โŒ Workflows directory not found[/red]") return None # Try exact name match for workflow_file in workflows_dir.glob("*.yaml"): try: workflow = WorkflowDefinition.from_yaml(workflow_file) if workflow.name.lower() == name.lower(): return workflow except Exception: continue # Try filename match workflow_path = workflows_dir / f"{name}.yaml" if workflow_path.exists(): try: return WorkflowDefinition.from_yaml(workflow_path) except Exception as e: console.print(f"[red]โŒ Failed to load workflow: {e}[/red]") return None console.print(f"[red]โŒ Workflow '{name}' not found[/red]") console.print("\nTry: [cyan]delegation-workflow list[/cyan] to see available workflows") return None def main(): """Entry point.""" cli(obj={}) if __name__ == '__main__': main()

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/carlosduplar/multi-agent-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server