mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-05-06 17:52:35 +00:00
refactor(core): extract crewai-core for shared utilities and standalone CLI
- New lib/crewai-core/ package: version, paths, constants, lock_store, user_data,
printer, telemetry. Pure leaf — depends only on appdirs/portalocker/rich/otel.
- crewai now depends on crewai-core; old crewai.utilities.{version,paths,printer,
lock_store} and the user-data block of events/listeners/tracing/utils.py become
one-shot DeprecationWarning shims that re-export from crewai_core.
- crewai-cli drops its hard dep on crewai and depends only on crewai-core. CLI
imports for telemetry/version/printer/constants now point at crewai_core.
- tools/main.py lazy-imports project_utils + get_user_id; the publish/login
subcommands print a friendly "requires crewai" error if it's missing.
- crewai-cli is now genuinely standalone: 'crewai --help', 'version', 'login',
'config', 'traces', 'create', 'template' all work without crewai installed.
- 351 CLI tests + 9 crewai-core smoke tests + crewai's full mypy (471 files) clean.
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
# crewai-cli
|
||||
|
||||
CLI for CrewAI — scaffold, run, deploy and manage AI agent crews.
|
||||
|
||||
The CLI depends on the `crewai` framework and pulls it in automatically.
|
||||
CLI for CrewAI — scaffold, run, deploy and manage AI agent crews without
|
||||
installing the full framework.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -10,7 +9,17 @@ The CLI depends on the `crewai` framework and pulls it in automatically.
|
||||
pip install crewai-cli
|
||||
```
|
||||
|
||||
Or install via the framework's extra:
|
||||
This pulls in `crewai-core` (shared utilities) but not the `crewai` framework
|
||||
itself, so commands that don't need a crew loaded — `crewai version`,
|
||||
`crewai login`, `crewai org list`, `crewai config *`, `crewai traces *`,
|
||||
`crewai create`, `crewai template *` — work standalone.
|
||||
|
||||
Commands that load a user's crew or flow (`crewai run`, `crewai train`,
|
||||
`crewai test`, `crewai chat`, `crewai replay`, `crewai reset-memories`,
|
||||
`crewai deploy push`, `crewai tool publish`) require `crewai` to be installed
|
||||
in the project's environment. They print a clear error if it is missing.
|
||||
|
||||
To install both at once:
|
||||
|
||||
```bash
|
||||
pip install crewai[cli]
|
||||
|
||||
@@ -8,7 +8,7 @@ authors = [
|
||||
]
|
||||
requires-python = ">=3.10, <3.14"
|
||||
dependencies = [
|
||||
"crewai>=1.14.5a2",
|
||||
"crewai-core>=1.14.5a2",
|
||||
"click~=8.1.7",
|
||||
"pydantic>=2.11.9,<2.13",
|
||||
"pydantic-settings~=2.10.1",
|
||||
@@ -22,7 +22,6 @@ dependencies = [
|
||||
"packaging>=23.0",
|
||||
"python-dotenv>=1.2.2,<2",
|
||||
"uv~=0.9.13",
|
||||
"portalocker~=2.7.0",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from pathlib import Path
|
||||
|
||||
import click
|
||||
from crewai.utilities.printer import PRINTER
|
||||
from crewai_core.printer import PRINTER
|
||||
|
||||
from crewai_cli.utils import copy_template
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from crewai.telemetry.telemetry import Telemetry
|
||||
from crewai_core.telemetry import Telemetry
|
||||
import httpx
|
||||
from rich.console import Console
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ from pathlib import Path
|
||||
import shutil
|
||||
|
||||
import click
|
||||
from crewai.telemetry import Telemetry
|
||||
from crewai_core.telemetry import Telemetry
|
||||
|
||||
|
||||
def create_flow(name: str) -> None:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import subprocess
|
||||
|
||||
import click
|
||||
from crewai.utilities.constants import CREWAI_TRAINED_AGENTS_FILE_ENV
|
||||
from crewai_core.constants import CREWAI_TRAINED_AGENTS_FILE_ENV
|
||||
|
||||
from crewai_cli.utils import build_env_with_all_tool_credentials
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import subprocess
|
||||
|
||||
import click
|
||||
from crewai.utilities.constants import CREWAI_TRAINED_AGENTS_FILE_ENV
|
||||
from crewai_core.constants import CREWAI_TRAINED_AGENTS_FILE_ENV
|
||||
|
||||
from crewai_cli.utils import build_env_with_all_tool_credentials
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ from enum import Enum
|
||||
import subprocess
|
||||
|
||||
import click
|
||||
from crewai.utilities.constants import CREWAI_TRAINED_AGENTS_FILE_ENV
|
||||
from crewai_core.constants import CREWAI_TRAINED_AGENTS_FILE_ENV
|
||||
from packaging import version
|
||||
|
||||
from crewai_cli.utils import build_env_with_all_tool_credentials, read_toml
|
||||
|
||||
@@ -8,11 +8,6 @@ import tempfile
|
||||
from typing import Any
|
||||
|
||||
import click
|
||||
from crewai.events.listeners.tracing.utils import get_user_id
|
||||
from crewai.utilities.project_utils import (
|
||||
extract_available_exports,
|
||||
extract_tools_metadata,
|
||||
)
|
||||
from rich.console import Console
|
||||
|
||||
from crewai_cli import git
|
||||
@@ -33,6 +28,32 @@ from crewai_cli.utils import (
|
||||
console = Console()
|
||||
|
||||
|
||||
_REQUIRES_CREWAI_MSG = (
|
||||
"[red]This subcommand requires the full crewai package.\n"
|
||||
"Install it with: pip install crewai[/red]"
|
||||
)
|
||||
|
||||
|
||||
def _require_project_utils() -> Any:
|
||||
try:
|
||||
from crewai.utilities import project_utils
|
||||
|
||||
return project_utils
|
||||
except ImportError:
|
||||
console.print(_REQUIRES_CREWAI_MSG)
|
||||
raise SystemExit(1) from None
|
||||
|
||||
|
||||
def _require_get_user_id() -> Any:
|
||||
try:
|
||||
from crewai.events.listeners.tracing.utils import get_user_id
|
||||
|
||||
return get_user_id
|
||||
except ImportError:
|
||||
console.print(_REQUIRES_CREWAI_MSG)
|
||||
raise SystemExit(1) from None
|
||||
|
||||
|
||||
class ToolCommand(BaseCommand, PlusAPIMixin):
|
||||
"""
|
||||
A class to handle tool repository related operations for CrewAI projects.
|
||||
@@ -99,7 +120,8 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
|
||||
encoded_tarball = None
|
||||
|
||||
console.print("[bold blue]Discovering tools from your project...[/bold blue]")
|
||||
available_exports = extract_available_exports()
|
||||
project_utils = _require_project_utils()
|
||||
available_exports = project_utils.extract_available_exports()
|
||||
|
||||
if available_exports:
|
||||
console.print(
|
||||
@@ -108,7 +130,7 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
|
||||
|
||||
console.print("[bold blue]Extracting tool metadata...[/bold blue]")
|
||||
try:
|
||||
tools_metadata = extract_tools_metadata()
|
||||
tools_metadata = project_utils.extract_tools_metadata()
|
||||
except Exception as e:
|
||||
console.print(
|
||||
f"[yellow]Warning: Could not extract tool metadata: {e}[/yellow]\n"
|
||||
@@ -202,6 +224,7 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
|
||||
console.print(f"Successfully installed {handle}", style="bold green")
|
||||
|
||||
def login(self) -> None:
|
||||
get_user_id = _require_get_user_id()
|
||||
login_response = self.plus_api_client.login_to_tool_repository(
|
||||
user_identifier=get_user_id()
|
||||
)
|
||||
|
||||
@@ -1,71 +1,22 @@
|
||||
"""Standalone user-data helpers for the CLI package.
|
||||
|
||||
These mirror the functions in ``crewai.events.listeners.tracing.utils`` but
|
||||
depend only on the standard library + *appdirs* so that crewai-cli can work
|
||||
without importing the full crewai framework.
|
||||
"""
|
||||
"""User-data helpers — re-exported from ``crewai_core.user_data``."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any, cast
|
||||
|
||||
import appdirs
|
||||
from crewai_core.paths import db_storage_path as _db_storage_path
|
||||
from crewai_core.user_data import (
|
||||
_load_user_data as _load_user_data,
|
||||
_save_user_data as _save_user_data,
|
||||
has_user_declined_tracing as has_user_declined_tracing,
|
||||
is_tracing_enabled as is_tracing_enabled,
|
||||
update_user_data as update_user_data,
|
||||
)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _get_project_directory_name() -> str:
|
||||
return os.environ.get("CREWAI_STORAGE_DIR", Path.cwd().name)
|
||||
|
||||
|
||||
def _db_storage_path() -> str:
|
||||
app_name = _get_project_directory_name()
|
||||
app_author = "CrewAI"
|
||||
data_dir = Path(appdirs.user_data_dir(app_name, app_author))
|
||||
data_dir.mkdir(parents=True, exist_ok=True)
|
||||
return str(data_dir)
|
||||
|
||||
|
||||
def _user_data_file() -> Path:
|
||||
base = Path(_db_storage_path())
|
||||
base.mkdir(parents=True, exist_ok=True)
|
||||
return base / ".crewai_user.json"
|
||||
|
||||
|
||||
def _load_user_data() -> dict[str, Any]:
|
||||
p = _user_data_file()
|
||||
if p.exists():
|
||||
try:
|
||||
return cast(dict[str, Any], json.loads(p.read_text()))
|
||||
except (json.JSONDecodeError, OSError, PermissionError) as e:
|
||||
logger.warning("Failed to load user data: %s", e)
|
||||
return {}
|
||||
|
||||
|
||||
def _save_user_data(data: dict[str, Any]) -> None:
|
||||
try:
|
||||
p = _user_data_file()
|
||||
p.write_text(json.dumps(data, indent=2))
|
||||
except (OSError, PermissionError) as e:
|
||||
logger.warning("Failed to save user data: %s", e)
|
||||
|
||||
|
||||
def is_tracing_enabled() -> bool:
|
||||
"""Check if tracing is enabled.
|
||||
|
||||
Mirrors ``crewai.events.listeners.tracing.utils.is_tracing_enabled``:
|
||||
consent only *blocks* tracing; activation requires
|
||||
``CREWAI_TRACING_ENABLED=true``.
|
||||
"""
|
||||
data = _load_user_data()
|
||||
if (
|
||||
data.get("first_execution_done", False)
|
||||
and data.get("trace_consent", False) is False
|
||||
):
|
||||
return False
|
||||
return os.getenv("CREWAI_TRACING_ENABLED", "false").lower() == "true"
|
||||
__all__ = [
|
||||
"_db_storage_path",
|
||||
"_load_user_data",
|
||||
"_save_user_data",
|
||||
"has_user_declined_tracing",
|
||||
"is_tracing_enabled",
|
||||
"update_user_data",
|
||||
]
|
||||
|
||||
@@ -10,7 +10,7 @@ from urllib import request
|
||||
from urllib.error import URLError
|
||||
|
||||
import appdirs
|
||||
from crewai.utilities.version import get_crewai_version as get_crewai_version
|
||||
from crewai_core.version import get_crewai_version as get_crewai_version
|
||||
from packaging.version import InvalidVersion, Version, parse
|
||||
|
||||
|
||||
|
||||
@@ -184,11 +184,11 @@ def test_publish_when_not_in_sync(mock_is_synced, mock_fetch, capsys, tool_comma
|
||||
@patch("crewai_cli.plus_api.PlusAPI.publish_tool")
|
||||
@patch("crewai_cli.tools.main.git.Repository.is_synced", return_value=False)
|
||||
@patch(
|
||||
"crewai_cli.tools.main.extract_available_exports",
|
||||
"crewai.utilities.project_utils.extract_available_exports",
|
||||
return_value=[{"name": "SampleTool"}],
|
||||
)
|
||||
@patch(
|
||||
"crewai_cli.tools.main.extract_tools_metadata",
|
||||
"crewai.utilities.project_utils.extract_tools_metadata",
|
||||
return_value=[{"name": "SampleTool", "humanized_name": "sample_tool", "description": "A sample tool", "run_params_schema": {}, "init_params_schema": {}, "env_vars": []}],
|
||||
)
|
||||
@patch("crewai_cli.tools.main.ToolCommand._print_current_organization")
|
||||
@@ -250,11 +250,11 @@ def test_publish_when_not_in_sync_and_force(
|
||||
@patch("crewai_cli.plus_api.PlusAPI.publish_tool")
|
||||
@patch("crewai_cli.tools.main.git.Repository.is_synced", return_value=True)
|
||||
@patch(
|
||||
"crewai_cli.tools.main.extract_available_exports",
|
||||
"crewai.utilities.project_utils.extract_available_exports",
|
||||
return_value=[{"name": "SampleTool"}],
|
||||
)
|
||||
@patch(
|
||||
"crewai_cli.tools.main.extract_tools_metadata",
|
||||
"crewai.utilities.project_utils.extract_tools_metadata",
|
||||
return_value=[{"name": "SampleTool", "humanized_name": "sample_tool", "description": "A sample tool", "run_params_schema": {}, "init_params_schema": {}, "env_vars": []}],
|
||||
)
|
||||
def test_publish_success(
|
||||
@@ -311,11 +311,11 @@ def test_publish_success(
|
||||
)
|
||||
@patch("crewai_cli.plus_api.PlusAPI.publish_tool")
|
||||
@patch(
|
||||
"crewai_cli.tools.main.extract_available_exports",
|
||||
"crewai.utilities.project_utils.extract_available_exports",
|
||||
return_value=[{"name": "SampleTool"}],
|
||||
)
|
||||
@patch(
|
||||
"crewai_cli.tools.main.extract_tools_metadata",
|
||||
"crewai.utilities.project_utils.extract_tools_metadata",
|
||||
return_value=[{"name": "SampleTool", "humanized_name": "sample_tool", "description": "A sample tool", "run_params_schema": {}, "init_params_schema": {}, "env_vars": []}],
|
||||
)
|
||||
def test_publish_failure(
|
||||
@@ -357,11 +357,11 @@ def test_publish_failure(
|
||||
)
|
||||
@patch("crewai_cli.plus_api.PlusAPI.publish_tool")
|
||||
@patch(
|
||||
"crewai_cli.tools.main.extract_available_exports",
|
||||
"crewai.utilities.project_utils.extract_available_exports",
|
||||
return_value=[{"name": "SampleTool"}],
|
||||
)
|
||||
@patch(
|
||||
"crewai_cli.tools.main.extract_tools_metadata",
|
||||
"crewai.utilities.project_utils.extract_tools_metadata",
|
||||
return_value=[{"name": "SampleTool", "humanized_name": "sample_tool", "description": "A sample tool", "run_params_schema": {}, "init_params_schema": {}, "env_vars": []}],
|
||||
)
|
||||
def test_publish_api_error(
|
||||
@@ -404,11 +404,11 @@ def test_publish_api_error(
|
||||
@patch("crewai_cli.plus_api.PlusAPI.publish_tool")
|
||||
@patch("crewai_cli.tools.main.git.Repository.is_synced", return_value=True)
|
||||
@patch(
|
||||
"crewai_cli.tools.main.extract_available_exports",
|
||||
"crewai.utilities.project_utils.extract_available_exports",
|
||||
return_value=[{"name": "SampleTool"}],
|
||||
)
|
||||
@patch(
|
||||
"crewai_cli.tools.main.extract_tools_metadata",
|
||||
"crewai.utilities.project_utils.extract_tools_metadata",
|
||||
side_effect=Exception("Failed to extract metadata"),
|
||||
)
|
||||
def test_publish_metadata_extraction_failure_continues_with_warning(
|
||||
|
||||
Reference in New Issue
Block a user