Compare commits

...

1 Commits

Author SHA1 Message Date
Greyson LaLonde
bad64b1ee6 chore(cli): drop self-explanatory comments
Some checks are pending
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Vulnerability Scan / pip-audit (push) Waiting to run
2026-05-26 01:05:25 -07:00
30 changed files with 17 additions and 205 deletions

View File

@@ -8,7 +8,6 @@ from crewai_cli.utils import copy_template
def add_crew_to_flow(crew_name: str) -> None:
"""Add a new crew to the current flow."""
# Check if pyproject.toml exists in the current directory
if not Path("pyproject.toml").exists():
PRINTER.print(
"This command must be run from the root of a flow project.", color="red"
@@ -17,7 +16,6 @@ def add_crew_to_flow(crew_name: str) -> None:
"This command must be run from the root of a flow project."
)
# Determine the flow folder based on the current directory
flow_folder = Path.cwd()
crews_folder = flow_folder / "src" / flow_folder.name / "crews"
@@ -25,7 +23,6 @@ def add_crew_to_flow(crew_name: str) -> None:
PRINTER.print("Crews folder does not exist in the current flow.", color="red")
raise click.ClickException("Crews folder does not exist in the current flow.")
# Create the crew within the flow's crews directory
create_embedded_crew(crew_name, parent_folder=crews_folder)
click.echo(
@@ -51,13 +48,12 @@ def create_embedded_crew(crew_name: str, parent_folder: Path) -> None:
click.secho(f"Creating crew {folder_name}...", fg="green", bold=True)
crew_folder.mkdir(parents=True)
# Create config and crew.py files
config_folder = crew_folder / "config"
config_folder.mkdir(exist_ok=True)
templates_dir = Path(__file__).parent / "templates" / "crew"
config_template_files = ["agents.yaml", "tasks.yaml"]
crew_template_file = f"{folder_name}.py" # Updated file name
crew_template_file = f"{folder_name}.py"
for file_name in config_template_files:
src_file = templates_dir / "config" / file_name

View File

@@ -222,9 +222,6 @@ def _entity_summary(entities: list[dict[str, Any]]) -> str:
return ", ".join(parts) if parts else "empty"
# --- JSON directory ---
def _list_json(location: str) -> list[dict[str, Any]]:
pattern = os.path.join(location, "**", "*.json")
results = []
@@ -275,9 +272,6 @@ def _info_json_file(path: str) -> dict[str, Any]:
return meta
# --- SQLite ---
def _list_sqlite(db_path: str) -> list[dict[str, Any]]:
results = []
with sqlite3.connect(db_path) as conn:
@@ -327,9 +321,6 @@ def _info_sqlite_id(db_path: str, checkpoint_id: str) -> dict[str, Any] | None:
return meta
# --- Public API ---
def list_checkpoints(location: str) -> None:
"""List all checkpoints at a location."""
if _is_sqlite(location):
@@ -367,7 +358,6 @@ def info_checkpoint(path: str) -> None:
"""Show details of a single checkpoint."""
meta: dict[str, Any] | None = None
# db_path#checkpoint_id format
if "#" in path:
db_path, checkpoint_id = path.rsplit("#", 1)
if _is_sqlite(db_path):
@@ -376,7 +366,6 @@ def info_checkpoint(path: str) -> None:
click.echo(f"Checkpoint not found: {checkpoint_id}")
return
# SQLite file — show latest
if meta is None and _is_sqlite(path):
meta = _info_sqlite_latest(path)
if not meta:
@@ -384,7 +373,6 @@ def info_checkpoint(path: str) -> None:
return
click.echo(f"Latest checkpoint: {meta['name']}\n")
# Directory — show latest JSON
if meta is None and os.path.isdir(path):
meta = _info_json_latest(path)
if not meta:
@@ -392,7 +380,6 @@ def info_checkpoint(path: str) -> None:
return
click.echo(f"Latest checkpoint: {meta['name']}\n")
# Specific JSON file
if meta is None and os.path.isfile(path):
try:
meta = _info_json_file(path)

View File

@@ -320,8 +320,6 @@ class CheckpointTUI(App[_TuiResult]):
self._refresh_tree()
self.query_one("#tree-panel", Tree).root.expand()
# ── Tree building ──────────────────────────────────────────────
@staticmethod
def _top_level_entity(entry: dict[str, Any]) -> tuple[str, str]:
etype, ename = "unknown", ""
@@ -473,8 +471,6 @@ class CheckpointTUI(App[_TuiResult]):
self.sub_title = self._location
self.query_one("#status", Static).update(f" {count} checkpoint(s) | {storage}")
# ── Detail panel ───────────────────────────────────────────────
async def _clear_scroll(self, tab_id: str) -> VerticalScroll:
tab = self.query_one(f"#{tab_id}", TabPane)
scroll = tab.query_one(VerticalScroll)
@@ -661,8 +657,6 @@ class CheckpointTUI(App[_TuiResult]):
)
await scroll.mount(row)
# ── Data collection ────────────────────────────────────────────
def _collect_inputs(self) -> dict[str, Any] | None:
if not self._input_keys:
return None
@@ -699,8 +693,6 @@ class CheckpointTUI(App[_TuiResult]):
return f"{self._location}#{entry['name']}"
return str(entry.get("name", ""))
# ── Events ─────────────────────────────────────────────────────
async def on_tree_node_highlighted(
self, event: Tree.NodeHighlighted[dict[str, Any]]
) -> None:

