|
| 1 | +import json |
| 2 | +import subprocess |
| 3 | +import sys |
| 4 | +from typing import Any, Dict |
| 5 | + |
| 6 | +import click |
| 7 | +from rich.console import Console |
| 8 | +from rich.table import Table |
| 9 | + |
| 10 | +from crewai.cli.command import BaseCommand, PlusAPIMixin |
| 11 | +from crewai.cli.utils import read_toml |
| 12 | + |
| 13 | +console = Console() |
| 14 | + |
| 15 | + |
| 16 | +class TriggersCommand(BaseCommand, PlusAPIMixin): |
| 17 | + """ |
| 18 | + A class to handle trigger-related operations for CrewAI projects. |
| 19 | + """ |
| 20 | + |
| 21 | + def __init__(self): |
| 22 | + BaseCommand.__init__(self) |
| 23 | + PlusAPIMixin.__init__(self, telemetry=self._telemetry) |
| 24 | + |
| 25 | + def list_triggers(self) -> None: |
| 26 | + """List all available triggers from integrations.""" |
| 27 | + try: |
| 28 | + console.print("[bold blue]Fetching available triggers...[/bold blue]") |
| 29 | + response = self.plus_api_client.get_triggers() |
| 30 | + self._validate_response(response) |
| 31 | + |
| 32 | + triggers_data = response.json() |
| 33 | + self._display_triggers(triggers_data) |
| 34 | + |
| 35 | + except Exception as e: |
| 36 | + console.print(f"[bold red]Error fetching triggers: {e}[/bold red]") |
| 37 | + raise SystemExit(1) |
| 38 | + |
| 39 | + def execute_with_trigger(self, trigger_path: str) -> None: |
| 40 | + """Execute crew with trigger payload.""" |
| 41 | + try: |
| 42 | + # Parse app_slug/trigger_slug |
| 43 | + if "/" not in trigger_path: |
| 44 | + console.print( |
| 45 | + "[bold red]Error: Trigger path must be in format 'app_slug/trigger_slug'[/bold red]" |
| 46 | + ) |
| 47 | + raise SystemExit(1) |
| 48 | + |
| 49 | + app_slug, trigger_slug = trigger_path.split("/", 1) |
| 50 | + |
| 51 | + console.print(f"[bold blue]Fetching trigger payload for {app_slug}/{trigger_slug}...[/bold blue]") |
| 52 | + response = self.plus_api_client.get_trigger_payload(app_slug, trigger_slug) |
| 53 | + |
| 54 | + if response.status_code == 404: |
| 55 | + error_data = response.json() |
| 56 | + console.print(f"[bold red]Error: {error_data.get('error', 'Trigger not found')}[/bold red]") |
| 57 | + raise SystemExit(1) |
| 58 | + |
| 59 | + self._validate_response(response) |
| 60 | + |
| 61 | + trigger_data = response.json() |
| 62 | + self._display_trigger_info(trigger_data) |
| 63 | + |
| 64 | + # Ask for confirmation |
| 65 | + if not click.confirm("Do you want to run the crew with this trigger payload?"): |
| 66 | + console.print("[yellow]Operation cancelled.[/yellow]") |
| 67 | + return |
| 68 | + |
| 69 | + # Run crew with trigger payload |
| 70 | + self._run_crew_with_payload(trigger_data.get("sample_payload", {})) |
| 71 | + |
| 72 | + except Exception as e: |
| 73 | + console.print(f"[bold red]Error executing crew with trigger: {e}[/bold red]") |
| 74 | + raise SystemExit(1) |
| 75 | + |
| 76 | + def _display_triggers(self, triggers_data: Dict[str, Any]) -> None: |
| 77 | + """Display triggers in a formatted table.""" |
| 78 | + apps = triggers_data.get("apps", []) |
| 79 | + |
| 80 | + if not apps: |
| 81 | + console.print("[yellow]No triggers found.[/yellow]") |
| 82 | + return |
| 83 | + |
| 84 | + for app in apps: |
| 85 | + app_name = app.get("name", "Unknown App") |
| 86 | + app_slug = app.get("slug", "unknown") |
| 87 | + is_connected = app.get("is_connected", False) |
| 88 | + connection_status = "[green]✓ Connected[/green]" if is_connected else "[red]✗ Not Connected[/red]" |
| 89 | + |
| 90 | + console.print(f"\n[bold cyan]{app_name}[/bold cyan] ({app_slug}) - {connection_status}") |
| 91 | + console.print(f"[dim]{app.get('description', 'No description available')}[/dim]") |
| 92 | + |
| 93 | + triggers = app.get("triggers", []) |
| 94 | + if triggers: |
| 95 | + table = Table(show_header=True, header_style="bold magenta") |
| 96 | + table.add_column("Trigger Path", style="cyan") |
| 97 | + table.add_column("Name", style="green") |
| 98 | + table.add_column("Description", style="dim") |
| 99 | + |
| 100 | + for trigger in triggers: |
| 101 | + trigger_path = f"{app_slug}/{trigger.get('slug', 'unknown')}" |
| 102 | + table.add_row( |
| 103 | + trigger_path, |
| 104 | + trigger.get("name", "Unknown"), |
| 105 | + trigger.get("description", "No description") |
| 106 | + ) |
| 107 | + |
| 108 | + console.print(table) |
| 109 | + else: |
| 110 | + console.print("[dim] No triggers available[/dim]") |
| 111 | + |
| 112 | + def _display_trigger_info(self, trigger_data: Dict[str, Any]) -> None: |
| 113 | + """Display trigger information before execution.""" |
| 114 | + console.print("\n[bold green]Trigger Information:[/bold green]") |
| 115 | + console.print(f"App: [cyan]{trigger_data.get('app', 'Unknown')}[/cyan]") |
| 116 | + console.print(f"Trigger: [cyan]{trigger_data.get('trigger', 'Unknown')}[/cyan]") |
| 117 | + console.print(f"Name: [green]{trigger_data.get('trigger_name', 'Unknown')}[/green]") |
| 118 | + console.print(f"Description: [dim]{trigger_data.get('description', 'No description')}[/dim]") |
| 119 | + |
| 120 | + sample_payload = trigger_data.get("sample_payload", {}) |
| 121 | + if sample_payload: |
| 122 | + console.print("\n[bold yellow]Sample Payload:[/bold yellow]") |
| 123 | + console.print(json.dumps(sample_payload, indent=2)) |
| 124 | + |
| 125 | + console.print("\n[bold cyan]Usage in Crew:[/bold cyan]") |
| 126 | + console.print("The payload will be available in your crew inputs as:") |
| 127 | + console.print("- [green]crewai_trigger_payload[/green]: Full payload object") |
| 128 | + console.print("\nExample: Access payload data in your YAML configs using {crewai_trigger_payload}") |
| 129 | + |
| 130 | + def _run_crew_with_payload(self, payload: Dict[str, Any]) -> None: |
| 131 | + """Run the crew with the trigger payload using the run_with_trigger method.""" |
| 132 | + try: |
| 133 | + # Check if we're in a crew project |
| 134 | + pyproject_data = read_toml() |
| 135 | + is_flow = pyproject_data.get("tool", {}).get("crewai", {}).get("type") == "flow" |
| 136 | + |
| 137 | + console.print(f"[bold blue]Running {'Flow' if is_flow else 'Crew'} with trigger payload...[/bold blue]") |
| 138 | + |
| 139 | + # Execute using the run_with_trigger method with JSON payload as argument |
| 140 | + result = subprocess.run( |
| 141 | + ["uv", "run", "run_with_trigger", json.dumps(payload)], |
| 142 | + capture_output=False, |
| 143 | + text=True, |
| 144 | + check=True |
| 145 | + ) |
| 146 | + |
| 147 | + console.print("[bold green]Execution completed successfully![/bold green]") |
| 148 | + |
| 149 | + except subprocess.CalledProcessError as e: |
| 150 | + console.print(f"[bold red]Execution failed with exit code {e.returncode}[/bold red]") |
| 151 | + raise SystemExit(e.returncode) |
| 152 | + except FileNotFoundError: |
| 153 | + console.print( |
| 154 | + "[bold red]Error: Not in a CrewAI project directory. " |
| 155 | + "Please run this command from a CrewAI project root.[/bold red]" |
| 156 | + ) |
| 157 | + raise SystemExit(1) |
| 158 | + except Exception as e: |
| 159 | + console.print(f"[bold red]Unexpected error running project: {e}[/bold red]") |
| 160 | + raise SystemExit(1) |
0 commit comments