from __future__ import annotations from importlib.metadata import version as get_version import os import subprocess from typing import TYPE_CHECKING, Any import click from crewai_core.token_manager import TokenManager from crewai_cli.config import Settings from crewai_cli.user_data import ( _load_user_data, is_tracing_enabled, update_user_data, ) from crewai_cli.utils import ( build_env_with_all_tool_credentials, enable_prompt_line_editing, is_dmn_mode_enabled, read_toml, ) def train_crew(*args: Any, **kwargs: Any) -> Any: from crewai_cli.train_crew import train_crew as _train_crew return _train_crew(*args, **kwargs) def evaluate_crew(*args: Any, **kwargs: Any) -> Any: from crewai_cli.evaluate_crew import evaluate_crew as _evaluate_crew return _evaluate_crew(*args, **kwargs) def replay_task_command(*args: Any, **kwargs: Any) -> Any: from crewai_cli.replay_from_task import replay_task_command as _replay_task_command return _replay_task_command(*args, **kwargs) def run_crew(*args: Any, **kwargs: Any) -> Any: from crewai_cli.run_crew import run_crew as _run_crew return _run_crew(*args, **kwargs) if TYPE_CHECKING: # mypy sees the real classes; at runtime the shims below defer the # heavy imports until a command actually instantiates them. from crewai_cli.authentication.main import AuthenticationCommand from crewai_cli.deploy.main import DeployCommand from crewai_cli.organization.main import OrganizationCommand from crewai_cli.remote_template.main import TemplateCommand else: class AuthenticationCommand: def __new__(cls, *args: Any, **kwargs: Any) -> Any: from crewai_cli.authentication.main import ( AuthenticationCommand as _AuthenticationCommand, ) return _AuthenticationCommand(*args, **kwargs) class DeployCommand: def __new__(cls, *args: Any, **kwargs: Any) -> Any: from crewai_cli.deploy.main import DeployCommand as _DeployCommand return _DeployCommand(*args, **kwargs) class TemplateCommand: def __new__(cls, *args: Any, **kwargs: Any) -> Any: from crewai_cli.remote_template.main import ( TemplateCommand as _TemplateCommand, ) return _TemplateCommand(*args, **kwargs) class OrganizationCommand: def __new__(cls, *args: Any, **kwargs: Any) -> Any: from crewai_cli.organization.main import ( OrganizationCommand as _OrganizationCommand, ) return _OrganizationCommand(*args, **kwargs) def _get_cli_version() -> str: """Return the best available version string for the CLI.""" try: return get_version("crewai") except Exception: # noqa: S110 pass try: return get_version("crewai-cli") except Exception: return "unknown" @click.group() @click.version_option(_get_cli_version()) def crewai() -> None: """Top-level command group for crewai.""" @crewai.command( name="uv", context_settings={"ignore_unknown_options": True}, ) @click.argument("uv_args", nargs=-1, type=click.UNPROCESSED) def uv(uv_args: tuple[str, ...]) -> None: """A wrapper around uv commands that adds custom tool authentication through env vars.""" try: read_toml() except FileNotFoundError as e: raise SystemExit( "Error. A valid pyproject.toml file is required. Check that a valid pyproject.toml file exists in the current directory." ) from e except Exception as e: raise SystemExit(f"Error: {e}") from e env = build_env_with_all_tool_credentials() try: subprocess.run( # noqa: S603 ["uv", *uv_args], # noqa: S607 capture_output=False, env=env, text=True, check=True, ) except subprocess.CalledProcessError as e: click.secho(f"uv command failed with exit code {e.returncode}", fg="red") raise SystemExit(e.returncode) from e @crewai.command() @click.argument( "type", required=False, default=None, type=click.Choice(["crew", "flow"]) ) @click.argument("name", required=False, default=None) @click.option("--provider", type=str, help="The provider to use for the crew") @click.option("--skip_provider", is_flag=True, help="Skip provider validation") @click.option( "--classic", is_flag=True, help="Use classic Python/YAML project structure instead of JSON", ) @click.option( "--declarative", is_flag=True, help="Create a declarative Flow project instead of a Python Flow project", ) def create( type: str | None, name: str | None, provider: str | None, skip_provider: bool = False, classic: bool = False, declarative: bool = False, ) -> None: """Create a new crew, or flow.""" dmn_mode = is_dmn_mode_enabled() if not type: if dmn_mode: raise click.UsageError( "TYPE is required when CREWAI_DMN is set. " "Use `crewai create crew ` or `crewai create flow `." ) from crewai_cli.tui_picker import pick options = [ ("crew", "A team of AI agents working together"), ( "flow", "A deterministic workflow with full control over agents and crews", ), ] type = pick("What would you like to create?", options) if type is None: raise SystemExit(0) click.echo() if not name: if dmn_mode: raise click.UsageError("NAME is required when CREWAI_DMN is set.") enable_prompt_line_editing() name = click.prompt( click.style(f" Name of your {type}", fg="cyan", bold=True), prompt_suffix=click.style(" › ", fg="bright_white"), # noqa: RUF001 ) if dmn_mode: skip_provider = True if type == "crew": if declarative: raise click.UsageError("--declarative can only be used with flow projects") if classic: from crewai_cli.create_crew import create_crew create_crew(name, provider, skip_provider) else: from crewai_cli.create_json_crew import create_json_crew create_json_crew(name, provider, skip_provider) elif type == "flow": from crewai_cli.create_flow import create_flow create_flow(name, declarative=declarative) else: click.secho("Error: Invalid type. Must be 'crew' or 'flow'.", fg="red") @crewai.command() @click.option( "--tools", is_flag=True, help="Show the installed version of crewai tools" ) def version(tools: bool) -> None: """Show the installed version of crewai.""" try: crewai_version = get_version("crewai") except Exception: crewai_version = "unknown version" click.echo(f"crewai version: {crewai_version}") if tools: try: tools_version = get_version("crewai-tools") click.echo(f"crewai tools version: {tools_version}") except Exception: click.echo("crewai tools not installed") @crewai.command() @click.option( "-n", "--n_iterations", type=int, default=5, help="Number of iterations to train the crew", ) @click.option( "-f", "--filename", type=str, default="trained_agents_data.pkl", help="Path to a custom file for training", ) def train(n_iterations: int, filename: str) -> None: """Train the crew.""" click.echo(f"Training the Crew for {n_iterations} iterations") train_crew(n_iterations, filename) @crewai.command() @click.option( "-t", "--task_id", type=str, help="Replay the crew from this task ID, including all subsequent tasks.", ) @click.option( "-f", "--filename", "trained_agents_file", type=str, default=None, help=( "Path to a trained-agents pickle (produced by `crewai train -f`). " "When set, agents load suggestions from this file instead of the " "default trained_agents_data.pkl. Equivalent to setting " "CREWAI_TRAINED_AGENTS_FILE." ), ) def replay(task_id: str, trained_agents_file: str | None) -> None: """Replay the crew execution from a specific task. Args: task_id: The ID of the task to replay from. trained_agents_file: Optional trained-agents pickle path. """ try: click.echo(f"Replaying the crew from task {task_id}") replay_task_command(task_id, trained_agents_file=trained_agents_file) except Exception as e: click.echo(f"An error occurred while replaying: {e}", err=True) @crewai.command() def log_tasks_outputs() -> None: """Retrieve your latest crew.kickoff() task outputs.""" try: from crewai_cli.task_outputs import load_task_outputs tasks = load_task_outputs() if not tasks: click.echo( "No task outputs found. Only crew kickoff task outputs are logged." ) return for index, task in enumerate(tasks, 1): click.echo(f"Task {index}: {task['task_id']}") click.echo(f"Description: {task['expected_output']}") click.echo("------") except Exception as e: click.echo(f"An error occurred while logging task outputs: {e}", err=True) @crewai.command() @click.option("-m", "--memory", is_flag=True, help="Reset MEMORY") @click.option( "-l", "--long", is_flag=True, hidden=True, help="[Deprecated: use --memory] Reset memory", ) @click.option( "-s", "--short", is_flag=True, hidden=True, help="[Deprecated: use --memory] Reset memory", ) @click.option( "-e", "--entities", is_flag=True, hidden=True, help="[Deprecated: use --memory] Reset memory", ) @click.option("-kn", "--knowledge", is_flag=True, help="Reset KNOWLEDGE storage") @click.option( "-akn", "--agent-knowledge", is_flag=True, help="Reset AGENT KNOWLEDGE storage" ) @click.option( "-k", "--kickoff-outputs", is_flag=True, help="Reset LATEST KICKOFF TASK OUTPUTS" ) @click.option("-a", "--all", is_flag=True, help="Reset ALL memories") def reset_memories( memory: bool, long: bool, short: bool, entities: bool, knowledge: bool, kickoff_outputs: bool, agent_knowledge: bool, all: bool, ) -> None: """Reset the crew memories (memory, knowledge, agent_knowledge, kickoff_outputs). This will delete all the data saved.""" try: if long or short or entities: legacy_used = [ f for f, v in [ ("--long", long), ("--short", short), ("--entities", entities), ] if v ] click.echo( f"Warning: {', '.join(legacy_used)} {'is' if len(legacy_used) == 1 else 'are'} " "deprecated. Use --memory (-m) instead. All memory is now unified." ) memory = True memory_types = [ memory, knowledge, agent_knowledge, kickoff_outputs, all, ] if not any(memory_types): click.echo( "Please specify at least one memory type to reset using the appropriate flags." ) return from crewai_cli.reset_memories_command import reset_memories_command reset_memories_command(memory, knowledge, agent_knowledge, kickoff_outputs, all) except Exception as e: click.echo(f"An error occurred while resetting memories: {e}", err=True) @crewai.command() @click.option( "--storage-path", type=str, default=None, help="Path to LanceDB memory directory. If omitted, uses ./.crewai/memory.", ) @click.option( "--embedder-provider", type=str, default=None, help="Embedder provider for recall queries (e.g. openai, google-vertex, cohere, ollama).", ) @click.option( "--embedder-model", type=str, default=None, help="Embedder model name (e.g. text-embedding-3-large, gemini-embedding-001).", ) @click.option( "--embedder-config", type=str, default=None, help='Full embedder config as JSON (e.g. \'{"provider": "cohere", "config": {"model_name": "embed-v4.0"}}\').', ) def memory( storage_path: str | None, embedder_provider: str | None, embedder_model: str | None, embedder_config: str | None, ) -> None: """Open the Memory TUI to browse scopes and recall memories.""" try: from crewai_cli.memory_tui import MemoryTUI except ImportError as exc: click.echo( "Textual is required for the memory TUI but could not be imported. " "Try reinstalling crewai or: pip install textual" ) raise SystemExit(1) from exc embedder_spec: dict[str, Any] | None = None if embedder_config: import json as _json try: embedder_spec = _json.loads(embedder_config) except _json.JSONDecodeError as exc: click.echo(f"Invalid --embedder-config JSON: {exc}") raise SystemExit(1) from exc elif embedder_provider: cfg: dict[str, str] = {} if embedder_model: cfg["model_name"] = embedder_model embedder_spec = {"provider": embedder_provider, "config": cfg} app = MemoryTUI(storage_path=storage_path, embedder_config=embedder_spec) app.run() @crewai.command() @click.option( "-n", "--n_iterations", type=int, default=3, help="Number of iterations to Test the crew", ) @click.option( "-m", "--model", type=str, default="gpt-5.4-mini", help="LLM Model to run the tests on the Crew. For now only accepting only OpenAI models.", ) @click.option( "-f", "--filename", "trained_agents_file", type=str, default=None, help=( "Crew-only: path to a trained-agents pickle (produced by `crewai train -f`). " "When set, agents load suggestions from this file instead of the " "default trained_agents_data.pkl. Equivalent to setting " "CREWAI_TRAINED_AGENTS_FILE." ), ) def test(n_iterations: int, model: str, trained_agents_file: str | None) -> None: """Test the crew and evaluate the results.""" click.echo(f"Testing the crew for {n_iterations} iterations with model {model}") evaluate_crew(n_iterations, model, trained_agents_file=trained_agents_file) @crewai.command( context_settings={ "ignore_unknown_options": True, "allow_extra_args": True, } ) @click.pass_context def install(context: click.Context) -> None: """Install the Crew.""" from crewai_cli.install_crew import install_crew install_crew(context.args) @crewai.command() @click.option( "-f", "--filename", "trained_agents_file", type=str, default=None, help=( "Path to a trained-agents pickle (produced by `crewai train -f`). " "When set, agents load suggestions from this file instead of the " "default trained_agents_data.pkl. Equivalent to setting " "CREWAI_TRAINED_AGENTS_FILE." ), ) @click.option( "--definition", type=str, default=None, help="Flow-only: path to a declarative flow definition.", ) @click.option( "--inputs", type=str, default=None, help='Flow-only: JSON object passed to the declarative flow, e.g. \'{"topic":"AI"}\'.', ) def run( trained_agents_file: str | None, definition: str | None, inputs: str | None, ) -> None: """Run the Crew or Flow.""" if inputs is not None and definition is None: raise click.UsageError("--inputs requires --definition") if trained_agents_file is not None and definition is not None: raise click.UsageError("--filename can only be used when running crews") run_crew( trained_agents_file=trained_agents_file, definition=definition, inputs=inputs, ) @crewai.command() def update() -> None: """Update the pyproject.toml of the Crew project to use uv.""" from crewai_cli.update_crew import update_crew update_crew() @crewai.command() def login() -> None: """Sign Up/Login to CrewAI AMP.""" Settings().clear_user_settings() AuthenticationCommand().login() @crewai.command() @click.option( "--reset", is_flag=True, help="Also reset all CLI configuration to defaults" ) def logout(reset: bool) -> None: """Logout from CrewAI AMP.""" settings = Settings() if reset: settings.reset() click.echo("Successfully logged out and reset all CLI configuration.") else: TokenManager().clear_tokens() settings.clear_user_settings() click.echo("Successfully logged out from CrewAI AMP.") @crewai.group() def deploy() -> None: """Deploy the Crew CLI group.""" @deploy.command(name="create") @click.option("-y", "--yes", is_flag=True, help="Skip the confirmation prompt") @click.option( "--skip-validate", is_flag=True, help="Skip the pre-deploy validation checks.", ) def deploy_create(yes: bool, skip_validate: bool) -> None: """Create a Crew deployment.""" deploy_cmd = DeployCommand() deploy_cmd.create_crew(yes, skip_validate=skip_validate) @deploy.command(name="list") def deploy_list() -> None: """List all deployments.""" deploy_cmd = DeployCommand() deploy_cmd.list_crews() @deploy.command(name="push") @click.option("-u", "--uuid", type=str, help="Crew UUID parameter") @click.option( "--skip-validate", is_flag=True, help="Skip the pre-deploy validation checks.", ) def deploy_push(uuid: str | None, skip_validate: bool) -> None: """Deploy the Crew.""" deploy_cmd = DeployCommand() deploy_cmd.deploy(uuid=uuid, skip_validate=skip_validate) @deploy.command(name="validate") def deploy_validate() -> None: """Validate the current project against common deployment failures. Runs the same pre-deploy checks that `crewai deploy create` and `crewai deploy push` run automatically, without contacting the platform. Exits non-zero if any blocking issues are found. """ from crewai_cli.deploy.validate import run_validate_command run_validate_command() @deploy.command(name="status") @click.option("-u", "--uuid", type=str, help="Crew UUID parameter") def deply_status(uuid: str | None) -> None: """Get the status of a deployment.""" deploy_cmd = DeployCommand() deploy_cmd.get_crew_status(uuid=uuid) @deploy.command(name="logs") @click.option("-u", "--uuid", type=str, help="Crew UUID parameter") def deploy_logs(uuid: str | None) -> None: """Get the logs of a deployment.""" deploy_cmd = DeployCommand() deploy_cmd.get_crew_logs(uuid=uuid) @deploy.command(name="remove") @click.option("-u", "--uuid", type=str, help="Crew UUID parameter") def deploy_remove(uuid: str | None) -> None: """Remove a deployment.""" deploy_cmd = DeployCommand() deploy_cmd.remove_crew(uuid=uuid) @crewai.group() def tool() -> None: """Tool Repository related commands.""" @tool.command(name="create") @click.argument("handle") def tool_create(handle: str) -> None: from crewai_cli.tools.main import ToolCommand tool_cmd = ToolCommand() tool_cmd.create(handle) @tool.command(name="install") @click.argument("handle") def tool_install(handle: str) -> None: from crewai_cli.tools.main import ToolCommand tool_cmd = ToolCommand() tool_cmd.login() tool_cmd.install(handle) @tool.command(name="publish") @click.option( "--force", is_flag=True, show_default=True, default=False, help="Bypasses Git remote validations", ) @click.option("--public", "is_public", flag_value=True, default=False) @click.option("--private", "is_public", flag_value=False) def tool_publish(is_public: bool, force: bool) -> None: from crewai_cli.tools.main import ToolCommand tool_cmd = ToolCommand() tool_cmd.login() tool_cmd.publish(is_public, force) @crewai.group() def experimental() -> None: """Experimental, unstable commands. Subject to change without notice.""" import os if os.environ.get("CREWAI_EXPERIMENTAL") != "1": raise click.UsageError( "Experimental commands are gated. Set CREWAI_EXPERIMENTAL=1 to enable." ) @experimental.group(name="skill") def skill() -> None: """Skill Repository related commands (experimental).""" @skill.command(name="create") @click.argument("name") @click.option( "--no-project", "in_project", is_flag=True, default=True, flag_value=False, help="Create skill in current dir instead of ./skills/", ) def skill_create(name: str, in_project: bool) -> None: from crewai_cli.experimental.skills.main import SkillCommand skill_cmd = SkillCommand() skill_cmd.create(name, in_project=in_project) @skill.command(name="install") @click.argument("ref") def skill_install(ref: str) -> None: from crewai_cli.experimental.skills.main import SkillCommand skill_cmd = SkillCommand() skill_cmd.install(ref) @skill.command(name="publish") @click.option( "--force", is_flag=True, default=False, show_default=True, help="Skip git-state validation.", ) @click.option("--public", "is_public", flag_value=True, default=False) @click.option("--private", "is_public", flag_value=False) @click.option("--org", default=None, help="Organisation slug (overrides settings).") def skill_publish(is_public: bool, org: str | None, force: bool) -> None: from crewai_cli.experimental.skills.main import SkillCommand skill_cmd = SkillCommand() skill_cmd.publish(is_public, org=org, force=force) @skill.command(name="list") def skill_list() -> None: """List locally installed skills.""" from crewai_cli.experimental.skills.main import SkillCommand skill_cmd = SkillCommand() skill_cmd.list_cached() @crewai.group() def template() -> None: """Browse and install project templates.""" @template.command(name="list") def template_list() -> None: """List available templates and select one to install.""" template_cmd = TemplateCommand() template_cmd.list_templates() @template.command(name="add") @click.argument("name") @click.option( "-o", "--output-dir", type=str, default=None, help="Directory name for the template (defaults to template name)", ) def template_add(name: str, output_dir: str | None) -> None: """Add a template to the current directory.""" template_cmd = TemplateCommand() template_cmd.add_template(name, output_dir) @crewai.group() def flow() -> None: """Flow related commands.""" @flow.command(name="kickoff") def flow_run() -> None: """Kickoff the Flow.""" click.secho( "The command 'crewai flow kickoff' is deprecated. Use 'crewai run' instead.", fg="yellow", ) run_crew(trained_agents_file=None, definition=None, inputs=None) @flow.command(name="plot") def flow_plot() -> None: """Plot the Flow.""" from crewai_cli.plot_flow import plot_flow click.echo("Plotting the Flow") plot_flow() @flow.command(name="add-crew") @click.argument("crew_name") def flow_add_crew(crew_name: str) -> None: """Add a crew to an existing flow.""" from crewai_cli.add_crew_to_flow import add_crew_to_flow click.echo(f"Adding crew {crew_name} to the flow") add_crew_to_flow(crew_name) @crewai.group() def triggers() -> None: """Trigger related commands. Use 'crewai triggers list' to see available triggers, or 'crewai triggers run app_slug/trigger_slug' to execute.""" @triggers.command(name="list") def triggers_list() -> None: """List all available triggers from integrations.""" from crewai_cli.triggers.main import TriggersCommand triggers_cmd = TriggersCommand() triggers_cmd.list_triggers() @triggers.command(name="run") @click.argument("trigger_path") def triggers_run(trigger_path: str) -> None: """Execute crew with trigger payload. Format: app_slug/trigger_slug""" from crewai_cli.triggers.main import TriggersCommand triggers_cmd = TriggersCommand() triggers_cmd.execute_with_trigger(trigger_path) @crewai.command() def chat() -> None: """Start a conversation with the Crew, collecting user-supplied inputs, and using the Chat LLM to generate responses. """ click.secho( "\nStarting a conversation with the Crew\nType 'exit' or Ctrl+C to quit.\n", ) from crewai_cli.crew_chat import run_chat run_chat() @crewai.group(invoke_without_command=True) def org() -> None: """Organization management commands.""" @org.command("list") def org_list() -> None: """List available organizations.""" org_command = OrganizationCommand() org_command.list() @org.command() @click.argument("id") def switch(id: str) -> None: """Switch to a specific organization.""" org_command = OrganizationCommand() org_command.switch(id) @org.command() def current() -> None: """Show current organization when 'crewai org' is called without subcommands.""" org_command = OrganizationCommand() org_command.current() @crewai.group() def enterprise() -> None: """Enterprise Configuration commands.""" @enterprise.command("configure") @click.argument("enterprise_url") def enterprise_configure(enterprise_url: str) -> None: """Configure CrewAI AMP OAuth2 settings from the provided Enterprise URL.""" from crewai_cli.enterprise.main import EnterpriseConfigureCommand enterprise_command = EnterpriseConfigureCommand() enterprise_command.configure(enterprise_url) @crewai.group() def config() -> None: """CLI Configuration commands.""" @config.command("list") def config_list() -> None: """List all CLI configuration parameters.""" from crewai_cli.settings.main import SettingsCommand config_command = SettingsCommand() config_command.list() @config.command("set") @click.argument("key") @click.argument("value") def config_set(key: str, value: str) -> None: """Set a CLI configuration parameter.""" from crewai_cli.settings.main import SettingsCommand config_command = SettingsCommand() config_command.set(key, value) @config.command("reset") def config_reset() -> None: """Reset all CLI configuration parameters to default values.""" from crewai_cli.settings.main import SettingsCommand config_command = SettingsCommand() config_command.reset_all_settings() @crewai.group() def env() -> None: """Environment variable commands.""" @env.command("view") def env_view() -> None: """View tracing-related environment variables.""" from pathlib import Path from rich.console import Console from rich.panel import Panel from rich.table import Table console = Console() env_file = Path(".env") env_file_exists = env_file.exists() table = Table(show_header=True, header_style="bold cyan", expand=True) table.add_column("Environment Variable", style="cyan", width=30) table.add_column("Value", style="white", width=20) table.add_column("Source", style="yellow", width=20) crewai_tracing = os.getenv("CREWAI_TRACING_ENABLED", "") if crewai_tracing: table.add_row( "CREWAI_TRACING_ENABLED", crewai_tracing, "Environment/Shell", ) else: table.add_row( "CREWAI_TRACING_ENABLED", "[dim]Not set[/dim]", "[dim]—[/dim]", ) crewai_testing = os.getenv("CREWAI_TESTING", "") if crewai_testing: table.add_row("CREWAI_TESTING", crewai_testing, "Environment/Shell") crewai_user_id = os.getenv("CREWAI_USER_ID", "") if crewai_user_id: table.add_row("CREWAI_USER_ID", crewai_user_id, "Environment/Shell") crewai_org_id = os.getenv("CREWAI_ORG_ID", "") if crewai_org_id: table.add_row("CREWAI_ORG_ID", crewai_org_id, "Environment/Shell") table.add_row( ".env file", "✅ Found" if env_file_exists else "❌ Not found", str(env_file.resolve()) if env_file_exists else "N/A", ) panel = Panel( table, title="Tracing Environment Variables", border_style="blue", padding=(1, 2), ) console.print("\n") console.print(panel) if env_file_exists: console.print( "\n[dim]💡 Tip: To enable tracing via .env, add: CREWAI_TRACING_ENABLED=true[/dim]" ) else: console.print( "\n[dim]💡 Tip: Create a .env file in your project root and add: CREWAI_TRACING_ENABLED=true[/dim]" ) console.print() @crewai.group() def traces() -> None: """Trace collection management commands.""" @traces.command("enable") def traces_enable() -> None: """Enable trace collection for crew/flow executions.""" from rich.console import Console from rich.panel import Panel console = Console() update_user_data({"trace_consent": True, "first_execution_done": True}) panel = Panel( "✅ Trace collection enabled.\n\n" "Your crew/flow executions will now send traces to CrewAI+.\n" "Use 'crewai traces disable' to opt out.", title="Traces Enabled", border_style="green", padding=(1, 2), ) console.print(panel) @traces.command("disable") def traces_disable() -> None: """Disable trace collection for crew/flow executions.""" from rich.console import Console from rich.panel import Panel console = Console() update_user_data({"trace_consent": False, "first_execution_done": True}) panel = Panel( "❌ Trace collection disabled.\n\n" "Your crew/flow executions will no longer send traces " "(unless [bold]CREWAI_TRACING_ENABLED=true[/bold] is set in the environment, " "which overrides the opt-out).\n" "Use 'crewai traces enable' to opt back in.", title="Traces Disabled", border_style="red", padding=(1, 2), ) console.print(panel) @traces.command("status") def traces_status() -> None: """Show current trace collection status.""" from rich.console import Console from rich.panel import Panel from rich.table import Table console = Console() user_data = _load_user_data() table = Table(show_header=False, box=None) table.add_column("Setting", style="cyan") table.add_column("Value", style="white") env_enabled = os.getenv("CREWAI_TRACING_ENABLED", "false") table.add_row("CREWAI_TRACING_ENABLED", env_enabled) trace_consent = user_data.get("trace_consent") if trace_consent is True: consent_status = "✅ Enabled (user consented)" elif trace_consent is False: consent_status = "❌ Disabled (user declined)" else: consent_status = "⚪ Not set (first-time user)" table.add_row("User Consent", consent_status) if is_tracing_enabled(): overall_status = "✅ ENABLED" border_style = "green" else: overall_status = "❌ DISABLED" border_style = "red" table.add_row("Overall Status", overall_status) panel = Panel( table, title="Trace Collection Status", border_style=border_style, padding=(1, 2), ) console.print(panel) @crewai.group(invoke_without_command=True) @click.option( "--location", default="./.checkpoints", help="Checkpoint directory or SQLite file." ) @click.pass_context def checkpoint(ctx: click.Context, location: str) -> None: """Browse and inspect checkpoints. Launches a TUI when called without a subcommand.""" from crewai_cli.checkpoint_cli import _detect_location location = _detect_location(location) ctx.ensure_object(dict) ctx.obj["location"] = location if ctx.invoked_subcommand is None: from crewai_cli.checkpoint_tui import run_checkpoint_tui run_checkpoint_tui(location) @checkpoint.command("list") @click.argument("location", default="./.checkpoints") def checkpoint_list(location: str) -> None: """List checkpoints in a directory.""" from crewai_cli.checkpoint_cli import _detect_location, list_checkpoints list_checkpoints(_detect_location(location)) @checkpoint.command("info") @click.argument("path", default="./.checkpoints") def checkpoint_info(path: str) -> None: """Show details of a checkpoint. Pass a file or directory for latest.""" from crewai_cli.checkpoint_cli import _detect_location, info_checkpoint info_checkpoint(_detect_location(path)) @checkpoint.command("resume") @click.argument("checkpoint_id", required=False, default=None) @click.pass_context def checkpoint_resume(ctx: click.Context, checkpoint_id: str | None) -> None: """Resume from a checkpoint. Defaults to the most recent.""" from crewai_cli.checkpoint_cli import resume_checkpoint resume_checkpoint(ctx.obj["location"], checkpoint_id) @checkpoint.command("diff") @click.argument("id1") @click.argument("id2") @click.pass_context def checkpoint_diff(ctx: click.Context, id1: str, id2: str) -> None: """Compare two checkpoints side-by-side.""" from crewai_cli.checkpoint_cli import diff_checkpoints diff_checkpoints(ctx.obj["location"], id1, id2) @checkpoint.command("prune") @click.option( "--keep", type=int, default=None, help="Keep the N most recent checkpoints." ) @click.option( "--older-than", default=None, help="Remove checkpoints older than duration (e.g. 7d, 24h, 30m).", ) @click.option( "--dry-run", is_flag=True, help="Show what would be pruned without deleting." ) @click.pass_context def checkpoint_prune( ctx: click.Context, keep: int | None, older_than: str | None, dry_run: bool ) -> None: """Remove old checkpoints.""" from crewai_cli.checkpoint_cli import prune_checkpoints prune_checkpoints(ctx.obj["location"], keep, older_than, dry_run) if __name__ == "__main__": crewai()