View File

@@ -42,7 +42,6 @@ from crewai_cli.utils import build_env_with_all_tool_credentials, read_toml
def _get_cli_version() -> str:
"""Return the best available version string for the CLI."""
# Prefer crewai version if installed (keeps existing UX)
try:
return get_version("crewai")
except Exception: # noqa: S110
@@ -67,7 +66,6 @@ def crewai() -> None:
def uv(uv_args: tuple[str, ...]) -> None:
"""A wrapper around uv commands that adds custom tool authentication through env vars."""
try:
# Verify pyproject.toml exists first
read_toml()
except FileNotFoundError as e:
raise SystemExit(
@@ -321,7 +319,6 @@ def memory(
)
raise SystemExit(1) from exc
# Build embedder spec from CLI flags.
embedder_spec: dict[str, Any] | None = None
if embedder_config:
import json as _json
@@ -435,7 +432,6 @@ def logout(reset: bool) -> None:
click.echo("Successfully logged out from CrewAI AMP.")
# DEPLOY CREWAI+ COMMANDS
@crewai.group()
def deploy() -> None:
"""Deploy the Crew CLI group."""
@@ -766,17 +762,14 @@ def env_view() -> None:
console = Console()
# Check for .env file
env_file = Path(".env")
env_file_exists = env_file.exists()
# Create table for environment variables
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)
# Check CREWAI_TRACING_ENABLED
crewai_tracing = os.getenv("CREWAI_TRACING_ENABLED", "")
if crewai_tracing:
table.add_row(
@@ -791,7 +784,6 @@ def env_view() -> None:
"[dim]—[/dim]",
)
# Check other related env vars
crewai_testing = os.getenv("CREWAI_TESTING", "")
if crewai_testing:
table.add_row("CREWAI_TESTING", crewai_testing, "Environment/Shell")
@@ -804,7 +796,6 @@ def env_view() -> None:
if crewai_org_id:
table.add_row("CREWAI_ORG_ID", crewai_org_id, "Environment/Shell")
# Check if .env file exists
table.add_row(
".env file",
"✅ Found" if env_file_exists else "❌ Not found",
@@ -820,7 +811,6 @@ def env_view() -> None:
console.print("\n")
console.print(panel)
# Show helpful message
if env_file_exists:
console.print(
"\n[dim]💡 Tip: To enable tracing via .env, add: CREWAI_TRACING_ENABLED=true[/dim]"
@@ -896,11 +886,9 @@ def traces_status() -> None:
table.add_column("Setting", style="cyan")
table.add_column("Value", style="white")
# Check environment variable
env_enabled = os.getenv("CREWAI_TRACING_ENABLED", "false")
table.add_row("CREWAI_TRACING_ENABLED", env_enabled)
# Check user consent
trace_consent = user_data.get("trace_consent")
if trace_consent is True:
consent_status = "✅ Enabled (user consented)"
@@ -910,7 +898,6 @@ def traces_status() -> None:
consent_status = "⚪ Not set (first-time user)"
table.add_row("User Consent", consent_status)
# Check overall status
if is_tracing_enabled():
overall_status = "✅ ENABLED"
border_style = "green"

View File

@@ -50,7 +50,6 @@ def create_folder_structure(
folder_name = name.replace(" ", "_").replace("-", "_").lower()
folder_name = re.sub(r"[^a-zA-Z0-9_]", "", folder_name)
# Check if the name starts with invalid characters or is primarily invalid
if re.match(r"^[^a-zA-Z0-9_-]+", name):
raise ValueError(
f"Project name '{name}' contains no valid characters for a Python module name"
@@ -98,7 +97,6 @@ def create_folder_structure(
f"Project name '{name}' would generate class name '{class_name}' which cannot start with a digit"
)
# Check if the original name (before title casing) is a keyword
original_name_clean = re.sub(
r"[^a-zA-Z0-9_]", "", name.replace("_", "").replace("-", "").lower()
)
@@ -128,7 +126,7 @@ def create_folder_structure(
click.secho("Operation cancelled.", fg="yellow")
sys.exit(0)
click.secho(f"Overriding folder {folder_name}...", fg="green", bold=True)
shutil.rmtree(folder_path) # Delete the existing folder and its contents
shutil.rmtree(folder_path)
click.secho(
f"Creating {'crew' if parent_folder else 'folder'} {folder_name}...",
@@ -144,7 +142,6 @@ def create_folder_structure(
(folder_path / "src" / folder_name / "tools").mkdir(parents=True)
(folder_path / "src" / folder_name / "config").mkdir(parents=True)
# Copy AGENTS.md to project root (top-level projects only)
package_dir = Path(__file__).parent
agents_md_src = package_dir / "templates" / "AGENTS.md"
if agents_md_src.exists():
@@ -232,25 +229,22 @@ def create_crew(
while True:
selected_provider = select_provider(provider_models)
if selected_provider is None: # User typed 'q'
if selected_provider is None:
click.secho("Exiting...", fg="yellow")
sys.exit(0)
if selected_provider and isinstance(
selected_provider, str
): # Valid selection
if selected_provider and isinstance(selected_provider, str):
break
click.secho(
"No provider selected. Please try again or press 'q' to exit.", fg="red"
)
# Check if the selected provider has predefined models
if MODELS.get(selected_provider):
while True:
selected_model = select_model(selected_provider, provider_models)
if selected_model is None: # User typed 'q'
if selected_model is None:
click.secho("Exiting...", fg="yellow")
sys.exit(0)
if selected_model: # Valid selection
if selected_model:
break
click.secho(
"No model selected. Please try again or press 'q' to exit.",
@@ -258,17 +252,14 @@ def create_crew(
)
env_vars["MODEL"] = selected_model
# Check if the selected provider requires API keys
if selected_provider in ENV_VARS:
provider_env_vars = ENV_VARS[selected_provider]
for details in provider_env_vars:
if details.get("default", False):
# Automatically add default key-value pairs
for key, value in details.items():
if key not in ["prompt", "key_name", "default"]:
env_vars[key] = value
elif "key_name" in details:
# Prompt for non-default key-value pairs
prompt = details["prompt"]
key_name = details["key_name"]
api_key_value = click.prompt(prompt, default="", show_default=False)

View File

@@ -20,25 +20,21 @@ def create_flow(name: str) -> None:
telemetry = Telemetry()
telemetry.flow_creation_span(class_name)
# Create directory structure
(project_root / "src" / folder_name).mkdir(parents=True)
(project_root / "src" / folder_name / "crews").mkdir(parents=True)
(project_root / "src" / folder_name / "tools").mkdir(parents=True)
(project_root / "tests").mkdir(exist_ok=True)
# Create .env file
with open(project_root / ".env", "w") as file:
file.write("OPENAI_API_KEY=YOUR_API_KEY")
package_dir = Path(__file__).parent
templates_dir = package_dir / "templates" / "flow"
# Copy AGENTS.md to project root
agents_md_src = package_dir / "templates" / "AGENTS.md"
if agents_md_src.exists():
shutil.copy2(agents_md_src, project_root / "AGENTS.md")
# List of template files to copy
root_template_files = [".gitignore", "pyproject.toml", "README.md"]
src_template_files = ["__init__.py", "main.py"]
tools_template_files = ["tools/__init__.py", "tools/custom_tool.py"]
@@ -65,25 +61,21 @@ def create_flow(name: str) -> None:
with open(dst_file, "w") as file:
file.write(content)
# Copy and process root template files
for file_name in root_template_files:
src_file = templates_dir / file_name
dst_file = project_root / file_name
process_file(src_file, dst_file)
# Copy and process src template files
for file_name in src_template_files:
src_file = templates_dir / file_name
dst_file = project_root / "src" / folder_name / file_name
process_file(src_file, dst_file)
# Copy tools files
for file_name in tools_template_files:
src_file = templates_dir / file_name
dst_file = project_root / "src" / folder_name / file_name
process_file(src_file, dst_file)
# Copy crew folders
for crew_folder in crew_folders:
src_crew_folder = templates_dir / "crews" / crew_folder
dst_crew_folder = project_root / "src" / folder_name / "crews" / crew_folder

View File

@@ -74,7 +74,6 @@ class ValidationResult:
hint: str = ""
# Maps known provider env var names → label used in hint messages.
_KNOWN_API_KEY_HINTS: dict[str, str] = {
"OPENAI_API_KEY": "OpenAI",
"ANTHROPIC_API_KEY": "Anthropic",

View File

@@ -10,10 +10,9 @@ from textual.containers import Horizontal, Vertical
from textual.widgets import Footer, Header, Input, OptionList, Static, Tree
# -- CrewAI brand palette --
_PRIMARY = "#eb6658" # coral
_SECONDARY = "#1F7982" # teal
_TERTIARY = "#ffffff" # white
_PRIMARY = "#eb6658"
_SECONDARY = "#1F7982"
_TERTIARY = "#ffffff"
def _format_scope_info(info: Any) -> str:
@@ -193,8 +192,6 @@ class MemoryTUI(App[None]):
node = parent_node.add(label, data=child)
self._add_scope_children(node, child, depth + 1, max_depth)
# -- Populating the OptionList -------------------------------------------
def _populate_entry_list(self) -> None:
"""Clear the OptionList and fill it with the current scope's entries."""
option_list = self.query_one("#entry-list", OptionList)
@@ -226,8 +223,6 @@ class MemoryTUI(App[None]):
)
option_list.add_option(label)
# -- Detail rendering ----------------------------------------------------
def _format_record_detail(self, record: Any, context_line: str = "") -> str:
"""Format a full MemoryRecord as Rich markup for the detail view.
@@ -246,7 +241,6 @@ class MemoryTUI(App[None]):
lines.append(context_line)
lines.append("")
# -- Fields block --
lines.append(f"[dim]ID:[/] {record.id}")
lines.append(f"[dim]Scope:[/] [bold]{record.scope}[/]")
lines.append(f"[dim]Importance:[/] [bold]{record.importance:.2f}[/]")
@@ -264,12 +258,10 @@ class MemoryTUI(App[None]):
lines.append(f"[dim]Source:[/] {record.source or '-'}")
lines.append(f"[dim]Private:[/] {'Yes' if record.private else 'No'}")
# -- Content block --
lines.append(f"\n{sep}")
lines.append("[bold]Content[/]\n")
lines.append(record.content)
# -- Metadata block --
if record.metadata:
lines.append(f"\n{sep}")
lines.append("[bold]Metadata[/]\n")
@@ -278,8 +270,6 @@ class MemoryTUI(App[None]):
return "\n".join(lines)
# -- Event handlers ------------------------------------------------------
def on_tree_node_selected(self, event: Tree.NodeSelected[str]) -> None:
"""Load entries for the selected scope and populate the OptionList."""
path = event.node.data if event.node.data is not None else "/"

View File

@@ -68,12 +68,12 @@ def select_provider(provider_models: dict[str, list[str]]) -> str | None | bool:
provider = select_choice(
"Select a provider to set up:", [*predefined_providers, "other"]
)
if provider is None: # User typed 'q'
if provider is None:
return None
if provider == "other":
provider = select_choice("Select a provider from the full list:", all_providers)
if provider is None: # User typed 'q'
if provider is None:
return None
return provider.lower() if provider else False

View File

@@ -31,7 +31,6 @@ def run_crew(trained_agents_file: str | None = None) -> None:
min_required_version = "0.71.0"
pyproject_data = read_toml()
# Check for legacy poetry configuration
if pyproject_data.get("tool", {}).get("poetry") and (
version.parse(crewai_version) < version.parse(min_required_version)
):
@@ -41,14 +40,11 @@ def run_crew(trained_agents_file: str | None = None) -> None:
fg="red",
)
# Determine crew type
is_flow = pyproject_data.get("tool", {}).get("crewai", {}).get("type") == "flow"
crew_type = CrewType.FLOW if is_flow else CrewType.STANDARD
# Display appropriate message
click.echo(f"Running the {'Flow' if is_flow else 'Crew'}")
# Execute the appropriate command
execute_command(crew_type, trained_agents_file=trained_agents_file)

View File

@@ -28,10 +28,8 @@ class SettingsCommand(BaseCommand):
table.add_column("Value", style="green")
table.add_column("Description", style="yellow")
# Add all settings to the table
for field_name, field_info in Settings.model_fields.items():
if field_name in HIDDEN_SETTINGS_KEYS:
# Do not display hidden settings
continue
current_value = getattr(self.settings, field_name)
@@ -42,10 +40,8 @@ class SettingsCommand(BaseCommand):
table.add_row(field_name, display_value, description)
# Add trace-related settings from user data
user_data = _load_user_data()
# CREWAI_TRACING_ENABLED environment variable
env_tracing = os.getenv("CREWAI_TRACING_ENABLED", "")
env_tracing_display = env_tracing if env_tracing else "Not set"
table.add_row(
@@ -54,7 +50,6 @@ class SettingsCommand(BaseCommand):
"Environment variable to enable/disable tracing",
)
# Trace consent status
trace_consent = user_data.get("trace_consent")
if trace_consent is True:
consent_display = "✅ Enabled"
@@ -66,7 +61,6 @@ class SettingsCommand(BaseCommand):
"trace_consent", consent_display, "Whether trace collection is enabled"
)
# First execution timestamp
if user_data.get("first_execution_at"):
timestamp = datetime.fromtimestamp(user_data["first_execution_at"])
first_exec_display = timestamp.strftime("%Y-%m-%d %H:%M:%S")

View File

@@ -41,10 +41,6 @@ class SkillCommand(BaseCommand, PlusAPIMixin):
BaseCommand.__init__(self)
PlusAPIMixin.__init__(self, telemetry=self._telemetry)
# ------------------------------------------------------------------
# create
# ------------------------------------------------------------------
def create(self, name: str, in_project: bool = True) -> None:
"""Scaffold a new skill directory.
@@ -73,10 +69,6 @@ class SkillCommand(BaseCommand, PlusAPIMixin):
)
console.print(f"Edit [bold]{skill_md}[/bold] to define the skill instructions.")
# ------------------------------------------------------------------
# install
# ------------------------------------------------------------------
def install(self, ref: str) -> None:
"""Download and install a registry skill.
@@ -182,10 +174,6 @@ class SkillCommand(BaseCommand, PlusAPIMixin):
f"[green]Installed [bold]{ref}[/bold]{' (' + version + ')' if version else ''} to global cache.[/green]"
)
# ------------------------------------------------------------------
# publish
# ------------------------------------------------------------------
def publish(self, is_public: bool, org: str | None, force: bool = False) -> None:
"""Publish the skill in the current directory to the registry."""
skill_md = Path("SKILL.md")
@@ -196,7 +184,6 @@ class SkillCommand(BaseCommand, PlusAPIMixin):
)
raise SystemExit(1)
# Parse frontmatter to extract name + version
try:
frontmatter = self._parse_frontmatter(skill_md.read_text())
except ValueError as exc:
@@ -257,10 +244,6 @@ class SkillCommand(BaseCommand, PlusAPIMixin):
f"Monitor status at: {base_url}/crewai_plus/skills/{effective_org}/{name}[/green]"
)
# ------------------------------------------------------------------
# list_cached
# ------------------------------------------------------------------
def list_cached(self) -> None:
"""Show locally installed skills."""
table = Table(title="Installed Skills", show_lines=True)
@@ -269,7 +252,6 @@ class SkillCommand(BaseCommand, PlusAPIMixin):
table.add_column("Version")
table.add_column("Path")
# Project-local ./skills/
local_skills_dir = Path("skills")
if local_skills_dir.is_dir():
for skill_dir in sorted(local_skills_dir.iterdir()):
@@ -282,7 +264,6 @@ class SkillCommand(BaseCommand, PlusAPIMixin):
str(skill_dir),
)
# Global cache
cache_root = Path.home() / ".crewai" / "skills"
if cache_root.exists():
for org_dir in sorted(cache_root.iterdir()):
@@ -306,10 +287,6 @@ class SkillCommand(BaseCommand, PlusAPIMixin):
console.print(table)
# ------------------------------------------------------------------
# internal helpers
# ------------------------------------------------------------------
def _print_current_organization(self) -> None:
settings = Settings()
if settings.org_uuid:
@@ -326,7 +303,6 @@ class SkillCommand(BaseCommand, PlusAPIMixin):
def _unpack_archive(self, archive_bytes: bytes, dest: Path) -> None:
"""Unpack a .tar.gz or .zip archive into dest."""
# Try tar first, then zip
try:
with tarfile.open(fileobj=io.BytesIO(archive_bytes), mode="r:gz") as tf:
try:
@@ -337,7 +313,6 @@ class SkillCommand(BaseCommand, PlusAPIMixin):
except tarfile.TarError:
pass
# Fallback: zip
with zipfile.ZipFile(io.BytesIO(archive_bytes)) as zf:
_safe_extract_zip(zf, dest)

View File

@@ -1,9 +1,7 @@
from crewai import Agent, Crew, Process, Task
from crewai.project import CrewBase, agent, crew, task
from crewai.agents.agent_builder.base_agent import BaseAgent
# If you want to run a snippet of code before or after the crew starts,
# you can use the @before_kickoff and @after_kickoff decorators
# https://docs.crewai.com/concepts/crews#example-crew-class-with-decorators
@CrewBase
class {{crew_name}}():
@@ -12,12 +10,6 @@ class {{crew_name}}():
agents: list[BaseAgent]
tasks: list[Task]
# Learn more about YAML configuration files here:
# Agents: https://docs.crewai.com/concepts/agents#yaml-configuration-recommended
# Tasks: https://docs.crewai.com/concepts/tasks#yaml-configuration-recommended
# If you would like to add tools to your agents, you can learn more about it here:
# https://docs.crewai.com/concepts/agents#agent-tools
@agent
def researcher(self) -> Agent:
return Agent(
@@ -32,9 +24,6 @@ class {{crew_name}}():
verbose=True
)
# To learn more about structured task outputs,
# task dependencies, and task callbacks, check out the documentation:
# https://docs.crewai.com/concepts/tasks#overview-of-a-task
@task
def research_task(self) -> Task:
return Task(
@@ -51,13 +40,9 @@ class {{crew_name}}():
@crew
def crew(self) -> Crew:
"""Creates the {{crew_name}} crew"""
# To learn how to add knowledge sources to your crew, check out the documentation:
# https://docs.crewai.com/concepts/knowledge#what-is-knowledge
return Crew(
agents=self.agents, # Automatically created by the @agent decorator
tasks=self.tasks, # Automatically created by the @task decorator
agents=self.agents,
tasks=self.tasks,
process=Process.sequential,
verbose=True,
# process=Process.hierarchical, # In case you wanna use that instead https://docs.crewai.com/how-to/Hierarchical/
)

View File

@@ -8,10 +8,6 @@ from {{folder_name}}.crew import {{crew_name}}
warnings.filterwarnings("ignore", category=SyntaxWarning, module="pysbd")
# This main file is intended to be a way for you to run your
# crew locally, so refrain from adding unnecessary logic into this file.
# Replace with inputs you want to test with, it will automatically
# interpolate any tasks and agents information
def run():
"""

View File

@@ -15,5 +15,4 @@ class MyCustomTool(BaseTool):
args_schema: Type[BaseModel] = MyCustomToolInput
def _run(self, argument: str) -> str:
# Implementation goes here
return "this is an example of a tool output, ignore it and move along."

View File

@@ -2,10 +2,6 @@ from crewai import Agent, Crew, Process, Task
from crewai.agents.agent_builder.base_agent import BaseAgent
from crewai.project import CrewBase, agent, crew, task
# If you want to run a snippet of code before or after the crew starts,
# you can use the @before_kickoff and @after_kickoff decorators
# https://docs.crewai.com/concepts/crews#example-crew-class-with-decorators
@CrewBase
class ContentCrew:
@@ -14,14 +10,9 @@ class ContentCrew:
agents: list[BaseAgent]
tasks: list[Task]
# Learn more about YAML configuration files here:
# Agents: https://docs.crewai.com/concepts/agents#yaml-configuration-recommended
# Tasks: https://docs.crewai.com/concepts/tasks#yaml-configuration-recommended
agents_config = "config/agents.yaml"
tasks_config = "config/tasks.yaml"
# If you would like to add tools to your crew, you can learn more about it here:
# https://docs.crewai.com/concepts/agents#agent-tools
@agent
def planner(self) -> Agent:
return Agent(
@@ -40,9 +31,6 @@ class ContentCrew:
config=self.agents_config["editor"], # type: ignore[index]
)
# To learn more about structured task outputs,
# task dependencies, and task callbacks, check out the documentation:
# https://docs.crewai.com/concepts/tasks#overview-of-a-task
@task
def planning_task(self) -> Task:
return Task(
@@ -64,12 +52,9 @@ class ContentCrew:
@crew
def crew(self) -> Crew:
"""Creates the Content Crew"""
# To learn how to add knowledge sources to your crew, check out the documentation:
# https://docs.crewai.com/concepts/knowledge#what-is-knowledge
return Crew(
agents=self.agents, # Automatically created by the @agent decorator
tasks=self.tasks, # Automatically created by the @task decorator
agents=self.agents,
tasks=self.tasks,
process=Process.sequential,
verbose=True,
)

View File

@@ -68,7 +68,6 @@ def run_with_trigger():
import json
import sys
# Get trigger payload from command line argument
if len(sys.argv) < 2:
raise Exception("No trigger payload provided. Please provide JSON payload as argument.")
@@ -77,8 +76,6 @@ def run_with_trigger():
except json.JSONDecodeError:
raise Exception("Invalid JSON payload provided as argument")
# Create flow and kickoff with trigger payload
# The @start() methods will automatically receive crewai_trigger_payload parameter
content_flow = ContentFlow()
try:

View File

@@ -17,5 +17,4 @@ class MyCustomTool(BaseTool):
args_schema: Type[BaseModel] = MyCustomToolInput
def _run(self, argument: str) -> str:
# Implementation goes here
return "this is an example of a tool output, ignore it and move along."

View File

@@ -6,5 +6,4 @@ class {{class_name}}(BaseTool):
description: str = "What this tool does. It's vital for effective utilization."
def _run(self, argument: str) -> str:
# Your tool's logic here
return "Tool's result"

View File

@@ -82,7 +82,6 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
tree_find_and_replace(project_root, "{{folder_name}}", folder_name)
tree_find_and_replace(project_root, "{{class_name}}", class_name)
# Copy AGENTS.md to project root
agents_md_src = Path(__file__).parent.parent / "templates" / "AGENTS.md"
if agents_md_src.exists():
shutil.copy2(agents_md_src, project_root / "AGENTS.md")

View File

@@ -37,7 +37,6 @@ class TriggersCommand(BaseCommand, PlusAPIMixin):
def execute_with_trigger(self, trigger_path: str) -> None:
"""Execute crew with trigger payload."""
try:
# Parse app_slug/trigger_slug
if "/" not in trigger_path:
console.print(
"[bold red]Error: Trigger must be in format 'app_slug/trigger_slug'[/bold red]"
@@ -63,7 +62,6 @@ class TriggersCommand(BaseCommand, PlusAPIMixin):
trigger_data = response.json()
self._display_trigger_info(trigger_data)
# Run crew with trigger payload
self._run_crew_with_payload(trigger_data.get("sample_payload", {}))
except Exception as e:

View File

@@ -21,7 +21,6 @@ def migrate_pyproject(input_file: str, output_file: str) -> None:
When the time comes that uv supports the new format, this function will be deprecated.
"""
poetry_data = {}
# Read the input pyproject.toml
pyproject_data = read_toml()
new_pyproject: dict[str, Any] = {
@@ -29,7 +28,6 @@ def migrate_pyproject(input_file: str, output_file: str) -> None:
"build-system": {"requires": ["hatchling"], "build-backend": "hatchling.build"},
}
# Migrate project metadata
if "tool" in pyproject_data and "poetry" in pyproject_data["tool"]:
poetry_data = pyproject_data["tool"]["poetry"]
new_pyproject["project"]["name"] = poetry_data.get("name")
@@ -44,18 +42,15 @@ def migrate_pyproject(input_file: str, output_file: str) -> None:
]
new_pyproject["project"]["requires-python"] = poetry_data.get("python")
else:
# If it's already in the new format, just copy the project and tool sections
new_pyproject["project"] = pyproject_data.get("project", {})
new_pyproject["tool"] = pyproject_data.get("tool", {})
# Migrate or copy dependencies
if "dependencies" in new_pyproject["project"]:
# If dependencies are already in the new format, keep them as is
pass
elif poetry_data and "dependencies" in poetry_data:
new_pyproject["project"]["dependencies"] = []
for dep, version in poetry_data["dependencies"].items():
if isinstance(version, dict): # Handle extras
if isinstance(version, dict):
extras = ",".join(version.get("extras", []))
new_dep = f"{dep}[{extras}]"
if "version" in version:
@@ -67,7 +62,6 @@ def migrate_pyproject(input_file: str, output_file: str) -> None:
new_dep = f"{dep}{parse_version(version)}"
new_pyproject["project"]["dependencies"].append(new_dep)
# Migrate or copy scripts
if poetry_data and "scripts" in poetry_data:
new_pyproject["project"]["scripts"] = poetry_data["scripts"]
elif pyproject_data.get("project", {}) and "scripts" in pyproject_data["project"]:
@@ -79,7 +73,6 @@ def migrate_pyproject(input_file: str, output_file: str) -> None:
"run_crew" not in new_pyproject["project"]["scripts"]
and len(new_pyproject["project"]["scripts"]) > 0
):
# Extract the module name from any existing script
existing_scripts = new_pyproject["project"]["scripts"]
module_name = next(
(value.split(".")[0] for value in existing_scripts.values() if "." in value)
@@ -87,15 +80,12 @@ def migrate_pyproject(input_file: str, output_file: str) -> None:
new_pyproject["project"]["scripts"]["run_crew"] = f"{module_name}.main:run"
# Migrate optional dependencies
if poetry_data and "extras" in poetry_data:
new_pyproject["project"]["optional-dependencies"] = poetry_data["extras"]
# Backup the old pyproject.toml
backup_file = "pyproject-old.toml"
shutil.copy2(input_file, backup_file)
# Rename the poetry.lock file
lock_file = "poetry.lock"
lock_backup = "poetry-old.lock"
if os.path.exists(lock_file):
@@ -103,7 +93,6 @@ def migrate_pyproject(input_file: str, output_file: str) -> None:
else:
pass
# Write the new pyproject.toml
with open(output_file, "wb") as f:
tomli_w.dump(new_pyproject, f)

View File

@@ -333,7 +333,6 @@ class TestAuthenticationCommand:
@patch("crewai_core.auth.oauth2.httpx.post")
def test_poll_for_token_error(self, mock_post):
"""Test the method to poll for token (error path)."""
# Setup mock to return error
mock_response_error = MagicMock()
mock_response_error.status_code = 400
mock_response_error.json.return_value = {

View File

@@ -12,7 +12,6 @@ class TestUtils(unittest.TestCase):
def test_validate_jwt_token(self, mock_jwt, mock_pyjwkclient):
mock_jwt.decode.return_value = {"exp": 1719859200}
# Create signing key object mock with a .key attribute
mock_pyjwkclient.return_value.get_signing_key_from_jwt.return_value = MagicMock(
key="mock_signing_key"
)

View File

@@ -164,7 +164,6 @@ def test_poetry_lock_is_accepted(tmp_path: Path) -> None:
def test_stale_lockfile_warns(tmp_path: Path) -> None:
_scaffold_standard_crew(tmp_path)
# Make lockfile older than pyproject.
lock = tmp_path / "uv.lock"
pyproject = tmp_path / "pyproject.toml"
old_time = pyproject.stat().st_mtime - 60

View File

@@ -41,14 +41,9 @@ def skill_command():
yield cmd
# ---------------------------------------------------------------------------
# create
# ---------------------------------------------------------------------------
class TestSkillCreate:
def test_create_in_project(self, skill_command, tmp_path):
with in_temp_dir():
# Simulate being inside a project
Path("pyproject.toml").write_text("[tool.poetry]\nname = 'test'\n")
skill_command.create("my-skill")
assert Path("skills/my-skill/SKILL.md").exists()
@@ -75,10 +70,6 @@ class TestSkillCreate:
skill_command.create("existing-skill", in_project=False)
# ---------------------------------------------------------------------------
# install
# ---------------------------------------------------------------------------
class TestSkillInstall:
def _zip_skill(self, name: str) -> bytes:
buf = io.BytesIO()
@@ -118,10 +109,6 @@ class TestSkillInstall:
assert Path("skills/my-skill/SKILL.md").exists()
# ---------------------------------------------------------------------------
# publish
# ---------------------------------------------------------------------------
class TestSkillPublish:
def test_publish_no_skill_md(self, skill_command):
with in_temp_dir():
@@ -155,7 +142,6 @@ class TestSkillPublish:
mock_resp.status_code = 200
mock_resp.json.return_value = {}
mock_client.publish_skill.return_value = mock_resp
# No org set → should SystemExit (no org_name in settings)
with patch("crewai_cli.skills.main.Settings") as mock_settings_cls:
mock_settings_cls.return_value.org_name = None
mock_settings_cls.return_value.enterprise_base_url = None
@@ -184,15 +170,10 @@ class TestSkillPublish:
assert call_kwargs.kwargs["version"] == "1.0.0"
# ---------------------------------------------------------------------------
# list_cached
# ---------------------------------------------------------------------------
class TestSkillListCached:
def test_list_cached_empty(self, skill_command, capsys):
with in_temp_dir():
skill_command.list_cached()
# Should not raise
def test_list_cached_shows_project_skills(self, skill_command, capsys):
with in_temp_dir():
@@ -202,4 +183,3 @@ class TestSkillListCached:
"---\nname: my-skill\nversion: 0.5.0\ndescription: A skill.\n---\nBody."
)
skill_command.list_cached()
# Should complete without error

View File

@@ -83,7 +83,6 @@ def test_test_crew_called_process_error(mock_subprocess_run, click):
@mock.patch("crewai_cli.evaluate_crew.click")
@mock.patch("crewai_cli.evaluate_crew.subprocess.run")
def test_test_crew_unexpected_exception(mock_subprocess_run, click):
# Arrange
n_iterations = 5
mock_subprocess_run.side_effect = Exception("Unexpected error")
evaluate_crew.evaluate_crew(n_iterations, "gpt-4o")

View File

@@ -35,7 +35,6 @@ class TestSettingsCommand(unittest.TestCase):
self.settings_command.list()
# Tests that the table is created skipping hidden settings
mock_table_instance.add_row.assert_has_calls(
[
call(
@@ -48,7 +47,6 @@ class TestSettingsCommand(unittest.TestCase):
]
)
# Tests that the table is printed
mock_console.print.assert_called_once_with(mock_table_instance)
def test_set_valid_keys(self):

View File

@@ -146,7 +146,6 @@ class TestAtomicFileOperations(unittest.TestCase):
self.temp_dir = tempfile.mkdtemp()
self.original_get_path = TokenManager._get_secure_storage_path
# Patch to use temp directory
def mock_get_path() -> Path:
return Path(self.temp_dir)
@@ -182,7 +181,6 @@ class TestAtomicFileOperations(unittest.TestCase):
mock_get_key.return_value = Fernet.generate_key()
tm = TokenManager()
# Create file first
file_path = Path(self.temp_dir) / "test.txt"
file_path.write_bytes(b"original")
@@ -231,7 +229,6 @@ class TestAtomicFileOperations(unittest.TestCase):
tm._atomic_write_secure_file("test.txt", b"content")
# Check no temp files remain
temp_files = list(Path(self.temp_dir).glob(".test.txt.*"))
self.assertEqual(len(temp_files), 0)
@@ -285,7 +282,6 @@ class TestAtomicFileOperations(unittest.TestCase):
mock_get_key.return_value = Fernet.generate_key()
tm = TokenManager()
# Should not raise
tm._delete_secure_file("nonexistent.txt")

View File

@@ -27,9 +27,7 @@ def in_temp_dir():
@pytest.fixture
def tool_command():
# Create a temporary directory for each test to avoid token storage conflicts
with tempfile.TemporaryDirectory() as temp_dir:
# Mock the secure storage path to use the temp directory
with patch.object(
TokenManager, "_get_secure_storage_path", return_value=Path(temp_dir)
):
@@ -97,7 +95,6 @@ def test_install_success(
env=unittest.mock.ANY,
)
# Verify _print_current_organization was called
mock_print_org.assert_called_once()