diff --git a/src/cleveragents/cli/commands/plan.py b/src/cleveragents/cli/commands/plan.py index 23b371378..034c33a2a 100644 --- a/src/cleveragents/cli/commands/plan.py +++ b/src/cleveragents/cli/commands/plan.py @@ -25,6 +25,7 @@ import re import time import warnings from contextlib import suppress +from datetime import datetime from pathlib import Path from typing import TYPE_CHECKING, Annotated, Any @@ -2343,43 +2344,96 @@ def lifecycle_list_plans( console.print(format_output(data, fmt)) return - # Display plans table - table = Table(title=f"V3 Lifecycle Plans ({len(plans)} total)") + # Display plans table with spec-required columns + table = Table(title="Plans") table.add_column("ID", style="cyan") - table.add_column("Name", style="white") - table.add_column("Action", style="blue") table.add_column("Phase", style="yellow") table.add_column("State", style="magenta") - table.add_column("Profile", style="blue") - table.add_column("Invariants", style="dim") - table.add_column("Projects", style="green") - table.add_column("Created", style="dim") + table.add_column("Action", style="blue") + table.add_column("Project", style="green") + table.add_column("Elapsed", style="dim") for plan in plans: + # Get first project or "(none)" link_names = [link.project_name for link in plan.project_links] - projects = ", ".join(link_names[:2]) - if len(link_names) > 2: - projects += f" +{len(link_names) - 2} more" + project_display = link_names[0] if link_names else "(none)" - profile = ( - plan.automation_profile.profile_name if plan.automation_profile else "" - ) - inv_count = str(len(plan.invariants)) if plan.invariants else "" + # Calculate elapsed time + created = plan.timestamps.created_at + now = datetime.now(created.tzinfo) if created.tzinfo else datetime.now() + elapsed = now - created + hours = int(elapsed.total_seconds() // 3600) + minutes = int((elapsed.total_seconds() % 3600) // 60) + seconds = int(elapsed.total_seconds() % 60) + elapsed_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}" table.add_row( - plan.identity.plan_id[:8] + "...", - str(plan.namespaced_name), - plan.action_name, + plan.identity.plan_id[:8], plan.phase.value, plan.state.value if plan.state else "", - profile, - inv_count, - projects or "(none)", - plan.timestamps.created_at.strftime("%Y-%m-%d %H:%M"), + plan.action_name, + project_display, + elapsed_str, ) console.print(table) + # Display Filters panel (only if any filter is active) + active_filters = [] + if phase_filter: + active_filters.append(f"[yellow]Phase:[/yellow] {phase_filter}") + else: + active_filters.append("[yellow]Phase:[/yellow] (any)") + + if state_filter: + active_filters.append(f"[yellow]State:[/yellow] {state_filter.value}") + else: + active_filters.append("[yellow]State:[/yellow] (any)") + + if project_id: + active_filters.append(f"[yellow]Project:[/yellow] {project_id}") + else: + active_filters.append("[yellow]Project:[/yellow] (any)") + + if action_filter: + active_filters.append(f"[yellow]Action:[/yellow] {action_filter}") + else: + active_filters.append("[yellow]Action:[/yellow] (any)") + + # Only show Filters panel if at least one filter is active + if phase_filter or state_filter or project_id or action_filter: + filters_text = "\n".join(active_filters) + filters_panel = Panel(filters_text, title="Filters", border_style="dim") + console.print(filters_panel) + + # Display Summary panel + total_plans = len(plans) + processing_count = sum( + 1 for p in plans if p.processing_state.value == "processing" + ) + completed_count = sum( + 1 for p in plans if p.processing_state.value == "complete" + ) + errored_count = sum( + 1 for p in plans if p.processing_state.value == "errored" + ) + + summary_text = ( + f"[yellow bold]Total:[/yellow bold] {total_plans}\n" + f"[blue bold]Processing:[/blue bold] {processing_count}\n" + f"[green bold]Completed:[/green bold] {completed_count}\n" + f"[red bold]Errored:[/red bold] {errored_count}" + ) + summary_panel = Panel(summary_text, title="Summary", border_style="dim") + console.print(summary_panel) + + # Print success message + plan_word = "plan" if total_plans == 1 else "plans" + console.print( + f"[green bold]✓ OK[/green bold] {total_plans} {plan_word} listed" + ) + + except CleverAgentsError as e: console.print(f"[red]Error:[/red] {e.message}") raise typer.Abort() from e