diff --git a/lib/cli/src/crewai_cli/user_data.py b/lib/cli/src/crewai_cli/user_data.py index 143ab9ddd..dba02658b 100644 --- a/lib/cli/src/crewai_cli/user_data.py +++ b/lib/cli/src/crewai_cli/user_data.py @@ -56,11 +56,16 @@ def _save_user_data(data: dict[str, Any]) -> None: def is_tracing_enabled() -> bool: - """Check if tracing is enabled (mirrors crewai core logic).""" + """Check if tracing is enabled. + + Returns True when the user has positively consented (e.g. via + ``crewai traces enable``), False when they have declined, and falls back + to the ``CREWAI_TRACING_ENABLED`` env var when consent is unset. + """ data = _load_user_data() - if ( - data.get("first_execution_done", False) - and data.get("trace_consent", False) is False - ): + trace_consent = data.get("trace_consent") + if trace_consent is True: + return True + if data.get("first_execution_done", False) and trace_consent is False: return False return os.getenv("CREWAI_TRACING_ENABLED", "false").lower() == "true" diff --git a/lib/cli/src/crewai_cli/utils.py b/lib/cli/src/crewai_cli/utils.py index 13215fe80..68b0232b1 100644 --- a/lib/cli/src/crewai_cli/utils.py +++ b/lib/cli/src/crewai_cli/utils.py @@ -1,7 +1,6 @@ from __future__ import annotations from functools import reduce -from inspect import getmro, isclass import os from pathlib import Path import shutil @@ -239,119 +238,6 @@ def write_env_file(folder_path: Path, env_vars: dict[str, Any]) -> None: file.write(f"{key.upper()}={value}\n") -def is_valid_tool(obj: Any) -> bool: - """Check if an object is a valid tool class. - - Works without importing crewai by checking MRO class names. - Falls back to crewai's ``is_valid_tool`` when available. - """ - try: - from crewai.utilities.project_utils import is_valid_tool as _core_is_valid_tool - - return _core_is_valid_tool(obj) - except ImportError: - pass - - if isclass(obj): - try: - return any(base.__name__ == "BaseTool" for base in getmro(obj)) - except (TypeError, AttributeError): - return False - return False - - -def extract_available_exports(dir_path: str = "src") -> list[dict[str, Any]]: - """Extract available tool classes from the project's __init__.py files.""" - try: - init_files = Path(dir_path).glob("**/__init__.py") - available_exports: list[dict[str, Any]] = [] - - for init_file in init_files: - tools = _load_tools_from_init(init_file) - available_exports.extend(tools) - - if not available_exports: - _print_no_tools_warning() - raise SystemExit(1) - - return available_exports - - except SystemExit: - raise - except Exception as e: - console.print(f"[red]Error: Could not extract tool classes: {e!s}[/red]") - console.print( - "Please ensure your project contains valid tools (classes inheriting from BaseTool or functions with @tool decorator)." - ) - raise SystemExit(1) from e - - -def _load_tools_from_init(init_file: Path) -> list[dict[str, Any]]: - """Load and validate tools from a given __init__.py file.""" - import importlib.util as _importlib_util - - spec = _importlib_util.spec_from_file_location("temp_module", init_file) - - if not spec or not spec.loader: - return [] - - module = _importlib_util.module_from_spec(spec) - sys.modules["temp_module"] = module - - try: - spec.loader.exec_module(module) - - if not hasattr(module, "__all__"): - console.print( - f"Warning: No __all__ defined in {init_file}", - style="bold yellow", - ) - raise SystemExit(1) - - return [ - {"name": name} - for name in module.__all__ - if hasattr(module, name) and is_valid_tool(getattr(module, name)) - ] - - except SystemExit: - raise - except Exception as e: - console.print(f"[red]Warning: Could not load {init_file}: {e!s}[/red]") - raise SystemExit(1) from e - - finally: - sys.modules.pop("temp_module", None) - - -def _print_no_tools_warning() -> None: - """Display warning and usage instructions if no tools were found.""" - console.print( - "\n[bold yellow]Warning: No valid tools were exposed in your __init__.py file![/bold yellow]" - ) - console.print( - "Your __init__.py file must contain all classes that inherit from [bold]BaseTool[/bold] " - "or functions decorated with [bold]@tool[/bold]." - ) - console.print( - "\nExample:\n[dim]# In your __init__.py file[/dim]\n" - "[green]__all__ = ['YourTool', 'your_tool_function'][/green]\n\n" - "[dim]# In your tool.py file[/dim]\n" - "[green]from crewai.tools import BaseTool, tool\n\n" - "# Tool class example\n" - "class YourTool(BaseTool):\n" - ' name = "your_tool"\n' - ' description = "Your tool description"\n' - " # ... rest of implementation\n\n" - "# Decorated function example\n" - "@tool\n" - "def your_tool_function(text: str) -> str:\n" - ' """Your tool description"""\n' - " # ... implementation\n" - " return result\n" - ) - - def build_env_with_tool_repository_credentials( repository_handle: str, ) -> dict[str, Any]: diff --git a/lib/cli/tests/test_utils.py b/lib/cli/tests/test_utils.py index 41cdaaa49..0e5695054 100644 --- a/lib/cli/tests/test_utils.py +++ b/lib/cli/tests/test_utils.py @@ -102,45 +102,6 @@ def test_tree_copy_to_existing_directory(temp_tree): shutil.rmtree(dest_dir) -@pytest.fixture -def temp_project_dir(): - """Create a temporary directory for testing tool extraction.""" - with tempfile.TemporaryDirectory() as temp_dir: - yield Path(temp_dir) - - -def create_init_file(directory, content): - return create_file(directory / "__init__.py", content) - - -def test_extract_available_exports_empty_project(temp_project_dir, capsys): - with pytest.raises(SystemExit): - utils.extract_available_exports(dir_path=temp_project_dir) - captured = capsys.readouterr() - - assert "No valid tools were exposed in your __init__.py file" in captured.out - - -def test_extract_available_exports_no_init_file(temp_project_dir, capsys): - (temp_project_dir / "some_file.py").write_text("print('hello')") - with pytest.raises(SystemExit): - utils.extract_available_exports(dir_path=temp_project_dir) - captured = capsys.readouterr() - - assert "No valid tools were exposed in your __init__.py file" in captured.out - - -def test_extract_available_exports_empty_init_file(temp_project_dir, capsys): - create_init_file(temp_project_dir, "") - with pytest.raises(SystemExit): - utils.extract_available_exports(dir_path=temp_project_dir) - captured = capsys.readouterr() - - assert "Warning: No __all__ defined in" in captured.out - - -# Tests for extract_available_exports with crewai.tools (BaseTool, @tool) -# remain in lib/crewai/tests/cli/test_utils.py as they require the crewai core package. - -# Tests for get_crews, get_flows, fetch_crews, is_valid_tool -# remain in lib/crewai/tests/cli/test_utils.py as they require the crewai core package. +# Tests for extract_available_exports, get_crews, get_flows, fetch_crews, +# is_valid_tool live in lib/crewai/tests/cli/test_utils.py — the canonical +# implementations are in crewai.utilities.project_utils.